gencode-ai 0.1.1 → 0.1.2

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 (263) hide show
  1. package/.gencode/settings.local.json +7 -0
  2. package/README.md +11 -11
  3. package/dist/agent/agent.d.ts +42 -1
  4. package/dist/agent/agent.d.ts.map +1 -1
  5. package/dist/agent/agent.js +82 -15
  6. package/dist/agent/agent.js.map +1 -1
  7. package/dist/cli/components/App.d.ts +8 -1
  8. package/dist/cli/components/App.d.ts.map +1 -1
  9. package/dist/cli/components/App.js +231 -29
  10. package/dist/cli/components/App.js.map +1 -1
  11. package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
  12. package/dist/cli/components/CommandSuggestions.js +2 -0
  13. package/dist/cli/components/CommandSuggestions.js.map +1 -1
  14. package/dist/cli/components/Header.d.ts +1 -1
  15. package/dist/cli/components/Header.d.ts.map +1 -1
  16. package/dist/cli/components/Header.js +4 -6
  17. package/dist/cli/components/Header.js.map +1 -1
  18. package/dist/cli/components/Logo.d.ts +1 -0
  19. package/dist/cli/components/Logo.d.ts.map +1 -1
  20. package/dist/cli/components/Logo.js +16 -3
  21. package/dist/cli/components/Logo.js.map +1 -1
  22. package/dist/cli/components/Messages.d.ts +4 -4
  23. package/dist/cli/components/Messages.d.ts.map +1 -1
  24. package/dist/cli/components/Messages.js +51 -25
  25. package/dist/cli/components/Messages.js.map +1 -1
  26. package/dist/cli/components/PermissionPrompt.d.ts +60 -0
  27. package/dist/cli/components/PermissionPrompt.d.ts.map +1 -0
  28. package/dist/cli/components/PermissionPrompt.js +192 -0
  29. package/dist/cli/components/PermissionPrompt.js.map +1 -0
  30. package/dist/cli/components/ProviderManager.js +3 -3
  31. package/dist/cli/components/ProviderManager.js.map +1 -1
  32. package/dist/cli/components/Spinner.d.ts +7 -2
  33. package/dist/cli/components/Spinner.d.ts.map +1 -1
  34. package/dist/cli/components/Spinner.js +116 -25
  35. package/dist/cli/components/Spinner.js.map +1 -1
  36. package/dist/cli/components/TodoList.d.ts +7 -0
  37. package/dist/cli/components/TodoList.d.ts.map +1 -0
  38. package/dist/cli/components/TodoList.js +34 -0
  39. package/dist/cli/components/TodoList.js.map +1 -0
  40. package/dist/cli/components/index.d.ts +1 -0
  41. package/dist/cli/components/index.d.ts.map +1 -1
  42. package/dist/cli/components/index.js +1 -0
  43. package/dist/cli/components/index.js.map +1 -1
  44. package/dist/cli/index.js +47 -7
  45. package/dist/cli/index.js.map +1 -1
  46. package/dist/config/index.d.ts +13 -4
  47. package/dist/config/index.d.ts.map +1 -1
  48. package/dist/config/index.js +18 -3
  49. package/dist/config/index.js.map +1 -1
  50. package/dist/config/levels.d.ts +49 -0
  51. package/dist/config/levels.d.ts.map +1 -0
  52. package/dist/config/levels.js +222 -0
  53. package/dist/config/levels.js.map +1 -0
  54. package/dist/config/loader.d.ts +46 -0
  55. package/dist/config/loader.d.ts.map +1 -0
  56. package/dist/config/loader.js +153 -0
  57. package/dist/config/loader.js.map +1 -0
  58. package/dist/config/manager.d.ts +115 -15
  59. package/dist/config/manager.d.ts.map +1 -1
  60. package/dist/config/manager.js +260 -34
  61. package/dist/config/manager.js.map +1 -1
  62. package/dist/config/manager.test.d.ts +5 -0
  63. package/dist/config/manager.test.d.ts.map +1 -0
  64. package/dist/config/manager.test.js +192 -0
  65. package/dist/config/manager.test.js.map +1 -0
  66. package/dist/config/merger.d.ts +56 -0
  67. package/dist/config/merger.d.ts.map +1 -0
  68. package/dist/config/merger.js +177 -0
  69. package/dist/config/merger.js.map +1 -0
  70. package/dist/config/test-utils.d.ts +24 -0
  71. package/dist/config/test-utils.d.ts.map +1 -0
  72. package/dist/config/test-utils.js +55 -0
  73. package/dist/config/test-utils.js.map +1 -0
  74. package/dist/config/types.d.ts +78 -9
  75. package/dist/config/types.d.ts.map +1 -1
  76. package/dist/config/types.js +52 -2
  77. package/dist/config/types.js.map +1 -1
  78. package/dist/memory/import-resolver.d.ts +46 -0
  79. package/dist/memory/import-resolver.d.ts.map +1 -0
  80. package/dist/memory/import-resolver.js +117 -0
  81. package/dist/memory/import-resolver.js.map +1 -0
  82. package/dist/memory/index.d.ts +7 -6
  83. package/dist/memory/index.d.ts.map +1 -1
  84. package/dist/memory/index.js +7 -5
  85. package/dist/memory/index.js.map +1 -1
  86. package/dist/memory/init-prompt.d.ts +22 -0
  87. package/dist/memory/init-prompt.d.ts.map +1 -0
  88. package/dist/memory/init-prompt.js +103 -0
  89. package/dist/memory/init-prompt.js.map +1 -0
  90. package/dist/memory/memory-manager.d.ts +119 -0
  91. package/dist/memory/memory-manager.d.ts.map +1 -0
  92. package/dist/memory/memory-manager.js +587 -0
  93. package/dist/memory/memory-manager.js.map +1 -0
  94. package/dist/memory/rules-parser.d.ts +38 -0
  95. package/dist/memory/rules-parser.d.ts.map +1 -0
  96. package/dist/memory/rules-parser.js +69 -0
  97. package/dist/memory/rules-parser.js.map +1 -0
  98. package/dist/memory/test-utils.d.ts +20 -0
  99. package/dist/memory/test-utils.d.ts.map +1 -0
  100. package/dist/memory/test-utils.js +44 -0
  101. package/dist/memory/test-utils.js.map +1 -0
  102. package/dist/memory/types.d.ts +70 -63
  103. package/dist/memory/types.d.ts.map +1 -1
  104. package/dist/memory/types.js +42 -2
  105. package/dist/memory/types.js.map +1 -1
  106. package/dist/permissions/audit.d.ts +82 -0
  107. package/dist/permissions/audit.d.ts.map +1 -0
  108. package/dist/permissions/audit.js +229 -0
  109. package/dist/permissions/audit.js.map +1 -0
  110. package/dist/permissions/index.d.ts +11 -1
  111. package/dist/permissions/index.d.ts.map +1 -1
  112. package/dist/permissions/index.js +15 -0
  113. package/dist/permissions/index.js.map +1 -1
  114. package/dist/permissions/manager.d.ts +149 -13
  115. package/dist/permissions/manager.d.ts.map +1 -1
  116. package/dist/permissions/manager.js +480 -35
  117. package/dist/permissions/manager.js.map +1 -1
  118. package/dist/permissions/manager.test.d.ts +5 -0
  119. package/dist/permissions/manager.test.d.ts.map +1 -0
  120. package/dist/permissions/manager.test.js +213 -0
  121. package/dist/permissions/manager.test.js.map +1 -0
  122. package/dist/permissions/persistence.d.ts +74 -0
  123. package/dist/permissions/persistence.d.ts.map +1 -0
  124. package/dist/permissions/persistence.js +248 -0
  125. package/dist/permissions/persistence.js.map +1 -0
  126. package/dist/permissions/persistence.test.d.ts +5 -0
  127. package/dist/permissions/persistence.test.d.ts.map +1 -0
  128. package/dist/permissions/persistence.test.js +171 -0
  129. package/dist/permissions/persistence.test.js.map +1 -0
  130. package/dist/permissions/prompt-matcher.d.ts +64 -0
  131. package/dist/permissions/prompt-matcher.d.ts.map +1 -0
  132. package/dist/permissions/prompt-matcher.js +415 -0
  133. package/dist/permissions/prompt-matcher.js.map +1 -0
  134. package/dist/permissions/prompt-matcher.test.d.ts +5 -0
  135. package/dist/permissions/prompt-matcher.test.d.ts.map +1 -0
  136. package/dist/permissions/prompt-matcher.test.js +107 -0
  137. package/dist/permissions/prompt-matcher.test.js.map +1 -0
  138. package/dist/permissions/types.d.ts +157 -0
  139. package/dist/permissions/types.d.ts.map +1 -1
  140. package/dist/permissions/types.js +43 -8
  141. package/dist/permissions/types.js.map +1 -1
  142. package/dist/prompts/index.d.ts +92 -0
  143. package/dist/prompts/index.d.ts.map +1 -0
  144. package/dist/prompts/index.js +241 -0
  145. package/dist/prompts/index.js.map +1 -0
  146. package/dist/tools/builtin/bash.d.ts.map +1 -1
  147. package/dist/tools/builtin/bash.js +2 -1
  148. package/dist/tools/builtin/bash.js.map +1 -1
  149. package/dist/tools/builtin/edit.d.ts.map +1 -1
  150. package/dist/tools/builtin/edit.js +2 -1
  151. package/dist/tools/builtin/edit.js.map +1 -1
  152. package/dist/tools/builtin/glob.d.ts.map +1 -1
  153. package/dist/tools/builtin/glob.js +2 -1
  154. package/dist/tools/builtin/glob.js.map +1 -1
  155. package/dist/tools/builtin/grep.d.ts.map +1 -1
  156. package/dist/tools/builtin/grep.js +2 -1
  157. package/dist/tools/builtin/grep.js.map +1 -1
  158. package/dist/tools/builtin/read.d.ts.map +1 -1
  159. package/dist/tools/builtin/read.js +2 -1
  160. package/dist/tools/builtin/read.js.map +1 -1
  161. package/dist/tools/builtin/todowrite.d.ts +15 -0
  162. package/dist/tools/builtin/todowrite.d.ts.map +1 -0
  163. package/dist/tools/builtin/todowrite.js +88 -0
  164. package/dist/tools/builtin/todowrite.js.map +1 -0
  165. package/dist/tools/builtin/webfetch.d.ts.map +1 -1
  166. package/dist/tools/builtin/webfetch.js +2 -5
  167. package/dist/tools/builtin/webfetch.js.map +1 -1
  168. package/dist/tools/builtin/websearch.d.ts.map +1 -1
  169. package/dist/tools/builtin/websearch.js +2 -16
  170. package/dist/tools/builtin/websearch.js.map +1 -1
  171. package/dist/tools/builtin/write.d.ts.map +1 -1
  172. package/dist/tools/builtin/write.js +2 -1
  173. package/dist/tools/builtin/write.js.map +1 -1
  174. package/dist/tools/index.d.ts +7 -0
  175. package/dist/tools/index.d.ts.map +1 -1
  176. package/dist/tools/index.js +4 -0
  177. package/dist/tools/index.js.map +1 -1
  178. package/dist/tools/types.d.ts +22 -0
  179. package/dist/tools/types.d.ts.map +1 -1
  180. package/dist/tools/types.js +8 -0
  181. package/dist/tools/types.js.map +1 -1
  182. package/docs/config-system-comparison.md +707 -0
  183. package/docs/memory-system.md +238 -0
  184. package/docs/permissions.md +368 -0
  185. package/docs/proposals/0005-todo-system.md +350 -85
  186. package/docs/proposals/0006-memory-system.md +11 -10
  187. package/docs/proposals/0012-ask-user-question.md +941 -206
  188. package/docs/proposals/0023-permission-enhancements.md +61 -2
  189. package/docs/proposals/0041-configuration-system.md +33 -2
  190. package/docs/proposals/0042-prompt-optimization.md +866 -0
  191. package/docs/proposals/README.md +6 -5
  192. package/jest.config.js +26 -0
  193. package/package.json +8 -2
  194. package/src/agent/agent.ts +111 -16
  195. package/src/cli/components/App.tsx +309 -36
  196. package/src/cli/components/CommandSuggestions.tsx +2 -0
  197. package/src/cli/components/Header.tsx +11 -17
  198. package/src/cli/components/Logo.tsx +76 -9
  199. package/src/cli/components/Messages.tsx +73 -53
  200. package/src/cli/components/PermissionPrompt.tsx +388 -0
  201. package/src/cli/components/ProviderManager.tsx +5 -5
  202. package/src/cli/components/Spinner.tsx +138 -25
  203. package/src/cli/components/TodoList.tsx +54 -0
  204. package/src/cli/components/index.ts +6 -0
  205. package/src/cli/index.tsx +54 -6
  206. package/src/config/index.ts +78 -4
  207. package/src/config/levels.test.ts +163 -0
  208. package/src/config/levels.ts +285 -0
  209. package/src/config/loader.test.ts +120 -0
  210. package/src/config/loader.ts +178 -0
  211. package/src/config/manager.test.ts +215 -0
  212. package/src/config/manager.ts +328 -40
  213. package/src/config/merger.test.ts +360 -0
  214. package/src/config/merger.ts +221 -0
  215. package/src/config/test-utils.ts +79 -0
  216. package/src/config/types.ts +152 -9
  217. package/src/memory/import-resolver.test.ts +117 -0
  218. package/src/memory/import-resolver.ts +149 -0
  219. package/src/memory/index.ts +11 -0
  220. package/src/memory/init-prompt.ts +113 -0
  221. package/src/memory/memory-manager.test.ts +198 -0
  222. package/src/memory/memory-manager.ts +716 -0
  223. package/src/memory/rules-parser.test.ts +182 -0
  224. package/src/memory/rules-parser.ts +82 -0
  225. package/src/memory/test-utils.ts +60 -0
  226. package/src/memory/types.ts +119 -0
  227. package/src/permissions/audit.ts +284 -0
  228. package/src/permissions/index.ts +20 -1
  229. package/src/permissions/manager.test.ts +260 -0
  230. package/src/permissions/manager.ts +592 -40
  231. package/src/permissions/persistence.test.ts +220 -0
  232. package/src/permissions/persistence.ts +301 -0
  233. package/src/permissions/prompt-matcher.test.ts +213 -0
  234. package/src/permissions/prompt-matcher.ts +472 -0
  235. package/src/permissions/types.ts +236 -8
  236. package/src/prompts/index.test.ts +279 -0
  237. package/src/prompts/index.ts +306 -0
  238. package/src/prompts/system/anthropic.txt +29 -0
  239. package/src/prompts/system/base.txt +124 -0
  240. package/src/prompts/system/gemini.txt +35 -0
  241. package/src/prompts/system/generic.txt +128 -0
  242. package/src/prompts/system/openai.txt +29 -0
  243. package/src/prompts/tools/bash.txt +60 -0
  244. package/src/prompts/tools/edit.txt +29 -0
  245. package/src/prompts/tools/glob.txt +35 -0
  246. package/src/prompts/tools/grep.txt +43 -0
  247. package/src/prompts/tools/read.txt +22 -0
  248. package/src/prompts/tools/todowrite.txt +71 -0
  249. package/src/prompts/tools/webfetch.txt +34 -0
  250. package/src/prompts/tools/websearch.txt +41 -0
  251. package/src/prompts/tools/write.txt +23 -0
  252. package/src/tools/builtin/bash.ts +2 -1
  253. package/src/tools/builtin/edit.ts +2 -1
  254. package/src/tools/builtin/glob.ts +2 -1
  255. package/src/tools/builtin/grep.ts +2 -1
  256. package/src/tools/builtin/read.ts +2 -1
  257. package/src/tools/builtin/todowrite.ts +102 -0
  258. package/src/tools/builtin/webfetch.ts +2 -5
  259. package/src/tools/builtin/websearch.ts +2 -16
  260. package/src/tools/builtin/write.ts +2 -1
  261. package/src/tools/index.ts +4 -0
  262. package/src/tools/types.ts +12 -0
  263. package/tsconfig.json +1 -1
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Spinner Component - Compact thinking animation (Claude Code style)
2
+ * Spinner Component - Vivid thinking animation
3
3
  */
