Skip to content

Commit 58e704b

Browse files
committed
feat: add --config and --crt flags for external config and CA certs
--config <path>: - Load opencode.json from external path instead of ./opencode.json - Useful for shared team configs or configs outside the project dir - Shown in detection output: 'Existing config: yes (/path/to/config.json)' - External configs mounted read-only into Docker sandbox --crt <path>: - Set NODE_EXTRA_CA_CERTS for custom CA certificates - Supports .crt and .pem files - Certificate mounted into Docker sandbox at /certs/ (read-only) - Validates file exists before proceeding Docker sandbox improvements: - Volume mounts now include cert and external config when provided - All flags (--skipSSL, --crt, --config) automatically forwarded Updated --help and README with flag documentation and examples
1 parent 770a05f commit 58e704b

3 files changed

Lines changed: 126 additions & 30 deletions

File tree

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,27 @@ awesome-opencode configure mcp # Add/remove MCP servers
429429
awesome-opencode --help # Show help
430430
```
431431

432+
### Flags
433+
434+
```bash
435+
awesome-opencode --config <path> # Use external opencode.json
436+
awesome-opencode --crt <path> # Custom CA certificate (.crt/.pem)
437+
awesome-opencode --skipSSL # Disable TLS verification
438+
```
439+
440+
Flags can be combined with subcommands and with each other:
441+
442+
```bash
443+
# Corporate setup: custom cert + external config
444+
awesome-opencode --crt /etc/ssl/corporate-ca.crt --config ~/shared/opencode.json
445+
446+
# Behind proxy with self-signed cert
447+
awesome-opencode --skipSSL configure mcp
448+
449+
# All flags are forwarded into Docker sandbox automatically
450+
awesome-opencode --crt /certs/ca.pem --skipSSL
451+
```
452+
432453
### Sandboxed Mode (Docker)
433454

434455
Run OpenCode in an isolated Docker container where only the current project is accessible:

cli-tool/src/index.js

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,69 @@
11
#!/usr/bin/env node
22

3+
import path from 'path';
4+
import fs from 'fs';
35
import { intro, checkExistingSetup, detectProject, promptAgents, promptSkills, promptModels, promptMcp, promptMcpSearch, promptCostControl, generateFiles, promptAgentsMd, outro, launchOpenCode } from './setup.js';
46

5-
const args = process.argv.slice(2);
7+
// ── Parse flags ─────────────────────────────────────────────────────────────
8+
9+
const rawArgs = process.argv.slice(2);
10+
const flags = {};
11+
const positional = [];
12+
13+
for (let i = 0; i < rawArgs.length; i++) {
14+
const arg = rawArgs[i];
15+
if (arg === '--skipSSL' || arg === '--skip-ssl') {
16+
flags.skipSSL = true;
17+
} else if (arg === '--config' && rawArgs[i + 1]) {
18+
flags.configPath = path.resolve(rawArgs[++i]);
19+
} else if (arg === '--crt' && rawArgs[i + 1]) {
20+
flags.crtPath = path.resolve(rawArgs[++i]);
21+
} else if (arg === '--help' || arg === '-h') {
22+
flags.help = true;
23+
} else {
24+
positional.push(arg);
25+
}
26+
}
27+
28+
const command = positional[0] || '';
29+
const subcommand = positional[1] || '';
30+
31+
// ── Apply flags ─────────────────────────────────────────────────────────────
632

7-
// ── Handle --skipSSL flag (can appear anywhere in args) ───────────────────
8-
if (args.includes('--skipSSL') || args.includes('--skip-ssl')) {
33+
if (flags.skipSSL) {
934
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
1035
}
1136

12-
const filteredArgs = args.filter(a => a !== '--skipSSL' && a !== '--skip-ssl');
13-
const command = filteredArgs[0] || '';
14-
const subcommand = filteredArgs[1] || '';
37+
if (flags.crtPath) {
38+
if (!fs.existsSync(flags.crtPath)) {
39+
console.error(`Error: Certificate file not found: ${flags.crtPath}`);
40+
process.exit(1);
41+
}
42+
process.env.NODE_EXTRA_CA_CERTS = flags.crtPath;
43+
}
44+
45+
if (flags.configPath) {
46+
if (!fs.existsSync(flags.configPath)) {
47+
console.error(`Error: Config file not found: ${flags.configPath}`);
48+
process.exit(1);
49+
}
50+
}
51+
52+
// ── Main ────────────────────────────────────────────────────────────────────
1553

1654
async function main() {
1755
try {
18-
// ── Handle --help / -h ────────────────────────────────────────────────
19-
if (command === '--help' || command === '-h') {
56+
if (flags.help) {
2057
showHelp();
2158
return;
2259
}
2360

24-
// ── Handle subcommands ────────────────────────────────────────────────
2561
if (command === 'configure') {
2662
intro();
2763
await handleConfigure(subcommand);
2864
return;
2965
}
3066

31-
// ── Default: full setup or re-run ─────────────────────────────────────
3267
intro();
3368

3469
const existing = checkExistingSetup();
@@ -59,17 +94,22 @@ function showHelp() {
5994
awesome-opencode configure skills Add/remove skills
6095
awesome-opencode configure models Change model strategy
6196
awesome-opencode configure mcp Add/remove MCP servers
62-
awesome-opencode --help Show this help
6397
6498
Flags:
65-
--skipSSL Set NODE_TLS_REJECT_UNAUTHORIZED=0
66-
(useful behind corporate proxies)
99+
--help, -h Show this help
100+
--config <path> Path to external opencode.json
101+
(default: ./opencode.json)
102+
--crt <path> Path to custom CA certificate (.crt/.pem)
103+
Sets NODE_EXTRA_CA_CERTS for TLS
104+
--skipSSL Disable TLS certificate verification
105+
Sets NODE_TLS_REJECT_UNAUTHORIZED=0
67106
68107
Examples:
69108
npx @weisser-dev/awesome-opencode
70-
awesome-opencode configure mcp
71-
awesome-opencode --skipSSL
109+
awesome-opencode --config ~/shared/opencode.json
110+
awesome-opencode --crt /etc/ssl/corporate-ca.crt
72111
awesome-opencode --skipSSL configure models
112+
awesome-opencode --config /mnt/config/opencode.json --crt /mnt/certs/ca.pem
73113
74114
Docs: https://github.com/weisser-dev/awesome-opencode
75115
`);
@@ -93,6 +133,15 @@ async function handleExistingSetup(existing) {
93133
if (existing.modelStrategy) {
94134
console.log(chalk.gray(` Models: ${existing.modelStrategy}`));
95135
}
136+
if (flags.configPath) {
137+
console.log(chalk.gray(` Config: ${flags.configPath}`));
138+
}
139+
if (flags.crtPath) {
140+
console.log(chalk.gray(` CA cert: ${flags.crtPath}`));
141+
}
142+
if (flags.skipSSL) {
143+
console.log(chalk.yellow(' SSL: verification disabled (--skipSSL)'));
144+
}
96145
console.log('');
97146

98147
// Check if Docker is available for sandboxed option
@@ -124,10 +173,10 @@ async function handleExistingSetup(existing) {
124173

125174
switch (action) {
126175
case 'start':
127-
await launchOpenCode({ forceSandbox: false });
176+
await launchOpenCode({ forceSandbox: false, flags });
128177
break;
129178
case 'sandbox':
130-
await launchOpenCode({ forceSandbox: true });
179+
await launchOpenCode({ forceSandbox: true, flags });
131180
break;
132181
case 'reconfigure':
133182
await runFullSetup();
@@ -149,7 +198,7 @@ async function handleExistingSetup(existing) {
149198
}
150199

151200
async function handleConfigure(what) {
152-
const project = await detectProject();
201+
const project = await detectProject({ configPath: flags.configPath });
153202

154203
switch (what) {
155204
case 'agents': {
@@ -175,7 +224,6 @@ async function handleConfigure(what) {
175224
break;
176225
}
177226
default: {
178-
// No subcommand: full reconfigure
179227
await runFullSetup();
180228
}
181229
}
@@ -184,22 +232,20 @@ async function handleConfigure(what) {
184232
}
185233

186234
async function runFullSetup() {
187-
const project = await detectProject();
235+
const project = await detectProject({ configPath: flags.configPath });
188236
const agents = await promptAgents(project);
189237
const skills = await promptSkills(project);
190238
const modelConfig = await promptModels(project);
191239

192-
// MCP: curated list + optional mcp.so search
193240
const mcpConfig = await promptMcp(project);
194241
const mcpSearchResults = await promptMcpSearch(mcpConfig);
195242

196-
// Cost & context control: step limits per agent
197243
const costControl = await promptCostControl(agents);
198244

199245
await generateFiles({ project, agents, skills, modelConfig, mcpConfig, mcpSearchResults, costControl });
200246
await promptAgentsMd({ project, agents, skills, modelConfig });
201247
outro();
202-
await launchOpenCode();
248+
await launchOpenCode({ flags });
203249
}
204250

205251
main();

cli-tool/src/setup.js

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ function detectPackageManager() {
335335
return null;
336336
}
337337

338-
export async function detectProject() {
338+
export async function detectProject({ configPath: externalConfigPath } = {}) {
339339
const spinner = ora('Analyzing project...').start();
340340

341341
const project = {
@@ -349,10 +349,12 @@ export async function detectProject() {
349349
existingAgents: [],
350350
existingMcps: [],
351351
existingProviders: [],
352+
configPath: null,
352353
};
353354

354-
// Detect existing opencode.json
355-
const configPath = path.join(CWD, 'opencode.json');
355+
// Detect existing opencode.json (external path takes priority)
356+
const configPath = externalConfigPath || path.join(CWD, 'opencode.json');
357+
project.configPath = configPath;
356358
if (fs.existsSync(configPath)) {
357359
project.hasOpenCodeConfig = true;
358360
try {
@@ -466,7 +468,10 @@ export async function detectProject() {
466468
if (project.packageManager) {
467469
console.log(chalk.gray(` Package manager: ${project.packageManager}`));
468470
}
469-
console.log(chalk.gray(` Existing config: ${project.hasOpenCodeConfig ? 'yes' : 'no'}`));
471+
const configLabel = project.hasOpenCodeConfig
472+
? (externalConfigPath ? `yes (${externalConfigPath})` : 'yes')
473+
: 'no';
474+
console.log(chalk.gray(` Existing config: ${configLabel}`));
470475
if (project.existingProviders.length > 0) {
471476
console.log(chalk.gray(` Custom providers: ${project.existingProviders.join(', ')}`));
472477
}
@@ -2286,7 +2291,7 @@ const PROVIDER_ENV_CONFIGS = [
22862291
},
22872292
];
22882293

2289-
export async function launchOpenCode({ forceSandbox } = {}) {
2294+
export async function launchOpenCode({ forceSandbox, flags = {} } = {}) {
22902295
const { input } = await import('@inquirer/prompts');
22912296

22922297
// ── Step 1: Sandbox question ──────────────────────────────────────────────
@@ -2436,12 +2441,35 @@ export async function launchOpenCode({ forceSandbox } = {}) {
24362441
}
24372442

24382443
// Build Docker command
2444+
const volumeFlags = ['-v "$PWD":"$PWD"'];
2445+
24392446
// If NODE_TLS_REJECT_UNAUTHORIZED=0 is set (via --skipSSL), pass it into the container
24402447
if (process.env.NODE_TLS_REJECT_UNAUTHORIZED === '0') {
24412448
envFlags.push('-e NODE_TLS_REJECT_UNAUTHORIZED=0');
24422449
}
2450+
2451+
// If --crt is set, mount the cert into the container and set NODE_EXTRA_CA_CERTS
2452+
if (flags.crtPath && fs.existsSync(flags.crtPath)) {
2453+
const certDir = path.dirname(flags.crtPath);
2454+
const certFile = path.basename(flags.crtPath);
2455+
const containerCertPath = `/certs/${certFile}`;
2456+
volumeFlags.push(`-v "${flags.crtPath}":"${containerCertPath}":ro`);
2457+
envFlags.push(`-e NODE_EXTRA_CA_CERTS=${containerCertPath}`);
2458+
}
2459+
2460+
// If --config points to a file outside $PWD, mount it into the container
2461+
if (flags.configPath && fs.existsSync(flags.configPath)) {
2462+
const configResolved = path.resolve(flags.configPath);
2463+
const cwd = process.cwd();
2464+
if (!configResolved.startsWith(cwd)) {
2465+
// External config -- mount it read-only
2466+
volumeFlags.push(`-v "${configResolved}":"${configResolved}":ro`);
2467+
}
2468+
}
2469+
2470+
const volumeString = volumeFlags.join(' \\\n ');
24432471
const envString = envFlags.length > 0 ? ' \\\n ' + envFlags.join(' \\\n ') : '';
2444-
const dockerCmd = `docker run -it --rm \\\n -v "$PWD":"$PWD" \\\n -w "$PWD"${envString} \\\n node:22 \\\n bash -c "npm i -g opencode-ai && opencode"`;
2472+
const dockerCmd = `docker run -it --rm \\\n ${volumeString} \\\n -w "$PWD"${envString} \\\n node:22 \\\n bash -c "npm i -g opencode-ai && opencode"`;
24452473

24462474
console.log('');
24472475
console.log(chalk.bold(' Docker command:'));
@@ -2472,7 +2500,8 @@ export async function launchOpenCode({ forceSandbox } = {}) {
24722500

24732501
// Build the actual command as a single string for shell execution
24742502
const shellEnv = envFlags.join(' ');
2475-
const shellCmd = `docker run -it --rm -v "$PWD":"$PWD" -w "$PWD" ${shellEnv} node:22 bash -c "npm i -g opencode-ai && opencode"`;
2503+
const shellVolumes = volumeFlags.join(' ');
2504+
const shellCmd = `docker run -it --rm ${shellVolumes} -w "$PWD" ${shellEnv} node:22 bash -c "npm i -g opencode-ai && opencode"`;
24762505

24772506
const child = spawn('sh', ['-c', shellCmd], {
24782507
stdio: 'inherit',

0 commit comments

Comments
 (0)