free-antigravity-cli 1.0.0 → 1.0.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/README.md +107 -60
- package/dist/cli.js +163 -159
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +160 -172
- package/src/chat.ts +0 -184
package/README.md
CHANGED
|
@@ -1,44 +1,59 @@
|
|
|
1
1
|
# Free Antigravity CLI
|
|
2
2
|
|
|
3
|
-
**Open Source Community Edition** -
|
|
3
|
+
**Open Source Community Edition** - Wraps the official [Antigravity CLI](https://antigravity.google/cli) (`agy`) with custom AI model support.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Use **OpenAI, Anthropic, Ollama, OpenRouter, Google AI Studio, and any OpenAI-compatible provider** alongside Gemini models -- all through the native `agy` CLI experience.
|
|
6
|
+
|
|
7
|
+
## How It Works
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
antigravity
|
|
11
|
+
├── Starts local proxy (port 50999)
|
|
12
|
+
├── Auto-patches agy.exe to route through proxy
|
|
13
|
+
└── Delegates to agy CLI
|
|
14
|
+
├── Google models → daily-cloudcode-pa.googleapis.com (transparent)
|
|
15
|
+
└── Custom models → injected by proxy into model list
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The CLI is a thin wrapper: it starts a local HTTP proxy that intercepts `fetchAvailableModels` API calls, injects your custom model definitions, then hands off to the official `agy` CLI. You get the full native Antigravity CLI experience plus custom models.
|
|
6
19
|
|
|
7
20
|
## Quick Start
|
|
8
21
|
|
|
9
22
|
```bash
|
|
10
|
-
# Install
|
|
23
|
+
# 1. Install official Antigravity CLI first
|
|
24
|
+
curl -fsSL https://antigravity.google/cli/install.cmd -o install.cmd && install.cmd && del install.cmd
|
|
25
|
+
|
|
26
|
+
# 2. Install Free Antigravity CLI
|
|
11
27
|
npm install -g free-antigravity-cli
|
|
12
28
|
|
|
13
|
-
#
|
|
14
|
-
antigravity
|
|
29
|
+
# 3. Add your custom models
|
|
30
|
+
antigravity models add
|
|
15
31
|
|
|
16
|
-
#
|
|
17
|
-
|
|
32
|
+
# 4. Start chatting (all models appear in agy's model selector)
|
|
33
|
+
antigravity
|
|
18
34
|
```
|
|
19
35
|
|
|
20
|
-
##
|
|
36
|
+
## Prerequisites
|
|
21
37
|
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
24
|
-
- **Model Management**: Add, remove, list, and import models via CLI
|
|
25
|
-
- **Local Proxy**: Built-in proxy server for IDE integration
|
|
26
|
-
- **API Key Encryption**: AES-256-CBC encryption for stored API keys
|
|
27
|
-
- **Community Owned**: Apache 2.0 license, fully open source
|
|
38
|
+
- **Node.js** >= 18
|
|
39
|
+
- **Official Antigravity CLI** (`agy`) installed at `%LOCALAPPDATA%\agy\bin\agy.exe`
|
|
28
40
|
|
|
29
41
|
## Commands
|
|
30
42
|
|
|
31
43
|
```
|
|
32
|
-
antigravity chat
|
|
33
|
-
antigravity chat
|
|
34
|
-
antigravity models list
|
|
35
|
-
antigravity models add
|
|
36
|
-
antigravity models remove <name>
|
|
37
|
-
antigravity models import
|
|
38
|
-
antigravity
|
|
39
|
-
antigravity
|
|
44
|
+
antigravity Start interactive chat (proxy + agy)
|
|
45
|
+
antigravity chat Same as above
|
|
46
|
+
antigravity models list List configured custom models
|
|
47
|
+
antigravity models add Add a new custom model (interactive wizard)
|
|
48
|
+
antigravity models remove <name> Remove a custom model
|
|
49
|
+
antigravity models import Import models from desktop Antigravity
|
|
50
|
+
antigravity configure Show configuration info
|
|
51
|
+
antigravity version Show version
|
|
52
|
+
antigravity help Show this help
|
|
40
53
|
```
|
|
41
54
|
|
|
55
|
+
Any arguments not listed above are passed directly to `agy` CLI.
|
|
56
|
+
|
|
42
57
|
## Supported Providers
|
|
43
58
|
|
|
44
59
|
| Provider | CLI Value | Auth |
|
|
@@ -56,13 +71,7 @@ antigravity configure # Show config info
|
|
|
56
71
|
|
|
57
72
|
```bash
|
|
58
73
|
npm install -g free-antigravity-cli
|
|
59
|
-
antigravity
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### npx (No Install)
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
npx free-antigravity-cli chat
|
|
74
|
+
antigravity
|
|
66
75
|
```
|
|
67
76
|
|
|
68
77
|
### From Source
|
|
@@ -72,29 +81,10 @@ git clone https://github.com/vahapogut/free-antigravity-cli.git
|
|
|
72
81
|
cd free-antigravity-cli
|
|
73
82
|
npm install
|
|
74
83
|
npm run build
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
## How It Works
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
Terminal (antigravity chat)
|
|
82
|
-
→ CLI sends Gemini-format request
|
|
83
|
-
→ Local proxy (port 50999) intercepts
|
|
84
|
-
├── Google models → daily-cloudcode-pa.googleapis.com
|
|
85
|
-
└── Custom models → OpenAI / Anthropic / Ollama / etc.
|
|
86
|
-
→ Response translated back to Gemini format
|
|
87
|
-
→ Streaming output to terminal
|
|
84
|
+
npm link
|
|
85
|
+
antigravity
|
|
88
86
|
```
|
|
89
87
|
|
|
90
|
-
The CLI includes a **built-in proxy server** that translates between Gemini format and provider-native formats:
|
|
91
|
-
|
|
92
|
-
- **OpenAI**: `Gemini ↔ OpenAI Chat Completions`
|
|
93
|
-
- **Anthropic**: `Gemini ↔ Anthropic Messages`
|
|
94
|
-
- **Ollama**: `Gemini ↔ OpenAI-compatible` (port 11434)
|
|
95
|
-
- **Google AI Studio**: Passthrough with URL routing
|
|
96
|
-
- **Custom**: OpenAI-compatible format
|
|
97
|
-
|
|
98
88
|
## Configuration
|
|
99
89
|
|
|
100
90
|
Models are stored in `~/.free-antigravity/models.json`:
|
|
@@ -109,34 +99,91 @@ Models are stored in `~/.free-antigravity/models.json`:
|
|
|
109
99
|
"apiKey": "sk-...",
|
|
110
100
|
"apiUrl": "https://api.openai.com/v1/chat/completions",
|
|
111
101
|
"externalModelName": "gpt-4o"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"name": "models/claude-opus-4-7",
|
|
105
|
+
"displayName": "Claude Opus 4.7",
|
|
106
|
+
"provider": "anthropic",
|
|
107
|
+
"apiKey": "sk-ant-...",
|
|
108
|
+
"apiUrl": "https://api.anthropic.com/v1/messages",
|
|
109
|
+
"externalModelName": "claude-opus-4-7"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "models/llama3",
|
|
113
|
+
"displayName": "Llama 3 (Local)",
|
|
114
|
+
"provider": "ollama",
|
|
115
|
+
"apiKey": "none",
|
|
116
|
+
"apiUrl": "http://localhost:11434/v1/chat/completions",
|
|
117
|
+
"externalModelName": "llama3"
|
|
112
118
|
}
|
|
113
119
|
]
|
|
114
120
|
}
|
|
115
121
|
```
|
|
116
122
|
|
|
117
|
-
|
|
123
|
+
### Importing from Desktop Antigravity
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
antigravity models import
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
> **NOTE:** API keys from the desktop app are encrypted with Electron's `safeStorage` and cannot be decrypted by the CLI. After importing, re-enter your API keys via `antigravity models add`.
|
|
130
|
+
|
|
131
|
+
## Technical Details
|
|
132
|
+
|
|
133
|
+
### Binary Patching
|
|
134
|
+
|
|
135
|
+
On first run, the CLI automatically patches `agy.exe` to replace the hardcoded Google API URL:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
https://daily-cloudcode-pa.googleapis.com
|
|
139
|
+
→ http://localhost:50999/v1internal/xxxxxxx
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
This forces `agy` to route its `fetchAvailableModels` calls through the local proxy, where custom model definitions are injected.
|
|
143
|
+
|
|
144
|
+
The original binary is backed up at `agy.exe.bak`.
|
|
145
|
+
|
|
146
|
+
### Proxy Server
|
|
147
|
+
|
|
148
|
+
The proxy runs on `http://127.0.0.1:50999` (falls back to dynamic port if busy) and:
|
|
149
|
+
|
|
150
|
+
1. **Intercepts `fetchAvailableModels`**: Merges custom model definitions into the response
|
|
151
|
+
2. **Intercepts `generateContent`/`streamGenerateContent`**: Routes custom model requests to external APIs
|
|
152
|
+
3. **Translates formats**: Gemini ↔ OpenAI / Anthropic / Ollama / Google AI Studio
|
|
153
|
+
4. **Transparent forwarding**: All other requests pass through to Google unchanged
|
|
154
|
+
|
|
155
|
+
### Provider Translation
|
|
156
|
+
|
|
157
|
+
| Provider | Request Translation | Response Translation | Streaming |
|
|
158
|
+
|---|---|---|---|
|
|
159
|
+
| OpenAI | Gemini → Chat Completions | Chat Completions → Gemini | SSE delta → Gemini chunks |
|
|
160
|
+
| Anthropic | Gemini → Messages API | Messages → Gemini | SSE events → Gemini chunks |
|
|
161
|
+
| Ollama | Gemini → OpenAI-compatible | OpenAI → Gemini | Same as OpenAI |
|
|
162
|
+
| Google AI Studio | Passthrough (native Gemini) | Passthrough | SSE chunks |
|
|
163
|
+
| OpenRouter | Same as OpenAI | Same as OpenAI | Same as OpenAI |
|
|
164
|
+
| Custom | Same as OpenAI | Same as OpenAI | Same as OpenAI |
|
|
165
|
+
|
|
166
|
+
## Comparison
|
|
118
167
|
|
|
119
168
|
| Feature | Free Antigravity CLI | Google agy CLI |
|
|
120
169
|
|---|---|---|
|
|
121
170
|
| Open Source | Yes (Apache 2.0) | No |
|
|
122
171
|
| Custom Models | Yes | No |
|
|
123
|
-
| OpenAI | Yes | No |
|
|
124
|
-
| Anthropic (Claude) | Yes | No |
|
|
125
|
-
| Ollama (Local) | Yes | No |
|
|
172
|
+
| OpenAI / Anthropic / Ollama | Yes | No |
|
|
126
173
|
| Gemini | Yes | Yes |
|
|
127
|
-
|
|
|
174
|
+
| All agy Features | Yes (wraps agy) | Yes |
|
|
175
|
+
| Size | ~90 KB | 151 MB |
|
|
128
176
|
| npm Install | Yes | No |
|
|
177
|
+
| Auto-updates | Via npm | Built-in |
|
|
129
178
|
|
|
130
179
|
## Contributing
|
|
131
180
|
|
|
132
|
-
Pull requests welcome
|
|
181
|
+
Pull requests welcome at [github.com/vahapogut/free-antigravity-cli](https://github.com/vahapogut/free-antigravity-cli).
|
|
133
182
|
|
|
134
183
|
## License
|
|
135
184
|
|
|
136
|
-
Apache License 2.0 - see LICENSE
|
|
185
|
+
Apache License 2.0 - see [LICENSE](LICENSE).
|
|
137
186
|
|
|
138
187
|
## Author
|
|
139
188
|
|
|
140
|
-
**Vahap Ogut**
|
|
141
|
-
|
|
142
|
-
[GitHub](https://github.com/vahapogut)
|
|
189
|
+
**Vahap Ogut** - [GitHub](https://github.com/vahapogut)
|
package/dist/cli.js
CHANGED
|
@@ -35,182 +35,186 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
})();
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
37
|
/**
|
|
38
|
-
* Free Antigravity CLI -
|
|
39
|
-
*
|
|
38
|
+
* Free Antigravity CLI - Community Edition
|
|
39
|
+
* Wraps the official agy CLI with custom model support via a local proxy.
|
|
40
40
|
*/
|
|
41
|
-
const
|
|
41
|
+
const child_process_1 = require("child_process");
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
43
|
const fs = __importStar(require("fs"));
|
|
44
|
+
const os = __importStar(require("os"));
|
|
44
45
|
const config_1 = require("./config");
|
|
45
46
|
const proxy_1 = require("./proxy");
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
program
|
|
54
|
-
.command('chat')
|
|
55
|
-
.description('Start interactive chat (default command)')
|
|
56
|
-
.argument('[prompt]', 'One-shot prompt (non-interactive)')
|
|
57
|
-
.option('-m, --model <name>', 'Model to use')
|
|
58
|
-
.action(async (prompt, options) => {
|
|
59
|
-
if (prompt) {
|
|
60
|
-
// One-shot mode
|
|
61
|
-
console.log(`Sending to ${options?.model || 'default model'}...`);
|
|
62
|
-
await (0, chat_1.startChat)(options?.model);
|
|
63
|
-
// In a full implementation, would send the prompt and exit
|
|
64
|
-
console.log('One-shot mode: Use interactive chat for now.');
|
|
47
|
+
function getAgyBin() {
|
|
48
|
+
const locations = [
|
|
49
|
+
path.join(os.homedir(), 'AppData', 'Local', 'agy', 'bin', 'agy.exe'),
|
|
50
|
+
];
|
|
51
|
+
for (const loc of locations) {
|
|
52
|
+
if (fs.existsSync(loc))
|
|
53
|
+
return loc;
|
|
65
54
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const modelsCmd = program.command('models').description('Manage custom AI models');
|
|
72
|
-
modelsCmd
|
|
73
|
-
.command('list')
|
|
74
|
-
.description('List all configured models')
|
|
75
|
-
.action(() => {
|
|
76
|
-
const models = (0, config_1.listModels)();
|
|
77
|
-
if (models.length === 0) {
|
|
78
|
-
console.log('No models configured. Use "antigravity models add" to add one.');
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
console.log('\nConfigured Models:');
|
|
82
|
-
console.log('─'.repeat(60));
|
|
83
|
-
for (const m of models) {
|
|
84
|
-
const keyStatus = m.apiKey && m.apiKey !== 'none' ? '🔑' : '🔓';
|
|
85
|
-
console.log(` ${keyStatus} ${m.displayName || m.name}`);
|
|
86
|
-
console.log(` Provider: ${m.provider} | Model: ${m.externalModelName}`);
|
|
87
|
-
console.log(` URL: ${m.apiUrl}`);
|
|
88
|
-
console.log();
|
|
89
|
-
}
|
|
90
|
-
console.log(`${models.length} model(s) configured.`);
|
|
91
|
-
});
|
|
92
|
-
modelsCmd
|
|
93
|
-
.command('add')
|
|
94
|
-
.description('Add a new custom model (interactive wizard)')
|
|
95
|
-
.action(async () => {
|
|
96
|
-
const inquirer = require('inquirer');
|
|
97
|
-
console.log('\n Add Custom AI Model\n' + '─'.repeat(40));
|
|
98
|
-
const answers = await inquirer.prompt([
|
|
99
|
-
{ type: 'list', name: 'provider', message: 'Provider:', choices: ['openai', 'anthropic', 'google', 'ollama', 'openrouter', 'custom'] },
|
|
100
|
-
{ type: 'input', name: 'modelId', message: 'Model ID (e.g. gpt-4o):', validate: (v) => v.length > 0 },
|
|
101
|
-
{ type: 'input', name: 'displayName', message: 'Display name (optional):' },
|
|
102
|
-
{ type: 'password', name: 'apiKey', message: 'API Key:', mask: '*' },
|
|
103
|
-
{ type: 'input', name: 'apiUrl', message: 'API URL:', default: (ans) => {
|
|
104
|
-
const defaults = { openai: 'https://api.openai.com/v1/chat/completions', anthropic: 'https://api.anthropic.com/v1/messages', ollama: 'http://localhost:11434/v1/chat/completions', openrouter: 'https://openrouter.ai/api/v1/chat/completions', custom: 'https://api.together.xyz/v1' };
|
|
105
|
-
return ans.provider === 'google' ? `https://generativelanguage.googleapis.com/v1beta/models/${ans.modelId}:generateContent` : (defaults[ans.provider] || '');
|
|
106
|
-
} },
|
|
107
|
-
]);
|
|
108
|
-
const entry = {
|
|
109
|
-
name: 'models/' + answers.modelId,
|
|
110
|
-
displayName: answers.displayName || answers.modelId,
|
|
111
|
-
description: `${answers.displayName || answers.modelId} custom model via Free Antigravity CLI`,
|
|
112
|
-
provider: answers.provider,
|
|
113
|
-
apiKey: answers.apiKey || 'none',
|
|
114
|
-
apiUrl: answers.apiUrl,
|
|
115
|
-
externalModelName: answers.modelId,
|
|
116
|
-
};
|
|
117
|
-
const result = (0, config_1.addModel)(entry);
|
|
118
|
-
if (result.success) {
|
|
119
|
-
console.log(`\n Model "${entry.displayName}" added successfully!`);
|
|
120
|
-
console.log(' Restart the proxy or chat to use it.\n');
|
|
55
|
+
return path.join(os.homedir(), 'AppData', 'Local', 'agy', 'bin', 'agy.exe');
|
|
56
|
+
}
|
|
57
|
+
async function ensureProxy() {
|
|
58
|
+
try {
|
|
59
|
+
return await (0, proxy_1.startProxy)();
|
|
121
60
|
}
|
|
122
|
-
|
|
123
|
-
|
|
61
|
+
catch {
|
|
62
|
+
return (0, proxy_1.getProxyPort)() || 50999;
|
|
124
63
|
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.
|
|
128
|
-
.description('Remove a model')
|
|
129
|
-
.argument('<name>', 'Model name or display name')
|
|
130
|
-
.action((name) => {
|
|
131
|
-
const result = (0, config_1.removeModel)(name);
|
|
132
|
-
if (result.success)
|
|
133
|
-
console.log(`Model "${name}" removed.`);
|
|
134
|
-
else
|
|
135
|
-
console.error(`Failed: ${result.error}`);
|
|
136
|
-
});
|
|
137
|
-
modelsCmd
|
|
138
|
-
.command('import')
|
|
139
|
-
.description('Import models from Antigravity desktop custom_models.json')
|
|
140
|
-
.action(() => {
|
|
141
|
-
const desktopPath = path.join(require('os').homedir(), '.gemini', 'antigravity', 'custom_models.json');
|
|
142
|
-
if (!fs.existsSync(desktopPath)) {
|
|
143
|
-
console.log('Desktop custom_models.json not found at:', desktopPath);
|
|
64
|
+
}
|
|
65
|
+
function ensureAgyPatched(binPath) {
|
|
66
|
+
if (!fs.existsSync(binPath))
|
|
144
67
|
return;
|
|
145
|
-
}
|
|
146
68
|
try {
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
if (
|
|
151
|
-
|
|
69
|
+
const buf = fs.readFileSync(binPath);
|
|
70
|
+
const original = Buffer.from('https://daily-cloudcode-pa.googleapis.com');
|
|
71
|
+
const replacement = Buffer.from('http://localhost:50999/v1internal/xxxxxxx');
|
|
72
|
+
if (buf.includes(replacement))
|
|
73
|
+
return;
|
|
74
|
+
const idx = buf.indexOf(original);
|
|
75
|
+
if (idx === -1)
|
|
76
|
+
return;
|
|
77
|
+
replacement.copy(buf, idx);
|
|
78
|
+
fs.writeFileSync(binPath, buf);
|
|
79
|
+
console.log('[ok] agy binary patched for custom model support.');
|
|
80
|
+
}
|
|
81
|
+
catch { /* ignore */ }
|
|
82
|
+
}
|
|
83
|
+
async function startAndDelegate(agyArgs) {
|
|
84
|
+
if (agyArgs.length === 0)
|
|
85
|
+
agyArgs.push('--prompt-interactive');
|
|
86
|
+
const agyBin = getAgyBin();
|
|
87
|
+
if (!fs.existsSync(agyBin)) {
|
|
88
|
+
console.error(`\nagy CLI not found at: ${agyBin}`);
|
|
89
|
+
console.error('Install: curl -fsSL https://antigravity.google/cli/install.cmd | cmd');
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
ensureAgyPatched(agyBin);
|
|
93
|
+
process.stdout.write('Starting proxy... ');
|
|
94
|
+
const port = await ensureProxy();
|
|
95
|
+
console.log(`ready (port ${port})\n`);
|
|
96
|
+
const child = (0, child_process_1.spawn)(agyBin, agyArgs, { stdio: 'inherit', shell: true });
|
|
97
|
+
child.on('exit', async (code) => { await (0, proxy_1.stopProxy)(); process.exit(code || 0); });
|
|
98
|
+
process.on('SIGINT', async () => { child.kill(); await (0, proxy_1.stopProxy)(); process.exit(0); });
|
|
99
|
+
}
|
|
100
|
+
async function main() {
|
|
101
|
+
const args = process.argv.slice(2);
|
|
102
|
+
const cmd = args[0];
|
|
103
|
+
// --- Model management ---
|
|
104
|
+
if (cmd === 'models') {
|
|
105
|
+
const sub = args[1];
|
|
106
|
+
if (sub === 'list') {
|
|
107
|
+
const models = (0, config_1.listModels)();
|
|
108
|
+
if (models.length === 0) {
|
|
109
|
+
console.log('No models. Use "antigravity models add".');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
console.log('\nCustom Models:\n' + '='.repeat(50));
|
|
113
|
+
for (const m of models) {
|
|
114
|
+
console.log(` ${m.displayName || m.name}`);
|
|
115
|
+
console.log(` Provider: ${m.provider} | Model: ${m.externalModelName}`);
|
|
116
|
+
console.log(` URL: ${m.apiUrl}\n`);
|
|
117
|
+
}
|
|
152
118
|
return;
|
|
153
119
|
}
|
|
154
|
-
(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
120
|
+
if (sub === 'add') {
|
|
121
|
+
const inquirer = require('inquirer');
|
|
122
|
+
console.log('\n Add Custom AI Model\n' + '─'.repeat(40));
|
|
123
|
+
const answers = await inquirer.prompt([
|
|
124
|
+
{ type: 'list', name: 'provider', message: 'Provider:', choices: ['openai', 'anthropic', 'google', 'ollama', 'openrouter', 'custom'] },
|
|
125
|
+
{ type: 'input', name: 'modelId', message: 'Model ID (e.g. gpt-4o):', validate: (v) => v.length > 0 },
|
|
126
|
+
{ type: 'input', name: 'displayName', message: 'Display name:' },
|
|
127
|
+
{ type: 'password', name: 'apiKey', message: 'API Key:', mask: '*' },
|
|
128
|
+
{ type: 'input', name: 'apiUrl', message: 'API URL:', default: (a) => {
|
|
129
|
+
const d = { openai: 'https://api.openai.com/v1/chat/completions', anthropic: 'https://api.anthropic.com/v1/messages', ollama: 'http://localhost:11434/v1/chat/completions', openrouter: 'https://openrouter.ai/api/v1/chat/completions', custom: 'https://api.together.xyz/v1', google: `https://generativelanguage.googleapis.com/v1beta/models/${a.modelId}:generateContent` };
|
|
130
|
+
return d[a.provider] || '';
|
|
131
|
+
} },
|
|
132
|
+
]);
|
|
133
|
+
const r = (0, config_1.addModel)({ name: 'models/' + answers.modelId, displayName: answers.displayName || answers.modelId, description: '', provider: answers.provider, apiKey: answers.apiKey || 'none', apiUrl: answers.apiUrl, externalModelName: answers.modelId });
|
|
134
|
+
console.log(r.success ? `\nModel "${answers.displayName || answers.modelId}" added!\n` : `\nFailed: ${r.error}\n`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (sub === 'remove') {
|
|
138
|
+
const name = args[2];
|
|
139
|
+
if (!name) {
|
|
140
|
+
console.log('Usage: antigravity models remove <name>');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
(0, config_1.removeModel)(name);
|
|
144
|
+
console.log(`Model "${name}" removed.`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (sub === 'import') {
|
|
148
|
+
const desktopPath = path.join(os.homedir(), '.gemini', 'antigravity', 'custom_models.json');
|
|
149
|
+
if (!fs.existsSync(desktopPath)) {
|
|
150
|
+
console.log('Desktop Antigravity models not found.\nUse "antigravity models add" instead.');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const models = JSON.parse(fs.readFileSync(desktopPath, 'utf-8')).models || [];
|
|
155
|
+
if (models.length === 0) {
|
|
156
|
+
console.log('No models in desktop config.');
|
|
157
|
+
return;
|
|
163
158
|
}
|
|
164
|
-
|
|
159
|
+
(0, config_1.ensureConfigDir)();
|
|
160
|
+
const { decryptString } = require('./crypto');
|
|
161
|
+
const { saveModels } = require('./config');
|
|
162
|
+
const imported = [];
|
|
163
|
+
for (const m of models) {
|
|
164
|
+
let key = m.apiKey || 'none';
|
|
165
|
+
if (m.encrypted && key !== 'none') {
|
|
166
|
+
try {
|
|
167
|
+
key = decryptString(key);
|
|
168
|
+
}
|
|
169
|
+
catch { /* keep */ }
|
|
170
|
+
}
|
|
171
|
+
imported.push({ name: m.name, displayName: m.displayName, description: m.description, provider: m.provider, apiKey: key, apiUrl: m.apiUrl, externalModelName: m.externalModelName, allowUnauthorized: m.allowUnauthorized });
|
|
172
|
+
}
|
|
173
|
+
saveModels(imported);
|
|
174
|
+
console.log(`Imported ${imported.length} model(s). NOTE: API keys from desktop need to be re-entered via "antigravity models add".`);
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
console.error('Import failed:', e);
|
|
165
178
|
}
|
|
166
|
-
|
|
167
|
-
name: m.name, displayName: m.displayName, description: m.description,
|
|
168
|
-
provider: m.provider, apiKey, apiUrl: m.apiUrl,
|
|
169
|
-
externalModelName: m.externalModelName, allowUnauthorized: m.allowUnauthorized,
|
|
170
|
-
});
|
|
179
|
+
return;
|
|
171
180
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
saveModels(imported);
|
|
175
|
-
console.log(`Imported ${imported.length} model(s) from desktop Antigravity.`);
|
|
176
|
-
for (const m of imported)
|
|
177
|
-
console.log(` - ${m.displayName} (${m.provider})`);
|
|
181
|
+
console.log('Usage: antigravity models <list|add|remove|import>');
|
|
182
|
+
return;
|
|
178
183
|
}
|
|
179
|
-
|
|
180
|
-
|
|
184
|
+
// --- Info commands ---
|
|
185
|
+
if (cmd === 'configure') {
|
|
186
|
+
console.log(`Models file: ${path.join(os.homedir(), '.free-antigravity', 'models.json')}`);
|
|
187
|
+
console.log(`Models configured: ${(0, config_1.listModels)().length}`);
|
|
188
|
+
console.log(`Proxy: ${(0, proxy_1.getProxyPort)() ? `port ${(0, proxy_1.getProxyPort)()}` : 'not running'}`);
|
|
189
|
+
console.log(`agy binary: ${getAgyBin()}`);
|
|
190
|
+
return;
|
|
181
191
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
.command('proxy')
|
|
186
|
-
.description('Start the proxy server (for IDE integration)')
|
|
187
|
-
.action(async () => {
|
|
188
|
-
try {
|
|
189
|
-
const port = await (0, proxy_1.startProxy)();
|
|
190
|
-
console.log(`Proxy running on http://127.0.0.1:${port}`);
|
|
191
|
-
console.log('Press Ctrl+C to stop.');
|
|
192
|
-
// Keep alive
|
|
193
|
-
process.on('SIGINT', async () => { await (0, proxy_1.stopProxy)(); process.exit(0); });
|
|
192
|
+
if (cmd === 'version' || cmd === '--version' || cmd === '-V' || cmd === '-v') {
|
|
193
|
+
console.log('Free Antigravity CLI v1.0.0 (Community Edition)');
|
|
194
|
+
return;
|
|
194
195
|
}
|
|
195
|
-
|
|
196
|
-
console.
|
|
196
|
+
if (cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
197
|
+
console.log(`Free Antigravity CLI v1.0.0 - Community Edition
|
|
198
|
+
Wraps the official agy CLI with custom model support.
|
|
199
|
+
|
|
200
|
+
Commands:
|
|
201
|
+
(no args) Start interactive chat with custom model support
|
|
202
|
+
chat Same as above
|
|
203
|
+
models list List custom models
|
|
204
|
+
models add Add a custom model
|
|
205
|
+
models remove <name> Remove a custom model
|
|
206
|
+
models import Import models from desktop Antigravity
|
|
207
|
+
configure Show configuration
|
|
208
|
+
version Show version
|
|
209
|
+
help This help
|
|
210
|
+
|
|
211
|
+
Any other arguments are passed directly to agy CLI.`);
|
|
212
|
+
return;
|
|
197
213
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const configPath = require('./config').getModelsPath();
|
|
205
|
-
console.log('Free Antigravity CLI Configuration');
|
|
206
|
-
console.log('─'.repeat(40));
|
|
207
|
-
console.log(`Models file: ${configPath}`);
|
|
208
|
-
console.log(`Models: ${(0, config_1.listModels)().length} configured`);
|
|
209
|
-
console.log(`Proxy port: ${(0, proxy_1.getProxyPort)() || 'not running'}`);
|
|
210
|
-
});
|
|
211
|
-
// Default: chat if no command
|
|
212
|
-
program.action(() => {
|
|
213
|
-
(0, chat_1.startChat)();
|
|
214
|
-
});
|
|
215
|
-
program.parse(process.argv);
|
|
214
|
+
// --- Default: delegate to agy with proxy ---
|
|
215
|
+
if (cmd === 'chat')
|
|
216
|
+
args.shift();
|
|
217
|
+
await startAndDelegate(args);
|
|
218
|
+
}
|
|
219
|
+
main().catch(console.error);
|
|
216
220
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA;;;GAGG;AACH,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-antigravity-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Open-source community CLI for Antigravity - supports custom AI models (OpenAI, Anthropic, Ollama, OpenRouter, Google AI Studio) alongside Gemini",
|
|
5
5
|
"homepage": "https://github.com/vahapogut/free-antigravity-cli",
|
|
6
6
|
"author": {
|
package/src/cli.ts
CHANGED
|
@@ -1,184 +1,172 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Free Antigravity CLI -
|
|
4
|
-
*
|
|
3
|
+
* Free Antigravity CLI - Community Edition
|
|
4
|
+
* Wraps the official agy CLI with custom model support via a local proxy.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import * as fs from 'fs';
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
9
|
+
import * as os from 'os';
|
|
10
|
+
import { addModel, removeModel, listModels, ensureConfigDir, CustomModelEntry } from './config';
|
|
11
|
+
import { startProxy, getProxyPort, stopProxy } from './proxy';
|
|
12
|
+
|
|
13
|
+
function getAgyBin(): string {
|
|
14
|
+
const locations = [
|
|
15
|
+
path.join(os.homedir(), 'AppData', 'Local', 'agy', 'bin', 'agy.exe'),
|
|
16
|
+
];
|
|
17
|
+
for (const loc of locations) {
|
|
18
|
+
if (fs.existsSync(loc)) return loc;
|
|
19
|
+
}
|
|
20
|
+
return path.join(os.homedir(), 'AppData', 'Local', 'agy', 'bin', 'agy.exe');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function ensureProxy(): Promise<number> {
|
|
24
|
+
try { return await startProxy(); }
|
|
25
|
+
catch { return getProxyPort() || 50999; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ensureAgyPatched(binPath: string): void {
|
|
29
|
+
if (!fs.existsSync(binPath)) return;
|
|
30
|
+
try {
|
|
31
|
+
const buf = fs.readFileSync(binPath);
|
|
32
|
+
const original = Buffer.from('https://daily-cloudcode-pa.googleapis.com');
|
|
33
|
+
const replacement = Buffer.from('http://localhost:50999/v1internal/xxxxxxx');
|
|
34
|
+
if (buf.includes(replacement)) return;
|
|
35
|
+
const idx = buf.indexOf(original);
|
|
36
|
+
if (idx === -1) return;
|
|
37
|
+
replacement.copy(buf, idx);
|
|
38
|
+
fs.writeFileSync(binPath, buf);
|
|
39
|
+
console.log('[ok] agy binary patched for custom model support.');
|
|
40
|
+
} catch { /* ignore */ }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function startAndDelegate(agyArgs: string[]): Promise<void> {
|
|
44
|
+
if (agyArgs.length === 0) agyArgs.push('--prompt-interactive');
|
|
45
|
+
const agyBin = getAgyBin();
|
|
46
|
+
|
|
47
|
+
if (!fs.existsSync(agyBin)) {
|
|
48
|
+
console.error(`\nagy CLI not found at: ${agyBin}`);
|
|
49
|
+
console.error('Install: curl -fsSL https://antigravity.google/cli/install.cmd | cmd');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
ensureAgyPatched(agyBin);
|
|
54
|
+
process.stdout.write('Starting proxy... ');
|
|
55
|
+
const port = await ensureProxy();
|
|
56
|
+
console.log(`ready (port ${port})\n`);
|
|
57
|
+
|
|
58
|
+
const child = spawn(agyBin, agyArgs, { stdio: 'inherit', shell: true });
|
|
59
|
+
child.on('exit', async (code) => { await stopProxy(); process.exit(code || 0); });
|
|
60
|
+
process.on('SIGINT', async () => { child.kill(); await stopProxy(); process.exit(0); });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function main(): Promise<void> {
|
|
64
|
+
const args = process.argv.slice(2);
|
|
65
|
+
const cmd = args[0];
|
|
66
|
+
|
|
67
|
+
// --- Model management ---
|
|
68
|
+
if (cmd === 'models') {
|
|
69
|
+
const sub = args[1];
|
|
70
|
+
|
|
71
|
+
if (sub === 'list') {
|
|
72
|
+
const models = listModels();
|
|
73
|
+
if (models.length === 0) { console.log('No models. Use "antigravity models add".'); return; }
|
|
74
|
+
console.log('\nCustom Models:\n' + '='.repeat(50));
|
|
75
|
+
for (const m of models) {
|
|
76
|
+
console.log(` ${m.displayName || m.name}`);
|
|
77
|
+
console.log(` Provider: ${m.provider} | Model: ${m.externalModelName}`);
|
|
78
|
+
console.log(` URL: ${m.apiUrl}\n`);
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
36
81
|
}
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// --- Models command ---
|
|
40
82
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
83
|
+
if (sub === 'add') {
|
|
84
|
+
const inquirer = require('inquirer');
|
|
85
|
+
console.log('\n Add Custom AI Model\n' + '─'.repeat(40));
|
|
86
|
+
const answers = await inquirer.prompt([
|
|
87
|
+
{ type: 'list', name: 'provider', message: 'Provider:', choices: ['openai', 'anthropic', 'google', 'ollama', 'openrouter', 'custom'] },
|
|
88
|
+
{ type: 'input', name: 'modelId', message: 'Model ID (e.g. gpt-4o):', validate: (v: string) => v.length > 0 },
|
|
89
|
+
{ type: 'input', name: 'displayName', message: 'Display name:' },
|
|
90
|
+
{ type: 'password', name: 'apiKey', message: 'API Key:', mask: '*' },
|
|
91
|
+
{ type: 'input', name: 'apiUrl', message: 'API URL:', default: (a: any) => {
|
|
92
|
+
const d: Record<string, string> = { openai: 'https://api.openai.com/v1/chat/completions', anthropic: 'https://api.anthropic.com/v1/messages', ollama: 'http://localhost:11434/v1/chat/completions', openrouter: 'https://openrouter.ai/api/v1/chat/completions', custom: 'https://api.together.xyz/v1', google: `https://generativelanguage.googleapis.com/v1beta/models/${a.modelId}:generateContent` };
|
|
93
|
+
return d[a.provider] || '';
|
|
94
|
+
}},
|
|
95
|
+
]);
|
|
96
|
+
const r = addModel({ name: 'models/' + answers.modelId, displayName: answers.displayName || answers.modelId, description: '', provider: answers.provider, apiKey: answers.apiKey || 'none', apiUrl: answers.apiUrl, externalModelName: answers.modelId });
|
|
97
|
+
console.log(r.success ? `\nModel "${answers.displayName || answers.modelId}" added!\n` : `\nFailed: ${r.error}\n`);
|
|
50
98
|
return;
|
|
51
99
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
console.log(`
|
|
58
|
-
console.log(` URL: ${m.apiUrl}`);
|
|
59
|
-
console.log();
|
|
60
|
-
}
|
|
61
|
-
console.log(`${models.length} model(s) configured.`);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
modelsCmd
|
|
65
|
-
.command('add')
|
|
66
|
-
.description('Add a new custom model (interactive wizard)')
|
|
67
|
-
.action(async () => {
|
|
68
|
-
const inquirer = require('inquirer');
|
|
69
|
-
|
|
70
|
-
console.log('\n Add Custom AI Model\n' + '─'.repeat(40));
|
|
71
|
-
|
|
72
|
-
const answers = await inquirer.prompt([
|
|
73
|
-
{ type: 'list', name: 'provider', message: 'Provider:', choices: ['openai', 'anthropic', 'google', 'ollama', 'openrouter', 'custom'] },
|
|
74
|
-
{ type: 'input', name: 'modelId', message: 'Model ID (e.g. gpt-4o):', validate: (v: string) => v.length > 0 },
|
|
75
|
-
{ type: 'input', name: 'displayName', message: 'Display name (optional):' },
|
|
76
|
-
{ type: 'password', name: 'apiKey', message: 'API Key:', mask: '*' },
|
|
77
|
-
{ type: 'input', name: 'apiUrl', message: 'API URL:', default: (ans: any) => {
|
|
78
|
-
const defaults: Record<string, string> = { openai: 'https://api.openai.com/v1/chat/completions', anthropic: 'https://api.anthropic.com/v1/messages', ollama: 'http://localhost:11434/v1/chat/completions', openrouter: 'https://openrouter.ai/api/v1/chat/completions', custom: 'https://api.together.xyz/v1' };
|
|
79
|
-
return ans.provider === 'google' ? `https://generativelanguage.googleapis.com/v1beta/models/${ans.modelId}:generateContent` : (defaults[ans.provider] || '');
|
|
80
|
-
}},
|
|
81
|
-
]);
|
|
82
|
-
|
|
83
|
-
const entry: CustomModelEntry = {
|
|
84
|
-
name: 'models/' + answers.modelId,
|
|
85
|
-
displayName: answers.displayName || answers.modelId,
|
|
86
|
-
description: `${answers.displayName || answers.modelId} custom model via Free Antigravity CLI`,
|
|
87
|
-
provider: answers.provider,
|
|
88
|
-
apiKey: answers.apiKey || 'none',
|
|
89
|
-
apiUrl: answers.apiUrl,
|
|
90
|
-
externalModelName: answers.modelId,
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const result = addModel(entry);
|
|
94
|
-
if (result.success) {
|
|
95
|
-
console.log(`\n Model "${entry.displayName}" added successfully!`);
|
|
96
|
-
console.log(' Restart the proxy or chat to use it.\n');
|
|
97
|
-
} else {
|
|
98
|
-
console.error(`\n Failed: ${result.error}\n`);
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
modelsCmd
|
|
103
|
-
.command('remove')
|
|
104
|
-
.description('Remove a model')
|
|
105
|
-
.argument('<name>', 'Model name or display name')
|
|
106
|
-
.action((name: string) => {
|
|
107
|
-
const result = removeModel(name);
|
|
108
|
-
if (result.success) console.log(`Model "${name}" removed.`);
|
|
109
|
-
else console.error(`Failed: ${result.error}`);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
modelsCmd
|
|
113
|
-
.command('import')
|
|
114
|
-
.description('Import models from Antigravity desktop custom_models.json')
|
|
115
|
-
.action(() => {
|
|
116
|
-
const desktopPath = path.join(require('os').homedir(), '.gemini', 'antigravity', 'custom_models.json');
|
|
117
|
-
if (!fs.existsSync(desktopPath)) {
|
|
118
|
-
console.log('Desktop custom_models.json not found at:', desktopPath);
|
|
100
|
+
|
|
101
|
+
if (sub === 'remove') {
|
|
102
|
+
const name = args[2];
|
|
103
|
+
if (!name) { console.log('Usage: antigravity models remove <name>'); return; }
|
|
104
|
+
removeModel(name);
|
|
105
|
+
console.log(`Model "${name}" removed.`);
|
|
119
106
|
return;
|
|
120
107
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
try {
|
|
108
|
+
|
|
109
|
+
if (sub === 'import') {
|
|
110
|
+
const desktopPath = path.join(os.homedir(), '.gemini', 'antigravity', 'custom_models.json');
|
|
111
|
+
if (!fs.existsSync(desktopPath)) { console.log('Desktop Antigravity models not found.\nUse "antigravity models add" instead.'); return; }
|
|
112
|
+
try {
|
|
113
|
+
const models = (JSON.parse(fs.readFileSync(desktopPath, 'utf-8')) as { models?: any[] }).models || [];
|
|
114
|
+
if (models.length === 0) { console.log('No models in desktop config.'); return; }
|
|
115
|
+
ensureConfigDir();
|
|
116
|
+
const { decryptString } = require('./crypto');
|
|
117
|
+
const { saveModels } = require('./config');
|
|
118
|
+
const imported: CustomModelEntry[] = [];
|
|
119
|
+
for (const m of models) {
|
|
120
|
+
let key = m.apiKey || 'none';
|
|
121
|
+
if (m.encrypted && key !== 'none') { try { key = decryptString(key); } catch { /* keep */ } }
|
|
122
|
+
imported.push({ name: m.name, displayName: m.displayName, description: m.description, provider: m.provider, apiKey: key, apiUrl: m.apiUrl, externalModelName: m.externalModelName, allowUnauthorized: m.allowUnauthorized });
|
|
135
123
|
}
|
|
136
|
-
imported
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Default:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
124
|
+
saveModels(imported);
|
|
125
|
+
console.log(`Imported ${imported.length} model(s). NOTE: API keys from desktop need to be re-entered via "antigravity models add".`);
|
|
126
|
+
} catch (e) { console.error('Import failed:', e); }
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.log('Usage: antigravity models <list|add|remove|import>');
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// --- Info commands ---
|
|
135
|
+
if (cmd === 'configure') {
|
|
136
|
+
console.log(`Models file: ${path.join(os.homedir(), '.free-antigravity', 'models.json')}`);
|
|
137
|
+
console.log(`Models configured: ${listModels().length}`);
|
|
138
|
+
console.log(`Proxy: ${getProxyPort() ? `port ${getProxyPort()}` : 'not running'}`);
|
|
139
|
+
console.log(`agy binary: ${getAgyBin()}`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (cmd === 'version' || cmd === '--version' || cmd === '-V' || cmd === '-v') {
|
|
144
|
+
console.log('Free Antigravity CLI v1.0.0 (Community Edition)');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
149
|
+
console.log(`Free Antigravity CLI v1.0.0 - Community Edition
|
|
150
|
+
Wraps the official agy CLI with custom model support.
|
|
151
|
+
|
|
152
|
+
Commands:
|
|
153
|
+
(no args) Start interactive chat with custom model support
|
|
154
|
+
chat Same as above
|
|
155
|
+
models list List custom models
|
|
156
|
+
models add Add a custom model
|
|
157
|
+
models remove <name> Remove a custom model
|
|
158
|
+
models import Import models from desktop Antigravity
|
|
159
|
+
configure Show configuration
|
|
160
|
+
version Show version
|
|
161
|
+
help This help
|
|
162
|
+
|
|
163
|
+
Any other arguments are passed directly to agy CLI.`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// --- Default: delegate to agy with proxy ---
|
|
168
|
+
if (cmd === 'chat') args.shift();
|
|
169
|
+
await startAndDelegate(args);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
main().catch(console.error);
|
package/src/chat.ts
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Interactive chat REPL - streaming AI chat in the terminal.
|
|
3
|
-
*/
|
|
4
|
-
import * as readline from 'readline';
|
|
5
|
-
import * as http from 'http';
|
|
6
|
-
import { startProxy, getProxyPort, loadCustomModels, generateModelPlaceholderId, toSlug, CustomModel } from './proxy';
|
|
7
|
-
import { loadModels, CustomModelEntry } from './config';
|
|
8
|
-
|
|
9
|
-
function toProxyModel(m: CustomModelEntry): CustomModel {
|
|
10
|
-
return { ...m, displayName: m.displayName || m.name, description: m.description || '', _slug: undefined };
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
async function getModels(): Promise<string[]> {
|
|
14
|
-
const custom = loadModels();
|
|
15
|
-
return custom.map((m) => m.displayName || m.name);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async function selectModel(): Promise<string> {
|
|
19
|
-
const models = await getModels();
|
|
20
|
-
console.log('\nAvailable models:');
|
|
21
|
-
models.forEach((m, i) => console.log(` ${i + 1}. ${m}`));
|
|
22
|
-
console.log(' 0. Enter model name manually');
|
|
23
|
-
|
|
24
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
25
|
-
return new Promise((resolve) => {
|
|
26
|
-
rl.question('\nSelect model (number): ', (answer) => {
|
|
27
|
-
rl.close();
|
|
28
|
-
const idx = parseInt(answer, 10) - 1;
|
|
29
|
-
if (idx >= 0 && idx < models.length) resolve(models[idx]);
|
|
30
|
-
else resolve('');
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export async function startChat(model?: string): Promise<void> {
|
|
36
|
-
// Start proxy
|
|
37
|
-
let proxyPort: number;
|
|
38
|
-
try { proxyPort = await startProxy(); }
|
|
39
|
-
catch { console.error('Failed to start proxy. Is port 50999 in use?'); process.exit(1); }
|
|
40
|
-
|
|
41
|
-
console.log(`
|
|
42
|
-
╔══════════════════════════════════════╗
|
|
43
|
-
║ Free Antigravity CLI v1.0.0 ║
|
|
44
|
-
║ Community Edition ║
|
|
45
|
-
╚══════════════════════════════════════╝
|
|
46
|
-
Proxy: http://127.0.0.1:${proxyPort}
|
|
47
|
-
`);
|
|
48
|
-
|
|
49
|
-
if (!model) model = await selectModel();
|
|
50
|
-
if (!model) { console.log('No model selected. Exiting.'); process.exit(0); }
|
|
51
|
-
|
|
52
|
-
const customModels = loadModels();
|
|
53
|
-
const selectedEntry = customModels.find((m) => (m.displayName || m.name) === model);
|
|
54
|
-
if (!selectedEntry) {
|
|
55
|
-
console.log(`Model "${model}" not configured. Use "antigravity models add" first.`);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const selectedModel = toProxyModel(selectedEntry);
|
|
60
|
-
console.log(`Using: ${selectedModel.displayName} (${selectedModel.provider})`);
|
|
61
|
-
console.log('Type /help for commands, /quit to exit.\n');
|
|
62
|
-
|
|
63
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: '\n> ' });
|
|
64
|
-
|
|
65
|
-
let conversationHistory: { role: string; parts: { text?: string }[] }[] = [];
|
|
66
|
-
|
|
67
|
-
rl.on('line', async (line) => {
|
|
68
|
-
const input = line.trim();
|
|
69
|
-
|
|
70
|
-
if (input === '/quit' || input === '/exit') {
|
|
71
|
-
console.log('Goodbye!');
|
|
72
|
-
rl.close();
|
|
73
|
-
process.exit(0);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (input === '/help') {
|
|
77
|
-
console.log('Commands: /quit, /help, /clear (clear history), /model (switch model)');
|
|
78
|
-
rl.prompt();
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (input === '/clear') {
|
|
83
|
-
conversationHistory = [];
|
|
84
|
-
console.log('Conversation cleared.');
|
|
85
|
-
rl.prompt();
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (input === '/model') {
|
|
90
|
-
const newModel = await selectModel();
|
|
91
|
-
if (newModel) {
|
|
92
|
-
const m = customModels.find((x) => (x.displayName || x.name) === newModel);
|
|
93
|
-
if (m) { console.log(`Switched to ${m.displayName}`); }
|
|
94
|
-
}
|
|
95
|
-
rl.prompt();
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!input) { rl.prompt(); return; }
|
|
100
|
-
|
|
101
|
-
// Build Gemini request
|
|
102
|
-
const geminiBody = {
|
|
103
|
-
model: selectedModel.name,
|
|
104
|
-
contents: [
|
|
105
|
-
...conversationHistory,
|
|
106
|
-
{ role: 'user', parts: [{ text: input }] },
|
|
107
|
-
],
|
|
108
|
-
generationConfig: { temperature: 0.7, maxOutputTokens: 4096 },
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
// Send to proxy
|
|
112
|
-
const placeholderId = generateModelPlaceholderId(selectedModel);
|
|
113
|
-
const slug = toSlug(selectedModel);
|
|
114
|
-
|
|
115
|
-
const postData = JSON.stringify({
|
|
116
|
-
request: geminiBody,
|
|
117
|
-
model: slug,
|
|
118
|
-
project: 'free-antigravity-cli',
|
|
119
|
-
requestId: Date.now().toString(),
|
|
120
|
-
requestType: 'CHAT',
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
const options: http.RequestOptions = {
|
|
124
|
-
hostname: '127.0.0.1',
|
|
125
|
-
port: proxyPort,
|
|
126
|
-
path: '/v1internal:streamGenerateContent?alt=sse',
|
|
127
|
-
method: 'POST',
|
|
128
|
-
headers: {
|
|
129
|
-
'Content-Type': 'application/json',
|
|
130
|
-
'Content-Length': Buffer.byteLength(postData),
|
|
131
|
-
'Accept': 'text/event-stream',
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const req = http.request(options, (res) => {
|
|
136
|
-
let buffer = '';
|
|
137
|
-
let fullResponse = '';
|
|
138
|
-
|
|
139
|
-
res.on('data', (chunk: Buffer) => {
|
|
140
|
-
buffer += chunk.toString('utf-8');
|
|
141
|
-
const lines = buffer.split('\n');
|
|
142
|
-
buffer = lines.pop() || '';
|
|
143
|
-
|
|
144
|
-
for (const line of lines) {
|
|
145
|
-
const trimmed = line.trim();
|
|
146
|
-
if (!trimmed || !trimmed.startsWith('data: ')) continue;
|
|
147
|
-
try {
|
|
148
|
-
const data = JSON.parse(trimmed.substring(6));
|
|
149
|
-
const candidates = data.response?.candidates || [];
|
|
150
|
-
for (const c of candidates) {
|
|
151
|
-
const parts = c.content?.parts || [];
|
|
152
|
-
for (const p of parts) {
|
|
153
|
-
if (p.text && !p.thought) {
|
|
154
|
-
process.stdout.write(p.text);
|
|
155
|
-
fullResponse += p.text;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
} catch { /* partial chunk */ }
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
res.on('end', () => {
|
|
164
|
-
console.log();
|
|
165
|
-
conversationHistory.push({ role: 'user', parts: [{ text: input }] });
|
|
166
|
-
conversationHistory.push({ role: 'model', parts: [{ text: fullResponse }] });
|
|
167
|
-
// Keep history manageable
|
|
168
|
-
if (conversationHistory.length > 20) conversationHistory = conversationHistory.slice(-20);
|
|
169
|
-
rl.prompt();
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
req.on('error', (err) => {
|
|
174
|
-
console.error(`\nConnection error: ${err.message}`);
|
|
175
|
-
console.error('Is the proxy running? Try: antigravity proxy');
|
|
176
|
-
rl.prompt();
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
req.write(postData);
|
|
180
|
-
req.end();
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
rl.prompt();
|
|
184
|
-
}
|