@hasna/skills 0.4.0 → 0.5.1

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 (48) hide show
  1. package/README.md +220 -5
  2. package/bin/index.js +7747 -5645
  3. package/bin/mcp.js +1493 -431
  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/commands/workspace-selection.d.ts +11 -0
  16. package/dist/cli/env-assignment.d.ts +9 -0
  17. package/dist/index.d.ts +8 -2
  18. package/dist/index.js +841 -167
  19. package/dist/lib/agent-sync.d.ts +13 -8
  20. package/dist/lib/api-url.d.ts +4 -3
  21. package/dist/lib/app-home.d.ts +0 -1
  22. package/dist/lib/client-types.d.ts +75 -0
  23. package/dist/lib/credential-state.d.ts +12 -0
  24. package/dist/lib/fleet-credentials.d.ts +41 -15
  25. package/dist/lib/home-adoption.d.ts +2 -0
  26. package/dist/lib/home-census.d.ts +3 -1
  27. package/dist/lib/local-opt-in.d.ts +24 -0
  28. package/dist/lib/portable-skills-files.d.ts +6 -2
  29. package/dist/lib/read-access.d.ts +83 -0
  30. package/dist/lib/remote-auth.d.ts +23 -3
  31. package/dist/lib/remote-client.d.ts +45 -5
  32. package/dist/lib/remote-profile.d.ts +26 -0
  33. package/dist/lib/remote-registry.d.ts +7 -3
  34. package/dist/lib/remote-workspace-selection.d.ts +58 -0
  35. package/dist/lib/remote-workspace.d.ts +76 -0
  36. package/dist/lib/skillinfo.d.ts +1 -1
  37. package/dist/lib/workspace-profile.d.ts +49 -0
  38. package/dist/mcp/helpers.d.ts +22 -0
  39. package/dist/mcp/index.d.ts +16 -0
  40. package/dist/sdk/governance-store.d.ts +1 -0
  41. package/dist/sdk/index.d.ts +8 -2
  42. package/dist/sdk/index.js +1312 -297
  43. package/dist/sdk/outputs.d.ts +0 -11
  44. package/dist/sdk/runs.d.ts +1 -1
  45. package/dist/storage.js +6 -40
  46. package/docs/skill-standard.md +30 -2
  47. package/package.json +6 -4
  48. 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,78 @@ 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
+ import { basename as basename3, dirname as dirname5, join as join13 } from "path";
8020
+ function getAuthFilePath(env = process.env) {
8021
+ return skillsCredentialFilePath(env);
8022
+ }
8023
+ function getIdentityFilePath(env = process.env) {
8024
+ const file = skillsCredentialFilePath(env);
8025
+ return join13(dirname5(file), basename3(file).replace(/^credentials/, "identity") + ".json");
8026
+ }
8027
+ function getApiUrl(action, env = process.env, options = {}) {
8028
+ return requireSkillsApiOrigin(action, env, options);
8029
+ }
8030
+ function credentialFileMode(env = process.env) {
8031
+ try {
8032
+ return statSync7(skillsCredentialFilePath(env)).mode & 511;
8033
+ } catch {
8034
+ return null;
8035
+ }
8036
+ }
8037
+ var init_auth_store = __esm(() => {
8038
+ init_transport();
8039
+ init_instance_credentials();
8040
+ init_fleet_credentials();
8041
+ init_transport();
8042
+ init_fleet_credentials();
8043
+ });
8044
+
8045
+ // src/lib/remote-run-contract.ts
8046
+ function normalizeRemoteSkillRunContract(payload, fallbackSkill) {
8047
+ const record3 = isRecord3(payload) ? payload : {};
8048
+ return {
8049
+ contractVersion: REMOTE_SKILL_RUN_CONTRACT_VERSION,
8050
+ ...pickString(record3, "id"),
8051
+ skill: pickStringValue(record3, "skill") ?? fallbackSkill,
8052
+ ...pickString(record3, "requestedSlug"),
8053
+ ...pickString(record3, "status"),
8054
+ ...pickNumber(record3, "exitCode"),
8055
+ ...pickString(record3, "correlationId"),
8056
+ ...pickString(record3, "createdAt"),
8057
+ ...pickString(record3, "startedAt"),
8058
+ ...pickString(record3, "completedAt"),
8059
+ ...pickNumber(record3, "durationMs"),
8060
+ ...pickString(record3, "outputType"),
8061
+ ...hasOwn(record3, "outputPreview") ? { outputPreview: record3.outputPreview } : {},
8062
+ ...pickString(record3, "errorCode"),
8063
+ ...pickString(record3, "errorMessage"),
8064
+ ...pickString(record3, "error"),
8065
+ ...pickString(record3, "code"),
8066
+ ...hasOwn(record3, "details") ? { details: record3.details } : {}
8067
+ };
8068
+ }
8069
+ function isRecord3(value) {
8070
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
8071
+ }
8072
+ function hasOwn(record3, key) {
8073
+ return Object.prototype.hasOwnProperty.call(record3, key);
8074
+ }
8075
+ function pickString(record3, key) {
8076
+ const value = pickStringValue(record3, key);
8077
+ return value === undefined ? {} : { [key]: value };
8078
+ }
8079
+ function pickStringValue(record3, key) {
8080
+ const value = record3[key];
8081
+ return typeof value === "string" ? value : undefined;
8082
+ }
8083
+ function pickNumber(record3, key) {
8084
+ const value = record3[key];
8085
+ return typeof value === "number" && Number.isFinite(value) ? { [key]: value } : {};
8086
+ }
8087
+ var REMOTE_SKILL_RUN_CONTRACT_VERSION = 1;
8088
+
8012
8089
  // src/lib/blog-article.ts
8013
8090
  var exports_blog_article = {};
8014
8091
  __export(exports_blog_article, {
@@ -8223,16 +8300,194 @@ var init_remote_files = __esm(() => {
8223
8300
  MAX_REMOTE_FILE_BYTES = 64 * 1024 * 1024;
8224
8301
  });
8225
8302
 
8226
- // src/lib/auth-store.ts
8227
- function getApiUrl(action, env = process.env, options = {}) {
8228
- return requireSkillsApiOrigin(action, env, options);
8303
+ // src/lib/remote-workspace-selection.ts
8304
+ function workspaceExpectedUserId(value) {
8305
+ if (!uuid2(value))
8306
+ throw new WorkspaceContextInputError;
8307
+ return value;
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 workspaceContext(value) {
8310
+ if (!record3(value) || Object.keys(value).sort().join(",") !== "membershipId,userId" || !uuid2(value.userId) || !uuid2(value.membershipId))
8311
+ throw new WorkspaceContextInputError;
8312
+ return { userId: value.userId, membershipId: value.membershipId };
8313
+ }
8314
+ function invalid() {
8315
+ throw new Error(invalidWorkspaceResult);
8316
+ }
8317
+ function organization(v) {
8318
+ if (!record3(v) || !uuid2(v.id) || !text(v.slug) || !text(v.name))
8319
+ return invalid();
8320
+ return { id: v.id, slug: v.slug, name: v.name };
8321
+ }
8322
+ function parseAccountWorkspaces(value) {
8323
+ if (!record3(value) || !Array.isArray(value.workspaces) || !value.workspaces.length || value.workspaces.length > 1000)
8324
+ return invalid();
8325
+ const workspaces = value.workspaces.map((v) => {
8326
+ if (!record3(v) || !uuid2(v.membershipId) || !role(v.role) || typeof v.current !== "boolean")
8327
+ return invalid();
8328
+ return { membershipId: v.membershipId, organization: organization(v.organization), role: v.role, current: v.current };
8329
+ });
8330
+ if (workspaces.filter((w) => w.current).length !== 1 || new Set(workspaces.map((w) => w.membershipId)).size !== workspaces.length || new Set(workspaces.map((w) => w.organization.id)).size !== workspaces.length)
8331
+ return invalid();
8332
+ return { workspaces };
8333
+ }
8334
+ function parseWorkspaceIdentity(value, expectedUserId) {
8335
+ if (!record3(value))
8336
+ return invalid();
8337
+ const user = value.user;
8338
+ if (!record3(user) || !uuid2(user.id) || !uuid2(user.membershipId) || !text(user.email, 320) || !(user.displayName === null || text(user.displayName)) || !role(user.role))
8339
+ return invalid();
8340
+ if (user.id !== expectedUserId)
8341
+ throw new WorkspaceIdentityMismatchError;
8342
+ return { user: { id: user.id, membershipId: user.membershipId, email: user.email, displayName: user.displayName, role: user.role }, organization: organization(value.organization) };
8343
+ }
8344
+ function sessionToken(value) {
8345
+ if (typeof value !== "string" || !value || value.length > 8192 || /[^\x21-\x7e]/.test(value) || value.startsWith("sk_"))
8346
+ return invalid();
8347
+ return value;
8348
+ }
8349
+ function parseWorkspaceSession(value, expected) {
8350
+ const identity = parseWorkspaceIdentity(value, expected.userId);
8351
+ if (identity.user.membershipId !== expected.membershipId)
8352
+ throw new WorkspaceIdentityMismatchError;
8353
+ return { token: sessionToken(value.token), ...identity };
8354
+ }
8355
+ function parseWorkspaceLogin(value, expectedUserId) {
8356
+ const user = record3(value) && value.user;
8357
+ if (!record3(value) || !record3(user) || !uuid2(user.id))
8358
+ return invalid();
8359
+ if (expectedUserId !== undefined && user.id !== expectedUserId)
8360
+ throw new WorkspaceIdentityMismatchError;
8361
+ return { token: sessionToken(value.token), userId: user.id };
8362
+ }
8363
+ function workspaceSelectionFailure(value, status) {
8364
+ if (!record3(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceSelectionFailures, value.code))
8365
+ return null;
8366
+ const code = value.code;
8367
+ return workspaceSelectionFailures[code][0] === status ? code : null;
8368
+ }
8369
+ var record3 = (v) => !!v && typeof v === "object" && !Array.isArray(v), uuid2 = (v) => typeof v === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v), text = (v, max = 1024) => typeof v === "string" && !!v.trim() && v.length <= max && !/[\p{Cc}\p{Cs}\u2028\u2029]/u.test(v), role = (v) => typeof v === "string" && ["owner", "admin", "member", "viewer"].includes(v), invalidWorkspaceResult = "The server returned an invalid workspace selection result.", WorkspaceContextInputError, WorkspaceIdentityMismatchError, workspaceSelectionFailures;
8370
+ var init_remote_workspace_selection = __esm(() => {
8371
+ WorkspaceContextInputError = class WorkspaceContextInputError extends Error {
8372
+ constructor() {
8373
+ super("Provide the observed user ID and exact lowercase membership ID.");
8374
+ this.name = "WorkspaceContextInputError";
8375
+ }
8376
+ };
8377
+ WorkspaceIdentityMismatchError = class WorkspaceIdentityMismatchError extends Error {
8378
+ constructor() {
8379
+ super("The verified account does not match the requested workspace context.");
8380
+ this.name = "WorkspaceIdentityMismatchError";
8381
+ }
8382
+ };
8383
+ workspaceSelectionFailures = {
8384
+ INVALID_WORKSPACE_SELECTION: [400, "Provide only the exact membership ID from your workspace list."],
8385
+ SESSION_EXPIRED: [401, "Sign in again before selecting a workspace."],
8386
+ ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
8387
+ INTERACTIVE_SESSION_REQUIRED: [403, "Interactive sign-in is required to select a workspace."],
8388
+ WORKSPACE_UNAVAILABLE: [404, "Workspace is unavailable. Refresh your workspace list."],
8389
+ WORKSPACE_BUSY: [503, "Workspace is busy. Refresh before retrying."]
8390
+ };
8391
+ });
8392
+
8393
+ // src/lib/remote-workspace.ts
8394
+ function workspaceMembersQuery(options = {}) {
8395
+ if (!record4(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))
8396
+ throw new Error("Use a roster limit from 1 to 100 and an unchanged continuation cursor.");
8397
+ const query = new URLSearchParams;
8398
+ if (options.limit !== undefined)
8399
+ query.set("limit", String(options.limit));
8400
+ if (options.cursor !== undefined)
8401
+ query.set("cursor", options.cursor);
8402
+ return query.size ? `?${query}` : "";
8403
+ }
8404
+ function timestamp(value) {
8405
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$/.test(value))
8406
+ return false;
8407
+ const time3 = Date.parse(value);
8408
+ return Number.isFinite(time3) && new Date(time3).toISOString().slice(0, 23) === value.slice(0, 23);
8409
+ }
8410
+ function parseMember(row, fail) {
8411
+ if (!record4(row) || !uuid3(row.membershipId) || !uuid3(row.userId) || typeof row.email !== "string" || !row.email || !(row.displayName === null || typeof row.displayName === "string") || !isRole(row.role) || !timestamp(row.createdAt))
8412
+ return fail();
8413
+ return {
8414
+ membershipId: row.membershipId,
8415
+ userId: row.userId,
8416
+ email: row.email,
8417
+ displayName: row.displayName,
8418
+ role: row.role,
8419
+ createdAt: row.createdAt
8420
+ };
8421
+ }
8422
+ function mutationInput(membershipId, input, roleChange) {
8423
+ if (typeof membershipId !== "string" || !uuid3(membershipId) || membershipId !== membershipId.toLowerCase() || !record4(input) || Object.keys(input).sort().join(",") !== (roleChange ? "expectedRole,role" : "expectedRole"))
8424
+ throw new WorkspaceMemberInputError;
8425
+ const expectedRole = input.expectedRole, role2 = roleChange ? input.role : undefined;
8426
+ if (!isRole(expectedRole) || roleChange && !isRole(role2))
8427
+ throw new WorkspaceMemberInputError;
8428
+ return { membershipId, role: role2, expectedRole };
8429
+ }
8430
+ function workspaceMemberRoleInput(membershipId, input) {
8431
+ const value = mutationInput(membershipId, input, true);
8432
+ return { membershipId: value.membershipId, body: { role: value.role, expectedRole: value.expectedRole } };
8433
+ }
8434
+ function workspaceMemberRemovalInput(membershipId, input) {
8435
+ const value = mutationInput(membershipId, input, false);
8436
+ return { membershipId: value.membershipId, body: { expectedRole: value.expectedRole } };
8437
+ }
8438
+ function parseWorkspaceMemberRoleResult(value, membershipId, role2) {
8439
+ const fail = () => {
8440
+ throw new Error(invalidMemberResult);
8441
+ };
8442
+ if (!record4(value) || !uuid3(value.organizationId) || typeof value.changed !== "boolean")
8443
+ return fail();
8444
+ const member = parseMember(value.member, fail);
8445
+ if (member.membershipId !== membershipId || member.role !== role2)
8446
+ return fail();
8447
+ return { organizationId: value.organizationId, member, changed: value.changed };
8448
+ }
8449
+ function parseWorkspaceMemberRemovalResult(value, membershipId) {
8450
+ if (!record4(value) || !uuid3(value.organizationId) || value.membershipId !== membershipId || value.removed !== true || typeof value.alreadyRemoved !== "boolean")
8451
+ throw new Error(invalidMemberResult);
8452
+ return { organizationId: value.organizationId, membershipId, removed: true, alreadyRemoved: value.alreadyRemoved };
8453
+ }
8454
+ function workspaceMemberFailure(value, status) {
8455
+ if (!record4(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceMemberFailures, value.code))
8456
+ return null;
8457
+ const code = value.code;
8458
+ return workspaceMemberFailures[code][0] === status ? code : null;
8459
+ }
8460
+ function parseWorkspaceMembersPage(value) {
8461
+ const fail = () => {
8462
+ throw new Error("The server returned an invalid workspace roster.");
8463
+ };
8464
+ if (!record4(value) || !uuid3(value.organizationId) || !Array.isArray(value.members) || value.members.length > 100 || !(value.nextCursor === null || cursor(value.nextCursor)) || !value.members.length && value.nextCursor !== null)
8465
+ return fail();
8466
+ const members = value.members.map((row) => parseMember(row, fail));
8467
+ if (new Set(members.map((row) => row.membershipId)).size !== members.length)
8468
+ return fail();
8469
+ return { organizationId: value.organizationId, members, nextCursor: value.nextCursor };
8470
+ }
8471
+ var record4 = (value) => !!value && typeof value === "object" && !Array.isArray(value), cursor = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,512}$/.test(value), uuid3 = (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;
8472
+ var init_remote_workspace = __esm(() => {
8473
+ WorkspaceMemberInputError = class WorkspaceMemberInputError extends Error {
8474
+ constructor() {
8475
+ super("Use an unchanged lowercase membership ID and the exact role and expected-role parameters from the roster.");
8476
+ this.name = "WorkspaceMemberInputError";
8477
+ }
8478
+ };
8479
+ workspaceMemberFailures = {
8480
+ INVALID_REQUEST: [400, "Provide the exact membership role parameters."],
8481
+ ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
8482
+ INTERACTIVE_SESSION_REQUIRED: [403, "Fresh interactive sign-in is required."],
8483
+ WORKSPACE_ADMIN_REQUIRED: [403, "A current workspace owner or admin is required."],
8484
+ MEMBERSHIP_ACTION_FORBIDDEN: [403, "Your current workspace role cannot perform this membership action."],
8485
+ MEMBERSHIP_NOT_FOUND: [404, "Membership was not found in the current workspace."],
8486
+ SELF_REMOVAL_UNAVAILABLE: [409, "Leaving your own workspace is not available through member removal."],
8487
+ MEMBERSHIP_ROLE_CHANGED: [409, "The member role changed. Refresh the roster before another action."],
8488
+ LAST_OWNER_REQUIRED: [409, "The workspace must retain at least one active owner."],
8489
+ MEMBERSHIP_BUSY: [503, "Membership is busy. Refresh the roster before another action."]
8490
+ };
8236
8491
  });
8237
8492
 
8238
8493
  // src/lib/remote-account.ts
@@ -8314,14 +8569,48 @@ var init_remote_account = __esm(() => {
8314
8569
  };
8315
8570
  });
8316
8571
 
8572
+ // src/lib/remote-profile.ts
8573
+ function customerNamePatch(input, field) {
8574
+ if (!isRecord4(input) || Object.keys(input).length !== 1 || !Object.hasOwn(input, field))
8575
+ throw new Error("Provide only the requested name field.");
8576
+ const value = input[field];
8577
+ if (typeof value !== "string" || /[\p{Cc}\p{Cs}\u2028\u2029]/u.test(value) || !value.trim() || [...value.trim()].length > 100) {
8578
+ throw new Error("Use a name of 1\u2013100 characters without control characters or newlines.");
8579
+ }
8580
+ return { [field]: value.trim() };
8581
+ }
8582
+ function isRecord4(value) {
8583
+ return !!value && typeof value === "object" && !Array.isArray(value);
8584
+ }
8585
+ function string4(value) {
8586
+ return typeof value === "string" && value.length > 0;
8587
+ }
8588
+ function parseUpdatedProfile(value) {
8589
+ const user = isRecord4(value) && value.user;
8590
+ 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)) {
8591
+ throw new Error("The server returned an invalid account profile.");
8592
+ }
8593
+ return { user: { id: user.id, email: user.email, displayName: user.displayName, role: user.role } };
8594
+ }
8595
+ function parseUpdatedWorkspace(value) {
8596
+ const organization2 = isRecord4(value) && value.organization;
8597
+ if (!isRecord4(organization2) || !string4(organization2.id) || !string4(organization2.slug) || !string4(organization2.name)) {
8598
+ throw new Error("The server returned an invalid workspace.");
8599
+ }
8600
+ return { organization: { id: organization2.id, slug: organization2.slug, name: organization2.name } };
8601
+ }
8602
+
8317
8603
  // src/lib/remote-client.ts
