clawcontrol 0.1.5 → 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.
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  // src/index.tsx
2
+ import { createRequire } from "module";
2
3
  import { createCliRenderer } from "@opentui/core";
3
4
  import { createRoot } from "@opentui/react";
4
5
 
5
6
  // src/App.tsx
6
- import { useState as useState10, useCallback as useCallback3, useRef as useRef5 } from "react";
7
- 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";
8
9
 
9
10
  // src/components/Home.tsx
10
11
  import { useState } from "react";
@@ -141,10 +142,12 @@ var LOGO = `
141
142
  `;
142
143
  var COMMANDS = [
143
144
  { name: "/new", description: "Initialize a new deployment" },
145
+ { name: "/list", description: "List, edit, or fork deployments" },
144
146
  { name: "/deploy", description: "Deploy an initialized configuration" },
145
147
  { name: "/status", description: "View deployment status" },
146
148
  { name: "/ssh", description: "SSH into a deployment" },
147
149
  { name: "/logs", description: "View deployment logs" },
150
+ { name: "/dashboard", description: "Open OpenClaw dashboard in browser" },
148
151
  { name: "/destroy", description: "Destroy a deployment" },
149
152
  { name: "/templates", description: "Manage deployment templates" },
150
153
  { name: "/help", description: "Show help" }
@@ -157,10 +160,12 @@ function Home({ context }) {
157
160
  setError(null);
158
161
  const viewMap = {
159
162
  "/new": "new",
163
+ "/list": "list",
160
164
  "/deploy": "deploy",
161
165
  "/status": "status",
162
166
  "/ssh": "ssh",
163
167
  "/logs": "logs",
168
+ "/dashboard": "dashboard",
164
169
  "/destroy": "destroy",
165
170
  "/templates": "templates",
166
171
  "/help": "help"
@@ -222,7 +227,7 @@ function Home({ context }) {
222
227
  children: [
223
228
  /* @__PURE__ */ jsx("text", { fg: t.fg.primary, children: "Available Commands" }),
224
229
  /* @__PURE__ */ jsx("box", { flexDirection: "column", marginTop: 1, children: COMMANDS.map((cmd) => /* @__PURE__ */ jsxs("box", { flexDirection: "row", children: [
225
- /* @__PURE__ */ jsx("text", { fg: t.accent, width: 12, children: cmd.name }),
230
+ /* @__PURE__ */ jsx("text", { fg: t.accent, width: 14, children: cmd.name }),
226
231
  /* @__PURE__ */ jsx("text", { fg: t.fg.secondary, children: cmd.description })
227
232
  ] }, cmd.name)) })
228
233
  ]
@@ -371,7 +376,8 @@ var DeploymentConfigSchema = z.object({
371
376
  hetzner: HetznerConfigSchema.optional(),
372
377
  digitalocean: DigitalOceanConfigSchema.optional(),
373
378
  openclawConfig: OpenClawConfigSchema,
374
- openclawAgent: OpenClawAgentConfigSchema.optional()
379
+ openclawAgent: OpenClawAgentConfigSchema.optional(),
380
+ skipTailscale: z.boolean().optional()
375
381
  });
376
382
  var DeploymentStateSchema = z.object({
377
383
  status: DeploymentStatusSchema,
@@ -387,6 +393,7 @@ var DeploymentStateSchema = z.object({
387
393
  retryCount: z.number()
388
394
  })
389
395
  ),
396
+ gatewayToken: z.string().optional(),
390
397
  lastError: z.string().optional(),
391
398
  deployedAt: z.string().optional(),
392
399
  updatedAt: z.string()
@@ -530,6 +537,14 @@ function deleteDeployment(name) {
530
537
  }
531
538
  rmSync(deploymentDir, { recursive: true, force: true });
532
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
+ }
533
548
  function validateDeploymentName(name) {
534
549
  if (!name || name.trim() === "") {
535
550
  return { valid: false, error: "Deployment name cannot be empty" };
@@ -1195,20 +1210,36 @@ var DO_DROPLET_SIZES = [
1195
1210
  { slug: "s-4vcpu-8gb", label: "4 vCPU, 8GB RAM, 160GB SSD", price: "$48/mo" },
1196
1211
  { slug: "s-8vcpu-16gb", label: "8 vCPU, 16GB RAM, 320GB SSD", price: "$96/mo" }
1197
1212
  ];
1198
- 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
+ }
1199
1230
  const base = ["template_choice"];
1200
1231
  if (!activeTemplate) {
1201
1232
  base.push("name", "provider", "api_key_choose");
1202
1233
  if (provider === "digitalocean") {
1203
1234
  base.push("droplet_size");
1204
1235
  }
1205
- 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");
1206
1237
  } else {
1207
1238
  base.push("name", "api_key_choose");
1208
1239
  if (activeTemplate.provider === "digitalocean") {
1209
1240
  base.push("droplet_size");
1210
1241
  }
1211
- 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");
1212
1243
  }
1213
1244
  return base;
1214
1245
  }
@@ -1235,19 +1266,39 @@ function resolveStepForProgress(s) {
1235
1266
  }
1236
1267
  }
1237
1268
  function NewDeployment({ context }) {
1269
+ const [editingConfig, setEditingConfig] = useState2(null);
1270
+ const [editMode, setEditMode] = useState2(null);
1238
1271
  const [templateChoices, setTemplateChoices] = useState2([]);
1239
1272
  const [selectedTemplateIndex, setSelectedTemplateIndex] = useState2(0);
1240
1273
  const [activeTemplate, setActiveTemplate] = useState2(null);
1241
1274
  const [step, setStep] = useState2(() => {
1275
+ if (context.editingDeployment) {
1276
+ return context.editingDeployment.mode === "edit" ? "api_key_choose" : "name";
1277
+ }
1242
1278
  if (context.selectedTemplate) return "name";
1243
1279
  return "template_choice";
1244
1280
  });
1245
- const [name, setName] = useState2("");
1281
+ const [name, setName] = useState2(() => {
1282
+ if (context.editingDeployment?.mode === "edit") return context.editingDeployment.config.name;
1283
+ return "";
1284
+ });
1246
1285
  const [provider, setProvider] = useState2(() => {
1286
+ if (context.editingDeployment) return context.editingDeployment.config.provider;
1247
1287
  return context.selectedTemplate?.provider ?? "hetzner";
1248
1288
  });
1249
- 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
+ });
1250
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
+ }
1251
1302
  if (context.selectedTemplate?.digitalocean) {
1252
1303
  const idx = DO_DROPLET_SIZES.findIndex((s) => s.slug === context.selectedTemplate.digitalocean.size);
1253
1304
  return idx >= 0 ? idx : 0;
@@ -1255,17 +1306,40 @@ function NewDeployment({ context }) {
1255
1306
  return 0;
1256
1307
  });
1257
1308
  const [aiProvider, setAiProvider] = useState2(() => {
1309
+ if (context.editingDeployment?.config.openclawAgent) return context.editingDeployment.config.openclawAgent.aiProvider;
1258
1310
  return context.selectedTemplate?.aiProvider ?? "";
1259
1311
  });
1260
- const [aiApiKey, setAiApiKey] = useState2("");
1312
+ const [aiApiKey, setAiApiKey] = useState2(() => {
1313
+ if (context.editingDeployment?.config.openclawAgent) return context.editingDeployment.config.openclawAgent.aiApiKey;
1314
+ return "";
1315
+ });
1261
1316
  const [model, setModel] = useState2(() => {
1317
+ if (context.editingDeployment?.config.openclawAgent) return context.editingDeployment.config.openclawAgent.model;
1262
1318
  return context.selectedTemplate?.model ?? "";
1263
1319
  });
1264
- const [telegramBotToken, setTelegramBotToken] = useState2("");
1265
- 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
+ });
1266
1336
  const [error, setError] = useState2(null);
1267
1337
  const [isValidating, setIsValidating] = useState2(false);
1268
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
+ }
1269
1343
  if (context.selectedTemplate) {
1270
1344
  const idx = SUPPORTED_PROVIDERS.indexOf(context.selectedTemplate.provider);
1271
1345
  return idx >= 0 ? idx : 0;
@@ -1273,6 +1347,10 @@ function NewDeployment({ context }) {
1273
1347
  return 0;
1274
1348
  });
