api-to-cli 0.1.1

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.
Files changed (35) hide show
  1. package/LICENSE +21 -0
  2. package/PROJECT_BRIEF.md +65 -0
  3. package/README.md +130 -0
  4. package/SPEC.md +99 -0
  5. package/bin/api-to-cli.js +5 -0
  6. package/examples/trello/api-to-cli.config.js +60 -0
  7. package/examples/trello/trelloapi-agent/README.md +11 -0
  8. package/examples/trello/trelloapi-agent/agentbridge.manifest.json +68 -0
  9. package/examples/trello/trelloapi-agent/cli/README.md +25 -0
  10. package/examples/trello/trelloapi-agent/cli/bin/trelloapi.js +37 -0
  11. package/examples/trello/trelloapi-agent/cli/commands/get-board.js +41 -0
  12. package/examples/trello/trelloapi-agent/cli/commands/list-board-lists.js +41 -0
  13. package/examples/trello/trelloapi-agent/cli/commands/list-list-cards.js +41 -0
  14. package/examples/trello/trelloapi-agent/cli/lib/client.js +90 -0
  15. package/examples/trello/trelloapi-agent/cli/lib/output.js +21 -0
  16. package/examples/trello/trelloapi-agent/cli/package.json +16 -0
  17. package/examples/trello/trelloapi-agent/skill/SKILL.md +34 -0
  18. package/examples/trello/trelloapi-cli/README.md +25 -0
  19. package/examples/trello/trelloapi-cli/bin/trelloapi.js +37 -0
  20. package/examples/trello/trelloapi-cli/commands/get-board.js +41 -0
  21. package/examples/trello/trelloapi-cli/commands/list-board-lists.js +41 -0
  22. package/examples/trello/trelloapi-cli/commands/list-list-cards.js +41 -0
  23. package/examples/trello/trelloapi-cli/lib/client.js +90 -0
  24. package/examples/trello/trelloapi-cli/lib/output.js +21 -0
  25. package/examples/trello/trelloapi-cli/package.json +16 -0
  26. package/package.json +48 -0
  27. package/src/commands/generate.js +36 -0
  28. package/src/commands/scaffold.js +110 -0
  29. package/src/commands/validate.js +30 -0
  30. package/src/index.js +92 -0
  31. package/src/lib/config-utils.js +21 -0
  32. package/src/lib/generate-cli.js +295 -0
  33. package/src/lib/generate-manifest.js +51 -0
  34. package/src/lib/generate-skill.js +50 -0
  35. package/src/lib/load-config.js +120 -0
