clawcontrol 0.1.6 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1834 -728
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,14 +1,16 @@
1
1
  // src/index.tsx
2
2
  import { createRequire } from "module";
3
+ import { release } from "os";
3
4
  import { createCliRenderer } from "@opentui/core";
4
5
  import { createRoot } from "@opentui/react";
5
6
 
6
7
  // src/App.tsx
7
- import { useState as useState10, useCallback as useCallback3, useRef as useRef5 } from "react";
8
- import { useRenderer } from "@opentui/react";
8
+ import { useState as useState12, useCallback as useCallback4, useRef as useRef7 } from "react";
9
+ import { useRenderer as useRenderer2 } from "@opentui/react";
9
10
 
10
11
  // src/components/Home.tsx
11
- import { useState } from "react";
12
+ import { useState, useRef } from "react";
13
+ import { useKeyboard } from "@opentui/react";
12
14
 
13
15
  // src/theme.ts
14
16
  var palette = {
@@ -142,10 +144,12 @@ var LOGO = `
142
144
  `;
143
145
  var COMMANDS = [
144
146
  { name: "/new", description: "Initialize a new deployment" },
147
+ { name: "/list", description: "List, edit, or fork deployments" },
145
148
  { name: "/deploy", description: "Deploy an initialized configuration" },
146
149
  { name: "/status", description: "View deployment status" },
147
150
  { name: "/ssh", description: "SSH into a deployment" },
148
151
  { name: "/logs", description: "View deployment logs" },
152
+ { name: "/dashboard", description: "Open OpenClaw dashboard in browser" },
149
153
  { name: "/destroy", description: "Destroy a deployment" },
150
154
  { name: "/templates", description: "Manage deployment templates" },
151
155
  { name: "/help", description: "Show help" }
@@ -153,25 +157,53 @@ var COMMANDS = [
153
157
  function Home({ context }) {
154
158
  const [inputValue, setInputValue] = useState("");
155
159
  const [error, setError] = useState(null);
160
+ const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(-1);
161
+ const viewMap = {
162
+ "/new": "new",
163
+ "/list": "list",
164
+ "/deploy": "deploy",
165
+ "/status": "status",
166
+ "/ssh": "ssh",
167
+ "/logs": "logs",
168
+ "/dashboard": "dashboard",
169
+ "/destroy": "destroy",
170
+ "/templates": "templates",
171
+ "/help": "help"
172
+ };
173
+ const filteredCommands = inputValue.length >= 2 && inputValue.startsWith("/") ? COMMANDS.filter((cmd) => cmd.name.startsWith(inputValue.toLowerCase())) : [];
174
+ const stateRef = useRef({ selectedSuggestionIndex, filteredCommands, inputValue });
175
+ stateRef.current = { selectedSuggestionIndex, filteredCommands, inputValue };
156
176
  const handleCommand = (command) => {
157
177
  const cmd = command.trim().toLowerCase();
158
178
  setError(null);
159
- const viewMap = {
160
- "/new": "new",
161
- "/deploy": "deploy",
162
- "/status": "status",
163
- "/ssh": "ssh",
164
- "/logs": "logs",
165
- "/destroy": "destroy",
166
- "/templates": "templates",
167
- "/help": "help"
168
- };
169
179
  if (viewMap[cmd]) {
170
180
  context.navigateTo(viewMap[cmd]);
171
181
  } else if (cmd.startsWith("/")) {
172
182
  setError(`Unknown command: ${cmd}. Type /help for available commands.`);
173
183
  }
174
184
  };
185
+ useKeyboard((key) => {
186
+ const s = stateRef.current;
187
+ if (s.filteredCommands.length === 0) return;
188
+ if (key.name === "down") {
189
+ setSelectedSuggestionIndex(
190
+ (prev) => prev < s.filteredCommands.length - 1 ? prev + 1 : 0
191
+ );
192
+ } else if (key.name === "up") {
193
+ setSelectedSuggestionIndex(
194
+ (prev) => prev > 0 ? prev - 1 : s.filteredCommands.length - 1
195
+ );
196
+ } else if (key.name === "tab") {
197
+ if (s.selectedSuggestionIndex >= 0 && s.selectedSuggestionIndex < s.filteredCommands.length) {
198
+ const cmd = s.filteredCommands[s.selectedSuggestionIndex].name;
199
+ setInputValue("");
200
+ setSelectedSuggestionIndex(-1);
201
+ handleCommand(cmd);
202
+ }
203
+ } else if (key.name === "escape") {
204
+ setSelectedSuggestionIndex(-1);
205
+ }
206
+ });
175
207
  return /* @__PURE__ */ jsxs("box", { flexDirection: "column", width: "100%", height: "100%", children: [
176
208
  /* @__PURE__ */ jsx(
177
209
  "scrollbox",
@@ -223,7 +255,7 @@ function Home({ context }) {
223
255
  children: [
224
256
  /* @__PURE__ */ jsx("text", { fg: t.fg.primary, children: "Available Commands" }),
225
257
  /* @__PURE__ */ jsx("box", { flexDirection: "column", marginTop: 1, children: COMMANDS.map((cmd) => /* @__PURE__ */ jsxs("box", { flexDirection: "row", children: [
226
- /* @__PURE__ */ jsx("text", { fg: t.accent, width: 12, children: cmd.name }),
258
+ /* @__PURE__ */ jsx("text", { fg: t.accent, width: 14, children: cmd.name }),
227
259
  /* @__PURE__ */ jsx("text", { fg: t.fg.secondary, children: cmd.description })
228
260
  ] }, cmd.name)) })
229
261
  ]
@@ -274,9 +306,19 @@ function Home({ context }) {
274
306
  placeholder: "Type a command (e.g., /new)...",
275
307
  focused: true,
276
308
  width: "100%",
277
- onInput: (value) => setInputValue(value),
309
+ onInput: (value) => {
310
+ setInputValue(value);
311
+ const matches = value.length >= 2 && value.startsWith("/") ? COMMANDS.filter((cmd) => cmd.name.startsWith(value.toLowerCase())) : [];
312
+ setSelectedSuggestionIndex(matches.length > 0 ? 0 : -1);
313
+ },
278
314
  onSubmit: (value) => {
279
- if (typeof value === "string" && typeof value.trim === "function" && value.trim()) {
315
+ const s = stateRef.current;
316
+ if (s.selectedSuggestionIndex >= 0 && s.selectedSuggestionIndex < s.filteredCommands.length) {
317
+ const cmd = s.filteredCommands[s.selectedSuggestionIndex].name;
318
+ setInputValue("");
319
+ setSelectedSuggestionIndex(-1);
320
+ handleCommand(cmd);
321
+ } else if (typeof value === "string" && typeof value.trim === "function" && value.trim()) {
280
322
  handleCommand(value);
281
323
  setInputValue("");
282
324
  }
@@ -286,14 +328,22 @@ function Home({ context }) {
286
328
  }
287
329
  )
288
330
  }
289
- )
331
+ ),
332
+ filteredCommands.length > 0 && /* @__PURE__ */ jsx("box", { flexDirection: "column", paddingLeft: 1, backgroundColor: t.bg.elevated, children: filteredCommands.map((cmd, i) => {
333
+ const selected = i === selectedSuggestionIndex;
334
+ return /* @__PURE__ */ jsxs("box", { flexDirection: "row", height: 1, overflow: "hidden", backgroundColor: selected ? t.selection.bg : t.bg.elevated, children: [
335
+ /* @__PURE__ */ jsx("text", { fg: selected ? t.selection.indicator : t.fg.muted, children: selected ? "> " : " " }),
336
+ /* @__PURE__ */ jsx("text", { fg: selected ? t.accent : t.fg.primary, width: 14, children: cmd.name }),
337
+ /* @__PURE__ */ jsx("text", { fg: t.fg.secondary, children: cmd.description })
338
+ ] }, cmd.name);
339
+ }) })
290
340
  ] })
291
341
  ] });
292
342
  }
293
343
 
294
344
  // src/components/NewDeployment.tsx
295
- import { useState as useState2, useRef, useEffect } from "react";
296
- import { useKeyboard } from "@opentui/react";
345
+ import { useState as useState2, useRef as useRef2, useEffect } from "react";
346
+ import { useKeyboard as useKeyboard2 } from "@opentui/react";
297
347
  import { appendFileSync } from "fs";
298
348
  import { homedir as homedir3 } from "os";
299
349
  import { join as join4 } from "path";
@@ -372,7 +422,8 @@ var DeploymentConfigSchema = z.object({
372
422
  hetzner: HetznerConfigSchema.optional(),
373
423
  digitalocean: DigitalOceanConfigSchema.optional(),
374
424
  openclawConfig: OpenClawConfigSchema,
375
- openclawAgent: OpenClawAgentConfigSchema.optional()
425
+ openclawAgent: OpenClawAgentConfigSchema.optional(),
426
+ skipTailscale: z.boolean().optional()
376
427
  });
377
428
  var DeploymentStateSchema = z.object({
378
429
  status: DeploymentStatusSchema,
@@ -388,6 +439,7 @@ var DeploymentStateSchema = z.object({
388
439
  retryCount: z.number()
389
440
  })
390
441
  ),
442
+ gatewayToken: z.string().optional(),
391
443
  lastError: z.string().optional(),
392
444
  deployedAt: z.string().optional(),
393
445
  updatedAt: z.string()
@@ -531,6 +583,14 @@ function deleteDeployment(name) {
531
583
  }
532
584
  rmSync(deploymentDir, { recursive: true, force: true });
533
585
  }
586
+ function updateDeploymentConfig(name, config) {
587
+ const configPath = getConfigPath(name);
588
+ if (!existsSync(configPath)) {
589
+ throw new Error(`Deployment "${name}" not found`);
590
+ }
591
+ const validated = DeploymentConfigSchema.parse(config);
592
+ writeFileSync(configPath, JSON.stringify(validated, null, 2));
593
+ }
534
594
  function validateDeploymentName(name) {
535
595
  if (!name || name.trim() === "") {
536
596
  return { valid: false, error: "Deployment name cannot be empty" };
@@ -1196,20 +1256,36 @@ var DO_DROPLET_SIZES = [
1196
1256
  { slug: "s-4vcpu-8gb", label: "4 vCPU, 8GB RAM, 160GB SSD", price: "$48/mo" },
1197
1257
  { slug: "s-8vcpu-16gb", label: "8 vCPU, 16GB RAM, 320GB SSD", price: "$96/mo" }
1198
1258
  ];
1199
- function getStepList(provider, activeTemplate) {
1259
+ function getStepList(provider, activeTemplate, editMode) {
1260
+ if (editMode === "edit") {
1261
+ const steps = ["api_key_choose"];
1262
+ if (provider === "digitalocean") {
1263
+ steps.push("droplet_size");
1264
+ }
1265
+ steps.push("ai_api_key_choose", "model", "telegram_token_choose", "telegram_allow_choose", "confirm");
1266
+ return steps;
1267
+ }
1268
+ if (editMode === "fork") {
1269
+ const steps = ["name", "api_key_choose"];
1270
+ if (provider === "digitalocean") {
1271
+ steps.push("droplet_size");
1272
+ }
1273
+ steps.push("ai_api_key_choose", "model", "telegram_token_choose", "telegram_allow_choose", "tailscale", "confirm");
1274
+ return steps;
1275
+ }
1200
1276
  const base = ["template_choice"];
1201
1277
  if (!activeTemplate) {
1202
1278
  base.push("name", "provider", "api_key_choose");
1203
1279
  if (provider === "digitalocean") {
1204
1280
  base.push("droplet_size");
1205
1281
  }
1206
- base.push("ai_provider", "ai_api_key_choose", "model", "telegram_token_choose", "telegram_allow_choose", "confirm");
1282
+ base.push("ai_provider", "ai_api_key_choose", "model", "telegram_token_choose", "telegram_allow_choose", "tailscale", "confirm");
1207
1283
  } else {
1208
1284
  base.push("name", "api_key_choose");
1209
1285
  if (activeTemplate.provider === "digitalocean") {
1210
1286
  base.push("droplet_size");
1211
1287
  }
1212
- base.push("ai_api_key_choose", "model", "telegram_token_choose", "telegram_allow_choose", "confirm");
1288
+ base.push("ai_api_key_choose", "model", "telegram_token_choose", "telegram_allow_choose", "tailscale", "confirm");
1213
1289
  }
1214
1290
  return base;
1215
1291
  }
@@ -1236,19 +1312,39 @@ function resolveStepForProgress(s) {
1236
1312
  }
1237
1313
  }
1238
1314
  function NewDeployment({ context }) {
1315
+ const [editingConfig, setEditingConfig] = useState2(null);
1316
+ const [editMode, setEditMode] = useState2(null);
1239
1317
  const [templateChoices, setTemplateChoices] = useState2([]);
1240
1318
  const [selectedTemplateIndex, setSelectedTemplateIndex] = useState2(0);
1241
1319
  const [activeTemplate, setActiveTemplate] = useState2(null);
1242
1320
  const [step, setStep] = useState2(() => {
1321
+ if (context.editingDeployment) {
1322
+ return context.editingDeployment.mode === "edit" ? "api_key_choose" : "name";
1323
+ }
1243
1324
  if (context.selectedTemplate) return "name";
1244
1325
  return "template_choice";
1245
1326
  });
1246
- const [name, setName] = useState2("");
1327
+ const [name, setName] = useState2(() => {
1328
+ if (context.editingDeployment?.mode === "edit") return context.editingDeployment.config.name;
1329
+ return "";
1330
+ });
1247
1331
  const [provider, setProvider] = useState2(() => {
1332
+ if (context.editingDeployment) return context.editingDeployment.config.provider;
1248
1333
  return context.selectedTemplate?.provider ?? "hetzner";
1249
1334
  });
1250
- const [apiKey, setApiKey] = useState2("");
1335
+ const [apiKey, setApiKey] = useState2(() => {
1336
+ if (context.editingDeployment) {
1337
+ const cfg = context.editingDeployment.config;
1338
+ if (cfg.provider === "hetzner" && cfg.hetzner) return cfg.hetzner.apiKey;
1339
+ if (cfg.provider === "digitalocean" && cfg.digitalocean) return cfg.digitalocean.apiKey;
1340
+ }
1341
+ return "";
1342
+ });
1251
1343
  const [selectedDropletSizeIndex, setSelectedDropletSizeIndex] = useState2(() => {
1344
+ if (context.editingDeployment?.config.digitalocean) {
1345
+ const idx = DO_DROPLET_SIZES.findIndex((s) => s.slug === context.editingDeployment.config.digitalocean.size);
1346
+ return idx >= 0 ? idx : 0;
1347
+ }
1252
1348
  if (context.selectedTemplate?.digitalocean) {
1253
1349
  const idx = DO_DROPLET_SIZES.findIndex((s) => s.slug === context.selectedTemplate.digitalocean.size);
1254
1350
  return idx >= 0 ? idx : 0;
@@ -1256,17 +1352,40 @@ function NewDeployment({ context }) {
1256
1352
  return 0;
1257
1353
  });
1258
1354
  const [aiProvider, setAiProvider] = useState2(() => {
1355
+ if (context.editingDeployment?.config.openclawAgent) return context.editingDeployment.config.openclawAgent.aiProvider;
1259
1356
  return context.selectedTemplate?.aiProvider ?? "";
1260
1357
  });
1261
- const [aiApiKey, setAiApiKey] = useState2("");
1358
+ const [aiApiKey, setAiApiKey] = useState2(() => {
1359
+ if (context.editingDeployment?.config.openclawAgent) return context.editingDeployment.config.openclawAgent.aiApiKey;
1360
+ return "";
1361
+ });
1262
1362
  const [model, setModel] = useState2(() => {
1363
+ if (context.editingDeployment?.config.openclawAgent) return context.editingDeployment.config.openclawAgent.model;
1263
1364
  return context.selectedTemplate?.model ?? "";
1264
1365
  });
1265
- const [telegramBotToken, setTelegramBotToken] = useState2("");
1266
- const [telegramAllowFrom, setTelegramAllowFrom] = useState2("");
1366
+ const [telegramBotToken, setTelegramBotToken] = useState2(() => {
1367
+ if (context.editingDeployment?.config.openclawAgent) return context.editingDeployment.config.openclawAgent.telegramBotToken;
1368
+ return "";
1369
+ });
1370
+ const [telegramAllowFrom, setTelegramAllowFrom] = useState2(() => {
1371
+ if (context.editingDeployment?.config.openclawAgent) return context.editingDeployment.config.openclawAgent.telegramAllowFrom ?? "";
1372
+ return "";
1373
+ });
1374
+ const [enableTailscale, setEnableTailscale] = useState2(() => {
1375
+ if (context.editingDeployment?.mode === "fork") return !context.editingDeployment.config.skipTailscale;
1376
+ return false;
1377
+ });
1378
+ const [selectedTailscaleIndex, setSelectedTailscaleIndex] = useState2(() => {
1379
+ if (context.editingDeployment?.mode === "fork" && !context.editingDeployment.config.skipTailscale) return 1;
1380
+ return 0;
1381
+ });
1267
1382
  const [error, setError] = useState2(null);
1268
1383
  const [isValidating, setIsValidating] = useState2(false);
1269
1384
  const [selectedProviderIndex, setSelectedProviderIndex] = useState2(() => {
1385
+ if (context.editingDeployment) {
1386
+ const idx = SUPPORTED_PROVIDERS.indexOf(context.editingDeployment.config.provider);
1387
+ return idx >= 0 ? idx : 0;
1388
+ }
1270
1389
  if (context.selectedTemplate) {
1271
1390
  const idx = SUPPORTED_PROVIDERS.indexOf(context.selectedTemplate.provider);
1272
1391
  return idx >= 0 ? idx : 0;
@@ -1274,6 +1393,10 @@ function NewDeployment({ context }) {
1274
1393
  return 0;
1275
1394
  });
1276
1395
  const [selectedAiProviderIndex, setSelectedAiProviderIndex] = useState2(() => {
1396
+ if (context.editingDeployment?.config.openclawAgent) {
1397
+ const idx = AI_PROVIDERS.findIndex((p) => p.name === context.editingDeployment.config.openclawAgent.aiProvider);
1398
+ return idx >= 0 ? idx : 0;
1399
+ }
1277
1400
  if (context.selectedTemplate) {
1278
1401
  const idx = AI_PROVIDERS.findIndex((p) => p.name === context.selectedTemplate.aiProvider);
1279
1402
  return idx >= 0 ? idx : 0;
@@ -1286,12 +1409,39 @@ function NewDeployment({ context }) {
1286
1409
  const [savedTelegramUsers, setSavedTelegramUsers] = useState2([]);
1287
1410
  const [selectedSavedKeyIndex, setSelectedSavedKeyIndex] = useState2(0);
1288
1411
  const [newKeyName, setNewKeyName] = useState2("");
1289
- const [apiKeyFromSaved, setApiKeyFromSaved] = useState2(false);
1290
- const [aiApiKeyFromSaved, setAiApiKeyFromSaved] = useState2(false);
1291
- const [telegramTokenFromSaved, setTelegramTokenFromSaved] = useState2(false);
1292
- const [telegramAllowFromSaved, setTelegramAllowFromSaved] = useState2(false);
1412
+ const [apiKeyFromSaved, setApiKeyFromSaved] = useState2(() => !!context.editingDeployment);
1413
+ const [aiApiKeyFromSaved, setAiApiKeyFromSaved] = useState2(() => !!context.editingDeployment);
1414
+ const [telegramTokenFromSaved, setTelegramTokenFromSaved] = useState2(() => !!context.editingDeployment);
1415
+ const [telegramAllowFromSaved, setTelegramAllowFromSaved] = useState2(() => !!context.editingDeployment);
1293
1416
  useEffect(() => {
1294
- if (context.selectedTemplate) {
1417
+ if (context.editingDeployment) {
1418
+ const ed = context.editingDeployment;
1419
+ setEditingConfig(ed.config);
1420
+ setEditMode(ed.mode);
1421
+ const syntheticTemplate = {
1422
+ id: "__editing__",
1423
+ name: ed.config.name,
1424
+ description: "Editing deployment",
1425
+ builtIn: false,
1426
+ createdAt: ed.config.createdAt,
1427
+ provider: ed.config.provider,
1428
+ hetzner: ed.config.hetzner ? {
1429
+ serverType: ed.config.hetzner.serverType,
1430
+ location: ed.config.hetzner.location,
1431
+ image: ed.config.hetzner.image
1432
+ } : void 0,
1433
+ digitalocean: ed.config.digitalocean ? {
1434
+ size: ed.config.digitalocean.size,
1435
+ region: ed.config.digitalocean.region,
1436
+ image: ed.config.digitalocean.image
1437
+ } : void 0,
1438
+ aiProvider: ed.config.openclawAgent?.aiProvider ?? "",
1439
+ model: ed.config.openclawAgent?.model ?? "",
1440
+ channel: ed.config.openclawAgent?.channel ?? "telegram"
1441
+ };
1442
+ setActiveTemplate(syntheticTemplate);
1443
+ context.setEditingDeployment(null);
1444
+ } else if (context.selectedTemplate) {
1295
1445
  setActiveTemplate(context.selectedTemplate);
1296
1446
  context.setSelectedTemplate(null);
1297
1447
  }
@@ -1324,7 +1474,7 @@ function NewDeployment({ context }) {
1324
1474
  setSelectedSavedKeyIndex(0);
1325
1475
  }
1326
1476
  }, [step]);
1327
- const stateRef = useRef({
1477
+ const stateRef = useRef2({
1328
1478
  name,
1329
1479
  provider,
1330
1480
  apiKey,
@@ -1336,6 +1486,8 @@ function NewDeployment({ context }) {
1336
1486
  step,
1337
1487
  selectedDropletSizeIndex,
1338
1488
  activeTemplate,
1489
+ editingConfig,
1490
+ editMode,
1339
1491
  savedProviderKeys,
1340
1492
  savedAiKeys,
1341
1493
  savedTelegramTokens,
@@ -1345,7 +1497,9 @@ function NewDeployment({ context }) {
1345
1497
  apiKeyFromSaved,
1346
1498
  aiApiKeyFromSaved,
1347
1499
  telegramTokenFromSaved,
1348
- telegramAllowFromSaved
1500
+ telegramAllowFromSaved,
1501
+ enableTailscale,
1502
+ selectedTailscaleIndex
1349
1503
  });
1350
1504
  stateRef.current = {
1351
1505
  name,
@@ -1359,6 +1513,8 @@ function NewDeployment({ context }) {
1359
1513
  step,
1360
1514
  selectedDropletSizeIndex,
1361
1515
  activeTemplate,
1516
+ editingConfig,
1517
+ editMode,
1362
1518
  savedProviderKeys,
1363
1519
  savedAiKeys,
1364
1520
  savedTelegramTokens,
@@ -1368,10 +1524,12 @@ function NewDeployment({ context }) {
1368
1524
  apiKeyFromSaved,
1369
1525
  aiApiKeyFromSaved,
1370
1526
  telegramTokenFromSaved,
1371
- telegramAllowFromSaved
1527
+ telegramAllowFromSaved,
1528
+ enableTailscale,
1529
+ selectedTailscaleIndex
1372
1530
  };
1373
1531
  debugLog(`RENDER: step=${step}, apiKey.length=${apiKey?.length ?? "null"}`);
1374
- const stepList = getStepList(provider, activeTemplate);
1532
+ const stepList = getStepList(provider, activeTemplate, editMode ?? void 0);
1375
1533
  const handleConfirmFromRef = () => {
1376
1534
  const s = stateRef.current;
1377
1535
  debugLog(`handleConfirmFromRef CALLED: apiKey.length=${s.apiKey?.length ?? "null"}`);
@@ -1410,7 +1568,7 @@ function NewDeployment({ context }) {
1410
1568
  const config = {
1411
1569
  name: s.name,
1412
1570
  provider: s.provider,
1413
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1571
+ createdAt: s.editingConfig?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1414
1572
  hetzner: s.provider === "hetzner" ? {
1415
1573
  apiKey: s.apiKey,
1416
1574
  serverType: tmpl?.hetzner?.serverType ?? "cpx11",
@@ -1424,6 +1582,7 @@ function NewDeployment({ context }) {
1424
1582
  image: tmpl?.digitalocean?.image ?? "ubuntu-24-04-x64"
1425
1583
  } : void 0,
1426
1584
  openclawConfig: void 0,
1585
+ skipTailscale: !s.enableTailscale,
1427
1586
  openclawAgent: {
1428
1587
  aiProvider: s.aiProvider,
1429
1588
  aiApiKey: s.aiApiKey,
@@ -1433,14 +1592,19 @@ function NewDeployment({ context }) {
1433
1592
  telegramAllowFrom: s.telegramAllowFrom
1434
1593
  }
1435
1594
  };
1436
- createDeployment(config);
1595
+ if (s.editingConfig && s.editMode === "edit") {
1596
+ const updatedConfig = { ...config, name: s.editingConfig.name, createdAt: s.editingConfig.createdAt };
1597
+ updateDeploymentConfig(s.editingConfig.name, updatedConfig);
1598
+ } else {
1599
+ createDeployment(config);
1600
+ }
1437
1601
  context.refreshDeployments();
1438
1602
  setStep("complete");
1439
1603
  } catch (err) {
1440
- setError(`Failed to create deployment: ${err instanceof Error ? err.message : String(err)}`);
1604
+ setError(`Failed to ${s.editMode === "edit" ? "update" : "create"} deployment: ${err instanceof Error ? err.message : String(err)}`);
1441
1605
  }
1442
1606
  };
1443
- useKeyboard((key) => {
1607
+ useKeyboard2((key) => {
1444
1608
  const currentState = stateRef.current;
1445
1609
  debugLog(`useKeyboard: key=${key.name}, step=${currentState.step}`);
1446
1610
  if (currentState.step === "template_choice") {
@@ -1629,7 +1793,7 @@ function NewDeployment({ context }) {
1629
1793
  setTelegramAllowFrom(saved.value);
1630
1794
  setTelegramAllowFromSaved(true);
1631
1795
  setError(null);
1632
- setStep("confirm");
1796
+ setStep(currentState.editMode === "edit" ? "confirm" : "tailscale");
1633
1797
  }
1634
1798
  } else if (key.name === "escape") {
1635
1799
  setStep("telegram_token_choose");
@@ -1666,13 +1830,26 @@ function NewDeployment({ context }) {
1666
1830
  setStep("telegram_allow_name");
1667
1831
  setNewKeyName("");
1668
1832
  } else if (key.name === "n" || key.name === "escape") {
1833
+ setStep(currentState.editMode === "edit" ? "confirm" : "tailscale");
1834
+ }
1835
+ } else if (currentState.step === "tailscale") {
1836
+ const TAILSCALE_OPTIONS = ["Skip", "Configure Tailscale"];
1837
+ if (key.name === "up") {
1838
+ setSelectedTailscaleIndex((prev) => Math.max(0, prev - 1));
1839
+ } else if (key.name === "down") {
1840
+ setSelectedTailscaleIndex((prev) => Math.min(TAILSCALE_OPTIONS.length - 1, prev + 1));
1841
+ } else if (key.name === "return") {
1842
+ setEnableTailscale(currentState.selectedTailscaleIndex === 1);
1843
+ setError(null);
1669
1844
  setStep("confirm");
1845
+ } else if (key.name === "escape") {
1846
+ setStep("telegram_allow_choose");
1670
1847
  }
1671
1848
  } else if (currentState.step === "confirm") {
1672
1849
  if (key.name === "y" || key.name === "return") {
1673
1850
  handleConfirmFromRef();
1674
1851
  } else if (key.name === "n" || key.name === "escape") {
1675
- setStep("telegram_allow_choose");
1852
+ setStep(currentState.editMode === "edit" ? "telegram_allow_choose" : "tailscale");
1676
1853
  }
1677
1854
  } else if (currentState.step === "complete") {
1678
1855
  context.navigateTo("home");
@@ -1794,7 +1971,7 @@ function NewDeployment({ context }) {
1794
1971
  }
1795
1972
  setError(null);
1796
1973
  if (telegramAllowFromSaved) {
1797
- setStep("confirm");
1974
+ setStep(editMode === "edit" ? "confirm" : "tailscale");
1798
1975
  } else {
1799
1976
  setStep("telegram_allow_save");
1800
1977
  }
@@ -2508,10 +2685,10 @@ function NewDeployment({ context }) {
2508
2685
  setNewKeyName(value);
2509
2686
  }
2510
2687
  },
2511
- onSubmit: () => handleSaveKeyName("telegram-user", telegramAllowFrom, "confirm"),
2688
+ onSubmit: () => handleSaveKeyName("telegram-user", telegramAllowFrom, editMode === "edit" ? "confirm" : "tailscale"),
2512
2689
  onKeyDown: (e) => {
2513
2690
  if (e.name === "escape") {
2514
- setStep("confirm");
2691
+ setStep(editMode === "edit" ? "confirm" : "tailscale");
2515
2692
  }
2516
2693
  }
2517
2694
  }
@@ -2519,6 +2696,41 @@ function NewDeployment({ context }) {
2519
2696
  error && /* @__PURE__ */ jsx2("text", { fg: t.status.error, marginTop: 1, children: error }),
2520
2697
  /* @__PURE__ */ jsx2("text", { fg: t.fg.muted, marginTop: 2, children: "Press Enter to save, Esc to skip" })
2521
2698
  ] });
2699
+ case "tailscale": {
2700
+ const tailscaleOptions = [
2701
+ { label: "Skip", description: "Use SSH tunnel via /dashboard instead" },
2702
+ { label: "Configure Tailscale", description: "Private VPN between your devices and the server" }
2703
+ ];
2704
+ return /* @__PURE__ */ jsxs2("box", { flexDirection: "column", children: [
2705
+ /* @__PURE__ */ jsxs2("text", { fg: t.accent, children: [
2706
+ "Step ",
2707
+ currentStepNumber("tailscale"),
2708
+ ": Tailscale Setup (Optional)"
2709
+ ] }),
2710
+ /* @__PURE__ */ jsx2("text", { fg: t.fg.secondary, marginTop: 1, children: "Tailscale creates a private VPN between your devices and your OpenClaw server." }),
2711
+ /* @__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)." }),
2712
+ /* @__PURE__ */ jsx2(
2713
+ "box",
2714
+ {
2715
+ flexDirection: "column",
2716
+ borderStyle: "single",
2717
+ borderColor: t.border.default,
2718
+ marginTop: 1,
2719
+ padding: 1,
2720
+ children: tailscaleOptions.map((opt, i) => {
2721
+ const isSelected = i === selectedTailscaleIndex;
2722
+ return /* @__PURE__ */ jsxs2("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
2723
+ /* @__PURE__ */ jsx2("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
2724
+ /* @__PURE__ */ jsx2("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: opt.label }),
2725
+ /* @__PURE__ */ jsx2("text", { fg: isSelected ? t.fg.primary : t.fg.secondary, children: " - " + opt.description })
2726
+ ] }, opt.label);
2727
+ })
2728
+ }
2729
+ ),
2730
+ error && /* @__PURE__ */ jsx2("text", { fg: t.status.error, marginTop: 1, children: error }),
2731
+ /* @__PURE__ */ jsx2("text", { fg: t.fg.muted, marginTop: 1, children: "Press Enter to select, Esc to go back" })
2732
+ ] });
2733
+ }
2522
2734
  case "confirm":
2523
2735
  return /* @__PURE__ */ jsxs2("box", { flexDirection: "column", children: [
2524
2736
  /* @__PURE__ */ jsxs2("text", { fg: t.accent, children: [
@@ -2578,6 +2790,10 @@ function NewDeployment({ context }) {
2578
2790
  /* @__PURE__ */ jsxs2("box", { flexDirection: "row", children: [
2579
2791
  /* @__PURE__ */ jsx2("text", { fg: t.fg.secondary, width: 20, children: "Allow From:" }),
2580
2792
  /* @__PURE__ */ jsx2("text", { fg: t.fg.primary, children: telegramAllowFrom || "N/A" })
2793
+ ] }),
2794
+ /* @__PURE__ */ jsxs2("box", { flexDirection: "row", children: [
2795
+ /* @__PURE__ */ jsx2("text", { fg: t.fg.secondary, width: 20, children: "Tailscale:" }),
2796
+ /* @__PURE__ */ jsx2("text", { fg: enableTailscale ? t.status.success : t.fg.muted, children: enableTailscale ? "Enabled" : "Skipped" })
2581
2797
  ] })
2582
2798
  ]
2583
2799
  }
@@ -2587,7 +2803,7 @@ function NewDeployment({ context }) {
2587
2803
  ] });
2588
2804
  case "complete":
2589
2805
  return /* @__PURE__ */ jsxs2("box", { flexDirection: "column", children: [
2590
- /* @__PURE__ */ jsx2("text", { fg: t.status.success, children: "Deployment Configuration Created!" }),
2806
+ /* @__PURE__ */ jsx2("text", { fg: t.status.success, children: editMode === "edit" ? "Deployment Updated Successfully!" : "Deployment Configuration Created!" }),
2591
2807
  /* @__PURE__ */ jsxs2(
2592
2808
  "box",
2593
2809
  {
@@ -2597,14 +2813,10 @@ function NewDeployment({ context }) {
2597
2813
  padding: 1,
2598
2814
  marginTop: 1,
2599
2815
  children: [
2600
- /* @__PURE__ */ jsxs2("text", { fg: t.fg.primary, children: [
2601
- 'Your deployment "',
2602
- name,
2603
- '" has been initialized.'
2604
- ] }),
2816
+ /* @__PURE__ */ jsx2("text", { fg: t.fg.primary, children: editMode === "edit" ? `Your deployment "${editingConfig?.name ?? name}" has been updated.` : `Your deployment "${name}" has been initialized.` }),
2605
2817
  /* @__PURE__ */ jsxs2("text", { fg: t.fg.secondary, marginTop: 1, children: [
2606
2818
  "Configuration saved to: ~/.clawcontrol/deployments/",
2607
- name,
2819
+ editingConfig?.name ?? name,
2608
2820
  "/"
2609
2821
  ] }),
2610
2822
  /* @__PURE__ */ jsxs2("text", { fg: t.fg.secondary, marginTop: 1, children: [
@@ -2621,15 +2833,15 @@ function NewDeployment({ context }) {
2621
2833
  ]
2622
2834
  }
2623
2835
  ),
2624
- /* @__PURE__ */ jsx2("text", { fg: t.accent, marginTop: 2, children: "Next step: Run /deploy to deploy this configuration" }),
2836
+ editMode !== "edit" && /* @__PURE__ */ jsx2("text", { fg: t.accent, marginTop: 2, children: "Next step: Run /deploy to deploy this configuration" }),
2625
2837
  /* @__PURE__ */ jsx2("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
2626
2838
  ] });
2627
2839
  }
2628
2840
  };
2629
2841
  return /* @__PURE__ */ jsxs2("box", { flexDirection: "column", width: "100%", padding: 1, children: [
2630
2842
  /* @__PURE__ */ jsxs2("box", { flexDirection: "row", marginBottom: 2, children: [
2631
- /* @__PURE__ */ jsx2("text", { fg: t.accent, children: "/new" }),
2632
- /* @__PURE__ */ jsx2("text", { fg: t.fg.secondary, children: " - Initialize a new deployment" })
2843
+ /* @__PURE__ */ jsx2("text", { fg: t.accent, children: editMode === "edit" ? "/list" : "/new" }),
2844
+ /* @__PURE__ */ jsx2("text", { fg: t.fg.secondary, children: editMode === "edit" ? ` - Edit Deployment: ${editingConfig?.name ?? ""}` : editMode === "fork" ? " - Fork Deployment" : " - Initialize a new deployment" })
2633
2845
  ] }),
2634
2846
  /* @__PURE__ */ jsx2("box", { flexDirection: "row", marginBottom: 2, children: stepList.map((s, i) => {
2635
2847
  const resolvedStep = resolveStepForProgress(step);
@@ -2644,46 +2856,81 @@ function NewDeployment({ context }) {
2644
2856
  ] });
2645
2857
  }
2646
2858
 
2647
- // src/components/DeployView.tsx
2648
- import { useState as useState3 } from "react";
2649
- import { useKeyboard as useKeyboard2 } from "@opentui/react";
2650
- import { jsx as jsx3, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
2651
- function DeployView({ context }) {
2859
+ // src/components/ListView.tsx
2860
+ import { useState as useState3, useRef as useRef3 } from "react";
2861
+ import { useKeyboard as useKeyboard3 } from "@opentui/react";
2862
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
2863
+ function ListView({ context }) {
2864
+ const [viewState, setViewState] = useState3("listing");
2652
2865
  const [selectedIndex, setSelectedIndex] = useState3(0);
2653
- const [confirmMode, setConfirmMode] = useState3(false);
2866
+ const [confirmText, setConfirmText] = useState3("");
2867
+ const [error, setError] = useState3(null);
2868
+ const [deletedName, setDeletedName] = useState3("");
2654
2869
  const deployments = context.deployments;
2655
- const notDeployed = deployments.filter((d) => d.state.status !== "deployed");
2656
- const deployed = deployments.filter((d) => d.state.status === "deployed");
2657
- const allDeployments = [...notDeployed, ...deployed];
2658
- const selectedDeployment = allDeployments[selectedIndex];
2659
- useKeyboard2((key) => {
2660
- if (allDeployments.length === 0) {
2870
+ const selectedDeployment = deployments[selectedIndex];
2871
+ const stateRef = useRef3({ viewState, selectedIndex });
2872
+ stateRef.current = { viewState, selectedIndex };
2873
+ useKeyboard3((key) => {
2874
+ const current = stateRef.current;
2875
+ if (deployments.length === 0) {
2661
2876
  context.navigateTo("home");
2662
2877
  return;
2663
2878
  }
2664
- if (confirmMode) {
2665
- if (key.name === "y" || key.name === "return") {
2666
- context.navigateTo("deploying", selectedDeployment.config.name);
2667
- } else if (key.name === "n" || key.name === "escape") {
2668
- setConfirmMode(false);
2669
- }
2670
- } else {
2671
- if (key.name === "up" && selectedIndex > 0) {
2672
- setSelectedIndex(selectedIndex - 1);
2673
- } else if (key.name === "down" && selectedIndex < allDeployments.length - 1) {
2674
- setSelectedIndex(selectedIndex + 1);
2879
+ if (current.viewState === "listing") {
2880
+ if (key.name === "up" && current.selectedIndex > 0) {
2881
+ setSelectedIndex(current.selectedIndex - 1);
2882
+ } else if (key.name === "down" && current.selectedIndex < deployments.length - 1) {
2883
+ setSelectedIndex(current.selectedIndex + 1);
2675
2884
  } else if (key.name === "return") {
2676
- setConfirmMode(true);
2885
+ setViewState("detail");
2677
2886
  } else if (key.name === "escape") {
2678
2887
  context.navigateTo("home");
2679
2888
  }
2889
+ } else if (current.viewState === "detail") {
2890
+ const dep = deployments[current.selectedIndex];
2891
+ if (!dep) return;
2892
+ if (key.name === "e" && dep.state.status === "initialized") {
2893
+ context.setEditingDeployment({ config: dep.config, mode: "edit" });
2894
+ context.navigateTo("new");
2895
+ } else if (key.name === "f") {
2896
+ context.setEditingDeployment({ config: dep.config, mode: "fork" });
2897
+ context.navigateTo("new");
2898
+ } else if (key.name === "d") {
2899
+ if (dep.state.status === "initialized") {
2900
+ setConfirmText("");
2901
+ setError(null);
2902
+ setViewState("delete_confirm");
2903
+ } else {
2904
+ setError("Deployed agents must be destroyed first using the /destroy command.");
2905
+ }
2906
+ } else if (key.name === "escape") {
2907
+ setError(null);
2908
+ setViewState("listing");
2909
+ }
2910
+ } else if (current.viewState === "success") {
2911
+ context.navigateTo("home");
2912
+ } else if (current.viewState === "error") {
2913
+ setError(null);
2914
+ setViewState("listing");
2680
2915
  }
2681
2916
  });
2682
- if (allDeployments.length === 0) {
2917
+ const handleDelete = (name) => {
2918
+ setViewState("deleting");
2919
+ try {
2920
+ deleteDeployment(name);
2921
+ setDeletedName(name);
2922
+ context.refreshDeployments();
2923
+ setViewState("success");
2924
+ } catch (err) {
2925
+ setError(err instanceof Error ? err.message : String(err));
2926
+ setViewState("error");
2927
+ }
2928
+ };
2929
+ if (deployments.length === 0) {
2683
2930
  return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
2684
2931
  /* @__PURE__ */ jsxs3("box", { flexDirection: "row", marginBottom: 2, children: [
2685
- /* @__PURE__ */ jsx3("text", { fg: t.accent, children: "/deploy" }),
2686
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, children: " - Deploy a configuration" })
2932
+ /* @__PURE__ */ jsx3("text", { fg: t.accent, children: "/list" }),
2933
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, children: " - Deployments" })
2687
2934
  ] }),
2688
2935
  /* @__PURE__ */ jsxs3(
2689
2936
  "box",
@@ -2694,135 +2941,410 @@ function DeployView({ context }) {
2694
2941
  padding: 1,
2695
2942
  children: [
2696
2943
  /* @__PURE__ */ jsx3("text", { fg: t.status.warning, children: "No deployments found!" }),
2697
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, marginTop: 1, children: "Run /new first to create a deployment configuration." })
2944
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, marginTop: 1, children: "Run /new to create a deployment." })
2698
2945
  ]
2699
2946
  }
2700
2947
  ),
2701
2948
  /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
2702
2949
  ] });
2703
2950
  }
2704
- if (confirmMode) {
2951
+ if (viewState === "listing") {
2705
2952
  return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
2706
2953
  /* @__PURE__ */ jsxs3("box", { flexDirection: "row", marginBottom: 2, children: [
2707
- /* @__PURE__ */ jsx3("text", { fg: t.accent, children: "/deploy" }),
2708
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, children: " - Confirm deployment" })
2954
+ /* @__PURE__ */ jsx3("text", { fg: t.accent, children: "/list" }),
2955
+ /* @__PURE__ */ jsxs3("text", { fg: t.fg.secondary, children: [
2956
+ " - Deployments (",
2957
+ deployments.length,
2958
+ ")"
2959
+ ] })
2709
2960
  ] }),
2710
- /* @__PURE__ */ jsxs3(
2961
+ /* @__PURE__ */ jsx3(
2711
2962
  "box",
2712
2963
  {
2713
2964
  flexDirection: "column",
2714
2965
  borderStyle: "single",
2715
2966
  borderColor: t.border.default,
2716
2967
  padding: 1,
2968
+ children: deployments.map((dep, index) => {
2969
+ const isSelected = index === selectedIndex;
2970
+ return /* @__PURE__ */ jsxs3(
2971
+ "box",
2972
+ {
2973
+ flexDirection: "row",
2974
+ backgroundColor: isSelected ? t.selection.bg : void 0,
2975
+ children: [
2976
+ /* @__PURE__ */ jsx3("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
2977
+ /* @__PURE__ */ jsx3("text", { fg: isSelected ? t.selection.fg : t.fg.primary, width: 25, children: dep.config.name }),
2978
+ /* @__PURE__ */ jsxs3("text", { fg: statusColor(dep.state.status), width: 16, children: [
2979
+ "[",
2980
+ dep.state.status,
2981
+ "]"
2982
+ ] }),
2983
+ /* @__PURE__ */ jsx3("text", { fg: isSelected ? t.fg.secondary : t.fg.muted, children: PROVIDER_NAMES[dep.config.provider] })
2984
+ ]
2985
+ },
2986
+ dep.config.name
2987
+ );
2988
+ })
2989
+ }
2990
+ ),
2991
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, marginTop: 2, children: "Up/Down: Select | Enter: Details | Esc: Back" })
2992
+ ] });
2993
+ }
2994
+ if (viewState === "detail" && selectedDeployment) {
2995
+ const dep = selectedDeployment;
2996
+ const isInitialized = dep.state.status === "initialized";
2997
+ return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
2998
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", marginBottom: 2, children: [
2999
+ /* @__PURE__ */ jsx3("text", { fg: t.accent, children: "/list" }),
3000
+ /* @__PURE__ */ jsxs3("text", { fg: t.fg.secondary, children: [
3001
+ " - ",
3002
+ dep.config.name
3003
+ ] })
3004
+ ] }),
3005
+ /* @__PURE__ */ jsxs3(
3006
+ "box",
3007
+ {
3008
+ flexDirection: "column",
3009
+ borderStyle: "single",
3010
+ borderColor: t.border.focus,
3011
+ padding: 1,
3012
+ marginBottom: 1,
2717
3013
  children: [
2718
- /* @__PURE__ */ jsxs3("text", { fg: t.status.warning, children: [
2719
- 'Deploy "',
2720
- selectedDeployment.config.name,
2721
- '"?'
3014
+ /* @__PURE__ */ jsx3("text", { fg: t.accent, marginBottom: 1, children: "Deployment Details" }),
3015
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
3016
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Name:" }),
3017
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: dep.config.name })
2722
3018
  ] }),
2723
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, marginTop: 1, children: "This will:" }),
2724
- /* @__PURE__ */ jsxs3("text", { fg: t.fg.primary, children: [
2725
- "\u2022 Create a VPS on ",
2726
- selectedDeployment.config.provider
3019
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
3020
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Status:" }),
3021
+ /* @__PURE__ */ jsx3("text", { fg: statusColor(dep.state.status), children: dep.state.status })
3022
+ ] }),
3023
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
3024
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Provider:" }),
3025
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: PROVIDER_NAMES[dep.config.provider] })
3026
+ ] }),
3027
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
3028
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Created:" }),
3029
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: new Date(dep.config.createdAt).toLocaleString() })
2727
3030
  ] }),
2728
- /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: "\u2022 Install and configure OpenClaw" }),
2729
- /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: "\u2022 Set up Tailscale for secure access" }),
2730
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, marginTop: 1, children: "Estimated cost: ~$4.99/month (Hetzner CPX11)" })
3031
+ dep.state.serverIp && /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
3032
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Server IP:" }),
3033
+ /* @__PURE__ */ jsx3("text", { fg: t.accent, children: dep.state.serverIp })
3034
+ ] }),
3035
+ dep.config.provider === "hetzner" && dep.config.hetzner && /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
3036
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Server Type:" }),
3037
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: dep.config.hetzner.serverType })
3038
+ ] }),
3039
+ dep.config.provider === "digitalocean" && dep.config.digitalocean && /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
3040
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Droplet Size:" }),
3041
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: dep.config.digitalocean.size })
3042
+ ] }),
3043
+ dep.config.openclawAgent && /* @__PURE__ */ jsxs3(Fragment, { children: [
3044
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
3045
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "AI Provider:" }),
3046
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: dep.config.openclawAgent.aiProvider })
3047
+ ] }),
3048
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
3049
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Model:" }),
3050
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: dep.config.openclawAgent.model })
3051
+ ] }),
3052
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", children: [
3053
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, width: 20, children: "Channel:" }),
3054
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, children: dep.config.openclawAgent.channel })
3055
+ ] })
3056
+ ] })
2731
3057
  ]
