@hasna/skills 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +176 -5
  2. package/bin/index.js +6518 -4950
  3. package/bin/mcp.js +1173 -410
  4. package/bin/migrate.js +148 -40
  5. package/bin/server.js +53 -83
  6. package/bin/worker.js +41 -73
  7. package/dist/admin-contract.d.ts +37 -19
  8. package/dist/admin-contract.js +1 -1
  9. package/dist/cli/cli.test-utils.d.ts +10 -8
  10. package/dist/cli/commands/customer-profile.d.ts +2 -0
  11. package/dist/cli/commands/customer-verification.d.ts +5 -0
  12. package/dist/cli/commands/tool-primitives.d.ts +1 -1
  13. package/dist/cli/commands/workspace-member-mutations.d.ts +2 -0
  14. package/dist/cli/commands/workspace-members.d.ts +2 -0
  15. package/dist/cli/env-assignment.d.ts +9 -0
  16. package/dist/index.d.ts +5 -2
  17. package/dist/index.js +613 -153
  18. package/dist/lib/agent-sync.d.ts +13 -8
  19. package/dist/lib/api-url.d.ts +4 -3
  20. package/dist/lib/app-home.d.ts +0 -1
  21. package/dist/lib/client-types.d.ts +75 -0
  22. package/dist/lib/credential-state.d.ts +12 -0
  23. package/dist/lib/fleet-credentials.d.ts +41 -15
  24. package/dist/lib/home-adoption.d.ts +2 -0
  25. package/dist/lib/home-census.d.ts +3 -1
  26. package/dist/lib/local-opt-in.d.ts +24 -0
  27. package/dist/lib/portable-skills-files.d.ts +6 -2
  28. package/dist/lib/read-access.d.ts +83 -0
  29. package/dist/lib/remote-auth.d.ts +14 -0
  30. package/dist/lib/remote-client.d.ts +34 -5
  31. package/dist/lib/remote-profile.d.ts +26 -0
  32. package/dist/lib/remote-registry.d.ts +7 -3
  33. package/dist/lib/remote-workspace.d.ts +76 -0
  34. package/dist/lib/skillinfo.d.ts +1 -1
  35. package/dist/mcp/helpers.d.ts +22 -0
  36. package/dist/mcp/index.d.ts +16 -0
  37. package/dist/sdk/governance-store.d.ts +1 -0
  38. package/dist/sdk/index.d.ts +5 -2
  39. package/dist/sdk/index.js +1084 -283
  40. package/dist/sdk/outputs.d.ts +0 -11
  41. package/dist/sdk/runs.d.ts +1 -1
  42. package/dist/storage.js +6 -40
  43. package/docs/skill-standard.md +30 -2
  44. package/package.json +6 -4
  45. package/dist/lib/instance-credentials-race.fixture.d.ts +0 -1
package/dist/index.js CHANGED
@@ -111,12 +111,7 @@ import { homedir } from "os";
111
111
  import { join, resolve } from "path";
112
112
  import { homedir as pathsResolverHomedir } from "os";
113
113
  import { join as pathsResolverJoin } from "path";
114
- var PATHS_RESOLVER_KIND_ENV = {
115
- config: "HASNA_CONFIG_HOME",
116
- data: "HASNA_DATA_HOME",
117
- state: "HASNA_STATE_HOME",
118
- cache: "HASNA_CACHE_HOME"
119
- };
114
+ var PATHS_RESOLVER_DATA_ENV = "HASNA_DATA_HOME";
120
115
  var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
121
116
  function pathsResolverAssertApp(app) {
122
117
  if (typeof app !== "string" || app.length === 0) {
@@ -126,48 +121,19 @@ function pathsResolverAssertApp(app) {
126
121
  throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
127
122
  }
128
123
  }
129
- function pathsResolverAssertKind(kind) {
130
- if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
131
- throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
132
- }
133
- }
134
- function pathsResolverBaseDir(kind, options) {
135
- pathsResolverAssertKind(kind);
124
+ function pathsResolverDataBaseDir(options) {
136
125
  const env = options.env ?? process.env;
137
- const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
126
+ const override = env[PATHS_RESOLVER_DATA_ENV];
138
127
  if (typeof override === "string" && override.length > 0)
139
128
  return override;
140
129
  const home = options.home ?? pathsResolverHomedir();
141
130
  const platform = options.platform ?? process.platform;
142
- if (platform === "darwin") {
143
- switch (kind) {
144
- case "config":
145
- case "data":
146
- return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
147
- case "cache":
148
- return pathsResolverJoin(home, "Library", "Caches", "Hasna");
149
- case "state":
150
- return pathsResolverJoin(home, "Library", "Logs", "Hasna");
151
- }
152
- }
153
- switch (kind) {
154
- case "config":
155
- return pathsResolverJoin(home, ".config", "hasna");
156
- case "data":
157
- return pathsResolverJoin(home, ".local", "share", "hasna");
158
- case "state":
159
- return pathsResolverJoin(home, ".local", "state", "hasna");
160
- case "cache":
161
- return pathsResolverJoin(home, ".cache", "hasna");
162
- }
163
- }
164
- function pathsResolverResolve(kind, options) {
165
- pathsResolverAssertApp(options.app);
166
- const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
167
- return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
131
+ return platform === "darwin" ? pathsResolverJoin(home, "Library", "Application Support", "Hasna") : pathsResolverJoin(home, ".local", "share", "hasna");
168
132
  }
169
133
  function dataDir(options) {
170
- return pathsResolverResolve("data", options);
134
+ pathsResolverAssertApp(options.app);
135
+ const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
136
+ return pathsResolverJoin(pathsResolverDataBaseDir(options), appSegment);
171
137
  }
172
138
  var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
173
139
  var HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME";
@@ -1838,14 +1804,27 @@ function normalizePortableSkillName(name) {
1838
1804
  }
1839
1805
  return normalized;
1840
1806
  }
1807
+ function normalizeNewPortableSkillName(name) {
1808
+ normalizePortableSkillName(name);
1809
+ 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, "");
1810
+ if (!normalized)
1811
+ throw new Error(`Invalid skill name '${name}'. Include letters or numbers.`);
1812
+ return normalized;
1813
+ }
1841
1814
  function readPortableSkillManifest(skillPath, fallbackName = basename(skillPath)) {
1815
+ return readManifest(skillPath, fallbackName, normalizePortableSkillName);
1816
+ }
1817
+ function readPortableSkillManifestForImport(skillPath) {
1818
+ return readManifest(skillPath, basename(skillPath), normalizeNewPortableSkillName);
1819
+ }
1820
+ function readManifest(skillPath, fallbackName, normalizeName) {
1842
1821
  const skillJsonPath = join6(skillPath, "skill.json");
1843
1822
  const skillMdPath = join6(skillPath, "SKILL.md");
1844
1823
  const pkgPath = join6(skillPath, "package.json");
1845
1824
  const jsonManifest = existsSync6(skillJsonPath) ? readJsonObject(skillJsonPath) : undefined;
1846
1825
  const frontmatter = existsSync6(skillMdPath) ? parseSkillFrontmatter(readFileSync5(skillMdPath, "utf-8")) ?? undefined : undefined;
1847
1826
  const pkg = existsSync6(pkgPath) ? readJsonObject(pkgPath) : undefined;
1848
- const name = normalizePortableSkillName(stringField(jsonManifest, "name") ?? frontmatter?.name ?? stringValue(pkg?.name) ?? fallbackName);
1827
+ const name = normalizeName(stringField(jsonManifest, "name") ?? frontmatter?.name ?? stringValue(pkg?.name) ?? fallbackName);
1849
1828
  const description = stringField(jsonManifest, "description") ?? frontmatter?.description ?? stringValue(pkg?.description) ?? `${name} skill`;
1850
1829
  const version = readDeclaredSkillVersion(skillPath) ?? PORTABLE_SKILL_DEFAULT_VERSION;
1851
1830
  const kind = parseSkillKind(stringField(jsonManifest, "kind") ?? frontmatter?.kind);
@@ -2093,10 +2072,45 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
2093
2072
  };
2094
2073
  if (!existsSync6(join6(skillPath, "SKILL.md"))) {
2095
2074
  writeFileSync2(join6(skillPath, "SKILL.md"), renderSkillMd(next));
2075
+ } else {
2076
+ const path = join6(skillPath, "SKILL.md");
2077
+ const content = readFileSync5(path, "utf8");
2078
+ const declaredName = parseSkillFrontmatter(content)?.name;
2079
+ if (declaredName && declaredName !== next.name) {
2080
+ writeFileSync2(path, renameInstructionFrontmatter(content, next.name));
2081
+ }
2082
+ }
2083
+ const packagePath = join6(skillPath, "package.json");
2084
+ if (existsSync6(packagePath)) {
2085
+ const pkg = readJsonObject(packagePath);
2086
+ if (typeof pkg.name === "string" && pkg.name !== next.name) {
2087
+ writeFileSync2(packagePath, `${JSON.stringify({ ...pkg, name: next.name }, null, 2)}
2088
+ `);
2089
+ }
2096
2090
  }
2097
2091
  writeSkillJsonWithHash(skillPath, next);
2098
2092
  return readPortableSkillManifest(skillPath, next.name);
2099
2093
  }
