@tothalex/nulljs 0.0.59 → 0.0.61

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 +1519 -869
  2. package/package.json +4 -4
package/dist/cli.js CHANGED
@@ -12,259 +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
-
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}`;
35
- }
36
- }
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
15
 
52
- // ../../packages/api/actions/deployments.ts
53
- var init_deployments = () => {};
54
-
55
- // ../../packages/api/actions/deploy.ts
56
- import { mkdtempSync, writeFileSync, rmSync } from "fs";
57
- import { tmpdir } from "os";
58
- import { join } from "path";
59
- var {$ } = globalThis.Bun;
60
- var getMimeType = (fileName) => {
61
- if (fileName.endsWith(".js"))
62
- return "application/javascript";
63
- if (fileName.endsWith(".css"))
64
- return "text/css";
65
- if (fileName.endsWith(".html"))
66
- return "text/html";
67
- if (fileName.endsWith(".json"))
68
- return "application/json";
69
- return "application/octet-stream";
70
- }, createFormDataWithInfo = (assets2) => {
71
- const form = new FormData;
72
- const blobs = [];
73
- for (const asset of assets2) {
74
- const mime = getMimeType(asset.fileName);
75
- const buffer = Buffer.from(asset.code);
76
- const blob = new Blob([buffer], { type: mime });
77
- form.append(asset.fileName, blob);
78
- blobs.push({ fileName: asset.fileName, size: blob.size, type: mime });
79
- }
80
- return { form, blobs };
81
- }, createSignature = async (blobs, privateKey) => {
82
- const sortedBlobs = [...blobs].sort((a, b) => a.fileName.localeCompare(b.fileName));
83
- const dataString = sortedBlobs.map((b) => `${b.fileName}:${b.size}:${b.type}`).join("|");
84
- const sign = await crypto.subtle.sign("Ed25519", privateKey, Buffer.from(dataString));
85
- return btoa(String.fromCharCode(...new Uint8Array(sign)));
86
- }, postWithCurl = async (url, signature, assets2) => {
87
- const tempDir = mkdtempSync(join(tmpdir(), "nulljs-deploy-"));
88
- try {
89
- const formParts = [];
90
- for (const asset of assets2) {
91
- const filePath = join(tempDir, asset.fileName);
92
- writeFileSync(filePath, asset.code);
93
- const mime = getMimeType(asset.fileName);
94
- formParts.push(`-F`);
95
- formParts.push(`${asset.fileName}=@${filePath};type=${mime}`);
96
- }
97
- const result = await $`curl -s -w '\n%{http_code}' -X POST -H "authorization: ${signature}" ${formParts} ${url}`.text();
98
- const lines = result.trim().split(`
99
- `);
100
- const statusCode = parseInt(lines.pop() || "0", 10);
101
- const body = lines.join(`
102
- `);
103
- return { ok: statusCode >= 200 && statusCode < 300, status: statusCode, text: body };
104
- } finally {
105
- rmSync(tempDir, { recursive: true, force: true });
106
- }
107
- }, createFunctionDeployment = async (deployment2, options) => {
108
- const handler = deployment2.assets.find((asset) => asset.fileName === "handler.js");
109
- if (!handler) {
110
- throw new Error(`Handler not found for ${deployment2.name}`);
111
- }
112
- const { blobs } = createFormDataWithInfo([handler]);
113
- const signature = await createSignature(blobs, options.privateKey);
114
- const baseUrl = options.url || "";
115
- const url = `${baseUrl}/api/deployment`;
116
- if (options.useCurl) {
117
- const result = await postWithCurl(url, signature, [handler]);
118
- if (!result.ok) {
119
- throw new Error(`Deployment failed (${result.status}): ${result.text}`);
120
- }
121
- return;
122
- }
123
- const { form } = createFormDataWithInfo([handler]);
124
- const response = await fetch(url, {
125
- method: "POST",
126
- headers: {
127
- authorization: signature
128
- },
129
- body: form
130
- });
131
- if (!response.ok) {
132
- const errorText = await response.text();
133
- throw new Error(`Deployment failed (${response.status}): ${errorText}`);
134
- }
135
- }, createReactDeployment = async (deployment2, options) => {
136
- const { blobs } = createFormDataWithInfo(deployment2.assets);
137
- const signature = await createSignature(blobs, options.privateKey);
138
- const baseUrl = options.url || "";
139
- const url = `${baseUrl}/api/deployment/react`;
140
- if (options.useCurl) {
141
- const result = await postWithCurl(url, signature, deployment2.assets);
142
- if (!result.ok) {
143
- throw new Error(`Deployment failed (${result.status}): ${result.text}`);
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;
144
24
  }
145
- return;
146
- }
147
- const { form } = createFormDataWithInfo(deployment2.assets);
148
- const response = await fetch(url, {
149
- method: "POST",
150
- headers: {
151
- authorization: signature
152
- },
153
- body: form
154
- });
155
- if (!response.ok) {
156
- const errorText = await response.text();
157
- throw new Error(`Deployment failed (${response.status}): ${errorText}`);
158
- }
159
- };
160
- var init_deploy = () => {};
161
-
162
- // ../../packages/api/actions/invocations.ts
163
- var init_invocations = () => {};
164
-
165
- // ../../packages/api/actions/logs.ts
166
- var getSystemLogs = async (public_key, query, url) => {
167
- const path = buildUrl("/api/system/logs", { url, query });
168
- return apiGet(path, { publicKey: public_key });
169
- }, searchDeploymentLogs = async (public_key, props, url) => {
170
- const path = buildUrl("/api/logs/search", { url, query: props });
171
- return apiGet(path, { publicKey: public_key });
172
- };
173
- var init_logs = () => {};
174
-
175
- // ../../packages/api/actions/assets.ts
176
- var init_assets = () => {};
177
-
178
- // ../../packages/api/actions/activity.ts
179
- var init_activity = () => {};
180
-
181
- // ../../packages/api/actions/secrets.ts
182
- var createSignatureHeader = async (privateKey, props) => {
183
- const timestamp = new Date().toISOString();
184
- const raw = `${props.method}-${props.path}-${timestamp}${props.body ? "-" + props.body : ""}`;
185
- const sign = await crypto.subtle.sign("Ed25519", privateKey, Buffer.from(raw));
186
- const signature = btoa(String.fromCharCode(...new Uint8Array(sign)));
187
- const header = {
188
- authorization: signature,
189
- "x-time": timestamp
190
- };
191
- if (props.body) {
192
- header["x-body"] = btoa(props.body);
193
- }
194
- return header;
195
- }, listSecrets = async (options) => {
196
- const baseUrl = options.url || "";
197
- const path = `${baseUrl}/api/secrets`;
198
- const headers = await createSignatureHeader(options.privateKey, {
199
- method: "GET",
200
- path
201
- });
202
- let response;
203
- try {
204
- response = await fetch(path, {
205
- method: "GET",
206
- headers
207
- });
208
- } catch (err) {
209
- throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
210
- }
211
- if (!response.ok) {
212
- const errorText = await response.text();
213
- throw new Error(`Failed to list secrets (${response.status}): ${errorText}`);
214
- }
215
- return response.json();
216
- }, createSecret = async (secret, options) => {
217
- const baseUrl = options.url || "";
218
- const path = `${baseUrl}/api/secrets`;
219
- const body = JSON.stringify(secret);
220
- const headers = await createSignatureHeader(options.privateKey, {
221
- method: "POST",
222
- path,
223
- body
224
- });
225
- const response = await fetch(path, {
226
- method: "POST",
227
- headers: {
228
- "Content-Type": "application/json",
229
- ...headers
230
- },
231
- body
232
- });
233
- if (!response.ok) {
234
- const errorText = await response.text();
235
- throw new Error(`Failed to create secret (${response.status}): ${errorText}`);
25
+ currentPath = dirname(currentPath);
236
26
  }
27
+ return startPath;
28
+ }, getCloudPath = () => {
29
+ return join(findProjectRoot(), ".nulljs");
237
30
  };
238
-
239
- // ../../packages/api/actions/index.ts
240
- var init_actions = __esm(() => {
241
- init_deployments();
242
- init_deploy();
243
- init_invocations();
244
- init_logs();
245
- init_assets();
246
- init_activity();
247
- });
248
-
249
- // ../../packages/api/index.ts
250
- var init_api = __esm(() => {
251
- init_types();
252
- init_actions();
253
- });
31
+ var init_paths = () => {};
254
32
 
255
33
  // src/config/index.ts
256
- import { readFileSync, writeFileSync as writeFileSync2, existsSync, mkdirSync } from "fs";
34
+ import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync, readdirSync, rmSync } from "fs";
257
35
  import { join as join2 } from "path";
258
36
  import chalk from "chalk";
259
- var getLocalConfigDir = () => join2(process.cwd(), ".nulljs"), getLocalConfigFile = () => join2(getLocalConfigDir(), "config.json"), ensureLocalConfigDir = () => {
37
+ var getLocalConfigDir = () => getCloudPath(), getLocalConfigFile = () => join2(getLocalConfigDir(), "config.json"), ensureLocalConfigDir = () => {
260
38
  const localDir = getLocalConfigDir();
261
- if (!existsSync(localDir)) {
39
+ if (!existsSync2(localDir)) {
262
40
  mkdirSync(localDir, { recursive: true });
263
41
  }
264
42
  }, readConfigStore = () => {
265
43
  try {
266
44
  const localFile = getLocalConfigFile();
267
- if (!existsSync(localFile)) {
45
+ if (!existsSync2(localFile)) {
268
46
  return null;
269
47
  }
270
48
  const data = readFileSync(localFile, "utf-8");
@@ -274,7 +52,7 @@ var getLocalConfigDir = () => join2(process.cwd(), ".nulljs"), getLocalConfigFil
274
52
  }
275
53
  }, writeConfigStore = (store) => {
276
54
  ensureLocalConfigDir();
277
- writeFileSync2(getLocalConfigFile(), JSON.stringify(store, null, 2));
55
+ writeFileSync(getLocalConfigFile(), JSON.stringify(store, null, 2));
278
56
  }, readLocalConfig = () => {
279
57
  const store = readConfigStore();
280
58
  if (!store)
@@ -368,46 +146,59 @@ var getLocalConfigDir = () => join2(process.cwd(), ".nulljs"), getLocalConfigFil
368
146
  }, loadPrivateKey = async (config) => {
369
147
  const privateKeyBytes = Uint8Array.from(atob(config.key.private), (c) => c.charCodeAt(0));
370
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 };
371
169
  };
372
- var init_config = () => {};
170
+ var init_config = __esm(() => {
171
+ init_paths();
172
+ });
373
173
 
374
174
  // src/lib/server.ts
375
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, realpathSync } from "fs";
376
- import { join as join3, dirname } from "path";
175
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, realpathSync } from "fs";
176
+ import { join as join3, dirname as dirname2 } from "path";
377
177
  import chalk2 from "chalk";
378
178
  var CLI_DIR, PLATFORMS, getPlatformKey = () => {
379
179
  return `${process.platform}-${process.arch}`;
380
- }, findProjectRoot = (startPath = process.cwd()) => {
381
- let currentPath = startPath;
382
- while (currentPath !== dirname(currentPath)) {
383
- if (existsSync2(join3(currentPath, "package.json"))) {
384
- return currentPath;
385
- }
386
- currentPath = dirname(currentPath);
387
- }
388
- return startPath;
389
180
  }, findMonorepoRoot = (startPath = process.cwd()) => {
390
181
  let currentPath = startPath;
391
- while (currentPath !== dirname(currentPath)) {
392
- if (existsSync2(join3(currentPath, "Cargo.toml")) && existsSync2(join3(currentPath, "package.json"))) {
182
+ while (currentPath !== dirname2(currentPath)) {
183
+ if (existsSync3(join3(currentPath, "Cargo.toml")) && existsSync3(join3(currentPath, "package.json"))) {
393
184
  return currentPath;
394
185
  }
395
- currentPath = dirname(currentPath);
186
+ currentPath = dirname2(currentPath);
396
187
  }
397
188
  return null;
398
189
  }, getLocalBinaryPath = () => {
399
190
  const envBinary = process.env.NULLJS_SERVER_BINARY;
400
- if (envBinary && existsSync2(envBinary)) {
191
+ if (envBinary && existsSync3(envBinary)) {
401
192
  return { path: envBinary, source: "local-env" };
402
193
  }
403
194
  const monorepoRoot = findMonorepoRoot() || findMonorepoRoot(CLI_DIR);
404
195
  if (monorepoRoot) {
405
196
  const debugBinary = join3(monorepoRoot, "target", "debug", "server");
406
- if (existsSync2(debugBinary)) {
197
+ if (existsSync3(debugBinary)) {
407
198
  return { path: debugBinary, source: "local-debug" };
408
199
  }
409
200
  const releaseBinary = join3(monorepoRoot, "target", "release", "server");
410
- if (existsSync2(releaseBinary)) {
201
+ if (existsSync3(releaseBinary)) {
411
202
  return { path: releaseBinary, source: "local-release" };
412
203
  }
413
204
  }
@@ -428,10 +219,9 @@ var CLI_DIR, PLATFORMS, getPlatformKey = () => {
428
219
  const args = ["--dev"];
429
220
  args.push("--api-port", "3000");
430
221
  args.push("--gateway-port", "3001");
431
- const projectRoot = findProjectRoot();
432
- const cloudPath = join3(projectRoot, ".nulljs");
222
+ const cloudPath = getCloudPath();
433
223
  args.push("--cloud-path", cloudPath);
434
- if (!existsSync2(cloudPath)) {
224
+ if (!existsSync3(cloudPath)) {
435
225
  mkdirSync2(cloudPath, { recursive: true });
436
226
  }
437
227
  args.push("--public-key", devConfig.key.public);
@@ -473,7 +263,8 @@ var CLI_DIR, PLATFORMS, getPlatformKey = () => {
473
263
  };
474
264
  var init_server = __esm(() => {
475
265
  init_config();
476
- CLI_DIR = dirname(realpathSync(import.meta.dir));
266
+ init_paths();
267
+ CLI_DIR = dirname2(realpathSync(import.meta.dir));
477
268
  PLATFORMS = {
478
269
  "linux-x64": "@tothalex/nulljs-linux-x64",
479
270
  "linux-arm64": "@tothalex/nulljs-linux-arm64",
@@ -680,77 +471,254 @@ var init_bundle = __esm(() => {
680
471
  init_function();
681
472
  });
682
473
 
683
- // src/lib/deploy.ts
684
- var exports_deploy = {};
685
- __export(exports_deploy, {
686
- deployReact: () => deployReact,
687
- deployFunction: () => deployFunction,
688
- clearDeployCache: () => clearDeployCache
689
- });
690
- import { basename as basename2 } from "path";
691
- import { build } from "vite";
692
- var deployedHashes, clearDeployCache = () => {
693
- deployedHashes.clear();
694
- }, hashCode = (code) => {
695
- return Bun.hash(code).toString(16);
696
- }, deployFunction = async (filePath, privateKey, apiUrl, useCurl) => {
697
- const fileName = basename2(filePath);
698
- const result = await build(functionConfig(filePath));
699
- const handler = result.output.find((output) => output.type === "chunk" && output.fileName === "handler.js");
700
- if (!handler) {
701
- throw new Error("Bundle failed: handler.js not found in output");
474
+ // ../../packages/api/actions/http.ts
475
+ import { mkdtempSync, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "fs";
476
+ import { tmpdir } from "os";
477
+ import { join as join4 } from "path";
478
+ var {$ } = globalThis.Bun;
479
+ var parseCurlResponse = (result) => {
480
+ const lines = result.trim().split(`
481
+ `);
482
+ const status = parseInt(lines.pop() || "0", 10);
483
+ const body = lines.join(`
484
+ `);
485
+ return {
486
+ ok: status >= 200 && status < 300,
487
+ status,
488
+ body
489
+ };
490
+ }, buildHeaderFlags = (headers) => {
491
+ const flags = [];
492
+ for (const [key, value] of Object.entries(headers)) {
493
+ flags.push("-H");
494
+ flags.push(`${key}: ${value}`);
495
+ }
496
+ return flags;
497
+ }, httpGet = async (path, options) => {
498
+ const url = options?.baseUrl ? `${options.baseUrl}${path}` : path;
499
+ const headers = options?.headers || {};
500
+ if (options?.useCurl) {
501
+ const headerFlags = buildHeaderFlags(headers);
502
+ const result = await $`curl -s -w '\n%{http_code}' -X GET ${headerFlags} ${url}`.text();
503
+ return parseCurlResponse(result);
702
504
  }
703
- const hash = hashCode(handler.code);
704
- const lastHash = deployedHashes.get(filePath);
705
- if (lastHash === hash) {
706
- return { deployed: false, skipped: true };
505
+ const response = await fetch(url, {
506
+ method: "GET",
507
+ headers
508
+ });
509
+ return {
510
+ ok: response.ok,
511
+ status: response.status,
512
+ body: await response.text()
513
+ };
514
+ }, httpPost = async (path, body, options) => {
515
+ const url = options?.baseUrl ? `${options.baseUrl}${path}` : path;
516
+ const headers = options?.headers || {};
517
+ if (options?.useCurl) {
518
+ const tempDir = mkdtempSync(join4(tmpdir(), "nulljs-http-"));
519
+ const bodyFile = join4(tempDir, "body.json");
520
+ try {
521
+ writeFileSync2(bodyFile, body);
522
+ const headerFlags = buildHeaderFlags(headers);
523
+ const result = await $`curl -s -w '\n%{http_code}' -X POST ${headerFlags} -d @${bodyFile} ${url}`.text();
524
+ return parseCurlResponse(result);
525
+ } finally {
526
+ rmSync2(tempDir, { recursive: true, force: true });
527
+ }
707
528
  }
708
- await createFunctionDeployment({ name: fileName, assets: [{ fileName: "handler.js", code: handler.code }] }, { privateKey, url: apiUrl, useCurl });
709
- deployedHashes.set(filePath, hash);
710
- return { deployed: true, skipped: false };
711
- }, deployReact = async (filePath, privateKey, apiUrl, useCurl) => {
712
- const fileName = basename2(filePath);
713
- const result = await build(spaClientConfig(filePath));
714
- const assets3 = [];
715
- for (const output of result.output) {
716
- if (output.type === "chunk") {
717
- assets3.push({ fileName: output.fileName, code: output.code });
718
- } else if (output.type === "asset" && typeof output.source === "string") {
719
- assets3.push({ fileName: output.fileName, code: output.source });
529
+ const response = await fetch(url, {
530
+ method: "POST",
531
+ headers,
532
+ body
533
+ });
534
+ return {
535
+ ok: response.ok,
536
+ status: response.status,
537
+ body: await response.text()
538
+ };
539
+ }, httpPostFormData = async (path, files, options) => {
540
+ const url = options?.baseUrl ? `${options.baseUrl}${path}` : path;
541
+ const headers = options?.headers || {};
542
+ if (options?.useCurl) {
543
+ const tempDir = mkdtempSync(join4(tmpdir(), "nulljs-http-"));
544
+ try {
545
+ const formParts = [];
546
+ for (const file of files) {
547
+ const filePath = join4(tempDir, file.fileName);
548
+ writeFileSync2(filePath, file.content);
549
+ formParts.push("-F");
550
+ formParts.push(`${file.fileName}=@${filePath};type=${file.mimeType}`);
551
+ }
552
+ const headerFlags = buildHeaderFlags(headers);
553
+ const result = await $`curl -s -w '\n%{http_code}' -X POST ${headerFlags} ${formParts} ${url}`.text();
554
+ return parseCurlResponse(result);
555
+ } finally {
556
+ rmSync2(tempDir, { recursive: true, force: true });
720
557
  }
721
558
  }
722
- const combinedCode = assets3.map((a) => a.code).join("");
723
- const hash = hashCode(combinedCode);
724
- const lastHash = deployedHashes.get(filePath);
725
- if (lastHash === hash) {
726
- return { deployed: false, skipped: true };
559
+ const form = new FormData;
560
+ for (const file of files) {
561
+ const buffer = Buffer.from(file.content);
562
+ const blob = new Blob([buffer], { type: file.mimeType });
563
+ form.append(file.fileName, blob);
727
564
  }
728
- await createReactDeployment({ name: fileName, assets: assets3 }, { privateKey, url: apiUrl, useCurl });
729
- deployedHashes.set(filePath, hash);
730
- return { deployed: true, skipped: false };
731
- };
732
- var init_deploy2 = __esm(() => {
733
- init_api();
565
+ const response = await fetch(url, {
566
+ method: "POST",
567
+ headers,
568
+ body: form
569
+ });
570
+ return {
571
+ ok: response.ok,
572
+ status: response.status,
573
+ body: await response.text()
574
+ };
575
+ };
576
+ var init_http = () => {};
577
+
578
+ // ../../packages/api/actions/deploy.ts
579
+ var getMimeType = (fileName) => {
580
+ if (fileName.endsWith(".js"))
581
+ return "application/javascript";
582
+ if (fileName.endsWith(".css"))
583
+ return "text/css";
584
+ if (fileName.endsWith(".html"))
585
+ return "text/html";
586
+ if (fileName.endsWith(".json"))
587
+ return "application/json";
588
+ return "application/octet-stream";
589
+ }, assetsToFileData = (assets) => {
590
+ const files = [];
591
+ const blobs = [];
592
+ for (const asset of assets) {
593
+ const mimeType = getMimeType(asset.fileName);
594
+ const buffer = Buffer.from(asset.code);
595
+ files.push({
596
+ fileName: asset.fileName,
597
+ content: asset.code,
598
+ mimeType
599
+ });
600
+ blobs.push({
601
+ fileName: asset.fileName,
602
+ size: buffer.length,
603
+ type: mimeType
604
+ });
605
+ }
606
+ return { files, blobs };
607
+ }, createSignature = async (blobs, privateKey) => {
608
+ const sortedBlobs = [...blobs].sort((a, b) => a.fileName.localeCompare(b.fileName));
609
+ const dataString = sortedBlobs.map((b) => `${b.fileName}:${b.size}:${b.type}`).join("|");
610
+ const sign = await crypto.subtle.sign("Ed25519", privateKey, Buffer.from(dataString));
611
+ return btoa(String.fromCharCode(...new Uint8Array(sign)));
612
+ }, createFunctionDeployment = async (deployment, options) => {
613
+ const handler = deployment.assets.find((asset) => asset.fileName === "handler.js");
614
+ if (!handler) {
615
+ throw new Error(`Handler not found for ${deployment.name}`);
616
+ }
617
+ const { files, blobs } = assetsToFileData([handler]);
618
+ const signature = await createSignature(blobs, options.privateKey);
619
+ const baseUrl = options.url || "";
620
+ const path = "/api/deployment";
621
+ const result = await httpPostFormData(path, files, {
622
+ baseUrl,
623
+ useCurl: options.useCurl,
624
+ headers: {
625
+ authorization: signature
626
+ }
627
+ });
628
+ if (!result.ok) {
629
+ throw new Error(`Deployment failed (${result.status}): ${result.body}`);
630
+ }
631
+ }, createReactDeployment = async (deployment, options) => {
632
+ const { files, blobs } = assetsToFileData(deployment.assets);
633
+ const signature = await createSignature(blobs, options.privateKey);
634
+ const baseUrl = options.url || "";
635
+ const path = "/api/deployment/react";
636
+ const result = await httpPostFormData(path, files, {
637
+ baseUrl,
638
+ useCurl: options.useCurl,
639
+ headers: {
640
+ authorization: signature
641
+ }
642
+ });
643
+ if (!result.ok) {
644
+ throw new Error(`Deployment failed (${result.status}): ${result.body}`);
645
+ }
646
+ };
647
+ var init_deploy = __esm(() => {
648
+ init_http();
649
+ });
650
+
651
+ // src/lib/deploy.ts
652
+ var exports_deploy = {};
653
+ __export(exports_deploy, {
654
+ deployReact: () => deployReact,
655
+ deployFunction: () => deployFunction,
656
+ clearDeployCache: () => clearDeployCache
657
+ });
658
+ import { basename as basename2 } from "path";
659
+ import { build } from "vite";
660
+ var deployedHashes, clearDeployCache = () => {
661
+ deployedHashes.clear();
662
+ }, hashCode = (code) => {
663
+ return Bun.hash(code).toString(16);
664
+ }, deployFunction = async (filePath, privateKey, apiUrl, useCurl) => {
665
+ const fileName = basename2(filePath);
666
+ const result = await build(functionConfig(filePath));
667
+ const handler = result.output.find((output) => output.type === "chunk" && output.fileName === "handler.js");
668
+ if (!handler) {
669
+ throw new Error("Bundle failed: handler.js not found in output");
670
+ }
671
+ const hash = hashCode(handler.code);
672
+ const lastHash = deployedHashes.get(filePath);
673
+ if (lastHash === hash) {
674
+ return { deployed: false, skipped: true };
675
+ }
676
+ await createFunctionDeployment({ name: fileName, assets: [{ fileName: "handler.js", code: handler.code }] }, { privateKey, url: apiUrl, useCurl });
677
+ deployedHashes.set(filePath, hash);
678
+ return { deployed: true, skipped: false };
679
+ }, deployReact = async (filePath, privateKey, apiUrl, useCurl) => {
680
+ const fileName = basename2(filePath);
681
+ const result = await build(spaClientConfig(filePath));
682
+ const assets = [];
683
+ for (const output of result.output) {
684
+ if (output.type === "chunk") {
685
+ assets.push({ fileName: output.fileName, code: output.code });
686
+ } else if (output.type === "asset" && typeof output.source === "string") {
687
+ assets.push({ fileName: output.fileName, code: output.source });
688
+ }
689
+ }
690
+ const combinedCode = assets.map((a) => a.code).join("");
691
+ const hash = hashCode(combinedCode);
692
+ const lastHash = deployedHashes.get(filePath);
693
+ if (lastHash === hash) {
694
+ return { deployed: false, skipped: true };
695
+ }
696
+ await createReactDeployment({ name: fileName, assets }, { privateKey, url: apiUrl, useCurl });
697
+ deployedHashes.set(filePath, hash);
698
+ return { deployed: true, skipped: false };
699
+ };
700
+ var init_deploy2 = __esm(() => {
701
+ init_deploy();
734
702
  init_bundle();
735
703
  deployedHashes = new Map;
736
704
  });
737
705
 
738
706
  // src/lib/watcher.ts
739
- import { watch, existsSync as existsSync3 } from "fs";
707
+ import { watch, existsSync as existsSync4 } from "fs";
740
708
  import { readdir } from "fs/promises";
741
- import { join as join4 } from "path";
709
+ import { join as join5 } from "path";
742
710
  import { build as build2 } from "vite";
743
711
  var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
744
712
  return Bun.hash(code).toString(16);
745
713
  }, findFunctionEntries = async (functionsPath) => {
746
714
  const entries = [];
747
715
  for (const dir of FUNCTION_DIRS) {
748
- const dirPath = join4(functionsPath, dir);
716
+ const dirPath = join5(functionsPath, dir);
749
717
  try {
750
718
  const items = await readdir(dirPath, { withFileTypes: true });
751
719
  for (const item of items) {
752
720
  if (item.isFile() && (item.name.endsWith(".ts") || item.name.endsWith(".tsx"))) {
753
- const fullPath = join4(dirPath, item.name);
721
+ const fullPath = join5(dirPath, item.name);
754
722
  const name = `${dir}/${item.name.replace(/\.tsx?$/, "")}`;
755
723
  entries.push({
756
724
  name,
@@ -770,7 +738,7 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
770
738
  console.error('No local configuration found. Run "nulljs dev" first.');
771
739
  return () => {};
772
740
  }
773
- const functionsPath = join4(srcPath, "function");
741
+ const functionsPath = join5(srcPath, "function");
774
742
  let entries = await findFunctionEntries(functionsPath);
775
743
  let viteWatcher = null;
776
744
  let isRestarting = false;
@@ -841,8 +809,8 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
841
809
  viteWatcher = await startViteWatcher();
842
810
  const dirWatchers = [];
843
811
  for (const dir of FUNCTION_DIRS) {
844
- const dirPath = join4(functionsPath, dir);
845
- if (!existsSync3(dirPath))
812
+ const dirPath = join5(functionsPath, dir);
813
+ if (!existsSync4(dirPath))
846
814
  continue;
847
815
  try {
848
816
  const watcher = watch(dirPath, async (eventType, filename) => {
@@ -850,10 +818,10 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
850
818
  return;
851
819
  if (!filename.endsWith(".ts") && !filename.endsWith(".tsx"))
852
820
  return;
853
- const fullPath = join4(dirPath, filename);
821
+ const fullPath = join5(dirPath, filename);
854
822
  const entryName = `${dir}/${filename.replace(/\.tsx?$/, "")}`;
855
823
  const existingEntry = entries.find((e) => e.name === entryName);
856
- const fileExists = existsSync3(fullPath);
824
+ const fileExists = existsSync4(fullPath);
857
825
  if (!existingEntry && fileExists || existingEntry && !fileExists) {
858
826
  await restartViteWatcher();
859
827
  }
@@ -870,23 +838,25 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
870
838
  }
871
839
  };
872
840
  }, forceDeployAll = async (srcPath, options = {}) => {
873
- const { onDeploy, onComplete } = options;
841
+ const { onStart, onDeploy, onComplete } = options;
874
842
  const config = readLocalConfig();
875
843
  if (!config) {
876
844
  throw new Error('No local configuration found. Run "nulljs dev" first.');
877
845
  }
878
846
  const privateKey = await loadPrivateKey(config);
879
847
  deployedHashes2.clear();
880
- const functionsPath = join4(srcPath, "function");
848
+ const functionsPath = join5(srcPath, "function");
881
849
  const entries = await findFunctionEntries(functionsPath);
882
- const reactEntry = join4(srcPath, "index.tsx");
883
- const hasReact = existsSync3(reactEntry);
884
- 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);
885
853
  let successful = 0;
886
854
  let failed = 0;
855
+ let completed = 0;
887
856
  const { deployFunction: deployFunction2, deployReact: deployReact2, clearDeployCache: clearDeployCache2 } = await Promise.resolve().then(() => (init_deploy2(), exports_deploy));
888
857
  clearDeployCache2();
889
858
  for (const entry of entries) {
859
+ onStart?.(entry.name, completed, total);
890
860
  try {
891
861
  await deployFunction2(entry.path, privateKey, config.api);
892
862
  successful++;
@@ -895,9 +865,11 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
895
865
  failed++;
896
866
  onDeploy?.(entry.name, false, error instanceof Error ? error.message : String(error));
897
867
  }
868
+ completed++;
898
869
  }
899
870
  if (hasReact) {
900
871
  const fileName = "index.tsx";
872
+ onStart?.(fileName, completed, total);
901
873
  try {
902
874
  await deployReact2(reactEntry, privateKey, config.api);
903
875
  successful++;
@@ -906,24 +878,25 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
906
878
  failed++;
907
879
  onDeploy?.(fileName, false, error instanceof Error ? error.message : String(error));
908
880
  }
881
+ completed++;
909
882
  }
910
883
  onComplete?.(total, successful, failed);
911
884
  };
912
885
  var init_watcher = __esm(() => {
913
886
  init_config();
914
887
  init_bundle();
915
- init_api();
888
+ init_deploy();
916
889
  FUNCTION_DIRS = ["api", "cron", "event"];
917
890
  deployedHashes2 = new Map;
918
891
  });
919
892
 
920
893
  // src/lib/vite.ts
921
894
  import { createServer } from "vite";
922
- import { dirname as dirname2, resolve, basename as basename3 } from "path";
895
+ import { dirname as dirname3, resolve, basename as basename3 } from "path";
923
896
  import react3 from "@vitejs/plugin-react";
924
897
  import tailwindcss2 from "@tailwindcss/vite";
925
898
  import { writeFile, unlink } from "fs/promises";
926
- import { existsSync as existsSync4 } from "fs";
899
+ import { existsSync as existsSync5 } from "fs";
927
900
  var VITE_PORT = 5173, GATEWAY_PORT = 3001, createViteLogger = (onLog) => {
928
901
  return {
929
902
  info: (msg) => onLog?.(msg, "info"),
@@ -967,10 +940,10 @@ var VITE_PORT = 5173, GATEWAY_PORT = 3001, createViteLogger = (onLog) => {
967
940
  </html>`;
