clawcontrol 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1682 -717
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,8 +4,8 @@ import { createCliRenderer } from "@opentui/core";
4
4
  import { createRoot } from "@opentui/react";
5
5
 
6
6
  // src/App.tsx
7
- import { useState as useState10, useCallback as useCallback3, useRef as useRef5 } from "react";
8
- import { useRenderer } from "@opentui/react";
7
+ import { useState as useState12, useCallback as useCallback4, useRef as useRef6 } from "react";
8
+ import { useRenderer as useRenderer2 } from "@opentui/react";
9
9
 
10
10
  // src/components/Home.tsx
11
11
  import { useState } from "react";
@@ -142,10 +142,12 @@ var LOGO = `
142
142
  `;
143
143
  var COMMANDS = [
144
144
  { name: "/new", description: "Initialize a new deployment" },
145
+ { name: "/list", description: "List, edit, or fork deployments" },
145
146
  { name: "/deploy", description: "Deploy an initialized configuration" },
146
147
  { name: "/status", description: "View deployment status" },
147
148
  { name: "/ssh", description: "SSH into a deployment" },
148
149
  { name: "/logs", description: "View deployment logs" },
150
+ { name: "/dashboard", description: "Open OpenClaw dashboard in browser" },
149
151
  { name: "/destroy", description: "Destroy a deployment" },
150
152
  { name: "/templates", description: "Manage deployment templates" },
151
153
  { name: "/help", description: "Show help" }
@@ -158,10 +160,12 @@ function Home({ context }) {
158
160
  setError(null);
159
161
  const viewMap = {
160
162
  "/new": "new",
163
+ "/list": "list",
161
164
  "/deploy": "deploy",
162
165
  "/status": "status",
163
166
  "/ssh": "ssh",
164
167
  "/logs": "logs",
168
+ "/dashboard": "dashboard",
165
169
  "/destroy": "destroy",
166
170
  "/templates": "templates",
167
171
  "/help": "help"
@@ -223,7 +227,7 @@ function Home({ context }) {
223
227
  children: [
224
228
  /* @__PURE__ */ jsx("text", { fg: t.fg.primary, children: "Available Commands" }),
225
229
  /* @__PURE__ */ jsx("box", { flexDirection: "column", marginTop: 1, children: COMMANDS.map((cmd) => /* @__PURE__ */ jsxs("box", { flexDirection: "row", children: [
226
- /* @__PURE__ */ jsx("text", { fg: t.accent, width: 12, children: cmd.name }),
230
+ /* @__PURE__ */ jsx("text", { fg: t.accent, width: 14, children: cmd.name }),
227
231
  /* @__PURE__ */ jsx("text", { fg: t.fg.secondary, children: cmd.description })
228
232
  ] }, cmd.name)) })
229
233
  ]
@@ -372,7 +376,8 @@ var DeploymentConfigSchema = z.object({
372
376
  hetzner: HetznerConfigSchema.optional(),
373
377
  digitalocean: DigitalOceanConfigSchema.optional(),
374
378
  openclawConfig: OpenClawConfigSchema,
375
- openclawAgent: OpenClawAgentConfigSchema.optional()
379
+ openclawAgent: OpenClawAgentConfigSchema.optional(),
380
+ skipTailscale: z.boolean().optional()
376
381
  });
377
382
  var DeploymentStateSchema = z.object({
378
383
  status: DeploymentStatusSchema,
@@ -388,6 +393,7 @@ var DeploymentStateSchema = z.object({
388
393
  retryCount: z.number()
389
394
  })
390
395
  ),
396
+ gatewayToken: z.string().optional(),
391
397
  lastError: z.string().optional(),
392
398
  deployedAt: z.string().optional(),
393
399
  updatedAt: z.string()
@@ -531,6 +537,14 @@ function deleteDeployment(name) {
531
537
  }
532
538
  rmSync(deploymentDir, { recursive: true, force: true });
533
539
  }
540
+ function updateDeploymentConfig(name, config) {
541
+ const configPath = getConfigPath(name);
542
+ if (!existsSync(configPath)) {
543
+ throw new Error(`Deployment "${name}" not found`);
544
+ }
545
+ const validated = DeploymentConfigSchema.parse(config);
546
+ writeFileSync(configPath, JSON.stringify(validated, null, 2));
547
+ }
534
548
  function validateDeploymentName(name) {
535
549
  if (!name || name.trim() === "") {
536
550
  return { valid: false, error: "Deployment name cannot be empty" };
@@ -1196,20 +1210,36 @@ var DO_DROPLET_SIZES = [
1196
1210
  { slug: "s-4vcpu-8gb", label: "4 vCPU, 8GB RAM, 160GB SSD", price: "$48/mo" },
1197
1211
  { slug: "s-8vcpu-16gb", label: "8 vCPU, 16GB RAM, 320GB SSD", price: "$96/mo" }
1198
1212
  ];
1199
- function getStepList(provider, activeTemplate) {
1213
+ function getStepList(provider, activeTemplate, editMode) {
1214
+ if (editMode === "edit") {
1215
+ const steps = ["api_key_choose"];
1216
+ if (provider === "digitalocean") {
1217
+ steps.push("droplet_size");
1218
+ }
1219
+ steps.push("ai_api_key_choose", "model", "telegram_token_choose", "telegram_allow_choose", "confirm");
1220
+ return steps;
1221
+ }
1222
+ if (editMode === "fork") {
1223
+ const steps = ["name", "api_key_choose"];
1224
+ if (provider === "digitalocean") {
1225
+ steps.push("droplet_size");
1226
+ }
1227
+ steps.push("ai_api_key_choose", "model", "telegram_token_choose", "telegram_allow_choose", "tailscale", "confirm");
1228
+ return steps;
1229
+ }
1200
1230
  const base = ["template_choice"];
1201
1231
  if (!activeTemplate) {
1202
1232
  base.push("name", "provider", "api_key_choose");
1203
1233
  if (provider === "digitalocean") {
1204
1234
  base.push("droplet_size");
1205
1235
  }
1206
- base.push("ai_provider", "ai_api_key_choose", "model", "telegram_token_choose", "telegram_allow_choose", "confirm");
1236
+ base.push("ai_provider", "ai_api_key_choose", "model", "telegram_token_choose", "telegram_allow_choose", "tailscale", "confirm");
1207
1237
  } else {
1208
1238
  base.push("name", "api_key_choose");
1209
1239
  if (activeTemplate.provider === "digitalocean") {
1210
1240
  base.push("droplet_size");
1211
1241
  }
1212
- base.push("ai_api_key_choose", "model", "telegram_token_choose", "telegram_allow_choose", "confirm");
1242
+ base.push("ai_api_key_choose", "model", "telegram_token_choose", "telegram_allow_choose", "tailscale", "confirm");
1213
1243
  }
1214
1244
  return base;
1215
1245
  }
@@ -1236,19 +1266,39 @@ function resolveStepForProgress(s) {
1236
1266
  }
1237
1267
  }
1238
1268
  function NewDeployment({ context }) {
1269
+ const [editingConfig, setEditingConfig] = useState2(null);
1270
+ const [editMode, setEditMode] = useState2(null);
1239
1271
  const [templateChoices, setTemplateChoices] = useState2([]);
1240
1272
  const [selectedTemplateIndex, setSelectedTemplateIndex] = useState2(0);
1241
1273
  const [activeTemplate, setActiveTemplate] = useState2(null);
1242
1274
  const [step, setStep] = useState2(() => {
1275
+ if (context.editingDeployment) {
1276
+ return context.editingDeployment.mode === "edit" ? "api_key_choose" : "name";
1277
+ }
1243
1278
  if (context.selectedTemplate) return "name";
1244
1279
  return "template_choice";
1245
1280
  });
1246
- const [name, setName] = useState2("");
1281
+ const [name, setName] = useState2(() => {
1282
+ if (context.editingDeployment?.mode === "edit") return context.editingDeployment.config.name;
1283
+ return "";
1284
+ });
1247
1285
  const [provider, setProvider] = useState2(() => {
1286
+ if (context.editingDeployment) return context.editingDeployment.config.provider;
1248
1287
  return context.selectedTemplate?.provider ?? "hetzner";
1249
1288
  });
1250
- const [apiKey, setApiKey] = useState2("");
1289
+ const [apiKey, setApiKey] = useState2(() => {
1290
+ if (context.editingDeployment) {
1291
+ const cfg = context.editingDeployment.config;
1292
+ if (cfg.provider === "hetzner" && cfg.hetzner) return cfg.hetzner.apiKey;
1293
+ if (cfg.provider === "digitalocean" && cfg.digitalocean) return cfg.digitalocean.apiKey;
1294
+ }
1295
+ return "";
1296
+ });
1251
1297
  const [selectedDropletSizeIndex, setSelectedDropletSizeIndex] = useState2(() => {
1298
+ if (context.editingDeployment?.config.digitalocean) {
1299
+ const idx = DO_DROPLET_SIZES.findIndex((s) => s.slug === context.editingDeployment.config.digitalocean.size);
1300
+ return idx >= 0 ? idx : 0;
1301
+ }
1252
1302
  if (context.selectedTemplate?.digitalocean) {
1253
1303
  const idx = DO_DROPLET_SIZES.findIndex((s) => s.slug === context.selectedTemplate.digitalocean.size);
1254
1304
  return idx >= 0 ? idx : 0;
@@ -1256,17 +1306,40 @@ function NewDeployment({ context }) {
1256
1306
  return 0;
1257
1307
  });
1258
1308
  const [aiProvider, setAiProvider] = useState2(() => {
1309
+ if (context.editingDeployment?.config.openclawAgent) return context.editingDeployment.config.openclawAgent.aiProvider;
1259
1310
  return context.selectedTemplate?.aiProvider ?? "";
1260
1311
  });
1261
- const [aiApiKey, setAiApiKey] = useState2("");
1312
+ const [aiApiKey, setAiApiKey] = useState2(() => {
1313
+ if (context.editingDeployment?.config.openclawAgent) return context.editingDeployment.config.openclawAgent.aiApiKey;
1314
+ return "";
1315
+ });
1262
1316
  const [model, setModel] = useState2(() => {
1317
+ if (context.editingDeployment?.config.openclawAgent) return context.editingDeployment.config.openclawAgent.model;
1263
1318
  return context.selectedTemplate?.model ?? "";
1264
1319
  });
1265
- const [telegramBotToken, setTelegramBotToken] = useState2("");
1266
- const [telegramAllowFrom, setTelegramAllowFrom] = useState2("");
1320
+ const [telegramBotToken, setTelegramBotToken] = useState2(() => {
1321
+ if (context.editingDeployment?.config.openclawAgent) return context.editingDeployment.config.openclawAgent.telegramBotToken;
1322
+ return "";
1323
+ });
1324
+ const [telegramAllowFrom, setTelegramAllowFrom] = useState2(() => {
1325
+ if (context.editingDeployment?.config.openclawAgent) return context.editingDeployment.config.openclawAgent.telegramAllowFrom ?? "";
1326
+ return "";
1327
+ });
1328
+ const [enableTailscale, setEnableTailscale] = useState2(() => {
1329
+ if (context.editingDeployment?.mode === "fork") return !context.editingDeployment.config.skipTailscale;
1330
+ return false;
1331
+ });
1332
+ const [selectedTailscaleIndex, setSelectedTailscaleIndex] = useState2(() => {
1333
+ if (context.editingDeployment?.mode === "fork" && !context.editingDeployment.config.skipTailscale) return 1;
1334
+ return 0;
1335
+ });
1267
1336
  const [error, setError] = useState2(null);
1268
1337
  const [isValidating, setIsValidating] = useState2(false);
1269
1338
  const [selectedProviderIndex, setSelectedProviderIndex] = useState2(() => {
1339
+ if (context.editingDeployment) {
1340
+ const idx = SUPPORTED_PROVIDERS.indexOf(context.editingDeployment.config.provider);
1341
+ return idx >= 0 ? idx : 0;
1342
+ }
1270
1343
  if (context.selectedTemplate) {
1271
1344
  const idx = SUPPORTED_PROVIDERS.indexOf(context.selectedTemplate.provider);
1272
1345
  return idx >= 0 ? idx : 0;
@@ -1274,6 +1347,10 @@ function NewDeployment({ context }) {
1274
1347
  return 0;
1275
1348
  });
1276
1349
  const [selectedAiProviderIndex, setSelectedAiProviderIndex] = useState2(() => {
1350
+ if (context.editingDeployment?.config.openclawAgent) {
1351
+ const idx = AI_PROVIDERS.findIndex((p) => p.name === context.editingDeployment.config.openclawAgent.aiProvider);
1352
+ return idx >= 0 ? idx : 0;
1353
+ }
1277
1354
  if (context.selectedTemplate) {
1278
1355
  const idx = AI_PROVIDERS.findIndex((p) => p.name === context.selectedTemplate.aiProvider);
1279
1356
  return idx >= 0 ? idx : 0;
@@ -1286,12 +1363,39 @@ function NewDeployment({ context }) {
1286
1363
  const [savedTelegramUsers, setSavedTelegramUsers] = useState2([]);
1287
1364
  const [selectedSavedKeyIndex, setSelectedSavedKeyIndex] = useState2(0);
1288
1365
  const [newKeyName, setNewKeyName] = useState2("");
1289
- const [apiKeyFromSaved, setApiKeyFromSaved] = useState2(false);
1290
- const [aiApiKeyFromSaved, setAiApiKeyFromSaved] = useState2(false);
1291
- const [telegramTokenFromSaved, setTelegramTokenFromSaved] = useState2(false);
1292
- const [telegramAllowFromSaved, setTelegramAllowFromSaved] = useState2(false);
1366
+ const [apiKeyFromSaved, setApiKeyFromSaved] = useState2(() => !!context.editingDeployment);
1367
+ const [aiApiKeyFromSaved, setAiApiKeyFromSaved] = useState2(() => !!context.editingDeployment);
1368
+ const [telegramTokenFromSaved, setTelegramTokenFromSaved] = useState2(() => !!context.editingDeployment);
1369
+ const [telegramAllowFromSaved, setTelegramAllowFromSaved] = useState2(() => !!context.editingDeployment);
1293
1370
  useEffect(() => {
1294
- if (context.selectedTemplate) {
1371
+ if (context.editingDeployment) {
1372
+ const ed = context.editingDeployment;
1373
+ setEditingConfig(ed.config);
1374
+ setEditMode(ed.mode);
1375
+ const syntheticTemplate = {
1376
+ id: "__editing__",
1377
+ name: ed.config.name,
1378
+ description: "Editing deployment",
1379
+ builtIn: false,
1380
+ createdAt: ed.config.createdAt,
1381
+ provider: ed.config.provider,
1382
+ hetzner: ed.config.hetzner ? {
1383
+ serverType: ed.config.hetzner.serverType,
1384
+ location: ed.config.hetzner.location,
1385
+ image: ed.config.hetzner.image
1386
+ } : void 0,
1387
+ digitalocean: ed.config.digitalocean ? {
1388
+ size: ed.config.digitalocean.size,
1389
+ region: ed.config.digitalocean.region,
1390
+ image: ed.config.digitalocean.image
1391
+ } : void 0,
1392
+ aiProvider: ed.config.openclawAgent?.aiProvider ?? "",
1393
+ model: ed.config.openclawAgent?.model ?? "",
1394
+ channel: ed.config.openclawAgent?.channel ?? "telegram"
1395
+ };
1396
+ setActiveTemplate(syntheticTemplate);
1397
+ context.setEditingDeployment(null);
1398
+ } else if (context.selectedTemplate) {
1295
1399
  setActiveTemplate(context.selectedTemplate);
1296
1400
  context.setSelectedTemplate(null);
1297
1401
  }
@@ -1336,6 +1440,8 @@ function NewDeployment({ context }) {
1336
1440
  step,
1337
1441
  selectedDropletSizeIndex,
1338
1442
  activeTemplate,
1443
+ editingConfig,
1444
+ editMode,
1339
1445
  savedProviderKeys,
1340
1446
  savedAiKeys,
1341
1447
  savedTelegramTokens,
@@ -1345,7 +1451,9 @@ function NewDeployment({ context }) {
1345
1451
  apiKeyFromSaved,
1346
1452
  aiApiKeyFromSaved,
1347
1453
  telegramTokenFromSaved,
1348
- telegramAllowFromSaved
1454
+ telegramAllowFromSaved,
1455
+ enableTailscale,
1456
+ selectedTailscaleIndex
1349
1457
  });
1350
1458
  stateRef.current = {
1351
1459
  name,
@@ -1359,6 +1467,8 @@ function NewDeployment({ context }) {
1359
1467
  step,
1360
1468
  selectedDropletSizeIndex,
1361
1469
  activeTemplate,
1470
+ editingConfig,
1471
+ editMode,
1362
1472
  savedProviderKeys,
1363
1473
  savedAiKeys,
1364
1474
  savedTelegramTokens,
@@ -1368,10 +1478,12 @@ function NewDeployment({ context }) {
1368
1478
  apiKeyFromSaved,
1369
1479
  aiApiKeyFromSaved,
1370
1480
  telegramTokenFromSaved,
1371
- telegramAllowFromSaved
1481
+ telegramAllowFromSaved,
1482
+ enableTailscale,
1483
+ selectedTailscaleIndex
1372
1484
  };
1373
1485
  debugLog(`RENDER: step=${step}, apiKey.length=${apiKey?.length ?? "null"}`);
1374
- const stepList = getStepList(provider, activeTemplate);
1486
+ const stepList = getStepList(provider, activeTemplate, editMode ?? void 0);
1375
1487
  const handleConfirmFromRef = () => {
1376
1488
  const s = stateRef.current;
1377
1489
  debugLog(`handleConfirmFromRef CALLED: apiKey.length=${s.apiKey?.length ?? "null"}`);
@@ -1410,7 +1522,7 @@ function NewDeployment({ context }) {
1410
1522
  const config = {
1411
1523
  name: s.name,
1412
1524
  provider: s.provider,
1413
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1525
+ createdAt: s.editingConfig?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1414
1526
  hetzner: s.provider === "hetzner" ? {
1415
1527
  apiKey: s.apiKey,
1416
1528
  serverType: tmpl?.hetzner?.serverType ?? "cpx11",
@@ -1424,6 +1536,7 @@ function NewDeployment({ context }) {
1424
1536
  image: tmpl?.digitalocean?.image ?? "ubuntu-24-04-x64"
1425
1537
  } : void 0,
1426
1538
  openclawConfig: void 0,
1539
+ skipTailscale: !s.enableTailscale,
1427
1540
  openclawAgent: {
1428
1541
  aiProvider: s.aiProvider,
1429
1542
  aiApiKey: s.aiApiKey,
@@ -1433,11 +1546,16 @@ function NewDeployment({ context }) {
1433
1546
  telegramAllowFrom: s.telegramAllowFrom
1434
1547
  }
1435
1548
  };
1436
- createDeployment(config);
1549
+ if (s.editingConfig && s.editMode === "edit") {
1550
+ const updatedConfig = { ...config, name: s.editingConfig.name, createdAt: s.editingConfig.createdAt };
1551
+ updateDeploymentConfig(s.editingConfig.name, updatedConfig);
1552
+ } else {
1553
+ createDeployment(config);
1554
+ }
1437
1555
  context.refreshDeployments();
1438
1556
  setStep("complete");
1439
1557
  } catch (err) {
1440
- setError(`Failed to create deployment: ${err instanceof Error ? err.message : String(err)}`);
1558
+ setError(`Failed to ${s.editMode === "edit" ? "update" : "create"} deployment: ${err instanceof Error ? err.message : String(err)}`);
1441
1559
  }
1442
1560
  };
1443
1561
  useKeyboard((key) => {
@@ -1629,7 +1747,7 @@ function NewDeployment({ context }) {
1629
1747
  setTelegramAllowFrom(saved.value);
1630
1748
  setTelegramAllowFromSaved(true);
1631
1749
  setError(null);
1632
- setStep("confirm");
1750
+ setStep(currentState.editMode === "edit" ? "confirm" : "tailscale");
1633
1751
  }
1634
1752
  } else if (key.name === "escape") {
1635
1753
  setStep("telegram_token_choose");
@@ -1666,13 +1784,26 @@ function NewDeployment({ context }) {
1666
1784
  setStep("telegram_allow_name");
1667
1785
  setNewKeyName("");
1668
1786
  } else if (key.name === "n" || key.name === "escape") {
1787
+ setStep(currentState.editMode === "edit" ? "confirm" : "tailscale");
1788
+ }
1789
+ } else if (currentState.step === "tailscale") {
1790
+ const TAILSCALE_OPTIONS = ["Skip", "Configure Tailscale"];
1791
+ if (key.name === "up") {
1792
+ setSelectedTailscaleIndex((prev) => Math.max(0, prev - 1));
1793
+ } else if (key.name === "down") {
1794
+ setSelectedTailscaleIndex((prev) => Math.min(TAILSCALE_OPTIONS.length - 1, prev + 1));
1795
+ } else if (key.name === "return") {
1796
+ setEnableTailscale(currentState.selectedTailscaleIndex === 1);
1797
+ setError(null);
1669
1798
  setStep("confirm");
1799
+ } else if (key.name === "escape") {
1800
+ setStep("telegram_allow_choose");
1670
1801
  }
1671
1802
  } else if (currentState.step === "confirm") {
1672
1803
  if (key.name === "y" || key.name === "return") {
1673
1804
  handleConfirmFromRef();
1674
1805
  } else if (key.name === "n" || key.name === "escape") {
1675
- setStep("telegram_allow_choose");
1806
+ setStep(currentState.editMode === "edit" ? "telegram_allow_choose" : "tailscale");
1676
1807
  }
1677
1808
  } else if (currentState.step === "complete") {
1678
1809
  context.navigateTo("home");
@@ -1794,7 +1925,7 @@ function NewDeployment({ context }) {
1794
1925
  }
1795
1926
  setError(null);
1796
1927
  if (telegramAllowFromSaved) {
1797
- setStep("confirm");
1928
+ setStep(editMode === "edit" ? "confirm" : "tailscale");
1798
1929
  } else {
1799
1930
  setStep("telegram_allow_save");
1800
1931
  }
@@ -2508,10 +2639,10 @@ function NewDeployment({ context }) {
2508
2639
  setNewKeyName(value);
2509
2640
  }
2510
2641
  },
2511
- onSubmit: () => handleSaveKeyName("telegram-user", telegramAllowFrom, "confirm"),
2642
+ onSubmit: () => handleSaveKeyName("telegram-user", telegramAllowFrom, editMode === "edit" ? "confirm" : "tailscale"),
2512
2643
  onKeyDown: (e) => {
2513
2644
  if (e.name === "escape") {
2514
- setStep("confirm");
2645
+ setStep(editMode === "edit" ? "confirm" : "tailscale");
2515
2646
  }
2516
2647
  }
2517
2648
  }
@@ -2519,6 +2650,41 @@ function NewDeployment({ context }) {
2519
2650
  error && /* @__PURE__ */ jsx2("text", { fg: t.status.error, marginTop: 1, children: error }),
2520
2651
  /* @__PURE__ */ jsx2("text", { fg: t.fg.muted, marginTop: 2, children: "Press Enter to save, Esc to skip" })
2521
2652
  ] });
2653
+ case "tailscale": {
2654
+ const tailscaleOptions = [
2655
+ { label: "Skip", description: "Use SSH tunnel via /dashboard instead" },
2656
+ { label: "Configure Tailscale", description: "Private VPN between your devices and the server" }
2657
+ ];
2658
+ return /* @__PURE__ */ jsxs2("box", { flexDirection: "column", children: [
2659
+ /* @__PURE__ */ jsxs2("text", { fg: t.accent, children: [
2660
+ "Step ",
2661
+ currentStepNumber("tailscale"),
2662
+ ": Tailscale Setup (Optional)"
2663
+ ] }),
2664
+ /* @__PURE__ */ jsx2("text", { fg: t.fg.secondary, marginTop: 1, children: "Tailscale creates a private VPN between your devices and your OpenClaw server." }),
2665
+ /* @__PURE__ */ jsx2("text", { fg: t.fg.secondary, children: "You can skip this and use an SSH tunnel instead (the /dashboard command sets one up automatically)." }),
2666
+ /* @__PURE__ */ jsx2(
2667
+ "box",
2668
+ {
2669
+ flexDirection: "column",
2670
+ borderStyle: "single",
2671
+ borderColor: t.border.default,
2672
+ marginTop: 1,
2673
+ padding: 1,
2674
+ children: tailscaleOptions.map((opt, i) => {
2675
+ const isSelected = i === selectedTailscaleIndex;
2676
+ return /* @__PURE__ */ jsxs2("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
2677
+ /* @__PURE__ */ jsx2("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
2678
+ /* @__PURE__ */ jsx2("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: opt.label }),
2679
+ /* @__PURE__ */ jsx2("text", { fg: isSelected ? t.fg.primary : t.fg.secondary, children: " - " + opt.description })
2680
+ ] }, opt.label);
2681
+ })
2682
+ }
2683
+ ),
2684
+ error && /* @__PURE__ */ jsx2("text", { fg: t.status.error, marginTop: 1, children: error }),
2685
+ /* @__PURE__ */ jsx2("text", { fg: t.fg.muted, marginTop: 1, children: "Press Enter to select, Esc to go back" })
2686
+ ] });
2687
+ }
2522
2688
  case "confirm":
2523
2689
  return /* @__PURE__ */ jsxs2("box", { flexDirection: "column", children: [
2524
2690
  /* @__PURE__ */ jsxs2("text", { fg: t.accent, children: [
@@ -2578,6 +2744,10 @@ function NewDeployment({ context }) {
2578
2744
  /* @__PURE__ */ jsxs2("box", { flexDirection: "row", children: [
2579
2745
  /* @__PURE__ */ jsx2("text", { fg: t.fg.secondary, width: 20, children: "Allow From:" }),
2580
2746
  /* @__PURE__ */ jsx2("text", { fg: t.fg.primary, children: telegramAllowFrom || "N/A" })
2747
+ ] }),
2748
+ /* @__PURE__ */ jsxs2("box", { flexDirection: "row", children: [
2749
+ /* @__PURE__ */ jsx2("text", { fg: t.fg.secondary, width: 20, children: "Tailscale:" }),
2750
+ /* @__PURE__ */ jsx2("text", { fg: enableTailscale ? t.status.success : t.fg.muted, children: enableTailscale ? "Enabled" : "Skipped" })
2581
2751
  ] })
2582
2752
  ]
2583
2753
  }
@@ -2587,7 +2757,7 @@ function NewDeployment({ context }) {
2587
2757
  ] });
2588
2758
  case "complete":
2589
2759
  return /* @__PURE__ */ jsxs2("box", { flexDirection: "column", children: [
2590
- /* @__PURE__ */ jsx2("text", { fg: t.status.success, children: "Deployment Configuration Created!" }),
2760
+ /* @__PURE__ */ jsx2("text", { fg: t.status.success, children: editMode === "edit" ? "Deployment Updated Successfully!" : "Deployment Configuration Created!" }),
2591
2761
  /* @__PURE__ */ jsxs2(
2592
2762
  "box",
2593
2763
  {
@@ -2597,14 +2767,10 @@ function NewDeployment({ context }) {
2597
2767
  padding: 1,
2598
2768
  marginTop: 1,
2599
2769
  children: [
2600
- /* @__PURE__ */ jsxs2("text", { fg: t.fg.primary, children: [
2601
- 'Your deployment "',
2602
- name,
2603
- '" has been initialized.'
2604
- ] }),
2770
+ /* @__PURE__ */ jsx2("text", { fg: t.fg.primary, children: editMode === "edit" ? `Your deployment "${editingConfig?.name ?? name}" has been updated.` : `Your deployment "${name}" has been initialized.` }),
2605
2771
  /* @__PURE__ */ jsxs2("text", { fg: t.fg.secondary, marginTop: 1, children: [
2606
2772
  "Configuration saved to: ~/.clawcontrol/deployments/",
2607
- name,
2773
+ editingConfig?.name ?? name,
2608
2774
  "/"
2609
2775
  ] }),
2610
2776
  /* @__PURE__ */ jsxs2("text", { fg: t.fg.secondary, marginTop: 1, children: [
@@ -2621,15 +2787,15 @@ function NewDeployment({ context }) {
2621
2787
  ]
2622
2788
  }
2623
2789
  ),
2624
- /* @__PURE__ */ jsx2("text", { fg: t.accent, marginTop: 2, children: "Next step: Run /deploy to deploy this configuration" }),
2790
+ editMode !== "edit" && /* @__PURE__ */ jsx2("text", { fg: t.accent, marginTop: 2, children: "Next step: Run /deploy to deploy this configuration" }),
2625
2791
  /* @__PURE__ */ jsx2("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
2626
2792
  ] });
2627
2793
  }
2628
2794
  };
2629
2795
  return /* @__PURE__ */ jsxs2("box", { flexDirection: "column", width: "100%", padding: 1, children: [
2630
2796
  /* @__PURE__ */ jsxs2("box", { flexDirection: "row", marginBottom: 2, children: [
2631
- /* @__PURE__ */ jsx2("text", { fg: t.accent, children: "/new" }),
2632
- /* @__PURE__ */ jsx2("text", { fg: t.fg.secondary, children: " - Initialize a new deployment" })
2797
+ /* @__PURE__ */ jsx2("text", { fg: t.accent, children: editMode === "edit" ? "/list" : "/new" }),
2798
+ /* @__PURE__ */ jsx2("text", { fg: t.fg.secondary, children: editMode === "edit" ? ` - Edit Deployment: ${editingConfig?.name ?? ""}` : editMode === "fork" ? " - Fork Deployment" : " - Initialize a new deployment" })
2633
2799
  ] }),
2634
2800
  /* @__PURE__ */ jsx2("box", { flexDirection: "row", marginBottom: 2, children: stepList.map((s, i) => {
2635
2801
  const resolvedStep = resolveStepForProgress(step);
@@ -2644,46 +2810,81 @@ function NewDeployment({ context }) {
2644
2810
  ] });
2645
2811
  }
2646
2812
 
2647
- // src/components/DeployView.tsx
2648
- import { useState as useState3 } from "react";
2813
+ // src/components/ListView.tsx
2814
+ import { useState as useState3, useRef as useRef2 } from "react";
2649
2815
  import { useKeyboard as useKeyboard2 } from "@opentui/react";
2650
- import { jsx as jsx3, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
2651
- function DeployView({ context }) {
2816
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
2817
+ function ListView({ context }) {
2818
+ const [viewState, setViewState] = useState3("listing");
2652
2819
  const [selectedIndex, setSelectedIndex] = useState3(0);
2653
- const [confirmMode, setConfirmMode] = useState3(false);
2820
+ const [confirmText, setConfirmText] = useState3("");
2821
+ const [error, setError] = useState3(null);
2822
+ const [deletedName, setDeletedName] = useState3("");
2654
2823
  const deployments = context.deployments;
2655
- const notDeployed = deployments.filter((d) => d.state.status !== "deployed");
2656
- const deployed = deployments.filter((d) => d.state.status === "deployed");
2657
- const allDeployments = [...notDeployed, ...deployed];
2658
- const selectedDeployment = allDeployments[selectedIndex];
2824
+ const selectedDeployment = deployments[selectedIndex];
2825
+ const stateRef = useRef2({ viewState, selectedIndex });
2826
+ stateRef.current = { viewState, selectedIndex };
2659
2827
  useKeyboard2((key) => {
2660
- if (allDeployments.length === 0) {
2828
+ const current = stateRef.current;
2829
+ if (deployments.length === 0) {
2661
2830
  context.navigateTo("home");
2662
2831
  return;
2663
2832
  }
2664
- if (confirmMode) {
2665
- if (key.name === "y" || key.name === "return") {
2666
- context.navigateTo("deploying", selectedDeployment.config.name);
2667
- } else if (key.name === "n" || key.name === "escape") {
2668
- setConfirmMode(false);
2669
- }
2670
- } else {
2671
- if (key.name === "up" && selectedIndex > 0) {
2672
- setSelectedIndex(selectedIndex - 1);
2673
- } else if (key.name === "down" && selectedIndex < allDeployments.length - 1) {
2674
- setSelectedIndex(selectedIndex + 1);
2833
+ if (current.viewState === "listing") {
2834
+ if (key.name === "up" && current.selectedIndex > 0) {
2835
+ setSelectedIndex(current.selectedIndex - 1);
2836
+ } else if (key.name === "down" && current.selectedIndex < deployments.length - 1) {
2837
+ setSelectedIndex(current.selectedIndex + 1);
2675
2838
  } else if (key.name === "return") {
2676
- setConfirmMode(true);
2839
+ setViewState("detail");
2677
2840
  } else if (key.name === "escape") {
2678
2841
  context.navigateTo("home");
2679
2842
  }
2843
+ } else if (current.viewState === "detail") {
2844
+ const dep = deployments[current.selectedIndex];
2845
+ if (!dep) return;
2846
+ if (key.name === "e" && dep.state.status === "initialized") {
2847
+ context.setEditingDeployment({ config: dep.config, mode: "edit" });
2848
+ context.navigateTo("new");
2849
+ } else if (key.name === "f") {
2850
+ context.setEditingDeployment({ config: dep.config, mode: "fork" });
2851
+ context.navigateTo("new");
2852
+ } else if (key.name === "d") {
2853
+ if (dep.state.status === "initialized") {
2854
+ setConfirmText("");
2855
+ setError(null);
2856
+ setViewState("delete_confirm");
2857
+ } else {
2858
+ setError("Deployed agents must be destroyed first using the /destroy command.");
2859
+ }
2860
+ } else if (key.name === "escape") {
2861
+ setError(null);
2862
+ setViewState("listing");
2863
+ }
2864
+ } else if (current.viewState === "success") {
2865
+ context.navigateTo("home");
2866
+ } else if (current.viewState === "error") {
2867
+ setError(null);
2868
+ setViewState("listing");
2680
2869
  }
2681
2870
  });
2682
- if (allDeployments.length === 0) {
2871
+ const handleDelete = (name) => {
2872
+ setViewState("deleting");
2873
+ try {
2874
+ deleteDeployment(name);
2875
+ setDeletedName(name);
2876
+ context.refreshDeployments();
2877
+ setViewState("success");
2878
+ } catch (err) {
2879
+ setError(err instanceof Error ? err.message : String(err));
2880
+ setViewState("error");
2881
+ }
2882
+ };
2883
+ if (deployments.length === 0) {
2683
2884
  return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
2684
2885
  /* @__PURE__ */ jsxs3("box", { flexDirection: "row", marginBottom: 2, children: [
2685
- /* @__PURE__ */ jsx3("text", { fg: t.accent, children: "/deploy" }),
2686
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, children: " - Deploy a configuration" })
2886
+ /* @__PURE__ */ jsx3("text", { fg: t.accent, children: "/list" }),
2887
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, children: " - Deployments" })
2687
2888
  ] }),
2688
2889
  /* @__PURE__ */ jsxs3(
2689
2890
  "box",
@@ -2694,157 +2895,432 @@ function DeployView({ context }) {
2694
2895
  padding: 1,
2695
2896
  children: [
2696
2897
  /* @__PURE__ */ jsx3("text", { fg: t.status.warning, children: "No deployments found!" }),
2697
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, marginTop: 1, children: "Run /new first to create a deployment configuration." })
2898
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, marginTop: 1, children: "Run /new to create a deployment." })
2698
2899
  ]
2699
2900
  }
2700
2901
  ),
2701
2902
  /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
2702
2903
  ] });
2703
2904
  }
2704
- if (confirmMode) {
2905
+ if (viewState === "listing") {
2705
2906
  return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
2706
2907
  /* @__PURE__ */ jsxs3("box", { flexDirection: "row", marginBottom: 2, children: [
2707
- /* @__PURE__ */ jsx3("text", { fg: t.accent, children: "/deploy" }),
2708
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, children: " - Confirm deployment" })
2908
+ /* @__PURE__ */ jsx3("text", { fg: t.accent, children: "/list" }),
2909
+ /* @__PURE__ */ jsxs3("text", { fg: t.fg.secondary, children: [
2910
+ " - Deployments (",
2911
+ deployments.length,
2912
+ ")"
2913
+ ] })
2709
2914
  ] }),
2710
- /* @__PURE__ */ jsxs3(
2915
+ /* @__PURE__ */ jsx3(
2711
2916
  "box",
2712
2917
  {
2713
2918
  flexDirection: "column",
2714
2919
  borderStyle: "single",
2715
2920
  borderColor: t.border.default,
2716
2921
  padding: 1,
2922
+ children: deployments.map((dep, index) => {
2923
+ const isSelected = index === selectedIndex;
2924
+ return /* @__PURE__ */ jsxs3(
2925
+ "box",
2926
+ {
2927
+ flexDirection: "row",
2928
+ backgroundColor: isSelected ? t.selection.bg : void 0,
2929
+ children: [
2930
+ /* @__PURE__ */ jsx3("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
2931
+ /* @__PURE__ */ jsx3("text", { fg: isSelected ? t.selection.fg : t.fg.primary, width: 25, children: dep.config.name }),
2932
+ /* @__PURE__ */ jsxs3("text", { fg: statusColor(dep.state.status), width: 16, children: [
2933
+ "[",
2934
+ dep.state.status,
2935
+ "]"
2936
+ ] }),
2937
+ /* @__PURE__ */ jsx3("text", { fg: isSelected ? t.fg.secondary : t.fg.muted, children: PROVIDER_NAMES[dep.config.provider] })
2938
+ ]
2939
+ },
2940
+ dep.config.name
2941
+ );
2942
+ })
2943
+ }
2944
+ ),
2945
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, marginTop: 2, children: "Up/Down: Select | Enter: Details | Esc: Back" })
2946
+ ] });
2947
+ }
2948
+ if (viewState === "detail" && selectedDeployment) {
2949
+ const dep = selectedDeployment;
2950
+ const isInitialized = dep.state.status === "initialized";
2951
+ return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
2952
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", marginBottom: 2, children: [
2953
+ /* @__PURE__ */ jsx3("text", { fg: t.accent, children: "/list" }),
2954
+ /* @__PURE__ */ jsxs3("text", { fg: t.fg.secondary, children: [
2955
+ " - ",
2956
+ dep.config.name
2957
+ ] })
2958
+ ] }),
2959
+ /* @__PURE__ */ jsxs3(
2960
+ "box",
2961
+ {
2962
+ flexDirection: "column",
2963
+ borderStyle: "single",
2964
+ borderColor: t.border.focus,
2965
+ padding: 1,
2966
+ marginBottom: 1,
2717
2967
  children: [
2718
- /* @__PURE__ */ jsxs3("text", { fg: t.status.warning, children: [
2719
- 'Deploy "',
2720
- selectedDeployment.config.name,
2721
- '"?'
2968
+ /* @__PURE__ */ jsx3("text", { fg: t.accent, marginBottom: 1, children: "Deployment Details" }),
2969
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
2970
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Name:" }),
2971
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: dep.config.name })
2722
2972
  ] }),
2723
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, marginTop: 1, children: "This will:" }),
2724
- /* @__PURE__ */ jsxs3("text", { fg: t.fg.primary, children: [
2725
- "\u2022 Create a VPS on ",
2726
- selectedDeployment.config.provider
2973
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
2974
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Status:" }),
2975
+ /* @__PURE__ */ jsx3("text", { fg: statusColor(dep.state.status), children: dep.state.status })
2976
+ ] }),
2977
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
2978
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Provider:" }),
2979
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: PROVIDER_NAMES[dep.config.provider] })
2980
+ ] }),
2981
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
2982
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Created:" }),
2983
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: new Date(dep.config.createdAt).toLocaleString() })
2984
+ ] }),
2985
+ dep.state.serverIp && /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
2986
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Server IP:" }),
2987
+ /* @__PURE__ */ jsx3("text", { fg: t.accent, children: dep.state.serverIp })
2988
+ ] }),
2989
+ dep.config.provider === "hetzner" && dep.config.hetzner && /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
2990
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Server Type:" }),
2991
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: dep.config.hetzner.serverType })
2727
2992
  ] }),
2728
- /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: "\u2022 Install and configure OpenClaw" }),
2729
- /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: "\u2022 Set up Tailscale for secure access" }),
2730
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, marginTop: 1, children: "Estimated cost: ~$4.99/month (Hetzner CPX11)" })
2993
+ dep.config.provider === "digitalocean" && dep.config.digitalocean && /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
2994
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Droplet Size:" }),
2995
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: dep.config.digitalocean.size })
2996
+ ] }),
2997
+ dep.config.openclawAgent && /* @__PURE__ */ jsxs3(Fragment, { children: [
2998
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
2999
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "AI Provider:" }),
3000
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: dep.config.openclawAgent.aiProvider })
3001
+ ] }),
3002
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
3003
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Model:" }),
3004
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: dep.config.openclawAgent.model })
3005
+ ] }),
3006
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
3007
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Channel:" }),
3008
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: dep.config.openclawAgent.channel })
3009
+ ] })
3010
+ ] })
2731
3011
  ]
