project-compass 4.0.3 → 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/commands.md CHANGED
@@ -92,3 +92,10 @@ Compass scans for the following manifests and requires their binaries in your PA
92
92
  - Go (mod init)
93
93
  - **Enter**: Confirm selection and move to next step.
94
94
  - **Esc / Shift+N**: Exit architect mode.
95
+
96
+ ## Advanced Configuration
97
+
98
+ You can manually edit the config file to change defaults:
99
+ - **Location**: `~/.project-compass/config.json`
100
+ - **maxVisibleProjects**: Adjust how many projects appear per page (default: 3).
101
+ - **aiProvider / aiModel / aiToken**: Change your AI intelligence settings.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-compass",
3
- "version": "4.0.3",
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
@@ -294,7 +294,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
294
294
 
295
295
  useInput((input, key) => {
296
296
  if (quitConfirm) {
297
- if (input?.toLowerCase() === 'y') { killAllTasks(); console.clear(); exit(); return; }
297
+ if (input?.toLowerCase() === 'y') { killAllTasks(); process.stdout.write('\x1b[2J\x1b[0;0H'); exit(); return; }
298
298
  if (input?.toLowerCase() === 'n' || key.escape) { setQuitConfirm(false); return; }
299
299
  return;
300
300
  }
@@ -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; }
@@ -468,7 +469,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
468
469
  return;
469
470
  }
470
471
  if (shiftCombo('q') || isCtrlC) {
471
- if (hasRunningTasks) setQuitConfirm(true); else { console.clear(); exit(); }
472
+ if (hasRunningTasks) setQuitConfirm(true); else { process.stdout.write('\x1b[2J\x1b[0;0H'); exit(); }
472
473
  return;
473
474
  }
474
475
 
@@ -581,9 +582,9 @@ function Compass({rootPath, initialView = 'navigator'}) {
581
582
  create(Footer, {toggleHint, running, stdinBuffer, stdinCursor, CursorText})
582
583
  ),
583
584
  config.showHelpCards && create(Box, {key: 'help-cards', marginTop: 1, flexDirection: 'row', justifyContent: 'space-between', flexWrap: 'wrap'}, [
584
- {label: 'Navigation', color: 'magenta', body: [' / ↓ move focus, Enter: details', 'Shift+↑ / ↓ scroll output', 'Shift+H toggle help cards', 'Shift+D detach from task']},
585
+ {label: 'Navigation', color: 'magenta', body: ['↑/↓: focus, PgUp/Dn: Page, Enter: Details', 'Shift+↑ / ↓ scroll output', 'Shift+H toggle help cards', 'Shift+D detach from task']},
585
586
  {label: 'Management', color: 'cyan', body: ['Shift+P Package Registry', 'Shift+N Project Architect', 'Shift+X clear / Shift+E export']},
586
- {label: 'Orbit & AI', color: 'yellow', body: ['Shift+T task manager', 'Shift+A studio / Shift+O AI Horizon', 'Shift+S structure / Shift+Q quit']}
587
+ {label: 'Orbit & AI', color: 'yellow', body: ['Shift+T: Tasks, Shift+O: AI, 0: Analyze', 'Shift+A studio / Shift+O AI Horizon', 'Shift+S structure / Shift+Q quit']}
587
588
  ].map((card, idx) => create(Box, {key: card.label, flexGrow: 1, flexBasis: 0, minWidth: HELP_CARD_MIN_WIDTH, marginRight: idx < 2 ? 1 : 0, marginBottom: 1, borderStyle: 'round', borderColor: card.color, padding: 1, flexDirection: 'column'}, create(Text, {color: card.color, bold: true, marginBottom: 1}, card.label), ...card.body.map((line, lidx) => create(Text, {key: lidx, dimColor: card.color === 'yellow'}, line))))),
588
589
  config.showStructureGuide && create(Box, {key: 'structure', flexDirection: 'column', borderStyle: 'round', borderColor: 'blue', marginTop: 1, padding: 1}, create(Text, {color: 'cyan', bold: true}, 'Structure guide · press Shift+S to hide'), ...SCHEMA_GUIDE.map(e => create(Text, {key: e.type, dimColor: true}, `• ${e.icon} ${e.label}: ${e.files.join(', ')}`))),
589
590
  showHelp && create(Box, {key: 'overlay', flexDirection: 'column', borderStyle: 'double', borderColor: 'cyan', marginTop: 1, padding: 1}, create(Text, {color: 'cyan', bold: true}, 'Help overlay'), create(Text, null, 'Shift+↑/↓ scrolls logs; Shift+X clears; Shift+E exports; Shift+A Studio; Shift+T Tasks; Shift+D Detach; Shift+B Toggle Art Board; Shift+P Packages; Shift+N Creator; Shift+O AI Horizon.'))
