@codelia/config 0.1.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/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # @codelia/config
2
+
3
+ Config schema + registry (no I/O).
4
+
5
+ This package does not read files or environment variables. It only provides:
6
+ - Config types
7
+ - A registry for defaults
8
+ - Merge/resolve of loaded config layers
9
+
10
+ File I/O lives in `@codelia/config-loader`.
11
+
12
+ ## Minimal config.json
13
+
14
+ ```json
15
+ {
16
+ "version": 1,
17
+ "model": {
18
+ "provider": "openai",
19
+ "name": "gpt-5.2-codex",
20
+ "reasoning": "medium",
21
+ "verbosity": "medium"
22
+ },
23
+ "permissions": {
24
+ "allow": [
25
+ { "tool": "read" },
26
+ { "tool": "skill_load", "skill_name": "repo-review" }
27
+ ],
28
+ "deny": [{ "tool": "bash", "command": "rm" }]
29
+ }
30
+ }
31
+ ```
32
+
33
+ ## How it is used (runtime/CLI)
34
+
35
+ 1) Modules register defaults into the shared registry.
36
+ 2) CLI/runtime loads the config file.
37
+ 3) CLI/runtime merges defaults + config and uses the effective values.
38
+
39
+ ```ts
40
+ import { configRegistry } from "@codelia/config";
41
+ import { loadConfig } from "@codelia/config-loader";
42
+
43
+ // defaults are registered by modules (e.g. @codelia/core on import)
44
+ const config = await loadConfig("/path/to/config.json");
45
+ const effective = configRegistry.resolve([config]);
46
+ ```
47
+
48
+ ## Where config.json is loaded
49
+
50
+ Current behavior:
51
+ - Global config only.
52
+ - Path is resolved in CLI/runtime.
53
+ - `CODELIA_CONFIG_PATH` overrides the global config file location.
54
+
55
+ Planned:
56
+ - Project config at `.codelia/config.json` (repo-local override).
57
+
58
+ See `docs/specs/storage-layout.md` for the default global path (home/XDG).
package/dist/index.cjs ADDED
@@ -0,0 +1,258 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CONFIG_VERSION: () => CONFIG_VERSION,
24
+ ConfigRegistry: () => ConfigRegistry,
25
+ configRegistry: () => configRegistry,
26
+ parseConfig: () => parseConfig
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+ var CONFIG_VERSION = 1;
30
+ var MCP_SERVER_ID_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;
31
+ var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
32
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
33
+ var pickString = (value) => typeof value === "string" ? value : void 0;
34
+ var pickBoolean = (value) => typeof value === "boolean" ? value : void 0;
35
+ var pickNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
36
+ var pickPositiveInt = (value) => {
37
+ const num = pickNumber(value);
38
+ if (num === void 0) return void 0;
39
+ if (!Number.isInteger(num) || num <= 0) return void 0;
40
+ return num;
41
+ };
42
+ var pickStringArray = (value) => {
43
+ if (!Array.isArray(value)) return void 0;
44
+ const values = value.filter(
45
+ (entry) => typeof entry === "string"
46
+ );
47
+ return values.length ? values : void 0;
48
+ };
49
+ var pickStringRecord = (value) => {
50
+ if (!isRecord(value)) return void 0;
51
+ const entries = Object.entries(value).filter(
52
+ ([, entry]) => typeof entry === "string"
53
+ );
54
+ if (!entries.length) return void 0;
55
+ return Object.fromEntries(entries);
56
+ };
57
+ var parsePermissionRule = (value) => {
58
+ if (!isRecord(value)) return null;
59
+ const tool = pickString(value.tool);
60
+ if (!tool) return null;
61
+ const command = pickString(value.command);
62
+ const commandGlob = pickString(value.command_glob);
63
+ const rawSkillName = pickString(value.skill_name)?.trim().toLowerCase();
64
+ const skillName = rawSkillName && SKILL_NAME_PATTERN.test(rawSkillName) ? rawSkillName : void 0;
65
+ return {
66
+ tool,
67
+ ...command ? { command } : {},
68
+ ...commandGlob ? { command_glob: commandGlob } : {},
69
+ ...skillName ? { skill_name: skillName } : {}
70
+ };
71
+ };
72
+ var parsePermissionRules = (value) => {
73
+ if (!Array.isArray(value)) return void 0;
74
+ const rules = value.map((entry) => parsePermissionRule(entry)).filter((entry) => !!entry);
75
+ return rules.length ? rules : void 0;
76
+ };
77
+ var parseMcpServerConfig = (value) => {
78
+ if (!isRecord(value)) return null;
79
+ const transport = value.transport === "http" || value.transport === "stdio" ? value.transport : void 0;
80
+ if (!transport) return null;
81
+ const enabled = pickBoolean(value.enabled);
82
+ const requestTimeoutMs = pickNumber(value.request_timeout_ms);
83
+ const oauth = isRecord(value.oauth) ? {
84
+ ...pickString(value.oauth.authorization_url) ? { authorization_url: pickString(value.oauth.authorization_url) } : {},
85
+ ...pickString(value.oauth.token_url) ? { token_url: pickString(value.oauth.token_url) } : {},
86
+ ...pickString(value.oauth.registration_url) ? { registration_url: pickString(value.oauth.registration_url) } : {},
87
+ ...pickString(value.oauth.client_id) ? { client_id: pickString(value.oauth.client_id) } : {},
88
+ ...pickString(value.oauth.client_secret) ? { client_secret: pickString(value.oauth.client_secret) } : {},
89
+ ...pickString(value.oauth.scope) ? { scope: pickString(value.oauth.scope) } : {}
90
+ } : void 0;
91
+ if (transport === "http") {
92
+ const url = pickString(value.url);
93
+ if (!url) return null;
94
+ return {
95
+ transport: "http",
96
+ ...enabled !== void 0 ? { enabled } : {},
97
+ url,
98
+ ...pickStringRecord(value.headers) ? { headers: pickStringRecord(value.headers) } : {},
99
+ ...pickStringRecord(value.env) ? { env: pickStringRecord(value.env) } : {},
100
+ ...requestTimeoutMs !== void 0 ? { request_timeout_ms: requestTimeoutMs } : {},
101
+ ...oauth && Object.keys(oauth).length ? { oauth } : {}
102
+ };
103
+ }
104
+ const command = pickString(value.command);
105
+ if (!command) return null;
106
+ return {
107
+ transport: "stdio",
108
+ ...enabled !== void 0 ? { enabled } : {},
109
+ command,
110
+ ...pickStringArray(value.args) ? { args: pickStringArray(value.args) } : {},
111
+ ...pickString(value.cwd) ? { cwd: pickString(value.cwd) } : {},
112
+ ...pickStringRecord(value.env) ? { env: pickStringRecord(value.env) } : {},
113
+ ...requestTimeoutMs !== void 0 ? { request_timeout_ms: requestTimeoutMs } : {},
114
+ ...oauth && Object.keys(oauth).length ? { oauth } : {}
115
+ };
116
+ };
117
+ var parseMcpConfig = (value) => {
118
+ if (!isRecord(value) || !isRecord(value.servers)) return void 0;
119
+ const servers = {};
120
+ for (const [serverId, serverValue] of Object.entries(value.servers)) {
121
+ if (!MCP_SERVER_ID_PATTERN.test(serverId)) {
122
+ continue;
123
+ }
124
+ const parsed = parseMcpServerConfig(serverValue);
125
+ if (parsed) {
126
+ servers[serverId] = parsed;
127
+ }
128
+ }
129
+ if (!Object.keys(servers).length) return void 0;
130
+ return { servers };
131
+ };
132
+ var parseSkillsConfig = (value) => {
133
+ if (!isRecord(value)) return void 0;
134
+ const enabled = pickBoolean(value.enabled);
135
+ const initial = isRecord(value.initial) ? {
136
+ ...pickPositiveInt(value.initial.maxEntries) !== void 0 ? { maxEntries: pickPositiveInt(value.initial.maxEntries) } : {},
137
+ ...pickPositiveInt(value.initial.maxBytes) !== void 0 ? { maxBytes: pickPositiveInt(value.initial.maxBytes) } : {}
138
+ } : void 0;
139
+ const search = isRecord(value.search) ? {
140
+ ...pickPositiveInt(value.search.defaultLimit) !== void 0 ? { defaultLimit: pickPositiveInt(value.search.defaultLimit) } : {},
141
+ ...pickPositiveInt(value.search.maxLimit) !== void 0 ? { maxLimit: pickPositiveInt(value.search.maxLimit) } : {}
142
+ } : void 0;
143
+ const result = {};
144
+ if (enabled !== void 0) {
145
+ result.enabled = enabled;
146
+ }
147
+ if (initial && Object.keys(initial).length > 0) {
148
+ result.initial = initial;
149
+ }
150
+ if (search && Object.keys(search).length > 0) {
151
+ result.search = search;
152
+ }
153
+ return Object.keys(result).length > 0 ? result : void 0;
154
+ };
155
+ var parseConfig = (value, sourceLabel) => {
156
+ if (!isRecord(value)) {
157
+ throw new Error(`${sourceLabel}: config must be an object`);
158
+ }
159
+ const version = value.version;
160
+ if (version !== CONFIG_VERSION) {
161
+ throw new Error(`${sourceLabel}: unsupported version ${String(version)}`);
162
+ }
163
+ const modelValue = value.model;
164
+ const model = isRecord(modelValue) ? {
165
+ provider: pickString(modelValue.provider),
166
+ name: pickString(modelValue.name),
167
+ reasoning: pickString(modelValue.reasoning),
168
+ verbosity: pickString(modelValue.verbosity)
169
+ } : void 0;
170
+ const permissionsValue = value.permissions;
171
+ const permissions = isRecord(permissionsValue) ? {
172
+ allow: parsePermissionRules(permissionsValue.allow),
173
+ deny: parsePermissionRules(permissionsValue.deny)
174
+ } : void 0;
175
+ const hasPermissions = permissions && (permissions.allow || permissions.deny) ? permissions : void 0;
176
+ const mcp = parseMcpConfig(value.mcp);
177
+ const skills = parseSkillsConfig(value.skills);
178
+ const result = { version, model };
179
+ if (hasPermissions) {
180
+ result.permissions = hasPermissions;
181
+ }
182
+ if (mcp) {
183
+ result.mcp = mcp;
184
+ }
185
+ if (skills) {
186
+ result.skills = skills;
187
+ }
188
+ return result;
189
+ };
190
+ var ConfigRegistry = class {
191
+ defaults = [];
192
+ registerDefaults(layer) {
193
+ this.defaults.push(layer);
194
+ }
195
+ resolve(layers) {
196
+ const merged = { version: CONFIG_VERSION };
197
+ for (const layer of [...this.defaults, ...layers]) {
198
+ if (layer?.model) {
199
+ merged.model = { ...merged.model, ...layer.model };
200
+ }
201
+ if (layer?.permissions) {
202
+ const nextAllow = layer.permissions.allow ?? [];
203
+ const nextDeny = layer.permissions.deny ?? [];
204
+ if (nextAllow.length > 0 || nextDeny.length > 0) {
205
+ merged.permissions ??= {};
206
+ if (nextAllow.length > 0) {
207
+ merged.permissions.allow = [
208
+ ...merged.permissions.allow ?? [],
209
+ ...nextAllow
210
+ ];
211
+ }
212
+ if (nextDeny.length > 0) {
213
+ merged.permissions.deny = [
214
+ ...merged.permissions.deny ?? [],
215
+ ...nextDeny
216
+ ];
217
+ }
218
+ }
219
+ }
220
+ if (layer?.mcp?.servers) {
221
+ merged.mcp = {
222
+ servers: {
223
+ ...merged.mcp?.servers ?? {},
224
+ ...layer.mcp.servers
225
+ }
226
+ };
227
+ }
228
+ if (layer?.skills) {
229
+ const nextSkills = layer.skills;
230
+ merged.skills ??= {};
231
+ if (nextSkills.enabled !== void 0) {
232
+ merged.skills.enabled = nextSkills.enabled;
233
+ }
234
+ if (nextSkills.initial) {
235
+ merged.skills.initial = {
236
+ ...merged.skills.initial ?? {},
237
+ ...nextSkills.initial
238
+ };
239
+ }
240
+ if (nextSkills.search) {
241
+ merged.skills.search = {
242
+ ...merged.skills.search ?? {},
243
+ ...nextSkills.search
244
+ };
245
+ }
246
+ }
247
+ }
248
+ return merged;
249
+ }
250
+ };
251
+ var configRegistry = new ConfigRegistry();
252
+ // Annotate the CommonJS export names for ESM import in node:
253
+ 0 && (module.exports = {
254
+ CONFIG_VERSION,
255
+ ConfigRegistry,
256
+ configRegistry,
257
+ parseConfig
258
+ });
@@ -0,0 +1,72 @@
1
+ type ModelConfig = {
2
+ provider?: string;
3
+ name?: string;
4
+ reasoning?: string;
5
+ verbosity?: string;
6
+ };
7
+ type PermissionRule = {
8
+ tool: string;
9
+ command?: string;
10
+ command_glob?: string;
11
+ skill_name?: string;
12
+ };
13
+ type PermissionsConfig = {
14
+ allow?: PermissionRule[];
15
+ deny?: PermissionRule[];
16
+ };
17
+ type McpServerConfig = {
18
+ transport: "stdio" | "http";
19
+ enabled?: boolean;
20
+ command?: string;
21
+ args?: string[];
22
+ cwd?: string;
23
+ env?: Record<string, string>;
24
+ url?: string;
25
+ headers?: Record<string, string>;
26
+ request_timeout_ms?: number;
27
+ oauth?: {
28
+ authorization_url?: string;
29
+ token_url?: string;
30
+ registration_url?: string;
31
+ client_id?: string;
32
+ client_secret?: string;
33
+ scope?: string;
34
+ };
35
+ };
36
+ type McpConfig = {
37
+ servers: Record<string, McpServerConfig>;
38
+ };
39
+ type SkillsConfig = {
40
+ enabled?: boolean;
41
+ initial?: {
42
+ maxEntries?: number;
43
+ maxBytes?: number;
44
+ };
45
+ search?: {
46
+ defaultLimit?: number;
47
+ maxLimit?: number;
48
+ };
49
+ };
50
+ type CodeliaConfig = {
51
+ version: number;
52
+ model?: ModelConfig;
53
+ permissions?: PermissionsConfig;
54
+ mcp?: McpConfig;
55
+ skills?: SkillsConfig;
56
+ };
57
+ declare const CONFIG_VERSION = 1;
58
+ declare const parseConfig: (value: unknown, sourceLabel: string) => CodeliaConfig;
59
+ type ConfigLayer = {
60
+ model?: ModelConfig;
61
+ permissions?: PermissionsConfig;
62
+ mcp?: McpConfig;
63
+ skills?: SkillsConfig;
64
+ };
65
+ declare class ConfigRegistry {
66
+ private readonly defaults;
67
+ registerDefaults(layer: ConfigLayer): void;
68
+ resolve(layers: Array<ConfigLayer | null | undefined>): CodeliaConfig;
69
+ }
70
+ declare const configRegistry: ConfigRegistry;
71
+
72
+ export { CONFIG_VERSION, type CodeliaConfig, ConfigRegistry, type McpConfig, type McpServerConfig, type ModelConfig, type PermissionRule, type PermissionsConfig, type SkillsConfig, configRegistry, parseConfig };
@@ -0,0 +1,72 @@
1
+ type ModelConfig = {
2
+ provider?: string;
3
+ name?: string;
4
+ reasoning?: string;
5
+ verbosity?: string;
6
+ };
7
+ type PermissionRule = {
8
+ tool: string;
9
+ command?: string;
10
+ command_glob?: string;
11
+ skill_name?: string;
12
+ };
13
+ type PermissionsConfig = {
14
+ allow?: PermissionRule[];
15
+ deny?: PermissionRule[];
16
+ };
17
+ type McpServerConfig = {
18
+ transport: "stdio" | "http";
19
+ enabled?: boolean;
20
+ command?: string;
21
+ args?: string[];
22
+ cwd?: string;
23
+ env?: Record<string, string>;
24
+ url?: string;
25
+ headers?: Record<string, string>;
26
+ request_timeout_ms?: number;
27
+ oauth?: {
28
+ authorization_url?: string;
29
+ token_url?: string;
30
+ registration_url?: string;
31
+ client_id?: string;
32
+ client_secret?: string;
33
+ scope?: string;
34
+ };
35
+ };
36
+ type McpConfig = {
37
+ servers: Record<string, McpServerConfig>;
38
+ };
39
+ type SkillsConfig = {
40
+ enabled?: boolean;
41
+ initial?: {
42
+ maxEntries?: number;
43
+ maxBytes?: number;
44
+ };
45
+ search?: {
46
+ defaultLimit?: number;
47
+ maxLimit?: number;
48
+ };
49
+ };
50
+ type CodeliaConfig = {
51
+ version: number;
52
+ model?: ModelConfig;
53
+ permissions?: PermissionsConfig;
54
+ mcp?: McpConfig;
55
+ skills?: SkillsConfig;
56
+ };
57
+ declare const CONFIG_VERSION = 1;
58
+ declare const parseConfig: (value: unknown, sourceLabel: string) => CodeliaConfig;
59
+ type ConfigLayer = {
60
+ model?: ModelConfig;
61
+ permissions?: PermissionsConfig;
62
+ mcp?: McpConfig;
63
+ skills?: SkillsConfig;
64
+ };
65
+ declare class ConfigRegistry {
66
+ private readonly defaults;
67
+ registerDefaults(layer: ConfigLayer): void;
68
+ resolve(layers: Array<ConfigLayer | null | undefined>): CodeliaConfig;
69
+ }
70
+ declare const configRegistry: ConfigRegistry;
71
+
72
+ export { CONFIG_VERSION, type CodeliaConfig, ConfigRegistry, type McpConfig, type McpServerConfig, type ModelConfig, type PermissionRule, type PermissionsConfig, type SkillsConfig, configRegistry, parseConfig };
package/dist/index.js ADDED
@@ -0,0 +1,230 @@
1
+ // src/index.ts
2
+ var CONFIG_VERSION = 1;
3
+ var MCP_SERVER_ID_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;
4
+ var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
5
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6
+ var pickString = (value) => typeof value === "string" ? value : void 0;
7
+ var pickBoolean = (value) => typeof value === "boolean" ? value : void 0;
8
+ var pickNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
9
+ var pickPositiveInt = (value) => {
10
+ const num = pickNumber(value);
11
+ if (num === void 0) return void 0;
12
+ if (!Number.isInteger(num) || num <= 0) return void 0;
13
+ return num;
14
+ };
15
+ var pickStringArray = (value) => {
16
+ if (!Array.isArray(value)) return void 0;
17
+ const values = value.filter(
18
+ (entry) => typeof entry === "string"
19
+ );
20
+ return values.length ? values : void 0;
21
+ };
22
+ var pickStringRecord = (value) => {
23
+ if (!isRecord(value)) return void 0;
24
+ const entries = Object.entries(value).filter(
25
+ ([, entry]) => typeof entry === "string"
26
+ );
27
+ if (!entries.length) return void 0;
28
+ return Object.fromEntries(entries);
29
+ };
30
+ var parsePermissionRule = (value) => {
31
+ if (!isRecord(value)) return null;
32
+ const tool = pickString(value.tool);
33
+ if (!tool) return null;
34
+ const command = pickString(value.command);
35
+ const commandGlob = pickString(value.command_glob);
36
+ const rawSkillName = pickString(value.skill_name)?.trim().toLowerCase();
37
+ const skillName = rawSkillName && SKILL_NAME_PATTERN.test(rawSkillName) ? rawSkillName : void 0;
38
+ return {
39
+ tool,
40
+ ...command ? { command } : {},
41
+ ...commandGlob ? { command_glob: commandGlob } : {},
42
+ ...skillName ? { skill_name: skillName } : {}
43
+ };
44
+ };
45
+ var parsePermissionRules = (value) => {
46
+ if (!Array.isArray(value)) return void 0;
47
+ const rules = value.map((entry) => parsePermissionRule(entry)).filter((entry) => !!entry);
48
+ return rules.length ? rules : void 0;
49
+ };
50
+ var parseMcpServerConfig = (value) => {
51
+ if (!isRecord(value)) return null;
52
+ const transport = value.transport === "http" || value.transport === "stdio" ? value.transport : void 0;
53
+ if (!transport) return null;
54
+ const enabled = pickBoolean(value.enabled);
55
+ const requestTimeoutMs = pickNumber(value.request_timeout_ms);
56
+ const oauth = isRecord(value.oauth) ? {
57
+ ...pickString(value.oauth.authorization_url) ? { authorization_url: pickString(value.oauth.authorization_url) } : {},
58
+ ...pickString(value.oauth.token_url) ? { token_url: pickString(value.oauth.token_url) } : {},
59
+ ...pickString(value.oauth.registration_url) ? { registration_url: pickString(value.oauth.registration_url) } : {},
60
+ ...pickString(value.oauth.client_id) ? { client_id: pickString(value.oauth.client_id) } : {},
61
+ ...pickString(value.oauth.client_secret) ? { client_secret: pickString(value.oauth.client_secret) } : {},
62
+ ...pickString(value.oauth.scope) ? { scope: pickString(value.oauth.scope) } : {}
63
+ } : void 0;
64
+ if (transport === "http") {
65
+ const url = pickString(value.url);
66
+ if (!url) return null;
67
+ return {
68
+ transport: "http",
69
+ ...enabled !== void 0 ? { enabled } : {},
70
+ url,
71
+ ...pickStringRecord(value.headers) ? { headers: pickStringRecord(value.headers) } : {},
72
+ ...pickStringRecord(value.env) ? { env: pickStringRecord(value.env) } : {},
73
+ ...requestTimeoutMs !== void 0 ? { request_timeout_ms: requestTimeoutMs } : {},
74
+ ...oauth && Object.keys(oauth).length ? { oauth } : {}
75
+ };
76
+ }
77
+ const command = pickString(value.command);
78
+ if (!command) return null;
79
+ return {
80
+ transport: "stdio",
81
+ ...enabled !== void 0 ? { enabled } : {},
82
+ command,
83
+ ...pickStringArray(value.args) ? { args: pickStringArray(value.args) } : {},
84
+ ...pickString(value.cwd) ? { cwd: pickString(value.cwd) } : {},
85
+ ...pickStringRecord(value.env) ? { env: pickStringRecord(value.env) } : {},
86
+ ...requestTimeoutMs !== void 0 ? { request_timeout_ms: requestTimeoutMs } : {},
87
+ ...oauth && Object.keys(oauth).length ? { oauth } : {}
88
+ };
89
+ };
90
+ var parseMcpConfig = (value) => {
91
+ if (!isRecord(value) || !isRecord(value.servers)) return void 0;
92
+ const servers = {};
93
+ for (const [serverId, serverValue] of Object.entries(value.servers)) {
94
+ if (!MCP_SERVER_ID_PATTERN.test(serverId)) {
95
+ continue;
96
+ }
97
+ const parsed = parseMcpServerConfig(serverValue);
98
+ if (parsed) {
99
+ servers[serverId] = parsed;
100
+ }
101
+ }
102
+ if (!Object.keys(servers).length) return void 0;
103
+ return { servers };
104
+ };
105
+ var parseSkillsConfig = (value) => {
106
+ if (!isRecord(value)) return void 0;
107
+ const enabled = pickBoolean(value.enabled);
108
+ const initial = isRecord(value.initial) ? {
109
+ ...pickPositiveInt(value.initial.maxEntries) !== void 0 ? { maxEntries: pickPositiveInt(value.initial.maxEntries) } : {},
110
+ ...pickPositiveInt(value.initial.maxBytes) !== void 0 ? { maxBytes: pickPositiveInt(value.initial.maxBytes) } : {}
111
+ } : void 0;
112
+ const search = isRecord(value.search) ? {
113
+ ...pickPositiveInt(value.search.defaultLimit) !== void 0 ? { defaultLimit: pickPositiveInt(value.search.defaultLimit) } : {},
114
+ ...pickPositiveInt(value.search.maxLimit) !== void 0 ? { maxLimit: pickPositiveInt(value.search.maxLimit) } : {}
115
+ } : void 0;
116
+ const result = {};
117
+ if (enabled !== void 0) {
118
+ result.enabled = enabled;
119
+ }
120
+ if (initial && Object.keys(initial).length > 0) {
121
+ result.initial = initial;
122
+ }
123
+ if (search && Object.keys(search).length > 0) {
124
+ result.search = search;
125
+ }
126
+ return Object.keys(result).length > 0 ? result : void 0;
127
+ };
128
+ var parseConfig = (value, sourceLabel) => {
129
+ if (!isRecord(value)) {
130
+ throw new Error(`${sourceLabel}: config must be an object`);
131
+ }
132
+ const version = value.version;
133
+ if (version !== CONFIG_VERSION) {
134
+ throw new Error(`${sourceLabel}: unsupported version ${String(version)}`);
135
+ }
136
+ const modelValue = value.model;
137
+ const model = isRecord(modelValue) ? {
138
+ provider: pickString(modelValue.provider),
139
+ name: pickString(modelValue.name),
140
+ reasoning: pickString(modelValue.reasoning),
141
+ verbosity: pickString(modelValue.verbosity)
142
+ } : void 0;
143
+ const permissionsValue = value.permissions;
144
+ const permissions = isRecord(permissionsValue) ? {
145
+ allow: parsePermissionRules(permissionsValue.allow),
146
+ deny: parsePermissionRules(permissionsValue.deny)
147
+ } : void 0;
148
+ const hasPermissions = permissions && (permissions.allow || permissions.deny) ? permissions : void 0;
149
+ const mcp = parseMcpConfig(value.mcp);
150
+ const skills = parseSkillsConfig(value.skills);
151
+ const result = { version, model };
152
+ if (hasPermissions) {
153
+ result.permissions = hasPermissions;
154
+ }
155
+ if (mcp) {
156
+ result.mcp = mcp;
157
+ }
158
+ if (skills) {
159
+ result.skills = skills;
160
+ }
161
+ return result;
162
+ };
163
+ var ConfigRegistry = class {
164
+ defaults = [];
165
+ registerDefaults(layer) {
166
+ this.defaults.push(layer);
167
+ }
168
+ resolve(layers) {
169
+ const merged = { version: CONFIG_VERSION };
170
+ for (const layer of [...this.defaults, ...layers]) {
171
+ if (layer?.model) {
172
+ merged.model = { ...merged.model, ...layer.model };
173
+ }
174
+ if (layer?.permissions) {
175
+ const nextAllow = layer.permissions.allow ?? [];
176
+ const nextDeny = layer.permissions.deny ?? [];
177
+ if (nextAllow.length > 0 || nextDeny.length > 0) {
178
+ merged.permissions ??= {};
179
+ if (nextAllow.length > 0) {
180
+ merged.permissions.allow = [
181
+ ...merged.permissions.allow ?? [],
182
+ ...nextAllow
183
+ ];
184
+ }
185
+ if (nextDeny.length > 0) {
186
+ merged.permissions.deny = [
187
+ ...merged.permissions.deny ?? [],
188
+ ...nextDeny
189
+ ];
190
+ }
191
+ }
192
+ }
193
+ if (layer?.mcp?.servers) {
194
+ merged.mcp = {
195
+ servers: {
196
+ ...merged.mcp?.servers ?? {},
197
+ ...layer.mcp.servers
198
+ }
199
+ };
200
+ }
201
+ if (layer?.skills) {
202
+ const nextSkills = layer.skills;
203
+ merged.skills ??= {};
204
+ if (nextSkills.enabled !== void 0) {
205
+ merged.skills.enabled = nextSkills.enabled;
206
+ }
207
+ if (nextSkills.initial) {
208
+ merged.skills.initial = {
209
+ ...merged.skills.initial ?? {},
210
+ ...nextSkills.initial
211
+ };
212
+ }
213
+ if (nextSkills.search) {
214
+ merged.skills.search = {
215
+ ...merged.skills.search ?? {},
216
+ ...nextSkills.search
217
+ };
218
+ }
219
+ }
220
+ }
221
+ return merged;
222
+ }
223
+ };
224
+ var configRegistry = new ConfigRegistry();
225
+ export {
226
+ CONFIG_VERSION,
227
+ ConfigRegistry,
228
+ configRegistry,
229
+ parseConfig
230
+ };
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@codelia/config",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
20
+ "typecheck": "tsc --noEmit"
21
+ },
22
+ "dependencies": {},
23
+ "publishConfig": {
24
+ "access": "public"
25
+ }
26
+ }