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
@@ -22,13 +22,22 @@ 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';
25
31
  import { colors, icons } from './theme.js';
32
+ import { getTodos } from '../../tools/index.js';
26
33
  import type { ProviderName } from '../../providers/index.js';
34
+ import type { ApprovalAction, ApprovalSuggestion } from '../../permissions/types.js';
35
+ import { gatherContextFiles, buildInitPrompt, getContextSummary } from '../../memory/index.js';
27
36
 
28
37
  // Types
29
38
  interface HistoryItem {
30
39
  id: string;
31
- type: 'header' | 'welcome' | 'user' | 'assistant' | 'tool_call' | 'tool_result' | 'info' | 'completion';
40
+ type: 'header' | 'welcome' | 'user' | 'assistant' | 'tool_call' | 'tool_result' | 'info' | 'completion' | 'todos';
32
41
  content: string;
33
42
  meta?: Record<string, unknown>;
34
43
  }
@@ -36,11 +45,14 @@ interface HistoryItem {
36
45
  interface ConfirmState {
37
46
  tool: string;
38
47
  input: Record<string, unknown>;
39
- resolve: (confirmed: boolean) => void;
48
+ suggestions: ApprovalSuggestion[];
49
+ resolve: (action: ApprovalAction) => void;
40
50
  }
41
51
 
42
52
  interface SettingsManager {
43
53
  save: (settings: { model?: string }) => Promise<void>;
54
+ getCwd?: () => string;
55
+ addPermissionRule?: (pattern: string, type: 'allow' | 'deny', level?: 'global' | 'project' | 'local') => Promise<void>;
44
56
  }
45
57
 
46
58
  interface Session {
@@ -49,10 +61,16 @@ interface Session {
49
61
  updatedAt: string;
50
62
  }
51
63
 
64
+ interface PermissionSettings {
65
+ allow?: string[];
66
+ deny?: string[];
67
+ }
68
+
52
69
  interface AppProps {
53
70
  config: AgentConfig;
54
71
  settingsManager?: SettingsManager;
55
72
  resumeLatest?: boolean;
73
+ permissionSettings?: PermissionSettings;
56
74
  }
57
75
 
58
76
  // ============================================================================
@@ -125,10 +143,64 @@ function SessionsTable({ sessions }: SessionsTableProps) {
125
143
  );
126
144
  }
127
145
 
146
+ // ============================================================================
147
+ // Memory Files Display Component
148
+ // ============================================================================
149
+ interface MemoryFileInfo {
150
+ path: string;
151
+ level: string;
152
+ size: number;
153
+ type: 'file' | 'rule';
154
+ }
155
+
156
+ function MemoryFilesDisplay({ files }: { files: MemoryFileInfo[] }) {
157
+ const formatSize = (bytes: number): string => {
158
+ if (bytes < 1024) return `${bytes}B`;
159
+ return `${(bytes / 1024).toFixed(1)}KB`;
160
+ };
161
+
162
+ const memoryFiles = files.filter((f) => f.type === 'file');
163
+ const ruleFiles = files.filter((f) => f.type === 'rule');
164
+
165
+ return (
166
+ <Box flexDirection="column">
167
+ {memoryFiles.length > 0 && (
168
+ <>
169
+ <Text color={colors.info}>Loaded Memory Files:</Text>
170
+ {memoryFiles.map((f, i) => (
171
+ <Text key={f.path}>
172
+ <Text color={colors.textMuted}> [{i + 1}] </Text>
173
+ <Text color={colors.primary}>{f.path} </Text>
174
+ <Text color={colors.textMuted}>({f.level}, {formatSize(f.size)})</Text>
175
+ </Text>
176
+ ))}
177
+ </>
178
+ )}
179
+ {ruleFiles.length > 0 && (
180
+ <Box flexDirection="column" marginTop={memoryFiles.length > 0 ? 1 : 0}>
181
+ <Text color={colors.info}>
182
+ Loaded Rules:
183
+ </Text>
184
+ {ruleFiles.map((f, i) => (
185
+ <Text key={f.path}>
186
+ <Text color={colors.textMuted}> [{i + 1}] </Text>
187
+ <Text color={colors.warning}>{f.path} </Text>
188
+ <Text color={colors.textMuted}>({f.level}, {formatSize(f.size)})</Text>
189
+ </Text>
190
+ ))}
191
+ </Box>
192
+ )}
193
+ {files.length === 0 && (
194
+ <Text color={colors.textMuted}>No memory files loaded</Text>
195
+ )}
196
+ </Box>
197
+ );
198
+ }
199
+
128
200
  // ============================================================================
129
201
  // Main App
130
202
  // ============================================================================
131
- export function App({ config, settingsManager, resumeLatest }: AppProps) {
203
+ export function App({ config, settingsManager, resumeLatest, permissionSettings }: AppProps) {
132
204
  const { exit } = useApp();
133
205
  const agent = useAgent(config);
134
206
 
@@ -160,6 +232,8 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
160
232
  const [isThinking, setIsThinking] = useState(false);
161
233
  const [streamingText, setStreamingText] = useState('');
162
234
  const streamingTextRef = useRef(''); // Track current streaming text for closure
235
+ const [processingStartTime, setProcessingStartTime] = useState<number | undefined>(undefined);
236
+ const [tokenCount, setTokenCount] = useState(0);
163
237
  const [confirmState, setConfirmState] = useState<ConfirmState | null>(null);
164
238
  const [showModelSelector, setShowModelSelector] = useState(false);
165
239
  const [showProviderManager, setShowProviderManager] = useState(false);
@@ -168,6 +242,7 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
168
242
  const [inputKey, setInputKey] = useState(0); // Force cursor to end after autocomplete
169
243
  const [pendingTool, setPendingTool] = useState<{ name: string; input: Record<string, unknown> } | null>(null);
170
244
  const pendingToolRef = useRef<{ name: string; input: Record<string, unknown> } | null>(null);
245
+ const [todos, setTodos] = useState<ReturnType<typeof getTodos>>([]);
171
246
 
172
247
  // Check if showing command suggestions
173
248
  const showCmdSuggestions = input.startsWith('/') && !isProcessing;
@@ -186,12 +261,30 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
186
261
  // Initialize
187
262
  useEffect(() => {
188
263
  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 });
264
+ // Initialize permission system with settings
265
+ await agent.initializePermissions(permissionSettings);
266
+
267
+ // Set enhanced confirm callback with approval options
268
+ agent.setEnhancedConfirmCallback(async (tool, toolInput, suggestions) => {
269
+ return new Promise<ApprovalAction>((resolve) => {
270
+ setConfirmState({
271
+ tool,
272
+ input: toolInput as Record<string, unknown>,
273
+ suggestions,
274
+ resolve,
275
+ });
192
276
  });
193
277
  });
194
278
 
279
+ // Set callback to save permission rules to settings.local.json
280
+ if (settingsManager?.addPermissionRule) {
281
+ agent.setSaveRuleCallback(async (tool, pattern) => {
282
+ // Format as Claude Code style pattern: Tool(pattern) or just Tool
283
+ const rulePattern = pattern ? `${tool}(${pattern})` : tool;
284
+ await settingsManager.addPermissionRule!(rulePattern, 'allow', 'local');
285
+ });
286
+ }
287
+
195
288
  if (resumeLatest) {
196
289
  const resumed = await agent.resumeLatest();
197
290
  if (resumed) {
@@ -200,12 +293,12 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
200
293
  }
201
294
  };
202
295
  init();
203
- }, [agent, resumeLatest, addHistory]);
296
+ }, [agent, resumeLatest, addHistory, permissionSettings, settingsManager]);
204
297
 
