project-compass 4.3.6 → 4.3.8

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 (41) hide show
  1. package/AGENTS.md +1121 -22
  2. package/ARCHITECTURE.md +826 -10
  3. package/CONTRIBUTING.md +974 -10
  4. package/PROJECT_CONTEXT.md +404 -0
  5. package/README.md +637 -67
  6. package/assets/screenshots/ai_mode.png +0 -0
  7. package/assets/screenshots/art_bar.png +0 -0
  8. package/assets/screenshots/help_structure.png +0 -0
  9. package/assets/screenshots/home.png +0 -0
  10. package/assets/screenshots/languages_checker_omni_studio.png +0 -0
  11. package/assets/screenshots/task_manager.png +0 -0
  12. package/commands.md +841 -104
  13. package/package.json +5 -4
  14. package/src/cli.js +310 -169
  15. package/src/components/AIHorizon.js +138 -255
  16. package/src/components/Footer.js +8 -64
  17. package/src/components/Header.js +8 -47
  18. package/src/components/Navigator.js +16 -70
  19. package/src/components/PackageRegistry.js +4 -3
  20. package/src/components/ProjectArchitect.js +6 -1
  21. package/src/components/TaskManager.js +12 -70
  22. package/src/detectors/dotnet.js +3 -2
  23. package/src/detectors/frameworks.js +28 -28
  24. package/src/detectors/go.js +6 -6
  25. package/src/detectors/java.js +2 -1
  26. package/src/detectors/node.js +3 -2
  27. package/src/detectors/php.js +1 -1
  28. package/src/detectors/python.js +11 -4
  29. package/src/detectors/ruby.js +1 -1
  30. package/src/detectors/rust.js +2 -2
  31. package/src/detectors/utils.js +6 -7
  32. package/src/projectDetection.js +41 -5
  33. package/assets/screenshots/architect.jpg +0 -0
  34. package/assets/screenshots/artboard.jpg +0 -0
  35. package/assets/screenshots/exit-confirm.jpg +0 -0
  36. package/assets/screenshots/navigator.jpg +0 -0
  37. package/assets/screenshots/overlays.jpg +0 -0
  38. package/assets/screenshots/registry.jpg +0 -0
  39. package/assets/screenshots/studio.jpg +0 -0
  40. package/assets/screenshots/taskmanager.jpg +0 -0
  41. package/src/store/useProjectStore.js +0 -32
@@ -1,4 +1,4 @@
1
- /* global fetch, setTimeout */
1
+ /* global fetch */
2
2
  import React, {useState, memo} from 'react';
3
3
  import {Box, Text, useInput} from 'ink';
4
4
  import fs from 'fs';
@@ -13,108 +13,89 @@ const AI_PROVIDERS = [
13
13
  { id: 'ollama', name: 'Ollama (Local)', endpoint: 'http://localhost:11434/api/generate' }
14
14
  ];
15
15
 
