replicas-cli 0.2.40 → 0.2.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -63,11 +63,12 @@ Create a `replicas.json` or `replicas.yaml` file in your repository root:
63
63
  }
64
64
  ```
65
65
 
66
- Or use YAML:
66
+ Or use YAML for better readability (especially for multiline system prompts):
67
67
 
68
68
  ```yaml
69
69
  systemPrompt: |
70
70
  Custom instructions for coding agents.
71
+ Supports multiline text.
71
72
 
72
73
  startHook:
73
74
  commands:
@@ -77,20 +78,19 @@ startHook:
77
78
 
78
79
  ## Features
79
80
 
80
- - **Interactive Dashboard**: Full-featured TUI with workspace management, chat, and environment monitoring
81
81
  - **Secure Authentication**: OAuth-based login with token refresh
82
82
  - **Quick SSH Access**: Connect to workspaces with a single command
83
+ - **SSH Key Caching**: Reuse SSH keys across sessions
83
84
  - **Multi-Organization**: Switch between different organizations
84
85
 
85
- ## Development
86
+ ## How it Works
86
87
 
87
- ```bash
88
- # Run from source
89
- bun run dev
88
+ 1. **SSH Key Management**: The CLI securely caches SSH keys in `~/.replicas/keys/`
90
89
 
91
- # Build
92
- bun run build
93
- ```
90
+ ## Requirements
91
+
92
+ - Node.js 18+
93
+ - SSH client (openssh)
94
94
 
95
95
  ## Support
96
96
 
@@ -7524,6 +7524,22 @@ var MAX_WARM_HOOK_TIMEOUT_MS = 15 * 60 * 1e3;
7524
7524
  // ../shared/src/replicas-config.ts
7525
7525
  var REPLICAS_CONFIG_FILENAMES = ["replicas.json", "replicas.yaml", "replicas.yml"];
7526
7526
 
7527
+ // ../shared/src/engine/types.ts
7528
+ var DEFAULT_CHAT_TITLES = {
7529
+ claude: "Claude Code",
7530
+ codex: "Codex"
7531
+ };
7532
+ function getInitialChatId(chats, preferredProvider = "claude") {
7533
+ if (chats.length === 0) return null;
7534
+ const isDefault = (c) => c.title === DEFAULT_CHAT_TITLES[c.provider];
7535
+ const hasActivity = (c) => c.updatedAt > c.createdAt;
7536
+ const mostActive = (list) => list.filter(hasActivity).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))[0] ?? null;
7537
+ const fallbackProvider = preferredProvider === "claude" ? "codex" : "claude";
7538
+ const preferred = chats.filter((c) => c.provider === preferredProvider);
7539
+ const fallback = chats.filter((c) => c.provider === fallbackProvider);
7540
+ return mostActive(preferred)?.id ?? mostActive(fallback)?.id ?? preferred.find(isDefault)?.id ?? fallback.find(isDefault)?.id ?? chats[0]?.id ?? null;
7541
+ }
7542
+
7527
7543
  // ../shared/src/prompts.ts
7528
7544
  var REPLICAS_INSTRUCTIONS_TAG = "replicas_instructions";
7529
7545
  function removeTag(text, tag) {
@@ -8237,6 +8253,7 @@ export {
8237
8253
  getCurrentUser,
8238
8254
  SANDBOX_PATHS,
8239
8255
  REPLICAS_CONFIG_FILENAMES,
8256
+ getInitialChatId,
8240
8257
  parseAgentEvents,
8241
8258
  createUserMessage,
8242
8259
  isAgentBackendEvent,
package/dist/index.mjs CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  setIdeCommand,
16
16
  setOrganizationId,
17
17
  writeConfig
18
- } from "./chunk-WCKZW4VV.mjs";
18
+ } from "./chunk-IO4QHXPW.mjs";
19
19
 
20
20
  // src/index.ts
21
21
  import "dotenv/config";
