@pleri/olam-cli 0.1.48 → 0.1.50
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/__tests__/mcp-import.test.d.ts +11 -0
- package/dist/__tests__/mcp-import.test.d.ts.map +1 -0
- package/dist/__tests__/mcp-import.test.js +134 -0
- package/dist/__tests__/mcp-import.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.compose-path.test.d.ts +20 -0
- package/dist/commands/__tests__/upgrade.compose-path.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.compose-path.test.js +152 -0
- package/dist/commands/__tests__/upgrade.compose-path.test.js.map +1 -0
- package/dist/commands/host-cp.d.ts +6 -0
- package/dist/commands/host-cp.d.ts.map +1 -1
- package/dist/commands/host-cp.js +1 -1
- package/dist/commands/host-cp.js.map +1 -1
- package/dist/commands/mcp/add.d.ts +9 -0
- package/dist/commands/mcp/add.d.ts.map +1 -0
- package/dist/commands/mcp/add.js +87 -0
- package/dist/commands/mcp/add.js.map +1 -0
- package/dist/commands/mcp/client.d.ts +60 -0
- package/dist/commands/mcp/client.d.ts.map +1 -0
- package/dist/commands/mcp/client.js +70 -0
- package/dist/commands/mcp/client.js.map +1 -0
- package/dist/commands/mcp/import-discovery.d.ts +25 -0
- package/dist/commands/mcp/import-discovery.d.ts.map +1 -0
- package/dist/commands/mcp/import-discovery.js +135 -0
- package/dist/commands/mcp/import-discovery.js.map +1 -0
- package/dist/commands/mcp/import-validate.d.ts +15 -0
- package/dist/commands/mcp/import-validate.d.ts.map +1 -0
- package/dist/commands/mcp/import-validate.js +55 -0
- package/dist/commands/mcp/import-validate.js.map +1 -0
- package/dist/commands/mcp/import.d.ts +12 -0
- package/dist/commands/mcp/import.d.ts.map +1 -0
- package/dist/commands/mcp/import.js +126 -0
- package/dist/commands/mcp/import.js.map +1 -0
- package/dist/commands/mcp/index.d.ts +11 -0
- package/dist/commands/mcp/index.d.ts.map +1 -0
- package/dist/commands/mcp/index.js +26 -0
- package/dist/commands/mcp/index.js.map +1 -0
- package/dist/commands/mcp/list.d.ts +6 -0
- package/dist/commands/mcp/list.d.ts.map +1 -0
- package/dist/commands/mcp/list.js +56 -0
- package/dist/commands/mcp/list.js.map +1 -0
- package/dist/commands/mcp/login.d.ts +6 -0
- package/dist/commands/mcp/login.d.ts.map +1 -0
- package/dist/commands/mcp/login.js +38 -0
- package/dist/commands/mcp/login.js.map +1 -0
- package/dist/commands/mcp/remove.d.ts +6 -0
- package/dist/commands/mcp/remove.d.ts.map +1 -0
- package/dist/commands/mcp/remove.js +21 -0
- package/dist/commands/mcp/remove.js.map +1 -0
- package/dist/commands/mcp/status.d.ts +6 -0
- package/dist/commands/mcp/status.d.ts.map +1 -0
- package/dist/commands/mcp/status.js +57 -0
- package/dist/commands/mcp/status.js.map +1 -0
- package/dist/commands/upgrade.d.ts +3 -3
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +4 -5
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/image-digests.json +2 -2
- package/dist/index.js +606 -61
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -454,8 +454,8 @@ var init_parseUtil = __esm({
|
|
|
454
454
|
init_errors();
|
|
455
455
|
init_en();
|
|
456
456
|
makeIssue = (params) => {
|
|
457
|
-
const { data, path:
|
|
458
|
-
const fullPath = [...
|
|
457
|
+
const { data, path: path44, errorMaps, issueData } = params;
|
|
458
|
+
const fullPath = [...path44, ...issueData.path || []];
|
|
459
459
|
const fullIssue = {
|
|
460
460
|
...issueData,
|
|
461
461
|
path: fullPath
|
|
@@ -763,11 +763,11 @@ var init_types = __esm({
|
|
|
763
763
|
init_parseUtil();
|
|
764
764
|
init_util();
|
|
765
765
|
ParseInputLazyPath = class {
|
|
766
|
-
constructor(parent, value,
|
|
766
|
+
constructor(parent, value, path44, key) {
|
|
767
767
|
this._cachedPath = [];
|
|
768
768
|
this.parent = parent;
|
|
769
769
|
this.data = value;
|
|
770
|
-
this._path =
|
|
770
|
+
this._path = path44;
|
|
771
771
|
this._key = key;
|
|
772
772
|
}
|
|
773
773
|
get path() {
|
|
@@ -4248,7 +4248,7 @@ import YAML from "yaml";
|
|
|
4248
4248
|
function bootstrapStepCmd(entry) {
|
|
4249
4249
|
return typeof entry === "string" ? entry : entry.cmd;
|
|
4250
4250
|
}
|
|
4251
|
-
function refineForbiddenKeys(value,
|
|
4251
|
+
function refineForbiddenKeys(value, path44, ctx, rejectSource) {
|
|
4252
4252
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
4253
4253
|
return;
|
|
4254
4254
|
}
|
|
@@ -4256,12 +4256,12 @@ function refineForbiddenKeys(value, path43, ctx, rejectSource) {
|
|
|
4256
4256
|
if (FORBIDDEN_KEYS.has(key)) {
|
|
4257
4257
|
ctx.addIssue({
|
|
4258
4258
|
code: external_exports.ZodIssueCode.custom,
|
|
4259
|
-
path: [...
|
|
4259
|
+
path: [...path44, key],
|
|
4260
4260
|
message: `forbidden key "${key}" (prototype-pollution surface)`
|
|
4261
4261
|
});
|
|
4262
4262
|
continue;
|
|
4263
4263
|
}
|
|
4264
|
-
if (rejectSource &&
|
|
4264
|
+
if (rejectSource && path44.length === 0 && key === "source") {
|
|
4265
4265
|
ctx.addIssue({
|
|
4266
4266
|
code: external_exports.ZodIssueCode.custom,
|
|
4267
4267
|
path: ["source"],
|
|
@@ -4269,21 +4269,21 @@ function refineForbiddenKeys(value, path43, ctx, rejectSource) {
|
|
|
4269
4269
|
});
|
|
4270
4270
|
continue;
|
|
4271
4271
|
}
|
|
4272
|
-
refineForbiddenKeys(value[key], [...
|
|
4272
|
+
refineForbiddenKeys(value[key], [...path44, key], ctx, false);
|
|
4273
4273
|
}
|
|
4274
4274
|
}
|
|
4275
|
-
function rejectForbiddenKeys(value,
|
|
4275
|
+
function rejectForbiddenKeys(value, path44, rejectSource) {
|
|
4276
4276
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
4277
4277
|
return;
|
|
4278
4278
|
}
|
|
4279
4279
|
for (const key of Object.keys(value)) {
|
|
4280
4280
|
if (FORBIDDEN_KEYS.has(key)) {
|
|
4281
|
-
throw new Error(`[manifest] ${
|
|
4281
|
+
throw new Error(`[manifest] ${path44}: forbidden key "${key}" (prototype-pollution surface)`);
|
|
4282
4282
|
}
|
|
4283
4283
|
if (rejectSource && key === "source") {
|
|
4284
|
-
throw new Error(`[manifest] ${
|
|
4284
|
+
throw new Error(`[manifest] ${path44}: top-level "source" is loader-stamped \u2014 manifests must not author it`);
|
|
4285
4285
|
}
|
|
4286
|
-
rejectForbiddenKeys(value[key], `${
|
|
4286
|
+
rejectForbiddenKeys(value[key], `${path44}.${key}`, false);
|
|
4287
4287
|
}
|
|
4288
4288
|
}
|
|
4289
4289
|
function unknownTopLevelKeys(parsed) {
|
|
@@ -5224,8 +5224,8 @@ var init_client = __esm({
|
|
|
5224
5224
|
throw new Error(`failed to report rate-limit for ${accountId} (HTTP ${res.status})`);
|
|
5225
5225
|
}
|
|
5226
5226
|
}
|
|
5227
|
-
async request(method,
|
|
5228
|
-
const url = `${this.baseUrl}${
|
|
5227
|
+
async request(method, path44, body, attempt = 0) {
|
|
5228
|
+
const url = `${this.baseUrl}${path44}`;
|
|
5229
5229
|
const controller = new AbortController();
|
|
5230
5230
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
5231
5231
|
const headers = {};
|
|
@@ -5243,7 +5243,7 @@ var init_client = __esm({
|
|
|
5243
5243
|
} catch (err) {
|
|
5244
5244
|
if (attempt < RETRY_COUNT && isTransient(err)) {
|
|
5245
5245
|
await sleep(RETRY_BACKOFF_MS * (attempt + 1));
|
|
5246
|
-
return this.request(method,
|
|
5246
|
+
return this.request(method, path44, body, attempt + 1);
|
|
5247
5247
|
}
|
|
5248
5248
|
throw err;
|
|
5249
5249
|
} finally {
|
|
@@ -6722,8 +6722,8 @@ var init_provider3 = __esm({
|
|
|
6722
6722
|
// -----------------------------------------------------------------------
|
|
6723
6723
|
// Internal fetch helper
|
|
6724
6724
|
// -----------------------------------------------------------------------
|
|
6725
|
-
async request(
|
|
6726
|
-
const url = `${this.config.workerUrl}${
|
|
6725
|
+
async request(path44, method, body) {
|
|
6726
|
+
const url = `${this.config.workerUrl}${path44}`;
|
|
6727
6727
|
const bearer = await this.config.mintToken();
|
|
6728
6728
|
const headers = {
|
|
6729
6729
|
Authorization: `Bearer ${bearer}`
|
|
@@ -7983,8 +7983,8 @@ import { execFileSync as execFileSync3 } from "node:child_process";
|
|
|
7983
7983
|
import * as fs13 from "node:fs";
|
|
7984
7984
|
import * as os9 from "node:os";
|
|
7985
7985
|
import * as path14 from "node:path";
|
|
7986
|
-
function expandHome(p,
|
|
7987
|
-
return p.replace(/^~(?=$|\/|\\)/,
|
|
7986
|
+
function expandHome(p, homedir22) {
|
|
7987
|
+
return p.replace(/^~(?=$|\/|\\)/, homedir22());
|
|
7988
7988
|
}
|
|
7989
7989
|
function sanitizeRepoFilename(name) {
|
|
7990
7990
|
const sanitized = name.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
@@ -8007,7 +8007,7 @@ ${stderr}`;
|
|
|
8007
8007
|
}
|
|
8008
8008
|
function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
|
|
8009
8009
|
const exec = deps.exec ?? ((cmd, args, opts) => execFileSync3(cmd, args, opts));
|
|
8010
|
-
const
|
|
8010
|
+
const homedir22 = deps.homedir ?? (() => os9.homedir());
|
|
8011
8011
|
const baselineDir = path14.join(workspacePath, ".olam", "baseline");
|
|
8012
8012
|
try {
|
|
8013
8013
|
fs13.mkdirSync(baselineDir, { recursive: true });
|
|
@@ -8023,7 +8023,7 @@ function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
|
|
|
8023
8023
|
continue;
|
|
8024
8024
|
const filename = `${sanitizeRepoFilename(repo.name)}.diff`;
|
|
8025
8025
|
const outPath = path14.join(baselineDir, filename);
|
|
8026
|
-
const repoPath = expandHome(repo.path,
|
|
8026
|
+
const repoPath = expandHome(repo.path, homedir22);
|
|
8027
8027
|
if (!fs13.existsSync(repoPath)) {
|
|
8028
8028
|
writeBaselineFile(outPath, `# repo: ${repo.name}
|
|
8029
8029
|
# (skipped: path ${repoPath} does not exist)
|
|
@@ -11938,6 +11938,7 @@ __export(host_cp_exports, {
|
|
|
11938
11938
|
callHostCpProxy: () => callHostCpProxy,
|
|
11939
11939
|
callHostCpRegistry: () => callHostCpRegistry,
|
|
11940
11940
|
captureGhToken: () => captureGhToken,
|
|
11941
|
+
findComposeFile: () => findComposeFile,
|
|
11941
11942
|
findHostCpContainer: () => findHostCpContainer,
|
|
11942
11943
|
gatherProbeFailureDiagnostics: () => gatherProbeFailureDiagnostics,
|
|
11943
11944
|
openHostCpUrl: () => openUrl,
|
|
@@ -12419,10 +12420,10 @@ async function readHostCpToken2() {
|
|
|
12419
12420
|
if (!fs19.existsSync(tp)) return null;
|
|
12420
12421
|
return fs19.readFileSync(tp, "utf-8").trim();
|
|
12421
12422
|
}
|
|
12422
|
-
async function callHostCpProxy(method, worldId,
|
|
12423
|
+
async function callHostCpProxy(method, worldId, path44, body) {
|
|
12423
12424
|
const token = await readHostCpToken2();
|
|
12424
12425
|
if (!token) return { ok: false, status: 0, error: "no token (host CP not started)" };
|
|
12425
|
-
const url = `http://127.0.0.1:${HOST_CP_PORT}/api/world/${encodeURIComponent(worldId)}${
|
|
12426
|
+
const url = `http://127.0.0.1:${HOST_CP_PORT}/api/world/${encodeURIComponent(worldId)}${path44}`;
|
|
12426
12427
|
try {
|
|
12427
12428
|
const headers = {
|
|
12428
12429
|
Authorization: `Bearer ${token}`
|
|
@@ -12930,8 +12931,8 @@ var init_machine_schema = __esm({
|
|
|
12930
12931
|
|
|
12931
12932
|
// src/index.ts
|
|
12932
12933
|
import { Command } from "commander";
|
|
12933
|
-
import * as
|
|
12934
|
-
import * as
|
|
12934
|
+
import * as fs39 from "node:fs";
|
|
12935
|
+
import * as path43 from "node:path";
|
|
12935
12936
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
12936
12937
|
|
|
12937
12938
|
// src/commands/init.ts
|
|
@@ -13308,9 +13309,9 @@ var UnknownArchetypeError = class extends Error {
|
|
|
13308
13309
|
};
|
|
13309
13310
|
var ArchetypeCycleError = class extends Error {
|
|
13310
13311
|
path;
|
|
13311
|
-
constructor(
|
|
13312
|
-
super(`Archetype inheritance cycle detected: ${
|
|
13313
|
-
this.path =
|
|
13312
|
+
constructor(path44) {
|
|
13313
|
+
super(`Archetype inheritance cycle detected: ${path44.join(" \u2192 ")} \u2192 ${path44[0] ?? "?"}`);
|
|
13314
|
+
this.path = path44;
|
|
13314
13315
|
this.name = "ArchetypeCycleError";
|
|
13315
13316
|
}
|
|
13316
13317
|
};
|
|
@@ -14082,8 +14083,8 @@ function runStep(label, cmd, args, opts = {}) {
|
|
|
14082
14083
|
}
|
|
14083
14084
|
async function confirm(message) {
|
|
14084
14085
|
if (!process.stdin.isTTY) return true;
|
|
14085
|
-
const { createInterface:
|
|
14086
|
-
const rl =
|
|
14086
|
+
const { createInterface: createInterface4 } = await import("node:readline");
|
|
14087
|
+
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
14087
14088
|
return new Promise((resolve8) => {
|
|
14088
14089
|
rl.question(`${message} [y/N] `, (answer) => {
|
|
14089
14090
|
rl.close();
|
|
@@ -14653,9 +14654,9 @@ function formatFreshnessWarning(result, image = DEFAULT_DEVBOX_IMAGE) {
|
|
|
14653
14654
|
"These source files have changed since the image was built; the",
|
|
14654
14655
|
"changes will NOT take effect in fresh worlds until you rebuild:"
|
|
14655
14656
|
];
|
|
14656
|
-
for (const { path:
|
|
14657
|
+
for (const { path: path44, mtimeMs } of result.newerSources) {
|
|
14657
14658
|
const when = new Date(mtimeMs).toISOString();
|
|
14658
|
-
lines.push(` \u2022 ${
|
|
14659
|
+
lines.push(` \u2022 ${path44} (modified ${when})`);
|
|
14659
14660
|
}
|
|
14660
14661
|
lines.push("");
|
|
14661
14662
|
lines.push("Rebuild with:");
|
|
@@ -14814,15 +14815,15 @@ init_host_cp();
|
|
|
14814
14815
|
var HOST_CP_URL = "http://127.0.0.1:19000";
|
|
14815
14816
|
async function readHostCpTokenForCreate() {
|
|
14816
14817
|
try {
|
|
14817
|
-
const { default:
|
|
14818
|
-
const { default:
|
|
14819
|
-
const { default:
|
|
14820
|
-
const tp =
|
|
14821
|
-
process.env.OLAM_HOME ??
|
|
14818
|
+
const { default: fs40 } = await import("node:fs");
|
|
14819
|
+
const { default: os24 } = await import("node:os");
|
|
14820
|
+
const { default: path44 } = await import("node:path");
|
|
14821
|
+
const tp = path44.join(
|
|
14822
|
+
process.env.OLAM_HOME ?? path44.join(os24.homedir(), ".olam"),
|
|
14822
14823
|
"host-cp.token"
|
|
14823
14824
|
);
|
|
14824
|
-
if (!
|
|
14825
|
-
return
|
|
14825
|
+
if (!fs40.existsSync(tp)) return null;
|
|
14826
|
+
return fs40.readFileSync(tp, "utf-8").trim();
|
|
14826
14827
|
} catch {
|
|
14827
14828
|
return null;
|
|
14828
14829
|
}
|
|
@@ -15184,12 +15185,12 @@ function defaultNameFromPrompt(prompt) {
|
|
|
15184
15185
|
}
|
|
15185
15186
|
async function readHostCpToken3() {
|
|
15186
15187
|
try {
|
|
15187
|
-
const { default:
|
|
15188
|
-
const { default:
|
|
15189
|
-
const { default:
|
|
15190
|
-
const tp =
|
|
15191
|
-
if (!
|
|
15192
|
-
const raw =
|
|
15188
|
+
const { default: fs40 } = await import("node:fs");
|
|
15189
|
+
const { default: os24 } = await import("node:os");
|
|
15190
|
+
const { default: path44 } = await import("node:path");
|
|
15191
|
+
const tp = path44.join(os24.homedir(), ".olam", "host-cp.token");
|
|
15192
|
+
if (!fs40.existsSync(tp)) return null;
|
|
15193
|
+
const raw = fs40.readFileSync(tp, "utf-8").trim();
|
|
15193
15194
|
return raw.length > 0 ? raw : null;
|
|
15194
15195
|
} catch {
|
|
15195
15196
|
return null;
|
|
@@ -18780,8 +18781,8 @@ function performRollbackSwap(plan) {
|
|
|
18780
18781
|
}
|
|
18781
18782
|
async function confirm2(message) {
|
|
18782
18783
|
if (!process.stdin.isTTY) return true;
|
|
18783
|
-
const { createInterface:
|
|
18784
|
-
const rl =
|
|
18784
|
+
const { createInterface: createInterface4 } = await import("node:readline");
|
|
18785
|
+
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
18785
18786
|
return new Promise((resolve8) => {
|
|
18786
18787
|
rl.question(`${message} [y/N] `, (answer) => {
|
|
18787
18788
|
rl.close();
|
|
@@ -18982,7 +18983,7 @@ async function runUpgradePullByDigest(deps = {}) {
|
|
|
18982
18983
|
}
|
|
18983
18984
|
}
|
|
18984
18985
|
tagSpinner.succeed("Re-tagged 3 images to canonical refs");
|
|
18985
|
-
const composeFile = deps.composeFile ??
|
|
18986
|
+
const composeFile = deps.composeFile ?? findComposeFile();
|
|
18986
18987
|
const authSecret = deps.authSecret ?? readAuthSecret2();
|
|
18987
18988
|
const composeRunner = deps.runComposeImpl ?? runCompose;
|
|
18988
18989
|
const composeSpinner = ora7("docker compose recreate host-cp").start();
|
|
@@ -19182,8 +19183,7 @@ manually inspect images with \`docker images olam-*:olam-rollback\`.`
|
|
|
19182
19183
|
return;
|
|
19183
19184
|
}
|
|
19184
19185
|
printInfo("Rollback", swapResult.summary);
|
|
19185
|
-
const
|
|
19186
|
-
const composeFile = path31.join(cwd, "packages/host-cp/compose.yaml");
|
|
19186
|
+
const composeFile = findComposeFile();
|
|
19187
19187
|
const authSecret = readAuthSecret2();
|
|
19188
19188
|
process.stdout.write(` ${pc16.dim("docker compose recreate host-cp".padEnd(34))}`);
|
|
19189
19189
|
const composeStart = Date.now();
|
|
@@ -19457,7 +19457,7 @@ Recovery options:
|
|
|
19457
19457
|
return;
|
|
19458
19458
|
}
|
|
19459
19459
|
printInfo("Swap", swapResult.summary);
|
|
19460
|
-
const composeFile =
|
|
19460
|
+
const composeFile = findComposeFile();
|
|
19461
19461
|
process.stdout.write(` ${pc16.dim("docker compose recreate".padEnd(34))}`);
|
|
19462
19462
|
const composeStart = Date.now();
|
|
19463
19463
|
const composeResult = runCompose(
|
|
@@ -21137,19 +21137,563 @@ function registerWorldUpgrade(program2) {
|
|
|
21137
21137
|
});
|
|
21138
21138
|
}
|
|
21139
21139
|
|
|
21140
|
-
// src/
|
|
21140
|
+
// src/commands/mcp/login.ts
|
|
21141
|
+
init_output();
|
|
21142
|
+
|
|
21143
|
+
// src/commands/mcp/client.ts
|
|
21144
|
+
var BASE_URL = process.env["OLAM_MCP_AUTH_SERVICE_URL"] ?? "http://127.0.0.1:9998";
|
|
21145
|
+
var SECRET = process.env["OLAM_MCP_AUTH_SECRET"] ?? "";
|
|
21146
|
+
function authHeaders() {
|
|
21147
|
+
return SECRET ? { "X-Olam-Mcp-Secret": SECRET } : {};
|
|
21148
|
+
}
|
|
21149
|
+
async function apiFetch(path44, init = {}) {
|
|
21150
|
+
const res = await fetch(`${BASE_URL}${path44}`, {
|
|
21151
|
+
...init,
|
|
21152
|
+
headers: {
|
|
21153
|
+
"Content-Type": "application/json",
|
|
21154
|
+
...authHeaders(),
|
|
21155
|
+
...init.headers
|
|
21156
|
+
}
|
|
21157
|
+
});
|
|
21158
|
+
if (!res.ok) {
|
|
21159
|
+
let msg = `HTTP ${res.status}`;
|
|
21160
|
+
try {
|
|
21161
|
+
const body = await res.json();
|
|
21162
|
+
if (body.error) msg = body.error;
|
|
21163
|
+
} catch {
|
|
21164
|
+
}
|
|
21165
|
+
return { ok: false, error: msg };
|
|
21166
|
+
}
|
|
21167
|
+
return res.json();
|
|
21168
|
+
}
|
|
21169
|
+
function getMcpAuthClient() {
|
|
21170
|
+
return {
|
|
21171
|
+
async status() {
|
|
21172
|
+
const res = await apiFetch("/credentials/status");
|
|
21173
|
+
return res;
|
|
21174
|
+
},
|
|
21175
|
+
async getCredential(service) {
|
|
21176
|
+
return apiFetch(`/credentials/${encodeURIComponent(service)}`);
|
|
21177
|
+
},
|
|
21178
|
+
async staticAdd({ service, token, label, envName }) {
|
|
21179
|
+
return apiFetch("/credentials/static/add", {
|
|
21180
|
+
method: "POST",
|
|
21181
|
+
body: JSON.stringify({ service, token, label, envName })
|
|
21182
|
+
});
|
|
21183
|
+
},
|
|
21184
|
+
async oauthStart({ service, label }) {
|
|
21185
|
+
return apiFetch("/credentials/oauth/start", {
|
|
21186
|
+
method: "POST",
|
|
21187
|
+
body: JSON.stringify({ service, label })
|
|
21188
|
+
});
|
|
21189
|
+
},
|
|
21190
|
+
async oauthComplete({ state, accessToken, refreshToken }) {
|
|
21191
|
+
return apiFetch("/credentials/oauth/complete", {
|
|
21192
|
+
method: "POST",
|
|
21193
|
+
body: JSON.stringify({ state, accessToken, refreshToken })
|
|
21194
|
+
});
|
|
21195
|
+
},
|
|
21196
|
+
async remove(id) {
|
|
21197
|
+
return apiFetch(`/credentials/${encodeURIComponent(id)}`, {
|
|
21198
|
+
method: "DELETE"
|
|
21199
|
+
});
|
|
21200
|
+
}
|
|
21201
|
+
};
|
|
21202
|
+
}
|
|
21203
|
+
|
|
21204
|
+
// src/commands/mcp/login.ts
|
|
21205
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
21206
|
+
function openBrowser2(url) {
|
|
21207
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
21208
|
+
try {
|
|
21209
|
+
const child = spawn4(cmd, [url], { detached: true, stdio: "ignore" });
|
|
21210
|
+
child.unref();
|
|
21211
|
+
} catch {
|
|
21212
|
+
}
|
|
21213
|
+
}
|
|
21214
|
+
function registerMcpLogin(cmd) {
|
|
21215
|
+
cmd.command("login <service>").description("Start OAuth login flow for an MCP service (linear, slack)").option("--label <label>", "Label for this credential").action(async (service, opts) => {
|
|
21216
|
+
const client = getMcpAuthClient();
|
|
21217
|
+
const result = await client.oauthStart({ service, label: opts.label });
|
|
21218
|
+
if (!result.ok && result.error) {
|
|
21219
|
+
printError(result.error);
|
|
21220
|
+
process.exitCode = 1;
|
|
21221
|
+
return;
|
|
21222
|
+
}
|
|
21223
|
+
if (result.loginUrl) {
|
|
21224
|
+
printInfo("Login URL", result.loginUrl);
|
|
21225
|
+
openBrowser2(result.loginUrl);
|
|
21226
|
+
printInfo("State", result.state ?? "");
|
|
21227
|
+
printSuccess(`OAuth flow started for ${service}. Complete in your browser, then run:`);
|
|
21228
|
+
console.log(` olam mcp complete ${service} --state ${result.state ?? "<state>"} --token <access_token>`);
|
|
21229
|
+
}
|
|
21230
|
+
});
|
|
21231
|
+
}
|
|
21232
|
+
|
|
21233
|
+
// src/commands/mcp/add.ts
|
|
21234
|
+
init_output();
|
|
21235
|
+
import * as readline2 from "node:readline";
|
|
21236
|
+
async function readTokenSilent(prompt) {
|
|
21237
|
+
return new Promise((resolve8, reject) => {
|
|
21238
|
+
const rl = readline2.createInterface({
|
|
21239
|
+
input: process.stdin,
|
|
21240
|
+
output: process.stdout,
|
|
21241
|
+
terminal: true
|
|
21242
|
+
});
|
|
21243
|
+
process.stdout.write(prompt);
|
|
21244
|
+
if (process.stdin.isTTY) {
|
|
21245
|
+
process.stdin.setRawMode(true);
|
|
21246
|
+
}
|
|
21247
|
+
let token = "";
|
|
21248
|
+
process.stdin.on("data", function onData(chunk) {
|
|
21249
|
+
const char = chunk.toString("utf8");
|
|
21250
|
+
if (char === "\r" || char === "\n") {
|
|
21251
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
21252
|
+
process.stdin.removeListener("data", onData);
|
|
21253
|
+
process.stdout.write("\n");
|
|
21254
|
+
rl.close();
|
|
21255
|
+
resolve8(token);
|
|
21256
|
+
} else if (char === "") {
|
|
21257
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
21258
|
+
process.stdin.removeListener("data", onData);
|
|
21259
|
+
rl.close();
|
|
21260
|
+
reject(new Error("Cancelled"));
|
|
21261
|
+
} else if (char === "\x7F") {
|
|
21262
|
+
token = token.slice(0, -1);
|
|
21263
|
+
} else {
|
|
21264
|
+
token += char;
|
|
21265
|
+
}
|
|
21266
|
+
});
|
|
21267
|
+
});
|
|
21268
|
+
}
|
|
21269
|
+
function registerMcpAdd(cmd) {
|
|
21270
|
+
cmd.command("add <service>").description("Add a static-key MCP credential (token input hidden from terminal)").option("--token <token>", "Token value (use stdin prompt if omitted)").option("--label <label>", "Human-friendly label").option("--env-name <envName>", "Environment variable name to inject into worlds").action(async (service, opts) => {
|
|
21271
|
+
let token = opts.token;
|
|
21272
|
+
if (!token) {
|
|
21273
|
+
try {
|
|
21274
|
+
token = await readTokenSilent(`Enter token for ${service} (hidden): `);
|
|
21275
|
+
} catch (err) {
|
|
21276
|
+
printError(err instanceof Error ? err.message : "Cancelled");
|
|
21277
|
+
process.exitCode = 1;
|
|
21278
|
+
return;
|
|
21279
|
+
}
|
|
21280
|
+
}
|
|
21281
|
+
if (!token) {
|
|
21282
|
+
printError("Token is required");
|
|
21283
|
+
process.exitCode = 1;
|
|
21284
|
+
return;
|
|
21285
|
+
}
|
|
21286
|
+
const client = getMcpAuthClient();
|
|
21287
|
+
const result = await client.staticAdd({ service, token, label: opts.label, envName: opts.envName });
|
|
21288
|
+
if (!result.ok) {
|
|
21289
|
+
printError(result.error ?? "Failed to add credential");
|
|
21290
|
+
process.exitCode = 1;
|
|
21291
|
+
return;
|
|
21292
|
+
}
|
|
21293
|
+
printSuccess(`Added ${service} credential`);
|
|
21294
|
+
if (opts.envName) {
|
|
21295
|
+
printInfo("Env var", opts.envName);
|
|
21296
|
+
}
|
|
21297
|
+
});
|
|
21298
|
+
}
|
|
21299
|
+
|
|
21300
|
+
// src/commands/mcp/list.ts
|
|
21301
|
+
init_output();
|
|
21302
|
+
import pc23 from "picocolors";
|
|
21303
|
+
function registerMcpList(cmd) {
|
|
21304
|
+
cmd.command("list").description("List all registered MCP credentials").action(async () => {
|
|
21305
|
+
const client = getMcpAuthClient();
|
|
21306
|
+
let data;
|
|
21307
|
+
try {
|
|
21308
|
+
data = await client.status();
|
|
21309
|
+
} catch (err) {
|
|
21310
|
+
printError(`Could not reach mcp-auth service: ${err instanceof Error ? err.message : "unknown error"}`);
|
|
21311
|
+
printError("Is mcp-auth running? (OLAM_MCP_AUTH_SERVICE_URL defaults to http://127.0.0.1:9998)");
|
|
21312
|
+
process.exitCode = 1;
|
|
21313
|
+
return;
|
|
21314
|
+
}
|
|
21315
|
+
const mcps = data.mcps ?? [];
|
|
21316
|
+
if (mcps.length === 0) {
|
|
21317
|
+
console.log(pc23.dim("No MCP credentials registered."));
|
|
21318
|
+
console.log(pc23.dim("Add one with: olam mcp add <service>"));
|
|
21319
|
+
return;
|
|
21320
|
+
}
|
|
21321
|
+
const [c0, c1, c2, c3] = [16, 20, 10, 24];
|
|
21322
|
+
const header = [
|
|
21323
|
+
"SERVICE".padEnd(c0),
|
|
21324
|
+
"LABEL".padEnd(c1),
|
|
21325
|
+
"TYPE".padEnd(c2),
|
|
21326
|
+
"ENV VAR".padEnd(c3),
|
|
21327
|
+
"STATUS"
|
|
21328
|
+
].join(" ");
|
|
21329
|
+
console.log(pc23.dim(header));
|
|
21330
|
+
console.log(pc23.dim("\u2500".repeat(header.length)));
|
|
21331
|
+
for (const m of mcps) {
|
|
21332
|
+
const status = !m.validated ? pc23.yellow("unvalidated") : m.expired ? pc23.red("expired") : pc23.green("active");
|
|
21333
|
+
const row = [
|
|
21334
|
+
pc23.cyan(m.service.padEnd(c0)),
|
|
21335
|
+
m.label.padEnd(c1).slice(0, c1),
|
|
21336
|
+
m.type.padEnd(c2),
|
|
21337
|
+
(m.envName ?? "\u2014").padEnd(c3).slice(0, c3),
|
|
21338
|
+
status
|
|
21339
|
+
].join(" ");
|
|
21340
|
+
console.log(row);
|
|
21341
|
+
}
|
|
21342
|
+
});
|
|
21343
|
+
}
|
|
21344
|
+
|
|
21345
|
+
// src/commands/mcp/remove.ts
|
|
21346
|
+
init_output();
|
|
21347
|
+
function registerMcpRemove(cmd) {
|
|
21348
|
+
cmd.command("remove <service>").description("Remove an MCP credential by service ID").action(async (service) => {
|
|
21349
|
+
const client = getMcpAuthClient();
|
|
21350
|
+
const result = await client.remove(service);
|
|
21351
|
+
if (!result.ok) {
|
|
21352
|
+
printError(result.error ?? `No credential found for: ${service}`);
|
|
21353
|
+
process.exitCode = 1;
|
|
21354
|
+
return;
|
|
21355
|
+
}
|
|
21356
|
+
printSuccess(`Removed credential for ${service}`);
|
|
21357
|
+
});
|
|
21358
|
+
}
|
|
21359
|
+
|
|
21360
|
+
// src/commands/mcp/status.ts
|
|
21361
|
+
init_output();
|
|
21362
|
+
import pc24 from "picocolors";
|
|
21363
|
+
function formatExpiry(expiresAt) {
|
|
21364
|
+
if (!expiresAt) return "\u2014";
|
|
21365
|
+
const ms = expiresAt - Date.now();
|
|
21366
|
+
if (ms <= 0) return pc24.red("expired");
|
|
21367
|
+
const hours = ms / (1e3 * 60 * 60);
|
|
21368
|
+
if (hours < 1) return pc24.yellow(`${Math.ceil(ms / 6e4)}m`);
|
|
21369
|
+
return `${hours.toFixed(1)}h`;
|
|
21370
|
+
}
|
|
21371
|
+
function registerMcpStatus(cmd) {
|
|
21372
|
+
cmd.command("status").description("Show status of all MCP credentials").action(async () => {
|
|
21373
|
+
const client = getMcpAuthClient();
|
|
21374
|
+
let data;
|
|
21375
|
+
try {
|
|
21376
|
+
data = await client.status();
|
|
21377
|
+
} catch (err) {
|
|
21378
|
+
printError(`Could not reach mcp-auth service: ${err instanceof Error ? err.message : "unknown error"}`);
|
|
21379
|
+
process.exitCode = 1;
|
|
21380
|
+
return;
|
|
21381
|
+
}
|
|
21382
|
+
const mcps = data.mcps ?? [];
|
|
21383
|
+
printHeader(`MCP Credentials (${mcps.length})`);
|
|
21384
|
+
if (mcps.length === 0) {
|
|
21385
|
+
console.log(pc24.dim("No credentials registered."));
|
|
21386
|
+
return;
|
|
21387
|
+
}
|
|
21388
|
+
for (const m of mcps) {
|
|
21389
|
+
const stateColor = !m.validated ? pc24.yellow : m.expired ? pc24.red : pc24.green;
|
|
21390
|
+
const stateLabel = !m.validated ? "unvalidated" : m.expired ? "expired" : "active";
|
|
21391
|
+
console.log(`
|
|
21392
|
+
${pc24.cyan(m.service)} ${stateColor(`[${stateLabel}]`)}`);
|
|
21393
|
+
console.log(` label: ${m.label}`);
|
|
21394
|
+
console.log(` type: ${m.type}`);
|
|
21395
|
+
if (m.envName) console.log(` env var: ${m.envName}`);
|
|
21396
|
+
if (m.importedFrom) console.log(` imported: ${m.importedFrom}`);
|
|
21397
|
+
if (m.type === "oauth") console.log(` expires in: ${formatExpiry(m.expiresAt)}`);
|
|
21398
|
+
if (m.lastUsed) console.log(` last used: ${new Date(m.lastUsed).toLocaleString()}`);
|
|
21399
|
+
}
|
|
21400
|
+
console.log("");
|
|
21401
|
+
});
|
|
21402
|
+
}
|
|
21403
|
+
|
|
21404
|
+
// src/commands/mcp/import.ts
|
|
21405
|
+
init_output();
|
|
21406
|
+
import * as readline3 from "node:readline";
|
|
21407
|
+
import pc25 from "picocolors";
|
|
21408
|
+
|
|
21409
|
+
// src/commands/mcp/import-discovery.ts
|
|
21141
21410
|
import * as fs37 from "node:fs";
|
|
21411
|
+
import * as os23 from "node:os";
|
|
21142
21412
|
import * as path41 from "node:path";
|
|
21413
|
+
function readJsonFile(filePath) {
|
|
21414
|
+
try {
|
|
21415
|
+
const raw = fs37.readFileSync(filePath, "utf-8");
|
|
21416
|
+
return JSON.parse(raw);
|
|
21417
|
+
} catch {
|
|
21418
|
+
return null;
|
|
21419
|
+
}
|
|
21420
|
+
}
|
|
21421
|
+
function extractMcpServers(obj, source, sourceLabel) {
|
|
21422
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return [];
|
|
21423
|
+
const record = obj;
|
|
21424
|
+
const mcpServers = record["mcpServers"] ?? record["mcps"];
|
|
21425
|
+
if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) return [];
|
|
21426
|
+
const result = [];
|
|
21427
|
+
for (const [name, entry] of Object.entries(mcpServers)) {
|
|
21428
|
+
if (!entry || typeof entry !== "object") continue;
|
|
21429
|
+
const command = entry.command;
|
|
21430
|
+
if (!command || typeof command !== "string") continue;
|
|
21431
|
+
result.push({
|
|
21432
|
+
name,
|
|
21433
|
+
command,
|
|
21434
|
+
args: Array.isArray(entry.args) ? entry.args : [],
|
|
21435
|
+
env: entry.env && typeof entry.env === "object" ? entry.env : void 0,
|
|
21436
|
+
source,
|
|
21437
|
+
sourceLabel
|
|
21438
|
+
});
|
|
21439
|
+
}
|
|
21440
|
+
return result;
|
|
21441
|
+
}
|
|
21442
|
+
function getClaudeDesktopPath() {
|
|
21443
|
+
if (process.platform === "darwin") {
|
|
21444
|
+
return path41.join(os23.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
21445
|
+
}
|
|
21446
|
+
if (process.platform === "win32") {
|
|
21447
|
+
const appData = process.env["APPDATA"] ?? path41.join(os23.homedir(), "AppData", "Roaming");
|
|
21448
|
+
return path41.join(appData, "Claude", "claude_desktop_config.json");
|
|
21449
|
+
}
|
|
21450
|
+
return path41.join(os23.homedir(), ".config", "Claude", "claude_desktop_config.json");
|
|
21451
|
+
}
|
|
21452
|
+
function getOlamRepoPaths() {
|
|
21453
|
+
const configPaths = [
|
|
21454
|
+
path41.join(os23.homedir(), ".olam", "config.yaml"),
|
|
21455
|
+
path41.join(process.cwd(), ".olam", "config.yaml")
|
|
21456
|
+
];
|
|
21457
|
+
const paths = [];
|
|
21458
|
+
for (const configPath of configPaths) {
|
|
21459
|
+
if (!fs37.existsSync(configPath)) continue;
|
|
21460
|
+
try {
|
|
21461
|
+
const raw = fs37.readFileSync(configPath, "utf-8");
|
|
21462
|
+
const repoMatches = [...raw.matchAll(/path:\s*["']?([^\s"'\n]+)/g)];
|
|
21463
|
+
for (const m of repoMatches) {
|
|
21464
|
+
if (m[1]) paths.push(m[1]);
|
|
21465
|
+
}
|
|
21466
|
+
} catch {
|
|
21467
|
+
}
|
|
21468
|
+
}
|
|
21469
|
+
return paths;
|
|
21470
|
+
}
|
|
21471
|
+
async function discoverMcpSources(repoPaths) {
|
|
21472
|
+
const start = performance.now();
|
|
21473
|
+
const allEntries = [];
|
|
21474
|
+
const sources = [];
|
|
21475
|
+
const sourceDefs = [
|
|
21476
|
+
{
|
|
21477
|
+
path: path41.join(os23.homedir(), ".claude.json"),
|
|
21478
|
+
label: "Claude Code (~/.claude.json)"
|
|
21479
|
+
},
|
|
21480
|
+
{
|
|
21481
|
+
path: getClaudeDesktopPath(),
|
|
21482
|
+
label: "Claude Desktop"
|
|
21483
|
+
},
|
|
21484
|
+
{
|
|
21485
|
+
path: path41.join(os23.homedir(), ".cursor", "mcp.json"),
|
|
21486
|
+
label: "Cursor (~/.cursor/mcp.json)"
|
|
21487
|
+
},
|
|
21488
|
+
{
|
|
21489
|
+
path: path41.join(os23.homedir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
21490
|
+
label: "Windsurf (~/.codeium/windsurf/mcp_config.json)"
|
|
21491
|
+
}
|
|
21492
|
+
];
|
|
21493
|
+
const resolvedRepoPaths = repoPaths ?? getOlamRepoPaths();
|
|
21494
|
+
for (const repoPath of resolvedRepoPaths) {
|
|
21495
|
+
sourceDefs.push({
|
|
21496
|
+
path: path41.join(repoPath, ".mcp.json"),
|
|
21497
|
+
label: `.mcp.json (${path41.basename(repoPath)})`
|
|
21498
|
+
});
|
|
21499
|
+
}
|
|
21500
|
+
const reads = await Promise.all(
|
|
21501
|
+
sourceDefs.map(async ({ path: filePath, label }) => ({
|
|
21502
|
+
filePath,
|
|
21503
|
+
label,
|
|
21504
|
+
data: await Promise.resolve(readJsonFile(filePath))
|
|
21505
|
+
}))
|
|
21506
|
+
);
|
|
21507
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21508
|
+
for (const { filePath, label, data } of reads) {
|
|
21509
|
+
if (!data) continue;
|
|
21510
|
+
sources.push(label);
|
|
21511
|
+
const entries = extractMcpServers(data, filePath, label);
|
|
21512
|
+
for (const entry of entries) {
|
|
21513
|
+
if (!seen.has(entry.name)) {
|
|
21514
|
+
seen.add(entry.name);
|
|
21515
|
+
allEntries.push(entry);
|
|
21516
|
+
}
|
|
21517
|
+
}
|
|
21518
|
+
}
|
|
21519
|
+
return {
|
|
21520
|
+
entries: allEntries,
|
|
21521
|
+
sources,
|
|
21522
|
+
durationMs: performance.now() - start
|
|
21523
|
+
};
|
|
21524
|
+
}
|
|
21525
|
+
|
|
21526
|
+
// src/commands/mcp/import-validate.ts
|
|
21527
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
21528
|
+
var VALIDATION_TIMEOUT_MS = 5e3;
|
|
21529
|
+
async function validateMcpEntry(entry) {
|
|
21530
|
+
return new Promise((resolve8) => {
|
|
21531
|
+
let stdout = "";
|
|
21532
|
+
let timedOut = false;
|
|
21533
|
+
let child;
|
|
21534
|
+
try {
|
|
21535
|
+
child = spawn5(entry.command, [...entry.args ?? []], {
|
|
21536
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
21537
|
+
env: { ...process.env, ...entry.env ?? {} }
|
|
21538
|
+
});
|
|
21539
|
+
} catch (err) {
|
|
21540
|
+
resolve8({
|
|
21541
|
+
name: entry.name,
|
|
21542
|
+
validated: false,
|
|
21543
|
+
reason: err instanceof Error ? err.message : "spawn failed"
|
|
21544
|
+
});
|
|
21545
|
+
return;
|
|
21546
|
+
}
|
|
21547
|
+
const timer = setTimeout(() => {
|
|
21548
|
+
timedOut = true;
|
|
21549
|
+
child.kill("SIGKILL");
|
|
21550
|
+
}, VALIDATION_TIMEOUT_MS);
|
|
21551
|
+
child.stdout?.on("data", (chunk) => {
|
|
21552
|
+
stdout += chunk.toString();
|
|
21553
|
+
});
|
|
21554
|
+
child.on("close", (code) => {
|
|
21555
|
+
clearTimeout(timer);
|
|
21556
|
+
if (timedOut) {
|
|
21557
|
+
resolve8({ name: entry.name, validated: false, reason: "timeout (5s)" });
|
|
21558
|
+
return;
|
|
21559
|
+
}
|
|
21560
|
+
const validated = code === 0 && stdout.trim().length > 0;
|
|
21561
|
+
resolve8({
|
|
21562
|
+
name: entry.name,
|
|
21563
|
+
validated,
|
|
21564
|
+
reason: validated ? "ok" : `exit code ${code ?? "null"}`
|
|
21565
|
+
});
|
|
21566
|
+
});
|
|
21567
|
+
child.on("error", (err) => {
|
|
21568
|
+
clearTimeout(timer);
|
|
21569
|
+
resolve8({ name: entry.name, validated: false, reason: err.message });
|
|
21570
|
+
});
|
|
21571
|
+
});
|
|
21572
|
+
}
|
|
21573
|
+
|
|
21574
|
+
// src/commands/mcp/import.ts
|
|
21575
|
+
async function multiSelectPicker(entries) {
|
|
21576
|
+
if (entries.length === 0) return [];
|
|
21577
|
+
console.log("\n" + pc25.bold("Discovered MCP servers:"));
|
|
21578
|
+
entries.forEach((e, i) => {
|
|
21579
|
+
console.log(
|
|
21580
|
+
` ${pc25.dim(`[${i + 1}]`)} ${pc25.cyan(e.name.padEnd(20))} ${pc25.dim(e.sourceLabel)}`
|
|
21581
|
+
);
|
|
21582
|
+
});
|
|
21583
|
+
console.log("\n" + pc25.dim('Enter numbers to import (e.g. 1,2,3 or "all" or Enter to skip):'));
|
|
21584
|
+
const answer = await new Promise((resolve8) => {
|
|
21585
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
21586
|
+
rl.question("> ", (ans) => {
|
|
21587
|
+
rl.close();
|
|
21588
|
+
resolve8(ans.trim());
|
|
21589
|
+
});
|
|
21590
|
+
});
|
|
21591
|
+
if (!answer || answer === "") return [];
|
|
21592
|
+
if (answer.toLowerCase() === "all") return entries;
|
|
21593
|
+
const indices = answer.split(/[\s,]+/).map((s) => parseInt(s, 10) - 1).filter((i) => !isNaN(i) && i >= 0 && i < entries.length);
|
|
21594
|
+
const unique = [...new Set(indices)];
|
|
21595
|
+
return unique.map((i) => entries[i]).filter((e) => e != null);
|
|
21596
|
+
}
|
|
21597
|
+
function registerMcpImport(cmd) {
|
|
21598
|
+
cmd.command("import").description("Discover MCP servers from Claude Code, Desktop, Cursor, Windsurf, and .mcp.json").option("--reimport", "Re-import services that are already registered").option("--no-validate", "Skip validation probes").option("--repo-paths <paths>", "Comma-separated list of repo paths to scan for .mcp.json").action(async (opts) => {
|
|
21599
|
+
const client = getMcpAuthClient();
|
|
21600
|
+
let existingIds = /* @__PURE__ */ new Set();
|
|
21601
|
+
try {
|
|
21602
|
+
const data = await client.status();
|
|
21603
|
+
existingIds = new Set((data.mcps ?? []).map((m) => m.id));
|
|
21604
|
+
} catch {
|
|
21605
|
+
}
|
|
21606
|
+
printInfo("Scanning", "Claude Code, Claude Desktop, Cursor, Windsurf, .mcp.json\u2026");
|
|
21607
|
+
const repoPaths = opts.repoPaths ? opts.repoPaths.split(",").map((s) => s.trim()) : void 0;
|
|
21608
|
+
const { entries, sources, durationMs } = await discoverMcpSources(repoPaths);
|
|
21609
|
+
if (entries.length === 0) {
|
|
21610
|
+
console.log(pc25.dim("No MCP servers found in any source path."));
|
|
21611
|
+
return;
|
|
21612
|
+
}
|
|
21613
|
+
printInfo("Sources", sources.length > 0 ? sources.join(", ") : "none");
|
|
21614
|
+
printInfo("Found", `${entries.length} MCP server(s) in ${Math.round(durationMs)}ms`);
|
|
21615
|
+
let candidates = entries;
|
|
21616
|
+
let skippedCount = 0;
|
|
21617
|
+
if (!opts.reimport) {
|
|
21618
|
+
const filtered = entries.filter((e) => !existingIds.has(e.name));
|
|
21619
|
+
skippedCount = entries.length - filtered.length;
|
|
21620
|
+
candidates = filtered;
|
|
21621
|
+
}
|
|
21622
|
+
if (skippedCount > 0) {
|
|
21623
|
+
console.log(pc25.dim(`skipped: ${skippedCount} already registered`));
|
|
21624
|
+
}
|
|
21625
|
+
if (candidates.length === 0) {
|
|
21626
|
+
console.log(pc25.dim("Nothing new to import. Use --reimport to force."));
|
|
21627
|
+
return;
|
|
21628
|
+
}
|
|
21629
|
+
const selected = await multiSelectPicker(candidates);
|
|
21630
|
+
if (selected.length === 0) {
|
|
21631
|
+
console.log(pc25.dim("No servers selected."));
|
|
21632
|
+
return;
|
|
21633
|
+
}
|
|
21634
|
+
console.log(`
|
|
21635
|
+
Importing ${selected.length} server(s)\u2026`);
|
|
21636
|
+
let importedCount = 0;
|
|
21637
|
+
let validatedCount = 0;
|
|
21638
|
+
for (const entry of selected) {
|
|
21639
|
+
let validated = false;
|
|
21640
|
+
let validationReason = "skipped";
|
|
21641
|
+
if (opts.validate !== false) {
|
|
21642
|
+
process.stdout.write(` ${pc25.dim("\u2192")} ${entry.name} validating\u2026 `);
|
|
21643
|
+
const vr = await validateMcpEntry(entry);
|
|
21644
|
+
validated = vr.validated;
|
|
21645
|
+
validationReason = vr.reason;
|
|
21646
|
+
process.stdout.write(
|
|
21647
|
+
validated ? pc25.green("ok\n") : pc25.yellow(`unvalidated (${vr.reason})
|
|
21648
|
+
`)
|
|
21649
|
+
);
|
|
21650
|
+
} else {
|
|
21651
|
+
console.log(` ${pc25.dim("\u2192")} ${entry.name} ${pc25.dim("(validation skipped)")}`);
|
|
21652
|
+
}
|
|
21653
|
+
if (validated) validatedCount++;
|
|
21654
|
+
const result = await client.staticAdd({
|
|
21655
|
+
service: entry.name,
|
|
21656
|
+
token: `mcp://${entry.command}`,
|
|
21657
|
+
label: entry.name
|
|
21658
|
+
});
|
|
21659
|
+
if (result.ok) {
|
|
21660
|
+
importedCount++;
|
|
21661
|
+
} else {
|
|
21662
|
+
printError(`Failed to register ${entry.name}: ${result.error ?? "unknown error"}`);
|
|
21663
|
+
}
|
|
21664
|
+
}
|
|
21665
|
+
console.log("");
|
|
21666
|
+
console.log(pc25.green(`\u2713 Imported ${importedCount}/${selected.length}`));
|
|
21667
|
+
if (validatedCount > 0) {
|
|
21668
|
+
console.log(pc25.dim(` ${validatedCount} validated, ${importedCount - validatedCount} unvalidated`));
|
|
21669
|
+
}
|
|
21670
|
+
});
|
|
21671
|
+
}
|
|
21672
|
+
|
|
21673
|
+
// src/commands/mcp/index.ts
|
|
21674
|
+
function registerMcp(program2) {
|
|
21675
|
+
const mcp = program2.command("mcp").description("Manage MCP server credentials (login, add, list, remove, import)");
|
|
21676
|
+
registerMcpLogin(mcp);
|
|
21677
|
+
registerMcpAdd(mcp);
|
|
21678
|
+
registerMcpList(mcp);
|
|
21679
|
+
registerMcpRemove(mcp);
|
|
21680
|
+
registerMcpStatus(mcp);
|
|
21681
|
+
registerMcpImport(mcp);
|
|
21682
|
+
}
|
|
21683
|
+
|
|
21684
|
+
// src/pleri-config.ts
|
|
21685
|
+
import * as fs38 from "node:fs";
|
|
21686
|
+
import * as path42 from "node:path";
|
|
21143
21687
|
function isPleriConfigured(configDir = process.env.OLAM_CONFIG_DIR ?? ".olam") {
|
|
21144
21688
|
if (process.env.PLERI_BASE_URL) {
|
|
21145
21689
|
return true;
|
|
21146
21690
|
}
|
|
21147
|
-
const configPath =
|
|
21148
|
-
if (!
|
|
21691
|
+
const configPath = path42.join(configDir, "config.yaml");
|
|
21692
|
+
if (!fs38.existsSync(configPath)) {
|
|
21149
21693
|
return false;
|
|
21150
21694
|
}
|
|
21151
21695
|
try {
|
|
21152
|
-
const contents =
|
|
21696
|
+
const contents = fs38.readFileSync(configPath, "utf8");
|
|
21153
21697
|
return /^[^#\n]*\bpleri:/m.test(contents);
|
|
21154
21698
|
} catch {
|
|
21155
21699
|
return false;
|
|
@@ -21160,14 +21704,14 @@ function isPleriConfigured(configDir = process.env.OLAM_CONFIG_DIR ?? ".olam") {
|
|
|
21160
21704
|
var program = new Command();
|
|
21161
21705
|
function readCliVersion() {
|
|
21162
21706
|
try {
|
|
21163
|
-
const here =
|
|
21707
|
+
const here = path43.dirname(fileURLToPath4(import.meta.url));
|
|
21164
21708
|
for (const candidate of [
|
|
21165
|
-
|
|
21166
|
-
|
|
21167
|
-
|
|
21709
|
+
path43.join(here, "package.json"),
|
|
21710
|
+
path43.join(here, "..", "package.json"),
|
|
21711
|
+
path43.join(here, "..", "..", "package.json")
|
|
21168
21712
|
]) {
|
|
21169
|
-
if (
|
|
21170
|
-
const pkg = JSON.parse(
|
|
21713
|
+
if (fs39.existsSync(candidate)) {
|
|
21714
|
+
const pkg = JSON.parse(fs39.readFileSync(candidate, "utf-8"));
|
|
21171
21715
|
if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
|
|
21172
21716
|
}
|
|
21173
21717
|
}
|
|
@@ -21205,4 +21749,5 @@ registerUpdate(program);
|
|
|
21205
21749
|
registerBegin(program);
|
|
21206
21750
|
registerStop(program);
|
|
21207
21751
|
registerWorldUpgrade(program);
|
|
21752
|
+
registerMcp(program);
|
|
21208
21753
|
program.parse();
|