vibewithme 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js ADDED
@@ -0,0 +1,1550 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createMessageId
4
+ } from "./chunk-CSC6MWPD.js";
5
+ import {
6
+ Doc
7
+ } from "./chunk-2JHYHMND.js";
8
+ import "./chunk-MO4EEYFW.js";
9
+
10
+ // src/bin.tsx
11
+ import { render } from "ink";
12
+ import { Command } from "commander";
13
+ import path3 from "path";
14
+
15
+ // ../tui/dist/index.js
16
+ import { useEffect as useEffect4, createContext, useContext } from "react";
17
+ import { Box as Box15, useStdout } from "ink";
18
+ import { Box, Text } from "ink";
19
+
20
+ // ../../node_modules/zustand/esm/vanilla.mjs
21
+ var createStoreImpl = (createState) => {
22
+ let state;
23
+ const listeners = /* @__PURE__ */ new Set();
24
+ const setState = (partial, replace) => {
25
+ const nextState = typeof partial === "function" ? partial(state) : partial;
26
+ if (!Object.is(nextState, state)) {
27
+ const previousState = state;
28
+ state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
29
+ listeners.forEach((listener) => listener(state, previousState));
30
+ }
31
+ };
32
+ const getState = () => state;
33
+ const getInitialState = () => initialState;
34
+ const subscribe = (listener) => {
35
+ listeners.add(listener);
36
+ return () => listeners.delete(listener);
37
+ };
38
+ const api = { setState, getState, getInitialState, subscribe };
39
+ const initialState = state = createState(setState, getState, api);
40
+ return api;
41
+ };
42
+ var createStore = ((createState) => createState ? createStoreImpl(createState) : createStoreImpl);
43
+
44
+ // ../../node_modules/zustand/esm/react.mjs
45
+ import React from "react";
46
+ var identity = (arg) => arg;
47
+ function useStore(api, selector = identity) {
48
+ const slice = React.useSyncExternalStore(
49
+ api.subscribe,
50
+ React.useCallback(() => selector(api.getState()), [api, selector]),
51
+ React.useCallback(() => selector(api.getInitialState()), [api, selector])
52
+ );
53
+ React.useDebugValue(slice);
54
+ return slice;
55
+ }
56
+ var createImpl = (createState) => {
57
+ const api = createStore(createState);
58
+ const useBoundStore = (selector) => useStore(api, selector);
59
+ Object.assign(useBoundStore, api);
60
+ return useBoundStore;
61
+ };
62
+ var create = ((createState) => createState ? createImpl(createState) : createImpl);
63
+
64
+ // ../tui/dist/index.js
65
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
66
+ import { Box as Box2, Text as Text2 } from "ink";
67
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
68
+ import { useState as useState3 } from "react";
69
+ import { Box as Box7, Text as Text7 } from "ink";
70
+ import { Box as Box3, Text as Text3, useInput } from "ink";
71
+ import { useCallback } from "react";
72
+ import fs from "fs";
73
+ import path from "path";
74
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
75
+ import { useState } from "react";
76
+ import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
77
+ import { useCallback as useCallback2, useEffect } from "react";
78
+ import fs2 from "fs";
79
+ import path2 from "path";
80
+ import os from "os";
81
+ import crypto from "crypto";
82
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
83
+ import { useState as useState2 } from "react";
84
+ import { Box as Box5, Text as Text5, useInput as useInput3 } from "ink";
85
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
86
+ import { Box as Box6, Text as Text6, useInput as useInput4 } from "ink";
87
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
88
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
89
+ import { Box as Box9, Text as Text9 } from "ink";
90
+ import React4 from "react";
91
+ import { Box as Box8, Text as Text8, useInput as useInput5 } from "ink";
92
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
93
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
94
+ import { Box as Box12, Text as Text12 } from "ink";
95
+ import { Box as Box10, Text as Text10 } from "ink";
96
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
97
+ import React5 from "react";
98
+ import { Box as Box11, Text as Text11, useInput as useInput6 } from "ink";
99
+ import { useCallback as useCallback3, useRef } from "react";
100
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
101
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
102
+ import { useState as useState4, useMemo } from "react";
103
+ import { Box as Box13, Text as Text13, useInput as useInput7 } from "ink";
104
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
105
+ import { useState as useState5, useEffect as useEffect2 } from "react";
106
+ import { Box as Box14, Text as Text14, useInput as useInput8 } from "ink";
107
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
108
+ import { useInput as useInput9 } from "ink";
109
+ import { useEffect as useEffect3, useRef as useRef2, useCallback as useCallback4, useState as useState6 } from "react";
110
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
111
+ var theme = {
112
+ colors: {
113
+ bg: "#1e1e2e",
114
+ surface: "#313244",
115
+ border: "#45475a",
116
+ borderFocus: "#89b4fa",
117
+ text: "#cdd6f4",
118
+ textDim: "#6c7086",
119
+ textBright: "#f5f5f5",
120
+ primary: "#89b4fa",
121
+ secondary: "#a6e3a1",
122
+ accent: "#f5c2e7",
123
+ warning: "#f9e2af",
124
+ error: "#f38ba8",
125
+ success: "#a6e3a1",
126
+ aiBubble: "#b4befe",
127
+ userBubble: "#89b4fa",
128
+ systemText: "#6c7086"
129
+ },
130
+ borders: {
131
+ single: "single",
132
+ double: "double",
133
+ round: "round",
134
+ bold: "bold"
135
+ },
136
+ icons: {
137
+ file: " ",
138
+ folder: " ",
139
+ folderOpen: " ",
140
+ chevronRight: " >",
141
+ chevronDown: " v",
142
+ online: " *",
143
+ offline: " o",
144
+ idle: " ~",
145
+ ai: " #",
146
+ lock: " !",
147
+ check: " +",
148
+ cross: " x",
149
+ spinner: " -"
150
+ }
151
+ };
152
+ var useWorkspaceStore = create((set, get) => ({
153
+ projectPath: process.cwd(),
154
+ projectName: "",
155
+ fileTree: [],
156
+ activeFile: null,
157
+ fileContent: null,
158
+ openFiles: [],
159
+ expandedDirs: /* @__PURE__ */ new Set(),
160
+ selectedIndex: 0,
161
+ setProjectPath: (path32) => set({ projectPath: path32 }),
162
+ setProjectName: (name) => set({ projectName: name }),
163
+ setFileTree: (tree) => set({ fileTree: tree }),
164
+ setActiveFile: (path32) => set({ activeFile: path32 }),
165
+ setFileContent: (content) => set({ fileContent: content }),
166
+ openFile: (path32) => {
167
+ const { openFiles } = get();
168
+ if (!openFiles.includes(path32)) {
169
+ set({ openFiles: [...openFiles, path32], activeFile: path32 });
170
+ } else {
171
+ set({ activeFile: path32 });
172
+ }
173
+ },
174
+ closeFile: (path32) => {
175
+ const { openFiles, activeFile } = get();
176
+ const next = openFiles.filter((f) => f !== path32);
177
+ set({
178
+ openFiles: next,
179
+ activeFile: activeFile === path32 ? next[next.length - 1] ?? null : activeFile
180
+ });
181
+ },
182
+ toggleDir: (path32) => {
183
+ const { expandedDirs } = get();
184
+ const next = new Set(expandedDirs);
185
+ if (next.has(path32)) {
186
+ next.delete(path32);
187
+ } else {
188
+ next.add(path32);
189
+ }
190
+ set({ expandedDirs: next });
191
+ },
192
+ setSelectedIndex: (index) => set({ selectedIndex: index })
193
+ }));
194
+ function Header() {
195
+ const projectName = useWorkspaceStore((s) => s.projectName);
196
+ const { connected, remoteUsers, roomId } = useCollab();
197
+ return /* @__PURE__ */ jsxs(
198
+ Box,
199
+ {
200
+ borderStyle: "single",
201
+ borderColor: theme.colors.border,
202
+ paddingX: 1,
203
+ justifyContent: "space-between",
204
+ children: [
205
+ /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
206
+ /* @__PURE__ */ jsx(Text, { bold: true, color: theme.colors.accent, children: "vibewithme" }),
207
+ /* @__PURE__ */ jsx(Text, { color: theme.colors.textDim, children: "|" }),
208
+ /* @__PURE__ */ jsx(Text, { color: theme.colors.text, children: projectName || "no project" }),
209
+ roomId && /* @__PURE__ */ jsxs(Fragment, { children: [
210
+ /* @__PURE__ */ jsx(Text, { color: theme.colors.textDim, children: "|" }),
211
+ /* @__PURE__ */ jsxs(Text, { color: theme.colors.textDim, children: [
212
+ "room: ",
213
+ roomId
214
+ ] })
215
+ ] })
216
+ ] }),
217
+ /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
218
+ /* @__PURE__ */ jsx(Text, { color: theme.colors.success, children: "* you" }),
219
+ remoteUsers.map((user) => {
220
+ const isActive = Date.now() - user.lastActive < 3e4;
221
+ return /* @__PURE__ */ jsxs(Text, { color: isActive ? user.color : theme.colors.textDim, children: [
222
+ isActive ? "*" : "~",
223
+ " ",
224
+ user.name
225
+ ] }, user.id);
226
+ }),
227
+ roomId && /* @__PURE__ */ jsx(Text, { color: connected ? theme.colors.success : theme.colors.error, children: connected ? "[live]" : "[offline]" }),
228
+ /* @__PURE__ */ jsx(Text, { color: theme.colors.textDim, children: "v0.1.0" })
229
+ ] })
230
+ ]
231
+ }
232
+ );
233
+ }
234
+ var panelOrder = ["sidebar", "workspace", "chat"];
235
+ var useUIStore = create((set, get) => ({
236
+ focusedPanel: "chat",
237
+ sidebarTab: "files",
238
+ modal: null,
239
+ sidebarWidth: 24,
240
+ chatWidth: 32,
241
+ terminalWidth: 120,
242
+ terminalHeight: 40,
243
+ setFocusedPanel: (panel) => set({ focusedPanel: panel }),
244
+ setSidebarTab: (tab) => set({ sidebarTab: tab }),
245
+ setModal: (modal) => set({ modal }),
246
+ setTerminalSize: (width, height) => set({ terminalWidth: width, terminalHeight: height }),
247
+ focusNext: () => {
248
+ const { focusedPanel } = get();
249
+ const idx = panelOrder.indexOf(focusedPanel);
250
+ const next = panelOrder[(idx + 1) % panelOrder.length];
251
+ set({ focusedPanel: next });
252
+ },
253
+ focusPrev: () => {
254
+ const { focusedPanel } = get();
255
+ const idx = panelOrder.indexOf(focusedPanel);
256
+ const prev = panelOrder[(idx - 1 + panelOrder.length) % panelOrder.length];
257
+ set({ focusedPanel: prev });
258
+ }
259
+ }));
260
+ var useChatStore = create((set, get) => ({
261
+ messages: [],
262
+ inputValue: "",
263
+ isAgentRunning: false,
264
+ agentStreamText: "",
265
+ addMessage: (msg) => {
266
+ const message = {
267
+ ...msg,
268
+ id: createMessageId(),
269
+ timestamp: Date.now()
270
+ };
271
+ set({ messages: [...get().messages, message] });
272
+ },
273
+ setInputValue: (value) => set({ inputValue: value }),
274
+ setAgentRunning: (running) => set({ isAgentRunning: running }),
275
+ setAgentStreamText: (text) => set({ agentStreamText: text }),
276
+ appendAgentStreamText: (text) => set({ agentStreamText: get().agentStreamText + text })
277
+ }));
278
+ function Footer() {
279
+ const focusedPanel = useUIStore((s) => s.focusedPanel);
280
+ const isAgentRunning = useChatStore((s) => s.isAgentRunning);
281
+ return /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, justifyContent: "space-between", children: [
282
+ /* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
283
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.colors.textDim, children: [
284
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: focusedPanel === "sidebar" ? theme.colors.primary : void 0, children: "^1" }),
285
+ " ",
286
+ "Files"
287
+ ] }),
288
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.colors.textDim, children: [
289
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: focusedPanel === "workspace" ? theme.colors.primary : void 0, children: "^2" }),
290
+ " ",
291
+ "Editor"
292
+ ] }),
293
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.colors.textDim, children: [
294
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: focusedPanel === "chat" ? theme.colors.primary : void 0, children: "^3" }),
295
+ " ",
296
+ "Chat"
297
+ ] }),
298
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.colors.textDim, children: [
299
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: "^P" }),
300
+ " Palette"
301
+ ] }),
302
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.colors.textDim, children: [
303
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: "@ai" }),
304
+ " Claude"
305
+ ] })
306
+ ] }),
307
+ /* @__PURE__ */ jsx2(Box2, { gap: 1, children: isAgentRunning && /* @__PURE__ */ jsx2(Text2, { color: theme.colors.aiBubble, children: "Claude is working..." }) })
308
+ ] });
309
+ }
310
+ var IGNORED = /* @__PURE__ */ new Set([
311
+ "node_modules",
312
+ ".git",
313
+ ".next",
314
+ ".turbo",
315
+ "dist",
316
+ ".DS_Store",
317
+ "coverage",
318
+ ".env",
319
+ ".env.local"
320
+ ]);
321
+ function readDir(dirPath, depth = 0) {
322
+ if (depth > 3) return [];
323
+ try {
324
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
325
+ const result = [];
326
+ const dirs = entries.filter((e) => e.isDirectory() && !IGNORED.has(e.name) && !e.name.startsWith(".")).sort((a, b) => a.name.localeCompare(b.name));
327
+ const files = entries.filter((e) => e.isFile() && !IGNORED.has(e.name)).sort((a, b) => a.name.localeCompare(b.name));
328
+ for (const dir of dirs) {
329
+ const fullPath = path.join(dirPath, dir.name);
330
+ result.push({
331
+ name: dir.name,
332
+ path: fullPath,
333
+ type: "directory",
334
+ children: readDir(fullPath, depth + 1)
335
+ });
336
+ }
337
+ for (const file of files) {
338
+ const fullPath = path.join(dirPath, file.name);
339
+ result.push({
340
+ name: file.name,
341
+ path: fullPath,
342
+ type: "file",
343
+ extension: path.extname(file.name).slice(1)
344
+ });
345
+ }
346
+ return result;
347
+ } catch {
348
+ return [];
349
+ }
350
+ }
351
+ function useFileSystem() {
352
+ const { projectPath, setFileTree, setFileContent, setProjectName } = useWorkspaceStore();
353
+ const loadProject = useCallback(
354
+ (projectDir) => {
355
+ const dir = projectDir ?? projectPath;
356
+ if (!dir) return;
357
+ useWorkspaceStore.getState().setProjectPath(dir);
358
+ const name = path.basename(dir);
359
+ setProjectName(name);
360
+ const tree = readDir(dir);
361
+ setFileTree(tree);
362
+ },
363
+ [projectPath, setFileTree, setProjectName]
364
+ );
365
+ const readFile = useCallback(
366
+ (filePath) => {
367
+ try {
368
+ const content = fs.readFileSync(filePath, "utf-8");
369
+ setFileContent(content);
370
+ } catch {
371
+ setFileContent("Error: Could not read file");
372
+ }
373
+ },
374
+ [setFileContent]
375
+ );
376
+ return { loadProject, readFile };
377
+ }
378
+ function flattenTree(entries, expandedDirs, depth = 0) {
379
+ const result = [];
380
+ for (const entry of entries) {
381
+ result.push({ entry, depth });
382
+ if (entry.type === "directory" && entry.children && expandedDirs.has(entry.path)) {
383
+ result.push(...flattenTree(entry.children, expandedDirs, depth + 1));
384
+ }
385
+ }
386
+ return result;
387
+ }
388
+ function FileTree({ isFocused }) {
389
+ const {
390
+ fileTree,
391
+ expandedDirs,
392
+ selectedIndex,
393
+ toggleDir,
394
+ setSelectedIndex
395
+ } = useWorkspaceStore();
396
+ const { openFile } = useWorkspaceStore();
397
+ const { readFile } = useFileSystem();
398
+ const flatItems = flattenTree(fileTree, expandedDirs);
399
+ useInput(
400
+ (input, key) => {
401
+ if (!isFocused) return;
402
+ if (key.upArrow || input === "k") {
403
+ setSelectedIndex(Math.max(0, selectedIndex - 1));
404
+ return;
405
+ }
406
+ if (key.downArrow || input === "j") {
407
+ setSelectedIndex(Math.min(flatItems.length - 1, selectedIndex + 1));
408
+ return;
409
+ }
410
+ if (key.return || input === "l") {
411
+ const item = flatItems[selectedIndex];
412
+ if (!item) return;
413
+ if (item.entry.type === "directory") {
414
+ toggleDir(item.entry.path);
415
+ } else {
416
+ openFile(item.entry.path);
417
+ readFile(item.entry.path);
418
+ }
419
+ return;
420
+ }
421
+ if (input === "h") {
422
+ const item = flatItems[selectedIndex];
423
+ if (item?.entry.type === "directory" && expandedDirs.has(item.entry.path)) {
424
+ toggleDir(item.entry.path);
425
+ }
426
+ return;
427
+ }
428
+ }
429
+ );
430
+ if (flatItems.length === 0) {
431
+ return /* @__PURE__ */ jsx3(Text3, { color: theme.colors.textDim, children: "Empty directory" });
432
+ }
433
+ const maxVisible = 20;
434
+ const startIdx = Math.max(0, selectedIndex - Math.floor(maxVisible / 2));
435
+ const visibleItems = flatItems.slice(startIdx, startIdx + maxVisible);
436
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
437
+ visibleItems.map(({ entry, depth }, i) => {
438
+ const globalIdx = startIdx + i;
439
+ const isSelected = globalIdx === selectedIndex;
440
+ const isDir = entry.type === "directory";
441
+ const isExpanded = expandedDirs.has(entry.path);
442
+ const icon = isDir ? isExpanded ? "v " : "> " : " ";
443
+ const indent = " ".repeat(depth);
444
+ return /* @__PURE__ */ jsx3(Text3, { children: /* @__PURE__ */ jsxs3(
445
+ Text3,
446
+ {
447
+ color: isSelected ? theme.colors.textBright : isDir ? theme.colors.primary : theme.colors.text,
448
+ bold: isSelected,
449
+ inverse: isSelected && isFocused,
450
+ children: [
451
+ indent,
452
+ icon,
453
+ entry.name
454
+ ]
455
+ }
456
+ ) }, entry.path);
457
+ }),
458
+ flatItems.length > maxVisible && /* @__PURE__ */ jsxs3(Text3, { color: theme.colors.textDim, children: [
459
+ "(",
460
+ flatItems.length,
461
+ " items)"
462
+ ] })
463
+ ] });
464
+ }
465
+ var useSecretsStore = create((set) => ({
466
+ secrets: [],
467
+ loaded: false,
468
+ setSecrets: (secrets) => set({ secrets }),
469
+ setLoaded: (loaded) => set({ loaded })
470
+ }));
471
+ var SECRETS_DIR = path2.join(os.homedir(), ".vibewithme");
472
+ var SECRETS_FILE = path2.join(SECRETS_DIR, "secrets.enc");
473
+ var KEY_FILE = path2.join(SECRETS_DIR, "secrets.key");
474
+ function getEncryptionKey() {
475
+ if (fs2.existsSync(KEY_FILE)) {
476
+ return Buffer.from(fs2.readFileSync(KEY_FILE, "utf-8"), "hex");
477
+ }
478
+ const key = crypto.randomBytes(32);
479
+ if (!fs2.existsSync(SECRETS_DIR)) {
480
+ fs2.mkdirSync(SECRETS_DIR, { recursive: true, mode: 448 });
481
+ }
482
+ fs2.writeFileSync(KEY_FILE, key.toString("hex"), { mode: 384 });
483
+ return key;
484
+ }
485
+ function encrypt(data) {
486
+ const key = getEncryptionKey();
487
+ const iv = crypto.randomBytes(16);
488
+ const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
489
+ let encrypted = cipher.update(data, "utf-8", "hex");
490
+ encrypted += cipher.final("hex");
491
+ const authTag = cipher.getAuthTag();
492
+ return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
493
+ }
494
+ function decrypt(data) {
495
+ const key = getEncryptionKey();
496
+ const [ivHex, authTagHex, encrypted] = data.split(":");
497
+ const iv = Buffer.from(ivHex, "hex");
498
+ const authTag = Buffer.from(authTagHex, "hex");
499
+ const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
500
+ decipher.setAuthTag(authTag);
501
+ let decrypted = decipher.update(encrypted, "hex", "utf-8");
502
+ decrypted += decipher.final("utf-8");
503
+ return decrypted;
504
+ }
505
+ function loadSecretsFromDisk() {
506
+ try {
507
+ if (!fs2.existsSync(SECRETS_FILE)) return [];
508
+ const raw = fs2.readFileSync(SECRETS_FILE, "utf-8");
509
+ const json = decrypt(raw);
510
+ return JSON.parse(json);
511
+ } catch {
512
+ return [];
513
+ }
514
+ }
515
+ function saveSecretsToDisk(secrets) {
516
+ if (!fs2.existsSync(SECRETS_DIR)) {
517
+ fs2.mkdirSync(SECRETS_DIR, { recursive: true, mode: 448 });
518
+ }
519
+ const json = JSON.stringify(secrets);
520
+ const encrypted = encrypt(json);
521
+ fs2.writeFileSync(SECRETS_FILE, encrypted, { mode: 384 });
522
+ }
523
+ function useSecrets() {
524
+ const { secrets, loaded, setSecrets, setLoaded } = useSecretsStore();
525
+ useEffect(() => {
526
+ if (!loaded) {
527
+ const stored = loadSecretsFromDisk();
528
+ setSecrets(stored);
529
+ setLoaded(true);
530
+ }
531
+ }, [loaded, setSecrets, setLoaded]);
532
+ const addSecret = useCallback2(
533
+ (key, value) => {
534
+ const now = Date.now();
535
+ const existing = secrets.findIndex((s) => s.key === key);
536
+ let next;
537
+ if (existing >= 0) {
538
+ next = [...secrets];
539
+ next[existing] = { key, value, createdAt: next[existing].createdAt, updatedAt: now };
540
+ } else {
541
+ next = [...secrets, { key, value, createdAt: now, updatedAt: now }];
542
+ }
543
+ setSecrets(next);
544
+ saveSecretsToDisk(next);
545
+ },
546
+ [secrets, setSecrets]
547
+ );
548
+ const removeSecret = useCallback2(
549
+ (key) => {
550
+ const next = secrets.filter((s) => s.key !== key);
551
+ setSecrets(next);
552
+ saveSecretsToDisk(next);
553
+ },
554
+ [secrets, setSecrets]
555
+ );
556
+ const getSecretValue = useCallback2(
557
+ (key) => {
558
+ return secrets.find((s) => s.key === key)?.value;
559
+ },
560
+ [secrets]
561
+ );
562
+ const getEnvVars = useCallback2(() => {
563
+ const env = {};
564
+ for (const s of secrets) {
565
+ env[s.key] = s.value;
566
+ }
567
+ return env;
568
+ }, [secrets]);
569
+ const toEnvFile = useCallback2(() => {
570
+ return secrets.map((s) => `${s.key}=${s.value}`).join("\n") + "\n";
571
+ }, [secrets]);
572
+ return { secrets, addSecret, removeSecret, getSecretValue, getEnvVars, toEnvFile };
573
+ }
574
+ function SecretsList({ isFocused, onAddNew }) {
575
+ const { secrets, removeSecret } = useSecrets();
576
+ const [selectedIndex, setSelectedIndex] = useState(0);
577
+ const [revealedKey, setRevealedKey] = useState(null);
578
+ useInput2(
579
+ (input, key) => {
580
+ if (!isFocused) return;
581
+ if (key.upArrow || input === "k") {
582
+ setSelectedIndex((i) => Math.max(0, i - 1));
583
+ }
584
+ if (key.downArrow || input === "j") {
585
+ setSelectedIndex((i) => Math.min(secrets.length - 1, i + 1));
586
+ }
587
+ if (input === "a") {
588
+ onAddNew();
589
+ }
590
+ if (input === "r" && secrets[selectedIndex]) {
591
+ setRevealedKey(
592
+ revealedKey === secrets[selectedIndex].key ? null : secrets[selectedIndex].key
593
+ );
594
+ }
595
+ if (input === "d" && secrets[selectedIndex]) {
596
+ removeSecret(secrets[selectedIndex].key);
597
+ setSelectedIndex((i) => Math.max(0, i - 1));
598
+ }
599
+ }
600
+ );
601
+ if (secrets.length === 0) {
602
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", gap: 1, children: [
603
+ /* @__PURE__ */ jsx4(Text4, { color: theme.colors.textDim, children: "No secrets stored." }),
604
+ /* @__PURE__ */ jsxs4(Text4, { color: theme.colors.textDim, children: [
605
+ "Press ",
606
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: theme.colors.primary, children: "a" }),
607
+ " to add one."
608
+ ] })
609
+ ] });
610
+ }
611
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
612
+ secrets.map((secret, i) => {
613
+ const isSelected = i === selectedIndex;
614
+ const isRevealed = revealedKey === secret.key;
615
+ const masked = secret.value.slice(0, 3) + "..." + secret.value.slice(-3);
616
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
617
+ /* @__PURE__ */ jsxs4(
618
+ Text4,
619
+ {
620
+ inverse: isSelected && isFocused,
621
+ bold: isSelected,
622
+ color: isSelected ? theme.colors.textBright : theme.colors.text,
623
+ children: [
624
+ "! ",
625
+ secret.key
626
+ ]
627
+ }
628
+ ),
629
+ /* @__PURE__ */ jsxs4(Text4, { color: theme.colors.textDim, children: [
630
+ " ",
631
+ isRevealed ? secret.value : masked
632
+ ] })
633
+ ] }, secret.key);
634
+ }),
635
+ /* @__PURE__ */ jsx4(Text4, { color: theme.colors.textDim, children: "a:add r:reveal d:delete" })
636
+ ] });
637
+ }
638
+ function SecretForm({ onClose }) {
639
+ const { addSecret } = useSecrets();
640
+ const [field, setField] = useState2("key");
641
+ const [key, setKey] = useState2("");
642
+ const [value, setValue] = useState2("");
643
+ useInput3((input, inkKey) => {
644
+ if (inkKey.escape) {
645
+ onClose();
646
+ return;
647
+ }
648
+ if (inkKey.return) {
649
+ if (field === "key" && key.trim()) {
650
+ setField("value");
651
+ } else if (field === "value" && key.trim() && value.trim()) {
652
+ addSecret(key.trim(), value.trim());
653
+ onClose();
654
+ }
655
+ return;
656
+ }
657
+ if (inkKey.backspace || inkKey.delete) {
658
+ if (field === "key") setKey((s) => s.slice(0, -1));
659
+ else setValue((s) => s.slice(0, -1));
660
+ return;
661
+ }
662
+ if (input && !inkKey.ctrl && !inkKey.meta) {
663
+ if (field === "key") setKey((s) => s + input.toUpperCase());
664
+ else setValue((s) => s + input);
665
+ }
666
+ });
667
+ return /* @__PURE__ */ jsxs5(
668
+ Box5,
669
+ {
670
+ flexDirection: "column",
671
+ borderStyle: "round",
672
+ borderColor: theme.colors.accent,
673
+ paddingX: 2,
674
+ paddingY: 1,
675
+ children: [
676
+ /* @__PURE__ */ jsx5(Text5, { bold: true, color: theme.colors.accent, children: "Add Secret" }),
677
+ /* @__PURE__ */ jsxs5(Box5, { children: [
678
+ /* @__PURE__ */ jsxs5(Text5, { color: field === "key" ? theme.colors.primary : theme.colors.textDim, children: [
679
+ "Key:",
680
+ " "
681
+ ] }),
682
+ /* @__PURE__ */ jsxs5(Text5, { color: theme.colors.text, children: [
683
+ key,
684
+ field === "key" && /* @__PURE__ */ jsx5(Text5, { inverse: true, children: " " })
685
+ ] })
686
+ ] }),
687
+ /* @__PURE__ */ jsxs5(Box5, { children: [
688
+ /* @__PURE__ */ jsxs5(Text5, { color: field === "value" ? theme.colors.primary : theme.colors.textDim, children: [
689
+ "Val:",
690
+ " "
691
+ ] }),
692
+ /* @__PURE__ */ jsxs5(Text5, { color: theme.colors.text, children: [
693
+ value,
694
+ field === "value" && /* @__PURE__ */ jsx5(Text5, { inverse: true, children: " " })
695
+ ] })
696
+ ] }),
697
+ /* @__PURE__ */ jsx5(Text5, { color: theme.colors.textDim, children: "Enter: next/save | Esc: cancel" })
698
+ ]
699
+ }
700
+ );
701
+ }
702
+ function TeamPanel({ isFocused, onInvite }) {
703
+ const { remoteUsers, connected, roomId } = useCollab();
704
+ useInput4(
705
+ (input) => {
706
+ if (!isFocused) return;
707
+ if (input === "i") {
708
+ onInvite();
709
+ }
710
+ }
711
+ );
712
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
713
+ roomId ? /* @__PURE__ */ jsx6(Text6, { color: connected ? theme.colors.success : theme.colors.error, children: connected ? "* Connected" : "x Disconnected" }) : /* @__PURE__ */ jsx6(Text6, { color: theme.colors.textDim, children: "No room joined" }),
714
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
715
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.colors.success, children: "* you" }),
716
+ /* @__PURE__ */ jsx6(Text6, { color: theme.colors.textDim, children: " (owner)" })
717
+ ] }),
718
+ remoteUsers.map((user) => {
719
+ const isActive = Date.now() - user.lastActive < 3e4;
720
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
721
+ /* @__PURE__ */ jsxs6(Text6, { color: isActive ? user.color : theme.colors.textDim, children: [
722
+ isActive ? "*" : "~",
723
+ " ",
724
+ user.name
725
+ ] }),
726
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.colors.textDim, children: [
727
+ " ",
728
+ "(",
729
+ user.panel || "idle",
730
+ ")"
731
+ ] })
732
+ ] }, user.id);
733
+ }),
734
+ remoteUsers.length === 0 && roomId && /* @__PURE__ */ jsx6(Text6, { color: theme.colors.textDim, children: "No one else here yet." }),
735
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: theme.colors.textDim, children: [
736
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.colors.primary, children: "i" }),
737
+ ":invite"
738
+ ] }) })
739
+ ] });
740
+ }
741
+ var tabs = [
742
+ { id: "files", label: "Files" },
743
+ { id: "secrets", label: "Secrets" },
744
+ { id: "team", label: "Team" }
745
+ ];
746
+ function Sidebar() {
747
+ const { focusedPanel, sidebarTab, setSidebarTab, setModal } = useUIStore();
748
+ const isFocused = focusedPanel === "sidebar";
749
+ const [showSecretForm, setShowSecretForm] = useState3(false);
750
+ return /* @__PURE__ */ jsxs7(
751
+ Box7,
752
+ {
753
+ flexDirection: "column",
754
+ borderStyle: "single",
755
+ borderColor: isFocused ? theme.colors.borderFocus : theme.colors.border,
756
+ width: 24,
757
+ children: [
758
+ /* @__PURE__ */ jsx7(Box7, { paddingX: 1, gap: 1, children: tabs.map((tab) => /* @__PURE__ */ jsx7(
759
+ Text7,
760
+ {
761
+ bold: sidebarTab === tab.id,
762
+ color: sidebarTab === tab.id ? theme.colors.primary : theme.colors.textDim,
763
+ children: tab.label
764
+ },
765
+ tab.id
766
+ )) }),
767
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
768
+ sidebarTab === "files" && /* @__PURE__ */ jsx7(FileTree, { isFocused }),
769
+ sidebarTab === "secrets" && !showSecretForm && /* @__PURE__ */ jsx7(
770
+ SecretsList,
771
+ {
772
+ isFocused,
773
+ onAddNew: () => setShowSecretForm(true)
774
+ }
775
+ ),
776
+ sidebarTab === "secrets" && showSecretForm && /* @__PURE__ */ jsx7(SecretForm, { onClose: () => setShowSecretForm(false) }),
777
+ sidebarTab === "team" && /* @__PURE__ */ jsx7(
778
+ TeamPanel,
779
+ {
780
+ isFocused,
781
+ onInvite: () => setModal("invite")
782
+ }
783
+ )
784
+ ] })
785
+ ]
786
+ }
787
+ );
788
+ }
789
+ function FileViewer({ isFocused }) {
790
+ const { fileContent, activeFile } = useWorkspaceStore();
791
+ const [scrollOffset, setScrollOffset] = React4.useState(0);
792
+ React4.useEffect(() => {
793
+ setScrollOffset(0);
794
+ }, [activeFile]);
795
+ useInput5(
796
+ (input, key) => {
797
+ if (!isFocused) return;
798
+ if (key.upArrow || input === "k") {
799
+ setScrollOffset((s) => Math.max(0, s - 1));
800
+ }
801
+ if (key.downArrow || input === "j") {
802
+ setScrollOffset((s) => s + 1);
803
+ }
804
+ if (input === "d" && key.ctrl) {
805
+ setScrollOffset((s) => s + 15);
806
+ }
807
+ if (input === "u" && key.ctrl) {
808
+ setScrollOffset((s) => Math.max(0, s - 15));
809
+ }
810
+ }
811
+ );
812
+ if (!fileContent) {
813
+ return /* @__PURE__ */ jsx8(Text8, { color: theme.colors.textDim, children: "Loading..." });
814
+ }
815
+ const lines = fileContent.split("\n");
816
+ const maxVisible = 30;
817
+ const visibleLines = lines.slice(scrollOffset, scrollOffset + maxVisible);
818
+ const gutterWidth = String(lines.length).length;
819
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
820
+ visibleLines.map((line, i) => {
821
+ const lineNum = scrollOffset + i + 1;
822
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
823
+ /* @__PURE__ */ jsx8(Text8, { color: theme.colors.textDim, children: String(lineNum).padStart(gutterWidth, " ") }),
824
+ /* @__PURE__ */ jsx8(Text8, { color: theme.colors.textDim, children: " | " }),
825
+ /* @__PURE__ */ jsx8(Text8, { color: theme.colors.text, children: line })
826
+ ] }, lineNum);
827
+ }),
828
+ lines.length > maxVisible && /* @__PURE__ */ jsxs8(Text8, { color: theme.colors.textDim, children: [
829
+ "Line ",
830
+ scrollOffset + 1,
831
+ "-",
832
+ Math.min(scrollOffset + maxVisible, lines.length),
833
+ " of",
834
+ " ",
835
+ lines.length,
836
+ " (j/k to scroll)"
837
+ ] })
838
+ ] });
839
+ }
840
+ function MainPanel() {
841
+ const isFocused = useUIStore((s) => s.focusedPanel === "workspace");
842
+ const activeFile = useWorkspaceStore((s) => s.activeFile);
843
+ const projectName = useWorkspaceStore((s) => s.projectName);
844
+ return /* @__PURE__ */ jsxs9(
845
+ Box9,
846
+ {
847
+ flexDirection: "column",
848
+ flexGrow: 1,
849
+ borderStyle: "single",
850
+ borderColor: isFocused ? theme.colors.borderFocus : theme.colors.border,
851
+ children: [
852
+ /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: activeFile ? /* @__PURE__ */ jsx9(Text9, { color: theme.colors.primary, bold: true, children: activeFile.split("/").pop() }) : /* @__PURE__ */ jsx9(Text9, { color: theme.colors.textDim, children: "no file open" }) }),
853
+ /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: activeFile ? /* @__PURE__ */ jsx9(FileViewer, { isFocused }) : /* @__PURE__ */ jsxs9(
854
+ Box9,
855
+ {
856
+ flexDirection: "column",
857
+ alignItems: "center",
858
+ justifyContent: "center",
859
+ flexGrow: 1,
860
+ children: [
861
+ /* @__PURE__ */ jsx9(Text9, { color: theme.colors.textDim, children: projectName ? "Select a file from the sidebar" : "No project loaded" }),
862
+ /* @__PURE__ */ jsx9(Text9, { color: theme.colors.textDim, children: "Use ^1 to focus the file tree" })
863
+ ]
864
+ }
865
+ ) })
866
+ ]
867
+ }
868
+ );
869
+ }
870
+ function MessageList() {
871
+ const { messages, isAgentRunning, agentStreamText } = useChatStore();
872
+ if (messages.length === 0 && !isAgentRunning) {
873
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", gap: 1, paddingY: 1, children: [
874
+ /* @__PURE__ */ jsx10(Text10, { color: theme.colors.textDim, children: "No messages yet." }),
875
+ /* @__PURE__ */ jsx10(Text10, { color: theme.colors.textDim, children: "Type a message below to chat." }),
876
+ /* @__PURE__ */ jsxs10(Text10, { color: theme.colors.textDim, children: [
877
+ "Start with ",
878
+ /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.colors.aiBubble, children: "@ai" }),
879
+ " to talk to Claude."
880
+ ] })
881
+ ] });
882
+ }
883
+ const maxMessages = 15;
884
+ const visibleMessages = messages.slice(-maxMessages);
885
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", gap: 0, children: [
886
+ visibleMessages.map((msg) => /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
887
+ /* @__PURE__ */ jsx10(Text10, { children: /* @__PURE__ */ jsx10(
888
+ Text10,
889
+ {
890
+ bold: true,
891
+ color: msg.type === "agent" ? theme.colors.aiBubble : msg.type === "system" ? theme.colors.systemText : msg.userColor || theme.colors.userBubble,
892
+ children: msg.type === "agent" ? "# Claude" : msg.type === "system" ? "~ system" : msg.userName
893
+ }
894
+ ) }),
895
+ /* @__PURE__ */ jsx10(Text10, { color: theme.colors.text, wrap: "wrap", children: msg.text })
896
+ ] }, msg.id)),
897
+ isAgentRunning && agentStreamText && /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
898
+ /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.colors.aiBubble, children: "# Claude" }),
899
+ /* @__PURE__ */ jsx10(Text10, { color: theme.colors.text, wrap: "wrap", children: agentStreamText }),
900
+ /* @__PURE__ */ jsx10(Text10, { color: theme.colors.aiBubble, children: "..." })
901
+ ] }),
902
+ isAgentRunning && !agentStreamText && /* @__PURE__ */ jsx10(Text10, { color: theme.colors.aiBubble, children: "Claude is thinking..." })
903
+ ] });
904
+ }
905
+ function useClaudeAgent() {
906
+ const sessionRef = useRef(null);
907
+ const {
908
+ addMessage,
909
+ setAgentRunning,
910
+ setAgentStreamText,
911
+ appendAgentStreamText
912
+ } = useChatStore();
913
+ const projectPath = useWorkspaceStore((s) => s.projectPath);
914
+ const runAgent = useCallback3(
915
+ async (prompt) => {
916
+ const cleanPrompt = prompt.replace(/^@ai\s*/i, "").trim();
917
+ if (!cleanPrompt) return;
918
+ setAgentRunning(true);
919
+ setAgentStreamText("");
920
+ try {
921
+ const { query } = await import("./sdk-LM2XQCS5.js");
922
+ const session = query({
923
+ prompt: cleanPrompt,
924
+ options: {
925
+ allowedTools: [
926
+ "Read",
927
+ "Write",
928
+ "Edit",
929
+ "Bash",
930
+ "Glob",
931
+ "Grep"
932
+ ],
933
+ cwd: projectPath,
934
+ maxTurns: 25
935
+ }
936
+ });
937
+ sessionRef.current = session;
938
+ let fullText = "";
939
+ for await (const message of session) {
940
+ switch (message.type) {
941
+ case "assistant": {
942
+ const content = message.message?.content;
943
+ if (Array.isArray(content)) {
944
+ for (const block of content) {
945
+ if (block.type === "text") {
946
+ fullText = block.text;
947
+ setAgentStreamText(fullText);
948
+ } else if (block.type === "tool_use") {
949
+ addMessage({
950
+ userId: "claude",
951
+ userName: "Claude",
952
+ userColor: "#b4befe",
953
+ text: `[${block.name}] ${typeof block.input === "object" ? JSON.stringify(block.input).slice(0, 100) : String(block.input)}...`,
954
+ type: "system"
955
+ });
956
+ }
957
+ }
958
+ }
959
+ break;
960
+ }
961
+ case "result": {
962
+ const resultContent = message.result?.content;
963
+ if (typeof resultContent === "string" && resultContent) {
964
+ fullText = resultContent;
965
+ } else if (Array.isArray(resultContent)) {
966
+ for (const block of resultContent) {
967
+ if (block.type === "text") {
968
+ fullText = block.text;
969
+ }
970
+ }
971
+ }
972
+ break;
973
+ }
974
+ default:
975
+ break;
976
+ }
977
+ }
978
+ if (fullText) {
979
+ addMessage({
980
+ userId: "claude",
981
+ userName: "Claude",
982
+ userColor: "#b4befe",
983
+ text: fullText,
984
+ type: "agent"
985
+ });
986
+ }
987
+ } catch (err) {
988
+ const errorMsg = err instanceof Error ? err.message : "Unknown error";
989
+ addMessage({
990
+ userId: "system",
991
+ userName: "system",
992
+ userColor: "#6c7086",
993
+ text: `Error: ${errorMsg}`,
994
+ type: "system"
995
+ });
996
+ } finally {
997
+ setAgentRunning(false);
998
+ setAgentStreamText("");
999
+ sessionRef.current = null;
1000
+ }
1001
+ },
1002
+ [
1003
+ projectPath,
1004
+ addMessage,
1005
+ setAgentRunning,
1006
+ setAgentStreamText,
1007
+ appendAgentStreamText
1008
+ ]
1009
+ );
1010
+ const interrupt = useCallback3(() => {
1011
+ if (sessionRef.current) {
1012
+ sessionRef.current.close();
1013
+ sessionRef.current = null;
1014
+ setAgentRunning(false);
1015
+ setAgentStreamText("");
1016
+ addMessage({
1017
+ userId: "system",
1018
+ userName: "system",
1019
+ userColor: "#6c7086",
1020
+ text: "Agent interrupted.",
1021
+ type: "system"
1022
+ });
1023
+ }
1024
+ }, [addMessage, setAgentRunning, setAgentStreamText]);
1025
+ return { runAgent, interrupt };
1026
+ }
1027
+ function ChatInput({ isFocused }) {
1028
+ const [cursorVisible, setCursorVisible] = React5.useState(true);
1029
+ const { inputValue, setInputValue, addMessage, isAgentRunning } = useChatStore();
1030
+ const { runAgent, interrupt } = useClaudeAgent();
1031
+ React5.useEffect(() => {
1032
+ if (!isFocused) return;
1033
+ const interval = setInterval(() => setCursorVisible((v) => !v), 530);
1034
+ return () => clearInterval(interval);
1035
+ }, [isFocused]);
1036
+ useInput6(
1037
+ (input, key) => {
1038
+ if (!isFocused) return;
1039
+ if (key.ctrl && input === "c" && isAgentRunning) {
1040
+ interrupt();
1041
+ return;
1042
+ }
1043
+ if (key.return && inputValue.trim()) {
1044
+ const text = inputValue.trim();
1045
+ const isAiMention = text.toLowerCase().startsWith("@ai");
1046
+ addMessage({
1047
+ userId: "local",
1048
+ userName: "you",
1049
+ userColor: theme.colors.userBubble,
1050
+ text,
1051
+ type: "user",
1052
+ isAiMention
1053
+ });
1054
+ setInputValue("");
1055
+ if (isAiMention && !isAgentRunning) {
1056
+ runAgent(text);
1057
+ }
1058
+ return;
1059
+ }
1060
+ if (key.backspace || key.delete) {
1061
+ setInputValue(inputValue.slice(0, -1));
1062
+ return;
1063
+ }
1064
+ if (input && !key.ctrl && !key.meta) {
1065
+ setInputValue(inputValue + input);
1066
+ }
1067
+ }
1068
+ );
1069
+ return /* @__PURE__ */ jsxs11(Box11, { children: [
1070
+ /* @__PURE__ */ jsxs11(Text11, { color: isFocused ? theme.colors.primary : theme.colors.textDim, children: [
1071
+ ">",
1072
+ " "
1073
+ ] }),
1074
+ /* @__PURE__ */ jsxs11(Text11, { color: theme.colors.text, children: [
1075
+ inputValue,
1076
+ isFocused && cursorVisible ? /* @__PURE__ */ jsx11(Text11, { inverse: true, children: " " }) : ""
1077
+ ] })
1078
+ ] });
1079
+ }
1080
+ function ChatPanel() {
1081
+ const isFocused = useUIStore((s) => s.focusedPanel === "chat");
1082
+ return /* @__PURE__ */ jsxs12(
1083
+ Box12,
1084
+ {
1085
+ flexDirection: "column",
1086
+ width: 34,
1087
+ borderStyle: "single",
1088
+ borderColor: isFocused ? theme.colors.borderFocus : theme.colors.border,
1089
+ children: [
1090
+ /* @__PURE__ */ jsxs12(Box12, { paddingX: 1, children: [
1091
+ /* @__PURE__ */ jsx12(Text12, { bold: true, color: theme.colors.primary, children: "Chat" }),
1092
+ /* @__PURE__ */ jsx12(Text12, { color: theme.colors.textDim, children: " | @ai for Claude" })
1093
+ ] }),
1094
+ /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: /* @__PURE__ */ jsx12(MessageList, {}) }),
1095
+ /* @__PURE__ */ jsx12(Box12, { paddingX: 1, borderStyle: "single", borderColor: theme.colors.border, borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, children: /* @__PURE__ */ jsx12(ChatInput, { isFocused }) })
1096
+ ]
1097
+ }
1098
+ );
1099
+ }
1100
+ function flattenFiles(entries, prefix = "") {
1101
+ const result = [];
1102
+ for (const entry of entries) {
1103
+ const displayPath = prefix ? `${prefix}/${entry.name}` : entry.name;
1104
+ if (entry.type === "file") {
1105
+ result.push({ name: displayPath, path: entry.path });
1106
+ }
1107
+ if (entry.type === "directory" && entry.children) {
1108
+ result.push(...flattenFiles(entry.children, displayPath));
1109
+ }
1110
+ }
1111
+ return result;
1112
+ }
1113
+ function CommandPalette({ onClose }) {
1114
+ const [query, setQuery] = useState4("");
1115
+ const [selectedIndex, setSelectedIndex] = useState4(0);
1116
+ const fileTree = useWorkspaceStore((s) => s.fileTree);
1117
+ const { openFile } = useWorkspaceStore();
1118
+ const { readFile } = useFileSystem();
1119
+ const { setFocusedPanel, setSidebarTab } = useUIStore();
1120
+ const items = useMemo(() => {
1121
+ const actions = [
1122
+ {
1123
+ id: "cmd:files",
1124
+ label: "> Show Files",
1125
+ type: "action",
1126
+ action: () => {
1127
+ setFocusedPanel("sidebar");
1128
+ setSidebarTab("files");
1129
+ }
1130
+ },
1131
+ {
1132
+ id: "cmd:secrets",
1133
+ label: "> Show Secrets",
1134
+ type: "action",
1135
+ action: () => {
1136
+ setFocusedPanel("sidebar");
1137
+ setSidebarTab("secrets");
1138
+ }
1139
+ },
1140
+ {
1141
+ id: "cmd:team",
1142
+ label: "> Show Team",
1143
+ type: "action",
1144
+ action: () => {
1145
+ setFocusedPanel("sidebar");
1146
+ setSidebarTab("team");
1147
+ }
1148
+ },
1149
+ {
1150
+ id: "cmd:chat",
1151
+ label: "> Focus Chat",
1152
+ type: "action",
1153
+ action: () => setFocusedPanel("chat")
1154
+ },
1155
+ {
1156
+ id: "cmd:editor",
1157
+ label: "> Focus Editor",
1158
+ type: "action",
1159
+ action: () => setFocusedPanel("workspace")
1160
+ }
1161
+ ];
1162
+ const files = flattenFiles(fileTree).map((f) => ({
1163
+ id: `file:${f.path}`,
1164
+ label: f.name,
1165
+ type: "file",
1166
+ action: () => {
1167
+ openFile(f.path);
1168
+ readFile(f.path);
1169
+ setFocusedPanel("workspace");
1170
+ }
1171
+ }));
1172
+ return [...actions, ...files];
1173
+ }, [fileTree, openFile, readFile, setFocusedPanel, setSidebarTab]);
1174
+ const filtered = useMemo(() => {
1175
+ if (!query) return items.slice(0, 15);
1176
+ const q = query.toLowerCase();
1177
+ return items.filter((item) => {
1178
+ let idx = 0;
1179
+ const label = item.label.toLowerCase();
1180
+ for (const char of q) {
1181
+ idx = label.indexOf(char, idx);
1182
+ if (idx === -1) return false;
1183
+ idx++;
1184
+ }
1185
+ return true;
1186
+ }).slice(0, 15);
1187
+ }, [items, query]);
1188
+ useInput7((input, key) => {
1189
+ if (key.escape) {
1190
+ onClose();
1191
+ return;
1192
+ }
1193
+ if (key.upArrow) {
1194
+ setSelectedIndex((i) => Math.max(0, i - 1));
1195
+ return;
1196
+ }
1197
+ if (key.downArrow) {
1198
+ setSelectedIndex((i) => Math.min(filtered.length - 1, i + 1));
1199
+ return;
1200
+ }
1201
+ if (key.return && filtered[selectedIndex]) {
1202
+ filtered[selectedIndex].action();
1203
+ onClose();
1204
+ return;
1205
+ }
1206
+ if (key.backspace || key.delete) {
1207
+ setQuery((s) => s.slice(0, -1));
1208
+ setSelectedIndex(0);
1209
+ return;
1210
+ }
1211
+ if (input && !key.ctrl && !key.meta) {
1212
+ setQuery((s) => s + input);
1213
+ setSelectedIndex(0);
1214
+ }
1215
+ });
1216
+ return /* @__PURE__ */ jsxs13(
1217
+ Box13,
1218
+ {
1219
+ flexDirection: "column",
1220
+ borderStyle: "round",
1221
+ borderColor: theme.colors.accent,
1222
+ paddingX: 2,
1223
+ paddingY: 1,
1224
+ width: 60,
1225
+ children: [
1226
+ /* @__PURE__ */ jsxs13(Box13, { children: [
1227
+ /* @__PURE__ */ jsxs13(Text13, { color: theme.colors.accent, children: [
1228
+ ">",
1229
+ " "
1230
+ ] }),
1231
+ /* @__PURE__ */ jsxs13(Text13, { color: theme.colors.text, children: [
1232
+ query,
1233
+ /* @__PURE__ */ jsx13(Text13, { inverse: true, children: " " })
1234
+ ] })
1235
+ ] }),
1236
+ /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", marginTop: 1, children: [
1237
+ filtered.map((item, i) => /* @__PURE__ */ jsxs13(
1238
+ Text13,
1239
+ {
1240
+ inverse: i === selectedIndex,
1241
+ color: i === selectedIndex ? theme.colors.textBright : item.type === "action" ? theme.colors.primary : theme.colors.text,
1242
+ children: [
1243
+ item.type === "action" ? "" : " ",
1244
+ item.label
1245
+ ]
1246
+ },
1247
+ item.id
1248
+ )),
1249
+ filtered.length === 0 && /* @__PURE__ */ jsx13(Text13, { color: theme.colors.textDim, children: "No matches" })
1250
+ ] }),
1251
+ /* @__PURE__ */ jsx13(Text13, { color: theme.colors.textDim, children: "Esc: close | Enter: select" })
1252
+ ]
1253
+ }
1254
+ );
1255
+ }
1256
+ function InviteModal({ serverUrl, token, onClose }) {
1257
+ const { roomId } = useCollab();
1258
+ const [inviteCode, setInviteCode] = useState5(null);
1259
+ const [error, setError] = useState5(null);
1260
+ const [loading, setLoading] = useState5(false);
1261
+ useInput8((_, key) => {
1262
+ if (key.escape) {
1263
+ onClose();
1264
+ }
1265
+ });
1266
+ useEffect2(() => {
1267
+ if (!serverUrl || !token || !roomId) {
1268
+ setError("Not connected to a room. Use --room <id> when starting.");
1269
+ return;
1270
+ }
1271
+ setLoading(true);
1272
+ fetch(`${serverUrl}/api/rooms/${roomId}/invite`, {
1273
+ method: "POST",
1274
+ headers: {
1275
+ Authorization: `Bearer ${token}`,
1276
+ "Content-Type": "application/json"
1277
+ }
1278
+ }).then((res) => res.json()).then((data) => {
1279
+ if (data.inviteCode) {
1280
+ setInviteCode(data.inviteCode);
1281
+ } else {
1282
+ setError(data.error || "Failed to create invite");
1283
+ }
1284
+ }).catch((err) => setError(err.message)).finally(() => setLoading(false));
1285
+ }, [serverUrl, token, roomId]);
1286
+ return /* @__PURE__ */ jsxs14(
1287
+ Box14,
1288
+ {
1289
+ flexDirection: "column",
1290
+ borderStyle: "round",
1291
+ borderColor: theme.colors.accent,
1292
+ paddingX: 2,
1293
+ paddingY: 1,
1294
+ width: 50,
1295
+ children: [
1296
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: theme.colors.accent, children: "Invite to Room" }),
1297
+ loading && /* @__PURE__ */ jsx14(Text14, { color: theme.colors.textDim, children: "Generating invite..." }),
1298
+ error && /* @__PURE__ */ jsx14(Text14, { color: theme.colors.error, children: error }),
1299
+ inviteCode && /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", marginTop: 1, children: [
1300
+ /* @__PURE__ */ jsx14(Text14, { color: theme.colors.text, children: "Share this command:" }),
1301
+ /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text14, { bold: true, color: theme.colors.success, children: [
1302
+ "vibewithme --room ",
1303
+ roomId,
1304
+ " --server ",
1305
+ serverUrl
1306
+ ] }) }),
1307
+ /* @__PURE__ */ jsxs14(Box14, { marginTop: 1, children: [
1308
+ /* @__PURE__ */ jsxs14(Text14, { color: theme.colors.textDim, children: [
1309
+ "Invite code: ",
1310
+ /* @__PURE__ */ jsx14(Text14, { color: theme.colors.warning, children: inviteCode })
1311
+ ] }),
1312
+ /* @__PURE__ */ jsx14(Text14, { color: theme.colors.textDim, children: " (expires in 24h)" })
1313
+ ] })
1314
+ ] }),
1315
+ /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { color: theme.colors.textDim, children: "Esc: close" }) })
1316
+ ]
1317
+ }
1318
+ );
1319
+ }
1320
+ function useKeyBindings() {
1321
+ const { setFocusedPanel, focusNext, setModal, modal } = useUIStore();
1322
+ useInput9((input, key) => {
1323
+ if (key.escape && modal) {
1324
+ setModal(null);
1325
+ return;
1326
+ }
1327
+ if (key.ctrl && input === "1") {
1328
+ setFocusedPanel("sidebar");
1329
+ return;
1330
+ }
1331
+ if (key.ctrl && input === "2") {
1332
+ setFocusedPanel("workspace");
1333
+ return;
1334
+ }
1335
+ if (key.ctrl && input === "3") {
1336
+ setFocusedPanel("chat");
1337
+ return;
1338
+ }
1339
+ if (key.tab && !key.shift) {
1340
+ focusNext();
1341
+ return;
1342
+ }
1343
+ if (key.ctrl && input === "p") {
1344
+ setModal("command-palette");
1345
+ return;
1346
+ }
1347
+ });
1348
+ }
1349
+ function useCollaboration(serverUrl, token, roomId) {
1350
+ const [state, setState] = useState6({
1351
+ connected: false,
1352
+ remoteUsers: [],
1353
+ roomId: roomId ?? null
1354
+ });
1355
+ const docRef = useRef2(null);
1356
+ const providerRef = useRef2(null);
1357
+ useEffect3(() => {
1358
+ if (!serverUrl || !token || !roomId) return;
1359
+ const doc = new Doc();
1360
+ docRef.current = doc;
1361
+ import("./y-websocket-MJLMIWAR.js").then(({ WebsocketProvider }) => {
1362
+ const wsUrl = serverUrl.replace(/^http/, "ws");
1363
+ const provider = new WebsocketProvider(wsUrl, roomId, doc, {
1364
+ params: { token }
1365
+ });
1366
+ providerRef.current = provider;
1367
+ provider.on("status", ({ status }) => {
1368
+ setState((s) => ({ ...s, connected: status === "connected" }));
1369
+ });
1370
+ provider.awareness.on("change", () => {
1371
+ const states = Array.from(
1372
+ provider.awareness.getStates().entries()
1373
+ );
1374
+ const remoteUsers = states.filter(([clientId]) => clientId !== doc.clientID).map(([, state2]) => state2).filter((s) => s?.id);
1375
+ setState((prev) => ({ ...prev, remoteUsers }));
1376
+ });
1377
+ const chatArray = doc.getArray("chat");
1378
+ chatArray.observe(() => {
1379
+ const messages = chatArray.toArray();
1380
+ useChatStore.setState({ messages });
1381
+ });
1382
+ });
1383
+ return () => {
1384
+ providerRef.current?.destroy();
1385
+ doc.destroy();
1386
+ };
1387
+ }, [serverUrl, token, roomId]);
1388
+ const setPresence = useCallback4(
1389
+ (presenceData) => {
1390
+ if (!providerRef.current) return;
1391
+ providerRef.current.awareness.setLocalState({
1392
+ ...providerRef.current.awareness.getLocalState(),
1393
+ ...presenceData,
1394
+ lastActive: Date.now()
1395
+ });
1396
+ },
1397
+ []
1398
+ );
1399
+ const sendMessage = useCallback4(
1400
+ (msg) => {
1401
+ if (!docRef.current) {
1402
+ useChatStore.getState().addMessage(msg);
1403
+ return;
1404
+ }
1405
+ const chatArray = docRef.current.getArray("chat");
1406
+ chatArray.push([msg]);
1407
+ },
1408
+ []
1409
+ );
1410
+ const createHighlight = useCallback4(
1411
+ (highlight) => {
1412
+ if (!providerRef.current) return;
1413
+ const currentState = providerRef.current.awareness.getLocalState() || {};
1414
+ providerRef.current.awareness.setLocalState({
1415
+ ...currentState,
1416
+ highlight: {
1417
+ ...highlight,
1418
+ expiresAt: Date.now() + 1e4
1419
+ // 10 second highlight
1420
+ },
1421
+ lastActive: Date.now()
1422
+ });
1423
+ },
1424
+ []
1425
+ );
1426
+ return {
1427
+ ...state,
1428
+ setPresence,
1429
+ sendMessage,
1430
+ createHighlight,
1431
+ doc: docRef.current
1432
+ };
1433
+ }
1434
+ var CollabContext = createContext({
1435
+ connected: false,
1436
+ remoteUsers: [],
1437
+ roomId: null,
1438
+ setPresence: () => {
1439
+ },
1440
+ sendMessage: () => {
1441
+ },
1442
+ createHighlight: () => {
1443
+ }
1444
+ });
1445
+ var useCollab = () => useContext(CollabContext);
1446
+ function App({ projectPath, serverUrl, token, roomId }) {
1447
+ const { stdout } = useStdout();
1448
+ const setTerminalSize = useUIStore((s) => s.setTerminalSize);
1449
+ const setProjectPath = useWorkspaceStore((s) => s.setProjectPath);
1450
+ const { loadProject } = useFileSystem();
1451
+ const modal = useUIStore((s) => s.modal);
1452
+ const setModal = useUIStore((s) => s.setModal);
1453
+ const collab = useCollaboration(serverUrl, token, roomId);
1454
+ useEffect4(() => {
1455
+ if (!stdout) return;
1456
+ const updateSize = () => {
1457
+ setTerminalSize(stdout.columns || 120, stdout.rows || 40);
1458
+ };
1459
+ updateSize();
1460
+ stdout.on("resize", updateSize);
1461
+ return () => {
1462
+ stdout.off("resize", updateSize);
1463
+ };
1464
+ }, [stdout, setTerminalSize]);
1465
+ useEffect4(() => {
1466
+ if (projectPath) {
1467
+ setProjectPath(projectPath);
1468
+ }
1469
+ loadProject(projectPath);
1470
+ }, [projectPath, setProjectPath, loadProject]);
1471
+ useKeyBindings();
1472
+ const focusedPanel = useUIStore((s) => s.focusedPanel);
1473
+ useEffect4(() => {
1474
+ collab.setPresence({ panel: focusedPanel });
1475
+ }, [focusedPanel, collab.setPresence]);
1476
+ return /* @__PURE__ */ jsx15(CollabContext.Provider, { value: collab, children: /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", height: "100%", children: [
1477
+ /* @__PURE__ */ jsx15(Header, {}),
1478
+ /* @__PURE__ */ jsxs15(Box15, { flexDirection: "row", flexGrow: 1, children: [
1479
+ /* @__PURE__ */ jsx15(Sidebar, {}),
1480
+ /* @__PURE__ */ jsx15(MainPanel, {}),
1481
+ /* @__PURE__ */ jsx15(ChatPanel, {})
1482
+ ] }),
1483
+ /* @__PURE__ */ jsx15(Footer, {}),
1484
+ modal === "command-palette" && /* @__PURE__ */ jsx15(
1485
+ Box15,
1486
+ {
1487
+ position: "absolute",
1488
+ marginTop: 3,
1489
+ marginLeft: 20,
1490
+ children: /* @__PURE__ */ jsx15(CommandPalette, { onClose: () => setModal(null) })
1491
+ }
1492
+ ),
1493
+ modal === "invite" && /* @__PURE__ */ jsx15(
1494
+ Box15,
1495
+ {
1496
+ position: "absolute",
1497
+ marginTop: 5,
1498
+ marginLeft: 15,
1499
+ children: /* @__PURE__ */ jsx15(
1500
+ InviteModal,
1501
+ {
1502
+ serverUrl,
1503
+ token,
1504
+ onClose: () => setModal(null)
1505
+ }
1506
+ )
1507
+ }
1508
+ )
1509
+ ] }) });
1510
+ }
1511
+
1512
+ // src/bin.tsx
1513
+ import { jsx as jsx16 } from "react/jsx-runtime";
1514
+ var program = new Command();
1515
+ program.name("vibewithme").description("Collaborative terminal IDE for vibe coding").version("0.1.0");
1516
+ program.argument("[project-path]", "Path to project directory", ".").option("-s, --server <url>", "Collaboration server URL", "http://localhost:3847").option("-r, --room <id>", "Room ID to join").option("--solo", "Run without collaboration server").action(
1517
+ (projectPath, options) => {
1518
+ const resolvedPath = path3.resolve(projectPath);
1519
+ const { waitUntilExit } = render(
1520
+ /* @__PURE__ */ jsx16(
1521
+ App,
1522
+ {
1523
+ projectPath: resolvedPath,
1524
+ serverUrl: options.solo ? void 0 : options.server,
1525
+ roomId: options.room
1526
+ }
1527
+ ),
1528
+ {
1529
+ exitOnCtrlC: true
1530
+ }
1531
+ );
1532
+ waitUntilExit().then(() => {
1533
+ process.exit(0);
1534
+ });
1535
+ }
1536
+ );
1537
+ program.command("serve").description("Start the collaboration server").option("-p, --port <port>", "HTTP port", "3847").option("-w, --ws-port <port>", "WebSocket port", "3848").action(async (options) => {
1538
+ process.env.VWM_PORT = options.port;
1539
+ process.env.VWM_WS_PORT = options.wsPort;
1540
+ try {
1541
+ await import("./dist-HFZMQ7BV.js");
1542
+ } catch (err) {
1543
+ console.error(
1544
+ "Server package not found. Install @vibewithme/server to run the collab server."
1545
+ );
1546
+ console.error(err);
1547
+ process.exit(1);
1548
+ }
1549
+ });
1550
+ program.parse();