2732
3012
  }
2733
3013
  ),
2734
- /* @__PURE__ */ jsx3("text", { fg: t.status.warning, marginTop: 2, children: "Press Y to confirm, N to cancel" })
3014
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", marginBottom: 1, children: [
3015
+ isInitialized && /* @__PURE__ */ jsx3("text", { fg: t.accent, marginRight: 2, children: "[E]dit" }),
3016
+ /* @__PURE__ */ jsx3("text", { fg: t.accent, marginRight: 2, children: "[F]ork" }),
3017
+ isInitialized ? /* @__PURE__ */ jsx3("text", { fg: t.status.error, children: "[D]elete" }) : /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, children: "[D]elete (use /destroy)" })
3018
+ ] }),
3019
+ error && /* @__PURE__ */ jsx3("text", { fg: t.status.error, marginTop: 1, children: error }),
3020
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, marginTop: 1, children: "Esc: Back to list" })
2735
3021
  ] });
2736
3022
  }
2737
- return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
2738
- /* @__PURE__ */ jsxs3("box", { flexDirection: "row", marginBottom: 2, children: [
2739
- /* @__PURE__ */ jsx3("text", { fg: t.accent, children: "/deploy" }),
2740
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, children: " - Select a deployment to deploy" })
2741
- ] }),
2742
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, marginBottom: 1, children: "Use arrow keys to select, Enter to deploy:" }),
2743
- /* @__PURE__ */ jsx3(
2744
- "box",
2745
- {
2746
- flexDirection: "column",
2747
- borderStyle: "single",
2748
- borderColor: t.border.default,
2749
- padding: 1,
2750
- children: allDeployments.map((deployment, index) => {
2751
- const isSelected = index === selectedIndex;
2752
- return /* @__PURE__ */ jsxs3(
2753
- "box",
2754
- {
2755
- flexDirection: "row",
2756
- backgroundColor: isSelected ? t.selection.bg : void 0,
2757
- children: [
2758
- /* @__PURE__ */ jsx3("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
2759
- /* @__PURE__ */ jsx3("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
2760
- /* @__PURE__ */ jsxs3("text", { fg: statusColor(deployment.state.status), width: 15, children: [
2761
- "[",
2762
- deployment.state.status,
2763
- "]"
2764
- ] }),
2765
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, children: deployment.config.provider })
2766
- ]
2767
- },
2768
- deployment.config.name
2769
- );
2770
- })
2771
- }
2772
- ),
2773
- /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, marginTop: 2, children: "Press Esc to go back" })
2774
- ] });
2775
- }
2776
-
2777
- // src/components/DeployingView.tsx
2778
- import { useState as useState4, useEffect as useEffect2, useCallback, useRef as useRef2 } from "react";
2779
- import { useKeyboard as useKeyboard3 } from "@opentui/react";
2780
- import open from "open";
2781
-
2782
- // src/services/ssh.ts
2783
- import { Client } from "ssh2";
2784
- import { generateKeyPairSync, randomBytes } from "crypto";
2785
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, chmodSync, existsSync as existsSync4 } from "fs";
2786
- import { mkdirSync as mkdirSync4 } from "fs";
2787
- function generateSSHKeyPair(comment = "clawcontrol") {
2788
- const { privateKey, publicKey } = generateKeyPairSync("ed25519", {
2789
- publicKeyEncoding: {
2790
- type: "spki",
2791
- format: "pem"
2792
- },
2793
- privateKeyEncoding: {
2794
- type: "pkcs8",
2795
- format: "pem"
2796
- }
2797
- });
2798
- const pubKeyDer = extractDERFromPEM(publicKey);
2799
- const privKeyDer = extractDERFromPEM(privateKey);
2800
- const publicKeyBytes = pubKeyDer.slice(-32);
2801
- const privateKeyBytes = privKeyDer.slice(-32);
2802
- const publicKeyOpenSSH = buildOpenSSHPublicKey(publicKeyBytes, comment);
2803
- const privateKeyOpenSSH = buildOpenSSHPrivateKey(privateKeyBytes, publicKeyBytes, comment);
2804
- return {
2805
- privateKey: privateKeyOpenSSH,
2806
- publicKey: publicKeyOpenSSH
2807
- };
2808
- }
2809
- function extractDERFromPEM(pem) {
2810
- const lines = pem.split("\n").filter((line) => !line.startsWith("-----") && line.trim() !== "");
2811
- return Buffer.from(lines.join(""), "base64");
2812
- }
2813
- function buildOpenSSHPublicKey(publicKeyBytes, comment) {
2814
- const keyType = Buffer.from("ssh-ed25519");
2815
- const keyTypeLen = Buffer.alloc(4);
2816
- keyTypeLen.writeUInt32BE(keyType.length);
2817
- const keyLen = Buffer.alloc(4);
2818
- keyLen.writeUInt32BE(publicKeyBytes.length);
2819
- const opensshKey = Buffer.concat([keyTypeLen, keyType, keyLen, publicKeyBytes]);
2820
- return `ssh-ed25519 ${opensshKey.toString("base64")} ${comment}`;
2821
- }
2822
- function buildOpenSSHPrivateKey(privateKeyBytes, publicKeyBytes, comment) {
2823
- const AUTH_MAGIC = Buffer.from("openssh-key-v1\0");
2824
- const cipherName = Buffer.from("none");
2825
- const kdfName = Buffer.from("none");
2826
- const kdfOptions = Buffer.alloc(0);
2827
- const numKeys = 1;
2828
- const keyType = Buffer.from("ssh-ed25519");
2829
- const pubKeyBlob = Buffer.concat([
2830
- uint32BE(keyType.length),
2831
- keyType,
2832
- uint32BE(publicKeyBytes.length),
2833
- publicKeyBytes
2834
- ]);
2835
- const checkInt = randomBytes(4);
2836
- const ed25519PrivKey = Buffer.concat([privateKeyBytes, publicKeyBytes]);
2837
- const commentBuf = Buffer.from(comment);
2838
- const privateSection = Buffer.concat([
2839
- checkInt,
2840
- checkInt,
2841
- // Two identical check integers
2842
- uint32BE(keyType.length),
2843
- keyType,
2844
- uint32BE(publicKeyBytes.length),
2845
- publicKeyBytes,
2846
- uint32BE(ed25519PrivKey.length),
2847
- ed25519PrivKey,
3023
+ if (viewState === "delete_confirm" && selectedDeployment) {
3024
+ return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
3025
+ /* @__PURE__ */ jsx3("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx3("text", { fg: t.status.error, children: "Confirm Deletion" }) }),
3026
+ /* @__PURE__ */ jsxs3(
3027
+ "box",
3028
+ {
3029
+ flexDirection: "column",
3030
+ borderStyle: "double",
3031
+ borderColor: t.status.error,
3032
+ padding: 1,
3033
+ children: [
3034
+ /* @__PURE__ */ jsx3("text", { fg: t.status.error, children: "You are about to delete:" }),
3035
+ /* @__PURE__ */ jsxs3("text", { fg: t.fg.primary, marginTop: 1, children: [
3036
+ "Deployment: ",
3037
+ selectedDeployment.config.name
3038
+ ] }),
3039
+ /* @__PURE__ */ jsx3("text", { fg: t.status.error, marginTop: 1, children: "This will permanently delete:" }),
3040
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, children: " Local configuration files" }),
3041
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, children: " SSH keys" })
3042
+ ]
3043
+ }
3044
+ ),
3045
+ /* @__PURE__ */ jsx3("text", { fg: t.status.warning, marginTop: 2, children: "Type the deployment name to confirm:" }),
3046
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, marginTop: 1, children: "Confirm:" }),
3047
+ /* @__PURE__ */ jsx3(
3048
+ "input",
3049
+ {
3050
+ value: confirmText,
3051
+ placeholder: selectedDeployment.config.name,
3052
+ focused: true,
3053
+ onInput: (value) => setConfirmText(value),
3054
+ onSubmit: (value) => {
3055
+ if (value === selectedDeployment.config.name) {
3056
+ handleDelete(selectedDeployment.config.name);
3057
+ } else {
3058
+ setError("Name does not match. Please type the exact deployment name.");
3059
+ }
3060
+ },
3061
+ onKeyDown: (e) => {
3062
+ if (e.name === "escape") {
3063
+ setViewState("detail");
3064
+ setConfirmText("");
3065
+ setError(null);
3066
+ }
3067
+ }
3068
+ }
3069
+ ),
3070
+ error && /* @__PURE__ */ jsx3("text", { fg: t.status.error, marginTop: 1, children: error }),
3071
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, marginTop: 2, children: "Press Esc to cancel" })
3072
+ ] });
3073
+ }
3074
+ if (viewState === "deleting") {
3075
+ return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
3076
+ /* @__PURE__ */ jsx3("text", { fg: t.status.warning, children: "Deleting deployment..." }),
3077
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, marginTop: 1, children: "Removing local configuration files..." })
3078
+ ] });
3079
+ }
3080
+ if (viewState === "success") {
3081
+ return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
3082
+ /* @__PURE__ */ jsxs3(
3083
+ "box",
3084
+ {
3085
+ flexDirection: "column",
3086
+ borderStyle: "single",
3087
+ borderColor: t.status.success,
3088
+ padding: 1,
3089
+ children: [
3090
+ /* @__PURE__ */ jsx3("text", { fg: t.status.success, children: "Deployment Deleted" }),
3091
+ /* @__PURE__ */ jsxs3("text", { fg: t.fg.primary, marginTop: 1, children: [
3092
+ 'The deployment "',
3093
+ deletedName,
3094
+ '" has been permanently deleted.'
3095
+ ] })
3096
+ ]
3097
+ }
3098
+ ),
3099
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
3100
+ ] });
3101
+ }
3102
+ if (viewState === "error") {
3103
+ return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
3104
+ /* @__PURE__ */ jsxs3(
3105
+ "box",
3106
+ {
3107
+ flexDirection: "column",
3108
+ borderStyle: "single",
3109
+ borderColor: t.status.error,
3110
+ padding: 1,
3111
+ children: [
3112
+ /* @__PURE__ */ jsx3("text", { fg: t.status.error, children: "Deletion Failed" }),
3113
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, marginTop: 1, children: error })
3114
+ ]
3115
+ }
3116
+ ),
3117
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to go back" })
3118
+ ] });
3119
+ }
3120
+ return null;
3121
+ }
3122
+
3123
+ // src/components/DeployView.tsx
3124
+ import { useState as useState4 } from "react";
3125
+ import { useKeyboard as useKeyboard3 } from "@opentui/react";
3126
+ import { jsx as jsx4, jsxs as jsxs4 } from "@opentui/react/jsx-runtime";
3127
+ function DeployView({ context }) {
3128
+ const [selectedIndex, setSelectedIndex] = useState4(0);
3129
+ const [confirmMode, setConfirmMode] = useState4(false);
3130
+ const deployments = context.deployments;
3131
+ const notDeployed = deployments.filter((d) => d.state.status !== "deployed");
3132
+ const deployed = deployments.filter((d) => d.state.status === "deployed");
3133
+ const allDeployments = [...notDeployed, ...deployed];
3134
+ const selectedDeployment = allDeployments[selectedIndex];
3135
+ useKeyboard3((key) => {
3136
+ if (allDeployments.length === 0) {
3137
+ context.navigateTo("home");
3138
+ return;
3139
+ }
3140
+ if (confirmMode) {
3141
+ if (key.name === "y" || key.name === "return") {
3142
+ context.navigateTo("deploying", selectedDeployment.config.name);
3143
+ } else if (key.name === "n" || key.name === "escape") {
3144
+ setConfirmMode(false);
3145
+ }
3146
+ } else {
3147
+ if (key.name === "up" && selectedIndex > 0) {
3148
+ setSelectedIndex(selectedIndex - 1);
3149
+ } else if (key.name === "down" && selectedIndex < allDeployments.length - 1) {
3150
+ setSelectedIndex(selectedIndex + 1);
3151
+ } else if (key.name === "return") {
3152
+ setConfirmMode(true);
3153
+ } else if (key.name === "escape") {
3154
+ context.navigateTo("home");
3155
+ }
3156
+ }
3157
+ });
3158
+ if (allDeployments.length === 0) {
3159
+ return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", width: "100%", padding: 1, children: [
3160
+ /* @__PURE__ */ jsxs4("box", { flexDirection: "row", marginBottom: 2, children: [
3161
+ /* @__PURE__ */ jsx4("text", { fg: t.accent, children: "/deploy" }),
3162
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: " - Deploy a configuration" })
3163
+ ] }),
3164
+ /* @__PURE__ */ jsxs4(
3165
+ "box",
3166
+ {
3167
+ flexDirection: "column",
3168
+ borderStyle: "single",
3169
+ borderColor: t.border.default,
3170
+ padding: 1,
3171
+ children: [
3172
+ /* @__PURE__ */ jsx4("text", { fg: t.status.warning, children: "No deployments found!" }),
3173
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, marginTop: 1, children: "Run /new first to create a deployment configuration." })
3174
+ ]
3175
+ }
3176
+ ),
3177
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
3178
+ ] });
3179
+ }
3180
+ if (confirmMode) {
3181
+ return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", width: "100%", padding: 1, children: [
3182
+ /* @__PURE__ */ jsxs4("box", { flexDirection: "row", marginBottom: 2, children: [
3183
+ /* @__PURE__ */ jsx4("text", { fg: t.accent, children: "/deploy" }),
3184
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: " - Confirm deployment" })
3185
+ ] }),
3186
+ /* @__PURE__ */ jsxs4(
3187
+ "box",
3188
+ {
3189
+ flexDirection: "column",
3190
+ borderStyle: "single",
3191
+ borderColor: t.border.default,
3192
+ padding: 1,
3193
+ children: [
3194
+ /* @__PURE__ */ jsxs4("text", { fg: t.status.warning, children: [
3195
+ 'Deploy "',
3196
+ selectedDeployment.config.name,
3197
+ '"?'
3198
+ ] }),
3199
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, marginTop: 1, children: "This will:" }),
3200
+ /* @__PURE__ */ jsxs4("text", { fg: t.fg.primary, children: [
3201
+ "\u2022 Create a VPS on ",
3202
+ selectedDeployment.config.provider
3203
+ ] }),
3204
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "\u2022 Install and configure OpenClaw" }),
3205
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "\u2022 Set up Tailscale for secure access" }),
3206
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, marginTop: 1, children: "Estimated cost: ~$4.99/month (Hetzner CPX11)" })
3207
+ ]
3208
+ }
3209
+ ),
3210
+ /* @__PURE__ */ jsx4("text", { fg: t.status.warning, marginTop: 2, children: "Press Y to confirm, N to cancel" })
3211
+ ] });
3212
+ }
3213
+ return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", width: "100%", padding: 1, children: [
3214
+ /* @__PURE__ */ jsxs4("box", { flexDirection: "row", marginBottom: 2, children: [
3215
+ /* @__PURE__ */ jsx4("text", { fg: t.accent, children: "/deploy" }),
3216
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: " - Select a deployment to deploy" })
3217
+ ] }),
3218
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, marginBottom: 1, children: "Use arrow keys to select, Enter to deploy:" }),
3219
+ /* @__PURE__ */ jsx4(
3220
+ "box",
3221
+ {
3222
+ flexDirection: "column",
3223
+ borderStyle: "single",
3224
+ borderColor: t.border.default,
3225
+ padding: 1,
3226
+ children: allDeployments.map((deployment, index) => {
3227
+ const isSelected = index === selectedIndex;
3228
+ return /* @__PURE__ */ jsxs4(
3229
+ "box",
3230
+ {
3231
+ flexDirection: "row",
3232
+ backgroundColor: isSelected ? t.selection.bg : void 0,
3233
+ children: [
3234
+ /* @__PURE__ */ jsx4("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
3235
+ /* @__PURE__ */ jsx4("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
3236
+ /* @__PURE__ */ jsxs4("text", { fg: statusColor(deployment.state.status), width: 15, children: [
3237
+ "[",
3238
+ deployment.state.status,
3239
+ "]"
3240
+ ] }),
3241
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: deployment.config.provider })
3242
+ ]
3243
+ },
3244
+ deployment.config.name
3245
+ );
3246
+ })
3247
+ }
3248
+ ),
3249
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.muted, marginTop: 2, children: "Press Esc to go back" })
3250
+ ] });
3251
+ }
3252
+
3253
+ // src/components/DeployingView.tsx
3254
+ import { useState as useState5, useEffect as useEffect2, useCallback, useRef as useRef3 } from "react";
3255
+ import { useKeyboard as useKeyboard4 } from "@opentui/react";
3256
+ import open from "open";
3257
+
3258
+ // src/services/ssh.ts
3259
+ import { Client } from "ssh2";
3260
+ import { generateKeyPairSync, randomBytes } from "crypto";
3261
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, chmodSync, existsSync as existsSync4 } from "fs";
3262
+ import { mkdirSync as mkdirSync4 } from "fs";
3263
+ function generateSSHKeyPair(comment = "clawcontrol") {
3264
+ const { privateKey, publicKey } = generateKeyPairSync("ed25519", {
3265
+ publicKeyEncoding: {
3266
+ type: "spki",
3267
+ format: "pem"
3268
+ },
3269
+ privateKeyEncoding: {
3270
+ type: "pkcs8",
3271
+ format: "pem"
3272
+ }
3273
+ });
3274
+ const pubKeyDer = extractDERFromPEM(publicKey);
3275
+ const privKeyDer = extractDERFromPEM(privateKey);
3276
+ const publicKeyBytes = pubKeyDer.slice(-32);
3277
+ const privateKeyBytes = privKeyDer.slice(-32);
3278
+ const publicKeyOpenSSH = buildOpenSSHPublicKey(publicKeyBytes, comment);
3279
+ const privateKeyOpenSSH = buildOpenSSHPrivateKey(privateKeyBytes, publicKeyBytes, comment);
3280
+ return {
3281
+ privateKey: privateKeyOpenSSH,
3282
+ publicKey: publicKeyOpenSSH
3283
+ };
3284
+ }
3285
+ function extractDERFromPEM(pem) {
3286
+ const lines = pem.split("\n").filter((line) => !line.startsWith("-----") && line.trim() !== "");
3287
+ return Buffer.from(lines.join(""), "base64");
3288
+ }
3289
+ function buildOpenSSHPublicKey(publicKeyBytes, comment) {
3290
+ const keyType = Buffer.from("ssh-ed25519");
3291
+ const keyTypeLen = Buffer.alloc(4);
3292
+ keyTypeLen.writeUInt32BE(keyType.length);
3293
+ const keyLen = Buffer.alloc(4);
3294
+ keyLen.writeUInt32BE(publicKeyBytes.length);
3295
+ const opensshKey = Buffer.concat([keyTypeLen, keyType, keyLen, publicKeyBytes]);
3296
+ return `ssh-ed25519 ${opensshKey.toString("base64")} ${comment}`;
3297
+ }
3298
+ function buildOpenSSHPrivateKey(privateKeyBytes, publicKeyBytes, comment) {
3299
+ const AUTH_MAGIC = Buffer.from("openssh-key-v1\0");
3300
+ const cipherName = Buffer.from("none");
3301
+ const kdfName = Buffer.from("none");
3302
+ const kdfOptions = Buffer.alloc(0);
3303
+ const numKeys = 1;
3304
+ const keyType = Buffer.from("ssh-ed25519");
3305
+ const pubKeyBlob = Buffer.concat([
3306
+ uint32BE(keyType.length),
3307
+ keyType,
3308
+ uint32BE(publicKeyBytes.length),
3309
+ publicKeyBytes
3310
+ ]);
3311
+ const checkInt = randomBytes(4);
3312
+ const ed25519PrivKey = Buffer.concat([privateKeyBytes, publicKeyBytes]);
3313
+ const commentBuf = Buffer.from(comment);
3314
+ const privateSection = Buffer.concat([
3315
+ checkInt,
3316
+ checkInt,
3317
+ // Two identical check integers
3318
+ uint32BE(keyType.length),
3319
+ keyType,
3320
+ uint32BE(publicKeyBytes.length),
3321
+ publicKeyBytes,
3322
+ uint32BE(ed25519PrivKey.length),
3323
+ ed25519PrivKey,
2848
3324
  uint32BE(commentBuf.length),
2849
3325
  commentBuf
2850
3326
  ]);
@@ -3090,6 +3566,9 @@ async function waitForSSH(host, privateKey, timeoutMs = 18e4, pollIntervalMs = 5
3090
3566
  throw new Error(`SSH not available after ${timeoutMs / 1e3} seconds`);
3091
3567
  }
3092
3568
 
3569
+ // src/services/deployment.ts
3570
+ import { randomBytes as randomBytes2 } from "crypto";
3571
+
3093
3572
  // src/services/setup/index.ts
3094
3573
  async function execOrFail(ssh, command, errorMessage) {
3095
3574
  const result = await ssh.exec(command);
@@ -3241,7 +3720,7 @@ async function installOpenClaw(ssh) {
3241
3720
  throw new Error("OpenClaw installation verification failed");
3242
3721
  }
3243
3722
  }
3244
- async function configureOpenClaw(ssh, customConfig, agentConfig) {
3723
+ async function configureOpenClaw(ssh, customConfig, agentConfig, gatewayToken) {
3245
3724
  await ssh.exec("mkdir -p ~/.openclaw");
3246
3725
  const config = {
3247
3726
  browser: {
@@ -3263,6 +3742,7 @@ async function configureOpenClaw(ssh, customConfig, agentConfig) {
3263
3742
  port: 18789,
3264
3743
  mode: "local",
3265
3744
  bind: "loopback",
3745
+ ...gatewayToken ? { auth: { token: gatewayToken } } : {},
3266
3746
  tailscale: {
3267
3747
  mode: "serve",
3268
3748
  resetOnExit: false
@@ -3510,6 +3990,41 @@ async function getOpenClawLogs(ssh, lines = 100) {
3510
3990
  const result = await ssh.exec(`journalctl -u openclaw -n ${lines} --no-pager`);
3511
3991
  return result.stdout;
3512
3992
  }
3993
+ async function getDashboardUrl(ssh) {
3994
+ const nvmPrefix = "source ~/.nvm/nvm.sh &&";
3995
+ const dashResult = await ssh.exec(
3996
+ `${nvmPrefix} timeout 10 openclaw dashboard 2>&1 || true`
3997
+ );
3998
+ const rawOutput = dashResult.stdout + "\n" + dashResult.stderr;
3999
+ const output = rawOutput.replace(/\x1b\[[0-9;]*m/g, "");
4000
+ const urlMatch = output.match(/https?:\/\/[^\s\])'"<>]+/);
4001
+ if (urlMatch) {
4002
+ try {
4003
+ const parsed = new URL(urlMatch[0]);
4004
+ return { url: urlMatch[0], port: parseInt(parsed.port) || 18789 };
4005
+ } catch {
4006
+ }
4007
+ }
4008
+ const tokenResult = await ssh.exec(
4009
+ `${nvmPrefix} openclaw config get gateway.auth.token 2>/dev/null || true`
4010
+ );
4011
+ const token = tokenResult.stdout.trim().replace(/^["']|["']$/g, "");
4012
+ if (token) {
4013
+ return {
4014
+ url: `http://127.0.0.1:18789/?token=${encodeURIComponent(token)}`,
4015
+ port: 18789
4016
+ };
4017
+ }
4018
+ const statusResult = await ssh.exec(
4019
+ "systemctl is-active openclaw 2>/dev/null || true"
4020
+ );
4021
+ if (statusResult.stdout.trim() !== "active") {
4022
+ throw new Error("OpenClaw gateway is not running on this server");
4023
+ }
4024
+ throw new Error(
4025
+ "Could not retrieve dashboard URL. Try running 'openclaw dashboard' on the server manually."
4026
+ );
4027
+ }
3513
4028
 
3514
4029
  // src/services/deployment.ts
3515
4030
  var MAX_RETRIES = 3;
@@ -3608,6 +4123,7 @@ var DeploymentOrchestrator = class {
3608
4123
  onConfirm;
3609
4124
  onOpenUrl;
3610
4125
  onSpawnTerminal;
4126
+ tailscaleSkipped = false;
3611
4127
  constructor(deploymentName, onProgress, onConfirm, onOpenUrl, onSpawnTerminal) {
3612
4128
  this.deploymentName = deploymentName;
3613
4129
  this.deployment = readDeployment(deploymentName);
@@ -3991,18 +4507,28 @@ Would you like to retry from the beginning?`
3991
4507
  const ssh = await this.ensureSSHConnected();
3992
4508
  const customConfig = this.deployment.config.openclawConfig;
3993
4509
  const agentConfig = this.deployment.config.openclawAgent;
3994
- await configureOpenClaw(ssh, customConfig, agentConfig);
4510
+ const gatewayToken = randomBytes2(32).toString("hex");
4511
+ await configureOpenClaw(ssh, customConfig, agentConfig, gatewayToken);
4512
+ updateDeploymentState(this.deploymentName, { gatewayToken });
3995
4513
  if (agentConfig) {
3996
4514
  this.reportProgress("openclaw_configured", "Writing AI provider environment...");
3997
4515
  await writeOpenClawEnvFile(ssh, agentConfig);
3998
4516
  }
3999
4517
  }
4000
4518
  async installTailscale() {
4519
+ if (this.deployment.config.skipTailscale) {
4520
+ this.tailscaleSkipped = true;
4521
+ this.reportProgress("tailscale_installed", "Tailscale setup skipped.");
4522
+ return;
4523
+ }
4001
4524
  const ssh = await this.ensureSSHConnected();
4002
4525
  await installTailscale(ssh);
4003
4526
  }
4004
4527
  async authenticateTailscale() {
4528
+ if (this.tailscaleSkipped) return;
4005
4529
  const ssh = await this.ensureSSHConnected();
4530
+ const check = await ssh.exec("which tailscale 2>/dev/null");
4531
+ if (check.code !== 0) return;
4006
4532
  const authUrl = await getTailscaleAuthUrl(ssh);
4007
4533
  if (authUrl) {
4008
4534
  const confirmed = await this.onConfirm(
@@ -4025,7 +4551,10 @@ URL: ${authUrl}`
4025
4551
  }
4026
4552
  }
4027
4553
  async configureTailscale() {
4554
+ if (this.tailscaleSkipped) return;
4028
4555
  const ssh = await this.ensureSSHConnected();
4556
+ const check = await ssh.exec("which tailscale 2>/dev/null");
4557
+ if (check.code !== 0) return;
4029
4558
  const tailscaleIp = await configureTailscaleServe(ssh);
4030
4559
  updateDeploymentState(this.deploymentName, {
4031
4560
  tailscaleIp
@@ -4510,14 +5039,14 @@ function getTerminalDisplayName(app) {
4510
5039
  }
4511
5040
 
4512
5041
  // src/components/DeployingView.tsx
4513
- import { jsx as jsx4, jsxs as jsxs4 } from "@opentui/react/jsx-runtime";
5042
+ import { jsx as jsx5, jsxs as jsxs5 } from "@opentui/react/jsx-runtime";
4514
5043
  function DeployingView({ context }) {
4515
- const [deployState, setDeployState] = useState4("deploying");
4516
- const [progress, setProgress] = useState4(null);
4517
- const [logs, setLogs] = useState4([]);
4518
- const [error, setError] = useState4(null);
4519
- const [confirmPrompt, setConfirmPrompt] = useState4(null);
4520
- const [terminalResolve, setTerminalResolve] = useState4(null);
5044
+ const [deployState, setDeployState] = useState5("deploying");
5045
+ const [progress, setProgress] = useState5(null);
5046
+ const [logs, setLogs] = useState5([]);
5047
+ const [error, setError] = useState5(null);
5048
+ const [confirmPrompt, setConfirmPrompt] = useState5(null);
5049
+ const [terminalResolve, setTerminalResolve] = useState5(null);
4521
5050
  const deploymentName = context.selectedDeployment;
4522
5051
  const addLog = useCallback((message) => {
4523
5052
  setLogs((prev) => [...prev.slice(-20), `[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] ${message}`]);
@@ -4565,9 +5094,9 @@ function DeployingView({ context }) {
4565
5094
  setDeployState("deploying");
4566
5095
  addLog("Terminal session confirmed complete, continuing deployment...");
4567
5096
  }, [terminalResolve, addLog]);
4568
- const stateRef = useRef2({ deployState, terminalResolve });
5097
+ const stateRef = useRef3({ deployState, terminalResolve });
4569
5098
  stateRef.current = { deployState, terminalResolve };
4570
- useKeyboard3((key) => {
5099
+ useKeyboard4((key) => {
4571
5100
  const currentState = stateRef.current;
4572
5101
  if (currentState.deployState === "waiting_terminal") {
4573
5102
  if (key.name === "return") {
@@ -4621,19 +5150,19 @@ function DeployingView({ context }) {
4621
5150
  const width = 40;
4622
5151
  const filled = Math.round(progress.progress / 100 * width);
4623
5152
  const empty = width - filled;
4624
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", marginBottom: 1, children: [
4625
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", children: [
4626
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: "[" }),
4627
- /* @__PURE__ */ jsx4("text", { fg: t.status.success, children: "\u2588".repeat(filled) }),
4628
- /* @__PURE__ */ jsx4("text", { fg: t.fg.muted, children: "\u2591".repeat(empty) }),
4629
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: "]" }),
4630
- /* @__PURE__ */ jsxs4("text", { fg: t.fg.primary, children: [
5153
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", marginBottom: 1, children: [
5154
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
5155
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: "[" }),
5156
+ /* @__PURE__ */ jsx5("text", { fg: t.status.success, children: "\u2588".repeat(filled) }),
5157
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.muted, children: "\u2591".repeat(empty) }),
5158
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: "]" }),
5159
+ /* @__PURE__ */ jsxs5("text", { fg: t.fg.primary, children: [
4631
5160
  " ",
4632
5161
  Math.round(progress.progress),
4633
5162
  "%"
4634
5163
  ] })
4635
5164
  ] }),
4636
- /* @__PURE__ */ jsxs4("text", { fg: t.accent, marginTop: 1, children: [
5165
+ /* @__PURE__ */ jsxs5("text", { fg: t.accent, marginTop: 1, children: [
4637
5166
  "Current: ",
4638
5167
  progress.message
4639
5168
  ] })
@@ -4642,7 +5171,7 @@ function DeployingView({ context }) {
4642
5171
  const renderConfirmDialog = () => {
4643
5172
  if (!confirmPrompt) return null;
4644
5173
  const lines = confirmPrompt.message.split("\n");
4645
- return /* @__PURE__ */ jsxs4(
5174
+ return /* @__PURE__ */ jsxs5(
4646
5175
  "box",
4647
5176
  {
4648
5177
  flexDirection: "column",
@@ -4651,16 +5180,16 @@ function DeployingView({ context }) {
4651
5180
  padding: 1,
4652
5181
  marginBottom: 1,
4653
5182
  children: [
4654
- /* @__PURE__ */ jsx4("text", { fg: t.status.warning, children: "Confirmation Required" }),
4655
- /* @__PURE__ */ jsx4("box", { flexDirection: "column", marginTop: 1, children: lines.map((line, i) => /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: line }, i)) }),
4656
- /* @__PURE__ */ jsx4("text", { fg: t.status.warning, marginTop: 1, children: "Press Y for Yes, N for No" })
5183
+ /* @__PURE__ */ jsx5("text", { fg: t.status.warning, children: "Confirmation Required" }),
5184
+ /* @__PURE__ */ jsx5("box", { flexDirection: "column", marginTop: 1, children: lines.map((line, i) => /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: line }, i)) }),
5185
+ /* @__PURE__ */ jsx5("text", { fg: t.status.warning, marginTop: 1, children: "Press Y for Yes, N for No" })
4657
5186
  ]
4658
5187
  }
4659
5188
  );
4660
5189
  };
4661
5190
  const renderWaitingTerminal = () => {
4662
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", flexGrow: 1, children: [
4663
- /* @__PURE__ */ jsxs4(
5191
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", flexGrow: 1, children: [
5192
+ /* @__PURE__ */ jsxs5(
4664
5193
  "box",
4665
5194
  {
4666
5195
  flexDirection: "column",
@@ -4669,12 +5198,12 @@ function DeployingView({ context }) {
4669
5198
  padding: 1,
4670
5199
  marginBottom: 1,
4671
5200
  children: [
4672
- /* @__PURE__ */ jsx4("text", { fg: t.accent, children: "Interactive Setup" }),
4673
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, marginTop: 1, children: "A terminal window has been opened." })
5201
+ /* @__PURE__ */ jsx5("text", { fg: t.accent, children: "Interactive Setup" }),
5202
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, marginTop: 1, children: "A terminal window has been opened." })
4674
5203
  ]
4675
5204
  }
4676
5205
  ),
4677
- /* @__PURE__ */ jsxs4(
5206
+ /* @__PURE__ */ jsxs5(
4678
5207
  "box",
4679
5208
  {
4680
5209
  flexDirection: "column",
@@ -4683,32 +5212,32 @@ function DeployingView({ context }) {
4683
5212
  padding: 1,
4684
5213
  marginBottom: 1,
4685
5214
  children: [
4686
- /* @__PURE__ */ jsx4("text", { fg: t.status.warning, children: "Instructions:" }),
4687
- /* @__PURE__ */ jsxs4("box", { flexDirection: "column", marginTop: 1, children: [
4688
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "1. Complete the setup in the terminal window" }),
4689
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "2. Follow the prompts shown in the terminal" }),
4690
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "3. When done, close the terminal window" }),
4691
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "4. Press Enter here to continue" })
5215
+ /* @__PURE__ */ jsx5("text", { fg: t.status.warning, children: "Instructions:" }),
5216
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "column", marginTop: 1, children: [
5217
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: "1. Complete the setup in the terminal window" }),
5218
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: "2. Follow the prompts shown in the terminal" }),
5219
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: "3. When done, close the terminal window" }),
5220
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: "4. Press Enter here to continue" })
4692
5221
  ] })
4693
5222
  ]
4694
5223
  }
4695
5224
  ),