16
- const AIHorizon = memo(({selectedProject, CursorText, config, setConfig, saveConfig}) => {
16
+ function readProjectFile(projectPath, filename) {
17
+ const fullPath = path.join(projectPath, filename);
18
+ try {
19
+ if (fs.existsSync(fullPath)) {
20
+ return fs.readFileSync(fullPath, 'utf-8').slice(0, 3000);
21
+ }
22
+ } catch {
23
+ // file may not be readable
24
+ }
25
+ return null;
26
+ }
27
+
28
+ function buildProjectContext(project, rootPath) {
29
+ const lines = [];
30
+ lines.push(`Project: ${project.name}`);
31
+ lines.push(`Type: ${project.type}`);
32
+ lines.push(`Location: ${path.relative(rootPath, project.path) || '.'}`);
33
+ lines.push(`Manifest: ${project.manifest}`);
34
+ if (project.description) lines.push(`Description: ${project.description}`);
35
+ if (project.frameworks?.length) {
36
+ lines.push(`Frameworks: ${project.frameworks.map(f => f.name).join(', ')}`);
37
+ }
38
+ const readme = readProjectFile(project.path, 'README.md') || readProjectFile(project.path, 'Readme.md') || readProjectFile(project.path, 'readme.md');
39
+ if (readme) {
40
+ lines.push(`--- README ---\n${readme.slice(0, 2000)}\n--- END README ---`);
41
+ } else {
42
+ const mainCandidates = ['src/main.py', 'main.py', 'app.py', 'src/index.js', 'index.js', 'src/main.ts', 'main.ts', 'server.js', 'app.js'];
43
+ for (const candidate of mainCandidates) {
44
+ const content = readProjectFile(project.path, candidate);
45
+ if (content) {
46
+ lines.push(`--- ${candidate} ---\n${content.slice(0, 1500)}\n--- END ${candidate} ---`);
47
+ break;
48
+ }
49
+ }
50
+ }
51
+ if (project.metadata?.scripts) {
52
+ lines.push('Scripts: ' + JSON.stringify(project.metadata.scripts));
53
+ }
54
+ if (project.metadata?.dependencies?.length) {
55
+ const depNames = project.metadata.dependencies.slice(0, 30).map(d => typeof d === 'string' ? d : d.name);
56
+ lines.push('Dependencies: ' + depNames.join(', '));
57
+ }
58
+ return lines.join('\n');
59
+ }
60
+
61
+ const AIHorizon = memo(({rootPath, selectedProject, CursorText, config, setConfig, saveConfig}) => {
17
62
  const [step, setStep] = useState(config?.aiToken ? 'analyze' : 'provider');
18
- const [providerIdx, setProviderIdx] = useState(AI_PROVIDERS.findIndex(p => p.id === (config?.aiProvider || 'openrouter')) || 0);
63
+ const providerIndex = AI_PROVIDERS.findIndex(p => p.id === (config?.aiProvider || 'openrouter'));
64
+ const [providerIdx, setProviderIdx] = useState(providerIndex >= 0 ? providerIndex : 0);
19
65
  const [model, setModel] = useState(config?.aiModel || 'deepseek/deepseek-r1');
20
66
  const [token, setToken] = useState(config?.aiToken || '');
21
67
  const [cursor, setCursor] = useState(0);
22
68
  const [status, setStatus] = useState('ready');
23
69
  const [error, setError] = useState(null);
24
70
  const [suggestions, setSuggestions] = useState([]);
25
- const [editingIdx, setEditingIdx] = useState(-1);
26
- const [editInput, setEditInput] = useState('');
27
- const [editCursor, setEditCursor] = useState(0);
28
- const [customContext, setCustomContext] = useState('');
29
-
30
-
31
- const readProjectFile = (filePath) => {
32
- try {
33
- const fullPath = selectedProject?.path ? path.join(selectedProject.path, filePath) : filePath;
34
- if (fs.existsSync(fullPath)) {
35
- const content = fs.readFileSync(fullPath, 'utf-8');
36
- return content.slice(0, 2000); // First 2000 chars
37
- }
38
- } catch { /* ignore */ }
39
- return '';
40
- };
41
-
42
- const buildProjectContext = () => {
43
- if (!selectedProject) return null;
44
- const pm = selectedProject.metadata?.packageManager || 'npm';
45
- const frameworks = (selectedProject.frameworks || []).map(f => f.name).join(', ');
46
- const scripts = selectedProject.metadata?.scripts || {};
47
- const deps = (selectedProject.metadata?.dependencies || []).slice(0, 30);
48
-
49
- // Read actual project files
50
- const readme = readProjectFile('README.md') || readProjectFile('readme.md') || '';
51
- const mainFile = readProjectFile('main.py') || readProjectFile('app.py') ||
52
- readProjectFile('src/main.py') || readProjectFile('index.js') || '';
53
- const configFile = readProjectFile('pyproject.toml') || readProjectFile('package.json') || '';
54
-
55
- return {
56
- name: selectedProject.name,
57
- type: selectedProject.type,
58
- path: selectedProject.path,
59
- manifest: selectedProject.manifest,
60
- packageManager: pm,
61
- frameworks: frameworks || 'None detected',
62
- scripts: scripts,
63
- scriptNames: Object.keys(scripts).join(', ') || 'None',
64
- dependencies: deps,
65
- description: selectedProject.description || '',
66
- hasPort: !!selectedProject.metadata?.port,
67
- readme: readme.slice(0, 1500),
68
- mainFile: mainFile.slice(0, 1500),
69
- configFile: configFile.slice(0, 1500),
70
- customContext
71
- };
72
- };
71
+ const [rawAIResponse, setRawAIResponse] = useState('');
73
72
 
74
73
  const runRealAnalysis = async () => {
75
74
  setStatus('busy');
76
75
  setError(null);
76
+ setRawAIResponse('');
77
77
  const provider = AI_PROVIDERS[providerIdx];
78
- const context = buildProjectContext();
79
-
80
- if (!context) {
81
- setError('No project selected for analysis');
82
- setStatus('ready');
83
- return;
84
- }
85
-
78
+
86
79
  try {
87
- const prompt = `You are analyzing a ${context.type} project named "${context.name}".
88
- Project Details:
89
- - Package Manager: ${context.packageManager}
90
- - Frameworks: ${context.frameworks}
91
- - Available Scripts: ${context.scriptNames}
92
- - Key Dependencies: ${JSON.stringify(context.dependencies)}
93
- - Description: ${context.description}
80
+ const projectContext = buildProjectContext(selectedProject, rootPath);
94
81
 
95
- Based on this REAL project data, suggest the correct CLI commands for:
96
- 1. BUILD (compile/build the project)
97
- 2. RUN (start/run the project in development)
98
- 3. INSTALL (install dependencies)
99
- 4. TEST (run tests)
82
+ const prompt = `You are analyzing a software project. Based on the project data below, suggest valid CLI commands for:
83
+ 1. Build
84
+ 2. Run (with port flag if applicable)
85
+ 3. Install dependencies
86
+ 4. Test
100
87
 
101
- CRITICAL RULES:
102
- - Use the ACTUAL package manager: ${context.packageManager}
103
- - Use ACTUAL script names from: ${context.scriptNames}
104
- - For Python: use "uv run" if uv is available, else "python" or "pip"
105
- - For Node: use "npm run", "yarn", "pnpm" or "bun" based on ${context.packageManager}
106
- - For Rust: use "cargo" commands
107
- - For Go: use "go" commands
108
- - DO NOT suggest generic commands - use the project's actual tools
88
+ Project Data:
89
+ ${projectContext}
109
90
 
110
- Return ONLY valid JSON (no markdown, no code blocks):
111
- {"build": "exact command here", "run": "exact command here", "install": "exact command here", "test": "exact command here"}
91
+ Return ONLY a JSON object with this structure:
92
+ {"build": "build command here", "run": "run command here", "install": "install command here", "test": "test command here"}
112
93
 
113
- Example for Node.js with npm: {"build": "npm run build", "run": "npm run dev", "install": "npm install", "test": "npm run test"}`;
94
+ Use the project's detected type (${selectedProject.type}) to ensure commands are correct. Do NOT wrap the JSON in markdown code blocks.`;
114
95
 
115
96
  let response;
116
97
  let aiText = '';
117
-
98
+
118
99
  if (provider.id === 'openrouter') {
119
100
  response = await fetch(provider.endpoint, {
120
101
  method: 'POST',
@@ -130,8 +111,8 @@ Example for Node.js with npm: {"build": "npm run build", "run": "npm run dev", "
130
111
  })
131
112
  });
132
113
  const data = await response.json();
133
- if (!response.ok) throw new Error(data.error?.message || 'OpenRouter Error');
134
- aiText = data.choices[0].message.content;
114
+ if (!response.ok) throw new Error(data.error?.message || data.error || 'OpenRouter Error');
115
+ aiText = data.choices?.[0]?.message?.content || '';
135
116
  } else if (provider.id === 'gemini') {
136
117
  const url = provider.endpoint.replace('{model}', model) + `?key=${token}`;
137
118
  response = await fetch(url, {
@@ -140,8 +121,8 @@ Example for Node.js with npm: {"build": "npm run build", "run": "npm run dev", "
140
121
  body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] })
141
122
  });
142
123
  const data = await response.json();
143
- if (!response.ok) throw new Error(data.error?.message || 'Gemini Error');
144
- aiText = data.candidates[0].content.parts[0].text;
124
+ if (!response.ok) throw new Error(data.error?.message || data.error || 'Gemini Error');
125
+ aiText = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
145
126
  } else if (provider.id === 'claude') {
146
127
  response = await fetch(provider.endpoint, {
147
128
  method: 'POST',
@@ -157,8 +138,8 @@ Example for Node.js with npm: {"build": "npm run build", "run": "npm run dev", "
157
138
  })
158
139
  });
159
140
  const data = await response.json();
160
- if (!response.ok) throw new Error(data.error?.message || 'Claude Error');
161
- aiText = data.content[0].text;
141
+ if (!response.ok) throw new Error(data.error?.message || data.error || 'Claude Error');
142
+ aiText = data.content?.[0]?.text || '';
162
143
  } else if (provider.id === 'ollama') {
163
144
  response = await fetch(provider.endpoint, {
164
145
  method: 'POST',
@@ -166,58 +147,47 @@ Example for Node.js with npm: {"build": "npm run build", "run": "npm run dev", "
166
147
  body: JSON.stringify({ model: model, prompt: prompt, stream: false })
167
148
  });
168
149
  const data = await response.json();
169
- if (!response.ok) throw new Error(data.error || 'Ollama Error');
170
- aiText = data.response;
150
+ if (!response.ok) throw new Error(data.error || data.message || 'Ollama Error');
151
+ aiText = data.response || '';
171
152
  }
172
-
173
- const jsonMatch = aiText.match(/{[\s\S]*?}/);
174
- if (!jsonMatch) throw new Error("AI returned invalid JSON format.");
175
-
153
+
154
+ if (!aiText) throw new Error('Empty response from AI provider');
155
+
156
+ setRawAIResponse(aiText);
157
+
158
+ let jsonStr = aiText;
159
+ const codeBlockMatch = aiText.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
160
+ if (codeBlockMatch) {
161
+ jsonStr = codeBlockMatch[1];
162
+ }
163
+ const jsonMatch = jsonStr.match(/\{[\s\S]*?\}/);
164
+ if (!jsonMatch) throw new Error("AI returned invalid JSON format. Raw response:\n" + aiText.slice(0, 500));
165
+
176
166
  const parsed = JSON.parse(jsonMatch[0]);
177
-
178
- // Validate commands are arrays
179
167
  const mapped = [
180
- { key: 'build', label: 'Build', command: parsed.build?.split(' ').filter(Boolean) || [] },
181
- { key: 'run', label: 'Run', command: parsed.run?.split(' ').filter(Boolean) || [] },
182
- { key: 'install', label: 'Install', command: parsed.install?.split(' ').filter(Boolean) || [] },
183
- { key: 'test', label: 'Test', command: parsed.test?.split(' ').filter(Boolean) || [] }
184
- ].filter(cmd => cmd.command.length > 0);
185
-
168
+ { label: 'AI Build', command: String(parsed.build || '').trim().split(/\s+/) },
169
+ { label: 'AI Run', command: String(parsed.run || '').trim().split(/\s+/) },
170
+ { label: 'AI Install', command: String(parsed.install || '').trim().split(/\s+/) },
171
+ { label: 'AI Test', command: String(parsed.test || '').trim().split(/\s+/) }
172
+ ].filter(cmd => cmd.command.length > 0 && cmd.command[0] !== '');
173
+
174
+ if (mapped.length === 0) throw new Error("AI returned empty commands. Raw response:\n" + aiText.slice(0, 500));
175
+
186
176
  setSuggestions(mapped);
187
- setStatus('review'); // NOW go to review step instead of auto-saving
177
+ const projectKey = selectedProject.path;
178
+ const currentCustom = config.customCommands?.[projectKey] || [];
179
+ const nextConfig = {
180
+ ...config,
181
+ customCommands: { ...config.customCommands, [projectKey]: [...currentCustom, ...mapped] }
182
+ };
183
+ setConfig(nextConfig); saveConfig(nextConfig);
184
+ setStatus('done');
188
185
  } catch (err) {
189
186
  setError(err.message);
190
187
  setStatus('ready');
191
188
  }
192
189
  };
