gencode-ai 0.1.1 → 0.1.3

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 (287) hide show
  1. package/.gencode/settings.local.json +7 -0
  2. package/CLAUDE.md +86 -0
  3. package/README.md +13 -16
  4. package/dist/agent/agent.d.ts +50 -1
  5. package/dist/agent/agent.d.ts.map +1 -1
  6. package/dist/agent/agent.js +96 -16
  7. package/dist/agent/agent.js.map +1 -1
  8. package/dist/agent/index.d.ts +1 -0
  9. package/dist/agent/index.d.ts.map +1 -1
  10. package/dist/agent/types.d.ts +14 -1
  11. package/dist/agent/types.d.ts.map +1 -1
  12. package/dist/cli/components/App.d.ts +8 -1
  13. package/dist/cli/components/App.d.ts.map +1 -1
  14. package/dist/cli/components/App.js +266 -29
  15. package/dist/cli/components/App.js.map +1 -1
  16. package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
  17. package/dist/cli/components/CommandSuggestions.js +2 -0
  18. package/dist/cli/components/CommandSuggestions.js.map +1 -1
  19. package/dist/cli/components/Header.d.ts +1 -1
  20. package/dist/cli/components/Header.d.ts.map +1 -1
  21. package/dist/cli/components/Header.js +4 -6
  22. package/dist/cli/components/Header.js.map +1 -1
  23. package/dist/cli/components/Logo.d.ts +1 -0
  24. package/dist/cli/components/Logo.d.ts.map +1 -1
  25. package/dist/cli/components/Logo.js +16 -3
  26. package/dist/cli/components/Logo.js.map +1 -1
  27. package/dist/cli/components/Messages.d.ts +4 -4
  28. package/dist/cli/components/Messages.d.ts.map +1 -1
  29. package/dist/cli/components/Messages.js +66 -23
  30. package/dist/cli/components/Messages.js.map +1 -1
  31. package/dist/cli/components/PermissionPrompt.d.ts +60 -0
  32. package/dist/cli/components/PermissionPrompt.d.ts.map +1 -0
  33. package/dist/cli/components/PermissionPrompt.js +192 -0
  34. package/dist/cli/components/PermissionPrompt.js.map +1 -0
  35. package/dist/cli/components/ProviderManager.js +3 -3
  36. package/dist/cli/components/ProviderManager.js.map +1 -1
  37. package/dist/cli/components/QuestionPrompt.d.ts +23 -0
  38. package/dist/cli/components/QuestionPrompt.d.ts.map +1 -0
  39. package/dist/cli/components/QuestionPrompt.js +231 -0
  40. package/dist/cli/components/QuestionPrompt.js.map +1 -0
  41. package/dist/cli/components/Spinner.d.ts +7 -2
  42. package/dist/cli/components/Spinner.d.ts.map +1 -1
  43. package/dist/cli/components/Spinner.js +116 -25
  44. package/dist/cli/components/Spinner.js.map +1 -1
  45. package/dist/cli/components/TodoList.d.ts +7 -0
  46. package/dist/cli/components/TodoList.d.ts.map +1 -0
  47. package/dist/cli/components/TodoList.js +34 -0
  48. package/dist/cli/components/TodoList.js.map +1 -0
  49. package/dist/cli/components/index.d.ts +2 -0
  50. package/dist/cli/components/index.d.ts.map +1 -1
  51. package/dist/cli/components/index.js +2 -0
  52. package/dist/cli/components/index.js.map +1 -1
  53. package/dist/cli/components/theme.d.ts +7 -0
  54. package/dist/cli/components/theme.d.ts.map +1 -1
  55. package/dist/cli/components/theme.js +11 -1
  56. package/dist/cli/components/theme.js.map +1 -1
  57. package/dist/cli/index.js +47 -7
  58. package/dist/cli/index.js.map +1 -1
  59. package/dist/config/index.d.ts +13 -4
  60. package/dist/config/index.d.ts.map +1 -1
  61. package/dist/config/index.js +18 -3
  62. package/dist/config/index.js.map +1 -1
  63. package/dist/config/levels.d.ts +49 -0
  64. package/dist/config/levels.d.ts.map +1 -0
  65. package/dist/config/levels.js +222 -0
  66. package/dist/config/levels.js.map +1 -0
  67. package/dist/config/loader.d.ts +46 -0
  68. package/dist/config/loader.d.ts.map +1 -0
  69. package/dist/config/loader.js +153 -0
  70. package/dist/config/loader.js.map +1 -0
  71. package/dist/config/manager.d.ts +115 -15
  72. package/dist/config/manager.d.ts.map +1 -1
  73. package/dist/config/manager.js +260 -34
  74. package/dist/config/manager.js.map +1 -1
  75. package/dist/config/manager.test.d.ts +5 -0
  76. package/dist/config/manager.test.d.ts.map +1 -0
  77. package/dist/config/manager.test.js +192 -0
  78. package/dist/config/manager.test.js.map +1 -0
  79. package/dist/config/merger.d.ts +56 -0
  80. package/dist/config/merger.d.ts.map +1 -0
  81. package/dist/config/merger.js +177 -0
  82. package/dist/config/merger.js.map +1 -0
  83. package/dist/config/test-utils.d.ts +24 -0
  84. package/dist/config/test-utils.d.ts.map +1 -0
  85. package/dist/config/test-utils.js +55 -0
  86. package/dist/config/test-utils.js.map +1 -0
  87. package/dist/config/types.d.ts +78 -9
  88. package/dist/config/types.d.ts.map +1 -1
  89. package/dist/config/types.js +52 -2
  90. package/dist/config/types.js.map +1 -1
  91. package/dist/memory/import-resolver.d.ts +46 -0
  92. package/dist/memory/import-resolver.d.ts.map +1 -0
  93. package/dist/memory/import-resolver.js +117 -0
  94. package/dist/memory/import-resolver.js.map +1 -0
  95. package/dist/memory/index.d.ts +7 -6
  96. package/dist/memory/index.d.ts.map +1 -1
  97. package/dist/memory/index.js +7 -5
  98. package/dist/memory/index.js.map +1 -1
  99. package/dist/memory/init-prompt.d.ts +22 -0
  100. package/dist/memory/init-prompt.d.ts.map +1 -0
  101. package/dist/memory/init-prompt.js +103 -0
  102. package/dist/memory/init-prompt.js.map +1 -0
  103. package/dist/memory/memory-manager.d.ts +119 -0
  104. package/dist/memory/memory-manager.d.ts.map +1 -0
  105. package/dist/memory/memory-manager.js +587 -0
  106. package/dist/memory/memory-manager.js.map +1 -0
  107. package/dist/memory/rules-parser.d.ts +38 -0
  108. package/dist/memory/rules-parser.d.ts.map +1 -0
  109. package/dist/memory/rules-parser.js +69 -0
  110. package/dist/memory/rules-parser.js.map +1 -0
  111. package/dist/memory/test-utils.d.ts +20 -0
  112. package/dist/memory/test-utils.d.ts.map +1 -0
  113. package/dist/memory/test-utils.js +44 -0
  114. package/dist/memory/test-utils.js.map +1 -0
  115. package/dist/memory/types.d.ts +70 -63
  116. package/dist/memory/types.d.ts.map +1 -1
  117. package/dist/memory/types.js +42 -2
  118. package/dist/memory/types.js.map +1 -1
  119. package/dist/permissions/audit.d.ts +82 -0
  120. package/dist/permissions/audit.d.ts.map +1 -0
  121. package/dist/permissions/audit.js +229 -0
  122. package/dist/permissions/audit.js.map +1 -0
  123. package/dist/permissions/index.d.ts +11 -1
  124. package/dist/permissions/index.d.ts.map +1 -1
  125. package/dist/permissions/index.js +15 -0
  126. package/dist/permissions/index.js.map +1 -1
  127. package/dist/permissions/manager.d.ts +149 -13
  128. package/dist/permissions/manager.d.ts.map +1 -1
  129. package/dist/permissions/manager.js +480 -35
  130. package/dist/permissions/manager.js.map +1 -1
  131. package/dist/permissions/manager.test.d.ts +5 -0
  132. package/dist/permissions/manager.test.d.ts.map +1 -0
  133. package/dist/permissions/manager.test.js +213 -0
  134. package/dist/permissions/manager.test.js.map +1 -0
  135. package/dist/permissions/persistence.d.ts +74 -0
  136. package/dist/permissions/persistence.d.ts.map +1 -0
  137. package/dist/permissions/persistence.js +248 -0
  138. package/dist/permissions/persistence.js.map +1 -0
  139. package/dist/permissions/persistence.test.d.ts +5 -0
  140. package/dist/permissions/persistence.test.d.ts.map +1 -0
  141. package/dist/permissions/persistence.test.js +171 -0
  142. package/dist/permissions/persistence.test.js.map +1 -0
  143. package/dist/permissions/prompt-matcher.d.ts +64 -0
  144. package/dist/permissions/prompt-matcher.d.ts.map +1 -0
  145. package/dist/permissions/prompt-matcher.js +415 -0
  146. package/dist/permissions/prompt-matcher.js.map +1 -0
  147. package/dist/permissions/prompt-matcher.test.d.ts +5 -0
  148. package/dist/permissions/prompt-matcher.test.d.ts.map +1 -0
  149. package/dist/permissions/prompt-matcher.test.js +107 -0
  150. package/dist/permissions/prompt-matcher.test.js.map +1 -0
  151. package/dist/permissions/types.d.ts +157 -0
  152. package/dist/permissions/types.d.ts.map +1 -1
  153. package/dist/permissions/types.js +45 -8
  154. package/dist/permissions/types.js.map +1 -1
  155. package/dist/prompts/index.d.ts +92 -0
  156. package/dist/prompts/index.d.ts.map +1 -0
  157. package/dist/prompts/index.js +241 -0
  158. package/dist/prompts/index.js.map +1 -0
  159. package/dist/tools/builtin/ask-user.d.ts +64 -0
  160. package/dist/tools/builtin/ask-user.d.ts.map +1 -0
  161. package/dist/tools/builtin/ask-user.js +148 -0
  162. package/dist/tools/builtin/ask-user.js.map +1 -0
  163. package/dist/tools/builtin/bash.d.ts.map +1 -1
  164. package/dist/tools/builtin/bash.js +2 -1
  165. package/dist/tools/builtin/bash.js.map +1 -1
  166. package/dist/tools/builtin/edit.d.ts.map +1 -1
  167. package/dist/tools/builtin/edit.js +2 -1
  168. package/dist/tools/builtin/edit.js.map +1 -1
  169. package/dist/tools/builtin/glob.d.ts.map +1 -1
  170. package/dist/tools/builtin/glob.js +2 -1
  171. package/dist/tools/builtin/glob.js.map +1 -1
  172. package/dist/tools/builtin/grep.d.ts.map +1 -1
  173. package/dist/tools/builtin/grep.js +2 -1
  174. package/dist/tools/builtin/grep.js.map +1 -1
  175. package/dist/tools/builtin/read.d.ts.map +1 -1
  176. package/dist/tools/builtin/read.js +2 -1
  177. package/dist/tools/builtin/read.js.map +1 -1
  178. package/dist/tools/builtin/todowrite.d.ts +15 -0
  179. package/dist/tools/builtin/todowrite.d.ts.map +1 -0
  180. package/dist/tools/builtin/todowrite.js +88 -0
  181. package/dist/tools/builtin/todowrite.js.map +1 -0
  182. package/dist/tools/builtin/webfetch.d.ts.map +1 -1
  183. package/dist/tools/builtin/webfetch.js +2 -5
  184. package/dist/tools/builtin/webfetch.js.map +1 -1
  185. package/dist/tools/builtin/websearch.d.ts.map +1 -1
  186. package/dist/tools/builtin/websearch.js +2 -16
  187. package/dist/tools/builtin/websearch.js.map +1 -1
  188. package/dist/tools/builtin/write.d.ts.map +1 -1
  189. package/dist/tools/builtin/write.js +2 -1
  190. package/dist/tools/builtin/write.js.map +1 -1
  191. package/dist/tools/index.d.ts +19 -0
  192. package/dist/tools/index.d.ts.map +1 -1
  193. package/dist/tools/index.js +8 -0
  194. package/dist/tools/index.js.map +1 -1
  195. package/dist/tools/types.d.ts +39 -0
  196. package/dist/tools/types.d.ts.map +1 -1
  197. package/dist/tools/types.js +8 -0
  198. package/dist/tools/types.js.map +1 -1
  199. package/docs/config-system-comparison.md +707 -0
  200. package/docs/memory-system.md +238 -0
  201. package/docs/permissions.md +368 -0
  202. package/docs/proposals/0005-todo-system.md +350 -85
  203. package/docs/proposals/0006-memory-system.md +11 -10
  204. package/docs/proposals/0012-ask-user-question.md +1007 -207
  205. package/docs/proposals/0023-permission-enhancements.md +61 -2
  206. package/docs/proposals/0041-configuration-system.md +33 -2
  207. package/docs/proposals/0042-prompt-optimization.md +866 -0
  208. package/docs/proposals/README.md +7 -6
  209. package/examples/test-ask-user.ts +167 -0
  210. package/jest.config.js +26 -0
  211. package/package.json +8 -2
  212. package/src/agent/agent.ts +130 -16
  213. package/src/agent/index.ts +1 -0
  214. package/src/agent/types.ts +13 -1
  215. package/src/cli/components/App.tsx +362 -37
  216. package/src/cli/components/CommandSuggestions.tsx +2 -0
  217. package/src/cli/components/Header.tsx +11 -17
  218. package/src/cli/components/Logo.tsx +76 -9
  219. package/src/cli/components/Messages.tsx +104 -41
  220. package/src/cli/components/PermissionPrompt.tsx +388 -0
  221. package/src/cli/components/ProviderManager.tsx +5 -5
  222. package/src/cli/components/QuestionPrompt.tsx +462 -0
  223. package/src/cli/components/Spinner.tsx +138 -25
  224. package/src/cli/components/TodoList.tsx +54 -0
  225. package/src/cli/components/index.ts +7 -0
  226. package/src/cli/components/theme.ts +11 -1
  227. package/src/cli/index.tsx +54 -6
  228. package/src/config/index.ts +78 -4
  229. package/src/config/levels.test.ts +163 -0
  230. package/src/config/levels.ts +285 -0
  231. package/src/config/loader.test.ts +120 -0
  232. package/src/config/loader.ts +178 -0
  233. package/src/config/manager.test.ts +215 -0
  234. package/src/config/manager.ts +328 -40
  235. package/src/config/merger.test.ts +360 -0
  236. package/src/config/merger.ts +221 -0
  237. package/src/config/test-utils.ts +79 -0
  238. package/src/config/types.ts +152 -9
  239. package/src/memory/import-resolver.test.ts +117 -0
  240. package/src/memory/import-resolver.ts +149 -0
  241. package/src/memory/index.ts +11 -0
  242. package/src/memory/init-prompt.ts +113 -0
  243. package/src/memory/memory-manager.test.ts +198 -0
  244. package/src/memory/memory-manager.ts +716 -0
  245. package/src/memory/rules-parser.test.ts +182 -0
  246. package/src/memory/rules-parser.ts +82 -0
  247. package/src/memory/test-utils.ts +60 -0
  248. package/src/memory/types.ts +119 -0
  249. package/src/permissions/audit.ts +284 -0
  250. package/src/permissions/index.ts +20 -1
  251. package/src/permissions/manager.test.ts +260 -0
  252. package/src/permissions/manager.ts +592 -40
  253. package/src/permissions/persistence.test.ts +220 -0
  254. package/src/permissions/persistence.ts +301 -0
  255. package/src/permissions/prompt-matcher.test.ts +213 -0
  256. package/src/permissions/prompt-matcher.ts +472 -0
  257. package/src/permissions/types.ts +238 -8
  258. package/src/prompts/index.test.ts +279 -0
  259. package/src/prompts/index.ts +306 -0
  260. package/src/prompts/system/anthropic.txt +29 -0
  261. package/src/prompts/system/base.txt +166 -0
  262. package/src/prompts/system/gemini.txt +35 -0
  263. package/src/prompts/system/generic.txt +128 -0
  264. package/src/prompts/system/openai.txt +29 -0
  265. package/src/prompts/tools/ask-user.txt +110 -0
  266. package/src/prompts/tools/bash.txt +60 -0
  267. package/src/prompts/tools/edit.txt +29 -0
  268. package/src/prompts/tools/glob.txt +35 -0
  269. package/src/prompts/tools/grep.txt +43 -0
  270. package/src/prompts/tools/read.txt +22 -0
  271. package/src/prompts/tools/todowrite.txt +71 -0
  272. package/src/prompts/tools/webfetch.txt +34 -0
  273. package/src/prompts/tools/websearch.txt +41 -0
  274. package/src/prompts/tools/write.txt +23 -0
  275. package/src/tools/builtin/ask-user.ts +185 -0
  276. package/src/tools/builtin/bash.ts +2 -1
  277. package/src/tools/builtin/edit.ts +2 -1
  278. package/src/tools/builtin/glob.ts +2 -1
  279. package/src/tools/builtin/grep.ts +2 -1
  280. package/src/tools/builtin/read.ts +2 -1
  281. package/src/tools/builtin/todowrite.ts +102 -0
  282. package/src/tools/builtin/webfetch.ts +2 -5
  283. package/src/tools/builtin/websearch.ts +2 -16
  284. package/src/tools/builtin/write.ts +2 -1
  285. package/src/tools/index.ts +19 -0
  286. package/src/tools/types.ts +30 -0
  287. package/tsconfig.json +1 -1
