@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/dist/ConfigUI-4436NFLC.js +306 -0
- package/dist/{Setup-Q32JPHGP.js → Setup-HAPL64ZK.js} +8 -5
- package/dist/index.js +393 -177
- package/package.json +1 -1
- package/src/components/ConfigUI.tsx +353 -0
- package/src/components/ProviderSelector.tsx +271 -69
- package/src/components/Setup.tsx +5 -6
- package/src/index.tsx +95 -83
- package/src/lib/ollama.ts +140 -0
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
31
|
+
setApiKey(provider, key);
|
|
32
|
+
console.log(`✓ ${provider} API key saved`);
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
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 (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
console.log('Usage: cnapse config [show|set <key> <value>]');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
75
83
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
86
|
-
cnapse config
|
|
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
|
-
|
|
105
|
-
|
|
114
|
+
process.exit(0);
|
|
115
|
+
}
|
|
106
116
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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
|
+
}
|