codeep 1.0.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/LICENSE +201 -0
- package/README.md +576 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.js +421 -0
- package/dist/app.d.ts +2 -0
- package/dist/app.js +1406 -0
- package/dist/components/AgentProgress.d.ts +33 -0
- package/dist/components/AgentProgress.js +97 -0
- package/dist/components/Export.d.ts +8 -0
- package/dist/components/Export.js +27 -0
- package/dist/components/Help.d.ts +2 -0
- package/dist/components/Help.js +3 -0
- package/dist/components/Input.d.ts +9 -0
- package/dist/components/Input.js +89 -0
- package/dist/components/Loading.d.ts +9 -0
- package/dist/components/Loading.js +31 -0
- package/dist/components/Login.d.ts +7 -0
- package/dist/components/Login.js +77 -0
- package/dist/components/Logo.d.ts +8 -0
- package/dist/components/Logo.js +89 -0
- package/dist/components/LogoutPicker.d.ts +8 -0
- package/dist/components/LogoutPicker.js +61 -0
- package/dist/components/Message.d.ts +10 -0
- package/dist/components/Message.js +234 -0
- package/dist/components/MessageList.d.ts +10 -0
- package/dist/components/MessageList.js +8 -0
- package/dist/components/ProjectPermission.d.ts +7 -0
- package/dist/components/ProjectPermission.js +52 -0
- package/dist/components/Search.d.ts +10 -0
- package/dist/components/Search.js +30 -0
- package/dist/components/SessionPicker.d.ts +9 -0
- package/dist/components/SessionPicker.js +88 -0
- package/dist/components/Sessions.d.ts +12 -0
- package/dist/components/Sessions.js +102 -0
- package/dist/components/Settings.d.ts +7 -0
- package/dist/components/Settings.js +162 -0
- package/dist/components/Status.d.ts +2 -0
- package/dist/components/Status.js +12 -0
- package/dist/config/config.test.d.ts +1 -0
- package/dist/config/config.test.js +157 -0
- package/dist/config/index.d.ts +121 -0
- package/dist/config/index.js +555 -0
- package/dist/config/providers.d.ts +43 -0
- package/dist/config/providers.js +82 -0
- package/dist/config/providers.test.d.ts +1 -0
- package/dist/config/providers.test.js +132 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +38 -0
- package/dist/utils/agent.d.ts +37 -0
- package/dist/utils/agent.js +627 -0
- package/dist/utils/codeReview.d.ts +36 -0
- package/dist/utils/codeReview.js +390 -0
- package/dist/utils/context.d.ts +49 -0
- package/dist/utils/context.js +216 -0
- package/dist/utils/diffPreview.d.ts +57 -0
- package/dist/utils/diffPreview.js +335 -0
- package/dist/utils/export.d.ts +19 -0
- package/dist/utils/export.js +94 -0
- package/dist/utils/git.d.ts +85 -0
- package/dist/utils/git.js +399 -0
- package/dist/utils/git.test.d.ts +1 -0
- package/dist/utils/git.test.js +193 -0
- package/dist/utils/history.d.ts +93 -0
- package/dist/utils/history.js +348 -0
- package/dist/utils/interactive.d.ts +34 -0
- package/dist/utils/interactive.js +206 -0
- package/dist/utils/keychain.d.ts +17 -0
- package/dist/utils/keychain.js +160 -0
- package/dist/utils/learning.d.ts +89 -0
- package/dist/utils/learning.js +330 -0
- package/dist/utils/logger.d.ts +33 -0
- package/dist/utils/logger.js +130 -0
- package/dist/utils/project.d.ts +86 -0
- package/dist/utils/project.js +415 -0
- package/dist/utils/project.test.d.ts +1 -0
- package/dist/utils/project.test.js +212 -0
- package/dist/utils/ratelimit.d.ts +26 -0
- package/dist/utils/ratelimit.js +132 -0
- package/dist/utils/ratelimit.test.d.ts +1 -0
- package/dist/utils/ratelimit.test.js +131 -0
- package/dist/utils/retry.d.ts +28 -0
- package/dist/utils/retry.js +109 -0
- package/dist/utils/retry.test.d.ts +1 -0
- package/dist/utils/retry.test.js +163 -0
- package/dist/utils/search.d.ts +11 -0
- package/dist/utils/search.js +29 -0
- package/dist/utils/shell.d.ts +45 -0
- package/dist/utils/shell.js +242 -0
- package/dist/utils/skills.d.ts +144 -0
- package/dist/utils/skills.js +1137 -0
- package/dist/utils/smartContext.d.ts +29 -0
- package/dist/utils/smartContext.js +441 -0
- package/dist/utils/tools.d.ts +224 -0
- package/dist/utils/tools.js +731 -0
- package/dist/utils/update.d.ts +22 -0
- package/dist/utils/update.js +128 -0
- package/dist/utils/validation.d.ts +28 -0
- package/dist/utils/validation.js +141 -0
- package/dist/utils/validation.test.d.ts +1 -0
- package/dist/utils/validation.test.js +164 -0
- package/dist/utils/verify.d.ts +78 -0
- package/dist/utils/verify.js +464 -0
- package/package.json +68 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import { Text, Box } from 'ink';
|
|
4
|
+
// Global code block storage for copy functionality
|
|
5
|
+
let codeBlocks = [];
|
|
6
|
+
export function getCodeBlocks() {
|
|
7
|
+
return codeBlocks;
|
|
8
|
+
}
|
|
9
|
+
export function clearCodeBlocks() {
|
|
10
|
+
codeBlocks = [];
|
|
11
|
+
}
|
|
12
|
+
export function getCodeBlock(index) {
|
|
13
|
+
if (index < 0) {
|
|
14
|
+
// Negative index = from end (-1 = last block)
|
|
15
|
+
const actualIndex = codeBlocks.length + index;
|
|
16
|
+
return codeBlocks[actualIndex] || null;
|
|
17
|
+
}
|
|
18
|
+
return codeBlocks[index] || null;
|
|
19
|
+
}
|
|
20
|
+
export const MessageView = memo(({ role, content }) => {
|
|
21
|
+
if (role === 'user') {
|
|
22
|
+
return (_jsx(Box, { marginY: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: "#f02a30", bold: true, children: '> ' }), _jsx(Text, { children: content })] }) }));
|
|
23
|
+
}
|
|
24
|
+
if (role === 'system') {
|
|
25
|
+
return (_jsx(Box, { marginY: 1, justifyContent: "center", children: _jsx(Text, { italic: true, children: content }) }));
|
|
26
|
+
}
|
|
27
|
+
// Assistant message - parse code blocks and markdown
|
|
28
|
+
// Clear and rebuild code blocks on each render
|
|
29
|
+
codeBlocks = [];
|
|
30
|
+
return (_jsx(Box, { flexDirection: "column", marginY: 1, paddingX: 1, children: _jsx(FormattedResponse, { content: content }) }));
|
|
31
|
+
});
|
|
32
|
+
const FormattedResponse = memo(({ content }) => {
|
|
33
|
+
const segments = parseContent(content);
|
|
34
|
+
let codeBlockIndex = 0;
|
|
35
|
+
return (_jsx(_Fragment, { children: segments.map((segment, i) => {
|
|
36
|
+
if (segment.type === 'code') {
|
|
37
|
+
const idx = codeBlockIndex++;
|
|
38
|
+
// Store code block for copy functionality
|
|
39
|
+
codeBlocks.push(segment.content);
|
|
40
|
+
return _jsx(CodeBlock, { code: segment.content, language: segment.language || 'code', index: idx }, i);
|
|
41
|
+
}
|
|
42
|
+
// Render text with inline markdown
|
|
43
|
+
return _jsx(MarkdownText, { text: segment.content }, i);
|
|
44
|
+
}) }));
|
|
45
|
+
});
|
|
46
|
+
// Render inline markdown: **bold**, *italic*, `code`, __underline__
|
|
47
|
+
const MarkdownText = memo(({ text }) => {
|
|
48
|
+
const parts = [];
|
|
49
|
+
let remaining = text;
|
|
50
|
+
let key = 0;
|
|
51
|
+
while (remaining.length > 0) {
|
|
52
|
+
// Bold: **text** or __text__
|
|
53
|
+
const boldMatch = remaining.match(/^(\*\*|__)(.+?)\1/);
|
|
54
|
+
if (boldMatch) {
|
|
55
|
+
parts.push(_jsx(Text, { bold: true, children: boldMatch[2] }, key++));
|
|
56
|
+
remaining = remaining.slice(boldMatch[0].length);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
// Italic: *text* or _text_
|
|
60
|
+
const italicMatch = remaining.match(/^(\*|_)([^*_]+)\1/);
|
|
61
|
+
if (italicMatch) {
|
|
62
|
+
parts.push(_jsx(Text, { italic: true, children: italicMatch[2] }, key++));
|
|
63
|
+
remaining = remaining.slice(italicMatch[0].length);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Inline code: `code`
|
|
67
|
+
const codeMatch = remaining.match(/^`([^`]+)`/);
|
|
68
|
+
if (codeMatch) {
|
|
69
|
+
parts.push(_jsx(Text, { color: "cyan", backgroundColor: "gray", children: codeMatch[1] }, key++));
|
|
70
|
+
remaining = remaining.slice(codeMatch[0].length);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
// Headers: # ## ### at start of line
|
|
74
|
+
const headerMatch = remaining.match(/^(#{1,3})\s+(.+?)(?:\n|$)/);
|
|
75
|
+
if (headerMatch) {
|
|
76
|
+
const level = headerMatch[1].length;
|
|
77
|
+
const color = level === 1 ? '#f02a30' : level === 2 ? 'cyan' : 'green';
|
|
78
|
+
parts.push(_jsx(Text, { color: color, bold: true, children: headerMatch[2] }, key++));
|
|
79
|
+
parts.push(_jsx(Text, { children: '\n' }, key++));
|
|
80
|
+
remaining = remaining.slice(headerMatch[0].length);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// List items: - item or * item or number. item
|
|
84
|
+
const listMatch = remaining.match(/^(\s*)([-*]|\d+\.)\s+(.+?)(?:\n|$)/);
|
|
85
|
+
if (listMatch) {
|
|
86
|
+
const indent = listMatch[1];
|
|
87
|
+
const bullet = listMatch[2].match(/^\d/) ? listMatch[2] : '•';
|
|
88
|
+
parts.push(_jsxs(Text, { children: [indent, _jsx(Text, { color: "#f02a30", children: bullet }), " ", listMatch[3], '\n'] }, key++));
|
|
89
|
+
remaining = remaining.slice(listMatch[0].length);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
// Links: [text](url)
|
|
93
|
+
const linkMatch = remaining.match(/^\[([^\]]+)\]\(([^)]+)\)/);
|
|
94
|
+
if (linkMatch) {
|
|
95
|
+
parts.push(_jsx(Text, { color: "blue", underline: true, children: linkMatch[1] }, key++));
|
|
96
|
+
remaining = remaining.slice(linkMatch[0].length);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
// Default: consume until next special character or end
|
|
100
|
+
const nextSpecial = remaining.search(/[\*_`#\[\n-]/);
|
|
101
|
+
if (nextSpecial === -1) {
|
|
102
|
+
parts.push(_jsx(Text, { children: remaining }, key++));
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
else if (nextSpecial === 0) {
|
|
106
|
+
// Not a valid markdown char, consume single char
|
|
107
|
+
parts.push(_jsx(Text, { children: remaining[0] }, key++));
|
|
108
|
+
remaining = remaining.slice(1);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
parts.push(_jsx(Text, { children: remaining.slice(0, nextSpecial) }, key++));
|
|
112
|
+
remaining = remaining.slice(nextSpecial);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return _jsx(Text, { children: parts });
|
|
116
|
+
});
|
|
117
|
+
const CodeBlock = memo(({ code, language, index }) => {
|
|
118
|
+
const lines = code.split('\n');
|
|
119
|
+
return (_jsxs(Box, { flexDirection: "column", marginY: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: "cyan", bold: true, children: language }), _jsxs(Text, { children: ["[", index, "]"] })] }), _jsx(Text, { children: " " }), lines.map((line, i) => (_jsx(SyntaxLine, { line: line, language: language }, i)))] }));
|
|
120
|
+
});
|
|
121
|
+
// Keywords by language - defined outside component to avoid recreation
|
|
122
|
+
const SYNTAX_KEYWORDS = {
|
|
123
|
+
python: ['def', 'class', 'return', 'if', 'else', 'elif', 'for', 'while', 'import', 'from', 'as', 'try', 'except', 'raise', 'with', 'True', 'False', 'None', 'and', 'or', 'not', 'in', 'is', 'lambda', 'pass', 'break', 'continue', 'global', 'async', 'await'],
|
|
124
|
+
javascript: ['const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'class', 'import', 'export', 'from', 'async', 'await', 'try', 'catch', 'throw', 'new', 'this', 'true', 'false', 'null', 'undefined', 'typeof', 'instanceof'],
|
|
125
|
+
typescript: ['const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'class', 'import', 'export', 'from', 'async', 'await', 'try', 'catch', 'throw', 'new', 'this', 'true', 'false', 'null', 'undefined', 'interface', 'type', 'enum', 'private', 'public', 'protected', 'readonly'],
|
|
126
|
+
java: ['public', 'private', 'protected', 'class', 'interface', 'extends', 'implements', 'return', 'if', 'else', 'for', 'while', 'new', 'this', 'static', 'final', 'void', 'int', 'String', 'boolean', 'true', 'false', 'null', 'try', 'catch', 'throw', 'throws'],
|
|
127
|
+
go: ['func', 'var', 'const', 'if', 'else', 'for', 'range', 'switch', 'case', 'return', 'struct', 'interface', 'package', 'import', 'type', 'map', 'chan', 'go', 'defer', 'true', 'false', 'nil'],
|
|
128
|
+
rust: ['fn', 'let', 'mut', 'const', 'if', 'else', 'for', 'while', 'loop', 'match', 'struct', 'enum', 'impl', 'trait', 'pub', 'use', 'mod', 'self', 'super', 'true', 'false', 'return', 'async', 'await'],
|
|
129
|
+
bash: ['if', 'then', 'else', 'fi', 'for', 'do', 'done', 'while', 'case', 'esac', 'function', 'return', 'echo', 'exit', 'export', 'local'],
|
|
130
|
+
sh: ['if', 'then', 'else', 'fi', 'for', 'do', 'done', 'while', 'case', 'esac', 'function', 'return', 'echo', 'exit', 'export', 'local'],
|
|
131
|
+
php: ['function', 'class', 'public', 'private', 'protected', 'return', 'if', 'else', 'elseif', 'for', 'foreach', 'while', 'echo', 'print', 'new', 'this', 'true', 'false', 'null', 'use', 'namespace', 'extends', 'implements'],
|
|
132
|
+
html: ['html', 'head', 'body', 'div', 'span', 'p', 'a', 'img', 'script', 'style', 'link', 'meta', 'title', 'h1', 'h2', 'h3', 'ul', 'li', 'table', 'tr', 'td', 'form', 'input', 'button'],
|
|
133
|
+
css: ['color', 'background', 'margin', 'padding', 'border', 'width', 'height', 'display', 'flex', 'grid', 'position', 'top', 'left', 'right', 'bottom', 'font', 'text'],
|
|
134
|
+
sql: ['SELECT', 'FROM', 'WHERE', 'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'TABLE', 'INDEX', 'JOIN', 'LEFT', 'RIGHT', 'INNER', 'OUTER', 'ON', 'AND', 'OR', 'NOT', 'NULL', 'ORDER', 'BY', 'GROUP', 'HAVING', 'LIMIT'],
|
|
135
|
+
};
|
|
136
|
+
// Syntax highlighting component - memoized to prevent re-renders
|
|
137
|
+
const SyntaxLine = memo(({ line, language }) => {
|
|
138
|
+
const lang = language.toLowerCase();
|
|
139
|
+
const langKeywords = SYNTAX_KEYWORDS[lang] || [];
|
|
140
|
+
const parts = [];
|
|
141
|
+
let remaining = line;
|
|
142
|
+
let key = 0;
|
|
143
|
+
while (remaining.length > 0) {
|
|
144
|
+
// Comments
|
|
145
|
+
if (remaining.startsWith('//') || remaining.startsWith('#')) {
|
|
146
|
+
parts.push(_jsx(Text, { color: "gray", children: remaining }, key++));
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
// Multi-line string/docstring
|
|
150
|
+
if (remaining.startsWith('"""') || remaining.startsWith("'''")) {
|
|
151
|
+
const quote = remaining.slice(0, 3);
|
|
152
|
+
parts.push(_jsx(Text, { color: "green", children: remaining }, key++));
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
// Strings
|
|
156
|
+
const stringMatch = remaining.match(/^(["'`])(?:[^\\]|\\.)*?\1/);
|
|
157
|
+
if (stringMatch) {
|
|
158
|
+
parts.push(_jsx(Text, { color: "green", children: stringMatch[0] }, key++));
|
|
159
|
+
remaining = remaining.slice(stringMatch[0].length);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
// Keywords
|
|
163
|
+
const wordMatch = remaining.match(/^[a-zA-Z_]\w*/);
|
|
164
|
+
if (wordMatch) {
|
|
165
|
+
const word = wordMatch[0];
|
|
166
|
+
if (langKeywords.includes(word)) {
|
|
167
|
+
parts.push(_jsx(Text, { color: "magenta", bold: true, children: word }, key++));
|
|
168
|
+
}
|
|
169
|
+
else if (word.match(/^[A-Z]/)) {
|
|
170
|
+
// Class names / constants
|
|
171
|
+
parts.push(_jsx(Text, { color: "cyan", children: word }, key++));
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
parts.push(_jsx(Text, { children: word }, key++));
|
|
175
|
+
}
|
|
176
|
+
remaining = remaining.slice(word.length);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
// Numbers
|
|
180
|
+
const numMatch = remaining.match(/^\d+\.?\d*/);
|
|
181
|
+
if (numMatch) {
|
|
182
|
+
parts.push(_jsx(Text, { color: "#f02a30", children: numMatch[0] }, key++));
|
|
183
|
+
remaining = remaining.slice(numMatch[0].length);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
// Operators and brackets
|
|
187
|
+
const opMatch = remaining.match(/^[+\-*/%=<>!&|^~?:;,.()\[\]{}]+/);
|
|
188
|
+
if (opMatch) {
|
|
189
|
+
parts.push(_jsx(Text, { color: "white", children: opMatch[0] }, key++));
|
|
190
|
+
remaining = remaining.slice(opMatch[0].length);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
// Default
|
|
194
|
+
parts.push(_jsx(Text, { children: remaining[0] }, key++));
|
|
195
|
+
remaining = remaining.slice(1);
|
|
196
|
+
}
|
|
197
|
+
return _jsx(Text, { children: parts });
|
|
198
|
+
});
|
|
199
|
+
function parseContent(text) {
|
|
200
|
+
const segments = [];
|
|
201
|
+
// Match code blocks: ```lang or ``` followed by code and closing ```
|
|
202
|
+
const codeBlockRegex = /```(\w*)\n?([\s\S]*?)```/g;
|
|
203
|
+
let lastIndex = 0;
|
|
204
|
+
let match;
|
|
205
|
+
while ((match = codeBlockRegex.exec(text)) !== null) {
|
|
206
|
+
// Text before code block
|
|
207
|
+
if (match.index > lastIndex) {
|
|
208
|
+
const textBefore = text.slice(lastIndex, match.index).trim();
|
|
209
|
+
if (textBefore) {
|
|
210
|
+
segments.push({ type: 'text', content: textBefore });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Code block
|
|
214
|
+
const lang = match[1] || 'code';
|
|
215
|
+
const code = match[2] || '';
|
|
216
|
+
segments.push({
|
|
217
|
+
type: 'code',
|
|
218
|
+
content: code.trim(),
|
|
219
|
+
language: lang,
|
|
220
|
+
});
|
|
221
|
+
lastIndex = match.index + match[0].length;
|
|
222
|
+
}
|
|
223
|
+
// Remaining text
|
|
224
|
+
if (lastIndex < text.length) {
|
|
225
|
+
const remaining = text.slice(lastIndex).trim();
|
|
226
|
+
if (remaining) {
|
|
227
|
+
segments.push({ type: 'text', content: remaining });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (segments.length === 0) {
|
|
231
|
+
segments.push({ type: 'text', content: text });
|
|
232
|
+
}
|
|
233
|
+
return segments;
|
|
234
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Message } from '../config';
|
|
3
|
+
interface MessageListProps {
|
|
4
|
+
messages: Message[];
|
|
5
|
+
streamingContent?: string;
|
|
6
|
+
scrollOffset: number;
|
|
7
|
+
terminalHeight: number;
|
|
8
|
+
}
|
|
9
|
+
export declare const MessageList: React.FC<MessageListProps>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Static } from 'ink';
|
|
3
|
+
import { MessageView } from './Message';
|
|
4
|
+
export const MessageList = ({ messages, }) => {
|
|
5
|
+
// Use Static component to prevent messages from re-rendering on every keystroke
|
|
6
|
+
// This keeps the scroll position stable when typing in input field
|
|
7
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsx(Static, { items: messages, children: (msg, index) => (_jsx(MessageView, { role: msg.role, content: msg.content }, index)) }) }));
|
|
8
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { setProjectPermission } from '../config/index';
|
|
5
|
+
import { getProjectSummary } from '../utils/project';
|
|
6
|
+
export const ProjectPermission = ({ projectPath, onComplete }) => {
|
|
7
|
+
const [step, setStep] = useState('read');
|
|
8
|
+
const [readGranted, setReadGranted] = useState(false);
|
|
9
|
+
const summary = getProjectSummary(projectPath);
|
|
10
|
+
const projectName = summary?.name || projectPath.split('/').pop() || 'Unknown';
|
|
11
|
+
useInput((input, key) => {
|
|
12
|
+
const char = input.toLowerCase();
|
|
13
|
+
if (step === 'read') {
|
|
14
|
+
if (char === 'y') {
|
|
15
|
+
// Grant for this session only
|
|
16
|
+
setReadGranted(true);
|
|
17
|
+
setStep('write');
|
|
18
|
+
}
|
|
19
|
+
else if (char === 'a') {
|
|
20
|
+
// Grant permanently (always)
|
|
21
|
+
setProjectPermission(projectPath, true, false);
|
|
22
|
+
setReadGranted(true);
|
|
23
|
+
setStep('write');
|
|
24
|
+
}
|
|
25
|
+
else if (char === 'n' || key.escape) {
|
|
26
|
+
// Deny
|
|
27
|
+
onComplete(false, false);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else if (step === 'write') {
|
|
31
|
+
if (char === 'y') {
|
|
32
|
+
// Grant write for session
|
|
33
|
+
onComplete(true, false, true);
|
|
34
|
+
}
|
|
35
|
+
else if (char === 'a') {
|
|
36
|
+
// Grant write permanently
|
|
37
|
+
setProjectPermission(projectPath, true, true);
|
|
38
|
+
onComplete(true, true, true);
|
|
39
|
+
}
|
|
40
|
+
else if (char === 'n' || key.escape) {
|
|
41
|
+
// Read only
|
|
42
|
+
if (readGranted) {
|
|
43
|
+
onComplete(true, false, false);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
onComplete(false, false, false);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#f02a30", padding: 1, children: [_jsx(Text, { color: "#f02a30", bold: true, children: "Project Access Request" }), _jsx(Text, { children: " " }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { children: "Project: " }), _jsx(Text, { color: "cyan", bold: true, children: projectName })] }), _jsxs(Text, { children: [_jsx(Text, { children: "Path: " }), _jsx(Text, { children: projectPath })] }), summary && (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [_jsx(Text, { children: "Type: " }), _jsx(Text, { children: summary.type })] }), _jsxs(Text, { children: [_jsx(Text, { children: "Files: " }), _jsxs(Text, { children: [summary.fileCount, " code files"] })] })] }))] }), _jsx(Text, { children: " " }), step === 'read' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "#f02a30", children: "Allow Codeep to read project files?" }), _jsx(Text, { children: " " }), _jsx(Text, { children: "This enables:" }), _jsx(Text, { children: " \u2022 Auto-detect file paths in your messages" }), _jsx(Text, { children: " \u2022 Send file contents to AI for analysis" }), _jsx(Text, { children: " \u2022 Project structure awareness" }), _jsx(Text, { children: " " }), _jsxs(Box, { children: [_jsx(Text, { color: "green", children: "[Y]" }), _jsx(Text, { children: " Yes (this session) " }), _jsx(Text, { color: "cyan", children: "[A]" }), _jsx(Text, { children: " Always " }), _jsx(Text, { color: "#f02a30", children: "[N]" }), _jsx(Text, { children: " No" })] })] })), step === 'write' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "green", children: "\u2713 Read permission granted" }), _jsx(Text, { children: " " }), _jsx(Text, { color: "#f02a30", children: "Allow Codeep to suggest file changes?" }), _jsx(Text, { children: " " }), _jsx(Text, { children: "This enables:" }), _jsx(Text, { children: " \u2022 AI can suggest code modifications" }), _jsx(Text, { children: " \u2022 You'll always see changes before applying" }), _jsx(Text, { children: " " }), _jsxs(Box, { children: [_jsx(Text, { color: "green", children: "[Y]" }), _jsx(Text, { children: " Yes (this session) " }), _jsx(Text, { color: "cyan", children: "[A]" }), _jsx(Text, { children: " Always " }), _jsx(Text, { color: "#f02a30", children: "[N]" }), _jsx(Text, { children: " Read-only" })] })] }))] }));
|
|
52
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { SearchResult } from '../utils/search';
|
|
3
|
+
interface SearchProps {
|
|
4
|
+
results: SearchResult[];
|
|
5
|
+
searchTerm: string;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
onSelectMessage: (index: number) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare const Search: React.FC<SearchProps>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
export const Search = ({ results, searchTerm, onClose, onSelectMessage }) => {
|
|
5
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
6
|
+
useInput((input, key) => {
|
|
7
|
+
if (key.escape) {
|
|
8
|
+
onClose();
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (key.upArrow) {
|
|
12
|
+
setSelectedIndex(i => Math.max(0, i - 1));
|
|
13
|
+
}
|
|
14
|
+
if (key.downArrow) {
|
|
15
|
+
setSelectedIndex(i => Math.min(results.length - 1, i + 1));
|
|
16
|
+
}
|
|
17
|
+
if (key.return && results.length > 0) {
|
|
18
|
+
onSelectMessage(results[selectedIndex].messageIndex);
|
|
19
|
+
onClose();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
if (results.length === 0) {
|
|
23
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#f02a30", padding: 1, children: [_jsx(Text, { color: "#f02a30", bold: true, children: "Search Results" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsx(Text, { children: "Query: " }), _jsxs(Text, { color: "cyan", children: ["\"", searchTerm, "\""] })] }), _jsx(Text, { children: " " }), _jsx(Text, { children: "No results found." }), _jsx(Text, { children: " " }), _jsx(Text, { children: "Press Esc to close" })] }));
|
|
24
|
+
}
|
|
25
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#f02a30", padding: 1, children: [_jsx(Text, { color: "#f02a30", bold: true, children: "Search Results" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsx(Text, { children: "Query: " }), _jsxs(Text, { color: "cyan", children: ["\"", searchTerm, "\""] }), _jsxs(Text, { children: [" (", results.length, " ", results.length === 1 ? 'result' : 'results', ")"] })] }), _jsx(Text, { children: " " }), results.map((result, i) => {
|
|
26
|
+
const isSelected = i === selectedIndex;
|
|
27
|
+
const roleColor = result.role === 'user' ? 'green' : 'blue';
|
|
28
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: i < results.length - 1 ? 1 : 0, children: [_jsxs(Text, { children: [isSelected ? _jsx(Text, { color: "#f02a30", children: "\u25B8 " }) : ' ', _jsxs(Text, { color: roleColor, bold: true, children: ["[", result.role.toUpperCase(), "]"] }), _jsxs(Text, { children: [" Message #", result.messageIndex + 1] })] }), _jsxs(Text, { children: [' ', _jsx(Text, { children: result.matchedText })] })] }, i));
|
|
29
|
+
}), _jsx(Text, { children: " " }), _jsx(Text, { children: "\u2191/\u2193 Navigate, Enter = Jump to message, Esc = Close" })] }));
|
|
30
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Message } from '../config/index';
|
|
3
|
+
interface SessionPickerProps {
|
|
4
|
+
onSelect: (messages: Message[], sessionName: string) => void;
|
|
5
|
+
onNewSession: () => void;
|
|
6
|
+
projectPath?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const SessionPicker: React.FC<SessionPickerProps>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useMemo } from 'react';
|
|
3
|
+
import { Text, Box, useInput } from 'ink';
|
|
4
|
+
import { listSessionsWithInfo, loadSession, startNewSession } from '../config/index';
|
|
5
|
+
/**
|
|
6
|
+
* Format relative time (e.g., "today", "yesterday", "3 days ago")
|
|
7
|
+
*/
|
|
8
|
+
function formatRelativeTime(dateStr) {
|
|
9
|
+
const date = new Date(dateStr);
|
|
10
|
+
const now = new Date();
|
|
11
|
+
const diffMs = now.getTime() - date.getTime();
|
|
12
|
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
13
|
+
if (diffDays === 0)
|
|
14
|
+
return 'today';
|
|
15
|
+
if (diffDays === 1)
|
|
16
|
+
return 'yesterday';
|
|
17
|
+
if (diffDays < 7)
|
|
18
|
+
return `${diffDays}d ago`;
|
|
19
|
+
if (diffDays < 30)
|
|
20
|
+
return `${Math.floor(diffDays / 7)}w ago`;
|
|
21
|
+
return `${Math.floor(diffDays / 30)}mo ago`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Format file size (e.g., "1.2 KB")
|
|
25
|
+
*/
|
|
26
|
+
function formatFileSize(bytes) {
|
|
27
|
+
if (bytes < 1024)
|
|
28
|
+
return `${bytes} B`;
|
|
29
|
+
if (bytes < 1024 * 1024)
|
|
30
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
31
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Truncate session name for display
|
|
35
|
+
*/
|
|
36
|
+
function truncateName(name, maxLength = 25) {
|
|
37
|
+
if (name.length <= maxLength)
|
|
38
|
+
return name;
|
|
39
|
+
return name.substring(0, maxLength - 3) + '...';
|
|
40
|
+
}
|
|
41
|
+
export const SessionPicker = ({ onSelect, onNewSession, projectPath }) => {
|
|
42
|
+
const sessions = useMemo(() => listSessionsWithInfo(projectPath), [projectPath]);
|
|
43
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
44
|
+
useInput((input, key) => {
|
|
45
|
+
// N = New session
|
|
46
|
+
if (input === 'n' || input === 'N') {
|
|
47
|
+
startNewSession();
|
|
48
|
+
onNewSession();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Arrow navigation
|
|
52
|
+
if (key.upArrow) {
|
|
53
|
+
setSelectedIndex(i => Math.max(0, i - 1));
|
|
54
|
+
}
|
|
55
|
+
if (key.downArrow) {
|
|
56
|
+
setSelectedIndex(i => Math.min(sessions.length - 1, i + 1));
|
|
57
|
+
}
|
|
58
|
+
// Enter = Load selected session
|
|
59
|
+
if (key.return && sessions.length > 0) {
|
|
60
|
+
const selected = sessions[selectedIndex];
|
|
61
|
+
const messages = loadSession(selected.name, projectPath);
|
|
62
|
+
if (messages) {
|
|
63
|
+
onSelect(messages, selected.name);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Escape = Start new session (same as N)
|
|
67
|
+
if (key.escape) {
|
|
68
|
+
startNewSession();
|
|
69
|
+
onNewSession();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
// If no sessions exist, auto-start new session
|
|
73
|
+
if (sessions.length === 0) {
|
|
74
|
+
// Will trigger on next render cycle
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
startNewSession();
|
|
77
|
+
onNewSession();
|
|
78
|
+
}, 0);
|
|
79
|
+
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(Text, { color: "cyan", children: "Starting new session..." }) }));
|
|
80
|
+
}
|
|
81
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Select a session:" }) }), _jsx(Box, { flexDirection: "column", marginBottom: 1, children: sessions.map((session, index) => {
|
|
82
|
+
const isSelected = index === selectedIndex;
|
|
83
|
+
const prefix = isSelected ? '→ ' : ' ';
|
|
84
|
+
const name = truncateName(session.name);
|
|
85
|
+
const meta = `${session.messageCount} msg, ${formatRelativeTime(session.createdAt)}`;
|
|
86
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ? 'green' : 'white', bold: isSelected, children: [prefix, name] }), _jsxs(Text, { color: "gray", children: [" (", meta, ")"] })] }, session.name));
|
|
87
|
+
}) }), _jsxs(Box, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { color: "yellow", children: "[N]" }), _jsx(Text, { children: " New session" })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "gray", children: "\u2191\u2193 Navigate " }), _jsx(Text, { color: "gray", children: "Enter Select " }), _jsx(Text, { color: "gray", children: "N New " }), _jsx(Text, { color: "gray", children: "Esc New" })] })] }));
|
|
88
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Message } from '../config/index';
|
|
3
|
+
interface SessionsProps {
|
|
4
|
+
history: Message[];
|
|
5
|
+
onLoad: (history: Message[], name: string) => void;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
onDelete?: (name: string) => void;
|
|
8
|
+
deleteMode?: boolean;
|
|
9
|
+
projectPath?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare const Sessions: React.FC<SessionsProps>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Text, Box, useInput } from 'ink';
|
|
4
|
+
import TextInput from 'ink-text-input';
|
|
5
|
+
import { listSessions, saveSession, loadSession, deleteSession } from '../config/index';
|
|
6
|
+
export const Sessions = ({ history, onLoad, onClose, onDelete, deleteMode = false, projectPath }) => {
|
|
7
|
+
const [name, setName] = useState('');
|
|
8
|
+
const [message, setMessage] = useState(deleteMode ? 'Select a session to delete (D or Enter)' : '');
|
|
9
|
+
const [sessions, setSessions] = useState(listSessions(projectPath));
|
|
10
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
11
|
+
const [confirmDelete, setConfirmDelete] = useState(null);
|
|
12
|
+
useInput((input, key) => {
|
|
13
|
+
// Handle delete confirmation
|
|
14
|
+
if (confirmDelete) {
|
|
15
|
+
if (input === 'y' || input === 'Y') {
|
|
16
|
+
if (deleteSession(confirmDelete, projectPath)) {
|
|
17
|
+
setMessage(`Deleted: ${confirmDelete}`);
|
|
18
|
+
setSessions(listSessions(projectPath));
|
|
19
|
+
if (onDelete)
|
|
20
|
+
onDelete(confirmDelete);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
setMessage('Failed to delete');
|
|
24
|
+
}
|
|
25
|
+
setConfirmDelete(null);
|
|
26
|
+
}
|
|
27
|
+
else if (input === 'n' || input === 'N' || key.escape) {
|
|
28
|
+
setConfirmDelete(null);
|
|
29
|
+
setMessage('Delete cancelled');
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (key.escape) {
|
|
34
|
+
onClose();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (key.upArrow && sessions.length > 0) {
|
|
38
|
+
setSelectedIndex(i => Math.max(0, i - 1));
|
|
39
|
+
setName(sessions[Math.max(0, selectedIndex - 1)] || '');
|
|
40
|
+
}
|
|
41
|
+
if (key.downArrow && sessions.length > 0) {
|
|
42
|
+
setSelectedIndex(i => Math.min(sessions.length - 1, i + 1));
|
|
43
|
+
setName(sessions[Math.min(sessions.length - 1, selectedIndex + 1)] || '');
|
|
44
|
+
}
|
|
45
|
+
// In delete mode, Enter also triggers delete
|
|
46
|
+
if (deleteMode && key.return) {
|
|
47
|
+
handleDelete();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// S = Save, L = Load, D = Delete
|
|
51
|
+
if (!deleteMode && (input === 's' || input === 'S')) {
|
|
52
|
+
handleSave();
|
|
53
|
+
}
|
|
54
|
+
if (!deleteMode && (input === 'l' || input === 'L')) {
|
|
55
|
+
handleLoad();
|
|
56
|
+
}
|
|
57
|
+
if (input === 'd' || input === 'D') {
|
|
58
|
+
handleDelete();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
const handleSave = () => {
|
|
62
|
+
if (!name.trim()) {
|
|
63
|
+
setMessage('Enter session name');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (history.length === 0) {
|
|
67
|
+
setMessage('Nothing to save');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (saveSession(name.trim(), history, projectPath)) {
|
|
71
|
+
setMessage(`Saved: ${name}`);
|
|
72
|
+
setTimeout(onClose, 1000);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
setMessage('Failed to save');
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const handleLoad = () => {
|
|
79
|
+
if (!name.trim()) {
|
|
80
|
+
setMessage('Enter session name');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const loaded = loadSession(name.trim(), projectPath);
|
|
84
|
+
if (loaded) {
|
|
85
|
+
onLoad(loaded, name.trim());
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
setMessage('Session not found');
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const handleDelete = () => {
|
|
92
|
+
const sessionName = name.trim() || (sessions.length > 0 ? sessions[selectedIndex] : '');
|
|
93
|
+
if (!sessionName) {
|
|
94
|
+
setMessage('Select a session to delete');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Ask for confirmation
|
|
98
|
+
setConfirmDelete(sessionName);
|
|
99
|
+
setMessage(`Delete "${sessionName}"? (Y/N)`);
|
|
100
|
+
};
|
|
101
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#f02a30", padding: 1, children: [_jsx(Text, { color: "#f02a30", bold: true, children: deleteMode ? 'Delete Session' : 'Sessions' }), _jsx(Text, { children: " " }), !deleteMode && (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Text, { color: "#f02a30", children: "Name: " }), _jsx(TextInput, { value: name, onChange: setName, placeholder: "session name..." })] }), _jsx(Text, { children: " " }), _jsxs(Text, { children: ["Actions: ", _jsx(Text, { color: "#f02a30", children: "S" }), "=Save ", _jsx(Text, { color: "#f02a30", children: "L" }), "=Load ", _jsx(Text, { color: "#f02a30", children: "D" }), "=Delete ", _jsx(Text, { color: "#f02a30", children: "Esc" }), "=Close"] })] })), deleteMode && (_jsx(_Fragment, { children: _jsxs(Text, { children: ["Actions: ", _jsx(Text, { color: "#f02a30", children: "Enter/D" }), "=Delete ", _jsx(Text, { color: "#f02a30", children: "Esc" }), "=Cancel"] }) })), sessions.length > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { children: "Saved sessions (\u2191/\u2193 to select):" }), sessions.map((s, i) => (_jsxs(Text, { children: [i === selectedIndex ? _jsx(Text, { color: "#f02a30", children: "\u25B8 " }) : ' ', _jsx(Text, { color: i === selectedIndex ? '#f02a30' : undefined, children: s })] }, s)))] })), message && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: "cyan", children: message })] }))] }));
|
|
102
|
+
};
|