4696
- /* @__PURE__ */ jsx4(
5225
+ /* @__PURE__ */ jsx5(
4697
5226
  "box",
4698
5227
  {
4699
5228
  flexDirection: "column",
4700
5229
  borderStyle: "single",
4701
5230
  borderColor: t.status.success,
4702
5231
  padding: 1,
4703
- children: /* @__PURE__ */ jsx4("text", { fg: t.status.success, children: "Press Enter when you have completed the setup in the terminal" })
5232
+ children: /* @__PURE__ */ jsx5("text", { fg: t.status.success, children: "Press Enter when you have completed the setup in the terminal" })
4704
5233
  }
4705
5234
  )
4706
5235
  ] });
4707
5236
  };
4708
5237
  const renderSuccess = () => {
4709
5238
  const state = readDeploymentState(deploymentName);
4710
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", children: [
4711
- /* @__PURE__ */ jsxs4(
5239
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
5240
+ /* @__PURE__ */ jsxs5(
4712
5241
  "box",
4713
5242
  {
4714
5243
  flexDirection: "column",
@@ -4717,12 +5246,12 @@ function DeployingView({ context }) {
4717
5246
  padding: 1,
4718
5247
  marginBottom: 1,
4719
5248
  children: [
4720
- /* @__PURE__ */ jsx4("text", { fg: t.status.success, children: "Deployment Successful!" }),
4721
- /* @__PURE__ */ jsx4("text", { fg: t.status.success, marginTop: 1, children: "Your OpenClaw instance is now running." })
5249
+ /* @__PURE__ */ jsx5("text", { fg: t.status.success, children: "Deployment Successful!" }),
5250
+ /* @__PURE__ */ jsx5("text", { fg: t.status.success, marginTop: 1, children: "Your OpenClaw instance is now running." })
4722
5251
  ]
4723
5252
  }
4724
5253
  ),
4725
- /* @__PURE__ */ jsxs4(
5254
+ /* @__PURE__ */ jsxs5(
4726
5255
  "box",
4727
5256
  {
4728
5257
  flexDirection: "column",
@@ -4731,36 +5260,36 @@ function DeployingView({ context }) {
4731
5260
  padding: 1,
4732
5261
  marginBottom: 1,
4733
5262
  children: [
4734
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "Connection Details" }),
4735
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", marginTop: 1, children: [
4736
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, width: 15, children: "Server IP:" }),
4737
- /* @__PURE__ */ jsx4("text", { fg: t.accent, children: state.serverIp || "N/A" })
5263
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: "Connection Details" }),
5264
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", marginTop: 1, children: [
5265
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 15, children: "Server IP:" }),
5266
+ /* @__PURE__ */ jsx5("text", { fg: t.accent, children: state.serverIp || "N/A" })
4738
5267
  ] }),
