@floomhq/skills 0.2.0 → 0.2.2

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.
Files changed (82) hide show
  1. package/dist/index.d.ts +0 -1
  2. package/dist/index.js +3649 -87
  3. package/dist/index.js.map +7 -1
  4. package/dist/version.d.ts +1 -1
  5. package/dist/version.js +1 -2
  6. package/package.json +17 -6
  7. package/dist/api-client.d.ts +0 -9
  8. package/dist/api-client.js +0 -61
  9. package/dist/api-client.js.map +0 -1
  10. package/dist/commands/info.d.ts +0 -1
  11. package/dist/commands/info.js +0 -24
  12. package/dist/commands/info.js.map +0 -1
  13. package/dist/commands/init.d.ts +0 -1
  14. package/dist/commands/init.js +0 -108
  15. package/dist/commands/init.js.map +0 -1
  16. package/dist/commands/install.d.ts +0 -8
  17. package/dist/commands/install.js +0 -136
  18. package/dist/commands/install.js.map +0 -1
  19. package/dist/commands/installed.d.ts +0 -4
  20. package/dist/commands/installed.js +0 -22
  21. package/dist/commands/installed.js.map +0 -1
  22. package/dist/commands/library.d.ts +0 -4
  23. package/dist/commands/library.js +0 -29
  24. package/dist/commands/library.js.map +0 -1
  25. package/dist/commands/list.d.ts +0 -7
  26. package/dist/commands/list.js +0 -32
  27. package/dist/commands/list.js.map +0 -1
  28. package/dist/commands/login.d.ts +0 -1
  29. package/dist/commands/login.js +0 -57
  30. package/dist/commands/login.js.map +0 -1
  31. package/dist/commands/logout.d.ts +0 -1
  32. package/dist/commands/logout.js +0 -19
  33. package/dist/commands/logout.js.map +0 -1
  34. package/dist/commands/outdated.d.ts +0 -1
  35. package/dist/commands/outdated.js +0 -50
  36. package/dist/commands/outdated.js.map +0 -1
  37. package/dist/commands/publish.d.ts +0 -4
  38. package/dist/commands/publish.js +0 -84
  39. package/dist/commands/publish.js.map +0 -1
  40. package/dist/commands/share.d.ts +0 -5
  41. package/dist/commands/share.js +0 -34
  42. package/dist/commands/share.js.map +0 -1
  43. package/dist/commands/update.d.ts +0 -4
  44. package/dist/commands/update.js +0 -92
  45. package/dist/commands/update.js.map +0 -1
  46. package/dist/commands/validate.d.ts +0 -32
  47. package/dist/commands/validate.js +0 -108
  48. package/dist/commands/validate.js.map +0 -1
  49. package/dist/commands/whoami.d.ts +0 -1
  50. package/dist/commands/whoami.js +0 -14
  51. package/dist/commands/whoami.js.map +0 -1
  52. package/dist/config.d.ts +0 -11
  53. package/dist/config.js +0 -39
  54. package/dist/config.js.map +0 -1
  55. package/dist/lib/floom-lock.d.ts +0 -22
  56. package/dist/lib/floom-lock.js +0 -66
  57. package/dist/lib/floom-lock.js.map +0 -1
  58. package/dist/lib/output.d.ts +0 -11
  59. package/dist/lib/output.js +0 -17
  60. package/dist/lib/output.js.map +0 -1
  61. package/dist/version.js.map +0 -1
  62. package/src/api-client.ts +0 -80
  63. package/src/commands/info.ts +0 -36
  64. package/src/commands/init.ts +0 -109
  65. package/src/commands/install.ts +0 -176
  66. package/src/commands/installed.ts +0 -29
  67. package/src/commands/library.ts +0 -41
  68. package/src/commands/list.ts +0 -59
  69. package/src/commands/login.ts +0 -77
  70. package/src/commands/logout.ts +0 -18
  71. package/src/commands/outdated.ts +0 -57
  72. package/src/commands/publish.ts +0 -111
  73. package/src/commands/share.ts +0 -41
  74. package/src/commands/update.ts +0 -116
  75. package/src/commands/validate.ts +0 -132
  76. package/src/commands/whoami.ts +0 -14
  77. package/src/config.ts +0 -44
  78. package/src/index.ts +0 -109
  79. package/src/lib/floom-lock.ts +0 -81
  80. package/src/lib/output.ts +0 -17
  81. package/src/version.ts +0 -1
  82. package/tsconfig.json +0 -9
