infinicode 1.0.0
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/README.md +130 -0
- package/bin/infinicode.js +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +166 -0
- package/dist/commands/models.d.ts +2 -0
- package/dist/commands/models.js +76 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +84 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +60 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +40 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# ⚡ Infinicode
|
|
2
|
+
|
|
3
|
+
**AI coding agent powered by your local Ollama.** Fork of OpenCode optimized for self-hosted LLMs.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g infinicode
|
|
7
|
+
|
|
8
|
+
# Connect to your Ollama master
|
|
9
|
+
infinicode connect 192.168.1.100
|
|
10
|
+
|
|
11
|
+
# Start coding
|
|
12
|
+
infinicode
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Why Infinicode?
|
|
16
|
+
|
|
17
|
+
- **100% Local** — All inference on YOUR hardware
|
|
18
|
+
- **Zero Cloud** — No API keys, no subscriptions, no data leaving your network
|
|
19
|
+
- **One Command Setup** — `infinicode connect <ip>` and you're done
|
|
20
|
+
- **Multi-Node** — Run on any machine, point to one Ollama master
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### 1. Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install -g infinicode
|
|
28
|
+
# or
|
|
29
|
+
pnpm add -g infinicode
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. Setup Ollama Master
|
|
33
|
+
|
|
34
|
+
On your GPU machine:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Allow network connections
|
|
38
|
+
OLLAMA_HOST=0.0.0.0:11434 ollama serve
|
|
39
|
+
|
|
40
|
+
# Pull a coding model
|
|
41
|
+
ollama pull qwen2.5-coder:14b
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 3. Connect & Code
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# From any machine on your network
|
|
48
|
+
infinicode connect 192.168.1.100 # Your Ollama IP
|
|
49
|
+
infinicode # Start coding!
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Commands
|
|
53
|
+
|
|
54
|
+
| Command | Alias | Description |
|
|
55
|
+
|---------|-------|-------------|
|
|
56
|
+
| `infinicode connect <ip>` | `ic c` | Quick connect to Ollama master |
|
|
57
|
+
| `infinicode setup` | `ic s` | Interactive setup wizard |
|
|
58
|
+
| `infinicode run` | `ic` | Start the coding agent (default) |
|
|
59
|
+
| `infinicode models` | `ic m` | List available models |
|
|
60
|
+
| `infinicode status` | | Show config & connection status |
|
|
61
|
+
| `infinicode config --list` | | View all configuration |
|
|
62
|
+
| `infinicode config --reset` | | Reset to defaults |
|
|
63
|
+
|
|
64
|
+
## Configuration
|
|
65
|
+
|
|
66
|
+
Config is stored automatically. Override with:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Set default model
|
|
70
|
+
infinicode config --set defaultModel=codestral:22b
|
|
71
|
+
|
|
72
|
+
# Set master URL
|
|
73
|
+
infinicode config --set masterUrl=http://192.168.1.100:11434
|
|
74
|
+
|
|
75
|
+
# View all
|
|
76
|
+
infinicode config --list
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Recommended Models
|
|
80
|
+
|
|
81
|
+
For coding tasks on Ollama:
|
|
82
|
+
|
|
83
|
+
| Model | Size | Best For |
|
|
84
|
+
|-------|------|----------|
|
|
85
|
+
| `qwen2.5-coder:14b` | 9GB | Great balance |
|
|
86
|
+
| `qwen2.5-coder:32b` | 20GB | Best quality |
|
|
87
|
+
| `deepseek-coder-v2:16b` | 10GB | Strong reasoning |
|
|
88
|
+
| `codestral:22b` | 13GB | Fast completion |
|
|
89
|
+
|
|
90
|
+
## Architecture
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
┌─────────────────┐
|
|
94
|
+
│ Laptop │
|
|
95
|
+
│ (infinicode) │──┐
|
|
96
|
+
└─────────────────┘ │
|
|
97
|
+
│ ┌─────────────────┐
|
|
98
|
+
┌─────────────────┐ │ │ GPU Server │
|
|
99
|
+
│ Desktop │──┼───▶│ (Ollama) │
|
|
100
|
+
│ (infinicode) │ │ │ │
|
|
101
|
+
└─────────────────┘ │ │ qwen2.5-coder │
|
|
102
|
+
│ │ codestral │
|
|
103
|
+
┌─────────────────┐ │ └─────────────────┘
|
|
104
|
+
│ Server │──┘
|
|
105
|
+
│ (infinicode) │
|
|
106
|
+
└─────────────────┘
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Security
|
|
110
|
+
|
|
111
|
+
Exposing Ollama to your network means anyone on that network can use it.
|
|
112
|
+
|
|
113
|
+
**Secure options:**
|
|
114
|
+
1. **VPN/Tailscale** — Only accessible on private network
|
|
115
|
+
2. **SSH Tunnel** — `ssh -L 11434:localhost:11434 gpu-server`
|
|
116
|
+
3. **Firewall** — Allow only specific IPs
|
|
117
|
+
|
|
118
|
+
## Requirements
|
|
119
|
+
|
|
120
|
+
- Node.js 20+
|
|
121
|
+
- OpenCode installed (`npm install -g opencode`)
|
|
122
|
+
- Ollama running somewhere on your network
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT — Based on [OpenCode](https://github.com/sst/opencode)
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
⚡ Built for sovereign computing. Your code, your models, your hardware.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import Conf from 'conf';
|
|
5
|
+
import { setupMaster } from './commands/setup.js';
|
|
6
|
+
import { runAgent } from './commands/run.js';
|
|
7
|
+
import { showStatus } from './commands/status.js';
|
|
8
|
+
import { listModels } from './commands/models.js';
|
|
9
|
+
const VERSION = '1.0.0';
|
|
10
|
+
const config = new Conf({
|
|
11
|
+
projectName: 'infinicode',
|
|
12
|
+
defaults: {
|
|
13
|
+
masterUrl: 'http://localhost:11434',
|
|
14
|
+
defaultModel: 'qwen2.5-coder:14b',
|
|
15
|
+
theme: 'infini'
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
const program = new Command();
|
|
19
|
+
console.log(chalk.cyan(`
|
|
20
|
+
⚡ ${chalk.bold('INFINICODE')} v${VERSION}
|
|
21
|
+
${chalk.dim('AI Coding Agent • Powered by Ollama')}
|
|
22
|
+
`));
|
|
23
|
+
program
|
|
24
|
+
.name('infinicode')
|
|
25
|
+
.description('AI coding agent powered by your local Ollama')
|
|
26
|
+
.version(VERSION);
|
|
27
|
+
// Quick connect command
|
|
28
|
+
program
|
|
29
|
+
.command('connect <master-ip>')
|
|
30
|
+
.alias('c')
|
|
31
|
+
.description('Quick connect to Ollama master (e.g., infinicode connect 192.168.1.100)')
|
|
32
|
+
.option('-p, --port <port>', 'Ollama port', '11434')
|
|
33
|
+
.option('-m, --model <model>', 'Default model', 'qwen2.5-coder:14b')
|
|
34
|
+
.action(async (masterIp, options) => {
|
|
35
|
+
await setupMaster(masterIp, options.port, options.model, config);
|
|
36
|
+
});
|
|
37
|
+
// Setup wizard
|
|
38
|
+
program
|
|
39
|
+
.command('setup')
|
|
40
|
+
.alias('s')
|
|
41
|
+
.description('Interactive setup wizard')
|
|
42
|
+
.action(async () => {
|
|
43
|
+
const inquirer = (await import('inquirer')).default;
|
|
44
|
+
const answers = await inquirer.prompt([
|
|
45
|
+
{
|
|
46
|
+
type: 'input',
|
|
47
|
+
name: 'masterIp',
|
|
48
|
+
message: 'Ollama master IP:',
|
|
49
|
+
default: 'localhost'
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: 'input',
|
|
53
|
+
name: 'port',
|
|
54
|
+
message: 'Ollama port:',
|
|
55
|
+
default: '11434'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: 'input',
|
|
59
|
+
name: 'model',
|
|
60
|
+
message: 'Default model:',
|
|
61
|
+
default: 'qwen2.5-coder:14b'
|
|
62
|
+
}
|
|
63
|
+
]);
|
|
64
|
+
await setupMaster(answers.masterIp, answers.port, answers.model, config);
|
|
65
|
+
});
|
|
66
|
+
// Run the agent (main command)
|
|
67
|
+
program
|
|
68
|
+
.command('run', { isDefault: true })
|
|
69
|
+
.alias('r')
|
|
70
|
+
.description('Start the coding agent')
|
|
71
|
+
.option('-m, --model <model>', 'Model to use')
|
|
72
|
+
.option('--no-tui', 'Disable TUI, use simple mode')
|
|
73
|
+
.action(async (options) => {
|
|
74
|
+
const masterUrl = config.get('masterUrl');
|
|
75
|
+
const model = options.model || config.get('defaultModel');
|
|
76
|
+
if (!masterUrl || masterUrl === 'http://localhost:11434') {
|
|
77
|
+
console.log(chalk.yellow('\n⚠️ No master configured. Using localhost.'));
|
|
78
|
+
console.log(chalk.dim(' Run: infinicode connect <master-ip>\n'));
|
|
79
|
+
}
|
|
80
|
+
await runAgent(masterUrl, model, options.tui !== false, config);
|
|
81
|
+
});
|
|
82
|
+
// Status command
|
|
83
|
+
program
|
|
84
|
+
.command('status')
|
|
85
|
+
.description('Show current configuration and connection status')
|
|
86
|
+
.action(async () => {
|
|
87
|
+
await showStatus(config);
|
|
88
|
+
});
|
|
89
|
+
// Models command
|
|
90
|
+
program
|
|
91
|
+
.command('models')
|
|
92
|
+
.alias('m')
|
|
93
|
+
.description('List available models on master')
|
|
94
|
+
.action(async () => {
|
|
95
|
+
await listModels(config);
|
|
96
|
+
});
|
|
97
|
+
// Config commands
|
|
98
|
+
program
|
|
99
|
+
.command('config')
|
|
100
|
+
.description('View or modify configuration')
|
|
101
|
+
.option('--get <key>', 'Get a config value')
|
|
102
|
+
.option('--set <key=value>', 'Set a config value')
|
|
103
|
+
.option('--list', 'List all config')
|
|
104
|
+
.option('--reset', 'Reset to defaults')
|
|
105
|
+
.action((options) => {
|
|
106
|
+
if (options.reset) {
|
|
107
|
+
config.clear();
|
|
108
|
+
console.log(chalk.green('✓ Config reset to defaults'));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (options.list) {
|
|
112
|
+
console.log(chalk.bold('\nConfiguration:'));
|
|
113
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
114
|
+
for (const [key, value] of Object.entries(config.store)) {
|
|
115
|
+
console.log(` ${chalk.cyan(key)}: ${value}`);
|
|
116
|
+
}
|
|
117
|
+
console.log();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (options.get) {
|
|
121
|
+
console.log(config.get(options.get));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (options.set) {
|
|
125
|
+
const [key, value] = options.set.split('=');
|
|
126
|
+
config.set(key, value);
|
|
127
|
+
console.log(chalk.green(`✓ Set ${key} = ${value}`));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// Default: show config
|
|
131
|
+
console.log(chalk.bold('\nConfiguration:'));
|
|
132
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
133
|
+
for (const [key, value] of Object.entries(config.store)) {
|
|
134
|
+
console.log(` ${chalk.cyan(key)}: ${value}`);
|
|
135
|
+
}
|
|
136
|
+
console.log();
|
|
137
|
+
});
|
|
138
|
+
// Passthrough to opencode
|
|
139
|
+
program
|
|
140
|
+
.command('opencode [args...]')
|
|
141
|
+
.description('Run opencode directly with Ollama config injected')
|
|
142
|
+
.allowUnknownOption()
|
|
143
|
+
.action(async (args) => {
|
|
144
|
+
const { execa } = await import('execa');
|
|
145
|
+
const masterUrl = config.get('masterUrl');
|
|
146
|
+
process.env.LOCAL_ENDPOINT = `${masterUrl}/v1`;
|
|
147
|
+
process.env.OPENCODE_CONFIG_CONTENT = JSON.stringify({
|
|
148
|
+
providers: {
|
|
149
|
+
anthropic: { disabled: true },
|
|
150
|
+
openai: { disabled: true },
|
|
151
|
+
gemini: { disabled: true },
|
|
152
|
+
local: { apiKey: 'not-needed' }
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
try {
|
|
156
|
+
await execa('opencode', args, {
|
|
157
|
+
stdio: 'inherit',
|
|
158
|
+
env: process.env
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
if (e.exitCode)
|
|
163
|
+
process.exit(e.exitCode);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
program.parse();
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
function formatSize(bytes) {
|
|
4
|
+
const gb = bytes / (1024 ** 3);
|
|
5
|
+
if (gb >= 1)
|
|
6
|
+
return `${gb.toFixed(1)} GB`;
|
|
7
|
+
const mb = bytes / (1024 ** 2);
|
|
8
|
+
return `${mb.toFixed(0)} MB`;
|
|
9
|
+
}
|
|
10
|
+
export async function listModels(config) {
|
|
11
|
+
const masterUrl = config.get('masterUrl');
|
|
12
|
+
const defaultModel = config.get('defaultModel');
|
|
13
|
+
console.log(chalk.bold('\n⚡ Available Models'));
|
|
14
|
+
console.log(chalk.dim(` Master: ${masterUrl}`));
|
|
15
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
16
|
+
const spinner = ora('Fetching models...').start();
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(`${masterUrl}/api/tags`, {
|
|
19
|
+
signal: AbortSignal.timeout(10000)
|
|
20
|
+
});
|
|
21
|
+
if (!response.ok) {
|
|
22
|
+
throw new Error(`HTTP ${response.status}`);
|
|
23
|
+
}
|
|
24
|
+
const data = await response.json();
|
|
25
|
+
spinner.stop();
|
|
26
|
+
const models = data.models || [];
|
|
27
|
+
if (models.length === 0) {
|
|
28
|
+
console.log(chalk.yellow('\n No models installed.'));
|
|
29
|
+
console.log(chalk.dim('\n Recommended models for coding:'));
|
|
30
|
+
console.log(chalk.cyan(' ollama pull qwen2.5-coder:14b'));
|
|
31
|
+
console.log(chalk.cyan(' ollama pull deepseek-coder-v2:16b'));
|
|
32
|
+
console.log(chalk.cyan(' ollama pull codestral:22b'));
|
|
33
|
+
console.log();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Categorize models
|
|
37
|
+
const codingModels = [];
|
|
38
|
+
const otherModels = [];
|
|
39
|
+
for (const m of models) {
|
|
40
|
+
const name = m.name.toLowerCase();
|
|
41
|
+
if (name.includes('code') || name.includes('coder') || name.includes('stral')) {
|
|
42
|
+
codingModels.push(m);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
otherModels.push(m);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Display coding models first
|
|
49
|
+
if (codingModels.length > 0) {
|
|
50
|
+
console.log(chalk.bold('\n 💻 Coding Models:'));
|
|
51
|
+
for (const m of codingModels) {
|
|
52
|
+
const isDefault = m.name === defaultModel || m.name.startsWith(defaultModel.split(':')[0]);
|
|
53
|
+
const marker = isDefault ? chalk.green('★') : ' ';
|
|
54
|
+
const size = formatSize(m.size);
|
|
55
|
+
console.log(` ${marker} ${chalk.cyan(m.name.padEnd(30))} ${chalk.dim(size)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (otherModels.length > 0) {
|
|
59
|
+
console.log(chalk.bold('\n 🤖 Other Models:'));
|
|
60
|
+
for (const m of otherModels) {
|
|
61
|
+
const isDefault = m.name === defaultModel;
|
|
62
|
+
const marker = isDefault ? chalk.green('★') : ' ';
|
|
63
|
+
const size = formatSize(m.size);
|
|
64
|
+
console.log(` ${marker} ${chalk.cyan(m.name.padEnd(30))} ${chalk.dim(size)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
console.log(chalk.dim(`\n Total: ${models.length} models`));
|
|
68
|
+
console.log(chalk.dim(` ★ = current default\n`));
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
spinner.fail('Failed to fetch models');
|
|
72
|
+
console.log(chalk.red(`\n ✗ Could not reach ${masterUrl}`));
|
|
73
|
+
console.log(chalk.dim(' Is Ollama running?\n'));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
export async function runAgent(masterUrl, model, useTui, config) {
|
|
4
|
+
// Check connection first
|
|
5
|
+
const spinner = ora('Connecting to Ollama...').start();
|
|
6
|
+
try {
|
|
7
|
+
const response = await fetch(`${masterUrl}/api/tags`, {
|
|
8
|
+
signal: AbortSignal.timeout(5000)
|
|
9
|
+
});
|
|
10
|
+
if (!response.ok) {
|
|
11
|
+
throw new Error(`HTTP ${response.status}`);
|
|
12
|
+
}
|
|
13
|
+
const data = await response.json();
|
|
14
|
+
const models = data.models || [];
|
|
15
|
+
const hasModel = models.some(m => m.name === model || m.name.startsWith(model.split(':')[0]));
|
|
16
|
+
if (!hasModel) {
|
|
17
|
+
spinner.warn(`Model '${model}' not found`);
|
|
18
|
+
if (models.length > 0) {
|
|
19
|
+
console.log(chalk.dim(`\nAvailable: ${models.slice(0, 5).map(m => m.name).join(', ')}`));
|
|
20
|
+
}
|
|
21
|
+
console.log(chalk.yellow(`\nPull the model first:`));
|
|
22
|
+
console.log(chalk.cyan(` ollama pull ${model}\n`));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
spinner.succeed('Ollama connected');
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
spinner.fail('Connection failed');
|
|
29
|
+
console.log(chalk.red(`\n✗ Could not connect to ${masterUrl}`));
|
|
30
|
+
console.log(chalk.dim('\nRun: infinicode connect <master-ip>\n'));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
// Set environment for opencode
|
|
34
|
+
process.env.LOCAL_ENDPOINT = `${masterUrl}/v1`;
|
|
35
|
+
process.env.OPENCODE_CONFIG_CONTENT = JSON.stringify({
|
|
36
|
+
providers: {
|
|
37
|
+
anthropic: { disabled: true },
|
|
38
|
+
openai: { disabled: true },
|
|
39
|
+
gemini: { disabled: true },
|
|
40
|
+
groq: { disabled: true },
|
|
41
|
+
openrouter: { disabled: true },
|
|
42
|
+
xai: { disabled: true },
|
|
43
|
+
azure: { disabled: true },
|
|
44
|
+
bedrock: { disabled: true },
|
|
45
|
+
vertexai: { disabled: true },
|
|
46
|
+
copilot: { disabled: true },
|
|
47
|
+
local: { apiKey: 'not-needed' }
|
|
48
|
+
},
|
|
49
|
+
agents: {
|
|
50
|
+
coder: { model: `local.${model}` }
|
|
51
|
+
},
|
|
52
|
+
tui: {
|
|
53
|
+
theme: config.get('theme') || 'infini'
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
console.log(chalk.dim(`\nStarting with ${chalk.cyan(model)}...`));
|
|
57
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
58
|
+
// Try to run opencode
|
|
59
|
+
const { execa } = await import('execa');
|
|
60
|
+
try {
|
|
61
|
+
// Check if opencode is installed
|
|
62
|
+
await execa('which', ['opencode']).catch(() => execa('where', ['opencode']));
|
|
63
|
+
// Run opencode
|
|
64
|
+
await execa('opencode', [], {
|
|
65
|
+
stdio: 'inherit',
|
|
66
|
+
env: process.env
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
if (e.code === 'ENOENT' || e.exitCode === 1) {
|
|
71
|
+
console.log(chalk.yellow('\n⚠️ OpenCode not found.'));
|
|
72
|
+
console.log(chalk.dim('\nInstall it with:'));
|
|
73
|
+
console.log(chalk.cyan(' npm install -g opencode'));
|
|
74
|
+
console.log(chalk.dim('\nOr use infinicode with the Go binary:'));
|
|
75
|
+
console.log(chalk.cyan(' cd /path/to/opencode-infini && go build -o infini && ./infini'));
|
|
76
|
+
console.log();
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
if (e.exitCode) {
|
|
80
|
+
process.exit(e.exitCode);
|
|
81
|
+
}
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
export async function setupMaster(masterIp, port, model, config) {
|
|
4
|
+
const masterUrl = `http://${masterIp}:${port}`;
|
|
5
|
+
console.log(chalk.bold('\n⚡ Infinicode Setup'));
|
|
6
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
7
|
+
console.log(` Master: ${chalk.cyan(masterUrl)}`);
|
|
8
|
+
console.log(` Model: ${chalk.cyan(model)}`);
|
|
9
|
+
console.log();
|
|
10
|
+
const spinner = ora('Testing connection...').start();
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch(`${masterUrl}/api/tags`, {
|
|
13
|
+
signal: AbortSignal.timeout(10000)
|
|
14
|
+
});
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
throw new Error(`HTTP ${response.status}`);
|
|
17
|
+
}
|
|
18
|
+
const data = await response.json();
|
|
19
|
+
spinner.succeed('Connected to Ollama');
|
|
20
|
+
// Save config
|
|
21
|
+
config.set('masterUrl', masterUrl);
|
|
22
|
+
config.set('defaultModel', model);
|
|
23
|
+
// Show available models
|
|
24
|
+
console.log(chalk.bold('\nAvailable models:'));
|
|
25
|
+
const models = data.models || [];
|
|
26
|
+
if (models.length === 0) {
|
|
27
|
+
console.log(chalk.yellow(' No models found. Pull one with:'));
|
|
28
|
+
console.log(chalk.dim(` ollama pull ${model}`));
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
for (const m of models.slice(0, 10)) {
|
|
32
|
+
const isDefault = m.name === model || m.name.startsWith(model.split(':')[0]);
|
|
33
|
+
console.log(` ${isDefault ? chalk.green('●') : chalk.dim('○')} ${m.name}`);
|
|
34
|
+
}
|
|
35
|
+
if (models.length > 10) {
|
|
36
|
+
console.log(chalk.dim(` ... and ${models.length - 10} more`));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Check if default model exists
|
|
40
|
+
const hasModel = models.some(m => m.name === model || m.name.startsWith(model.split(':')[0]));
|
|
41
|
+
if (!hasModel && models.length > 0) {
|
|
42
|
+
console.log(chalk.yellow(`\n⚠️ Model '${model}' not found.`));
|
|
43
|
+
console.log(chalk.dim(` Available: ${models.slice(0, 3).map(m => m.name).join(', ')}`));
|
|
44
|
+
console.log(chalk.dim(` Pull it: ollama pull ${model}`));
|
|
45
|
+
}
|
|
46
|
+
console.log(chalk.green('\n✓ Configuration saved!'));
|
|
47
|
+
console.log(chalk.dim('\nRun `infinicode` or `ic` to start coding.\n'));
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
spinner.fail('Connection failed');
|
|
51
|
+
console.log(chalk.red(`\n✗ Could not connect to ${masterUrl}`));
|
|
52
|
+
console.log(chalk.dim('\nMake sure Ollama is running with network access:'));
|
|
53
|
+
console.log(chalk.cyan(' OLLAMA_HOST=0.0.0.0:11434 ollama serve'));
|
|
54
|
+
console.log(chalk.dim('\nOr via systemd:'));
|
|
55
|
+
console.log(chalk.cyan(' sudo systemctl edit ollama'));
|
|
56
|
+
console.log(chalk.dim(' Add: Environment="OLLAMA_HOST=0.0.0.0:11434"'));
|
|
57
|
+
console.log(chalk.cyan(' sudo systemctl restart ollama\n'));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
export async function showStatus(config) {
|
|
4
|
+
const masterUrl = config.get('masterUrl');
|
|
5
|
+
const defaultModel = config.get('defaultModel');
|
|
6
|
+
console.log(chalk.bold('\n⚡ Infinicode Status'));
|
|
7
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
8
|
+
console.log(` Master URL: ${chalk.cyan(masterUrl)}`);
|
|
9
|
+
console.log(` Default Model: ${chalk.cyan(defaultModel)}`);
|
|
10
|
+
const spinner = ora('Checking connection...').start();
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch(`${masterUrl}/api/tags`, {
|
|
13
|
+
signal: AbortSignal.timeout(5000)
|
|
14
|
+
});
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
throw new Error(`HTTP ${response.status}`);
|
|
17
|
+
}
|
|
18
|
+
const data = await response.json();
|
|
19
|
+
spinner.succeed('Ollama connected');
|
|
20
|
+
const models = data.models || [];
|
|
21
|
+
console.log(` Models: ${chalk.green(models.length)} available`);
|
|
22
|
+
// Check if default model is available
|
|
23
|
+
const hasDefault = models.some(m => m.name === defaultModel || m.name.startsWith(defaultModel.split(':')[0]));
|
|
24
|
+
if (hasDefault) {
|
|
25
|
+
console.log(` Status: ${chalk.green('● Ready')}`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
console.log(` Status: ${chalk.yellow('● Missing model')}`);
|
|
29
|
+
console.log(chalk.yellow(`\n ⚠️ Default model '${defaultModel}' not found.`));
|
|
30
|
+
console.log(chalk.dim(` Pull it: ollama pull ${defaultModel}`));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
spinner.fail('Connection failed');
|
|
35
|
+
console.log(` Status: ${chalk.red('● Offline')}`);
|
|
36
|
+
console.log(chalk.red(`\n ✗ Could not reach ${masterUrl}`));
|
|
37
|
+
console.log(chalk.dim(' Is Ollama running with OLLAMA_HOST=0.0.0.0?'));
|
|
38
|
+
}
|
|
39
|
+
console.log();
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "infinicode",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI coding agent powered by your local Ollama. Fork of OpenCode optimized for self-hosted LLMs.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/cli.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"infinicode": "./bin/infinicode.js",
|
|
9
|
+
"ic": "./bin/infinicode.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin",
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"dev": "tsc --watch",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"ai",
|
|
23
|
+
"coding",
|
|
24
|
+
"agent",
|
|
25
|
+
"ollama",
|
|
26
|
+
"llm",
|
|
27
|
+
"cli",
|
|
28
|
+
"opencode",
|
|
29
|
+
"local",
|
|
30
|
+
"self-hosted"
|
|
31
|
+
],
|
|
32
|
+
"author": "Ra Kai'Un",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/chosen-to-build/infinicode"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=20"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"chalk": "^5.3.0",
|
|
43
|
+
"commander": "^12.1.0",
|
|
44
|
+
"conf": "^12.0.0",
|
|
45
|
+
"execa": "^9.3.0",
|
|
46
|
+
"inquirer": "^9.2.23",
|
|
47
|
+
"node-fetch": "^3.3.2",
|
|
48
|
+
"ora": "^8.0.1"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/inquirer": "^9.0.9",
|
|
52
|
+
"@types/node": "^20.14.0",
|
|
53
|
+
"typescript": "^5.4.5"
|
|
54
|
+
}
|
|
55
|
+
}
|