205
- // Handle confirm
206
- const handleConfirm = (confirmed: boolean) => {
298
+ // Handle permission decision
299
+ const handlePermissionDecision = (action: ApprovalAction) => {
207
300
  if (confirmState) {
208
- confirmState.resolve(confirmed);
301
+ confirmState.resolve(action);
209
302
  setConfirmState(null);
210
303
  }
211
304
  };
@@ -321,13 +414,113 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
321
414
  return true;
322
415
  }
323
416
 
417
+ case 'permissions': {
418
+ const permManager = agent.getPermissionManager();
419
+
420
+ if (arg === 'audit') {
421
+ // Show audit log
422
+ const auditLog = permManager.getAuditLog(20);
423
+ if (auditLog.length === 0) {
424
+ addHistory({ type: 'info', content: 'No permission decisions recorded yet' });
425
+ } else {
426
+ const entries = auditLog.map((e) => ({
427
+ time: e.timestamp.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }),
428
+ tool: e.tool,
429
+ input: e.inputSummary,
430
+ decision: e.decision,
431
+ rule: e.matchedRule,
432
+ }));
433
+ addHistory({
434
+ type: 'info',
435
+ content: '__PERMISSION_AUDIT__',
436
+ meta: { entries },
437
+ });
438
+ }
439
+ } else if (arg === 'stats') {
440
+ // Show statistics
441
+ const stats = permManager.getAuditStats();
442
+ addHistory({
443
+ type: 'info',
444
+ content: `Permissions: ${stats.allowed + stats.confirmed} allowed, ${stats.denied + stats.rejected} denied`,
445
+ });
446
+ } else {
447
+ // Show rules
448
+ const rules = permManager.getRules();
449
+ const prompts = permManager.getAllowedPrompts();
450
+ const displayRules = rules.map((r) => ({
451
+ type: r.scope === 'session' ? 'Session' : r.description?.startsWith('Settings') ? 'Settings' : 'Built-in',
452
+ tool: typeof r.tool === 'string' ? r.tool : r.tool.toString(),
453
+ pattern: typeof r.pattern === 'string' ? r.pattern : r.pattern?.toString(),
454
+ scope: r.scope ?? 'session',
455
+ mode: r.mode,
456
+ }));
457
+ addHistory({
458
+ type: 'info',
459
+ content: '__PERMISSIONS__',
460
+ meta: { rules: displayRules, prompts },
461
+ });
462
+ }
463
+ return true;
464
+ }
465
+
324
466
  case 'init': {
325
- addHistory({ type: 'info', content: '/init command not available in this version' });
467
+ // Gather context files and generate AGENT.md
468
+ addHistory({ type: 'info', content: 'Analyzing codebase...' });
469
+
470
+ const contextFiles = await gatherContextFiles(cwd);
471
+ addHistory({ type: 'info', content: getContextSummary(contextFiles) });
472
+
473
+ // Check if AGENT.md already exists
474
+ const memoryManager = agent.getMemoryManager();
475
+ const existingPath = await memoryManager.getExistingProjectMemoryPath(cwd);
476
+ let existingContent: string | undefined;
477
+
478
+ if (existingPath) {
479
+ try {
480
+ const fs = await import('fs/promises');
481
+ existingContent = await fs.readFile(existingPath, 'utf-8');
482
+ addHistory({
483
+ type: 'info',
484
+ content: `Found existing: ${existingPath.replace(cwd, '.')}`,
485
+ });
486
+ } catch {
487
+ // File doesn't exist or can't be read
488
+ }
489
+ }
490
+
491
+ // Build init prompt and run through agent
492
+ const initPrompt = buildInitPrompt(contextFiles, existingContent);
493
+ addHistory({ type: 'info', content: 'Generating AGENT.md...' });
494
+ addHistory({ type: 'user', content: '/init' });
495
+ await runAgent(initPrompt);
326
496
  return true;
327
497
  }