8318
8604
  var exports_remote_client = {};
8319
8605
  __export(exports_remote_client, {
8320
8606
  createRemoteSkillsClientReadOnly: () => createRemoteSkillsClientReadOnly,
8321
8607
  createRemoteSkillsClient: () => createRemoteSkillsClient,
8608
+ RemoteWorkspaceSelectionError: () => RemoteWorkspaceSelectionError,
8609
+ RemoteWorkspaceMemberError: () => RemoteWorkspaceMemberError,
8322
8610
  RemoteSkillsClient: () => RemoteSkillsClient,
8323
8611
  RemoteRouteUnsupportedError: () => RemoteRouteUnsupportedError,
8324
- RemoteRequestError: () => RemoteRequestError
8612
+ RemoteRequestError: () => RemoteRequestError,
8613
+ RemoteCapabilityUnavailableError: () => RemoteCapabilityUnavailableError
8325
8614
  });
8326
8615
 
8327
8616
  class RemoteSkillsClient {
@@ -8336,6 +8625,7 @@ class RemoteSkillsClient {
8336
8625
  return fetch(`${this.apiUrl}${path}`, {
8337
8626
  ...options,
8338
8627
  redirect: "error",
8628
+ credentials: "omit",
8339
8629
  signal: options?.signal ?? AbortSignal.timeout(15000),
8340
8630
  headers: {
8341
8631
  Authorization: `Bearer ${this.apiKey}`,
@@ -8351,9 +8641,14 @@ class RemoteSkillsClient {
8351
8641
  if (response.status === 404 && opts.domainNotFoundCodes?.length && await responseBodyCarriesCode(response, opts.domainNotFoundCodes)) {
8352
8642
  return response;
8353
8643
  }
8644
+ response.body?.cancel().catch(() => {});
8354
8645
  throw new RemoteRouteUnsupportedError(routePath, response.status, this.apiUrl);
8355
8646
  }
8356
8647
  if (!response.ok) {
8648
+ if (path === "/api/v1/billing/checkout" && options?.method === "POST" && response.status === 503 && await responseBodyCarriesCode(response, ["SUBSCRIPTION_CHECKOUT_UNAVAILABLE"])) {
8649
+ throw new RemoteCapabilityUnavailableError;
8650
+ }
8651
+ response.body?.cancel().catch(() => {});
8357
8652
  throw new RemoteRequestError(routePath, response.status, response.statusText);
8358
8653
  }
8359
8654
  return response;
@@ -8437,6 +8732,117 @@ class RemoteSkillsClient {
8437
8732
  async getIdentity() {
8438
8733
  return (await this.requestNewRoute("/api/auth/whoami")).json();
8439
8734
  }
8735
+ async listAccountWorkspaces(expectedUserId) {
8736
+ const expected = expectedUserId === undefined ? undefined : workspaceExpectedUserId(expectedUserId);
8737
+ const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
8738
+ let identity;
8739
+ if (expected !== undefined) {
8740
+ const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
8741
+ if (!value || typeof value !== "object" || value.authMethod !== "jwt")
8742
+ throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces", "INTERACTIVE_SESSION_REQUIRED");
8743
+ identity = parseWorkspaceIdentity(value, expected);
8744
+ }
8745
+ const result = parseAccountWorkspaces(await connection.requestWorkspaceSelection("/api/v1/account/workspaces"));
8746
+ const current = result.workspaces.find((workspace) => workspace.current);
8747
+ if (identity && (current.membershipId !== identity.user.membershipId || current.organization.id !== identity.organization.id))
8748
+ throw new WorkspaceIdentityMismatchError;
8749
+ return result;
8750
+ }
8751
+ async switchWorkspace(context) {
8752
+ const target = workspaceContext(context);
8753
+ const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
8754
+ const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
8755
+ if (!value || typeof value !== "object" || value.authMethod !== "jwt")
8756
+ throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces/switch", "INTERACTIVE_SESSION_REQUIRED");
8757
+ parseWorkspaceIdentity(value, target.userId);
8758
+ const selected = parseWorkspaceSession(await connection.requestWorkspaceSelection("/api/v1/account/workspaces/switch", {
8759
+ method: "POST",
8760
+ body: JSON.stringify({ membershipId: target.membershipId })
8761
+ }), target);
8762
+ const verified = await new RemoteSkillsClient(selected.token, connection.apiUrl).requestWorkspaceSelection("/api/auth/whoami");
8763
+ if (!verified || typeof verified !== "object" || verified.authMethod !== "jwt")
8764
+ throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces/switch", "INTERACTIVE_SESSION_REQUIRED");
8765
+ const identity = parseWorkspaceIdentity(verified, target.userId);
8766
+ if (identity.user.membershipId !== target.membershipId || identity.organization.id !== selected.organization.id)
8767
+ throw new WorkspaceIdentityMismatchError;
8768
+ return { token: selected.token, ...identity };
8769
+ }
8770
+ async requestWorkspaceSelection(path, options) {
8771
+ let response;
8772
+ try {
8773
+ response = await this.request(path, { ...options, credentials: "omit" });
8774
+ } catch {
8775
+ throw new Error("Unable to reach the Skills workspace API.");
8776
+ }
8777
+ let value;
8778
+ try {
8779
+ value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, response.ok ? 1024 * 1024 : 4096)));
8780
+ } catch {
8781
+ if (response.ok)
8782
+ throw new Error(invalidWorkspaceResult);
8783
+ }
8784
+ if (!response.ok) {
8785
+ const code = workspaceSelectionFailure(value, response.status);
8786
+ if (code)
8787
+ throw new RemoteWorkspaceSelectionError(path, code);
8788
+ if (response.status === 404 || response.status === 405)
8789
+ throw new RemoteRouteUnsupportedError(path, response.status, this.apiUrl);
8790
+ throw new RemoteRequestError(path, response.status);
8791
+ }
8792
+ return value;
8793
+ }
8794
+ async updateProfile(input) {
8795
+ const body = customerNamePatch(input, "displayName");
8796
+ return parseUpdatedProfile(await (await this.requestNewRoute("/api/v1/account/profile", { method: "PATCH", body: JSON.stringify(body) })).json());
8797
+ }
8798
+ async updateCurrentWorkspace(input) {
8799
+ const body = customerNamePatch(input, "name");
8800
+ return parseUpdatedWorkspace(await (await this.requestNewRoute("/api/v1/workspaces/current", { method: "PATCH", body: JSON.stringify(body) })).json());
8801
+ }
8802
+ async listWorkspaceMembers(options = {}) {
8803
+ const query = workspaceMembersQuery(options);
8804
+ const requestedCursor = options.cursor;
8805
+ const response = await this.requestNewRoute(`/api/v1/workspace/members${query}`);
8806
+ let value;
8807
+ try {
8808
+ value = await response.json();
8809
+ } catch {
8810
+ throw new Error("The server returned an invalid workspace roster.");
8811
+ }
8812
+ const page = parseWorkspaceMembersPage(value);
8813
+ if (requestedCursor !== undefined && page.nextCursor === requestedCursor)
8814
+ throw new Error("The server returned an invalid workspace roster.");
8815
+ return page;
8816
+ }
8817
+ async setWorkspaceMemberRole(membershipId, input) {
8818
+ const captured = workspaceMemberRoleInput(membershipId, input);
8819
+ const value = await this.requestWorkspaceMember(captured.membershipId, "PATCH", captured.body);
8820
+ return parseWorkspaceMemberRoleResult(value, captured.membershipId, captured.body.role);
8821
+ }
8822
+ async removeWorkspaceMember(membershipId, input) {
8823
+ const captured = workspaceMemberRemovalInput(membershipId, input);
8824
+ return parseWorkspaceMemberRemovalResult(await this.requestWorkspaceMember(captured.membershipId, "DELETE", captured.body), captured.membershipId);
8825
+ }
8826
+ async requestWorkspaceMember(membershipId, method, body) {
8827
+ const path = `/api/v1/workspace/members/${membershipId}`;
8828
+ const response = await this.request(path, { method, body: JSON.stringify(body) });
8829
+ let value;
8830
+ try {
8831
+ value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, response.ok ? 64 * 1024 : 4096)));
8832
+ } catch {
8833
+ if (response.ok)
8834
+ throw new Error(invalidMemberResult);
8835
+ }
8836
+ if (!response.ok) {
8837
+ const code = workspaceMemberFailure(value, response.status);
8838
+ if (code)
8839
+ throw new RemoteWorkspaceMemberError(path, code);
8840
+ if (response.status === 404 || response.status === 405)
8841
+ throw new RemoteRouteUnsupportedError(path, response.status, this.apiUrl);
8842
+ throw new RemoteRequestError(path, response.status);
8843
+ }
8844
+ return value;
8845
+ }
8440
8846
  async listApiKeys() {
8441
8847
  return this.arrayResponse("/api/auth/keys");
8442
8848
  }
@@ -8628,8 +9034,10 @@ class RemoteSkillsClient {
8628
9034
  return [];
8629
9035
  if (!response.ok)
8630
9036
  throw new Error(`versions request failed: ${response.status}`);
8631
- const body = await response.json();
8632
- return Array.isArray(body.versions) ? body.versions : [];
9037
+ const body = await readSkillVersionPayload(response);
9038
+ if (!isVersionRecord(body) || !Array.isArray(body.versions) || body.slug !== undefined && body.slug !== slug)
9039
+ throw new Error(INVALID_SKILL_VERSION_RESPONSE);
9040
+ return body.versions.map((entry) => normalizeSkillVersion(entry, slug));
8633
9041
  }
