codebase-rag-tui 0.1.0 → 0.3.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.
@@ -1,4 +1,8 @@
1
- export declare function sendMessage(question: string, socketId: string, sessionId?: string): Promise<{
1
+ export declare function sendMessage(question: string, socketId: string, mode: string, sessionId?: string): Promise<{
2
2
  session_id: string;
3
3
  response: string;
4
+ edit: boolean;
5
+ }>;
6
+ export declare function rejectChange(socketId: string, sessionId: string): Promise<{
7
+ response: string;
4
8
  }>;
package/dist/api/chat.js CHANGED
@@ -1,14 +1,21 @@
1
- export async function sendMessage(question, socketId, sessionId) {
1
+ export async function sendMessage(question, socketId, mode, sessionId) {
2
2
  const res = await fetch(`${process.env['BACKEND_URI']}/remote/repo/query`, {
3
3
  method: "POST",
4
4
  headers: {
5
- "Content-Type": "application/json", // Add this!
5
+ "Content-Type": "application/json",
6
6
  },
7
7
  body: JSON.stringify({
8
8
  question: question,
9
9
  socket_id: socketId,
10
+ mode: mode,
10
11
  session_id: sessionId,
11
12
  })
12
13
  });
13
14
  return res.json();
14
15
  }
16
+ export async function rejectChange(socketId, sessionId) {
17
+ const res = await fetch(`${process.env['BACKEND_URI']}/remote/repo/reject/sessions/${sessionId}?socket_id=${encodeURIComponent(socketId)}`, {
18
+ method: "DELETE",
19
+ });
20
+ return res.json();
21
+ }
package/dist/app.js CHANGED
@@ -1,12 +1,14 @@
1
- import React, { useState, useEffect } from 'react';
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
2
  import { Box, Text, useApp } from 'ink';
3
3
  import Header from './components/Header.js';
4
4
  import Message from './components/Message.js';
5
5
  import Input from './components/Input.js';
6
- import { sendMessage } from './api/chat.js';
6
+ import TreeAnimation from './components/TreeAnimation.js';
7
+ import { sendMessage, rejectChange } from './api/chat.js';
7
8
  import { useSocket } from './contexts/SocketContext.js';
8
9
  import { getWorkspaceInfo } from './utils/workspace.js';
9
10
  import { useWorkspace } from './contexts/WorkspaceContext.js';
11
+ import SelectInput from 'ink-select-input';
10
12
  export default function App() {
11
13
  const { exit } = useApp();
12
14
  const { socket, isConnected, isConnecting, connectionError } = useSocket();
@@ -15,9 +17,51 @@ export default function App() {
15
17
  const [isLoading, setIsLoading] = useState(false);
16
18
  const [error, setError] = useState(null);
17
19
  const [sessionId, setSessionId] = useState(null);
20
+ const [mode, setMode] = useState(null);
21
+ const [pendingReview, setPendingReview] = useState(false);
22
+ const modes = [
23
+ {
24
+ label: 'Chat',
25
+ value: 'chat'
26
+ },
27
+ {
28
+ label: 'Agent',
29
+ value: 'agent'
30
+ }
31
+ ];
32
+ const [showAnimation, setShowAnimation] = useState(true);
33
+ const handleAnimationComplete = useCallback(() => {
34
+ setShowAnimation(false);
35
+ }, []);
36
+ const reviewChoices = [
37
+ { label: '✅ Accept changes', value: 'accept' },
38
+ { label: '❌ Reject changes', value: 'reject' },
39
+ ];
40
+ const handleReviewSelect = async (item) => {
41
+ if (item.value === 'accept') {
42
+ setMessages((prev) => [...prev, { role: 'model', text: 'Changes accepted.', edit: false }]);
43
+ setPendingReview(false);
44
+ }
45
+ else if (item.value === 'reject') {
46
+ if (!socket || !isConnected || !socket.id || !sessionId) {
47
+ setMessages((prev) => [...prev, { role: 'model', text: 'Cannot reject: not connected or no active session.', edit: false }]);
48
+ setPendingReview(false);
49
+ return;
50
+ }
51
+ try {
52
+ const res = await rejectChange(socket.id, sessionId);
53
+ setMessages((prev) => [...prev, { role: 'model', text: res.response, edit: false }]);
54
+ }
55
+ catch (err) {
56
+ const message = err instanceof Error ? err.message : 'Unknown error occurred';
57
+ setMessages((prev) => [...prev, { role: 'model', text: `Error rejecting changes: ${message}`, edit: false }]);
58
+ }
59
+ setPendingReview(false);
60
+ }
61
+ };
18
62
  const welcomeMessage = 'Start you session by entering the path to your repository';
19
63
  useEffect(() => {
20
- setMessages([{ role: 'model', text: welcomeMessage }]);
64
+ setMessages([{ role: 'model', text: welcomeMessage, edit: false }]);
21
65
  }, []); // Only run once on mount
22
66
  const handleSubmit = async (text) => {
23
67
  if (text === '/help') {
@@ -25,8 +69,10 @@ export default function App() {
25
69
  • /help - Show this help message
26
70
  • /clear - Clear conversation
27
71
  • /quit - Leave current session and reset workspace
28
- • /exit - Exit the application`;
29
- setMessages((prev) => [...prev, { role: 'model', text: helpText }]);
72
+ • /exit - Exit the application
73
+ /agent - Switch to agent mode
74
+ • /chat - Switch to chat mode`;
75
+ setMessages((prev) => [...prev, { role: 'model', text: helpText, edit: false }]);
30
76
  }
31
77
  if (text === '/exit') {
32
78
  exit();
@@ -40,45 +86,59 @@ export default function App() {
40
86
  if (text === '/quit') {
41
87
  setSessionId(null);
42
88
  setWorkspace(process.cwd());
43
- setMessages([{ role: 'model', text: welcomeMessage }]);
89
+ setMessages([{ role: 'model', text: welcomeMessage, edit: false }]);
44
90
  return;
45
91
  }
46
- setMessages((prev) => [...prev, { role: 'user', text }]);
92
+ // if (text === '/agent') {
93
+ // setMode('agent');
94
+ // setMessages((prev) => [...prev, {role: 'model', text: 'Switched to agent mode'}]);
95
+ // return;
96
+ // }
97
+ // if (text === '/chat') {
98
+ // setMode('chat');
99
+ // setMessages((prev) => [...prev, {role: 'model', text: 'Switched to chat mode'}]);
100
+ // return;
101
+ // }
102
+ setMessages((prev) => [...prev, { role: 'user', text, edit: false }]);
47
103
  if (workspace == process.cwd()) {
48
104
  try {
49
105
  const info = await getWorkspaceInfo(text);
50
106
  if (info.exists && info.isDirectory) {
51
107
  setWorkspace(info.absolutePath);
52
- setMessages((prev) => [...prev, { role: 'model', text: `Workspace is set to: ${info.absolutePath}` }]);
108
+ setMessages((prev) => [...prev, { role: 'model', text: `Workspace is set to: ${info.absolutePath}`, edit: false }]);
53
109
  }
54
110
  else if (info.exists) {
55
- setMessages((prev) => [...prev, { role: 'model', text: 'Path exists but is not a directory. Please enter a valid directory path.' }]);
111
+ setMessages((prev) => [...prev, { role: 'model', text: 'Path exists but is not a directory. Please enter a valid directory path.', edit: false }]);
56
112
  }
57
113
  else {
58
- setMessages((prev) => [...prev, { role: 'model', text: 'Directory does not exist. Please enter a valid project directory path.' }]);
114
+ setMessages((prev) => [...prev, { role: 'model', text: 'Directory does not exist. Please enter a valid project directory path.', edit: false }]);
59
115
  }
60
116
  }
61
117
  catch (err) {
62
118
  const message = err instanceof Error ? err.message : 'Unknown error occurred';
63
- setMessages((prev) => [...prev, { role: 'model', text: `Error validating path: ${message}` }]);
119
+ setMessages((prev) => [...prev, { role: 'model', text: `Error validating path: ${message}`, edit: false }]);
64
120
  }
65
121
  return;
66
122
  }
67
123
  setIsLoading(true);
68
124
  setError(null);
69
125
  if (!socket || !isConnected || !socket.id) {
70
- setMessages((prev) => [...prev, { role: 'model', text: 'Failed to connect to server' }]);
126
+ setMessages((prev) => [...prev, { role: 'model', text: 'Failed to connect to server', edit: false }]);
71
127
  return;
72
128
  }
73
129
  try {
74
130
  if (sessionId) {
75
- const res = await sendMessage(text, socket.id, sessionId);
76
- setMessages((prev) => [...prev, { role: 'model', text: res.response }]);
131
+ const res = await sendMessage(text, socket.id, mode, sessionId);
132
+ setMessages((prev) => [...prev, { role: 'model', text: res.response, edit: res.edit }]);
133
+ if (res.edit)
134
+ setPendingReview(true);
77
135
  }
78
136
  else {
79
- const res = await sendMessage(text, socket.id);
137
+ const res = await sendMessage(text, socket.id, mode);
80
138
  setSessionId(res.session_id);
81
- setMessages((prev) => [...prev, { role: 'model', text: res.response }]);
139
+ setMessages((prev) => [...prev, { role: 'model', text: res.response, edit: res.edit }]);
140
+ if (res.edit)
141
+ setPendingReview(true);
82
142
  }
83
143
  }
84
144
  catch (err) {
@@ -89,17 +149,22 @@ export default function App() {
89
149
  setIsLoading(false);
90
150
  }
91
151
  };
152
+ if (showAnimation) {
153
+ return React.createElement(TreeAnimation, { onComplete: handleAnimationComplete });
154
+ }
92
155
  return (React.createElement(Box, { flexDirection: "column", padding: 1 },
93
156
  React.createElement(Header, null),
94
- React.createElement(Box, { flexDirection: "column", marginTop: 1 }, messages.map((msg, i) => (
95
- // eslint-disable-next-line react/no-array-index-key
96
- React.createElement(Message, { key: i, role: msg.role, text: msg.text })))),
157
+ (!mode) && (React.createElement(SelectInput, { items: modes, onSelect: m => setMode(m.value) })),
158
+ (mode) && (React.createElement(Box, { flexDirection: "column", marginTop: 1 }, messages.map((msg, i) => (React.createElement(Message, { key: i, role: msg.role, text: msg.text, edit: msg.edit }))))),
97
159
  (error || connectionError) && (React.createElement(Box, { marginTop: 1 },
98
160
  React.createElement(Text, { color: "red" }, error || connectionError))),
99
161
  isConnecting && (React.createElement(Box, { marginTop: 1 },
100
162
  React.createElement(Text, { color: "yellow" }, "Connecting to server..."))),
101
163
  !isConnected && !isConnecting && !connectionError && (React.createElement(Box, { marginTop: 1 },
102
164
  React.createElement(Text, { color: "gray" }, "Disconnected from server"))),
103
- React.createElement(Box, { marginTop: 1 },
104
- React.createElement(Input, { onSubmit: handleSubmit, isLoading: isLoading }))));
165
+ mode && pendingReview && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
166
+ React.createElement(Text, { color: "yellow", bold: true }, "Review changes \u2014 accept or reject?"),
167
+ React.createElement(SelectInput, { items: reviewChoices, onSelect: handleReviewSelect }))),
168
+ mode && !pendingReview && (React.createElement(Box, { marginTop: 1 },
169
+ React.createElement(Input, { onSubmit: handleSubmit, isLoading: isLoading, mode: mode })))));
105
170
  }
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  type Props = {
3
3
  onSubmit: (value: string) => void;
4
4
  isLoading: boolean;
5
+ mode: string;
5
6
  };
6
- export default function Input({ onSubmit, isLoading }: Props): React.JSX.Element;
7
+ export default function Input({ onSubmit, isLoading, mode }: Props): React.JSX.Element;
7
8
  export {};
@@ -1,7 +1,7 @@
1
1
  import React, { useState } from 'react';
2
2
  import { Box, Text } from 'ink';
3
3
  import TextInput from 'ink-text-input';
4
- export default function Input({ onSubmit, isLoading }) {
4
+ export default function Input({ onSubmit, isLoading, mode }) {
5
5
  const [value, setValue] = useState('');
6
6
  const handleSubmit = (text) => {
7
7
  if (text.trim().length === 0) {
@@ -15,6 +15,10 @@ export default function Input({ onSubmit, isLoading }) {
15
15
  React.createElement(Text, { dimColor: true }, "Thinking...")));
16
16
  }
17
17
  return (React.createElement(Box, null,
18
- React.createElement(Text, { bold: true, color: "green" }, '> '),
18
+ React.createElement(Text, { color: "cyan" },
19
+ "[",
20
+ mode,
21
+ "]"),
22
+ React.createElement(Text, { bold: true, color: "green" }, ' > '),
19
23
  React.createElement(TextInput, { value: value, onChange: setValue, onSubmit: handleSubmit, placeholder: "Type a message...", focus: !isLoading })));
20
24
  }
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  type Props = {
3
3
  role: 'user' | 'model';
4
4
  text: string;
5
+ edit?: boolean;
5
6
  };
6
- export default function Message({ role, text }: Props): React.JSX.Element;
7
+ export default function Message({ role, text, edit }: Props): React.JSX.Element;
7
8
  export {};
@@ -1,14 +1,16 @@
1
1
  import React from 'react';
2
2
  import { Box, Text } from 'ink';
3
- export default function Message({ role, text }) {
3
+ import Markdown from 'ink-markdown-es';
4
+ export default function Message({ role, text, edit }) {
4
5
  if (role === 'user') {
5
6
  return (React.createElement(Box, null,
6
7
  React.createElement(Text, { color: "green" },
7
8
  '> ',
8
9
  text)));
9
10
  }
10
- return (React.createElement(Box, null,
11
- React.createElement(Text, { color: "blue" },
12
- "Codebase Rag Agent: ",
13
- text)));
11
+ return (React.createElement(Box, { flexDirection: "column" },
12
+ React.createElement(Text, { color: "blue", bold: true }, "Codebase Rag Agent:"),
13
+ React.createElement(Markdown, null, text),
14
+ edit && (React.createElement(Box, { marginLeft: 2, marginTop: 0 },
15
+ React.createElement(Text, { color: "yellow" }, "\u26A1 Code changes were applied")))));
14
16
  }
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ type Props = {
3
+ onComplete: () => void;
4
+ };
5
+ export default function TreeAnimation({ onComplete }: Props): React.JSX.Element;
6
+ export {};
@@ -0,0 +1,100 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Box, Text } from 'ink';
3
+ const treeFrames = [
4
+ // Frame 1: Ground only
5
+ String.raw `
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+ | =|
16
+ | |
17
+ --------------------/ , . \--------._
18
+ `,
19
+ // Frame 2: Trunk grows
20
+ String.raw `
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+ | |//
29
+ |_ /
30
+ |- |
31
+ | =|
32
+ | |
33
+ --------------------/ , . \--------._
34
+ `,
35
+ // Frame 3: Lower branches
36
+ String.raw `
37
+
38
+
39
+
40
+
41
+ '7-,--. || / / ,
42
+ /' . / / |/_.'
43
+ | |//
44
+ |_ /
45
+ |- |
46
+ | =|
47
+ | |
48
+ --------------------/ , . \--------._
49
+ `,
50
+ // Frame 4: More branches
51
+ String.raw `
52
+
53
+
54
+ \\ y | //
55
+ _ _.___\\, / -. ||
56
+ '7-,--.'._|| / / ,
57
+ /' '-. './ / |/_.'
58
+ | |//
59
+ |_ /
60
+ |- |
61
+ | =|
62
+ | |
63
+ --------------------/ , . \--------._
64
+ `,
65
+ // Frame 5: Full tree
66
+ String.raw `
67
+ v . ._, |_ .,
68
+ '-._\/ . \ / |/_
69
+ \\ _\, y | \//
70
+ _\_.___\\, \\/ -.\||
71
+ '7-,--.'. || / / ,
72
+ /' '-. './ / |/_.'
73
+ | |//
74
+ |_ /
75
+ |- |
76
+ | =|
77
+ | |
78
+ --------------------/ , . \--------._
79
+ `,
80
+ ];
81
+ export default function TreeAnimation({ onComplete }) {
82
+ const [frameIndex, setFrameIndex] = useState(0);
83
+ useEffect(() => {
84
+ if (frameIndex < treeFrames.length - 1) {
85
+ const timer = setTimeout(() => {
86
+ setFrameIndex((prev) => prev + 1);
87
+ }, 200);
88
+ return () => clearTimeout(timer);
89
+ }
90
+ else {
91
+ const timer = setTimeout(() => {
92
+ onComplete();
93
+ }, 800);
94
+ return () => clearTimeout(timer);
95
+ }
96
+ }, [frameIndex, onComplete]);
97
+ return (React.createElement(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", padding: 2 },
98
+ React.createElement(Text, { color: "green" }, treeFrames[frameIndex]),
99
+ React.createElement(Text, { color: "cyan", bold: true }, "Codebase RAG Agent")));
100
+ }
@@ -55,27 +55,23 @@ export const SocketProvider = ({ children, serverUrl = process.env['BACKEND_URI'
55
55
  });
56
56
  // File system event handlers
57
57
  newSocket.on('dir:list', async (payload, ack) => {
58
- console.log('dir:list event heard');
59
58
  const result = await listDirectory(payload.dir_path, workspaceRef.current);
60
59
  ack(result);
61
60
  });
62
61
  newSocket.on('bytes:read', async (payload, ack) => {
63
- console.log('bytes:read event heard');
64
62
  const result = await readFileBytes(payload.file_path, workspaceRef.current);
65
63
  ack(result);
66
64
  });
67
65
  newSocket.on('file:read', async (payload, ack) => {
68
- console.log('file:read event heard');
69
66
  const result = await readFileText(payload.file_path, workspaceRef.current);
70
67
  ack(result);
71
68
  });
72
69
  newSocket.on('file:write', async (payload, ack) => {
73
- console.log('file:write event heard');
74
70
  const result = await writeFile(payload.file_path, payload.content, workspaceRef.current);
75
71
  ack(result);
76
72
  });
77
73
  newSocket.on("command:run", async (payload, ack) => {
78
- const res = await runCommand(payload.cmd_parts, workspace, payload.timeout);
74
+ const res = await runCommand(payload.cmd_parts, workspaceRef.current, payload.timeout);
79
75
  ack(res);
80
76
  });
81
77
  setSocket(newSocket);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebase-rag-tui",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Terminal-based AI agent for codebase RAG (Retrieval-Augmented Generation)",
5
5
  "license": "MIT",
6
6
  "author": "johnsonafool",
@@ -42,6 +42,8 @@
42
42
  "@google/generative-ai": "^0.24.1",
43
43
  "dotenv": "^17.2.4",
44
44
  "ink": "^4.1.0",
45
+ "ink-markdown-es": "^1.1.0",
46
+ "ink-select-input": "^6.2.0",
45
47
  "ink-text-input": "^6.0.0",
46
48
  "meow": "^11.0.0",
47
49
  "react": "^18.2.0",