4739
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", children: [
4740
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, width: 15, children: "Tailscale IP:" }),
4741
- /* @__PURE__ */ jsx4("text", { fg: t.accent, children: state.tailscaleIp || "N/A" })
5268
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
5269
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 15, children: "Tailscale IP:" }),
5270
+ /* @__PURE__ */ jsx5("text", { fg: t.accent, children: state.tailscaleIp || "N/A" })
4742
5271
  ] }),
4743
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", children: [
4744
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, width: 15, children: "Gateway Port:" }),
4745
- /* @__PURE__ */ jsx4("text", { fg: t.accent, children: "18789" })
5272
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
5273
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 15, children: "Gateway Port:" }),
5274
+ /* @__PURE__ */ jsx5("text", { fg: t.accent, children: "18789" })
4746
5275
  ] })
4747
5276
  ]
4748
5277
  }
4749
5278
  ),
4750
- /* @__PURE__ */ jsx4("text", { fg: t.status.success, children: "Next steps:" }),
4751
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: " /ssh - Connect to your server" }),
4752
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: " /logs - View OpenClaw logs" }),
4753
- /* @__PURE__ */ jsxs4("text", { fg: t.fg.primary, children: [
5279
+ /* @__PURE__ */ jsx5("text", { fg: t.status.success, children: "Next steps:" }),
5280
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: " /ssh - Connect to your server" }),
5281
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: " /logs - View OpenClaw logs" }),
5282
+ /* @__PURE__ */ jsxs5("text", { fg: t.fg.primary, children: [
4754
5283
  " Gateway: http://",
4755
5284
  state.tailscaleIp || state.serverIp,
4756
5285
  ":18789/"
4757
5286
  ] }),
4758
- /* @__PURE__ */ jsx4("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5287
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
4759
5288
  ] });
4760
5289
  };
4761
5290
  const renderFailed = () => {
4762
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", children: [
4763
- /* @__PURE__ */ jsxs4(
5291
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
5292
+ /* @__PURE__ */ jsxs5(
4764
5293
  "box",
4765
5294
  {
4766
5295
  flexDirection: "column",
@@ -4769,26 +5298,26 @@ function DeployingView({ context }) {
4769
5298
  padding: 1,
4770
5299
  marginBottom: 1,
4771
5300
  children: [
4772
- /* @__PURE__ */ jsx4("text", { fg: t.status.error, children: "Deployment Failed" }),
4773
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, marginTop: 1, children: "Something went wrong during deployment." }),
4774
- /* @__PURE__ */ jsxs4("text", { fg: t.status.error, marginTop: 1, children: [
5301
+ /* @__PURE__ */ jsx5("text", { fg: t.status.error, children: "Deployment Failed" }),
5302
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, marginTop: 1, children: "Something went wrong during deployment." }),
5303
+ /* @__PURE__ */ jsxs5("text", { fg: t.status.error, marginTop: 1, children: [
4775
5304
  "Error: ",
4776
5305
  error
4777
5306
  ] })
4778
5307
  ]
4779
5308
  }
4780
5309
  ),
4781
- /* @__PURE__ */ jsxs4("box", { flexDirection: "column", marginBottom: 1, children: [
4782
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "What you can do:" }),
4783
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: " 1. Run /deploy again - it will resume from the last successful step" }),
4784
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: " 2. Run /status to check the current state of your deployment" }),
4785
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: " 3. Run /destroy and /new to start fresh if the issue persists" })
5310
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "column", marginBottom: 1, children: [
5311
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: "What you can do:" }),
5312
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: " 1. Run /deploy again - it will resume from the last successful step" }),
5313
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: " 2. Run /status to check the current state of your deployment" }),
5314
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: " 3. Run /destroy and /new to start fresh if the issue persists" })
4786
5315
  ] }),
4787
- /* @__PURE__ */ jsx4("text", { fg: t.fg.muted, marginTop: 1, children: "Press any key to return to home" })
5316
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.muted, marginTop: 1, children: "Press any key to return to home" })
4788
5317
  ] });
4789
5318
  };
4790
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", width: "100%", padding: 1, children: [
4791
- /* @__PURE__ */ jsx4("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsxs4("text", { fg: t.accent, children: [
5319
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5320
+ /* @__PURE__ */ jsx5("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsxs5("text", { fg: t.accent, children: [
4792
5321
  "Deploying: ",
4793
5322
  deploymentName
4794
5323
  ] }) }),
@@ -4797,7 +5326,7 @@ function DeployingView({ context }) {
4797
5326
  deployState === "waiting_terminal" && renderWaitingTerminal(),
4798
5327
  deployState === "success" && renderSuccess(),
4799
5328
  deployState === "failed" && renderFailed(),
4800
- /* @__PURE__ */ jsxs4(
5329
+ /* @__PURE__ */ jsxs5(
4801
5330
  "box",
4802
5331
  {
4803
5332
  flexDirection: "column",
@@ -4805,8 +5334,8 @@ function DeployingView({ context }) {
4805
5334
  borderColor: t.border.default,
4806
5335
  padding: 1,
4807
5336
  children: [
4808
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: "Deployment Log" }),
4809
- /* @__PURE__ */ jsx4("box", { flexDirection: "column", marginTop: 1, children: logs.map((log, i) => /* @__PURE__ */ jsx4("text", { fg: t.fg.muted, children: log }, i)) })
5337
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: "Deployment Log" }),
5338
+ /* @__PURE__ */ jsx5("box", { flexDirection: "column", marginTop: 1, children: logs.map((log, i) => /* @__PURE__ */ jsx5("text", { fg: t.fg.muted, children: log }, i)) })
4810
5339
  ]
4811
5340
  }
4812
5341
  )
@@ -4814,13 +5343,13 @@ function DeployingView({ context }) {
4814
5343
  }
4815
5344
 
4816
5345
  // src/components/StatusView.tsx
4817
- import { useState as useState5 } from "react";
4818
- import { useKeyboard as useKeyboard4 } from "@opentui/react";
4819
- import { jsx as jsx5, jsxs as jsxs5 } from "@opentui/react/jsx-runtime";
5346
+ import { useState as useState6 } from "react";
5347
+ import { useKeyboard as useKeyboard5 } from "@opentui/react";
5348
+ import { jsx as jsx6, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
4820
5349
  function StatusView({ context }) {
4821
- const [selectedIndex, setSelectedIndex] = useState5(0);
4822
- const [healthStatus, setHealthStatus] = useState5(/* @__PURE__ */ new Map());
4823
- const [checking, setChecking] = useState5(null);
5350
+ const [selectedIndex, setSelectedIndex] = useState6(0);
5351
+ const [healthStatus, setHealthStatus] = useState6(/* @__PURE__ */ new Map());
5352
+ const [checking, setChecking] = useState6(null);
4824
5353
  const deployments = context.deployments;
4825
5354
  const checkHealth = async (deployment) => {
4826
5355
  const name = deployment.config.name;
@@ -4845,7 +5374,7 @@ function StatusView({ context }) {
4845
5374
  setHealthStatus((prev) => new Map(prev).set(name, health));
4846
5375
  setChecking(null);
4847
5376
  };
4848
- useKeyboard4((key) => {
5377
+ useKeyboard5((key) => {
4849
5378
  if (deployments.length === 0) {
4850
5379
  context.navigateTo("home");
4851
5380
  return;
@@ -4861,12 +5390,12 @@ function StatusView({ context }) {
4861
5390
  }
4862
5391
  });
4863
5392
  if (deployments.length === 0) {
4864
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", width: "100%", padding: 1, children: [
4865
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", marginBottom: 2, children: [
4866
- /* @__PURE__ */ jsx5("text", { fg: t.accent, children: "/status" }),
4867
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: " - Deployment Status" })
5393
+ return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5394
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 2, children: [
5395
+ /* @__PURE__ */ jsx6("text", { fg: t.accent, children: "/status" }),
5396
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, children: " - Deployment Status" })
4868
5397
  ] }),
4869
- /* @__PURE__ */ jsxs5(
5398
+ /* @__PURE__ */ jsxs6(
4870
5399
  "box",
4871
5400
  {
4872
5401
  flexDirection: "column",
@@ -4874,22 +5403,22 @@ function StatusView({ context }) {
4874
5403
  borderColor: t.border.default,
4875
5404
  padding: 1,
4876
5405
  children: [
4877
- /* @__PURE__ */ jsx5("text", { fg: t.status.warning, children: "No deployments found!" }),
4878
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, marginTop: 1, children: "Run /new to create a deployment." })
5406
+ /* @__PURE__ */ jsx6("text", { fg: t.status.warning, children: "No deployments found!" }),
5407
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, marginTop: 1, children: "Run /new to create a deployment." })
4879
5408
  ]
4880
5409
  }
4881
5410
  ),
4882
- /* @__PURE__ */ jsx5("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5411
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
4883
5412
  ] });
4884
5413
  }
4885
5414
  const selectedDeployment = deployments[selectedIndex];
4886
5415
  const selectedHealth = healthStatus.get(selectedDeployment.config.name);
4887
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", width: "100%", padding: 1, children: [
4888
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", marginBottom: 2, children: [
4889
- /* @__PURE__ */ jsx5("text", { fg: t.accent, children: "/status" }),
4890
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: " - Deployment Status" })
5416
+ return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5417
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 2, children: [
5418
+ /* @__PURE__ */ jsx6("text", { fg: t.accent, children: "/status" }),
5419
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, children: " - Deployment Status" })
4891
5420
  ] }),
4892
- /* @__PURE__ */ jsxs5(
5421
+ /* @__PURE__ */ jsxs6(
4893
5422
  "box",
4894
5423
  {
4895
5424
  flexDirection: "column",
@@ -4898,22 +5427,22 @@ function StatusView({ context }) {
4898
5427
  padding: 1,
4899
5428
  marginBottom: 1,
4900
5429
  children: [
4901
- /* @__PURE__ */ jsxs5("text", { fg: t.fg.primary, marginBottom: 1, children: [
5430
+ /* @__PURE__ */ jsxs6("text", { fg: t.fg.primary, marginBottom: 1, children: [
4902
5431
  "Deployments (",
4903
5432
  deployments.length,
4904
5433
  ")"
4905
5434
  ] }),
4906
5435
  deployments.map((deployment, index) => {
4907
5436
  const isSelected = index === selectedIndex;
4908
- return /* @__PURE__ */ jsxs5(
5437
+ return /* @__PURE__ */ jsxs6(
4909
5438
  "box",
4910
5439
  {
4911
5440
  flexDirection: "row",
4912
5441
  backgroundColor: isSelected ? t.selection.bg : void 0,
4913
5442
  children: [
4914
- /* @__PURE__ */ jsx5("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
4915
- /* @__PURE__ */ jsx5("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
4916
- /* @__PURE__ */ jsxs5("text", { fg: statusColor(deployment.state.status), children: [
5443
+ /* @__PURE__ */ jsx6("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
5444
+ /* @__PURE__ */ jsx6("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
5445
+ /* @__PURE__ */ jsxs6("text", { fg: statusColor(deployment.state.status), children: [
4917
5446
  "[",
4918
5447
  deployment.state.status,
4919
5448
  "]"
@@ -4926,7 +5455,7 @@ function StatusView({ context }) {
4926
5455
  ]
4927
5456
  }
4928
5457
  ),
4929
- /* @__PURE__ */ jsxs5(
5458
+ /* @__PURE__ */ jsxs6(
4930
5459
  "box",
4931
5460
  {
4932
5461
  flexDirection: "column",
@@ -4935,78 +5464,78 @@ function StatusView({ context }) {
4935
5464
  padding: 1,
4936
5465
  marginBottom: 1,
4937
5466
  children: [
4938
- /* @__PURE__ */ jsxs5("text", { fg: t.accent, children: [
5467
+ /* @__PURE__ */ jsxs6("text", { fg: t.accent, children: [
4939
5468
  "Details: ",
4940
5469
  selectedDeployment.config.name
4941
5470
  ] }),
4942
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", marginTop: 1, children: [
4943
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Status:" }),
4944
- /* @__PURE__ */ jsx5("text", { fg: statusColor(selectedDeployment.state.status), children: selectedDeployment.state.status })
5471
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginTop: 1, children: [
5472
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "Status:" }),
5473
+ /* @__PURE__ */ jsx6("text", { fg: statusColor(selectedDeployment.state.status), children: selectedDeployment.state.status })
4945
5474
  ] }),
4946
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4947
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Provider:" }),
4948
- /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: selectedDeployment.config.provider })
5475
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", children: [
5476
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "Provider:" }),
5477
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.primary, children: selectedDeployment.config.provider })
4949
5478
  ] }),
4950
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4951
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Server IP:" }),
4952
- /* @__PURE__ */ jsx5("text", { fg: t.accent, children: selectedDeployment.state.serverIp || "Not deployed" })
5479
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", children: [
5480
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "Server IP:" }),
5481
+ /* @__PURE__ */ jsx6("text", { fg: t.accent, children: selectedDeployment.state.serverIp || "Not deployed" })
4953
5482
  ] }),
4954
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4955
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Tailscale IP:" }),
4956
- /* @__PURE__ */ jsx5("text", { fg: t.accent, children: selectedDeployment.state.tailscaleIp || "Not configured" })
5483
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", children: [
5484
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "Tailscale IP:" }),
5485
+ /* @__PURE__ */ jsx6("text", { fg: t.accent, children: selectedDeployment.state.tailscaleIp || "Not configured" })
4957
5486
  ] }),
4958
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4959
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Created:" }),
4960
- /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: new Date(selectedDeployment.config.createdAt).toLocaleString() })
5487
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", children: [
5488
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "Created:" }),
5489
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.primary, children: new Date(selectedDeployment.config.createdAt).toLocaleString() })
4961
5490
  ] }),
4962
- selectedDeployment.state.deployedAt && /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4963
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Deployed:" }),
4964
- /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: new Date(selectedDeployment.state.deployedAt).toLocaleString() })
5491
+ selectedDeployment.state.deployedAt && /* @__PURE__ */ jsxs6("box", { flexDirection: "row", children: [
5492
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "Deployed:" }),
5493
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.primary, children: new Date(selectedDeployment.state.deployedAt).toLocaleString() })
4965
5494
  ] }),