328
498
 
329
499
  case 'memory': {
330
- addHistory({ type: 'info', content: '/memory command not available in this version' });
500
+ // Show loaded memory files
501
+ const memoryManager = agent.getMemoryManager();
502
+ const loadedFiles = memoryManager.getLoadedFileList();
503
+
504
+ if (loadedFiles.length === 0) {
505
+ // Try to load memory first
506
+ await agent.loadMemory();
507
+ const filesAfterLoad = memoryManager.getLoadedFileList();
508
+ if (filesAfterLoad.length === 0) {
509
+ addHistory({ type: 'info', content: 'No memory files found' });
510
+ } else {
511
+ addHistory({
512
+ type: 'info',
513
+ content: '__MEMORY__',
514
+ meta: { files: filesAfterLoad },
515
+ });
516
+ }
517
+ } else {
518
+ addHistory({
519
+ type: 'info',
520
+ content: '__MEMORY__',
521
+ meta: { files: loadedFiles },
522
+ });
523
+ }
331
524
  return true;
332
525
  }
333
526
 
@@ -347,6 +540,8 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
347
540
  streamingTextRef.current = '';
348
541
  interruptFlagRef.current = false;
349
542
  const startTime = Date.now();
543
+ setProcessingStartTime(startTime);
544
+ setTokenCount(0);
350
545
 