4
- import { useState, useEffect } from 'react';
4
+ import { useState, useEffect, useMemo } from 'react';
5
5
  import { Box, Text } from 'ink';
6
6
  import InkSpinner from 'ink-spinner';
7
7
  import { colors } from './theme.js';
@@ -33,40 +33,153 @@ export function LoadingSpinner({ text = 'Loading...' }: SpinnerProps) {
33
33
  );
34
34
  }
35
35
 
36
+ // Thinking phrases that rotate during processing
37
+ const thinkingPhrases = [
38
+ 'Thinking',
39
+ 'Pondering',
40
+ 'Analyzing',
41
+ 'Processing',
42
+ 'Reasoning',
43
+ 'Contemplating',
44
+ 'Figuring out',
45
+ 'Working on it',
46
+ 'Almost there',
47
+ 'Crafting response',
48
+ ];
49
+
50
+ // Animation frames for different styles
51
+ const animations = {
52
+ // Brainwave animation
53
+ brainwave: ['🧠 ∿∿∿', '🧠∿ ∿∿', '🧠∿∿ ∿', '🧠∿∿∿ ', '🧠 ∿∿∿', '🧠∿ ∿∿'],
54
+ // Sparkle animation
55
+ sparkle: ['✨ ', ' ✨ ', ' ✨ ', ' ✨ ', ' ✨', ' ✨ ', ' ✨ ', ' ✨ '],
56
+ // DNA helix
57
+ dna: ['🔬 ⌬⌬⌬', '🔬⌬ ⌬⌬', '🔬⌬⌬ ⌬', '🔬⌬⌬⌬ ', '🔬 ⌬⌬⌬'],
58
+ // Pulse dots
59
+ pulse: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
60
+ // Wave animation
61
+ wave: ['≋≈∼∽', '∽≋≈∼', '∼∽≋≈', '≈∼∽≋'],
62
+ // Bounce bar with gradient
63
+ bounceGradient: [
64
+ '█▓▒░ ',
65
+ ' █▓▒░ ',
66
+ ' █▓▒░ ',
67
+ ' █▓▒░ ',
68
+ ' █▓▒░',
69
+ ' ░▒▓█ ',
70
+ ' ░▒▓█ ',
71
+ ' ░▒▓█ ',
72
+ '░▒▓█ ',
73
+ ],
74
+ // Orbit animation
75
+ orbit: ['◐', '◓', '◑', '◒'],
76
+ // Loading bar with shimmer
77
+ shimmer: [
78
+ '▓▓▓▓▓░░░',
79
+ '░▓▓▓▓▓░░',
80
+ '░░▓▓▓▓▓░',
81
+ '░░░▓▓▓▓▓',
82
+ '░░░░▓▓▓▓',
83
+ '░░░▓▓▓▓▓',
84
+ '░░▓▓▓▓▓░',
85
+ '░▓▓▓▓▓░░',
86
+ ],
87
+ };
88
+
89
+ type AnimationType = keyof typeof animations;
90
+ const animationTypes = Object.keys(animations) as AnimationType[];
91
+
92
+ // Format elapsed time
93
+ function formatElapsed(ms: number): string {
94
+ const secs = Math.floor(ms / 1000);
95
+ if (secs < 60) return `${secs}s`;
96
+ const mins = Math.floor(secs / 60);
97
+ const remainSecs = secs % 60;
98
+ return `${mins}m ${remainSecs}s`;
99
+ }
100
+
101
+ // Format token count
102
+ function formatTokens(count: number): string {
103
+ if (count >= 1000) {
104
+ return `${(count / 1000).toFixed(1)}k`;
105
+ }
106
+ return `${count}`;
107
+ }
108
+
109
+ interface ProgressBarProps {
110
+ startTime?: number;
111
+ tokenCount?: number;
112
+ isThinking?: boolean;
113
+ }
114
+
36
115
  /**
37
116
  * Progress bar animation for processing state
38
- * Bouncing ball with trail effect
117
+ * Claude Code style with time, tokens, and thinking status
39
118
  */
