project-compass 4.3.3 → 4.3.7
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.
- package/AGENTS.md +1121 -22
- package/ARCHITECTURE.md +826 -10
- package/CONTRIBUTING.md +974 -10
- package/PROJECT_CONTEXT.md +404 -0
- package/README.md +594 -67
- package/commands.md +841 -104
- package/package.json +5 -4
- package/src/cli.js +310 -169
- package/src/components/AIHorizon.js +138 -255
- package/src/components/Footer.js +8 -64
- package/src/components/Header.js +8 -47
- package/src/components/Navigator.js +16 -70
- package/src/components/PackageRegistry.js +4 -3
- package/src/components/ProjectArchitect.js +6 -1
- package/src/components/TaskManager.js +12 -70
- package/src/detectors/dotnet.js +3 -2
- package/src/detectors/frameworks.js +28 -28
- package/src/detectors/go.js +6 -6
- package/src/detectors/java.js +2 -1
- package/src/detectors/node.js +3 -2
- package/src/detectors/php.js +1 -1
- package/src/detectors/python.js +33 -12
- package/src/detectors/ruby.js +1 -1
- package/src/detectors/rust.js +2 -2
- package/src/detectors/utils.js +6 -7
- package/src/projectDetection.js +41 -5
- package/src/store/useProjectStore.js +0 -32
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* global fetch
|
|
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
|
-
|
|
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
|
|
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 [
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
if (!context) {
|
|
81
|
-
setError('No project selected for analysis');
|
|
82
|
-
setStatus('ready');
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
78
|
+
|
|
86
79
|
try {
|
|
87
|
-
const
|
|
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
|
|
96
|
-
1.
|
|
97
|
-
2.
|
|
98
|
-
3.
|
|
99
|
-
4.
|
|
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
|
-
|
|
102
|
-
|
|
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
|
|
111
|
-
{"build": "
|
|
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
|
-
|
|
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]
|
|
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]
|
|
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]
|
|
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
|
-
|
|
174
|
-
|
|
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
|
-
{
|
|
181
|
-
{
|
|
182
|
-
{
|
|
183
|
-
{
|
|
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
|
-
|
|
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
|
|
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}, '
|
|
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: '
|
|
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
|
-
|
|
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
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
create(
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
)
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
});
|
package/src/components/Footer.js
CHANGED
|
@@ -1,81 +1,25 @@
|
|
|
1
|
-
|
|
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'
|
|
27
|
-
React.createElement(
|
|
28
|
-
|
|
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
|
-
|
|
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
|
|
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
|
}
|