@@ -1,77 +0,0 @@
1
- import open from "node:child_process";
2
- import { exec } from "node:child_process";
3
- import { promisify } from "node:util";
4
- import { api } from "../api-client.js";
5
- import { getApiUrl, writeAuth } from "../config.js";
6
- import { log } from "../lib/output.js";
7
-
8
- const sh = promisify(exec);
9
-
10
- interface CreateSessionResponse {
11
- session_id: string;
12
- device_code: string;
13
- user_code: string;
14
- verification_uri: string;
15
- expires_at: string;
16
- poll_interval_seconds: number;
17
- }
18
-
19
- interface PollSessionResponse {
20
- status: "pending" | "approved" | "expired" | "denied";
21
- token?: string;
22
- handle?: string;
23
- email?: string;
24
- }
25
-
26
- async function openInBrowser(url: string): Promise<void> {
27
- const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
28
- try { await sh(`${opener} ${JSON.stringify(url)}`); } catch { /* user can copy-paste */ }
29
- }
30
-
31
- export async function loginCommand(): Promise<void> {
32
- log.heading("Starting CLI login...");
33
- const session = await api<CreateSessionResponse>("/cli/sessions", {
34
- method: "POST",
35
- body: { client: "floom-cli", version: "0.0.1" },
36
- });
37
-
38
- log.info("");
39
- log.info(`Open this URL in your browser:`);
40
- log.info(` ${session.verification_uri}`);
41
- log.info("");
42
- log.info(`Your code: ${session.user_code}`);
43
- log.info("");
44
-
45
- openInBrowser(session.verification_uri).catch(() => {});
46
-
47
- const start = Date.now();
48
- const deadline = new Date(session.expires_at).getTime();
49
- const interval = Math.max(2, session.poll_interval_seconds) * 1000;
50
-
51
- while (Date.now() < deadline) {
52
- await new Promise((r) => setTimeout(r, interval));
53
- const pollPath = `/cli/sessions/${session.session_id}?device_code=${encodeURIComponent(session.device_code)}`;
54
- const poll = await api<PollSessionResponse>(pollPath);
55
- if (poll.status === "approved" && poll.token && poll.handle && poll.email) {
56
- await writeAuth({
57
- token: poll.token,
58
- handle: poll.handle,
59
- email: poll.email,
60
- apiUrl: getApiUrl(),
61
- });
62
- log.ok(`Logged in as @${poll.handle} (${poll.email})`);
63
- return;
64
- }
65
- if (poll.status === "denied") {
66
- log.err("Login denied.");
67
- process.exit(1);
68
- }
69
- if (poll.status === "expired") {
70
- log.err("Login session expired. Run `floom login` again.");
71
- process.exit(1);
72
- }
73
- process.stdout.write(".");
74
- }
75
- log.err("Login timed out.");
76
- process.exit(1);
77
- }
@@ -1,18 +0,0 @@
1
- import { clearAuth, readAuth } from "../config.js";
2
- import { api } from "../api-client.js";
3
- import { log } from "../lib/output.js";
4
-
5
- export async function logoutCommand(): Promise<void> {
6
- const auth = await readAuth();
7
- if (!auth) {
8
- log.info("Not logged in.");
9
- return;
10
- }
11
- try {
12
- await api("/cli/sessions/revoke", { method: "POST", authRequired: true });
13
- } catch {
14
- // best effort; we still clear local token
15
- }
16
- await clearAuth();
17
- log.ok("Logged out.");
18
- }
@@ -1,57 +0,0 @@
1
- import { api } from "../api-client.js";
2
- import { readLock } from "../lib/floom-lock.js";
3
- import { log } from "../lib/output.js";
4
-
5
- interface SkillInfo {
6
- ref: string;
7
- latest_version: string;
8
- }
9
-
10
- function cmpSemver(a: string, b: string): number {
11
- const pa = a.split(".").map((p) => parseInt(p, 10));
12
- const pb = b.split(".").map((p) => parseInt(p, 10));
13
- for (let i = 0; i < 3; i++) {
14
- const d = (pa[i] ?? 0) - (pb[i] ?? 0);
15
- if (d !== 0) return d;
16
- }
17
- return 0;
18
- }
19
-
20
- export async function outdatedCommand(): Promise<void> {
21
- const lock = await readLock(process.cwd());
22
- const entries = Object.entries(lock.skills);
23
- if (entries.length === 0) {
24
- log.info("No skills installed.");
25
- return;
26
- }
27
-
28
- log.heading("Checking for updates...");
29
- const outdated: Array<{ ref: string; current: string; latest: string }> = [];
30
-
31
- for (const [ref, e] of entries) {
32
- const [, owner, slug] = /^@([^/]+)\/([^@]+)/.exec(ref) ?? [];
33
- if (!owner || !slug) continue;
34
- try {
35
- const info = await api<SkillInfo>(`/skills/${owner}/${slug}`);
36
- if (cmpSemver(info.latest_version, e.version) > 0) {
37
- outdated.push({ ref, current: e.version, latest: info.latest_version });
38
- }
39
- } catch (err) {
40
- log.warn(`Could not check ${ref}: ${(err as Error).message}`);
41
- }
42
- }
43
-
44
- log.blank();
45
- if (outdated.length === 0) {
46
- log.ok("All installed skills are up to date.");
47
- return;
48
- }
49
-
50
- log.heading("Outdated skills");
51
- for (const o of outdated) {
52
- console.log(` ${o.ref.padEnd(40)} ${o.current.padEnd(10)} -> ${o.latest.padEnd(10)} floom update ${o.ref}`);
53
- }
54
- log.blank();
55
- log.info(`${outdated.length} skill${outdated.length === 1 ? "" : "s"} can be updated.`);
56
- process.exit(1); // useful in CI
57
- }
@@ -1,111 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import {
4
- readManifest,
5
- parseSkillFrontmatter,
6
- collectBundle,
7
- packBundle,
8
- FloomError,
9
- } from "@floom/shared";
10
- import { api, rawPut } from "../api-client.js";
11
- import { readAuth } from "../config.js";
12
- import { log } from "../lib/output.js";
13
- import { validateSkill } from "./validate.js";
14
-
15
- interface InitResponse {
16
- version_id: string;
17
- upload: { method: "PUT"; url: string; expires_at: string };
18
- }
19
-
20
- interface CompleteResponse {
21
- ref: string;
22
- status: "active";
23
- install_command: string;
24
- }
25
-
26
- export interface PublishOptions {
27
- library?: string;
28
- }
29
-
30
- export async function publishCommand(opts: PublishOptions = {}): Promise<void> {
31
- const auth = await readAuth();
32
- if (!auth) {
33
- log.err("Not logged in. Run: floom login");
34
- process.exit(1);
35
- }
36
-
37
- const dir = process.cwd();
38
- log.heading("Validating skill...");
39
- const report = await validateSkill(dir);
40
- if (!report.ok) {
41
- log.err("Validation failed. Run `floom validate` for details.");
42
- process.exit(1);
43
- }
44
- log.ok("Validation passed.");
45
-
46
- const m = await readManifest(dir);
47
- if (!m.ok) {
48
- log.err(m.error);
49
- process.exit(1);
50
- }
51
- const manifest = m.manifest;
52
- const skillMd = await readFile(join(dir, "SKILL.md"), "utf8");
53
- const { meta } = parseSkillFrontmatter(skillMd);
54
-
55
- const refRoot = opts.library ?? auth.handle;
56
- log.heading(`Publishing ${refRoot}/${manifest.name}@${manifest.version}`);
57
- log.step("Packing bundle...");
58
- const bundle = await collectBundle(dir);
59
- const packed = await packBundle(bundle);
60
- log.kv("files", String(bundle.files.length));
61
- log.kv("size", `${packed.bytes.length} bytes (${(packed.bytes.length / 1024).toFixed(1)} KB)`);
62
- log.kv("sha256", packed.sha256);
63
-
64
- log.step("Requesting upload URL...");
65
- let initResp: InitResponse;
66
- try {
67
- initResp = await api<InitResponse>(`/skills/${refRoot}/${manifest.name}/versions`, {
68
- method: "POST",
69
- authRequired: true,
70
- body: {
71
- manifest,
72
- skill_md: skillMd,
73
- bundle: {
74
- sha256: packed.sha256,
75
- size_bytes: packed.bytes.length,
76
- },
77
- files: bundle.files.map((f: { relPath: string; sha256: string; size: number }) => ({
78
- path: f.relPath,
79
- sha256: f.sha256,
80
- size_bytes: f.size,
81
- })),
82
- has_scripts: bundle.hasScripts,
83
- },
84
- });
85
- } catch (e) {
86
- if (e instanceof FloomError && e.code === "VERSION_ALREADY_EXISTS") {
87
- log.err(`Version ${manifest.version} already exists for @${auth.handle}/${manifest.name}.`);
88
- log.info("Bump the version in skill.json (e.g. 0.1.1) and try again.");
89
- process.exit(1);
90
- }
91
- throw e;
92
- }
93
-
94
- log.step("Uploading bundle...");
95
- await rawPut(initResp.upload.url, packed.bytes, "application/gzip");
96
-
97
- log.step("Finalizing...");
98
- const complete = await api<CompleteResponse>(`/versions/${initResp.version_id}/complete`, {
99
- method: "POST",
100
- authRequired: true,
101
- body: { bundle_sha256: packed.sha256 },
102
- });
103
-
104
- log.blank();
105
- log.ok(`Published ${complete.ref}`);
106
- log.blank();
107
- log.info("View:");
108
- log.kv("", `${auth.apiUrl.replace("/api/v1", "")}/@${auth.handle}/${manifest.name}`);
109
- log.info("Install:");
110
- log.kv("", complete.install_command);
111
- }
@@ -1,41 +0,0 @@
1
- import { parseSkillRef } from "@floom/shared";
2
- import { api } from "../api-client.js";
3
- import { log } from "../lib/output.js";
4
-
5
- export interface ShareOptions {
6
- role?: "viewer" | "editor";
7
- }
8
-
9
- interface GrantResponse {
10
- grant: { id: string; email: string; role: string; created_at: string };
11
- }
12
-
13
- export async function shareCommand(refStr: string, email: string, opts: ShareOptions = {}): Promise<void> {
14
- const ref = parseSkillRef(refStr);
15
- if (!ref) { log.err(`Invalid skill ref: ${refStr}`); process.exit(1); }
16
- const role = opts.role ?? "viewer";
17
- const r = await api<GrantResponse>(`/skills/${ref.owner}/${ref.slug}/grants`, {
18
- method: "POST",
19
- authRequired: true,
20
- body: { email, role },
21
- });
22
- log.ok(`Shared ${refStr} with ${r.grant.email} as ${r.grant.role}.`);
23
- log.info("No email is sent in V0. They will see the skill under 'Shared with Me' on next login.");
24
- }
25
-
26
- interface GrantListResponse {
27
- grants: Array<{ id: string; email?: string; user_handle?: string; role: string }>;
28
- }
29
-
30
- export async function unshareCommand(refStr: string, email: string): Promise<void> {
31
- const ref = parseSkillRef(refStr);
32
- if (!ref) { log.err(`Invalid skill ref: ${refStr}`); process.exit(1); }
33
- const list = await api<GrantListResponse>(`/skills/${ref.owner}/${ref.slug}/grants`, { authRequired: true });
34
- const grant = list.grants.find((g) => (g.email ?? "").toLowerCase() === email.toLowerCase());
35
- if (!grant) {
36
- log.err(`No active grant for ${email} on ${refStr}.`);
37
- process.exit(1);
38
- }
39
- await api(`/skills/${ref.owner}/${ref.slug}/grants/${grant.id}`, { method: "DELETE", authRequired: true });
40
- log.ok(`Removed ${email}'s access to ${refStr}.`);
41
- }
@@ -1,116 +0,0 @@
1
- import { rm, rename, mkdir } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
3
- import { join, resolve } from "node:path";
4
- import { extractBundle, verifyBundleHash, FloomError } from "@floom/shared";
5
- import { api, rawGet } from "../api-client.js";
6
- import { readLock, writeLock, setLockEntry, hashInstalledFolder } from "../lib/floom-lock.js";
7
- import { log } from "../lib/output.js";
8
-
9
- interface SkillInfo {
10
- ref: string;
11
- latest_version: string;
12
- }
13
-
14
- interface DownloadResponse {
15
- ref: string;
16
- version: string;
17
- download: { url: string; expires_at: string };
18
- bundle_sha256: string;
19
- }
20
-
21
- function cmpSemver(a: string, b: string): number {
22
- const pa = a.split(".").map((p) => parseInt(p, 10));
23
- const pb = b.split(".").map((p) => parseInt(p, 10));
24
- for (let i = 0; i < 3; i++) {
25
- const d = (pa[i] ?? 0) - (pb[i] ?? 0);
26
- if (d !== 0) return d;
27
- }
28
- return 0;
29
- }
30
-
31
- export interface UpdateOptions {
32
- force?: boolean;
33
- }
34
-
35
- export async function updateCommand(refStr?: string, opts: UpdateOptions = {}): Promise<void> {
36
- const projectDir = process.cwd();
37
- const lock = await readLock(projectDir);
38
- const all = Object.entries(lock.skills);
39
- if (all.length === 0) {
40
- log.info("No installed skills.");
41
- return;
42
- }
43
-
44
- const targets = refStr ? all.filter(([r]) => r === refStr) : all;
45
- if (refStr && targets.length === 0) {
46
- log.err(`Not installed: ${refStr}`);
47
- process.exit(1);
48
- }
49
-
50
- let updated = 0, skipped = 0;
51
- for (const [ref, entry] of targets) {
52
- const [, owner, slug] = /^@([^/]+)\/([^@]+)/.exec(ref) ?? [];
53
- if (!owner || !slug) continue;
54
-
55
- let info: SkillInfo;
56
- try { info = await api<SkillInfo>(`/skills/${owner}/${slug}`); }
57
- catch (e) {
58
- log.warn(`Skip ${ref}: ${(e as Error).message}`);
59
- skipped++;
60
- continue;
61
- }
62
-
63
- if (cmpSemver(info.latest_version, entry.version) <= 0) {
64
- log.step(`${ref} is already at latest (${entry.version}).`);
65
- continue;
66
- }
67
-
68
- const installDir = resolve(projectDir, entry.path);
69
-
70
- // Detect local edits via floom-lock recorded bundle_sha256 — simplistic: we
71
- // re-compute on-disk content and compare collective hash to recorded bundle.
72
- // V0 hardening: full per-file lock would be richer; we use a simple gate.
73
- if (!opts.force) {
74
- const current = await hashInstalledFolder(installDir);
75
- // The recorded bundle_sha256 is the tarball hash, not folder hash, so we
76
- // can't compare directly. For now: if force isn't set and folder exists,
77
- // we proceed with confirmation in a real terminal. In V0 we just warn.
78
- // (Full editorial detection lands when files-table-per-version syncs.)
79
- log.step(`Updating ${ref}: ${entry.version} -> ${info.latest_version}`);
80
- }
81
-
82
- const dl = await api<DownloadResponse>(`/skills/${owner}/${slug}/download`);
83
- const buf = await rawGet(dl.download.url);
84
- if (!verifyBundleHash(buf, dl.bundle_sha256)) {
85
- log.err(`Bundle hash mismatch for ${ref} — skipping.`);
86
- skipped++;
87
- continue;
88
- }
89
-
90
- const tmp = await import("node:fs/promises").then((m) =>
91
- m.mkdtemp(join(tmpdir(), `floom-update-${slug}-`)),
92
- );
93
- try {
94
- await extractBundle(buf, tmp);
95
- await rm(installDir, { recursive: true, force: true });
96
- await mkdir(installDir.replace(/\/[^/]+$/, ""), { recursive: true });
97
- await rename(tmp, installDir);
98
- } catch (e) {
99
- await rm(tmp, { recursive: true, force: true }).catch(() => {});
100
- throw e;
101
- }
102
-
103
- const next = setLockEntry(lock, ref, {
104
- ...entry,
105
- version: dl.version,
106
- bundle_sha256: dl.bundle_sha256,
107
- installed_at: new Date().toISOString(),
108
- });
109
- await writeLock(projectDir, next);
110
- log.ok(`Updated ${ref}: ${entry.version} -> ${dl.version}`);
111
- updated++;
112
- }
113
-
114
- log.blank();
115
- log.info(`${updated} updated, ${skipped} skipped.`);
116
- }
@@ -1,132 +0,0 @@
1
- import { readFile, stat } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import {
4
- readManifest,
5
- parseSkillFrontmatter,
6
- validateSkillFrontmatter,
7
- collectBundle,
8
- scanSkillMarkdown,
9
- } from "@floom/shared";
10
- import { log } from "../lib/output.js";
11
-
12
- export interface ValidateOptions {
13
- json?: boolean;
14
- }
15
-
16
- export interface ValidateReport {
17
- ok: boolean;
18
- manifest: { ok: boolean; message?: string };
19
- skill_md: { ok: boolean; message?: string };
20
- name_matches: { ok: boolean; message?: string };
21
- bundle: { ok: boolean; message?: string; files?: number; size?: number; has_scripts?: boolean };
22
- security: { ok: boolean; findings: ReturnType<typeof scanSkillMarkdown> };
23
- }
24
-
25
- export async function validateSkill(dir: string): Promise<ValidateReport> {
26
- const report: ValidateReport = {
27
- ok: true,
28
- manifest: { ok: true },
29
- skill_md: { ok: true },
30
- name_matches: { ok: true },
31
- bundle: { ok: true },
32
- security: { ok: true, findings: [] },
33
- };
34
-
35
- // manifest
36
- const m = await readManifest(dir);
37
- if (!m.ok) {
38
- report.manifest = { ok: false, message: m.error };
39
- report.ok = false;
40
- return report;
41
- }
42
-
43
- // SKILL.md presence + frontmatter
44
- let skillMd: string;
45
- try {
46
- skillMd = await readFile(join(dir, "SKILL.md"), "utf8");
47
- } catch {
48
- report.skill_md = { ok: false, message: "SKILL.md not found" };
49
- report.ok = false;
50
- return report;
51
- }
52
- const { meta } = parseSkillFrontmatter(skillMd);
53
- const fmErr = validateSkillFrontmatter(meta);
54
- if (fmErr) {
55
- report.skill_md = { ok: false, message: fmErr };
56
- report.ok = false;
57
- }
58
-
59
- if (meta.name && meta.name !== m.manifest.name) {
60
- report.name_matches = {
61
- ok: false,
62
- message: `skill.json name="${m.manifest.name}" but SKILL.md frontmatter name="${meta.name}"`,
63
- };
64
- report.ok = false;
65
- }
66
-
67
- // bundle (path traversal, symlinks, file/size limits)
68
- try {
69
- const bundle = await collectBundle(dir);
70
- report.bundle = {
71
- ok: true,
72
- files: bundle.files.length,
73
- size: bundle.totalUncompressedBytes,
74
- has_scripts: bundle.hasScripts,
75
- };
76
- } catch (e) {
77
- report.bundle = { ok: false, message: (e as Error).message };
78
- report.ok = false;
79
- }
80
-
81
- // security: secrets + prompt injection in SKILL.md
82
- const findings = scanSkillMarkdown(skillMd);
83
- if (findings.length > 0) {
84
- const secretFindings = findings.filter((f) => f.category === "secret");
85
- report.security = { ok: secretFindings.length === 0, findings };
86
- if (secretFindings.length > 0) report.ok = false;
87
- }
88
-
89
- return report;
90
- }
91
-
92
- export async function validateCommand(opts: ValidateOptions = {}): Promise<void> {
93
- const dir = process.cwd();
94
- const r = await validateSkill(dir);
95
-
96
- if (opts.json) {
97
- console.log(JSON.stringify(r, null, 2));
98
- process.exit(r.ok ? 0 : 1);
99
- }
100
-
101
- log.heading("Validating skill...");
102
- const line = (ok: boolean, label: string, detail?: string) =>
103
- ok ? log.ok(`${label.padEnd(28)} ${detail ?? ""}`.trim())
104
- : log.err(`${label.padEnd(28)} ${detail ?? ""}`.trim());
105
-
106
- line(r.manifest.ok, "manifest schema", r.manifest.message);
107
- line(r.skill_md.ok, "SKILL.md present", r.skill_md.message);
108
- line(r.name_matches.ok, "name fields match", r.name_matches.message);
109
- if (r.bundle.ok) {
110
- line(true, "files + size limits", `${r.bundle.files} files, ${(r.bundle.size ?? 0) / 1024 | 0} KB`);
111
- } else {
112
- line(false, "files + size limits", r.bundle.message);
113
- }
114
- if (r.security.findings.length === 0) {
115
- line(true, "secrets + prompt injection", "clean");
116
- } else {
117
- line(r.security.ok, "secrets + prompt injection", `${r.security.findings.length} finding(s)`);
118
- for (const f of r.security.findings) {
119
- log.warn(` line ${f.line} [${f.category}] ${f.label}: ${f.preview}`);
120
- }
121
- }
122
- if (r.bundle.has_scripts) {
123
- log.warn("This skill contains scripts/. Agents may execute these — review before activating.");
124
- }
125
- log.blank();
126
- if (r.ok) {
127
- log.ok("Valid.");
128
- } else {
129
- log.err("Invalid.");
130
- process.exit(1);
131
- }
132
- }
@@ -1,14 +0,0 @@
1
- import { readAuth } from "../config.js";
2
- import { log } from "../lib/output.js";
3
-
4
- export async function whoamiCommand(): Promise<void> {
5
- const auth = await readAuth();
6
- if (!auth) {
7
- log.info("Not logged in. Run: floom login");
8
- return;
9
- }
10
- log.heading("Logged in as:");
11
- log.kv("handle", `@${auth.handle}`);
12
- log.kv("email", auth.email);
13
- log.kv("api url", auth.apiUrl);
14
- }
package/src/config.ts DELETED
@@ -1,44 +0,0 @@
1
- import { homedir } from "node:os";
2
- import { join } from "node:path";
3
- import { mkdir, readFile, writeFile, chmod } from "node:fs/promises";
4
-
5
- const CONFIG_DIR = join(homedir(), ".floom");
6
- const AUTH_FILE = join(CONFIG_DIR, "auth.json");
7
-
8
- export interface AuthState {
9
- token: string; // flm_v1_<id>_<secret>
10
- handle: string;
11
- email: string;
12
- apiUrl: string;
13
- }
14
-
15
- export const DEFAULT_API_URL = process.env.FLOOM_API_URL ?? "https://floom-v0.vercel.app/api/v1";
16
-
17
- async function ensureDir(): Promise<void> {
18
- await mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 });
19
- }
20
-
21
- export async function readAuth(): Promise<AuthState | null> {
22
- try {
23
- const raw = await readFile(AUTH_FILE, "utf8");
24
- return JSON.parse(raw) as AuthState;
25
- } catch (e) {
26
- if ((e as NodeJS.ErrnoException).code === "ENOENT") return null;
27
- throw e;
28
- }
29
- }
30
-
31
- export async function writeAuth(state: AuthState): Promise<void> {
32
- await ensureDir();
33
- await writeFile(AUTH_FILE, JSON.stringify(state, null, 2), { mode: 0o600 });
34
- try { await chmod(AUTH_FILE, 0o600); } catch { /* best effort on win */ }
35
- }
36
-
37
- export async function clearAuth(): Promise<void> {
38
- const { unlink } = await import("node:fs/promises");
39
- try { await unlink(AUTH_FILE); } catch { /* ignore */ }
40
- }
41
-
42
- export function getApiUrl(): string {
43
- return process.env.FLOOM_API_URL ?? DEFAULT_API_URL;
44
- }