@@ -22,13 +22,24 @@ import { PromptInput, ConfirmPrompt } from './Input.js';
22
22
  import { ModelSelector } from './ModelSelector.js';
23
23
  import { ProviderManager } from './ProviderManager.js';
24
24
  import { CommandSuggestions, getFilteredCommands } from './CommandSuggestions.js';
25
+ import {
26
+ PermissionPrompt,
27
+ PermissionRulesDisplay,
28
+ PermissionAuditDisplay,
29
+ } from './PermissionPrompt.js';
30
+ import { TodoList } from './TodoList.js';
31
+ import { QuestionPrompt, AnswerDisplay } from './QuestionPrompt.js';
25
32
  import { colors, icons } from './theme.js';
33
+ import { getTodos, formatAnswersForDisplay } from '../../tools/index.js';
34
+ import type { Question, QuestionAnswer } from '../../tools/types.js';
26
35
  import type { ProviderName } from '../../providers/index.js';
36
+ import type { ApprovalAction, ApprovalSuggestion } from '../../permissions/types.js';
37
+ import { gatherContextFiles, buildInitPrompt, getContextSummary } from '../../memory/index.js';
27
38
 
28
39
  // Types
29
40
  interface HistoryItem {
30
41
  id: string;
31
- type: 'header' | 'welcome' | 'user' | 'assistant' | 'tool_call' | 'tool_result' | 'info' | 'completion';
42
+ type: 'header' | 'welcome' | 'user' | 'assistant' | 'tool_call' | 'tool_result' | 'info' | 'completion' | 'todos';
32
43
  content: string;
33
44
  meta?: Record<string, unknown>;
34
45
  }
