@hasna/skills 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +269 -14
  2. package/bin/index.js +7835 -5440
  3. package/bin/mcp.js +2040 -589
  4. package/bin/migrate.js +148 -40
  5. package/bin/server.js +66 -87
  6. package/bin/worker.js +42 -75
  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/remote-account.d.ts +7 -0
  13. package/dist/cli/commands/tool-primitives.d.ts +1 -1
  14. package/dist/cli/commands/workspace-member-mutations.d.ts +2 -0
  15. package/dist/cli/commands/workspace-members.d.ts +2 -0
  16. package/dist/cli/env-assignment.d.ts +9 -0
  17. package/dist/index.d.ts +7 -2
  18. package/dist/index.js +1369 -349
  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/auth-store.d.ts +1 -1
  23. package/dist/lib/client-types.d.ts +75 -0
  24. package/dist/lib/credential-state.d.ts +12 -0
  25. package/dist/lib/fleet-credentials.d.ts +49 -17
  26. package/dist/lib/home-adoption.d.ts +2 -0
  27. package/dist/lib/home-census.d.ts +3 -1
  28. package/dist/lib/instance-credentials.d.ts +13 -0
  29. package/dist/lib/local-opt-in.d.ts +24 -0
  30. package/dist/lib/mcp-contracts.d.ts +4 -0
  31. package/dist/lib/portable-skills-files.d.ts +10 -2
  32. package/dist/lib/portable-skills-types.d.ts +2 -0
  33. package/dist/lib/read-access.d.ts +83 -0
  34. package/dist/lib/remote-account.d.ts +42 -0
  35. package/dist/lib/remote-auth.d.ts +46 -0
  36. package/dist/lib/remote-client.d.ts +90 -6
  37. package/dist/lib/remote-customer-operations.d.ts +106 -0
  38. package/dist/lib/remote-files.d.ts +21 -0
  39. package/dist/lib/remote-profile.d.ts +26 -0
  40. package/dist/lib/remote-registry.d.ts +7 -3
  41. package/dist/lib/remote-workspace.d.ts +76 -0
  42. package/dist/lib/run-routing.d.ts +1 -0
  43. package/dist/lib/run-state.d.ts +3 -0
  44. package/dist/lib/skillinfo.d.ts +1 -1
  45. package/dist/mcp/helpers.d.ts +22 -0
  46. package/dist/mcp/index.d.ts +16 -0
  47. package/dist/mcp/remote-customer-tools.d.ts +2 -0
  48. package/dist/sdk/governance-store.d.ts +1 -0
  49. package/dist/sdk/index.d.ts +8 -1
  50. package/dist/sdk/index.js +1994 -416
  51. package/dist/sdk/outputs.d.ts +0 -11
  52. package/dist/sdk/runs.d.ts +5 -5
  53. package/dist/storage.js +6 -40
  54. package/docs/skill-standard.md +30 -2
  55. package/package.json +7 -6
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");
@@ -7608,93 +7564,7 @@ function toV1BaseUrl(apiUrl) {
7608
7564
  url.pathname = `${path}/v1`;
7609
7565
  return url.toString().replace(/\/+$/, "");
7610
7566
  }
