@wangzhizhi/remi 0.0.1-alpha
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/README.md +9 -0
- package/dist/doctor.js +108 -0
- package/dist/git.js +41 -0
- package/dist/help.js +27 -0
- package/dist/i18n.js +422 -0
- package/dist/index.js +97 -0
- package/dist/initPrompt.js +17 -0
- package/dist/model.js +116 -0
- package/dist/modelSelection.js +34 -0
- package/dist/permissionDisplay.js +46 -0
- package/dist/permissions.js +206 -0
- package/dist/repl.js +346 -0
- package/dist/resume.js +3 -0
- package/dist/setup.js +62 -0
- package/dist/statusline.js +59 -0
- package/dist/style.js +48 -0
- package/dist/syntaxTheme.js +39 -0
- package/dist/tui/RemiApp.js +1756 -0
- package/dist/tui/commands.js +427 -0
- package/dist/tui/index.js +42 -0
- package/dist/tui/renderers/Header.js +28 -0
- package/dist/tui/renderers/MessageList.js +1176 -0
- package/dist/tui/renderers/PromptBox.js +118 -0
- package/dist/tui/renderers/StatusLine.js +124 -0
- package/dist/tui/renderers/WorkingIndicator.js +70 -0
- package/dist/tui/slashCommandHighlight.js +8 -0
- package/dist/tui/theme.js +13 -0
- package/dist/tui/types.js +1 -0
- package/dist/usage.js +66 -0
- package/dist/version.js +5 -0
- package/node_modules/@remi/compact/dist/index.js +389 -0
- package/node_modules/@remi/compact/package.json +8 -0
- package/node_modules/@remi/config/dist/index.js +426 -0
- package/node_modules/@remi/config/package.json +8 -0
- package/node_modules/@remi/core/dist/contextBuilder.js +344 -0
- package/node_modules/@remi/core/dist/directoryOverview.js +359 -0
- package/node_modules/@remi/core/dist/index.js +2843 -0
- package/node_modules/@remi/core/dist/projectInstructions.js +123 -0
- package/node_modules/@remi/core/dist/responseStyles.js +98 -0
- package/node_modules/@remi/core/package.json +8 -0
- package/node_modules/@remi/llm/dist/index.js +804 -0
- package/node_modules/@remi/llm/package.json +8 -0
- package/node_modules/@remi/memory/dist/index.js +312 -0
- package/node_modules/@remi/memory/package.json +8 -0
- package/node_modules/@remi/permissions/dist/index.js +90 -0
- package/node_modules/@remi/permissions/package.json +8 -0
- package/node_modules/@remi/sessions/dist/index.js +370 -0
- package/node_modules/@remi/sessions/package.json +8 -0
- package/node_modules/@remi/skills/dist/index.js +273 -0
- package/node_modules/@remi/skills/package.json +8 -0
- package/node_modules/@remi/terminal-markdown/dist/index.js +1412 -0
- package/node_modules/@remi/terminal-markdown/package.json +8 -0
- package/node_modules/@remi/tools/dist/index.js +3875 -0
- package/node_modules/@remi/tools/package.json +8 -0
- package/package.json +48 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
export const configPackageName = '@remi/config';
|
|
5
|
+
export const languageCodes = ['en', 'zh-Hans'];
|
|
6
|
+
export const modelRoles = ['main', 'planning', 'subagent', 'compact', 'memory', 'toolRepair', 'embedding'];
|
|
7
|
+
export const statusLineItemIds = [
|
|
8
|
+
'model',
|
|
9
|
+
'cwd',
|
|
10
|
+
'git-branch',
|
|
11
|
+
'branch-changes',
|
|
12
|
+
'context-remaining',
|
|
13
|
+
'used-tokens',
|
|
14
|
+
'total-input-tokens',
|
|
15
|
+
'total-output-tokens',
|
|
16
|
+
'run-state',
|
|
17
|
+
'permissions',
|
|
18
|
+
];
|
|
19
|
+
export const permissionProfiles = ['default', 'auto-review', 'full-access'];
|
|
20
|
+
const deepSeekApiKey = '${DEEPSEEK_API_KEY}';
|
|
21
|
+
const defaultAnthropicBaseURL = 'https://api.anthropic.com';
|
|
22
|
+
export function createDeepSeekPresetConfig() {
|
|
23
|
+
return {
|
|
24
|
+
activeProfile: 'daily',
|
|
25
|
+
providers: {
|
|
26
|
+
deepseek: {
|
|
27
|
+
type: 'openai-compatible',
|
|
28
|
+
baseURL: 'https://api.deepseek.com',
|
|
29
|
+
apiKey: deepSeekApiKey,
|
|
30
|
+
apiKeyEnv: 'DEEPSEEK_API_KEY',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
models: {
|
|
34
|
+
'deepseek.flash': {
|
|
35
|
+
provider: 'deepseek',
|
|
36
|
+
model: 'deepseek-v4-flash',
|
|
37
|
+
displayName: 'DeepSeek-V4-Flash',
|
|
38
|
+
contextWindow: 1_000_000,
|
|
39
|
+
maxOutputTokens: 384_000,
|
|
40
|
+
supportsTools: true,
|
|
41
|
+
supportsReasoning: true,
|
|
42
|
+
tokenEstimator: 'deepseek-local-tokenizer',
|
|
43
|
+
tokenEstimateProfile: 'code-heavy',
|
|
44
|
+
usageShape: 'openai-compatible',
|
|
45
|
+
cacheUsageShape: 'openai-prompt-token-details',
|
|
46
|
+
},
|
|
47
|
+
'deepseek.pro': {
|
|
48
|
+
provider: 'deepseek',
|
|
49
|
+
model: 'deepseek-v4-pro',
|
|
50
|
+
displayName: 'DeepSeek-V4-Pro',
|
|
51
|
+
contextWindow: 1_000_000,
|
|
52
|
+
maxOutputTokens: 384_000,
|
|
53
|
+
supportsTools: true,
|
|
54
|
+
supportsReasoning: true,
|
|
55
|
+
tokenEstimator: 'deepseek-local-tokenizer',
|
|
56
|
+
tokenEstimateProfile: 'code-heavy',
|
|
57
|
+
usageShape: 'openai-compatible',
|
|
58
|
+
cacheUsageShape: 'openai-prompt-token-details',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
modelSets: {
|
|
62
|
+
deepseek: ['deepseek.flash', 'deepseek.pro'],
|
|
63
|
+
},
|
|
64
|
+
profiles: {
|
|
65
|
+
daily: {
|
|
66
|
+
roles: {
|
|
67
|
+
main: 'deepseek.flash',
|
|
68
|
+
planning: 'deepseek.pro',
|
|
69
|
+
subagent: 'deepseek.flash',
|
|
70
|
+
compact: 'deepseek.flash',
|
|
71
|
+
memory: 'deepseek.flash',
|
|
72
|
+
toolRepair: 'deepseek.flash',
|
|
73
|
+
},
|
|
74
|
+
effort: 'medium',
|
|
75
|
+
},
|
|
76
|
+
pro: {
|
|
77
|
+
extends: 'daily',
|
|
78
|
+
roles: {
|
|
79
|
+
main: 'deepseek.pro',
|
|
80
|
+
planning: 'deepseek.pro',
|
|
81
|
+
compact: 'deepseek.pro',
|
|
82
|
+
},
|
|
83
|
+
effort: 'high',
|
|
84
|
+
},
|
|
85
|
+
flash: {
|
|
86
|
+
extends: 'daily',
|
|
87
|
+
roles: {
|
|
88
|
+
main: 'deepseek.flash',
|
|
89
|
+
planning: 'deepseek.flash',
|
|
90
|
+
compact: 'deepseek.flash',
|
|
91
|
+
},
|
|
92
|
+
effort: 'low',
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export function createClaudeCodeEnvConfig(env = process.env) {
|
|
98
|
+
const mainModel = env['ANTHROPIC_MODEL'];
|
|
99
|
+
const smallModel = env['ANTHROPIC_SMALL_FAST_MODEL'] ?? mainModel;
|
|
100
|
+
const apiKeyEnv = env['ANTHROPIC_AUTH_TOKEN'] ? 'ANTHROPIC_AUTH_TOKEN' : env['ANTHROPIC_API_KEY'] ? 'ANTHROPIC_API_KEY' : undefined;
|
|
101
|
+
const baseURL = env['ANTHROPIC_BASE_URL'] ?? defaultAnthropicBaseURL;
|
|
102
|
+
const config = {};
|
|
103
|
+
if (apiKeyEnv || env['ANTHROPIC_BASE_URL']) {
|
|
104
|
+
config.providers = {
|
|
105
|
+
claudeCode: {
|
|
106
|
+
type: 'anthropic-compatible',
|
|
107
|
+
baseURL,
|
|
108
|
+
...(apiKeyEnv ? { apiKey: `\${${apiKeyEnv}}`, apiKeyEnv } : {}),
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (!mainModel) {
|
|
113
|
+
return config;
|
|
114
|
+
}
|
|
115
|
+
const models = {
|
|
116
|
+
'claude-code.main': createClaudeCodeModelConfig(mainModel),
|
|
117
|
+
};
|
|
118
|
+
const mainAlias = 'claude-code.main';
|
|
119
|
+
let smallAlias = mainAlias;
|
|
120
|
+
if (smallModel && smallModel !== mainModel) {
|
|
121
|
+
smallAlias = 'claude-code.small';
|
|
122
|
+
models[smallAlias] = createClaudeCodeModelConfig(smallModel);
|
|
123
|
+
}
|
|
124
|
+
const envImportConfig = {
|
|
125
|
+
activeProfile: 'claude-code',
|
|
126
|
+
models,
|
|
127
|
+
modelSets: {
|
|
128
|
+
'claude-code': Object.keys(models),
|
|
129
|
+
},
|
|
130
|
+
profiles: {
|
|
131
|
+
'claude-code': {
|
|
132
|
+
roles: {
|
|
133
|
+
main: mainAlias,
|
|
134
|
+
planning: mainAlias,
|
|
135
|
+
subagent: smallAlias,
|
|
136
|
+
compact: smallAlias,
|
|
137
|
+
memory: smallAlias,
|
|
138
|
+
toolRepair: smallAlias,
|
|
139
|
+
},
|
|
140
|
+
effort: 'medium',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
if (config.providers) {
|
|
145
|
+
envImportConfig.providers = config.providers;
|
|
146
|
+
}
|
|
147
|
+
return mergeRemiConfig(config, envImportConfig);
|
|
148
|
+
}
|
|
149
|
+
export function configPaths(options = {}) {
|
|
150
|
+
const cwd = options.cwd ?? process.cwd();
|
|
151
|
+
const homeDir = options.homeDir ?? homedir();
|
|
152
|
+
const sources = [
|
|
153
|
+
{ kind: 'user', path: userConfigPath(homeDir), exists: false },
|
|
154
|
+
{ kind: 'project', path: join(cwd, '.agent', 'config.json'), exists: false },
|
|
155
|
+
{ kind: 'local', path: join(cwd, '.agent', 'config.local.json'), exists: false },
|
|
156
|
+
];
|
|
157
|
+
return sources.map(source => ({ ...source, exists: existsSync(source.path) }));
|
|
158
|
+
}
|
|
159
|
+
export function userConfigPath(homeDir = homedir()) {
|
|
160
|
+
return join(homeDir, '.remi', 'config.json');
|
|
161
|
+
}
|
|
162
|
+
export function readUserRemiConfig(homeDir = homedir()) {
|
|
163
|
+
const path = userConfigPath(homeDir);
|
|
164
|
+
if (!existsSync(path)) {
|
|
165
|
+
return {};
|
|
166
|
+
}
|
|
167
|
+
return parseConfigFile(path);
|
|
168
|
+
}
|
|
169
|
+
export function writeUserRemiConfig(config, homeDir = homedir()) {
|
|
170
|
+
mkdirSync(join(homeDir, '.remi'), { recursive: true });
|
|
171
|
+
writeFileSync(userConfigPath(homeDir), `${JSON.stringify(config, null, 2)}\n`);
|
|
172
|
+
}
|
|
173
|
+
export function projectConfigPath(cwd = process.cwd()) {
|
|
174
|
+
return join(cwd, '.agent', 'config.json');
|
|
175
|
+
}
|
|
176
|
+
export function readProjectRemiConfig(cwd = process.cwd()) {
|
|
177
|
+
const path = projectConfigPath(cwd);
|
|
178
|
+
if (!existsSync(path)) {
|
|
179
|
+
return {};
|
|
180
|
+
}
|
|
181
|
+
return parseConfigFile(path);
|
|
182
|
+
}
|
|
183
|
+
export function writeProjectRemiConfig(config, cwd = process.cwd()) {
|
|
184
|
+
mkdirSync(join(cwd, '.agent'), { recursive: true });
|
|
185
|
+
writeFileSync(projectConfigPath(cwd), `${JSON.stringify(config, null, 2)}\n`);
|
|
186
|
+
}
|
|
187
|
+
export function isPermissionProfile(value) {
|
|
188
|
+
return typeof value === 'string' && permissionProfiles.includes(value);
|
|
189
|
+
}
|
|
190
|
+
export function normalizePermissionProfile(permissions) {
|
|
191
|
+
if (isPermissionProfile(permissions?.profile)) {
|
|
192
|
+
return permissions.profile;
|
|
193
|
+
}
|
|
194
|
+
if (permissions?.mode === 'bypass') {
|
|
195
|
+
return 'full-access';
|
|
196
|
+
}
|
|
197
|
+
return 'default';
|
|
198
|
+
}
|
|
199
|
+
export function permissionProfileToToolMode(profile) {
|
|
200
|
+
if (profile === 'full-access') {
|
|
201
|
+
return 'bypass';
|
|
202
|
+
}
|
|
203
|
+
return 'auto';
|
|
204
|
+
}
|
|
205
|
+
export function resolveToolPermissionMode(permissions) {
|
|
206
|
+
if (isPermissionProfile(permissions?.profile)) {
|
|
207
|
+
return permissionProfileToToolMode(permissions.profile);
|
|
208
|
+
}
|
|
209
|
+
return permissions?.mode ?? permissionProfileToToolMode('default');
|
|
210
|
+
}
|
|
211
|
+
export function transitionPermissionProfile(config, profile) {
|
|
212
|
+
const permissions = {
|
|
213
|
+
...config.permissions,
|
|
214
|
+
profile,
|
|
215
|
+
mode: permissionProfileToToolMode(profile),
|
|
216
|
+
};
|
|
217
|
+
return {
|
|
218
|
+
...config,
|
|
219
|
+
permissions,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
export function interpolateEnv(value, env = process.env) {
|
|
223
|
+
const match = /^\$\{([A-Z0-9_]+)\}$/.exec(value);
|
|
224
|
+
if (!match) {
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
const envName = match[1];
|
|
228
|
+
if (!envName) {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
return env[envName];
|
|
232
|
+
}
|
|
233
|
+
export function providerApiKeyStatus(provider, env = process.env) {
|
|
234
|
+
return resolveProviderApiKey(provider, env) ? 'configured' : 'missing';
|
|
235
|
+
}
|
|
236
|
+
export function resolveProviderApiKey(provider, env = process.env) {
|
|
237
|
+
if (provider.apiKeyEnv && env[provider.apiKeyEnv]) {
|
|
238
|
+
return env[provider.apiKeyEnv];
|
|
239
|
+
}
|
|
240
|
+
if (provider.apiKey) {
|
|
241
|
+
return interpolateEnv(provider.apiKey, env);
|
|
242
|
+
}
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
export function hasProviderApiKey(provider, env = process.env) {
|
|
246
|
+
if (provider.apiKeyEnv && env[provider.apiKeyEnv]) {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
if (provider.apiKey && interpolateEnv(provider.apiKey, env)) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
export function mergeRemiConfig(...configs) {
|
|
255
|
+
return configs.reduce((merged, config) => {
|
|
256
|
+
const result = {};
|
|
257
|
+
const schema = config.$schema ?? merged.$schema;
|
|
258
|
+
const activeProfile = config.activeProfile ?? merged.activeProfile;
|
|
259
|
+
const language = config.language ?? merged.language;
|
|
260
|
+
const responseStyle = config.responseStyle ?? merged.responseStyle;
|
|
261
|
+
const syntaxTheme = config.syntaxTheme ?? merged.syntaxTheme;
|
|
262
|
+
const permissions = config.permissions || merged.permissions
|
|
263
|
+
? {
|
|
264
|
+
...merged.permissions,
|
|
265
|
+
...config.permissions,
|
|
266
|
+
}
|
|
267
|
+
: undefined;
|
|
268
|
+
const agent = config.agent || merged.agent
|
|
269
|
+
? {
|
|
270
|
+
...merged.agent,
|
|
271
|
+
...config.agent,
|
|
272
|
+
}
|
|
273
|
+
: undefined;
|
|
274
|
+
const statusLine = config.statusLine || merged.statusLine
|
|
275
|
+
? {
|
|
276
|
+
...merged.statusLine,
|
|
277
|
+
...config.statusLine,
|
|
278
|
+
}
|
|
279
|
+
: undefined;
|
|
280
|
+
const mergedDisabledSkills = uniqueStrings([...(merged.skills?.disabled ?? []), ...(config.skills?.disabled ?? [])]);
|
|
281
|
+
const skills = config.skills || merged.skills
|
|
282
|
+
? {
|
|
283
|
+
...merged.skills,
|
|
284
|
+
...config.skills,
|
|
285
|
+
...(mergedDisabledSkills ? { disabled: mergedDisabledSkills } : {}),
|
|
286
|
+
}
|
|
287
|
+
: undefined;
|
|
288
|
+
const providers = {
|
|
289
|
+
...merged.providers,
|
|
290
|
+
...config.providers,
|
|
291
|
+
};
|
|
292
|
+
const models = {
|
|
293
|
+
...merged.models,
|
|
294
|
+
...config.models,
|
|
295
|
+
};
|
|
296
|
+
const modelSets = {
|
|
297
|
+
...merged.modelSets,
|
|
298
|
+
...config.modelSets,
|
|
299
|
+
};
|
|
300
|
+
const profiles = mergeProfiles(merged.profiles, config.profiles);
|
|
301
|
+
if (schema !== undefined)
|
|
302
|
+
result.$schema = schema;
|
|
303
|
+
if (activeProfile !== undefined)
|
|
304
|
+
result.activeProfile = activeProfile;
|
|
305
|
+
if (language !== undefined)
|
|
306
|
+
result.language = language;
|
|
307
|
+
if (responseStyle !== undefined)
|
|
308
|
+
result.responseStyle = responseStyle;
|
|
309
|
+
if (syntaxTheme !== undefined)
|
|
310
|
+
result.syntaxTheme = syntaxTheme;
|
|
311
|
+
if (permissions !== undefined)
|
|
312
|
+
result.permissions = permissions;
|
|
313
|
+
if (agent !== undefined)
|
|
314
|
+
result.agent = agent;
|
|
315
|
+
if (statusLine !== undefined)
|
|
316
|
+
result.statusLine = statusLine;
|
|
317
|
+
if (skills !== undefined)
|
|
318
|
+
result.skills = skills;
|
|
319
|
+
if (Object.keys(providers).length > 0)
|
|
320
|
+
result.providers = providers;
|
|
321
|
+
if (Object.keys(models).length > 0)
|
|
322
|
+
result.models = models;
|
|
323
|
+
if (Object.keys(modelSets).length > 0)
|
|
324
|
+
result.modelSets = modelSets;
|
|
325
|
+
if (profiles !== undefined)
|
|
326
|
+
result.profiles = profiles;
|
|
327
|
+
return result;
|
|
328
|
+
}, {});
|
|
329
|
+
}
|
|
330
|
+
function mergeProfiles(base, next) {
|
|
331
|
+
if (!base && !next) {
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
const result = { ...(base ?? {}) };
|
|
335
|
+
for (const [name, profile] of Object.entries(next ?? {})) {
|
|
336
|
+
const previous = result[name];
|
|
337
|
+
const mergedProfile = {
|
|
338
|
+
roles: {
|
|
339
|
+
...(previous?.roles ?? {}),
|
|
340
|
+
...profile.roles,
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
const extendedFrom = profile.extends ?? previous?.extends;
|
|
344
|
+
const effort = profile.effort ?? previous?.effort;
|
|
345
|
+
if (extendedFrom !== undefined)
|
|
346
|
+
mergedProfile.extends = extendedFrom;
|
|
347
|
+
if (effort !== undefined)
|
|
348
|
+
mergedProfile.effort = effort;
|
|
349
|
+
result[name] = mergedProfile;
|
|
350
|
+
}
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
export function loadRemiConfig(options = {}) {
|
|
354
|
+
const sources = configPaths(options);
|
|
355
|
+
const diagnostics = [];
|
|
356
|
+
const env = options.env ?? process.env;
|
|
357
|
+
const envConfig = createClaudeCodeEnvConfig(env);
|
|
358
|
+
const envSource = { kind: 'env', path: 'environment:claude-code', exists: hasConfigContent(envConfig) };
|
|
359
|
+
const configs = envSource.exists ? [envConfig] : [];
|
|
360
|
+
for (const source of sources) {
|
|
361
|
+
if (!source.exists) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
configs.push(parseConfigFile(source.path));
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
diagnostics.push({
|
|
369
|
+
level: 'error',
|
|
370
|
+
path: source.path,
|
|
371
|
+
message: error instanceof Error ? error.message : 'Failed to parse config file',
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
config: mergeRemiConfig(...configs),
|
|
377
|
+
sources: [envSource, ...sources],
|
|
378
|
+
diagnostics,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function createClaudeCodeModelConfig(model) {
|
|
382
|
+
const lower = model.toLowerCase();
|
|
383
|
+
const isDeepSeek = lower.includes('deepseek');
|
|
384
|
+
const isSmall = lower.includes('small') || lower.includes('haiku') || lower.includes('flash') || lower.includes('chat');
|
|
385
|
+
return {
|
|
386
|
+
provider: 'claudeCode',
|
|
387
|
+
model,
|
|
388
|
+
displayName: model,
|
|
389
|
+
contextWindow: isDeepSeek ? 1_000_000 : 200_000,
|
|
390
|
+
maxOutputTokens: isDeepSeek ? 384_000 : 64_000,
|
|
391
|
+
supportsTools: true,
|
|
392
|
+
supportsReasoning: !isSmall,
|
|
393
|
+
tokenEstimator: isDeepSeek ? 'deepseek-local-tokenizer' : 'anthropic-count-tokens',
|
|
394
|
+
tokenEstimateProfile: isDeepSeek ? 'code-heavy' : 'default',
|
|
395
|
+
usageShape: 'anthropic-compatible',
|
|
396
|
+
cacheUsageShape: 'anthropic-cache-input',
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function hasConfigContent(config) {
|
|
400
|
+
return Boolean(config.activeProfile ||
|
|
401
|
+
config.language ||
|
|
402
|
+
config.responseStyle ||
|
|
403
|
+
config.syntaxTheme ||
|
|
404
|
+
config.statusLine ||
|
|
405
|
+
config.permissions ||
|
|
406
|
+
config.agent ||
|
|
407
|
+
config.skills ||
|
|
408
|
+
Object.keys(config.providers ?? {}).length > 0 ||
|
|
409
|
+
Object.keys(config.models ?? {}).length > 0 ||
|
|
410
|
+
Object.keys(config.modelSets ?? {}).length > 0 ||
|
|
411
|
+
Object.keys(config.profiles ?? {}).length > 0);
|
|
412
|
+
}
|
|
413
|
+
function parseConfigFile(path) {
|
|
414
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
415
|
+
if (!isRecord(parsed)) {
|
|
416
|
+
throw new Error('Config root must be a JSON object');
|
|
417
|
+
}
|
|
418
|
+
return parsed;
|
|
419
|
+
}
|
|
420
|
+
function isRecord(value) {
|
|
421
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
422
|
+
}
|
|
423
|
+
function uniqueStrings(values) {
|
|
424
|
+
const unique = Array.from(new Set(values.map(value => value.trim()).filter(Boolean)));
|
|
425
|
+
return unique.length > 0 ? unique : undefined;
|
|
426
|
+
}
|