project-compass 4.0.4 → 4.0.6

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.6",
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,118 @@ 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
+ let aiText = '';
51
+
52
+ if (provider.id === 'openrouter') {
53
+ response = await fetch(provider.endpoint, {
54
+ method: 'POST',
55
+ headers: {
56
+ 'Authorization': `Bearer ${token}`,
57
+ 'Content-Type': 'application/json',
58
+ 'HTTP-Referer': 'https://github.com/CrimsonDevil333333/project-compass',
59
+ 'X-Title': 'Project Compass'
60
+ },
61
+ body: JSON.stringify({
62
+ model: model,
63
+ messages: [{ role: 'user', content: prompt }]
64
+ })
65
+ });
66
+ const data = await response.json();
67
+ if (!response.ok) throw new Error(data.error?.message || 'OpenRouter Error');
68
+ aiText = data.choices[0].message.content;
69
+ } else if (provider.id === 'gemini') {
70
+ const url = provider.endpoint.replace('{model}', model) + `?key=${token}`;
71
+ response = await fetch(url, {
72
+ method: 'POST',
73
+ headers: { 'Content-Type': 'application/json' },
74
+ body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] })
75
+ });
76
+ const data = await response.json();
77
+ if (!response.ok) throw new Error(data.error?.message || 'Gemini Error');
78
+ aiText = data.candidates[0].content.parts[0].text;
79
+ } else if (provider.id === 'claude') {
80
+ response = await fetch(provider.endpoint, {
81
+ method: 'POST',
82
+ headers: {
83
+ 'x-api-key': token,
84
+ 'anthropic-version': '2023-06-01',
85
+ 'Content-Type': 'application/json'
86
+ },
87
+ body: JSON.stringify({
88
+ model: model,
89
+ max_tokens: 1024,
90
+ messages: [{ role: 'user', content: prompt }]
91
+ })
92
+ });
93
+ const data = await response.json();
94
+ if (!response.ok) throw new Error(data.error?.message || 'Claude Error');
95
+ aiText = data.content[0].text;
96
+ } else if (provider.id === 'ollama') {
97
+ response = await fetch(provider.endpoint, {
98
+ method: 'POST',
99
+ headers: { 'Content-Type': 'application/json' },
100
+ body: JSON.stringify({ model: model, prompt: prompt, stream: false })
101
+ });
102
+ const data = await response.json();
103
+ if (!response.ok) throw new Error(data.error || 'Ollama Error');
104
+ aiText = data.response;
105
+ }
106
+
107
+ const jsonMatch = aiText.match(/{.*?}/s);
108
+ if (!jsonMatch) throw new Error("AI returned invalid DNA mapping format.");
109
+
110
+ const parsed = JSON.parse(jsonMatch[0]);
111
+ const mapped = [
112
+ { label: 'AI Build', command: parsed.build.split(' ') },
113
+ { label: 'AI Run', command: parsed.run.split(' ') },
114
+ { label: 'AI Install', command: parsed.install.split(' ') },
115
+ { label: 'AI Test', command: parsed.test.split(' ') }
116
+ ];
117
+
118
+ setSuggestions(mapped);
119
+ const projectKey = selectedProject.path;
120
+ const currentCustom = config.customCommands?.[projectKey] || [];
121
+ const nextConfig = {
122
+ ...config,
123
+ customCommands: { ...config.customCommands, [projectKey]: [...currentCustom, ...mapped] }
124
+ };
125
+ setConfig(nextConfig); saveConfig(nextConfig);
126
+ setStatus('done');
127
+ } catch (err) {
128
+ setError(err.message);
129
+ setStatus('ready');
130
+ }
131
+ };
132
+
23
133
  useInput((input, key) => {
24
134
  if (step === 'provider') {
25
135
  if (key.upArrow) setProviderIdx(p => (p - 1 + AI_PROVIDERS.length) % AI_PROVIDERS.length);
@@ -55,32 +165,7 @@ const AIHorizon = memo(({selectedProject, CursorText, config, setConfig, saveCon
55
165
  }
56
166
  } else if (step === 'analyze') {
57
167
  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);
168
+ runRealAnalysis();
84
169
  }
85
170
  if (input === 'r') {
86
171
  const nextConfig = { ...config, aiToken: '' };
@@ -132,13 +217,14 @@ const AIHorizon = memo(({selectedProject, CursorText, config, setConfig, saveCon
132
217
  create(Text, {dimColor: true}, 'Active: ' + config.aiProvider + ' (' + config.aiModel + ')'),
133
218
 
134
219
  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...'),
220
+ status === 'ready' && create(Text, null, 'Press Enter to perform real agentic analysis and auto-configure macros.'),
221
+ status === 'busy' && create(Text, {color: 'yellow'}, ' ⏳ Contacting AI Agent... mapping project structure...'),
137
222
  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.'),
223
+ create(Text, {color: 'green', bold: true}, ' ✅ DNA Mapped via AI Agent!'),
224
+ create(Text, null, ' Successfully injected ' + suggestions.length + ' optimized commands into project config.'),
140
225
  create(Text, {dimColor: true, marginTop: 1}, 'Return to Navigator to use BRIT shortcuts.')
141
- )
226
+ ),
227
+ error && create(Text, {color: 'red', bold: true, marginTop: 1}, ' ✗ AI ERROR: ' + error)
142
228
  ),
143
229
  create(Text, {dimColor: true, marginTop: 1}, 'Esc: Return, R: Reset Credentials')
144
230
  )