4966
- selectedDeployment.state.lastError && /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4967
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Last Error:" }),
4968
- /* @__PURE__ */ jsx5("text", { fg: t.status.error, children: selectedDeployment.state.lastError })
5495
+ selectedDeployment.state.lastError && /* @__PURE__ */ jsxs6("box", { flexDirection: "row", children: [
5496
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "Last Error:" }),
5497
+ /* @__PURE__ */ jsx6("text", { fg: t.status.error, children: selectedDeployment.state.lastError })
4969
5498
  ] }),
4970
- selectedHealth && /* @__PURE__ */ jsxs5("box", { flexDirection: "column", marginTop: 1, children: [
4971
- /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: "Health Check:" }),
4972
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4973
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "SSH:" }),
4974
- /* @__PURE__ */ jsx5("text", { fg: selectedHealth.sshConnectable ? t.status.success : t.status.error, children: selectedHealth.sshConnectable ? "Connected" : "Unreachable" })
5499
+ selectedHealth && /* @__PURE__ */ jsxs6("box", { flexDirection: "column", marginTop: 1, children: [
5500
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.primary, children: "Health Check:" }),
5501
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", children: [
5502
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "SSH:" }),
5503
+ /* @__PURE__ */ jsx6("text", { fg: selectedHealth.sshConnectable ? t.status.success : t.status.error, children: selectedHealth.sshConnectable ? "Connected" : "Unreachable" })
4975
5504
  ] }),
4976
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4977
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "OpenClaw:" }),
4978
- /* @__PURE__ */ jsx5("text", { fg: selectedHealth.openclawRunning ? t.status.success : t.status.error, children: selectedHealth.openclawRunning ? "Running" : "Not running" })
5505
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", children: [
5506
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "OpenClaw:" }),
5507
+ /* @__PURE__ */ jsx6("text", { fg: selectedHealth.openclawRunning ? t.status.success : t.status.error, children: selectedHealth.openclawRunning ? "Running" : "Not running" })
4979
5508
  ] }),
4980
- /* @__PURE__ */ jsxs5("text", { fg: t.fg.muted, children: [
5509
+ /* @__PURE__ */ jsxs6("text", { fg: t.fg.muted, children: [
4981
5510
  "Last checked: ",
4982
5511
  selectedHealth.lastChecked.toLocaleTimeString()
4983
5512
  ] })
4984
5513
  ] }),
4985
- checking === selectedDeployment.config.name && /* @__PURE__ */ jsx5("text", { fg: t.status.warning, marginTop: 1, children: "Checking health..." })
5514
+ checking === selectedDeployment.config.name && /* @__PURE__ */ jsx6("text", { fg: t.status.warning, marginTop: 1, children: "Checking health..." })
4986
5515
  ]
4987
5516
  }
4988
5517
  ),
4989
- selectedDeployment.state.checkpoints.length > 0 && /* @__PURE__ */ jsxs5("box", { flexDirection: "row", marginBottom: 1, children: [
4990
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: "Checkpoints: " }),
4991
- /* @__PURE__ */ jsxs5("text", { fg: t.status.success, children: [
5518
+ selectedDeployment.state.checkpoints.length > 0 && /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 1, children: [
5519
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, children: "Checkpoints: " }),
5520
+ /* @__PURE__ */ jsxs6("text", { fg: t.status.success, children: [
4992
5521
  selectedDeployment.state.checkpoints.length,
4993
5522
  " completed"
4994
5523
  ] })
4995
5524
  ] }),
4996
- /* @__PURE__ */ jsx5("text", { fg: t.fg.muted, children: "Up/Down: Select | Enter: Health check | Esc: Back" })
5525
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.muted, children: "Up/Down: Select | Enter: Health check | Esc: Back" })
4997
5526
  ] });
4998
5527
  }
4999
5528
 
5000
5529
  // src/components/SSHView.tsx
5001
- import { useState as useState6, useCallback as useCallback2 } from "react";
5002
- import { useKeyboard as useKeyboard5 } from "@opentui/react";
5003
- import { jsx as jsx6, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
5530
+ import { useState as useState7, useCallback as useCallback2 } from "react";
5531
+ import { useKeyboard as useKeyboard6 } from "@opentui/react";
5532
+ import { jsx as jsx7, jsxs as jsxs7 } from "@opentui/react/jsx-runtime";
5004
5533
  function SSHView({ context }) {
5005
- const [viewState, setViewState] = useState6("selecting");
5006
- const [selectedIndex, setSelectedIndex] = useState6(0);
5007
- const [error, setError] = useState6(null);
5008
- const [connectedDeployment, setConnectedDeployment] = useState6(null);
5009
- const [terminalName, setTerminalName] = useState6("");
5534
+ const [viewState, setViewState] = useState7("selecting");
5535
+ const [selectedIndex, setSelectedIndex] = useState7(0);
5536
+ const [error, setError] = useState7(null);
5537
+ const [connectedDeployment, setConnectedDeployment] = useState7(null);
5538
+ const [terminalName, setTerminalName] = useState7("");
5010
5539
  const deployedDeployments = context.deployments.filter(
5011
5540
  (d) => d.state.status === "deployed" && d.state.serverIp
5012
5541
  );
@@ -5025,7 +5554,7 @@ function SSHView({ context }) {
5025
5554
  }
5026
5555
  }, []);
5027
5556
  const selectedDeployment = deployedDeployments[selectedIndex];
5028
- useKeyboard5((key) => {
5557
+ useKeyboard6((key) => {
5029
5558
  if (deployedDeployments.length === 0) {
5030
5559
  context.navigateTo("home");
5031
5560
  return;
@@ -5049,12 +5578,12 @@ function SSHView({ context }) {
5049
5578
  }
5050
5579
  });
5051
5580
  if (deployedDeployments.length === 0) {
5052
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5053
- /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 2, children: [
5054
- /* @__PURE__ */ jsx6("text", { fg: t.accent, children: "/ssh" }),
5055
- /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, children: " - SSH into deployment" })
5581
+ return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5582
+ /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 2, children: [
5583
+ /* @__PURE__ */ jsx7("text", { fg: t.accent, children: "/ssh" }),
5584
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, children: " - SSH into deployment" })
5056
5585
  ] }),
5057
- /* @__PURE__ */ jsxs6(
5586
+ /* @__PURE__ */ jsxs7(
5058
5587
  "box",
5059
5588
  {
5060
5589
  flexDirection: "column",
@@ -5062,23 +5591,23 @@ function SSHView({ context }) {
5062
5591
  borderColor: t.border.default,
5063
5592
  padding: 1,
5064
5593
  children: [
5065
- /* @__PURE__ */ jsx6("text", { fg: t.status.warning, children: "No deployed instances found!" }),
5066
- /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, marginTop: 1, children: "Deploy an instance first with /deploy" })
5594
+ /* @__PURE__ */ jsx7("text", { fg: t.status.warning, children: "No deployed instances found!" }),
5595
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, marginTop: 1, children: "Deploy an instance first with /deploy" })
5067
5596
  ]
5068
5597
  }
5069
5598
  ),
5070
- /* @__PURE__ */ jsx6("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5599
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5071
5600
  ] });
5072
5601
  }
5073
5602
  if (viewState === "selecting") {
5074
5603
  const terminal = detectTerminal();
5075
5604
  const terminalDisplayName = getTerminalDisplayName(terminal.app);
5076
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5077
- /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 2, children: [
5078
- /* @__PURE__ */ jsx6("text", { fg: t.accent, children: "/ssh" }),
5079
- /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, children: " - Select a deployment to connect" })
5605
+ return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5606
+ /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 2, children: [
5607
+ /* @__PURE__ */ jsx7("text", { fg: t.accent, children: "/ssh" }),
5608
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, children: " - Select a deployment to connect" })
5080
5609
  ] }),
5081
- /* @__PURE__ */ jsx6(
5610
+ /* @__PURE__ */ jsx7(
5082
5611
  "box",
5083
5612
  {
5084
5613
  flexDirection: "column",
@@ -5088,15 +5617,15 @@ function SSHView({ context }) {
5088
5617
  marginBottom: 1,
5089
5618
  children: deployedDeployments.map((deployment, index) => {
5090
5619
  const isSelected = index === selectedIndex;
5091
- return /* @__PURE__ */ jsxs6(
5620
+ return /* @__PURE__ */ jsxs7(
5092
5621
  "box",
5093
5622
  {
5094
5623
  flexDirection: "row",
5095
5624
  backgroundColor: isSelected ? t.selection.bg : void 0,
5096
5625
  children: [
5097
- /* @__PURE__ */ jsx6("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
5098
- /* @__PURE__ */ jsx6("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
5099
- /* @__PURE__ */ jsx6("text", { fg: t.accent, children: deployment.state.serverIp })
5626
+ /* @__PURE__ */ jsx7("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
5627
+ /* @__PURE__ */ jsx7("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
5628
+ /* @__PURE__ */ jsx7("text", { fg: t.accent, children: deployment.state.serverIp })
5100
5629
  ]
5101
5630
  },
5102
5631
  deployment.config.name
@@ -5104,24 +5633,24 @@ function SSHView({ context }) {
5104
5633
  })
5105
5634
  }
5106
5635
  ),
5107
- /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 1, children: [
5108
- /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, children: "Terminal: " }),
5109
- /* @__PURE__ */ jsx6("text", { fg: t.status.success, children: terminalDisplayName }),
5110
- /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, children: " (will open in a new window)" })
5636
+ /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 1, children: [
5637
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, children: "Terminal: " }),
5638
+ /* @__PURE__ */ jsx7("text", { fg: t.status.success, children: terminalDisplayName }),
5639
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, children: " (will open in a new window)" })
5111
5640
  ] }),
5112
- /* @__PURE__ */ jsx6("text", { fg: t.fg.muted, children: "Arrow keys to select | Enter to connect | Esc to go back" })
5641
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, children: "Arrow keys to select | Enter to connect | Esc to go back" })
5113
5642
  ] });
5114
5643
  }
5115
5644
  if (viewState === "connected") {
5116
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5117
- /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 2, children: [
5118
- /* @__PURE__ */ jsx6("text", { fg: t.status.success, children: "/ssh" }),
5119
- /* @__PURE__ */ jsxs6("text", { fg: t.fg.secondary, children: [
5645
+ return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5646
+ /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 2, children: [
5647
+ /* @__PURE__ */ jsx7("text", { fg: t.status.success, children: "/ssh" }),
5648
+ /* @__PURE__ */ jsxs7("text", { fg: t.fg.secondary, children: [
5120
5649
  " - Connected to ",
5121
5650
  connectedDeployment
5122
5651
  ] })
5123
5652
  ] }),
5124
- /* @__PURE__ */ jsxs6(
5653
+ /* @__PURE__ */ jsxs7(
5125
5654
  "box",
5126
5655
  {
5127
5656
  flexDirection: "column",
@@ -5130,8 +5659,8 @@ function SSHView({ context }) {
5130
5659
  padding: 1,
5131
5660
  marginBottom: 1,
5132
5661
  children: [
5133
- /* @__PURE__ */ jsx6("text", { fg: t.status.success, children: "SSH Session Opened" }),
5134
- /* @__PURE__ */ jsxs6("text", { fg: t.fg.primary, marginTop: 1, children: [
5662
+ /* @__PURE__ */ jsx7("text", { fg: t.status.success, children: "SSH Session Opened" }),
5663
+ /* @__PURE__ */ jsxs7("text", { fg: t.fg.primary, marginTop: 1, children: [
5135
5664
  "A new ",
5136
5665
  terminalName,
5137
5666
  " window/tab has been opened."
@@ -5139,7 +5668,7 @@ function SSHView({ context }) {
5139
5668
  ]
5140
5669
  }
5141
5670
  ),
5142
- /* @__PURE__ */ jsxs6(
5671
+ /* @__PURE__ */ jsxs7(
5143
5672
  "box",
5144
5673
  {
5145
5674
  flexDirection: "column",
@@ -5148,41 +5677,41 @@ function SSHView({ context }) {
5148
5677
  padding: 1,
5149
5678
  marginBottom: 1,
5150
5679
  children: [
5151
- /* @__PURE__ */ jsxs6("text", { fg: t.fg.primary, children: [
5680
+ /* @__PURE__ */ jsxs7("text", { fg: t.fg.primary, children: [
5152
5681
  "Your SSH session is running in ",
5153
5682
  terminalName,
5154
5683
  "."
5155
5684
  ] }),
5156
- /* @__PURE__ */ jsx6("text", { fg: t.fg.primary, marginTop: 1, children: "When you're done, type 'exit' or close the tab." })
5685
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.primary, marginTop: 1, children: "When you're done, type 'exit' or close the tab." })
5157
5686
  ]
5158
5687
  }
5159
5688
  ),
5160
- /* @__PURE__ */ jsx6("text", { fg: t.fg.muted, marginTop: 1, children: "Press Enter or Esc to return to ClawControl" })
5689
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, marginTop: 1, children: "Press Enter or Esc to return to ClawControl" })
5161
5690
  ] });
5162
5691
  }
5163
5692
  if (viewState === "error") {
5164
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5165
- /* @__PURE__ */ jsx6("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx6("text", { fg: t.status.error, children: "SSH Error" }) }),
5166
- /* @__PURE__ */ jsx6(
5693
+ return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5694
+ /* @__PURE__ */ jsx7("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx7("text", { fg: t.status.error, children: "SSH Error" }) }),
5695
+ /* @__PURE__ */ jsx7(
5167
5696
  "box",
5168
5697
  {
5169
5698
  flexDirection: "column",
5170
5699
  borderStyle: "single",
5171
5700
  borderColor: t.status.error,
5172
5701
  padding: 1,
5173
- children: /* @__PURE__ */ jsx6("text", { fg: t.status.error, children: error })
5702
+ children: /* @__PURE__ */ jsx7("text", { fg: t.status.error, children: error })
5174
5703
  }
5175
5704
  ),
5176
- /* @__PURE__ */ jsx6("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5705
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5177
5706
  ] });
5178
5707
  }
5179
5708
  return null;
5180
5709
  }
5181
5710
 
5182
5711
  // src/components/LogsView.tsx
5183
- import { useState as useState7, useEffect as useEffect3, useRef as useRef3 } from "react";
5184
- import { useKeyboard as useKeyboard6 } from "@opentui/react";
5185
- import { Fragment, jsx as jsx7, jsxs as jsxs7 } from "@opentui/react/jsx-runtime";
5712
+ import { useState as useState8, useEffect as useEffect3, useRef as useRef4 } from "react";
5713
+ import { useKeyboard as useKeyboard7 } from "@opentui/react";
5714
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
5186
5715
  function parseLogLine(line) {
5187
5716
  if (!line.trim()) return null;
5188
5717
  const match = line.match(/^(\w+\s+\d+\s+[\d:]+)\s+\S+\s+\S+:\s*(.*)$/);
@@ -5201,14 +5730,14 @@ function truncateLine(line, maxWidth = 120) {
5201
5730
  return line.substring(0, maxWidth - 3) + "...";
5202
5731
  }
5203
5732
  function LogsView({ context }) {
5204
- const [viewState, setViewState] = useState7("selecting");
5205
- const [selectedIndex, setSelectedIndex] = useState7(0);
5206
- const [logs, setLogs] = useState7([]);
5207
- const [error, setError] = useState7(null);
5208
- const [autoRefresh, setAutoRefresh] = useState7(false);
5209
- const [lastFetched, setLastFetched] = useState7(null);
5210
- const sshRef = useRef3(null);
5211
- const refreshIntervalRef = useRef3(null);
5733
+ const [viewState, setViewState] = useState8("selecting");
5734
+ const [selectedIndex, setSelectedIndex] = useState8(0);
5735
+ const [logs, setLogs] = useState8([]);
5736
+ const [error, setError] = useState8(null);
5737
+ const [autoRefresh, setAutoRefresh] = useState8(false);
5738
+ const [lastFetched, setLastFetched] = useState8(null);
5739
+ const sshRef = useRef4(null);
5740
+ const refreshIntervalRef = useRef4(null);
5212
5741
  const deployedDeployments = context.deployments.filter(
5213
5742
  (d) => d.state.status === "deployed" && d.state.serverIp
5214
5743
  );
@@ -5259,7 +5788,7 @@ function LogsView({ context }) {
5259
5788
  setLogs([]);
5260
5789
  };
5261
5790
  const selectedDeployment = deployedDeployments[selectedIndex];
5262
- useKeyboard6((key) => {
5791
+ useKeyboard7((key) => {
5263
5792
  if (deployedDeployments.length === 0) {
5264
5793
  context.navigateTo("home");
5265
5794
  return;
@@ -5292,12 +5821,12 @@ function LogsView({ context }) {
5292
5821
  }
5293
5822
  });
5294
5823
  if (deployedDeployments.length === 0) {
5295
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5296
- /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 2, children: [
5297
- /* @__PURE__ */ jsx7("text", { fg: t.accent, children: "/logs" }),
5298
- /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, children: " - View deployment logs" })
5824
+ return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5825
+ /* @__PURE__ */ jsxs8("box", { flexDirection: "row", marginBottom: 2, children: [
5826
+ /* @__PURE__ */ jsx8("text", { fg: t.accent, children: "/logs" }),
5827
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: " - View deployment logs" })
5299
5828
  ] }),
5300
- /* @__PURE__ */ jsxs7(
5829
+ /* @__PURE__ */ jsxs8(
5301
5830
  "box",
5302
5831
  {
5303
5832
  flexDirection: "column",
@@ -5305,21 +5834,21 @@ function LogsView({ context }) {
5305
5834
  borderColor: t.border.default,
5306
5835
  padding: 1,
5307
5836
  children: [
5308
- /* @__PURE__ */ jsx7("text", { fg: t.status.warning, children: "No deployed instances found!" }),
5309
- /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, marginTop: 1, children: "Deploy an instance first with /deploy" })
5837
+ /* @__PURE__ */ jsx8("text", { fg: t.status.warning, children: "No deployed instances found!" }),
5838
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, marginTop: 1, children: "Deploy an instance first with /deploy" })
5310
5839
  ]
5311
5840
  }
5312
5841
  ),
5313
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5842
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5314
5843
  ] });
5315
5844
  }
5316
5845
  if (viewState === "selecting") {
5317
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5318
- /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 2, children: [
5319
- /* @__PURE__ */ jsx7("text", { fg: t.accent, children: "/logs" }),
5320
- /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, children: " - Select a deployment" })
5846
+ return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5847
+ /* @__PURE__ */ jsxs8("box", { flexDirection: "row", marginBottom: 2, children: [
5848
+ /* @__PURE__ */ jsx8("text", { fg: t.accent, children: "/logs" }),
5849
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: " - Select a deployment" })
5321
5850
  ] }),
5322
- /* @__PURE__ */ jsx7(
5851
+ /* @__PURE__ */ jsx8(
5323
5852
  "box",
5324
5853
  {
5325
5854
  flexDirection: "column",
@@ -5328,15 +5857,15 @@ function LogsView({ context }) {
5328
5857
  padding: 1,
5329
5858
  children: deployedDeployments.map((deployment, index) => {
5330
5859
  const isSelected = index === selectedIndex;
5331
- return /* @__PURE__ */ jsxs7(
5860
+ return /* @__PURE__ */ jsxs8(
5332
5861
  "box",
5333
5862
  {
5334
5863
  flexDirection: "row",
5335
5864
  backgroundColor: isSelected ? t.selection.bg : void 0,
5336
5865
  children: [
5337
- /* @__PURE__ */ jsx7("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
5338
- /* @__PURE__ */ jsx7("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
5339
- /* @__PURE__ */ jsx7("text", { fg: t.accent, children: deployment.state.serverIp })
5866
+ /* @__PURE__ */ jsx8("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
5867
+ /* @__PURE__ */ jsx8("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
5868
+ /* @__PURE__ */ jsx8("text", { fg: t.accent, children: deployment.state.serverIp })
5340
5869
  ]
5341
5870
  },
5342
5871
  deployment.config.name
@@ -5344,70 +5873,70 @@ function LogsView({ context }) {
5344
5873
  })
5345
5874
  }
5346
5875
  ),
5347
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, marginTop: 2, children: "Arrow keys to select | Enter to view logs | Esc to go back" })
5876
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, marginTop: 2, children: "Arrow keys to select | Enter to view logs | Esc to go back" })
5348
5877
  ] });
5349
5878
  }
5350
5879
  if (viewState === "loading") {
5351
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5352
- /* @__PURE__ */ jsx7("text", { fg: t.accent, children: "Loading logs..." }),
5353
- /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, marginTop: 1, children: "Fetching OpenClaw logs from server..." })
5880
+ return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5881
+ /* @__PURE__ */ jsx8("text", { fg: t.accent, children: "Loading logs..." }),
5882
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, marginTop: 1, children: "Fetching OpenClaw logs from server..." })
5354
5883
  ] });
5355
5884
  }
5356
5885
  if (viewState === "error") {
5357
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5358
- /* @__PURE__ */ jsx7("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx7("text", { fg: t.status.error, children: "Error Loading Logs" }) }),
5359
- /* @__PURE__ */ jsx7(
5886
+ return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5887
+ /* @__PURE__ */ jsx8("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "Error Loading Logs" }) }),
5888
+ /* @__PURE__ */ jsx8(
5360
5889
  "box",
5361
5890
  {
5362
5891
  flexDirection: "column",
5363
5892
  borderStyle: "single",
5364
5893
  borderColor: t.status.error,
5365
5894
  padding: 1,
5366
- children: /* @__PURE__ */ jsx7("text", { fg: t.status.error, children: error })
5895
+ children: /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: error })
5367
5896
  }
5368
5897
  ),
5369
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to go back" })
5898
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to go back" })
5370
5899
  ] });
5371
5900
  }
5372
5901
  const visibleLogs = logs;