@@ -36,11 +47,19 @@ interface HistoryItem {
36
47
  interface ConfirmState {
37
48
  tool: string;
38
49
  input: Record<string, unknown>;
39
- resolve: (confirmed: boolean) => void;
50
+ suggestions: ApprovalSuggestion[];
51
+ resolve: (action: ApprovalAction) => void;
52
+ }
53
+
54
+ interface QuestionState {
55
+ questions: Question[];
56
+ resolve: (answers: QuestionAnswer[]) => void;
40
57
  }
41
58
 
42
59
  interface SettingsManager {
43
60
  save: (settings: { model?: string }) => Promise<void>;
61
+ getCwd?: () => string;
62
+ addPermissionRule?: (pattern: string, type: 'allow' | 'deny', level?: 'global' | 'project' | 'local') => Promise<void>;
44
63
  }
45
64
 
46
65
  interface Session {
@@ -49,10 +68,16 @@ interface Session {
49
68
  updatedAt: string;
50
69
  }
51
70
 
71
+ interface PermissionSettings {
72
+ allow?: string[];
73
+ deny?: string[];
74
+ }
75
+
52
76
  interface AppProps {
53
77
  config: AgentConfig;
54
78
  settingsManager?: SettingsManager;
55
79
  resumeLatest?: boolean;
80
+ permissionSettings?: PermissionSettings;
56
81
  }
57
82
 
58
83
  // ============================================================================
@@ -125,10 +150,64 @@ function SessionsTable({ sessions }: SessionsTableProps) {
125
150
  );
126
151
  }
127
152
 
153
+ // ============================================================================
154
+ // Memory Files Display Component
155
+ // ============================================================================
156
+ interface MemoryFileInfo {
157
+ path: string;
158
+ level: string;
159
+ size: number;
160
+ type: 'file' | 'rule';
161
+ }
162
+
163
+ function MemoryFilesDisplay({ files }: { files: MemoryFileInfo[] }) {
164
+ const formatSize = (bytes: number): string => {
165
+ if (bytes < 1024) return `${bytes}B`;
166
+ return `${(bytes / 1024).toFixed(1)}KB`;
167
+ };
168
+
169
+ const memoryFiles = files.filter((f) => f.type === 'file');
170
+ const ruleFiles = files.filter((f) => f.type === 'rule');
171
+
172
+ return (
173
+ <Box flexDirection="column">
174
+ {memoryFiles.length > 0 && (
175
+ <>
176
+ <Text color={colors.info}>Loaded Memory Files:</Text>
177
+ {memoryFiles.map((f, i) => (
178
+ <Text key={f.path}>
179
+ <Text color={colors.textMuted}> [{i + 1}] </Text>
180
+ <Text color={colors.primary}>{f.path} </Text>
181
+ <Text color={colors.textMuted}>({f.level}, {formatSize(f.size)})</Text>
182
+ </Text>
183
+ ))}
184
+ </>
185
+ )}
186
+ {ruleFiles.length > 0 && (
187
+ <Box flexDirection="column" marginTop={memoryFiles.length > 0 ? 1 : 0}>
188
+ <Text color={colors.info}>
189
+ Loaded Rules:
190
+ </Text>
191
+ {ruleFiles.map((f, i) => (
192
+ <Text key={f.path}>
193
+ <Text color={colors.textMuted}> [{i + 1}] </Text>
194
+ <Text color={colors.warning}>{f.path} </Text>
195
+ <Text color={colors.textMuted}>({f.level}, {formatSize(f.size)})</Text>
196
+ </Text>
197
+ ))}
198
+ </Box>
199
+ )}
200
+ {files.length === 0 && (
201
+ <Text color={colors.textMuted}>No memory files loaded</Text>
202
+ )}
203
+ </Box>
204
+ );
205
+ }
206
+
128
207
  // ============================================================================
129
208
  // Main App
130
209
  // ============================================================================
131
- export function App({ config, settingsManager, resumeLatest }: AppProps) {
210
+ export function App({ config, settingsManager, resumeLatest, permissionSettings }: AppProps) {
132
211
  const { exit } = useApp();
133
212
  const agent = useAgent(config);
134
213
 
@@ -160,7 +239,10 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
160
239
  const [isThinking, setIsThinking] = useState(false);
161
240
  const [streamingText, setStreamingText] = useState('');
162
241
  const streamingTextRef = useRef(''); // Track current streaming text for closure
242
+ const [processingStartTime, setProcessingStartTime] = useState<number | undefined>(undefined);
243
+ const [tokenCount, setTokenCount] = useState(0);
163
244
  const [confirmState, setConfirmState] = useState<ConfirmState | null>(null);
245
+ const [questionState, setQuestionState] = useState<QuestionState | null>(null);
164
246
  const [showModelSelector, setShowModelSelector] = useState(false);
165
247
  const [showProviderManager, setShowProviderManager] = useState(false);
166
248
  const [currentModel, setCurrentModel] = useState(config.model);
@@ -168,6 +250,7 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
168
250
  const [inputKey, setInputKey] = useState(0); // Force cursor to end after autocomplete
169
251
  const [pendingTool, setPendingTool] = useState<{ name: string; input: Record<string, unknown> } | null>(null);
170
252
  const pendingToolRef = useRef<{ name: string; input: Record<string, unknown> } | null>(null);
253
+ const [todos, setTodos] = useState<ReturnType<typeof getTodos>>([]);
171
254
 
172
255
  // Check if showing command suggestions
173
256
  const showCmdSuggestions = input.startsWith('/') && !isProcessing;
@@ -186,12 +269,37 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
186
269
  // Initialize
187
270
  useEffect(() => {
188
271
  const init = async () => {
189
- agent.setConfirmCallback(async (tool: string, toolInput: unknown) => {
190
- return new Promise<boolean>((resolve) => {
191
- setConfirmState({ tool, input: toolInput as Record<string, unknown>, resolve });
272
+ // Initialize permission system with settings
273
+ await agent.initializePermissions(permissionSettings);
274
+
275
+ // Set enhanced confirm callback with approval options
276
+ agent.setEnhancedConfirmCallback(async (tool, toolInput, suggestions) => {
277
+ return new Promise<ApprovalAction>((resolve) => {
278
+ setConfirmState({
279
+ tool,
280
+ input: toolInput as Record<string, unknown>,
281
+ suggestions,
282
+ resolve,
283
+ });
284
+ });
285
+ });
286
+
287
+ // Set askUser callback for AskUserQuestion tool
288
+ agent.setAskUserCallback(async (questions) => {
289
+ return new Promise<QuestionAnswer[]>((resolve) => {
290
+ setQuestionState({ questions, resolve });
192
291
  });
193
292
  });
194
293
 
294
+ // Set callback to save permission rules to settings.local.json
295
+ if (settingsManager?.addPermissionRule) {
296
+ agent.setSaveRuleCallback(async (tool, pattern) => {
297
+ // Format as Claude Code style pattern: Tool(pattern) or just Tool
298
+ const rulePattern = pattern ? `${tool}(${pattern})` : tool;
299
+ await settingsManager.addPermissionRule!(rulePattern, 'allow', 'local');
300
+ });
301
+ }
302
+
195
303
  if (resumeLatest) {
196
304
  const resumed = await agent.resumeLatest();
197
305
  if (resumed) {
@@ -200,12 +308,38 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
200
308
  }
201
309
  };
202
310
  init();
203
- }, [agent, resumeLatest, addHistory]);
311
+ }, [agent, resumeLatest, addHistory, permissionSettings, settingsManager]);
204
312
 
205
- // Handle confirm
206
- const handleConfirm = (confirmed: boolean) => {
313
+ // Handle question answers (AskUserQuestion)
314
+ const handleQuestionComplete = useCallback((answers: QuestionAnswer[]) => {
315
+ if (questionState) {
316
+ // Show confirmation in history
317
+ addHistory({
318
+ type: 'info',
319
+ content: formatAnswersForDisplay(answers),
320
+ });
321
+ questionState.resolve(answers);
322
+ setQuestionState(null);
323
+ }
324
+ }, [questionState, addHistory]);
325
+
326
+ // Handle question cancel
327
+ const handleQuestionCancel = useCallback(() => {
328
+ if (questionState) {
329
+ // Clear pending tool display (no more spinner)
330
+ pendingToolRef.current = null;
331
+ setPendingTool(null);
332
+ // Add canceled message to history
333
+ addHistory({ type: 'info', content: 'Question canceled' });
334
+ questionState.resolve([]); // Return empty answers on cancel
335
+ setQuestionState(null);
336
+ }
337
+ }, [questionState, addHistory]);
338
+
339
+ // Handle permission decision
340
+ const handlePermissionDecision = (action: ApprovalAction) => {
207
341
  if (confirmState) {
208
- confirmState.resolve(confirmed);
342
+ confirmState.resolve(action);
209
343
  setConfirmState(null);
210
344
  }
211
345
  };
@@ -321,13 +455,113 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
321
455
  return true;
322
456
  }
323
457
 
458
+ case 'permissions': {
459
+ const permManager = agent.getPermissionManager();
460
+
461
+ if (arg === 'audit') {
462
+ // Show audit log
463
+ const auditLog = permManager.getAuditLog(20);
464
+ if (auditLog.length === 0) {
465
+ addHistory({ type: 'info', content: 'No permission decisions recorded yet' });
466
+ } else {
467
+ const entries = auditLog.map((e) => ({
468
+ time: e.timestamp.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }),
469
+ tool: e.tool,
470
+ input: e.inputSummary,
471
+ decision: e.decision,
472
+ rule: e.matchedRule,
473
+ }));
474
+ addHistory({
475
+ type: 'info',
476
+ content: '__PERMISSION_AUDIT__',
477
+ meta: { entries },
478
+ });
479
+ }
480
+ } else if (arg === 'stats') {
481
+ // Show statistics
482
+ const stats = permManager.getAuditStats();
483
+ addHistory({
484
+ type: 'info',
485
+ content: `Permissions: ${stats.allowed + stats.confirmed} allowed, ${stats.denied + stats.rejected} denied`,
486
+ });
487
+ } else {
488
+ // Show rules
489
+ const rules = permManager.getRules();
490
+ const prompts = permManager.getAllowedPrompts();
491
+ const displayRules = rules.map((r) => ({
492
+ type: r.scope === 'session' ? 'Session' : r.description?.startsWith('Settings') ? 'Settings' : 'Built-in',
493
+ tool: typeof r.tool === 'string' ? r.tool : r.tool.toString(),
494
+ pattern: typeof r.pattern === 'string' ? r.pattern : r.pattern?.toString(),
495
+ scope: r.scope ?? 'session',
496
+ mode: r.mode,
497
+ }));
498
+ addHistory({
499
+ type: 'info',
500
+ content: '__PERMISSIONS__',
501
+ meta: { rules: displayRules, prompts },
502
+ });
503
+ }
504
+ return true;
505
+ }
506
+
324
507
  case 'init': {
325
- addHistory({ type: 'info', content: '/init command not available in this version' });
508
+ // Gather context files and generate AGENT.md
509
+ addHistory({ type: 'info', content: 'Analyzing codebase...' });
510
+
511
+ const contextFiles = await gatherContextFiles(cwd);
512
+ addHistory({ type: 'info', content: getContextSummary(contextFiles) });
513
+
514
+ // Check if AGENT.md already exists
515
+ const memoryManager = agent.getMemoryManager();
516
+ const existingPath = await memoryManager.getExistingProjectMemoryPath(cwd);
517
+ let existingContent: string | undefined;
518
+
519
+ if (existingPath) {
520
+ try {
521
+ const fs = await import('fs/promises');
522
+ existingContent = await fs.readFile(existingPath, 'utf-8');
523
+ addHistory({
524
+ type: 'info',
525
+ content: `Found existing: ${existingPath.replace(cwd, '.')}`,
526
+ });
527
+ } catch {
528
+ // File doesn't exist or can't be read
529
+ }
530
+ }
531
+
532
+ // Build init prompt and run through agent
533
+ const initPrompt = buildInitPrompt(contextFiles, existingContent);
534
+ addHistory({ type: 'info', content: 'Generating AGENT.md...' });
535
+ addHistory({ type: 'user', content: '/init' });
536
+ await runAgent(initPrompt);
326
537
  return true;
327
538
  }
328
539
 
329
540
  case 'memory': {
330
- addHistory({ type: 'info', content: '/memory command not available in this version' });
541
+ // Show loaded memory files
542
+ const memoryManager = agent.getMemoryManager();
543
+ const loadedFiles = memoryManager.getLoadedFileList();
544
+
545
+ if (loadedFiles.length === 0) {
546
+ // Try to load memory first
547
+ await agent.loadMemory();
548
+ const filesAfterLoad = memoryManager.getLoadedFileList();
549
+ if (filesAfterLoad.length === 0) {
550
+ addHistory({ type: 'info', content: 'No memory files found' });
551
+ } else {
552
+ addHistory({
553
+ type: 'info',
554
+ content: '__MEMORY__',
555
+ meta: { files: filesAfterLoad },
556
+ });
557
+ }
558
+ } else {
559
+ addHistory({
560
+ type: 'info',
561
+ content: '__MEMORY__',
562
+ meta: { files: loadedFiles },
563
+ });
564
+ }
331
565
  return true;
332
566
  }
333
567
 
@@ -347,6 +581,8 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
347
581
  streamingTextRef.current = '';
348
582
  interruptFlagRef.current = false;
349
583
  const startTime = Date.now();
584
+ setProcessingStartTime(startTime);
585
+ setTokenCount(0);
350
586
 
351
587
  try {
352
588
  for await (const event of agent.run(prompt)) {
@@ -360,6 +596,8 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
360
596
  setIsThinking(false);
361
597
  streamingTextRef.current += event.text;
362
598
  setStreamingText(streamingTextRef.current);
599
+ // Estimate token count (roughly 4 chars per token)
600
+ setTokenCount((prev) => prev + Math.max(1, Math.ceil(event.text.length / 4)));
363
601
  break;
364
602
 
365
603
  case 'tool_start':
@@ -376,24 +614,37 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
376
614
  break;
377
615
 
378
616
  case 'tool_result':
379
- // Add tool_call to history (now completed) - use ref for correct value
380
- if (pendingToolRef.current) {
617
+ // For TodoWrite: add todos first, then hide tool_call/tool_result
618
+ if (event.name === 'TodoWrite') {
619
+ const currentTodos = getTodos();
620
+ setTodos(currentTodos);
621
+ if (currentTodos.length > 0) {
622
+ addHistory({
623
+ type: 'todos',
624
+ content: '',
625
+ meta: { todos: currentTodos },
626
+ });
627
+ }
628
+ } else {
629
+ // Add tool_call to history (now completed) - use ref for correct value
630
+ if (pendingToolRef.current) {
631
+ addHistory({
632
+ type: 'tool_call',
633
+ content: pendingToolRef.current.name,
634
+ meta: { toolName: pendingToolRef.current.name, input: pendingToolRef.current.input },
635
+ });
636
+ }
637
+ // Add tool_result to history
381
638
  addHistory({
382
- type: 'tool_call',
383
- content: pendingToolRef.current.name,
384
- meta: { toolName: pendingToolRef.current.name, input: pendingToolRef.current.input },
639
+ type: 'tool_result',
640
+ content: event.result.output,
641
+ meta: {
642
+ toolName: event.name,
643
+ success: event.result.success,
644
+ metadata: event.result.metadata,
645
+ },
385
646
  });
386
647
  }
387
- // Add tool_result to history
388
- addHistory({
389
- type: 'tool_result',
390
- content: event.result.output,
391
- meta: {
392
- toolName: event.name,
393
- success: event.result.success,
394
- metadata: event.result.metadata,
395
- },
396
- });
397
648
  pendingToolRef.current = null;
398
649
  setPendingTool(null);
399
650
  setIsThinking(true);
@@ -413,6 +664,7 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
413
664
  // Add completion message with duration
414
665
  const durationMs = Date.now() - startTime;
415
666
  addHistory({ type: 'completion', content: '', meta: { durationMs } });
667
+ setProcessingStartTime(undefined);
416
668
  break;
417
669
  }
418
670
  }
@@ -454,6 +706,47 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
454
706
  return;
455
707
  }
456
708
 
709
+ // Handle # prefix for quick memory adds
710
+ // ## note -> user memory (~/.gencode/AGENT.md)
711
+ // # note -> project memory (./AGENT.md)
712
+ if (trimmed.startsWith('#') && !trimmed.startsWith('#!/')) {
713
+ const memoryManager = agent.getMemoryManager();
714
+ let level: 'user' | 'project';
715
+ let content: string;
716
+
717
+ if (trimmed.startsWith('## ')) {
718
+ level = 'user';
719
+ content = trimmed.slice(3).trim();
720
+ } else if (trimmed.startsWith('# ')) {
721
+ level = 'project';
722
+ content = trimmed.slice(2).trim();
723
+ } else {
724
+ // Just # with no space, treat as project
725
+ level = 'project';
726
+ content = trimmed.slice(1).trim();
727
+ }
728
+
729
+ if (!content) {
730
+ addHistory({ type: 'info', content: 'Empty memory entry ignored' });
731
+ return;
732
+ }
733
+
734
+ try {
735
+ const savedPath = await memoryManager.quickAdd(content, level, cwd);
736
+ const displayPath = savedPath.replace(process.env.HOME || '', '~');
737
+ addHistory({
738
+ type: 'info',
739
+ content: `Added to ${level} memory: ${displayPath}`,
740
+ });
741
+ } catch (error) {
742
+ addHistory({
743
+ type: 'info',
744
+ content: `Failed to add to memory: ${error instanceof Error ? error.message : String(error)}`,
745
+ });
746
+ }
747
+ return;
748
+ }
749
+
457
750
  if (trimmed.startsWith('/')) {
458
751
  const handled = await handleCommand(trimmed);
459
752
  if (!handled) {
@@ -478,6 +771,9 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
478
771
  setIsProcessing(false);
479
772
  setStreamingText('');
480
773
  streamingTextRef.current = '';
774
+ // Clear pending tool (stop spinner)
775
+ pendingToolRef.current = null;
776
+ setPendingTool(null);
481
777
  addHistory({ type: 'info', content: 'Interrupted' });
482
778
  }
483
779
 
@@ -537,9 +833,29 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
537
833
  if (item.content === '__SESSIONS__' && item.meta?.input) {
538
834
  return <SessionsTable sessions={item.meta.input as Session[]} />;
539
835
  }
836
+ if (item.content === '__PERMISSIONS__' && item.meta?.rules) {
837
+ return (
838
+ <PermissionRulesDisplay
839
+ rules={item.meta.rules as { type: string; tool: string; pattern?: string; scope: string; mode: string }[]}
840
+ allowedPrompts={item.meta.prompts as { tool: string; prompt: string }[] | undefined}
841
+ />
842
+ );
843
+ }
844
+ if (item.content === '__PERMISSION_AUDIT__' && item.meta?.entries) {
845
+ return (
846
+ <PermissionAuditDisplay
847
+ entries={item.meta.entries as { time: string; tool: string; input: string; decision: string; rule?: string }[]}
848
+ />
849
+ );
850
+ }
851
+ if (item.content === '__MEMORY__' && item.meta?.files) {
852
+ return <MemoryFilesDisplay files={item.meta.files as MemoryFileInfo[]} />;
853
+ }
540
854
  return <InfoMessage text={item.content} />;
541
855
  case 'completion':
542
856
  return <CompletionMessage durationMs={(item.meta?.durationMs as number) || 0} />;
857
+ case 'todos':
858
+ return <TodoList todos={item.meta?.todos as ReturnType<typeof getTodos>} />;
543
859
  default:
544
860
  return null;
545
861
  }
@@ -551,17 +867,26 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
551
867
  {(item) => <Box key={item.id}>{renderHistoryItem(item)}</Box>}
552
868
  </Static>
553
869
 
554
- {pendingTool && <PendingToolCall name={pendingTool.name} input={pendingTool.input} />}
870
+ {pendingTool && !confirmState && <PendingToolCall name={pendingTool.name} input={pendingTool.input} />}
555
871
 
556
872
  {streamingText && <AssistantMessage text={streamingText} streaming />}
557
873
 
558
874
  {confirmState && (
559
- <Box flexDirection="column" marginTop={1}>
560
- <Text color={colors.warning}>
561
- {icons.warning} {confirmState.tool}
562
- </Text>
563
- <ConfirmPrompt message="Allow?" onConfirm={handleConfirm} />
564
- </Box>
875
+ <PermissionPrompt
876
+ tool={confirmState.tool}
877
+ input={confirmState.input}
878
+ suggestions={confirmState.suggestions}
879
+ onDecision={handlePermissionDecision}
880
+ projectPath={settingsManager?.getCwd?.() ?? process.cwd()}
881
+ />
882
+ )}
883
+
884
+ {questionState && (
885
+ <QuestionPrompt
886
+ questions={questionState.questions}
887
+ onComplete={handleQuestionComplete}
888
+ onCancel={handleQuestionCancel}
889
+ />
565
890
  )}
566
891
 
567
892
  {showModelSelector && (
@@ -588,7 +913,7 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
588
913
  </Box>
589
914
  )}
590
915
 
591
- {!confirmState && !showModelSelector && !showProviderManager && (
916
+ {!confirmState && !questionState && !showModelSelector && !showProviderManager && (
592
917
  <Box flexDirection="column" marginTop={1}>
593
918
  <PromptInput
594
919
  key={inputKey}
@@ -602,8 +927,8 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
602
927
  </Box>
603
928
  )}
604
929
 
605
- {isProcessing ? (
606
- <ProgressBar />
930
+ {isProcessing && !confirmState && !questionState ? (
931
+ <ProgressBar startTime={processingStartTime} tokenCount={tokenCount} isThinking={isThinking} />
607
932
  ) : showCmdSuggestions && cmdSuggestions.length > 0 ? (
608
933
  <Box marginTop={1}>
609
934
  <Text color={colors.textMuted}> Tab to complete · ↑↓ navigate</Text>
@@ -9,6 +9,8 @@ interface Command {
9
9
  export const COMMANDS: Command[] = [
10
10
  { name: '/model', description: 'Switch model' },
11
11
  { name: '/provider', description: 'Manage providers' },
12
+ { name: '/permissions', description: 'View permission rules' },
13
+ { name: '/permissions audit', description: 'View permission audit log' },
12
14
  { name: '/sessions', description: 'List sessions' },
13
15
  { name: '/resume', description: 'Resume session' },
14
16
  { name: '/new', description: 'New session' },
@@ -1,6 +1,6 @@
1
1
  import { Box, Text } from 'ink';
2
2
  import { colors } from './theme.js';
3
- import { Logo } from './Logo.js';
3
+ import { BigLogo } from './Logo.js';
4
4
 
5
5
  interface HeaderProps {
6
6
  provider: string;
@@ -8,20 +8,14 @@ interface HeaderProps {
8
8
  cwd: string;
9
9
  }
10
10
 
11
- export function Header({ provider, model, cwd }: HeaderProps) {
12
- const home = process.env.HOME || '';
13
- const cwdDisplay = cwd.startsWith(home) ? '~' + cwd.slice(home.length) : cwd;
14
-
11
+ export function Header({ model, cwd }: HeaderProps) {
15
12
  return (
16
- <Box flexDirection="row" marginBottom={1} marginTop={1}>
17
- <Logo />
18
- <Box flexDirection="column" marginLeft={1}>
19
- <Box>
20
- <Text bold color={colors.text}>gencode </Text>
21
- <Text color={colors.textMuted}>v0.1.0</Text>
22
- </Box>
23
- <Text color={colors.textMuted}>{model} · API Usage Billing</Text>
24
- <Text color={colors.textMuted}>{cwdDisplay}</Text>
13
+ <Box flexDirection="column" marginTop={1}>
14
+ <BigLogo />
15
+ <Box marginTop={1}>
16
+ <Text color={colors.textSecondary}>{model}</Text>
17
+ <Text color={colors.textMuted}> · </Text>
18
+ <Text color={colors.textMuted}>{cwd}</Text>
25
19
  </Box>
26
20
  </Box>
27
21
  );
@@ -29,8 +23,8 @@ export function Header({ provider, model, cwd }: HeaderProps) {
29
23
 
30
24
  export function Welcome() {
31
25
  return (
32
- <Text color={colors.textMuted}>
33
- Type a message or /help. Ctrl+C to exit.
34
- </Text>
26
+ <Box marginTop={1}>
27
+ <Text color={colors.textMuted}>? for help · Ctrl+C to exit</Text>
28
+ </Box>
35
29
  );
36
30
  }