351
546
  try {
352
547
  for await (const event of agent.run(prompt)) {
@@ -360,6 +555,8 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
360
555
  setIsThinking(false);
361
556
  streamingTextRef.current += event.text;
362
557
  setStreamingText(streamingTextRef.current);
558
+ // Estimate token count (roughly 4 chars per token)
559
+ setTokenCount((prev) => prev + Math.max(1, Math.ceil(event.text.length / 4)));
363
560
  break;
364
561
 
365
562
  case 'tool_start':
@@ -376,24 +573,37 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
376
573
  break;
377
574
 
378
575
  case 'tool_result':
379
- // Add tool_call to history (now completed) - use ref for correct value
380
- if (pendingToolRef.current) {
576
+ // For TodoWrite: add todos first, then hide tool_call/tool_result
577
+ if (event.name === 'TodoWrite') {
578
+ const currentTodos = getTodos();
579
+ setTodos(currentTodos);
580
+ if (currentTodos.length > 0) {
581
+ addHistory({
582
+ type: 'todos',
583
+ content: '',
584
+ meta: { todos: currentTodos },
585
+ });
586
+ }
587
+ } else {
588
+ // Add tool_call to history (now completed) - use ref for correct value
589
+ if (pendingToolRef.current) {
590
+ addHistory({
591
+ type: 'tool_call',
592
+ content: pendingToolRef.current.name,
593
+ meta: { toolName: pendingToolRef.current.name, input: pendingToolRef.current.input },
594
+ });
595
+ }
596
+ // Add tool_result to history
381
597
  addHistory({
382
- type: 'tool_call',
383
- content: pendingToolRef.current.name,
384
- meta: { toolName: pendingToolRef.current.name, input: pendingToolRef.current.input },
598
+ type: 'tool_result',
599
+ content: event.result.output,
600
+ meta: {
601
+ toolName: event.name,
602
+ success: event.result.success,
603
+ metadata: event.result.metadata,
604
+ },
385
605
  });
386
606
  }
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
607
  pendingToolRef.current = null;
398
608
  setPendingTool(null);
399
609
  setIsThinking(true);
@@ -413,6 +623,7 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
413
623
  // Add completion message with duration
414
624
  const durationMs = Date.now() - startTime;
415
625
  addHistory({ type: 'completion', content: '', meta: { durationMs } });
626
+ setProcessingStartTime(undefined);
416
627
  break;
417
628
  }
418
629
  }
@@ -454,6 +665,47 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
454
665
  return;
455
666
  }
456
667
 
