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