7611
- function resolveClientTransportSnapshot(name, env = process.env, options = {}) {
7612
- env = snapshotClientEnvironment(name, env);
7613
- const keys = clientTransportEnvKeys(name);
7614
- const definedUrlEntries = keys.apiUrlKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, raw: String(env[key]) }));
7615
- const blankUrl = definedUrlEntries.find((entry) => entry.raw.trim().length === 0);
7616
- if (blankUrl) {
7617
- throw new ClientTransportConfigurationError(name, `${blankUrl.key} is set but blank; public clients require an explicit HTTPS API URL and never select local storage.`, [blankUrl.key]);
7618
- }
7619
- const controlledUrl = definedUrlEntries.find((entry) => ASCII_CONTROL_PATTERN.test(entry.raw));
7620
- if (controlledUrl) {
7621
- throw new ClientTransportConfigurationError(name, `${controlledUrl.key} contains ASCII control characters.`, [controlledUrl.key]);
7622
- }
7623
- const usableUrlEntries = definedUrlEntries.map((entry) => ({ key: entry.key, value: entry.raw.trim() }));
7624
- if (usableUrlEntries.length > 1 && new Set(usableUrlEntries.map((entry) => entry.value)).size > 1) {
7625
- throw new ClientTransportConfigurationError(name, `${usableUrlEntries.map((entry) => entry.key).join(" and ")} disagree; client authority aliases must be identical or only one may be set.`, usableUrlEntries.map((entry) => entry.key));
7626
- }
7627
- const envUrlHit = usableUrlEntries[0] ?? null;
7628
- const keychainUrlHit = keychainConfigValue(name, env, options.credentials?.keychain);
7629
- const diskConfigUrlHit = appConfigDiskValue(name, env, keys.apiUrlKeys);
7630
- if (diskConfigUrlHit?.unusable) {
7631
- throw new ClientTransportConfigurationError(name, `${diskConfigUrlHit.key} in ${diskConfigUrlHit.path} is declared but blank or malformed; public clients require a valid HTTPS service authority.`, [diskConfigUrlHit.path]);
7632
- }
7633
- const urlCandidates = [
7634
- ...envUrlHit ? [envUrlHit] : [],
7635
- ...keychainUrlHit ? [{ key: keychainUrlHit.source, value: keychainUrlHit.value }] : [],
7636
- ...diskConfigUrlHit ? [{ key: diskConfigUrlHit.path, value: diskConfigUrlHit.value.trim() }] : []
7637
- ];
7638
- const configuredUrl = urlCandidates[0] ?? null;
7639
- const divergentUrls = urlCandidates.filter((candidate) => candidate.value !== configuredUrl?.value);
7640
- if (configuredUrl && divergentUrls.length > 0) {
7641
- throw new ClientTransportConfigurationError(name, `${configuredUrl.key} and ${divergentUrls.map((candidate) => candidate.key).join(" and ")} select different service authorities; refusing to send a credential written for one authority to the other.`, urlCandidates.map((candidate) => candidate.key));
7642
- }
7643
- const warnings = [];
7644
- if (configuredUrl && !envUrlHit) {
7645
- warnings.push(`No ${keys.apiUrlKeys[0]} in the environment; the server URL in ${configuredUrl.key} was used, so this client connects to the server. ` + `Keep that entry aligned with the intended service authority.`);
7646
- }
7647
- const credential = resolveCredential(name, env, options.credentials);
7648
- if (!credential) {
7649
- const diskHint = credentialDiskSourcesForMessage(name, env);
7650
- const lead = configuredUrl ? `${configuredUrl.key} selects the HTTP server for '${name}', but no API key could be resolved` : `${keys.apiUrlKeys[0]} is not set and no API key could be resolved for '${name}'; a credential is required before the default fleet gateway authority applies`;
7651
- warnings.push(`${lead}; refusing to create an unauthenticated client \u2014 public clients never fall back to SQLite or another local store. ` + `Looked in the Keychain (macOS only), then for a credential file at ${diskHint}, then for ${keys.apiKeyKeys[0]} in the environment.`);
7652
- throw new ClientTransportConfigurationError(name, warnings.join(" "), [configuredUrl?.key ?? keys.apiUrlKeys[0]]);
7653
- }
7654
- if (credential.warning)
7655
- warnings.push(credential.warning);
7656
- let urlHit;
7657
- if (configuredUrl) {
7658
- urlHit = configuredUrl;
7659
- } else {
7660
- try {
7661
- urlHit = { key: DEFAULT_AUTHORITY_SOURCE, value: defaultFleetGatewayBaseUrl(name) };
7662
- } catch (error2) {
7663
- const message = error2 instanceof Error ? error2.message : String(error2);
7664
- throw new ClientTransportConfigurationError(name, `No ${keys.apiUrlKeys[0]} is configured and the default fleet gateway authority cannot be composed for '${name}': ${message}`, [keys.apiUrlKeys[0]]);
7665
- }
7666
- }
7667
- const apiUrlSource = urlHit.key;
7668
- let baseUrl;
7669
- try {
7670
- baseUrl = toV1BaseUrl(urlHit.value);
7671
- } catch (error2) {
7672
- const message = error2 instanceof Error ? error2.message : String(error2);
7673
- throw new ClientTransportConfigurationError(name, `Invalid API URL from ${apiUrlSource}: ${message}`, [apiUrlSource]);
7674
- }
7675
- return {
7676
- resolution: {
7677
- transport: "http",
7678
- transportSource: urlHit.key,
7679
- baseUrl,
7680
- apiUrlSource,
7681
- apiKeyPresent: true,
7682
- apiKeySource: credential.source,
7683
- apiKeyTier: credential.tier,
7684
- misconfigured: false,
7685
- warning: warnings.length > 0 ? warnings.join(" ") : null
7686
- },
7687
- credential
7688
- };
7689
- }
7690
- function resolveClientTransport(name, env = process.env, options = {}) {
7691
- return resolveClientTransportSnapshot(name, env, options).resolution;
7692
- }
7693
- function credentialDiskSourcesForMessage(name, env) {
7694
- const paths = credentialDiskSources(name, env);
7695
- return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
7696
- }
7697
- var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM, CREDENTIAL_SEAL, AMBIENT_ENVIRONMENT, SECRETS_PACKAGE_SPECIFIER, requireSecretsSdk, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, ClientTransportConfigurationError, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
7567
+ var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM, CREDENTIAL_SEAL, AMBIENT_ENVIRONMENT, SECRETS_PACKAGE_SPECIFIER, requireSecretsSdk, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
7698
7568
  var init_transport = __esm(() => {
7699
7569
  CredentialResolutionError = class CredentialResolutionError extends Error {
7700
7570
  appName;
@@ -7727,16 +7597,6 @@ var init_transport = __esm(() => {
7727
7597
  requireSecretsSdk = createRequire(import.meta.url);
7728
7598
  ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
7729
7599
  DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
7730
- ClientTransportConfigurationError = class ClientTransportConfigurationError extends Error {
7731
- appName;
7732
- sources;
7733
- constructor(appName, message, sources = []) {
7734
- super(message);
7735
- this.name = "ClientTransportConfigurationError";
7736
- this.appName = appName;
7737
- this.sources = Object.freeze([...sources]);
7738
- }
7739
- };
7740
7600
  IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
7741
7601
  AUTHORITY_OVERRIDE_HEADERS = new Set([
7742
7602
  "host",
@@ -7747,13 +7607,132 @@ var init_transport = __esm(() => {
7747
7607
  ]);
7748
7608
  });
7749
7609
 
7610
+ // src/lib/instance-credentials.ts
7611
+ import { closeSync as closeSync2, constants, fstatSync as fstatSync2, lstatSync as lstatSync3, openSync as openSync2, readSync } from "fs";
7612
+ function selectedSkillsProfile(env, explicit) {
7613
+ const selected = explicit ?? env.HASNA_PROFILE;
7614
+ if (selected === undefined)
7615
+ return null;
7616
+ const profile = selected.trim();
7617
+ if (!/^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/.test(profile))
7618
+ throw new Error("Invalid Skills credential profile");
7619
+ return profile;
7620
+ }
7621
+ function skillsProfileCredentialFiles(env, explicit) {
7622
+ return credentialDiskSourceList("skills", env, selectedSkillsProfile(env, explicit)).map((source) => source.path);
7623
+ }
7624
+ function fileIdentity(file) {
7625
+ try {
7626
+ return lstatSync3(file);
7627
+ } catch (error2) {
7628
+ if (["ENOENT", "ENOTDIR"].includes(error2.code ?? ""))
7629
+ return null;
7630
+ throw new Error("Cannot inspect Skills instance configuration");
7631
+ }
7632
+ }
7633
+ function unchanged(before, after) {
7634
+ return before === null || after === null ? before === after : ["dev", "ino", "size", "mtimeMs", "ctimeMs", "mode", "uid"].every((key) => before[key] === after[key]);
7635
+ }
7636
+ function captureSkillsCredentialFiles(files) {
7637
+ const identities = files.map((file) => [file, fileIdentity(file)]);
7638
+ return () => {
7639
+ if (identities.some(([file, before]) => !unchanged(before, fileIdentity(file)))) {
7640
+ throw new Error("Skills instance configuration changed while resolving credentials; retry without sending a credential");
7641
+ }
7642
+ };
7643
+ }
7644
+ function readMetadataText(file) {
7645
+ let fd;
7646
+ try {
7647
+ fd = openSync2(file, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
7648
+ } catch (error2) {
7649
+ if (["ENOENT", "ENOTDIR"].includes(error2.code ?? ""))
7650
+ return null;
7651
+ throw new Error("Cannot safely read Skills instance configuration");
7652
+ }
7653
+ try {
7654
+ const before = fstatSync2(fd);
7655
+ const uid = process.getuid?.() ?? process.geteuid?.();
7656
+ if (!before.isFile() || ![256, 384].includes(before.mode & 4095) || uid !== undefined && before.uid !== uid || before.size > 64 * 1024) {
7657
+ throw new Error("Unsafe Skills instance configuration; expected a bounded owner-only regular file");
7658
+ }
7659
+ const bytes = Buffer.alloc(64 * 1024 + 1);
7660
+ let length = 0;
7661
+ while (length < bytes.length) {
7662
+ const count = readSync(fd, bytes, length, bytes.length - length, null);
7663
+ if (!count)
7664
+ break;
7665
+ length += count;
7666
+ }
7667
+ if (length > 64 * 1024 || !unchanged(before, fstatSync2(fd)) || !unchanged(before, fileIdentity(file))) {
7668
+ throw new Error("Skills instance configuration changed while reading");
7669
+ }
7670
+ return bytes.subarray(0, length).toString("utf8");
7671
+ } finally {
7672
+ closeSync2(fd);
7673
+ }
7674
+ }
7675
+ function readSkillsInstanceMetadata(file) {
7676
+ const text = readMetadataText(file);
7677
+ if (text === null)
7678
+ return {};
7679
+ const values = new Map;
7680
+ for (const line of text.split(/\r?\n/)) {
7681
+ const match = /^\s*(?:export\s+)?(HASNA_SKILLS_API_URL|SKILLS_API_URL|HASNA_SKILLS_BOUND_API_URL)\s*=\s*(.*)$/.exec(line);
7682
+ if (!match)
7683
+ continue;
7684
+ let value = match[2].trim();
7685
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))
7686
+ value = value.slice(1, -1);
7687
+ if (!value || /[\x00-\x20\x7f]/.test(value) || values.has(match[1]))
7688
+ throw new Error("Invalid Skills instance configuration");
7689
+ values.set(match[1], value);
7690
+ }
7691
+ const urls = [values.get("HASNA_SKILLS_API_URL"), values.get("SKILLS_API_URL")].filter(Boolean);
7692
+ if (new Set(urls).size > 1)
7693
+ throw new Error("Skills API URL aliases disagree");
7694
+ return { apiUrl: urls[0], binding: values.get(SKILLS_BOUND_API_URL) };
7695
+ }
7696
+ var SKILLS_BOUND_API_URL = "HASNA_SKILLS_BOUND_API_URL";
7697
+ var init_instance_credentials = __esm(() => {
7698
+ init_transport();
7699
+ });
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
+
7750
7727
  // src/lib/fleet-credentials.ts
7751
7728
  var exports_fleet_credentials = {};
7752
7729
  __export(exports_fleet_credentials, {
7753
7730
  skillsCredentialOrReason: () => skillsCredentialOrReason,
7754
7731
  skillsCredentialFiles: () => skillsCredentialFiles,
7755
7732
  skillsCredentialFilePath: () => skillsCredentialFilePath,
7733
+ selectsSkillsLocalMode: () => selectsSkillsLocalMode,
7756
7734
  resolveSkillsFleet: () => resolveSkillsFleet,
7735
+ resolveSkillsConnection: () => resolveSkillsConnection,
7757
7736
  resolveSkillsApiOrigin: () => resolveSkillsApiOrigin,
7758
7737
  resolveSkillsApiKey: () => resolveSkillsApiKey,
7759
7738
  resetLocalSkillsModeNotice: () => resetLocalSkillsModeNotice,
@@ -7762,8 +7741,11 @@ __export(exports_fleet_credentials, {
7762
7741
  requireSkillsApiKey: () => requireSkillsApiKey,
7763
7742
  noticeLocalSkillsMode: () => noticeLocalSkillsMode,
7764
7743
  normalizeSkillsApiOrigin: () => normalizeSkillsApiOrigin,
7744
+ isSkillsLocalOptIn: () => isSkillsLocalOptIn,
7745
+ isSkillsFleetCredentialError: () => isSkillsFleetCredentialError,
7765
7746
  configuredSkillsApiUrl: () => configuredSkillsApiUrl,
7766
7747
  SkillsFleetCredentialError: () => SkillsFleetCredentialError,
7748
+ SKILLS_LOCAL_OPT_IN_ENV_KEYS: () => SKILLS_LOCAL_OPT_IN_ENV_KEYS,
7767
7749
  SKILLS_APP: () => SKILLS_APP,
7768
7750
  SKILLS_API_URL_ENV_KEYS: () => SKILLS_API_URL_ENV_KEYS,
7769
7751
  SKILLS_API_URL_ENV: () => SKILLS_API_URL_ENV,
@@ -7771,12 +7753,12 @@ __export(exports_fleet_credentials, {
7771
7753
  SKILLS_API_KEY_ENV: () => SKILLS_API_KEY_ENV,
7772
7754
  MissingSkillsFleetError: () => MissingSkillsFleetError
7773
7755
  });
7774
- function isClientTransportConfigurationError(error2) {
7775
- return error2 instanceof ClientTransportConfigurationError || typeof error2 === "object" && error2 !== null && error2.name === "ClientTransportConfigurationError";
7776
- }
7777
7756
  function isCredentialResolutionError(error2) {
7778
7757
  return error2 instanceof CredentialResolutionError || typeof error2 === "object" && error2 !== null && error2.name === "CredentialResolutionError";
7779
7758
  }
7759
+ function isSkillsFleetCredentialError(error2) {
7760
+ return error2 instanceof SkillsFleetCredentialError || typeof error2 === "object" && error2 !== null && error2.name === "SkillsFleetCredentialError";
7761
+ }
7780
7762
  function asSkillsFleetCredentialError(error2) {
7781
7763
  if (!isCredentialResolutionError(error2))
7782
7764
  return null;
@@ -7784,6 +7766,9 @@ function asSkillsFleetCredentialError(error2) {
7784
7766
  }
7785
7767
  function normalizeSkillsApiOrigin(apiUrl) {
7786
7768
  const url = new URL(apiUrl);
7769
+ if (url.username || url.password || url.search || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) {
7770
+ throw new SkillsFleetCredentialError("A Skills API URL must use HTTPS (or loopback HTTP), without credentials, query or fragment", "INVALID_API_URL");
7771
+ }
7787
7772
  const pathname = url.pathname.replace(/\/+$/, "");
7788
7773
  if (pathname === "/api" || pathname === "/api/v1") {
7789
7774
  url.pathname = "/";
@@ -7794,11 +7779,24 @@ function normalizeSkillsApiOrigin(apiUrl) {
7794
7779
  }
7795
7780
  return url.toString().replace(/\/+$/, "");
7796
7781
  }
7797
- function configuredSkillsApiUrl(env = process.env, keychain) {
7798
- for (const key of SKILLS_API_URL_ENV_KEYS) {
7799
- const value = env[key]?.trim();
7800
- if (value)
7801
- return { value, source: key };
7782
+ function configuredSkillsApiUrl(env = process.env, keychain, profile) {
7783
+ const declared = SKILLS_API_URL_ENV_KEYS.filter((key) => env[key] !== undefined).map((key) => ({ key, value: env[key] }));
7784
+ for (const entry of declared) {
7785
+ if (!entry.value.trim() || /[\x00-\x1f\x7f]/.test(entry.value))
7786
+ throw new SkillsFleetCredentialError(`${entry.key} is blank or contains control characters`, "INVALID_API_URL");
7787
+ }
7788
+ const normalized = declared.map((entry) => ({ value: normalizeSkillsApiOrigin(entry.value), source: entry.key }));
7789
+ if (new Set(normalized.map((entry) => entry.value)).size > 1)
7790
+ throw new SkillsFleetCredentialError("Skills API URL aliases disagree", "INVALID_API_URL");
7791
+ if (normalized[0])
7792
+ return normalized[0];
7793
+ if (selectedSkillsProfile(env, profile)) {
7794
+ for (const file of skillsProfileCredentialFiles(env, profile)) {
7795
+ const metadata = readSkillsInstanceMetadata(file);
7796
+ if (metadata.apiUrl || metadata.binding)
7797
+ return { value: metadata.apiUrl ?? metadata.binding, source: file };
7798
+ }
7799
+ return { value: defaultFleetGatewayBaseUrl(SKILLS_APP), source: "default" };
7802
7800
  }
7803
7801
  const fromKeychain = keychainConfigValue(SKILLS_APP, env, keychain);
7804
7802
  if (fromKeychain)
@@ -7812,7 +7810,7 @@ function configuredSkillsApiUrl(env = process.env, keychain) {
7812
7810
  return null;
7813
7811
  }
7814
7812
  function skillsCredentialFiles(env = process.env) {
7815
- return credentialDiskSources(SKILLS_APP, env);
7813
+ return skillsProfileCredentialFiles(env);
7816
7814
  }
7817
7815
  function skillsCredentialFilePath(env = process.env) {
7818
7816
  const paths = skillsCredentialFiles(env);
@@ -7826,14 +7824,18 @@ function noticeLocalSkillsMode(write = (line) => console.error(line)) {
7826
7824
  if (localNoticePrinted)
7827
7825
  return;
7828
7826
  localNoticePrinted = true;
7829
- 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.`);
7830
7828
  }
7831
7829
  function resetLocalSkillsModeNotice() {
7832
7830
  localNoticePrinted = false;
7833
7831
  }
7834
7832
  function resolveSkillsFleet(env = process.env, options = {}) {
7835
7833
  try {
7836
- return resolveSkillsFleetOrThrow(env, options);
7834
+ const snapshot = snapshotSkillsEnvironment(env);
7835
+ const resolved = resolveSkillsFleetOrThrow(snapshot, snapshotSkillsOptions(env, options));
7836
+ if (resolved.mode === "local" && env === process.env)
7837
+ noticeLocalSkillsMode();
7838
+ return resolved;
7837
7839
  } catch (error2) {
7838
7840
  const translated = asSkillsFleetCredentialError(error2);
7839
7841
  if (translated)
@@ -7841,38 +7843,49 @@ function resolveSkillsFleet(env = process.env, options = {}) {
7841
7843
  throw error2;
7842
7844
  }
7843
7845
  }
7844
- function resolveSkillsFleetOrThrow(env, options) {
7845
- let resolution;
7846
- try {
7847
- resolution = resolveClientTransport(SKILLS_APP, env, { credentials: options.credentials });
7848
- } catch (error2) {
7849
- if (!isClientTransportConfigurationError(error2))
7850
- throw error2;
7851
- const configured2 = configuredSkillsApiUrl(env, options.credentials?.keychain);
7852
- const credential2 = resolveCredential(SKILLS_APP, env, options.credentials);
7853
- if (!configured2 && !credential2) {
7854
- if (env === process.env)
7855
- noticeLocalSkillsMode();
7856
- return { mode: "local", apiOrigin: null, apiKey: null };
7857
- }
7858
- if (configured2 && !credential2) {
7859
- throw new SkillsFleetCredentialError(`${configured2.source} points this CLI at a Skills service but no API key resolved \u2014 ` + `refusing to run locally instead. Looked in the Keychain item ` + `hasna.credentials.${SKILLS_APP}.api-key, then ${skillsCredentialFiles(env).join(" or ") || "no credentials file (no HOME)"}, ` + `then ${SKILLS_API_KEY_ENV}. Sign in with: skills auth login`);
7846
+ function snapshotSkillsEnvironment(env) {
7847
+ const snapshot = {};
7848
+ for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(env))) {
7849
+ if (!("value" in descriptor)) {
7850
+ if (/^(?:HASNA_|SKILLS_|HOME$|USER$)/.test(key))
7851
+ throw new SkillsFleetCredentialError("Accessor-backed Skills configuration is unsupported");
7852
+ continue;
7860
7853
  }
7861
- throw error2;
7854
+ snapshot[key] = descriptor.value;
7862
7855
  }
7863
- const configured = configuredSkillsApiUrl(env, options.credentials?.keychain);
7864
- const apiOrigin = configured ? normalizeSkillsApiOrigin(configured.value) : stripV1(resolution.baseUrl);
7856
+ return Object.freeze(snapshot);
7857
+ }
7858
+ function snapshotSkillsOptions(env, options) {
7859
+ if (env !== process.env)
7860
+ return options;
7861
+ return { ...options, credentials: { ...options.credentials, keychain: {
7862
+ ...options.credentials?.keychain,
7863
+ enabled: options.credentials?.keychain?.enabled ?? true
7864
+ } } };
7865
+ }
7866
+ function resolveSkillsFleetOrThrow(env, options) {
7867
+ if (selectsSkillsLocalMode(env))
7868
+ return { mode: "local", apiOrigin: null, apiKey: null };
7869
+ const assertFilesUnchanged = captureSkillsCredentialFiles(skillsProfileCredentialFiles(env, options.credentials?.profile));
7870
+ const configured = configuredSkillsApiUrl(env, options.credentials?.keychain, options.credentials?.profile);
7865
7871
  const credential = resolveCredential(SKILLS_APP, env, options.credentials);
7866
7872
  if (!credential) {
7867
- throw new SkillsFleetCredentialError(`A Skills authority resolved but no API key did. 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`);
7868
7877
  }
7878
+ const apiOrigin = normalizeSkillsApiOrigin(configured?.value ?? defaultFleetGatewayBaseUrl(SKILLS_APP));
7879
+ toV1BaseUrl(apiOrigin);
7880
+ assertCredentialInstance(credential, apiOrigin, env, options);
7881
+ assertFilesUnchanged();
7869
7882
  const base = {
7870
7883
  mode: "hosted",
7871
7884
  apiOrigin,
7872
- apiUrlSource: resolution.apiUrlSource ?? (configured?.source ?? "default"),
7873
- apiKeySource: resolution.apiKeySource ?? credential.source,
7874
- apiKeyTier: resolution.apiKeyTier,
7875
- warning: resolution.warning
7885
+ apiUrlSource: configured?.source ?? "default",
7886
+ apiKeySource: credential.source,
7887
+ apiKeyTier: credential.tier,
7888
+ warning: credential.warning
7876
7889
  };
7877
7890
  if (credential.tier === "pointer") {
7878
7891
  return { ...base, apiKey: null, apiKeyPointer: credential };
@@ -7882,19 +7895,41 @@ function resolveSkillsFleetOrThrow(env, options) {
7882
7895
  }
7883
7896
  return { ...base, apiKey: credential.apiKey, apiKeyPointer: null };
7884
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
+ }
7902
+ function assertCredentialInstance(credential, apiOrigin, env, options) {
7903
+ let bound;
7904
+ if (credential.tier === "disk" || credential.tier === "profile") {
7905
+ const metadata = readSkillsInstanceMetadata(credential.source);
7906
+ bound = metadata.binding ?? metadata.apiUrl ?? defaultFleetGatewayBaseUrl(SKILLS_APP);
7907
+ } else if (credential.tier === "keychain") {
7908
+ bound = keychainConfigValue(SKILLS_APP, env, options.credentials?.keychain)?.value ?? defaultFleetGatewayBaseUrl(SKILLS_APP);
7909
+ }
7910
+ if (bound && normalizeSkillsApiOrigin(bound) !== apiOrigin) {
7911
+ throw new SkillsFleetCredentialError("The selected Skills API does not match this credential's instance. Select its profile or sign in to the new instance; no credential was sent.", "INSTANCE_CREDENTIAL_MISMATCH");
7912
+ }
7913
+ }
7885
7914
  async function resolveSkillsApiKey(env = process.env, options = {}) {
7886
- const fleet = resolveSkillsFleet(env, options);
7915
+ return (await resolveSkillsConnection(env, options))?.apiKey ?? null;
7916
+ }
7917
+ async function resolveSkillsConnection(env = process.env, options = {}) {
7918
+ const snapshotEnv = snapshotSkillsEnvironment(env);
7919
+ const fleet = resolveSkillsFleet(snapshotEnv, snapshotSkillsOptions(env, options));
7920
+ if (fleet.mode === "local" && env === process.env)
7921
+ noticeLocalSkillsMode();
7887
7922
  if (fleet.mode !== "hosted")
7888
7923
  return null;
7889
7924
  if (fleet.apiKey)
7890
- return fleet.apiKey;
7925
+ return { ...fleet, apiKey: fleet.apiKey };
7891
7926
  const pointer = fleet.apiKeyPointer;
7892
7927
  if (!pointer) {
7893
7928
  throw new SkillsFleetCredentialError(`A Skills authority resolved but no API key did. Sign in with: skills auth login`);
7894
7929
  }
7895
7930
  let completed;
7896
7931
  try {
7897
- completed = await completePointerCredential(SKILLS_APP, pointer, env);
7932
+ completed = await completePointerCredential(SKILLS_APP, pointer, snapshotEnv);
7898
7933
  } catch (error2) {
7899
7934
  const translated = asSkillsFleetCredentialError(error2);
7900
7935
  if (translated)
@@ -7904,7 +7939,7 @@ async function resolveSkillsApiKey(env = process.env, options = {}) {
7904
7939
  if (!completed.apiKey?.trim()) {
7905
7940
  throw new SkillsFleetCredentialError(`${credentialPointerEnvKey(SKILLS_APP)} names a vault item that produced an empty Skills API key \u2014 ` + `refusing to send an unauthenticated request.`);
7906
7941
  }
7907
- return completed.apiKey;
7942
+ return { ...fleet, apiKey: completed.apiKey, apiKeyPointer: null };
7908
7943
  }
7909
7944
  async function requireSkillsApiKey(action = "This command", env = process.env, options = {}) {
7910
7945
  const apiKey = await resolveSkillsApiKey(env, options);
@@ -7912,27 +7947,31 @@ async function requireSkillsApiKey(action = "This command", env = process.env, o
7912
7947
  throw new MissingSkillsFleetError(action);
7913
7948
  return apiKey;
7914
7949
  }
7915
- function stripV1(baseUrl) {
7916
- return baseUrl.replace(/\/v1$/, "").replace(/\/+$/, "");
7917
- }
7918
7950
  async function skillsCredentialOrReason(env = process.env, options = {}) {
7919
7951
  try {
7920
- const apiKey = await resolveSkillsApiKey(env, options);
7921
- return apiKey ? { apiKey, reason: null } : { apiKey: null, reason: null };
7952
+ const connection = await resolveSkillsConnection(env, options);
7953
+ return connection ? { apiKey: connection.apiKey, apiOrigin: connection.apiOrigin, reason: null } : { apiKey: null, apiOrigin: null, reason: null };
7922
7954
  } catch (error2) {
7923
- if (error2 instanceof SkillsFleetCredentialError || error2?.name === "SkillsFleetCredentialError") {
7924
- return { apiKey: null, reason: error2.message };
7955
+ if (isSkillsFleetCredentialError(error2)) {
7956
+ return { apiKey: null, apiOrigin: null, reason: error2.message };
7925
7957
  }
7926
7958
  throw error2;
7927
7959
  }
7928
7960
  }
7929
7961
  function resolveSkillsApiOrigin(env = process.env, options = {}) {
7930
- const configured = configuredSkillsApiUrl(env, options.credentials?.keychain);
7962
+ const configured = configuredSkillsApiUrl(env, options.credentials?.keychain, options.credentials?.profile);
7931
7963
  if (configured) {
7932
7964
  toV1BaseUrl(configured.value);
7933
7965
  return { origin: normalizeSkillsApiOrigin(configured.value), source: configured.source };
7934
7966
  }
7935
- 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
+ }
7936
7975
  return fleet.mode === "hosted" ? { origin: fleet.apiOrigin, source: fleet.apiUrlSource } : null;
7937
7976
  }
7938
7977
  function requireSkillsApiOrigin(action = "This command", env = process.env, options = {}) {
@@ -7950,6 +7989,9 @@ function requireSkillsFleet(action = "This command", env = process.env, options
7950
7989
  var SKILLS_APP = "skills", ENV_KEYS, SKILLS_API_URL_ENV_KEYS, SKILLS_API_KEY_ENV_KEYS, SKILLS_API_URL_ENV, SKILLS_API_KEY_ENV, SkillsFleetCredentialError, localNoticePrinted = false, MissingSkillsFleetError;
7951
7990
  var init_fleet_credentials = __esm(() => {
7952
7991
  init_transport();
7992
+ init_instance_credentials();
7993
+ init_local_opt_in();
7994
+ init_local_opt_in();
7953
7995
  ENV_KEYS = clientTransportEnvKeys(SKILLS_APP);
7954
7996
  SKILLS_API_URL_ENV_KEYS = ENV_KEYS.apiUrlKeys;
7955
7997
  SKILLS_API_KEY_ENV_KEYS = ENV_KEYS.apiKeyKeys;
@@ -7972,6 +8014,73 @@ var init_fleet_credentials = __esm(() => {
7972
8014
  };
7973
8015
  });
7974
8016
 
8017
+ // src/lib/auth-store.ts
8018
+ import { chmodSync, existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync11, renameSync as renameSync2, statSync as statSync7, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
8019
+ function getAuthFilePath(env = process.env) {
8020
+ return skillsCredentialFilePath(env);
8021
+ }
8022
+ function getApiUrl(action, env = process.env, options = {}) {
8023
+ return requireSkillsApiOrigin(action, env, options);
8024
+ }
8025
+ function credentialFileMode(env = process.env) {
8026
+ try {
8027
+ return statSync7(skillsCredentialFilePath(env)).mode & 511;
8028
+ } catch {
8029
+ return null;
8030
+ }
8031
+ }
8032
+ var init_auth_store = __esm(() => {
8033
+ init_transport();
8034
+ init_instance_credentials();
8035
+ init_fleet_credentials();
8036
+ init_transport();
8037
+ init_fleet_credentials();
8038
+ });
8039
+
8040
+ // src/lib/remote-run-contract.ts
8041
+ function normalizeRemoteSkillRunContract(payload, fallbackSkill) {
8042
+ const record3 = isRecord3(payload) ? payload : {};
8043
+ return {
8044
+ contractVersion: REMOTE_SKILL_RUN_CONTRACT_VERSION,
8045
+ ...pickString(record3, "id"),
8046
+ skill: pickStringValue(record3, "skill") ?? fallbackSkill,
8047
+ ...pickString(record3, "requestedSlug"),
8048
+ ...pickString(record3, "status"),
8049
+ ...pickNumber(record3, "exitCode"),
8050
+ ...pickString(record3, "correlationId"),
8051
+ ...pickString(record3, "createdAt"),
8052
+ ...pickString(record3, "startedAt"),
8053
+ ...pickString(record3, "completedAt"),
8054
+ ...pickNumber(record3, "durationMs"),
8055
+ ...pickString(record3, "outputType"),
8056
+ ...hasOwn(record3, "outputPreview") ? { outputPreview: record3.outputPreview } : {},
8057
+ ...pickString(record3, "errorCode"),
8058
+ ...pickString(record3, "errorMessage"),
8059
+ ...pickString(record3, "error"),
8060
+ ...pickString(record3, "code"),
8061
+ ...hasOwn(record3, "details") ? { details: record3.details } : {}
8062
+ };
8063
+ }
8064
+ function isRecord3(value) {
8065
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
8066
+ }
8067
+ function hasOwn(record3, key) {
8068
+ return Object.prototype.hasOwnProperty.call(record3, key);
8069
+ }
8070
+ function pickString(record3, key) {
8071
+ const value = pickStringValue(record3, key);
8072
+ return value === undefined ? {} : { [key]: value };
8073
+ }
8074
+ function pickStringValue(record3, key) {
8075
+ const value = record3[key];
8076
+ return typeof value === "string" ? value : undefined;
8077
+ }
8078
+ function pickNumber(record3, key) {
8079
+ const value = record3[key];
8080
+ return typeof value === "number" && Number.isFinite(value) ? { [key]: value } : {};
8081
+ }
8082
+ var REMOTE_SKILL_RUN_CONTRACT_VERSION = 1;
8083
+
7975
8084
  // src/lib/blog-article.ts
7976
8085
  var exports_blog_article = {};
7977
8086
  __export(exports_blog_article, {
@@ -8102,35 +8211,325 @@ var init_blog_article = __esm(() => {
8102
8211
  ARTICLE_LENGTHS = ["short", "medium", "long"];
8103
8212
  });
8104
8213
 
8105
- // src/lib/auth-store.ts
8106
- function getApiUrl(action, env = process.env, options = {}) {
8107
- return requireSkillsApiOrigin(action, env, options);
8214
+ // src/lib/remote-files.ts
8215
+ var exports_remote_files = {};
8216
+ __export(exports_remote_files, {
8217
+ sha256: () => sha256,
8218
+ readBoundedResponse: () => readBoundedResponse,
8219
+ describeRemoteFiles: () => describeRemoteFiles,
8220
+ decodeRemoteFiles: () => decodeRemoteFiles,
8221
+ MAX_REMOTE_FILE_BYTES: () => MAX_REMOTE_FILE_BYTES
8222
+ });
8223
+ import { createHash as createHash3 } from "crypto";
8224
+ function describeRemoteFiles(files) {
8225
+ if (files.length > 10)
8226
+ throw new Error("At most 10 input files are supported");
8227
+ const names = new Set;
8228
+ let total = 0;
8229
+ return files.map((file) => {
8230
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/.test(file.name) || file.name === "." || file.name === ".." || names.has(file.name))
8231
+ throw new Error("Input file names must be unique safe basenames");
8232
+ names.add(file.name);
8233
+ total += file.bytes.byteLength;
8234
+ if (file.bytes.byteLength > 20 * 1024 * 1024 || total > 50 * 1024 * 1024)
8235
+ throw new Error("Input files exceed the supported size limit");
8236
+ return { name: file.name, sizeBytes: file.bytes.byteLength, sha256: sha256(file.bytes), contentType: file.contentType ?? "application/octet-stream" };
8237
+ });
8108
8238
  }
8109
- var init_auth_store = __esm(() => {
8110
- init_fleet_credentials();
8111
- init_fleet_credentials();
8239
+ function sha256(bytes) {
8240
+ return createHash3("sha256").update(bytes).digest("hex");
8241
+ }
8242
+ async function readBoundedResponse(response, maximum) {
8243
+ if (!Number.isSafeInteger(maximum) || maximum < 0 || maximum > MAX_REMOTE_FILE_BYTES)
8244
+ throw new Error("Invalid artifact size limit");
8245
+ const length = response.headers.get("content-length");
8246
+ if (length && (!/^\d+$/.test(length) || Number(length) > maximum)) {
8247
+ await response.body?.cancel();
8248
+ throw new Error("Artifact exceeds its declared size limit");
8249
+ }
8250
+ const reader = response.body?.getReader();
8251
+ if (!reader)
8252
+ return new Uint8Array;
8253
+ const chunks = [];
8254
+ let size = 0;
8255
+ try {
8256
+ while (true) {
8257
+ const next = await reader.read();
8258
+ if (next.done)
8259
+ break;
8260
+ size += next.value.byteLength;
8261
+ if (size > maximum)
8262
+ throw new Error("Artifact exceeds its declared size limit");
8263
+ chunks.push(next.value);
8264
+ }
8265
+ } catch (error2) {
8266
+ await reader.cancel().catch(() => {});
8267
+ throw error2;
8268
+ } finally {
8269
+ reader.releaseLock();
8270
+ }
8271
+ const bytes = new Uint8Array(size);
8272
+ let offset = 0;
8273
+ for (const chunk of chunks) {
8274
+ bytes.set(chunk, offset);
8275
+ offset += chunk.byteLength;
8276
+ }
8277
+ return bytes;
8278
+ }
8279
+ function decodeRemoteFiles(files) {
8280
+ let total = 0;
8281
+ const decoded = files.map((file) => {
8282
+ if (file.base64.length > 1398104 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(file.base64))
8283
+ throw new Error("Invalid inline input encoding");
8284
+ const bytes = Buffer.from(file.base64, "base64");
8285
+ total += bytes.byteLength;
8286
+ if (total > 1024 * 1024 || bytes.toString("base64") !== file.base64)
8287
+ throw new Error("Inline inputs must total at most 1 MiB");
8288
+ return { name: file.name, bytes, contentType: file.contentType };
8289
+ });
8290
+ describeRemoteFiles(decoded);
8291
+ return decoded;
8292
+ }
8293
+ var MAX_REMOTE_FILE_BYTES;
8294
+ var init_remote_files = __esm(() => {
8295
+ MAX_REMOTE_FILE_BYTES = 64 * 1024 * 1024;
8296
+ });
8297
+
8298
+ // src/lib/remote-workspace.ts
8299
+ function workspaceMembersQuery(options = {}) {
8300
+ if (!record3(options) || Object.keys(options).some((key) => key !== "limit" && key !== "cursor") || options.limit !== undefined && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 100) || options.cursor !== undefined && !cursor(options.cursor))
8301
+ throw new Error("Use a roster limit from 1 to 100 and an unchanged continuation cursor.");
8302
+ const query = new URLSearchParams;
8303
+ if (options.limit !== undefined)
8304
+ query.set("limit", String(options.limit));
8305
+ if (options.cursor !== undefined)
8306
+ query.set("cursor", options.cursor);
8307
+ return query.size ? `?${query}` : "";
8308
+ }
8309
+ function timestamp(value) {
8310
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$/.test(value))
8311
+ return false;
8312
+ const time3 = Date.parse(value);
8313
+ return Number.isFinite(time3) && new Date(time3).toISOString().slice(0, 23) === value.slice(0, 23);
8314
+ }
8315
+ function parseMember(row, fail) {
8316
+ if (!record3(row) || !uuid2(row.membershipId) || !uuid2(row.userId) || typeof row.email !== "string" || !row.email || !(row.displayName === null || typeof row.displayName === "string") || !isRole(row.role) || !timestamp(row.createdAt))
8317
+ return fail();
8318
+ return {
8319
+ membershipId: row.membershipId,
8320
+ userId: row.userId,
8321
+ email: row.email,
8322
+ displayName: row.displayName,
8323
+ role: row.role,
8324
+ createdAt: row.createdAt
8325
+ };
8326
+ }
8327
+ function mutationInput(membershipId, input, roleChange) {
8328
+ if (typeof membershipId !== "string" || !uuid2(membershipId) || membershipId !== membershipId.toLowerCase() || !record3(input) || Object.keys(input).sort().join(",") !== (roleChange ? "expectedRole,role" : "expectedRole"))
8329
+ throw new WorkspaceMemberInputError;
8330
+ const expectedRole = input.expectedRole, role = roleChange ? input.role : undefined;
8331
+ if (!isRole(expectedRole) || roleChange && !isRole(role))
8332
+ throw new WorkspaceMemberInputError;
8333
+ return { membershipId, role, expectedRole };
8334
+ }
8335
+ function workspaceMemberRoleInput(membershipId, input) {
8336
+ const value = mutationInput(membershipId, input, true);
8337
+ return { membershipId: value.membershipId, body: { role: value.role, expectedRole: value.expectedRole } };
8338
+ }
8339
+ function workspaceMemberRemovalInput(membershipId, input) {
8340
+ const value = mutationInput(membershipId, input, false);
8341
+ return { membershipId: value.membershipId, body: { expectedRole: value.expectedRole } };
8342
+ }
8343
+ function parseWorkspaceMemberRoleResult(value, membershipId, role) {
8344
+ const fail = () => {
8345
+ throw new Error(invalidMemberResult);
8346
+ };
8347
+ if (!record3(value) || !uuid2(value.organizationId) || typeof value.changed !== "boolean")
8348
+ return fail();
8349
+ const member = parseMember(value.member, fail);
8350
+ if (member.membershipId !== membershipId || member.role !== role)
8351
+ return fail();
8352
+ return { organizationId: value.organizationId, member, changed: value.changed };
8353
+ }
8354
+ function parseWorkspaceMemberRemovalResult(value, membershipId) {
8355
+ if (!record3(value) || !uuid2(value.organizationId) || value.membershipId !== membershipId || value.removed !== true || typeof value.alreadyRemoved !== "boolean")
8356
+ throw new Error(invalidMemberResult);
8357
+ return { organizationId: value.organizationId, membershipId, removed: true, alreadyRemoved: value.alreadyRemoved };
8358
+ }
8359
+ function workspaceMemberFailure(value, status) {
8360
+ if (!record3(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceMemberFailures, value.code))
8361
+ return null;
8362
+ const code = value.code;
8363
+ return workspaceMemberFailures[code][0] === status ? code : null;
8364
+ }
8365
+ function parseWorkspaceMembersPage(value) {
8366
+ const fail = () => {
8367
+ throw new Error("The server returned an invalid workspace roster.");
8368
+ };
8369
+ if (!record3(value) || !uuid2(value.organizationId) || !Array.isArray(value.members) || value.members.length > 100 || !(value.nextCursor === null || cursor(value.nextCursor)) || !value.members.length && value.nextCursor !== null)
8370
+ return fail();
8371
+ const members = value.members.map((row) => parseMember(row, fail));
8372
+ if (new Set(members.map((row) => row.membershipId)).size !== members.length)
8373
+ return fail();
8374
+ return { organizationId: value.organizationId, members, nextCursor: value.nextCursor };
8375
+ }
8376
+ var record3 = (value) => !!value && typeof value === "object" && !Array.isArray(value), cursor = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,512}$/.test(value), uuid2 = (value) => typeof value === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(value), isRole = (value) => typeof value === "string" && ["owner", "admin", "member", "viewer"].includes(value), WorkspaceMemberInputError, invalidMemberResult = "The server returned an invalid workspace member result. Refresh the roster before another action.", workspaceMemberFailures;
8377
+ var init_remote_workspace = __esm(() => {
8378
+ WorkspaceMemberInputError = class WorkspaceMemberInputError extends Error {
8379
+ constructor() {
8380
+ super("Use an unchanged lowercase membership ID and the exact role and expected-role parameters from the roster.");
8381
+ this.name = "WorkspaceMemberInputError";
8382
+ }
8383
+ };
8384
+ workspaceMemberFailures = {
8385
+ INVALID_REQUEST: [400, "Provide the exact membership role parameters."],
8386
+ ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
8387
+ INTERACTIVE_SESSION_REQUIRED: [403, "Fresh interactive sign-in is required."],
8388
+ WORKSPACE_ADMIN_REQUIRED: [403, "A current workspace owner or admin is required."],
8389
+ MEMBERSHIP_ACTION_FORBIDDEN: [403, "Your current workspace role cannot perform this membership action."],
8390
+ MEMBERSHIP_NOT_FOUND: [404, "Membership was not found in the current workspace."],
8391
+ SELF_REMOVAL_UNAVAILABLE: [409, "Leaving your own workspace is not available through member removal."],
8392
+ MEMBERSHIP_ROLE_CHANGED: [409, "The member role changed. Refresh the roster before another action."],
8393
+ LAST_OWNER_REQUIRED: [409, "The workspace must retain at least one active owner."],
8394
+ MEMBERSHIP_BUSY: [503, "Membership is busy. Refresh the roster before another action."]
8395
+ };
8396
+ });
8397
+
8398
+ // src/lib/remote-account.ts
8399
+ function creditCount(value) {
8400
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0 || value > 2147483647) {
8401
+ throw new Error("The Skills server returned an invalid credit count");
8402
+ }
8403
+ return value;
8404
+ }
8405
+ function parseRemoteRunQuote(value) {
8406
+ const quote = object4(value);
8407
+ const pricing = object4(quote.pricing);
8408
+ if (typeof quote.skill !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(quote.skill))
8409
+ throw new Error("Invalid quoted skill");
8410
+ const costCents = creditCount(pricing.costCredits ?? pricing.costCents);
8411
+ if (pricing.costCredits !== undefined && pricing.costCents !== undefined && pricing.costCents !== costCents)
8412
+ throw new Error("Inconsistent quoted credit count");
8413
+ if (quote.availability && object4(quote.availability).status !== "available")
8414
+ throw new Error("This skill is unavailable for remote execution");
8415
+ return { ...quote, skill: quote.skill, pricing: { ...pricing, costCredits: costCents, costCents, formattedCost: `${costCents} credits` } };
8416
+ }
8417
+ function parseRemoteCreditPacks(value) {
8418
+ if (!Array.isArray(value))
8419
+ throw new Error("Invalid credit pack response");
8420
+ const ids = new Set;
8421
+ return value.map((value2) => {
8422
+ const row = object4(value2);
8423
+ const counts = [row.credits, row.creditsCents, row.amountCents].filter((value3) => value3 !== undefined).map(creditCount);
8424
+ if (!counts.length || counts[0] === 0 || counts.some((count) => count !== counts[0]))
8425
+ throw new Error("Inconsistent credit pack counts");
8426
+ const credits = counts[0];
8427
+ const id = row.id ?? `credits_${credits}`;
8428
+ if (typeof id !== "string" || !/^[a-z0-9][a-z0-9_-]{0,99}$/.test(id) || ids.has(id))
8429
+ throw new Error("Invalid credit pack ID");
8430
+ if (id.startsWith("credits_") && id !== `credits_${credits}`)
8431
+ throw new Error("Inconsistent credit pack ID");
8432
+ ids.add(id);
8433
+ return { id, credits, ...row.expiresInDays === undefined ? {} : { expiresInDays: creditCount(row.expiresInDays) } };
8434
+ });
8435
+ }
8436
+ function parseRemoteBillingStatus(value) {
8437
+ const row = object4(value);
8438
+ const counts = [row.creditBalance, row.balanceCents].filter((value2) => value2 !== undefined).map(creditCount);
8439
+ if (!counts.length || counts.some((count) => count !== counts[0]))
8440
+ throw new Error("Inconsistent credit balance");
8441
+ return {
8442
+ creditBalance: counts[0],
8443
+ formattedCreditBalance: `${counts[0]} credits`,
8444
+ ...typeof row.plan === "string" ? { plan: row.plan } : {},
8445
+ ...typeof row.hasPaymentMethod === "boolean" ? { hasPaymentMethod: row.hasPaymentMethod } : {}
8446
+ };
8447
+ }
8448
+ function parseRemoteCheckout(value) {
8449
+ const row = object4(value);
8450
+ if (typeof row.url !== "string")
8451
+ throw new Error("Invalid checkout URL");
8452
+ const url = new URL(row.url);
8453
+ if (url.protocol !== "https:" || url.username || url.password)
8454
+ throw new Error("Invalid checkout URL");
8455
+ return { url: row.url };
8456
+ }
8457
+ function object4(value) {
8458
+ if (!value || typeof value !== "object" || Array.isArray(value))
8459
+ throw new Error("Invalid Skills server response");
8460
+ return value;
8461
+ }
8462
+ var RemoteCreditApprovalError;
8463
+ var init_remote_account = __esm(() => {
8464
+ RemoteCreditApprovalError = class RemoteCreditApprovalError extends Error {
8465
+ requiredCredits;
8466
+ maximumCredits;
8467
+ code = "CREDIT_APPROVAL_REQUIRED";
8468
+ constructor(requiredCredits, maximumCredits) {
8469
+ super(`This run requires ${requiredCredits} credits; the approved maximum is ${maximumCredits}. Quote the run and explicitly approve its cost.`);
8470
+ this.requiredCredits = requiredCredits;
8471
+ this.maximumCredits = maximumCredits;
8472
+ this.name = "RemoteCreditApprovalError";
8473
+ }
8474
+ };
8112
8475
  });
8113
8476
 
8477
+ // src/lib/remote-profile.ts
8478
+ function customerNamePatch(input, field) {
8479
+ if (!isRecord4(input) || Object.keys(input).length !== 1 || !Object.hasOwn(input, field))
8480
+ throw new Error("Provide only the requested name field.");
8481
+ const value = input[field];
8482
+ if (typeof value !== "string" || /[\p{Cc}\p{Cs}\u2028\u2029]/u.test(value) || !value.trim() || [...value.trim()].length > 100) {
8483
+ throw new Error("Use a name of 1\u2013100 characters without control characters or newlines.");
8484
+ }
8485
+ return { [field]: value.trim() };
8486
+ }
8487
+ function isRecord4(value) {
8488
+ return !!value && typeof value === "object" && !Array.isArray(value);
8489
+ }
8490
+ function string4(value) {
8491
+ return typeof value === "string" && value.length > 0;
8492
+ }
8493
+ function parseUpdatedProfile(value) {
8494
+ const user = isRecord4(value) && value.user;
8495
+ if (!isRecord4(user) || !string4(user.id) || !string4(user.email) || !(user.displayName === null || typeof user.displayName === "string") || typeof user.role !== "string" || !["owner", "admin", "member", "viewer"].includes(user.role)) {
8496
+ throw new Error("The server returned an invalid account profile.");
8497
+ }
8498
+ return { user: { id: user.id, email: user.email, displayName: user.displayName, role: user.role } };
8499
+ }
8500
+ function parseUpdatedWorkspace(value) {
8501
+ const organization = isRecord4(value) && value.organization;
8502
+ if (!isRecord4(organization) || !string4(organization.id) || !string4(organization.slug) || !string4(organization.name)) {
8503
+ throw new Error("The server returned an invalid workspace.");
8504
+ }
8505
+ return { organization: { id: organization.id, slug: organization.slug, name: organization.name } };
8506
+ }
8507
+
8114
8508
  // src/lib/remote-client.ts
8115
8509
  var exports_remote_client = {};
8116
8510
  __export(exports_remote_client, {
8117
8511
  createRemoteSkillsClientReadOnly: () => createRemoteSkillsClientReadOnly,
8118
8512
  createRemoteSkillsClient: () => createRemoteSkillsClient,
8513
+ RemoteWorkspaceMemberError: () => RemoteWorkspaceMemberError,
8119
8514
  RemoteSkillsClient: () => RemoteSkillsClient,
8120
8515
  RemoteRouteUnsupportedError: () => RemoteRouteUnsupportedError,
8121
- RemoteRequestError: () => RemoteRequestError
8516
+ RemoteRequestError: () => RemoteRequestError,
8517
+ RemoteCapabilityUnavailableError: () => RemoteCapabilityUnavailableError
8122
8518
  });
8123
8519
 
8124
8520
  class RemoteSkillsClient {
8125
8521
  apiUrl;
8126
8522
  apiKey;
8523
+ capabilities;
8127
8524
  constructor(apiKey, apiUrl = getApiUrl()) {
8128
8525
  this.apiKey = apiKey;
8129
- this.apiUrl = apiUrl.replace(/\/$/, "");
8526
+ this.apiUrl = normalizeSkillsApiOrigin(apiUrl);
8130
8527
  }
8131
8528
  async request(path, options) {
8132
8529
  return fetch(`${this.apiUrl}${path}`, {
8133
8530
  ...options,
8531
+ redirect: "error",
8532
+ signal: options?.signal ?? AbortSignal.timeout(15000),
8134
8533
  headers: {
8135
8534
  Authorization: `Bearer ${this.apiKey}`,
8136
8535
  "Content-Type": "application/json",
@@ -8145,16 +8544,20 @@ class RemoteSkillsClient {
8145
8544
  if (response.status === 404 && opts.domainNotFoundCodes?.length && await responseBodyCarriesCode(response, opts.domainNotFoundCodes)) {
8146
8545
  return response;
8147
8546
  }
8547
+ response.body?.cancel().catch(() => {});
8148
8548
  throw new RemoteRouteUnsupportedError(routePath, response.status, this.apiUrl);
8149
8549
  }
8150
8550
  if (!response.ok) {
8551
+ if (path === "/api/v1/billing/checkout" && options?.method === "POST" && response.status === 503 && await responseBodyCarriesCode(response, ["SUBSCRIPTION_CHECKOUT_UNAVAILABLE"])) {
8552
+ throw new RemoteCapabilityUnavailableError;
8553
+ }
8554
+ response.body?.cancel().catch(() => {});
8151
8555
  throw new RemoteRequestError(routePath, response.status, response.statusText);
8152
8556
  }
8153
8557
  return response;
8154
8558
  }
8155
8559
  async listSkills() {
8156
- const res = await this.request("/api/v1/skills");
8157
- return res.json();
8560
+ return this.arrayResponse("/api/v1/skills");
8158
8561
  }
8159
8562
  async getSkillMd(slug) {
8160
8563
  const res = await this.request(`/api/v1/skills/${slug}/skill.md`);
@@ -8176,38 +8579,268 @@ class RemoteSkillsClient {
8176
8579
  } catch {}
8177
8580
  return { status: res.status, body };
8178
8581
  }
8179
- async submitRun(slug, input, args) {
8180
- const res = await this.request(`/api/v1/runs/${slug}`, {
8582
+ async submitRun(slug, input, args, approval = {}) {
8583
+ if (approval.idempotencyKey !== undefined && !/^[A-Za-z0-9._:-]{1,128}$/.test(approval.idempotencyKey))
8584
+ throw new Error("Idempotency key must be 1-128 URL-safe characters");
8585
+ if (approval.maxCostCents !== undefined)
8586
+ creditCount(approval.maxCostCents);
8587
+ if (approval.maxCredits !== undefined)
8588
+ creditCount(approval.maxCredits);
8589
+ if (approval.maxCredits !== undefined && approval.maxCostCents !== undefined && approval.maxCredits !== approval.maxCostCents)
8590
+ throw new Error("Credit approval fields disagree");
8591
+ const res = await this.request(`/api/v1/runs/${encodeURIComponent(slug)}`, {
8181
8592
  method: "POST",
8182
- body: JSON.stringify({ input, args })
8593
+ body: JSON.stringify({
8594
+ input,
8595
+ args,
8596
+ ...approval.maxCredits !== undefined ? { maxCredits: approval.maxCredits } : {},
8597
+ ...approval.maxCostCents !== undefined ? { maxCostCents: approval.maxCostCents } : {},
8598
+ ...approval.idempotencyKey !== undefined ? { idempotencyKey: approval.idempotencyKey } : {},
8599
+ ...approval.inputFiles !== undefined ? { files: approval.inputFiles } : {}
8600
+ })
8183
8601
  });
8184
8602
  return normalizeRemoteSkillRunContract(await res.json(), slug);
8185
8603
  }
8604
+ async quoteRun(slug, input = {}, args = []) {
8605
+ const response = await this.requestNewRoute(`/api/v1/skills/${encodeURIComponent(slug)}/quote`, {
8606
+ method: "POST",
8607
+ body: JSON.stringify({ input, args })
8608
+ });
8609
+ return parseRemoteRunQuote(await response.json());
8610
+ }
8611
+ getCapabilities() {
8612
+ if (!this.capabilities)
8613
+ this.capabilities = (async () => {
8614
+ const value = await (await this.requestNewRoute("/api/v1/capabilities")).json();
8615
+ if (value.contractVersion !== 1 || value.apiVersion !== 1 || !Array.isArray(value.capabilities) || value.capabilities.some((item) => typeof item !== "string"))
8616
+ throw new Error("Unsupported Skills server capability contract");
8617
+ const billing = value.billing;
8618
+ return { contractVersion: 1, apiVersion: 1, capabilities: value.capabilities, ...billing ? { billing } : {} };
8619
+ })();
8620
+ return this.capabilities;
8621
+ }
8622
+ async submitQuotedRun(slug, input = {}, args = [], approval = {}) {
8623
+ const maximum = creditCount(approval.maxCredits ?? approval.maxCostCents ?? 0);
8624
+ if (approval.maxCostCents !== undefined && approval.maxCostCents !== maximum)
8625
+ throw new Error("Credit approval fields disagree");
8626
+ const quote = await this.quoteRun(slug, input, args);
8627
+ if (quote.pricing.costCents > maximum)
8628
+ throw new RemoteCreditApprovalError(quote.pricing.costCents, maximum);
8629
+ const capabilities = await this.getCapabilities();
8630
+ if (!capabilities.capabilities.includes("runs.submit") || capabilities.billing?.boundedRunApproval !== true || capabilities.billing.unit !== "credits") {
8631
+ throw new Error("The configured server does not support bounded credit approval; refusing remote submission");
8632
+ }
8633
+ return this.submitRun(quote.skill, input, args, { ...approval, maxCredits: maximum, maxCostCents: maximum });
8634
+ }
8635
+ async getIdentity() {
8636
+ return (await this.requestNewRoute("/api/auth/whoami")).json();
8637
+ }
8638
+ async updateProfile(input) {
8639
+ const body = customerNamePatch(input, "displayName");
8640
+ return parseUpdatedProfile(await (await this.requestNewRoute("/api/v1/account/profile", { method: "PATCH", body: JSON.stringify(body) })).json());
8641
+ }
8642
+ async updateCurrentWorkspace(input) {
8643
+ const body = customerNamePatch(input, "name");
8644
+ return parseUpdatedWorkspace(await (await this.requestNewRoute("/api/v1/workspaces/current", { method: "PATCH", body: JSON.stringify(body) })).json());
8645
+ }
8646
+ async listWorkspaceMembers(options = {}) {
8647
+ const query = workspaceMembersQuery(options);
8648
+ const requestedCursor = options.cursor;
8649
+ const response = await this.requestNewRoute(`/api/v1/workspace/members${query}`);
8650
+ let value;
8651
+ try {
8652
+ value = await response.json();
8653
+ } catch {
8654
+ throw new Error("The server returned an invalid workspace roster.");
8655
+ }
8656
+ const page = parseWorkspaceMembersPage(value);
8657
+ if (requestedCursor !== undefined && page.nextCursor === requestedCursor)
8658
+ throw new Error("The server returned an invalid workspace roster.");
8659
+ return page;
8660
+ }
8661
+ async setWorkspaceMemberRole(membershipId, input) {
8662
+ const captured = workspaceMemberRoleInput(membershipId, input);
8663
+ const value = await this.requestWorkspaceMember(captured.membershipId, "PATCH", captured.body);
8664
+ return parseWorkspaceMemberRoleResult(value, captured.membershipId, captured.body.role);
8665
+ }
8666
+ async removeWorkspaceMember(membershipId, input) {
8667
+ const captured = workspaceMemberRemovalInput(membershipId, input);
8668
+ return parseWorkspaceMemberRemovalResult(await this.requestWorkspaceMember(captured.membershipId, "DELETE", captured.body), captured.membershipId);
8669
+ }
8670
+ async requestWorkspaceMember(membershipId, method, body) {
8671
+ const path = `/api/v1/workspace/members/${membershipId}`;
8672
+ const response = await this.request(path, { method, body: JSON.stringify(body) });
8673
+ let value;
8674
+ try {
8675
+ value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, response.ok ? 64 * 1024 : 4096)));
8676
+ } catch {
8677
+ if (response.ok)
8678
+ throw new Error(invalidMemberResult);
8679
+ }
8680
+ if (!response.ok) {
8681
+ const code = workspaceMemberFailure(value, response.status);
8682
+ if (code)
8683
+ throw new RemoteWorkspaceMemberError(path, code);
8684
+ if (response.status === 404 || response.status === 405)
8685
+ throw new RemoteRouteUnsupportedError(path, response.status, this.apiUrl);
8686
+ throw new RemoteRequestError(path, response.status);
8687
+ }
8688
+ return value;
8689
+ }
8690
+ async listApiKeys() {
8691
+ return this.arrayResponse("/api/auth/keys");
8692
+ }
8693
+ async createApiKey(name, scopes) {
8694
+ if (!name.trim() || name.length > 100)
8695
+ throw new Error("API key name must be 1-100 characters");
8696
+ const value = await (await this.requestNewRoute("/api/auth/keys", { method: "POST", body: JSON.stringify({ name, ...scopes ? { scopes } : {} }) })).json();
8697
+ if (!value || typeof value.key !== "string" || !value.key.trim())
8698
+ throw new Error("The server did not return a created API key");
8699
+ return value;
8700
+ }
8701
+ async revokeApiKey(keyId) {
8702
+ return (await this.requestNewRoute(`/api/auth/keys/${encodeURIComponent(keyId)}`, { method: "DELETE" })).json();
8703
+ }
8704
+ async getBillingStatus() {
8705
+ return parseRemoteBillingStatus(await (await this.requestNewRoute("/api/v1/billing/status")).json());
8706
+ }
8707
+ async listCreditPacks() {
8708
+ return parseRemoteCreditPacks(await (await this.requestNewRoute("/api/v1/billing/credits")).json());
8709
+ }
8710
+ async createCreditCheckout(packId) {
8711
+ const packs = await this.listCreditPacks();
8712
+ if (!packs.some((pack) => pack.id === packId))
8713
+ throw new Error("Choose a credit pack returned by skills credits packs");
8714
+ return parseRemoteCheckout(await (await this.requestNewRoute("/api/v1/billing/credits", {
8715
+ method: "POST",
8716
+ body: JSON.stringify({ packId })
8717
+ })).json());
8718
+ }
8719
+ async getUsage() {
8720
+ return this.arrayResponse("/api/v1/billing/usage");
8721
+ }
8722
+ async listInvoices() {
8723
+ return this.arrayResponse("/api/v1/billing/invoices");
8724
+ }
8725
+ async createBillingCheckout() {
8726
+ return this.checkoutResponse("/api/v1/billing/checkout");
8727
+ }
8728
+ async createBillingPortal() {
8729
+ return this.checkoutResponse("/api/v1/billing/portal");
8730
+ }
8731
+ async cancelRun(runId) {
8732
+ return this.controlRun(runId, "cancel");
8733
+ }
8734
+ async resumeRun(runId) {
8735
+ return this.controlRun(runId, "resume");
8736
+ }
8737
+ async controlRun(runId, action) {
8738
+ const response = await this.requestNewRoute(`/api/v1/runs/${encodeURIComponent(runId)}/${action}`, { method: "POST", body: "{}" });
8739
+ return normalizeRemoteSkillRunContract(await response.json());
8740
+ }
8741
+ async checkoutResponse(path) {
8742
+ return parseRemoteCheckout(await (await this.requestNewRoute(path, { method: "POST", body: "{}" })).json());
8743
+ }
8744
+ async arrayResponse(path) {
8745
+ const rows = await (await this.requestNewRoute(path)).json();
8746
+ if (!Array.isArray(rows) || rows.some((row) => !row || typeof row !== "object" || Array.isArray(row)))
8747
+ throw new Error("Invalid Skills server list response");
8748
+ return rows;
8749
+ }
8186
8750
  async getRun(runId) {
8187
- const res = await this.request(`/api/v1/runs/${runId}`);
8188
- if (!res.ok)
8751
+ const path = `/api/v1/runs/${encodeURIComponent(runId)}`;
8752
+ const res = await this.request(path);
8753
+ if (res.status === 404)
8189
8754
  return null;
8755
+ if (!res.ok)
8756
+ throw new RemoteRequestError(path, res.status, res.statusText);
8190
8757
  return normalizeRemoteSkillRunContract(await res.json());
8191
8758
  }
8192
8759
  async getRunLogs(runId) {
8193
- const res = await this.request(`/api/v1/runs/${runId}/logs`);
8194
- if (!res.ok)
8195
- return [];
8196
- const payload = await res.json();
8197
- return Array.isArray(payload) ? payload : [];
8760
+ return this.arrayResponse(`/api/v1/runs/${encodeURIComponent(runId)}/logs`);
8198
8761
  }
8199
8762
  async listRuns(limit = 20) {
8200
- const res = await this.request(`/api/v1/runs?limit=${limit}`);
8201
- return res.json();
8763
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100)
8764
+ throw new Error("Run limit must be an integer from 1 to 100");
8765
+ return this.arrayResponse(`/api/v1/runs?limit=${limit}`);
8202
8766
  }
8203
8767
  async getRunArtifacts(runId) {
8204
- const res = await this.request(`/api/v1/runs/${runId}/artifacts`);
8205
- return res.json();
8768
+ return this.arrayResponse(`/api/v1/runs/${encodeURIComponent(runId)}/artifacts`);
8769
+ }
8770
+ async downloadRunArtifact(runId, artifactId) {
8771
+ return this.request(`/api/v1/runs/${encodeURIComponent(runId)}/artifacts/${encodeURIComponent(artifactId)}/download`, {
8772
+ method: "GET"
8773
+ });
8774
+ }
8775
+ async getVerifiedRunArtifact(runId, artifactId, maximumBytes = MAX_REMOTE_FILE_BYTES) {
8776
+ const artifacts = await this.getRunArtifacts(runId);
8777
+ const artifact = artifacts.find((row) => row.id === artifactId);
8778
+ if (!artifact)
8779
+ throw new Error("Run artifact not found");
8780
+ if (!Number.isSafeInteger(artifact.byteSize) || artifact.byteSize < 0 || artifact.byteSize > maximumBytes || typeof artifact.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(artifact.sha256))
8781
+ throw new Error("The server does not provide valid artifact integrity metadata");
8782
+ const response = await this.downloadRunArtifact(runId, artifactId);
8783
+ if (!response.ok)
8784
+ throw new RemoteRequestError("artifact download", response.status, response.statusText);
8785
+ const bytes = await readBoundedResponse(response, artifact.byteSize);
8786
+ if (bytes.byteLength !== artifact.byteSize || sha256(bytes) !== artifact.sha256)
8787
+ throw new Error("Artifact integrity verification failed");
8788
+ return { id: artifactId, fileName: String(artifact.fileName ?? artifactId), bytes, byteSize: bytes.byteLength, sha256: artifact.sha256 };
8789
+ }
8790
+ async submitQuotedRunWithFiles(slug, input, args, files, approval = {}) {
8791
+ const inputFiles = describeRemoteFiles(files);
8792
+ if (files.length && !(await this.getCapabilities()).capabilities.includes("runs.uploads"))
8793
+ throw new Error("The configured server does not support input uploads");
8794
+ const run = await this.submitQuotedRun(slug, input, args, { ...approval, inputFiles });
8795
+ if (run.error || !run.id || !files.length)
8796
+ return run;
8797
+ const pastUploads = (status) => typeof status === "string" && [
8798
+ "running",
8799
+ "completed",
8800
+ "failed",
8801
+ "cancelled",
8802
+ "expired",
8803
+ "pending_approval",
8804
+ "approved",
8805
+ "waiting"
8806
+ ].includes(status);
8807
+ if (pastUploads(run.status))
8808
+ return run;
8809
+ try {
8810
+ await this.uploadRunFiles(run.id, files);
8811
+ } catch {
8812
+ try {
8813
+ const current = await this.getRun(run.id);
8814
+ if (current && pastUploads(current.status))
8815
+ return current;
8816
+ } catch {}
8817
+ let cancellationRequested = false;
8818
+ try {
8819
+ await this.cancelRun(run.id);
8820
+ cancellationRequested = true;
8821
+ } catch {}
8822
+ throw new Error(`Input upload failed for run ${run.id}; ${cancellationRequested ? "cancellation requested" : "check its status and cancel the run"}`);
8823
+ }
8824
+ return run;
8206
8825
  }
8207
- async downloadRunArtifact(runId, artifactId) {
8208
- return this.request(`/api/v1/runs/${runId}/artifacts/${artifactId}/download`, {
8209
- method: "GET"
8210
- });
8826
+ async uploadRunFiles(runId, files) {
8827
+ const descriptors = describeRemoteFiles(files);
8828
+ const response = await this.requestNewRoute(`/api/v1/runs/${encodeURIComponent(runId)}/uploads`, { method: "POST", body: JSON.stringify({ files: descriptors }) });
8829
+ const payload = await response.json();
8830
+ if (!Array.isArray(payload.files) || payload.files.length !== files.length || new Set(payload.files.map((file) => file.name)).size !== files.length)
8831
+ throw new Error("Invalid input upload response");
8832
+ for (const file of files) {
8833
+ const upload = payload.files.find((row) => row.name === file.name);
8834
+ if (!upload)
8835
+ throw new Error("Missing input upload URL");
8836
+ const url = new URL(upload.uploadUrl);
8837
+ if (url.username || url.password || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)))
8838
+ throw new Error("Unsafe input upload URL");
8839
+ const uploaded = await fetch(url, { method: "PUT", body: file.bytes, headers: { "Content-Type": file.contentType ?? "application/octet-stream" }, redirect: "error", signal: AbortSignal.timeout(60000) });
8840
+ if (!uploaded.ok)
8841
+ throw new Error("Input upload failed");
8842
+ await uploaded.body?.cancel();
8843
+ }
8211
8844
  }
8212
8845
  async publishSkill(manifest, bundle, ifMatch) {
8213
8846
  const form = new FormData;
@@ -8221,7 +8854,9 @@ class RemoteSkillsClient {
8221
8854
  return fetch(`${this.apiUrl}/api/v1/skills`, {
8222
8855
  method: "POST",
8223
8856
  headers,
8224
- body: form
8857
+ body: form,
8858
+ redirect: "error",
8859
+ signal: AbortSignal.timeout(15000)
8225
8860
  });
8226
8861
  }
8227
8862
  async deleteSkill(slug) {
@@ -8243,8 +8878,10 @@ class RemoteSkillsClient {
8243
8878
  return [];
8244
8879
  if (!response.ok)
8245
8880
  throw new Error(`versions request failed: ${response.status}`);
8246
- const body = await response.json();
8247
- return Array.isArray(body.versions) ? body.versions : [];
8881
+ const body = await readSkillVersionPayload(response);
8882
+ if (!isVersionRecord(body) || !Array.isArray(body.versions) || body.slug !== undefined && body.slug !== slug)
8883
+ throw new Error(INVALID_SKILL_VERSION_RESPONSE);
8884
+ return body.versions.map((entry) => normalizeSkillVersion(entry, slug));
8248
8885
  }
8249
8886
  async getSkillVersion(slug, version2) {
8250
8887
  const response = await this.requestNewRoute(`/api/v1/skills/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version2)}`, undefined, { domainNotFoundCodes: ["SKILL_NOT_FOUND", "SKILL_VERSION_NOT_FOUND"] });
@@ -8252,7 +8889,7 @@ class RemoteSkillsClient {
8252
8889
  return null;
8253
8890
  if (!response.ok)
8254
8891
  throw new Error(`version request failed: ${response.status}`);
8255
- return await response.json();
8892
+ return normalizeSkillVersion(await readSkillVersionPayload(response), slug, version2);
8256
8893
  }
8257
8894
  async listPins() {
8258
8895
  const response = await this.requestNewRoute("/api/v1/pins");
@@ -8277,12 +8914,13 @@ class RemoteSkillsClient {
8277
8914
  if (!Array.isArray(payload)) {
8278
8915
  throw new Error("Remote tags payload did not match the expected contract (expected an array of tag names)");
8279
8916
  }
8280
- for (const tag of payload) {
8281
- if (typeof tag !== "string" || tag.trim().length === 0) {
8282
- throw new Error("Remote tags payload did not match the expected contract (every element must be a non-empty tag name)");
8283
- }
8917
+ const isName = (value) => typeof value === "string" && value.trim().length > 0;
8918
+ if (payload.every(isName))
8919
+ return payload;
8920
+ if (payload.every((tag) => tag !== null && typeof tag === "object" && !Array.isArray(tag) && isName(tag.name) && Number.isSafeInteger(tag.count) && tag.count >= 0)) {
8921
+ return payload.map((tag) => tag.name);
8284
8922
  }
8285
- return payload;
8923
+ throw new Error("Remote tags payload did not match the expected contract (every element must be a non-empty tag name, or every element must be a counted tag record)");
8286
8924
  }
8287
8925
  async skillsByTag(tag) {
8288
8926
  const path = `/api/v1/tags/${encodeURIComponent(tag)}/skills`;
@@ -8299,31 +8937,47 @@ class RemoteSkillsClient {
8299
8937
  return normalizeUpdatedSincePage(await response.json());
8300
8938
  }
8301
8939
  }
8302
- function requireOptionalString(record3, field) {
8303
- if (record3[field] === undefined)
8940
+ function requireOptionalString(record4, field) {
8941
+ if (record4[field] === undefined)
8304
8942
  return;
8305
- if (typeof record3[field] !== "string") {
8943
+ if (typeof record4[field] !== "string") {
8306
8944
  throw new Error(`Remote payload did not match the expected contract (${field} must be a string when present)`);
8307
8945
  }
8308
- return record3[field];
8946
+ return record4[field];
8947
+ }
8948
+ function isVersionRecord(value) {
8949
+ return value !== null && typeof value === "object" && !Array.isArray(value);
8950
+ }
8951
+ async function readSkillVersionPayload(response) {
8952
+ try {
8953
+ return await response.json();
8954
+ } catch {
8955
+ throw new Error(INVALID_SKILL_VERSION_RESPONSE);
8956
+ }
8957
+ }
8958
+ function normalizeSkillVersion(entry, slug, version2) {
8959
+ if (!isVersionRecord(entry) || typeof entry.slug !== "string" || !entry.slug.trim() || entry.slug !== slug || typeof entry.version !== "string" || !entry.version.trim() || version2 !== undefined && entry.version !== version2 || typeof entry.bundleSha256 !== "string" || !/^[a-f0-9]{64}$/i.test(entry.bundleSha256) || typeof entry.bundleByteSize !== "number" || !Number.isSafeInteger(entry.bundleByteSize) || entry.bundleByteSize < 0 || typeof entry.createdAt !== "string" || !entry.createdAt.trim() || entry.current !== undefined && typeof entry.current !== "boolean" || entry.storageKind !== undefined && typeof entry.storageKind !== "string" || entry.manifest !== undefined && !isVersionRecord(entry.manifest)) {
8960
+ throw new Error(INVALID_SKILL_VERSION_RESPONSE);
8961
+ }
8962
+ return entry;
8309
8963
  }
8310
8964
  function normalizePin(entry) {
8311
8965
  if (!entry || typeof entry !== "object") {
8312
8966
  throw new Error("Remote pin payload did not match the expected contract (expected an object)");
8313
8967
  }
8314
- const record3 = entry;
8315
- const slug = typeof record3.slug === "string" && record3.slug.trim() ? record3.slug.trim() : undefined;
8968
+ const record4 = entry;
8969
+ const slug = typeof record4.slug === "string" && record4.slug.trim() ? record4.slug.trim() : undefined;
8316
8970
  if (!slug) {
8317
8971
  throw new Error("Remote pin payload did not match the expected contract (missing slug)");
8318
8972
  }
8319
8973
  let metadata;
8320
- if (record3.metadata !== undefined) {
8321
- if (!record3.metadata || typeof record3.metadata !== "object" || Array.isArray(record3.metadata)) {
8974
+ if (record4.metadata !== undefined) {
8975
+ if (!record4.metadata || typeof record4.metadata !== "object" || Array.isArray(record4.metadata)) {
8322
8976
  throw new Error("Remote pin payload did not match the expected contract (metadata must be a JSON object when present)");
8323
8977
  }
8324
- metadata = record3.metadata;
8978
+ metadata = record4.metadata;
8325
8979
  }
8326
- const pinnedAt = requireOptionalString(record3, "pinnedAt");
8980
+ const pinnedAt = requireOptionalString(record4, "pinnedAt");
8327
8981
  return {
8328
8982
  slug,
8329
8983
  ...pinnedAt !== undefined ? { pinnedAt } : {},
@@ -8340,16 +8994,16 @@ function normalizeSkillSummary(entry) {
8340
8994
  if (!entry || typeof entry !== "object") {
8341
8995
  throw new Error("Remote skill payload did not match the expected contract (expected an object)");
8342
8996
  }
8343
- const record3 = entry;
8344
- const slug = typeof record3.slug === "string" && record3.slug.trim() ? record3.slug.trim() : undefined;
8997
+ const record4 = entry;
8998
+ const slug = typeof record4.slug === "string" && record4.slug.trim() ? record4.slug.trim() : undefined;
8345
8999
  if (!slug) {
8346
9000
  throw new Error("Remote skill payload did not match the expected contract (missing slug)");
8347
9001
  }
8348
9002
  return {
8349
9003
  slug,
8350
- ...requireOptionalString(record3, "name") !== undefined ? { name: requireOptionalString(record3, "name") } : {},
8351
- ...requireOptionalString(record3, "version") !== undefined ? { version: requireOptionalString(record3, "version") } : {},
8352
- ...requireOptionalString(record3, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record3, "updatedAt") } : {}
9004
+ ...requireOptionalString(record4, "name") !== undefined ? { name: requireOptionalString(record4, "name") } : {},
9005
+ ...requireOptionalString(record4, "version") !== undefined ? { version: requireOptionalString(record4, "version") } : {},
9006
+ ...requireOptionalString(record4, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record4, "updatedAt") } : {}
8353
9007
  };
8354
9008
  }
8355
9009
  function normalizeSkillSummaryList(payload) {
@@ -8359,48 +9013,75 @@ function normalizeSkillSummaryList(payload) {
8359
9013
  return payload.map(normalizeSkillSummary);
8360
9014
  }
8361
9015
  async function responseBodyCarriesCode(response, codes) {
9016
+ const reader = response.body?.getReader();
9017
+ if (!reader)
9018
+ return false;
9019
+ const maximum = 8 * 1024;
9020
+ let deadline;
9021
+ const expired = new Promise((_, reject) => {
9022
+ deadline = setTimeout(() => reject(new Error("Error response read deadline exceeded")), 1000);
9023
+ });
8362
9024
  try {
8363
- const payload = await response.clone().json();
8364
- if (!payload || typeof payload !== "object")
9025
+ const chunks = [];
9026
+ let size = 0;
9027
+ while (true) {
9028
+ const next = await Promise.race([reader.read(), expired]);
9029
+ if (next.done)
9030
+ break;
9031
+ size += next.value.byteLength;
9032
+ if (size > maximum)
9033
+ return false;
9034
+ chunks.push(next.value);
9035
+ }
9036
+ const bytes = new Uint8Array(size);
9037
+ let offset = 0;
9038
+ for (const chunk of chunks) {
9039
+ bytes.set(chunk, offset);
9040
+ offset += chunk.byteLength;
9041
+ }
9042
+ const payload = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
9043
+ if (!payload || typeof payload !== "object" || Array.isArray(payload) || !Object.hasOwn(payload, "code"))
8365
9044
  return false;
8366
9045
  const code = payload.code;
8367
9046
  return typeof code === "string" && codes.includes(code);
8368
9047
  } catch {
8369
9048
  return false;
9049
+ } finally {
9050
+ clearTimeout(deadline);
9051
+ reader.cancel().catch(() => {});
9052
+ reader.releaseLock();
8370
9053
  }
8371
9054
  }
8372
9055
  function normalizeUpdatedSincePage(payload) {
8373
9056
  if (!payload || typeof payload !== "object") {
8374
9057
  throw new Error("Updated-since payload did not match the expected contract (expected an object)");
8375
9058
  }
8376
- const record3 = payload;
8377
- if (!Array.isArray(record3.skills)) {
9059
+ const record4 = payload;
9060
+ if (!Array.isArray(record4.skills)) {
8378
9061
  throw new Error("Updated-since payload did not match the expected contract (missing skills array)");
8379
9062
  }
8380
- const skills = record3.skills.map(normalizeSkillSummary);
8381
- const nextCursor = record3.nextCursor === undefined || record3.nextCursor === null ? null : record3.nextCursor;
9063
+ const skills = record4.skills.map(normalizeSkillSummary);
9064
+ const nextCursor = record4.nextCursor === undefined || record4.nextCursor === null ? null : record4.nextCursor;
8382
9065
  if (nextCursor !== null && typeof nextCursor !== "string") {
8383
9066
  throw new Error("Updated-since payload did not match the expected contract (nextCursor must be a string or absent)");
8384
9067
  }
8385
9068
  return { skills, nextCursor };
8386
9069
  }
8387
9070
  async function createRemoteSkillsClient(env = process.env) {
8388
- const fleet = resolveSkillsFleet(env);
8389
- if (fleet.mode !== "hosted")
8390
- return null;
8391
- const apiKey = await resolveSkillsApiKey(env);
8392
- if (!apiKey) {
8393
- throw new Error("A Skills authority resolved but no API key did. Sign in with: skills auth login");
8394
- }
8395
- return new RemoteSkillsClient(apiKey, fleet.apiOrigin);
9071
+ const connection = await resolveSkillsConnection(env);
9072
+ return connection ? new RemoteSkillsClient(connection.apiKey, connection.apiOrigin) : null;
8396
9073
  }
8397
9074
  function createRemoteSkillsClientReadOnly(env = process.env) {
8398
9075
  return createRemoteSkillsClient(env);
8399
9076
  }
8400
- var RemoteRouteUnsupportedError, RemoteRequestError;
9077
+ var RemoteRouteUnsupportedError, RemoteRequestError, RemoteWorkspaceMemberError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
8401
9078
  var init_remote_client = __esm(() => {
9079
+ init_remote_workspace();
9080
+ init_remote_workspace();
8402
9081
  init_auth_store();
8403
9082
  init_fleet_credentials();
9083
+ init_remote_account();
9084
+ init_remote_files();
8404
9085
  RemoteRouteUnsupportedError = class RemoteRouteUnsupportedError extends Error {
8405
9086
  path;
8406
9087
  status;
@@ -8416,13 +9097,30 @@ var init_remote_client = __esm(() => {
8416
9097
  RemoteRequestError = class RemoteRequestError extends Error {
8417
9098
  path;
8418
9099
  status;
8419
- constructor(path, status, statusText) {
8420
- super(`Remote request to ${path} failed: HTTP ${status}${statusText ? ` ${statusText}` : ""}`);
9100
+ constructor(path, status, _statusText) {
9101
+ super(`Remote request to ${path} failed: HTTP ${status}`);
8421
9102
  this.path = path;
8422
9103
  this.status = status;
8423
9104
  this.name = "RemoteRequestError";
8424
9105
  }
8425
9106
  };
9107
+ RemoteWorkspaceMemberError = class RemoteWorkspaceMemberError extends RemoteRequestError {
9108
+ code;
9109
+ constructor(path, code) {
9110
+ super(path, workspaceMemberFailures[code][0]);
9111
+ this.code = code;
9112
+ this.name = "RemoteWorkspaceMemberError";
9113
+ this.message = workspaceMemberFailures[code][1];
9114
+ }
9115
+ };
9116
+ RemoteCapabilityUnavailableError = class RemoteCapabilityUnavailableError extends RemoteRequestError {
9117
+ code = "SUBSCRIPTION_CHECKOUT_UNAVAILABLE";
9118
+ constructor() {
9119
+ super("/api/v1/billing/checkout", 503);
9120
+ this.name = "RemoteCapabilityUnavailableError";
9121
+ this.message = "Subscription checkout is unavailable on the configured Skills server. " + "Use skills credits packs to view credit packs, or skills billing portal to manage an existing subscription.";
9122
+ }
9123
+ };
8426
9124
  });
8427
9125
 
8428
9126
  // ../../node_modules/.bun/content-type@1.0.5/node_modules/content-type/index.js
@@ -8449,7 +9147,7 @@ var require_content_type = __commonJS((exports) => {
8449
9147
  if (!type || !TYPE_REGEXP.test(type)) {
8450
9148
  throw new TypeError("invalid type");
8451
9149
  }
8452
- var string4 = type;
9150
+ var string5 = type;
8453
9151
  if (parameters && typeof parameters === "object") {
8454
9152
  var param;
8455
9153
  var params = Object.keys(parameters).sort();
@@ -8458,16 +9156,16 @@ var require_content_type = __commonJS((exports) => {
8458
9156
  if (!TOKEN_REGEXP.test(param)) {
8459
9157
  throw new TypeError("invalid parameter name");
8460
9158
  }
8461
- string4 += "; " + param + "=" + qstring(parameters[param]);
9159
+ string5 += "; " + param + "=" + qstring(parameters[param]);
8462
9160
  }
8463
9161
  }
8464
- return string4;
9162
+ return string5;
8465
9163
  }
8466
- function parse6(string4) {
8467
- if (!string4) {
9164
+ function parse6(string5) {
9165
+ if (!string5) {
8468
9166
  throw new TypeError("argument string is required");
8469
9167
  }
8470
- var header = typeof string4 === "object" ? getcontenttype(string4) : string4;
9168
+ var header = typeof string5 === "object" ? getcontenttype(string5) : string5;
8471
9169
  if (typeof header !== "string") {
8472
9170
  throw new TypeError("argument string is required to be a string");
8473
9171
  }
@@ -13941,7 +14639,7 @@ class StdioServerTransport {
13941
14639
  // package.json
13942
14640
  var package_default = {
13943
14641
  name: "@hasna/skills",
13944
- version: "0.3.0",
14642
+ version: "0.5.0",
13945
14643
  description: "Skills library for AI coding agents",
13946
14644
  type: "module",
13947
14645
  bin: {
@@ -13973,6 +14671,7 @@ var package_default = {
13973
14671
  files: [
13974
14672
  "dist/",
13975
14673
  "!dist/**/*.test.d.ts",
14674
+ "!dist/**/*.fixture.d.ts",
13976
14675
  "!dist/test-preload.d.ts",
13977
14676
  "!dist/platform",
13978
14677
  "bin/",
@@ -13998,10 +14697,10 @@ var package_default = {
13998
14697
  migrate: "bun run ./src/server/migrate.ts",
13999
14698
  typecheck: "tsc --noEmit",
14000
14699
  "verify:release": "bun run scripts/release-guard.ts",
14700
+ "verify:consumer-types": "bun run scripts/consumer-types.ts",
14001
14701
  prepare: "bun run build:js",
14002
- prepack: "bun run build && bun run verify:release",
14003
- prepublishOnly: "bun run typecheck && bun run test",
14004
- postinstall: "mkdir -p $HOME/.hasna/skills/custom 2>/dev/null || true"
14702
+ prepack: "bun run build && bun run verify:release && bun run verify:consumer-types",
14703
+ prepublishOnly: "bun run typecheck && bun run test"
14005
14704
  },
14006
14705
  keywords: [
14007
14706
  "skills",
@@ -14022,6 +14721,7 @@ var package_default = {
14022
14721
  author: "Hasna",
14023
14722
  license: "Apache-2.0",
14024
14723
  devDependencies: {
14724
+ "@hasna/contracts": "1.0.2",
14025
14725
  "@types/bun": "1.3.14",
14026
14726
  "@types/node": "25.2.3",
14027
14727
  "@types/react": "^18.2.0",
@@ -14033,8 +14733,7 @@ var package_default = {
14033
14733
  dependencies: {
14034
14734
  "@aws-sdk/client-ecs": "^3.1079.0",
14035
14735
  "@aws-sdk/client-s3": "^3.1079.0",
14036
- "@hasna/contracts": "1.0.1",
14037
- "@hasna/events": "0.1.16",
14736
+ "@hasna/events": "0.1.18",
14038
14737
  "@modelcontextprotocol/sdk": "^1.26.0",
14039
14738
  chalk: "^5.3.0",
14040
14739
  commander: "^12.1.0",
@@ -21950,12 +22649,7 @@ import { homedir } from "os";
21950
22649
  import { join, resolve } from "path";
21951
22650
  import { homedir as pathsResolverHomedir } from "os";
21952
22651
  import { join as pathsResolverJoin } from "path";
21953
- var PATHS_RESOLVER_KIND_ENV = {
21954
- config: "HASNA_CONFIG_HOME",
21955
- data: "HASNA_DATA_HOME",
21956
- state: "HASNA_STATE_HOME",
21957
- cache: "HASNA_CACHE_HOME"
21958
- };
22652
+ var PATHS_RESOLVER_DATA_ENV = "HASNA_DATA_HOME";
21959
22653
  var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
21960
22654
  function pathsResolverAssertApp(app) {
21961
22655
  if (typeof app !== "string" || app.length === 0) {
@@ -21965,48 +22659,19 @@ function pathsResolverAssertApp(app) {
21965
22659
  throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
21966
22660
  }
21967
22661
  }
21968
- function pathsResolverAssertKind(kind) {
21969
- if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
21970
- throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
21971
- }
21972
- }
21973
- function pathsResolverBaseDir(kind, options) {
21974
- pathsResolverAssertKind(kind);
22662
+ function pathsResolverDataBaseDir(options) {
21975
22663
  const env = options.env ?? process.env;
21976
- const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
22664
+ const override = env[PATHS_RESOLVER_DATA_ENV];
21977
22665
  if (typeof override === "string" && override.length > 0)
21978
22666
  return override;
21979
22667
  const home = options.home ?? pathsResolverHomedir();
21980
22668
  const platform = options.platform ?? process.platform;
21981
- if (platform === "darwin") {
21982
- switch (kind) {
21983
- case "config":
21984
- case "data":
21985
- return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
21986
- case "cache":
21987
- return pathsResolverJoin(home, "Library", "Caches", "Hasna");
21988
- case "state":
21989
- return pathsResolverJoin(home, "Library", "Logs", "Hasna");
21990
- }
21991
- }
21992
- switch (kind) {
21993
- case "config":
21994
- return pathsResolverJoin(home, ".config", "hasna");
21995
- case "data":
21996
- return pathsResolverJoin(home, ".local", "share", "hasna");
21997
- case "state":
21998
- return pathsResolverJoin(home, ".local", "state", "hasna");
21999
- case "cache":
22000
- return pathsResolverJoin(home, ".cache", "hasna");
22001
- }
22002
- }
22003
- function pathsResolverResolve(kind, options) {
22004
- pathsResolverAssertApp(options.app);
22005
- const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
22006
- return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
22669
+ return platform === "darwin" ? pathsResolverJoin(home, "Library", "Application Support", "Hasna") : pathsResolverJoin(home, ".local", "share", "hasna");
22007
22670
  }
22008
22671
  function dataDir(options) {
22009
- return pathsResolverResolve("data", options);
22672
+ pathsResolverAssertApp(options.app);
22673
+ const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
22674
+ return pathsResolverJoin(pathsResolverDataBaseDir(options), appSegment);
22010
22675
  }
22011
22676
  var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
22012
22677
  var HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME";
@@ -22993,6 +23658,16 @@ function isHostedMetadataSkill(skillName, frontmatter, registryMeta, packageDecl
22993
23658
  return true;
22994
23659
  return false;
22995
23660
  }
23661
+ function frontmatterString(raw) {
23662
+ if (raw.startsWith('"') && raw.endsWith('"')) {
23663
+ try {
23664
+ const decoded = JSON.parse(raw);
23665
+ if (typeof decoded === "string")
23666
+ return decoded;
23667
+ } catch {}
23668
+ }
23669
+ return raw.replace(/^["']|["']$/g, "");
23670
+ }
22996
23671
  function parseSkillFrontmatter(content) {
22997
23672
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
22998
23673
  if (!match)
@@ -23012,12 +23687,12 @@ function parseSkillFrontmatter(content) {
23012
23687
  const tags = [];
23013
23688
  while (i + 1 < lines.length && /^\s+-\s+/.test(lines[i + 1])) {
23014
23689
  i++;
23015
- tags.push(lines[i].replace(/^\s+-\s+/, "").trim());
23690
+ tags.push(frontmatterString(lines[i].replace(/^\s+-\s+/, "").trim()));
23016
23691
  }
23017
23692
  result.tags = tags;
23018
23693
  continue;
23019
23694
  }
23020
- const value = rawValue.replace(/^["']|["']$/g, "");
23695
+ const value = frontmatterString(rawValue);
23021
23696
  if (!value)
23022
23697
  continue;
23023
23698
  if (key === "name")
@@ -23576,14 +24251,27 @@ function normalizePortableSkillName(name) {
23576
24251
  }
23577
24252
  return normalized;
23578
24253
  }
24254
+ function normalizeNewPortableSkillName(name) {
24255
+ normalizePortableSkillName(name);
24256
+ const normalized = name.trim().replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
24257
+ if (!normalized)
24258
+ throw new Error(`Invalid skill name '${name}'. Include letters or numbers.`);
24259
+ return normalized;
24260
+ }
23579
24261
  function readPortableSkillManifest(skillPath, fallbackName = basename(skillPath)) {
24262
+ return readManifest(skillPath, fallbackName, normalizePortableSkillName);
24263
+ }
24264
+ function readPortableSkillManifestForImport(skillPath) {
24265
+ return readManifest(skillPath, basename(skillPath), normalizeNewPortableSkillName);
24266
+ }
24267
+ function readManifest(skillPath, fallbackName, normalizeName) {
23580
24268
  const skillJsonPath = join6(skillPath, "skill.json");
23581
24269
  const skillMdPath = join6(skillPath, "SKILL.md");
23582
24270
  const pkgPath = join6(skillPath, "package.json");
23583
24271
  const jsonManifest = existsSync6(skillJsonPath) ? readJsonObject(skillJsonPath) : undefined;
23584
24272
  const frontmatter = existsSync6(skillMdPath) ? parseSkillFrontmatter(readFileSync5(skillMdPath, "utf-8")) ?? undefined : undefined;
23585
24273
  const pkg = existsSync6(pkgPath) ? readJsonObject(pkgPath) : undefined;
23586
- const name = normalizePortableSkillName(stringField(jsonManifest, "name") ?? frontmatter?.name ?? stringValue(pkg?.name) ?? fallbackName);
24274
+ const name = normalizeName(stringField(jsonManifest, "name") ?? frontmatter?.name ?? stringValue(pkg?.name) ?? fallbackName);
23587
24275
  const description = stringField(jsonManifest, "description") ?? frontmatter?.description ?? stringValue(pkg?.description) ?? `${name} skill`;
23588
24276
  const version2 = readDeclaredSkillVersion(skillPath) ?? PORTABLE_SKILL_DEFAULT_VERSION;
23589
24277
  const kind = parseSkillKind(stringField(jsonManifest, "kind") ?? frontmatter?.kind);
@@ -23626,8 +24314,8 @@ function createInstructionManifest(name, options) {
23626
24314
  description: options.description,
23627
24315
  version: PORTABLE_SKILL_DEFAULT_VERSION,
23628
24316
  displayName: displayName(name),
23629
- category: "Development Tools",
23630
- tags: ["custom", name],
24317
+ category: options.category ?? "Development Tools",
24318
+ tags: options.tags ?? ["custom", name],
23631
24319
  kind: "instruction",
23632
24320
  inputs: [],
23633
24321
  commands: [],
@@ -23642,16 +24330,16 @@ function writeInstructionSkillTemplate(skillPath, manifest) {
23642
24330
  }
23643
24331
  function renderInstructionSkillMd(manifest) {
23644
24332
  const tags = manifest.tags?.length ? `tags:
23645
- ${manifest.tags.map((tag) => ` - ${tag}`).join(`
24333
+ ${manifest.tags.map((tag) => ` - ${yamlString(tag)}`).join(`
23646
24334
  `)}
23647
24335
  ` : "";
23648
24336
  return `---
23649
24337
  name: ${manifest.name}
23650
- description: ${manifest.description}
24338
+ description: ${yamlString(manifest.description)}
23651
24339
  kind: instruction
23652
24340
  version: ${manifest.version}
23653
24341
  source: custom
23654
- category: ${manifest.category ?? "Development Tools"}
24342
+ category: ${yamlString(manifest.category ?? "Development Tools")}
23655
24343
  ${tags}---
23656
24344
 
23657
24345
  # ${manifest.displayName ?? displayName(manifest.name)}
@@ -23672,8 +24360,8 @@ function createPortableManifest(name, options) {
23672
24360
  description: options.description,
23673
24361
  version: PORTABLE_SKILL_DEFAULT_VERSION,
23674
24362
  displayName: displayName(name),
23675
- category: "Development Tools",
23676
- tags: ["custom", name],
24363
+ category: options.category ?? "Development Tools",
24364
+ tags: options.tags ?? ["custom", name],
23677
24365
  inputs: DEFAULT_INPUTS,
23678
24366
  commands: [{
23679
24367
  name,
@@ -23831,10 +24519,45 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
23831
24519
  };
23832
24520
  if (!existsSync6(join6(skillPath, "SKILL.md"))) {
23833
24521
  writeFileSync2(join6(skillPath, "SKILL.md"), renderSkillMd(next));
24522
+ } else {
24523
+ const path = join6(skillPath, "SKILL.md");
24524
+ const content = readFileSync5(path, "utf8");
24525
+ const declaredName = parseSkillFrontmatter(content)?.name;
24526
+ if (declaredName && declaredName !== next.name) {
24527
+ writeFileSync2(path, renameInstructionFrontmatter(content, next.name));
24528
+ }
24529
+ }
24530
+ const packagePath = join6(skillPath, "package.json");
24531
+ if (existsSync6(packagePath)) {
24532
+ const pkg = readJsonObject(packagePath);
24533
+ if (typeof pkg.name === "string" && pkg.name !== next.name) {
24534
+ writeFileSync2(packagePath, `${JSON.stringify({ ...pkg, name: next.name }, null, 2)}
24535
+ `);
24536
+ }
23834
24537
  }
23835
24538
  writeSkillJsonWithHash(skillPath, next);
23836
24539
  return readPortableSkillManifest(skillPath, next.name);
23837
24540
  }
24541
+ function renameInstructionFrontmatter(content, name) {
24542
+ const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?=\r?\n|$)/);
24543
+ const names = frontmatter?.[1]?.match(/^[ \t]*name[ \t]*:[^\r\n]*/gm) ?? [];
24544
+ const declaration = names.length === 1 ? names[0].match(/^(name[ \t]*:[ \t]*)(.*?)([ \t]*)$/) : null;
24545
+ const scalar = declaration?.[2] ?? "";
24546
+ let simple = /^[a-zA-Z0-9_.@/ -]+$/.test(scalar);
24547
+ if (scalar.startsWith('"')) {
24548
+ try {
24549
+ simple = typeof JSON.parse(scalar) === "string";
24550
+ } catch {
24551
+ simple = false;
24552
+ }
24553
+ } else if (scalar.startsWith("'"))
24554
+ simple = /^'[^'\r\n]*'$/.test(scalar);
24555
+ if (!frontmatter || !declaration || !simple) {
24556
+ throw new Error("Cannot rename instruction SKILL.md: use one unambiguous top-level name scalar in frontmatter.");
24557
+ }
24558
+ const renamed = frontmatter[0].replace(/^name[ \t]*:[^\r\n]*/m, () => `${declaration[1]}${name}${declaration[3]}`);
24559
+ return renamed + content.slice(frontmatter[0].length);
24560
+ }
23838
24561
  function copySkillDirectory(source, destination) {
23839
24562
  const resolvedSource = lstatSync2(source).isSymbolicLink() ? realpathSync(source) : source;
23840
24563
  mkdirSync2(destination, { recursive: true });
@@ -23864,10 +24587,13 @@ function isExcludedCopyEntry(name, isFirstSegment) {
23864
24587
  return true;
23865
24588
  return false;
23866
24589
  }
24590
+ function yamlString(value) {
24591
+ return JSON.stringify(value);
24592
+ }
23867
24593
  function renderSkillMd(manifest) {
23868
24594
  return `---
23869
24595
  name: ${manifest.name}
23870
- description: ${manifest.description}
24596
+ description: ${yamlString(manifest.description)}
23871
24597
  ---
23872
24598
 
23873
24599
  # ${manifest.displayName ?? displayName(manifest.name)}
@@ -24230,7 +24956,7 @@ function isOfficialSkillName(name) {
24230
24956
  return OFFICIAL_SKILL_NAMES.has(name);
24231
24957
  }
24232
24958
  function scaffoldPortableSkill(name, options = {}) {
24233
- const skillName = normalizePortableSkillName(name);
24959
+ const skillName = normalizeNewPortableSkillName(name);
24234
24960
  const root = getPortableSkillsRoot(options);
24235
24961
  const skillPath = join7(root, skillName);
24236
24962
  if (existsSync7(skillPath)) {
@@ -24241,11 +24967,11 @@ function scaffoldPortableSkill(name, options = {}) {
24241
24967
  const kind = options.kind ?? "executable";
24242
24968
  const description = options.description ?? `${displayName(skillName)} skill`;
24243
24969
  if (kind === "instruction") {
24244
- const manifest2 = createInstructionManifest(skillName, { description });
24970
+ const manifest2 = createInstructionManifest(skillName, { description, category: options.category, tags: options.tags });
24245
24971
  writeInstructionSkillTemplate(skillPath, manifest2);
24246
24972
  return { name: skillName, path: skillPath, manifest: manifest2, created: true };
24247
24973
  }
24248
- const manifest = createPortableManifest(skillName, { description });
24974
+ const manifest = createPortableManifest(skillName, { description, category: options.category, tags: options.tags });
24249
24975
  writePortableSkillTemplate(skillPath, manifest);
24250
24976
  return { name: skillName, path: skillPath, manifest, created: true };
24251
24977
  }
@@ -24254,9 +24980,9 @@ function portPortableSkill(sourcePath, options = {}) {
24254
24980
  if (!existsSync7(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
24255
24981
  throw new Error(`Skill source directory not found: ${sourcePath}`);
24256
24982
  }
24257
- const inferred = readPortableSkillManifest(absoluteSource, basename2(absoluteSource));
24983
+ const inferred = readPortableSkillManifestForImport(absoluteSource);
24258
24984
  const explicitName = options.name != null;
24259
- const skillName = normalizePortableSkillName(options.name ?? inferred.name);
24985
+ const skillName = normalizeNewPortableSkillName(options.name ?? inferred.name);
24260
24986
  if (isOfficialSkillName(skillName) && !options.allowShadow) {
24261
24987
  const sourceSlug = safeNormalizeName(basename2(absoluteSource));
24262
24988
  const via = explicitName ? `Name '${skillName}' matches a bundled official skill.` : `Inferred name '${skillName}'${sourceSlug && sourceSlug !== skillName ? ` (from source folder '${basename2(absoluteSource)}')` : ""} matches a bundled official skill.`;
@@ -24737,9 +25463,286 @@ function mergeCustomSkills(skills) {
24737
25463
  return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
24738
25464
  }
24739
25465
 
25466
+ // src/lib/read-access.ts
25467
+ init_fleet_credentials();
25468
+
25469
+ // src/lib/api-url.ts
25470
+ init_fleet_credentials();
25471
+ init_fleet_credentials();
25472
+ var API_URL_ENV_VAR = SKILLS_API_URL_ENV;
25473
+ var MISSING_API_URL_HINT = `run: skills auth login, or set ${API_URL_ENV_VAR}=<your Skills instance origin>, ` + `or run: skills setup --api-url <your Skills instance origin>`;
25474
+ function resolveApiUrl(env = process.env, options = {}) {
25475
+ const fleet = resolveSkillsFleet(env, options);
25476
+ return fleet.mode === "hosted" ? fleet.apiOrigin : undefined;
25477
+ }
25478
+
25479
+ // src/lib/remote-registry.ts
25480
+ init_fleet_credentials();
25481
+
25482
+ // src/lib/discovery.ts
25483
+ var VENDOR_TERMS = [
25484
+ "Google Gemini",
25485
+ "OpenAI Sora",
25486
+ "MiniMax Hailuo",
25487
+ "Claude Code",
25488
+ "Claude Vision",
25489
+ "DALL-E 3",
25490
+ "GPT-4o Mini",
25491
+ "Cerebras",
25492
+ "OpenRouter",
25493
+ "Firecrawl",
25494
+ "ElevenLabs",
25495
+ "Anthropic",
25496
+ "OpenAI",
25497
+ "Minimax",
25498
+ "MiniMax",
25499
+ "Gemini",
25500
+ "Claude",
25501
+ "Whisper",
25502
+ "Seedance",
25503
+ "Lyria",
25504
+ "Sora",
25505
+ "Veo",
25506
+ "Exa.ai",
25507
+ "Exa",
25508
+ "XAI",
25509
+ "xAI"
25510
+ ];
25511
+ var VENDOR_TAGS = new Set([
25512
+ "anthropic",
25513
+ "cerebras",
25514
+ "claude",
25515
+ "exa",
25516
+ "firecrawl",
25517
+ "gemini",
25518
+ "google",
25519
+ "minimax",
25520
+ "openai",
25521
+ "openrouter",
25522
+ "seedance",
25523
+ "whisper",
25524
+ "xai"
25525
+ ]);
25526
+ var vendorPattern = new RegExp(`\\b(${VENDOR_TERMS.map(escapeRegExp).join("|")})\\b`, "gi");
25527
+ function getCompactSkillDiscovery(skill) {
25528
+ return {
25529
+ name: skill.name,
25530
+ category: skill.category,
25531
+ description: sanitizePublicDiscoveryText(skill.description)
25532
+ };
25533
+ }
25534
+ function getPublicSkillDiscovery(skill) {
25535
+ return {
25536
+ ...skill,
25537
+ description: sanitizePublicDiscoveryText(skill.description),
25538
+ tags: publicDiscoveryTags(skill.tags)
25539
+ };
25540
+ }
25541
+ function publicDiscoveryTags(tags) {
25542
+ return tags.filter((tag) => !VENDOR_TAGS.has(tag.toLowerCase()));
25543
+ }
25544
+ function sanitizePublicDiscoveryText(text) {
25545
+ let sanitized = text.replace(vendorPattern, "hosted AI").replace(/\bLLM\b/g, "AI").replace(/\s{2,}/g, " ");
25546
+ let previous;
25547
+ do {
25548
+ previous = sanitized;
25549
+ sanitized = sanitized.replace(/\bhosted AI(?: providers)?\s*,\s*hosted AI(?: providers)?\b/gi, "hosted AI providers").replace(/\bhosted AI(?: providers)?\s*,?\s*and\s*hosted AI(?: providers)?\b/gi, "hosted AI providers").replace(/\bhosted AI(?: providers)?\s+or\s+hosted AI(?: providers)?\b/gi, "hosted AI providers").replace(/\bhosted AI providers\s+hosted AI\b/gi, "hosted AI providers").replace(/\bhosted AI providers\s+providers\b/gi, "hosted AI providers");
25550
+ } while (sanitized !== previous);
25551
+ return sanitized.trim();
25552
+ }
25553
+ function publicDiscoveryEnvVars(_skillName, envVars) {
25554
+ return envVars;
25555
+ }
25556
+ function publicDiscoveryDependencies(_skillName, dependencies) {
25557
+ return dependencies;
25558
+ }
25559
+ function publicDiscoveryDocumentation(_skill, documentation) {
25560
+ return documentation;
25561
+ }
25562
+ function escapeRegExp(value) {
25563
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25564
+ }
25565
+
25566
+ // src/lib/remote-registry.ts
25567
+ var remoteAvailabilitySchema = exports_external.object({
25568
+ status: exports_external.enum(["available", "unavailable"]),
25569
+ code: exports_external.string().optional(),
25570
+ message: exports_external.string().optional(),
25571
+ details: exports_external.array(exports_external.string()).optional()
25572
+ }).passthrough();
25573
+ var remoteSkillSchema = exports_external.object({
25574
+ name: exports_external.string().min(1).optional(),
25575
+ slug: exports_external.string().min(1).optional(),
25576
+ displayName: exports_external.string().optional(),
25577
+ description: exports_external.string().optional(),
25578
+ category: exports_external.string().optional(),
25579
+ tags: exports_external.array(exports_external.string()).optional(),
25580
+ dependencies: exports_external.array(exports_external.string()).optional(),
25581
+ version: exports_external.string().optional(),
25582
+ availability: remoteAvailabilitySchema.optional()
25583
+ }).passthrough().refine((skill) => skill.name || skill.slug, {
25584
+ message: "Remote skill requires name or slug"
25585
+ });
25586
+ var secretValuePatterns = [
25587
+ /\bsk-[A-Za-z0-9_-]{8,}\b/g,
25588
+ /\bgh[opsur]_[A-Za-z0-9_]{8,}\b/g,
25589
+ /\bgithub_pat_[A-Za-z0-9_]{8,}\b/g,
25590
+ /\bnpm_[A-Za-z0-9_]{8,}\b/g,
25591
+ /\bAKIA[A-Z0-9]{12,}\b/g,
25592
+ /\bAIza[A-Za-z0-9_-]{10,}\b/g,
25593
+ new RegExp("\\bsecret" + "-token:\\s*[A-Za-z0-9._-]+", "gi"),
25594
+ /\bctx7sk\-[A-Za-z0-9_-]{8,}\b/g,
25595
+ /\bxai\-[A-Za-z0-9_-]{8,}\b/g
25596
+ ];
25597
+ var remoteSkillDetailSchema = exports_external.union([
25598
+ remoteSkillSchema,
25599
+ exports_external.object({ skill: remoteSkillSchema }),
25600
+ exports_external.object({ data: remoteSkillSchema })
25601
+ ]);
25602
+ var remoteRegistrySchema = exports_external.union([
25603
+ exports_external.array(remoteSkillSchema),
25604
+ exports_external.object({ skills: exports_external.array(remoteSkillSchema) }),
25605
+ exports_external.object({ data: exports_external.array(remoteSkillSchema) })
25606
+ ]);
25607
+ function getConfiguredApiUrl(env = process.env) {
25608
+ return resolveApiUrl(env);
25609
+ }
25610
+ function buildSkillsApiUrl(apiUrl, endpoint = "/skills") {
25611
+ const url = new URL(apiUrl);
25612
+ const cleanEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
25613
+ const pathname = url.pathname.replace(/\/+$/, "");
25614
+ const apiBase = /\/api(?:\/v1)?\/skills$/.test(pathname) ? pathname.slice(0, -"/skills".length) : pathname;
25615
+ if (/\/api(?:\/v1)?$/.test(apiBase)) {
25616
+ url.pathname = `${apiBase}${cleanEndpoint}`;
25617
+ return url.toString();
25618
+ }
25619
+ url.pathname = `${apiBase}/api/v1${cleanEndpoint}`.replace(/\/{2,}/g, "/");
25620
+ return url.toString();
25621
+ }
25622
+ function titleize(name) {
25623
+ return name.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
25624
+ }
25625
+ function normalizeRemoteSkill(skill) {
25626
+ const name = skill.name || skill.slug;
25627
+ if (!name)
25628
+ throw new Error("Remote skill requires name or slug");
25629
+ return {
25630
+ name,
25631
+ displayName: skill.displayName || titleize(name),
25632
+ description: skill.description || "",
25633
+ category: skill.category || "Remote",
25634
+ tags: skill.tags || ["remote"],
25635
+ dependencies: skill.dependencies,
25636
+ ...skill.version ? { version: skill.version } : {},
25637
+ availability: normalizeRemoteAvailability(skill.availability),
25638
+ source: "remote"
25639
+ };
25640
+ }
25641
+ function normalizeRemoteAvailability(availability) {
25642
+ if (!availability)
25643
+ return { status: "available" };
25644
+ if (availability.status === "available")
25645
+ return { status: "available" };
25646
+ return {
25647
+ status: availability.status,
25648
+ ...safeAvailabilityCode(availability.code) ? { code: safeAvailabilityCode(availability.code) } : {},
25649
+ ...availability.message ? { message: sanitizeAvailabilityText(availability.message) } : {},
25650
+ ...availability.details ? { details: availability.details.map(sanitizeAvailabilityText).filter(Boolean) } : {}
25651
+ };
25652
+ }
25653
+ function safeAvailabilityCode(code) {
25654
+ if (!code)
25655
+ return;
25656
+ return /^[A-Z0-9_]+$/.test(code) ? code : undefined;
25657
+ }
25658
+ function sanitizeAvailabilityText(text) {
25659
+ return secretValuePatterns.reduce((value, pattern) => value.replace(pattern, "credential"), sanitizePublicDiscoveryText(text).replace(/\b[A-Z0-9_]*(?:API_KEY|SECRET|TOKEN|CREDENTIAL)[A-Z0-9_]*\b/g, "credential")).replace(/\s{2,}/g, " ").trim();
25660
+ }
25661
+ function parseRemoteRegistryPayload(payload) {
25662
+ const parsed = parseRemoteContract(remoteRegistrySchema, payload, "Remote registry payload did not match the expected skills contract");
25663
+ const rawSkills = Array.isArray(parsed) ? parsed : ("skills" in parsed) ? parsed.skills : parsed.data;
25664
+ return rawSkills.map(normalizeRemoteSkill);
25665
+ }
25666
+ function parseRemoteContract(schema, payload, message) {
25667
+ try {
25668
+ return schema.parse(payload);
25669
+ } catch (error2) {
25670
+ if (error2 instanceof exports_external.ZodError)
25671
+ throw new Error(message, { cause: error2 });
25672
+ throw error2;
25673
+ }
25674
+ }
25675
+ async function remoteRequestHeaders(options) {
25676
+ const headers = new Headers({ Accept: "application/json" });
25677
+ const token = options.authToken !== undefined ? options.authToken : await ambientTokenFor(options.apiUrl);
25678
+ const trimmed = token?.trim();
25679
+ if (trimmed)
25680
+ headers.set("Authorization", `Bearer ${trimmed}`);
25681
+ return headers;
25682
+ }
25683
+ async function ambientTokenFor(callerApiUrl) {
25684
+ const connection = await resolveSkillsConnection();
25685
+ if (!connection)
25686
+ return null;
25687
+ if (callerApiUrl !== undefined && normalizeSkillsApiOrigin(callerApiUrl) !== connection.apiOrigin) {
25688
+ throw new SkillsFleetCredentialError(`The Skills credential resolved for ${connection.apiOrigin} is never sent to a caller-supplied apiUrl ` + `(${normalizeSkillsApiOrigin(callerApiUrl)}). Pass an explicit authToken for that instance, or authToken: null ` + `for an unauthenticated read; no credential was sent.`, "INSTANCE_CREDENTIAL_MISMATCH");
25689
+ }
25690
+ return connection.apiKey;
25691
+ }
25692
+ async function fetchRemoteJson(url, options) {
25693
+ const fetchImpl = options.fetchImpl || fetch;
25694
+ const headers = await remoteRequestHeaders(options);
25695
+ const controller = new AbortController;
25696
+ const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 1e4);
25697
+ try {
25698
+ const response = await fetchImpl(url, {
25699
+ headers,
25700
+ signal: controller.signal
25701
+ });
25702
+ if (!response.ok) {
25703
+ throw new Error(`Remote registry request failed: ${response.status} ${response.statusText}`);
25704
+ }
25705
+ return response.json();
25706
+ } finally {
25707
+ clearTimeout(timeout);
25708
+ }
25709
+ }
25710
+ async function loadRemoteRegistry(options = {}) {
25711
+ const apiUrl = options.apiUrl || getConfiguredApiUrl();
25712
+ if (!apiUrl) {
25713
+ throw new Error(`Remote registry requires a Skills credential (${SKILLS_API_KEY_ENV}, the Keychain item, or ~/.hasna/skills/config/credentials) and, for your own instance, ${SKILLS_API_URL_ENV}`);
25714
+ }
25715
+ const url = buildSkillsApiUrl(apiUrl, options.endpoint);
25716
+ return parseRemoteRegistryPayload(await fetchRemoteJson(url, options));
25717
+ }
25718
+ async function mergeRemoteRegistry(local, options = {}) {
25719
+ const apiUrl = options.apiUrl || getConfiguredApiUrl();
25720
+ if (!apiUrl)
25721
+ return local;
25722
+ if (options.authToken !== undefined && !options.authToken?.trim())
25723
+ return local;
25724
+ const remote = await loadRemoteRegistry({ ...options, apiUrl });
25725
+ return mergeSkillRegistryLists(local, remote);
25726
+ }
25727
+
25728
+ // src/lib/read-access.ts
25729
+ async function requireSkillsReadAccess(env = process.env, options = {}) {
25730
+ const connection = await resolveSkillsConnection(env, options);
25731
+ return connection ? { mode: "hosted", apiOrigin: connection.apiOrigin } : { mode: "local" };
25732
+ }
25733
+ async function getBrowseRegistry(options = {}) {
25734
+ const profile = options.all ? "all" : "basic";
25735
+ const local = loadRegistryProfile(profile);
25736
+ if (options.remote) {
25737
+ const remote = await loadRemoteRegistry();
25738
+ return mergeSkillRegistryLists(local, remote);
25739
+ }
25740
+ return mergeRemoteRegistry(local);
25741
+ }
25742
+
24740
25743
  // src/lib/installer.ts
24741
- import { existsSync as existsSync10, readFileSync as readFileSync8, rmSync as rmSync2 } from "fs";
24742
- import { dirname as dirname4, join as join10 } from "path";
25744
+ import { existsSync as existsSync10, readFileSync as readFileSync9, rmSync as rmSync2 } from "fs";
25745
+ import { dirname as dirname4, join as join11 } from "path";
24743
25746
  import { homedir as homedir2 } from "os";
24744
25747
  import { fileURLToPath } from "url";
24745
25748
  // src/lib/utils.ts
@@ -24748,8 +25751,8 @@ function normalizeSkillName(name) {
24748
25751
  }
24749
25752
 
24750
25753
  // src/lib/project-state.ts
24751
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
24752
- import { join as join9 } from "path";
25754
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
25755
+ import { join as join10 } from "path";
24753
25756
  var VALID_PIN_SOURCES = [
24754
25757
  "official",
24755
25758
  "custom",
@@ -24764,17 +25767,17 @@ var SKILLS_PROJECT_DIR = ".skills";
24764
25767
  var PROJECT_CONFIG_FILE = "project.json";
24765
25768
  var DEFAULT_EXPORT_DIR = ".skills/exports";
24766
25769
  function getProjectStateDir(targetDir = process.cwd()) {
24767
- return join9(targetDir, SKILLS_PROJECT_DIR);
25770
+ return join10(targetDir, SKILLS_PROJECT_DIR);
24768
25771
  }
24769
25772
  function getProjectConfigPath(targetDir = process.cwd()) {
24770
- return join9(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
25773
+ return join10(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
24771
25774
  }
24772
25775
  function loadProjectConfig(targetDir = process.cwd()) {
24773
25776
  const path = getProjectConfigPath(targetDir);
24774
25777
  if (!existsSync9(path))
24775
25778
  return null;
24776
25779
  try {
24777
- return normalizeProjectConfig(JSON.parse(readFileSync7(path, "utf-8")));
25780
+ return normalizeProjectConfig(JSON.parse(readFileSync8(path, "utf-8")));
24778
25781
  } catch {
24779
25782
  return null;
24780
25783
  }
@@ -24873,12 +25876,12 @@ var __dirname2 = dirname4(fileURLToPath(import.meta.url));
24873
25876
  function findSkillsDir() {
24874
25877
  let dir = __dirname2;
24875
25878
  for (let i = 0;i < 5; i++) {
24876
- const candidate = join10(dir, "skills");
25879
+ const candidate = join11(dir, "skills");
24877
25880
  if (existsSync10(candidate) && !dir.includes(".skills"))
24878
25881
  return candidate;
24879
25882
  dir = dirname4(dir);
24880
25883
  }
24881
- return join10(__dirname2, "..", "skills");
25884
+ return join11(__dirname2, "..", "skills");
24882
25885
  }
24883
25886
  var SKILLS_DIR = findSkillsDir();
24884
25887
  function getSkillPath(name) {
@@ -24886,13 +25889,13 @@ function getSkillPath(name) {
24886
25889
  const portable = findPortableSkill(skillName);
24887
25890
  if (portable)
24888
25891
  return portable.path;
24889
- const legacyCustomPath = join10(getDataDir(), "custom", skillName);
25892
+ const legacyCustomPath = join11(getDataDir(), "custom", skillName);
24890
25893
  if (existsSync10(legacyCustomPath))
24891
25894
  return legacyCustomPath;
24892
25895
  const extensionPath = findExtensionSkillPath(skillName);
24893
25896
  if (extensionPath)
24894
25897
  return extensionPath;
24895
- return join10(SKILLS_DIR, skillName);
25898
+ return join11(SKILLS_DIR, skillName);
24896
25899
  }
24897
25900
  function getCanonicalSkillName(name) {
24898
25901
  return getSkill(name)?.name ?? resolveSkillAlias(normalizeSkillSlug(name));
@@ -24956,11 +25959,11 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
24956
25959
  const base = projectDir || process.cwd();
24957
25960
  switch (agent) {
24958
25961
  case "pi":
24959
- return scope === "project" ? join10(base, ".pi", "skills") : join10(homedir2(), ".pi", "agent", "skills");
25962
+ return scope === "project" ? join11(base, ".pi", "skills") : join11(homedir2(), ".pi", "agent", "skills");
24960
25963
  case "opencode":
24961
- return scope === "project" ? join10(base, ".opencode", "skills") : join10(homedir2(), ".config", "opencode", "skills");
25964
+ return scope === "project" ? join11(base, ".opencode", "skills") : join11(homedir2(), ".config", "opencode", "skills");
24962
25965
  default:
24963
- return scope === "project" ? join10(base, `.${agent}`, "skills") : join10(homedir2(), `.${agent}`, "skills");
25966
+ return scope === "project" ? join11(base, `.${agent}`, "skills") : join11(homedir2(), `.${agent}`, "skills");
24964
25967
  }
24965
25968
  }
24966
25969
  function warnMissingDependencies(name, targetDir) {
@@ -24975,11 +25978,11 @@ function warnMissingDependencies(name, targetDir) {
24975
25978
  }
24976
25979
  }
24977
25980
  function readBundledSkillVersion(name) {
24978
- const pkgPath = join10(getSkillPath(name), "package.json");
25981
+ const pkgPath = join11(getSkillPath(name), "package.json");
24979
25982
  if (!existsSync10(pkgPath))
24980
25983
  return "unknown";
24981
25984
  try {
24982
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
25985
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
24983
25986
  return pkg.version || "unknown";
24984
25987
  } catch {
24985
25988
  return "unknown";
@@ -24987,16 +25990,16 @@ function readBundledSkillVersion(name) {
24987
25990
  }
24988
25991
 
24989
25992
  // src/lib/skillinfo.ts
24990
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
24991
- import { join as join11 } from "path";
25993
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
25994
+ import { join as join12 } from "path";
24992
25995
  function isInstructionSkillDir(skillPath, meta) {
24993
25996
  if (meta?.kind === "instruction")
24994
25997
  return true;
24995
- const skillMdPath = join11(skillPath, "SKILL.md");
25998
+ const skillMdPath = join12(skillPath, "SKILL.md");
24996
25999
  if (!existsSync11(skillMdPath))
24997
26000
  return false;
24998
26001
  try {
24999
- return parseSkillFrontmatter(readFileSync9(skillMdPath, "utf-8"))?.kind === "instruction";
26002
+ return parseSkillFrontmatter(readFileSync10(skillMdPath, "utf-8"))?.kind === "instruction";
25000
26003
  } catch {
25001
26004
  return false;
25002
26005
  }
@@ -25022,9 +26025,9 @@ function getSkillDocs(name) {
25022
26025
  if (!existsSync11(skillPath))
25023
26026
  return null;
25024
26027
  return {
25025
- skillMd: readIfExists(join11(skillPath, "SKILL.md")),
25026
- readme: readIfExists(join11(skillPath, "README.md")),
25027
- claudeMd: readIfExists(join11(skillPath, "CLAUDE.md"))
26028
+ skillMd: readIfExists(join12(skillPath, "SKILL.md")),
26029
+ readme: readIfExists(join12(skillPath, "README.md")),
26030
+ claudeMd: readIfExists(join12(skillPath, "CLAUDE.md"))
25028
26031
  };
25029
26032
  }
25030
26033
  function getSkillBestDoc(name) {
@@ -25039,7 +26042,7 @@ function getSkillRequirements(name) {
25039
26042
  return null;
25040
26043
  const texts = [];
25041
26044
  for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
25042
- const content = readIfExists(join11(skillPath, file));
26045
+ const content = readIfExists(join12(skillPath, file));
25043
26046
  if (content)
25044
26047
  texts.push(content);
25045
26048
  }
@@ -25079,10 +26082,10 @@ function getSkillRequirements(name) {
25079
26082
  const skillName = normalizeSkillName(name);
25080
26083
  let cliCommand = `skills run ${skillName}`;
25081
26084
  let dependencies = {};
25082
- const pkgPath = join11(skillPath, "package.json");
26085
+ const pkgPath = join12(skillPath, "package.json");
25083
26086
  if (existsSync11(pkgPath)) {
25084
26087
  try {
25085
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
26088
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
25086
26089
  dependencies = pkg.dependencies || {};
25087
26090
  } catch {}
25088
26091
  }
@@ -25109,13 +26112,13 @@ async function runSkill(name, args, options = {}) {
25109
26112
  error: `Skill '${name}' is an instruction skill (kind: instruction) and is not runnable. Instruction skills are consumed by coding agents via SKILL.md, not executed with 'skills run'.`
25110
26113
  };
25111
26114
  }
25112
- const pkgPath = join11(skillPath, "package.json");
26115
+ const pkgPath = join12(skillPath, "package.json");
25113
26116
  if (!existsSync11(pkgPath)) {
25114
26117
  return { exitCode: 1, error: `No package.json in skill '${name}'` };
25115
26118
  }
25116
26119
  let entryPoint;
25117
26120
  try {
25118
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
26121
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
25119
26122
  if (pkg.bin) {
25120
26123
  const binValues = Object.values(pkg.bin);
25121
26124
  entryPoint = binValues[0];
@@ -25129,11 +26132,11 @@ async function runSkill(name, args, options = {}) {
25129
26132
  } catch {
25130
26133
  return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
25131
26134
  }
25132
- const entryPath = join11(skillPath, entryPoint);
26135
+ const entryPath = join12(skillPath, entryPoint);
25133
26136
  if (!existsSync11(entryPath)) {
25134
26137
  return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
25135
26138
  }
25136
- const nodeModules = join11(skillPath, "node_modules");
26139
+ const nodeModules = join12(skillPath, "node_modules");
25137
26140
  if (!existsSync11(nodeModules)) {
25138
26141
  const install = Bun.spawn(["bun", "install", "--no-save"], {
25139
26142
  cwd: skillPath,
@@ -25144,7 +26147,7 @@ async function runSkill(name, args, options = {}) {
25144
26147
  }
25145
26148
  const proc = Bun.spawn(["bun", "run", entryPath, ...args], {
25146
26149
  cwd: skillPath,
25147
- stdout: options.stdio === "pipe" ? "pipe" : "inherit",
26150
+ stdout: options.stdio === "pipe" ? "pipe" : options.stdio === "stderr" ? 2 : "inherit",
25148
26151
  stderr: options.stdio === "pipe" ? "pipe" : "inherit",
25149
26152
  stdin: "inherit",
25150
26153
  env: { ...process.env, ...options.env }
@@ -25161,7 +26164,7 @@ async function runSkill(name, args, options = {}) {
25161
26164
  return { exitCode };
25162
26165
  }
25163
26166
  function detectProjectSkills(cwd = process.cwd()) {
25164
- const pkgPath = join11(cwd, "package.json");
26167
+ const pkgPath = join12(cwd, "package.json");
25165
26168
  if (!existsSync11(pkgPath)) {
25166
26169
  const alwaysRecommend = ["market-research-report", "repo-onboarding-report", "blog-article"];
25167
26170
  const recommended2 = alwaysRecommend.map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
@@ -25169,7 +26172,7 @@ function detectProjectSkills(cwd = process.cwd()) {
25169
26172
  }
25170
26173
  let pkg;
25171
26174
  try {
25172
- pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
26175
+ pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
25173
26176
  } catch {
25174
26177
  const alwaysRecommend = ["market-research-report", "repo-onboarding-report", "blog-article"];
25175
26178
  const recommended2 = alwaysRecommend.map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
@@ -25259,12 +26262,30 @@ function extractEnvVars(text) {
25259
26262
  function readIfExists(path) {
25260
26263
  try {
25261
26264
  if (existsSync11(path)) {
25262
- return readFileSync9(path, "utf-8");
26265
+ return readFileSync10(path, "utf-8");
25263
26266
  }
25264
26267
  } catch {}
25265
26268
  return null;
25266
26269
  }
25267
26270
 
26271
+ // src/lib/remote-customer-operations.ts
26272
+ var REMOTE_CUSTOMER_OPERATIONS = [
26273
+ { name: "get_account", title: "Get Account Identity", parameter: null, mutates: false, invoke: (client) => client.getIdentity() },
26274
+ { name: "get_server_capabilities", title: "Get Server Capabilities", parameter: null, mutates: false, invoke: (client) => client.getCapabilities() },
26275
+ { name: "list_remote_skills", title: "List Remote Skills", parameter: null, mutates: false, invoke: (client) => client.listSkills() },
26276
+ { name: "get_billing_status", title: "Get Billing Status", parameter: null, mutates: false, invoke: (client) => client.getBillingStatus() },
26277
+ { name: "list_credit_packs", title: "List Credit Packs", parameter: null, mutates: false, invoke: (client) => client.listCreditPacks() },
26278
+ { name: "create_credit_checkout", title: "Create Credit Checkout", parameter: "pack_id", mutates: true, invoke: (client, value) => client.createCreditCheckout(value) },
26279
+ { name: "get_billing_usage", title: "Get Billing Usage", parameter: null, mutates: false, invoke: (client) => client.getUsage() },
26280
+ { name: "list_invoices", title: "List Invoices", parameter: null, mutates: false, invoke: (client) => client.listInvoices() },
26281
+ { name: "create_billing_checkout", title: "Create Billing Checkout", parameter: null, mutates: true, invoke: (client) => client.createBillingCheckout() },
26282
+ { name: "create_billing_portal", title: "Create Billing Portal", parameter: null, mutates: true, invoke: (client) => client.createBillingPortal() },
26283
+ { name: "get_run_logs", title: "Get Run Logs", parameter: "run_id", mutates: false, invoke: (client, value) => client.getRunLogs(value) },
26284
+ { name: "cancel_run", title: "Cancel Run", parameter: "run_id", mutates: true, invoke: (client, value) => client.cancelRun(value) },
26285
+ { name: "resume_run", title: "Resume Run", parameter: "run_id", mutates: true, invoke: (client, value) => client.resumeRun(value) },
26286
+ { name: "list_run_artifacts", title: "List Run Artifacts", parameter: "run_id", mutates: false, invoke: (client, value) => client.getRunArtifacts(value) }
26287
+ ];
26288
+
25268
26289
  // src/lib/mcp-contracts.ts
25269
26290
  var MCP_CONTRACT_SCHEMA_VERSION = 1;
25270
26291
  var stringSchema = (description) => ({
@@ -25648,19 +26669,150 @@ var toolContracts = [
25648
26669
  category: "metadata",
25649
26670
  sideEffects: "none",
25650
26671
  stable: true,
25651
- inputSchema: objectSchema({ name: skillNameInput }, ["name"]),
25652
- outputSchema: objectSchema({
25653
- envVars: stringArraySchema("Environment variable names."),
25654
- systemDeps: stringArraySchema("System dependency names."),
25655
- cliCommand: stringSchema("Preferred CLI command."),
25656
- dependencies: objectSchema({}, [], "Package dependencies.", true)
25657
- })
26672
+ inputSchema: objectSchema({ name: skillNameInput }, ["name"]),
26673
+ outputSchema: objectSchema({
26674
+ envVars: stringArraySchema("Environment variable names."),
26675
+ systemDeps: stringArraySchema("System dependency names."),
26676
+ cliCommand: stringSchema("Preferred CLI command."),
26677
+ dependencies: objectSchema({}, [], "Package dependencies.", true)
26678
+ })
26679
+ },
26680
+ {
26681
+ name: "update_account_profile",
26682
+ title: "Update Account Display Name",
26683
+ description: "Update your display name with fresh email verification on the configured server.",
26684
+ params: ["name", "email", "code"],
26685
+ category: "execution",
26686
+ sideEffects: "local-process-or-remote-run",
26687
+ stable: true,
26688
+ inputSchema: objectSchema({ name: stringSchema("Display name, 1\u2013100 characters"), email: { type: "string", format: "email" }, code: { type: "string", pattern: "^\\d{6}$" } }, ["name", "email", "code"]),
26689
+ outputSchema: objectSchema({ user: objectSchema({ id: stringSchema("Account identifier."), email: stringSchema("Account email."), displayName: stringSchema("Display name."), role: stringSchema("Current workspace role.") }, ["id", "email", "displayName", "role"]) }, ["user"])
26690
+ },
26691
+ {
26692
+ name: "update_workspace_name",
26693
+ title: "Update Workspace Name",
26694
+ description: "Update the current workspace name as an owner/admin with fresh email verification.",
26695
+ params: ["name", "email", "code"],
26696
+ category: "execution",
26697
+ sideEffects: "local-process-or-remote-run",
26698
+ stable: true,
26699
+ inputSchema: objectSchema({ name: stringSchema("Workspace name, 1\u2013100 characters"), email: { type: "string", format: "email" }, code: { type: "string", pattern: "^\\d{6}$" } }, ["name", "email", "code"]),
26700
+ outputSchema: objectSchema({ organization: objectSchema({ id: stringSchema("Workspace identifier."), slug: stringSchema("Stable workspace slug."), name: stringSchema("Workspace name.") }, ["id", "slug", "name"]) }, ["organization"])
26701
+ },
26702
+ {
26703
+ name: "set_workspace_member_role",
26704
+ title: "Set Current Workspace Member Role",
26705
+ description: "Set an exact membership incarnation's role with its observed expectedRole and fresh verification; no automatic retry.",
26706
+ params: ["membershipId", "role", "expectedRole", "email", "code"],
26707
+ category: "execution",
26708
+ sideEffects: "local-process-or-remote-run",
26709
+ stable: true,
26710
+ inputSchema: objectSchema({
26711
+ membershipId: { type: "string", pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$" },
26712
+ role: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
26713
+ expectedRole: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
26714
+ email: { type: "string", format: "email" },
26715
+ code: { type: "string", pattern: "^\\d{6}$" }
26716
+ }, ["membershipId", "role", "expectedRole", "email", "code"]),
26717
+ outputSchema: objectSchema({
26718
+ organizationId: stringSchema("Current workspace identifier."),
26719
+ changed: { type: "boolean" },
26720
+ member: objectSchema({
26721
+ membershipId: stringSchema("Membership incarnation."),
26722
+ userId: stringSchema("Account identifier."),
26723
+ email: stringSchema("Member email."),
26724
+ displayName: { oneOf: [{ type: "string" }, { type: "null" }] },
26725
+ role: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
26726
+ createdAt: stringSchema("Exact server timestamp including microseconds.")
26727
+ }, ["membershipId", "userId", "email", "displayName", "role", "createdAt"])
26728
+ }, ["organizationId", "member", "changed"])
26729
+ },
26730
+ {
26731
+ name: "remove_workspace_member",
26732
+ title: "Remove Current Workspace Member",
26733
+ description: "Remove exactly this membership incarnation using its observed expectedRole and fresh verification; self-removal is unavailable.",
26734
+ params: ["membershipId", "expectedRole", "email", "code"],
26735
+ category: "execution",
26736
+ sideEffects: "local-process-or-remote-run",
26737
+ stable: true,
26738
+ inputSchema: objectSchema({
26739
+ membershipId: { type: "string", pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$" },
26740
+ expectedRole: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
26741
+ email: { type: "string", format: "email" },
26742
+ code: { type: "string", pattern: "^\\d{6}$" }
26743
+ }, ["membershipId", "expectedRole", "email", "code"]),
26744
+ outputSchema: objectSchema({
26745
+ organizationId: stringSchema("Current workspace identifier."),
26746
+ membershipId: stringSchema("Removed membership incarnation."),
26747
+ removed: { type: "boolean", const: true },
26748
+ alreadyRemoved: { type: "boolean" }
26749
+ }, ["organizationId", "membershipId", "removed", "alreadyRemoved"])
26750
+ },
26751
+ {
26752
+ name: "list_workspace_members",
26753
+ title: "List Current Workspace Members",
26754
+ description: "Read one current-workspace roster page with fresh owner/admin email verification; saved credentials stay unchanged.",
26755
+ params: ["email", "code", "limit?", "cursor?"],
26756
+ category: "execution",
26757
+ sideEffects: "local-process-or-remote-run",
26758
+ stable: true,
26759
+ inputSchema: objectSchema({
26760
+ email: { type: "string", format: "email" },
26761
+ code: { type: "string", pattern: "^\\d{6}$" },
26762
+ limit: { type: "integer", minimum: 1, maximum: 100 },
26763
+ cursor: { type: "string", pattern: "^[A-Za-z0-9_-]{1,512}$" }
26764
+ }, ["email", "code"]),
26765
+ outputSchema: objectSchema({
26766
+ organizationId: stringSchema("Current workspace identifier."),
26767
+ members: arraySchema(objectSchema({
26768
+ membershipId: stringSchema("Membership incarnation."),
26769
+ userId: stringSchema("Account identifier."),
26770
+ email: stringSchema("Member email."),
26771
+ displayName: { oneOf: [{ type: "string" }, { type: "null" }] },
26772
+ role: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
26773
+ createdAt: stringSchema("Exact server timestamp including microseconds.")
26774
+ }, ["membershipId", "userId", "email", "displayName", "role", "createdAt"])),
26775
+ nextCursor: { oneOf: [{ type: "string" }, { type: "null" }] }
26776
+ }, ["organizationId", "members", "nextCursor"])
26777
+ },
26778
+ {
26779
+ name: "list_api_keys",
26780
+ title: "List API Keys",
26781
+ description: "List keys using fresh email OTP reauthentication.",
26782
+ params: ["email", "code"],
26783
+ category: "execution",
26784
+ sideEffects: "local-process-or-remote-run",
26785
+ stable: true,
26786
+ inputSchema: objectSchema({ email: { type: "string", format: "email" }, code: { type: "string", pattern: "^\\d{6}$" } }, ["email", "code"]),
26787
+ outputSchema: arraySchema(objectSchema({}, [], "API key metadata", true))
26788
+ },
26789
+ {
26790
+ name: "revoke_api_key",
26791
+ title: "Revoke API Key",
26792
+ description: "Revoke a key using fresh email OTP reauthentication.",
26793
+ params: ["key_id", "email", "code"],
26794
+ category: "execution",
26795
+ sideEffects: "local-process-or-remote-run",
26796
+ stable: true,
26797
+ inputSchema: objectSchema({ key_id: stringSchema("API key ID"), email: { type: "string", format: "email" }, code: { type: "string", pattern: "^\\d{6}$" } }, ["key_id", "email", "code"]),
26798
+ outputSchema: objectSchema({}, [], "Revocation result", true)
26799
+ },
26800
+ {
26801
+ name: "create_api_key",
26802
+ title: "Create API Key",
26803
+ description: "Create a key with fresh email OTP reauthentication; returns the secret once.",
26804
+ params: ["name", "email", "code", "scopes?"],
26805
+ category: "execution",
26806
+ sideEffects: "local-process-or-remote-run",
26807
+ stable: true,
26808
+ inputSchema: objectSchema({ name: stringSchema("Key name"), email: { type: "string", format: "email" }, code: { type: "string", pattern: "^\\d{6}$" }, scopes: arraySchema(stringSchema("Scope")) }, ["name", "email", "code"]),
26809
+ outputSchema: objectSchema({}, [], "Created key and one-time secret", true)
25658
26810
  },
25659
26811
  {
25660
26812
  name: "run_skill",
25661
26813
  title: "Run Skill",
25662
26814
  description: "Run a skill locally or through a configured remote runner. Returns compact stdout/stderr previews and run summaries by default; pass detail:true for full records.",
25663
- params: ["name", "input?", "args?", "detail?"],
26815
+ params: ["name", "input?", "args?", "detail?", "remote?", "maxCredits?", "maxCostCents?", "idempotency_key?", "files?"],
25664
26816
  category: "execution",
25665
26817
  sideEffects: "local-process-or-remote-run",
25666
26818
  stable: true,
@@ -25668,7 +26820,12 @@ var toolContracts = [
25668
26820
  name: skillNameInput,
25669
26821
  input: runInputSchema,
25670
26822
  args: runArgsSchema,
25671
- detail: { type: "boolean", default: false, description: "Return full stdout/stderr, remote run, and local run metadata." }
26823
+ detail: { type: "boolean", default: false, description: "Return full stdout/stderr, remote run, and local run metadata." },
26824
+ remote: { type: "boolean", description: "Use the configured server catalog." },
26825
+ maxCredits: { type: "integer", minimum: 0, description: "Maximum explicitly approved integer credits; omitted permits only free remote runs." },
26826
+ maxCostCents: { type: "integer", minimum: 0, description: "Legacy alias for maxCredits; both must agree." },
26827
+ idempotency_key: { type: "string", pattern: "^[A-Za-z0-9._:-]{1,128}$", description: "Stable retry key for the same remote submission." },
26828
+ files: { type: "array", maxItems: 10, items: objectSchema({ name: stringSchema("Safe basename"), base64: { type: "string", maxLength: 1398104 }, contentType: stringSchema("MIME type") }, ["name", "base64"]), description: "Inline remote inputs, at most 1 MiB combined." }
25672
26829
  }, ["name"]),
25673
26830
  outputSchema: runOutputSchema
25674
26831
  },
@@ -25935,7 +27092,40 @@ var toolContracts = [
25935
27092
  outputSchema: objectSchema({}, [], "Feedback save result.", true)
25936
27093
  }
25937
27094
  ];
25938
- var contracts = [...toolContracts].sort((a, b) => a.name.localeCompare(b.name));
27095
+ var remoteCustomerContracts = REMOTE_CUSTOMER_OPERATIONS.map((operation) => ({
27096
+ name: operation.name,
27097
+ title: operation.title,
27098
+ description: `${operation.title} on the configured server; unavailable capabilities fail explicitly.`,
27099
+ params: operation.parameter ? [operation.parameter] : [],
27100
+ category: "execution",
27101
+ sideEffects: operation.mutates ? "local-process-or-remote-run" : "none",
27102
+ stable: true,
27103
+ inputSchema: objectSchema(operation.parameter ? { [operation.parameter]: stringSchema("Server resource identifier.") } : {}, operation.parameter ? [operation.parameter] : []),
27104
+ outputSchema: { oneOf: [objectSchema({}, [], "Server response.", true), { type: "array", items: objectSchema({}, [], "Server record.", true) }] }
27105
+ }));
27106
+ remoteCustomerContracts.push({
27107
+ name: "quote_skill",
27108
+ title: "Quote Remote Skill",
27109
+ description: "Get a server credit quote without submitting a run.",
27110
+ params: ["name", "input?", "args?"],
27111
+ category: "execution",
27112
+ sideEffects: "none",
27113
+ stable: true,
27114
+ inputSchema: objectSchema({ name: skillNameInput, input: runInputSchema, args: runArgsSchema }, ["name"]),
27115
+ outputSchema: objectSchema({ skill: stringSchema("Canonical server skill."), pricing: objectSchema({}, [], "Quoted integer credits.", true) }, ["skill", "pricing"], undefined, true)
27116
+ });
27117
+ remoteCustomerContracts.push({
27118
+ name: "download_run_artifact",
27119
+ title: "Download Verified Run Artifact",
27120
+ description: "Return verified artifact bytes as base64, bounded to 1 MiB.",
27121
+ params: ["run_id", "artifact_id"],
27122
+ category: "execution",
27123
+ sideEffects: "none",
27124
+ stable: true,
27125
+ inputSchema: objectSchema({ run_id: stringSchema("Run identifier."), artifact_id: stringSchema("Artifact identifier.") }, ["run_id", "artifact_id"]),
27126
+ outputSchema: objectSchema({ id: stringSchema("Artifact identifier."), fileName: stringSchema("Artifact file name."), base64: stringSchema("Verified bytes."), sha256: stringSchema("SHA256 digest."), byteSize: { type: "integer", minimum: 0 } }, ["id", "fileName", "base64", "sha256", "byteSize"])
27127
+ });
27128
+ var contracts = [...toolContracts, ...remoteCustomerContracts].sort((a, b) => a.name.localeCompare(b.name));
25939
27129
  var resourceContracts = [
25940
27130
  {
25941
27131
  uri: "skills://mcp/contracts",
@@ -26094,90 +27284,6 @@ function clone2(value) {
26094
27284
  return JSON.parse(JSON.stringify(value));
26095
27285
  }
26096
27286
 
26097
- // src/lib/discovery.ts
26098
- var VENDOR_TERMS = [
26099
- "Google Gemini",
26100
- "OpenAI Sora",
26101
- "MiniMax Hailuo",
26102
- "Claude Code",
26103
- "Claude Vision",
26104
- "DALL-E 3",
26105
- "GPT-4o Mini",
26106
- "Cerebras",
26107
- "OpenRouter",
26108
- "Firecrawl",
26109
- "ElevenLabs",
26110
- "Anthropic",
26111
- "OpenAI",
26112
- "Minimax",
26113
- "MiniMax",
26114
- "Gemini",
26115
- "Claude",
26116
- "Whisper",
26117
- "Seedance",
26118
- "Lyria",
26119
- "Sora",
26120
- "Veo",
26121
- "Exa.ai",
26122
- "Exa",
26123
- "XAI",
26124
- "xAI"
26125
- ];
26126
- var VENDOR_TAGS = new Set([
26127
- "anthropic",
26128
- "cerebras",
26129
- "claude",
26130
- "exa",
26131
- "firecrawl",
26132
- "gemini",
26133
- "google",
26134
- "minimax",
26135
- "openai",
26136
- "openrouter",
26137
- "seedance",
26138
- "whisper",
26139
- "xai"
26140
- ]);
26141
- var vendorPattern = new RegExp(`\\b(${VENDOR_TERMS.map(escapeRegExp).join("|")})\\b`, "gi");
26142
- function getCompactSkillDiscovery(skill) {
26143
- return {
26144
- name: skill.name,
26145
- category: skill.category,
26146
- description: sanitizePublicDiscoveryText(skill.description)
26147
- };
26148
- }
26149
- function getPublicSkillDiscovery(skill) {
26150
- return {
26151
- ...skill,
26152
- description: sanitizePublicDiscoveryText(skill.description),
26153
- tags: publicDiscoveryTags(skill.tags)
26154
- };
26155
- }
26156
- function publicDiscoveryTags(tags) {
26157
- return tags.filter((tag) => !VENDOR_TAGS.has(tag.toLowerCase()));
26158
- }
26159
- function sanitizePublicDiscoveryText(text) {
26160
- let sanitized = text.replace(vendorPattern, "hosted AI").replace(/\bLLM\b/g, "AI").replace(/\s{2,}/g, " ");
26161
- let previous;
26162
- do {
26163
- previous = sanitized;
26164
- 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");
26165
- } while (sanitized !== previous);
26166
- return sanitized.trim();
26167
- }
26168
- function publicDiscoveryEnvVars(_skillName, envVars) {
26169
- return envVars;
26170
- }
26171
- function publicDiscoveryDependencies(_skillName, dependencies) {
26172
- return dependencies;
26173
- }
26174
- function publicDiscoveryDocumentation(_skill, documentation) {
26175
- return documentation;
26176
- }
26177
- function escapeRegExp(value) {
26178
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26179
- }
26180
-
26181
27287
  // src/lib/tool-primitives.ts
26182
27288
  var TOOL_PRIMITIVE_SCHEMA_VERSION = 1;
26183
27289
  var TOOL_PRIMITIVES = [
@@ -26636,22 +27742,20 @@ function compactRemoteRun(run) {
26636
27742
  }
26637
27743
 
26638
27744
  // src/mcp/helpers.ts
27745
+ init_fleet_credentials();
27746
+ async function readSurface(body) {
27747
+ try {
27748
+ return await body();
27749
+ } catch (error2) {
27750
+ if (isSkillsFleetCredentialError(error2))
27751
+ return mcpError("AUTH_REQUIRED", error2.message, ["skills auth login"]);
27752
+ throw error2;
27753
+ }
27754
+ }
26639
27755
  function stripNulls(obj) {
26640
27756
  return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== null && v !== undefined && !(Array.isArray(v) && v.length === 0)));
26641
27757
  }
26642
27758
  var searchCache = new Map;
26643
- var CACHE_MAX = 100;
26644
- function cacheGet(key) {
26645
- return searchCache.get(key);
26646
- }
26647
- function cacheSet(key, value) {
26648
- if (searchCache.size >= CACHE_MAX) {
26649
- const first = searchCache.keys().next().value;
26650
- if (first !== undefined)
26651
- searchCache.delete(first);
26652
- }
26653
- searchCache.set(key, value);
26654
- }
26655
27759
  function cacheClear() {
26656
27760
  searchCache.clear();
26657
27761
  }
@@ -26691,9 +27795,10 @@ function registerDiscoveryTools(server) {
26691
27795
  limit: exports_external.number().optional(),
26692
27796
  offset: exports_external.number().optional()
26693
27797
  }
26694
- }, async ({ category, profile, detail, limit, offset }) => {
27798
+ }, async ({ category, profile, detail, limit, offset }) => readSurface(async () => {
26695
27799
  const selectedProfile = profile || "basic";
26696
- const skills = category ? loadRegistryProfile(selectedProfile).filter((s) => s.category === category) : loadRegistryProfile(selectedProfile);
27800
+ const registry2 = await getBrowseRegistry({ all: selectedProfile === "all" });
27801
+ const skills = category ? registry2.filter((s) => s.category === category) : registry2;
26697
27802
  const mapped = detail ? skills.map(getPublicSkillDiscovery) : skills.map(getCompactSkillDiscovery);
26698
27803
  const page = paginate(mapped, {
26699
27804
  limit: parsePageLimit(limit, DEFAULT_MCP_LIMIT, { max: 100 }),
@@ -26709,7 +27814,7 @@ function registerDiscoveryTools(server) {
26709
27814
  nextArguments: page.hasMore ? { profile: selectedProfile, category, detail: Boolean(detail), limit: page.limit, offset: page.nextOffset } : null,
26710
27815
  detailHint: detail ? undefined : "Set detail:true for full public skill objects, or call get_skill_info for one skill."
26711
27816
  });
26712
- });
27817
+ }));
26713
27818
  server.registerTool("list_pinned_skills", {
26714
27819
  title: "List Pinned Skills",
26715
27820
  description: "List skills pinned in the current project's .skills/project.json.",
@@ -26733,13 +27838,9 @@ function registerDiscoveryTools(server) {
26733
27838
  limit: exports_external.number().optional(),
26734
27839
  offset: exports_external.number().optional()
26735
27840
  }
26736
- }, async ({ query, profile, detail, limit, offset }) => {
27841
+ }, async ({ query, profile, detail, limit, offset }) => readSurface(async () => {
26737
27842
  const selectedProfile = profile || "basic";
26738
- const cacheKey = `${selectedProfile}:${query}:${detail ?? false}`;
26739
- const cached2 = cacheGet(cacheKey);
26740
- const results = cached2 ? cached2 : searchSkills(query, loadRegistryProfile(selectedProfile));
26741
- if (!cached2)
26742
- cacheSet(cacheKey, results);
27843
+ const results = searchSkills(query, await getBrowseRegistry({ all: selectedProfile === "all" }));
26743
27844
  const out = detail ? results.map(getPublicSkillDiscovery) : results.map(getCompactSkillDiscovery);
26744
27845
  const page = paginate(out, {
26745
27846
  limit: parsePageLimit(limit, DEFAULT_MCP_LIMIT, { max: 100 }),
@@ -26755,14 +27856,15 @@ function registerDiscoveryTools(server) {
26755
27856
  nextArguments: page.hasMore ? { query, profile: selectedProfile, detail: Boolean(detail), limit: page.limit, offset: page.nextOffset } : null,
26756
27857
  detailHint: detail ? undefined : "Set detail:true for full public skill objects, or call get_skill_info for one skill."
26757
27858
  });
26758
- });
27859
+ }));
26759
27860
  server.registerTool("get_skill_info", {
26760
27861
  title: "Get Skill Info",
26761
27862
  description: "Get skill metadata, env vars, and dependencies.",
26762
27863
  inputSchema: {
26763
27864
  name: exports_external.string()
26764
27865
  }
26765
- }, async ({ name }) => {
27866
+ }, async ({ name }) => readSurface(async () => {
27867
+ await requireSkillsReadAccess();
26766
27868
  const skill = getSkill(name);
26767
27869
  if (!skill) {
26768
27870
  return mcpError("SKILL_NOT_FOUND", `Skill '${name}' not found`, findSimilarSkills(name));
@@ -26782,20 +27884,21 @@ function registerDiscoveryTools(server) {
26782
27884
  return {
26783
27885
  content: [{ type: "text", text: JSON.stringify(result) }]
26784
27886
  };
26785
- });
27887
+ }));
26786
27888
  server.registerTool("get_skill_docs", {
26787
27889
  title: "Get Skill Docs",
26788
27890
  description: "Get skill documentation (SKILL.md > README.md > CLAUDE.md).",
26789
27891
  inputSchema: {
26790
27892
  name: exports_external.string()
26791
27893
  }
26792
- }, async ({ name }) => {
27894
+ }, async ({ name }) => readSurface(async () => {
27895
+ await requireSkillsReadAccess();
26793
27896
  const doc2 = getSkillBestDoc(name);
26794
27897
  if (!doc2) {
26795
27898
  return mcpError("NO_DOCS", `No documentation found for '${name}'`);
26796
27899
  }
26797
27900
  return { content: [{ type: "text", text: doc2 }] };
26798
- });
27901
+ }));
26799
27902
  server.registerTool("list_tool_primitives", {
26800
27903
  title: "List Tool Primitives",
26801
27904
  description: "List primitive tools that skills depend on across CLI, MCP, API, and hosted worker execution.",
@@ -26841,25 +27944,74 @@ function registerDiscoveryTools(server) {
26841
27944
  }
26842
27945
 
26843
27946
  // src/mcp/operation-tools.ts
26844
- import { existsSync as existsSync13, readdirSync as readdirSync8, statSync as statSync8 } from "fs";
27947
+ import { existsSync as existsSync14, readdirSync as readdirSync8, statSync as statSync9 } from "fs";
26845
27948
  import { join as join14 } from "path";
26846
27949
 
27950
+ // src/lib/credential-state.ts
27951
+ init_auth_store();
27952
+ init_fleet_credentials();
27953
+ function describeCredentialState() {
27954
+ let credentialsFile = null;
27955
+ let mode = null;
27956
+ try {
27957
+ credentialsFile = getAuthFilePath();
27958
+ const bits = credentialFileMode();
27959
+ mode = bits === null ? null : `0${bits.toString(8).padStart(3, "0")}`;
27960
+ } catch {}
27961
+ try {
27962
+ const fleet = resolveSkillsFleet();
27963
+ if (fleet.mode === "hosted") {
27964
+ return {
27965
+ mode: "hosted",
27966
+ apiUrl: fleet.apiOrigin,
27967
+ apiUrlSource: fleet.apiUrlSource,
27968
+ apiKeySource: fleet.apiKeySource,
27969
+ apiKeyTier: fleet.apiKeyTier,
27970
+ credentialsFile,
27971
+ credentialsFileMode: mode,
27972
+ error: null
27973
+ };
27974
+ }
27975
+ return {
27976
+ mode: "local",
27977
+ apiUrl: null,
27978
+ apiUrlSource: null,
27979
+ apiKeySource: null,
27980
+ apiKeyTier: null,
27981
+ credentialsFile,
27982
+ credentialsFileMode: mode,
27983
+ error: null
27984
+ };
27985
+ } catch (error2) {
27986
+ return {
27987
+ mode: "misconfigured",
27988
+ apiUrl: null,
27989
+ apiUrlSource: null,
27990
+ apiKeySource: null,
27991
+ apiKeyTier: null,
27992
+ credentialsFile,
27993
+ credentialsFileMode: mode,
27994
+ error: error2.message
27995
+ };
27996
+ }
27997
+ }
27998
+
26847
27999
  // src/lib/run-state.ts
26848
28000
  import { createHash as createHash2, randomBytes } from "crypto";
26849
- import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync10, readdirSync as readdirSync7, statSync as statSync7, writeFileSync as writeFileSync5 } from "fs";
26850
- import { extname, join as join12, relative as relative2 } from "path";
28001
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync12, readdirSync as readdirSync7, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
28002
+ import { extname, join as join13, relative as relative2 } from "path";
26851
28003
  function createSkillRun(params, targetDir = process.cwd()) {
26852
28004
  const now = new Date;
26853
28005
  const id = createRunId(now);
26854
28006
  const day = now.toISOString().slice(0, 10);
26855
28007
  const skillName = normalizeSkillName(params.skill);
26856
28008
  const root = getProjectStateDir(targetDir);
26857
- const runDir = join12(root, "runs", day, id);
26858
- const logsDir = join12(runDir, "logs");
26859
- const exportDir = join12(root, "exports", skillName, id);
26860
- mkdirSync5(logsDir, { recursive: true });
26861
- mkdirSync5(exportDir, { recursive: true });
26862
- mkdirSync5(join12(root, "tmp"), { recursive: true });
28009
+ const runDir = join13(root, "runs", day, id);
28010
+ const logsDir = join13(runDir, "logs");
28011
+ const exportDir = join13(root, "exports", skillName, id);
28012
+ mkdirSync6(logsDir, { recursive: true });
28013
+ mkdirSync6(exportDir, { recursive: true });
28014
+ mkdirSync6(join13(root, "tmp"), { recursive: true });
26863
28015
  const record3 = {
26864
28016
  id,
26865
28017
  skill: skillName,
@@ -26869,6 +28021,7 @@ function createSkillRun(params, targetDir = process.cwd()) {
26869
28021
  startedAt: now.toISOString(),
26870
28022
  remote: params.remote ?? false,
26871
28023
  ...params.remoteRunId ? { remoteRunId: params.remoteRunId } : {},
28024
+ ...params.remoteApiOrigin ? { remoteApiOrigin: params.remoteApiOrigin } : {},
26872
28025
  ...params.costCents !== undefined ? { costCents: params.costCents } : {},
26873
28026
  artifacts: [],
26874
28027
  paths: {
@@ -26910,22 +28063,22 @@ function updateSkillRun(context, patch) {
26910
28063
  return context.record;
26911
28064
  }
26912
28065
  function writeRunLogs(context, stdout = "", stderr = "") {
26913
- writeFileSync5(join12(context.logsDir, "stdout.log"), stdout);
26914
- writeFileSync5(join12(context.logsDir, "stderr.log"), stderr);
28066
+ writeFileSync6(join13(context.logsDir, "stdout.log"), stdout);
28067
+ writeFileSync6(join13(context.logsDir, "stderr.log"), stderr);
26915
28068
  }
26916
28069
  function appendRunEvent(context, event, data = {}) {
26917
28070
  const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
26918
28071
  `;
26919
- const path = join12(context.runDir, "events.ndjson");
26920
- const previous = existsSync12(path) ? readFileSync10(path, "utf-8") : "";
26921
- writeFileSync5(path, previous + line);
28072
+ const path = join13(context.runDir, "events.ndjson");
28073
+ const previous = existsSync13(path) ? readFileSync12(path, "utf-8") : "";
28074
+ writeFileSync6(path, previous + line);
26922
28075
  }
26923
28076
  function findSkillRun(runId, targetDir = process.cwd()) {
26924
- const runsRoot = join12(getProjectStateDir(targetDir), "runs");
26925
- if (!existsSync12(runsRoot))
28077
+ const runsRoot = join13(getProjectStateDir(targetDir), "runs");
28078
+ if (!existsSync13(runsRoot))
26926
28079
  return null;
26927
28080
  for (const day of readdirSync7(runsRoot)) {
26928
- const record3 = readRunRecord(join12(runsRoot, day, runId));
28081
+ const record3 = readRunRecord(join13(runsRoot, day, runId));
26929
28082
  if (record3)
26930
28083
  return record3;
26931
28084
  }
@@ -26942,20 +28095,20 @@ function skillRunEnv(context) {
26942
28095
  };
26943
28096
  }
26944
28097
  function writeRunRecord(context) {
26945
- writeFileSync5(join12(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
28098
+ writeFileSync6(join13(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
26946
28099
  `);
26947
28100
  }
26948
28101
  function writeArtifactsManifest(context, artifacts) {
26949
- writeFileSync5(join12(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
28102
+ writeFileSync6(join13(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
26950
28103
  `);
26951
28104
  }
26952
28105
  function collectRunArtifacts(context) {
26953
- if (!existsSync12(context.exportDir))
28106
+ if (!existsSync13(context.exportDir))
26954
28107
  return [];
26955
28108
  const artifacts = [];
26956
28109
  for (const path of walkFiles(context.exportDir)) {
26957
- const stat = statSync7(path);
26958
- const bytes = readFileSync10(path);
28110
+ const stat = statSync8(path);
28111
+ const bytes = readFileSync12(path);
26959
28112
  artifacts.push({
26960
28113
  path: toProjectRelative(context.targetDir, path),
26961
28114
  mime: mimeForPath(path),
@@ -26966,11 +28119,11 @@ function collectRunArtifacts(context) {
26966
28119
  return artifacts.sort((a, b) => a.path.localeCompare(b.path));
26967
28120
  }
26968
28121
  function readRunRecord(runDir) {
26969
- const path = join12(runDir, "run.json");
26970
- if (!existsSync12(path))
28122
+ const path = join13(runDir, "run.json");
28123
+ if (!existsSync13(path))
26971
28124
  return null;
26972
28125
  try {
26973
- return JSON.parse(readFileSync10(path, "utf-8"));
28126
+ return JSON.parse(readFileSync12(path, "utf-8"));
26974
28127
  } catch {
26975
28128
  return null;
26976
28129
  }
@@ -26978,8 +28131,8 @@ function readRunRecord(runDir) {
26978
28131
  function walkFiles(dir) {
26979
28132
  const files = [];
26980
28133
  for (const entry of readdirSync7(dir)) {
26981
- const full = join12(dir, entry);
26982
- if (statSync7(full).isDirectory())
28134
+ const full = join13(dir, entry);
28135
+ if (statSync8(full).isDirectory())
26983
28136
  files.push(...walkFiles(full));
26984
28137
  else
26985
28138
  files.push(full);
@@ -27045,25 +28198,26 @@ function resolveRunRouting(skill, apiKey, apiUrl) {
27045
28198
  error: `${skill.name} is a server-owned skill. Run: skills auth login`
27046
28199
  };
27047
28200
  }
27048
- return { route: "remote", apiKey };
28201
+ return { route: "remote", apiKey, apiOrigin: apiUrl };
27049
28202
  }
27050
28203
  async function resolveConfiguredRunRouting(skill, env = process.env) {
27051
- let fleet;
27052
- let apiKey;
28204
+ let connection;
27053
28205
  try {
27054
- fleet = resolveSkillsFleet(env);
27055
- apiKey = fleet.mode === "hosted" ? await resolveSkillsApiKey(env) : null;
28206
+ connection = await resolveSkillsConnection(env);
27056
28207
  } catch (error2) {
27057
- const isMissingCredential = (error2 instanceof SkillsFleetCredentialError || error2?.name === "SkillsFleetCredentialError") && error2.code === "MISSING_API_CREDENTIAL";
28208
+ const isMissingCredential = isSkillsFleetCredentialError2(error2) && error2.code === "MISSING_API_CREDENTIAL";
27058
28209
  if (!isMissingCredential)
27059
28210
  throw error2;
27060
28211
  return {
27061
28212
  route: "error",
27062
28213
  code: "REMOTE_REQUIRES_CREDENTIAL",
27063
- error: `${skill.name} is a server-owned skill. ${error2.message}`
28214
+ error: skill.serverOwned ? `${skill.name} is a server-owned skill. ${error2.message}` : `${skill.name} cannot run: ${error2.message}`
27064
28215
  };
27065
28216
  }
27066
- return resolveRunRouting(skill, apiKey, fleet.apiOrigin ?? undefined);
28217
+ return resolveRunRouting(skill, connection?.apiKey, connection?.apiOrigin);
28218
+ }
28219
+ function isSkillsFleetCredentialError2(error2) {
28220
+ return typeof error2 === "object" && error2 !== null && error2.name === "SkillsFleetCredentialError";
27067
28221
  }
27068
28222
 
27069
28223
  // src/mcp/operation-tools.ts
@@ -27227,39 +28381,42 @@ function registerOperationTools(server) {
27227
28381
  server.registerTool("list_categories", {
27228
28382
  title: "List Categories",
27229
28383
  description: "List all 17 skill categories with skill counts."
27230
- }, async () => {
27231
- const cats = CATEGORIES.map((category) => ({
28384
+ }, async () => readSurface(async () => {
28385
+ const registry2 = await getBrowseRegistry({ all: true });
28386
+ const extras = Array.from(new Set(registry2.map((skill) => skill.category))).filter((category) => !CATEGORIES.includes(category)).sort();
28387
+ const cats = [...CATEGORIES, ...extras].map((category) => ({
27232
28388
  name: category,
27233
- count: getSkillsByCategory(category).length
28389
+ count: registry2.filter((skill) => skill.category === category).length
27234
28390
  }));
27235
28391
  return { content: [{ type: "text", text: JSON.stringify(cats, null, 2) }] };
27236
- });
28392
+ }));
27237
28393
  server.registerTool("list_tags", {
27238
28394
  title: "List Tags",
27239
28395
  description: "List all unique skill tags with occurrence counts."
27240
- }, async () => {
28396
+ }, async () => readSurface(async () => {
27241
28397
  const tagCounts = new Map;
27242
- for (const skill of loadRegistry()) {
28398
+ for (const skill of await getBrowseRegistry({ all: true })) {
27243
28399
  for (const tag of skill.tags) {
27244
28400
  tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
27245
28401
  }
27246
28402
  }
27247
28403
  const sorted = Array.from(tagCounts.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => ({ name, count }));
27248
28404
  return { content: [{ type: "text", text: JSON.stringify(sorted, null, 2) }] };
27249
- });
28405
+ }));
27250
28406
  server.registerTool("get_requirements", {
27251
28407
  title: "Get Requirements",
27252
28408
  description: "Get env vars, system deps, and npm dependencies for a skill.",
27253
28409
  inputSchema: {
27254
28410
  name: exports_external.string()
27255
28411
  }
27256
- }, async ({ name }) => {
28412
+ }, async ({ name }) => readSurface(async () => {
28413
+ await requireSkillsReadAccess();
27257
28414
  const reqs = getSkillRequirements(name);
27258
28415
  if (!reqs) {
27259
28416
  return mcpError("SKILL_NOT_FOUND", `Skill '${name}' not found`, findSimilarSkills(name));
27260
28417
  }
27261
28418
  return { content: [{ type: "text", text: JSON.stringify(reqs, null, 2) }] };
27262
- });
28419
+ }));
27263
28420
  server.registerTool("run_skill", {
27264
28421
  title: "Run Skill",
27265
28422
  description: "Run a skill by name with optional arguments.",
@@ -27267,10 +28424,15 @@ function registerOperationTools(server) {
27267
28424
  name: exports_external.string(),
27268
28425
  input: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
27269
28426
  args: exports_external.array(exports_external.string()).optional(),
27270
- detail: exports_external.boolean().optional()
27271
- }
27272
- }, async ({ name, input, args, detail }) => {
27273
- const skill = getSkill(name);
28427
+ detail: exports_external.boolean().optional(),
28428
+ maxCostCents: exports_external.number().int().min(0).max(2147483647).optional().describe("Maximum integer credits explicitly approved by the user for a remote run; omitted permits only free runs"),
28429
+ maxCredits: exports_external.number().int().min(0).max(2147483647).optional(),
28430
+ remote: exports_external.boolean().optional().describe("Use the configured server catalog, including skills not installed locally"),
28431
+ idempotency_key: exports_external.string().regex(/^[A-Za-z0-9._:-]{1,128}$/).optional().describe("Reuse for the same approved submission after an interrupted response"),
28432
+ files: exports_external.array(exports_external.object({ name: exports_external.string(), base64: exports_external.string().max(1398104), contentType: exports_external.string().optional() })).max(10).optional().describe("Inline remote inputs, at most 1 MiB combined; use CLI or SDK for larger files")
28433
+ }
28434
+ }, async ({ name, input, args, detail, maxCostCents, maxCredits, remote, idempotency_key, files }) => {
28435
+ const skill = remote ? { name, serverOwned: true } : getSkill(name);
27274
28436
  if (!skill) {
27275
28437
  return mcpError("SKILL_NOT_FOUND", `Skill '${name}' not found`, findSimilarSkills(name));
27276
28438
  }
@@ -27281,31 +28443,36 @@ function registerOperationTools(server) {
27281
28443
  const skillName = skill.name;
27282
28444
  const runInput = input || {};
27283
28445
  const runArgs = args || [];
27284
- if (skillName === ARTICLE_GENERATION_SLUG2) {
28446
+ if (!remote && skillName === ARTICLE_GENERATION_SLUG2) {
27285
28447
  const validation = validateBlogArticleRunOptions2(runInput, runArgs, { requireTopic: true });
27286
28448
  if (!validation.ok) {
27287
28449
  return mcpError("INVALID_BLOG_ARTICLE_OPTIONS", validation.errors.join(" "));
27288
28450
  }
27289
28451
  }
27290
28452
  const routing = await resolveConfiguredRunRouting(skill);
28453
+ if (files?.length && routing.route !== "remote")
28454
+ return mcpError("REMOTE_REQUIRED", "Inline inputs require a remote run");
28455
+ let inputFiles;
28456
+ try {
28457
+ inputFiles = (await Promise.resolve().then(() => (init_remote_files(), exports_remote_files))).decodeRemoteFiles(files ?? []);
28458
+ } catch (error2) {
28459
+ return mcpError("INVALID_INPUT_FILES", error2.message);
28460
+ }
28461
+ if (routing.route === "error") {
28462
+ const suggestions = routing.code === "REMOTE_REQUIRES_ORIGIN" ? ["skills setup --api-url <url>", "skills auth login"] : ["skills auth login"];
28463
+ return mcpError(routing.code, routing.error, suggestions);
28464
+ }
27291
28465
  const runContext = createSkillRun({
27292
28466
  skill: skillName,
27293
28467
  args: runArgs,
27294
- remote: routing.route === "remote"
28468
+ remote: routing.route === "remote",
28469
+ ...routing.route === "remote" ? { remoteApiOrigin: routing.apiOrigin } : {}
27295
28470
  });
27296
- if (routing.route === "error") {
27297
- const error2 = routing.error;
27298
- writeRunLogs(runContext, "", error2 + `
27299
- `);
27300
- const run = completeSkillRun(runContext, { status: "failed", error: error2 });
27301
- const suggestions = routing.code === "REMOTE_REQUIRES_ORIGIN" ? ["skills setup --api-url <url>", "skills auth login"] : ["skills auth login"];
27302
- return mcpError(routing.code, `${error2}. Local run metadata: ${run.paths.runDir}/run.json`, suggestions);
27303
- }
27304
28471
  if (routing.route === "remote") {
27305
28472
  try {
27306
28473
  const { RemoteSkillsClient: RemoteSkillsClient2 } = await Promise.resolve().then(() => (init_remote_client(), exports_remote_client));
27307
- const client = new RemoteSkillsClient2(routing.apiKey);
27308
- const run = await client.submitRun(skillName, runInput, runArgs);
28474
+ const client = new RemoteSkillsClient2(routing.apiKey, routing.apiOrigin);
28475
+ const run = await client.submitQuotedRunWithFiles(skillName, runInput, runArgs, inputFiles, { maxCredits, maxCostCents, idempotencyKey: idempotency_key ?? runContext.record.id });
27309
28476
  if (run.error) {
27310
28477
  writeRunLogs(runContext, "", String(run.error) + `
27311
28478
  `);
@@ -27367,7 +28534,7 @@ function registerOperationTools(server) {
27367
28534
  }
27368
28535
  }, async ({ run_id, detail }) => {
27369
28536
  const { skillsCredentialOrReason: skillsCredentialOrReason2 } = await Promise.resolve().then(() => (init_fleet_credentials(), exports_fleet_credentials));
27370
- const { apiKey, reason } = await skillsCredentialOrReason2();
28537
+ const { apiKey, apiOrigin, reason } = await skillsCredentialOrReason2();
27371
28538
  if (!apiKey) {
27372
28539
  return mcpError("AUTH_REQUIRED", reason ?? "Remote run status requires API access. Run: skills auth login", ["skills auth login"]);
27373
28540
  }
@@ -27378,7 +28545,9 @@ function registerOperationTools(server) {
27378
28545
  }
27379
28546
  try {
27380
28547
  const { RemoteSkillsClient: RemoteSkillsClient2 } = await Promise.resolve().then(() => (init_remote_client(), exports_remote_client));
27381
- const client = new RemoteSkillsClient2(apiKey);
28548
+ if (localRun?.remoteApiOrigin && localRun.remoteApiOrigin !== apiOrigin)
28549
+ return mcpError("INSTANCE_MISMATCH", "This run belongs to another Skills instance; select its credential profile");
28550
+ const client = new RemoteSkillsClient2(apiKey, apiOrigin);
27382
28551
  const run = await client.getRun(remoteRunId);
27383
28552
  if (!run)
27384
28553
  return mcpError("RUN_NOT_FOUND", `Remote run '${remoteRunId}' not found`);
@@ -27455,21 +28624,22 @@ function registerOperationTools(server) {
27455
28624
  });
27456
28625
  server.registerTool("whoami", {
27457
28626
  title: "Skills Whoami",
27458
- description: "Show setup summary: version, pinned skills, agent configs, cwd."
28627
+ description: "Show setup summary: version, pinned skills, agent configs, cwd, and the credential/transport SOURCES (never values)."
27459
28628
  }, async () => {
27460
28629
  const version2 = package_default.version;
27461
28630
  const cwd = process.cwd();
28631
+ const credential = describeCredentialState();
27462
28632
  const installed = getInstalledSkills();
27463
28633
  const agents = [];
27464
28634
  for (const agent of AGENT_TARGETS) {
27465
28635
  const agentSkillsPath = getAgentSkillsDir(agent, "global");
27466
- const exists = existsSync13(agentSkillsPath);
28636
+ const exists = existsSync14(agentSkillsPath);
27467
28637
  let skillCount = 0;
27468
28638
  if (exists) {
27469
28639
  try {
27470
28640
  skillCount = readdirSync8(agentSkillsPath).filter((f) => {
27471
28641
  const full = join14(agentSkillsPath, f);
27472
- return !f.startsWith(".") && statSync8(full).isDirectory();
28642
+ return !f.startsWith(".") && statSync9(full).isDirectory();
27473
28643
  }).length;
27474
28644
  } catch {}
27475
28645
  }
@@ -27482,7 +28652,8 @@ function registerOperationTools(server) {
27482
28652
  installed,
27483
28653
  agents,
27484
28654
  skillsDir,
27485
- cwd
28655
+ cwd,
28656
+ credential
27486
28657
  };
27487
28658
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
27488
28659
  });
@@ -27514,29 +28685,17 @@ function compactRunToolPayload(payload, detailHint) {
27514
28685
  }
27515
28686
 
27516
28687
  // src/lib/feedback.ts
27517
- import { appendFileSync, existsSync as existsSync14, mkdirSync as mkdirSync6 } from "fs";
28688
+ import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync7 } from "fs";
27518
28689
  import { dirname as dirname5, join as join15 } from "path";
27519
28690
  import { Database } from "bun:sqlite";
27520
-
27521
- // src/lib/api-url.ts
27522
- init_fleet_credentials();
27523
- init_fleet_credentials();
27524
- var API_URL_ENV_VAR = SKILLS_API_URL_ENV;
27525
- 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>`;
27526
- function resolveApiUrl(env = process.env, options = {}) {
27527
- const fleet = resolveSkillsFleet(env, options);
27528
- return fleet.mode === "hosted" ? fleet.apiOrigin : undefined;
27529
- }
27530
-
27531
- // src/lib/feedback.ts
27532
28691
  function getFeedbackDbPath() {
27533
28692
  return join15(getDataDir(), "skills.db");
27534
28693
  }
27535
28694
  function getFeedbackDb() {
27536
28695
  const dbPath = getFeedbackDbPath();
27537
28696
  const dir = dirname5(dbPath);
27538
- if (!existsSync14(dir))
27539
- mkdirSync6(dir, { recursive: true });
28697
+ if (!existsSync15(dir))
28698
+ mkdirSync7(dir, { recursive: true });
27540
28699
  const db = new Database(dbPath);
27541
28700
  db.exec("PRAGMA journal_mode = WAL");
27542
28701
  db.exec([
@@ -27564,8 +28723,8 @@ function saveFeedback(input) {
27564
28723
  if (isApiMode()) {
27565
28724
  const path = join15(getDataDir(), "feedback.jsonl");
27566
28725
  const dir = dirname5(path);
27567
- if (!existsSync14(dir))
27568
- mkdirSync6(dir, { recursive: true });
28726
+ if (!existsSync15(dir))
28727
+ mkdirSync7(dir, { recursive: true });
27569
28728
  appendFileSync(path, JSON.stringify({ message, category, email: input.email ?? null, agent: input.agent ?? null, version: input.version ?? null, createdAt: new Date().toISOString() }) + `
27570
28729
  `);
27571
28730
  return { saved: true, category, path };
@@ -27710,16 +28869,16 @@ function registerResourceMetaTools(server) {
27710
28869
  }
27711
28870
 
27712
28871
  // src/lib/scheduler.ts
27713
- import { existsSync as existsSync15, readFileSync as readFileSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7 } from "fs";
28872
+ import { existsSync as existsSync16, readFileSync as readFileSync13, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8 } from "fs";
27714
28873
  import { join as join16 } from "path";
27715
28874
  function getSchedulesPath(targetDir = process.cwd()) {
27716
28875
  return join16(targetDir, ".skills", "schedules.json");
27717
28876
  }
27718
28877
  function loadSchedules(targetDir = process.cwd()) {
27719
28878
  const path = getSchedulesPath(targetDir);
27720
- if (existsSync15(path)) {
28879
+ if (existsSync16(path)) {
27721
28880
  try {
27722
- return JSON.parse(readFileSync12(path, "utf-8"));
28881
+ return JSON.parse(readFileSync13(path, "utf-8"));
27723
28882
  } catch {}
27724
28883
  }
27725
28884
  return { version: 1, schedules: [] };
@@ -27727,9 +28886,9 @@ function loadSchedules(targetDir = process.cwd()) {
27727
28886
  function saveSchedules(data, targetDir = process.cwd()) {
27728
28887
  const path = getSchedulesPath(targetDir);
27729
28888
  const dir = join16(targetDir, ".skills");
27730
- if (!existsSync15(dir))
27731
- mkdirSync7(dir, { recursive: true });
27732
- writeFileSync6(path, JSON.stringify(data, null, 2));
28889
+ if (!existsSync16(dir))
28890
+ mkdirSync8(dir, { recursive: true });
28891
+ writeFileSync7(path, JSON.stringify(data, null, 2));
27733
28892
  }
27734
28893
  function validateCronField(expr, min, max, label) {
27735
28894
  for (const part of expr.split(",")) {
@@ -27987,14 +29146,14 @@ function registerScheduleTools(server) {
27987
29146
  }
27988
29147
 
27989
29148
  // src/lib/native-storage.ts
27990
- import { createHash as createHash3, createHmac } from "crypto";
29149
+ import { createHash as createHash4, createHmac } from "crypto";
27991
29150
  import {
27992
- existsSync as existsSync16,
27993
- mkdirSync as mkdirSync8,
27994
- readFileSync as readFileSync13,
29151
+ existsSync as existsSync17,
29152
+ mkdirSync as mkdirSync9,
29153
+ readFileSync as readFileSync14,
27995
29154
  readdirSync as readdirSync9,
27996
- statSync as statSync9,
27997
- writeFileSync as writeFileSync7
29155
+ statSync as statSync10,
29156
+ writeFileSync as writeFileSync8
27998
29157
  } from "fs";
27999
29158
  import { dirname as dirname6, join as join17, normalize as normalize3, relative as relative3, sep as sep2 } from "path";
28000
29159
  var SKILLS_STORAGE_TABLES = [
@@ -28092,14 +29251,14 @@ function getStorageStatus(options = {}) {
28092
29251
  function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
28093
29252
  const projectStateDir = getProjectStateDir(targetDir);
28094
29253
  const files = [];
28095
- if (existsSync16(projectStateDir)) {
29254
+ if (existsSync17(projectStateDir)) {
28096
29255
  for (const filePath of walkFiles2(projectStateDir)) {
28097
- const bytes = readFileSync13(filePath);
29256
+ const bytes = readFileSync14(filePath);
28098
29257
  const relativePath = toPosix(relative3(targetDir, filePath));
28099
29258
  files.push({
28100
29259
  path: relativePath,
28101
29260
  sizeBytes: bytes.byteLength,
28102
- sha256: createHash3("sha256").update(bytes).digest("hex"),
29261
+ sha256: createHash4("sha256").update(bytes).digest("hex"),
28103
29262
  ...options.includeFileContents ? { contentBase64: Buffer.from(bytes).toString("base64") } : {}
28104
29263
  });
28105
29264
  }
@@ -28177,7 +29336,7 @@ function walkFiles2(dir) {
28177
29336
  const files = [];
28178
29337
  for (const entry of readdirSync9(dir)) {
28179
29338
  const full = join17(dir, entry);
28180
- const stats = statSync9(full);
29339
+ const stats = statSync10(full);
28181
29340
  if (stats.isDirectory())
28182
29341
  files.push(...walkFiles2(full));
28183
29342
  else
@@ -28231,6 +29390,282 @@ function registerStorageTools(server) {
28231
29390
  });
28232
29391
  }
28233
29392
 
29393
+ // src/lib/remote-auth.ts
29394
+ init_remote_workspace();
29395
+ init_remote_workspace();
29396
+ init_remote_client();
29397
+ init_fleet_credentials();
29398
+ var MAX_ERROR_DETAIL_LENGTH = 200;
29399
+
29400
+ class HostedApiError extends Error {
29401
+ status;
29402
+ code;
29403
+ detail;
29404
+ endpoint;
29405
+ apiUrl;
29406
+ constructor(message, options = {}) {
29407
+ super(message);
29408
+ this.name = "HostedApiError";
29409
+ this.status = options.status;
29410
+ this.code = options.code;
29411
+ this.detail = options.detail;
29412
+ this.endpoint = options.endpoint;
29413
+ this.apiUrl = options.apiUrl;
29414
+ }
29415
+ }
29416
+ async function requestAuthApi(instance, path, options) {
29417
+ const url = normalizeSkillsApiOrigin(instance);
29418
+ const safeUrl = url;
29419
+ const endpoint = `${(options?.method || "GET").toUpperCase()} ${safeUrl}${path}`;
29420
+ let res;
29421
+ try {
29422
+ res = await fetch(`${url}${path}`, {
29423
+ ...options,
29424
+ redirect: "error",
29425
+ signal: options?.signal ?? AbortSignal.timeout(15000),
29426
+ headers: { "Content-Type": "application/json", ...options?.headers }
29427
+ });
29428
+ } catch (err) {
29429
+ throw new HostedApiError(`Unable to reach the Skills API: ${err.message}`, {
29430
+ endpoint,
29431
+ apiUrl: safeUrl
29432
+ });
29433
+ }
29434
+ const text = await res.text();
29435
+ const body = text ? parseJsonBody(text) : {};
29436
+ if (!res.ok) {
29437
+ const record4 = isRecord5(body) ? body : {};
29438
+ const detail = typeof record4.detail === "string" ? record4.detail : undefined;
29439
+ const error2 = typeof record4.error === "string" ? record4.error : undefined;
29440
+ const code = typeof record4.code === "string" ? record4.code : undefined;
29441
+ throw new HostedApiError(detail || error2 || `${res.status} ${res.statusText}`, {
29442
+ status: res.status,
29443
+ code,
29444
+ detail,
29445
+ endpoint,
29446
+ apiUrl: safeUrl
29447
+ });
29448
+ }
29449
+ return body;
29450
+ }
29451
+ function parseJsonBody(text) {
29452
+ try {
29453
+ return JSON.parse(text);
29454
+ } catch {
29455
+ return { detail: condenseErrorBody(text) };
29456
+ }
29457
+ }
29458
+ function condenseErrorBody(text) {
29459
+ const stripped = /<[a-z!/]/i.test(text) ? text.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]*>/g, " ") : text;
29460
+ const collapsed = stripped.replace(/\s+/g, " ").trim();
29461
+ if (collapsed.length <= MAX_ERROR_DETAIL_LENGTH)
29462
+ return collapsed;
29463
+ return `${collapsed.slice(0, MAX_ERROR_DETAIL_LENGTH - 1).trimEnd()}\u2026`;
29464
+ }
29465
+ function isRecord5(value) {
29466
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
29467
+ }
29468
+
29469
+ class RemoteSkillsAuthClient {
29470
+ apiOrigin;
29471
+ constructor(apiUrl) {
29472
+ this.apiOrigin = normalizeSkillsApiOrigin(apiUrl);
29473
+ }
29474
+ requestCode(email2) {
29475
+ return this.request("/api/auth/login", { method: "POST", body: JSON.stringify({ email: email2 }) });
29476
+ }
29477
+ verifyCode(email2, code) {
29478
+ return this.request("/api/auth/verify", { method: "POST", body: JSON.stringify({ email: email2, code }) });
29479
+ }
29480
+ startDevice() {
29481
+ return this.request("/api/auth/device/start", { method: "POST", body: JSON.stringify({ client: "skills-sdk" }) });
29482
+ }
29483
+ pollDevice(deviceCode) {
29484
+ return this.request("/api/auth/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) });
29485
+ }
29486
+ async sessionClient(email2, code) {
29487
+ const apiOrigin = this.apiOrigin;
29488
+ if (!email2.includes("@") || !/^\d{6}$/.test(code))
29489
+ throw new Error("Fresh email and six-digit verification code are required to manage this account");
29490
+ const login = await this.verifyCode(email2, code);
29491
+ if (!login || typeof login.token !== "string" || !login.token)
29492
+ throw new Error("The server did not return an authorized account session");
29493
+ return new RemoteSkillsClient(login.token, apiOrigin);
29494
+ }
29495
+ async createApiKey(email2, code, name, scopes) {
29496
+ return (await this.sessionClient(email2, code)).createApiKey(name, scopes);
29497
+ }
29498
+ async listApiKeys(email2, code) {
29499
+ return (await this.sessionClient(email2, code)).listApiKeys();
29500
+ }
29501
+ async revokeApiKey(email2, code, keyId) {
29502
+ return (await this.sessionClient(email2, code)).revokeApiKey(keyId);
29503
+ }
29504
+ async updateProfile(email2, code, input) {
29505
+ customerNamePatch(input, "displayName");
29506
+ return (await this.sessionClient(email2, code)).updateProfile(input);
29507
+ }
29508
+ async updateCurrentWorkspace(email2, code, input) {
29509
+ customerNamePatch(input, "name");
29510
+ return (await this.sessionClient(email2, code)).updateCurrentWorkspace(input);
29511
+ }
29512
+ async listWorkspaceMembers(email2, code, options = {}) {
29513
+ workspaceMembersQuery(options);
29514
+ return (await this.sessionClient(email2, code)).listWorkspaceMembers(options);
29515
+ }
29516
+ async setWorkspaceMemberRole(email2, code, membershipId, input) {
29517
+ const captured = workspaceMemberRoleInput(membershipId, input);
29518
+ return (await this.sessionClient(email2, code)).setWorkspaceMemberRole(captured.membershipId, captured.body);
29519
+ }
29520
+ async removeWorkspaceMember(email2, code, membershipId, input) {
29521
+ const captured = workspaceMemberRemovalInput(membershipId, input);
29522
+ return (await this.sessionClient(email2, code)).removeWorkspaceMember(captured.membershipId, captured.body);
29523
+ }
29524
+ request(path, options) {
29525
+ if (!["/api/auth/login", "/api/auth/verify", "/api/auth/device/start", "/api/auth/device/token", "/api/auth/keys", "/api/auth/whoami"].includes(path))
29526
+ throw new Error("Unsupported authentication operation");
29527
+ return requestAuthApi(this.apiOrigin, path, options);
29528
+ }
29529
+ }
29530
+
29531
+ // src/mcp/remote-customer-tools.ts
29532
+ init_auth_store();
29533
+ init_remote_client();
29534
+ function registerRemoteCustomerTools(server) {
29535
+ const memberRole = exports_external.enum(["owner", "admin", "member", "viewer"]);
29536
+ const memberInput = {
29537
+ membershipId: exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/),
29538
+ expectedRole: memberRole,
29539
+ email: exports_external.string().email(),
29540
+ code: exports_external.string().regex(/^\d{6}$/)
29541
+ };
29542
+ server.registerTool("set_workspace_member_role", {
29543
+ title: "Set Current Workspace Member Role",
29544
+ description: "Change exactly this membership incarnation with its observed expectedRole and fresh verification. The server enforces owner/admin policy. No automatic refresh or retry; saved credentials stay unchanged.",
29545
+ inputSchema: exports_external.object({ ...memberInput, role: memberRole }).strict()
29546
+ }, async ({ membershipId, role, expectedRole, email: email2, code }) => {
29547
+ try {
29548
+ return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("Set workspace member role")).setWorkspaceMemberRole(email2, code, membershipId, { role, expectedRole }));
29549
+ } catch (error2) {
29550
+ return memberError(error2);
29551
+ }
29552
+ });
29553
+ server.registerTool("remove_workspace_member", {
29554
+ title: "Remove Current Workspace Member",
29555
+ description: "Remove exactly this membership incarnation using its observed expectedRole and fresh verification. Self-removal is unavailable. A retry cannot remove a later replacement membership; saved credentials stay unchanged.",
29556
+ inputSchema: exports_external.object(memberInput).strict()
29557
+ }, async ({ membershipId, expectedRole, email: email2, code }) => {
29558
+ try {
29559
+ return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("Remove workspace member")).removeWorkspaceMember(email2, code, membershipId, { expectedRole }));
29560
+ } catch (error2) {
29561
+ return memberError(error2);
29562
+ }
29563
+ });
29564
+ server.registerTool("list_workspace_members", {
29565
+ title: "List Current Workspace Members",
29566
+ description: "Read one roster page on the selected Skills server using fresh owner/admin email verification. Saved credentials are unchanged. This does not invite, change or switch members/workspaces.",
29567
+ inputSchema: exports_external.object({
29568
+ email: exports_external.string().email(),
29569
+ code: exports_external.string().regex(/^\d{6}$/),
29570
+ limit: exports_external.number().int().min(1).max(100).optional(),
29571
+ cursor: exports_external.string().regex(/^[A-Za-z0-9_-]{1,512}$/).optional()
29572
+ }).strict()
29573
+ }, async ({ email: email2, code, limit, cursor: cursor2 }) => {
29574
+ try {
29575
+ return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("List workspace members")).listWorkspaceMembers(email2, code, { limit, cursor: cursor2 }));
29576
+ } catch {
29577
+ return mcpError("WORKSPACE_MEMBERS_FAILED", "Unable to list workspace members. Check the selected server, owner/admin permissions, pagination and fresh verification code.");
29578
+ }
29579
+ });
29580
+ for (const kind of ["profile", "workspace"]) {
29581
+ server.registerTool(kind === "profile" ? "update_account_profile" : "update_workspace_name", {
29582
+ title: kind === "profile" ? "Update Account Display Name" : "Update Workspace Name",
29583
+ description: "Update only the name on the explicitly selected Skills server using fresh email OTP. Workspace changes require an owner/admin. Saved credentials are unchanged.",
29584
+ inputSchema: exports_external.object({ name: exports_external.string().min(1), email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/) }).strict()
29585
+ }, async ({ name, email: email2, code }) => {
29586
+ try {
29587
+ const client = new RemoteSkillsAuthClient(getApiUrl("Update customer name"));
29588
+ return mcpJson(kind === "profile" ? await client.updateProfile(email2, code, { displayName: name }) : await client.updateCurrentWorkspace(email2, code, { name }));
29589
+ } catch {
29590
+ return mcpError("NAME_UPDATE_FAILED", "Unable to update the name. Check the selected server, name, permissions and fresh verification code.");
29591
+ }
29592
+ });
29593
+ }
29594
+ for (const operation of REMOTE_CUSTOMER_OPERATIONS) {
29595
+ const inputSchema = {};
29596
+ if (operation.parameter)
29597
+ inputSchema[operation.parameter] = exports_external.string().min(1);
29598
+ server.registerTool(operation.name, {
29599
+ title: operation.title,
29600
+ description: `${operation.title} on the explicitly configured Skills server. Missing server capabilities return an error. Checkout links require external customer confirmation.`,
29601
+ inputSchema
29602
+ }, async (input) => callRemote((client) => operation.invoke(client, operation.parameter ? String(input[operation.parameter]) : "")));
29603
+ }
29604
+ server.registerTool("list_api_keys", {
29605
+ title: "List API Keys",
29606
+ description: "List account API keys using fresh email OTP reauthentication.",
29607
+ inputSchema: { email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/) }
29608
+ }, async ({ email: email2, code }) => {
29609
+ try {
29610
+ return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("List API keys")).listApiKeys(email2, code));
29611
+ } catch (error2) {
29612
+ return mcpError("KEY_LIST_FAILED", error2.message);
29613
+ }
29614
+ });
29615
+ server.registerTool("revoke_api_key", {
29616
+ title: "Revoke API Key",
29617
+ description: "Revoke an account API key using fresh email OTP reauthentication.",
29618
+ inputSchema: { key_id: exports_external.string().min(1), email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/) }
29619
+ }, async ({ key_id, email: email2, code }) => {
29620
+ try {
29621
+ return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("Revoke API key")).revokeApiKey(email2, code, key_id));
29622
+ } catch (error2) {
29623
+ return mcpError("KEY_REVOKE_FAILED", error2.message);
29624
+ }
29625
+ });
29626
+ server.registerTool("create_api_key", {
29627
+ title: "Create API Key",
29628
+ description: "Create an API key using fresh email OTP reauthentication; returns its secret once. A stored API key cannot grant this authority.",
29629
+ 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() }
29630
+ }, async ({ name, email: email2, code, scopes }) => {
29631
+ try {
29632
+ return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("Create API key")).createApiKey(email2, code, name, scopes));
29633
+ } catch (error2) {
29634
+ return mcpError("KEY_CREATION_FAILED", error2.message);
29635
+ }
29636
+ });
29637
+ server.registerTool("quote_skill", {
29638
+ title: "Quote Remote Skill",
29639
+ description: "Get the configured server's credit quote without submitting a run.",
29640
+ inputSchema: { name: exports_external.string(), input: exports_external.record(exports_external.string(), exports_external.unknown()).optional(), args: exports_external.array(exports_external.string()).optional() }
29641
+ }, ({ name, input, args }) => callRemote((client) => client.quoteRun(name, input, args)));
29642
+ server.registerTool("download_run_artifact", {
29643
+ title: "Download Verified Run Artifact",
29644
+ description: "Return verified artifact bytes as base64 (at most 1 MiB); use the CLI for larger files.",
29645
+ inputSchema: { run_id: exports_external.string(), artifact_id: exports_external.string() }
29646
+ }, ({ run_id, artifact_id }) => callRemote(async (client) => {
29647
+ const artifact = await client.getVerifiedRunArtifact(run_id, artifact_id, 1024 * 1024);
29648
+ const { bytes, ...metadata } = artifact;
29649
+ return { ...metadata, base64: Buffer.from(bytes).toString("base64") };
29650
+ }));
29651
+ }
29652
+ function memberError(error2) {
29653
+ return error2 instanceof RemoteWorkspaceMemberError ? mcpError(error2.code, error2.message) : mcpError("WORKSPACE_MEMBER_FAILED", "Unable to manage workspace member. Check the selected server and fresh verification, then refresh the roster before another action.");
29654
+ }
29655
+ async function callRemote(action) {
29656
+ try {
29657
+ const client = await createRemoteSkillsClient();
29658
+ if (!client)
29659
+ return mcpError("AUTH_REQUIRED", "Configure a Skills API and sign in with skills auth login");
29660
+ return mcpJson(await action(client));
29661
+ } catch (error2) {
29662
+ if (error2 instanceof RemoteCapabilityUnavailableError) {
29663
+ return { ...mcpJson({ code: error2.code, message: error2.message, status: error2.status }), isError: true };
29664
+ }
29665
+ return mcpError("REMOTE_REQUEST_FAILED", error2 instanceof Error ? error2.message : "Skills server request failed");
29666
+ }
29667
+ }
29668
+
28234
29669
  // src/mcp/server.ts
28235
29670
  function buildServer() {
28236
29671
  const server = new McpServer({
@@ -28242,6 +29677,7 @@ function buildServer() {
28242
29677
  registerScheduleTools(server);
28243
29678
  registerStorageTools(server);
28244
29679
  registerResourceMetaTools(server);
29680
+ registerRemoteCustomerTools(server);
28245
29681
  return server;
28246
29682
  }
28247
29683
  var server = buildServer();
@@ -29696,6 +31132,7 @@ async function startSkillsMcpHttpServer(options = {}) {
29696
31132
  }
29697
31133
 
29698
31134
  // src/mcp/index.ts
31135
+ init_fleet_credentials();
29699
31136
  var args = process.argv.slice(2);
29700
31137
  function printHelp() {
29701
31138
  console.log(`Usage: skills-mcp [options]
@@ -29705,7 +31142,8 @@ MCP server for ${package_default.name}
29705
31142
  Options:
29706
31143
  -V, --version output the version number
29707
31144
  -h, --help display help for command
29708
- --http run Streamable HTTP transport on 127.0.0.1 (default port 8836)
31145
+ --stdio run newline-delimited JSON-RPC for agent hosts
31146
+ --http run Streamable HTTP transport on 127.0.0.1 (default; port 8836)
29709
31147
  --port <n> HTTP port (--http or MCP_HTTP=1)`);
29710
31148
  }
29711
31149
  if (args.includes("--help") || args.includes("-h")) {
@@ -29716,7 +31154,18 @@ if (args.includes("--version") || args.includes("-V")) {
29716
31154
  console.log(package_default.version);
29717
31155
  process.exit(0);
29718
31156
  }
31157
+ function assertSkillsMcpConfigured(env = process.env) {
31158
+ try {
31159
+ resolveSkillsFleet(env);
31160
+ } catch (error2) {
31161
+ if (!isSkillsFleetCredentialError(error2))
31162
+ throw error2;
31163
+ console.error(error2.message);
31164
+ process.exit(1);
31165
+ }
31166
+ }
29719
31167
  async function startMcpStdio() {
31168
+ assertSkillsMcpConfigured();
29720
31169
  const server2 = buildServer();
29721
31170
  await server2.connect(new StdioServerTransport);
29722
31171
  }
@@ -29725,6 +31174,7 @@ async function main() {
29725
31174
  await startMcpStdio();
29726
31175
  return;
29727
31176
  }
31177
+ assertSkillsMcpConfigured();
29728
31178
  const port = parseMcpHttpPort(args);
29729
31179
  await startSkillsMcpHttpServer({ port, hostname: "127.0.0.1" });
29730
31180
  }
@@ -29736,5 +31186,6 @@ if (import.meta.main) {
29736
31186
  }
29737
31187
  export {
29738
31188
  startMcpStdio,
29739
- buildServer
31189
+ buildServer,
31190
+ assertSkillsMcpConfigured
29740
31191
  };