free-antigravity-cli 1.0.0 → 1.0.1
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/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/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.1",
|
|
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
|
-
}
|