968
941
  }, startViteServer = async (options) => {
969
942
  const indexTsxPath = resolve(options.srcDir, "index.tsx");
970
- if (!existsSync4(indexTsxPath)) {
943
+ if (!existsSync5(indexTsxPath)) {
971
944
  return null;
972
945
  }
973
- const projectDir = dirname2(options.srcDir);
946
+ const projectDir = dirname3(options.srcDir);
974
947
  const tempIndexPath = resolve(projectDir, "index.html");
975
948
  try {
976
949
  const indexHtml = createTempIndexHtml(indexTsxPath, projectDir);
@@ -1011,235 +984,397 @@ var VITE_PORT = 5173, GATEWAY_PORT = 3001, createViteLogger = (onLog) => {
1011
984
  };
1012
985
  var init_vite = () => {};
1013
986
 
1014
- // src/components/DeploymentLogsPane.tsx
1015
- import { jsxDEV } from "@opentui/react/jsx-dev-runtime";
1016
- var getLevelColor = (level) => {
1017
- switch (level.toLowerCase()) {
1018
- case "error":
1019
- return "red";
1020
- case "warn":
1021
- case "warning":
1022
- return "yellow";
1023
- case "info":
1024
- return "cyan";
1025
- case "debug":
1026
- return "gray";
1027
- default:
1028
- return "white";
1029
- }
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";
1030
991
  }, sanitizeMessage = (message) => {
1031
992
  return message.replace(/\x1b\[[0-9;]*m/g, "").replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
1032
- }, DeploymentLogsPane = (props) => {
1033
- if (props.loading && props.logs.length === 0) {
1034
- return /* @__PURE__ */ jsxDEV("box", {
1035
- flexDirection: "column",
1036
- padding: 1,
1037
- children: /* @__PURE__ */ jsxDEV("text", {
1038
- children: "Loading deployment logs..."
1039
- }, undefined, false, undefined, this)
1040
- }, undefined, false, undefined, this);
1041
- }
1042
- if (!props.loading && props.logs.length === 0) {
1043
- return /* @__PURE__ */ jsxDEV("box", {
1044
- flexDirection: "column",
1045
- padding: 1,
1046
- children: /* @__PURE__ */ jsxDEV("text", {
1047
- children: "No deployment logs found"
1048
- }, undefined, false, undefined, this)
1049
- }, 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;
1050
1038
  }
1051
- const logsInOrder = [...props.logs].reverse();
1052
- return /* @__PURE__ */ jsxDEV("scrollbox", {
1053
- focused: true,
1054
- stickyStart: props.autoScroll ? "bottom" : "top",
1055
- stickyScroll: props.autoScroll,
1056
- children: logsInOrder.map((log) => {
1057
- const time = new Date(log.time).toLocaleTimeString();
1058
- const timestamp = `[${time}]`;
1059
- const level = log.level.toUpperCase();
1060
- return /* @__PURE__ */ jsxDEV("box", {
1061
- flexDirection: "row",
1062
- 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,
1063
1060
  children: [
1064
- /* @__PURE__ */ jsxDEV("span", {
1065
- fg: "gray",
1066
- children: timestamp
1067
- }, undefined, false, undefined, this),
1068
- /* @__PURE__ */ jsxDEV("span", {
1069
- fg: getLevelColor(log.level),
1070
- children: ` ${level} `
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)
1071
1066
  }, undefined, false, undefined, this),
1072
- /* @__PURE__ */ jsxDEV("span", {
1073
- fg: "blue",
1074
- children: `(${log.deployment_name})`
1067
+ /* @__PURE__ */ jsxDEV("text", {
1068
+ children: /* @__PURE__ */ jsxDEV("span", {
1069
+ fg: "gray",
1070
+ children: " | "
1071
+ }, undefined, false, undefined, this)
1075
1072
  }, undefined, false, undefined, this),
1076
- /* @__PURE__ */ jsxDEV("span", {
1077
- fg: "cyan",
1078
- children: ` ${log.trigger} `
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)
1078
+ }, undefined, false, undefined, this)
1079
+ ]
1080
+ }, undefined, true, undefined, this),
1081
+ /* @__PURE__ */ jsxDEV("box", {
1082
+ flexDirection: "row",
1083
+ gap: 2,
1084
+ children: [
1085
+ isDeploying && /* @__PURE__ */ jsxDEV("text", {
1086
+ children: /* @__PURE__ */ jsxDEV("span", {
1087
+ fg: "yellow",
1088
+ children: "deploying..."
1089
+ }, undefined, false, undefined, this)
1079
1090
  }, undefined, false, undefined, this),
1080
- /* @__PURE__ */ jsxDEV("span", {
1081
- children: sanitizeMessage(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)
1082
1104
  }, undefined, false, undefined, this)
1083
1105
  ]
1084
1106
  }, undefined, true, undefined, this)
1085
- }, log.id, false, undefined, this);
1086
- })
1087
- }, `${props.autoScroll}-${props.jumpTrigger}`, false, undefined, this);
1107
+ ]
1108
+ }, undefined, true, undefined, this)
1109
+ }, undefined, false, undefined, this);
1088
1110
  };
