@gengjiawen/os-init 1.14.1 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +11 -0
- package/bin/bin.js +32 -3
- package/build/all-agents.d.ts +3 -0
- package/build/all-agents.js +4 -1
- package/build/claude-code.js +4 -1
- package/build/codex.js +4 -1
- package/build/gemini-cli.js +4 -1
- package/build/index.d.ts +1 -0
- package/build/index.js +4 -1
- package/build/opencode.d.ts +4 -0
- package/build/opencode.js +66 -0
- package/build/utils.d.ts +3 -0
- package/build/utils.js +4 -0
- package/libs/all-agents.test.ts +50 -0
- package/libs/all-agents.ts +7 -3
- package/libs/claude-code.ts +5 -2
- package/libs/codex.ts +5 -2
- package/libs/gemini-cli.ts +5 -2
- package/libs/index.ts +3 -0
- package/libs/opencode.test.ts +55 -0
- package/libs/opencode.ts +72 -0
- package/libs/utils.ts +4 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.15.0](https://github.com/gengjiawen/os-init/compare/v1.14.1...v1.15.0) (2026-03-04)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add glm and kimi models to opencode template ([597379d](https://github.com/gengjiawen/os-init/commit/597379db82169f068b4732e71ccba4631e58c951))
|
|
9
|
+
* add opencode setup and update agents installer ([b62d83d](https://github.com/gengjiawen/os-init/commit/b62d83d925cb11bd2fff1465a3936c17706b1267))
|
|
10
|
+
|
|
3
11
|
## [1.14.1](https://github.com/gengjiawen/os-init/compare/v1.14.0...v1.14.1) (2026-02-28)
|
|
4
12
|
|
|
5
13
|
|
package/README.md
CHANGED
|
@@ -98,6 +98,17 @@ Configures Codex CLI with your API key. This command will:
|
|
|
98
98
|
- Write `~/.codex/auth.json`
|
|
99
99
|
- Install global tool: `@openai/codex`
|
|
100
100
|
|
|
101
|
+
### Configure OpenCode CLI
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pnpx @gengjiawen/os-init set-opencode <API_KEY>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Configures OpenCode CLI with your API key. This command will:
|
|
108
|
+
|
|
109
|
+
- Write `~/.config/opencode/opencode.json`
|
|
110
|
+
- Install global tool: `opencode-ai` (provides `opencode` command)
|
|
111
|
+
|
|
101
112
|
### Configure Raycast AI
|
|
102
113
|
|
|
103
114
|
```bash
|
package/bin/bin.js
CHANGED
|
@@ -8,6 +8,8 @@ const {
|
|
|
8
8
|
installCodexDeps,
|
|
9
9
|
writeGeminiConfig,
|
|
10
10
|
installGeminiDeps,
|
|
11
|
+
writeOpencodeConfig,
|
|
12
|
+
installOpencodeDeps,
|
|
11
13
|
writeAllAgentsConfig,
|
|
12
14
|
installAllAgentsDeps,
|
|
13
15
|
writeRaycastConfig,
|
|
@@ -91,10 +93,33 @@ program
|
|
|
91
93
|
)
|
|
92
94
|
})
|
|
93
95
|
|
|
96
|
+
program
|
|
97
|
+
.command('set-opencode')
|
|
98
|
+
.description('setup OpenCode CLI config and auth')
|
|
99
|
+
.argument('<apiKey>', 'API key to set for OpenCode')
|
|
100
|
+
.action(async (apiKey) => {
|
|
101
|
+
if (!apiKey || String(apiKey).trim().length === 0) {
|
|
102
|
+
console.error('Missing required argument: <apiKey>')
|
|
103
|
+
program.help({ error: true })
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const { configPath } = writeOpencodeConfig(apiKey)
|
|
108
|
+
console.log(`OpenCode config written to: ${configPath}`)
|
|
109
|
+
await installOpencodeDeps()
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.error('Failed to setup OpenCode:', err.message)
|
|
112
|
+
process.exit(1)
|
|
113
|
+
}
|
|
114
|
+
console.log(
|
|
115
|
+
'OpenCode is ready. use `opencode` in terminal to start building'
|
|
116
|
+
)
|
|
117
|
+
})
|
|
118
|
+
|
|
94
119
|
program
|
|
95
120
|
.command('set-agents')
|
|
96
121
|
.description(
|
|
97
|
-
'setup Claude Code and
|
|
122
|
+
'setup Claude Code, Codex, and OpenCode at once (use --full to include Gemini CLI)'
|
|
98
123
|
)
|
|
99
124
|
.argument('<apiKey>', 'API key to set for agents')
|
|
100
125
|
.option('--full', 'Also setup Gemini CLI')
|
|
@@ -110,8 +135,8 @@ program
|
|
|
110
135
|
try {
|
|
111
136
|
console.log(
|
|
112
137
|
full
|
|
113
|
-
? 'Setting up Claude Code + Codex + Gemini CLI...\n'
|
|
114
|
-
: 'Setting up Claude Code + Codex...\n'
|
|
138
|
+
? 'Setting up Claude Code + Codex + OpenCode + Gemini CLI...\n'
|
|
139
|
+
: 'Setting up Claude Code + Codex + OpenCode...\n'
|
|
115
140
|
)
|
|
116
141
|
|
|
117
142
|
const result = writeAllAgentsConfig(apiKey, { full })
|
|
@@ -126,6 +151,9 @@ program
|
|
|
126
151
|
console.log(` Config written to: ${result.codex.configPath}`)
|
|
127
152
|
console.log(` Auth written to: ${result.codex.authPath}`)
|
|
128
153
|
|
|
154
|
+
console.log('\nOpenCode:')
|
|
155
|
+
console.log(` Config written to: ${result.opencode.configPath}`)
|
|
156
|
+
|
|
129
157
|
if (result.gemini) {
|
|
130
158
|
console.log('\nGemini CLI:')
|
|
131
159
|
console.log(` Env written to: ${result.gemini.envPath}`)
|
|
@@ -142,6 +170,7 @@ program
|
|
|
142
170
|
console.log('\nSetup complete!')
|
|
143
171
|
console.log(' - Use `claude` for Claude Code')
|
|
144
172
|
console.log(' - Use `codex` for Codex')
|
|
173
|
+
console.log(' - Use `opencode` for OpenCode')
|
|
145
174
|
if (full) console.log(' - Use `gemini` for Gemini CLI')
|
|
146
175
|
})
|
|
147
176
|
|
package/build/all-agents.d.ts
CHANGED
package/build/all-agents.js
CHANGED
|
@@ -5,12 +5,15 @@ exports.installAllAgentsDeps = installAllAgentsDeps;
|
|
|
5
5
|
const claude_code_1 = require("./claude-code");
|
|
6
6
|
const codex_1 = require("./codex");
|
|
7
7
|
const gemini_cli_1 = require("./gemini-cli");
|
|
8
|
+
const opencode_1 = require("./opencode");
|
|
8
9
|
function writeAllAgentsConfig(apiKey, options = {}) {
|
|
9
10
|
const claudeResult = (0, claude_code_1.writeClaudeConfig)(apiKey);
|
|
10
11
|
const codexResult = (0, codex_1.writeCodexConfig)(apiKey);
|
|
12
|
+
const opencodeResult = (0, opencode_1.writeOpencodeConfig)(apiKey);
|
|
11
13
|
const result = {
|
|
12
14
|
claude: claudeResult,
|
|
13
15
|
codex: codexResult,
|
|
16
|
+
opencode: opencodeResult,
|
|
14
17
|
};
|
|
15
18
|
if (options.full) {
|
|
16
19
|
result.gemini = (0, gemini_cli_1.writeGeminiConfig)(apiKey);
|
|
@@ -18,7 +21,7 @@ function writeAllAgentsConfig(apiKey, options = {}) {
|
|
|
18
21
|
return result;
|
|
19
22
|
}
|
|
20
23
|
async function installAllAgentsDeps(options = {}) {
|
|
21
|
-
const installers = [(0, claude_code_1.installDeps)(), (0, codex_1.installCodexDeps)()];
|
|
24
|
+
const installers = [(0, claude_code_1.installDeps)(), (0, codex_1.installCodexDeps)(), (0, opencode_1.installOpencodeDeps)()];
|
|
22
25
|
if (options.full)
|
|
23
26
|
installers.push((0, gemini_cli_1.installGeminiDeps)());
|
|
24
27
|
await Promise.all(installers);
|
package/build/claude-code.js
CHANGED
|
@@ -90,7 +90,10 @@ async function installDeps() {
|
|
|
90
90
|
const usePnpm = await (0, utils_1.commandExists)('pnpm');
|
|
91
91
|
if (usePnpm) {
|
|
92
92
|
console.log('pnpm detected. Installing dependencies with pnpm...');
|
|
93
|
-
await (0, execa_1.execa)('pnpm', ['add', '-g', ...packages], {
|
|
93
|
+
await (0, execa_1.execa)('pnpm', ['add', '-g', ...packages], {
|
|
94
|
+
stdio: 'inherit',
|
|
95
|
+
env: utils_1.PNPM_INSTALL_ENV,
|
|
96
|
+
});
|
|
94
97
|
}
|
|
95
98
|
else {
|
|
96
99
|
console.log('pnpm not found. Falling back to npm...');
|
package/build/codex.js
CHANGED
|
@@ -36,7 +36,10 @@ async function installCodexDeps() {
|
|
|
36
36
|
const usePnpm = await (0, utils_1.commandExists)('pnpm');
|
|
37
37
|
if (usePnpm) {
|
|
38
38
|
console.log('pnpm detected. Installing Codex dependency with pnpm...');
|
|
39
|
-
await (0, execa_1.execa)('pnpm', ['add', '-g', ...packages], {
|
|
39
|
+
await (0, execa_1.execa)('pnpm', ['add', '-g', ...packages], {
|
|
40
|
+
stdio: 'inherit',
|
|
41
|
+
env: utils_1.PNPM_INSTALL_ENV,
|
|
42
|
+
});
|
|
40
43
|
}
|
|
41
44
|
else {
|
|
42
45
|
console.log('pnpm not found. Falling back to npm...');
|
package/build/gemini-cli.js
CHANGED
|
@@ -39,7 +39,10 @@ async function installGeminiDeps() {
|
|
|
39
39
|
const geminiPackage = '@google/gemini-cli';
|
|
40
40
|
if (usePnpm) {
|
|
41
41
|
console.log('pnpm detected. Installing Gemini CLI with pnpm...');
|
|
42
|
-
await (0, execa_1.execa)('pnpm', ['add', '-g', geminiPackage], {
|
|
42
|
+
await (0, execa_1.execa)('pnpm', ['add', '-g', geminiPackage], {
|
|
43
|
+
stdio: 'inherit',
|
|
44
|
+
env: utils_1.PNPM_INSTALL_ENV,
|
|
45
|
+
});
|
|
43
46
|
}
|
|
44
47
|
else {
|
|
45
48
|
console.log('pnpm not found. Falling back to npm...');
|
package/build/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare function writeRaycastConfig(apiKey: string): {
|
|
|
4
4
|
export { writeClaudeConfig, installDeps } from './claude-code';
|
|
5
5
|
export { writeCodexConfig, installCodexDeps } from './codex';
|
|
6
6
|
export { writeGeminiConfig, installGeminiDeps } from './gemini-cli';
|
|
7
|
+
export { writeOpencodeConfig, installOpencodeDeps } from './opencode';
|
|
7
8
|
export { writeAllAgentsConfig, installAllAgentsDeps } from './all-agents';
|
|
8
9
|
export { setupDevEnvironment } from './dev-setup';
|
|
9
10
|
export { setupAndroidEnvironment } from './android-setup';
|
package/build/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.setupAndroidEnvironment = exports.setupDevEnvironment = exports.installAllAgentsDeps = exports.writeAllAgentsConfig = exports.installGeminiDeps = exports.writeGeminiConfig = exports.installCodexDeps = exports.writeCodexConfig = exports.installDeps = exports.writeClaudeConfig = void 0;
|
|
3
|
+
exports.setupAndroidEnvironment = exports.setupDevEnvironment = exports.installAllAgentsDeps = exports.writeAllAgentsConfig = exports.installOpencodeDeps = exports.writeOpencodeConfig = exports.installGeminiDeps = exports.writeGeminiConfig = exports.installCodexDeps = exports.writeCodexConfig = exports.installDeps = exports.writeClaudeConfig = void 0;
|
|
4
4
|
exports.writeRaycastConfig = writeRaycastConfig;
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const path = require("path");
|
|
@@ -47,6 +47,9 @@ Object.defineProperty(exports, "installCodexDeps", { enumerable: true, get: func
|
|
|
47
47
|
var gemini_cli_1 = require("./gemini-cli");
|
|
48
48
|
Object.defineProperty(exports, "writeGeminiConfig", { enumerable: true, get: function () { return gemini_cli_1.writeGeminiConfig; } });
|
|
49
49
|
Object.defineProperty(exports, "installGeminiDeps", { enumerable: true, get: function () { return gemini_cli_1.installGeminiDeps; } });
|
|
50
|
+
var opencode_1 = require("./opencode");
|
|
51
|
+
Object.defineProperty(exports, "writeOpencodeConfig", { enumerable: true, get: function () { return opencode_1.writeOpencodeConfig; } });
|
|
52
|
+
Object.defineProperty(exports, "installOpencodeDeps", { enumerable: true, get: function () { return opencode_1.installOpencodeDeps; } });
|
|
50
53
|
var all_agents_1 = require("./all-agents");
|
|
51
54
|
Object.defineProperty(exports, "writeAllAgentsConfig", { enumerable: true, get: function () { return all_agents_1.writeAllAgentsConfig; } });
|
|
52
55
|
Object.defineProperty(exports, "installAllAgentsDeps", { enumerable: true, get: function () { return all_agents_1.installAllAgentsDeps; } });
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeOpencodeConfig = writeOpencodeConfig;
|
|
4
|
+
exports.installOpencodeDeps = installOpencodeDeps;
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const execa_1 = require("execa");
|
|
9
|
+
const utils_1 = require("./utils");
|
|
10
|
+
const OPENCODE_PROVIDER_ID = 'MyCustomProvider';
|
|
11
|
+
const OPENCODE_MODEL_ID = 'code';
|
|
12
|
+
const OPENCODE_GLM_MODEL_ID = 'glm';
|
|
13
|
+
const OPENCODE_KIMI_MODEL_ID = 'kimi';
|
|
14
|
+
const OPENCODE_BASE_URL = 'https://ai.gengjiawen.com/api/openai/v1';
|
|
15
|
+
function getOpencodeConfigDir() {
|
|
16
|
+
return path.join(os.homedir(), '.config', 'opencode');
|
|
17
|
+
}
|
|
18
|
+
function writeOpencodeConfig(apiKey) {
|
|
19
|
+
const configDir = getOpencodeConfigDir();
|
|
20
|
+
(0, utils_1.ensureDir)(configDir);
|
|
21
|
+
const configContent = {
|
|
22
|
+
$schema: 'https://opencode.ai/config.json',
|
|
23
|
+
provider: {
|
|
24
|
+
[OPENCODE_PROVIDER_ID]: {
|
|
25
|
+
npm: '@ai-sdk/openai-compatible',
|
|
26
|
+
name: 'JWProvider',
|
|
27
|
+
options: {
|
|
28
|
+
baseURL: OPENCODE_BASE_URL,
|
|
29
|
+
apiKey,
|
|
30
|
+
},
|
|
31
|
+
models: {
|
|
32
|
+
[OPENCODE_MODEL_ID]: {
|
|
33
|
+
name: OPENCODE_MODEL_ID,
|
|
34
|
+
},
|
|
35
|
+
[OPENCODE_GLM_MODEL_ID]: {
|
|
36
|
+
name: OPENCODE_GLM_MODEL_ID,
|
|
37
|
+
},
|
|
38
|
+
[OPENCODE_KIMI_MODEL_ID]: {
|
|
39
|
+
name: OPENCODE_KIMI_MODEL_ID,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
model: `${OPENCODE_PROVIDER_ID}/${OPENCODE_MODEL_ID}`,
|
|
45
|
+
small_model: `${OPENCODE_PROVIDER_ID}/${OPENCODE_MODEL_ID}`,
|
|
46
|
+
};
|
|
47
|
+
const configPath = path.join(configDir, 'opencode.json');
|
|
48
|
+
fs.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
|
|
49
|
+
return { configPath };
|
|
50
|
+
}
|
|
51
|
+
async function installOpencodeDeps() {
|
|
52
|
+
const packages = ['opencode-ai'];
|
|
53
|
+
const usePnpm = await (0, utils_1.commandExists)('pnpm');
|
|
54
|
+
if (usePnpm) {
|
|
55
|
+
console.log('pnpm detected. Installing OpenCode dependency with pnpm...');
|
|
56
|
+
await (0, execa_1.execa)('pnpm', ['add', '-g', ...packages], {
|
|
57
|
+
stdio: 'inherit',
|
|
58
|
+
env: utils_1.PNPM_INSTALL_ENV,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
console.log('pnpm not found. Falling back to npm...');
|
|
63
|
+
await (0, execa_1.execa)('npm', ['install', '-g', ...packages], { stdio: 'inherit' });
|
|
64
|
+
}
|
|
65
|
+
console.log('OpenCode dependency installed successfully.');
|
|
66
|
+
}
|
package/build/utils.d.ts
CHANGED
package/build/utils.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PNPM_INSTALL_ENV = void 0;
|
|
3
4
|
exports.ensureDir = ensureDir;
|
|
4
5
|
exports.commandExists = commandExists;
|
|
5
6
|
const fs = require("fs");
|
|
6
7
|
const execa_1 = require("execa");
|
|
8
|
+
exports.PNPM_INSTALL_ENV = {
|
|
9
|
+
PNPM_CONFIG_ENABLE_PRE_POST_SCRIPTS: 'true',
|
|
10
|
+
};
|
|
7
11
|
function ensureDir(dirPath) {
|
|
8
12
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
9
13
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as fs from 'fs'
|
|
2
|
+
import * as os from 'os'
|
|
3
|
+
import * as path from 'path'
|
|
4
|
+
|
|
5
|
+
jest.mock('execa', () => ({
|
|
6
|
+
execa: jest.fn(),
|
|
7
|
+
}))
|
|
8
|
+
|
|
9
|
+
import { writeAllAgentsConfig } from './all-agents'
|
|
10
|
+
|
|
11
|
+
describe('writeAllAgentsConfig', () => {
|
|
12
|
+
let tempHome: string
|
|
13
|
+
let homedirSpy: jest.SpiedFunction<typeof os.homedir>
|
|
14
|
+
let originalAppData: string | undefined
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'os-init-all-agents-'))
|
|
18
|
+
homedirSpy = jest.spyOn(os, 'homedir').mockReturnValue(tempHome)
|
|
19
|
+
originalAppData = process.env.APPDATA
|
|
20
|
+
process.env.APPDATA = path.join(tempHome, 'AppData', 'Roaming')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
homedirSpy.mockRestore()
|
|
25
|
+
if (originalAppData === undefined) {
|
|
26
|
+
delete process.env.APPDATA
|
|
27
|
+
} else {
|
|
28
|
+
process.env.APPDATA = originalAppData
|
|
29
|
+
}
|
|
30
|
+
fs.rmSync(tempHome, { recursive: true, force: true })
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('writes Claude, Codex and OpenCode config by default', () => {
|
|
34
|
+
const result = writeAllAgentsConfig('test-api-key')
|
|
35
|
+
|
|
36
|
+
expect(fs.existsSync(result.claude.settingsPath)).toBe(true)
|
|
37
|
+
expect(fs.existsSync(result.codex.configPath)).toBe(true)
|
|
38
|
+
expect(fs.existsSync(result.codex.authPath)).toBe(true)
|
|
39
|
+
expect(fs.existsSync(result.opencode.configPath)).toBe(true)
|
|
40
|
+
expect(result.gemini).toBeUndefined()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('includes Gemini config when full option is enabled', () => {
|
|
44
|
+
const result = writeAllAgentsConfig('test-api-key', { full: true })
|
|
45
|
+
|
|
46
|
+
expect(result.gemini).toBeDefined()
|
|
47
|
+
expect(fs.existsSync(result.gemini!.envPath)).toBe(true)
|
|
48
|
+
expect(fs.existsSync(result.gemini!.settingsPath)).toBe(true)
|
|
49
|
+
})
|
|
50
|
+
})
|
package/libs/all-agents.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { writeClaudeConfig, installDeps } from './claude-code'
|
|
2
2
|
import { writeCodexConfig, installCodexDeps } from './codex'
|
|
3
3
|
import { writeGeminiConfig, installGeminiDeps } from './gemini-cli'
|
|
4
|
+
import { writeOpencodeConfig, installOpencodeDeps } from './opencode'
|
|
4
5
|
|
|
5
6
|
export interface AllAgentsOptions {
|
|
6
7
|
/** When true, also include Gemini CLI in the combined setup. */
|
|
@@ -10,20 +11,23 @@ export interface AllAgentsOptions {
|
|
|
10
11
|
export interface AllAgentsResult {
|
|
11
12
|
claude: { settingsPath: string; vscodeSettingsPath: string }
|
|
12
13
|
codex: { configPath: string; authPath: string }
|
|
14
|
+
opencode: { configPath: string }
|
|
13
15
|
gemini?: { envPath: string; settingsPath: string }
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
/** Write configuration for the combined setup (Claude Code + Codex, optional Gemini CLI). */
|
|
18
|
+
/** Write configuration for the combined setup (Claude Code + Codex + OpenCode, optional Gemini CLI). */
|
|
17
19
|
export function writeAllAgentsConfig(
|
|
18
20
|
apiKey: string,
|
|
19
21
|
options: AllAgentsOptions = {}
|
|
20
22
|
): AllAgentsResult {
|
|
21
23
|
const claudeResult = writeClaudeConfig(apiKey)
|
|
22
24
|
const codexResult = writeCodexConfig(apiKey)
|
|
25
|
+
const opencodeResult = writeOpencodeConfig(apiKey)
|
|
23
26
|
|
|
24
27
|
const result: AllAgentsResult = {
|
|
25
28
|
claude: claudeResult,
|
|
26
29
|
codex: codexResult,
|
|
30
|
+
opencode: opencodeResult,
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
if (options.full) {
|
|
@@ -33,11 +37,11 @@ export function writeAllAgentsConfig(
|
|
|
33
37
|
return result
|
|
34
38
|
}
|
|
35
39
|
|
|
36
|
-
/** Install dependencies for the combined setup (Claude Code + Codex, optional Gemini CLI). */
|
|
40
|
+
/** Install dependencies for the combined setup (Claude Code + Codex + OpenCode, optional Gemini CLI). */
|
|
37
41
|
export async function installAllAgentsDeps(
|
|
38
42
|
options: AllAgentsOptions = {}
|
|
39
43
|
): Promise<void> {
|
|
40
|
-
const installers = [installDeps(), installCodexDeps()]
|
|
44
|
+
const installers = [installDeps(), installCodexDeps(), installOpencodeDeps()]
|
|
41
45
|
if (options.full) installers.push(installGeminiDeps())
|
|
42
46
|
await Promise.all(installers)
|
|
43
47
|
}
|
package/libs/claude-code.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as path from 'path'
|
|
|
3
3
|
import * as os from 'os'
|
|
4
4
|
import { execa } from 'execa'
|
|
5
5
|
import { ParseError, applyEdits, modify, parse } from 'jsonc-parser'
|
|
6
|
-
import { ensureDir, commandExists } from './utils'
|
|
6
|
+
import { ensureDir, commandExists, PNPM_INSTALL_ENV } from './utils'
|
|
7
7
|
|
|
8
8
|
const CLAUDE_BASE_URL = 'https://ai.gengjiawen.com/api/claude/'
|
|
9
9
|
|
|
@@ -139,7 +139,10 @@ export async function installDeps(): Promise<void> {
|
|
|
139
139
|
|
|
140
140
|
if (usePnpm) {
|
|
141
141
|
console.log('pnpm detected. Installing dependencies with pnpm...')
|
|
142
|
-
await execa('pnpm', ['add', '-g', ...packages], {
|
|
142
|
+
await execa('pnpm', ['add', '-g', ...packages], {
|
|
143
|
+
stdio: 'inherit',
|
|
144
|
+
env: PNPM_INSTALL_ENV,
|
|
145
|
+
})
|
|
143
146
|
} else {
|
|
144
147
|
console.log('pnpm not found. Falling back to npm...')
|
|
145
148
|
await execa('npm', ['install', '-g', ...packages], { stdio: 'inherit' })
|
package/libs/codex.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from 'fs'
|
|
|
2
2
|
import * as path from 'path'
|
|
3
3
|
import * as os from 'os'
|
|
4
4
|
import { execa } from 'execa'
|
|
5
|
-
import { ensureDir, commandExists } from './utils'
|
|
5
|
+
import { ensureDir, commandExists, PNPM_INSTALL_ENV } from './utils'
|
|
6
6
|
|
|
7
7
|
/** Return Codex configuration directory path */
|
|
8
8
|
function getCodexConfigDir(): string {
|
|
@@ -47,7 +47,10 @@ export async function installCodexDeps(): Promise<void> {
|
|
|
47
47
|
|
|
48
48
|
if (usePnpm) {
|
|
49
49
|
console.log('pnpm detected. Installing Codex dependency with pnpm...')
|
|
50
|
-
await execa('pnpm', ['add', '-g', ...packages], {
|
|
50
|
+
await execa('pnpm', ['add', '-g', ...packages], {
|
|
51
|
+
stdio: 'inherit',
|
|
52
|
+
env: PNPM_INSTALL_ENV,
|
|
53
|
+
})
|
|
51
54
|
} else {
|
|
52
55
|
console.log('pnpm not found. Falling back to npm...')
|
|
53
56
|
await execa('npm', ['install', '-g', ...packages], { stdio: 'inherit' })
|
package/libs/gemini-cli.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from 'fs'
|
|
|
2
2
|
import * as path from 'path'
|
|
3
3
|
import * as os from 'os'
|
|
4
4
|
import { execa } from 'execa'
|
|
5
|
-
import { ensureDir, commandExists } from './utils'
|
|
5
|
+
import { ensureDir, commandExists, PNPM_INSTALL_ENV } from './utils'
|
|
6
6
|
|
|
7
7
|
/** Return Gemini CLI configuration directory path */
|
|
8
8
|
function getGeminiConfigDir(): string {
|
|
@@ -57,7 +57,10 @@ export async function installGeminiDeps(): Promise<void> {
|
|
|
57
57
|
|
|
58
58
|
if (usePnpm) {
|
|
59
59
|
console.log('pnpm detected. Installing Gemini CLI with pnpm...')
|
|
60
|
-
await execa('pnpm', ['add', '-g', geminiPackage], {
|
|
60
|
+
await execa('pnpm', ['add', '-g', geminiPackage], {
|
|
61
|
+
stdio: 'inherit',
|
|
62
|
+
env: PNPM_INSTALL_ENV,
|
|
63
|
+
})
|
|
61
64
|
} else {
|
|
62
65
|
console.log('pnpm not found. Falling back to npm...')
|
|
63
66
|
await execa('npm', ['install', '-g', geminiPackage], { stdio: 'inherit' })
|
package/libs/index.ts
CHANGED
|
@@ -55,6 +55,9 @@ export { writeCodexConfig, installCodexDeps } from './codex'
|
|
|
55
55
|
// Re-export gemini-cli functionality
|
|
56
56
|
export { writeGeminiConfig, installGeminiDeps } from './gemini-cli'
|
|
57
57
|
|
|
58
|
+
// Re-export opencode functionality
|
|
59
|
+
export { writeOpencodeConfig, installOpencodeDeps } from './opencode'
|
|
60
|
+
|
|
58
61
|
// Re-export all-agents functionality
|
|
59
62
|
export { writeAllAgentsConfig, installAllAgentsDeps } from './all-agents'
|
|
60
63
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as fs from 'fs'
|
|
2
|
+
import * as os from 'os'
|
|
3
|
+
import * as path from 'path'
|
|
4
|
+
|
|
5
|
+
jest.mock('execa', () => ({
|
|
6
|
+
execa: jest.fn(),
|
|
7
|
+
}))
|
|
8
|
+
|
|
9
|
+
import { writeOpencodeConfig } from './opencode'
|
|
10
|
+
|
|
11
|
+
describe('writeOpencodeConfig', () => {
|
|
12
|
+
let tempHome: string
|
|
13
|
+
let homedirSpy: jest.SpiedFunction<typeof os.homedir>
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'os-init-opencode-'))
|
|
17
|
+
homedirSpy = jest.spyOn(os, 'homedir').mockReturnValue(tempHome)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
homedirSpy.mockRestore()
|
|
22
|
+
fs.rmSync(tempHome, { recursive: true, force: true })
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('writes OpenCode config file', () => {
|
|
26
|
+
const result = writeOpencodeConfig('test-api-key')
|
|
27
|
+
|
|
28
|
+
expect(result.configPath).toBe(
|
|
29
|
+
path.join(tempHome, '.config', 'opencode', 'opencode.json')
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
const config = JSON.parse(fs.readFileSync(result.configPath, 'utf8'))
|
|
33
|
+
|
|
34
|
+
expect(config.$schema).toBe('https://opencode.ai/config.json')
|
|
35
|
+
expect(config.provider.MyCustomProvider.npm).toBe(
|
|
36
|
+
'@ai-sdk/openai-compatible'
|
|
37
|
+
)
|
|
38
|
+
expect(config.provider.MyCustomProvider.name).toBe('JWProvider')
|
|
39
|
+
expect(config.provider.MyCustomProvider.options.baseURL).toBe(
|
|
40
|
+
'https://ai.gengjiawen.com/api/openai/v1'
|
|
41
|
+
)
|
|
42
|
+
expect(config.provider.MyCustomProvider.options.apiKey).toBe('test-api-key')
|
|
43
|
+
expect(config.provider.MyCustomProvider.models.code).toEqual({
|
|
44
|
+
name: 'code',
|
|
45
|
+
})
|
|
46
|
+
expect(config.provider.MyCustomProvider.models.glm).toEqual({
|
|
47
|
+
name: 'glm',
|
|
48
|
+
})
|
|
49
|
+
expect(config.provider.MyCustomProvider.models.kimi).toEqual({
|
|
50
|
+
name: 'kimi',
|
|
51
|
+
})
|
|
52
|
+
expect(config.model).toBe('MyCustomProvider/code')
|
|
53
|
+
expect(config.small_model).toBe('MyCustomProvider/code')
|
|
54
|
+
})
|
|
55
|
+
})
|
package/libs/opencode.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as fs from 'fs'
|
|
2
|
+
import * as os from 'os'
|
|
3
|
+
import * as path from 'path'
|
|
4
|
+
import { execa } from 'execa'
|
|
5
|
+
import { commandExists, ensureDir, PNPM_INSTALL_ENV } from './utils'
|
|
6
|
+
|
|
7
|
+
const OPENCODE_PROVIDER_ID = 'MyCustomProvider'
|
|
8
|
+
const OPENCODE_MODEL_ID = 'code'
|
|
9
|
+
const OPENCODE_GLM_MODEL_ID = 'glm'
|
|
10
|
+
const OPENCODE_KIMI_MODEL_ID = 'kimi'
|
|
11
|
+
const OPENCODE_BASE_URL = 'https://ai.gengjiawen.com/api/openai/v1'
|
|
12
|
+
|
|
13
|
+
/** Return OpenCode configuration directory path */
|
|
14
|
+
function getOpencodeConfigDir(): string {
|
|
15
|
+
return path.join(os.homedir(), '.config', 'opencode')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Write OpenCode config file */
|
|
19
|
+
export function writeOpencodeConfig(apiKey: string): { configPath: string } {
|
|
20
|
+
const configDir = getOpencodeConfigDir()
|
|
21
|
+
ensureDir(configDir)
|
|
22
|
+
|
|
23
|
+
const configContent = {
|
|
24
|
+
$schema: 'https://opencode.ai/config.json',
|
|
25
|
+
provider: {
|
|
26
|
+
[OPENCODE_PROVIDER_ID]: {
|
|
27
|
+
npm: '@ai-sdk/openai-compatible',
|
|
28
|
+
name: 'JWProvider',
|
|
29
|
+
options: {
|
|
30
|
+
baseURL: OPENCODE_BASE_URL,
|
|
31
|
+
apiKey,
|
|
32
|
+
},
|
|
33
|
+
models: {
|
|
34
|
+
[OPENCODE_MODEL_ID]: {
|
|
35
|
+
name: OPENCODE_MODEL_ID,
|
|
36
|
+
},
|
|
37
|
+
[OPENCODE_GLM_MODEL_ID]: {
|
|
38
|
+
name: OPENCODE_GLM_MODEL_ID,
|
|
39
|
+
},
|
|
40
|
+
[OPENCODE_KIMI_MODEL_ID]: {
|
|
41
|
+
name: OPENCODE_KIMI_MODEL_ID,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
model: `${OPENCODE_PROVIDER_ID}/${OPENCODE_MODEL_ID}`,
|
|
47
|
+
small_model: `${OPENCODE_PROVIDER_ID}/${OPENCODE_MODEL_ID}`,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const configPath = path.join(configDir, 'opencode.json')
|
|
51
|
+
fs.writeFileSync(configPath, JSON.stringify(configContent, null, 2))
|
|
52
|
+
|
|
53
|
+
return { configPath }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Install OpenCode dependency */
|
|
57
|
+
export async function installOpencodeDeps(): Promise<void> {
|
|
58
|
+
const packages = ['opencode-ai']
|
|
59
|
+
const usePnpm = await commandExists('pnpm')
|
|
60
|
+
|
|
61
|
+
if (usePnpm) {
|
|
62
|
+
console.log('pnpm detected. Installing OpenCode dependency with pnpm...')
|
|
63
|
+
await execa('pnpm', ['add', '-g', ...packages], {
|
|
64
|
+
stdio: 'inherit',
|
|
65
|
+
env: PNPM_INSTALL_ENV,
|
|
66
|
+
})
|
|
67
|
+
} else {
|
|
68
|
+
console.log('pnpm not found. Falling back to npm...')
|
|
69
|
+
await execa('npm', ['install', '-g', ...packages], { stdio: 'inherit' })
|
|
70
|
+
}
|
|
71
|
+
console.log('OpenCode dependency installed successfully.')
|
|
72
|
+
}
|
package/libs/utils.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import * as fs from 'fs'
|
|
2
2
|
import { execa } from 'execa'
|
|
3
3
|
|
|
4
|
+
export const PNPM_INSTALL_ENV = {
|
|
5
|
+
PNPM_CONFIG_ENABLE_PRE_POST_SCRIPTS: 'true',
|
|
6
|
+
}
|
|
7
|
+
|
|
4
8
|
/** Ensure directory exists */
|
|
5
9
|
export function ensureDir(dirPath: string): void {
|
|
6
10
|
fs.mkdirSync(dirPath, { recursive: true })
|