5373
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5374
- /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 1, children: [
5375
- /* @__PURE__ */ jsxs7("text", { fg: t.accent, children: [
5902
+ return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5903
+ /* @__PURE__ */ jsxs8("box", { flexDirection: "row", marginBottom: 1, children: [
5904
+ /* @__PURE__ */ jsxs8("text", { fg: t.accent, children: [
5376
5905
  "Logs: ",
5377
5906
  selectedDeployment.config.name
5378
5907
  ] }),
5379
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, children: " | " }),
5380
- /* @__PURE__ */ jsxs7("text", { fg: autoRefresh ? t.status.success : t.fg.muted, children: [
5908
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, children: " | " }),
5909
+ /* @__PURE__ */ jsxs8("text", { fg: autoRefresh ? t.status.success : t.fg.muted, children: [
5381
5910
  "Auto: ",
5382
5911
  autoRefresh ? "ON (5s)" : "OFF"
5383
5912
  ] }),
5384
- lastFetched && /* @__PURE__ */ jsxs7(Fragment, { children: [
5385
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, children: " | " }),
5386
- /* @__PURE__ */ jsxs7("text", { fg: t.fg.muted, children: [
5913
+ lastFetched && /* @__PURE__ */ jsxs8(Fragment2, { children: [
5914
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, children: " | " }),
5915
+ /* @__PURE__ */ jsxs8("text", { fg: t.fg.muted, children: [
5387
5916
  "Fetched: ",
5388
5917
  lastFetched.toLocaleTimeString()
5389
5918
  ] })
5390
5919
  ] })
5391
5920
  ] }),
5392
- /* @__PURE__ */ jsx7(
5921
+ /* @__PURE__ */ jsx8(
5393
5922
  "box",
5394
5923
  {
5395
5924
  flexDirection: "column",
5396
5925
  borderStyle: "single",
5397
5926
  borderColor: t.border.default,
5398
5927
  padding: 1,
5399
- children: /* @__PURE__ */ jsx7("box", { flexDirection: "column", children: visibleLogs.map((line, i) => {
5928
+ children: /* @__PURE__ */ jsx8("box", { flexDirection: "column", children: visibleLogs.map((line, i) => {
5400
5929
  const parsed = parseLogLine(line);
5401
5930
  if (!parsed) return null;
5402
5931
  const displayLine = parsed.timestamp ? `${parsed.timestamp} ${truncateLine(parsed.message, 100)}` : truncateLine(parsed.message, 120);
5403
- return /* @__PURE__ */ jsx7("text", { fg: logLevelColor(parsed.level), children: displayLine }, i);
5932
+ return /* @__PURE__ */ jsx8("text", { fg: logLevelColor(parsed.level), children: displayLine }, i);
5404
5933
  }) })
5405
5934
  }
5406
5935
  ),
5407
- /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginTop: 1, children: [
5408
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, children: "R: Refresh | A: Toggle auto-refresh | Esc: Back" }),
5409
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, children: " | " }),
5410
- /* @__PURE__ */ jsxs7("text", { fg: t.accent, children: [
5936
+ /* @__PURE__ */ jsxs8("box", { flexDirection: "row", marginTop: 1, children: [
5937
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, children: "R: Refresh | A: Toggle auto-refresh | Esc: Back" }),
5938
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, children: " | " }),
5939
+ /* @__PURE__ */ jsxs8("text", { fg: t.accent, children: [
5411
5940
  "Showing last ",
5412
5941
  visibleLogs.length,
5413
5942
  " lines"
@@ -5417,14 +5946,14 @@ function LogsView({ context }) {
5417
5946
  }
5418
5947
 
5419
5948
  // src/components/DestroyView.tsx
5420
- import { useState as useState8 } from "react";
5421
- import { useKeyboard as useKeyboard7 } from "@opentui/react";
5422
- import { jsx as jsx8, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
5949
+ import { useState as useState9 } from "react";
5950
+ import { useKeyboard as useKeyboard8 } from "@opentui/react";
5951
+ import { jsx as jsx9, jsxs as jsxs9 } from "@opentui/react/jsx-runtime";
5423
5952
  function DestroyView({ context }) {
5424
- const [viewState, setViewState] = useState8("selecting");
5425
- const [selectedIndex, setSelectedIndex] = useState8(0);
5426
- const [error, setError] = useState8(null);
5427
- const [confirmText, setConfirmText] = useState8("");
5953
+ const [viewState, setViewState] = useState9("selecting");
5954
+ const [selectedIndex, setSelectedIndex] = useState9(0);
5955
+ const [error, setError] = useState9(null);
5956
+ const [confirmText, setConfirmText] = useState9("");
5428
5957
  const deployments = context.deployments;
5429
5958
  const destroyDeployment = async (name) => {
5430
5959
  setViewState("destroying");
@@ -5469,7 +5998,7 @@ function DestroyView({ context }) {
5469
5998
  }
5470
5999
  };
5471
6000
  const selectedDeployment = deployments[selectedIndex];
5472
- useKeyboard7((key) => {
6001
+ useKeyboard8((key) => {
5473
6002
  if (deployments.length === 0) {
5474
6003
  context.navigateTo("home");
5475
6004
  return;
@@ -5494,31 +6023,31 @@ function DestroyView({ context }) {
5494
6023
  }
5495
6024
  });
5496
6025
  if (deployments.length === 0) {
5497
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5498
- /* @__PURE__ */ jsxs8("box", { flexDirection: "row", marginBottom: 2, children: [
5499
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "/destroy" }),
5500
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: " - Destroy deployment" })
6026
+ return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6027
+ /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginBottom: 2, children: [
6028
+ /* @__PURE__ */ jsx9("text", { fg: t.status.error, children: "/destroy" }),
6029
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " - Destroy deployment" })
5501
6030
  ] }),
5502
- /* @__PURE__ */ jsx8(
6031
+ /* @__PURE__ */ jsx9(
5503
6032
  "box",
5504
6033
  {
5505
6034
  flexDirection: "column",
5506
6035
  borderStyle: "single",
5507
6036
  borderColor: t.border.default,
5508
6037
  padding: 1,
5509
- children: /* @__PURE__ */ jsx8("text", { fg: t.status.warning, children: "No deployments found!" })
6038
+ children: /* @__PURE__ */ jsx9("text", { fg: t.status.warning, children: "No deployments found!" })
5510
6039
  }
5511
6040
  ),
5512
- /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
6041
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5513
6042
  ] });
5514
6043
  }
5515
6044
  if (viewState === "selecting") {
5516
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5517
- /* @__PURE__ */ jsxs8("box", { flexDirection: "row", marginBottom: 2, children: [
5518
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "/destroy" }),
5519
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: " - Select a deployment to destroy" })
6045
+ return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6046
+ /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginBottom: 2, children: [
6047
+ /* @__PURE__ */ jsx9("text", { fg: t.status.error, children: "/destroy" }),
6048
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " - Select a deployment to destroy" })
5520
6049
  ] }),
5521
- /* @__PURE__ */ jsxs8(
6050
+ /* @__PURE__ */ jsxs9(
5522
6051
  "box",
5523
6052
  {
5524
6053
  flexDirection: "column",
@@ -5526,18 +6055,18 @@ function DestroyView({ context }) {
5526
6055
  borderColor: t.status.error,
5527
6056
  padding: 1,
5528
6057
  children: [
5529
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, marginBottom: 1, children: "WARNING: This action cannot be undone!" }),
6058
+ /* @__PURE__ */ jsx9("text", { fg: t.status.error, marginBottom: 1, children: "WARNING: This action cannot be undone!" }),
5530
6059
  deployments.map((deployment, index) => {
5531
6060
  const isSelected = index === selectedIndex;
5532
- return /* @__PURE__ */ jsxs8(
6061
+ return /* @__PURE__ */ jsxs9(
5533
6062
  "box",
5534
6063
  {
5535
6064
  flexDirection: "row",
5536
6065
  backgroundColor: isSelected ? t.selection.bg : void 0,
5537
6066
  children: [
5538
- /* @__PURE__ */ jsx8("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
5539
- /* @__PURE__ */ jsx8("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
5540
- /* @__PURE__ */ jsxs8("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: [
6067
+ /* @__PURE__ */ jsx9("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
6068
+ /* @__PURE__ */ jsx9("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
6069
+ /* @__PURE__ */ jsxs9("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: [
5541
6070
  "[",
5542
6071
  deployment.state.status,
5543
6072
  "]"
@@ -5550,13 +6079,13 @@ function DestroyView({ context }) {
5550
6079
  ]
5551
6080
  }
5552
6081
  ),
5553
- /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, marginTop: 2, children: "Arrow keys to select | Enter to destroy | Esc to go back" })
6082
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.muted, marginTop: 2, children: "Arrow keys to select | Enter to destroy | Esc to go back" })
5554
6083
  ] });
5555
6084
  }
5556
6085
  if (viewState === "confirming") {
5557
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5558
- /* @__PURE__ */ jsx8("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "Confirm Destruction" }) }),
5559
- /* @__PURE__ */ jsxs8(
6086
+ return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6087
+ /* @__PURE__ */ jsx9("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx9("text", { fg: t.status.error, children: "Confirm Destruction" }) }),
6088
+ /* @__PURE__ */ jsxs9(
5560
6089
  "box",
5561
6090
  {
5562
6091
  flexDirection: "column",
@@ -5564,26 +6093,26 @@ function DestroyView({ context }) {
5564
6093
  borderColor: t.status.error,
5565
6094
  padding: 1,
5566
6095
  children: [
5567
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "You are about to destroy:" }),
5568
- /* @__PURE__ */ jsxs8("text", { fg: t.fg.primary, marginTop: 1, children: [
6096
+ /* @__PURE__ */ jsx9("text", { fg: t.status.error, children: "You are about to destroy:" }),
6097
+ /* @__PURE__ */ jsxs9("text", { fg: t.fg.primary, marginTop: 1, children: [
5569
6098
  "Deployment: ",
5570
6099
  selectedDeployment.config.name
5571
6100
  ] }),
5572
- selectedDeployment.state.serverIp && /* @__PURE__ */ jsxs8("text", { fg: t.fg.primary, children: [
6101
+ selectedDeployment.state.serverIp && /* @__PURE__ */ jsxs9("text", { fg: t.fg.primary, children: [
5573
6102
  "Server IP: ",
5574
6103
  selectedDeployment.state.serverIp
5575
6104
  ] }),
5576
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, marginTop: 1, children: "This will permanently delete:" }),
5577
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: "\u2022 The VPS server (if deployed)" }),
5578
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: "\u2022 All data on the server" }),
5579
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: "\u2022 Local configuration files" }),
5580
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: "\u2022 SSH keys" })
6105
+ /* @__PURE__ */ jsx9("text", { fg: t.status.error, marginTop: 1, children: "This will permanently delete:" }),
6106
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "\u2022 The VPS server (if deployed)" }),
6107
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "\u2022 All data on the server" }),
6108
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "\u2022 Local configuration files" }),
6109
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "\u2022 SSH keys" })
5581
6110
  ]
5582
6111
  }
5583
6112
  ),
5584
- /* @__PURE__ */ jsx8("text", { fg: t.status.warning, marginTop: 2, children: "Type the deployment name to confirm:" }),
5585
- /* @__PURE__ */ jsx8("text", { fg: t.fg.primary, marginTop: 1, children: "Confirm:" }),
5586
- /* @__PURE__ */ jsx8(
6113
+ /* @__PURE__ */ jsx9("text", { fg: t.status.warning, marginTop: 2, children: "Type the deployment name to confirm:" }),
6114
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, marginTop: 1, children: "Confirm:" }),
6115
+ /* @__PURE__ */ jsx9(
5587
6116
  "input",
5588
6117
  {
5589
6118
  value: confirmText,
@@ -5606,19 +6135,19 @@ function DestroyView({ context }) {
5606
6135
  }
5607
6136
  }
5608
6137
  ),
5609
- error && /* @__PURE__ */ jsx8("text", { fg: t.status.error, marginTop: 1, children: error }),
5610
- /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, marginTop: 2, children: "Press Esc to cancel" })
6138
+ error && /* @__PURE__ */ jsx9("text", { fg: t.status.error, marginTop: 1, children: error }),
6139
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.muted, marginTop: 2, children: "Press Esc to cancel" })
5611
6140
  ] });
5612
6141
  }
5613
6142
  if (viewState === "destroying") {
5614
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5615
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "Destroying deployment..." }),
5616
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, marginTop: 1, children: "Deleting server and cleaning up resources..." })
6143
+ return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6144
+ /* @__PURE__ */ jsx9("text", { fg: t.status.error, children: "Destroying deployment..." }),
6145
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, marginTop: 1, children: "Deleting server and cleaning up resources..." })
5617
6146
  ] });
5618
6147
  }
5619
6148
  if (viewState === "success") {
5620
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5621
- /* @__PURE__ */ jsxs8(
6149
+ return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6150
+ /* @__PURE__ */ jsxs9(
5622
6151
  "box",
5623
6152
  {
5624
6153
  flexDirection: "column",
@@ -5626,8 +6155,8 @@ function DestroyView({ context }) {
5626
6155
  borderColor: t.status.success,
5627
6156
  padding: 1,
5628
6157
  children: [
5629
- /* @__PURE__ */ jsx8("text", { fg: t.status.success, children: "Deployment Destroyed" }),
5630
- /* @__PURE__ */ jsxs8("text", { fg: t.fg.primary, marginTop: 1, children: [
6158
+ /* @__PURE__ */ jsx9("text", { fg: t.status.success, children: "Deployment Destroyed" }),
6159
+ /* @__PURE__ */ jsxs9("text", { fg: t.fg.primary, marginTop: 1, children: [
5631
6160
  'The deployment "',
5632
6161
  selectedDeployment.config.name,
5633
6162
  '" has been permanently deleted.'
@@ -5635,12 +6164,12 @@ function DestroyView({ context }) {
5635
6164
  ]
5636
6165
  }
5637
6166
  ),
5638
- /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
6167
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5639
6168
  ] });
5640
6169
  }
5641
6170
  if (viewState === "error") {
5642
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5643
- /* @__PURE__ */ jsxs8(
6171
+ return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6172
+ /* @__PURE__ */ jsxs9(
5644
6173
  "box",
5645
6174
  {
5646
6175
  flexDirection: "column",
@@ -5648,30 +6177,30 @@ function DestroyView({ context }) {
5648
6177
  borderColor: t.status.error,
5649
6178
  padding: 1,
5650
6179
  children: [
5651
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "Destruction Failed" }),
5652
- /* @__PURE__ */ jsx8("text", { fg: t.fg.primary, marginTop: 1, children: error })
6180
+ /* @__PURE__ */ jsx9("text", { fg: t.status.error, children: "Destruction Failed" }),
6181
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, marginTop: 1, children: error })
5653
6182
  ]
5654
6183
  }
5655
6184
  ),
5656
- /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to go back" })
6185
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to go back" })
5657
6186
  ] });
5658
6187
  }
5659
6188
  return null;
5660
6189
  }
5661
6190
 
5662
6191
  // src/components/HelpView.tsx
5663
- import { useKeyboard as useKeyboard8 } from "@opentui/react";
5664
- import { jsx as jsx9, jsxs as jsxs9 } from "@opentui/react/jsx-runtime";
6192
+ import { useKeyboard as useKeyboard9 } from "@opentui/react";
6193
+ import { jsx as jsx10, jsxs as jsxs10 } from "@opentui/react/jsx-runtime";
5665
6194
  function HelpView({ context }) {
5666
- useKeyboard8(() => {
6195
+ useKeyboard9(() => {
5667
6196
  context.navigateTo("home");
5668
6197
  });
5669
- return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5670
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginBottom: 2, children: [
5671
- /* @__PURE__ */ jsx9("text", { fg: t.accent, children: "/help" }),
5672
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " - ClawControl Help" })
6198
+ return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6199
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginBottom: 2, children: [
6200
+ /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "/help" }),
6201
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " - ClawControl Help" })
5673
6202
  ] }),
5674
- /* @__PURE__ */ jsxs9(
6203
+ /* @__PURE__ */ jsxs10(
5675
6204
  "box",
5676
6205
  {
5677
6206
  flexDirection: "column",
@@ -5680,12 +6209,12 @@ function HelpView({ context }) {
5680
6209
  padding: 1,
5681
6210
  marginBottom: 1,
5682
6211
  children: [
5683
- /* @__PURE__ */ jsx9("text", { fg: t.accent, children: "What is ClawControl?" }),
5684
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, marginTop: 1, children: "ClawControl is a CLI tool that simplifies deploying OpenClaw instances to cloud providers. It handles all the complex setup including VPS provisioning, Node.js installation, OpenClaw configuration, and Tailscale VPN setup." })
6212
+ /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "What is ClawControl?" }),
6213
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, marginTop: 1, children: "ClawControl is a CLI tool that simplifies deploying OpenClaw instances to cloud providers. It handles all the complex setup including VPS provisioning, Node.js installation, OpenClaw configuration, and Tailscale VPN setup." })
5685
6214
  ]
5686
6215
  }
5687
6216
  ),
5688
- /* @__PURE__ */ jsxs9(
6217
+ /* @__PURE__ */ jsxs10(
5689
6218
  "box",
5690
6219
  {
5691
6220
  flexDirection: "column",
@@ -5694,48 +6223,48 @@ function HelpView({ context }) {
5694
6223
  padding: 1,
5695
6224
  marginBottom: 1,
5696
6225
  children: [
5697
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Available Commands" }),
5698
- /* @__PURE__ */ jsxs9("box", { flexDirection: "column", marginTop: 1, children: [
5699
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", children: [
5700
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/new" }),
5701
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Initialize a new deployment configuration" })
6226
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "Available Commands" }),
6227
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "column", marginTop: 1, children: [
6228
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6229
+ /* @__PURE__ */ jsx10("text", { fg: t.accent, width: 12, children: "/new" }),
6230
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "Initialize a new deployment configuration" })
5702
6231
  ] }),
5703
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Creates deployment config in ~/.clawcontrol/deployments/" }),
5704
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5705
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/deploy" }),
5706
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Deploy a configured instance to the cloud" })
6232
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " Creates deployment config in ~/.clawcontrol/deployments/" }),
6233
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginTop: 1, children: [
6234
+ /* @__PURE__ */ jsx10("text", { fg: t.accent, width: 12, children: "/deploy" }),
6235
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "Deploy a configured instance to the cloud" })
5707
6236
  ] }),
5708
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Provisions VPS, installs dependencies, configures OpenClaw" }),
5709
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5710
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/status" }),
5711
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "View status of all deployments" })
6237
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " Provisions VPS, installs dependencies, configures OpenClaw" }),
6238
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginTop: 1, children: [
6239
+ /* @__PURE__ */ jsx10("text", { fg: t.accent, width: 12, children: "/status" }),
6240
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "View status of all deployments" })
5712
6241
  ] }),
5713
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Shows deployment state, health checks, and connection info" }),
5714
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5715
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/ssh" }),
5716
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "SSH into a deployed instance" })
6242
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " Shows deployment state, health checks, and connection info" }),
6243
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginTop: 1, children: [
6244
+ /* @__PURE__ */ jsx10("text", { fg: t.accent, width: 12, children: "/ssh" }),
6245
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "SSH into a deployed instance" })
5717
6246
  ] }),
5718
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Opens interactive SSH session to your server" }),
5719
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5720
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/logs" }),
5721
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "View OpenClaw logs from a deployment" })
6247
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " Opens interactive SSH session to your server" }),
6248
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginTop: 1, children: [
6249
+ /* @__PURE__ */ jsx10("text", { fg: t.accent, width: 12, children: "/logs" }),
6250
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "View OpenClaw logs from a deployment" })
5722
6251
  ] }),
5723
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Streams logs from journalctl with auto-refresh option" }),
5724
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5725
- /* @__PURE__ */ jsx9("text", { fg: t.status.error, width: 12, children: "/destroy" }),
5726
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Permanently delete a deployment" })
6252
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " Streams logs from journalctl with auto-refresh option" }),
6253
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginTop: 1, children: [
6254
+ /* @__PURE__ */ jsx10("text", { fg: t.status.error, width: 12, children: "/destroy" }),
6255
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "Permanently delete a deployment" })
5727
6256
  ] }),
5728
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Deletes VPS, SSH keys, and local configuration" }),
5729
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5730
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/templates" }),
5731
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Manage deployment templates" })
6257
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " Deletes VPS, SSH keys, and local configuration" }),
6258
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginTop: 1, children: [
6259
+ /* @__PURE__ */ jsx10("text", { fg: t.accent, width: 12, children: "/templates" }),
6260
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "Manage deployment templates" })
5732
6261
  ] }),
5733
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " View, fork, and use reusable deployment presets" })
6262
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " View, fork, and use reusable deployment presets" })
5734
6263
  ] })
5735
6264
  ]
5736
6265
  }
5737
6266
  ),
5738
- /* @__PURE__ */ jsxs9(
6267
+ /* @__PURE__ */ jsxs10(
5739
6268
  "box",
5740
6269
  {
5741
6270
  flexDirection: "column",
@@ -5744,17 +6273,17 @@ function HelpView({ context }) {
5744
6273
  padding: 1,
5745
6274
  marginBottom: 1,
5746
6275
  children: [
5747
- /* @__PURE__ */ jsx9("text", { fg: t.status.success, children: "Typical Workflow" }),
5748
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, marginTop: 1, children: "1. Run /templates to browse or create reusable presets" }),
5749
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "2. Run /new to create a deployment config (optionally from a template)" }),
5750
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "3. Run /deploy to deploy to the cloud" }),
5751
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "4. Authenticate Tailscale when prompted" }),
5752
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "5. Complete OpenClaw onboarding via SSH" }),
5753
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "6. Use /status and /logs to monitor" })
6276
+ /* @__PURE__ */ jsx10("text", { fg: t.status.success, children: "Typical Workflow" }),
6277
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, marginTop: 1, children: "1. Run /templates to browse or create reusable presets" }),
6278
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "2. Run /new to create a deployment config (optionally from a template)" }),
6279
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "3. Run /deploy to deploy to the cloud" }),
6280
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "4. Authenticate Tailscale when prompted" }),
6281
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "5. Complete OpenClaw onboarding via SSH" }),
6282
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "6. Use /status and /logs to monitor" })
5754
6283
  ]
5755
6284
  }
5756
6285
  ),
5757
- /* @__PURE__ */ jsxs9(
6286
+ /* @__PURE__ */ jsxs10(
5758
6287
  "box",
5759
6288
  {
5760
6289
  flexDirection: "column",
@@ -5763,36 +6292,36 @@ function HelpView({ context }) {
5763
6292
  padding: 1,
5764
6293
  marginBottom: 1,
5765
6294
  children: [
5766
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Supported Cloud Providers" }),
5767
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5768
- /* @__PURE__ */ jsx9("text", { fg: t.status.success, children: "\u2713 " }),
5769
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Hetzner Cloud - ~$4.99/mo for CPX11 (US East)" })
6295
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "Supported Cloud Providers" }),
6296
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginTop: 1, children: [
6297
+ /* @__PURE__ */ jsx10("text", { fg: t.status.success, children: "\u2713 " }),
6298
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "Hetzner Cloud - ~$4.99/mo for CPX11 (US East)" })
5770
6299
  ] }),
5771
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", children: [
5772
- /* @__PURE__ */ jsx9("text", { fg: t.status.success, children: "\u2713 " }),
5773
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "DigitalOcean - Starting at $12/mo (NYC1)" })
6300
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6301
+ /* @__PURE__ */ jsx10("text", { fg: t.status.success, children: "\u2713 " }),
6302
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "DigitalOcean - Starting at $12/mo (NYC1)" })
5774
6303
  ] }),
5775
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", children: [
5776
- /* @__PURE__ */ jsx9("text", { fg: t.fg.muted, children: "\u25CB " }),
5777
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "Vultr - Coming soon" })
6304
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6305
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, children: "\u25CB " }),
6306
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: "Vultr - Coming soon" })
5778
6307
  ] })
5779
6308
  ]
5780
6309
  }
5781
6310
  ),
