@projectservan8n/cnapse 0.5.0 → 0.5.2

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/src/index.tsx CHANGED
@@ -6,85 +6,94 @@ import { setApiKey, setProvider, setModel, getConfig } from './lib/config.js';
6
6
 
7
7
  const args = process.argv.slice(2);
8
8
 
9
- // Handle CLI commands
10
- if (args.length > 0) {
11
- const command = args[0];
12
-
13
- switch (command) {
14
- case 'auth': {
15
- const provider = args[1] as 'openrouter' | 'anthropic' | 'openai';
16
- const key = args[2];
17
-
18
- if (!provider || !key) {
19
- console.log('Usage: cnapse auth <provider> <api-key>');
20
- console.log('Providers: openrouter, anthropic, openai');
21
- process.exit(1);
22
- }
9
+ async function main() {
10
+ // Handle CLI commands
11
+ if (args.length > 0) {
12
+ const command = args[0];
13
+
14
+ switch (command) {
15
+ case 'auth': {
16
+ const provider = args[1] as 'openrouter' | 'anthropic' | 'openai';
17
+ const key = args[2];
23
18
 
24
- if (!['openrouter', 'anthropic', 'openai'].includes(provider)) {
25
- console.log(`Invalid provider: ${provider}`);
26
- console.log('Valid providers: openrouter, anthropic, openai');
27
- process.exit(1);
28
- }
19
+ if (!provider || !key) {
20
+ console.log('Usage: cnapse auth <provider> <api-key>');
21
+ console.log('Providers: openrouter, anthropic, openai');
22
+ process.exit(1);
23
+ }
29
24
 
30
- setApiKey(provider, key);
31
- console.log(`✓ ${provider} API key saved`);
32
- process.exit(0);
33
- }
25
+ if (!['openrouter', 'anthropic', 'openai'].includes(provider)) {
26
+ console.log(`Invalid provider: ${provider}`);
27
+ console.log('Valid providers: openrouter, anthropic, openai');
28
+ process.exit(1);
29
+ }
34
30
 
35
- case 'config': {
36
- const subcommand = args[1];
31
+ setApiKey(provider, key);
32
+ console.log(`✓ ${provider} API key saved`);
33
+ process.exit(0);
34
+ }
37
35
 
38
- if (subcommand === 'set') {
39
- const key = args[2];
40
- const value = args[3];
36
+ case 'config': {
37
+ const subcommand = args[1];
38
+
39
+ // Interactive config TUI if no subcommand
40
+ if (!subcommand) {
41
+ const { ConfigUI } = await import('./components/ConfigUI.js');
42
+ render(<ConfigUI />);
43
+ return; // Don't render App
44
+ }
41
45
 
42
- if (key === 'provider') {
43
- if (!['openrouter', 'ollama', 'anthropic', 'openai'].includes(value!)) {
44
- console.log('Valid providers: openrouter, ollama, anthropic, openai');
45
- process.exit(1);
46
+ if (subcommand === 'set') {
47
+ const key = args[2];
48
+ const value = args[3];
49
+
50
+ if (key === 'provider') {
51
+ if (!['openrouter', 'ollama', 'anthropic', 'openai'].includes(value!)) {
52
+ console.log('Valid providers: openrouter, ollama, anthropic, openai');
53
+ process.exit(1);
54
+ }
55
+ setProvider(value as any);
56
+ console.log(`✓ Provider set to: ${value}`);
57
+ } else if (key === 'model') {
58
+ setModel(value!);
59
+ console.log(`✓ Model set to: ${value}`);
60
+ } else {
61
+ console.log('Usage: cnapse config set <provider|model> <value>');
46
62
  }
47
- setProvider(value as any);
48
- console.log(`✓ Provider set to: ${value}`);
49
- } else if (key === 'model') {
50
- setModel(value!);
51
- console.log(`✓ Model set to: ${value}`);
52
- } else {
53
- console.log('Usage: cnapse config set <provider|model> <value>');
63
+ process.exit(0);
54
64
  }
55
- process.exit(0);
56
- }
57
65
 
58
- if (subcommand === 'show' || !subcommand) {
59
- const config = getConfig();
60
- console.log('\nC-napse Configuration:');
61
- console.log(` Provider: ${config.provider}`);
62
- console.log(` Model: ${config.model}`);
63
- console.log(` Ollama Host: ${config.ollamaHost}`);
64
- console.log(` API Keys configured:`);
65
- console.log(` - OpenRouter: ${config.apiKeys.openrouter ? '✓' : '✗'}`);
66
- console.log(` - Anthropic: ${config.apiKeys.anthropic ? '✓' : '✗'}`);
67
- console.log(` - OpenAI: ${config.apiKeys.openai ? '✓' : '✗'}`);
68
- console.log('');
69
- process.exit(0);
70
- }
66
+ if (subcommand === 'show') {
67
+ const config = getConfig();
68
+ console.log('\nC-napse Configuration:');
69
+ console.log(` Provider: ${config.provider}`);
70
+ console.log(` Model: ${config.model}`);
71
+ console.log(` Ollama Host: ${config.ollamaHost}`);
72
+ console.log(` API Keys configured:`);
73
+ console.log(` - OpenRouter: ${config.apiKeys.openrouter ? '✓' : '✗'}`);
74
+ console.log(` - Anthropic: ${config.apiKeys.anthropic ? '✓' : '✗'}`);
75
+ console.log(` - OpenAI: ${config.apiKeys.openai ? '✓' : '✗'}`);
76
+ console.log('');
77
+ process.exit(0);
78
+ }
71
79
 
72
- console.log('Usage: cnapse config [show|set <key> <value>]');
73
- process.exit(1);
74
- }
80
+ console.log('Usage: cnapse config [show|set <key> <value>]');
81
+ process.exit(1);
82
+ }
75
83
 
76
- case 'help':
77
- case '--help':
78
- case '-h': {
79
- console.log(`
84
+ case 'help':
85
+ case '--help':
86
+ case '-h': {
87
+ console.log(`
80
88
  C-napse - Autonomous PC Intelligence
81
89
 
82
90
  Usage:
83
91
  cnapse Start interactive chat
84
92
  cnapse init Interactive setup wizard
85
- cnapse auth <provider> <key> Set API key
86
- cnapse config Show configuration
93
+ cnapse config Interactive configuration
94
+ cnapse config show Show current configuration
87
95
  cnapse config set <k> <v> Set config value
96
+ cnapse auth <provider> <key> Set API key
88
97
  cnapse help Show this help
89
98
 
90
99
  Providers:
@@ -95,36 +104,39 @@ Providers:
95
104
 
96
105
  Quick Start:
97
106
  cnapse init # Interactive setup
107
+ cnapse config # Change provider/model
98
108
 
99
109
  Manual Setup:
100
110
  cnapse auth openrouter sk-or-xxxxx
101
111
  cnapse config set provider openrouter
102
112
  cnapse config set model qwen/qwen-2.5-coder-32b-instruct
103
113
  `);
104
- process.exit(0);
105
- }
114
+ process.exit(0);
115
+ }
106
116
 
107
- case 'version':
108
- case '--version':
109
- case '-v': {
110
- console.log('cnapse v0.2.0');
111
- process.exit(0);
112
- }
117
+ case 'version':
118
+ case '--version':
119
+ case '-v': {
120
+ console.log('cnapse v0.5.0');
121
+ process.exit(0);
122
+ }
113
123
 
114
- case 'init': {
115
- // Interactive setup with Ink UI
116
- const { Setup } = await import('./components/Setup.js');
117
- render(<Setup />);
118
- process.exit(0);
119
- }
124
+ case 'init': {
125
+ // Interactive setup with Ink UI
126
+ const { Setup } = await import('./components/Setup.js');
127
+ render(<Setup />);
128
+ return; // Don't render App
129
+ }
120
130
 
121
- default: {
122
- // Treat as a direct question
123
- // For now, just start the app
124
- break;
131
+ default: {
132
+ // Unknown command - start app anyway
133
+ break;
134
+ }
125
135
  }
126
136
  }
137
+
138
+ // Start interactive TUI
139
+ render(<App />);
127
140
  }
128
141
 
129
- // Start interactive TUI
130
- render(<App />);
142
+ main();
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Ollama utilities - Check status, list models, pull/run models
3
+ */
4
+
5
+ import { exec } from 'child_process';
6
+ import { promisify } from 'util';
7
+
8
+ const execAsync = promisify(exec);
9
+
10
+ export interface OllamaModel {
11
+ name: string;
12
+ size: string;
13
+ modified: string;
14
+ }
15
+
16
+ export interface OllamaStatus {
17
+ installed: boolean;
18
+ running: boolean;
19
+ models: OllamaModel[];
20
+ error?: string;
21
+ }
22
+
23
+ /**
24
+ * Check if Ollama is installed and running, list available models
25
+ */
26
+ export async function checkOllamaStatus(): Promise<OllamaStatus> {
27
+ try {
28
+ // Try to list models - this checks both installation and if it's running
29
+ const { stdout } = await execAsync('ollama list', { timeout: 10000 });
30
+
31
+ // Parse the output
32
+ const lines = stdout.trim().split('\n');
33
+ const models: OllamaModel[] = [];
34
+
35
+ // Skip header line
36
+ for (let i = 1; i < lines.length; i++) {
37
+ const line = lines[i];
38
+ if (!line?.trim()) continue;
39
+
40
+ // Parse: NAME ID SIZE MODIFIED
41
+ const parts = line.split(/\s{2,}/);
42
+ if (parts.length >= 3) {
43
+ models.push({
44
+ name: parts[0]?.trim() || '',
45
+ size: parts[2]?.trim() || '',
46
+ modified: parts[3]?.trim() || '',
47
+ });
48
+ }
49
+ }
50
+
51
+ return {
52
+ installed: true,
53
+ running: true,
54
+ models,
55
+ };
56
+ } catch (err) {
57
+ const errorMsg = err instanceof Error ? err.message : 'Unknown error';
58
+
59
+ // Check if Ollama is installed but not running
60
+ if (errorMsg.includes('connect') || errorMsg.includes('refused')) {
61
+ return {
62
+ installed: true,
63
+ running: false,
64
+ models: [],
65
+ error: 'Ollama is not running. Start it with: ollama serve',
66
+ };
67
+ }
68
+
69
+ // Ollama not installed
70
+ if (errorMsg.includes('not found') || errorMsg.includes('not recognized')) {
71
+ return {
72
+ installed: false,
73
+ running: false,
74
+ models: [],
75
+ error: 'Ollama not installed. Get it at: https://ollama.ai',
76
+ };
77
+ }
78
+
79
+ return {
80
+ installed: false,
81
+ running: false,
82
+ models: [],
83
+ error: errorMsg,
84
+ };
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Check if a specific model is available
90
+ */
91
+ export function hasModel(status: OllamaStatus, modelId: string): boolean {
92
+ const modelName = modelId.split(':')[0]?.toLowerCase() || '';
93
+ return status.models.some(m => m.name.toLowerCase().startsWith(modelName));
94
+ }
95
+
96
+ /**
97
+ * Pull a model (download it)
98
+ */
99
+ export async function pullModel(modelId: string, onProgress?: (msg: string) => void): Promise<boolean> {
100
+ try {
101
+ onProgress?.(`Downloading ${modelId}...`);
102
+
103
+ // Pull with a long timeout (models can be large)
104
+ await execAsync(`ollama pull ${modelId}`, { timeout: 600000 }); // 10 min
105
+
106
+ onProgress?.(`Downloaded ${modelId}`);
107
+ return true;
108
+ } catch (err) {
109
+ const errorMsg = err instanceof Error ? err.message : 'Unknown error';
110
+ onProgress?.(`Failed to download: ${errorMsg}`);
111
+ return false;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Run a model to load it into memory
117
+ */
118
+ export async function runModel(modelId: string): Promise<boolean> {
119
+ try {
120
+ // Send a simple prompt to load the model
121
+ await execAsync(`ollama run ${modelId} "Hi" --nowordwrap`, { timeout: 120000 });
122
+ return true;
123
+ } catch {
124
+ return false;
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Get model size in human-readable format
130
+ */
131
+ export function getModelInfo(status: OllamaStatus, modelId: string): { available: boolean; size?: string } {
132
+ const modelName = modelId.split(':')[0]?.toLowerCase() || '';
133
+ const model = status.models.find(m => m.name.toLowerCase().startsWith(modelName));
134
+
135
+ if (model) {
136
+ return { available: true, size: model.size };
137
+ }
138
+
139
+ return { available: false };
140
+ }