1089
- var init_DeploymentLogsPane = () => {};
1111
+ var init_Header = __esm(() => {
1112
+ init_utils();
1113
+ });
1090
1114
 
1091
- // src/components/SystemLogsPane.tsx
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
1092
1177
  import { jsxDEV as jsxDEV2 } from "@opentui/react/jsx-dev-runtime";
1093
- var getLevelColor2 = (level) => {
1094
- switch (level.toLowerCase()) {
1095
- case "error":
1096
- return "red";
1097
- case "warn":
1098
- case "warning":
1099
- return "yellow";
1100
- case "info":
1101
- return "green";
1102
- case "debug":
1103
- case "trace":
1104
- return "gray";
1105
- default:
1106
- return "white";
1107
- }
1108
- }, sanitizeMessage2 = (message) => {
1109
- return message.replace(/\x1b\[[0-9;]*m/g, "").replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
1110
- }, SystemLogsPane = (props) => {
1111
- if (props.loading && props.logs.length === 0) {
1112
- return /* @__PURE__ */ jsxDEV2("box", {
1113
- flexDirection: "column",
1114
- padding: 1,
1115
- children: /* @__PURE__ */ jsxDEV2("text", {
1116
- children: "Loading system logs..."
1117
- }, undefined, false, undefined, this)
1118
- }, undefined, false, undefined, this);
1119
- }
1120
- if (!props.loading && props.logs.length === 0) {
1121
- return /* @__PURE__ */ jsxDEV2("box", {
1122
- flexDirection: "column",
1123
- padding: 1,
1124
- children: /* @__PURE__ */ jsxDEV2("text", {
1125
- children: "No system logs found"
1126
- }, undefined, false, undefined, this)
1127
- }, undefined, false, undefined, this);
1128
- }
1129
- const logsInOrder = [...props.logs].reverse();
1130
- return /* @__PURE__ */ jsxDEV2("scrollbox", {
1131
- focused: true,
1132
- stickyStart: props.autoScroll ? "bottom" : "top",
1133
- stickyScroll: props.autoScroll,
1134
- children: logsInOrder.map((log) => {
1135
- const time = new Date(log.timestamp).toLocaleTimeString();
1136
- const timestamp = `[${time}]`;
1137
- const level = log.level.toUpperCase();
1138
- return /* @__PURE__ */ jsxDEV2("box", {
1139
- flexDirection: "row",
1140
- children: /* @__PURE__ */ jsxDEV2("text", {
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", {
1183
+ backgroundColor: "black",
1184
+ flexDirection: "row",
1185
+ paddingLeft: 1,
1186
+ paddingRight: 1,
1187
+ children: /* @__PURE__ */ jsxDEV2("text", {
1188
+ children: [
1189
+ /* @__PURE__ */ jsxDEV2("span", {
1190
+ fg: DEPLOY_COLORS.progress,
1191
+ children: "\u27F3 Deploying"
1192
+ }, undefined, false, undefined, this),
1193
+ /* @__PURE__ */ jsxDEV2("span", {
1141
1194
  children: [
1142
- /* @__PURE__ */ jsxDEV2("span", {
1143
- fg: "gray",
1144
- children: timestamp
1145
- }, undefined, false, undefined, this),
1146
- /* @__PURE__ */ jsxDEV2("span", {
1147
- fg: getLevelColor2(log.level),
1148
- children: ` ${level} `
1149
- }, undefined, false, undefined, this),
1150
- /* @__PURE__ */ jsxDEV2("span", {
1151
- children: sanitizeMessage2(log.message)
1152
- }, undefined, false, undefined, this)
1195
+ " ",
1196
+ currentFile
1197
+ ]
1198
+ }, undefined, true, undefined, this),
1199
+ /* @__PURE__ */ jsxDEV2("span", {
1200
+ fg: DEPLOY_COLORS.muted,
1201
+ children: [
1202
+ " ",
1203
+ "(",
1204
+ completedCount + 1,
1205
+ "/",
1206
+ totalFiles,
1207
+ ")"
1208
+ ]
1209
+ }, undefined, true, undefined, this),
1210
+ failedCount > 0 && /* @__PURE__ */ jsxDEV2("span", {
1211
+ fg: DEPLOY_COLORS.error,
1212
+ children: [
1213
+ " (",
1214
+ failedCount,
1215
+ " failed)"
1153
1216
  ]
1154
1217
  }, undefined, true, undefined, this)
1155
- }, log.id, false, undefined, this);
1156
- })
1157
- }, `${props.autoScroll}-${props.jumpTrigger}`, false, undefined, this);
1218
+ ]
1219
+ }, undefined, true, undefined, this)
1220
+ }, undefined, false, undefined, this);
1158
1221
  };
1159
- var init_SystemLogsPane = () => {};
1222
+ var init_DeployProgress = __esm(() => {
1223
+ init_constants();
1224
+ });
1160
1225
 
1161
- // src/components/DeployAnimation.tsx
1162
- import { useEffect, useState, useRef } from "react";
1163
- import { jsxDEV as jsxDEV3 } from "@opentui/react/jsx-dev-runtime";
1164
- var DeployAnimation = (props) => {
1165
- const [currentIndex, setCurrentIndex] = useState(0);
1166
- const [progress, setProgress] = useState(0);
1167
- const intervalRef = useRef(null);
1168
- const completedRef = useRef(false);
1169
- const duration = 400;
1170
- const frameRate = 16;
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;
1171
1234
  useEffect(() => {
1172
- if (props.deployed.length === 0) {
1173
- props.onComplete();
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;
1243
+ }
1244
+ return /* @__PURE__ */ jsxDEV3("box", {
1245
+ backgroundColor: "black",
1246
+ 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,
1252
+ children: [
1253
+ "\u2713 Deployed ",
1254
+ successCount,
1255
+ " files"
1256
+ ]
1257
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV3(Fragment, {
1258
+ children: [
1259
+ /* @__PURE__ */ jsxDEV3("span", {
1260
+ fg: DEPLOY_COLORS.success,
1261
+ children: [
1262
+ "\u2713 ",
1263
+ successCount,
1264
+ " deployed"
1265
+ ]
1266
+ }, undefined, true, 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)
1275
+ ]
1276
+ }, undefined, true, undefined, this)
1277
+ }, undefined, false, undefined, this)
1278
+ }, undefined, false, undefined, this);
1279
+ };
1280
+ var init_DeployComplete = __esm(() => {
1281
+ init_constants();
1282
+ });
1283
+
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();
1174
1294
  return;
1175
1295
  }
