replicas-cli 0.2.39 → 0.2.40

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.
@@ -0,0 +1,1925 @@
1
+ #!/usr/bin/env bun
2
+ import {
3
+ buildGroups,
4
+ createMockWorkspaceRecord,
5
+ createUserMessage,
6
+ filterDisplayMessages,
7
+ getOrganizationId,
8
+ getValidToken,
9
+ isAgentBackendEvent,
10
+ parseAgentEvents
11
+ } from "./chunk-WCKZW4VV.mjs";
12
+
13
+ // src/interactive/index.tsx
14
+ import { createCliRenderer } from "@opentui/core";
15
+ import { createRoot } from "@opentui/react";
16
+
17
+ // 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";
20
+ import { QueryClient } from "@tanstack/react-query";
21
+
22
+ // ../shared/src/hooks/auth-context.ts
23
+ import { createContext, useContext } from "react";
24
+ var ReplicasAuthContext = createContext(null);
25
+ var ReplicasAuthProvider = ReplicasAuthContext.Provider;
26
+ function useReplicasAuth() {
27
+ const ctx = useContext(ReplicasAuthContext);
28
+ if (!ctx) {
29
+ throw new Error("useReplicasAuth must be used within a ReplicasAuthProvider");
30
+ }
31
+ return ctx;
32
+ }
33
+
34
+ // ../shared/src/hooks/fetch.ts
35
+ import { useCallback } from "react";
36
+ var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
37
+ var MAX_REDIRECTS = 5;
38
+ async function fetchWithRedirects(url, init, attempt = 0) {
39
+ const response = await fetch(url, { ...init, redirect: "manual" });
40
+ if (REDIRECT_STATUSES.has(response.status)) {
41
+ if (attempt >= MAX_REDIRECTS) {
42
+ throw new Error("Too many redirects while contacting the Replicas API");
43
+ }
44
+ const location = response.headers.get("location");
45
+ if (!location) {
46
+ throw new Error(`Redirect status ${response.status} missing Location header`);
47
+ }
48
+ const nextUrl = new URL(location, url).toString();
49
+ const shouldForceGet = response.status === 303 && init.method !== void 0 && init.method.toUpperCase() !== "GET";
50
+ const nextInit = {
51
+ ...init,
52
+ method: shouldForceGet ? "GET" : init.method,
53
+ body: shouldForceGet ? void 0 : init.body
54
+ };
55
+ return fetchWithRedirects(nextUrl, nextInit, attempt + 1);
56
+ }
57
+ return response;
58
+ }
59
+ function useOrgFetch() {
60
+ const auth = useReplicasAuth();
61
+ return useCallback(
62
+ async function orgFetch(url, options) {
63
+ const token = await auth.getAccessToken();
64
+ const orgId = auth.getOrganizationId();
65
+ const headers = {
66
+ "Authorization": `Bearer ${token}`,
67
+ "Replicas-Org-Id": orgId,
68
+ "Content-Type": "application/json",
69
+ ...options?.headers || {}
70
+ };
71
+ const absoluteUrl = `${auth.monolithUrl}${url}`;
72
+ const requestInit = {
73
+ ...options,
74
+ headers,
75
+ body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0
76
+ };
77
+ const response = await fetchWithRedirects(absoluteUrl, requestInit);
78
+ if (!response.ok) {
79
+ const error = await response.json().catch(() => ({ error: "Request failed" }));
80
+ throw new Error(error.error || `Request failed with status ${response.status}`);
81
+ }
82
+ return response.json();
83
+ },
84
+ [auth]
85
+ );
86
+ }
87
+ function useRawOrgFetch() {
88
+ const auth = useReplicasAuth();
89
+ return useCallback(
90
+ async function rawOrgFetch(url, options) {
91
+ const token = await auth.getAccessToken();
92
+ const orgId = auth.getOrganizationId();
93
+ const headers = {
94
+ "Authorization": `Bearer ${token}`,
95
+ "Replicas-Org-Id": orgId,
96
+ ...options?.headers || {}
97
+ };
98
+ const absoluteUrl = `${auth.monolithUrl}${url}`;
99
+ return fetch(absoluteUrl, { ...options, headers });
100
+ },
101
+ [auth]
102
+ );
103
+ }
104
+
105
+ // ../shared/src/hooks/useWorkspaces.ts
106
+ import { useQuery, useMutation } from "@tanstack/react-query";
107
+ function useWorkspaces(page = 1, limit = 100, scope = "organization") {
108
+ const auth = useReplicasAuth();
109
+ const orgFetch = useOrgFetch();
110
+ const orgId = auth.getOrganizationId();
111
+ return useQuery({
112
+ queryKey: ["workspaces", orgId, scope, page, limit],
113
+ queryFn: () => orgFetch(`/v1/workspaces?scope=${scope}&page=${page}&limit=${limit}`),
114
+ enabled: !!orgId
115
+ }, auth.queryClient);
116
+ }
117
+ function useCreateWorkspace() {
118
+ const auth = useReplicasAuth();
119
+ const orgFetch = useOrgFetch();
120
+ const orgId = auth.getOrganizationId();
121
+ return useMutation({
122
+ mutationFn: (request) => orgFetch("/v1/workspaces", {
123
+ method: "POST",
124
+ body: request
125
+ }),
126
+ onSettled: () => {
127
+ auth.queryClient.invalidateQueries({ queryKey: ["workspaces", orgId] });
128
+ }
129
+ }, auth.queryClient);
130
+ }
131
+ function useDeleteWorkspace() {
132
+ const auth = useReplicasAuth();
133
+ const orgFetch = useOrgFetch();
134
+ const orgId = auth.getOrganizationId();
135
+ return useMutation({
136
+ mutationFn: (workspaceId) => orgFetch(`/v1/workspaces/${workspaceId}`, {
137
+ method: "DELETE"
138
+ }),
139
+ onSuccess: (_data, deletedWorkspaceId) => {
140
+ auth.queryClient.invalidateQueries({ queryKey: ["workspaces", orgId] });
141
+ auth.queryClient.removeQueries({ queryKey: ["workspace-status", deletedWorkspaceId] });
142
+ auth.queryClient.removeQueries({ queryKey: ["workspace-chats", deletedWorkspaceId] });
143
+ auth.queryClient.removeQueries({ queryKey: ["chat-history", deletedWorkspaceId] });
144
+ auth.queryClient.removeQueries({ queryKey: ["workspace-previews", deletedWorkspaceId] });
145
+ }
146
+ }, auth.queryClient);
147
+ }
148
+ function useGenerateWorkspaceName() {
149
+ const auth = useReplicasAuth();
150
+ const orgFetch = useOrgFetch();
151
+ return useMutation({
152
+ mutationFn: ({ workspaceId, prompt }) => orgFetch(`/v1/workspaces/${workspaceId}/generate-name`, {
153
+ method: "POST",
154
+ body: { prompt }
155
+ }),
156
+ onSuccess: () => {
157
+ auth.queryClient.invalidateQueries({ queryKey: ["workspaces"] });
158
+ }
159
+ }, auth.queryClient);
160
+ }
161
+
162
+ // ../shared/src/hooks/useWorkspaceEngine.ts
163
+ import { useCallback as useCallback2, useEffect, useMemo, useState } from "react";
164
+ import { useQuery as useQuery2, useMutation as useMutation2 } from "@tanstack/react-query";
165
+ function upsertChat(chats, chat) {
166
+ const existingIndex = chats.findIndex((item) => item.id === chat.id);
167
+ if (existingIndex === -1) {
168
+ return [chat, ...chats].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
169
+ }
170
+ const next = [...chats];
171
+ next[existingIndex] = chat;
172
+ return next.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
173
+ }
174
+ function patchChat(chats, chatId, patch) {
175
+ let changed = false;
176
+ const next = chats.map((chat) => {
177
+ if (chat.id !== chatId) return chat;
178
+ changed = true;
179
+ return { ...chat, ...patch };
180
+ });
181
+ return changed ? next.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)) : chats;
182
+ }
183
+ function parseSseEvent(raw) {
184
+ for (const line of raw.split("\n")) {
185
+ if (line.startsWith("data: ")) {
186
+ try {
187
+ return JSON.parse(line.slice(6));
188
+ } catch {
189
+ return null;
190
+ }
191
+ }
192
+ }
193
+ return null;
194
+ }
195
+ function getEventSignature(value) {
196
+ const normalize = (input) => {
197
+ if (Array.isArray(input)) return input.map(normalize);
198
+ if (input && typeof input === "object") {
199
+ const entries = Object.entries(input).sort(([a], [b]) => a.localeCompare(b)).map(([key, val]) => [key, normalize(val)]);
200
+ return Object.fromEntries(entries);
201
+ }
202
+ return input;
203
+ };
204
+ try {
205
+ return JSON.stringify(normalize(value));
206
+ } catch {
207
+ return String(value);
208
+ }
209
+ }
210
+ function useWorkspaceChats(workspaceId) {
211
+ const auth = useReplicasAuth();
212
+ const orgFetch = useOrgFetch();
213
+ return useQuery2({
214
+ queryKey: ["workspace-chats", workspaceId],
215
+ queryFn: () => orgFetch(`/v1/workspaces/${workspaceId}/chats`),
216
+ enabled: !!workspaceId,
217
+ staleTime: 6e4
218
+ }, auth.queryClient);
219
+ }
220
+ function useChatHistory(workspaceId, chatId) {
221
+ const auth = useReplicasAuth();
222
+ const orgFetch = useOrgFetch();
223
+ return useQuery2({
224
+ queryKey: ["chat-history", workspaceId, chatId],
225
+ queryFn: () => orgFetch(`/v1/workspaces/${workspaceId}/chats/${chatId}/history`),
226
+ enabled: !!workspaceId && !!chatId
227
+ }, auth.queryClient);
228
+ }
229
+ function useSendChatMessage(workspaceId, chatId) {
230
+ const auth = useReplicasAuth();
231
+ const orgFetch = useOrgFetch();
232
+ return useMutation2({
233
+ mutationFn: (body) => {
234
+ if (!workspaceId || !chatId) throw new Error("No chat selected");
235
+ return orgFetch(
236
+ `/v1/workspaces/${workspaceId}/chats/${chatId}/messages`,
237
+ { method: "POST", body }
238
+ );
239
+ }
240
+ }, auth.queryClient);
241
+ }
242
+ function useInterruptChat(workspaceId, chatId) {
243
+ const auth = useReplicasAuth();
244
+ const orgFetch = useOrgFetch();
245
+ return useMutation2({
246
+ mutationFn: () => {
247
+ if (!workspaceId || !chatId) throw new Error("No chat selected");
248
+ return orgFetch(
249
+ `/v1/workspaces/${workspaceId}/chats/${chatId}/interrupt`,
250
+ { method: "POST" }
251
+ );
252
+ }
253
+ }, auth.queryClient);
254
+ }
255
+ function useWorkspaceStatus(workspaceId, options) {
256
+ const auth = useReplicasAuth();
257
+ const orgFetch = useOrgFetch();
258
+ const includeDiffs = options?.includeDiffs ?? false;
259
+ const enabled = options?.enabled ?? true;
260
+ return useQuery2({
261
+ queryKey: ["workspace-status", workspaceId, includeDiffs],
262
+ queryFn: () => {
263
+ const url = `/v1/workspaces/${workspaceId}/status${includeDiffs ? "?includeDiffs=true" : ""}`;
264
+ return orgFetch(url);
265
+ },
266
+ enabled: !!workspaceId && enabled,
267
+ staleTime: 3e3,
268
+ retry: 2,
269
+ placeholderData: (previousData) => previousData
270
+ }, auth.queryClient);
271
+ }
272
+ function useWorkspacePreviews(workspaceId) {
273
+ const auth = useReplicasAuth();
274
+ const orgFetch = useOrgFetch();
275
+ return useQuery2({
276
+ queryKey: ["workspace-previews", workspaceId],
277
+ queryFn: () => orgFetch(`/v1/workspaces/${workspaceId}/previews`),
278
+ enabled: !!workspaceId,
279
+ staleTime: 5e3,
280
+ retry: 2
281
+ }, auth.queryClient);
282
+ }
283
+ function useWorkspaceEvents(workspaceId, enabled = true) {
284
+ const auth = useReplicasAuth();
285
+ const rawFetch = useRawOrgFetch();
286
+ const qc = auth.queryClient;
287
+ const [connected, setConnected] = useState(false);
288
+ const handleEvent = useCallback2((event) => {
289
+ if (!workspaceId) return;
290
+ const chatsKey = ["workspace-chats", workspaceId];
291
+ if (event.type === "chat.created" || event.type === "chat.updated") {
292
+ qc.setQueryData(chatsKey, (current) => {
293
+ if (!current) return { chats: [event.payload.chat] };
294
+ return { chats: upsertChat(current.chats, event.payload.chat) };
295
+ });
296
+ return;
297
+ }
298
+ if (event.type === "chat.deleted") {
299
+ qc.setQueryData(chatsKey, (current) => {
300
+ if (!current) return current;
301
+ return { chats: current.chats.filter((chat) => chat.id !== event.payload.chatId) };
302
+ });
303
+ qc.removeQueries({ queryKey: ["chat-history", workspaceId, event.payload.chatId] });
304
+ return;
305
+ }
306
+ if (event.type === "chat.turn.accepted") {
307
+ if (!event.payload.queued) {
308
+ qc.setQueryData(chatsKey, (current) => {
309
+ if (!current) return current;
310
+ return { chats: patchChat(current.chats, event.payload.chatId, { processing: true, updatedAt: event.ts }) };
311
+ });
312
+ }
313
+ return;
314
+ }
315
+ if (event.type === "chat.turn.started") {
316
+ qc.setQueryData(chatsKey, (current) => {
317
+ if (!current) return current;
318
+ return { chats: patchChat(current.chats, event.payload.chatId, { processing: true, updatedAt: event.ts }) };
319
+ });
320
+ return;
321
+ }
322
+ if (event.type === "chat.turn.completed" || event.type === "chat.interrupted") {
323
+ qc.setQueryData(chatsKey, (current) => {
324
+ if (!current) return current;
325
+ return { chats: patchChat(current.chats, event.payload.chatId, { processing: false, updatedAt: event.ts }) };
326
+ });
327
+ qc.invalidateQueries({ queryKey: ["chat-history", workspaceId, event.payload.chatId] });
328
+ return;
329
+ }
330
+ if (event.type === "chat.turn.delta") {
331
+ qc.setQueryData(
332
+ ["chat-history", workspaceId, event.payload.chatId],
333
+ (current) => {
334
+ const incomingSignature = getEventSignature(event.payload.event);
335
+ if (!current) {
336
+ return { thread_id: null, events: [event.payload.event] };
337
+ }
338
+ const hasDuplicate = current.events.some((e) => getEventSignature(e) === incomingSignature);
339
+ if (hasDuplicate) return current;
340
+ return { ...current, events: [...current.events, event.payload.event] };
341
+ }
342
+ );
343
+ return;
344
+ }
345
+ if (event.type === "repo.status.changed") {
346
+ qc.setQueryData(["workspace-status", workspaceId, false], (current) => {
347
+ if (!current) return current;
348
+ return { ...current, repoStatuses: event.payload.repos };
349
+ });
350
+ return;
351
+ }
352
+ if (event.type === "preview.changed") {
353
+ qc.setQueryData(["workspace-previews", workspaceId], {
354
+ previews: event.payload.previews
355
+ });
356
+ return;
357
+ }
358
+ }, [qc, workspaceId]);
359
+ useEffect(() => {
360
+ if (!workspaceId || !enabled) return;
361
+ const controller = new AbortController();
362
+ let cancelled = false;
363
+ const connect = async () => {
364
+ try {
365
+ const response = await rawFetch(`/v1/workspaces/${workspaceId}/events`, {
366
+ headers: { "Accept": "text/event-stream" },
367
+ signal: controller.signal
368
+ });
369
+ if (!response.ok || !response.body) {
370
+ setConnected(false);
371
+ return false;
372
+ }
373
+ setConnected(true);
374
+ const reader = response.body.getReader();
375
+ const decoder = new TextDecoder();
376
+ let buffer = "";
377
+ while (!cancelled) {
378
+ const { done, value } = await reader.read();
379
+ if (done) return false;
380
+ buffer += decoder.decode(value, { stream: true });
381
+ const chunks = buffer.split("\n\n");
382
+ buffer = chunks.pop() ?? "";
383
+ for (const chunk of chunks) {
384
+ const event = parseSseEvent(chunk);
385
+ if (event) handleEvent(event);
386
+ }
387
+ }
388
+ return false;
389
+ } catch {
390
+ setConnected(false);
391
+ return false;
392
+ }
393
+ };
394
+ const run = async () => {
395
+ while (!cancelled) {
396
+ await connect();
397
+ if (cancelled) break;
398
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
399
+ }
400
+ };
401
+ run().catch(() => setConnected(false));
402
+ return () => {
403
+ cancelled = true;
404
+ controller.abort();
405
+ setConnected(false);
406
+ };
407
+ }, [enabled, handleEvent, rawFetch, workspaceId]);
408
+ return useMemo(() => ({ connected }), [connected]);
409
+ }
410
+
411
+ // ../shared/src/hooks/useRepositories.ts
412
+ import { useQuery as useQuery3 } from "@tanstack/react-query";
413
+ function useRepositories() {
414
+ const auth = useReplicasAuth();
415
+ const orgFetch = useOrgFetch();
416
+ const orgId = auth.getOrganizationId();
417
+ return useQuery3({
418
+ queryKey: ["repositories", orgId],
419
+ queryFn: () => orgFetch("/v1/repositories"),
420
+ enabled: !!orgId,
421
+ staleTime: 2 * 60 * 1e3
422
+ }, auth.queryClient);
423
+ }
424
+ function useRepositorySets() {
425
+ const auth = useReplicasAuth();
426
+ const orgFetch = useOrgFetch();
427
+ const orgId = auth.getOrganizationId();
428
+ return useQuery3({
429
+ queryKey: ["repository-sets", orgId],
430
+ queryFn: () => orgFetch("/v1/repository-sets"),
431
+ enabled: !!orgId
432
+ }, auth.queryClient);
433
+ }
434
+
435
+ // ../shared/src/hooks/useEnvironment.ts
436
+ import { useQuery as useQuery4 } from "@tanstack/react-query";
437
+ function useEnvironmentSkills() {
438
+ const auth = useReplicasAuth();
439
+ const orgFetch = useOrgFetch();
440
+ const orgId = auth.getOrganizationId();
441
+ return useQuery4({
442
+ queryKey: ["env-skills", orgId],
443
+ queryFn: () => orgFetch("/v1/environment/skills"),
444
+ enabled: !!orgId
445
+ }, auth.queryClient);
446
+ }
447
+ function useEnvironmentVariables() {
448
+ const auth = useReplicasAuth();
449
+ const orgFetch = useOrgFetch();
450
+ const orgId = auth.getOrganizationId();
451
+ return useQuery4({
452
+ queryKey: ["env-variables", orgId],
453
+ queryFn: () => orgFetch("/v1/environment/variables"),
454
+ enabled: !!orgId
455
+ }, auth.queryClient);
456
+ }
457
+ function useEnvironmentFiles() {
458
+ const auth = useReplicasAuth();
459
+ const orgFetch = useOrgFetch();
460
+ const orgId = auth.getOrganizationId();
461
+ return useQuery4({
462
+ queryKey: ["env-files", orgId],
463
+ queryFn: () => orgFetch("/v1/environment/files"),
464
+ enabled: !!orgId
465
+ }, auth.queryClient);
466
+ }
467
+
468
+ // src/interactive/components/StatusBar.tsx
469
+ import { jsx, jsxs } from "@opentui/react/jsx-runtime";
470
+ var KEYBINDS = {
471
+ sidebar: "j/k nav \u21B5 select d del a add",
472
+ "chat-tabs": "\u2190/\u2192 switch tabs",
473
+ "chat-history": "j/k scroll",
474
+ "chat-input": "\u21B5 send \u21E5 plan/build",
475
+ info: "j/k scroll o dashboard"
476
+ };
477
+ function StatusBar({ focusPanel }) {
478
+ return /* @__PURE__ */ jsxs(
479
+ "box",
480
+ {
481
+ height: 1,
482
+ backgroundColor: "#111111",
483
+ paddingX: 1,
484
+ flexDirection: "row",
485
+ justifyContent: "space-between",
486
+ width: "100%",
487
+ children: [
488
+ /* @__PURE__ */ jsx("text", { children: /* @__PURE__ */ jsx("span", { fg: "#66bb6a", children: /* @__PURE__ */ jsx("strong", { children: "Replicas" }) }) }),
489
+ /* @__PURE__ */ jsxs("text", { children: [
490
+ /* @__PURE__ */ jsxs("span", { fg: "#555555", children: [
491
+ KEYBINDS[focusPanel],
492
+ " "
493
+ ] }),
494
+ /* @__PURE__ */ jsxs("span", { fg: "#888888", children: [
495
+ "\u21E7",
496
+ "Tab"
497
+ ] }),
498
+ /* @__PURE__ */ jsx("span", { fg: "#555555", children: " panels " }),
499
+ /* @__PURE__ */ jsx("span", { fg: "#888888", children: "^C" }),
500
+ /* @__PURE__ */ jsx("span", { fg: "#555555", children: " quit" })
501
+ ] })
502
+ ]
503
+ }
504
+ );
505
+ }
506
+
507
+ // src/interactive/components/WorkspaceSidebar.tsx
508
+ import { useState as useState2, useMemo as useMemo2, useCallback as useCallback3 } from "react";
509
+ import { useKeyboard, useTerminalDimensions } from "@opentui/react";
510
+ import { jsx as jsx2, jsxs as jsxs2 } from "@opentui/react/jsx-runtime";
511
+ function flattenGroups(groups, expandedGroups) {
512
+ const items = [];
513
+ for (const group of groups) {
514
+ if (group.id === "__ungrouped__" && group.workspaces.length === 0) continue;
515
+ const expanded = expandedGroups.has(group.id);
516
+ items.push({ type: "group", id: group.id, name: group.name, groupType: group.type, expanded });
517
+ if (expanded) {
518
+ for (const ws of group.workspaces) {
519
+ items.push({
520
+ type: "workspace",
521
+ workspaceId: ws.id,
522
+ name: ws.name,
523
+ status: ws.status,
524
+ isMock: ws.status === "preparing"
525
+ });
526
+ }
527
+ if (group.id !== "__ungrouped__") {
528
+ items.push({ type: "add", groupId: group.id, groupType: group.type });
529
+ }
530
+ }
531
+ }
532
+ return items;
533
+ }
534
+ function truncate(text, maxLen) {
535
+ return text.length > maxLen ? text.slice(0, maxLen - 1) + "\u2026" : text;
536
+ }
537
+ function WorkspaceSidebar({
538
+ groups,
539
+ selectedWorkspaceId,
540
+ mockIds,
541
+ onSelectWorkspace,
542
+ onCreateWorkspace,
543
+ onDeleteWorkspace,
544
+ focused,
545
+ loading
546
+ }) {
547
+ const { height: termHeight } = useTerminalDimensions();
548
+ const [expandedGroups, setExpandedGroups] = useState2(
549
+ () => new Set(groups.map((g) => g.id))
550
+ );
551
+ const [cursorIndex, setCursorIndex] = useState2(0);
552
+ const [scrollOffset, setScrollOffset] = useState2(0);
553
+ const [confirmDelete, setConfirmDelete] = useState2(null);
554
+ const expandedWithNew = useMemo2(() => {
555
+ const next = new Set(expandedGroups);
556
+ for (const g of groups) {
557
+ if (!next.has(g.id)) next.add(g.id);
558
+ }
559
+ return next;
560
+ }, [expandedGroups, groups]);
561
+ const items = useMemo2(() => flattenGroups(groups, expandedWithNew), [groups, expandedWithNew]);
562
+ const clampedCursor = Math.min(cursorIndex, Math.max(0, items.length - 1));
563
+ if (clampedCursor !== cursorIndex) {
564
+ setCursorIndex(clampedCursor);
565
+ }
566
+ const visibleHeight = Math.max(1, termHeight - 4);
567
+ const adjustScroll = useCallback3(
568
+ (newCursor) => {
569
+ let newOffset = scrollOffset;
570
+ if (newCursor < newOffset) {
571
+ newOffset = newCursor;
572
+ } else if (newCursor >= newOffset + visibleHeight) {
573
+ newOffset = newCursor - visibleHeight + 1;
574
+ }
575
+ setScrollOffset(newOffset);
576
+ },
577
+ [scrollOffset, visibleHeight]
578
+ );
579
+ const moveCursor = useCallback3(
580
+ (next) => {
581
+ const clamped = Math.max(0, Math.min(items.length - 1, next));
582
+ setCursorIndex(clamped);
583
+ adjustScroll(clamped);
584
+ },
585
+ [items.length, adjustScroll]
586
+ );
587
+ const handleAction = useCallback3(
588
+ (item) => {
589
+ if (item.type === "group") {
590
+ setExpandedGroups((prev) => {
591
+ const next = new Set(prev);
592
+ if (next.has(item.id)) {
593
+ next.delete(item.id);
594
+ } else {
595
+ next.add(item.id);
596
+ }
597
+ return next;
598
+ });
599
+ } else if (item.type === "workspace") {
600
+ onSelectWorkspace(item.workspaceId);
601
+ } else if (item.type === "add") {
602
+ onCreateWorkspace(item.groupId, item.groupType);
603
+ }
604
+ },
605
+ [onSelectWorkspace, onCreateWorkspace]
606
+ );
607
+ const handleDelete = useCallback3(() => {
608
+ const item = items[cursorIndex];
609
+ if (!item || item.type !== "workspace") return;
610
+ if (item.isMock) return;
611
+ if (confirmDelete === item.workspaceId) {
612
+ onDeleteWorkspace(item.workspaceId);
613
+ setConfirmDelete(null);
614
+ } else {
615
+ setConfirmDelete(item.workspaceId);
616
+ }
617
+ }, [items, cursorIndex, confirmDelete, onDeleteWorkspace]);
618
+ const moveCursorAndClearConfirm = useCallback3(
619
+ (next) => {
620
+ setConfirmDelete(null);
621
+ moveCursor(next);
622
+ },
623
+ [moveCursor]
624
+ );
625
+ useKeyboard((key) => {
626
+ if (!focused) return;
627
+ if (confirmDelete) {
628
+ if (key.name === "y") {
629
+ onDeleteWorkspace(confirmDelete);
630
+ setConfirmDelete(null);
631
+ return;
632
+ }
633
+ if (key.name === "n" || key.name === "escape") {
634
+ setConfirmDelete(null);
635
+ return;
636
+ }
637
+ return;
638
+ }
639
+ if (key.name === "up" || key.name === "k") {
640
+ moveCursorAndClearConfirm(cursorIndex - 1);
641
+ return;
642
+ }
643
+ if (key.name === "down" || key.name === "j") {
644
+ moveCursorAndClearConfirm(cursorIndex + 1);
645
+ return;
646
+ }
647
+ if (key.name === "g" && !key.shift) {
648
+ moveCursorAndClearConfirm(0);
649
+ return;
650
+ }
651
+ if (key.name === "g" && key.shift) {
652
+ moveCursorAndClearConfirm(items.length - 1);
653
+ return;
654
+ }
655
+ if (key.name === "enter" || key.name === "return" || key.name === "l") {
656
+ const item = items[cursorIndex];
657
+ if (item) handleAction(item);
658
+ return;
659
+ }
660
+ if (key.name === "h") {
661
+ const item = items[cursorIndex];
662
+ if (item?.type === "group" && item.expanded) {
663
+ handleAction(item);
664
+ }
665
+ return;
666
+ }
667
+ if (key.name === "d" || key.name === "x" || key.name === "delete") {
668
+ handleDelete();
669
+ return;
670
+ }
671
+ if (key.name === "a") {
672
+ const item = items[cursorIndex];
673
+ if (item?.type === "add") {
674
+ onCreateWorkspace(item.groupId, item.groupType);
675
+ }
676
+ return;
677
+ }
678
+ });
679
+ const handleItemClick = useCallback3(
680
+ (globalIndex) => {
681
+ setConfirmDelete(null);
682
+ setCursorIndex(globalIndex);
683
+ const item = items[globalIndex];
684
+ if (item) handleAction(item);
685
+ },
686
+ [items, handleAction]
687
+ );
688
+ const handleDeleteClick = useCallback3(
689
+ (workspaceId, e) => {
690
+ e?.preventDefault?.();
691
+ if (confirmDelete === workspaceId) {
692
+ onDeleteWorkspace(workspaceId);
693
+ setConfirmDelete(null);
694
+ } else {
695
+ setConfirmDelete(workspaceId);
696
+ }
697
+ },
698
+ [confirmDelete, onDeleteWorkspace]
699
+ );
700
+ const visibleItems = items.slice(scrollOffset, scrollOffset + visibleHeight);
701
+ if (loading) {
702
+ return /* @__PURE__ */ jsx2(
703
+ "box",
704
+ {
705
+ width: 28,
706
+ border: true,
707
+ borderStyle: "rounded",
708
+ borderColor: "#333333",
709
+ title: "Workspaces",
710
+ titleAlignment: "center",
711
+ flexDirection: "column",
712
+ backgroundColor: "#000000",
713
+ children: /* @__PURE__ */ jsx2("text", { fg: "#666666", children: "Loading..." })
714
+ }
715
+ );
716
+ }
717
+ return /* @__PURE__ */ jsxs2(
718
+ "box",
719
+ {
720
+ width: 28,
721
+ border: true,
722
+ borderStyle: "rounded",
723
+ borderColor: focused ? "#66bb6a" : "#333333",
724
+ title: "Workspaces",
725
+ titleAlignment: "center",
726
+ flexDirection: "column",
727
+ backgroundColor: "#000000",
728
+ children: [
729
+ items.length === 0 ? /* @__PURE__ */ jsx2("text", { fg: "#666666", paddingX: 1, children: "No repositories" }) : visibleItems.map((item, vi) => {
730
+ const globalIndex = scrollOffset + vi;
731
+ const isCursor = globalIndex === cursorIndex;
732
+ if (item.type === "group") {
733
+ const chevron = item.expanded ? "\u25BE" : "\u25B8";
734
+ const label = truncate(item.name, 20);
735
+ const suffix = item.groupType === "set" ? " Set" : "";
736
+ return /* @__PURE__ */ jsx2(
737
+ "box",
738
+ {
739
+ height: 1,
740
+ backgroundColor: isCursor ? "#1a1a1a" : "#151515",
741
+ paddingX: 1,
742
+ onMouseDown: () => handleItemClick(globalIndex),
743
+ children: /* @__PURE__ */ jsxs2("text", { children: [
744
+ /* @__PURE__ */ jsxs2("span", { fg: isCursor ? "#ffffff" : "#888888", children: [
745
+ chevron,
746
+ " "
747
+ ] }),
748
+ /* @__PURE__ */ jsx2("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx2("strong", { children: label }) }),
749
+ suffix && /* @__PURE__ */ jsxs2("span", { fg: "#555555", children: [
750
+ " ",
751
+ suffix
752
+ ] })
753
+ ] })
754
+ },
755
+ `g-${item.id}`
756
+ );
757
+ }
758
+ if (item.type === "workspace") {
759
+ const isSelected = item.workspaceId === selectedWorkspaceId;
760
+ const isConfirming = confirmDelete === item.workspaceId;
761
+ const dot = item.status === "active" ? "\u25CF" : item.status === "preparing" ? "\u25CC" : "\u25CB";
762
+ const dotColor = item.status === "active" ? "#66bb6a" : item.status === "preparing" ? "#ffaa00" : "#666666";
763
+ const nameColor = isSelected ? "#66bb6a" : "#cccccc";
764
+ const itemBg = isConfirming ? "#331111" : isCursor ? "#1a1a1a" : isSelected ? "#0a1a0a" : "#0a0a0a";
765
+ if (isConfirming) {
766
+ return /* @__PURE__ */ jsxs2("box", { height: 1, backgroundColor: "#331111", paddingX: 1, flexDirection: "row", gap: 1, children: [
767
+ /* @__PURE__ */ jsx2("text", { children: /* @__PURE__ */ jsx2("span", { fg: "#ff4444", children: "Delete?" }) }),
768
+ /* @__PURE__ */ jsx2("box", { onMouseDown: () => {
769
+ onDeleteWorkspace(item.workspaceId);
770
+ setConfirmDelete(null);
771
+ }, children: /* @__PURE__ */ jsx2("text", { children: /* @__PURE__ */ jsx2("span", { fg: "#66bb6a", children: "[y]" }) }) }),
772
+ /* @__PURE__ */ jsx2("box", { onMouseDown: () => setConfirmDelete(null), children: /* @__PURE__ */ jsx2("text", { children: /* @__PURE__ */ jsx2("span", { fg: "#ff4444", children: "[n]" }) }) })
773
+ ] }, `w-${item.workspaceId}`);
774
+ }
775
+ return /* @__PURE__ */ jsxs2(
776
+ "box",
777
+ {
778
+ height: 1,
779
+ backgroundColor: itemBg,
780
+ paddingX: 1,
781
+ flexDirection: "row",
782
+ justifyContent: "space-between",
783
+ onMouseDown: () => handleItemClick(globalIndex),
784
+ children: [
785
+ /* @__PURE__ */ jsxs2("text", { children: [
786
+ /* @__PURE__ */ jsxs2("span", { fg: dotColor, children: [
787
+ " ",
788
+ dot,
789
+ " "
790
+ ] }),
791
+ /* @__PURE__ */ jsx2("span", { fg: nameColor, children: truncate(item.name, 17) })
792
+ ] }),
793
+ !item.isMock && /* @__PURE__ */ jsx2("box", { onMouseDown: (e) => handleDeleteClick(item.workspaceId, e), children: /* @__PURE__ */ jsx2("text", { children: /* @__PURE__ */ jsx2("span", { fg: isCursor ? "#ff4444" : "#222222", children: "\u2717" }) }) })
794
+ ]
795
+ },
796
+ `w-${item.workspaceId}`
797
+ );
798
+ }
799
+ return /* @__PURE__ */ jsx2(
800
+ "box",
801
+ {
802
+ height: 1,
803
+ backgroundColor: isCursor ? "#1a1a1a" : "#0a0a0a",
804
+ paddingX: 1,
805
+ onMouseDown: () => handleItemClick(globalIndex),
806
+ children: /* @__PURE__ */ jsxs2("text", { children: [
807
+ /* @__PURE__ */ jsx2("span", { fg: isCursor ? "#66bb6a" : "#444444", children: " + " }),
808
+ /* @__PURE__ */ jsx2("span", { fg: isCursor ? "#888888" : "#333333", children: "New workspace" })
809
+ ] })
810
+ },
811
+ `a-${item.groupId}`
812
+ );
813
+ }),
814
+ focused && /* @__PURE__ */ jsx2("box", { height: 1, paddingX: 1, backgroundColor: "#111111", children: /* @__PURE__ */ jsxs2("text", { children: [
815
+ /* @__PURE__ */ jsx2("span", { fg: "#555555", children: "j/k:nav " }),
816
+ /* @__PURE__ */ jsx2("span", { fg: "#555555", children: "d:del " }),
817
+ /* @__PURE__ */ jsx2("span", { fg: "#555555", children: "a:add" })
818
+ ] }) })
819
+ ]
820
+ }
821
+ );
822
+ }
823
+
824
+ // src/interactive/components/ChatArea.tsx
825
+ import { useRef, useEffect as useEffect2 } from "react";
826
+ import { useKeyboard as useKeyboard2 } from "@opentui/react";
827
+
828
+ // src/interactive/components/ChatMessage.tsx
829
+ import { useState as useState3 } from "react";
830
+ import { SyntaxStyle } from "@opentui/core";
831
+
832
+ // src/interactive/components/Spinner.tsx
833
+ import "opentui-spinner/react";
834
+ import { createPulse } from "opentui-spinner";
835
+ import { jsx as jsx3, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
836
+ var thinkingColor = createPulse(["#66bb6a", "#4caf50", "#2e7d32"], 0.5);
837
+ function SpinnerLabel({ color, label }) {
838
+ return /* @__PURE__ */ jsxs3("box", { flexDirection: "row", gap: 1, children: [
839
+ /* @__PURE__ */ jsx3("spinner", { name: "dots", color: color ?? thinkingColor, interval: 80 }),
840
+ label && /* @__PURE__ */ jsx3("text", { children: /* @__PURE__ */ jsx3("span", { fg: color ?? "#66bb6a", children: label }) })
841
+ ] });
842
+ }
843
+
844
+ // src/interactive/components/ChatMessage.tsx
845
+ import { jsx as jsx4, jsxs as jsxs4 } from "@opentui/react/jsx-runtime";
846
+ var sharedSyntaxStyle = null;
847
+ function getSyntaxStyle() {
848
+ if (!sharedSyntaxStyle) {
849
+ sharedSyntaxStyle = SyntaxStyle.fromTheme([
850
+ // Markdown text styling
851
+ { scope: ["markup.heading", "markup.heading.1", "markup.heading.2", "markup.heading.3"], style: { foreground: "#66bb6a", bold: true } },
852
+ { scope: ["markup.bold", "markup.strong"], style: { foreground: "#ffffff", bold: true } },
853
+ { scope: ["markup.italic", "markup.emphasis"], style: { foreground: "#e0e0e0", italic: true } },
854
+ { scope: ["markup.link"], style: { foreground: "#7dcfff", underline: true } },
855
+ { scope: ["markup.link.url"], style: { foreground: "#7dcfff", underline: true } },
856
+ { scope: ["markup.link.text"], style: { foreground: "#66bb6a", underline: true } },
857
+ { scope: ["markup.link.label"], style: { foreground: "#66bb6a", underline: true } },
858
+ { scope: ["markup.list"], style: { foreground: "#66bb6a" } },
859
+ { scope: ["markup.quote"], style: { foreground: "#888888", italic: true } },
860
+ { scope: ["markup.raw", "markup.raw.block"], style: { foreground: "#bb9af7" } },
861
+ { scope: ["markup.raw.inline"], style: { foreground: "#bb9af7", background: "#1a1a2e" } },
862
+ // Code syntax highlighting
863
+ { scope: ["keyword", "keyword.control", "keyword.operator"], style: { foreground: "#bb9af7" } },
864
+ { scope: ["string", "string.quoted"], style: { foreground: "#9ece6a" } },
865
+ { scope: ["comment", "comment.line", "comment.block"], style: { foreground: "#555555", italic: true } },
866
+ { scope: ["constant", "constant.numeric", "constant.language"], style: { foreground: "#ff9e64" } },
867
+ { scope: ["entity.name.function", "support.function"], style: { foreground: "#7aa2f7" } },
868
+ { scope: ["entity.name.type", "support.type"], style: { foreground: "#2ac3de" } },
869
+ { scope: ["variable", "variable.other"], style: { foreground: "#c0caf5" } },
870
+ { scope: ["punctuation"], style: { foreground: "#888888" } }
871
+ ]);
872
+ }
873
+ return sharedSyntaxStyle;
874
+ }
875
+ var AGENT_LABELS = {
876
+ claude: "Claude",
877
+ codex: "Codex"
878
+ };
879
+ function truncate2(text, maxLen) {
880
+ return text.length > maxLen ? text.slice(0, maxLen - 1) + "\u2026" : text;
881
+ }
882
+ function StatusIcon({ status }) {
883
+ if (status === "completed") return /* @__PURE__ */ jsxs4("span", { fg: "#66bb6a", children: [
884
+ " ",
885
+ "\u2713"
886
+ ] });
887
+ if (status === "failed") return /* @__PURE__ */ jsxs4("span", { fg: "#ff4444", children: [
888
+ " ",
889
+ "\u2717"
890
+ ] });
891
+ if (status === "in_progress") return /* @__PURE__ */ jsxs4("span", { fg: "#ffaa00", children: [
892
+ " ",
893
+ "\u2026"
894
+ ] });
895
+ return null;
896
+ }
897
+ function ActionLine({
898
+ label,
899
+ arg,
900
+ status,
901
+ expandable,
902
+ onToggle,
903
+ expanded
904
+ }) {
905
+ return /* @__PURE__ */ jsxs4(
906
+ "box",
907
+ {
908
+ paddingX: 1,
909
+ flexDirection: "row",
910
+ justifyContent: "space-between",
911
+ onMouseDown: onToggle,
912
+ children: [
913
+ /* @__PURE__ */ jsxs4("text", { children: [
914
+ /* @__PURE__ */ jsx4("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx4("strong", { children: label }) }),
915
+ /* @__PURE__ */ jsxs4("span", { fg: "#cccccc", children: [
916
+ " ",
917
+ arg.replace(/\n/g, " ").replace(/\s+/g, " ")
918
+ ] }),
919
+ /* @__PURE__ */ jsx4(StatusIcon, { status })
920
+ ] }),
921
+ expandable && /* @__PURE__ */ jsx4("text", { children: /* @__PURE__ */ jsx4("span", { fg: "#555555", children: expanded ? "\u25BE" : "\u25B8" }) })
922
+ ]
923
+ }
924
+ );
925
+ }
926
+ function ExpandableAction({
927
+ label,
928
+ arg,
929
+ status,
930
+ children
931
+ }) {
932
+ const [expanded, setExpanded] = useState3(false);
933
+ return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", children: [
934
+ /* @__PURE__ */ jsx4(
935
+ ActionLine,
936
+ {
937
+ label,
938
+ arg,
939
+ status,
940
+ expandable: true,
941
+ expanded,
942
+ onToggle: () => setExpanded((e) => !e)
943
+ }
944
+ ),
945
+ expanded && children
946
+ ] });
947
+ }
948
+ function ChatMessage({ message, provider }) {
949
+ switch (message.type) {
950
+ case "user":
951
+ if (message.content === "Request interrupted") {
952
+ return /* @__PURE__ */ jsx4("box", { paddingX: 1, justifyContent: "center", children: /* @__PURE__ */ jsx4("text", { fg: "#555555", children: "--- Request interrupted ---" }) });
953
+ }
954
+ return /* @__PURE__ */ jsxs4(
955
+ "box",
956
+ {
957
+ flexDirection: "column",
958
+ backgroundColor: "#151515",
959
+ paddingX: 2,
960
+ paddingY: 1,
961
+ marginX: 1,
962
+ children: [
963
+ /* @__PURE__ */ jsx4("text", { children: /* @__PURE__ */ jsx4("span", { fg: "#66bb6a", children: /* @__PURE__ */ jsx4("strong", { children: "You" }) }) }),
964
+ /* @__PURE__ */ jsx4("text", { fg: "#ffffff", selectable: true, children: message.content })
965
+ ]
966
+ }
967
+ );
968
+ case "agent":
969
+ return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", paddingX: 1, children: [
970
+ /* @__PURE__ */ jsx4("text", { children: /* @__PURE__ */ jsx4("span", { fg: "#66bb6a", children: /* @__PURE__ */ jsx4("strong", { children: AGENT_LABELS[provider] }) }) }),
971
+ /* @__PURE__ */ jsx4(
972
+ "markdown",
973
+ {
974
+ content: message.content,
975
+ streaming: true,
976
+ syntaxStyle: getSyntaxStyle(),
977
+ conceal: true,
978
+ fg: "#ffffff",
979
+ bg: "#000000"
980
+ }
981
+ )
982
+ ] });
983
+ case "reasoning":
984
+ return /* @__PURE__ */ jsx4("box", { paddingX: 1, children: /* @__PURE__ */ jsx4(SpinnerLabel, { color: "#555555", label: "thinking" }) });
985
+ case "command": {
986
+ const cmdArg = truncate2(message.command, 60);
987
+ const exitSuffix = message.exitCode !== void 0 && message.exitCode !== 0 ? ` (exit ${message.exitCode})` : "";
988
+ if (!message.output) {
989
+ return /* @__PURE__ */ jsx4(ActionLine, { label: "Command", arg: cmdArg + exitSuffix, status: message.status });
990
+ }
991
+ return /* @__PURE__ */ jsx4(ExpandableAction, { label: "Command", arg: cmdArg + exitSuffix, status: message.status, children: /* @__PURE__ */ jsx4("box", { paddingLeft: 2, paddingX: 1, children: /* @__PURE__ */ jsx4("text", { fg: "#888888", selectable: true, children: message.output.length > 500 ? message.output.slice(0, 500) + "\n\u2026" : message.output }) }) });
992
+ }
993
+ case "file_change": {
994
+ const count = message.changes.length;
995
+ return /* @__PURE__ */ jsx4(ExpandableAction, { label: "Files", arg: `${count}`, status: message.status, children: /* @__PURE__ */ jsx4("box", { flexDirection: "column", paddingLeft: 2, paddingX: 1, children: message.changes.map((change, i) => /* @__PURE__ */ jsxs4("text", { children: [
996
+ /* @__PURE__ */ jsx4(
997
+ "span",
998
+ {
999
+ fg: change.kind === "add" ? "#66bb6a" : change.kind === "delete" ? "#ff4444" : "#ffaa00",
1000
+ children: change.kind === "add" ? "+" : change.kind === "delete" ? "-" : "~"
1001
+ }
1002
+ ),
1003
+ /* @__PURE__ */ jsxs4("span", { fg: "#cccccc", children: [
1004
+ " ",
1005
+ change.path
1006
+ ] })
1007
+ ] }, i)) }) });
1008
+ }
1009
+ case "tool_call": {
1010
+ const toolArg = truncate2(message.tool, 50);
1011
+ if (message.output || message.input) {
1012
+ return /* @__PURE__ */ jsx4(ExpandableAction, { label: "Tool", arg: toolArg, status: message.status, children: /* @__PURE__ */ jsxs4("box", { flexDirection: "column", paddingLeft: 2, paddingX: 1, children: [
1013
+ message.input && /* @__PURE__ */ jsx4("text", { fg: "#888888", selectable: true, children: typeof message.input === "string" ? truncate2(message.input, 300) : truncate2(JSON.stringify(message.input, null, 2), 300) }),
1014
+ message.output && /* @__PURE__ */ jsx4("text", { fg: "#888888", selectable: true, children: truncate2(message.output, 300) })
1015
+ ] }) });
1016
+ }
1017
+ return /* @__PURE__ */ jsx4(ActionLine, { label: "Tool", arg: toolArg, status: message.status });
1018
+ }
1019
+ case "web_search":
1020
+ return /* @__PURE__ */ jsx4(ActionLine, { label: "Search", arg: truncate2(message.query, 50), status: message.status });
1021
+ case "todo_list": {
1022
+ const completed = message.items.filter((i) => i.completed).length;
1023
+ const total = message.items.length;
1024
+ return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", paddingX: 1, children: [
1025
+ /* @__PURE__ */ jsx4("text", { children: /* @__PURE__ */ jsx4("span", { fg: "#888888", children: /* @__PURE__ */ jsxs4("strong", { children: [
1026
+ "Plan (",
1027
+ completed,
1028
+ "/",
1029
+ total,
1030
+ ")"
1031
+ ] }) }) }),
1032
+ message.items.map((item, i) => /* @__PURE__ */ jsxs4("text", { children: [
1033
+ /* @__PURE__ */ jsx4("span", { fg: item.completed ? "#66bb6a" : "#555555", children: item.completed ? " \u2611" : " \u2610" }),
1034
+ /* @__PURE__ */ jsxs4("span", { fg: "#ffffff", children: [
1035
+ " ",
1036
+ item.text
1037
+ ] })
1038
+ ] }, i))
1039
+ ] });
1040
+ }
1041
+ case "subagent":
1042
+ return /* @__PURE__ */ jsx4(ActionLine, { label: "Agent", arg: truncate2(message.description, 50), status: message.status });
1043
+ case "error":
1044
+ return /* @__PURE__ */ jsx4("box", { paddingX: 1, children: /* @__PURE__ */ jsx4("text", { fg: "#ff4444", selectable: true, children: message.message }) });
1045
+ case "skill":
1046
+ return /* @__PURE__ */ jsx4(ActionLine, { label: "Skill", arg: message.skillName + (message.args ? ` ${message.args}` : ""), status: message.status });
1047
+ case "patch": {
1048
+ const opCount = message.operations.length;
1049
+ return /* @__PURE__ */ jsx4(ExpandableAction, { label: "Patch", arg: `${opCount}`, children: /* @__PURE__ */ jsx4("box", { flexDirection: "column", paddingLeft: 2, paddingX: 1, children: message.operations.map((op, i) => /* @__PURE__ */ jsxs4("text", { children: [
1050
+ /* @__PURE__ */ jsx4(
1051
+ "span",
1052
+ {
1053
+ fg: op.action === "add" ? "#66bb6a" : op.action === "delete" ? "#ff4444" : "#ffaa00",
1054
+ children: op.action === "add" ? "+" : op.action === "delete" ? "-" : "~"
1055
+ }
1056
+ ),
1057
+ /* @__PURE__ */ jsxs4("span", { fg: "#cccccc", children: [
1058
+ " ",
1059
+ op.path
1060
+ ] })
1061
+ ] }, i)) }) });
1062
+ }
1063
+ default:
1064
+ return null;
1065
+ }
1066
+ }
1067
+
1068
+ // src/interactive/components/ChatArea.tsx
1069
+ import { jsx as jsx5, jsxs as jsxs5 } from "@opentui/react/jsx-runtime";
1070
+ var textareaKeyBindings = [
1071
+ { name: "return", action: "submit" },
1072
+ { name: "return", shift: true, action: "newline" },
1073
+ { name: "return", ctrl: true, action: "newline" },
1074
+ { name: "return", meta: true, action: "newline" }
1075
+ ];
1076
+ var MODE_COLORS = {
1077
+ build: { border: "#66bb6a", text: "#66bb6a" },
1078
+ plan: { border: "#d97706", text: "#d97706" }
1079
+ };
1080
+ function ChatArea({
1081
+ chats,
1082
+ selectedChatId,
1083
+ displayMessages,
1084
+ onSelectChat,
1085
+ onSendMessage,
1086
+ onFocus,
1087
+ focusPanel,
1088
+ taskMode,
1089
+ isProcessing,
1090
+ loading
1091
+ }) {
1092
+ const textareaRef = useRef(null);
1093
+ const scrollboxRef = useRef(null);
1094
+ const isAtBottomRef = useRef(true);
1095
+ const onSendMessageRef = useRef(onSendMessage);
1096
+ onSendMessageRef.current = onSendMessage;
1097
+ const inputFocused = focusPanel === "chat-input";
1098
+ const tabsFocused = focusPanel === "chat-tabs";
1099
+ const historyFocused = focusPanel === "chat-history";
1100
+ const anyFocused = inputFocused || tabsFocused || historyFocused;
1101
+ const modeColors = MODE_COLORS[taskMode];
1102
+ useEffect2(() => {
1103
+ const scrollbox = scrollboxRef.current;
1104
+ if (!scrollbox) return;
1105
+ let lastScrollHeight = 0;
1106
+ const tick = () => {
1107
+ const viewportHeight = scrollbox.viewport?.height ?? 0;
1108
+ const atBottom = scrollbox.scrollTop + viewportHeight >= scrollbox.scrollHeight - 3;
1109
+ if (atBottom) {
1110
+ isAtBottomRef.current = true;
1111
+ } else if (scrollbox.scrollHeight === lastScrollHeight) {
1112
+ isAtBottomRef.current = false;
1113
+ }
1114
+ if (isAtBottomRef.current && scrollbox.scrollHeight > lastScrollHeight) {
1115
+ scrollbox.scrollTo(scrollbox.scrollHeight);
1116
+ }
1117
+ lastScrollHeight = scrollbox.scrollHeight;
1118
+ };
1119
+ const interval = setInterval(tick, 100);
1120
+ return () => clearInterval(interval);
1121
+ }, []);
1122
+ useEffect2(() => {
1123
+ const textarea = textareaRef.current;
1124
+ if (!textarea) return;
1125
+ textarea.onSubmit = () => {
1126
+ const message = textarea.plainText.trim();
1127
+ if (message) {
1128
+ isAtBottomRef.current = true;
1129
+ onSendMessageRef.current(message);
1130
+ textarea.setText("");
1131
+ }
1132
+ };
1133
+ return () => {
1134
+ textarea.onSubmit = void 0;
1135
+ };
1136
+ }, []);
1137
+ useKeyboard2((key) => {
1138
+ if (!historyFocused) return;
1139
+ const scrollbox = scrollboxRef.current;
1140
+ if (!scrollbox) return;
1141
+ if (key.name === "j" || key.name === "down") {
1142
+ scrollbox.scrollBy(3);
1143
+ return;
1144
+ }
1145
+ if (key.name === "k" || key.name === "up") {
1146
+ scrollbox.scrollBy(-3);
1147
+ return;
1148
+ }
1149
+ });
1150
+ useKeyboard2((key) => {
1151
+ if (!tabsFocused || chats.length <= 1 || !selectedChatId) return;
1152
+ if (key.name === "left" || key.name === "right" || key.name === "tab" && !key.shift) {
1153
+ const currentIdx = chats.findIndex((c) => c.id === selectedChatId);
1154
+ if (currentIdx >= 0) {
1155
+ const nextIdx = key.name === "left" ? (currentIdx - 1 + chats.length) % chats.length : (currentIdx + 1) % chats.length;
1156
+ onSelectChat(chats[nextIdx].id);
1157
+ }
1158
+ }
1159
+ });
1160
+ return /* @__PURE__ */ jsxs5(
1161
+ "box",
1162
+ {
1163
+ flexGrow: 1,
1164
+ border: true,
1165
+ borderStyle: "rounded",
1166
+ borderColor: anyFocused ? "#66bb6a" : "#333333",
1167
+ title: "Chat",
1168
+ titleAlignment: "center",
1169
+ flexDirection: "column",
1170
+ backgroundColor: "#000000",
1171
+ children: [
1172
+ chats.length > 1 && /* @__PURE__ */ jsx5(
1173
+ "box",
1174
+ {
1175
+ height: 3,
1176
+ paddingX: 1,
1177
+ marginX: 1,
1178
+ border: true,
1179
+ borderColor: tabsFocused ? "#66bb6a" : "#333333",
1180
+ backgroundColor: "#000000",
1181
+ flexDirection: "row",
1182
+ gap: 1,
1183
+ alignItems: "center",
1184
+ onMouseDown: () => onFocus("chat-tabs"),
1185
+ children: chats.map((chat) => {
1186
+ const isSelected = chat.id === selectedChatId;
1187
+ const label = `${chat.title}${chat.processing ? " ..." : ""}`;
1188
+ return /* @__PURE__ */ jsx5(
1189
+ "box",
1190
+ {
1191
+ backgroundColor: isSelected ? "#1a1a1a" : void 0,
1192
+ paddingX: 1,
1193
+ onMouseDown: () => {
1194
+ onSelectChat(chat.id);
1195
+ onFocus("chat-tabs");
1196
+ },
1197
+ children: /* @__PURE__ */ jsx5("text", { children: /* @__PURE__ */ jsx5("span", { fg: isSelected ? modeColors.text : "#666666", children: label }) })
1198
+ },
1199
+ chat.id
1200
+ );
1201
+ })
1202
+ }
1203
+ ),
1204
+ /* @__PURE__ */ jsx5(
1205
+ "scrollbox",
1206
+ {
1207
+ ref: scrollboxRef,
1208
+ flexGrow: 1,
1209
+ focused: historyFocused,
1210
+ paddingX: 1,
1211
+ paddingY: 1,
1212
+ onMouseDown: () => onFocus("chat-history"),
1213
+ children: loading ? /* @__PURE__ */ jsx5("box", { paddingX: 1, children: /* @__PURE__ */ jsx5("text", { fg: "#666666", children: "Loading messages..." }) }) : displayMessages.length === 0 ? /* @__PURE__ */ jsx5("box", { paddingX: 1, children: /* @__PURE__ */ jsx5("text", { fg: "#666666", children: "No messages yet. Send a message to start chatting." }) }) : (() => {
1214
+ const activeChat = chats.find((c) => c.id === selectedChatId);
1215
+ const provider = activeChat?.provider ?? "claude";
1216
+ const primaryTypes = /* @__PURE__ */ new Set(["user", "agent", "todo_list"]);
1217
+ return displayMessages.map((msg, i) => {
1218
+ const prev = i > 0 ? displayMessages[i - 1] : null;
1219
+ const needsSpacing = prev && (primaryTypes.has(msg.type) || primaryTypes.has(prev.type));
1220
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
1221
+ needsSpacing && /* @__PURE__ */ jsx5("box", { height: 1 }),
1222
+ /* @__PURE__ */ jsx5(ChatMessage, { message: msg, provider })
1223
+ ] }, msg.id || `msg-${i}`);
1224
+ });
1225
+ })()
1226
+ }
1227
+ ),
1228
+ /* @__PURE__ */ jsxs5(
1229
+ "box",
1230
+ {
1231
+ flexDirection: "column",
1232
+ flexShrink: 0,
1233
+ border: true,
1234
+ borderColor: inputFocused ? modeColors.border : "#333333",
1235
+ backgroundColor: "#111111",
1236
+ onMouseDown: () => onFocus("chat-input"),
1237
+ children: [
1238
+ /* @__PURE__ */ jsx5("box", { height: 6, paddingX: 1, children: /* @__PURE__ */ jsx5(
1239
+ "textarea",
1240
+ {
1241
+ ref: textareaRef,
1242
+ keyBindings: textareaKeyBindings,
1243
+ placeholder: "Type a message... (Enter to send, Shift+Enter for newline)",
1244
+ focused: inputFocused,
1245
+ backgroundColor: "#111111",
1246
+ textColor: "#ffffff",
1247
+ focusedBackgroundColor: "#111111",
1248
+ placeholderColor: "#555555",
1249
+ width: "100%",
1250
+ height: 5
1251
+ }
1252
+ ) }),
1253
+ /* @__PURE__ */ jsxs5(
1254
+ "box",
1255
+ {
1256
+ height: 1,
1257
+ paddingX: 1,
1258
+ flexDirection: "row",
1259
+ justifyContent: "space-between",
1260
+ children: [
1261
+ /* @__PURE__ */ jsx5("text", { children: /* @__PURE__ */ jsx5("span", { fg: modeColors.text, children: taskMode === "build" ? "Build" : "Plan" }) }),
1262
+ isProcessing && /* @__PURE__ */ jsx5(SpinnerLabel, { color: "#ffaa00", label: "thinking" })
1263
+ ]
1264
+ }
1265
+ )
1266
+ ]
1267
+ }
1268
+ )
1269
+ ]
1270
+ }
1271
+ );
1272
+ }
1273
+
1274
+ // src/interactive/components/WorkspaceInfo.tsx
1275
+ import React4 from "react";
1276
+ import open from "open";
1277
+ import { Fragment, jsx as jsx6, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
1278
+ var WEB_APP_URL = process.env.REPLICAS_WEB_URL || "https://replicas.dev";
1279
+ var AUTH_METHOD_LABELS = {
1280
+ oauth: "OAuth",
1281
+ api_key: "API Key",
1282
+ bedrock: "Bedrock",
1283
+ none: null
1284
+ };
1285
+ function StatusDot({ status }) {
1286
+ if (status === true || status === "yes") {
1287
+ return /* @__PURE__ */ jsx6("span", { fg: "#66bb6a", children: "\u2713" });
1288
+ }
1289
+ if (status === false || status === "no") {
1290
+ return /* @__PURE__ */ jsx6("span", { fg: "#ff4444", children: "\u2717" });
1291
+ }
1292
+ return /* @__PURE__ */ jsx6("span", { fg: "#555555", children: "-" });
1293
+ }
1294
+ function SectionLabel({ title }) {
1295
+ return /* @__PURE__ */ jsx6("box", { backgroundColor: "#151515", paddingX: 1, children: /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#666666", children: /* @__PURE__ */ jsx6("strong", { children: title.toUpperCase() }) }) }) });
1296
+ }
1297
+ function Section({ title, children }) {
1298
+ return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", marginBottom: 1, children: [
1299
+ /* @__PURE__ */ jsx6(SectionLabel, { title }),
1300
+ /* @__PURE__ */ jsx6("box", { flexDirection: "column", backgroundColor: "#0a0a0a", children })
1301
+ ] });
1302
+ }
1303
+ function CardItem({ label, status }) {
1304
+ return /* @__PURE__ */ jsxs6(
1305
+ "box",
1306
+ {
1307
+ flexDirection: "row",
1308
+ justifyContent: "space-between",
1309
+ paddingX: 1,
1310
+ backgroundColor: "#111111",
1311
+ marginBottom: 0,
1312
+ children: [
1313
+ /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#cccccc", children: label }) }),
1314
+ /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6(StatusDot, { status }) })
1315
+ ]
1316
+ }
1317
+ );
1318
+ }
1319
+ function DetailList({
1320
+ expected,
1321
+ actual
1322
+ }) {
1323
+ const uniqueExpected = Array.from(new Set(expected)).sort((a, b) => a.localeCompare(b));
1324
+ const actualSet = new Set(actual);
1325
+ const rows = uniqueExpected.length > 0 ? uniqueExpected : Array.from(actualSet).sort((a, b) => a.localeCompare(b));
1326
+ if (rows.length === 0) return null;
1327
+ return /* @__PURE__ */ jsx6(Fragment, { children: rows.map((item, i) => /* @__PURE__ */ jsx6(CardItem, { label: item, status: actualSet.has(item) }, i)) });
1328
+ }
1329
+ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, envConfig, previews }) {
1330
+ const borderColor = focused ? "#66bb6a" : "#333333";
1331
+ if (!workspaceName) {
1332
+ return /* @__PURE__ */ jsx6(
1333
+ "box",
1334
+ {
1335
+ width: 30,
1336
+ border: true,
1337
+ borderStyle: "rounded",
1338
+ borderColor,
1339
+ title: "Info",
1340
+ titleAlignment: "center",
1341
+ flexDirection: "column",
1342
+ paddingX: 1,
1343
+ backgroundColor: "#000000",
1344
+ children: /* @__PURE__ */ jsx6("text", { fg: "#666666", children: "Select a workspace" })
1345
+ }
1346
+ );
1347
+ }
1348
+ if (loading || !status) {
1349
+ return /* @__PURE__ */ jsx6(
1350
+ "box",
1351
+ {
1352
+ width: 30,
1353
+ border: true,
1354
+ borderStyle: "rounded",
1355
+ borderColor,
1356
+ title: "Info",
1357
+ titleAlignment: "center",
1358
+ flexDirection: "column",
1359
+ paddingX: 1,
1360
+ backgroundColor: "#000000",
1361
+ children: /* @__PURE__ */ jsx6("text", { fg: "#666666", children: "Loading..." })
1362
+ }
1363
+ );
1364
+ }
1365
+ const statusColor = status.status === "active" ? "#66bb6a" : status.status === "sleeping" ? "#ffaa00" : "#ff4444";
1366
+ const env = status.environmentDetails;
1367
+ const expectedSkills = (envConfig.skills?.environment_skills ?? []).map((s) => s.source);
1368
+ const expectedGlobalVars = (envConfig.variables?.environment_variables ?? []).filter((v) => v.scope_type === "global").map((v) => v.key);
1369
+ const expectedGlobalFiles = (envConfig.files?.environment_files ?? []).filter((f) => f.scope_type === "global").map((f) => f.path);
1370
+ return /* @__PURE__ */ jsx6(
1371
+ "box",
1372
+ {
1373
+ width: 30,
1374
+ border: true,
1375
+ borderStyle: "rounded",
1376
+ borderColor,
1377
+ title: "Info",
1378
+ titleAlignment: "center",
1379
+ flexDirection: "column",
1380
+ backgroundColor: "#000000",
1381
+ children: /* @__PURE__ */ jsxs6("scrollbox", { focused, flexGrow: 1, children: [
1382
+ /* @__PURE__ */ jsx6("box", { backgroundColor: "#111111", paddingX: 1, marginBottom: 1, children: /* @__PURE__ */ jsxs6("box", { flexDirection: "column", children: [
1383
+ /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx6("strong", { children: workspaceName }) }) }),
1384
+ /* @__PURE__ */ jsxs6("text", { children: [
1385
+ /* @__PURE__ */ jsxs6("span", { fg: statusColor, children: [
1386
+ "\u25CF",
1387
+ " ",
1388
+ status.status
1389
+ ] }),
1390
+ env && /* @__PURE__ */ jsxs6("span", { fg: "#555555", children: [
1391
+ " ",
1392
+ "\u2502",
1393
+ " ",
1394
+ env.engineVersion
1395
+ ] })
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
+ )
1413
+ ] }) }),
1414
+ (status.isClaudeProcessing || status.isCodexProcessing) && /* @__PURE__ */ jsxs6("box", { backgroundColor: "#1a1500", paddingX: 1, marginX: 1, marginBottom: 1, children: [
1415
+ status.isClaudeProcessing && /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsxs6("span", { fg: "#ffaa00", children: [
1416
+ "\u25C6",
1417
+ " Claude thinking..."
1418
+ ] }) }),
1419
+ status.isCodexProcessing && /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsxs6("span", { fg: "#ffaa00", children: [
1420
+ "\u25C6",
1421
+ " Codex thinking..."
1422
+ ] }) })
1423
+ ] }),
1424
+ env && /* @__PURE__ */ jsxs6(Section, { title: "Agents", children: [
1425
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
1426
+ /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#cccccc", children: "Claude" }) }),
1427
+ /* @__PURE__ */ jsx6("text", { children: AUTH_METHOD_LABELS[env.claudeAuthMethod] ? /* @__PURE__ */ jsx6("span", { fg: "#66bb6a", children: AUTH_METHOD_LABELS[env.claudeAuthMethod] }) : /* @__PURE__ */ jsx6("span", { fg: "#ff4444", children: "\u2717" }) })
1428
+ ] }),
1429
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
1430
+ /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#cccccc", children: "Codex" }) }),
1431
+ /* @__PURE__ */ jsx6("text", { children: AUTH_METHOD_LABELS[env.codexAuthMethod] ? /* @__PURE__ */ jsx6("span", { fg: "#66bb6a", children: AUTH_METHOD_LABELS[env.codexAuthMethod] }) : /* @__PURE__ */ jsx6("span", { fg: "#ff4444", children: "\u2717" }) })
1432
+ ] })
1433
+ ] }),
1434
+ env && /* @__PURE__ */ jsxs6(Section, { title: "Integrations", children: [
1435
+ /* @__PURE__ */ jsx6(CardItem, { label: "GitHub", status: env.githubAccessConfigured }),
1436
+ /* @__PURE__ */ jsx6(CardItem, { label: "Slack", status: env.slackAccessConfigured }),
1437
+ /* @__PURE__ */ jsx6(CardItem, { label: "Linear", status: env.linearAccessConfigured })
1438
+ ] }),
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)) }),
1475
+ env && /* @__PURE__ */ jsxs6(Section, { title: "Hooks", children: [
1476
+ /* @__PURE__ */ jsx6(CardItem, { label: "Global warm", status: env.globalWarmHookCompleted.status }),
1477
+ env.repositories.map((repo, i) => /* @__PURE__ */ jsxs6(React4.Fragment, { children: [
1478
+ /* @__PURE__ */ jsx6("box", { backgroundColor: "#111111", paddingX: 1, children: /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx6("strong", { children: repo.repositoryName }) }) }) }),
1479
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
1480
+ /* @__PURE__ */ jsxs6("text", { children: [
1481
+ /* @__PURE__ */ jsxs6("span", { fg: "#555555", children: [
1482
+ "\u251C",
1483
+ " "
1484
+ ] }),
1485
+ /* @__PURE__ */ jsx6("span", { fg: "#cccccc", children: "warm" })
1486
+ ] }),
1487
+ /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6(StatusDot, { status: repo.warmHookCompleted }) })
1488
+ ] }),
1489
+ /* @__PURE__ */ jsxs6("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
1490
+ /* @__PURE__ */ jsxs6("text", { children: [
1491
+ /* @__PURE__ */ jsxs6("span", { fg: "#555555", children: [
1492
+ "\u2514",
1493
+ " "
1494
+ ] }),
1495
+ /* @__PURE__ */ jsx6("span", { fg: "#cccccc", children: "start" })
1496
+ ] }),
1497
+ /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6(StatusDot, { status: repo.startHookCompleted }) })
1498
+ ] })
1499
+ ] }, i))
1500
+ ] }),
1501
+ env && (expectedSkills.length > 0 || env.skillsInstalled.length > 0) && /* @__PURE__ */ jsx6(Section, { title: "Skills", children: /* @__PURE__ */ jsx6(DetailList, { expected: expectedSkills, actual: env.skillsInstalled }) }),
1502
+ env && (expectedGlobalVars.length > 0 || env.envVarsSet.length > 0) && /* @__PURE__ */ jsx6(Section, { title: "Env Vars", children: /* @__PURE__ */ jsx6(DetailList, { expected: expectedGlobalVars, actual: env.envVarsSet }) }),
1503
+ env && (expectedGlobalFiles.length > 0 || env.filesUploaded.length > 0) && /* @__PURE__ */ jsx6(Section, { title: "Files", children: /* @__PURE__ */ jsx6(DetailList, { expected: expectedGlobalFiles, actual: env.filesUploaded }) })
1504
+ ] })
1505
+ }
1506
+ );
1507
+ }
1508
+
1509
+ // src/interactive/components/Toast.tsx
1510
+ import { useTerminalDimensions as useTerminalDimensions2 } from "@opentui/react";
1511
+
1512
+ // src/interactive/toast-context.tsx
1513
+ import { createContext as createContext2, useContext as useContext2, useState as useState4, useCallback as useCallback4, useRef as useRef2 } from "react";
1514
+ import { jsx as jsx7 } from "@opentui/react/jsx-runtime";
1515
+ var ToastContext = createContext2(null);
1516
+ function useToast() {
1517
+ const ctx = useContext2(ToastContext);
1518
+ if (!ctx) throw new Error("useToast must be used within ToastProvider");
1519
+ return ctx;
1520
+ }
1521
+ function ToastProvider({ children }) {
1522
+ const [current, setCurrent] = useState4(null);
1523
+ const timeoutRef = useRef2(null);
1524
+ const show = useCallback4((options) => {
1525
+ const { message, variant = "info", duration = 3e3 } = options;
1526
+ setCurrent({ message, variant });
1527
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
1528
+ timeoutRef.current = setTimeout(() => {
1529
+ setCurrent(null);
1530
+ }, duration);
1531
+ }, []);
1532
+ const error = useCallback4((err) => {
1533
+ const message = err instanceof Error ? err.message : "An error occurred";
1534
+ show({ message, variant: "error", duration: 5e3 });
1535
+ }, [show]);
1536
+ return /* @__PURE__ */ jsx7(ToastContext.Provider, { value: { current, show, error }, children });
1537
+ }
1538
+
1539
+ // src/interactive/components/Toast.tsx
1540
+ import { jsx as jsx8 } from "@opentui/react/jsx-runtime";
1541
+ var VARIANT_COLORS = {
1542
+ info: { border: "#66bb6a", bg: "#0a1a0a" },
1543
+ success: { border: "#66bb6a", bg: "#0a1a0a" },
1544
+ warning: { border: "#ffaa00", bg: "#1a1a0a" },
1545
+ error: { border: "#ff4444", bg: "#1a0a0a" }
1546
+ };
1547
+ function Toast() {
1548
+ const { current } = useToast();
1549
+ const { width } = useTerminalDimensions2();
1550
+ if (!current) return null;
1551
+ const colors = VARIANT_COLORS[current.variant] ?? VARIANT_COLORS.info;
1552
+ const toastWidth = Math.min(60, width - 4);
1553
+ return /* @__PURE__ */ jsx8(
1554
+ "box",
1555
+ {
1556
+ position: "absolute",
1557
+ bottom: 2,
1558
+ right: 2,
1559
+ width: toastWidth,
1560
+ paddingX: 2,
1561
+ paddingY: 1,
1562
+ backgroundColor: colors.bg,
1563
+ borderColor: colors.border,
1564
+ border: true,
1565
+ borderStyle: "rounded",
1566
+ zIndex: 100,
1567
+ children: /* @__PURE__ */ jsx8("text", { fg: "#ffffff", selectable: true, children: current.message })
1568
+ }
1569
+ );
1570
+ }
1571
+
1572
+ // src/interactive/App.tsx
1573
+ import { jsx as jsx9, jsxs as jsxs7 } from "@opentui/react/jsx-runtime";
1574
+ var FOCUS_ORDER = ["sidebar", "chat-tabs", "chat-history", "chat-input", "info"];
1575
+ var MOCK_CHATS = [
1576
+ { id: "mock-claude", provider: "claude", title: "Claude Code", createdAt: "", updatedAt: "", processing: false },
1577
+ { id: "mock-codex", provider: "codex", title: "Codex", createdAt: "", updatedAt: "", processing: false }
1578
+ ];
1579
+ var MONOLITH_URL = process.env.REPLICAS_MONOLITH_URL || "https://api.replicas.dev";
1580
+ var queryClient = new QueryClient({
1581
+ defaultOptions: {
1582
+ queries: {
1583
+ retry: 1,
1584
+ refetchOnWindowFocus: false
1585
+ }
1586
+ }
1587
+ });
1588
+ var authValue = {
1589
+ getAccessToken: () => getValidToken(),
1590
+ getOrganizationId: () => getOrganizationId(),
1591
+ monolithUrl: MONOLITH_URL,
1592
+ queryClient
1593
+ };
1594
+ function App() {
1595
+ return /* @__PURE__ */ jsx9(ReplicasAuthProvider, { value: authValue, children: /* @__PURE__ */ jsxs7(ToastProvider, { children: [
1596
+ /* @__PURE__ */ jsx9(AppInner, {}),
1597
+ /* @__PURE__ */ jsx9(Toast, {})
1598
+ ] }) });
1599
+ }
1600
+ function AppInner() {
1601
+ const renderer = useRenderer();
1602
+ const { width } = useTerminalDimensions3();
1603
+ const toast = useToast();
1604
+ useEffect3(() => {
1605
+ const handler = (selection) => {
1606
+ const text = selection.getSelectedText();
1607
+ if (text) {
1608
+ renderer.copyToClipboardOSC52(text);
1609
+ toast.show({ message: "Copied to clipboard", variant: "info", duration: 1500 });
1610
+ }
1611
+ };
1612
+ renderer.on("selection", handler);
1613
+ return () => {
1614
+ renderer.off("selection", handler);
1615
+ };
1616
+ }, [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([]);
1622
+ const mockToRealRef = useRef3(/* @__PURE__ */ new Map());
1623
+ const mockGroupRef = useRef3(/* @__PURE__ */ new Map());
1624
+ const [mockMessages, setMockMessages] = useState5([]);
1625
+ const [mockThinking, setMockThinking] = useState5(false);
1626
+ const pendingMessageRef = useRef3(/* @__PURE__ */ new Map());
1627
+ const mockIds = useMemo3(() => new Set(mockWorkspaces.map((m) => m.id)), [mockWorkspaces]);
1628
+ const isMockSelected = selectedWorkspaceId ? mockIds.has(selectedWorkspaceId) : false;
1629
+ const { data: workspacesData, isLoading: loadingWorkspaces } = useWorkspaces(1, 100, "organization");
1630
+ const { data: reposData } = useRepositories();
1631
+ const { data: setsData } = useRepositorySets();
1632
+ const { data: envSkills } = useEnvironmentSkills();
1633
+ const { data: envVariables } = useEnvironmentVariables();
1634
+ const { data: envFiles } = useEnvironmentFiles();
1635
+ const { data: statusData, isLoading: loadingStatus } = useWorkspaceStatus(
1636
+ isMockSelected ? null : selectedWorkspaceId,
1637
+ { includeDiffs: true }
1638
+ );
1639
+ const { data: chatsData } = useWorkspaceChats(isMockSelected ? null : selectedWorkspaceId);
1640
+ const { data: historyData, isLoading: loadingMessages } = useChatHistory(
1641
+ isMockSelected ? null : selectedWorkspaceId,
1642
+ selectedChatId?.startsWith("mock-") ? null : selectedChatId
1643
+ );
1644
+ const { data: previewsData } = useWorkspacePreviews(isMockSelected ? null : selectedWorkspaceId);
1645
+ const { connected: sseConnected } = useWorkspaceEvents(
1646
+ isMockSelected ? null : selectedWorkspaceId
1647
+ );
1648
+ const createWorkspaceMutation = useCreateWorkspace();
1649
+ const deleteWorkspaceMutation = useDeleteWorkspace();
1650
+ const generateNameMutation = useGenerateWorkspaceName();
1651
+ const sendMessageMutation = useSendChatMessage(selectedWorkspaceId, selectedChatId);
1652
+ const interruptMutation = useInterruptChat(selectedWorkspaceId, selectedChatId);
1653
+ const workspaces = workspacesData?.workspaces ?? [];
1654
+ const repositories = reposData?.repositories ?? [];
1655
+ const repositorySets = setsData?.repository_sets ?? [];
1656
+ const chats = isMockSelected ? MOCK_CHATS : chatsData?.chats ?? [];
1657
+ const previews = previewsData?.previews ?? [];
1658
+ const allWorkspaces = useMemo3(() => [...mockWorkspaces, ...workspaces], [mockWorkspaces, workspaces]);
1659
+ const groups = useMemo3(
1660
+ () => buildGroups(allWorkspaces, workspacesData, repositories, repositorySets, mockGroupRef.current),
1661
+ [allWorkspaces, workspacesData, repositories, repositorySets]
1662
+ );
1663
+ const selectedWorkspace = allWorkspaces.find((ws) => ws.id === selectedWorkspaceId) ?? null;
1664
+ const selectedChat = chats.find((c) => c.id === selectedChatId) ?? null;
1665
+ const isProcessing = mockThinking || (selectedChat?.processing ?? false);
1666
+ const displayMessages = useMemo3(() => {
1667
+ if (isMockSelected) return mockMessages;
1668
+ const rawEvents = historyData?.events ?? [];
1669
+ const events = rawEvents.filter(isAgentBackendEvent);
1670
+ if (events.length === 0) return [];
1671
+ const provider = selectedChat?.provider ?? "claude";
1672
+ const parsed = parseAgentEvents(events, provider);
1673
+ return filterDisplayMessages(parsed, provider);
1674
+ }, [isMockSelected, mockMessages, historyData, selectedChat?.provider]);
1675
+ const showInfoPanel = width >= 100;
1676
+ const showWorkspacePanel = width >= 60;
1677
+ useEffect3(() => {
1678
+ if (!selectedWorkspaceId && workspaces.length > 0) {
1679
+ setSelectedWorkspaceId(workspaces[0].id);
1680
+ }
1681
+ }, [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
+ useEffect3(() => {
1695
+ if (mockWorkspaces.length === 0) return;
1696
+ const realIds = new Set(workspaces.map((w) => w.id));
1697
+ let changed = false;
1698
+ const remaining = mockWorkspaces.filter((mock) => {
1699
+ const realId = mockToRealRef.current.get(mock.id);
1700
+ if (realId && realIds.has(realId)) {
1701
+ if (selectedWorkspaceId === mock.id) {
1702
+ setSelectedWorkspaceId(realId);
1703
+ }
1704
+ const pending = pendingMessageRef.current.get(mock.id);
1705
+ if (pending) {
1706
+ pendingMessageRef.current.delete(mock.id);
1707
+ pendingMessageRef.current.set(realId, pending);
1708
+ }
1709
+ mockToRealRef.current.delete(mock.id);
1710
+ mockGroupRef.current.delete(mock.id);
1711
+ setMockMessages([]);
1712
+ setMockThinking(false);
1713
+ changed = true;
1714
+ return false;
1715
+ }
1716
+ return true;
1717
+ });
1718
+ if (changed) setMockWorkspaces(remaining);
1719
+ }, [workspaces, mockWorkspaces, selectedWorkspaceId]);
1720
+ useEffect3(() => {
1721
+ if (!selectedWorkspaceId || isMockSelected) return;
1722
+ if (!selectedChatId || selectedChatId.startsWith("mock-")) return;
1723
+ if (statusData?.status !== "active") return;
1724
+ const pending = pendingMessageRef.current.get(selectedWorkspaceId);
1725
+ if (!pending) return;
1726
+ pendingMessageRef.current.delete(selectedWorkspaceId);
1727
+ const noBranches = statusData?.repoStatuses && statusData.repoStatuses.length > 0 && statusData.repoStatuses.every((r) => r.currentBranch === r.defaultBranch);
1728
+ if (noBranches) {
1729
+ generateNameMutation.mutateAsync({ workspaceId: selectedWorkspaceId, prompt: pending }).catch(() => {
1730
+ });
1731
+ }
1732
+ sendMessageMutation.mutate({ message: pending });
1733
+ }, [selectedWorkspaceId, selectedChatId, isMockSelected, statusData?.status]);
1734
+ useEffect3(() => {
1735
+ if (mockWorkspaces.length === 0) return;
1736
+ const interval = setInterval(() => {
1737
+ queryClient.invalidateQueries({ queryKey: ["workspaces"] });
1738
+ }, 5e3);
1739
+ return () => clearInterval(interval);
1740
+ }, [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) => {
1783
+ setSelectedWorkspaceId(workspaceId);
1784
+ setFocusPanel("chat-input");
1785
+ }, []);
1786
+ const handleFocus = useCallback5((panel) => {
1787
+ setFocusPanel(panel);
1788
+ }, []);
1789
+ const handleCreateWorkspace = useCallback5(
1790
+ async (groupId, groupType) => {
1791
+ const orgId = getOrganizationId() ?? "";
1792
+ const mock = createMockWorkspaceRecord(orgId);
1793
+ mockGroupRef.current.set(mock.id, groupId);
1794
+ setMockWorkspaces((prev) => [mock, ...prev]);
1795
+ setSelectedWorkspaceId(mock.id);
1796
+ try {
1797
+ const request = groupType === "set" ? { repository_set_id: groupId.replace(/^set:/, ""), name: mock.name, placeholder: true } : { repository_ids: [groupId], name: mock.name, placeholder: true };
1798
+ const result = await createWorkspaceMutation.mutateAsync(request);
1799
+ mockToRealRef.current.set(mock.id, result.workspace.id);
1800
+ } catch (err) {
1801
+ mockGroupRef.current.delete(mock.id);
1802
+ setMockWorkspaces((prev) => prev.filter((m) => m.id !== mock.id));
1803
+ toast.error(err);
1804
+ }
1805
+ },
1806
+ [createWorkspaceMutation, toast]
1807
+ );
1808
+ const handleDeleteWorkspace = useCallback5(
1809
+ async (workspaceId) => {
1810
+ if (mockIds.has(workspaceId)) {
1811
+ mockGroupRef.current.delete(workspaceId);
1812
+ mockToRealRef.current.delete(workspaceId);
1813
+ setMockWorkspaces((prev) => prev.filter((m) => m.id !== workspaceId));
1814
+ if (selectedWorkspaceId === workspaceId) setSelectedWorkspaceId(null);
1815
+ return;
1816
+ }
1817
+ try {
1818
+ await deleteWorkspaceMutation.mutateAsync(workspaceId);
1819
+ if (selectedWorkspaceId === workspaceId) setSelectedWorkspaceId(null);
1820
+ toast.show({ message: "Workspace deleted", variant: "info" });
1821
+ } catch (err) {
1822
+ toast.error(err);
1823
+ }
1824
+ },
1825
+ [mockIds, selectedWorkspaceId, deleteWorkspaceMutation]
1826
+ );
1827
+ const handleSelectChat = useCallback5((chatId) => {
1828
+ setSelectedChatId(chatId);
1829
+ }, []);
1830
+ const handleSendMessage = useCallback5(
1831
+ async (message) => {
1832
+ if (!selectedWorkspaceId || !selectedChatId) return;
1833
+ if (isMockSelected) {
1834
+ const { message: userMsg } = createUserMessage(message);
1835
+ setMockMessages((prev) => [...prev, userMsg]);
1836
+ pendingMessageRef.current.set(selectedWorkspaceId, message);
1837
+ setMockThinking(true);
1838
+ return;
1839
+ }
1840
+ sendMessageMutation.mutate({
1841
+ message,
1842
+ permissionMode: taskMode === "plan" ? "read" : void 0
1843
+ });
1844
+ },
1845
+ [selectedWorkspaceId, selectedChatId, isMockSelected, sendMessageMutation, taskMode]
1846
+ );
1847
+ return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", width: "100%", height: "100%", backgroundColor: "#000000", children: [
1848
+ /* @__PURE__ */ jsxs7("box", { flexDirection: "row", flexGrow: 1, backgroundColor: "#000000", children: [
1849
+ showWorkspacePanel && /* @__PURE__ */ jsx9(
1850
+ WorkspaceSidebar,
1851
+ {
1852
+ groups,
1853
+ selectedWorkspaceId,
1854
+ mockIds,
1855
+ onSelectWorkspace: handleSelectWorkspace,
1856
+ onCreateWorkspace: handleCreateWorkspace,
1857
+ onDeleteWorkspace: handleDeleteWorkspace,
1858
+ focused: focusPanel === "sidebar",
1859
+ loading: loadingWorkspaces
1860
+ }
1861
+ ),
1862
+ selectedWorkspaceId ? /* @__PURE__ */ jsx9(
1863
+ ChatArea,
1864
+ {
1865
+ chats,
1866
+ selectedChatId,
1867
+ displayMessages,
1868
+ onSelectChat: handleSelectChat,
1869
+ onSendMessage: handleSendMessage,
1870
+ onFocus: handleFocus,
1871
+ focusPanel,
1872
+ taskMode,
1873
+ isProcessing,
1874
+ loading: loadingMessages
1875
+ }
1876
+ ) : /* @__PURE__ */ jsx9(
1877
+ "box",
1878
+ {
1879
+ flexGrow: 1,
1880
+ border: true,
1881
+ borderStyle: "rounded",
1882
+ borderColor: "#333333",
1883
+ backgroundColor: "#000000",
1884
+ justifyContent: "center",
1885
+ alignItems: "center",
1886
+ children: /* @__PURE__ */ jsx9("text", { fg: "#555555", children: "Create a workspace to begin building!" })
1887
+ }
1888
+ ),
1889
+ showInfoPanel && /* @__PURE__ */ jsx9(
1890
+ WorkspaceInfo,
1891
+ {
1892
+ status: statusData ?? null,
1893
+ workspaceName: selectedWorkspace?.name ?? null,
1894
+ workspaceId: selectedWorkspaceId,
1895
+ focused: focusPanel === "info",
1896
+ loading: loadingStatus,
1897
+ envConfig: {
1898
+ skills: envSkills ?? null,
1899
+ variables: envVariables ?? null,
1900
+ files: envFiles ?? null
1901
+ },
1902
+ previews
1903
+ }
1904
+ )
1905
+ ] }),
1906
+ /* @__PURE__ */ jsx9(
1907
+ StatusBar,
1908
+ {
1909
+ focusPanel
1910
+ }
1911
+ )
1912
+ ] });
1913
+ }
1914
+
1915
+ // src/interactive/index.tsx
1916
+ import { jsx as jsx10 } from "@opentui/react/jsx-runtime";
1917
+ async function launchInteractive() {
1918
+ const renderer = await createCliRenderer({
1919
+ exitOnCtrlC: false
1920
+ });
1921
+ createRoot(renderer).render(/* @__PURE__ */ jsx10(App, {}));
1922
+ }
1923
+ export {
1924
+ launchInteractive
1925
+ };