@hasna/skills 0.4.0 → 0.5.0

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 (45) hide show
  1. package/README.md +176 -5
  2. package/bin/index.js +6518 -4950
  3. package/bin/mcp.js +1173 -410
  4. package/bin/migrate.js +148 -40
  5. package/bin/server.js +53 -83
  6. package/bin/worker.js +41 -73
  7. package/dist/admin-contract.d.ts +37 -19
  8. package/dist/admin-contract.js +1 -1
  9. package/dist/cli/cli.test-utils.d.ts +10 -8
  10. package/dist/cli/commands/customer-profile.d.ts +2 -0
  11. package/dist/cli/commands/customer-verification.d.ts +5 -0
  12. package/dist/cli/commands/tool-primitives.d.ts +1 -1
  13. package/dist/cli/commands/workspace-member-mutations.d.ts +2 -0
  14. package/dist/cli/commands/workspace-members.d.ts +2 -0
  15. package/dist/cli/env-assignment.d.ts +9 -0
  16. package/dist/index.d.ts +5 -2
  17. package/dist/index.js +613 -153
  18. package/dist/lib/agent-sync.d.ts +13 -8
  19. package/dist/lib/api-url.d.ts +4 -3
  20. package/dist/lib/app-home.d.ts +0 -1
  21. package/dist/lib/client-types.d.ts +75 -0
  22. package/dist/lib/credential-state.d.ts +12 -0
  23. package/dist/lib/fleet-credentials.d.ts +41 -15
  24. package/dist/lib/home-adoption.d.ts +2 -0
  25. package/dist/lib/home-census.d.ts +3 -1
  26. package/dist/lib/local-opt-in.d.ts +24 -0
  27. package/dist/lib/portable-skills-files.d.ts +6 -2
  28. package/dist/lib/read-access.d.ts +83 -0
  29. package/dist/lib/remote-auth.d.ts +14 -0
  30. package/dist/lib/remote-client.d.ts +34 -5
  31. package/dist/lib/remote-profile.d.ts +26 -0
  32. package/dist/lib/remote-registry.d.ts +7 -3
  33. package/dist/lib/remote-workspace.d.ts +76 -0
  34. package/dist/lib/skillinfo.d.ts +1 -1
  35. package/dist/mcp/helpers.d.ts +22 -0
  36. package/dist/mcp/index.d.ts +16 -0
  37. package/dist/sdk/governance-store.d.ts +1 -0
  38. package/dist/sdk/index.d.ts +5 -2
  39. package/dist/sdk/index.js +1084 -283
  40. package/dist/sdk/outputs.d.ts +0 -11
  41. package/dist/sdk/runs.d.ts +1 -1
  42. package/dist/storage.js +6 -40
  43. package/docs/skill-standard.md +30 -2
  44. package/package.json +6 -4
  45. package/dist/lib/instance-credentials-race.fixture.d.ts +0 -1
package/bin/mcp.js CHANGED
@@ -6932,58 +6932,14 @@ var require_dist = __commonJS((exports, module) => {
6932
6932
  exports.default = formatsPlugin;
6933
6933
  });
6934
6934
 
6935
- // src/lib/remote-run-contract.ts
6936
- function normalizeRemoteSkillRunContract(payload, fallbackSkill) {
6937
- const record3 = isRecord3(payload) ? payload : {};
6938
- return {
6939
- contractVersion: REMOTE_SKILL_RUN_CONTRACT_VERSION,
6940
- ...pickString(record3, "id"),
6941
- skill: pickStringValue(record3, "skill") ?? fallbackSkill,
6942
- ...pickString(record3, "requestedSlug"),
6943
- ...pickString(record3, "status"),
6944
- ...pickNumber(record3, "exitCode"),
6945
- ...pickString(record3, "correlationId"),
6946
- ...pickString(record3, "createdAt"),
6947
- ...pickString(record3, "startedAt"),
6948
- ...pickString(record3, "completedAt"),
6949
- ...pickNumber(record3, "durationMs"),
6950
- ...pickString(record3, "outputType"),
6951
- ...hasOwn(record3, "outputPreview") ? { outputPreview: record3.outputPreview } : {},
6952
- ...pickString(record3, "errorCode"),
6953
- ...pickString(record3, "errorMessage"),
6954
- ...pickString(record3, "error"),
6955
- ...pickString(record3, "code"),
6956
- ...hasOwn(record3, "details") ? { details: record3.details } : {}
6957
- };
6958
- }
6959
- function isRecord3(value) {
6960
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
6961
- }
6962
- function hasOwn(record3, key) {
6963
- return Object.prototype.hasOwnProperty.call(record3, key);
6964
- }
6965
- function pickString(record3, key) {
6966
- const value = pickStringValue(record3, key);
6967
- return value === undefined ? {} : { [key]: value };
6968
- }
6969
- function pickStringValue(record3, key) {
6970
- const value = record3[key];
6971
- return typeof value === "string" ? value : undefined;
6972
- }
6973
- function pickNumber(record3, key) {
6974
- const value = record3[key];
6975
- return typeof value === "number" && Number.isFinite(value) ? { [key]: value } : {};
6976
- }
6977
- var REMOTE_SKILL_RUN_CONTRACT_VERSION = 1;
6978
-
6979
6935
  // ../contracts/dist/client/transport.js
6980
6936
  import { isIP } from "net";
6981
6937
  import { spawnSync } from "child_process";
6982
- import { closeSync, fstatSync, openSync, readFileSync as readFileSync11 } from "fs";
6938
+ import { closeSync, fstatSync, openSync, readFileSync as readFileSync7 } from "fs";
6983
6939
  import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
6984
6940
  import { createRequire } from "module";
6985
6941
  import { hostname as osHostname } from "os";
6986
- import { isAbsolute as isAbsolute3, join as join13 } from "path";
6942
+ import { isAbsolute as isAbsolute3, join as join9 } from "path";
6987
6943
  function envToken(name) {
6988
6944
  return name.toUpperCase().replace(/-/g, "_");
6989
6945
  }
@@ -7013,14 +6969,14 @@ function hasnaHomeDir(env) {
7013
6969
  if (override)
7014
6970
  return override;
7015
6971
  const home = homeDir(env);
7016
- return home ? join13(home, HASNA_HOME_DIR) : null;
6972
+ return home ? join9(home, HASNA_HOME_DIR) : null;
7017
6973
  }
7018
6974
  function appConfigDir(name, env) {
7019
6975
  const configRoot = absoluteOverride(env, HASNA_CONFIG_HOME_ENV_KEY);
7020
6976
  if (configRoot)
7021
- return join13(configRoot, name);
6977
+ return join9(configRoot, name);
7022
6978
  const root = hasnaHomeDir(env);
7023
- return root ? join13(root, name, CONFIG_SUBDIR) : null;
6979
+ return root ? join9(root, name, CONFIG_SUBDIR) : null;
7024
6980
  }
