@de-otio/epimethian-mcp 2.0.2 → 3.0.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/dist/cli/index.js +824 -382
- package/dist/cli/index.js.map +4 -4
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -6803,20 +6803,28 @@ var require_dist = __commonJS({
|
|
|
6803
6803
|
});
|
|
6804
6804
|
|
|
6805
6805
|
// src/shared/keychain.ts
|
|
6806
|
+
function accountForProfile(profile) {
|
|
6807
|
+
if (!PROFILE_NAME_RE.test(profile)) {
|
|
6808
|
+
throw new Error(
|
|
6809
|
+
`Invalid profile name: "${profile}". Use lowercase alphanumeric and hyphens only (1-63 chars).`
|
|
6810
|
+
);
|
|
6811
|
+
}
|
|
6812
|
+
return `${LEGACY_ACCOUNT}/${profile}`;
|
|
6813
|
+
}
|
|
6806
6814
|
function exec(cmd, args) {
|
|
6807
6815
|
return new Promise((resolve2, reject) => {
|
|
6808
|
-
(0, import_node_child_process.execFile)(cmd, args, { timeout: 5e3 }, (err,
|
|
6816
|
+
(0, import_node_child_process.execFile)(cmd, args, { timeout: 5e3 }, (err, stdout3, stderr) => {
|
|
6809
6817
|
if (err) {
|
|
6810
6818
|
reject(new Error(stderr || err.message));
|
|
6811
6819
|
} else {
|
|
6812
|
-
resolve2(
|
|
6820
|
+
resolve2(stdout3);
|
|
6813
6821
|
}
|
|
6814
6822
|
});
|
|
6815
6823
|
});
|
|
6816
6824
|
}
|
|
6817
|
-
async function writeMacOS(password) {
|
|
6825
|
+
async function writeMacOS(account, password) {
|
|
6818
6826
|
try {
|
|
6819
|
-
await exec("security", ["delete-generic-password", "-s", SERVICE, "-a",
|
|
6827
|
+
await exec("security", ["delete-generic-password", "-s", SERVICE, "-a", account]);
|
|
6820
6828
|
} catch {
|
|
6821
6829
|
}
|
|
6822
6830
|
await exec("security", [
|
|
@@ -6824,23 +6832,26 @@ async function writeMacOS(password) {
|
|
|
6824
6832
|
"-s",
|
|
6825
6833
|
SERVICE,
|
|
6826
6834
|
"-a",
|
|
6827
|
-
|
|
6835
|
+
account,
|
|
6828
6836
|
"-w",
|
|
6829
6837
|
password,
|
|
6830
6838
|
"-U"
|
|
6831
6839
|
]);
|
|
6832
6840
|
}
|
|
6833
|
-
async function readMacOS() {
|
|
6841
|
+
async function readMacOS(account) {
|
|
6834
6842
|
return (await exec("security", [
|
|
6835
6843
|
"find-generic-password",
|
|
6836
6844
|
"-s",
|
|
6837
6845
|
SERVICE,
|
|
6838
6846
|
"-a",
|
|
6839
|
-
|
|
6847
|
+
account,
|
|
6840
6848
|
"-w"
|
|
6841
6849
|
])).trim();
|
|
6842
6850
|
}
|
|
6843
|
-
async function
|
|
6851
|
+
async function deleteMacOS(account) {
|
|
6852
|
+
await exec("security", ["delete-generic-password", "-s", SERVICE, "-a", account]);
|
|
6853
|
+
}
|
|
6854
|
+
async function writeLinux(account, password) {
|
|
6844
6855
|
return new Promise((resolve2, reject) => {
|
|
6845
6856
|
const proc = (0, import_node_child_process.spawn)("secret-tool", [
|
|
6846
6857
|
"store",
|
|
@@ -6849,7 +6860,7 @@ async function writeLinux(password) {
|
|
|
6849
6860
|
"service",
|
|
6850
6861
|
SERVICE,
|
|
6851
6862
|
"account",
|
|
6852
|
-
|
|
6863
|
+
account
|
|
6853
6864
|
]);
|
|
6854
6865
|
proc.stdin.write(password);
|
|
6855
6866
|
proc.stdin.end();
|
|
@@ -6860,55 +6871,122 @@ async function writeLinux(password) {
|
|
|
6860
6871
|
proc.on("error", reject);
|
|
6861
6872
|
});
|
|
6862
6873
|
}
|
|
6863
|
-
async function readLinux() {
|
|
6874
|
+
async function readLinux(account) {
|
|
6864
6875
|
return (await exec("secret-tool", [
|
|
6865
6876
|
"lookup",
|
|
6866
6877
|
"service",
|
|
6867
6878
|
SERVICE,
|
|
6868
6879
|
"account",
|
|
6869
|
-
|
|
6880
|
+
account
|
|
6870
6881
|
])).trim();
|
|
6871
6882
|
}
|
|
6872
|
-
async function
|
|
6883
|
+
async function deleteLinux(account) {
|
|
6884
|
+
await exec("secret-tool", [
|
|
6885
|
+
"clear",
|
|
6886
|
+
"service",
|
|
6887
|
+
SERVICE,
|
|
6888
|
+
"account",
|
|
6889
|
+
account
|
|
6890
|
+
]);
|
|
6891
|
+
}
|
|
6892
|
+
function resolveAccount(profile) {
|
|
6893
|
+
if (profile !== void 0) {
|
|
6894
|
+
return accountForProfile(profile);
|
|
6895
|
+
}
|
|
6896
|
+
return LEGACY_ACCOUNT;
|
|
6897
|
+
}
|
|
6898
|
+
async function saveToKeychain(creds, profile) {
|
|
6899
|
+
const account = resolveAccount(profile);
|
|
6873
6900
|
const json = JSON.stringify(creds);
|
|
6874
6901
|
if (process.platform === "darwin") {
|
|
6875
|
-
await writeMacOS(json);
|
|
6902
|
+
await writeMacOS(account, json);
|
|
6876
6903
|
} else if (process.platform === "linux") {
|
|
6877
|
-
await writeLinux(json);
|
|
6904
|
+
await writeLinux(account, json);
|
|
6878
6905
|
} else {
|
|
6879
6906
|
throw new Error(`Keychain not supported on ${process.platform}`);
|
|
6880
6907
|
}
|
|
6881
6908
|
}
|
|
6882
|
-
async function readFromKeychain() {
|
|
6909
|
+
async function readFromKeychain(profile) {
|
|
6910
|
+
const account = resolveAccount(profile);
|
|
6911
|
+
let raw;
|
|
6883
6912
|
try {
|
|
6884
|
-
let raw;
|
|
6885
6913
|
if (process.platform === "darwin") {
|
|
6886
|
-
raw = await readMacOS();
|
|
6914
|
+
raw = await readMacOS(account);
|
|
6887
6915
|
} else if (process.platform === "linux") {
|
|
6888
|
-
raw = await readLinux();
|
|
6916
|
+
raw = await readLinux(account);
|
|
6889
6917
|
} else {
|
|
6890
6918
|
return null;
|
|
6891
6919
|
}
|
|
6892
|
-
const parsed = JSON.parse(raw);
|
|
6893
|
-
if (parsed && typeof parsed.apiToken === "string") {
|
|
6894
|
-
return parsed;
|
|
6895
|
-
}
|
|
6896
|
-
return null;
|
|
6897
6920
|
} catch {
|
|
6898
6921
|
return null;
|
|
6899
6922
|
}
|
|
6923
|
+
let parsed;
|
|
6924
|
+
try {
|
|
6925
|
+
parsed = JSON.parse(raw);
|
|
6926
|
+
} catch {
|
|
6927
|
+
const label = profile ? `profile "${profile}"` : "legacy keychain entry";
|
|
6928
|
+
throw new Error(`Corrupted keychain entry for ${label}: invalid JSON.`);
|
|
6929
|
+
}
|
|
6930
|
+
if (!parsed || typeof parsed !== "object" || typeof parsed.apiToken !== "string" || typeof parsed.url !== "string" || typeof parsed.email !== "string") {
|
|
6931
|
+
const label = profile ? `profile "${profile}"` : "legacy keychain entry";
|
|
6932
|
+
throw new Error(
|
|
6933
|
+
`Corrupted keychain entry for ${label}: missing required fields (url, email, apiToken).`
|
|
6934
|
+
);
|
|
6935
|
+
}
|
|
6936
|
+
return parsed;
|
|
6937
|
+
}
|
|
6938
|
+
async function deleteFromKeychain(profile) {
|
|
6939
|
+
const account = resolveAccount(profile);
|
|
6940
|
+
try {
|
|
6941
|
+
if (process.platform === "darwin") {
|
|
6942
|
+
await deleteMacOS(account);
|
|
6943
|
+
} else if (process.platform === "linux") {
|
|
6944
|
+
await deleteLinux(account);
|
|
6945
|
+
}
|
|
6946
|
+
} catch {
|
|
6947
|
+
}
|
|
6900
6948
|
}
|
|
6901
|
-
var import_node_child_process, SERVICE,
|
|
6949
|
+
var import_node_child_process, SERVICE, LEGACY_ACCOUNT, PROFILE_NAME_RE;
|
|
6902
6950
|
var init_keychain = __esm({
|
|
6903
6951
|
"src/shared/keychain.ts"() {
|
|
6904
6952
|
"use strict";
|
|
6905
6953
|
import_node_child_process = require("node:child_process");
|
|
6906
6954
|
SERVICE = "epimethian-mcp";
|
|
6907
|
-
|
|
6955
|
+
LEGACY_ACCOUNT = "confluence-credentials";
|
|
6956
|
+
PROFILE_NAME_RE = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
6908
6957
|
}
|
|
6909
6958
|
});
|
|
6910
6959
|
|
|
6911
6960
|
// src/shared/test-connection.ts
|
|
6961
|
+
async function verifyTenantIdentity(url, email2, apiToken) {
|
|
6962
|
+
const endpoint = `${url.replace(/\/+$/, "")}/wiki/rest/api/user/current`;
|
|
6963
|
+
const auth = Buffer.from(`${email2}:${apiToken}`).toString("base64");
|
|
6964
|
+
try {
|
|
6965
|
+
const response = await fetch(endpoint, {
|
|
6966
|
+
method: "GET",
|
|
6967
|
+
headers: {
|
|
6968
|
+
Authorization: `Basic ${auth}`,
|
|
6969
|
+
Accept: "application/json"
|
|
6970
|
+
}
|
|
6971
|
+
});
|
|
6972
|
+
if (!response.ok) {
|
|
6973
|
+
return { ok: false, message: `HTTP ${response.status}: ${response.statusText}` };
|
|
6974
|
+
}
|
|
6975
|
+
const body = await response.json();
|
|
6976
|
+
const authenticatedEmail = body.email ?? "";
|
|
6977
|
+
if (authenticatedEmail.toLowerCase() !== email2.toLowerCase()) {
|
|
6978
|
+
return {
|
|
6979
|
+
ok: false,
|
|
6980
|
+
authenticatedEmail,
|
|
6981
|
+
message: `Tenant identity mismatch. Expected: ${email2}, authenticated as: ${authenticatedEmail}. This may indicate a DNS or configuration issue.`
|
|
6982
|
+
};
|
|
6983
|
+
}
|
|
6984
|
+
return { ok: true, authenticatedEmail, message: `Verified identity: ${authenticatedEmail}` };
|
|
6985
|
+
} catch (err) {
|
|
6986
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6987
|
+
return { ok: false, message: `Identity verification failed: ${message}` };
|
|
6988
|
+
}
|
|
6989
|
+
}
|
|
6912
6990
|
async function testConnection(url, email2, apiToken) {
|
|
6913
6991
|
const endpoint = `${url.replace(/\/+$/, "")}/wiki/api/v2/spaces?limit=1`;
|
|
6914
6992
|
const auth = Buffer.from(`${email2}:${apiToken}`).toString("base64");
|
|
@@ -6946,6 +7024,76 @@ var init_test_connection = __esm({
|
|
|
6946
7024
|
}
|
|
6947
7025
|
});
|
|
6948
7026
|
|
|
7027
|
+
// src/shared/profiles.ts
|
|
7028
|
+
async function ensureConfigDir() {
|
|
7029
|
+
await (0, import_promises2.mkdir)(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
7030
|
+
}
|
|
7031
|
+
async function readProfileRegistry() {
|
|
7032
|
+
try {
|
|
7033
|
+
const raw = await (0, import_promises2.readFile)(REGISTRY_FILE, "utf-8");
|
|
7034
|
+
const parsed = JSON.parse(raw);
|
|
7035
|
+
if (parsed && typeof parsed === "object" && Array.isArray(parsed.profiles) && parsed.profiles.every((p) => typeof p === "string")) {
|
|
7036
|
+
return parsed.profiles;
|
|
7037
|
+
}
|
|
7038
|
+
console.error(
|
|
7039
|
+
"Warning: Profile registry has unexpected format. Treating as empty."
|
|
7040
|
+
);
|
|
7041
|
+
return [];
|
|
7042
|
+
} catch (err) {
|
|
7043
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
7044
|
+
return [];
|
|
7045
|
+
}
|
|
7046
|
+
console.error("Warning: Could not read profile registry. Treating as empty.");
|
|
7047
|
+
return [];
|
|
7048
|
+
}
|
|
7049
|
+
}
|
|
7050
|
+
async function addToProfileRegistry(name) {
|
|
7051
|
+
await ensureConfigDir();
|
|
7052
|
+
const profiles = await readProfileRegistry();
|
|
7053
|
+
if (profiles.includes(name)) return;
|
|
7054
|
+
profiles.push(name);
|
|
7055
|
+
const data = JSON.stringify({ profiles }, null, 2) + "\n";
|
|
7056
|
+
const tmpFile = (0, import_node_path2.join)(
|
|
7057
|
+
CONFIG_DIR,
|
|
7058
|
+
`.profiles.${(0, import_node_crypto.randomBytes)(4).toString("hex")}.tmp`
|
|
7059
|
+
);
|
|
7060
|
+
await (0, import_promises2.writeFile)(tmpFile, data, { mode: 384 });
|
|
7061
|
+
await (0, import_promises2.rename)(tmpFile, REGISTRY_FILE);
|
|
7062
|
+
}
|
|
7063
|
+
async function removeFromProfileRegistry(name) {
|
|
7064
|
+
const profiles = await readProfileRegistry();
|
|
7065
|
+
const filtered = profiles.filter((p) => p !== name);
|
|
7066
|
+
if (filtered.length === profiles.length) return;
|
|
7067
|
+
await ensureConfigDir();
|
|
7068
|
+
const data = JSON.stringify({ profiles: filtered }, null, 2) + "\n";
|
|
7069
|
+
const tmpFile = (0, import_node_path2.join)(
|
|
7070
|
+
CONFIG_DIR,
|
|
7071
|
+
`.profiles.${(0, import_node_crypto.randomBytes)(4).toString("hex")}.tmp`
|
|
7072
|
+
);
|
|
7073
|
+
await (0, import_promises2.writeFile)(tmpFile, data, { mode: 384 });
|
|
7074
|
+
await (0, import_promises2.rename)(tmpFile, REGISTRY_FILE);
|
|
7075
|
+
}
|
|
7076
|
+
async function appendAuditLog(message) {
|
|
7077
|
+
await ensureConfigDir();
|
|
7078
|
+
const entry = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
|
|
7079
|
+
`;
|
|
7080
|
+
const { appendFile } = await import("node:fs/promises");
|
|
7081
|
+
await appendFile(AUDIT_LOG, entry, { mode: 384 });
|
|
7082
|
+
}
|
|
7083
|
+
var import_promises2, import_node_path2, import_node_os2, import_node_crypto, CONFIG_DIR, REGISTRY_FILE, AUDIT_LOG;
|
|
7084
|
+
var init_profiles = __esm({
|
|
7085
|
+
"src/shared/profiles.ts"() {
|
|
7086
|
+
"use strict";
|
|
7087
|
+
import_promises2 = require("node:fs/promises");
|
|
7088
|
+
import_node_path2 = require("node:path");
|
|
7089
|
+
import_node_os2 = require("node:os");
|
|
7090
|
+
import_node_crypto = require("node:crypto");
|
|
7091
|
+
CONFIG_DIR = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".config", "epimethian-mcp");
|
|
7092
|
+
REGISTRY_FILE = (0, import_node_path2.join)(CONFIG_DIR, "profiles.json");
|
|
7093
|
+
AUDIT_LOG = (0, import_node_path2.join)(CONFIG_DIR, "audit.log");
|
|
7094
|
+
}
|
|
7095
|
+
});
|
|
7096
|
+
|
|
6949
7097
|
// src/cli/setup.ts
|
|
6950
7098
|
var setup_exports = {};
|
|
6951
7099
|
__export(setup_exports, {
|
|
@@ -6981,15 +7129,22 @@ function readPassword(prompt) {
|
|
|
6981
7129
|
import_node_process2.stdin.on("data", onData);
|
|
6982
7130
|
});
|
|
6983
7131
|
}
|
|
6984
|
-
async function runSetup() {
|
|
7132
|
+
async function runSetup(profile) {
|
|
6985
7133
|
if (!import_node_process2.stdin.isTTY) {
|
|
6986
7134
|
console.error(
|
|
6987
7135
|
"Error: setup requires an interactive terminal.\nFor non-interactive environments, set CONFLUENCE_URL, CONFLUENCE_EMAIL, and CONFLUENCE_API_TOKEN as environment variables."
|
|
6988
7136
|
);
|
|
6989
7137
|
process.exit(1);
|
|
6990
7138
|
}
|
|
6991
|
-
|
|
6992
|
-
|
|
7139
|
+
if (profile !== void 0 && !PROFILE_NAME_RE.test(profile)) {
|
|
7140
|
+
console.error(
|
|
7141
|
+
`Error: Invalid profile name "${profile}". Use lowercase alphanumeric and hyphens only (1-63 chars).`
|
|
7142
|
+
);
|
|
7143
|
+
process.exit(1);
|
|
7144
|
+
}
|
|
7145
|
+
const banner = profile ? `Epimethian MCP - Credential setup for profile "${profile}"` : "Epimethian MCP - Confluence credential setup";
|
|
7146
|
+
console.log(banner + "\n");
|
|
7147
|
+
const existing = await readFromKeychain(profile);
|
|
6993
7148
|
const rl = readline.createInterface({ input: import_node_process2.stdin, output: import_node_process2.stdout });
|
|
6994
7149
|
try {
|
|
6995
7150
|
const defaultUrl = existing?.url ?? "";
|
|
@@ -7005,6 +7160,21 @@ async function runSetup() {
|
|
|
7005
7160
|
console.error("Error: URL must start with https://");
|
|
7006
7161
|
process.exit(1);
|
|
7007
7162
|
}
|
|
7163
|
+
try {
|
|
7164
|
+
const parsed = new URL(url);
|
|
7165
|
+
if (parsed.username || parsed.password || /[\n\r]/.test(url)) {
|
|
7166
|
+
console.error("Error: URL contains invalid characters.");
|
|
7167
|
+
process.exit(1);
|
|
7168
|
+
}
|
|
7169
|
+
if (!parsed.hostname.endsWith(".atlassian.net")) {
|
|
7170
|
+
console.error(
|
|
7171
|
+
`Warning: URL does not match *.atlassian.net. Ensure this is the correct Confluence instance.`
|
|
7172
|
+
);
|
|
7173
|
+
}
|
|
7174
|
+
} catch {
|
|
7175
|
+
console.error("Error: Invalid URL format.");
|
|
7176
|
+
process.exit(1);
|
|
7177
|
+
}
|
|
7008
7178
|
const defaultEmail = existing?.email ?? "";
|
|
7009
7179
|
const emailPrompt = defaultEmail ? `Email [${defaultEmail}]: ` : "Email: ";
|
|
7010
7180
|
let email2 = (await rl.question(emailPrompt)).trim();
|
|
@@ -7029,8 +7199,17 @@ async function runSetup() {
|
|
|
7029
7199
|
}
|
|
7030
7200
|
console.log(`${result.message}
|
|
7031
7201
|
`);
|
|
7032
|
-
await saveToKeychain({ url, email: email2, apiToken });
|
|
7033
|
-
|
|
7202
|
+
await saveToKeychain({ url, email: email2, apiToken }, profile);
|
|
7203
|
+
if (profile) {
|
|
7204
|
+
await addToProfileRegistry(profile);
|
|
7205
|
+
console.log(`Credentials saved to OS keychain (profile: ${profile}).
|
|
7206
|
+
`);
|
|
7207
|
+
} else {
|
|
7208
|
+
console.log("Credentials saved to OS keychain.\n");
|
|
7209
|
+
console.log(
|
|
7210
|
+
"Tip: Use --profile <name> for multi-tenant support.\n"
|
|
7211
|
+
);
|
|
7212
|
+
}
|
|
7034
7213
|
console.log(`Available tools (${TOOLS.length}):`);
|
|
7035
7214
|
console.log(` ${TOOLS.join(", ")}`);
|
|
7036
7215
|
console.log(
|
|
@@ -7048,6 +7227,7 @@ var init_setup = __esm({
|
|
|
7048
7227
|
import_node_process2 = require("node:process");
|
|
7049
7228
|
init_test_connection();
|
|
7050
7229
|
init_keychain();
|
|
7230
|
+
init_profiles();
|
|
7051
7231
|
TOOLS = [
|
|
7052
7232
|
"create_page",
|
|
7053
7233
|
"get_page",
|
|
@@ -7065,6 +7245,164 @@ var init_setup = __esm({
|
|
|
7065
7245
|
}
|
|
7066
7246
|
});
|
|
7067
7247
|
|
|
7248
|
+
// src/cli/profiles.ts
|
|
7249
|
+
var profiles_exports = {};
|
|
7250
|
+
__export(profiles_exports, {
|
|
7251
|
+
runProfiles: () => runProfiles
|
|
7252
|
+
});
|
|
7253
|
+
async function runProfiles() {
|
|
7254
|
+
const args = process.argv.slice(3);
|
|
7255
|
+
const removeIdx = args.indexOf("--remove");
|
|
7256
|
+
if (removeIdx > -1) {
|
|
7257
|
+
const name = args[removeIdx + 1];
|
|
7258
|
+
if (!name || !PROFILE_NAME_RE.test(name)) {
|
|
7259
|
+
console.error(
|
|
7260
|
+
"Error: --remove requires a valid profile name."
|
|
7261
|
+
);
|
|
7262
|
+
process.exit(1);
|
|
7263
|
+
}
|
|
7264
|
+
await removeProfile(name, args.includes("--force"));
|
|
7265
|
+
return;
|
|
7266
|
+
}
|
|
7267
|
+
const verbose = args.includes("--verbose");
|
|
7268
|
+
const profiles = await readProfileRegistry();
|
|
7269
|
+
if (profiles.length === 0) {
|
|
7270
|
+
console.log("No profiles configured. Run `epimethian-mcp setup --profile <name>` to create one.");
|
|
7271
|
+
return;
|
|
7272
|
+
}
|
|
7273
|
+
if (verbose) {
|
|
7274
|
+
console.log(
|
|
7275
|
+
` ${"Profile".padEnd(20)} ${"URL".padEnd(40)} Email`
|
|
7276
|
+
);
|
|
7277
|
+
console.log(
|
|
7278
|
+
` ${"\u2500".repeat(20)} ${"\u2500".repeat(40)} ${"\u2500".repeat(30)}`
|
|
7279
|
+
);
|
|
7280
|
+
for (const name of profiles) {
|
|
7281
|
+
try {
|
|
7282
|
+
const creds = await readFromKeychain(name);
|
|
7283
|
+
if (creds) {
|
|
7284
|
+
console.log(
|
|
7285
|
+
` ${name.padEnd(20)} ${creds.url.padEnd(40)} ${creds.email}`
|
|
7286
|
+
);
|
|
7287
|
+
} else {
|
|
7288
|
+
console.log(
|
|
7289
|
+
` ${name.padEnd(20)} (credentials missing)`
|
|
7290
|
+
);
|
|
7291
|
+
}
|
|
7292
|
+
} catch {
|
|
7293
|
+
console.log(
|
|
7294
|
+
` ${name.padEnd(20)} (credentials corrupted)`
|
|
7295
|
+
);
|
|
7296
|
+
}
|
|
7297
|
+
}
|
|
7298
|
+
} else {
|
|
7299
|
+
console.log("Configured profiles:");
|
|
7300
|
+
for (const name of profiles) {
|
|
7301
|
+
console.log(` ${name}`);
|
|
7302
|
+
}
|
|
7303
|
+
console.log("\nUse --verbose to show URLs and emails.");
|
|
7304
|
+
}
|
|
7305
|
+
}
|
|
7306
|
+
async function removeProfile(name, force) {
|
|
7307
|
+
if (!force || import_node_process3.stdin.isTTY) {
|
|
7308
|
+
if (!import_node_process3.stdin.isTTY) {
|
|
7309
|
+
console.error(
|
|
7310
|
+
"Error: Removing a profile requires an interactive terminal or --force in non-TTY mode."
|
|
7311
|
+
);
|
|
7312
|
+
process.exit(1);
|
|
7313
|
+
}
|
|
7314
|
+
const rl = readline2.createInterface({ input: import_node_process3.stdin, output: import_node_process3.stdout });
|
|
7315
|
+
try {
|
|
7316
|
+
const answer = await rl.question(
|
|
7317
|
+
`Remove profile "${name}" and delete its credentials? [y/N] `
|
|
7318
|
+
);
|
|
7319
|
+
if (answer.trim().toLowerCase() !== "y") {
|
|
7320
|
+
console.log("Cancelled.");
|
|
7321
|
+
return;
|
|
7322
|
+
}
|
|
7323
|
+
} finally {
|
|
7324
|
+
rl.close();
|
|
7325
|
+
}
|
|
7326
|
+
}
|
|
7327
|
+
await deleteFromKeychain(name);
|
|
7328
|
+
await removeFromProfileRegistry(name);
|
|
7329
|
+
await appendAuditLog(`Removed profile "${name}"`);
|
|
7330
|
+
console.log(`Profile "${name}" removed.`);
|
|
7331
|
+
}
|
|
7332
|
+
var import_node_process3, readline2;
|
|
7333
|
+
var init_profiles2 = __esm({
|
|
7334
|
+
"src/cli/profiles.ts"() {
|
|
7335
|
+
"use strict";
|
|
7336
|
+
import_node_process3 = require("node:process");
|
|
7337
|
+
readline2 = __toESM(require("node:readline/promises"));
|
|
7338
|
+
init_keychain();
|
|
7339
|
+
init_profiles();
|
|
7340
|
+
}
|
|
7341
|
+
});
|
|
7342
|
+
|
|
7343
|
+
// src/cli/status.ts
|
|
7344
|
+
var status_exports = {};
|
|
7345
|
+
__export(status_exports, {
|
|
7346
|
+
runStatus: () => runStatus
|
|
7347
|
+
});
|
|
7348
|
+
async function runStatus() {
|
|
7349
|
+
const profile = process.env.CONFLUENCE_PROFILE || "";
|
|
7350
|
+
const urlEnv = process.env.CONFLUENCE_URL || "";
|
|
7351
|
+
const emailEnv = process.env.CONFLUENCE_EMAIL || "";
|
|
7352
|
+
const tokenEnv = process.env.CONFLUENCE_API_TOKEN || "";
|
|
7353
|
+
let url;
|
|
7354
|
+
let email2;
|
|
7355
|
+
let apiToken;
|
|
7356
|
+
let mode;
|
|
7357
|
+
if (profile) {
|
|
7358
|
+
if (!PROFILE_NAME_RE.test(profile)) {
|
|
7359
|
+
console.error(`Invalid CONFLUENCE_PROFILE: "${profile}".`);
|
|
7360
|
+
process.exit(1);
|
|
7361
|
+
}
|
|
7362
|
+
const creds = await readFromKeychain(profile);
|
|
7363
|
+
if (!creds) {
|
|
7364
|
+
console.error(
|
|
7365
|
+
`No credentials found for profile "${profile}". Run \`epimethian-mcp setup --profile ${profile}\` to configure.`
|
|
7366
|
+
);
|
|
7367
|
+
process.exit(1);
|
|
7368
|
+
}
|
|
7369
|
+
url = creds.url;
|
|
7370
|
+
email2 = creds.email;
|
|
7371
|
+
apiToken = creds.apiToken;
|
|
7372
|
+
mode = `profile: ${profile}`;
|
|
7373
|
+
} else if (urlEnv && emailEnv && tokenEnv) {
|
|
7374
|
+
url = urlEnv;
|
|
7375
|
+
email2 = emailEnv;
|
|
7376
|
+
apiToken = tokenEnv;
|
|
7377
|
+
mode = "env-var mode";
|
|
7378
|
+
} else {
|
|
7379
|
+
const legacy = await readFromKeychain();
|
|
7380
|
+
if (!legacy) {
|
|
7381
|
+
console.error(
|
|
7382
|
+
"No credentials configured. Run `epimethian-mcp setup --profile <name>` to get started."
|
|
7383
|
+
);
|
|
7384
|
+
process.exit(1);
|
|
7385
|
+
}
|
|
7386
|
+
url = legacy.url;
|
|
7387
|
+
email2 = legacy.email;
|
|
7388
|
+
apiToken = legacy.apiToken;
|
|
7389
|
+
mode = "legacy keychain (no profile)";
|
|
7390
|
+
}
|
|
7391
|
+
console.log(`Profile: ${mode}`);
|
|
7392
|
+
console.log(`URL: ${url}`);
|
|
7393
|
+
console.log(`Email: ${email2}`);
|
|
7394
|
+
console.log("Testing connection...");
|
|
7395
|
+
const result = await testConnection(url, email2, apiToken);
|
|
7396
|
+
console.log(`Status: ${result.ok ? "Connected" : "Failed"} - ${result.message}`);
|
|
7397
|
+
}
|
|
7398
|
+
var init_status = __esm({
|
|
7399
|
+
"src/cli/status.ts"() {
|
|
7400
|
+
"use strict";
|
|
7401
|
+
init_test_connection();
|
|
7402
|
+
init_keychain();
|
|
7403
|
+
}
|
|
7404
|
+
});
|
|
7405
|
+
|
|
7068
7406
|
// node_modules/zod/v3/external.js
|
|
7069
7407
|
var external_exports = {};
|
|
7070
7408
|
__export(external_exports, {
|
|
@@ -21285,29 +21623,64 @@ var import_node_path = require("node:path");
|
|
|
21285
21623
|
|
|
21286
21624
|
// src/server/confluence-client.ts
|
|
21287
21625
|
init_keychain();
|
|
21626
|
+
init_test_connection();
|
|
21288
21627
|
var _config = null;
|
|
21289
|
-
async function
|
|
21290
|
-
|
|
21291
|
-
|
|
21292
|
-
|
|
21293
|
-
|
|
21294
|
-
if (
|
|
21295
|
-
|
|
21296
|
-
|
|
21297
|
-
|
|
21298
|
-
|
|
21299
|
-
|
|
21300
|
-
}
|
|
21301
|
-
|
|
21302
|
-
|
|
21628
|
+
async function resolveCredentials() {
|
|
21629
|
+
const profileEnv = process.env.CONFLUENCE_PROFILE || "";
|
|
21630
|
+
const urlEnv = process.env.CONFLUENCE_URL?.replace(/\/$/, "") || "";
|
|
21631
|
+
const emailEnv = process.env.CONFLUENCE_EMAIL || "";
|
|
21632
|
+
const tokenEnv = process.env.CONFLUENCE_API_TOKEN || "";
|
|
21633
|
+
if (profileEnv) {
|
|
21634
|
+
if (!PROFILE_NAME_RE.test(profileEnv)) {
|
|
21635
|
+
console.error(
|
|
21636
|
+
`Invalid CONFLUENCE_PROFILE: "${profileEnv}". Use lowercase alphanumeric and hyphens only (1-63 chars).`
|
|
21637
|
+
);
|
|
21638
|
+
process.exit(1);
|
|
21639
|
+
}
|
|
21640
|
+
const creds = await readFromKeychain(profileEnv);
|
|
21641
|
+
if (!creds) {
|
|
21642
|
+
console.error(
|
|
21643
|
+
`No credentials found for profile "${profileEnv}". Run \`epimethian-mcp setup --profile ${profileEnv}\` to configure.`
|
|
21644
|
+
);
|
|
21645
|
+
process.exit(1);
|
|
21646
|
+
}
|
|
21647
|
+
return {
|
|
21648
|
+
url: creds.url.replace(/\/$/, ""),
|
|
21649
|
+
email: creds.email,
|
|
21650
|
+
apiToken: creds.apiToken,
|
|
21651
|
+
profile: profileEnv
|
|
21652
|
+
};
|
|
21653
|
+
}
|
|
21654
|
+
if (urlEnv && emailEnv && tokenEnv) {
|
|
21655
|
+
delete process.env.CONFLUENCE_API_TOKEN;
|
|
21656
|
+
return { url: urlEnv, email: emailEnv, apiToken: tokenEnv, profile: null };
|
|
21657
|
+
}
|
|
21658
|
+
const setVars = [
|
|
21659
|
+
urlEnv && "CONFLUENCE_URL",
|
|
21660
|
+
emailEnv && "CONFLUENCE_EMAIL",
|
|
21661
|
+
tokenEnv && "CONFLUENCE_API_TOKEN"
|
|
21662
|
+
].filter(Boolean);
|
|
21663
|
+
if (setVars.length > 0) {
|
|
21303
21664
|
console.error(
|
|
21304
|
-
|
|
21665
|
+
`Error: Partial credentials detected (${setVars.join(", ")} set, but not all three).
|
|
21666
|
+
Either set CONFLUENCE_PROFILE or provide all three environment variables (CONFLUENCE_URL, CONFLUENCE_EMAIL, CONFLUENCE_API_TOKEN).
|
|
21667
|
+
Run \`epimethian-mcp setup --profile <name>\` for guided setup.`
|
|
21305
21668
|
);
|
|
21306
21669
|
process.exit(1);
|
|
21307
21670
|
}
|
|
21671
|
+
console.error(
|
|
21672
|
+
"Missing Confluence credentials. Set CONFLUENCE_PROFILE environment variable, or run `epimethian-mcp setup --profile <name>` to configure."
|
|
21673
|
+
);
|
|
21674
|
+
process.exit(1);
|
|
21675
|
+
}
|
|
21676
|
+
async function getConfig() {
|
|
21677
|
+
if (_config) return _config;
|
|
21678
|
+
const { url, email: email2, apiToken, profile } = await resolveCredentials();
|
|
21308
21679
|
const authHeader = "Basic " + Buffer.from(`${email2}:${apiToken}`).toString("base64");
|
|
21309
|
-
_config = {
|
|
21680
|
+
_config = Object.freeze({
|
|
21310
21681
|
url,
|
|
21682
|
+
email: email2,
|
|
21683
|
+
profile,
|
|
21311
21684
|
apiV2: `${url}/wiki/api/v2`,
|
|
21312
21685
|
apiV1: `${url}/wiki/rest/api`,
|
|
21313
21686
|
authHeader,
|
|
@@ -21315,9 +21688,43 @@ async function getConfig() {
|
|
|
21315
21688
|
Authorization: authHeader,
|
|
21316
21689
|
"Content-Type": "application/json"
|
|
21317
21690
|
}
|
|
21318
|
-
};
|
|
21691
|
+
});
|
|
21319
21692
|
return _config;
|
|
21320
21693
|
}
|
|
21694
|
+
async function validateStartup(config2) {
|
|
21695
|
+
const { url, email: email2, profile } = config2;
|
|
21696
|
+
const decoded = Buffer.from(
|
|
21697
|
+
config2.authHeader.replace("Basic ", ""),
|
|
21698
|
+
"base64"
|
|
21699
|
+
).toString();
|
|
21700
|
+
const colonIndex = decoded.indexOf(":");
|
|
21701
|
+
const apiToken = decoded.slice(colonIndex + 1);
|
|
21702
|
+
const connResult = await testConnection(url, email2, apiToken);
|
|
21703
|
+
if (!connResult.ok) {
|
|
21704
|
+
const profileHint = profile ? `Run \`epimethian-mcp setup --profile ${profile}\` to update credentials.` : "Check your CONFLUENCE_URL, CONFLUENCE_EMAIL, and CONFLUENCE_API_TOKEN.";
|
|
21705
|
+
console.error(
|
|
21706
|
+
`Error: Confluence credentials rejected by ${url}
|
|
21707
|
+
${connResult.message}
|
|
21708
|
+
${profileHint}`
|
|
21709
|
+
);
|
|
21710
|
+
process.exit(1);
|
|
21711
|
+
}
|
|
21712
|
+
const identityResult = await verifyTenantIdentity(url, email2, apiToken);
|
|
21713
|
+
if (!identityResult.ok) {
|
|
21714
|
+
const profileHint = profile ? `Run \`epimethian-mcp setup --profile ${profile}\` to reconfigure.` : "Check your credential configuration.";
|
|
21715
|
+
console.error(
|
|
21716
|
+
`Error: Tenant identity mismatch for ${profile ? `profile "${profile}"` : "configured credentials"}.
|
|
21717
|
+
Expected user: ${email2}
|
|
21718
|
+
` + (identityResult.authenticatedEmail ? `Authenticated as: ${identityResult.authenticatedEmail}
|
|
21719
|
+
` : "") + `This may indicate a DNS or configuration issue. ${profileHint}`
|
|
21720
|
+
);
|
|
21721
|
+
process.exit(1);
|
|
21722
|
+
}
|
|
21723
|
+
const profileLabel = profile ? `profile: ${profile}` : "env-var mode";
|
|
21724
|
+
console.error(
|
|
21725
|
+
`epimethian-mcp: connected to ${url} as ${email2} (${profileLabel})`
|
|
21726
|
+
);
|
|
21727
|
+
}
|
|
21321
21728
|
var PageSchema = external_exports.object({
|
|
21322
21729
|
id: external_exports.string(),
|
|
21323
21730
|
title: external_exports.string(),
|
|
@@ -21366,12 +21773,22 @@ var UploadResultSchema = external_exports.object({
|
|
|
21366
21773
|
})
|
|
21367
21774
|
).default([])
|
|
21368
21775
|
});
|
|
21776
|
+
function sanitizeError(message) {
|
|
21777
|
+
let safe = message.slice(0, 500);
|
|
21778
|
+
safe = safe.replace(/Basic [A-Za-z0-9+/=]{20,}/g, "Basic [REDACTED]");
|
|
21779
|
+
safe = safe.replace(/Authorization:\s*\S+/gi, "Authorization: [REDACTED]");
|
|
21780
|
+
safe = safe.replace(/Bearer [A-Za-z0-9._-]{20,}/g, "Bearer [REDACTED]");
|
|
21781
|
+
return safe;
|
|
21782
|
+
}
|
|
21369
21783
|
async function confluenceRequest(url, options = {}) {
|
|
21370
21784
|
const cfg = await getConfig();
|
|
21371
21785
|
const res = await fetch(url, { headers: cfg.jsonHeaders, ...options });
|
|
21372
21786
|
if (!res.ok) {
|
|
21373
21787
|
const body = await res.text();
|
|
21374
|
-
|
|
21788
|
+
console.error(`Confluence API error (${res.status}): ${body}`);
|
|
21789
|
+
throw new Error(
|
|
21790
|
+
`Confluence API error (${res.status}): ${sanitizeError(body)}`
|
|
21791
|
+
);
|
|
21375
21792
|
}
|
|
21376
21793
|
return res;
|
|
21377
21794
|
}
|
|
@@ -21530,7 +21947,10 @@ async function uploadAttachment(pageId, fileData, filename, comment) {
|
|
|
21530
21947
|
});
|
|
21531
21948
|
if (!res.ok) {
|
|
21532
21949
|
const body = await res.text();
|
|
21533
|
-
|
|
21950
|
+
console.error(`Confluence API error (${res.status}): ${body}`);
|
|
21951
|
+
throw new Error(
|
|
21952
|
+
`Confluence API error (${res.status}): ${sanitizeError(body)}`
|
|
21953
|
+
);
|
|
21534
21954
|
}
|
|
21535
21955
|
const data = UploadResultSchema.parse(await res.json());
|
|
21536
21956
|
const att = data.results[0];
|
|
@@ -21592,360 +22012,374 @@ function toolResult(text) {
|
|
|
21592
22012
|
return { content: [{ type: "text", text }] };
|
|
21593
22013
|
}
|
|
21594
22014
|
function toolError(err) {
|
|
21595
|
-
const
|
|
22015
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
22016
|
+
const message = sanitizeError(raw);
|
|
21596
22017
|
return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
|
|
21597
22018
|
}
|
|
21598
|
-
|
|
21599
|
-
|
|
21600
|
-
|
|
21601
|
-
|
|
21602
|
-
|
|
21603
|
-
|
|
21604
|
-
|
|
21605
|
-
|
|
21606
|
-
|
|
21607
|
-
|
|
21608
|
-
|
|
21609
|
-
|
|
21610
|
-
|
|
21611
|
-
|
|
21612
|
-
|
|
22019
|
+
function tenantEcho(config2) {
|
|
22020
|
+
const host = new URL(config2.url).hostname;
|
|
22021
|
+
const mode = config2.profile ? `profile: ${config2.profile}` : "env-var mode";
|
|
22022
|
+
return `
|
|
22023
|
+
Tenant: ${host} (${mode})`;
|
|
22024
|
+
}
|
|
22025
|
+
function registerTools(server, config2) {
|
|
22026
|
+
const echo = tenantEcho(config2);
|
|
22027
|
+
server.registerTool(
|
|
22028
|
+
"create_page",
|
|
22029
|
+
{
|
|
22030
|
+
description: "Create a new page in Confluence",
|
|
22031
|
+
inputSchema: {
|
|
22032
|
+
title: external_exports.string().describe("Page title"),
|
|
22033
|
+
space_key: external_exports.string().describe("Confluence space key, e.g. 'DEV' or 'TEAM'"),
|
|
22034
|
+
body: external_exports.string().describe(
|
|
22035
|
+
"Page content \u2013 plain text or Confluence storage format (HTML)"
|
|
22036
|
+
),
|
|
22037
|
+
parent_id: external_exports.string().optional().describe("Optional parent page ID")
|
|
22038
|
+
},
|
|
22039
|
+
annotations: { destructiveHint: false, idempotentHint: false }
|
|
21613
22040
|
},
|
|
21614
|
-
|
|
21615
|
-
|
|
21616
|
-
|
|
21617
|
-
|
|
21618
|
-
|
|
21619
|
-
|
|
21620
|
-
|
|
21621
|
-
|
|
21622
|
-
|
|
21623
|
-
|
|
21624
|
-
|
|
21625
|
-
|
|
21626
|
-
|
|
21627
|
-
|
|
21628
|
-
|
|
21629
|
-
|
|
21630
|
-
|
|
21631
|
-
|
|
21632
|
-
|
|
22041
|
+
async ({ title, space_key, body, parent_id }) => {
|
|
22042
|
+
try {
|
|
22043
|
+
const spaceId = await resolveSpaceId(space_key);
|
|
22044
|
+
const page = await createPage(spaceId, title, body, parent_id);
|
|
22045
|
+
return toolResult(await formatPage(page, false) + echo);
|
|
22046
|
+
} catch (err) {
|
|
22047
|
+
return toolError(err);
|
|
22048
|
+
}
|
|
22049
|
+
}
|
|
22050
|
+
);
|
|
22051
|
+
server.registerTool(
|
|
22052
|
+
"get_page",
|
|
22053
|
+
{
|
|
22054
|
+
description: "Read a Confluence page by ID",
|
|
22055
|
+
inputSchema: {
|
|
22056
|
+
page_id: external_exports.string().describe("The Confluence page ID"),
|
|
22057
|
+
include_body: external_exports.boolean().default(true).describe("Whether to include the page body content")
|
|
22058
|
+
},
|
|
22059
|
+
annotations: { readOnlyHint: true }
|
|
21633
22060
|
},
|
|
21634
|
-
|
|
21635
|
-
|
|
21636
|
-
|
|
21637
|
-
|
|
21638
|
-
|
|
21639
|
-
|
|
21640
|
-
|
|
21641
|
-
|
|
21642
|
-
|
|
21643
|
-
|
|
21644
|
-
|
|
21645
|
-
|
|
21646
|
-
|
|
21647
|
-
|
|
21648
|
-
|
|
21649
|
-
|
|
21650
|
-
|
|
21651
|
-
|
|
21652
|
-
|
|
21653
|
-
|
|
22061
|
+
async ({ page_id, include_body }) => {
|
|
22062
|
+
try {
|
|
22063
|
+
const page = await getPage(page_id, include_body);
|
|
22064
|
+
return toolResult(await formatPage(page, include_body));
|
|
22065
|
+
} catch (err) {
|
|
22066
|
+
return toolError(err);
|
|
22067
|
+
}
|
|
22068
|
+
}
|
|
22069
|
+
);
|
|
22070
|
+
server.registerTool(
|
|
22071
|
+
"update_page",
|
|
22072
|
+
{
|
|
22073
|
+
description: "Update an existing Confluence page. Auto-increments version number.",
|
|
22074
|
+
inputSchema: {
|
|
22075
|
+
page_id: external_exports.string().describe("The Confluence page ID"),
|
|
22076
|
+
title: external_exports.string().optional().describe("New title (omit to keep current)"),
|
|
22077
|
+
body: external_exports.string().optional().describe("New body content in plain text or storage format"),
|
|
22078
|
+
version_message: external_exports.string().optional().describe("Optional version comment")
|
|
22079
|
+
},
|
|
22080
|
+
annotations: { destructiveHint: false, idempotentHint: true }
|
|
21654
22081
|
},
|
|
21655
|
-
|
|
21656
|
-
|
|
21657
|
-
|
|
21658
|
-
|
|
21659
|
-
|
|
21660
|
-
|
|
21661
|
-
|
|
21662
|
-
|
|
21663
|
-
|
|
21664
|
-
|
|
21665
|
-
|
|
21666
|
-
|
|
21667
|
-
|
|
21668
|
-
return toolError(err);
|
|
22082
|
+
async ({ page_id, title, body, version_message }) => {
|
|
22083
|
+
try {
|
|
22084
|
+
const { page, newVersion } = await updatePage(page_id, {
|
|
22085
|
+
title,
|
|
22086
|
+
body,
|
|
22087
|
+
versionMessage: version_message
|
|
22088
|
+
});
|
|
22089
|
+
return toolResult(
|
|
22090
|
+
`Updated: ${page.title} (ID: ${page.id}, version: ${newVersion})` + echo
|
|
22091
|
+
);
|
|
22092
|
+
} catch (err) {
|
|
22093
|
+
return toolError(err);
|
|
22094
|
+
}
|
|
21669
22095
|
}
|
|
21670
|
-
|
|
21671
|
-
|
|
21672
|
-
|
|
21673
|
-
|
|
21674
|
-
|
|
21675
|
-
|
|
21676
|
-
|
|
21677
|
-
|
|
22096
|
+
);
|
|
22097
|
+
server.registerTool(
|
|
22098
|
+
"delete_page",
|
|
22099
|
+
{
|
|
22100
|
+
description: "Delete a Confluence page by ID",
|
|
22101
|
+
inputSchema: {
|
|
22102
|
+
page_id: external_exports.string().describe("The Confluence page ID to delete")
|
|
22103
|
+
},
|
|
22104
|
+
annotations: { destructiveHint: true, idempotentHint: true }
|
|
21678
22105
|
},
|
|
21679
|
-
|
|
21680
|
-
|
|
21681
|
-
|
|
21682
|
-
|
|
21683
|
-
|
|
21684
|
-
|
|
21685
|
-
|
|
21686
|
-
|
|
21687
|
-
|
|
21688
|
-
|
|
21689
|
-
|
|
21690
|
-
|
|
21691
|
-
|
|
21692
|
-
|
|
21693
|
-
|
|
21694
|
-
|
|
21695
|
-
|
|
21696
|
-
|
|
21697
|
-
|
|
21698
|
-
|
|
22106
|
+
async ({ page_id }) => {
|
|
22107
|
+
try {
|
|
22108
|
+
await deletePage(page_id);
|
|
22109
|
+
return toolResult(`Deleted page ${page_id}` + echo);
|
|
22110
|
+
} catch (err) {
|
|
22111
|
+
return toolError(err);
|
|
22112
|
+
}
|
|
22113
|
+
}
|
|
22114
|
+
);
|
|
22115
|
+
server.registerTool(
|
|
22116
|
+
"search_pages",
|
|
22117
|
+
{
|
|
22118
|
+
description: "Search Confluence pages using CQL (Confluence Query Language)",
|
|
22119
|
+
inputSchema: {
|
|
22120
|
+
cql: external_exports.string().describe(
|
|
22121
|
+
`CQL query string (e.g., 'space = "DEV" AND title ~ "architecture"')`
|
|
22122
|
+
),
|
|
22123
|
+
limit: external_exports.number().default(25).describe("Maximum results to return (default: 25)")
|
|
22124
|
+
},
|
|
22125
|
+
annotations: { readOnlyHint: true }
|
|
21699
22126
|
},
|
|
21700
|
-
|
|
21701
|
-
|
|
21702
|
-
|
|
21703
|
-
|
|
21704
|
-
|
|
21705
|
-
|
|
21706
|
-
|
|
21707
|
-
|
|
21708
|
-
|
|
21709
|
-
|
|
21710
|
-
|
|
21711
|
-
lines.
|
|
21712
|
-
}
|
|
21713
|
-
|
|
21714
|
-
|
|
21715
|
-
|
|
21716
|
-
|
|
21717
|
-
|
|
21718
|
-
|
|
21719
|
-
|
|
21720
|
-
|
|
21721
|
-
|
|
21722
|
-
|
|
21723
|
-
|
|
21724
|
-
|
|
21725
|
-
|
|
21726
|
-
|
|
22127
|
+
async ({ cql, limit }) => {
|
|
22128
|
+
try {
|
|
22129
|
+
const results = await searchPages(cql, limit);
|
|
22130
|
+
if (results.length === 0) {
|
|
22131
|
+
return toolResult("No pages found matching the query.");
|
|
22132
|
+
}
|
|
22133
|
+
const lines = [`Found ${results.length} page(s):`, ""];
|
|
22134
|
+
for (const p of results) {
|
|
22135
|
+
const spaceKey = p.spaceId ?? p.space?.key ?? "N/A";
|
|
22136
|
+
lines.push(`- ${p.title} (ID: ${p.id}, space: ${spaceKey})`);
|
|
22137
|
+
}
|
|
22138
|
+
return toolResult(lines.join("\n"));
|
|
22139
|
+
} catch (err) {
|
|
22140
|
+
return toolError(err);
|
|
22141
|
+
}
|
|
22142
|
+
}
|
|
22143
|
+
);
|
|
22144
|
+
server.registerTool(
|
|
22145
|
+
"list_pages",
|
|
22146
|
+
{
|
|
22147
|
+
description: "List pages in a Confluence space",
|
|
22148
|
+
inputSchema: {
|
|
22149
|
+
space_key: external_exports.string().describe("Confluence space key (e.g., 'DEV')"),
|
|
22150
|
+
limit: external_exports.number().default(25).describe("Maximum results (default: 25)"),
|
|
22151
|
+
status: external_exports.string().default("current").describe("Page status filter (default: 'current')")
|
|
22152
|
+
},
|
|
22153
|
+
annotations: { readOnlyHint: true }
|
|
21727
22154
|
},
|
|
21728
|
-
|
|
21729
|
-
|
|
21730
|
-
|
|
21731
|
-
|
|
21732
|
-
|
|
21733
|
-
|
|
21734
|
-
|
|
21735
|
-
|
|
21736
|
-
|
|
21737
|
-
|
|
21738
|
-
|
|
21739
|
-
lines.
|
|
21740
|
-
}
|
|
21741
|
-
|
|
21742
|
-
|
|
21743
|
-
|
|
21744
|
-
|
|
21745
|
-
|
|
21746
|
-
|
|
21747
|
-
|
|
21748
|
-
|
|
21749
|
-
|
|
21750
|
-
|
|
21751
|
-
|
|
21752
|
-
|
|
21753
|
-
|
|
22155
|
+
async ({ space_key, limit, status }) => {
|
|
22156
|
+
try {
|
|
22157
|
+
const spaceId = await resolveSpaceId(space_key);
|
|
22158
|
+
const pages = await listPages(spaceId, limit, status);
|
|
22159
|
+
if (pages.length === 0) {
|
|
22160
|
+
return toolResult(`No pages found in space ${space_key}.`);
|
|
22161
|
+
}
|
|
22162
|
+
const lines = [`Pages in ${space_key} (${pages.length}):`, ""];
|
|
22163
|
+
for (const p of pages) {
|
|
22164
|
+
lines.push(`- ${p.title} (ID: ${p.id})`);
|
|
22165
|
+
}
|
|
22166
|
+
return toolResult(lines.join("\n"));
|
|
22167
|
+
} catch (err) {
|
|
22168
|
+
return toolError(err);
|
|
22169
|
+
}
|
|
22170
|
+
}
|
|
22171
|
+
);
|
|
22172
|
+
server.registerTool(
|
|
22173
|
+
"get_page_children",
|
|
22174
|
+
{
|
|
22175
|
+
description: "Get child pages of a given Confluence page",
|
|
22176
|
+
inputSchema: {
|
|
22177
|
+
page_id: external_exports.string().describe("Parent page ID"),
|
|
22178
|
+
limit: external_exports.number().default(25).describe("Maximum results (default: 25)")
|
|
22179
|
+
},
|
|
22180
|
+
annotations: { readOnlyHint: true }
|
|
21754
22181
|
},
|
|
21755
|
-
|
|
21756
|
-
|
|
21757
|
-
|
|
21758
|
-
|
|
21759
|
-
|
|
21760
|
-
|
|
21761
|
-
|
|
21762
|
-
|
|
21763
|
-
|
|
21764
|
-
|
|
21765
|
-
lines.
|
|
21766
|
-
}
|
|
21767
|
-
|
|
21768
|
-
|
|
21769
|
-
|
|
21770
|
-
|
|
21771
|
-
|
|
21772
|
-
|
|
21773
|
-
|
|
21774
|
-
|
|
21775
|
-
|
|
21776
|
-
|
|
21777
|
-
|
|
21778
|
-
|
|
21779
|
-
|
|
22182
|
+
async ({ page_id, limit }) => {
|
|
22183
|
+
try {
|
|
22184
|
+
const children = await getPageChildren(page_id, limit);
|
|
22185
|
+
if (children.length === 0) {
|
|
22186
|
+
return toolResult(`No child pages found for page ${page_id}.`);
|
|
22187
|
+
}
|
|
22188
|
+
const lines = [`Child pages (${children.length}):`, ""];
|
|
22189
|
+
for (const p of children) {
|
|
22190
|
+
lines.push(`- ${p.title} (ID: ${p.id})`);
|
|
22191
|
+
}
|
|
22192
|
+
return toolResult(lines.join("\n"));
|
|
22193
|
+
} catch (err) {
|
|
22194
|
+
return toolError(err);
|
|
22195
|
+
}
|
|
22196
|
+
}
|
|
22197
|
+
);
|
|
22198
|
+
server.registerTool(
|
|
22199
|
+
"get_spaces",
|
|
22200
|
+
{
|
|
22201
|
+
description: "List available Confluence spaces",
|
|
22202
|
+
inputSchema: {
|
|
22203
|
+
limit: external_exports.number().default(25).describe("Maximum results (default: 25)"),
|
|
22204
|
+
type: external_exports.string().optional().describe("Filter by space type (e.g., 'global', 'personal')")
|
|
22205
|
+
},
|
|
22206
|
+
annotations: { readOnlyHint: true }
|
|
21780
22207
|
},
|
|
21781
|
-
|
|
21782
|
-
|
|
21783
|
-
|
|
21784
|
-
|
|
21785
|
-
|
|
21786
|
-
|
|
21787
|
-
|
|
21788
|
-
|
|
21789
|
-
|
|
21790
|
-
|
|
21791
|
-
lines.
|
|
21792
|
-
}
|
|
21793
|
-
|
|
21794
|
-
|
|
21795
|
-
|
|
21796
|
-
|
|
21797
|
-
|
|
21798
|
-
|
|
21799
|
-
|
|
21800
|
-
|
|
21801
|
-
|
|
21802
|
-
|
|
21803
|
-
|
|
21804
|
-
|
|
21805
|
-
|
|
21806
|
-
|
|
22208
|
+
async ({ limit, type }) => {
|
|
22209
|
+
try {
|
|
22210
|
+
const spaces = await getSpaces(limit, type);
|
|
22211
|
+
if (spaces.length === 0) {
|
|
22212
|
+
return toolResult("No spaces found.");
|
|
22213
|
+
}
|
|
22214
|
+
const lines = [`Found ${spaces.length} space(s):`, ""];
|
|
22215
|
+
for (const s of spaces) {
|
|
22216
|
+
lines.push(`- ${s.name} (key: ${s.key}, type: ${s.type})`);
|
|
22217
|
+
}
|
|
22218
|
+
return toolResult(lines.join("\n"));
|
|
22219
|
+
} catch (err) {
|
|
22220
|
+
return toolError(err);
|
|
22221
|
+
}
|
|
22222
|
+
}
|
|
22223
|
+
);
|
|
22224
|
+
server.registerTool(
|
|
22225
|
+
"get_page_by_title",
|
|
22226
|
+
{
|
|
22227
|
+
description: "Look up a Confluence page by its title within a space",
|
|
22228
|
+
inputSchema: {
|
|
22229
|
+
title: external_exports.string().describe("Page title to search for"),
|
|
22230
|
+
space_key: external_exports.string().describe("Confluence space key (e.g., 'DEV')"),
|
|
22231
|
+
include_body: external_exports.boolean().default(false).describe("Whether to include the page body content")
|
|
22232
|
+
},
|
|
22233
|
+
annotations: { readOnlyHint: true }
|
|
21807
22234
|
},
|
|
21808
|
-
|
|
21809
|
-
|
|
21810
|
-
|
|
21811
|
-
|
|
21812
|
-
|
|
21813
|
-
|
|
21814
|
-
|
|
22235
|
+
async ({ title, space_key, include_body }) => {
|
|
22236
|
+
try {
|
|
22237
|
+
const spaceId = await resolveSpaceId(space_key);
|
|
22238
|
+
const page = await getPageByTitle(spaceId, title, include_body);
|
|
22239
|
+
if (!page) {
|
|
22240
|
+
return toolResult(
|
|
22241
|
+
`No page found with title "${title}" in space ${space_key}.`
|
|
22242
|
+
);
|
|
22243
|
+
}
|
|
22244
|
+
return toolResult(await formatPage(page, include_body));
|
|
22245
|
+
} catch (err) {
|
|
22246
|
+
return toolError(err);
|
|
22247
|
+
}
|
|
22248
|
+
}
|
|
22249
|
+
);
|
|
22250
|
+
server.registerTool(
|
|
22251
|
+
"add_attachment",
|
|
22252
|
+
{
|
|
22253
|
+
description: "Upload a file as an attachment to a Confluence page. The file_path must be an absolute path under the current working directory.",
|
|
22254
|
+
inputSchema: {
|
|
22255
|
+
page_id: external_exports.string().describe("The Confluence page ID to attach the file to"),
|
|
22256
|
+
file_path: external_exports.string().describe("Absolute path to the file on the local filesystem"),
|
|
22257
|
+
filename: external_exports.string().optional().describe(
|
|
22258
|
+
"Filename to use in Confluence (defaults to the basename of file_path)"
|
|
22259
|
+
),
|
|
22260
|
+
comment: external_exports.string().optional().describe("Optional comment for the attachment")
|
|
22261
|
+
},
|
|
22262
|
+
annotations: { destructiveHint: false, idempotentHint: false }
|
|
22263
|
+
},
|
|
22264
|
+
async ({ page_id, file_path, filename, comment }) => {
|
|
22265
|
+
try {
|
|
22266
|
+
const resolved = await (0, import_promises.realpath)((0, import_node_path.resolve)(file_path));
|
|
22267
|
+
const cwd = await (0, import_promises.realpath)(process.cwd());
|
|
22268
|
+
if (!resolved.startsWith(cwd + "/") && resolved !== cwd) {
|
|
22269
|
+
return toolError(
|
|
22270
|
+
new Error(
|
|
22271
|
+
`File path must be under the working directory (${cwd}). Got: ${resolved}`
|
|
22272
|
+
)
|
|
22273
|
+
);
|
|
22274
|
+
}
|
|
22275
|
+
const fileData = await (0, import_promises.readFile)(resolved);
|
|
22276
|
+
const name = filename ?? resolved.split("/").pop() ?? "attachment";
|
|
22277
|
+
const att = await uploadAttachment(page_id, fileData, name, comment);
|
|
21815
22278
|
return toolResult(
|
|
21816
|
-
`
|
|
22279
|
+
`Attached: ${att.title} (ID: ${att.id}, size: ${att.fileSize ?? "unknown"} bytes) to page ${page_id}` + echo
|
|
21817
22280
|
);
|
|
21818
|
-
}
|
|
21819
|
-
|
|
21820
|
-
|
|
21821
|
-
|
|
21822
|
-
|
|
21823
|
-
|
|
21824
|
-
|
|
21825
|
-
|
|
21826
|
-
|
|
21827
|
-
|
|
21828
|
-
|
|
21829
|
-
|
|
21830
|
-
|
|
21831
|
-
|
|
21832
|
-
|
|
21833
|
-
|
|
21834
|
-
|
|
21835
|
-
|
|
22281
|
+
} catch (err) {
|
|
22282
|
+
return toolError(err);
|
|
22283
|
+
}
|
|
22284
|
+
}
|
|
22285
|
+
);
|
|
22286
|
+
server.registerTool(
|
|
22287
|
+
"add_drawio_diagram",
|
|
22288
|
+
{
|
|
22289
|
+
description: "Add a draw.io diagram to a Confluence page. Uploads the diagram as an attachment and embeds it using the draw.io macro. Requires the draw.io app on the Confluence instance.",
|
|
22290
|
+
inputSchema: {
|
|
22291
|
+
page_id: external_exports.string().describe("The Confluence page ID to add the diagram to"),
|
|
22292
|
+
diagram_xml: external_exports.string().describe(
|
|
22293
|
+
"The draw.io diagram content in mxGraph XML format (the full XML starting with <mxfile>)"
|
|
22294
|
+
),
|
|
22295
|
+
diagram_name: external_exports.string().regex(
|
|
22296
|
+
/^[a-zA-Z0-9_\-. ]+$/,
|
|
22297
|
+
"Diagram name may only contain letters, numbers, spaces, hyphens, underscores, and dots"
|
|
22298
|
+
).describe(
|
|
22299
|
+
"Name for the diagram file (e.g., 'architecture.drawio'). Will have .drawio appended if not present."
|
|
22300
|
+
),
|
|
22301
|
+
append: external_exports.boolean().default(true).describe(
|
|
22302
|
+
"If true, appends the diagram to existing page content. If false, replaces the page body."
|
|
22303
|
+
)
|
|
22304
|
+
},
|
|
22305
|
+
annotations: { destructiveHint: false, idempotentHint: false }
|
|
21836
22306
|
},
|
|
21837
|
-
|
|
21838
|
-
|
|
21839
|
-
|
|
21840
|
-
|
|
21841
|
-
|
|
21842
|
-
|
|
21843
|
-
|
|
21844
|
-
|
|
21845
|
-
|
|
21846
|
-
|
|
21847
|
-
)
|
|
22307
|
+
async ({ page_id, diagram_xml, diagram_name, append }) => {
|
|
22308
|
+
try {
|
|
22309
|
+
const filename = diagram_name.endsWith(".drawio") ? diagram_name : `${diagram_name}.drawio`;
|
|
22310
|
+
const tmpDir = await (0, import_promises.mkdtemp)((0, import_node_path.join)((0, import_node_os.tmpdir)(), "drawio-"));
|
|
22311
|
+
try {
|
|
22312
|
+
const tmpPath = (0, import_node_path.join)(tmpDir, filename);
|
|
22313
|
+
await (0, import_promises.writeFile)(tmpPath, diagram_xml, "utf-8");
|
|
22314
|
+
const fileData = await (0, import_promises.readFile)(tmpPath);
|
|
22315
|
+
await uploadAttachment(page_id, fileData, filename);
|
|
22316
|
+
} finally {
|
|
22317
|
+
await (0, import_promises.rm)(tmpDir, { recursive: true, force: true });
|
|
22318
|
+
}
|
|
22319
|
+
const macro = [
|
|
22320
|
+
`<ac:structured-macro ac:name="drawio" ac:schema-version="1">`,
|
|
22321
|
+
` <ac:parameter ac:name="diagramName">${escapeXml(filename)}</ac:parameter>`,
|
|
22322
|
+
` <ac:parameter ac:name="attachment">${escapeXml(filename)}</ac:parameter>`,
|
|
22323
|
+
`</ac:structured-macro>`
|
|
22324
|
+
].join("\n");
|
|
22325
|
+
const current = await getPage(page_id, true);
|
|
22326
|
+
const newVersion = (current.version?.number ?? 0) + 1;
|
|
22327
|
+
const existingBody = current.body?.storage?.value ?? current.body?.value ?? "";
|
|
22328
|
+
const newBody = append ? `${existingBody}
|
|
22329
|
+
${macro}` : macro;
|
|
22330
|
+
const { page } = await updatePage(page_id, {
|
|
22331
|
+
body: newBody,
|
|
22332
|
+
versionMessage: `Added diagram: ${filename}`
|
|
22333
|
+
});
|
|
22334
|
+
return toolResult(
|
|
22335
|
+
`Diagram "${filename}" added to page ${page.title} (ID: ${page.id}, version: ${newVersion})` + echo
|
|
21848
22336
|
);
|
|
22337
|
+
} catch (err) {
|
|
22338
|
+
return toolError(err);
|
|
21849
22339
|
}
|
|
21850
|
-
|
|
21851
|
-
|
|
21852
|
-
|
|
21853
|
-
|
|
21854
|
-
|
|
21855
|
-
|
|
21856
|
-
|
|
21857
|
-
|
|
21858
|
-
|
|
21859
|
-
|
|
21860
|
-
|
|
21861
|
-
server.registerTool(
|
|
21862
|
-
"add_drawio_diagram",
|
|
21863
|
-
{
|
|
21864
|
-
description: "Add a draw.io diagram to a Confluence page. Uploads the diagram as an attachment and embeds it using the draw.io macro. Requires the draw.io app on the Confluence instance.",
|
|
21865
|
-
inputSchema: {
|
|
21866
|
-
page_id: external_exports.string().describe("The Confluence page ID to add the diagram to"),
|
|
21867
|
-
diagram_xml: external_exports.string().describe(
|
|
21868
|
-
"The draw.io diagram content in mxGraph XML format (the full XML starting with <mxfile>)"
|
|
21869
|
-
),
|
|
21870
|
-
diagram_name: external_exports.string().regex(
|
|
21871
|
-
/^[a-zA-Z0-9_\-. ]+$/,
|
|
21872
|
-
"Diagram name may only contain letters, numbers, spaces, hyphens, underscores, and dots"
|
|
21873
|
-
).describe(
|
|
21874
|
-
"Name for the diagram file (e.g., 'architecture.drawio'). Will have .drawio appended if not present."
|
|
21875
|
-
),
|
|
21876
|
-
append: external_exports.boolean().default(true).describe(
|
|
21877
|
-
"If true, appends the diagram to existing page content. If false, replaces the page body."
|
|
21878
|
-
)
|
|
22340
|
+
}
|
|
22341
|
+
);
|
|
22342
|
+
server.registerTool(
|
|
22343
|
+
"get_attachments",
|
|
22344
|
+
{
|
|
22345
|
+
description: "List attachments on a Confluence page",
|
|
22346
|
+
inputSchema: {
|
|
22347
|
+
page_id: external_exports.string().describe("The Confluence page ID"),
|
|
22348
|
+
limit: external_exports.number().default(25).describe("Maximum results (default: 25)")
|
|
22349
|
+
},
|
|
22350
|
+
annotations: { readOnlyHint: true }
|
|
21879
22351
|
},
|
|
21880
|
-
|
|
21881
|
-
},
|
|
21882
|
-
async ({ page_id, diagram_xml, diagram_name, append }) => {
|
|
21883
|
-
try {
|
|
21884
|
-
const filename = diagram_name.endsWith(".drawio") ? diagram_name : `${diagram_name}.drawio`;
|
|
21885
|
-
const tmpDir = await (0, import_promises.mkdtemp)((0, import_node_path.join)((0, import_node_os.tmpdir)(), "drawio-"));
|
|
22352
|
+
async ({ page_id, limit }) => {
|
|
21886
22353
|
try {
|
|
21887
|
-
const
|
|
21888
|
-
|
|
21889
|
-
|
|
21890
|
-
|
|
21891
|
-
|
|
21892
|
-
|
|
21893
|
-
|
|
21894
|
-
|
|
21895
|
-
|
|
21896
|
-
|
|
21897
|
-
|
|
21898
|
-
|
|
21899
|
-
|
|
21900
|
-
|
|
21901
|
-
|
|
21902
|
-
|
|
21903
|
-
const newBody = append ? `${existingBody}
|
|
21904
|
-
${macro}` : macro;
|
|
21905
|
-
const { page } = await updatePage(page_id, {
|
|
21906
|
-
body: newBody,
|
|
21907
|
-
versionMessage: `Added diagram: ${filename}`
|
|
21908
|
-
});
|
|
21909
|
-
return toolResult(
|
|
21910
|
-
`Diagram "${filename}" added to page ${page.title} (ID: ${page.id}, version: ${newVersion})`
|
|
21911
|
-
);
|
|
21912
|
-
} catch (err) {
|
|
21913
|
-
return toolError(err);
|
|
21914
|
-
}
|
|
21915
|
-
}
|
|
21916
|
-
);
|
|
21917
|
-
server.registerTool(
|
|
21918
|
-
"get_attachments",
|
|
21919
|
-
{
|
|
21920
|
-
description: "List attachments on a Confluence page",
|
|
21921
|
-
inputSchema: {
|
|
21922
|
-
page_id: external_exports.string().describe("The Confluence page ID"),
|
|
21923
|
-
limit: external_exports.number().default(25).describe("Maximum results (default: 25)")
|
|
21924
|
-
},
|
|
21925
|
-
annotations: { readOnlyHint: true }
|
|
21926
|
-
},
|
|
21927
|
-
async ({ page_id, limit }) => {
|
|
21928
|
-
try {
|
|
21929
|
-
const attachments = await getAttachments(page_id, limit);
|
|
21930
|
-
if (attachments.length === 0) {
|
|
21931
|
-
return toolResult(`No attachments found on page ${page_id}.`);
|
|
21932
|
-
}
|
|
21933
|
-
const lines = [
|
|
21934
|
-
`Attachments on page ${page_id} (${attachments.length}):`,
|
|
21935
|
-
""
|
|
21936
|
-
];
|
|
21937
|
-
for (const a of attachments) {
|
|
21938
|
-
const size = a.extensions?.fileSize ? `${Math.round(a.extensions.fileSize / 1024)}KB` : "unknown size";
|
|
21939
|
-
const mediaType = a.extensions?.mediaType ?? "unknown type";
|
|
21940
|
-
lines.push(`- ${a.title} (ID: ${a.id}, ${mediaType}, ${size})`);
|
|
22354
|
+
const attachments = await getAttachments(page_id, limit);
|
|
22355
|
+
if (attachments.length === 0) {
|
|
22356
|
+
return toolResult(`No attachments found on page ${page_id}.`);
|
|
22357
|
+
}
|
|
22358
|
+
const lines = [
|
|
22359
|
+
`Attachments on page ${page_id} (${attachments.length}):`,
|
|
22360
|
+
""
|
|
22361
|
+
];
|
|
22362
|
+
for (const a of attachments) {
|
|
22363
|
+
const size = a.extensions?.fileSize ? `${Math.round(a.extensions.fileSize / 1024)}KB` : "unknown size";
|
|
22364
|
+
const mediaType = a.extensions?.mediaType ?? "unknown type";
|
|
22365
|
+
lines.push(`- ${a.title} (ID: ${a.id}, ${mediaType}, ${size})`);
|
|
22366
|
+
}
|
|
22367
|
+
return toolResult(lines.join("\n"));
|
|
22368
|
+
} catch (err) {
|
|
22369
|
+
return toolError(err);
|
|
21941
22370
|
}
|
|
21942
|
-
return toolResult(lines.join("\n"));
|
|
21943
|
-
} catch (err) {
|
|
21944
|
-
return toolError(err);
|
|
21945
22371
|
}
|
|
21946
|
-
|
|
21947
|
-
|
|
22372
|
+
);
|
|
22373
|
+
}
|
|
21948
22374
|
async function main() {
|
|
22375
|
+
const config2 = await getConfig();
|
|
22376
|
+
await validateStartup(config2);
|
|
22377
|
+
const serverName = config2.profile ? `confluence-${config2.profile}` : "confluence";
|
|
22378
|
+
const server = new McpServer({
|
|
22379
|
+
name: serverName,
|
|
22380
|
+
version: "1.0.0"
|
|
22381
|
+
});
|
|
22382
|
+
registerTools(server, config2);
|
|
21949
22383
|
const transport = new StdioServerTransport();
|
|
21950
22384
|
await server.connect(transport);
|
|
21951
22385
|
}
|
|
@@ -21954,8 +22388,16 @@ async function main() {
|
|
|
21954
22388
|
async function run() {
|
|
21955
22389
|
const command = process.argv[2];
|
|
21956
22390
|
if (command === "setup") {
|
|
22391
|
+
const idx = process.argv.indexOf("--profile");
|
|
22392
|
+
const profile = idx > -1 ? process.argv[idx + 1] : void 0;
|
|
21957
22393
|
const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
|
|
21958
|
-
await runSetup2();
|
|
22394
|
+
await runSetup2(profile);
|
|
22395
|
+
} else if (command === "profiles") {
|
|
22396
|
+
const { runProfiles: runProfiles2 } = await Promise.resolve().then(() => (init_profiles2(), profiles_exports));
|
|
22397
|
+
await runProfiles2();
|
|
22398
|
+
} else if (command === "status") {
|
|
22399
|
+
const { runStatus: runStatus2 } = await Promise.resolve().then(() => (init_status(), status_exports));
|
|
22400
|
+
await runStatus2();
|
|
21959
22401
|
} else {
|
|
21960
22402
|
await main();
|
|
21961
22403
|
}
|