2732
3058
  }
2733
3059
  ),
2734
- /* @__PURE__ */ jsx3("text", { fg: t.status.warning, marginTop: 2, children: "Press Y to confirm, N to cancel" })
3060
+ /* @__PURE__ */ jsxs3("box", { flexDirection: "row", marginBottom: 1, children: [
3061
+ isInitialized && /* @__PURE__ */ jsx3("text", { fg: t.accent, marginRight: 2, children: "[E]dit" }),
3062
+ /* @__PURE__ */ jsx3("text", { fg: t.accent, marginRight: 2, children: "[F]ork" }),
3063
+ isInitialized ? /* @__PURE__ */ jsx3("text", { fg: t.status.error, children: "[D]elete" }) : /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, children: "[D]elete (use /destroy)" })
3064
+ ] }),
3065
+ error && /* @__PURE__ */ jsx3("text", { fg: t.status.error, marginTop: 1, children: error }),
3066
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, marginTop: 1, children: "Esc: Back to list" })
2735
3067
  ] });
2736
3068
  }
2737
- return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
2738
- /* @__PURE__ */ jsxs3("box", { flexDirection: "row", marginBottom: 2, children: [
2739
- /* @__PURE__ */ jsx3("text", { fg: t.accent, children: "/deploy" }),
2740
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, children: " - Select a deployment to deploy" })
2741
- ] }),
2742
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, marginBottom: 1, children: "Use arrow keys to select, Enter to deploy:" }),
2743
- /* @__PURE__ */ jsx3(
2744
- "box",
2745
- {
2746
- flexDirection: "column",
2747
- borderStyle: "single",
2748
- borderColor: t.border.default,
2749
- padding: 1,
2750
- children: allDeployments.map((deployment, index) => {
2751
- const isSelected = index === selectedIndex;
2752
- return /* @__PURE__ */ jsxs3(
2753
- "box",
2754
- {
2755
- flexDirection: "row",
2756
- backgroundColor: isSelected ? t.selection.bg : void 0,
2757
- children: [
2758
- /* @__PURE__ */ jsx3("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
2759
- /* @__PURE__ */ jsx3("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
2760
- /* @__PURE__ */ jsxs3("text", { fg: statusColor(deployment.state.status), width: 15, children: [
2761
- "[",
2762
- deployment.state.status,
2763
- "]"
2764
- ] }),
2765
- /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, children: deployment.config.provider })
2766
- ]
2767
- },
2768
- deployment.config.name
2769
- );
2770
- })
2771
- }
2772
- ),
2773
- /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, marginTop: 2, children: "Press Esc to go back" })
2774
- ] });
2775
- }
2776
-
2777
- // src/components/DeployingView.tsx
2778
- import { useState as useState4, useEffect as useEffect2, useCallback, useRef as useRef2 } from "react";
2779
- import { useKeyboard as useKeyboard3 } from "@opentui/react";
2780
- import open from "open";
2781
-
2782
- // src/services/ssh.ts
2783
- import { Client } from "ssh2";
2784
- import { generateKeyPairSync, randomBytes } from "crypto";
2785
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, chmodSync, existsSync as existsSync4 } from "fs";
2786
- import { mkdirSync as mkdirSync4 } from "fs";
2787
- function generateSSHKeyPair(comment = "clawcontrol") {
2788
- const { privateKey, publicKey } = generateKeyPairSync("ed25519", {
2789
- publicKeyEncoding: {
2790
- type: "spki",
2791
- format: "pem"
2792
- },
2793
- privateKeyEncoding: {
2794
- type: "pkcs8",
2795
- format: "pem"
2796
- }
2797
- });
2798
- const pubKeyDer = extractDERFromPEM(publicKey);
2799
- const privKeyDer = extractDERFromPEM(privateKey);
2800
- const publicKeyBytes = pubKeyDer.slice(-32);
2801
- const privateKeyBytes = privKeyDer.slice(-32);
2802
- const publicKeyOpenSSH = buildOpenSSHPublicKey(publicKeyBytes, comment);
2803
- const privateKeyOpenSSH = buildOpenSSHPrivateKey(privateKeyBytes, publicKeyBytes, comment);
2804
- return {
2805
- privateKey: privateKeyOpenSSH,
2806
- publicKey: publicKeyOpenSSH
2807
- };
2808
- }
2809
- function extractDERFromPEM(pem) {
2810
- const lines = pem.split("\n").filter((line) => !line.startsWith("-----") && line.trim() !== "");
2811
- return Buffer.from(lines.join(""), "base64");
2812
- }
2813
- function buildOpenSSHPublicKey(publicKeyBytes, comment) {
2814
- const keyType = Buffer.from("ssh-ed25519");
2815
- const keyTypeLen = Buffer.alloc(4);
2816
- keyTypeLen.writeUInt32BE(keyType.length);
2817
- const keyLen = Buffer.alloc(4);
2818
- keyLen.writeUInt32BE(publicKeyBytes.length);
2819
- const opensshKey = Buffer.concat([keyTypeLen, keyType, keyLen, publicKeyBytes]);
2820
- return `ssh-ed25519 ${opensshKey.toString("base64")} ${comment}`;
2821
- }
2822
- function buildOpenSSHPrivateKey(privateKeyBytes, publicKeyBytes, comment) {
2823
- const AUTH_MAGIC = Buffer.from("openssh-key-v1\0");
2824
- const cipherName = Buffer.from("none");
2825
- const kdfName = Buffer.from("none");
3069
+ if (viewState === "delete_confirm" && selectedDeployment) {
3070
+ return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
3071
+ /* @__PURE__ */ jsx3("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx3("text", { fg: t.status.error, children: "Confirm Deletion" }) }),
3072
+ /* @__PURE__ */ jsxs3(
3073
+ "box",
3074
+ {
3075
+ flexDirection: "column",
3076
+ borderStyle: "double",
3077
+ borderColor: t.status.error,
3078
+ padding: 1,
3079
+ children: [
3080
+ /* @__PURE__ */ jsx3("text", { fg: t.status.error, children: "You are about to delete:" }),
3081
+ /* @__PURE__ */ jsxs3("text", { fg: t.fg.primary, marginTop: 1, children: [
3082
+ "Deployment: ",
3083
+ selectedDeployment.config.name
3084
+ ] }),
3085
+ /* @__PURE__ */ jsx3("text", { fg: t.status.error, marginTop: 1, children: "This will permanently delete:" }),
3086
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, children: " Local configuration files" }),
3087
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, children: " SSH keys" })
3088
+ ]
3089
+ }
3090
+ ),
3091
+ /* @__PURE__ */ jsx3("text", { fg: t.status.warning, marginTop: 2, children: "Type the deployment name to confirm:" }),
3092
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, marginTop: 1, children: "Confirm:" }),
3093
+ /* @__PURE__ */ jsx3(
3094
+ "input",
3095
+ {
3096
+ value: confirmText,
3097
+ placeholder: selectedDeployment.config.name,
3098
+ focused: true,
3099
+ onInput: (value) => setConfirmText(value),
3100
+ onSubmit: (value) => {
3101
+ if (value === selectedDeployment.config.name) {
3102
+ handleDelete(selectedDeployment.config.name);
3103
+ } else {
3104
+ setError("Name does not match. Please type the exact deployment name.");
3105
+ }
3106
+ },
3107
+ onKeyDown: (e) => {
3108
+ if (e.name === "escape") {
3109
+ setViewState("detail");
3110
+ setConfirmText("");
3111
+ setError(null);
3112
+ }
3113
+ }
3114
+ }
3115
+ ),
3116
+ error && /* @__PURE__ */ jsx3("text", { fg: t.status.error, marginTop: 1, children: error }),
3117
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, marginTop: 2, children: "Press Esc to cancel" })
3118
+ ] });
3119
+ }
3120
+ if (viewState === "deleting") {
3121
+ return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
3122
+ /* @__PURE__ */ jsx3("text", { fg: t.status.warning, children: "Deleting deployment..." }),
3123
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.secondary, marginTop: 1, children: "Removing local configuration files..." })
3124
+ ] });
3125
+ }
3126
+ if (viewState === "success") {
3127
+ return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
3128
+ /* @__PURE__ */ jsxs3(
3129
+ "box",
3130
+ {
3131
+ flexDirection: "column",
3132
+ borderStyle: "single",
3133
+ borderColor: t.status.success,
3134
+ padding: 1,
3135
+ children: [
3136
+ /* @__PURE__ */ jsx3("text", { fg: t.status.success, children: "Deployment Deleted" }),
3137
+ /* @__PURE__ */ jsxs3("text", { fg: t.fg.primary, marginTop: 1, children: [
3138
+ 'The deployment "',
3139
+ deletedName,
3140
+ '" has been permanently deleted.'
3141
+ ] })
3142
+ ]
3143
+ }
3144
+ ),
3145
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
3146
+ ] });
3147
+ }
3148
+ if (viewState === "error") {
3149
+ return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", width: "100%", padding: 1, children: [
3150
+ /* @__PURE__ */ jsxs3(
3151
+ "box",
3152
+ {
3153
+ flexDirection: "column",
3154
+ borderStyle: "single",
3155
+ borderColor: t.status.error,
3156
+ padding: 1,
3157
+ children: [
3158
+ /* @__PURE__ */ jsx3("text", { fg: t.status.error, children: "Deletion Failed" }),
3159
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.primary, marginTop: 1, children: error })
3160
+ ]
3161
+ }
3162
+ ),
3163
+ /* @__PURE__ */ jsx3("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to go back" })
3164
+ ] });
3165
+ }
3166
+ return null;
3167
+ }
3168
+
3169
+ // src/components/DeployView.tsx
3170
+ import { useState as useState4 } from "react";
3171
+ import { useKeyboard as useKeyboard4 } from "@opentui/react";
3172
+ import { jsx as jsx4, jsxs as jsxs4 } from "@opentui/react/jsx-runtime";
3173
+ function DeployView({ context }) {
3174
+ const [selectedIndex, setSelectedIndex] = useState4(0);
3175
+ const [confirmMode, setConfirmMode] = useState4(false);
3176
+ const deployments = context.deployments;
3177
+ const notDeployed = deployments.filter((d) => d.state.status !== "deployed");
3178
+ const deployed = deployments.filter((d) => d.state.status === "deployed");
3179
+ const allDeployments = [...notDeployed, ...deployed];
3180
+ const selectedDeployment = allDeployments[selectedIndex];
3181
+ useKeyboard4((key) => {
3182
+ if (allDeployments.length === 0) {
3183
+ context.navigateTo("home");
3184
+ return;
3185
+ }
3186
+ if (confirmMode) {
3187
+ if (key.name === "y" || key.name === "return") {
3188
+ context.navigateTo("deploying", selectedDeployment.config.name);
3189
+ } else if (key.name === "n" || key.name === "escape") {
3190
+ setConfirmMode(false);
3191
+ }
3192
+ } else {
3193
+ if (key.name === "up" && selectedIndex > 0) {
3194
+ setSelectedIndex(selectedIndex - 1);
3195
+ } else if (key.name === "down" && selectedIndex < allDeployments.length - 1) {
3196
+ setSelectedIndex(selectedIndex + 1);
3197
+ } else if (key.name === "return") {
3198
+ setConfirmMode(true);
3199
+ } else if (key.name === "escape") {
3200
+ context.navigateTo("home");
3201
+ }
3202
+ }
3203
+ });
3204
+ if (allDeployments.length === 0) {
3205
+ return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", width: "100%", padding: 1, children: [
3206
+ /* @__PURE__ */ jsxs4("box", { flexDirection: "row", marginBottom: 2, children: [
3207
+ /* @__PURE__ */ jsx4("text", { fg: t.accent, children: "/deploy" }),
3208
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: " - Deploy a configuration" })
3209
+ ] }),
3210
+ /* @__PURE__ */ jsxs4(
3211
+ "box",
3212
+ {
3213
+ flexDirection: "column",
3214
+ borderStyle: "single",
3215
+ borderColor: t.border.default,
3216
+ padding: 1,
3217
+ children: [
3218
+ /* @__PURE__ */ jsx4("text", { fg: t.status.warning, children: "No deployments found!" }),
3219
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, marginTop: 1, children: "Run /new first to create a deployment configuration." })
3220
+ ]
3221
+ }
3222
+ ),
3223
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
3224
+ ] });
3225
+ }
3226
+ if (confirmMode) {
3227
+ return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", width: "100%", padding: 1, children: [
3228
+ /* @__PURE__ */ jsxs4("box", { flexDirection: "row", marginBottom: 2, children: [
3229
+ /* @__PURE__ */ jsx4("text", { fg: t.accent, children: "/deploy" }),
3230
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: " - Confirm deployment" })
3231
+ ] }),
3232
+ /* @__PURE__ */ jsxs4(
3233
+ "box",
3234
+ {
3235
+ flexDirection: "column",
3236
+ borderStyle: "single",
3237
+ borderColor: t.border.default,
3238
+ padding: 1,
3239
+ children: [
3240
+ /* @__PURE__ */ jsxs4("text", { fg: t.status.warning, children: [
3241
+ 'Deploy "',
3242
+ selectedDeployment.config.name,
3243
+ '"?'
3244
+ ] }),
3245
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, marginTop: 1, children: "This will:" }),
3246
+ /* @__PURE__ */ jsxs4("text", { fg: t.fg.primary, children: [
3247
+ "\u2022 Create a VPS on ",
3248
+ selectedDeployment.config.provider
3249
+ ] }),
3250
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "\u2022 Install and configure OpenClaw" }),
3251
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "\u2022 Set up Tailscale for secure access" }),
3252
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, marginTop: 1, children: "Estimated cost: ~$4.99/month (Hetzner CPX11)" })
3253
+ ]
3254
+ }
3255
+ ),
3256
+ /* @__PURE__ */ jsx4("text", { fg: t.status.warning, marginTop: 2, children: "Press Y to confirm, N to cancel" })
3257
+ ] });
3258
+ }
3259
+ return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", width: "100%", padding: 1, children: [
3260
+ /* @__PURE__ */ jsxs4("box", { flexDirection: "row", marginBottom: 2, children: [
3261
+ /* @__PURE__ */ jsx4("text", { fg: t.accent, children: "/deploy" }),
3262
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: " - Select a deployment to deploy" })
3263
+ ] }),
3264
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, marginBottom: 1, children: "Use arrow keys to select, Enter to deploy:" }),
3265
+ /* @__PURE__ */ jsx4(
3266
+ "box",
3267
+ {
3268
+ flexDirection: "column",
3269
+ borderStyle: "single",
3270
+ borderColor: t.border.default,
3271
+ padding: 1,
3272
+ children: allDeployments.map((deployment, index) => {
3273
+ const isSelected = index === selectedIndex;
3274
+ return /* @__PURE__ */ jsxs4(
3275
+ "box",
3276
+ {
3277
+ flexDirection: "row",
3278
+ backgroundColor: isSelected ? t.selection.bg : void 0,
3279
+ children: [
3280
+ /* @__PURE__ */ jsx4("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
3281
+ /* @__PURE__ */ jsx4("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
3282
+ /* @__PURE__ */ jsxs4("text", { fg: statusColor(deployment.state.status), width: 15, children: [
3283
+ "[",
3284
+ deployment.state.status,
3285
+ "]"
3286
+ ] }),
3287
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: deployment.config.provider })
3288
+ ]
3289
+ },
3290
+ deployment.config.name
3291
+ );
3292
+ })
3293
+ }
3294
+ ),
3295
+ /* @__PURE__ */ jsx4("text", { fg: t.fg.muted, marginTop: 2, children: "Press Esc to go back" })
3296
+ ] });
3297
+ }
3298
+
3299
+ // src/components/DeployingView.tsx
3300
+ import { useState as useState5, useEffect as useEffect2, useCallback, useRef as useRef4 } from "react";
3301
+ import { useKeyboard as useKeyboard5 } from "@opentui/react";
3302
+ import open from "open";
3303
+
3304
+ // src/services/ssh.ts
3305
+ import { Client } from "ssh2";
3306
+ import { generateKeyPairSync, randomBytes } from "crypto";
3307
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, chmodSync, existsSync as existsSync4 } from "fs";
3308
+ import { mkdirSync as mkdirSync4 } from "fs";
3309
+ function generateSSHKeyPair(comment = "clawcontrol") {
3310
+ const { privateKey, publicKey } = generateKeyPairSync("ed25519", {
3311
+ publicKeyEncoding: {
3312
+ type: "spki",
3313
+ format: "pem"
3314
+ },
3315
+ privateKeyEncoding: {
3316
+ type: "pkcs8",
3317
+ format: "pem"
3318
+ }
3319
+ });
3320
+ const pubKeyDer = extractDERFromPEM(publicKey);
3321
+ const privKeyDer = extractDERFromPEM(privateKey);
3322
+ const publicKeyBytes = pubKeyDer.slice(-32);
3323
+ const privateKeyBytes = privKeyDer.slice(-32);
3324
+ const publicKeyOpenSSH = buildOpenSSHPublicKey(publicKeyBytes, comment);
3325
+ const privateKeyOpenSSH = buildOpenSSHPrivateKey(privateKeyBytes, publicKeyBytes, comment);
3326
+ return {
3327
+ privateKey: privateKeyOpenSSH,
3328
+ publicKey: publicKeyOpenSSH
3329
+ };
3330
+ }
3331
+ function extractDERFromPEM(pem) {
3332
+ const lines = pem.split("\n").filter((line) => !line.startsWith("-----") && line.trim() !== "");
3333
+ return Buffer.from(lines.join(""), "base64");
3334
+ }
3335
+ function buildOpenSSHPublicKey(publicKeyBytes, comment) {
3336
+ const keyType = Buffer.from("ssh-ed25519");
3337
+ const keyTypeLen = Buffer.alloc(4);
3338
+ keyTypeLen.writeUInt32BE(keyType.length);
3339
+ const keyLen = Buffer.alloc(4);
3340
+ keyLen.writeUInt32BE(publicKeyBytes.length);
3341
+ const opensshKey = Buffer.concat([keyTypeLen, keyType, keyLen, publicKeyBytes]);
3342
+ return `ssh-ed25519 ${opensshKey.toString("base64")} ${comment}`;
3343
+ }
3344
+ function buildOpenSSHPrivateKey(privateKeyBytes, publicKeyBytes, comment) {
3345
+ const AUTH_MAGIC = Buffer.from("openssh-key-v1\0");
3346
+ const cipherName = Buffer.from("none");
3347
+ const kdfName = Buffer.from("none");
2826
3348
  const kdfOptions = Buffer.alloc(0);
2827
3349
  const numKeys = 1;
2828
3350
  const keyType = Buffer.from("ssh-ed25519");
@@ -3090,6 +3612,9 @@ async function waitForSSH(host, privateKey, timeoutMs = 18e4, pollIntervalMs = 5
3090
3612
  throw new Error(`SSH not available after ${timeoutMs / 1e3} seconds`);
3091
3613
  }
3092
3614
 
3615
+ // src/services/deployment.ts
3616
+ import { randomBytes as randomBytes2 } from "crypto";
3617
+
3093
3618
  // src/services/setup/index.ts
3094
3619
  async function execOrFail(ssh, command, errorMessage) {
3095
3620
  const result = await ssh.exec(command);
@@ -3241,7 +3766,7 @@ async function installOpenClaw(ssh) {
3241
3766
  throw new Error("OpenClaw installation verification failed");
3242
3767
  }
3243
3768
  }
3244
- async function configureOpenClaw(ssh, customConfig, agentConfig) {
3769
+ async function configureOpenClaw(ssh, customConfig, agentConfig, gatewayToken) {
3245
3770
  await ssh.exec("mkdir -p ~/.openclaw");
3246
3771
  const config = {
3247
3772
  browser: {
@@ -3263,6 +3788,7 @@ async function configureOpenClaw(ssh, customConfig, agentConfig) {
3263
3788
  port: 18789,
3264
3789
  mode: "local",
3265
3790
  bind: "loopback",
3791
+ ...gatewayToken ? { auth: { token: gatewayToken } } : {},
3266
3792
  tailscale: {
3267
3793
  mode: "serve",
3268
3794
  resetOnExit: false
@@ -3510,6 +4036,41 @@ async function getOpenClawLogs(ssh, lines = 100) {
3510
4036
  const result = await ssh.exec(`journalctl -u openclaw -n ${lines} --no-pager`);
3511
4037
  return result.stdout;
3512
4038
  }
4039
+ async function getDashboardUrl(ssh) {
4040
+ const nvmPrefix = "source ~/.nvm/nvm.sh &&";
4041
+ const dashResult = await ssh.exec(
4042
+ `${nvmPrefix} timeout 10 openclaw dashboard 2>&1 || true`
4043
+ );
4044
+ const rawOutput = dashResult.stdout + "\n" + dashResult.stderr;
4045
+ const output = rawOutput.replace(/\x1b\[[0-9;]*m/g, "");
4046
+ const urlMatch = output.match(/https?:\/\/[^\s\])'"<>]+/);
4047
+ if (urlMatch) {
4048
+ try {
4049
+ const parsed = new URL(urlMatch[0]);
4050
+ return { url: urlMatch[0], port: parseInt(parsed.port) || 18789 };
4051
+ } catch {
4052
+ }
4053
+ }
4054
+ const tokenResult = await ssh.exec(
4055
+ `${nvmPrefix} openclaw config get gateway.auth.token 2>/dev/null || true`
4056
+ );
4057
+ const token = tokenResult.stdout.trim().replace(/^["']|["']$/g, "");
4058
+ if (token) {
4059
+ return {
4060
+ url: `http://127.0.0.1:18789/?token=${encodeURIComponent(token)}`,
4061
+ port: 18789
4062
+ };
4063
+ }
4064
+ const statusResult = await ssh.exec(
4065
+ "systemctl is-active openclaw 2>/dev/null || true"
4066
+ );
4067
+ if (statusResult.stdout.trim() !== "active") {
4068
+ throw new Error("OpenClaw gateway is not running on this server");
4069
+ }
4070
+ throw new Error(
4071
+ "Could not retrieve dashboard URL. Try running 'openclaw dashboard' on the server manually."
4072
+ );
4073
+ }
3513
4074
 
3514
4075
  // src/services/deployment.ts
3515
4076
  var MAX_RETRIES = 3;
@@ -3608,6 +4169,7 @@ var DeploymentOrchestrator = class {
3608
4169
  onConfirm;
3609
4170
  onOpenUrl;
3610
4171
  onSpawnTerminal;
4172
+ tailscaleSkipped = false;
3611
4173
  constructor(deploymentName, onProgress, onConfirm, onOpenUrl, onSpawnTerminal) {
3612
4174
  this.deploymentName = deploymentName;
3613
4175
  this.deployment = readDeployment(deploymentName);
@@ -3991,18 +4553,28 @@ Would you like to retry from the beginning?`
3991
4553
  const ssh = await this.ensureSSHConnected();
3992
4554
  const customConfig = this.deployment.config.openclawConfig;
3993
4555
  const agentConfig = this.deployment.config.openclawAgent;
3994
- await configureOpenClaw(ssh, customConfig, agentConfig);
4556
+ const gatewayToken = randomBytes2(32).toString("hex");
4557
+ await configureOpenClaw(ssh, customConfig, agentConfig, gatewayToken);
4558
+ updateDeploymentState(this.deploymentName, { gatewayToken });
3995
4559
  if (agentConfig) {
3996
4560
  this.reportProgress("openclaw_configured", "Writing AI provider environment...");
3997
4561
  await writeOpenClawEnvFile(ssh, agentConfig);
3998
4562
  }
3999
4563
  }
4000
4564
  async installTailscale() {
4565
+ if (this.deployment.config.skipTailscale) {
4566
+ this.tailscaleSkipped = true;
4567
+ this.reportProgress("tailscale_installed", "Tailscale setup skipped.");
4568
+ return;
4569
+ }
4001
4570
  const ssh = await this.ensureSSHConnected();
4002
4571
  await installTailscale(ssh);
4003
4572
  }
4004
4573
  async authenticateTailscale() {
4574
+ if (this.tailscaleSkipped) return;
4005
4575
  const ssh = await this.ensureSSHConnected();
4576
+ const check = await ssh.exec("which tailscale 2>/dev/null");
4577
+ if (check.code !== 0) return;
4006
4578
  const authUrl = await getTailscaleAuthUrl(ssh);
4007
4579
  if (authUrl) {
4008
4580
  const confirmed = await this.onConfirm(
@@ -4025,7 +4597,10 @@ URL: ${authUrl}`
4025
4597
  }
4026
4598
  }
4027
4599
  async configureTailscale() {
4600
+ if (this.tailscaleSkipped) return;
4028
4601
  const ssh = await this.ensureSSHConnected();
4602
+ const check = await ssh.exec("which tailscale 2>/dev/null");
4603
+ if (check.code !== 0) return;
4029
4604
  const tailscaleIp = await configureTailscaleServe(ssh);
4030
4605
  updateDeploymentState(this.deploymentName, {
4031
4606
  tailscaleIp
@@ -4184,6 +4759,12 @@ function detectTerminal() {
4184
4759
  return { app: "konsole", canOpenTab: true };
4185
4760
  }
4186
4761
  }
4762
+ if (os === "win32") {
4763
+ if (env.WT_SESSION) {
4764
+ return { app: "windows-terminal", canOpenTab: true };
4765
+ }
4766
+ return { app: "powershell", canOpenTab: false };
4767
+ }
4187
4768
  if (os === "darwin") {
4188
4769
  return { app: "terminal.app", canOpenTab: true };
4189
4770
  }
@@ -4218,11 +4799,19 @@ function openTerminalWithCommand(command) {
4218
4799
  return openKonsole(command);
4219
4800
  case "xfce4-terminal":
4220
4801
  return openXfce4Terminal(command);
4802
+ case "windows-terminal":
4803
+ return openWindowsTerminal(command);
4804
+ case "powershell":
4805
+ return openPowerShell(command);
4806
+ case "cmd":
4807
+ return openCmd(command);
4221
4808
  default:
4222
4809
  if (os === "darwin") {
4223
4810
  return openTerminalApp(command);
4224
4811
  } else if (os === "linux") {
4225
4812
  return openLinuxFallback(command);
4813
+ } else if (os === "win32") {
4814
+ return openWindowsFallback(command);
4226
4815
  }
4227
4816
  return { success: false, error: `Unsupported terminal or OS: ${terminal.app}` };
4228
4817
  }
@@ -4418,6 +5007,9 @@ function openCursorTerminal(command) {
4418
5007
  }
4419
5008
  return openTerminalApp(command);
4420
5009
  }
5010
+ if (platform() === "win32") {
5011
+ return openWindowsFallback(command);
5012
+ }
4421
5013
  return openLinuxFallback(command);
4422
5014
  }
4423
5015
  function openVSCodeTerminal(command) {
@@ -4489,35 +5081,94 @@ function openLinuxFallback(command) {
4489
5081
  }
4490
5082
  return { success: false, error: "Could not find a supported terminal emulator" };
4491
5083
  }
4492
- function getTerminalDisplayName(app) {
4493
- const names = {
4494
- "terminal.app": "Terminal.app",
4495
- "iterm2": "iTerm2",
4496
- "ghostty": "Ghostty",
4497
- "kitty": "Kitty",
4498
- "alacritty": "Alacritty",
4499
- "wezterm": "WezTerm",
4500
- "hyper": "Hyper",
4501
- "vscode": "VS Code",
4502
- "cursor": "Cursor",
5084
+ function createTempScriptWindows(command) {
5085
+ const scriptPath = join5(tmpdir(), `clawcontrol-${process.pid}-${Date.now()}.cmd`);
5086
+ const content = [
5087
+ "@echo off",
5088
+ command,
5089
+ `del "${scriptPath}"`,
5090
+ ""
5091
+ ].join("\r\n");
5092
+ writeFileSync5(scriptPath, content);
5093
+ return scriptPath;
5094
+ }
5095
+ function openWindowsTerminal(command) {
5096
+ const script = createTempScriptWindows(command);
5097
+ const proc = spawn("wt.exe", ["new-tab", "cmd", "/c", script], {
5098
+ stdio: "ignore",
5099
+ detached: true,
5100
+ shell: true
5101
+ });
5102
+ proc.unref();
5103
+ return { success: true };
5104
+ }
5105
+ function openPowerShell(command) {
5106
+ const proc = spawn("powershell.exe", [
5107
+ "-NoProfile",
5108
+ "Start-Process",
5109
+ "powershell",
5110
+ "-ArgumentList",
5111
+ `'-NoExit -Command "${command.replace(/"/g, '`"')}"'`
5112
+ ], {
5113
+ stdio: "ignore",
5114
+ detached: true,
5115
+ shell: true
5116
+ });
5117
+ proc.unref();
5118
+ return { success: true };
5119
+ }
5120
+ function openCmd(command) {
5121
+ const script = createTempScriptWindows(command);
5122
+ const proc = spawn("cmd.exe", ["/c", "start", "cmd", "/k", script], {
5123
+ stdio: "ignore",
5124
+ detached: true,
5125
+ shell: true
5126
+ });
5127
+ proc.unref();
5128
+ return { success: true };
5129
+ }
5130
+ function openWindowsFallback(command) {
5131
+ try {
5132
+ const result = spawnSync("where", ["wt.exe"], { stdio: "pipe", timeout: 2e3 });
5133
+ if (result.status === 0) {
5134
+ return openWindowsTerminal(command);
5135
+ }
5136
+ } catch {
5137
+ }
5138
+ return openPowerShell(command);
5139
+ }
5140
+ function getTerminalDisplayName(app) {
5141
+ const names = {
5142
+ "terminal.app": "Terminal.app",
5143
+ "iterm2": "iTerm2",
5144
+ "ghostty": "Ghostty",
5145
+ "kitty": "Kitty",
5146
+ "alacritty": "Alacritty",
5147
+ "wezterm": "WezTerm",
5148
+ "hyper": "Hyper",
5149
+ "vscode": "VS Code",
5150
+ "cursor": "Cursor",
4503
5151
  "gnome-terminal": "GNOME Terminal",
4504
5152
  "konsole": "Konsole",
4505
5153
  "xfce4-terminal": "XFCE Terminal",
4506
5154
  "xterm": "XTerm",
5155
+ "windows-terminal": "Windows Terminal",
5156
+ "powershell": "PowerShell",
5157
+ "cmd": "Command Prompt",
4507
5158
  "unknown": "System Terminal"
4508
5159
  };
4509
5160
  return names[app];
4510
5161
  }
4511
5162
 
4512
5163
  // src/components/DeployingView.tsx
4513
- import { jsx as jsx4, jsxs as jsxs4 } from "@opentui/react/jsx-runtime";
5164
+ import { jsx as jsx5, jsxs as jsxs5 } from "@opentui/react/jsx-runtime";
4514
5165
  function DeployingView({ context }) {
4515
- const [deployState, setDeployState] = useState4("deploying");
4516
- const [progress, setProgress] = useState4(null);
4517
- const [logs, setLogs] = useState4([]);
4518
- const [error, setError] = useState4(null);
4519
- const [confirmPrompt, setConfirmPrompt] = useState4(null);
4520
- const [terminalResolve, setTerminalResolve] = useState4(null);
5166
+ const [deployState, setDeployState] = useState5("deploying");
5167
+ const [progress, setProgress] = useState5(null);
5168
+ const [logs, setLogs] = useState5([]);
5169
+ const [error, setError] = useState5(null);
5170
+ const [confirmPrompt, setConfirmPrompt] = useState5(null);
5171
+ const [terminalResolve, setTerminalResolve] = useState5(null);
4521
5172
  const deploymentName = context.selectedDeployment;
4522
5173
  const addLog = useCallback((message) => {
4523
5174
  setLogs((prev) => [...prev.slice(-20), `[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] ${message}`]);
@@ -4565,9 +5216,9 @@ function DeployingView({ context }) {
4565
5216
  setDeployState("deploying");
4566
5217
  addLog("Terminal session confirmed complete, continuing deployment...");
4567
5218
  }, [terminalResolve, addLog]);
4568
- const stateRef = useRef2({ deployState, terminalResolve });
5219
+ const stateRef = useRef4({ deployState, terminalResolve });
4569
5220
  stateRef.current = { deployState, terminalResolve };
4570
- useKeyboard3((key) => {
5221
+ useKeyboard5((key) => {
4571
5222
  const currentState = stateRef.current;
4572
5223
  if (currentState.deployState === "waiting_terminal") {
4573
5224
  if (key.name === "return") {
@@ -4621,19 +5272,19 @@ function DeployingView({ context }) {
4621
5272
  const width = 40;
4622
5273
  const filled = Math.round(progress.progress / 100 * width);
4623
5274
  const empty = width - filled;
4624
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", marginBottom: 1, children: [
4625
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", children: [
4626
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: "[" }),
4627
- /* @__PURE__ */ jsx4("text", { fg: t.status.success, children: "\u2588".repeat(filled) }),
4628
- /* @__PURE__ */ jsx4("text", { fg: t.fg.muted, children: "\u2591".repeat(empty) }),
4629
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: "]" }),
4630
- /* @__PURE__ */ jsxs4("text", { fg: t.fg.primary, children: [
5275
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", marginBottom: 1, children: [
5276
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
5277
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: "[" }),
5278
+ /* @__PURE__ */ jsx5("text", { fg: t.status.success, children: "\u2588".repeat(filled) }),
5279
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.muted, children: "\u2591".repeat(empty) }),
5280
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: "]" }),
5281
+ /* @__PURE__ */ jsxs5("text", { fg: t.fg.primary, children: [
4631
5282
  " ",
4632
5283
  Math.round(progress.progress),
4633
5284
  "%"
4634
5285
  ] })
4635
5286
  ] }),
4636
- /* @__PURE__ */ jsxs4("text", { fg: t.accent, marginTop: 1, children: [
5287
+ /* @__PURE__ */ jsxs5("text", { fg: t.accent, marginTop: 1, children: [
4637
5288
  "Current: ",
4638
5289
  progress.message
4639
5290
  ] })
@@ -4642,7 +5293,7 @@ function DeployingView({ context }) {
4642
5293
  const renderConfirmDialog = () => {
4643
5294
  if (!confirmPrompt) return null;
4644
5295
  const lines = confirmPrompt.message.split("\n");
4645
- return /* @__PURE__ */ jsxs4(
5296
+ return /* @__PURE__ */ jsxs5(
4646
5297
  "box",
4647
5298
  {
4648
5299
  flexDirection: "column",
@@ -4651,16 +5302,16 @@ function DeployingView({ context }) {
4651
5302
  padding: 1,
4652
5303
  marginBottom: 1,
4653
5304
  children: [
4654
- /* @__PURE__ */ jsx4("text", { fg: t.status.warning, children: "Confirmation Required" }),
4655
- /* @__PURE__ */ jsx4("box", { flexDirection: "column", marginTop: 1, children: lines.map((line, i) => /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: line }, i)) }),
4656
- /* @__PURE__ */ jsx4("text", { fg: t.status.warning, marginTop: 1, children: "Press Y for Yes, N for No" })
5305
+ /* @__PURE__ */ jsx5("text", { fg: t.status.warning, children: "Confirmation Required" }),
5306
+ /* @__PURE__ */ jsx5("box", { flexDirection: "column", marginTop: 1, children: lines.map((line, i) => /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: line }, i)) }),
5307
+ /* @__PURE__ */ jsx5("text", { fg: t.status.warning, marginTop: 1, children: "Press Y for Yes, N for No" })
4657
5308
  ]
4658
5309
  }
4659
5310
  );
4660
5311
  };
4661
5312
  const renderWaitingTerminal = () => {
4662
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", flexGrow: 1, children: [
4663
- /* @__PURE__ */ jsxs4(
5313
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", flexGrow: 1, children: [
5314
+ /* @__PURE__ */ jsxs5(
4664
5315
  "box",
4665
5316
  {
4666
5317
  flexDirection: "column",
@@ -4669,12 +5320,12 @@ function DeployingView({ context }) {
4669
5320
  padding: 1,
4670
5321
  marginBottom: 1,
4671
5322
  children: [
4672
- /* @__PURE__ */ jsx4("text", { fg: t.accent, children: "Interactive Setup" }),
4673
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, marginTop: 1, children: "A terminal window has been opened." })
5323
+ /* @__PURE__ */ jsx5("text", { fg: t.accent, children: "Interactive Setup" }),
5324
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, marginTop: 1, children: "A terminal window has been opened." })
4674
5325
  ]
4675
5326
  }
4676
5327
  ),
4677
- /* @__PURE__ */ jsxs4(
5328
+ /* @__PURE__ */ jsxs5(
4678
5329
  "box",
4679
5330
  {
4680
5331
  flexDirection: "column",
@@ -4683,32 +5334,32 @@ function DeployingView({ context }) {
4683
5334
  padding: 1,
4684
5335
  marginBottom: 1,
4685
5336
  children: [
4686
- /* @__PURE__ */ jsx4("text", { fg: t.status.warning, children: "Instructions:" }),
4687
- /* @__PURE__ */ jsxs4("box", { flexDirection: "column", marginTop: 1, children: [
4688
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "1. Complete the setup in the terminal window" }),
4689
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "2. Follow the prompts shown in the terminal" }),
4690
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "3. When done, close the terminal window" }),
4691
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "4. Press Enter here to continue" })
5337
+ /* @__PURE__ */ jsx5("text", { fg: t.status.warning, children: "Instructions:" }),
5338
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "column", marginTop: 1, children: [
5339
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: "1. Complete the setup in the terminal window" }),
5340
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: "2. Follow the prompts shown in the terminal" }),
5341
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: "3. When done, close the terminal window" }),
5342
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: "4. Press Enter here to continue" })
4692
5343
  ] })
4693
5344
  ]
4694
5345
  }
