opencode-azure-setup 1.0.2 → 1.0.4
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/index.js +158 -41
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -37,6 +37,36 @@ function getConfigPath() {
|
|
|
37
37
|
return path.join(os.homedir(), '.config', 'opencode', 'opencode.json');
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
// Load existing config if it exists
|
|
41
|
+
function loadExistingConfig() {
|
|
42
|
+
const configPath = getConfigPath();
|
|
43
|
+
try {
|
|
44
|
+
if (fs.existsSync(configPath)) {
|
|
45
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
46
|
+
return JSON.parse(content);
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// Config doesn't exist or is invalid
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Extract existing Azure settings from config
|
|
55
|
+
function getExistingAzureSettings(config) {
|
|
56
|
+
if (!config?.provider?.azure?.options) return null;
|
|
57
|
+
|
|
58
|
+
const azure = config.provider.azure;
|
|
59
|
+
const opts = azure.options;
|
|
60
|
+
const modelName = Object.keys(azure.models || {})[0] || 'model-router';
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
baseUrl: opts.baseURL || '',
|
|
64
|
+
apiKey: opts.apiKey || '',
|
|
65
|
+
deployment: modelName,
|
|
66
|
+
apiVersion: opts.apiVersion || '2025-01-01-preview',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
40
70
|
const rl = readline.createInterface({
|
|
41
71
|
input: process.stdin,
|
|
42
72
|
output: process.stdout,
|
|
@@ -49,9 +79,15 @@ function ask(question, defaultValue = '') {
|
|
|
49
79
|
});
|
|
50
80
|
}
|
|
51
81
|
|
|
52
|
-
function askPassword(question) {
|
|
82
|
+
function askPassword(question, existingValue = '') {
|
|
53
83
|
return new Promise((resolve) => {
|
|
54
|
-
|
|
84
|
+
if (existingValue) {
|
|
85
|
+
const masked = existingValue.slice(0, 4) + '...' + existingValue.slice(-4);
|
|
86
|
+
process.stdout.write(`${question} [${masked}]: `);
|
|
87
|
+
} else {
|
|
88
|
+
process.stdout.write(`${question}: `);
|
|
89
|
+
}
|
|
90
|
+
|
|
55
91
|
if (process.stdin.isTTY) {
|
|
56
92
|
const stdin = process.stdin;
|
|
57
93
|
stdin.setRawMode(true);
|
|
@@ -63,7 +99,8 @@ function askPassword(question) {
|
|
|
63
99
|
stdin.setRawMode(false);
|
|
64
100
|
stdin.removeListener('data', onData);
|
|
65
101
|
console.log();
|
|
66
|
-
|
|
102
|
+
// If user just pressed Enter and there's an existing value, use it
|
|
103
|
+
resolve(password || existingValue);
|
|
67
104
|
} else if (char === '\u0003') {
|
|
68
105
|
process.exit();
|
|
69
106
|
} else if (char === '\u007F' || char === '\b') {
|
|
@@ -74,7 +111,7 @@ function askPassword(question) {
|
|
|
74
111
|
};
|
|
75
112
|
stdin.on('data', onData);
|
|
76
113
|
} else {
|
|
77
|
-
rl.question('', resolve);
|
|
114
|
+
rl.question('', (answer) => resolve(answer || existingValue));
|
|
78
115
|
}
|
|
79
116
|
});
|
|
80
117
|
}
|
|
@@ -193,40 +230,77 @@ async function main() {
|
|
|
193
230
|
console.log('─'.repeat(40));
|
|
194
231
|
console.log();
|
|
195
232
|
|
|
233
|
+
// Load existing config
|
|
234
|
+
const existingConfig = loadExistingConfig();
|
|
235
|
+
const existingAzure = getExistingAzureSettings(existingConfig);
|
|
236
|
+
|
|
196
237
|
// Fetch latest defaults (non-blocking, falls back to hardcoded)
|
|
197
238
|
const defaults = await fetchDefaults();
|
|
198
239
|
|
|
240
|
+
// If existing config found, show current values
|
|
241
|
+
if (existingAzure && existingAzure.baseUrl) {
|
|
242
|
+
console.log(colors.green + '✓ Existing configuration found' + colors.reset);
|
|
243
|
+
console.log(colors.dim + ' Press Enter to keep current values, or type new ones' + colors.reset);
|
|
244
|
+
console.log();
|
|
245
|
+
}
|
|
246
|
+
|
|
199
247
|
// Endpoint - accepts full URL or just the base
|
|
200
|
-
|
|
201
|
-
console.log(colors.dim + 'Tip: You can paste the full URL from Azure Portal - we\'ll extract what we need' + colors.reset);
|
|
202
|
-
console.log();
|
|
203
|
-
const rawEndpoint = await ask('Endpoint');
|
|
248
|
+
let baseUrl, deployment, apiVersion;
|
|
204
249
|
|
|
205
|
-
if (
|
|
206
|
-
console.log(
|
|
207
|
-
|
|
208
|
-
|
|
250
|
+
if (existingAzure?.baseUrl) {
|
|
251
|
+
console.log('Azure OpenAI Endpoint');
|
|
252
|
+
const rawEndpoint = await ask('Endpoint', existingAzure.baseUrl);
|
|
253
|
+
|
|
254
|
+
if (rawEndpoint === existingAzure.baseUrl) {
|
|
255
|
+
// User kept existing - use existing parsed values
|
|
256
|
+
baseUrl = existingAzure.baseUrl;
|
|
257
|
+
deployment = existingAzure.deployment;
|
|
258
|
+
apiVersion = existingAzure.apiVersion;
|
|
259
|
+
} else {
|
|
260
|
+
// User entered new value - parse it
|
|
261
|
+
const parsed = parseAzureEndpoint(rawEndpoint, defaults);
|
|
262
|
+
baseUrl = parsed.baseUrl;
|
|
263
|
+
deployment = parsed.deployment;
|
|
264
|
+
apiVersion = parsed.apiVersion;
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
console.log('Paste your Azure OpenAI endpoint');
|
|
268
|
+
console.log(colors.dim + 'Tip: You can paste the full URL from Azure Portal - we\'ll extract what we need' + colors.reset);
|
|
269
|
+
console.log();
|
|
270
|
+
const rawEndpoint = await ask('Endpoint');
|
|
209
271
|
|
|
210
|
-
|
|
211
|
-
|
|
272
|
+
if (!rawEndpoint) {
|
|
273
|
+
console.log(colors.red + 'Endpoint is required' + colors.reset);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const parsed = parseAzureEndpoint(rawEndpoint, defaults);
|
|
278
|
+
baseUrl = parsed.baseUrl;
|
|
279
|
+
deployment = parsed.deployment;
|
|
280
|
+
apiVersion = parsed.apiVersion;
|
|
281
|
+
}
|
|
212
282
|
|
|
213
283
|
// API Key
|
|
214
284
|
console.log();
|
|
215
|
-
const apiKey = await askPassword('API Key');
|
|
285
|
+
const apiKey = await askPassword('API Key', existingAzure?.apiKey || '');
|
|
216
286
|
if (!apiKey) {
|
|
217
287
|
console.log(colors.red + 'API Key is required' + colors.reset);
|
|
218
288
|
process.exit(1);
|
|
219
289
|
}
|
|
220
290
|
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
291
|
+
// Deployment (only ask if not using existing)
|
|
292
|
+
if (existingAzure?.deployment && deployment === existingAzure.deployment) {
|
|
293
|
+
// Keep existing
|
|
294
|
+
} else {
|
|
295
|
+
console.log();
|
|
296
|
+
deployment = await ask('Deployment name', deployment);
|
|
297
|
+
}
|
|
224
298
|
|
|
225
299
|
console.log();
|
|
226
300
|
console.log(colors.blue + 'Testing connection...' + colors.reset);
|
|
227
|
-
console.log(colors.dim + ` ${
|
|
301
|
+
console.log(colors.dim + ` ${baseUrl}/deployments/${deployment}` + colors.reset);
|
|
228
302
|
|
|
229
|
-
let result = await testConnection(
|
|
303
|
+
let result = await testConnection(baseUrl, apiKey, deployment, apiVersion);
|
|
230
304
|
|
|
231
305
|
if (result.ok) {
|
|
232
306
|
console.log(colors.green + '✓ Connection successful!' + colors.reset);
|
|
@@ -249,7 +323,7 @@ async function main() {
|
|
|
249
323
|
|
|
250
324
|
console.log();
|
|
251
325
|
console.log(colors.blue + 'Retrying...' + colors.reset);
|
|
252
|
-
result = await testConnection(
|
|
326
|
+
result = await testConnection(baseUrl, apiKey, deployment, apiVersion);
|
|
253
327
|
|
|
254
328
|
if (result.ok) {
|
|
255
329
|
console.log(colors.green + '✓ Connection successful!' + colors.reset);
|
|
@@ -260,38 +334,81 @@ async function main() {
|
|
|
260
334
|
}
|
|
261
335
|
}
|
|
262
336
|
|
|
263
|
-
// Create config
|
|
337
|
+
// Create config - preserve existing settings like agents, permissions
|
|
264
338
|
const configPath = getConfigPath();
|
|
265
339
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
266
340
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
341
|
+
// Start with existing config or empty object
|
|
342
|
+
const config = existingConfig || {};
|
|
343
|
+
|
|
344
|
+
// Update schema and model
|
|
345
|
+
config.$schema = 'https://opencode.ai/config.json';
|
|
346
|
+
config.model = `azure/${deployment}`;
|
|
347
|
+
|
|
348
|
+
// Update Azure provider settings
|
|
349
|
+
config.provider = config.provider || {};
|
|
350
|
+
config.provider.azure = {
|
|
351
|
+
npm: '@ai-sdk/azure',
|
|
352
|
+
name: 'Azure OpenAI',
|
|
353
|
+
options: {
|
|
354
|
+
baseURL: baseUrl,
|
|
355
|
+
apiKey: apiKey,
|
|
356
|
+
useDeploymentBasedUrls: true,
|
|
357
|
+
apiVersion: apiVersion,
|
|
358
|
+
},
|
|
359
|
+
models: {
|
|
360
|
+
[deployment]: {
|
|
361
|
+
name: deployment,
|
|
362
|
+
limit: { context: 200000, output: 16384 },
|
|
286
363
|
},
|
|
287
364
|
},
|
|
288
365
|
};
|
|
289
366
|
|
|
367
|
+
// Install and configure MCP Marketplace
|
|
368
|
+
console.log();
|
|
369
|
+
console.log(colors.blue + 'Setting up MCP Marketplace...' + colors.reset);
|
|
370
|
+
|
|
371
|
+
const mcpDir = path.join(os.homedir(), '.config', 'opencode', 'mcps');
|
|
372
|
+
fs.mkdirSync(mcpDir, { recursive: true });
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
const { execFileSync } = await import('child_process');
|
|
376
|
+
|
|
377
|
+
// Install mcp-marketplace package globally
|
|
378
|
+
execFileSync('npm', ['install', '-g', 'opencode-mcp-marketplace'], { stdio: 'pipe' });
|
|
379
|
+
|
|
380
|
+
// Find the installed package path
|
|
381
|
+
const npmRoot = execFileSync('npm', ['root', '-g'], { encoding: 'utf-8' }).trim();
|
|
382
|
+
const mcpPath = path.join(npmRoot, 'opencode-mcp-marketplace', 'dist', 'index.js');
|
|
383
|
+
|
|
384
|
+
// Add to config
|
|
385
|
+
config.mcp = config.mcp || {};
|
|
386
|
+
config.mcp['mcp-marketplace'] = {
|
|
387
|
+
command: 'node',
|
|
388
|
+
args: [mcpPath],
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
console.log(colors.green + '✓ MCP Marketplace installed!' + colors.reset);
|
|
392
|
+
} catch (e) {
|
|
393
|
+
console.log(colors.yellow + '⚠ MCP Marketplace install skipped (npm not available or failed)' + colors.reset);
|
|
394
|
+
}
|
|
395
|
+
|
|
290
396
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
291
397
|
|
|
292
398
|
console.log();
|
|
293
399
|
console.log(colors.green + '✓ Configuration saved!' + colors.reset);
|
|
294
400
|
console.log(colors.dim + ` ${configPath}` + colors.reset);
|
|
401
|
+
|
|
402
|
+
// Show what was preserved
|
|
403
|
+
if (existingConfig) {
|
|
404
|
+
const preserved = [];
|
|
405
|
+
if (existingConfig.agent) preserved.push('agents');
|
|
406
|
+
if (existingConfig.permission) preserved.push('permissions');
|
|
407
|
+
if (preserved.length > 0) {
|
|
408
|
+
console.log(colors.dim + ` Preserved: ${preserved.join(', ')}` + colors.reset);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
295
412
|
console.log();
|
|
296
413
|
console.log('─'.repeat(40));
|
|
297
414
|
console.log(colors.green + 'You\'re all set! Run:' + colors.reset);
|