8634
9042
  async getSkillVersion(slug, version2) {
8635
9043
  const response = await this.requestNewRoute(`/api/v1/skills/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version2)}`, undefined, { domainNotFoundCodes: ["SKILL_NOT_FOUND", "SKILL_VERSION_NOT_FOUND"] });
@@ -8637,7 +9045,7 @@ class RemoteSkillsClient {
8637
9045
  return null;
8638
9046
  if (!response.ok)
8639
9047
  throw new Error(`version request failed: ${response.status}`);
8640
- return await response.json();
9048
+ return normalizeSkillVersion(await readSkillVersionPayload(response), slug, version2);
8641
9049
  }
8642
9050
  async listPins() {
8643
9051
  const response = await this.requestNewRoute("/api/v1/pins");
@@ -8685,31 +9093,47 @@ class RemoteSkillsClient {
8685
9093
  return normalizeUpdatedSincePage(await response.json());
8686
9094
  }
8687
9095
  }
8688
- function requireOptionalString(record3, field) {
8689
- if (record3[field] === undefined)
9096
+ function requireOptionalString(record5, field) {
9097
+ if (record5[field] === undefined)
8690
9098
  return;
8691
- if (typeof record3[field] !== "string") {
9099
+ if (typeof record5[field] !== "string") {
8692
9100
  throw new Error(`Remote payload did not match the expected contract (${field} must be a string when present)`);
8693
9101
  }
8694
- return record3[field];
9102
+ return record5[field];
9103
+ }
9104
+ function isVersionRecord(value) {
9105
+ return value !== null && typeof value === "object" && !Array.isArray(value);
9106
+ }
9107
+ async function readSkillVersionPayload(response) {
9108
+ try {
9109
+ return await response.json();
9110
+ } catch {
9111
+ throw new Error(INVALID_SKILL_VERSION_RESPONSE);
9112
+ }
9113
+ }
9114
+ function normalizeSkillVersion(entry, slug, version2) {
9115
+ 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)) {
9116
+ throw new Error(INVALID_SKILL_VERSION_RESPONSE);
9117
+ }
9118
+ return entry;
8695
9119
  }
8696
9120
  function normalizePin(entry) {
8697
9121
  if (!entry || typeof entry !== "object") {
8698
9122
  throw new Error("Remote pin payload did not match the expected contract (expected an object)");
8699
9123
  }
8700
- const record3 = entry;
8701
- const slug = typeof record3.slug === "string" && record3.slug.trim() ? record3.slug.trim() : undefined;
9124
+ const record5 = entry;
9125
+ const slug = typeof record5.slug === "string" && record5.slug.trim() ? record5.slug.trim() : undefined;
8702
9126
  if (!slug) {
8703
9127
  throw new Error("Remote pin payload did not match the expected contract (missing slug)");
8704
9128
  }
8705
9129
  let metadata;
8706
- if (record3.metadata !== undefined) {
8707
- if (!record3.metadata || typeof record3.metadata !== "object" || Array.isArray(record3.metadata)) {
9130
+ if (record5.metadata !== undefined) {
9131
+ if (!record5.metadata || typeof record5.metadata !== "object" || Array.isArray(record5.metadata)) {
8708
9132
  throw new Error("Remote pin payload did not match the expected contract (metadata must be a JSON object when present)");
8709
9133
  }
8710
- metadata = record3.metadata;
9134
+ metadata = record5.metadata;
8711
9135
  }
8712
- const pinnedAt = requireOptionalString(record3, "pinnedAt");
9136
+ const pinnedAt = requireOptionalString(record5, "pinnedAt");
8713
9137
  return {
8714
9138
  slug,
8715
9139
  ...pinnedAt !== undefined ? { pinnedAt } : {},
@@ -8726,16 +9150,16 @@ function normalizeSkillSummary(entry) {
8726
9150
  if (!entry || typeof entry !== "object") {
8727
9151
  throw new Error("Remote skill payload did not match the expected contract (expected an object)");
8728
9152
  }
8729
- const record3 = entry;
8730
- const slug = typeof record3.slug === "string" && record3.slug.trim() ? record3.slug.trim() : undefined;
9153
+ const record5 = entry;
9154
+ const slug = typeof record5.slug === "string" && record5.slug.trim() ? record5.slug.trim() : undefined;
8731
9155
  if (!slug) {
8732
9156
  throw new Error("Remote skill payload did not match the expected contract (missing slug)");
8733
9157
  }
8734
9158
  return {
8735
9159
  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") } : {}
9160
+ ...requireOptionalString(record5, "name") !== undefined ? { name: requireOptionalString(record5, "name") } : {},
9161
+ ...requireOptionalString(record5, "version") !== undefined ? { version: requireOptionalString(record5, "version") } : {},
9162
+ ...requireOptionalString(record5, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record5, "updatedAt") } : {}
8739
9163
  };
8740
9164
  }
8741
9165
  function normalizeSkillSummaryList(payload) {
@@ -8745,26 +9169,55 @@ function normalizeSkillSummaryList(payload) {
8745
9169
  return payload.map(normalizeSkillSummary);
8746
9170
  }
8747
9171
  async function responseBodyCarriesCode(response, codes) {
9172
+ const reader = response.body?.getReader();
9173
+ if (!reader)
9174
+ return false;
9175
+ const maximum = 8 * 1024;
9176
+ let deadline;
9177
+ const expired = new Promise((_, reject) => {
9178
+ deadline = setTimeout(() => reject(new Error("Error response read deadline exceeded")), 1000);
9179
+ });
8748
9180
  try {
8749
- const payload = await response.clone().json();
8750
- if (!payload || typeof payload !== "object")
9181
+ const chunks = [];
9182
+ let size = 0;
9183
+ while (true) {
9184
+ const next = await Promise.race([reader.read(), expired]);
9185
+ if (next.done)
9186
+ break;
9187
+ size += next.value.byteLength;
9188
+ if (size > maximum)
9189
+ return false;
9190
+ chunks.push(next.value);
9191
+ }
9192
+ const bytes = new Uint8Array(size);
9193
+ let offset = 0;
9194
+ for (const chunk of chunks) {
9195
+ bytes.set(chunk, offset);
9196
+ offset += chunk.byteLength;
9197
+ }
9198
+ const payload = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
9199
+ if (!payload || typeof payload !== "object" || Array.isArray(payload) || !Object.hasOwn(payload, "code"))
8751
9200
  return false;
8752
9201
  const code = payload.code;
8753
9202
  return typeof code === "string" && codes.includes(code);
8754
9203
  } catch {
8755
9204
  return false;
9205
+ } finally {
9206
+ clearTimeout(deadline);
9207
+ reader.cancel().catch(() => {});
9208
+ reader.releaseLock();
8756
9209
  }
8757
9210
  }
8758
9211
  function normalizeUpdatedSincePage(payload) {
8759
9212
  if (!payload || typeof payload !== "object") {
8760
9213
  throw new Error("Updated-since payload did not match the expected contract (expected an object)");
8761
9214
  }
8762
- const record3 = payload;
8763
- if (!Array.isArray(record3.skills)) {
9215
+ const record5 = payload;
9216
+ if (!Array.isArray(record5.skills)) {
8764
9217
  throw new Error("Updated-since payload did not match the expected contract (missing skills array)");
8765
9218
  }
8766
- const skills = record3.skills.map(normalizeSkillSummary);
8767
- const nextCursor = record3.nextCursor === undefined || record3.nextCursor === null ? null : record3.nextCursor;
9219
+ const skills = record5.skills.map(normalizeSkillSummary);
9220
+ const nextCursor = record5.nextCursor === undefined || record5.nextCursor === null ? null : record5.nextCursor;
8768
9221
  if (nextCursor !== null && typeof nextCursor !== "string") {
8769
9222
  throw new Error("Updated-since payload did not match the expected contract (nextCursor must be a string or absent)");
8770
9223
  }
@@ -8777,8 +9230,11 @@ async function createRemoteSkillsClient(env = process.env) {
8777
9230
  function createRemoteSkillsClientReadOnly(env = process.env) {
8778
9231
  return createRemoteSkillsClient(env);
8779
9232
  }
8780
- var RemoteRouteUnsupportedError, RemoteRequestError;
9233
+ var RemoteRouteUnsupportedError, RemoteRequestError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
8781
9234
  var init_remote_client = __esm(() => {
9235
+ init_remote_workspace_selection();
9236
+ init_remote_workspace();
9237
+ init_remote_workspace();
8782
9238
  init_auth_store();
8783
9239
  init_fleet_credentials();
8784
9240
  init_remote_account();
@@ -8798,13 +9254,39 @@ var init_remote_client = __esm(() => {
8798
9254
  RemoteRequestError = class RemoteRequestError extends Error {
8799
9255
  path;
8800
9256
  status;
8801
- constructor(path, status, statusText) {
8802
- super(`Remote request to ${path} failed: HTTP ${status}${statusText ? ` ${statusText}` : ""}`);
9257
+ constructor(path, status, _statusText) {
9258
+ super(`Remote request to ${path} failed: HTTP ${status}`);
8803
9259
  this.path = path;
8804
9260
  this.status = status;
8805
9261
  this.name = "RemoteRequestError";
8806
9262
  }
8807
9263
  };
9264
+ RemoteWorkspaceMemberError = class RemoteWorkspaceMemberError extends RemoteRequestError {
9265
+ code;
9266
+ constructor(path, code) {
9267
+ super(path, workspaceMemberFailures[code][0]);
9268
+ this.code = code;
9269
+ this.name = "RemoteWorkspaceMemberError";
9270
+ this.message = workspaceMemberFailures[code][1];
9271
+ }
9272
+ };
9273
+ RemoteWorkspaceSelectionError = class RemoteWorkspaceSelectionError extends RemoteRequestError {
9274
+ code;
9275
+ constructor(path, code) {
9276
+ super(path, workspaceSelectionFailures[code][0]);
9277
+ this.code = code;
9278
+ this.name = "RemoteWorkspaceSelectionError";
9279
+ this.message = workspaceSelectionFailures[code][1];
9280
+ }
9281
+ };
9282
+ RemoteCapabilityUnavailableError = class RemoteCapabilityUnavailableError extends RemoteRequestError {
9283
+ code = "SUBSCRIPTION_CHECKOUT_UNAVAILABLE";
9284
+ constructor() {
9285
+ super("/api/v1/billing/checkout", 503);
9286
+ this.name = "RemoteCapabilityUnavailableError";
9287
+ 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.";
9288
+ }
9289
+ };
8808
9290
  });
8809
9291
 
8810
9292
  // ../../node_modules/.bun/content-type@1.0.5/node_modules/content-type/index.js
@@ -8831,7 +9313,7 @@ var require_content_type = __commonJS((exports) => {
8831
9313
  if (!type || !TYPE_REGEXP.test(type)) {
8832
9314
  throw new TypeError("invalid type");
8833
9315
  }
8834
- var string4 = type;
9316
+ var string5 = type;
8835
9317
  if (parameters && typeof parameters === "object") {
8836
9318
  var param;
8837
9319
  var params = Object.keys(parameters).sort();
@@ -8840,16 +9322,16 @@ var require_content_type = __commonJS((exports) => {
8840
9322
  if (!TOKEN_REGEXP.test(param)) {
8841
9323
  throw new TypeError("invalid parameter name");
8842
9324
  }
8843
- string4 += "; " + param + "=" + qstring(parameters[param]);
9325
+ string5 += "; " + param + "=" + qstring(parameters[param]);
8844
9326
  }
8845
9327
  }
8846
- return string4;
9328
+ return string5;
8847
9329
  }
8848
- function parse6(string4) {
8849
- if (!string4) {
9330
+ function parse6(string5) {
9331
+ if (!string5) {
8850
9332
  throw new TypeError("argument string is required");
8851
9333
  }
8852
- var header = typeof string4 === "object" ? getcontenttype(string4) : string4;
9334
+ var header = typeof string5 === "object" ? getcontenttype(string5) : string5;
8853
9335
  if (typeof header !== "string") {
8854
9336
  throw new TypeError("argument string is required to be a string");
8855
9337
  }
@@ -14323,7 +14805,7 @@ class StdioServerTransport {
14323
14805
  // package.json
14324
14806
  var package_default = {
14325
14807
  name: "@hasna/skills",
14326
- version: "0.4.0",
14808
+ version: "0.5.1",
14327
14809
  description: "Skills library for AI coding agents",
14328
14810
  type: "module",
14329
14811
  bin: {
@@ -14355,6 +14837,7 @@ var package_default = {
14355
14837
  files: [
14356
14838
  "dist/",
14357
14839
  "!dist/**/*.test.d.ts",
14840
+ "!dist/**/*.fixture.d.ts",
14358
14841
  "!dist/test-preload.d.ts",
14359
14842
  "!dist/platform",
14360
14843
  "bin/",
@@ -14380,8 +14863,9 @@ var package_default = {
14380
14863
  migrate: "bun run ./src/server/migrate.ts",
14381
14864
  typecheck: "tsc --noEmit",
14382
14865
  "verify:release": "bun run scripts/release-guard.ts",
14866
+ "verify:consumer-types": "bun run scripts/consumer-types.ts",
14383
14867
  prepare: "bun run build:js",
14384
- prepack: "bun run build && bun run verify:release",
14868
+ prepack: "bun run build && bun run verify:release && bun run verify:consumer-types",
14385
14869
  prepublishOnly: "bun run typecheck && bun run test"
14386
14870
  },
14387
14871
  keywords: [
@@ -14403,6 +14887,7 @@ var package_default = {
14403
14887
  author: "Hasna",
14404
14888
  license: "Apache-2.0",
14405
14889
  devDependencies: {
14890
+ "@hasna/contracts": "1.0.2",
14406
14891
  "@types/bun": "1.3.14",
14407
14892
  "@types/node": "25.2.3",
14408
14893
  "@types/react": "^18.2.0",
@@ -14414,8 +14899,7 @@ var package_default = {
14414
14899
  dependencies: {
14415
14900
  "@aws-sdk/client-ecs": "^3.1079.0",
14416
14901
  "@aws-sdk/client-s3": "^3.1079.0",
14417
- "@hasna/contracts": "1.0.1",
14418
- "@hasna/events": "0.1.16",
14902
+ "@hasna/events": "0.1.18",
14419
14903
  "@modelcontextprotocol/sdk": "^1.26.0",
14420
14904
  chalk: "^5.3.0",
14421
14905
  commander: "^12.1.0",
@@ -22331,12 +22815,7 @@ import { homedir } from "os";
22331
22815
  import { join, resolve } from "path";
22332
22816
  import { homedir as pathsResolverHomedir } from "os";
22333
22817
  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
- };
22818
+ var PATHS_RESOLVER_DATA_ENV = "HASNA_DATA_HOME";
22340
22819
  var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
22341
22820
  function pathsResolverAssertApp(app) {
22342
22821
  if (typeof app !== "string" || app.length === 0) {
@@ -22346,48 +22825,19 @@ function pathsResolverAssertApp(app) {
22346
22825
  throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
22347
22826
  }
22348
22827
  }
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);
22828
+ function pathsResolverDataBaseDir(options) {
22356
22829
  const env = options.env ?? process.env;
22357
- const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
22830
+ const override = env[PATHS_RESOLVER_DATA_ENV];
22358
22831
  if (typeof override === "string" && override.length > 0)
22359
22832
  return override;
22360
22833
  const home = options.home ?? pathsResolverHomedir();
22361
22834
  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);
22835
+ return platform === "darwin" ? pathsResolverJoin(home, "Library", "Application Support", "Hasna") : pathsResolverJoin(home, ".local", "share", "hasna");
22388
22836
  }
22389
22837
  function dataDir(options) {
22390
- return pathsResolverResolve("data", options);
22838
+ pathsResolverAssertApp(options.app);
22839
+ const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
22840
+ return pathsResolverJoin(pathsResolverDataBaseDir(options), appSegment);
22391
22841
  }
22392
22842
  var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
22393
22843
  var HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME";
@@ -23967,14 +24417,27 @@ function normalizePortableSkillName(name) {
23967
24417
  }
23968
24418
  return normalized;
23969
24419
  }
24420
+ function normalizeNewPortableSkillName(name) {
24421
+ normalizePortableSkillName(name);
24422
+ 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, "");
24423
+ if (!normalized)
24424
+ throw new Error(`Invalid skill name '${name}'. Include letters or numbers.`);
24425
+ return normalized;
24426
+ }
23970
24427
  function readPortableSkillManifest(skillPath, fallbackName = basename(skillPath)) {
24428
+ return readManifest(skillPath, fallbackName, normalizePortableSkillName);
24429
+ }
24430
+ function readPortableSkillManifestForImport(skillPath) {
24431
+ return readManifest(skillPath, basename(skillPath), normalizeNewPortableSkillName);
24432
+ }
24433
+ function readManifest(skillPath, fallbackName, normalizeName) {
23971
24434
  const skillJsonPath = join6(skillPath, "skill.json");
23972
24435
  const skillMdPath = join6(skillPath, "SKILL.md");
23973
24436
  const pkgPath = join6(skillPath, "package.json");
23974
24437
  const jsonManifest = existsSync6(skillJsonPath) ? readJsonObject(skillJsonPath) : undefined;
23975
24438
  const frontmatter = existsSync6(skillMdPath) ? parseSkillFrontmatter(readFileSync5(skillMdPath, "utf-8")) ?? undefined : undefined;
23976
24439
  const pkg = existsSync6(pkgPath) ? readJsonObject(pkgPath) : undefined;
23977
- const name = normalizePortableSkillName(stringField(jsonManifest, "name") ?? frontmatter?.name ?? stringValue(pkg?.name) ?? fallbackName);
24440
+ const name = normalizeName(stringField(jsonManifest, "name") ?? frontmatter?.name ?? stringValue(pkg?.name) ?? fallbackName);
23978
24441
  const description = stringField(jsonManifest, "description") ?? frontmatter?.description ?? stringValue(pkg?.description) ?? `${name} skill`;
23979
24442
  const version2 = readDeclaredSkillVersion(skillPath) ?? PORTABLE_SKILL_DEFAULT_VERSION;
23980
24443
  const kind = parseSkillKind(stringField(jsonManifest, "kind") ?? frontmatter?.kind);
@@ -24222,10 +24685,45 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
24222
24685
  };
24223
24686
  if (!existsSync6(join6(skillPath, "SKILL.md"))) {
24224
24687
  writeFileSync2(join6(skillPath, "SKILL.md"), renderSkillMd(next));
24688
+ } else {
24689
+ const path = join6(skillPath, "SKILL.md");
24690
+ const content = readFileSync5(path, "utf8");
24691
+ const declaredName = parseSkillFrontmatter(content)?.name;
24692
+ if (declaredName && declaredName !== next.name) {
24693
+ writeFileSync2(path, renameInstructionFrontmatter(content, next.name));
24694
+ }
24695
+ }
24696
+ const packagePath = join6(skillPath, "package.json");
24697
+ if (existsSync6(packagePath)) {
24698
+ const pkg = readJsonObject(packagePath);
24699
+ if (typeof pkg.name === "string" && pkg.name !== next.name) {
24700
+ writeFileSync2(packagePath, `${JSON.stringify({ ...pkg, name: next.name }, null, 2)}
24701
+ `);
24702
+ }
24225
24703
  }
24226
24704
  writeSkillJsonWithHash(skillPath, next);
24227
24705
  return readPortableSkillManifest(skillPath, next.name);
24228
24706
  }
24707
+ function renameInstructionFrontmatter(content, name) {
24708
+ const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?=\r?\n|$)/);
24709
+ const names = frontmatter?.[1]?.match(/^[ \t]*name[ \t]*:[^\r\n]*/gm) ?? [];
24710
+ const declaration = names.length === 1 ? names[0].match(/^(name[ \t]*:[ \t]*)(.*?)([ \t]*)$/) : null;
24711
+ const scalar = declaration?.[2] ?? "";
24712
+ let simple = /^[a-zA-Z0-9_.@/ -]+$/.test(scalar);
24713
+ if (scalar.startsWith('"')) {
24714
+ try {
24715
+ simple = typeof JSON.parse(scalar) === "string";
24716
+ } catch {
24717
+ simple = false;
24718
+ }
24719
+ } else if (scalar.startsWith("'"))
24720
+ simple = /^'[^'\r\n]*'$/.test(scalar);
24721
+ if (!frontmatter || !declaration || !simple) {
24722
+ throw new Error("Cannot rename instruction SKILL.md: use one unambiguous top-level name scalar in frontmatter.");
24723
+ }
24724
+ const renamed = frontmatter[0].replace(/^name[ \t]*:[^\r\n]*/m, () => `${declaration[1]}${name}${declaration[3]}`);
24725
+ return renamed + content.slice(frontmatter[0].length);
24726
+ }
24229
24727
  function copySkillDirectory(source, destination) {
24230
24728
  const resolvedSource = lstatSync2(source).isSymbolicLink() ? realpathSync(source) : source;
24231
24729
  mkdirSync2(destination, { recursive: true });
@@ -24624,7 +25122,7 @@ function isOfficialSkillName(name) {
24624
25122
  return OFFICIAL_SKILL_NAMES.has(name);
24625
25123
  }
24626
25124
  function scaffoldPortableSkill(name, options = {}) {
24627
- const skillName = normalizePortableSkillName(name);
25125
+ const skillName = normalizeNewPortableSkillName(name);
24628
25126
  const root = getPortableSkillsRoot(options);
24629
25127
  const skillPath = join7(root, skillName);
24630
25128
  if (existsSync7(skillPath)) {
@@ -24648,9 +25146,9 @@ function portPortableSkill(sourcePath, options = {}) {
24648
25146
  if (!existsSync7(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
24649
25147
  throw new Error(`Skill source directory not found: ${sourcePath}`);
24650
25148
  }
24651
- const inferred = readPortableSkillManifest(absoluteSource, basename2(absoluteSource));
25149
+ const inferred = readPortableSkillManifestForImport(absoluteSource);
24652
25150
  const explicitName = options.name != null;
24653
- const skillName = normalizePortableSkillName(options.name ?? inferred.name);
25151
+ const skillName = normalizeNewPortableSkillName(options.name ?? inferred.name);
24654
25152
  if (isOfficialSkillName(skillName) && !options.allowShadow) {
24655
25153
  const sourceSlug = safeNormalizeName(basename2(absoluteSource));
24656
25154
  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,23 +25629,300 @@ function mergeCustomSkills(skills) {
25131
25629
  return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
25132
25630
  }
25133
25631
 
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
- }
25632
+ // src/lib/read-access.ts
25633
+ init_fleet_credentials();
25143
25634
 
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",
25635
+ // src/lib/api-url.ts
25636
+ init_fleet_credentials();
25637
+ init_fleet_credentials();
25638
+ var API_URL_ENV_VAR = SKILLS_API_URL_ENV;
25639
+ 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>`;
25640
+ function resolveApiUrl(env = process.env, options = {}) {
25641
+ const fleet = resolveSkillsFleet(env, options);
25642
+ return fleet.mode === "hosted" ? fleet.apiOrigin : undefined;
25643
+ }
25644
+
25645
+ // src/lib/remote-registry.ts
25646
+ init_fleet_credentials();
25647
+
25648
+ // src/lib/discovery.ts
25649
+ var VENDOR_TERMS = [
25650
+ "Google Gemini",
25651
+ "OpenAI Sora",
25652
+ "MiniMax Hailuo",
25653
+ "Claude Code",
25654
+ "Claude Vision",
25655
+ "DALL-E 3",
25656
+ "GPT-4o Mini",
25657
+ "Cerebras",
25658
+ "OpenRouter",
25659
+ "Firecrawl",
25660
+ "ElevenLabs",
25661
+ "Anthropic",
25662
+ "OpenAI",
25663
+ "Minimax",
25664
+ "MiniMax",
25665
+ "Gemini",
25666
+ "Claude",
25667
+ "Whisper",
25668
+ "Seedance",
25669
+ "Lyria",
25670
+ "Sora",
25671
+ "Veo",
25672
+ "Exa.ai",
25673
+ "Exa",
25674
+ "XAI",
25675
+ "xAI"
25676
+ ];
25677
+ var VENDOR_TAGS = new Set([
25678
+ "anthropic",
25679
+ "cerebras",
25680
+ "claude",
25681
+ "exa",
25682
+ "firecrawl",
25683
+ "gemini",
25684
+ "google",
25685
+ "minimax",
25686
+ "openai",
25687
+ "openrouter",
25688
+ "seedance",
25689
+ "whisper",
25690
+ "xai"
25691
+ ]);
25692
+ var vendorPattern = new RegExp(`\\b(${VENDOR_TERMS.map(escapeRegExp).join("|")})\\b`, "gi");
25693
+ function getCompactSkillDiscovery(skill) {
25694
+ return {
25695
+ name: skill.name,
25696
+ category: skill.category,
25697
+ description: sanitizePublicDiscoveryText(skill.description)
25698
+ };
25699
+ }
25700
+ function getPublicSkillDiscovery(skill) {
25701
+ return {
25702
+ ...skill,
25703
+ description: sanitizePublicDiscoveryText(skill.description),
25704
+ tags: publicDiscoveryTags(skill.tags)
25705
+ };
25706
+ }
25707
+ function publicDiscoveryTags(tags) {
25708
+ return tags.filter((tag) => !VENDOR_TAGS.has(tag.toLowerCase()));
25709
+ }
25710
+ function sanitizePublicDiscoveryText(text) {
25711
+ let sanitized = text.replace(vendorPattern, "hosted AI").replace(/\bLLM\b/g, "AI").replace(/\s{2,}/g, " ");
25712
+ let previous;
25713
+ do {
25714
+ previous = sanitized;
25715
+ 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");
25716
+ } while (sanitized !== previous);
25717
+ return sanitized.trim();
25718
+ }
25719
+ function publicDiscoveryEnvVars(_skillName, envVars) {
25720
+ return envVars;
25721
+ }
25722
+ function publicDiscoveryDependencies(_skillName, dependencies) {
25723
+ return dependencies;
25724
+ }
25725
+ function publicDiscoveryDocumentation(_skill, documentation) {
25726
+ return documentation;
25727
+ }
25728
+ function escapeRegExp(value) {
25729
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25730
+ }
25731
+
25732
+ // src/lib/remote-registry.ts
25733
+ var remoteAvailabilitySchema = exports_external.object({
25734
+ status: exports_external.enum(["available", "unavailable"]),
25735
+ code: exports_external.string().optional(),
25736
+ message: exports_external.string().optional(),
25737
+ details: exports_external.array(exports_external.string()).optional()
25738
+ }).passthrough();
25739
+ var remoteSkillSchema = exports_external.object({
25740
+ name: exports_external.string().min(1).optional(),
25741
+ slug: exports_external.string().min(1).optional(),
25742
+ displayName: exports_external.string().optional(),
25743
+ description: exports_external.string().optional(),
25744
+ category: exports_external.string().optional(),
25745
+ tags: exports_external.array(exports_external.string()).optional(),
25746
+ dependencies: exports_external.array(exports_external.string()).optional(),
25747
+ version: exports_external.string().optional(),
25748
+ availability: remoteAvailabilitySchema.optional()
25749
+ }).passthrough().refine((skill) => skill.name || skill.slug, {
25750
+ message: "Remote skill requires name or slug"
25751
+ });
25752
+ var secretValuePatterns = [
25753
+ /\bsk-[A-Za-z0-9_-]{8,}\b/g,
25754
+ /\bgh[opsur]_[A-Za-z0-9_]{8,}\b/g,
25755
+ /\bgithub_pat_[A-Za-z0-9_]{8,}\b/g,
25756
+ /\bnpm_[A-Za-z0-9_]{8,}\b/g,
25757
+ /\bAKIA[A-Z0-9]{12,}\b/g,
25758
+ /\bAIza[A-Za-z0-9_-]{10,}\b/g,
25759
+ new RegExp("\\bsecret" + "-token:\\s*[A-Za-z0-9._-]+", "gi"),
25760
+ /\bctx7sk\-[A-Za-z0-9_-]{8,}\b/g,
25761
+ /\bxai\-[A-Za-z0-9_-]{8,}\b/g
25762
+ ];
25763
+ var remoteSkillDetailSchema = exports_external.union([
25764
+ remoteSkillSchema,
25765
+ exports_external.object({ skill: remoteSkillSchema }),
25766
+ exports_external.object({ data: remoteSkillSchema })
25767
+ ]);
25768
+ var remoteRegistrySchema = exports_external.union([
25769
+ exports_external.array(remoteSkillSchema),
25770
+ exports_external.object({ skills: exports_external.array(remoteSkillSchema) }),
25771
+ exports_external.object({ data: exports_external.array(remoteSkillSchema) })
25772
+ ]);
25773
+ function getConfiguredApiUrl(env = process.env) {
25774
+ return resolveApiUrl(env);
25775
+ }
25776
+ function buildSkillsApiUrl(apiUrl, endpoint = "/skills") {
25777
+ const url = new URL(apiUrl);
25778
+ const cleanEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
25779
+ const pathname = url.pathname.replace(/\/+$/, "");
25780
+ const apiBase = /\/api(?:\/v1)?\/skills$/.test(pathname) ? pathname.slice(0, -"/skills".length) : pathname;
25781
+ if (/\/api(?:\/v1)?$/.test(apiBase)) {
25782
+ url.pathname = `${apiBase}${cleanEndpoint}`;
25783
+ return url.toString();
25784
+ }
25785
+ url.pathname = `${apiBase}/api/v1${cleanEndpoint}`.replace(/\/{2,}/g, "/");
25786
+ return url.toString();
25787
+ }
25788
+ function titleize(name) {
25789
+ return name.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
25790
+ }
25791
+ function normalizeRemoteSkill(skill) {
25792
+ const name = skill.name || skill.slug;
25793
+ if (!name)
25794
+ throw new Error("Remote skill requires name or slug");
25795
+ return {
25796
+ name,
25797
+ displayName: skill.displayName || titleize(name),
25798
+ description: skill.description || "",
25799
+ category: skill.category || "Remote",
25800
+ tags: skill.tags || ["remote"],
25801
+ dependencies: skill.dependencies,
25802
+ ...skill.version ? { version: skill.version } : {},
25803
+ availability: normalizeRemoteAvailability(skill.availability),
25804
+ source: "remote"
25805
+ };
25806
+ }
25807
+ function normalizeRemoteAvailability(availability) {
25808
+ if (!availability)
25809
+ return { status: "available" };
25810
+ if (availability.status === "available")
25811
+ return { status: "available" };
25812
+ return {
25813
+ status: availability.status,
25814
+ ...safeAvailabilityCode(availability.code) ? { code: safeAvailabilityCode(availability.code) } : {},
25815
+ ...availability.message ? { message: sanitizeAvailabilityText(availability.message) } : {},
25816
+ ...availability.details ? { details: availability.details.map(sanitizeAvailabilityText).filter(Boolean) } : {}
25817
+ };
25818
+ }
25819
+ function safeAvailabilityCode(code) {
25820
+ if (!code)
25821
+ return;
25822
+ return /^[A-Z0-9_]+$/.test(code) ? code : undefined;
25823
+ }
25824
+ function sanitizeAvailabilityText(text) {
25825
+ 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();
25826
+ }
25827
+ function parseRemoteRegistryPayload(payload) {
25828
+ const parsed = parseRemoteContract(remoteRegistrySchema, payload, "Remote registry payload did not match the expected skills contract");
25829
+ const rawSkills = Array.isArray(parsed) ? parsed : ("skills" in parsed) ? parsed.skills : parsed.data;
25830
+ return rawSkills.map(normalizeRemoteSkill);
25831
+ }
25832
+ function parseRemoteContract(schema, payload, message) {
25833
+ try {
25834
+ return schema.parse(payload);
25835
+ } catch (error2) {
25836
+ if (error2 instanceof exports_external.ZodError)
25837
+ throw new Error(message, { cause: error2 });
25838
+ throw error2;
25839
+ }
25840
+ }
25841
+ async function remoteRequestHeaders(options) {
25842
+ const headers = new Headers({ Accept: "application/json" });
25843
+ const token = options.authToken !== undefined ? options.authToken : await ambientTokenFor(options.apiUrl);
25844
+ const trimmed = token?.trim();
25845
+ if (trimmed)
25846
+ headers.set("Authorization", `Bearer ${trimmed}`);
25847
+ return headers;
25848
+ }
25849
+ async function ambientTokenFor(callerApiUrl) {
25850
+ const connection = await resolveSkillsConnection();
25851
+ if (!connection)
25852
+ return null;
25853
+ if (callerApiUrl !== undefined && normalizeSkillsApiOrigin(callerApiUrl) !== connection.apiOrigin) {
25854
+ 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");
25855
+ }
25856
+ return connection.apiKey;
25857
+ }
25858
+ async function fetchRemoteJson(url, options) {
25859
+ const fetchImpl = options.fetchImpl || fetch;
25860
+ const headers = await remoteRequestHeaders(options);
25861
+ const controller = new AbortController;
25862
+ const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 1e4);
25863
+ try {
25864
+ const response = await fetchImpl(url, {
25865
+ headers,
25866
+ signal: controller.signal
25867
+ });
25868
+ if (!response.ok) {
25869
+ throw new Error(`Remote registry request failed: ${response.status} ${response.statusText}`);
25870
+ }
25871
+ return response.json();
25872
+ } finally {
25873
+ clearTimeout(timeout);
25874
+ }
25875
+ }
25876
+ async function loadRemoteRegistry(options = {}) {
25877
+ const apiUrl = options.apiUrl || getConfiguredApiUrl();
25878
+ if (!apiUrl) {
25879
+ 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}`);
25880
+ }
25881
+ const url = buildSkillsApiUrl(apiUrl, options.endpoint);
25882
+ return parseRemoteRegistryPayload(await fetchRemoteJson(url, options));
25883
+ }
25884
+ async function mergeRemoteRegistry(local, options = {}) {
25885
+ const apiUrl = options.apiUrl || getConfiguredApiUrl();
25886
+ if (!apiUrl)
25887
+ return local;
25888
+ if (options.authToken !== undefined && !options.authToken?.trim())
25889
+ return local;
25890
+ const remote = await loadRemoteRegistry({ ...options, apiUrl });
25891
+ return mergeSkillRegistryLists(local, remote);
25892
+ }
25893
+
25894
+ // src/lib/read-access.ts
25895
+ async function requireSkillsReadAccess(env = process.env, options = {}) {
25896
+ const connection = await resolveSkillsConnection(env, options);
25897
+ return connection ? { mode: "hosted", apiOrigin: connection.apiOrigin } : { mode: "local" };
25898
+ }
25899
+ async function getBrowseRegistry(options = {}) {
25900
+ const profile = options.all ? "all" : "basic";
25901
+ const local = loadRegistryProfile(profile);
25902
+ if (options.remote) {
25903
+ const remote = await loadRemoteRegistry();
25904
+ return mergeSkillRegistryLists(local, remote);
25905
+ }
25906
+ return mergeRemoteRegistry(local);
25907
+ }
25908
+
25909
+ // src/lib/installer.ts
25910
+ import { existsSync as existsSync10, readFileSync as readFileSync9, rmSync as rmSync2 } from "fs";
25911
+ import { dirname as dirname4, join as join11 } from "path";
25912
+ import { homedir as homedir2 } from "os";
25913
+ import { fileURLToPath } from "url";
25914
+ // src/lib/utils.ts
25915
+ function normalizeSkillName(name) {
25916
+ return name;
25917
+ }
25918
+
25919
+ // src/lib/project-state.ts
25920
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
25921
+ import { join as join10 } from "path";
25922
+ var VALID_PIN_SOURCES = [
25923
+ "official",
25924
+ "custom",
25925
+ "remote",
25151
25926
  "private",
25152
25927
  "private-hosted",
25153
25928
  "upstream",
@@ -25158,17 +25933,17 @@ var SKILLS_PROJECT_DIR = ".skills";
25158
25933
  var PROJECT_CONFIG_FILE = "project.json";
25159
25934
  var DEFAULT_EXPORT_DIR = ".skills/exports";
25160
25935
  function getProjectStateDir(targetDir = process.cwd()) {
25161
- return join9(targetDir, SKILLS_PROJECT_DIR);
25936
+ return join10(targetDir, SKILLS_PROJECT_DIR);
25162
25937
  }
25163
25938
  function getProjectConfigPath(targetDir = process.cwd()) {
25164
- return join9(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
25939
+ return join10(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
25165
25940
  }
25166
25941
  function loadProjectConfig(targetDir = process.cwd()) {
25167
25942
  const path = getProjectConfigPath(targetDir);
25168
25943
  if (!existsSync9(path))
25169
25944
  return null;
25170
25945
  try {
25171
- return normalizeProjectConfig(JSON.parse(readFileSync7(path, "utf-8")));
25946
+ return normalizeProjectConfig(JSON.parse(readFileSync8(path, "utf-8")));
25172
25947
  } catch {
25173
25948
  return null;
25174
25949
  }
@@ -25267,12 +26042,12 @@ var __dirname2 = dirname4(fileURLToPath(import.meta.url));
25267
26042
  function findSkillsDir() {
25268
26043
  let dir = __dirname2;
25269
26044
  for (let i = 0;i < 5; i++) {
25270
- const candidate = join10(dir, "skills");
26045
+ const candidate = join11(dir, "skills");
25271
26046
  if (existsSync10(candidate) && !dir.includes(".skills"))
25272
26047
  return candidate;
25273
26048
  dir = dirname4(dir);
25274
26049
  }
25275
- return join10(__dirname2, "..", "skills");
26050
+ return join11(__dirname2, "..", "skills");
25276
26051
  }
25277
26052
  var SKILLS_DIR = findSkillsDir();
25278
26053
  function getSkillPath(name) {
@@ -25280,13 +26055,13 @@ function getSkillPath(name) {
25280
26055
  const portable = findPortableSkill(skillName);
25281
26056
  if (portable)
25282
26057
  return portable.path;
25283
- const legacyCustomPath = join10(getDataDir(), "custom", skillName);
26058
+ const legacyCustomPath = join11(getDataDir(), "custom", skillName);
25284
26059
  if (existsSync10(legacyCustomPath))
25285
26060
  return legacyCustomPath;
25286
26061
  const extensionPath = findExtensionSkillPath(skillName);
25287
26062
  if (extensionPath)
25288
26063
  return extensionPath;
25289
- return join10(SKILLS_DIR, skillName);
26064
+ return join11(SKILLS_DIR, skillName);
25290
26065
  }
25291
26066
  function getCanonicalSkillName(name) {
25292
26067
  return getSkill(name)?.name ?? resolveSkillAlias(normalizeSkillSlug(name));
@@ -25350,11 +26125,11 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
25350
26125
  const base = projectDir || process.cwd();
25351
26126
  switch (agent) {
25352
26127
  case "pi":
25353
- return scope === "project" ? join10(base, ".pi", "skills") : join10(homedir2(), ".pi", "agent", "skills");
26128
+ return scope === "project" ? join11(base, ".pi", "skills") : join11(homedir2(), ".pi", "agent", "skills");
25354
26129
  case "opencode":
25355
- return scope === "project" ? join10(base, ".opencode", "skills") : join10(homedir2(), ".config", "opencode", "skills");
26130
+ return scope === "project" ? join11(base, ".opencode", "skills") : join11(homedir2(), ".config", "opencode", "skills");
25356
26131
  default:
25357
- return scope === "project" ? join10(base, `.${agent}`, "skills") : join10(homedir2(), `.${agent}`, "skills");
26132
+ return scope === "project" ? join11(base, `.${agent}`, "skills") : join11(homedir2(), `.${agent}`, "skills");
25358
26133
  }
25359
26134
  }
25360
26135
  function warnMissingDependencies(name, targetDir) {
@@ -25369,11 +26144,11 @@ function warnMissingDependencies(name, targetDir) {
25369
26144
  }
25370
26145
  }
25371
26146
  function readBundledSkillVersion(name) {
25372
- const pkgPath = join10(getSkillPath(name), "package.json");
26147
+ const pkgPath = join11(getSkillPath(name), "package.json");
25373
26148
  if (!existsSync10(pkgPath))
25374
26149
  return "unknown";
25375
26150
  try {
25376
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
26151
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
25377
26152
  return pkg.version || "unknown";
25378
26153
  } catch {
25379
26154
  return "unknown";
@@ -25381,16 +26156,16 @@ function readBundledSkillVersion(name) {
25381
26156
  }
25382
26157
 
25383
26158
  // src/lib/skillinfo.ts
25384
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
25385
- import { join as join11 } from "path";
26159
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
26160
+ import { join as join12 } from "path";
25386
26161
  function isInstructionSkillDir(skillPath, meta) {
25387
26162
  if (meta?.kind === "instruction")
25388
26163
  return true;
25389
- const skillMdPath = join11(skillPath, "SKILL.md");
26164
+ const skillMdPath = join12(skillPath, "SKILL.md");
25390
26165
  if (!existsSync11(skillMdPath))
25391
26166
  return false;
25392
26167
  try {
25393
- return parseSkillFrontmatter(readFileSync9(skillMdPath, "utf-8"))?.kind === "instruction";
26168
+ return parseSkillFrontmatter(readFileSync10(skillMdPath, "utf-8"))?.kind === "instruction";
25394
26169
  } catch {
25395
26170
  return false;
25396
26171
  }
@@ -25416,9 +26191,9 @@ function getSkillDocs(name) {
25416
26191
  if (!existsSync11(skillPath))
25417
26192
  return null;
25418
26193
  return {
25419
- skillMd: readIfExists(join11(skillPath, "SKILL.md")),
25420
- readme: readIfExists(join11(skillPath, "README.md")),
25421
- claudeMd: readIfExists(join11(skillPath, "CLAUDE.md"))
26194
+ skillMd: readIfExists(join12(skillPath, "SKILL.md")),
26195
+ readme: readIfExists(join12(skillPath, "README.md")),
26196
+ claudeMd: readIfExists(join12(skillPath, "CLAUDE.md"))
25422
26197
  };
25423
26198
  }
25424
26199
  function getSkillBestDoc(name) {
@@ -25433,7 +26208,7 @@ function getSkillRequirements(name) {
25433
26208
  return null;
25434
26209
  const texts = [];
25435
26210
  for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
25436
- const content = readIfExists(join11(skillPath, file));
26211
+ const content = readIfExists(join12(skillPath, file));
25437
26212
  if (content)
25438
26213
  texts.push(content);
25439
26214
  }
@@ -25473,10 +26248,10 @@ function getSkillRequirements(name) {
25473
26248
  const skillName = normalizeSkillName(name);
25474
26249
  let cliCommand = `skills run ${skillName}`;
25475
26250
  let dependencies = {};
25476
- const pkgPath = join11(skillPath, "package.json");
26251
+ const pkgPath = join12(skillPath, "package.json");
25477
26252
  if (existsSync11(pkgPath)) {
25478
26253
  try {
25479
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
26254
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
25480
26255
  dependencies = pkg.dependencies || {};
25481
26256
  } catch {}
25482
26257
  }
@@ -25503,13 +26278,13 @@ async function runSkill(name, args, options = {}) {
25503
26278
  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
26279
  };
25505
26280
  }
25506
- const pkgPath = join11(skillPath, "package.json");
26281
+ const pkgPath = join12(skillPath, "package.json");
25507
26282
  if (!existsSync11(pkgPath)) {
25508
26283
  return { exitCode: 1, error: `No package.json in skill '${name}'` };
25509
26284
  }
25510
26285
  let entryPoint;
25511
26286
  try {
25512
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
26287
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
25513
26288
  if (pkg.bin) {
25514
26289
  const binValues = Object.values(pkg.bin);
25515
26290
  entryPoint = binValues[0];
@@ -25523,11 +26298,11 @@ async function runSkill(name, args, options = {}) {
25523
26298
  } catch {
25524
26299
  return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
25525
26300
  }
25526
- const entryPath = join11(skillPath, entryPoint);
26301
+ const entryPath = join12(skillPath, entryPoint);
25527
26302
  if (!existsSync11(entryPath)) {
25528
26303
  return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
25529
26304
  }
25530
- const nodeModules = join11(skillPath, "node_modules");
26305
+ const nodeModules = join12(skillPath, "node_modules");
25531
26306
  if (!existsSync11(nodeModules)) {
25532
26307
  const install = Bun.spawn(["bun", "install", "--no-save"], {
25533
26308
  cwd: skillPath,
@@ -25538,7 +26313,7 @@ async function runSkill(name, args, options = {}) {
25538
26313
  }
25539
26314
  const proc = Bun.spawn(["bun", "run", entryPath, ...args], {
25540
26315
  cwd: skillPath,
25541
- stdout: options.stdio === "pipe" ? "pipe" : "inherit",
26316
+ stdout: options.stdio === "pipe" ? "pipe" : options.stdio === "stderr" ? 2 : "inherit",
25542
26317
  stderr: options.stdio === "pipe" ? "pipe" : "inherit",
25543
26318
  stdin: "inherit",
25544
26319
  env: { ...process.env, ...options.env }
@@ -25555,7 +26330,7 @@ async function runSkill(name, args, options = {}) {
25555
26330
  return { exitCode };
25556
26331
  }
25557
26332
  function detectProjectSkills(cwd = process.cwd()) {
25558
- const pkgPath = join11(cwd, "package.json");
26333
+ const pkgPath = join12(cwd, "package.json");
25559
26334
  if (!existsSync11(pkgPath)) {
25560
26335
  const alwaysRecommend = ["market-research-report", "repo-onboarding-report", "blog-article"];
25561
26336
  const recommended2 = alwaysRecommend.map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
@@ -25563,7 +26338,7 @@ function detectProjectSkills(cwd = process.cwd()) {
25563
26338
  }
25564
26339
  let pkg;
25565
26340
  try {
25566
- pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
26341
+ pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
25567
26342
  } catch {
25568
26343
  const alwaysRecommend = ["market-research-report", "repo-onboarding-report", "blog-article"];
25569
26344
  const recommended2 = alwaysRecommend.map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
@@ -25653,7 +26428,7 @@ function extractEnvVars(text) {
25653
26428
  function readIfExists(path) {
25654
26429
  try {
25655
26430
  if (existsSync11(path)) {
25656
- return readFileSync9(path, "utf-8");
26431
+ return readFileSync10(path, "utf-8");
25657
26432
  }
25658
26433
  } catch {}
25659
26434
  return null;
@@ -26068,6 +26843,104 @@ var toolContracts = [
26068
26843
  dependencies: objectSchema({}, [], "Package dependencies.", true)
26069
26844
  })
26070
26845
  },
26846
+ {
26847
+ name: "update_account_profile",
26848
+ title: "Update Account Display Name",
26849
+ description: "Update your display name with fresh email verification on the configured server.",
26850
+ params: ["name", "email", "code"],
26851
+ category: "execution",
26852
+ sideEffects: "local-process-or-remote-run",
26853
+ stable: true,
26854
+ inputSchema: objectSchema({ name: stringSchema("Display name, 1\u2013100 characters"), email: { type: "string", format: "email" }, code: { type: "string", pattern: "^\\d{6}$" } }, ["name", "email", "code"]),
26855
+ 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"])
26856
+ },
26857
+ {
26858
+ name: "update_workspace_name",
26859
+ title: "Update Workspace Name",
26860
+ description: "Update the current workspace name as an owner/admin with fresh email verification.",
26861
+ params: ["name", "email", "code"],
26862
+ category: "execution",
26863
+ sideEffects: "local-process-or-remote-run",
26864
+ stable: true,
26865
+ inputSchema: objectSchema({ name: stringSchema("Workspace name, 1\u2013100 characters"), email: { type: "string", format: "email" }, code: { type: "string", pattern: "^\\d{6}$" } }, ["name", "email", "code"]),
26866
+ outputSchema: objectSchema({ organization: objectSchema({ id: stringSchema("Workspace identifier."), slug: stringSchema("Stable workspace slug."), name: stringSchema("Workspace name.") }, ["id", "slug", "name"]) }, ["organization"])
26867
+ },
26868
+ {
26869
+ name: "set_workspace_member_role",
26870
+ title: "Set Current Workspace Member Role",
26871
+ description: "Set an exact membership incarnation's role with its observed expectedRole and fresh verification; no automatic retry.",
26872
+ params: ["membershipId", "role", "expectedRole", "email", "code"],
26873
+ category: "execution",
26874
+ sideEffects: "local-process-or-remote-run",
26875
+ stable: true,
26876
+ inputSchema: objectSchema({
26877
+ membershipId: { type: "string", pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$" },
26878
+ role: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
26879
+ expectedRole: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
26880
+ email: { type: "string", format: "email" },
26881
+ code: { type: "string", pattern: "^\\d{6}$" }
26882
+ }, ["membershipId", "role", "expectedRole", "email", "code"]),
26883
+ outputSchema: objectSchema({
26884
+ organizationId: stringSchema("Current workspace identifier."),
26885
+ changed: { type: "boolean" },
26886
+ member: objectSchema({
26887
+ membershipId: stringSchema("Membership incarnation."),
26888
+ userId: stringSchema("Account identifier."),
26889
+ email: stringSchema("Member email."),
26890
+ displayName: { oneOf: [{ type: "string" }, { type: "null" }] },
26891
+ role: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
26892
+ createdAt: stringSchema("Exact server timestamp including microseconds.")
26893
+ }, ["membershipId", "userId", "email", "displayName", "role", "createdAt"])
26894
+ }, ["organizationId", "member", "changed"])
26895
+ },
26896
+ {
26897
+ name: "remove_workspace_member",
26898
+ title: "Remove Current Workspace Member",
26899
+ description: "Remove exactly this membership incarnation using its observed expectedRole and fresh verification; self-removal is unavailable.",
26900
+ params: ["membershipId", "expectedRole", "email", "code"],
26901
+ category: "execution",
26902
+ sideEffects: "local-process-or-remote-run",
26903
+ stable: true,
26904
+ inputSchema: objectSchema({
26905
+ membershipId: { type: "string", pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$" },
26906
+ expectedRole: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
26907
+ email: { type: "string", format: "email" },
26908
+ code: { type: "string", pattern: "^\\d{6}$" }
26909
+ }, ["membershipId", "expectedRole", "email", "code"]),
26910
+ outputSchema: objectSchema({
26911
+ organizationId: stringSchema("Current workspace identifier."),
26912
+ membershipId: stringSchema("Removed membership incarnation."),
26913
+ removed: { type: "boolean", const: true },
26914
+ alreadyRemoved: { type: "boolean" }
26915
+ }, ["organizationId", "membershipId", "removed", "alreadyRemoved"])
26916
+ },
26917
+ {
26918
+ name: "list_workspace_members",
26919
+ title: "List Current Workspace Members",
26920
+ description: "Read one current-workspace roster page with fresh owner/admin email verification; saved credentials stay unchanged.",
26921
+ params: ["email", "code", "limit?", "cursor?"],
26922
+ category: "execution",
26923
+ sideEffects: "local-process-or-remote-run",
26924
+ stable: true,
26925
+ inputSchema: objectSchema({
26926
+ email: { type: "string", format: "email" },
26927
+ code: { type: "string", pattern: "^\\d{6}$" },
26928
+ limit: { type: "integer", minimum: 1, maximum: 100 },
26929
+ cursor: { type: "string", pattern: "^[A-Za-z0-9_-]{1,512}$" }
26930
+ }, ["email", "code"]),
26931
+ outputSchema: objectSchema({
26932
+ organizationId: stringSchema("Current workspace identifier."),
26933
+ members: arraySchema(objectSchema({
26934
+ membershipId: stringSchema("Membership incarnation."),
26935
+ userId: stringSchema("Account identifier."),
26936
+ email: stringSchema("Member email."),
26937
+ displayName: { oneOf: [{ type: "string" }, { type: "null" }] },
26938
+ role: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
26939
+ createdAt: stringSchema("Exact server timestamp including microseconds.")
26940
+ }, ["membershipId", "userId", "email", "displayName", "role", "createdAt"])),
26941
+ nextCursor: { oneOf: [{ type: "string" }, { type: "null" }] }
26942
+ }, ["organizationId", "members", "nextCursor"])
26943
+ },
26071
26944
  {
26072
26945
  name: "list_api_keys",
26073
26946
  title: "List API Keys",
@@ -26577,90 +27450,6 @@ function clone2(value) {
26577
27450
  return JSON.parse(JSON.stringify(value));
26578
27451
  }
26579
27452
 
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
27453
  // src/lib/tool-primitives.ts
26665
27454
  var TOOL_PRIMITIVE_SCHEMA_VERSION = 1;
26666
27455
  var TOOL_PRIMITIVES = [
@@ -27119,22 +27908,20 @@ function compactRemoteRun(run) {
27119
27908
  }
27120
27909
 
27121
27910
  // src/mcp/helpers.ts
27911
+ init_fleet_credentials();
27912
+ async function readSurface(body) {
27913
+ try {
27914
+ return await body();
27915
+ } catch (error2) {
27916
+ if (isSkillsFleetCredentialError(error2))
27917
+ return mcpError("AUTH_REQUIRED", error2.message, ["skills auth login"]);
27918
+ throw error2;
27919
+ }
27920
+ }
27122
27921
  function stripNulls(obj) {
27123
27922
  return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== null && v !== undefined && !(Array.isArray(v) && v.length === 0)));
27124
27923
  }
27125
27924
  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
27925
  function cacheClear() {
27139
27926
  searchCache.clear();
27140
27927
  }
@@ -27174,9 +27961,10 @@ function registerDiscoveryTools(server) {
27174
27961
  limit: exports_external.number().optional(),
27175
27962
  offset: exports_external.number().optional()
27176
27963
  }
27177
- }, async ({ category, profile, detail, limit, offset }) => {
27964
+ }, async ({ category, profile, detail, limit, offset }) => readSurface(async () => {
27178
27965
  const selectedProfile = profile || "basic";
27179
- const skills = category ? loadRegistryProfile(selectedProfile).filter((s) => s.category === category) : loadRegistryProfile(selectedProfile);
27966
+ const registry2 = await getBrowseRegistry({ all: selectedProfile === "all" });
27967
+ const skills = category ? registry2.filter((s) => s.category === category) : registry2;
27180
27968
  const mapped = detail ? skills.map(getPublicSkillDiscovery) : skills.map(getCompactSkillDiscovery);
27181
27969
  const page = paginate(mapped, {
27182
27970
  limit: parsePageLimit(limit, DEFAULT_MCP_LIMIT, { max: 100 }),
@@ -27192,7 +27980,7 @@ function registerDiscoveryTools(server) {
27192
27980
  nextArguments: page.hasMore ? { profile: selectedProfile, category, detail: Boolean(detail), limit: page.limit, offset: page.nextOffset } : null,
27193
27981
  detailHint: detail ? undefined : "Set detail:true for full public skill objects, or call get_skill_info for one skill."
27194
27982
  });
27195
- });
27983
+ }));
27196
27984
  server.registerTool("list_pinned_skills", {
27197
27985
  title: "List Pinned Skills",
27198
27986
  description: "List skills pinned in the current project's .skills/project.json.",
@@ -27216,13 +28004,9 @@ function registerDiscoveryTools(server) {
27216
28004
  limit: exports_external.number().optional(),
27217
28005
  offset: exports_external.number().optional()
27218
28006
  }
27219
- }, async ({ query, profile, detail, limit, offset }) => {
28007
+ }, async ({ query, profile, detail, limit, offset }) => readSurface(async () => {
27220
28008
  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);
28009
+ const results = searchSkills(query, await getBrowseRegistry({ all: selectedProfile === "all" }));
27226
28010
  const out = detail ? results.map(getPublicSkillDiscovery) : results.map(getCompactSkillDiscovery);
27227
28011
  const page = paginate(out, {
27228
28012
  limit: parsePageLimit(limit, DEFAULT_MCP_LIMIT, { max: 100 }),
@@ -27238,14 +28022,15 @@ function registerDiscoveryTools(server) {
27238
28022
  nextArguments: page.hasMore ? { query, profile: selectedProfile, detail: Boolean(detail), limit: page.limit, offset: page.nextOffset } : null,
27239
28023
  detailHint: detail ? undefined : "Set detail:true for full public skill objects, or call get_skill_info for one skill."
27240
28024
  });
27241
- });
28025
+ }));
27242
28026
  server.registerTool("get_skill_info", {
27243
28027
  title: "Get Skill Info",
27244
28028
  description: "Get skill metadata, env vars, and dependencies.",
27245
28029
  inputSchema: {
27246
28030
  name: exports_external.string()
27247
28031
  }
27248
- }, async ({ name }) => {
28032
+ }, async ({ name }) => readSurface(async () => {
28033
+ await requireSkillsReadAccess();
27249
28034
  const skill = getSkill(name);
27250
28035
  if (!skill) {
27251
28036
  return mcpError("SKILL_NOT_FOUND", `Skill '${name}' not found`, findSimilarSkills(name));
@@ -27265,20 +28050,21 @@ function registerDiscoveryTools(server) {
27265
28050
  return {
27266
28051
  content: [{ type: "text", text: JSON.stringify(result) }]
27267
28052
  };
27268
- });
28053
+ }));
27269
28054
  server.registerTool("get_skill_docs", {
27270
28055
  title: "Get Skill Docs",
27271
28056
  description: "Get skill documentation (SKILL.md > README.md > CLAUDE.md).",
27272
28057
  inputSchema: {
27273
28058
  name: exports_external.string()
27274
28059
  }
27275
- }, async ({ name }) => {
28060
+ }, async ({ name }) => readSurface(async () => {
28061
+ await requireSkillsReadAccess();
27276
28062
  const doc2 = getSkillBestDoc(name);
27277
28063
  if (!doc2) {
27278
28064
  return mcpError("NO_DOCS", `No documentation found for '${name}'`);
27279
28065
  }
27280
28066
  return { content: [{ type: "text", text: doc2 }] };
27281
- });
28067
+ }));
27282
28068
  server.registerTool("list_tool_primitives", {
27283
28069
  title: "List Tool Primitives",
27284
28070
  description: "List primitive tools that skills depend on across CLI, MCP, API, and hosted worker execution.",
@@ -27324,25 +28110,74 @@ function registerDiscoveryTools(server) {
27324
28110
  }
27325
28111
 
27326
28112
  // src/mcp/operation-tools.ts
27327
- import { existsSync as existsSync13, readdirSync as readdirSync8, statSync as statSync8 } from "fs";
27328
- import { join as join14 } from "path";
28113
+ import { existsSync as existsSync14, readdirSync as readdirSync8, statSync as statSync9 } from "fs";
28114
+ import { join as join15 } from "path";
28115
+
28116
+ // src/lib/credential-state.ts
28117
+ init_auth_store();
28118
+ init_fleet_credentials();
28119
+ function describeCredentialState() {
28120
+ let credentialsFile = null;
28121
+ let mode = null;
28122
+ try {
28123
+ credentialsFile = getAuthFilePath();
28124
+ const bits = credentialFileMode();
28125
+ mode = bits === null ? null : `0${bits.toString(8).padStart(3, "0")}`;
28126
+ } catch {}
28127
+ try {
28128
+ const fleet = resolveSkillsFleet();
28129
+ if (fleet.mode === "hosted") {
28130
+ return {
28131
+ mode: "hosted",
28132
+ apiUrl: fleet.apiOrigin,
28133
+ apiUrlSource: fleet.apiUrlSource,
28134
+ apiKeySource: fleet.apiKeySource,
28135
+ apiKeyTier: fleet.apiKeyTier,
28136
+ credentialsFile,
28137
+ credentialsFileMode: mode,
28138
+ error: null
28139
+ };
28140
+ }
28141
+ return {
28142
+ mode: "local",
28143
+ apiUrl: null,
28144
+ apiUrlSource: null,
28145
+ apiKeySource: null,
28146
+ apiKeyTier: null,
28147
+ credentialsFile,
28148
+ credentialsFileMode: mode,
28149
+ error: null
28150
+ };
28151
+ } catch (error2) {
28152
+ return {
28153
+ mode: "misconfigured",
28154
+ apiUrl: null,
28155
+ apiUrlSource: null,
28156
+ apiKeySource: null,
28157
+ apiKeyTier: null,
28158
+ credentialsFile,
28159
+ credentialsFileMode: mode,
28160
+ error: error2.message
28161
+ };
28162
+ }
28163
+ }
27329
28164
 
27330
28165
  // src/lib/run-state.ts
27331
28166
  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";
28167
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync12, readdirSync as readdirSync7, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
28168
+ import { extname, join as join14, relative as relative2 } from "path";
27334
28169
  function createSkillRun(params, targetDir = process.cwd()) {
27335
28170
  const now = new Date;
27336
28171
  const id = createRunId(now);
27337
28172
  const day = now.toISOString().slice(0, 10);
27338
28173
  const skillName = normalizeSkillName(params.skill);
27339
28174
  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 });
28175
+ const runDir = join14(root, "runs", day, id);
28176
+ const logsDir = join14(runDir, "logs");
28177
+ const exportDir = join14(root, "exports", skillName, id);
28178
+ mkdirSync6(logsDir, { recursive: true });
28179
+ mkdirSync6(exportDir, { recursive: true });
28180
+ mkdirSync6(join14(root, "tmp"), { recursive: true });
27346
28181
  const record3 = {
27347
28182
  id,
27348
28183
  skill: skillName,
@@ -27394,22 +28229,22 @@ function updateSkillRun(context, patch) {
27394
28229
  return context.record;
27395
28230
  }
27396
28231
  function writeRunLogs(context, stdout = "", stderr = "") {
27397
- writeFileSync5(join12(context.logsDir, "stdout.log"), stdout);
27398
- writeFileSync5(join12(context.logsDir, "stderr.log"), stderr);
28232
+ writeFileSync6(join14(context.logsDir, "stdout.log"), stdout);
28233
+ writeFileSync6(join14(context.logsDir, "stderr.log"), stderr);
27399
28234
  }
27400
28235
  function appendRunEvent(context, event, data = {}) {
27401
28236
  const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
27402
28237
  `;
27403
- const path = join12(context.runDir, "events.ndjson");
27404
- const previous = existsSync12(path) ? readFileSync10(path, "utf-8") : "";
27405
- writeFileSync5(path, previous + line);
28238
+ const path = join14(context.runDir, "events.ndjson");
28239
+ const previous = existsSync13(path) ? readFileSync12(path, "utf-8") : "";
28240
+ writeFileSync6(path, previous + line);
27406
28241
  }
27407
28242
  function findSkillRun(runId, targetDir = process.cwd()) {
27408
- const runsRoot = join12(getProjectStateDir(targetDir), "runs");
27409
- if (!existsSync12(runsRoot))
28243
+ const runsRoot = join14(getProjectStateDir(targetDir), "runs");
28244
+ if (!existsSync13(runsRoot))
27410
28245
  return null;
27411
28246
  for (const day of readdirSync7(runsRoot)) {
27412
- const record3 = readRunRecord(join12(runsRoot, day, runId));
28247
+ const record3 = readRunRecord(join14(runsRoot, day, runId));
27413
28248
  if (record3)
27414
28249
  return record3;
27415
28250
  }
@@ -27426,20 +28261,20 @@ function skillRunEnv(context) {
27426
28261
  };
27427
28262
  }
27428
28263
  function writeRunRecord(context) {
27429
- writeFileSync5(join12(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
28264
+ writeFileSync6(join14(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
27430
28265
  `);
27431
28266
  }
27432
28267
  function writeArtifactsManifest(context, artifacts) {
27433
- writeFileSync5(join12(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
28268
+ writeFileSync6(join14(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
27434
28269
  `);
27435
28270
  }
27436
28271
  function collectRunArtifacts(context) {
27437
- if (!existsSync12(context.exportDir))
28272
+ if (!existsSync13(context.exportDir))
27438
28273
  return [];
27439
28274
  const artifacts = [];
27440
28275
  for (const path of walkFiles(context.exportDir)) {
27441
- const stat = statSync7(path);
27442
- const bytes = readFileSync10(path);
28276
+ const stat = statSync8(path);
28277
+ const bytes = readFileSync12(path);
27443
28278
  artifacts.push({
27444
28279
  path: toProjectRelative(context.targetDir, path),
27445
28280
  mime: mimeForPath(path),
@@ -27450,11 +28285,11 @@ function collectRunArtifacts(context) {
27450
28285
  return artifacts.sort((a, b) => a.path.localeCompare(b.path));
27451
28286
  }
27452
28287
  function readRunRecord(runDir) {
27453
- const path = join12(runDir, "run.json");
27454
- if (!existsSync12(path))
28288
+ const path = join14(runDir, "run.json");
28289
+ if (!existsSync13(path))
27455
28290
  return null;
27456
28291
  try {
27457
- return JSON.parse(readFileSync10(path, "utf-8"));
28292
+ return JSON.parse(readFileSync12(path, "utf-8"));
27458
28293
  } catch {
27459
28294
  return null;
27460
28295
  }
@@ -27462,8 +28297,8 @@ function readRunRecord(runDir) {
27462
28297
  function walkFiles(dir) {
27463
28298
  const files = [];
27464
28299
  for (const entry of readdirSync7(dir)) {
27465
- const full = join12(dir, entry);
27466
- if (statSync7(full).isDirectory())
28300
+ const full = join14(dir, entry);
28301
+ if (statSync8(full).isDirectory())
27467
28302
  files.push(...walkFiles(full));
27468
28303
  else
27469
28304
  files.push(full);
@@ -27536,17 +28371,20 @@ async function resolveConfiguredRunRouting(skill, env = process.env) {
27536
28371
  try {
27537
28372
  connection = await resolveSkillsConnection(env);
27538
28373
  } catch (error2) {
27539
- const isMissingCredential = (error2 instanceof SkillsFleetCredentialError || error2?.name === "SkillsFleetCredentialError") && error2.code === "MISSING_API_CREDENTIAL";
28374
+ const isMissingCredential = isSkillsFleetCredentialError2(error2) && error2.code === "MISSING_API_CREDENTIAL";
27540
28375
  if (!isMissingCredential)
27541
28376
  throw error2;
27542
28377
  return {
27543
28378
  route: "error",
27544
28379
  code: "REMOTE_REQUIRES_CREDENTIAL",
27545
- error: `${skill.name} is a server-owned skill. ${error2.message}`
28380
+ error: skill.serverOwned ? `${skill.name} is a server-owned skill. ${error2.message}` : `${skill.name} cannot run: ${error2.message}`
27546
28381
  };
27547
28382
  }
27548
28383
  return resolveRunRouting(skill, connection?.apiKey, connection?.apiOrigin);
27549
28384
  }
28385
+ function isSkillsFleetCredentialError2(error2) {
28386
+ return typeof error2 === "object" && error2 !== null && error2.name === "SkillsFleetCredentialError";
28387
+ }
27550
28388
 
27551
28389
  // src/mcp/operation-tools.ts
27552
28390
  function registerOperationTools(server) {
@@ -27709,39 +28547,42 @@ function registerOperationTools(server) {
27709
28547
  server.registerTool("list_categories", {
27710
28548
  title: "List Categories",
27711
28549
  description: "List all 17 skill categories with skill counts."
27712
- }, async () => {
27713
- const cats = CATEGORIES.map((category) => ({
28550
+ }, async () => readSurface(async () => {
28551
+ const registry2 = await getBrowseRegistry({ all: true });
28552
+ const extras = Array.from(new Set(registry2.map((skill) => skill.category))).filter((category) => !CATEGORIES.includes(category)).sort();
28553
+ const cats = [...CATEGORIES, ...extras].map((category) => ({
27714
28554
  name: category,
27715
- count: getSkillsByCategory(category).length
28555
+ count: registry2.filter((skill) => skill.category === category).length
27716
28556
  }));
27717
28557
  return { content: [{ type: "text", text: JSON.stringify(cats, null, 2) }] };
27718
- });
28558
+ }));
27719
28559
  server.registerTool("list_tags", {
27720
28560
  title: "List Tags",
27721
28561
  description: "List all unique skill tags with occurrence counts."
27722
- }, async () => {
28562
+ }, async () => readSurface(async () => {
27723
28563
  const tagCounts = new Map;
27724
- for (const skill of loadRegistry()) {
28564
+ for (const skill of await getBrowseRegistry({ all: true })) {
27725
28565
  for (const tag of skill.tags) {
27726
28566
  tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
27727
28567
  }
27728
28568
  }
27729
28569
  const sorted = Array.from(tagCounts.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => ({ name, count }));
27730
28570
  return { content: [{ type: "text", text: JSON.stringify(sorted, null, 2) }] };
27731
- });
28571
+ }));
27732
28572
  server.registerTool("get_requirements", {
27733
28573
  title: "Get Requirements",
27734
28574
  description: "Get env vars, system deps, and npm dependencies for a skill.",
27735
28575
  inputSchema: {
27736
28576
  name: exports_external.string()
27737
28577
  }
27738
- }, async ({ name }) => {
28578
+ }, async ({ name }) => readSurface(async () => {
28579
+ await requireSkillsReadAccess();
27739
28580
  const reqs = getSkillRequirements(name);
27740
28581
  if (!reqs) {
27741
28582
  return mcpError("SKILL_NOT_FOUND", `Skill '${name}' not found`, findSimilarSkills(name));
27742
28583
  }
27743
28584
  return { content: [{ type: "text", text: JSON.stringify(reqs, null, 2) }] };
27744
- });
28585
+ }));
27745
28586
  server.registerTool("run_skill", {
27746
28587
  title: "Run Skill",
27747
28588
  description: "Run a skill by name with optional arguments.",
@@ -27783,20 +28624,16 @@ function registerOperationTools(server) {
27783
28624
  } catch (error2) {
27784
28625
  return mcpError("INVALID_INPUT_FILES", error2.message);
27785
28626
  }
28627
+ if (routing.route === "error") {
28628
+ const suggestions = routing.code === "REMOTE_REQUIRES_ORIGIN" ? ["skills setup --api-url <url>", "skills auth login"] : ["skills auth login"];
28629
+ return mcpError(routing.code, routing.error, suggestions);
28630
+ }
27786
28631
  const runContext = createSkillRun({
27787
28632
  skill: skillName,
27788
28633
  args: runArgs,
27789
28634
  remote: routing.route === "remote",
27790
28635
  ...routing.route === "remote" ? { remoteApiOrigin: routing.apiOrigin } : {}
27791
28636
  });
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
28637
  if (routing.route === "remote") {
27801
28638
  try {
27802
28639
  const { RemoteSkillsClient: RemoteSkillsClient2 } = await Promise.resolve().then(() => (init_remote_client(), exports_remote_client));
@@ -27953,21 +28790,22 @@ function registerOperationTools(server) {
27953
28790
  });
27954
28791
  server.registerTool("whoami", {
27955
28792
  title: "Skills Whoami",
27956
- description: "Show setup summary: version, pinned skills, agent configs, cwd."
28793
+ description: "Show setup summary: version, pinned skills, agent configs, cwd, and the credential/transport SOURCES (never values)."
27957
28794
  }, async () => {
27958
28795
  const version2 = package_default.version;
27959
28796
  const cwd = process.cwd();
28797
+ const credential = describeCredentialState();
27960
28798
  const installed = getInstalledSkills();
27961
28799
  const agents = [];
27962
28800
  for (const agent of AGENT_TARGETS) {
27963
28801
  const agentSkillsPath = getAgentSkillsDir(agent, "global");
27964
- const exists = existsSync13(agentSkillsPath);
28802
+ const exists = existsSync14(agentSkillsPath);
27965
28803
  let skillCount = 0;
27966
28804
  if (exists) {
27967
28805
  try {
27968
28806
  skillCount = readdirSync8(agentSkillsPath).filter((f) => {
27969
- const full = join14(agentSkillsPath, f);
27970
- return !f.startsWith(".") && statSync8(full).isDirectory();
28807
+ const full = join15(agentSkillsPath, f);
28808
+ return !f.startsWith(".") && statSync9(full).isDirectory();
27971
28809
  }).length;
27972
28810
  } catch {}
27973
28811
  }
@@ -27980,7 +28818,8 @@ function registerOperationTools(server) {
27980
28818
  installed,
27981
28819
  agents,
27982
28820
  skillsDir,
27983
- cwd
28821
+ cwd,
28822
+ credential
27984
28823
  };
27985
28824
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
27986
28825
  });
@@ -28012,29 +28851,17 @@ function compactRunToolPayload(payload, detailHint) {
28012
28851
  }
28013
28852
 
28014
28853
  // src/lib/feedback.ts
28015
- import { appendFileSync, existsSync as existsSync14, mkdirSync as mkdirSync6 } from "fs";
28016
- import { dirname as dirname5, join as join15 } from "path";
28854
+ import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync7 } from "fs";
28855
+ import { dirname as dirname6, join as join16 } from "path";
28017
28856
  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
28857
  function getFeedbackDbPath() {
28031
- return join15(getDataDir(), "skills.db");
28858
+ return join16(getDataDir(), "skills.db");
28032
28859
  }
28033
28860
  function getFeedbackDb() {
28034
28861
  const dbPath = getFeedbackDbPath();
28035
- const dir = dirname5(dbPath);
28036
- if (!existsSync14(dir))
28037
- mkdirSync6(dir, { recursive: true });
28862
+ const dir = dirname6(dbPath);
28863
+ if (!existsSync15(dir))
28864
+ mkdirSync7(dir, { recursive: true });
28038
28865
  const db = new Database(dbPath);
28039
28866
  db.exec("PRAGMA journal_mode = WAL");
28040
28867
  db.exec([
@@ -28060,10 +28887,10 @@ function saveFeedback(input) {
28060
28887
  throw new Error("Feedback message is required");
28061
28888
  const category = input.category ?? "general";
28062
28889
  if (isApiMode()) {
28063
- const path = join15(getDataDir(), "feedback.jsonl");
28064
- const dir = dirname5(path);
28065
- if (!existsSync14(dir))
28066
- mkdirSync6(dir, { recursive: true });
28890
+ const path = join16(getDataDir(), "feedback.jsonl");
28891
+ const dir = dirname6(path);
28892
+ if (!existsSync15(dir))
28893
+ mkdirSync7(dir, { recursive: true });
28067
28894
  appendFileSync(path, JSON.stringify({ message, category, email: input.email ?? null, agent: input.agent ?? null, version: input.version ?? null, createdAt: new Date().toISOString() }) + `
28068
28895
  `);
28069
28896
  return { saved: true, category, path };
@@ -28208,26 +29035,26 @@ function registerResourceMetaTools(server) {
28208
29035
  }
28209
29036
 
28210
29037
  // src/lib/scheduler.ts
28211
- import { existsSync as existsSync15, readFileSync as readFileSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7 } from "fs";
28212
- import { join as join16 } from "path";
29038
+ import { existsSync as existsSync16, readFileSync as readFileSync13, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8 } from "fs";
29039
+ import { join as join17 } from "path";
28213
29040
  function getSchedulesPath(targetDir = process.cwd()) {
28214
- return join16(targetDir, ".skills", "schedules.json");
29041
+ return join17(targetDir, ".skills", "schedules.json");
28215
29042
  }
28216
29043
  function loadSchedules(targetDir = process.cwd()) {
28217
29044
  const path = getSchedulesPath(targetDir);
28218
- if (existsSync15(path)) {
29045
+ if (existsSync16(path)) {
28219
29046
  try {
28220
- return JSON.parse(readFileSync12(path, "utf-8"));
29047
+ return JSON.parse(readFileSync13(path, "utf-8"));
28221
29048
  } catch {}
28222
29049
  }
28223
29050
  return { version: 1, schedules: [] };
28224
29051
  }
28225
29052
  function saveSchedules(data, targetDir = process.cwd()) {
28226
29053
  const path = getSchedulesPath(targetDir);
28227
- const dir = join16(targetDir, ".skills");
28228
- if (!existsSync15(dir))
28229
- mkdirSync7(dir, { recursive: true });
28230
- writeFileSync6(path, JSON.stringify(data, null, 2));
29054
+ const dir = join17(targetDir, ".skills");
29055
+ if (!existsSync16(dir))
29056
+ mkdirSync8(dir, { recursive: true });
29057
+ writeFileSync7(path, JSON.stringify(data, null, 2));
28231
29058
  }
28232
29059
  function validateCronField(expr, min, max, label) {
28233
29060
  for (const part of expr.split(",")) {
@@ -28487,14 +29314,14 @@ function registerScheduleTools(server) {
28487
29314
  // src/lib/native-storage.ts
28488
29315
  import { createHash as createHash4, createHmac } from "crypto";
28489
29316
  import {
28490
- existsSync as existsSync16,
28491
- mkdirSync as mkdirSync8,
28492
- readFileSync as readFileSync13,
29317
+ existsSync as existsSync17,
29318
+ mkdirSync as mkdirSync9,
29319
+ readFileSync as readFileSync14,
28493
29320
  readdirSync as readdirSync9,
28494
- statSync as statSync9,
28495
- writeFileSync as writeFileSync7
29321
+ statSync as statSync10,
29322
+ writeFileSync as writeFileSync8
28496
29323
  } from "fs";
28497
- import { dirname as dirname6, join as join17, normalize as normalize3, relative as relative3, sep as sep2 } from "path";
29324
+ import { dirname as dirname7, join as join18, normalize as normalize3, relative as relative3, sep as sep2 } from "path";
28498
29325
  var SKILLS_STORAGE_TABLES = [
28499
29326
  "skills_sync_records",
28500
29327
  "skills_sync_cursors"
@@ -28570,7 +29397,7 @@ function getSkillsNativeStorageStatus(options = {}) {
28570
29397
  local: {
28571
29398
  dataDir: getDataDir(),
28572
29399
  projectStateDir: getProjectStateDir(targetDir),
28573
- feedbackDbPath: join17(getDataDir(), "skills.db")
29400
+ feedbackDbPath: join18(getDataDir(), "skills.db")
28574
29401
  },
28575
29402
  remote: {
28576
29403
  databaseConfigured: Boolean(config2.databaseUrl),
@@ -28590,9 +29417,9 @@ function getStorageStatus(options = {}) {
28590
29417
  function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
28591
29418
  const projectStateDir = getProjectStateDir(targetDir);
28592
29419
  const files = [];
28593
- if (existsSync16(projectStateDir)) {
29420
+ if (existsSync17(projectStateDir)) {
28594
29421
  for (const filePath of walkFiles2(projectStateDir)) {
28595
- const bytes = readFileSync13(filePath);
29422
+ const bytes = readFileSync14(filePath);
28596
29423
  const relativePath = toPosix(relative3(targetDir, filePath));
28597
29424
  files.push({
28598
29425
  path: relativePath,
@@ -28674,8 +29501,8 @@ function parsePositiveInteger(value) {
28674
29501
  function walkFiles2(dir) {
28675
29502
  const files = [];
28676
29503
  for (const entry of readdirSync9(dir)) {
28677
- const full = join17(dir, entry);
28678
- const stats = statSync9(full);
29504
+ const full = join18(dir, entry);
29505
+ const stats = statSync10(full);
28679
29506
  if (stats.isDirectory())
28680
29507
  files.push(...walkFiles2(full));
28681
29508
  else
@@ -28730,6 +29557,10 @@ function registerStorageTools(server) {
28730
29557
  }
28731
29558
 
28732
29559
  // src/lib/remote-auth.ts
29560
+ init_remote_workspace_selection();
29561
+ init_remote_files();
29562
+ init_remote_workspace();
29563
+ init_remote_workspace();
28733
29564
  init_remote_client();
28734
29565
  init_fleet_credentials();
28735
29566
  var MAX_ERROR_DETAIL_LENGTH = 200;
@@ -28768,13 +29599,13 @@ async function requestAuthApi(instance, path, options) {
28768
29599
  apiUrl: safeUrl
28769
29600
  });
28770
29601
  }
28771
- const text = await res.text();
28772
- const body = text ? parseJsonBody(text) : {};
29602
+ const text2 = await res.text();
29603
+ const body = text2 ? parseJsonBody(text2) : {};
28773
29604
  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;
29605
+ const record5 = isRecord5(body) ? body : {};
29606
+ const detail = typeof record5.detail === "string" ? record5.detail : undefined;
29607
+ const error2 = typeof record5.error === "string" ? record5.error : undefined;
29608
+ const code = typeof record5.code === "string" ? record5.code : undefined;
28778
29609
  throw new HostedApiError(detail || error2 || `${res.status} ${res.statusText}`, {
28779
29610
  status: res.status,
28780
29611
  code,
@@ -28785,21 +29616,21 @@ async function requestAuthApi(instance, path, options) {
28785
29616
  }
28786
29617
  return body;
28787
29618
  }
28788
- function parseJsonBody(text) {
29619
+ function parseJsonBody(text2) {
28789
29620
  try {
28790
- return JSON.parse(text);
29621
+ return JSON.parse(text2);
28791
29622
  } catch {
28792
- return { detail: condenseErrorBody(text) };
29623
+ return { detail: condenseErrorBody(text2) };
28793
29624
  }
28794
29625
  }
28795
- function condenseErrorBody(text) {
28796
- const stripped = /<[a-z!/]/i.test(text) ? text.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]*>/g, " ") : text;
29626
+ function condenseErrorBody(text2) {
29627
+ const stripped = /<[a-z!/]/i.test(text2) ? text2.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]*>/g, " ") : text2;
28797
29628
  const collapsed = stripped.replace(/\s+/g, " ").trim();
28798
29629
  if (collapsed.length <= MAX_ERROR_DETAIL_LENGTH)
28799
29630
  return collapsed;
28800
29631
  return `${collapsed.slice(0, MAX_ERROR_DETAIL_LENGTH - 1).trimEnd()}\u2026`;
28801
29632
  }
28802
- function isRecord4(value) {
29633
+ function isRecord5(value) {
28803
29634
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
28804
29635
  }
28805
29636
 
@@ -28820,22 +29651,90 @@ class RemoteSkillsAuthClient {
28820
29651
  pollDevice(deviceCode) {
28821
29652
  return this.request("/api/auth/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) });
28822
29653
  }
28823
- async sessionClient(email2, code) {
29654
+ async sessionClient(email2, code, context) {
29655
+ if (context !== undefined) {
29656
+ const target = workspaceContext(context), apiOrigin2 = this.apiOrigin;
29657
+ const session = await this.switchWorkspace(email2, code, target);
29658
+ return new RemoteSkillsClient(session.token, apiOrigin2);
29659
+ }
29660
+ const apiOrigin = this.apiOrigin;
28824
29661
  if (!email2.includes("@") || !/^\d{6}$/.test(code))
28825
- throw new Error("Fresh email and six-digit verification code are required to manage API keys");
29662
+ throw new Error("Fresh email and six-digit verification code are required to manage this account");
28826
29663
  const login = await this.verifyCode(email2, code);
28827
29664
  if (!login || typeof login.token !== "string" || !login.token)
28828
29665
  throw new Error("The server did not return an authorized account session");
28829
- return new RemoteSkillsClient(login.token, this.apiOrigin);
29666
+ return new RemoteSkillsClient(login.token, apiOrigin);
29667
+ }
29668
+ async listAccountWorkspaces(email2, code, expectedUserId) {
29669
+ const login = await this.workspaceLogin(email2, code, expectedUserId);
29670
+ const result = await new RemoteSkillsClient(login.token, login.apiOrigin).listAccountWorkspaces(login.userId);
29671
+ return { userId: login.userId, ...result };
29672
+ }
29673
+ async switchWorkspace(email2, code, context) {
29674
+ const target = workspaceContext(context);
29675
+ const login = await this.workspaceLogin(email2, code, target.userId);
29676
+ return new RemoteSkillsClient(login.token, login.apiOrigin).switchWorkspace(target);
29677
+ }
29678
+ async workspaceLogin(email2, code, expectedUserId) {
29679
+ const expected = expectedUserId === undefined ? undefined : workspaceExpectedUserId(expectedUserId);
29680
+ const apiOrigin = this.apiOrigin;
29681
+ if (typeof email2 !== "string" || !email2.includes("@") || typeof code !== "string" || !/^\d{6}$/.test(code))
29682
+ throw new Error("Fresh email and six-digit verification code are required to manage this account");
29683
+ let response;
29684
+ try {
29685
+ response = await fetch(`${apiOrigin}/api/auth/verify`, {
29686
+ method: "POST",
29687
+ redirect: "error",
29688
+ credentials: "omit",
29689
+ signal: AbortSignal.timeout(15000),
29690
+ headers: { "Content-Type": "application/json" },
29691
+ body: JSON.stringify({ email: email2, code })
29692
+ });
29693
+ } catch {
29694
+ throw new HostedApiError("Unable to verify the Skills account.");
29695
+ }
29696
+ if (!response.ok) {
29697
+ response.body?.cancel().catch(() => {});
29698
+ throw new HostedApiError("Unable to verify the Skills account.", { status: response.status });
29699
+ }
29700
+ let value;
29701
+ try {
29702
+ value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, 64 * 1024)));
29703
+ } catch {
29704
+ throw new HostedApiError("The server returned an invalid account verification result.");
29705
+ }
29706
+ return { ...parseWorkspaceLogin(value, expected), apiOrigin };
28830
29707
  }
28831
- async createApiKey(email2, code, name, scopes) {
28832
- return (await this.sessionClient(email2, code)).createApiKey(name, scopes);
29708
+ async createApiKey(email2, code, name, scopes, context) {
29709
+ const capturedScopes = scopes === undefined ? undefined : [...scopes];
29710
+ return (await this.sessionClient(email2, code, context)).createApiKey(name, capturedScopes);
28833
29711
  }
28834
- async listApiKeys(email2, code) {
28835
- return (await this.sessionClient(email2, code)).listApiKeys();
29712
+ async listApiKeys(email2, code, context) {
29713
+ return (await this.sessionClient(email2, code, context)).listApiKeys();
28836
29714
  }
28837
- async revokeApiKey(email2, code, keyId) {
28838
- return (await this.sessionClient(email2, code)).revokeApiKey(keyId);
29715
+ async revokeApiKey(email2, code, keyId, context) {
29716
+ return (await this.sessionClient(email2, code, context)).revokeApiKey(keyId);
29717
+ }
29718
+ async updateProfile(email2, code, input, context) {
29719
+ const body = customerNamePatch(input, "displayName");
29720
+ return (await this.sessionClient(email2, code, context)).updateProfile({ displayName: body.displayName });
29721
+ }
29722
+ async updateCurrentWorkspace(email2, code, input, context) {
29723
+ const body = customerNamePatch(input, "name");
29724
+ return (await this.sessionClient(email2, code, context)).updateCurrentWorkspace({ name: body.name });
29725
+ }
29726
+ async listWorkspaceMembers(email2, code, options = {}, context) {
29727
+ workspaceMembersQuery(options);
29728
+ const captured = { ...options };
29729
+ return (await this.sessionClient(email2, code, context)).listWorkspaceMembers(captured);
29730
+ }
29731
+ async setWorkspaceMemberRole(email2, code, membershipId, input, context) {
29732
+ const captured = workspaceMemberRoleInput(membershipId, input);
29733
+ return (await this.sessionClient(email2, code, context)).setWorkspaceMemberRole(captured.membershipId, captured.body);
29734
+ }
29735
+ async removeWorkspaceMember(email2, code, membershipId, input, context) {
29736
+ const captured = workspaceMemberRemovalInput(membershipId, input);
29737
+ return (await this.sessionClient(email2, code, context)).removeWorkspaceMember(captured.membershipId, captured.body);
28839
29738
  }
28840
29739
  request(path, options) {
28841
29740
  if (!["/api/auth/login", "/api/auth/verify", "/api/auth/device/start", "/api/auth/device/token", "/api/auth/keys", "/api/auth/whoami"].includes(path))
@@ -28844,10 +29743,147 @@ class RemoteSkillsAuthClient {
28844
29743
  }
28845
29744
  }
28846
29745
 
28847
- // src/mcp/remote-customer-tools.ts
29746
+ // src/lib/workspace-profile.ts
29747
+ import { constants as constants2, closeSync as closeSync3, fstatSync as fstatSync3, lstatSync as lstatSync4, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync2, openSync as openSync3, readFileSync as readFileSync15, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync9 } from "fs";
28848
29748
  init_auth_store();
29749
+ init_fleet_credentials();
29750
+ init_instance_credentials();
29751
+ init_remote_client();
29752
+ init_remote_workspace_selection();
29753
+
29754
+ class WorkspaceProfileError extends Error {
29755
+ }
29756
+ var fail = (message) => {
29757
+ throw new WorkspaceProfileError(message);
29758
+ };
29759
+ function stat(path) {
29760
+ try {
29761
+ return lstatSync4(path);
29762
+ } catch (error2) {
29763
+ if (error2.code === "ENOENT")
29764
+ return null;
29765
+ throw error2;
29766
+ }
29767
+ }
29768
+ function safeText(file) {
29769
+ if (stat(file) === null)
29770
+ return null;
29771
+ const fd = openSync3(file, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
29772
+ try {
29773
+ const s = fstatSync3(fd);
29774
+ if (!s.isFile() || s.size > 65536 || ![256, 384].includes(s.mode & 4095) || process.getuid && s.uid !== process.getuid())
29775
+ return fail("The selected profile must use bounded owner-only regular files.");
29776
+ return readFileSync15(fd, "utf8");
29777
+ } finally {
29778
+ closeSync3(fd);
29779
+ }
29780
+ }
29781
+ function checkIdentityMetadata(file, identity) {
29782
+ const text2 = safeText(file);
29783
+ if (text2 === null)
29784
+ return;
29785
+ let value;
29786
+ try {
29787
+ value = JSON.parse(text2);
29788
+ } catch {
29789
+ return fail("The profile identity metadata is invalid. Sign in again before managing this workspace.");
29790
+ }
29791
+ if (!value || typeof value !== "object" || Array.isArray(value))
29792
+ return fail("The profile identity metadata is invalid.");
29793
+ for (const [key, expected] of Object.entries({ userId: identity.user.id, orgId: identity.organization.id })) {
29794
+ if (value[key] !== undefined && value[key] !== expected)
29795
+ return fail("The profile identity metadata does not match its authenticated key. Sign in again before managing this workspace.");
29796
+ }
29797
+ }
29798
+ async function keyIdentity(key, origin) {
29799
+ const value = await new RemoteSkillsClient(key, origin).getIdentity();
29800
+ if (value.authMethod !== "api_key")
29801
+ return fail("The selected credential is not a workspace API key.");
29802
+ const user = value.user;
29803
+ return parseWorkspaceIdentity(value, workspaceExpectedUserId(user?.id));
29804
+ }
29805
+ function prepareProfileWorkspace(action, source = process.env) {
29806
+ const env = { ...source }, origin = getApiUrl(action, env), profile = selectedSkillsProfile(env);
29807
+ const unchanged2 = captureSkillsCredentialFiles(skillsProfileCredentialFiles(env));
29808
+ return { origin, async resolve() {
29809
+ unchanged2();
29810
+ if (!profile)
29811
+ return { origin, context: undefined, unchanged: unchanged2 };
29812
+ const connection = await resolveSkillsConnection(env);
29813
+ if (!connection || connection.apiOrigin !== origin)
29814
+ return fail("The selected profile has no usable credential for this server.");
29815
+ const identity = await keyIdentity(connection.apiKey, origin);
29816
+ checkIdentityMetadata(getIdentityFilePath(env), identity);
29817
+ unchanged2();
29818
+ const context = { userId: identity.user.id, membershipId: identity.user.membershipId };
29819
+ return { origin, context, unchanged: unchanged2 };
29820
+ } };
29821
+ }
29822
+ async function captureProfileWorkspace(action, source = process.env) {
29823
+ return prepareProfileWorkspace(action, source).resolve();
29824
+ }
29825
+
29826
+ // src/mcp/remote-customer-tools.ts
28849
29827
  init_remote_client();
28850
29828
  function registerRemoteCustomerTools(server) {
29829
+ const memberRole = exports_external.enum(["owner", "admin", "member", "viewer"]);
29830
+ const memberInput = {
29831
+ 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}$/),
29832
+ expectedRole: memberRole,
29833
+ email: exports_external.string().email(),
29834
+ code: exports_external.string().regex(/^\d{6}$/)
29835
+ };
29836
+ server.registerTool("set_workspace_member_role", {
29837
+ title: "Set Current Workspace Member Role",
29838
+ 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.",
29839
+ inputSchema: exports_external.object({ ...memberInput, role: memberRole }).strict()
29840
+ }, async ({ membershipId, role: role2, expectedRole, email: email2, code }) => {
29841
+ try {
29842
+ return mcpJson(await freshAccount("Set workspace member role", (client, context) => client.setWorkspaceMemberRole(email2, code, membershipId, { role: role2, expectedRole }, context)));
29843
+ } catch (error2) {
29844
+ return memberError(error2);
29845
+ }
29846
+ });
29847
+ server.registerTool("remove_workspace_member", {
29848
+ title: "Remove Current Workspace Member",
29849
+ 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.",
29850
+ inputSchema: exports_external.object(memberInput).strict()
29851
+ }, async ({ membershipId, expectedRole, email: email2, code }) => {
29852
+ try {
29853
+ return mcpJson(await freshAccount("Remove workspace member", (client, context) => client.removeWorkspaceMember(email2, code, membershipId, { expectedRole }, context)));
29854
+ } catch (error2) {
29855
+ return memberError(error2);
29856
+ }
29857
+ });
29858
+ server.registerTool("list_workspace_members", {
29859
+ title: "List Current Workspace Members",
29860
+ 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.",
29861
+ inputSchema: exports_external.object({
29862
+ email: exports_external.string().email(),
29863
+ code: exports_external.string().regex(/^\d{6}$/),
29864
+ limit: exports_external.number().int().min(1).max(100).optional(),
29865
+ cursor: exports_external.string().regex(/^[A-Za-z0-9_-]{1,512}$/).optional()
29866
+ }).strict()
29867
+ }, async ({ email: email2, code, limit, cursor: cursor2 }) => {
29868
+ try {
29869
+ return mcpJson(await freshAccount("List workspace members", (client, context) => client.listWorkspaceMembers(email2, code, { limit, cursor: cursor2 }, context)));
29870
+ } catch {
29871
+ return mcpError("WORKSPACE_MEMBERS_FAILED", "Unable to list workspace members. Check the selected server, owner/admin permissions, pagination and fresh verification code.");
29872
+ }
29873
+ });
29874
+ for (const kind of ["profile", "workspace"]) {
29875
+ server.registerTool(kind === "profile" ? "update_account_profile" : "update_workspace_name", {
29876
+ title: kind === "profile" ? "Update Account Display Name" : "Update Workspace Name",
29877
+ 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.",
29878
+ inputSchema: exports_external.object({ name: exports_external.string().min(1), email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/) }).strict()
29879
+ }, async ({ name, email: email2, code }) => {
29880
+ try {
29881
+ return mcpJson(await freshAccount("Update customer name", async (client, context) => kind === "profile" ? client.updateProfile(email2, code, { displayName: name }, context) : client.updateCurrentWorkspace(email2, code, { name }, context)));
29882
+ } catch {
29883
+ return mcpError("NAME_UPDATE_FAILED", "Unable to update the name. Check the selected server, name, permissions and fresh verification code.");
29884
+ }
29885
+ });
29886
+ }
28851
29887
  for (const operation of REMOTE_CUSTOMER_OPERATIONS) {
28852
29888
  const inputSchema = {};
28853
29889
  if (operation.parameter)
@@ -28864,9 +29900,9 @@ function registerRemoteCustomerTools(server) {
28864
29900
  inputSchema: { email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/) }
28865
29901
  }, async ({ email: email2, code }) => {
28866
29902
  try {
28867
- return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("List API keys")).listApiKeys(email2, code));
28868
- } catch (error2) {
28869
- return mcpError("KEY_LIST_FAILED", error2.message);
29903
+ return mcpJson(await freshAccount("List API keys", (client, context) => client.listApiKeys(email2, code, context)));
29904
+ } catch {
29905
+ return mcpError("KEY_LIST_FAILED", "Unable to list API keys. Check the selected profile, server, account and fresh verification code.");
28870
29906
  }
28871
29907
  });
28872
29908
  server.registerTool("revoke_api_key", {
@@ -28875,9 +29911,9 @@ function registerRemoteCustomerTools(server) {
28875
29911
  inputSchema: { key_id: exports_external.string().min(1), email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/) }
28876
29912
  }, async ({ key_id, email: email2, code }) => {
28877
29913
  try {
28878
- return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("Revoke API key")).revokeApiKey(email2, code, key_id));
28879
- } catch (error2) {
28880
- return mcpError("KEY_REVOKE_FAILED", error2.message);
29914
+ return mcpJson(await freshAccount("Revoke API key", (client, context) => client.revokeApiKey(email2, code, key_id, context)));
29915
+ } catch {
29916
+ return mcpError("KEY_REVOKE_FAILED", "Unable to revoke this API key. Check the selected profile, key, account and fresh verification code.");
28881
29917
  }
28882
29918
  });
28883
29919
  server.registerTool("create_api_key", {
@@ -28885,10 +29921,11 @@ function registerRemoteCustomerTools(server) {
28885
29921
  description: "Create an API key using fresh email OTP reauthentication; returns its secret once. A stored API key cannot grant this authority.",
28886
29922
  inputSchema: { name: exports_external.string().min(1), email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/), scopes: exports_external.array(exports_external.string()).optional() }
28887
29923
  }, async ({ name, email: email2, code, scopes }) => {
29924
+ const capturedScopes = scopes === undefined ? undefined : [...scopes];
28888
29925
  try {
28889
- return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("Create API key")).createApiKey(email2, code, name, scopes));
28890
- } catch (error2) {
28891
- return mcpError("KEY_CREATION_FAILED", error2.message);
29926
+ return mcpJson(await freshAccount("Create API key", (client, context) => client.createApiKey(email2, code, name, capturedScopes, context)));
29927
+ } catch {
29928
+ return mcpError("KEY_CREATION_FAILED", "API key creation could not be confirmed. Check the selected profile and workspace keys before retrying; a lost response may still have created a key.");
28892
29929
  }
28893
29930
  });
28894
29931
  server.registerTool("quote_skill", {
@@ -28906,6 +29943,9 @@ function registerRemoteCustomerTools(server) {
28906
29943
  return { ...metadata, base64: Buffer.from(bytes).toString("base64") };
28907
29944
  }));
28908
29945
  }
29946
+ function memberError(error2) {
29947
+ 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.");
29948
+ }
28909
29949
  async function callRemote(action) {
28910
29950
  try {
28911
29951
  const client = await createRemoteSkillsClient();
@@ -28913,9 +29953,17 @@ async function callRemote(action) {
28913
29953
  return mcpError("AUTH_REQUIRED", "Configure a Skills API and sign in with skills auth login");
28914
29954
  return mcpJson(await action(client));
28915
29955
  } catch (error2) {
29956
+ if (error2 instanceof RemoteCapabilityUnavailableError) {
29957
+ return { ...mcpJson({ code: error2.code, message: error2.message, status: error2.status }), isError: true };
29958
+ }
28916
29959
  return mcpError("REMOTE_REQUEST_FAILED", error2 instanceof Error ? error2.message : "Skills server request failed");
28917
29960
  }
28918
29961
  }
29962
+ async function freshAccount(action, operation) {
29963
+ const target = await captureProfileWorkspace(action);
29964
+ target.unchanged();
29965
+ return operation(new RemoteSkillsAuthClient(target.origin), target.context);
29966
+ }
28919
29967
 
28920
29968
  // src/mcp/server.ts
28921
29969
  function buildServer() {
@@ -30383,6 +31431,7 @@ async function startSkillsMcpHttpServer(options = {}) {
30383
31431
  }
30384
31432
 
30385
31433
  // src/mcp/index.ts
31434
+ init_fleet_credentials();
30386
31435
  var args = process.argv.slice(2);
30387
31436
  function printHelp() {
30388
31437
  console.log(`Usage: skills-mcp [options]
@@ -30404,7 +31453,18 @@ if (args.includes("--version") || args.includes("-V")) {
30404
31453
  console.log(package_default.version);
30405
31454
  process.exit(0);
30406
31455
  }
31456
+ function assertSkillsMcpConfigured(env = process.env) {
31457
+ try {
31458
+ resolveSkillsFleet(env);
31459
+ } catch (error2) {
31460
+ if (!isSkillsFleetCredentialError(error2))
31461
+ throw error2;
31462
+ console.error(error2.message);
31463
+ process.exit(1);
31464
+ }
31465
+ }
30407
31466
  async function startMcpStdio() {
31467
+ assertSkillsMcpConfigured();
30408
31468
  const server2 = buildServer();
30409
31469
  await server2.connect(new StdioServerTransport);
30410
31470
  }
@@ -30413,6 +31473,7 @@ async function main() {
30413
31473
  await startMcpStdio();
30414
31474
  return;
30415
31475
  }
31476
+ assertSkillsMcpConfigured();
30416
31477
  const port = parseMcpHttpPort(args);
30417
31478
  await startSkillsMcpHttpServer({ port, hostname: "127.0.0.1" });
30418
31479
  }
@@ -30424,5 +31485,6 @@ if (import.meta.main) {
30424
31485
  }
30425
31486
  export {
30426
31487
  startMcpStdio,
30427
- buildServer
31488
+ buildServer,
31489
+ assertSkillsMcpConfigured
30428
31490
  };