@floomhq/skills 0.2.17 → 0.2.19

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
@@ -2547,7 +2547,7 @@ function isLegacyApiUrl(apiUrl) {
2547
2547
  }
2548
2548
 
2549
2549
  // src/version.ts
2550
- var VERSION = "0.2.17";
2550
+ var VERSION = "0.2.19";
2551
2551
 
2552
2552
  // src/api-client.ts
2553
2553
  var DEFAULT_TIMEOUT_MS = 2e4;
@@ -3104,37 +3104,31 @@ function buildAuthenticatedSkillUrl(appUrl, librarySlug, skillSlug) {
3104
3104
  }
3105
3105
  function buildPublicSkillUrl(appUrl, handle, librarySlug, skillSlug) {
3106
3106
  const base = trimAppUrl(appUrl);
3107
- if (librarySlug !== handle) return null;
3108
- return `${base}/@${handle}/${skillSlug}`;
3107
+ if (librarySlug === handle) return `${base}/@${encodeURIComponent(handle)}/${encodeURIComponent(skillSlug)}`;
3108
+ return `${base}/${encodeURIComponent(librarySlug)}/${encodeURIComponent(skillSlug)}`;
3109
3109
  }
3110
3110
  function buildPublishViewLines(args) {
3111
3111
  const manageUrl = buildAuthenticatedSkillUrl(args.appUrl, args.refRoot, args.slug);
3112
3112
  const publicUrl = buildPublicSkillUrl(args.appUrl, args.handle, args.refRoot, args.slug);
3113
3113
  if (args.visibility === "public") {
3114
3114
  return {
3115
- heading: publicUrl ? "View (public):" : "Manage (workspace skill):",
3116
- primaryUrl: publicUrl ?? manageUrl,
3117
- ...publicUrl ? {} : { note: "Workspace skills stay under their authenticated library URL unless copied to your personal library." },
3115
+ heading: "View (public):",
3116
+ primaryUrl: publicUrl,
3118
3117
  shareUrl: args.shareUrl ?? void 0
3119
3118
  };
3120
3119
  }
3121
3120
  if (args.visibility === "unlisted") {
3122
3121
  return {
3123
- heading: publicUrl ? "View (unlisted - use a share link for unauthenticated access):" : "Manage (unlisted workspace skill):",
3124
- primaryUrl: publicUrl ?? manageUrl,
3125
- ...publicUrl ? {} : { note: "Use the share link for unauthenticated access; workspace detail URLs remain authenticated." },
3122
+ heading: "View (unlisted - use a share link for unauthenticated access):",
3123
+ primaryUrl: publicUrl,
3126
3124
  shareUrl: args.shareUrl ?? void 0
3127
3125
  };
3128
3126
  }
3129
3127
  return {
3130
3128
  heading: "Manage (private - sign in to your workspace):",
3131
3129
  primaryUrl: manageUrl,
3132
- ...publicUrl ? {
3133
- secondaryHeading: "Public URL after you make it public or unlisted:",
3134
- secondaryUrl: publicUrl
3135
- } : {
3136
- note: "Workspace skills stay under their authenticated library URL unless copied to your personal library."
3137
- }
3130
+ secondaryHeading: "Public URL after you make it public or unlisted:",
3131
+ secondaryUrl: publicUrl
3138
3132
  };
3139
3133
  }
3140
3134
 