4695
5346
  ),
4696
- /* @__PURE__ */ jsx4(
5347
+ /* @__PURE__ */ jsx5(
4697
5348
  "box",
4698
5349
  {
4699
5350
  flexDirection: "column",
4700
5351
  borderStyle: "single",
4701
5352
  borderColor: t.status.success,
4702
5353
  padding: 1,
4703
- children: /* @__PURE__ */ jsx4("text", { fg: t.status.success, children: "Press Enter when you have completed the setup in the terminal" })
5354
+ children: /* @__PURE__ */ jsx5("text", { fg: t.status.success, children: "Press Enter when you have completed the setup in the terminal" })
4704
5355
  }
4705
5356
  )
4706
5357
  ] });
4707
5358
  };
4708
5359
  const renderSuccess = () => {
4709
5360
  const state = readDeploymentState(deploymentName);
4710
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", children: [
4711
- /* @__PURE__ */ jsxs4(
5361
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
5362
+ /* @__PURE__ */ jsxs5(
4712
5363
  "box",
4713
5364
  {
4714
5365
  flexDirection: "column",
@@ -4717,12 +5368,12 @@ function DeployingView({ context }) {
4717
5368
  padding: 1,
4718
5369
  marginBottom: 1,
4719
5370
  children: [
4720
- /* @__PURE__ */ jsx4("text", { fg: t.status.success, children: "Deployment Successful!" }),
4721
- /* @__PURE__ */ jsx4("text", { fg: t.status.success, marginTop: 1, children: "Your OpenClaw instance is now running." })
5371
+ /* @__PURE__ */ jsx5("text", { fg: t.status.success, children: "Deployment Successful!" }),
5372
+ /* @__PURE__ */ jsx5("text", { fg: t.status.success, marginTop: 1, children: "Your OpenClaw instance is now running." })
4722
5373
  ]
4723
5374
  }
4724
5375
  ),
4725
- /* @__PURE__ */ jsxs4(
5376
+ /* @__PURE__ */ jsxs5(
4726
5377
  "box",
4727
5378
  {
4728
5379
  flexDirection: "column",
@@ -4731,36 +5382,36 @@ function DeployingView({ context }) {
4731
5382
  padding: 1,
4732
5383
  marginBottom: 1,
4733
5384
  children: [
4734
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "Connection Details" }),
4735
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", marginTop: 1, children: [
4736
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, width: 15, children: "Server IP:" }),
4737
- /* @__PURE__ */ jsx4("text", { fg: t.accent, children: state.serverIp || "N/A" })
5385
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: "Connection Details" }),
5386
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", marginTop: 1, children: [
5387
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 15, children: "Server IP:" }),
5388
+ /* @__PURE__ */ jsx5("text", { fg: t.accent, children: state.serverIp || "N/A" })
4738
5389
  ] }),