40
- export function ProgressBar() {
119
+ export function ProgressBar({ startTime, tokenCount = 0, isThinking = false }: ProgressBarProps) {
41
120
  const [frame, setFrame] = useState(0);
121
+ const [phraseIndex, setPhraseIndex] = useState(0);
122
+ const [elapsed, setElapsed] = useState(0);
42
123
 
43
- useEffect(() => {
44
- const timer = setInterval(() => {
45
- setFrame((f) => (f + 1) % 14);
46
- }, 100);
47
- return () => clearInterval(timer);
124
+ // Pick a random animation style on mount
125
+ const animStyle = useMemo(() => {
126
+ const randomIndex = Math.floor(Math.random() * animationTypes.length);
127
+ return animationTypes[randomIndex];
48
128
  }, []);
49
129
 
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
- }
130
+ const currentAnim = animations[animStyle];
131
+
132
+ useEffect(() => {
133
+ // Fast animation update
134
+ const animTimer = setInterval(() => {
135
+ setFrame((f) => (f + 1) % currentAnim.length);
136
+ }, 120);
137
+
138
+ // Slower phrase rotation (every 2.5 seconds)
139
+ const phraseTimer = setInterval(() => {
140
+ setPhraseIndex((p) => (p + 1) % thinkingPhrases.length);
141
+ }, 2500);
142
+
143
+ // Update elapsed time every second
144
+ const elapsedTimer = setInterval(() => {
145
+ if (startTime) {
146
+ setElapsed(Date.now() - startTime);
147
+ }
148
+ }, 1000);
149
+
150
+ return () => {
151
+ clearInterval(animTimer);
152
+ clearInterval(phraseTimer);
153
+ clearInterval(elapsedTimer);
154
+ };
155
+ }, [currentAnim.length, startTime]);
156
+
157
+ const animFrame = currentAnim[frame];
158
+ const phrase = thinkingPhrases[phraseIndex];
159
+
160
+ // Animated ellipsis
161
+ const ellipsis = '.'.repeat((frame % 3) + 1).padEnd(3, ' ');
162
+
163
+ // Build status parts
164
+ const parts: string[] = [];
165
+ if (startTime && elapsed > 0) {
166
+ parts.push(formatElapsed(elapsed));
167
+ }
168
+ if (tokenCount > 0) {
169
+ parts.push(`↓ ${formatTokens(tokenCount)} tokens`);
64
170
  }
171
+ if (isThinking) {
172
+ parts.push('thinking');
173
+ }
174
+
175
+ const statusText = parts.length > 0 ? ` · ${parts.join(' · ')}` : '';
65
176
 
66
177
  return (
67
178
  <Box>
68
- <Text color={colors.brand}>{bar}</Text>
69
- <Text color={colors.textMuted}> esc to stop</Text>
179
+ <Text color={colors.brand}>{animFrame}</Text>
180
+ <Text color={colors.textSecondary}> {phrase}</Text>
181
+ <Text color={colors.textMuted}>{ellipsis}</Text>
182
+ <Text color={colors.textMuted}>(esc to stop{statusText})</Text>
70
183
  </Box>
71
184
  );
72
185
  }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * TodoList Component - Display current todos in CLI
3
+ * Design: Minimal, clean, status-driven with clear visual hierarchy
4
+ */
5
+ import { Box, Text } from 'ink';
6
+ import { colors } from './theme.js';
7
+ import type { TodoItem } from '../../tools/types.js';
8
+
9
+ interface TodoListProps {
10
+ todos: TodoItem[];
11
+ }
12
+
13
+ export function TodoList({ todos }: TodoListProps) {
14
+ if (todos.length === 0) return null;
15
+
16
+ const completed = todos.filter((t) => t.status === 'completed').length;
17
+ const total = todos.length;
18
+
19
+ return (
20
+ <Box flexDirection="column" marginTop={1} marginLeft={2}>
21
+ {/* Header with count */}
22
+ <Text color={colors.textMuted}>
23
+ Tasks {completed}/{total}
24
+ </Text>
25
+ {/* Task list */}
26
+ {todos.map((todo, i) => {
27
+ const isCompleted = todo.status === 'completed';
28
+ const isInProgress = todo.status === 'in_progress';
29
+
30
+ // Status indicators: [x] done, [>] active, [ ] pending
31
+ let bracket: string;
32
+ let bracketColor: string;
33
+
34
+ if (isCompleted) {
35
+ bracket = '[x]';
36
+ bracketColor = colors.success;
37
+ } else if (isInProgress) {
38
+ bracket = '[>]';
39
+ bracketColor = colors.warning;
40
+ } else {
41
+ bracket = '[ ]';
42
+ bracketColor = colors.textMuted;
43
+ }
44
+
45
+ return (
46
+ <Text key={i} dimColor={isCompleted}>
47
+ <Text color={bracketColor}>{bracket}</Text>
48
+ <Text strikethrough={isCompleted}> {todo.content}</Text>
49
+ </Text>
50
+ );
51
+ })}
52
+ </Box>
53
+ );
54
+ }
@@ -19,3 +19,9 @@ export { PromptInput, ConfirmPrompt } from './Input.js';
19
19
  export { colors, icons } from './theme.js';
20
20
  export { ModelSelector } from './ModelSelector.js';
21
21
  export { CommandSuggestions, COMMANDS, getFilteredCommands } from './CommandSuggestions.js';
22
+ export {
23
+ PermissionPrompt,
24
+ SimpleConfirmPrompt,
25
+ PermissionRulesDisplay,
26
+ PermissionAuditDisplay,
27
+ } from './PermissionPrompt.js';
package/src/cli/index.tsx CHANGED
@@ -88,10 +88,21 @@ function detectConfig(settings: Settings, providersConfig: ProvidersConfigManage
88
88
  // ============================================================================
89
89
  function parseArgs() {
90
90
  const args = process.argv.slice(2);
91
+
92
+ // Extract prompt value from -p "message" or --prompt "message"
93
+ let prompt: string | undefined;
94
+ for (let i = 0; i < args.length; i++) {
95
+ if ((args[i] === '-p' || args[i] === '--prompt') && args[i + 1]) {
96
+ prompt = args[i + 1];
97
+ break;
98
+ }
99
+ }
100
+
91
101
  return {
92
102
  continue: args.includes('-c') || args.includes('--continue'),
93
103
  resume: args.includes('-r') || args.includes('--resume'),
94
104
  help: args.includes('-h') || args.includes('--help'),
105
+ prompt,
95
106
  };
96
107
  }
97
108
 
@@ -102,17 +113,47 @@ function printUsage(): void {
102
113
  console.log(' Usage: gencode [options]');
103
114
  console.log();
104
115
  console.log(' Options:');
105
- console.log(' -c, --continue Resume the most recent session');
106
- console.log(' -r, --resume Select a session interactively');
107
- console.log(' -h, --help Show this help');
116
+ console.log(' -c, --continue Resume the most recent session');
117
+ console.log(' -r, --resume Select a session interactively');
118
+ console.log(' -p, --prompt <msg> Run a single prompt (non-interactive)');
119
+ console.log(' -h, --help Show this help');
108
120
  console.log();
109
121
  console.log(' Examples:');
110
- console.log(' gencode Start new session');
111
- console.log(' gencode -c Continue last session');
112
- console.log(' gencode -r Pick a session');
122
+ console.log(' gencode Start new session');
123
+ console.log(' gencode -c Continue last session');
124
+ console.log(' gencode -r Pick a session');
125
+ console.log(' gencode -p "2+2" Run single prompt');
113
126
  console.log();
114
127
  }
115
128
 
129
+ // ============================================================================
130
+ // Non-interactive mode
131
+ // ============================================================================
132
+ async function runNonInteractive(prompt: string, config: AgentConfig): Promise<void> {
133
+ const { Agent } = await import('../agent/agent.js');
134
+
135
+ const agent = new Agent(config);
136
+
137
+ let response = '';
138
+ for await (const event of agent.run(prompt)) {
139
+ switch (event.type) {
140
+ case 'text':
141
+ response += event.text;
142
+ break;
143
+ case 'tool_start':
144
+ console.error(`[tool] ${event.name}`);
145
+ break;
146
+ case 'error':
147
+ console.error(`[error] ${event.error.message}`);
148
+ break;
149
+ case 'done':
150
+ break;
151
+ }
152
+ }
153
+
154
+ console.log(response);
155
+ }
156
+
116
157
  // ============================================================================
117
158
  // Main
118
159
  // ============================================================================
@@ -135,12 +176,19 @@ async function main() {
135
176
 
136
177
  const config = detectConfig(settings, providersConfig);
137
178
 
179
+ // Non-interactive mode with -p flag
180
+ if (args.prompt) {
181
+ await runNonInteractive(args.prompt, config);
182
+ return;
183
+ }
184
+
138
185
  // Render the Ink app
139
186
  render(
140
187
  <App
141
188
  config={config}
142
189
  settingsManager={settingsManager}
143
190
  resumeLatest={args.continue}
191
+ permissionSettings={settings.permissions}
144
192
  />
145
193
  );
146
194
  }
@@ -1,8 +1,82 @@
1
1
  /**
2
- * Config Module Exports
2
+ * Configuration Module - Multi-level configuration system (Claude Code compatible)
3
+ *
4
+ * This module provides a hierarchical configuration system that:
5
+ * - Supports multiple levels: user, project, local, managed
6
+ * - Merges .gencode and .claude directories at each level (gencode wins)
7
+ * - Supports GENCODE_CONFIG_DIRS environment variable for extra config dirs
8
+ * - Enforces managed settings that cannot be overridden
3
9
  */
4
10
 
5
- export { SettingsManager } from './manager.js';
11
+ // Core types
12
+ export type {
13
+ Settings,
14
+ SettingsManagerOptions,
15
+ ProviderName,
16
+ PermissionRules,
17
+ ConfigLevelType,
18
+ ConfigLevel,
19
+ ConfigSource,
20
+ MergedConfig,
21
+ ProvidersConfig,
22
+ ProviderConnection,
23
+ CachedModel,
24
+ ProviderModels,
25
+ } from './types.js';
26
+
27
+ // Constants
28
+ export {
29
+ DEFAULT_SETTINGS_DIR,
30
+ PROJECT_SETTINGS_DIR,
31
+ FALLBACK_SETTINGS_DIR,
32
+ FALLBACK_PROJECT_DIR,
33
+ SETTINGS_FILE_NAME,
34
+ SETTINGS_LOCAL_FILE_NAME,
35
+ MANAGED_SETTINGS_FILE_NAME,
36
+ PROVIDERS_FILE_NAME,
37
+ GENCODE_DIR,
38
+ CLAUDE_DIR,
39
+ USER_GENCODE_DIR,
40
+ USER_CLAUDE_DIR,
41
+ GENCODE_CONFIG_DIRS_ENV,
42
+ getManagedPaths,
43
+ } from './types.js';
44
+
45
+ // Configuration levels
46
+ export {
47
+ findProjectRoot,
48
+ parseExtraConfigDirs,
49
+ getConfigLevels,
50
+ getPrimarySettingsDir,
51
+ getSettingsFilePath,
52
+ type ConfigPathInfo,
53
+ type ResolvedLevel,
54
+ } from './levels.js';
55
+
56
+ // Configuration loader
57
+ export {
58
+ loadAllSources,
59
+ loadSourcesByLevel,
60
+ loadUserSettings,
61
+ loadProjectSettings,
62
+ loadManagedSettings,
63
+ getConfigInfo,
64
+ getExistingConfigFiles,
65
+ } from './loader.js';
66
+
67
+ // Configuration merger
68
+ export {
69
+ deepMerge,
70
+ mergeSettings,
71
+ extractManagedDeny,
72
+ applyManagedRestrictions,
73
+ mergeAllSources,
74
+ mergeWithCliArgs,
75
+ createMergeSummary,
76
+ } from './merger.js';
77
+
78
+ // Configuration managers
79
+ export { ConfigManager, SettingsManager } from './manager.js';
80
+
81
+ // Providers configuration
6
82
  export { ProvidersConfigManager } from './providers-config.js';
7
- export type { Settings, SettingsManagerOptions, ProviderName, ProvidersConfig } from './types.js';
8
- export { DEFAULT_SETTINGS_DIR, SETTINGS_FILE_NAME, PROVIDERS_FILE_NAME } from './types.js';
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Config Levels Tests
3
+ */
4
+
5
+ import * as fs from 'fs/promises';
6
+ import * as path from 'path';
7
+ import * as os from 'os';
8
+ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
9
+ import {
10
+ findProjectRoot,
11
+ parseExtraConfigDirs,
12
+ getConfigLevels,
13
+ getPrimarySettingsDir,
14
+ getSettingsFilePath,
15
+ } from './levels.js';
16
+ import { createTestProject, type TestProject } from './test-utils.js';
17
+
18
+ describe('findProjectRoot', () => {
19
+ let test: TestProject;
20
+
21
+ beforeEach(async () => {
22
+ test = await createTestProject('gencode-levels-');
23
+ });
24
+
25
+ afterEach(() => test.cleanup());
26
+
27
+ it('should find git root', async () => {
28
+ const subDir = path.join(test.projectDir, 'src', 'components');
29
+ await fs.mkdir(subDir, { recursive: true });
30
+
31
+ expect(await findProjectRoot(subDir)).toBe(test.projectDir);
32
+ });
33
+
34
+ it('should find .gencode directory as project root', async () => {
35
+ // Remove .git, add .gencode
36
+ await fs.rm(path.join(test.projectDir, '.git'), { recursive: true });
37
+ await fs.mkdir(path.join(test.projectDir, '.gencode'));
38
+ const subDir = path.join(test.projectDir, 'src');
39
+ await fs.mkdir(subDir, { recursive: true });
40
+
41
+ expect(await findProjectRoot(subDir)).toBe(test.projectDir);
42
+ });
43
+
44
+ it('should find .claude directory as project root', async () => {
45
+ await fs.rm(path.join(test.projectDir, '.git'), { recursive: true });
46
+ await fs.mkdir(path.join(test.projectDir, '.claude'));
47
+ const subDir = path.join(test.projectDir, 'lib');
48
+ await fs.mkdir(subDir, { recursive: true });
49
+
50
+ expect(await findProjectRoot(subDir)).toBe(test.projectDir);
51
+ });
52
+
53
+ it('should return cwd if no project markers found', async () => {
54
+ const subDir = path.join(test.tempDir, 'random', 'path');
55
+ await fs.mkdir(subDir, { recursive: true });
56
+
57
+ expect(await findProjectRoot(subDir)).toBe(subDir);
58
+ });
59
+ });
60
+
61
+ describe('parseExtraConfigDirs', () => {
62
+ const originalEnv = process.env.GENCODE_CONFIG_DIRS;
63
+
64
+ afterEach(() => {
65
+ if (originalEnv === undefined) {
66
+ delete process.env.GENCODE_CONFIG_DIRS;
67
+ } else {
68
+ process.env.GENCODE_CONFIG_DIRS = originalEnv;
69
+ }
70
+ });
71
+
72
+ it('should return empty array when env var not set', () => {
73
+ delete process.env.GENCODE_CONFIG_DIRS;
74
+ expect(parseExtraConfigDirs()).toEqual([]);
75
+ });
76
+
77
+ it('should parse single directory', () => {
78
+ process.env.GENCODE_CONFIG_DIRS = '/team/config';
79
+ expect(parseExtraConfigDirs()).toEqual(['/team/config']);
80
+ });
81
+
82
+ it('should parse multiple directories', () => {
83
+ process.env.GENCODE_CONFIG_DIRS = '/team/config:/shared/rules';
84
+ expect(parseExtraConfigDirs()).toEqual(['/team/config', '/shared/rules']);
85
+ });
86
+
87
+ it('should expand tilde to home directory', () => {
88
+ process.env.GENCODE_CONFIG_DIRS = '~/my-config';
89
+ expect(parseExtraConfigDirs()[0]).toBe(path.join(os.homedir(), 'my-config'));
90
+ });
91
+
92
+ it('should trim whitespace and filter empty strings', () => {
93
+ process.env.GENCODE_CONFIG_DIRS = ' /path/one : : /path/two ';
94
+ expect(parseExtraConfigDirs()).toEqual(['/path/one', '/path/two']);
95
+ });
96
+ });
97
+
98
+ describe('getConfigLevels', () => {
99
+ let test: TestProject;
100
+
101
+ beforeEach(async () => {
102
+ test = await createTestProject('gencode-levels-');
103
+ });
104
+
105
+ afterEach(() => test.cleanup());
106
+
107
+ it('should return levels in priority order', async () => {
108
+ const types = (await getConfigLevels(test.projectDir)).map((l) => l.type);
109
+
110
+ expect(types.indexOf('user')).toBeLessThan(types.indexOf('project'));
111
+ expect(types.indexOf('project')).toBeLessThan(types.indexOf('local'));
112
+ expect(types.indexOf('local')).toBeLessThan(types.indexOf('managed'));
113
+ });
114
+
115
+ it('should include both claude and gencode paths at each level', async () => {
116
+ const userLevel = (await getConfigLevels(test.projectDir)).find((l) => l.type === 'user');
117
+
118
+ expect(userLevel?.paths.length).toBe(2);
119
+ expect(userLevel?.paths.some((p) => p.namespace === 'claude')).toBe(true);
120
+ expect(userLevel?.paths.some((p) => p.namespace === 'gencode')).toBe(true);
121
+ });
122
+
123
+ it('should include extra dirs when env var is set', async () => {
124
+ process.env.GENCODE_CONFIG_DIRS = '/team/config';
125
+ const extraLevels = (await getConfigLevels(test.projectDir)).filter((l) => l.type === 'extra');
126
+
127
+ expect(extraLevels.length).toBeGreaterThan(0);
128
+ });
129
+
130
+ it('should have claude before gencode in each level (for merge order)', async () => {
131
+ for (const level of await getConfigLevels(test.projectDir)) {
132
+ if (level.paths.length >= 2) {
133
+ const claudeIdx = level.paths.findIndex((p) => p.namespace === 'claude');
134
+ const gencodeIdx = level.paths.findIndex((p) => p.namespace === 'gencode');
135
+ if (claudeIdx !== -1 && gencodeIdx !== -1) {
136
+ expect(claudeIdx).toBeLessThan(gencodeIdx);
137
+ }
138
+ }
139
+ }
140
+ });
141
+ });
142
+
143
+ describe('getPrimarySettingsDir', () => {
144
+ it('should return ~/.gencode for user level', () => {
145
+ expect(getPrimarySettingsDir('user', '/project')).toBe(path.join(os.homedir(), '.gencode'));
146
+ });
147
+
148
+ it('should return project/.gencode for project and local levels', () => {
149
+ expect(getPrimarySettingsDir('project', '/my/project')).toBe('/my/project/.gencode');
150
+ expect(getPrimarySettingsDir('local', '/my/project')).toBe('/my/project/.gencode');
151
+ });
152
+ });
153
+
154
+ describe('getSettingsFilePath', () => {
155
+ it('should return correct paths for each level', () => {
156
+ expect(getSettingsFilePath('user', '/project'))
157
+ .toBe(path.join(os.homedir(), '.gencode', 'settings.json'));
158
+ expect(getSettingsFilePath('project', '/my/project'))
159
+ .toBe('/my/project/.gencode/settings.json');
160
+ expect(getSettingsFilePath('local', '/my/project'))
161
+ .toBe('/my/project/.gencode/settings.local.json');
162
+ });
163
+ });