@tothalex/nulljs 0.0.60 → 0.0.62

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 (2) hide show
  1. package/dist/cli.js +1232 -651
  2. package/package.json +4 -4
package/dist/cli.js CHANGED
@@ -12,93 +12,37 @@ var __export = (target, all) => {
12
12
  };
13
13
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
14
14
  var __require = import.meta.require;
15
- // ../../packages/api/types/index.ts
16
- var init_types = () => {};
17
15
 
18
- // ../../packages/api/actions/client.ts
19
- var buildUrl = (path, options) => {
20
- let fullPath = options?.url ? `${options.url}${path}` : path;
21
- if (options?.query) {
22
- const params = [];
23
- for (const [key, value] of Object.entries(options.query)) {
24
- if (value === undefined || value === null)
25
- continue;
26
- if (Array.isArray(value)) {
27
- params.push(`${key}=${value.join(",")}`);
28
- } else {
29
- params.push(`${key}=${encodeURIComponent(String(value))}`);
30
- }
31
- }
32
- const queryString = params.join("&");
33
- if (queryString) {
34
- fullPath = `${fullPath}?${queryString}`;
16
+ // src/lib/paths.ts
17
+ import { existsSync } from "fs";
18
+ import { join, dirname } from "path";
19
+ var findProjectRoot = (startPath = process.cwd()) => {
20
+ let currentPath = startPath;
21
+ while (currentPath !== dirname(currentPath)) {
22
+ if (existsSync(join(currentPath, "package.json"))) {
23
+ return currentPath;
35
24
  }
25
+ currentPath = dirname(currentPath);
36
26
  }
37
- return fullPath;
38
- }, apiGet = async (path, options) => {
39
- const url = options?.url ? `${options.url}${path}` : path;
40
- const headers = {};
41
- if (options?.publicKey) {
42
- headers["x-public-key"] = options.publicKey;
43
- }
44
- const response = await fetch(url, { headers });
45
- if (!response.ok) {
46
- const message = await response.text().catch(() => "");
47
- throw new Error(`HTTP error! status: ${response.status}${message ? ` message: ${message}` : ""}`);
48
- }
49
- return response.json();
50
- };
51
-
52
- // ../../packages/api/actions/deployments.ts
53
- var init_deployments = () => {};
54
-
55
- // ../../packages/api/actions/invocations.ts
56
- var init_invocations = () => {};
57
-
58
- // ../../packages/api/actions/logs.ts
59
- var getSystemLogs = async (public_key, query, url) => {
60
- const path = buildUrl("/api/system/logs", { url, query });
61
- return apiGet(path, { publicKey: public_key });
62
- }, searchDeploymentLogs = async (public_key, props, url) => {
63
- const path = buildUrl("/api/logs/search", { url, query: props });
64
- return apiGet(path, { publicKey: public_key });
27
+ return startPath;
28
+ }, getCloudPath = () => {
29
+ return join(findProjectRoot(), ".nulljs");
65
30
  };
66
- var init_logs = () => {};
67
-
68
- // ../../packages/api/actions/assets.ts
69
- var init_assets = () => {};
70
-
71
- // ../../packages/api/actions/activity.ts
72
- var init_activity = () => {};
73
-
74
- // ../../packages/api/actions/index.ts
75
- var init_actions = __esm(() => {
76
- init_deployments();
77
- init_invocations();
78
- init_logs();
79
- init_assets();
80
- init_activity();
81
- });
82
-
83
- // ../../packages/api/index.ts
84
- var init_api = __esm(() => {
85
- init_types();
86
- init_actions();
87
- });
31
+ var init_paths = () => {};
88
32
 
89
33
  // src/config/index.ts
90
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
91
- import { join } from "path";
34
+ import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync, readdirSync, rmSync } from "fs";
35
+ import { join as join2 } from "path";
92
36
  import chalk from "chalk";
93
- var getLocalConfigDir = () => join(process.cwd(), ".nulljs"), getLocalConfigFile = () => join(getLocalConfigDir(), "config.json"), ensureLocalConfigDir = () => {
37
+ var getLocalConfigDir = () => getCloudPath(), getLocalConfigFile = () => join2(getLocalConfigDir(), "config.json"), ensureLocalConfigDir = () => {
94
38
  const localDir = getLocalConfigDir();
95
- if (!existsSync(localDir)) {
39
+ if (!existsSync2(localDir)) {
96
40
  mkdirSync(localDir, { recursive: true });
97
41
  }
98
42
  }, readConfigStore = () => {
99
43
  try {
100
44
  const localFile = getLocalConfigFile();
101
- if (!existsSync(localFile)) {
45
+ if (!existsSync2(localFile)) {
102
46
  return null;
103
47
  }
104
48
  const data = readFileSync(localFile, "utf-8");
@@ -202,46 +146,59 @@ var getLocalConfigDir = () => join(process.cwd(), ".nulljs"), getLocalConfigFile
202
146
  }, loadPrivateKey = async (config) => {
203
147
  const privateKeyBytes = Uint8Array.from(atob(config.key.private), (c) => c.charCodeAt(0));
204
148
  return crypto.subtle.importKey("pkcs8", privateKeyBytes, { name: "Ed25519", namedCurve: "Ed25519" }, false, ["sign"]);
149
+ }, cleanLocalData = () => {
150
+ const localDir = getLocalConfigDir();
151
+ const deleted = [];
152
+ const preserved = [];
153
+ if (!existsSync2(localDir)) {
154
+ return { deleted, preserved };
155
+ }
156
+ const entries = readdirSync(localDir);
157
+ for (const entry of entries) {
158
+ if (entry === "config.json") {
159
+ preserved.push(entry);
160
+ continue;
161
+ }
162
+ const entryPath = join2(localDir, entry);
163
+ try {
164
+ rmSync(entryPath, { recursive: true, force: true });
165
+ deleted.push(entry);
166
+ } catch {}
167
+ }
168
+ return { deleted, preserved };
205
169
  };
206
- var init_config = () => {};
170
+ var init_config = __esm(() => {
171
+ init_paths();
172
+ });
207
173
 
208
174
  // src/lib/server.ts
209
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, realpathSync } from "fs";
210
- import { join as join2, dirname } from "path";
175
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, realpathSync } from "fs";
176
+ import { join as join3, dirname as dirname2 } from "path";
211
177
  import chalk2 from "chalk";
212
178
  var CLI_DIR, PLATFORMS, getPlatformKey = () => {
213
179
  return `${process.platform}-${process.arch}`;
214
- }, findProjectRoot = (startPath = process.cwd()) => {
215
- let currentPath = startPath;
216
- while (currentPath !== dirname(currentPath)) {
217
- if (existsSync2(join2(currentPath, "package.json"))) {
218
- return currentPath;
219
- }
220
- currentPath = dirname(currentPath);
221
- }
222
- return startPath;
223
180
  }, findMonorepoRoot = (startPath = process.cwd()) => {
224
181
  let currentPath = startPath;
225
- while (currentPath !== dirname(currentPath)) {
226
- if (existsSync2(join2(currentPath, "Cargo.toml")) && existsSync2(join2(currentPath, "package.json"))) {
182
+ while (currentPath !== dirname2(currentPath)) {
183
+ if (existsSync3(join3(currentPath, "Cargo.toml")) && existsSync3(join3(currentPath, "package.json"))) {
227
184
  return currentPath;
228
185
  }
229
- currentPath = dirname(currentPath);
186
+ currentPath = dirname2(currentPath);
230
187
  }
231
188
  return null;
232
189
  }, getLocalBinaryPath = () => {
233
190
  const envBinary = process.env.NULLJS_SERVER_BINARY;
234
- if (envBinary && existsSync2(envBinary)) {
191
+ if (envBinary && existsSync3(envBinary)) {
235
192
  return { path: envBinary, source: "local-env" };
236
193
  }
237
194
  const monorepoRoot = findMonorepoRoot() || findMonorepoRoot(CLI_DIR);
238
195
  if (monorepoRoot) {
239
- const debugBinary = join2(monorepoRoot, "target", "debug", "server");
240
- if (existsSync2(debugBinary)) {
196
+ const debugBinary = join3(monorepoRoot, "target", "debug", "server");
197
+ if (existsSync3(debugBinary)) {
241
198
  return { path: debugBinary, source: "local-debug" };
242
199
  }
243
- const releaseBinary = join2(monorepoRoot, "target", "release", "server");
244
- if (existsSync2(releaseBinary)) {
200
+ const releaseBinary = join3(monorepoRoot, "target", "release", "server");
201
+ if (existsSync3(releaseBinary)) {
245
202
  return { path: releaseBinary, source: "local-release" };
246
203
  }
247
204
  }
@@ -262,10 +219,9 @@ var CLI_DIR, PLATFORMS, getPlatformKey = () => {
262
219
  const args = ["--dev"];
263
220
  args.push("--api-port", "3000");
264
221
  args.push("--gateway-port", "3001");
265
- const projectRoot = findProjectRoot();
266
- const cloudPath = join2(projectRoot, ".nulljs");
222
+ const cloudPath = getCloudPath();
267
223
  args.push("--cloud-path", cloudPath);
268
- if (!existsSync2(cloudPath)) {
224
+ if (!existsSync3(cloudPath)) {
269
225
  mkdirSync2(cloudPath, { recursive: true });
270
226
  }
271
227
  args.push("--public-key", devConfig.key.public);
@@ -307,7 +263,8 @@ var CLI_DIR, PLATFORMS, getPlatformKey = () => {
307
263
  };
308
264
  var init_server = __esm(() => {
309
265
  init_config();
310
- CLI_DIR = dirname(realpathSync(import.meta.dir));
266
+ init_paths();
267
+ CLI_DIR = dirname2(realpathSync(import.meta.dir));
311
268
  PLATFORMS = {
312
269
  "linux-x64": "@tothalex/nulljs-linux-x64",
313
270
  "linux-arm64": "@tothalex/nulljs-linux-arm64",
@@ -515,9 +472,9 @@ var init_bundle = __esm(() => {
515
472
  });
516
473
 
517
474
  // ../../packages/api/actions/http.ts
518
- import { mkdtempSync, writeFileSync as writeFileSync2, rmSync } from "fs";
475
+ import { mkdtempSync, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "fs";
519
476
  import { tmpdir } from "os";
520
- import { join as join3 } from "path";
477
+ import { join as join4 } from "path";
521
478
  var {$ } = globalThis.Bun;
522
479
  var parseCurlResponse = (result) => {
523
480
  const lines = result.trim().split(`
@@ -558,15 +515,15 @@ var parseCurlResponse = (result) => {
558
515
  const url = options?.baseUrl ? `${options.baseUrl}${path}` : path;
559
516
  const headers = options?.headers || {};
560
517
  if (options?.useCurl) {
561
- const tempDir = mkdtempSync(join3(tmpdir(), "nulljs-http-"));
562
- const bodyFile = join3(tempDir, "body.json");
518
+ const tempDir = mkdtempSync(join4(tmpdir(), "nulljs-http-"));
519
+ const bodyFile = join4(tempDir, "body.json");
563
520
  try {
564
521
  writeFileSync2(bodyFile, body);
565
522
  const headerFlags = buildHeaderFlags(headers);
566
523
  const result = await $`curl -s -w '\n%{http_code}' -X POST ${headerFlags} -d @${bodyFile} ${url}`.text();
567
524
  return parseCurlResponse(result);
568
525
  } finally {
569
- rmSync(tempDir, { recursive: true, force: true });
526
+ rmSync2(tempDir, { recursive: true, force: true });
570
527
  }
571
528
  }
572
529
  const response = await fetch(url, {
@@ -583,11 +540,11 @@ var parseCurlResponse = (result) => {
583
540
  const url = options?.baseUrl ? `${options.baseUrl}${path}` : path;
584
541
  const headers = options?.headers || {};
585
542
  if (options?.useCurl) {
586
- const tempDir = mkdtempSync(join3(tmpdir(), "nulljs-http-"));
543
+ const tempDir = mkdtempSync(join4(tmpdir(), "nulljs-http-"));
587
544
  try {
588
545
  const formParts = [];
589
546
  for (const file of files) {
590
- const filePath = join3(tempDir, file.fileName);
547
+ const filePath = join4(tempDir, file.fileName);
591
548
  writeFileSync2(filePath, file.content);
592
549
  formParts.push("-F");
593
550
  formParts.push(`${file.fileName}=@${filePath};type=${file.mimeType}`);
@@ -596,7 +553,7 @@ var parseCurlResponse = (result) => {
596
553
  const result = await $`curl -s -w '\n%{http_code}' -X POST ${headerFlags} ${formParts} ${url}`.text();
597
554
  return parseCurlResponse(result);
598
555
  } finally {
599
- rmSync(tempDir, { recursive: true, force: true });
556
+ rmSync2(tempDir, { recursive: true, force: true });
600
557
  }
601
558
  }
602
559
  const form = new FormData;
@@ -629,10 +586,10 @@ var getMimeType = (fileName) => {
629
586
  if (fileName.endsWith(".json"))
630
587
  return "application/json";
631
588
  return "application/octet-stream";
632
- }, assetsToFileData = (assets3) => {
589
+ }, assetsToFileData = (assets) => {
633
590
  const files = [];
634
591
  const blobs = [];
635
- for (const asset of assets3) {
592
+ for (const asset of assets) {
636
593
  const mimeType = getMimeType(asset.fileName);
637
594
  const buffer = Buffer.from(asset.code);
638
595
  files.push({
@@ -652,10 +609,10 @@ var getMimeType = (fileName) => {
652
609
  const dataString = sortedBlobs.map((b) => `${b.fileName}:${b.size}:${b.type}`).join("|");
653
610
  const sign = await crypto.subtle.sign("Ed25519", privateKey, Buffer.from(dataString));
654
611
  return btoa(String.fromCharCode(...new Uint8Array(sign)));
655
- }, createFunctionDeployment = async (deployment2, options) => {
656
- const handler = deployment2.assets.find((asset) => asset.fileName === "handler.js");
612
+ }, createFunctionDeployment = async (deployment, options) => {
613
+ const handler = deployment.assets.find((asset) => asset.fileName === "handler.js");
657
614
  if (!handler) {
658
- throw new Error(`Handler not found for ${deployment2.name}`);
615
+ throw new Error(`Handler not found for ${deployment.name}`);
659
616
  }
660
617
  const { files, blobs } = assetsToFileData([handler]);
661
618
  const signature = await createSignature(blobs, options.privateKey);
@@ -671,8 +628,8 @@ var getMimeType = (fileName) => {
671
628
  if (!result.ok) {
672
629
  throw new Error(`Deployment failed (${result.status}): ${result.body}`);
673
630
  }
674
- }, createReactDeployment = async (deployment2, options) => {
675
- const { files, blobs } = assetsToFileData(deployment2.assets);
631
+ }, createReactDeployment = async (deployment, options) => {
632
+ const { files, blobs } = assetsToFileData(deployment.assets);
676
633
  const signature = await createSignature(blobs, options.privateKey);
677
634
  const baseUrl = options.url || "";
678
635
  const path = "/api/deployment/react";
@@ -722,21 +679,21 @@ var deployedHashes, clearDeployCache = () => {
722
679
  }, deployReact = async (filePath, privateKey, apiUrl, useCurl) => {
723
680
  const fileName = basename2(filePath);
724
681
  const result = await build(spaClientConfig(filePath));
725
- const assets3 = [];
682
+ const assets = [];
726
683
  for (const output of result.output) {
727
684
  if (output.type === "chunk") {
728
- assets3.push({ fileName: output.fileName, code: output.code });
685
+ assets.push({ fileName: output.fileName, code: output.code });
729
686
  } else if (output.type === "asset" && typeof output.source === "string") {
730
- assets3.push({ fileName: output.fileName, code: output.source });
687
+ assets.push({ fileName: output.fileName, code: output.source });
731
688
  }
732
689
  }
733
- const combinedCode = assets3.map((a) => a.code).join("");
690
+ const combinedCode = assets.map((a) => a.code).join("");
734
691
  const hash = hashCode(combinedCode);
735
692
  const lastHash = deployedHashes.get(filePath);
736
693
  if (lastHash === hash) {
737
694
  return { deployed: false, skipped: true };
738
695
  }
739
- await createReactDeployment({ name: fileName, assets: assets3 }, { privateKey, url: apiUrl, useCurl });
696
+ await createReactDeployment({ name: fileName, assets }, { privateKey, url: apiUrl, useCurl });
740
697
  deployedHashes.set(filePath, hash);
741
698
  return { deployed: true, skipped: false };
742
699
  };
@@ -747,21 +704,21 @@ var init_deploy2 = __esm(() => {
747
704
  });
748
705
 
749
706
  // src/lib/watcher.ts
750
- import { watch, existsSync as existsSync3 } from "fs";
707
+ import { watch, existsSync as existsSync4 } from "fs";
751
708
  import { readdir } from "fs/promises";
752
- import { join as join4 } from "path";
709
+ import { join as join5 } from "path";
753
710
  import { build as build2 } from "vite";
754
711
  var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
755
712
  return Bun.hash(code).toString(16);
756
713
  }, findFunctionEntries = async (functionsPath) => {
757
714
  const entries = [];
758
715
  for (const dir of FUNCTION_DIRS) {
759
- const dirPath = join4(functionsPath, dir);
716
+ const dirPath = join5(functionsPath, dir);
760
717
  try {
761
718
  const items = await readdir(dirPath, { withFileTypes: true });
762
719
  for (const item of items) {
763
720
  if (item.isFile() && (item.name.endsWith(".ts") || item.name.endsWith(".tsx"))) {
764
- const fullPath = join4(dirPath, item.name);
721
+ const fullPath = join5(dirPath, item.name);
765
722
  const name = `${dir}/${item.name.replace(/\.tsx?$/, "")}`;
766
723
  entries.push({
767
724
  name,
@@ -781,7 +738,7 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
781
738
  console.error('No local configuration found. Run "nulljs dev" first.');
782
739
  return () => {};
783
740
  }
784
- const functionsPath = join4(srcPath, "function");
741
+ const functionsPath = join5(srcPath, "function");
785
742
  let entries = await findFunctionEntries(functionsPath);
786
743
  let viteWatcher = null;
787
744
  let isRestarting = false;
@@ -852,8 +809,8 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
852
809
  viteWatcher = await startViteWatcher();
853
810
  const dirWatchers = [];
854
811
  for (const dir of FUNCTION_DIRS) {
855
- const dirPath = join4(functionsPath, dir);
856
- if (!existsSync3(dirPath))
812
+ const dirPath = join5(functionsPath, dir);
813
+ if (!existsSync4(dirPath))
857
814
  continue;
858
815
  try {
859
816
  const watcher = watch(dirPath, async (eventType, filename) => {
@@ -861,10 +818,10 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
861
818
  return;
862
819
  if (!filename.endsWith(".ts") && !filename.endsWith(".tsx"))
863
820
  return;
864
- const fullPath = join4(dirPath, filename);
821
+ const fullPath = join5(dirPath, filename);
865
822
  const entryName = `${dir}/${filename.replace(/\.tsx?$/, "")}`;
866
823
  const existingEntry = entries.find((e) => e.name === entryName);
867
- const fileExists = existsSync3(fullPath);
824
+ const fileExists = existsSync4(fullPath);
868
825
  if (!existingEntry && fileExists || existingEntry && !fileExists) {
869
826
  await restartViteWatcher();
870
827
  }
@@ -881,23 +838,25 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
881
838
  }
882
839
  };
883
840
  }, forceDeployAll = async (srcPath, options = {}) => {
884
- const { onDeploy, onComplete } = options;
841
+ const { onStart, onDeploy, onComplete } = options;
885
842
  const config = readLocalConfig();
886
843
  if (!config) {
887
844
  throw new Error('No local configuration found. Run "nulljs dev" first.');
888
845
  }
889
846
  const privateKey = await loadPrivateKey(config);
890
847
  deployedHashes2.clear();
891
- const functionsPath = join4(srcPath, "function");
848
+ const functionsPath = join5(srcPath, "function");
892
849
  const entries = await findFunctionEntries(functionsPath);
893
- const reactEntry = join4(srcPath, "index.tsx");
894
- const hasReact = existsSync3(reactEntry);
895
- let total = entries.length + (hasReact ? 1 : 0);
850
+ const reactEntry = join5(srcPath, "index.tsx");
851
+ const hasReact = existsSync4(reactEntry);
852
+ const total = entries.length + (hasReact ? 1 : 0);
896
853
  let successful = 0;
897
854
  let failed = 0;
855
+ let completed = 0;
898
856
  const { deployFunction: deployFunction2, deployReact: deployReact2, clearDeployCache: clearDeployCache2 } = await Promise.resolve().then(() => (init_deploy2(), exports_deploy));
899
857
  clearDeployCache2();
900
858
  for (const entry of entries) {
859
+ onStart?.(entry.name, completed, total);
901
860
  try {
902
861
  await deployFunction2(entry.path, privateKey, config.api);
903
862
  successful++;
@@ -906,9 +865,11 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
906
865
  failed++;
907
866
  onDeploy?.(entry.name, false, error instanceof Error ? error.message : String(error));
908
867
  }
868
+ completed++;
909
869
  }
910
870
  if (hasReact) {
911
871
  const fileName = "index.tsx";
872
+ onStart?.(fileName, completed, total);
912
873
  try {
913
874
  await deployReact2(reactEntry, privateKey, config.api);
914
875
  successful++;
@@ -917,6 +878,7 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
917
878
  failed++;
918
879
  onDeploy?.(fileName, false, error instanceof Error ? error.message : String(error));
919
880
  }
881
+ completed++;
920
882
  }
921
883
  onComplete?.(total, successful, failed);
922
884
  };
@@ -930,11 +892,11 @@ var init_watcher = __esm(() => {
930
892
 
931
893
  // src/lib/vite.ts
932
894
  import { createServer } from "vite";
933
- import { dirname as dirname2, resolve, basename as basename3 } from "path";
895
+ import { dirname as dirname3, resolve, basename as basename3 } from "path";
934
896
  import react3 from "@vitejs/plugin-react";
935
897
  import tailwindcss2 from "@tailwindcss/vite";
936
898
  import { writeFile, unlink } from "fs/promises";
937
- import { existsSync as existsSync4 } from "fs";
899
+ import { existsSync as existsSync5 } from "fs";
938
900
  var VITE_PORT = 5173, GATEWAY_PORT = 3001, createViteLogger = (onLog) => {
939
901
  return {
940
902
  info: (msg) => onLog?.(msg, "info"),
@@ -978,10 +940,10 @@ var VITE_PORT = 5173, GATEWAY_PORT = 3001, createViteLogger = (onLog) => {
978
940
  </html>`;
979
941
  }, startViteServer = async (options) => {
980
942
  const indexTsxPath = resolve(options.srcDir, "index.tsx");
981
- if (!existsSync4(indexTsxPath)) {
943
+ if (!existsSync5(indexTsxPath)) {
982
944
  return null;
983
945
  }
984
- const projectDir = dirname2(options.srcDir);
946
+ const projectDir = dirname3(options.srcDir);
985
947
  const tempIndexPath = resolve(projectDir, "index.html");
986
948
  try {
987
949
  const indexHtml = createTempIndexHtml(indexTsxPath, projectDir);
@@ -1022,344 +984,641 @@ var VITE_PORT = 5173, GATEWAY_PORT = 3001, createViteLogger = (onLog) => {
1022
984
  };
1023
985
  var init_vite = () => {};
1024
986
 
1025
- // src/components/DeploymentLogsPane.tsx
1026
- import { jsxDEV } from "@opentui/react/jsx-dev-runtime";
1027
- var getLevelColor = (level) => {
1028
- switch (level.toLowerCase()) {
1029
- case "error":
1030
- return "red";
1031
- case "warn":
1032
- case "warning":
1033
- return "yellow";
1034
- case "info":
1035
- return "cyan";
1036
- case "debug":
1037
- return "gray";
1038
- default:
1039
- return "white";
1040
- }
987
+ // src/ui/utils/logs.ts
988
+ var LOG_LEVEL_COLORS, SYSTEM_LOG_LEVEL_COLORS, getLevelColor = (level, variant = "deployment") => {
989
+ const colors = variant === "system" ? SYSTEM_LOG_LEVEL_COLORS : LOG_LEVEL_COLORS;
990
+ return colors[level.toLowerCase()] || "white";
1041
991
  }, sanitizeMessage = (message) => {
1042
992
  return message.replace(/\x1b\[[0-9;]*m/g, "").replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
1043
- }, DeploymentLogsPane = (props) => {
1044
- if (props.loading && props.logs.length === 0) {
1045
- return /* @__PURE__ */ jsxDEV("box", {
1046
- flexDirection: "column",
1047
- padding: 1,
1048
- children: /* @__PURE__ */ jsxDEV("text", {
1049
- children: "Loading deployment logs..."
1050
- }, undefined, false, undefined, this)
1051
- }, undefined, false, undefined, this);
1052
- }
1053
- if (!props.loading && props.logs.length === 0) {
1054
- return /* @__PURE__ */ jsxDEV("box", {
1055
- flexDirection: "column",
1056
- padding: 1,
1057
- children: /* @__PURE__ */ jsxDEV("text", {
1058
- children: "No deployment logs found"
1059
- }, undefined, false, undefined, this)
1060
- }, undefined, false, undefined, this);
993
+ };
994
+ var init_logs = __esm(() => {
995
+ LOG_LEVEL_COLORS = {
996
+ error: "red",
997
+ warn: "yellow",
998
+ warning: "yellow",
999
+ info: "cyan",
1000
+ debug: "gray",
1001
+ trace: "gray"
1002
+ };
1003
+ SYSTEM_LOG_LEVEL_COLORS = {
1004
+ ...LOG_LEVEL_COLORS,
1005
+ info: "green"
1006
+ };
1007
+ });
1008
+
1009
+ // src/ui/utils/clipboard.ts
1010
+ import { exec } from "child_process";
1011
+ var copyToClipboard = (text) => {
1012
+ return new Promise((resolve2, reject) => {
1013
+ const proc = exec("pbcopy", (err) => {
1014
+ if (err)
1015
+ reject(err);
1016
+ else
1017
+ resolve2();
1018
+ });
1019
+ proc.stdin?.write(text);
1020
+ proc.stdin?.end();
1021
+ });
1022
+ };
1023
+ var init_clipboard = () => {};
1024
+
1025
+ // src/ui/utils/formatting.ts
1026
+ var getBinarySourceLabel = (source) => {
1027
+ switch (source) {
1028
+ case "local-debug":
1029
+ return "local debug";
1030
+ case "local-release":
1031
+ return "local release";
1032
+ case "local-env":
1033
+ return "env";
1034
+ case "npm":
1035
+ return "npm";
1036
+ default:
1037
+ return source;
1061
1038
  }
1062
- const logsInOrder = [...props.logs].reverse();
1063
- return /* @__PURE__ */ jsxDEV("scrollbox", {
1064
- focused: true,
1065
- stickyStart: props.autoScroll ? "bottom" : "top",
1066
- stickyScroll: props.autoScroll,
1067
- children: logsInOrder.map((log) => {
1068
- const time = new Date(log.time).toLocaleTimeString();
1069
- const timestamp = `[${time}]`;
1070
- const level = log.level.toUpperCase();
1071
- return /* @__PURE__ */ jsxDEV("box", {
1072
- flexDirection: "row",
1073
- children: /* @__PURE__ */ jsxDEV("text", {
1039
+ };
1040
+
1041
+ // src/ui/utils/index.ts
1042
+ var init_utils = __esm(() => {
1043
+ init_logs();
1044
+ init_clipboard();
1045
+ });
1046
+
1047
+ // src/ui/components/layout/Header.tsx
1048
+ import { jsxDEV } from "@opentui/react/jsx-dev-runtime";
1049
+ var Header = (props) => {
1050
+ const { activeTab, functionCount, viteRunning, isDeploying, binarySource } = props;
1051
+ return /* @__PURE__ */ jsxDEV("box", {
1052
+ flexDirection: "column",
1053
+ children: /* @__PURE__ */ jsxDEV("box", {
1054
+ flexDirection: "row",
1055
+ justifyContent: "space-between",
1056
+ children: [
1057
+ /* @__PURE__ */ jsxDEV("box", {
1058
+ flexDirection: "row",
1059
+ gap: 2,
1074
1060
  children: [
1075
- /* @__PURE__ */ jsxDEV("span", {
1076
- fg: "gray",
1077
- children: timestamp
1078
- }, undefined, false, undefined, this),
1079
- /* @__PURE__ */ jsxDEV("span", {
1080
- fg: getLevelColor(log.level),
1081
- children: ` ${level} `
1082
- }, undefined, false, undefined, this),
1083
- /* @__PURE__ */ jsxDEV("span", {
1084
- fg: "blue",
1085
- children: `(${log.deployment_name})`
1061
+ /* @__PURE__ */ jsxDEV("text", {
1062
+ children: /* @__PURE__ */ jsxDEV("span", {
1063
+ fg: activeTab === "system" ? "cyan" : "gray",
1064
+ children: "[1] System Logs"
1065
+ }, undefined, false, undefined, this)
1086
1066
  }, undefined, false, undefined, this),
1087
- /* @__PURE__ */ jsxDEV("span", {
1088
- fg: "cyan",
1089
- children: ` ${log.trigger} `
1067
+ /* @__PURE__ */ jsxDEV("text", {
1068
+ children: /* @__PURE__ */ jsxDEV("span", {
1069
+ fg: "gray",
1070
+ children: " | "
1071
+ }, undefined, false, undefined, this)
1090
1072
  }, undefined, false, undefined, this),
1091
- /* @__PURE__ */ jsxDEV("span", {
1092
- children: sanitizeMessage(log.message)
1073
+ /* @__PURE__ */ jsxDEV("text", {
1074
+ children: /* @__PURE__ */ jsxDEV("span", {
1075
+ fg: activeTab === "deployment" ? "cyan" : "gray",
1076
+ children: "[2] Deployment Logs"
1077
+ }, undefined, false, undefined, this)
1093
1078
  }, undefined, false, undefined, this)
1094
1079
  ]
1095
- }, undefined, true, undefined, this)
1096
- }, log.id, false, undefined, this);
1097
- })
1098
- }, `${props.autoScroll}-${props.jumpTrigger}`, false, undefined, this);
1099
- };
1100
- var init_DeploymentLogsPane = () => {};
1101
-
1102
- // src/components/SystemLogsPane.tsx
1103
- import { jsxDEV as jsxDEV2 } from "@opentui/react/jsx-dev-runtime";
1104
- var getLevelColor2 = (level) => {
1105
- switch (level.toLowerCase()) {
1106
- case "error":
1107
- return "red";
1108
- case "warn":
1109
- case "warning":
1110
- return "yellow";
1111
- case "info":
1112
- return "green";
1113
- case "debug":
1114
- case "trace":
1115
- return "gray";
1116
- default:
1117
- return "white";
1118
- }
1119
- }, sanitizeMessage2 = (message) => {
1120
- return message.replace(/\x1b\[[0-9;]*m/g, "").replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
1121
- }, SystemLogsPane = (props) => {
1122
- if (props.loading && props.logs.length === 0) {
1123
- return /* @__PURE__ */ jsxDEV2("box", {
1124
- flexDirection: "column",
1125
- padding: 1,
1126
- children: /* @__PURE__ */ jsxDEV2("text", {
1127
- children: "Loading system logs..."
1128
- }, undefined, false, undefined, this)
1129
- }, undefined, false, undefined, this);
1130
- }
1131
- if (!props.loading && props.logs.length === 0) {
1132
- return /* @__PURE__ */ jsxDEV2("box", {
1133
- flexDirection: "column",
1134
- padding: 1,
1135
- children: /* @__PURE__ */ jsxDEV2("text", {
1136
- children: "No system logs found"
1137
- }, undefined, false, undefined, this)
1138
- }, undefined, false, undefined, this);
1139
- }
1140
- const logsInOrder = [...props.logs].reverse();
1141
- return /* @__PURE__ */ jsxDEV2("scrollbox", {
1142
- focused: true,
1143
- stickyStart: props.autoScroll ? "bottom" : "top",
1144
- stickyScroll: props.autoScroll,
1145
- children: logsInOrder.map((log) => {
1146
- const time = new Date(log.timestamp).toLocaleTimeString();
1147
- const timestamp = `[${time}]`;
1148
- const level = log.level.toUpperCase();
1149
- return /* @__PURE__ */ jsxDEV2("box", {
1150
- flexDirection: "row",
1151
- children: /* @__PURE__ */ jsxDEV2("text", {
1080
+ }, undefined, true, undefined, this),
1081
+ /* @__PURE__ */ jsxDEV("box", {
1082
+ flexDirection: "row",
1083
+ gap: 2,
1152
1084
  children: [
1153
- /* @__PURE__ */ jsxDEV2("span", {
1154
- fg: "gray",
1155
- children: timestamp
1156
- }, undefined, false, undefined, this),
1157
- /* @__PURE__ */ jsxDEV2("span", {
1158
- fg: getLevelColor2(log.level),
1159
- children: ` ${level} `
1085
+ isDeploying && /* @__PURE__ */ jsxDEV("text", {
1086
+ children: /* @__PURE__ */ jsxDEV("span", {
1087
+ fg: "yellow",
1088
+ children: "deploying..."
1089
+ }, undefined, false, undefined, this)
1160
1090
  }, undefined, false, undefined, this),
1161
- /* @__PURE__ */ jsxDEV2("span", {
1162
- children: sanitizeMessage2(log.message)
1091
+ /* @__PURE__ */ jsxDEV("text", {
1092
+ children: /* @__PURE__ */ jsxDEV("span", {
1093
+ fg: "gray",
1094
+ children: [
1095
+ functionCount,
1096
+ " functions | vite ",
1097
+ viteRunning ? "running" : "stopped",
1098
+ " |",
1099
+ " ",
1100
+ getBinarySourceLabel(binarySource),
1101
+ " | ?=help"
1102
+ ]
1103
+ }, undefined, true, undefined, this)
1163
1104
  }, undefined, false, undefined, this)
1164
1105
  ]
1165
1106
  }, undefined, true, undefined, this)
1166
- }, log.id, false, undefined, this);
1167
- })
1168
- }, `${props.autoScroll}-${props.jumpTrigger}`, false, undefined, this);
1107
+ ]
1108
+ }, undefined, true, undefined, this)
1109
+ }, undefined, false, undefined, this);
1169
1110
  };
1170
- var init_SystemLogsPane = () => {};
1111
+ var init_Header = __esm(() => {
1112
+ init_utils();
1113
+ });
1171
1114
 
1172
- // src/components/DeployAnimation.tsx
1173
- import { useEffect, useState, useRef } from "react";
1174
- import { jsxDEV as jsxDEV3 } from "@opentui/react/jsx-dev-runtime";
1175
- var DeployAnimation = (props) => {
1176
- const [currentIndex, setCurrentIndex] = useState(0);
1177
- const [progress, setProgress] = useState(0);
1178
- const intervalRef = useRef(null);
1179
- const completedRef = useRef(false);
1180
- const duration = 400;
1181
- const frameRate = 16;
1182
- useEffect(() => {
1183
- if (props.deployed.length === 0) {
1184
- props.onComplete();
1185
- return;
1186
- }
1187
- if (completedRef.current) {
1188
- return;
1189
- }
1190
- setProgress(0);
1191
- const startTime = Date.now();
1192
- intervalRef.current = setInterval(() => {
1193
- const elapsed = Date.now() - startTime;
1194
- const newProgress = Math.min(100, elapsed / duration * 100);
1195
- setProgress(newProgress);
1196
- if (newProgress >= 100) {
1197
- if (intervalRef.current) {
1198
- clearInterval(intervalRef.current);
1199
- intervalRef.current = null;
1200
- }
1201
- setTimeout(() => {
1202
- if (currentIndex >= props.deployed.length - 1) {
1203
- completedRef.current = true;
1204
- props.onComplete();
1205
- } else {
1206
- setCurrentIndex((prev) => prev + 1);
1207
- }
1208
- }, 150);
1209
- }
1210
- }, frameRate);
1211
- return () => {
1212
- if (intervalRef.current) {
1213
- clearInterval(intervalRef.current);
1214
- }
1215
- };
1216
- }, [currentIndex, props.deployed.length, props.onComplete]);
1217
- const current = props.deployed[currentIndex];
1218
- if (props.deployed.length === 0 || !current || completedRef.current) {
1219
- return null;
1220
- }
1221
- const barWidth = 8;
1222
- const filled = Math.round(progress / 100 * barWidth);
1223
- const empty = barWidth - filled;
1224
- const bar = "\u2501".repeat(filled) + "\u2500".repeat(empty);
1225
- return /* @__PURE__ */ jsxDEV3("box", {
1115
+ // src/ui/constants/colors.ts
1116
+ var STATUS_COLORS, DEPLOY_COLORS;
1117
+ var init_colors = __esm(() => {
1118
+ STATUS_COLORS = {
1119
+ success: "#22c55e",
1120
+ warning: "#eab308",
1121
+ error: "#ef4444",
1122
+ info: "#3b82f6"
1123
+ };
1124
+ DEPLOY_COLORS = {
1125
+ success: "#22c55e",
1126
+ warning: "#eab308",
1127
+ error: "#ef4444",
1128
+ progress: "#3b82f6",
1129
+ muted: "#666"
1130
+ };
1131
+ });
1132
+
1133
+ // src/ui/constants/keybindings.ts
1134
+ var KEYBINDINGS, KEYBINDING_DESCRIPTIONS;
1135
+ var init_keybindings = __esm(() => {
1136
+ KEYBINDINGS = {
1137
+ "1": { type: "NAVIGATE", tab: "system" },
1138
+ "2": { type: "NAVIGATE", tab: "deployment" },
1139
+ "\t": { type: "CYCLE_TAB" },
1140
+ h: { type: "NAVIGATE", tab: "system" },
1141
+ l: { type: "NAVIGATE", tab: "deployment" },
1142
+ g: { type: "SCROLL", to: "top" },
1143
+ G: { type: "SCROLL", to: "bottom" },
1144
+ "?": { type: "TOGGLE_HELP" },
1145
+ "\x1B": { type: "CLOSE_MODAL" },
1146
+ d: { type: "DEPLOY" },
1147
+ y: { type: "YANK" },
1148
+ x: { type: "CLEAN" }
1149
+ };
1150
+ KEYBINDING_DESCRIPTIONS = [
1151
+ { section: "Navigation", bindings: [
1152
+ { keys: "1/2", description: "Switch tabs" },
1153
+ { keys: "h/l", description: "Previous/next tab" },
1154
+ { keys: "Tab", description: "Cycle tabs" }
1155
+ ] },
1156
+ { section: "Scrolling", bindings: [
1157
+ { keys: "j/k", description: "Scroll down/up" },
1158
+ { keys: "g/G", description: "Top/bottom" }
1159
+ ] },
1160
+ { section: "Actions", bindings: [
1161
+ { keys: "y", description: "Yank (copy) selection" },
1162
+ { keys: "d", description: "Deploy all" },
1163
+ { keys: "x", description: "Clean local data" },
1164
+ { keys: "?", description: "Toggle help" },
1165
+ { keys: "Esc", description: "Close help" }
1166
+ ] }
1167
+ ];
1168
+ });
1169
+
1170
+ // src/ui/constants/index.ts
1171
+ var init_constants = __esm(() => {
1172
+ init_colors();
1173
+ init_keybindings();
1174
+ });
1175
+
1176
+ // src/ui/components/deploy/DeployProgress.tsx
1177
+ import { jsxDEV as jsxDEV2 } from "@opentui/react/jsx-dev-runtime";
1178
+ var DeployProgress = (props) => {
1179
+ const { currentFile, completedFiles, totalFiles } = props.progress;
1180
+ const completedCount = completedFiles.length;
1181
+ const failedCount = completedFiles.filter((f) => !f.success).length;
1182
+ return /* @__PURE__ */ jsxDEV2("box", {
1226
1183
  backgroundColor: "black",
1227
1184
  flexDirection: "row",
1228
1185
  paddingLeft: 1,
1229
1186
  paddingRight: 1,
1230
- children: /* @__PURE__ */ jsxDEV3("text", {
1187
+ children: /* @__PURE__ */ jsxDEV2("text", {
1231
1188
  children: [
1232
- /* @__PURE__ */ jsxDEV3("span", {
1233
- fg: current.success ? "#22c55e" : "#ef4444",
1234
- children: [
1235
- bar,
1236
- " ",
1237
- current.success ? "\u2713" : "\u2717"
1238
- ]
1239
- }, undefined, true, undefined, this),
1240
- /* @__PURE__ */ jsxDEV3("span", {
1189
+ /* @__PURE__ */ jsxDEV2("span", {
1190
+ fg: DEPLOY_COLORS.progress,
1191
+ children: "\u27F3 Deploying"
1192
+ }, undefined, false, undefined, this),
1193
+ /* @__PURE__ */ jsxDEV2("span", {
1241
1194
  children: [
1242
1195
  " ",
1243
- current.name
1196
+ currentFile
1244
1197
  ]
1245
1198
  }, undefined, true, undefined, this),
1246
- props.deployed.length > 1 && /* @__PURE__ */ jsxDEV3("span", {
1247
- fg: "#666",
1199
+ /* @__PURE__ */ jsxDEV2("span", {
1200
+ fg: DEPLOY_COLORS.muted,
1248
1201
  children: [
1249
1202
  " ",
1250
1203
  "(",
1251
- currentIndex + 1,
1204
+ completedCount + 1,
1252
1205
  "/",
1253
- props.deployed.length,
1206
+ totalFiles,
1254
1207
  ")"
1255
1208
  ]
1209
+ }, undefined, true, undefined, this),
1210
+ failedCount > 0 && /* @__PURE__ */ jsxDEV2("span", {
1211
+ fg: DEPLOY_COLORS.error,
1212
+ children: [
1213
+ " (",
1214
+ failedCount,
1215
+ " failed)"
1216
+ ]
1256
1217
  }, undefined, true, undefined, this)
1257
1218
  ]
1258
1219
  }, undefined, true, undefined, this)
1259
1220
  }, undefined, false, undefined, this);
1260
1221
  };
1261
- var init_DeployAnimation = () => {};
1222
+ var init_DeployProgress = __esm(() => {
1223
+ init_constants();
1224
+ });
1262
1225
 
1263
- // src/components/Header.tsx
1264
- import { jsxDEV as jsxDEV4, Fragment } from "@opentui/react/jsx-dev-runtime";
1265
- var getBinarySourceLabel = (source) => {
1266
- switch (source) {
1267
- case "local-debug":
1268
- return "local (debug)";
1269
- case "local-release":
1270
- return "local (release)";
1271
- case "local-env":
1272
- return "local (env)";
1273
- case "npm":
1274
- return "npm";
1226
+ // src/ui/components/deploy/DeployComplete.tsx
1227
+ import { useEffect, useState } from "react";
1228
+ import { jsxDEV as jsxDEV3, Fragment } from "@opentui/react/jsx-dev-runtime";
1229
+ var DeployComplete = (props) => {
1230
+ const { results, onComplete } = props;
1231
+ const [visible, setVisible] = useState(true);
1232
+ const successCount = results.filter((r) => r.success).length;
1233
+ const failedCount = results.filter((r) => !r.success).length;
1234
+ useEffect(() => {
1235
+ const timer = setTimeout(() => {
1236
+ setVisible(false);
1237
+ onComplete();
1238
+ }, 3000);
1239
+ return () => clearTimeout(timer);
1240
+ }, [onComplete]);
1241
+ if (!visible || results.length === 0) {
1242
+ return null;
1275
1243
  }
1276
- }, Header = (props) => {
1277
- const { activeTab, functionCount, viteRunning, isDeploying, binarySource } = props;
1278
- return /* @__PURE__ */ jsxDEV4("box", {
1244
+ return /* @__PURE__ */ jsxDEV3("box", {
1245
+ backgroundColor: "black",
1279
1246
  flexDirection: "row",
1280
- justifyContent: "space-between",
1281
- children: [
1282
- /* @__PURE__ */ jsxDEV4("box", {
1283
- flexDirection: "row",
1247
+ paddingLeft: 1,
1248
+ paddingRight: 1,
1249
+ children: /* @__PURE__ */ jsxDEV3("text", {
1250
+ children: failedCount === 0 ? /* @__PURE__ */ jsxDEV3("span", {
1251
+ fg: DEPLOY_COLORS.success,
1284
1252
  children: [
1285
- /* @__PURE__ */ jsxDEV4("text", {
1286
- fg: activeTab === "system" ? "cyan" : undefined,
1287
- content: "[1] System Logs"
1288
- }, undefined, false, undefined, this),
1289
- /* @__PURE__ */ jsxDEV4("text", {
1290
- content: " | "
1291
- }, undefined, false, undefined, this),
1292
- /* @__PURE__ */ jsxDEV4("text", {
1293
- fg: activeTab === "deployment" ? "cyan" : undefined,
1294
- content: "[2] Deployment Logs"
1295
- }, undefined, false, undefined, this)
1253
+ "\u2713 Deployed ",
1254
+ successCount,
1255
+ " files"
1296
1256
  ]
1297
- }, undefined, true, undefined, this),
1298
- /* @__PURE__ */ jsxDEV4("box", {
1299
- flexDirection: "row",
1257
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV3(Fragment, {
1300
1258
  children: [
1301
- isDeploying ? /* @__PURE__ */ jsxDEV4("text", {
1302
- fg: "yellow",
1303
- content: "\u25CF deploying all..."
1304
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV4("text", {
1305
- fg: functionCount > 0 ? "green" : "yellow",
1306
- content: "\u25CF watching"
1307
- }, undefined, false, undefined, this),
1308
- /* @__PURE__ */ jsxDEV4("text", {
1309
- fg: "gray",
1310
- content: ` (${functionCount} functions)`
1311
- }, undefined, false, undefined, this),
1312
- viteRunning && /* @__PURE__ */ jsxDEV4(Fragment, {
1259
+ /* @__PURE__ */ jsxDEV3("span", {
1260
+ fg: DEPLOY_COLORS.success,
1313
1261
  children: [
1314
- /* @__PURE__ */ jsxDEV4("text", {
1315
- fg: "gray",
1316
- content: " | vite: "
1317
- }, undefined, false, undefined, this),
1318
- /* @__PURE__ */ jsxDEV4("text", {
1319
- fg: "magenta",
1320
- content: ":5173"
1321
- }, undefined, false, undefined, this)
1262
+ "\u2713 ",
1263
+ successCount,
1264
+ " deployed"
1322
1265
  ]
1323
1266
  }, undefined, true, undefined, this),
1324
- /* @__PURE__ */ jsxDEV4("text", {
1325
- fg: "gray",
1326
- content: " | server: "
1327
- }, undefined, false, undefined, this),
1328
- /* @__PURE__ */ jsxDEV4("text", {
1329
- fg: binarySource.startsWith("local") ? "yellow" : "green",
1330
- content: getBinarySourceLabel(binarySource)
1331
- }, undefined, false, undefined, this)
1267
+ /* @__PURE__ */ jsxDEV3("span", {
1268
+ fg: DEPLOY_COLORS.error,
1269
+ children: [
1270
+ " \u2717 ",
1271
+ failedCount,
1272
+ " failed"
1273
+ ]
1274
+ }, undefined, true, undefined, this)
1332
1275
  ]
1333
1276
  }, undefined, true, undefined, this)
1334
- ]
1335
- }, undefined, true, undefined, this);
1277
+ }, undefined, false, undefined, this)
1278
+ }, undefined, false, undefined, this);
1336
1279
  };
1337
- var init_Header = () => {};
1280
+ var init_DeployComplete = __esm(() => {
1281
+ init_constants();
1282
+ });
1338
1283
 
1339
- // src/components/HelpModal.tsx
1340
- import { jsxDEV as jsxDEV5 } from "@opentui/react/jsx-dev-runtime";
1341
- var KeyBinding = ({ keys, description }) => /* @__PURE__ */ jsxDEV5("text", {
1342
- children: [
1343
- " ",
1344
- /* @__PURE__ */ jsxDEV5("span", {
1345
- fg: "green",
1346
- children: keys
1347
- }, undefined, false, undefined, this),
1348
- /* @__PURE__ */ jsxDEV5("span", {
1349
- fg: "gray",
1350
- children: " - "
1351
- }, undefined, false, undefined, this),
1352
- description
1353
- ]
1354
- }, undefined, true, undefined, this), Section = ({ title }) => /* @__PURE__ */ jsxDEV5("text", {
1355
- children: /* @__PURE__ */ jsxDEV5("span", {
1356
- fg: "gray",
1357
- children: title
1358
- }, undefined, false, undefined, this)
1359
- }, undefined, false, undefined, this), HelpModal = ({ visible }) => {
1360
- if (!visible)
1361
- return null;
1362
- return /* @__PURE__ */ jsxDEV5("box", {
1284
+ // src/ui/components/deploy/CleanProgress.tsx
1285
+ import { useEffect as useEffect2, useState as useState2 } from "react";
1286
+ import { jsxDEV as jsxDEV4 } from "@opentui/react/jsx-dev-runtime";
1287
+ var CleanProgress = (props) => {
1288
+ const { items, onRestartServer, onComplete } = props;
1289
+ const [currentIndex, setCurrentIndex] = useState2(0);
1290
+ const [phase, setPhase] = useState2("deleting");
1291
+ useEffect2(() => {
1292
+ if (items.length === 0) {
1293
+ onComplete();
1294
+ return;
1295
+ }
1296
+ if (phase === "deleting") {
1297
+ if (currentIndex >= items.length) {
1298
+ setPhase("restarting");
1299
+ return;
1300
+ }
1301
+ const timer = setTimeout(() => {
1302
+ setCurrentIndex((prev) => prev + 1);
1303
+ }, 300);
1304
+ return () => clearTimeout(timer);
1305
+ }
1306
+ if (phase === "restarting") {
1307
+ onRestartServer().then(() => {
1308
+ setPhase("complete");
1309
+ });
1310
+ return;
1311
+ }
1312
+ if (phase === "complete") {
1313
+ const timer = setTimeout(onComplete, 2000);
1314
+ return () => clearTimeout(timer);
1315
+ }
1316
+ }, [currentIndex, items.length, phase, onRestartServer, onComplete]);
1317
+ if (items.length === 0) {
1318
+ return null;
1319
+ }
1320
+ if (phase === "complete") {
1321
+ return /* @__PURE__ */ jsxDEV4("box", {
1322
+ backgroundColor: "black",
1323
+ flexDirection: "row",
1324
+ paddingLeft: 1,
1325
+ paddingRight: 1,
1326
+ children: /* @__PURE__ */ jsxDEV4("text", {
1327
+ children: /* @__PURE__ */ jsxDEV4("span", {
1328
+ fg: DEPLOY_COLORS.success,
1329
+ children: [
1330
+ "\u2713 Cleaned ",
1331
+ items.length,
1332
+ " items, server restarted"
1333
+ ]
1334
+ }, undefined, true, undefined, this)
1335
+ }, undefined, false, undefined, this)
1336
+ }, undefined, false, undefined, this);
1337
+ }
1338
+ if (phase === "restarting") {
1339
+ return /* @__PURE__ */ jsxDEV4("box", {
1340
+ backgroundColor: "black",
1341
+ flexDirection: "row",
1342
+ paddingLeft: 1,
1343
+ paddingRight: 1,
1344
+ children: /* @__PURE__ */ jsxDEV4("text", {
1345
+ children: /* @__PURE__ */ jsxDEV4("span", {
1346
+ fg: DEPLOY_COLORS.warning,
1347
+ children: "\u27F3 Restarting server..."
1348
+ }, undefined, false, undefined, this)
1349
+ }, undefined, false, undefined, this)
1350
+ }, undefined, false, undefined, this);
1351
+ }
1352
+ const current = items[currentIndex];
1353
+ return /* @__PURE__ */ jsxDEV4("box", {
1354
+ backgroundColor: "black",
1355
+ flexDirection: "row",
1356
+ paddingLeft: 1,
1357
+ paddingRight: 1,
1358
+ children: /* @__PURE__ */ jsxDEV4("text", {
1359
+ children: [
1360
+ /* @__PURE__ */ jsxDEV4("span", {
1361
+ fg: DEPLOY_COLORS.error,
1362
+ children: "\u2717 Deleting"
1363
+ }, undefined, false, undefined, this),
1364
+ /* @__PURE__ */ jsxDEV4("span", {
1365
+ children: [
1366
+ " ",
1367
+ current
1368
+ ]
1369
+ }, undefined, true, undefined, this),
1370
+ /* @__PURE__ */ jsxDEV4("span", {
1371
+ fg: DEPLOY_COLORS.muted,
1372
+ children: [
1373
+ " ",
1374
+ "(",
1375
+ currentIndex + 1,
1376
+ "/",
1377
+ items.length,
1378
+ ")"
1379
+ ]
1380
+ }, undefined, true, undefined, this)
1381
+ ]
1382
+ }, undefined, true, undefined, this)
1383
+ }, undefined, false, undefined, this);
1384
+ };
1385
+ var init_CleanProgress = __esm(() => {
1386
+ init_constants();
1387
+ });
1388
+
1389
+ // src/ui/components/layout/StatusBar.tsx
1390
+ import { jsxDEV as jsxDEV5 } from "@opentui/react/jsx-dev-runtime";
1391
+ var StatusBar = (props) => {
1392
+ const { deployProgress, deployComplete, cleanProgress, message } = props;
1393
+ if (!deployProgress && !deployComplete?.results.length && !cleanProgress && !message) {
1394
+ return null;
1395
+ }
1396
+ if (deployProgress) {
1397
+ return /* @__PURE__ */ jsxDEV5("box", {
1398
+ position: "absolute",
1399
+ bottom: 0,
1400
+ left: 0,
1401
+ width: "100%",
1402
+ flexDirection: "column",
1403
+ children: /* @__PURE__ */ jsxDEV5(DeployProgress, {
1404
+ progress: deployProgress
1405
+ }, undefined, false, undefined, this)
1406
+ }, undefined, false, undefined, this);
1407
+ }
1408
+ if (deployComplete && deployComplete.results.length > 0) {
1409
+ return /* @__PURE__ */ jsxDEV5("box", {
1410
+ position: "absolute",
1411
+ bottom: 0,
1412
+ left: 0,
1413
+ width: "100%",
1414
+ flexDirection: "column",
1415
+ children: /* @__PURE__ */ jsxDEV5(DeployComplete, {
1416
+ results: deployComplete.results,
1417
+ onComplete: deployComplete.onComplete
1418
+ }, undefined, false, undefined, this)
1419
+ }, undefined, false, undefined, this);
1420
+ }
1421
+ if (cleanProgress && cleanProgress.items.length > 0) {
1422
+ return /* @__PURE__ */ jsxDEV5("box", {
1423
+ position: "absolute",
1424
+ bottom: 0,
1425
+ left: 0,
1426
+ width: "100%",
1427
+ flexDirection: "column",
1428
+ children: /* @__PURE__ */ jsxDEV5(CleanProgress, {
1429
+ items: cleanProgress.items,
1430
+ onRestartServer: cleanProgress.onRestartServer,
1431
+ onComplete: cleanProgress.onComplete
1432
+ }, undefined, false, undefined, this)
1433
+ }, undefined, false, undefined, this);
1434
+ }
1435
+ if (message) {
1436
+ return /* @__PURE__ */ jsxDEV5("box", {
1437
+ position: "absolute",
1438
+ bottom: 0,
1439
+ left: 0,
1440
+ width: "100%",
1441
+ flexDirection: "column",
1442
+ children: /* @__PURE__ */ jsxDEV5("box", {
1443
+ backgroundColor: "black",
1444
+ paddingLeft: 1,
1445
+ paddingRight: 1,
1446
+ children: /* @__PURE__ */ jsxDEV5("text", {
1447
+ children: [
1448
+ /* @__PURE__ */ jsxDEV5("span", {
1449
+ fg: STATUS_COLORS[message.type],
1450
+ children: [
1451
+ message.icon,
1452
+ " ",
1453
+ message.text
1454
+ ]
1455
+ }, undefined, true, undefined, this),
1456
+ message.detail && /* @__PURE__ */ jsxDEV5("span", {
1457
+ fg: "#666",
1458
+ children: [
1459
+ " ",
1460
+ message.detail
1461
+ ]
1462
+ }, undefined, true, undefined, this)
1463
+ ]
1464
+ }, undefined, true, undefined, this)
1465
+ }, undefined, false, undefined, this)
1466
+ }, undefined, false, undefined, this);
1467
+ }
1468
+ return null;
1469
+ };
1470
+ var init_StatusBar = __esm(() => {
1471
+ init_DeployProgress();
1472
+ init_DeployComplete();
1473
+ init_CleanProgress();
1474
+ init_constants();
1475
+ });
1476
+
1477
+ // src/ui/components/layout/LogPane.tsx
1478
+ import { jsxDEV as jsxDEV6 } from "@opentui/react/jsx-dev-runtime";
1479
+ var LogPane = (props) => {
1480
+ const { logs: logs2, loading, autoScroll, jumpTrigger, emptyMessage, loadingMessage, children } = props;
1481
+ if (loading && logs2.length === 0) {
1482
+ return /* @__PURE__ */ jsxDEV6("box", {
1483
+ flexDirection: "column",
1484
+ padding: 1,
1485
+ children: /* @__PURE__ */ jsxDEV6("text", {
1486
+ children: loadingMessage
1487
+ }, undefined, false, undefined, this)
1488
+ }, undefined, false, undefined, this);
1489
+ }
1490
+ if (!loading && logs2.length === 0) {
1491
+ return /* @__PURE__ */ jsxDEV6("box", {
1492
+ flexDirection: "column",
1493
+ padding: 1,
1494
+ children: /* @__PURE__ */ jsxDEV6("text", {
1495
+ children: emptyMessage
1496
+ }, undefined, false, undefined, this)
1497
+ }, undefined, false, undefined, this);
1498
+ }
1499
+ return /* @__PURE__ */ jsxDEV6("scrollbox", {
1500
+ focused: true,
1501
+ flexGrow: 1,
1502
+ height: "100%",
1503
+ stickyStart: autoScroll ? "bottom" : "top",
1504
+ stickyScroll: autoScroll,
1505
+ children
1506
+ }, `${autoScroll}-${jumpTrigger}`, false, undefined, this);
1507
+ };
1508
+ var init_LogPane = () => {};
1509
+
1510
+ // src/ui/components/layout/index.ts
1511
+ var init_layout = __esm(() => {
1512
+ init_Header();
1513
+ init_StatusBar();
1514
+ init_LogPane();
1515
+ });
1516
+
1517
+ // src/ui/components/logs/LogEntry.tsx
1518
+ import { jsxDEV as jsxDEV7 } from "@opentui/react/jsx-dev-runtime";
1519
+ var LogEntry = (props) => {
1520
+ const { timestamp, level, message, variant = "deployment", deploymentName, trigger } = props;
1521
+ return /* @__PURE__ */ jsxDEV7("box", {
1522
+ flexDirection: "row",
1523
+ children: /* @__PURE__ */ jsxDEV7("text", {
1524
+ children: [
1525
+ /* @__PURE__ */ jsxDEV7("span", {
1526
+ fg: "gray",
1527
+ children: [
1528
+ "[",
1529
+ timestamp,
1530
+ "]"
1531
+ ]
1532
+ }, undefined, true, undefined, this),
1533
+ /* @__PURE__ */ jsxDEV7("span", {
1534
+ fg: getLevelColor(level, variant),
1535
+ children: ` ${level.toUpperCase()} `
1536
+ }, undefined, false, undefined, this),
1537
+ deploymentName && /* @__PURE__ */ jsxDEV7("span", {
1538
+ fg: "blue",
1539
+ children: `(${deploymentName})`
1540
+ }, undefined, false, undefined, this),
1541
+ trigger && /* @__PURE__ */ jsxDEV7("span", {
1542
+ fg: "cyan",
1543
+ children: ` ${trigger} `
1544
+ }, undefined, false, undefined, this),
1545
+ /* @__PURE__ */ jsxDEV7("span", {
1546
+ children: sanitizeMessage(message)
1547
+ }, undefined, false, undefined, this)
1548
+ ]
1549
+ }, undefined, true, undefined, this)
1550
+ }, undefined, false, undefined, this);
1551
+ };
1552
+ var init_LogEntry = __esm(() => {
1553
+ init_utils();
1554
+ });
1555
+
1556
+ // src/ui/components/logs/SystemLogs.tsx
1557
+ import { jsxDEV as jsxDEV8 } from "@opentui/react/jsx-dev-runtime";
1558
+ var SystemLogs = (props) => {
1559
+ const { logs: logs2, loading, autoScroll, jumpTrigger } = props;
1560
+ const logsInOrder = [...logs2].reverse();
1561
+ return /* @__PURE__ */ jsxDEV8(LogPane, {
1562
+ logs: logs2,
1563
+ loading,
1564
+ autoScroll,
1565
+ jumpTrigger,
1566
+ loadingMessage: "Loading system logs...",
1567
+ emptyMessage: "No system logs found",
1568
+ children: logsInOrder.map((log) => /* @__PURE__ */ jsxDEV8(LogEntry, {
1569
+ timestamp: new Date(log.timestamp).toLocaleTimeString(),
1570
+ level: log.level,
1571
+ message: log.message,
1572
+ variant: "system"
1573
+ }, log.id, false, undefined, this))
1574
+ }, undefined, false, undefined, this);
1575
+ };
1576
+ var init_SystemLogs = __esm(() => {
1577
+ init_layout();
1578
+ init_LogEntry();
1579
+ });
1580
+
1581
+ // src/ui/components/logs/DeploymentLogs.tsx
1582
+ import { jsxDEV as jsxDEV9 } from "@opentui/react/jsx-dev-runtime";
1583
+ var DeploymentLogs = (props) => {
1584
+ const { logs: logs2, loading, autoScroll, jumpTrigger } = props;
1585
+ const logsInOrder = [...logs2].reverse();
1586
+ return /* @__PURE__ */ jsxDEV9(LogPane, {
1587
+ logs: logs2,
1588
+ loading,
1589
+ autoScroll,
1590
+ jumpTrigger,
1591
+ loadingMessage: "Loading deployment logs...",
1592
+ emptyMessage: "No deployment logs found",
1593
+ children: logsInOrder.map((log) => /* @__PURE__ */ jsxDEV9(LogEntry, {
1594
+ timestamp: new Date(log.time).toLocaleTimeString(),
1595
+ level: log.level,
1596
+ message: log.message,
1597
+ variant: "deployment",
1598
+ deploymentName: log.deployment_name,
1599
+ trigger: log.trigger
1600
+ }, log.id, false, undefined, this))
1601
+ }, undefined, false, undefined, this);
1602
+ };
1603
+ var init_DeploymentLogs = __esm(() => {
1604
+ init_layout();
1605
+ init_LogEntry();
1606
+ });
1607
+
1608
+ // src/ui/components/logs/index.ts
1609
+ var init_logs2 = __esm(() => {
1610
+ init_LogEntry();
1611
+ init_SystemLogs();
1612
+ init_DeploymentLogs();
1613
+ });
1614
+
1615
+ // src/ui/components/modal/Modal.tsx
1616
+ import { jsxDEV as jsxDEV10 } from "@opentui/react/jsx-dev-runtime";
1617
+ var Modal = (props) => {
1618
+ const { visible, width = 50, height = 20, children } = props;
1619
+ if (!visible)
1620
+ return null;
1621
+ return /* @__PURE__ */ jsxDEV10("box", {
1363
1622
  position: "absolute",
1364
1623
  top: 0,
1365
1624
  left: 0,
@@ -1369,124 +1628,190 @@ var KeyBinding = ({ keys, description }) => /* @__PURE__ */ jsxDEV5("text", {
1369
1628
  opacity: 0.9,
1370
1629
  justifyContent: "center",
1371
1630
  alignItems: "center",
1372
- children: /* @__PURE__ */ jsxDEV5("box", {
1373
- width: 50,
1374
- height: 18,
1631
+ children: /* @__PURE__ */ jsxDEV10("box", {
1632
+ width,
1633
+ height,
1375
1634
  border: true,
1376
1635
  borderStyle: "rounded",
1377
1636
  flexDirection: "column",
1378
1637
  padding: 1,
1379
1638
  backgroundColor: "black",
1380
- children: [
1381
- /* @__PURE__ */ jsxDEV5("text", {
1382
- children: /* @__PURE__ */ jsxDEV5("span", {
1383
- fg: "cyan",
1384
- children: /* @__PURE__ */ jsxDEV5("strong", {
1385
- children: "Keyboard Shortcuts"
1386
- }, undefined, false, undefined, this)
1639
+ children
1640
+ }, undefined, false, undefined, this)
1641
+ }, undefined, false, undefined, this);
1642
+ };
1643
+ var init_Modal = () => {};
1644
+
1645
+ // src/ui/components/modal/HelpModal.tsx
1646
+ import { jsxDEV as jsxDEV11 } from "@opentui/react/jsx-dev-runtime";
1647
+ var KeyBinding = ({ keys, description }) => /* @__PURE__ */ jsxDEV11("text", {
1648
+ children: [
1649
+ " ",
1650
+ /* @__PURE__ */ jsxDEV11("span", {
1651
+ fg: "green",
1652
+ children: keys
1653
+ }, undefined, false, undefined, this),
1654
+ /* @__PURE__ */ jsxDEV11("span", {
1655
+ fg: "gray",
1656
+ children: " - "
1657
+ }, undefined, false, undefined, this),
1658
+ description
1659
+ ]
1660
+ }, undefined, true, undefined, this), Section = ({ title }) => /* @__PURE__ */ jsxDEV11("text", {
1661
+ children: /* @__PURE__ */ jsxDEV11("span", {
1662
+ fg: "gray",
1663
+ children: title
1664
+ }, undefined, false, undefined, this)
1665
+ }, undefined, false, undefined, this), HelpModal = ({ visible }) => {
1666
+ return /* @__PURE__ */ jsxDEV11(Modal, {
1667
+ visible,
1668
+ width: 50,
1669
+ height: 20,
1670
+ children: [
1671
+ /* @__PURE__ */ jsxDEV11("text", {
1672
+ children: /* @__PURE__ */ jsxDEV11("span", {
1673
+ fg: "cyan",
1674
+ children: /* @__PURE__ */ jsxDEV11("strong", {
1675
+ children: "Keyboard Shortcuts"
1387
1676
  }, undefined, false, undefined, this)
1388
- }, undefined, false, undefined, this),
1389
- /* @__PURE__ */ jsxDEV5("text", {}, undefined, false, undefined, this),
1390
- /* @__PURE__ */ jsxDEV5(Section, {
1391
- title: "Navigation"
1392
- }, undefined, false, undefined, this),
1393
- /* @__PURE__ */ jsxDEV5(KeyBinding, {
1394
- keys: "1/2",
1395
- description: "Switch tabs"
1396
- }, undefined, false, undefined, this),
1397
- /* @__PURE__ */ jsxDEV5(KeyBinding, {
1398
- keys: "h/l",
1399
- description: "Previous/next tab"
1400
- }, undefined, false, undefined, this),
1401
- /* @__PURE__ */ jsxDEV5(KeyBinding, {
1402
- keys: "Tab",
1403
- description: "Cycle tabs"
1404
- }, undefined, false, undefined, this),
1405
- /* @__PURE__ */ jsxDEV5("text", {}, undefined, false, undefined, this),
1406
- /* @__PURE__ */ jsxDEV5(Section, {
1407
- title: "Scrolling"
1408
- }, undefined, false, undefined, this),
1409
- /* @__PURE__ */ jsxDEV5(KeyBinding, {
1410
- keys: "j/k",
1411
- description: "Scroll down/up"
1412
- }, undefined, false, undefined, this),
1413
- /* @__PURE__ */ jsxDEV5(KeyBinding, {
1414
- keys: "g/G",
1415
- description: "Top/bottom"
1416
- }, undefined, false, undefined, this),
1417
- /* @__PURE__ */ jsxDEV5("text", {}, undefined, false, undefined, this),
1418
- /* @__PURE__ */ jsxDEV5(Section, {
1419
- title: "Actions"
1420
- }, undefined, false, undefined, this),
1421
- /* @__PURE__ */ jsxDEV5(KeyBinding, {
1422
- keys: "d",
1423
- description: "Deploy all"
1424
- }, undefined, false, undefined, this),
1425
- /* @__PURE__ */ jsxDEV5(KeyBinding, {
1426
- keys: "?",
1427
- description: "Toggle help"
1428
- }, undefined, false, undefined, this),
1429
- /* @__PURE__ */ jsxDEV5(KeyBinding, {
1430
- keys: "Esc",
1431
- description: "Close help"
1432
1677
  }, undefined, false, undefined, this)
1433
- ]
1434
- }, undefined, true, undefined, this)
1435
- }, undefined, false, undefined, this);
1678
+ }, undefined, false, undefined, this),
1679
+ /* @__PURE__ */ jsxDEV11("text", {}, undefined, false, undefined, this),
1680
+ KEYBINDING_DESCRIPTIONS.map((section, sectionIndex) => /* @__PURE__ */ jsxDEV11("box", {
1681
+ flexDirection: "column",
1682
+ children: [
1683
+ sectionIndex > 0 && /* @__PURE__ */ jsxDEV11("text", {}, undefined, false, undefined, this),
1684
+ /* @__PURE__ */ jsxDEV11(Section, {
1685
+ title: section.section
1686
+ }, undefined, false, undefined, this),
1687
+ section.bindings.map((binding) => /* @__PURE__ */ jsxDEV11(KeyBinding, {
1688
+ keys: binding.keys,
1689
+ description: binding.description
1690
+ }, binding.keys, false, undefined, this))
1691
+ ]
1692
+ }, section.section, true, undefined, this))
1693
+ ]
1694
+ }, undefined, true, undefined, this);
1436
1695
  };
1437
- var init_HelpModal = () => {};
1696
+ var init_HelpModal = __esm(() => {
1697
+ init_Modal();
1698
+ init_constants();
1699
+ });
1438
1700
 
1439
- // src/ui.tsx
1440
- var exports_ui = {};
1441
- import { useEffect as useEffect2, useState as useState2 } from "react";
1442
- import { createCliRenderer } from "@opentui/core";
1443
- import { createRoot } from "@opentui/react";
1444
- import { jsxDEV as jsxDEV6 } from "@opentui/react/jsx-dev-runtime";
1445
- var App = (props) => {
1446
- const [error, setError] = useState2(null);
1447
- const [loading, setLoading] = useState2(true);
1448
- const [deploymentLogs, setDeploymentLogs] = useState2([]);
1449
- const [systemLogs, setSystemLogs] = useState2([]);
1450
- const [activeTab, setActiveTab] = useState2("system");
1451
- const [autoScroll, setAutoScroll] = useState2(true);
1452
- const [jumpTrigger, setJumpTrigger] = useState2(0);
1453
- const [watcherStatus, setWatcherStatus] = useState2(null);
1454
- const [showKeyBindings, setShowKeyBindings] = useState2(false);
1455
- const [isDeploying, setIsDeploying] = useState2(false);
1456
- useEffect2(() => {
1457
- props.onWatcherEvent((event) => {
1458
- console.error("DEBUG: received watcher event:", event.type, event.deployed?.length);
1459
- setWatcherStatus(event);
1460
- });
1461
- }, [props.onWatcherEvent]);
1462
- useEffect2(() => {
1463
- const fn = async () => {
1701
+ // src/ui/components/modal/index.ts
1702
+ var init_modal = __esm(() => {
1703
+ init_Modal();
1704
+ init_HelpModal();
1705
+ });
1706
+ // ../../packages/api/types/index.ts
1707
+ var init_types = () => {};
1708
+
1709
+ // ../../packages/api/actions/client.ts
1710
+ var buildUrl = (path, options) => {
1711
+ let fullPath = options?.url ? `${options.url}${path}` : path;
1712
+ if (options?.query) {
1713
+ const params = [];
1714
+ for (const [key, value] of Object.entries(options.query)) {
1715
+ if (value === undefined || value === null)
1716
+ continue;
1717
+ if (Array.isArray(value)) {
1718
+ params.push(`${key}=${value.join(",")}`);
1719
+ } else {
1720
+ params.push(`${key}=${encodeURIComponent(String(value))}`);
1721
+ }
1722
+ }
1723
+ const queryString = params.join("&");
1724
+ if (queryString) {
1725
+ fullPath = `${fullPath}?${queryString}`;
1726
+ }
1727
+ }
1728
+ return fullPath;
1729
+ }, apiGet = async (path, options) => {
1730
+ const url = options?.url ? `${options.url}${path}` : path;
1731
+ const headers = {};
1732
+ if (options?.publicKey) {
1733
+ headers["x-public-key"] = options.publicKey;
1734
+ }
1735
+ const response = await fetch(url, { headers });
1736
+ if (!response.ok) {
1737
+ const message = await response.text().catch(() => "");
1738
+ throw new Error(`HTTP error! status: ${response.status}${message ? ` message: ${message}` : ""}`);
1739
+ }
1740
+ return response.json();
1741
+ };
1742
+
1743
+ // ../../packages/api/actions/deployments.ts
1744
+ var init_deployments = () => {};
1745
+
1746
+ // ../../packages/api/actions/invocations.ts
1747
+ var init_invocations = () => {};
1748
+
1749
+ // ../../packages/api/actions/logs.ts
1750
+ var getSystemLogs = async (public_key, query, url) => {
1751
+ const path = buildUrl("/api/system/logs", { url, query });
1752
+ return apiGet(path, { publicKey: public_key });
1753
+ }, searchDeploymentLogs = async (public_key, props, url) => {
1754
+ const path = buildUrl("/api/logs/search", { url, query: props });
1755
+ return apiGet(path, { publicKey: public_key });
1756
+ };
1757
+ var init_logs3 = () => {};
1758
+
1759
+ // ../../packages/api/actions/assets.ts
1760
+ var init_assets = () => {};
1761
+
1762
+ // ../../packages/api/actions/activity.ts
1763
+ var init_activity = () => {};
1764
+
1765
+ // ../../packages/api/actions/index.ts
1766
+ var init_actions = __esm(() => {
1767
+ init_deployments();
1768
+ init_invocations();
1769
+ init_logs3();
1770
+ init_assets();
1771
+ init_activity();
1772
+ });
1773
+
1774
+ // ../../packages/api/index.ts
1775
+ var init_api = __esm(() => {
1776
+ init_types();
1777
+ init_actions();
1778
+ });
1779
+
1780
+ // src/ui/hooks/useLogs.ts
1781
+ import { useEffect as useEffect3, useState as useState3 } from "react";
1782
+ var useLogs = (options) => {
1783
+ const { config, pollInterval = 2000 } = options;
1784
+ const [systemLogs, setSystemLogs] = useState3([]);
1785
+ const [deploymentLogs, setDeploymentLogs] = useState3([]);
1786
+ const [loading, setLoading] = useState3(true);
1787
+ const [error, setError] = useState3(null);
1788
+ useEffect3(() => {
1789
+ const fetchLogs = async () => {
1464
1790
  try {
1465
1791
  setLoading(true);
1466
1792
  setError(null);
1467
1793
  const [deploymentLogsData, systemLogsData] = await Promise.all([
1468
- searchDeploymentLogs(props.config.key.public, { limit: 100 }, props.config.api),
1469
- getSystemLogs(props.config.key.public, { limit: 100 }, props.config.api)
1794
+ searchDeploymentLogs(config.key.public, { limit: 100 }, config.api),
1795
+ getSystemLogs(config.key.public, { limit: 100 }, config.api)
1470
1796
  ]);
1471
1797
  setDeploymentLogs(deploymentLogsData);
1472
1798
  setSystemLogs(systemLogsData);
1473
1799
  } catch (err) {
1474
1800
  setError(err instanceof Error ? err.message : String(err));
1475
- console.error("Failed to fetch logs:", err);
1476
1801
  } finally {
1477
1802
  setLoading(false);
1478
1803
  }
1479
1804
  };
1480
- fn();
1481
- }, [props.config.key.public, props.config.api]);
1482
- useEffect2(() => {
1805
+ fetchLogs();
1806
+ }, [config.key.public, config.api]);
1807
+ useEffect3(() => {
1808
+ if (loading)
1809
+ return;
1483
1810
  const interval = setInterval(async () => {
1484
- if (loading)
1485
- return;
1486
1811
  try {
1487
1812
  const [newDeploymentLogs, newSystemLogs] = await Promise.all([
1488
- searchDeploymentLogs(props.config.key.public, { limit: 100 }, props.config.api),
1489
- getSystemLogs(props.config.key.public, { limit: 100 }, props.config.api)
1813
+ searchDeploymentLogs(config.key.public, { limit: 100 }, config.api),
1814
+ getSystemLogs(config.key.public, { limit: 100 }, config.api)
1490
1815
  ]);
1491
1816
  if (newDeploymentLogs.length > 0) {
1492
1817
  setDeploymentLogs(newDeploymentLogs);
@@ -1494,59 +1819,206 @@ var App = (props) => {
1494
1819
  if (newSystemLogs.length > 0) {
1495
1820
  setSystemLogs(newSystemLogs);
1496
1821
  }
1497
- } catch (err) {
1498
- console.error("Failed to poll for new logs:", err);
1499
- }
1500
- }, 2000);
1822
+ } catch {}
1823
+ }, pollInterval);
1501
1824
  return () => clearInterval(interval);
1502
- }, [loading, props.config.key.public, props.config.api]);
1503
- useEffect2(() => {
1504
- const handleKeyPress = (data) => {
1505
- const key = data.toString();
1506
- if (key === "1") {
1507
- setActiveTab("system");
1508
- return;
1509
- }
1510
- if (key === "2") {
1511
- setActiveTab("deployment");
1512
- return;
1513
- }
1514
- if (key === "\t") {
1515
- setActiveTab((prev) => prev === "system" ? "deployment" : "system");
1516
- return;
1517
- }
1518
- if (key === "h") {
1519
- setActiveTab("system");
1520
- return;
1521
- }
1522
- if (key === "l") {
1523
- setActiveTab("deployment");
1524
- return;
1525
- }
1526
- if (key === "g") {
1527
- setAutoScroll(false);
1528
- setJumpTrigger((prev) => prev + 1);
1529
- return;
1530
- }
1531
- if (key === "G") {
1532
- setAutoScroll(true);
1533
- setJumpTrigger((prev) => prev + 1);
1534
- return;
1535
- }
1536
- if (key === "?") {
1537
- setShowKeyBindings((prev) => !prev);
1538
- return;
1825
+ }, [loading, config.key.public, config.api, pollInterval]);
1826
+ return { systemLogs, deploymentLogs, loading, error };
1827
+ };
1828
+ var init_useLogs = __esm(() => {
1829
+ init_api();
1830
+ });
1831
+
1832
+ // src/ui/hooks/useStatusMessage.ts
1833
+ import { useState as useState4, useCallback, useRef } from "react";
1834
+ var DEFAULT_DURATION = 3000, useStatusMessage = () => {
1835
+ const [message, setMessage] = useState4(null);
1836
+ const timeoutRef = useRef(null);
1837
+ const clearMessage = useCallback(() => {
1838
+ if (timeoutRef.current) {
1839
+ clearTimeout(timeoutRef.current);
1840
+ timeoutRef.current = null;
1841
+ }
1842
+ setMessage(null);
1843
+ }, []);
1844
+ const showMessage = useCallback((type, icon, text, detail, duration = DEFAULT_DURATION) => {
1845
+ clearMessage();
1846
+ setMessage({ type, icon, text, detail });
1847
+ timeoutRef.current = setTimeout(clearMessage, duration);
1848
+ }, [clearMessage]);
1849
+ const showSuccess = useCallback((text, detail, duration) => {
1850
+ showMessage("success", "\u2713", text, detail, duration);
1851
+ }, [showMessage]);
1852
+ const showError = useCallback((text, detail, duration) => {
1853
+ showMessage("error", "\u2717", text, detail, duration);
1854
+ }, [showMessage]);
1855
+ const showWarning = useCallback((text, detail, duration) => {
1856
+ showMessage("warning", "!", text, detail, duration);
1857
+ }, [showMessage]);
1858
+ const showInfo = useCallback((text, detail, duration) => {
1859
+ showMessage("info", "\u25CB", text, detail, duration);
1860
+ }, [showMessage]);
1861
+ return {
1862
+ message,
1863
+ showMessage,
1864
+ showSuccess,
1865
+ showError,
1866
+ showWarning,
1867
+ showInfo,
1868
+ clearMessage
1869
+ };
1870
+ };
1871
+ var init_useStatusMessage = () => {};
1872
+
1873
+ // src/ui/hooks/useScrollControl.ts
1874
+ import { useState as useState5, useCallback as useCallback2 } from "react";
1875
+ var useScrollControl = () => {
1876
+ const [autoScroll, setAutoScroll] = useState5(true);
1877
+ const [jumpTrigger, setJumpTrigger] = useState5(0);
1878
+ const jumpToTop = useCallback2(() => {
1879
+ setAutoScroll(false);
1880
+ setJumpTrigger((prev) => prev + 1);
1881
+ }, []);
1882
+ const jumpToBottom = useCallback2(() => {
1883
+ setAutoScroll(true);
1884
+ setJumpTrigger((prev) => prev + 1);
1885
+ }, []);
1886
+ return {
1887
+ autoScroll,
1888
+ jumpTrigger,
1889
+ jumpToTop,
1890
+ jumpToBottom,
1891
+ setAutoScroll
1892
+ };
1893
+ };
1894
+ var init_useScrollControl = () => {};
1895
+
1896
+ // src/ui/hooks/useDeployment.ts
1897
+ import { useState as useState6, useCallback as useCallback3, useEffect as useEffect4 } from "react";
1898
+ var useDeployment = (options) => {
1899
+ const { onForceDeploy, onWatcherEvent } = options;
1900
+ const [isDeploying, setIsDeploying] = useState6(false);
1901
+ const [progress, setProgress] = useState6(null);
1902
+ const [completedResults, setCompletedResults] = useState6(null);
1903
+ const [watcherResults, setWatcherResults] = useState6(null);
1904
+ useEffect4(() => {
1905
+ onWatcherEvent((event) => {
1906
+ if (event.type === "deploy" && event.deployed?.length) {
1907
+ setWatcherResults(event.deployed);
1539
1908
  }
1540
- if (key === "\x1B") {
1541
- setShowKeyBindings(false);
1542
- return;
1909
+ });
1910
+ }, [onWatcherEvent]);
1911
+ const deploy = useCallback3(() => {
1912
+ if (isDeploying)
1913
+ return;
1914
+ setIsDeploying(true);
1915
+ onForceDeploy({
1916
+ onProgress: (p) => setProgress(p),
1917
+ onComplete: (results) => {
1918
+ setProgress(null);
1919
+ setCompletedResults(results);
1543
1920
  }
1544
- if (key === "d" && !isDeploying) {
1545
- setIsDeploying(true);
1546
- props.onForceDeploy().finally(() => {
1547
- setIsDeploying(false);
1548
- });
1921
+ }).finally(() => {
1922
+ setIsDeploying(false);
1923
+ });
1924
+ }, [isDeploying, onForceDeploy]);
1925
+ const clearCompleted = useCallback3(() => {
1926
+ setCompletedResults(null);
1927
+ }, []);
1928
+ const clearWatcherResults = useCallback3(() => {
1929
+ setWatcherResults(null);
1930
+ }, []);
1931
+ return {
1932
+ isDeploying,
1933
+ progress,
1934
+ completedResults,
1935
+ watcherResults,
1936
+ deploy,
1937
+ clearCompleted,
1938
+ clearWatcherResults
1939
+ };
1940
+ };
1941
+ var init_useDeployment = () => {};
1942
+
1943
+ // src/ui/hooks/useKeyboard.ts
1944
+ import { useEffect as useEffect5 } from "react";
1945
+ import { useRenderer } from "@opentui/react";
1946
+ var useKeyboard = (options) => {
1947
+ const {
1948
+ activeTab,
1949
+ isDeploying,
1950
+ isCleaning,
1951
+ onTabChange,
1952
+ onScrollTop,
1953
+ onScrollBottom,
1954
+ onToggleHelp,
1955
+ onCloseModal,
1956
+ onDeploy,
1957
+ onCopySuccess,
1958
+ onCopyError,
1959
+ onNoSelection,
1960
+ onClean,
1961
+ onCleanEmpty
1962
+ } = options;
1963
+ const renderer = useRenderer();
1964
+ useEffect5(() => {
1965
+ const handleKeyPress = (data) => {
1966
+ const key = data.toString();
1967
+ const action = KEYBINDINGS[key];
1968
+ if (!action)
1549
1969
  return;
1970
+ switch (action.type) {
1971
+ case "NAVIGATE":
1972
+ onTabChange(action.tab);
1973
+ break;
1974
+ case "CYCLE_TAB":
1975
+ onTabChange(activeTab === "system" ? "deployment" : "system");
1976
+ break;
1977
+ case "SCROLL":
1978
+ if (action.to === "top") {
1979
+ onScrollTop();
1980
+ } else {
1981
+ onScrollBottom();
1982
+ }
1983
+ break;
1984
+ case "TOGGLE_HELP":
1985
+ onToggleHelp();
1986
+ break;
1987
+ case "CLOSE_MODAL":
1988
+ onCloseModal();
1989
+ break;
1990
+ case "DEPLOY":
1991
+ if (!isDeploying) {
1992
+ onDeploy();
1993
+ }
1994
+ break;
1995
+ case "YANK":
1996
+ if (!renderer.hasSelection) {
1997
+ onNoSelection();
1998
+ return;
1999
+ }
2000
+ const text = renderer.getSelection()?.getSelectedText();
2001
+ if (!text) {
2002
+ onNoSelection();
2003
+ return;
2004
+ }
2005
+ copyToClipboard(text).then(() => {
2006
+ renderer.clearSelection();
2007
+ onCopySuccess(text);
2008
+ }).catch(() => {
2009
+ onCopyError();
2010
+ });
2011
+ break;
2012
+ case "CLEAN":
2013
+ if (isCleaning)
2014
+ return;
2015
+ const { deleted } = cleanLocalData();
2016
+ if (deleted.length > 0) {
2017
+ onClean(deleted);
2018
+ } else {
2019
+ onCleanEmpty();
2020
+ }
2021
+ break;
1550
2022
  }
1551
2023
  };
1552
2024
  process.stdin.setRawMode(true);
@@ -1555,14 +2027,101 @@ var App = (props) => {
1555
2027
  process.stdin.setRawMode(false);
1556
2028
  process.stdin.off("data", handleKeyPress);
1557
2029
  };
1558
- }, [isDeploying, props.onForceDeploy]);
2030
+ }, [
2031
+ activeTab,
2032
+ isDeploying,
2033
+ isCleaning,
2034
+ renderer,
2035
+ onTabChange,
2036
+ onScrollTop,
2037
+ onScrollBottom,
2038
+ onToggleHelp,
2039
+ onCloseModal,
2040
+ onDeploy,
2041
+ onCopySuccess,
2042
+ onCopyError,
2043
+ onNoSelection,
2044
+ onClean,
2045
+ onCleanEmpty
2046
+ ]);
2047
+ };
2048
+ var init_useKeyboard = __esm(() => {
2049
+ init_constants();
2050
+ init_utils();
2051
+ init_config();
2052
+ });
2053
+
2054
+ // src/ui/hooks/index.ts
2055
+ var init_hooks = __esm(() => {
2056
+ init_useLogs();
2057
+ init_useStatusMessage();
2058
+ init_useScrollControl();
2059
+ init_useDeployment();
2060
+ init_useKeyboard();
2061
+ });
2062
+
2063
+ // src/ui/App.tsx
2064
+ import { useState as useState7, useCallback as useCallback4 } from "react";
2065
+ import { jsxDEV as jsxDEV12 } from "@opentui/react/jsx-dev-runtime";
2066
+ var App = (props) => {
2067
+ const { config, binarySource, functionCount, viteRunning, onWatcherEvent, onForceDeploy, onRestartServer } = props;
2068
+ const [activeTab, setActiveTab] = useState7("system");
2069
+ const [showKeyBindings, setShowKeyBindings] = useState7(false);
2070
+ const [cleaningItems, setCleaningItems] = useState7(null);
2071
+ const { systemLogs, deploymentLogs, loading, error } = useLogs({ config });
2072
+ const { message, showInfo } = useStatusMessage();
2073
+ const { autoScroll, jumpTrigger, jumpToTop, jumpToBottom } = useScrollControl();
2074
+ const {
2075
+ isDeploying,
2076
+ progress: deployProgress,
2077
+ completedResults,
2078
+ watcherResults,
2079
+ deploy,
2080
+ clearCompleted,
2081
+ clearWatcherResults
2082
+ } = useDeployment({ onForceDeploy, onWatcherEvent });
2083
+ const handleCopySuccess = useCallback4((text) => {
2084
+ const preview = text.length > 30 ? text.substring(0, 30) + "..." : text;
2085
+ showInfo("Copied", `"${preview.replace(/\n/g, "\u21B5")}"`);
2086
+ }, [showInfo]);
2087
+ const handleCopyError = useCallback4(() => {
2088
+ showInfo("Failed to copy");
2089
+ }, [showInfo]);
2090
+ const handleNoSelection = useCallback4(() => {
2091
+ showInfo("No selection", "Select text first");
2092
+ }, [showInfo]);
2093
+ const handleClean = useCallback4((deletedItems) => {
2094
+ setCleaningItems(deletedItems);
2095
+ }, []);
2096
+ const handleCleanComplete = useCallback4(() => {
2097
+ setCleaningItems(null);
2098
+ }, []);
2099
+ const handleCleanEmpty = useCallback4(() => {
2100
+ showInfo("Nothing to clean");
2101
+ }, [showInfo]);
2102
+ useKeyboard({
2103
+ activeTab,
2104
+ isDeploying,
2105
+ isCleaning: cleaningItems !== null,
2106
+ onTabChange: setActiveTab,
2107
+ onScrollTop: jumpToTop,
2108
+ onScrollBottom: jumpToBottom,
2109
+ onToggleHelp: () => setShowKeyBindings((prev) => !prev),
2110
+ onCloseModal: () => setShowKeyBindings(false),
2111
+ onDeploy: deploy,
2112
+ onCopySuccess: handleCopySuccess,
2113
+ onCopyError: handleCopyError,
2114
+ onNoSelection: handleNoSelection,
2115
+ onClean: handleClean,
2116
+ onCleanEmpty: handleCleanEmpty
2117
+ });
1559
2118
  if (error) {
1560
- return /* @__PURE__ */ jsxDEV6("box", {
2119
+ return /* @__PURE__ */ jsxDEV12("box", {
1561
2120
  flexGrow: 1,
1562
2121
  flexDirection: "column",
1563
2122
  padding: 1,
1564
- children: /* @__PURE__ */ jsxDEV6("text", {
1565
- children: /* @__PURE__ */ jsxDEV6("span", {
2123
+ children: /* @__PURE__ */ jsxDEV12("text", {
2124
+ children: /* @__PURE__ */ jsxDEV12("span", {
1566
2125
  fg: "red",
1567
2126
  children: [
1568
2127
  "Error: ",
@@ -1572,50 +2131,72 @@ var App = (props) => {
1572
2131
  }, undefined, false, undefined, this)
1573
2132
  }, undefined, false, undefined, this);
1574
2133
  }
1575
- return /* @__PURE__ */ jsxDEV6("box", {
2134
+ return /* @__PURE__ */ jsxDEV12("box", {
1576
2135
  flexDirection: "column",
2136
+ height: "100%",
1577
2137
  children: [
1578
- /* @__PURE__ */ jsxDEV6(Header, {
2138
+ /* @__PURE__ */ jsxDEV12(Header, {
1579
2139
  activeTab,
1580
- functionCount: props.functionCount,
1581
- viteRunning: props.viteRunning,
2140
+ functionCount,
2141
+ viteRunning,
1582
2142
  isDeploying,
1583
- binarySource: props.binarySource
2143
+ binarySource
1584
2144
  }, undefined, false, undefined, this),
1585
- /* @__PURE__ */ jsxDEV6("box", {
2145
+ /* @__PURE__ */ jsxDEV12("box", {
1586
2146
  paddingTop: 1,
1587
2147
  flexDirection: "column",
1588
- children: [
1589
- activeTab === "system" ? /* @__PURE__ */ jsxDEV6(SystemLogsPane, {
1590
- logs: systemLogs,
1591
- loading,
1592
- autoScroll,
1593
- jumpTrigger
1594
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV6(DeploymentLogsPane, {
1595
- logs: deploymentLogs,
1596
- loading,
1597
- autoScroll,
1598
- jumpTrigger
1599
- }, undefined, false, undefined, this),
1600
- watcherStatus?.type === "deploy" && watcherStatus.deployed && watcherStatus.deployed.length > 0 && /* @__PURE__ */ jsxDEV6(DeployAnimation, {
1601
- deployed: watcherStatus.deployed,
1602
- onComplete: () => {
1603
- console.error("DEBUG: animation complete");
1604
- setWatcherStatus(null);
1605
- }
1606
- }, undefined, false, undefined, this)
1607
- ]
1608
- }, undefined, true, undefined, this),
1609
- /* @__PURE__ */ jsxDEV6(HelpModal, {
2148
+ flexGrow: 1,
2149
+ overflow: "hidden",
2150
+ children: activeTab === "system" ? /* @__PURE__ */ jsxDEV12(SystemLogs, {
2151
+ logs: systemLogs,
2152
+ loading,
2153
+ autoScroll,
2154
+ jumpTrigger
2155
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV12(DeploymentLogs, {
2156
+ logs: deploymentLogs,
2157
+ loading,
2158
+ autoScroll,
2159
+ jumpTrigger
2160
+ }, undefined, false, undefined, this)
2161
+ }, undefined, false, undefined, this),
2162
+ /* @__PURE__ */ jsxDEV12(StatusBar, {
2163
+ deployProgress,
2164
+ deployComplete: completedResults ? { results: completedResults, onComplete: clearCompleted } : watcherResults ? { results: watcherResults, onComplete: clearWatcherResults } : undefined,
2165
+ cleanProgress: cleaningItems ? { items: cleaningItems, onRestartServer, onComplete: handleCleanComplete } : null,
2166
+ message
2167
+ }, undefined, false, undefined, this),
2168
+ /* @__PURE__ */ jsxDEV12(HelpModal, {
1610
2169
  visible: showKeyBindings
1611
2170
  }, undefined, false, undefined, this)
1612
2171
  ]
1613
2172
  }, undefined, true, undefined, this);
1614
- }, main = async () => {
2173
+ };
2174
+ var init_App = __esm(() => {
2175
+ init_layout();
2176
+ init_logs2();
2177
+ init_modal();
2178
+ init_hooks();
2179
+ });
2180
+
2181
+ // src/ui.tsx
2182
+ var exports_ui = {};
2183
+ import { createCliRenderer } from "@opentui/core";
2184
+ import { createRoot } from "@opentui/react";
2185
+ import { jsxDEV as jsxDEV13 } from "@opentui/react/jsx-dev-runtime";
2186
+ var main = async () => {
1615
2187
  let serverInfo = null;
1616
2188
  let viteInfo = null;
1617
2189
  let stopWatcher = null;
1618
2190
  let isCleaningUp = false;
2191
+ const stopServer = () => {
2192
+ if (serverInfo) {
2193
+ const pid = serverInfo.process.pid;
2194
+ serverInfo = null;
2195
+ try {
2196
+ process.kill(pid, "SIGTERM");
2197
+ } catch {}
2198
+ }
2199
+ };
1619
2200
  const cleanup = async (exitAfter = true) => {
1620
2201
  if (isCleaningUp)
1621
2202
  return;
@@ -1628,13 +2209,7 @@ var App = (props) => {
1628
2209
  await stopViteServer(viteInfo);
1629
2210
  viteInfo = null;
1630
2211
  }
1631
- if (serverInfo) {
1632
- const pid = serverInfo.process.pid;
1633
- serverInfo = null;
1634
- try {
1635
- process.kill(pid, "SIGTERM");
1636
- } catch {}
1637
- }
2212
+ stopServer();
1638
2213
  if (exitAfter) {
1639
2214
  process.exit(0);
1640
2215
  }
@@ -1648,11 +2223,12 @@ var App = (props) => {
1648
2223
  const srcPath = process.cwd() + "/src";
1649
2224
  viteInfo = await startViteServer({
1650
2225
  srcDir: srcPath,
1651
- onLog: (_msg, _level) => {}
2226
+ onLog: () => {}
1652
2227
  });
1653
2228
  let watcherEventHandler = null;
1654
2229
  let pendingEvents = [];
1655
2230
  let functionCount = 0;
2231
+ let isInitialBuild = true;
1656
2232
  const sendEvent = (event) => {
1657
2233
  if (watcherEventHandler) {
1658
2234
  watcherEventHandler(event);
@@ -1660,22 +2236,27 @@ var App = (props) => {
1660
2236
  pendingEvents.push(event);
1661
2237
  }
1662
2238
  };
2239
+ const restartServer = async () => {
2240
+ stopServer();
2241
+ serverInfo = await startServer();
2242
+ };
1663
2243
  const renderer = await createCliRenderer();
1664
2244
  const root = createRoot(renderer);
1665
- const handleForceDeploy = async () => {
2245
+ const handleForceDeploy = async (callbacks) => {
1666
2246
  const deployResults = [];
1667
2247
  await forceDeployAll(srcPath, {
2248
+ onStart: (filePath, completedCount, totalCount) => {
2249
+ callbacks.onProgress({
2250
+ currentFile: filePath,
2251
+ completedFiles: [...deployResults],
2252
+ totalFiles: totalCount
2253
+ });
2254
+ },
1668
2255
  onDeploy: (filePath, success, error) => {
1669
2256
  deployResults.push({ name: filePath, success, error });
1670
2257
  }
1671
2258
  });
1672
- if (deployResults.length > 0) {
1673
- sendEvent({
1674
- type: "deploy",
1675
- deployed: deployResults,
1676
- timestamp: new Date
1677
- });
1678
- }
2259
+ callbacks.onComplete(deployResults);
1679
2260
  };
1680
2261
  stopWatcher = await startWatcher(srcPath, {
1681
2262
  silent: true,
@@ -1683,6 +2264,10 @@ var App = (props) => {
1683
2264
  functionCount = count;
1684
2265
  },
1685
2266
  onDeployBatch: (results) => {
2267
+ if (isInitialBuild) {
2268
+ isInitialBuild = false;
2269
+ return;
2270
+ }
1686
2271
  sendEvent({
1687
2272
  type: "deploy",
1688
2273
  deployed: results.map((r) => ({
@@ -1694,7 +2279,7 @@ var App = (props) => {
1694
2279
  });
1695
2280
  }
1696
2281
  });
1697
- root.render(/* @__PURE__ */ jsxDEV6(App, {
2282
+ root.render(/* @__PURE__ */ jsxDEV13(App, {
1698
2283
  config: devConfig,
1699
2284
  binarySource: serverInfo.binarySource,
1700
2285
  functionCount,
@@ -1708,7 +2293,8 @@ var App = (props) => {
1708
2293
  pendingEvents = [];
1709
2294
  }
1710
2295
  },
1711
- onForceDeploy: handleForceDeploy
2296
+ onForceDeploy: handleForceDeploy,
2297
+ onRestartServer: restartServer
1712
2298
  }, undefined, false, undefined, this));
1713
2299
  } catch (error) {
1714
2300
  console.error("Failed to start server:", error);
@@ -1716,16 +2302,11 @@ var App = (props) => {
1716
2302
  }
1717
2303
  };
1718
2304
  var init_ui = __esm(() => {
1719
- init_api();
1720
2305
  init_server();
1721
2306
  init_watcher();
1722
2307
  init_vite();
1723
2308
  init_config();
1724
- init_DeploymentLogsPane();
1725
- init_SystemLogsPane();
1726
- init_DeployAnimation();
1727
- init_Header();
1728
- init_HelpModal();
2309
+ init_App();
1729
2310
  main();
1730
2311
  });
1731
2312
 
@@ -1743,8 +2324,8 @@ init_config();
1743
2324
  init_bundle();
1744
2325
  init_deploy2();
1745
2326
  import chalk3 from "chalk";
1746
- import { basename as basename4, resolve as resolve2, join as join5 } from "path";
1747
- import { existsSync as existsSync5 } from "fs";
2327
+ import { basename as basename4, resolve as resolve2, join as join6 } from "path";
2328
+ import { existsSync as existsSync6 } from "fs";
1748
2329
  import { readdir as readdir2 } from "fs/promises";
1749
2330
  import * as p from "@clack/prompts";
1750
2331
  var FUNCTION_DIRS2 = ["api", "cron", "event"];
@@ -1773,25 +2354,25 @@ var selectConfig = async () => {
1773
2354
  };
1774
2355
  var findAllDeployables = async (srcPath) => {
1775
2356
  const functions = [];
1776
- const functionsPath = join5(srcPath, "function");
2357
+ const functionsPath = join6(srcPath, "function");
1777
2358
  for (const dir of FUNCTION_DIRS2) {
1778
- const dirPath = join5(functionsPath, dir);
2359
+ const dirPath = join6(functionsPath, dir);
1779
2360
  try {
1780
2361
  const items = await readdir2(dirPath, { withFileTypes: true });
1781
2362
  for (const item of items) {
1782
2363
  if (item.isFile() && (item.name.endsWith(".ts") || item.name.endsWith(".tsx"))) {
1783
- functions.push(join5(dirPath, item.name));
2364
+ functions.push(join6(dirPath, item.name));
1784
2365
  }
1785
2366
  }
1786
2367
  } catch {}
1787
2368
  }
1788
- const reactEntry = join5(srcPath, "index.tsx");
1789
- const hasReact = existsSync5(reactEntry);
2369
+ const reactEntry = join6(srcPath, "index.tsx");
2370
+ const hasReact = existsSync6(reactEntry);
1790
2371
  return { functions, reactEntry: hasReact ? reactEntry : null };
1791
2372
  };
1792
2373
  var deployAll = async (config, options) => {
1793
- const srcPath = join5(process.cwd(), "src");
1794
- if (!existsSync5(srcPath)) {
2374
+ const srcPath = join6(process.cwd(), "src");
2375
+ if (!existsSync6(srcPath)) {
1795
2376
  console.error(chalk3.red("\u2717 No src directory found"));
1796
2377
  process.exit(1);
1797
2378
  }
@@ -1875,7 +2456,7 @@ var registerDeployCommand = (program) => {
1875
2456
  return;
1876
2457
  }
1877
2458
  const filePath = resolve2(file);
1878
- if (!existsSync5(filePath)) {
2459
+ if (!existsSync6(filePath)) {
1879
2460
  console.error(chalk3.red(`\u2717 File not found: ${filePath}`));
1880
2461
  process.exit(1);
1881
2462
  }
@@ -2046,7 +2627,7 @@ import { Command as Command2 } from "commander";
2046
2627
  import chalk6 from "chalk";
2047
2628
  import { resolve as resolve3 } from "path";
2048
2629
  import { readFile, readdir as readdir3 } from "fs/promises";
2049
- import { existsSync as existsSync6 } from "fs";
2630
+ import { existsSync as existsSync7 } from "fs";
2050
2631
  import * as p3 from "@clack/prompts";
2051
2632
 
2052
2633
  // ../../packages/api/actions/secrets.ts
@@ -2281,7 +2862,7 @@ Secret keys:`));
2281
2862
  process.exit(1);
2282
2863
  }
2283
2864
  }
2284
- if (!existsSync6(filePath)) {
2865
+ if (!existsSync7(filePath)) {
2285
2866
  console.error(chalk6.red(`\u2717 File not found: ${filePath}`));
2286
2867
  process.exit(1);
2287
2868
  }
@@ -2331,7 +2912,7 @@ Secret keys:`));
2331
2912
  process.exit(1);
2332
2913
  }
2333
2914
  }
2334
- if (!existsSync6(filePath)) {
2915
+ if (!existsSync7(filePath)) {
2335
2916
  console.error(chalk6.red(`\u2717 File not found: ${filePath}`));
2336
2917
  process.exit(1);
2337
2918
  }
@@ -2370,8 +2951,8 @@ Secret keys:`));
2370
2951
  }));
2371
2952
  };
2372
2953
  // src/commands/host.ts
2373
- import { existsSync as existsSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2 } from "fs";
2374
- import { join as join6 } from "path";
2954
+ import { existsSync as existsSync8, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2 } from "fs";
2955
+ import { join as join7 } from "path";
2375
2956
  import { homedir } from "os";
2376
2957
  var {$: $2 } = globalThis.Bun;
2377
2958
  import chalk7 from "chalk";
@@ -2404,7 +2985,7 @@ var generateProductionKeys = async () => {
2404
2985
  return { privateKey, publicKey };
2405
2986
  };
2406
2987
  var saveProductionConfig = async (cloudPath, privateKey, publicKey) => {
2407
- const configPath = join6(cloudPath, "config.json");
2988
+ const configPath = join7(cloudPath, "config.json");
2408
2989
  const config = {
2409
2990
  key: {
2410
2991
  private: privateKey,
@@ -2438,7 +3019,7 @@ WantedBy=multi-user.target
2438
3019
  return serviceContent;
2439
3020
  };
2440
3021
  var ensureCloudDirectory = (cloudPath) => {
2441
- if (!existsSync7(cloudPath)) {
3022
+ if (!existsSync8(cloudPath)) {
2442
3023
  console.log(chalk7.blue("Creating cloud directory..."));
2443
3024
  try {
2444
3025
  mkdirSync3(cloudPath, { recursive: true });
@@ -2468,15 +3049,15 @@ var validateLinux = () => {
2468
3049
  }
2469
3050
  };
2470
3051
  var validateServerBinary = (serverBinPath) => {
2471
- if (!existsSync7(serverBinPath)) {
3052
+ if (!existsSync8(serverBinPath)) {
2472
3053
  console.log(chalk7.red("Server binary not found at:"), serverBinPath);
2473
3054
  console.log(chalk7.yellow("Make sure you have installed the package correctly"));
2474
3055
  process.exit(1);
2475
3056
  }
2476
3057
  };
2477
3058
  var checkExistingProductionConfig = (cloudPath) => {
2478
- const configPath = join6(cloudPath, "config.json");
2479
- if (!existsSync7(configPath)) {
3059
+ const configPath = join7(cloudPath, "config.json");
3060
+ if (!existsSync8(configPath)) {
2480
3061
  return null;
2481
3062
  }
2482
3063
  try {
@@ -2507,15 +3088,15 @@ var unhost = async (options = {}) => {
2507
3088
  console.log(chalk7.yellow("Service was not enabled"));
2508
3089
  }
2509
3090
  const servicePath = "/etc/systemd/system/nulljs.service";
2510
- if (existsSync7(servicePath)) {
3091
+ if (existsSync8(servicePath)) {
2511
3092
  console.log(chalk7.blue("Removing systemd service file..."));
2512
3093
  await $2`sudo rm ${servicePath}`;
2513
3094
  await $2`sudo systemctl daemon-reload`;
2514
3095
  console.log(chalk7.green("Systemd service file removed"));
2515
3096
  }
2516
3097
  if (!options.keepData) {
2517
- const defaultCloudPath = join6(homedir(), ".nulljs");
2518
- if (existsSync7(defaultCloudPath)) {
3098
+ const defaultCloudPath = join7(homedir(), ".nulljs");
3099
+ if (existsSync8(defaultCloudPath)) {
2519
3100
  console.log(chalk7.blue("Removing cloud directory..."));
2520
3101
  await $2`rm -rf ${defaultCloudPath}`;
2521
3102
  console.log(chalk7.green("Cloud directory removed"));
@@ -2539,9 +3120,9 @@ var unhost = async (options = {}) => {
2539
3120
  };
2540
3121
  var update = async () => {
2541
3122
  validateLinux();
2542
- const cloudPath = join6(homedir(), ".nulljs");
3123
+ const cloudPath = join7(homedir(), ".nulljs");
2543
3124
  const servicePath = "/etc/systemd/system/nulljs.service";
2544
- if (!existsSync7(servicePath)) {
3125
+ if (!existsSync8(servicePath)) {
2545
3126
  console.log(chalk7.red("NullJS service is not installed."));
2546
3127
  console.log(chalk7.gray('Run "nulljs host" to set up hosting first.'));
2547
3128
  process.exit(1);
@@ -2572,7 +3153,7 @@ var update = async () => {
2572
3153
  };
2573
3154
  var host = async (cloudPath) => {
2574
3155
  validateLinux();
2575
- const resolvedCloudPath = cloudPath ? cloudPath.startsWith("/") ? cloudPath : join6(process.cwd(), cloudPath) : join6(homedir(), ".nulljs");
3156
+ const resolvedCloudPath = cloudPath ? cloudPath.startsWith("/") ? cloudPath : join7(process.cwd(), cloudPath) : join7(homedir(), ".nulljs");
2576
3157
  console.log(chalk7.blue("Setting up NullJS production hosting..."));
2577
3158
  console.log(chalk7.gray(` Cloud path: ${resolvedCloudPath}`));
2578
3159
  const serverBinPath = getServerBinPath();