4739
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", children: [
4740
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, width: 15, children: "Tailscale IP:" }),
4741
- /* @__PURE__ */ jsx4("text", { fg: t.accent, children: state.tailscaleIp || "N/A" })
5390
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
5391
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 15, children: "Tailscale IP:" }),
5392
+ /* @__PURE__ */ jsx5("text", { fg: t.accent, children: state.tailscaleIp || "N/A" })
4742
5393
  ] }),
4743
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", children: [
4744
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, width: 15, children: "Gateway Port:" }),
4745
- /* @__PURE__ */ jsx4("text", { fg: t.accent, children: "18789" })
5394
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
5395
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 15, children: "Gateway Port:" }),
5396
+ /* @__PURE__ */ jsx5("text", { fg: t.accent, children: "18789" })
4746
5397
  ] })
4747
5398
  ]
4748
5399
  }
4749
5400
  ),
4750
- /* @__PURE__ */ jsx4("text", { fg: t.status.success, children: "Next steps:" }),
4751
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: " /ssh - Connect to your server" }),
4752
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: " /logs - View OpenClaw logs" }),
4753
- /* @__PURE__ */ jsxs4("text", { fg: t.fg.primary, children: [
5401
+ /* @__PURE__ */ jsx5("text", { fg: t.status.success, children: "Next steps:" }),
5402
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: " /ssh - Connect to your server" }),
5403
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: " /logs - View OpenClaw logs" }),
5404
+ /* @__PURE__ */ jsxs5("text", { fg: t.fg.primary, children: [
4754
5405
  " Gateway: http://",
4755
5406
  state.tailscaleIp || state.serverIp,
4756
5407
  ":18789/"
4757
5408
  ] }),
4758
- /* @__PURE__ */ jsx4("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5409
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
4759
5410
  ] });
4760
5411
  };
4761
5412
  const renderFailed = () => {
4762
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", children: [
4763
- /* @__PURE__ */ jsxs4(
5413
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
5414
+ /* @__PURE__ */ jsxs5(
4764
5415
  "box",
4765
5416
  {
4766
5417
  flexDirection: "column",
@@ -4769,26 +5420,26 @@ function DeployingView({ context }) {
4769
5420
  padding: 1,
4770
5421
  marginBottom: 1,
4771
5422
  children: [
4772
- /* @__PURE__ */ jsx4("text", { fg: t.status.error, children: "Deployment Failed" }),
4773
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, marginTop: 1, children: "Something went wrong during deployment." }),
4774
- /* @__PURE__ */ jsxs4("text", { fg: t.status.error, marginTop: 1, children: [
5423
+ /* @__PURE__ */ jsx5("text", { fg: t.status.error, children: "Deployment Failed" }),
5424
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, marginTop: 1, children: "Something went wrong during deployment." }),
5425
+ /* @__PURE__ */ jsxs5("text", { fg: t.status.error, marginTop: 1, children: [
4775
5426
  "Error: ",
4776
5427
  error
4777
5428
  ] })
4778
5429
  ]
4779
5430
  }
4780
5431
  ),
4781
- /* @__PURE__ */ jsxs4("box", { flexDirection: "column", marginBottom: 1, children: [
4782
- /* @__PURE__ */ jsx4("text", { fg: t.fg.primary, children: "What you can do:" }),
4783
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: " 1. Run /deploy again - it will resume from the last successful step" }),
4784
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: " 2. Run /status to check the current state of your deployment" }),
4785
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: " 3. Run /destroy and /new to start fresh if the issue persists" })
5432
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "column", marginBottom: 1, children: [
5433
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: "What you can do:" }),
5434
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: " 1. Run /deploy again - it will resume from the last successful step" }),
5435
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: " 2. Run /status to check the current state of your deployment" }),
5436
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: " 3. Run /destroy and /new to start fresh if the issue persists" })
4786
5437
  ] }),
4787
- /* @__PURE__ */ jsx4("text", { fg: t.fg.muted, marginTop: 1, children: "Press any key to return to home" })
5438
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.muted, marginTop: 1, children: "Press any key to return to home" })
4788
5439
  ] });
4789
5440
  };
4790
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", width: "100%", padding: 1, children: [
4791
- /* @__PURE__ */ jsx4("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsxs4("text", { fg: t.accent, children: [
5441
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5442
+ /* @__PURE__ */ jsx5("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsxs5("text", { fg: t.accent, children: [
4792
5443
  "Deploying: ",
4793
5444
  deploymentName
4794
5445
  ] }) }),
@@ -4797,7 +5448,7 @@ function DeployingView({ context }) {
4797
5448
  deployState === "waiting_terminal" && renderWaitingTerminal(),
4798
5449
  deployState === "success" && renderSuccess(),
4799
5450
  deployState === "failed" && renderFailed(),
4800
- /* @__PURE__ */ jsxs4(
5451
+ /* @__PURE__ */ jsxs5(
4801
5452
  "box",
4802
5453
  {
4803
5454
  flexDirection: "column",
@@ -4805,8 +5456,8 @@ function DeployingView({ context }) {
4805
5456
  borderColor: t.border.default,
4806
5457
  padding: 1,
4807
5458
  children: [
4808
- /* @__PURE__ */ jsx4("text", { fg: t.fg.secondary, children: "Deployment Log" }),
4809
- /* @__PURE__ */ jsx4("box", { flexDirection: "column", marginTop: 1, children: logs.map((log, i) => /* @__PURE__ */ jsx4("text", { fg: t.fg.muted, children: log }, i)) })
5459
+ /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: "Deployment Log" }),
5460
+ /* @__PURE__ */ jsx5("box", { flexDirection: "column", marginTop: 1, children: logs.map((log, i) => /* @__PURE__ */ jsx5("text", { fg: t.fg.muted, children: log }, i)) })
4810
5461
  ]
4811
5462
  }
4812
5463
  )
@@ -4814,13 +5465,13 @@ function DeployingView({ context }) {
4814
5465
  }
4815
5466
 
4816
5467
  // src/components/StatusView.tsx
4817
- import { useState as useState5 } from "react";
4818
- import { useKeyboard as useKeyboard4 } from "@opentui/react";
4819
- import { jsx as jsx5, jsxs as jsxs5 } from "@opentui/react/jsx-runtime";
5468
+ import { useState as useState6 } from "react";
5469
+ import { useKeyboard as useKeyboard6 } from "@opentui/react";
5470
+ import { jsx as jsx6, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
4820
5471
  function StatusView({ context }) {
4821
- const [selectedIndex, setSelectedIndex] = useState5(0);
4822
- const [healthStatus, setHealthStatus] = useState5(/* @__PURE__ */ new Map());
4823
- const [checking, setChecking] = useState5(null);
5472
+ const [selectedIndex, setSelectedIndex] = useState6(0);
5473
+ const [healthStatus, setHealthStatus] = useState6(/* @__PURE__ */ new Map());
5474
+ const [checking, setChecking] = useState6(null);
4824
5475
  const deployments = context.deployments;
4825
5476
  const checkHealth = async (deployment) => {
4826
5477
  const name = deployment.config.name;
@@ -4845,7 +5496,7 @@ function StatusView({ context }) {
4845
5496
  setHealthStatus((prev) => new Map(prev).set(name, health));
4846
5497
  setChecking(null);
4847
5498
  };
4848
- useKeyboard4((key) => {
5499
+ useKeyboard6((key) => {
4849
5500
  if (deployments.length === 0) {
4850
5501
  context.navigateTo("home");
4851
5502
  return;
@@ -4861,12 +5512,12 @@ function StatusView({ context }) {
4861
5512
  }
4862
5513
  });
4863
5514
  if (deployments.length === 0) {
4864
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", width: "100%", padding: 1, children: [
4865
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", marginBottom: 2, children: [
4866
- /* @__PURE__ */ jsx5("text", { fg: t.accent, children: "/status" }),
4867
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: " - Deployment Status" })
5515
+ return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5516
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 2, children: [
5517
+ /* @__PURE__ */ jsx6("text", { fg: t.accent, children: "/status" }),
5518
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, children: " - Deployment Status" })
4868
5519
  ] }),
4869
- /* @__PURE__ */ jsxs5(
5520
+ /* @__PURE__ */ jsxs6(
4870
5521
  "box",
4871
5522
  {
4872
5523
  flexDirection: "column",
@@ -4874,22 +5525,22 @@ function StatusView({ context }) {
4874
5525
  borderColor: t.border.default,
4875
5526
  padding: 1,
4876
5527
  children: [
4877
- /* @__PURE__ */ jsx5("text", { fg: t.status.warning, children: "No deployments found!" }),
4878
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, marginTop: 1, children: "Run /new to create a deployment." })
5528
+ /* @__PURE__ */ jsx6("text", { fg: t.status.warning, children: "No deployments found!" }),
5529
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, marginTop: 1, children: "Run /new to create a deployment." })
4879
5530
  ]
4880
5531
  }
4881
5532
  ),
4882
- /* @__PURE__ */ jsx5("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5533
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
4883
5534
  ] });
4884
5535
  }
4885
5536
  const selectedDeployment = deployments[selectedIndex];
4886
5537
  const selectedHealth = healthStatus.get(selectedDeployment.config.name);
4887
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", width: "100%", padding: 1, children: [
4888
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", marginBottom: 2, children: [
4889
- /* @__PURE__ */ jsx5("text", { fg: t.accent, children: "/status" }),
4890
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: " - Deployment Status" })
5538
+ return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5539
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 2, children: [
5540
+ /* @__PURE__ */ jsx6("text", { fg: t.accent, children: "/status" }),
5541
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, children: " - Deployment Status" })
4891
5542
  ] }),