@@ -0,0 +1,30 @@
1
+ const path = require('path');
2
+ const { loadConfig } = require('../lib/load-config');
3
+
4
+ async function validate(flags) {
5
+ if (!flags.config) {
6
+ throw new Error('Missing required flag: --config <path>');
7
+ }
8
+
9
+ const configPath = path.resolve(process.cwd(), String(flags.config));
10
+ const config = loadConfig(configPath);
11
+
12
+ console.log(
13
+ JSON.stringify({
14
+ ok: true,
15
+ command: 'validate',
16
+ configPath,
17
+ summary: {
18
+ name: config.name,
19
+ version: config.version,
20
+ apiBase: config.apiBase,
21
+ commandCount: config.commands.length,
22
+ hasAuth: Boolean(config.auth && config.auth.credentials && config.auth.credentials.length)
23
+ }
24
+ })
25
+ );
26
+ }
27
+
28
+ module.exports = {
29
+ validate
30
+ };
package/src/index.js ADDED
@@ -0,0 +1,92 @@
1
+ const { generate } = require('./commands/generate');
2
+ const { validate } = require('./commands/validate');
3
+ const { scaffold } = require('./commands/scaffold');
4
+
5
+ function printUsage() {
6
+ console.error(
7
+ [
8
+ 'Usage:',
9
+ ' api-to-cli generate --config <path> --output <dir>',
10
+ ' api-to-cli validate --config <path>',
11
+ ' api-to-cli scaffold --config <path> --output <dir> [--with-skill] [--with-manifest]',
12
+ '',
13
+ 'Commands:',
14
+ ' generate Generate a CLI from config',
15
+ ' validate Validate config only',
16
+ ' scaffold Generate CLI + optional skill/manifest bundle'
17
+ ].join('\n')
18
+ );
19
+ }
20
+
21
+ function parseFlags(args) {
22
+ const flags = {};
23
+
24
+ for (let i = 0; i < args.length; i += 1) {
25
+ const arg = args[i];
26
+
27
+ if (!arg.startsWith('--')) {
28
+ continue;
29
+ }
30
+
31
+ const key = arg.slice(2);
32
+ const value = args[i + 1];
33
+
34
+ if (!value || value.startsWith('--')) {
35
+ flags[key] = true;
36
+ continue;
37
+ }
38
+
39
+ flags[key] = value;
40
+ i += 1;
41
+ }
42
+
43
+ return flags;
44
+ }
45
+
46
+ async function run(argv) {
47
+ const [command, ...rest] = argv;
48
+
49
+ if (!command || command === '--help' || command === '-h') {
50
+ printUsage();
51
+ process.exit(0);
52
+ }
53
+
54
+ try {
55
+ const flags = parseFlags(rest);
56
+
57
+ if (command === 'generate') {
58
+ await generate(flags);
59
+ return;
60
+ }
61
+
62
+ if (command === 'validate') {
63
+ await validate(flags);
64
+ return;
65
+ }
66
+
67
+ if (command === 'scaffold') {
68
+ await scaffold(flags);
69
+ return;
70
+ }
71
+
72
+ throw new Error(`Unknown command: ${command}`);
73
+ } catch (error) {
74
+ console.error(
75
+ JSON.stringify(
76
+ {
77
+ error: true,
78
+ code: 'GENERATOR_FAILED',
79
+ message: error.message,
80
+ details: {}
81
+ },
82
+ null,
83
+ 2
84
+ )
85
+ );
86
+ process.exit(1);
87
+ }
88
+ }
89
+
90
+ module.exports = {
91
+ run
92
+ };
@@ -0,0 +1,21 @@
1
+ function toKebab(input) {
2
+ return String(input)
3
+ .trim()
4
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
5
+ .toLowerCase()
6
+ .replace(/[^a-z0-9]+/g, '-')
7
+ .replace(/^-+|-+$/g, '');
8
+ }
9
+
10
+ function getAuthEnvVars(config) {
11
+ const authVars = (config.auth && Array.isArray(config.auth.credentials))
12
+ ? config.auth.credentials.map((credential) => credential.envVar)
13
+ : [];
14
+
15
+ return [...new Set(authVars)];
16
+ }
17
+
18
+ module.exports = {
19
+ toKebab,
20
+ getAuthEnvVars
21
+ };
@@ -0,0 +1,295 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { toKebab } = require('./config-utils');
4
+
5
+ function ensureDir(dirPath) {
6
+ fs.mkdirSync(dirPath, { recursive: true });
7
+ }
8
+
9
+ function commandToMethodName(commandName) {
10
+ return String(commandName)
11
+ .split(/[^a-zA-Z0-9]/)
12
+ .filter(Boolean)
13
+ .map((part, index) => {
14
+ const lower = part.toLowerCase();
15
+ return index === 0 ? lower : lower[0].toUpperCase() + lower.slice(1);
16
+ })
17
+ .join('');
18
+ }
19
+
20
+ function renderPackageJson(config) {
21
+ const packageJson = {
22
+ name: `${toKebab(config.name)}-cli`,
23
+ version: config.version,
24
+ description: `${config.name} CLI generated by AgentBridge`,
25
+ license: 'MIT',
26
+ type: 'commonjs',
27
+ bin: {
28
+ [toKebab(config.name)]: `./bin/${toKebab(config.name)}.js`
29
+ },
30
+ scripts: {
31
+ start: `node ./bin/${toKebab(config.name)}.js`
32
+ },
33
+ dependencies: {
34
+ commander: '^12.1.0'
35
+ }
36
+ };
37
+
38
+ return `${JSON.stringify(packageJson, null, 2)}\n`;
39
+ }
40
+
41
+ function renderOutputLib() {
42
+ return `function json(data, pretty) {
43
+ const text = pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);
44
+ process.stdout.write(text + '\\n');
45
+ }
46
+
47
+ function error(payload, pretty) {
48
+ const envelope = {
49
+ error: true,
50
+ code: payload.code || 'REQUEST_FAILED',
51
+ message: payload.message || 'Request failed',
52
+ details: payload.details || {}
53
+ };
54
+
55
+ const text = pretty ? JSON.stringify(envelope, null, 2) : JSON.stringify(envelope);
56
+ process.stderr.write(text + '\\n');
57
+ }
58
+
59
+ module.exports = {
60
+ json,
61
+ error
62
+ };
63
+ `;
64
+ }
65
+
66
+ function renderClientLib(config) {
67
+ const authConfig = JSON.stringify(config.auth || { credentials: [] }, null, 2);
68
+
69
+ return `async function request(command, options) {
70
+ const auth = ${authConfig};
71
+ const params = new URLSearchParams();
72
+ const commandParams = command.params || {};
73
+ let resolvedPath = command.path;
74
+ const headers = {
75
+ accept: 'application/json'
76
+ };
77
+
78
+ (auth.credentials || []).forEach((credential) => {
79
+ const envValue = process.env[credential.envVar];
80
+
81
+ if (!envValue) {
82
+ throw new Error(\`Missing required auth environment variable: \${credential.envVar}\`);
83
+ }
84
+
85
+ const authValue = credential.prefix ? \`\${credential.prefix}\${envValue}\` : envValue;
86
+
87
+ if (credential.in === 'header') {
88
+ headers[credential.name] = authValue;
89
+ return;
90
+ }
91
+
92
+ params.append(credential.name, authValue);
93
+ });
94
+
95
+ Object.entries(commandParams).forEach(([name, schema]) => {
96
+ const value = options[name];
97
+
98
+ if ((value === undefined || value === null || value === '') && schema.required) {
99
+ throw new Error(\`Missing required parameter: --\${name}\`);
100
+ }
101
+
102
+ if (value === undefined || value === null || value === '') {
103
+ return;
104
+ }
105
+
106
+ const token = \`{\${name}}\`;
107
+
108
+ if (resolvedPath.includes(token)) {
109
+ resolvedPath = resolvedPath.replaceAll(token, encodeURIComponent(String(value)));
110
+ return;
111
+ }
112
+
113
+ params.append(name, String(value));
114
+ });
115
+
116
+ const query = params.toString();
117
+ const url = '${config.apiBase}' + resolvedPath + (query ? '?' + query : '');
118
+
119
+ const response = await fetch(url, {
120
+ method: command.method,
121
+ headers
122
+ });
123
+
124
+ const text = await response.text();
125
+ let body = text;
126
+
127
+ try {
128
+ body = text ? JSON.parse(text) : null;
129
+ } catch (_err) {
130
+ body = text;
131
+ }
132
+
133
+ if (!response.ok) {
134
+ const error = new Error(\`HTTP \${response.status}\`);
135
+ error.statusCode = response.status;
136
+ error.responseBody = body;
137
+ throw error;
138
+ }
139
+
140
+ return body;
141
+ }
142
+
143
+ module.exports = {
144
+ request
145
+ };
146
+ `;
147
+ }
148
+
149
+ function renderCommandModule(command) {
150
+ const functionName = commandToMethodName(command.name);
151
+ const serializedCommand = JSON.stringify(command, null, 2);
152
+
153
+ return `const { request } = require('../lib/client');
154
+ const output = require('../lib/output');
155
+
156
+ const command = ${serializedCommand};
157
+
158
+ async function ${functionName}(options) {
159
+ try {
160
+ const data = await request(command, options);
161
+ output.json(data, Boolean(options.pretty));
162
+ } catch (error) {
163
+ output.error(
164
+ {
165
+ code: error.statusCode ? 'HTTP_ERROR' : 'REQUEST_FAILED',
166
+ message: error.message,
167
+ details: {
168
+ statusCode: error.statusCode || null,
169
+ command: command.name
170
+ }
171
+ },
172
+ Boolean(options.pretty)
173
+ );
174
+ process.exit(1);
175
+ }
176
+ }
177
+
178
+ module.exports = {
179
+ run: ${functionName},
180
+ command
181
+ };
182
+ `;
183
+ }
184
+
185
+ function renderBinFile(config) {
186
+ const imports = config.commands
187
+ .map((command, index) => {
188
+ const varName = `cmd${index}`;
189
+ return `const ${varName} = require('../commands/${toKebab(command.name)}');`;
190
+ })
191
+ .join('\n');
192
+
193
+ const registrations = config.commands
194
+ .map((command, index) => {
195
+ const varName = `cmd${index}`;
196
+ const params = command.params || {};
197
+ const options = Object.entries(params)
198
+ .map(([paramName, schema]) => {
199
+ const flagName = `--${toKebab(paramName)} <value>`;
200
+ const desc = schema.description || `${paramName} parameter`;
201
+ return ` .option('${flagName}', '${desc}')`;
202
+ })
203
+ .join('\n');
204
+
205
+ return `program
206
+ .command('${command.name}')
207
+ .description('${command.description.replace(/'/g, "\\'")}')
208
+ ${options ? `${options}\n` : ''} .option('--pretty', 'Pretty-print JSON')
209
+ .action((options) => ${varName}.run(options));`;
210
+ })
211
+ .join('\n\n');
212
+
213
+ return `#!/usr/bin/env node
214
+
215
+ const { Command } = require('commander');
216
+
217
+ ${imports}
218
+
219
+ const program = new Command();
220
+
221
+ program
222
+ .name('${toKebab(config.name)}')
223
+ .description('${config.name} CLI generated by AgentBridge')
224
+ .version('${config.version}');
225
+
226
+ ${registrations}
227
+
228
+ program.parse(process.argv);
229
+ `;
230
+ }
231
+
232
+ function renderReadme(config) {
233
+ const commandList = config.commands
234
+ .map((command) => `- \`${toKebab(config.name)} ${command.name}\` - ${command.description}`)
235
+ .join('\n');
236
+ const authVars = (config.auth && Array.isArray(config.auth.credentials))
237
+ ? config.auth.credentials.map((credential) => credential.envVar)
238
+ : [];
239
+ const uniqueAuthVars = [...new Set(authVars)];
240
+ const authSection = uniqueAuthVars.length
241
+ ? `## Auth\n\nSet required environment variables before running commands:\n\n${uniqueAuthVars.map((envVar) => `- \`${envVar}\``).join('\n')}\n\nDo not pass secrets as command flags.\n\n`
242
+ : '';
243
+
244
+ return `# ${config.name} CLI
245
+
246
+ Generated by AgentBridge (api-to-cli).
247
+
248
+ ## Install
249
+
250
+ \`\`\`bash
251
+ npm install
252
+ npm link
253
+ \`\`\`
254
+
255
+ ${authSection}## Commands
256
+
257
+ ${commandList}
258
+ `;
259
+ }
260
+
261
+ function writeFile(filePath, content, executable = false) {
262
+ fs.writeFileSync(filePath, content, 'utf8');
263
+ if (executable) {
264
+ fs.chmodSync(filePath, 0o755);
265
+ }
266
+ }
267
+
268
+ function generateCliProject({ config, outputPath }) {
269
+ const binName = toKebab(config.name);
270
+ const binDir = path.join(outputPath, 'bin');
271
+ const commandsDir = path.join(outputPath, 'commands');
272
+ const libDir = path.join(outputPath, 'lib');
273
+
274
+ ensureDir(outputPath);
275
+ ensureDir(binDir);
276
+ ensureDir(commandsDir);
277
+ ensureDir(libDir);
278
+
279
+ writeFile(path.join(outputPath, 'package.json'), renderPackageJson(config));
280
+ writeFile(path.join(outputPath, 'README.md'), renderReadme(config));
281
+ writeFile(path.join(libDir, 'output.js'), renderOutputLib());
282
+ writeFile(path.join(libDir, 'client.js'), renderClientLib(config));
283
+ writeFile(path.join(binDir, `${binName}.js`), renderBinFile(config), true);
284
+
285
+ config.commands.forEach((command) => {
286
+ writeFile(
287
+ path.join(commandsDir, `${toKebab(command.name)}.js`),
288
+ renderCommandModule(command)
289
+ );
290
+ });
291
+ }
292
+
293
+ module.exports = {
294
+ generateCliProject
295
+ };
@@ -0,0 +1,51 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { toKebab, getAuthEnvVars } = require('./config-utils');
4
+
5
+ function buildManifest(config, cliProjectPath, skillPath) {
6
+ const binName = toKebab(config.name);
7
+
8
+ return {
9
+ schemaVersion: '1.0',
10
+ generatedBy: 'AgentBridge',
11
+ generatedAt: new Date().toISOString(),
12
+ project: {
13
+ name: config.name,
14
+ version: config.version
15
+ },
16
+ cli: {
17
+ projectPath: cliProjectPath,
18
+ packageName: `${binName}-cli`,
19
+ binary: binName,
20
+ install: ['npm install', 'npm link']
21
+ },
22
+ auth: {
23
+ envVars: getAuthEnvVars(config)
24
+ },
25
+ commands: config.commands.map((command) => ({
26
+ name: command.name,
27
+ description: command.description,
28
+ method: command.method,
29
+ path: command.path,
30
+ params: Object.entries(command.params || {}).map(([name, schema]) => ({
31
+ name,
32
+ required: Boolean(schema.required),
33
+ description: schema.description || ''
34
+ }))
35
+ })),
36
+ agent: {
37
+ skillPath: skillPath || null
38
+ }
39
+ };
40
+ }
41
+
42
+ function writeManifest(outputPath, manifest) {
43
+ const filePath = path.join(outputPath, 'agentbridge.manifest.json');
44
+ fs.writeFileSync(filePath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
45
+ return filePath;
46
+ }
47
+
48
+ module.exports = {
49
+ buildManifest,
50
+ writeManifest
51
+ };
@@ -0,0 +1,50 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { toKebab, getAuthEnvVars } = require('./config-utils');
4
+
5
+ function renderSkill(config, cliProjectPath) {
6
+ const binName = toKebab(config.name);
7
+ const envVars = getAuthEnvVars(config);
8
+ const envSection = envVars.length
9
+ ? envVars.map((envVar) => `- export ${envVar}=\"<value>\"`).join('\n')
10
+ : '- No auth env vars required';
11
+
12
+ const commandDocs = config.commands
13
+ .map((command) => {
14
+ const flags = Object.entries(command.params || {})
15
+ .map(([name, schema]) => {
16
+ const flag = `--${toKebab(name)} <value>`;
17
+ const req = schema.required ? 'required' : 'optional';
18
+ return ` - ${flag} (${req})`;
19
+ })
20
+ .join('\n');
21
+
22
+ return [
23
+ `- ${command.name}: ${command.description}`,
24
+ flags || ' - no params',
25
+ ` - example: ${binName} ${command.name}${flags ? ` ${Object.keys(command.params || {}).map((p) => `--${toKebab(p)} <value>`).join(' ')}` : ''}`
26
+ ].join('\n');
27
+ })
28
+ .join('\n');
29
+
30
+ return `# ${config.name} CLI Skill\n\n## Purpose\nUse the generated ${config.name} CLI from AgentBridge. Always prefer JSON output for machine parsing.\n\n## Location\n- CLI project: ${cliProjectPath}\n- Binary name: ${binName}\n\n## Setup\n1. cd ${cliProjectPath}\n2. npm install\n3. npm link\n\n## Auth\n${envSection}\n\n## Commands\n${commandDocs}\n\n## Rules\n- Do not echo or log auth secrets.\n- Do not pass credentials as command flags.\n- Parse command stdout as JSON.\n- Treat non-zero exits as failure and read stderr JSON envelope.\n`;
31
+ }
32
+
33
+ function writeSkillPackage(outputPath, config, cliProjectPath) {
34
+ const skillDir = path.join(outputPath, 'skill');
35
+ fs.mkdirSync(skillDir, { recursive: true });
36
+
37
+ const skillContent = renderSkill(config, cliProjectPath);
38
+ const skillPath = path.join(skillDir, 'SKILL.md');
39
+
40
+ fs.writeFileSync(skillPath, skillContent, 'utf8');
41
+
42
+ return {
43
+ skillDir,
44
+ skillPath
45
+ };
46
+ }
47
+
48
+ module.exports = {
49
+ writeSkillPackage
50
+ };
@@ -0,0 +1,120 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ function fail(message) {
5
+ throw new Error(`Invalid config: ${message}`);
6
+ }
7
+
8
+ function isObject(value) {
9
+ return value && typeof value === 'object' && !Array.isArray(value);
10
+ }
11
+
12
+ function isNonEmptyString(value) {
13
+ return typeof value === 'string' && Boolean(value.trim());
14
+ }
15
+
16
+ function validateAuthCredential(credential, index) {
17
+ if (!isObject(credential)) {
18
+ fail(`auth.credentials[${index}] must be an object`);
19
+ }
20
+
21
+ if (!isNonEmptyString(credential.envVar)) {
22
+ fail(`auth.credentials[${index}].envVar must be a non-empty string`);
23
+ }
24
+
25
+ if (credential.in !== 'header' && credential.in !== 'query') {
26
+ fail(`auth.credentials[${index}].in must be either "header" or "query"`);
27
+ }
28
+
29
+ if (!isNonEmptyString(credential.name)) {
30
+ fail(`auth.credentials[${index}].name must be a non-empty string`);
31
+ }
32
+
33
+ if (credential.prefix !== undefined && typeof credential.prefix !== 'string') {
34
+ fail(`auth.credentials[${index}].prefix must be a string when provided`);
35
+ }
36
+ }
37
+
38
+ function validateAuth(auth) {
39
+ if (!isObject(auth)) {
40
+ fail('auth must be an object when provided');
41
+ }
42
+
43
+ if (!Array.isArray(auth.credentials) || auth.credentials.length === 0) {
44
+ fail('auth.credentials must be a non-empty array when auth is provided');
45
+ }
46
+
47
+ auth.credentials.forEach(validateAuthCredential);
48
+ }
49
+
50
+ function validateCommand(command, index) {
51
+ if (!isObject(command)) {
52
+ fail(`commands[${index}] must be an object`);
53
+ }
54
+
55
+ if (!isNonEmptyString(command.name)) {
56
+ fail(`commands[${index}].name must be a non-empty string`);
57
+ }
58
+
59
+ if (!isNonEmptyString(command.description)) {
60
+ fail(`commands[${index}].description must be a non-empty string`);
61
+ }
62
+
63
+ if (command.method !== 'GET') {
64
+ fail(`commands[${index}].method must be GET for MVP`);
65
+ }
66
+
67
+ if (typeof command.path !== 'string' || !command.path.startsWith('/')) {
68
+ fail(`commands[${index}].path must be a string starting with /`);
69
+ }
70
+
71
+ if (command.params !== undefined && !isObject(command.params)) {
72
+ fail(`commands[${index}].params must be an object when provided`);
73
+ }
74
+ }
75
+
76
+ function validateConfig(config) {
77
+ if (!isObject(config)) {
78
+ fail('config must export an object');
79
+ }
80
+
81
+ if (!isNonEmptyString(config.name)) {
82
+ fail('name must be a non-empty string');
83
+ }
84
+
85
+ if (!isNonEmptyString(config.version)) {
86
+ fail('version must be a non-empty string');
87
+ }
88
+
89
+ if (typeof config.apiBase !== 'string' || !/^https?:\/\//.test(config.apiBase)) {
90
+ fail('apiBase must be an http(s) URL');
91
+ }
92
+
93
+ if (!Array.isArray(config.commands) || config.commands.length === 0) {
94
+ fail('commands must be a non-empty array');
95
+ }
96
+
97
+ if (config.auth !== undefined) {
98
+ validateAuth(config.auth);
99
+ }
100
+
101
+ config.commands.forEach(validateCommand);
102
+ }
103
+
104
+ function loadConfig(configPath) {
105
+ if (!fs.existsSync(configPath)) {
106
+ throw new Error(`Config file not found: ${configPath}`);
107
+ }
108
+
109
+ const resolved = path.resolve(configPath);
110
+ delete require.cache[resolved];
111
+ const config = require(resolved);
112
+
113
+ validateConfig(config);
114
+
115
+ return config;
116
+ }
117
+
118
+ module.exports = {
119
+ loadConfig
120
+ };