@@ -3189,6 +3183,7 @@ async function publishCommand(opts = {}) {
3189
3183
  authRequired: true,
3190
3184
  body: {
3191
3185
  manifest,
3186
+ folder_id: opts.folder,
3192
3187
  skill_md: skillMd,
3193
3188
  bundle: {
3194
3189
  sha256: packed.sha256,
@@ -3755,6 +3750,8 @@ async function infoCommand(refStr) {
3755
3750
  log.kv("Latest", r.latest_version);
3756
3751
  log.kv("Visibility", r.visibility);
3757
3752
  log.kv("Owner", `@${r.owner.handle}${r.owner.display_name ? ` (${r.owner.display_name})` : ""}`);
3753
+ if (r.folder) log.kv("Folder", `${r.folder.name} (${r.folder.slug}, ${r.folder.access_mode})`);
3754
+ if (r.tags?.length) log.kv("Tags", r.tags.map((tag) => `#${tag}`).join(" "));
3758
3755
  if (r.has_scripts) {
3759
3756
  log.kv("Scripts", "yes (review before activating)");
3760
3757
  }
@@ -3764,9 +3761,30 @@ async function infoCommand(refStr) {
3764
3761
  }
3765
3762
 
3766
3763
  // src/commands/share.ts
3764
+ import prompts2 from "prompts";
3767
3765
  function isValidEmail(email) {
3768
3766
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
3769
3767
  }
3768
+ function isValidRole(role) {
3769
+ return role === "viewer" || role === "editor";
3770
+ }
3771
+ async function confirmShare(refStr, email, role, opts) {
3772
+ if (opts.yes) return;
3773
+ if (!process.stdin.isTTY) {
3774
+ log.err(`Sharing ${refStr} with ${email} requires confirmation. Re-run with --yes to confirm.`);
3775
+ process.exit(1);
3776
+ }
3777
+ const answer = await prompts2({
3778
+ type: "confirm",
3779
+ name: "confirmed",
3780
+ message: `Share ${refStr} with ${email} as ${role}?`,
3781
+ initial: false
3782
+ });
3783
+ if (!answer.confirmed) {
3784
+ log.info("Share cancelled.");
3785
+ process.exit(1);
3786
+ }
3787
+ }
3770
3788
  async function shareCommand(refStr, email, opts = {}) {
3771
3789
  const ref = parseSkillRef(refStr);
3772
3790
  if (!ref) {
@@ -3778,12 +3796,18 @@ async function shareCommand(refStr, email, opts = {}) {
3778
3796
  process.exit(1);
3779
3797
  }
3780
3798
  const role = opts.role ?? "viewer";
3799
+ if (!isValidRole(role)) {
3800
+ log.err(`Invalid role: ${role}. Expected viewer or editor.`);
3801
+ process.exit(1);
3802
+ }
3803
+ await confirmShare(refStr, email, role, opts);
3781
3804
  const r = await api(`/skills/${ref.owner}/${ref.slug}/grants`, {
3782
3805
  method: "POST",
3783
3806
  authRequired: true,
3784
3807
  body: { email, role }
3785
3808
  });
3786
3809
  log.ok(`Shared ${refStr} with ${r.grant.email} as ${r.grant.role}.`);
3810
+ log.info(`Undo: floom unshare ${refStr} ${r.grant.email}`);
3787
3811
  if (r.email_status) log.kv("email", r.email_status);
3788
3812
  }
3789
3813
  async function unshareCommand(refStr, email) {
@@ -4956,8 +4980,8 @@ program.command("logout").description("Log out and revoke local token.").action(
4956
4980
  program.command("whoami").description("Show the logged-in user.").action(whoamiCommand);
4957
4981
  program.command("init").description("Scaffold a new skill in the current directory.").action(initCommand);
4958
4982
  program.command("validate").description("Validate the skill in the current directory.").option("--json", "Emit machine-readable JSON").action((opts) => validateCommand(opts));
4959
- program.command("publish").description("Publish the skill in the current directory.").option("--dry-run", "Validate and pack locally without uploading.").option("--workspace <slug>", "Publish into a shared workspace slug (default: personal)").option("--library <slug>", "Legacy alias for --workspace").addHelpText("after", "\nExamples:\n $ floom publish\n $ floom publish --workspace team-workspace").action((opts) => publishCommand(opts));
4960
- program.command("push [dir]").description("Publish one skill folder or every immediate child skill folder.").option("--dry-run", "Validate and pack locally without uploading.").option("--workspace <slug>", "Publish into a shared workspace slug").option("--library <slug>", "Legacy alias for --workspace").option("--concurrency <n>", "Bulk push concurrency, 1-16", "6").addHelpText("after", "\nExamples:\n $ floom push\n $ floom push ./skills --workspace team-workspace --concurrency 4").action((dir, opts) => pushCommand(dir ?? ".", opts));
4983
+ program.command("publish").description("Publish the skill in the current directory.").option("--dry-run", "Validate and pack locally without uploading.").option("--workspace <slug>", "Publish into a shared workspace slug (default: personal)").option("--library <slug>", "Legacy alias for --workspace").option("--folder <uuid>", "Publish into a workspace folder id").addHelpText("after", "\nExamples:\n $ floom publish\n $ floom publish --workspace team-workspace\n $ floom publish --workspace team-workspace --folder 00000000-0000-0000-0000-000000000000").action((opts) => publishCommand(opts));
4984
+ program.command("push [dir]").description("Publish one skill folder or every immediate child skill folder.").option("--dry-run", "Validate and pack locally without uploading.").option("--workspace <slug>", "Publish into a shared workspace slug").option("--library <slug>", "Legacy alias for --workspace").option("--folder <uuid>", "Publish into a workspace folder id").option("--concurrency <n>", "Bulk push concurrency, 1-16", "6").addHelpText("after", "\nExamples:\n $ floom push\n $ floom push ./skills --workspace team-workspace --concurrency 4\n $ floom push ./skills --workspace team-workspace --folder 00000000-0000-0000-0000-000000000000").action((dir, opts) => pushCommand(dir ?? ".", opts));
4961
4985
  program.command("install <ref>").description("Install a skill (default: .agents/skills/<slug>/).").option("--for <target>", "Tool preset: claude | codex | cursor | gemini | opencode | kimi | all").option("--to <path>", "Parent directory; installs to <path>/<skill-slug>/ (not the skill folder itself)").option("--global", "Install to user-level folder instead of project-local").option("--force", "Overwrite existing folder").addHelpText("after", "\nExamples:\n $ floom install @alice/research-brief\n $ floom install @alice/research-brief --for codex\n $ floom install @alice/research-brief --to .agents/skills\n\nNote: --to is the parent folder. The skill lands in .agents/skills/research-brief/, not directly in .agents/skills/.").action((ref, opts) => installCommand(ref, opts));
4962
4986
  program.command("installed").description("List installed skills in this project.").option("--json").action(installedCommand);
4963
4987
  program.command("outdated").description("Show installed skills with newer versions available.").action(outdatedCommand);
@@ -4969,7 +4993,7 @@ program.command("pull").description("Pull account/workspace instructions for act
4969
4993
  program.command("pin <ref>").description("Pin a workspace skill for local pull").option("--workspace <slug>", "Workspace slug").option("--target <target>", "claude | codex | cursor | kimi | opencode", "codex").action((ref, opts) => pinCommand(ref, opts));
4970
4994
  program.command("pinned").alias("pins").description("List workspace skills pinned for local pull").option("--workspace <slug>", "Workspace slug").option("--target <target>", "claude | codex | cursor | kimi | opencode", "codex").option("--json", "Emit machine-readable JSON").action((opts) => pinnedCommand(opts));
4971
4995
  program.command("unpin <ref>").description("Unpin a workspace skill for local pull").option("--workspace <slug>", "Workspace slug").option("--target <target>", "claude | codex | cursor | kimi | opencode", "codex").action((ref, opts) => unpinCommand(ref, opts));
4972
- program.command("share <ref> <email>").description("Invite someone to a skill by email.").option("--role <role>", "viewer (default) or editor").action((ref, email, opts) => shareCommand(ref, email, opts));
4996
+ program.command("share <ref> <email>").description("Invite someone to a skill by email.").option("--role <role>", "viewer (default) or editor").option("--yes", "Confirm the share without an interactive prompt").action((ref, email, opts) => shareCommand(ref, email, opts));
4973
4997
  program.command("unshare <ref> <email>").description("Revoke someone's access.").action((ref, email) => unshareCommand(ref, email));
4974
4998
  var linkCmd = program.command("link").description("Create opaque share links for unlisted/public skills");
4975
4999
  linkCmd.command("create <ref>").description("Create a share link URL for a skill.").option("--name <name>", "Optional link label").option("--role <role>", "viewer (default) or editor").action((ref, opts) => linkCreateCommand(ref, opts));
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.2.17";
1
+ export const VERSION = "0.2.19";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floomhq/skills",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
4
4
  "description": "Floom CLI — publish, install, sync, and share AI agent skills.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://skills.floom.dev",