@floomhq/skills 0.2.3 → 0.2.5
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 +92 -18
- package/dist/index.js.map +2 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2360,12 +2360,17 @@ import { join as join3 } from "node:path";
|
|
|
2360
2360
|
import { mkdir as mkdir2, readFile as readFile3, writeFile, chmod } from "node:fs/promises";
|
|
2361
2361
|
var CONFIG_DIR = join3(homedir2(), ".floom");
|
|
2362
2362
|
var AUTH_FILE = join3(CONFIG_DIR, "auth.json");
|
|
2363
|
-
var DEFAULT_APP_URL = "https://floom.dev";
|
|
2364
|
-
var DEFAULT_API_URL = "https://floom
|
|
2363
|
+
var DEFAULT_APP_URL = "https://skills.floom.dev";
|
|
2364
|
+
var DEFAULT_API_URL = "https://skills.floom.dev/api/v1";
|
|
2365
|
+
var LEGACY_API_HOSTS = /* @__PURE__ */ new Set(["floom-v0.vercel.app"]);
|
|
2365
2366
|
async function ensureDir() {
|
|
2366
2367
|
await mkdir2(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
2367
2368
|
}
|
|
2368
2369
|
async function readAuth() {
|
|
2370
|
+
const parsed = await readRawAuth();
|
|
2371
|
+
return parsed ? { ...parsed, apiUrl: normalizeApiUrl(parsed.apiUrl) } : null;
|
|
2372
|
+
}
|
|
2373
|
+
async function readRawAuth() {
|
|
2369
2374
|
try {
|
|
2370
2375
|
const raw = await readFile3(AUTH_FILE, "utf8");
|
|
2371
2376
|
return JSON.parse(raw);
|
|
@@ -2376,7 +2381,7 @@ async function readAuth() {
|
|
|
2376
2381
|
}
|
|
2377
2382
|
async function writeAuth(state) {
|
|
2378
2383
|
await ensureDir();
|
|
2379
|
-
await writeFile(AUTH_FILE, JSON.stringify(state, null, 2), { mode: 384 });
|
|
2384
|
+
await writeFile(AUTH_FILE, JSON.stringify({ ...state, apiUrl: normalizeApiUrl(state.apiUrl) }, null, 2), { mode: 384 });
|
|
2380
2385
|
try {
|
|
2381
2386
|
await chmod(AUTH_FILE, 384);
|
|
2382
2387
|
} catch {
|
|
@@ -2399,15 +2404,33 @@ function getAppUrl() {
|
|
|
2399
2404
|
}
|
|
2400
2405
|
function getApiBaseUrls(preferred) {
|
|
2401
2406
|
const explicitApiUrl = process.env.FLOOM_API_URL?.trim();
|
|
2402
|
-
if (explicitApiUrl) return [explicitApiUrl
|
|
2403
|
-
const primary = (preferred ?? getApiUrl())
|
|
2407
|
+
if (explicitApiUrl) return [normalizeApiUrl(explicitApiUrl)];
|
|
2408
|
+
const primary = normalizeApiUrl(preferred ?? getApiUrl());
|
|
2404
2409
|
const bases = [primary];
|
|
2405
2410
|
if (preferred && !process.env.FLOOM_APP_URL) bases.push(DEFAULT_API_URL);
|
|
2406
2411
|
return Array.from(new Set(bases));
|
|
2407
2412
|
}
|
|
2413
|
+
function normalizeApiUrl(apiUrl) {
|
|
2414
|
+
const trimmed = apiUrl.replace(/\/$/, "");
|
|
2415
|
+
try {
|
|
2416
|
+
const url = new URL(trimmed);
|
|
2417
|
+
if (LEGACY_API_HOSTS.has(url.hostname)) return DEFAULT_API_URL;
|
|
2418
|
+
} catch {
|
|
2419
|
+
return DEFAULT_API_URL;
|
|
2420
|
+
}
|
|
2421
|
+
return trimmed;
|
|
2422
|
+
}
|
|
2423
|
+
function isLegacyApiUrl(apiUrl) {
|
|
2424
|
+
if (!apiUrl) return false;
|
|
2425
|
+
try {
|
|
2426
|
+
return LEGACY_API_HOSTS.has(new URL(apiUrl).hostname);
|
|
2427
|
+
} catch {
|
|
2428
|
+
return false;
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2408
2431
|
|
|
2409
2432
|
// src/version.ts
|
|
2410
|
-
var VERSION = "0.2.
|
|
2433
|
+
var VERSION = "0.2.5";
|
|
2411
2434
|
|
|
2412
2435
|
// src/api-client.ts
|
|
2413
2436
|
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
@@ -2431,7 +2454,8 @@ async function fetchWithTimeout(url, init = {}) {
|
|
|
2431
2454
|
}
|
|
2432
2455
|
async function api(path, opts = {}) {
|
|
2433
2456
|
const auth = await readAuth();
|
|
2434
|
-
|
|
2457
|
+
const token = process.env.FLOOM_API_TOKEN?.trim() || auth?.token;
|
|
2458
|
+
if (opts.authRequired && !token) {
|
|
2435
2459
|
throw new FloomError("AUTH_REQUIRED", "Not logged in. Run: floom login");
|
|
2436
2460
|
}
|
|
2437
2461
|
let lastError = null;
|
|
@@ -2448,7 +2472,7 @@ async function api(path, opts = {}) {
|
|
|
2448
2472
|
"User-Agent": `floom-cli/${VERSION}`,
|
|
2449
2473
|
"x-floom-cli-version": VERSION
|
|
2450
2474
|
};
|
|
2451
|
-
if (
|
|
2475
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
2452
2476
|
let res;
|
|
2453
2477
|
try {
|
|
2454
2478
|
res = await fetchWithTimeout(url.toString(), {
|
|
@@ -2594,22 +2618,25 @@ async function logoutCommand() {
|
|
|
2594
2618
|
// src/commands/whoami.ts
|
|
2595
2619
|
async function whoamiCommand() {
|
|
2596
2620
|
const auth = await readAuth();
|
|
2597
|
-
|
|
2621
|
+
const envToken = process.env.FLOOM_API_TOKEN?.trim();
|
|
2622
|
+
if (!auth && !envToken) {
|
|
2598
2623
|
log.info("Not logged in. Run: floom login");
|
|
2599
2624
|
return;
|
|
2600
2625
|
}
|
|
2626
|
+
let me;
|
|
2601
2627
|
try {
|
|
2602
|
-
await api("/me", { authRequired: true });
|
|
2628
|
+
me = await api("/me", { authRequired: true });
|
|
2603
2629
|
} catch (e) {
|
|
2604
|
-
log.err(`
|
|
2630
|
+
log.err(`Login is not accepted by the Floom API: ${e.message}`);
|
|
2605
2631
|
log.info("Run: floom login");
|
|
2606
2632
|
process.exitCode = 1;
|
|
2607
2633
|
return;
|
|
2608
2634
|
}
|
|
2609
2635
|
log.heading("Logged in as:");
|
|
2610
|
-
log.kv("handle", `@${
|
|
2611
|
-
log.kv("email",
|
|
2612
|
-
log.kv("api url", auth
|
|
2636
|
+
log.kv("handle", `@${me.user.handle}`);
|
|
2637
|
+
log.kv("email", me.user.email);
|
|
2638
|
+
log.kv("api url", process.env.FLOOM_API_URL ?? auth?.apiUrl ?? "default");
|
|
2639
|
+
if (envToken) log.kv("auth", "FLOOM_API_TOKEN");
|
|
2613
2640
|
}
|
|
2614
2641
|
|
|
2615
2642
|
// src/commands/init.ts
|
|
@@ -3516,6 +3543,22 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
|
|
3516
3543
|
function textOf(result) {
|
|
3517
3544
|
return String(result?.content?.[0]?.text ?? "");
|
|
3518
3545
|
}
|
|
3546
|
+
function toolError(result) {
|
|
3547
|
+
const text = textOf(result);
|
|
3548
|
+
if (result?.isError) return text || "tool returned an MCP error";
|
|
3549
|
+
if (/authentication required|forbidden|not found|invalid token/i.test(text)) return text;
|
|
3550
|
+
return null;
|
|
3551
|
+
}
|
|
3552
|
+
function jsonArrayLength(result, key) {
|
|
3553
|
+
if (toolError(result)) return null;
|
|
3554
|
+
try {
|
|
3555
|
+
const parsed = JSON.parse(textOf(result));
|
|
3556
|
+
const value = parsed?.[key];
|
|
3557
|
+
return Array.isArray(value) ? value.length : null;
|
|
3558
|
+
} catch {
|
|
3559
|
+
return null;
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3519
3562
|
function pass(name, detail) {
|
|
3520
3563
|
return { name, ok: true, detail };
|
|
3521
3564
|
}
|
|
@@ -3528,7 +3571,25 @@ function fail(name, detail) {
|
|
|
3528
3571
|
async function doctorCommand(opts = {}) {
|
|
3529
3572
|
if (!opts.freshAgent) {
|
|
3530
3573
|
const auth2 = await readAuth();
|
|
3531
|
-
const
|
|
3574
|
+
const rawAuth = await readRawAuth();
|
|
3575
|
+
let authCheck;
|
|
3576
|
+
const envToken = process.env.FLOOM_API_TOKEN?.trim();
|
|
3577
|
+
if (!auth2?.token && !envToken) {
|
|
3578
|
+
authCheck = fail("auth", "not logged in");
|
|
3579
|
+
} else {
|
|
3580
|
+
try {
|
|
3581
|
+
const me = await api("/me", { authRequired: true });
|
|
3582
|
+
const authSource = envToken ? "FLOOM_API_TOKEN" : "~/.floom/auth.json";
|
|
3583
|
+
authCheck = pass("auth", `@${me.user.handle} via ${authSource}`);
|
|
3584
|
+
} catch (e) {
|
|
3585
|
+
authCheck = fail("auth", `saved login rejected by API: ${e.message}`);
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
const checks2 = [
|
|
3589
|
+
pass("cli_version", VERSION),
|
|
3590
|
+
authCheck,
|
|
3591
|
+
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)
|
|
3592
|
+
];
|
|
3532
3593
|
emitDoctor(checks2, opts.json);
|
|
3533
3594
|
if (checks2.some((check) => !check.ok)) process.exit(1);
|
|
3534
3595
|
return;
|
|
@@ -3557,7 +3618,7 @@ async function doctorCommand(opts = {}) {
|
|
|
3557
3618
|
HOME: tmpHome,
|
|
3558
3619
|
FLOOM_SKILLS_DIR: tmpSkills,
|
|
3559
3620
|
...token ? { FLOOM_API_TOKEN: token } : {},
|
|
3560
|
-
...auth?.apiUrl ? { FLOOM_API_URL: auth.apiUrl } : {}
|
|
3621
|
+
...process.env.FLOOM_API_URL ? { FLOOM_API_URL: normalizeApiUrl(process.env.FLOOM_API_URL) } : auth?.apiUrl ? { FLOOM_API_URL: auth.apiUrl } : {}
|
|
3561
3622
|
},
|
|
3562
3623
|
stderr: "pipe"
|
|
3563
3624
|
});
|
|
@@ -3575,10 +3636,23 @@ async function doctorCommand(opts = {}) {
|
|
|
3575
3636
|
return;
|
|
3576
3637
|
}
|
|
3577
3638
|
const workspaces = await client.callTool({ name: "list_workspaces", arguments: {} });
|
|
3578
|
-
|
|
3639
|
+
const workspaceError = toolError(workspaces);
|
|
3640
|
+
const workspaceCount = jsonArrayLength(workspaces, "libraries");
|
|
3641
|
+
const workspacesOk = !workspaceError && workspaceCount !== null;
|
|
3642
|
+
checks.push(workspacesOk ? pass("mcp_list_workspaces", `${workspaceCount} workspaces`) : fail("mcp_list_workspaces", workspaceError ?? "unexpected response shape"));
|
|
3579
3643
|
const search = await client.callTool({ name: "search_skills", arguments: { query: opts.query ?? "pdf" } });
|
|
3580
|
-
|
|
3644
|
+
const searchError = toolError(search);
|
|
3645
|
+
const skillCount = jsonArrayLength(search, "skills");
|
|
3646
|
+
const searchOk = !searchError && skillCount !== null;
|
|
3647
|
+
checks.push(searchOk ? pass("mcp_search_skills", `${skillCount} skills`) : fail("mcp_search_skills", searchError ?? "unexpected response shape"));
|
|
3581
3648
|
if (opts.ref) {
|
|
3649
|
+
if (!workspacesOk || !searchOk) {
|
|
3650
|
+
checks.push(warn("mcp_get_skill", "skipped because authenticated MCP API prechecks failed"));
|
|
3651
|
+
checks.push(warn("mcp_install_skill", "skipped because authenticated MCP API prechecks failed"));
|
|
3652
|
+
emitDoctor(checks, opts.json);
|
|
3653
|
+
if (checks.some((check) => !check.ok)) process.exit(1);
|
|
3654
|
+
return;
|
|
3655
|
+
}
|
|
3582
3656
|
const skill = await client.callTool({ name: "get_skill", arguments: { ref: opts.ref } });
|
|
3583
3657
|
checks.push(/SKILL\.md/.test(textOf(skill)) ? pass("mcp_get_skill", opts.ref) : fail("mcp_get_skill", "bundle did not include SKILL.md"));
|
|
3584
3658
|
const installed = await client.callTool({ name: "install_skill", arguments: { ref: opts.ref, target: opts.target ?? "codex" } });
|