1176
- if (completedRef.current) {
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
+ });
1177
1310
  return;
1178
1311
  }
1179
- setProgress(0);
1180
- const startTime = Date.now();
1181
- intervalRef.current = setInterval(() => {
1182
- const elapsed = Date.now() - startTime;
1183
- const newProgress = Math.min(100, elapsed / duration * 100);
1184
- setProgress(newProgress);
1185
- if (newProgress >= 100) {
1186
- if (intervalRef.current) {
1187
- clearInterval(intervalRef.current);
1188
- intervalRef.current = null;
1189
- }
1190
- setTimeout(() => {
1191
- if (currentIndex >= props.deployed.length - 1) {
1192
- completedRef.current = true;
1193
- props.onComplete();
1194
- } else {
1195
- setCurrentIndex((prev) => prev + 1);
1196
- }
1197
- }, 150);
1198
- }
1199
- }, frameRate);
1200
- return () => {
1201
- if (intervalRef.current) {
1202
- clearInterval(intervalRef.current);
1203
- }
1204
- };
1205
- }, [currentIndex, props.deployed.length, props.onComplete]);
1206
- const current = props.deployed[currentIndex];
1207
- if (props.deployed.length === 0 || !current || completedRef.current) {
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) {
1208
1318
  return null;
1209
1319
  }
