@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 +132 -32
- package/dist/index.js.map +3 -3
- package/dist/version.js +1 -1
- package/package.json +2 -2
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
|
|
2080
|
+
target,
|
|
2067
2081
|
dir: args.to,
|
|
2068
2082
|
origin: "explicit",
|
|
2069
|
-
compatibleAgents: COMPATIBLE_AGENTS[
|
|
2083
|
+
compatibleAgents: COMPATIBLE_AGENTS[target]
|
|
2070
2084
|
};
|
|
2071
2085
|
}
|
|
2072
|
-
const targetEnvDir = envDirForTarget(
|
|
2086
|
+
const targetEnvDir = envDirForTarget(target);
|
|
2073
2087
|
if (targetEnvDir) {
|
|
2074
2088
|
return {
|
|
2075
|
-
target
|
|
2089
|
+
target,
|
|
2076
2090
|
dir: targetEnvDir,
|
|
2077
2091
|
origin: "env",
|
|
2078
|
-
compatibleAgents: COMPATIBLE_AGENTS[
|
|
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:
|
|
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 [
|
|
2427
|
-
const primary =
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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(`
|
|
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
|
|
2592
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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", {
|
|
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 ?
|
|
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:
|
|
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
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
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 {
|