@floomhq/skills 0.2.5 → 0.2.6
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 +60 -52
- package/dist/index.js.map +3 -3
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2430,7 +2430,7 @@ function isLegacyApiUrl(apiUrl) {
|
|
|
2430
2430
|
}
|
|
2431
2431
|
|
|
2432
2432
|
// src/version.ts
|
|
2433
|
-
var VERSION = "0.2.
|
|
2433
|
+
var VERSION = "0.2.6";
|
|
2434
2434
|
|
|
2435
2435
|
// src/api-client.ts
|
|
2436
2436
|
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
@@ -3405,14 +3405,19 @@ async function fetchWithTimeout2(url, init = {}) {
|
|
|
3405
3405
|
clearTimeout(timer);
|
|
3406
3406
|
}
|
|
3407
3407
|
}
|
|
3408
|
-
async function
|
|
3408
|
+
async function resolveOptionalToken() {
|
|
3409
3409
|
const fromEnv = process.env.FLOOM_API_TOKEN?.trim();
|
|
3410
3410
|
if (fromEnv) return fromEnv;
|
|
3411
3411
|
const auth = await readAuth();
|
|
3412
3412
|
if (auth?.token) return auth.token;
|
|
3413
|
+
return null;
|
|
3414
|
+
}
|
|
3415
|
+
async function resolveRequiredToken() {
|
|
3416
|
+
const token = await resolveOptionalToken();
|
|
3417
|
+
if (token) return token;
|
|
3413
3418
|
throw new Error("Authentication required. Run `floom login` or set FLOOM_API_TOKEN.");
|
|
3414
3419
|
}
|
|
3415
|
-
async function
|
|
3420
|
+
async function apiRequest(token, path, query) {
|
|
3416
3421
|
const auth = await readAuth();
|
|
3417
3422
|
let lastError = null;
|
|
3418
3423
|
for (const base of getApiBaseUrls(auth?.apiUrl)) {
|
|
@@ -3423,7 +3428,10 @@ async function apiWithToken(token, path, query) {
|
|
|
3423
3428
|
let res;
|
|
3424
3429
|
try {
|
|
3425
3430
|
res = await fetchWithTimeout2(url, {
|
|
3426
|
-
headers: {
|
|
3431
|
+
headers: {
|
|
3432
|
+
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
3433
|
+
"Content-Type": "application/json"
|
|
3434
|
+
}
|
|
3427
3435
|
});
|
|
3428
3436
|
} catch (e) {
|
|
3429
3437
|
lastError = new Error(`Unable to reach Floom API at ${base}: ${e.message}`);
|
|
@@ -3445,8 +3453,8 @@ async function apiWithToken(token, path, query) {
|
|
|
3445
3453
|
async function installViaApi(token, refText, target) {
|
|
3446
3454
|
const parsed = parseSkillRef(refText);
|
|
3447
3455
|
if (!parsed) throw new Error(`Invalid ref: ${refText}`);
|
|
3448
|
-
const info = await
|
|
3449
|
-
const dl = await
|
|
3456
|
+
const info = await apiRequest(token, `/skills/${parsed.owner}/${parsed.slug}`);
|
|
3457
|
+
const dl = await apiRequest(token, `/skills/${parsed.owner}/${parsed.slug}/download`, parsed.version ? { version: parsed.version } : void 0);
|
|
3450
3458
|
const bundle = await rawGet(dl.download.url);
|
|
3451
3459
|
if (!verifyBundleHash(bundle, dl.bundle_sha256)) throw new Error("Bundle hash mismatch");
|
|
3452
3460
|
const install = resolveInstallDir({ target });
|
|
@@ -3503,30 +3511,30 @@ async function parseSkillBundle(bundle) {
|
|
|
3503
3511
|
async function mcpCommand() {
|
|
3504
3512
|
const server = new McpServer({ name: "floom", version: VERSION });
|
|
3505
3513
|
server.tool("search_skills", { query: z2.string().min(1), workspace: z2.string().optional(), library: z2.string().optional() }, async ({ query, workspace, library }) => {
|
|
3506
|
-
const token = await
|
|
3514
|
+
const token = await resolveRequiredToken();
|
|
3507
3515
|
const workspaceSlug = workspace ?? library;
|
|
3508
|
-
const result = await
|
|
3516
|
+
const result = await apiRequest(token, "/skills", { q: query, ...workspaceSlug ? { library: workspaceSlug } : {} });
|
|
3509
3517
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
3510
3518
|
});
|
|
3511
3519
|
server.tool("get_skill", { ref: z2.string().min(3) }, async ({ ref }) => {
|
|
3512
|
-
const token = await
|
|
3520
|
+
const token = await resolveOptionalToken();
|
|
3513
3521
|
const parsed = parseSkillRef(ref);
|
|
3514
3522
|
if (!parsed) throw new Error("Invalid ref. Expected @owner/slug or workspace/slug");
|
|
3515
|
-
const meta = await
|
|
3516
|
-
const dl = await
|
|
3523
|
+
const meta = await apiRequest(token, `/skills/${parsed.owner}/${parsed.slug}`);
|
|
3524
|
+
const dl = await apiRequest(token, `/skills/${parsed.owner}/${parsed.slug}/download`);
|
|
3517
3525
|
const buf = await rawGet(dl.download.url);
|
|
3518
3526
|
const parsedBundle = await parseSkillBundle(buf);
|
|
3519
3527
|
return { content: [{ type: "text", text: JSON.stringify({ ref, meta, ...parsedBundle }) }] };
|
|
3520
3528
|
});
|
|
3521
3529
|
async function listWorkspaces() {
|
|
3522
|
-
const token = await
|
|
3523
|
-
const result = await
|
|
3530
|
+
const token = await resolveRequiredToken();
|
|
3531
|
+
const result = await apiRequest(token, "/libraries");
|
|
3524
3532
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
3525
3533
|
}
|
|
3526
3534
|
server.tool("list_workspaces", {}, listWorkspaces);
|
|
3527
3535
|
server.tool("list_libraries", {}, listWorkspaces);
|
|
3528
3536
|
server.tool("install_skill", { ref: z2.string().min(3), target: z2.enum(["claude", "codex", "cursor", "kimi", "opencode", "gemini"]) }, async ({ ref, target }) => {
|
|
3529
|
-
const token = await
|
|
3537
|
+
const token = await resolveOptionalToken();
|
|
3530
3538
|
const installed = await installViaApi(token, ref, target);
|
|
3531
3539
|
return { content: [{ type: "text", text: JSON.stringify(installed) }] };
|
|
3532
3540
|
});
|
|
@@ -3568,26 +3576,35 @@ function warn(name, detail) {
|
|
|
3568
3576
|
function fail(name, detail) {
|
|
3569
3577
|
return { name, ok: false, detail };
|
|
3570
3578
|
}
|
|
3579
|
+
async function validateCurrentToken(token) {
|
|
3580
|
+
if (!token) return warn("fresh_agent_auth", "missing token; authenticated MCP calls skipped");
|
|
3581
|
+
try {
|
|
3582
|
+
const me = await api("/me", { authRequired: true });
|
|
3583
|
+
return pass("fresh_agent_auth", `@${me.user.handle}`);
|
|
3584
|
+
} catch (e) {
|
|
3585
|
+
return warn("fresh_agent_auth", `saved login rejected; authenticated MCP calls skipped: ${e.message}`);
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3571
3588
|
async function doctorCommand(opts = {}) {
|
|
3572
3589
|
if (!opts.freshAgent) {
|
|
3573
3590
|
const auth2 = await readAuth();
|
|
3574
3591
|
const rawAuth = await readRawAuth();
|
|
3575
|
-
let
|
|
3592
|
+
let authCheck2;
|
|
3576
3593
|
const envToken = process.env.FLOOM_API_TOKEN?.trim();
|
|
3577
3594
|
if (!auth2?.token && !envToken) {
|
|
3578
|
-
|
|
3595
|
+
authCheck2 = fail("auth", "not logged in");
|
|
3579
3596
|
} else {
|
|
3580
3597
|
try {
|
|
3581
3598
|
const me = await api("/me", { authRequired: true });
|
|
3582
3599
|
const authSource = envToken ? "FLOOM_API_TOKEN" : "~/.floom/auth.json";
|
|
3583
|
-
|
|
3600
|
+
authCheck2 = pass("auth", `@${me.user.handle} via ${authSource}`);
|
|
3584
3601
|
} catch (e) {
|
|
3585
|
-
|
|
3602
|
+
authCheck2 = fail("auth", `saved login rejected by API: ${e.message}`);
|
|
3586
3603
|
}
|
|
3587
3604
|
}
|
|
3588
3605
|
const checks2 = [
|
|
3589
3606
|
pass("cli_version", VERSION),
|
|
3590
|
-
|
|
3607
|
+
authCheck2,
|
|
3591
3608
|
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
3609
|
];
|
|
3593
3610
|
emitDoctor(checks2, opts.json);
|
|
@@ -3597,9 +3614,9 @@ async function doctorCommand(opts = {}) {
|
|
|
3597
3614
|
const checks = [];
|
|
3598
3615
|
const auth = await readAuth();
|
|
3599
3616
|
const token = process.env.FLOOM_API_TOKEN?.trim() || auth?.token;
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3617
|
+
const authCheck = await validateCurrentToken(token);
|
|
3618
|
+
const hasValidToken = authCheck.ok && authCheck.status !== "warn" && Boolean(token);
|
|
3619
|
+
checks.push(authCheck);
|
|
3603
3620
|
const cliPath = process.argv[1];
|
|
3604
3621
|
if (!cliPath) {
|
|
3605
3622
|
checks.push(fail("fresh_agent_cli_path", "process.argv[1] is empty"));
|
|
@@ -3617,7 +3634,7 @@ async function doctorCommand(opts = {}) {
|
|
|
3617
3634
|
...process.env,
|
|
3618
3635
|
HOME: tmpHome,
|
|
3619
3636
|
FLOOM_SKILLS_DIR: tmpSkills,
|
|
3620
|
-
...token ? { FLOOM_API_TOKEN: token } : {},
|
|
3637
|
+
...hasValidToken && token ? { FLOOM_API_TOKEN: token } : {},
|
|
3621
3638
|
...process.env.FLOOM_API_URL ? { FLOOM_API_URL: normalizeApiUrl(process.env.FLOOM_API_URL) } : auth?.apiUrl ? { FLOOM_API_URL: auth.apiUrl } : {}
|
|
3622
3639
|
},
|
|
3623
3640
|
stderr: "pipe"
|
|
@@ -3627,38 +3644,29 @@ async function doctorCommand(opts = {}) {
|
|
|
3627
3644
|
await client.connect(transport);
|
|
3628
3645
|
const tools = await client.listTools();
|
|
3629
3646
|
const toolNames = tools.tools.map((tool) => tool.name).sort();
|
|
3630
|
-
const expected = ["get_skill", "install_skill", "list_workspaces", "search_skills"];
|
|
3647
|
+
const expected = ["get_skill", "install_skill", "list_libraries", "list_workspaces", "search_skills"];
|
|
3631
3648
|
const missing = expected.filter((name) => !toolNames.includes(name));
|
|
3632
3649
|
checks.push(missing.length === 0 ? pass("mcp_tools", toolNames.join(", ")) : fail("mcp_tools", `missing ${missing.join(", ")}`));
|
|
3633
|
-
if (
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
const searchOk = !searchError && skillCount !== null;
|
|
3647
|
-
checks.push(searchOk ? pass("mcp_search_skills", `${skillCount} skills`) : fail("mcp_search_skills", searchError ?? "unexpected response shape"));
|
|
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
|
-
}
|
|
3656
|
-
const skill = await client.callTool({ name: "get_skill", arguments: { ref: opts.ref } });
|
|
3657
|
-
checks.push(/SKILL\.md/.test(textOf(skill)) ? pass("mcp_get_skill", opts.ref) : fail("mcp_get_skill", "bundle did not include SKILL.md"));
|
|
3658
|
-
const installed = await client.callTool({ name: "install_skill", arguments: { ref: opts.ref, target: opts.target ?? "codex" } });
|
|
3659
|
-
const entries = await readdir5(tmpSkills);
|
|
3660
|
-
checks.push(entries.length > 0 ? pass("mcp_install_skill", textOf(installed)) : fail("mcp_install_skill", "target directory is empty"));
|
|
3650
|
+
if (hasValidToken) {
|
|
3651
|
+
const workspaces = await client.callTool({ name: "list_workspaces", arguments: {} });
|
|
3652
|
+
const workspaceError = toolError(workspaces);
|
|
3653
|
+
const workspaceCount = jsonArrayLength(workspaces, "libraries");
|
|
3654
|
+
const workspacesOk = !workspaceError && workspaceCount !== null;
|
|
3655
|
+
checks.push(workspacesOk ? pass("mcp_list_workspaces", `${workspaceCount} workspaces`) : fail("mcp_list_workspaces", workspaceError ?? "unexpected response shape"));
|
|
3656
|
+
const search = await client.callTool({ name: "search_skills", arguments: { query: opts.query ?? "pdf" } });
|
|
3657
|
+
const searchError = toolError(search);
|
|
3658
|
+
const skillCount = jsonArrayLength(search, "skills");
|
|
3659
|
+
const searchOk = !searchError && skillCount !== null;
|
|
3660
|
+
checks.push(searchOk ? pass("mcp_search_skills", `${skillCount} skills`) : fail("mcp_search_skills", searchError ?? "unexpected response shape"));
|
|
3661
|
+
} else {
|
|
3662
|
+
checks.push(warn("mcp_authenticated_tools", "skipped list_workspaces/search_skills because no valid token is available"));
|
|
3661
3663
|
}
|
|
3664
|
+
const ref = opts.ref ?? "floom-demo/brand-voice";
|
|
3665
|
+
const skill = await client.callTool({ name: "get_skill", arguments: { ref } });
|
|
3666
|
+
checks.push(/SKILL\.md/.test(textOf(skill)) ? pass("mcp_get_skill", ref) : fail("mcp_get_skill", "bundle did not include SKILL.md"));
|
|
3667
|
+
const installed = await client.callTool({ name: "install_skill", arguments: { ref, target: opts.target ?? "codex" } });
|
|
3668
|
+
const entries = await readdir5(tmpSkills);
|
|
3669
|
+
checks.push(entries.length > 0 ? pass("mcp_install_skill", textOf(installed)) : fail("mcp_install_skill", "target directory is empty"));
|
|
3662
3670
|
} catch (e) {
|
|
3663
3671
|
checks.push(fail("fresh_agent_mcp", e.message));
|
|
3664
3672
|
} finally {
|