opencode-morphllm 0.0.6 → 0.0.7
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/.gitleaks.toml +6 -0
- package/.husky/pre-commit +3 -1
- package/.prettierignore +1 -0
- package/.prettierrc +1 -1
- package/README.md +7 -2
- package/bun.lock +23 -0
- package/bunfig.toml +4 -0
- package/dist/index.js +11 -12
- package/dist/morph/mcps.js +11 -11
- package/dist/morph/mcps.test.js +32 -32
- package/dist/morph/router.d.ts +16 -21
- package/dist/morph/router.js +51 -55
- package/dist/morph/router.test.js +230 -231
- package/dist/shared/config.d.ts +16 -18
- package/dist/shared/config.js +62 -64
- package/dist/shared/config.test.js +183 -186
- package/dist/shared/opencode-config-dir.d.ts +18 -8
- package/dist/shared/opencode-config-dir.js +93 -47
- package/dist/shared/opencode-config-dir.test.d.ts +1 -0
- package/dist/shared/opencode-config-dir.test.js +310 -0
- package/package.json +3 -2
- package/src/index.ts +1 -2
- package/src/morph/router.test.ts +3 -2
- package/src/morph/router.ts +8 -3
- package/src/shared/config.test.ts +18 -6
- package/src/shared/config.ts +5 -5
- package/src/shared/opencode-config-dir.test.ts +404 -0
- package/src/shared/opencode-config-dir.ts +90 -11
|
@@ -1,204 +1,201 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
it,
|
|
4
|
-
expect,
|
|
5
|
-
beforeEach,
|
|
6
|
-
vi,
|
|
7
|
-
beforeAll,
|
|
8
|
-
afterAll,
|
|
9
|
-
} from 'bun:test';
|
|
10
|
-
import { existsSync, writeFileSync, rmSync, mkdirSync } from 'node:fs';
|
|
1
|
+
import { describe, it, expect, beforeEach, beforeAll, afterAll, } from 'bun:test';
|
|
2
|
+
import { existsSync, writeFileSync, rmSync, mkdirSync, } from 'node:fs';
|
|
11
3
|
import { join } from 'node:path';
|
|
12
4
|
import { tmpdir } from 'node:os';
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
import { env } from 'node:process';
|
|
6
|
+
// Use a real temporary directory for testing - no mocking needed
|
|
7
|
+
const mockConfigDir = join(tmpdir(), 'mock-morph-config-test');
|
|
15
8
|
const mockMorphJson = join(mockConfigDir, 'morph.json');
|
|
16
9
|
const mockMorphJsonc = join(mockConfigDir, 'morph.jsonc');
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
import {
|
|
21
|
-
getMorphPluginConfigPath,
|
|
22
|
-
loadMorphPluginConfig,
|
|
23
|
-
loadMorphPluginConfigWithProjectOverride,
|
|
24
|
-
} from './config';
|
|
10
|
+
// Save original environment
|
|
11
|
+
let originalEnv;
|
|
12
|
+
import { getMorphPluginConfigPath, loadMorphPluginConfig, loadMorphPluginConfigWithProjectOverride, } from './config';
|
|
25
13
|
describe('config.ts', () => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
14
|
+
beforeAll(() => {
|
|
15
|
+
// Save original environment and set custom config dir
|
|
16
|
+
originalEnv = {
|
|
17
|
+
OPENCODE_CONFIG_DIR: env.OPENCODE_CONFIG_DIR,
|
|
18
|
+
};
|
|
19
|
+
env.OPENCODE_CONFIG_DIR = mockConfigDir;
|
|
20
|
+
// Create mock config directory
|
|
21
|
+
if (!existsSync(mockConfigDir)) {
|
|
22
|
+
mkdirSync(mockConfigDir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
afterAll(() => {
|
|
26
|
+
// Restore original environment
|
|
27
|
+
if (originalEnv.OPENCODE_CONFIG_DIR !== undefined) {
|
|
28
|
+
env.OPENCODE_CONFIG_DIR = originalEnv.OPENCODE_CONFIG_DIR;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
delete env.OPENCODE_CONFIG_DIR;
|
|
32
|
+
}
|
|
33
|
+
// Cleanup
|
|
34
|
+
try {
|
|
35
|
+
if (existsSync(mockMorphJson))
|
|
36
|
+
rmSync(mockMorphJson);
|
|
37
|
+
if (existsSync(mockMorphJsonc))
|
|
38
|
+
rmSync(mockMorphJsonc);
|
|
39
|
+
if (existsSync(mockConfigDir))
|
|
40
|
+
rmSync(mockConfigDir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Ignore cleanup errors
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
// Clean up any existing config files before each test
|
|
48
|
+
try {
|
|
49
|
+
if (existsSync(mockMorphJson))
|
|
50
|
+
rmSync(mockMorphJson);
|
|
51
|
+
if (existsSync(mockMorphJsonc))
|
|
52
|
+
rmSync(mockMorphJsonc);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Ignore cleanup errors
|
|
56
|
+
}
|
|
56
57
|
});
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
describe('getMorphPluginConfigPath', () => {
|
|
59
|
+
it('should return path to morph.json in config directory', () => {
|
|
60
|
+
const path = getMorphPluginConfigPath();
|
|
61
|
+
expect(path).toContain('morph.json');
|
|
62
|
+
expect(path).toContain(mockConfigDir);
|
|
63
|
+
});
|
|
62
64
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
describe('loadMorphPluginConfig', () => {
|
|
66
|
+
it('should return null when no config files exist', () => {
|
|
67
|
+
const result = loadMorphPluginConfig();
|
|
68
|
+
expect(result).toBeNull();
|
|
69
|
+
});
|
|
70
|
+
it('should load config from .jsonc file', () => {
|
|
71
|
+
const content = `{
|
|
65
72
|
"MORPH_API_KEY": "test-key-123",
|
|
66
73
|
"MORPH_ROUTER_ENABLED": false
|
|
67
74
|
}`;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
});
|
|
146
|
-
describe('loadMorphPluginConfigWithProjectOverride', () => {
|
|
147
|
-
it('should return empty object when no config exists', () => {
|
|
148
|
-
const result =
|
|
149
|
-
loadMorphPluginConfigWithProjectOverride('/non-existent-path');
|
|
150
|
-
expect(result).toEqual({});
|
|
151
|
-
});
|
|
152
|
-
it('should merge user config with project config', () => {
|
|
153
|
-
// Create user config
|
|
154
|
-
writeFileSync(
|
|
155
|
-
mockMorphJson,
|
|
156
|
-
JSON.stringify({
|
|
157
|
-
MORPH_API_KEY: 'user-api-key',
|
|
158
|
-
MORPH_MODEL_EASY: 'user-easy-model',
|
|
159
|
-
})
|
|
160
|
-
);
|
|
161
|
-
// Create project config directory
|
|
162
|
-
const projectDir = join(tmpdir(), 'test-project');
|
|
163
|
-
const projectConfigDir = join(projectDir, '.opencode');
|
|
164
|
-
const projectConfigPath = join(projectConfigDir, 'morph.json');
|
|
165
|
-
mkdirSync(projectConfigDir, { recursive: true });
|
|
166
|
-
writeFileSync(
|
|
167
|
-
projectConfigPath,
|
|
168
|
-
JSON.stringify({
|
|
169
|
-
MORPH_API_KEY: 'project-api-key',
|
|
170
|
-
MORPH_MODEL_HARD: 'project-hard-model',
|
|
171
|
-
})
|
|
172
|
-
);
|
|
173
|
-
const result = loadMorphPluginConfigWithProjectOverride(projectDir);
|
|
174
|
-
// Project config should override user config
|
|
175
|
-
expect(result.MORPH_API_KEY).toBe('project-api-key');
|
|
176
|
-
expect(result.MORPH_MODEL_EASY).toBe('user-easy-model');
|
|
177
|
-
expect(result.MORPH_MODEL_HARD).toBe('project-hard-model');
|
|
75
|
+
writeFileSync(mockMorphJsonc, content);
|
|
76
|
+
const result = loadMorphPluginConfig();
|
|
77
|
+
expect(result).not.toBeNull();
|
|
78
|
+
expect(result?.MORPH_API_KEY).toBe('test-key-123');
|
|
79
|
+
expect(result?.MORPH_ROUTER_ENABLED).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
it('should load config from .json file', () => {
|
|
82
|
+
const content = JSON.stringify({
|
|
83
|
+
MORPH_API_KEY: 'json-key-456',
|
|
84
|
+
MORPH_MODEL_EASY: 'provider/model-easy',
|
|
85
|
+
});
|
|
86
|
+
writeFileSync(mockMorphJson, content);
|
|
87
|
+
const result = loadMorphPluginConfig();
|
|
88
|
+
expect(result).not.toBeNull();
|
|
89
|
+
expect(result?.MORPH_API_KEY).toBe('json-key-456');
|
|
90
|
+
expect(result?.MORPH_MODEL_EASY).toBe('provider/model-easy');
|
|
91
|
+
});
|
|
92
|
+
it('should prefer .jsonc over .json when both exist', () => {
|
|
93
|
+
const jsoncContent = '{"MORPH_API_KEY": "from-jsonc"}';
|
|
94
|
+
const jsonContent = '{"MORPH_API_KEY": "from-json"}';
|
|
95
|
+
writeFileSync(mockMorphJsonc, jsoncContent);
|
|
96
|
+
writeFileSync(mockMorphJson, jsonContent);
|
|
97
|
+
const result = loadMorphPluginConfig();
|
|
98
|
+
expect(result?.MORPH_API_KEY).toBe('from-jsonc');
|
|
99
|
+
});
|
|
100
|
+
it('should return null for invalid .jsonc content', () => {
|
|
101
|
+
writeFileSync(mockMorphJsonc, 'invalid json content');
|
|
102
|
+
const result = loadMorphPluginConfig();
|
|
103
|
+
expect(result).toBeNull();
|
|
104
|
+
});
|
|
105
|
+
it('should return null for invalid .json content', () => {
|
|
106
|
+
writeFileSync(mockMorphJson, 'invalid json content');
|
|
107
|
+
const result = loadMorphPluginConfig();
|
|
108
|
+
expect(result).toBeNull();
|
|
109
|
+
});
|
|
110
|
+
it('should handle all MORPH config fields', () => {
|
|
111
|
+
const content = JSON.stringify({
|
|
112
|
+
MORPH_API_KEY: 'api-key',
|
|
113
|
+
MORPH_ROUTER_ENABLED: true,
|
|
114
|
+
MORPH_MODEL_EASY: 'easy/provider/model',
|
|
115
|
+
MORPH_MODEL_MEDIUM: 'medium/provider/model',
|
|
116
|
+
MORPH_MODEL_HARD: 'hard/provider/model',
|
|
117
|
+
MORPH_MODEL_DEFAULT: 'default/provider/model',
|
|
118
|
+
});
|
|
119
|
+
writeFileSync(mockMorphJson, content);
|
|
120
|
+
const result = loadMorphPluginConfig();
|
|
121
|
+
expect(result).toEqual({
|
|
122
|
+
MORPH_API_KEY: 'api-key',
|
|
123
|
+
MORPH_ROUTER_ENABLED: true,
|
|
124
|
+
MORPH_MODEL_EASY: 'easy/provider/model',
|
|
125
|
+
MORPH_MODEL_MEDIUM: 'medium/provider/model',
|
|
126
|
+
MORPH_MODEL_HARD: 'hard/provider/model',
|
|
127
|
+
MORPH_MODEL_DEFAULT: 'default/provider/model',
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
it('should handle nested MORPH_ROUTER_CONFIGS', () => {
|
|
131
|
+
const content = JSON.stringify({
|
|
132
|
+
MORPH_API_KEY: 'api-key',
|
|
133
|
+
MORPH_ROUTER_CONFIGS: {
|
|
134
|
+
MORPH_ROUTER_ENABLED: true,
|
|
135
|
+
MORPH_MODEL_EASY: 'easy/provider/model',
|
|
136
|
+
MORPH_MODEL_MEDIUM: 'medium/provider/model',
|
|
137
|
+
MORPH_MODEL_HARD: 'hard/provider/model',
|
|
138
|
+
MORPH_MODEL_DEFAULT: 'default/provider/model',
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
writeFileSync(mockMorphJson, content);
|
|
142
|
+
const result = loadMorphPluginConfig();
|
|
143
|
+
expect(result?.MORPH_API_KEY).toBe('api-key');
|
|
144
|
+
expect(result?.MORPH_ROUTER_CONFIGS).toEqual({
|
|
145
|
+
MORPH_ROUTER_ENABLED: true,
|
|
146
|
+
MORPH_MODEL_EASY: 'easy/provider/model',
|
|
147
|
+
MORPH_MODEL_MEDIUM: 'medium/provider/model',
|
|
148
|
+
MORPH_MODEL_HARD: 'hard/provider/model',
|
|
149
|
+
MORPH_MODEL_DEFAULT: 'default/provider/model',
|
|
150
|
+
});
|
|
151
|
+
});
|
|
178
152
|
});
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
153
|
+
describe('loadMorphPluginConfigWithProjectOverride', () => {
|
|
154
|
+
it('should return empty object when no config exists', () => {
|
|
155
|
+
const result = loadMorphPluginConfigWithProjectOverride('/non-existent-path');
|
|
156
|
+
expect(result).toEqual({});
|
|
157
|
+
});
|
|
158
|
+
it('should merge user config with project config', () => {
|
|
159
|
+
// Create user config
|
|
160
|
+
writeFileSync(mockMorphJson, JSON.stringify({
|
|
161
|
+
MORPH_API_KEY: 'user-api-key',
|
|
162
|
+
MORPH_MODEL_EASY: 'user-easy-model',
|
|
163
|
+
}));
|
|
164
|
+
// Create project config directory
|
|
165
|
+
const projectDir = join(tmpdir(), 'test-project');
|
|
166
|
+
const projectConfigDir = join(projectDir, '.opencode');
|
|
167
|
+
const projectConfigPath = join(projectConfigDir, 'morph.json');
|
|
168
|
+
mkdirSync(projectConfigDir, { recursive: true });
|
|
169
|
+
writeFileSync(projectConfigPath, JSON.stringify({
|
|
170
|
+
MORPH_API_KEY: 'project-api-key',
|
|
171
|
+
MORPH_MODEL_HARD: 'project-hard-model',
|
|
172
|
+
}));
|
|
173
|
+
const result = loadMorphPluginConfigWithProjectOverride(projectDir);
|
|
174
|
+
// Project config should override user config
|
|
175
|
+
expect(result.MORPH_API_KEY).toBe('project-api-key');
|
|
176
|
+
expect(result.MORPH_MODEL_EASY).toBe('user-easy-model');
|
|
177
|
+
expect(result.MORPH_MODEL_HARD).toBe('project-hard-model');
|
|
178
|
+
});
|
|
179
|
+
it('should handle project .jsonc config', () => {
|
|
180
|
+
// Create user config
|
|
181
|
+
writeFileSync(mockMorphJson, JSON.stringify({
|
|
182
|
+
MORPH_API_KEY: 'user-key',
|
|
183
|
+
}));
|
|
184
|
+
// Create project config with .jsonc
|
|
185
|
+
const projectDir = join(tmpdir(), 'test-project2');
|
|
186
|
+
const projectConfigDir = join(projectDir, '.opencode');
|
|
187
|
+
const projectConfigPath = join(projectConfigDir, 'morph.jsonc');
|
|
188
|
+
mkdirSync(projectConfigDir, { recursive: true });
|
|
189
|
+
const jsoncContent = `
|
|
193
190
|
// Project config with comments
|
|
194
191
|
{
|
|
195
192
|
"MORPH_MODEL_MEDIUM": "project-medium-model"
|
|
196
193
|
}
|
|
197
194
|
`;
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
195
|
+
writeFileSync(projectConfigPath, jsoncContent);
|
|
196
|
+
const result = loadMorphPluginConfigWithProjectOverride(projectDir);
|
|
197
|
+
expect(result.MORPH_API_KEY).toBe('user-key');
|
|
198
|
+
expect(result.MORPH_MODEL_MEDIUM).toBe('project-medium-model');
|
|
199
|
+
});
|
|
202
200
|
});
|
|
203
|
-
});
|
|
204
201
|
});
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export type OpenCodeBinaryType = 'opencode' | 'opencode-desktop';
|
|
2
|
+
export interface OpenCodeConfigDirOptions {
|
|
3
|
+
binary: OpenCodeBinaryType;
|
|
4
|
+
version?: string | null;
|
|
5
|
+
checkExisting?: boolean;
|
|
5
6
|
}
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
export interface OpenCodeConfigPaths {
|
|
8
|
+
configDir: string;
|
|
9
|
+
configJson: string;
|
|
10
|
+
configJsonc: string;
|
|
11
|
+
packageJson: string;
|
|
12
|
+
omoConfig: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const TAURI_APP_IDENTIFIER = "ai.opencode.desktop";
|
|
15
|
+
export declare const TAURI_APP_IDENTIFIER_DEV = "ai.opencode.desktop.dev";
|
|
16
|
+
export declare function isDevBuild(version: string | null | undefined): boolean;
|
|
17
|
+
export declare function getOpenCodeConfigDir(options: OpenCodeConfigDirOptions): string;
|
|
18
|
+
export declare function getOpenCodeConfigPaths(options: OpenCodeConfigDirOptions): OpenCodeConfigPaths;
|
|
19
|
+
export declare function detectExistingConfigDir(binary: OpenCodeBinaryType, version?: string | null): string | null;
|
|
@@ -1,56 +1,102 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
|
+
export const TAURI_APP_IDENTIFIER = 'ai.opencode.desktop';
|
|
5
|
+
export const TAURI_APP_IDENTIFIER_DEV = 'ai.opencode.desktop.dev';
|
|
6
|
+
export function isDevBuild(version) {
|
|
7
|
+
if (!version)
|
|
8
|
+
return false;
|
|
9
|
+
return version.includes('-dev') || version.includes('.dev');
|
|
10
|
+
}
|
|
4
11
|
function getTauriConfigDir(identifier) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
}
|
|
12
|
+
const platform = process.platform;
|
|
13
|
+
switch (platform) {
|
|
14
|
+
case 'darwin':
|
|
15
|
+
return join(homedir(), 'Library', 'Application Support', identifier);
|
|
16
|
+
case 'win32': {
|
|
17
|
+
const appData = process.env.APPDATA || join(homedir(), 'AppData', 'Roaming');
|
|
18
|
+
return join(appData, identifier);
|
|
19
|
+
}
|
|
20
|
+
case 'linux':
|
|
21
|
+
default: {
|
|
22
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join(homedir(), '.config');
|
|
23
|
+
return join(xdgConfig, identifier);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
21
26
|
}
|
|
22
27
|
function getCliConfigDir() {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return join(xdgConfig, 'opencode');
|
|
28
|
+
const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
29
|
+
if (envConfigDir) {
|
|
30
|
+
return resolve(envConfigDir);
|
|
31
|
+
}
|
|
32
|
+
if (process.platform === 'win32') {
|
|
33
|
+
const crossPlatformDir = join(homedir(), '.config', 'opencode');
|
|
34
|
+
const crossPlatformConfig = join(crossPlatformDir, 'opencode.json');
|
|
35
|
+
if (existsSync(crossPlatformConfig)) {
|
|
36
|
+
return crossPlatformDir;
|
|
37
|
+
}
|
|
38
|
+
const appData = process.env.APPDATA || join(homedir(), 'AppData', 'Roaming');
|
|
39
|
+
const appdataDir = join(appData, 'opencode');
|
|
40
|
+
const appdataConfig = join(appdataDir, 'opencode.json');
|
|
41
|
+
if (existsSync(appdataConfig)) {
|
|
42
|
+
return appdataDir;
|
|
43
|
+
}
|
|
44
|
+
return crossPlatformDir;
|
|
45
|
+
}
|
|
46
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join(homedir(), '.config');
|
|
47
|
+
return join(xdgConfig, 'opencode');
|
|
44
48
|
}
|
|
45
49
|
export function getOpenCodeConfigDir(options) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const identifier =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
const { binary, version, checkExisting = true } = options;
|
|
51
|
+
if (binary === 'opencode') {
|
|
52
|
+
return getCliConfigDir();
|
|
53
|
+
}
|
|
54
|
+
const identifier = isDevBuild(version)
|
|
55
|
+
? TAURI_APP_IDENTIFIER_DEV
|
|
56
|
+
: TAURI_APP_IDENTIFIER;
|
|
57
|
+
const tauriDir = getTauriConfigDir(identifier);
|
|
58
|
+
if (checkExisting) {
|
|
59
|
+
const legacyDir = getCliConfigDir();
|
|
60
|
+
const legacyConfig = join(legacyDir, 'opencode.json');
|
|
61
|
+
const legacyConfigC = join(legacyDir, 'opencode.jsonc');
|
|
62
|
+
if (existsSync(legacyConfig) || existsSync(legacyConfigC)) {
|
|
63
|
+
return legacyDir;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return tauriDir;
|
|
67
|
+
}
|
|
68
|
+
export function getOpenCodeConfigPaths(options) {
|
|
69
|
+
const configDir = getOpenCodeConfigDir(options);
|
|
70
|
+
return {
|
|
71
|
+
configDir,
|
|
72
|
+
configJson: join(configDir, 'opencode.json'),
|
|
73
|
+
configJsonc: join(configDir, 'opencode.jsonc'),
|
|
74
|
+
packageJson: join(configDir, 'package.json'),
|
|
75
|
+
omoConfig: join(configDir, 'oh-my-opencode.json'),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export function detectExistingConfigDir(binary, version) {
|
|
79
|
+
const locations = [];
|
|
80
|
+
const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
81
|
+
if (envConfigDir) {
|
|
82
|
+
locations.push(resolve(envConfigDir));
|
|
83
|
+
}
|
|
84
|
+
if (binary === 'opencode-desktop') {
|
|
85
|
+
const identifier = isDevBuild(version)
|
|
86
|
+
? TAURI_APP_IDENTIFIER_DEV
|
|
87
|
+
: TAURI_APP_IDENTIFIER;
|
|
88
|
+
locations.push(getTauriConfigDir(identifier));
|
|
89
|
+
if (isDevBuild(version)) {
|
|
90
|
+
locations.push(getTauriConfigDir(TAURI_APP_IDENTIFIER));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
locations.push(getCliConfigDir());
|
|
94
|
+
for (const dir of locations) {
|
|
95
|
+
const configJson = join(dir, 'opencode.json');
|
|
96
|
+
const configJsonc = join(dir, 'opencode.jsonc');
|
|
97
|
+
if (existsSync(configJson) || existsSync(configJsonc)) {
|
|
98
|
+
return dir;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
56
102
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|