668
+ // Handle # prefix for quick memory adds
669
+ // ## note -> user memory (~/.gencode/AGENT.md)
670
+ // # note -> project memory (./AGENT.md)
671
+ if (trimmed.startsWith('#') && !trimmed.startsWith('#!/')) {
672
+ const memoryManager = agent.getMemoryManager();
673
+ let level: 'user' | 'project';
674
+ let content: string;
675
+
676
+ if (trimmed.startsWith('## ')) {
677
+ level = 'user';
678
+ content = trimmed.slice(3).trim();
679
+ } else if (trimmed.startsWith('# ')) {
680
+ level = 'project';
681
+ content = trimmed.slice(2).trim();
682
+ } else {
683
+ // Just # with no space, treat as project
684
+ level = 'project';
685
+ content = trimmed.slice(1).trim();
686
+ }
687
+
688
+ if (!content) {
689
+ addHistory({ type: 'info', content: 'Empty memory entry ignored' });
690
+ return;
691
+ }
692
+
693
+ try {
694
+ const savedPath = await memoryManager.quickAdd(content, level, cwd);
695
+ const displayPath = savedPath.replace(process.env.HOME || '', '~');
696
+ addHistory({
697
+ type: 'info',
698
+ content: `Added to ${level} memory: ${displayPath}`,
699
+ });
700
+ } catch (error) {
701
+ addHistory({
702
+ type: 'info',
703
+ content: `Failed to add to memory: ${error instanceof Error ? error.message : String(error)}`,
704
+ });
705
+ }
706
+ return;
707
+ }
708
+
457
709
  if (trimmed.startsWith('/')) {
458
710
  const handled = await handleCommand(trimmed);
459
711
  if (!handled) {
@@ -537,9 +789,29 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
537
789
  if (item.content === '__SESSIONS__' && item.meta?.input) {
538
790
  return <SessionsTable sessions={item.meta.input as Session[]} />;
539
791
  }
792
+ if (item.content === '__PERMISSIONS__' && item.meta?.rules) {
793
+ return (
794
+ <PermissionRulesDisplay
795
+ rules={item.meta.rules as { type: string; tool: string; pattern?: string; scope: string; mode: string }[]}
796
+ allowedPrompts={item.meta.prompts as { tool: string; prompt: string }[] | undefined}
797
+ />
798
+ );
799
+ }
800
+ if (item.content === '__PERMISSION_AUDIT__' && item.meta?.entries) {
801
+ return (
802
+ <PermissionAuditDisplay
803
+ entries={item.meta.entries as { time: string; tool: string; input: string; decision: string; rule?: string }[]}
804
+ />
805
+ );
806
+ }
807
+ if (item.content === '__MEMORY__' && item.meta?.files) {
808
+ return <MemoryFilesDisplay files={item.meta.files as MemoryFileInfo[]} />;
809
+ }
540
810
  return <InfoMessage text={item.content} />;
541
811
  case 'completion':
542
812
  return <CompletionMessage durationMs={(item.meta?.durationMs as number) || 0} />;
813
+ case 'todos':
814
+ return <TodoList todos={item.meta?.todos as ReturnType<typeof getTodos>} />;
543
815
  default:
544
816
  return null;
545
817
  }
@@ -551,17 +823,18 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
551
823
  {(item) => <Box key={item.id}>{renderHistoryItem(item)}</Box>}
552
824
  </Static>
553
825
 
554
- {pendingTool && <PendingToolCall name={pendingTool.name} input={pendingTool.input} />}
826
+ {pendingTool && !confirmState && <PendingToolCall name={pendingTool.name} input={pendingTool.input} />}
555
827
 
556
828
  {streamingText && <AssistantMessage text={streamingText} streaming />}
557
829
 
558
830
  {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>
831
+ <PermissionPrompt
832
+ tool={confirmState.tool}
833
+ input={confirmState.input}
834
+ suggestions={confirmState.suggestions}
835
+ onDecision={handlePermissionDecision}
836
+ projectPath={settingsManager?.getCwd?.() ?? process.cwd()}
837
+ />
565
838
  )}
566
839
 
567
840
  {showModelSelector && (
@@ -602,8 +875,8 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
602
875
  </Box>
603
876
  )}
604
877
 