5782
- /* @__PURE__ */ jsxs9("box", { flexDirection: "column", marginBottom: 1, children: [
5783
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Useful Links" }),
5784
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "\u2022 OpenClaw Docs: https://docs.openclaw.ai/" }),
5785
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "\u2022 Hetzner API: https://docs.hetzner.cloud/" }),
5786
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "\u2022 Tailscale: https://tailscale.com/" })
6311
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "column", marginBottom: 1, children: [
6312
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "Useful Links" }),
6313
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: "\u2022 OpenClaw Docs: https://docs.openclaw.ai/" }),
6314
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: "\u2022 Hetzner API: https://docs.hetzner.cloud/" }),
6315
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: "\u2022 Tailscale: https://tailscale.com/" })
5787
6316
  ] }),
5788
- /* @__PURE__ */ jsx9("text", { fg: t.fg.muted, children: "Press any key to return to home" })
6317
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, children: "Press any key to return to home" })
5789
6318
  ] });
5790
6319
  }
5791
6320
 
5792
6321
  // src/components/TemplatesView.tsx
5793
- import { useState as useState9, useRef as useRef4, useEffect as useEffect4 } from "react";
5794
- import { useKeyboard as useKeyboard9 } from "@opentui/react";
5795
- import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs10 } from "@opentui/react/jsx-runtime";
6322
+ import { useState as useState10, useRef as useRef5, useEffect as useEffect4 } from "react";
6323
+ import { useKeyboard as useKeyboard10 } from "@opentui/react";
6324
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs11 } from "@opentui/react/jsx-runtime";
5796
6325
  var DO_DROPLET_SIZES2 = [
5797
6326
  { slug: "s-1vcpu-2gb", label: "1 vCPU, 2GB RAM, 50GB SSD", price: "$12/mo" },
5798
6327
  { slug: "s-2vcpu-2gb", label: "2 vCPU, 2GB RAM, 60GB SSD", price: "$18/mo" },
@@ -5801,20 +6330,20 @@ var DO_DROPLET_SIZES2 = [
5801
6330
  { slug: "s-8vcpu-16gb", label: "8 vCPU, 16GB RAM, 320GB SSD", price: "$96/mo" }
5802
6331
  ];
5803
6332
  function TemplatesView({ context }) {
5804
- const [viewState, setViewState] = useState9("listing");
5805
- const [templates, setTemplates] = useState9([]);
5806
- const [selectedIndex, setSelectedIndex] = useState9(0);
5807
- const [selectedTemplate, setSelectedTemplate] = useState9(null);
5808
- const [error, setError] = useState9(null);
5809
- const [forkStep, setForkStep] = useState9("fork_name");
5810
- const [forkName, setForkName] = useState9("");
5811
- const [forkProvider, setForkProvider] = useState9("hetzner");
5812
- const [forkProviderIndex, setForkProviderIndex] = useState9(0);
5813
- const [forkDropletSizeIndex, setForkDropletSizeIndex] = useState9(0);
5814
- const [forkAiProvider, setForkAiProvider] = useState9("");
5815
- const [forkAiProviderIndex, setForkAiProviderIndex] = useState9(0);
5816
- const [forkModel, setForkModel] = useState9("");
5817
- const stateRef = useRef4({
6333
+ const [viewState, setViewState] = useState10("listing");
6334
+ const [templates, setTemplates] = useState10([]);
6335
+ const [selectedIndex, setSelectedIndex] = useState10(0);
6336
+ const [selectedTemplate, setSelectedTemplate] = useState10(null);
6337
+ const [error, setError] = useState10(null);
6338
+ const [forkStep, setForkStep] = useState10("fork_name");
6339
+ const [forkName, setForkName] = useState10("");
6340
+ const [forkProvider, setForkProvider] = useState10("hetzner");
6341
+ const [forkProviderIndex, setForkProviderIndex] = useState10(0);
6342
+ const [forkDropletSizeIndex, setForkDropletSizeIndex] = useState10(0);
6343
+ const [forkAiProvider, setForkAiProvider] = useState10("");
6344
+ const [forkAiProviderIndex, setForkAiProviderIndex] = useState10(0);
6345
+ const [forkModel, setForkModel] = useState10("");
6346
+ const stateRef = useRef5({
5818
6347
  viewState,
5819
6348
  selectedIndex,
5820
6349
  selectedTemplate,
@@ -5928,7 +6457,7 @@ function TemplatesView({ context }) {
5928
6457
  setViewState("viewing");
5929
6458
  }
5930
6459
  };
5931
- useKeyboard9((key) => {
6460
+ useKeyboard10((key) => {
5932
6461
  const s = stateRef.current;
5933
6462
  if (s.viewState === "listing") {
5934
6463
  if (key.name === "up") {
@@ -6020,37 +6549,37 @@ function TemplatesView({ context }) {
6020
6549
  }
6021
6550
  }
6022
6551
  });
6023
- const renderListing = () => /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6024
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginBottom: 1, children: "Select a template to view details. Use arrow keys and Enter." }),
6025
- /* @__PURE__ */ jsx10(
6552
+ const renderListing = () => /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6553
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, marginBottom: 1, children: "Select a template to view details. Use arrow keys and Enter." }),
6554
+ /* @__PURE__ */ jsx11(
6026
6555
  "box",
6027
6556
  {
6028
6557
  flexDirection: "column",
6029
6558
  borderStyle: "single",
6030
6559
  borderColor: t.border.default,
6031
6560
  padding: 1,
6032
- children: templates.length === 0 ? /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, children: "No templates found." }) : templates.map((tmpl, i) => {
6561
+ children: templates.length === 0 ? /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, children: "No templates found." }) : templates.map((tmpl, i) => {
6033
6562
  const isSelected = i === selectedIndex;
6034
6563
  const badge = tmpl.builtIn ? "[built-in]" : "[custom]";
6035
6564
  const badgeColor = tmpl.builtIn ? t.status.info : t.status.success;
6036
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6037
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6038
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: tmpl.name }),
6039
- /* @__PURE__ */ jsx10("text", { fg: badgeColor, children: " " + badge }),
6040
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, children: " \xB7 " + tmpl.channel })
6565
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6566
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6567
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: tmpl.name }),
6568
+ /* @__PURE__ */ jsx11("text", { fg: badgeColor, children: " " + badge }),
6569
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, children: " \xB7 " + tmpl.channel })
6041
6570
  ] }, tmpl.id);
6042
6571
  })
6043
6572
  }
6044
6573
  ),
6045
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, marginTop: 1, children: "Enter to view, Esc to go back" })
6574
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, marginTop: 1, children: "Enter to view, Esc to go back" })
6046
6575
  ] });
6047
6576
  const renderViewing = () => {
6048
6577
  if (!selectedTemplate) return null;
6049
6578
  const tmpl = selectedTemplate;
6050
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6051
- /* @__PURE__ */ jsx10("text", { fg: t.accent, marginBottom: 1, children: tmpl.name }),
6052
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginBottom: 1, children: tmpl.description }),
6053
- /* @__PURE__ */ jsxs10(
6579
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6580
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, marginBottom: 1, children: tmpl.name }),
6581
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, marginBottom: 1, children: tmpl.description }),
6582
+ /* @__PURE__ */ jsxs11(
6054
6583
  "box",
6055
6584
  {
6056
6585
  flexDirection: "column",
@@ -6059,89 +6588,89 @@ function TemplatesView({ context }) {
6059
6588
  padding: 1,
6060
6589
  marginBottom: 1,
6061
6590
  children: [
6062
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6063
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Type:" }),
6064
- /* @__PURE__ */ jsx10("text", { fg: tmpl.builtIn ? t.status.info : t.status.success, children: tmpl.builtIn ? "Built-in" : "Custom" })
6591
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6592
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Type:" }),
6593
+ /* @__PURE__ */ jsx11("text", { fg: tmpl.builtIn ? t.status.info : t.status.success, children: tmpl.builtIn ? "Built-in" : "Custom" })
6065
6594
  ] }),
6066
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6067
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Provider:" }),
6068
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: PROVIDER_NAMES[tmpl.provider] })
6595
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6596
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Provider:" }),
6597
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: PROVIDER_NAMES[tmpl.provider] })
6069
6598
  ] }),
6070
- tmpl.hetzner && /* @__PURE__ */ jsxs10(Fragment2, { children: [
6071
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6072
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Server Type:" }),
6073
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: tmpl.hetzner.serverType })
6599
+ tmpl.hetzner && /* @__PURE__ */ jsxs11(Fragment3, { children: [
6600
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6601
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Server Type:" }),
6602
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: tmpl.hetzner.serverType })
6074
6603
  ] }),
6075
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6076
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Location:" }),
6077
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: tmpl.hetzner.location })
6604
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6605
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Location:" }),
6606
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: tmpl.hetzner.location })
6078
6607
  ] })
6079
6608
  ] }),
6080
- tmpl.digitalocean && /* @__PURE__ */ jsxs10(Fragment2, { children: [
6081
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6082
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Droplet Size:" }),
6083
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: tmpl.digitalocean.size })
6609
+ tmpl.digitalocean && /* @__PURE__ */ jsxs11(Fragment3, { children: [
6610
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6611
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Droplet Size:" }),
6612
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: tmpl.digitalocean.size })
6084
6613
  ] }),
6085
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6086
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Region:" }),
6087
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: tmpl.digitalocean.region })
6614
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6615
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Region:" }),
6616
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: tmpl.digitalocean.region })
6088
6617
  ] })
6089
6618
  ] }),
6090
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6091
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "AI Provider:" }),
6092
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: tmpl.aiProvider })
6619
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6620
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "AI Provider:" }),
6621
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: tmpl.aiProvider })
6093
6622
  ] }),
6094
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6095
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Model:" }),
6096
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: tmpl.model })
6623
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6624
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Model:" }),
6625
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: tmpl.model })
6097
6626
  ] }),
6098
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6099
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Channel:" }),
6100
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: tmpl.channel })
6627
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6628
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Channel:" }),
6629
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: tmpl.channel })
6101
6630
  ] })
6102
6631
  ]
6103
6632
  }
6104
6633
  ),
6105
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6106
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "[F]" }),
6107
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "ork " }),
6108
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "[U]" }),
6109
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "se " }),
6110
- !tmpl.builtIn && /* @__PURE__ */ jsxs10(Fragment2, { children: [
6111
- /* @__PURE__ */ jsx10("text", { fg: t.status.error, children: "[D]" }),
6112
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "elete " })
6634
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6635
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "[F]" }),
6636
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: "ork " }),
6637
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "[U]" }),
6638
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: "se " }),
6639
+ !tmpl.builtIn && /* @__PURE__ */ jsxs11(Fragment3, { children: [
6640
+ /* @__PURE__ */ jsx11("text", { fg: t.status.error, children: "[D]" }),
6641
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: "elete " })
6113
6642
  ] }),
6114
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, children: "[Esc] Back" })
6643
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, children: "[Esc] Back" })
6115
6644
  ] }),
6116
- error && /* @__PURE__ */ jsx10("text", { fg: t.status.error, marginTop: 1, children: error })
6645
+ error && /* @__PURE__ */ jsx11("text", { fg: t.status.error, marginTop: 1, children: error })
6117
6646
  ] });
6118
6647
  };
6119
- const renderDeleteConfirm = () => /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6120
- /* @__PURE__ */ jsx10("text", { fg: t.status.error, children: "Delete Template" }),
6121
- /* @__PURE__ */ jsxs10("text", { fg: t.fg.primary, marginTop: 1, children: [
6648
+ const renderDeleteConfirm = () => /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6649
+ /* @__PURE__ */ jsx11("text", { fg: t.status.error, children: "Delete Template" }),
6650
+ /* @__PURE__ */ jsxs11("text", { fg: t.fg.primary, marginTop: 1, children: [
6122
6651
  'Are you sure you want to delete "',
6123
6652
  selectedTemplate?.name,
6124
6653
  '"?'
6125
6654
  ] }),
6126
- /* @__PURE__ */ jsx10("text", { fg: t.status.warning, marginTop: 1, children: "This action cannot be undone." }),
6127
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, marginTop: 2, children: "Press Y to confirm, N to cancel" })
6655
+ /* @__PURE__ */ jsx11("text", { fg: t.status.warning, marginTop: 1, children: "This action cannot be undone." }),
6656
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, marginTop: 2, children: "Press Y to confirm, N to cancel" })
6128
6657
  ] });
6129
- const renderForkComplete = () => /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6130
- /* @__PURE__ */ jsx10("text", { fg: t.status.success, children: "Template Created!" }),
6131
- /* @__PURE__ */ jsxs10("text", { fg: t.fg.primary, marginTop: 1, children: [
6658
+ const renderForkComplete = () => /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6659
+ /* @__PURE__ */ jsx11("text", { fg: t.status.success, children: "Template Created!" }),
6660
+ /* @__PURE__ */ jsxs11("text", { fg: t.fg.primary, marginTop: 1, children: [
6132
6661
  'Your template "',
6133
6662
  forkName,
6134
6663
  '" has been saved.'
6135
6664
  ] }),
6136
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to templates list" })
6665
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to templates list" })
6137
6666
  ] });
6138
6667
  const renderForking = () => {
6139
6668
  switch (forkStep) {
6140
6669
  case "fork_name":
6141
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6142
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - Name" }),
6143
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginTop: 1, children: "Enter a name for the new template:" }),
6144
- /* @__PURE__ */ jsx10(
6670
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6671
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "Fork Template - Name" }),
6672
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, marginTop: 1, children: "Enter a name for the new template:" }),
6673
+ /* @__PURE__ */ jsx11(
6145
6674
  "input",
6146
6675
  {
6147
6676
  value: forkName,
@@ -6164,14 +6693,14 @@ function TemplatesView({ context }) {
6164
6693
  }
6165
6694
  }
6166
6695
  ),
6167
- error && /* @__PURE__ */ jsx10("text", { fg: t.status.error, marginTop: 1, children: error }),
6168
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, marginTop: 2, children: "Press Enter to continue, Esc to go back" })
6696
+ error && /* @__PURE__ */ jsx11("text", { fg: t.status.error, marginTop: 1, children: error }),
6697
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, marginTop: 2, children: "Press Enter to continue, Esc to go back" })
6169
6698
  ] });
6170
6699
  case "fork_provider":
6171
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6172
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - Provider" }),
6173
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginTop: 1, children: "Select cloud provider:" }),
6174
- /* @__PURE__ */ jsx10(
6700
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6701
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "Fork Template - Provider" }),
6702
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, marginTop: 1, children: "Select cloud provider:" }),
6703
+ /* @__PURE__ */ jsx11(
6175
6704
  "box",
6176
6705
  {
6177
6706
  flexDirection: "column",
@@ -6181,20 +6710,20 @@ function TemplatesView({ context }) {
6181
6710
  padding: 1,
6182
6711
  children: SUPPORTED_PROVIDERS.map((p, i) => {
6183
6712
  const isSelected = i === forkProviderIndex;
6184
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6185
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6186
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: PROVIDER_NAMES[p] })
6713
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6714
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6715
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: PROVIDER_NAMES[p] })
6187
6716
  ] }, p);
6188
6717
  })
6189
6718
  }
6190
6719
  ),
6191
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, marginTop: 1, children: "Enter to select, Esc to go back" })
6720
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, marginTop: 1, children: "Enter to select, Esc to go back" })
6192
6721
  ] });
6193
6722
  case "fork_droplet_size":
6194
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6195
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - Droplet Size" }),
6196
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginTop: 1, children: "Select DigitalOcean droplet size:" }),
6197
- /* @__PURE__ */ jsx10(
6723
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6724
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "Fork Template - Droplet Size" }),
6725
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, marginTop: 1, children: "Select DigitalOcean droplet size:" }),
6726
+ /* @__PURE__ */ jsx11(
6198
6727
  "box",
6199
6728
  {
6200
6729
  flexDirection: "column",
@@ -6204,21 +6733,21 @@ function TemplatesView({ context }) {
6204
6733
  padding: 1,
6205
6734
  children: DO_DROPLET_SIZES2.map((size, i) => {
6206
6735
  const isSelected = i === forkDropletSizeIndex;
6207
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6208
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6209
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: size.slug }),
6210
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.fg.primary : t.fg.secondary, children: " - " + size.label + " - " + size.price })
6736
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6737
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6738
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: size.slug }),
6739
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.fg.primary : t.fg.secondary, children: " - " + size.label + " - " + size.price })
6211
6740
  ] }, size.slug);
6212
6741
  })
6213
6742
  }
6214
6743
  ),
6215
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, marginTop: 1, children: "Enter to select, Esc to go back" })
6744
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, marginTop: 1, children: "Enter to select, Esc to go back" })
6216
6745
  ] });
6217
6746
  case "fork_ai_provider":
6218
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6219
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - AI Provider" }),
6220
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginTop: 1, children: "Select AI provider:" }),
6221
- /* @__PURE__ */ jsx10(
6747
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6748
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "Fork Template - AI Provider" }),
6749
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, marginTop: 1, children: "Select AI provider:" }),
6750
+ /* @__PURE__ */ jsx11(
6222
6751
  "box",
6223
6752
  {
6224
6753
  flexDirection: "column",
@@ -6228,21 +6757,21 @@ function TemplatesView({ context }) {
6228
6757
  padding: 1,
6229
6758
  children: AI_PROVIDERS.map((p, i) => {
6230
6759
  const isSelected = i === forkAiProviderIndex;
6231
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6232
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6233
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: p.label }),
6234
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.fg.primary : t.fg.secondary, children: " - " + p.description })
6760
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6761
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6762
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: p.label }),
6763
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.fg.primary : t.fg.secondary, children: " - " + p.description })
6235
6764
  ] }, p.name);
6236
6765
  })
6237
6766
  }
6238
6767
  ),
6239
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, marginTop: 1, children: "Enter to select, Esc to go back" })
6768
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, marginTop: 1, children: "Enter to select, Esc to go back" })
6240
6769
  ] });
6241
6770
  case "fork_model":
6242
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6243
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - Model" }),
6244
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginTop: 1, children: "Enter the model identifier:" }),
6245
- /* @__PURE__ */ jsx10(
6771
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6772
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "Fork Template - Model" }),
6773
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, marginTop: 1, children: "Enter the model identifier:" }),
6774
+ /* @__PURE__ */ jsx11(
6246
6775
  "input",
6247
6776
  {
6248
6777
  value: forkModel,
@@ -6265,13 +6794,13 @@ function TemplatesView({ context }) {
6265
6794
  }
6266
6795
  }
6267
6796
  ),
6268
- error && /* @__PURE__ */ jsx10("text", { fg: t.status.error, marginTop: 1, children: error }),
6269
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, marginTop: 2, children: "Press Enter to continue, Esc to go back" })
6797
+ error && /* @__PURE__ */ jsx11("text", { fg: t.status.error, marginTop: 1, children: error }),
6798
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, marginTop: 2, children: "Press Enter to continue, Esc to go back" })
6270
6799
  ] });
6271
6800
  case "fork_confirm":
6272
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6273
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - Confirm" }),
6274
- /* @__PURE__ */ jsxs10(
6801
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6802
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "Fork Template - Confirm" }),
6803
+ /* @__PURE__ */ jsxs11(
6275
6804
  "box",
6276
6805
  {
6277
6806
  flexDirection: "column",
@@ -6280,35 +6809,35 @@ function TemplatesView({ context }) {
6280
6809
  padding: 1,
6281
6810
  marginTop: 1,
6282
6811
  children: [
6283
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6284
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Name:" }),
6285
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: forkName })
6812
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6813
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Name:" }),
6814
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: forkName })
6286
6815
  ] }),
6287
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6288
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Provider:" }),
6289
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: PROVIDER_NAMES[forkProvider] })
6816
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6817
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Provider:" }),
6818
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: PROVIDER_NAMES[forkProvider] })
6290
6819
  ] }),
6291
- forkProvider === "digitalocean" && /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6292
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Droplet Size:" }),
6293
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: DO_DROPLET_SIZES2[forkDropletSizeIndex].slug })
6820
+ forkProvider === "digitalocean" && /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6821
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Droplet Size:" }),
6822
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: DO_DROPLET_SIZES2[forkDropletSizeIndex].slug })
6294
6823
  ] }),
6295
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6296
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "AI Provider:" }),
6297
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: forkAiProvider })
6824
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6825
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "AI Provider:" }),
6826
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: forkAiProvider })
6298
6827
  ] }),
6299
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6300
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Model:" }),
6301
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: forkModel })
6828
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6829
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Model:" }),
6830
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: forkModel })
6302
6831
  ] }),
6303
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6304
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Channel:" }),
6305
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "telegram" })
6832
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6833
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Channel:" }),
6834
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: "telegram" })
6306
6835
  ] })
6307
6836
  ]
6308
6837
  }
6309
6838
  ),
6310
- error && /* @__PURE__ */ jsx10("text", { fg: t.status.error, marginTop: 1, children: error }),
6311
- /* @__PURE__ */ jsx10("text", { fg: t.status.warning, marginTop: 2, children: "Press Y to confirm, N to go back" })
6839
+ error && /* @__PURE__ */ jsx11("text", { fg: t.status.error, marginTop: 1, children: error }),
6840
+ /* @__PURE__ */ jsx11("text", { fg: t.status.warning, marginTop: 2, children: "Press Y to confirm, N to go back" })
6312
6841
  ] });
6313
6842
  }
6314
6843
  };
@@ -6326,34 +6855,464 @@ function TemplatesView({ context }) {
6326
6855
  return renderForking();
6327
6856
  }
6328
6857
  };
6329
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6330
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginBottom: 2, children: [
6331
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "/templates" }),
6332
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " - Manage deployment templates" })
6858
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6859
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", marginBottom: 2, children: [
6860
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "/templates" }),
6861
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, children: " - Manage deployment templates" })
6333
6862
  ] }),
6334
6863
  renderContent()
6335
6864
  ] });
6336
6865
  }
6337
6866
 
