@ainote/mcp 1.3.4 → 1.3.6

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/lib/cli/signup.js CHANGED
@@ -1,118 +1,130 @@
1
- import * as readline from 'node:readline/promises';
2
- import { stdin as input, stdout as output } from 'node:process';
3
- import axios from 'axios';
4
- import { getConfig } from '../core/config-manager.js';
5
-
6
- /**
7
- * Interactive CLI signup for AI Note MCP.
8
- * Usage: ainote-mcp signup
9
- */
10
- export async function runSignup() {
11
- const config = getConfig();
12
- const rl = readline.createInterface({ input, output });
13
-
14
- console.log('\n AI Note MCP — Sign Up\n');
15
-
16
- try {
17
- const email = await rl.question(' Email: ');
18
- const password = await rl.question(' Password (min 6 chars): ');
19
- const name = await rl.question(' Name (optional, press Enter to skip): ');
20
-
21
- if (!email.trim() || !password.trim()) {
22
- console.error('\n Error: Email and password are required.\n');
23
- process.exit(1);
24
- }
25
-
26
- if (password.trim().length < 6) {
27
- console.error('\n Error: Password must be at least 6 characters.\n');
28
- process.exit(1);
29
- }
30
-
31
- console.log('\n Creating account...');
32
-
33
- const rpcRequest = {
34
- jsonrpc: '2.0',
35
- id: 1,
36
- method: 'tools/call',
37
- params: {
38
- name: 'signup_and_get_key',
39
- arguments: {
40
- email: email.trim(),
41
- password: password.trim(),
42
- ...(name.trim() ? { name: name.trim() } : {})
43
- }
44
- }
45
- };
46
-
47
- const response = await axios.post(`${config.api.url}/api/mcp`, rpcRequest, {
48
- headers: { 'Content-Type': 'application/json' },
49
- timeout: 30000
50
- });
51
-
52
- const data = response.data?.data;
53
-
54
- // Check for JSON-RPC error
55
- if (data?.error) {
56
- console.error(`\n Error: ${data.error.message}`);
57
- if (data.error.data?.suggestion) {
58
- console.log(` Hint: ${data.error.data.suggestion}`);
59
- }
60
- console.log();
61
- process.exit(1);
62
- }
63
-
64
- // Extract MCP key from structured resource data
65
- const content = data?.result?.content;
66
- let mcpKey = null;
67
-
68
- if (Array.isArray(content)) {
69
- for (const item of content) {
70
- if (item.type === 'resource' && item.resource?.text) {
71
- try {
72
- const resourceData = JSON.parse(item.resource.text);
73
- mcpKey = resourceData.mcp_key;
74
- } catch { /* ignore */ }
75
- }
76
- }
77
- }
78
-
79
- if (mcpKey) {
80
- console.log('\n Account created successfully!\n');
81
- console.log(` Your MCP API Key:\n`);
82
- console.log(` ${mcpKey}\n`);
83
- console.log(' Add this to your MCP config:\n');
84
- console.log(' {');
85
- console.log(' "mcpServers": {');
86
- console.log(' "ainote": {');
87
- console.log(' "command": "npx",');
88
- console.log(' "args": ["-y", "@ainote/mcp"],');
89
- console.log(' "env": {');
90
- console.log(` "AINOTE_API_KEY": "${mcpKey}"`);
91
- console.log(' }');
92
- console.log(' }');
93
- console.log(' }');
94
- console.log(' }\n');
95
- console.log(' Then restart Claude Desktop.\n');
96
- } else {
97
- // Fallback: print text content
98
- if (Array.isArray(content)) {
99
- for (const item of content) {
100
- if (item.type === 'text') {
101
- console.log(`\n${item.text}\n`);
102
- }
103
- }
104
- } else {
105
- console.log('\n Account created! Check the response for your API key.\n');
106
- }
107
- }
108
- } catch (error) {
109
- if (error.response?.data) {
110
- console.error(`\n Error: ${JSON.stringify(error.response.data)}\n`);
111
- } else {
112
- console.error(`\n Error: ${error.message}\n`);
113
- }
114
- process.exit(1);
115
- } finally {
116
- rl.close();
117
- }
118
- }
1
+ import * as readline from 'node:readline/promises';
2
+ import { stdin as input, stdout as output } from 'node:process';
3
+ import axios from 'axios';
4
+ import { getConfig } from '../core/config-manager.js';
5
+
6
+ /**
7
+ * Interactive CLI signup for AI Note MCP.
8
+ * Usage: ainote-mcp signup
9
+ */
10
+ export async function runSignup() {
11
+ const config = getConfig();
12
+ const rl = readline.createInterface({ input, output });
13
+
14
+ console.log('\n AI Note MCP — Sign Up\n');
15
+
16
+ try {
17
+ const email = await rl.question(' Email: ');
18
+ const password = await rl.question(' Password (min 6 chars): ');
19
+ const name = await rl.question(' Name (optional, press Enter to skip): ');
20
+
21
+ if (!email.trim() || !password.trim()) {
22
+ console.error('\n Error: Email and password are required.\n');
23
+ process.exit(1);
24
+ }
25
+
26
+ if (password.trim().length < 6) {
27
+ console.error('\n Error: Password must be at least 6 characters.\n');
28
+ process.exit(1);
29
+ }
30
+
31
+ console.log('\n Creating account...');
32
+
33
+ const rpcRequest = {
34
+ jsonrpc: '2.0',
35
+ id: 1,
36
+ method: 'tools/call',
37
+ params: {
38
+ name: 'signup_and_get_key',
39
+ arguments: {
40
+ email: email.trim(),
41
+ password: password.trim(),
42
+ ...(name.trim() ? { name: name.trim() } : {})
43
+ }
44
+ }
45
+ };
46
+
47
+ const response = await axios.post(`${config.api.url}/api/mcp`, rpcRequest, {
48
+ headers: {
49
+ 'Content-Type': 'application/json',
50
+ Accept: 'application/json, text/event-stream'
51
+ },
52
+ timeout: 30000
53
+ });
54
+
55
+ // /api/mcp 는 JSON-RPC 를 봉투 없이 그대로 돌려준다. 옛 서버의
56
+ // { success, data: {...} } 봉투도 함께 받는다.
57
+ const data = response.data?.data ?? response.data;
58
+
59
+ // Check for JSON-RPC error
60
+ if (data?.error) {
61
+ console.error(`\n Error: ${data.error.message}`);
62
+ if (data.error.data?.suggestion) {
63
+ console.log(` Hint: ${data.error.data.suggestion}`);
64
+ }
65
+ console.log();
66
+ process.exit(1);
67
+ }
68
+
69
+ // Extract MCP key from structured resource data
70
+ const content = data?.result?.content;
71
+
72
+ // 도구 오류는 result.isError 로 내려온다 (이메일 중복·비밀번호 규칙 등)
73
+ if (data?.result?.isError) {
74
+ const message = Array.isArray(content) ? content.find((item) => item.type === 'text')?.text : null;
75
+ console.error(`\n Error: ${message || 'Signup failed'}\n`);
76
+ process.exit(1);
77
+ }
78
+ let mcpKey = null;
79
+
80
+ if (Array.isArray(content)) {
81
+ for (const item of content) {
82
+ if (item.type === 'resource' && item.resource?.text) {
83
+ try {
84
+ const resourceData = JSON.parse(item.resource.text);
85
+ mcpKey = resourceData.mcp_key;
86
+ } catch { /* ignore */ }
87
+ }
88
+ }
89
+ }
90
+
91
+ if (mcpKey) {
92
+ console.log('\n Account created successfully!\n');
93
+ console.log(` Your MCP API Key:\n`);
94
+ console.log(` ${mcpKey}\n`);
95
+ console.log(' Add this to your MCP config:\n');
96
+ console.log(' {');
97
+ console.log(' "mcpServers": {');
98
+ console.log(' "ainote": {');
99
+ console.log(' "command": "npx",');
100
+ console.log(' "args": ["-y", "@ainote/mcp"],');
101
+ console.log(' "env": {');
102
+ console.log(` "AINOTE_API_KEY": "${mcpKey}"`);
103
+ console.log(' }');
104
+ console.log(' }');
105
+ console.log(' }');
106
+ console.log(' }\n');
107
+ console.log(' Then restart Claude Desktop.\n');
108
+ } else {
109
+ // Fallback: print text content
110
+ if (Array.isArray(content)) {
111
+ for (const item of content) {
112
+ if (item.type === 'text') {
113
+ console.log(`\n${item.text}\n`);
114
+ }
115
+ }
116
+ } else {
117
+ console.log('\n Account created! Check the response for your API key.\n');
118
+ }
119
+ }
120
+ } catch (error) {
121
+ if (error.response?.data) {
122
+ console.error(`\n Error: ${JSON.stringify(error.response.data)}\n`);
123
+ } else {
124
+ console.error(`\n Error: ${error.message}\n`);
125
+ }
126
+ process.exit(1);
127
+ } finally {
128
+ rl.close();
129
+ }
130
+ }
package/lib/cli/whoami.js CHANGED
@@ -1,76 +1,76 @@
1
- /**
2
- * `ainote-mcp whoami` — show the currently authenticated user.
3
- *
4
- * Strategy:
5
- * 1. Read credentials from the token store.
6
- * 2. If we have an access_token, hit GET /api/users/me with Bearer auth and
7
- * print the response.
8
- * 3. If the API call fails (404, 401, network) but we have a stored mcp_key,
9
- * print a minimal local-only summary so the user at least sees that
10
- * credentials exist for this host.
11
- */
12
- import process from 'node:process';
13
- import axios from 'axios';
14
- import { getCredentials } from '../auth/token-store.js';
15
- import { resolveApiUrl } from '../core/config-manager.js';
16
-
17
- function summarizeUser(user) {
18
- if (!user || typeof user !== 'object') return null;
19
- const parts = [];
20
- if (user.email) parts.push(user.email);
21
- else if (user.name) parts.push(user.name);
22
- else if (user.id) parts.push(`user #${user.id}`);
23
- return parts.join(' ');
24
- }
25
-
26
- export async function run() {
27
- const apiUrl = resolveApiUrl();
28
- const creds = await getCredentials(apiUrl);
29
-
30
- if (!creds) {
31
- console.error(' Not logged in. Run `ainote-mcp login`.');
32
- process.exit(1);
33
- return;
34
- }
35
-
36
- let identity = null;
37
-
38
- if (creds.accessToken) {
39
- try {
40
- const response = await axios.get(`${apiUrl}/api/users/me`, {
41
- headers: {
42
- Authorization: `Bearer ${creds.accessToken}`,
43
- Accept: 'application/json'
44
- },
45
- timeout: 15000,
46
- validateStatus: () => true
47
- });
48
- if (response.status >= 200 && response.status < 300) {
49
- const body = response.data || {};
50
- identity = summarizeUser(body.user || body.data || body) || summarizeUser(body);
51
- }
52
- } catch {
53
- // fall through to local-only fallback below
54
- }
55
- }
56
-
57
- if (identity) {
58
- console.log(` Logged in as ${identity}`);
59
- } else {
60
- // Local-only fallback: report what we have.
61
- const host = (() => {
62
- try { return new URL(apiUrl).host; } catch { return apiUrl; }
63
- })();
64
- console.log(` Credentials present for ${host}`);
65
- if (creds.mcpKey) console.log(` MCP key: ${creds.mcpKey.slice(0, 8)}…`);
66
- if (creds.expiresAt) {
67
- const remaining = Math.floor((creds.expiresAt - Date.now()) / 1000);
68
- if (remaining > 0) console.log(` Access token expires in ~${remaining}s`);
69
- else console.log(` Access token expired (refresh on next call).`);
70
- }
71
- if (!creds.accessToken && !creds.mcpKey) {
72
- console.error(' Could not verify identity. Try `ainote-mcp login` again.');
73
- process.exit(1);
74
- }
75
- }
76
- }
1
+ /**
2
+ * `ainote-mcp whoami` — show the currently authenticated user.
3
+ *
4
+ * Strategy:
5
+ * 1. Read credentials from the token store.
6
+ * 2. If we have an access_token, hit GET /api/users/me with Bearer auth and
7
+ * print the response.
8
+ * 3. If the API call fails (404, 401, network) but we have a stored mcp_key,
9
+ * print a minimal local-only summary so the user at least sees that
10
+ * credentials exist for this host.
11
+ */
12
+ import process from 'node:process';
13
+ import axios from 'axios';
14
+ import { getCredentials } from '../auth/token-store.js';
15
+ import { resolveApiUrl } from '../core/config-manager.js';
16
+
17
+ function summarizeUser(user) {
18
+ if (!user || typeof user !== 'object') return null;
19
+ const parts = [];
20
+ if (user.email) parts.push(user.email);
21
+ else if (user.name) parts.push(user.name);
22
+ else if (user.id) parts.push(`user #${user.id}`);
23
+ return parts.join(' ');
24
+ }
25
+
26
+ export async function run() {
27
+ const apiUrl = resolveApiUrl();
28
+ const creds = await getCredentials(apiUrl);
29
+
30
+ if (!creds) {
31
+ console.error(' Not logged in. Run `ainote-mcp login`.');
32
+ process.exit(1);
33
+ return;
34
+ }
35
+
36
+ let identity = null;
37
+
38
+ if (creds.accessToken) {
39
+ try {
40
+ const response = await axios.get(`${apiUrl}/api/users/me`, {
41
+ headers: {
42
+ Authorization: `Bearer ${creds.accessToken}`,
43
+ Accept: 'application/json'
44
+ },
45
+ timeout: 15000,
46
+ validateStatus: () => true
47
+ });
48
+ if (response.status >= 200 && response.status < 300) {
49
+ const body = response.data || {};
50
+ identity = summarizeUser(body.user || body.data || body) || summarizeUser(body);
51
+ }
52
+ } catch {
53
+ // fall through to local-only fallback below
54
+ }
55
+ }
56
+
57
+ if (identity) {
58
+ console.log(` Logged in as ${identity}`);
59
+ } else {
60
+ // Local-only fallback: report what we have.
61
+ const host = (() => {
62
+ try { return new URL(apiUrl).host; } catch { return apiUrl; }
63
+ })();
64
+ console.log(` Credentials present for ${host}`);
65
+ if (creds.mcpKey) console.log(` MCP key: ${creds.mcpKey.slice(0, 8)}…`);
66
+ if (creds.expiresAt) {
67
+ const remaining = Math.floor((creds.expiresAt - Date.now()) / 1000);
68
+ if (remaining > 0) console.log(` Access token expires in ~${remaining}s`);
69
+ else console.log(` Access token expired (refresh on next call).`);
70
+ }
71
+ if (!creds.accessToken && !creds.mcpKey) {
72
+ console.error(' Could not verify identity. Try `ainote-mcp login` again.');
73
+ process.exit(1);
74
+ }
75
+ }
76
+ }
@@ -1 +1 @@
1
- export * from '../utils/api-client.js';
1
+ export * from '../utils/api-client.js';
@@ -1,126 +1,126 @@
1
- /**
2
- * Single source of truth for the default AI Note backend URL.
3
- *
4
- * Every entry point (MCP server, `login`, `logout`, `whoami`, `call`) must
5
- * resolve the API URL through resolveApiUrl(). Credentials written by
6
- * `ainote-mcp login` are namespaced by API host in the token store, so any
7
- * divergence here makes the other commands look up the wrong host.
8
- *
9
- * (api.ainote.dev is currently a CNAME to this same Render service, but it is
10
- * a different token-store key, so the default must stay identical everywhere.)
11
- */
12
- export const DEFAULT_API_URL = 'https://ainote-5muq.onrender.com';
13
-
14
- /**
15
- * Resolve the effective API base URL: AINOTE_API_URL env override, else the
16
- * default. Trailing slashes are stripped so "${url}/api/..." joins cleanly.
17
- */
18
- export function resolveApiUrl(env = process.env) {
19
- return (env.AINOTE_API_URL || DEFAULT_API_URL).replace(/\/+$/, '');
20
- }
21
-
22
- const DEFAULT_HTTP_PORT = 3030;
23
-
24
- const parseBoolean = (value, fallback) => {
25
- if (value === undefined) {
26
- return fallback;
27
- }
28
- if (typeof value === 'boolean') {
29
- return value;
30
- }
31
- const normalized = `${value}`.trim().toLowerCase();
32
- if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) {
33
- return true;
34
- }
35
- if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) {
36
- return false;
37
- }
38
- return fallback;
39
- };
40
-
41
- const parseNumber = (value, fallback) => {
42
- if (value === undefined) {
43
- return fallback;
44
- }
45
- const parsed = Number(value);
46
- return Number.isFinite(parsed) ? parsed : fallback;
47
- };
48
-
49
- const parseList = (value) => {
50
- if (!value) {
51
- return [];
52
- }
53
- if (Array.isArray(value)) {
54
- return value;
55
- }
56
- return value
57
- .split(',')
58
- .map((item) => item.trim())
59
- .filter((item) => item.length > 0);
60
- };
61
-
62
- /**
63
- * Synchronous config snapshot. Reads env vars only — used by the JSON-RPC
64
- * stdio entry path that cannot await.
65
- *
66
- * Note: stored CLI credentials (mcp_key from `ainote-mcp login`) are loaded
67
- * asynchronously by `getConfigAsync()` below. Callers that can await should
68
- * prefer the async form.
69
- */
70
- export function getConfig() {
71
- const features = {
72
- chatgptSupport: parseBoolean(process.env.AINOTE_ENABLE_CHATGPT_SUPPORT, true),
73
- oauthAuth: parseBoolean(process.env.AINOTE_ENABLE_OAUTH_AUTH, false),
74
- sseTransport: parseBoolean(process.env.AINOTE_ENABLE_SSE_TRANSPORT, true)
75
- };
76
-
77
- return {
78
- api: {
79
- key: process.env.AINOTE_API_KEY,
80
- url: resolveApiUrl()
81
- },
82
- http: {
83
- port: parseNumber(process.env.AINOTE_MCP_HTTP_PORT, DEFAULT_HTTP_PORT),
84
- endpoint: process.env.AINOTE_MCP_HTTP_ENDPOINT || '/sse',
85
- messageEndpoint: process.env.AINOTE_MCP_HTTP_MESSAGES_ENDPOINT || '/messages',
86
- allowedOrigins: parseList(process.env.AINOTE_MCP_ALLOWED_ORIGINS),
87
- allowedHosts: parseList(process.env.AINOTE_MCP_ALLOWED_HOSTS)
88
- },
89
- features,
90
- requiresLogin: !process.env.AINOTE_API_KEY
91
- };
92
- }
93
-
94
- /**
95
- * Async config that consults the local token store for an mcp_key when the
96
- * environment variable is unset. Used by the stdio entry path that runs
97
- * `await getConfigAsync()` at startup.
98
- */
99
- export async function getConfigAsync() {
100
- const base = getConfig();
101
-
102
- if (base.api.key) {
103
- return { ...base, requiresLogin: false };
104
- }
105
-
106
- try {
107
- const { getCredentials } = await import('../auth/token-store.js');
108
- const creds = await getCredentials(base.api.url);
109
- if (creds && creds.mcpKey) {
110
- return {
111
- ...base,
112
- api: { ...base.api, key: creds.mcpKey },
113
- requiresLogin: false,
114
- api_key_source: 'token-store'
115
- };
116
- }
117
- } catch (err) {
118
- process.stderr.write(`[ainote-mcp] token store unavailable: ${err.message}\n`);
119
- }
120
-
121
- return { ...base, requiresLogin: true };
122
- }
123
-
124
- export function getFeatureFlags() {
125
- return getConfig().features;
126
- }
1
+ /**
2
+ * Single source of truth for the default AI Note backend URL.
3
+ *
4
+ * Every entry point (MCP server, `login`, `logout`, `whoami`, `call`) must
5
+ * resolve the API URL through resolveApiUrl(). Credentials written by
6
+ * `ainote-mcp login` are namespaced by API host in the token store, so any
7
+ * divergence here makes the other commands look up the wrong host.
8
+ *
9
+ * (api.ainote.dev is currently a CNAME to this same Render service, but it is
10
+ * a different token-store key, so the default must stay identical everywhere.)
11
+ */
12
+ export const DEFAULT_API_URL = 'https://ainote-5muq.onrender.com';
13
+
14
+ /**
15
+ * Resolve the effective API base URL: AINOTE_API_URL env override, else the
16
+ * default. Trailing slashes are stripped so "${url}/api/..." joins cleanly.
17
+ */
18
+ export function resolveApiUrl(env = process.env) {
19
+ return (env.AINOTE_API_URL || DEFAULT_API_URL).replace(/\/+$/, '');
20
+ }
21
+
22
+ const DEFAULT_HTTP_PORT = 3030;
23
+
24
+ const parseBoolean = (value, fallback) => {
25
+ if (value === undefined) {
26
+ return fallback;
27
+ }
28
+ if (typeof value === 'boolean') {
29
+ return value;
30
+ }
31
+ const normalized = `${value}`.trim().toLowerCase();
32
+ if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) {
33
+ return true;
34
+ }
35
+ if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) {
36
+ return false;
37
+ }
38
+ return fallback;
39
+ };
40
+
41
+ const parseNumber = (value, fallback) => {
42
+ if (value === undefined) {
43
+ return fallback;
44
+ }
45
+ const parsed = Number(value);
46
+ return Number.isFinite(parsed) ? parsed : fallback;
47
+ };
48
+
49
+ const parseList = (value) => {
50
+ if (!value) {
51
+ return [];
52
+ }
53
+ if (Array.isArray(value)) {
54
+ return value;
55
+ }
56
+ return value
57
+ .split(',')
58
+ .map((item) => item.trim())
59
+ .filter((item) => item.length > 0);
60
+ };
61
+
62
+ /**
63
+ * Synchronous config snapshot. Reads env vars only — used by the JSON-RPC
64
+ * stdio entry path that cannot await.
65
+ *
66
+ * Note: stored CLI credentials (mcp_key from `ainote-mcp login`) are loaded
67
+ * asynchronously by `getConfigAsync()` below. Callers that can await should
68
+ * prefer the async form.
69
+ */
70
+ export function getConfig() {
71
+ const features = {
72
+ chatgptSupport: parseBoolean(process.env.AINOTE_ENABLE_CHATGPT_SUPPORT, true),
73
+ oauthAuth: parseBoolean(process.env.AINOTE_ENABLE_OAUTH_AUTH, false),
74
+ sseTransport: parseBoolean(process.env.AINOTE_ENABLE_SSE_TRANSPORT, true)
75
+ };
76
+
77
+ return {
78
+ api: {
79
+ key: process.env.AINOTE_API_KEY,
80
+ url: resolveApiUrl()
81
+ },
82
+ http: {
83
+ port: parseNumber(process.env.AINOTE_MCP_HTTP_PORT, DEFAULT_HTTP_PORT),
84
+ endpoint: process.env.AINOTE_MCP_HTTP_ENDPOINT || '/sse',
85
+ messageEndpoint: process.env.AINOTE_MCP_HTTP_MESSAGES_ENDPOINT || '/messages',
86
+ allowedOrigins: parseList(process.env.AINOTE_MCP_ALLOWED_ORIGINS),
87
+ allowedHosts: parseList(process.env.AINOTE_MCP_ALLOWED_HOSTS)
88
+ },
89
+ features,
90
+ requiresLogin: !process.env.AINOTE_API_KEY
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Async config that consults the local token store for an mcp_key when the
96
+ * environment variable is unset. Used by the stdio entry path that runs
97
+ * `await getConfigAsync()` at startup.
98
+ */
99
+ export async function getConfigAsync() {
100
+ const base = getConfig();
101
+
102
+ if (base.api.key) {
103
+ return { ...base, requiresLogin: false };
104
+ }
105
+
106
+ try {
107
+ const { getCredentials } = await import('../auth/token-store.js');
108
+ const creds = await getCredentials(base.api.url);
109
+ if (creds && creds.mcpKey) {
110
+ return {
111
+ ...base,
112
+ api: { ...base.api, key: creds.mcpKey },
113
+ requiresLogin: false,
114
+ api_key_source: 'token-store'
115
+ };
116
+ }
117
+ } catch (err) {
118
+ process.stderr.write(`[ainote-mcp] token store unavailable: ${err.message}\n`);
119
+ }
120
+
121
+ return { ...base, requiresLogin: true };
122
+ }
123
+
124
+ export function getFeatureFlags() {
125
+ return getConfig().features;
126
+ }
@@ -1 +1 @@
1
- export * from './config-manager.js';
1
+ export * from './config-manager.js';