@hasna/skills 0.2.0 → 0.3.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.
package/bin/index.js CHANGED
@@ -36860,12 +36860,13 @@ var package_default;
36860
36860
  var init_package = __esm(() => {
36861
36861
  package_default = {
36862
36862
  name: "@hasna/skills",
36863
- version: "0.2.0",
36863
+ version: "0.3.0",
36864
36864
  description: "Skills library for AI coding agents",
36865
36865
  type: "module",
36866
36866
  bin: {
36867
36867
  skills: "bin/index.js",
36868
36868
  "skills-mcp": "bin/mcp.js",
36869
+ "skills-serve": "bin/server.js",
36869
36870
  "skills-server": "bin/server.js",
36870
36871
  "skills-worker": "bin/worker.js",
36871
36872
  "skills-migrate": "bin/migrate.js"
@@ -36951,6 +36952,7 @@ var init_package = __esm(() => {
36951
36952
  dependencies: {
36952
36953
  "@aws-sdk/client-ecs": "^3.1079.0",
36953
36954
  "@aws-sdk/client-s3": "^3.1079.0",
36955
+ "@hasna/contracts": "1.0.1",
36954
36956
  "@hasna/events": "0.1.16",
36955
36957
  "@modelcontextprotocol/sdk": "^1.26.0",
36956
36958
  chalk: "^5.3.0",
@@ -37897,14 +37899,19 @@ function assertNoRetiredConfigKeys(config, source) {
37897
37899
  for (const [key, replacement] of Object.entries(RETIRED_CONFIG_KEYS)) {
37898
37900
  if (!(key in config))
37899
37901
  continue;
37900
- throw new RetiredSettingError(key, `${source}: "${key}" is no longer a configuration key. ` + "Deployment modes were removed: a Skills client either has an API origin " + "configured or it does not, and that is the whole of it. " + `Use "${replacement}" instead (skills config set ${replacement} <origin>), and ` + `remove the old key with: skills config unset ${key}. ` + "Refused rather than ignored, because silently dropping it would leave an " + "operator believing they had pointed this install at a server.");
37902
+ throw new RetiredSettingError(key, `${source}: "${key}" is no longer a configuration key. ` + `${RETIRED_CONFIG_KEY_REASONS[key] ?? ""} ` + `Use ${replacement} instead, and remove the old key with: skills config unset ${key}. ` + "Refused rather than ignored, because silently dropping it would leave an " + "operator believing they had pointed this install at a server.");
37901
37903
  }
37902
37904
  }
37903
- var RETIRED_ENV_SUFFIXES, RETIRED_CONFIG_KEYS, RetiredSettingError;
37905
+ var RETIRED_ENV_SUFFIXES, RETIRED_CONFIG_KEYS, RETIRED_CONFIG_KEY_REASONS, RetiredSettingError;
37904
37906
  var init_retired_settings = __esm(() => {
37905
37907
  RETIRED_ENV_SUFFIXES = ["_STORAGE_MODE", "_DEPLOYMENT_MODE", "_CLOUD_MODE"];
37906
37908
  RETIRED_CONFIG_KEYS = {
37907
- mode: "apiUrl"
37909
+ mode: "a configured API origin",
37910
+ apiUrl: "skills setup --api-url <origin>"
37911
+ };
37912
+ RETIRED_CONFIG_KEY_REASONS = {
37913
+ mode: "Deployment modes were removed: a Skills client either resolves a credential " + "or it does not, and that is the whole of it.",
37914
+ apiUrl: "The service address is no longer kept in this app's config file: it is read from " + "HASNA_SKILLS_API_URL, then the macOS Keychain item hasna.credentials.skills.api-url, " + "then ~/.hasna/skills/config/credentials, then the fleet gateway."
37908
37915
  };
37909
37916
  RetiredSettingError = class RetiredSettingError extends Error {
37910
37917
  code = "RETIRED_SETTING";
@@ -38036,7 +38043,6 @@ var init_app_home = __esm(() => {
38036
38043
  // src/lib/config.ts
38037
38044
  import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
38038
38045
  import { join as join4, dirname } from "path";
38039
- import { homedir as homedir3 } from "os";
38040
38046
  function validKeys() {
38041
38047
  return [...Object.keys(ENUM_KEYS), ...STRING_KEYS];
38042
38048
  }
@@ -38067,16 +38073,6 @@ function normalizeConfigValue(key, value) {
38067
38073
  const allowed = allowedValues(key);
38068
38074
  if (allowed)
38069
38075
  return allowed.includes(value) ? value : undefined;
38070
- if (key === "apiUrl") {
38071
- try {
38072
- const url = new URL(value);
38073
- if (url.protocol !== "http:" && url.protocol !== "https:")
38074
- return;
38075
- return value.replace(/\/+$/, "");
38076
- } catch {
38077
- return;
38078
- }
38079
- }
38080
38076
  if (key === "extensionsDir")
38081
38077
  return value.trim() ? value : undefined;
38082
38078
  return;
@@ -38107,27 +38103,6 @@ function getDataDir() {
38107
38103
  function getDataDirReadOnly() {
38108
38104
  return getDataRoot();
38109
38105
  }
38110
- function getConfigPathReadOnly(scope) {
38111
- if (scope === "global")
38112
- return join4(getDataDirReadOnly(), "config.json");
38113
- return join4(process.cwd(), "skills.config.json");
38114
- }
38115
- function loadConfigReadOnly() {
38116
- const canonicalConfigPath = getConfigPathReadOnly("global");
38117
- let globalConfig;
38118
- if (existsSync4(canonicalConfigPath)) {
38119
- globalConfig = readConfigFile(canonicalConfigPath);
38120
- } else if (hasOperatorOverride()) {
38121
- globalConfig = {};
38122
- } else {
38123
- globalConfig = readConfigFile(legacyConfigFilePath());
38124
- }
38125
- const projectConfig = readConfigFile(getConfigPathReadOnly("project"));
38126
- return { ...globalConfig, ...projectConfig };
38127
- }
38128
- function legacyConfigFilePath() {
38129
- return join4(process.env["HOME"] || process.env["USERPROFILE"] || homedir3(), ".skillsrc");
38130
- }
38131
38106
  function getConfigPath(scope) {
38132
38107
  if (scope === "global") {
38133
38108
  return join4(getDataDir(), "config.json");
@@ -38167,7 +38142,7 @@ function saveConfig(key, value, scope = "project") {
38167
38142
  const normalized = normalizeConfigValue(key, value);
38168
38143
  if (normalized === undefined) {
38169
38144
  const allowed = allowedValues(key);
38170
- throw new Error(allowed ? `Invalid value '${value}' for ${key}. Allowed: ${allowed.join(", ")}` : `Invalid value '${value}' for ${key}. Expected ${key === "apiUrl" ? "an http(s) URL" : "a non-empty path"}`);
38145
+ throw new Error(allowed ? `Invalid value '${value}' for ${key}. Allowed: ${allowed.join(", ")}` : `Invalid value '${value}' for ${key}. Expected a non-empty path`);
38171
38146
  }
38172
38147
  const filePath = getConfigPath(scope);
38173
38148
  let existing = {};
@@ -38224,7 +38199,7 @@ var init_config = __esm(() => {
38224
38199
  defaultScope: ["global", "project"],
38225
38200
  format: ["compact", "json", "csv"]
38226
38201
  };
38227
- STRING_KEYS = ["apiUrl", "extensionsDir"];
38202
+ STRING_KEYS = ["extensionsDir"];
38228
38203
  });
38229
38204
 
38230
38205
  // src/lib/registry-data/development-tools.ts
@@ -39729,7 +39704,7 @@ function readPortableSkillManifest(skillPath, fallbackName = basename(skillPath)
39729
39704
  const pkg = existsSync8(pkgPath) ? readJsonObject(pkgPath) : undefined;
39730
39705
  const name = normalizePortableSkillName(stringField(jsonManifest, "name") ?? frontmatter?.name ?? stringValue(pkg?.name) ?? fallbackName);
39731
39706
  const description = stringField(jsonManifest, "description") ?? frontmatter?.description ?? stringValue(pkg?.description) ?? `${name} skill`;
39732
- const version = stringField(jsonManifest, "version") ?? frontmatter?.version ?? stringValue(pkg?.version) ?? PORTABLE_SKILL_DEFAULT_VERSION;
39707
+ const version = readDeclaredSkillVersion(skillPath) ?? PORTABLE_SKILL_DEFAULT_VERSION;
39733
39708
  const kind = parseSkillKind(stringField(jsonManifest, "kind") ?? frontmatter?.kind);
39734
39709
  const commands = parseManifestCommands(jsonManifest) ?? (kind === "instruction" ? [] : inferPackageCommands(pkg, name)) ?? [];
39735
39710
  return {
@@ -39753,6 +39728,15 @@ function parseSkillKind(value) {
39753
39728
  return value;
39754
39729
  return;
39755
39730
  }
39731
+ function readDeclaredSkillVersion(skillPath) {
39732
+ const skillJsonPath = join8(skillPath, "skill.json");
39733
+ const skillMdPath = join8(skillPath, "SKILL.md");
39734
+ const pkgPath = join8(skillPath, "package.json");
39735
+ const jsonManifest = existsSync8(skillJsonPath) ? readJsonObject(skillJsonPath) : undefined;
39736
+ const frontmatter = existsSync8(skillMdPath) ? parseSkillFrontmatter(readFileSync6(skillMdPath, "utf-8")) ?? undefined : undefined;
39737
+ const pkg = existsSync8(pkgPath) ? readJsonObject(pkgPath) : undefined;
39738
+ return stringField(jsonManifest, "version") ?? frontmatter?.version ?? stringValue(pkg?.version);
39739
+ }
39756
39740
  function createInstructionManifest(name, options) {
39757
39741
  return {
39758
39742
  $schema: PORTABLE_SKILL_SCHEMA,
@@ -42858,7 +42842,7 @@ import {
42858
42842
  statSync as statSync8,
42859
42843
  writeFileSync as writeFileSync5
42860
42844
  } from "fs";
42861
- import { homedir as homedir4 } from "os";
42845
+ import { homedir as homedir3 } from "os";
42862
42846
  import { basename as basename3, dirname as dirname4, join as join12 } from "path";
42863
42847
  function isSyncAgent(value) {
42864
42848
  return SYNC_AGENTS.includes(value);
@@ -42871,7 +42855,7 @@ function resolveSyncAgents(arg) {
42871
42855
  }
42872
42856
  return [arg];
42873
42857
  }
42874
- function agentGlobalSkillsDir(agent, homeDir = homedir4()) {
42858
+ function agentGlobalSkillsDir(agent, homeDir = homedir3()) {
42875
42859
  switch (agent) {
42876
42860
  case "opencode":
42877
42861
  return join12(homeDir, ".config", "opencode", "skills");
@@ -42966,7 +42950,7 @@ function isDirectory(path) {
42966
42950
  function syncSkillsToAgents(options = {}) {
42967
42951
  const requested = normalizeRequested(options.names);
42968
42952
  const agents = options.agents?.length ? options.agents : [...SYNC_AGENTS];
42969
- const homeDir = options.homeDir ?? homedir4();
42953
+ const homeDir = options.homeDir ?? homedir3();
42970
42954
  const { roots, source } = resolveSyncCorpus(options);
42971
42955
  const corpus = listPortableSkillsAcrossRoots(roots);
42972
42956
  const byName = new Map(corpus.map((skill) => [skill.name, skill]));
@@ -43021,7 +43005,7 @@ function syncSkillsToAgents(options = {}) {
43021
43005
  return { actions };
43022
43006
  }
43023
43007
  function writeManagedAgentSkill(params) {
43024
- const homeDir = params.homeDir ?? homedir4();
43008
+ const homeDir = params.homeDir ?? homedir3();
43025
43009
  const dir = join12(agentGlobalSkillsDir(params.agent, homeDir), params.skill);
43026
43010
  const result2 = writeManagedSkillDir(dir, params.skillMd, {
43027
43011
  skill: params.skill,
@@ -43339,7 +43323,7 @@ __export(exports_installer, {
43339
43323
  });
43340
43324
  import { existsSync as existsSync14, readFileSync as readFileSync10, rmSync as rmSync4 } from "fs";
43341
43325
  import { dirname as dirname5, join as join14 } from "path";
43342
- import { homedir as homedir5 } from "os";
43326
+ import { homedir as homedir4 } from "os";
43343
43327
  import { fileURLToPath } from "url";
43344
43328
  function findSkillsDir() {
43345
43329
  let dir = __dirname2;
@@ -43530,11 +43514,11 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
43530
43514
  const base2 = projectDir || process.cwd();
43531
43515
  switch (agent) {
43532
43516
  case "pi":
43533
- return scope === "project" ? join14(base2, ".pi", "skills") : join14(homedir5(), ".pi", "agent", "skills");
43517
+ return scope === "project" ? join14(base2, ".pi", "skills") : join14(homedir4(), ".pi", "agent", "skills");
43534
43518
  case "opencode":
43535
- return scope === "project" ? join14(base2, ".opencode", "skills") : join14(homedir5(), ".config", "opencode", "skills");
43519
+ return scope === "project" ? join14(base2, ".opencode", "skills") : join14(homedir4(), ".config", "opencode", "skills");
43536
43520
  default:
43537
- return scope === "project" ? join14(base2, `.${agent}`, "skills") : join14(homedir5(), `.${agent}`, "skills");
43521
+ return scope === "project" ? join14(base2, `.${agent}`, "skills") : join14(homedir4(), `.${agent}`, "skills");
43538
43522
  }
43539
43523
  }
43540
43524
  function getAgentSkillPath(name, agent, scope = "global", projectDir) {
@@ -47719,116 +47703,811 @@ var init_zod = __esm(() => {
47719
47703
  init_external();
47720
47704
  });
47721
47705
 
47722
- // src/lib/api-url.ts
47723
- function resolveApiUrl(config = loadConfig(), env3 = process.env) {
47724
- const raw = env3[API_URL_ENV_VAR] || config[API_URL_CONFIG_KEY];
47725
- const trimmed = raw?.trim().replace(/\/+$/, "");
47726
- return trimmed || undefined;
47706
+ // ../contracts/dist/client/transport.js
47707
+ import { isIP } from "net";
47708
+ import { spawnSync } from "child_process";
47709
+ import { closeSync, fstatSync, openSync, readFileSync as readFileSync11 } from "fs";
47710
+ import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
47711
+ import { createRequire } from "module";
47712
+ import { hostname as osHostname } from "os";
47713
+ import { isAbsolute as isAbsolute3, join as join15 } from "path";
47714
+ function envToken(name) {
47715
+ return name.toUpperCase().replace(/-/g, "_");
47716
+ }
47717
+ function clientTransportEnvKeys(name) {
47718
+ const envSegment = envToken(name);
47719
+ return {
47720
+ apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
47721
+ apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
47722
+ };
47727
47723
  }
47728
- function requireApiUrl(action = "This command", config, env3) {
47729
- const resolved = resolveApiUrl(config ?? loadConfig(), env3 ?? process.env);
47730
- if (!resolved)
47731
- throw new MissingApiUrlError(action);
47732
- return resolved;
47724
+ function credentialOverrideEnvKey(name) {
47725
+ return `HASNA_${envToken(name)}_API_KEY_OVERRIDE`;
47733
47726
  }
47734
- var API_URL_ENV_VAR = "SKILLS_API_URL", API_URL_CONFIG_KEY = "apiUrl", MISSING_API_URL_HINT, MissingApiUrlError;
47735
- var init_api_url = __esm(() => {
47736
- init_config();
47737
- MISSING_API_URL_HINT = `set ${API_URL_ENV_VAR}=<your Skills instance origin>, ` + `or run: skills setup --api-url <your Skills instance origin>`;
47738
- MissingApiUrlError = class MissingApiUrlError extends Error {
47739
- code = "MISSING_API_URL";
47740
- constructor(action = "This command") {
47741
- super(`${action} requires a Skills API URL and none is configured \u2014 ${MISSING_API_URL_HINT}`);
47742
- this.name = "MissingApiUrlError";
47727
+ function credentialPointerEnvKey(name) {
47728
+ return `HASNA_${envToken(name)}_API_KEY_REF`;
47729
+ }
47730
+ function homeDir(env3) {
47731
+ const home = env3.HOME?.trim();
47732
+ return home ? home : null;
47733
+ }
47734
+ function absoluteOverride(env3, key) {
47735
+ const value = env3[key]?.trim();
47736
+ return value && isAbsolute3(value) ? value : null;
47737
+ }
47738
+ function hasnaHomeDir(env3) {
47739
+ const override = absoluteOverride(env3, HASNA_HOME_ENV_KEY);
47740
+ if (override)
47741
+ return override;
47742
+ const home = homeDir(env3);
47743
+ return home ? join15(home, HASNA_HOME_DIR) : null;
47744
+ }
47745
+ function appConfigDir(name, env3) {
47746
+ const configRoot = absoluteOverride(env3, HASNA_CONFIG_HOME_ENV_KEY);
47747
+ if (configRoot)
47748
+ return join15(configRoot, name);
47749
+ const root = hasnaHomeDir(env3);
47750
+ return root ? join15(root, name, CONFIG_SUBDIR) : null;
47751
+ }
47752
+ function credentialDiskSourceList(name, env3, profile = null) {
47753
+ if (!SAFE_APP_SLUG.test(name))
47754
+ return [];
47755
+ const directory = appConfigDir(name, env3);
47756
+ if (!directory)
47757
+ return [];
47758
+ const file = profile ? `${CREDENTIALS_FILE}-${profile}` : CREDENTIALS_FILE;
47759
+ return [{ path: join15(directory, file), tier: "disk" }];
47760
+ }
47761
+ function credentialDiskSources(name, env3) {
47762
+ return credentialDiskSourceList(name, env3, null).map((s) => s.path);
47763
+ }
47764
+ function profileDiskSources(name, env3, profile) {
47765
+ return credentialDiskSourceList(name, env3, profile).map((s) => s.path);
47766
+ }
47767
+ function parseEnvFile(text) {
47768
+ const values2 = new Map;
47769
+ const unusable = new Set;
47770
+ for (const rawLine of text.split(/\r?\n/)) {
47771
+ const line = rawLine.trim();
47772
+ if (line.length === 0 || line.startsWith("#"))
47773
+ continue;
47774
+ const withoutExport = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
47775
+ const equals = withoutExport.indexOf("=");
47776
+ if (equals <= 0)
47777
+ continue;
47778
+ const key = withoutExport.slice(0, equals).trim();
47779
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
47780
+ continue;
47781
+ let value = withoutExport.slice(equals + 1).trim();
47782
+ const quote = value[0];
47783
+ if (quote === '"' || quote === "'") {
47784
+ if (value.length < 2 || !value.endsWith(quote)) {
47785
+ unusable.add(key);
47786
+ continue;
47787
+ }
47788
+ value = value.slice(1, -1);
47743
47789
  }
47744
- };
47745
- });
47746
-
47747
- // src/lib/auth-store.ts
47748
- var exports_auth_store = {};
47749
- __export(exports_auth_store, {
47750
- saveAuthConfig: () => saveAuthConfig,
47751
- normalizeSkillsApiOrigin: () => normalizeSkillsApiOrigin,
47752
- getAuthFilePathReadOnly: () => getAuthFilePathReadOnly,
47753
- getAuthFilePath: () => getAuthFilePath,
47754
- getAuthConfigReadOnly: () => getAuthConfigReadOnly,
47755
- getAuthConfig: () => getAuthConfig,
47756
- getApiUrl: () => getApiUrl,
47757
- getApiKeyReadOnly: () => getApiKeyReadOnly,
47758
- getApiKey: () => getApiKey,
47759
- clearAuthConfig: () => clearAuthConfig
47760
- });
47761
- import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync7, unlinkSync } from "fs";
47762
- import { dirname as dirname6, join as join15 } from "path";
47763
- import { homedir as homedir6 } from "os";
47764
- function getAuthFilePath() {
47765
- return join15(getDataDir(), "auth.json");
47790
+ if (value.trim().length === 0) {
47791
+ unusable.add(key);
47792
+ continue;
47793
+ }
47794
+ if (values2.has(key) && values2.get(key) !== value)
47795
+ unusable.add(key);
47796
+ values2.set(key, value);
47797
+ }
47798
+ return { values: values2, unusable };
47766
47799
  }
47767
- function getAuthFilePathReadOnly() {
47768
- return join15(getDataDirReadOnly(), "auth.json");
47800
+ function configFileModeAllowed(mode) {
47801
+ const permissions = mode & 4095;
47802
+ return permissions === 256 || permissions === 384;
47769
47803
  }
47770
- function legacyAuthFilePath() {
47771
- return join15(process.env["HOME"] || process.env["USERPROFILE"] || homedir6(), ".skills", "auth.json");
47804
+ function configFileReadsCoherent(before2, after2) {
47805
+ return before2.dev === after2.dev && before2.ino === after2.ino && before2.size === after2.size && before2.mtimeMs === after2.mtimeMs && before2.ctimeMs === after2.ctimeMs;
47772
47806
  }
47773
- function getAuthConfig() {
47774
- if (cachedConfig !== undefined)
47775
- return cachedConfig;
47807
+ function readAppConfigFile(path) {
47808
+ const unsafe = (reason) => {
47809
+ throw new CredentialFileUnsafeError(path, reason);
47810
+ };
47811
+ let fd = -1;
47776
47812
  try {
47777
- const file = existsSync15(getAuthFilePath()) ? getAuthFilePath() : legacyAuthFilePath();
47778
- const raw = readFileSync11(file, "utf-8");
47779
- const config = JSON.parse(raw);
47780
- if (!config.apiKey) {
47781
- cachedConfig = null;
47813
+ fd = openSync(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
47814
+ } catch (error) {
47815
+ const code = error.code;
47816
+ if (code === "ENOENT" || code === "ENOTDIR")
47782
47817
  return null;
47818
+ if (code === "ELOOP")
47819
+ unsafe("the path is a symlink");
47820
+ unsafe(`the path could not be opened (${code ?? "unknown error"})`);
47821
+ }
47822
+ try {
47823
+ const before2 = fstatSync(fd);
47824
+ if (!before2.isFile())
47825
+ unsafe("the path is not a regular file");
47826
+ if (!configFileModeAllowed(before2.mode)) {
47827
+ unsafe(`permission mode ${(before2.mode & 4095).toString(8).padStart(4, "0")} is not owner-only 0400 or 0600`);
47828
+ }
47829
+ const uid = process.getuid?.() ?? process.geteuid?.();
47830
+ if (uid !== undefined && before2.uid !== uid)
47831
+ unsafe("the file is not owned by the current user");
47832
+ if (before2.size > MAX_CREDENTIAL_FILE_BYTES)
47833
+ unsafe("the file exceeds the size limit");
47834
+ const bytes = readFileSync11(fd);
47835
+ const after2 = fstatSync(fd);
47836
+ if (!configFileReadsCoherent(before2, after2)) {
47837
+ unsafe("the file changed while being read");
47838
+ }
47839
+ return parseEnvFile(bytes.toString("utf8"));
47840
+ } finally {
47841
+ if (fd !== -1)
47842
+ closeSync(fd);
47843
+ }
47844
+ }
47845
+ function readCredentialFile(path, apiKeyKeys) {
47846
+ const parsed = readAppConfigFile(path);
47847
+ if (!parsed)
47848
+ return null;
47849
+ for (const key of apiKeyKeys) {
47850
+ if (parsed.unusable.has(key)) {
47851
+ throw new CredentialFileUnsafeError(path, `${key} is declared but blank or malformed`);
47783
47852
  }
47784
- cachedConfig = config;
47785
- return config;
47786
- } catch {
47787
- cachedConfig = null;
47853
+ }
47854
+ const values2 = apiKeyKeys.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
47855
+ if (new Set(values2).size > 1) {
47856
+ throw new CredentialFileUnsafeError(path, "credential aliases disagree");
47857
+ }
47858
+ return values2[0] ?? null;
47859
+ }
47860
+ function appConfigDiskValue(name, env3, keys2) {
47861
+ const wanted = keys2.filter((key) => !CREDENTIAL_SHAPED_KEY.test(key));
47862
+ if (wanted.length === 0)
47788
47863
  return null;
47864
+ for (const path of credentialDiskSources(name, env3)) {
47865
+ const parsed = readAppConfigFile(path);
47866
+ if (!parsed)
47867
+ continue;
47868
+ if (wanted.some((key) => parsed.unusable.has(key))) {
47869
+ return { key: wanted.find((key) => parsed.unusable.has(key)), value: "", path, unusable: true };
47870
+ }
47871
+ const values2 = wanted.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
47872
+ if (new Set(values2).size > 1)
47873
+ throw new CredentialFileUnsafeError(path, "configuration aliases disagree");
47874
+ for (const key of wanted) {
47875
+ if (parsed.unusable.has(key))
47876
+ return { key, value: "", path, unusable: true };
47877
+ const value = parsed.values.get(key)?.trim();
47878
+ if (value)
47879
+ return { key, value, path };
47880
+ }
47789
47881
  }
47882
+ return null;
47790
47883
  }
47791
- function saveAuthConfig(config) {
47792
- const file = getAuthFilePath();
47793
- mkdirSync7(dirname6(file), { recursive: true, mode: 448 });
47794
- writeFileSync7(file, JSON.stringify(config, null, 2) + `
47795
- `, { mode: 384 });
47796
- cachedConfig = config;
47884
+ function assertUsableCredential(appName, source, value) {
47885
+ if (VAULT_POINTER_SHAPE.test(value)) {
47886
+ throw new CredentialResolutionError(appName, `The credential from ${source} looks like a secrets-vault pointer (a path-shaped reference like ` + `'namespace/app/live/api_key'). A vault path is NEVER accepted as a literal API key. ` + `Use ${credentialPointerEnvKey(appName)} to resolve the key through the vault, or provide the actual key value.`, [source]);
47887
+ }
47888
+ if (!ILLEGAL_IN_HEADER_VALUE.test(value))
47889
+ return;
47890
+ throw new CredentialResolutionError(appName, `The credential from ${source} contains characters that cannot be sent in an HTTP header ` + `(a control character or non-ASCII byte). A file written with CR-only line endings is the usual ` + `cause. Rewrite that credential file with one LF-terminated KEY=value line. ` + `The value is not shown here, and is deliberately never logged.`, [source]);
47891
+ }
47892
+ function sealCredential(fields) {
47893
+ const { apiKey } = fields;
47894
+ const visible = {
47895
+ tier: fields.tier,
47896
+ source: fields.source,
47897
+ deliberate: fields.deliberate,
47898
+ diskCandidates: Object.freeze([...fields.diskCandidates]),
47899
+ warning: fields.warning
47900
+ };
47901
+ const sealed = { ...visible };
47902
+ Object.defineProperty(sealed, "apiKey", {
47903
+ value: apiKey,
47904
+ enumerable: false,
47905
+ writable: false,
47906
+ configurable: false
47907
+ });
47908
+ if (fields.pointerVaultKey !== undefined) {
47909
+ Object.defineProperty(sealed, "pointerVaultKey", {
47910
+ value: fields.pointerVaultKey,
47911
+ enumerable: false,
47912
+ writable: false,
47913
+ configurable: false
47914
+ });
47915
+ }
47916
+ Object.defineProperty(sealed, INSPECT_CUSTOM, {
47917
+ value: () => ({ ...visible, apiKey: "[redacted]" }),
47918
+ enumerable: false,
47919
+ writable: false,
47920
+ configurable: false
47921
+ });
47922
+ Object.defineProperty(sealed, CREDENTIAL_SEAL, {
47923
+ value: true,
47924
+ enumerable: false,
47925
+ writable: false,
47926
+ configurable: false
47927
+ });
47928
+ return Object.freeze(sealed);
47929
+ }
47930
+ function firstEnvValue(env3, keys2) {
47931
+ for (const key of keys2) {
47932
+ if (!Object.prototype.hasOwnProperty.call(env3, key))
47933
+ continue;
47934
+ const value = env3[key]?.trim();
47935
+ if (value)
47936
+ return { key, value };
47937
+ }
47938
+ return null;
47939
+ }
47940
+ function isAmbientEnvironment(env3) {
47941
+ return env3 === process.env || env3[AMBIENT_ENVIRONMENT] === true;
47942
+ }
47943
+ function defaultKeychainRunner(argv) {
47944
+ const result2 = spawnSync(KEYCHAIN_SECURITY_BIN, [...argv], {
47945
+ encoding: "utf8",
47946
+ stdio: ["ignore", "pipe", "pipe"],
47947
+ timeout: KEYCHAIN_SPAWN_TIMEOUT_MS
47948
+ });
47949
+ return {
47950
+ status: result2.status,
47951
+ stdout: result2.stdout ?? "",
47952
+ stderr: result2.error ? result2.error.message : result2.stderr ?? ""
47953
+ };
47797
47954
  }
47798
- function clearAuthConfig() {
47955
+ function keychainTierEnabled(env3, options) {
47956
+ if ((options.platform ?? process.platform) !== "darwin")
47957
+ return false;
47958
+ if (options.enabled !== undefined)
47959
+ return options.enabled;
47960
+ return options.run !== undefined || isAmbientEnvironment(env3);
47961
+ }
47962
+ function keychainAccount(env3, options) {
47963
+ const station = env3[KEYCHAIN_STATION_ENV_KEY]?.trim();
47964
+ if (station)
47965
+ return station;
47966
+ const host = (options.hostname ?? osHostname)().split(".")[0]?.trim() ?? "";
47967
+ if (host)
47968
+ return host;
47969
+ const user = env3.USER?.trim();
47970
+ return user || null;
47971
+ }
47972
+ function keychainFailureHint(text) {
47973
+ const line = text.split(/\r?\n/).find((entry) => entry.trim().length > 0)?.trim() ?? "";
47974
+ const clean = line.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 200);
47975
+ return clean ? `: ${clean}` : "";
47976
+ }
47977
+ function readKeychainItem(name, env3, kind, options) {
47978
+ if (!SAFE_APP_SLUG.test(name) || !keychainTierEnabled(env3, options))
47979
+ return null;
47980
+ const account = keychainAccount(env3, options);
47981
+ if (!account)
47982
+ return null;
47983
+ const service = `${KEYCHAIN_SERVICE_PREFIX}.${name}.${kind}`;
47984
+ const source = `keychain:${service}@${account}`;
47985
+ const run = options.run ?? defaultKeychainRunner;
47986
+ let result2;
47799
47987
  try {
47800
- unlinkSync(getAuthFilePath());
47801
- } catch {}
47988
+ result2 = run(["find-generic-password", "-a", account, "-s", service, "-w"]);
47989
+ } catch (error) {
47990
+ const reason = keychainFailureHint(error instanceof Error ? error.message : String(error));
47991
+ throw new CredentialResolutionError(name, `The Keychain lookup for ${source} could not run${reason}. A Keychain failure is never resolved ` + `around: fix the keychain, or delete the item to fall through to the credential on disk.`, [source]);
47992
+ }
47993
+ if (result2.status === KEYCHAIN_ITEM_NOT_FOUND_STATUS)
47994
+ return null;
47995
+ if (result2.status !== 0) {
47996
+ throw new CredentialResolutionError(name, `The Keychain lookup for ${source} failed (security exited ` + `${result2.status ?? "without a status"}${keychainFailureHint(result2.stderr)}). A Keychain item that ` + `exists but cannot be read is never resolved around: unlock the keychain, run from a session that ` + `may use it, or delete the item to fall through to the credential on disk.`, [source]);
47997
+ }
47998
+ const value = result2.stdout.trim();
47999
+ if (!value) {
48000
+ throw new CredentialResolutionError(name, `${source} exists but holds an empty value; a declared item never falls through to another ` + `identity. Store a value in it or delete the item.`, [source]);
48001
+ }
48002
+ return { value, source };
48003
+ }
48004
+ function keychainConfigValue(name, env3, options = {}) {
48005
+ return readKeychainItem(name, env3, "api-url", options);
48006
+ }
48007
+ function snapshotClientEnvironment(name, env3) {
48008
+ const keys2 = clientTransportEnvKeys(name);
48009
+ const ambient = isAmbientEnvironment(env3);
48010
+ const snapshot = Object.create(null);
48011
+ for (const key of [
48012
+ ...keys2.apiUrlKeys,
48013
+ ...keys2.apiKeyKeys,
48014
+ credentialOverrideEnvKey(name),
48015
+ credentialPointerEnvKey(name),
48016
+ CREDENTIAL_PROFILE_ENV_KEY,
48017
+ "HOME",
48018
+ HASNA_HOME_ENV_KEY,
48019
+ HASNA_CONFIG_HOME_ENV_KEY,
48020
+ KEYCHAIN_STATION_ENV_KEY,
48021
+ "USER"
48022
+ ]) {
48023
+ const descriptor = Object.getOwnPropertyDescriptor(env3, key);
48024
+ if (!descriptor)
48025
+ continue;
48026
+ if (!("value" in descriptor)) {
48027
+ throw new CredentialResolutionError(name, `${key} is accessor-backed; client configuration requires own data properties.`, [key]);
48028
+ }
48029
+ if (descriptor.value !== undefined && typeof descriptor.value !== "string") {
48030
+ throw new CredentialResolutionError(name, `${key} must be a string data property.`, [key]);
48031
+ }
48032
+ snapshot[key] = descriptor.value;
48033
+ }
48034
+ if (ambient) {
48035
+ Object.defineProperty(snapshot, AMBIENT_ENVIRONMENT, {
48036
+ value: true,
48037
+ enumerable: false,
48038
+ writable: false,
48039
+ configurable: false
48040
+ });
48041
+ }
48042
+ return Object.freeze(snapshot);
48043
+ }
48044
+ function resolveCredential(name, env3, options = {}) {
48045
+ env3 = snapshotClientEnvironment(name, env3);
48046
+ const { apiKeyKeys } = clientTransportEnvKeys(name);
48047
+ const diskPaths = credentialDiskSources(name, env3);
48048
+ if (options.apiKey !== undefined) {
48049
+ const explicitKey = options.apiKey.trim();
48050
+ if (!explicitKey) {
48051
+ throw new CredentialResolutionError(name, "The explicit apiKey argument is blank; an explicit credential never falls through to another identity.", ["explicit apiKey argument"]);
48052
+ }
48053
+ assertUsableCredential(name, "the explicit apiKey argument", explicitKey);
48054
+ return sealCredential({
48055
+ apiKey: explicitKey,
48056
+ tier: "argument",
48057
+ source: "explicit apiKey argument",
48058
+ deliberate: true,
48059
+ diskCandidates: diskPaths,
48060
+ warning: null
48061
+ });
48062
+ }
48063
+ const overrideKeyName = credentialOverrideEnvKey(name);
48064
+ const overrideRaw = Object.prototype.hasOwnProperty.call(env3, overrideKeyName) ? env3[overrideKeyName] : undefined;
48065
+ if (overrideRaw !== undefined) {
48066
+ const override = overrideRaw.trim();
48067
+ if (!override) {
48068
+ throw new CredentialResolutionError(name, `${overrideKeyName} is set but empty. It is a deliberate override, so it is not resolved around: ` + `either give it a real key or unset it to fall back to the credential on disk.`, [overrideKeyName]);
48069
+ }
48070
+ assertUsableCredential(name, overrideKeyName, override);
48071
+ return sealCredential({
48072
+ apiKey: override,
48073
+ tier: "override",
48074
+ source: overrideKeyName,
48075
+ deliberate: true,
48076
+ diskCandidates: diskPaths,
48077
+ warning: null
48078
+ });
48079
+ }
48080
+ const pointerKeyName = credentialPointerEnvKey(name);
48081
+ const pointerRaw = Object.prototype.hasOwnProperty.call(env3, pointerKeyName) ? env3[pointerKeyName] : undefined;
48082
+ if (pointerRaw !== undefined) {
48083
+ const pointer = pointerRaw.trim();
48084
+ if (!pointer) {
48085
+ throw new CredentialResolutionError(name, `${pointerKeyName} is set but empty. It is a deliberate vault pointer, so it is not resolved around: ` + `either give it a vault item key or unset it to fall back to the credential on disk.`, [pointerKeyName]);
48086
+ }
48087
+ if (!VAULT_POINTER_SHAPE.test(pointer)) {
48088
+ throw new CredentialResolutionError(name, `${pointerKeyName} must name a vault ITEM KEY (a path-shaped reference like ` + `'namespace/app/live/api_key'), not a credential value. A pointer that carries a literal is refused.`, [pointerKeyName]);
48089
+ }
48090
+ return sealCredential({
48091
+ apiKey: "",
48092
+ pointerVaultKey: pointer,
48093
+ tier: "pointer",
48094
+ source: pointerKeyName,
48095
+ deliberate: true,
48096
+ diskCandidates: diskPaths,
48097
+ warning: null
48098
+ });
48099
+ }
48100
+ if (options.profile !== undefined && !options.profile.trim()) {
48101
+ throw new CredentialResolutionError(name, "The explicit profile argument is blank; an explicit identity selection never falls through.", ["explicit profile argument"]);
48102
+ }
48103
+ const profileRaw = Object.prototype.hasOwnProperty.call(env3, CREDENTIAL_PROFILE_ENV_KEY) ? env3[CREDENTIAL_PROFILE_ENV_KEY] : undefined;
48104
+ if (profileRaw !== undefined && !profileRaw.trim()) {
48105
+ throw new CredentialResolutionError(name, `${CREDENTIAL_PROFILE_ENV_KEY} is set but blank.`, [CREDENTIAL_PROFILE_ENV_KEY]);
48106
+ }
48107
+ const profile = options.profile?.trim() || profileRaw?.trim();
48108
+ if (profile) {
48109
+ const profileSource = options.profile?.trim() ? "explicit profile argument" : CREDENTIAL_PROFILE_ENV_KEY;
48110
+ if (!SAFE_PROFILE.test(profile)) {
48111
+ throw new CredentialResolutionError(name, `Profile name from ${profileSource} is not usable in a path. ` + `Use letters, digits, dot, dash, or underscore.`, [profileSource]);
48112
+ }
48113
+ const paths = profileDiskSources(name, env3, profile);
48114
+ for (const path of paths) {
48115
+ const value = readCredentialFile(path, apiKeyKeys);
48116
+ if (value) {
48117
+ assertUsableCredential(name, path, value);
48118
+ return sealCredential({
48119
+ apiKey: value,
48120
+ tier: "profile",
48121
+ source: path,
48122
+ deliberate: true,
48123
+ diskCandidates: paths,
48124
+ warning: null
48125
+ });
48126
+ }
48127
+ }
48128
+ throw new CredentialResolutionError(name, `Profile '${profile}' (from ${profileSource}) has no ${apiKeyKeys[0]} for '${name}'. ` + `Looked in: ${paths.join(", ") || "<no HOME in this environment>"}. ` + `A profile names WHICH identity to use, so it is never resolved around \u2014 ` + `create the profile's credential file or unset ${CREDENTIAL_PROFILE_ENV_KEY}.`, paths);
48129
+ }
48130
+ const definedEnvEntries = apiKeyKeys.filter((key) => Object.prototype.hasOwnProperty.call(env3, key) && env3[key] !== undefined).map((key) => ({ key, value: String(env3[key]).trim() }));
48131
+ const blankEnv = definedEnvEntries.find((entry) => entry.value.length === 0);
48132
+ if (blankEnv) {
48133
+ throw new CredentialResolutionError(name, `${blankEnv.key} is set but blank; a declared credential never falls through to another alias or identity.`, [blankEnv.key]);
48134
+ }
48135
+ if (definedEnvEntries.length > 1 && new Set(definedEnvEntries.map((entry) => entry.value)).size > 1) {
48136
+ throw new CredentialResolutionError(name, `${definedEnvEntries.map((entry) => entry.key).join(" and ")} disagree; credential aliases must be identical or only one may be set.`, definedEnvEntries.map((entry) => entry.key));
48137
+ }
48138
+ const envHit = firstEnvValue(env3, apiKeyKeys);
48139
+ const keychainHit = readKeychainItem(name, env3, "api-key", options.keychain ?? {});
48140
+ if (keychainHit) {
48141
+ assertUsableCredential(name, keychainHit.source, keychainHit.value);
48142
+ const warning = envHit && envHit.value !== keychainHit.value ? `Credential sources disagree for '${name}': ${keychainHit.source} and ${envHit.key} hold ` + `different keys. ${keychainHit.source} wins, because the Keychain is re-read on every call while ` + `an environment variable is a snapshot. Reconcile them \u2014 a rotation that updated only one leaves ` + `the other to fail 401 wherever it is loaded first.` : null;
48143
+ return sealCredential({
48144
+ apiKey: keychainHit.value,
48145
+ tier: "keychain",
48146
+ source: keychainHit.source,
48147
+ deliberate: false,
48148
+ diskCandidates: diskPaths,
48149
+ warning
48150
+ });
48151
+ }
48152
+ const diskSourceList = credentialDiskSourceList(name, env3, null);
48153
+ const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys) })).filter((hit) => hit.value !== null);
48154
+ if (diskHits.length > 0) {
48155
+ const winner = diskHits[0];
48156
+ assertUsableCredential(name, winner.src.path, winner.value);
48157
+ const divergentSources = [
48158
+ ...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.src.path),
48159
+ ...envHit && envHit.value !== winner.value ? [envHit.key] : []
48160
+ ];
48161
+ const warning = divergentSources.length > 0 ? `Credential sources disagree for '${name}': ${winner.src.path} and ` + `${divergentSources.join(", ")} hold different keys. ${winner.src.path} wins, because a file on ` + `disk is re-read on every call while an environment variable is a snapshot. Reconcile them \u2014 ` + `a rotation that updated only one leaves the other to fail 401 wherever it is loaded first.` : null;
48162
+ return sealCredential({
48163
+ apiKey: winner.value,
48164
+ tier: winner.src.tier,
48165
+ source: winner.src.path,
48166
+ deliberate: false,
48167
+ diskCandidates: diskPaths,
48168
+ warning
48169
+ });
48170
+ }
48171
+ if (envHit) {
48172
+ assertUsableCredential(name, envHit.key, envHit.value);
48173
+ return sealCredential({
48174
+ apiKey: envHit.value,
48175
+ tier: "env",
48176
+ source: envHit.key,
48177
+ deliberate: false,
48178
+ diskCandidates: diskPaths,
48179
+ warning: null
48180
+ });
48181
+ }
48182
+ return null;
48183
+ }
48184
+ async function completePointerCredential(name, pointerResolution, env3 = process.env) {
48185
+ const vaultKey = pointerResolution.pointerVaultKey;
48186
+ const pointerEnvKey = pointerResolution.source;
48187
+ if (!vaultKey) {
48188
+ throw new CredentialResolutionError(name, `Pointer resolution from ${pointerEnvKey} carries no vault item key; this is a defect in the resolver.`, [pointerEnvKey]);
48189
+ }
48190
+ let secretsSdk;
47802
48191
  try {
47803
- unlinkSync(legacyAuthFilePath());
47804
- } catch {}
47805
- cachedConfig = undefined;
48192
+ secretsSdk = requireSecretsSdk(SECRETS_PACKAGE_SPECIFIER);
48193
+ } catch {
48194
+ throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets SDK (@hasna/secrets) is not installed ` + `in this process. A vault pointer is TERMINAL: install @hasna/secrets to resolve it, or unset ${pointerEnvKey}.`, [pointerEnvKey]);
48195
+ }
48196
+ let client;
48197
+ try {
48198
+ client = secretsSdk.createSecretsClientFromEnv(env3);
48199
+ } catch {
48200
+ throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets client could not be configured from this ` + `environment (the secrets service URL and key env are missing or invalid). A vault pointer is TERMINAL and ` + `never falls through to a literal or disk credential.`, [pointerEnvKey]);
48201
+ }
48202
+ let secret;
48203
+ try {
48204
+ secret = await client.getSecret({ key: vaultKey });
48205
+ } catch {
48206
+ throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the vault could not be reached or the item is ` + `unavailable. A vault pointer is TERMINAL and never falls through to a literal or disk credential.`, [pointerEnvKey]);
48207
+ }
48208
+ const value = secret.value;
48209
+ if (!value) {
48210
+ throw new CredentialResolutionError(name, `${pointerEnvKey} resolved vault item '${vaultKey}', but it holds no value. A vault pointer is TERMINAL.`, [pointerEnvKey]);
48211
+ }
48212
+ assertUsableCredential(name, `${pointerEnvKey} -> vault:${vaultKey}`, value);
48213
+ return sealCredential({
48214
+ apiKey: value,
48215
+ tier: "pointer",
48216
+ source: `${pointerEnvKey} -> vault:${vaultKey}`,
48217
+ deliberate: true,
48218
+ diskCandidates: pointerResolution.diskCandidates,
48219
+ warning: null
48220
+ });
47806
48221
  }
47807
- function getApiKey() {
47808
- if (process.env.SKILLS_API_KEY)
47809
- return process.env.SKILLS_API_KEY;
47810
- if (process.env.SKILL_API_KEY)
47811
- return process.env.SKILL_API_KEY;
47812
- return getAuthConfig()?.apiKey || null;
48222
+ function defaultFleetGatewayBaseUrl(name) {
48223
+ return `${DEFAULT_FLEET_GATEWAY_ORIGIN}/${validateAppSlug(name)}`;
47813
48224
  }
47814
- function getAuthConfigReadOnly() {
48225
+ function isValidDnsDomain(value) {
48226
+ if (value.length === 0 || value.length > 253 || ASCII_CONTROL_PATTERN.test(value) || /[^\x00-\x7f]/.test(value)) {
48227
+ return false;
48228
+ }
48229
+ return value.split(".").every((label) => label.length <= 63 && !label.startsWith("xn--") && DNS_LABEL_PATTERN.test(label));
48230
+ }
48231
+ function validateAppSlug(name) {
48232
+ if (name.length > 63 || !DNS_LABEL_PATTERN.test(name)) {
48233
+ throw new Error("App name must be one lowercase DNS label.");
48234
+ }
48235
+ return name;
48236
+ }
48237
+ function rawAuthority(value) {
48238
+ const match = /^[a-z][a-z0-9+.-]*:\/\//i.exec(value);
48239
+ if (!match)
48240
+ throw new Error("API URL must be absolute.");
48241
+ const afterScheme = value.slice(match[0].length);
48242
+ const boundary = afterScheme.search(/[/?#]/);
48243
+ const authority = boundary === -1 ? afterScheme : afterScheme.slice(0, boundary);
48244
+ if (!authority)
48245
+ throw new Error("API URL must include a hostname.");
48246
+ return authority;
48247
+ }
48248
+ function assertCanonicalPort(port) {
48249
+ if (!/^[0-9]+$/.test(port) || port.length > 1 && port.startsWith("0")) {
48250
+ throw new Error("API URL authority must contain a canonical port between 1 and 65535.");
48251
+ }
48252
+ const numericPort = Number(port);
48253
+ if (!Number.isSafeInteger(numericPort) || numericPort < 1 || numericPort > 65535) {
48254
+ throw new Error("API URL authority must contain a canonical port between 1 and 65535.");
48255
+ }
48256
+ }
48257
+ function canonicalAuthorityHostname(authority) {
48258
+ let rawHostname;
48259
+ if (authority.startsWith("[")) {
48260
+ const closingBracket = authority.indexOf("]");
48261
+ if (closingBracket === -1) {
48262
+ throw new Error("API URL authority must contain a canonical hostname.");
48263
+ }
48264
+ rawHostname = authority.slice(0, closingBracket + 1);
48265
+ const portSuffix = authority.slice(closingBracket + 1);
48266
+ if (portSuffix) {
48267
+ if (!portSuffix.startsWith(":")) {
48268
+ throw new Error("API URL authority must contain a canonical hostname and port.");
48269
+ }
48270
+ assertCanonicalPort(portSuffix.slice(1));
48271
+ }
48272
+ if (isIP(rawHostname.slice(1, -1)) !== 6) {
48273
+ throw new Error("API URL authority must contain a canonical IPv6 literal.");
48274
+ }
48275
+ } else {
48276
+ const firstColon = authority.indexOf(":");
48277
+ const lastColon = authority.lastIndexOf(":");
48278
+ if (firstColon !== lastColon) {
48279
+ throw new Error("IPv6 API URL authorities must use brackets.");
48280
+ }
48281
+ if (lastColon !== -1) {
48282
+ const port = authority.slice(lastColon + 1);
48283
+ assertCanonicalPort(port);
48284
+ rawHostname = authority.slice(0, lastColon);
48285
+ } else {
48286
+ rawHostname = authority;
48287
+ }
48288
+ const ipVersion = isIP(rawHostname);
48289
+ const numericAddressParts = rawHostname.split(".");
48290
+ const looksLikeNonCanonicalIpv4 = numericAddressParts.every((part) => /^(?:0x[0-9a-f]+|[0-9]+)$/i.test(part));
48291
+ if (ipVersion !== 4 && looksLikeNonCanonicalIpv4 || ipVersion !== 4 && !isValidDnsDomain(rawHostname.toLowerCase())) {
48292
+ throw new Error("API URL authority must contain a canonical ASCII hostname.");
48293
+ }
48294
+ }
48295
+ return rawHostname.toLowerCase();
48296
+ }
48297
+ function isDeliberateLoopbackHttpAuthority(authority) {
48298
+ return /^(?:localhost|127\.0\.0\.1|\[::1\])(?::[0-9]+)?$/i.test(authority);
48299
+ }
48300
+ function toV1BaseUrl(apiUrl) {
48301
+ if (ASCII_CONTROL_PATTERN.test(apiUrl)) {
48302
+ throw new Error("API URL must not contain ASCII control characters.");
48303
+ }
48304
+ const input = apiUrl.trim();
48305
+ const authority = rawAuthority(input);
48306
+ if (authority.includes("@") || authority.includes("\\") || authority.includes("%") || /[^\x00-\x7f]/.test(authority)) {
48307
+ throw new Error("API URL authority must be canonical ASCII without credentials.");
48308
+ }
48309
+ const canonicalHostname = canonicalAuthorityHostname(authority);
48310
+ const url = new URL(input);
48311
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
48312
+ throw new Error("API URL must use http or https.");
48313
+ }
48314
+ if (url.username || url.password) {
48315
+ throw new Error("API URL must not include credentials.");
48316
+ }
48317
+ if (!url.hostname || url.hostname.endsWith(".")) {
48318
+ throw new Error("API URL must include a canonical hostname.");
48319
+ }
48320
+ if (url.hostname.toLowerCase() !== canonicalHostname) {
48321
+ throw new Error("API URL authority must not rely on parser hostname normalization.");
48322
+ }
48323
+ if (url.hostname.split(".").some((label) => label.toLowerCase().startsWith("xn--"))) {
48324
+ throw new Error("API URL must not use IDN or punycode hostnames.");
48325
+ }
48326
+ if (url.protocol === "http:" && !isDeliberateLoopbackHttpAuthority(authority)) {
48327
+ throw new Error("API URL may use http only for an exact loopback authority.");
48328
+ }
48329
+ if (url.search || url.hash) {
48330
+ throw new Error("API URL must not include a query string or fragment.");
48331
+ }
48332
+ let path = url.pathname.replace(/\/+$/, "");
48333
+ if (path.endsWith("/v1"))
48334
+ path = path.slice(0, -"/v1".length);
48335
+ url.pathname = `${path}/v1`;
48336
+ return url.toString().replace(/\/+$/, "");
48337
+ }
48338
+ function resolveClientTransportSnapshot(name, env3 = process.env, options = {}) {
48339
+ env3 = snapshotClientEnvironment(name, env3);
48340
+ const keys2 = clientTransportEnvKeys(name);
48341
+ const definedUrlEntries = keys2.apiUrlKeys.filter((key) => Object.prototype.hasOwnProperty.call(env3, key) && env3[key] !== undefined).map((key) => ({ key, raw: String(env3[key]) }));
48342
+ const blankUrl = definedUrlEntries.find((entry) => entry.raw.trim().length === 0);
48343
+ if (blankUrl) {
48344
+ 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]);
48345
+ }
48346
+ const controlledUrl = definedUrlEntries.find((entry) => ASCII_CONTROL_PATTERN.test(entry.raw));
48347
+ if (controlledUrl) {
48348
+ throw new ClientTransportConfigurationError(name, `${controlledUrl.key} contains ASCII control characters.`, [controlledUrl.key]);
48349
+ }
48350
+ const usableUrlEntries = definedUrlEntries.map((entry) => ({ key: entry.key, value: entry.raw.trim() }));
48351
+ if (usableUrlEntries.length > 1 && new Set(usableUrlEntries.map((entry) => entry.value)).size > 1) {
48352
+ 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));
48353
+ }
48354
+ const envUrlHit = usableUrlEntries[0] ?? null;
48355
+ const keychainUrlHit = keychainConfigValue(name, env3, options.credentials?.keychain);
48356
+ const diskConfigUrlHit = appConfigDiskValue(name, env3, keys2.apiUrlKeys);
48357
+ if (diskConfigUrlHit?.unusable) {
48358
+ 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]);
48359
+ }
48360
+ const urlCandidates = [
48361
+ ...envUrlHit ? [envUrlHit] : [],
48362
+ ...keychainUrlHit ? [{ key: keychainUrlHit.source, value: keychainUrlHit.value }] : [],
48363
+ ...diskConfigUrlHit ? [{ key: diskConfigUrlHit.path, value: diskConfigUrlHit.value.trim() }] : []
48364
+ ];
48365
+ const configuredUrl = urlCandidates[0] ?? null;
48366
+ const divergentUrls = urlCandidates.filter((candidate) => candidate.value !== configuredUrl?.value);
48367
+ if (configuredUrl && divergentUrls.length > 0) {
48368
+ 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));
48369
+ }
48370
+ const warnings = [];
48371
+ if (configuredUrl && !envUrlHit) {
48372
+ warnings.push(`No ${keys2.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.`);
48373
+ }
48374
+ const credential = resolveCredential(name, env3, options.credentials);
48375
+ if (!credential) {
48376
+ const diskHint = credentialDiskSourcesForMessage(name, env3);
48377
+ const lead = configuredUrl ? `${configuredUrl.key} selects the HTTP server for '${name}', but no API key could be resolved` : `${keys2.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`;
48378
+ 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 ${keys2.apiKeyKeys[0]} in the environment.`);
48379
+ throw new ClientTransportConfigurationError(name, warnings.join(" "), [configuredUrl?.key ?? keys2.apiUrlKeys[0]]);
48380
+ }
48381
+ if (credential.warning)
48382
+ warnings.push(credential.warning);
48383
+ let urlHit;
48384
+ if (configuredUrl) {
48385
+ urlHit = configuredUrl;
48386
+ } else {
48387
+ try {
48388
+ urlHit = { key: DEFAULT_AUTHORITY_SOURCE, value: defaultFleetGatewayBaseUrl(name) };
48389
+ } catch (error) {
48390
+ const message = error instanceof Error ? error.message : String(error);
48391
+ throw new ClientTransportConfigurationError(name, `No ${keys2.apiUrlKeys[0]} is configured and the default fleet gateway authority cannot be composed for '${name}': ${message}`, [keys2.apiUrlKeys[0]]);
48392
+ }
48393
+ }
48394
+ const apiUrlSource = urlHit.key;
48395
+ let baseUrl;
47815
48396
  try {
47816
- const file = existsSync15(getAuthFilePathReadOnly()) ? getAuthFilePathReadOnly() : legacyAuthFilePath();
47817
- const raw = readFileSync11(file, "utf-8");
47818
- const config = JSON.parse(raw);
47819
- if (!config.apiKey)
47820
- return null;
47821
- return config;
47822
- } catch {
47823
- return null;
48397
+ baseUrl = toV1BaseUrl(urlHit.value);
48398
+ } catch (error) {
48399
+ const message = error instanceof Error ? error.message : String(error);
48400
+ throw new ClientTransportConfigurationError(name, `Invalid API URL from ${apiUrlSource}: ${message}`, [apiUrlSource]);
47824
48401
  }
48402
+ return {
48403
+ resolution: {
48404
+ transport: "http",
48405
+ transportSource: urlHit.key,
48406
+ baseUrl,
48407
+ apiUrlSource,
48408
+ apiKeyPresent: true,
48409
+ apiKeySource: credential.source,
48410
+ apiKeyTier: credential.tier,
48411
+ misconfigured: false,
48412
+ warning: warnings.length > 0 ? warnings.join(" ") : null
48413
+ },
48414
+ credential
48415
+ };
48416
+ }
48417
+ function resolveClientTransport(name, env3 = process.env, options = {}) {
48418
+ return resolveClientTransportSnapshot(name, env3, options).resolution;
47825
48419
  }
47826
- function getApiKeyReadOnly() {
47827
- if (process.env.SKILLS_API_KEY)
47828
- return process.env.SKILLS_API_KEY;
47829
- if (process.env.SKILL_API_KEY)
47830
- return process.env.SKILL_API_KEY;
47831
- return getAuthConfigReadOnly()?.apiKey || null;
48420
+ function credentialDiskSourcesForMessage(name, env3) {
48421
+ const paths = credentialDiskSources(name, env3);
48422
+ return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
48423
+ }
48424
+ 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;
48425
+ var init_transport = __esm(() => {
48426
+ CredentialResolutionError = class CredentialResolutionError extends Error {
48427
+ appName;
48428
+ attempted;
48429
+ constructor(appName, message, attempted) {
48430
+ super(message);
48431
+ this.name = "CredentialResolutionError";
48432
+ this.appName = appName;
48433
+ this.attempted = attempted;
48434
+ }
48435
+ };
48436
+ CredentialFileUnsafeError = class CredentialFileUnsafeError extends Error {
48437
+ path;
48438
+ constructor(path, reason) {
48439
+ super(`Refusing unsafe credential/config file ${path}: ${reason}.`);
48440
+ this.name = "CredentialFileUnsafeError";
48441
+ this.path = path;
48442
+ }
48443
+ };
48444
+ MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
48445
+ SAFE_APP_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
48446
+ SAFE_PROFILE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
48447
+ ILLEGAL_IN_HEADER_VALUE = /[^\t\x20-\x7e]/;
48448
+ VAULT_POINTER_SHAPE = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-_.]*){2,}$/;
48449
+ CREDENTIAL_SHAPED_KEY = /(?:^|_)(?:API_KEY|KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)(?:_|$)/;
48450
+ INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
48451
+ CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
48452
+ AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
48453
+ SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
48454
+ requireSecretsSdk = createRequire(import.meta.url);
48455
+ ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
48456
+ DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
48457
+ ClientTransportConfigurationError = class ClientTransportConfigurationError extends Error {
48458
+ appName;
48459
+ sources;
48460
+ constructor(appName, message, sources = []) {
48461
+ super(message);
48462
+ this.name = "ClientTransportConfigurationError";
48463
+ this.appName = appName;
48464
+ this.sources = Object.freeze([...sources]);
48465
+ }
48466
+ };
48467
+ IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
48468
+ AUTHORITY_OVERRIDE_HEADERS = new Set([
48469
+ "host",
48470
+ ":authority",
48471
+ "forwarded",
48472
+ "x-forwarded-host",
48473
+ "x-original-host"
48474
+ ]);
48475
+ });
48476
+
48477
+ // src/lib/fleet-credentials.ts
48478
+ var exports_fleet_credentials = {};
48479
+ __export(exports_fleet_credentials, {
48480
+ skillsCredentialOrReason: () => skillsCredentialOrReason,
48481
+ skillsCredentialFiles: () => skillsCredentialFiles,
48482
+ skillsCredentialFilePath: () => skillsCredentialFilePath,
48483
+ resolveSkillsFleet: () => resolveSkillsFleet,
48484
+ resolveSkillsApiOrigin: () => resolveSkillsApiOrigin,
48485
+ resolveSkillsApiKey: () => resolveSkillsApiKey,
48486
+ resetLocalSkillsModeNotice: () => resetLocalSkillsModeNotice,
48487
+ requireSkillsFleet: () => requireSkillsFleet,
48488
+ requireSkillsApiOrigin: () => requireSkillsApiOrigin,
48489
+ requireSkillsApiKey: () => requireSkillsApiKey,
48490
+ noticeLocalSkillsMode: () => noticeLocalSkillsMode,
48491
+ normalizeSkillsApiOrigin: () => normalizeSkillsApiOrigin,
48492
+ configuredSkillsApiUrl: () => configuredSkillsApiUrl,
48493
+ SkillsFleetCredentialError: () => SkillsFleetCredentialError,
48494
+ SKILLS_APP: () => SKILLS_APP,
48495
+ SKILLS_API_URL_ENV_KEYS: () => SKILLS_API_URL_ENV_KEYS,
48496
+ SKILLS_API_URL_ENV: () => SKILLS_API_URL_ENV,
48497
+ SKILLS_API_KEY_ENV_KEYS: () => SKILLS_API_KEY_ENV_KEYS,
48498
+ SKILLS_API_KEY_ENV: () => SKILLS_API_KEY_ENV,
48499
+ MissingSkillsFleetError: () => MissingSkillsFleetError
48500
+ });
48501
+ function isClientTransportConfigurationError(error) {
48502
+ return error instanceof ClientTransportConfigurationError || typeof error === "object" && error !== null && error.name === "ClientTransportConfigurationError";
48503
+ }
48504
+ function isCredentialResolutionError(error) {
48505
+ return error instanceof CredentialResolutionError || typeof error === "object" && error !== null && error.name === "CredentialResolutionError";
48506
+ }
48507
+ function asSkillsFleetCredentialError(error) {
48508
+ if (!isCredentialResolutionError(error))
48509
+ return null;
48510
+ return new SkillsFleetCredentialError(error.message, "MISSING_API_CREDENTIAL");
47832
48511
  }
47833
48512
  function normalizeSkillsApiOrigin(apiUrl) {
47834
48513
  const url = new URL(apiUrl);
@@ -47842,36 +48521,211 @@ function normalizeSkillsApiOrigin(apiUrl) {
47842
48521
  }
47843
48522
  return url.toString().replace(/\/+$/, "");
47844
48523
  }
47845
- function getApiUrl(action) {
47846
- return normalizeSkillsApiOrigin(requireApiUrl(action));
48524
+ function configuredSkillsApiUrl(env3 = process.env, keychain) {
48525
+ for (const key of SKILLS_API_URL_ENV_KEYS) {
48526
+ const value = env3[key]?.trim();
48527
+ if (value)
48528
+ return { value, source: key };
48529
+ }
48530
+ const fromKeychain = keychainConfigValue(SKILLS_APP, env3, keychain);
48531
+ if (fromKeychain)
48532
+ return { value: fromKeychain.value.trim(), source: fromKeychain.source };
48533
+ const fromDisk = appConfigDiskValue(SKILLS_APP, env3, SKILLS_API_URL_ENV_KEYS);
48534
+ if (fromDisk?.unusable) {
48535
+ throw new SkillsFleetCredentialError(`${fromDisk.key} in ${fromDisk.path} is declared but blank or malformed; ` + `a Skills authority must be a valid https URL (or an exact loopback http URL).`, "INVALID_API_URL");
48536
+ }
48537
+ if (fromDisk)
48538
+ return { value: fromDisk.value.trim(), source: fromDisk.path };
48539
+ return null;
47847
48540
  }
47848
- var cachedConfig;
47849
- var init_auth_store = __esm(() => {
47850
- init_api_url();
47851
- init_config();
48541
+ function skillsCredentialFiles(env3 = process.env) {
48542
+ return credentialDiskSources(SKILLS_APP, env3);
48543
+ }
48544
+ function skillsCredentialFilePath(env3 = process.env) {
48545
+ const paths = skillsCredentialFiles(env3);
48546
+ const path = paths[0];
48547
+ if (!path) {
48548
+ throw new Error("No home directory is set (HOME or HASNA_HOME), so there is nowhere to store a Skills credential.");
48549
+ }
48550
+ return path;
48551
+ }
48552
+ function noticeLocalSkillsMode(write = (line) => console.error(line)) {
48553
+ if (localNoticePrinted)
48554
+ return;
48555
+ localNoticePrinted = true;
48556
+ 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`);
48557
+ }
48558
+ function resetLocalSkillsModeNotice() {
48559
+ localNoticePrinted = false;
48560
+ }
48561
+ function resolveSkillsFleet(env3 = process.env, options = {}) {
48562
+ try {
48563
+ return resolveSkillsFleetOrThrow(env3, options);
48564
+ } catch (error) {
48565
+ const translated = asSkillsFleetCredentialError(error);
48566
+ if (translated)
48567
+ throw translated;
48568
+ throw error;
48569
+ }
48570
+ }
48571
+ function resolveSkillsFleetOrThrow(env3, options) {
48572
+ let resolution;
48573
+ try {
48574
+ resolution = resolveClientTransport(SKILLS_APP, env3, { credentials: options.credentials });
48575
+ } catch (error) {
48576
+ if (!isClientTransportConfigurationError(error))
48577
+ throw error;
48578
+ const configured2 = configuredSkillsApiUrl(env3, options.credentials?.keychain);
48579
+ const credential2 = resolveCredential(SKILLS_APP, env3, options.credentials);
48580
+ if (!configured2 && !credential2) {
48581
+ if (env3 === process.env)
48582
+ noticeLocalSkillsMode();
48583
+ return { mode: "local", apiOrigin: null, apiKey: null };
48584
+ }
48585
+ if (configured2 && !credential2) {
48586
+ 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(env3).join(" or ") || "no credentials file (no HOME)"}, ` + `then ${SKILLS_API_KEY_ENV}. Sign in with: skills auth login`);
48587
+ }
48588
+ throw error;
48589
+ }
48590
+ const configured = configuredSkillsApiUrl(env3, options.credentials?.keychain);
48591
+ const apiOrigin = configured ? normalizeSkillsApiOrigin(configured.value) : stripV1(resolution.baseUrl);
48592
+ const credential = resolveCredential(SKILLS_APP, env3, options.credentials);
48593
+ if (!credential) {
48594
+ throw new SkillsFleetCredentialError(`A Skills authority resolved but no API key did. Sign in with: skills auth login`);
48595
+ }
48596
+ const base2 = {
48597
+ mode: "hosted",
48598
+ apiOrigin,
48599
+ apiUrlSource: resolution.apiUrlSource ?? (configured?.source ?? "default"),
48600
+ apiKeySource: resolution.apiKeySource ?? credential.source,
48601
+ apiKeyTier: resolution.apiKeyTier,
48602
+ warning: resolution.warning
48603
+ };
48604
+ if (credential.tier === "pointer") {
48605
+ return { ...base2, apiKey: null, apiKeyPointer: credential };
48606
+ }
48607
+ if (!credential.apiKey.trim()) {
48608
+ throw new SkillsFleetCredentialError(`The Skills API key from ${credential.source} is empty \u2014 refusing to send an unauthenticated request. ` + `Sign in with: skills auth login`);
48609
+ }
48610
+ return { ...base2, apiKey: credential.apiKey, apiKeyPointer: null };
48611
+ }
48612
+ async function resolveSkillsApiKey(env3 = process.env, options = {}) {
48613
+ const fleet = resolveSkillsFleet(env3, options);
48614
+ if (fleet.mode !== "hosted")
48615
+ return null;
48616
+ if (fleet.apiKey)
48617
+ return fleet.apiKey;
48618
+ const pointer = fleet.apiKeyPointer;
48619
+ if (!pointer) {
48620
+ throw new SkillsFleetCredentialError(`A Skills authority resolved but no API key did. Sign in with: skills auth login`);
48621
+ }
48622
+ let completed;
48623
+ try {
48624
+ completed = await completePointerCredential(SKILLS_APP, pointer, env3);
48625
+ } catch (error) {
48626
+ const translated = asSkillsFleetCredentialError(error);
48627
+ if (translated)
48628
+ throw translated;
48629
+ throw error;
48630
+ }
48631
+ if (!completed.apiKey?.trim()) {
48632
+ throw new SkillsFleetCredentialError(`${credentialPointerEnvKey(SKILLS_APP)} names a vault item that produced an empty Skills API key \u2014 ` + `refusing to send an unauthenticated request.`);
48633
+ }
48634
+ return completed.apiKey;
48635
+ }
48636
+ async function requireSkillsApiKey(action = "This command", env3 = process.env, options = {}) {
48637
+ const apiKey = await resolveSkillsApiKey(env3, options);
48638
+ if (!apiKey)
48639
+ throw new MissingSkillsFleetError(action);
48640
+ return apiKey;
48641
+ }
48642
+ function stripV1(baseUrl) {
48643
+ return baseUrl.replace(/\/v1$/, "").replace(/\/+$/, "");
48644
+ }
48645
+ async function skillsCredentialOrReason(env3 = process.env, options = {}) {
48646
+ try {
48647
+ const apiKey = await resolveSkillsApiKey(env3, options);
48648
+ return apiKey ? { apiKey, reason: null } : { apiKey: null, reason: null };
48649
+ } catch (error) {
48650
+ if (error instanceof SkillsFleetCredentialError || error?.name === "SkillsFleetCredentialError") {
48651
+ return { apiKey: null, reason: error.message };
48652
+ }
48653
+ throw error;
48654
+ }
48655
+ }
48656
+ function resolveSkillsApiOrigin(env3 = process.env, options = {}) {
48657
+ const configured = configuredSkillsApiUrl(env3, options.credentials?.keychain);
48658
+ if (configured) {
48659
+ toV1BaseUrl(configured.value);
48660
+ return { origin: normalizeSkillsApiOrigin(configured.value), source: configured.source };
48661
+ }
48662
+ const fleet = resolveSkillsFleet(env3, options);
48663
+ return fleet.mode === "hosted" ? { origin: fleet.apiOrigin, source: fleet.apiUrlSource } : null;
48664
+ }
48665
+ function requireSkillsApiOrigin(action = "This command", env3 = process.env, options = {}) {
48666
+ const resolved = resolveSkillsApiOrigin(env3, options);
48667
+ if (!resolved)
48668
+ throw new MissingSkillsFleetError(action);
48669
+ return resolved.origin;
48670
+ }
48671
+ function requireSkillsFleet(action = "This command", env3 = process.env, options = {}) {
48672
+ const fleet = resolveSkillsFleet(env3, options);
48673
+ if (fleet.mode === "hosted")
48674
+ return fleet;
48675
+ throw new MissingSkillsFleetError(action);
48676
+ }
48677
+ 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;
48678
+ var init_fleet_credentials = __esm(() => {
48679
+ init_transport();
48680
+ ENV_KEYS = clientTransportEnvKeys(SKILLS_APP);
48681
+ SKILLS_API_URL_ENV_KEYS = ENV_KEYS.apiUrlKeys;
48682
+ SKILLS_API_KEY_ENV_KEYS = ENV_KEYS.apiKeyKeys;
48683
+ SKILLS_API_URL_ENV = SKILLS_API_URL_ENV_KEYS[0];
48684
+ SKILLS_API_KEY_ENV = SKILLS_API_KEY_ENV_KEYS[0];
48685
+ SkillsFleetCredentialError = class SkillsFleetCredentialError extends Error {
48686
+ code;
48687
+ constructor(message, code = "MISSING_API_CREDENTIAL") {
48688
+ super(message);
48689
+ this.name = "SkillsFleetCredentialError";
48690
+ this.code = code;
48691
+ }
48692
+ };
48693
+ MissingSkillsFleetError = class MissingSkillsFleetError extends Error {
48694
+ code = "MISSING_API_URL";
48695
+ constructor(action = "This command") {
48696
+ super(`${action} requires a Skills API credential and none is configured \u2014 ` + `run: skills auth login, or set ${SKILLS_API_KEY_ENV} ` + `(add the Keychain item hasna.credentials.${SKILLS_APP}.api-key, or write ~/.hasna/skills/config/credentials). ` + `Point at your own instance with ${SKILLS_API_URL_ENV}, or run: skills setup --api-url <your Skills instance origin>`);
48697
+ this.name = "MissingSkillsFleetError";
48698
+ }
48699
+ };
48700
+ });
48701
+
48702
+ // src/lib/api-url.ts
48703
+ function resolveApiUrl(env3 = process.env, options = {}) {
48704
+ const fleet = resolveSkillsFleet(env3, options);
48705
+ return fleet.mode === "hosted" ? fleet.apiOrigin : undefined;
48706
+ }
48707
+ var API_URL_ENV_VAR, MISSING_API_URL_HINT;
48708
+ var init_api_url = __esm(() => {
48709
+ init_fleet_credentials();
48710
+ init_fleet_credentials();
48711
+ API_URL_ENV_VAR = SKILLS_API_URL_ENV;
48712
+ 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>`;
47852
48713
  });
47853
48714
 
47854
48715
  // src/lib/remote-registry.ts
47855
- function getConfiguredApiUrl(config = loadConfig(), env3 = process.env) {
47856
- return resolveApiUrl(config, env3);
48716
+ function getConfiguredApiUrl(env3 = process.env) {
48717
+ return resolveApiUrl(env3);
47857
48718
  }
47858
48719
  function buildSkillsApiUrl(apiUrl, endpoint = "/skills") {
47859
48720
  const url = new URL(apiUrl);
47860
48721
  const cleanEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
47861
48722
  const pathname = url.pathname.replace(/\/+$/, "");
47862
- if (pathname.endsWith("/skills")) {
47863
- if (cleanEndpoint === "/skills") {
47864
- url.pathname = pathname;
47865
- } else {
47866
- url.pathname = `${pathname.slice(0, -"/skills".length)}${cleanEndpoint}` || cleanEndpoint;
47867
- }
48723
+ const apiBase = /\/api(?:\/v1)?\/skills$/.test(pathname) ? pathname.slice(0, -"/skills".length) : pathname;
48724
+ if (/\/api(?:\/v1)?$/.test(apiBase)) {
48725
+ url.pathname = `${apiBase}${cleanEndpoint}`;
47868
48726
  return url.toString();
47869
48727
  }
47870
- if (pathname.endsWith("/api") || pathname.endsWith("/api/v1")) {
47871
- url.pathname = `${pathname}${cleanEndpoint}`;
47872
- return url.toString();
47873
- }
47874
- url.pathname = `${pathname}/api/v1${cleanEndpoint}`.replace(/\/{2,}/g, "/");
48728
+ url.pathname = `${apiBase}/api/v1${cleanEndpoint}`.replace(/\/{2,}/g, "/");
47875
48729
  return url.toString();
47876
48730
  }
47877
48731
  function titleize(name) {
@@ -47932,9 +48786,9 @@ function parseRemoteContract(schema, payload, message) {
47932
48786
  throw error;
47933
48787
  }
47934
48788
  }
47935
- function remoteRequestHeaders(options) {
48789
+ async function remoteRequestHeaders(options) {
47936
48790
  const headers = new Headers({ Accept: "application/json" });
47937
- const token = options.authToken !== undefined ? options.authToken : getApiKey();
48791
+ const token = options.authToken !== undefined ? options.authToken : await resolveSkillsApiKey();
47938
48792
  const trimmed = token?.trim();
47939
48793
  if (trimmed)
47940
48794
  headers.set("Authorization", `Bearer ${trimmed}`);
@@ -47942,11 +48796,12 @@ function remoteRequestHeaders(options) {
47942
48796
  }
47943
48797
  async function fetchRemoteJson(url, options) {
47944
48798
  const fetchImpl = options.fetchImpl || fetch;
48799
+ const headers = await remoteRequestHeaders(options);
47945
48800
  const controller = new AbortController;
47946
48801
  const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 1e4);
47947
48802
  try {
47948
48803
  const response = await fetchImpl(url, {
47949
- headers: remoteRequestHeaders(options),
48804
+ headers,
47950
48805
  signal: controller.signal
47951
48806
  });
47952
48807
  if (!response.ok) {
@@ -47960,7 +48815,7 @@ async function fetchRemoteJson(url, options) {
47960
48815
  async function loadRemoteRegistry(options = {}) {
47961
48816
  const apiUrl = options.apiUrl || getConfiguredApiUrl();
47962
48817
  if (!apiUrl) {
47963
- throw new Error("Remote registry requires SKILLS_API_URL or config apiUrl");
48818
+ 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}`);
47964
48819
  }
47965
48820
  const url = buildSkillsApiUrl(apiUrl, options.endpoint);
47966
48821
  return parseRemoteRegistryPayload(await fetchRemoteJson(url, options));
@@ -47969,16 +48824,15 @@ async function mergeRemoteRegistry(local, options = {}) {
47969
48824
  const apiUrl = options.apiUrl || getConfiguredApiUrl();
47970
48825
  if (!apiUrl)
47971
48826
  return local;
47972
- const token = options.authToken !== undefined ? options.authToken : getApiKey();
47973
- if (!token?.trim())
48827
+ if (options.authToken !== undefined && !options.authToken?.trim())
47974
48828
  return local;
47975
- const remote = await loadRemoteRegistry({ ...options, apiUrl, authToken: token });
48829
+ const remote = await loadRemoteRegistry({ ...options, apiUrl });
47976
48830
  return mergeSkillRegistryLists(local, remote);
47977
48831
  }
47978
48832
  async function loadRemoteSkill(name, options = {}) {
47979
48833
  const apiUrl = options.apiUrl || getConfiguredApiUrl();
47980
48834
  if (!apiUrl) {
47981
- throw new Error("Remote registry requires SKILLS_API_URL or config apiUrl");
48835
+ 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}`);
47982
48836
  }
47983
48837
  const slug = encodeURIComponent(name);
47984
48838
  const url = buildSkillsApiUrl(apiUrl, options.endpoint ?? `/skills/${slug}`);
@@ -47988,8 +48842,7 @@ var remoteAvailabilitySchema, remoteSkillSchema, secretValuePatterns, remoteSkil
47988
48842
  var init_remote_registry = __esm(() => {
47989
48843
  init_zod();
47990
48844
  init_api_url();
47991
- init_auth_store();
47992
- init_config();
48845
+ init_fleet_credentials();
47993
48846
  init_discovery();
47994
48847
  init_registry_merge();
47995
48848
  remoteAvailabilitySchema = exports_external.object({
@@ -48034,6 +48887,162 @@ var init_remote_registry = __esm(() => {
48034
48887
  ]);
48035
48888
  });
48036
48889
 
48890
+ // src/lib/auth-store.ts
48891
+ import { chmodSync, existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync12, renameSync as renameSync4, statSync as statSync9, unlinkSync, writeFileSync as writeFileSync7 } from "fs";
48892
+ import { dirname as dirname6, join as join16 } from "path";
48893
+ function getAuthFilePath(env3 = process.env) {
48894
+ return skillsCredentialFilePath(env3);
48895
+ }
48896
+ function getIdentityFilePath(env3 = process.env) {
48897
+ return join16(dirname6(skillsCredentialFilePath(env3)), "identity.json");
48898
+ }
48899
+ function getAuthIdentity(env3 = process.env) {
48900
+ return readIdentity(env3);
48901
+ }
48902
+ function readIdentity(env3 = process.env) {
48903
+ try {
48904
+ const parsed = JSON.parse(readFileSync12(getIdentityFilePath(env3), "utf-8"));
48905
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
48906
+ return {};
48907
+ const record = parsed;
48908
+ const identity2 = {};
48909
+ for (const field of ["email", "orgId", "orgSlug", "userId"]) {
48910
+ const value = record[field];
48911
+ if (typeof value === "string" && value.length > 0)
48912
+ identity2[field] = value;
48913
+ }
48914
+ return identity2;
48915
+ } catch {
48916
+ return {};
48917
+ }
48918
+ }
48919
+ function getAuthConfig(env3 = process.env, options = {}) {
48920
+ const fleet = resolveSkillsFleet(env3, options);
48921
+ if (fleet.mode !== "hosted")
48922
+ return null;
48923
+ return { apiKey: fleet.apiKey, ...readIdentity(env3) };
48924
+ }
48925
+ function writeCredentialValues(values2, env3 = process.env) {
48926
+ const file = skillsCredentialFilePath(env3);
48927
+ mkdirSync7(dirname6(file), { recursive: true, mode: 448 });
48928
+ const lines = [];
48929
+ const written = new Set;
48930
+ if (existsSync15(file)) {
48931
+ for (const raw of readFileSync12(file, "utf-8").split(/\r?\n/)) {
48932
+ const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(raw);
48933
+ const key = match?.[1];
48934
+ if (key && key in values2) {
48935
+ const next = values2[key];
48936
+ if (next !== null && next !== undefined) {
48937
+ lines.push(`${key}=${next}`);
48938
+ written.add(key);
48939
+ }
48940
+ continue;
48941
+ }
48942
+ lines.push(raw);
48943
+ }
48944
+ }
48945
+ for (const [key, value] of Object.entries(values2)) {
48946
+ if (value === null || value === undefined || written.has(key))
48947
+ continue;
48948
+ lines.push(`${key}=${value}`);
48949
+ }
48950
+ const body = lines.filter((line, index) => !(line.trim() === "" && index === lines.length - 1)).join(`
48951
+ `) + `
48952
+ `;
48953
+ const temp = `${file}.tmp-${process.pid}`;
48954
+ writeFileSync7(temp, body, { mode: 384 });
48955
+ chmodSync(temp, 384);
48956
+ renameSync4(temp, file);
48957
+ return file;
48958
+ }
48959
+ function readCredentialValue(key, env3 = process.env) {
48960
+ let file;
48961
+ try {
48962
+ file = skillsCredentialFilePath(env3);
48963
+ } catch {
48964
+ return null;
48965
+ }
48966
+ if (!existsSync15(file))
48967
+ return null;
48968
+ try {
48969
+ for (const raw of readFileSync12(file, "utf-8").split(/\r?\n/)) {
48970
+ const match = new RegExp(`^\\s*(?:export\\s+)?${key}\\s*=\\s*(.*)$`).exec(raw);
48971
+ if (!match)
48972
+ continue;
48973
+ let value = (match[1] ?? "").trim();
48974
+ const quote = value[0];
48975
+ if ((quote === '"' || quote === "'") && value.length >= 2 && value.endsWith(quote)) {
48976
+ value = value.slice(1, -1);
48977
+ }
48978
+ return value || null;
48979
+ }
48980
+ } catch {
48981
+ return null;
48982
+ }
48983
+ return null;
48984
+ }
48985
+ function saveAuthConfig(config, env3 = process.env) {
48986
+ const apiKey = config.apiKey.trim();
48987
+ if (!apiKey)
48988
+ throw new Error("Refusing to store an empty Skills API key.");
48989
+ if (/[^\t\x20-\x7e]/.test(apiKey)) {
48990
+ throw new Error("Refusing to store a Skills API key containing control characters or non-ASCII bytes.");
48991
+ }
48992
+ const file = writeCredentialValues({ [SKILLS_API_KEY_ENV]: apiKey }, env3);
48993
+ const identity2 = {};
48994
+ for (const field of ["email", "orgId", "orgSlug", "userId"]) {
48995
+ const value = config[field];
48996
+ if (typeof value === "string" && value.length > 0)
48997
+ identity2[field] = value;
48998
+ }
48999
+ const identityFile = getIdentityFilePath(env3);
49000
+ if (Object.keys(identity2).length > 0) {
49001
+ writeFileSync7(identityFile, JSON.stringify(identity2, null, 2) + `
49002
+ `, { mode: 384 });
49003
+ } else {
49004
+ try {
49005
+ unlinkSync(identityFile);
49006
+ } catch {}
49007
+ }
49008
+ return file;
49009
+ }
49010
+ function saveApiUrl(apiUrl, env3 = process.env) {
49011
+ return writeCredentialValues({ [SKILLS_API_URL_ENV]: apiUrl }, env3);
49012
+ }
49013
+ function readStoredApiUrl(env3 = process.env) {
49014
+ return readCredentialValue(SKILLS_API_URL_ENV, env3);
49015
+ }
49016
+ function clearAuthConfig(env3 = process.env) {
49017
+ try {
49018
+ writeCredentialValues({ [SKILLS_API_KEY_ENV]: null }, env3);
49019
+ } catch {}
49020
+ try {
49021
+ unlinkSync(getIdentityFilePath(env3));
49022
+ } catch {}
49023
+ let stillResolves;
49024
+ try {
49025
+ stillResolves = resolveSkillsFleet(env3).mode === "hosted";
49026
+ } catch {
49027
+ stillResolves = true;
49028
+ }
49029
+ return { stillResolves };
49030
+ }
49031
+ function getApiUrl(action, env3 = process.env, options = {}) {
49032
+ return requireSkillsApiOrigin(action, env3, options);
49033
+ }
49034
+ function credentialFileMode(env3 = process.env) {
49035
+ try {
49036
+ return statSync9(skillsCredentialFilePath(env3)).mode & 511;
49037
+ } catch {
49038
+ return null;
49039
+ }
49040
+ }
49041
+ var init_auth_store = __esm(() => {
49042
+ init_fleet_credentials();
49043
+ init_fleet_credentials();
49044
+ });
49045
+
48037
49046
  // src/lib/remote-run-contract.ts
48038
49047
  function normalizeRemoteSkillRunContract(payload, fallbackSkill) {
48039
49048
  const record = isRecord3(payload) ? payload : {};
@@ -48351,26 +49360,23 @@ function normalizeUpdatedSincePage(payload) {
48351
49360
  }
48352
49361
  return { skills, nextCursor };
48353
49362
  }
48354
- function createRemoteSkillsClient() {
48355
- const apiKey = getApiKey();
48356
- if (!apiKey)
49363
+ async function createRemoteSkillsClient(env3 = process.env) {
49364
+ const fleet = resolveSkillsFleet(env3);
49365
+ if (fleet.mode !== "hosted")
48357
49366
  return null;
48358
- return new RemoteSkillsClient(apiKey);
49367
+ const apiKey = await resolveSkillsApiKey(env3);
49368
+ if (!apiKey) {
49369
+ throw new Error("A Skills authority resolved but no API key did. Sign in with: skills auth login");
49370
+ }
49371
+ return new RemoteSkillsClient(apiKey, fleet.apiOrigin);
48359
49372
  }
48360
- function createRemoteSkillsClientReadOnly() {
48361
- const apiKey = getApiKeyReadOnly();
48362
- if (!apiKey)
48363
- return null;
48364
- const apiUrl = resolveApiUrl(loadConfigReadOnly(), process.env);
48365
- if (!apiUrl)
48366
- throw new MissingApiUrlError("the cloud group's sync verb (--dry-run)");
48367
- return new RemoteSkillsClient(apiKey, apiUrl);
49373
+ function createRemoteSkillsClientReadOnly(env3 = process.env) {
49374
+ return createRemoteSkillsClient(env3);
48368
49375
  }
48369
49376
  var RemoteRouteUnsupportedError, RemoteRequestError;
48370
49377
  var init_remote_client = __esm(() => {
48371
49378
  init_auth_store();
48372
- init_api_url();
48373
- init_config();
49379
+ init_fleet_credentials();
48374
49380
  RemoteRouteUnsupportedError = class RemoteRouteUnsupportedError extends Error {
48375
49381
  path;
48376
49382
  status;
@@ -48420,8 +49426,8 @@ var init_revision = __esm(() => {
48420
49426
 
48421
49427
  // src/lib/skill-bundle.ts
48422
49428
  import { createHash as createHash3 } from "crypto";
48423
- import { readFileSync as readFileSync12, readdirSync as readdirSync9, statSync as statSync9 } from "fs";
48424
- import { join as join16, relative as relative2 } from "path";
49429
+ import { readFileSync as readFileSync13, readdirSync as readdirSync9, statSync as statSync10 } from "fs";
49430
+ import { join as join17, relative as relative2 } from "path";
48425
49431
  function isDotenvFile(lower) {
48426
49432
  if (lower === ".env" || lower.startsWith(".env."))
48427
49433
  return true;
@@ -48459,7 +49465,7 @@ function collectSkillBundleEntries(dir) {
48459
49465
  }
48460
49466
  function walk(root, current, out) {
48461
49467
  for (const entry of readdirSync9(current, { withFileTypes: true })) {
48462
- const absolute = join16(current, entry.name);
49468
+ const absolute = join17(current, entry.name);
48463
49469
  const rel = relative2(root, absolute).split("\\").join("/");
48464
49470
  const isRootLevel = !rel.includes("/");
48465
49471
  if (ANY_SEGMENT_EXCLUDES.has(entry.name.toLowerCase()))
@@ -48480,10 +49486,10 @@ function walk(root, current, out) {
48480
49486
  continue;
48481
49487
  if (isCredentialFile(entry.name))
48482
49488
  continue;
48483
- const stats = statSync9(absolute);
49489
+ const stats = statSync10(absolute);
48484
49490
  out.push({
48485
49491
  path: rel,
48486
- bytes: ownBytes(readFileSync12(absolute)),
49492
+ bytes: ownBytes(readFileSync13(absolute)),
48487
49493
  mode: stats.mode & 64 ? 493 : 420
48488
49494
  });
48489
49495
  }
@@ -48778,12 +49784,12 @@ var init_skill_bundles = __esm(() => {
48778
49784
  });
48779
49785
 
48780
49786
  // src/lib/pull.ts
48781
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, readFileSync as readFileSync13, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
48782
- import { dirname as dirname7, join as join17 } from "path";
49787
+ import { existsSync as existsSync16, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, readFileSync as readFileSync14, renameSync as renameSync5, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
49788
+ import { dirname as dirname7, join as join18 } from "path";
48783
49789
  async function pullSkills(options = {}) {
48784
- const client = options.client !== undefined ? options.client : createRemoteSkillsClient();
49790
+ const client = options.client !== undefined ? options.client : await createRemoteSkillsClient();
48785
49791
  if (!client) {
48786
- throw new PullSkillError("No API key configured, so there is no instance to pull from.", ["Run `skills login`, or set SKILLS_API_KEY and SKILLS_API_URL for this instance."]);
49792
+ throw new PullSkillError("No API key configured, so there is no instance to pull from.", ["Run `skills auth login`, or set HASNA_SKILLS_API_KEY (and HASNA_SKILLS_API_URL for your own instance)."]);
48787
49793
  }
48788
49794
  const signingKey = options.signingKey ?? resolveSigningKey() ?? undefined;
48789
49795
  const targets = await resolveTargetSlugs(client, options);
@@ -48835,8 +49841,8 @@ async function pullOne(client, rawName, corpusOptions, verify) {
48835
49841
  return reconcileTombstone(slug, corpusOptions);
48836
49842
  }
48837
49843
  if (bundleResponse.status === 404) {
48838
- const marker = readPullMarker(join17(getPortableSkillsRoot(corpusOptions), slug));
48839
- if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
49844
+ const marker = readPullMarker(join18(getPortableSkillsRoot(corpusOptions), slug));
49845
+ if (isPublishedInstallMarker(marker)) {
48840
49846
  return { name: slug, success: true, purged: true, removed: false };
48841
49847
  }
48842
49848
  }
@@ -48874,8 +49880,8 @@ async function pullOne(client, rawName, corpusOptions, verify) {
48874
49880
  return { name: slug, success: false, error: `Skill '${slug}' was not found on the configured Skills instance.` };
48875
49881
  }
48876
49882
  if (!meta?.revisionId) {
48877
- const marker = readPullMarker(join17(getPortableSkillsRoot(corpusOptions), slug));
48878
- if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
49883
+ const marker = readPullMarker(join18(getPortableSkillsRoot(corpusOptions), slug));
49884
+ if (isPublishedInstallMarker(marker)) {
48879
49885
  return { name: slug, success: true, purged: true, removed: false };
48880
49886
  }
48881
49887
  }
@@ -48931,8 +49937,8 @@ function provenRevision(meta, slug, bundle) {
48931
49937
  return declared;
48932
49938
  }
48933
49939
  function reconcileTombstone(slug, corpusOptions) {
48934
- const target = join17(getPortableSkillsRoot(corpusOptions), slug);
48935
- if (!existsSync16(join17(target, PULL_MARKER_FILE))) {
49940
+ const target = join18(getPortableSkillsRoot(corpusOptions), slug);
49941
+ if (!existsSync16(join18(target, PULL_MARKER_FILE))) {
48936
49942
  return { name: slug, success: true, tombstoned: true, removed: false, leftInPlace: true };
48937
49943
  }
48938
49944
  rmSync5(target, { recursive: true, force: true });
@@ -48940,11 +49946,18 @@ function reconcileTombstone(slug, corpusOptions) {
48940
49946
  }
48941
49947
  function readPullMarker(dir) {
48942
49948
  try {
48943
- return JSON.parse(readFileSync13(join17(dir, PULL_MARKER_FILE), "utf-8"));
49949
+ return JSON.parse(readFileSync14(join18(dir, PULL_MARKER_FILE), "utf-8"));
48944
49950
  } catch {
48945
49951
  return null;
48946
49952
  }
48947
49953
  }
49954
+ function isPublishedInstallMarker(marker) {
49955
+ if (!marker)
49956
+ return false;
49957
+ const revisionId = typeof marker.revisionId === "string" ? marker.revisionId : "";
49958
+ const contentHash = typeof marker.contentHash === "string" ? marker.contentHash : "";
49959
+ return revisionId.length > 0 || contentHash.length > 0;
49960
+ }
48948
49961
  function installVerifiedBundle(slug, response, meta, corpusOptions, verify, exact) {
48949
49962
  return response.arrayBuffer().then((buffer) => {
48950
49963
  const verified = verifyBundleResponseBytes(buffer, response, verify);
@@ -49068,14 +50081,14 @@ function verifyBundleResponseBytes(buffer, response, verify = {}) {
49068
50081
  function installBundleAtomically(name, entries, options = {}, marker = {}) {
49069
50082
  const root = getPortableSkillsRoot(options);
49070
50083
  mkdirSync8(root, { recursive: true });
49071
- const target = join17(root, name);
50084
+ const target = join18(root, name);
49072
50085
  const created = !existsSync16(target);
49073
- const staging = mkdtempSync3(join17(root, `.pull-${name}-`));
50086
+ const staging = mkdtempSync3(join18(root, `.pull-${name}-`));
49074
50087
  let moved = false;
49075
50088
  let backup = null;
49076
50089
  try {
49077
50090
  for (const entry of entries) {
49078
- const destination = join17(staging, entry.path);
50091
+ const destination = join18(staging, entry.path);
49079
50092
  mkdirSync8(dirname7(destination), { recursive: true });
49080
50093
  writeFileSync8(destination, entry.bytes, { mode: entry.mode });
49081
50094
  }
@@ -49088,18 +50101,18 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
49088
50101
  ...marker.revisionId ? { revisionId: marker.revisionId } : {}
49089
50102
  });
49090
50103
  if (existsSync16(target)) {
49091
- backup = mkdtempSync3(join17(root, `.pull-backup-${name}-`));
49092
- renameSync4(target, join17(backup, name));
50104
+ backup = mkdtempSync3(join18(root, `.pull-backup-${name}-`));
50105
+ renameSync5(target, join18(backup, name));
49093
50106
  moved = true;
49094
50107
  }
49095
- renameSync4(staging, target);
50108
+ renameSync5(staging, target);
49096
50109
  if (moved && backup)
49097
50110
  rmSync5(backup, { recursive: true, force: true });
49098
50111
  } catch (error) {
49099
50112
  rmSync5(staging, { recursive: true, force: true });
49100
- if (moved && backup && existsSync16(join17(backup, name))) {
50113
+ if (moved && backup && existsSync16(join18(backup, name))) {
49101
50114
  try {
49102
- renameSync4(join17(backup, name), target);
50115
+ renameSync5(join18(backup, name), target);
49103
50116
  } catch {}
49104
50117
  }
49105
50118
  throw error;
@@ -49118,7 +50131,7 @@ function writePullMarker(dir, record) {
49118
50131
  ...record.revisionId ? { revisionId: record.revisionId } : {},
49119
50132
  syncedAt: new Date().toISOString()
49120
50133
  };
49121
- writeFileSync8(join17(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
50134
+ writeFileSync8(join18(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
49122
50135
  `);
49123
50136
  }
49124
50137
  async function safeMeta(client, slug) {
@@ -49197,7 +50210,7 @@ __export(exports_install, {
49197
50210
  });
49198
50211
  function registerInstall(parent) {
49199
50212
  parent.command("install").argument("[args...]", "Deprecated. Use 'skills pin <name>' for project skill pins.").option("--json", "Output result as JSON", false).description("Deprecated. Render skills into native agent folders with 'skills render'.").action((args, options) => handleDeprecatedInstall(args, options));
49200
- parent.command("pin").argument("[skills...]", "Skills to pin").option("-o, --overwrite", "Refresh existing pins", false).option("--json", "Output results as JSON", false).option("--dry-run", "Print what would happen without actually pinning", false).option("--category <category>", "Pin all skills in a category (case-insensitive)").option("--remote", "Pin skills from the remote registry configured by SKILLS_API_URL or config apiUrl", false).description("Pin skills in .skills/project.json without copying source").action((skills, options) => {
50213
+ parent.command("pin").argument("[skills...]", "Skills to pin").option("-o, --overwrite", "Refresh existing pins", false).option("--json", "Output results as JSON", false).option("--dry-run", "Print what would happen without actually pinning", false).option("--category <category>", "Pin all skills in a category (case-insensitive)").option("--remote", "Pin skills from the remote registry (the resolved Skills credential; HASNA_SKILLS_API_URL for your own instance). Implied for name@version pins", false).description("Pin skills in .skills/project.json without copying source").action((skills, options) => {
49201
50214
  handlePin(skills, options).catch(handlePinError);
49202
50215
  });
49203
50216
  parent.command("unpin").argument("<skill>", "Skill to unpin").option("--json", "Output as JSON", false).option("--dry-run", "Print what would happen without actually unpinning", false).description("Remove a skill from project pins").action((skill, options) => handleUnpin(skill, options));
@@ -49280,7 +50293,10 @@ Pinning ${skills.length} skills from "${matchedCategory}"...
49280
50293
  const { name: pinName, version: pinnedVersion } = splitNameVersion(skills[i]);
49281
50294
  if (total > 1 && !options.json)
49282
50295
  process.stdout.write(`[${i + 1}/${total}] Pinning ${skills[i]}...`);
49283
- const result2 = pinnedVersion ? await pinExactVersion(pinName, pinnedVersion, { useRemote, overwrite: options.overwrite }) : useRemote ? pinRemoteSkill(pinName, remoteByName, options.overwrite) : installSkill(pinName, { overwrite: options.overwrite });
50296
+ const result2 = pinnedVersion ? await pinExactVersion(pinName, pinnedVersion, {
50297
+ useRemote: true,
50298
+ overwrite: options.overwrite
50299
+ }) : useRemote ? pinRemoteSkill(pinName, remoteByName, options.overwrite) : installSkill(pinName, { overwrite: options.overwrite });
49284
50300
  results.push(result2);
49285
50301
  if (total > 1 && !options.json)
49286
50302
  console.log(result2.success ? " done" : ` ${source_default.red("failed")}`);
@@ -49389,7 +50405,12 @@ Pinned skills (${pins.length}):
49389
50405
  }
49390
50406
  async function pinExactVersion(name, version, options) {
49391
50407
  if (!options.useRemote) {
49392
- return { skill: name, success: false, error: `Pinning '${name}@${version}' needs a configured Skills instance to fetch that version from (skills login / SKILLS_API_URL).`, mode: "pin" };
50408
+ return {
50409
+ skill: name,
50410
+ success: false,
50411
+ error: `Pinning '${name}@${version}' needs a configured Skills instance: an exact version only exists on an instance, so the fetch is implied for name@version pins (skills auth login, or HASNA_SKILLS_API_URL for your own instance).`,
50412
+ mode: "pin"
50413
+ };
49393
50414
  }
49394
50415
  const pull2 = options.pull ?? (async (spec) => (await pullSkills({ names: [spec] })).results[0] ?? { success: false, error: "pull returned no result" });
49395
50416
  const pulled = await pull2(`${name}@${version}`);
@@ -49507,16 +50528,16 @@ __export(exports_list, {
49507
50528
  registerBrowse: () => registerBrowse
49508
50529
  });
49509
50530
  function registerBrowse(parent) {
49510
- parent.command("list").alias("ls").option("-c, --category <category>", "Filter by category").option("-p, --pinned", "Show only pinned skills", false).option("-t, --tags <tags>", "Filter by comma-separated tags (OR logic, case-insensitive)").option("--tag <tags>", "Filter by comma-separated tags (alias for --tags)").option("--all", "Show the full skill registry instead of the default basic set", false).option("--remote", "Use remote registry from SKILLS_API_URL or config apiUrl", false).option("--json", "Output as JSON", false).option("--brief", "One line per skill: name \u2014 description [category]", false).option("--format <format>", "Output format: compact (names only) or csv (name,category,price,description)").option("--limit <n>", "Maximum rows to print for human output (default: 30, use 0 or all for every row)").option("--cursor <n>", "Numeric offset for human-output pagination", "0").option("--verbose", "Show longer descriptions and tags in human output", false).action((options) => {
50531
+ parent.command("list").alias("ls").option("-c, --category <category>", "Filter by category").option("-p, --pinned", "Show only pinned skills", false).option("-t, --tags <tags>", "Filter by comma-separated tags (OR logic, case-insensitive)").option("--tag <tags>", "Filter by comma-separated tags (alias for --tags)").option("--all", "Show the full skill registry instead of the default basic set", false).option("--remote", "Use the remote registry (the resolved Skills credential; HASNA_SKILLS_API_URL for your own instance)", false).option("--json", "Output as JSON", false).option("--brief", "One line per skill: name \u2014 description [category]", false).option("--format <format>", "Output format: compact (names only) or csv (name,category,price,description)").option("--limit <n>", "Maximum rows to print for human output (default: 30, use 0 or all for every row)").option("--cursor <n>", "Numeric offset for human-output pagination", "0").option("--verbose", "Show longer descriptions and tags in human output", false).action((options) => {
49511
50532
  return handleList(options).catch(handleBrowseError);
49512
50533
  });
49513
- parent.command("search").alias("s").argument("<query>", "Search term").option("--json", "Output as JSON", false).option("--brief", "One line per skill: name \u2014 description [category]", false).option("--format <format>", "Output format: compact (names only) or csv (name,category,price,description)").option("-c, --category <category>", "Filter results by category").option("-t, --tags <tags>", "Filter results by comma-separated tags (OR logic, case-insensitive)").option("--all", "Search the full skill registry instead of the default basic set", false).option("--remote", "Use remote registry from SKILLS_API_URL or config apiUrl", false).option("--limit <n>", "Maximum rows to print for human output (default: 20, use 0 or all for every row)").option("--cursor <n>", "Numeric offset for human-output pagination", "0").option("--verbose", "Show longer descriptions and tags in human output", false).description("Search for skills").action((query, options) => {
50534
+ parent.command("search").alias("s").argument("<query>", "Search term").option("--json", "Output as JSON", false).option("--brief", "One line per skill: name \u2014 description [category]", false).option("--format <format>", "Output format: compact (names only) or csv (name,category,price,description)").option("-c, --category <category>", "Filter results by category").option("-t, --tags <tags>", "Filter results by comma-separated tags (OR logic, case-insensitive)").option("--all", "Search the full skill registry instead of the default basic set", false).option("--remote", "Use the remote registry (the resolved Skills credential; HASNA_SKILLS_API_URL for your own instance)", false).option("--limit <n>", "Maximum rows to print for human output (default: 20, use 0 or all for every row)").option("--cursor <n>", "Numeric offset for human-output pagination", "0").option("--verbose", "Show longer descriptions and tags in human output", false).description("Search for skills").action((query, options) => {
49514
50535
  return handleSearch(query, options).catch(handleBrowseError);
49515
50536
  });
49516
- parent.command("categories").option("--json", "Output as JSON", false).option("--remote", "Use remote registry from SKILLS_API_URL or config apiUrl", false).description("List all categories").action((options) => {
50537
+ parent.command("categories").option("--json", "Output as JSON", false).option("--remote", "Use the remote registry (the resolved Skills credential; HASNA_SKILLS_API_URL for your own instance)", false).description("List all categories").action((options) => {
49517
50538
  return handleCategories(options).catch(handleBrowseError);
49518
50539
  });
49519
- parent.command("tags").option("--json", "Output as JSON", false).option("--remote", "Use remote registry from SKILLS_API_URL or config apiUrl", false).option("--limit <n>", "Maximum rows to print for human output (default: 80, use 0 or all for every row)").option("--cursor <n>", "Numeric offset for human-output pagination", "0").description("List all unique tags with counts").action((options) => {
50540
+ parent.command("tags").option("--json", "Output as JSON", false).option("--remote", "Use the remote registry (the resolved Skills credential; HASNA_SKILLS_API_URL for your own instance)", false).option("--limit <n>", "Maximum rows to print for human output (default: 80, use 0 or all for every row)").option("--cursor <n>", "Numeric offset for human-output pagination", "0").description("List all unique tags with counts").action((options) => {
49520
50541
  return handleTags(options).catch(handleBrowseError);
49521
50542
  });
49522
50543
  }
@@ -49883,16 +50904,16 @@ __export(exports_skillinfo, {
49883
50904
  generateEnvExample: () => generateEnvExample,
49884
50905
  detectProjectSkills: () => detectProjectSkills
49885
50906
  });
49886
- import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
49887
- import { join as join18 } from "path";
50907
+ import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
50908
+ import { join as join19 } from "path";
49888
50909
  function isInstructionSkillDir(skillPath, meta) {
49889
50910
  if (meta?.kind === "instruction")
49890
50911
  return true;
49891
- const skillMdPath = join18(skillPath, "SKILL.md");
50912
+ const skillMdPath = join19(skillPath, "SKILL.md");
49892
50913
  if (!existsSync17(skillMdPath))
49893
50914
  return false;
49894
50915
  try {
49895
- return parseSkillFrontmatter(readFileSync14(skillMdPath, "utf-8"))?.kind === "instruction";
50916
+ return parseSkillFrontmatter(readFileSync15(skillMdPath, "utf-8"))?.kind === "instruction";
49896
50917
  } catch {
49897
50918
  return false;
49898
50919
  }
@@ -49902,9 +50923,9 @@ function getSkillDocs(name) {
49902
50923
  if (!existsSync17(skillPath))
49903
50924
  return null;
49904
50925
  return {
49905
- skillMd: readIfExists(join18(skillPath, "SKILL.md")),
49906
- readme: readIfExists(join18(skillPath, "README.md")),
49907
- claudeMd: readIfExists(join18(skillPath, "CLAUDE.md"))
50926
+ skillMd: readIfExists(join19(skillPath, "SKILL.md")),
50927
+ readme: readIfExists(join19(skillPath, "README.md")),
50928
+ claudeMd: readIfExists(join19(skillPath, "CLAUDE.md"))
49908
50929
  };
49909
50930
  }
49910
50931
  function getSkillBestDoc(name) {
@@ -49919,7 +50940,7 @@ function getSkillRequirements(name) {
49919
50940
  return null;
49920
50941
  const texts = [];
49921
50942
  for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
49922
- const content = readIfExists(join18(skillPath, file));
50943
+ const content = readIfExists(join19(skillPath, file));
49923
50944
  if (content)
49924
50945
  texts.push(content);
49925
50946
  }
@@ -49935,7 +50956,8 @@ function getSkillRequirements(name) {
49935
50956
  }
49936
50957
  }
49937
50958
  envVars.delete("SKILL_API_KEY");
49938
- envVars.add("SKILLS_API_KEY");
50959
+ envVars.delete("SKILLS_API_KEY");
50960
+ envVars.add("HASNA_SKILLS_API_KEY");
49939
50961
  }
49940
50962
  const systemDeps = new Set;
49941
50963
  const depPatterns = [
@@ -49958,10 +50980,10 @@ function getSkillRequirements(name) {
49958
50980
  const skillName = normalizeSkillName(name);
49959
50981
  let cliCommand = `skills run ${skillName}`;
49960
50982
  let dependencies = {};
49961
- const pkgPath = join18(skillPath, "package.json");
50983
+ const pkgPath = join19(skillPath, "package.json");
49962
50984
  if (existsSync17(pkgPath)) {
49963
50985
  try {
49964
- const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
50986
+ const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
49965
50987
  dependencies = pkg.dependencies || {};
49966
50988
  } catch {}
49967
50989
  }
@@ -49978,9 +51000,9 @@ function isHostedPremiumSkill(skillName, meta) {
49978
51000
  function isPackageResolvable(pkgName, fromDir) {
49979
51001
  let dir = fromDir;
49980
51002
  while (true) {
49981
- if (existsSync17(join18(dir, "node_modules", pkgName, "package.json")))
51003
+ if (existsSync17(join19(dir, "node_modules", pkgName, "package.json")))
49982
51004
  return true;
49983
- const parent = join18(dir, "..");
51005
+ const parent = join19(dir, "..");
49984
51006
  if (parent === dir)
49985
51007
  return false;
49986
51008
  dir = parent;
@@ -50009,13 +51031,13 @@ async function runSkill(name, args, options = {}) {
50009
51031
  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'.`
50010
51032
  };
50011
51033
  }
50012
- const pkgPath = join18(skillPath, "package.json");
51034
+ const pkgPath = join19(skillPath, "package.json");
50013
51035
  if (!existsSync17(pkgPath)) {
50014
51036
  return { exitCode: 1, error: `No package.json in skill '${name}'` };
50015
51037
  }
50016
51038
  let entryPoint;
50017
51039
  try {
50018
- const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
51040
+ const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
50019
51041
  if (pkg.bin) {
50020
51042
  const binValues = Object.values(pkg.bin);
50021
51043
  entryPoint = binValues[0];
@@ -50029,11 +51051,11 @@ async function runSkill(name, args, options = {}) {
50029
51051
  } catch {
50030
51052
  return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
50031
51053
  }
50032
- const entryPath = join18(skillPath, entryPoint);
51054
+ const entryPath = join19(skillPath, entryPoint);
50033
51055
  if (!existsSync17(entryPath)) {
50034
51056
  return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
50035
51057
  }
50036
- const nodeModules = join18(skillPath, "node_modules");
51058
+ const nodeModules = join19(skillPath, "node_modules");
50037
51059
  if (!existsSync17(nodeModules)) {
50038
51060
  const install = Bun.spawn(["bun", "install", "--no-save"], {
50039
51061
  cwd: skillPath,
@@ -50061,7 +51083,7 @@ async function runSkill(name, args, options = {}) {
50061
51083
  return { exitCode };
50062
51084
  }
50063
51085
  function detectProjectSkills(cwd2 = process.cwd()) {
50064
- const pkgPath = join18(cwd2, "package.json");
51086
+ const pkgPath = join19(cwd2, "package.json");
50065
51087
  if (!existsSync17(pkgPath)) {
50066
51088
  const alwaysRecommend = ["market-research-report", "repo-onboarding-report", "blog-article"];
50067
51089
  const recommended2 = alwaysRecommend.map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
@@ -50069,7 +51091,7 @@ function detectProjectSkills(cwd2 = process.cwd()) {
50069
51091
  }
50070
51092
  let pkg;
50071
51093
  try {
50072
- pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
51094
+ pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
50073
51095
  } catch {
50074
51096
  const alwaysRecommend = ["market-research-report", "repo-onboarding-report", "blog-article"];
50075
51097
  const recommended2 = alwaysRecommend.map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
@@ -50198,13 +51220,13 @@ function generateSkillMd(name) {
50198
51220
  "---"
50199
51221
  ].join(`
50200
51222
  `);
50201
- const readme = readIfExists(join18(skillPath, "README.md"));
50202
- const claudeMd = readIfExists(join18(skillPath, "CLAUDE.md"));
51223
+ const readme = readIfExists(join19(skillPath, "README.md"));
51224
+ const claudeMd = readIfExists(join19(skillPath, "CLAUDE.md"));
50203
51225
  let cliCommand = null;
50204
- const pkgPath = join18(skillPath, "package.json");
51226
+ const pkgPath = join19(skillPath, "package.json");
50205
51227
  if (existsSync17(pkgPath)) {
50206
51228
  try {
50207
- const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
51229
+ const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
50208
51230
  if (pkg.bin) {
50209
51231
  const binKeys = Object.keys(pkg.bin);
50210
51232
  if (binKeys.length > 0)
@@ -50278,7 +51300,7 @@ function extractEnvVars(text) {
50278
51300
  function readIfExists(path) {
50279
51301
  try {
50280
51302
  if (existsSync17(path)) {
50281
- return readFileSync14(path, "utf-8");
51303
+ return readFileSync15(path, "utf-8");
50282
51304
  }
50283
51305
  } catch {}
50284
51306
  return null;
@@ -50313,11 +51335,11 @@ var exports_introspect = {};
50313
51335
  __export(exports_introspect, {
50314
51336
  registerIntrospect: () => registerIntrospect
50315
51337
  });
50316
- import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
50317
- import { join as join19 } from "path";
51338
+ import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
51339
+ import { join as join20 } from "path";
50318
51340
  import { execSync } from "child_process";
50319
51341
  function registerIntrospect(parent) {
50320
- parent.command("info").argument("<skill>", "Skill name").option("--json", "Output as JSON", false).option("--brief", "Single line: name \u2014 description [category] (tags: ...)", false).option("--remote", "Use remote registry from SKILLS_API_URL or config apiUrl", false).description("Show details about a specific skill").action((name, options) => {
51342
+ parent.command("info").argument("<skill>", "Skill name").option("--json", "Output as JSON", false).option("--brief", "Single line: name \u2014 description [category] (tags: ...)", false).option("--remote", "Use the remote registry (the resolved Skills credential; HASNA_SKILLS_API_URL for your own instance)", false).description("Show details about a specific skill").action((name, options) => {
50321
51343
  return handleInfo(name, options).catch(async (error) => {
50322
51344
  const notFound = await resolveRemoteNotFound(name, options.remote, error.message);
50323
51345
  if (options.json)
@@ -50327,7 +51349,7 @@ function registerIntrospect(parent) {
50327
51349
  process.exitCode = 1;
50328
51350
  });
50329
51351
  });
50330
- parent.command("show").argument("<skill>", "Skill name").option("--json", "Output as JSON", false).option("--brief", "Single line: name \u2014 description [category] (tags: ...)", false).option("--remote", "Use remote registry from SKILLS_API_URL or config apiUrl", false).description("Show details about a specific skill").action((name, options) => {
51352
+ parent.command("show").argument("<skill>", "Skill name").option("--json", "Output as JSON", false).option("--brief", "Single line: name \u2014 description [category] (tags: ...)", false).option("--remote", "Use the remote registry (the resolved Skills credential; HASNA_SKILLS_API_URL for your own instance)", false).description("Show details about a specific skill").action((name, options) => {
50331
51353
  return handleInfo(name, options).catch(async (error) => {
50332
51354
  const notFound = await resolveRemoteNotFound(name, options.remote, error.message);
50333
51355
  if (options.json)
@@ -50535,36 +51557,36 @@ function handleDiff(name, options) {
50535
51557
  const bare = name;
50536
51558
  const normalized = normalizePortableSkillName(bare);
50537
51559
  const sourcePath = getSkillPath(bare);
50538
- const canonicalDir = join19(resolveCorpusRoot(), normalized);
50539
- const canonicalSkillMd = join19(canonicalDir, "SKILL.md");
51560
+ const canonicalDir = join20(resolveCorpusRoot(), normalized);
51561
+ const canonicalSkillMd = join20(canonicalDir, "SKILL.md");
50540
51562
  const canonical = {
50541
51563
  present: existsSync18(canonicalSkillMd),
50542
51564
  path: canonicalDir,
50543
51565
  ...existsSync18(canonicalSkillMd) ? { hash: hashSkillMarkdownFile(canonicalSkillMd) } : {},
50544
- ...existsSync18(canonicalSkillMd) ? { stub: isPointerSkillMd(readFileSync15(canonicalSkillMd, "utf-8")) } : {}
51566
+ ...existsSync18(canonicalSkillMd) ? { stub: isPointerSkillMd(readFileSync16(canonicalSkillMd, "utf-8")) } : {}
50545
51567
  };
50546
51568
  const pinned = getInstalledSkills().includes(bare);
50547
51569
  const installMeta = getInstallMeta();
50548
51570
  const installedVersion = installMeta.skills[bare]?.version ?? "unknown";
50549
- const registryPkgPath = join19(sourcePath, "package.json");
51571
+ const registryPkgPath = join20(sourcePath, "package.json");
50550
51572
  let registryVersion = "unknown";
50551
51573
  if (existsSync18(registryPkgPath)) {
50552
51574
  try {
50553
- registryVersion = JSON.parse(readFileSync15(registryPkgPath, "utf-8")).version || "unknown";
51575
+ registryVersion = JSON.parse(readFileSync16(registryPkgPath, "utf-8")).version || "unknown";
50554
51576
  } catch {}
50555
51577
  }
50556
51578
  const upToDate = installedVersion === registryVersion;
50557
51579
  const homes = [];
50558
51580
  for (const agent of SYNC_AGENTS) {
50559
- const dir = join19(agentGlobalSkillsDir(agent), normalized);
51581
+ const dir = join20(agentGlobalSkillsDir(agent), normalized);
50560
51582
  const present = existsSync18(dir);
50561
- const managed = existsSync18(join19(dir, SYNC_MARKER_FILE));
50562
- const skillMdPath = join19(dir, "SKILL.md");
51583
+ const managed = existsSync18(join20(dir, SYNC_MARKER_FILE));
51584
+ const skillMdPath = join20(dir, "SKILL.md");
50563
51585
  const hash = present && existsSync18(skillMdPath) ? hashSkillMarkdownFile(skillMdPath) : undefined;
50564
51586
  let stub;
50565
51587
  if (present && existsSync18(skillMdPath)) {
50566
51588
  try {
50567
- stub = isPointerSkillMd(readFileSync15(skillMdPath, "utf-8"));
51589
+ stub = isPointerSkillMd(readFileSync16(skillMdPath, "utf-8"));
50568
51590
  } catch {
50569
51591
  stub = undefined;
50570
51592
  }
@@ -50816,7 +51838,7 @@ var init_tool_primitives = __esm(() => {
50816
51838
  cliCommands: ["skills tools deps <skill>", "skills run <skill>"],
50817
51839
  mcpTools: ["get_skill_tool_dependencies", "run_skill"],
50818
51840
  apiSurfaces: ["runSkill", "SkillRunRecord", "RemoteSkillRunContract"],
50819
- envVars: ["SKILLS_API_KEY"],
51841
+ envVars: ["HASNA_SKILLS_API_KEY"],
50820
51842
  outputTypes: ["text", "json", "markdown", "artifact"],
50821
51843
  capabilities: ["completion", "reasoning", "tool-calling", "vision-input", "structured-output"]
50822
51844
  },
@@ -50872,7 +51894,7 @@ var init_tool_primitives = __esm(() => {
50872
51894
  cliCommands: ["skills tools deps <skill>", "skills run <skill>"],
50873
51895
  mcpTools: ["get_skill_tool_dependencies", "run_skill"],
50874
51896
  apiSurfaces: ["runSkill", "RemoteSkillRunContract"],
50875
- envVars: ["SKILLS_API_KEY"],
51897
+ envVars: ["HASNA_SKILLS_API_KEY"],
50876
51898
  outputTypes: ["png", "jpeg", "webp", "svg", "zip"],
50877
51899
  capabilities: ["image-generation", "image-analysis", "image-editing", "asset-packaging"]
50878
51900
  },
@@ -50886,7 +51908,7 @@ var init_tool_primitives = __esm(() => {
50886
51908
  cliCommands: ["skills tools deps <skill>", "skills run <skill>"],
50887
51909
  mcpTools: ["get_skill_tool_dependencies", "run_skill"],
50888
51910
  apiSurfaces: ["runSkill", "RemoteSkillRunContract"],
50889
- envVars: ["SKILLS_API_KEY"],
51911
+ envVars: ["HASNA_SKILLS_API_KEY"],
50890
51912
  outputTypes: ["mp3", "wav", "txt", "srt", "json", "zip"],
50891
51913
  capabilities: ["transcription", "audio-generation", "voiceover", "audio-cleanup"]
50892
51914
  },
@@ -50900,7 +51922,7 @@ var init_tool_primitives = __esm(() => {
50900
51922
  cliCommands: ["skills tools deps <skill>", "skills run <skill>"],
50901
51923
  mcpTools: ["get_skill_tool_dependencies", "run_skill"],
50902
51924
  apiSurfaces: ["runSkill", "RemoteSkillRunContract"],
50903
- envVars: ["SKILLS_API_KEY"],
51925
+ envVars: ["HASNA_SKILLS_API_KEY"],
50904
51926
  outputTypes: ["mp4", "mov", "srt", "png", "json", "zip"],
50905
51927
  capabilities: ["video-generation", "video-analysis", "captioning", "highlight-extraction"]
50906
51928
  },
@@ -50928,7 +51950,7 @@ var init_tool_primitives = __esm(() => {
50928
51950
  cliCommands: ["skills tools deps <skill>", "skills run <skill>"],
50929
51951
  mcpTools: ["get_skill_tool_dependencies", "run_skill"],
50930
51952
  apiSurfaces: ["SkillRunContext", "RemoteSkillRunContract"],
50931
- envVars: ["SKILLS_API_KEY"],
51953
+ envVars: ["HASNA_SKILLS_API_KEY"],
50932
51954
  outputTypes: ["json", "artifact"],
50933
51955
  capabilities: ["approval", "external-api", "account-scoped-execution"]
50934
51956
  },
@@ -50970,7 +51992,7 @@ var init_tool_primitives = __esm(() => {
50970
51992
  cliCommands: ["skills auth login", "skills run <skill>"],
50971
51993
  mcpTools: ["run_skill"],
50972
51994
  apiSurfaces: ["RemoteSkillsClient", "RemoteSkillRunContract"],
50973
- envVars: ["SKILLS_API_KEY"],
51995
+ envVars: ["HASNA_SKILLS_API_KEY"],
50974
51996
  outputTypes: ["json"],
50975
51997
  capabilities: ["account-auth", "remote-run-submit"]
50976
51998
  }
@@ -51097,8 +52119,8 @@ var exports_init = {};
51097
52119
  __export(exports_init, {
51098
52120
  registerSetup: () => registerSetup
51099
52121
  });
51100
- import { existsSync as existsSync19, readFileSync as readFileSync16, writeFileSync as writeFileSync9, appendFileSync } from "fs";
51101
- import { join as join20 } from "path";
52122
+ import { existsSync as existsSync19, readFileSync as readFileSync17, writeFileSync as writeFileSync9, appendFileSync } from "fs";
52123
+ import { join as join21 } from "path";
51102
52124
  function registerSetup(parent) {
51103
52125
  parent.command("init").option("--json", "Output as JSON", false).option("--for <agent>", "Detect project type and show MCP registration guidance for agent").option("--scope <scope>", "Deprecated; agent skill-folder installs are disabled", "global").description("Initialize project for pinned skills (.env.example, .gitignore)").action((options) => handleInit(options));
51104
52126
  parent.command("export").option("--json", "Output as JSON (default behavior)", false).description("Export pinned skills to JSON for sharing or backup").action((_options) => handleExport());
@@ -51182,7 +52204,7 @@ Use: skills render`));
51182
52204
  lines.push(`# Used by: ${skills.join(", ")}`);
51183
52205
  lines.push(`${envVar}=`);
51184
52206
  }
51185
- writeFileSync9(join20(cwd2, ".env.example"), lines.join(`
52207
+ writeFileSync9(join21(cwd2, ".env.example"), lines.join(`
51186
52208
  `) + `
51187
52209
  `);
51188
52210
  envVarCount = envMap.size;
@@ -51190,9 +52212,9 @@ Use: skills render`));
51190
52212
  console.log(source_default.green(`\u2713 Generated .env.example (${envVarCount} variables from ${installed.length} skills)`));
51191
52213
  } else if (!options.json)
51192
52214
  console.log(source_default.dim(" No environment variables detected across pinned skills"));
51193
- const gitignorePath = join20(cwd2, ".gitignore");
52215
+ const gitignorePath = join21(cwd2, ".gitignore");
51194
52216
  const gitignoreEntries = [".skills/runs/", ".skills/exports/", ".skills/tmp/"];
51195
- let gitignoreContent = existsSync19(gitignorePath) ? readFileSync16(gitignorePath, "utf-8") : "";
52217
+ let gitignoreContent = existsSync19(gitignorePath) ? readFileSync17(gitignorePath, "utf-8") : "";
51196
52218
  let gitignoreUpdated = false;
51197
52219
  const missingEntries = gitignoreEntries.filter((entry) => !gitignoreContent.includes(entry));
51198
52220
  if (missingEntries.length > 0) {
@@ -51247,7 +52269,7 @@ async function handleImport(file, options) {
51247
52269
  process.exitCode = 1;
51248
52270
  return;
51249
52271
  }
51250
- raw = readFileSync16(file, "utf-8");
52272
+ raw = readFileSync17(file, "utf-8");
51251
52273
  }
51252
52274
  } catch (err) {
51253
52275
  const error = `Failed to read file: ${err.message}`;
@@ -51344,9 +52366,9 @@ var init_init = __esm(() => {
51344
52366
  });
51345
52367
 
51346
52368
  // src/lib/home-adoption.ts
51347
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync10, readFileSync as readFileSync17, rmSync as rmSync6, statSync as statSync10, writeFileSync as writeFileSync10 } from "fs";
51348
- import { homedir as homedir7 } from "os";
51349
- import { join as join21 } from "path";
52369
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync10, readFileSync as readFileSync18, rmSync as rmSync6, statSync as statSync11, writeFileSync as writeFileSync10 } from "fs";
52370
+ import { homedir as homedir5 } from "os";
52371
+ import { join as join22 } from "path";
51350
52372
  function indexCanonicalCorpus(corpusRoot) {
51351
52373
  const byName = new Map;
51352
52374
  if (!existsSync20(corpusRoot))
@@ -51360,7 +52382,7 @@ function indexCanonicalCorpus(corpusRoot) {
51360
52382
  for (const entry of entries.sort()) {
51361
52383
  if (entry.startsWith("."))
51362
52384
  continue;
51363
- const skillMd = join21(corpusRoot, entry, "SKILL.md");
52385
+ const skillMd = join22(corpusRoot, entry, "SKILL.md");
51364
52386
  if (!existsSync20(skillMd))
51365
52387
  continue;
51366
52388
  try {
@@ -51370,13 +52392,13 @@ function indexCanonicalCorpus(corpusRoot) {
51370
52392
  return byName;
51371
52393
  }
51372
52394
  function scanUnmarkedHomes(options = {}) {
51373
- const homeDir = options.homeDir ?? homedir7();
52395
+ const homeDir2 = options.homeDir ?? homedir5();
51374
52396
  const corpusRoot = resolveCorpusRoot(options);
51375
52397
  const index = indexCanonicalCorpus(corpusRoot);
51376
52398
  const agents = options.agents?.length ? options.agents : [...SYNC_AGENTS];
51377
52399
  const scan = { adoptable: [], conflicts: [], unknown: [], managed: 0 };
51378
52400
  for (const agent of agents) {
51379
- const home = agentGlobalSkillsDir(agent, homeDir);
52401
+ const home = agentGlobalSkillsDir(agent, homeDir2);
51380
52402
  if (!existsSync20(home))
51381
52403
  continue;
51382
52404
  let entries = [];
@@ -51388,22 +52410,22 @@ function scanUnmarkedHomes(options = {}) {
51388
52410
  for (const skill of entries.sort()) {
51389
52411
  if (skill.startsWith("."))
51390
52412
  continue;
51391
- const dir = join21(home, skill);
52413
+ const dir = join22(home, skill);
51392
52414
  try {
51393
- if (!statSync10(dir).isDirectory())
52415
+ if (!statSync11(dir).isDirectory())
51394
52416
  continue;
51395
52417
  } catch {
51396
52418
  continue;
51397
52419
  }
51398
- if (existsSync20(join21(dir, SYNC_MARKER_FILE))) {
52420
+ if (existsSync20(join22(dir, SYNC_MARKER_FILE))) {
51399
52421
  scan.managed += 1;
51400
52422
  continue;
51401
52423
  }
51402
- const skillMdPath = join21(dir, "SKILL.md");
52424
+ const skillMdPath = join22(dir, "SKILL.md");
51403
52425
  if (!existsSync20(skillMdPath))
51404
52426
  continue;
51405
52427
  const hash = hashSkillMarkdownFile(skillMdPath);
51406
- const mtime = statSync10(skillMdPath).mtime.toISOString();
52428
+ const mtime = statSync11(skillMdPath).mtime.toISOString();
51407
52429
  const entry = { agent, skill, home, path: dir, hash, mtime };
51408
52430
  const canonicalHash = index.get(skill);
51409
52431
  if (canonicalHash === undefined) {
@@ -51420,11 +52442,11 @@ function scanUnmarkedHomes(options = {}) {
51420
52442
  function appendConflictsLedger(appDir, conflicts) {
51421
52443
  if (conflicts.length === 0)
51422
52444
  return;
51423
- const ledgerPath = join21(appDir, CONFLICTS_LEDGER_FILE);
52445
+ const ledgerPath = join22(appDir, CONFLICTS_LEDGER_FILE);
51424
52446
  let ledger = { version: 1, entries: [] };
51425
52447
  if (existsSync20(ledgerPath)) {
51426
52448
  try {
51427
- const parsed = JSON.parse(readFileSync17(ledgerPath, "utf-8"));
52449
+ const parsed = JSON.parse(readFileSync18(ledgerPath, "utf-8"));
51428
52450
  if (parsed && typeof parsed === "object" && Array.isArray(parsed.entries)) {
51429
52451
  ledger = parsed;
51430
52452
  }
@@ -51440,9 +52462,9 @@ function appendConflictsLedger(appDir, conflicts) {
51440
52462
  `);
51441
52463
  }
51442
52464
  function writeRollbackRecord(mode, entries, appDir = getDataDir()) {
51443
- const dir = join21(appDir, ROLLBACK_DIRNAME);
52465
+ const dir = join22(appDir, ROLLBACK_DIRNAME);
51444
52466
  mkdirSync9(dir, { recursive: true });
51445
- const file = join21(dir, `${mode}-${Date.now()}.json`);
52467
+ const file = join22(dir, `${mode}-${Date.now()}.json`);
51446
52468
  const record = { version: 1, mode, timestamp: new Date().toISOString(), entries };
51447
52469
  writeFileSync10(file, `${JSON.stringify(record, null, 2)}
51448
52470
  `);
@@ -51453,7 +52475,7 @@ function adoptUnmarkedHomes(options = {}) {
51453
52475
  if (!options.apply) {
51454
52476
  return { ...scan, applied: false };
51455
52477
  }
51456
- const appDir = options.homeDir ? join21(options.homeDir, ".hasna", "skills") : getDataDir();
52478
+ const appDir = options.homeDir ? join22(options.homeDir, ".hasna", "skills") : getDataDir();
51457
52479
  const markers = scan.adoptable.map((entry) => {
51458
52480
  const marker = {
51459
52481
  managedBy: SYNC_MARKER_MANAGED_BY,
@@ -51461,7 +52483,7 @@ function adoptUnmarkedHomes(options = {}) {
51461
52483
  source: "adopted",
51462
52484
  syncedAt: new Date().toISOString()
51463
52485
  };
51464
- writeFileSync10(join21(entry.path, SYNC_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
52486
+ writeFileSync10(join22(entry.path, SYNC_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
51465
52487
  `);
51466
52488
  return { agent: entry.agent, skill: entry.skill, path: entry.path, hash: entry.hash, marker };
51467
52489
  });
@@ -51473,13 +52495,13 @@ function adoptUnmarkedHomes(options = {}) {
51473
52495
  return { ...scan, applied: true, rollbackFile };
51474
52496
  }
51475
52497
  function pruneStrayHomes(options = {}) {
51476
- const homeDir = options.homeDir ?? homedir7();
52498
+ const homeDir2 = options.homeDir ?? homedir5();
51477
52499
  const corpusRoot = resolveCorpusRoot(options);
51478
52500
  const index = indexCanonicalCorpus(corpusRoot);
51479
52501
  const agents = options.agents?.length ? options.agents : [...SYNC_AGENTS];
51480
52502
  const candidates = [];
51481
52503
  for (const agent of agents) {
51482
- const home = agentGlobalSkillsDir(agent, homeDir);
52504
+ const home = agentGlobalSkillsDir(agent, homeDir2);
51483
52505
  if (!existsSync20(home))
51484
52506
  continue;
51485
52507
  let entries = [];
@@ -51491,28 +52513,28 @@ function pruneStrayHomes(options = {}) {
51491
52513
  for (const skill of entries.sort()) {
51492
52514
  if (skill.startsWith("."))
51493
52515
  continue;
51494
- const dir = join21(home, skill);
52516
+ const dir = join22(home, skill);
51495
52517
  try {
51496
- if (!statSync10(dir).isDirectory())
52518
+ if (!statSync11(dir).isDirectory())
51497
52519
  continue;
51498
52520
  } catch {
51499
52521
  continue;
51500
52522
  }
51501
- const markerPath = join21(dir, SYNC_MARKER_FILE);
52523
+ const markerPath = join22(dir, SYNC_MARKER_FILE);
51502
52524
  if (!existsSync20(markerPath))
51503
52525
  continue;
51504
52526
  if (index.has(skill))
51505
52527
  continue;
51506
52528
  let marker;
51507
52529
  try {
51508
- const parsed = JSON.parse(readFileSync17(markerPath, "utf-8"));
52530
+ const parsed = JSON.parse(readFileSync18(markerPath, "utf-8"));
51509
52531
  if (!parsed || typeof parsed !== "object" || typeof parsed.managedBy !== "string")
51510
52532
  continue;
51511
52533
  marker = parsed;
51512
52534
  } catch {
51513
52535
  continue;
51514
52536
  }
51515
- const skillMdPath = join21(dir, "SKILL.md");
52537
+ const skillMdPath = join22(dir, "SKILL.md");
51516
52538
  const hash = existsSync20(skillMdPath) ? hashSkillMarkdownFile(skillMdPath) : "";
51517
52539
  candidates.push({ agent, skill, home, path: dir, hash, marker });
51518
52540
  }
@@ -51520,7 +52542,7 @@ function pruneStrayHomes(options = {}) {
51520
52542
  if (!options.apply) {
51521
52543
  return { candidates, pruned: 0, dryRun: true };
51522
52544
  }
51523
- const appDir = options.homeDir ? join21(options.homeDir, ".hasna", "skills") : getDataDir();
52545
+ const appDir = options.homeDir ? join22(options.homeDir, ".hasna", "skills") : getDataDir();
51524
52546
  const rollbackFile = writeRollbackRecord("prune", candidates.map(({ agent, skill, path, hash, marker }) => ({ agent, skill, path, hash, marker })), appDir);
51525
52547
  for (const candidate of candidates) {
51526
52548
  rmSync6(candidate.path, { recursive: true, force: true });
@@ -51536,9 +52558,9 @@ var init_home_adoption = __esm(() => {
51536
52558
  });
51537
52559
 
51538
52560
  // src/lib/home-census.ts
51539
- import { existsSync as existsSync21, readFileSync as readFileSync18, readdirSync as readdirSync11, statSync as statSync11 } from "fs";
51540
- import { homedir as homedir8 } from "os";
51541
- import { join as join22 } from "path";
52561
+ import { existsSync as existsSync21, readFileSync as readFileSync19, readdirSync as readdirSync11, statSync as statSync12 } from "fs";
52562
+ import { homedir as homedir6 } from "os";
52563
+ import { join as join23 } from "path";
51542
52564
  function sortEntries(entries) {
51543
52565
  return entries.sort((a, b) => {
51544
52566
  const left = `${a.agent}/${a.skill}/${a.kind}`;
@@ -51547,7 +52569,7 @@ function sortEntries(entries) {
51547
52569
  });
51548
52570
  }
51549
52571
  function censusHomeDrift(options = {}) {
51550
- const homeDir = options.homeDir ?? homedir8();
52572
+ const homeDir2 = options.homeDir ?? homedir6();
51551
52573
  const corpusRoot = resolveCorpusRoot(options);
51552
52574
  const index = indexCanonicalCorpus(corpusRoot);
51553
52575
  const agents = options.agents?.length ? options.agents : [...SYNC_AGENTS];
@@ -51556,7 +52578,7 @@ function censusHomeDrift(options = {}) {
51556
52578
  let managed = 0;
51557
52579
  let homesChecked = 0;
51558
52580
  for (const agent of agents) {
51559
- const home = agentGlobalSkillsDir(agent, homeDir);
52581
+ const home = agentGlobalSkillsDir(agent, homeDir2);
51560
52582
  if (!existsSync21(home))
51561
52583
  continue;
51562
52584
  homesChecked += 1;
@@ -51570,15 +52592,15 @@ function censusHomeDrift(options = {}) {
51570
52592
  for (const skill of dirEntries.sort()) {
51571
52593
  if (skill.startsWith("."))
51572
52594
  continue;
51573
- const dir = join22(home, skill);
52595
+ const dir = join23(home, skill);
51574
52596
  try {
51575
- if (!statSync11(dir).isDirectory())
52597
+ if (!statSync12(dir).isDirectory())
51576
52598
  continue;
51577
52599
  } catch {
51578
52600
  continue;
51579
52601
  }
51580
52602
  present.add(skill);
51581
- const markerPath = join22(dir, SYNC_MARKER_FILE);
52603
+ const markerPath = join23(dir, SYNC_MARKER_FILE);
51582
52604
  if (!existsSync21(markerPath)) {
51583
52605
  unmarked += 1;
51584
52606
  continue;
@@ -51589,7 +52611,7 @@ function censusHomeDrift(options = {}) {
51589
52611
  entries.push({ agent, skill, kind: "stray-in-home", path: dir });
51590
52612
  continue;
51591
52613
  }
51592
- const skillMdPath = join22(dir, "SKILL.md");
52614
+ const skillMdPath = join23(dir, "SKILL.md");
51593
52615
  if (!existsSync21(skillMdPath)) {
51594
52616
  entries.push({ agent, skill, kind: "diverged", path: dir, canonicalHash });
51595
52617
  continue;
@@ -51598,14 +52620,14 @@ function censusHomeDrift(options = {}) {
51598
52620
  if (homeHash !== canonicalHash) {
51599
52621
  let homeStub;
51600
52622
  try {
51601
- homeStub = isPointerSkillMd(readFileSync18(skillMdPath, "utf-8"));
52623
+ homeStub = isPointerSkillMd(readFileSync19(skillMdPath, "utf-8"));
51602
52624
  } catch {
51603
52625
  homeStub = undefined;
51604
52626
  }
51605
52627
  let canonicalStub;
51606
- const canonicalSkillMd = join22(corpusRoot, skill, "SKILL.md");
52628
+ const canonicalSkillMd = join23(corpusRoot, skill, "SKILL.md");
51607
52629
  try {
51608
- canonicalStub = isPointerSkillMd(readFileSync18(canonicalSkillMd, "utf-8"));
52630
+ canonicalStub = isPointerSkillMd(readFileSync19(canonicalSkillMd, "utf-8"));
51609
52631
  } catch {
51610
52632
  canonicalStub = undefined;
51611
52633
  }
@@ -51618,7 +52640,7 @@ function censusHomeDrift(options = {}) {
51618
52640
  agent,
51619
52641
  skill: name,
51620
52642
  kind: "missing-from-home",
51621
- path: join22(home, name),
52643
+ path: join23(home, name),
51622
52644
  canonicalHash
51623
52645
  });
51624
52646
  }
@@ -51644,8 +52666,8 @@ var exports_diagnostic = {};
51644
52666
  __export(exports_diagnostic, {
51645
52667
  registerDiagnostic: () => registerDiagnostic
51646
52668
  });
51647
- import { existsSync as existsSync22, readFileSync as readFileSync19, readdirSync as readdirSync12, statSync as statSync12, writeFileSync as writeFileSync11 } from "fs";
51648
- import { join as join23 } from "path";
52669
+ import { existsSync as existsSync22, readFileSync as readFileSync20, readdirSync as readdirSync12, statSync as statSync13, writeFileSync as writeFileSync11 } from "fs";
52670
+ import { join as join24 } from "path";
51649
52671
  import { execSync as execSync2 } from "child_process";
51650
52672
  function registerDiagnostic(parent) {
51651
52673
  parent.command("doctor").option("--json", "Output as JSON", false).description("Check env vars, system deps, and readiness for pinned skills").action((options) => handleDoctor(options));
@@ -51761,7 +52783,7 @@ Skills Test (${results.length} skill${results.length === 1 ? "" : "s"}):
51761
52783
  }
51762
52784
  function handleAuth(name, options) {
51763
52785
  const cwd2 = process.cwd();
51764
- const envFilePath = join23(cwd2, ".env");
52786
+ const envFilePath = join24(cwd2, ".env");
51765
52787
  if (options.set) {
51766
52788
  const eqIdx = options.set.indexOf("=");
51767
52789
  if (eqIdx === -1) {
@@ -51783,7 +52805,7 @@ function handleAuth(name, options) {
51783
52805
  process.exitCode = 1;
51784
52806
  return;
51785
52807
  }
51786
- let existing = existsSync22(envFilePath) ? readFileSync19(envFilePath, "utf-8") : "";
52808
+ let existing = existsSync22(envFilePath) ? readFileSync20(envFilePath, "utf-8") : "";
51787
52809
  const keyPattern = new RegExp(`^${key}=.*$`, "m");
51788
52810
  const updated = keyPattern.test(existing) ? existing.replace(keyPattern, `${key}=${value}`) : existing.endsWith(`
51789
52811
  `) || existing === "" ? existing + `${key}=${value}
@@ -51853,13 +52875,14 @@ function handleWhoami(options) {
51853
52875
  let skillCount = 0;
51854
52876
  if (exists)
51855
52877
  try {
51856
- skillCount = readdirSync12(agentSkillsPath).filter((f) => !f.startsWith(".") && statSync12(join23(agentSkillsPath, f)).isDirectory()).length;
52878
+ skillCount = readdirSync12(agentSkillsPath).filter((f) => !f.startsWith(".") && statSync13(join24(agentSkillsPath, f)).isDirectory()).length;
51857
52879
  } catch {}
51858
52880
  agentConfigs.push({ agent, label: AGENT_LABELS[agent], path: agentSkillsPath, exists, skillCount });
51859
52881
  }
51860
52882
  const skillsDir = resolveCorpusRoot();
52883
+ const credential = describeCredentialState();
51861
52884
  if (options.json) {
51862
- console.log(JSON.stringify({ version: package_default.version, installedCount: installed.length, installed, agents: agentConfigs, skillsDir, cwd: process.cwd() }, null, 2));
52885
+ console.log(JSON.stringify({ version: package_default.version, installedCount: installed.length, installed, agents: agentConfigs, skillsDir, cwd: process.cwd(), credential }, null, 2));
51863
52886
  return;
51864
52887
  }
51865
52888
  console.log(source_default.bold(`
@@ -51867,6 +52890,13 @@ skills v${package_default.version}
51867
52890
  `));
51868
52891
  console.log(`${source_default.dim("Working directory:")} ${process.cwd()}`);
51869
52892
  console.log(`${source_default.dim("Corpus directory:")} ${skillsDir}`);
52893
+ console.log(`${source_default.dim("Mode:")} ${credential.mode}`);
52894
+ if (credential.apiUrl)
52895
+ console.log(`${source_default.dim("API:")} ${credential.apiUrl} (${credential.apiUrlSource})`);
52896
+ if (credential.apiKeySource)
52897
+ console.log(`${source_default.dim("Credential:")} ${credential.apiKeySource}`);
52898
+ if (credential.error)
52899
+ console.log(source_default.red(`${source_default.dim("Credential:")} ${credential.error}`));
51870
52900
  console.log();
51871
52901
  if (!installed.length)
51872
52902
  console.log(source_default.dim("No pinned skills in current project"));
@@ -51880,6 +52910,51 @@ skills v${package_default.version}
51880
52910
  for (const cfg of agentConfigs)
51881
52911
  console.log(cfg.exists ? ` ${source_default.green("\u2713")} ${cfg.agent} \u2014 ${cfg.skillCount} skill(s) at ${cfg.path}` : ` ${source_default.dim("\u2717")} ${cfg.agent} \u2014 not configured`);
51882
52912
  }
52913
+ function describeCredentialState() {
52914
+ let credentialsFile = null;
52915
+ let mode = null;
52916
+ try {
52917
+ credentialsFile = getAuthFilePath();
52918
+ const bits = credentialFileMode();
52919
+ mode = bits === null ? null : `0${bits.toString(8).padStart(3, "0")}`;
52920
+ } catch {}
52921
+ try {
52922
+ const fleet = resolveSkillsFleet();
52923
+ if (fleet.mode === "hosted") {
52924
+ return {
52925
+ mode: "hosted",
52926
+ apiUrl: fleet.apiOrigin,
52927
+ apiUrlSource: fleet.apiUrlSource,
52928
+ apiKeySource: fleet.apiKeySource,
52929
+ apiKeyTier: fleet.apiKeyTier,
52930
+ credentialsFile,
52931
+ credentialsFileMode: mode,
52932
+ error: null
52933
+ };
52934
+ }
52935
+ return {
52936
+ mode: "local",
52937
+ apiUrl: null,
52938
+ apiUrlSource: null,
52939
+ apiKeySource: null,
52940
+ apiKeyTier: null,
52941
+ credentialsFile,
52942
+ credentialsFileMode: mode,
52943
+ error: null
52944
+ };
52945
+ } catch (error) {
52946
+ return {
52947
+ mode: "misconfigured",
52948
+ apiUrl: null,
52949
+ apiUrlSource: null,
52950
+ apiKeySource: null,
52951
+ apiKeyTier: null,
52952
+ credentialsFile,
52953
+ credentialsFileMode: mode,
52954
+ error: error.message
52955
+ };
52956
+ }
52957
+ }
51883
52958
  function handleOutdated(options) {
51884
52959
  const installed = getInstalledSkills();
51885
52960
  const pins = [];
@@ -51887,11 +52962,11 @@ function handleOutdated(options) {
51887
52962
  for (const name of installed) {
51888
52963
  const installedVersion = meta.skills[name]?.version ?? "unknown";
51889
52964
  const registryPath = getSkillPath(name);
51890
- const registryPkgPath = join23(registryPath, "package.json");
52965
+ const registryPkgPath = join24(registryPath, "package.json");
51891
52966
  let registryVersion = "unknown";
51892
52967
  if (existsSync22(registryPkgPath))
51893
52968
  try {
51894
- registryVersion = JSON.parse(readFileSync19(registryPkgPath, "utf-8")).version || "unknown";
52969
+ registryVersion = JSON.parse(readFileSync20(registryPkgPath, "utf-8")).version || "unknown";
51895
52970
  } catch {}
51896
52971
  if (installedVersion !== registryVersion)
51897
52972
  pins.push({ skill: name, installedVersion, registryVersion });
@@ -51940,6 +53015,8 @@ var init_diagnostic = __esm(() => {
51940
53015
  init_installer();
51941
53016
  init_home_census();
51942
53017
  init_home_migration();
53018
+ init_auth_store();
53019
+ init_fleet_credentials();
51943
53020
  });
51944
53021
 
51945
53022
  // src/lib/blog-article.ts
@@ -52112,20 +53189,20 @@ var init_runs = __esm(() => {
52112
53189
 
52113
53190
  // src/lib/run-state.ts
52114
53191
  import { createHash as createHash4, randomBytes } from "crypto";
52115
- import { existsSync as existsSync23, mkdirSync as mkdirSync10, readFileSync as readFileSync20, readdirSync as readdirSync13, statSync as statSync13, writeFileSync as writeFileSync12 } from "fs";
52116
- import { extname, join as join24, relative as relative3 } from "path";
53192
+ import { existsSync as existsSync23, mkdirSync as mkdirSync10, readFileSync as readFileSync21, readdirSync as readdirSync13, statSync as statSync14, writeFileSync as writeFileSync12 } from "fs";
53193
+ import { extname, join as join25, relative as relative3 } from "path";
52117
53194
  function createSkillRun(params, targetDir = process.cwd()) {
52118
53195
  const now3 = new Date;
52119
53196
  const id = createRunId(now3);
52120
53197
  const day = now3.toISOString().slice(0, 10);
52121
53198
  const skillName = normalizeSkillName(params.skill);
52122
53199
  const root = getProjectStateDir(targetDir);
52123
- const runDir = join24(root, "runs", day, id);
52124
- const logsDir = join24(runDir, "logs");
52125
- const exportDir = join24(root, "exports", skillName, id);
53200
+ const runDir = join25(root, "runs", day, id);
53201
+ const logsDir = join25(runDir, "logs");
53202
+ const exportDir = join25(root, "exports", skillName, id);
52126
53203
  mkdirSync10(logsDir, { recursive: true });
52127
53204
  mkdirSync10(exportDir, { recursive: true });
52128
- mkdirSync10(join24(root, "tmp"), { recursive: true });
53205
+ mkdirSync10(join25(root, "tmp"), { recursive: true });
52129
53206
  const record = {
52130
53207
  id,
52131
53208
  skill: skillName,
@@ -52176,27 +53253,27 @@ function updateSkillRun(context, patch) {
52176
53253
  return context.record;
52177
53254
  }
52178
53255
  function writeRunLogs(context, stdout = "", stderr = "") {
52179
- writeFileSync12(join24(context.logsDir, "stdout.log"), stdout);
52180
- writeFileSync12(join24(context.logsDir, "stderr.log"), stderr);
53256
+ writeFileSync12(join25(context.logsDir, "stdout.log"), stdout);
53257
+ writeFileSync12(join25(context.logsDir, "stderr.log"), stderr);
52181
53258
  }
52182
53259
  function appendRunEvent(context, event, data = {}) {
52183
53260
  const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
52184
53261
  `;
52185
- const path = join24(context.runDir, "events.ndjson");
52186
- const previous = existsSync23(path) ? readFileSync20(path, "utf-8") : "";
53262
+ const path = join25(context.runDir, "events.ndjson");
53263
+ const previous = existsSync23(path) ? readFileSync21(path, "utf-8") : "";
52187
53264
  writeFileSync12(path, previous + line);
52188
53265
  }
52189
53266
  function listSkillRuns(targetDir = process.cwd(), limit = 50) {
52190
- const runsRoot = join24(getProjectStateDir(targetDir), "runs");
53267
+ const runsRoot = join25(getProjectStateDir(targetDir), "runs");
52191
53268
  if (!existsSync23(runsRoot))
52192
53269
  return [];
52193
53270
  const records = [];
52194
53271
  for (const day of readdirSync13(runsRoot).sort().reverse()) {
52195
- const dayDir = join24(runsRoot, day);
52196
- if (!statSync13(dayDir).isDirectory())
53272
+ const dayDir = join25(runsRoot, day);
53273
+ if (!statSync14(dayDir).isDirectory())
52197
53274
  continue;
52198
53275
  for (const runId of readdirSync13(dayDir).sort().reverse()) {
52199
- const record = readRunRecord(join24(dayDir, runId));
53276
+ const record = readRunRecord(join25(dayDir, runId));
52200
53277
  if (record)
52201
53278
  records.push(record);
52202
53279
  if (records.length >= limit)
@@ -52206,11 +53283,11 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
52206
53283
  return records;
52207
53284
  }
52208
53285
  function findSkillRun(runId, targetDir = process.cwd()) {
52209
- const runsRoot = join24(getProjectStateDir(targetDir), "runs");
53286
+ const runsRoot = join25(getProjectStateDir(targetDir), "runs");
52210
53287
  if (!existsSync23(runsRoot))
52211
53288
  return null;
52212
53289
  for (const day of readdirSync13(runsRoot)) {
52213
- const record = readRunRecord(join24(runsRoot, day, runId));
53290
+ const record = readRunRecord(join25(runsRoot, day, runId));
52214
53291
  if (record)
52215
53292
  return record;
52216
53293
  }
@@ -52227,14 +53304,14 @@ function skillRunEnv(context) {
52227
53304
  };
52228
53305
  }
52229
53306
  function getRunExportDir(runId, skill, targetDir = process.cwd()) {
52230
- return join24(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
53307
+ return join25(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
52231
53308
  }
52232
53309
  function writeRunRecord(context) {
52233
- writeFileSync12(join24(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
53310
+ writeFileSync12(join25(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
52234
53311
  `);
52235
53312
  }
52236
53313
  function writeArtifactsManifest(context, artifacts) {
52237
- writeFileSync12(join24(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
53314
+ writeFileSync12(join25(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
52238
53315
  `);
52239
53316
  }
52240
53317
  function collectRunArtifacts(context) {
@@ -52242,8 +53319,8 @@ function collectRunArtifacts(context) {
52242
53319
  return [];
52243
53320
  const artifacts = [];
52244
53321
  for (const path of walkFiles(context.exportDir)) {
52245
- const stat = statSync13(path);
52246
- const bytes = readFileSync20(path);
53322
+ const stat = statSync14(path);
53323
+ const bytes = readFileSync21(path);
52247
53324
  artifacts.push({
52248
53325
  path: toProjectRelative(context.targetDir, path),
52249
53326
  mime: mimeForPath(path),
@@ -52254,11 +53331,11 @@ function collectRunArtifacts(context) {
52254
53331
  return artifacts.sort((a, b) => a.path.localeCompare(b.path));
52255
53332
  }
52256
53333
  function readRunRecord(runDir) {
52257
- const path = join24(runDir, "run.json");
53334
+ const path = join25(runDir, "run.json");
52258
53335
  if (!existsSync23(path))
52259
53336
  return null;
52260
53337
  try {
52261
- return JSON.parse(readFileSync20(path, "utf-8"));
53338
+ return JSON.parse(readFileSync21(path, "utf-8"));
52262
53339
  } catch {
52263
53340
  return null;
52264
53341
  }
@@ -52266,8 +53343,8 @@ function readRunRecord(runDir) {
52266
53343
  function walkFiles(dir) {
52267
53344
  const files = [];
52268
53345
  for (const entry of readdirSync13(dir)) {
52269
- const full = join24(dir, entry);
52270
- if (statSync13(full).isDirectory())
53346
+ const full = join25(dir, entry);
53347
+ if (statSync14(full).isDirectory())
52271
53348
  files.push(...walkFiles(full));
52272
53349
  else
52273
53350
  files.push(full);
@@ -56901,7 +57978,7 @@ var init_v4 = __esm(() => {
56901
57978
  init_classic();
56902
57979
  });
56903
57980
 
56904
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
57981
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
56905
57982
  function assertCompleteRequestPrompt(request) {
56906
57983
  if (request.params.ref.type !== "ref/prompt") {
56907
57984
  throw new TypeError(`Expected CompleteRequestPrompt, but got ${request.params.ref.type}`);
@@ -57739,7 +58816,7 @@ var init_types2 = __esm(() => {
57739
58816
  };
57740
58817
  });
57741
58818
 
57742
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
58819
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
57743
58820
  class ReadBuffer {
57744
58821
  constructor(options) {
57745
58822
  this._maxBufferSize = options?.maxBufferSize ?? STDIO_DEFAULT_MAX_BUFFER_SIZE;
@@ -57782,7 +58859,7 @@ var init_stdio = __esm(() => {
57782
58859
  STDIO_DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
57783
58860
  });
57784
58861
 
57785
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
58862
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
57786
58863
  import process14 from "process";
57787
58864
 
57788
58865
  class StdioServerTransport {
@@ -57940,7 +59017,7 @@ var init_v4_mini = __esm(() => {
57940
59017
  init_mini();
57941
59018
  });
57942
59019
 
57943
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
59020
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
57944
59021
  function isZ4Schema(s) {
57945
59022
  const schema = s;
57946
59023
  return !!schema._zod;
@@ -58105,7 +59182,7 @@ var init_zod_compat = __esm(() => {
58105
59182
  init_v4_mini();
58106
59183
  });
58107
59184
 
58108
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
59185
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
58109
59186
  function isTerminal(status) {
58110
59187
  return status === "completed" || status === "failed" || status === "cancelled";
58111
59188
  }
@@ -59509,7 +60586,7 @@ var init_esm2 = __esm(() => {
59509
60586
  init_zodToJsonSchema();
59510
60587
  });
59511
60588
 
59512
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
60589
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
59513
60590
  function mapMiniTarget(t) {
59514
60591
  if (!t)
59515
60592
  return "draft-7";
@@ -59556,7 +60633,7 @@ var init_zod_json_schema_compat = __esm(() => {
59556
60633
  init_esm2();
59557
60634
  });
59558
60635
 
59559
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
60636
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
59560
60637
  class Protocol {
59561
60638
  constructor(_options) {
59562
60639
  this._options = _options;
@@ -63403,13 +64480,32 @@ var require_data = __commonJS((exports, module) => {
63403
64480
  };
63404
64481
  });
63405
64482
 
63406
- // ../../node_modules/.bun/fast-uri@3.1.5/node_modules/fast-uri/lib/utils.js
64483
+ // ../../node_modules/.bun/fast-uri@3.1.7/node_modules/fast-uri/lib/utils.js
63407
64484
  var require_utils = __commonJS((exports, module) => {
63408
64485
  var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
63409
64486
  var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
64487
+ var isPort = RegExp.prototype.test.bind(/^\d*$/u);
63410
64488
  var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
63411
64489
  var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
63412
- var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
64490
+ var isPathCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/]$/u);
64491
+ var isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/?]$/u);
64492
+ var isUserinfoCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:]$/u);
64493
+ var BYTE_HEX = new Array(256);
64494
+ {
64495
+ const HEX_DIGITS = "0123456789ABCDEF";
64496
+ for (let i = 0;i < 256; i++) {
64497
+ BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
64498
+ }
64499
+ }
64500
+ function percentEncodeNonAscii(cp) {
64501
+ if (cp < 2048) {
64502
+ return BYTE_HEX[192 | cp >> 6] + BYTE_HEX[128 | cp & 63];
64503
+ }
64504
+ if (cp < 65536) {
64505
+ return BYTE_HEX[224 | cp >> 12] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
64506
+ }
64507
+ return BYTE_HEX[240 | cp >> 18] + BYTE_HEX[128 | cp >> 12 & 63] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
64508
+ }
63413
64509
  function stringArrayToHexStripped(input) {
63414
64510
  let acc = "";
63415
64511
  let code = 0;
@@ -63434,91 +64530,122 @@ var require_utils = __commonJS((exports, module) => {
63434
64530
  }
63435
64531
  return acc;
63436
64532
  }
64533
+ var isHextet = RegExp.prototype.test.bind(/^[\dA-Fa-f]{1,4}$/);
64534
+ var isIPvFuture = RegExp.prototype.test.bind(/^[vV][\dA-Fa-f]+\.[A-Za-z\d\-._~!$&'()*+,;=:]+$/);
64535
+ var isZoneCharacter = RegExp.prototype.test.bind(/^[A-Za-z\d\-._~]$/);
63437
64536
  var nonSimpleDomain = RegExp.prototype.test.bind(/[^!"$&'()*+,\-.;=_`a-z{}~]/u);
63438
- function consumeIsZone(buffer) {
63439
- buffer.length = 0;
63440
- return true;
63441
- }
63442
- function consumeHextets(buffer, address, output) {
63443
- if (buffer.length) {
63444
- const hex = stringArrayToHexStripped(buffer);
63445
- if (hex !== "") {
63446
- address.push(hex);
63447
- } else {
63448
- output.error = true;
63449
- return false;
64537
+ function isZoneIdentifier(zone) {
64538
+ if (zone.length === 0)
64539
+ return false;
64540
+ for (let i = 0;i < zone.length; i++) {
64541
+ if (isZoneCharacter(zone[i]))
64542
+ continue;
64543
+ if (zone[i] === "%" && i + 2 < zone.length && isHexPair(zone.slice(i + 1, i + 3))) {
64544
+ i += 2;
64545
+ continue;
63450
64546
  }
63451
- buffer.length = 0;
64547
+ return false;
63452
64548
  }
63453
64549
  return true;
63454
64550
  }
63455
- function getIPV6(input) {
63456
- let tokenCount = 0;
63457
- const output = { error: false, address: "", zone: "" };
63458
- const address = [];
63459
- const buffer = [];
63460
- let endipv6Encountered = false;
63461
- let endIpv6 = false;
63462
- let consume = consumeHextets;
63463
- for (let i = 0;i < input.length; i++) {
63464
- const cursor = input[i];
63465
- if (cursor === "[" || cursor === "]") {
63466
- continue;
63467
- }
63468
- if (cursor === ":") {
63469
- if (endipv6Encountered === true) {
63470
- endIpv6 = true;
63471
- }
63472
- if (!consume(buffer, address, output)) {
63473
- break;
63474
- }
63475
- if (++tokenCount > 7) {
63476
- output.error = true;
63477
- break;
63478
- }
63479
- if (i > 0 && input[i - 1] === ":") {
63480
- endipv6Encountered = true;
63481
- }
63482
- address.push(":");
63483
- continue;
63484
- } else if (cursor === "%") {
63485
- if (!consume(buffer, address, output)) {
63486
- break;
64551
+ function compressIPv6ZeroRun(hextets) {
64552
+ let bestStart = -1;
64553
+ let bestLength = 0;
64554
+ let runStart = -1;
64555
+ let runLength = 0;
64556
+ for (let i = 0;i < hextets.length; i++) {
64557
+ if (hextets[i] === "0") {
64558
+ if (runStart === -1)
64559
+ runStart = i;
64560
+ runLength++;
64561
+ if (runLength > bestLength) {
64562
+ bestLength = runLength;
64563
+ bestStart = runStart;
63487
64564
  }
63488
- consume = consumeIsZone;
63489
64565
  } else {
63490
- buffer.push(cursor);
63491
- continue;
64566
+ runStart = -1;
64567
+ runLength = 0;
63492
64568
  }
63493
64569
  }
63494
- if (buffer.length) {
63495
- if (consume === consumeIsZone) {
63496
- output.zone = buffer.join("");
63497
- } else if (endIpv6) {
63498
- address.push(buffer.join(""));
63499
- } else {
63500
- address.push(stringArrayToHexStripped(buffer));
64570
+ if (bestLength < 2)
64571
+ return hextets.join(":");
64572
+ const head2 = hextets.slice(0, bestStart).join(":");
64573
+ const tail2 = hextets.slice(bestStart + bestLength).join(":");
64574
+ return head2 + "::" + tail2;
64575
+ }
64576
+ function normalizeIPv6Address(input) {
64577
+ const compression = input.indexOf("::");
64578
+ if (compression !== -1 && input.indexOf("::", compression + 1) !== -1)
64579
+ return;
64580
+ const left = compression === -1 ? input.split(":") : input.slice(0, compression).split(":");
64581
+ const right = compression === -1 ? [] : input.slice(compression + 2).split(":");
64582
+ if (compression !== -1) {
64583
+ if (left.length === 1 && left[0] === "")
64584
+ left.length = 0;
64585
+ if (right.length === 1 && right[0] === "")
64586
+ right.length = 0;
64587
+ }
64588
+ const parts = left.concat(right);
64589
+ let hextetCount = 0;
64590
+ for (let i = 0;i < parts.length; i++) {
64591
+ const part = parts[i];
64592
+ if (part === "")
64593
+ return;
64594
+ if (part.indexOf(".") !== -1) {
64595
+ if (i !== parts.length - 1 || compression !== -1 && right.length === 0 || !isIPv4(part))
64596
+ return;
64597
+ hextetCount += 2;
64598
+ continue;
63501
64599
  }
64600
+ if (!isHextet(part))
64601
+ return;
64602
+ parts[i] = parseInt(part, 16).toString(16);
64603
+ hextetCount++;
63502
64604
  }
63503
- output.address = address.join("");
63504
- return output;
64605
+ if (compression === -1) {
64606
+ if (hextetCount !== 8)
64607
+ return;
64608
+ return compressIPv6ZeroRun(parts);
64609
+ }
64610
+ if (hextetCount >= 8)
64611
+ return;
64612
+ const expanded = parts.slice(0, left.length);
64613
+ for (let i = hextetCount;i < 8; i++)
64614
+ expanded.push("0");
64615
+ for (let i = left.length;i < parts.length; i++)
64616
+ expanded.push(parts[i]);
64617
+ return compressIPv6ZeroRun(expanded);
63505
64618
  }
63506
64619
  function normalizeIPv6(host) {
63507
- if (findToken(host, ":") < 2) {
63508
- return { host, isIPV6: false };
63509
- }
63510
- const ipv62 = getIPV6(host);
63511
- if (!ipv62.error) {
63512
- let newHost = ipv62.address;
63513
- let escapedHost = ipv62.address;
63514
- if (ipv62.zone) {
63515
- newHost += "%" + ipv62.zone;
63516
- escapedHost += "%25" + ipv62.zone;
63517
- }
63518
- return { host: newHost, isIPV6: true, escapedHost };
63519
- } else {
63520
- return { host, isIPV6: false };
63521
- }
64620
+ const bracketed = host[0] === "[" && host[host.length - 1] === "]";
64621
+ const hasBracket = host[0] === "[" || host[host.length - 1] === "]";
64622
+ if (hasBracket && !bracketed)
64623
+ return { host, isIPV6: false, error: true };
64624
+ let input = bracketed ? host.slice(1, -1) : host;
64625
+ if (bracketed && isIPvFuture(input)) {
64626
+ input = input.toLowerCase();
64627
+ return { host: `[${input}]`, escapedHost: input, isIPV6: false, isIPVFuture: true };
64628
+ }
64629
+ if (findToken(input, ":") < 2) {
64630
+ return { host, isIPV6: false, error: bracketed };
64631
+ }
64632
+ let zoneIdentifier = "";
64633
+ const zoneSeparator = input.indexOf("%");
64634
+ if (zoneSeparator !== -1) {
64635
+ const separatorLength = input.slice(zoneSeparator, zoneSeparator + 3).toLowerCase() === "%25" ? 3 : 1;
64636
+ zoneIdentifier = input.slice(zoneSeparator + separatorLength);
64637
+ if (!isZoneIdentifier(zoneIdentifier))
64638
+ return { host, isIPV6: false, error: true };
64639
+ input = input.slice(0, zoneSeparator);
64640
+ }
64641
+ const address = normalizeIPv6Address(input);
64642
+ if (address === undefined)
64643
+ return { host, isIPV6: false, error: true };
64644
+ return {
64645
+ host: address + (zoneIdentifier ? "%" + zoneIdentifier : ""),
64646
+ escapedHost: address + (zoneIdentifier ? "%25" + zoneIdentifier : ""),
64647
+ isIPV6: true
64648
+ };
63522
64649
  }
63523
64650
  function findToken(str2, token) {
63524
64651
  let ind = 0;
@@ -63606,8 +64733,8 @@ var require_utils = __commonJS((exports, module) => {
63606
64733
  var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
63607
64734
  var HOST_DELIM_RE = /[@/?#:]/g;
63608
64735
  var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
63609
- function reescapeHostDelimiters(host, isIP) {
63610
- const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
64736
+ function reescapeHostDelimiters(host, isIP2) {
64737
+ const re = isIP2 ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
63611
64738
  re.lastIndex = 0;
63612
64739
  return host.replace(re, (ch) => HOST_DELIMS[ch]);
63613
64740
  }
@@ -63638,7 +64765,8 @@ var require_utils = __commonJS((exports, module) => {
63638
64765
  function normalizePathEncoding(input) {
63639
64766
  let output = "";
63640
64767
  for (let i = 0;i < input.length; i++) {
63641
- if (input[i] === "%" && i + 2 < input.length) {
64768
+ const ch = input[i];
64769
+ if (ch === "%" && i + 2 < input.length) {
63642
64770
  const hex = input.slice(i + 1, i + 3);
63643
64771
  if (isHexPair(hex)) {
63644
64772
  const normalizedHex = hex.toUpperCase();
@@ -63652,10 +64780,152 @@ var require_utils = __commonJS((exports, module) => {
63652
64780
  continue;
63653
64781
  }
63654
64782
  }
63655
- if (isPathCharacter(input[i])) {
63656
- output += input[i];
64783
+ if (isPathCharacter(ch)) {
64784
+ output += ch;
63657
64785
  } else {
63658
- output += escape(input[i]);
64786
+ const code = input.charCodeAt(i);
64787
+ if (code < 128) {
64788
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
64789
+ } else if (code < 55296 || code > 57343) {
64790
+ output += percentEncodeNonAscii(code);
64791
+ } else if (code <= 56319 && i + 1 < input.length) {
64792
+ const low = input.charCodeAt(i + 1);
64793
+ if (low >= 56320 && low <= 57343) {
64794
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
64795
+ i++;
64796
+ } else {
64797
+ output += percentEncodeNonAscii(65533);
64798
+ }
64799
+ } else {
64800
+ output += percentEncodeNonAscii(65533);
64801
+ }
64802
+ }
64803
+ }
64804
+ return output;
64805
+ }
64806
+ function serializePathEncoding(input, pathNoScheme = false) {
64807
+ let output = "";
64808
+ let firstSegment = pathNoScheme && input[0] !== "/";
64809
+ for (let i = 0;i < input.length; i++) {
64810
+ const ch = input[i];
64811
+ if (ch === "%" && i + 2 < input.length) {
64812
+ const hex = input.slice(i + 1, i + 3);
64813
+ if (isHexPair(hex)) {
64814
+ output += "%" + hex.toUpperCase();
64815
+ i += 2;
64816
+ continue;
64817
+ }
64818
+ }
64819
+ if (ch === "/") {
64820
+ firstSegment = false;
64821
+ }
64822
+ if (isPathCharacter(ch) && (ch !== ":" || !firstSegment)) {
64823
+ output += ch;
64824
+ } else {
64825
+ const code = input.charCodeAt(i);
64826
+ if (code < 128) {
64827
+ output += BYTE_HEX[code];
64828
+ } else if (code < 55296 || code > 57343) {
64829
+ output += percentEncodeNonAscii(code);
64830
+ } else if (code <= 56319 && i + 1 < input.length) {
64831
+ const low = input.charCodeAt(i + 1);
64832
+ if (low >= 56320 && low <= 57343) {
64833
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
64834
+ i++;
64835
+ } else {
64836
+ output += percentEncodeNonAscii(65533);
64837
+ }
64838
+ } else {
64839
+ output += percentEncodeNonAscii(65533);
64840
+ }
64841
+ }
64842
+ }
64843
+ return output;
64844
+ }
64845
+ function encodeComponent(input, isAllowed) {
64846
+ let output = "";
64847
+ for (let i = 0;i < input.length; i++) {
64848
+ const ch = input[i];
64849
+ if (ch === "%" && i + 2 < input.length) {
64850
+ const hex = input.slice(i + 1, i + 3);
64851
+ if (isHexPair(hex)) {
64852
+ output += "%" + hex.toUpperCase();
64853
+ i += 2;
64854
+ continue;
64855
+ }
64856
+ }
64857
+ if (isAllowed(ch)) {
64858
+ output += ch;
64859
+ } else {
64860
+ const code = input.charCodeAt(i);
64861
+ if (code < 128) {
64862
+ output += BYTE_HEX[code];
64863
+ } else if (code < 55296 || code > 57343) {
64864
+ output += percentEncodeNonAscii(code);
64865
+ } else if (code <= 56319 && i + 1 < input.length) {
64866
+ const low = input.charCodeAt(i + 1);
64867
+ if (low >= 56320 && low <= 57343) {
64868
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
64869
+ i++;
64870
+ } else {
64871
+ output += percentEncodeNonAscii(65533);
64872
+ }
64873
+ } else {
64874
+ output += percentEncodeNonAscii(65533);
64875
+ }
64876
+ }
64877
+ }
64878
+ return output;
64879
+ }
64880
+ function encodeUserinfo(input) {
64881
+ return encodeComponent(input, isUserinfoCharacter);
64882
+ }
64883
+ function encodeQuery(input) {
64884
+ return encodeComponent(input, isQueryFragmentCharacter);
64885
+ }
64886
+ function encodeFragment(input) {
64887
+ return encodeComponent(input, isQueryFragmentCharacter);
64888
+ }
64889
+ function isEscapeSafe(cp) {
64890
+ return cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp === 42 || cp === 43 || cp === 45 || cp === 46 || cp === 47 || cp === 64 || cp === 95;
64891
+ }
64892
+ function normalizeQueryFragmentEncoding(input) {
64893
+ let output = "";
64894
+ for (let i = 0;i < input.length; i++) {
64895
+ const ch = input[i];
64896
+ if (ch === "%" && i + 2 < input.length) {
64897
+ const hex = input.slice(i + 1, i + 3);
64898
+ if (isHexPair(hex)) {
64899
+ const normalizedHex = hex.toUpperCase();
64900
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
64901
+ if (isUnreserved(decoded)) {
64902
+ output += decoded;
64903
+ } else {
64904
+ output += "%" + normalizedHex;
64905
+ }
64906
+ i += 2;
64907
+ continue;
64908
+ }
64909
+ }
64910
+ if (isQueryFragmentCharacter(ch)) {
64911
+ output += ch;
64912
+ } else {
64913
+ const code = input.charCodeAt(i);
64914
+ if (code < 128) {
64915
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
64916
+ } else if (code < 55296 || code > 57343) {
64917
+ output += percentEncodeNonAscii(code);
64918
+ } else if (code <= 56319 && i + 1 < input.length) {
64919
+ const low = input.charCodeAt(i + 1);
64920
+ if (low >= 56320 && low <= 57343) {
64921
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
64922
+ i++;
64923
+ } else {
64924
+ output += percentEncodeNonAscii(65533);
64925
+ }
64926
+ } else {
64927
+ output += percentEncodeNonAscii(65533);
64928
+ }
63659
64929
  }
63660
64930
  }
63661
64931
  return output;
@@ -63678,14 +64948,18 @@ var require_utils = __commonJS((exports, module) => {
63678
64948
  function recomposeAuthority(component) {
63679
64949
  const uriTokens = [];
63680
64950
  if (component.userinfo !== undefined) {
63681
- uriTokens.push(component.userinfo);
64951
+ uriTokens.push(encodeUserinfo(component.userinfo));
63682
64952
  uriTokens.push("@");
63683
64953
  }
63684
64954
  if (component.host !== undefined) {
63685
- let host = unescape(component.host);
64955
+ let host = component.host;
63686
64956
  if (!isIPv4(host)) {
63687
- const ipV6res = normalizeIPv6(host);
63688
- if (ipV6res.isIPV6 === true) {
64957
+ let ipV6res = normalizeIPv6(host);
64958
+ if (ipV6res.isIPV6 !== true && ipV6res.isIPVFuture !== true) {
64959
+ host = normalizePercentEncoding(host, true);
64960
+ ipV6res = normalizeIPv6(host);
64961
+ }
64962
+ if (ipV6res.isIPV6 === true || ipV6res.isIPVFuture === true) {
63689
64963
  host = `[${ipV6res.escapedHost}]`;
63690
64964
  } else {
63691
64965
  host = reescapeHostDelimiters(host, false);
@@ -63694,8 +64968,12 @@ var require_utils = __commonJS((exports, module) => {
63694
64968
  uriTokens.push(host);
63695
64969
  }
63696
64970
  if (typeof component.port === "number" || typeof component.port === "string") {
64971
+ const port = String(component.port);
64972
+ if (!isPort(port)) {
64973
+ throw new TypeError("URI port is malformed.");
64974
+ }
63697
64975
  uriTokens.push(":");
63698
- uriTokens.push(String(component.port));
64976
+ uriTokens.push(port);
63699
64977
  }
63700
64978
  return uriTokens.length ? uriTokens.join("") : undefined;
63701
64979
  }
@@ -63705,6 +64983,11 @@ var require_utils = __commonJS((exports, module) => {
63705
64983
  reescapeHostDelimiters,
63706
64984
  normalizePercentEncoding,
63707
64985
  normalizePathEncoding,
64986
+ serializePathEncoding,
64987
+ normalizeQueryFragmentEncoding,
64988
+ encodeUserinfo,
64989
+ encodeQuery,
64990
+ encodeFragment,
63708
64991
  escapePreservingEscapes,
63709
64992
  removeDotSegments,
63710
64993
  isIPv4,
@@ -63714,10 +64997,10 @@ var require_utils = __commonJS((exports, module) => {
63714
64997
  };
63715
64998
  });
63716
64999
 
63717
- // ../../node_modules/.bun/fast-uri@3.1.5/node_modules/fast-uri/lib/schemes.js
65000
+ // ../../node_modules/.bun/fast-uri@3.1.7/node_modules/fast-uri/lib/schemes.js
63718
65001
  var require_schemes = __commonJS((exports, module) => {
63719
65002
  var { isUUID } = require_utils();
63720
- var URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu;
65003
+ var URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
63721
65004
  var supportedSchemeNames = [
63722
65005
  "http",
63723
65006
  "https",
@@ -63772,9 +65055,10 @@ var require_schemes = __commonJS((exports, module) => {
63772
65055
  wsComponent.secure = undefined;
63773
65056
  }
63774
65057
  if (wsComponent.resourceName) {
63775
- const [path, query] = wsComponent.resourceName.split("?");
65058
+ const queryIndex = wsComponent.resourceName.indexOf("?");
65059
+ const path = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
63776
65060
  wsComponent.path = path && path !== "/" ? path : undefined;
63777
- wsComponent.query = query;
65061
+ wsComponent.query = queryIndex === -1 ? undefined : wsComponent.resourceName.slice(queryIndex + 1);
63778
65062
  wsComponent.resourceName = undefined;
63779
65063
  }
63780
65064
  wsComponent.fragment = undefined;
@@ -63786,7 +65070,7 @@ var require_schemes = __commonJS((exports, module) => {
63786
65070
  return urnComponent;
63787
65071
  }
63788
65072
  const matches2 = urnComponent.path.match(URN_REG);
63789
- if (matches2) {
65073
+ if (matches2 && matches2[0] === urnComponent.path) {
63790
65074
  const scheme = options.scheme || urnComponent.scheme || "urn";
63791
65075
  urnComponent.nid = matches2[1].toLowerCase();
63792
65076
  urnComponent.nss = matches2[2];
@@ -63888,10 +65172,19 @@ var require_schemes = __commonJS((exports, module) => {
63888
65172
  };
63889
65173
  });
63890
65174
 
63891
- // ../../node_modules/.bun/fast-uri@3.1.5/node_modules/fast-uri/index.js
65175
+ // ../../node_modules/.bun/fast-uri@3.1.7/node_modules/fast-uri/index.js
63892
65176
  var require_fast_uri = __commonJS((exports, module) => {
63893
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
65177
+ var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
63894
65178
  var { SCHEMES, getSchemeHandler } = require_schemes();
65179
+ var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
65180
+ var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
65181
+ function decodeValidScheme(scheme) {
65182
+ const decodedScheme = unescape(String(scheme));
65183
+ if (!VALID_SCHEME.test(decodedScheme)) {
65184
+ throw new TypeError(MALFORMED_SCHEME_ERROR);
65185
+ }
65186
+ return decodedScheme;
65187
+ }
63895
65188
  function normalize3(uri, options) {
63896
65189
  if (typeof uri === "string") {
63897
65190
  uri = normalizeString(uri, options);
@@ -63902,12 +65195,34 @@ var require_fast_uri = __commonJS((exports, module) => {
63902
65195
  }
63903
65196
  function resolve2(baseURI, relativeURI, options) {
63904
65197
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
63905
- const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
63906
- const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
63907
- if (baseMalformed || relativeMalformed) {
65198
+ const {
65199
+ parsed: baseParsed,
65200
+ malformedAuthorityOrPort: baseMalformed,
65201
+ malformedPercentEncoding: baseMalformedPercentEncoding,
65202
+ malformedSchemeSpecific: baseMalformedSchemeSpecific,
65203
+ malformedHost: baseMalformedHost,
65204
+ malformedScheme: baseMalformedScheme
65205
+ } = parseWithStatus(baseURI, schemelessOptions);
65206
+ const {
65207
+ parsed: relativeParsed,
65208
+ malformedAuthorityOrPort: relativeMalformed,
65209
+ malformedPercentEncoding: relativeMalformedPercentEncoding,
65210
+ malformedSchemeSpecific: relativeMalformedSchemeSpecific,
65211
+ malformedHost: relativeMalformedHost,
65212
+ malformedScheme: relativeMalformedScheme
65213
+ } = parseWithStatus(relativeURI, schemelessOptions);
65214
+ if (baseMalformed || relativeMalformed || baseMalformedPercentEncoding || relativeMalformedPercentEncoding || baseMalformedSchemeSpecific || relativeMalformedSchemeSpecific || baseMalformedHost || relativeMalformedHost || baseMalformedScheme || relativeMalformedScheme) {
63908
65215
  throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
63909
65216
  }
63910
65217
  const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
65218
+ const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
65219
+ const resolvedHost = resolved.host;
65220
+ const resolvedHostIsIP = resolvedHost !== undefined && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
65221
+ canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
65222
+ const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !/\P{ASCII}/u.test(resolvedHost);
65223
+ if (resolved.error && !encodedASCIIHost) {
65224
+ throw new Error(resolved.error);
65225
+ }
63911
65226
  schemelessOptions.skipEscape = true;
63912
65227
  return serialize(resolved, schemelessOptions);
63913
65228
  }
@@ -63967,7 +65282,7 @@ var require_fast_uri = __commonJS((exports, module) => {
63967
65282
  function equal(uriA, uriB, options) {
63968
65283
  const normalizedA = normalizeComparableURI(uriA, options);
63969
65284
  const normalizedB = normalizeComparableURI(uriB, options);
63970
- return normalizedA !== undefined && normalizedB !== undefined && normalizedA.toLowerCase() === normalizedB.toLowerCase();
65285
+ return normalizedA !== undefined && normalizedB !== undefined && normalizedA === normalizedB;
63971
65286
  }
63972
65287
  function serialize(cmpts, opts) {
63973
65288
  const component = {
@@ -63988,20 +65303,23 @@ var require_fast_uri = __commonJS((exports, module) => {
63988
65303
  };
63989
65304
  const options = Object.assign({}, opts);
63990
65305
  const uriTokens = [];
65306
+ if (component.scheme) {
65307
+ component.scheme = decodeValidScheme(component.scheme);
65308
+ }
63991
65309
  const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
63992
65310
  if (schemeHandler && schemeHandler.serialize)
63993
65311
  schemeHandler.serialize(component, options);
65312
+ const hasAuthority = component.userinfo !== undefined || component.host !== undefined || component.port !== undefined;
65313
+ const pathNoScheme = !options.skipEscape && component.scheme === undefined && !hasAuthority;
63994
65314
  if (component.path !== undefined) {
63995
65315
  if (!options.skipEscape) {
63996
- component.path = escapePreservingEscapes(component.path);
63997
- if (component.scheme !== undefined) {
63998
- component.path = component.path.split("%3A").join(":");
63999
- }
65316
+ component.path = serializePathEncoding(component.path, pathNoScheme);
64000
65317
  } else {
64001
65318
  component.path = normalizePercentEncoding(component.path);
64002
65319
  }
64003
65320
  }
64004
65321
  if (options.reference !== "suffix" && component.scheme) {
65322
+ component.scheme = decodeValidScheme(component.scheme);
64005
65323
  uriTokens.push(component.scheme, ":");
64006
65324
  }
64007
65325
  const authority = recomposeAuthority(component);
@@ -64019,16 +65337,19 @@ var require_fast_uri = __commonJS((exports, module) => {
64019
65337
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
64020
65338
  s = removeDotSegments(s);
64021
65339
  }
65340
+ if (pathNoScheme) {
65341
+ s = serializePathEncoding(s, true);
65342
+ }
64022
65343
  if (authority === undefined && s[0] === "/" && s[1] === "/") {
64023
65344
  s = "/%2F" + s.slice(2);
64024
65345
  }
64025
65346
  uriTokens.push(s);
64026
65347
  }
64027
65348
  if (component.query !== undefined) {
64028
- uriTokens.push("?", component.query);
65349
+ uriTokens.push("?", encodeQuery(component.query));
64029
65350
  }
64030
65351
  if (component.fragment !== undefined) {
64031
- uriTokens.push("#", component.fragment);
65352
+ uriTokens.push("#", encodeFragment(component.fragment));
64032
65353
  }
64033
65354
  return uriTokens.join("");
64034
65355
  }
@@ -64044,6 +65365,36 @@ var require_fast_uri = __commonJS((exports, module) => {
64044
65365
  }
64045
65366
  return;
64046
65367
  }
65368
+ function hasMalformedPercentEncoding(component) {
65369
+ if (component === undefined)
65370
+ return false;
65371
+ let percent = component.indexOf("%");
65372
+ while (percent !== -1) {
65373
+ if (percent + 2 >= component.length || !/^[\da-f]{2}$/iu.test(component.slice(percent + 1, percent + 3))) {
65374
+ return true;
65375
+ }
65376
+ percent = component.indexOf("%", percent + 3);
65377
+ }
65378
+ return false;
65379
+ }
65380
+ function isIPLiteral(host) {
65381
+ return host[0] === "[" && host[host.length - 1] === "]";
65382
+ }
65383
+ function hasMalformedComponentPercentEncoding(matches2) {
65384
+ const host = matches2[4];
65385
+ return hasMalformedPercentEncoding(matches2[3]) || host !== undefined && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches2[6]) || hasMalformedPercentEncoding(matches2[7]) || hasMalformedPercentEncoding(matches2[8]);
65386
+ }
65387
+ function canonicalizeHost(parsed, options, schemeHandler, isIP2) {
65388
+ if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP2 === false && nonSimpleDomain(parsed.host)) {
65389
+ try {
65390
+ parsed.host = new URL("http://" + parsed.host).hostname;
65391
+ } catch (e) {
65392
+ parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
65393
+ return true;
65394
+ }
65395
+ }
65396
+ return false;
65397
+ }
64047
65398
  function parseWithStatus(uri, opts) {
64048
65399
  const options = Object.assign({}, opts);
64049
65400
  const parsed = {
@@ -64056,7 +65407,12 @@ var require_fast_uri = __commonJS((exports, module) => {
64056
65407
  fragment: undefined
64057
65408
  };
64058
65409
  let malformedAuthorityOrPort = false;
64059
- let isIP = false;
65410
+ let malformedPercentEncoding = false;
65411
+ let malformedSchemeSpecific = false;
65412
+ let malformedHost = false;
65413
+ let malformedIPLiteral = false;
65414
+ let malformedScheme = false;
65415
+ let isIP2 = false;
64060
65416
  if (options.reference === "suffix") {
64061
65417
  if (options.scheme) {
64062
65418
  uri = options.scheme + ":" + uri;
@@ -64092,6 +65448,19 @@ var require_fast_uri = __commonJS((exports, module) => {
64092
65448
  parsed.path = matches2[6] || "";
64093
65449
  parsed.query = matches2[7];
64094
65450
  parsed.fragment = matches2[8];
65451
+ if (parsed.scheme !== undefined) {
65452
+ const decodedScheme = unescape(parsed.scheme);
65453
+ if (VALID_SCHEME.test(decodedScheme)) {
65454
+ parsed.scheme = decodedScheme.toLowerCase();
65455
+ } else {
65456
+ parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
65457
+ malformedScheme = true;
65458
+ }
65459
+ }
65460
+ malformedPercentEncoding = hasMalformedComponentPercentEncoding(matches2);
65461
+ if (malformedPercentEncoding) {
65462
+ parsed.error = parsed.error || "URI contains malformed percent-encoding.";
65463
+ }
64095
65464
  if (isNaN(parsed.port)) {
64096
65465
  parsed.port = matches2[5];
64097
65466
  }
@@ -64103,11 +65472,18 @@ var require_fast_uri = __commonJS((exports, module) => {
64103
65472
  if (parsed.host) {
64104
65473
  const ipv4result = isIPv4(parsed.host);
64105
65474
  if (ipv4result === false) {
65475
+ const bracketedIPLiteral = isIPLiteral(parsed.host);
65476
+ const hasIPLiteralBracket = parsed.host.indexOf("[") !== -1 || parsed.host.indexOf("]") !== -1;
64106
65477
  const ipv6result = normalizeIPv6(parsed.host);
64107
- parsed.host = ipv6result.host.toLowerCase();
64108
- isIP = ipv6result.isIPV6;
65478
+ isIP2 = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
65479
+ malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
65480
+ parsed.host = isIP2 ? ipv6result.host : ipv6result.host.toLowerCase();
65481
+ if (malformedIPLiteral) {
65482
+ parsed.error = parsed.error || "URI host is malformed.";
65483
+ malformedAuthorityOrPort = true;
65484
+ }
64109
65485
  } else {
64110
- isIP = true;
65486
+ isIP2 = true;
64111
65487
  }
64112
65488
  }
64113
65489
  if (parsed.scheme === undefined && parsed.userinfo === undefined && parsed.host === undefined && parsed.port === undefined && parsed.query === undefined && !parsed.path) {
@@ -64123,42 +65499,36 @@ var require_fast_uri = __commonJS((exports, module) => {
64123
65499
  parsed.error = parsed.error || "URI is not a " + options.reference + " reference.";
64124
65500
  }
64125
65501
  const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
64126
- if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
64127
- if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
64128
- try {
64129
- parsed.host = new URL("http://" + parsed.host).hostname;
64130
- } catch (e) {
64131
- parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
64132
- }
64133
- }
65502
+ if (!malformedIPLiteral) {
65503
+ malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP2);
64134
65504
  }
64135
65505
  if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
64136
65506
  if (uri.indexOf("%") !== -1) {
64137
- if (parsed.scheme !== undefined) {
64138
- parsed.scheme = unescape(parsed.scheme);
64139
- }
64140
- if (parsed.host !== undefined) {
64141
- parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
65507
+ if (parsed.host !== undefined && !malformedIPLiteral) {
65508
+ const host = isIP2 ? parsed.host : normalizePercentEncoding(parsed.host, true);
65509
+ parsed.host = reescapeHostDelimiters(host, isIP2);
64142
65510
  }
64143
65511
  }
64144
65512
  if (parsed.path) {
64145
65513
  parsed.path = normalizePathEncoding(parsed.path);
64146
65514
  }
65515
+ if (parsed.query) {
65516
+ parsed.query = normalizeQueryFragmentEncoding(parsed.query);
65517
+ }
64147
65518
  if (parsed.fragment) {
64148
- try {
64149
- parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
64150
- } catch {
64151
- parsed.error = parsed.error || "URI malformed";
64152
- }
65519
+ parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment);
64153
65520
  }
64154
65521
  }
64155
65522
  if (schemeHandler && schemeHandler.parse) {
64156
65523
  schemeHandler.parse(parsed, options);
65524
+ if (schemeHandler === SCHEMES.urn && parsed.nid === undefined) {
65525
+ malformedSchemeSpecific = true;
65526
+ }
64157
65527
  }
64158
65528
  } else {
64159
65529
  parsed.error = parsed.error || "URI can not be parsed.";
64160
65530
  }
64161
- return { parsed, malformedAuthorityOrPort };
65531
+ return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
64162
65532
  }
64163
65533
  function parse6(uri, opts) {
64164
65534
  return parseWithStatus(uri, opts).parsed;
@@ -64167,20 +65537,28 @@ var require_fast_uri = __commonJS((exports, module) => {
64167
65537
  return normalizeStringWithStatus(uri, opts).normalized;
64168
65538
  }
64169
65539
  function normalizeStringWithStatus(uri, opts) {
64170
- const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
65540
+ const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts);
64171
65541
  return {
64172
- normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
64173
- malformedAuthorityOrPort
65542
+ normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
65543
+ malformedAuthorityOrPort,
65544
+ malformedPercentEncoding,
65545
+ malformedSchemeSpecific,
65546
+ malformedHost,
65547
+ malformedScheme
64174
65548
  };
64175
65549
  }
64176
65550
  function normalizeComparableURI(uri, opts) {
64177
- if (typeof uri === "string") {
64178
- const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
64179
- return malformedAuthorityOrPort ? undefined : normalized;
65551
+ if (typeof uri !== "string" && typeof uri !== "object") {
65552
+ return;
64180
65553
  }
64181
- if (typeof uri === "object") {
64182
- return serialize(uri, opts);
65554
+ let value;
65555
+ try {
65556
+ value = typeof uri === "string" ? uri : serialize(uri, opts);
65557
+ } catch {
65558
+ return;
64183
65559
  }
65560
+ const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts);
65561
+ return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? undefined : normalized;
64184
65562
  }
64185
65563
  var fastUri = {
64186
65564
  SCHEMES,
@@ -66986,7 +68364,7 @@ var require_dist = __commonJS((exports, module) => {
66986
68364
  exports.default = formatsPlugin;
66987
68365
  });
66988
68366
 
66989
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
68367
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
66990
68368
  function createDefaultAjvInstance() {
66991
68369
  const ajv = new import_ajv.default({
66992
68370
  strict: false,
@@ -67029,7 +68407,7 @@ var init_ajv_provider = __esm(() => {
67029
68407
  import_ajv_formats = __toESM(require_dist(), 1);
67030
68408
  });
67031
68409
 
67032
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
68410
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
67033
68411
  class ExperimentalServerTasks {
67034
68412
  constructor(_server) {
67035
68413
  this._server = _server;
@@ -67110,7 +68488,7 @@ var init_server = __esm(() => {
67110
68488
  init_types2();
67111
68489
  });
67112
68490
 
67113
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
68491
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
67114
68492
  function assertToolsCallTaskCapability(requests, method2, entityName) {
67115
68493
  if (!requests) {
67116
68494
  throw new Error(`${entityName} does not support task creation (required for ${method2})`);
@@ -67145,7 +68523,7 @@ function assertClientRequestTaskCapability(requests, method2, entityName) {
67145
68523
  }
67146
68524
  }
67147
68525
 
67148
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
68526
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
67149
68527
  var Server;
67150
68528
  var init_server2 = __esm(() => {
67151
68529
  init_protocol();
@@ -67477,7 +68855,7 @@ var init_server2 = __esm(() => {
67477
68855
  };
67478
68856
  });
67479
68857
 
67480
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
68858
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
67481
68859
  function isCompletable(schema) {
67482
68860
  return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
67483
68861
  }
@@ -67493,7 +68871,7 @@ var init_completable = __esm(() => {
67493
68871
  })(McpZodTypeKind || (McpZodTypeKind = {}));
67494
68872
  });
67495
68873
 
67496
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/uriTemplate.js
68874
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/uriTemplate.js
67497
68875
  class UriTemplate {
67498
68876
  static isTemplate(str2) {
67499
68877
  return /\{[^}\s]+\}/.test(str2);
@@ -67707,7 +69085,7 @@ class UriTemplate {
67707
69085
  }
67708
69086
  var MAX_TEMPLATE_LENGTH = 1e6, MAX_VARIABLE_LENGTH = 1e6, MAX_TEMPLATE_EXPRESSIONS = 1e4, MAX_REGEX_LENGTH = 1e6;
67709
69087
 
67710
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
69088
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
67711
69089
  function validateToolName(name) {
67712
69090
  const warnings = [];
67713
69091
  if (name.length === 0) {
@@ -67768,7 +69146,7 @@ var init_toolNameValidation = __esm(() => {
67768
69146
  TOOL_NAME_REGEX = /^[A-Za-z0-9._-]{1,128}$/;
67769
69147
  });
67770
69148
 
67771
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
69149
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
67772
69150
  class ExperimentalMcpServerTasks {
67773
69151
  constructor(_mcpServer) {
67774
69152
  this._mcpServer = _mcpServer;
@@ -67783,7 +69161,7 @@ class ExperimentalMcpServerTasks {
67783
69161
  }
67784
69162
  }
67785
69163
 
67786
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
69164
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
67787
69165
  class McpServer {
67788
69166
  constructor(serverInfo, options) {
67789
69167
  this._registeredResources = {};
@@ -69597,7 +70975,7 @@ function resolveRunRouting(skill, apiKey, apiUrl) {
69597
70975
  return {
69598
70976
  route: "error",
69599
70977
  code: "REMOTE_REQUIRES_ORIGIN",
69600
- error: `${skill.name} is a server-owned skill. Point the CLI at a Skills API: ` + `skills setup --api-url <url> (or export SKILLS_API_URL)`
70978
+ error: `${skill.name} is a server-owned skill. Point the CLI at a Skills API: ` + `skills setup --api-url <url> (or export HASNA_SKILLS_API_URL)`
69601
70979
  };
69602
70980
  }
69603
70981
  if (!apiKey) {
@@ -69609,17 +70987,31 @@ function resolveRunRouting(skill, apiKey, apiUrl) {
69609
70987
  }
69610
70988
  return { route: "remote", apiKey };
69611
70989
  }
69612
- function resolveConfiguredRunRouting(skill) {
69613
- return resolveRunRouting(skill, getApiKey(), resolveApiUrl());
70990
+ async function resolveConfiguredRunRouting(skill, env3 = process.env) {
70991
+ let fleet;
70992
+ let apiKey;
70993
+ try {
70994
+ fleet = resolveSkillsFleet(env3);
70995
+ apiKey = fleet.mode === "hosted" ? await resolveSkillsApiKey(env3) : null;
70996
+ } catch (error2) {
70997
+ const isMissingCredential = (error2 instanceof SkillsFleetCredentialError || error2?.name === "SkillsFleetCredentialError") && error2.code === "MISSING_API_CREDENTIAL";
70998
+ if (!isMissingCredential)
70999
+ throw error2;
71000
+ return {
71001
+ route: "error",
71002
+ code: "REMOTE_REQUIRES_CREDENTIAL",
71003
+ error: `${skill.name} is a server-owned skill. ${error2.message}`
71004
+ };
71005
+ }
71006
+ return resolveRunRouting(skill, apiKey, fleet.apiOrigin ?? undefined);
69614
71007
  }
69615
71008
  var init_run_routing = __esm(() => {
69616
- init_api_url();
69617
- init_auth_store();
71009
+ init_fleet_credentials();
69618
71010
  });
69619
71011
 
69620
71012
  // src/mcp/operation-tools.ts
69621
- import { existsSync as existsSync24, readdirSync as readdirSync14, statSync as statSync14 } from "fs";
69622
- import { join as join25 } from "path";
71013
+ import { existsSync as existsSync24, readdirSync as readdirSync14, statSync as statSync15 } from "fs";
71014
+ import { join as join26 } from "path";
69623
71015
  function registerOperationTools(server) {
69624
71016
  server.registerTool("scaffold_skill", {
69625
71017
  title: "Scaffold Skill",
@@ -69840,7 +71232,7 @@ function registerOperationTools(server) {
69840
71232
  return mcpError("INVALID_BLOG_ARTICLE_OPTIONS", validation.errors.join(" "));
69841
71233
  }
69842
71234
  }
69843
- const routing = resolveConfiguredRunRouting(skill);
71235
+ const routing = await resolveConfiguredRunRouting(skill);
69844
71236
  const runContext = createSkillRun({
69845
71237
  skill: skillName,
69846
71238
  args: runArgs,
@@ -69919,10 +71311,10 @@ function registerOperationTools(server) {
69919
71311
  detail: exports_external.boolean().optional()
69920
71312
  }
69921
71313
  }, async ({ run_id, detail }) => {
69922
- const { getApiKey: getApiKey2 } = await Promise.resolve().then(() => (init_auth_store(), exports_auth_store));
69923
- const apiKey = getApiKey2();
71314
+ const { skillsCredentialOrReason: skillsCredentialOrReason2 } = await Promise.resolve().then(() => (init_fleet_credentials(), exports_fleet_credentials));
71315
+ const { apiKey, reason } = await skillsCredentialOrReason2();
69924
71316
  if (!apiKey) {
69925
- return mcpError("AUTH_REQUIRED", "Remote run status requires API access. Run: skills auth login", ["skills auth login"]);
71317
+ return mcpError("AUTH_REQUIRED", reason ?? "Remote run status requires API access. Run: skills auth login", ["skills auth login"]);
69926
71318
  }
69927
71319
  const localRun = findSkillRun(run_id);
69928
71320
  const remoteRunId = localRun?.remoteRunId || run_id;
@@ -70021,8 +71413,8 @@ function registerOperationTools(server) {
70021
71413
  if (exists) {
70022
71414
  try {
70023
71415
  skillCount = readdirSync14(agentSkillsPath).filter((f) => {
70024
- const full = join25(agentSkillsPath, f);
70025
- return !f.startsWith(".") && statSync14(full).isDirectory();
71416
+ const full = join26(agentSkillsPath, f);
71417
+ return !f.startsWith(".") && statSync15(full).isDirectory();
70026
71418
  }).length;
70027
71419
  } catch {}
70028
71420
  }
@@ -70079,10 +71471,10 @@ var init_operation_tools = __esm(() => {
70079
71471
 
70080
71472
  // src/lib/feedback.ts
70081
71473
  import { appendFileSync as appendFileSync2, existsSync as existsSync25, mkdirSync as mkdirSync11 } from "fs";
70082
- import { dirname as dirname8, join as join26 } from "path";
71474
+ import { dirname as dirname8, join as join27 } from "path";
70083
71475
  import { Database } from "bun:sqlite";
70084
71476
  function getFeedbackDbPath() {
70085
- return join26(getDataDir(), "skills.db");
71477
+ return join27(getDataDir(), "skills.db");
70086
71478
  }
70087
71479
  function getFeedbackDb() {
70088
71480
  const dbPath = getFeedbackDbPath();
@@ -70114,7 +71506,7 @@ function saveFeedback(input) {
70114
71506
  throw new Error("Feedback message is required");
70115
71507
  const category = input.category ?? "general";
70116
71508
  if (isApiMode()) {
70117
- const path = join26(getDataDir(), "feedback.jsonl");
71509
+ const path = join27(getDataDir(), "feedback.jsonl");
70118
71510
  const dir = dirname8(path);
70119
71511
  if (!existsSync25(dir))
70120
71512
  mkdirSync11(dir, { recursive: true });
@@ -70131,12 +71523,10 @@ function saveFeedback(input) {
70131
71523
  return { saved: true, category, path: getFeedbackDbPath() };
70132
71524
  }
70133
71525
  function isApiMode(env3 = process.env) {
70134
- if (env3.HASNA_SKILLS_API_URL?.trim())
70135
- return true;
70136
71526
  try {
70137
- return Boolean(resolveApiUrl(undefined, env3));
71527
+ return Boolean(resolveApiUrl(env3));
70138
71528
  } catch {
70139
- return Boolean(env3.SKILLS_API_URL?.trim());
71529
+ return true;
70140
71530
  }
70141
71531
  }
70142
71532
  var init_feedback = __esm(() => {
@@ -70280,23 +71670,23 @@ var init_resource_meta_tools = __esm(() => {
70280
71670
  });
70281
71671
 
70282
71672
  // src/lib/scheduler.ts
70283
- import { existsSync as existsSync26, readFileSync as readFileSync21, writeFileSync as writeFileSync13, mkdirSync as mkdirSync12 } from "fs";
70284
- import { join as join27 } from "path";
71673
+ import { existsSync as existsSync26, readFileSync as readFileSync22, writeFileSync as writeFileSync13, mkdirSync as mkdirSync12 } from "fs";
71674
+ import { join as join28 } from "path";
70285
71675
  function getSchedulesPath(targetDir = process.cwd()) {
70286
- return join27(targetDir, ".skills", "schedules.json");
71676
+ return join28(targetDir, ".skills", "schedules.json");
70287
71677
  }
70288
71678
  function loadSchedules(targetDir = process.cwd()) {
70289
71679
  const path = getSchedulesPath(targetDir);
70290
71680
  if (existsSync26(path)) {
70291
71681
  try {
70292
- return JSON.parse(readFileSync21(path, "utf-8"));
71682
+ return JSON.parse(readFileSync22(path, "utf-8"));
70293
71683
  } catch {}
70294
71684
  }
70295
71685
  return { version: 1, schedules: [] };
70296
71686
  }
70297
71687
  function saveSchedules(data, targetDir = process.cwd()) {
70298
71688
  const path = getSchedulesPath(targetDir);
70299
- const dir = join27(targetDir, ".skills");
71689
+ const dir = join28(targetDir, ".skills");
70300
71690
  if (!existsSync26(dir))
70301
71691
  mkdirSync12(dir, { recursive: true });
70302
71692
  writeFileSync13(path, JSON.stringify(data, null, 2));
@@ -70599,12 +71989,12 @@ import { createHash as createHash5, createHmac as createHmac3 } from "crypto";
70599
71989
  import {
70600
71990
  existsSync as existsSync27,
70601
71991
  mkdirSync as mkdirSync13,
70602
- readFileSync as readFileSync22,
71992
+ readFileSync as readFileSync23,
70603
71993
  readdirSync as readdirSync15,
70604
- statSync as statSync15,
71994
+ statSync as statSync16,
70605
71995
  writeFileSync as writeFileSync14
70606
71996
  } from "fs";
70607
- import { dirname as dirname9, join as join28, normalize as normalize3, relative as relative4, sep as sep2 } from "path";
71997
+ import { dirname as dirname9, join as join29, normalize as normalize3, relative as relative4, sep as sep2 } from "path";
70608
71998
  function resolveSkillsNativeStorageConfig(env3 = process.env) {
70609
71999
  assertNoRetiredModeEnvVars(env3, {
70610
72000
  app: SKILLS_ENV_NAMESPACE,
@@ -70645,7 +72035,7 @@ function getSkillsNativeStorageStatus(options = {}) {
70645
72035
  local: {
70646
72036
  dataDir: getDataDir(),
70647
72037
  projectStateDir: getProjectStateDir(targetDir),
70648
- feedbackDbPath: join28(getDataDir(), "skills.db")
72038
+ feedbackDbPath: join29(getDataDir(), "skills.db")
70649
72039
  },
70650
72040
  remote: {
70651
72041
  databaseConfigured: Boolean(config2.databaseUrl),
@@ -70667,7 +72057,7 @@ function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
70667
72057
  const files = [];
70668
72058
  if (existsSync27(projectStateDir)) {
70669
72059
  for (const filePath of walkFiles2(projectStateDir)) {
70670
- const bytes = readFileSync22(filePath);
72060
+ const bytes = readFileSync23(filePath);
70671
72061
  const relativePath = toPosix(relative4(targetDir, filePath));
70672
72062
  files.push({
70673
72063
  path: relativePath,
@@ -70726,8 +72116,8 @@ function parsePositiveInteger(value) {
70726
72116
  function walkFiles2(dir) {
70727
72117
  const files = [];
70728
72118
  for (const entry of readdirSync15(dir)) {
70729
- const full = join28(dir, entry);
70730
- const stats = statSync15(full);
72119
+ const full = join29(dir, entry);
72120
+ const stats = statSync16(full);
70731
72121
  if (stats.isDirectory())
70732
72122
  files.push(...walkFiles2(full));
70733
72123
  else
@@ -71203,26 +72593,26 @@ var require_content_type = __commonJS((exports) => {
71203
72593
  }
71204
72594
  });
71205
72595
 
71206
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/mediaType.js
72596
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/mediaType.js
71207
72597
  var import_content_type;
71208
72598
  var init_mediaType = __esm(() => {
71209
72599
  import_content_type = __toESM(require_content_type(), 1);
71210
72600
  });
71211
72601
 
71212
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/sseKeepAlive.js
72602
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/sseKeepAlive.js
71213
72603
  var MAX_TIMER_DELAY_MS;
71214
72604
  var init_sseKeepAlive = __esm(() => {
71215
72605
  MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
71216
72606
  });
71217
72607
 
71218
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js
72608
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js
71219
72609
  var init_webStandardStreamableHttp = __esm(() => {
71220
72610
  init_mediaType();
71221
72611
  init_sseKeepAlive();
71222
72612
  init_types2();
71223
72613
  });
71224
72614
 
71225
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/streamableHttp.js
72615
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/streamableHttp.js
71226
72616
  var init_streamableHttp = __esm(() => {
71227
72617
  init_dist();
71228
72618
  init_webStandardStreamableHttp();
@@ -71275,9 +72665,9 @@ var init_mcp2 = __esm(() => {
71275
72665
  });
71276
72666
 
71277
72667
  // src/cli/commands/runtime-mcp.ts
71278
- import { existsSync as existsSync28, mkdirSync as mkdirSync14, readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "fs";
71279
- import { homedir as homedir9 } from "os";
71280
- import { dirname as dirname10, join as join29 } from "path";
72668
+ import { existsSync as existsSync28, mkdirSync as mkdirSync14, readFileSync as readFileSync24, writeFileSync as writeFileSync15 } from "fs";
72669
+ import { homedir as homedir7 } from "os";
72670
+ import { dirname as dirname10, join as join30 } from "path";
71281
72671
  async function handleMcp(options) {
71282
72672
  if (options.register) {
71283
72673
  let agents;
@@ -71320,24 +72710,24 @@ async function registerMcpForAgent(agent, command) {
71320
72710
  case "codex":
71321
72711
  return registerCodexMcp(command);
71322
72712
  case "gemini":
71323
- return registerJsonMcpServer(agent, join29(homedir9(), ".gemini", "settings.json"), "mcpServers", {
72713
+ return registerJsonMcpServer(agent, join30(homedir7(), ".gemini", "settings.json"), "mcpServers", {
71324
72714
  command,
71325
72715
  args: []
71326
72716
  });
71327
72717
  case "pi":
71328
- return registerJsonMcpServer(agent, join29(homedir9(), ".pi", "agent", "mcp.json"), "mcpServers", {
72718
+ return registerJsonMcpServer(agent, join30(homedir7(), ".pi", "agent", "mcp.json"), "mcpServers", {
71329
72719
  command,
71330
72720
  args: []
71331
72721
  });
71332
72722
  case "opencode":
71333
72723
  return registerOpenCodeMcp(command);
71334
72724
  case "cursor":
71335
- return registerJsonMcpServer(agent, join29(homedir9(), ".cursor", "mcp.json"), "mcpServers", {
72725
+ return registerJsonMcpServer(agent, join30(homedir7(), ".cursor", "mcp.json"), "mcpServers", {
71336
72726
  command,
71337
72727
  args: []
71338
72728
  });
71339
72729
  case "windsurf":
71340
- return registerJsonMcpServer(agent, join29(homedir9(), ".windsurf", "mcp.json"), "mcpServers", {
72730
+ return registerJsonMcpServer(agent, join30(homedir7(), ".windsurf", "mcp.json"), "mcpServers", {
71341
72731
  command,
71342
72732
  args: []
71343
72733
  });
@@ -71360,7 +72750,7 @@ async function registerClaudeMcp(command) {
71360
72750
  if (exitCode === 0) {
71361
72751
  return { agent: "claude", success: true, command: cliCommand };
71362
72752
  }
71363
- const fallback = registerJsonMcpServer("claude", join29(homedir9(), ".claude", ".mcp.json"), "mcpServers", {
72753
+ const fallback = registerJsonMcpServer("claude", join30(homedir7(), ".claude", ".mcp.json"), "mcpServers", {
71364
72754
  command,
71365
72755
  args: []
71366
72756
  });
@@ -71370,7 +72760,7 @@ async function registerClaudeMcp(command) {
71370
72760
  error: fallback.success ? undefined : `claude exited with ${exitCode}: ${(stderr || stdout).trim()}`
71371
72761
  };
71372
72762
  } catch (err) {
71373
- const fallback = registerJsonMcpServer("claude", join29(homedir9(), ".claude", ".mcp.json"), "mcpServers", {
72763
+ const fallback = registerJsonMcpServer("claude", join30(homedir7(), ".claude", ".mcp.json"), "mcpServers", {
71374
72764
  command,
71375
72765
  args: []
71376
72766
  });
@@ -71382,11 +72772,11 @@ async function registerClaudeMcp(command) {
71382
72772
  }
71383
72773
  }
71384
72774
  function registerCodexMcp(command) {
71385
- const path = join29(homedir9(), ".codex", "config.toml");
72775
+ const path = join30(homedir7(), ".codex", "config.toml");
71386
72776
  const config2 = `[mcp_servers.${MCP_SERVER_NAME}]
71387
72777
  command = ${JSON.stringify(command)}`;
71388
72778
  try {
71389
- const current = existsSync28(path) ? readFileSync23(path, "utf-8") : "";
72779
+ const current = existsSync28(path) ? readFileSync24(path, "utf-8") : "";
71390
72780
  writeTextFile(path, upsertTomlSection(current, `[mcp_servers.${MCP_SERVER_NAME}]`, `command = ${JSON.stringify(command)}`));
71391
72781
  return { agent: "codex", success: true, path, config: config2 };
71392
72782
  } catch (err) {
@@ -71394,7 +72784,7 @@ command = ${JSON.stringify(command)}`;
71394
72784
  }
71395
72785
  }
71396
72786
  function registerOpenCodeMcp(command) {
71397
- const path = join29(homedir9(), ".config", "opencode", "opencode.json");
72787
+ const path = join30(homedir7(), ".config", "opencode", "opencode.json");
71398
72788
  const config2 = JSON.stringify({
71399
72789
  $schema: "https://opencode.ai/config.json",
71400
72790
  mcp: {
@@ -71438,7 +72828,7 @@ function registerJsonMcpServer(agent, path, containerKey, server2) {
71438
72828
  function readJsonObject2(path) {
71439
72829
  if (!existsSync28(path))
71440
72830
  return {};
71441
- const raw = readFileSync23(path, "utf-8").trim();
72831
+ const raw = readFileSync24(path, "utf-8").trim();
71442
72832
  if (!raw)
71443
72833
  return {};
71444
72834
  const parsed = JSON.parse(raw);
@@ -71482,7 +72872,7 @@ function findCommandOnPath(command) {
71482
72872
  for (const dir of pathValue.split(":")) {
71483
72873
  if (!dir)
71484
72874
  continue;
71485
- const candidate = join29(dir, command);
72875
+ const candidate = join30(dir, command);
71486
72876
  if (existsSync28(candidate))
71487
72877
  return candidate;
71488
72878
  }
@@ -71503,7 +72893,7 @@ __export(exports_runtime, {
71503
72893
  registerRuntime: () => registerRuntime
71504
72894
  });
71505
72895
  import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync16 } from "fs";
71506
- import { dirname as dirname11, join as join30 } from "path";
72896
+ import { dirname as dirname11, join as join31 } from "path";
71507
72897
  import { createInterface } from "readline";
71508
72898
  function registerRuntime(parent) {
71509
72899
  parent.command("run").argument("<skill>", "Skill name").argument("[args...]", "Arguments to pass to the skill").allowUnknownOption(true).passThroughOptions(true).option("--json", "Output result as JSON", false).option("--wait", "Poll remote runs until a terminal status", false).option("--poll-interval-ms <ms>", "Remote polling interval in milliseconds", "1000").option("--poll-timeout-ms <ms>", "Maximum time to wait for a remote run", "300000").description("Run a skill directly").action(async (name, args2, options) => handleRun(name, args2, options));
@@ -71522,7 +72912,7 @@ function registerRuntime(parent) {
71522
72912
  }
71523
72913
  await handleMcp(options);
71524
72914
  });
71525
- const setup = parent.command("setup").description("Point this CLI at a Skills API server, or register agent integrations").option("--api-url <url>", "Skills API origin to send remote work to").option("--global", "Save the API origin globally instead of in this project", false).option("--json", "Output setup result as JSON", false).action(async (options) => handleSetup(options));
72915
+ const setup = parent.command("setup").description("Point this CLI at a Skills API server, or register agent integrations").option("--api-url <url>", "Skills API origin to send remote work to").option("--global", "Accepted for compatibility; the API origin is always per-user", false).option("--json", "Output setup result as JSON", false).action(async (options) => handleSetup(options));
71526
72916
  setup.command("agents").option("--json", "Output registration result as JSON", false).description("Register the Skills MCP server with all supported agents").action(async (options) => handleMcp({ register: "all", json: options.json }));
71527
72917
  parent.command("self-update").description("Update @hasna/skills to the latest version").option("--json", "Output result as JSON", false).action(async (options) => {
71528
72918
  if (process.env.SKILLS_TEST_MODE === "1") {
@@ -71568,13 +72958,12 @@ Updating ${name}...
71568
72958
  });
71569
72959
  }
71570
72960
  async function handleSetup(options) {
71571
- const scope = options.global ? "global" : "project";
71572
72961
  if (options.apiUrl !== undefined && !options.apiUrl.trim()) {
71573
- const error2 = "Invalid value '' for --api-url. Expected an http(s) URL";
72962
+ const error3 = "Invalid value '' for --api-url. Expected an http(s) URL";
71574
72963
  if (options.json)
71575
- console.log(JSON.stringify({ saved: null, scope, error: error2 }, null, 2));
72964
+ console.log(JSON.stringify({ saved: null, error: error3 }, null, 2));
71576
72965
  else
71577
- console.error(source_default.red(error2));
72966
+ console.error(source_default.red(error3));
71578
72967
  process.exitCode = 1;
71579
72968
  return;
71580
72969
  }
@@ -71583,49 +72972,89 @@ async function handleSetup(options) {
71583
72972
  requested = (await promptLine("Skills API URL (blank to leave unchanged): ")).trim();
71584
72973
  }
71585
72974
  let saved = null;
72975
+ let credentialsFile = null;
71586
72976
  if (requested) {
71587
72977
  try {
71588
- saveConfig("apiUrl", requested, scope);
71589
- saved = loadConfig().apiUrl ?? requested;
72978
+ const normalized = normalizeSkillsApiOrigin(requireHttpUrl(requested));
72979
+ credentialsFile = saveApiUrl(normalized);
72980
+ saved = normalized;
71590
72981
  } catch (err) {
71591
- const error2 = err.message;
72982
+ const error3 = err.message;
71592
72983
  if (options.json)
71593
- console.log(JSON.stringify({ saved: null, requested, scope, error: error2 }, null, 2));
72984
+ console.log(JSON.stringify({ saved: null, requested, error: error3 }, null, 2));
71594
72985
  else
71595
- console.error(source_default.red(error2));
72986
+ console.error(source_default.red(error3));
71596
72987
  process.exitCode = 1;
71597
72988
  return;
71598
72989
  }
71599
72990
  }
71600
- const config2 = loadConfig();
71601
- const configured = config2.apiUrl ?? null;
71602
- const next = configured ? ["skills auth login", "skills list --remote"] : ["skills list", "skills run <skill>"];
72991
+ let configured = null;
72992
+ let source = null;
72993
+ let authenticated = false;
72994
+ let error2 = null;
72995
+ try {
72996
+ const fleet = resolveSkillsFleet();
72997
+ if (fleet.mode === "hosted") {
72998
+ configured = fleet.apiOrigin;
72999
+ source = fleet.apiUrlSource;
73000
+ authenticated = true;
73001
+ }
73002
+ } catch (err) {
73003
+ error2 = err.message;
73004
+ configured = saved;
73005
+ }
73006
+ const next = error2 ? ["skills auth login"] : configured ? ["skills auth login", "skills list --remote"] : ["skills list", "skills run <skill>"];
71603
73007
  const payload = {
71604
73008
  apiUrl: configured,
73009
+ source,
71605
73010
  saved,
71606
- scope,
71607
- config: config2,
73011
+ credentialsFile,
73012
+ authenticated,
73013
+ ...error2 ? { error: error2 } : {},
73014
+ config: loadConfig(),
71608
73015
  next
71609
73016
  };
71610
73017
  if (options.json) {
71611
73018
  console.log(JSON.stringify(payload, null, 2));
73019
+ if (error2)
73020
+ process.exitCode = 1;
73021
+ return;
73022
+ }
73023
+ if (error2) {
73024
+ if (saved)
73025
+ console.log(source_default.green(`Skills API set to ${saved}`));
73026
+ console.error(source_default.red(error2));
73027
+ process.exitCode = 1;
71612
73028
  return;
71613
73029
  }
71614
73030
  if (saved) {
71615
73031
  console.log(source_default.green(`Skills API set to ${saved}`));
71616
- console.log(source_default.dim(` Scope: ${scope}`));
73032
+ if (credentialsFile)
73033
+ console.log(source_default.dim(` Saved in: ${credentialsFile}`));
71617
73034
  console.log(source_default.dim(" Next: skills auth login"));
71618
73035
  } else if (configured) {
71619
73036
  console.log(source_default.green(`Skills API already configured: ${configured}`));
73037
+ console.log(source_default.dim(` Source: ${source}`));
71620
73038
  console.log(source_default.dim(" Change it with: skills setup --api-url <url>"));
71621
- console.log(source_default.dim(" Clear it with: skills config unset apiUrl"));
71622
- console.log(source_default.dim(" Next: skills auth login"));
73039
+ console.log(source_default.dim(` Clear it with: skills config unset apiUrl (or unset ${SKILLS_API_URL_ENV})`));
71623
73040
  } else {
71624
73041
  console.log(source_default.green("No Skills API configured; skills run on this machine."));
71625
73042
  console.log(source_default.dim(" Point at a server with: skills setup --api-url <url>"));
71626
73043
  console.log(source_default.dim(" Next: skills list"));
71627
73044
  }
71628
73045
  }
73046
+ function requireHttpUrl(value) {
73047
+ let url;
73048
+ try {
73049
+ url = new URL(value);
73050
+ } catch {
73051
+ throw new Error(`Invalid value '${value}' for --api-url. Expected an http(s) URL`);
73052
+ }
73053
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
73054
+ throw new Error(`Invalid value '${value}' for --api-url. Expected an http(s) URL`);
73055
+ }
73056
+ return value.replace(/\/+$/, "");
73057
+ }
71629
73058
  function promptLine(question) {
71630
73059
  const rl = createInterface({ input: process.stdin, output: process.stdout });
71631
73060
  return new Promise((resolve2) => {
@@ -71657,7 +73086,7 @@ async function handleRun(name, args2, options) {
71657
73086
  return;
71658
73087
  }
71659
73088
  }
71660
- const routing = resolveConfiguredRunRouting(skill);
73089
+ const routing = await resolveConfiguredRunRouting(skill);
71661
73090
  const runContext = createSkillRun({
71662
73091
  skill: skill.name,
71663
73092
  args: args2,
@@ -71853,10 +73282,10 @@ ${run.id}
71853
73282
  }
71854
73283
  }
71855
73284
  async function handleRunsStatus(runId, options) {
71856
- const { getApiKey: getApiKey2 } = await Promise.resolve().then(() => (init_auth_store(), exports_auth_store));
71857
- const apiKey = getApiKey2();
73285
+ const { skillsCredentialOrReason: skillsCredentialOrReason2 } = await Promise.resolve().then(() => (init_fleet_credentials(), exports_fleet_credentials));
73286
+ const { apiKey, reason } = await skillsCredentialOrReason2();
71858
73287
  if (!apiKey) {
71859
- const error2 = "Remote run status requires API access. Run: skills auth login";
73288
+ const error2 = reason ?? "Remote run status requires API access. Run: skills auth login";
71860
73289
  if (options.json)
71861
73290
  console.log(JSON.stringify({ contractVersion: REMOTE_SKILL_RUN_CONTRACT_VERSION, error: error2 }, null, 2));
71862
73291
  else
@@ -71954,10 +73383,10 @@ async function handleExportsOpen(runId, options) {
71954
73383
  } catch {}
71955
73384
  }
71956
73385
  async function handleExportsDownload(runId, options) {
71957
- const { getApiKey: getApiKey2 } = await Promise.resolve().then(() => (init_auth_store(), exports_auth_store));
71958
- const apiKey = getApiKey2();
73386
+ const { skillsCredentialOrReason: skillsCredentialOrReason2 } = await Promise.resolve().then(() => (init_fleet_credentials(), exports_fleet_credentials));
73387
+ const { apiKey, reason } = await skillsCredentialOrReason2();
71959
73388
  if (!apiKey) {
71960
- const error2 = "Remote artifact downloads require API access. Run: skills auth login";
73389
+ const error2 = reason ?? "Remote artifact downloads require API access. Run: skills auth login";
71961
73390
  if (options.json)
71962
73391
  console.log(JSON.stringify({ error: error2 }, null, 2));
71963
73392
  else
@@ -71992,7 +73421,7 @@ async function handleExportsDownload(runId, options) {
71992
73421
  if (!response.ok)
71993
73422
  throw new Error(`download failed for artifact ${artifactId}: ${response.status}`);
71994
73423
  const relativePath = safeArtifactRelativePath(typeof artifact.relativePath === "string" ? artifact.relativePath : artifact.fileName, String(artifact.fileName || artifactId));
71995
- const outputPath = join30(exportDir, relativePath);
73424
+ const outputPath = join31(exportDir, relativePath);
71996
73425
  mkdirSync15(dirname11(outputPath), { recursive: true });
71997
73426
  const bytes = new Uint8Array(await response.arrayBuffer());
71998
73427
  writeFileSync16(outputPath, bytes);
@@ -72208,6 +73637,8 @@ var init_runtime = __esm(() => {
72208
73637
  init_skillinfo();
72209
73638
  init_blog_article();
72210
73639
  init_config();
73640
+ init_auth_store();
73641
+ init_fleet_credentials();
72211
73642
  init_runs();
72212
73643
  init_run_state();
72213
73644
  init_runtime_mcp();
@@ -72354,9 +73785,9 @@ var init_completion = __esm(() => {
72354
73785
  });
72355
73786
 
72356
73787
  // src/lib/portable-snapshot-filter.ts
72357
- import { readdirSync as readdirSync16, statSync as statSync16 } from "fs";
72358
- import { homedir as homedir10 } from "os";
72359
- import { join as join31, sep as sep3 } from "path";
73788
+ import { readdirSync as readdirSync16, statSync as statSync17 } from "fs";
73789
+ import { homedir as homedir8 } from "os";
73790
+ import { join as join32, sep as sep3 } from "path";
72360
73791
  function isExcludedSkillFileName(fileName) {
72361
73792
  if (EXCLUDE_FILE_NAMES.has(fileName)) {
72362
73793
  return true;
@@ -72377,18 +73808,18 @@ function isPortableWithinSkill(relativeParts) {
72377
73808
  return PORTABLE_SUBDIRS.has(second);
72378
73809
  }
72379
73810
  function homePathFor(definition, homesRoot) {
72380
- const home = homesRoot ?? homedir10();
73811
+ const home = homesRoot ?? homedir8();
72381
73812
  if (definition.subClass === "skills" || definition.subClass === "custom") {
72382
- return join31(skillsDataRootForHome(home), definition.name);
73813
+ return join32(skillsDataRootForHome(home), definition.name);
72383
73814
  }
72384
73815
  if (definition.agent === "opencode") {
72385
- return join31(home, ".config", "opencode", "skills");
73816
+ return join32(home, ".config", "opencode", "skills");
72386
73817
  }
72387
- return join31(home, `.${definition.agent}`, "skills");
73818
+ return join32(home, `.${definition.agent}`, "skills");
72388
73819
  }
72389
73820
  function destinationFor(definition, stationId, relativePath) {
72390
- const category = definition.subClass === "agent-homes" ? join31("agent-homes", definition.agent ?? "") : definition.name;
72391
- return join31("resources", stationId, "skills", category, ...relativePath.split(sep3));
73821
+ const category = definition.subClass === "agent-homes" ? join32("agent-homes", definition.agent ?? "") : definition.name;
73822
+ return join32("resources", stationId, "skills", category, ...relativePath.split(sep3));
72392
73823
  }
72393
73824
  function walkEntries(absoluteRoot) {
72394
73825
  let entries;
@@ -72399,7 +73830,7 @@ function walkEntries(absoluteRoot) {
72399
73830
  }
72400
73831
  const output = [];
72401
73832
  for (const entry of entries) {
72402
- const childFull = join31(absoluteRoot, entry.name);
73833
+ const childFull = join32(absoluteRoot, entry.name);
72403
73834
  if (entry.isSymbolicLink()) {
72404
73835
  output.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
72405
73836
  continue;
@@ -72410,7 +73841,7 @@ function walkEntries(absoluteRoot) {
72410
73841
  }
72411
73842
  const nested = walkEntries(childFull);
72412
73843
  for (const item of nested) {
72413
- output.push({ ...item, relativePath: join31(entry.name, item.relativePath) });
73844
+ output.push({ ...item, relativePath: join32(entry.name, item.relativePath) });
72414
73845
  }
72415
73846
  continue;
72416
73847
  }
@@ -72422,7 +73853,7 @@ function walkEntries(absoluteRoot) {
72422
73853
  }
72423
73854
  function isRegularFile(filePath) {
72424
73855
  try {
72425
- return statSync16(filePath).isFile();
73856
+ return statSync17(filePath).isFile();
72426
73857
  } catch {
72427
73858
  return false;
72428
73859
  }
@@ -72507,18 +73938,18 @@ import { createHash as createHash6 } from "crypto";
72507
73938
  import {
72508
73939
  copyFileSync as copyFileSync2,
72509
73940
  mkdirSync as mkdirSync16,
72510
- readFileSync as readFileSync24,
72511
- statSync as statSync17,
73941
+ readFileSync as readFileSync25,
73942
+ statSync as statSync18,
72512
73943
  writeFileSync as writeFileSync17
72513
73944
  } from "fs";
72514
- import { dirname as dirname12, isAbsolute as isAbsolute3, relative as relative5, resolve as resolve2, sep as sep4 } from "path";
73945
+ import { dirname as dirname12, isAbsolute as isAbsolute4, relative as relative5, resolve as resolve2, sep as sep4 } from "path";
72515
73946
  function validateStationId(stationId) {
72516
73947
  if (!/^[a-z0-9-]+$/.test(stationId)) {
72517
73948
  throw new StationSnapshotError("INVALID_STATION", `station id must be a slug, got: ${stationId}`);
72518
73949
  }
72519
73950
  }
72520
73951
  function sha256File(filePath) {
72521
- return createHash6("sha256").update(readFileSync24(filePath)).digest("hex");
73952
+ return createHash6("sha256").update(readFileSync25(filePath)).digest("hex");
72522
73953
  }
72523
73954
  function scanHome(definition, homesRoot) {
72524
73955
  const homePath = homePathFor(definition, homesRoot);
@@ -72548,7 +73979,7 @@ function scanHome(definition, homesRoot) {
72548
73979
  skipped.push({ relativePath: entry.relativePath, reason: "not-regular-file" });
72549
73980
  continue;
72550
73981
  }
72551
- const info = statSync17(entry.fullPath);
73982
+ const info = statSync18(entry.fullPath);
72552
73983
  portable.push({
72553
73984
  relativePath: entry.relativePath,
72554
73985
  fullPath: entry.fullPath,
@@ -72617,7 +74048,7 @@ function writeStationSnapshot(options) {
72617
74048
  for (const plan of plans) {
72618
74049
  const destination = resolve2(repoRoot, plan.destination);
72619
74050
  const destinationRelative = relative5(repoRoot, destination);
72620
- if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep4) || isAbsolute3(destinationRelative)) {
74051
+ if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep4) || isAbsolute4(destinationRelative)) {
72621
74052
  throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
72622
74053
  }
72623
74054
  let existingDigest = null;
@@ -72691,7 +74122,7 @@ __export(exports_create_sync_config, {
72691
74122
  registerCreateSync: () => registerCreateSync
72692
74123
  });
72693
74124
  import { existsSync as existsSync29, writeFileSync as writeFileSync18, mkdirSync as mkdirSync17 } from "fs";
72694
- import { join as join32 } from "path";
74125
+ import { join as join33 } from "path";
72695
74126
  function registerCreateSync(parent) {
72696
74127
  const configCmd = parent.command("config").description("Manage skills configuration");
72697
74128
  configCmd.command("show", { isDefault: true }).option("--json", "Output as JSON", false).description("Show current merged configuration").action((options) => {
@@ -72728,6 +74159,20 @@ function registerCreateSync(parent) {
72728
74159
  configCmd.command("unset <key>").option("--global", "Remove from the global config instead of the project config", false).option("--json", "Output as JSON", false).description("Remove a configuration value").action((key, options) => {
72729
74160
  const scope = options.global ? "global" : "project";
72730
74161
  try {
74162
+ if (key === "apiUrl") {
74163
+ const hadStored = Boolean(readStoredApiUrl());
74164
+ if (hadStored)
74165
+ saveApiUrl(null);
74166
+ const removedStaleKey = unsetConfig(key, scope);
74167
+ const removed2 = hadStored || removedStaleKey;
74168
+ if (options.json)
74169
+ console.log(JSON.stringify({ key, removed: removed2, scope, path: getConfigPath(scope) }));
74170
+ else if (removed2)
74171
+ console.log(source_default.green(`Unset ${key}`));
74172
+ else
74173
+ console.log(source_default.dim(`${key} was not set`));
74174
+ return;
74175
+ }
72731
74176
  const removed = unsetConfig(key, scope);
72732
74177
  if (options.json)
72733
74178
  console.log(JSON.stringify({ key, removed, scope, path: getConfigPath(scope) }));
@@ -72772,7 +74217,7 @@ function handleCreate(name, options) {
72772
74217
  const bare = name.trim();
72773
74218
  const dirName = bare;
72774
74219
  const baseDir = getPortableSkillsRoot();
72775
- const skillDir = join32(baseDir, dirName);
74220
+ const skillDir = join33(baseDir, dirName);
72776
74221
  if (existsSync29(skillDir)) {
72777
74222
  console.log(options.json ? JSON.stringify({ error: `Skill '${bare}' already exists at ${skillDir}` }) : source_default.red(`Skill '${bare}' already exists at ${skillDir}`));
72778
74223
  process.exitCode = 1;
@@ -72781,8 +74226,8 @@ function handleCreate(name, options) {
72781
74226
  const description = options.description || `${bare} skill`;
72782
74227
  const tags = options.tags ? options.tags.split(",").map((t) => t.trim()).filter(Boolean) : [bare];
72783
74228
  const displayName2 = bare.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
72784
- mkdirSync17(join32(skillDir, "src"), { recursive: true });
72785
- writeFileSync18(join32(skillDir, "SKILL.md"), [
74229
+ mkdirSync17(join33(skillDir, "src"), { recursive: true });
74230
+ writeFileSync18(join33(skillDir, "SKILL.md"), [
72786
74231
  "---",
72787
74232
  `name: ${bare}`,
72788
74233
  `description: ${description}`,
@@ -72802,11 +74247,11 @@ function handleCreate(name, options) {
72802
74247
  ""
72803
74248
  ].join(`
72804
74249
  `));
72805
- writeFileSync18(join32(skillDir, "src", "index.ts"), [`#!/usr/bin/env bun`, `/**`, ` * ${displayName2} \u2014 ${description}`, ` */`, "", `console.log("${displayName2}");`, ""].join(`
74250
+ writeFileSync18(join33(skillDir, "src", "index.ts"), [`#!/usr/bin/env bun`, `/**`, ` * ${displayName2} \u2014 ${description}`, ` */`, "", `console.log("${displayName2}");`, ""].join(`
72806
74251
  `));
72807
- writeFileSync18(join32(skillDir, "package.json"), JSON.stringify({ name: bare, version: "0.1.0", description, bin: { [bare]: "./src/index.ts" }, scripts: { dev: `bun src/index.ts` }, dependencies: {} }, null, 2) + `
74252
+ writeFileSync18(join33(skillDir, "package.json"), JSON.stringify({ name: bare, version: "0.1.0", description, bin: { [bare]: "./src/index.ts" }, scripts: { dev: `bun src/index.ts` }, dependencies: {} }, null, 2) + `
72808
74253
  `);
72809
- writeFileSync18(join32(skillDir, "tsconfig.json"), JSON.stringify({ compilerOptions: { target: "ES2022", module: "ESNext", moduleResolution: "bundler", strict: true, outDir: "dist" }, include: ["src/**/*.ts"] }, null, 2) + `
74254
+ writeFileSync18(join33(skillDir, "tsconfig.json"), JSON.stringify({ compilerOptions: { target: "ES2022", module: "ESNext", moduleResolution: "bundler", strict: true, outDir: "dist" }, include: ["src/**/*.ts"] }, null, 2) + `
72810
74255
  `);
72811
74256
  clearRegistryCache();
72812
74257
  if (options.json)
@@ -72815,8 +74260,8 @@ function handleCreate(name, options) {
72815
74260
  console.log(source_default.green(`\u2713 Created custom skill '${bare}' at ${skillDir}`));
72816
74261
  console.log(source_default.dim(` Category: ${options.category}`));
72817
74262
  console.log(source_default.dim(` Tags: ${tags.join(", ")}`));
72818
- console.log(` ${source_default.cyan("Edit:")} ${join32(skillDir, "src", "index.ts")}`);
72819
- console.log(` ${source_default.cyan("Run:")} bun ${join32(skillDir, "src", "index.ts")}`);
74263
+ console.log(` ${source_default.cyan("Edit:")} ${join33(skillDir, "src", "index.ts")}`);
74264
+ console.log(` ${source_default.cyan("Run:")} bun ${join33(skillDir, "src", "index.ts")}`);
72820
74265
  }
72821
74266
  }
72822
74267
  function handleSync(names, options) {
@@ -73052,6 +74497,7 @@ ${written}/${actions.length} ${dryRun ? "would be written" : "written"}`));
73052
74497
  var init_create_sync_config = __esm(() => {
73053
74498
  init_source();
73054
74499
  init_config();
74500
+ init_auth_store();
73055
74501
  init_portable_skills();
73056
74502
  init_registry();
73057
74503
  init_agent_sync();
@@ -73066,23 +74512,23 @@ import {
73066
74512
  copyFileSync as copyFileSync3,
73067
74513
  mkdirSync as mkdirSync18,
73068
74514
  readdirSync as readdirSync17,
73069
- readFileSync as readFileSync25,
73070
- statSync as statSync18,
74515
+ readFileSync as readFileSync26,
74516
+ statSync as statSync19,
73071
74517
  writeFileSync as writeFileSync19
73072
74518
  } from "fs";
73073
- import { dirname as dirname13, join as join33, resolve as resolve3, sep as sep5 } from "path";
74519
+ import { dirname as dirname13, join as join34, resolve as resolve3, sep as sep5 } from "path";
73074
74520
  function fail2(code, message, detail = []) {
73075
74521
  throw new StationSnapshotError(code, message, detail);
73076
74522
  }
73077
74523
  function snapshotRootFor(repoRoot, stationId) {
73078
- return join33(repoRoot, "resources", stationId, "skills");
74524
+ return join34(repoRoot, "resources", stationId, "skills");
73079
74525
  }
73080
74526
  function readSnapshotManifest(repoRoot, stationId) {
73081
74527
  const snapshotRoot = snapshotRootFor(repoRoot, stationId);
73082
- const manifestPath = join33(snapshotRoot, "sync-manifest.json");
74528
+ const manifestPath = join34(snapshotRoot, "sync-manifest.json");
73083
74529
  let manifest;
73084
74530
  try {
73085
- manifest = JSON.parse(readFileSync25(manifestPath, "utf8"));
74531
+ manifest = JSON.parse(readFileSync26(manifestPath, "utf8"));
73086
74532
  } catch (error2) {
73087
74533
  fail2("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error2.message}`);
73088
74534
  }
@@ -73106,7 +74552,7 @@ function planStationHydration(stationId, repoRoot) {
73106
74552
  const hashMismatches = [];
73107
74553
  const skippedByRule = [];
73108
74554
  for (const agent of SYNC_AGENTS) {
73109
- const agentRoot = join33(snapshotRoot, "agent-homes", agent);
74555
+ const agentRoot = join34(snapshotRoot, "agent-homes", agent);
73110
74556
  let identEntries;
73111
74557
  try {
73112
74558
  identEntries = readdirSync17(agentRoot, { withFileTypes: true });
@@ -73117,7 +74563,7 @@ function planStationHydration(stationId, repoRoot) {
73117
74563
  if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
73118
74564
  continue;
73119
74565
  }
73120
- const identRoot = join33(agentRoot, identEntry.name);
74566
+ const identRoot = join34(agentRoot, identEntry.name);
73121
74567
  const entries = walkEntries(identRoot);
73122
74568
  for (const entry of entries) {
73123
74569
  const relativeParts = [identEntry.name, ...entry.relativePath.split(sep5)];
@@ -73164,7 +74610,7 @@ function planStationHydration(stationId, repoRoot) {
73164
74610
  });
73165
74611
  continue;
73166
74612
  }
73167
- const info = statSync18(entry.fullPath);
74613
+ const info = statSync19(entry.fullPath);
73168
74614
  const manifestHash = manifestHashes.get(`${agent}${MANIFEST_HASH_KEY_SEP}${homeRelative}`) ?? null;
73169
74615
  let verified = false;
73170
74616
  if (manifestHash !== null) {
@@ -73218,7 +74664,7 @@ function planStationHydration(stationId, repoRoot) {
73218
74664
  for (const copy of copies) {
73219
74665
  let isStub = false;
73220
74666
  try {
73221
- isStub = isPointerSkillMd(readFileSync25(copy.fullPath, "utf8"));
74667
+ isStub = isPointerSkillMd(readFileSync26(copy.fullPath, "utf8"));
73222
74668
  } catch {
73223
74669
  isStub = false;
73224
74670
  }
@@ -73302,7 +74748,7 @@ function writeStationHydration(options) {
73302
74748
  const toWrite = [];
73303
74749
  for (const skill of plan.winners) {
73304
74750
  for (const file of skill.files) {
73305
- const destination = join33(cacheRoot, skill.ident, file.withinIdent);
74751
+ const destination = join34(cacheRoot, skill.ident, file.withinIdent);
73306
74752
  const digest = sha256File(file.winner.fullPath);
73307
74753
  let existingDigest = null;
73308
74754
  try {
@@ -73344,7 +74790,7 @@ function writeStationHydration(options) {
73344
74790
  },
73345
74791
  skills: resultSkills
73346
74792
  };
73347
- const hydrationManifestPath = join33(dirname13(cacheRoot), `hydration-${options.stationId}.json`);
74793
+ const hydrationManifestPath = join34(dirname13(cacheRoot), `hydration-${options.stationId}.json`);
73348
74794
  mkdirSync18(dirname13(hydrationManifestPath), { recursive: true });
73349
74795
  writeFileSync19(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
73350
74796
  `);
@@ -73706,7 +75152,7 @@ async function executeScheduledSkill(skillName, args2, options) {
73706
75152
  if (!skill)
73707
75153
  throw new Error(`Skill '${skillName}' not found`);
73708
75154
  const { resolveConfiguredRunRouting: resolveConfiguredRunRouting2 } = await Promise.resolve().then(() => (init_run_routing(), exports_run_routing));
73709
- const routing = resolveConfiguredRunRouting2(skill);
75155
+ const routing = await resolveConfiguredRunRouting2(skill);
73710
75156
  if (routing.route === "error") {
73711
75157
  throw new Error(`${routing.code}: ${routing.error}`);
73712
75158
  }
@@ -73925,7 +75371,7 @@ ${ok}/${results.length} pulled into ~/.hasna/skills/installed`));
73925
75371
  }
73926
75372
  function registerVersions(parent) {
73927
75373
  parent.command("versions").argument("<name>", "Skill name on the configured instance").option("--json", "Output as JSON", false).description("List the published versions of a skill on the configured instance").action(async (name, options) => {
73928
- const client = createRemoteSkillsClient();
75374
+ const client = await createRemoteSkillsClient();
73929
75375
  if (!client) {
73930
75376
  const message = "No API key configured, so there is no instance to list versions from.";
73931
75377
  if (options.json)
@@ -73972,6 +75418,7 @@ var init_registry2 = __esm(() => {
73972
75418
  // src/cli/commands/publish.ts
73973
75419
  var exports_publish = {};
73974
75420
  __export(exports_publish, {
75421
+ sanitizeGitRemote: () => sanitizeGitRemote,
73975
75422
  registerPublish: () => registerPublish,
73976
75423
  pushSkill: () => pushSkill,
73977
75424
  bumpPatch: () => bumpPatch,
@@ -73979,9 +75426,9 @@ __export(exports_publish, {
73979
75426
  PushSkillError: () => PushSkillError
73980
75427
  });
73981
75428
  import { execFileSync } from "child_process";
73982
- import { existsSync as existsSync30, readFileSync as readFileSync26 } from "fs";
75429
+ import { existsSync as existsSync30, readFileSync as readFileSync27 } from "fs";
73983
75430
  import { hostname as hostname2 } from "os";
73984
- import { join as join34 } from "path";
75431
+ import { join as join35 } from "path";
73985
75432
  function registerPublish(parent) {
73986
75433
  parent.command("push").argument("<name>", "Name of a skill in the local corpus (~/.hasna/skills/installed or the migrated ~/.hasna/skills/skills)").option("--version <version>", "Override the version recorded on the instance").option("--force-new-version", "If name@version already exists with different content, publish as the next patch version", false).option("--dry-run", "Pack and validate without uploading", false).option("--json", "Output result as JSON", false).description("Publish a local skill to the configured skills instance").action(async (name, options) => {
73987
75434
  try {
@@ -74022,10 +75469,14 @@ async function pushSkill(name, options = {}) {
74022
75469
  throw new PushSkillError(`Skill '${skill.name}' is not valid and was not published.`, validation.issues.map((issue2) => `${issue2.code}: ${issue2.message}`));
74023
75470
  }
74024
75471
  const manifest = readPortableSkillManifest(skill.path, skill.name);
75472
+ const declaredVersion = options.version ?? readDeclaredSkillVersion(skill.path);
75473
+ if (!declaredVersion) {
75474
+ throw new PushSkillError(`Skill '${skill.name}' declares no version, so it was not published as an invented '0.1.0'.`, ["Declare a version in skill.json (or the SKILL.md frontmatter / package.json), or pass --version <version> to this push."]);
75475
+ }
74025
75476
  const packed = packSkillBundle(skill.path, { maxUnpackedBytes: MAX_UNPACKED_BYTES });
74026
75477
  const versionManifest = buildVersionManifest(skill.path, packed);
74027
- const skillMdPath = join34(skill.path, "SKILL.md");
74028
- const skillMd = existsSync30(skillMdPath) ? readFileSync26(skillMdPath, "utf-8") : undefined;
75478
+ const skillMdPath = join35(skill.path, "SKILL.md");
75479
+ const skillMd = existsSync30(skillMdPath) ? readFileSync27(skillMdPath, "utf-8") : undefined;
74029
75480
  const base2 = {
74030
75481
  slug: skill.name,
74031
75482
  path: skill.path,
@@ -74040,13 +75491,13 @@ async function pushSkill(name, options = {}) {
74040
75491
  };
74041
75492
  if (options.dryRun)
74042
75493
  return base2;
74043
- const client = options.client !== undefined ? options.client : createRemoteSkillsClient();
75494
+ const client = options.client !== undefined ? options.client : await createRemoteSkillsClient();
74044
75495
  if (!client) {
74045
- throw new PushSkillError("No API key configured, so there is nowhere to publish to.", ["Run `skills login`, or set SKILLS_API_KEY and SKILLS_API_URL for this instance."]);
75496
+ throw new PushSkillError("No API key configured, so there is nowhere to publish to.", ["Run `skills auth login`, or set HASNA_SKILLS_API_KEY (and HASNA_SKILLS_API_URL for your own instance)."]);
74046
75497
  }
74047
75498
  const current = await client.getSkill(skill.name);
74048
75499
  const ifMatch = current && typeof current.revisionId === "string" && current.revisionId ? current.revisionId : undefined;
74049
- let version2 = options.version ?? manifest.version ?? "0.1.0";
75500
+ let version2 = options.version ?? manifest.version;
74050
75501
  let response = await publishOnce(client, skill, manifest, packed, skillMd, versionManifest, version2, ifMatch);
74051
75502
  let payload = await readBody(response);
74052
75503
  if (response.status === 409 && codeOf(payload) === "SKILL_VERSION_EXISTS") {
@@ -74059,6 +75510,9 @@ async function pushSkill(name, options = {}) {
74059
75510
  version2 = bumpPatch(version2);
74060
75511
  response = await publishOnce(client, skill, manifest, packed, skillMd, versionManifest, version2, ifMatch);
74061
75512
  payload = await readBody(response);
75513
+ if (response.status === 409 && codeOf(payload) === "SKILL_VERSION_EXISTS") {
75514
+ throw new PushSkillError(`Publishing '${skill.name}@${version2}' failed: even the bumped version already exists on the instance with different content.`, ["code: SKILL_VERSION_EXISTS", "Pick an explicit --version <new> that is free, and push again."]);
75515
+ }
74062
75516
  }
74063
75517
  if (!response.ok) {
74064
75518
  const code = codeOf(payload);
@@ -74070,7 +75524,15 @@ async function pushSkill(name, options = {}) {
74070
75524
  }
74071
75525
  throw new PushSkillError(`Publishing '${skill.name}' failed: ${response.status} ${describeError(payload)}`, code ? [`code: ${code}`] : undefined);
74072
75526
  }
74073
- return { ...base2, published: true, status: response.status, response: payload, version: version2 };
75527
+ const alreadyPublished = isAlreadyPublishedPayload(payload);
75528
+ return {
75529
+ ...base2,
75530
+ published: true,
75531
+ status: response.status,
75532
+ response: payload,
75533
+ version: version2,
75534
+ ...alreadyPublished ? { alreadyPublished: true } : {}
75535
+ };
74074
75536
  }
74075
75537
  async function publishOnce(client, skill, manifest, packed, skillMd, versionManifest, version2, ifMatch) {
74076
75538
  return client.publishSkill({
@@ -74091,6 +75553,9 @@ async function publishOnce(client, skill, manifest, packed, skillMd, versionMani
74091
75553
  function codeOf(payload) {
74092
75554
  return typeof payload === "object" && payload && "code" in payload ? String(payload.code) : undefined;
74093
75555
  }
75556
+ function isAlreadyPublishedPayload(payload) {
75557
+ return typeof payload === "object" && payload !== null && payload.alreadyPublished === true;
75558
+ }
74094
75559
  function bumpPatch(version2) {
74095
75560
  const match = /^(\d+)\.(\d+)\.(\d+)(.*)$/.exec(version2);
74096
75561
  if (!match)
@@ -74108,12 +75573,28 @@ function buildVersionManifest(skillDir, packed) {
74108
75573
  machine: hostname2(),
74109
75574
  agent: process.env.SKILLS_AGENT_ID ?? process.env.HASNA_AGENT_ID ?? process.env.AGENT_ID ?? null,
74110
75575
  cliVersion: package_default.version,
74111
- gitRemote: gitValue(skillDir, ["remote", "get-url", "origin"]),
75576
+ gitRemote: sanitizeGitRemote(gitValue(skillDir, ["remote", "get-url", "origin"])),
74112
75577
  gitSha: gitValue(skillDir, ["rev-parse", "HEAD"]),
74113
75578
  packedAt: new Date().toISOString()
74114
75579
  }
74115
75580
  };
74116
75581
  }
75582
+ function sanitizeGitRemote(url) {
75583
+ if (!url)
75584
+ return null;
75585
+ try {
75586
+ const parsed = new URL(url);
75587
+ if (!parsed.username && !parsed.password)
75588
+ return url;
75589
+ if (parsed.protocol === "ssh:")
75590
+ return url;
75591
+ parsed.username = "";
75592
+ parsed.password = "";
75593
+ return parsed.toString();
75594
+ } catch {
75595
+ return url;
75596
+ }
75597
+ }
74117
75598
  function gitValue(dir, args2) {
74118
75599
  try {
74119
75600
  const out = execFileSync("git", ["-C", dir, ...args2], { stdio: ["ignore", "pipe", "ignore"], encoding: "utf-8" }).trim();
@@ -74126,10 +75607,14 @@ function printHuman(result2) {
74126
75607
  if (!result2.published) {
74127
75608
  console.log(source_default.bold(`
74128
75609
  Dry run: '${result2.slug}' would be published
75610
+ `));
75611
+ } else if (result2.alreadyPublished) {
75612
+ console.log(source_default.green(`
75613
+ \u2713 '${result2.slug}@${result2.version}' was already published \u2014 no new version created
74129
75614
  `));
74130
75615
  } else {
74131
75616
  console.log(source_default.green(`
74132
- \u2713 Published '${result2.slug}'
75617
+ \u2713 Published '${result2.slug}'${result2.version ? ` as ${result2.version}` : ""}
74133
75618
  `));
74134
75619
  }
74135
75620
  console.log(` ${source_default.dim("source")} ${result2.path}`);
@@ -74294,14 +75779,17 @@ function writeCommandError(err, fallback, json) {
74294
75779
  if (payload.endpoint)
74295
75780
  console.error(source_default.dim(`Endpoint: ${payload.endpoint}`));
74296
75781
  if (status !== undefined && CONFIG_HINT_STATUSES.has(status)) {
74297
- console.error(source_default.dim(`Hint: check SKILLS_API_URL (currently ${payload.apiUrl}) or run: skills setup`));
75782
+ console.error(source_default.dim(`Hint: check ${SKILLS_API_URL_ENV} (currently ${payload.apiUrl}) or run: skills setup`));
74298
75783
  }
74299
75784
  process.exitCode = 1;
74300
75785
  }
74301
- function envApiKey() {
74302
- const key = process.env.SKILLS_API_KEY || process.env.SKILL_API_KEY;
74303
- const trimmed = key?.trim();
74304
- return trimmed || null;
75786
+ function credentialSource() {
75787
+ try {
75788
+ const fleet = resolveSkillsFleet();
75789
+ return fleet.mode === "hosted" ? fleet.apiKeySource : null;
75790
+ } catch {
75791
+ return null;
75792
+ }
74305
75793
  }
74306
75794
  function stringField2(value) {
74307
75795
  return typeof value === "string" && value.length > 0 ? value : undefined;
@@ -74341,8 +75829,8 @@ function printWhoami(payload) {
74341
75829
  console.log(source_default.bold("Role: ") + payload.role);
74342
75830
  if (payload.organizationName)
74343
75831
  console.log(source_default.bold("Name: ") + payload.organizationName);
74344
- if (payload.authSource === "env")
74345
- console.log(source_default.dim("Auth: SKILLS_API_KEY"));
75832
+ if (payload.authSource)
75833
+ console.log(source_default.dim(`Auth: ${payload.authSource}`));
74346
75834
  if (payload.offline)
74347
75835
  console.log(source_default.dim("(offline \u2014 showing cached info)"));
74348
75836
  }
@@ -74644,35 +76132,46 @@ function registerAuth(parent) {
74644
76132
  console.log(source_default.dim("Not signed in"));
74645
76133
  return;
74646
76134
  }
74647
- clearAuthConfig();
74648
- console.log(source_default.green(`\u2713 Signed out (was ${existing.email})`));
76135
+ const { stillResolves } = clearAuthConfig();
76136
+ console.log(source_default.green(`\u2713 Signed out${existing.email ? ` (was ${existing.email})` : ""}`));
76137
+ if (stillResolves) {
76138
+ console.log(source_default.yellow(`A Skills credential still resolves from ${credentialSource() ?? "another source"}; ` + `clear it there to finish signing out.`));
76139
+ }
74649
76140
  });
74650
76141
  auth.command("whoami").description("Show current account info").option("--json", "Output as JSON", false).action(async (options) => {
74651
- const envKey = envApiKey();
74652
- const config2 = getAuthConfig();
74653
- const apiKey = envKey ?? config2?.apiKey;
74654
- if (!apiKey) {
74655
- const payload = { status: "unauthenticated", error: "Not signed in. Run: skills auth login" };
76142
+ let fleet;
76143
+ try {
76144
+ fleet = resolveSkillsFleet();
76145
+ } catch (err) {
76146
+ writeCommandError(err, "Failed to resolve the Skills credential", options.json);
76147
+ return;
76148
+ }
76149
+ if (fleet.mode !== "hosted") {
76150
+ const payload = {
76151
+ status: "unauthenticated",
76152
+ error: `Not signed in. Run: skills auth login, or set ${SKILLS_API_KEY_ENV}`
76153
+ };
74656
76154
  if (options.json)
74657
76155
  console.log(JSON.stringify(payload, null, 2));
74658
76156
  else
74659
76157
  console.log(source_default.dim(payload.error));
74660
76158
  return;
74661
76159
  }
74662
- const authSource = envKey ? "env" : "stored";
76160
+ const cached2 = fleet.apiKeyTier === "disk" ? getAuthIdentity() : null;
76161
+ const authSource = fleet.apiKeySource;
74663
76162
  try {
74664
76163
  const res = await apiRequest("/api/auth/whoami", {
74665
- headers: { Authorization: `Bearer ${apiKey}` }
76164
+ headers: { Authorization: `Bearer ${fleet.apiKey}` }
74666
76165
  });
74667
- const payload = authIdentityPayload(authSource, res, envKey ? null : config2);
76166
+ const payload = authIdentityPayload(authSource, res, cached2);
74668
76167
  if (options.json) {
74669
76168
  console.log(JSON.stringify(payload, null, 2));
74670
76169
  } else {
74671
76170
  printWhoami(payload);
74672
76171
  }
74673
76172
  } catch (err) {
74674
- if (config2 && !envKey) {
74675
- const payload = authIdentityPayload("stored", {}, config2, true);
76173
+ if (cached2 && Object.keys(cached2).length > 0) {
76174
+ const payload = authIdentityPayload(authSource, {}, cached2, true);
74676
76175
  if (options.json)
74677
76176
  console.log(JSON.stringify(payload, null, 2));
74678
76177
  else
@@ -74687,6 +76186,7 @@ var isTTY, DEFAULT_DEVICE_POLL_TIMEOUT_MS, MAX_ERROR_DETAIL_LENGTH = 200, CONFIG
74687
76186
  var init_auth = __esm(() => {
74688
76187
  init_source();
74689
76188
  init_auth_store();
76189
+ init_fleet_credentials();
74690
76190
  isTTY = process.stdin.isTTY && process.stdout.isTTY;
74691
76191
  DEFAULT_DEVICE_POLL_TIMEOUT_MS = 10 * 60 * 1000;
74692
76192
  CONFIG_HINT_STATUSES = new Set([401, 403, 404, 405, 501]);
@@ -74848,11 +76348,11 @@ var init_storage = __esm(() => {
74848
76348
  });
74849
76349
 
74850
76350
  // src/lib/registry-reconcile.ts
74851
- import { existsSync as existsSync31, readFileSync as readFileSync27, statSync as statSync19, writeFileSync as writeFileSync21 } from "fs";
74852
- import { join as join35 } from "path";
76351
+ import { existsSync as existsSync31, readFileSync as readFileSync28, statSync as statSync20, writeFileSync as writeFileSync21 } from "fs";
76352
+ import { join as join36 } from "path";
74853
76353
  function isDirectory2(path) {
74854
76354
  try {
74855
- return statSync19(path).isDirectory();
76355
+ return statSync20(path).isDirectory();
74856
76356
  } catch {
74857
76357
  return false;
74858
76358
  }
@@ -74860,15 +76360,15 @@ function isDirectory2(path) {
74860
76360
  function migrationNeeded(options) {
74861
76361
  if (options.rootDir)
74862
76362
  return false;
74863
- const appDir = options.homeDir ? join35(options.homeDir, ".hasna", "skills") : getDataDir();
74864
- return !(isOwnerLayoutMigrated(appDir) && isDirectory2(join35(appDir, SKILLS_CACHE_DIRNAME)));
76363
+ const appDir = options.homeDir ? join36(options.homeDir, ".hasna", "skills") : getDataDir();
76364
+ return !(isOwnerLayoutMigrated(appDir) && isDirectory2(join36(appDir, SKILLS_CACHE_DIRNAME)));
74865
76365
  }
74866
76366
  function readBaseline(skillDir) {
74867
- const markerPath = join35(skillDir, PULL_MARKER_FILE);
76367
+ const markerPath = join36(skillDir, PULL_MARKER_FILE);
74868
76368
  if (!existsSync31(markerPath))
74869
76369
  return;
74870
76370
  try {
74871
- const marker = JSON.parse(readFileSync27(markerPath, "utf-8"));
76371
+ const marker = JSON.parse(readFileSync28(markerPath, "utf-8"));
74872
76372
  return {
74873
76373
  ...typeof marker.contentHash === "string" && marker.contentHash ? { contentHash: marker.contentHash } : {},
74874
76374
  ...typeof marker.version === "string" && marker.version ? { version: marker.version } : {}
@@ -74878,11 +76378,11 @@ function readBaseline(skillDir) {
74878
76378
  }
74879
76379
  }
74880
76380
  function readCursor(root) {
74881
- const path = join35(root, SYNC_CURSOR_FILE);
76381
+ const path = join36(root, SYNC_CURSOR_FILE);
74882
76382
  if (!existsSync31(path))
74883
76383
  return { runCount: 0 };
74884
76384
  try {
74885
- const cursor = JSON.parse(readFileSync27(path, "utf-8"));
76385
+ const cursor = JSON.parse(readFileSync28(path, "utf-8"));
74886
76386
  return { runCount: typeof cursor.runCount === "number" ? cursor.runCount : 0 };
74887
76387
  } catch {
74888
76388
  return { runCount: 0 };
@@ -74891,12 +76391,12 @@ function readCursor(root) {
74891
76391
  function resolveCorpusRootReadOnly(options) {
74892
76392
  if (options.rootDir)
74893
76393
  return { root: options.rootDir, migrationPending: false };
74894
- const appDir = options.homeDir ? join35(options.homeDir, ".hasna", "skills") : getDataDirReadOnly();
74895
- const cache3 = join35(appDir, SKILLS_CACHE_DIRNAME);
76394
+ const appDir = options.homeDir ? join36(options.homeDir, ".hasna", "skills") : getDataDirReadOnly();
76395
+ const cache3 = join36(appDir, SKILLS_CACHE_DIRNAME);
74896
76396
  if (isOwnerLayoutMigrated(appDir) && isDirectory2(cache3)) {
74897
76397
  return { root: cache3, migrationPending: false };
74898
76398
  }
74899
- return { root: join35(appDir, INSTALLED_SKILLS_DIRNAME), migrationPending: true };
76399
+ return { root: join36(appDir, INSTALLED_SKILLS_DIRNAME), migrationPending: true };
74900
76400
  }
74901
76401
  function remoteRowToSkill(record3) {
74902
76402
  const slug = typeof record3.slug === "string" ? record3.slug : typeof record3.name === "string" ? record3.name : undefined;
@@ -74994,9 +76494,9 @@ async function reconcileRegistry(options = {}) {
74994
76494
  }
74995
76495
  const direction = options.pull && !options.push && !options.all ? "pull" : options.push && !options.pull && !options.all ? "push" : "all";
74996
76496
  const dryRun = options.dryRun ?? false;
74997
- const client = options.client !== undefined ? options.client : dryRun ? createRemoteSkillsClientReadOnly() : createRemoteSkillsClient();
76497
+ const client = options.client !== undefined ? options.client : dryRun ? await createRemoteSkillsClientReadOnly() : await createRemoteSkillsClient();
74998
76498
  if (!client) {
74999
- throw new ReconcileRegistryError("No API key configured, so there is nowhere to sync to.", ["Run `skills login`, or set SKILLS_API_KEY and SKILLS_API_URL for this instance."]);
76499
+ throw new ReconcileRegistryError("No API key configured, so there is nowhere to sync to.", ["Run `skills auth login`, or set HASNA_SKILLS_API_KEY (and HASNA_SKILLS_API_URL for your own instance)."]);
75000
76500
  }
75001
76501
  const { root, migrationPending } = dryRun ? resolveCorpusRootReadOnly(options) : { root: resolveCorpusRoot(options), migrationPending: migrationNeeded(options) };
75002
76502
  const localSkills = listPortableSkillMetas({ rootDir: root });
@@ -75013,7 +76513,7 @@ async function reconcileRegistry(options = {}) {
75013
76513
  const remoteRowsPayload = await client.listSkills();
75014
76514
  if (!Array.isArray(remoteRowsPayload)) {
75015
76515
  const shape = remoteRowsPayload && typeof remoteRowsPayload === "object" ? `object with keys [${Object.keys(remoteRowsPayload).join(", ")}]` : typeof remoteRowsPayload;
75016
- throw new ReconcileRegistryError(`Registry listing failed: expected an array of skills, got ${shape}.`, ["Check SKILLS_API_URL and the stored credential; a failed listing must not be read as an empty registry."]);
76516
+ throw new ReconcileRegistryError(`Registry listing failed: expected an array of skills, got ${shape}.`, ["Check HASNA_SKILLS_API_URL and the resolved credential; a failed listing must not be read as an empty registry."]);
75017
76517
  }
75018
76518
  const remotes = new Map;
75019
76519
  for (const row of remoteRowsPayload) {
@@ -75028,7 +76528,7 @@ async function reconcileRegistry(options = {}) {
75028
76528
  for (const slug of allSlugs) {
75029
76529
  const local = locals.get(slug);
75030
76530
  const remote = remotes.get(slug);
75031
- const baseline = local ? readBaseline(join35(root, slug)) : undefined;
76531
+ const baseline = local ? readBaseline(join36(root, slug)) : undefined;
75032
76532
  const { state, reason } = classifySkill(local, remote, baseline);
75033
76533
  let { action, reason: actionReason } = resolveAction(state, direction, conflict);
75034
76534
  if (state === "remote-only" && isDigestless(remote)) {
@@ -75087,7 +76587,7 @@ async function reconcileRegistry(options = {}) {
75087
76587
  try {
75088
76588
  await pushSkill(slug, { rootDir: root, client });
75089
76589
  const pushed = locals.get(slug);
75090
- writePullMarker(join35(root, slug), {
76590
+ writePullMarker(join36(root, slug), {
75091
76591
  skill: slug,
75092
76592
  ...pushed?.version ? { version: pushed.version } : {},
75093
76593
  ...pushed?.sha256 ? { contentHash: pushed.sha256 } : {},
@@ -75159,7 +76659,7 @@ async function reconcileRegistry(options = {}) {
75159
76659
  runCount: readCursor(root).runCount + 1,
75160
76660
  summary
75161
76661
  };
75162
- writeFileSync21(join35(root, SYNC_CURSOR_FILE), `${JSON.stringify(cursor, null, 2)}
76662
+ writeFileSync21(join36(root, SYNC_CURSOR_FILE), `${JSON.stringify(cursor, null, 2)}
75163
76663
  `);
75164
76664
  return {
75165
76665
  corpusRoot: root,
@@ -75276,7 +76776,7 @@ import process12 from "process";
75276
76776
  // ../../node_modules/.bun/ink@5.2.1+6e93904c7bfe2418/node_modules/ink/build/ink.js
75277
76777
  var import_react10 = __toESM(require_react(), 1);
75278
76778
  import process11 from "process";
75279
- // ../../node_modules/.bun/es-toolkit@1.51.0/node_modules/es-toolkit/dist/function/debounce.mjs
76779
+ // ../../node_modules/.bun/es-toolkit@1.52.0/node_modules/es-toolkit/dist/function/debounce.mjs
75280
76780
  function debounce(func, debounceMs, { signal, edges } = {}) {
75281
76781
  let pendingThis = undefined;
75282
76782
  let pendingArgs = null;
@@ -75334,7 +76834,7 @@ function debounce(func, debounceMs, { signal, edges } = {}) {
75334
76834
  return debounced;
75335
76835
  }
75336
76836
 
75337
- // ../../node_modules/.bun/es-toolkit@1.51.0/node_modules/es-toolkit/dist/compat/function/debounce.mjs
76837
+ // ../../node_modules/.bun/es-toolkit@1.52.0/node_modules/es-toolkit/dist/compat/function/debounce.mjs
75338
76838
  function debounce2(func, debounceMs = 0, options = {}) {
75339
76839
  if (typeof options !== "object")
75340
76840
  options = {};
@@ -75355,7 +76855,8 @@ function debounce2(func, debounceMs = 0, options = {}) {
75355
76855
  if (pendingAt === null)
75356
76856
  pendingAt = Date.now();
75357
76857
  if (Date.now() - pendingAt >= maxWait) {
75358
- result = func.apply(this, args);
76858
+ if (leading || trailing)
76859
+ result = func.apply(this, args);
75359
76860
  pendingAt = Date.now();
75360
76861
  _debounced.cancel();
75361
76862
  _debounced.schedule();
@@ -75374,7 +76875,7 @@ function debounce2(func, debounceMs = 0, options = {}) {
75374
76875
  return debounced;
75375
76876
  }
75376
76877
 
75377
- // ../../node_modules/.bun/es-toolkit@1.51.0/node_modules/es-toolkit/dist/compat/function/throttle.mjs
76878
+ // ../../node_modules/.bun/es-toolkit@1.52.0/node_modules/es-toolkit/dist/compat/function/throttle.mjs
75378
76879
  function throttle(func, throttleMs = 0, options = {}) {
75379
76880
  const { leading = true, trailing = true } = options;
75380
76881
  return debounce2(func, throttleMs, {