gencode-ai 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/.env.example +11 -0
- package/CLAUDE.md +70 -0
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/dist/agent/agent.d.ts +84 -0
- package/dist/agent/agent.d.ts.map +1 -0
- package/dist/agent/agent.js +233 -0
- package/dist/agent/agent.js.map +1 -0
- package/dist/agent/index.d.ts +6 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +6 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/types.d.ts +47 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +5 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/cli/components/App.d.ts +14 -0
- package/dist/cli/components/App.d.ts.map +1 -0
- package/dist/cli/components/App.js +395 -0
- package/dist/cli/components/App.js.map +1 -0
- package/dist/cli/components/CommandSuggestions.d.ts +13 -0
- package/dist/cli/components/CommandSuggestions.d.ts.map +1 -0
- package/dist/cli/components/CommandSuggestions.js +32 -0
- package/dist/cli/components/CommandSuggestions.js.map +1 -0
- package/dist/cli/components/Header.d.ts +9 -0
- package/dist/cli/components/Header.d.ts.map +1 -0
- package/dist/cli/components/Header.js +13 -0
- package/dist/cli/components/Header.js.map +1 -0
- package/dist/cli/components/Input.d.ts +13 -0
- package/dist/cli/components/Input.d.ts.map +1 -0
- package/dist/cli/components/Input.js +27 -0
- package/dist/cli/components/Input.js.map +1 -0
- package/dist/cli/components/Logo.d.ts +2 -0
- package/dist/cli/components/Logo.d.ts.map +1 -0
- package/dist/cli/components/Logo.js +8 -0
- package/dist/cli/components/Logo.js.map +1 -0
- package/dist/cli/components/Messages.d.ts +37 -0
- package/dist/cli/components/Messages.d.ts.map +1 -0
- package/dist/cli/components/Messages.js +106 -0
- package/dist/cli/components/Messages.js.map +1 -0
- package/dist/cli/components/ModelSelector.d.ts +13 -0
- package/dist/cli/components/ModelSelector.d.ts.map +1 -0
- package/dist/cli/components/ModelSelector.js +72 -0
- package/dist/cli/components/ModelSelector.js.map +1 -0
- package/dist/cli/components/Spinner.d.ts +12 -0
- package/dist/cli/components/Spinner.d.ts.map +1 -0
- package/dist/cli/components/Spinner.js +45 -0
- package/dist/cli/components/Spinner.js.map +1 -0
- package/dist/cli/components/index.d.ts +12 -0
- package/dist/cli/components/index.d.ts.map +1 -0
- package/dist/cli/components/index.js +12 -0
- package/dist/cli/components/index.js.map +1 -0
- package/dist/cli/components/theme.d.ts +31 -0
- package/dist/cli/components/theme.d.ts.map +1 -0
- package/dist/cli/components/theme.js +36 -0
- package/dist/cli/components/theme.js.map +1 -0
- package/dist/cli/index-legacy.d.ts +7 -0
- package/dist/cli/index-legacy.d.ts.map +1 -0
- package/dist/cli/index-legacy.js +431 -0
- package/dist/cli/index-legacy.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +116 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/ink-cli.d.ts +7 -0
- package/dist/cli/ink-cli.d.ts.map +1 -0
- package/dist/cli/ink-cli.js +105 -0
- package/dist/cli/ink-cli.js.map +1 -0
- package/dist/cli/session-picker.d.ts +16 -0
- package/dist/cli/session-picker.d.ts.map +1 -0
- package/dist/cli/session-picker.js +280 -0
- package/dist/cli/session-picker.js.map +1 -0
- package/dist/cli/ui.d.ts +61 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +364 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +6 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/manager.d.ts +31 -0
- package/dist/config/manager.d.ts.map +1 -0
- package/dist/config/manager.js +65 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/config/types.d.ts +22 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +6 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/index.d.ts +10 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +9 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/init.d.ts +20 -0
- package/dist/memory/init.d.ts.map +1 -0
- package/dist/memory/init.js +332 -0
- package/dist/memory/init.js.map +1 -0
- package/dist/memory/manager.d.ts +85 -0
- package/dist/memory/manager.d.ts.map +1 -0
- package/dist/memory/manager.js +234 -0
- package/dist/memory/manager.js.map +1 -0
- package/dist/memory/types.d.ts +74 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +6 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/permissions/index.d.ts +7 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +6 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/permissions/manager.d.ts +32 -0
- package/dist/permissions/manager.d.ts.map +1 -0
- package/dist/permissions/manager.js +79 -0
- package/dist/permissions/manager.js.map +1 -0
- package/dist/permissions/types.d.ts +14 -0
- package/dist/permissions/types.d.ts.map +1 -0
- package/dist/permissions/types.js +17 -0
- package/dist/permissions/types.js.map +1 -0
- package/dist/providers/anthropic.d.ts +20 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +185 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/gemini.d.ts +21 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +241 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/index.d.ts +34 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +72 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai.d.ts +19 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +221 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/types.d.ts +125 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +6 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/session/index.d.ts +6 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +6 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/manager.d.ts +101 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +295 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/session/types.d.ts +39 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +10 -0
- package/dist/session/types.js.map +1 -0
- package/dist/tools/builtin/bash.d.ts +7 -0
- package/dist/tools/builtin/bash.d.ts.map +1 -0
- package/dist/tools/builtin/bash.js +80 -0
- package/dist/tools/builtin/bash.js.map +1 -0
- package/dist/tools/builtin/edit.d.ts +7 -0
- package/dist/tools/builtin/edit.d.ts.map +1 -0
- package/dist/tools/builtin/edit.js +32 -0
- package/dist/tools/builtin/edit.js.map +1 -0
- package/dist/tools/builtin/glob.d.ts +7 -0
- package/dist/tools/builtin/glob.d.ts.map +1 -0
- package/dist/tools/builtin/glob.js +36 -0
- package/dist/tools/builtin/glob.js.map +1 -0
- package/dist/tools/builtin/grep.d.ts +7 -0
- package/dist/tools/builtin/grep.d.ts.map +1 -0
- package/dist/tools/builtin/grep.js +59 -0
- package/dist/tools/builtin/grep.js.map +1 -0
- package/dist/tools/builtin/read.d.ts +7 -0
- package/dist/tools/builtin/read.d.ts.map +1 -0
- package/dist/tools/builtin/read.js +29 -0
- package/dist/tools/builtin/read.js.map +1 -0
- package/dist/tools/builtin/write.d.ts +7 -0
- package/dist/tools/builtin/write.d.ts.map +1 -0
- package/dist/tools/builtin/write.js +24 -0
- package/dist/tools/builtin/write.js.map +1 -0
- package/dist/tools/index.d.ts +38 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +32 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/registry.d.ts +22 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +71 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/types.d.ts +62 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +126 -0
- package/dist/tools/types.js.map +1 -0
- package/docs/README.md +16 -0
- package/docs/proposals/0001-web-fetch-tool.md +293 -0
- package/docs/proposals/0002-web-search-tool.md +306 -0
- package/docs/proposals/0003-task-subagents.md +333 -0
- package/docs/proposals/0004-plan-mode.md +338 -0
- package/docs/proposals/0005-todo-system.md +299 -0
- package/docs/proposals/0006-memory-system.md +539 -0
- package/docs/proposals/0007-context-management.md +429 -0
- package/docs/proposals/0008-checkpointing.md +327 -0
- package/docs/proposals/0009-hooks-system.md +343 -0
- package/docs/proposals/0010-mcp-integration.md +382 -0
- package/docs/proposals/0011-custom-commands.md +374 -0
- package/docs/proposals/0012-ask-user-question.md +317 -0
- package/docs/proposals/0013-multi-edit-tool.md +345 -0
- package/docs/proposals/0014-lsp-tool.md +478 -0
- package/docs/proposals/0015-ls-tool.md +407 -0
- package/docs/proposals/0016-kill-shell-tool.md +455 -0
- package/docs/proposals/0017-background-tasks.md +489 -0
- package/docs/proposals/0018-parallel-tool-execution.md +415 -0
- package/docs/proposals/0019-session-enhancements.md +462 -0
- package/docs/proposals/0020-session-summarization.md +447 -0
- package/docs/proposals/0021-skills-system.md +409 -0
- package/docs/proposals/0022-plugin-system.md +467 -0
- package/docs/proposals/0023-permission-enhancements.md +470 -0
- package/docs/proposals/0024-keyboard-shortcuts.md +443 -0
- package/docs/proposals/0025-cost-tracking.md +447 -0
- package/docs/proposals/0026-git-integration.md +475 -0
- package/docs/proposals/0027-enhanced-read-tool.md +514 -0
- package/docs/proposals/0028-enhanced-bash-tool.md +511 -0
- package/docs/proposals/0029-notebook-edit-tool.md +413 -0
- package/docs/proposals/0030-plugin-marketplace.md +360 -0
- package/docs/proposals/0031-command-suggestions.md +295 -0
- package/docs/proposals/0032-ide-integrations.md +328 -0
- package/docs/proposals/0033-enterprise-deployment.md +221 -0
- package/docs/proposals/0034-sandboxing.md +273 -0
- package/docs/proposals/0035-auto-updater.md +311 -0
- package/docs/proposals/0036-enhanced-glob-tool.md +267 -0
- package/docs/proposals/0037-enhanced-grep-tool.md +360 -0
- package/docs/proposals/0038-interactive-cli-ui.md +373 -0
- package/docs/proposals/0039-streaming-enhancements.md +359 -0
- package/docs/proposals/0040-multi-provider-enhancements.md +369 -0
- package/docs/proposals/README.md +84 -0
- package/docs/proposals/TEMPLATE.md +57 -0
- package/docs/proposals/research/claude-code-research.md +307 -0
- package/examples/agent-demo.ts +115 -0
- package/examples/basic.ts +166 -0
- package/package.json +50 -0
- package/src/agent/agent.ts +276 -0
- package/src/agent/index.ts +6 -0
- package/src/agent/types.ts +62 -0
- package/src/cli/components/App.tsx +565 -0
- package/src/cli/components/CommandSuggestions.tsx +58 -0
- package/src/cli/components/Header.tsx +36 -0
- package/src/cli/components/Input.tsx +60 -0
- package/src/cli/components/Logo.tsx +16 -0
- package/src/cli/components/Messages.tsx +210 -0
- package/src/cli/components/ModelSelector.tsx +135 -0
- package/src/cli/components/Spinner.tsx +72 -0
- package/src/cli/components/index.ts +21 -0
- package/src/cli/components/theme.ts +36 -0
- package/src/cli/index.tsx +136 -0
- package/src/config/index.ts +7 -0
- package/src/config/manager.ts +77 -0
- package/src/config/types.ts +25 -0
- package/src/index.ts +86 -0
- package/src/permissions/index.ts +7 -0
- package/src/permissions/manager.ts +97 -0
- package/src/permissions/types.ts +29 -0
- package/src/providers/anthropic.ts +224 -0
- package/src/providers/gemini.ts +295 -0
- package/src/providers/index.ts +97 -0
- package/src/providers/openai.ts +261 -0
- package/src/providers/types.ts +181 -0
- package/src/session/index.ts +6 -0
- package/src/session/manager.ts +354 -0
- package/src/session/types.ts +49 -0
- package/src/tools/builtin/bash.ts +92 -0
- package/src/tools/builtin/edit.ts +37 -0
- package/src/tools/builtin/glob.ts +42 -0
- package/src/tools/builtin/grep.ts +67 -0
- package/src/tools/builtin/read.ts +34 -0
- package/src/tools/builtin/write.ts +27 -0
- package/src/tools/index.ts +36 -0
- package/src/tools/registry.ts +83 -0
- package/src/tools/types.ts +172 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Box, Text, useInput } from 'ink';
|
|
2
|
+
import TextInput from 'ink-text-input';
|
|
3
|
+
import { colors, icons } from './theme.js';
|
|
4
|
+
|
|
5
|
+
interface PromptInputProps {
|
|
6
|
+
value: string;
|
|
7
|
+
onChange: (value: string) => void;
|
|
8
|
+
onSubmit: (value: string) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function PromptInput({ value, onChange, onSubmit }: PromptInputProps) {
|
|
12
|
+
const handleSubmit = (text: string) => {
|
|
13
|
+
if (text.trim()) {
|
|
14
|
+
onSubmit(text);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Get terminal full width for border
|
|
19
|
+
const width = process.stdout.columns || 80;
|
|
20
|
+
const border = '─'.repeat(width);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Box flexDirection="column" marginTop={0}>
|
|
24
|
+
<Text color={colors.textMuted}>{border}</Text>
|
|
25
|
+
<Box>
|
|
26
|
+
<Text color={colors.brand}>{icons.prompt} </Text>
|
|
27
|
+
<TextInput
|
|
28
|
+
value={value}
|
|
29
|
+
onChange={onChange}
|
|
30
|
+
onSubmit={handleSubmit}
|
|
31
|
+
placeholder=""
|
|
32
|
+
/>
|
|
33
|
+
</Box>
|
|
34
|
+
<Text color={colors.textMuted}>{border}</Text>
|
|
35
|
+
</Box>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ConfirmPromptProps {
|
|
40
|
+
message: string;
|
|
41
|
+
onConfirm: (confirmed: boolean) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function ConfirmPrompt({ message, onConfirm }: ConfirmPromptProps) {
|
|
45
|
+
useInput((input, key) => {
|
|
46
|
+
if (input.toLowerCase() === 'y' || key.return) {
|
|
47
|
+
onConfirm(true);
|
|
48
|
+
} else if (input.toLowerCase() === 'n' || key.escape) {
|
|
49
|
+
onConfirm(false);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Box>
|
|
55
|
+
<Text color={colors.warning}>{icons.warning} </Text>
|
|
56
|
+
<Text>{message} </Text>
|
|
57
|
+
<Text color={colors.textMuted}>[y/n] </Text>
|
|
58
|
+
</Box>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Box, Text } from 'ink';
|
|
2
|
+
|
|
3
|
+
export function Logo() {
|
|
4
|
+
// Full G with 3D shadow - subdued slate color
|
|
5
|
+
const slateColor = "#64748B"; // Slate 500 - stable, professional
|
|
6
|
+
return (
|
|
7
|
+
<Box flexDirection="column" marginRight={1}>
|
|
8
|
+
<Text color={slateColor}> ██████╗ </Text>
|
|
9
|
+
<Text color={slateColor}> ██╔════╝ </Text>
|
|
10
|
+
<Text color={slateColor}> ██║ ███╗</Text>
|
|
11
|
+
<Text color={slateColor}> ██║ ██║</Text>
|
|
12
|
+
<Text color={slateColor}> ╚██████╔╝</Text>
|
|
13
|
+
<Text color={slateColor}> ╚═════╝ </Text>
|
|
14
|
+
</Box>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { Box, Text } from 'ink';
|
|
2
|
+
import { colors, icons } from './theme.js';
|
|
3
|
+
|
|
4
|
+
// Word wrap text to terminal width
|
|
5
|
+
function wrapText(text: string, width: number): string[] {
|
|
6
|
+
const lines: string[] = [];
|
|
7
|
+
for (const line of text.split('\n')) {
|
|
8
|
+
if (line.length <= width) {
|
|
9
|
+
lines.push(line);
|
|
10
|
+
} else {
|
|
11
|
+
// Simple word wrap
|
|
12
|
+
let currentLine = '';
|
|
13
|
+
const words = line.split(' ');
|
|
14
|
+
for (const word of words) {
|
|
15
|
+
if (currentLine.length + word.length + 1 <= width) {
|
|
16
|
+
currentLine += (currentLine ? ' ' : '') + word;
|
|
17
|
+
} else {
|
|
18
|
+
if (currentLine) lines.push(currentLine);
|
|
19
|
+
currentLine = word;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (currentLine) lines.push(currentLine);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return lines;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface UserMessageProps {
|
|
29
|
+
text: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function UserMessage({ text }: UserMessageProps) {
|
|
33
|
+
const lines = text.trimEnd().split('\n');
|
|
34
|
+
return (
|
|
35
|
+
<Box flexDirection="column" marginTop={1} marginBottom={0}>
|
|
36
|
+
{lines.map((line, i) => (
|
|
37
|
+
<Box key={i}>
|
|
38
|
+
<Text color={colors.brand}>{icons.userPrompt} </Text>
|
|
39
|
+
<Text backgroundColor="#1E293B" color={colors.text}> {line} </Text>
|
|
40
|
+
</Box>
|
|
41
|
+
))}
|
|
42
|
+
</Box>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface AssistantMessageProps {
|
|
47
|
+
text: string;
|
|
48
|
+
streaming?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function AssistantMessage({ text, streaming }: AssistantMessageProps) {
|
|
52
|
+
if (!text) return null;
|
|
53
|
+
|
|
54
|
+
// Get terminal width for wrapping
|
|
55
|
+
const termWidth = process.stdout.columns || 80;
|
|
56
|
+
const contentWidth = termWidth - 4; // Account for prefix
|
|
57
|
+
|
|
58
|
+
// Wrap text to terminal width
|
|
59
|
+
const lines = wrapText(text.trimEnd(), contentWidth);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Box flexDirection="column" marginTop={1} marginBottom={0}>
|
|
63
|
+
{lines.map((line, i) => (
|
|
64
|
+
<Box key={i}>
|
|
65
|
+
{i === 0 && <Text color={colors.success}>{icons.assistant} </Text>}
|
|
66
|
+
{i > 0 && <Text> </Text>}
|
|
67
|
+
<Text>
|
|
68
|
+
{line}
|
|
69
|
+
{streaming && i === lines.length - 1 ? (
|
|
70
|
+
<Text color={colors.brandLight}>{icons.cursor}</Text>
|
|
71
|
+
) : (
|
|
72
|
+
''
|
|
73
|
+
)}
|
|
74
|
+
</Text>
|
|
75
|
+
</Box>
|
|
76
|
+
))}
|
|
77
|
+
</Box>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface ToolCallProps {
|
|
82
|
+
name: string;
|
|
83
|
+
input: Record<string, unknown>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function ToolCall({ name, input }: ToolCallProps) {
|
|
87
|
+
const inputStr = JSON.stringify(input);
|
|
88
|
+
const shortInput = inputStr.length > 50 ? inputStr.slice(0, 47) + '...' : inputStr;
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Box marginLeft={2}>
|
|
92
|
+
<Text dimColor>
|
|
93
|
+
<Text color={colors.tool}>{icons.tool}</Text> {name}{' '}
|
|
94
|
+
<Text color={colors.textMuted}>{shortInput}</Text>
|
|
95
|
+
</Text>
|
|
96
|
+
</Box>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface ToolResultProps {
|
|
101
|
+
name: string;
|
|
102
|
+
success: boolean;
|
|
103
|
+
output: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function ToolResult({ name, success, output }: ToolResultProps) {
|
|
107
|
+
const firstLine = output.split('\n')[0]?.trim() || '';
|
|
108
|
+
const displayOutput = firstLine.length > 50 ? firstLine.slice(0, 47) + '...' : firstLine;
|
|
109
|
+
const statusColor = success ? colors.success : colors.error;
|
|
110
|
+
const statusIcon = success ? icons.success : icons.error;
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<Box marginLeft={2}>
|
|
114
|
+
<Text dimColor>
|
|
115
|
+
<Text color={statusColor}>{statusIcon}</Text> {name}{' '}
|
|
116
|
+
<Text color={colors.textMuted}>{displayOutput}</Text>
|
|
117
|
+
</Text>
|
|
118
|
+
</Box>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface InfoMessageProps {
|
|
123
|
+
text: string;
|
|
124
|
+
type?: 'info' | 'success' | 'warning' | 'error';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function InfoMessage({ text, type = 'info' }: InfoMessageProps) {
|
|
128
|
+
const config = {
|
|
129
|
+
info: { color: colors.info, icon: icons.info },
|
|
130
|
+
success: { color: colors.success, icon: icons.success },
|
|
131
|
+
warning: { color: colors.warning, icon: icons.warning },
|
|
132
|
+
error: { color: colors.error, icon: icons.error },
|
|
133
|
+
};
|
|
134
|
+
const { color, icon } = config[type];
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<Box>
|
|
138
|
+
<Text color={color}>{icon} </Text>
|
|
139
|
+
<Text color={colors.textSecondary}>{text}</Text>
|
|
140
|
+
</Box>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function Separator() {
|
|
145
|
+
const width = process.stdout.columns || 80;
|
|
146
|
+
return <Text color={colors.separator}>{'─'.repeat(width)}</Text>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface WelcomeMessageProps {
|
|
150
|
+
model: string;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function WelcomeMessage({ model }: WelcomeMessageProps) {
|
|
154
|
+
return (
|
|
155
|
+
<Box marginTop={1} marginBottom={0}>
|
|
156
|
+
<Text color={colors.textMuted}>Welcome to </Text>
|
|
157
|
+
<Text color={colors.brand}>{model}</Text>
|
|
158
|
+
</Box>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function ShortcutsHint() {
|
|
163
|
+
return (
|
|
164
|
+
<Box marginTop={1}>
|
|
165
|
+
<Text color={colors.textMuted}> ? for shortcuts</Text>
|
|
166
|
+
</Box>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Random verbs for completion message (Claude Code style)
|
|
171
|
+
const COMPLETION_VERBS = [
|
|
172
|
+
'Baked',
|
|
173
|
+
'Crafted',
|
|
174
|
+
'Brewed',
|
|
175
|
+
'Cooked',
|
|
176
|
+
'Forged',
|
|
177
|
+
'Built',
|
|
178
|
+
'Woven',
|
|
179
|
+
'Assembled',
|
|
180
|
+
'Conjured',
|
|
181
|
+
'Rendered',
|
|
182
|
+
'Compiled',
|
|
183
|
+
'Distilled',
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
function formatDuration(ms: number): string {
|
|
187
|
+
const totalSecs = Math.floor(ms / 1000);
|
|
188
|
+
const mins = Math.floor(totalSecs / 60);
|
|
189
|
+
const secs = totalSecs % 60;
|
|
190
|
+
if (mins > 0) {
|
|
191
|
+
return `${mins}m ${secs}s`;
|
|
192
|
+
}
|
|
193
|
+
return `${secs}s`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
interface CompletionMessageProps {
|
|
197
|
+
durationMs: number;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function CompletionMessage({ durationMs }: CompletionMessageProps) {
|
|
201
|
+
// Pick a random verb (stable per render via useMemo would be better, but keep simple)
|
|
202
|
+
const verb = COMPLETION_VERBS[Math.floor(Math.random() * COMPLETION_VERBS.length)];
|
|
203
|
+
return (
|
|
204
|
+
<Box marginTop={1}>
|
|
205
|
+
<Text color={colors.textMuted}>
|
|
206
|
+
✻ {verb} for {formatDuration(durationMs)}
|
|
207
|
+
</Text>
|
|
208
|
+
</Box>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Selector Component - Interactive model selection with fuzzy filter
|
|
3
|
+
*/
|
|
4
|
+
import { useState, useEffect } from 'react';
|
|
5
|
+
import { Box, Text, useInput } from 'ink';
|
|
6
|
+
import TextInput from 'ink-text-input';
|
|
7
|
+
import { colors, icons } from './theme.js';
|
|
8
|
+
import { LoadingSpinner } from './Spinner.js';
|
|
9
|
+
|
|
10
|
+
interface Model {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ModelSelectorProps {
|
|
16
|
+
currentModel: string;
|
|
17
|
+
onSelect: (modelId: string) => void;
|
|
18
|
+
onCancel: () => void;
|
|
19
|
+
listModels: () => Promise<Model[]>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ModelSelector({
|
|
23
|
+
currentModel,
|
|
24
|
+
onSelect,
|
|
25
|
+
onCancel,
|
|
26
|
+
listModels,
|
|
27
|
+
}: ModelSelectorProps) {
|
|
28
|
+
const [models, setModels] = useState<Model[]>([]);
|
|
29
|
+
const [loading, setLoading] = useState(true);
|
|
30
|
+
const [error, setError] = useState<string | null>(null);
|
|
31
|
+
const [filter, setFilter] = useState('');
|
|
32
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const fetchModels = async () => {
|
|
36
|
+
try {
|
|
37
|
+
const result = await listModels();
|
|
38
|
+
setModels(result);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
setError(err instanceof Error ? err.message : 'Failed to fetch models');
|
|
41
|
+
} finally {
|
|
42
|
+
setLoading(false);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
fetchModels();
|
|
46
|
+
}, [listModels]);
|
|
47
|
+
|
|
48
|
+
// Fuzzy filter models
|
|
49
|
+
const filtered = models.filter(
|
|
50
|
+
(m) =>
|
|
51
|
+
m.id.toLowerCase().includes(filter.toLowerCase()) ||
|
|
52
|
+
m.name.toLowerCase().includes(filter.toLowerCase())
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Reset selection when filter changes
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
setSelectedIndex(0);
|
|
58
|
+
}, [filter]);
|
|
59
|
+
|
|
60
|
+
// Keyboard navigation
|
|
61
|
+
useInput((input, key) => {
|
|
62
|
+
if (key.upArrow) {
|
|
63
|
+
setSelectedIndex((i) => Math.max(0, i - 1));
|
|
64
|
+
} else if (key.downArrow) {
|
|
65
|
+
setSelectedIndex((i) => Math.min(filtered.length - 1, i + 1));
|
|
66
|
+
} else if (key.return) {
|
|
67
|
+
if (filtered.length > 0) {
|
|
68
|
+
onSelect(filtered[selectedIndex].id);
|
|
69
|
+
}
|
|
70
|
+
} else if (key.escape) {
|
|
71
|
+
onCancel();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (loading) {
|
|
76
|
+
return (
|
|
77
|
+
<Box>
|
|
78
|
+
<LoadingSpinner />
|
|
79
|
+
<Text color={colors.textMuted}> Loading models...</Text>
|
|
80
|
+
</Box>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (error) {
|
|
85
|
+
onCancel();
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const maxVisible = 8;
|
|
90
|
+
const startIndex = Math.max(
|
|
91
|
+
0,
|
|
92
|
+
Math.min(selectedIndex - Math.floor(maxVisible / 2), filtered.length - maxVisible)
|
|
93
|
+
);
|
|
94
|
+
const visibleModels = filtered.slice(startIndex, startIndex + maxVisible);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<Box flexDirection="column">
|
|
98
|
+
<Box>
|
|
99
|
+
<Text color={colors.primary}>{icons.prompt} </Text>
|
|
100
|
+
<TextInput
|
|
101
|
+
value={filter}
|
|
102
|
+
onChange={setFilter}
|
|
103
|
+
placeholder="Type to filter models..."
|
|
104
|
+
/>
|
|
105
|
+
</Box>
|
|
106
|
+
<Box flexDirection="column" marginTop={1}>
|
|
107
|
+
{visibleModels.length === 0 ? (
|
|
108
|
+
<Text color={colors.textMuted}>No models match "{filter}"</Text>
|
|
109
|
+
) : (
|
|
110
|
+
visibleModels.map((m, i) => {
|
|
111
|
+
const actualIndex = startIndex + i;
|
|
112
|
+
const isSelected = actualIndex === selectedIndex;
|
|
113
|
+
const isCurrent = m.id === currentModel;
|
|
114
|
+
return (
|
|
115
|
+
<Box key={m.id}>
|
|
116
|
+
<Text color={isSelected ? colors.primary : colors.textMuted}>
|
|
117
|
+
{isSelected ? icons.arrow : ' '}
|
|
118
|
+
</Text>
|
|
119
|
+
<Text color={isSelected ? colors.text : colors.textSecondary} bold={isSelected}>
|
|
120
|
+
{m.name}
|
|
121
|
+
</Text>
|
|
122
|
+
{isCurrent && <Text color={colors.success}> (current)</Text>}
|
|
123
|
+
</Box>
|
|
124
|
+
);
|
|
125
|
+
})
|
|
126
|
+
)}
|
|
127
|
+
</Box>
|
|
128
|
+
<Box marginTop={1}>
|
|
129
|
+
<Text color={colors.textMuted}>
|
|
130
|
+
{filtered.length} models · ↑↓ navigate · Enter select · Esc cancel
|
|
131
|
+
</Text>
|
|
132
|
+
</Box>
|
|
133
|
+
</Box>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spinner Component - Compact thinking animation (Claude Code style)
|
|
3
|
+
*/
|
|
4
|
+
import { useState, useEffect } from 'react';
|
|
5
|
+
import { Box, Text } from 'ink';
|
|
6
|
+
import InkSpinner from 'ink-spinner';
|
|
7
|
+
import { colors } from './theme.js';
|
|
8
|
+
|
|
9
|
+
interface SpinnerProps {
|
|
10
|
+
text?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function ThinkingSpinner({ text = 'Thinking' }: SpinnerProps) {
|
|
14
|
+
return (
|
|
15
|
+
<Box marginTop={1} marginBottom={0}>
|
|
16
|
+
<Text color={colors.brand}>
|
|
17
|
+
<InkSpinner type="dots" />
|
|
18
|
+
</Text>
|
|
19
|
+
<Text color={colors.textSecondary}> {text}</Text>
|
|
20
|
+
<Text color={colors.textMuted}> · ctrl+c to stop</Text>
|
|
21
|
+
</Box>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function LoadingSpinner({ text = 'Loading...' }: SpinnerProps) {
|
|
26
|
+
return (
|
|
27
|
+
<Box>
|
|
28
|
+
<Text color={colors.textSecondary}>
|
|
29
|
+
<InkSpinner type="dots" />
|
|
30
|
+
</Text>
|
|
31
|
+
<Text color={colors.textMuted}> {text}</Text>
|
|
32
|
+
</Box>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Progress bar animation for processing state
|
|
38
|
+
* Bouncing ball with trail effect
|
|
39
|
+
*/
|
|
40
|
+
export function ProgressBar() {
|
|
41
|
+
const [frame, setFrame] = useState(0);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const timer = setInterval(() => {
|
|
45
|
+
setFrame((f) => (f + 1) % 14);
|
|
46
|
+
}, 100);
|
|
47
|
+
return () => clearInterval(timer);
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
// Bouncing ball animation: ball moves left-right with trail
|
|
51
|
+
const width = 7;
|
|
52
|
+
// Frame 0-6: left to right, 7-13: right to left
|
|
53
|
+
const pos = frame < 7 ? frame : 13 - frame;
|
|
54
|
+
|
|
55
|
+
let bar = '';
|
|
56
|
+
for (let i = 0; i < width; i++) {
|
|
57
|
+
if (i === pos) {
|
|
58
|
+
bar += '●';
|
|
59
|
+
} else if (i === pos - 1 || i === pos + 1) {
|
|
60
|
+
bar += '○';
|
|
61
|
+
} else {
|
|
62
|
+
bar += '·';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Box>
|
|
68
|
+
<Text color={colors.brand}>{bar}</Text>
|
|
69
|
+
<Text color={colors.textMuted}> esc to stop</Text>
|
|
70
|
+
</Box>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ink Components Index
|
|
3
|
+
*/
|
|
4
|
+
export { App } from './App.js';
|
|
5
|
+
export { Header, Welcome } from './Header.js';
|
|
6
|
+
export {
|
|
7
|
+
UserMessage,
|
|
8
|
+
AssistantMessage,
|
|
9
|
+
ToolCall,
|
|
10
|
+
ToolResult,
|
|
11
|
+
InfoMessage,
|
|
12
|
+
Separator,
|
|
13
|
+
WelcomeMessage,
|
|
14
|
+
ShortcutsHint,
|
|
15
|
+
CompletionMessage,
|
|
16
|
+
} from './Messages.js';
|
|
17
|
+
export { ThinkingSpinner, LoadingSpinner, ProgressBar } from './Spinner.js';
|
|
18
|
+
export { PromptInput, ConfirmPrompt } from './Input.js';
|
|
19
|
+
export { colors, icons } from './theme.js';
|
|
20
|
+
export { ModelSelector } from './ModelSelector.js';
|
|
21
|
+
export { CommandSuggestions, COMMANDS, getFilteredCommands } from './CommandSuggestions.js';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Configuration - Claude Code inspired
|
|
3
|
+
*/
|
|
4
|
+
export const colors = {
|
|
5
|
+
brand: '#818CF8', // Indigo 400
|
|
6
|
+
brandLight: '#A5B4FC', // Indigo 300
|
|
7
|
+
primary: '#818CF8',
|
|
8
|
+
success: '#34D399', // Emerald 400
|
|
9
|
+
warning: '#FBBF24', // Amber 400
|
|
10
|
+
error: '#F87171', // Red 400
|
|
11
|
+
info: '#60A5FA', // Blue 400
|
|
12
|
+
text: '#F1F5F9', // Slate 100
|
|
13
|
+
textSecondary: '#94A3B8', // Slate 400
|
|
14
|
+
textMuted: '#64748B', // Slate 500
|
|
15
|
+
tool: '#C084FC', // Purple 400
|
|
16
|
+
separator: '#1E293B', // Slate 800
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const icons = {
|
|
20
|
+
// Message prefixes (Claude Code style)
|
|
21
|
+
userPrompt: '❯', // Chevron for user input
|
|
22
|
+
assistant: '●', // Filled circle for assistant
|
|
23
|
+
// Prompt
|
|
24
|
+
prompt: '❯',
|
|
25
|
+
// Status
|
|
26
|
+
success: '✔',
|
|
27
|
+
error: '✖',
|
|
28
|
+
warning: '⚠',
|
|
29
|
+
info: 'ℹ',
|
|
30
|
+
// Tools
|
|
31
|
+
tool: '⚡', // Lightning for tools
|
|
32
|
+
arrow: '→',
|
|
33
|
+
// UI
|
|
34
|
+
thinking: '✱', // Star for thinking state
|
|
35
|
+
cursor: '▋',
|
|
36
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* GenCode CLI - Modern Ink-based Interactive Agent Interface
|
|
4
|
+
* Beautiful terminal UI with React components
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import 'dotenv/config';
|
|
8
|
+
import { render } from 'ink';
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { App } from './components/App.js';
|
|
11
|
+
import type { AgentConfig } from '../agent/types.js';
|
|
12
|
+
import { SettingsManager, type Settings } from '../config/index.js';
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Proxy Setup
|
|
16
|
+
// ============================================================================
|
|
17
|
+
async function setupProxy(): Promise<void> {
|
|
18
|
+
const proxyUrl = process.env.HTTP_PROXY || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.https_proxy;
|
|
19
|
+
|
|
20
|
+
if (proxyUrl) {
|
|
21
|
+
try {
|
|
22
|
+
const { ProxyAgent, setGlobalDispatcher } = await import('undici');
|
|
23
|
+
const agent = new ProxyAgent(proxyUrl);
|
|
24
|
+
setGlobalDispatcher(agent);
|
|
25
|
+
} catch {
|
|
26
|
+
// undici not available, proxy won't work
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Configuration
|
|
33
|
+
// ============================================================================
|
|
34
|
+
function detectConfig(settings: Settings): AgentConfig {
|
|
35
|
+
let provider: 'openai' | 'anthropic' | 'gemini' = 'gemini';
|
|
36
|
+
let model = 'gemini-2.0-flash';
|
|
37
|
+
|
|
38
|
+
// Auto-detect from API keys
|
|
39
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
40
|
+
provider = 'anthropic';
|
|
41
|
+
model = 'claude-sonnet-4-20250514';
|
|
42
|
+
} else if (process.env.OPENAI_API_KEY) {
|
|
43
|
+
provider = 'openai';
|
|
44
|
+
model = 'gpt-4o';
|
|
45
|
+
} else if (process.env.GOOGLE_API_KEY) {
|
|
46
|
+
provider = 'gemini';
|
|
47
|
+
model = 'gemini-2.0-flash';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Override from env vars
|
|
51
|
+
if (process.env.GENCODE_PROVIDER) {
|
|
52
|
+
provider = process.env.GENCODE_PROVIDER as 'openai' | 'anthropic' | 'gemini';
|
|
53
|
+
}
|
|
54
|
+
if (process.env.GENCODE_MODEL) {
|
|
55
|
+
model = process.env.GENCODE_MODEL;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Override from saved settings (highest priority)
|
|
59
|
+
if (settings.provider) {
|
|
60
|
+
provider = settings.provider;
|
|
61
|
+
}
|
|
62
|
+
if (settings.model) {
|
|
63
|
+
model = settings.model;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
provider,
|
|
68
|
+
model,
|
|
69
|
+
cwd: process.cwd(),
|
|
70
|
+
maxTurns: 20,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// CLI Arguments
|
|
76
|
+
// ============================================================================
|
|
77
|
+
function parseArgs() {
|
|
78
|
+
const args = process.argv.slice(2);
|
|
79
|
+
return {
|
|
80
|
+
continue: args.includes('-c') || args.includes('--continue'),
|
|
81
|
+
resume: args.includes('-r') || args.includes('--resume'),
|
|
82
|
+
help: args.includes('-h') || args.includes('--help'),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function printUsage(): void {
|
|
87
|
+
console.log();
|
|
88
|
+
console.log(' gencode - AI-Powered Coding Assistant');
|
|
89
|
+
console.log();
|
|
90
|
+
console.log(' Usage: gencode [options]');
|
|
91
|
+
console.log();
|
|
92
|
+
console.log(' Options:');
|
|
93
|
+
console.log(' -c, --continue Resume the most recent session');
|
|
94
|
+
console.log(' -r, --resume Select a session interactively');
|
|
95
|
+
console.log(' -h, --help Show this help');
|
|
96
|
+
console.log();
|
|
97
|
+
console.log(' Examples:');
|
|
98
|
+
console.log(' gencode Start new session');
|
|
99
|
+
console.log(' gencode -c Continue last session');
|
|
100
|
+
console.log(' gencode -r Pick a session');
|
|
101
|
+
console.log();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// Main
|
|
106
|
+
// ============================================================================
|
|
107
|
+
async function main() {
|
|
108
|
+
const args = parseArgs();
|
|
109
|
+
|
|
110
|
+
if (args.help) {
|
|
111
|
+
printUsage();
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
await setupProxy();
|
|
116
|
+
|
|
117
|
+
// Load saved settings
|
|
118
|
+
const settingsManager = new SettingsManager();
|
|
119
|
+
const settings = await settingsManager.load();
|
|
120
|
+
|
|
121
|
+
const config = detectConfig(settings);
|
|
122
|
+
|
|
123
|
+
// Render the Ink app
|
|
124
|
+
render(
|
|
125
|
+
<App
|
|
126
|
+
config={config}
|
|
127
|
+
settingsManager={settingsManager}
|
|
128
|
+
resumeLatest={args.continue}
|
|
129
|
+
/>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
main().catch((error) => {
|
|
134
|
+
console.error('Fatal error:', error);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
});
|