4892
- /* @__PURE__ */ jsxs5(
5543
+ /* @__PURE__ */ jsxs6(
4893
5544
  "box",
4894
5545
  {
4895
5546
  flexDirection: "column",
@@ -4898,22 +5549,22 @@ function StatusView({ context }) {
4898
5549
  padding: 1,
4899
5550
  marginBottom: 1,
4900
5551
  children: [
4901
- /* @__PURE__ */ jsxs5("text", { fg: t.fg.primary, marginBottom: 1, children: [
5552
+ /* @__PURE__ */ jsxs6("text", { fg: t.fg.primary, marginBottom: 1, children: [
4902
5553
  "Deployments (",
4903
5554
  deployments.length,
4904
5555
  ")"
4905
5556
  ] }),
4906
5557
  deployments.map((deployment, index) => {
4907
5558
  const isSelected = index === selectedIndex;
4908
- return /* @__PURE__ */ jsxs5(
5559
+ return /* @__PURE__ */ jsxs6(
4909
5560
  "box",
4910
5561
  {
4911
5562
  flexDirection: "row",
4912
5563
  backgroundColor: isSelected ? t.selection.bg : void 0,
4913
5564
  children: [
4914
- /* @__PURE__ */ jsx5("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
4915
- /* @__PURE__ */ jsx5("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
4916
- /* @__PURE__ */ jsxs5("text", { fg: statusColor(deployment.state.status), children: [
5565
+ /* @__PURE__ */ jsx6("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
5566
+ /* @__PURE__ */ jsx6("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
5567
+ /* @__PURE__ */ jsxs6("text", { fg: statusColor(deployment.state.status), children: [
4917
5568
  "[",
4918
5569
  deployment.state.status,
4919
5570
  "]"
@@ -4926,7 +5577,7 @@ function StatusView({ context }) {
4926
5577
  ]
4927
5578
  }
4928
5579
  ),
4929
- /* @__PURE__ */ jsxs5(
5580
+ /* @__PURE__ */ jsxs6(
4930
5581
  "box",
4931
5582
  {
4932
5583
  flexDirection: "column",
@@ -4935,78 +5586,78 @@ function StatusView({ context }) {
4935
5586
  padding: 1,
4936
5587
  marginBottom: 1,
4937
5588
  children: [
4938
- /* @__PURE__ */ jsxs5("text", { fg: t.accent, children: [
5589
+ /* @__PURE__ */ jsxs6("text", { fg: t.accent, children: [
4939
5590
  "Details: ",
4940
5591
  selectedDeployment.config.name
4941
5592
  ] }),
4942
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", marginTop: 1, children: [
4943
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Status:" }),
4944
- /* @__PURE__ */ jsx5("text", { fg: statusColor(selectedDeployment.state.status), children: selectedDeployment.state.status })
5593
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginTop: 1, children: [
5594
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "Status:" }),
5595
+ /* @__PURE__ */ jsx6("text", { fg: statusColor(selectedDeployment.state.status), children: selectedDeployment.state.status })
4945
5596
  ] }),
4946
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4947
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Provider:" }),
4948
- /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: selectedDeployment.config.provider })
5597
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", children: [
5598
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "Provider:" }),
5599
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.primary, children: selectedDeployment.config.provider })
4949
5600
  ] }),
4950
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4951
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Server IP:" }),
4952
- /* @__PURE__ */ jsx5("text", { fg: t.accent, children: selectedDeployment.state.serverIp || "Not deployed" })
5601
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", children: [
5602
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "Server IP:" }),
5603
+ /* @__PURE__ */ jsx6("text", { fg: t.accent, children: selectedDeployment.state.serverIp || "Not deployed" })
4953
5604
  ] }),
4954
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4955
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Tailscale IP:" }),
4956
- /* @__PURE__ */ jsx5("text", { fg: t.accent, children: selectedDeployment.state.tailscaleIp || "Not configured" })
5605
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", children: [
5606
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "Tailscale IP:" }),
5607
+ /* @__PURE__ */ jsx6("text", { fg: t.accent, children: selectedDeployment.state.tailscaleIp || "Not configured" })
4957
5608
  ] }),
4958
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4959
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Created:" }),
4960
- /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: new Date(selectedDeployment.config.createdAt).toLocaleString() })
5609
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", children: [
5610
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "Created:" }),
5611
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.primary, children: new Date(selectedDeployment.config.createdAt).toLocaleString() })
4961
5612
  ] }),
4962
- selectedDeployment.state.deployedAt && /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4963
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Deployed:" }),
4964
- /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: new Date(selectedDeployment.state.deployedAt).toLocaleString() })
5613
+ selectedDeployment.state.deployedAt && /* @__PURE__ */ jsxs6("box", { flexDirection: "row", children: [
5614
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "Deployed:" }),
5615
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.primary, children: new Date(selectedDeployment.state.deployedAt).toLocaleString() })
4965
5616
  ] }),
4966
- selectedDeployment.state.lastError && /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4967
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "Last Error:" }),
4968
- /* @__PURE__ */ jsx5("text", { fg: t.status.error, children: selectedDeployment.state.lastError })
5617
+ selectedDeployment.state.lastError && /* @__PURE__ */ jsxs6("box", { flexDirection: "row", children: [
5618
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "Last Error:" }),
5619
+ /* @__PURE__ */ jsx6("text", { fg: t.status.error, children: selectedDeployment.state.lastError })
4969
5620
  ] }),
4970
- selectedHealth && /* @__PURE__ */ jsxs5("box", { flexDirection: "column", marginTop: 1, children: [
4971
- /* @__PURE__ */ jsx5("text", { fg: t.fg.primary, children: "Health Check:" }),
4972
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4973
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "SSH:" }),
4974
- /* @__PURE__ */ jsx5("text", { fg: selectedHealth.sshConnectable ? t.status.success : t.status.error, children: selectedHealth.sshConnectable ? "Connected" : "Unreachable" })
5621
+ selectedHealth && /* @__PURE__ */ jsxs6("box", { flexDirection: "column", marginTop: 1, children: [
5622
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.primary, children: "Health Check:" }),
5623
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", children: [
5624
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "SSH:" }),
5625
+ /* @__PURE__ */ jsx6("text", { fg: selectedHealth.sshConnectable ? t.status.success : t.status.error, children: selectedHealth.sshConnectable ? "Connected" : "Unreachable" })
4975
5626
  ] }),
4976
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", children: [
4977
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, width: 18, children: "OpenClaw:" }),
4978
- /* @__PURE__ */ jsx5("text", { fg: selectedHealth.openclawRunning ? t.status.success : t.status.error, children: selectedHealth.openclawRunning ? "Running" : "Not running" })
5627
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", children: [
5628
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, width: 18, children: "OpenClaw:" }),
5629
+ /* @__PURE__ */ jsx6("text", { fg: selectedHealth.openclawRunning ? t.status.success : t.status.error, children: selectedHealth.openclawRunning ? "Running" : "Not running" })
4979
5630
  ] }),
4980
- /* @__PURE__ */ jsxs5("text", { fg: t.fg.muted, children: [
5631
+ /* @__PURE__ */ jsxs6("text", { fg: t.fg.muted, children: [
4981
5632
  "Last checked: ",
4982
5633
  selectedHealth.lastChecked.toLocaleTimeString()
4983
5634
  ] })
4984
5635
  ] }),
4985
- checking === selectedDeployment.config.name && /* @__PURE__ */ jsx5("text", { fg: t.status.warning, marginTop: 1, children: "Checking health..." })
5636
+ checking === selectedDeployment.config.name && /* @__PURE__ */ jsx6("text", { fg: t.status.warning, marginTop: 1, children: "Checking health..." })
4986
5637
  ]
4987
5638
  }
4988
5639
  ),
4989
- selectedDeployment.state.checkpoints.length > 0 && /* @__PURE__ */ jsxs5("box", { flexDirection: "row", marginBottom: 1, children: [
4990
- /* @__PURE__ */ jsx5("text", { fg: t.fg.secondary, children: "Checkpoints: " }),
4991
- /* @__PURE__ */ jsxs5("text", { fg: t.status.success, children: [
5640
+ selectedDeployment.state.checkpoints.length > 0 && /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 1, children: [
5641
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, children: "Checkpoints: " }),
5642
+ /* @__PURE__ */ jsxs6("text", { fg: t.status.success, children: [
4992
5643
  selectedDeployment.state.checkpoints.length,
4993
5644
  " completed"
4994
5645
  ] })
4995
5646
  ] }),
4996
- /* @__PURE__ */ jsx5("text", { fg: t.fg.muted, children: "Up/Down: Select | Enter: Health check | Esc: Back" })
5647
+ /* @__PURE__ */ jsx6("text", { fg: t.fg.muted, children: "Up/Down: Select | Enter: Health check | Esc: Back" })
4997
5648
  ] });
4998
5649
  }
4999
5650
 
5000
5651
  // src/components/SSHView.tsx
5001
- import { useState as useState6, useCallback as useCallback2 } from "react";
5002
- import { useKeyboard as useKeyboard5 } from "@opentui/react";
5003
- import { jsx as jsx6, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
5652
+ import { useState as useState7, useCallback as useCallback2 } from "react";
5653
+ import { useKeyboard as useKeyboard7 } from "@opentui/react";
5654
+ import { jsx as jsx7, jsxs as jsxs7 } from "@opentui/react/jsx-runtime";
5004
5655
  function SSHView({ context }) {
5005
- const [viewState, setViewState] = useState6("selecting");
5006
- const [selectedIndex, setSelectedIndex] = useState6(0);
5007
- const [error, setError] = useState6(null);
5008
- const [connectedDeployment, setConnectedDeployment] = useState6(null);
5009
- const [terminalName, setTerminalName] = useState6("");
5656
+ const [viewState, setViewState] = useState7("selecting");
5657
+ const [selectedIndex, setSelectedIndex] = useState7(0);
5658
+ const [error, setError] = useState7(null);
5659
+ const [connectedDeployment, setConnectedDeployment] = useState7(null);
5660
+ const [terminalName, setTerminalName] = useState7("");
5010
5661
  const deployedDeployments = context.deployments.filter(
5011
5662
  (d) => d.state.status === "deployed" && d.state.serverIp
5012
5663
  );
@@ -5025,7 +5676,7 @@ function SSHView({ context }) {
5025
5676
  }
5026
5677
  }, []);
5027
5678
  const selectedDeployment = deployedDeployments[selectedIndex];
5028
- useKeyboard5((key) => {
5679
+ useKeyboard7((key) => {
5029
5680
  if (deployedDeployments.length === 0) {
5030
5681
  context.navigateTo("home");
5031
5682
  return;
@@ -5049,12 +5700,12 @@ function SSHView({ context }) {
5049
5700
  }
5050
5701
  });
5051
5702
  if (deployedDeployments.length === 0) {
5052
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5053
- /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 2, children: [
5054
- /* @__PURE__ */ jsx6("text", { fg: t.accent, children: "/ssh" }),
5055
- /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, children: " - SSH into deployment" })
5703
+ return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5704
+ /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 2, children: [
5705
+ /* @__PURE__ */ jsx7("text", { fg: t.accent, children: "/ssh" }),
5706
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, children: " - SSH into deployment" })
5056
5707
  ] }),
5057
- /* @__PURE__ */ jsxs6(
5708
+ /* @__PURE__ */ jsxs7(
5058
5709
  "box",
5059
5710
  {
5060
5711
  flexDirection: "column",
@@ -5062,23 +5713,23 @@ function SSHView({ context }) {
5062
5713
  borderColor: t.border.default,
5063
5714
  padding: 1,
5064
5715
  children: [
5065
- /* @__PURE__ */ jsx6("text", { fg: t.status.warning, children: "No deployed instances found!" }),
5066
- /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, marginTop: 1, children: "Deploy an instance first with /deploy" })
5716
+ /* @__PURE__ */ jsx7("text", { fg: t.status.warning, children: "No deployed instances found!" }),
5717
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, marginTop: 1, children: "Deploy an instance first with /deploy" })
5067
5718
  ]
5068
5719
  }
5069
5720
  ),
5070
- /* @__PURE__ */ jsx6("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5721
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5071
5722
  ] });
5072
5723
  }
5073
5724
  if (viewState === "selecting") {
5074
5725
  const terminal = detectTerminal();
5075
5726
  const terminalDisplayName = getTerminalDisplayName(terminal.app);
5076
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5077
- /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 2, children: [
5078
- /* @__PURE__ */ jsx6("text", { fg: t.accent, children: "/ssh" }),
5079
- /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, children: " - Select a deployment to connect" })
5727
+ return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5728
+ /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 2, children: [
5729
+ /* @__PURE__ */ jsx7("text", { fg: t.accent, children: "/ssh" }),
5730
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, children: " - Select a deployment to connect" })
5080
5731
  ] }),
5081
- /* @__PURE__ */ jsx6(
5732
+ /* @__PURE__ */ jsx7(
5082
5733
  "box",
5083
5734
  {
5084
5735
  flexDirection: "column",
@@ -5088,15 +5739,15 @@ function SSHView({ context }) {
5088
5739
  marginBottom: 1,
5089
5740
  children: deployedDeployments.map((deployment, index) => {
5090
5741
  const isSelected = index === selectedIndex;
5091
- return /* @__PURE__ */ jsxs6(
5742
+ return /* @__PURE__ */ jsxs7(
5092
5743
  "box",
5093
5744
  {
5094
5745
  flexDirection: "row",
5095
5746
  backgroundColor: isSelected ? t.selection.bg : void 0,
5096
5747
  children: [
5097
- /* @__PURE__ */ jsx6("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
5098
- /* @__PURE__ */ jsx6("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
5099
- /* @__PURE__ */ jsx6("text", { fg: t.accent, children: deployment.state.serverIp })
5748
+ /* @__PURE__ */ jsx7("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
5749
+ /* @__PURE__ */ jsx7("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
5750
+ /* @__PURE__ */ jsx7("text", { fg: t.accent, children: deployment.state.serverIp })
5100
5751
  ]
5101
5752
  },
5102
5753
  deployment.config.name
@@ -5104,24 +5755,24 @@ function SSHView({ context }) {
5104
5755
  })
5105
5756
  }
5106
5757
  ),
5107
- /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 1, children: [
5108
- /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, children: "Terminal: " }),
5109
- /* @__PURE__ */ jsx6("text", { fg: t.status.success, children: terminalDisplayName }),
5110
- /* @__PURE__ */ jsx6("text", { fg: t.fg.secondary, children: " (will open in a new window)" })
5758
+ /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 1, children: [
5759
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, children: "Terminal: " }),
5760
+ /* @__PURE__ */ jsx7("text", { fg: t.status.success, children: terminalDisplayName }),
5761
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, children: " (will open in a new window)" })
5111
5762
  ] }),
5112
- /* @__PURE__ */ jsx6("text", { fg: t.fg.muted, children: "Arrow keys to select | Enter to connect | Esc to go back" })
5763
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, children: "Arrow keys to select | Enter to connect | Esc to go back" })
5113
5764
  ] });
5114
5765
  }
5115
5766
  if (viewState === "connected") {
5116
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5117
- /* @__PURE__ */ jsxs6("box", { flexDirection: "row", marginBottom: 2, children: [
5118
- /* @__PURE__ */ jsx6("text", { fg: t.status.success, children: "/ssh" }),
5119
- /* @__PURE__ */ jsxs6("text", { fg: t.fg.secondary, children: [
5767
+ return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5768
+ /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 2, children: [
5769
+ /* @__PURE__ */ jsx7("text", { fg: t.status.success, children: "/ssh" }),
5770
+ /* @__PURE__ */ jsxs7("text", { fg: t.fg.secondary, children: [
5120
5771
  " - Connected to ",
5121
5772
  connectedDeployment
5122
5773
  ] })
5123
5774
  ] }),
5124
- /* @__PURE__ */ jsxs6(
5775
+ /* @__PURE__ */ jsxs7(
5125
5776
  "box",
5126
5777
  {
5127
5778
  flexDirection: "column",
@@ -5130,8 +5781,8 @@ function SSHView({ context }) {
5130
5781
  padding: 1,
5131
5782
  marginBottom: 1,
5132
5783
  children: [
5133
- /* @__PURE__ */ jsx6("text", { fg: t.status.success, children: "SSH Session Opened" }),
5134
- /* @__PURE__ */ jsxs6("text", { fg: t.fg.primary, marginTop: 1, children: [
5784
+ /* @__PURE__ */ jsx7("text", { fg: t.status.success, children: "SSH Session Opened" }),
5785
+ /* @__PURE__ */ jsxs7("text", { fg: t.fg.primary, marginTop: 1, children: [
5135
5786
  "A new ",
5136
5787
  terminalName,
5137
5788
  " window/tab has been opened."
@@ -5139,7 +5790,7 @@ function SSHView({ context }) {
5139
5790
  ]
5140
5791
  }
5141
5792
  ),
5142
- /* @__PURE__ */ jsxs6(
5793
+ /* @__PURE__ */ jsxs7(
5143
5794
  "box",
5144
5795
  {
5145
5796
  flexDirection: "column",
@@ -5148,41 +5799,41 @@ function SSHView({ context }) {
5148
5799
  padding: 1,
5149
5800
  marginBottom: 1,
5150
5801
  children: [
5151
- /* @__PURE__ */ jsxs6("text", { fg: t.fg.primary, children: [
5802
+ /* @__PURE__ */ jsxs7("text", { fg: t.fg.primary, children: [
5152
5803
  "Your SSH session is running in ",
5153
5804
  terminalName,
5154
5805
  "."
5155
5806
  ] }),
5156
- /* @__PURE__ */ jsx6("text", { fg: t.fg.primary, marginTop: 1, children: "When you're done, type 'exit' or close the tab." })
5807
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.primary, marginTop: 1, children: "When you're done, type 'exit' or close the tab." })
5157
5808
  ]
5158
5809
  }
5159
5810
  ),
5160
- /* @__PURE__ */ jsx6("text", { fg: t.fg.muted, marginTop: 1, children: "Press Enter or Esc to return to ClawControl" })
5811
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, marginTop: 1, children: "Press Enter or Esc to return to ClawControl" })
5161
5812
  ] });
5162
5813
  }
5163
5814
  if (viewState === "error") {
5164
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5165
- /* @__PURE__ */ jsx6("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx6("text", { fg: t.status.error, children: "SSH Error" }) }),
5166
- /* @__PURE__ */ jsx6(
5815
+ return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5816
+ /* @__PURE__ */ jsx7("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx7("text", { fg: t.status.error, children: "SSH Error" }) }),
5817
+ /* @__PURE__ */ jsx7(
5167
5818
  "box",
5168
5819
  {
5169
5820
  flexDirection: "column",
5170
5821
  borderStyle: "single",
5171
5822
  borderColor: t.status.error,
5172
5823
  padding: 1,
5173
- children: /* @__PURE__ */ jsx6("text", { fg: t.status.error, children: error })
5824
+ children: /* @__PURE__ */ jsx7("text", { fg: t.status.error, children: error })
5174
5825
  }
5175
5826
  ),
5176
- /* @__PURE__ */ jsx6("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5827
+ /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5177
5828
  ] });
5178
5829
  }
5179
5830
  return null;
5180
5831
  }
5181
5832
 
5182
5833
  // src/components/LogsView.tsx
5183
- import { useState as useState7, useEffect as useEffect3, useRef as useRef3 } from "react";
5184
- import { useKeyboard as useKeyboard6 } from "@opentui/react";
5185
- import { Fragment, jsx as jsx7, jsxs as jsxs7 } from "@opentui/react/jsx-runtime";
5834
+ import { useState as useState8, useEffect as useEffect3, useRef as useRef5 } from "react";
5835
+ import { useKeyboard as useKeyboard8 } from "@opentui/react";
5836
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
5186
5837
  function parseLogLine(line) {
5187
5838
  if (!line.trim()) return null;
5188
5839
  const match = line.match(/^(\w+\s+\d+\s+[\d:]+)\s+\S+\s+\S+:\s*(.*)$/);
@@ -5201,14 +5852,14 @@ function truncateLine(line, maxWidth = 120) {
5201
5852
  return line.substring(0, maxWidth - 3) + "...";
5202
5853
  }
5203
5854
  function LogsView({ context }) {
5204
- const [viewState, setViewState] = useState7("selecting");
5205
- const [selectedIndex, setSelectedIndex] = useState7(0);
5206
- const [logs, setLogs] = useState7([]);
5207
- const [error, setError] = useState7(null);
5208
- const [autoRefresh, setAutoRefresh] = useState7(false);
5209
- const [lastFetched, setLastFetched] = useState7(null);
5210
- const sshRef = useRef3(null);
5211
- const refreshIntervalRef = useRef3(null);
5855
+ const [viewState, setViewState] = useState8("selecting");
5856
+ const [selectedIndex, setSelectedIndex] = useState8(0);
5857
+ const [logs, setLogs] = useState8([]);
5858
+ const [error, setError] = useState8(null);
5859
+ const [autoRefresh, setAutoRefresh] = useState8(false);
5860
+ const [lastFetched, setLastFetched] = useState8(null);
5861
+ const sshRef = useRef5(null);
5862
+ const refreshIntervalRef = useRef5(null);
5212
5863
  const deployedDeployments = context.deployments.filter(
5213
5864
  (d) => d.state.status === "deployed" && d.state.serverIp
5214
5865
  );
@@ -5259,7 +5910,7 @@ function LogsView({ context }) {
5259
5910
  setLogs([]);
5260
5911
  };
5261
5912
  const selectedDeployment = deployedDeployments[selectedIndex];
5262
- useKeyboard6((key) => {
5913
+ useKeyboard8((key) => {
5263
5914
  if (deployedDeployments.length === 0) {
5264
5915
  context.navigateTo("home");
5265
5916
  return;
@@ -5292,12 +5943,12 @@ function LogsView({ context }) {
5292
5943
  }
5293
5944
  });
5294
5945
  if (deployedDeployments.length === 0) {
5295
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5296
- /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 2, children: [
5297
- /* @__PURE__ */ jsx7("text", { fg: t.accent, children: "/logs" }),
5298
- /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, children: " - View deployment logs" })
5946
+ return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5947
+ /* @__PURE__ */ jsxs8("box", { flexDirection: "row", marginBottom: 2, children: [
5948
+ /* @__PURE__ */ jsx8("text", { fg: t.accent, children: "/logs" }),
5949
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: " - View deployment logs" })
5299
5950
  ] }),
5300
- /* @__PURE__ */ jsxs7(
5951
+ /* @__PURE__ */ jsxs8(
5301
5952
  "box",
5302
5953
  {
5303
5954
  flexDirection: "column",
@@ -5305,21 +5956,21 @@ function LogsView({ context }) {
5305
5956
  borderColor: t.border.default,
5306
5957
  padding: 1,
5307
5958
  children: [
5308
- /* @__PURE__ */ jsx7("text", { fg: t.status.warning, children: "No deployed instances found!" }),
5309
- /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, marginTop: 1, children: "Deploy an instance first with /deploy" })
5959
+ /* @__PURE__ */ jsx8("text", { fg: t.status.warning, children: "No deployed instances found!" }),
5960
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, marginTop: 1, children: "Deploy an instance first with /deploy" })
5310
5961
  ]
5311
5962
  }
5312
5963
  ),
5313
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5964
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5314
5965
  ] });
5315
5966
  }
5316
5967
  if (viewState === "selecting") {
5317
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5318
- /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 2, children: [
5319
- /* @__PURE__ */ jsx7("text", { fg: t.accent, children: "/logs" }),
5320
- /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, children: " - Select a deployment" })
5968
+ return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5969
+ /* @__PURE__ */ jsxs8("box", { flexDirection: "row", marginBottom: 2, children: [
5970
+ /* @__PURE__ */ jsx8("text", { fg: t.accent, children: "/logs" }),
5971
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: " - Select a deployment" })
5321
5972
  ] }),
5322
- /* @__PURE__ */ jsx7(
5973
+ /* @__PURE__ */ jsx8(
5323
5974
  "box",
5324
5975
  {
5325
5976
  flexDirection: "column",
@@ -5328,15 +5979,15 @@ function LogsView({ context }) {
5328
5979
  padding: 1,
5329
5980
  children: deployedDeployments.map((deployment, index) => {
5330
5981
  const isSelected = index === selectedIndex;
5331
- return /* @__PURE__ */ jsxs7(
5982
+ return /* @__PURE__ */ jsxs8(
5332
5983
  "box",
5333
5984
  {
5334
5985
  flexDirection: "row",
5335
5986
  backgroundColor: isSelected ? t.selection.bg : void 0,
5336
5987
  children: [
5337
- /* @__PURE__ */ jsx7("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
5338
- /* @__PURE__ */ jsx7("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
5339
- /* @__PURE__ */ jsx7("text", { fg: t.accent, children: deployment.state.serverIp })
5988
+ /* @__PURE__ */ jsx8("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
5989
+ /* @__PURE__ */ jsx8("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
5990
+ /* @__PURE__ */ jsx8("text", { fg: t.accent, children: deployment.state.serverIp })
5340
5991
  ]
5341
5992
  },
5342
5993
  deployment.config.name
@@ -5344,70 +5995,70 @@ function LogsView({ context }) {
5344
5995
  })
5345
5996
  }
5346
5997
  ),
5347
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, marginTop: 2, children: "Arrow keys to select | Enter to view logs | Esc to go back" })
5998
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, marginTop: 2, children: "Arrow keys to select | Enter to view logs | Esc to go back" })
5348
5999
  ] });
5349
6000
  }
5350
6001
  if (viewState === "loading") {
5351
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5352
- /* @__PURE__ */ jsx7("text", { fg: t.accent, children: "Loading logs..." }),
5353
- /* @__PURE__ */ jsx7("text", { fg: t.fg.secondary, marginTop: 1, children: "Fetching OpenClaw logs from server..." })
6002
+ return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6003
+ /* @__PURE__ */ jsx8("text", { fg: t.accent, children: "Loading logs..." }),
6004
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, marginTop: 1, children: "Fetching OpenClaw logs from server..." })
5354
6005
  ] });
5355
6006
  }
5356
6007
  if (viewState === "error") {
5357
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5358
- /* @__PURE__ */ jsx7("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx7("text", { fg: t.status.error, children: "Error Loading Logs" }) }),
5359
- /* @__PURE__ */ jsx7(
6008
+ return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6009
+ /* @__PURE__ */ jsx8("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "Error Loading Logs" }) }),
6010
+ /* @__PURE__ */ jsx8(
5360
6011
  "box",
5361
6012
  {
5362
6013
  flexDirection: "column",
5363
6014
  borderStyle: "single",
5364
6015
  borderColor: t.status.error,
5365
6016
  padding: 1,
5366
- children: /* @__PURE__ */ jsx7("text", { fg: t.status.error, children: error })
6017
+ children: /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: error })
5367
6018
  }
5368
6019
  ),
5369
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to go back" })
6020
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to go back" })
5370
6021
  ] });
5371
6022
  }