6867
+ // src/components/DashboardView.tsx
6868
+ import { useState as useState11, useCallback as useCallback3 } from "react";
6869
+ import { useKeyboard as useKeyboard11, useRenderer } from "@opentui/react";
6870
+ import { spawnSync as spawnSync2 } from "child_process";
6871
+ import { platform as platform2 } from "os";
6872
+
6873
+ // src/services/tunnel.ts
6874
+ import { spawn as spawn2 } from "child_process";
6875
+ import { createServer } from "net";
6876
+ var activeTunnels = /* @__PURE__ */ new Map();
6877
+ function isPortAvailable(port) {
6878
+ return new Promise((resolve) => {
6879
+ const server = createServer();
6880
+ server.once("error", () => resolve(false));
6881
+ server.once("listening", () => {
6882
+ server.close(() => resolve(true));
6883
+ });
6884
+ server.listen(port, "127.0.0.1");
6885
+ });
6886
+ }
6887
+ async function findAvailablePort(startPort) {
6888
+ for (let port = startPort; port < startPort + 100; port++) {
6889
+ if (await isPortAvailable(port)) return port;
6890
+ }
6891
+ throw new Error(`No available port found in range ${startPort}\u2013${startPort + 99}`);
6892
+ }
6893
+ async function startTunnel(deploymentName, serverIp, remotePort = 18789) {
6894
+ const existing = getTunnel(deploymentName);
6895
+ if (existing) return existing;
6896
+ const sshKeyPath = getSSHKeyPath(deploymentName);
6897
+ const localPort = await findAvailablePort(remotePort);
6898
+ const proc = spawn2("ssh", [
6899
+ "-N",
6900
+ "-L",
6901
+ `${localPort}:127.0.0.1:${remotePort}`,
6902
+ "-i",
6903
+ sshKeyPath,
6904
+ "-o",
6905
+ "StrictHostKeyChecking=no",
6906
+ "-o",
6907
+ "UserKnownHostsFile=/dev/null",
6908
+ "-o",
6909
+ "ServerAliveInterval=15",
6910
+ "-o",
6911
+ "ServerAliveCountMax=3",
6912
+ "-o",
6913
+ "ExitOnForwardFailure=yes",
6914
+ `root@${serverIp}`
6915
+ ], {
6916
+ stdio: ["ignore", "pipe", "pipe"],
6917
+ detached: false
6918
+ });
6919
+ const tunnel = {
6920
+ deploymentName,
6921
+ serverIp,
6922
+ localPort,
6923
+ remotePort,
6924
+ process: proc,
6925
+ dashboardUrl: null,
6926
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
6927
+ };
6928
+ activeTunnels.set(deploymentName, tunnel);
6929
+ proc.on("exit", () => {
6930
+ activeTunnels.delete(deploymentName);
6931
+ });
6932
+ await new Promise((resolve) => setTimeout(resolve, 2500));
6933
+ if (proc.exitCode !== null || proc.killed) {
6934
+ activeTunnels.delete(deploymentName);
6935
+ throw new Error("SSH tunnel failed to establish. Check that the server is reachable.");
6936
+ }
6937
+ return tunnel;
6938
+ }
6939
+ function stopTunnel(deploymentName) {
6940
+ const tunnel = activeTunnels.get(deploymentName);
6941
+ if (tunnel) {
6942
+ tunnel.process.kill();
6943
+ activeTunnels.delete(deploymentName);
6944
+ }
6945
+ }
6946
+ function getTunnel(deploymentName) {
6947
+ const tunnel = activeTunnels.get(deploymentName);
6948
+ if (tunnel && tunnel.process.exitCode === null && !tunnel.process.killed) {
6949
+ return tunnel;
6950
+ }
6951
+ if (tunnel) activeTunnels.delete(deploymentName);
6952
+ return null;
6953
+ }
6954
+ function getActiveTunnels() {
6955
+ for (const [name, tunnel] of activeTunnels) {
6956
+ if (tunnel.process.exitCode !== null || tunnel.process.killed) {
6957
+ activeTunnels.delete(name);
6958
+ }
6959
+ }
6960
+ return Array.from(activeTunnels.values());
6961
+ }
6962
+
6963
+ // src/components/DashboardView.tsx
6964
+ import { jsx as jsx12, jsxs as jsxs12 } from "@opentui/react/jsx-runtime";
6965
+ function openInBrowser(url) {
6966
+ try {
6967
+ if (platform2() === "darwin") {
6968
+ spawnSync2("open", [url]);
6969
+ } else {
6970
+ spawnSync2("xdg-open", [url]);
6971
+ }
6972
+ return true;
6973
+ } catch {
6974
+ return false;
6975
+ }
6976
+ }
6977
+ function DashboardView({ context }) {
6978
+ const renderer = useRenderer();
6979
+ const [viewState, setViewState] = useState11("selecting");
6980
+ const [selectedIndex, setSelectedIndex] = useState11(0);
6981
+ const [error, setError] = useState11(null);
6982
+ const [activeTunnel, setActiveTunnel] = useState11(null);
6983
+ const [dashboardUrl, setDashboardUrl] = useState11(null);
6984
+ const [connectMessage, setConnectMessage] = useState11("");
6985
+ const [copied, setCopied] = useState11(false);
6986
+ const deployedDeployments = context.deployments.filter(
6987
+ (d) => d.state.status === "deployed" && d.state.serverIp
6988
+ );
6989
+ const connectToDashboard = useCallback3(async (deployment) => {
6990
+ setViewState("connecting");
6991
+ setError(null);
6992
+ setCopied(false);
6993
+ setConnectMessage("Establishing SSH tunnel...");
6994
+ try {
6995
+ const serverIp = deployment.state.serverIp;
6996
+ const remotePort = deployment.config.openclawConfig?.gateway?.port || 18789;
6997
+ const tunnel = await startTunnel(
6998
+ deployment.config.name,
6999
+ serverIp,
7000
+ remotePort
7001
+ );
7002
+ setActiveTunnel(tunnel);
7003
+ if (tunnel.dashboardUrl) {
7004
+ setDashboardUrl(tunnel.dashboardUrl);
7005
+ setViewState("active");
7006
+ return;
7007
+ }
7008
+ setConnectMessage("Retrieving dashboard URL...");
7009
+ const localToken = deployment.state.gatewayToken;
7010
+ if (localToken) {
7011
+ const localUrl = `http://127.0.0.1:${tunnel.localPort}/?token=${encodeURIComponent(localToken)}`;
7012
+ tunnel.dashboardUrl = localUrl;
7013
+ setDashboardUrl(localUrl);
7014
+ setViewState("active");
7015
+ return;
7016
+ }
7017
+ const ssh = await connectToDeployment(
7018
+ deployment.config.name,
7019
+ serverIp
7020
+ );
7021
+ try {
7022
+ const info = await getDashboardUrl(ssh);
7023
+ const parsed = new URL(info.url);
7024
+ parsed.hostname = "127.0.0.1";
7025
+ parsed.port = String(tunnel.localPort);
7026
+ const localUrl = parsed.toString();
7027
+ tunnel.dashboardUrl = localUrl;
7028
+ setDashboardUrl(localUrl);
7029
+ setViewState("active");
7030
+ } finally {
7031
+ ssh.disconnect();
7032
+ }
7033
+ } catch (err) {
7034
+ setError(err instanceof Error ? err.message : String(err));
7035
+ setViewState("error");
7036
+ }
7037
+ }, []);
7038
+ useKeyboard11((key) => {
7039
+ if (viewState === "selecting") {
7040
+ if (deployedDeployments.length === 0) {
7041
+ if (key.name === "escape" || key.name === "return") {
7042
+ context.navigateTo("home");
7043
+ }
7044
+ return;
7045
+ }
7046
+ if (key.name === "up" && selectedIndex > 0) {
7047
+ setSelectedIndex(selectedIndex - 1);
7048
+ } else if (key.name === "down" && selectedIndex < deployedDeployments.length - 1) {
7049
+ setSelectedIndex(selectedIndex + 1);
7050
+ } else if (key.name === "return") {
7051
+ const deployment = deployedDeployments[selectedIndex];
7052
+ const existing = getTunnel(deployment.config.name);
7053
+ if (existing && existing.dashboardUrl) {
7054
+ setActiveTunnel(existing);
7055
+ setDashboardUrl(existing.dashboardUrl);
7056
+ setViewState("active");
7057
+ return;
7058
+ }
7059
+ connectToDashboard(deployment);
7060
+ } else if (key.name === "escape") {
7061
+ context.navigateTo("home");
7062
+ }
7063
+ } else if (viewState === "active") {
7064
+ if (key.sequence === "o" || key.sequence === "O") {
7065
+ if (dashboardUrl) openInBrowser(dashboardUrl);
7066
+ } else if (key.sequence === "c" || key.sequence === "C") {
7067
+ if (dashboardUrl) {
7068
+ renderer.copyToClipboardOSC52(dashboardUrl);
7069
+ setCopied(true);
7070
+ setTimeout(() => setCopied(false), 3e3);
7071
+ }
7072
+ } else if (key.sequence === "d" || key.sequence === "D") {
7073
+ if (activeTunnel) {
7074
+ stopTunnel(activeTunnel.deploymentName);
7075
+ setActiveTunnel(null);
7076
+ setDashboardUrl(null);
7077
+ setViewState("selecting");
7078
+ }
7079
+ } else if (key.name === "escape") {
7080
+ setViewState("selecting");
7081
+ }
7082
+ } else if (viewState === "error") {
7083
+ if (key.name === "escape" || key.name === "return") {
7084
+ setViewState("selecting");
7085
+ }
7086
+ }
7087
+ });
7088
+ if (deployedDeployments.length === 0) {
7089
+ return /* @__PURE__ */ jsxs12("box", { flexDirection: "column", width: "100%", padding: 1, children: [
7090
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", marginBottom: 2, children: [
7091
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, children: "/dashboard" }),
7092
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.secondary, children: " - Open OpenClaw dashboard" })
7093
+ ] }),
7094
+ /* @__PURE__ */ jsxs12(
7095
+ "box",
7096
+ {
7097
+ flexDirection: "column",
7098
+ borderStyle: "single",
7099
+ borderColor: t.border.default,
7100
+ padding: 1,
7101
+ children: [
7102
+ /* @__PURE__ */ jsx12("text", { fg: t.status.warning, children: "No deployed instances found!" }),
7103
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.secondary, marginTop: 1, children: "Deploy an instance first with /deploy" })
7104
+ ]
7105
+ }
7106
+ ),
7107
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
7108
+ ] });
7109
+ }
7110
+ if (viewState === "selecting") {
7111
+ const tunnels = getActiveTunnels();
7112
+ return /* @__PURE__ */ jsxs12("box", { flexDirection: "column", width: "100%", padding: 1, children: [
7113
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", marginBottom: 2, children: [
7114
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, children: "/dashboard" }),
7115
+ /* @__PURE__ */ jsxs12("text", { fg: t.fg.secondary, children: [
7116
+ " ",
7117
+ "- Select a deployment to open its dashboard"
7118
+ ] })
7119
+ ] }),
7120
+ tunnels.length > 0 && /* @__PURE__ */ jsxs12(
7121
+ "box",
7122
+ {
7123
+ flexDirection: "column",
7124
+ borderStyle: "single",
7125
+ borderColor: t.status.success,
7126
+ padding: 1,
7127
+ marginBottom: 1,
7128
+ children: [
7129
+ /* @__PURE__ */ jsxs12("text", { fg: t.status.success, children: [
7130
+ tunnels.length,
7131
+ " active tunnel",
7132
+ tunnels.length > 1 ? "s" : ""
7133
+ ] }),
7134
+ tunnels.map((tun) => /* @__PURE__ */ jsxs12("text", { fg: t.fg.secondary, children: [
7135
+ tun.deploymentName,
7136
+ ": 127.0.0.1:",
7137
+ tun.localPort,
7138
+ " \u2192",
7139
+ " ",
7140
+ tun.serverIp,
7141
+ ":",
7142
+ tun.remotePort
7143
+ ] }, tun.deploymentName))
7144
+ ]
7145
+ }
7146
+ ),
7147
+ /* @__PURE__ */ jsx12(
7148
+ "box",
7149
+ {
7150
+ flexDirection: "column",
7151
+ borderStyle: "single",
7152
+ borderColor: t.border.default,
7153
+ padding: 1,
7154
+ marginBottom: 1,
7155
+ children: deployedDeployments.map((deployment, index) => {
7156
+ const isSelected = index === selectedIndex;
7157
+ const hasTunnel = getTunnel(deployment.config.name) !== null;
7158
+ return /* @__PURE__ */ jsxs12(
7159
+ "box",
7160
+ {
7161
+ flexDirection: "row",
7162
+ backgroundColor: isSelected ? t.selection.bg : void 0,
7163
+ children: [
7164
+ /* @__PURE__ */ jsx12("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
7165
+ /* @__PURE__ */ jsx12(
7166
+ "text",
7167
+ {
7168
+ fg: isSelected ? t.selection.fg : t.fg.primary,
7169
+ width: 25,
7170
+ children: deployment.config.name
7171
+ }
7172
+ ),
7173
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, width: 18, children: deployment.state.serverIp }),
7174
+ hasTunnel && /* @__PURE__ */ jsx12("text", { fg: t.status.success, children: "[tunnel active]" })
7175
+ ]
7176
+ },
7177
+ deployment.config.name
7178
+ );
7179
+ })
7180
+ }
7181
+ ),
7182
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.muted, children: "Arrow keys to select | Enter to connect | Esc to go back" })
7183
+ ] });
7184
+ }
7185
+ if (viewState === "connecting") {
7186
+ return /* @__PURE__ */ jsxs12("box", { flexDirection: "column", width: "100%", padding: 1, children: [
7187
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", marginBottom: 2, children: [
7188
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, children: "/dashboard" }),
7189
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.secondary, children: " - Connecting..." })
7190
+ ] }),
7191
+ /* @__PURE__ */ jsxs12(
7192
+ "box",
7193
+ {
7194
+ flexDirection: "column",
7195
+ borderStyle: "single",
7196
+ borderColor: t.border.focus,
7197
+ padding: 1,
7198
+ children: [
7199
+ /* @__PURE__ */ jsx12("text", { fg: t.status.info, children: connectMessage }),
7200
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.muted, marginTop: 1, children: "This may take a few seconds..." })
7201
+ ]
7202
+ }
7203
+ )
7204
+ ] });
7205
+ }
7206
+ if (viewState === "active" && activeTunnel && dashboardUrl) {
7207
+ return /* @__PURE__ */ jsxs12("box", { flexDirection: "column", width: "100%", padding: 1, children: [
7208
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", marginBottom: 2, children: [
7209
+ /* @__PURE__ */ jsx12("text", { fg: t.status.success, children: "/dashboard" }),
7210
+ /* @__PURE__ */ jsxs12("text", { fg: t.fg.secondary, children: [
7211
+ " ",
7212
+ "- ",
7213
+ activeTunnel.deploymentName
7214
+ ] })
7215
+ ] }),
7216
+ /* @__PURE__ */ jsxs12(
7217
+ "box",
7218
+ {
7219
+ flexDirection: "column",
7220
+ borderStyle: "double",
7221
+ borderColor: t.status.success,
7222
+ padding: 1,
7223
+ marginBottom: 1,
7224
+ children: [
7225
+ /* @__PURE__ */ jsx12("text", { fg: t.status.success, children: "Dashboard Ready" }),
7226
+ /* @__PURE__ */ jsxs12("text", { fg: t.fg.primary, marginTop: 1, children: [
7227
+ "Tunnel: 127.0.0.1:",
7228
+ activeTunnel.localPort,
7229
+ " \u2192",
7230
+ " ",
7231
+ activeTunnel.serverIp,
7232
+ ":",
7233
+ activeTunnel.remotePort
7234
+ ] })
7235
+ ]
7236
+ }
7237
+ ),
7238
+ /* @__PURE__ */ jsxs12(
7239
+ "box",
7240
+ {
7241
+ flexDirection: "column",
7242
+ borderStyle: "single",
7243
+ borderColor: t.accent,
7244
+ padding: 1,
7245
+ marginBottom: 1,
7246
+ children: [
7247
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.secondary, children: "Dashboard URL:" }),
7248
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, marginTop: 1, children: dashboardUrl })
7249
+ ]
7250
+ }
7251
+ ),
7252
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "column", marginBottom: 1, children: [
7253
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", children: [
7254
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, width: 4, children: "O" }),
7255
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.primary, children: "Open in browser" })
7256
+ ] }),
7257
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", children: [
7258
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, width: 4, children: "C" }),
7259
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.primary, children: "Copy URL to clipboard" })
7260
+ ] }),
7261
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", children: [
7262
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, width: 4, children: "D" }),
7263
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.primary, children: "Disconnect tunnel" })
7264
+ ] }),
7265
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", children: [
7266
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, width: 4, children: "Esc" }),
7267
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.primary, children: "Back to selection (tunnel stays active)" })
7268
+ ] })
7269
+ ] }),
7270
+ copied && /* @__PURE__ */ jsx12("text", { fg: t.status.success, children: "URL copied to clipboard!" })
7271
+ ] });
7272
+ }
7273
+ if (viewState === "error") {
7274
+ return /* @__PURE__ */ jsxs12("box", { flexDirection: "column", width: "100%", padding: 1, children: [
7275
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", marginBottom: 2, children: [
7276
+ /* @__PURE__ */ jsx12("text", { fg: t.status.error, children: "/dashboard" }),
7277
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.secondary, children: " - Connection failed" })
7278
+ ] }),
7279
+ /* @__PURE__ */ jsx12(
7280
+ "box",
7281
+ {
7282
+ flexDirection: "column",
7283
+ borderStyle: "single",
7284
+ borderColor: t.status.error,
7285
+ padding: 1,
7286
+ marginBottom: 1,
7287
+ children: /* @__PURE__ */ jsx12("text", { fg: t.status.error, children: error })
7288
+ }
7289
+ ),
7290
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.muted, children: "Press Enter or Esc to go back" })
7291
+ ] });
7292
+ }
7293
+ return null;
7294
+ }
7295
+
6338
7296
  // src/App.tsx
6339
- import { jsx as jsx11 } from "@opentui/react/jsx-runtime";
7297
+ import { jsx as jsx13 } from "@opentui/react/jsx-runtime";
6340
7298
  function App() {
6341
- const renderer = useRenderer();
6342
- const [currentView, setCurrentView] = useState10("home");
6343
- const [selectedDeployment, setSelectedDeployment] = useState10(null);
6344
- const [deployments, setDeployments] = useState10(() => {
7299
+ const renderer = useRenderer2();
7300
+ const [currentView, setCurrentView] = useState12("home");
7301
+ const [selectedDeployment, setSelectedDeployment] = useState12(null);
7302
+ const [deployments, setDeployments] = useState12(() => {
6345
7303
  try {
6346
7304
  return getAllDeployments();
6347
7305
  } catch {
6348
7306
  return [];
6349
7307
  }
6350
7308
  });
6351
- const [selectedTemplate, setSelectedTemplate] = useState10(null);
6352
- const wasDraggingRef = useRef5(false);
6353
- const handleMouseDrag = useCallback3(() => {
7309
+ const [selectedTemplate, setSelectedTemplate] = useState12(null);
7310
+ const [editingDeployment, setEditingDeployment] = useState12(null);
7311
+ const wasDraggingRef = useRef6(false);
7312
+ const handleMouseDrag = useCallback4(() => {
6354
7313
  wasDraggingRef.current = true;
6355
7314
  }, []);
6356
- const handleMouseUp = useCallback3(() => {
7315
+ const handleMouseUp = useCallback4(() => {
6357
7316
  if (!wasDraggingRef.current) return;
6358
7317
  wasDraggingRef.current = false;
6359
7318
  const selection = renderer.getSelection();
@@ -6364,14 +7323,14 @@ function App() {
6364
7323
  }
6365
7324
  }
6366
7325
  }, [renderer]);
6367
- const refreshDeployments = useCallback3(() => {
7326
+ const refreshDeployments = useCallback4(() => {
6368
7327
  try {
6369
7328
  setDeployments(getAllDeployments());
6370
7329
  } catch {
6371
7330
  setDeployments([]);
6372
7331
  }
6373
7332
  }, []);
6374
- const navigateTo = useCallback3((view, deployment) => {
7333
+ const navigateTo = useCallback4((view, deployment) => {
6375
7334
  if (deployment !== void 0) {
6376
7335
  setSelectedDeployment(deployment);
6377
7336
  }
@@ -6384,35 +7343,41 @@ function App() {
6384
7343
  deployments,
6385
7344
  refreshDeployments,
6386
7345
  selectedTemplate,
6387
- setSelectedTemplate
7346
+ setSelectedTemplate,
7347
+ editingDeployment,
7348
+ setEditingDeployment
6388
7349
  };
6389
7350
  const renderView = () => {
6390
7351
  switch (currentView) {
6391
7352
  case "home":
6392
- return /* @__PURE__ */ jsx11(Home, { context });
7353
+ return /* @__PURE__ */ jsx13(Home, { context });
6393
7354
  case "new":
6394
- return /* @__PURE__ */ jsx11(NewDeployment, { context });
7355
+ return /* @__PURE__ */ jsx13(NewDeployment, { context });
7356
+ case "list":
7357
+ return /* @__PURE__ */ jsx13(ListView, { context });
6395
7358
  case "deploy":
6396
- return /* @__PURE__ */ jsx11(DeployView, { context });
7359
+ return /* @__PURE__ */ jsx13(DeployView, { context });
6397
7360
  case "deploying":
6398
- return /* @__PURE__ */ jsx11(DeployingView, { context });
7361
+ return /* @__PURE__ */ jsx13(DeployingView, { context });
6399
7362
  case "status":
6400
- return /* @__PURE__ */ jsx11(StatusView, { context });
7363
+ return /* @__PURE__ */ jsx13(StatusView, { context });
6401
7364
  case "ssh":
6402
- return /* @__PURE__ */ jsx11(SSHView, { context });
7365
+ return /* @__PURE__ */ jsx13(SSHView, { context });
6403
7366
  case "logs":
6404
- return /* @__PURE__ */ jsx11(LogsView, { context });
7367
+ return /* @__PURE__ */ jsx13(LogsView, { context });
7368
+ case "dashboard":
7369
+ return /* @__PURE__ */ jsx13(DashboardView, { context });
6405
7370
  case "destroy":
6406
- return /* @__PURE__ */ jsx11(DestroyView, { context });
7371
+ return /* @__PURE__ */ jsx13(DestroyView, { context });
6407
7372
  case "help":
6408
- return /* @__PURE__ */ jsx11(HelpView, { context });
7373
+ return /* @__PURE__ */ jsx13(HelpView, { context });
6409
7374
  case "templates":
6410
- return /* @__PURE__ */ jsx11(TemplatesView, { context });
7375
+ return /* @__PURE__ */ jsx13(TemplatesView, { context });
6411
7376
  default:
6412
- return /* @__PURE__ */ jsx11(Home, { context });
7377
+ return /* @__PURE__ */ jsx13(Home, { context });
6413
7378
  }
6414
7379
  };
6415
- return /* @__PURE__ */ jsx11(
7380
+ return /* @__PURE__ */ jsx13(
6416
7381
  "scrollbox",
6417
7382
  {
6418
7383
  width: "100%",
@@ -6439,7 +7404,7 @@ function App() {
6439
7404
  }
6440
7405
 
6441
7406
  // src/index.tsx
6442
- import { jsx as jsx12 } from "@opentui/react/jsx-runtime";
7407
+ import { jsx as jsx14 } from "@opentui/react/jsx-runtime";
6443
7408
  var args = process.argv.slice(2);
6444
7409
  if (args.includes("--version") || args.includes("-v")) {
6445
7410
  const require2 = createRequire(import.meta.url);
@@ -6473,6 +7438,6 @@ async function main() {
6473
7438
  useMouse: true
6474
7439
  });
6475
7440
  const root = createRoot(renderer);
6476
- root.render(/* @__PURE__ */ jsx12(App, {}));
7441
+ root.render(/* @__PURE__ */ jsx14(App, {}));
6477
7442
  }
6478
7443
  main().catch(console.error);