@@ -622,7 +623,6 @@ async function main() {
622
623
  return;
623
624
  }
624
625
  if (args.help) {
625
- console.clear();
626
626
  console.log(kleur.bold(kleur.magenta('🧭 Project Compass · Premium Developer Cockpit')));
627
627
  console.log(kleur.dim('───────────────────────────────────────────────────'));
628
628
  console.log('');
@@ -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', keyEnv: 'OPENROUTER_API_KEY' },
9
- { id: 'gemini', name: 'Google Gemini', endpoint: 'https://generativelanguage.googleapis.com', keyEnv: 'GEMINI_API_KEY' },
10
- { id: 'claude', name: 'Anthropic Claude', endpoint: 'https://api.anthropic.com', keyEnv: 'ANTHROPIC_API_KEY' },
11
- { id: 'ollama', name: 'Ollama (Local)', endpoint: 'http://localhost:11434', keyEnv: 'NONE' }
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,6 +18,83 @@ 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);
22
+ const [suggestions, setSuggestions] = useState([]);
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
+ };
21
98
 
22
99
  useInput((input, key) => {
23
100
  if (step === 'provider') {
@@ -25,18 +102,14 @@ const AIHorizon = memo(({selectedProject, CursorText, config, setConfig, saveCon
25
102
  if (key.downArrow) setProviderIdx(p => (p + 1) % AI_PROVIDERS.length);
26
103
  if (key.return) {
27
104
  const nextConfig = { ...config, aiProvider: AI_PROVIDERS[providerIdx].id };
28
- if (setConfig) setConfig(nextConfig);
29
- if (saveConfig) saveConfig(nextConfig);
30
- setStep('model');
31
- setCursor(model.length);
105
+ setConfig(nextConfig); saveConfig(nextConfig);
106
+ setStep('model'); setCursor(model.length);
32
107
  }
33
108
  } else if (step === 'model') {
34
109
  if (key.return) {
35
110
  const nextConfig = { ...config, aiModel: model };
36
- if (setConfig) setConfig(nextConfig);
37
- if (saveConfig) saveConfig(nextConfig);
38
- setStep('token');
39
- setCursor(token.length);
111
+ setConfig(nextConfig); saveConfig(nextConfig);
112
+ setStep('token'); setCursor(token.length);
40
113
  }
41
114
  if (key.escape) setStep('provider');
42
115
  if (key.backspace || key.delete) {
@@ -47,8 +120,7 @@ const AIHorizon = memo(({selectedProject, CursorText, config, setConfig, saveCon
47
120
  } else if (step === 'token') {
48
121
  if (key.return) {
49
122
  const nextConfig = { ...config, aiToken: token };
50
- if (setConfig) setConfig(nextConfig);
51
- if (saveConfig) saveConfig(nextConfig);
123
+ setConfig(nextConfig); saveConfig(nextConfig);
52
124
  setStep('analyze');
53
125
  }
54
126
  if (key.escape) setStep('model');
@@ -59,33 +131,16 @@ const AIHorizon = memo(({selectedProject, CursorText, config, setConfig, saveCon
59
131
  }
60
132
  } else if (step === 'analyze') {
61
133
  if (key.return && status === 'ready' && selectedProject) {
62
- setStatus('busy');
63
- setTimeout(() => {
64
- const projectKey = selectedProject.path;
65
- const currentCustom = config.customCommands?.[projectKey] || [];
66
- const nextConfig = {
67
- ...config,
68
- customCommands: {
69
- ...config.customCommands,
70
- [projectKey]: [...currentCustom, { label: 'AI: Optimized Run', command: ['npm', 'run', 'dev'] }]
71
- }
72
- };
73
- if (setConfig) setConfig(nextConfig);
74
- if (saveConfig) saveConfig(nextConfig);
75
- setStatus('done');
76
- }, 1000);
134
+ runRealAnalysis();
77
135
  }
78
136
  if (input === 'r') {
79
- const resetConfig = { ...config, aiToken: '' };
80
- if (setConfig) setConfig(resetConfig);
81
- if (saveConfig) saveConfig(resetConfig);
137
+ const nextConfig = { ...config, aiToken: '' };
138
+ setConfig(nextConfig); saveConfig(nextConfig);
82
139
  setStep('provider');
83
140
  }
84
141
  }
85
142
  });
86
143
 
87
- const currentProvider = AI_PROVIDERS[providerIdx];
88
-
89
144
  return create(
90
145
  Box,
91
146
  {flexDirection: 'column', borderStyle: 'double', borderColor: 'magenta', padding: 1, width: '100%'},
@@ -95,7 +150,7 @@ const AIHorizon = memo(({selectedProject, CursorText, config, setConfig, saveCon
95
150
  Box,
96
151
  {flexDirection: 'column'},
97
152
  create(Text, {bold: true, marginBottom: 1}, 'Step 1: Select AI Infrastructure'),
98
- ...AI_PROVIDERS.map((p, i) => create(Text, {key: p.id, color: i === providerIdx ? 'cyan' : 'white'}, (i === providerIdx ? '→ ' : ' ') + p.name + ' (' + p.endpoint + ')')),
153
+ ...AI_PROVIDERS.map((p, i) => create(Text, {key: p.id, color: i === providerIdx ? 'cyan' : 'white'}, (i === providerIdx ? '→ ' : ' ') + p.name)),
99
154
  create(Text, {dimColor: true, marginTop: 1}, 'Enter: Save & Next')
100
155
  ),
101
156
 
@@ -107,36 +162,37 @@ const AIHorizon = memo(({selectedProject, CursorText, config, setConfig, saveCon
107
162
  create(Text, null, 'Model ID: '),
108
163
  create(CursorText, {value: model, cursorIndex: cursor})
109
164
  ),
110
- create(Text, {dimColor: true, marginTop: 1}, 'Enter: Save, Esc: Back')
165
+ create(Text, {dimColor: true, marginTop: 1}, 'Enter: Save Model, Esc: Back')
111
166
  ),
112
167
 
113
168
  step === 'token' && create(
114
169
  Box,
115
170
  {flexDirection: 'column'},
116
- create(Text, {bold: true, color: 'red', marginBottom: 1}, 'Step 3: Secure API Authorization'),
117
- create(Text, {dimColor: true}, 'Token required for ' + (currentProvider?.name || 'Provider')),
118
- create(Box, {flexDirection: 'row', marginTop: 1},
119
- create(Text, null, 'API Token: '),
171
+ create(Text, {bold: true, color: 'red', marginBottom: 1}, 'Step 3: API Token Authorization'),
172
+ create(Box, {flexDirection: 'row'},
173
+ create(Text, null, 'Token: '),
120
174
  create(CursorText, {value: '*'.repeat(token.length), cursorIndex: cursor})
121
175
  ),
122
- create(Text, {dimColor: true, marginTop: 1}, 'Enter: Encrypt & Save, Esc: Back')
176
+ create(Text, {dimColor: true, marginTop: 1}, 'Enter: Save Token, Esc: Back')
123
177
  ),
124
178
 
125
179
  step === 'analyze' && create(
126
180
  Box,
127
181
  {flexDirection: 'column'},
128
- create(Text, {bold: true, color: 'cyan', marginBottom: 1}, 'Intelligence Active: ' + (selectedProject ? selectedProject.name : 'Current Workspace')),
129
- create(Text, {dimColor: true}, 'Provider: ' + (config.aiProvider || 'N/A') + ' | Model: ' + (config.aiModel || 'N/A')),
182
+ create(Text, {bold: true, color: 'cyan', marginBottom: 1}, 'Ready to analyze: ' + (selectedProject ? selectedProject.name : 'No project selected')),
183
+ create(Text, {dimColor: true}, 'Active: ' + config.aiProvider + ' (' + config.aiModel + ')'),
184
+
130
185
  create(Box, {marginTop: 1, flexDirection: 'column'},
131
- status === 'ready' && create(Text, null, 'Press Enter to perform DNA analysis and auto-configure BRIT commands.'),
132
- status === 'busy' && create(Text, {color: 'yellow'}, ' ⏳ Accessing AI... Mapping manifests...'),
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...'),
133
188
  status === 'done' && create(Box, {flexDirection: 'column'},
134
- create(Text, {color: 'green', bold: true}, ' ✅ DNA Mapped!'),
135
- create(Text, null, ' Intelligence has successfully injected optimized commands into your project config.'),
136
- create(Text, {dimColor: true, marginTop: 1}, 'Press Esc to return to the Navigator.')
137
- )
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.'),
191
+ create(Text, {dimColor: true, marginTop: 1}, 'Return to Navigator to use BRIT shortcuts.')
192
+ ),
193
+ error && create(Text, {color: 'red', bold: true, marginTop: 1}, ' ✗ AI ERROR: ' + error)
138
194
  ),
139
- create(Text, {dimColor: true, marginTop: 1}, 'Esc: Back, R: Reset Credentials')
195
+ create(Text, {dimColor: true, marginTop: 1}, 'Esc: Return, R: Reset Credentials')
140
196
  )
141
197
  );
142
198
  });