1210
- const barWidth = 8;
1211
- const filled = Math.round(progress / 100 * barWidth);
1212
- const empty = barWidth - filled;
1213
- const bar = "\u2501".repeat(filled) + "\u2500".repeat(empty);
1214
- return /* @__PURE__ */ jsxDEV3("box", {
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", {
1215
1354
  backgroundColor: "black",
1216
1355
  flexDirection: "row",
1217
1356
  paddingLeft: 1,
1218
1357
  paddingRight: 1,
1219
- children: /* @__PURE__ */ jsxDEV3("text", {
1358
+ children: /* @__PURE__ */ jsxDEV4("text", {
1220
1359
  children: [
1221
- /* @__PURE__ */ jsxDEV3("span", {
1222
- fg: current.success ? "#22c55e" : "#ef4444",
1223
- children: [
1224
- bar,
1225
- " ",
1226
- current.success ? "\u2713" : "\u2717"
1227
- ]
1228
- }, undefined, true, undefined, this),
1229
- /* @__PURE__ */ jsxDEV3("span", {
1360
+ /* @__PURE__ */ jsxDEV4("span", {
1361
+ fg: DEPLOY_COLORS.error,
1362
+ children: "\u2717 Deleting"
1363
+ }, undefined, false, undefined, this),
1364
+ /* @__PURE__ */ jsxDEV4("span", {
1230
1365
  children: [
1231
1366
  " ",
1232
- current.name
1367
+ current
1233
1368
  ]
1234
1369
  }, undefined, true, undefined, this),
1235
- props.deployed.length > 1 && /* @__PURE__ */ jsxDEV3("span", {
1236
- fg: "#666",
1370
+ /* @__PURE__ */ jsxDEV4("span", {
1371
+ fg: DEPLOY_COLORS.muted,
1237
1372
  children: [
1238
1373
  " ",
1239
1374
  "(",
1240
1375
  currentIndex + 1,
1241
1376
  "/",
1242
- props.deployed.length,
1377
+ items.length,
1243
1378
  ")"
1244
1379
  ]
1245
1380
  }, undefined, true, undefined, this)
@@ -1247,235 +1382,436 @@ var DeployAnimation = (props) => {
1247
1382
  }, undefined, true, undefined, this)
1248
1383
  }, undefined, false, undefined, this);
1249
1384
  };
1250
- var init_DeployAnimation = () => {};
1385
+ var init_CleanProgress = __esm(() => {
1386
+ init_constants();
1387
+ });
1251
1388
 
1252
- // src/components/Header.tsx
1253
- import { jsxDEV as jsxDEV4, Fragment } from "@opentui/react/jsx-dev-runtime";
1254
- var getBinarySourceLabel = (source) => {
1255
- switch (source) {
1256
- case "local-debug":
1257
- return "local (debug)";
1258
- case "local-release":
1259
- return "local (release)";
1260
- case "local-env":
1261
- return "local (env)";
1262
- case "npm":
1263
- return "npm";
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;
1264
1395
  }
1265
- }, Header = (props) => {
1266
- const { activeTab, functionCount, viteRunning, isDeploying, binarySource } = props;
1267
- return /* @__PURE__ */ jsxDEV4("box", {
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", {
1268
1522
  flexDirection: "row",
1269
- justifyContent: "space-between",
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", {
1622
+ position: "absolute",
1623
+ top: 0,
1624
+ left: 0,
1625
+ width: "100%",
1626
+ height: "100%",
1627
+ backgroundColor: "black",
1628
+ opacity: 0.9,
1629
+ justifyContent: "center",
1630
+ alignItems: "center",
1631
+ children: /* @__PURE__ */ jsxDEV10("box", {
1632
+ width,
1633
+ height,
1634
+ border: true,
1635
+ borderStyle: "rounded",
1636
+ flexDirection: "column",
1637
+ padding: 1,
1638
+ backgroundColor: "black",
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,
1270
1670
  children: [
1271
- /* @__PURE__ */ jsxDEV4("box", {
1272
- flexDirection: "row",
1273
- children: [
1274
- /* @__PURE__ */ jsxDEV4("text", {
1275
- fg: activeTab === "system" ? "cyan" : undefined,
1276
- content: "[1] System Logs"
1277
- }, undefined, false, undefined, this),
1278
- /* @__PURE__ */ jsxDEV4("text", {
1279
- content: " | "
1280
- }, undefined, false, undefined, this),
1281
- /* @__PURE__ */ jsxDEV4("text", {
1282
- fg: activeTab === "deployment" ? "cyan" : undefined,
1283
- content: "[2] Deployment Logs"
1671
+ /* @__PURE__ */ jsxDEV11("text", {
1672
+ children: /* @__PURE__ */ jsxDEV11("span", {
1673
+ fg: "cyan",
1674
+ children: /* @__PURE__ */ jsxDEV11("strong", {
1675
+ children: "Keyboard Shortcuts"
1284
1676
  }, undefined, false, undefined, this)
1285
- ]
1286
- }, undefined, true, undefined, this),
1287
- /* @__PURE__ */ jsxDEV4("box", {
1288
- flexDirection: "row",
1677
+ }, 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",
1289
1682
  children: [
1290
- isDeploying ? /* @__PURE__ */ jsxDEV4("text", {
1291
- fg: "yellow",
1292
- content: "\u25CF deploying all..."
1293
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV4("text", {
1294
- fg: functionCount > 0 ? "green" : "yellow",
1295
- content: "\u25CF watching"
1296
- }, undefined, false, undefined, this),
1297
- /* @__PURE__ */ jsxDEV4("text", {
1298
- fg: "gray",
1299
- content: ` (${functionCount} functions)`
1683
+ sectionIndex > 0 && /* @__PURE__ */ jsxDEV11("text", {}, undefined, false, undefined, this),
1684
+ /* @__PURE__ */ jsxDEV11(Section, {
1685
+ title: section.section
1300
1686
  }, undefined, false, undefined, this),
1301
- viteRunning && /* @__PURE__ */ jsxDEV4(Fragment, {
1302
- children: [
1303
- /* @__PURE__ */ jsxDEV4("text", {
1304
- fg: "gray",
1305
- content: " | vite: "
1306
- }, undefined, false, undefined, this),
1307
- /* @__PURE__ */ jsxDEV4("text", {
1308
- fg: "magenta",
1309
- content: ":5173"
1310
- }, undefined, false, undefined, this)
1311
- ]
1312
- }, undefined, true, undefined, this),
1313
- /* @__PURE__ */ jsxDEV4("text", {
1314
- fg: "gray",
1315
- content: " | server: "
1316
- }, undefined, false, undefined, this),
1317
- /* @__PURE__ */ jsxDEV4("text", {
1318
- fg: binarySource.startsWith("local") ? "yellow" : "green",
1319
- content: getBinarySourceLabel(binarySource)
1320
- }, 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))
1321
1691
  ]
1322
- }, undefined, true, undefined, this)
1692
+ }, section.section, true, undefined, this))
1323
1693
  ]
1324
1694
  }, undefined, true, undefined, this);
1325
1695
  };
1326
- var init_Header = () => {};
1696
+ var init_HelpModal = __esm(() => {
1697
+ init_Modal();
1698
+ init_constants();
1699
+ });
1700
+
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
+ };
1327
1742
 
1328
- // src/components/HelpModal.tsx
1329
- import { jsxDEV as jsxDEV5 } from "@opentui/react/jsx-dev-runtime";
1330
- var KeyBinding = ({ keys, description }) => /* @__PURE__ */ jsxDEV5("text", {
1331
- children: [
1332
- " ",
1333
- /* @__PURE__ */ jsxDEV5("span", {
1334
- fg: "green",
1335
- children: keys
1336
- }, undefined, false, undefined, this),
1337
- /* @__PURE__ */ jsxDEV5("span", {
1338
- fg: "gray",
1339
- children: " - "
1340
- }, undefined, false, undefined, this),
1341
- description
1342
- ]
1343
- }, undefined, true, undefined, this), Section = ({ title }) => /* @__PURE__ */ jsxDEV5("text", {
1344
- children: /* @__PURE__ */ jsxDEV5("span", {
1345
- fg: "gray",
1346
- children: title
1347
- }, undefined, false, undefined, this)
1348
- }, undefined, false, undefined, this), HelpModal = ({ visible }) => {
1349
- if (!visible)
1350
- return null;
1351
- return /* @__PURE__ */ jsxDEV5("box", {
1352
- position: "absolute",
1353
- top: 0,
1354
- left: 0,
1355
- width: "100%",
1356
- height: "100%",
1357
- backgroundColor: "black",
1358
- opacity: 0.9,
1359
- justifyContent: "center",
1360
- alignItems: "center",
1361
- children: /* @__PURE__ */ jsxDEV5("box", {
1362
- width: 50,
1363
- height: 18,
1364
- border: true,
1365
- borderStyle: "rounded",
1366
- flexDirection: "column",
1367
- padding: 1,
1368
- backgroundColor: "black",
1369
- children: [
1370
- /* @__PURE__ */ jsxDEV5("text", {
1371
- children: /* @__PURE__ */ jsxDEV5("span", {
1372
- fg: "cyan",
1373
- children: /* @__PURE__ */ jsxDEV5("strong", {
1374
- children: "Keyboard Shortcuts"
1375
- }, undefined, false, undefined, this)
1376
- }, undefined, false, undefined, this)
1377
- }, undefined, false, undefined, this),
1378
- /* @__PURE__ */ jsxDEV5("text", {}, undefined, false, undefined, this),
1379
- /* @__PURE__ */ jsxDEV5(Section, {
1380
- title: "Navigation"
1381
- }, undefined, false, undefined, this),
1382
- /* @__PURE__ */ jsxDEV5(KeyBinding, {
1383
- keys: "1/2",
1384
- description: "Switch tabs"
1385
- }, undefined, false, undefined, this),
1386
- /* @__PURE__ */ jsxDEV5(KeyBinding, {
1387
- keys: "h/l",
1388
- description: "Previous/next tab"
1389
- }, undefined, false, undefined, this),
1390
- /* @__PURE__ */ jsxDEV5(KeyBinding, {
1391
- keys: "Tab",
1392
- description: "Cycle tabs"
1393
- }, undefined, false, undefined, this),
1394
- /* @__PURE__ */ jsxDEV5("text", {}, undefined, false, undefined, this),
1395
- /* @__PURE__ */ jsxDEV5(Section, {
1396
- title: "Scrolling"
1397
- }, undefined, false, undefined, this),
1398
- /* @__PURE__ */ jsxDEV5(KeyBinding, {
1399
- keys: "j/k",
1400
- description: "Scroll down/up"
1401
- }, undefined, false, undefined, this),
1402
- /* @__PURE__ */ jsxDEV5(KeyBinding, {
1403
- keys: "g/G",
1404
- description: "Top/bottom"
1405
- }, undefined, false, undefined, this),
1406
- /* @__PURE__ */ jsxDEV5("text", {}, undefined, false, undefined, this),
1407
- /* @__PURE__ */ jsxDEV5(Section, {
1408
- title: "Actions"
1409
- }, undefined, false, undefined, this),
1410
- /* @__PURE__ */ jsxDEV5(KeyBinding, {
1411
- keys: "d",
1412
- description: "Deploy all"
1413
- }, undefined, false, undefined, this),
1414
- /* @__PURE__ */ jsxDEV5(KeyBinding, {
1415
- keys: "?",
1416
- description: "Toggle help"
1417
- }, undefined, false, undefined, this),
1418
- /* @__PURE__ */ jsxDEV5(KeyBinding, {
1419
- keys: "Esc",
1420
- description: "Close help"
1421
- }, undefined, false, undefined, this)
1422
- ]
1423
- }, undefined, true, undefined, this)
1424
- }, undefined, false, undefined, this);
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 });
1425
1756
  };
1426
- var init_HelpModal = () => {};
1757
+ var init_logs3 = () => {};
1427
1758
 
1428
- // src/ui.tsx
1429
- var exports_ui = {};
1430
- import { useEffect as useEffect2, useState as useState2 } from "react";
1431
- import { createCliRenderer } from "@opentui/core";
1432
- import { createRoot } from "@opentui/react";
1433
- import { jsxDEV as jsxDEV6 } from "@opentui/react/jsx-dev-runtime";
1434
- var App = (props) => {
1435
- const [error, setError] = useState2(null);
1436
- const [loading, setLoading] = useState2(true);
1437
- const [deploymentLogs, setDeploymentLogs] = useState2([]);
1438
- const [systemLogs, setSystemLogs] = useState2([]);
1439
- const [activeTab, setActiveTab] = useState2("system");
1440
- const [autoScroll, setAutoScroll] = useState2(true);
1441
- const [jumpTrigger, setJumpTrigger] = useState2(0);
1442
- const [watcherStatus, setWatcherStatus] = useState2(null);
1443
- const [showKeyBindings, setShowKeyBindings] = useState2(false);
1444
- const [isDeploying, setIsDeploying] = useState2(false);
1445
- useEffect2(() => {
1446
- props.onWatcherEvent((event) => {
1447
- console.error("DEBUG: received watcher event:", event.type, event.deployed?.length);
1448
- setWatcherStatus(event);
1449
- });
1450
- }, [props.onWatcherEvent]);
1451
- useEffect2(() => {
1452
- const fn = async () => {
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 () => {
1453
1790
  try {
1454
1791
  setLoading(true);
1455
1792
  setError(null);
1456
1793
  const [deploymentLogsData, systemLogsData] = await Promise.all([
1457
- searchDeploymentLogs(props.config.key.public, { limit: 100 }, props.config.api),
1458
- 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)
1459
1796
  ]);
1460
1797
  setDeploymentLogs(deploymentLogsData);
1461
1798
  setSystemLogs(systemLogsData);
1462
1799
  } catch (err) {
1463
1800
  setError(err instanceof Error ? err.message : String(err));
1464
- console.error("Failed to fetch logs:", err);
1465
1801
  } finally {
1466
1802
  setLoading(false);
1467
1803
  }
1468
1804
  };
1469
- fn();
1470
- }, [props.config.key.public, props.config.api]);
1471
- useEffect2(() => {
1805
+ fetchLogs();
1806
+ }, [config.key.public, config.api]);
1807
+ useEffect3(() => {
1808
+ if (loading)
1809
+ return;
1472
1810
  const interval = setInterval(async () => {
1473
- if (loading)
1474
- return;
1475
1811
  try {
1476
1812
  const [newDeploymentLogs, newSystemLogs] = await Promise.all([
1477
- searchDeploymentLogs(props.config.key.public, { limit: 100 }, props.config.api),
1478
- 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)
1479
1815
  ]);
1480
1816
  if (newDeploymentLogs.length > 0) {
1481
1817
  setDeploymentLogs(newDeploymentLogs);
@@ -1483,59 +1819,206 @@ var App = (props) => {
1483
1819
  if (newSystemLogs.length > 0) {
1484
1820
  setSystemLogs(newSystemLogs);
1485
1821
  }
1486
- } catch (err) {
1487
- console.error("Failed to poll for new logs:", err);
1488
- }
1489
- }, 2000);
1822
+ } catch {}
1823
+ }, pollInterval);
1490
1824
  return () => clearInterval(interval);
1491
- }, [loading, props.config.key.public, props.config.api]);
1492
- useEffect2(() => {
1493
- const handleKeyPress = (data) => {
1494
- const key = data.toString();
1495
- if (key === "1") {
1496
- setActiveTab("system");
1497
- return;
1498
- }
1499
- if (key === "2") {
1500
- setActiveTab("deployment");
1501
- return;
1502
- }
1503
- if (key === "\t") {
1504
- setActiveTab((prev) => prev === "system" ? "deployment" : "system");
1505
- return;
1506
- }
1507
- if (key === "h") {
1508
- setActiveTab("system");
1509
- return;
1510
- }
1511
- if (key === "l") {
1512
- setActiveTab("deployment");
1513
- return;
1514
- }
1515
- if (key === "g") {
1516
- setAutoScroll(false);
1517
- setJumpTrigger((prev) => prev + 1);
1518
- return;
1519
- }
1520
- if (key === "G") {
1521
- setAutoScroll(true);
1522
- setJumpTrigger((prev) => prev + 1);
1523
- return;
1524
- }
1525
- if (key === "?") {
1526
- setShowKeyBindings((prev) => !prev);
1527
- 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);
1528
1908
  }
1529
- if (key === "\x1B") {
1530
- setShowKeyBindings(false);
1531
- 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);
1532
1920
  }
1533
- if (key === "d" && !isDeploying) {
1534
- setIsDeploying(true);
1535
- props.onForceDeploy().finally(() => {
1536
- setIsDeploying(false);
1537
- });
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)
1538
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;
1539
2022
  }
1540
2023
  };
1541
2024
  process.stdin.setRawMode(true);
@@ -1544,14 +2027,101 @@ var App = (props) => {
1544
2027
  process.stdin.setRawMode(false);
1545
2028
  process.stdin.off("data", handleKeyPress);
1546
2029
  };
1547
- }, [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
+ });
1548
2118
  if (error) {
1549
- return /* @__PURE__ */ jsxDEV6("box", {
2119
+ return /* @__PURE__ */ jsxDEV12("box", {
1550
2120
  flexGrow: 1,
1551
2121
  flexDirection: "column",
1552
2122
  padding: 1,
1553
- children: /* @__PURE__ */ jsxDEV6("text", {
1554
- children: /* @__PURE__ */ jsxDEV6("span", {
2123
+ children: /* @__PURE__ */ jsxDEV12("text", {
2124
+ children: /* @__PURE__ */ jsxDEV12("span", {
1555
2125
  fg: "red",
1556
2126
  children: [
1557
2127
  "Error: ",
@@ -1561,50 +2131,72 @@ var App = (props) => {
1561
2131
  }, undefined, false, undefined, this)
1562
2132
  }, undefined, false, undefined, this);
1563
2133
  }
1564
- return /* @__PURE__ */ jsxDEV6("box", {
2134
+ return /* @__PURE__ */ jsxDEV12("box", {
1565
2135
  flexDirection: "column",
2136
+ height: "100%",
1566
2137
  children: [
1567
- /* @__PURE__ */ jsxDEV6(Header, {
2138
+ /* @__PURE__ */ jsxDEV12(Header, {
1568
2139
  activeTab,
1569
- functionCount: props.functionCount,
1570
- viteRunning: props.viteRunning,
2140
+ functionCount,
2141
+ viteRunning,
1571
2142
  isDeploying,
1572
- binarySource: props.binarySource
2143
+ binarySource
1573
2144
  }, undefined, false, undefined, this),
1574
- /* @__PURE__ */ jsxDEV6("box", {
2145
+ /* @__PURE__ */ jsxDEV12("box", {
1575
2146
  paddingTop: 1,
1576
2147
  flexDirection: "column",
1577
- children: [
1578
- activeTab === "system" ? /* @__PURE__ */ jsxDEV6(SystemLogsPane, {
1579
- logs: systemLogs,
1580
- loading,
1581
- autoScroll,
1582
- jumpTrigger
1583
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV6(DeploymentLogsPane, {
1584
- logs: deploymentLogs,
1585
- loading,
1586
- autoScroll,
1587
- jumpTrigger
1588
- }, undefined, false, undefined, this),
1589
- watcherStatus?.type === "deploy" && watcherStatus.deployed && watcherStatus.deployed.length > 0 && /* @__PURE__ */ jsxDEV6(DeployAnimation, {
1590
- deployed: watcherStatus.deployed,
1591
- onComplete: () => {
1592
- console.error("DEBUG: animation complete");
1593
- setWatcherStatus(null);
1594
- }
1595
- }, undefined, false, undefined, this)
1596
- ]
1597
- }, undefined, true, undefined, this),
1598
- /* @__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, {
1599
2169
  visible: showKeyBindings
1600
2170
  }, undefined, false, undefined, this)
1601
2171
  ]
1602
2172
  }, undefined, true, undefined, this);
1603
- }, 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 () => {
1604
2187
  let serverInfo = null;
1605
2188
  let viteInfo = null;
1606
2189
  let stopWatcher = null;
1607
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
+ };
1608
2200
  const cleanup = async (exitAfter = true) => {
1609
2201
  if (isCleaningUp)
1610
2202
  return;
@@ -1617,13 +2209,7 @@ var App = (props) => {
1617
2209
  await stopViteServer(viteInfo);
1618
2210
  viteInfo = null;
1619
2211
  }
1620
- if (serverInfo) {
1621
- const pid = serverInfo.process.pid;
1622
- serverInfo = null;
1623
- try {
1624
- process.kill(pid, "SIGTERM");
1625
- } catch {}
1626
- }
2212
+ stopServer();
1627
2213
  if (exitAfter) {
1628
2214
  process.exit(0);
1629
2215
  }
@@ -1637,11 +2223,12 @@ var App = (props) => {
1637
2223
  const srcPath = process.cwd() + "/src";
1638
2224
  viteInfo = await startViteServer({
1639
2225
  srcDir: srcPath,
1640
- onLog: (_msg, _level) => {}
2226
+ onLog: () => {}
1641
2227
  });
1642
2228
  let watcherEventHandler = null;
1643
2229
  let pendingEvents = [];
1644
2230
  let functionCount = 0;
2231
+ let isInitialBuild = true;
1645
2232
  const sendEvent = (event) => {
1646
2233
  if (watcherEventHandler) {
1647
2234
  watcherEventHandler(event);
@@ -1649,22 +2236,27 @@ var App = (props) => {
1649
2236
  pendingEvents.push(event);
1650
2237
  }
1651
2238
  };
2239
+ const restartServer = async () => {
2240
+ stopServer();
2241
+ serverInfo = await startServer();
2242
+ };
1652
2243
  const renderer = await createCliRenderer();
1653
2244
  const root = createRoot(renderer);
1654
- const handleForceDeploy = async () => {
2245
+ const handleForceDeploy = async (callbacks) => {
1655
2246
  const deployResults = [];
1656
2247
  await forceDeployAll(srcPath, {
2248
+ onStart: (filePath, completedCount, totalCount) => {
2249
+ callbacks.onProgress({
2250
+ currentFile: filePath,
2251
+ completedFiles: [...deployResults],
2252
+ totalFiles: totalCount
2253
+ });
2254
+ },
1657
2255
  onDeploy: (filePath, success, error) => {
1658
2256
  deployResults.push({ name: filePath, success, error });
1659
2257
  }
1660
2258
  });
1661
- if (deployResults.length > 0) {
1662
- sendEvent({
1663
- type: "deploy",
1664
- deployed: deployResults,
1665
- timestamp: new Date
1666
- });
1667
- }
2259
+ callbacks.onComplete(deployResults);
1668
2260
  };
1669
2261
  stopWatcher = await startWatcher(srcPath, {
1670
2262
  silent: true,
@@ -1672,6 +2264,10 @@ var App = (props) => {
1672
2264
  functionCount = count;
1673
2265
  },
1674
2266
  onDeployBatch: (results) => {
2267
+ if (isInitialBuild) {
2268
+ isInitialBuild = false;
2269
+ return;
2270
+ }
1675
2271
  sendEvent({
1676
2272
  type: "deploy",
1677
2273
  deployed: results.map((r) => ({
@@ -1683,7 +2279,7 @@ var App = (props) => {
1683
2279
  });
1684
2280
  }
1685
2281
  });
1686
- root.render(/* @__PURE__ */ jsxDEV6(App, {
2282
+ root.render(/* @__PURE__ */ jsxDEV13(App, {
1687
2283
  config: devConfig,
1688
2284
  binarySource: serverInfo.binarySource,
1689
2285
  functionCount,
@@ -1697,7 +2293,8 @@ var App = (props) => {
1697
2293
  pendingEvents = [];
1698
2294
  }
1699
2295
  },
1700
- onForceDeploy: handleForceDeploy
2296
+ onForceDeploy: handleForceDeploy,
2297
+ onRestartServer: restartServer
1701
2298
  }, undefined, false, undefined, this));
1702
2299
  } catch (error) {
1703
2300
  console.error("Failed to start server:", error);
@@ -1705,16 +2302,11 @@ var App = (props) => {
1705
2302
  }
1706
2303
  };
1707
2304
  var init_ui = __esm(() => {
1708
- init_api();
1709
2305
  init_server();
1710
2306
  init_watcher();
1711
2307
  init_vite();
1712
2308
  init_config();
1713
- init_DeploymentLogsPane();
1714
- init_SystemLogsPane();
1715
- init_DeployAnimation();
1716
- init_Header();
1717
- init_HelpModal();
2309
+ init_App();
1718
2310
  main();
1719
2311
  });
1720
2312
 
@@ -1732,8 +2324,8 @@ init_config();
1732
2324
  init_bundle();
1733
2325
  init_deploy2();
1734
2326
  import chalk3 from "chalk";
1735
- import { basename as basename4, resolve as resolve2, join as join5 } from "path";
1736
- 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";
1737
2329
  import { readdir as readdir2 } from "fs/promises";
1738
2330
  import * as p from "@clack/prompts";
1739
2331
  var FUNCTION_DIRS2 = ["api", "cron", "event"];
@@ -1762,25 +2354,25 @@ var selectConfig = async () => {
1762
2354
  };
1763
2355
  var findAllDeployables = async (srcPath) => {
1764
2356
  const functions = [];
1765
- const functionsPath = join5(srcPath, "function");
2357
+ const functionsPath = join6(srcPath, "function");
1766
2358
  for (const dir of FUNCTION_DIRS2) {
1767
- const dirPath = join5(functionsPath, dir);
2359
+ const dirPath = join6(functionsPath, dir);
1768
2360
  try {
1769
2361
  const items = await readdir2(dirPath, { withFileTypes: true });
1770
2362
  for (const item of items) {
1771
2363
  if (item.isFile() && (item.name.endsWith(".ts") || item.name.endsWith(".tsx"))) {
1772
- functions.push(join5(dirPath, item.name));
2364
+ functions.push(join6(dirPath, item.name));
1773
2365
  }
1774
2366
  }
1775
2367
  } catch {}
1776
2368
  }
1777
- const reactEntry = join5(srcPath, "index.tsx");
1778
- const hasReact = existsSync5(reactEntry);
2369
+ const reactEntry = join6(srcPath, "index.tsx");
2370
+ const hasReact = existsSync6(reactEntry);
1779
2371
  return { functions, reactEntry: hasReact ? reactEntry : null };
1780
2372
  };
1781
2373
  var deployAll = async (config, options) => {
1782
- const srcPath = join5(process.cwd(), "src");
1783
- if (!existsSync5(srcPath)) {
2374
+ const srcPath = join6(process.cwd(), "src");
2375
+ if (!existsSync6(srcPath)) {
1784
2376
  console.error(chalk3.red("\u2717 No src directory found"));
1785
2377
  process.exit(1);
1786
2378
  }
@@ -1864,7 +2456,7 @@ var registerDeployCommand = (program) => {
1864
2456
  return;
1865
2457
  }
1866
2458
  const filePath = resolve2(file);
1867
- if (!existsSync5(filePath)) {
2459
+ if (!existsSync6(filePath)) {
1868
2460
  console.error(chalk3.red(`\u2717 File not found: ${filePath}`));
1869
2461
  process.exit(1);
1870
2462
  }
@@ -2031,14 +2623,72 @@ var printAvailableCommands = () => {
2031
2623
  console.log(chalk5.cyan(" nulljs host") + chalk5.gray(" - Set up production hosting with systemd"));
2032
2624
  };
2033
2625
  // src/commands/secret.ts
2034
- init_api();
2035
- init_config();
2036
2626
  import { Command as Command2 } from "commander";
2037
2627
  import chalk6 from "chalk";
2038
2628
  import { resolve as resolve3 } from "path";
2039
2629
  import { readFile, readdir as readdir3 } from "fs/promises";
2040
- import { existsSync as existsSync6 } from "fs";
2630
+ import { existsSync as existsSync7 } from "fs";
2041
2631
  import * as p3 from "@clack/prompts";
2632
+
2633
+ // ../../packages/api/actions/secrets.ts
2634
+ init_http();
2635
+ var createSignatureHeader = async (privateKey, props) => {
2636
+ const timestamp = new Date().toISOString();
2637
+ const raw = `${props.method}-${props.path}-${timestamp}${props.body ? "-" + props.body : ""}`;
2638
+ const sign = await crypto.subtle.sign("Ed25519", privateKey, Buffer.from(raw));
2639
+ const signature = btoa(String.fromCharCode(...new Uint8Array(sign)));
2640
+ const header = {
2641
+ authorization: signature,
2642
+ "x-time": timestamp
2643
+ };
2644
+ if (props.body) {
2645
+ header["x-body"] = btoa(props.body);
2646
+ }
2647
+ return header;
2648
+ };
2649
+ var listSecrets = async (options) => {
2650
+ const baseUrl = options.url || "";
2651
+ const path = "/api/secrets";
2652
+ const fullPath = `${baseUrl}${path}`;
2653
+ const headers = await createSignatureHeader(options.privateKey, {
2654
+ method: "GET",
2655
+ path: fullPath
2656
+ });
2657
+ const result = await httpGet(path, {
2658
+ baseUrl,
2659
+ useCurl: options.useCurl,
2660
+ headers
2661
+ });
2662
+ if (!result.ok) {
2663
+ throw new Error(`Failed to list secrets (${result.status}): ${result.body}`);
2664
+ }
2665
+ return JSON.parse(result.body);
2666
+ };
2667
+ var createSecret = async (secret, options) => {
2668
+ const baseUrl = options.url || "";
2669
+ const path = "/api/secrets";
2670
+ const fullPath = `${baseUrl}${path}`;
2671
+ const body = JSON.stringify(secret);
2672
+ const signatureHeaders = await createSignatureHeader(options.privateKey, {
2673
+ method: "POST",
2674
+ path: fullPath,
2675
+ body
2676
+ });
2677
+ const result = await httpPost(path, body, {
2678
+ baseUrl,
2679
+ useCurl: options.useCurl,
2680
+ headers: {
2681
+ "Content-Type": "application/json",
2682
+ ...signatureHeaders
2683
+ }
2684
+ });
2685
+ if (!result.ok) {
2686
+ throw new Error(`Failed to create secret (${result.status}): ${result.body}`);
2687
+ }
2688
+ };
2689
+
2690
+ // src/commands/secret.ts
2691
+ init_config();
2042
2692
  var selectConfig2 = async () => {
2043
2693
  const configList = listConfigs();
2044
2694
  if (!configList || configList.configs.length === 0) {
@@ -2093,7 +2743,7 @@ var parseEnvFile = async (filePath) => {
2093
2743
  const content = await readFile(filePath, "utf-8");
2094
2744
  const lines = content.split(`
2095
2745
  `);
2096
- const secrets2 = [];
2746
+ const secrets = [];
2097
2747
  for (const line of lines) {
2098
2748
  const trimmed = line.trim();
2099
2749
  if (!trimmed || trimmed.startsWith("#")) {
@@ -2109,13 +2759,13 @@ var parseEnvFile = async (filePath) => {
2109
2759
  value = value.slice(1, -1);
2110
2760
  }
2111
2761
  if (key) {
2112
- secrets2.push({ key, value });
2762
+ secrets.push({ key, value });
2113
2763
  }
2114
2764
  }
2115
- return secrets2;
2765
+ return secrets;
2116
2766
  };
2117
2767
  var registerSecretCommand = (program) => {
2118
- program.command("secret").description("Secret management").addCommand(new Command2("list").description("List all secret keys").option("-e, --env <name>", "Use a specific config environment").action(async (options) => {
2768
+ program.command("secret").description("Secret management").addCommand(new Command2("list").description("List all secret keys").option("-e, --env <name>", "Use a specific config environment").option("--curl", "Use curl instead of fetch (workaround for macOS local network issues)").action(async (options) => {
2119
2769
  let config;
2120
2770
  try {
2121
2771
  config = options.env ? getConfig(options.env) : await selectConfig2();
@@ -2132,7 +2782,7 @@ var registerSecretCommand = (program) => {
2132
2782
  try {
2133
2783
  console.log(chalk6.gray(`Fetching secrets from ${config.api}...`));
2134
2784
  const privateKey = await loadPrivateKey(config);
2135
- const keys = await listSecrets({ privateKey, url: config.api });
2785
+ const keys = await listSecrets({ privateKey, url: config.api, useCurl: options.curl });
2136
2786
  if (keys.length === 0) {
2137
2787
  console.log(chalk6.yellow("No secrets found"));
2138
2788
  return;
@@ -2146,7 +2796,7 @@ Secret keys:`));
2146
2796
  console.error(chalk6.red("\u2717 Failed to list secrets:"), error instanceof Error ? error.message : String(error));
2147
2797
  process.exit(1);
2148
2798
  }
2149
- })).addCommand(new Command2("create").description("Create a new secret").argument("[key]", "Secret key").argument("[value]", "Secret value").option("-e, --env <name>", "Use a specific config environment").action(async (key, value, options) => {
2799
+ })).addCommand(new Command2("create").description("Create a new secret").argument("[key]", "Secret key").argument("[value]", "Secret value").option("-e, --env <name>", "Use a specific config environment").option("--curl", "Use curl instead of fetch (workaround for macOS local network issues)").action(async (key, value, options) => {
2150
2800
  const config = options?.env ? getConfig(options.env) : await selectConfig2();
2151
2801
  if (!config) {
2152
2802
  console.error(chalk6.red("\u2717 No configuration found."));
@@ -2192,13 +2842,13 @@ Secret keys:`));
2192
2842
  await new Promise((resolve4) => setImmediate(resolve4));
2193
2843
  try {
2194
2844
  const privateKey = await loadPrivateKey(config);
2195
- await createSecret({ key: secretKey, value: secretValue }, { privateKey, url: config.api });
2845
+ await createSecret({ key: secretKey, value: secretValue }, { privateKey, url: config.api, useCurl: options?.curl });
2196
2846
  console.log(chalk6.green("\u2713 Secret created:") + ` ${chalk6.cyan(secretKey)}`);
2197
2847
  } catch (error) {
2198
2848
  console.error(chalk6.red("\u2717 Failed to create secret:"), error instanceof Error ? error.message : error);
2199
2849
  process.exit(1);
2200
2850
  }
2201
- })).addCommand(new Command2("deploy").description("Deploy secrets from a .secret file").argument("[file]", "Path to .secret file").option("-e, --env <name>", "Use a specific config environment").action(async (file, options) => {
2851
+ })).addCommand(new Command2("deploy").description("Deploy secrets from a .secret file").argument("[file]", "Path to .secret file").option("-e, --env <name>", "Use a specific config environment").option("--curl", "Use curl instead of fetch (workaround for macOS local network issues)").action(async (file, options) => {
2202
2852
  const config = options?.env ? getConfig(options.env) : await selectConfig2();
2203
2853
  if (!config) {
2204
2854
  console.error(chalk6.red("\u2717 No configuration found."));
@@ -2212,25 +2862,25 @@ Secret keys:`));
2212
2862
  process.exit(1);
2213
2863
  }
2214
2864
  }
2215
- if (!existsSync6(filePath)) {
2865
+ if (!existsSync7(filePath)) {
2216
2866
  console.error(chalk6.red(`\u2717 File not found: ${filePath}`));
2217
2867
  process.exit(1);
2218
2868
  }
2219
2869
  await new Promise((resolve4) => setImmediate(resolve4));
2220
2870
  try {
2221
- const secrets2 = await parseEnvFile(filePath);
2222
- if (secrets2.length === 0) {
2871
+ const secrets = await parseEnvFile(filePath);
2872
+ if (secrets.length === 0) {
2223
2873
  console.log(chalk6.yellow("No secrets found in file"));
2224
2874
  return;
2225
2875
  }
2226
2876
  const privateKey = await loadPrivateKey(config);
2227
2877
  let created = 0;
2228
2878
  let failed = 0;
2229
- console.log(chalk6.cyan(`Deploying ${secrets2.length} secret(s) to ${config.name}...
2879
+ console.log(chalk6.cyan(`Deploying ${secrets.length} secret(s) to ${config.name}...
2230
2880
  `));
2231
- for (const secret of secrets2) {
2881
+ for (const secret of secrets) {
2232
2882
  try {
2233
- await createSecret(secret, { privateKey, url: config.api });
2883
+ await createSecret(secret, { privateKey, url: config.api, useCurl: options?.curl });
2234
2884
  console.log(chalk6.green("\u2713") + ` ${secret.key}`);
2235
2885
  created++;
2236
2886
  } catch (error) {
@@ -2248,7 +2898,7 @@ Secret keys:`));
2248
2898
  console.error(chalk6.red("\u2717 Failed to deploy secrets:"), error instanceof Error ? error.message : error);
2249
2899
  process.exit(1);
2250
2900
  }
2251
- })).addCommand(new Command2("import").description("Import secrets from a file (alias for deploy)").argument("[file]", "Path to .secret file").option("-e, --env <name>", "Use a specific config environment").action(async (file, options) => {
2901
+ })).addCommand(new Command2("import").description("Import secrets from a file (alias for deploy)").argument("[file]", "Path to .secret file").option("-e, --env <name>", "Use a specific config environment").option("--curl", "Use curl instead of fetch (workaround for macOS local network issues)").action(async (file, options) => {
2252
2902
  const config = options?.env ? getConfig(options.env) : await selectConfig2();
2253
2903
  if (!config) {
2254
2904
  console.error(chalk6.red("\u2717 No configuration found."));
@@ -2262,25 +2912,25 @@ Secret keys:`));
2262
2912
  process.exit(1);
2263
2913
  }
2264
2914
  }
2265
- if (!existsSync6(filePath)) {
2915
+ if (!existsSync7(filePath)) {
2266
2916
  console.error(chalk6.red(`\u2717 File not found: ${filePath}`));
2267
2917
  process.exit(1);
2268
2918
  }
2269
2919
  await new Promise((resolve4) => setImmediate(resolve4));
2270
2920
  try {
2271
- const secrets2 = await parseEnvFile(filePath);
2272
- if (secrets2.length === 0) {
2921
+ const secrets = await parseEnvFile(filePath);
2922
+ if (secrets.length === 0) {
2273
2923
  console.log(chalk6.yellow("No secrets found in file"));
2274
2924
  return;
2275
2925
  }
2276
2926
  const privateKey = await loadPrivateKey(config);
2277
2927
  let created = 0;
2278
2928
  let failed = 0;
2279
- console.log(chalk6.cyan(`Importing ${secrets2.length} secret(s) to ${config.name}...
2929
+ console.log(chalk6.cyan(`Importing ${secrets.length} secret(s) to ${config.name}...
2280
2930
  `));
2281
- for (const secret of secrets2) {
2931
+ for (const secret of secrets) {
2282
2932
  try {
2283
- await createSecret(secret, { privateKey, url: config.api });
2933
+ await createSecret(secret, { privateKey, url: config.api, useCurl: options?.curl });
2284
2934
  console.log(chalk6.green("\u2713") + ` ${secret.key}`);
2285
2935
  created++;
2286
2936
  } catch (error) {
@@ -2301,8 +2951,8 @@ Secret keys:`));
2301
2951
  }));
2302
2952
  };
2303
2953
  // src/commands/host.ts
2304
- import { existsSync as existsSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2 } from "fs";
2305
- 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";
2306
2956
  import { homedir } from "os";
2307
2957
  var {$: $2 } = globalThis.Bun;
2308
2958
  import chalk7 from "chalk";
@@ -2335,7 +2985,7 @@ var generateProductionKeys = async () => {
2335
2985
  return { privateKey, publicKey };
2336
2986
  };
2337
2987
  var saveProductionConfig = async (cloudPath, privateKey, publicKey) => {
2338
- const configPath = join6(cloudPath, "config.json");
2988
+ const configPath = join7(cloudPath, "config.json");
2339
2989
  const config = {
2340
2990
  key: {
2341
2991
  private: privateKey,
@@ -2369,7 +3019,7 @@ WantedBy=multi-user.target
2369
3019
  return serviceContent;
2370
3020
  };
2371
3021
  var ensureCloudDirectory = (cloudPath) => {
2372
- if (!existsSync7(cloudPath)) {
3022
+ if (!existsSync8(cloudPath)) {
2373
3023
  console.log(chalk7.blue("Creating cloud directory..."));
2374
3024
  try {
2375
3025
  mkdirSync3(cloudPath, { recursive: true });
@@ -2399,15 +3049,15 @@ var validateLinux = () => {
2399
3049
  }
2400
3050
  };
2401
3051
  var validateServerBinary = (serverBinPath) => {
2402
- if (!existsSync7(serverBinPath)) {
3052
+ if (!existsSync8(serverBinPath)) {
2403
3053
  console.log(chalk7.red("Server binary not found at:"), serverBinPath);
2404
3054
  console.log(chalk7.yellow("Make sure you have installed the package correctly"));
2405
3055
  process.exit(1);
2406
3056
  }
2407
3057
  };
2408
3058
  var checkExistingProductionConfig = (cloudPath) => {
2409
- const configPath = join6(cloudPath, "config.json");
2410
- if (!existsSync7(configPath)) {
3059
+ const configPath = join7(cloudPath, "config.json");
3060
+ if (!existsSync8(configPath)) {
2411
3061
  return null;
2412
3062
  }
2413
3063
  try {
@@ -2438,15 +3088,15 @@ var unhost = async (options = {}) => {
2438
3088
  console.log(chalk7.yellow("Service was not enabled"));
2439
3089
  }
2440
3090
  const servicePath = "/etc/systemd/system/nulljs.service";
2441
- if (existsSync7(servicePath)) {
3091
+ if (existsSync8(servicePath)) {
2442
3092
  console.log(chalk7.blue("Removing systemd service file..."));
2443
3093
  await $2`sudo rm ${servicePath}`;
2444
3094
  await $2`sudo systemctl daemon-reload`;
2445
3095
  console.log(chalk7.green("Systemd service file removed"));
2446
3096
  }
2447
3097
  if (!options.keepData) {
2448
- const defaultCloudPath = join6(homedir(), ".nulljs");
2449
- if (existsSync7(defaultCloudPath)) {
3098
+ const defaultCloudPath = join7(homedir(), ".nulljs");
3099
+ if (existsSync8(defaultCloudPath)) {
2450
3100
  console.log(chalk7.blue("Removing cloud directory..."));
2451
3101
  await $2`rm -rf ${defaultCloudPath}`;
2452
3102
  console.log(chalk7.green("Cloud directory removed"));
@@ -2470,9 +3120,9 @@ var unhost = async (options = {}) => {
2470
3120
  };
2471
3121
  var update = async () => {
2472
3122
  validateLinux();
2473
- const cloudPath = join6(homedir(), ".nulljs");
3123
+ const cloudPath = join7(homedir(), ".nulljs");
2474
3124
  const servicePath = "/etc/systemd/system/nulljs.service";
2475
- if (!existsSync7(servicePath)) {
3125
+ if (!existsSync8(servicePath)) {
2476
3126
  console.log(chalk7.red("NullJS service is not installed."));
2477
3127
  console.log(chalk7.gray('Run "nulljs host" to set up hosting first.'));
2478
3128
  process.exit(1);
@@ -2503,7 +3153,7 @@ var update = async () => {
2503
3153
  };
2504
3154
  var host = async (cloudPath) => {
2505
3155
  validateLinux();
2506
- 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");
2507
3157
  console.log(chalk7.blue("Setting up NullJS production hosting..."));
2508
3158
  console.log(chalk7.gray(` Cloud path: ${resolvedCloudPath}`));
2509
3159
  const serverBinPath = getServerBinPath();