1275
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
+ }
1276
1354
  if (context.selectedTemplate) {
1277
1355
  const idx = AI_PROVIDERS.findIndex((p) => p.name === context.selectedTemplate.aiProvider);
1278
1356
  return idx >= 0 ? idx : 0;
@@ -1285,12 +1363,39 @@ function NewDeployment({ context }) {
1285
1363
  const [savedTelegramUsers, setSavedTelegramUsers] = useState2([]);
1286
1364
  const [selectedSavedKeyIndex, setSelectedSavedKeyIndex] = useState2(0);
1287
1365
  const [newKeyName, setNewKeyName] = useState2("");
1288
- const [apiKeyFromSaved, setApiKeyFromSaved] = useState2(false);
1289
- const [aiApiKeyFromSaved, setAiApiKeyFromSaved] = useState2(false);
1290
- const [telegramTokenFromSaved, setTelegramTokenFromSaved] = useState2(false);
1291
- 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);
1292
1370
  useEffect(() => {
1293
- 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) {
1294
1399
  setActiveTemplate(context.selectedTemplate);
1295
1400
  context.setSelectedTemplate(null);
1296
1401
  }
@@ -1335,6 +1440,8 @@ function NewDeployment({ context }) {
1335
1440
  step,
1336
1441
  selectedDropletSizeIndex,
1337
1442
  activeTemplate,
1443
+ editingConfig,
1444
+ editMode,
1338
1445
  savedProviderKeys,
1339
1446
  savedAiKeys,
1340
1447
  savedTelegramTokens,
@@ -1344,7 +1451,9 @@ function NewDeployment({ context }) {
1344
1451
  apiKeyFromSaved,
1345
1452
  aiApiKeyFromSaved,
1346
1453
  telegramTokenFromSaved,
1347
- telegramAllowFromSaved
1454
+ telegramAllowFromSaved,
1455
+ enableTailscale,
1456
+ selectedTailscaleIndex
1348
1457
  });
1349
1458
  stateRef.current = {
1350
1459
  name,
@@ -1358,6 +1467,8 @@ function NewDeployment({ context }) {
1358
1467
  step,
1359
1468
  selectedDropletSizeIndex,
1360
1469
  activeTemplate,
1470
+ editingConfig,
1471
+ editMode,
1361
1472
  savedProviderKeys,
1362
1473
  savedAiKeys,
1363
1474
  savedTelegramTokens,
@@ -1367,10 +1478,12 @@ function NewDeployment({ context }) {
1367
1478
  apiKeyFromSaved,
1368
1479
  aiApiKeyFromSaved,
1369
1480
  telegramTokenFromSaved,
1370
- telegramAllowFromSaved
1481
+ telegramAllowFromSaved,
1482
+ enableTailscale,
1483
+ selectedTailscaleIndex
1371
1484
  };
1372
1485
  debugLog(`RENDER: step=${step}, apiKey.length=${apiKey?.length ?? "null"}`);
1373
- const stepList = getStepList(provider, activeTemplate);
1486
+ const stepList = getStepList(provider, activeTemplate, editMode ?? void 0);
1374
1487
  const handleConfirmFromRef = () => {
1375
1488
  const s = stateRef.current;
1376
1489
  debugLog(`handleConfirmFromRef CALLED: apiKey.length=${s.apiKey?.length ?? "null"}`);
@@ -1409,7 +1522,7 @@ function NewDeployment({ context }) {
1409
1522
  const config = {
1410
1523
  name: s.name,
1411
1524
  provider: s.provider,
1412
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1525
+ createdAt: s.editingConfig?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1413
1526
  hetzner: s.provider === "hetzner" ? {
1414
1527
  apiKey: s.apiKey,
1415
1528
  serverType: tmpl?.hetzner?.serverType ?? "cpx11",
@@ -1423,6 +1536,7 @@ function NewDeployment({ context }) {
1423
1536
  image: tmpl?.digitalocean?.image ?? "ubuntu-24-04-x64"
1424
1537
  } : void 0,
1425
1538
  openclawConfig: void 0,
1539
+ skipTailscale: !s.enableTailscale,
1426
1540
  openclawAgent: {
1427
1541
  aiProvider: s.aiProvider,
1428
1542
  aiApiKey: s.aiApiKey,
@@ -1432,11 +1546,16 @@ function NewDeployment({ context }) {
1432
1546
  telegramAllowFrom: s.telegramAllowFrom
1433
1547
  }
1434
1548
  };
1435
- 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
+ }
1436
1555
  context.refreshDeployments();
1437
1556
  setStep("complete");
1438
1557
  } catch (err) {
1439
- 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)}`);
1440
1559
  }
1441
1560
  };
1442
1561
  useKeyboard((key) => {
@@ -1628,7 +1747,7 @@ function NewDeployment({ context }) {
1628
1747
  setTelegramAllowFrom(saved.value);
1629
1748
  setTelegramAllowFromSaved(true);
1630
1749
  setError(null);
1631
- setStep("confirm");
1750
+ setStep(currentState.editMode === "edit" ? "confirm" : "tailscale");
1632
1751
  }
1633
1752
  } else if (key.name === "escape") {
1634
1753
  setStep("telegram_token_choose");
@@ -1665,13 +1784,26 @@ function NewDeployment({ context }) {
1665
1784
  setStep("telegram_allow_name");
1666
1785
  setNewKeyName("");
1667
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);
1668
1798
  setStep("confirm");
1799
+ } else if (key.name === "escape") {
1800
+ setStep("telegram_allow_choose");
1669
1801
  }
1670
1802
  } else if (currentState.step === "confirm") {
1671
1803
  if (key.name === "y" || key.name === "return") {
1672
1804
  handleConfirmFromRef();
1673
1805
  } else if (key.name === "n" || key.name === "escape") {
1674
- setStep("telegram_allow_choose");
1806
+ setStep(currentState.editMode === "edit" ? "telegram_allow_choose" : "tailscale");
1675
1807
  }
1676
1808
  } else if (currentState.step === "complete") {
1677
1809
  context.navigateTo("home");
@@ -1793,7 +1925,7 @@ function NewDeployment({ context }) {
1793
1925
  }
1794
1926
  setError(null);
1795
1927
  if (telegramAllowFromSaved) {
1796
- setStep("confirm");
1928
+ setStep(editMode === "edit" ? "confirm" : "tailscale");
1797
1929
  } else {
1798
1930
  setStep("telegram_allow_save");
1799
1931
  }
@@ -2507,10 +2639,10 @@ function NewDeployment({ context }) {
2507
2639
  setNewKeyName(value);
2508
2640
  }
2509
2641
  },
2510
- onSubmit: () => handleSaveKeyName("telegram-user", telegramAllowFrom, "confirm"),
2642
+ onSubmit: () => handleSaveKeyName("telegram-user", telegramAllowFrom, editMode === "edit" ? "confirm" : "tailscale"),
2511
2643
  onKeyDown: (e) => {
2512
2644
  if (e.name === "escape") {
2513
- setStep("confirm");
2645
+ setStep(editMode === "edit" ? "confirm" : "tailscale");
2514
2646
  }
2515
2647
  }
2516
2648
  }
@@ -2518,6 +2650,41 @@ function NewDeployment({ context }) {
2518
2650
  error && /* @__PURE__ */ jsx2("text", { fg: t.status.error, marginTop: 1, children: error }),
2519
2651
  /* @__PURE__ */ jsx2("text", { fg: t.fg.muted, marginTop: 2, children: "Press Enter to save, Esc to skip" })
2520
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
+ }
2521
2688
  case "confirm":
2522
2689
  return /* @__PURE__ */ jsxs2("box", { flexDirection: "column", children: [
2523
2690
  /* @__PURE__ */ jsxs2("text", { fg: t.accent, children: [
@@ -2577,6 +2744,10 @@ function NewDeployment({ context }) {
2577
2744
  /* @__PURE__ */ jsxs2("box", { flexDirection: "row", children: [
2578
2745
  /* @__PURE__ */ jsx2("text", { fg: t.fg.secondary, width: 20, children: "Allow From:" }),
2579
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" })
2580
2751
  ] })
2581
2752
  ]
2582
2753
  }
@@ -2586,7 +2757,7 @@ function NewDeployment({ context }) {
2586
2757
  ] });
2587
2758
  case "complete":
2588
2759
  return /* @__PURE__ */ jsxs2("box", { flexDirection: "column", children: [
2589
- /* @__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!" }),
2590
2761
  /* @__PURE__ */ jsxs2(
2591
2762
  "box",
2592
2763
  {
@@ -2596,14 +2767,10 @@ function NewDeployment({ context }) {
2596
2767
  padding: 1,
2597
2768
  marginTop: 1,
2598
2769
  children: [
2599
- /* @__PURE__ */ jsxs2("text", { fg: t.fg.primary, children: [
2600
- 'Your deployment "',
2601
- name,
2602
- '" has been initialized.'
2603
- ] }),
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.` }),
2604
2771
  /* @__PURE__ */ jsxs2("text", { fg: t.fg.secondary, marginTop: 1, children: [
2605
2772
  "Configuration saved to: ~/.clawcontrol/deployments/",
2606
- name,
2773
+ editingConfig?.name ?? name,
2607
2774
  "/"
2608
2775
  ] }),
2609
2776
  /* @__PURE__ */ jsxs2("text", { fg: t.fg.secondary, marginTop: 1, children: [
@@ -2620,15 +2787,15 @@ function NewDeployment({ context }) {
2620
2787
  ]
2621
2788
  }
2622
2789
  ),
2623
- /* @__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" }),
2624
2791
  /* @__PURE__ */ jsx2("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
2625
2792
  ] });
2626
2793
  }
2627
2794
  };
