@hasna/connectors 0.3.12 → 0.3.14
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/bin/index.js +43 -16
- package/bin/mcp.js +1 -1
- package/connectors/connect-googlecalendar/src/utils/config.ts +27 -8
- package/connectors/connect-googlecontacts/src/utils/config.ts +27 -8
- package/connectors/connect-googledrive/src/utils/config.ts +27 -8
- package/connectors/connect-googlesheets/src/utils/config.ts +27 -8
- package/connectors/connect-googletasks/src/utils/config.ts +26 -8
- package/connectors/connect-mistral/src/utils/config.ts +47 -15
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -6833,7 +6833,7 @@ var PRESETS = {
|
|
|
6833
6833
|
commerce: { description: "Commerce and finance", connectors: ["stripe", "shopify", "revolut", "mercury", "pandadoc"] }
|
|
6834
6834
|
};
|
|
6835
6835
|
var program2 = new Command;
|
|
6836
|
-
program2.name("connectors").description("Install API connectors for your project").version("0.3.
|
|
6836
|
+
program2.name("connectors").description("Install API connectors for your project").version("0.3.14").enablePositionalOptions();
|
|
6837
6837
|
program2.command("interactive", { isDefault: true }).alias("i").description("Interactive connector browser").action(() => {
|
|
6838
6838
|
if (!isTTY) {
|
|
6839
6839
|
console.log(`Non-interactive environment detected. Use a subcommand:
|
|
@@ -7951,27 +7951,48 @@ program2.command("export").option("-o, --output <file>", "Write to file instead
|
|
|
7951
7951
|
if (!statSync3(entryPath).isDirectory() || !entry.startsWith("connect-"))
|
|
7952
7952
|
continue;
|
|
7953
7953
|
const connectorName = entry.replace(/^connect-/, "");
|
|
7954
|
+
let credentials = undefined;
|
|
7955
|
+
const credentialsPath = join6(entryPath, "credentials.json");
|
|
7956
|
+
if (existsSync6(credentialsPath)) {
|
|
7957
|
+
try {
|
|
7958
|
+
credentials = JSON.parse(readFileSync5(credentialsPath, "utf-8"));
|
|
7959
|
+
} catch {}
|
|
7960
|
+
}
|
|
7954
7961
|
const profilesDir = join6(entryPath, "profiles");
|
|
7955
|
-
if (!existsSync6(profilesDir))
|
|
7962
|
+
if (!existsSync6(profilesDir) && !credentials)
|
|
7956
7963
|
continue;
|
|
7957
7964
|
const profiles = {};
|
|
7958
|
-
|
|
7959
|
-
const
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
profiles[pEntry.replace(/\.json$/, "")] = JSON.parse(readFileSync5(pPath, "utf-8"));
|
|
7963
|
-
} catch {}
|
|
7964
|
-
} else if (statSync3(pPath).isDirectory()) {
|
|
7965
|
-
const configPath = join6(pPath, "config.json");
|
|
7966
|
-
if (existsSync6(configPath)) {
|
|
7965
|
+
if (existsSync6(profilesDir)) {
|
|
7966
|
+
for (const pEntry of readdirSync4(profilesDir)) {
|
|
7967
|
+
const pPath = join6(profilesDir, pEntry);
|
|
7968
|
+
if (statSync3(pPath).isFile() && pEntry.endsWith(".json")) {
|
|
7967
7969
|
try {
|
|
7968
|
-
profiles[pEntry] = JSON.parse(readFileSync5(
|
|
7970
|
+
profiles[pEntry.replace(/\.json$/, "")] = JSON.parse(readFileSync5(pPath, "utf-8"));
|
|
7969
7971
|
} catch {}
|
|
7972
|
+
} else if (statSync3(pPath).isDirectory()) {
|
|
7973
|
+
const configPath = join6(pPath, "config.json");
|
|
7974
|
+
const tokensPath = join6(pPath, "tokens.json");
|
|
7975
|
+
let merged = {};
|
|
7976
|
+
if (existsSync6(configPath)) {
|
|
7977
|
+
try {
|
|
7978
|
+
merged = { ...merged, ...JSON.parse(readFileSync5(configPath, "utf-8")) };
|
|
7979
|
+
} catch {}
|
|
7980
|
+
}
|
|
7981
|
+
if (existsSync6(tokensPath)) {
|
|
7982
|
+
try {
|
|
7983
|
+
merged = { ...merged, ...JSON.parse(readFileSync5(tokensPath, "utf-8")) };
|
|
7984
|
+
} catch {}
|
|
7985
|
+
}
|
|
7986
|
+
if (Object.keys(merged).length > 0)
|
|
7987
|
+
profiles[pEntry] = merged;
|
|
7970
7988
|
}
|
|
7971
7989
|
}
|
|
7972
7990
|
}
|
|
7973
|
-
|
|
7974
|
-
|
|
7991
|
+
const connectorData = { profiles };
|
|
7992
|
+
if (credentials)
|
|
7993
|
+
connectorData.credentials = credentials;
|
|
7994
|
+
if (Object.keys(profiles).length > 0 || credentials)
|
|
7995
|
+
result[connectorName] = connectorData;
|
|
7975
7996
|
}
|
|
7976
7997
|
}
|
|
7977
7998
|
const exportPayload = options.includeSecrets ? { connectors: result, exportedAt: new Date().toISOString() } : { connectors: redactSecrets(result), exportedAt: new Date().toISOString(), redacted: true };
|
|
@@ -8031,9 +8052,15 @@ program2.command("import").argument("<file>", "JSON backup file to import (use -
|
|
|
8031
8052
|
for (const [connectorName, connData] of Object.entries(data.connectors)) {
|
|
8032
8053
|
if (!/^[a-z0-9-]+$/.test(connectorName))
|
|
8033
8054
|
continue;
|
|
8055
|
+
const connectorDir = join6(connectDir, `connect-${connectorName}`);
|
|
8056
|
+
if (connData.credentials && typeof connData.credentials === "object") {
|
|
8057
|
+
mkdirSync4(connectorDir, { recursive: true });
|
|
8058
|
+
writeFileSync4(join6(connectorDir, "credentials.json"), JSON.stringify(connData.credentials, null, 2));
|
|
8059
|
+
imported++;
|
|
8060
|
+
}
|
|
8034
8061
|
if (!connData.profiles || typeof connData.profiles !== "object")
|
|
8035
8062
|
continue;
|
|
8036
|
-
const profilesDir = join6(
|
|
8063
|
+
const profilesDir = join6(connectorDir, "profiles");
|
|
8037
8064
|
for (const [profileName, config] of Object.entries(connData.profiles)) {
|
|
8038
8065
|
if (!config || typeof config !== "object")
|
|
8039
8066
|
continue;
|
|
@@ -8049,7 +8076,7 @@ program2.command("import").argument("<file>", "JSON backup file to import (use -
|
|
|
8049
8076
|
}
|
|
8050
8077
|
});
|
|
8051
8078
|
program2.command("upgrade").alias("self-update").option("--check", "Only check for updates, don't install", false).option("--json", "Output as JSON", false).description("Check for updates and upgrade to the latest version").action(async (options) => {
|
|
8052
|
-
const currentVersion =
|
|
8079
|
+
const currentVersion = program2.version();
|
|
8053
8080
|
try {
|
|
8054
8081
|
const res = await fetch("https://registry.npmjs.org/@hasna/connectors/latest");
|
|
8055
8082
|
if (!res.ok)
|
package/bin/mcp.js
CHANGED
|
@@ -20315,7 +20315,7 @@ async function getConnectorCommandHelp(name, command) {
|
|
|
20315
20315
|
loadConnectorVersions();
|
|
20316
20316
|
var server = new McpServer({
|
|
20317
20317
|
name: "connectors",
|
|
20318
|
-
version: "0.3.
|
|
20318
|
+
version: "0.3.14"
|
|
20319
20319
|
});
|
|
20320
20320
|
server.registerTool("search_connectors", {
|
|
20321
20321
|
title: "Search Connectors",
|
|
@@ -143,22 +143,41 @@ export function deleteProfile(profile: string): boolean {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
/**
|
|
146
|
-
* Load profile config
|
|
146
|
+
* Load profile config — checks both flat and directory patterns, merges tokens.json
|
|
147
147
|
*/
|
|
148
148
|
export function loadProfile(profile?: string): ProfileConfig {
|
|
149
149
|
ensureConfigDir();
|
|
150
150
|
const profileName = profile || getCurrentProfile();
|
|
151
|
-
const profilePath = getProfilePath(profileName);
|
|
152
151
|
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
let config: ProfileConfig = {};
|
|
153
|
+
|
|
154
|
+
// Pattern 1: profiles/<name>.json (flat file)
|
|
155
|
+
const flatPath = getProfilePath(profileName);
|
|
156
|
+
if (existsSync(flatPath)) {
|
|
157
|
+
try { config = JSON.parse(readFileSync(flatPath, 'utf-8')); } catch {}
|
|
155
158
|
}
|
|
156
159
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
160
|
+
// Pattern 2: profiles/<name>/config.json (directory)
|
|
161
|
+
const dirConfigPath = join(PROFILES_DIR, profileName, 'config.json');
|
|
162
|
+
if (existsSync(dirConfigPath)) {
|
|
163
|
+
try {
|
|
164
|
+
const dirConfig = JSON.parse(readFileSync(dirConfigPath, 'utf-8'));
|
|
165
|
+
config = { ...config, ...dirConfig };
|
|
166
|
+
} catch {}
|
|
161
167
|
}
|
|
168
|
+
|
|
169
|
+
// Pattern 3: profiles/<name>/tokens.json (OAuth tokens)
|
|
170
|
+
const tokensPath = join(PROFILES_DIR, profileName, 'tokens.json');
|
|
171
|
+
if (existsSync(tokensPath)) {
|
|
172
|
+
try {
|
|
173
|
+
const tokens = JSON.parse(readFileSync(tokensPath, 'utf-8'));
|
|
174
|
+
if (tokens.accessToken && !config.accessToken) config.accessToken = tokens.accessToken;
|
|
175
|
+
if (tokens.refreshToken && !config.refreshToken) config.refreshToken = tokens.refreshToken;
|
|
176
|
+
if (tokens.expiresAt && !config.expiresAt) config.expiresAt = tokens.expiresAt;
|
|
177
|
+
} catch {}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return config;
|
|
162
181
|
}
|
|
163
182
|
|
|
164
183
|
/**
|
|
@@ -136,22 +136,41 @@ export function deleteProfile(profile: string): boolean {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
/**
|
|
139
|
-
* Load profile config
|
|
139
|
+
* Load profile config — checks both flat and directory patterns, merges tokens.json
|
|
140
140
|
*/
|
|
141
141
|
export function loadProfile(profile?: string): ProfileConfig {
|
|
142
142
|
ensureConfigDir();
|
|
143
143
|
const profileName = profile || getCurrentProfile();
|
|
144
|
-
const profilePath = getProfilePath(profileName);
|
|
145
144
|
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
let config: ProfileConfig = {};
|
|
146
|
+
|
|
147
|
+
// Pattern 1: profiles/<name>.json (flat file)
|
|
148
|
+
const flatPath = getProfilePath(profileName);
|
|
149
|
+
if (existsSync(flatPath)) {
|
|
150
|
+
try { config = JSON.parse(readFileSync(flatPath, 'utf-8')); } catch {}
|
|
148
151
|
}
|
|
149
152
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
// Pattern 2: profiles/<name>/config.json (directory)
|
|
154
|
+
const dirConfigPath = join(PROFILES_DIR, profileName, 'config.json');
|
|
155
|
+
if (existsSync(dirConfigPath)) {
|
|
156
|
+
try {
|
|
157
|
+
const dirConfig = JSON.parse(readFileSync(dirConfigPath, 'utf-8'));
|
|
158
|
+
config = { ...config, ...dirConfig };
|
|
159
|
+
} catch {}
|
|
154
160
|
}
|
|
161
|
+
|
|
162
|
+
// Pattern 3: profiles/<name>/tokens.json (OAuth tokens)
|
|
163
|
+
const tokensPath = join(PROFILES_DIR, profileName, 'tokens.json');
|
|
164
|
+
if (existsSync(tokensPath)) {
|
|
165
|
+
try {
|
|
166
|
+
const tokens = JSON.parse(readFileSync(tokensPath, 'utf-8'));
|
|
167
|
+
if (tokens.accessToken && !config.accessToken) config.accessToken = tokens.accessToken;
|
|
168
|
+
if (tokens.refreshToken && !config.refreshToken) config.refreshToken = tokens.refreshToken;
|
|
169
|
+
if (tokens.expiresAt && !config.tokenExpiresAt) config.tokenExpiresAt = tokens.expiresAt;
|
|
170
|
+
} catch {}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return config;
|
|
155
174
|
}
|
|
156
175
|
|
|
157
176
|
/**
|
|
@@ -259,18 +259,37 @@ export function ensureImportsDir(): string {
|
|
|
259
259
|
|
|
260
260
|
export function loadConfig(): CliConfig {
|
|
261
261
|
ensureConfigDir();
|
|
262
|
-
const configFile = join(getConfigDirInternal(), 'config.json');
|
|
263
262
|
|
|
264
|
-
|
|
265
|
-
|
|
263
|
+
let config: CliConfig = {};
|
|
264
|
+
|
|
265
|
+
// Pattern 1: profiles/<name>.json (flat file)
|
|
266
|
+
const profileName = getCurrentProfile();
|
|
267
|
+
const flatPath = join(getProfilesDir(), `${profileName}.json`);
|
|
268
|
+
if (existsSync(flatPath)) {
|
|
269
|
+
try { config = JSON.parse(readFileSync(flatPath, 'utf-8')); } catch {}
|
|
266
270
|
}
|
|
267
271
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
272
|
+
// Pattern 2: profiles/<name>/config.json (directory)
|
|
273
|
+
const configFile = join(getConfigDirInternal(), 'config.json');
|
|
274
|
+
if (existsSync(configFile)) {
|
|
275
|
+
try {
|
|
276
|
+
const dirConfig = JSON.parse(readFileSync(configFile, 'utf-8'));
|
|
277
|
+
config = { ...config, ...dirConfig };
|
|
278
|
+
} catch {}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Pattern 3: profiles/<name>/tokens.json (OAuth tokens)
|
|
282
|
+
const tokensPath = join(getConfigDirInternal(), 'tokens.json');
|
|
283
|
+
if (existsSync(tokensPath)) {
|
|
284
|
+
try {
|
|
285
|
+
const tokens = JSON.parse(readFileSync(tokensPath, 'utf-8'));
|
|
286
|
+
if (tokens.accessToken && !config.tokens) {
|
|
287
|
+
config.tokens = tokens;
|
|
288
|
+
}
|
|
289
|
+
} catch {}
|
|
273
290
|
}
|
|
291
|
+
|
|
292
|
+
return config;
|
|
274
293
|
}
|
|
275
294
|
|
|
276
295
|
export function saveConfig(config: CliConfig): void {
|
|
@@ -150,22 +150,41 @@ export function deleteProfile(profile: string): boolean {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
/**
|
|
153
|
-
* Load profile config
|
|
153
|
+
* Load profile config — checks both flat and directory patterns, merges tokens.json
|
|
154
154
|
*/
|
|
155
155
|
export function loadProfile(profile?: string): ProfileConfig {
|
|
156
156
|
ensureConfigDir();
|
|
157
157
|
const profileName = profile || getCurrentProfile();
|
|
158
|
-
const profilePath = getProfilePath(profileName);
|
|
159
158
|
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
let config: ProfileConfig = {};
|
|
160
|
+
|
|
161
|
+
// Pattern 1: profiles/<name>.json (flat file)
|
|
162
|
+
const flatPath = getProfilePath(profileName);
|
|
163
|
+
if (existsSync(flatPath)) {
|
|
164
|
+
try { config = JSON.parse(readFileSync(flatPath, 'utf-8')); } catch {}
|
|
162
165
|
}
|
|
163
166
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
// Pattern 2: profiles/<name>/config.json (directory)
|
|
168
|
+
const dirConfigPath = join(PROFILES_DIR, profileName, 'config.json');
|
|
169
|
+
if (existsSync(dirConfigPath)) {
|
|
170
|
+
try {
|
|
171
|
+
const dirConfig = JSON.parse(readFileSync(dirConfigPath, 'utf-8'));
|
|
172
|
+
config = { ...config, ...dirConfig };
|
|
173
|
+
} catch {}
|
|
168
174
|
}
|
|
175
|
+
|
|
176
|
+
// Pattern 3: profiles/<name>/tokens.json (OAuth tokens)
|
|
177
|
+
const tokensPath = join(PROFILES_DIR, profileName, 'tokens.json');
|
|
178
|
+
if (existsSync(tokensPath)) {
|
|
179
|
+
try {
|
|
180
|
+
const tokens = JSON.parse(readFileSync(tokensPath, 'utf-8'));
|
|
181
|
+
if (tokens.accessToken && !config.accessToken) config.accessToken = tokens.accessToken;
|
|
182
|
+
if (tokens.refreshToken && !config.refreshToken) config.refreshToken = tokens.refreshToken;
|
|
183
|
+
if (tokens.expiresAt && !config.expiresAt) config.expiresAt = tokens.expiresAt;
|
|
184
|
+
} catch {}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return config;
|
|
169
188
|
}
|
|
170
189
|
|
|
171
190
|
/**
|
|
@@ -167,18 +167,36 @@ export function ensureConfigDir(): void {
|
|
|
167
167
|
export function loadProfile(profile?: string): ProfileConfig {
|
|
168
168
|
ensureConfigDir();
|
|
169
169
|
const profileName = profile || getCurrentProfile();
|
|
170
|
-
const profileDir = join(getProfilesDir(), profileName);
|
|
171
|
-
const configFile = join(profileDir, 'config.json');
|
|
172
170
|
|
|
173
|
-
|
|
174
|
-
|
|
171
|
+
let config: ProfileConfig = {};
|
|
172
|
+
|
|
173
|
+
// Pattern 1: profiles/<name>.json (flat file)
|
|
174
|
+
const flatPath = join(getProfilesDir(), `${profileName}.json`);
|
|
175
|
+
if (existsSync(flatPath)) {
|
|
176
|
+
try { config = JSON.parse(readFileSync(flatPath, 'utf-8')); } catch {}
|
|
175
177
|
}
|
|
176
178
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
// Pattern 2: profiles/<name>/config.json (directory)
|
|
180
|
+
const dirConfigPath = join(getProfilesDir(), profileName, 'config.json');
|
|
181
|
+
if (existsSync(dirConfigPath)) {
|
|
182
|
+
try {
|
|
183
|
+
const dirConfig = JSON.parse(readFileSync(dirConfigPath, 'utf-8'));
|
|
184
|
+
config = { ...config, ...dirConfig };
|
|
185
|
+
} catch {}
|
|
181
186
|
}
|
|
187
|
+
|
|
188
|
+
// Pattern 3: profiles/<name>/tokens.json (OAuth tokens)
|
|
189
|
+
const tokensPath = join(getProfilesDir(), profileName, 'tokens.json');
|
|
190
|
+
if (existsSync(tokensPath)) {
|
|
191
|
+
try {
|
|
192
|
+
const tokens = JSON.parse(readFileSync(tokensPath, 'utf-8'));
|
|
193
|
+
if (tokens.accessToken && !config.accessToken) config.accessToken = tokens.accessToken;
|
|
194
|
+
if (tokens.refreshToken && !config.refreshToken) config.refreshToken = tokens.refreshToken;
|
|
195
|
+
if (tokens.expiresAt && !config.tokenExpiry) config.tokenExpiry = tokens.expiresAt;
|
|
196
|
+
} catch {}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return config;
|
|
182
200
|
}
|
|
183
201
|
|
|
184
202
|
export function saveProfile(config: ProfileConfig, profile?: string): void {
|
|
@@ -77,10 +77,10 @@ export function setCurrentProfile(profile: string): void {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
|
-
* Check if a profile exists
|
|
80
|
+
* Check if a profile exists (flat file or directory pattern)
|
|
81
81
|
*/
|
|
82
82
|
export function profileExists(profile: string): boolean {
|
|
83
|
-
return existsSync(getProfilePath(profile));
|
|
83
|
+
return existsSync(getProfilePath(profile)) || existsSync(join(PROFILES_DIR, profile));
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/**
|
|
@@ -93,10 +93,16 @@ export function listProfiles(): string[] {
|
|
|
93
93
|
return [];
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
.
|
|
96
|
+
const seen = new Set<string>();
|
|
97
|
+
const entries = readdirSync(PROFILES_DIR, { withFileTypes: true });
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
if (entry.isDirectory()) {
|
|
100
|
+
seen.add(entry.name);
|
|
101
|
+
} else if (entry.name.endsWith('.json')) {
|
|
102
|
+
seen.add(entry.name.replace('.json', ''));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return Array.from(seen).sort();
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
/**
|
|
@@ -135,27 +141,53 @@ export function deleteProfile(profile: string): boolean {
|
|
|
135
141
|
setCurrentProfile(DEFAULT_PROFILE);
|
|
136
142
|
}
|
|
137
143
|
|
|
138
|
-
|
|
144
|
+
// Remove flat file pattern
|
|
145
|
+
const flatPath = getProfilePath(profile);
|
|
146
|
+
if (existsSync(flatPath)) {
|
|
147
|
+
rmSync(flatPath);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Remove directory pattern
|
|
151
|
+
const dirPath = join(PROFILES_DIR, profile);
|
|
152
|
+
if (existsSync(dirPath)) {
|
|
153
|
+
rmSync(dirPath, { recursive: true });
|
|
154
|
+
}
|
|
155
|
+
|
|
139
156
|
return true;
|
|
140
157
|
}
|
|
141
158
|
|
|
142
159
|
/**
|
|
143
|
-
* Load profile config
|
|
160
|
+
* Load profile config.
|
|
161
|
+
* Checks both flat (profiles/<name>.json) and directory (profiles/<name>/config.json)
|
|
162
|
+
* patterns. Flat pattern takes precedence when both exist.
|
|
144
163
|
*/
|
|
145
164
|
export function loadProfile(profile?: string): ProfileConfig {
|
|
146
165
|
ensureConfigDir();
|
|
147
166
|
const profileName = profile || getCurrentProfile();
|
|
148
|
-
const profilePath = getProfilePath(profileName);
|
|
149
167
|
|
|
150
|
-
|
|
151
|
-
|
|
168
|
+
let config: ProfileConfig = {};
|
|
169
|
+
|
|
170
|
+
// Pattern 2: profiles/<name>/config.json (directory pattern, used by dashboard)
|
|
171
|
+
const dirConfigPath = join(PROFILES_DIR, profileName, 'config.json');
|
|
172
|
+
if (existsSync(dirConfigPath)) {
|
|
173
|
+
try {
|
|
174
|
+
config = JSON.parse(readFileSync(dirConfigPath, 'utf-8'));
|
|
175
|
+
} catch {
|
|
176
|
+
// ignore
|
|
177
|
+
}
|
|
152
178
|
}
|
|
153
179
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
180
|
+
// Pattern 1: profiles/<name>.json (flat pattern, used by CLI)
|
|
181
|
+
const profilePath = getProfilePath(profileName);
|
|
182
|
+
if (existsSync(profilePath)) {
|
|
183
|
+
try {
|
|
184
|
+
config = { ...config, ...JSON.parse(readFileSync(profilePath, 'utf-8')) };
|
|
185
|
+
} catch {
|
|
186
|
+
// ignore
|
|
187
|
+
}
|
|
158
188
|
}
|
|
189
|
+
|
|
190
|
+
return config;
|
|
159
191
|
}
|
|
160
192
|
|
|
161
193
|
/**
|