5372
6023
  const visibleLogs = logs;
5373
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5374
- /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginBottom: 1, children: [
5375
- /* @__PURE__ */ jsxs7("text", { fg: t.accent, children: [
6024
+ return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6025
+ /* @__PURE__ */ jsxs8("box", { flexDirection: "row", marginBottom: 1, children: [
6026
+ /* @__PURE__ */ jsxs8("text", { fg: t.accent, children: [
5376
6027
  "Logs: ",
5377
6028
  selectedDeployment.config.name
5378
6029
  ] }),
5379
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, children: " | " }),
5380
- /* @__PURE__ */ jsxs7("text", { fg: autoRefresh ? t.status.success : t.fg.muted, children: [
6030
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, children: " | " }),
6031
+ /* @__PURE__ */ jsxs8("text", { fg: autoRefresh ? t.status.success : t.fg.muted, children: [
5381
6032
  "Auto: ",
5382
6033
  autoRefresh ? "ON (5s)" : "OFF"
5383
6034
  ] }),
5384
- lastFetched && /* @__PURE__ */ jsxs7(Fragment, { children: [
5385
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, children: " | " }),
5386
- /* @__PURE__ */ jsxs7("text", { fg: t.fg.muted, children: [
6035
+ lastFetched && /* @__PURE__ */ jsxs8(Fragment2, { children: [
6036
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, children: " | " }),
6037
+ /* @__PURE__ */ jsxs8("text", { fg: t.fg.muted, children: [
5387
6038
  "Fetched: ",
5388
6039
  lastFetched.toLocaleTimeString()
5389
6040
  ] })
5390
6041
  ] })
5391
6042
  ] }),
5392
- /* @__PURE__ */ jsx7(
6043
+ /* @__PURE__ */ jsx8(
5393
6044
  "box",
5394
6045
  {
5395
6046
  flexDirection: "column",
5396
6047
  borderStyle: "single",
5397
6048
  borderColor: t.border.default,
5398
6049
  padding: 1,
5399
- children: /* @__PURE__ */ jsx7("box", { flexDirection: "column", children: visibleLogs.map((line, i) => {
6050
+ children: /* @__PURE__ */ jsx8("box", { flexDirection: "column", children: visibleLogs.map((line, i) => {
5400
6051
  const parsed = parseLogLine(line);
5401
6052
  if (!parsed) return null;
5402
6053
  const displayLine = parsed.timestamp ? `${parsed.timestamp} ${truncateLine(parsed.message, 100)}` : truncateLine(parsed.message, 120);
5403
- return /* @__PURE__ */ jsx7("text", { fg: logLevelColor(parsed.level), children: displayLine }, i);
6054
+ return /* @__PURE__ */ jsx8("text", { fg: logLevelColor(parsed.level), children: displayLine }, i);
5404
6055
  }) })
5405
6056
  }
5406
6057
  ),
5407
- /* @__PURE__ */ jsxs7("box", { flexDirection: "row", marginTop: 1, children: [
5408
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, children: "R: Refresh | A: Toggle auto-refresh | Esc: Back" }),
5409
- /* @__PURE__ */ jsx7("text", { fg: t.fg.muted, children: " | " }),
5410
- /* @__PURE__ */ jsxs7("text", { fg: t.accent, children: [
6058
+ /* @__PURE__ */ jsxs8("box", { flexDirection: "row", marginTop: 1, children: [
6059
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, children: "R: Refresh | A: Toggle auto-refresh | Esc: Back" }),
6060
+ /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, children: " | " }),
6061
+ /* @__PURE__ */ jsxs8("text", { fg: t.accent, children: [
5411
6062
  "Showing last ",
5412
6063
  visibleLogs.length,
5413
6064
  " lines"
@@ -5417,14 +6068,14 @@ function LogsView({ context }) {
5417
6068
  }
5418
6069
 
5419
6070
  // src/components/DestroyView.tsx
5420
- import { useState as useState8 } from "react";
5421
- import { useKeyboard as useKeyboard7 } from "@opentui/react";
5422
- import { jsx as jsx8, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
6071
+ import { useState as useState9 } from "react";
6072
+ import { useKeyboard as useKeyboard9 } from "@opentui/react";
6073
+ import { jsx as jsx9, jsxs as jsxs9 } from "@opentui/react/jsx-runtime";
5423
6074
  function DestroyView({ context }) {
5424
- const [viewState, setViewState] = useState8("selecting");
5425
- const [selectedIndex, setSelectedIndex] = useState8(0);
5426
- const [error, setError] = useState8(null);
5427
- const [confirmText, setConfirmText] = useState8("");
6075
+ const [viewState, setViewState] = useState9("selecting");
6076
+ const [selectedIndex, setSelectedIndex] = useState9(0);
6077
+ const [error, setError] = useState9(null);
6078
+ const [confirmText, setConfirmText] = useState9("");
5428
6079
  const deployments = context.deployments;
5429
6080
  const destroyDeployment = async (name) => {
5430
6081
  setViewState("destroying");
@@ -5469,7 +6120,7 @@ function DestroyView({ context }) {
5469
6120
  }
5470
6121
  };
5471
6122
  const selectedDeployment = deployments[selectedIndex];
5472
- useKeyboard7((key) => {
6123
+ useKeyboard9((key) => {
5473
6124
  if (deployments.length === 0) {
5474
6125
  context.navigateTo("home");
5475
6126
  return;
@@ -5494,31 +6145,31 @@ function DestroyView({ context }) {
5494
6145
  }
5495
6146
  });
5496
6147
  if (deployments.length === 0) {
5497
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5498
- /* @__PURE__ */ jsxs8("box", { flexDirection: "row", marginBottom: 2, children: [
5499
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "/destroy" }),
5500
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: " - Destroy deployment" })
6148
+ return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6149
+ /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginBottom: 2, children: [
6150
+ /* @__PURE__ */ jsx9("text", { fg: t.status.error, children: "/destroy" }),
6151
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " - Destroy deployment" })
5501
6152
  ] }),
5502
- /* @__PURE__ */ jsx8(
6153
+ /* @__PURE__ */ jsx9(
5503
6154
  "box",
5504
6155
  {
5505
6156
  flexDirection: "column",
5506
6157
  borderStyle: "single",
5507
6158
  borderColor: t.border.default,
5508
6159
  padding: 1,
5509
- children: /* @__PURE__ */ jsx8("text", { fg: t.status.warning, children: "No deployments found!" })
6160
+ children: /* @__PURE__ */ jsx9("text", { fg: t.status.warning, children: "No deployments found!" })
5510
6161
  }
5511
6162
  ),
5512
- /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
6163
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5513
6164
  ] });
5514
6165
  }
5515
6166
  if (viewState === "selecting") {
5516
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5517
- /* @__PURE__ */ jsxs8("box", { flexDirection: "row", marginBottom: 2, children: [
5518
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "/destroy" }),
5519
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: " - Select a deployment to destroy" })
6167
+ return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6168
+ /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginBottom: 2, children: [
6169
+ /* @__PURE__ */ jsx9("text", { fg: t.status.error, children: "/destroy" }),
6170
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " - Select a deployment to destroy" })
5520
6171
  ] }),
5521
- /* @__PURE__ */ jsxs8(
6172
+ /* @__PURE__ */ jsxs9(
5522
6173
  "box",
5523
6174
  {
5524
6175
  flexDirection: "column",
@@ -5526,18 +6177,18 @@ function DestroyView({ context }) {
5526
6177
  borderColor: t.status.error,
5527
6178
  padding: 1,
5528
6179
  children: [
5529
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, marginBottom: 1, children: "WARNING: This action cannot be undone!" }),
6180
+ /* @__PURE__ */ jsx9("text", { fg: t.status.error, marginBottom: 1, children: "WARNING: This action cannot be undone!" }),
5530
6181
  deployments.map((deployment, index) => {
5531
6182
  const isSelected = index === selectedIndex;
5532
- return /* @__PURE__ */ jsxs8(
6183
+ return /* @__PURE__ */ jsxs9(
5533
6184
  "box",
5534
6185
  {
5535
6186
  flexDirection: "row",
5536
6187
  backgroundColor: isSelected ? t.selection.bg : void 0,
5537
6188
  children: [
5538
- /* @__PURE__ */ jsx8("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
5539
- /* @__PURE__ */ jsx8("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
5540
- /* @__PURE__ */ jsxs8("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: [
6189
+ /* @__PURE__ */ jsx9("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
6190
+ /* @__PURE__ */ jsx9("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, width: 25, children: deployment.config.name }),
6191
+ /* @__PURE__ */ jsxs9("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: [
5541
6192
  "[",
5542
6193
  deployment.state.status,
5543
6194
  "]"
@@ -5550,13 +6201,13 @@ function DestroyView({ context }) {
5550
6201
  ]
5551
6202
  }
5552
6203
  ),
5553
- /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, marginTop: 2, children: "Arrow keys to select | Enter to destroy | Esc to go back" })
6204
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.muted, marginTop: 2, children: "Arrow keys to select | Enter to destroy | Esc to go back" })
5554
6205
  ] });
5555
6206
  }
5556
6207
  if (viewState === "confirming") {
5557
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5558
- /* @__PURE__ */ jsx8("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "Confirm Destruction" }) }),
5559
- /* @__PURE__ */ jsxs8(
6208
+ return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6209
+ /* @__PURE__ */ jsx9("box", { flexDirection: "row", marginBottom: 2, children: /* @__PURE__ */ jsx9("text", { fg: t.status.error, children: "Confirm Destruction" }) }),
6210
+ /* @__PURE__ */ jsxs9(
5560
6211
  "box",
5561
6212
  {
5562
6213
  flexDirection: "column",
@@ -5564,26 +6215,26 @@ function DestroyView({ context }) {
5564
6215
  borderColor: t.status.error,
5565
6216
  padding: 1,
5566
6217
  children: [
5567
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "You are about to destroy:" }),
5568
- /* @__PURE__ */ jsxs8("text", { fg: t.fg.primary, marginTop: 1, children: [
6218
+ /* @__PURE__ */ jsx9("text", { fg: t.status.error, children: "You are about to destroy:" }),
6219
+ /* @__PURE__ */ jsxs9("text", { fg: t.fg.primary, marginTop: 1, children: [
5569
6220
  "Deployment: ",
5570
6221
  selectedDeployment.config.name
5571
6222
  ] }),
5572
- selectedDeployment.state.serverIp && /* @__PURE__ */ jsxs8("text", { fg: t.fg.primary, children: [
6223
+ selectedDeployment.state.serverIp && /* @__PURE__ */ jsxs9("text", { fg: t.fg.primary, children: [
5573
6224
  "Server IP: ",
5574
6225
  selectedDeployment.state.serverIp
5575
6226
  ] }),
5576
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, marginTop: 1, children: "This will permanently delete:" }),
5577
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: "\u2022 The VPS server (if deployed)" }),
5578
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: "\u2022 All data on the server" }),
5579
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: "\u2022 Local configuration files" }),
5580
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, children: "\u2022 SSH keys" })
6227
+ /* @__PURE__ */ jsx9("text", { fg: t.status.error, marginTop: 1, children: "This will permanently delete:" }),
6228
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "\u2022 The VPS server (if deployed)" }),
6229
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "\u2022 All data on the server" }),
6230
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "\u2022 Local configuration files" }),
6231
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "\u2022 SSH keys" })
5581
6232
  ]
5582
6233
  }
5583
6234
  ),
5584
- /* @__PURE__ */ jsx8("text", { fg: t.status.warning, marginTop: 2, children: "Type the deployment name to confirm:" }),
5585
- /* @__PURE__ */ jsx8("text", { fg: t.fg.primary, marginTop: 1, children: "Confirm:" }),
5586
- /* @__PURE__ */ jsx8(
6235
+ /* @__PURE__ */ jsx9("text", { fg: t.status.warning, marginTop: 2, children: "Type the deployment name to confirm:" }),
6236
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, marginTop: 1, children: "Confirm:" }),
6237
+ /* @__PURE__ */ jsx9(
5587
6238
  "input",
5588
6239
  {
5589
6240
  value: confirmText,
@@ -5606,19 +6257,19 @@ function DestroyView({ context }) {
5606
6257
  }
5607
6258
  }
5608
6259
  ),
5609
- error && /* @__PURE__ */ jsx8("text", { fg: t.status.error, marginTop: 1, children: error }),
5610
- /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, marginTop: 2, children: "Press Esc to cancel" })
6260
+ error && /* @__PURE__ */ jsx9("text", { fg: t.status.error, marginTop: 1, children: error }),
6261
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.muted, marginTop: 2, children: "Press Esc to cancel" })
5611
6262
  ] });
5612
6263
  }
5613
6264
  if (viewState === "destroying") {
5614
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5615
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "Destroying deployment..." }),
5616
- /* @__PURE__ */ jsx8("text", { fg: t.fg.secondary, marginTop: 1, children: "Deleting server and cleaning up resources..." })
6265
+ return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6266
+ /* @__PURE__ */ jsx9("text", { fg: t.status.error, children: "Destroying deployment..." }),
6267
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, marginTop: 1, children: "Deleting server and cleaning up resources..." })
5617
6268
  ] });
5618
6269
  }
5619
6270
  if (viewState === "success") {
5620
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5621
- /* @__PURE__ */ jsxs8(
6271
+ return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6272
+ /* @__PURE__ */ jsxs9(
5622
6273
  "box",
5623
6274
  {
5624
6275
  flexDirection: "column",
@@ -5626,8 +6277,8 @@ function DestroyView({ context }) {
5626
6277
  borderColor: t.status.success,
5627
6278
  padding: 1,
5628
6279
  children: [
5629
- /* @__PURE__ */ jsx8("text", { fg: t.status.success, children: "Deployment Destroyed" }),
5630
- /* @__PURE__ */ jsxs8("text", { fg: t.fg.primary, marginTop: 1, children: [
6280
+ /* @__PURE__ */ jsx9("text", { fg: t.status.success, children: "Deployment Destroyed" }),
6281
+ /* @__PURE__ */ jsxs9("text", { fg: t.fg.primary, marginTop: 1, children: [
5631
6282
  'The deployment "',
5632
6283
  selectedDeployment.config.name,
5633
6284
  '" has been permanently deleted.'
@@ -5635,12 +6286,12 @@ function DestroyView({ context }) {
5635
6286
  ]
5636
6287
  }
5637
6288
  ),
5638
- /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
6289
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
5639
6290
  ] });
5640
6291
  }
5641
6292
  if (viewState === "error") {
5642
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5643
- /* @__PURE__ */ jsxs8(
6293
+ return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6294
+ /* @__PURE__ */ jsxs9(
5644
6295
  "box",
5645
6296
  {
5646
6297
  flexDirection: "column",
@@ -5648,30 +6299,30 @@ function DestroyView({ context }) {
5648
6299
  borderColor: t.status.error,
5649
6300
  padding: 1,
5650
6301
  children: [
5651
- /* @__PURE__ */ jsx8("text", { fg: t.status.error, children: "Destruction Failed" }),
5652
- /* @__PURE__ */ jsx8("text", { fg: t.fg.primary, marginTop: 1, children: error })
6302
+ /* @__PURE__ */ jsx9("text", { fg: t.status.error, children: "Destruction Failed" }),
6303
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, marginTop: 1, children: error })
5653
6304
  ]
5654
6305
  }
5655
6306
  ),
5656
- /* @__PURE__ */ jsx8("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to go back" })
6307
+ /* @__PURE__ */ jsx9("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to go back" })
5657
6308
  ] });
5658
6309
  }
5659
6310
  return null;
5660
6311
  }
5661
6312
 
5662
6313
  // src/components/HelpView.tsx
5663
- import { useKeyboard as useKeyboard8 } from "@opentui/react";
5664
- import { jsx as jsx9, jsxs as jsxs9 } from "@opentui/react/jsx-runtime";
6314
+ import { useKeyboard as useKeyboard10 } from "@opentui/react";
6315
+ import { jsx as jsx10, jsxs as jsxs10 } from "@opentui/react/jsx-runtime";
5665
6316
  function HelpView({ context }) {
5666
- useKeyboard8(() => {
6317
+ useKeyboard10(() => {
5667
6318
  context.navigateTo("home");
5668
6319
  });
5669
- return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", padding: 1, children: [
5670
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginBottom: 2, children: [
5671
- /* @__PURE__ */ jsx9("text", { fg: t.accent, children: "/help" }),
5672
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " - ClawControl Help" })
6320
+ return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6321
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginBottom: 2, children: [
6322
+ /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "/help" }),
6323
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " - ClawControl Help" })
5673
6324
  ] }),
5674
- /* @__PURE__ */ jsxs9(
6325
+ /* @__PURE__ */ jsxs10(
5675
6326
  "box",
5676
6327
  {
5677
6328
  flexDirection: "column",
@@ -5680,12 +6331,12 @@ function HelpView({ context }) {
5680
6331
  padding: 1,
5681
6332
  marginBottom: 1,
5682
6333
  children: [
5683
- /* @__PURE__ */ jsx9("text", { fg: t.accent, children: "What is ClawControl?" }),
5684
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, marginTop: 1, children: "ClawControl is a CLI tool that simplifies deploying OpenClaw instances to cloud providers. It handles all the complex setup including VPS provisioning, Node.js installation, OpenClaw configuration, and Tailscale VPN setup." })
6334
+ /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "What is ClawControl?" }),
6335
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, marginTop: 1, children: "ClawControl is a CLI tool that simplifies deploying OpenClaw instances to cloud providers. It handles all the complex setup including VPS provisioning, Node.js installation, OpenClaw configuration, and Tailscale VPN setup." })
5685
6336
  ]
5686
6337
  }
5687
6338
  ),
5688
- /* @__PURE__ */ jsxs9(
6339
+ /* @__PURE__ */ jsxs10(
5689
6340
  "box",
5690
6341
  {
5691
6342
  flexDirection: "column",
@@ -5694,48 +6345,48 @@ function HelpView({ context }) {
5694
6345
  padding: 1,
5695
6346
  marginBottom: 1,
5696
6347
  children: [
5697
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Available Commands" }),
5698
- /* @__PURE__ */ jsxs9("box", { flexDirection: "column", marginTop: 1, children: [
5699
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", children: [
5700
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/new" }),
5701
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Initialize a new deployment configuration" })
6348
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "Available Commands" }),
6349
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "column", marginTop: 1, children: [
6350
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6351
+ /* @__PURE__ */ jsx10("text", { fg: t.accent, width: 12, children: "/new" }),
6352
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "Initialize a new deployment configuration" })
5702
6353
  ] }),
5703
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Creates deployment config in ~/.clawcontrol/deployments/" }),
5704
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5705
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/deploy" }),
5706
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Deploy a configured instance to the cloud" })
6354
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " Creates deployment config in ~/.clawcontrol/deployments/" }),
6355
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginTop: 1, children: [
6356
+ /* @__PURE__ */ jsx10("text", { fg: t.accent, width: 12, children: "/deploy" }),
6357
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "Deploy a configured instance to the cloud" })
5707
6358
  ] }),
5708
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Provisions VPS, installs dependencies, configures OpenClaw" }),
5709
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5710
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/status" }),
5711
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "View status of all deployments" })
6359
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " Provisions VPS, installs dependencies, configures OpenClaw" }),
6360
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginTop: 1, children: [
6361
+ /* @__PURE__ */ jsx10("text", { fg: t.accent, width: 12, children: "/status" }),
6362
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "View status of all deployments" })
5712
6363
  ] }),
5713
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Shows deployment state, health checks, and connection info" }),
5714
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5715
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/ssh" }),
5716
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "SSH into a deployed instance" })
6364
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " Shows deployment state, health checks, and connection info" }),
6365
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginTop: 1, children: [
6366
+ /* @__PURE__ */ jsx10("text", { fg: t.accent, width: 12, children: "/ssh" }),
6367
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "SSH into a deployed instance" })
5717
6368
  ] }),
5718
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Opens interactive SSH session to your server" }),
5719
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5720
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/logs" }),
5721
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "View OpenClaw logs from a deployment" })
6369
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " Opens interactive SSH session to your server" }),
6370
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginTop: 1, children: [
6371
+ /* @__PURE__ */ jsx10("text", { fg: t.accent, width: 12, children: "/logs" }),
6372
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "View OpenClaw logs from a deployment" })
5722
6373
  ] }),
5723
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Streams logs from journalctl with auto-refresh option" }),
5724
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5725
- /* @__PURE__ */ jsx9("text", { fg: t.status.error, width: 12, children: "/destroy" }),
5726
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Permanently delete a deployment" })
6374
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " Streams logs from journalctl with auto-refresh option" }),
6375
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginTop: 1, children: [
6376
+ /* @__PURE__ */ jsx10("text", { fg: t.status.error, width: 12, children: "/destroy" }),
6377
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "Permanently delete a deployment" })
5727
6378
  ] }),
5728
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " Deletes VPS, SSH keys, and local configuration" }),
5729
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5730
- /* @__PURE__ */ jsx9("text", { fg: t.accent, width: 12, children: "/templates" }),
5731
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Manage deployment templates" })
6379
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " Deletes VPS, SSH keys, and local configuration" }),
6380
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginTop: 1, children: [
6381
+ /* @__PURE__ */ jsx10("text", { fg: t.accent, width: 12, children: "/templates" }),
6382
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "Manage deployment templates" })
5732
6383
  ] }),
5733
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: " View, fork, and use reusable deployment presets" })
6384
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " View, fork, and use reusable deployment presets" })
5734
6385
  ] })
5735
6386
  ]
5736
6387
  }
5737
6388
  ),
5738
- /* @__PURE__ */ jsxs9(
6389
+ /* @__PURE__ */ jsxs10(
5739
6390
  "box",
5740
6391
  {
5741
6392
  flexDirection: "column",
@@ -5744,17 +6395,17 @@ function HelpView({ context }) {
5744
6395
  padding: 1,
5745
6396
  marginBottom: 1,
5746
6397
  children: [
5747
- /* @__PURE__ */ jsx9("text", { fg: t.status.success, children: "Typical Workflow" }),
5748
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, marginTop: 1, children: "1. Run /templates to browse or create reusable presets" }),
5749
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "2. Run /new to create a deployment config (optionally from a template)" }),
5750
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "3. Run /deploy to deploy to the cloud" }),
5751
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "4. Authenticate Tailscale when prompted" }),
5752
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "5. Complete OpenClaw onboarding via SSH" }),
5753
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "6. Use /status and /logs to monitor" })
6398
+ /* @__PURE__ */ jsx10("text", { fg: t.status.success, children: "Typical Workflow" }),
6399
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, marginTop: 1, children: "1. Run /templates to browse or create reusable presets" }),
6400
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "2. Run /new to create a deployment config (optionally from a template)" }),
6401
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "3. Run /deploy to deploy to the cloud" }),
6402
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "4. Authenticate Tailscale when prompted" }),
6403
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "5. Complete OpenClaw onboarding via SSH" }),
6404
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "6. Use /status and /logs to monitor" })
5754
6405
  ]
5755
6406
  }
5756
6407
  ),
5757
- /* @__PURE__ */ jsxs9(
6408
+ /* @__PURE__ */ jsxs10(
5758
6409
  "box",
5759
6410
  {
5760
6411
  flexDirection: "column",
@@ -5763,36 +6414,36 @@ function HelpView({ context }) {
5763
6414
  padding: 1,
5764
6415
  marginBottom: 1,
5765
6416
  children: [
5766
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Supported Cloud Providers" }),
5767
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", marginTop: 1, children: [
5768
- /* @__PURE__ */ jsx9("text", { fg: t.status.success, children: "\u2713 " }),
5769
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Hetzner Cloud - ~$4.99/mo for CPX11 (US East)" })
6417
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "Supported Cloud Providers" }),
6418
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginTop: 1, children: [
6419
+ /* @__PURE__ */ jsx10("text", { fg: t.status.success, children: "\u2713 " }),
6420
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "Hetzner Cloud - ~$4.99/mo for CPX11 (US East)" })
5770
6421
  ] }),
5771
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", children: [
5772
- /* @__PURE__ */ jsx9("text", { fg: t.status.success, children: "\u2713 " }),
5773
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "DigitalOcean - Starting at $12/mo (NYC1)" })
6422
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6423
+ /* @__PURE__ */ jsx10("text", { fg: t.status.success, children: "\u2713 " }),
6424
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "DigitalOcean - Starting at $12/mo (NYC1)" })
5774
6425
  ] }),