193
-
194
- const saveSuggestions = () => {
195
- if (!selectedProject || suggestions.length === 0) return;
196
-
197
- const projectKey = selectedProject.path;
198
- const currentCustom = config.customCommands?.[projectKey] || [];
199
-
200
- // Convert suggestions to proper format and add to custom commands
201
- const newCommands = suggestions.map(cmd => ({
202
- label: `AI ${cmd.label}`,
203
- command: cmd.command,
204
- source: 'ai'
205
- }));
206
-
207
- const nextConfig = {
208
- ...config,
209
- customCommands: {
210
- ...config.customCommands,
211
- [projectKey]: [...currentCustom, ...newCommands]
212
- }
213
- };
214
- setConfig(nextConfig);
215
- saveConfig(nextConfig);
216
- setStatus('saved');
217
-
218
- setTimeout(() => setStatus('ready'), 2000);
219
- };
220
-
190
+
221
191
  useInput((input, key) => {
222
192
  if (step === 'provider') {
223
193
  if (key.upArrow) setProviderIdx(p => (p - 1 + AI_PROVIDERS.length) % AI_PROVIDERS.length);
@@ -255,93 +225,25 @@ Example for Node.js with npm: {"build": "npm run build", "run": "npm run dev", "
255
225
  if (key.return && status === 'ready' && selectedProject) {
256
226
  runRealAnalysis();
257
227
  }
258
- if (key.return && status === 'review') {
259
- // Toggle edit mode for a suggestion
260
- if (editingIdx >= 0) {
261
- const updated = [...suggestions];
262
- updated[editingIdx] = { ...updated[editingIdx], command: editInput.split(' ').filter(Boolean) };
263
- setSuggestions(updated);
264
- setEditingIdx(-1);
265
- }
266
- }
267
- if (input === 's' && status === 'review') {
268
- saveSuggestions();
269
- }
270
- if (input === 'e' && status === 'review' && suggestions.length > 0) {
271
- setEditingIdx(0);
272
- setEditInput(suggestions[0].command.join(' '));
273
- setEditCursor(suggestions[0].command.join(' ').length);
274
- }
275
- if (key.upArrow && status === 'review' && editingIdx < 0) {
276
- setEditingIdx(p => Math.max(0, p - 1));
277
- if (suggestions[Math.max(0, editingIdx - 1)]) {
278
- setEditInput(suggestions[Math.max(0, editingIdx - 1)].command.join(' '));
279
- setEditCursor(suggestions[Math.max(0, editingIdx - 1)].command.join(' ').length);
280
- }
281
- }
282
- if (key.downArrow && status === 'review' && editingIdx < 0) {
283
- setEditingIdx(p => Math.min(suggestions.length - 1, p + 1));
284
- if (suggestions[Math.min(suggestions.length - 1, editingIdx + 1)]) {
285
- setEditInput(suggestions[Math.min(suggestions.length - 1, editingIdx + 1)].command.join(' '));
286
- setEditCursor(suggestions[Math.min(suggestions.length - 1, editingIdx + 1)].command.join(' ').length);
287
- }
288
- }
289
- if (key.escape) {
290
- if (editingIdx >= 0) {
291
- setEditingIdx(-1);
292
- } else if (status === 'review') {
293
- setStatus('ready');
294
- setSuggestions([]);
295
- }
296
- }
297
228
  if (input === 'r') {
298
229
  const nextConfig = { ...config, aiToken: '' };
299
230
  setConfig(nextConfig); saveConfig(nextConfig);
300
231
  setStep('provider');
301
232
  }
302
233
  }
303
-
304
- // Handle editing input
305
- if (editingIdx >= 0) {
306
- if (key.return) {
307
- const updated = [...suggestions];
308
- updated[editingIdx] = { ...updated[editingIdx], command: editInput.split(' ').filter(Boolean) };
309
- setSuggestions(updated);
310
- setEditingIdx(-1);
311
- }
312
- if (key.escape) {
313
- setEditingIdx(-1);
314
- }
315
- if (key.backspace || key.delete) {
316
- if (editCursor > 0) {
317
- setEditInput(prev => prev.slice(0, editCursor - 1) + prev.slice(editCursor));
318
- setEditCursor(c => Math.max(0, c - 1));
319
- }
320
- } else if (key.leftArrow) {
321
- setEditCursor(c => Math.max(0, c - 1));
322
- } else if (key.rightArrow) {
323
- setEditCursor(c => Math.min(editInput.length, c + 1));
324
- } else if (input && !key.ctrl && !key.meta) {
325
- setEditInput(prev => prev.slice(0, editCursor) + input + prev.slice(editCursor));
326
- setEditCursor(c => c + input.length);
327
- }
328
- }
329
234
  });
330
235
 
331
- const context = selectedProject ? buildProjectContext() : null;
332
-
333
236
  return create(
334
237
  Box,
335
238
  {flexDirection: 'column', borderStyle: 'double', borderColor: 'magenta', padding: 1, width: '100%'},
336
- create(Text, {bold: true, color: 'magenta'}, '🤖 AI Horizon | Project Intelligence'),
337
- create(Text, {dimColor: true}, 'Real AI analysis with project context\n'),
239
+ create(Text, {bold: true, color: 'magenta'}, '🤖 AI Horizon | Integrated Project Intelligence'),
338
240
 
339
241
  step === 'provider' && create(
340
242
  Box,
341
243
  {flexDirection: 'column'},
342
- create(Text, {bold: true, marginBottom: 1}, 'Step 1: Select AI Provider'),
244
+ create(Text, {bold: true, marginBottom: 1}, 'Step 1: Select AI Infrastructure'),
343
245
  ...AI_PROVIDERS.map((p, i) => create(Text, {key: p.id, color: i === providerIdx ? 'cyan' : 'white'}, (i === providerIdx ? '→ ' : ' ') + p.name)),
344
- create(Text, {dimColor: true, marginTop: 1}, '↑/↓: Navigate, Enter: Select')
246
+ create(Text, {dimColor: true, marginTop: 1}, 'Enter: Save & Next')
345
247
  ),
346
248
 
347
249
  step === 'model' && create(
@@ -349,76 +251,57 @@ Example for Node.js with npm: {"build": "npm run build", "run": "npm run dev", "
349
251
  {flexDirection: 'column'},
350
252
  create(Text, {bold: true, color: 'yellow', marginBottom: 1}, 'Step 2: Model Configuration'),
351
253
  create(Box, {flexDirection: 'row'},
352
- create(Text, null, 'Model: '),
254
+ create(Text, null, 'Model ID: '),
353
255
  create(CursorText, {value: model, cursorIndex: cursor})
354
256
  ),
355
- create(Text, {dimColor: true, marginTop: 1}, 'Enter: Save, Esc: Back')
257
+ create(Text, {dimColor: true, marginTop: 1}, 'Enter: Save Model, Esc: Back')
356
258
  ),
357
259
 
358
260
  step === 'token' && create(
359
261
  Box,
360
262
  {flexDirection: 'column'},
361
- create(Text, {bold: true, color: 'red', marginBottom: 1}, 'Step 3: API Token'),
263
+ create(Text, {bold: true, color: 'red', marginBottom: 1}, 'Step 3: API Token Authorization'),
362
264
  create(Box, {flexDirection: 'row'},
363
265
  create(Text, null, 'Token: '),
364
- create(CursorText, {value: '*'.repeat(token.length), cursorIndex: cursor})
266
+ create(CursorText, {value: token ? '********' : '', cursorIndex: cursor})
365
267
  ),
366
- create(Text, {dimColor: true, marginTop: 1}, 'Enter: Save, Esc: Back')
268
+ create(Text, {dimColor: true, marginTop: 1}, 'Enter: Save Token, Esc: Back')
367
269
  ),
368
270
 
369
271
  step === 'analyze' && create(
370
272
  Box,
371
273
  {flexDirection: 'column'},
372
- create(Text, {bold: true, color: 'cyan', marginBottom: 1},
373
- 'Project: ' + (selectedProject ? selectedProject.name : 'No project selected')),
374
-
375
- context && create(
376
- Box,
377
- {flexDirection: 'column', marginBottom: 1},
378
- create(Text, {dimColor: true}, `Type: ${context.type} | PM: ${context.packageManager}`),
379
- create(Text, {dimColor: true}, `Frameworks: ${context.frameworks}`),
380
- create(Text, {dimColor: true}, `Scripts: ${context.scriptNames}`)
381
- ),
382
-
383
- status === 'ready' && create(
384
- Box,
385
- {flexDirection: 'column'},
386
- create(Text, null, 'Press Enter to analyze with AI using real project context'),
387
- create(Text, {dimColor: true}, 'Provider: ' + config.aiProvider + ' | Model: ' + config.aiModel)
388
- ),
389
-
390
- status === 'busy' && create(Text, {color: 'yellow', marginTop: 1}, ' ⏳ Analyzing project with AI...'),
274
+ create(Text, {bold: true, color: 'cyan', marginBottom: 1}, 'Ready to analyze: ' + (selectedProject ? selectedProject.name : 'No project selected')),
275
+ create(Text, {dimColor: true}, 'Active: ' + config.aiProvider + ' (' + config.aiModel + ')'),
391
276
 
392
- status === 'review' && create(
393
- Box,
394
- {flexDirection: 'column', marginTop: 1},
395
- create(Text, {bold: true, color: 'green'}, '✅ AI Suggestions (Review & Edit):'),
396
- ...suggestions.map((cmd, idx) => {
397
- const isEditing = editingIdx === idx;
398
- return create(
399
- Box,
400
- {key: cmd.key, flexDirection: 'column', marginBottom: 0,
401
- borderStyle: isEditing ? 'double' : 'single',
402
- borderColor: isEditing ? 'yellow' : (idx === editingIdx ? 'cyan' : 'gray'),
403
- paddingX: 1},
404
- create(Text, {bold: true, color: idx === editingIdx ? 'cyan' : 'white'},
405
- (idx === editingIdx ? '→ ' : ' ') + cmd.label + ':'),
406
- isEditing
407
- ? create(CursorText, {value: editInput, cursorIndex: editCursor})
408
- : create(Text, {dimColor: true}, ' ' + cmd.command.join(' ')),
409
- );
410
- }),
411
- create(Box, {marginTop: 1, flexDirection: 'row', justifyContent: 'space-between'},
412
- create(Text, {dimColor: true}, '↑/↓: Select | E: Edit | S: Save to Config'),
413
- create(Text, {color: 'green'}, 'Esc: Cancel')
277
+ create(Box, {marginTop: 1, flexDirection: 'column'},
278
+ status === 'ready' && create(Text, null, 'Press Enter to perform real agentic analysis and auto-configure macros.'),
279
+ status === 'busy' && create(Text, {color: 'yellow'}, ' ⏳ Contacting AI Agent... mapping project structure...'),
280
+ status === 'done' && create(Box, {flexDirection: 'column'},
281
+ create(Text, {color: 'green', bold: true}, ' ✅ DNA Mapped via AI Agent!'),
282
+ create(Text, null, ' Successfully injected ' + suggestions.length + ' optimized commands.'),
283
+ create(Text, {dimColor: true, marginTop: 1}, 'Return to Navigator to use BRIT shortcuts.')
284
+ ),
285
+ status === 'done' && rawAIResponse && create(
286
+ Box,
287
+ {flexDirection: 'column', marginTop: 1, borderStyle: 'single', borderColor: 'gray', padding: 1},
288
+ create(Text, {bold: true, color: 'cyan'}, '📄 Raw AI Response:'),
289
+ create(Text, {dimColor: true}, rawAIResponse.slice(0, 2000))
290
+ ),
291
+ status === 'busy' && rawAIResponse && create(
292
+ Box,
293
+ {flexDirection: 'column', marginTop: 1, borderStyle: 'single', borderColor: 'gray', padding: 1},
294
+ create(Text, {bold: true, color: 'yellow'}, '📄 Partial Response:'),
295
+ create(Text, {dimColor: true}, rawAIResponse.slice(0, 1000))
296
+ ),
297
+ error && create(
298
+ Box,
299
+ {flexDirection: 'column', marginTop: 1, borderStyle: 'single', borderColor: 'red', padding: 1},
300
+ create(Text, {color: 'red', bold: true}, ' ✗ AI ERROR'),
301
+ create(Text, {color: 'red'}, error.length > 500 ? error.slice(0, 500) + '...' : error)
414
302
  )
415
303
  ),
416
-
417
- status === 'saved' && create(Text, {color: 'green', bold: true, marginTop: 1}, '✅ Commands saved to config!'),
418
-
419
- error && create(Text, {color: 'red', bold: true, marginTop: 1}, '✗ Error: ' + error),
420
-
421
- create(Text, {dimColor: true, marginTop: 1}, 'Esc: Back | R: Reset Auth')
304
+ create(Text, {dimColor: true, marginTop: 1}, 'Esc: Return, R: Reset Credentials')
422
305
  )
423
306
  );
424
307
  });
@@ -1,81 +1,25 @@
1
- /* global setInterval, clearInterval */
2
- import React, { useState, useEffect } from 'react';
1
+ import React from 'react';
3
2
  import { Box, Text } from 'ink';
4
3
 
5
- const PULSE = ['▱', '▰', '▱'];
6
- const ARROW = ['→', '⇒', '⇨', '⇒'];
7
-
8
4
  export default function Footer({ toggleHint, running, stdinBuffer, stdinCursor, CursorText }) {
9
- const [pulse, setPulse] = useState(0);
10
-
11
- useEffect(() => {
12
- if (!running) return;
13
- const timer = setInterval(() => setPulse(p => p + 1), 500);
14
- return () => clearInterval(timer);
15
- }, [running]);
16
-
17
- const pulseChar = running ? PULSE[pulse % PULSE.length] : '';
18
- const arrow = running ? ARROW[pulse % ARROW.length] : '→';
19
-
20
5
  return React.createElement(
21
6
  Box,
22
7
  { flexDirection: 'column', marginTop: 1 },
23
- // Status bar
24
8
  React.createElement(
25
9
  Box,
26
- { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 0 },
27
- React.createElement(
28
- Box,
29
- { flexDirection: 'row', alignItems: 'center' },
30
- React.createElement(Text, { dimColor: true }, `${arrow} `),
31
- React.createElement(Text, { dimColor: true },
32
- running ? 'Type to feed stdin · Enter: submit' : 'Run a command or press Shift+T for tasks'
33
- )
34
- ),
35
- React.createElement(
36
- Box,
37
- { flexDirection: 'row', alignItems: 'center' },
38
- React.createElement(Text, { dimColor: true }, `${toggleHint}`),
39
- React.createElement(Text, { dimColor: true }, ` · `),
40
- React.createElement(Text, { dimColor: true }, `Shift+S: Guide`)
41
- )
10
+ { flexDirection: 'row', justifyContent: 'space-between' },
11
+ React.createElement(Text, { dimColor: true }, running ? 'Type to feed stdin; Enter: submit.' : 'Run a command or press Shift+T to switch tasks.'),
12
+ React.createElement(Text, { dimColor: true }, `${toggleHint}, Shift+S: Structure Guide`)
42
13
  ),
43
- // Input area
44
14
  React.createElement(
45
15
  Box,
46
- {
47
- marginTop: 0,
48
- flexDirection: 'row',
49
- borderStyle: running ? 'double' : 'round',
50
- borderColor: running ? 'green' : 'gray',
51
- paddingX: 1,
52
- paddingY: 0
53
- },
54
- React.createElement(
55
- Text,
56
- { bold: true, color: running ? 'green' : 'white' },
57
- running ? ` ${pulseChar} Stdin ` : ' ○ Input '
58
- ),
16
+ { marginTop: 1, flexDirection: 'row', borderStyle: 'round', borderColor: running ? 'green' : 'gray', paddingX: 1 },
17
+ React.createElement(Text, { bold: true, color: running ? 'green' : 'white' }, running ? ' Stdin buffer ' : ' Input ready '),
59
18
  React.createElement(
60
19
  Box,
61
- { marginLeft: 1, flexGrow: 1 },
62
- React.createElement(CursorText, {
63
- value: stdinBuffer || (running ? '' : 'Ready for commands…'),
64
- cursorIndex: stdinCursor,
65
- active: running
66
- })
67
- ),
68
- running && React.createElement(
69
- Text,
70
- { color: 'green', bold: true },
71
- ' [Active]'
20
+ { marginLeft: 1 },
21
+ React.createElement(CursorText, { value: stdinBuffer || (running ? '' : 'Start a command to feed stdin'), cursorIndex: stdinCursor, active: running })
72
22
  )
73
- ),
74
- // Separator
75
- React.createElement(
76
- Box,
77
- { marginTop: 0 },
78
- React.createElement(Text, { dimColor: true }, '─'.repeat(50))
79
23
  )
80
24
  );
81
25
  }