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.
Files changed (274) hide show
  1. package/.env.example +11 -0
  2. package/CLAUDE.md +70 -0
  3. package/LICENSE +21 -0
  4. package/README.md +117 -0
  5. package/dist/agent/agent.d.ts +84 -0
  6. package/dist/agent/agent.d.ts.map +1 -0
  7. package/dist/agent/agent.js +233 -0
  8. package/dist/agent/agent.js.map +1 -0
  9. package/dist/agent/index.d.ts +6 -0
  10. package/dist/agent/index.d.ts.map +1 -0
  11. package/dist/agent/index.js +6 -0
  12. package/dist/agent/index.js.map +1 -0
  13. package/dist/agent/types.d.ts +47 -0
  14. package/dist/agent/types.d.ts.map +1 -0
  15. package/dist/agent/types.js +5 -0
  16. package/dist/agent/types.js.map +1 -0
  17. package/dist/cli/components/App.d.ts +14 -0
  18. package/dist/cli/components/App.d.ts.map +1 -0
  19. package/dist/cli/components/App.js +395 -0
  20. package/dist/cli/components/App.js.map +1 -0
  21. package/dist/cli/components/CommandSuggestions.d.ts +13 -0
  22. package/dist/cli/components/CommandSuggestions.d.ts.map +1 -0
  23. package/dist/cli/components/CommandSuggestions.js +32 -0
  24. package/dist/cli/components/CommandSuggestions.js.map +1 -0
  25. package/dist/cli/components/Header.d.ts +9 -0
  26. package/dist/cli/components/Header.d.ts.map +1 -0
  27. package/dist/cli/components/Header.js +13 -0
  28. package/dist/cli/components/Header.js.map +1 -0
  29. package/dist/cli/components/Input.d.ts +13 -0
  30. package/dist/cli/components/Input.d.ts.map +1 -0
  31. package/dist/cli/components/Input.js +27 -0
  32. package/dist/cli/components/Input.js.map +1 -0
  33. package/dist/cli/components/Logo.d.ts +2 -0
  34. package/dist/cli/components/Logo.d.ts.map +1 -0
  35. package/dist/cli/components/Logo.js +8 -0
  36. package/dist/cli/components/Logo.js.map +1 -0
  37. package/dist/cli/components/Messages.d.ts +37 -0
  38. package/dist/cli/components/Messages.d.ts.map +1 -0
  39. package/dist/cli/components/Messages.js +106 -0
  40. package/dist/cli/components/Messages.js.map +1 -0
  41. package/dist/cli/components/ModelSelector.d.ts +13 -0
  42. package/dist/cli/components/ModelSelector.d.ts.map +1 -0
  43. package/dist/cli/components/ModelSelector.js +72 -0
  44. package/dist/cli/components/ModelSelector.js.map +1 -0
  45. package/dist/cli/components/Spinner.d.ts +12 -0
  46. package/dist/cli/components/Spinner.d.ts.map +1 -0
  47. package/dist/cli/components/Spinner.js +45 -0
  48. package/dist/cli/components/Spinner.js.map +1 -0
  49. package/dist/cli/components/index.d.ts +12 -0
  50. package/dist/cli/components/index.d.ts.map +1 -0
  51. package/dist/cli/components/index.js +12 -0
  52. package/dist/cli/components/index.js.map +1 -0
  53. package/dist/cli/components/theme.d.ts +31 -0
  54. package/dist/cli/components/theme.d.ts.map +1 -0
  55. package/dist/cli/components/theme.js +36 -0
  56. package/dist/cli/components/theme.js.map +1 -0
  57. package/dist/cli/index-legacy.d.ts +7 -0
  58. package/dist/cli/index-legacy.d.ts.map +1 -0
  59. package/dist/cli/index-legacy.js +431 -0
  60. package/dist/cli/index-legacy.js.map +1 -0
  61. package/dist/cli/index.d.ts +7 -0
  62. package/dist/cli/index.d.ts.map +1 -0
  63. package/dist/cli/index.js +116 -0
  64. package/dist/cli/index.js.map +1 -0
  65. package/dist/cli/ink-cli.d.ts +7 -0
  66. package/dist/cli/ink-cli.d.ts.map +1 -0
  67. package/dist/cli/ink-cli.js +105 -0
  68. package/dist/cli/ink-cli.js.map +1 -0
  69. package/dist/cli/session-picker.d.ts +16 -0
  70. package/dist/cli/session-picker.d.ts.map +1 -0
  71. package/dist/cli/session-picker.js +280 -0
  72. package/dist/cli/session-picker.js.map +1 -0
  73. package/dist/cli/ui.d.ts +61 -0
  74. package/dist/cli/ui.d.ts.map +1 -0
  75. package/dist/cli/ui.js +364 -0
  76. package/dist/cli/ui.js.map +1 -0
  77. package/dist/config/index.d.ts +7 -0
  78. package/dist/config/index.d.ts.map +1 -0
  79. package/dist/config/index.js +6 -0
  80. package/dist/config/index.js.map +1 -0
  81. package/dist/config/manager.d.ts +31 -0
  82. package/dist/config/manager.d.ts.map +1 -0
  83. package/dist/config/manager.js +65 -0
  84. package/dist/config/manager.js.map +1 -0
  85. package/dist/config/types.d.ts +22 -0
  86. package/dist/config/types.d.ts.map +1 -0
  87. package/dist/config/types.js +6 -0
  88. package/dist/config/types.js.map +1 -0
  89. package/dist/index.d.ts +12 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +21 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/memory/index.d.ts +10 -0
  94. package/dist/memory/index.d.ts.map +1 -0
  95. package/dist/memory/index.js +9 -0
  96. package/dist/memory/index.js.map +1 -0
  97. package/dist/memory/init.d.ts +20 -0
  98. package/dist/memory/init.d.ts.map +1 -0
  99. package/dist/memory/init.js +332 -0
  100. package/dist/memory/init.js.map +1 -0
  101. package/dist/memory/manager.d.ts +85 -0
  102. package/dist/memory/manager.d.ts.map +1 -0
  103. package/dist/memory/manager.js +234 -0
  104. package/dist/memory/manager.js.map +1 -0
  105. package/dist/memory/types.d.ts +74 -0
  106. package/dist/memory/types.d.ts.map +1 -0
  107. package/dist/memory/types.js +6 -0
  108. package/dist/memory/types.js.map +1 -0
  109. package/dist/permissions/index.d.ts +7 -0
  110. package/dist/permissions/index.d.ts.map +1 -0
  111. package/dist/permissions/index.js +6 -0
  112. package/dist/permissions/index.js.map +1 -0
  113. package/dist/permissions/manager.d.ts +32 -0
  114. package/dist/permissions/manager.d.ts.map +1 -0
  115. package/dist/permissions/manager.js +79 -0
  116. package/dist/permissions/manager.js.map +1 -0
  117. package/dist/permissions/types.d.ts +14 -0
  118. package/dist/permissions/types.d.ts.map +1 -0
  119. package/dist/permissions/types.js +17 -0
  120. package/dist/permissions/types.js.map +1 -0
  121. package/dist/providers/anthropic.d.ts +20 -0
  122. package/dist/providers/anthropic.d.ts.map +1 -0
  123. package/dist/providers/anthropic.js +185 -0
  124. package/dist/providers/anthropic.js.map +1 -0
  125. package/dist/providers/gemini.d.ts +21 -0
  126. package/dist/providers/gemini.d.ts.map +1 -0
  127. package/dist/providers/gemini.js +241 -0
  128. package/dist/providers/gemini.js.map +1 -0
  129. package/dist/providers/index.d.ts +34 -0
  130. package/dist/providers/index.d.ts.map +1 -0
  131. package/dist/providers/index.js +72 -0
  132. package/dist/providers/index.js.map +1 -0
  133. package/dist/providers/openai.d.ts +19 -0
  134. package/dist/providers/openai.d.ts.map +1 -0
  135. package/dist/providers/openai.js +221 -0
  136. package/dist/providers/openai.js.map +1 -0
  137. package/dist/providers/types.d.ts +125 -0
  138. package/dist/providers/types.d.ts.map +1 -0
  139. package/dist/providers/types.js +6 -0
  140. package/dist/providers/types.js.map +1 -0
  141. package/dist/session/index.d.ts +6 -0
  142. package/dist/session/index.d.ts.map +1 -0
  143. package/dist/session/index.js +6 -0
  144. package/dist/session/index.js.map +1 -0
  145. package/dist/session/manager.d.ts +101 -0
  146. package/dist/session/manager.d.ts.map +1 -0
  147. package/dist/session/manager.js +295 -0
  148. package/dist/session/manager.js.map +1 -0
  149. package/dist/session/types.d.ts +39 -0
  150. package/dist/session/types.d.ts.map +1 -0
  151. package/dist/session/types.js +10 -0
  152. package/dist/session/types.js.map +1 -0
  153. package/dist/tools/builtin/bash.d.ts +7 -0
  154. package/dist/tools/builtin/bash.d.ts.map +1 -0
  155. package/dist/tools/builtin/bash.js +80 -0
  156. package/dist/tools/builtin/bash.js.map +1 -0
  157. package/dist/tools/builtin/edit.d.ts +7 -0
  158. package/dist/tools/builtin/edit.d.ts.map +1 -0
  159. package/dist/tools/builtin/edit.js +32 -0
  160. package/dist/tools/builtin/edit.js.map +1 -0
  161. package/dist/tools/builtin/glob.d.ts +7 -0
  162. package/dist/tools/builtin/glob.d.ts.map +1 -0
  163. package/dist/tools/builtin/glob.js +36 -0
  164. package/dist/tools/builtin/glob.js.map +1 -0
  165. package/dist/tools/builtin/grep.d.ts +7 -0
  166. package/dist/tools/builtin/grep.d.ts.map +1 -0
  167. package/dist/tools/builtin/grep.js +59 -0
  168. package/dist/tools/builtin/grep.js.map +1 -0
  169. package/dist/tools/builtin/read.d.ts +7 -0
  170. package/dist/tools/builtin/read.d.ts.map +1 -0
  171. package/dist/tools/builtin/read.js +29 -0
  172. package/dist/tools/builtin/read.js.map +1 -0
  173. package/dist/tools/builtin/write.d.ts +7 -0
  174. package/dist/tools/builtin/write.d.ts.map +1 -0
  175. package/dist/tools/builtin/write.js +24 -0
  176. package/dist/tools/builtin/write.js.map +1 -0
  177. package/dist/tools/index.d.ts +38 -0
  178. package/dist/tools/index.d.ts.map +1 -0
  179. package/dist/tools/index.js +32 -0
  180. package/dist/tools/index.js.map +1 -0
  181. package/dist/tools/registry.d.ts +22 -0
  182. package/dist/tools/registry.d.ts.map +1 -0
  183. package/dist/tools/registry.js +71 -0
  184. package/dist/tools/registry.js.map +1 -0
  185. package/dist/tools/types.d.ts +62 -0
  186. package/dist/tools/types.d.ts.map +1 -0
  187. package/dist/tools/types.js +126 -0
  188. package/dist/tools/types.js.map +1 -0
  189. package/docs/README.md +16 -0
  190. package/docs/proposals/0001-web-fetch-tool.md +293 -0
  191. package/docs/proposals/0002-web-search-tool.md +306 -0
  192. package/docs/proposals/0003-task-subagents.md +333 -0
  193. package/docs/proposals/0004-plan-mode.md +338 -0
  194. package/docs/proposals/0005-todo-system.md +299 -0
  195. package/docs/proposals/0006-memory-system.md +539 -0
  196. package/docs/proposals/0007-context-management.md +429 -0
  197. package/docs/proposals/0008-checkpointing.md +327 -0
  198. package/docs/proposals/0009-hooks-system.md +343 -0
  199. package/docs/proposals/0010-mcp-integration.md +382 -0
  200. package/docs/proposals/0011-custom-commands.md +374 -0
  201. package/docs/proposals/0012-ask-user-question.md +317 -0
  202. package/docs/proposals/0013-multi-edit-tool.md +345 -0
  203. package/docs/proposals/0014-lsp-tool.md +478 -0
  204. package/docs/proposals/0015-ls-tool.md +407 -0
  205. package/docs/proposals/0016-kill-shell-tool.md +455 -0
  206. package/docs/proposals/0017-background-tasks.md +489 -0
  207. package/docs/proposals/0018-parallel-tool-execution.md +415 -0
  208. package/docs/proposals/0019-session-enhancements.md +462 -0
  209. package/docs/proposals/0020-session-summarization.md +447 -0
  210. package/docs/proposals/0021-skills-system.md +409 -0
  211. package/docs/proposals/0022-plugin-system.md +467 -0
  212. package/docs/proposals/0023-permission-enhancements.md +470 -0
  213. package/docs/proposals/0024-keyboard-shortcuts.md +443 -0
  214. package/docs/proposals/0025-cost-tracking.md +447 -0
  215. package/docs/proposals/0026-git-integration.md +475 -0
  216. package/docs/proposals/0027-enhanced-read-tool.md +514 -0
  217. package/docs/proposals/0028-enhanced-bash-tool.md +511 -0
  218. package/docs/proposals/0029-notebook-edit-tool.md +413 -0
  219. package/docs/proposals/0030-plugin-marketplace.md +360 -0
  220. package/docs/proposals/0031-command-suggestions.md +295 -0
  221. package/docs/proposals/0032-ide-integrations.md +328 -0
  222. package/docs/proposals/0033-enterprise-deployment.md +221 -0
  223. package/docs/proposals/0034-sandboxing.md +273 -0
  224. package/docs/proposals/0035-auto-updater.md +311 -0
  225. package/docs/proposals/0036-enhanced-glob-tool.md +267 -0
  226. package/docs/proposals/0037-enhanced-grep-tool.md +360 -0
  227. package/docs/proposals/0038-interactive-cli-ui.md +373 -0
  228. package/docs/proposals/0039-streaming-enhancements.md +359 -0
  229. package/docs/proposals/0040-multi-provider-enhancements.md +369 -0
  230. package/docs/proposals/README.md +84 -0
  231. package/docs/proposals/TEMPLATE.md +57 -0
  232. package/docs/proposals/research/claude-code-research.md +307 -0
  233. package/examples/agent-demo.ts +115 -0
  234. package/examples/basic.ts +166 -0
  235. package/package.json +50 -0
  236. package/src/agent/agent.ts +276 -0
  237. package/src/agent/index.ts +6 -0
  238. package/src/agent/types.ts +62 -0
  239. package/src/cli/components/App.tsx +565 -0
  240. package/src/cli/components/CommandSuggestions.tsx +58 -0
  241. package/src/cli/components/Header.tsx +36 -0
  242. package/src/cli/components/Input.tsx +60 -0
  243. package/src/cli/components/Logo.tsx +16 -0
  244. package/src/cli/components/Messages.tsx +210 -0
  245. package/src/cli/components/ModelSelector.tsx +135 -0
  246. package/src/cli/components/Spinner.tsx +72 -0
  247. package/src/cli/components/index.ts +21 -0
  248. package/src/cli/components/theme.ts +36 -0
  249. package/src/cli/index.tsx +136 -0
  250. package/src/config/index.ts +7 -0
  251. package/src/config/manager.ts +77 -0
  252. package/src/config/types.ts +25 -0
  253. package/src/index.ts +86 -0
  254. package/src/permissions/index.ts +7 -0
  255. package/src/permissions/manager.ts +97 -0
  256. package/src/permissions/types.ts +29 -0
  257. package/src/providers/anthropic.ts +224 -0
  258. package/src/providers/gemini.ts +295 -0
  259. package/src/providers/index.ts +97 -0
  260. package/src/providers/openai.ts +261 -0
  261. package/src/providers/types.ts +181 -0
  262. package/src/session/index.ts +6 -0
  263. package/src/session/manager.ts +354 -0
  264. package/src/session/types.ts +49 -0
  265. package/src/tools/builtin/bash.ts +92 -0
  266. package/src/tools/builtin/edit.ts +37 -0
  267. package/src/tools/builtin/glob.ts +42 -0
  268. package/src/tools/builtin/grep.ts +67 -0
  269. package/src/tools/builtin/read.ts +34 -0
  270. package/src/tools/builtin/write.ts +27 -0
  271. package/src/tools/index.ts +36 -0
  272. package/src/tools/registry.ts +83 -0
  273. package/src/tools/types.ts +172 -0
  274. 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
+ });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Config Module Exports
3
+ */
4
+
5
+ export { SettingsManager } from './manager.js';
6
+ export type { Settings, SettingsManagerOptions, ProviderName } from './types.js';
7
+ export { DEFAULT_SETTINGS_DIR, SETTINGS_FILE_NAME } from './types.js';