@@ -448,7 +448,7 @@ Found ${response.workspaces.length} workspaces matching "${workspaceName}":`));
448
448
  console.log(chalk4.gray(` Status: ${selectedWorkspace.status || "unknown"}`));
449
449
  if (selectedWorkspace.status === "sleeping") {
450
450
  throw new Error(
451
- "Workspace is currently sleeping. Please wake it up in the dashboard at https://replicas.dev before connecting."
451
+ "Workspace is currently sleeping. Wake it using `replicas app` (press w on a sleeping workspace) or visit https://replicas.dev"
452
452
  );
453
453
  }
454
454
  console.log(chalk4.blue("\nRequesting SSH access token..."));
@@ -1855,12 +1855,12 @@ async function interactiveCommand() {
1855
1855
  );
1856
1856
  }
1857
1857
  console.log(chalk17.gray("Starting interactive mode..."));
1858
- const { launchInteractive } = await import("./interactive-NB2QAJE2.mjs");
1858
+ const { launchInteractive } = await import("./interactive-M3UR4H6X.mjs");
1859
1859
  await launchInteractive();
1860
1860
  }
1861
1861
 
1862
1862
  // src/index.ts
1863
- var CLI_VERSION = "0.2.40";
1863
+ var CLI_VERSION = "0.2.42";
1864
1864
  var program = new Command();
1865
1865
  program.name("replicas").description("CLI for managing Replicas workspaces").version(CLI_VERSION);
1866
1866
  program.command("login").description("Authenticate with your Replicas account").action(async () => {
@@ -4,19 +4,20 @@ import {
4
4
  createMockWorkspaceRecord,
5
5
  createUserMessage,
6
6
  filterDisplayMessages,
7
+ getInitialChatId,
7
8
  getOrganizationId,
8
9
  getValidToken,
9
10
  isAgentBackendEvent,
10
11
  parseAgentEvents
11
- } from "./chunk-WCKZW4VV.mjs";
12
+ } from "./chunk-IO4QHXPW.mjs";
12
13
 
13
14
  // src/interactive/index.tsx
14
15
  import { createCliRenderer } from "@opentui/core";
15
16
  import { createRoot } from "@opentui/react";
16
17
 
17
18
  // src/interactive/App.tsx
18
- import { useState as useState5, useEffect as useEffect3, useMemo as useMemo3, useCallback as useCallback5, useRef as useRef3 } from "react";
19
- import { useKeyboard as useKeyboard3, useRenderer, useTerminalDimensions as useTerminalDimensions3 } from "@opentui/react";
19
+ import { useState as useState6, useEffect as useEffect3, useMemo as useMemo4, useCallback as useCallback6, useRef as useRef3 } from "react";
20
+ import { useKeyboard as useKeyboard4, useRenderer, useTerminalDimensions as useTerminalDimensions3 } from "@opentui/react";
20
21
  import { QueryClient } from "@tanstack/react-query";
21
22
 
22
23
  // ../shared/src/hooks/auth-context.ts
@@ -145,6 +146,21 @@ function useDeleteWorkspace() {
145
146
  }
146
147
  }, auth.queryClient);
147
148
  }
149
+ function useWakeWorkspace() {
150
+ const auth = useReplicasAuth();
151
+ const orgFetch = useOrgFetch();
152
+ const orgId = auth.getOrganizationId();
153
+ return useMutation({
154
+ mutationFn: (workspaceId) => orgFetch(`/v1/workspaces/${workspaceId}/wake`, {
155
+ method: "POST"
156
+ }),
157
+ onSuccess: (_data, workspaceId) => {
158
+ auth.queryClient.invalidateQueries({ queryKey: ["workspaces", orgId] });
159
+ auth.queryClient.invalidateQueries({ queryKey: ["workspace-status", workspaceId] });
160
+ auth.queryClient.invalidateQueries({ queryKey: ["workspace-chats", workspaceId] });
161
+ }
162
+ }, auth.queryClient);
163
+ }
148
164
  function useGenerateWorkspaceName() {
149
165
  const auth = useReplicasAuth();
150
166
  const orgFetch = useOrgFetch();
@@ -468,11 +484,11 @@ function useEnvironmentFiles() {
468
484
  // src/interactive/components/StatusBar.tsx
469
485
  import { jsx, jsxs } from "@opentui/react/jsx-runtime";
470
486
  var KEYBINDS = {
471
- sidebar: "j/k nav \u21B5 select d del a add",
487
+ sidebar: "j/k nav \u21B5 select d del a add w wake",
472
488
  "chat-tabs": "\u2190/\u2192 switch tabs",
473
489
  "chat-history": "j/k scroll",
474
490
  "chat-input": "\u21B5 send \u21E5 plan/build",
475
- info: "j/k scroll o dashboard"
491
+ info: "j/k nav \u21B5 open o dashboard w wake"
476
492
  };
477
493
  function StatusBar({ focusPanel }) {
478
494
  return /* @__PURE__ */ jsxs(
@@ -541,6 +557,8 @@ function WorkspaceSidebar({
541
557
  onSelectWorkspace,
542
558
  onCreateWorkspace,
543
559
  onDeleteWorkspace,
560
+ onWakeWorkspace,
561
+ wakingWorkspaceId,
544
562
  focused,
545
563
  loading
546
564
  }) {
@@ -675,6 +693,13 @@ function WorkspaceSidebar({
675
693
  }
676
694
  return;
677
695
  }
696
+ if (key.name === "w") {
697
+ const item = items[cursorIndex];
698
+ if (item?.type === "workspace" && item.status === "sleeping" && wakingWorkspaceId !== item.workspaceId) {
699
+ onWakeWorkspace(item.workspaceId);
700
+ }
701
+ return;
702
+ }
678
703
  });
679
704
  const handleItemClick = useCallback3(
680
705
  (globalIndex) => {
@@ -814,7 +839,8 @@ function WorkspaceSidebar({
814
839
  focused && /* @__PURE__ */ jsx2("box", { height: 1, paddingX: 1, backgroundColor: "#111111", children: /* @__PURE__ */ jsxs2("text", { children: [
815
840
  /* @__PURE__ */ jsx2("span", { fg: "#555555", children: "j/k:nav " }),
816
841
  /* @__PURE__ */ jsx2("span", { fg: "#555555", children: "d:del " }),
817
- /* @__PURE__ */ jsx2("span", { fg: "#555555", children: "a:add" })
842
+ /* @__PURE__ */ jsx2("span", { fg: "#555555", children: "a:add " }),
843
+ /* @__PURE__ */ jsx2("span", { fg: "#555555", children: "w:wake" })
818
844
  ] }) })
819
845
  ]
820
846
  }
@@ -1272,10 +1298,45 @@ function ChatArea({
1272
1298
  }
1273
1299
 
1274
1300
  // src/interactive/components/WorkspaceInfo.tsx
1275
- import React4 from "react";
1301
+ import React4, { useState as useState4, useMemo as useMemo3, useCallback as useCallback4 } from "react";
1276
1302
  import open from "open";
1303
+ import { useKeyboard as useKeyboard3 } from "@opentui/react";
1277
1304
  import { Fragment, jsx as jsx6, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
1278
1305
  var WEB_APP_URL = process.env.REPLICAS_WEB_URL || "https://replicas.dev";
1306
+ function buildInteractiveItems(workspaceId, status, previews, wakingWorkspaceId) {
1307
+ const items = [];
1308
+ if (workspaceId) {
1309
+ items.push({ type: "dashboard", workspaceId });
1310
+ }
1311
+ if (workspaceId && status.status === "sleeping" && wakingWorkspaceId !== workspaceId) {
1312
+ items.push({ type: "wake", workspaceId });
1313
+ }
1314
+ for (const preview of previews) {
1315
+ if (preview.publicUrl) {
1316
+ items.push({ type: "preview", url: preview.publicUrl, port: preview.port });
1317
+ }
1318
+ }
1319
+ if (status.repoStatuses) {
1320
+ for (const repo of status.repoStatuses) {
1321
+ if (repo.prUrl) {
1322
+ items.push({ type: "pr", url: repo.prUrl, repoName: repo.name });
1323
+ }
1324
+ }
1325
+ }
1326
+ return items;
1327
+ }
1328
+ function getItemLabel(item) {
1329
+ switch (item.type) {
1330
+ case "dashboard":
1331
+ return "\u2197 Open in Dashboard";
1332
+ case "wake":
1333
+ return "\u25B6 Wake";
1334
+ case "preview":
1335
+ return `\u2197 Preview :${item.port}`;
1336
+ case "pr":
1337
+ return `\u2197 View PR (${item.repoName})`;
1338
+ }
1339
+ }
1279
1340
  var AUTH_METHOD_LABELS = {
1280
1341
  oauth: "OAuth",
1281
1342
  api_key: "API Key",
@@ -1326,8 +1387,108 @@ function DetailList({
1326
1387
  if (rows.length === 0) return null;
1327
1388
  return /* @__PURE__ */ jsx6(Fragment, { children: rows.map((item, i) => /* @__PURE__ */ jsx6(CardItem, { label: item, status: actualSet.has(item) }, i)) });
1328
1389
  }
1329
- function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, envConfig, previews }) {
1390
+ function InteractiveRow({ label, highlighted, onClick }) {
1391
+ return /* @__PURE__ */ jsx6(
1392
+ "box",
1393
+ {
1394
+ paddingX: 1,
1395
+ backgroundColor: highlighted ? "#1a2a1a" : "#111111",
1396
+ onMouseDown: onClick,
1397
+ children: /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: highlighted ? "#66bb6a" : "#7dcfff", children: label }) })
1398
+ }
1399
+ );
1400
+ }
1401
+ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, envConfig, previews, onWakeWorkspace, wakingWorkspaceId }) {
1330
1402
  const borderColor = focused ? "#66bb6a" : "#333333";
1403
+ const [cursorIndex, setCursorIndex] = useState4(0);
1404
+ const interactiveItems = useMemo3(() => {
1405
+ if (!status || !workspaceName) return [];
1406
+ return buildInteractiveItems(workspaceId, status, previews, wakingWorkspaceId);
1407
+ }, [workspaceId, workspaceName, status, previews, wakingWorkspaceId]);
1408
+ const safeCursor = interactiveItems.length > 0 ? Math.min(cursorIndex, interactiveItems.length - 1) : 0;
1409
+ const moveCursor = useCallback4(
1410
+ (next) => {
1411
+ const clamped = Math.max(0, Math.min(interactiveItems.length - 1, next));
1412
+ setCursorIndex(clamped);
1413
+ },
1414
+ [interactiveItems.length]
1415
+ );
1416
+ const handleAction = useCallback4(
1417
+ (item) => {
1418
+ if (!item) return;
1419
+ switch (item.type) {
1420
+ case "dashboard":
1421
+ open(`${WEB_APP_URL}/dashboard?workspaceId=${item.workspaceId}`).catch(() => {
1422
+ });
1423
+ break;
1424
+ case "wake":
1425
+ onWakeWorkspace(item.workspaceId);
1426
+ break;
1427
+ case "preview":
1428
+ open(item.url).catch(() => {
1429
+ });
1430
+ break;
1431
+ case "pr":
1432
+ open(item.url).catch(() => {
1433
+ });
1434
+ break;
1435
+ }
1436
+ },
1437
+ [onWakeWorkspace]
1438
+ );
1439
+ useKeyboard3((key) => {
1440
+ if (!focused) return;
1441
+ if (interactiveItems.length === 0) return;
1442
+ if (key.name === "j" || key.name === "down") {
1443
+ moveCursor(safeCursor + 1);
1444
+ return;
1445
+ }
1446
+ if (key.name === "k" || key.name === "up") {
1447
+ moveCursor(safeCursor - 1);
1448
+ return;
1449
+ }
1450
+ if (key.name === "g" && !key.shift) {
1451
+ moveCursor(0);
1452
+ return;
1453
+ }
1454
+ if (key.name === "g" && key.shift) {
1455
+ moveCursor(interactiveItems.length - 1);
1456
+ return;
1457
+ }
1458
+ if (key.name === "enter" || key.name === "return") {
1459
+ handleAction(interactiveItems[safeCursor]);
1460
+ return;
1461
+ }
1462
+ if (key.name === "o") {
1463
+ const dashboardItem2 = interactiveItems.find((item) => item.type === "dashboard");
1464
+ if (dashboardItem2) handleAction(dashboardItem2);
1465
+ return;
1466
+ }
1467
+ if (key.name === "w") {
1468
+ const wakeItem2 = interactiveItems.find((item) => item.type === "wake");
1469
+ if (wakeItem2) handleAction(wakeItem2);
1470
+ return;
1471
+ }
1472
+ });
1473
+ const isHighlighted = useCallback4(
1474
+ (item) => {
1475
+ if (!focused) return false;
1476
+ const idx = interactiveItems.indexOf(item);
1477
+ return idx === safeCursor;
1478
+ },
1479
+ [focused, interactiveItems, safeCursor]
1480
+ );
1481
+ const findItem = useCallback4(
1482
+ (type, key) => {
1483
+ return interactiveItems.find((item) => {
1484
+ if (item.type !== type) return false;
1485
+ if (type === "preview" && key) return item.port === Number(key);
1486
+ if (type === "pr" && key) return item.repoName === key;
1487
+ return true;
1488
+ });
1489
+ },
1490
+ [interactiveItems]
1491
+ );
1331
1492
  if (!workspaceName) {
1332
1493
  return /* @__PURE__ */ jsx6(
1333
1494
  "box",
@@ -1367,6 +1528,8 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, e
1367
1528
  const expectedSkills = (envConfig.skills?.environment_skills ?? []).map((s) => s.source);
1368
1529
  const expectedGlobalVars = (envConfig.variables?.environment_variables ?? []).filter((v) => v.scope_type === "global").map((v) => v.key);
1369
1530
  const expectedGlobalFiles = (envConfig.files?.environment_files ?? []).filter((f) => f.scope_type === "global").map((f) => f.path);
1531
+ const dashboardItem = findItem("dashboard");
1532
+ const wakeItem = findItem("wake");
1370
1533
  return /* @__PURE__ */ jsx6(
1371
1534
  "box",
1372
1535
  {
@@ -1393,24 +1556,24 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, e
1393
1556
  " ",
1394
1557
  env.engineVersion
1395
1558
  ] })
1396
- ] }),
1397
- workspaceId && /* @__PURE__ */ jsx6(
1398
- "box",
1399
- {
1400
- onMouseDown: () => {
1401
- open(`${WEB_APP_URL}/dashboard?workspaceId=${workspaceId}`).catch(() => {
1402
- });
1403
- },
1404
- children: /* @__PURE__ */ jsxs6("text", { children: [
1405
- /* @__PURE__ */ jsxs6("span", { fg: "#7dcfff", children: [
1406
- "\u2197",
1407
- " Open in Dashboard"
1408
- ] }),
1409
- /* @__PURE__ */ jsx6("span", { fg: "#555555", children: " (o)" })
1410
- ] })
1411
- }
1412
- )
1559
+ ] })
1413
1560
  ] }) }),
1561
+ dashboardItem && /* @__PURE__ */ jsx6(
1562
+ InteractiveRow,
1563
+ {
1564
+ label: getItemLabel(dashboardItem),
1565
+ highlighted: isHighlighted(dashboardItem),
1566
+ onClick: () => handleAction(dashboardItem)
1567
+ }
1568
+ ),
1569
+ wakeItem && /* @__PURE__ */ jsx6(
1570
+ InteractiveRow,
1571
+ {
1572
+ label: getItemLabel(wakeItem),
1573
+ highlighted: isHighlighted(wakeItem),
1574
+ onClick: () => handleAction(wakeItem)
1575
+ }
1576
+ ),
1414
1577
  (status.isClaudeProcessing || status.isCodexProcessing) && /* @__PURE__ */ jsxs6("box", { backgroundColor: "#1a1500", paddingX: 1, marginX: 1, marginBottom: 1, children: [
1415
1578
  status.isClaudeProcessing && /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsxs6("span", { fg: "#ffaa00", children: [
1416
1579
  "\u25C6",
@@ -1436,42 +1599,60 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, e
1436
1599
  /* @__PURE__ */ jsx6(CardItem, { label: "Slack", status: env.slackAccessConfigured }),
1437
1600
  /* @__PURE__ */ jsx6(CardItem, { label: "Linear", status: env.linearAccessConfigured })
1438
1601
  ] }),
1439
- previews.length > 0 && /* @__PURE__ */ jsx6(Section, { title: "Previews", children: previews.map((preview, i) => /* @__PURE__ */ jsxs6("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
1440
- /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsxs6("span", { fg: "#cccccc", children: [
1441
- ":",
1442
- preview.port
1443
- ] }) }),
1444
- /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("a", { href: preview.publicUrl, children: /* @__PURE__ */ jsx6("span", { fg: "#66bb6a", children: "\u2197" }) }) })
1445
- ] }, i)) }),
1446
- status.repoStatuses && status.repoStatuses.length > 0 && /* @__PURE__ */ jsx6(Section, { title: "Repositories", children: status.repoStatuses.map((repo, i) => /* @__PURE__ */ jsxs6(React4.Fragment, { children: [
1447
- /* @__PURE__ */ jsx6("box", { backgroundColor: "#111111", paddingX: 1, children: /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx6("strong", { children: repo.name }) }) }) }),
1448
- /* @__PURE__ */ jsx6("box", { backgroundColor: "#111111", paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: /* @__PURE__ */ jsxs6("text", { children: [
1449
- /* @__PURE__ */ jsxs6("span", { fg: "#555555", children: [
1450
- "\u2514",
1451
- " "
1452
- ] }),
1453
- /* @__PURE__ */ jsx6("span", { fg: repo.currentBranch !== repo.defaultBranch ? "#ffaa00" : "#66bb6a", children: repo.currentBranch })
1454
- ] }) }),
1455
- repo.gitDiff && (repo.gitDiff.added > 0 || repo.gitDiff.removed > 0) && /* @__PURE__ */ jsx6("box", { backgroundColor: "#111111", paddingX: 1, flexDirection: "row", children: /* @__PURE__ */ jsxs6("text", { children: [
1456
- /* @__PURE__ */ jsx6("span", { fg: "#555555", children: " " }),
1457
- /* @__PURE__ */ jsxs6("span", { fg: "#66bb6a", children: [
1458
- "+",
1459
- repo.gitDiff.added
1460
- ] }),
1461
- /* @__PURE__ */ jsx6("span", { fg: "#444444", children: " / " }),
1462
- /* @__PURE__ */ jsxs6("span", { fg: "#ff4444", children: [
1463
- "-",
1464
- repo.gitDiff.removed
1465
- ] })
1466
- ] }) }),
1467
- repo.prUrl && /* @__PURE__ */ jsx6("box", { backgroundColor: "#111111", paddingX: 1, children: /* @__PURE__ */ jsxs6("text", { children: [
1468
- /* @__PURE__ */ jsx6("span", { fg: "#555555", children: " " }),
1469
- /* @__PURE__ */ jsx6("a", { href: repo.prUrl, children: /* @__PURE__ */ jsxs6("span", { fg: "#66bb6a", children: [
1470
- "View PR ",
1471
- "\u2197"
1472
- ] }) })
1473
- ] }) })
1474
- ] }, i)) }),
1602
+ previews.length > 0 && /* @__PURE__ */ jsx6(Section, { title: "Previews", children: previews.map((preview, i) => {
1603
+ const previewItem = findItem("preview", String(preview.port));
1604
+ return /* @__PURE__ */ jsxs6(
1605
+ "box",
1606
+ {
1607
+ flexDirection: "row",
1608
+ justifyContent: "space-between",
1609
+ paddingX: 1,
1610
+ backgroundColor: previewItem && isHighlighted(previewItem) ? "#1a2a1a" : "#111111",
1611
+ onMouseDown: () => previewItem && handleAction(previewItem),
1612
+ children: [
1613
+ /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsxs6("span", { fg: "#cccccc", children: [
1614
+ ":",
1615
+ preview.port
1616
+ ] }) }),
1617
+ /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: previewItem && isHighlighted(previewItem) ? "#66bb6a" : "#7dcfff", children: "\u2197" }) })
1618
+ ]
1619
+ },
1620
+ i
1621
+ );
1622
+ }) }),
1623
+ status.repoStatuses && status.repoStatuses.length > 0 && /* @__PURE__ */ jsx6(Section, { title: "Repositories", children: status.repoStatuses.map((repo, i) => {
1624
+ const prItem = findItem("pr", repo.name);
1625
+ return /* @__PURE__ */ jsxs6(React4.Fragment, { children: [
1626
+ /* @__PURE__ */ jsx6("box", { backgroundColor: "#111111", paddingX: 1, children: /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx6("strong", { children: repo.name }) }) }) }),
1627
+ /* @__PURE__ */ jsx6("box", { backgroundColor: "#111111", paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: /* @__PURE__ */ jsxs6("text", { children: [
1628
+ /* @__PURE__ */ jsxs6("span", { fg: "#555555", children: [
1629
+ "\u2514",
1630
+ " "
1631
+ ] }),
1632
+ /* @__PURE__ */ jsx6("span", { fg: repo.currentBranch !== repo.defaultBranch ? "#ffaa00" : "#66bb6a", children: repo.currentBranch })
1633
+ ] }) }),
1634
+ repo.gitDiff && (repo.gitDiff.added > 0 || repo.gitDiff.removed > 0) && /* @__PURE__ */ jsx6("box", { backgroundColor: "#111111", paddingX: 1, flexDirection: "row", children: /* @__PURE__ */ jsxs6("text", { children: [
1635
+ /* @__PURE__ */ jsx6("span", { fg: "#555555", children: " " }),
1636
+ /* @__PURE__ */ jsxs6("span", { fg: "#66bb6a", children: [
1637
+ "+",
1638
+ repo.gitDiff.added
1639
+ ] }),
1640
+ /* @__PURE__ */ jsx6("span", { fg: "#444444", children: " / " }),
1641
+ /* @__PURE__ */ jsxs6("span", { fg: "#ff4444", children: [
1642
+ "-",
1643
+ repo.gitDiff.removed
1644
+ ] })
1645
+ ] }) }),
1646
+ prItem && /* @__PURE__ */ jsx6(
1647
+ InteractiveRow,
1648
+ {
1649
+ label: ` ${getItemLabel(prItem)}`,
1650
+ highlighted: isHighlighted(prItem),
1651
+ onClick: () => handleAction(prItem)
1652
+ }
1653
+ )
1654
+ ] }, i);
1655
+ }) }),
1475
1656
  env && /* @__PURE__ */ jsxs6(Section, { title: "Hooks", children: [
1476
1657
  /* @__PURE__ */ jsx6(CardItem, { label: "Global warm", status: env.globalWarmHookCompleted.status }),
1477
1658
  env.repositories.map((repo, i) => /* @__PURE__ */ jsxs6(React4.Fragment, { children: [
@@ -1510,7 +1691,7 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, e
1510
1691
  import { useTerminalDimensions as useTerminalDimensions2 } from "@opentui/react";
1511
1692
 
1512
1693
  // src/interactive/toast-context.tsx
1513
- import { createContext as createContext2, useContext as useContext2, useState as useState4, useCallback as useCallback4, useRef as useRef2 } from "react";
1694
+ import { createContext as createContext2, useContext as useContext2, useState as useState5, useCallback as useCallback5, useRef as useRef2 } from "react";
1514
1695
  import { jsx as jsx7 } from "@opentui/react/jsx-runtime";
1515
1696
  var ToastContext = createContext2(null);
1516
1697
  function useToast() {
@@ -1519,9 +1700,9 @@ function useToast() {
1519
1700
  return ctx;
1520
1701
  }
1521
1702
  function ToastProvider({ children }) {
1522
- const [current, setCurrent] = useState4(null);
1703
+ const [current, setCurrent] = useState5(null);
1523
1704
  const timeoutRef = useRef2(null);
1524
- const show = useCallback4((options) => {
1705
+ const show = useCallback5((options) => {
1525
1706
  const { message, variant = "info", duration = 3e3 } = options;
1526
1707
  setCurrent({ message, variant });
1527
1708
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
@@ -1529,7 +1710,7 @@ function ToastProvider({ children }) {
1529
1710
  setCurrent(null);
1530
1711
  }, duration);
1531
1712
  }, []);
1532
- const error = useCallback4((err) => {
1713
+ const error = useCallback5((err) => {
1533
1714
  const message = err instanceof Error ? err.message : "An error occurred";
1534
1715
  show({ message, variant: "error", duration: 5e3 });
1535
1716
  }, [show]);
@@ -1614,17 +1795,17 @@ function AppInner() {
1614
1795
  renderer.off("selection", handler);
1615
1796
  };
1616
1797
  }, [renderer, toast]);
1617
- const [selectedWorkspaceId, setSelectedWorkspaceId] = useState5(null);
1618
- const [selectedChatId, setSelectedChatId] = useState5(null);
1619
- const [focusPanel, setFocusPanel] = useState5("sidebar");
1620
- const [taskMode, setTaskMode] = useState5("build");
1621
- const [mockWorkspaces, setMockWorkspaces] = useState5([]);
1798
+ const [selectedWorkspaceId, setSelectedWorkspaceId] = useState6(null);
1799
+ const [selectedChatId, setSelectedChatId] = useState6(null);
1800
+ const [focusPanel, setFocusPanel] = useState6("sidebar");
1801
+ const [taskMode, setTaskMode] = useState6("build");
1802
+ const [mockWorkspaces, setMockWorkspaces] = useState6([]);
1622
1803
  const mockToRealRef = useRef3(/* @__PURE__ */ new Map());
1623
1804
  const mockGroupRef = useRef3(/* @__PURE__ */ new Map());
1624
- const [mockMessages, setMockMessages] = useState5([]);
1625
- const [mockThinking, setMockThinking] = useState5(false);
1805
+ const [mockMessages, setMockMessages] = useState6([]);
1806
+ const [mockThinking, setMockThinking] = useState6(false);
1626
1807
  const pendingMessageRef = useRef3(/* @__PURE__ */ new Map());
1627
- const mockIds = useMemo3(() => new Set(mockWorkspaces.map((m) => m.id)), [mockWorkspaces]);
1808
+ const mockIds = useMemo4(() => new Set(mockWorkspaces.map((m) => m.id)), [mockWorkspaces]);
1628
1809
  const isMockSelected = selectedWorkspaceId ? mockIds.has(selectedWorkspaceId) : false;
1629
1810
  const { data: workspacesData, isLoading: loadingWorkspaces } = useWorkspaces(1, 100, "organization");
1630
1811
  const { data: reposData } = useRepositories();
@@ -1637,9 +1818,16 @@ function AppInner() {
1637
1818
  { includeDiffs: true }
1638
1819
  );
1639
1820
  const { data: chatsData } = useWorkspaceChats(isMockSelected ? null : selectedWorkspaceId);
1821
+ const chats = isMockSelected ? MOCK_CHATS : chatsData?.chats ?? [];
1822
+ const resolvedChatId = useMemo4(() => {
1823
+ if (selectedChatId && chats.find((c) => c.id === selectedChatId)) {
1824
+ return selectedChatId;
1825
+ }
1826
+ return getInitialChatId(chats);
1827
+ }, [chats, selectedChatId]);
1640
1828
  const { data: historyData, isLoading: loadingMessages } = useChatHistory(
1641
1829
  isMockSelected ? null : selectedWorkspaceId,
1642
- selectedChatId?.startsWith("mock-") ? null : selectedChatId
1830
+ resolvedChatId?.startsWith("mock-") ? null : resolvedChatId
1643
1831
  );
1644
1832
  const { data: previewsData } = useWorkspacePreviews(isMockSelected ? null : selectedWorkspaceId);
1645
1833
  const { connected: sseConnected } = useWorkspaceEvents(
@@ -1647,23 +1835,24 @@ function AppInner() {
1647
1835
  );
1648
1836
  const createWorkspaceMutation = useCreateWorkspace();
1649
1837
  const deleteWorkspaceMutation = useDeleteWorkspace();
1838
+ const wakeWorkspaceMutation = useWakeWorkspace();
1650
1839
  const generateNameMutation = useGenerateWorkspaceName();
1651
- const sendMessageMutation = useSendChatMessage(selectedWorkspaceId, selectedChatId);
1652
- const interruptMutation = useInterruptChat(selectedWorkspaceId, selectedChatId);
1840
+ const [wakingWorkspaceId, setWakingWorkspaceId] = useState6(null);
1841
+ const sendMessageMutation = useSendChatMessage(selectedWorkspaceId, resolvedChatId);
1842
+ const interruptMutation = useInterruptChat(selectedWorkspaceId, resolvedChatId);
1653
1843
  const workspaces = workspacesData?.workspaces ?? [];
1654
1844
  const repositories = reposData?.repositories ?? [];
1655
1845
  const repositorySets = setsData?.repository_sets ?? [];
1656
- const chats = isMockSelected ? MOCK_CHATS : chatsData?.chats ?? [];
1657
1846
  const previews = previewsData?.previews ?? [];
1658
- const allWorkspaces = useMemo3(() => [...mockWorkspaces, ...workspaces], [mockWorkspaces, workspaces]);
1659
- const groups = useMemo3(
1847
+ const allWorkspaces = useMemo4(() => [...mockWorkspaces, ...workspaces], [mockWorkspaces, workspaces]);
1848
+ const groups = useMemo4(
1660
1849
  () => buildGroups(allWorkspaces, workspacesData, repositories, repositorySets, mockGroupRef.current),
1661
1850
  [allWorkspaces, workspacesData, repositories, repositorySets]
1662
1851
  );
1663
1852
  const selectedWorkspace = allWorkspaces.find((ws) => ws.id === selectedWorkspaceId) ?? null;
1664
- const selectedChat = chats.find((c) => c.id === selectedChatId) ?? null;
1853
+ const selectedChat = chats.find((c) => c.id === resolvedChatId) ?? null;
1665
1854
  const isProcessing = mockThinking || (selectedChat?.processing ?? false);
1666
- const displayMessages = useMemo3(() => {
1855
+ const displayMessages = useMemo4(() => {
1667
1856
  if (isMockSelected) return mockMessages;
1668
1857
  const rawEvents = historyData?.events ?? [];
1669
1858
  const events = rawEvents.filter(isAgentBackendEvent);
@@ -1679,18 +1868,6 @@ function AppInner() {
1679
1868
  setSelectedWorkspaceId(workspaces[0].id);
1680
1869
  }
1681
1870
  }, [workspaces, selectedWorkspaceId]);
1682
- useEffect3(() => {
1683
- if (chats.length > 0 && !selectedChatId) {
1684
- setSelectedChatId(chats[0].id);
1685
- } else if (chats.length > 0 && selectedChatId && !chats.find((c) => c.id === selectedChatId)) {
1686
- setSelectedChatId(chats[0].id);
1687
- }
1688
- }, [chats, selectedChatId]);
1689
- useEffect3(() => {
1690
- setMockMessages([]);
1691
- setMockThinking(false);
1692
- setSelectedChatId(null);
1693
- }, [selectedWorkspaceId]);
1694
1871
  useEffect3(() => {
1695
1872
  if (mockWorkspaces.length === 0) return;
1696
1873
  const realIds = new Set(workspaces.map((w) => w.id));
@@ -1708,6 +1885,7 @@ function AppInner() {
1708
1885
  }
1709
1886
  mockToRealRef.current.delete(mock.id);
1710
1887
  mockGroupRef.current.delete(mock.id);
1888
+ setSelectedChatId(null);
1711
1889
  setMockMessages([]);
1712
1890
  setMockThinking(false);
1713
1891
  changed = true;
@@ -1719,7 +1897,7 @@ function AppInner() {
1719
1897
  }, [workspaces, mockWorkspaces, selectedWorkspaceId]);
1720
1898
  useEffect3(() => {
1721
1899
  if (!selectedWorkspaceId || isMockSelected) return;
1722
- if (!selectedChatId || selectedChatId.startsWith("mock-")) return;
1900
+ if (!resolvedChatId || resolvedChatId.startsWith("mock-")) return;
1723
1901
  if (statusData?.status !== "active") return;
1724
1902
  const pending = pendingMessageRef.current.get(selectedWorkspaceId);
1725
1903
  if (!pending) return;
@@ -1730,7 +1908,7 @@ function AppInner() {
1730
1908
  });
1731
1909
  }
1732
1910
  sendMessageMutation.mutate({ message: pending });
1733
- }, [selectedWorkspaceId, selectedChatId, isMockSelected, statusData?.status]);
1911
+ }, [selectedWorkspaceId, resolvedChatId, isMockSelected, statusData?.status]);
1734
1912
  useEffect3(() => {
1735
1913
  if (mockWorkspaces.length === 0) return;
1736
1914
  const interval = setInterval(() => {
@@ -1738,55 +1916,17 @@ function AppInner() {
1738
1916
  }, 5e3);
1739
1917
  return () => clearInterval(interval);
1740
1918
  }, [mockWorkspaces.length]);
1741
- useKeyboard3((key) => {
1742
- if (key.name === "f12") {
1743
- renderer.console.toggle();
1744
- return;
1745
- }
1746
- if (key.ctrl && key.name === "c") {
1747
- renderer.destroy();
1748
- return;
1749
- }
1750
- if (key.name === "escape") {
1751
- if (isProcessing && selectedWorkspaceId && selectedChatId) {
1752
- interruptMutation.mutate();
1753
- }
1754
- return;
1755
- }
1756
- if (key.shift && key.name === "tab") {
1757
- setFocusPanel((current) => {
1758
- const availablePanels = FOCUS_ORDER.filter((p) => {
1759
- if (p === "sidebar" && !showWorkspacePanel) return false;
1760
- if ((p === "chat-tabs" || p === "chat-history" || p === "chat-input") && !selectedWorkspaceId) return false;
1761
- if (p === "chat-tabs" && chats.length <= 1) return false;
1762
- if (p === "info" && !showInfoPanel) return false;
1763
- return true;
1764
- });
1765
- const idx = availablePanels.indexOf(current);
1766
- return availablePanels[(idx + 1) % availablePanels.length];
1767
- });
1768
- return;
1769
- }
1770
- if (key.name === "tab" && focusPanel === "chat-input") {
1771
- setTaskMode((m) => m === "build" ? "plan" : "build");
1772
- return;
1773
- }
1774
- if (key.name === "o" && focusPanel === "info" && selectedWorkspaceId) {
1775
- const webUrl = process.env.REPLICAS_WEB_URL || "https://replicas.dev";
1776
- import("open").then((mod) => mod.default(`${webUrl}/dashboard?workspaceId=${selectedWorkspaceId}`)).catch(() => {
1777
- });
1778
- return;
1779
- }
1780
- if (focusPanel === "chat-input") return;
1781
- });
1782
- const handleSelectWorkspace = useCallback5((workspaceId) => {
1919
+ const handleSelectWorkspace = useCallback6((workspaceId) => {
1783
1920
  setSelectedWorkspaceId(workspaceId);
1921
+ setSelectedChatId(null);
1922
+ setMockMessages([]);
1923
+ setMockThinking(false);
1784
1924
  setFocusPanel("chat-input");
1785
1925
  }, []);
1786
- const handleFocus = useCallback5((panel) => {
1926
+ const handleFocus = useCallback6((panel) => {
1787
1927
  setFocusPanel(panel);
1788
1928
  }, []);
1789
- const handleCreateWorkspace = useCallback5(
1929
+ const handleCreateWorkspace = useCallback6(
1790
1930
  async (groupId, groupType) => {
1791
1931
  const orgId = getOrganizationId() ?? "";
1792
1932
  const mock = createMockWorkspaceRecord(orgId);
@@ -1805,7 +1945,7 @@ function AppInner() {
1805
1945
  },
1806
1946
  [createWorkspaceMutation, toast]
1807
1947
  );
1808
- const handleDeleteWorkspace = useCallback5(
1948
+ const handleDeleteWorkspace = useCallback6(
1809
1949
  async (workspaceId) => {
1810
1950
  if (mockIds.has(workspaceId)) {
1811
1951
  mockGroupRef.current.delete(workspaceId);
@@ -1824,12 +1964,67 @@ function AppInner() {
1824
1964
  },
1825
1965
  [mockIds, selectedWorkspaceId, deleteWorkspaceMutation]
1826
1966
  );
1827
- const handleSelectChat = useCallback5((chatId) => {
1967
+ const handleWakeWorkspace = useCallback6(
1968
+ async (workspaceId) => {
1969
+ setWakingWorkspaceId(workspaceId);
1970
+ try {
1971
+ await wakeWorkspaceMutation.mutateAsync(workspaceId);
1972
+ toast.show({ message: "Workspace waking up...", variant: "info" });
1973
+ } catch (err) {
1974
+ toast.error(err);
1975
+ } finally {
1976
+ setWakingWorkspaceId(null);
1977
+ }
1978
+ },
1979
+ [wakeWorkspaceMutation, toast]
1980
+ );
1981
+ const handleSelectChat = useCallback6((chatId) => {
1828
1982
  setSelectedChatId(chatId);
1829
1983
  }, []);
1830
- const handleSendMessage = useCallback5(
1984
+ useKeyboard4((key) => {
1985
+ if (key.name === "f12") {
1986
+ renderer.console.toggle();
1987
+ return;
1988
+ }
1989
+ if (key.ctrl && key.name === "c") {
1990
+ renderer.destroy();
1991
+ return;
1992
+ }
1993
+ if (key.name === "escape") {
1994
+ if (isProcessing && selectedWorkspaceId && resolvedChatId) {
1995
+ interruptMutation.mutate();
1996
+ }
1997
+ return;
1998
+ }
1999
+ if (key.shift && key.name === "tab") {
2000
+ setFocusPanel((current) => {
2001
+ const availablePanels = FOCUS_ORDER.filter((p) => {
2002
+ if (p === "sidebar" && !showWorkspacePanel) return false;
2003
+ if ((p === "chat-tabs" || p === "chat-history" || p === "chat-input") && (!selectedWorkspaceId || statusData?.status === "sleeping")) return false;
2004
+ if (p === "chat-tabs" && chats.length <= 1) return false;
2005
+ if (p === "info" && !showInfoPanel) return false;
2006
+ return true;
2007
+ });
2008
+ const idx = availablePanels.indexOf(current);
2009
+ return availablePanels[(idx + 1) % availablePanels.length];
2010
+ });
2011
+ return;
2012
+ }
2013
+ if (key.name === "tab" && focusPanel === "chat-input") {
2014
+ setTaskMode((m) => m === "build" ? "plan" : "build");
2015
+ return;
2016
+ }
2017
+ if (key.name === "w" && focusPanel !== "sidebar" && focusPanel !== "info" && selectedWorkspaceId) {
2018
+ if (statusData?.status === "sleeping" && !wakingWorkspaceId) {
2019
+ handleWakeWorkspace(selectedWorkspaceId);
2020
+ }
2021
+ return;
2022
+ }
2023
+ if (focusPanel === "chat-input") return;
2024
+ });
2025
+ const handleSendMessage = useCallback6(
1831
2026
  async (message) => {
1832
- if (!selectedWorkspaceId || !selectedChatId) return;
2027
+ if (!selectedWorkspaceId || !resolvedChatId) return;
1833
2028
  if (isMockSelected) {
1834
2029
  const { message: userMsg } = createUserMessage(message);
1835
2030
  setMockMessages((prev) => [...prev, userMsg]);
@@ -1842,7 +2037,7 @@ function AppInner() {
1842
2037
  permissionMode: taskMode === "plan" ? "read" : void 0
1843
2038
  });
1844
2039
  },
1845
- [selectedWorkspaceId, selectedChatId, isMockSelected, sendMessageMutation, taskMode]
2040
+ [selectedWorkspaceId, resolvedChatId, isMockSelected, sendMessageMutation, taskMode]
1846
2041
  );
1847
2042
  return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", height: "100%", backgroundColor: "#000000", children: [
1848
2043
  /* @__PURE__ */ jsxs7("box", { flexDirection: "row", flexGrow: 1, backgroundColor: "#000000", children: [
@@ -1855,15 +2050,17 @@ function AppInner() {
1855
2050
  onSelectWorkspace: handleSelectWorkspace,
1856
2051
  onCreateWorkspace: handleCreateWorkspace,
1857
2052
  onDeleteWorkspace: handleDeleteWorkspace,
2053
+ onWakeWorkspace: handleWakeWorkspace,
2054
+ wakingWorkspaceId,
1858
2055
  focused: focusPanel === "sidebar",
1859
2056
  loading: loadingWorkspaces
1860
2057
  }
1861
2058
  ),
1862
- selectedWorkspaceId ? /* @__PURE__ */ jsx9(
2059
+ selectedWorkspaceId && statusData?.status !== "sleeping" ? /* @__PURE__ */ jsx9(
1863
2060
  ChatArea,
1864
2061
  {
1865
2062
  chats,
1866
- selectedChatId,
2063
+ selectedChatId: resolvedChatId,
1867
2064
  displayMessages,
1868
2065
  onSelectChat: handleSelectChat,
1869
2066
  onSendMessage: handleSendMessage,
@@ -1873,6 +2070,30 @@ function AppInner() {
1873
2070
  isProcessing,
1874
2071
  loading: loadingMessages
1875
2072
  }
2073
+ ) : selectedWorkspaceId && statusData?.status === "sleeping" ? /* @__PURE__ */ jsxs7(
2074
+ "box",
2075
+ {
2076
+ flexGrow: 1,
2077
+ border: true,
2078
+ borderStyle: "rounded",
2079
+ borderColor: "#333333",
2080
+ backgroundColor: "#000000",
2081
+ justifyContent: "center",
2082
+ alignItems: "center",
2083
+ flexDirection: "column",
2084
+ gap: 1,
2085
+ children: [
2086
+ /* @__PURE__ */ jsxs7("text", { fg: "#888888", children: [
2087
+ "\u263E",
2088
+ " This workspace is sleeping"
2089
+ ] }),
2090
+ /* @__PURE__ */ jsxs7("text", { fg: "#555555", children: [
2091
+ "Press ",
2092
+ /* @__PURE__ */ jsx9("span", { fg: "#66bb6a", children: /* @__PURE__ */ jsx9("strong", { children: "w" }) }),
2093
+ " to wake it up"
2094
+ ] })
2095
+ ]
2096
+ }
1876
2097
  ) : /* @__PURE__ */ jsx9(
1877
2098
  "box",
1878
2099
  {
@@ -1899,7 +2120,9 @@ function AppInner() {
1899
2120
  variables: envVariables ?? null,
1900
2121
  files: envFiles ?? null
1901
2122
  },
1902
- previews
2123
+ previews,
2124
+ onWakeWorkspace: handleWakeWorkspace,
2125
+ wakingWorkspaceId
1903
2126
  }
1904
2127
  )
1905
2128
  ] }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-cli",
3
- "version": "0.2.40",
3
+ "version": "0.2.42",
4
4
  "description": "CLI for managing Replicas workspaces - SSH into cloud dev environments with automatic port forwarding",
5
5
  "main": "dist/index.mjs",
6
6
  "bin": {