7025
6981
  function credentialDiskSourceList(name, env, profile = null) {
7026
6982
  if (!SAFE_APP_SLUG.test(name))
@@ -7029,7 +6985,7 @@ function credentialDiskSourceList(name, env, profile = null) {
7029
6985
  if (!directory)
7030
6986
  return [];
7031
6987
  const file = profile ? `${CREDENTIALS_FILE}-${profile}` : CREDENTIALS_FILE;
7032
- return [{ path: join13(directory, file), tier: "disk" }];
6988
+ return [{ path: join9(directory, file), tier: "disk" }];
7033
6989
  }
7034
6990
  function credentialDiskSources(name, env) {
7035
6991
  return credentialDiskSourceList(name, env, null).map((s) => s.path);
@@ -7104,7 +7060,7 @@ function readAppConfigFile(path) {
7104
7060
  unsafe("the file is not owned by the current user");
7105
7061
  if (before.size > MAX_CREDENTIAL_FILE_BYTES)
7106
7062
  unsafe("the file exceeds the size limit");
7107
- const bytes = readFileSync11(fd);
7063
+ const bytes = readFileSync7(fd);
7108
7064
  const after = fstatSync(fd);
7109
7065
  if (!configFileReadsCoherent(before, after)) {
7110
7066
  unsafe("the file changed while being read");
@@ -7742,12 +7698,39 @@ var init_instance_credentials = __esm(() => {
7742
7698
  init_transport();
7743
7699
  });
7744
7700
 
7701
+ // src/lib/local-opt-in.ts
7702
+ function isSkillsLocalOptIn(env = process.env) {
7703
+ return SKILLS_LOCAL_OPT_IN_ENV_KEYS.some((key) => (env[key] ?? "").trim() !== "");
7704
+ }
7705
+ function skillsAuthorityEnvKeys() {
7706
+ const keys = clientTransportEnvKeys("skills");
7707
+ return [
7708
+ ...keys.apiUrlKeys,
7709
+ ...keys.apiKeyKeys,
7710
+ credentialOverrideEnvKey("skills"),
7711
+ credentialPointerEnvKey("skills"),
7712
+ CREDENTIAL_PROFILE_ENV_KEY
7713
+ ];
7714
+ }
7715
+ function hasSkillsEnvAuthorityIntent(env = process.env) {
7716
+ return skillsAuthorityEnvKeys().some((key) => (env[key] ?? "").trim() !== "");
7717
+ }
7718
+ function selectsSkillsLocalMode(env = process.env) {
7719
+ return !hasSkillsEnvAuthorityIntent(env) && isSkillsLocalOptIn(env);
7720
+ }
7721
+ var SKILLS_LOCAL_OPT_IN_ENV_KEYS;
7722
+ var init_local_opt_in = __esm(() => {
7723
+ init_transport();
7724
+ SKILLS_LOCAL_OPT_IN_ENV_KEYS = ["HASNA_SKILLS_LOCAL", "SKILLS_LOCAL"];
7725
+ });
7726
+
7745
7727
  // src/lib/fleet-credentials.ts
7746
7728
  var exports_fleet_credentials = {};
7747
7729
  __export(exports_fleet_credentials, {
7748
7730
  skillsCredentialOrReason: () => skillsCredentialOrReason,
7749
7731
  skillsCredentialFiles: () => skillsCredentialFiles,
7750
7732
  skillsCredentialFilePath: () => skillsCredentialFilePath,
7733
+ selectsSkillsLocalMode: () => selectsSkillsLocalMode,
7751
7734
  resolveSkillsFleet: () => resolveSkillsFleet,
7752
7735
  resolveSkillsConnection: () => resolveSkillsConnection,
7753
7736
  resolveSkillsApiOrigin: () => resolveSkillsApiOrigin,
@@ -7758,8 +7741,11 @@ __export(exports_fleet_credentials, {
7758
7741
  requireSkillsApiKey: () => requireSkillsApiKey,
7759
7742
  noticeLocalSkillsMode: () => noticeLocalSkillsMode,
7760
7743
  normalizeSkillsApiOrigin: () => normalizeSkillsApiOrigin,
7744
+ isSkillsLocalOptIn: () => isSkillsLocalOptIn,
7745
+ isSkillsFleetCredentialError: () => isSkillsFleetCredentialError,
7761
7746
  configuredSkillsApiUrl: () => configuredSkillsApiUrl,
7762
7747
  SkillsFleetCredentialError: () => SkillsFleetCredentialError,
7748
+ SKILLS_LOCAL_OPT_IN_ENV_KEYS: () => SKILLS_LOCAL_OPT_IN_ENV_KEYS,
7763
7749
  SKILLS_APP: () => SKILLS_APP,
7764
7750
  SKILLS_API_URL_ENV_KEYS: () => SKILLS_API_URL_ENV_KEYS,
7765
7751
  SKILLS_API_URL_ENV: () => SKILLS_API_URL_ENV,
@@ -7770,6 +7756,9 @@ __export(exports_fleet_credentials, {
7770
7756
  function isCredentialResolutionError(error2) {
7771
7757
  return error2 instanceof CredentialResolutionError || typeof error2 === "object" && error2 !== null && error2.name === "CredentialResolutionError";
7772
7758
  }
7759
+ function isSkillsFleetCredentialError(error2) {
7760
+ return error2 instanceof SkillsFleetCredentialError || typeof error2 === "object" && error2 !== null && error2.name === "SkillsFleetCredentialError";
7761
+ }
7773
7762
  function asSkillsFleetCredentialError(error2) {
7774
7763
  if (!isCredentialResolutionError(error2))
7775
7764
  return null;
@@ -7835,7 +7824,7 @@ function noticeLocalSkillsMode(write = (line) => console.error(line)) {
7835
7824
  if (localNoticePrinted)
7836
7825
  return;
7837
7826
  localNoticePrinted = true;
7838
- write(`skills: local mode \u2014 no ${SKILLS_API_KEY_ENV} and no ${SKILLS_API_URL_ENV} resolved, ` + `so this runs on this machine against the bundled corpus. ` + `Sign in with: skills auth login`);
7827
+ write(`skills: local mode (${SKILLS_LOCAL_OPT_IN_ENV_KEYS[0]}=1) \u2014 running on this machine against the bundled corpus.`);
7839
7828
  }
7840
7829
  function resetLocalSkillsModeNotice() {
7841
7830
  localNoticePrinted = false;
@@ -7875,13 +7864,16 @@ function snapshotSkillsOptions(env, options) {
7875
7864
  } } };
7876
7865
  }
7877
7866
  function resolveSkillsFleetOrThrow(env, options) {
7867
+ if (selectsSkillsLocalMode(env))
7868
+ return { mode: "local", apiOrigin: null, apiKey: null };
7878
7869
  const assertFilesUnchanged = captureSkillsCredentialFiles(skillsProfileCredentialFiles(env, options.credentials?.profile));
7879
7870
  const configured = configuredSkillsApiUrl(env, options.credentials?.keychain, options.credentials?.profile);
7880
7871
  const credential = resolveCredential(SKILLS_APP, env, options.credentials);
7881
7872
  if (!credential) {
7882
- if (!configured)
7883
- return { mode: "local", apiOrigin: null, apiKey: null };
7884
- throw new SkillsFleetCredentialError(`${configured.source} points this CLI at a Skills service but no API key resolved \u2014 refusing to run locally instead. ` + `Looked in hasna.credentials.skills.api-key, ${skillsCredentialFiles(env).join(" or ") || "no credentials file"}, and ${SKILLS_API_KEY_ENV}. Sign in with: skills auth login`);
7873
+ if (!configured) {
7874
+ throw new SkillsFleetCredentialError(`No API key resolved and no Skills API URL is configured \u2014 failing closed ` + `(local mode is opt-in only: set ${SKILLS_LOCAL_OPT_IN_ENV_KEYS[0]}=1 to run on this machine). ` + `Looked in ${credentialLocations(env)}. Sign in with: skills auth login`);
7875
+ }
7876
+ throw new SkillsFleetCredentialError(`${configured.source} points this CLI at a Skills service but no API key resolved \u2014 refusing to run locally instead. ` + `Looked in ${credentialLocations(env)}. Sign in with: skills auth login`);
7885
7877
  }
7886
7878
  const apiOrigin = normalizeSkillsApiOrigin(configured?.value ?? defaultFleetGatewayBaseUrl(SKILLS_APP));
7887
7879
  toV1BaseUrl(apiOrigin);
@@ -7903,6 +7895,10 @@ function resolveSkillsFleetOrThrow(env, options) {
7903
7895
  }
7904
7896
  return { ...base, apiKey: credential.apiKey, apiKeyPointer: null };
7905
7897
  }
7898
+ function credentialLocations(env) {
7899
+ const files = skillsCredentialFiles(env).join(" or ") || "no credentials file (HOME is unset)";
7900
+ return `hasna.credentials.${SKILLS_APP}.api-key (macOS Keychain, account HASNA_STATION or the host name), ${files}, and ${SKILLS_API_KEY_ENV}`;
7901
+ }
7906
7902
  function assertCredentialInstance(credential, apiOrigin, env, options) {
7907
7903
  let bound;
7908
7904
  if (credential.tier === "disk" || credential.tier === "profile") {
@@ -7956,7 +7952,7 @@ async function skillsCredentialOrReason(env = process.env, options = {}) {
7956
7952
  const connection = await resolveSkillsConnection(env, options);
7957
7953
  return connection ? { apiKey: connection.apiKey, apiOrigin: connection.apiOrigin, reason: null } : { apiKey: null, apiOrigin: null, reason: null };
7958
7954
  } catch (error2) {
7959
- if (error2 instanceof SkillsFleetCredentialError || error2?.name === "SkillsFleetCredentialError") {
7955
+ if (isSkillsFleetCredentialError(error2)) {
7960
7956
  return { apiKey: null, apiOrigin: null, reason: error2.message };
7961
7957
  }
7962
7958
  throw error2;
@@ -7968,7 +7964,14 @@ function resolveSkillsApiOrigin(env = process.env, options = {}) {
7968
7964
  toV1BaseUrl(configured.value);
7969
7965
  return { origin: normalizeSkillsApiOrigin(configured.value), source: configured.source };
7970
7966
  }
7971
- const fleet = resolveSkillsFleet(env, options);
7967
+ let fleet;
7968
+ try {
7969
+ fleet = resolveSkillsFleet(env, options);
7970
+ } catch (error2) {
7971
+ if (isSkillsFleetCredentialError(error2))
7972
+ return null;
7973
+ throw error2;
7974
+ }
7972
7975
  return fleet.mode === "hosted" ? { origin: fleet.apiOrigin, source: fleet.apiUrlSource } : null;
7973
7976
  }
7974
7977
  function requireSkillsApiOrigin(action = "This command", env = process.env, options = {}) {
@@ -7987,6 +7990,8 @@ var SKILLS_APP = "skills", ENV_KEYS, SKILLS_API_URL_ENV_KEYS, SKILLS_API_KEY_ENV
7987
7990
  var init_fleet_credentials = __esm(() => {
7988
7991
  init_transport();
7989
7992
  init_instance_credentials();
7993
+ init_local_opt_in();
7994
+ init_local_opt_in();
7990
7995
  ENV_KEYS = clientTransportEnvKeys(SKILLS_APP);
7991
7996
  SKILLS_API_URL_ENV_KEYS = ENV_KEYS.apiUrlKeys;
7992
7997
  SKILLS_API_KEY_ENV_KEYS = ENV_KEYS.apiKeyKeys;
@@ -8009,6 +8014,73 @@ var init_fleet_credentials = __esm(() => {
8009
8014
  };
8010
8015
  });
8011
8016
 
8017
+ // src/lib/auth-store.ts
8018
+ import { chmodSync, existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync11, renameSync as renameSync2, statSync as statSync7, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
8019
+ function getAuthFilePath(env = process.env) {
8020
+ return skillsCredentialFilePath(env);
8021
+ }
8022
+ function getApiUrl(action, env = process.env, options = {}) {
8023
+ return requireSkillsApiOrigin(action, env, options);
8024
+ }
8025
+ function credentialFileMode(env = process.env) {
8026
+ try {
8027
+ return statSync7(skillsCredentialFilePath(env)).mode & 511;
8028
+ } catch {
8029
+ return null;
8030
+ }
8031
+ }
8032
+ var init_auth_store = __esm(() => {
8033
+ init_transport();
8034
+ init_instance_credentials();
8035
+ init_fleet_credentials();
8036
+ init_transport();
8037
+ init_fleet_credentials();
8038
+ });
8039
+
8040
+ // src/lib/remote-run-contract.ts
8041
+ function normalizeRemoteSkillRunContract(payload, fallbackSkill) {
8042
+ const record3 = isRecord3(payload) ? payload : {};
8043
+ return {
8044
+ contractVersion: REMOTE_SKILL_RUN_CONTRACT_VERSION,
8045
+ ...pickString(record3, "id"),
8046
+ skill: pickStringValue(record3, "skill") ?? fallbackSkill,
8047
+ ...pickString(record3, "requestedSlug"),
8048
+ ...pickString(record3, "status"),
8049
+ ...pickNumber(record3, "exitCode"),
8050
+ ...pickString(record3, "correlationId"),
8051
+ ...pickString(record3, "createdAt"),
8052
+ ...pickString(record3, "startedAt"),
8053
+ ...pickString(record3, "completedAt"),
8054
+ ...pickNumber(record3, "durationMs"),
8055
+ ...pickString(record3, "outputType"),
8056
+ ...hasOwn(record3, "outputPreview") ? { outputPreview: record3.outputPreview } : {},
8057
+ ...pickString(record3, "errorCode"),
8058
+ ...pickString(record3, "errorMessage"),
8059
+ ...pickString(record3, "error"),
8060
+ ...pickString(record3, "code"),
8061
+ ...hasOwn(record3, "details") ? { details: record3.details } : {}
8062
+ };
8063
+ }
8064
+ function isRecord3(value) {
8065
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
8066
+ }
8067
+ function hasOwn(record3, key) {
8068
+ return Object.prototype.hasOwnProperty.call(record3, key);
8069
+ }
8070
+ function pickString(record3, key) {
8071
+ const value = pickStringValue(record3, key);
8072
+ return value === undefined ? {} : { [key]: value };
8073
+ }
8074
+ function pickStringValue(record3, key) {
8075
+ const value = record3[key];
8076
+ return typeof value === "string" ? value : undefined;
8077
+ }
8078
+ function pickNumber(record3, key) {
8079
+ const value = record3[key];
8080
+ return typeof value === "number" && Number.isFinite(value) ? { [key]: value } : {};
8081
+ }
8082
+ var REMOTE_SKILL_RUN_CONTRACT_VERSION = 1;
8083
+
8012
8084
  // src/lib/blog-article.ts
8013
8085
  var exports_blog_article = {};
8014
8086
  __export(exports_blog_article, {
@@ -8223,16 +8295,104 @@ var init_remote_files = __esm(() => {
8223
8295
  MAX_REMOTE_FILE_BYTES = 64 * 1024 * 1024;
8224
8296
  });
8225
8297
 
8226
- // src/lib/auth-store.ts
8227
- function getApiUrl(action, env = process.env, options = {}) {
8228
- return requireSkillsApiOrigin(action, env, options);
8298
+ // src/lib/remote-workspace.ts
8299
+ function workspaceMembersQuery(options = {}) {
8300
+ if (!record3(options) || Object.keys(options).some((key) => key !== "limit" && key !== "cursor") || options.limit !== undefined && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 100) || options.cursor !== undefined && !cursor(options.cursor))
8301
+ throw new Error("Use a roster limit from 1 to 100 and an unchanged continuation cursor.");
8302
+ const query = new URLSearchParams;
8303
+ if (options.limit !== undefined)
8304
+ query.set("limit", String(options.limit));
8305
+ if (options.cursor !== undefined)
8306
+ query.set("cursor", options.cursor);
8307
+ return query.size ? `?${query}` : "";
8229
8308
  }
8230
- var init_auth_store = __esm(() => {
8231
- init_transport();
8232
- init_instance_credentials();
8233
- init_fleet_credentials();
8234
- init_transport();
8235
- init_fleet_credentials();
8309
+ function timestamp(value) {
8310
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$/.test(value))
8311
+ return false;
8312
+ const time3 = Date.parse(value);
8313
+ return Number.isFinite(time3) && new Date(time3).toISOString().slice(0, 23) === value.slice(0, 23);
8314
+ }
8315
+ function parseMember(row, fail) {
8316
+ if (!record3(row) || !uuid2(row.membershipId) || !uuid2(row.userId) || typeof row.email !== "string" || !row.email || !(row.displayName === null || typeof row.displayName === "string") || !isRole(row.role) || !timestamp(row.createdAt))
8317
+ return fail();
8318
+ return {
8319
+ membershipId: row.membershipId,
8320
+ userId: row.userId,
8321
+ email: row.email,
8322
+ displayName: row.displayName,
8323
+ role: row.role,
8324
+ createdAt: row.createdAt
8325
+ };
8326
+ }
8327
+ function mutationInput(membershipId, input, roleChange) {
8328
+ if (typeof membershipId !== "string" || !uuid2(membershipId) || membershipId !== membershipId.toLowerCase() || !record3(input) || Object.keys(input).sort().join(",") !== (roleChange ? "expectedRole,role" : "expectedRole"))
8329
+ throw new WorkspaceMemberInputError;
8330
+ const expectedRole = input.expectedRole, role = roleChange ? input.role : undefined;
8331
+ if (!isRole(expectedRole) || roleChange && !isRole(role))
8332
+ throw new WorkspaceMemberInputError;
8333
+ return { membershipId, role, expectedRole };
8334
+ }
8335
+ function workspaceMemberRoleInput(membershipId, input) {
8336
+ const value = mutationInput(membershipId, input, true);
8337
+ return { membershipId: value.membershipId, body: { role: value.role, expectedRole: value.expectedRole } };
8338
+ }
8339
+ function workspaceMemberRemovalInput(membershipId, input) {
8340
+ const value = mutationInput(membershipId, input, false);
8341
+ return { membershipId: value.membershipId, body: { expectedRole: value.expectedRole } };
8342
+ }
8343
+ function parseWorkspaceMemberRoleResult(value, membershipId, role) {
8344
+ const fail = () => {
8345
+ throw new Error(invalidMemberResult);
8346
+ };
8347
+ if (!record3(value) || !uuid2(value.organizationId) || typeof value.changed !== "boolean")
8348
+ return fail();
8349
+ const member = parseMember(value.member, fail);
8350
+ if (member.membershipId !== membershipId || member.role !== role)
8351
+ return fail();
8352
+ return { organizationId: value.organizationId, member, changed: value.changed };
8353
+ }
8354
+ function parseWorkspaceMemberRemovalResult(value, membershipId) {
8355
+ if (!record3(value) || !uuid2(value.organizationId) || value.membershipId !== membershipId || value.removed !== true || typeof value.alreadyRemoved !== "boolean")
8356
+ throw new Error(invalidMemberResult);
8357
+ return { organizationId: value.organizationId, membershipId, removed: true, alreadyRemoved: value.alreadyRemoved };
8358
+ }
8359
+ function workspaceMemberFailure(value, status) {
8360
+ if (!record3(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceMemberFailures, value.code))
8361
+ return null;
8362
+ const code = value.code;
8363
+ return workspaceMemberFailures[code][0] === status ? code : null;
8364
+ }
8365
+ function parseWorkspaceMembersPage(value) {
8366
+ const fail = () => {
8367
+ throw new Error("The server returned an invalid workspace roster.");
8368
+ };
8369
+ if (!record3(value) || !uuid2(value.organizationId) || !Array.isArray(value.members) || value.members.length > 100 || !(value.nextCursor === null || cursor(value.nextCursor)) || !value.members.length && value.nextCursor !== null)
8370
+ return fail();
8371
+ const members = value.members.map((row) => parseMember(row, fail));
8372
+ if (new Set(members.map((row) => row.membershipId)).size !== members.length)
8373
+ return fail();
8374
+ return { organizationId: value.organizationId, members, nextCursor: value.nextCursor };
8375
+ }
8376
+ var record3 = (value) => !!value && typeof value === "object" && !Array.isArray(value), cursor = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,512}$/.test(value), uuid2 = (value) => typeof value === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(value), isRole = (value) => typeof value === "string" && ["owner", "admin", "member", "viewer"].includes(value), WorkspaceMemberInputError, invalidMemberResult = "The server returned an invalid workspace member result. Refresh the roster before another action.", workspaceMemberFailures;
8377
+ var init_remote_workspace = __esm(() => {
8378
+ WorkspaceMemberInputError = class WorkspaceMemberInputError extends Error {
8379
+ constructor() {
8380
+ super("Use an unchanged lowercase membership ID and the exact role and expected-role parameters from the roster.");
8381
+ this.name = "WorkspaceMemberInputError";
8382
+ }
8383
+ };
8384
+ workspaceMemberFailures = {
8385
+ INVALID_REQUEST: [400, "Provide the exact membership role parameters."],
8386
+ ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
8387
+ INTERACTIVE_SESSION_REQUIRED: [403, "Fresh interactive sign-in is required."],
8388
+ WORKSPACE_ADMIN_REQUIRED: [403, "A current workspace owner or admin is required."],
8389
+ MEMBERSHIP_ACTION_FORBIDDEN: [403, "Your current workspace role cannot perform this membership action."],
8390
+ MEMBERSHIP_NOT_FOUND: [404, "Membership was not found in the current workspace."],
8391
+ SELF_REMOVAL_UNAVAILABLE: [409, "Leaving your own workspace is not available through member removal."],
8392
+ MEMBERSHIP_ROLE_CHANGED: [409, "The member role changed. Refresh the roster before another action."],
8393
+ LAST_OWNER_REQUIRED: [409, "The workspace must retain at least one active owner."],
8394
+ MEMBERSHIP_BUSY: [503, "Membership is busy. Refresh the roster before another action."]
8395
+ };
8236
8396
  });
8237
8397
 
8238
8398
  // src/lib/remote-account.ts
@@ -8314,14 +8474,47 @@ var init_remote_account = __esm(() => {
8314
8474
  };
8315
8475
  });
8316
8476
 
8477
+ // src/lib/remote-profile.ts
8478
+ function customerNamePatch(input, field) {
8479
+ if (!isRecord4(input) || Object.keys(input).length !== 1 || !Object.hasOwn(input, field))
8480
+ throw new Error("Provide only the requested name field.");
8481
+ const value = input[field];
8482
+ if (typeof value !== "string" || /[\p{Cc}\p{Cs}\u2028\u2029]/u.test(value) || !value.trim() || [...value.trim()].length > 100) {
8483
+ throw new Error("Use a name of 1\u2013100 characters without control characters or newlines.");
8484
+ }
8485
+ return { [field]: value.trim() };
8486
+ }
8487
+ function isRecord4(value) {
8488
+ return !!value && typeof value === "object" && !Array.isArray(value);
8489
+ }
8490
+ function string4(value) {
8491
+ return typeof value === "string" && value.length > 0;
8492
+ }
8493
+ function parseUpdatedProfile(value) {
8494
+ const user = isRecord4(value) && value.user;
8495
+ if (!isRecord4(user) || !string4(user.id) || !string4(user.email) || !(user.displayName === null || typeof user.displayName === "string") || typeof user.role !== "string" || !["owner", "admin", "member", "viewer"].includes(user.role)) {
8496
+ throw new Error("The server returned an invalid account profile.");
8497
+ }
8498
+ return { user: { id: user.id, email: user.email, displayName: user.displayName, role: user.role } };
8499
+ }
8500
+ function parseUpdatedWorkspace(value) {
8501
+ const organization = isRecord4(value) && value.organization;
8502
+ if (!isRecord4(organization) || !string4(organization.id) || !string4(organization.slug) || !string4(organization.name)) {
8503
+ throw new Error("The server returned an invalid workspace.");
8504
+ }
8505
+ return { organization: { id: organization.id, slug: organization.slug, name: organization.name } };
8506
+ }
8507
+
8317
8508
  // src/lib/remote-client.ts
8318
8509
  var exports_remote_client = {};
8319
8510
  __export(exports_remote_client, {
8320
8511
  createRemoteSkillsClientReadOnly: () => createRemoteSkillsClientReadOnly,
8321
8512
  createRemoteSkillsClient: () => createRemoteSkillsClient,
8513
+ RemoteWorkspaceMemberError: () => RemoteWorkspaceMemberError,
8322
8514
  RemoteSkillsClient: () => RemoteSkillsClient,
8323
8515
  RemoteRouteUnsupportedError: () => RemoteRouteUnsupportedError,
8324
- RemoteRequestError: () => RemoteRequestError
8516
+ RemoteRequestError: () => RemoteRequestError,
8517
+ RemoteCapabilityUnavailableError: () => RemoteCapabilityUnavailableError
8325
8518
  });
8326
8519
 
8327
8520
  class RemoteSkillsClient {
@@ -8351,9 +8544,14 @@ class RemoteSkillsClient {
8351
8544
  if (response.status === 404 && opts.domainNotFoundCodes?.length && await responseBodyCarriesCode(response, opts.domainNotFoundCodes)) {
8352
8545
  return response;
8353
8546
  }
8547
+ response.body?.cancel().catch(() => {});
8354
8548
  throw new RemoteRouteUnsupportedError(routePath, response.status, this.apiUrl);
8355
8549
  }
8356
8550
  if (!response.ok) {
8551
+ if (path === "/api/v1/billing/checkout" && options?.method === "POST" && response.status === 503 && await responseBodyCarriesCode(response, ["SUBSCRIPTION_CHECKOUT_UNAVAILABLE"])) {
8552
+ throw new RemoteCapabilityUnavailableError;
8553
+ }
8554
+ response.body?.cancel().catch(() => {});
8357
8555
  throw new RemoteRequestError(routePath, response.status, response.statusText);
8358
8556
  }
8359
8557
  return response;
@@ -8437,6 +8635,58 @@ class RemoteSkillsClient {
8437
8635
  async getIdentity() {
8438
8636
  return (await this.requestNewRoute("/api/auth/whoami")).json();
8439
8637
  }
8638
+ async updateProfile(input) {
8639
+ const body = customerNamePatch(input, "displayName");
8640
+ return parseUpdatedProfile(await (await this.requestNewRoute("/api/v1/account/profile", { method: "PATCH", body: JSON.stringify(body) })).json());
8641
+ }
8642
+ async updateCurrentWorkspace(input) {
8643
+ const body = customerNamePatch(input, "name");
8644
+ return parseUpdatedWorkspace(await (await this.requestNewRoute("/api/v1/workspaces/current", { method: "PATCH", body: JSON.stringify(body) })).json());
8645
+ }
8646
+ async listWorkspaceMembers(options = {}) {
8647
+ const query = workspaceMembersQuery(options);
8648
+ const requestedCursor = options.cursor;
8649
+ const response = await this.requestNewRoute(`/api/v1/workspace/members${query}`);
8650
+ let value;
8651
+ try {
8652
+ value = await response.json();
8653
+ } catch {
8654
+ throw new Error("The server returned an invalid workspace roster.");
8655
+ }
8656
+ const page = parseWorkspaceMembersPage(value);
8657
+ if (requestedCursor !== undefined && page.nextCursor === requestedCursor)
8658
+ throw new Error("The server returned an invalid workspace roster.");
8659
+ return page;
8660
+ }
8661
+ async setWorkspaceMemberRole(membershipId, input) {
8662
+ const captured = workspaceMemberRoleInput(membershipId, input);
8663
+ const value = await this.requestWorkspaceMember(captured.membershipId, "PATCH", captured.body);
8664
+ return parseWorkspaceMemberRoleResult(value, captured.membershipId, captured.body.role);
8665
+ }
8666
+ async removeWorkspaceMember(membershipId, input) {
8667
+ const captured = workspaceMemberRemovalInput(membershipId, input);
8668
+ return parseWorkspaceMemberRemovalResult(await this.requestWorkspaceMember(captured.membershipId, "DELETE", captured.body), captured.membershipId);
8669
+ }
8670
+ async requestWorkspaceMember(membershipId, method, body) {
8671
+ const path = `/api/v1/workspace/members/${membershipId}`;
8672
+ const response = await this.request(path, { method, body: JSON.stringify(body) });
8673
+ let value;
8674
+ try {
8675
+ value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, response.ok ? 64 * 1024 : 4096)));
8676
+ } catch {
8677
+ if (response.ok)
8678
+ throw new Error(invalidMemberResult);
8679
+ }
8680
+ if (!response.ok) {
8681
+ const code = workspaceMemberFailure(value, response.status);
8682
+ if (code)
8683
+ throw new RemoteWorkspaceMemberError(path, code);
8684
+ if (response.status === 404 || response.status === 405)
8685
+ throw new RemoteRouteUnsupportedError(path, response.status, this.apiUrl);
8686
+ throw new RemoteRequestError(path, response.status);
8687
+ }
8688
+ return value;
8689
+ }
8440
8690
  async listApiKeys() {
8441
8691
  return this.arrayResponse("/api/auth/keys");
8442
8692
  }
@@ -8628,8 +8878,10 @@ class RemoteSkillsClient {
8628
8878
  return [];
8629
8879
  if (!response.ok)
8630
8880
  throw new Error(`versions request failed: ${response.status}`);
8631
- const body = await response.json();
8632
- return Array.isArray(body.versions) ? body.versions : [];
8881
+ const body = await readSkillVersionPayload(response);
8882
+ if (!isVersionRecord(body) || !Array.isArray(body.versions) || body.slug !== undefined && body.slug !== slug)
8883
+ throw new Error(INVALID_SKILL_VERSION_RESPONSE);
8884
+ return body.versions.map((entry) => normalizeSkillVersion(entry, slug));
8633
8885
  }
8634
8886
  async getSkillVersion(slug, version2) {
8635
8887
  const response = await this.requestNewRoute(`/api/v1/skills/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version2)}`, undefined, { domainNotFoundCodes: ["SKILL_NOT_FOUND", "SKILL_VERSION_NOT_FOUND"] });
@@ -8637,7 +8889,7 @@ class RemoteSkillsClient {
8637
8889
  return null;
8638
8890
  if (!response.ok)
8639
8891
  throw new Error(`version request failed: ${response.status}`);
8640
- return await response.json();
8892
+ return normalizeSkillVersion(await readSkillVersionPayload(response), slug, version2);
8641
8893
  }
8642
8894
  async listPins() {
8643
8895
  const response = await this.requestNewRoute("/api/v1/pins");
@@ -8685,31 +8937,47 @@ class RemoteSkillsClient {
8685
8937
  return normalizeUpdatedSincePage(await response.json());
8686
8938
  }
8687
8939
  }
8688
- function requireOptionalString(record3, field) {
8689
- if (record3[field] === undefined)
8940
+ function requireOptionalString(record4, field) {
8941
+ if (record4[field] === undefined)
8690
8942
  return;
8691
- if (typeof record3[field] !== "string") {
8943
+ if (typeof record4[field] !== "string") {
8692
8944
  throw new Error(`Remote payload did not match the expected contract (${field} must be a string when present)`);
8693
8945
  }
8694
- return record3[field];
8946
+ return record4[field];
8947
+ }
8948
+ function isVersionRecord(value) {
8949
+ return value !== null && typeof value === "object" && !Array.isArray(value);
8950
+ }
8951
+ async function readSkillVersionPayload(response) {
8952
+ try {
8953
+ return await response.json();
8954
+ } catch {
8955
+ throw new Error(INVALID_SKILL_VERSION_RESPONSE);
8956
+ }
8957
+ }
8958
+ function normalizeSkillVersion(entry, slug, version2) {
8959
+ if (!isVersionRecord(entry) || typeof entry.slug !== "string" || !entry.slug.trim() || entry.slug !== slug || typeof entry.version !== "string" || !entry.version.trim() || version2 !== undefined && entry.version !== version2 || typeof entry.bundleSha256 !== "string" || !/^[a-f0-9]{64}$/i.test(entry.bundleSha256) || typeof entry.bundleByteSize !== "number" || !Number.isSafeInteger(entry.bundleByteSize) || entry.bundleByteSize < 0 || typeof entry.createdAt !== "string" || !entry.createdAt.trim() || entry.current !== undefined && typeof entry.current !== "boolean" || entry.storageKind !== undefined && typeof entry.storageKind !== "string" || entry.manifest !== undefined && !isVersionRecord(entry.manifest)) {
8960
+ throw new Error(INVALID_SKILL_VERSION_RESPONSE);
8961
+ }
8962
+ return entry;
8695
8963
  }
8696
8964
  function normalizePin(entry) {
8697
8965
  if (!entry || typeof entry !== "object") {
8698
8966
  throw new Error("Remote pin payload did not match the expected contract (expected an object)");
8699
8967
  }
8700
- const record3 = entry;
8701
- const slug = typeof record3.slug === "string" && record3.slug.trim() ? record3.slug.trim() : undefined;
8968
+ const record4 = entry;
8969
+ const slug = typeof record4.slug === "string" && record4.slug.trim() ? record4.slug.trim() : undefined;
8702
8970
  if (!slug) {
8703
8971
  throw new Error("Remote pin payload did not match the expected contract (missing slug)");
8704
8972
  }
8705
8973
  let metadata;
8706
- if (record3.metadata !== undefined) {
8707
- if (!record3.metadata || typeof record3.metadata !== "object" || Array.isArray(record3.metadata)) {
8974
+ if (record4.metadata !== undefined) {
8975
+ if (!record4.metadata || typeof record4.metadata !== "object" || Array.isArray(record4.metadata)) {
8708
8976
  throw new Error("Remote pin payload did not match the expected contract (metadata must be a JSON object when present)");
8709
8977
  }
8710
- metadata = record3.metadata;
8978
+ metadata = record4.metadata;
8711
8979
  }
8712
- const pinnedAt = requireOptionalString(record3, "pinnedAt");
8980
+ const pinnedAt = requireOptionalString(record4, "pinnedAt");
8713
8981
  return {
8714
8982
  slug,
8715
8983
  ...pinnedAt !== undefined ? { pinnedAt } : {},
@@ -8726,16 +8994,16 @@ function normalizeSkillSummary(entry) {
8726
8994
  if (!entry || typeof entry !== "object") {
8727
8995
  throw new Error("Remote skill payload did not match the expected contract (expected an object)");
8728
8996
  }
8729
- const record3 = entry;
8730
- const slug = typeof record3.slug === "string" && record3.slug.trim() ? record3.slug.trim() : undefined;
8997
+ const record4 = entry;
8998
+ const slug = typeof record4.slug === "string" && record4.slug.trim() ? record4.slug.trim() : undefined;
8731
8999
  if (!slug) {
8732
9000
  throw new Error("Remote skill payload did not match the expected contract (missing slug)");
8733
9001
  }
8734
9002
  return {
8735
9003
  slug,
8736
- ...requireOptionalString(record3, "name") !== undefined ? { name: requireOptionalString(record3, "name") } : {},
8737
- ...requireOptionalString(record3, "version") !== undefined ? { version: requireOptionalString(record3, "version") } : {},
8738
- ...requireOptionalString(record3, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record3, "updatedAt") } : {}
9004
+ ...requireOptionalString(record4, "name") !== undefined ? { name: requireOptionalString(record4, "name") } : {},
9005
+ ...requireOptionalString(record4, "version") !== undefined ? { version: requireOptionalString(record4, "version") } : {},
9006
+ ...requireOptionalString(record4, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record4, "updatedAt") } : {}
8739
9007
  };
8740
9008
  }
8741
9009
  function normalizeSkillSummaryList(payload) {
@@ -8745,26 +9013,55 @@ function normalizeSkillSummaryList(payload) {
8745
9013
  return payload.map(normalizeSkillSummary);
8746
9014
  }
8747
9015
  async function responseBodyCarriesCode(response, codes) {
9016
+ const reader = response.body?.getReader();
9017
+ if (!reader)
9018
+ return false;
9019
+ const maximum = 8 * 1024;
9020
+ let deadline;
9021
+ const expired = new Promise((_, reject) => {
9022
+ deadline = setTimeout(() => reject(new Error("Error response read deadline exceeded")), 1000);
9023
+ });
8748
9024
  try {
8749
- const payload = await response.clone().json();
8750
- if (!payload || typeof payload !== "object")
9025
+ const chunks = [];
9026
+ let size = 0;
9027
+ while (true) {
9028
+ const next = await Promise.race([reader.read(), expired]);
9029
+ if (next.done)
9030
+ break;
9031
+ size += next.value.byteLength;
9032
+ if (size > maximum)
9033
+ return false;
9034
+ chunks.push(next.value);
9035
+ }
9036
+ const bytes = new Uint8Array(size);
9037
+ let offset = 0;
9038
+ for (const chunk of chunks) {
9039
+ bytes.set(chunk, offset);
9040
+ offset += chunk.byteLength;
9041
+ }
9042
+ const payload = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
9043
+ if (!payload || typeof payload !== "object" || Array.isArray(payload) || !Object.hasOwn(payload, "code"))
8751
9044
  return false;
8752
9045
  const code = payload.code;
8753
9046
  return typeof code === "string" && codes.includes(code);
8754
9047
  } catch {
8755
9048
  return false;
9049
+ } finally {
9050
+ clearTimeout(deadline);
9051
+ reader.cancel().catch(() => {});
9052
+ reader.releaseLock();
8756
9053
  }
8757
9054
  }
8758
9055
  function normalizeUpdatedSincePage(payload) {
8759
9056
  if (!payload || typeof payload !== "object") {
8760
9057
  throw new Error("Updated-since payload did not match the expected contract (expected an object)");
8761
9058
  }
8762
- const record3 = payload;
8763
- if (!Array.isArray(record3.skills)) {
9059
+ const record4 = payload;
9060
+ if (!Array.isArray(record4.skills)) {
8764
9061
  throw new Error("Updated-since payload did not match the expected contract (missing skills array)");
8765
9062
  }
8766
- const skills = record3.skills.map(normalizeSkillSummary);
8767
- const nextCursor = record3.nextCursor === undefined || record3.nextCursor === null ? null : record3.nextCursor;
9063
+ const skills = record4.skills.map(normalizeSkillSummary);
9064
+ const nextCursor = record4.nextCursor === undefined || record4.nextCursor === null ? null : record4.nextCursor;
8768
9065
  if (nextCursor !== null && typeof nextCursor !== "string") {
8769
9066
  throw new Error("Updated-since payload did not match the expected contract (nextCursor must be a string or absent)");
8770
9067
  }
@@ -8777,8 +9074,10 @@ async function createRemoteSkillsClient(env = process.env) {
8777
9074
  function createRemoteSkillsClientReadOnly(env = process.env) {
8778
9075
  return createRemoteSkillsClient(env);
8779
9076
  }
8780
- var RemoteRouteUnsupportedError, RemoteRequestError;
9077
+ var RemoteRouteUnsupportedError, RemoteRequestError, RemoteWorkspaceMemberError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
8781
9078
  var init_remote_client = __esm(() => {
9079
+ init_remote_workspace();
9080
+ init_remote_workspace();
8782
9081
  init_auth_store();
8783
9082
  init_fleet_credentials();
8784
9083
  init_remote_account();
@@ -8798,13 +9097,30 @@ var init_remote_client = __esm(() => {
8798
9097
  RemoteRequestError = class RemoteRequestError extends Error {
8799
9098
  path;
8800
9099
  status;
8801
- constructor(path, status, statusText) {
8802
- super(`Remote request to ${path} failed: HTTP ${status}${statusText ? ` ${statusText}` : ""}`);
9100
+ constructor(path, status, _statusText) {
9101
+ super(`Remote request to ${path} failed: HTTP ${status}`);
8803
9102
  this.path = path;
8804
9103
  this.status = status;
8805
9104
  this.name = "RemoteRequestError";
8806
9105
  }
8807
9106
  };
9107
+ RemoteWorkspaceMemberError = class RemoteWorkspaceMemberError extends RemoteRequestError {
9108
+ code;
9109
+ constructor(path, code) {
9110
+ super(path, workspaceMemberFailures[code][0]);
9111
+ this.code = code;
9112
+ this.name = "RemoteWorkspaceMemberError";
9113
+ this.message = workspaceMemberFailures[code][1];
9114
+ }
9115
+ };
9116
+ RemoteCapabilityUnavailableError = class RemoteCapabilityUnavailableError extends RemoteRequestError {
9117
+ code = "SUBSCRIPTION_CHECKOUT_UNAVAILABLE";
9118
+ constructor() {
9119
+ super("/api/v1/billing/checkout", 503);
9120
+ this.name = "RemoteCapabilityUnavailableError";
9121
+ this.message = "Subscription checkout is unavailable on the configured Skills server. " + "Use skills credits packs to view credit packs, or skills billing portal to manage an existing subscription.";
9122
+ }
9123
+ };
8808
9124
  });
8809
9125
 
8810
9126
  // ../../node_modules/.bun/content-type@1.0.5/node_modules/content-type/index.js
@@ -8831,7 +9147,7 @@ var require_content_type = __commonJS((exports) => {
8831
9147
  if (!type || !TYPE_REGEXP.test(type)) {
8832
9148
  throw new TypeError("invalid type");
8833
9149
  }
8834
- var string4 = type;
9150
+ var string5 = type;
8835
9151
  if (parameters && typeof parameters === "object") {
8836
9152
  var param;
8837
9153
  var params = Object.keys(parameters).sort();
@@ -8840,16 +9156,16 @@ var require_content_type = __commonJS((exports) => {
8840
9156
  if (!TOKEN_REGEXP.test(param)) {
8841
9157
  throw new TypeError("invalid parameter name");
8842
9158
  }
8843
- string4 += "; " + param + "=" + qstring(parameters[param]);
9159
+ string5 += "; " + param + "=" + qstring(parameters[param]);
8844
9160
  }
8845
9161
  }
8846
- return string4;
9162
+ return string5;
8847
9163
  }
8848
- function parse6(string4) {
8849
- if (!string4) {
9164
+ function parse6(string5) {
9165
+ if (!string5) {
8850
9166
  throw new TypeError("argument string is required");
8851
9167
  }
8852
- var header = typeof string4 === "object" ? getcontenttype(string4) : string4;
9168
+ var header = typeof string5 === "object" ? getcontenttype(string5) : string5;
8853
9169
  if (typeof header !== "string") {
8854
9170
  throw new TypeError("argument string is required to be a string");
8855
9171
  }
@@ -14323,7 +14639,7 @@ class StdioServerTransport {
14323
14639
  // package.json
14324
14640
  var package_default = {
14325
14641
  name: "@hasna/skills",
14326
- version: "0.4.0",
14642
+ version: "0.5.0",
14327
14643
  description: "Skills library for AI coding agents",
14328
14644
  type: "module",
14329
14645
  bin: {
@@ -14355,6 +14671,7 @@ var package_default = {
14355
14671
  files: [
14356
14672
  "dist/",
14357
14673
  "!dist/**/*.test.d.ts",
14674
+ "!dist/**/*.fixture.d.ts",
14358
14675
  "!dist/test-preload.d.ts",
14359
14676
  "!dist/platform",
14360
14677
  "bin/",
@@ -14380,8 +14697,9 @@ var package_default = {
14380
14697
  migrate: "bun run ./src/server/migrate.ts",
14381
14698
  typecheck: "tsc --noEmit",
14382
14699
  "verify:release": "bun run scripts/release-guard.ts",
14700
+ "verify:consumer-types": "bun run scripts/consumer-types.ts",
14383
14701
  prepare: "bun run build:js",
14384
- prepack: "bun run build && bun run verify:release",
14702
+ prepack: "bun run build && bun run verify:release && bun run verify:consumer-types",
14385
14703
  prepublishOnly: "bun run typecheck && bun run test"
14386
14704
  },
14387
14705
  keywords: [
@@ -14403,6 +14721,7 @@ var package_default = {
14403
14721
  author: "Hasna",
14404
14722
  license: "Apache-2.0",
14405
14723
  devDependencies: {
14724
+ "@hasna/contracts": "1.0.2",
14406
14725
  "@types/bun": "1.3.14",
14407
14726
  "@types/node": "25.2.3",
14408
14727
  "@types/react": "^18.2.0",
@@ -14414,8 +14733,7 @@ var package_default = {
14414
14733
  dependencies: {
14415
14734
  "@aws-sdk/client-ecs": "^3.1079.0",
14416
14735
  "@aws-sdk/client-s3": "^3.1079.0",
14417
- "@hasna/contracts": "1.0.1",
14418
- "@hasna/events": "0.1.16",
14736
+ "@hasna/events": "0.1.18",
14419
14737
  "@modelcontextprotocol/sdk": "^1.26.0",
14420
14738
  chalk: "^5.3.0",
14421
14739
  commander: "^12.1.0",
@@ -22331,12 +22649,7 @@ import { homedir } from "os";
22331
22649
  import { join, resolve } from "path";
22332
22650
  import { homedir as pathsResolverHomedir } from "os";
22333
22651
  import { join as pathsResolverJoin } from "path";
22334
- var PATHS_RESOLVER_KIND_ENV = {
22335
- config: "HASNA_CONFIG_HOME",
22336
- data: "HASNA_DATA_HOME",
22337
- state: "HASNA_STATE_HOME",
22338
- cache: "HASNA_CACHE_HOME"
22339
- };
22652
+ var PATHS_RESOLVER_DATA_ENV = "HASNA_DATA_HOME";
22340
22653
  var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
22341
22654
  function pathsResolverAssertApp(app) {
22342
22655
  if (typeof app !== "string" || app.length === 0) {
@@ -22346,48 +22659,19 @@ function pathsResolverAssertApp(app) {
22346
22659
  throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
22347
22660
  }
22348
22661
  }
22349
- function pathsResolverAssertKind(kind) {
22350
- if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
22351
- throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
22352
- }
22353
- }
22354
- function pathsResolverBaseDir(kind, options) {
22355
- pathsResolverAssertKind(kind);
22662
+ function pathsResolverDataBaseDir(options) {
22356
22663
  const env = options.env ?? process.env;
22357
- const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
22664
+ const override = env[PATHS_RESOLVER_DATA_ENV];
22358
22665
  if (typeof override === "string" && override.length > 0)
22359
22666
  return override;
22360
22667
  const home = options.home ?? pathsResolverHomedir();
22361
22668
  const platform = options.platform ?? process.platform;
22362
- if (platform === "darwin") {
22363
- switch (kind) {
22364
- case "config":
22365
- case "data":
22366
- return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
22367
- case "cache":
22368
- return pathsResolverJoin(home, "Library", "Caches", "Hasna");
22369
- case "state":
22370
- return pathsResolverJoin(home, "Library", "Logs", "Hasna");
22371
- }
22372
- }
22373
- switch (kind) {
22374
- case "config":
22375
- return pathsResolverJoin(home, ".config", "hasna");
22376
- case "data":
22377
- return pathsResolverJoin(home, ".local", "share", "hasna");
22378
- case "state":
22379
- return pathsResolverJoin(home, ".local", "state", "hasna");
22380
- case "cache":
22381
- return pathsResolverJoin(home, ".cache", "hasna");
22382
- }
22383
- }
22384
- function pathsResolverResolve(kind, options) {
22385
- pathsResolverAssertApp(options.app);
22386
- const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
22387
- return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
22669
+ return platform === "darwin" ? pathsResolverJoin(home, "Library", "Application Support", "Hasna") : pathsResolverJoin(home, ".local", "share", "hasna");
22388
22670
  }
22389
22671
  function dataDir(options) {
22390
- return pathsResolverResolve("data", options);
22672
+ pathsResolverAssertApp(options.app);
22673
+ const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
22674
+ return pathsResolverJoin(pathsResolverDataBaseDir(options), appSegment);
22391
22675
  }
22392
22676
  var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
22393
22677
  var HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME";
@@ -23967,14 +24251,27 @@ function normalizePortableSkillName(name) {
23967
24251
  }
23968
24252
  return normalized;
23969
24253
  }
24254
+ function normalizeNewPortableSkillName(name) {
24255
+ normalizePortableSkillName(name);
24256
+ const normalized = name.trim().replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
24257
+ if (!normalized)
24258
+ throw new Error(`Invalid skill name '${name}'. Include letters or numbers.`);
24259
+ return normalized;
24260
+ }
23970
24261
  function readPortableSkillManifest(skillPath, fallbackName = basename(skillPath)) {
24262
+ return readManifest(skillPath, fallbackName, normalizePortableSkillName);
24263
+ }
24264
+ function readPortableSkillManifestForImport(skillPath) {
24265
+ return readManifest(skillPath, basename(skillPath), normalizeNewPortableSkillName);
24266
+ }
24267
+ function readManifest(skillPath, fallbackName, normalizeName) {
23971
24268
  const skillJsonPath = join6(skillPath, "skill.json");
23972
24269
  const skillMdPath = join6(skillPath, "SKILL.md");
23973
24270
  const pkgPath = join6(skillPath, "package.json");
23974
24271
  const jsonManifest = existsSync6(skillJsonPath) ? readJsonObject(skillJsonPath) : undefined;
23975
24272
  const frontmatter = existsSync6(skillMdPath) ? parseSkillFrontmatter(readFileSync5(skillMdPath, "utf-8")) ?? undefined : undefined;
23976
24273
  const pkg = existsSync6(pkgPath) ? readJsonObject(pkgPath) : undefined;
23977
- const name = normalizePortableSkillName(stringField(jsonManifest, "name") ?? frontmatter?.name ?? stringValue(pkg?.name) ?? fallbackName);
24274
+ const name = normalizeName(stringField(jsonManifest, "name") ?? frontmatter?.name ?? stringValue(pkg?.name) ?? fallbackName);
23978
24275
  const description = stringField(jsonManifest, "description") ?? frontmatter?.description ?? stringValue(pkg?.description) ?? `${name} skill`;
23979
24276
  const version2 = readDeclaredSkillVersion(skillPath) ?? PORTABLE_SKILL_DEFAULT_VERSION;
23980
24277
  const kind = parseSkillKind(stringField(jsonManifest, "kind") ?? frontmatter?.kind);
@@ -24222,10 +24519,45 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
24222
24519
  };
24223
24520
  if (!existsSync6(join6(skillPath, "SKILL.md"))) {
24224
24521
  writeFileSync2(join6(skillPath, "SKILL.md"), renderSkillMd(next));
24522
+ } else {
24523
+ const path = join6(skillPath, "SKILL.md");
24524
+ const content = readFileSync5(path, "utf8");
24525
+ const declaredName = parseSkillFrontmatter(content)?.name;
24526
+ if (declaredName && declaredName !== next.name) {
24527
+ writeFileSync2(path, renameInstructionFrontmatter(content, next.name));
24528
+ }
24529
+ }
24530
+ const packagePath = join6(skillPath, "package.json");
24531
+ if (existsSync6(packagePath)) {
24532
+ const pkg = readJsonObject(packagePath);
24533
+ if (typeof pkg.name === "string" && pkg.name !== next.name) {
24534
+ writeFileSync2(packagePath, `${JSON.stringify({ ...pkg, name: next.name }, null, 2)}
24535
+ `);
24536
+ }
24225
24537
  }
24226
24538
  writeSkillJsonWithHash(skillPath, next);
24227
24539
  return readPortableSkillManifest(skillPath, next.name);
24228
24540
  }
24541
+ function renameInstructionFrontmatter(content, name) {
24542
+ const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?=\r?\n|$)/);
24543
+ const names = frontmatter?.[1]?.match(/^[ \t]*name[ \t]*:[^\r\n]*/gm) ?? [];
24544
+ const declaration = names.length === 1 ? names[0].match(/^(name[ \t]*:[ \t]*)(.*?)([ \t]*)$/) : null;
24545
+ const scalar = declaration?.[2] ?? "";
24546
+ let simple = /^[a-zA-Z0-9_.@/ -]+$/.test(scalar);
24547
+ if (scalar.startsWith('"')) {
24548
+ try {
24549
+ simple = typeof JSON.parse(scalar) === "string";
24550
+ } catch {
24551
+ simple = false;
24552
+ }
24553
+ } else if (scalar.startsWith("'"))
24554
+ simple = /^'[^'\r\n]*'$/.test(scalar);
24555
+ if (!frontmatter || !declaration || !simple) {
24556
+ throw new Error("Cannot rename instruction SKILL.md: use one unambiguous top-level name scalar in frontmatter.");
24557
+ }
24558
+ const renamed = frontmatter[0].replace(/^name[ \t]*:[^\r\n]*/m, () => `${declaration[1]}${name}${declaration[3]}`);
24559
+ return renamed + content.slice(frontmatter[0].length);
24560
+ }
24229
24561
  function copySkillDirectory(source, destination) {
24230
24562
  const resolvedSource = lstatSync2(source).isSymbolicLink() ? realpathSync(source) : source;
24231
24563
  mkdirSync2(destination, { recursive: true });
@@ -24624,7 +24956,7 @@ function isOfficialSkillName(name) {
24624
24956
  return OFFICIAL_SKILL_NAMES.has(name);
24625
24957
  }
24626
24958
  function scaffoldPortableSkill(name, options = {}) {
24627
- const skillName = normalizePortableSkillName(name);
24959
+ const skillName = normalizeNewPortableSkillName(name);
24628
24960
  const root = getPortableSkillsRoot(options);
24629
24961
  const skillPath = join7(root, skillName);
24630
24962
  if (existsSync7(skillPath)) {
@@ -24648,9 +24980,9 @@ function portPortableSkill(sourcePath, options = {}) {
24648
24980
  if (!existsSync7(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
24649
24981
  throw new Error(`Skill source directory not found: ${sourcePath}`);
24650
24982
  }
24651
- const inferred = readPortableSkillManifest(absoluteSource, basename2(absoluteSource));
24983
+ const inferred = readPortableSkillManifestForImport(absoluteSource);
24652
24984
  const explicitName = options.name != null;
24653
- const skillName = normalizePortableSkillName(options.name ?? inferred.name);
24985
+ const skillName = normalizeNewPortableSkillName(options.name ?? inferred.name);
24654
24986
  if (isOfficialSkillName(skillName) && !options.allowShadow) {
24655
24987
  const sourceSlug = safeNormalizeName(basename2(absoluteSource));
24656
24988
  const via = explicitName ? `Name '${skillName}' matches a bundled official skill.` : `Inferred name '${skillName}'${sourceSlug && sourceSlug !== skillName ? ` (from source folder '${basename2(absoluteSource)}')` : ""} matches a bundled official skill.`;
@@ -25131,44 +25463,321 @@ function mergeCustomSkills(skills) {
25131
25463
  return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
25132
25464
  }
25133
25465
 
25134
- // src/lib/installer.ts
25135
- import { existsSync as existsSync10, readFileSync as readFileSync8, rmSync as rmSync2 } from "fs";
25136
- import { dirname as dirname4, join as join10 } from "path";
25137
- import { homedir as homedir2 } from "os";
25138
- import { fileURLToPath } from "url";
25139
- // src/lib/utils.ts
25140
- function normalizeSkillName(name) {
25141
- return name;
25142
- }
25466
+ // src/lib/read-access.ts
25467
+ init_fleet_credentials();
25143
25468
 
25144
- // src/lib/project-state.ts
25145
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
25146
- import { join as join9 } from "path";
25147
- var VALID_PIN_SOURCES = [
25148
- "official",
25149
- "custom",
25150
- "remote",
25151
- "private",
25152
- "private-hosted",
25153
- "upstream",
25154
- "extension",
25155
- "local"
25156
- ];
25157
- var SKILLS_PROJECT_DIR = ".skills";
25158
- var PROJECT_CONFIG_FILE = "project.json";
25159
- var DEFAULT_EXPORT_DIR = ".skills/exports";
25160
- function getProjectStateDir(targetDir = process.cwd()) {
25161
- return join9(targetDir, SKILLS_PROJECT_DIR);
25162
- }
25163
- function getProjectConfigPath(targetDir = process.cwd()) {
25164
- return join9(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
25469
+ // src/lib/api-url.ts
25470
+ init_fleet_credentials();
25471
+ init_fleet_credentials();
25472
+ var API_URL_ENV_VAR = SKILLS_API_URL_ENV;
25473
+ var MISSING_API_URL_HINT = `run: skills auth login, or set ${API_URL_ENV_VAR}=<your Skills instance origin>, ` + `or run: skills setup --api-url <your Skills instance origin>`;
25474
+ function resolveApiUrl(env = process.env, options = {}) {
25475
+ const fleet = resolveSkillsFleet(env, options);
25476
+ return fleet.mode === "hosted" ? fleet.apiOrigin : undefined;
25165
25477
  }
25166
- function loadProjectConfig(targetDir = process.cwd()) {
25167
- const path = getProjectConfigPath(targetDir);
25168
- if (!existsSync9(path))
25169
- return null;
25478
+
25479
+ // src/lib/remote-registry.ts
25480
+ init_fleet_credentials();
25481
+
25482
+ // src/lib/discovery.ts
25483
+ var VENDOR_TERMS = [
25484
+ "Google Gemini",
25485
+ "OpenAI Sora",
25486
+ "MiniMax Hailuo",
25487
+ "Claude Code",
25488
+ "Claude Vision",
25489
+ "DALL-E 3",
25490
+ "GPT-4o Mini",
25491
+ "Cerebras",
25492
+ "OpenRouter",
25493
+ "Firecrawl",
25494
+ "ElevenLabs",
25495
+ "Anthropic",
25496
+ "OpenAI",
25497
+ "Minimax",
25498
+ "MiniMax",
25499
+ "Gemini",
25500
+ "Claude",
25501
+ "Whisper",
25502
+ "Seedance",
25503
+ "Lyria",
25504
+ "Sora",
25505
+ "Veo",
25506
+ "Exa.ai",
25507
+ "Exa",
25508
+ "XAI",
25509
+ "xAI"
25510
+ ];
25511
+ var VENDOR_TAGS = new Set([
25512
+ "anthropic",
25513
+ "cerebras",
25514
+ "claude",
25515
+ "exa",
25516
+ "firecrawl",
25517
+ "gemini",
25518
+ "google",
25519
+ "minimax",
25520
+ "openai",
25521
+ "openrouter",
25522
+ "seedance",
25523
+ "whisper",
25524
+ "xai"
25525
+ ]);
25526
+ var vendorPattern = new RegExp(`\\b(${VENDOR_TERMS.map(escapeRegExp).join("|")})\\b`, "gi");
25527
+ function getCompactSkillDiscovery(skill) {
25528
+ return {
25529
+ name: skill.name,
25530
+ category: skill.category,
25531
+ description: sanitizePublicDiscoveryText(skill.description)
25532
+ };
25533
+ }
25534
+ function getPublicSkillDiscovery(skill) {
25535
+ return {
25536
+ ...skill,
25537
+ description: sanitizePublicDiscoveryText(skill.description),
25538
+ tags: publicDiscoveryTags(skill.tags)
25539
+ };
25540
+ }
25541
+ function publicDiscoveryTags(tags) {
25542
+ return tags.filter((tag) => !VENDOR_TAGS.has(tag.toLowerCase()));
25543
+ }
25544
+ function sanitizePublicDiscoveryText(text) {
25545
+ let sanitized = text.replace(vendorPattern, "hosted AI").replace(/\bLLM\b/g, "AI").replace(/\s{2,}/g, " ");
25546
+ let previous;
25547
+ do {
25548
+ previous = sanitized;
25549
+ sanitized = sanitized.replace(/\bhosted AI(?: providers)?\s*,\s*hosted AI(?: providers)?\b/gi, "hosted AI providers").replace(/\bhosted AI(?: providers)?\s*,?\s*and\s*hosted AI(?: providers)?\b/gi, "hosted AI providers").replace(/\bhosted AI(?: providers)?\s+or\s+hosted AI(?: providers)?\b/gi, "hosted AI providers").replace(/\bhosted AI providers\s+hosted AI\b/gi, "hosted AI providers").replace(/\bhosted AI providers\s+providers\b/gi, "hosted AI providers");
25550
+ } while (sanitized !== previous);
25551
+ return sanitized.trim();
25552
+ }
25553
+ function publicDiscoveryEnvVars(_skillName, envVars) {
25554
+ return envVars;
25555
+ }
25556
+ function publicDiscoveryDependencies(_skillName, dependencies) {
25557
+ return dependencies;
25558
+ }
25559
+ function publicDiscoveryDocumentation(_skill, documentation) {
25560
+ return documentation;
25561
+ }
25562
+ function escapeRegExp(value) {
25563
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25564
+ }
25565
+
25566
+ // src/lib/remote-registry.ts
25567
+ var remoteAvailabilitySchema = exports_external.object({
25568
+ status: exports_external.enum(["available", "unavailable"]),
25569
+ code: exports_external.string().optional(),
25570
+ message: exports_external.string().optional(),
25571
+ details: exports_external.array(exports_external.string()).optional()
25572
+ }).passthrough();
25573
+ var remoteSkillSchema = exports_external.object({
25574
+ name: exports_external.string().min(1).optional(),
25575
+ slug: exports_external.string().min(1).optional(),
25576
+ displayName: exports_external.string().optional(),
25577
+ description: exports_external.string().optional(),
25578
+ category: exports_external.string().optional(),
25579
+ tags: exports_external.array(exports_external.string()).optional(),
25580
+ dependencies: exports_external.array(exports_external.string()).optional(),
25581
+ version: exports_external.string().optional(),
25582
+ availability: remoteAvailabilitySchema.optional()
25583
+ }).passthrough().refine((skill) => skill.name || skill.slug, {
25584
+ message: "Remote skill requires name or slug"
25585
+ });
25586
+ var secretValuePatterns = [
25587
+ /\bsk-[A-Za-z0-9_-]{8,}\b/g,
25588
+ /\bgh[opsur]_[A-Za-z0-9_]{8,}\b/g,
25589
+ /\bgithub_pat_[A-Za-z0-9_]{8,}\b/g,
25590
+ /\bnpm_[A-Za-z0-9_]{8,}\b/g,
25591
+ /\bAKIA[A-Z0-9]{12,}\b/g,
25592
+ /\bAIza[A-Za-z0-9_-]{10,}\b/g,
25593
+ new RegExp("\\bsecret" + "-token:\\s*[A-Za-z0-9._-]+", "gi"),
25594
+ /\bctx7sk\-[A-Za-z0-9_-]{8,}\b/g,
25595
+ /\bxai\-[A-Za-z0-9_-]{8,}\b/g
25596
+ ];
25597
+ var remoteSkillDetailSchema = exports_external.union([
25598
+ remoteSkillSchema,
25599
+ exports_external.object({ skill: remoteSkillSchema }),
25600
+ exports_external.object({ data: remoteSkillSchema })
25601
+ ]);
25602
+ var remoteRegistrySchema = exports_external.union([
25603
+ exports_external.array(remoteSkillSchema),
25604
+ exports_external.object({ skills: exports_external.array(remoteSkillSchema) }),
25605
+ exports_external.object({ data: exports_external.array(remoteSkillSchema) })
25606
+ ]);
25607
+ function getConfiguredApiUrl(env = process.env) {
25608
+ return resolveApiUrl(env);
25609
+ }
25610
+ function buildSkillsApiUrl(apiUrl, endpoint = "/skills") {
25611
+ const url = new URL(apiUrl);
25612
+ const cleanEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
25613
+ const pathname = url.pathname.replace(/\/+$/, "");
25614
+ const apiBase = /\/api(?:\/v1)?\/skills$/.test(pathname) ? pathname.slice(0, -"/skills".length) : pathname;
25615
+ if (/\/api(?:\/v1)?$/.test(apiBase)) {
25616
+ url.pathname = `${apiBase}${cleanEndpoint}`;
25617
+ return url.toString();
25618
+ }
25619
+ url.pathname = `${apiBase}/api/v1${cleanEndpoint}`.replace(/\/{2,}/g, "/");
25620
+ return url.toString();
25621
+ }
25622
+ function titleize(name) {
25623
+ return name.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
25624
+ }
25625
+ function normalizeRemoteSkill(skill) {
25626
+ const name = skill.name || skill.slug;
25627
+ if (!name)
25628
+ throw new Error("Remote skill requires name or slug");
25629
+ return {
25630
+ name,
25631
+ displayName: skill.displayName || titleize(name),
25632
+ description: skill.description || "",
25633
+ category: skill.category || "Remote",
25634
+ tags: skill.tags || ["remote"],
25635
+ dependencies: skill.dependencies,
25636
+ ...skill.version ? { version: skill.version } : {},
25637
+ availability: normalizeRemoteAvailability(skill.availability),
25638
+ source: "remote"
25639
+ };
25640
+ }
25641
+ function normalizeRemoteAvailability(availability) {
25642
+ if (!availability)
25643
+ return { status: "available" };
25644
+ if (availability.status === "available")
25645
+ return { status: "available" };
25646
+ return {
25647
+ status: availability.status,
25648
+ ...safeAvailabilityCode(availability.code) ? { code: safeAvailabilityCode(availability.code) } : {},
25649
+ ...availability.message ? { message: sanitizeAvailabilityText(availability.message) } : {},
25650
+ ...availability.details ? { details: availability.details.map(sanitizeAvailabilityText).filter(Boolean) } : {}
25651
+ };
25652
+ }
25653
+ function safeAvailabilityCode(code) {
25654
+ if (!code)
25655
+ return;
25656
+ return /^[A-Z0-9_]+$/.test(code) ? code : undefined;
25657
+ }
25658
+ function sanitizeAvailabilityText(text) {
25659
+ return secretValuePatterns.reduce((value, pattern) => value.replace(pattern, "credential"), sanitizePublicDiscoveryText(text).replace(/\b[A-Z0-9_]*(?:API_KEY|SECRET|TOKEN|CREDENTIAL)[A-Z0-9_]*\b/g, "credential")).replace(/\s{2,}/g, " ").trim();
25660
+ }
25661
+ function parseRemoteRegistryPayload(payload) {
25662
+ const parsed = parseRemoteContract(remoteRegistrySchema, payload, "Remote registry payload did not match the expected skills contract");
25663
+ const rawSkills = Array.isArray(parsed) ? parsed : ("skills" in parsed) ? parsed.skills : parsed.data;
25664
+ return rawSkills.map(normalizeRemoteSkill);
25665
+ }
25666
+ function parseRemoteContract(schema, payload, message) {
25667
+ try {
25668
+ return schema.parse(payload);
25669
+ } catch (error2) {
25670
+ if (error2 instanceof exports_external.ZodError)
25671
+ throw new Error(message, { cause: error2 });
25672
+ throw error2;
25673
+ }
25674
+ }
25675
+ async function remoteRequestHeaders(options) {
25676
+ const headers = new Headers({ Accept: "application/json" });
25677
+ const token = options.authToken !== undefined ? options.authToken : await ambientTokenFor(options.apiUrl);
25678
+ const trimmed = token?.trim();
25679
+ if (trimmed)
25680
+ headers.set("Authorization", `Bearer ${trimmed}`);
25681
+ return headers;
25682
+ }
25683
+ async function ambientTokenFor(callerApiUrl) {
25684
+ const connection = await resolveSkillsConnection();
25685
+ if (!connection)
25686
+ return null;
25687
+ if (callerApiUrl !== undefined && normalizeSkillsApiOrigin(callerApiUrl) !== connection.apiOrigin) {
25688
+ throw new SkillsFleetCredentialError(`The Skills credential resolved for ${connection.apiOrigin} is never sent to a caller-supplied apiUrl ` + `(${normalizeSkillsApiOrigin(callerApiUrl)}). Pass an explicit authToken for that instance, or authToken: null ` + `for an unauthenticated read; no credential was sent.`, "INSTANCE_CREDENTIAL_MISMATCH");
25689
+ }
25690
+ return connection.apiKey;
25691
+ }
25692
+ async function fetchRemoteJson(url, options) {
25693
+ const fetchImpl = options.fetchImpl || fetch;
25694
+ const headers = await remoteRequestHeaders(options);
25695
+ const controller = new AbortController;
25696
+ const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 1e4);
25697
+ try {
25698
+ const response = await fetchImpl(url, {
25699
+ headers,
25700
+ signal: controller.signal
25701
+ });
25702
+ if (!response.ok) {
25703
+ throw new Error(`Remote registry request failed: ${response.status} ${response.statusText}`);
25704
+ }
25705
+ return response.json();
25706
+ } finally {
25707
+ clearTimeout(timeout);
25708
+ }
25709
+ }
25710
+ async function loadRemoteRegistry(options = {}) {
25711
+ const apiUrl = options.apiUrl || getConfiguredApiUrl();
25712
+ if (!apiUrl) {
25713
+ throw new Error(`Remote registry requires a Skills credential (${SKILLS_API_KEY_ENV}, the Keychain item, or ~/.hasna/skills/config/credentials) and, for your own instance, ${SKILLS_API_URL_ENV}`);
25714
+ }
25715
+ const url = buildSkillsApiUrl(apiUrl, options.endpoint);
25716
+ return parseRemoteRegistryPayload(await fetchRemoteJson(url, options));
25717
+ }
25718
+ async function mergeRemoteRegistry(local, options = {}) {
25719
+ const apiUrl = options.apiUrl || getConfiguredApiUrl();
25720
+ if (!apiUrl)
25721
+ return local;
25722
+ if (options.authToken !== undefined && !options.authToken?.trim())
25723
+ return local;
25724
+ const remote = await loadRemoteRegistry({ ...options, apiUrl });
25725
+ return mergeSkillRegistryLists(local, remote);
25726
+ }
25727
+
25728
+ // src/lib/read-access.ts
25729
+ async function requireSkillsReadAccess(env = process.env, options = {}) {
25730
+ const connection = await resolveSkillsConnection(env, options);
25731
+ return connection ? { mode: "hosted", apiOrigin: connection.apiOrigin } : { mode: "local" };
25732
+ }
25733
+ async function getBrowseRegistry(options = {}) {
25734
+ const profile = options.all ? "all" : "basic";
25735
+ const local = loadRegistryProfile(profile);
25736
+ if (options.remote) {
25737
+ const remote = await loadRemoteRegistry();
25738
+ return mergeSkillRegistryLists(local, remote);
25739
+ }
25740
+ return mergeRemoteRegistry(local);
25741
+ }
25742
+
25743
+ // src/lib/installer.ts
25744
+ import { existsSync as existsSync10, readFileSync as readFileSync9, rmSync as rmSync2 } from "fs";
25745
+ import { dirname as dirname4, join as join11 } from "path";
25746
+ import { homedir as homedir2 } from "os";
25747
+ import { fileURLToPath } from "url";
25748
+ // src/lib/utils.ts
25749
+ function normalizeSkillName(name) {
25750
+ return name;
25751
+ }
25752
+
25753
+ // src/lib/project-state.ts
25754
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
25755
+ import { join as join10 } from "path";
25756
+ var VALID_PIN_SOURCES = [
25757
+ "official",
25758
+ "custom",
25759
+ "remote",
25760
+ "private",
25761
+ "private-hosted",
25762
+ "upstream",
25763
+ "extension",
25764
+ "local"
25765
+ ];
25766
+ var SKILLS_PROJECT_DIR = ".skills";
25767
+ var PROJECT_CONFIG_FILE = "project.json";
25768
+ var DEFAULT_EXPORT_DIR = ".skills/exports";
25769
+ function getProjectStateDir(targetDir = process.cwd()) {
25770
+ return join10(targetDir, SKILLS_PROJECT_DIR);
25771
+ }
25772
+ function getProjectConfigPath(targetDir = process.cwd()) {
25773
+ return join10(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
25774
+ }
25775
+ function loadProjectConfig(targetDir = process.cwd()) {
25776
+ const path = getProjectConfigPath(targetDir);
25777
+ if (!existsSync9(path))
25778
+ return null;
25170
25779
  try {
25171
- return normalizeProjectConfig(JSON.parse(readFileSync7(path, "utf-8")));
25780
+ return normalizeProjectConfig(JSON.parse(readFileSync8(path, "utf-8")));
25172
25781
  } catch {
25173
25782
  return null;
25174
25783
  }
@@ -25267,12 +25876,12 @@ var __dirname2 = dirname4(fileURLToPath(import.meta.url));
25267
25876
  function findSkillsDir() {
25268
25877
  let dir = __dirname2;
25269
25878
  for (let i = 0;i < 5; i++) {
25270
- const candidate = join10(dir, "skills");
25879
+ const candidate = join11(dir, "skills");
25271
25880
  if (existsSync10(candidate) && !dir.includes(".skills"))
25272
25881
  return candidate;
25273
25882
  dir = dirname4(dir);
25274
25883
  }
25275
- return join10(__dirname2, "..", "skills");
25884
+ return join11(__dirname2, "..", "skills");
25276
25885
  }
25277
25886
  var SKILLS_DIR = findSkillsDir();
25278
25887
  function getSkillPath(name) {
@@ -25280,13 +25889,13 @@ function getSkillPath(name) {
25280
25889
  const portable = findPortableSkill(skillName);
25281
25890
  if (portable)
25282
25891
  return portable.path;
25283
- const legacyCustomPath = join10(getDataDir(), "custom", skillName);
25892
+ const legacyCustomPath = join11(getDataDir(), "custom", skillName);
25284
25893
  if (existsSync10(legacyCustomPath))
25285
25894
  return legacyCustomPath;
25286
25895
  const extensionPath = findExtensionSkillPath(skillName);
25287
25896
  if (extensionPath)
25288
25897
  return extensionPath;
25289
- return join10(SKILLS_DIR, skillName);
25898
+ return join11(SKILLS_DIR, skillName);
25290
25899
  }
25291
25900
  function getCanonicalSkillName(name) {
25292
25901
  return getSkill(name)?.name ?? resolveSkillAlias(normalizeSkillSlug(name));
@@ -25350,11 +25959,11 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
25350
25959
  const base = projectDir || process.cwd();
25351
25960
  switch (agent) {
25352
25961
  case "pi":
25353
- return scope === "project" ? join10(base, ".pi", "skills") : join10(homedir2(), ".pi", "agent", "skills");
25962
+ return scope === "project" ? join11(base, ".pi", "skills") : join11(homedir2(), ".pi", "agent", "skills");
25354
25963
  case "opencode":
25355
- return scope === "project" ? join10(base, ".opencode", "skills") : join10(homedir2(), ".config", "opencode", "skills");
25964
+ return scope === "project" ? join11(base, ".opencode", "skills") : join11(homedir2(), ".config", "opencode", "skills");
25356
25965
  default:
25357
- return scope === "project" ? join10(base, `.${agent}`, "skills") : join10(homedir2(), `.${agent}`, "skills");
25966
+ return scope === "project" ? join11(base, `.${agent}`, "skills") : join11(homedir2(), `.${agent}`, "skills");
25358
25967
  }
25359
25968
  }
25360
25969
  function warnMissingDependencies(name, targetDir) {
@@ -25369,11 +25978,11 @@ function warnMissingDependencies(name, targetDir) {
25369
25978
  }
25370
25979
  }
25371
25980
  function readBundledSkillVersion(name) {
25372
- const pkgPath = join10(getSkillPath(name), "package.json");
25981
+ const pkgPath = join11(getSkillPath(name), "package.json");
25373
25982
  if (!existsSync10(pkgPath))
25374
25983
  return "unknown";
25375
25984
  try {
25376
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
25985
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
25377
25986
  return pkg.version || "unknown";
25378
25987
  } catch {
25379
25988
  return "unknown";
@@ -25381,16 +25990,16 @@ function readBundledSkillVersion(name) {
25381
25990
  }
25382
25991
 
25383
25992
  // src/lib/skillinfo.ts
25384
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
25385
- import { join as join11 } from "path";
25993
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
25994
+ import { join as join12 } from "path";
25386
25995
  function isInstructionSkillDir(skillPath, meta) {
25387
25996
  if (meta?.kind === "instruction")
25388
25997
  return true;
25389
- const skillMdPath = join11(skillPath, "SKILL.md");
25998
+ const skillMdPath = join12(skillPath, "SKILL.md");
25390
25999
  if (!existsSync11(skillMdPath))
25391
26000
  return false;
25392
26001
  try {
25393
- return parseSkillFrontmatter(readFileSync9(skillMdPath, "utf-8"))?.kind === "instruction";
26002
+ return parseSkillFrontmatter(readFileSync10(skillMdPath, "utf-8"))?.kind === "instruction";
25394
26003
  } catch {
25395
26004
  return false;
25396
26005
  }
@@ -25416,9 +26025,9 @@ function getSkillDocs(name) {
25416
26025
  if (!existsSync11(skillPath))
25417
26026
  return null;
25418
26027
  return {
25419
- skillMd: readIfExists(join11(skillPath, "SKILL.md")),
25420
- readme: readIfExists(join11(skillPath, "README.md")),
25421
- claudeMd: readIfExists(join11(skillPath, "CLAUDE.md"))
26028
+ skillMd: readIfExists(join12(skillPath, "SKILL.md")),
26029
+ readme: readIfExists(join12(skillPath, "README.md")),
26030
+ claudeMd: readIfExists(join12(skillPath, "CLAUDE.md"))
25422
26031
  };
25423
26032
  }
25424
26033
  function getSkillBestDoc(name) {
@@ -25433,7 +26042,7 @@ function getSkillRequirements(name) {
25433
26042
  return null;
25434
26043
  const texts = [];
25435
26044
  for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
25436
- const content = readIfExists(join11(skillPath, file));
26045
+ const content = readIfExists(join12(skillPath, file));
25437
26046
  if (content)
25438
26047
  texts.push(content);
25439
26048
  }
@@ -25473,10 +26082,10 @@ function getSkillRequirements(name) {
25473
26082
  const skillName = normalizeSkillName(name);
25474
26083
  let cliCommand = `skills run ${skillName}`;
25475
26084
  let dependencies = {};
25476
- const pkgPath = join11(skillPath, "package.json");
26085
+ const pkgPath = join12(skillPath, "package.json");
25477
26086
  if (existsSync11(pkgPath)) {
25478
26087
  try {
25479
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
26088
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
25480
26089
  dependencies = pkg.dependencies || {};
25481
26090
  } catch {}
25482
26091
  }
@@ -25503,13 +26112,13 @@ async function runSkill(name, args, options = {}) {
25503
26112
  error: `Skill '${name}' is an instruction skill (kind: instruction) and is not runnable. Instruction skills are consumed by coding agents via SKILL.md, not executed with 'skills run'.`
25504
26113
  };
25505
26114
  }
25506
- const pkgPath = join11(skillPath, "package.json");
26115
+ const pkgPath = join12(skillPath, "package.json");
25507
26116
  if (!existsSync11(pkgPath)) {
25508
26117
  return { exitCode: 1, error: `No package.json in skill '${name}'` };
25509
26118
  }
25510
26119
  let entryPoint;
25511
26120
  try {
25512
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
26121
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
25513
26122
  if (pkg.bin) {
25514
26123
  const binValues = Object.values(pkg.bin);
25515
26124
  entryPoint = binValues[0];
@@ -25523,11 +26132,11 @@ async function runSkill(name, args, options = {}) {
25523
26132
  } catch {
25524
26133
  return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
25525
26134
  }
25526
- const entryPath = join11(skillPath, entryPoint);
26135
+ const entryPath = join12(skillPath, entryPoint);
25527
26136
  if (!existsSync11(entryPath)) {
25528
26137
  return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
25529
26138
  }
25530
- const nodeModules = join11(skillPath, "node_modules");
26139
+ const nodeModules = join12(skillPath, "node_modules");
25531
26140
  if (!existsSync11(nodeModules)) {
25532
26141
  const install = Bun.spawn(["bun", "install", "--no-save"], {
25533
26142
  cwd: skillPath,
@@ -25538,7 +26147,7 @@ async function runSkill(name, args, options = {}) {
25538
26147
  }
25539
26148
  const proc = Bun.spawn(["bun", "run", entryPath, ...args], {
25540
26149
  cwd: skillPath,
25541
- stdout: options.stdio === "pipe" ? "pipe" : "inherit",
26150
+ stdout: options.stdio === "pipe" ? "pipe" : options.stdio === "stderr" ? 2 : "inherit",
25542
26151
  stderr: options.stdio === "pipe" ? "pipe" : "inherit",
25543
26152
  stdin: "inherit",
25544
26153
  env: { ...process.env, ...options.env }
@@ -25555,7 +26164,7 @@ async function runSkill(name, args, options = {}) {
25555
26164
  return { exitCode };
25556
26165
  }
25557
26166
  function detectProjectSkills(cwd = process.cwd()) {
25558
- const pkgPath = join11(cwd, "package.json");
26167
+ const pkgPath = join12(cwd, "package.json");
25559
26168
  if (!existsSync11(pkgPath)) {
25560
26169
  const alwaysRecommend = ["market-research-report", "repo-onboarding-report", "blog-article"];
25561
26170
  const recommended2 = alwaysRecommend.map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
@@ -25563,7 +26172,7 @@ function detectProjectSkills(cwd = process.cwd()) {
25563
26172
  }
25564
26173
  let pkg;
25565
26174
  try {
25566
- pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
26175
+ pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
25567
26176
  } catch {
25568
26177
  const alwaysRecommend = ["market-research-report", "repo-onboarding-report", "blog-article"];
25569
26178
  const recommended2 = alwaysRecommend.map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
@@ -25653,7 +26262,7 @@ function extractEnvVars(text) {
25653
26262
  function readIfExists(path) {
25654
26263
  try {
25655
26264
  if (existsSync11(path)) {
25656
- return readFileSync9(path, "utf-8");
26265
+ return readFileSync10(path, "utf-8");
25657
26266
  }
25658
26267
  } catch {}
25659
26268
  return null;
@@ -26068,6 +26677,104 @@ var toolContracts = [
26068
26677
  dependencies: objectSchema({}, [], "Package dependencies.", true)
26069
26678
  })
26070
26679
  },
26680
+ {
26681
+ name: "update_account_profile",
26682
+ title: "Update Account Display Name",
26683
+ description: "Update your display name with fresh email verification on the configured server.",
26684
+ params: ["name", "email", "code"],
26685
+ category: "execution",
26686
+ sideEffects: "local-process-or-remote-run",
26687
+ stable: true,
26688
+ inputSchema: objectSchema({ name: stringSchema("Display name, 1\u2013100 characters"), email: { type: "string", format: "email" }, code: { type: "string", pattern: "^\\d{6}$" } }, ["name", "email", "code"]),
26689
+ outputSchema: objectSchema({ user: objectSchema({ id: stringSchema("Account identifier."), email: stringSchema("Account email."), displayName: stringSchema("Display name."), role: stringSchema("Current workspace role.") }, ["id", "email", "displayName", "role"]) }, ["user"])
26690
+ },
26691
+ {
26692
+ name: "update_workspace_name",
26693
+ title: "Update Workspace Name",
26694
+ description: "Update the current workspace name as an owner/admin with fresh email verification.",
26695
+ params: ["name", "email", "code"],
26696
+ category: "execution",
26697
+ sideEffects: "local-process-or-remote-run",
26698
+ stable: true,
26699
+ inputSchema: objectSchema({ name: stringSchema("Workspace name, 1\u2013100 characters"), email: { type: "string", format: "email" }, code: { type: "string", pattern: "^\\d{6}$" } }, ["name", "email", "code"]),
26700
+ outputSchema: objectSchema({ organization: objectSchema({ id: stringSchema("Workspace identifier."), slug: stringSchema("Stable workspace slug."), name: stringSchema("Workspace name.") }, ["id", "slug", "name"]) }, ["organization"])
26701
+ },
26702
+ {
26703
+ name: "set_workspace_member_role",
26704
+ title: "Set Current Workspace Member Role",
26705
+ description: "Set an exact membership incarnation's role with its observed expectedRole and fresh verification; no automatic retry.",
26706
+ params: ["membershipId", "role", "expectedRole", "email", "code"],
26707
+ category: "execution",
26708
+ sideEffects: "local-process-or-remote-run",
26709
+ stable: true,
26710
+ inputSchema: objectSchema({
26711
+ membershipId: { type: "string", pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$" },
26712
+ role: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
26713
+ expectedRole: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
26714
+ email: { type: "string", format: "email" },
26715
+ code: { type: "string", pattern: "^\\d{6}$" }
26716
+ }, ["membershipId", "role", "expectedRole", "email", "code"]),
26717
+ outputSchema: objectSchema({
26718
+ organizationId: stringSchema("Current workspace identifier."),
26719
+ changed: { type: "boolean" },
26720
+ member: objectSchema({
26721
+ membershipId: stringSchema("Membership incarnation."),
26722
+ userId: stringSchema("Account identifier."),
26723
+ email: stringSchema("Member email."),
26724
+ displayName: { oneOf: [{ type: "string" }, { type: "null" }] },
26725
+ role: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
26726
+ createdAt: stringSchema("Exact server timestamp including microseconds.")
26727
+ }, ["membershipId", "userId", "email", "displayName", "role", "createdAt"])
26728
+ }, ["organizationId", "member", "changed"])
26729
+ },
26730
+ {
26731
+ name: "remove_workspace_member",
26732
+ title: "Remove Current Workspace Member",
26733
+ description: "Remove exactly this membership incarnation using its observed expectedRole and fresh verification; self-removal is unavailable.",
26734
+ params: ["membershipId", "expectedRole", "email", "code"],
26735
+ category: "execution",
26736
+ sideEffects: "local-process-or-remote-run",
26737
+ stable: true,
26738
+ inputSchema: objectSchema({
26739
+ membershipId: { type: "string", pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$" },
26740
+ expectedRole: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
26741
+ email: { type: "string", format: "email" },
26742
+ code: { type: "string", pattern: "^\\d{6}$" }
26743
+ }, ["membershipId", "expectedRole", "email", "code"]),
26744
+ outputSchema: objectSchema({
26745
+ organizationId: stringSchema("Current workspace identifier."),
26746
+ membershipId: stringSchema("Removed membership incarnation."),
26747
+ removed: { type: "boolean", const: true },
26748
+ alreadyRemoved: { type: "boolean" }
26749
+ }, ["organizationId", "membershipId", "removed", "alreadyRemoved"])
26750
+ },
26751
+ {
26752
+ name: "list_workspace_members",
26753
+ title: "List Current Workspace Members",
26754
+ description: "Read one current-workspace roster page with fresh owner/admin email verification; saved credentials stay unchanged.",
26755
+ params: ["email", "code", "limit?", "cursor?"],
26756
+ category: "execution",
26757
+ sideEffects: "local-process-or-remote-run",
26758
+ stable: true,
26759
+ inputSchema: objectSchema({
26760
+ email: { type: "string", format: "email" },
26761
+ code: { type: "string", pattern: "^\\d{6}$" },
26762
+ limit: { type: "integer", minimum: 1, maximum: 100 },
26763
+ cursor: { type: "string", pattern: "^[A-Za-z0-9_-]{1,512}$" }
26764
+ }, ["email", "code"]),
26765
+ outputSchema: objectSchema({
26766
+ organizationId: stringSchema("Current workspace identifier."),
26767
+ members: arraySchema(objectSchema({
26768
+ membershipId: stringSchema("Membership incarnation."),
26769
+ userId: stringSchema("Account identifier."),
26770
+ email: stringSchema("Member email."),
26771
+ displayName: { oneOf: [{ type: "string" }, { type: "null" }] },
26772
+ role: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
26773
+ createdAt: stringSchema("Exact server timestamp including microseconds.")
26774
+ }, ["membershipId", "userId", "email", "displayName", "role", "createdAt"])),
26775
+ nextCursor: { oneOf: [{ type: "string" }, { type: "null" }] }
26776
+ }, ["organizationId", "members", "nextCursor"])
26777
+ },
26071
26778
  {
26072
26779
  name: "list_api_keys",
26073
26780
  title: "List API Keys",
@@ -26577,90 +27284,6 @@ function clone2(value) {
26577
27284
  return JSON.parse(JSON.stringify(value));
26578
27285
  }
26579
27286
 
26580
- // src/lib/discovery.ts
26581
- var VENDOR_TERMS = [
26582
- "Google Gemini",
26583
- "OpenAI Sora",
26584
- "MiniMax Hailuo",
26585
- "Claude Code",
26586
- "Claude Vision",
26587
- "DALL-E 3",
26588
- "GPT-4o Mini",
26589
- "Cerebras",
26590
- "OpenRouter",
26591
- "Firecrawl",
26592
- "ElevenLabs",
26593
- "Anthropic",
26594
- "OpenAI",
26595
- "Minimax",
26596
- "MiniMax",
26597
- "Gemini",
26598
- "Claude",
26599
- "Whisper",
26600
- "Seedance",
26601
- "Lyria",
26602
- "Sora",
26603
- "Veo",
26604
- "Exa.ai",
26605
- "Exa",
26606
- "XAI",
26607
- "xAI"
26608
- ];
26609
- var VENDOR_TAGS = new Set([
26610
- "anthropic",
26611
- "cerebras",
26612
- "claude",
26613
- "exa",
26614
- "firecrawl",
26615
- "gemini",
26616
- "google",
26617
- "minimax",
26618
- "openai",
26619
- "openrouter",
26620
- "seedance",
26621
- "whisper",
26622
- "xai"
26623
- ]);
26624
- var vendorPattern = new RegExp(`\\b(${VENDOR_TERMS.map(escapeRegExp).join("|")})\\b`, "gi");
26625
- function getCompactSkillDiscovery(skill) {
26626
- return {
26627
- name: skill.name,
26628
- category: skill.category,
26629
- description: sanitizePublicDiscoveryText(skill.description)
26630
- };
26631
- }
26632
- function getPublicSkillDiscovery(skill) {
26633
- return {
26634
- ...skill,
26635
- description: sanitizePublicDiscoveryText(skill.description),
26636
- tags: publicDiscoveryTags(skill.tags)
26637
- };
26638
- }
26639
- function publicDiscoveryTags(tags) {
26640
- return tags.filter((tag) => !VENDOR_TAGS.has(tag.toLowerCase()));
26641
- }
26642
- function sanitizePublicDiscoveryText(text) {
26643
- let sanitized = text.replace(vendorPattern, "hosted AI").replace(/\bLLM\b/g, "AI").replace(/\s{2,}/g, " ");
26644
- let previous;
26645
- do {
26646
- previous = sanitized;
26647
- sanitized = sanitized.replace(/\bhosted AI(?: providers)?\s*,\s*hosted AI(?: providers)?\b/gi, "hosted AI providers").replace(/\bhosted AI(?: providers)?\s*,?\s*and\s*hosted AI(?: providers)?\b/gi, "hosted AI providers").replace(/\bhosted AI(?: providers)?\s+or\s+hosted AI(?: providers)?\b/gi, "hosted AI providers").replace(/\bhosted AI providers\s+hosted AI\b/gi, "hosted AI providers").replace(/\bhosted AI providers\s+providers\b/gi, "hosted AI providers");
26648
- } while (sanitized !== previous);
26649
- return sanitized.trim();
26650
- }
26651
- function publicDiscoveryEnvVars(_skillName, envVars) {
26652
- return envVars;
26653
- }
26654
- function publicDiscoveryDependencies(_skillName, dependencies) {
26655
- return dependencies;
26656
- }
26657
- function publicDiscoveryDocumentation(_skill, documentation) {
26658
- return documentation;
26659
- }
26660
- function escapeRegExp(value) {
26661
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26662
- }
26663
-
26664
27287
  // src/lib/tool-primitives.ts
26665
27288
  var TOOL_PRIMITIVE_SCHEMA_VERSION = 1;
26666
27289
  var TOOL_PRIMITIVES = [
@@ -27119,22 +27742,20 @@ function compactRemoteRun(run) {
27119
27742
  }
27120
27743
 
27121
27744
  // src/mcp/helpers.ts
27745
+ init_fleet_credentials();
27746
+ async function readSurface(body) {
27747
+ try {
27748
+ return await body();
27749
+ } catch (error2) {
27750
+ if (isSkillsFleetCredentialError(error2))
27751
+ return mcpError("AUTH_REQUIRED", error2.message, ["skills auth login"]);
27752
+ throw error2;
27753
+ }
27754
+ }
27122
27755
  function stripNulls(obj) {
27123
27756
  return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== null && v !== undefined && !(Array.isArray(v) && v.length === 0)));
27124
27757
  }
27125
27758
  var searchCache = new Map;
27126
- var CACHE_MAX = 100;
27127
- function cacheGet(key) {
27128
- return searchCache.get(key);
27129
- }
27130
- function cacheSet(key, value) {
27131
- if (searchCache.size >= CACHE_MAX) {
27132
- const first = searchCache.keys().next().value;
27133
- if (first !== undefined)
27134
- searchCache.delete(first);
27135
- }
27136
- searchCache.set(key, value);
27137
- }
27138
27759
  function cacheClear() {
27139
27760
  searchCache.clear();
27140
27761
  }
@@ -27174,9 +27795,10 @@ function registerDiscoveryTools(server) {
27174
27795
  limit: exports_external.number().optional(),
27175
27796
  offset: exports_external.number().optional()
27176
27797
  }
27177
- }, async ({ category, profile, detail, limit, offset }) => {
27798
+ }, async ({ category, profile, detail, limit, offset }) => readSurface(async () => {
27178
27799
  const selectedProfile = profile || "basic";
27179
- const skills = category ? loadRegistryProfile(selectedProfile).filter((s) => s.category === category) : loadRegistryProfile(selectedProfile);
27800
+ const registry2 = await getBrowseRegistry({ all: selectedProfile === "all" });
27801
+ const skills = category ? registry2.filter((s) => s.category === category) : registry2;
27180
27802
  const mapped = detail ? skills.map(getPublicSkillDiscovery) : skills.map(getCompactSkillDiscovery);
27181
27803
  const page = paginate(mapped, {
27182
27804
  limit: parsePageLimit(limit, DEFAULT_MCP_LIMIT, { max: 100 }),
@@ -27192,7 +27814,7 @@ function registerDiscoveryTools(server) {
27192
27814
  nextArguments: page.hasMore ? { profile: selectedProfile, category, detail: Boolean(detail), limit: page.limit, offset: page.nextOffset } : null,
27193
27815
  detailHint: detail ? undefined : "Set detail:true for full public skill objects, or call get_skill_info for one skill."
27194
27816
  });
27195
- });
27817
+ }));
27196
27818
  server.registerTool("list_pinned_skills", {
27197
27819
  title: "List Pinned Skills",
27198
27820
  description: "List skills pinned in the current project's .skills/project.json.",
@@ -27216,13 +27838,9 @@ function registerDiscoveryTools(server) {
27216
27838
  limit: exports_external.number().optional(),
27217
27839
  offset: exports_external.number().optional()
27218
27840
  }
27219
- }, async ({ query, profile, detail, limit, offset }) => {
27841
+ }, async ({ query, profile, detail, limit, offset }) => readSurface(async () => {
27220
27842
  const selectedProfile = profile || "basic";
27221
- const cacheKey = `${selectedProfile}:${query}:${detail ?? false}`;
27222
- const cached2 = cacheGet(cacheKey);
27223
- const results = cached2 ? cached2 : searchSkills(query, loadRegistryProfile(selectedProfile));
27224
- if (!cached2)
27225
- cacheSet(cacheKey, results);
27843
+ const results = searchSkills(query, await getBrowseRegistry({ all: selectedProfile === "all" }));
27226
27844
  const out = detail ? results.map(getPublicSkillDiscovery) : results.map(getCompactSkillDiscovery);
27227
27845
  const page = paginate(out, {
27228
27846
  limit: parsePageLimit(limit, DEFAULT_MCP_LIMIT, { max: 100 }),
@@ -27238,14 +27856,15 @@ function registerDiscoveryTools(server) {
27238
27856
  nextArguments: page.hasMore ? { query, profile: selectedProfile, detail: Boolean(detail), limit: page.limit, offset: page.nextOffset } : null,
27239
27857
  detailHint: detail ? undefined : "Set detail:true for full public skill objects, or call get_skill_info for one skill."
27240
27858
  });
27241
- });
27859
+ }));
27242
27860
  server.registerTool("get_skill_info", {
27243
27861
  title: "Get Skill Info",
27244
27862
  description: "Get skill metadata, env vars, and dependencies.",
27245
27863
  inputSchema: {
27246
27864
  name: exports_external.string()
27247
27865
  }
27248
- }, async ({ name }) => {
27866
+ }, async ({ name }) => readSurface(async () => {
27867
+ await requireSkillsReadAccess();
27249
27868
  const skill = getSkill(name);
27250
27869
  if (!skill) {
27251
27870
  return mcpError("SKILL_NOT_FOUND", `Skill '${name}' not found`, findSimilarSkills(name));
@@ -27265,20 +27884,21 @@ function registerDiscoveryTools(server) {
27265
27884
  return {
27266
27885
  content: [{ type: "text", text: JSON.stringify(result) }]
27267
27886
  };
27268
- });
27887
+ }));
27269
27888
  server.registerTool("get_skill_docs", {
27270
27889
  title: "Get Skill Docs",
27271
27890
  description: "Get skill documentation (SKILL.md > README.md > CLAUDE.md).",
27272
27891
  inputSchema: {
27273
27892
  name: exports_external.string()
27274
27893
  }
27275
- }, async ({ name }) => {
27894
+ }, async ({ name }) => readSurface(async () => {
27895
+ await requireSkillsReadAccess();
27276
27896
  const doc2 = getSkillBestDoc(name);
27277
27897
  if (!doc2) {
27278
27898
  return mcpError("NO_DOCS", `No documentation found for '${name}'`);
27279
27899
  }
27280
27900
  return { content: [{ type: "text", text: doc2 }] };
27281
- });
27901
+ }));
27282
27902
  server.registerTool("list_tool_primitives", {
27283
27903
  title: "List Tool Primitives",
27284
27904
  description: "List primitive tools that skills depend on across CLI, MCP, API, and hosted worker execution.",
@@ -27324,25 +27944,74 @@ function registerDiscoveryTools(server) {
27324
27944
  }
27325
27945
 
27326
27946
  // src/mcp/operation-tools.ts
27327
- import { existsSync as existsSync13, readdirSync as readdirSync8, statSync as statSync8 } from "fs";
27947
+ import { existsSync as existsSync14, readdirSync as readdirSync8, statSync as statSync9 } from "fs";
27328
27948
  import { join as join14 } from "path";
27329
27949
 
27950
+ // src/lib/credential-state.ts
27951
+ init_auth_store();
27952
+ init_fleet_credentials();
27953
+ function describeCredentialState() {
27954
+ let credentialsFile = null;
27955
+ let mode = null;
27956
+ try {
27957
+ credentialsFile = getAuthFilePath();
27958
+ const bits = credentialFileMode();
27959
+ mode = bits === null ? null : `0${bits.toString(8).padStart(3, "0")}`;
27960
+ } catch {}
27961
+ try {
27962
+ const fleet = resolveSkillsFleet();
27963
+ if (fleet.mode === "hosted") {
27964
+ return {
27965
+ mode: "hosted",
27966
+ apiUrl: fleet.apiOrigin,
27967
+ apiUrlSource: fleet.apiUrlSource,
27968
+ apiKeySource: fleet.apiKeySource,
27969
+ apiKeyTier: fleet.apiKeyTier,
27970
+ credentialsFile,
27971
+ credentialsFileMode: mode,
27972
+ error: null
27973
+ };
27974
+ }
27975
+ return {
27976
+ mode: "local",
27977
+ apiUrl: null,
27978
+ apiUrlSource: null,
27979
+ apiKeySource: null,
27980
+ apiKeyTier: null,
27981
+ credentialsFile,
27982
+ credentialsFileMode: mode,
27983
+ error: null
27984
+ };
27985
+ } catch (error2) {
27986
+ return {
27987
+ mode: "misconfigured",
27988
+ apiUrl: null,
27989
+ apiUrlSource: null,
27990
+ apiKeySource: null,
27991
+ apiKeyTier: null,
27992
+ credentialsFile,
27993
+ credentialsFileMode: mode,
27994
+ error: error2.message
27995
+ };
27996
+ }
27997
+ }
27998
+
27330
27999
  // src/lib/run-state.ts
27331
28000
  import { createHash as createHash2, randomBytes } from "crypto";
27332
- import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync10, readdirSync as readdirSync7, statSync as statSync7, writeFileSync as writeFileSync5 } from "fs";
27333
- import { extname, join as join12, relative as relative2 } from "path";
28001
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync12, readdirSync as readdirSync7, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
28002
+ import { extname, join as join13, relative as relative2 } from "path";
27334
28003
  function createSkillRun(params, targetDir = process.cwd()) {
27335
28004
  const now = new Date;
27336
28005
  const id = createRunId(now);
27337
28006
  const day = now.toISOString().slice(0, 10);
27338
28007
  const skillName = normalizeSkillName(params.skill);
27339
28008
  const root = getProjectStateDir(targetDir);
27340
- const runDir = join12(root, "runs", day, id);
27341
- const logsDir = join12(runDir, "logs");
27342
- const exportDir = join12(root, "exports", skillName, id);
27343
- mkdirSync5(logsDir, { recursive: true });
27344
- mkdirSync5(exportDir, { recursive: true });
27345
- mkdirSync5(join12(root, "tmp"), { recursive: true });
28009
+ const runDir = join13(root, "runs", day, id);
28010
+ const logsDir = join13(runDir, "logs");
28011
+ const exportDir = join13(root, "exports", skillName, id);
28012
+ mkdirSync6(logsDir, { recursive: true });
28013
+ mkdirSync6(exportDir, { recursive: true });
28014
+ mkdirSync6(join13(root, "tmp"), { recursive: true });
27346
28015
  const record3 = {
27347
28016
  id,
27348
28017
  skill: skillName,
@@ -27394,22 +28063,22 @@ function updateSkillRun(context, patch) {
27394
28063
  return context.record;
27395
28064
  }
27396
28065
  function writeRunLogs(context, stdout = "", stderr = "") {
27397
- writeFileSync5(join12(context.logsDir, "stdout.log"), stdout);
27398
- writeFileSync5(join12(context.logsDir, "stderr.log"), stderr);
28066
+ writeFileSync6(join13(context.logsDir, "stdout.log"), stdout);
28067
+ writeFileSync6(join13(context.logsDir, "stderr.log"), stderr);
27399
28068
  }
27400
28069
  function appendRunEvent(context, event, data = {}) {
27401
28070
  const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
27402
28071
  `;
27403
- const path = join12(context.runDir, "events.ndjson");
27404
- const previous = existsSync12(path) ? readFileSync10(path, "utf-8") : "";
27405
- writeFileSync5(path, previous + line);
28072
+ const path = join13(context.runDir, "events.ndjson");
28073
+ const previous = existsSync13(path) ? readFileSync12(path, "utf-8") : "";
28074
+ writeFileSync6(path, previous + line);
27406
28075
  }
27407
28076
  function findSkillRun(runId, targetDir = process.cwd()) {
27408
- const runsRoot = join12(getProjectStateDir(targetDir), "runs");
27409
- if (!existsSync12(runsRoot))
28077
+ const runsRoot = join13(getProjectStateDir(targetDir), "runs");
28078
+ if (!existsSync13(runsRoot))
27410
28079
  return null;
27411
28080
  for (const day of readdirSync7(runsRoot)) {
27412
- const record3 = readRunRecord(join12(runsRoot, day, runId));
28081
+ const record3 = readRunRecord(join13(runsRoot, day, runId));
27413
28082
  if (record3)
27414
28083
  return record3;
27415
28084
  }
@@ -27426,20 +28095,20 @@ function skillRunEnv(context) {
27426
28095
  };
27427
28096
  }
27428
28097
  function writeRunRecord(context) {
27429
- writeFileSync5(join12(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
28098
+ writeFileSync6(join13(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
27430
28099
  `);
27431
28100
  }
27432
28101
  function writeArtifactsManifest(context, artifacts) {
27433
- writeFileSync5(join12(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
28102
+ writeFileSync6(join13(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
27434
28103
  `);
27435
28104
  }
27436
28105
  function collectRunArtifacts(context) {
27437
- if (!existsSync12(context.exportDir))
28106
+ if (!existsSync13(context.exportDir))
27438
28107
  return [];
27439
28108
  const artifacts = [];
27440
28109
  for (const path of walkFiles(context.exportDir)) {
27441
- const stat = statSync7(path);
27442
- const bytes = readFileSync10(path);
28110
+ const stat = statSync8(path);
28111
+ const bytes = readFileSync12(path);
27443
28112
  artifacts.push({
27444
28113
  path: toProjectRelative(context.targetDir, path),
27445
28114
  mime: mimeForPath(path),
@@ -27450,11 +28119,11 @@ function collectRunArtifacts(context) {
27450
28119
  return artifacts.sort((a, b) => a.path.localeCompare(b.path));
27451
28120
  }
27452
28121
  function readRunRecord(runDir) {
27453
- const path = join12(runDir, "run.json");
27454
- if (!existsSync12(path))
28122
+ const path = join13(runDir, "run.json");
28123
+ if (!existsSync13(path))
27455
28124
  return null;
27456
28125
  try {
27457
- return JSON.parse(readFileSync10(path, "utf-8"));
28126
+ return JSON.parse(readFileSync12(path, "utf-8"));
27458
28127
  } catch {
27459
28128
  return null;
27460
28129
  }
@@ -27462,8 +28131,8 @@ function readRunRecord(runDir) {
27462
28131
  function walkFiles(dir) {
27463
28132
  const files = [];
27464
28133
  for (const entry of readdirSync7(dir)) {
27465
- const full = join12(dir, entry);
27466
- if (statSync7(full).isDirectory())
28134
+ const full = join13(dir, entry);
28135
+ if (statSync8(full).isDirectory())
27467
28136
  files.push(...walkFiles(full));
27468
28137
  else
27469
28138
  files.push(full);
@@ -27536,17 +28205,20 @@ async function resolveConfiguredRunRouting(skill, env = process.env) {
27536
28205
  try {
27537
28206
  connection = await resolveSkillsConnection(env);
27538
28207
  } catch (error2) {
27539
- const isMissingCredential = (error2 instanceof SkillsFleetCredentialError || error2?.name === "SkillsFleetCredentialError") && error2.code === "MISSING_API_CREDENTIAL";
28208
+ const isMissingCredential = isSkillsFleetCredentialError2(error2) && error2.code === "MISSING_API_CREDENTIAL";
27540
28209
  if (!isMissingCredential)
27541
28210
  throw error2;
27542
28211
  return {
27543
28212
  route: "error",
27544
28213
  code: "REMOTE_REQUIRES_CREDENTIAL",
27545
- error: `${skill.name} is a server-owned skill. ${error2.message}`
28214
+ error: skill.serverOwned ? `${skill.name} is a server-owned skill. ${error2.message}` : `${skill.name} cannot run: ${error2.message}`
27546
28215
  };
27547
28216
  }
27548
28217
  return resolveRunRouting(skill, connection?.apiKey, connection?.apiOrigin);
27549
28218
  }
28219
+ function isSkillsFleetCredentialError2(error2) {
28220
+ return typeof error2 === "object" && error2 !== null && error2.name === "SkillsFleetCredentialError";
28221
+ }
27550
28222
 
27551
28223
  // src/mcp/operation-tools.ts
27552
28224
  function registerOperationTools(server) {
@@ -27709,39 +28381,42 @@ function registerOperationTools(server) {
27709
28381
  server.registerTool("list_categories", {
27710
28382
  title: "List Categories",
27711
28383
  description: "List all 17 skill categories with skill counts."
27712
- }, async () => {
27713
- const cats = CATEGORIES.map((category) => ({
28384
+ }, async () => readSurface(async () => {
28385
+ const registry2 = await getBrowseRegistry({ all: true });
28386
+ const extras = Array.from(new Set(registry2.map((skill) => skill.category))).filter((category) => !CATEGORIES.includes(category)).sort();
28387
+ const cats = [...CATEGORIES, ...extras].map((category) => ({
27714
28388
  name: category,
27715
- count: getSkillsByCategory(category).length
28389
+ count: registry2.filter((skill) => skill.category === category).length
27716
28390
  }));
27717
28391
  return { content: [{ type: "text", text: JSON.stringify(cats, null, 2) }] };
27718
- });
28392
+ }));
27719
28393
  server.registerTool("list_tags", {
27720
28394
  title: "List Tags",
27721
28395
  description: "List all unique skill tags with occurrence counts."
27722
- }, async () => {
28396
+ }, async () => readSurface(async () => {
27723
28397
  const tagCounts = new Map;
27724
- for (const skill of loadRegistry()) {
28398
+ for (const skill of await getBrowseRegistry({ all: true })) {
27725
28399
  for (const tag of skill.tags) {
27726
28400
  tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
27727
28401
  }
27728
28402
  }
27729
28403
  const sorted = Array.from(tagCounts.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => ({ name, count }));
27730
28404
  return { content: [{ type: "text", text: JSON.stringify(sorted, null, 2) }] };
27731
- });
28405
+ }));
27732
28406
  server.registerTool("get_requirements", {
27733
28407
  title: "Get Requirements",
27734
28408
  description: "Get env vars, system deps, and npm dependencies for a skill.",
27735
28409
  inputSchema: {
27736
28410
  name: exports_external.string()
27737
28411
  }
27738
- }, async ({ name }) => {
28412
+ }, async ({ name }) => readSurface(async () => {
28413
+ await requireSkillsReadAccess();
27739
28414
  const reqs = getSkillRequirements(name);
27740
28415
  if (!reqs) {
27741
28416
  return mcpError("SKILL_NOT_FOUND", `Skill '${name}' not found`, findSimilarSkills(name));
27742
28417
  }
27743
28418
  return { content: [{ type: "text", text: JSON.stringify(reqs, null, 2) }] };
27744
- });
28419
+ }));
27745
28420
  server.registerTool("run_skill", {
27746
28421
  title: "Run Skill",
27747
28422
  description: "Run a skill by name with optional arguments.",
@@ -27783,20 +28458,16 @@ function registerOperationTools(server) {
27783
28458
  } catch (error2) {
27784
28459
  return mcpError("INVALID_INPUT_FILES", error2.message);
27785
28460
  }
28461
+ if (routing.route === "error") {
28462
+ const suggestions = routing.code === "REMOTE_REQUIRES_ORIGIN" ? ["skills setup --api-url <url>", "skills auth login"] : ["skills auth login"];
28463
+ return mcpError(routing.code, routing.error, suggestions);
28464
+ }
27786
28465
  const runContext = createSkillRun({
27787
28466
  skill: skillName,
27788
28467
  args: runArgs,
27789
28468
  remote: routing.route === "remote",
27790
28469
  ...routing.route === "remote" ? { remoteApiOrigin: routing.apiOrigin } : {}
27791
28470
  });
27792
- if (routing.route === "error") {
27793
- const error2 = routing.error;
27794
- writeRunLogs(runContext, "", error2 + `
27795
- `);
27796
- const run = completeSkillRun(runContext, { status: "failed", error: error2 });
27797
- const suggestions = routing.code === "REMOTE_REQUIRES_ORIGIN" ? ["skills setup --api-url <url>", "skills auth login"] : ["skills auth login"];
27798
- return mcpError(routing.code, `${error2}. Local run metadata: ${run.paths.runDir}/run.json`, suggestions);
27799
- }
27800
28471
  if (routing.route === "remote") {
27801
28472
  try {
27802
28473
  const { RemoteSkillsClient: RemoteSkillsClient2 } = await Promise.resolve().then(() => (init_remote_client(), exports_remote_client));
@@ -27953,21 +28624,22 @@ function registerOperationTools(server) {
27953
28624
  });
27954
28625
  server.registerTool("whoami", {
27955
28626
  title: "Skills Whoami",
27956
- description: "Show setup summary: version, pinned skills, agent configs, cwd."
28627
+ description: "Show setup summary: version, pinned skills, agent configs, cwd, and the credential/transport SOURCES (never values)."
27957
28628
  }, async () => {
27958
28629
  const version2 = package_default.version;
27959
28630
  const cwd = process.cwd();
28631
+ const credential = describeCredentialState();
27960
28632
  const installed = getInstalledSkills();
27961
28633
  const agents = [];
27962
28634
  for (const agent of AGENT_TARGETS) {
27963
28635
  const agentSkillsPath = getAgentSkillsDir(agent, "global");
27964
- const exists = existsSync13(agentSkillsPath);
28636
+ const exists = existsSync14(agentSkillsPath);
27965
28637
  let skillCount = 0;
27966
28638
  if (exists) {
27967
28639
  try {
27968
28640
  skillCount = readdirSync8(agentSkillsPath).filter((f) => {
27969
28641
  const full = join14(agentSkillsPath, f);
27970
- return !f.startsWith(".") && statSync8(full).isDirectory();
28642
+ return !f.startsWith(".") && statSync9(full).isDirectory();
27971
28643
  }).length;
27972
28644
  } catch {}
27973
28645
  }
@@ -27980,7 +28652,8 @@ function registerOperationTools(server) {
27980
28652
  installed,
27981
28653
  agents,
27982
28654
  skillsDir,
27983
- cwd
28655
+ cwd,
28656
+ credential
27984
28657
  };
27985
28658
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
27986
28659
  });
@@ -28012,29 +28685,17 @@ function compactRunToolPayload(payload, detailHint) {
28012
28685
  }
28013
28686
 
28014
28687
  // src/lib/feedback.ts
28015
- import { appendFileSync, existsSync as existsSync14, mkdirSync as mkdirSync6 } from "fs";
28688
+ import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync7 } from "fs";
28016
28689
  import { dirname as dirname5, join as join15 } from "path";
28017
28690
  import { Database } from "bun:sqlite";
28018
-
28019
- // src/lib/api-url.ts
28020
- init_fleet_credentials();
28021
- init_fleet_credentials();
28022
- var API_URL_ENV_VAR = SKILLS_API_URL_ENV;
28023
- var MISSING_API_URL_HINT = `run: skills auth login, or set ${API_URL_ENV_VAR}=<your Skills instance origin>, ` + `or run: skills setup --api-url <your Skills instance origin>`;
28024
- function resolveApiUrl(env = process.env, options = {}) {
28025
- const fleet = resolveSkillsFleet(env, options);
28026
- return fleet.mode === "hosted" ? fleet.apiOrigin : undefined;
28027
- }
28028
-
28029
- // src/lib/feedback.ts
28030
28691
  function getFeedbackDbPath() {
28031
28692
  return join15(getDataDir(), "skills.db");
28032
28693
  }
28033
28694
  function getFeedbackDb() {
28034
28695
  const dbPath = getFeedbackDbPath();
28035
28696
  const dir = dirname5(dbPath);
28036
- if (!existsSync14(dir))
28037
- mkdirSync6(dir, { recursive: true });
28697
+ if (!existsSync15(dir))
28698
+ mkdirSync7(dir, { recursive: true });
28038
28699
  const db = new Database(dbPath);
28039
28700
  db.exec("PRAGMA journal_mode = WAL");
28040
28701
  db.exec([
@@ -28062,8 +28723,8 @@ function saveFeedback(input) {
28062
28723
  if (isApiMode()) {
28063
28724
  const path = join15(getDataDir(), "feedback.jsonl");
28064
28725
  const dir = dirname5(path);
28065
- if (!existsSync14(dir))
28066
- mkdirSync6(dir, { recursive: true });
28726
+ if (!existsSync15(dir))
28727
+ mkdirSync7(dir, { recursive: true });
28067
28728
  appendFileSync(path, JSON.stringify({ message, category, email: input.email ?? null, agent: input.agent ?? null, version: input.version ?? null, createdAt: new Date().toISOString() }) + `
28068
28729
  `);
28069
28730
  return { saved: true, category, path };
@@ -28208,16 +28869,16 @@ function registerResourceMetaTools(server) {
28208
28869
  }
28209
28870
 
28210
28871
  // src/lib/scheduler.ts
28211
- import { existsSync as existsSync15, readFileSync as readFileSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7 } from "fs";
28872
+ import { existsSync as existsSync16, readFileSync as readFileSync13, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8 } from "fs";
28212
28873
  import { join as join16 } from "path";
28213
28874
  function getSchedulesPath(targetDir = process.cwd()) {
28214
28875
  return join16(targetDir, ".skills", "schedules.json");
28215
28876
  }
28216
28877
  function loadSchedules(targetDir = process.cwd()) {
28217
28878
  const path = getSchedulesPath(targetDir);
28218
- if (existsSync15(path)) {
28879
+ if (existsSync16(path)) {
28219
28880
  try {
28220
- return JSON.parse(readFileSync12(path, "utf-8"));
28881
+ return JSON.parse(readFileSync13(path, "utf-8"));
28221
28882
  } catch {}
28222
28883
  }
28223
28884
  return { version: 1, schedules: [] };
@@ -28225,9 +28886,9 @@ function loadSchedules(targetDir = process.cwd()) {
28225
28886
  function saveSchedules(data, targetDir = process.cwd()) {
28226
28887
  const path = getSchedulesPath(targetDir);
28227
28888
  const dir = join16(targetDir, ".skills");
28228
- if (!existsSync15(dir))
28229
- mkdirSync7(dir, { recursive: true });
28230
- writeFileSync6(path, JSON.stringify(data, null, 2));
28889
+ if (!existsSync16(dir))
28890
+ mkdirSync8(dir, { recursive: true });
28891
+ writeFileSync7(path, JSON.stringify(data, null, 2));
28231
28892
  }
28232
28893
  function validateCronField(expr, min, max, label) {
28233
28894
  for (const part of expr.split(",")) {
@@ -28487,12 +29148,12 @@ function registerScheduleTools(server) {
28487
29148
  // src/lib/native-storage.ts
28488
29149
  import { createHash as createHash4, createHmac } from "crypto";
28489
29150
  import {
28490
- existsSync as existsSync16,
28491
- mkdirSync as mkdirSync8,
28492
- readFileSync as readFileSync13,
29151
+ existsSync as existsSync17,
29152
+ mkdirSync as mkdirSync9,
29153
+ readFileSync as readFileSync14,
28493
29154
  readdirSync as readdirSync9,
28494
- statSync as statSync9,
28495
- writeFileSync as writeFileSync7
29155
+ statSync as statSync10,
29156
+ writeFileSync as writeFileSync8
28496
29157
  } from "fs";
28497
29158
  import { dirname as dirname6, join as join17, normalize as normalize3, relative as relative3, sep as sep2 } from "path";
28498
29159
  var SKILLS_STORAGE_TABLES = [
@@ -28590,9 +29251,9 @@ function getStorageStatus(options = {}) {
28590
29251
  function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
28591
29252
  const projectStateDir = getProjectStateDir(targetDir);
28592
29253
  const files = [];
28593
- if (existsSync16(projectStateDir)) {
29254
+ if (existsSync17(projectStateDir)) {
28594
29255
  for (const filePath of walkFiles2(projectStateDir)) {
28595
- const bytes = readFileSync13(filePath);
29256
+ const bytes = readFileSync14(filePath);
28596
29257
  const relativePath = toPosix(relative3(targetDir, filePath));
28597
29258
  files.push({
28598
29259
  path: relativePath,
@@ -28675,7 +29336,7 @@ function walkFiles2(dir) {
28675
29336
  const files = [];
28676
29337
  for (const entry of readdirSync9(dir)) {
28677
29338
  const full = join17(dir, entry);
28678
- const stats = statSync9(full);
29339
+ const stats = statSync10(full);
28679
29340
  if (stats.isDirectory())
28680
29341
  files.push(...walkFiles2(full));
28681
29342
  else
@@ -28730,6 +29391,8 @@ function registerStorageTools(server) {
28730
29391
  }
28731
29392
 
28732
29393
  // src/lib/remote-auth.ts
29394
+ init_remote_workspace();
29395
+ init_remote_workspace();
28733
29396
  init_remote_client();
28734
29397
  init_fleet_credentials();
28735
29398
  var MAX_ERROR_DETAIL_LENGTH = 200;
@@ -28771,10 +29434,10 @@ async function requestAuthApi(instance, path, options) {
28771
29434
  const text = await res.text();
28772
29435
  const body = text ? parseJsonBody(text) : {};
28773
29436
  if (!res.ok) {
28774
- const record3 = isRecord4(body) ? body : {};
28775
- const detail = typeof record3.detail === "string" ? record3.detail : undefined;
28776
- const error2 = typeof record3.error === "string" ? record3.error : undefined;
28777
- const code = typeof record3.code === "string" ? record3.code : undefined;
29437
+ const record4 = isRecord5(body) ? body : {};
29438
+ const detail = typeof record4.detail === "string" ? record4.detail : undefined;
29439
+ const error2 = typeof record4.error === "string" ? record4.error : undefined;
29440
+ const code = typeof record4.code === "string" ? record4.code : undefined;
28778
29441
  throw new HostedApiError(detail || error2 || `${res.status} ${res.statusText}`, {
28779
29442
  status: res.status,
28780
29443
  code,
@@ -28799,7 +29462,7 @@ function condenseErrorBody(text) {
28799
29462
  return collapsed;
28800
29463
  return `${collapsed.slice(0, MAX_ERROR_DETAIL_LENGTH - 1).trimEnd()}\u2026`;
28801
29464
  }
28802
- function isRecord4(value) {
29465
+ function isRecord5(value) {
28803
29466
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
28804
29467
  }
28805
29468
 
@@ -28821,12 +29484,13 @@ class RemoteSkillsAuthClient {
28821
29484
  return this.request("/api/auth/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) });
28822
29485
  }
28823
29486
  async sessionClient(email2, code) {
29487
+ const apiOrigin = this.apiOrigin;
28824
29488
  if (!email2.includes("@") || !/^\d{6}$/.test(code))
28825
- throw new Error("Fresh email and six-digit verification code are required to manage API keys");
29489
+ throw new Error("Fresh email and six-digit verification code are required to manage this account");
28826
29490
  const login = await this.verifyCode(email2, code);
28827
29491
  if (!login || typeof login.token !== "string" || !login.token)
28828
29492
  throw new Error("The server did not return an authorized account session");
28829
- return new RemoteSkillsClient(login.token, this.apiOrigin);
29493
+ return new RemoteSkillsClient(login.token, apiOrigin);
28830
29494
  }
28831
29495
  async createApiKey(email2, code, name, scopes) {
28832
29496
  return (await this.sessionClient(email2, code)).createApiKey(name, scopes);
@@ -28837,6 +29501,26 @@ class RemoteSkillsAuthClient {
28837
29501
  async revokeApiKey(email2, code, keyId) {
28838
29502
  return (await this.sessionClient(email2, code)).revokeApiKey(keyId);
28839
29503
  }
29504
+ async updateProfile(email2, code, input) {
29505
+ customerNamePatch(input, "displayName");
29506
+ return (await this.sessionClient(email2, code)).updateProfile(input);
29507
+ }
29508
+ async updateCurrentWorkspace(email2, code, input) {
29509
+ customerNamePatch(input, "name");
29510
+ return (await this.sessionClient(email2, code)).updateCurrentWorkspace(input);
29511
+ }
29512
+ async listWorkspaceMembers(email2, code, options = {}) {
29513
+ workspaceMembersQuery(options);
29514
+ return (await this.sessionClient(email2, code)).listWorkspaceMembers(options);
29515
+ }
29516
+ async setWorkspaceMemberRole(email2, code, membershipId, input) {
29517
+ const captured = workspaceMemberRoleInput(membershipId, input);
29518
+ return (await this.sessionClient(email2, code)).setWorkspaceMemberRole(captured.membershipId, captured.body);
29519
+ }
29520
+ async removeWorkspaceMember(email2, code, membershipId, input) {
29521
+ const captured = workspaceMemberRemovalInput(membershipId, input);
29522
+ return (await this.sessionClient(email2, code)).removeWorkspaceMember(captured.membershipId, captured.body);
29523
+ }
28840
29524
  request(path, options) {
28841
29525
  if (!["/api/auth/login", "/api/auth/verify", "/api/auth/device/start", "/api/auth/device/token", "/api/auth/keys", "/api/auth/whoami"].includes(path))
28842
29526
  throw new Error("Unsupported authentication operation");
@@ -28848,6 +29532,65 @@ class RemoteSkillsAuthClient {
28848
29532
  init_auth_store();
28849
29533
  init_remote_client();
28850
29534
  function registerRemoteCustomerTools(server) {
29535
+ const memberRole = exports_external.enum(["owner", "admin", "member", "viewer"]);
29536
+ const memberInput = {
29537
+ membershipId: exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/),
29538
+ expectedRole: memberRole,
29539
+ email: exports_external.string().email(),
29540
+ code: exports_external.string().regex(/^\d{6}$/)
29541
+ };
29542
+ server.registerTool("set_workspace_member_role", {
29543
+ title: "Set Current Workspace Member Role",
29544
+ description: "Change exactly this membership incarnation with its observed expectedRole and fresh verification. The server enforces owner/admin policy. No automatic refresh or retry; saved credentials stay unchanged.",
29545
+ inputSchema: exports_external.object({ ...memberInput, role: memberRole }).strict()
29546
+ }, async ({ membershipId, role, expectedRole, email: email2, code }) => {
29547
+ try {
29548
+ return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("Set workspace member role")).setWorkspaceMemberRole(email2, code, membershipId, { role, expectedRole }));
29549
+ } catch (error2) {
29550
+ return memberError(error2);
29551
+ }
29552
+ });
29553
+ server.registerTool("remove_workspace_member", {
29554
+ title: "Remove Current Workspace Member",
29555
+ description: "Remove exactly this membership incarnation using its observed expectedRole and fresh verification. Self-removal is unavailable. A retry cannot remove a later replacement membership; saved credentials stay unchanged.",
29556
+ inputSchema: exports_external.object(memberInput).strict()
29557
+ }, async ({ membershipId, expectedRole, email: email2, code }) => {
29558
+ try {
29559
+ return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("Remove workspace member")).removeWorkspaceMember(email2, code, membershipId, { expectedRole }));
29560
+ } catch (error2) {
29561
+ return memberError(error2);
29562
+ }
29563
+ });
29564
+ server.registerTool("list_workspace_members", {
29565
+ title: "List Current Workspace Members",
29566
+ description: "Read one roster page on the selected Skills server using fresh owner/admin email verification. Saved credentials are unchanged. This does not invite, change or switch members/workspaces.",
29567
+ inputSchema: exports_external.object({
29568
+ email: exports_external.string().email(),
29569
+ code: exports_external.string().regex(/^\d{6}$/),
29570
+ limit: exports_external.number().int().min(1).max(100).optional(),
29571
+ cursor: exports_external.string().regex(/^[A-Za-z0-9_-]{1,512}$/).optional()
29572
+ }).strict()
29573
+ }, async ({ email: email2, code, limit, cursor: cursor2 }) => {
29574
+ try {
29575
+ return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("List workspace members")).listWorkspaceMembers(email2, code, { limit, cursor: cursor2 }));
29576
+ } catch {
29577
+ return mcpError("WORKSPACE_MEMBERS_FAILED", "Unable to list workspace members. Check the selected server, owner/admin permissions, pagination and fresh verification code.");
29578
+ }
29579
+ });
29580
+ for (const kind of ["profile", "workspace"]) {
29581
+ server.registerTool(kind === "profile" ? "update_account_profile" : "update_workspace_name", {
29582
+ title: kind === "profile" ? "Update Account Display Name" : "Update Workspace Name",
29583
+ description: "Update only the name on the explicitly selected Skills server using fresh email OTP. Workspace changes require an owner/admin. Saved credentials are unchanged.",
29584
+ inputSchema: exports_external.object({ name: exports_external.string().min(1), email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/) }).strict()
29585
+ }, async ({ name, email: email2, code }) => {
29586
+ try {
29587
+ const client = new RemoteSkillsAuthClient(getApiUrl("Update customer name"));
29588
+ return mcpJson(kind === "profile" ? await client.updateProfile(email2, code, { displayName: name }) : await client.updateCurrentWorkspace(email2, code, { name }));
29589
+ } catch {
29590
+ return mcpError("NAME_UPDATE_FAILED", "Unable to update the name. Check the selected server, name, permissions and fresh verification code.");
29591
+ }
29592
+ });
29593
+ }
28851
29594
  for (const operation of REMOTE_CUSTOMER_OPERATIONS) {
28852
29595
  const inputSchema = {};
28853
29596
  if (operation.parameter)
@@ -28906,6 +29649,9 @@ function registerRemoteCustomerTools(server) {
28906
29649
  return { ...metadata, base64: Buffer.from(bytes).toString("base64") };
28907
29650
  }));
28908
29651
  }
29652
+ function memberError(error2) {
29653
+ return error2 instanceof RemoteWorkspaceMemberError ? mcpError(error2.code, error2.message) : mcpError("WORKSPACE_MEMBER_FAILED", "Unable to manage workspace member. Check the selected server and fresh verification, then refresh the roster before another action.");
29654
+ }
28909
29655
  async function callRemote(action) {
28910
29656
  try {
28911
29657
  const client = await createRemoteSkillsClient();
@@ -28913,6 +29659,9 @@ async function callRemote(action) {
28913
29659
  return mcpError("AUTH_REQUIRED", "Configure a Skills API and sign in with skills auth login");
28914
29660
  return mcpJson(await action(client));
28915
29661
  } catch (error2) {
29662
+ if (error2 instanceof RemoteCapabilityUnavailableError) {
29663
+ return { ...mcpJson({ code: error2.code, message: error2.message, status: error2.status }), isError: true };
29664
+ }
28916
29665
  return mcpError("REMOTE_REQUEST_FAILED", error2 instanceof Error ? error2.message : "Skills server request failed");
28917
29666
  }
28918
29667
  }
@@ -30383,6 +31132,7 @@ async function startSkillsMcpHttpServer(options = {}) {
30383
31132
  }
30384
31133
 
30385
31134
  // src/mcp/index.ts
31135
+ init_fleet_credentials();
30386
31136
  var args = process.argv.slice(2);
30387
31137
  function printHelp() {
30388
31138
  console.log(`Usage: skills-mcp [options]
@@ -30404,7 +31154,18 @@ if (args.includes("--version") || args.includes("-V")) {
30404
31154
  console.log(package_default.version);
30405
31155
  process.exit(0);
30406
31156
  }
31157
+ function assertSkillsMcpConfigured(env = process.env) {
31158
+ try {
31159
+ resolveSkillsFleet(env);
31160
+ } catch (error2) {
31161
+ if (!isSkillsFleetCredentialError(error2))
31162
+ throw error2;
31163
+ console.error(error2.message);
31164
+ process.exit(1);
31165
+ }
31166
+ }
30407
31167
  async function startMcpStdio() {
31168
+ assertSkillsMcpConfigured();
30408
31169
  const server2 = buildServer();
30409
31170
  await server2.connect(new StdioServerTransport);
30410
31171
  }
@@ -30413,6 +31174,7 @@ async function main() {
30413
31174
  await startMcpStdio();
30414
31175
  return;
30415
31176
  }
31177
+ assertSkillsMcpConfigured();
30416
31178
  const port = parseMcpHttpPort(args);
30417
31179
  await startSkillsMcpHttpServer({ port, hostname: "127.0.0.1" });
30418
31180
  }
@@ -30424,5 +31186,6 @@ if (import.meta.main) {
30424
31186
  }
30425
31187
  export {
30426
31188
  startMcpStdio,
30427
- buildServer
31189
+ buildServer,
31190
+ assertSkillsMcpConfigured
30428
31191
  };