5775
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", children: [
5776
- /* @__PURE__ */ jsx9("text", { fg: t.fg.muted, children: "\u25CB " }),
5777
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "Vultr - Coming soon" })
6426
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6427
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, children: "\u25CB " }),
6428
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: "Vultr - Coming soon" })
5778
6429
  ] })
5779
6430
  ]
5780
6431
  }
5781
6432
  ),
5782
- /* @__PURE__ */ jsxs9("box", { flexDirection: "column", marginBottom: 1, children: [
5783
- /* @__PURE__ */ jsx9("text", { fg: t.fg.primary, children: "Useful Links" }),
5784
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "\u2022 OpenClaw Docs: https://docs.openclaw.ai/" }),
5785
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "\u2022 Hetzner API: https://docs.hetzner.cloud/" }),
5786
- /* @__PURE__ */ jsx9("text", { fg: t.fg.secondary, children: "\u2022 Tailscale: https://tailscale.com/" })
6433
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "column", marginBottom: 1, children: [
6434
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "Useful Links" }),
6435
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: "\u2022 OpenClaw Docs: https://docs.openclaw.ai/" }),
6436
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: "\u2022 Hetzner API: https://docs.hetzner.cloud/" }),
6437
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: "\u2022 Tailscale: https://tailscale.com/" })
5787
6438
  ] }),
5788
- /* @__PURE__ */ jsx9("text", { fg: t.fg.muted, children: "Press any key to return to home" })
6439
+ /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, children: "Press any key to return to home" })
5789
6440
  ] });
5790
6441
  }
5791
6442
 
5792
6443
  // src/components/TemplatesView.tsx
5793
- import { useState as useState9, useRef as useRef4, useEffect as useEffect4 } from "react";
5794
- import { useKeyboard as useKeyboard9 } from "@opentui/react";
5795
- import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs10 } from "@opentui/react/jsx-runtime";
6444
+ import { useState as useState10, useRef as useRef6, useEffect as useEffect4 } from "react";
6445
+ import { useKeyboard as useKeyboard11 } from "@opentui/react";
6446
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs11 } from "@opentui/react/jsx-runtime";
5796
6447
  var DO_DROPLET_SIZES2 = [
5797
6448
  { slug: "s-1vcpu-2gb", label: "1 vCPU, 2GB RAM, 50GB SSD", price: "$12/mo" },
5798
6449
  { slug: "s-2vcpu-2gb", label: "2 vCPU, 2GB RAM, 60GB SSD", price: "$18/mo" },
@@ -5801,20 +6452,20 @@ var DO_DROPLET_SIZES2 = [
5801
6452
  { slug: "s-8vcpu-16gb", label: "8 vCPU, 16GB RAM, 320GB SSD", price: "$96/mo" }
5802
6453
  ];
5803
6454
  function TemplatesView({ context }) {
5804
- const [viewState, setViewState] = useState9("listing");
5805
- const [templates, setTemplates] = useState9([]);
5806
- const [selectedIndex, setSelectedIndex] = useState9(0);
5807
- const [selectedTemplate, setSelectedTemplate] = useState9(null);
5808
- const [error, setError] = useState9(null);
5809
- const [forkStep, setForkStep] = useState9("fork_name");
5810
- const [forkName, setForkName] = useState9("");
5811
- const [forkProvider, setForkProvider] = useState9("hetzner");
5812
- const [forkProviderIndex, setForkProviderIndex] = useState9(0);
5813
- const [forkDropletSizeIndex, setForkDropletSizeIndex] = useState9(0);
5814
- const [forkAiProvider, setForkAiProvider] = useState9("");
5815
- const [forkAiProviderIndex, setForkAiProviderIndex] = useState9(0);
5816
- const [forkModel, setForkModel] = useState9("");
5817
- const stateRef = useRef4({
6455
+ const [viewState, setViewState] = useState10("listing");
6456
+ const [templates, setTemplates] = useState10([]);
6457
+ const [selectedIndex, setSelectedIndex] = useState10(0);
6458
+ const [selectedTemplate, setSelectedTemplate] = useState10(null);
6459
+ const [error, setError] = useState10(null);
6460
+ const [forkStep, setForkStep] = useState10("fork_name");
6461
+ const [forkName, setForkName] = useState10("");
6462
+ const [forkProvider, setForkProvider] = useState10("hetzner");
6463
+ const [forkProviderIndex, setForkProviderIndex] = useState10(0);
6464
+ const [forkDropletSizeIndex, setForkDropletSizeIndex] = useState10(0);
6465
+ const [forkAiProvider, setForkAiProvider] = useState10("");
6466
+ const [forkAiProviderIndex, setForkAiProviderIndex] = useState10(0);
6467
+ const [forkModel, setForkModel] = useState10("");
6468
+ const stateRef = useRef6({
5818
6469
  viewState,
5819
6470
  selectedIndex,
5820
6471
  selectedTemplate,
@@ -5928,7 +6579,7 @@ function TemplatesView({ context }) {
5928
6579
  setViewState("viewing");
5929
6580
  }
5930
6581
  };
5931
- useKeyboard9((key) => {
6582
+ useKeyboard11((key) => {
5932
6583
  const s = stateRef.current;
5933
6584
  if (s.viewState === "listing") {
5934
6585
  if (key.name === "up") {
@@ -6020,37 +6671,37 @@ function TemplatesView({ context }) {
6020
6671
  }
6021
6672
  }
6022
6673
  });
6023
- const renderListing = () => /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6024
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginBottom: 1, children: "Select a template to view details. Use arrow keys and Enter." }),
6025
- /* @__PURE__ */ jsx10(
6674
+ const renderListing = () => /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6675
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, marginBottom: 1, children: "Select a template to view details. Use arrow keys and Enter." }),
6676
+ /* @__PURE__ */ jsx11(
6026
6677
  "box",
6027
6678
  {
6028
6679
  flexDirection: "column",
6029
6680
  borderStyle: "single",
6030
6681
  borderColor: t.border.default,
6031
6682
  padding: 1,
6032
- children: templates.length === 0 ? /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, children: "No templates found." }) : templates.map((tmpl, i) => {
6683
+ children: templates.length === 0 ? /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, children: "No templates found." }) : templates.map((tmpl, i) => {
6033
6684
  const isSelected = i === selectedIndex;
6034
6685
  const badge = tmpl.builtIn ? "[built-in]" : "[custom]";
6035
6686
  const badgeColor = tmpl.builtIn ? t.status.info : t.status.success;
6036
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6037
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6038
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: tmpl.name }),
6039
- /* @__PURE__ */ jsx10("text", { fg: badgeColor, children: " " + badge }),
6040
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, children: " \xB7 " + tmpl.channel })
6687
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6688
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6689
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: tmpl.name }),
6690
+ /* @__PURE__ */ jsx11("text", { fg: badgeColor, children: " " + badge }),
6691
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, children: " \xB7 " + tmpl.channel })
6041
6692
  ] }, tmpl.id);
6042
6693
  })
6043
6694
  }
6044
6695
  ),
6045
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, marginTop: 1, children: "Enter to view, Esc to go back" })
6696
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, marginTop: 1, children: "Enter to view, Esc to go back" })
6046
6697
  ] });
6047
6698
  const renderViewing = () => {
6048
6699
  if (!selectedTemplate) return null;
6049
6700
  const tmpl = selectedTemplate;
6050
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6051
- /* @__PURE__ */ jsx10("text", { fg: t.accent, marginBottom: 1, children: tmpl.name }),
6052
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginBottom: 1, children: tmpl.description }),
6053
- /* @__PURE__ */ jsxs10(
6701
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6702
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, marginBottom: 1, children: tmpl.name }),
6703
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, marginBottom: 1, children: tmpl.description }),
6704
+ /* @__PURE__ */ jsxs11(
6054
6705
  "box",
6055
6706
  {
6056
6707
  flexDirection: "column",
@@ -6059,89 +6710,89 @@ function TemplatesView({ context }) {
6059
6710
  padding: 1,
6060
6711
  marginBottom: 1,
6061
6712
  children: [
6062
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6063
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Type:" }),
6064
- /* @__PURE__ */ jsx10("text", { fg: tmpl.builtIn ? t.status.info : t.status.success, children: tmpl.builtIn ? "Built-in" : "Custom" })
6713
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6714
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Type:" }),
6715
+ /* @__PURE__ */ jsx11("text", { fg: tmpl.builtIn ? t.status.info : t.status.success, children: tmpl.builtIn ? "Built-in" : "Custom" })
6065
6716
  ] }),
6066
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6067
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Provider:" }),
6068
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: PROVIDER_NAMES[tmpl.provider] })
6717
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6718
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Provider:" }),
6719
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: PROVIDER_NAMES[tmpl.provider] })
6069
6720
  ] }),
6070
- tmpl.hetzner && /* @__PURE__ */ jsxs10(Fragment2, { children: [
6071
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6072
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Server Type:" }),
6073
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: tmpl.hetzner.serverType })
6721
+ tmpl.hetzner && /* @__PURE__ */ jsxs11(Fragment3, { children: [
6722
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6723
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Server Type:" }),
6724
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: tmpl.hetzner.serverType })
6074
6725
  ] }),
6075
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6076
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Location:" }),
6077
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: tmpl.hetzner.location })
6726
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6727
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Location:" }),
6728
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: tmpl.hetzner.location })
6078
6729
  ] })
6079
6730
  ] }),
6080
- tmpl.digitalocean && /* @__PURE__ */ jsxs10(Fragment2, { children: [
6081
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6082
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Droplet Size:" }),
6083
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: tmpl.digitalocean.size })
6731
+ tmpl.digitalocean && /* @__PURE__ */ jsxs11(Fragment3, { children: [
6732
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6733
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Droplet Size:" }),
6734
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: tmpl.digitalocean.size })
6084
6735
  ] }),
6085
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6086
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Region:" }),
6087
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: tmpl.digitalocean.region })
6736
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6737
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Region:" }),
6738
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: tmpl.digitalocean.region })
6088
6739
  ] })
6089
6740
  ] }),
6090
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6091
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "AI Provider:" }),
6092
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: tmpl.aiProvider })
6741
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6742
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "AI Provider:" }),
6743
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: tmpl.aiProvider })
6093
6744
  ] }),
6094
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6095
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Model:" }),
6096
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: tmpl.model })
6745
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6746
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Model:" }),
6747
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: tmpl.model })
6097
6748
  ] }),
6098
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6099
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Channel:" }),
6100
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: tmpl.channel })
6749
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6750
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Channel:" }),
6751
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: tmpl.channel })
6101
6752
  ] })
6102
6753
  ]
6103
6754
  }
6104
6755
  ),
6105
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6106
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "[F]" }),
6107
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "ork " }),
6108
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "[U]" }),
6109
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "se " }),
6110
- !tmpl.builtIn && /* @__PURE__ */ jsxs10(Fragment2, { children: [
6111
- /* @__PURE__ */ jsx10("text", { fg: t.status.error, children: "[D]" }),
6112
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "elete " })
6756
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6757
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "[F]" }),
6758
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: "ork " }),
6759
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "[U]" }),
6760
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: "se " }),
6761
+ !tmpl.builtIn && /* @__PURE__ */ jsxs11(Fragment3, { children: [
6762
+ /* @__PURE__ */ jsx11("text", { fg: t.status.error, children: "[D]" }),
6763
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: "elete " })
6113
6764
  ] }),
6114
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, children: "[Esc] Back" })
6765
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, children: "[Esc] Back" })
6115
6766
  ] }),
6116
- error && /* @__PURE__ */ jsx10("text", { fg: t.status.error, marginTop: 1, children: error })
6767
+ error && /* @__PURE__ */ jsx11("text", { fg: t.status.error, marginTop: 1, children: error })
6117
6768
  ] });
6118
6769
  };
6119
- const renderDeleteConfirm = () => /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6120
- /* @__PURE__ */ jsx10("text", { fg: t.status.error, children: "Delete Template" }),
6121
- /* @__PURE__ */ jsxs10("text", { fg: t.fg.primary, marginTop: 1, children: [
6770
+ const renderDeleteConfirm = () => /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6771
+ /* @__PURE__ */ jsx11("text", { fg: t.status.error, children: "Delete Template" }),
6772
+ /* @__PURE__ */ jsxs11("text", { fg: t.fg.primary, marginTop: 1, children: [
6122
6773
  'Are you sure you want to delete "',
6123
6774
  selectedTemplate?.name,
6124
6775
  '"?'
6125
6776
  ] }),
6126
- /* @__PURE__ */ jsx10("text", { fg: t.status.warning, marginTop: 1, children: "This action cannot be undone." }),
6127
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, marginTop: 2, children: "Press Y to confirm, N to cancel" })
6777
+ /* @__PURE__ */ jsx11("text", { fg: t.status.warning, marginTop: 1, children: "This action cannot be undone." }),
6778
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, marginTop: 2, children: "Press Y to confirm, N to cancel" })
6128
6779
  ] });
6129
- const renderForkComplete = () => /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6130
- /* @__PURE__ */ jsx10("text", { fg: t.status.success, children: "Template Created!" }),
6131
- /* @__PURE__ */ jsxs10("text", { fg: t.fg.primary, marginTop: 1, children: [
6780
+ const renderForkComplete = () => /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6781
+ /* @__PURE__ */ jsx11("text", { fg: t.status.success, children: "Template Created!" }),
6782
+ /* @__PURE__ */ jsxs11("text", { fg: t.fg.primary, marginTop: 1, children: [
6132
6783
  'Your template "',
6133
6784
  forkName,
6134
6785
  '" has been saved.'
6135
6786
  ] }),
6136
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to templates list" })
6787
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to templates list" })
6137
6788
  ] });
6138
6789
  const renderForking = () => {
6139
6790
  switch (forkStep) {
6140
6791
  case "fork_name":
6141
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6142
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - Name" }),
6143
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginTop: 1, children: "Enter a name for the new template:" }),
6144
- /* @__PURE__ */ jsx10(
6792
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6793
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "Fork Template - Name" }),
6794
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, marginTop: 1, children: "Enter a name for the new template:" }),
6795
+ /* @__PURE__ */ jsx11(
6145
6796
  "input",
6146
6797
  {
6147
6798
  value: forkName,
@@ -6164,14 +6815,14 @@ function TemplatesView({ context }) {
6164
6815
  }
6165
6816
  }
6166
6817
  ),
6167
- error && /* @__PURE__ */ jsx10("text", { fg: t.status.error, marginTop: 1, children: error }),
6168
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, marginTop: 2, children: "Press Enter to continue, Esc to go back" })
6818
+ error && /* @__PURE__ */ jsx11("text", { fg: t.status.error, marginTop: 1, children: error }),
6819
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, marginTop: 2, children: "Press Enter to continue, Esc to go back" })
6169
6820
  ] });
6170
6821
  case "fork_provider":
6171
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6172
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - Provider" }),
6173
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginTop: 1, children: "Select cloud provider:" }),
6174
- /* @__PURE__ */ jsx10(
6822
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6823
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "Fork Template - Provider" }),
6824
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, marginTop: 1, children: "Select cloud provider:" }),
6825
+ /* @__PURE__ */ jsx11(
6175
6826
  "box",
6176
6827
  {
6177
6828
  flexDirection: "column",
@@ -6181,20 +6832,20 @@ function TemplatesView({ context }) {
6181
6832
  padding: 1,
6182
6833
  children: SUPPORTED_PROVIDERS.map((p, i) => {
6183
6834
  const isSelected = i === forkProviderIndex;
6184
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6185
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6186
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: PROVIDER_NAMES[p] })
6835
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6836
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6837
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: PROVIDER_NAMES[p] })
6187
6838
  ] }, p);
6188
6839
  })
6189
6840
  }
6190
6841
  ),
6191
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, marginTop: 1, children: "Enter to select, Esc to go back" })
6842
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, marginTop: 1, children: "Enter to select, Esc to go back" })
6192
6843
  ] });
6193
6844
  case "fork_droplet_size":
6194
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6195
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - Droplet Size" }),
6196
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginTop: 1, children: "Select DigitalOcean droplet size:" }),
6197
- /* @__PURE__ */ jsx10(
6845
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6846
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "Fork Template - Droplet Size" }),
6847
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, marginTop: 1, children: "Select DigitalOcean droplet size:" }),
6848
+ /* @__PURE__ */ jsx11(
6198
6849
  "box",
6199
6850
  {
6200
6851
  flexDirection: "column",
@@ -6204,21 +6855,21 @@ function TemplatesView({ context }) {
6204
6855
  padding: 1,
6205
6856
  children: DO_DROPLET_SIZES2.map((size, i) => {
6206
6857
  const isSelected = i === forkDropletSizeIndex;
6207
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6208
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6209
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: size.slug }),
6210
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.fg.primary : t.fg.secondary, children: " - " + size.label + " - " + size.price })
6858
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6859
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6860
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: size.slug }),
6861
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.fg.primary : t.fg.secondary, children: " - " + size.label + " - " + size.price })
6211
6862
  ] }, size.slug);
6212
6863
  })
6213
6864
  }
6214
6865
  ),
6215
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, marginTop: 1, children: "Enter to select, Esc to go back" })
6866
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, marginTop: 1, children: "Enter to select, Esc to go back" })
6216
6867
  ] });
6217
6868
  case "fork_ai_provider":
6218
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6219
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - AI Provider" }),
6220
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginTop: 1, children: "Select AI provider:" }),
6221
- /* @__PURE__ */ jsx10(
6869
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6870
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "Fork Template - AI Provider" }),
6871
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, marginTop: 1, children: "Select AI provider:" }),
6872
+ /* @__PURE__ */ jsx11(
6222
6873
  "box",
6223
6874
  {
6224
6875
  flexDirection: "column",
@@ -6228,21 +6879,21 @@ function TemplatesView({ context }) {
6228
6879
  padding: 1,
6229
6880
  children: AI_PROVIDERS.map((p, i) => {
6230
6881
  const isSelected = i === forkAiProviderIndex;
6231
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6232
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6233
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: p.label }),
6234
- /* @__PURE__ */ jsx10("text", { fg: isSelected ? t.fg.primary : t.fg.secondary, children: " - " + p.description })
6882
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "row", backgroundColor: isSelected ? t.selection.bg : void 0, children: [
6883
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.selection.indicator : t.fg.primary, children: isSelected ? "> " : " " }),
6884
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.selection.fg : t.fg.primary, children: p.label }),
6885
+ /* @__PURE__ */ jsx11("text", { fg: isSelected ? t.fg.primary : t.fg.secondary, children: " - " + p.description })
6235
6886
  ] }, p.name);
6236
6887
  })
6237
6888
  }
6238
6889
  ),
6239
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, marginTop: 1, children: "Enter to select, Esc to go back" })
6890
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, marginTop: 1, children: "Enter to select, Esc to go back" })
6240
6891
  ] });
6241
6892
  case "fork_model":
6242
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6243
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - Model" }),
6244
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, marginTop: 1, children: "Enter the model identifier:" }),
6245
- /* @__PURE__ */ jsx10(
6893
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6894
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "Fork Template - Model" }),
6895
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, marginTop: 1, children: "Enter the model identifier:" }),
6896
+ /* @__PURE__ */ jsx11(
6246
6897
  "input",
6247
6898
  {
6248
6899
  value: forkModel,
@@ -6265,13 +6916,13 @@ function TemplatesView({ context }) {
6265
6916
  }
6266
6917
  }
6267
6918
  ),
6268
- error && /* @__PURE__ */ jsx10("text", { fg: t.status.error, marginTop: 1, children: error }),
6269
- /* @__PURE__ */ jsx10("text", { fg: t.fg.muted, marginTop: 2, children: "Press Enter to continue, Esc to go back" })
6919
+ error && /* @__PURE__ */ jsx11("text", { fg: t.status.error, marginTop: 1, children: error }),
6920
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.muted, marginTop: 2, children: "Press Enter to continue, Esc to go back" })
6270
6921
  ] });
6271
6922
  case "fork_confirm":
6272
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
6273
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "Fork Template - Confirm" }),
6274
- /* @__PURE__ */ jsxs10(
6923
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", children: [
6924
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "Fork Template - Confirm" }),
6925
+ /* @__PURE__ */ jsxs11(
6275
6926
  "box",
6276
6927
  {
6277
6928
  flexDirection: "column",
@@ -6280,35 +6931,35 @@ function TemplatesView({ context }) {
6280
6931
  padding: 1,
6281
6932
  marginTop: 1,
6282
6933
  children: [
6283
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6284
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Name:" }),
6285
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: forkName })
6934
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6935
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Name:" }),
6936
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: forkName })
6286
6937
  ] }),
6287
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6288
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Provider:" }),
6289
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: PROVIDER_NAMES[forkProvider] })
6938
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6939
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Provider:" }),
6940
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: PROVIDER_NAMES[forkProvider] })
6290
6941
  ] }),
6291
- forkProvider === "digitalocean" && /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6292
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Droplet Size:" }),
6293
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: DO_DROPLET_SIZES2[forkDropletSizeIndex].slug })
6942
+ forkProvider === "digitalocean" && /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6943
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Droplet Size:" }),
6944
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: DO_DROPLET_SIZES2[forkDropletSizeIndex].slug })
6294
6945
  ] }),
6295
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6296
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "AI Provider:" }),
6297
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: forkAiProvider })
6946
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6947
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "AI Provider:" }),
6948
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: forkAiProvider })
6298
6949
  ] }),
6299
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6300
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Model:" }),
6301
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: forkModel })
6950
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6951
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Model:" }),
6952
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: forkModel })
6302
6953
  ] }),
6303
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
6304
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, width: 20, children: "Channel:" }),
6305
- /* @__PURE__ */ jsx10("text", { fg: t.fg.primary, children: "telegram" })
6954
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", children: [
6955
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, width: 20, children: "Channel:" }),
6956
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.primary, children: "telegram" })
6306
6957
  ] })
6307
6958
  ]
6308
6959
  }
6309
6960
  ),
6310
- error && /* @__PURE__ */ jsx10("text", { fg: t.status.error, marginTop: 1, children: error }),
6311
- /* @__PURE__ */ jsx10("text", { fg: t.status.warning, marginTop: 2, children: "Press Y to confirm, N to go back" })
6961
+ error && /* @__PURE__ */ jsx11("text", { fg: t.status.error, marginTop: 1, children: error }),
6962
+ /* @__PURE__ */ jsx11("text", { fg: t.status.warning, marginTop: 2, children: "Press Y to confirm, N to go back" })
6312
6963
  ] });
6313
6964
  }
6314
6965
  };
@@ -6326,34 +6977,464 @@ function TemplatesView({ context }) {
6326
6977
  return renderForking();
6327
6978
  }
6328
6979
  };