605
- {isProcessing ? (
606
- <ProgressBar />
878
+ {isProcessing && !confirmState ? (
879
+ <ProgressBar startTime={processingStartTime} tokenCount={tokenCount} isThinking={isThinking} />
607
880
  ) : showCmdSuggestions && cmdSuggestions.length > 0 ? (
608
881
  <Box marginTop={1}>
609
882
  <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
  }
@@ -1,16 +1,83 @@
1
1
  import { Box, Text } from 'ink';
2
+ import { colors } from './theme.js';
2
3
 
4
+ // Small G logo for inline use
3
5
  export function Logo() {
4
- // Full G with 3D shadow - subdued slate color
5
- const slateColor = "#64748B"; // Slate 500 - stable, professional
6
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>
7
+ <Box marginRight={1}>
8
+ <Text bold color={colors.brand}>◆</Text>
9
+ </Box>
10
+ );
11
+ }
12
+
13
+ // Large ASCII art logo with elegant gradient
14
+ export function BigLogo() {
15
+ // Indigo gradient - brand colors
16
+ const c1 = '#818CF8'; // Indigo 400
17
+ const c2 = '#818CF8'; // Indigo 400
18
+ const c3 = '#A5B4FC'; // Indigo 300
19
+ const c4 = '#A5B4FC'; // Indigo 300
20
+ const c5 = '#C7D2FE'; // Indigo 200
21
+ const c6 = '#C7D2FE'; // Indigo 200
22
+ const c7 = '#C7D2FE'; // Indigo 200
23
+
24
+ // G E N C O D E
25
+ return (
26
+ <Box flexDirection="column">
27
+ <Text>
28
+ <Text color={c1}> ██████╗ </Text>
29
+ <Text color={c2}>███████╗</Text>
30
+ <Text color={c3}>███╗ ██╗</Text>
31
+ <Text color={c4}> ██████╗</Text>
32
+ <Text color={c5}> ██████╗ </Text>
33
+ <Text color={c6}>██████╗ </Text>
34
+ <Text color={c7}>███████╗</Text>
35
+ </Text>
36
+ <Text>
37
+ <Text color={c1}>██╔════╝ </Text>
38
+ <Text color={c2}>██╔════╝</Text>
39
+ <Text color={c3}>████╗ ██║</Text>
40
+ <Text color={c4}>██╔════╝</Text>
41
+ <Text color={c5}>██╔═══██╗</Text>
42
+ <Text color={c6}>██╔══██╗</Text>
43
+ <Text color={c7}>██╔════╝</Text>
44
+ </Text>
45
+ <Text>
46
+ <Text color={c1}>██║ ███╗</Text>
47
+ <Text color={c2}>█████╗ </Text>
48
+ <Text color={c3}>██╔██╗ ██║</Text>
49
+ <Text color={c4}>██║ </Text>
50
+ <Text color={c5}>██║ ██║</Text>
51
+ <Text color={c6}>██║ ██║</Text>
52
+ <Text color={c7}>█████╗ </Text>
53
+ </Text>
54
+ <Text>
55
+ <Text color={c1}>██║ ██║</Text>
56
+ <Text color={c2}>██╔══╝ </Text>
57
+ <Text color={c3}>██║╚██╗██║</Text>
58
+ <Text color={c4}>██║ </Text>
59
+ <Text color={c5}>██║ ██║</Text>
60
+ <Text color={c6}>██║ ██║</Text>
61
+ <Text color={c7}>██╔══╝ </Text>
62
+ </Text>
63
+ <Text>
64
+ <Text color={c1}>╚██████╔╝</Text>
65
+ <Text color={c2}>███████╗</Text>
66
+ <Text color={c3}>██║ ╚████║</Text>
67
+ <Text color={c4}>╚██████╗</Text>
68
+ <Text color={c5}>╚██████╔╝</Text>
69
+ <Text color={c6}>██████╔╝</Text>
70
+ <Text color={c7}>███████╗</Text>
71
+ </Text>
72
+ <Text>
73
+ <Text color={c1}> ╚═════╝ </Text>
74
+ <Text color={c2}>╚══════╝</Text>
75
+ <Text color={c3}>╚═╝ ╚═══╝</Text>
76
+ <Text color={c4}> ╚═════╝</Text>
77
+ <Text color={c5}> ╚═════╝ </Text>
78
+ <Text color={c6}>╚═════╝ </Text>
79
+ <Text color={c7}>╚══════╝</Text>
80
+ </Text>
14
81
  </Box>
15
82
  );
16
83
  }