@hasna/skills 0.3.0 → 0.4.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,7 +36860,7 @@ var package_default;
36860
36860
  var init_package = __esm(() => {
36861
36861
  package_default = {
36862
36862
  name: "@hasna/skills",
36863
- version: "0.3.0",
36863
+ version: "0.4.0",
36864
36864
  description: "Skills library for AI coding agents",
36865
36865
  type: "module",
36866
36866
  bin: {
@@ -36919,8 +36919,7 @@ var init_package = __esm(() => {
36919
36919
  "verify:release": "bun run scripts/release-guard.ts",
36920
36920
  prepare: "bun run build:js",
36921
36921
  prepack: "bun run build && bun run verify:release",
36922
- prepublishOnly: "bun run typecheck && bun run test",
36923
- postinstall: "mkdir -p $HOME/.hasna/skills/custom 2>/dev/null || true"
36922
+ prepublishOnly: "bun run typecheck && bun run test"
36924
36923
  },
36925
36924
  keywords: [
36926
36925
  "skills",
@@ -39056,6 +39055,16 @@ function isHostedMetadataSkill(skillName, frontmatter, registryMeta, packageDecl
39056
39055
  return true;
39057
39056
  return false;
39058
39057
  }
39058
+ function frontmatterString(raw) {
39059
+ if (raw.startsWith('"') && raw.endsWith('"')) {
39060
+ try {
39061
+ const decoded = JSON.parse(raw);
39062
+ if (typeof decoded === "string")
39063
+ return decoded;
39064
+ } catch {}
39065
+ }
39066
+ return raw.replace(/^["']|["']$/g, "");
39067
+ }
39059
39068
  function parseSkillFrontmatter(content) {
39060
39069
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
39061
39070
  if (!match)
@@ -39075,12 +39084,12 @@ function parseSkillFrontmatter(content) {
39075
39084
  const tags = [];
39076
39085
  while (i + 1 < lines.length && /^\s+-\s+/.test(lines[i + 1])) {
39077
39086
  i++;
39078
- tags.push(lines[i].replace(/^\s+-\s+/, "").trim());
39087
+ tags.push(frontmatterString(lines[i].replace(/^\s+-\s+/, "").trim()));
39079
39088
  }
39080
39089
  result2.tags = tags;
39081
39090
  continue;
39082
39091
  }
39083
- const value = rawValue.replace(/^["']|["']$/g, "");
39092
+ const value = frontmatterString(rawValue);
39084
39093
  if (!value)
39085
39094
  continue;
39086
39095
  if (key === "name")
@@ -39745,8 +39754,8 @@ function createInstructionManifest(name, options) {
39745
39754
  description: options.description,
39746
39755
  version: PORTABLE_SKILL_DEFAULT_VERSION,
39747
39756
  displayName: displayName(name),
39748
- category: "Development Tools",
39749
- tags: ["custom", name],
39757
+ category: options.category ?? "Development Tools",
39758
+ tags: options.tags ?? ["custom", name],
39750
39759
  kind: "instruction",
39751
39760
  inputs: [],
39752
39761
  commands: [],
@@ -39761,16 +39770,16 @@ function writeInstructionSkillTemplate(skillPath, manifest) {
39761
39770
  }
39762
39771
  function renderInstructionSkillMd(manifest) {
39763
39772
  const tags = manifest.tags?.length ? `tags:
39764
- ${manifest.tags.map((tag) => ` - ${tag}`).join(`
39773
+ ${manifest.tags.map((tag) => ` - ${yamlString(tag)}`).join(`
39765
39774
  `)}
39766
39775
  ` : "";
39767
39776
  return `---
39768
39777
  name: ${manifest.name}
39769
- description: ${manifest.description}
39778
+ description: ${yamlString(manifest.description)}
39770
39779
  kind: instruction
39771
39780
  version: ${manifest.version}
39772
39781
  source: custom
39773
- category: ${manifest.category ?? "Development Tools"}
39782
+ category: ${yamlString(manifest.category ?? "Development Tools")}
39774
39783
  ${tags}---
39775
39784
 
39776
39785
  # ${manifest.displayName ?? displayName(manifest.name)}
@@ -39791,8 +39800,8 @@ function createPortableManifest(name, options) {
39791
39800
  description: options.description,
39792
39801
  version: PORTABLE_SKILL_DEFAULT_VERSION,
39793
39802
  displayName: displayName(name),
39794
- category: "Development Tools",
39795
- tags: ["custom", name],
39803
+ category: options.category ?? "Development Tools",
39804
+ tags: options.tags ?? ["custom", name],
39796
39805
  inputs: DEFAULT_INPUTS,
39797
39806
  commands: [{
39798
39807
  name,
@@ -39983,10 +39992,13 @@ function isExcludedCopyEntry(name, isFirstSegment) {
39983
39992
  return true;
39984
39993
  return false;
39985
39994
  }
39995
+ function yamlString(value) {
39996
+ return JSON.stringify(value);
39997
+ }
39986
39998
  function renderSkillMd(manifest) {
39987
39999
  return `---
39988
40000
  name: ${manifest.name}
39989
- description: ${manifest.description}
40001
+ description: ${yamlString(manifest.description)}
39990
40002
  ---
39991
40003
 
39992
40004
  # ${manifest.displayName ?? displayName(manifest.name)}
@@ -40396,11 +40408,11 @@ function scaffoldPortableSkill(name, options = {}) {
40396
40408
  const kind = options.kind ?? "executable";
40397
40409
  const description = options.description ?? `${displayName(skillName)} skill`;
40398
40410
  if (kind === "instruction") {
40399
- const manifest2 = createInstructionManifest(skillName, { description });
40411
+ const manifest2 = createInstructionManifest(skillName, { description, category: options.category, tags: options.tags });
40400
40412
  writeInstructionSkillTemplate(skillPath, manifest2);
40401
40413
  return { name: skillName, path: skillPath, manifest: manifest2, created: true };
40402
40414
  }
40403
- const manifest = createPortableManifest(skillName, { description });
40415
+ const manifest = createPortableManifest(skillName, { description, category: options.category, tags: options.tags });
40404
40416
  writePortableSkillTemplate(skillPath, manifest);
40405
40417
  return { name: skillName, path: skillPath, manifest, created: true };
40406
40418
  }
@@ -48335,93 +48347,7 @@ function toV1BaseUrl(apiUrl) {
48335
48347
  url.pathname = `${path}/v1`;
48336
48348
  return url.toString().replace(/\/+$/, "");
48337
48349
  }
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;
48396
- try {
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]);
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;
48419
- }
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;
48350
+ var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM, CREDENTIAL_SEAL, AMBIENT_ENVIRONMENT, SECRETS_PACKAGE_SPECIFIER, requireSecretsSdk, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
48425
48351
  var init_transport = __esm(() => {
48426
48352
  CredentialResolutionError = class CredentialResolutionError extends Error {
48427
48353
  appName;
@@ -48454,16 +48380,6 @@ var init_transport = __esm(() => {
48454
48380
  requireSecretsSdk = createRequire(import.meta.url);
48455
48381
  ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
48456
48382
  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
48383
  IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
48468
48384
  AUTHORITY_OVERRIDE_HEADERS = new Set([
48469
48385
  "host",
@@ -48474,6 +48390,97 @@ var init_transport = __esm(() => {
48474
48390
  ]);
48475
48391
  });
48476
48392
 
48393
+ // src/lib/instance-credentials.ts
48394
+ import { closeSync as closeSync2, constants as constants2, fstatSync as fstatSync2, lstatSync as lstatSync3, openSync as openSync2, readSync } from "fs";
48395
+ function selectedSkillsProfile(env3, explicit) {
48396
+ const selected = explicit ?? env3.HASNA_PROFILE;
48397
+ if (selected === undefined)
48398
+ return null;
48399
+ const profile = selected.trim();
48400
+ if (!/^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/.test(profile))
48401
+ throw new Error("Invalid Skills credential profile");
48402
+ return profile;
48403
+ }
48404
+ function skillsProfileCredentialFiles(env3, explicit) {
48405
+ return credentialDiskSourceList("skills", env3, selectedSkillsProfile(env3, explicit)).map((source) => source.path);
48406
+ }
48407
+ function fileIdentity(file) {
48408
+ try {
48409
+ return lstatSync3(file);
48410
+ } catch (error) {
48411
+ if (["ENOENT", "ENOTDIR"].includes(error.code ?? ""))
48412
+ return null;
48413
+ throw new Error("Cannot inspect Skills instance configuration");
48414
+ }
48415
+ }
48416
+ function unchanged(before2, after2) {
48417
+ return before2 === null || after2 === null ? before2 === after2 : ["dev", "ino", "size", "mtimeMs", "ctimeMs", "mode", "uid"].every((key) => before2[key] === after2[key]);
48418
+ }
48419
+ function captureSkillsCredentialFiles(files) {
48420
+ const identities = files.map((file) => [file, fileIdentity(file)]);
48421
+ return () => {
48422
+ if (identities.some(([file, before2]) => !unchanged(before2, fileIdentity(file)))) {
48423
+ throw new Error("Skills instance configuration changed while resolving credentials; retry without sending a credential");
48424
+ }
48425
+ };
48426
+ }
48427
+ function readMetadataText(file) {
48428
+ let fd;
48429
+ try {
48430
+ fd = openSync2(file, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
48431
+ } catch (error) {
48432
+ if (["ENOENT", "ENOTDIR"].includes(error.code ?? ""))
48433
+ return null;
48434
+ throw new Error("Cannot safely read Skills instance configuration");
48435
+ }
48436
+ try {
48437
+ const before2 = fstatSync2(fd);
48438
+ const uid = process.getuid?.() ?? process.geteuid?.();
48439
+ if (!before2.isFile() || ![256, 384].includes(before2.mode & 4095) || uid !== undefined && before2.uid !== uid || before2.size > 64 * 1024) {
48440
+ throw new Error("Unsafe Skills instance configuration; expected a bounded owner-only regular file");
48441
+ }
48442
+ const bytes = Buffer.alloc(64 * 1024 + 1);
48443
+ let length = 0;
48444
+ while (length < bytes.length) {
48445
+ const count = readSync(fd, bytes, length, bytes.length - length, null);
48446
+ if (!count)
48447
+ break;
48448
+ length += count;
48449
+ }
48450
+ if (length > 64 * 1024 || !unchanged(before2, fstatSync2(fd)) || !unchanged(before2, fileIdentity(file))) {
48451
+ throw new Error("Skills instance configuration changed while reading");
48452
+ }
48453
+ return bytes.subarray(0, length).toString("utf8");
48454
+ } finally {
48455
+ closeSync2(fd);
48456
+ }
48457
+ }
48458
+ function readSkillsInstanceMetadata(file) {
48459
+ const text = readMetadataText(file);
48460
+ if (text === null)
48461
+ return {};
48462
+ const values2 = new Map;
48463
+ for (const line of text.split(/\r?\n/)) {
48464
+ const match = /^\s*(?:export\s+)?(HASNA_SKILLS_API_URL|SKILLS_API_URL|HASNA_SKILLS_BOUND_API_URL)\s*=\s*(.*)$/.exec(line);
48465
+ if (!match)
48466
+ continue;
48467
+ let value = match[2].trim();
48468
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))
48469
+ value = value.slice(1, -1);
48470
+ if (!value || /[\x00-\x20\x7f]/.test(value) || values2.has(match[1]))
48471
+ throw new Error("Invalid Skills instance configuration");
48472
+ values2.set(match[1], value);
48473
+ }
48474
+ const urls = [values2.get("HASNA_SKILLS_API_URL"), values2.get("SKILLS_API_URL")].filter(Boolean);
48475
+ if (new Set(urls).size > 1)
48476
+ throw new Error("Skills API URL aliases disagree");
48477
+ return { apiUrl: urls[0], binding: values2.get(SKILLS_BOUND_API_URL) };
48478
+ }
48479
+ var SKILLS_BOUND_API_URL = "HASNA_SKILLS_BOUND_API_URL";
48480
+ var init_instance_credentials = __esm(() => {
48481
+ init_transport();
48482
+ });
48483
+
48477
48484
  // src/lib/fleet-credentials.ts
48478
48485
  var exports_fleet_credentials = {};
48479
48486
  __export(exports_fleet_credentials, {
@@ -48481,6 +48488,7 @@ __export(exports_fleet_credentials, {
48481
48488
  skillsCredentialFiles: () => skillsCredentialFiles,
48482
48489
  skillsCredentialFilePath: () => skillsCredentialFilePath,
48483
48490
  resolveSkillsFleet: () => resolveSkillsFleet,
48491
+ resolveSkillsConnection: () => resolveSkillsConnection,
48484
48492
  resolveSkillsApiOrigin: () => resolveSkillsApiOrigin,
48485
48493
  resolveSkillsApiKey: () => resolveSkillsApiKey,
48486
48494
  resetLocalSkillsModeNotice: () => resetLocalSkillsModeNotice,
@@ -48498,9 +48506,6 @@ __export(exports_fleet_credentials, {
48498
48506
  SKILLS_API_KEY_ENV: () => SKILLS_API_KEY_ENV,
48499
48507
  MissingSkillsFleetError: () => MissingSkillsFleetError
48500
48508
  });
48501
- function isClientTransportConfigurationError(error) {
48502
- return error instanceof ClientTransportConfigurationError || typeof error === "object" && error !== null && error.name === "ClientTransportConfigurationError";
48503
- }
48504
48509
  function isCredentialResolutionError(error) {
48505
48510
  return error instanceof CredentialResolutionError || typeof error === "object" && error !== null && error.name === "CredentialResolutionError";
48506
48511
  }
@@ -48511,6 +48516,9 @@ function asSkillsFleetCredentialError(error) {
48511
48516
  }
48512
48517
  function normalizeSkillsApiOrigin(apiUrl) {
48513
48518
  const url = new URL(apiUrl);
48519
+ if (url.username || url.password || url.search || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) {
48520
+ throw new SkillsFleetCredentialError("A Skills API URL must use HTTPS (or loopback HTTP), without credentials, query or fragment", "INVALID_API_URL");
48521
+ }
48514
48522
  const pathname = url.pathname.replace(/\/+$/, "");
48515
48523
  if (pathname === "/api" || pathname === "/api/v1") {
48516
48524
  url.pathname = "/";
@@ -48521,11 +48529,24 @@ function normalizeSkillsApiOrigin(apiUrl) {
48521
48529
  }
48522
48530
  return url.toString().replace(/\/+$/, "");
48523
48531
  }
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 };
48532
+ function configuredSkillsApiUrl(env3 = process.env, keychain, profile) {
48533
+ const declared = SKILLS_API_URL_ENV_KEYS.filter((key) => env3[key] !== undefined).map((key) => ({ key, value: env3[key] }));
48534
+ for (const entry of declared) {
48535
+ if (!entry.value.trim() || /[\x00-\x1f\x7f]/.test(entry.value))
48536
+ throw new SkillsFleetCredentialError(`${entry.key} is blank or contains control characters`, "INVALID_API_URL");
48537
+ }
48538
+ const normalized = declared.map((entry) => ({ value: normalizeSkillsApiOrigin(entry.value), source: entry.key }));
48539
+ if (new Set(normalized.map((entry) => entry.value)).size > 1)
48540
+ throw new SkillsFleetCredentialError("Skills API URL aliases disagree", "INVALID_API_URL");
48541
+ if (normalized[0])
48542
+ return normalized[0];
48543
+ if (selectedSkillsProfile(env3, profile)) {
48544
+ for (const file of skillsProfileCredentialFiles(env3, profile)) {
48545
+ const metadata = readSkillsInstanceMetadata(file);
48546
+ if (metadata.apiUrl || metadata.binding)
48547
+ return { value: metadata.apiUrl ?? metadata.binding, source: file };
48548
+ }
48549
+ return { value: defaultFleetGatewayBaseUrl(SKILLS_APP), source: "default" };
48529
48550
  }
48530
48551
  const fromKeychain = keychainConfigValue(SKILLS_APP, env3, keychain);
48531
48552
  if (fromKeychain)
@@ -48539,7 +48560,7 @@ function configuredSkillsApiUrl(env3 = process.env, keychain) {
48539
48560
  return null;
48540
48561
  }
48541
48562
  function skillsCredentialFiles(env3 = process.env) {
48542
- return credentialDiskSources(SKILLS_APP, env3);
48563
+ return skillsProfileCredentialFiles(env3);
48543
48564
  }
48544
48565
  function skillsCredentialFilePath(env3 = process.env) {
48545
48566
  const paths = skillsCredentialFiles(env3);
@@ -48560,7 +48581,11 @@ function resetLocalSkillsModeNotice() {
48560
48581
  }
48561
48582
  function resolveSkillsFleet(env3 = process.env, options = {}) {
48562
48583
  try {
48563
- return resolveSkillsFleetOrThrow(env3, options);
48584
+ const snapshot = snapshotSkillsEnvironment(env3);
48585
+ const resolved = resolveSkillsFleetOrThrow(snapshot, snapshotSkillsOptions(env3, options));
48586
+ if (resolved.mode === "local" && env3 === process.env)
48587
+ noticeLocalSkillsMode();
48588
+ return resolved;
48564
48589
  } catch (error) {
48565
48590
  const translated = asSkillsFleetCredentialError(error);
48566
48591
  if (translated)
@@ -48568,38 +48593,46 @@ function resolveSkillsFleet(env3 = process.env, options = {}) {
48568
48593
  throw error;
48569
48594
  }
48570
48595
  }
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`);
48596
+ function snapshotSkillsEnvironment(env3) {
48597
+ const snapshot = {};
48598
+ for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(env3))) {
48599
+ if (!("value" in descriptor)) {
48600
+ if (/^(?:HASNA_|SKILLS_|HOME$|USER$)/.test(key))
48601
+ throw new SkillsFleetCredentialError("Accessor-backed Skills configuration is unsupported");
48602
+ continue;
48587
48603
  }
48588
- throw error;
48604
+ snapshot[key] = descriptor.value;
48589
48605
  }
48590
- const configured = configuredSkillsApiUrl(env3, options.credentials?.keychain);
48591
- const apiOrigin = configured ? normalizeSkillsApiOrigin(configured.value) : stripV1(resolution.baseUrl);
48606
+ return Object.freeze(snapshot);
48607
+ }
48608
+ function snapshotSkillsOptions(env3, options) {
48609
+ if (env3 !== process.env)
48610
+ return options;
48611
+ return { ...options, credentials: { ...options.credentials, keychain: {
48612
+ ...options.credentials?.keychain,
48613
+ enabled: options.credentials?.keychain?.enabled ?? true
48614
+ } } };
48615
+ }
48616
+ function resolveSkillsFleetOrThrow(env3, options) {
48617
+ const assertFilesUnchanged = captureSkillsCredentialFiles(skillsProfileCredentialFiles(env3, options.credentials?.profile));
48618
+ const configured = configuredSkillsApiUrl(env3, options.credentials?.keychain, options.credentials?.profile);
48592
48619
  const credential = resolveCredential(SKILLS_APP, env3, options.credentials);
48593
48620
  if (!credential) {
48594
- throw new SkillsFleetCredentialError(`A Skills authority resolved but no API key did. Sign in with: skills auth login`);
48621
+ if (!configured)
48622
+ return { mode: "local", apiOrigin: null, apiKey: null };
48623
+ throw new SkillsFleetCredentialError(`${configured.source} points this CLI at a Skills service but no API key resolved \u2014 refusing to run locally instead. ` + `Looked in hasna.credentials.skills.api-key, ${skillsCredentialFiles(env3).join(" or ") || "no credentials file"}, and ${SKILLS_API_KEY_ENV}. Sign in with: skills auth login`);
48595
48624
  }
48625
+ const apiOrigin = normalizeSkillsApiOrigin(configured?.value ?? defaultFleetGatewayBaseUrl(SKILLS_APP));
48626
+ toV1BaseUrl(apiOrigin);
48627
+ assertCredentialInstance(credential, apiOrigin, env3, options);
48628
+ assertFilesUnchanged();
48596
48629
  const base2 = {
48597
48630
  mode: "hosted",
48598
48631
  apiOrigin,
48599
- apiUrlSource: resolution.apiUrlSource ?? (configured?.source ?? "default"),
48600
- apiKeySource: resolution.apiKeySource ?? credential.source,
48601
- apiKeyTier: resolution.apiKeyTier,
48602
- warning: resolution.warning
48632
+ apiUrlSource: configured?.source ?? "default",
48633
+ apiKeySource: credential.source,
48634
+ apiKeyTier: credential.tier,
48635
+ warning: credential.warning
48603
48636
  };
48604
48637
  if (credential.tier === "pointer") {
48605
48638
  return { ...base2, apiKey: null, apiKeyPointer: credential };
@@ -48609,19 +48642,37 @@ function resolveSkillsFleetOrThrow(env3, options) {
48609
48642
  }
48610
48643
  return { ...base2, apiKey: credential.apiKey, apiKeyPointer: null };
48611
48644
  }
48645
+ function assertCredentialInstance(credential, apiOrigin, env3, options) {
48646
+ let bound;
48647
+ if (credential.tier === "disk" || credential.tier === "profile") {
48648
+ const metadata = readSkillsInstanceMetadata(credential.source);
48649
+ bound = metadata.binding ?? metadata.apiUrl ?? defaultFleetGatewayBaseUrl(SKILLS_APP);
48650
+ } else if (credential.tier === "keychain") {
48651
+ bound = keychainConfigValue(SKILLS_APP, env3, options.credentials?.keychain)?.value ?? defaultFleetGatewayBaseUrl(SKILLS_APP);
48652
+ }
48653
+ if (bound && normalizeSkillsApiOrigin(bound) !== apiOrigin) {
48654
+ throw new SkillsFleetCredentialError("The selected Skills API does not match this credential's instance. Select its profile or sign in to the new instance; no credential was sent.", "INSTANCE_CREDENTIAL_MISMATCH");
48655
+ }
48656
+ }
48612
48657
  async function resolveSkillsApiKey(env3 = process.env, options = {}) {
48613
- const fleet = resolveSkillsFleet(env3, options);
48658
+ return (await resolveSkillsConnection(env3, options))?.apiKey ?? null;
48659
+ }
48660
+ async function resolveSkillsConnection(env3 = process.env, options = {}) {
48661
+ const snapshotEnv = snapshotSkillsEnvironment(env3);
48662
+ const fleet = resolveSkillsFleet(snapshotEnv, snapshotSkillsOptions(env3, options));
48663
+ if (fleet.mode === "local" && env3 === process.env)
48664
+ noticeLocalSkillsMode();
48614
48665
  if (fleet.mode !== "hosted")
48615
48666
  return null;
48616
48667
  if (fleet.apiKey)
48617
- return fleet.apiKey;
48668
+ return { ...fleet, apiKey: fleet.apiKey };
48618
48669
  const pointer = fleet.apiKeyPointer;
48619
48670
  if (!pointer) {
48620
48671
  throw new SkillsFleetCredentialError(`A Skills authority resolved but no API key did. Sign in with: skills auth login`);
48621
48672
  }
48622
48673
  let completed;
48623
48674
  try {
48624
- completed = await completePointerCredential(SKILLS_APP, pointer, env3);
48675
+ completed = await completePointerCredential(SKILLS_APP, pointer, snapshotEnv);
48625
48676
  } catch (error) {
48626
48677
  const translated = asSkillsFleetCredentialError(error);
48627
48678
  if (translated)
@@ -48631,7 +48682,7 @@ async function resolveSkillsApiKey(env3 = process.env, options = {}) {
48631
48682
  if (!completed.apiKey?.trim()) {
48632
48683
  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
48684
  }
48634
- return completed.apiKey;
48685
+ return { ...fleet, apiKey: completed.apiKey, apiKeyPointer: null };
48635
48686
  }
48636
48687
  async function requireSkillsApiKey(action = "This command", env3 = process.env, options = {}) {
48637
48688
  const apiKey = await resolveSkillsApiKey(env3, options);
@@ -48639,22 +48690,19 @@ async function requireSkillsApiKey(action = "This command", env3 = process.env,
48639
48690
  throw new MissingSkillsFleetError(action);
48640
48691
  return apiKey;
48641
48692
  }
48642
- function stripV1(baseUrl) {
48643
- return baseUrl.replace(/\/v1$/, "").replace(/\/+$/, "");
48644
- }
48645
48693
  async function skillsCredentialOrReason(env3 = process.env, options = {}) {
48646
48694
  try {
48647
- const apiKey = await resolveSkillsApiKey(env3, options);
48648
- return apiKey ? { apiKey, reason: null } : { apiKey: null, reason: null };
48695
+ const connection = await resolveSkillsConnection(env3, options);
48696
+ return connection ? { apiKey: connection.apiKey, apiOrigin: connection.apiOrigin, reason: null } : { apiKey: null, apiOrigin: null, reason: null };
48649
48697
  } catch (error) {
48650
48698
  if (error instanceof SkillsFleetCredentialError || error?.name === "SkillsFleetCredentialError") {
48651
- return { apiKey: null, reason: error.message };
48699
+ return { apiKey: null, apiOrigin: null, reason: error.message };
48652
48700
  }
48653
48701
  throw error;
48654
48702
  }
48655
48703
  }
48656
48704
  function resolveSkillsApiOrigin(env3 = process.env, options = {}) {
48657
- const configured = configuredSkillsApiUrl(env3, options.credentials?.keychain);
48705
+ const configured = configuredSkillsApiUrl(env3, options.credentials?.keychain, options.credentials?.profile);
48658
48706
  if (configured) {
48659
48707
  toV1BaseUrl(configured.value);
48660
48708
  return { origin: normalizeSkillsApiOrigin(configured.value), source: configured.source };
@@ -48677,6 +48725,7 @@ function requireSkillsFleet(action = "This command", env3 = process.env, options
48677
48725
  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
48726
  var init_fleet_credentials = __esm(() => {
48679
48727
  init_transport();
48728
+ init_instance_credentials();
48680
48729
  ENV_KEYS = clientTransportEnvKeys(SKILLS_APP);
48681
48730
  SKILLS_API_URL_ENV_KEYS = ENV_KEYS.apiUrlKeys;
48682
48731
  SKILLS_API_KEY_ENV_KEYS = ENV_KEYS.apiKeyKeys;
@@ -48889,12 +48938,13 @@ var init_remote_registry = __esm(() => {
48889
48938
 
48890
48939
  // src/lib/auth-store.ts
48891
48940
  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";
48941
+ import { basename as basename4, dirname as dirname6, join as join16 } from "path";
48893
48942
  function getAuthFilePath(env3 = process.env) {
48894
48943
  return skillsCredentialFilePath(env3);
48895
48944
  }
48896
48945
  function getIdentityFilePath(env3 = process.env) {
48897
- return join16(dirname6(skillsCredentialFilePath(env3)), "identity.json");
48946
+ const file = skillsCredentialFilePath(env3);
48947
+ return join16(dirname6(file), basename4(file).replace(/^credentials/, "identity") + ".json");
48898
48948
  }
48899
48949
  function getAuthIdentity(env3 = process.env) {
48900
48950
  return readIdentity(env3);
@@ -48905,6 +48955,10 @@ function readIdentity(env3 = process.env) {
48905
48955
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
48906
48956
  return {};
48907
48957
  const record = parsed;
48958
+ const selected = resolveSkillsApiOrigin(env3)?.origin;
48959
+ const bound = typeof record.apiUrl === "string" ? record.apiUrl : readCredentialValue(SKILLS_BOUND_API_URL, env3) ?? readStoredApiUrl(env3) ?? defaultFleetGatewayBaseUrl("skills");
48960
+ if (selected && normalizeSkillsApiOrigin(bound) !== selected)
48961
+ return {};
48908
48962
  const identity2 = {};
48909
48963
  for (const field of ["email", "orgId", "orgSlug", "userId"]) {
48910
48964
  const value = record[field];
@@ -48982,14 +49036,15 @@ function readCredentialValue(key, env3 = process.env) {
48982
49036
  }
48983
49037
  return null;
48984
49038
  }
48985
- function saveAuthConfig(config, env3 = process.env) {
49039
+ function saveAuthConfig(config, env3 = process.env, authenticatedOrigin) {
48986
49040
  const apiKey = config.apiKey.trim();
48987
49041
  if (!apiKey)
48988
49042
  throw new Error("Refusing to store an empty Skills API key.");
48989
49043
  if (/[^\t\x20-\x7e]/.test(apiKey)) {
48990
49044
  throw new Error("Refusing to store a Skills API key containing control characters or non-ASCII bytes.");
48991
49045
  }
48992
- const file = writeCredentialValues({ [SKILLS_API_KEY_ENV]: apiKey }, env3);
49046
+ const apiUrl = authenticatedOrigin ? normalizeSkillsApiOrigin(authenticatedOrigin) : resolveSkillsApiOrigin(env3)?.origin ?? defaultFleetGatewayBaseUrl("skills");
49047
+ const file = writeCredentialValues({ SKILLS_API_KEY: null, SKILLS_API_URL: null, [SKILLS_API_KEY_ENV]: apiKey, [SKILLS_BOUND_API_URL]: apiUrl, [SKILLS_API_URL_ENV]: apiUrl }, env3);
48993
49048
  const identity2 = {};
48994
49049
  for (const field of ["email", "orgId", "orgSlug", "userId"]) {
48995
49050
  const value = config[field];
@@ -48998,8 +49053,9 @@ function saveAuthConfig(config, env3 = process.env) {
48998
49053
  }
48999
49054
  const identityFile = getIdentityFilePath(env3);
49000
49055
  if (Object.keys(identity2).length > 0) {
49001
- writeFileSync7(identityFile, JSON.stringify(identity2, null, 2) + `
49056
+ writeFileSync7(identityFile, JSON.stringify({ ...identity2, apiUrl }, null, 2) + `
49002
49057
  `, { mode: 384 });
49058
+ chmodSync(identityFile, 384);
49003
49059
  } else {
49004
49060
  try {
49005
49061
  unlinkSync(identityFile);
@@ -49008,23 +49064,29 @@ function saveAuthConfig(config, env3 = process.env) {
49008
49064
  return file;
49009
49065
  }
49010
49066
  function saveApiUrl(apiUrl, env3 = process.env) {
49011
- return writeCredentialValues({ [SKILLS_API_URL_ENV]: apiUrl }, env3);
49067
+ const next = apiUrl === null ? null : normalizeSkillsApiOrigin(apiUrl);
49068
+ const values2 = { [SKILLS_API_URL_ENV]: next, SKILLS_API_URL: null };
49069
+ if (!readCredentialValue(SKILLS_BOUND_API_URL, env3) && (readCredentialValue(SKILLS_API_KEY_ENV, env3) || readCredentialValue("SKILLS_API_KEY", env3))) {
49070
+ values2[SKILLS_BOUND_API_URL] = normalizeSkillsApiOrigin(readStoredApiUrl(env3) ?? defaultFleetGatewayBaseUrl("skills"));
49071
+ }
49072
+ return writeCredentialValues(values2, env3);
49012
49073
  }
49013
49074
  function readStoredApiUrl(env3 = process.env) {
49014
- return readCredentialValue(SKILLS_API_URL_ENV, env3);
49075
+ return readCredentialValue(SKILLS_API_URL_ENV, env3) ?? readCredentialValue("SKILLS_API_URL", env3);
49015
49076
  }
49016
49077
  function clearAuthConfig(env3 = process.env) {
49017
49078
  try {
49018
- writeCredentialValues({ [SKILLS_API_KEY_ENV]: null }, env3);
49079
+ writeCredentialValues({ [SKILLS_API_KEY_ENV]: null, SKILLS_API_KEY: null, [SKILLS_BOUND_API_URL]: null }, env3);
49019
49080
  } catch {}
49020
49081
  try {
49021
49082
  unlinkSync(getIdentityFilePath(env3));
49022
49083
  } catch {}
49023
49084
  let stillResolves;
49024
49085
  try {
49025
- stillResolves = resolveSkillsFleet(env3).mode === "hosted";
49086
+ stillResolves = resolveCredential("skills", env3) !== null;
49026
49087
  } catch {
49027
- stillResolves = true;
49088
+ const emptyProfile = selectedSkillsProfile(env3) && !env3.HASNA_SKILLS_API_KEY_OVERRIDE && !env3.HASNA_SKILLS_API_KEY_REF && !readCredentialValue(SKILLS_API_KEY_ENV, env3) && !readCredentialValue("SKILLS_API_KEY", env3);
49089
+ stillResolves = !emptyProfile;
49028
49090
  }
49029
49091
  return { stillResolves };
49030
49092
  }
@@ -49039,7 +49101,10 @@ function credentialFileMode(env3 = process.env) {
49039
49101
  }
49040
49102
  }
49041
49103
  var init_auth_store = __esm(() => {
49104
+ init_transport();
49105
+ init_instance_credentials();
49042
49106
  init_fleet_credentials();
49107
+ init_transport();
49043
49108
  init_fleet_credentials();
49044
49109
  });
49045
49110
 
@@ -49087,6 +49152,169 @@ function pickNumber(record, key) {
49087
49152
  }
49088
49153
  var REMOTE_SKILL_RUN_CONTRACT_VERSION = 1;
49089
49154
 
49155
+ // src/lib/remote-account.ts
49156
+ function creditCount(value) {
49157
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0 || value > 2147483647) {
49158
+ throw new Error("The Skills server returned an invalid credit count");
49159
+ }
49160
+ return value;
49161
+ }
49162
+ function parseRemoteRunQuote(value) {
49163
+ const quote = object(value);
49164
+ const pricing = object(quote.pricing);
49165
+ if (typeof quote.skill !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(quote.skill))
49166
+ throw new Error("Invalid quoted skill");
49167
+ const costCents = creditCount(pricing.costCredits ?? pricing.costCents);
49168
+ if (pricing.costCredits !== undefined && pricing.costCents !== undefined && pricing.costCents !== costCents)
49169
+ throw new Error("Inconsistent quoted credit count");
49170
+ if (quote.availability && object(quote.availability).status !== "available")
49171
+ throw new Error("This skill is unavailable for remote execution");
49172
+ return { ...quote, skill: quote.skill, pricing: { ...pricing, costCredits: costCents, costCents, formattedCost: `${costCents} credits` } };
49173
+ }
49174
+ function parseRemoteCreditPacks(value) {
49175
+ if (!Array.isArray(value))
49176
+ throw new Error("Invalid credit pack response");
49177
+ const ids = new Set;
49178
+ return value.map((value2) => {
49179
+ const row = object(value2);
49180
+ const counts = [row.credits, row.creditsCents, row.amountCents].filter((value3) => value3 !== undefined).map(creditCount);
49181
+ if (!counts.length || counts[0] === 0 || counts.some((count) => count !== counts[0]))
49182
+ throw new Error("Inconsistent credit pack counts");
49183
+ const credits = counts[0];
49184
+ const id = row.id ?? `credits_${credits}`;
49185
+ if (typeof id !== "string" || !/^[a-z0-9][a-z0-9_-]{0,99}$/.test(id) || ids.has(id))
49186
+ throw new Error("Invalid credit pack ID");
49187
+ if (id.startsWith("credits_") && id !== `credits_${credits}`)
49188
+ throw new Error("Inconsistent credit pack ID");
49189
+ ids.add(id);
49190
+ return { id, credits, ...row.expiresInDays === undefined ? {} : { expiresInDays: creditCount(row.expiresInDays) } };
49191
+ });
49192
+ }
49193
+ function parseRemoteBillingStatus(value) {
49194
+ const row = object(value);
49195
+ const counts = [row.creditBalance, row.balanceCents].filter((value2) => value2 !== undefined).map(creditCount);
49196
+ if (!counts.length || counts.some((count) => count !== counts[0]))
49197
+ throw new Error("Inconsistent credit balance");
49198
+ return {
49199
+ creditBalance: counts[0],
49200
+ formattedCreditBalance: `${counts[0]} credits`,
49201
+ ...typeof row.plan === "string" ? { plan: row.plan } : {},
49202
+ ...typeof row.hasPaymentMethod === "boolean" ? { hasPaymentMethod: row.hasPaymentMethod } : {}
49203
+ };
49204
+ }
49205
+ function parseRemoteCheckout(value) {
49206
+ const row = object(value);
49207
+ if (typeof row.url !== "string")
49208
+ throw new Error("Invalid checkout URL");
49209
+ const url = new URL(row.url);
49210
+ if (url.protocol !== "https:" || url.username || url.password)
49211
+ throw new Error("Invalid checkout URL");
49212
+ return { url: row.url };
49213
+ }
49214
+ function object(value) {
49215
+ if (!value || typeof value !== "object" || Array.isArray(value))
49216
+ throw new Error("Invalid Skills server response");
49217
+ return value;
49218
+ }
49219
+ var RemoteCreditApprovalError;
49220
+ var init_remote_account = __esm(() => {
49221
+ RemoteCreditApprovalError = class RemoteCreditApprovalError extends Error {
49222
+ requiredCredits;
49223
+ maximumCredits;
49224
+ code = "CREDIT_APPROVAL_REQUIRED";
49225
+ constructor(requiredCredits, maximumCredits) {
49226
+ super(`This run requires ${requiredCredits} credits; the approved maximum is ${maximumCredits}. Quote the run and explicitly approve its cost.`);
49227
+ this.requiredCredits = requiredCredits;
49228
+ this.maximumCredits = maximumCredits;
49229
+ this.name = "RemoteCreditApprovalError";
49230
+ }
49231
+ };
49232
+ });
49233
+
49234
+ // src/lib/remote-files.ts
49235
+ var exports_remote_files = {};
49236
+ __export(exports_remote_files, {
49237
+ sha256: () => sha256,
49238
+ readBoundedResponse: () => readBoundedResponse,
49239
+ describeRemoteFiles: () => describeRemoteFiles,
49240
+ decodeRemoteFiles: () => decodeRemoteFiles,
49241
+ MAX_REMOTE_FILE_BYTES: () => MAX_REMOTE_FILE_BYTES
49242
+ });
49243
+ import { createHash as createHash2 } from "crypto";
49244
+ function describeRemoteFiles(files) {
49245
+ if (files.length > 10)
49246
+ throw new Error("At most 10 input files are supported");
49247
+ const names = new Set;
49248
+ let total = 0;
49249
+ return files.map((file) => {
49250
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/.test(file.name) || file.name === "." || file.name === ".." || names.has(file.name))
49251
+ throw new Error("Input file names must be unique safe basenames");
49252
+ names.add(file.name);
49253
+ total += file.bytes.byteLength;
49254
+ if (file.bytes.byteLength > 20 * 1024 * 1024 || total > 50 * 1024 * 1024)
49255
+ throw new Error("Input files exceed the supported size limit");
49256
+ return { name: file.name, sizeBytes: file.bytes.byteLength, sha256: sha256(file.bytes), contentType: file.contentType ?? "application/octet-stream" };
49257
+ });
49258
+ }
49259
+ function sha256(bytes) {
49260
+ return createHash2("sha256").update(bytes).digest("hex");
49261
+ }
49262
+ async function readBoundedResponse(response, maximum) {
49263
+ if (!Number.isSafeInteger(maximum) || maximum < 0 || maximum > MAX_REMOTE_FILE_BYTES)
49264
+ throw new Error("Invalid artifact size limit");
49265
+ const length = response.headers.get("content-length");
49266
+ if (length && (!/^\d+$/.test(length) || Number(length) > maximum)) {
49267
+ await response.body?.cancel();
49268
+ throw new Error("Artifact exceeds its declared size limit");
49269
+ }
49270
+ const reader = response.body?.getReader();
49271
+ if (!reader)
49272
+ return new Uint8Array;
49273
+ const chunks = [];
49274
+ let size2 = 0;
49275
+ try {
49276
+ while (true) {
49277
+ const next = await reader.read();
49278
+ if (next.done)
49279
+ break;
49280
+ size2 += next.value.byteLength;
49281
+ if (size2 > maximum)
49282
+ throw new Error("Artifact exceeds its declared size limit");
49283
+ chunks.push(next.value);
49284
+ }
49285
+ } catch (error) {
49286
+ await reader.cancel().catch(() => {});
49287
+ throw error;
49288
+ } finally {
49289
+ reader.releaseLock();
49290
+ }
49291
+ const bytes = new Uint8Array(size2);
49292
+ let offset = 0;
49293
+ for (const chunk2 of chunks) {
49294
+ bytes.set(chunk2, offset);
49295
+ offset += chunk2.byteLength;
49296
+ }
49297
+ return bytes;
49298
+ }
49299
+ function decodeRemoteFiles(files) {
49300
+ let total = 0;
49301
+ const decoded = files.map((file) => {
49302
+ if (file.base64.length > 1398104 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(file.base64))
49303
+ throw new Error("Invalid inline input encoding");
49304
+ const bytes = Buffer.from(file.base64, "base64");
49305
+ total += bytes.byteLength;
49306
+ if (total > 1024 * 1024 || bytes.toString("base64") !== file.base64)
49307
+ throw new Error("Inline inputs must total at most 1 MiB");
49308
+ return { name: file.name, bytes, contentType: file.contentType };
49309
+ });
49310
+ describeRemoteFiles(decoded);
49311
+ return decoded;
49312
+ }
49313
+ var MAX_REMOTE_FILE_BYTES;
49314
+ var init_remote_files = __esm(() => {
49315
+ MAX_REMOTE_FILE_BYTES = 64 * 1024 * 1024;
49316
+ });
49317
+
49090
49318
  // src/lib/remote-client.ts
49091
49319
  var exports_remote_client = {};
49092
49320
  __export(exports_remote_client, {
@@ -49100,13 +49328,16 @@ __export(exports_remote_client, {
49100
49328
  class RemoteSkillsClient {
49101
49329
  apiUrl;
49102
49330
  apiKey;
49331
+ capabilities;
49103
49332
  constructor(apiKey, apiUrl = getApiUrl()) {
49104
49333
  this.apiKey = apiKey;
49105
- this.apiUrl = apiUrl.replace(/\/$/, "");
49334
+ this.apiUrl = normalizeSkillsApiOrigin(apiUrl);
49106
49335
  }
49107
49336
  async request(path, options) {
49108
49337
  return fetch(`${this.apiUrl}${path}`, {
49109
49338
  ...options,
49339
+ redirect: "error",
49340
+ signal: options?.signal ?? AbortSignal.timeout(15000),
49110
49341
  headers: {
49111
49342
  Authorization: `Bearer ${this.apiKey}`,
49112
49343
  "Content-Type": "application/json",
@@ -49129,8 +49360,7 @@ class RemoteSkillsClient {
49129
49360
  return response;
49130
49361
  }
49131
49362
  async listSkills() {
49132
- const res = await this.request("/api/v1/skills");
49133
- return res.json();
49363
+ return this.arrayResponse("/api/v1/skills");
49134
49364
  }
49135
49365
  async getSkillMd(slug) {
49136
49366
  const res = await this.request(`/api/v1/skills/${slug}/skill.md`);
@@ -49152,39 +49382,217 @@ class RemoteSkillsClient {
49152
49382
  } catch {}
49153
49383
  return { status: res.status, body };
49154
49384
  }
49155
- async submitRun(slug, input, args) {
49156
- const res = await this.request(`/api/v1/runs/${slug}`, {
49385
+ async submitRun(slug, input, args, approval = {}) {
49386
+ if (approval.idempotencyKey !== undefined && !/^[A-Za-z0-9._:-]{1,128}$/.test(approval.idempotencyKey))
49387
+ throw new Error("Idempotency key must be 1-128 URL-safe characters");
49388
+ if (approval.maxCostCents !== undefined)
49389
+ creditCount(approval.maxCostCents);
49390
+ if (approval.maxCredits !== undefined)
49391
+ creditCount(approval.maxCredits);
49392
+ if (approval.maxCredits !== undefined && approval.maxCostCents !== undefined && approval.maxCredits !== approval.maxCostCents)
49393
+ throw new Error("Credit approval fields disagree");
49394
+ const res = await this.request(`/api/v1/runs/${encodeURIComponent(slug)}`, {
49157
49395
  method: "POST",
49158
- body: JSON.stringify({ input, args })
49396
+ body: JSON.stringify({
49397
+ input,
49398
+ args,
49399
+ ...approval.maxCredits !== undefined ? { maxCredits: approval.maxCredits } : {},
49400
+ ...approval.maxCostCents !== undefined ? { maxCostCents: approval.maxCostCents } : {},
49401
+ ...approval.idempotencyKey !== undefined ? { idempotencyKey: approval.idempotencyKey } : {},
49402
+ ...approval.inputFiles !== undefined ? { files: approval.inputFiles } : {}
49403
+ })
49159
49404
  });
49160
49405
  return normalizeRemoteSkillRunContract(await res.json(), slug);
49161
49406
  }
49407
+ async quoteRun(slug, input = {}, args = []) {
49408
+ const response = await this.requestNewRoute(`/api/v1/skills/${encodeURIComponent(slug)}/quote`, {
49409
+ method: "POST",
49410
+ body: JSON.stringify({ input, args })
49411
+ });
49412
+ return parseRemoteRunQuote(await response.json());
49413
+ }
49414
+ getCapabilities() {
49415
+ if (!this.capabilities)
49416
+ this.capabilities = (async () => {
49417
+ const value = await (await this.requestNewRoute("/api/v1/capabilities")).json();
49418
+ if (value.contractVersion !== 1 || value.apiVersion !== 1 || !Array.isArray(value.capabilities) || value.capabilities.some((item) => typeof item !== "string"))
49419
+ throw new Error("Unsupported Skills server capability contract");
49420
+ const billing = value.billing;
49421
+ return { contractVersion: 1, apiVersion: 1, capabilities: value.capabilities, ...billing ? { billing } : {} };
49422
+ })();
49423
+ return this.capabilities;
49424
+ }
49425
+ async submitQuotedRun(slug, input = {}, args = [], approval = {}) {
49426
+ const maximum = creditCount(approval.maxCredits ?? approval.maxCostCents ?? 0);
49427
+ if (approval.maxCostCents !== undefined && approval.maxCostCents !== maximum)
49428
+ throw new Error("Credit approval fields disagree");
49429
+ const quote = await this.quoteRun(slug, input, args);
49430
+ if (quote.pricing.costCents > maximum)
49431
+ throw new RemoteCreditApprovalError(quote.pricing.costCents, maximum);
49432
+ const capabilities = await this.getCapabilities();
49433
+ if (!capabilities.capabilities.includes("runs.submit") || capabilities.billing?.boundedRunApproval !== true || capabilities.billing.unit !== "credits") {
49434
+ throw new Error("The configured server does not support bounded credit approval; refusing remote submission");
49435
+ }
49436
+ return this.submitRun(quote.skill, input, args, { ...approval, maxCredits: maximum, maxCostCents: maximum });
49437
+ }
49438
+ async getIdentity() {
49439
+ return (await this.requestNewRoute("/api/auth/whoami")).json();
49440
+ }
49441
+ async listApiKeys() {
49442
+ return this.arrayResponse("/api/auth/keys");
49443
+ }
49444
+ async createApiKey(name, scopes) {
49445
+ if (!name.trim() || name.length > 100)
49446
+ throw new Error("API key name must be 1-100 characters");
49447
+ const value = await (await this.requestNewRoute("/api/auth/keys", { method: "POST", body: JSON.stringify({ name, ...scopes ? { scopes } : {} }) })).json();
49448
+ if (!value || typeof value.key !== "string" || !value.key.trim())
49449
+ throw new Error("The server did not return a created API key");
49450
+ return value;
49451
+ }
49452
+ async revokeApiKey(keyId) {
49453
+ return (await this.requestNewRoute(`/api/auth/keys/${encodeURIComponent(keyId)}`, { method: "DELETE" })).json();
49454
+ }
49455
+ async getBillingStatus() {
49456
+ return parseRemoteBillingStatus(await (await this.requestNewRoute("/api/v1/billing/status")).json());
49457
+ }
49458
+ async listCreditPacks() {
49459
+ return parseRemoteCreditPacks(await (await this.requestNewRoute("/api/v1/billing/credits")).json());
49460
+ }
49461
+ async createCreditCheckout(packId) {
49462
+ const packs = await this.listCreditPacks();
49463
+ if (!packs.some((pack) => pack.id === packId))
49464
+ throw new Error("Choose a credit pack returned by skills credits packs");
49465
+ return parseRemoteCheckout(await (await this.requestNewRoute("/api/v1/billing/credits", {
49466
+ method: "POST",
49467
+ body: JSON.stringify({ packId })
49468
+ })).json());
49469
+ }
49470
+ async getUsage() {
49471
+ return this.arrayResponse("/api/v1/billing/usage");
49472
+ }
49473
+ async listInvoices() {
49474
+ return this.arrayResponse("/api/v1/billing/invoices");
49475
+ }
49476
+ async createBillingCheckout() {
49477
+ return this.checkoutResponse("/api/v1/billing/checkout");
49478
+ }
49479
+ async createBillingPortal() {
49480
+ return this.checkoutResponse("/api/v1/billing/portal");
49481
+ }
49482
+ async cancelRun(runId) {
49483
+ return this.controlRun(runId, "cancel");
49484
+ }
49485
+ async resumeRun(runId) {
49486
+ return this.controlRun(runId, "resume");
49487
+ }
49488
+ async controlRun(runId, action) {
49489
+ const response = await this.requestNewRoute(`/api/v1/runs/${encodeURIComponent(runId)}/${action}`, { method: "POST", body: "{}" });
49490
+ return normalizeRemoteSkillRunContract(await response.json());
49491
+ }
49492
+ async checkoutResponse(path) {
49493
+ return parseRemoteCheckout(await (await this.requestNewRoute(path, { method: "POST", body: "{}" })).json());
49494
+ }
49495
+ async arrayResponse(path) {
49496
+ const rows = await (await this.requestNewRoute(path)).json();
49497
+ if (!Array.isArray(rows) || rows.some((row) => !row || typeof row !== "object" || Array.isArray(row)))
49498
+ throw new Error("Invalid Skills server list response");
49499
+ return rows;
49500
+ }
49162
49501
  async getRun(runId) {
49163
- const res = await this.request(`/api/v1/runs/${runId}`);
49164
- if (!res.ok)
49502
+ const path = `/api/v1/runs/${encodeURIComponent(runId)}`;
49503
+ const res = await this.request(path);
49504
+ if (res.status === 404)
49165
49505
  return null;
49506
+ if (!res.ok)
49507
+ throw new RemoteRequestError(path, res.status, res.statusText);
49166
49508
  return normalizeRemoteSkillRunContract(await res.json());
49167
49509
  }
49168
49510
  async getRunLogs(runId) {
49169
- const res = await this.request(`/api/v1/runs/${runId}/logs`);
49170
- if (!res.ok)
49171
- return [];
49172
- const payload = await res.json();
49173
- return Array.isArray(payload) ? payload : [];
49511
+ return this.arrayResponse(`/api/v1/runs/${encodeURIComponent(runId)}/logs`);
49174
49512
  }
49175
49513
  async listRuns(limit = 20) {
49176
- const res = await this.request(`/api/v1/runs?limit=${limit}`);
49177
- return res.json();
49514
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100)
49515
+ throw new Error("Run limit must be an integer from 1 to 100");
49516
+ return this.arrayResponse(`/api/v1/runs?limit=${limit}`);
49178
49517
  }
49179
49518
  async getRunArtifacts(runId) {
49180
- const res = await this.request(`/api/v1/runs/${runId}/artifacts`);
49181
- return res.json();
49519
+ return this.arrayResponse(`/api/v1/runs/${encodeURIComponent(runId)}/artifacts`);
49182
49520
  }
49183
49521
  async downloadRunArtifact(runId, artifactId) {
49184
- return this.request(`/api/v1/runs/${runId}/artifacts/${artifactId}/download`, {
49522
+ return this.request(`/api/v1/runs/${encodeURIComponent(runId)}/artifacts/${encodeURIComponent(artifactId)}/download`, {
49185
49523
  method: "GET"
49186
49524
  });
49187
49525
  }
49526
+ async getVerifiedRunArtifact(runId, artifactId, maximumBytes = MAX_REMOTE_FILE_BYTES) {
49527
+ const artifacts = await this.getRunArtifacts(runId);
49528
+ const artifact = artifacts.find((row) => row.id === artifactId);
49529
+ if (!artifact)
49530
+ throw new Error("Run artifact not found");
49531
+ if (!Number.isSafeInteger(artifact.byteSize) || artifact.byteSize < 0 || artifact.byteSize > maximumBytes || typeof artifact.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(artifact.sha256))
49532
+ throw new Error("The server does not provide valid artifact integrity metadata");
49533
+ const response = await this.downloadRunArtifact(runId, artifactId);
49534
+ if (!response.ok)
49535
+ throw new RemoteRequestError("artifact download", response.status, response.statusText);
49536
+ const bytes = await readBoundedResponse(response, artifact.byteSize);
49537
+ if (bytes.byteLength !== artifact.byteSize || sha256(bytes) !== artifact.sha256)
49538
+ throw new Error("Artifact integrity verification failed");
49539
+ return { id: artifactId, fileName: String(artifact.fileName ?? artifactId), bytes, byteSize: bytes.byteLength, sha256: artifact.sha256 };
49540
+ }
49541
+ async submitQuotedRunWithFiles(slug, input, args, files, approval = {}) {
49542
+ const inputFiles = describeRemoteFiles(files);
49543
+ if (files.length && !(await this.getCapabilities()).capabilities.includes("runs.uploads"))
49544
+ throw new Error("The configured server does not support input uploads");
49545
+ const run = await this.submitQuotedRun(slug, input, args, { ...approval, inputFiles });
49546
+ if (run.error || !run.id || !files.length)
49547
+ return run;
49548
+ const pastUploads = (status) => typeof status === "string" && [
49549
+ "running",
49550
+ "completed",
49551
+ "failed",
49552
+ "cancelled",
49553
+ "expired",
49554
+ "pending_approval",
49555
+ "approved",
49556
+ "waiting"
49557
+ ].includes(status);
49558
+ if (pastUploads(run.status))
49559
+ return run;
49560
+ try {
49561
+ await this.uploadRunFiles(run.id, files);
49562
+ } catch {
49563
+ try {
49564
+ const current = await this.getRun(run.id);
49565
+ if (current && pastUploads(current.status))
49566
+ return current;
49567
+ } catch {}
49568
+ let cancellationRequested = false;
49569
+ try {
49570
+ await this.cancelRun(run.id);
49571
+ cancellationRequested = true;
49572
+ } catch {}
49573
+ throw new Error(`Input upload failed for run ${run.id}; ${cancellationRequested ? "cancellation requested" : "check its status and cancel the run"}`);
49574
+ }
49575
+ return run;
49576
+ }
49577
+ async uploadRunFiles(runId, files) {
49578
+ const descriptors = describeRemoteFiles(files);
49579
+ const response = await this.requestNewRoute(`/api/v1/runs/${encodeURIComponent(runId)}/uploads`, { method: "POST", body: JSON.stringify({ files: descriptors }) });
49580
+ const payload = await response.json();
49581
+ if (!Array.isArray(payload.files) || payload.files.length !== files.length || new Set(payload.files.map((file) => file.name)).size !== files.length)
49582
+ throw new Error("Invalid input upload response");
49583
+ for (const file of files) {
49584
+ const upload = payload.files.find((row) => row.name === file.name);
49585
+ if (!upload)
49586
+ throw new Error("Missing input upload URL");
49587
+ const url = new URL(upload.uploadUrl);
49588
+ if (url.username || url.password || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)))
49589
+ throw new Error("Unsafe input upload URL");
49590
+ const uploaded = await fetch(url, { method: "PUT", body: file.bytes, headers: { "Content-Type": file.contentType ?? "application/octet-stream" }, redirect: "error", signal: AbortSignal.timeout(60000) });
49591
+ if (!uploaded.ok)
49592
+ throw new Error("Input upload failed");
49593
+ await uploaded.body?.cancel();
49594
+ }
49595
+ }
49188
49596
  async publishSkill(manifest, bundle, ifMatch) {
49189
49597
  const form = new FormData;
49190
49598
  form.set("manifest", JSON.stringify(manifest));
@@ -49197,7 +49605,9 @@ class RemoteSkillsClient {
49197
49605
  return fetch(`${this.apiUrl}/api/v1/skills`, {
49198
49606
  method: "POST",
49199
49607
  headers,
49200
- body: form
49608
+ body: form,
49609
+ redirect: "error",
49610
+ signal: AbortSignal.timeout(15000)
49201
49611
  });
49202
49612
  }
49203
49613
  async deleteSkill(slug) {
@@ -49253,12 +49663,13 @@ class RemoteSkillsClient {
49253
49663
  if (!Array.isArray(payload)) {
49254
49664
  throw new Error("Remote tags payload did not match the expected contract (expected an array of tag names)");
49255
49665
  }
49256
- for (const tag of payload) {
49257
- if (typeof tag !== "string" || tag.trim().length === 0) {
49258
- throw new Error("Remote tags payload did not match the expected contract (every element must be a non-empty tag name)");
49259
- }
49666
+ const isName = (value) => typeof value === "string" && value.trim().length > 0;
49667
+ if (payload.every(isName))
49668
+ return payload;
49669
+ if (payload.every((tag) => tag !== null && typeof tag === "object" && !Array.isArray(tag) && isName(tag.name) && Number.isSafeInteger(tag.count) && tag.count >= 0)) {
49670
+ return payload.map((tag) => tag.name);
49260
49671
  }
49261
- return payload;
49672
+ throw new Error("Remote tags payload did not match the expected contract (every element must be a non-empty tag name, or every element must be a counted tag record)");
49262
49673
  }
49263
49674
  async skillsByTag(tag) {
49264
49675
  const path = `/api/v1/tags/${encodeURIComponent(tag)}/skills`;
@@ -49361,14 +49772,8 @@ function normalizeUpdatedSincePage(payload) {
49361
49772
  return { skills, nextCursor };
49362
49773
  }
49363
49774
  async function createRemoteSkillsClient(env3 = process.env) {
49364
- const fleet = resolveSkillsFleet(env3);
49365
- if (fleet.mode !== "hosted")
49366
- return null;
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);
49775
+ const connection = await resolveSkillsConnection(env3);
49776
+ return connection ? new RemoteSkillsClient(connection.apiKey, connection.apiOrigin) : null;
49372
49777
  }
49373
49778
  function createRemoteSkillsClientReadOnly(env3 = process.env) {
49374
49779
  return createRemoteSkillsClient(env3);
@@ -49377,6 +49782,8 @@ var RemoteRouteUnsupportedError, RemoteRequestError;
49377
49782
  var init_remote_client = __esm(() => {
49378
49783
  init_auth_store();
49379
49784
  init_fleet_credentials();
49785
+ init_remote_account();
49786
+ init_remote_files();
49380
49787
  RemoteRouteUnsupportedError = class RemoteRouteUnsupportedError extends Error {
49381
49788
  path;
49382
49789
  status;
@@ -49402,7 +49809,7 @@ var init_remote_client = __esm(() => {
49402
49809
  });
49403
49810
 
49404
49811
  // src/lib/revision.ts
49405
- import { createHash as createHash2 } from "crypto";
49812
+ import { createHash as createHash3 } from "crypto";
49406
49813
  function revisionIdOf(content) {
49407
49814
  const canonical = JSON.stringify({
49408
49815
  slug: content.slug,
@@ -49417,7 +49824,7 @@ function revisionIdOf(content) {
49417
49824
  bundleSha256: content.bundleSha256 ?? null,
49418
49825
  bundleByteSize: content.bundleByteSize ?? null
49419
49826
  });
49420
- return createHash2("sha256").update(canonical).digest("hex");
49827
+ return createHash3("sha256").update(canonical).digest("hex");
49421
49828
  }
49422
49829
  var REVISION_ID_PATTERN;
49423
49830
  var init_revision = __esm(() => {
@@ -49425,7 +49832,7 @@ var init_revision = __esm(() => {
49425
49832
  });
49426
49833
 
49427
49834
  // src/lib/skill-bundle.ts
49428
- import { createHash as createHash3 } from "crypto";
49835
+ import { createHash as createHash4 } from "crypto";
49429
49836
  import { readFileSync as readFileSync13, readdirSync as readdirSync9, statSync as statSync10 } from "fs";
49430
49837
  import { join as join17, relative as relative2 } from "path";
49431
49838
  function isDotenvFile(lower) {
@@ -49456,7 +49863,7 @@ function ownBytes(view) {
49456
49863
  return out;
49457
49864
  }
49458
49865
  function sha256Hex(bytes) {
49459
- return createHash3("sha256").update(bytes).digest("hex");
49866
+ return createHash4("sha256").update(bytes).digest("hex");
49460
49867
  }
49461
49868
  function collectSkillBundleEntries(dir) {
49462
49869
  const entries = [];
@@ -53188,7 +53595,7 @@ var init_runs = __esm(() => {
53188
53595
  });
53189
53596
 
53190
53597
  // src/lib/run-state.ts
53191
- import { createHash as createHash4, randomBytes } from "crypto";
53598
+ import { createHash as createHash5, randomBytes } from "crypto";
53192
53599
  import { existsSync as existsSync23, mkdirSync as mkdirSync10, readFileSync as readFileSync21, readdirSync as readdirSync13, statSync as statSync14, writeFileSync as writeFileSync12 } from "fs";
53193
53600
  import { extname, join as join25, relative as relative3 } from "path";
53194
53601
  function createSkillRun(params, targetDir = process.cwd()) {
@@ -53212,6 +53619,7 @@ function createSkillRun(params, targetDir = process.cwd()) {
53212
53619
  startedAt: now3.toISOString(),
53213
53620
  remote: params.remote ?? false,
53214
53621
  ...params.remoteRunId ? { remoteRunId: params.remoteRunId } : {},
53622
+ ...params.remoteApiOrigin ? { remoteApiOrigin: params.remoteApiOrigin } : {},
53215
53623
  ...params.costCents !== undefined ? { costCents: params.costCents } : {},
53216
53624
  artifacts: [],
53217
53625
  paths: {
@@ -53324,7 +53732,7 @@ function collectRunArtifacts(context) {
53324
53732
  artifacts.push({
53325
53733
  path: toProjectRelative(context.targetDir, path),
53326
53734
  mime: mimeForPath(path),
53327
- sha256: createHash4("sha256").update(bytes).digest("hex"),
53735
+ sha256: createHash5("sha256").update(bytes).digest("hex"),
53328
53736
  sizeBytes: stat.size
53329
53737
  });
53330
53738
  }
@@ -53561,19 +53969,19 @@ function floatSafeRemainder2(val, step) {
53561
53969
  const stepInt = Number.parseInt(step.toFixed(decCount).replace(".", ""));
53562
53970
  return valInt % stepInt / 10 ** decCount;
53563
53971
  }
53564
- function defineLazy(object, key, getter) {
53972
+ function defineLazy(object2, key, getter) {
53565
53973
  const set2 = false;
53566
- Object.defineProperty(object, key, {
53974
+ Object.defineProperty(object2, key, {
53567
53975
  get() {
53568
53976
  if (!set2) {
53569
53977
  const value = getter();
53570
- object[key] = value;
53978
+ object2[key] = value;
53571
53979
  return value;
53572
53980
  }
53573
53981
  throw new Error("cached value already set");
53574
53982
  },
53575
53983
  set(v) {
53576
- Object.defineProperty(object, key, {
53984
+ Object.defineProperty(object2, key, {
53577
53985
  value: v
53578
53986
  });
53579
53987
  },
@@ -57357,7 +57765,7 @@ function never(params) {
57357
57765
  function array(element, params) {
57358
57766
  return _array(ZodArray2, element, params);
57359
57767
  }
57360
- function object(shape, params) {
57768
+ function object2(shape, params) {
57361
57769
  const def = {
57362
57770
  type: "object",
57363
57771
  get shape() {
@@ -58000,30 +58408,30 @@ var init_types2 = __esm(() => {
58000
58408
  ttl: number2().optional(),
58001
58409
  pollInterval: number2().optional()
58002
58410
  });
58003
- TaskMetadataSchema = object({
58411
+ TaskMetadataSchema = object2({
58004
58412
  ttl: number2().optional()
58005
58413
  });
58006
- RelatedTaskMetadataSchema = object({
58414
+ RelatedTaskMetadataSchema = object2({
58007
58415
  taskId: string2()
58008
58416
  });
58009
58417
  RequestMetaSchema = looseObject({
58010
58418
  progressToken: ProgressTokenSchema.optional(),
58011
58419
  [RELATED_TASK_META_KEY]: RelatedTaskMetadataSchema.optional()
58012
58420
  });
58013
- BaseRequestParamsSchema = object({
58421
+ BaseRequestParamsSchema = object2({
58014
58422
  _meta: RequestMetaSchema.optional()
58015
58423
  });
58016
58424
  TaskAugmentedRequestParamsSchema = BaseRequestParamsSchema.extend({
58017
58425
  task: TaskMetadataSchema.optional()
58018
58426
  });
58019
- RequestSchema = object({
58427
+ RequestSchema = object2({
58020
58428
  method: string2(),
58021
58429
  params: BaseRequestParamsSchema.loose().optional()
58022
58430
  });
58023
- NotificationsParamsSchema = object({
58431
+ NotificationsParamsSchema = object2({
58024
58432
  _meta: RequestMetaSchema.optional()
58025
58433
  });
58026
- NotificationSchema = object({
58434
+ NotificationSchema = object2({
58027
58435
  method: string2(),
58028
58436
  params: NotificationsParamsSchema.loose().optional()
58029
58437
  });
@@ -58031,16 +58439,16 @@ var init_types2 = __esm(() => {
58031
58439
  _meta: RequestMetaSchema.optional()
58032
58440
  });
58033
58441
  RequestIdSchema = union2([string2(), number2().int()]);
58034
- JSONRPCRequestSchema = object({
58442
+ JSONRPCRequestSchema = object2({
58035
58443
  jsonrpc: literal(JSONRPC_VERSION),
58036
58444
  id: RequestIdSchema,
58037
58445
  ...RequestSchema.shape
58038
58446
  }).strict();
58039
- JSONRPCNotificationSchema = object({
58447
+ JSONRPCNotificationSchema = object2({
58040
58448
  jsonrpc: literal(JSONRPC_VERSION),
58041
58449
  ...NotificationSchema.shape
58042
58450
  }).strict();
58043
- JSONRPCResultResponseSchema = object({
58451
+ JSONRPCResultResponseSchema = object2({
58044
58452
  jsonrpc: literal(JSONRPC_VERSION),
58045
58453
  id: RequestIdSchema,
58046
58454
  result: ResultSchema
@@ -58055,10 +58463,10 @@ var init_types2 = __esm(() => {
58055
58463
  ErrorCode2[ErrorCode2["InternalError"] = -32603] = "InternalError";
58056
58464
  ErrorCode2[ErrorCode2["UrlElicitationRequired"] = -32042] = "UrlElicitationRequired";
58057
58465
  })(ErrorCode || (ErrorCode = {}));
58058
- JSONRPCErrorResponseSchema = object({
58466
+ JSONRPCErrorResponseSchema = object2({
58059
58467
  jsonrpc: literal(JSONRPC_VERSION),
58060
58468
  id: RequestIdSchema.optional(),
58061
- error: object({
58469
+ error: object2({
58062
58470
  code: number2().int(),
58063
58471
  message: string2(),
58064
58472
  data: unknown().optional()
@@ -58080,16 +58488,16 @@ var init_types2 = __esm(() => {
58080
58488
  method: literal("notifications/cancelled"),
58081
58489
  params: CancelledNotificationParamsSchema
58082
58490
  });
58083
- IconSchema = object({
58491
+ IconSchema = object2({
58084
58492
  src: string2(),
58085
58493
  mimeType: string2().optional(),
58086
58494
  sizes: array(string2()).optional(),
58087
58495
  theme: _enum(["light", "dark"]).optional()
58088
58496
  });
58089
- IconsSchema = object({
58497
+ IconsSchema = object2({
58090
58498
  icons: array(IconSchema).optional()
58091
58499
  });
58092
- BaseMetadataSchema = object({
58500
+ BaseMetadataSchema = object2({
58093
58501
  name: string2(),
58094
58502
  title: string2().optional()
58095
58503
  });
@@ -58100,7 +58508,7 @@ var init_types2 = __esm(() => {
58100
58508
  websiteUrl: string2().optional(),
58101
58509
  description: string2().optional()
58102
58510
  });
58103
- FormElicitationCapabilitySchema = intersection2(object({
58511
+ FormElicitationCapabilitySchema = intersection2(object2({
58104
58512
  applyDefaults: boolean2().optional()
58105
58513
  }), record(string2(), unknown()));
58106
58514
  ElicitationCapabilitySchema = preprocess((value) => {
@@ -58110,7 +58518,7 @@ var init_types2 = __esm(() => {
58110
58518
  }
58111
58519
  }
58112
58520
  return value;
58113
- }, intersection2(object({
58521
+ }, intersection2(object2({
58114
58522
  form: FormElicitationCapabilitySchema.optional(),
58115
58523
  url: AssertObjectSchema.optional()
58116
58524
  }), record(string2(), unknown()).optional()));
@@ -58135,14 +58543,14 @@ var init_types2 = __esm(() => {
58135
58543
  }).optional()
58136
58544
  }).optional()
58137
58545
  });
58138
- ClientCapabilitiesSchema = object({
58546
+ ClientCapabilitiesSchema = object2({
58139
58547
  experimental: record(string2(), AssertObjectSchema).optional(),
58140
- sampling: object({
58548
+ sampling: object2({
58141
58549
  context: AssertObjectSchema.optional(),
58142
58550
  tools: AssertObjectSchema.optional()
58143
58551
  }).optional(),
58144
58552
  elicitation: ElicitationCapabilitySchema.optional(),
58145
- roots: object({
58553
+ roots: object2({
58146
58554
  listChanged: boolean2().optional()
58147
58555
  }).optional(),
58148
58556
  tasks: ClientTasksCapabilitySchema.optional(),
@@ -58157,18 +58565,18 @@ var init_types2 = __esm(() => {
58157
58565
  method: literal("initialize"),
58158
58566
  params: InitializeRequestParamsSchema
58159
58567
  });
58160
- ServerCapabilitiesSchema = object({
58568
+ ServerCapabilitiesSchema = object2({
58161
58569
  experimental: record(string2(), AssertObjectSchema).optional(),
58162
58570
  logging: AssertObjectSchema.optional(),
58163
58571
  completions: AssertObjectSchema.optional(),
58164
- prompts: object({
58572
+ prompts: object2({
58165
58573
  listChanged: boolean2().optional()
58166
58574
  }).optional(),
58167
- resources: object({
58575
+ resources: object2({
58168
58576
  subscribe: boolean2().optional(),
58169
58577
  listChanged: boolean2().optional()
58170
58578
  }).optional(),
58171
- tools: object({
58579
+ tools: object2({
58172
58580
  listChanged: boolean2().optional()
58173
58581
  }).optional(),
58174
58582
  tasks: ServerTasksCapabilitySchema.optional(),
@@ -58188,12 +58596,12 @@ var init_types2 = __esm(() => {
58188
58596
  method: literal("ping"),
58189
58597
  params: BaseRequestParamsSchema.optional()
58190
58598
  });
58191
- ProgressSchema = object({
58599
+ ProgressSchema = object2({
58192
58600
  progress: number2(),
58193
58601
  total: optional(number2()),
58194
58602
  message: optional(string2())
58195
58603
  });
58196
- ProgressNotificationParamsSchema = object({
58604
+ ProgressNotificationParamsSchema = object2({
58197
58605
  ...NotificationsParamsSchema.shape,
58198
58606
  ...ProgressSchema.shape,
58199
58607
  progressToken: ProgressTokenSchema
@@ -58212,7 +58620,7 @@ var init_types2 = __esm(() => {
58212
58620
  nextCursor: CursorSchema.optional()
58213
58621
  });
58214
58622
  TaskStatusSchema = _enum(["working", "input_required", "completed", "failed", "cancelled"]);
58215
- TaskSchema = object({
58623
+ TaskSchema = object2({
58216
58624
  taskId: string2(),
58217
58625
  status: TaskStatusSchema,
58218
58626
  ttl: union2([number2(), _null3()]),
@@ -58256,7 +58664,7 @@ var init_types2 = __esm(() => {
58256
58664
  })
58257
58665
  });
58258
58666
  CancelTaskResultSchema = ResultSchema.merge(TaskSchema);
58259
- ResourceContentsSchema = object({
58667
+ ResourceContentsSchema = object2({
58260
58668
  uri: string2(),
58261
58669
  mimeType: optional(string2()),
58262
58670
  _meta: record(string2(), unknown()).optional()
@@ -58276,12 +58684,12 @@ var init_types2 = __esm(() => {
58276
58684
  blob: Base64Schema
58277
58685
  });
58278
58686
  RoleSchema = _enum(["user", "assistant"]);
58279
- AnnotationsSchema = object({
58687
+ AnnotationsSchema = object2({
58280
58688
  audience: array(RoleSchema).optional(),
58281
58689
  priority: number2().min(0).max(1).optional(),
58282
58690
  lastModified: exports_iso.datetime({ offset: true }).optional()
58283
58691
  });
58284
- ResourceSchema = object({
58692
+ ResourceSchema = object2({
58285
58693
  ...BaseMetadataSchema.shape,
58286
58694
  ...IconsSchema.shape,
58287
58695
  uri: string2(),
@@ -58291,7 +58699,7 @@ var init_types2 = __esm(() => {
58291
58699
  annotations: AnnotationsSchema.optional(),
58292
58700
  _meta: optional(looseObject({}))
58293
58701
  });
58294
- ResourceTemplateSchema = object({
58702
+ ResourceTemplateSchema = object2({
58295
58703
  ...BaseMetadataSchema.shape,
58296
58704
  ...IconsSchema.shape,
58297
58705
  uriTemplate: string2(),
@@ -58344,12 +58752,12 @@ var init_types2 = __esm(() => {
58344
58752
  method: literal("notifications/resources/updated"),
58345
58753
  params: ResourceUpdatedNotificationParamsSchema
58346
58754
  });
58347
- PromptArgumentSchema = object({
58755
+ PromptArgumentSchema = object2({
58348
58756
  name: string2(),
58349
58757
  description: optional(string2()),
58350
58758
  required: optional(boolean2())
58351
58759
  });
58352
- PromptSchema = object({
58760
+ PromptSchema = object2({
58353
58761
  ...BaseMetadataSchema.shape,
58354
58762
  ...IconsSchema.shape,
58355
58763
  description: optional(string2()),
@@ -58370,34 +58778,34 @@ var init_types2 = __esm(() => {
58370
58778
  method: literal("prompts/get"),
58371
58779
  params: GetPromptRequestParamsSchema
58372
58780
  });
58373
- TextContentSchema = object({
58781
+ TextContentSchema = object2({
58374
58782
  type: literal("text"),
58375
58783
  text: string2(),
58376
58784
  annotations: AnnotationsSchema.optional(),
58377
58785
  _meta: record(string2(), unknown()).optional()
58378
58786
  });
58379
- ImageContentSchema = object({
58787
+ ImageContentSchema = object2({
58380
58788
  type: literal("image"),
58381
58789
  data: Base64Schema,
58382
58790
  mimeType: string2(),
58383
58791
  annotations: AnnotationsSchema.optional(),
58384
58792
  _meta: record(string2(), unknown()).optional()
58385
58793
  });
58386
- AudioContentSchema = object({
58794
+ AudioContentSchema = object2({
58387
58795
  type: literal("audio"),
58388
58796
  data: Base64Schema,
58389
58797
  mimeType: string2(),
58390
58798
  annotations: AnnotationsSchema.optional(),
58391
58799
  _meta: record(string2(), unknown()).optional()
58392
58800
  });
58393
- ToolUseContentSchema = object({
58801
+ ToolUseContentSchema = object2({
58394
58802
  type: literal("tool_use"),
58395
58803
  name: string2(),
58396
58804
  id: string2(),
58397
58805
  input: record(string2(), unknown()),
58398
58806
  _meta: record(string2(), unknown()).optional()
58399
58807
  });
58400
- EmbeddedResourceSchema = object({
58808
+ EmbeddedResourceSchema = object2({
58401
58809
  type: literal("resource"),
58402
58810
  resource: union2([TextResourceContentsSchema, BlobResourceContentsSchema]),
58403
58811
  annotations: AnnotationsSchema.optional(),
@@ -58413,7 +58821,7 @@ var init_types2 = __esm(() => {
58413
58821
  ResourceLinkSchema,
58414
58822
  EmbeddedResourceSchema
58415
58823
  ]);
58416
- PromptMessageSchema = object({
58824
+ PromptMessageSchema = object2({
58417
58825
  role: RoleSchema,
58418
58826
  content: ContentBlockSchema
58419
58827
  });
@@ -58425,26 +58833,26 @@ var init_types2 = __esm(() => {
58425
58833
  method: literal("notifications/prompts/list_changed"),
58426
58834
  params: NotificationsParamsSchema.optional()
58427
58835
  });
58428
- ToolAnnotationsSchema = object({
58836
+ ToolAnnotationsSchema = object2({
58429
58837
  title: string2().optional(),
58430
58838
  readOnlyHint: boolean2().optional(),
58431
58839
  destructiveHint: boolean2().optional(),
58432
58840
  idempotentHint: boolean2().optional(),
58433
58841
  openWorldHint: boolean2().optional()
58434
58842
  });
58435
- ToolExecutionSchema = object({
58843
+ ToolExecutionSchema = object2({
58436
58844
  taskSupport: _enum(["required", "optional", "forbidden"]).optional()
58437
58845
  });
58438
- ToolSchema = object({
58846
+ ToolSchema = object2({
58439
58847
  ...BaseMetadataSchema.shape,
58440
58848
  ...IconsSchema.shape,
58441
58849
  description: string2().optional(),
58442
- inputSchema: object({
58850
+ inputSchema: object2({
58443
58851
  type: literal("object"),
58444
58852
  properties: record(string2(), AssertObjectSchema).optional(),
58445
58853
  required: array(string2()).optional()
58446
58854
  }).catchall(unknown()),
58447
- outputSchema: object({
58855
+ outputSchema: object2({
58448
58856
  type: literal("object"),
58449
58857
  properties: record(string2(), AssertObjectSchema).optional(),
58450
58858
  required: array(string2()).optional()
@@ -58479,7 +58887,7 @@ var init_types2 = __esm(() => {
58479
58887
  method: literal("notifications/tools/list_changed"),
58480
58888
  params: NotificationsParamsSchema.optional()
58481
58889
  });
58482
- ListChangedOptionsBaseSchema = object({
58890
+ ListChangedOptionsBaseSchema = object2({
58483
58891
  autoRefresh: boolean2().default(true),
58484
58892
  debounceMs: number2().int().nonnegative().default(300)
58485
58893
  });
@@ -58500,23 +58908,23 @@ var init_types2 = __esm(() => {
58500
58908
  method: literal("notifications/message"),
58501
58909
  params: LoggingMessageNotificationParamsSchema
58502
58910
  });
58503
- ModelHintSchema = object({
58911
+ ModelHintSchema = object2({
58504
58912
  name: string2().optional()
58505
58913
  });
58506
- ModelPreferencesSchema = object({
58914
+ ModelPreferencesSchema = object2({
58507
58915
  hints: array(ModelHintSchema).optional(),
58508
58916
  costPriority: number2().min(0).max(1).optional(),
58509
58917
  speedPriority: number2().min(0).max(1).optional(),
58510
58918
  intelligencePriority: number2().min(0).max(1).optional()
58511
58919
  });
58512
- ToolChoiceSchema = object({
58920
+ ToolChoiceSchema = object2({
58513
58921
  mode: _enum(["auto", "required", "none"]).optional()
58514
58922
  });
58515
- ToolResultContentSchema = object({
58923
+ ToolResultContentSchema = object2({
58516
58924
  type: literal("tool_result"),
58517
58925
  toolUseId: string2().describe("The unique identifier for the corresponding tool call."),
58518
58926
  content: array(ContentBlockSchema).default([]),
58519
- structuredContent: object({}).loose().optional(),
58927
+ structuredContent: object2({}).loose().optional(),
58520
58928
  isError: boolean2().optional(),
58521
58929
  _meta: record(string2(), unknown()).optional()
58522
58930
  });
@@ -58528,7 +58936,7 @@ var init_types2 = __esm(() => {
58528
58936
  ToolUseContentSchema,
58529
58937
  ToolResultContentSchema
58530
58938
  ]);
58531
- SamplingMessageSchema = object({
58939
+ SamplingMessageSchema = object2({
58532
58940
  role: RoleSchema,
58533
58941
  content: union2([SamplingMessageContentBlockSchema, array(SamplingMessageContentBlockSchema)]),
58534
58942
  _meta: record(string2(), unknown()).optional()
@@ -58561,13 +58969,13 @@ var init_types2 = __esm(() => {
58561
58969
  role: RoleSchema,
58562
58970
  content: union2([SamplingMessageContentBlockSchema, array(SamplingMessageContentBlockSchema)])
58563
58971
  });
58564
- BooleanSchemaSchema = object({
58972
+ BooleanSchemaSchema = object2({
58565
58973
  type: literal("boolean"),
58566
58974
  title: string2().optional(),
58567
58975
  description: string2().optional(),
58568
58976
  default: boolean2().optional()
58569
58977
  });
58570
- StringSchemaSchema = object({
58978
+ StringSchemaSchema = object2({
58571
58979
  type: literal("string"),
58572
58980
  title: string2().optional(),
58573
58981
  description: string2().optional(),
@@ -58576,7 +58984,7 @@ var init_types2 = __esm(() => {
58576
58984
  format: _enum(["email", "uri", "date", "date-time"]).optional(),
58577
58985
  default: string2().optional()
58578
58986
  });
58579
- NumberSchemaSchema = object({
58987
+ NumberSchemaSchema = object2({
58580
58988
  type: _enum(["number", "integer"]),
58581
58989
  title: string2().optional(),
58582
58990
  description: string2().optional(),
@@ -58584,24 +58992,24 @@ var init_types2 = __esm(() => {
58584
58992
  maximum: number2().optional(),
58585
58993
  default: number2().optional()
58586
58994
  });
58587
- UntitledSingleSelectEnumSchemaSchema = object({
58995
+ UntitledSingleSelectEnumSchemaSchema = object2({
58588
58996
  type: literal("string"),
58589
58997
  title: string2().optional(),
58590
58998
  description: string2().optional(),
58591
58999
  enum: array(string2()),
58592
59000
  default: string2().optional()
58593
59001
  });
58594
- TitledSingleSelectEnumSchemaSchema = object({
59002
+ TitledSingleSelectEnumSchemaSchema = object2({
58595
59003
  type: literal("string"),
58596
59004
  title: string2().optional(),
58597
59005
  description: string2().optional(),
58598
- oneOf: array(object({
59006
+ oneOf: array(object2({
58599
59007
  const: string2(),
58600
59008
  title: string2()
58601
59009
  })),
58602
59010
  default: string2().optional()
58603
59011
  });
58604
- LegacyTitledEnumSchemaSchema = object({
59012
+ LegacyTitledEnumSchemaSchema = object2({
58605
59013
  type: literal("string"),
58606
59014
  title: string2().optional(),
58607
59015
  description: string2().optional(),
@@ -58610,26 +59018,26 @@ var init_types2 = __esm(() => {
58610
59018
  default: string2().optional()
58611
59019
  });
58612
59020
  SingleSelectEnumSchemaSchema = union2([UntitledSingleSelectEnumSchemaSchema, TitledSingleSelectEnumSchemaSchema]);
58613
- UntitledMultiSelectEnumSchemaSchema = object({
59021
+ UntitledMultiSelectEnumSchemaSchema = object2({
58614
59022
  type: literal("array"),
58615
59023
  title: string2().optional(),
58616
59024
  description: string2().optional(),
58617
59025
  minItems: number2().optional(),
58618
59026
  maxItems: number2().optional(),
58619
- items: object({
59027
+ items: object2({
58620
59028
  type: literal("string"),
58621
59029
  enum: array(string2())
58622
59030
  }),
58623
59031
  default: array(string2()).optional()
58624
59032
  });
58625
- TitledMultiSelectEnumSchemaSchema = object({
59033
+ TitledMultiSelectEnumSchemaSchema = object2({
58626
59034
  type: literal("array"),
58627
59035
  title: string2().optional(),
58628
59036
  description: string2().optional(),
58629
59037
  minItems: number2().optional(),
58630
59038
  maxItems: number2().optional(),
58631
- items: object({
58632
- anyOf: array(object({
59039
+ items: object2({
59040
+ anyOf: array(object2({
58633
59041
  const: string2(),
58634
59042
  title: string2()
58635
59043
  }))
@@ -58642,7 +59050,7 @@ var init_types2 = __esm(() => {
58642
59050
  ElicitRequestFormParamsSchema = TaskAugmentedRequestParamsSchema.extend({
58643
59051
  mode: literal("form").optional(),
58644
59052
  message: string2(),
58645
- requestedSchema: object({
59053
+ requestedSchema: object2({
58646
59054
  type: literal("object"),
58647
59055
  properties: record(string2(), PrimitiveSchemaDefinitionSchema),
58648
59056
  required: array(string2()).optional()
@@ -58670,21 +59078,21 @@ var init_types2 = __esm(() => {
58670
59078
  action: _enum(["accept", "decline", "cancel"]),
58671
59079
  content: preprocess((val) => val === null ? undefined : val, record(string2(), union2([string2(), number2(), boolean2(), array(string2())])).optional())
58672
59080
  });
58673
- ResourceTemplateReferenceSchema = object({
59081
+ ResourceTemplateReferenceSchema = object2({
58674
59082
  type: literal("ref/resource"),
58675
59083
  uri: string2()
58676
59084
  });
58677
- PromptReferenceSchema = object({
59085
+ PromptReferenceSchema = object2({
58678
59086
  type: literal("ref/prompt"),
58679
59087
  name: string2()
58680
59088
  });
58681
59089
  CompleteRequestParamsSchema = BaseRequestParamsSchema.extend({
58682
59090
  ref: union2([PromptReferenceSchema, ResourceTemplateReferenceSchema]),
58683
- argument: object({
59091
+ argument: object2({
58684
59092
  name: string2(),
58685
59093
  value: string2()
58686
59094
  }),
58687
- context: object({
59095
+ context: object2({
58688
59096
  arguments: record(string2(), string2()).optional()
58689
59097
  }).optional()
58690
59098
  });
@@ -58699,7 +59107,7 @@ var init_types2 = __esm(() => {
58699
59107
  hasMore: optional(boolean2())
58700
59108
  })
58701
59109
  });
58702
- RootSchema = object({
59110
+ RootSchema = object2({
58703
59111
  uri: string2().startsWith("file://"),
58704
59112
  name: string2().optional(),
58705
59113
  _meta: record(string2(), unknown()).optional()
@@ -58938,7 +59346,7 @@ var init_parse3 = __esm(() => {
58938
59346
  });
58939
59347
 
58940
59348
  // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v4/mini/schemas.js
58941
- function object2(shape, params) {
59349
+ function object3(shape, params) {
58942
59350
  const def = {
58943
59351
  type: "object",
58944
59352
  get shape() {
@@ -59025,11 +59433,11 @@ function isZ4Schema(s) {
59025
59433
  function objectFromShape(shape) {
59026
59434
  const values2 = Object.values(shape);
59027
59435
  if (values2.length === 0)
59028
- return object2({});
59436
+ return object3({});
59029
59437
  const allV4 = values2.every(isZ4Schema);
59030
59438
  const allV3 = values2.every((s) => !isZ4Schema(s));
59031
59439
  if (allV4)
59032
- return object2(shape);
59440
+ return object3(shape);
59033
59441
  if (allV3)
59034
59442
  return objectType(shape);
59035
59443
  throw new Error("Mixed Zod versions detected in object shape.");
@@ -59916,11 +60324,11 @@ var init_map = __esm(() => {
59916
60324
 
59917
60325
  // ../../node_modules/.bun/zod-to-json-schema@3.25.2+27912429049419a2/node_modules/zod-to-json-schema/dist/esm/parsers/nativeEnum.js
59918
60326
  function parseNativeEnumDef(def) {
59919
- const object3 = def.values;
60327
+ const object4 = def.values;
59920
60328
  const actualKeys = Object.keys(def.values).filter((key) => {
59921
- return typeof object3[object3[key]] !== "number";
60329
+ return typeof object4[object4[key]] !== "number";
59922
60330
  });
59923
- const actualValues = actualKeys.map((key) => object3[key]);
60331
+ const actualValues = actualKeys.map((key) => object4[key]);
59924
60332
  const parsedTypes = Array.from(new Set(actualValues.map((values2) => typeof values2)));
59925
60333
  return {
59926
60334
  type: parsedTypes.length === 1 ? parsedTypes[0] === "string" ? "string" : "number" : ["string", "number"],
@@ -61856,11 +62264,11 @@ var require_codegen = __commonJS((exports) => {
61856
62264
  const rhs = this.rhs === undefined ? "" : ` = ${this.rhs}`;
61857
62265
  return `${varKind} ${this.name}${rhs};` + _n;
61858
62266
  }
61859
- optimizeNames(names, constants2) {
62267
+ optimizeNames(names, constants3) {
61860
62268
  if (!names[this.name.str])
61861
62269
  return;
61862
62270
  if (this.rhs)
61863
- this.rhs = optimizeExpr(this.rhs, names, constants2);
62271
+ this.rhs = optimizeExpr(this.rhs, names, constants3);
61864
62272
  return this;
61865
62273
  }
61866
62274
  get names() {
@@ -61878,10 +62286,10 @@ var require_codegen = __commonJS((exports) => {
61878
62286
  render({ _n }) {
61879
62287
  return `${this.lhs} = ${this.rhs};` + _n;
61880
62288
  }
61881
- optimizeNames(names, constants2) {
62289
+ optimizeNames(names, constants3) {
61882
62290
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
61883
62291
  return;
61884
- this.rhs = optimizeExpr(this.rhs, names, constants2);
62292
+ this.rhs = optimizeExpr(this.rhs, names, constants3);
61885
62293
  return this;
61886
62294
  }
61887
62295
  get names() {
@@ -61947,8 +62355,8 @@ var require_codegen = __commonJS((exports) => {
61947
62355
  optimizeNodes() {
61948
62356
  return `${this.code}` ? this : undefined;
61949
62357
  }
61950
- optimizeNames(names, constants2) {
61951
- this.code = optimizeExpr(this.code, names, constants2);
62358
+ optimizeNames(names, constants3) {
62359
+ this.code = optimizeExpr(this.code, names, constants3);
61952
62360
  return this;
61953
62361
  }
61954
62362
  get names() {
@@ -61978,12 +62386,12 @@ var require_codegen = __commonJS((exports) => {
61978
62386
  }
61979
62387
  return nodes.length > 0 ? this : undefined;
61980
62388
  }
61981
- optimizeNames(names, constants2) {
62389
+ optimizeNames(names, constants3) {
61982
62390
  const { nodes } = this;
61983
62391
  let i = nodes.length;
61984
62392
  while (i--) {
61985
62393
  const n = nodes[i];
61986
- if (n.optimizeNames(names, constants2))
62394
+ if (n.optimizeNames(names, constants3))
61987
62395
  continue;
61988
62396
  subtractNames(names, n.names);
61989
62397
  nodes.splice(i, 1);
@@ -62040,12 +62448,12 @@ var require_codegen = __commonJS((exports) => {
62040
62448
  return;
62041
62449
  return this;
62042
62450
  }
62043
- optimizeNames(names, constants2) {
62451
+ optimizeNames(names, constants3) {
62044
62452
  var _a;
62045
- this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names, constants2);
62046
- if (!(super.optimizeNames(names, constants2) || this.else))
62453
+ this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names, constants3);
62454
+ if (!(super.optimizeNames(names, constants3) || this.else))
62047
62455
  return;
62048
- this.condition = optimizeExpr(this.condition, names, constants2);
62456
+ this.condition = optimizeExpr(this.condition, names, constants3);
62049
62457
  return this;
62050
62458
  }
62051
62459
  get names() {
@@ -62070,10 +62478,10 @@ var require_codegen = __commonJS((exports) => {
62070
62478
  render(opts) {
62071
62479
  return `for(${this.iteration})` + super.render(opts);
62072
62480
  }
62073
- optimizeNames(names, constants2) {
62074
- if (!super.optimizeNames(names, constants2))
62481
+ optimizeNames(names, constants3) {
62482
+ if (!super.optimizeNames(names, constants3))
62075
62483
  return;
62076
- this.iteration = optimizeExpr(this.iteration, names, constants2);
62484
+ this.iteration = optimizeExpr(this.iteration, names, constants3);
62077
62485
  return this;
62078
62486
  }
62079
62487
  get names() {
@@ -62111,10 +62519,10 @@ var require_codegen = __commonJS((exports) => {
62111
62519
  render(opts) {
62112
62520
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
62113
62521
  }
62114
- optimizeNames(names, constants2) {
62115
- if (!super.optimizeNames(names, constants2))
62522
+ optimizeNames(names, constants3) {
62523
+ if (!super.optimizeNames(names, constants3))
62116
62524
  return;
62117
- this.iterable = optimizeExpr(this.iterable, names, constants2);
62525
+ this.iterable = optimizeExpr(this.iterable, names, constants3);
62118
62526
  return this;
62119
62527
  }
62120
62528
  get names() {
@@ -62159,11 +62567,11 @@ var require_codegen = __commonJS((exports) => {
62159
62567
  (_b = this.finally) === null || _b === undefined || _b.optimizeNodes();
62160
62568
  return this;
62161
62569
  }
62162
- optimizeNames(names, constants2) {
62570
+ optimizeNames(names, constants3) {
62163
62571
  var _a, _b;
62164
- super.optimizeNames(names, constants2);
62165
- (_a = this.catch) === null || _a === undefined || _a.optimizeNames(names, constants2);
62166
- (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants2);
62572
+ super.optimizeNames(names, constants3);
62573
+ (_a = this.catch) === null || _a === undefined || _a.optimizeNames(names, constants3);
62574
+ (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants3);
62167
62575
  return this;
62168
62576
  }
62169
62577
  get names() {
@@ -62437,7 +62845,7 @@ var require_codegen = __commonJS((exports) => {
62437
62845
  function addExprNames(names, from) {
62438
62846
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
62439
62847
  }
62440
- function optimizeExpr(expr, names, constants2) {
62848
+ function optimizeExpr(expr, names, constants3) {
62441
62849
  if (expr instanceof code_1.Name)
62442
62850
  return replaceName(expr);
62443
62851
  if (!canOptimize(expr))
@@ -62452,14 +62860,14 @@ var require_codegen = __commonJS((exports) => {
62452
62860
  return items;
62453
62861
  }, []));
62454
62862
  function replaceName(n) {
62455
- const c = constants2[n.str];
62863
+ const c = constants3[n.str];
62456
62864
  if (c === undefined || names[n.str] !== 1)
62457
62865
  return n;
62458
62866
  delete names[n.str];
62459
62867
  return c;
62460
62868
  }
62461
62869
  function canOptimize(e) {
62462
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== undefined);
62870
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants3[c.str] !== undefined);
62463
62871
  }
62464
62872
  }
62465
62873
  function subtractNames(names, from) {
@@ -69913,6 +70321,27 @@ var init_mcp = __esm(() => {
69913
70321
  };
69914
70322
  });
69915
70323
 
70324
+ // src/lib/remote-customer-operations.ts
70325
+ var REMOTE_CUSTOMER_OPERATIONS;
70326
+ var init_remote_customer_operations = __esm(() => {
70327
+ REMOTE_CUSTOMER_OPERATIONS = [
70328
+ { name: "get_account", title: "Get Account Identity", parameter: null, mutates: false, invoke: (client) => client.getIdentity() },
70329
+ { name: "get_server_capabilities", title: "Get Server Capabilities", parameter: null, mutates: false, invoke: (client) => client.getCapabilities() },
70330
+ { name: "list_remote_skills", title: "List Remote Skills", parameter: null, mutates: false, invoke: (client) => client.listSkills() },
70331
+ { name: "get_billing_status", title: "Get Billing Status", parameter: null, mutates: false, invoke: (client) => client.getBillingStatus() },
70332
+ { name: "list_credit_packs", title: "List Credit Packs", parameter: null, mutates: false, invoke: (client) => client.listCreditPacks() },
70333
+ { name: "create_credit_checkout", title: "Create Credit Checkout", parameter: "pack_id", mutates: true, invoke: (client, value) => client.createCreditCheckout(value) },
70334
+ { name: "get_billing_usage", title: "Get Billing Usage", parameter: null, mutates: false, invoke: (client) => client.getUsage() },
70335
+ { name: "list_invoices", title: "List Invoices", parameter: null, mutates: false, invoke: (client) => client.listInvoices() },
70336
+ { name: "create_billing_checkout", title: "Create Billing Checkout", parameter: null, mutates: true, invoke: (client) => client.createBillingCheckout() },
70337
+ { name: "create_billing_portal", title: "Create Billing Portal", parameter: null, mutates: true, invoke: (client) => client.createBillingPortal() },
70338
+ { name: "get_run_logs", title: "Get Run Logs", parameter: "run_id", mutates: false, invoke: (client, value) => client.getRunLogs(value) },
70339
+ { name: "cancel_run", title: "Cancel Run", parameter: "run_id", mutates: true, invoke: (client, value) => client.cancelRun(value) },
70340
+ { name: "resume_run", title: "Resume Run", parameter: "run_id", mutates: true, invoke: (client, value) => client.resumeRun(value) },
70341
+ { name: "list_run_artifacts", title: "List Run Artifacts", parameter: "run_id", mutates: false, invoke: (client, value) => client.getRunArtifacts(value) }
70342
+ ];
70343
+ });
70344
+
69916
70345
  // src/lib/mcp-contracts.ts
69917
70346
  function listMcpToolContracts(query) {
69918
70347
  const needle = query?.toLowerCase();
@@ -70054,8 +70483,9 @@ var MCP_CONTRACT_SCHEMA_VERSION = 1, stringSchema = (description) => ({
70054
70483
  type: "array",
70055
70484
  items,
70056
70485
  ...description ? { description } : {}
70057
- }), skillNameInput, optionalAgentInput, scopeInput, runInputSchema, runArgsSchema, errorSchema, skillSummarySchema, toolPrimitiveSummarySchema, skillToolDependencySchema, validationMessageSchema, validationOutputSchema, installOutputSchema, runOutputSchema, toolContracts, contracts, resourceContracts;
70486
+ }), skillNameInput, optionalAgentInput, scopeInput, runInputSchema, runArgsSchema, errorSchema, skillSummarySchema, toolPrimitiveSummarySchema, skillToolDependencySchema, validationMessageSchema, validationOutputSchema, installOutputSchema, runOutputSchema, toolContracts, remoteCustomerContracts, contracts, resourceContracts;
70058
70487
  var init_mcp_contracts = __esm(() => {
70488
+ init_remote_customer_operations();
70059
70489
  skillNameInput = stringSchema("skill name or alias.");
70060
70490
  optionalAgentInput = stringSchema("Optional target agent slug. Use MCP registration instead of direct skill-folder installs.");
70061
70491
  scopeInput = {
@@ -70424,11 +70854,44 @@ var init_mcp_contracts = __esm(() => {
70424
70854
  dependencies: objectSchema({}, [], "Package dependencies.", true)
70425
70855
  })
70426
70856
  },
70857
+ {
70858
+ name: "list_api_keys",
70859
+ title: "List API Keys",
70860
+ description: "List keys using fresh email OTP reauthentication.",
70861
+ params: ["email", "code"],
70862
+ category: "execution",
70863
+ sideEffects: "local-process-or-remote-run",
70864
+ stable: true,
70865
+ inputSchema: objectSchema({ email: { type: "string", format: "email" }, code: { type: "string", pattern: "^\\d{6}$" } }, ["email", "code"]),
70866
+ outputSchema: arraySchema(objectSchema({}, [], "API key metadata", true))
70867
+ },
70868
+ {
70869
+ name: "revoke_api_key",
70870
+ title: "Revoke API Key",
70871
+ description: "Revoke a key using fresh email OTP reauthentication.",
70872
+ params: ["key_id", "email", "code"],
70873
+ category: "execution",
70874
+ sideEffects: "local-process-or-remote-run",
70875
+ stable: true,
70876
+ inputSchema: objectSchema({ key_id: stringSchema("API key ID"), email: { type: "string", format: "email" }, code: { type: "string", pattern: "^\\d{6}$" } }, ["key_id", "email", "code"]),
70877
+ outputSchema: objectSchema({}, [], "Revocation result", true)
70878
+ },
70879
+ {
70880
+ name: "create_api_key",
70881
+ title: "Create API Key",
70882
+ description: "Create a key with fresh email OTP reauthentication; returns the secret once.",
70883
+ params: ["name", "email", "code", "scopes?"],
70884
+ category: "execution",
70885
+ sideEffects: "local-process-or-remote-run",
70886
+ stable: true,
70887
+ inputSchema: objectSchema({ name: stringSchema("Key name"), email: { type: "string", format: "email" }, code: { type: "string", pattern: "^\\d{6}$" }, scopes: arraySchema(stringSchema("Scope")) }, ["name", "email", "code"]),
70888
+ outputSchema: objectSchema({}, [], "Created key and one-time secret", true)
70889
+ },
70427
70890
  {
70428
70891
  name: "run_skill",
70429
70892
  title: "Run Skill",
70430
70893
  description: "Run a skill locally or through a configured remote runner. Returns compact stdout/stderr previews and run summaries by default; pass detail:true for full records.",
70431
- params: ["name", "input?", "args?", "detail?"],
70894
+ params: ["name", "input?", "args?", "detail?", "remote?", "maxCredits?", "maxCostCents?", "idempotency_key?", "files?"],
70432
70895
  category: "execution",
70433
70896
  sideEffects: "local-process-or-remote-run",
70434
70897
  stable: true,
@@ -70436,7 +70899,12 @@ var init_mcp_contracts = __esm(() => {
70436
70899
  name: skillNameInput,
70437
70900
  input: runInputSchema,
70438
70901
  args: runArgsSchema,
70439
- detail: { type: "boolean", default: false, description: "Return full stdout/stderr, remote run, and local run metadata." }
70902
+ detail: { type: "boolean", default: false, description: "Return full stdout/stderr, remote run, and local run metadata." },
70903
+ remote: { type: "boolean", description: "Use the configured server catalog." },
70904
+ maxCredits: { type: "integer", minimum: 0, description: "Maximum explicitly approved integer credits; omitted permits only free remote runs." },
70905
+ maxCostCents: { type: "integer", minimum: 0, description: "Legacy alias for maxCredits; both must agree." },
70906
+ idempotency_key: { type: "string", pattern: "^[A-Za-z0-9._:-]{1,128}$", description: "Stable retry key for the same remote submission." },
70907
+ files: { type: "array", maxItems: 10, items: objectSchema({ name: stringSchema("Safe basename"), base64: { type: "string", maxLength: 1398104 }, contentType: stringSchema("MIME type") }, ["name", "base64"]), description: "Inline remote inputs, at most 1 MiB combined." }
70440
70908
  }, ["name"]),
70441
70909
  outputSchema: runOutputSchema
70442
70910
  },
@@ -70703,7 +71171,40 @@ var init_mcp_contracts = __esm(() => {
70703
71171
  outputSchema: objectSchema({}, [], "Feedback save result.", true)
70704
71172
  }
70705
71173
  ];
70706
- contracts = [...toolContracts].sort((a, b) => a.name.localeCompare(b.name));
71174
+ remoteCustomerContracts = REMOTE_CUSTOMER_OPERATIONS.map((operation) => ({
71175
+ name: operation.name,
71176
+ title: operation.title,
71177
+ description: `${operation.title} on the configured server; unavailable capabilities fail explicitly.`,
71178
+ params: operation.parameter ? [operation.parameter] : [],
71179
+ category: "execution",
71180
+ sideEffects: operation.mutates ? "local-process-or-remote-run" : "none",
71181
+ stable: true,
71182
+ inputSchema: objectSchema(operation.parameter ? { [operation.parameter]: stringSchema("Server resource identifier.") } : {}, operation.parameter ? [operation.parameter] : []),
71183
+ outputSchema: { oneOf: [objectSchema({}, [], "Server response.", true), { type: "array", items: objectSchema({}, [], "Server record.", true) }] }
71184
+ }));
71185
+ remoteCustomerContracts.push({
71186
+ name: "quote_skill",
71187
+ title: "Quote Remote Skill",
71188
+ description: "Get a server credit quote without submitting a run.",
71189
+ params: ["name", "input?", "args?"],
71190
+ category: "execution",
71191
+ sideEffects: "none",
71192
+ stable: true,
71193
+ inputSchema: objectSchema({ name: skillNameInput, input: runInputSchema, args: runArgsSchema }, ["name"]),
71194
+ outputSchema: objectSchema({ skill: stringSchema("Canonical server skill."), pricing: objectSchema({}, [], "Quoted integer credits.", true) }, ["skill", "pricing"], undefined, true)
71195
+ });
71196
+ remoteCustomerContracts.push({
71197
+ name: "download_run_artifact",
71198
+ title: "Download Verified Run Artifact",
71199
+ description: "Return verified artifact bytes as base64, bounded to 1 MiB.",
71200
+ params: ["run_id", "artifact_id"],
71201
+ category: "execution",
71202
+ sideEffects: "none",
71203
+ stable: true,
71204
+ inputSchema: objectSchema({ run_id: stringSchema("Run identifier."), artifact_id: stringSchema("Artifact identifier.") }, ["run_id", "artifact_id"]),
71205
+ outputSchema: objectSchema({ id: stringSchema("Artifact identifier."), fileName: stringSchema("Artifact file name."), base64: stringSchema("Verified bytes."), sha256: stringSchema("SHA256 digest."), byteSize: { type: "integer", minimum: 0 } }, ["id", "fileName", "base64", "sha256", "byteSize"])
71206
+ });
71207
+ contracts = [...toolContracts, ...remoteCustomerContracts].sort((a, b) => a.name.localeCompare(b.name));
70707
71208
  resourceContracts = [
70708
71209
  {
70709
71210
  uri: "skills://mcp/contracts",
@@ -70985,14 +71486,12 @@ function resolveRunRouting(skill, apiKey, apiUrl) {
70985
71486
  error: `${skill.name} is a server-owned skill. Run: skills auth login`
70986
71487
  };
70987
71488
  }
70988
- return { route: "remote", apiKey };
71489
+ return { route: "remote", apiKey, apiOrigin: apiUrl };
70989
71490
  }
70990
71491
  async function resolveConfiguredRunRouting(skill, env3 = process.env) {
70991
- let fleet;
70992
- let apiKey;
71492
+ let connection;
70993
71493
  try {
70994
- fleet = resolveSkillsFleet(env3);
70995
- apiKey = fleet.mode === "hosted" ? await resolveSkillsApiKey(env3) : null;
71494
+ connection = await resolveSkillsConnection(env3);
70996
71495
  } catch (error2) {
70997
71496
  const isMissingCredential = (error2 instanceof SkillsFleetCredentialError || error2?.name === "SkillsFleetCredentialError") && error2.code === "MISSING_API_CREDENTIAL";
70998
71497
  if (!isMissingCredential)
@@ -71003,7 +71502,7 @@ async function resolveConfiguredRunRouting(skill, env3 = process.env) {
71003
71502
  error: `${skill.name} is a server-owned skill. ${error2.message}`
71004
71503
  };
71005
71504
  }
71006
- return resolveRunRouting(skill, apiKey, fleet.apiOrigin ?? undefined);
71505
+ return resolveRunRouting(skill, connection?.apiKey, connection?.apiOrigin);
71007
71506
  }
71008
71507
  var init_run_routing = __esm(() => {
71009
71508
  init_fleet_credentials();
@@ -71212,10 +71711,15 @@ function registerOperationTools(server) {
71212
71711
  name: exports_external.string(),
71213
71712
  input: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
71214
71713
  args: exports_external.array(exports_external.string()).optional(),
71215
- detail: exports_external.boolean().optional()
71216
- }
71217
- }, async ({ name, input, args, detail }) => {
71218
- const skill = getSkill(name);
71714
+ detail: exports_external.boolean().optional(),
71715
+ maxCostCents: exports_external.number().int().min(0).max(2147483647).optional().describe("Maximum integer credits explicitly approved by the user for a remote run; omitted permits only free runs"),
71716
+ maxCredits: exports_external.number().int().min(0).max(2147483647).optional(),
71717
+ remote: exports_external.boolean().optional().describe("Use the configured server catalog, including skills not installed locally"),
71718
+ idempotency_key: exports_external.string().regex(/^[A-Za-z0-9._:-]{1,128}$/).optional().describe("Reuse for the same approved submission after an interrupted response"),
71719
+ files: exports_external.array(exports_external.object({ name: exports_external.string(), base64: exports_external.string().max(1398104), contentType: exports_external.string().optional() })).max(10).optional().describe("Inline remote inputs, at most 1 MiB combined; use CLI or SDK for larger files")
71720
+ }
71721
+ }, async ({ name, input, args, detail, maxCostCents, maxCredits, remote, idempotency_key, files }) => {
71722
+ const skill = remote ? { name, serverOwned: true } : getSkill(name);
71219
71723
  if (!skill) {
71220
71724
  return mcpError("SKILL_NOT_FOUND", `Skill '${name}' not found`, findSimilarSkills(name));
71221
71725
  }
@@ -71226,17 +71730,26 @@ function registerOperationTools(server) {
71226
71730
  const skillName = skill.name;
71227
71731
  const runInput = input || {};
71228
71732
  const runArgs = args || [];
71229
- if (skillName === ARTICLE_GENERATION_SLUG2) {
71733
+ if (!remote && skillName === ARTICLE_GENERATION_SLUG2) {
71230
71734
  const validation = validateBlogArticleRunOptions2(runInput, runArgs, { requireTopic: true });
71231
71735
  if (!validation.ok) {
71232
71736
  return mcpError("INVALID_BLOG_ARTICLE_OPTIONS", validation.errors.join(" "));
71233
71737
  }
71234
71738
  }
71235
71739
  const routing = await resolveConfiguredRunRouting(skill);
71740
+ if (files?.length && routing.route !== "remote")
71741
+ return mcpError("REMOTE_REQUIRED", "Inline inputs require a remote run");
71742
+ let inputFiles;
71743
+ try {
71744
+ inputFiles = (await Promise.resolve().then(() => (init_remote_files(), exports_remote_files))).decodeRemoteFiles(files ?? []);
71745
+ } catch (error2) {
71746
+ return mcpError("INVALID_INPUT_FILES", error2.message);
71747
+ }
71236
71748
  const runContext = createSkillRun({
71237
71749
  skill: skillName,
71238
71750
  args: runArgs,
71239
- remote: routing.route === "remote"
71751
+ remote: routing.route === "remote",
71752
+ ...routing.route === "remote" ? { remoteApiOrigin: routing.apiOrigin } : {}
71240
71753
  });
71241
71754
  if (routing.route === "error") {
71242
71755
  const error2 = routing.error;
@@ -71249,8 +71762,8 @@ function registerOperationTools(server) {
71249
71762
  if (routing.route === "remote") {
71250
71763
  try {
71251
71764
  const { RemoteSkillsClient: RemoteSkillsClient2 } = await Promise.resolve().then(() => (init_remote_client(), exports_remote_client));
71252
- const client = new RemoteSkillsClient2(routing.apiKey);
71253
- const run = await client.submitRun(skillName, runInput, runArgs);
71765
+ const client = new RemoteSkillsClient2(routing.apiKey, routing.apiOrigin);
71766
+ const run = await client.submitQuotedRunWithFiles(skillName, runInput, runArgs, inputFiles, { maxCredits, maxCostCents, idempotencyKey: idempotency_key ?? runContext.record.id });
71254
71767
  if (run.error) {
71255
71768
  writeRunLogs(runContext, "", String(run.error) + `
71256
71769
  `);
@@ -71312,7 +71825,7 @@ function registerOperationTools(server) {
71312
71825
  }
71313
71826
  }, async ({ run_id, detail }) => {
71314
71827
  const { skillsCredentialOrReason: skillsCredentialOrReason2 } = await Promise.resolve().then(() => (init_fleet_credentials(), exports_fleet_credentials));
71315
- const { apiKey, reason } = await skillsCredentialOrReason2();
71828
+ const { apiKey, apiOrigin, reason } = await skillsCredentialOrReason2();
71316
71829
  if (!apiKey) {
71317
71830
  return mcpError("AUTH_REQUIRED", reason ?? "Remote run status requires API access. Run: skills auth login", ["skills auth login"]);
71318
71831
  }
@@ -71323,7 +71836,9 @@ function registerOperationTools(server) {
71323
71836
  }
71324
71837
  try {
71325
71838
  const { RemoteSkillsClient: RemoteSkillsClient2 } = await Promise.resolve().then(() => (init_remote_client(), exports_remote_client));
71326
- const client = new RemoteSkillsClient2(apiKey);
71839
+ if (localRun?.remoteApiOrigin && localRun.remoteApiOrigin !== apiOrigin)
71840
+ return mcpError("INSTANCE_MISMATCH", "This run belongs to another Skills instance; select its credential profile");
71841
+ const client = new RemoteSkillsClient2(apiKey, apiOrigin);
71327
71842
  const run = await client.getRun(remoteRunId);
71328
71843
  if (!run)
71329
71844
  return mcpError("RUN_NOT_FOUND", `Remote run '${remoteRunId}' not found`);
@@ -71985,7 +72500,7 @@ var init_schedule_tools = __esm(() => {
71985
72500
  });
71986
72501
 
71987
72502
  // src/lib/native-storage.ts
71988
- import { createHash as createHash5, createHmac as createHmac3 } from "crypto";
72503
+ import { createHash as createHash6, createHmac as createHmac3 } from "crypto";
71989
72504
  import {
71990
72505
  existsSync as existsSync27,
71991
72506
  mkdirSync as mkdirSync13,
@@ -72062,7 +72577,7 @@ function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
72062
72577
  files.push({
72063
72578
  path: relativePath,
72064
72579
  sizeBytes: bytes.byteLength,
72065
- sha256: createHash5("sha256").update(bytes).digest("hex"),
72580
+ sha256: createHash6("sha256").update(bytes).digest("hex"),
72066
72581
  ...options.includeFileContents ? { contentBase64: Buffer.from(bytes).toString("base64") } : {}
72067
72582
  });
72068
72583
  }
@@ -72239,6 +72754,201 @@ var init_storage_tools = __esm(() => {
72239
72754
  init_helpers();
72240
72755
  });
72241
72756
 
72757
+ // src/lib/remote-auth.ts
72758
+ async function requestAuthApi(instance, path, options) {
72759
+ const url = normalizeSkillsApiOrigin(instance);
72760
+ const safeUrl = url;
72761
+ const endpoint = `${(options?.method || "GET").toUpperCase()} ${safeUrl}${path}`;
72762
+ let res;
72763
+ try {
72764
+ res = await fetch(`${url}${path}`, {
72765
+ ...options,
72766
+ redirect: "error",
72767
+ signal: options?.signal ?? AbortSignal.timeout(15000),
72768
+ headers: { "Content-Type": "application/json", ...options?.headers }
72769
+ });
72770
+ } catch (err) {
72771
+ throw new HostedApiError(`Unable to reach the Skills API: ${err.message}`, {
72772
+ endpoint,
72773
+ apiUrl: safeUrl
72774
+ });
72775
+ }
72776
+ const text = await res.text();
72777
+ const body = text ? parseJsonBody(text) : {};
72778
+ if (!res.ok) {
72779
+ const record3 = isRecord4(body) ? body : {};
72780
+ const detail = typeof record3.detail === "string" ? record3.detail : undefined;
72781
+ const error2 = typeof record3.error === "string" ? record3.error : undefined;
72782
+ const code = typeof record3.code === "string" ? record3.code : undefined;
72783
+ throw new HostedApiError(detail || error2 || `${res.status} ${res.statusText}`, {
72784
+ status: res.status,
72785
+ code,
72786
+ detail,
72787
+ endpoint,
72788
+ apiUrl: safeUrl
72789
+ });
72790
+ }
72791
+ return body;
72792
+ }
72793
+ function parseJsonBody(text) {
72794
+ try {
72795
+ return JSON.parse(text);
72796
+ } catch {
72797
+ return { detail: condenseErrorBody(text) };
72798
+ }
72799
+ }
72800
+ function condenseErrorBody(text) {
72801
+ const stripped = /<[a-z!/]/i.test(text) ? text.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]*>/g, " ") : text;
72802
+ const collapsed = stripped.replace(/\s+/g, " ").trim();
72803
+ if (collapsed.length <= MAX_ERROR_DETAIL_LENGTH)
72804
+ return collapsed;
72805
+ return `${collapsed.slice(0, MAX_ERROR_DETAIL_LENGTH - 1).trimEnd()}\u2026`;
72806
+ }
72807
+ function isRecord4(value) {
72808
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
72809
+ }
72810
+
72811
+ class RemoteSkillsAuthClient {
72812
+ apiOrigin;
72813
+ constructor(apiUrl) {
72814
+ this.apiOrigin = normalizeSkillsApiOrigin(apiUrl);
72815
+ }
72816
+ requestCode(email2) {
72817
+ return this.request("/api/auth/login", { method: "POST", body: JSON.stringify({ email: email2 }) });
72818
+ }
72819
+ verifyCode(email2, code) {
72820
+ return this.request("/api/auth/verify", { method: "POST", body: JSON.stringify({ email: email2, code }) });
72821
+ }
72822
+ startDevice() {
72823
+ return this.request("/api/auth/device/start", { method: "POST", body: JSON.stringify({ client: "skills-sdk" }) });
72824
+ }
72825
+ pollDevice(deviceCode) {
72826
+ return this.request("/api/auth/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) });
72827
+ }
72828
+ async sessionClient(email2, code) {
72829
+ if (!email2.includes("@") || !/^\d{6}$/.test(code))
72830
+ throw new Error("Fresh email and six-digit verification code are required to manage API keys");
72831
+ const login = await this.verifyCode(email2, code);
72832
+ if (!login || typeof login.token !== "string" || !login.token)
72833
+ throw new Error("The server did not return an authorized account session");
72834
+ return new RemoteSkillsClient(login.token, this.apiOrigin);
72835
+ }
72836
+ async createApiKey(email2, code, name, scopes) {
72837
+ return (await this.sessionClient(email2, code)).createApiKey(name, scopes);
72838
+ }
72839
+ async listApiKeys(email2, code) {
72840
+ return (await this.sessionClient(email2, code)).listApiKeys();
72841
+ }
72842
+ async revokeApiKey(email2, code, keyId) {
72843
+ return (await this.sessionClient(email2, code)).revokeApiKey(keyId);
72844
+ }
72845
+ request(path, options) {
72846
+ if (!["/api/auth/login", "/api/auth/verify", "/api/auth/device/start", "/api/auth/device/token", "/api/auth/keys", "/api/auth/whoami"].includes(path))
72847
+ throw new Error("Unsupported authentication operation");
72848
+ return requestAuthApi(this.apiOrigin, path, options);
72849
+ }
72850
+ }
72851
+ var MAX_ERROR_DETAIL_LENGTH = 200, HostedApiError;
72852
+ var init_remote_auth = __esm(() => {
72853
+ init_remote_client();
72854
+ init_fleet_credentials();
72855
+ HostedApiError = class HostedApiError extends Error {
72856
+ status;
72857
+ code;
72858
+ detail;
72859
+ endpoint;
72860
+ apiUrl;
72861
+ constructor(message, options = {}) {
72862
+ super(message);
72863
+ this.name = "HostedApiError";
72864
+ this.status = options.status;
72865
+ this.code = options.code;
72866
+ this.detail = options.detail;
72867
+ this.endpoint = options.endpoint;
72868
+ this.apiUrl = options.apiUrl;
72869
+ }
72870
+ };
72871
+ });
72872
+
72873
+ // src/mcp/remote-customer-tools.ts
72874
+ function registerRemoteCustomerTools(server) {
72875
+ for (const operation of REMOTE_CUSTOMER_OPERATIONS) {
72876
+ const inputSchema = {};
72877
+ if (operation.parameter)
72878
+ inputSchema[operation.parameter] = exports_external.string().min(1);
72879
+ server.registerTool(operation.name, {
72880
+ title: operation.title,
72881
+ description: `${operation.title} on the explicitly configured Skills server. Missing server capabilities return an error. Checkout links require external customer confirmation.`,
72882
+ inputSchema
72883
+ }, async (input) => callRemote((client) => operation.invoke(client, operation.parameter ? String(input[operation.parameter]) : "")));
72884
+ }
72885
+ server.registerTool("list_api_keys", {
72886
+ title: "List API Keys",
72887
+ description: "List account API keys using fresh email OTP reauthentication.",
72888
+ inputSchema: { email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/) }
72889
+ }, async ({ email: email2, code }) => {
72890
+ try {
72891
+ return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("List API keys")).listApiKeys(email2, code));
72892
+ } catch (error2) {
72893
+ return mcpError("KEY_LIST_FAILED", error2.message);
72894
+ }
72895
+ });
72896
+ server.registerTool("revoke_api_key", {
72897
+ title: "Revoke API Key",
72898
+ description: "Revoke an account API key using fresh email OTP reauthentication.",
72899
+ inputSchema: { key_id: exports_external.string().min(1), email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/) }
72900
+ }, async ({ key_id, email: email2, code }) => {
72901
+ try {
72902
+ return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("Revoke API key")).revokeApiKey(email2, code, key_id));
72903
+ } catch (error2) {
72904
+ return mcpError("KEY_REVOKE_FAILED", error2.message);
72905
+ }
72906
+ });
72907
+ server.registerTool("create_api_key", {
72908
+ title: "Create API Key",
72909
+ description: "Create an API key using fresh email OTP reauthentication; returns its secret once. A stored API key cannot grant this authority.",
72910
+ inputSchema: { name: exports_external.string().min(1), email: exports_external.string().email(), code: exports_external.string().regex(/^\d{6}$/), scopes: exports_external.array(exports_external.string()).optional() }
72911
+ }, async ({ name, email: email2, code, scopes }) => {
72912
+ try {
72913
+ return mcpJson(await new RemoteSkillsAuthClient(getApiUrl("Create API key")).createApiKey(email2, code, name, scopes));
72914
+ } catch (error2) {
72915
+ return mcpError("KEY_CREATION_FAILED", error2.message);
72916
+ }
72917
+ });
72918
+ server.registerTool("quote_skill", {
72919
+ title: "Quote Remote Skill",
72920
+ description: "Get the configured server's credit quote without submitting a run.",
72921
+ inputSchema: { name: exports_external.string(), input: exports_external.record(exports_external.string(), exports_external.unknown()).optional(), args: exports_external.array(exports_external.string()).optional() }
72922
+ }, ({ name, input, args }) => callRemote((client) => client.quoteRun(name, input, args)));
72923
+ server.registerTool("download_run_artifact", {
72924
+ title: "Download Verified Run Artifact",
72925
+ description: "Return verified artifact bytes as base64 (at most 1 MiB); use the CLI for larger files.",
72926
+ inputSchema: { run_id: exports_external.string(), artifact_id: exports_external.string() }
72927
+ }, ({ run_id, artifact_id }) => callRemote(async (client) => {
72928
+ const artifact = await client.getVerifiedRunArtifact(run_id, artifact_id, 1024 * 1024);
72929
+ const { bytes, ...metadata } = artifact;
72930
+ return { ...metadata, base64: Buffer.from(bytes).toString("base64") };
72931
+ }));
72932
+ }
72933
+ async function callRemote(action) {
72934
+ try {
72935
+ const client = await createRemoteSkillsClient();
72936
+ if (!client)
72937
+ return mcpError("AUTH_REQUIRED", "Configure a Skills API and sign in with skills auth login");
72938
+ return mcpJson(await action(client));
72939
+ } catch (error2) {
72940
+ return mcpError("REMOTE_REQUEST_FAILED", error2 instanceof Error ? error2.message : "Skills server request failed");
72941
+ }
72942
+ }
72943
+ var init_remote_customer_tools = __esm(() => {
72944
+ init_zod();
72945
+ init_remote_auth();
72946
+ init_auth_store();
72947
+ init_remote_customer_operations();
72948
+ init_remote_client();
72949
+ init_helpers();
72950
+ });
72951
+
72242
72952
  // src/mcp/server.ts
72243
72953
  function buildServer() {
72244
72954
  const server = new McpServer({
@@ -72250,6 +72960,7 @@ function buildServer() {
72250
72960
  registerScheduleTools(server);
72251
72961
  registerStorageTools(server);
72252
72962
  registerResourceMetaTools(server);
72963
+ registerRemoteCustomerTools(server);
72253
72964
  return server;
72254
72965
  }
72255
72966
  var server;
@@ -72261,6 +72972,7 @@ var init_server3 = __esm(() => {
72261
72972
  init_resource_meta_tools();
72262
72973
  init_schedule_tools();
72263
72974
  init_storage_tools();
72975
+ init_remote_customer_tools();
72264
72976
  server = buildServer();
72265
72977
  });
72266
72978
 
@@ -72638,7 +73350,8 @@ MCP server for ${package_default.name}
72638
73350
  Options:
72639
73351
  -V, --version output the version number
72640
73352
  -h, --help display help for command
72641
- --http run Streamable HTTP transport on 127.0.0.1 (default port 8836)
73353
+ --stdio run newline-delimited JSON-RPC for agent hosts
73354
+ --http run Streamable HTTP transport on 127.0.0.1 (default; port 8836)
72642
73355
  --port <n> HTTP port (--http or MCP_HTTP=1)`);
72643
73356
  }
72644
73357
  async function startMcpStdio() {
@@ -72887,18 +73600,61 @@ var init_runtime_mcp = __esm(() => {
72887
73600
  init_installer();
72888
73601
  });
72889
73602
 
73603
+ // src/cli/commands/remote-account.ts
73604
+ var exports_remote_account = {};
73605
+ __export(exports_remote_account, {
73606
+ registerRemoteAccount: () => registerRemoteAccount,
73607
+ execute: () => execute
73608
+ });
73609
+ function registerRemoteAccount(parent) {
73610
+ parent.command("capabilities").option("--json", "Output as JSON", false).description("Inspect the selected server's supported API contract").action((options) => execute(options, (client) => client.getCapabilities()));
73611
+ parent.command("quote").argument("<skill>").argument("[args...]").allowUnknownOption(true).passThroughOptions(true).option("--json", "Output the server quote as JSON", false).description("Quote a skill on the configured server without submitting it").action((skill, args2, options) => execute(options, async (client) => client.quoteRun(skill, {}, args2)));
73612
+ const billing = parent.command("billing").description("Inspect the configured server's billing account");
73613
+ billing.command("status").option("--json", "Output as JSON", false).action((options) => execute(options, (client) => client.getBillingStatus()));
73614
+ billing.command("usage").option("--json", "Output as JSON", false).action((options) => execute(options, (client) => client.getUsage()));
73615
+ billing.command("invoices").option("--json", "Output as JSON", false).action((options) => execute(options, (client) => client.listInvoices()));
73616
+ billing.command("checkout").option("--json", "Output as JSON", false).description("Create an external subscription checkout link").action((options) => execute(options, (client) => client.createBillingCheckout()));
73617
+ billing.command("portal").option("--json", "Output as JSON", false).description("Create an external customer billing portal link").action((options) => execute(options, (client) => client.createBillingPortal()));
73618
+ const credits = parent.command("credits").description("Inspect and purchase credits from the configured server");
73619
+ credits.command("packs").option("--json", "Output as JSON", false).action((options) => execute(options, (client) => client.listCreditPacks()));
73620
+ credits.command("buy").argument("<pack-id>", "An ID returned by credits packs").option("--json", "Output as JSON", false).description("Create an external checkout link; payment is confirmed in the browser").action((packId, options) => execute(options, (client) => client.createCreditCheckout(packId)));
73621
+ }
73622
+ async function execute(options, action) {
73623
+ try {
73624
+ const client = await createRemoteSkillsClient();
73625
+ if (!client)
73626
+ throw new Error("Configure a Skills server with skills setup --api-url <url>, then skills auth login");
73627
+ const result2 = await action(client);
73628
+ console.log(JSON.stringify(result2, null, 2));
73629
+ } catch (error2) {
73630
+ const message = error2 instanceof Error ? error2.message : "The Skills server request failed";
73631
+ if (options.json)
73632
+ console.log(JSON.stringify({ error: message }));
73633
+ else
73634
+ console.error(message);
73635
+ process.exitCode = 1;
73636
+ }
73637
+ }
73638
+ var init_remote_account2 = __esm(() => {
73639
+ init_remote_client();
73640
+ });
73641
+
72890
73642
  // src/cli/commands/runtime.ts
72891
73643
  var exports_runtime = {};
72892
73644
  __export(exports_runtime, {
72893
73645
  registerRuntime: () => registerRuntime
72894
73646
  });
72895
- import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync16 } from "fs";
72896
- import { dirname as dirname11, join as join31 } from "path";
73647
+ import { lstatSync as lstatSync4, mkdirSync as mkdirSync15, readFileSync as readFileSync25, writeFileSync as writeFileSync16 } from "fs";
73648
+ import { basename as basename5, join as join31 } from "path";
72897
73649
  import { createInterface } from "readline";
72898
73650
  function registerRuntime(parent) {
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));
73651
+ 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("--remote", "Run on the configured server, using its catalog and quote", false).option("--yes", "Approve the server's quoted credit cost for this run", false).option("--idempotency-key <key>", "Reuse this key when retrying the same remote submission").option("--file <path>", "Attach a local input file to a remote run (repeatable)", (value, prior) => [...prior, value], []).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));
72900
73652
  const runs = parent.command("runs").description("Inspect local skill run records");
72901
- runs.command("list").option("--json", "Output as JSON", false).option("--limit <n>", "Maximum number of runs", "20").option("--cursor <n>", "Numeric offset for human-output pagination", "0").description("List recent skill runs").action((options) => handleRunsList(options));
73653
+ runs.command("list").option("--remote", "List runs on the configured server", false).option("--json", "Output as JSON", false).option("--limit <n>", "Maximum number of runs", "20").option("--cursor <n>", "Numeric offset for human-output pagination", "0").description("List recent skill runs").action((options) => options.remote ? execute(options, (client) => client.listRuns(Number(options.limit))) : handleRunsList(options));
73654
+ runs.command("logs").argument("<run-id>").option("--json", "Output as JSON", false).action((id, options) => execute(options, (client) => client.getRunLogs(id)));
73655
+ runs.command("cancel").argument("<run-id>").option("--json", "Output as JSON", false).action((id, options) => execute(options, (client) => client.cancelRun(id)));
73656
+ runs.command("resume").argument("<run-id>").option("--json", "Output as JSON", false).action((id, options) => execute(options, (client) => client.resumeRun(id)));
73657
+ runs.command("artifacts").argument("<run-id>").option("--json", "Output as JSON", false).action((id, options) => execute(options, (client) => client.getRunArtifacts(id)));
72902
73658
  runs.command("show").argument("<run-id>", "Run id").option("--json", "Output as JSON", false).description("Show a skill run record").action((runId, options) => handleRunsShow(runId, options));
72903
73659
  runs.command("status").argument("<run-id>", "Remote run id, or local run id linked to a remote run").option("--json", "Output as JSON", false).description("Fetch remote run status").action((runId, options) => handleRunsStatus(runId, options));
72904
73660
  const exportsCommand = parent.command("exports").description("Inspect or open skill run exports");
@@ -72968,8 +73724,13 @@ async function handleSetup(options) {
72968
73724
  return;
72969
73725
  }
72970
73726
  let requested = options.apiUrl?.trim();
72971
- if (!requested && process.stdin.isTTY && process.stdout.isTTY) {
72972
- requested = (await promptLine("Skills API URL (blank to leave unchanged): ")).trim();
73727
+ if (!requested && !options.json && process.stdin.isTTY && process.stdout.isTTY) {
73728
+ const answer = await promptLine("Skills API URL (blank to leave unchanged): ");
73729
+ if (answer === null) {
73730
+ process.exitCode = 130;
73731
+ return;
73732
+ }
73733
+ requested = answer.trim();
72973
73734
  }
72974
73735
  let saved = null;
72975
73736
  let credentialsFile = null;
@@ -73000,8 +73761,14 @@ async function handleSetup(options) {
73000
73761
  authenticated = true;
73001
73762
  }
73002
73763
  } catch (err) {
73003
- error2 = err.message;
73004
- configured = saved;
73764
+ if (err instanceof SkillsFleetCredentialError && err.code === "MISSING_API_CREDENTIAL") {
73765
+ const authority = resolveSkillsApiOrigin();
73766
+ configured = authority?.origin ?? saved;
73767
+ source = authority?.source ?? null;
73768
+ } else {
73769
+ error2 = err.message;
73770
+ configured = saved;
73771
+ }
73005
73772
  }
73006
73773
  const next = error2 ? ["skills auth login"] : configured ? ["skills auth login", "skills list --remote"] : ["skills list", "skills run <skill>"];
73007
73774
  const payload = {
@@ -73058,14 +73825,23 @@ function requireHttpUrl(value) {
73058
73825
  function promptLine(question) {
73059
73826
  const rl = createInterface({ input: process.stdin, output: process.stdout });
73060
73827
  return new Promise((resolve2) => {
73061
- rl.question(source_default.bold(question), (answer) => {
73828
+ let settled = false;
73829
+ const finish = (answer) => {
73830
+ if (settled)
73831
+ return;
73832
+ settled = true;
73062
73833
  rl.close();
73063
- resolve2(answer.trim());
73834
+ resolve2(answer);
73835
+ };
73836
+ rl.once("SIGINT", () => finish(null));
73837
+ rl.once("close", () => finish(null));
73838
+ rl.question(source_default.bold(question), (answer) => {
73839
+ finish(answer.trim());
73064
73840
  });
73065
73841
  });
73066
73842
  }
73067
73843
  async function handleRun(name, args2, options) {
73068
- const skill = getSkill(name);
73844
+ const skill = options.remote ? { name, serverOwned: true } : getSkill(name);
73069
73845
  if (!skill) {
73070
73846
  const similar = findSimilarSkills(name);
73071
73847
  if (options.json) {
@@ -73079,7 +73855,7 @@ async function handleRun(name, args2, options) {
73079
73855
  return;
73080
73856
  }
73081
73857
  const prompt = extractPrompt(args2);
73082
- if (skill.name === ARTICLE_GENERATION_SLUG) {
73858
+ if (!options.remote && skill.name === ARTICLE_GENERATION_SLUG) {
73083
73859
  const validation = validateBlogArticleRunOptions({}, args2, { requireTopic: true });
73084
73860
  if (!validation.ok) {
73085
73861
  writeBlogArticleValidationError(validation.errors, options.json);
@@ -73087,11 +73863,62 @@ async function handleRun(name, args2, options) {
73087
73863
  }
73088
73864
  }
73089
73865
  const routing = await resolveConfiguredRunRouting(skill);
73866
+ if (routing.route !== "remote" && options.file?.length) {
73867
+ const error2 = "File uploads require an explicitly remote run";
73868
+ if (options.json)
73869
+ console.log(JSON.stringify({ error: error2 }));
73870
+ else
73871
+ console.error(error2);
73872
+ process.exitCode = 1;
73873
+ return;
73874
+ }
73875
+ let client;
73876
+ let approvedCredits = 0;
73877
+ let inputFiles = [];
73878
+ if (routing.route === "remote") {
73879
+ try {
73880
+ parsePollingOptions(options);
73881
+ inputFiles = (options.file ?? []).map((path) => {
73882
+ const info = lstatSync4(path);
73883
+ if (!info.isFile() || info.size > 20 * 1024 * 1024)
73884
+ throw new Error("Input must be a regular file no larger than 20 MiB");
73885
+ return { name: basename5(path), bytes: new Uint8Array(readFileSync25(path)) };
73886
+ });
73887
+ describeRemoteFiles(inputFiles);
73888
+ client = new RemoteSkillsClient(routing.apiKey, routing.apiOrigin);
73889
+ const quote = await client.quoteRun(skill.name, {}, args2);
73890
+ approvedCredits = quote.pricing.costCents;
73891
+ if (approvedCredits > 0 && !options.yes) {
73892
+ if (options.json || !process.stdin.isTTY || !process.stdout.isTTY) {
73893
+ throw new Error(`CREDIT_APPROVAL_REQUIRED: This run costs ${approvedCredits} credits. Review skills quote, then rerun with --yes before the skill name.`);
73894
+ }
73895
+ const answer = await promptLine(`Approve ${approvedCredits} credits for ${quote.skill}? (y/N) `);
73896
+ if (answer === null) {
73897
+ process.exitCode = 130;
73898
+ return;
73899
+ }
73900
+ if (!/^(y|yes)$/i.test(answer)) {
73901
+ process.exitCode = 1;
73902
+ return;
73903
+ }
73904
+ }
73905
+ skill.name = quote.skill;
73906
+ } catch (error2) {
73907
+ const message = error2 instanceof Error ? error2.message : "Remote quote failed";
73908
+ if (options.json)
73909
+ console.log(JSON.stringify({ skill: name, exitCode: 1, remote: true, error: message }));
73910
+ else
73911
+ console.error(message);
73912
+ process.exitCode = 1;
73913
+ return;
73914
+ }
73915
+ }
73090
73916
  const runContext = createSkillRun({
73091
73917
  skill: skill.name,
73092
73918
  args: args2,
73093
73919
  prompt,
73094
- remote: routing.route === "remote"
73920
+ remote: routing.route === "remote",
73921
+ ...routing.route === "remote" ? { remoteApiOrigin: routing.apiOrigin } : {}
73095
73922
  });
73096
73923
  if (routing.route === "error") {
73097
73924
  const error2 = routing.error;
@@ -73107,9 +73934,10 @@ async function handleRun(name, args2, options) {
73107
73934
  }
73108
73935
  if (routing.route === "remote") {
73109
73936
  try {
73110
- const { RemoteSkillsClient: RemoteSkillsClient2 } = await Promise.resolve().then(() => (init_remote_client(), exports_remote_client));
73111
- const client = new RemoteSkillsClient2(routing.apiKey);
73112
- const run = await client.submitRun(skill.name, {}, args2);
73937
+ const run = await client.submitQuotedRunWithFiles(skill.name, {}, args2, inputFiles, {
73938
+ maxCredits: approvedCredits,
73939
+ idempotencyKey: options.idempotencyKey ?? runContext.record.id
73940
+ });
73113
73941
  if (run.error) {
73114
73942
  writeRunLogs(runContext, "", String(run.error) + `
73115
73943
  `);
@@ -73283,7 +74111,7 @@ ${run.id}
73283
74111
  }
73284
74112
  async function handleRunsStatus(runId, options) {
73285
74113
  const { skillsCredentialOrReason: skillsCredentialOrReason2 } = await Promise.resolve().then(() => (init_fleet_credentials(), exports_fleet_credentials));
73286
- const { apiKey, reason } = await skillsCredentialOrReason2();
74114
+ const { apiKey, apiOrigin, reason } = await skillsCredentialOrReason2();
73287
74115
  if (!apiKey) {
73288
74116
  const error2 = reason ?? "Remote run status requires API access. Run: skills auth login";
73289
74117
  if (options.json)
@@ -73306,7 +74134,9 @@ async function handleRunsStatus(runId, options) {
73306
74134
  }
73307
74135
  try {
73308
74136
  const { RemoteSkillsClient: RemoteSkillsClient2 } = await Promise.resolve().then(() => (init_remote_client(), exports_remote_client));
73309
- const client = new RemoteSkillsClient2(apiKey);
74137
+ if (localRun?.remoteApiOrigin && localRun.remoteApiOrigin !== apiOrigin)
74138
+ throw new Error("This run belongs to another Skills instance; select its credential profile");
74139
+ const client = new RemoteSkillsClient2(apiKey, apiOrigin);
73310
74140
  const run = await client.getRun(remoteRunId);
73311
74141
  if (!run) {
73312
74142
  const error2 = `Remote run '${remoteRunId}' not found`;
@@ -73384,7 +74214,7 @@ async function handleExportsOpen(runId, options) {
73384
74214
  }
73385
74215
  async function handleExportsDownload(runId, options) {
73386
74216
  const { skillsCredentialOrReason: skillsCredentialOrReason2 } = await Promise.resolve().then(() => (init_fleet_credentials(), exports_fleet_credentials));
73387
- const { apiKey, reason } = await skillsCredentialOrReason2();
74217
+ const { apiKey, apiOrigin, reason } = await skillsCredentialOrReason2();
73388
74218
  if (!apiKey) {
73389
74219
  const error2 = reason ?? "Remote artifact downloads require API access. Run: skills auth login";
73390
74220
  if (options.json)
@@ -73396,7 +74226,7 @@ async function handleExportsDownload(runId, options) {
73396
74226
  }
73397
74227
  try {
73398
74228
  const { RemoteSkillsClient: RemoteSkillsClient2 } = await Promise.resolve().then(() => (init_remote_client(), exports_remote_client));
73399
- const client = new RemoteSkillsClient2(apiKey);
74229
+ const client = new RemoteSkillsClient2(apiKey, apiOrigin);
73400
74230
  const remoteRun = await client.getRun(runId);
73401
74231
  if (!remoteRun) {
73402
74232
  const error2 = `Remote run '${runId}' not found`;
@@ -73417,15 +74247,12 @@ async function handleExportsDownload(runId, options) {
73417
74247
  const artifactId = String(artifact.id || "");
73418
74248
  if (!artifactId)
73419
74249
  continue;
73420
- const response = await client.downloadRunArtifact(runId, artifactId);
73421
- if (!response.ok)
73422
- throw new Error(`download failed for artifact ${artifactId}: ${response.status}`);
74250
+ const verified = await client.getVerifiedRunArtifact(runId, artifactId);
73423
74251
  const relativePath = safeArtifactRelativePath(typeof artifact.relativePath === "string" ? artifact.relativePath : artifact.fileName, String(artifact.fileName || artifactId));
73424
74252
  const outputPath = join31(exportDir, relativePath);
73425
- mkdirSync15(dirname11(outputPath), { recursive: true });
73426
- const bytes = new Uint8Array(await response.arrayBuffer());
73427
- writeFileSync16(outputPath, bytes);
73428
- downloaded.push({ id: artifactId, path: outputPath, byteSize: bytes.byteLength });
74253
+ ensureSafeExportParent(exportDir, relativePath);
74254
+ writeFileSync16(outputPath, verified.bytes, { flag: "wx", mode: 384 });
74255
+ downloaded.push({ id: artifactId, path: outputPath, byteSize: verified.byteSize });
73429
74256
  }
73430
74257
  const payload = {
73431
74258
  runId,
@@ -73452,11 +74279,26 @@ async function handleExportsDownload(runId, options) {
73452
74279
  function safeArtifactRelativePath(value, fallback) {
73453
74280
  const raw = typeof value === "string" && value.trim() ? value : fallback;
73454
74281
  const parts = raw.split(/[\\/]+/).filter((part) => part && part !== ".");
73455
- if (parts.length === 0 || parts.some((part) => part === "..")) {
73456
- return fallback.replace(/[\\/\r\n"]/g, "_");
73457
- }
74282
+ if (raw.startsWith("/") || /^[A-Za-z]:/.test(raw) || parts.length === 0 || parts.some((part) => part === ".." || /[\x00-\x1f\x7f]/.test(part)))
74283
+ throw new Error("Unsafe artifact path");
73458
74284
  return parts.join("/");
73459
74285
  }
74286
+ function ensureSafeExportParent(root, relativePath) {
74287
+ const parts = relativePath.split("/").slice(0, -1);
74288
+ let parent = root;
74289
+ for (const part of ["", ...parts]) {
74290
+ if (part)
74291
+ parent = join31(parent, part);
74292
+ try {
74293
+ if (!lstatSync4(parent).isDirectory() || lstatSync4(parent).isSymbolicLink())
74294
+ throw new Error("Unsafe artifact directory");
74295
+ } catch (error2) {
74296
+ if (error2.code !== "ENOENT")
74297
+ throw error2;
74298
+ mkdirSync15(parent, { mode: 448 });
74299
+ }
74300
+ }
74301
+ }
73460
74302
  function parsePollingOptions(options) {
73461
74303
  return {
73462
74304
  intervalMs: parsePositiveInt(options.pollIntervalMs, 1000),
@@ -73643,6 +74485,9 @@ var init_runtime = __esm(() => {
73643
74485
  init_run_state();
73644
74486
  init_runtime_mcp();
73645
74487
  init_run_routing();
74488
+ init_remote_client();
74489
+ init_remote_account2();
74490
+ init_remote_files();
73646
74491
  });
73647
74492
 
73648
74493
  // src/cli/commands/completion.ts
@@ -73934,11 +74779,11 @@ var init_portable_snapshot_filter = __esm(() => {
73934
74779
  });
73935
74780
 
73936
74781
  // src/lib/station-snapshot.ts
73937
- import { createHash as createHash6 } from "crypto";
74782
+ import { createHash as createHash7 } from "crypto";
73938
74783
  import {
73939
74784
  copyFileSync as copyFileSync2,
73940
74785
  mkdirSync as mkdirSync16,
73941
- readFileSync as readFileSync25,
74786
+ readFileSync as readFileSync26,
73942
74787
  statSync as statSync18,
73943
74788
  writeFileSync as writeFileSync17
73944
74789
  } from "fs";
@@ -73949,7 +74794,7 @@ function validateStationId(stationId) {
73949
74794
  }
73950
74795
  }
73951
74796
  function sha256File(filePath) {
73952
- return createHash6("sha256").update(readFileSync25(filePath)).digest("hex");
74797
+ return createHash7("sha256").update(readFileSync26(filePath)).digest("hex");
73953
74798
  }
73954
74799
  function scanHome(definition, homesRoot) {
73955
74800
  const homePath = homePathFor(definition, homesRoot);
@@ -74074,7 +74919,7 @@ function writeStationSnapshot(options) {
74074
74919
  copyFileSync2(plan.source.fullPath, destination);
74075
74920
  written += 1;
74076
74921
  }
74077
- const unchanged = plans.length - untouched.length;
74922
+ const unchanged2 = plans.length - untouched.length;
74078
74923
  const manifest = {
74079
74924
  schema: STATION_SYNC_MANIFEST_SCHEMA,
74080
74925
  stationId: options.stationId,
@@ -74082,7 +74927,7 @@ function writeStationSnapshot(options) {
74082
74927
  producer: STATION_SNAPSHOT_PRODUCER,
74083
74928
  stats: {
74084
74929
  written,
74085
- unchanged,
74930
+ unchanged: unchanged2,
74086
74931
  files: plans.length,
74087
74932
  bytes: totalBytes
74088
74933
  },
@@ -74095,7 +74940,7 @@ function writeStationSnapshot(options) {
74095
74940
  return {
74096
74941
  ...base2,
74097
74942
  mode: "populate",
74098
- stats: { files: plans.length, bytes: totalBytes, written, unchanged },
74943
+ stats: { files: plans.length, bytes: totalBytes, written, unchanged: unchanged2 },
74099
74944
  manifestPath
74100
74945
  };
74101
74946
  }
@@ -74121,7 +74966,7 @@ var exports_create_sync_config = {};
74121
74966
  __export(exports_create_sync_config, {
74122
74967
  registerCreateSync: () => registerCreateSync
74123
74968
  });
74124
- import { existsSync as existsSync29, writeFileSync as writeFileSync18, mkdirSync as mkdirSync17 } from "fs";
74969
+ import { existsSync as existsSync29 } from "fs";
74125
74970
  import { join as join33 } from "path";
74126
74971
  function registerCreateSync(parent) {
74127
74972
  const configCmd = parent.command("config").description("Manage skills configuration");
@@ -74214,54 +75059,27 @@ function registerCreateSync(parent) {
74214
75059
  parent.command("sync").alias("render").argument("[names...]", "Skills to sync (default: every skill in this machine's corpus)").option("--for <agent>", `Target one agent (${SYNC_AGENTS.join(", ")}, or all)`, "all").option("--all", "Sync every corpus skill (the default)", false).option("--source <path>", "Canonical corpus source: a directory of skill folders, or a package root with skills/ (overrides $SKILLS_SOURCE)").option("--dry-run", "Show what would be written without touching any agent folder", false).option("--force", "Adopt an unmanaged skill that already has SKILL.md; other unmarked directories are never overwritten", false).option("--check", "Home drift census (missing-from-home / stray-in-home / diverged); exits non-zero on drift, writes nothing", false).option("--adopt", "Unmarked-home adoption mode: hash unmarked home skills against the corpus; exact matches are marked (dry-run by default)", false).option("--prune", "Prune mode: list (or with --apply, remove) marked home skill dirs that have no canonical corpus entry", false).option("--apply", "Write adoption markers / conflicts ledger, or perform prune removals", false).option("--json", "Output as JSON", false).option("--station <id>", "Per-station snapshot mode: snapshot the installed skill homes into resources/<station>/skills with a v3 sync-manifest (dry-run by default; --populate writes)").option("--populate", "Write the per-station snapshot (station mode; the default is dry-run)", false).option("--repo-root <path>", "Station snapshot destination repo root (default: cwd)").option("--homes-root <dir>", "Build the station snapshot from a staged mirror of the skill homes instead of this machine's $HOME").description("Write corpus skills into each coding agent's global skills folder, per-tool adapted; with --station, snapshot the homes into a reviewed snapshot repo instead").action((names, options) => handleSync(names, options));
74215
75060
  }
74216
75061
  function handleCreate(name, options) {
74217
- const bare = name.trim();
74218
- const dirName = bare;
74219
- const baseDir = getPortableSkillsRoot();
74220
- const skillDir = join33(baseDir, dirName);
74221
- if (existsSync29(skillDir)) {
74222
- console.log(options.json ? JSON.stringify({ error: `Skill '${bare}' already exists at ${skillDir}` }) : source_default.red(`Skill '${bare}' already exists at ${skillDir}`));
75062
+ try {
75063
+ const tags = options.tags?.split(",").map((tag) => tag.trim()).filter(Boolean);
75064
+ const result2 = scaffoldPortableSkill(name, {
75065
+ description: options.description,
75066
+ category: options.category,
75067
+ tags
75068
+ });
75069
+ clearRegistryCache();
75070
+ if (options.json)
75071
+ console.log(JSON.stringify({ created: result2.created, name: result2.name, path: result2.path, category: result2.manifest.category, tags: result2.manifest.tags }));
75072
+ else {
75073
+ console.log(source_default.green(`\u2713 Created custom skill '${result2.name}' at ${result2.path}`));
75074
+ console.log(source_default.dim(` Category: ${result2.manifest.category}`));
75075
+ console.log(source_default.dim(` Tags: ${result2.manifest.tags?.join(", ")}`));
75076
+ console.log(` ${source_default.cyan("Edit:")} ${join33(result2.path, "src", "index.ts")}`);
75077
+ console.log(` ${source_default.cyan("Run:")} skills run ${result2.name} --help`);
75078
+ }
75079
+ } catch (error2) {
75080
+ const message = error2.message;
75081
+ console.log(options.json ? JSON.stringify({ error: message }) : source_default.red(message));
74223
75082
  process.exitCode = 1;
74224
- return;
74225
- }
74226
- const description = options.description || `${bare} skill`;
74227
- const tags = options.tags ? options.tags.split(",").map((t) => t.trim()).filter(Boolean) : [bare];
74228
- const displayName2 = bare.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
74229
- mkdirSync17(join33(skillDir, "src"), { recursive: true });
74230
- writeFileSync18(join33(skillDir, "SKILL.md"), [
74231
- "---",
74232
- `name: ${bare}`,
74233
- `description: ${description}`,
74234
- `displayName: ${displayName2}`,
74235
- `category: ${options.category}`,
74236
- `tags: [${tags.join(", ")}]`,
74237
- "",
74238
- `# ${displayName2}`,
74239
- "",
74240
- description,
74241
- "",
74242
- "## Usage",
74243
- "",
74244
- "```bash",
74245
- `${bare} --help`,
74246
- "```",
74247
- ""
74248
- ].join(`
74249
- `));
74250
- writeFileSync18(join33(skillDir, "src", "index.ts"), [`#!/usr/bin/env bun`, `/**`, ` * ${displayName2} \u2014 ${description}`, ` */`, "", `console.log("${displayName2}");`, ""].join(`
74251
- `));
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) + `
74253
- `);
74254
- writeFileSync18(join33(skillDir, "tsconfig.json"), JSON.stringify({ compilerOptions: { target: "ES2022", module: "ESNext", moduleResolution: "bundler", strict: true, outDir: "dist" }, include: ["src/**/*.ts"] }, null, 2) + `
74255
- `);
74256
- clearRegistryCache();
74257
- if (options.json)
74258
- console.log(JSON.stringify({ created: true, name: bare, path: skillDir, category: options.category, tags }));
74259
- else {
74260
- console.log(source_default.green(`\u2713 Created custom skill '${bare}' at ${skillDir}`));
74261
- console.log(source_default.dim(` Category: ${options.category}`));
74262
- console.log(source_default.dim(` Tags: ${tags.join(", ")}`));
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")}`);
74265
75083
  }
74266
75084
  }
74267
75085
  function handleSync(names, options) {
@@ -74507,14 +75325,14 @@ var init_create_sync_config = __esm(() => {
74507
75325
  });
74508
75326
 
74509
75327
  // src/lib/station-hydrate.ts
74510
- import { createHash as createHash7 } from "crypto";
75328
+ import { createHash as createHash8 } from "crypto";
74511
75329
  import {
74512
75330
  copyFileSync as copyFileSync3,
74513
- mkdirSync as mkdirSync18,
75331
+ mkdirSync as mkdirSync17,
74514
75332
  readdirSync as readdirSync17,
74515
- readFileSync as readFileSync26,
75333
+ readFileSync as readFileSync27,
74516
75334
  statSync as statSync19,
74517
- writeFileSync as writeFileSync19
75335
+ writeFileSync as writeFileSync18
74518
75336
  } from "fs";
74519
75337
  import { dirname as dirname13, join as join34, resolve as resolve3, sep as sep5 } from "path";
74520
75338
  function fail2(code, message, detail = []) {
@@ -74528,7 +75346,7 @@ function readSnapshotManifest(repoRoot, stationId) {
74528
75346
  const manifestPath = join34(snapshotRoot, "sync-manifest.json");
74529
75347
  let manifest;
74530
75348
  try {
74531
- manifest = JSON.parse(readFileSync26(manifestPath, "utf8"));
75349
+ manifest = JSON.parse(readFileSync27(manifestPath, "utf8"));
74532
75350
  } catch (error2) {
74533
75351
  fail2("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error2.message}`);
74534
75352
  }
@@ -74664,7 +75482,7 @@ function planStationHydration(stationId, repoRoot) {
74664
75482
  for (const copy of copies) {
74665
75483
  let isStub = false;
74666
75484
  try {
74667
- isStub = isPointerSkillMd(readFileSync26(copy.fullPath, "utf8"));
75485
+ isStub = isPointerSkillMd(readFileSync27(copy.fullPath, "utf8"));
74668
75486
  } catch {
74669
75487
  isStub = false;
74670
75488
  }
@@ -74710,7 +75528,7 @@ function skillSha256(skill) {
74710
75528
  return sha256File(skill.files[0].winner.fullPath);
74711
75529
  }
74712
75530
  const joined = skill.files.map((file) => sha256File(file.winner.fullPath));
74713
- return createHash7("sha256").update(joined.sort().join(`
75531
+ return createHash8("sha256").update(joined.sort().join(`
74714
75532
  `)).digest("hex");
74715
75533
  }
74716
75534
  function writeStationHydration(options) {
@@ -74769,11 +75587,11 @@ function writeStationHydration(options) {
74769
75587
  }
74770
75588
  let written = 0;
74771
75589
  for (const entry of toWrite) {
74772
- mkdirSync18(dirname13(entry.destination), { recursive: true });
75590
+ mkdirSync17(dirname13(entry.destination), { recursive: true });
74773
75591
  copyFileSync3(entry.fullPath, entry.destination);
74774
75592
  written += 1;
74775
75593
  }
74776
- const unchanged = plan.totalFiles - written;
75594
+ const unchanged2 = plan.totalFiles - written;
74777
75595
  const hydration = {
74778
75596
  schema: STATION_HYDRATION_MANIFEST_SCHEMA,
74779
75597
  stationId: options.stationId,
@@ -74784,20 +75602,20 @@ function writeStationHydration(options) {
74784
75602
  stats: {
74785
75603
  idents: plan.winners.length,
74786
75604
  written,
74787
- unchanged,
75605
+ unchanged: unchanged2,
74788
75606
  files: plan.totalFiles,
74789
75607
  bytes: plan.totalBytes
74790
75608
  },
74791
75609
  skills: resultSkills
74792
75610
  };
74793
75611
  const hydrationManifestPath = join34(dirname13(cacheRoot), `hydration-${options.stationId}.json`);
74794
- mkdirSync18(dirname13(hydrationManifestPath), { recursive: true });
74795
- writeFileSync19(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
75612
+ mkdirSync17(dirname13(hydrationManifestPath), { recursive: true });
75613
+ writeFileSync18(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
74796
75614
  `);
74797
75615
  return {
74798
75616
  ...base2,
74799
75617
  mode: "apply",
74800
- stats: { ...base2.stats, written, unchanged },
75618
+ stats: { ...base2.stats, written, unchanged: unchanged2 },
74801
75619
  manifestPath: hydrationManifestPath
74802
75620
  };
74803
75621
  }
@@ -75194,7 +76012,7 @@ var init_schedule = __esm(() => {
75194
76012
  });
75195
76013
 
75196
76014
  // src/lib/registry-sync.ts
75197
- import { mkdirSync as mkdirSync19, writeFileSync as writeFileSync20 } from "fs";
76015
+ import { mkdirSync as mkdirSync18, writeFileSync as writeFileSync19 } from "fs";
75198
76016
  import { dirname as dirname14, relative as relative6 } from "path";
75199
76017
  function createRegistrySyncArtifact(options = {}) {
75200
76018
  const profile = options.profile ?? "all";
@@ -75252,8 +76070,8 @@ function createRegistrySyncArtifact(options = {}) {
75252
76070
  };
75253
76071
  }
75254
76072
  function writeRegistrySyncArtifact(path, artifact) {
75255
- mkdirSync19(dirname14(path), { recursive: true });
75256
- writeFileSync20(path, `${JSON.stringify(artifact, null, 2)}
76073
+ mkdirSync18(dirname14(path), { recursive: true });
76074
+ writeFileSync19(path, `${JSON.stringify(artifact, null, 2)}
75257
76075
  `);
75258
76076
  }
75259
76077
  function buildDocs(name) {
@@ -75426,7 +76244,7 @@ __export(exports_publish, {
75426
76244
  PushSkillError: () => PushSkillError
75427
76245
  });
75428
76246
  import { execFileSync } from "child_process";
75429
- import { existsSync as existsSync30, readFileSync as readFileSync27 } from "fs";
76247
+ import { existsSync as existsSync30, readFileSync as readFileSync28 } from "fs";
75430
76248
  import { hostname as hostname2 } from "os";
75431
76249
  import { join as join35 } from "path";
75432
76250
  function registerPublish(parent) {
@@ -75476,7 +76294,7 @@ async function pushSkill(name, options = {}) {
75476
76294
  const packed = packSkillBundle(skill.path, { maxUnpackedBytes: MAX_UNPACKED_BYTES });
75477
76295
  const versionManifest = buildVersionManifest(skill.path, packed);
75478
76296
  const skillMdPath = join35(skill.path, "SKILL.md");
75479
- const skillMd = existsSync30(skillMdPath) ? readFileSync27(skillMdPath, "utf-8") : undefined;
76297
+ const skillMd = existsSync30(skillMdPath) ? readFileSync28(skillMdPath, "utf-8") : undefined;
75480
76298
  const base2 = {
75481
76299
  slug: skill.name,
75482
76300
  path: skill.path,
@@ -75680,72 +76498,35 @@ import { createInterface as createInterface2 } from "readline";
75680
76498
  function prompt(question) {
75681
76499
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
75682
76500
  return new Promise((resolve4) => {
75683
- rl.question(question, (answer) => {
76501
+ let settled = false;
76502
+ const finish = (answer) => {
76503
+ if (settled)
76504
+ return;
76505
+ settled = true;
75684
76506
  rl.close();
75685
- resolve4(answer.trim());
75686
- });
76507
+ if (answer === null)
76508
+ process.exitCode = 130;
76509
+ resolve4(answer);
76510
+ };
76511
+ rl.once("SIGINT", () => finish(null));
76512
+ rl.once("close", () => finish(null));
76513
+ rl.question(question, (answer) => finish(answer.trim()));
75687
76514
  });
75688
76515
  }
75689
- function redactUrl(url) {
75690
- try {
75691
- const parsed = new URL(url);
75692
- if (!parsed.username && !parsed.password)
75693
- return url;
75694
- parsed.username = "";
75695
- parsed.password = "";
75696
- return parsed.toString().replace(/\/+$/, "");
75697
- } catch {
75698
- return url;
75699
- }
75700
- }
75701
- async function apiRequest(path, options) {
75702
- const url = getApiUrl(`${(options?.method || "GET").toUpperCase()} ${path}`);
75703
- const safeUrl = redactUrl(url);
75704
- const endpoint = `${(options?.method || "GET").toUpperCase()} ${safeUrl}${path}`;
75705
- let res;
76516
+ function authForPrompt() {
75706
76517
  try {
75707
- res = await fetch(`${url}${path}`, {
75708
- ...options,
75709
- headers: { "Content-Type": "application/json", ...options?.headers }
75710
- });
75711
- } catch (err) {
75712
- throw new HostedApiError(`Unable to reach the Skills API: ${err.message}`, {
75713
- endpoint,
75714
- apiUrl: safeUrl
75715
- });
75716
- }
75717
- const text = await res.text();
75718
- const body = text ? parseJsonBody(text) : {};
75719
- if (!res.ok) {
75720
- const record3 = isRecord4(body) ? body : {};
75721
- const detail = typeof record3.detail === "string" ? record3.detail : undefined;
75722
- const error2 = typeof record3.error === "string" ? record3.error : undefined;
75723
- const code = typeof record3.code === "string" ? record3.code : undefined;
75724
- throw new HostedApiError(detail || error2 || `${res.status} ${res.statusText}`, {
75725
- status: res.status,
75726
- code,
75727
- detail,
75728
- endpoint,
75729
- apiUrl: safeUrl
75730
- });
75731
- }
75732
- return body;
75733
- }
75734
- function parseJsonBody(text) {
75735
- try {
75736
- return JSON.parse(text);
75737
- } catch {
75738
- return { detail: condenseErrorBody(text) };
76518
+ return getAuthConfig();
76519
+ } catch (error2) {
76520
+ if (error2 instanceof SkillsFleetCredentialError && error2.code === "MISSING_API_CREDENTIAL")
76521
+ return null;
76522
+ throw error2;
75739
76523
  }
75740
76524
  }
75741
- function condenseErrorBody(text) {
75742
- const stripped = /<[a-z!/]/i.test(text) ? text.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]*>/g, " ") : text;
75743
- const collapsed = stripped.replace(/\s+/g, " ").trim();
75744
- if (collapsed.length <= MAX_ERROR_DETAIL_LENGTH)
75745
- return collapsed;
75746
- return `${collapsed.slice(0, MAX_ERROR_DETAIL_LENGTH - 1).trimEnd()}\u2026`;
76525
+ async function apiRequest(path, options, instance) {
76526
+ const origin = instance ?? getApiUrl(`${(options?.method || "GET").toUpperCase()} ${path}`);
76527
+ return new RemoteSkillsAuthClient(origin).request(path, options);
75747
76528
  }
75748
- function isRecord4(value) {
76529
+ function isRecord5(value) {
75749
76530
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
75750
76531
  }
75751
76532
  function commandErrorPayload(err, fallback) {
@@ -75759,7 +76540,7 @@ function commandErrorPayload(err, fallback) {
75759
76540
  ...err.apiUrl ? { apiUrl: err.apiUrl } : {}
75760
76541
  };
75761
76542
  }
75762
- const code = isRecord4(err) && typeof err.code === "string" ? err.code : undefined;
76543
+ const code = isRecord5(err) && typeof err.code === "string" ? err.code : undefined;
75763
76544
  return {
75764
76545
  error: err?.message || fallback,
75765
76546
  ...code ? { code } : {}
@@ -75783,19 +76564,11 @@ function writeCommandError(err, fallback, json) {
75783
76564
  }
75784
76565
  process.exitCode = 1;
75785
76566
  }
75786
- function credentialSource() {
75787
- try {
75788
- const fleet = resolveSkillsFleet();
75789
- return fleet.mode === "hosted" ? fleet.apiKeySource : null;
75790
- } catch {
75791
- return null;
75792
- }
75793
- }
75794
76567
  function stringField2(value) {
75795
76568
  return typeof value === "string" && value.length > 0 ? value : undefined;
75796
76569
  }
75797
76570
  function recordField(value) {
75798
- return isRecord4(value) ? value : undefined;
76571
+ return isRecord5(value) ? value : undefined;
75799
76572
  }
75800
76573
  function authIdentityPayload(authSource, live, cached2, offline = false) {
75801
76574
  const root = recordField(live) ?? {};
@@ -75852,7 +76625,7 @@ function openBrowser(url) {
75852
76625
  Bun.spawn(command, { stdout: "ignore", stderr: "ignore" });
75853
76626
  } catch {}
75854
76627
  }
75855
- async function ensureApiKey(loginResult) {
76628
+ async function ensureApiKey(loginResult, origin) {
75856
76629
  if (loginResult.apiKey)
75857
76630
  return loginResult.apiKey;
75858
76631
  if (!loginResult.token)
@@ -75861,11 +76634,11 @@ async function ensureApiKey(loginResult) {
75861
76634
  method: "POST",
75862
76635
  headers: { Authorization: `Bearer ${loginResult.token}` },
75863
76636
  body: JSON.stringify({ name: "cli" })
75864
- });
76637
+ }, origin);
75865
76638
  return keyRes.key;
75866
76639
  }
75867
- async function persistLoginResult(loginResult) {
75868
- const storedKey = await ensureApiKey(loginResult);
76640
+ async function persistLoginResult(loginResult, origin, env3) {
76641
+ const storedKey = await ensureApiKey(loginResult, origin);
75869
76642
  if (!storedKey)
75870
76643
  return;
75871
76644
  saveAuthConfig({
@@ -75874,7 +76647,7 @@ async function persistLoginResult(loginResult) {
75874
76647
  orgId: loginResult.organization.id,
75875
76648
  orgSlug: loginResult.organization.slug,
75876
76649
  userId: loginResult.user.id
75877
- });
76650
+ }, env3, origin);
75878
76651
  return storedKey;
75879
76652
  }
75880
76653
  function printLoginSuccess(loginResult, json) {
@@ -75895,6 +76668,14 @@ function printLoginSuccess(loginResult, json) {
75895
76668
  }
75896
76669
  }
75897
76670
  async function doLogin(email2, code, json) {
76671
+ const env3 = { ...process.env };
76672
+ let origin;
76673
+ try {
76674
+ origin = getApiUrl("Sign in");
76675
+ } catch (error2) {
76676
+ writeCommandError(error2, "Configure a Skills API before signing in", json);
76677
+ return;
76678
+ }
75898
76679
  if (!email2 || !email2.includes("@")) {
75899
76680
  writeCommandError(new Error("Invalid email"), "Invalid email", json);
75900
76681
  process.exitCode = 1;
@@ -75908,7 +76689,7 @@ async function doLogin(email2, code, json) {
75908
76689
  sendRes = await apiRequest("/api/auth/login", {
75909
76690
  method: "POST",
75910
76691
  body: JSON.stringify({ email: email2 })
75911
- });
76692
+ }, origin);
75912
76693
  } catch (err) {
75913
76694
  writeCommandError(err, "Failed to request login code", json);
75914
76695
  return;
@@ -75923,14 +76704,17 @@ async function doLogin(email2, code, json) {
75923
76704
  console.log(JSON.stringify({ status: "code_sent", email: email2, message: "Check email for 6-digit code, then run: skills auth login --email " + email2 + " --code <CODE>" }));
75924
76705
  return;
75925
76706
  }
75926
- code = await prompt(source_default.bold("Code: "));
76707
+ const answer = await prompt(source_default.bold("Code: "));
76708
+ if (answer === null)
76709
+ return;
76710
+ code = answer;
75927
76711
  }
75928
76712
  let verifyRes;
75929
76713
  try {
75930
76714
  verifyRes = await apiRequest("/api/auth/verify", {
75931
76715
  method: "POST",
75932
76716
  body: JSON.stringify({ email: email2, code })
75933
- });
76717
+ }, origin);
75934
76718
  } catch (err) {
75935
76719
  writeCommandError(err, "Failed to verify login code", json);
75936
76720
  return;
@@ -75941,7 +76725,7 @@ async function doLogin(email2, code, json) {
75941
76725
  }
75942
76726
  let storedKey;
75943
76727
  try {
75944
- storedKey = await persistLoginResult(verifyRes);
76728
+ storedKey = await persistLoginResult(verifyRes, origin, env3);
75945
76729
  } catch (err) {
75946
76730
  writeCommandError(err, "Login succeeded but API key creation failed", json);
75947
76731
  return;
@@ -75953,6 +76737,14 @@ async function doLogin(email2, code, json) {
75953
76737
  printLoginSuccess(verifyRes, Boolean(json));
75954
76738
  }
75955
76739
  async function doApiKeyLogin(apiKey, json) {
76740
+ const env3 = { ...process.env };
76741
+ let origin;
76742
+ try {
76743
+ origin = getApiUrl("Verify API key");
76744
+ } catch (error2) {
76745
+ writeCommandError(error2, "Configure a Skills API before signing in", json);
76746
+ return;
76747
+ }
75956
76748
  const trimmed = apiKey.trim();
75957
76749
  if (!trimmed) {
75958
76750
  writeCommandError(new Error("API key required"), "API key required", json);
@@ -75962,7 +76754,7 @@ async function doApiKeyLogin(apiKey, json) {
75962
76754
  try {
75963
76755
  whoami = await apiRequest("/api/auth/whoami", {
75964
76756
  headers: { Authorization: `Bearer ${trimmed}` }
75965
- });
76757
+ }, origin);
75966
76758
  } catch (err) {
75967
76759
  writeCommandError(err, "Failed to verify API key", json);
75968
76760
  return;
@@ -75978,7 +76770,7 @@ async function doApiKeyLogin(apiKey, json) {
75978
76770
  ...orgId ? { orgId } : {},
75979
76771
  ...orgSlug ? { orgSlug } : {},
75980
76772
  ...userId ? { userId } : {}
75981
- });
76773
+ }, env3, origin);
75982
76774
  if (json || !isTTY) {
75983
76775
  console.log(JSON.stringify({ ...identity2, status: "authenticated" }, null, 2));
75984
76776
  return;
@@ -75986,12 +76778,25 @@ async function doApiKeyLogin(apiKey, json) {
75986
76778
  printWhoami(identity2);
75987
76779
  }
75988
76780
  async function doDeviceLogin(options) {
76781
+ const timeoutMs = Number(options.pollTimeoutMs ?? DEFAULT_DEVICE_POLL_TIMEOUT_MS);
76782
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > DEFAULT_DEVICE_POLL_TIMEOUT_MS) {
76783
+ writeCommandError(new Error("Device polling timeout must be an integer from 1 to 600000 milliseconds"), "Invalid polling timeout", options.json);
76784
+ return;
76785
+ }
76786
+ const env3 = { ...process.env };
76787
+ let origin;
76788
+ try {
76789
+ origin = getApiUrl("Device sign in");
76790
+ } catch (error3) {
76791
+ writeCommandError(error3, "Configure a Skills API before signing in", options.json);
76792
+ return;
76793
+ }
75989
76794
  let start;
75990
76795
  try {
75991
76796
  start = await apiRequest("/api/auth/device/start", {
75992
76797
  method: "POST",
75993
76798
  body: JSON.stringify({ client: "skills-cli" })
75994
- });
76799
+ }, origin);
75995
76800
  } catch (err) {
75996
76801
  writeCommandError(err, "Failed to start device login", options.json);
75997
76802
  return;
@@ -76035,8 +76840,8 @@ Sign in in your browser
76035
76840
  console.log(source_default.dim(`
76036
76841
  Waiting for authentication...`));
76037
76842
  }
76038
- const intervalMs = Math.max(1000, Number(start.interval || 5) * 1000);
76039
- const timeoutMs = Number(options.pollTimeoutMs || DEFAULT_DEVICE_POLL_TIMEOUT_MS);
76843
+ const interval = Number(start.interval ?? 5);
76844
+ const intervalMs = Number.isFinite(interval) ? Math.min(30000, Math.max(1000, interval * 1000)) : 5000;
76040
76845
  const deadline = Date.now() + timeoutMs;
76041
76846
  while (Date.now() < deadline) {
76042
76847
  let tokenRes;
@@ -76044,13 +76849,13 @@ Waiting for authentication...`));
76044
76849
  tokenRes = await apiRequest("/api/auth/device/token", {
76045
76850
  method: "POST",
76046
76851
  body: JSON.stringify({ deviceCode: start.deviceCode })
76047
- });
76852
+ }, origin);
76048
76853
  } catch (err) {
76049
76854
  writeCommandError(err, "Failed to poll device login", options.json);
76050
76855
  return;
76051
76856
  }
76052
76857
  if (tokenRes.error === "authorization_pending" || tokenRes.status === "pending") {
76053
- await sleep(intervalMs);
76858
+ await sleep(Math.min(intervalMs, Math.max(0, deadline - Date.now())));
76054
76859
  continue;
76055
76860
  }
76056
76861
  if (tokenRes.error) {
@@ -76059,7 +76864,7 @@ Waiting for authentication...`));
76059
76864
  }
76060
76865
  let storedKey;
76061
76866
  try {
76062
- storedKey = await persistLoginResult(tokenRes);
76867
+ storedKey = await persistLoginResult(tokenRes, origin, env3);
76063
76868
  } catch (err) {
76064
76869
  writeCommandError(err, "Login succeeded but API key creation failed", options.json);
76065
76870
  return;
@@ -76080,6 +76885,30 @@ Waiting for authentication...`));
76080
76885
  }
76081
76886
  function registerAuth(parent) {
76082
76887
  const auth = parent.command("auth").description("Manage account authentication");
76888
+ const keys2 = auth.command("keys").description("Manage API keys on the configured instance");
76889
+ keys2.command("list").option("--json", "Output as JSON", false).requiredOption("--email <email>", "Account email for fresh reauthentication").requiredOption("--code <code>", "Fresh OTP requested through auth signup/login").action(async (options) => {
76890
+ try {
76891
+ console.log(JSON.stringify(await new RemoteSkillsAuthClient(getApiUrl("List API keys")).listApiKeys(options.email, options.code), null, 2));
76892
+ } catch (error2) {
76893
+ writeCommandError(error2, "Failed to list API keys", options.json);
76894
+ }
76895
+ });
76896
+ keys2.command("create").argument("<name>").option("--scope <scope>", "Limit key scope (repeatable)", (value, all) => [...all, value], []).option("--json", "Output the newly created key as JSON", false).requiredOption("--email <email>", "Account email for fresh reauthentication").requiredOption("--code <code>", "Fresh OTP requested through auth signup/login").description("Create a key; the returned secret is shown once and must be stored securely").action(async (name, options) => {
76897
+ try {
76898
+ const client = new RemoteSkillsAuthClient(getApiUrl("Create API key"));
76899
+ const created = await client.createApiKey(options.email, options.code, name, options.scope.length ? options.scope : undefined);
76900
+ console.log(JSON.stringify(created, null, 2));
76901
+ } catch (error2) {
76902
+ writeCommandError(error2, "Failed to create API key", options.json);
76903
+ }
76904
+ });
76905
+ keys2.command("revoke").argument("<key-id>").option("--json", "Output as JSON", false).requiredOption("--email <email>", "Account email for fresh reauthentication").requiredOption("--code <code>", "Fresh OTP requested through auth signup/login").action(async (id, options) => {
76906
+ try {
76907
+ console.log(JSON.stringify(await new RemoteSkillsAuthClient(getApiUrl("Revoke API key")).revokeApiKey(options.email, options.code, id), null, 2));
76908
+ } catch (error2) {
76909
+ writeCommandError(error2, "Failed to revoke API key", options.json);
76910
+ }
76911
+ });
76083
76912
  auth.command("login").description("Sign in with browser/device code or email code").option("--email <email>", "Email address (non-interactive)").option("--code <code>", "Verification code (non-interactive)").option("--api-key <key>", "Verify and store an API key").option("--device", "Use browser/device-code login", false).option("--no-open", "Do not open a browser for device-code login").option("--poll", "Poll until browser authentication completes in non-interactive mode", false).option("--poll-timeout-ms <ms>", "Maximum time to wait for device-code login").option("--json", "Output result as JSON", false).action(async (options) => {
76084
76913
  if (options.apiKey) {
76085
76914
  await doApiKeyLogin(options.apiKey, options.json);
@@ -76090,63 +76919,67 @@ function registerAuth(parent) {
76090
76919
  return;
76091
76920
  }
76092
76921
  let email2 = options.email;
76093
- if (!email2 && isTTY) {
76094
- const existing = getAuthConfig();
76922
+ if (!email2 && isTTY && !options.json) {
76923
+ const existing = authForPrompt();
76095
76924
  if (existing) {
76096
76925
  console.log(source_default.dim(`Already signed in as ${existing.email}`));
76097
76926
  const again = await prompt("Sign in with a different account? (y/N) ");
76098
- if (again.toLowerCase() !== "y")
76927
+ if (again === null || again.toLowerCase() !== "y")
76099
76928
  return;
76100
76929
  }
76101
- email2 = await prompt(source_default.bold("Email: "));
76930
+ const answer = await prompt(source_default.bold("Email: "));
76931
+ if (answer === null)
76932
+ return;
76933
+ email2 = answer;
76102
76934
  }
76103
76935
  if (!email2) {
76104
- console.error(source_default.red("Email required. Use: skills auth login --email you@example.com"));
76105
- process.exitCode = 1;
76936
+ writeCommandError(new Error("Email required. Use: skills auth login --email you@example.com"), "Email required", options.json);
76106
76937
  return;
76107
76938
  }
76108
76939
  await doLogin(email2, options.code, options.json);
76109
76940
  });
76110
- auth.command("signup").description("Create or sign in with your email (passwordless)").option("--email <email>", "Email address (non-interactive)").option("--code <code>", "Verification code (non-interactive)").action(async (options) => {
76941
+ auth.command("signup").description("Create or sign in with your email (passwordless)").option("--email <email>", "Email address (non-interactive)").option("--code <code>", "Verification code (non-interactive)").option("--json", "Output result as JSON without prompting", false).action(async (options) => {
76111
76942
  let email2 = options.email;
76112
- if (!email2 && isTTY) {
76113
- const existing = getAuthConfig();
76943
+ if (!email2 && isTTY && !options.json) {
76944
+ const existing = authForPrompt();
76114
76945
  if (existing) {
76115
76946
  console.log(source_default.dim(`Already signed in as ${existing.email}`));
76116
76947
  const again = await prompt("Continue with a different account? (y/N) ");
76117
- if (again.toLowerCase() !== "y")
76948
+ if (again === null || again.toLowerCase() !== "y")
76118
76949
  return;
76119
76950
  }
76120
- email2 = await prompt(source_default.bold("Email: "));
76951
+ const answer = await prompt(source_default.bold("Email: "));
76952
+ if (answer === null)
76953
+ return;
76954
+ email2 = answer;
76121
76955
  }
76122
76956
  if (!email2) {
76123
- console.error(source_default.red("Email required. Use: skills auth signup --email you@example.com"));
76957
+ const error2 = "Email required. Use: skills auth signup --email you@example.com";
76958
+ if (options.json)
76959
+ console.log(JSON.stringify({ error: error2 }));
76960
+ else
76961
+ console.error(source_default.red(error2));
76124
76962
  process.exitCode = 1;
76125
76963
  return;
76126
76964
  }
76127
- await doLogin(email2, options.code);
76965
+ await doLogin(email2, options.code, options.json);
76128
76966
  });
76129
- auth.command("logout").description("Sign out and remove stored credentials").action(() => {
76130
- const existing = getAuthConfig();
76131
- if (!existing) {
76132
- console.log(source_default.dim("Not signed in"));
76133
- return;
76134
- }
76967
+ auth.command("logout").description("Remove this profile's stored credentials; injected keys remain configured").option("--json", "Output as JSON", false).action((options) => {
76135
76968
  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
- }
76969
+ if (options.json)
76970
+ console.log(JSON.stringify({ status: stillResolves ? "credential_still_configured" : "signed_out", stillResolves }));
76971
+ else
76972
+ console.log(stillResolves ? "Stored credential removed. A credential is still configured by the environment, profile selection, or Keychain; clear it there to finish signing out." : "Signed out; this profile has no stored credential.");
76140
76973
  });
76141
76974
  auth.command("whoami").description("Show current account info").option("--json", "Output as JSON", false).action(async (options) => {
76142
76975
  let fleet;
76143
76976
  try {
76144
- fleet = resolveSkillsFleet();
76977
+ fleet = await resolveSkillsConnection();
76145
76978
  } catch (err) {
76146
76979
  writeCommandError(err, "Failed to resolve the Skills credential", options.json);
76147
76980
  return;
76148
76981
  }
76149
- if (fleet.mode !== "hosted") {
76982
+ if (!fleet) {
76150
76983
  const payload = {
76151
76984
  status: "unauthenticated",
76152
76985
  error: `Not signed in. Run: skills auth login, or set ${SKILLS_API_KEY_ENV}`
@@ -76157,12 +76990,12 @@ function registerAuth(parent) {
76157
76990
  console.log(source_default.dim(payload.error));
76158
76991
  return;
76159
76992
  }
76160
- const cached2 = fleet.apiKeyTier === "disk" ? getAuthIdentity() : null;
76993
+ const cached2 = fleet.apiKeyTier === "disk" || fleet.apiKeyTier === "profile" ? getAuthIdentity() : null;
76161
76994
  const authSource = fleet.apiKeySource;
76162
76995
  try {
76163
76996
  const res = await apiRequest("/api/auth/whoami", {
76164
76997
  headers: { Authorization: `Bearer ${fleet.apiKey}` }
76165
- });
76998
+ }, fleet.apiOrigin);
76166
76999
  const payload = authIdentityPayload(authSource, res, cached2);
76167
77000
  if (options.json) {
76168
77001
  console.log(JSON.stringify(payload, null, 2));
@@ -76170,7 +77003,7 @@ function registerAuth(parent) {
76170
77003
  printWhoami(payload);
76171
77004
  }
76172
77005
  } catch (err) {
76173
- if (cached2 && Object.keys(cached2).length > 0) {
77006
+ if (cached2 && Object.keys(cached2).length > 0 && !(err instanceof HostedApiError && err.status !== undefined && err.status < 500)) {
76174
77007
  const payload = authIdentityPayload(authSource, {}, cached2, true);
76175
77008
  if (options.json)
76176
77009
  console.log(JSON.stringify(payload, null, 2));
@@ -76182,30 +77015,15 @@ function registerAuth(parent) {
76182
77015
  }
76183
77016
  });
76184
77017
  }
76185
- var isTTY, DEFAULT_DEVICE_POLL_TIMEOUT_MS, MAX_ERROR_DETAIL_LENGTH = 200, CONFIG_HINT_STATUSES, HostedApiError;
77018
+ var isTTY, DEFAULT_DEVICE_POLL_TIMEOUT_MS, CONFIG_HINT_STATUSES;
76186
77019
  var init_auth = __esm(() => {
76187
77020
  init_source();
76188
77021
  init_auth_store();
76189
77022
  init_fleet_credentials();
77023
+ init_remote_auth();
76190
77024
  isTTY = process.stdin.isTTY && process.stdout.isTTY;
76191
77025
  DEFAULT_DEVICE_POLL_TIMEOUT_MS = 10 * 60 * 1000;
76192
77026
  CONFIG_HINT_STATUSES = new Set([401, 403, 404, 405, 501]);
76193
- HostedApiError = class HostedApiError extends Error {
76194
- status;
76195
- code;
76196
- detail;
76197
- endpoint;
76198
- apiUrl;
76199
- constructor(message, options = {}) {
76200
- super(message);
76201
- this.name = "HostedApiError";
76202
- this.status = options.status;
76203
- this.code = options.code;
76204
- this.detail = options.detail;
76205
- this.endpoint = options.endpoint;
76206
- this.apiUrl = options.apiUrl;
76207
- }
76208
- };
76209
77027
  });
76210
77028
 
76211
77029
  // src/cli/commands/feedback.ts
@@ -76348,7 +77166,7 @@ var init_storage = __esm(() => {
76348
77166
  });
76349
77167
 
76350
77168
  // src/lib/registry-reconcile.ts
76351
- import { existsSync as existsSync31, readFileSync as readFileSync28, statSync as statSync20, writeFileSync as writeFileSync21 } from "fs";
77169
+ import { existsSync as existsSync31, readFileSync as readFileSync29, statSync as statSync20, writeFileSync as writeFileSync20 } from "fs";
76352
77170
  import { join as join36 } from "path";
76353
77171
  function isDirectory2(path) {
76354
77172
  try {
@@ -76368,7 +77186,7 @@ function readBaseline(skillDir) {
76368
77186
  if (!existsSync31(markerPath))
76369
77187
  return;
76370
77188
  try {
76371
- const marker = JSON.parse(readFileSync28(markerPath, "utf-8"));
77189
+ const marker = JSON.parse(readFileSync29(markerPath, "utf-8"));
76372
77190
  return {
76373
77191
  ...typeof marker.contentHash === "string" && marker.contentHash ? { contentHash: marker.contentHash } : {},
76374
77192
  ...typeof marker.version === "string" && marker.version ? { version: marker.version } : {}
@@ -76382,7 +77200,7 @@ function readCursor(root) {
76382
77200
  if (!existsSync31(path))
76383
77201
  return { runCount: 0 };
76384
77202
  try {
76385
- const cursor = JSON.parse(readFileSync28(path, "utf-8"));
77203
+ const cursor = JSON.parse(readFileSync29(path, "utf-8"));
76386
77204
  return { runCount: typeof cursor.runCount === "number" ? cursor.runCount : 0 };
76387
77205
  } catch {
76388
77206
  return { runCount: 0 };
@@ -76510,7 +77328,9 @@ async function reconcileRegistry(options = {}) {
76510
77328
  locals.set(meta.name, { slug: meta.name, version: meta.version, sha256: "" });
76511
77329
  }
76512
77330
  }
76513
- const remoteRowsPayload = await client.listSkills();
77331
+ const remoteRowsPayload = await client.listSkills().catch(() => {
77332
+ throw new Error("Registry listing failed: the server refused or could not return the catalog.");
77333
+ });
76514
77334
  if (!Array.isArray(remoteRowsPayload)) {
76515
77335
  const shape = remoteRowsPayload && typeof remoteRowsPayload === "object" ? `object with keys [${Object.keys(remoteRowsPayload).join(", ")}]` : typeof remoteRowsPayload;
76516
77336
  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."]);
@@ -76659,7 +77479,7 @@ async function reconcileRegistry(options = {}) {
76659
77479
  runCount: readCursor(root).runCount + 1,
76660
77480
  summary
76661
77481
  };
76662
- writeFileSync21(join36(root, SYNC_CURSOR_FILE), `${JSON.stringify(cursor, null, 2)}
77482
+ writeFileSync20(join36(root, SYNC_CURSOR_FILE), `${JSON.stringify(cursor, null, 2)}
76663
77483
  `);
76664
77484
  return {
76665
77485
  corpusRoot: root,
@@ -84089,7 +84909,12 @@ if (process.argv.includes("--no-color")) {
84089
84909
  process.argv.splice(idx, 1);
84090
84910
  }
84091
84911
  var program2 = new Command;
84092
- program2.name("skills").description("Discover and run AI agent skills through the Skills CLI/MCP").version(package_default.version).option("--verbose", "Enable verbose logging", false).option("--no-color", "Disable colored output (also respects NO_COLOR env var)").enablePositionalOptions();
84912
+ program2.name("skills").description("Discover and run AI agent skills through the Skills CLI/MCP").version(package_default.version).option("--verbose", "Enable verbose logging", false).option("--profile <name>", "Use an isolated Skills instance credential profile").option("--no-color", "Disable colored output (also respects NO_COLOR env var)").enablePositionalOptions();
84913
+ program2.hook("preAction", () => {
84914
+ const profile = program2.opts().profile;
84915
+ if (profile !== undefined)
84916
+ process.env.HASNA_PROFILE = profile;
84917
+ });
84093
84918
  program2.command("interactive", { isDefault: true }).alias("i").allowExcessArguments(true).description("Interactive skill browser (TUI)").action((_options, command) => {
84094
84919
  const stray = command.args[0];
84095
84920
  if (stray !== undefined) {
@@ -84117,6 +84942,8 @@ var { registerDiagnostic: registerDiagnostic2 } = await Promise.resolve().then((
84117
84942
  registerDiagnostic2(program2);
84118
84943
  var { registerRuntime: registerRuntime2 } = await Promise.resolve().then(() => (init_runtime(), exports_runtime));
84119
84944
  registerRuntime2(program2);
84945
+ var { registerRemoteAccount: registerRemoteAccount2 } = await Promise.resolve().then(() => (init_remote_account2(), exports_remote_account));
84946
+ registerRemoteAccount2(program2);
84120
84947
  var { registerCompletion: registerCompletion2 } = await Promise.resolve().then(() => (init_completion(), exports_completion));
84121
84948
  registerCompletion2(program2);
84122
84949
  var { registerCreateSync: registerCreateSync2 } = await Promise.resolve().then(() => (init_create_sync_config(), exports_create_sync_config));