6329
- return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6330
- /* @__PURE__ */ jsxs10("box", { flexDirection: "row", marginBottom: 2, children: [
6331
- /* @__PURE__ */ jsx10("text", { fg: t.accent, children: "/templates" }),
6332
- /* @__PURE__ */ jsx10("text", { fg: t.fg.secondary, children: " - Manage deployment templates" })
6980
+ return /* @__PURE__ */ jsxs11("box", { flexDirection: "column", width: "100%", padding: 1, children: [
6981
+ /* @__PURE__ */ jsxs11("box", { flexDirection: "row", marginBottom: 2, children: [
6982
+ /* @__PURE__ */ jsx11("text", { fg: t.accent, children: "/templates" }),
6983
+ /* @__PURE__ */ jsx11("text", { fg: t.fg.secondary, children: " - Manage deployment templates" })
6333
6984
  ] }),
6334
6985
  renderContent()
6335
6986
  ] });
6336
6987
  }
6337
6988
 
6338
- // src/App.tsx
6339
- import { jsx as jsx11 } from "@opentui/react/jsx-runtime";
6340
- function App() {
6989
+ // src/components/DashboardView.tsx
6990
+ import { useState as useState11, useCallback as useCallback3 } from "react";
6991
+ import { useKeyboard as useKeyboard12, useRenderer } from "@opentui/react";
6992
+ import { spawnSync as spawnSync2 } from "child_process";
6993
+ import { platform as platform2 } from "os";
6994
+
6995
+ // src/services/tunnel.ts
6996
+ import { spawn as spawn2 } from "child_process";
6997
+ import { createServer } from "net";
6998
+ var activeTunnels = /* @__PURE__ */ new Map();
6999
+ function isPortAvailable(port) {
7000
+ return new Promise((resolve) => {
7001
+ const server = createServer();
7002
+ server.once("error", () => resolve(false));
7003
+ server.once("listening", () => {
7004
+ server.close(() => resolve(true));
7005
+ });
7006
+ server.listen(port, "127.0.0.1");
7007
+ });
7008
+ }
7009
+ async function findAvailablePort(startPort) {
7010
+ for (let port = startPort; port < startPort + 100; port++) {
7011
+ if (await isPortAvailable(port)) return port;
7012
+ }
7013
+ throw new Error(`No available port found in range ${startPort}\u2013${startPort + 99}`);
7014
+ }
7015
+ async function startTunnel(deploymentName, serverIp, remotePort = 18789) {
7016
+ const existing = getTunnel(deploymentName);
7017
+ if (existing) return existing;
7018
+ const sshKeyPath = getSSHKeyPath(deploymentName);
7019
+ const localPort = await findAvailablePort(remotePort);
7020
+ const proc = spawn2("ssh", [
7021
+ "-N",
7022
+ "-L",
7023
+ `${localPort}:127.0.0.1:${remotePort}`,
7024
+ "-i",
7025
+ sshKeyPath,
7026
+ "-o",
7027
+ "StrictHostKeyChecking=no",
7028
+ "-o",
7029
+ "UserKnownHostsFile=/dev/null",
7030
+ "-o",
7031
+ "ServerAliveInterval=15",
7032
+ "-o",
7033
+ "ServerAliveCountMax=3",
7034
+ "-o",
7035
+ "ExitOnForwardFailure=yes",
7036
+ `root@${serverIp}`
7037
+ ], {
7038
+ stdio: ["ignore", "pipe", "pipe"],
7039
+ detached: false
7040
+ });
7041
+ const tunnel = {
7042
+ deploymentName,
7043
+ serverIp,
7044
+ localPort,
7045
+ remotePort,
7046
+ process: proc,
7047
+ dashboardUrl: null,
7048
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
7049
+ };
7050
+ activeTunnels.set(deploymentName, tunnel);
7051
+ proc.on("exit", () => {
7052
+ activeTunnels.delete(deploymentName);
7053
+ });
7054
+ await new Promise((resolve) => setTimeout(resolve, 2500));
7055
+ if (proc.exitCode !== null || proc.killed) {
7056
+ activeTunnels.delete(deploymentName);
7057
+ throw new Error("SSH tunnel failed to establish. Check that the server is reachable.");
7058
+ }
7059
+ return tunnel;
7060
+ }
7061
+ function stopTunnel(deploymentName) {
7062
+ const tunnel = activeTunnels.get(deploymentName);
7063
+ if (tunnel) {
7064
+ tunnel.process.kill();
7065
+ activeTunnels.delete(deploymentName);
7066
+ }
7067
+ }
7068
+ function getTunnel(deploymentName) {
7069
+ const tunnel = activeTunnels.get(deploymentName);
7070
+ if (tunnel && tunnel.process.exitCode === null && !tunnel.process.killed) {
7071
+ return tunnel;
7072
+ }
7073
+ if (tunnel) activeTunnels.delete(deploymentName);
7074
+ return null;
7075
+ }
7076
+ function getActiveTunnels() {
7077
+ for (const [name, tunnel] of activeTunnels) {
7078
+ if (tunnel.process.exitCode !== null || tunnel.process.killed) {
7079
+ activeTunnels.delete(name);
7080
+ }
7081
+ }
7082
+ return Array.from(activeTunnels.values());
7083
+ }
7084
+
7085
+ // src/components/DashboardView.tsx
7086
+ import { jsx as jsx12, jsxs as jsxs12 } from "@opentui/react/jsx-runtime";
7087
+ function openInBrowser(url) {
7088
+ try {
7089
+ if (platform2() === "darwin") {
7090
+ spawnSync2("open", [url]);
7091
+ } else {
7092
+ spawnSync2("xdg-open", [url]);
7093
+ }
7094
+ return true;
7095
+ } catch {
7096
+ return false;
7097
+ }
7098
+ }
7099
+ function DashboardView({ context }) {
6341
7100
  const renderer = useRenderer();
6342
- const [currentView, setCurrentView] = useState10("home");
6343
- const [selectedDeployment, setSelectedDeployment] = useState10(null);
6344
- const [deployments, setDeployments] = useState10(() => {
7101
+ const [viewState, setViewState] = useState11("selecting");
7102
+ const [selectedIndex, setSelectedIndex] = useState11(0);
7103
+ const [error, setError] = useState11(null);
7104
+ const [activeTunnel, setActiveTunnel] = useState11(null);
7105
+ const [dashboardUrl, setDashboardUrl] = useState11(null);
7106
+ const [connectMessage, setConnectMessage] = useState11("");
7107
+ const [copied, setCopied] = useState11(false);
7108
+ const deployedDeployments = context.deployments.filter(
7109
+ (d) => d.state.status === "deployed" && d.state.serverIp
7110
+ );
7111
+ const connectToDashboard = useCallback3(async (deployment) => {
7112
+ setViewState("connecting");
7113
+ setError(null);
7114
+ setCopied(false);
7115
+ setConnectMessage("Establishing SSH tunnel...");
7116
+ try {
7117
+ const serverIp = deployment.state.serverIp;
7118
+ const remotePort = deployment.config.openclawConfig?.gateway?.port || 18789;
7119
+ const tunnel = await startTunnel(
7120
+ deployment.config.name,
7121
+ serverIp,
7122
+ remotePort
7123
+ );
7124
+ setActiveTunnel(tunnel);
7125
+ if (tunnel.dashboardUrl) {
7126
+ setDashboardUrl(tunnel.dashboardUrl);
7127
+ setViewState("active");
7128
+ return;
7129
+ }
7130
+ setConnectMessage("Retrieving dashboard URL...");
7131
+ const localToken = deployment.state.gatewayToken;
7132
+ if (localToken) {
7133
+ const localUrl = `http://127.0.0.1:${tunnel.localPort}/?token=${encodeURIComponent(localToken)}`;
7134
+ tunnel.dashboardUrl = localUrl;
7135
+ setDashboardUrl(localUrl);
7136
+ setViewState("active");
7137
+ return;
7138
+ }
7139
+ const ssh = await connectToDeployment(
7140
+ deployment.config.name,
7141
+ serverIp
7142
+ );
7143
+ try {
7144
+ const info = await getDashboardUrl(ssh);
7145
+ const parsed = new URL(info.url);
7146
+ parsed.hostname = "127.0.0.1";
7147
+ parsed.port = String(tunnel.localPort);
7148
+ const localUrl = parsed.toString();
7149
+ tunnel.dashboardUrl = localUrl;
7150
+ setDashboardUrl(localUrl);
7151
+ setViewState("active");
7152
+ } finally {
7153
+ ssh.disconnect();
7154
+ }
7155
+ } catch (err) {
7156
+ setError(err instanceof Error ? err.message : String(err));
7157
+ setViewState("error");
7158
+ }
7159
+ }, []);
7160
+ useKeyboard12((key) => {
7161
+ if (viewState === "selecting") {
7162
+ if (deployedDeployments.length === 0) {
7163
+ if (key.name === "escape" || key.name === "return") {
7164
+ context.navigateTo("home");
7165
+ }
7166
+ return;
7167
+ }
7168
+ if (key.name === "up" && selectedIndex > 0) {
7169
+ setSelectedIndex(selectedIndex - 1);
7170
+ } else if (key.name === "down" && selectedIndex < deployedDeployments.length - 1) {
7171
+ setSelectedIndex(selectedIndex + 1);
7172
+ } else if (key.name === "return") {
7173
+ const deployment = deployedDeployments[selectedIndex];
7174
+ const existing = getTunnel(deployment.config.name);
7175
+ if (existing && existing.dashboardUrl) {
7176
+ setActiveTunnel(existing);
7177
+ setDashboardUrl(existing.dashboardUrl);
7178
+ setViewState("active");
7179
+ return;
7180
+ }
7181
+ connectToDashboard(deployment);
7182
+ } else if (key.name === "escape") {
7183
+ context.navigateTo("home");
7184
+ }
7185
+ } else if (viewState === "active") {
7186
+ if (key.sequence === "o" || key.sequence === "O") {
7187
+ if (dashboardUrl) openInBrowser(dashboardUrl);
7188
+ } else if (key.sequence === "c" || key.sequence === "C") {
7189
+ if (dashboardUrl) {
7190
+ renderer.copyToClipboardOSC52(dashboardUrl);
7191
+ setCopied(true);
7192
+ setTimeout(() => setCopied(false), 3e3);
7193
+ }
7194
+ } else if (key.sequence === "d" || key.sequence === "D") {
7195
+ if (activeTunnel) {
7196
+ stopTunnel(activeTunnel.deploymentName);
7197
+ setActiveTunnel(null);
7198
+ setDashboardUrl(null);
7199
+ setViewState("selecting");
7200
+ }
7201
+ } else if (key.name === "escape") {
7202
+ setViewState("selecting");
7203
+ }
7204
+ } else if (viewState === "error") {
7205
+ if (key.name === "escape" || key.name === "return") {
7206
+ setViewState("selecting");
7207
+ }
7208
+ }
7209
+ });
7210
+ if (deployedDeployments.length === 0) {
7211
+ return /* @__PURE__ */ jsxs12("box", { flexDirection: "column", width: "100%", padding: 1, children: [
7212
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", marginBottom: 2, children: [
7213
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, children: "/dashboard" }),
7214
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.secondary, children: " - Open OpenClaw dashboard" })
7215
+ ] }),
7216
+ /* @__PURE__ */ jsxs12(
7217
+ "box",
7218
+ {
7219
+ flexDirection: "column",
7220
+ borderStyle: "single",
7221
+ borderColor: t.border.default,
7222
+ padding: 1,
7223
+ children: [
7224
+ /* @__PURE__ */ jsx12("text", { fg: t.status.warning, children: "No deployed instances found!" }),
7225
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.secondary, marginTop: 1, children: "Deploy an instance first with /deploy" })
7226
+ ]
7227
+ }
7228
+ ),
7229
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.muted, marginTop: 2, children: "Press any key to return to home" })
7230
+ ] });
7231
+ }
7232
+ if (viewState === "selecting") {
7233
+ const tunnels = getActiveTunnels();
7234
+ return /* @__PURE__ */ jsxs12("box", { flexDirection: "column", width: "100%", padding: 1, children: [
7235
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", marginBottom: 2, children: [
7236
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, children: "/dashboard" }),
7237
+ /* @__PURE__ */ jsxs12("text", { fg: t.fg.secondary, children: [
7238
+ " ",
7239
+ "- Select a deployment to open its dashboard"
7240
+ ] })
7241
+ ] }),
7242
+ tunnels.length > 0 && /* @__PURE__ */ jsxs12(
7243
+ "box",
7244
+ {
7245
+ flexDirection: "column",
7246
+ borderStyle: "single",
7247
+ borderColor: t.status.success,
7248
+ padding: 1,
7249
+ marginBottom: 1,
7250
+ children: [
7251
+ /* @__PURE__ */ jsxs12("text", { fg: t.status.success, children: [
7252
+ tunnels.length,
7253
+ " active tunnel",
7254
+ tunnels.length > 1 ? "s" : ""
7255
+ ] }),
7256
+ tunnels.map((tun) => /* @__PURE__ */ jsxs12("text", { fg: t.fg.secondary, children: [
7257
+ tun.deploymentName,
7258
+ ": 127.0.0.1:",
7259
+ tun.localPort,
7260
+ " \u2192",
7261
+ " ",
7262
+ tun.serverIp,
7263
+ ":",
7264
+ tun.remotePort
7265
+ ] }, tun.deploymentName))
7266
+ ]
7267
+ }
7268
+ ),
7269
+ /* @__PURE__ */ jsx12(
7270
+ "box",
7271
+ {
7272
+ flexDirection: "column",
7273
+ borderStyle: "single",
7274
+ borderColor: t.border.default,
7275
+ padding: 1,
7276
+ marginBottom: 1,
7277
+ children: deployedDeployments.map((deployment, index) => {
7278
+ const isSelected = index === selectedIndex;
7279
+ const hasTunnel = getTunnel(deployment.config.name) !== null;
7280
+ return /* @__PURE__ */ jsxs12(
7281
+ "box",
7282
+ {
7283
+ flexDirection: "row",
7284
+ backgroundColor: isSelected ? t.selection.bg : void 0,
7285
+ children: [
7286
+ /* @__PURE__ */ jsx12("text", { fg: isSelected ? t.selection.fg : t.fg.secondary, children: isSelected ? "> " : " " }),
7287
+ /* @__PURE__ */ jsx12(
7288
+ "text",
7289
+ {
7290
+ fg: isSelected ? t.selection.fg : t.fg.primary,
7291
+ width: 25,
7292
+ children: deployment.config.name
7293
+ }
7294
+ ),
7295
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, width: 18, children: deployment.state.serverIp }),
7296
+ hasTunnel && /* @__PURE__ */ jsx12("text", { fg: t.status.success, children: "[tunnel active]" })
7297
+ ]
7298
+ },
7299
+ deployment.config.name
7300
+ );
7301
+ })
7302
+ }
7303
+ ),
7304
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.muted, children: "Arrow keys to select | Enter to connect | Esc to go back" })
7305
+ ] });
7306
+ }
7307
+ if (viewState === "connecting") {
7308
+ return /* @__PURE__ */ jsxs12("box", { flexDirection: "column", width: "100%", padding: 1, children: [
7309
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", marginBottom: 2, children: [
7310
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, children: "/dashboard" }),
7311
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.secondary, children: " - Connecting..." })
7312
+ ] }),
7313
+ /* @__PURE__ */ jsxs12(
7314
+ "box",
7315
+ {
7316
+ flexDirection: "column",
7317
+ borderStyle: "single",
7318
+ borderColor: t.border.focus,
7319
+ padding: 1,
7320
+ children: [
7321
+ /* @__PURE__ */ jsx12("text", { fg: t.status.info, children: connectMessage }),
7322
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.muted, marginTop: 1, children: "This may take a few seconds..." })
7323
+ ]
7324
+ }
7325
+ )
7326
+ ] });
7327
+ }
7328
+ if (viewState === "active" && activeTunnel && dashboardUrl) {
7329
+ return /* @__PURE__ */ jsxs12("box", { flexDirection: "column", width: "100%", padding: 1, children: [
7330
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", marginBottom: 2, children: [
7331
+ /* @__PURE__ */ jsx12("text", { fg: t.status.success, children: "/dashboard" }),
7332
+ /* @__PURE__ */ jsxs12("text", { fg: t.fg.secondary, children: [
7333
+ " ",
7334
+ "- ",
7335
+ activeTunnel.deploymentName
7336
+ ] })
7337
+ ] }),
7338
+ /* @__PURE__ */ jsxs12(
7339
+ "box",
7340
+ {
7341
+ flexDirection: "column",
7342
+ borderStyle: "double",
7343
+ borderColor: t.status.success,
7344
+ padding: 1,
7345
+ marginBottom: 1,
7346
+ children: [
7347
+ /* @__PURE__ */ jsx12("text", { fg: t.status.success, children: "Dashboard Ready" }),
7348
+ /* @__PURE__ */ jsxs12("text", { fg: t.fg.primary, marginTop: 1, children: [
7349
+ "Tunnel: 127.0.0.1:",
7350
+ activeTunnel.localPort,
7351
+ " \u2192",
7352
+ " ",
7353
+ activeTunnel.serverIp,
7354
+ ":",
7355
+ activeTunnel.remotePort
7356
+ ] })
7357
+ ]
7358
+ }
7359
+ ),
7360
+ /* @__PURE__ */ jsxs12(
7361
+ "box",
7362
+ {
7363
+ flexDirection: "column",
7364
+ borderStyle: "single",
7365
+ borderColor: t.accent,
7366
+ padding: 1,
7367
+ marginBottom: 1,
7368
+ children: [
7369
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.secondary, children: "Dashboard URL:" }),
7370
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, marginTop: 1, children: dashboardUrl })
7371
+ ]
7372
+ }
7373
+ ),
7374
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "column", marginBottom: 1, children: [
7375
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", children: [
7376
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, width: 4, children: "O" }),
7377
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.primary, children: "Open in browser" })
7378
+ ] }),
7379
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", children: [
7380
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, width: 4, children: "C" }),
7381
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.primary, children: "Copy URL to clipboard" })
7382
+ ] }),
7383
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", children: [
7384
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, width: 4, children: "D" }),
7385
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.primary, children: "Disconnect tunnel" })
7386
+ ] }),
7387
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", children: [
7388
+ /* @__PURE__ */ jsx12("text", { fg: t.accent, width: 4, children: "Esc" }),
7389
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.primary, children: "Back to selection (tunnel stays active)" })
7390
+ ] })
7391
+ ] }),
7392
+ copied && /* @__PURE__ */ jsx12("text", { fg: t.status.success, children: "URL copied to clipboard!" })
7393
+ ] });
7394
+ }
7395
+ if (viewState === "error") {
7396
+ return /* @__PURE__ */ jsxs12("box", { flexDirection: "column", width: "100%", padding: 1, children: [
7397
+ /* @__PURE__ */ jsxs12("box", { flexDirection: "row", marginBottom: 2, children: [
7398
+ /* @__PURE__ */ jsx12("text", { fg: t.status.error, children: "/dashboard" }),
7399
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.secondary, children: " - Connection failed" })
7400
+ ] }),
7401
+ /* @__PURE__ */ jsx12(
7402
+ "box",
7403
+ {
7404
+ flexDirection: "column",
7405
+ borderStyle: "single",
7406
+ borderColor: t.status.error,
7407
+ padding: 1,
7408
+ marginBottom: 1,
7409
+ children: /* @__PURE__ */ jsx12("text", { fg: t.status.error, children: error })
7410
+ }
7411
+ ),
7412
+ /* @__PURE__ */ jsx12("text", { fg: t.fg.muted, children: "Press Enter or Esc to go back" })
7413
+ ] });
7414
+ }
7415
+ return null;
7416
+ }
7417
+
7418
+ // src/App.tsx
7419
+ import { jsx as jsx13, jsxs as jsxs13 } from "@opentui/react/jsx-runtime";
7420
+ function App({ lacksTrueColor: lacksTrueColor2 }) {
7421
+ const renderer = useRenderer2();
7422
+ const [currentView, setCurrentView] = useState12("home");
7423
+ const [selectedDeployment, setSelectedDeployment] = useState12(null);
7424
+ const [deployments, setDeployments] = useState12(() => {
6345
7425
  try {
6346
7426
  return getAllDeployments();
6347
7427
  } catch {
6348
7428
  return [];
6349
7429
  }
6350
7430
  });
6351
- const [selectedTemplate, setSelectedTemplate] = useState10(null);
6352
- const wasDraggingRef = useRef5(false);
6353
- const handleMouseDrag = useCallback3(() => {
7431
+ const [selectedTemplate, setSelectedTemplate] = useState12(null);
7432
+ const [editingDeployment, setEditingDeployment] = useState12(null);
7433
+ const wasDraggingRef = useRef7(false);
7434
+ const handleMouseDrag = useCallback4(() => {
6354
7435
  wasDraggingRef.current = true;
6355
7436
  }, []);
6356
- const handleMouseUp = useCallback3(() => {
7437
+ const handleMouseUp = useCallback4(() => {
6357
7438
  if (!wasDraggingRef.current) return;
6358
7439
  wasDraggingRef.current = false;
6359
7440
  const selection = renderer.getSelection();
@@ -6364,14 +7445,14 @@ function App() {
6364
7445
  }
6365
7446
  }
6366
7447
  }, [renderer]);
6367
- const refreshDeployments = useCallback3(() => {
7448
+ const refreshDeployments = useCallback4(() => {
6368
7449
  try {
6369
7450
  setDeployments(getAllDeployments());
6370
7451
  } catch {
6371
7452
  setDeployments([]);
6372
7453
  }
6373
7454
  }, []);
6374
- const navigateTo = useCallback3((view, deployment) => {
7455
+ const navigateTo = useCallback4((view, deployment) => {
6375
7456
  if (deployment !== void 0) {
6376
7457
  setSelectedDeployment(deployment);
6377
7458
  }
@@ -6384,35 +7465,41 @@ function App() {
6384
7465
  deployments,
6385
7466
  refreshDeployments,
6386
7467
  selectedTemplate,
6387
- setSelectedTemplate
7468
+ setSelectedTemplate,
7469
+ editingDeployment,
7470
+ setEditingDeployment
6388
7471
  };
6389
7472
  const renderView = () => {
6390
7473
  switch (currentView) {
6391
7474
  case "home":
6392
- return /* @__PURE__ */ jsx11(Home, { context });
7475
+ return /* @__PURE__ */ jsx13(Home, { context });
6393
7476
  case "new":
6394
- return /* @__PURE__ */ jsx11(NewDeployment, { context });
7477
+ return /* @__PURE__ */ jsx13(NewDeployment, { context });
7478
+ case "list":
7479
+ return /* @__PURE__ */ jsx13(ListView, { context });
6395
7480
  case "deploy":
6396
- return /* @__PURE__ */ jsx11(DeployView, { context });
7481
+ return /* @__PURE__ */ jsx13(DeployView, { context });
6397
7482
  case "deploying":
6398
- return /* @__PURE__ */ jsx11(DeployingView, { context });
7483
+ return /* @__PURE__ */ jsx13(DeployingView, { context });
6399
7484
  case "status":
6400
- return /* @__PURE__ */ jsx11(StatusView, { context });
7485
+ return /* @__PURE__ */ jsx13(StatusView, { context });
6401
7486
  case "ssh":
6402
- return /* @__PURE__ */ jsx11(SSHView, { context });
7487
+ return /* @__PURE__ */ jsx13(SSHView, { context });
6403
7488
  case "logs":
6404
- return /* @__PURE__ */ jsx11(LogsView, { context });
7489
+ return /* @__PURE__ */ jsx13(LogsView, { context });
7490
+ case "dashboard":
7491
+ return /* @__PURE__ */ jsx13(DashboardView, { context });
6405
7492
  case "destroy":
6406
- return /* @__PURE__ */ jsx11(DestroyView, { context });
7493
+ return /* @__PURE__ */ jsx13(DestroyView, { context });
6407
7494
  case "help":
6408
- return /* @__PURE__ */ jsx11(HelpView, { context });
7495
+ return /* @__PURE__ */ jsx13(HelpView, { context });
6409
7496
  case "templates":
6410
- return /* @__PURE__ */ jsx11(TemplatesView, { context });
7497
+ return /* @__PURE__ */ jsx13(TemplatesView, { context });
6411
7498
  default:
6412
- return /* @__PURE__ */ jsx11(Home, { context });
7499
+ return /* @__PURE__ */ jsx13(Home, { context });
6413
7500
  }
6414
7501
  };
6415
- return /* @__PURE__ */ jsx11(
7502
+ return /* @__PURE__ */ jsxs13(
6416
7503
  "scrollbox",
6417
7504
  {
6418
7505
  width: "100%",
@@ -6433,13 +7520,29 @@ function App() {
6433
7520
  verticalScrollbarOptions: {
6434
7521
  showArrows: false
6435
7522
  },
6436
- children: renderView()
7523
+ children: [
7524
+ lacksTrueColor2 && /* @__PURE__ */ jsx13(
7525
+ "box",
7526
+ {
7527
+ width: "100%",
7528
+ style: {
7529
+ paddingLeft: 1,
7530
+ paddingRight: 1,
7531
+ paddingTop: 0,
7532
+ paddingBottom: 0,
7533
+ backgroundColor: t.status.warning
7534
+ },
7535
+ children: /* @__PURE__ */ jsx13("text", { fg: "#000000", children: "\u26A0 Your terminal does not support true color. Colors may look wrong. For full color support, use Ghostty, iTerm2, Kitty, or WezTerm \u2014 or upgrade to macOS 26+ for Terminal.app true color support." })
7536
+ }
7537
+ ),
7538
+ renderView()
7539
+ ]
6437
7540
  }
6438
7541
  );
6439
7542
  }
6440
7543
 
6441
7544
  // src/index.tsx
6442
- import { jsx as jsx12 } from "@opentui/react/jsx-runtime";
7545
+ import { jsx as jsx14 } from "@opentui/react/jsx-runtime";
6443
7546
  var args = process.argv.slice(2);
6444
7547
  if (args.includes("--version") || args.includes("-v")) {
6445
7548
  const require2 = createRequire(import.meta.url);
@@ -6468,11 +7571,14 @@ Documentation & source:
6468
7571
  `);
6469
7572
  process.exit(0);
6470
7573
  }
7574
+ var isAppleTerminal = process.env.TERM_PROGRAM === "Apple_Terminal";
7575
+ var darwinMajor = process.platform === "darwin" ? parseInt(release(), 10) : Infinity;
7576
+ var lacksTrueColor = isAppleTerminal && darwinMajor < 25;
6471
7577
  async function main() {
6472
7578
  const renderer = await createCliRenderer({
6473
7579
  useMouse: true
6474
7580
  });
6475
7581
  const root = createRoot(renderer);
6476
- root.render(/* @__PURE__ */ jsx12(App, {}));
7582
+ root.render(/* @__PURE__ */ jsx14(App, { lacksTrueColor }));
6477
7583
  }
6478
7584
  main().catch(console.error);