2628
2795
  return /* @__PURE__ */ jsxs2("box", { flexDirection: "column", width: "100%", padding: 1, children: [
2629
2796
  /* @__PURE__ */ jsxs2("box", { flexDirection: "row", marginBottom: 2, children: [
2630
- /* @__PURE__ */ jsx2("text", { fg: t.accent, children: "/new" }),
2631
- /* @__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" })
2632
2799
  ] }),
2633
2800
  /* @__PURE__ */ jsx2("box", { flexDirection: "row", marginBottom: 2, children: stepList.map((s, i) => {
2634
2801
  const resolvedStep = resolveStepForProgress(step);
@@ -2643,46 +2810,81 @@ function NewDeployment({ context }) {
2643
2810
  ] });
2644
2811
  }
2645
2812
 
2646
- // src/components/DeployView.tsx
2647
- import { useState as useState3 } from "react";
2813
+ // src/components/ListView.tsx
2814
+ import { useState as useState3, useRef as useRef2 } from "react";
2648
2815
  import { useKeyboard as useKeyboard2 } from "@opentui/react";
2649
- import { jsx as jsx3, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
2650
- 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");
2651
2819
  const [selectedIndex, setSelectedIndex] = useState3(0);
2652
- const [confirmMode, setConfirmMode] = useState3(false);
2820
+ const [confirmText, setConfirmText] = useState3("");
2821
+ const [error, setError] = useState3(null);
2822
+ const [deletedName, setDeletedName] = useState3("");
2653
2823
  const deployments = context.deployments;
2654
- const notDeployed = deployments.filter((d) => d.state.status !== "deployed");
2655
- const deployed = deployments.filter((d) => d.state.status === "deployed");
2656
- const allDeployments = [...notDeployed, ...deployed];
2657
- const selectedDeployment = allDeployments[selectedIndex];
2824
+ const selectedDeployment = deployments[selectedIndex];
2825
+ const stateRef = useRef2({ viewState, selectedIndex });
2826
+ stateRef.current = { viewState, selectedIndex };
2658
2827
  useKeyboard2((key) => {
2659
- if (allDeployments.length === 0) {
2828
+ const current = stateRef.current;
2829
+ if (deployments.length === 0) {
2660
2830
  context.navigateTo("home");
2661
2831
  return;
2662
2832
  }
2663
- if (confirmMode) {
2664
- if (key.name === "y" || key.name === "return") {
2665
- context.navigateTo("deploying", selectedDeployment.config.name);
2666
- } else if (key.name === "n" || key.name === "escape") {
2667
- setConfirmMode(false);
2668
- }
2669
- } else {
2670
- if (key.name === "up" && selectedIndex > 0) {
2671
- setSelectedIndex(selectedIndex - 1);
2672
- } else if (key.name === "down" && selectedIndex < allDeployments.length - 1) {
2673
- 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);
2674
2838
  } else if (key.name === "return") {
2675
- setConfirmMode(true);
2839
+ setViewState("detail");
2676
2840
  } else if (key.name === "escape") {
2677
2841
  context.navigateTo("home");
2678
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");
2679
2869
  }
2680
2870
  });
2681
- 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) {
2682
2884
  return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
2683
2885
  /* @__PURE__ */ jsxs3("box", { flexDirection: "row", marginBottom: 2, children: [
2684
- /* @__PURE__ */ jsx3("text", { fg: t.accent, children: "/deploy" }),
2685
- /* @__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" })
2686
2888
  ] }),
2687
2889
  /* @__PURE__ */ jsxs3(
2688
2890
  "box",
@@ -2693,157 +2895,432 @@ function DeployView({ context }) {
2693
2895
  padding: 1,
2694
2896
  children: [
2695
2897
  /* @__PURE__ */ jsx3("text", { fg: t.status.warning, children: "No deployments found!" }),
2696
- /* @__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." })
2697
2899
  ]
2698
2900
  }
2699
2901
  ),
2700
2902
  /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
2701
2903
  ] });
2702
2904
  }
2703
- if (confirmMode) {
2905
+ if (viewState === "listing") {
2704
2906
  return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
2705
2907
  /* @__PURE__ */ jsxs3("box", { flexDirection: "row", marginBottom: 2, children: [
2706
- /* @__PURE__ */ jsx3("text", { fg: t.accent, children: "/deploy" }),
2707
- /* @__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
+ ] })
2708
2914
  ] }),
2709
- /* @__PURE__ */ jsxs3(
2915
+ /* @__PURE__ */ jsx3(
2710
2916
  "box",
2711
2917
  {
2712
2918
  flexDirection: "column",
2713
2919
  borderStyle: "single",
2714
2920
  borderColor: t.border.default,
2715
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,
2716
2967
  children: [
2717
- /* @__PURE__ */ jsxs3("text", { fg: t.status.warning, children: [
2718
- 'Deploy "',
2719
- selectedDeployment.config.name,
2720
- '"?'
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 })
2721
2972
  ] }),
2722
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, marginTop: 1, children: "This will:" }),
2723
- /* @__PURE__ */ jsxs3("text", { fg: t.fg.primary, children: [
2724
- "\u2022 Create a VPS on ",
2725
- 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 })
2726
2992
  ] }),
2727
- /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: "\u2022 Install and configure OpenClaw" }),
2728
- /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: "\u2022 Set up Tailscale for secure access" }),
2729
- /* @__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
+ ] })
2730
3011
  ]
2731
3012
  }
2732
3013
  ),
2733
- /* @__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" })
2734
3021
  ] });
2735
3022
  }
2736
- return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
2737
- /* @__PURE__ */ jsxs3("box", { flexDirection: "row", marginBottom: 2, children: [
2738
- /* @__PURE__ */ jsx3("text", { fg: t.accent, children: "/deploy" }),
2739
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, children: " - Select a deployment to deploy" })
2740
- ] }),
2741
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, marginBottom: 1, children: "Use arrow keys to select, Enter to deploy:" }),
2742
- /* @__PURE__ */ jsx3(
2743
- "box",
2744
- {
2745
- flexDirection: "column",
2746
- borderStyle: "single",
2747
- borderColor: t.border.default,
2748
- padding: 1,
2749
- children: allDeployments.map((deployment, index) => {
2750
- const isSelected = index === selectedIndex;
2751
- return /* @__PURE__ */ jsxs3(
2752
- "box",
2753
- {
2754
- flexDirection: "row",
2755
- backgroundColor: isSelected ? t.selection.bg : void 0,
2756
- children: [
2757
- /* @__PURE__ */ jsx3("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
2758
- /* @__PURE__ */ jsx3("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
2759
- /* @__PURE__ */ jsxs3("text", { fg: statusColor(deployment.state.status), width: 15, children: [
2760
- "[",
2761
- deployment.state.status,
2762
- "]"
2763
- ] }),
2764
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, children: deployment.config.provider })
2765
- ]
2766
- },
2767
- deployment.config.name
2768
- );
2769
- })
2770
- }
2771
- ),
2772
- /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, marginTop: 2, children: "Press Esc to go back" })
2773
- ] });
2774
- }
2775
-
2776
- // src/components/DeployingView.tsx
2777
- import { useState as useState4, useEffect as useEffect2, useCallback, useRef as useRef2 } from "react";
2778
- import { useKeyboard as useKeyboard3 } from "@opentui/react";
2779
- import open from "open";
2780
-
2781
- // src/services/ssh.ts
2782
- import { Client } from "ssh2";
2783
- import { generateKeyPairSync, randomBytes } from "crypto";
2784
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, chmodSync, existsSync as existsSync4 } from "fs";
2785
- import { mkdirSync as mkdirSync4 } from "fs";
2786
- function generateSSHKeyPair(comment = "clawcontrol") {
2787
- const { privateKey, publicKey } = generateKeyPairSync("ed25519", {
2788
- publicKeyEncoding: {
2789
- type: "spki",
2790
- format: "pem"
2791
- },
2792
- privateKeyEncoding: {
2793
- type: "pkcs8",
2794
- format: "pem"
2795
- }
2796
- });
2797
- const pubKeyDer = extractDERFromPEM(publicKey);
2798
- const privKeyDer = extractDERFromPEM(privateKey);
2799
- const publicKeyBytes = pubKeyDer.slice(-32);
2800
- const privateKeyBytes = privKeyDer.slice(-32);
2801
- const publicKeyOpenSSH = buildOpenSSHPublicKey(publicKeyBytes, comment);
2802
- const privateKeyOpenSSH = buildOpenSSHPrivateKey(privateKeyBytes, publicKeyBytes, comment);
2803
- return {
2804
- privateKey: privateKeyOpenSSH,
2805
- publicKey: publicKeyOpenSSH
2806
- };
2807
- }
2808
- function extractDERFromPEM(pem) {
2809
- const lines = pem.split("\n").filter((line) => !line.startsWith("-----") && line.trim() !== "");
2810
- return Buffer.from(lines.join(""), "base64");
2811
- }
2812
- function buildOpenSSHPublicKey(publicKeyBytes, comment) {
2813
- const keyType = Buffer.from("ssh-ed25519");
2814
- const keyTypeLen = Buffer.alloc(4);
2815
- keyTypeLen.writeUInt32BE(keyType.length);
2816
- const keyLen = Buffer.alloc(4);
2817
- keyLen.writeUInt32BE(publicKeyBytes.length);
2818
- const opensshKey = Buffer.concat([keyTypeLen, keyType, keyLen, publicKeyBytes]);
2819
- return `ssh-ed25519 ${opensshKey.toString("base64")} ${comment}`;
2820
- }
2821
- function buildOpenSSHPrivateKey(privateKeyBytes, publicKeyBytes, comment) {
2822
- const AUTH_MAGIC = Buffer.from("openssh-key-v1\0");
2823
- const cipherName = Buffer.from("none");
2824
- const kdfName = Buffer.from("none");
2825
- const kdfOptions = Buffer.alloc(0);
2826
- const numKeys = 1;
2827
- const keyType = Buffer.from("ssh-ed25519");
2828
- const pubKeyBlob = Buffer.concat([
2829
- uint32BE(keyType.length),
2830
- keyType,
2831
- uint32BE(publicKeyBytes.length),
2832
- publicKeyBytes
2833
- ]);
2834
- const checkInt = randomBytes(4);
2835
- const ed25519PrivKey = Buffer.concat([privateKeyBytes, publicKeyBytes]);
2836
- const commentBuf = Buffer.from(comment);
2837
- const privateSection = Buffer.concat([
2838
- checkInt,
2839
- checkInt,
2840
- // Two identical check integers
2841
- uint32BE(keyType.length),
2842
- keyType,
2843
- uint32BE(publicKeyBytes.length),
2844
- publicKeyBytes,
2845
- uint32BE(ed25519PrivKey.length),
2846
- 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,
2847
3324
  uint32BE(commentBuf.length),
2848
3325
  commentBuf
2849
3326
  ]);
@@ -3089,6 +3566,9 @@ async function waitForSSH(host, privateKey, timeoutMs = 18e4, pollIntervalMs = 5
3089
3566
  throw new Error(`SSH not available after ${timeoutMs / 1e3} seconds`);
3090
3567
  }
3091
3568
 
3569
+ // src/services/deployment.ts
3570
+ import { randomBytes as randomBytes2 } from "crypto";
3571
+
3092
3572
  // src/services/setup/index.ts
3093
3573
  async function execOrFail(ssh, command, errorMessage) {
3094
3574
  const result = await ssh.exec(command);
@@ -3240,7 +3720,7 @@ async function installOpenClaw(ssh) {
3240
3720
  throw new Error("OpenClaw installation verification failed");
3241
3721
  }
3242
3722
  }
3243
- async function configureOpenClaw(ssh, customConfig, agentConfig) {
3723
+ async function configureOpenClaw(ssh, customConfig, agentConfig, gatewayToken) {
3244
3724
  await ssh.exec("mkdir -p ~/.openclaw");
3245
3725
  const config = {
3246
3726
  browser: {
@@ -3262,6 +3742,7 @@ async function configureOpenClaw(ssh, customConfig, agentConfig) {
3262
3742
  port: 18789,
3263
3743
  mode: "local",
3264
3744
  bind: "loopback",
3745
+ ...gatewayToken ? { auth: { token: gatewayToken } } : {},
3265
3746
  tailscale: {
3266
3747
  mode: "serve",
3267
3748
  resetOnExit: false
@@ -3509,6 +3990,41 @@ async function getOpenClawLogs(ssh, lines = 100) {
3509
3990
  const result = await ssh.exec(`journalctl -u openclaw -n ${lines} --no-pager`);
3510
3991
  return result.stdout;
3511
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
+ }
3512
4028
 
3513
4029
  // src/services/deployment.ts
3514
4030
  var MAX_RETRIES = 3;
@@ -3607,6 +4123,7 @@ var DeploymentOrchestrator = class {
3607
4123
  onConfirm;
3608
4124
  onOpenUrl;
3609
4125
  onSpawnTerminal;
4126
+ tailscaleSkipped = false;
3610
4127
  constructor(deploymentName, onProgress, onConfirm, onOpenUrl, onSpawnTerminal) {
3611
4128
  this.deploymentName = deploymentName;
3612
4129
  this.deployment = readDeployment(deploymentName);
@@ -3990,18 +4507,28 @@ Would you like to retry from the beginning?`
3990
4507
  const ssh = await this.ensureSSHConnected();
3991
4508
  const customConfig = this.deployment.config.openclawConfig;
3992
4509
  const agentConfig = this.deployment.config.openclawAgent;
3993
- await configureOpenClaw(ssh, customConfig, agentConfig);
4510
+ const gatewayToken = randomBytes2(32).toString("hex");
4511
+ await configureOpenClaw(ssh, customConfig, agentConfig, gatewayToken);
4512
+ updateDeploymentState(this.deploymentName, { gatewayToken });
3994
4513
  if (agentConfig) {
3995
4514
  this.reportProgress("openclaw_configured", "Writing AI provider environment...");
3996
4515
  await writeOpenClawEnvFile(ssh, agentConfig);
3997
4516
  }
3998
4517
  }
3999
4518
  async installTailscale() {
4519
+ if (this.deployment.config.skipTailscale) {
4520
+ this.tailscaleSkipped = true;
4521
+ this.reportProgress("tailscale_installed", "Tailscale setup skipped.");
4522
+ return;
4523
+ }
4000
4524
  const ssh = await this.ensureSSHConnected();
4001
4525
  await installTailscale(ssh);
4002
4526
  }
4003
4527
  async authenticateTailscale() {
4528
+ if (this.tailscaleSkipped) return;
4004
4529
  const ssh = await this.ensureSSHConnected();
4530
+ const check = await ssh.exec("which tailscale 2>/dev/null");
4531
+ if (check.code !== 0) return;
4005
4532
  const authUrl = await getTailscaleAuthUrl(ssh);
4006
4533
  if (authUrl) {
4007
4534
  const confirmed = await this.onConfirm(
@@ -4024,7 +4551,10 @@ URL: ${authUrl}`
4024
4551
  }
4025
4552
  }
4026
4553
  async configureTailscale() {
4554
+ if (this.tailscaleSkipped) return;
4027
4555
  const ssh = await this.ensureSSHConnected();
4556
+ const check = await ssh.exec("which tailscale 2>/dev/null");
4557
+ if (check.code !== 0) return;
4028
4558
  const tailscaleIp = await configureTailscaleServe(ssh);
4029
4559
  updateDeploymentState(this.deploymentName, {
4030
4560
  tailscaleIp
@@ -4263,8 +4793,8 @@ function openGhostty(command) {
4263
4793
  " end tell",
4264
4794
  "end tell"
4265
4795
  ];
4266
- const args = appleScript.flatMap((line) => ["-e", line]);
4267
- const result = spawnSync("osascript", args, { timeout: 1e4, stdio: "pipe" });
4796
+ const args2 = appleScript.flatMap((line) => ["-e", line]);
4797
+ const result = spawnSync("osascript", args2, { timeout: 1e4, stdio: "pipe" });
4268
4798
  if (result.status === 0) {
4269
4799
  return { success: true };
4270
4800
  }
@@ -4509,14 +5039,14 @@ function getTerminalDisplayName(app) {
4509
5039
  }
4510
5040
 
4511
5041
  // src/components/DeployingView.tsx
4512
- 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";
4513
5043
  function DeployingView({ context }) {
4514
- const [deployState, setDeployState] = useState4("deploying");
4515
- const [progress, setProgress] = useState4(null);
4516
- const [logs, setLogs] = useState4([]);
4517
- const [error, setError] = useState4(null);
4518
- const [confirmPrompt, setConfirmPrompt] = useState4(null);
4519
- 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);
4520
5050
  const deploymentName = context.selectedDeployment;
4521
5051
  const addLog = useCallback((message) => {
4522
5052
  setLogs((prev) => [...prev.slice(-20), `[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] ${message}`]);
@@ -4564,9 +5094,9 @@ function DeployingView({ context }) {
4564
5094
  setDeployState("deploying");
4565
5095
  addLog("Terminal session confirmed complete, continuing deployment...");
4566
5096
  }, [terminalResolve, addLog]);
4567
- const stateRef = useRef2({ deployState, terminalResolve });
5097
+ const stateRef = useRef3({ deployState, terminalResolve });
4568
5098
  stateRef.current = { deployState, terminalResolve };
4569
- useKeyboard3((key) => {
5099
+ useKeyboard4((key) => {
4570
5100
  const currentState = stateRef.current;
4571
5101
  if (currentState.deployState === "waiting_terminal") {
4572
5102
  if (key.name === "return") {
@@ -4620,19 +5150,19 @@ function DeployingView({ context }) {
4620
5150
  const width = 40;
4621
5151
  const filled = Math.round(progress.progress / 100 * width);
4622
5152
  const empty = width - filled;
4623
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", marginBottom: 1, children: [
4624
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", children: [
4625
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: "[" }),
4626
- /* @__PURE__ */ jsx4("text", { fg: t.status.success, children: "\u2588".repeat(filled) }),
4627
- /* @__PURE__ */ jsx4("text", { fg: t.fg.muted, children: "\u2591".repeat(empty) }),
4628
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: "]" }),
4629
- /* @__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: [
4630
5160
  " ",
4631
5161
  Math.round(progress.progress),
4632
5162
  "%"
4633
5163
  ] })
4634
5164
  ] }),
4635
- /* @__PURE__ */ jsxs4("text", { fg: t.accent, marginTop: 1, children: [
5165
+ /* @__PURE__ */ jsxs5("text", { fg: t.accent, marginTop: 1, children: [
4636
5166
  "Current: ",
4637
5167
  progress.message
4638
5168
  ] })
@@ -4641,7 +5171,7 @@ function DeployingView({ context }) {
4641
5171
  const renderConfirmDialog = () => {
4642
5172
  if (!confirmPrompt) return null;
4643
5173
  const lines = confirmPrompt.message.split("\n");
4644
- return /* @__PURE__ */ jsxs4(
5174
+ return /* @__PURE__ */ jsxs5(
4645
5175
  "box",
4646
5176
  {
4647
5177
  flexDirection: "column",
@@ -4650,16 +5180,16 @@ function DeployingView({ context }) {
4650
5180
  padding: 1,
4651
5181
  marginBottom: 1,
4652
5182
  children: [
4653
- /* @__PURE__ */ jsx4("text", { fg: t.status.warning, children: "Confirmation Required" }),
4654
- /* @__PURE__ */ jsx4("box", { flexDirection: "column", marginTop: 1, children: lines.map((line, i) => /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: line }, i)) }),
4655
- /* @__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" })
4656
5186
  ]
4657
5187
  }
4658
5188
  );
4659
5189
  };
4660
5190
  const renderWaitingTerminal = () => {
4661
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", flexGrow: 1, children: [
4662
- /* @__PURE__ */ jsxs4(
5191
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", flexGrow: 1, children: [
5192
+ /* @__PURE__ */ jsxs5(
4663
5193
  "box",
4664
5194
  {
4665
5195
  flexDirection: "column",
@@ -4668,12 +5198,12 @@ function DeployingView({ context }) {
4668
5198
  padding: 1,
4669
5199
  marginBottom: 1,
4670
5200
  children: [
4671
- /* @__PURE__ */ jsx4("text", { fg: t.accent, children: "Interactive Setup" }),
4672
- /* @__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." })
4673
5203
  ]
4674
5204
  }
4675
5205
  ),
4676
- /* @__PURE__ */ jsxs4(
5206
+ /* @__PURE__ */ jsxs5(
4677
5207
  "box",
4678
5208
  {
4679
5209
  flexDirection: "column",
@@ -4682,32 +5212,32 @@ function DeployingView({ context }) {
4682
5212
  padding: 1,
4683
5213
  marginBottom: 1,
4684
5214
  children: [
4685
- /* @__PURE__ */ jsx4("text", { fg: t.status.warning, children: "Instructions:" }),
4686
- /* @__PURE__ */ jsxs4("box", { flexDirection: "column", marginTop: 1, children: [
4687
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "1. Complete the setup in the terminal window" }),
4688
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "2. Follow the prompts shown in the terminal" }),
4689
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "3. When done, close the terminal window" }),
4690
- /* @__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" })
4691
5221
  ] })
4692
5222
  ]
4693
5223
  }
4694
5224
  ),
4695
- /* @__PURE__ */ jsx4(
5225
+ /* @__PURE__ */ jsx5(
4696
5226
  "box",
4697
5227
  {
4698
5228
  flexDirection: "column",
4699
5229
  borderStyle: "single",
4700
5230
  borderColor: t.status.success,
4701
5231
  padding: 1,
4702
- 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" })
4703
5233
  }
4704
5234
  )
4705
5235
  ] });
4706
5236
  };
4707
5237
  const renderSuccess = () => {
4708
5238
  const state = readDeploymentState(deploymentName);
4709
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", children: [
4710
- /* @__PURE__ */ jsxs4(
5239
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
5240
+ /* @__PURE__ */ jsxs5(
4711
5241
  "box",
4712
5242
  {
4713
5243
  flexDirection: "column",
@@ -4716,12 +5246,12 @@ function DeployingView({ context }) {
4716
5246
  padding: 1,
4717
5247
  marginBottom: 1,
4718
5248
  children: [
4719
- /* @__PURE__ */ jsx4("text", { fg: t.status.success, children: "Deployment Successful!" }),
4720
- /* @__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." })
4721
5251
  ]
4722
5252
  }
4723
5253
  ),
4724
- /* @__PURE__ */ jsxs4(
5254
+ /* @__PURE__ */ jsxs5(
4725
5255
  "box",
4726
5256
  {
4727
5257
  flexDirection: "column",
@@ -4730,36 +5260,36 @@ function DeployingView({ context }) {
4730
5260
  padding: 1,
4731
5261
  marginBottom: 1,
4732
5262
  children: [
4733
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "Connection Details" }),
4734
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", marginTop: 1, children: [
4735
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, width: 15, children: "Server IP:" }),
4736
- /* @__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" })
4737
5267
  ] }),
4738
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", children: [
4739
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, width: 15, children: "Tailscale IP:" }),
4740
- /* @__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" })
4741
5271
  ] }),
4742
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", children: [
4743
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, width: 15, children: "Gateway Port:" }),
4744
- /* @__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" })
4745
5275
  ] })
4746
5276
  ]
4747
5277
  }
4748
5278
  ),
4749
- /* @__PURE__ */ jsx4("text", { fg: t.status.success, children: "Next steps:" }),
4750
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: " /ssh - Connect to your server" }),
4751
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: " /logs - View OpenClaw logs" }),
4752
- /* @__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: [
4753
5283
  " Gateway: http://",
4754
5284
  state.tailscaleIp || state.serverIp,
4755
5285
  ":18789/"
4756
5286
  ] }),
4757
- /* @__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" })
4758
5288
  ] });
4759
5289
  };
4760
5290
  const renderFailed = () => {
4761
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", children: [
4762
- /* @__PURE__ */ jsxs4(
5291
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
5292
+ /* @__PURE__ */ jsxs5(
4763
5293
  "box",
4764
5294
  {
4765
5295
  flexDirection: "column",
@@ -4768,26 +5298,26 @@ function DeployingView({ context }) {
4768
5298
  padding: 1,
4769
5299
  marginBottom: 1,
4770
5300
  children: [
4771
- /* @__PURE__ */ jsx4("text", { fg: t.status.error, children: "Deployment Failed" }),
4772
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, marginTop: 1, children: "Something went wrong during deployment." }),
4773
- /* @__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: [
4774
5304
  "Error: ",
4775
5305
  error
4776
5306
  ] })
4777
5307
  ]
4778
5308
  }
4779
5309
  ),
4780
- /* @__PURE__ */ jsxs4("box", { flexDirection: "column", marginBottom: 1, children: [
4781
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "What you can do:" }),
4782
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: " 1. Run /deploy again - it will resume from the last successful step" }),
4783
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: " 2. Run /status to check the current state of your deployment" }),
4784
- /* @__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" })
4785
5315
  ] }),
4786
- /* @__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" })
4787
5317
  ] });
4788
5318
  };
4789
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", width: "100%", padding: 1, children: [
4790
- /* @__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: [
4791
5321
  "Deploying: ",
4792
5322
  deploymentName
4793
5323
  ] }) }),
@@ -4796,7 +5326,7 @@ function DeployingView({ context }) {
4796
5326
  deployState === "waiting_terminal" && renderWaitingTerminal(),
4797
5327
  deployState === "success" && renderSuccess(),
4798
5328
  deployState === "failed" && renderFailed(),
4799
- /* @__PURE__ */ jsxs4(
5329
+ /* @__PURE__ */ jsxs5(
4800
5330
  "box",
4801
5331
  {
4802
5332
  flexDirection: "column",
@@ -4804,8 +5334,8 @@ function DeployingView({ context }) {
4804
5334
  borderColor: t.border.default,
4805
5335
  padding: 1,
4806
5336
  children: [
4807
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: "Deployment Log" }),
4808
- /* @__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)) })
4809
5339
  ]
4810
5340
  }
4811
5341
  )
@@ -4813,13 +5343,13 @@ function DeployingView({ context }) {
4813
5343
  }
4814
5344
 
4815
5345
  // src/components/StatusView.tsx
4816
- import { useState as useState5 } from "react";
4817
- import { useKeyboard as useKeyboard4 } from "@opentui/react";
4818
- 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";
4819
5349
  function StatusView({ context }) {
4820
- const [selectedIndex, setSelectedIndex] = useState5(0);
4821
- const [healthStatus, setHealthStatus] = useState5(/* @__PURE__ */ new Map());
4822
- 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);
4823
5353
  const deployments = context.deployments;
4824
5354
  const checkHealth = async (deployment) => {
4825
5355
  const name = deployment.config.name;
@@ -4844,7 +5374,7 @@ function StatusView({ context }) {
4844
5374
  setHealthStatus((prev) => new Map(prev).set(name, health));
4845
5375
  setChecking(null);
4846
5376
  };
4847
- useKeyboard4((key) => {
5377
+ useKeyboard5((key) => {
4848
5378
  if (deployments.length === 0) {
4849
5379
  context.navigateTo("home");
4850
5380
  return;
@@ -4860,12 +5390,12 @@ function StatusView({ context }) {
4860
5390
  }
4861
5391
  });
4862
5392
  if (deployments.length === 0) {
4863
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", width: "100%", padding: 1, children: [
4864
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", marginBottom: 2, children: [
4865
- /* @__PURE__ */ jsx5("text", { fg: t.accent, children: "/status" }),
4866
- /* @__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" })
4867
5397
  ] }),
4868
- /* @__PURE__ */ jsxs5(
5398
+ /* @__PURE__ */ jsxs6(
4869
5399
  "box",
4870
5400
  {
4871
5401
  flexDirection: "column",
@@ -4873,22 +5403,22 @@ function StatusView({ context }) {
4873
5403
  borderColor: t.border.default,
4874
5404
  padding: 1,
4875
5405
  children: [
4876
- /* @__PURE__ */ jsx5("text", { fg: t.status.warning, children: "No deployments found!" }),
4877
- /* @__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." })
4878
5408
  ]
4879
5409
  }
4880
5410
  ),
4881
- /* @__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" })
4882
5412
  ] });
4883
5413
  }
4884
5414
  const selectedDeployment = deployments[selectedIndex];
4885
5415
  const selectedHealth = healthStatus.get(selectedDeployment.config.name);
4886
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", width: "100%", padding: 1, children: [
4887
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", marginBottom: 2, children: [
4888
- /* @__PURE__ */ jsx5("text", { fg: t.accent, children: "/status" }),
4889
- /* @__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" })
4890
5420
  ] }),
4891
- /* @__PURE__ */ jsxs5(
5421
+ /* @__PURE__ */ jsxs6(
4892
5422
  "box",
4893
5423
  {
4894
5424
  flexDirection: "column",
@@ -4897,22 +5427,22 @@ function StatusView({ context }) {
4897
5427
  padding: 1,
4898
5428
  marginBottom: 1,
4899
5429
  children: [
4900
- /* @__PURE__ */ jsxs5("text", { fg: t.fg.primary, marginBottom: 1, children: [
5430
+ /* @__PURE__ */ jsxs6("text", { fg: t.fg.primary, marginBottom: 1, children: [
4901
5431
  "Deployments (",
4902
5432
  deployments.length,
4903
5433
  ")"
4904
5434
  ] }),
4905
5435
  deployments.map((deployment, index) => {
4906
5436
  const isSelected = index === selectedIndex;
4907
- return /* @__PURE__ */ jsxs5(
5437
+ return /* @__PURE__ */ jsxs6(
4908
5438
  "box",
4909
5439
  {
4910
5440
  flexDirection: "row",
4911
5441
  backgroundColor: isSelected ? t.selection.bg : void 0,
4912
5442
  children: [
4913
- /* @__PURE__ */ jsx5("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
4914
- /* @__PURE__ */ jsx5("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
4915
- /* @__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: [
4916
5446
  "[",
4917
5447
  deployment.state.status,
4918
5448
  "]"
@@ -4925,7 +5455,7 @@ function StatusView({ context }) {
4925
5455
  ]
4926
5456
  }
4927
5457
  ),
4928
- /* @__PURE__ */ jsxs5(
5458
+ /* @__PURE__ */ jsxs6(
4929
5459
  "box",
4930
5460
  {
4931
5461
  flexDirection: "column",
@@ -4934,78 +5464,78 @@ function StatusView({ context }) {
4934
5464
  padding: 1,
4935
5465
  marginBottom: 1,
4936
5466
  children: [
4937
- /* @__PURE__ */ jsxs5("text", { fg: t.accent, children: [
5467
+ /* @__PURE__ */ jsxs6("text", { fg: t.accent, children: [
4938
5468
  "Details: ",
4939
5469
  selectedDeployment.config.name
4940
5470
  ] }),
4941
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", marginTop: 1, children: [
4942
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Status:" }),
4943
- /* @__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 })
4944
5474
  ] }),
4945
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4946
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Provider:" }),
4947
- /* @__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 })
4948
5478
  ] }),
4949
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4950
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Server IP:" }),
4951
- /* @__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" })
4952
5482
  ] }),
4953
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4954
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Tailscale IP:" }),
4955
- /* @__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" })
4956
5486
  ] }),
4957
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4958
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Created:" }),
4959
- /* @__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() })
4960
5490
  ] }),
4961
- selectedDeployment.state.deployedAt && /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4962
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Deployed:" }),
4963
- /* @__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() })
4964
5494
  ] }),
4965
- selectedDeployment.state.lastError && /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4966
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Last Error:" }),
4967
- /* @__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 })
4968
5498
  ] }),
4969
- selectedHealth && /* @__PURE__ */ jsxs5("box", { flexDirection: "column", marginTop: 1, children: [
4970
- /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: "Health Check:" }),
4971
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4972
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "SSH:" }),
4973
- /* @__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" })
4974
5504
  ] }),
4975
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4976
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "OpenClaw:" }),
4977
- /* @__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" })
4978
5508
  ] }),
4979
- /* @__PURE__ */ jsxs5("text", { fg: t.fg.muted, children: [
5509
+ /* @__PURE__ */ jsxs6("text", { fg: t.fg.muted, children: [
4980
5510
  "Last checked: ",
4981
5511
  selectedHealth.lastChecked.toLocaleTimeString()
4982
5512
  ] })
4983
5513
  ] }),
4984
- 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..." })
4985
5515
  ]
4986
5516
  }
4987
5517
  ),
4988
- selectedDeployment.state.checkpoints.length > 0 && /* @__PURE__ */ jsxs5("box", { flexDirection: "row", marginBottom: 1, children: [
4989
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: "Checkpoints: " }),
4990
- /* @__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: [
4991
5521
  selectedDeployment.state.checkpoints.length,
4992
5522
  " completed"
4993
5523
  ] })
4994
5524
  ] }),
4995
- /* @__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" })
4996
5526
  ] });
4997
5527
  }
4998
5528
 
4999
5529
  // src/components/SSHView.tsx
5000
- import { useState as useState6, useCallback as useCallback2 } from "react";
5001
- import { useKeyboard as useKeyboard5 } from "@opentui/react";
5002
- 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";
5003
5533
  function SSHView({ context }) {
5004
- const [viewState, setViewState] = useState6("selecting");
5005
- const [selectedIndex, setSelectedIndex] = useState6(0);
5006
- const [error, setError] = useState6(null);
5007
- const [connectedDeployment, setConnectedDeployment] = useState6(null);
5008
- 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("");
5009
5539
  const deployedDeployments = context.deployments.filter(
5010
5540
  (d) => d.state.status === "deployed" && d.state.serverIp
5011
5541
  );
@@ -5024,7 +5554,7 @@ function SSHView({ context }) {
5024
5554
  }
5025
5555
  }, []);
5026
5556
  const selectedDeployment = deployedDeployments[selectedIndex];
5027
- useKeyboard5((key) => {
5557
+ useKeyboard6((key) => {
5028
5558
  if (deployedDeployments.length === 0) {
5029
5559
  context.navigateTo("home");
5030
5560
  return;
@@ -5048,12 +5578,12 @@ function SSHView({ context }) {
5048
5578
  }
5049
5579
  });
5050
5580
  if (deployedDeployments.length === 0) {
5051
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5052
- /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 2, children: [
5053
- /* @__PURE__ */ jsx6("text", { fg: t.accent, children: "/ssh" }),
5054
- /* @__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" })
5055
5585
  ] }),
5056
- /* @__PURE__ */ jsxs6(
5586
+ /* @__PURE__ */ jsxs7(
5057
5587
  "box",
5058
5588
  {
5059
5589
  flexDirection: "column",
@@ -5061,23 +5591,23 @@ function SSHView({ context }) {
5061
5591
  borderColor: t.border.default,
5062
5592
  padding: 1,
5063
5593
  children: [
5064
- /* @__PURE__ */ jsx6("text", { fg: t.status.warning, children: "No deployed instances found!" }),
5065
- /* @__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" })
5066
5596
  ]
5067
5597
  }
5068
5598
  ),
5069
- /* @__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" })
5070
5600
  ] });
5071
5601
  }
5072
5602
  if (viewState === "selecting") {
5073
5603
  const terminal = detectTerminal();
5074
5604
  const terminalDisplayName = getTerminalDisplayName(terminal.app);
5075
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5076
- /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 2, children: [
5077
- /* @__PURE__ */ jsx6("text", { fg: t.accent, children: "/ssh" }),
5078
- /* @__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" })
5079
5609
  ] }),
5080
- /* @__PURE__ */ jsx6(
5610
+ /* @__PURE__ */ jsx7(
5081
5611
  "box",
5082
5612
  {
5083
5613
  flexDirection: "column",
@@ -5087,15 +5617,15 @@ function SSHView({ context }) {
5087
5617
  marginBottom: 1,
5088
5618
  children: deployedDeployments.map((deployment, index) => {
5089
5619
  const isSelected = index === selectedIndex;
5090
- return /* @__PURE__ */ jsxs6(
5620
+ return /* @__PURE__ */ jsxs7(
5091
5621
  "box",
5092
5622
  {
5093
5623
  flexDirection: "row",
5094
5624
  backgroundColor: isSelected ? t.selection.bg : void 0,
5095
5625
  children: [
5096
- /* @__PURE__ */ jsx6("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
5097
- /* @__PURE__ */ jsx6("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
5098
- /* @__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 })
5099
5629
  ]
5100
5630
  },
5101
5631
  deployment.config.name
@@ -5103,24 +5633,24 @@ function SSHView({ context }) {
5103
5633
  })
5104
5634
  }
5105
5635
  ),
5106
- /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 1, children: [
5107
- /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, children: "Terminal: " }),
5108
- /* @__PURE__ */ jsx6("text", { fg: t.status.success, children: terminalDisplayName }),
5109
- /* @__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)" })
5110
5640
  ] }),
5111
- /* @__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" })
5112
5642
  ] });
5113
5643
  }
5114
5644
  if (viewState === "connected") {
5115
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5116
- /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 2, children: [
5117
- /* @__PURE__ */ jsx6("text", { fg: t.status.success, children: "/ssh" }),
5118
- /* @__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: [
5119
5649
  " - Connected to ",
5120
5650
  connectedDeployment
5121
5651
  ] })
5122
5652
  ] }),
5123
- /* @__PURE__ */ jsxs6(
5653
+ /* @__PURE__ */ jsxs7(
5124
5654
  "box",
5125
5655
  {
5126
5656
  flexDirection: "column",
@@ -5129,8 +5659,8 @@ function SSHView({ context }) {
5129
5659
  padding: 1,
5130
5660
  marginBottom: 1,
5131
5661
  children: [
5132
- /* @__PURE__ */ jsx6("text", { fg: t.status.success, children: "SSH Session Opened" }),
5133
- /* @__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: [
5134
5664
  "A new ",
5135
5665
  terminalName,
5136
5666
  " window/tab has been opened."
@@ -5138,7 +5668,7 @@ function SSHView({ context }) {
5138
5668
  ]
5139
5669
  }
5140
5670
  ),
5141
- /* @__PURE__ */ jsxs6(
5671
+ /* @__PURE__ */ jsxs7(
5142
5672
  "box",
5143
5673
  {
5144
5674
  flexDirection: "column",
@@ -5147,41 +5677,41 @@ function SSHView({ context }) {
5147
5677
  padding: 1,
5148
5678
  marginBottom: 1,
5149
5679
  children: [
5150
- /* @__PURE__ */ jsxs6("text", { fg: t.fg.primary, children: [
5680
+ /* @__PURE__ */ jsxs7("text", { fg: t.fg.primary, children: [
5151
5681
  "Your SSH session is running in ",
5152
5682
  terminalName,
5153
5683
  "."
5154
5684
  ] }),
5155
- /* @__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." })
5156
5686
  ]
5157
5687
  }
5158
5688
  ),
5159
- /* @__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" })
5160
5690
  ] });
5161
5691
  }
5162
5692
  if (viewState === "error") {
5163
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5164
- /* @__PURE__ */ jsx6("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx6("text", { fg: t.status.error, children: "SSH Error" }) }),
5165
- /* @__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(
5166
5696
  "box",
5167
5697
  {
5168
5698
  flexDirection: "column",
5169
5699
  borderStyle: "single",
5170
5700
  borderColor: t.status.error,
5171
5701
  padding: 1,
5172
- children: /* @__PURE__ */ jsx6("text", { fg: t.status.error, children: error })
5702
+ children: /* @__PURE__ */ jsx7("text", { fg: t.status.error, children: error })
5173
5703
  }
5174
5704
  ),
5175
- /* @__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" })
5176
5706
  ] });
5177
5707
  }
5178
5708
  return null;
5179
5709
  }
5180
5710
 
5181
5711
  // src/components/LogsView.tsx
5182
- import { useState as useState7, useEffect as useEffect3, useRef as useRef3 } from "react";
5183
- import { useKeyboard as useKeyboard6 } from "@opentui/react";
5184
- 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";
5185
5715
  function parseLogLine(line) {
5186
5716
  if (!line.trim()) return null;
5187
5717
  const match = line.match(/^(\w+\s+\d+\s+[\d:]+)\s+\S+\s+\S+:\s*(.*)$/);
@@ -5200,14 +5730,14 @@ function truncateLine(line, maxWidth = 120) {
5200
5730
  return line.substring(0, maxWidth - 3) + "...";
5201
5731
  }
5202
5732
  function LogsView({ context }) {
5203
- const [viewState, setViewState] = useState7("selecting");
5204
- const [selectedIndex, setSelectedIndex] = useState7(0);
5205
- const [logs, setLogs] = useState7([]);
5206
- const [error, setError] = useState7(null);
5207
- const [autoRefresh, setAutoRefresh] = useState7(false);
5208
- const [lastFetched, setLastFetched] = useState7(null);
5209
- const sshRef = useRef3(null);
5210
- 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);
5211
5741
  const deployedDeployments = context.deployments.filter(
5212
5742
  (d) => d.state.status === "deployed" && d.state.serverIp
5213
5743
  );
@@ -5258,7 +5788,7 @@ function LogsView({ context }) {
5258
5788
  setLogs([]);
5259
5789
  };
5260
5790
  const selectedDeployment = deployedDeployments[selectedIndex];
5261
- useKeyboard6((key) => {
5791
+ useKeyboard7((key) => {
5262
5792
  if (deployedDeployments.length === 0) {
5263
5793
  context.navigateTo("home");
5264
5794
  return;
@@ -5291,12 +5821,12 @@ function LogsView({ context }) {
5291
5821
  }
5292
5822
  });
5293
5823
  if (deployedDeployments.length === 0) {
5294
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5295
- /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 2, children: [
5296
- /* @__PURE__ */ jsx7("text", { fg: t.accent, children: "/logs" }),
5297
- /* @__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" })
5298
5828
  ] }),
5299
- /* @__PURE__ */ jsxs7(
5829
+ /* @__PURE__ */ jsxs8(
5300
5830
  "box",
5301
5831
  {
5302
5832
  flexDirection: "column",
@@ -5304,21 +5834,21 @@ function LogsView({ context }) {
5304
5834
  borderColor: t.border.default,
5305
5835
  padding: 1,
5306
5836
  children: [
5307
- /* @__PURE__ */ jsx7("text", { fg: t.status.warning, children: "No deployed instances found!" }),
5308
- /* @__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" })
5309
5839
  ]
5310
5840
  }
5311
5841
  ),
5312
- /* @__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" })
5313
5843
  ] });
5314
5844
  }
5315
5845
  if (viewState === "selecting") {
5316
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5317
- /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 2, children: [
5318
- /* @__PURE__ */ jsx7("text", { fg: t.accent, children: "/logs" }),
5319
- /* @__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" })
5320
5850
  ] }),
5321
- /* @__PURE__ */ jsx7(
5851
+ /* @__PURE__ */ jsx8(
5322
5852
  "box",
5323
5853
  {
5324
5854
  flexDirection: "column",
@@ -5327,15 +5857,15 @@ function LogsView({ context }) {
5327
5857
  padding: 1,
5328
5858
  children: deployedDeployments.map((deployment, index) => {
5329
5859
  const isSelected = index === selectedIndex;
5330
- return /* @__PURE__ */ jsxs7(
5860
+ return /* @__PURE__ */ jsxs8(
5331
5861
  "box",
5332
5862
  {
5333
5863
  flexDirection: "row",
5334
5864
  backgroundColor: isSelected ? t.selection.bg : void 0,
5335
5865
  children: [
5336
- /* @__PURE__ */ jsx7("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
5337
- /* @__PURE__ */ jsx7("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
5338
- /* @__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 })
5339
5869
  ]
5340
5870
  },
5341
5871
  deployment.config.name
@@ -5343,70 +5873,70 @@ function LogsView({ context }) {
5343
5873
  })
5344
5874
  }
5345
5875
  ),
5346
- /* @__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" })
5347
5877
  ] });
5348
5878
  }
5349
5879
  if (viewState === "loading") {
5350
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5351
- /* @__PURE__ */ jsx7("text", { fg: t.accent, children: "Loading logs..." }),
5352
- /* @__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..." })
5353
5883
  ] });
5354
5884
  }
5355
5885
  if (viewState === "error") {
5356
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5357
- /* @__PURE__ */ jsx7("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx7("text", { fg: t.status.error, children: "Error Loading Logs" }) }),
5358
- /* @__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(
5359
5889
  "box",
5360
5890
  {
5361
5891
  flexDirection: "column",
5362
5892
  borderStyle: "single",
5363
5893
  borderColor: t.status.error,
5364
5894
  padding: 1,
5365
- children: /* @__PURE__ */ jsx7("text", { fg: t.status.error, children: error })
5895
+ children: /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: error })
5366
5896
  }
5367
5897
  ),
5368
- /* @__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" })
5369
5899
  ] });
5370
5900
  }
5371
5901
  const visibleLogs = logs;
5372
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5373
- /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 1, children: [
5374
- /* @__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: [
5375
5905
  "Logs: ",
5376
5906
  selectedDeployment.config.name
5377
5907
  ] }),
5378
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, children: " | " }),
5379
- /* @__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: [
5380
5910
  "Auto: ",
5381
5911
  autoRefresh ? "ON (5s)" : "OFF"
5382
5912
  ] }),
5383
- lastFetched && /* @__PURE__ */ jsxs7(Fragment, { children: [
5384
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, children: " | " }),
5385
- /* @__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: [
5386
5916
  "Fetched: ",
5387
5917
  lastFetched.toLocaleTimeString()
5388
5918
  ] })
5389
5919
  ] })
5390
5920
  ] }),
5391
- /* @__PURE__ */ jsx7(
5921
+ /* @__PURE__ */ jsx8(
5392
5922
  "box",
5393
5923
  {
5394
5924
  flexDirection: "column",
5395
5925
  borderStyle: "single",
5396
5926
  borderColor: t.border.default,
5397
5927
  padding: 1,
5398
- children: /* @__PURE__ */ jsx7("box", { flexDirection: "column", children: visibleLogs.map((line, i) => {
5928
+ children: /* @__PURE__ */ jsx8("box", { flexDirection: "column", children: visibleLogs.map((line, i) => {
5399
5929
  const parsed = parseLogLine(line);
5400
5930
  if (!parsed) return null;
5401
5931
  const displayLine = parsed.timestamp ? `${parsed.timestamp} ${truncateLine(parsed.message, 100)}` : truncateLine(parsed.message, 120);
5402
- return /* @__PURE__ */ jsx7("text", { fg: logLevelColor(parsed.level), children: displayLine }, i);
5932
+ return /* @__PURE__ */ jsx8("text", { fg: logLevelColor(parsed.level), children: displayLine }, i);
5403
5933
  }) })
5404
5934
  }
5405
5935
  ),
5406
- /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginTop: 1, children: [
5407
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, children: "R: Refresh | A: Toggle auto-refresh | Esc: Back" }),
5408
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, children: " | " }),
5409
- /* @__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: [
5410
5940
  "Showing last ",
5411
5941
  visibleLogs.length,
5412
5942
  " lines"
@@ -5416,14 +5946,14 @@ function LogsView({ context }) {
5416
5946
  }
5417
5947
 
5418
5948
  // src/components/DestroyView.tsx
5419
- import { useState as useState8 } from "react";
5420
- import { useKeyboard as useKeyboard7 } from "@opentui/react";
5421
- 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";
5422
5952
  function DestroyView({ context }) {
5423
- const [viewState, setViewState] = useState8("selecting");
5424
- const [selectedIndex, setSelectedIndex] = useState8(0);
5425
- const [error, setError] = useState8(null);
5426
- 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("");
5427
5957
  const deployments = context.deployments;
5428
5958
  const destroyDeployment = async (name) => {
5429
5959
  setViewState("destroying");
@@ -5468,7 +5998,7 @@ function DestroyView({ context }) {
5468
5998
  }
5469
5999
  };
5470
6000
  const selectedDeployment = deployments[selectedIndex];
5471
- useKeyboard7((key) => {
6001
+ useKeyboard8((key) => {
5472
6002
  if (deployments.length === 0) {
5473
6003
  context.navigateTo("home");
5474
6004
  return;
@@ -5493,31 +6023,31 @@ function DestroyView({ context }) {
5493
6023
  }
5494
6024
  });
5495
6025
  if (deployments.length === 0) {
5496
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5497
- /* @__PURE__ */ jsxs8("box", { flexDirection: "row", marginBottom: 2, children: [
5498
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "/destroy" }),
5499
- /* @__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" })
5500
6030
  ] }),
5501
- /* @__PURE__ */ jsx8(
6031
+ /* @__PURE__ */ jsx9(
5502
6032
  "box",
5503
6033
  {
5504
6034
  flexDirection: "column",
5505
6035
  borderStyle: "single",
5506
6036
  borderColor: t.border.default,
5507
6037
  padding: 1,
5508
- 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!" })
5509
6039
  }
5510
6040
  ),
5511
- /* @__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" })
5512
6042
  ] });
5513
6043
  }
5514
6044
  if (viewState === "selecting") {
5515
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5516
- /* @__PURE__ */ jsxs8("box", { flexDirection: "row", marginBottom: 2, children: [
5517
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "/destroy" }),
5518
- /* @__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" })
5519
6049
  ] }),
5520
- /* @__PURE__ */ jsxs8(
6050
+ /* @__PURE__ */ jsxs9(
5521
6051
  "box",
5522
6052
  {
5523
6053
  flexDirection: "column",
@@ -5525,18 +6055,18 @@ function DestroyView({ context }) {
5525
6055
  borderColor: t.status.error,
5526
6056
  padding: 1,
5527
6057
  children: [
5528
- /* @__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!" }),
5529
6059
  deployments.map((deployment, index) => {
5530
6060
  const isSelected = index === selectedIndex;
5531
- return /* @__PURE__ */ jsxs8(
6061
+ return /* @__PURE__ */ jsxs9(
5532
6062
  "box",
5533
6063
  {
5534
6064
  flexDirection: "row",
5535
6065
  backgroundColor: isSelected ? t.selection.bg : void 0,
5536
6066
  children: [
5537
- /* @__PURE__ */ jsx8("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
5538
- /* @__PURE__ */ jsx8("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
5539
- /* @__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: [
5540
6070
  "[",
5541
6071
  deployment.state.status,
5542
6072
  "]"
@@ -5549,13 +6079,13 @@ function DestroyView({ context }) {
5549
6079
  ]
5550
6080
  }
5551
6081
  ),
5552
- /* @__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" })
5553
6083
  ] });
5554
6084
  }
5555
6085
  if (viewState === "confirming") {
5556
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5557
- /* @__PURE__ */ jsx8("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "Confirm Destruction" }) }),
5558
- /* @__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(
5559
6089
  "box",
5560
6090
  {
5561
6091
  flexDirection: "column",
@@ -5563,26 +6093,26 @@ function DestroyView({ context }) {
5563
6093
  borderColor: t.status.error,
5564
6094
  padding: 1,
5565
6095
  children: [
5566
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "You are about to destroy:" }),
5567
- /* @__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: [
5568
6098
  "Deployment: ",
5569
6099
  selectedDeployment.config.name
5570
6100
  ] }),
5571
- selectedDeployment.state.serverIp && /* @__PURE__ */ jsxs8("text", { fg: t.fg.primary, children: [
6101
+ selectedDeployment.state.serverIp && /* @__PURE__ */ jsxs9("text", { fg: t.fg.primary, children: [
5572
6102
  "Server IP: ",
5573
6103
  selectedDeployment.state.serverIp
5574
6104
  ] }),
5575
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, marginTop: 1, children: "This will permanently delete:" }),
5576
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: "\u2022 The VPS server (if deployed)" }),
5577
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: "\u2022 All data on the server" }),
5578
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: "\u2022 Local configuration files" }),
5579
- /* @__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" })
5580
6110
  ]
5581
6111
  }
5582
6112
  ),
5583
- /* @__PURE__ */ jsx8("text", { fg: t.status.warning, marginTop: 2, children: "Type the deployment name to confirm:" }),
5584
- /* @__PURE__ */ jsx8("text", { fg: t.fg.primary, marginTop: 1, children: "Confirm:" }),
5585
- /* @__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(
5586
6116
  "input",
5587
6117
  {
5588
6118
  value: confirmText,
@@ -5605,19 +6135,19 @@ function DestroyView({ context }) {
5605
6135
  }
5606
6136
  }
5607
6137
  ),
5608
- error && /* @__PURE__ */ jsx8("text", { fg: t.status.error, marginTop: 1, children: error }),
5609
- /* @__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" })
5610
6140
  ] });
5611
6141
  }
5612
6142
  if (viewState === "destroying") {
5613
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5614
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "Destroying deployment..." }),
5615
- /* @__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..." })
5616
6146
  ] });
5617
6147
  }
5618
6148
  if (viewState === "success") {
5619
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5620
- /* @__PURE__ */ jsxs8(
6149
+ return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6150
+ /* @__PURE__ */ jsxs9(
5621
6151
  "box",
5622
6152
  {
5623
6153
  flexDirection: "column",
@@ -5625,8 +6155,8 @@ function DestroyView({ context }) {
5625
6155
  borderColor: t.status.success,
5626
6156
  padding: 1,
5627
6157
  children: [
5628
- /* @__PURE__ */ jsx8("text", { fg: t.status.success, children: "Deployment Destroyed" }),
5629
- /* @__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: [
5630
6160
  'The deployment "',
5631
6161
  selectedDeployment.config.name,
5632
6162
  '" has been permanently deleted.'
@@ -5634,12 +6164,12 @@ function DestroyView({ context }) {
5634
6164
  ]
5635
6165
  }
5636
6166
  ),
5637
- /* @__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" })
5638
6168
  ] });
5639
6169
  }
5640
6170
  if (viewState === "error") {
5641
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5642
- /* @__PURE__ */ jsxs8(
6171
+ return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6172
+ /* @__PURE__ */ jsxs9(
5643
6173
  "box",
5644
6174
  {
5645
6175
  flexDirection: "column",
@@ -5647,30 +6177,30 @@ function DestroyView({ context }) {
5647
6177
  borderColor: t.status.error,
5648
6178
  padding: 1,
5649
6179
  children: [
5650
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "Destruction Failed" }),
5651
- /* @__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 })
5652
6182
  ]
5653
6183
  }
5654
6184
  ),
5655
- /* @__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" })
5656
6186
  ] });
5657
6187
  }
5658
6188
  return null;
5659
6189
  }
5660
6190
 
5661
6191
  // src/components/HelpView.tsx
5662
- import { useKeyboard as useKeyboard8 } from "@opentui/react";
5663
- 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";
5664
6194
  function HelpView({ context }) {
5665
- useKeyboard8(() => {
6195
+ useKeyboard9(() => {
5666
6196
  context.navigateTo("home");
5667
6197
  });
5668
- return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5669
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginBottom: 2, children: [
5670
- /* @__PURE__ */ jsx9("text", { fg: t.accent, children: "/help" }),
5671
- /* @__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" })
5672
6202
  ] }),
5673
- /* @__PURE__ */ jsxs9(
6203
+ /* @__PURE__ */ jsxs10(
5674
6204
  "box",
5675
6205
  {
5676
6206
  flexDirection: "column",
@@ -5679,12 +6209,12 @@ function HelpView({ context }) {
5679
6209
  padding: 1,
5680
6210
  marginBottom: 1,
5681
6211
  children: [
5682
- /* @__PURE__ */ jsx9("text", { fg: t.accent, children: "What is ClawControl?" }),
5683
- /* @__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." })
5684
6214
  ]
5685
6215
  }
5686
6216
  ),
5687
- /* @__PURE__ */ jsxs9(
6217
+ /* @__PURE__ */ jsxs10(
5688
6218
  "box",
5689
6219
  {
5690
6220
  flexDirection: "column",
@@ -5693,48 +6223,48 @@ function HelpView({ context }) {
5693
6223
  padding: 1,
5694
6224
  marginBottom: 1,
5695
6225
  children: [
5696
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Available Commands" }),
5697
- /* @__PURE__ */ jsxs9("box", { flexDirection: "column", marginTop: 1, children: [
5698
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", children: [
5699
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/new" }),
5700
- /* @__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" })
5701
6231
  ] }),
5702
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Creates deployment config in ~/.clawcontrol/deployments/" }),
5703
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5704
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/deploy" }),
5705
- /* @__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" })
5706
6236
  ] }),
5707
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Provisions VPS, installs dependencies, configures OpenClaw" }),
5708
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5709
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/status" }),
5710
- /* @__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" })
5711
6241
  ] }),
5712
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Shows deployment state, health checks, and connection info" }),
5713
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5714
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/ssh" }),
5715
- /* @__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" })
5716
6246
  ] }),
5717
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Opens interactive SSH session to your server" }),
5718
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5719
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/logs" }),
5720
- /* @__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" })
5721
6251
  ] }),
5722
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Streams logs from journalctl with auto-refresh option" }),
5723
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5724
- /* @__PURE__ */ jsx9("text", { fg: t.status.error, width: 12, children: "/destroy" }),
5725
- /* @__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" })
5726
6256
  ] }),
5727
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Deletes VPS, SSH keys, and local configuration" }),
5728
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5729
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/templates" }),
5730
- /* @__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" })
5731
6261
  ] }),
5732
- /* @__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" })
5733
6263
  ] })
5734
6264
  ]
5735
6265
  }
5736
6266
  ),
5737
- /* @__PURE__ */ jsxs9(
6267
+ /* @__PURE__ */ jsxs10(
5738
6268
  "box",
5739
6269
  {
5740
6270
  flexDirection: "column",
@@ -5743,17 +6273,17 @@ function HelpView({ context }) {
5743
6273
  padding: 1,
5744
6274
  marginBottom: 1,
5745
6275
  children: [
5746
- /* @__PURE__ */ jsx9("text", { fg: t.status.success, children: "Typical Workflow" }),
5747
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, marginTop: 1, children: "1. Run /templates to browse or create reusable presets" }),
5748
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "2. Run /new to create a deployment config (optionally from a template)" }),
5749
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "3. Run /deploy to deploy to the cloud" }),
5750
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "4. Authenticate Tailscale when prompted" }),
5751
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "5. Complete OpenClaw onboarding via SSH" }),
5752
- /* @__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" })
5753
6283
  ]
5754
6284
  }
5755
6285
  ),
5756
- /* @__PURE__ */ jsxs9(
6286
+ /* @__PURE__ */ jsxs10(
5757
6287
  "box",
5758
6288
  {
5759
6289
  flexDirection: "column",
@@ -5762,36 +6292,36 @@ function HelpView({ context }) {
5762
6292
  padding: 1,
5763
6293
  marginBottom: 1,
5764
6294
  children: [
5765
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Supported Cloud Providers" }),
5766
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5767
- /* @__PURE__ */ jsx9("text", { fg: t.status.success, children: "\u2713 " }),
5768
- /* @__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)" })
5769
6299
  ] }),
5770
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", children: [
5771
- /* @__PURE__ */ jsx9("text", { fg: t.status.success, children: "\u2713 " }),
5772
- /* @__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)" })
5773
6303
  ] }),
5774
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", children: [
5775
- /* @__PURE__ */ jsx9("text", { fg: t.fg.muted, children: "\u25CB " }),
5776
- /* @__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" })
5777
6307
  ] })
5778
6308
  ]
5779
6309
  }
5780
6310
  ),
5781
- /* @__PURE__ */ jsxs9("box", { flexDirection: "column", marginBottom: 1, children: [
5782
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Useful Links" }),
5783
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "\u2022 OpenClaw Docs: https://docs.openclaw.ai/" }),
5784
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "\u2022 Hetzner API: https://docs.hetzner.cloud/" }),
5785
- /* @__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/" })
5786
6316
  ] }),
5787
- /* @__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" })
5788
6318
  ] });
5789
6319
  }
5790
6320
 
5791
6321
  // src/components/TemplatesView.tsx
5792
- import { useState as useState9, useRef as useRef4, useEffect as useEffect4 } from "react";
5793
- import { useKeyboard as useKeyboard9 } from "@opentui/react";
5794
- 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";
5795
6325
  var DO_DROPLET_SIZES2 = [
5796
6326
  { slug: "s-1vcpu-2gb", label: "1 vCPU, 2GB RAM, 50GB SSD", price: "$12/mo" },
5797
6327
  { slug: "s-2vcpu-2gb", label: "2 vCPU, 2GB RAM, 60GB SSD", price: "$18/mo" },
@@ -5800,20 +6330,20 @@ var DO_DROPLET_SIZES2 = [
5800
6330
  { slug: "s-8vcpu-16gb", label: "8 vCPU, 16GB RAM, 320GB SSD", price: "$96/mo" }
5801
6331
  ];
5802
6332
  function TemplatesView({ context }) {
5803
- const [viewState, setViewState] = useState9("listing");
5804
- const [templates, setTemplates] = useState9([]);
5805
- const [selectedIndex, setSelectedIndex] = useState9(0);
5806
- const [selectedTemplate, setSelectedTemplate] = useState9(null);
5807
- const [error, setError] = useState9(null);
5808
- const [forkStep, setForkStep] = useState9("fork_name");
5809
- const [forkName, setForkName] = useState9("");
5810
- const [forkProvider, setForkProvider] = useState9("hetzner");
5811
- const [forkProviderIndex, setForkProviderIndex] = useState9(0);
5812
- const [forkDropletSizeIndex, setForkDropletSizeIndex] = useState9(0);
5813
- const [forkAiProvider, setForkAiProvider] = useState9("");
5814
- const [forkAiProviderIndex, setForkAiProviderIndex] = useState9(0);
5815
- const [forkModel, setForkModel] = useState9("");
5816
- 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({
5817
6347
  viewState,
5818
6348
  selectedIndex,
5819
6349
  selectedTemplate,
@@ -5927,7 +6457,7 @@ function TemplatesView({ context }) {
5927
6457
  setViewState("viewing");
5928
6458
  }
5929
6459
  };
5930
- useKeyboard9((key) => {
6460
+ useKeyboard10((key) => {
5931
6461
  const s = stateRef.current;
5932
6462
  if (s.viewState === "listing") {
5933
6463
  if (key.name === "up") {
@@ -6019,37 +6549,37 @@ function TemplatesView({ context }) {
6019
6549
  }
6020
6550
  }
6021
6551
  });
6022
- const renderListing = () => /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6023
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginBottom: 1, children: "Select a template to view details. Use arrow keys and Enter." }),
6024
- /* @__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(
6025
6555
  "box",
6026
6556
  {
6027
6557
  flexDirection: "column",
6028
6558
  borderStyle: "single",
6029
6559
  borderColor: t.border.default,
6030
6560
  padding: 1,
6031
- 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) => {
6032
6562
  const isSelected = i === selectedIndex;
6033
6563
  const badge = tmpl.builtIn ? "[built-in]" : "[custom]";
6034
6564
  const badgeColor = tmpl.builtIn ? t.status.info : t.status.success;
6035
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6036
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6037
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: tmpl.name }),
6038
- /* @__PURE__ */ jsx10("text", { fg: badgeColor, children: " " + badge }),
6039
- /* @__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 })
6040
6570
  ] }, tmpl.id);
6041
6571
  })
6042
6572
  }
6043
6573
  ),
6044
- /* @__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" })
6045
6575
  ] });
6046
6576
  const renderViewing = () => {
6047
6577
  if (!selectedTemplate) return null;
6048
6578
  const tmpl = selectedTemplate;
6049
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6050
- /* @__PURE__ */ jsx10("text", { fg: t.accent, marginBottom: 1, children: tmpl.name }),
6051
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginBottom: 1, children: tmpl.description }),
6052
- /* @__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(
6053
6583
  "box",
6054
6584
  {
6055
6585
  flexDirection: "column",
@@ -6058,89 +6588,89 @@ function TemplatesView({ context }) {
6058
6588
  padding: 1,
6059
6589
  marginBottom: 1,
6060
6590
  children: [
6061
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6062
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Type:" }),
6063
- /* @__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" })
6064
6594
  ] }),
6065
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6066
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Provider:" }),
6067
- /* @__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] })
6068
6598
  ] }),
6069
- tmpl.hetzner && /* @__PURE__ */ jsxs10(Fragment2, { children: [
6070
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6071
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Server Type:" }),
6072
- /* @__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 })
6073
6603
  ] }),
6074
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6075
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Location:" }),
6076
- /* @__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 })
6077
6607
  ] })
6078
6608
  ] }),
6079
- tmpl.digitalocean && /* @__PURE__ */ jsxs10(Fragment2, { children: [
6080
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6081
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Droplet Size:" }),
6082
- /* @__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 })
6083
6613
  ] }),
6084
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6085
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Region:" }),
6086
- /* @__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 })
6087
6617
  ] })
6088
6618
  ] }),
6089
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6090
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "AI Provider:" }),
6091
- /* @__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 })
6092
6622
  ] }),
6093
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6094
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Model:" }),
6095
- /* @__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 })
6096
6626
  ] }),
6097
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6098
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Channel:" }),
6099
- /* @__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 })
6100
6630
  ] })
6101
6631
  ]
6102
6632
  }
6103
6633
  ),
6104
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6105
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "[F]" }),
6106
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "ork " }),
6107
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "[U]" }),
6108
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "se " }),
6109
- !tmpl.builtIn && /* @__PURE__ */ jsxs10(Fragment2, { children: [
6110
- /* @__PURE__ */ jsx10("text", { fg: t.status.error, children: "[D]" }),
6111
- /* @__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 " })
6112
6642
  ] }),
6113
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, children: "[Esc] Back" })
6643
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, children: "[Esc] Back" })
6114
6644
  ] }),
6115
- 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 })
6116
6646
  ] });
6117
6647
  };
6118
- const renderDeleteConfirm = () => /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6119
- /* @__PURE__ */ jsx10("text", { fg: t.status.error, children: "Delete Template" }),
6120
- /* @__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: [
6121
6651
  'Are you sure you want to delete "',
6122
6652
  selectedTemplate?.name,
6123
6653
  '"?'
6124
6654
  ] }),
6125
- /* @__PURE__ */ jsx10("text", { fg: t.status.warning, marginTop: 1, children: "This action cannot be undone." }),
6126
- /* @__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" })
6127
6657
  ] });
6128
- const renderForkComplete = () => /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6129
- /* @__PURE__ */ jsx10("text", { fg: t.status.success, children: "Template Created!" }),
6130
- /* @__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: [
6131
6661
  'Your template "',
6132
6662
  forkName,
6133
6663
  '" has been saved.'
6134
6664
  ] }),
6135
- /* @__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" })
6136
6666
  ] });
6137
6667
  const renderForking = () => {
6138
6668
  switch (forkStep) {
6139
6669
  case "fork_name":
6140
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6141
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - Name" }),
6142
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginTop: 1, children: "Enter a name for the new template:" }),
6143
- /* @__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(
6144
6674
  "input",
6145
6675
  {
6146
6676
  value: forkName,
@@ -6163,14 +6693,14 @@ function TemplatesView({ context }) {
6163
6693
  }
6164
6694
  }
6165
6695
  ),
6166
- error && /* @__PURE__ */ jsx10("text", { fg: t.status.error, marginTop: 1, children: error }),
6167
- /* @__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" })
6168
6698
  ] });
6169
6699
  case "fork_provider":
6170
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6171
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - Provider" }),
6172
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginTop: 1, children: "Select cloud provider:" }),
6173
- /* @__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(
6174
6704
  "box",
6175
6705
  {
6176
6706
  flexDirection: "column",
@@ -6180,20 +6710,20 @@ function TemplatesView({ context }) {
6180
6710
  padding: 1,
6181
6711
  children: SUPPORTED_PROVIDERS.map((p, i) => {
6182
6712
  const isSelected = i === forkProviderIndex;
6183
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6184
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6185
- /* @__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] })
6186
6716
  ] }, p);
6187
6717
  })
6188
6718
  }
6189
6719
  ),
6190
- /* @__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" })
6191
6721
  ] });
6192
6722
  case "fork_droplet_size":
6193
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6194
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - Droplet Size" }),
6195
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginTop: 1, children: "Select DigitalOcean droplet size:" }),
6196
- /* @__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(
6197
6727
  "box",
6198
6728
  {
6199
6729
  flexDirection: "column",
@@ -6203,21 +6733,21 @@ function TemplatesView({ context }) {
6203
6733
  padding: 1,
6204
6734
  children: DO_DROPLET_SIZES2.map((size, i) => {
6205
6735
  const isSelected = i === forkDropletSizeIndex;
6206
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6207
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6208
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: size.slug }),
6209
- /* @__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 })
6210
6740
  ] }, size.slug);
6211
6741
  })
6212
6742
  }
6213
6743
  ),
6214
- /* @__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" })
6215
6745
  ] });
6216
6746
  case "fork_ai_provider":
6217
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6218
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - AI Provider" }),
6219
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginTop: 1, children: "Select AI provider:" }),
6220
- /* @__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(
6221
6751
  "box",
6222
6752
  {
6223
6753
  flexDirection: "column",
@@ -6227,21 +6757,21 @@ function TemplatesView({ context }) {
6227
6757
  padding: 1,
6228
6758
  children: AI_PROVIDERS.map((p, i) => {
6229
6759
  const isSelected = i === forkAiProviderIndex;
6230
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6231
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6232
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: p.label }),
6233
- /* @__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 })
6234
6764
  ] }, p.name);
6235
6765
  })
6236
6766
  }
6237
6767
  ),
6238
- /* @__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" })
6239
6769
  ] });
6240
6770
  case "fork_model":
6241
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6242
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - Model" }),
6243
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginTop: 1, children: "Enter the model identifier:" }),
6244
- /* @__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(
6245
6775
  "input",
6246
6776
  {
6247
6777
  value: forkModel,
@@ -6264,13 +6794,13 @@ function TemplatesView({ context }) {
6264
6794
  }
6265
6795
  }
6266
6796
  ),
6267
- error && /* @__PURE__ */ jsx10("text", { fg: t.status.error, marginTop: 1, children: error }),
6268
- /* @__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" })
6269
6799
  ] });
6270
6800
  case "fork_confirm":
6271
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6272
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - Confirm" }),
6273
- /* @__PURE__ */ jsxs10(
6801
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6802
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "Fork Template - Confirm" }),
6803
+ /* @__PURE__ */ jsxs11(
6274
6804
  "box",
6275
6805
  {
6276
6806
  flexDirection: "column",
@@ -6279,35 +6809,35 @@ function TemplatesView({ context }) {
6279
6809
  padding: 1,
6280
6810
  marginTop: 1,
6281
6811
  children: [
6282
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6283
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Name:" }),
6284
- /* @__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 })
6285
6815
  ] }),
6286
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6287
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Provider:" }),
6288
- /* @__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] })
6289
6819
  ] }),
6290
- forkProvider === "digitalocean" && /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6291
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Droplet Size:" }),
6292
- /* @__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 })
6293
6823
  ] }),
6294
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6295
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "AI Provider:" }),
6296
- /* @__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 })
6297
6827
  ] }),
6298
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6299
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Model:" }),
6300
- /* @__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 })
6301
6831
  ] }),
6302
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6303
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Channel:" }),
6304
- /* @__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" })
6305
6835
  ] })
6306
6836
  ]
6307
6837
  }
6308
6838
  ),
6309
- error && /* @__PURE__ */ jsx10("text", { fg: t.status.error, marginTop: 1, children: error }),
6310
- /* @__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" })
6311
6841
  ] });
6312
6842
  }
6313
6843
  };
@@ -6325,34 +6855,464 @@ function TemplatesView({ context }) {
6325
6855
  return renderForking();
6326
6856
  }
6327
6857
  };
6328
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6329
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginBottom: 2, children: [
6330
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "/templates" }),
6331
- /* @__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" })
6332
6862
  ] }),
6333
6863
  renderContent()
6334
6864
  ] });
6335
6865
  }
6336
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
+
6337
7296
  // src/App.tsx
6338
- import { jsx as jsx11 } from "@opentui/react/jsx-runtime";
7297
+ import { jsx as jsx13 } from "@opentui/react/jsx-runtime";
6339
7298
  function App() {
6340
- const renderer = useRenderer();
6341
- const [currentView, setCurrentView] = useState10("home");
6342
- const [selectedDeployment, setSelectedDeployment] = useState10(null);
6343
- 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(() => {
6344
7303
  try {
6345
7304
  return getAllDeployments();
6346
7305
  } catch {
6347
7306
  return [];
6348
7307
  }
6349
7308
  });
6350
- const [selectedTemplate, setSelectedTemplate] = useState10(null);
6351
- const wasDraggingRef = useRef5(false);
6352
- const handleMouseDrag = useCallback3(() => {
7309
+ const [selectedTemplate, setSelectedTemplate] = useState12(null);
7310
+ const [editingDeployment, setEditingDeployment] = useState12(null);
7311
+ const wasDraggingRef = useRef6(false);
7312
+ const handleMouseDrag = useCallback4(() => {
6353
7313
  wasDraggingRef.current = true;
6354
7314
  }, []);
6355
- const handleMouseUp = useCallback3(() => {
7315
+ const handleMouseUp = useCallback4(() => {
6356
7316
  if (!wasDraggingRef.current) return;
6357
7317
  wasDraggingRef.current = false;
6358
7318
  const selection = renderer.getSelection();
@@ -6363,14 +7323,14 @@ function App() {
6363
7323
  }
6364
7324
  }
6365
7325
  }, [renderer]);
6366
- const refreshDeployments = useCallback3(() => {
7326
+ const refreshDeployments = useCallback4(() => {
6367
7327
  try {
6368
7328
  setDeployments(getAllDeployments());
6369
7329
  } catch {
6370
7330
  setDeployments([]);
6371
7331
  }
6372
7332
  }, []);
6373
- const navigateTo = useCallback3((view, deployment) => {
7333
+ const navigateTo = useCallback4((view, deployment) => {
6374
7334
  if (deployment !== void 0) {
6375
7335
  setSelectedDeployment(deployment);
6376
7336
  }
@@ -6383,35 +7343,41 @@ function App() {
6383
7343
  deployments,
6384
7344
  refreshDeployments,
6385
7345
  selectedTemplate,
6386
- setSelectedTemplate
7346
+ setSelectedTemplate,
7347
+ editingDeployment,
7348
+ setEditingDeployment
6387
7349
  };
6388
7350
  const renderView = () => {
6389
7351
  switch (currentView) {
6390
7352
  case "home":
6391
- return /* @__PURE__ */ jsx11(Home, { context });
7353
+ return /* @__PURE__ */ jsx13(Home, { context });
6392
7354
  case "new":
6393
- return /* @__PURE__ */ jsx11(NewDeployment, { context });
7355
+ return /* @__PURE__ */ jsx13(NewDeployment, { context });
7356
+ case "list":
7357
+ return /* @__PURE__ */ jsx13(ListView, { context });
6394
7358
  case "deploy":
6395
- return /* @__PURE__ */ jsx11(DeployView, { context });
7359
+ return /* @__PURE__ */ jsx13(DeployView, { context });
6396
7360
  case "deploying":
6397
- return /* @__PURE__ */ jsx11(DeployingView, { context });
7361
+ return /* @__PURE__ */ jsx13(DeployingView, { context });
6398
7362
  case "status":
6399
- return /* @__PURE__ */ jsx11(StatusView, { context });
7363
+ return /* @__PURE__ */ jsx13(StatusView, { context });
6400
7364
  case "ssh":
6401
- return /* @__PURE__ */ jsx11(SSHView, { context });
7365
+ return /* @__PURE__ */ jsx13(SSHView, { context });
6402
7366
  case "logs":
6403
- return /* @__PURE__ */ jsx11(LogsView, { context });
7367
+ return /* @__PURE__ */ jsx13(LogsView, { context });
7368
+ case "dashboard":
7369
+ return /* @__PURE__ */ jsx13(DashboardView, { context });
6404
7370
  case "destroy":
6405
- return /* @__PURE__ */ jsx11(DestroyView, { context });
7371
+ return /* @__PURE__ */ jsx13(DestroyView, { context });
6406
7372
  case "help":
6407
- return /* @__PURE__ */ jsx11(HelpView, { context });
7373
+ return /* @__PURE__ */ jsx13(HelpView, { context });
6408
7374
  case "templates":
6409
- return /* @__PURE__ */ jsx11(TemplatesView, { context });
7375
+ return /* @__PURE__ */ jsx13(TemplatesView, { context });
6410
7376
  default:
6411
- return /* @__PURE__ */ jsx11(Home, { context });
7377
+ return /* @__PURE__ */ jsx13(Home, { context });
6412
7378
  }
6413
7379
  };
6414
- return /* @__PURE__ */ jsx11(
7380
+ return /* @__PURE__ */ jsx13(
6415
7381
  "scrollbox",
6416
7382
  {
6417
7383
  width: "100%",
@@ -6438,12 +7404,40 @@ function App() {
6438
7404
  }
6439
7405
 
6440
7406
  // src/index.tsx
6441
- import { jsx as jsx12 } from "@opentui/react/jsx-runtime";
7407
+ import { jsx as jsx14 } from "@opentui/react/jsx-runtime";
7408
+ var args = process.argv.slice(2);
7409
+ if (args.includes("--version") || args.includes("-v")) {
7410
+ const require2 = createRequire(import.meta.url);
7411
+ const pkg = require2("../package.json");
7412
+ console.log(`clawcontrol v${pkg.version}`);
7413
+ process.exit(0);
7414
+ }
7415
+ if (args.includes("--help") || args.includes("-h")) {
7416
+ const require2 = createRequire(import.meta.url);
7417
+ const pkg = require2("../package.json");
7418
+ console.log(`
7419
+ clawcontrol v${pkg.version}
7420
+ ${pkg.description}
7421
+
7422
+ Usage:
7423
+ clawcontrol Launch the interactive TUI
7424
+ clawcontrol --help Show this help message
7425
+ clawcontrol --version Show the version number
7426
+
7427
+ Options:
7428
+ -h, --help Show this help message
7429
+ -v, --version Show the version number
7430
+
7431
+ Documentation & source:
7432
+ https://github.com/ipenywis/clawcontrol
7433
+ `);
7434
+ process.exit(0);
7435
+ }
6442
7436
  async function main() {
6443
7437
  const renderer = await createCliRenderer({
6444
7438
  useMouse: true
6445
7439
  });
6446
7440
  const root = createRoot(renderer);
6447
- root.render(/* @__PURE__ */ jsx12(App, {}));
7441
+ root.render(/* @__PURE__ */ jsx14(App, {}));
6448
7442
  }
6449
7443
  main().catch(console.error);