@floomhq/skills 0.2.9 → 0.2.11

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/index.js CHANGED
@@ -2014,6 +2014,19 @@ var scrypt = promisify(scryptCb);
2014
2014
  // ../shared/src/install-targets.ts
2015
2015
  import { homedir } from "node:os";
2016
2016
  import { join } from "node:path";
2017
+ var INSTALL_TARGETS = [
2018
+ "generic",
2019
+ "all",
2020
+ "claude",
2021
+ "codex",
2022
+ "cursor",
2023
+ "gemini",
2024
+ "opencode",
2025
+ "kimi"
2026
+ ];
2027
+ function isInstallTarget(value) {
2028
+ return !!value && INSTALL_TARGETS.includes(value);
2029
+ }
2017
2030
  var COMPATIBLE_AGENTS = {
2018
2031
  generic: ["Claude Code", "Codex CLI", "Cursor", "Gemini CLI", "OpenCode", "Kimi CLI"],
2019
2032
  all: ["Claude Code", "Codex CLI", "Cursor", "Gemini CLI", "OpenCode", "Kimi CLI"],
@@ -2061,24 +2074,24 @@ function presetDir(target, opts) {
2061
2074
  }
2062
2075
  }
2063
2076
  function resolveInstallDir(args) {
2077
+ const target = args.target ?? "generic";
2064
2078
  if (args.to) {
2065
2079
  return {
2066
- target: args.target ?? "generic",
2080
+ target,
2067
2081
  dir: args.to,
2068
2082
  origin: "explicit",
2069
- compatibleAgents: COMPATIBLE_AGENTS[args.target ?? "generic"]
2083
+ compatibleAgents: COMPATIBLE_AGENTS[target]
2070
2084
  };
2071
2085
  }
2072
- const targetEnvDir = envDirForTarget(args.target ?? "generic");
2086
+ const targetEnvDir = envDirForTarget(target);
2073
2087
  if (targetEnvDir) {
2074
2088
  return {
2075
- target: args.target ?? "generic",
2089
+ target,
2076
2090
  dir: targetEnvDir,
2077
2091
  origin: "env",
2078
- compatibleAgents: COMPATIBLE_AGENTS[args.target ?? "generic"]
2092
+ compatibleAgents: COMPATIBLE_AGENTS[target]
2079
2093
  };
2080
2094
  }
2081
- const target = args.target ?? "generic";
2082
2095
  return {
2083
2096
  target,
2084
2097
  dir: presetDir(target, { global: args.global, cwd: args.cwd }),
@@ -2381,7 +2394,8 @@ var CONFIG_DIR = join3(homedir2(), ".floom");
2381
2394
  var AUTH_FILE = join3(CONFIG_DIR, "auth.json");
2382
2395
  var DEFAULT_APP_URL = "https://skills.floom.dev";
2383
2396
  var DEFAULT_API_URL = "https://skills.floom.dev/api/v1";
2384
- var LEGACY_API_HOSTS = /* @__PURE__ */ new Set(["floom-v0.vercel.app"]);
2397
+ var LEGACY_API_HOSTS = /* @__PURE__ */ new Set(["floom-v0.vercel.app", "skills.wasm.floom.dev"]);
2398
+ var TRUSTED_API_HOSTS = /* @__PURE__ */ new Set(["skills.floom.dev", "skills.wasm.floom.dev"]);
2385
2399
  async function ensureDir() {
2386
2400
  await mkdir2(CONFIG_DIR, { recursive: true, mode: 448 });
2387
2401
  }
@@ -2395,12 +2409,15 @@ async function readRawAuth() {
2395
2409
  return JSON.parse(raw);
2396
2410
  } catch (e) {
2397
2411
  if (e.code === "ENOENT") return null;
2412
+ if (e instanceof SyntaxError) {
2413
+ throw new FloomError("AUTH_REQUIRED", "Invalid ~/.floom/auth.json. Run: floom login to refresh local auth.");
2414
+ }
2398
2415
  throw e;
2399
2416
  }
2400
2417
  }
2401
2418
  async function writeAuth(state) {
2402
2419
  await ensureDir();
2403
- await writeFile(AUTH_FILE, JSON.stringify({ ...state, apiUrl: normalizeApiUrl(state.apiUrl) }, null, 2), { mode: 384 });
2420
+ await writeFile(AUTH_FILE, JSON.stringify({ ...state, apiUrl: trustedApiUrlOrDefault(state.apiUrl) }, null, 2), { mode: 384 });
2404
2421
  try {
2405
2422
  await chmod(AUTH_FILE, 384);
2406
2423
  } catch {
@@ -2423,14 +2440,16 @@ function getAppUrl() {
2423
2440
  }
2424
2441
  function getApiBaseUrls(preferred) {
2425
2442
  const explicitApiUrl = process.env.FLOOM_API_URL?.trim();
2426
- if (explicitApiUrl) return [normalizeApiUrl(explicitApiUrl)];
2427
- const primary = normalizeApiUrl(preferred ?? getApiUrl());
2443
+ if (explicitApiUrl) return [trustedApiUrlOrDefault(explicitApiUrl)];
2444
+ const primary = trustedApiUrlOrDefault(preferred ?? getApiUrl());
2428
2445
  const bases = [primary];
2429
2446
  if (preferred && !process.env.FLOOM_APP_URL) bases.push(DEFAULT_API_URL);
2430
2447
  return Array.from(new Set(bases));
2431
2448
  }
2432
2449
  function normalizeApiUrl(apiUrl) {
2433
- const trimmed = apiUrl.replace(/\/$/, "");
2450
+ if (typeof apiUrl !== "string") return DEFAULT_API_URL;
2451
+ const trimmed = apiUrl.trim().replace(/\/$/, "");
2452
+ if (!trimmed) return DEFAULT_API_URL;
2434
2453
  try {
2435
2454
  const url = new URL(trimmed);
2436
2455
  if (LEGACY_API_HOSTS.has(url.hostname)) return DEFAULT_API_URL;
@@ -2439,6 +2458,22 @@ function normalizeApiUrl(apiUrl) {
2439
2458
  }
2440
2459
  return trimmed;
2441
2460
  }
2461
+ function allowsCustomApiUrl() {
2462
+ const raw = process.env.FLOOM_ALLOW_CUSTOM_API_URL?.trim().toLowerCase();
2463
+ return raw === "1" || raw === "true" || raw === "yes";
2464
+ }
2465
+ function isTrustedApiUrl(apiUrl) {
2466
+ try {
2467
+ const url = new URL(apiUrl);
2468
+ return TRUSTED_API_HOSTS.has(url.hostname) || allowsCustomApiUrl();
2469
+ } catch {
2470
+ return false;
2471
+ }
2472
+ }
2473
+ function trustedApiUrlOrDefault(apiUrl) {
2474
+ const normalized = normalizeApiUrl(apiUrl);
2475
+ return isTrustedApiUrl(normalized) ? normalized : DEFAULT_API_URL;
2476
+ }
2442
2477
  function isLegacyApiUrl(apiUrl) {
2443
2478
  if (!apiUrl) return false;
2444
2479
  try {
@@ -2449,7 +2484,7 @@ function isLegacyApiUrl(apiUrl) {
2449
2484
  }
2450
2485
 
2451
2486
  // src/version.ts
2452
- var VERSION = "0.2.9";
2487
+ var VERSION = "0.2.11";
2453
2488
 
2454
2489
  // src/api-client.ts
2455
2490
  var DEFAULT_TIMEOUT_MS = 2e4;
@@ -2491,7 +2526,8 @@ async function api(path, opts = {}) {
2491
2526
  "User-Agent": `floom-cli/${VERSION}`,
2492
2527
  "x-floom-cli-version": VERSION
2493
2528
  };
2494
- if (token) headers.Authorization = `Bearer ${token}`;
2529
+ const requestToken = opts.tokenOverride ?? token;
2530
+ if (requestToken) headers.Authorization = `Bearer ${requestToken}`;
2495
2531
  let res;
2496
2532
  try {
2497
2533
  res = await fetchWithTimeout(url.toString(), {
@@ -2579,7 +2615,7 @@ async function loginCommand() {
2579
2615
  log.info(`Open this URL in your browser:`);
2580
2616
  log.info(` ${session.verification_uri}`);
2581
2617
  log.info("");
2582
- log.info(`Your code: ${session.user_code}`);
2618
+ log.info(`Confirm this code in the browser: ${session.user_code}`);
2583
2619
  log.info("");
2584
2620
  log.info("Waiting for approval in the browser. Press Ctrl+C to cancel.");
2585
2621
  openInBrowser(session.verification_uri).catch(() => {
@@ -2588,8 +2624,9 @@ async function loginCommand() {
2588
2624
  const interval = Math.max(2, session.poll_interval_seconds) * 1e3;
2589
2625
  while (Date.now() < deadline) {
2590
2626
  await new Promise((r) => setTimeout(r, interval));
2591
- const pollPath = `/cli/sessions/${session.session_id}?device_code=${encodeURIComponent(session.device_code)}`;
2592
- const poll = await api(pollPath);
2627
+ const poll = await api(`/cli/sessions/${session.session_id}`, {
2628
+ tokenOverride: session.device_code
2629
+ });
2593
2630
  if (poll.status === "approved" && poll.token && poll.handle && poll.email) {
2594
2631
  await writeAuth({
2595
2632
  token: poll.token,
@@ -2647,7 +2684,13 @@ async function whoamiCommand() {
2647
2684
  try {
2648
2685
  me = await api("/me", { authRequired: true });
2649
2686
  } catch (e) {
2650
- log.err(`Login is not accepted by the Floom API: ${e.message}`);
2687
+ if (e instanceof FloomError && e.code === "TOKEN_EXPIRED") {
2688
+ log.err("Login expired.");
2689
+ } else if (e instanceof FloomError && e.code === "TOKEN_INVALID") {
2690
+ log.err("Login revoked or unknown.");
2691
+ } else {
2692
+ log.err(`Login is not accepted by the Floom API: ${e.message}`);
2693
+ }
2651
2694
  log.info("Run: floom login");
2652
2695
  process.exitCode = 1;
2653
2696
  return;
@@ -2958,7 +3001,8 @@ async function publishCommand(opts = {}) {
2958
3001
  log.ok(`Published ${complete.ref}`);
2959
3002
  log.blank();
2960
3003
  log.info("View:");
2961
- log.kv("", `${(auth?.apiUrl ?? process.env.FLOOM_API_URL ?? "https://skills.floom.dev/api/v1").replace("/api/v1", "")}/@${handle}/${manifest.name}`);
3004
+ const displayApiUrl = trustedApiUrlOrDefault(process.env.FLOOM_API_URL ?? auth?.apiUrl ?? DEFAULT_API_URL);
3005
+ log.kv("", `${displayApiUrl.replace("/api/v1", "")}/@${handle}/${manifest.name}`);
2962
3006
  log.info("Install:");
2963
3007
  log.kv("", complete.install_command);
2964
3008
  }
@@ -3069,6 +3113,12 @@ async function installCommand(refStr, opts = {}) {
3069
3113
  log.info("Expected: @owner/slug, workspace-slug/slug, or with @version suffix");
3070
3114
  process.exit(1);
3071
3115
  }
3116
+ if (opts.for && !isInstallTarget(opts.for)) {
3117
+ log.err(`Invalid install target: ${opts.for}`);
3118
+ log.info("Expected: claude | codex | cursor | gemini | opencode | kimi | all");
3119
+ process.exit(1);
3120
+ }
3121
+ const installTarget = isInstallTarget(opts.for) ? opts.for : "generic";
3072
3122
  let info;
3073
3123
  try {
3074
3124
  info = await api(`/skills/${ref.owner}/${ref.slug}`);
@@ -3093,7 +3143,7 @@ async function installCommand(refStr, opts = {}) {
3093
3143
  process.exit(1);
3094
3144
  }
3095
3145
  const target = resolveInstallDir({
3096
- target: opts.for ?? "generic",
3146
+ target: installTarget,
3097
3147
  to: opts.to,
3098
3148
  global: opts.global
3099
3149
  });
@@ -3154,7 +3204,7 @@ async function installCommand(refStr, opts = {}) {
3154
3204
  bundle_sha256: dl.bundle_sha256,
3155
3205
  installed_at: (/* @__PURE__ */ new Date()).toISOString(),
3156
3206
  path: destFolder.replace(projectDir + "/", ""),
3157
- preset: opts.for
3207
+ preset: installTarget
3158
3208
  });
3159
3209
  await writeLock(projectDir, next);
3160
3210
  log.blank();
@@ -3370,12 +3420,19 @@ async function infoCommand(refStr) {
3370
3420
  }
3371
3421
 
3372
3422
  // src/commands/share.ts
3423
+ function isValidEmail(email) {
3424
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
3425
+ }
3373
3426
  async function shareCommand(refStr, email, opts = {}) {
3374
3427
  const ref = parseSkillRef(refStr);
3375
3428
  if (!ref) {
3376
3429
  log.err(`Invalid skill ref: ${refStr}`);
3377
3430
  process.exit(1);
3378
3431
  }
3432
+ if (!isValidEmail(email)) {
3433
+ log.err(`Invalid email: ${email}`);
3434
+ process.exit(1);
3435
+ }
3379
3436
  const role = opts.role ?? "viewer";
3380
3437
  const r = await api(`/skills/${ref.owner}/${ref.slug}/grants`, {
3381
3438
  method: "POST",
@@ -3391,6 +3448,10 @@ async function unshareCommand(refStr, email) {
3391
3448
  log.err(`Invalid skill ref: ${refStr}`);
3392
3449
  process.exit(1);
3393
3450
  }
3451
+ if (!isValidEmail(email)) {
3452
+ log.err(`Invalid email: ${email}`);
3453
+ process.exit(1);
3454
+ }
3394
3455
  const list = await api(`/skills/${ref.owner}/${ref.slug}/grants`, { authRequired: true });
3395
3456
  const grant = list.grants.find((g) => (g.email ?? "").toLowerCase() === email.toLowerCase());
3396
3457
  if (!grant) {
@@ -3402,6 +3463,9 @@ async function unshareCommand(refStr, email) {
3402
3463
  }
3403
3464
 
3404
3465
  // src/commands/library.ts
3466
+ function isValidEmail2(email) {
3467
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
3468
+ }
3405
3469
  async function libraryListCommand() {
3406
3470
  const resp = await api("/libraries", { authRequired: true });
3407
3471
  if (!resp.libraries.length) {
@@ -3417,6 +3481,10 @@ async function libraryCreateCommand(slug, name) {
3417
3481
  log.ok(`Created workspace ${resp.slug}`);
3418
3482
  }
3419
3483
  async function libraryInviteCommand(librarySlug, email, role = "viewer") {
3484
+ if (!isValidEmail2(email)) {
3485
+ log.err(`Invalid email: ${email}`);
3486
+ process.exit(1);
3487
+ }
3420
3488
  await api(`/libraries/${librarySlug}/pending-invites`, {
3421
3489
  method: "POST",
3422
3490
  authRequired: true,
@@ -3935,6 +4003,15 @@ import { z as z3 } from "zod";
3935
4003
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3936
4004
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3937
4005
  var API_TIMEOUT_MS = 2e4;
4006
+ function semverGte2(a, b) {
4007
+ const pa = a.split(".").map(Number);
4008
+ const pb = b.split(".").map(Number);
4009
+ for (let i = 0; i < 3; i++) {
4010
+ if ((pa[i] ?? 0) > (pb[i] ?? 0)) return true;
4011
+ if ((pa[i] ?? 0) < (pb[i] ?? 0)) return false;
4012
+ }
4013
+ return true;
4014
+ }
3938
4015
  async function fetchWithTimeout2(url, init = {}) {
3939
4016
  const controller = new AbortController();
3940
4017
  const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
@@ -3992,10 +4069,14 @@ async function apiRequest(token, path, query) {
3992
4069
  }
3993
4070
  throw lastError ?? new Error("API request failed");
3994
4071
  }
3995
- async function installViaApi(token, refText, target) {
4072
+ async function installViaApi(token, refText, target, options = {}) {
3996
4073
  const parsed = parseSkillRef(refText);
3997
4074
  if (!parsed) throw new Error(`Invalid ref: ${refText}`);
4075
+ if (!isInstallTarget(target)) throw new Error(`Invalid install target: ${target}`);
3998
4076
  const info = await apiRequest(token, `/skills/${parsed.owner}/${parsed.slug}`);
4077
+ if (info.min_floom_version && !semverGte2(VERSION, info.min_floom_version)) {
4078
+ throw new Error(`This skill requires Floom CLI >= ${info.min_floom_version} (you have ${VERSION}).`);
4079
+ }
3999
4080
  const dl = await apiRequest(token, `/skills/${parsed.owner}/${parsed.slug}/download`, parsed.version ? { version: parsed.version } : void 0);
4000
4081
  const bundle = await rawGet(dl.download.url);
4001
4082
  if (!verifyBundleHash(bundle, dl.bundle_sha256)) throw new Error("Bundle hash mismatch");
@@ -4003,6 +4084,9 @@ async function installViaApi(token, refText, target) {
4003
4084
  await mkdir9(install.dir, { recursive: true });
4004
4085
  const dest = join13(install.dir, parsed.slug);
4005
4086
  const exists = await readdir4(dest).then(() => true).catch(() => false);
4087
+ if (exists && !options.force) {
4088
+ throw new Error(`Folder already exists at ${dest}. Call install_skill with force=true to overwrite after reviewing local changes.`);
4089
+ }
4006
4090
  if (exists) await rm3(dest, { recursive: true, force: true });
4007
4091
  const temp = await mkdtemp(join13(tmpdir3(), `floom-mcp-${parsed.slug}-`));
4008
4092
  try {
@@ -4024,7 +4108,7 @@ async function installViaApi(token, refText, target) {
4024
4108
  preset: target
4025
4109
  });
4026
4110
  await writeLock(process.cwd(), next);
4027
- return { path: dest, version: dl.version, ref: info.ref ?? ref };
4111
+ return { path: dest, version: dl.version, ref: info.ref ?? ref, has_scripts: !!dl.has_scripts };
4028
4112
  }
4029
4113
  async function parseSkillBundle(bundle) {
4030
4114
  const tmp = await mkdtemp(join13(tmpdir3(), "floom-mcp-read-"));
@@ -4123,9 +4207,13 @@ async function mcpCommand() {
4123
4207
  const result = await apiRequest(token, `/libraries/${workspaceSlug}/pins`, { target });
4124
4208
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
4125
4209
  });
4126
- server.tool("install_skill", { ref: z3.string().min(3), target: z3.enum(["claude", "codex", "cursor", "kimi", "opencode", "gemini"]) }, async ({ ref, target }) => {
4210
+ server.tool("install_skill", {
4211
+ ref: z3.string().min(3),
4212
+ target: z3.enum(["claude", "codex", "cursor", "kimi", "opencode", "gemini"]),
4213
+ force: z3.boolean().optional().default(false)
4214
+ }, async ({ ref, target, force }) => {
4127
4215
  const token = await resolveOptionalToken();
4128
- const installed = await installViaApi(token, ref, target);
4216
+ const installed = await installViaApi(token, ref, target, { force });
4129
4217
  return { content: [{ type: "text", text: JSON.stringify(installed) }] };
4130
4218
  });
4131
4219
  const transport = new StdioServerTransport();
@@ -4166,6 +4254,15 @@ function warn(name, detail) {
4166
4254
  function fail(name, detail) {
4167
4255
  return { name, ok: false, detail };
4168
4256
  }
4257
+ function apiUrlCheck(rawApiUrl, label = "api_url") {
4258
+ const normalized = normalizeApiUrl(rawApiUrl);
4259
+ const trusted = trustedApiUrlOrDefault(rawApiUrl);
4260
+ if (isLegacyApiUrl(rawApiUrl)) return warn(label, `legacy URL ${rawApiUrl}; using ${DEFAULT_API_URL}`);
4261
+ if (normalized !== trusted) {
4262
+ return warn(label, `ignored untrusted API URL ${normalized}; set FLOOM_ALLOW_CUSTOM_API_URL=1 only for a trusted self-hosted Floom API`);
4263
+ }
4264
+ return pass(label, trusted);
4265
+ }
4169
4266
  async function validateCurrentToken(token) {
4170
4267
  if (!token) return warn("fresh_agent_auth", "missing token; authenticated MCP calls skipped");
4171
4268
  try {
@@ -4195,7 +4292,7 @@ async function doctorCommand(opts = {}) {
4195
4292
  const checks2 = [
4196
4293
  pass("cli_version", VERSION),
4197
4294
  authCheck2,
4198
- process.env.FLOOM_API_URL ? isLegacyApiUrl(process.env.FLOOM_API_URL) ? warn("api_url", `legacy FLOOM_API_URL ${process.env.FLOOM_API_URL}; using ${DEFAULT_API_URL}`) : pass("api_url", process.env.FLOOM_API_URL) : isLegacyApiUrl(rawAuth?.apiUrl) ? warn("auth_api_url", `legacy URL in ~/.floom/auth.json; using ${DEFAULT_API_URL}`) : pass("api_url", auth2?.apiUrl ?? DEFAULT_API_URL)
4295
+ process.env.FLOOM_API_URL ? apiUrlCheck(process.env.FLOOM_API_URL) : isLegacyApiUrl(rawAuth?.apiUrl) ? warn("auth_api_url", `legacy URL in ~/.floom/auth.json; using ${DEFAULT_API_URL}`) : apiUrlCheck(auth2?.apiUrl ?? DEFAULT_API_URL)
4199
4296
  ];
4200
4297
  emitDoctor(checks2, opts.json);
4201
4298
  if (checks2.some((check) => !check.ok)) process.exit(1);
@@ -4225,7 +4322,7 @@ async function doctorCommand(opts = {}) {
4225
4322
  HOME: tmpHome,
4226
4323
  FLOOM_SKILLS_DIR: tmpSkills,
4227
4324
  ...hasValidToken && token ? { FLOOM_API_TOKEN: token } : {},
4228
- ...process.env.FLOOM_API_URL ? { FLOOM_API_URL: normalizeApiUrl(process.env.FLOOM_API_URL) } : auth?.apiUrl ? { FLOOM_API_URL: auth.apiUrl } : {}
4325
+ ...process.env.FLOOM_API_URL ? { FLOOM_API_URL: trustedApiUrlOrDefault(process.env.FLOOM_API_URL) } : auth?.apiUrl ? { FLOOM_API_URL: trustedApiUrlOrDefault(auth.apiUrl) } : {}
4229
4326
  },
4230
4327
  stderr: "pipe"
4231
4328
  });
@@ -4251,12 +4348,15 @@ async function doctorCommand(opts = {}) {
4251
4348
  } else {
4252
4349
  checks.push(warn("mcp_authenticated_tools", "skipped list_workspaces/search_skills because no valid token is available"));
4253
4350
  }
4254
- const ref = opts.ref ?? "floom-demo/brand-voice";
4255
- const skill = await client.callTool({ name: "get_skill", arguments: { ref } });
4256
- checks.push(/SKILL\.md/.test(textOf(skill)) ? pass("mcp_get_skill", ref) : fail("mcp_get_skill", "bundle did not include SKILL.md"));
4257
- const installed = await client.callTool({ name: "install_skill", arguments: { ref, target: opts.target ?? "codex" } });
4258
- const entries = await readdir5(tmpSkills);
4259
- checks.push(entries.length > 0 ? pass("mcp_install_skill", textOf(installed)) : fail("mcp_install_skill", "target directory is empty"));
4351
+ if (opts.ref) {
4352
+ const skill = await client.callTool({ name: "get_skill", arguments: { ref: opts.ref } });
4353
+ checks.push(/SKILL\.md/.test(textOf(skill)) ? pass("mcp_get_skill", opts.ref) : fail("mcp_get_skill", "bundle did not include SKILL.md"));
4354
+ const installed = await client.callTool({ name: "install_skill", arguments: { ref: opts.ref, target: opts.target ?? "codex" } });
4355
+ const entries = await readdir5(tmpSkills);
4356
+ checks.push(entries.length > 0 ? pass("mcp_install_skill", textOf(installed)) : fail("mcp_install_skill", "target directory is empty"));
4357
+ } else {
4358
+ checks.push(warn("mcp_public_skill", "skipped; pass --ref <public-skill-ref> to verify get_skill/install_skill against a known public skill"));
4359
+ }
4260
4360
  } catch (e) {
4261
4361
  checks.push(fail("fresh_agent_mcp", e.message));
4262
4362
  } finally {