project-compass 4.0.4 → 4.0.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-compass",
3
- "version": "4.0.4",
3
+ "version": "4.0.5",
4
4
  "description": "Futuristic project navigator and runner for Node, Python, Rust, and Go",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -451,10 +451,11 @@ function Compass({rootPath, initialView = 'navigator'}) {
451
451
  if (key.shift && key.upArrow) { scrollLogs(1); return; }
452
452
  if (key.shift && key.downArrow) { scrollLogs(-1); return; }
453
453
 
454
- const pageStep = Math.max(1, config.maxVisibleProjects || 3);
454
+ const pageLimit = config.maxVisibleProjects || 3;
455
+ const pageStep = Math.max(1, pageLimit);
455
456
  const clampIndex = (value) => Math.max(0, Math.min(projects.length - 1, value));
456
- if (key.pageUp && projects.length > 0) { console.clear(); setSelectedIndex((prev) => clampIndex(prev - pageStep)); return; }
457
- if (key.pageDown && projects.length > 0) { console.clear(); setSelectedIndex((prev) => clampIndex(prev + pageStep)); return; }
457
+ if (key.pageUp && projects.length > pageLimit) { console.clear(); setSelectedIndex((prev) => clampIndex(prev - pageStep)); return; }
458
+ if (key.pageDown && projects.length > pageLimit) { console.clear(); setSelectedIndex((prev) => clampIndex(prev + pageStep)); return; }
458
459
 
459
460
  if (normalizedInput === '?') { console.clear(); setShowHelp((prev) => !prev); return; }
460
461
  if (shiftCombo('l') && lastCommandRef.current) { runProjectCommand(lastCommandRef.current.commandMeta, lastCommandRef.current.project); return; }
@@ -1,14 +1,14 @@
1
- /* global setTimeout */
1
+ /* global setTimeout, fetch */
2
2
  import React, {useState, memo} from 'react';
3
3
  import {Box, Text, useInput} from 'ink';
4
4
 
5
5
  const create = React.createElement;
6
6
 
7
7
  const AI_PROVIDERS = [
8
- { id: 'openrouter', name: 'OpenRouter', endpoint: 'https://openrouter.ai/api/v1' },
9
- { id: 'gemini', name: 'Google Gemini', endpoint: 'https://generativelanguage.googleapis.com' },
10
- { id: 'claude', name: 'Anthropic Claude', endpoint: 'https://api.anthropic.com' },
11
- { id: 'ollama', name: 'Ollama (Local)', endpoint: 'http://localhost:11434' }
8
+ { id: 'openrouter', name: 'OpenRouter', endpoint: 'https://openrouter.ai/api/v1/chat/completions' },
9
+ { id: 'gemini', name: 'Google Gemini', endpoint: 'https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent' },
10
+ { id: 'claude', name: 'Anthropic Claude', endpoint: 'https://api.anthropic.com/v1/messages' },
11
+ { id: 'ollama', name: 'Ollama (Local)', endpoint: 'http://localhost:11434/api/generate' }
12
12
  ];
13
13
 
14
14
  const AIHorizon = memo(({selectedProject, CursorText, config, setConfig, saveConfig}) => {
@@ -18,8 +18,84 @@ const AIHorizon = memo(({selectedProject, CursorText, config, setConfig, saveCon
18
18
  const [token, setToken] = useState(config?.aiToken || '');
19
19
  const [cursor, setCursor] = useState(0);
20
20
  const [status, setStatus] = useState('ready');
21
+ const [error, setError] = useState(null);
21
22
  const [suggestions, setSuggestions] = useState([]);
22
23
 
24
+ const runRealAnalysis = async () => {
25
+ setStatus('busy');
26
+ setError(null);
27
+ const provider = AI_PROVIDERS[providerIdx];
28
+
29
+ try {
30
+ const projectData = JSON.stringify({
31
+ name: selectedProject.name,
32
+ type: selectedProject.type,
33
+ manifest: selectedProject.manifest,
34
+ scripts: selectedProject.metadata?.scripts || {},
35
+ dependencies: selectedProject.metadata?.dependencies || []
36
+ });
37
+
38
+ const prompt = `Analyze this project structure and suggest valid CLI commands for:
39
+ 1. Build
40
+ 2. Run
41
+ 3. Install
42
+ 4. Test
43
+
44
+ Project Data: ${projectData}
45
+
46
+ Return ONLY a JSON object with this structure: {"build": "cmd", "run": "cmd", "install": "cmd", "test": "cmd"}.
47
+ Use the project's detected type (${selectedProject.type}) to ensure commands are correct (e.g., npm, pip, cargo).`;
48
+
49
+ let response;
50
+ if (provider.id === 'openrouter') {
51
+ response = await fetch(provider.endpoint, {
52
+ method: 'POST',
53
+ headers: {
54
+ 'Authorization': `Bearer ${token}`,
55
+ 'Content-Type': 'application/json',
56
+ 'HTTP-Referer': 'https://github.com/CrimsonDevil333333/project-compass',
57
+ 'X-Title': 'Project Compass'
58
+ },
59
+ body: JSON.stringify({
60
+ model: model,
61
+ messages: [{ role: 'user', content: prompt }]
62
+ })
63
+ });
64
+ } else {
65
+ // Fallback for others or throw unimplemented for now to avoid "fake" results
66
+ throw new Error(`Real-time agentic analysis for ${provider.name} is arriving in the next patch. Use OpenRouter for live testing now.`);
67
+ }
68
+
69
+ const data = await response.json();
70
+ if (!response.ok) throw new Error(data.error?.message || 'Authentication failed. Check your token.');
71
+
72
+ const aiText = data.choices[0].message.content;
73
+ const jsonMatch = aiText.match(/{.*?}/s);
74
+ if (!jsonMatch) throw new Error("AI returned invalid DNA mapping. Try a different model.");
75
+
76
+ const parsed = JSON.parse(jsonMatch[0]);
77
+ const mapped = [
78
+ { label: 'AI Build', command: parsed.build.split(' ') },
79
+ { label: 'AI Run', command: parsed.run.split(' ') },
80
+ { label: 'AI Install', command: parsed.install.split(' ') },
81
+ { label: 'AI Test', command: parsed.test.split(' ') }
82
+ ];
83
+
84
+ setSuggestions(mapped);
85
+ const projectKey = selectedProject.path;
86
+ const currentCustom = config.customCommands?.[projectKey] || [];
87
+ const nextConfig = {
88
+ ...config,
89
+ customCommands: { ...config.customCommands, [projectKey]: [...currentCustom, ...mapped] }
90
+ };
91
+ setConfig(nextConfig); saveConfig(nextConfig);
92
+ setStatus('done');
93
+ } catch (err) {
94
+ setError(err.message);
95
+ setStatus('ready');
96
+ }
97
+ };
98
+
23
99
  useInput((input, key) => {
24
100
  if (step === 'provider') {
25
101
  if (key.upArrow) setProviderIdx(p => (p - 1 + AI_PROVIDERS.length) % AI_PROVIDERS.length);
@@ -55,32 +131,7 @@ const AIHorizon = memo(({selectedProject, CursorText, config, setConfig, saveCon
55
131
  }
56
132
  } else if (step === 'analyze') {
57
133
  if (key.return && status === 'ready' && selectedProject) {
58
- setStatus('busy');
59
- setTimeout(() => {
60
- // REAL DNA MAPPING: Check project scripts and suggest real matches
61
- const scripts = selectedProject.metadata?.scripts || {};
62
- const suggested = [];
63
-
64
- if (scripts.build) suggested.push({ label: 'AI Build', command: ['npm', 'run', 'build'] });
65
- if (scripts.start || scripts.dev) suggested.push({ label: 'AI Run', command: ['npm', 'run', scripts.dev ? 'dev' : 'start'] });
66
- if (scripts.test) suggested.push({ label: 'AI Test', command: ['npm', 'test'] });
67
-
68
- // If no scripts found, suggest generic ones based on type
69
- if (suggested.length === 0) {
70
- if (selectedProject.type === 'Node.js') suggested.push({ label: 'AI Init', command: ['npm', 'install'] });
71
- else if (selectedProject.type === 'Python') suggested.push({ label: 'AI Run', command: ['python', 'main.py'] });
72
- }
73
-
74
- setSuggestions(suggested);
75
- const projectKey = selectedProject.path;
76
- const currentCustom = config.customCommands?.[projectKey] || [];
77
- const nextConfig = {
78
- ...config,
79
- customCommands: { ...config.customCommands, [projectKey]: [...currentCustom, ...suggested] }
80
- };
81
- setConfig(nextConfig); saveConfig(nextConfig);
82
- setStatus('done');
83
- }, 1200);
134
+ runRealAnalysis();
84
135
  }
85
136
  if (input === 'r') {
86
137
  const nextConfig = { ...config, aiToken: '' };
@@ -132,13 +183,14 @@ const AIHorizon = memo(({selectedProject, CursorText, config, setConfig, saveCon
132
183
  create(Text, {dimColor: true}, 'Active: ' + config.aiProvider + ' (' + config.aiModel + ')'),
133
184
 
134
185
  create(Box, {marginTop: 1, flexDirection: 'column'},
135
- status === 'ready' && create(Text, null, 'Press Enter to map project DNA and auto-configure macros.'),
136
- status === 'busy' && create(Text, {color: 'yellow'}, ' ⏳ Reading manifests... identifying build patterns...'),
186
+ status === 'ready' && create(Text, null, 'Press Enter to perform real agentic analysis and auto-configure macros.'),
187
+ status === 'busy' && create(Text, {color: 'yellow'}, ' ⏳ Contacting AI Agent... mapping project structure...'),
137
188
  status === 'done' && create(Box, {flexDirection: 'column'},
138
- create(Text, {color: 'green', bold: true}, ' ✅ DNA Mapped!'),
139
- create(Text, null, ' Identified ' + suggestions.length + ' valid commands based on your workspace structure.'),
189
+ create(Text, {color: 'green', bold: true}, ' ✅ DNA Mapped via AI Agent!'),
190
+ create(Text, null, ' Successfully injected ' + suggestions.length + ' optimized commands into project config.'),
140
191
  create(Text, {dimColor: true, marginTop: 1}, 'Return to Navigator to use BRIT shortcuts.')
141
- )
192
+ ),
193
+ error && create(Text, {color: 'red', bold: true, marginTop: 1}, ' ✗ AI ERROR: ' + error)
142
194
  ),
143
195
  create(Text, {dimColor: true, marginTop: 1}, 'Esc: Return, R: Reset Credentials')
144
196
  )