2094
+ function renameInstructionFrontmatter(content, name) {
2095
+ const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?=\r?\n|$)/);
2096
+ const names = frontmatter?.[1]?.match(/^[ \t]*name[ \t]*:[^\r\n]*/gm) ?? [];
2097
+ const declaration = names.length === 1 ? names[0].match(/^(name[ \t]*:[ \t]*)(.*?)([ \t]*)$/) : null;
2098
+ const scalar = declaration?.[2] ?? "";
2099
+ let simple = /^[a-zA-Z0-9_.@/ -]+$/.test(scalar);
2100
+ if (scalar.startsWith('"')) {
2101
+ try {
2102
+ simple = typeof JSON.parse(scalar) === "string";
2103
+ } catch {
2104
+ simple = false;
2105
+ }
2106
+ } else if (scalar.startsWith("'"))
2107
+ simple = /^'[^'\r\n]*'$/.test(scalar);
2108
+ if (!frontmatter || !declaration || !simple) {
2109
+ throw new Error("Cannot rename instruction SKILL.md: use one unambiguous top-level name scalar in frontmatter.");
2110
+ }
2111
+ const renamed = frontmatter[0].replace(/^name[ \t]*:[^\r\n]*/m, () => `${declaration[1]}${name}${declaration[3]}`);
2112
+ return renamed + content.slice(frontmatter[0].length);
2113
+ }
2100
2114
  function copySkillDirectory(source, destination) {
2101
2115
  const resolvedSource = lstatSync2(source).isSymbolicLink() ? realpathSync(source) : source;
2102
2116
  mkdirSync2(destination, { recursive: true });
@@ -2504,7 +2518,7 @@ function isOfficialSkillName(name) {
2504
2518
  return OFFICIAL_SKILL_NAMES.has(name);
2505
2519
  }
2506
2520
  function scaffoldPortableSkill(name, options = {}) {
2507
- const skillName = normalizePortableSkillName(name);
2521
+ const skillName = normalizeNewPortableSkillName(name);
2508
2522
  const root = getPortableSkillsRoot(options);
2509
2523
  const skillPath = join7(root, skillName);
2510
2524
  if (existsSync7(skillPath)) {
@@ -2574,9 +2588,9 @@ function portPortableSkill(sourcePath, options = {}) {
2574
2588
  if (!existsSync7(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
2575
2589
  throw new Error(`Skill source directory not found: ${sourcePath}`);
2576
2590
  }
2577
- const inferred = readPortableSkillManifest(absoluteSource, basename2(absoluteSource));
2591
+ const inferred = readPortableSkillManifestForImport(absoluteSource);
2578
2592
  const explicitName = options.name != null;
2579
- const skillName = normalizePortableSkillName(options.name ?? inferred.name);
2593
+ const skillName = normalizeNewPortableSkillName(options.name ?? inferred.name);
2580
2594
  if (isOfficialSkillName(skillName) && !options.allowShadow) {
2581
2595
  const sourceSlug = safeNormalizeName(basename2(absoluteSource));
2582
2596
  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.`;
@@ -3191,6 +3205,7 @@ import { fileURLToPath } from "url";
3191
3205
  import {
3192
3206
  cpSync as cpSync3,
3193
3207
  existsSync as existsSync9,
3208
+ lstatSync as lstatSync3,
3194
3209
  mkdirSync as mkdirSync4,
3195
3210
  mkdtempSync as mkdtempSync2,
3196
3211
  readFileSync as readFileSync7,
@@ -3212,6 +3227,20 @@ var SYNC_AGENTS = ["claude", "codewith", "codex", "opencode", "cursor"];
3212
3227
  var SKILLS_SOURCE_ENV = "SKILLS_SOURCE";
3213
3228
  var SYNC_MARKER_FILE = ".hasna-skills.json";
3214
3229
  var SYNC_MARKER_MANAGED_BY = "@hasna/skills";
3230
+ function isSkillsOwnershipMarker(marker) {
3231
+ return typeof marker === "object" && marker !== null && Object.hasOwn(marker, "managedBy") && marker.managedBy === SYNC_MARKER_MANAGED_BY;
3232
+ }
3233
+ function hasSkillsOwnershipMarker(dir) {
3234
+ const path = join9(dir, SYNC_MARKER_FILE);
3235
+ try {
3236
+ if (!lstatSync3(path).isFile())
3237
+ return false;
3238
+ const marker = JSON.parse(readFileSync7(path, "utf8"));
3239
+ return isSkillsOwnershipMarker(marker);
3240
+ } catch {
3241
+ return false;
3242
+ }
3243
+ }
3215
3244
  function isSyncAgent(value) {
3216
3245
  return SYNC_AGENTS.includes(value);
3217
3246
  }
@@ -3395,7 +3424,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
3395
3424
  const skillMdPath = join9(dir, "SKILL.md");
3396
3425
  const markerPath = join9(dir, SYNC_MARKER_FILE);
3397
3426
  const dirExists = existsSync9(dir);
3398
- const managed = existsSync9(markerPath);
3427
+ const managed = hasSkillsOwnershipMarker(dir);
3399
3428
  const hasSkillMd = existsSync9(skillMdPath);
3400
3429
  if (dirExists && !managed && !hasSkillMd) {
3401
3430
  return {
@@ -3408,7 +3437,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
3408
3437
  return {
3409
3438
  action: "skip",
3410
3439
  path: skillMdPath,
3411
- reason: "an unmanaged SKILL.md already exists here (hand-authored); pass --force to overwrite"
3440
+ reason: existsSync9(markerPath) ? "an unmanaged SKILL.md already exists here (invalid or foreign ownership marker); pass --force to overwrite" : "an unmanaged SKILL.md already exists here (hand-authored); pass --force to overwrite"
3412
3441
  };
3413
3442
  }
3414
3443
  if (dirExists && managed && hasSkillMd && !options.force && isPointerSkillMd(skillMd)) {
@@ -3485,7 +3514,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
3485
3514
  }
3486
3515
  function removeManagedAgentSkill(skill, agent, homeDir = homedir2()) {
3487
3516
  const dir = join9(agentGlobalSkillsDir(agent, homeDir), skill);
3488
- if (!existsSync9(join9(dir, SYNC_MARKER_FILE)))
3517
+ if (!hasSkillsOwnershipMarker(dir))
3489
3518
  return false;
3490
3519
  rmSync2(dir, { recursive: true, force: true });
3491
3520
  return true;
@@ -3861,7 +3890,7 @@ function removeSkillForAgent(name, options) {
3861
3890
  const canonicalName = getCanonicalSkillName(name);
3862
3891
  const scope = options.scope ?? "global";
3863
3892
  const dir = getAgentSkillPath(canonicalName, options.agent, scope, options.projectDir);
3864
- if (!existsSync11(join11(dir, SYNC_MARKER_FILE)))
3893
+ if (!hasSkillsOwnershipMarker(dir))
3865
3894
  return false;
3866
3895
  rmSync3(dir, { recursive: true, force: true });
3867
3896
  return true;
@@ -4288,7 +4317,7 @@ async function runSkill(name, args, options = {}) {
4288
4317
  }
4289
4318
  const proc = Bun.spawn(["bun", "run", entryPath, ...args], {
4290
4319
  cwd: skillPath,
4291
- stdout: options.stdio === "pipe" ? "pipe" : "inherit",
4320
+ stdout: options.stdio === "pipe" ? "pipe" : options.stdio === "stderr" ? 2 : "inherit",
4292
4321
  stderr: options.stdio === "pipe" ? "pipe" : "inherit",
4293
4322
  stdin: "inherit",
4294
4323
  env: { ...process.env, ...options.env }
@@ -9106,7 +9135,7 @@ var AUTHORITY_OVERRIDE_HEADERS = new Set([
9106
9135
  ]);
9107
9136
 
9108
9137
  // src/lib/instance-credentials.ts
9109
- import { closeSync as closeSync2, constants, fstatSync as fstatSync2, lstatSync as lstatSync3, openSync as openSync2, readSync } from "fs";
9138
+ import { closeSync as closeSync2, constants, fstatSync as fstatSync2, lstatSync as lstatSync4, openSync as openSync2, readSync } from "fs";
9110
9139
  var SKILLS_BOUND_API_URL = "HASNA_SKILLS_BOUND_API_URL";
9111
9140
  function selectedSkillsProfile(env, explicit) {
9112
9141
  const selected = explicit ?? env.HASNA_PROFILE;
@@ -9122,7 +9151,7 @@ function skillsProfileCredentialFiles(env, explicit) {
9122
9151
  }
9123
9152
  function fileIdentity(file) {
9124
9153
  try {
9125
- return lstatSync3(file);
9154
+ return lstatSync4(file);
9126
9155
  } catch (error) {
9127
9156
  if (["ENOENT", "ENOTDIR"].includes(error.code ?? ""))
9128
9157
  return null;
@@ -9193,6 +9222,28 @@ function readSkillsInstanceMetadata(file) {
9193
9222
  return { apiUrl: urls[0], binding: values.get(SKILLS_BOUND_API_URL) };
9194
9223
  }
9195
9224
 
9225
+ // src/lib/local-opt-in.ts
9226
+ var SKILLS_LOCAL_OPT_IN_ENV_KEYS = ["HASNA_SKILLS_LOCAL", "SKILLS_LOCAL"];
9227
+ function isSkillsLocalOptIn(env = process.env) {
9228
+ return SKILLS_LOCAL_OPT_IN_ENV_KEYS.some((key) => (env[key] ?? "").trim() !== "");
9229
+ }
9230
+ function skillsAuthorityEnvKeys() {
9231
+ const keys = clientTransportEnvKeys("skills");
9232
+ return [
9233
+ ...keys.apiUrlKeys,
9234
+ ...keys.apiKeyKeys,
9235
+ credentialOverrideEnvKey("skills"),
9236
+ credentialPointerEnvKey("skills"),
9237
+ CREDENTIAL_PROFILE_ENV_KEY
9238
+ ];
9239
+ }
9240
+ function hasSkillsEnvAuthorityIntent(env = process.env) {
9241
+ return skillsAuthorityEnvKeys().some((key) => (env[key] ?? "").trim() !== "");
9242
+ }
9243
+ function selectsSkillsLocalMode(env = process.env) {
9244
+ return !hasSkillsEnvAuthorityIntent(env) && isSkillsLocalOptIn(env);
9245
+ }
9246
+
9196
9247
  // src/lib/fleet-credentials.ts
9197
9248
  var SKILLS_APP = "skills";
9198
9249
  var ENV_KEYS = clientTransportEnvKeys(SKILLS_APP);
@@ -9212,6 +9263,9 @@ class SkillsFleetCredentialError extends Error {
9212
9263
  function isCredentialResolutionError(error) {
9213
9264
  return error instanceof CredentialResolutionError || typeof error === "object" && error !== null && error.name === "CredentialResolutionError";
9214
9265
  }
9266
+ function isSkillsFleetCredentialError(error) {
9267
+ return error instanceof SkillsFleetCredentialError || typeof error === "object" && error !== null && error.name === "SkillsFleetCredentialError";
9268
+ }
9215
9269
  function asSkillsFleetCredentialError(error) {
9216
9270
  if (!isCredentialResolutionError(error))
9217
9271
  return null;
@@ -9278,7 +9332,7 @@ function noticeLocalSkillsMode(write = (line) => console.error(line)) {
9278
9332
  if (localNoticePrinted)
9279
9333
  return;
9280
9334
  localNoticePrinted = true;
9281
- write(`skills: local mode \u2014 no ${SKILLS_API_KEY_ENV} and no ${SKILLS_API_URL_ENV} resolved, ` + `so this runs on this machine against the bundled corpus. ` + `Sign in with: skills auth login`);
9335
+ write(`skills: local mode (${SKILLS_LOCAL_OPT_IN_ENV_KEYS[0]}=1) \u2014 running on this machine against the bundled corpus.`);
9282
9336
  }
9283
9337
  function resolveSkillsFleet(env = process.env, options = {}) {
9284
9338
  try {
@@ -9315,13 +9369,16 @@ function snapshotSkillsOptions(env, options) {
9315
9369
  } } };
9316
9370
  }
9317
9371
  function resolveSkillsFleetOrThrow(env, options) {
9372
+ if (selectsSkillsLocalMode(env))
9373
+ return { mode: "local", apiOrigin: null, apiKey: null };
9318
9374
  const assertFilesUnchanged = captureSkillsCredentialFiles(skillsProfileCredentialFiles(env, options.credentials?.profile));
9319
9375
  const configured = configuredSkillsApiUrl(env, options.credentials?.keychain, options.credentials?.profile);
9320
9376
  const credential = resolveCredential(SKILLS_APP, env, options.credentials);
9321
9377
  if (!credential) {
9322
- if (!configured)
9323
- return { mode: "local", apiOrigin: null, apiKey: null };
9324
- throw new SkillsFleetCredentialError(`${configured.source} points this CLI at a Skills service but no API key resolved \u2014 refusing to run locally instead. ` + `Looked in hasna.credentials.skills.api-key, ${skillsCredentialFiles(env).join(" or ") || "no credentials file"}, and ${SKILLS_API_KEY_ENV}. Sign in with: skills auth login`);
9378
+ if (!configured) {
9379
+ 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`);
9380
+ }
9381
+ 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`);
9325
9382
  }
9326
9383
  const apiOrigin = normalizeSkillsApiOrigin(configured?.value ?? defaultFleetGatewayBaseUrl(SKILLS_APP));
9327
9384
  toV1BaseUrl(apiOrigin);
@@ -9343,6 +9400,10 @@ function resolveSkillsFleetOrThrow(env, options) {
9343
9400
  }
9344
9401
  return { ...base, apiKey: credential.apiKey, apiKeyPointer: null };
9345
9402
  }
9403
+ function credentialLocations(env) {
9404
+ const files = skillsCredentialFiles(env).join(" or ") || "no credentials file (HOME is unset)";
9405
+ return `hasna.credentials.${SKILLS_APP}.api-key (macOS Keychain, account HASNA_STATION or the host name), ${files}, and ${SKILLS_API_KEY_ENV}`;
9406
+ }
9346
9407
  function assertCredentialInstance(credential, apiOrigin, env, options) {
9347
9408
  let bound;
9348
9409
  if (credential.tier === "disk" || credential.tier === "profile") {
@@ -9396,7 +9457,7 @@ async function skillsCredentialOrReason(env = process.env, options = {}) {
9396
9457
  const connection = await resolveSkillsConnection(env, options);
9397
9458
  return connection ? { apiKey: connection.apiKey, apiOrigin: connection.apiOrigin, reason: null } : { apiKey: null, apiOrigin: null, reason: null };
9398
9459
  } catch (error) {
9399
- if (error instanceof SkillsFleetCredentialError || error?.name === "SkillsFleetCredentialError") {
9460
+ if (isSkillsFleetCredentialError(error)) {
9400
9461
  return { apiKey: null, apiOrigin: null, reason: error.message };
9401
9462
  }
9402
9463
  throw error;
@@ -9408,7 +9469,14 @@ function resolveSkillsApiOrigin(env = process.env, options = {}) {
9408
9469
  toV1BaseUrl(configured.value);
9409
9470
  return { origin: normalizeSkillsApiOrigin(configured.value), source: configured.source };
9410
9471
  }
9411
- const fleet = resolveSkillsFleet(env, options);
9472
+ let fleet;
9473
+ try {
9474
+ fleet = resolveSkillsFleet(env, options);
9475
+ } catch (error) {
9476
+ if (isSkillsFleetCredentialError(error))
9477
+ return null;
9478
+ throw error;
9479
+ }
9412
9480
  return fleet.mode === "hosted" ? { origin: fleet.apiOrigin, source: fleet.apiUrlSource } : null;
9413
9481
  }
9414
9482
  function requireSkillsApiOrigin(action = "This command", env = process.env, options = {}) {
@@ -9640,12 +9708,21 @@ function parseRemoteContract(schema, payload, message) {
9640
9708
  }
9641
9709
  async function remoteRequestHeaders(options) {
9642
9710
  const headers = new Headers({ Accept: "application/json" });
9643
- const token = options.authToken !== undefined ? options.authToken : await resolveSkillsApiKey();
9711
+ const token = options.authToken !== undefined ? options.authToken : await ambientTokenFor(options.apiUrl);
9644
9712
  const trimmed = token?.trim();
9645
9713
  if (trimmed)
9646
9714
  headers.set("Authorization", `Bearer ${trimmed}`);
9647
9715
  return headers;
9648
9716
  }
9717
+ async function ambientTokenFor(callerApiUrl) {
9718
+ const connection = await resolveSkillsConnection();
9719
+ if (!connection)
9720
+ return null;
9721
+ if (callerApiUrl !== undefined && normalizeSkillsApiOrigin(callerApiUrl) !== connection.apiOrigin) {
9722
+ 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");
9723
+ }
9724
+ return connection.apiKey;
9725
+ }
9649
9726
  async function fetchRemoteJson(url, options) {
9650
9727
  const fetchImpl = options.fetchImpl || fetch;
9651
9728
  const headers = await remoteRequestHeaders(options);
@@ -10177,6 +10254,108 @@ function primitiveHaystack(primitive) {
10177
10254
  function clone(value) {
10178
10255
  return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
10179
10256
  }
10257
+ // src/lib/remote-workspace.ts
10258
+ var record = (value) => !!value && typeof value === "object" && !Array.isArray(value);
10259
+ var cursor = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,512}$/.test(value);
10260
+ var uuid = (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);
10261
+ function workspaceMembersQuery(options = {}) {
10262
+ if (!record(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))
10263
+ throw new Error("Use a roster limit from 1 to 100 and an unchanged continuation cursor.");
10264
+ const query = new URLSearchParams;
10265
+ if (options.limit !== undefined)
10266
+ query.set("limit", String(options.limit));
10267
+ if (options.cursor !== undefined)
10268
+ query.set("cursor", options.cursor);
10269
+ return query.size ? `?${query}` : "";
10270
+ }
10271
+ function timestamp(value) {
10272
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$/.test(value))
10273
+ return false;
10274
+ const time = Date.parse(value);
10275
+ return Number.isFinite(time) && new Date(time).toISOString().slice(0, 23) === value.slice(0, 23);
10276
+ }
10277
+ function parseMember(row, fail) {
10278
+ if (!record(row) || !uuid(row.membershipId) || !uuid(row.userId) || typeof row.email !== "string" || !row.email || !(row.displayName === null || typeof row.displayName === "string") || !isRole(row.role) || !timestamp(row.createdAt))
10279
+ return fail();
10280
+ return {
10281
+ membershipId: row.membershipId,
10282
+ userId: row.userId,
10283
+ email: row.email,
10284
+ displayName: row.displayName,
10285
+ role: row.role,
10286
+ createdAt: row.createdAt
10287
+ };
10288
+ }
10289
+ var isRole = (value) => typeof value === "string" && ["owner", "admin", "member", "viewer"].includes(value);
10290
+
10291
+ class WorkspaceMemberInputError extends Error {
10292
+ constructor() {
10293
+ super("Use an unchanged lowercase membership ID and the exact role and expected-role parameters from the roster.");
10294
+ this.name = "WorkspaceMemberInputError";
10295
+ }
10296
+ }
10297
+ function mutationInput(membershipId, input, roleChange) {
10298
+ if (typeof membershipId !== "string" || !uuid(membershipId) || membershipId !== membershipId.toLowerCase() || !record(input) || Object.keys(input).sort().join(",") !== (roleChange ? "expectedRole,role" : "expectedRole"))
10299
+ throw new WorkspaceMemberInputError;
10300
+ const expectedRole = input.expectedRole, role = roleChange ? input.role : undefined;
10301
+ if (!isRole(expectedRole) || roleChange && !isRole(role))
10302
+ throw new WorkspaceMemberInputError;
10303
+ return { membershipId, role, expectedRole };
10304
+ }
10305
+ function workspaceMemberRoleInput(membershipId, input) {
10306
+ const value = mutationInput(membershipId, input, true);
10307
+ return { membershipId: value.membershipId, body: { role: value.role, expectedRole: value.expectedRole } };
10308
+ }
10309
+ function workspaceMemberRemovalInput(membershipId, input) {
10310
+ const value = mutationInput(membershipId, input, false);
10311
+ return { membershipId: value.membershipId, body: { expectedRole: value.expectedRole } };
10312
+ }
10313
+ var invalidMemberResult = "The server returned an invalid workspace member result. Refresh the roster before another action.";
10314
+ function parseWorkspaceMemberRoleResult(value, membershipId, role) {
10315
+ const fail = () => {
10316
+ throw new Error(invalidMemberResult);
10317
+ };
10318
+ if (!record(value) || !uuid(value.organizationId) || typeof value.changed !== "boolean")
10319
+ return fail();
10320
+ const member = parseMember(value.member, fail);
10321
+ if (member.membershipId !== membershipId || member.role !== role)
10322
+ return fail();
10323
+ return { organizationId: value.organizationId, member, changed: value.changed };
10324
+ }
10325
+ function parseWorkspaceMemberRemovalResult(value, membershipId) {
10326
+ if (!record(value) || !uuid(value.organizationId) || value.membershipId !== membershipId || value.removed !== true || typeof value.alreadyRemoved !== "boolean")
10327
+ throw new Error(invalidMemberResult);
10328
+ return { organizationId: value.organizationId, membershipId, removed: true, alreadyRemoved: value.alreadyRemoved };
10329
+ }
10330
+ var workspaceMemberFailures = {
10331
+ INVALID_REQUEST: [400, "Provide the exact membership role parameters."],
10332
+ ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
10333
+ INTERACTIVE_SESSION_REQUIRED: [403, "Fresh interactive sign-in is required."],
10334
+ WORKSPACE_ADMIN_REQUIRED: [403, "A current workspace owner or admin is required."],
10335
+ MEMBERSHIP_ACTION_FORBIDDEN: [403, "Your current workspace role cannot perform this membership action."],
10336
+ MEMBERSHIP_NOT_FOUND: [404, "Membership was not found in the current workspace."],
10337
+ SELF_REMOVAL_UNAVAILABLE: [409, "Leaving your own workspace is not available through member removal."],
10338
+ MEMBERSHIP_ROLE_CHANGED: [409, "The member role changed. Refresh the roster before another action."],
10339
+ LAST_OWNER_REQUIRED: [409, "The workspace must retain at least one active owner."],
10340
+ MEMBERSHIP_BUSY: [503, "Membership is busy. Refresh the roster before another action."]
10341
+ };
10342
+ function workspaceMemberFailure(value, status) {
10343
+ if (!record(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceMemberFailures, value.code))
10344
+ return null;
10345
+ const code = value.code;
10346
+ return workspaceMemberFailures[code][0] === status ? code : null;
10347
+ }
10348
+ function parseWorkspaceMembersPage(value) {
10349
+ const fail = () => {
10350
+ throw new Error("The server returned an invalid workspace roster.");
10351
+ };
10352
+ if (!record(value) || !uuid(value.organizationId) || !Array.isArray(value.members) || value.members.length > 100 || !(value.nextCursor === null || cursor(value.nextCursor)) || !value.members.length && value.nextCursor !== null)
10353
+ return fail();
10354
+ const members = value.members.map((row) => parseMember(row, fail));
10355
+ if (new Set(members.map((row) => row.membershipId)).size !== members.length)
10356
+ return fail();
10357
+ return { organizationId: value.organizationId, members, nextCursor: value.nextCursor };
10358
+ }
10180
10359
  // src/lib/auth-store.ts
10181
10360
  function getApiUrl(action, env = process.env, options = {}) {
10182
10361
  return requireSkillsApiOrigin(action, env, options);
@@ -10185,44 +10364,44 @@ function getApiUrl(action, env = process.env, options = {}) {
10185
10364
  // src/lib/remote-run-contract.ts
10186
10365
  var REMOTE_SKILL_RUN_CONTRACT_VERSION = 1;
10187
10366
  function normalizeRemoteSkillRunContract(payload, fallbackSkill) {
10188
- const record = isRecord3(payload) ? payload : {};
10367
+ const record2 = isRecord3(payload) ? payload : {};
10189
10368
  return {
10190
10369
  contractVersion: REMOTE_SKILL_RUN_CONTRACT_VERSION,
10191
- ...pickString(record, "id"),
10192
- skill: pickStringValue(record, "skill") ?? fallbackSkill,
10193
- ...pickString(record, "requestedSlug"),
10194
- ...pickString(record, "status"),
10195
- ...pickNumber(record, "exitCode"),
10196
- ...pickString(record, "correlationId"),
10197
- ...pickString(record, "createdAt"),
10198
- ...pickString(record, "startedAt"),
10199
- ...pickString(record, "completedAt"),
10200
- ...pickNumber(record, "durationMs"),
10201
- ...pickString(record, "outputType"),
10202
- ...hasOwn(record, "outputPreview") ? { outputPreview: record.outputPreview } : {},
10203
- ...pickString(record, "errorCode"),
10204
- ...pickString(record, "errorMessage"),
10205
- ...pickString(record, "error"),
10206
- ...pickString(record, "code"),
10207
- ...hasOwn(record, "details") ? { details: record.details } : {}
10370
+ ...pickString(record2, "id"),
10371
+ skill: pickStringValue(record2, "skill") ?? fallbackSkill,
10372
+ ...pickString(record2, "requestedSlug"),
10373
+ ...pickString(record2, "status"),
10374
+ ...pickNumber(record2, "exitCode"),
10375
+ ...pickString(record2, "correlationId"),
10376
+ ...pickString(record2, "createdAt"),
10377
+ ...pickString(record2, "startedAt"),
10378
+ ...pickString(record2, "completedAt"),
10379
+ ...pickNumber(record2, "durationMs"),
10380
+ ...pickString(record2, "outputType"),
10381
+ ...hasOwn(record2, "outputPreview") ? { outputPreview: record2.outputPreview } : {},
10382
+ ...pickString(record2, "errorCode"),
10383
+ ...pickString(record2, "errorMessage"),
10384
+ ...pickString(record2, "error"),
10385
+ ...pickString(record2, "code"),
10386
+ ...hasOwn(record2, "details") ? { details: record2.details } : {}
10208
10387
  };
10209
10388
  }
10210
10389
  function isRecord3(value) {
10211
10390
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
10212
10391
  }
10213
- function hasOwn(record, key) {
10214
- return Object.prototype.hasOwnProperty.call(record, key);
10392
+ function hasOwn(record2, key) {
10393
+ return Object.prototype.hasOwnProperty.call(record2, key);
10215
10394
  }
10216
- function pickString(record, key) {
10217
- const value = pickStringValue(record, key);
10395
+ function pickString(record2, key) {
10396
+ const value = pickStringValue(record2, key);
10218
10397
  return value === undefined ? {} : { [key]: value };
10219
10398
  }
10220
- function pickStringValue(record, key) {
10221
- const value = record[key];
10399
+ function pickStringValue(record2, key) {
10400
+ const value = record2[key];
10222
10401
  return typeof value === "string" ? value : undefined;
10223
10402
  }
10224
- function pickNumber(record, key) {
10225
- const value = record[key];
10403
+ function pickNumber(record2, key) {
10404
+ const value = record2[key];
10226
10405
  return typeof value === "number" && Number.isFinite(value) ? { [key]: value } : {};
10227
10406
  }
10228
10407
 
@@ -10361,6 +10540,37 @@ async function readBoundedResponse(response, maximum) {
10361
10540
  return bytes;
10362
10541
  }
10363
10542
 
10543
+ // src/lib/remote-profile.ts
10544
+ function customerNamePatch(input, field) {
10545
+ if (!isRecord4(input) || Object.keys(input).length !== 1 || !Object.hasOwn(input, field))
10546
+ throw new Error("Provide only the requested name field.");
10547
+ const value = input[field];
10548
+ if (typeof value !== "string" || /[\p{Cc}\p{Cs}\u2028\u2029]/u.test(value) || !value.trim() || [...value.trim()].length > 100) {
10549
+ throw new Error("Use a name of 1\u2013100 characters without control characters or newlines.");
10550
+ }
10551
+ return { [field]: value.trim() };
10552
+ }
10553
+ function isRecord4(value) {
10554
+ return !!value && typeof value === "object" && !Array.isArray(value);
10555
+ }
10556
+ function string(value) {
10557
+ return typeof value === "string" && value.length > 0;
10558
+ }
10559
+ function parseUpdatedProfile(value) {
10560
+ const user = isRecord4(value) && value.user;
10561
+ if (!isRecord4(user) || !string(user.id) || !string(user.email) || !(user.displayName === null || typeof user.displayName === "string") || typeof user.role !== "string" || !["owner", "admin", "member", "viewer"].includes(user.role)) {
10562
+ throw new Error("The server returned an invalid account profile.");
10563
+ }
10564
+ return { user: { id: user.id, email: user.email, displayName: user.displayName, role: user.role } };
10565
+ }
10566
+ function parseUpdatedWorkspace(value) {
10567
+ const organization = isRecord4(value) && value.organization;
10568
+ if (!isRecord4(organization) || !string(organization.id) || !string(organization.slug) || !string(organization.name)) {
10569
+ throw new Error("The server returned an invalid workspace.");
10570
+ }
10571
+ return { organization: { id: organization.id, slug: organization.slug, name: organization.name } };
10572
+ }
10573
+
10364
10574
  // src/lib/remote-client.ts
10365
10575
  class RemoteRouteUnsupportedError extends Error {
10366
10576
  path;
@@ -10378,14 +10588,33 @@ class RemoteRouteUnsupportedError extends Error {
10378
10588
  class RemoteRequestError extends Error {
10379
10589
  path;
10380
10590
  status;
10381
- constructor(path, status, statusText) {
10382
- super(`Remote request to ${path} failed: HTTP ${status}${statusText ? ` ${statusText}` : ""}`);
10591
+ constructor(path, status, _statusText) {
10592
+ super(`Remote request to ${path} failed: HTTP ${status}`);
10383
10593
  this.path = path;
10384
10594
  this.status = status;
10385
10595
  this.name = "RemoteRequestError";
10386
10596
  }
10387
10597
  }
10388
10598
 
10599
+ class RemoteWorkspaceMemberError extends RemoteRequestError {
10600
+ code;
10601
+ constructor(path, code) {
10602
+ super(path, workspaceMemberFailures[code][0]);
10603
+ this.code = code;
10604
+ this.name = "RemoteWorkspaceMemberError";
10605
+ this.message = workspaceMemberFailures[code][1];
10606
+ }
10607
+ }
10608
+
10609
+ class RemoteCapabilityUnavailableError extends RemoteRequestError {
10610
+ code = "SUBSCRIPTION_CHECKOUT_UNAVAILABLE";
10611
+ constructor() {
10612
+ super("/api/v1/billing/checkout", 503);
10613
+ this.name = "RemoteCapabilityUnavailableError";
10614
+ 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.";
10615
+ }
10616
+ }
10617
+
10389
10618
  class RemoteSkillsClient {
10390
10619
  apiUrl;
10391
10620
  apiKey;
@@ -10413,9 +10642,14 @@ class RemoteSkillsClient {
10413
10642
  if (response.status === 404 && opts.domainNotFoundCodes?.length && await responseBodyCarriesCode(response, opts.domainNotFoundCodes)) {
10414
10643
  return response;
10415
10644
  }
10645
+ response.body?.cancel().catch(() => {});
10416
10646
  throw new RemoteRouteUnsupportedError(routePath, response.status, this.apiUrl);
10417
10647
  }
10418
10648
  if (!response.ok) {
10649
+ if (path === "/api/v1/billing/checkout" && options?.method === "POST" && response.status === 503 && await responseBodyCarriesCode(response, ["SUBSCRIPTION_CHECKOUT_UNAVAILABLE"])) {
10650
+ throw new RemoteCapabilityUnavailableError;
10651
+ }
10652
+ response.body?.cancel().catch(() => {});
10419
10653
  throw new RemoteRequestError(routePath, response.status, response.statusText);
10420
10654
  }
10421
10655
  return response;
@@ -10499,6 +10733,58 @@ class RemoteSkillsClient {
10499
10733
  async getIdentity() {
10500
10734
  return (await this.requestNewRoute("/api/auth/whoami")).json();
10501
10735
  }
10736
+ async updateProfile(input) {
10737
+ const body = customerNamePatch(input, "displayName");
10738
+ return parseUpdatedProfile(await (await this.requestNewRoute("/api/v1/account/profile", { method: "PATCH", body: JSON.stringify(body) })).json());
10739
+ }
10740
+ async updateCurrentWorkspace(input) {
10741
+ const body = customerNamePatch(input, "name");
10742
+ return parseUpdatedWorkspace(await (await this.requestNewRoute("/api/v1/workspaces/current", { method: "PATCH", body: JSON.stringify(body) })).json());
10743
+ }
10744
+ async listWorkspaceMembers(options = {}) {
10745
+ const query = workspaceMembersQuery(options);
10746
+ const requestedCursor = options.cursor;
10747
+ const response = await this.requestNewRoute(`/api/v1/workspace/members${query}`);
10748
+ let value;
10749
+ try {
10750
+ value = await response.json();
10751
+ } catch {
10752
+ throw new Error("The server returned an invalid workspace roster.");
10753
+ }
10754
+ const page = parseWorkspaceMembersPage(value);
10755
+ if (requestedCursor !== undefined && page.nextCursor === requestedCursor)
10756
+ throw new Error("The server returned an invalid workspace roster.");
10757
+ return page;
10758
+ }
10759
+ async setWorkspaceMemberRole(membershipId, input) {
10760
+ const captured = workspaceMemberRoleInput(membershipId, input);
10761
+ const value = await this.requestWorkspaceMember(captured.membershipId, "PATCH", captured.body);
10762
+ return parseWorkspaceMemberRoleResult(value, captured.membershipId, captured.body.role);
10763
+ }
10764
+ async removeWorkspaceMember(membershipId, input) {
10765
+ const captured = workspaceMemberRemovalInput(membershipId, input);
10766
+ return parseWorkspaceMemberRemovalResult(await this.requestWorkspaceMember(captured.membershipId, "DELETE", captured.body), captured.membershipId);
10767
+ }
10768
+ async requestWorkspaceMember(membershipId, method, body) {
10769
+ const path = `/api/v1/workspace/members/${membershipId}`;
10770
+ const response = await this.request(path, { method, body: JSON.stringify(body) });
10771
+ let value;
10772
+ try {
10773
+ value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, response.ok ? 64 * 1024 : 4096)));
10774
+ } catch {
10775
+ if (response.ok)
10776
+ throw new Error(invalidMemberResult);
10777
+ }
10778
+ if (!response.ok) {
10779
+ const code = workspaceMemberFailure(value, response.status);
10780
+ if (code)
10781
+ throw new RemoteWorkspaceMemberError(path, code);
10782
+ if (response.status === 404 || response.status === 405)
10783
+ throw new RemoteRouteUnsupportedError(path, response.status, this.apiUrl);
10784
+ throw new RemoteRequestError(path, response.status);
10785
+ }
10786
+ return value;
10787
+ }
10502
10788
  async listApiKeys() {
10503
10789
  return this.arrayResponse("/api/auth/keys");
10504
10790
  }
@@ -10690,8 +10976,10 @@ class RemoteSkillsClient {
10690
10976
  return [];
10691
10977
  if (!response.ok)
10692
10978
  throw new Error(`versions request failed: ${response.status}`);
10693
- const body = await response.json();
10694
- return Array.isArray(body.versions) ? body.versions : [];
10979
+ const body = await readSkillVersionPayload(response);
10980
+ if (!isVersionRecord(body) || !Array.isArray(body.versions) || body.slug !== undefined && body.slug !== slug)
10981
+ throw new Error(INVALID_SKILL_VERSION_RESPONSE);
10982
+ return body.versions.map((entry) => normalizeSkillVersion(entry, slug));
10695
10983
  }
10696
10984
  async getSkillVersion(slug, version) {
10697
10985
  const response = await this.requestNewRoute(`/api/v1/skills/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}`, undefined, { domainNotFoundCodes: ["SKILL_NOT_FOUND", "SKILL_VERSION_NOT_FOUND"] });
@@ -10699,7 +10987,7 @@ class RemoteSkillsClient {
10699
10987
  return null;
10700
10988
  if (!response.ok)
10701
10989
  throw new Error(`version request failed: ${response.status}`);
10702
- return await response.json();
10990
+ return normalizeSkillVersion(await readSkillVersionPayload(response), slug, version);
10703
10991
  }
10704
10992
  async listPins() {
10705
10993
  const response = await this.requestNewRoute("/api/v1/pins");
@@ -10747,31 +11035,48 @@ class RemoteSkillsClient {
10747
11035
  return normalizeUpdatedSincePage(await response.json());
10748
11036
  }
10749
11037
  }
10750
- function requireOptionalString(record, field) {
10751
- if (record[field] === undefined)
11038
+ function requireOptionalString(record2, field) {
11039
+ if (record2[field] === undefined)
10752
11040
  return;
10753
- if (typeof record[field] !== "string") {
11041
+ if (typeof record2[field] !== "string") {
10754
11042
  throw new Error(`Remote payload did not match the expected contract (${field} must be a string when present)`);
10755
11043
  }
10756
- return record[field];
11044
+ return record2[field];
11045
+ }
11046
+ var INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
11047
+ function isVersionRecord(value) {
11048
+ return value !== null && typeof value === "object" && !Array.isArray(value);
11049
+ }
11050
+ async function readSkillVersionPayload(response) {
11051
+ try {
11052
+ return await response.json();
11053
+ } catch {
11054
+ throw new Error(INVALID_SKILL_VERSION_RESPONSE);
11055
+ }
11056
+ }
11057
+ function normalizeSkillVersion(entry, slug, version) {
11058
+ if (!isVersionRecord(entry) || typeof entry.slug !== "string" || !entry.slug.trim() || entry.slug !== slug || typeof entry.version !== "string" || !entry.version.trim() || version !== undefined && entry.version !== version || 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)) {
11059
+ throw new Error(INVALID_SKILL_VERSION_RESPONSE);
11060
+ }
11061
+ return entry;
10757
11062
  }
10758
11063
  function normalizePin(entry) {
10759
11064
  if (!entry || typeof entry !== "object") {
10760
11065
  throw new Error("Remote pin payload did not match the expected contract (expected an object)");
10761
11066
  }
10762
- const record = entry;
10763
- const slug = typeof record.slug === "string" && record.slug.trim() ? record.slug.trim() : undefined;
11067
+ const record2 = entry;
11068
+ const slug = typeof record2.slug === "string" && record2.slug.trim() ? record2.slug.trim() : undefined;
10764
11069
  if (!slug) {
10765
11070
  throw new Error("Remote pin payload did not match the expected contract (missing slug)");
10766
11071
  }
10767
11072
  let metadata;
10768
- if (record.metadata !== undefined) {
10769
- if (!record.metadata || typeof record.metadata !== "object" || Array.isArray(record.metadata)) {
11073
+ if (record2.metadata !== undefined) {
11074
+ if (!record2.metadata || typeof record2.metadata !== "object" || Array.isArray(record2.metadata)) {
10770
11075
  throw new Error("Remote pin payload did not match the expected contract (metadata must be a JSON object when present)");
10771
11076
  }
10772
- metadata = record.metadata;
11077
+ metadata = record2.metadata;
10773
11078
  }
10774
- const pinnedAt = requireOptionalString(record, "pinnedAt");
11079
+ const pinnedAt = requireOptionalString(record2, "pinnedAt");
10775
11080
  return {
10776
11081
  slug,
10777
11082
  ...pinnedAt !== undefined ? { pinnedAt } : {},
@@ -10788,16 +11093,16 @@ function normalizeSkillSummary(entry) {
10788
11093
  if (!entry || typeof entry !== "object") {
10789
11094
  throw new Error("Remote skill payload did not match the expected contract (expected an object)");
10790
11095
  }
10791
- const record = entry;
10792
- const slug = typeof record.slug === "string" && record.slug.trim() ? record.slug.trim() : undefined;
11096
+ const record2 = entry;
11097
+ const slug = typeof record2.slug === "string" && record2.slug.trim() ? record2.slug.trim() : undefined;
10793
11098
  if (!slug) {
10794
11099
  throw new Error("Remote skill payload did not match the expected contract (missing slug)");
10795
11100
  }
10796
11101
  return {
10797
11102
  slug,
10798
- ...requireOptionalString(record, "name") !== undefined ? { name: requireOptionalString(record, "name") } : {},
10799
- ...requireOptionalString(record, "version") !== undefined ? { version: requireOptionalString(record, "version") } : {},
10800
- ...requireOptionalString(record, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record, "updatedAt") } : {}
11103
+ ...requireOptionalString(record2, "name") !== undefined ? { name: requireOptionalString(record2, "name") } : {},
11104
+ ...requireOptionalString(record2, "version") !== undefined ? { version: requireOptionalString(record2, "version") } : {},
11105
+ ...requireOptionalString(record2, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record2, "updatedAt") } : {}
10801
11106
  };
10802
11107
  }
10803
11108
  function normalizeSkillSummaryList(payload) {
@@ -10807,26 +11112,55 @@ function normalizeSkillSummaryList(payload) {
10807
11112
  return payload.map(normalizeSkillSummary);
10808
11113
  }
10809
11114
  async function responseBodyCarriesCode(response, codes) {
11115
+ const reader = response.body?.getReader();
11116
+ if (!reader)
11117
+ return false;
11118
+ const maximum = 8 * 1024;
11119
+ let deadline;
11120
+ const expired = new Promise((_, reject) => {
11121
+ deadline = setTimeout(() => reject(new Error("Error response read deadline exceeded")), 1000);
11122
+ });
10810
11123
  try {
10811
- const payload = await response.clone().json();
10812
- if (!payload || typeof payload !== "object")
11124
+ const chunks = [];
11125
+ let size = 0;
11126
+ while (true) {
11127
+ const next = await Promise.race([reader.read(), expired]);
11128
+ if (next.done)
11129
+ break;
11130
+ size += next.value.byteLength;
11131
+ if (size > maximum)
11132
+ return false;
11133
+ chunks.push(next.value);
11134
+ }
11135
+ const bytes = new Uint8Array(size);
11136
+ let offset = 0;
11137
+ for (const chunk of chunks) {
11138
+ bytes.set(chunk, offset);
11139
+ offset += chunk.byteLength;
11140
+ }
11141
+ const payload = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
11142
+ if (!payload || typeof payload !== "object" || Array.isArray(payload) || !Object.hasOwn(payload, "code"))
10813
11143
  return false;
10814
11144
  const code = payload.code;
10815
11145
  return typeof code === "string" && codes.includes(code);
10816
11146
  } catch {
10817
11147
  return false;
11148
+ } finally {
11149
+ clearTimeout(deadline);
11150
+ reader.cancel().catch(() => {});
11151
+ reader.releaseLock();
10818
11152
  }
10819
11153
  }
10820
11154
  function normalizeUpdatedSincePage(payload) {
10821
11155
  if (!payload || typeof payload !== "object") {
10822
11156
  throw new Error("Updated-since payload did not match the expected contract (expected an object)");
10823
11157
  }
10824
- const record = payload;
10825
- if (!Array.isArray(record.skills)) {
11158
+ const record2 = payload;
11159
+ if (!Array.isArray(record2.skills)) {
10826
11160
  throw new Error("Updated-since payload did not match the expected contract (missing skills array)");
10827
11161
  }
10828
- const skills = record.skills.map(normalizeSkillSummary);
10829
- const nextCursor = record.nextCursor === undefined || record.nextCursor === null ? null : record.nextCursor;
11162
+ const skills = record2.skills.map(normalizeSkillSummary);
11163
+ const nextCursor = record2.nextCursor === undefined || record2.nextCursor === null ? null : record2.nextCursor;
10830
11164
  if (nextCursor !== null && typeof nextCursor !== "string") {
10831
11165
  throw new Error("Updated-since payload did not match the expected contract (nextCursor must be a string or absent)");
10832
11166
  }
@@ -11063,8 +11397,8 @@ function revisionIdOf(content) {
11063
11397
  });
11064
11398
  return createHash4("sha256").update(canonical).digest("hex");
11065
11399
  }
11066
- function revisionIdOfRecord(record) {
11067
- return revisionIdOf(record);
11400
+ function revisionIdOfRecord(record2) {
11401
+ return revisionIdOf(record2);
11068
11402
  }
11069
11403
 
11070
11404
  // src/lib/skill-bundle.ts
@@ -11590,7 +11924,7 @@ function provenRevision(meta, slug, bundle) {
11590
11924
  }
11591
11925
  function reconcileTombstone(slug, corpusOptions) {
11592
11926
  const target = join17(getPortableSkillsRoot(corpusOptions), slug);
11593
- if (!existsSync15(join17(target, PULL_MARKER_FILE))) {
11927
+ if (!hasSkillsOwnershipMarker(target)) {
11594
11928
  return { name: slug, success: true, tombstoned: true, removed: false, leftInPlace: true };
11595
11929
  }
11596
11930
  rmSync4(target, { recursive: true, force: true });
@@ -11771,16 +12105,16 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
11771
12105
  }
11772
12106
  return { path: target, created };
11773
12107
  }
11774
- function writePullMarker(dir, record) {
12108
+ function writePullMarker(dir, record2) {
11775
12109
  const marker = {
11776
12110
  managedBy: "@hasna/skills",
11777
- skill: record.skill,
11778
- source: record.source ?? "pull",
11779
- ...record.version ? { version: record.version } : {},
11780
- ...record.contentHash ? { contentHash: record.contentHash } : {},
11781
- ...record.sourceCommit ? { sourceCommit: record.sourceCommit } : {},
11782
- ...record.signature ? { signature: record.signature } : {},
11783
- ...record.revisionId ? { revisionId: record.revisionId } : {},
12111
+ skill: record2.skill,
12112
+ source: record2.source ?? "pull",
12113
+ ...record2.version ? { version: record2.version } : {},
12114
+ ...record2.contentHash ? { contentHash: record2.contentHash } : {},
12115
+ ...record2.sourceCommit ? { sourceCommit: record2.sourceCommit } : {},
12116
+ ...record2.signature ? { signature: record2.signature } : {},
12117
+ ...record2.revisionId ? { revisionId: record2.revisionId } : {},
11784
12118
  syncedAt: new Date().toISOString()
11785
12119
  };
11786
12120
  writeFileSync8(join17(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
@@ -11795,19 +12129,19 @@ async function safeMeta(client, slug) {
11795
12129
  }
11796
12130
  if (!raw || typeof raw !== "object")
11797
12131
  return null;
11798
- const record = raw;
11799
- const kind = record.kind === "instruction" || record.kind === "executable" ? record.kind : undefined;
11800
- const tags = Array.isArray(record.tags) ? record.tags.filter((tag) => typeof tag === "string" && tag.trim().length > 0) : undefined;
12132
+ const record2 = raw;
12133
+ const kind = record2.kind === "instruction" || record2.kind === "executable" ? record2.kind : undefined;
12134
+ const tags = Array.isArray(record2.tags) ? record2.tags.filter((tag) => typeof tag === "string" && tag.trim().length > 0) : undefined;
11801
12135
  return {
11802
- ...str(record.displayName) ? { displayName: str(record.displayName) } : {},
11803
- ...str(record.description) ? { description: str(record.description) } : {},
11804
- ...str(record.category) ? { category: str(record.category) } : {},
12136
+ ...str(record2.displayName) ? { displayName: str(record2.displayName) } : {},
12137
+ ...str(record2.description) ? { description: str(record2.description) } : {},
12138
+ ...str(record2.category) ? { category: str(record2.category) } : {},
11805
12139
  ...tags && tags.length ? { tags } : {},
11806
- ...str(record.version) ? { version: str(record.version) } : {},
12140
+ ...str(record2.version) ? { version: str(record2.version) } : {},
11807
12141
  ...kind ? { kind } : {},
11808
- ...REVISION_ID_PATTERN.test(str(record.revisionId) ?? "") ? { revisionId: str(record.revisionId) } : {},
11809
- ...typeof record.skillMd === "string" && record.skillMd.length > 0 ? { skillMd: record.skillMd } : {},
11810
- ...str(record.publishedSource) ? { publishedSource: str(record.publishedSource) } : {}
12142
+ ...REVISION_ID_PATTERN.test(str(record2.revisionId) ?? "") ? { revisionId: str(record2.revisionId) } : {},
12143
+ ...typeof record2.skillMd === "string" && record2.skillMd.length > 0 ? { skillMd: record2.skillMd } : {},
12144
+ ...str(record2.publishedSource) ? { publishedSource: str(record2.publishedSource) } : {}
11811
12145
  };
11812
12146
  }
11813
12147
  function pickCorpusOptions(options) {
@@ -11816,8 +12150,8 @@ function pickCorpusOptions(options) {
11816
12150
  function extractSlug(entry) {
11817
12151
  if (!entry || typeof entry !== "object")
11818
12152
  return;
11819
- const record = entry;
11820
- return str(record.slug) ?? str(record.name);
12153
+ const record2 = entry;
12154
+ return str(record2.slug) ?? str(record2.name);
11821
12155
  }
11822
12156
  function dedupe(values) {
11823
12157
  return [...new Set(values)];
@@ -11943,7 +12277,7 @@ import { dirname as dirname7, relative as relative4 } from "path";
11943
12277
  // package.json
11944
12278
  var package_default = {
11945
12279
  name: "@hasna/skills",
11946
- version: "0.4.0",
12280
+ version: "0.5.0",
11947
12281
  description: "Skills library for AI coding agents",
11948
12282
  type: "module",
11949
12283
  bin: {
@@ -11975,6 +12309,7 @@ var package_default = {
11975
12309
  files: [
11976
12310
  "dist/",
11977
12311
  "!dist/**/*.test.d.ts",
12312
+ "!dist/**/*.fixture.d.ts",
11978
12313
  "!dist/test-preload.d.ts",
11979
12314
  "!dist/platform",
11980
12315
  "bin/",
@@ -12000,8 +12335,9 @@ var package_default = {
12000
12335
  migrate: "bun run ./src/server/migrate.ts",
12001
12336
  typecheck: "tsc --noEmit",
12002
12337
  "verify:release": "bun run scripts/release-guard.ts",
12338
+ "verify:consumer-types": "bun run scripts/consumer-types.ts",
12003
12339
  prepare: "bun run build:js",
12004
- prepack: "bun run build && bun run verify:release",
12340
+ prepack: "bun run build && bun run verify:release && bun run verify:consumer-types",
12005
12341
  prepublishOnly: "bun run typecheck && bun run test"
12006
12342
  },
12007
12343
  keywords: [
@@ -12023,6 +12359,7 @@ var package_default = {
12023
12359
  author: "Hasna",
12024
12360
  license: "Apache-2.0",
12025
12361
  devDependencies: {
12362
+ "@hasna/contracts": "1.0.2",
12026
12363
  "@types/bun": "1.3.14",
12027
12364
  "@types/node": "25.2.3",
12028
12365
  "@types/react": "^18.2.0",
@@ -12034,8 +12371,7 @@ var package_default = {
12034
12371
  dependencies: {
12035
12372
  "@aws-sdk/client-ecs": "^3.1079.0",
12036
12373
  "@aws-sdk/client-s3": "^3.1079.0",
12037
- "@hasna/contracts": "1.0.1",
12038
- "@hasna/events": "0.1.16",
12374
+ "@hasna/events": "0.1.18",
12039
12375
  "@modelcontextprotocol/sdk": "^1.26.0",
12040
12376
  chalk: "^5.3.0",
12041
12377
  commander: "^12.1.0",
@@ -12540,6 +12876,104 @@ var toolContracts = [
12540
12876
  dependencies: objectSchema({}, [], "Package dependencies.", true)
12541
12877
  })
12542
12878
  },
12879
+ {
12880
+ name: "update_account_profile",
12881
+ title: "Update Account Display Name",
12882
+ description: "Update your display name with fresh email verification on the configured server.",
12883
+ params: ["name", "email", "code"],
12884
+ category: "execution",
12885
+ sideEffects: "local-process-or-remote-run",
12886
+ stable: true,
12887
+ inputSchema: objectSchema({ name: stringSchema("Display name, 1\u2013100 characters"), email: { type: "string", format: "email" }, code: { type: "string", pattern: "^\\d{6}$" } }, ["name", "email", "code"]),
12888
+ 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"])
12889
+ },
12890
+ {
12891
+ name: "update_workspace_name",
12892
+ title: "Update Workspace Name",
12893
+ description: "Update the current workspace name as an owner/admin with fresh email verification.",
12894
+ params: ["name", "email", "code"],
12895
+ category: "execution",
12896
+ sideEffects: "local-process-or-remote-run",
12897
+ stable: true,
12898
+ inputSchema: objectSchema({ name: stringSchema("Workspace name, 1\u2013100 characters"), email: { type: "string", format: "email" }, code: { type: "string", pattern: "^\\d{6}$" } }, ["name", "email", "code"]),
12899
+ outputSchema: objectSchema({ organization: objectSchema({ id: stringSchema("Workspace identifier."), slug: stringSchema("Stable workspace slug."), name: stringSchema("Workspace name.") }, ["id", "slug", "name"]) }, ["organization"])
12900
+ },
12901
+ {
12902
+ name: "set_workspace_member_role",
12903
+ title: "Set Current Workspace Member Role",
12904
+ description: "Set an exact membership incarnation's role with its observed expectedRole and fresh verification; no automatic retry.",
12905
+ params: ["membershipId", "role", "expectedRole", "email", "code"],
12906
+ category: "execution",
12907
+ sideEffects: "local-process-or-remote-run",
12908
+ stable: true,
12909
+ inputSchema: objectSchema({
12910
+ membershipId: { type: "string", pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$" },
12911
+ role: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
12912
+ expectedRole: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
12913
+ email: { type: "string", format: "email" },
12914
+ code: { type: "string", pattern: "^\\d{6}$" }
12915
+ }, ["membershipId", "role", "expectedRole", "email", "code"]),
12916
+ outputSchema: objectSchema({
12917
+ organizationId: stringSchema("Current workspace identifier."),
12918
+ changed: { type: "boolean" },
12919
+ member: objectSchema({
12920
+ membershipId: stringSchema("Membership incarnation."),
12921
+ userId: stringSchema("Account identifier."),
12922
+ email: stringSchema("Member email."),
12923
+ displayName: { oneOf: [{ type: "string" }, { type: "null" }] },
12924
+ role: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
12925
+ createdAt: stringSchema("Exact server timestamp including microseconds.")
12926
+ }, ["membershipId", "userId", "email", "displayName", "role", "createdAt"])
12927
+ }, ["organizationId", "member", "changed"])
12928
+ },
12929
+ {
12930
+ name: "remove_workspace_member",
12931
+ title: "Remove Current Workspace Member",
12932
+ description: "Remove exactly this membership incarnation using its observed expectedRole and fresh verification; self-removal is unavailable.",
12933
+ params: ["membershipId", "expectedRole", "email", "code"],
12934
+ category: "execution",
12935
+ sideEffects: "local-process-or-remote-run",
12936
+ stable: true,
12937
+ inputSchema: objectSchema({
12938
+ membershipId: { type: "string", pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$" },
12939
+ expectedRole: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
12940
+ email: { type: "string", format: "email" },
12941
+ code: { type: "string", pattern: "^\\d{6}$" }
12942
+ }, ["membershipId", "expectedRole", "email", "code"]),
12943
+ outputSchema: objectSchema({
12944
+ organizationId: stringSchema("Current workspace identifier."),
12945
+ membershipId: stringSchema("Removed membership incarnation."),
12946
+ removed: { type: "boolean", const: true },
12947
+ alreadyRemoved: { type: "boolean" }
12948
+ }, ["organizationId", "membershipId", "removed", "alreadyRemoved"])
12949
+ },
12950
+ {
12951
+ name: "list_workspace_members",
12952
+ title: "List Current Workspace Members",
12953
+ description: "Read one current-workspace roster page with fresh owner/admin email verification; saved credentials stay unchanged.",
12954
+ params: ["email", "code", "limit?", "cursor?"],
12955
+ category: "execution",
12956
+ sideEffects: "local-process-or-remote-run",
12957
+ stable: true,
12958
+ inputSchema: objectSchema({
12959
+ email: { type: "string", format: "email" },
12960
+ code: { type: "string", pattern: "^\\d{6}$" },
12961
+ limit: { type: "integer", minimum: 1, maximum: 100 },
12962
+ cursor: { type: "string", pattern: "^[A-Za-z0-9_-]{1,512}$" }
12963
+ }, ["email", "code"]),
12964
+ outputSchema: objectSchema({
12965
+ organizationId: stringSchema("Current workspace identifier."),
12966
+ members: arraySchema(objectSchema({
12967
+ membershipId: stringSchema("Membership incarnation."),
12968
+ userId: stringSchema("Account identifier."),
12969
+ email: stringSchema("Member email."),
12970
+ displayName: { oneOf: [{ type: "string" }, { type: "null" }] },
12971
+ role: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
12972
+ createdAt: stringSchema("Exact server timestamp including microseconds.")
12973
+ }, ["membershipId", "userId", "email", "displayName", "role", "createdAt"])),
12974
+ nextCursor: { oneOf: [{ type: "string" }, { type: "null" }] }
12975
+ }, ["organizationId", "members", "nextCursor"])
12976
+ },
12543
12977
  {
12544
12978
  name: "list_api_keys",
12545
12979
  title: "List API Keys",
@@ -13354,7 +13788,7 @@ class SkillsPostgresSyncStore {
13354
13788
  }
13355
13789
  async upsertRecords(records) {
13356
13790
  let count = 0;
13357
- for (const record of records) {
13791
+ for (const record2 of records) {
13358
13792
  await this.client.query([
13359
13793
  "INSERT INTO skills_sync_records",
13360
13794
  "(scope, kind, id, updated_at, deleted_at, source, payload)",
@@ -13365,13 +13799,13 @@ class SkillsPostgresSyncStore {
13365
13799
  "source = EXCLUDED.source,",
13366
13800
  "payload = EXCLUDED.payload"
13367
13801
  ].join(" "), [
13368
- record.scope,
13369
- record.kind,
13370
- record.id,
13371
- record.updatedAt,
13372
- record.deletedAt ?? null,
13373
- record.source ?? null,
13374
- JSON.stringify(record.payload)
13802
+ record2.scope,
13803
+ record2.kind,
13804
+ record2.id,
13805
+ record2.updatedAt,
13806
+ record2.deletedAt ?? null,
13807
+ record2.source ?? null,
13808
+ JSON.stringify(record2.payload)
13375
13809
  ]);
13376
13810
  count += 1;
13377
13811
  }
@@ -14353,10 +14787,10 @@ async function requestAuthApi(instance, path, options) {
14353
14787
  const text = await res.text();
14354
14788
  const body = text ? parseJsonBody(text) : {};
14355
14789
  if (!res.ok) {
14356
- const record = isRecord4(body) ? body : {};
14357
- const detail = typeof record.detail === "string" ? record.detail : undefined;
14358
- const error = typeof record.error === "string" ? record.error : undefined;
14359
- const code = typeof record.code === "string" ? record.code : undefined;
14790
+ const record2 = isRecord5(body) ? body : {};
14791
+ const detail = typeof record2.detail === "string" ? record2.detail : undefined;
14792
+ const error = typeof record2.error === "string" ? record2.error : undefined;
14793
+ const code = typeof record2.code === "string" ? record2.code : undefined;
14360
14794
  throw new HostedApiError(detail || error || `${res.status} ${res.statusText}`, {
14361
14795
  status: res.status,
14362
14796
  code,
@@ -14381,7 +14815,7 @@ function condenseErrorBody(text) {
14381
14815
  return collapsed;
14382
14816
  return `${collapsed.slice(0, MAX_ERROR_DETAIL_LENGTH - 1).trimEnd()}\u2026`;
14383
14817
  }
14384
- function isRecord4(value) {
14818
+ function isRecord5(value) {
14385
14819
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
14386
14820
  }
14387
14821
 
@@ -14403,12 +14837,13 @@ class RemoteSkillsAuthClient {
14403
14837
  return this.request("/api/auth/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) });
14404
14838
  }
14405
14839
  async sessionClient(email, code) {
14840
+ const apiOrigin = this.apiOrigin;
14406
14841
  if (!email.includes("@") || !/^\d{6}$/.test(code))
14407
- throw new Error("Fresh email and six-digit verification code are required to manage API keys");
14842
+ throw new Error("Fresh email and six-digit verification code are required to manage this account");
14408
14843
  const login = await this.verifyCode(email, code);
14409
14844
  if (!login || typeof login.token !== "string" || !login.token)
14410
14845
  throw new Error("The server did not return an authorized account session");
14411
- return new RemoteSkillsClient(login.token, this.apiOrigin);
14846
+ return new RemoteSkillsClient(login.token, apiOrigin);
14412
14847
  }
14413
14848
  async createApiKey(email, code, name, scopes) {
14414
14849
  return (await this.sessionClient(email, code)).createApiKey(name, scopes);
@@ -14419,6 +14854,26 @@ class RemoteSkillsAuthClient {
14419
14854
  async revokeApiKey(email, code, keyId) {
14420
14855
  return (await this.sessionClient(email, code)).revokeApiKey(keyId);
14421
14856
  }
14857
+ async updateProfile(email, code, input) {
14858
+ customerNamePatch(input, "displayName");
14859
+ return (await this.sessionClient(email, code)).updateProfile(input);
14860
+ }
14861
+ async updateCurrentWorkspace(email, code, input) {
14862
+ customerNamePatch(input, "name");
14863
+ return (await this.sessionClient(email, code)).updateCurrentWorkspace(input);
14864
+ }
14865
+ async listWorkspaceMembers(email, code, options = {}) {
14866
+ workspaceMembersQuery(options);
14867
+ return (await this.sessionClient(email, code)).listWorkspaceMembers(options);
14868
+ }
14869
+ async setWorkspaceMemberRole(email, code, membershipId, input) {
14870
+ const captured = workspaceMemberRoleInput(membershipId, input);
14871
+ return (await this.sessionClient(email, code)).setWorkspaceMemberRole(captured.membershipId, captured.body);
14872
+ }
14873
+ async removeWorkspaceMember(email, code, membershipId, input) {
14874
+ const captured = workspaceMemberRemovalInput(membershipId, input);
14875
+ return (await this.sessionClient(email, code)).removeWorkspaceMember(captured.membershipId, captured.body);
14876
+ }
14422
14877
  request(path, options) {
14423
14878
  if (!["/api/auth/login", "/api/auth/verify", "/api/auth/device/start", "/api/auth/device/token", "/api/auth/keys", "/api/auth/whoami"].includes(path))
14424
14879
  throw new Error("Unsupported authentication operation");
@@ -14461,6 +14916,7 @@ export {
14461
14916
  sha256File,
14462
14917
  setSkillDisabled,
14463
14918
  setScheduleEnabled,
14919
+ selectsSkillsLocalMode,
14464
14920
  searchSkills,
14465
14921
  scaffoldPortableSkill,
14466
14922
  saveProjectConfig,
@@ -14521,6 +14977,7 @@ export {
14521
14977
  listPinnedSkills,
14522
14978
  listMcpToolContracts,
14523
14979
  isSyncAgent,
14980
+ isSkillsLocalOptIn,
14524
14981
  isRegularFile,
14525
14982
  isPortableWithinSkill,
14526
14983
  isGatewayBackedSkill,
@@ -14628,6 +15085,7 @@ export {
14628
15085
  SKILLS_PROJECT_DIR,
14629
15086
  SKILLS_NATIVE_STORAGE_FALLBACK_ENV,
14630
15087
  SKILLS_NATIVE_STORAGE_ENV,
15088
+ SKILLS_LOCAL_OPT_IN_ENV_KEYS,
14631
15089
  SKILLS_CLI_MCP_PARITY,
14632
15090
  SKILLS_APP,
14633
15091
  SKILLS_API_URL_ENV_KEYS,
@@ -14635,11 +15093,13 @@ export {
14635
15093
  SKILLS_API_KEY_ENV_KEYS,
14636
15094
  SKILLS_API_KEY_ENV,
14637
15095
  SKILLS,
15096
+ RemoteWorkspaceMemberError,
14638
15097
  RemoteSkillsClient,
14639
15098
  RemoteSkillsAuthClient,
14640
15099
  RemoteRouteUnsupportedError,
14641
15100
  RemoteRequestError,
14642
15101
  RemoteCreditApprovalError,
15102
+ RemoteCapabilityUnavailableError,
14643
15103
  REMOTE_SKILL_RUN_CONTRACT_VERSION,
14644
15104
  REFUSED_SCANNER_FLAGGED,
14645
15105
  PullSkillError,