@hasna/skills 0.5.1 → 0.5.3

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/dist/sdk/index.js CHANGED
@@ -25462,7 +25462,7 @@ class MissingSkillsFleetError extends Error {
25462
25462
  // package.json
25463
25463
  var package_default = {
25464
25464
  name: "@hasna/skills",
25465
- version: "0.5.1",
25465
+ version: "0.5.3",
25466
25466
  description: "Skills library for AI coding agents",
25467
25467
  type: "module",
25468
25468
  bin: {
@@ -25635,6 +25635,41 @@ import { createHmac, timingSafeEqual } from "crypto";
25635
25635
  import { createHash } from "crypto";
25636
25636
  import { readFileSync as readFileSync2, readdirSync, statSync } from "fs";
25637
25637
  import { join as join2, relative } from "path";
25638
+ import { createGunzip } from "zlib";
25639
+
25640
+ // src/lib/skill-entry-path.ts
25641
+ class SkillEntryPaths {
25642
+ files = new Set;
25643
+ directories = new Set;
25644
+ add(path, maxBytes, invalid, limit) {
25645
+ if (path.length > maxBytes)
25646
+ limit();
25647
+ const encoded = new TextEncoder().encode(path);
25648
+ if (encoded.byteLength > maxBytes)
25649
+ limit();
25650
+ if (new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(encoded) !== path)
25651
+ invalid("Invalid UTF-8 entry path");
25652
+ if (!path || /[\\:\x00-\x1f\x7f]/u.test(path))
25653
+ invalid("Unsafe entry path");
25654
+ if (path.split("/").some((segment) => !segment || segment === "." || segment === ".."))
25655
+ invalid("Unsafe entry path segment");
25656
+ const key = path.normalize("NFC").toLowerCase().normalize("NFC");
25657
+ if (this.files.has(key) || this.directories.has(key))
25658
+ invalid("Duplicate or conflicting entry path");
25659
+ const parents = key.split("/");
25660
+ parents.pop();
25661
+ while (parents.length) {
25662
+ const parent = parents.join("/");
25663
+ if (this.files.has(parent))
25664
+ invalid("Conflicting entry file ancestor");
25665
+ this.directories.add(parent);
25666
+ parents.pop();
25667
+ }
25668
+ this.files.add(key);
25669
+ }
25670
+ }
25671
+
25672
+ // src/lib/skill-bundle.ts
25638
25673
  var BLOCK = 512;
25639
25674
  var ANY_SEGMENT_EXCLUDES = new Set([
25640
25675
  ".git",
@@ -25957,6 +25992,227 @@ function concat(chunks) {
25957
25992
  }
25958
25993
  return merged;
25959
25994
  }
25995
+ var SKILL_BUNDLE_INSPECTION_LIMITS = Object.freeze({
25996
+ compressedBytes: 16 * 1024 * 1024,
25997
+ decompressedBytes: 64 * 1024 * 1024,
25998
+ entries: 1024,
25999
+ fileBytes: 16 * 1024 * 1024,
26000
+ pathBytes: 100,
26001
+ timeoutMs: 5000
26002
+ });
26003
+
26004
+ class SkillBundleInspectionError extends Error {
26005
+ code;
26006
+ constructor(code, message) {
26007
+ super(message);
26008
+ this.code = code;
26009
+ this.name = "SkillBundleInspectionError";
26010
+ }
26011
+ }
26012
+ function invalidBundle(message) {
26013
+ throw new SkillBundleInspectionError("BUNDLE_INVALID", message);
26014
+ }
26015
+ function inspectionLimits(options) {
26016
+ const limits = { ...SKILL_BUNDLE_INSPECTION_LIMITS };
26017
+ for (const key of Object.keys(options.limits ?? {})) {
26018
+ if (!Object.hasOwn(limits, key))
26019
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Unknown bundle limit");
26020
+ const field = key;
26021
+ const value = options.limits[field];
26022
+ if (!Number.isSafeInteger(value) || value <= 0 || value > limits[field]) {
26023
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle limits must be positive integers within the hard ceilings");
26024
+ }
26025
+ limits[field] = value;
26026
+ }
26027
+ return limits;
26028
+ }
26029
+ async function inspectSkillBundle(bundle, options = {}) {
26030
+ const signal = options.signal;
26031
+ const limits = inspectionLimits(options);
26032
+ const deadline = performance.now() + limits.timeoutMs;
26033
+ const check = () => {
26034
+ if (signal?.aborted)
26035
+ throw new SkillBundleInspectionError("BUNDLE_ABORTED", "Bundle inspection aborted");
26036
+ if (performance.now() >= deadline)
26037
+ throw new SkillBundleInspectionError("BUNDLE_TIMEOUT", "Bundle inspection deadline exceeded");
26038
+ };
26039
+ check();
26040
+ if (bundle.byteLength > limits.compressedBytes)
26041
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Compressed bundle exceeds byte limit");
26042
+ const snapshot = ownBytes(bundle);
26043
+ check();
26044
+ const sha256 = sha256Hex(snapshot);
26045
+ check();
26046
+ const parser = new BoundedTarReader(limits, check);
26047
+ const streamOptions = { chunkSize: 16 * 1024, highWaterMark: 16 * 1024 };
26048
+ const decoder = createGunzip(streamOptions);
26049
+ let terminalError;
26050
+ const stop = (code) => {
26051
+ terminalError ??= new SkillBundleInspectionError(code, code === "BUNDLE_ABORTED" ? "Bundle inspection aborted" : "Bundle inspection deadline exceeded");
26052
+ decoder.destroy(terminalError);
26053
+ };
26054
+ const onAbort = () => stop("BUNDLE_ABORTED");
26055
+ const timer = setTimeout(() => stop("BUNDLE_TIMEOUT"), Math.max(1, deadline - performance.now()));
26056
+ signal?.addEventListener("abort", onAbort, { once: true });
26057
+ let decompressedByteSize = 0;
26058
+ let bytesSinceYield = 0;
26059
+ try {
26060
+ check();
26061
+ decoder.end(snapshot);
26062
+ for await (const chunk of decoder) {
26063
+ check();
26064
+ decompressedByteSize += chunk.byteLength;
26065
+ if (decompressedByteSize > limits.decompressedBytes)
26066
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Decompressed bundle exceeds byte limit");
26067
+ parser.push(chunk);
26068
+ bytesSinceYield += chunk.byteLength;
26069
+ if (bytesSinceYield >= 256 * 1024) {
26070
+ await new Promise((resolve) => setTimeout(resolve, 0));
26071
+ bytesSinceYield = 0;
26072
+ check();
26073
+ }
26074
+ }
26075
+ check();
26076
+ const entries = parser.finish();
26077
+ return {
26078
+ entries,
26079
+ sha256,
26080
+ compressedByteSize: snapshot.byteLength,
26081
+ decompressedByteSize,
26082
+ unpackedByteSize: parser.fileBytes,
26083
+ fileCount: entries.length
26084
+ };
26085
+ } catch (error) {
26086
+ if (terminalError)
26087
+ throw terminalError;
26088
+ if (error instanceof SkillBundleInspectionError)
26089
+ throw error;
26090
+ throw new SkillBundleInspectionError("BUNDLE_INVALID", "Invalid or truncated gzip bundle");
26091
+ } finally {
26092
+ clearTimeout(timer);
26093
+ signal?.removeEventListener("abort", onAbort);
26094
+ decoder.destroy();
26095
+ }
26096
+ }
26097
+
26098
+ class BoundedTarReader {
26099
+ limits;
26100
+ check;
26101
+ header = new Uint8Array(BLOCK);
26102
+ headerOffset = 0;
26103
+ pending;
26104
+ bodyOffset = 0;
26105
+ padding = 0;
26106
+ zeroBlocks = 0;
26107
+ entries = [];
26108
+ paths = new SkillEntryPaths;
26109
+ fileBytes = 0;
26110
+ constructor(limits, check) {
26111
+ this.limits = limits;
26112
+ this.check = check;
26113
+ }
26114
+ push(chunk) {
26115
+ let offset = 0;
26116
+ while (offset < chunk.byteLength) {
26117
+ this.check();
26118
+ if (this.pending) {
26119
+ const count = Math.min(this.pending.bytes.byteLength - this.bodyOffset, chunk.byteLength - offset);
26120
+ this.pending.bytes.set(chunk.subarray(offset, offset + count), this.bodyOffset);
26121
+ offset += count;
26122
+ this.bodyOffset += count;
26123
+ if (this.bodyOffset === this.pending.bytes.byteLength) {
26124
+ this.entries.push(this.pending);
26125
+ this.pending = undefined;
26126
+ }
26127
+ } else if (this.padding) {
26128
+ const count = Math.min(this.padding, chunk.byteLength - offset);
26129
+ if (chunk.subarray(offset, offset + count).some((byte) => byte !== 0))
26130
+ invalidBundle("Nonzero tar body padding");
26131
+ offset += count;
26132
+ this.padding -= count;
26133
+ } else {
26134
+ const count = Math.min(BLOCK - this.headerOffset, chunk.byteLength - offset);
26135
+ this.header.set(chunk.subarray(offset, offset + count), this.headerOffset);
26136
+ offset += count;
26137
+ this.headerOffset += count;
26138
+ if (this.headerOffset === BLOCK) {
26139
+ this.readHeader();
26140
+ this.headerOffset = 0;
26141
+ }
26142
+ }
26143
+ }
26144
+ }
26145
+ finish() {
26146
+ this.check();
26147
+ if (this.pending || this.padding || this.headerOffset || this.zeroBlocks < 2)
26148
+ invalidBundle("Truncated tar bundle");
26149
+ return this.entries;
26150
+ }
26151
+ readHeader() {
26152
+ this.check();
26153
+ const h = this.header;
26154
+ if (h.every((byte) => byte === 0)) {
26155
+ this.zeroBlocks++;
26156
+ return;
26157
+ }
26158
+ if (this.zeroBlocks)
26159
+ invalidBundle("Nonzero tar data after terminator");
26160
+ if (this.entries.length >= this.limits.entries)
26161
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle entry limit exceeded");
26162
+ let checksum = 0;
26163
+ for (let i = 0;i < BLOCK; i++)
26164
+ checksum += i >= 148 && i < 156 ? 32 : h[i];
26165
+ if (tarOctal(h.subarray(148, 156)) !== checksum)
26166
+ invalidBundle("Invalid tar header checksum");
26167
+ if (new TextDecoder().decode(h.subarray(257, 265)) !== "ustar\x00" + "00")
26168
+ invalidBundle("Unsupported tar format");
26169
+ if (h[156] !== 48 && h[156] !== 0 || h.subarray(157, 257).some((b) => b !== 0) || h.subarray(345).some((b) => b !== 0))
26170
+ invalidBundle("Unsupported tar entry or path prefix");
26171
+ const mode = tarOctal(h.subarray(100, 108));
26172
+ if (mode > 511)
26173
+ invalidBundle("Unsupported tar permission bits");
26174
+ tarOctal(h.subarray(108, 116));
26175
+ tarOctal(h.subarray(116, 124));
26176
+ tarOctal(h.subarray(136, 148));
26177
+ const size = tarOctal(h.subarray(124, 136));
26178
+ if (size > this.limits.fileBytes || this.fileBytes + size > this.limits.decompressedBytes) {
26179
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle file byte limit exceeded");
26180
+ }
26181
+ const name = h.subarray(0, 100);
26182
+ const end = name.indexOf(0);
26183
+ if (end !== -1 && name.subarray(end).some((b) => b !== 0))
26184
+ invalidBundle("Invalid tar path padding");
26185
+ const raw = end === -1 ? name : name.subarray(0, end);
26186
+ if (raw.byteLength > this.limits.pathBytes)
26187
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle path byte limit exceeded");
26188
+ let path;
26189
+ try {
26190
+ path = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(raw);
26191
+ } catch {
26192
+ return invalidBundle("Invalid UTF-8 bundle path");
26193
+ }
26194
+ this.paths.add(path, this.limits.pathBytes, invalidBundle, () => {
26195
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle path byte limit exceeded");
26196
+ });
26197
+ this.fileBytes += size;
26198
+ this.pending = { path, mode, bytes: new Uint8Array(new ArrayBuffer(size)) };
26199
+ this.bodyOffset = 0;
26200
+ this.padding = (BLOCK - size % BLOCK) % BLOCK;
26201
+ if (!size) {
26202
+ this.entries.push(this.pending);
26203
+ this.pending = undefined;
26204
+ }
26205
+ }
26206
+ }
26207
+ function tarOctal(field) {
26208
+ const text = new TextDecoder().decode(field);
26209
+ if (!/^[0-7]+[\0 ]*$/.test(text))
26210
+ invalidBundle("Invalid tar octal field");
26211
+ const value = Number.parseInt(text, 8);
26212
+ if (!Number.isSafeInteger(value))
26213
+ invalidBundle("Tar integer is out of range");
26214
+ return value;
26215
+ }
25960
26216
 
25961
26217
  // src/lib/skill-bundles.ts
25962
26218
  var SKILLS_SIGNING_KEY_ENV = "SKILLS_SIGNING_KEY";
@@ -38659,6 +38915,9 @@ import { existsSync as existsSync6, readdirSync as readdirSync6, readFileSync as
38659
38915
  import { join as join11, sep } from "path";
38660
38916
  var CONTENT_HASH_ALGORITHM = "sha256";
38661
38917
  var HASH_EXCLUDE_DIRS = new Set([".git", "node_modules", "dist", "build", ".turbo"]);
38918
+ function excludedHashEntry(name, directory) {
38919
+ return name.startsWith(".") || directory && HASH_EXCLUDE_DIRS.has(name);
38920
+ }
38662
38921
  var HASH_COVERAGE = [
38663
38922
  "SKILL.md",
38664
38923
  "skill.json",
@@ -38740,7 +38999,7 @@ function collectDirectory(files, dir, rel) {
38740
38999
  if (stats.isSymbolicLink())
38741
39000
  continue;
38742
39001
  if (stats.isDirectory()) {
38743
- if (HASH_EXCLUDE_DIRS.has(entry))
39002
+ if (excludedHashEntry(entry, true))
38744
39003
  continue;
38745
39004
  collectDirectory(files, absolute, childRel);
38746
39005
  } else if (stats.isFile()) {
@@ -38750,28 +39009,242 @@ function collectDirectory(files, dir, rel) {
38750
39009
  }
38751
39010
  function collectFile(files, absolute, rel) {
38752
39011
  const buffer = readFileSync8(absolute);
39012
+ files.push(normalizeBundleFile(rel.split(sep).join("/"), buffer));
39013
+ }
39014
+ function normalizeBundleFile(rel, buffer) {
38753
39015
  if (rel === "skill.json") {
38754
- files.push({ rel: rel.split(sep).join("/"), content: new TextEncoder().encode(canonicalizeManifest(new TextDecoder().decode(buffer))) });
38755
- return;
39016
+ return { rel, content: new TextEncoder().encode(canonicalizeManifest(new TextDecoder().decode(buffer))) };
38756
39017
  }
38757
39018
  if (looksLikeText(buffer)) {
38758
39019
  const normalized = normalizeLineEndings(new TextDecoder().decode(buffer));
38759
- files.push({ rel: rel.split(sep).join("/"), content: new TextEncoder().encode(normalized) });
38760
- return;
39020
+ return { rel, content: new TextEncoder().encode(normalized) };
38761
39021
  }
38762
- files.push({ rel: rel.split(sep).join("/"), content: buffer });
39022
+ return { rel, content: buffer };
38763
39023
  }
38764
39024
  function computeContentHash(skillPath) {
39025
+ return hashBundleFiles(collectBundleFiles(skillPath));
39026
+ }
39027
+ function* bundleHashParts(files) {
39028
+ for (const file of files) {
39029
+ yield new TextEncoder().encode(file.rel);
39030
+ yield new TextEncoder().encode(`\x00${file.content.length}\x00`);
39031
+ yield file.content;
39032
+ yield new TextEncoder().encode("\x00");
39033
+ }
39034
+ yield new TextEncoder().encode("\x00");
39035
+ }
39036
+ function hashBundleFiles(files) {
38765
39037
  const hash = createHash6(CONTENT_HASH_ALGORITHM);
38766
- for (const file of collectBundleFiles(skillPath)) {
38767
- hash.update(new TextEncoder().encode(file.rel));
38768
- hash.update(new TextEncoder().encode(`\x00${file.content.length}\x00`));
38769
- hash.update(file.content);
38770
- hash.update(new TextEncoder().encode("\x00"));
39038
+ for (const part of bundleHashParts(files))
39039
+ hash.update(part);
39040
+ return hash.digest("hex");
39041
+ }
39042
+ async function hashBundleFilesCooperatively(files, check) {
39043
+ const hash = createHash6(CONTENT_HASH_ALGORITHM);
39044
+ let bytesSinceYield = 0;
39045
+ for (const part of bundleHashParts(files)) {
39046
+ for (let offset = 0;offset < part.byteLength; offset += 64 * 1024) {
39047
+ check();
39048
+ const chunk = part.subarray(offset, offset + 64 * 1024);
39049
+ hash.update(chunk);
39050
+ bytesSinceYield += chunk.byteLength;
39051
+ if (bytesSinceYield >= 256 * 1024) {
39052
+ await new Promise((resolve2) => setImmediate(resolve2));
39053
+ bytesSinceYield = 0;
39054
+ }
39055
+ }
38771
39056
  }
38772
- hash.update(new TextEncoder().encode("\x00"));
39057
+ check();
38773
39058
  return hash.digest("hex");
38774
39059
  }
39060
+ var CONTENT_HASH_LIMITS = Object.freeze({
39061
+ entries: 1024,
39062
+ rawBytes: 64 * 1024 * 1024,
39063
+ normalizedBytes: 64 * 1024 * 1024,
39064
+ fileBytes: 16 * 1024 * 1024,
39065
+ normalizedFileBytes: 16 * 1024 * 1024,
39066
+ pathBytes: 100,
39067
+ manifestBytes: 16 * 1024,
39068
+ manifestDepth: 64,
39069
+ timeoutMs: 5000
39070
+ });
39071
+
39072
+ class ContentHashInputError extends Error {
39073
+ code;
39074
+ constructor(code, message) {
39075
+ super(message);
39076
+ this.code = code;
39077
+ this.name = "ContentHashInputError";
39078
+ }
39079
+ }
39080
+ function invalidContent(message = "Invalid content hash input") {
39081
+ throw new ContentHashInputError("CONTENT_HASH_INVALID", message);
39082
+ }
39083
+ function contentLimit(message) {
39084
+ throw new ContentHashInputError("CONTENT_HASH_LIMIT", message);
39085
+ }
39086
+ function contentRecord(value, allowed) {
39087
+ if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
39088
+ invalidContent();
39089
+ const result = Object.create(null);
39090
+ for (const key of Reflect.ownKeys(value)) {
39091
+ if (typeof key !== "string" || !allowed.includes(key))
39092
+ invalidContent();
39093
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
39094
+ if (!descriptor || !("value" in descriptor))
39095
+ invalidContent("Accessor content hash input is unsupported");
39096
+ result[key] = descriptor.value;
39097
+ }
39098
+ return result;
39099
+ }
39100
+ function contentOptions(options) {
39101
+ const record = contentRecord(options, ["limits", "signal"]);
39102
+ const limits = { ...CONTENT_HASH_LIMITS };
39103
+ if (record.limits !== undefined) {
39104
+ const supplied = contentRecord(record.limits, Object.keys(limits));
39105
+ for (const key of Object.keys(supplied)) {
39106
+ const value = supplied[key];
39107
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0 || value > limits[key])
39108
+ contentLimit("Invalid content hash limit");
39109
+ limits[key] = value;
39110
+ }
39111
+ }
39112
+ if (record.signal !== undefined && !(record.signal instanceof AbortSignal))
39113
+ invalidContent("Invalid content hash signal");
39114
+ return { limits, signal: record.signal };
39115
+ }
39116
+ var typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
39117
+ var byteLengthOf = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteLength").get;
39118
+ var bufferOf = Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer").get;
39119
+ function snapshotContentEntries(entries, limits, check) {
39120
+ if (!Array.isArray(entries))
39121
+ invalidContent("Content hash entries must be an array");
39122
+ if (entries.length > limits.entries)
39123
+ contentLimit("Content hash entry limit exceeded");
39124
+ if (Reflect.ownKeys(entries).length !== entries.length + 1)
39125
+ invalidContent("Invalid content hash entry array");
39126
+ const snapshot = [];
39127
+ const paths = new SkillEntryPaths;
39128
+ let rawBytes = 0;
39129
+ for (let index = 0;index < entries.length; index++) {
39130
+ check();
39131
+ const descriptor = Object.getOwnPropertyDescriptor(entries, String(index));
39132
+ if (!descriptor || !("value" in descriptor))
39133
+ invalidContent("Invalid content hash entry array");
39134
+ const entry = contentRecord(descriptor.value, ["path", "bytes", "mode"]);
39135
+ if (typeof entry.path !== "string" || typeof entry.mode !== "number" || !Number.isInteger(entry.mode) || entry.mode < 0 || entry.mode > 511)
39136
+ invalidContent("Invalid regular-file content hash entry");
39137
+ paths.add(entry.path, limits.pathBytes, invalidContent, () => contentLimit("Content hash path limit exceeded"));
39138
+ if (!(entry.bytes instanceof Uint8Array) || !ArrayBuffer.isView(entry.bytes))
39139
+ invalidContent("Content hash entry requires bytes");
39140
+ const size = byteLengthOf.call(entry.bytes);
39141
+ if (!(bufferOf.call(entry.bytes) instanceof ArrayBuffer))
39142
+ invalidContent("Shared content hash bytes are unsupported");
39143
+ if (size > limits.fileBytes || rawBytes + size > limits.rawBytes)
39144
+ contentLimit("Content hash raw byte limit exceeded");
39145
+ if (entry.path === "skill.json" && size > limits.manifestBytes)
39146
+ contentLimit("Content hash manifest byte limit exceeded");
39147
+ rawBytes += size;
39148
+ const bytes = new Uint8Array(new ArrayBuffer(size));
39149
+ bytes.set(entry.bytes);
39150
+ snapshot.push({ path: entry.path, bytes, mode: entry.mode });
39151
+ }
39152
+ check();
39153
+ return snapshot;
39154
+ }
39155
+ function coveredContentPath(path) {
39156
+ const segments = path.split("/");
39157
+ if (!HASH_COVERAGE.includes(segments[0]))
39158
+ return false;
39159
+ return !segments.slice(1).some((segment, index) => excludedHashEntry(segment, index < segments.length - 2));
39160
+ }
39161
+ function boundedManifest(raw, maxDepth) {
39162
+ let parsed;
39163
+ try {
39164
+ parsed = JSON.parse(raw);
39165
+ } catch {
39166
+ return;
39167
+ }
39168
+ const pending = [{ value: parsed, depth: 1 }];
39169
+ while (pending.length) {
39170
+ const { value, depth } = pending.pop();
39171
+ if (!value || typeof value !== "object")
39172
+ continue;
39173
+ if (depth > maxDepth)
39174
+ contentLimit("Content hash manifest depth limit exceeded");
39175
+ for (const child of Object.values(value))
39176
+ pending.push({ value: child, depth: depth + 1 });
39177
+ }
39178
+ return parsed;
39179
+ }
39180
+ async function hashContentEntries(entries, options) {
39181
+ const { limits, signal } = contentOptions(options);
39182
+ const deadline = performance.now() + limits.timeoutMs;
39183
+ let terminal;
39184
+ const abort = () => {
39185
+ terminal ??= new ContentHashInputError("CONTENT_HASH_ABORTED", "Content hashing aborted");
39186
+ };
39187
+ const timer = setTimeout(() => {
39188
+ terminal ??= new ContentHashInputError("CONTENT_HASH_TIMEOUT", "Content hashing deadline exceeded");
39189
+ }, limits.timeoutMs);
39190
+ const check = () => {
39191
+ if (signal?.aborted)
39192
+ abort();
39193
+ if (terminal)
39194
+ throw terminal;
39195
+ if (performance.now() >= deadline)
39196
+ throw new ContentHashInputError("CONTENT_HASH_TIMEOUT", "Content hashing deadline exceeded");
39197
+ };
39198
+ try {
39199
+ signal?.addEventListener("abort", abort, { once: true });
39200
+ check();
39201
+ const snapshot = snapshotContentEntries(entries, limits, check);
39202
+ const normalized = [];
39203
+ let normalizedBytes = 0;
39204
+ let manifest;
39205
+ await new Promise((resolve2) => setImmediate(resolve2));
39206
+ for (const entry of snapshot) {
39207
+ check();
39208
+ if (!coveredContentPath(entry.path))
39209
+ continue;
39210
+ if (entry.path === "skill.json")
39211
+ manifest = boundedManifest(new TextDecoder().decode(entry.bytes), limits.manifestDepth);
39212
+ const file = normalizeBundleFile(entry.path, entry.bytes);
39213
+ check();
39214
+ if (file.content.byteLength > limits.normalizedFileBytes || normalizedBytes + file.content.byteLength > limits.normalizedBytes)
39215
+ contentLimit("Content hash normalized byte limit exceeded");
39216
+ normalizedBytes += file.content.byteLength;
39217
+ normalized.push(file);
39218
+ await new Promise((resolve2) => setImmediate(resolve2));
39219
+ }
39220
+ normalized.sort((a3, b3) => a3.rel < b3.rel ? -1 : a3.rel > b3.rel ? 1 : 0);
39221
+ check();
39222
+ return { hash: await hashBundleFilesCooperatively(normalized, check), manifest };
39223
+ } catch (error) {
39224
+ if (error instanceof ContentHashInputError)
39225
+ throw error;
39226
+ throw new ContentHashInputError("CONTENT_HASH_INVALID", "Invalid content hash input");
39227
+ } finally {
39228
+ clearTimeout(timer);
39229
+ signal?.removeEventListener("abort", abort);
39230
+ }
39231
+ }
39232
+ async function computeContentHashFromEntries(entries, options = {}) {
39233
+ return (await hashContentEntries(entries, options)).hash;
39234
+ }
39235
+ async function verifyContentHashFromEntries(entries, options = {}) {
39236
+ const { hash, manifest } = await hashContentEntries(entries, options);
39237
+ const provenance = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? manifest.provenance : undefined;
39238
+ const value = provenance && typeof provenance === "object" && !Array.isArray(provenance) ? provenance.content_hash : undefined;
39239
+ if (value !== undefined && typeof value !== "string")
39240
+ invalidContent("Invalid content hash declaration");
39241
+ const declaredHash = value?.trim() || undefined;
39242
+ if (!declaredHash)
39243
+ return { declared: false, valid: false };
39244
+ if (!/^[a-f0-9]{64}$/.test(declaredHash))
39245
+ return { declared: true, valid: false, declaredHash };
39246
+ return { declared: true, valid: hash === declaredHash, declaredHash, computedHash: hash };
39247
+ }
38775
39248
  function verifyContentHash(skillPath, manifest) {
38776
39249
  const declaredHash = manifest?.provenance?.content_hash?.trim() || undefined;
38777
39250
  if (!declaredHash)
@@ -52164,11 +52637,174 @@ function createOfflineGate(options) {
52164
52637
  }
52165
52638
  };
52166
52639
  }
52167
- // src/lib/remote-workspace-selection.ts
52640
+ // src/lib/remote-invitations.ts
52641
+ class WorkspaceInvitationInputError extends Error {
52642
+ code = "INVITATION_INPUT_INVALID";
52643
+ constructor() {
52644
+ super("Provide only the documented invitation fields, exact lowercase IDs, expected generation and explicit confirmation. Issue and resend require your stable idempotency key.");
52645
+ this.name = "WorkspaceInvitationInputError";
52646
+ }
52647
+ }
52648
+ var invitationFailures = {
52649
+ INVALID_REQUEST: [400, "Invitation parameters were refused."],
52650
+ ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
52651
+ INTERACTIVE_SESSION_REQUIRED: [403, "Fresh interactive sign-in is required."],
52652
+ WORKSPACE_ADMIN_REQUIRED: [403, "A current workspace owner or admin is required."],
52653
+ INVITATION_FORBIDDEN: [403, "Your current role cannot manage this invitation."],
52654
+ INVITATION_UNAVAILABLE: [404, "Invitation is unavailable for this account."],
52655
+ INVITATION_CHANGED: [409, "Invitation changed. Read its current generation before another action."],
52656
+ INVITATION_EXISTS: [409, "A pending invitation already exists. Read current invitations."],
52657
+ ALREADY_MEMBER: [409, "An active membership already exists. An invitation cannot change its role."],
52658
+ IDEMPOTENCY_CONFLICT: [409, "This request key was used for different invitation parameters. Reconcile the original request."],
52659
+ INVITATION_LIMIT: [429, "Invitation limit reached. Wait before issuing or resending."],
52660
+ INVITATION_BUSY: [503, "Invitation is busy. Read its state before another action."],
52661
+ INVITATION_DELIVERY_UNAVAILABLE: [503, "Invitation email delivery is unavailable."]
52662
+ };
52663
+
52664
+ class RemoteWorkspaceInvitationError extends Error {
52665
+ code;
52666
+ status;
52667
+ constructor(code) {
52668
+ super(invitationFailures[code][1]);
52669
+ this.code = code;
52670
+ this.name = "RemoteWorkspaceInvitationError";
52671
+ this.status = invitationFailures[code][0];
52672
+ }
52673
+ }
52674
+
52675
+ class RemoteWorkspaceInvitationUnconfirmedError extends Error {
52676
+ code = "INVITATION_UNCONFIRMED";
52677
+ constructor() {
52678
+ super("The invitation outcome is unconfirmed. Read current invitations or memberships. Reconcile issue/resend only with the same request key, parameters, server and membership; never generate a new key or retry automatically. Saved credentials are unchanged.");
52679
+ this.name = "RemoteWorkspaceInvitationUnconfirmedError";
52680
+ }
52681
+ }
52682
+
52683
+ class RemoteWorkspaceInvitationReadError extends Error {
52684
+ code = "INVITATION_READ_FAILED";
52685
+ constructor() {
52686
+ super("Unable to read a valid invitation result. Check the selected server, account, current membership and permissions.");
52687
+ this.name = "RemoteWorkspaceInvitationReadError";
52688
+ }
52689
+ }
52690
+ function invitationFailure(value, status) {
52691
+ if (!record(value) || typeof value.code !== "string" || !Object.hasOwn(invitationFailures, value.code))
52692
+ return null;
52693
+ const code = value.code;
52694
+ return invitationFailures[code][0] === status ? code : null;
52695
+ }
52168
52696
  var record = (v2) => !!v2 && typeof v2 === "object" && !Array.isArray(v2);
52169
52697
  var uuid = (v2) => typeof v2 === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v2);
52170
- var text = (v2, max = 1024) => typeof v2 === "string" && !!v2.trim() && v2.length <= max && !/[\p{Cc}\p{Cs}\u2028\u2029]/u.test(v2);
52171
52698
  var role = (v2) => typeof v2 === "string" && ["owner", "admin", "member", "viewer"].includes(v2);
52699
+ var email = (v2) => typeof v2 === "string" && v2.length <= 254 && !/[\p{Cc}\p{Cs}\u2028\u2029\s]/u.test(v2) && /^[^@]+@[^@]+\.[^@]+$/.test(v2);
52700
+ var timestamp = (v2) => typeof v2 === "string" && v2.length <= 40 && /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d{1,6})?(?:Z|[+-]\d\d:\d\d)$/.test(v2) && Number.isFinite(Date.parse(v2));
52701
+ var inputFailure = () => {
52702
+ throw new WorkspaceInvitationInputError;
52703
+ };
52704
+ function invitationInput(action, input) {
52705
+ if (!record(input))
52706
+ return inputFailure();
52707
+ const keys = {
52708
+ list: ["after"],
52709
+ get: ["invitationId"],
52710
+ issue: ["email", "role", "idempotencyKey", "confirm"],
52711
+ resend: ["invitationId", "expectedGeneration", "idempotencyKey", "confirm"],
52712
+ revoke: ["invitationId", "expectedGeneration", "confirm"],
52713
+ accept: ["invitationId", "token", "confirm"]
52714
+ };
52715
+ if (Object.keys(input).some((key) => !keys[action].includes(key)) || action !== "list" && keys[action].some((key) => !Object.hasOwn(input, key)))
52716
+ return inputFailure();
52717
+ const value = { ...input };
52718
+ if (action === "list") {
52719
+ if (value.after !== undefined && !uuid(value.after))
52720
+ return inputFailure();
52721
+ }
52722
+ if (["get", "resend", "revoke", "accept"].includes(action) && !uuid(value.invitationId))
52723
+ return inputFailure();
52724
+ if (!["list", "get"].includes(action) && value.confirm !== true)
52725
+ return inputFailure();
52726
+ if (["issue", "resend"].includes(action) && !uuid(value.idempotencyKey))
52727
+ return inputFailure();
52728
+ if (action === "issue") {
52729
+ if (typeof value.email !== "string")
52730
+ return inputFailure();
52731
+ value.email = value.email.trim().toLowerCase();
52732
+ if (!email(value.email) || !role(value.role))
52733
+ return inputFailure();
52734
+ }
52735
+ if (["resend", "revoke"].includes(action) && (!Number.isInteger(value.expectedGeneration) || Number(value.expectedGeneration) < 1 || Number(value.expectedGeneration) > 10))
52736
+ return inputFailure();
52737
+ if (action === "accept" && (typeof value.token !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(value.token)))
52738
+ return inputFailure();
52739
+ return value;
52740
+ }
52741
+ function invitationRequest(action, input) {
52742
+ const base = "/api/v1/workspace/invitations", value = input;
52743
+ const { confirm: _confirm, invitationId: id, ...body } = value;
52744
+ if (action === "list")
52745
+ return { path: base + (value.after ? `?after=${value.after}` : ""), method: "GET" };
52746
+ if (action === "get")
52747
+ return { path: `${base}/${id}`, method: "GET" };
52748
+ if (action === "accept")
52749
+ return { path: "/api/v1/account/invitations/accept", method: "POST", body: JSON.stringify({ invitationId: id, token: value.token }) };
52750
+ return { path: action === "issue" ? base : `${base}/${id}${action === "resend" ? "/resend" : ""}`, method: action === "revoke" ? "DELETE" : "POST", body: JSON.stringify(body) };
52751
+ }
52752
+ function invalid() {
52753
+ throw new RemoteWorkspaceInvitationReadError;
52754
+ }
52755
+ function projection(v2, organizationId) {
52756
+ if (!record(v2) || !uuid(v2.id) || v2.organizationId !== organizationId || !email(v2.email) || v2.email !== v2.email.trim().toLowerCase() || !role(v2.role) || !Number.isInteger(v2.generation) || Number(v2.generation) < 1 || Number(v2.generation) > 10 || typeof v2.status !== "string" || !["pending", "expired", "accepted", "revoked"].includes(v2.status) || !timestamp(v2.expiresAt) || !timestamp(v2.createdAt) || !record(v2.delivery) || typeof v2.delivery.state !== "string" || !["queued", "sending", "uncertain", "provider_accepted", "failed", "cancelled"].includes(v2.delivery.state) || !Number.isInteger(v2.delivery.attempts) || Number(v2.delivery.attempts) < 0 || Number(v2.delivery.attempts) > 5)
52757
+ return invalid();
52758
+ return {
52759
+ id: v2.id,
52760
+ organizationId,
52761
+ email: v2.email,
52762
+ role: v2.role,
52763
+ generation: Number(v2.generation),
52764
+ status: v2.status,
52765
+ expiresAt: v2.expiresAt,
52766
+ createdAt: v2.createdAt,
52767
+ delivery: { state: v2.delivery.state, attempts: Number(v2.delivery.attempts) }
52768
+ };
52769
+ }
52770
+ function parseInvitationResult(action, value, input, organizationId) {
52771
+ if (!record(value))
52772
+ return invalid();
52773
+ const request = input;
52774
+ if (action === "accept") {
52775
+ if (!uuid(value.organizationId) || !uuid(value.membershipId) || value.accepted !== true || typeof value.changed !== "boolean")
52776
+ return invalid();
52777
+ return { organizationId: value.organizationId, membershipId: value.membershipId, accepted: true, changed: value.changed };
52778
+ }
52779
+ if (action === "list") {
52780
+ if (value.organizationId !== organizationId || !Array.isArray(value.invitations) || value.invitations.length > 50 || value.nextCursor !== null && !uuid(value.nextCursor))
52781
+ return invalid();
52782
+ const invitations = value.invitations.map((v2) => projection(v2, organizationId));
52783
+ if (invitations.some((v2, n2) => v2.id <= String(n2 ? invitations[n2 - 1].id : request.after ?? "")) || value.nextCursor !== null && (invitations.length !== 50 || value.nextCursor !== invitations.at(-1)?.id))
52784
+ return invalid();
52785
+ return { organizationId, invitations, nextCursor: value.nextCursor };
52786
+ }
52787
+ const invitation = projection(value.invitation, organizationId);
52788
+ if (action !== "issue" && invitation.id !== request.invitationId)
52789
+ return invalid();
52790
+ if (action === "get")
52791
+ return { invitation };
52792
+ if (typeof value.changed !== "boolean")
52793
+ return invalid();
52794
+ if (action === "issue" && (invitation.email !== request.email || invitation.role !== request.role || value.changed && invitation.generation !== 1))
52795
+ return invalid();
52796
+ if (action === "resend" && (value.changed ? invitation.generation !== Number(request.expectedGeneration) + 1 : invitation.generation <= Number(request.expectedGeneration)))
52797
+ return invalid();
52798
+ if (action === "revoke" && (invitation.status !== "revoked" || invitation.generation < Number(request.expectedGeneration) || value.changed && invitation.generation !== request.expectedGeneration))
52799
+ return invalid();
52800
+ return { invitation, changed: value.changed };
52801
+ }
52802
+
52803
+ // src/lib/remote-workspace-selection.ts
52804
+ var record2 = (v2) => !!v2 && typeof v2 === "object" && !Array.isArray(v2);
52805
+ var uuid2 = (v2) => typeof v2 === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v2);
52806
+ var text = (v2, max = 1024) => typeof v2 === "string" && !!v2.trim() && v2.length <= max && !/[\p{Cc}\p{Cs}\u2028\u2029]/u.test(v2);
52807
+ var role2 = (v2) => typeof v2 === "string" && ["owner", "admin", "member", "viewer"].includes(v2);
52172
52808
  var invalidWorkspaceResult = "The server returned an invalid workspace selection result.";
52173
52809
 
52174
52810
  class WorkspaceContextInputError extends Error {
@@ -52185,48 +52821,48 @@ class WorkspaceIdentityMismatchError extends Error {
52185
52821
  }
52186
52822
  }
52187
52823
  function workspaceExpectedUserId(value) {
52188
- if (!uuid(value))
52824
+ if (!uuid2(value))
52189
52825
  throw new WorkspaceContextInputError;
52190
52826
  return value;
52191
52827
  }
52192
52828
  function workspaceContext(value) {
52193
- if (!record(value) || Object.keys(value).sort().join(",") !== "membershipId,userId" || !uuid(value.userId) || !uuid(value.membershipId))
52829
+ if (!record2(value) || Object.keys(value).sort().join(",") !== "membershipId,userId" || !uuid2(value.userId) || !uuid2(value.membershipId))
52194
52830
  throw new WorkspaceContextInputError;
52195
52831
  return { userId: value.userId, membershipId: value.membershipId };
52196
52832
  }
52197
- function invalid() {
52833
+ function invalid2() {
52198
52834
  throw new Error(invalidWorkspaceResult);
52199
52835
  }
52200
52836
  function organization(v2) {
52201
- if (!record(v2) || !uuid(v2.id) || !text(v2.slug) || !text(v2.name))
52202
- return invalid();
52837
+ if (!record2(v2) || !uuid2(v2.id) || !text(v2.slug) || !text(v2.name))
52838
+ return invalid2();
52203
52839
  return { id: v2.id, slug: v2.slug, name: v2.name };
52204
52840
  }
52205
52841
  function parseAccountWorkspaces(value) {
52206
- if (!record(value) || !Array.isArray(value.workspaces) || !value.workspaces.length || value.workspaces.length > 1000)
52207
- return invalid();
52842
+ if (!record2(value) || !Array.isArray(value.workspaces) || !value.workspaces.length || value.workspaces.length > 1000)
52843
+ return invalid2();
52208
52844
  const workspaces = value.workspaces.map((v2) => {
52209
- if (!record(v2) || !uuid(v2.membershipId) || !role(v2.role) || typeof v2.current !== "boolean")
52210
- return invalid();
52845
+ if (!record2(v2) || !uuid2(v2.membershipId) || !role2(v2.role) || typeof v2.current !== "boolean")
52846
+ return invalid2();
52211
52847
  return { membershipId: v2.membershipId, organization: organization(v2.organization), role: v2.role, current: v2.current };
52212
52848
  });
52213
52849
  if (workspaces.filter((w2) => w2.current).length !== 1 || new Set(workspaces.map((w2) => w2.membershipId)).size !== workspaces.length || new Set(workspaces.map((w2) => w2.organization.id)).size !== workspaces.length)
52214
- return invalid();
52850
+ return invalid2();
52215
52851
  return { workspaces };
52216
52852
  }
52217
52853
  function parseWorkspaceIdentity(value, expectedUserId) {
52218
- if (!record(value))
52219
- return invalid();
52854
+ if (!record2(value))
52855
+ return invalid2();
52220
52856
  const user = value.user;
52221
- if (!record(user) || !uuid(user.id) || !uuid(user.membershipId) || !text(user.email, 320) || !(user.displayName === null || text(user.displayName)) || !role(user.role))
52222
- return invalid();
52857
+ if (!record2(user) || !uuid2(user.id) || !uuid2(user.membershipId) || !text(user.email, 320) || !(user.displayName === null || text(user.displayName)) || !role2(user.role))
52858
+ return invalid2();
52223
52859
  if (user.id !== expectedUserId)
52224
52860
  throw new WorkspaceIdentityMismatchError;
52225
52861
  return { user: { id: user.id, membershipId: user.membershipId, email: user.email, displayName: user.displayName, role: user.role }, organization: organization(value.organization) };
52226
52862
  }
52227
52863
  function sessionToken(value) {
52228
52864
  if (typeof value !== "string" || !value || value.length > 8192 || /[^\x21-\x7e]/.test(value) || value.startsWith("sk_"))
52229
- return invalid();
52865
+ return invalid2();
52230
52866
  return value;
52231
52867
  }
52232
52868
  function parseWorkspaceSession(value, expected) {
@@ -52236,9 +52872,9 @@ function parseWorkspaceSession(value, expected) {
52236
52872
  return { token: sessionToken(value.token), ...identity };
52237
52873
  }
52238
52874
  function parseWorkspaceLogin(value, expectedUserId) {
52239
- const user = record(value) && value.user;
52240
- if (!record(value) || !record(user) || !uuid(user.id))
52241
- return invalid();
52875
+ const user = record2(value) && value.user;
52876
+ if (!record2(value) || !record2(user) || !uuid2(user.id))
52877
+ return invalid2();
52242
52878
  if (expectedUserId !== undefined && user.id !== expectedUserId)
52243
52879
  throw new WorkspaceIdentityMismatchError;
52244
52880
  return { token: sessionToken(value.token), userId: user.id };
@@ -52252,18 +52888,18 @@ var workspaceSelectionFailures = {
52252
52888
  WORKSPACE_BUSY: [503, "Workspace is busy. Refresh before retrying."]
52253
52889
  };
52254
52890
  function workspaceSelectionFailure(value, status) {
52255
- if (!record(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceSelectionFailures, value.code))
52891
+ if (!record2(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceSelectionFailures, value.code))
52256
52892
  return null;
52257
52893
  const code = value.code;
52258
52894
  return workspaceSelectionFailures[code][0] === status ? code : null;
52259
52895
  }
52260
52896
 
52261
52897
  // src/lib/remote-workspace.ts
52262
- var record2 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
52898
+ var record3 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
52263
52899
  var cursor = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,512}$/.test(value);
52264
- var 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);
52900
+ var uuid3 = (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);
52265
52901
  function workspaceMembersQuery(options = {}) {
52266
- if (!record2(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))
52902
+ 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))
52267
52903
  throw new Error("Use a roster limit from 1 to 100 and an unchanged continuation cursor.");
52268
52904
  const query = new URLSearchParams;
52269
52905
  if (options.limit !== undefined)
@@ -52272,14 +52908,14 @@ function workspaceMembersQuery(options = {}) {
52272
52908
  query.set("cursor", options.cursor);
52273
52909
  return query.size ? `?${query}` : "";
52274
52910
  }
52275
- function timestamp(value) {
52911
+ function timestamp2(value) {
52276
52912
  if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$/.test(value))
52277
52913
  return false;
52278
52914
  const time = Date.parse(value);
52279
52915
  return Number.isFinite(time) && new Date(time).toISOString().slice(0, 23) === value.slice(0, 23);
52280
52916
  }
52281
52917
  function parseMember(row, fail) {
52282
- if (!record2(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))
52918
+ if (!record3(row) || !uuid3(row.membershipId) || !uuid3(row.userId) || typeof row.email !== "string" || !row.email || !(row.displayName === null || typeof row.displayName === "string") || !isRole(row.role) || !timestamp2(row.createdAt))
52283
52919
  return fail();
52284
52920
  return {
52285
52921
  membershipId: row.membershipId,
@@ -52299,12 +52935,12 @@ class WorkspaceMemberInputError extends Error {
52299
52935
  }
52300
52936
  }
52301
52937
  function mutationInput(membershipId, input, roleChange) {
52302
- if (typeof membershipId !== "string" || !uuid2(membershipId) || membershipId !== membershipId.toLowerCase() || !record2(input) || Object.keys(input).sort().join(",") !== (roleChange ? "expectedRole,role" : "expectedRole"))
52938
+ if (typeof membershipId !== "string" || !uuid3(membershipId) || membershipId !== membershipId.toLowerCase() || !record3(input) || Object.keys(input).sort().join(",") !== (roleChange ? "expectedRole,role" : "expectedRole"))
52303
52939
  throw new WorkspaceMemberInputError;
52304
- const expectedRole = input.expectedRole, role2 = roleChange ? input.role : undefined;
52305
- if (!isRole(expectedRole) || roleChange && !isRole(role2))
52940
+ const expectedRole = input.expectedRole, role3 = roleChange ? input.role : undefined;
52941
+ if (!isRole(expectedRole) || roleChange && !isRole(role3))
52306
52942
  throw new WorkspaceMemberInputError;
52307
- return { membershipId, role: role2, expectedRole };
52943
+ return { membershipId, role: role3, expectedRole };
52308
52944
  }
52309
52945
  function workspaceMemberRoleInput(membershipId, input) {
52310
52946
  const value = mutationInput(membershipId, input, true);
@@ -52315,19 +52951,19 @@ function workspaceMemberRemovalInput(membershipId, input) {
52315
52951
  return { membershipId: value.membershipId, body: { expectedRole: value.expectedRole } };
52316
52952
  }
52317
52953
  var invalidMemberResult = "The server returned an invalid workspace member result. Refresh the roster before another action.";
52318
- function parseWorkspaceMemberRoleResult(value, membershipId, role2) {
52954
+ function parseWorkspaceMemberRoleResult(value, membershipId, role3) {
52319
52955
  const fail = () => {
52320
52956
  throw new Error(invalidMemberResult);
52321
52957
  };
52322
- if (!record2(value) || !uuid2(value.organizationId) || typeof value.changed !== "boolean")
52958
+ if (!record3(value) || !uuid3(value.organizationId) || typeof value.changed !== "boolean")
52323
52959
  return fail();
52324
52960
  const member = parseMember(value.member, fail);
52325
- if (member.membershipId !== membershipId || member.role !== role2)
52961
+ if (member.membershipId !== membershipId || member.role !== role3)
52326
52962
  return fail();
52327
52963
  return { organizationId: value.organizationId, member, changed: value.changed };
52328
52964
  }
52329
52965
  function parseWorkspaceMemberRemovalResult(value, membershipId) {
52330
- if (!record2(value) || !uuid2(value.organizationId) || value.membershipId !== membershipId || value.removed !== true || typeof value.alreadyRemoved !== "boolean")
52966
+ if (!record3(value) || !uuid3(value.organizationId) || value.membershipId !== membershipId || value.removed !== true || typeof value.alreadyRemoved !== "boolean")
52331
52967
  throw new Error(invalidMemberResult);
52332
52968
  return { organizationId: value.organizationId, membershipId, removed: true, alreadyRemoved: value.alreadyRemoved };
52333
52969
  }
@@ -52344,7 +52980,7 @@ var workspaceMemberFailures = {
52344
52980
  MEMBERSHIP_BUSY: [503, "Membership is busy. Refresh the roster before another action."]
52345
52981
  };
52346
52982
  function workspaceMemberFailure(value, status) {
52347
- if (!record2(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceMemberFailures, value.code))
52983
+ if (!record3(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceMemberFailures, value.code))
52348
52984
  return null;
52349
52985
  const code = value.code;
52350
52986
  return workspaceMemberFailures[code][0] === status ? code : null;
@@ -52353,13 +52989,72 @@ function parseWorkspaceMembersPage(value) {
52353
52989
  const fail = () => {
52354
52990
  throw new Error("The server returned an invalid workspace roster.");
52355
52991
  };
52356
- if (!record2(value) || !uuid2(value.organizationId) || !Array.isArray(value.members) || value.members.length > 100 || !(value.nextCursor === null || cursor(value.nextCursor)) || !value.members.length && value.nextCursor !== null)
52992
+ if (!record3(value) || !uuid3(value.organizationId) || !Array.isArray(value.members) || value.members.length > 100 || !(value.nextCursor === null || cursor(value.nextCursor)) || !value.members.length && value.nextCursor !== null)
52357
52993
  return fail();
52358
52994
  const members = value.members.map((row) => parseMember(row, fail));
52359
52995
  if (new Set(members.map((row) => row.membershipId)).size !== members.length)
52360
52996
  return fail();
52361
52997
  return { organizationId: value.organizationId, members, nextCursor: value.nextCursor };
52362
52998
  }
52999
+
53000
+ // src/lib/remote-workspace-leave.ts
53001
+ class WorkspaceLeaveInputError extends Error {
53002
+ constructor() {
53003
+ super("Confirm leaving the exact observed user and membership with its expected role.");
53004
+ this.name = "WorkspaceLeaveInputError";
53005
+ }
53006
+ }
53007
+ function workspaceLeaveInput(context, input) {
53008
+ const target = workspaceContext(context);
53009
+ if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).sort().join(",") !== "confirm,expectedRole" || input.confirm !== true)
53010
+ throw new WorkspaceLeaveInputError;
53011
+ const captured = workspaceMemberRemovalInput(target.membershipId, { expectedRole: input.expectedRole });
53012
+ return {
53013
+ context: target,
53014
+ input: { expectedRole: captured.body.expectedRole, confirm: true },
53015
+ body: { membershipId: target.membershipId, expectedRole: captured.body.expectedRole }
53016
+ };
53017
+ }
53018
+ var workspaceLeaveFailures = {
53019
+ INVALID_REQUEST: [400, "Provide the exact current membership and expected role."],
53020
+ ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
53021
+ INTERACTIVE_SESSION_REQUIRED: [403, "Fresh interactive sign-in is required to leave a workspace."],
53022
+ MEMBERSHIP_ROLE_CHANGED: [409, "Your role changed. Sign in and inspect the workspace before leaving."],
53023
+ LAST_OWNER_REQUIRED: [409, "The workspace must retain another active owner."],
53024
+ LAST_WORKSPACE_REQUIRED: [409, "Another available workspace is required before leaving."],
53025
+ MEMBERSHIP_BUSY: [503, "Membership is busy. Inspect the workspace before another leave action."]
53026
+ };
53027
+
53028
+ class RemoteWorkspaceLeaveError extends Error {
53029
+ code;
53030
+ status;
53031
+ constructor(code) {
53032
+ super(workspaceLeaveFailures[code][1]);
53033
+ this.code = code;
53034
+ this.name = "RemoteWorkspaceLeaveError";
53035
+ this.status = workspaceLeaveFailures[code][0];
53036
+ }
53037
+ }
53038
+
53039
+ class RemoteWorkspaceLeaveUnconfirmedError extends Error {
53040
+ code = "WORKSPACE_LEAVE_UNCONFIRMED";
53041
+ constructor() {
53042
+ super("The leave outcome is unconfirmed. Sign in again and inspect available memberships before another action. Do not retry automatically; saved credentials are unchanged.");
53043
+ this.name = "RemoteWorkspaceLeaveUnconfirmedError";
53044
+ }
53045
+ }
53046
+ function workspaceLeaveFailure(value, status) {
53047
+ if (!value || typeof value !== "object" || Array.isArray(value))
53048
+ return null;
53049
+ const code = value.code;
53050
+ return typeof code === "string" && Object.hasOwn(workspaceLeaveFailures, code) && workspaceLeaveFailures[code][0] === status ? code : null;
53051
+ }
53052
+ function parseWorkspaceLeaveResult(value, membershipId, organizationId) {
53053
+ const row = value;
53054
+ if (!row || typeof row !== "object" || Array.isArray(row) || row.membershipId !== membershipId || row.organizationId !== organizationId || row.removed !== true || row.signInRequired !== true)
53055
+ throw new RemoteWorkspaceLeaveUnconfirmedError;
53056
+ return { membershipId, organizationId, removed: true, signInRequired: true };
53057
+ }
52363
53058
  // src/lib/auth-store.ts
52364
53059
  function getApiUrl(action, env = process.env, options = {}) {
52365
53060
  return requireSkillsApiOrigin(action, env, options);
@@ -52815,6 +53510,77 @@ class RemoteSkillsClient {
52815
53510
  }
52816
53511
  return value;
52817
53512
  }
53513
+ async leaveWorkspace(context, input) {
53514
+ const captured = workspaceLeaveInput(context, input);
53515
+ const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
53516
+ const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
53517
+ if (!value || typeof value !== "object" || value.authMethod !== "jwt")
53518
+ throw new RemoteWorkspaceLeaveError("INTERACTIVE_SESSION_REQUIRED");
53519
+ const identity = parseWorkspaceIdentity(value, captured.context.userId);
53520
+ if (identity.user.membershipId !== captured.context.membershipId)
53521
+ throw new WorkspaceIdentityMismatchError;
53522
+ let response, body;
53523
+ try {
53524
+ response = await connection.request("/api/v1/account/workspaces/leave", { method: "POST", body: JSON.stringify(captured.body) });
53525
+ body = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, 4096)));
53526
+ } catch {
53527
+ throw new RemoteWorkspaceLeaveUnconfirmedError;
53528
+ }
53529
+ if (!response.ok) {
53530
+ const code = workspaceLeaveFailure(body, response.status);
53531
+ if (code)
53532
+ throw new RemoteWorkspaceLeaveError(code);
53533
+ throw new RemoteWorkspaceLeaveUnconfirmedError;
53534
+ }
53535
+ return parseWorkspaceLeaveResult(body, captured.context.membershipId, identity.organization.id);
53536
+ }
53537
+ listWorkspaceInvitations(context, options = {}) {
53538
+ return this.requestWorkspaceInvitation(context, "list", options);
53539
+ }
53540
+ getWorkspaceInvitation(context, invitationId) {
53541
+ return this.requestWorkspaceInvitation(context, "get", { invitationId });
53542
+ }
53543
+ issueWorkspaceInvitation(context, input) {
53544
+ return this.requestWorkspaceInvitation(context, "issue", input);
53545
+ }
53546
+ resendWorkspaceInvitation(context, invitationId, input) {
53547
+ return this.requestWorkspaceInvitation(context, "resend", { ...input, invitationId });
53548
+ }
53549
+ revokeWorkspaceInvitation(context, invitationId, input) {
53550
+ return this.requestWorkspaceInvitation(context, "revoke", { ...input, invitationId });
53551
+ }
53552
+ acceptWorkspaceInvitation(context, invitationId, input) {
53553
+ return this.requestWorkspaceInvitation(context, "accept", { ...input, invitationId });
53554
+ }
53555
+ async requestWorkspaceInvitation(context, action, input) {
53556
+ const target = workspaceContext(context), captured = invitationInput(action, input);
53557
+ const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
53558
+ const identityValue = await connection.requestWorkspaceSelection("/api/auth/whoami");
53559
+ if (!identityValue || typeof identityValue !== "object" || identityValue.authMethod !== "jwt")
53560
+ throw new RemoteWorkspaceInvitationError("INTERACTIVE_SESSION_REQUIRED");
53561
+ const identity = parseWorkspaceIdentity(identityValue, target.userId);
53562
+ if (identity.user.membershipId !== target.membershipId)
53563
+ throw new WorkspaceIdentityMismatchError;
53564
+ const request = invitationRequest(action, captured), read = action === "list" || action === "get";
53565
+ let response, value;
53566
+ try {
53567
+ response = await connection.request(request.path, { method: request.method, ...request.body ? { body: request.body } : {}, credentials: "omit" });
53568
+ value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, response.ok ? 64 * 1024 : 4096)));
53569
+ } catch {
53570
+ throw read ? new RemoteWorkspaceInvitationReadError : new RemoteWorkspaceInvitationUnconfirmedError;
53571
+ }
53572
+ if (!response.ok) {
53573
+ const code = invitationFailure(value, response.status);
53574
+ if (code)
53575
+ throw new RemoteWorkspaceInvitationError(code);
53576
+ throw read ? new RemoteWorkspaceInvitationReadError : new RemoteWorkspaceInvitationUnconfirmedError;
53577
+ }
53578
+ try {
53579
+ return parseInvitationResult(action, value, captured, identity.organization.id);
53580
+ } catch {
53581
+ throw read ? new RemoteWorkspaceInvitationReadError : new RemoteWorkspaceInvitationUnconfirmedError;
53582
+ }
53583
+ }
52818
53584
  async listApiKeys() {
52819
53585
  return this.arrayResponse("/api/auth/keys");
52820
53586
  }
@@ -53065,13 +53831,13 @@ class RemoteSkillsClient {
53065
53831
  return normalizeUpdatedSincePage(await response.json());
53066
53832
  }
53067
53833
  }
53068
- function requireOptionalString(record3, field) {
53069
- if (record3[field] === undefined)
53834
+ function requireOptionalString(record4, field) {
53835
+ if (record4[field] === undefined)
53070
53836
  return;
53071
- if (typeof record3[field] !== "string") {
53837
+ if (typeof record4[field] !== "string") {
53072
53838
  throw new Error(`Remote payload did not match the expected contract (${field} must be a string when present)`);
53073
53839
  }
53074
- return record3[field];
53840
+ return record4[field];
53075
53841
  }
53076
53842
  var INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
53077
53843
  function isVersionRecord(value) {
@@ -53094,19 +53860,19 @@ function normalizePin(entry) {
53094
53860
  if (!entry || typeof entry !== "object") {
53095
53861
  throw new Error("Remote pin payload did not match the expected contract (expected an object)");
53096
53862
  }
53097
- const record3 = entry;
53098
- const slug = typeof record3.slug === "string" && record3.slug.trim() ? record3.slug.trim() : undefined;
53863
+ const record4 = entry;
53864
+ const slug = typeof record4.slug === "string" && record4.slug.trim() ? record4.slug.trim() : undefined;
53099
53865
  if (!slug) {
53100
53866
  throw new Error("Remote pin payload did not match the expected contract (missing slug)");
53101
53867
  }
53102
53868
  let metadata;
53103
- if (record3.metadata !== undefined) {
53104
- if (!record3.metadata || typeof record3.metadata !== "object" || Array.isArray(record3.metadata)) {
53869
+ if (record4.metadata !== undefined) {
53870
+ if (!record4.metadata || typeof record4.metadata !== "object" || Array.isArray(record4.metadata)) {
53105
53871
  throw new Error("Remote pin payload did not match the expected contract (metadata must be a JSON object when present)");
53106
53872
  }
53107
- metadata = record3.metadata;
53873
+ metadata = record4.metadata;
53108
53874
  }
53109
- const pinnedAt = requireOptionalString(record3, "pinnedAt");
53875
+ const pinnedAt = requireOptionalString(record4, "pinnedAt");
53110
53876
  return {
53111
53877
  slug,
53112
53878
  ...pinnedAt !== undefined ? { pinnedAt } : {},
@@ -53123,16 +53889,16 @@ function normalizeSkillSummary(entry) {
53123
53889
  if (!entry || typeof entry !== "object") {
53124
53890
  throw new Error("Remote skill payload did not match the expected contract (expected an object)");
53125
53891
  }
53126
- const record3 = entry;
53127
- const slug = typeof record3.slug === "string" && record3.slug.trim() ? record3.slug.trim() : undefined;
53892
+ const record4 = entry;
53893
+ const slug = typeof record4.slug === "string" && record4.slug.trim() ? record4.slug.trim() : undefined;
53128
53894
  if (!slug) {
53129
53895
  throw new Error("Remote skill payload did not match the expected contract (missing slug)");
53130
53896
  }
53131
53897
  return {
53132
53898
  slug,
53133
- ...requireOptionalString(record3, "name") !== undefined ? { name: requireOptionalString(record3, "name") } : {},
53134
- ...requireOptionalString(record3, "version") !== undefined ? { version: requireOptionalString(record3, "version") } : {},
53135
- ...requireOptionalString(record3, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record3, "updatedAt") } : {}
53899
+ ...requireOptionalString(record4, "name") !== undefined ? { name: requireOptionalString(record4, "name") } : {},
53900
+ ...requireOptionalString(record4, "version") !== undefined ? { version: requireOptionalString(record4, "version") } : {},
53901
+ ...requireOptionalString(record4, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record4, "updatedAt") } : {}
53136
53902
  };
53137
53903
  }
53138
53904
  function normalizeSkillSummaryList(payload) {
@@ -53185,12 +53951,12 @@ function normalizeUpdatedSincePage(payload) {
53185
53951
  if (!payload || typeof payload !== "object") {
53186
53952
  throw new Error("Updated-since payload did not match the expected contract (expected an object)");
53187
53953
  }
53188
- const record3 = payload;
53189
- if (!Array.isArray(record3.skills)) {
53954
+ const record4 = payload;
53955
+ if (!Array.isArray(record4.skills)) {
53190
53956
  throw new Error("Updated-since payload did not match the expected contract (missing skills array)");
53191
53957
  }
53192
- const skills = record3.skills.map(normalizeSkillSummary);
53193
- const nextCursor = record3.nextCursor === undefined || record3.nextCursor === null ? null : record3.nextCursor;
53958
+ const skills = record4.skills.map(normalizeSkillSummary);
53959
+ const nextCursor = record4.nextCursor === undefined || record4.nextCursor === null ? null : record4.nextCursor;
53194
53960
  if (nextCursor !== null && typeof nextCursor !== "string") {
53195
53961
  throw new Error("Updated-since payload did not match the expected contract (nextCursor must be a string or absent)");
53196
53962
  }
@@ -53200,6 +53966,101 @@ async function createRemoteSkillsClient(env = process.env) {
53200
53966
  const connection = await resolveSkillsConnection(env);
53201
53967
  return connection ? new RemoteSkillsClient(connection.apiKey, connection.apiOrigin) : null;
53202
53968
  }
53969
+ // src/lib/remote-invitation-recovery.ts
53970
+ class InvitationEmailInputError extends Error {
53971
+ code = "INVITATION_EMAIL_INPUT_INVALID";
53972
+ constructor() {
53973
+ super("Use an explicit Skills API URL, exact invitation and retained challenge IDs, secret input, and deliberate confirmation.");
53974
+ this.name = "InvitationEmailInputError";
53975
+ }
53976
+ }
53977
+ var refusals = {
53978
+ INVALID_REQUEST: [400, "Invitation recovery parameters were refused."],
53979
+ ORIGIN_REQUIRED: [403, "Invitation recovery requires the configured site origin."],
53980
+ INVITATION_PROOF_UNAVAILABLE: [401, "Invitation proof is unavailable. Sign in or explicitly request another recovery code."],
53981
+ RATE_LIMITED: [429, "Invitation verification is rate limited. Wait before another deliberate action."],
53982
+ INVITATION_BUSY: [503, "Invitation is busy. Sign in to check membership before another deliberate action."],
53983
+ INVITATION_DELIVERY_UNAVAILABLE: [503, "Invitation recovery is unavailable on this service."]
53984
+ };
53985
+
53986
+ class RemoteInvitationEmailError extends Error {
53987
+ code;
53988
+ status;
53989
+ constructor(code) {
53990
+ super(refusals[code][1]);
53991
+ this.code = code;
53992
+ this.name = "RemoteInvitationEmailError";
53993
+ this.status = refusals[code][0];
53994
+ }
53995
+ }
53996
+
53997
+ class RemoteInvitationEmailUnconfirmedError extends Error {
53998
+ action;
53999
+ code = "INVITATION_EMAIL_UNCONFIRMED";
54000
+ constructor(action) {
54001
+ super(action === "challenge" ? "The recovery challenge outcome is unconfirmed. Retain the same invitation, challenge ID and server. Check your inbox; never rotate the challenge or retry automatically. No delivery is confirmed." : "Invitation acceptance is unconfirmed. Use fresh ordinary sign-in to inspect available memberships. Do not retry acceptance automatically; explicitly request another recovery code only if needed. Saved credentials are unchanged.");
54002
+ this.action = action;
54003
+ this.name = "RemoteInvitationEmailUnconfirmedError";
54004
+ }
54005
+ }
54006
+ var record4 = (v2) => !!v2 && typeof v2 === "object" && !Array.isArray(v2);
54007
+ var uuid4 = (v2) => typeof v2 === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v2);
54008
+ function invitationEmailIds(invitationId, challengeId) {
54009
+ if (!uuid4(invitationId) || !uuid4(challengeId))
54010
+ throw new InvitationEmailInputError;
54011
+ return { invitationId, challengeId };
54012
+ }
54013
+ function invitationEmailInput(action, input) {
54014
+ const keys = ["invitationId", "token", "challengeId", "confirm", ...action === "accept" ? ["code"] : []];
54015
+ if (!record4(input) || Object.keys(input).length !== keys.length || keys.some((key) => !Object.hasOwn(input, key)))
54016
+ throw new InvitationEmailInputError;
54017
+ const value = { ...input };
54018
+ const ids = invitationEmailIds(value.invitationId, value.challengeId);
54019
+ if (value.confirm !== true || typeof value.token !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(value.token) || action === "accept" && (typeof value.code !== "string" || !/^\d{6}$/.test(value.code)))
54020
+ throw new InvitationEmailInputError;
54021
+ return { ...ids, token: value.token, confirm: true, ...action === "accept" ? { code: value.code } : {} };
54022
+ }
54023
+ async function requestInvitationEmail(origin, action, input) {
54024
+ const value = invitationEmailInput(action, input);
54025
+ let target;
54026
+ try {
54027
+ target = normalizeSkillsApiOrigin(origin);
54028
+ } catch {
54029
+ throw new InvitationEmailInputError;
54030
+ }
54031
+ const body = JSON.stringify({ invitationId: value.invitationId, token: value.token, challengeId: value.challengeId, ...action === "accept" ? { code: value.code } : {} });
54032
+ try {
54033
+ const response = await fetch(`${target}/api/v1/account/invitations/email-${action}`, {
54034
+ method: "POST",
54035
+ headers: { "Content-Type": "application/json" },
54036
+ body,
54037
+ credentials: "omit",
54038
+ redirect: "error",
54039
+ cache: "no-store",
54040
+ referrerPolicy: "no-referrer",
54041
+ signal: AbortSignal.timeout(15000)
54042
+ });
54043
+ const parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(await readBoundedResponse(response, 4096)));
54044
+ if (!response.ok && record4(parsed) && typeof parsed.code === "string" && Object.hasOwn(refusals, parsed.code)) {
54045
+ const code = parsed.code;
54046
+ if (response.status === refusals[code][0])
54047
+ throw new RemoteInvitationEmailError(code);
54048
+ }
54049
+ if (!record4(parsed))
54050
+ throw new RemoteInvitationEmailUnconfirmedError(action);
54051
+ if (action === "challenge" && response.status === 202 && parsed.challengeId === value.challengeId && parsed.expiresIn === 600 && typeof parsed.message === "string" && parsed.message.length <= 256) {
54052
+ return { challengeId: value.challengeId, message: "If this invitation is eligible, a verification code will arrive. Delivery is not confirmed.", expiresIn: 600 };
54053
+ }
54054
+ if (action === "accept" && response.status === 200 && uuid4(parsed.organizationId) && uuid4(parsed.membershipId) && parsed.accepted === true && parsed.changed === true && parsed.signInRequired === true) {
54055
+ return { organizationId: parsed.organizationId, membershipId: parsed.membershipId, accepted: true, changed: true, signInRequired: true };
54056
+ }
54057
+ } catch (error) {
54058
+ if (error instanceof RemoteInvitationEmailError)
54059
+ throw error;
54060
+ }
54061
+ throw new RemoteInvitationEmailUnconfirmedError(action);
54062
+ }
54063
+
53203
54064
  // src/lib/remote-auth.ts
53204
54065
  var MAX_ERROR_DETAIL_LENGTH = 200;
53205
54066
 
@@ -53240,10 +54101,10 @@ async function requestAuthApi(instance, path, options) {
53240
54101
  const text2 = await res.text();
53241
54102
  const body = text2 ? parseJsonBody(text2) : {};
53242
54103
  if (!res.ok) {
53243
- const record3 = isRecord6(body) ? body : {};
53244
- const detail = typeof record3.detail === "string" ? record3.detail : undefined;
53245
- const error = typeof record3.error === "string" ? record3.error : undefined;
53246
- const code = typeof record3.code === "string" ? record3.code : undefined;
54104
+ const record5 = isRecord6(body) ? body : {};
54105
+ const detail = typeof record5.detail === "string" ? record5.detail : undefined;
54106
+ const error = typeof record5.error === "string" ? record5.error : undefined;
54107
+ const code = typeof record5.code === "string" ? record5.code : undefined;
53247
54108
  throw new HostedApiError(detail || error || `${res.status} ${res.statusText}`, {
53248
54109
  status: res.status,
53249
54110
  code,
@@ -53277,11 +54138,17 @@ class RemoteSkillsAuthClient {
53277
54138
  constructor(apiUrl) {
53278
54139
  this.apiOrigin = normalizeSkillsApiOrigin(apiUrl);
53279
54140
  }
53280
- requestCode(email) {
53281
- return this.request("/api/auth/login", { method: "POST", body: JSON.stringify({ email }) });
54141
+ requestInvitationEmailChallenge(input) {
54142
+ return requestInvitationEmail(this.apiOrigin, "challenge", input);
54143
+ }
54144
+ acceptInvitationEmailChallenge(input) {
54145
+ return requestInvitationEmail(this.apiOrigin, "accept", input);
54146
+ }
54147
+ requestCode(email2) {
54148
+ return this.request("/api/auth/login", { method: "POST", body: JSON.stringify({ email: email2 }) });
53282
54149
  }
53283
- verifyCode(email, code) {
53284
- return this.request("/api/auth/verify", { method: "POST", body: JSON.stringify({ email, code }) });
54150
+ verifyCode(email2, code) {
54151
+ return this.request("/api/auth/verify", { method: "POST", body: JSON.stringify({ email: email2, code }) });
53285
54152
  }
53286
54153
  startDevice() {
53287
54154
  return this.request("/api/auth/device/start", { method: "POST", body: JSON.stringify({ client: "skills-sdk" }) });
@@ -53289,34 +54156,34 @@ class RemoteSkillsAuthClient {
53289
54156
  pollDevice(deviceCode) {
53290
54157
  return this.request("/api/auth/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) });
53291
54158
  }
53292
- async sessionClient(email, code, context) {
54159
+ async sessionClient(email2, code, context) {
53293
54160
  if (context !== undefined) {
53294
54161
  const target = workspaceContext(context), apiOrigin2 = this.apiOrigin;
53295
- const session = await this.switchWorkspace(email, code, target);
54162
+ const session = await this.switchWorkspace(email2, code, target);
53296
54163
  return new RemoteSkillsClient(session.token, apiOrigin2);
53297
54164
  }
53298
54165
  const apiOrigin = this.apiOrigin;
53299
- if (!email.includes("@") || !/^\d{6}$/.test(code))
54166
+ if (!email2.includes("@") || !/^\d{6}$/.test(code))
53300
54167
  throw new Error("Fresh email and six-digit verification code are required to manage this account");
53301
- const login = await this.verifyCode(email, code);
54168
+ const login = await this.verifyCode(email2, code);
53302
54169
  if (!login || typeof login.token !== "string" || !login.token)
53303
54170
  throw new Error("The server did not return an authorized account session");
53304
54171
  return new RemoteSkillsClient(login.token, apiOrigin);
53305
54172
  }
53306
- async listAccountWorkspaces(email, code, expectedUserId) {
53307
- const login = await this.workspaceLogin(email, code, expectedUserId);
54173
+ async listAccountWorkspaces(email2, code, expectedUserId) {
54174
+ const login = await this.workspaceLogin(email2, code, expectedUserId);
53308
54175
  const result = await new RemoteSkillsClient(login.token, login.apiOrigin).listAccountWorkspaces(login.userId);
53309
54176
  return { userId: login.userId, ...result };
53310
54177
  }
53311
- async switchWorkspace(email, code, context) {
54178
+ async switchWorkspace(email2, code, context) {
53312
54179
  const target = workspaceContext(context);
53313
- const login = await this.workspaceLogin(email, code, target.userId);
54180
+ const login = await this.workspaceLogin(email2, code, target.userId);
53314
54181
  return new RemoteSkillsClient(login.token, login.apiOrigin).switchWorkspace(target);
53315
54182
  }
53316
- async workspaceLogin(email, code, expectedUserId) {
54183
+ async workspaceLogin(email2, code, expectedUserId) {
53317
54184
  const expected = expectedUserId === undefined ? undefined : workspaceExpectedUserId(expectedUserId);
53318
54185
  const apiOrigin = this.apiOrigin;
53319
- if (typeof email !== "string" || !email.includes("@") || typeof code !== "string" || !/^\d{6}$/.test(code))
54186
+ if (typeof email2 !== "string" || !email2.includes("@") || typeof code !== "string" || !/^\d{6}$/.test(code))
53320
54187
  throw new Error("Fresh email and six-digit verification code are required to manage this account");
53321
54188
  let response;
53322
54189
  try {
@@ -53326,7 +54193,7 @@ class RemoteSkillsAuthClient {
53326
54193
  credentials: "omit",
53327
54194
  signal: AbortSignal.timeout(15000),
53328
54195
  headers: { "Content-Type": "application/json" },
53329
- body: JSON.stringify({ email, code })
54196
+ body: JSON.stringify({ email: email2, code })
53330
54197
  });
53331
54198
  } catch {
53332
54199
  throw new HostedApiError("Unable to verify the Skills account.");
@@ -53343,36 +54210,67 @@ class RemoteSkillsAuthClient {
53343
54210
  }
53344
54211
  return { ...parseWorkspaceLogin(value, expected), apiOrigin };
53345
54212
  }
53346
- async createApiKey(email, code, name, scopes, context) {
54213
+ async listWorkspaceInvitations(email2, code, context, options = {}) {
54214
+ const target = workspaceContext(context), captured = invitationInput("list", options);
54215
+ return (await this.sessionClient(email2, code, target)).listWorkspaceInvitations(target, captured);
54216
+ }
54217
+ async getWorkspaceInvitation(email2, code, context, invitationId) {
54218
+ const target = workspaceContext(context), captured = invitationInput("get", { invitationId });
54219
+ return (await this.sessionClient(email2, code, target)).getWorkspaceInvitation(target, captured.invitationId);
54220
+ }
54221
+ async issueWorkspaceInvitation(email2, code, context, input) {
54222
+ const target = workspaceContext(context), captured = invitationInput("issue", input);
54223
+ return (await this.sessionClient(email2, code, target)).issueWorkspaceInvitation(target, captured);
54224
+ }
54225
+ async resendWorkspaceInvitation(email2, code, context, invitationId, input) {
54226
+ const target = workspaceContext(context), captured = invitationInput("resend", { ...input, invitationId });
54227
+ const { invitationId: id, ...options } = captured;
54228
+ return (await this.sessionClient(email2, code, target)).resendWorkspaceInvitation(target, id, options);
54229
+ }
54230
+ async revokeWorkspaceInvitation(email2, code, context, invitationId, input) {
54231
+ const target = workspaceContext(context), captured = invitationInput("revoke", { ...input, invitationId });
54232
+ const { invitationId: id, ...options } = captured;
54233
+ return (await this.sessionClient(email2, code, target)).revokeWorkspaceInvitation(target, id, options);
54234
+ }
54235
+ async acceptWorkspaceInvitation(email2, code, context, invitationId, input) {
54236
+ const target = workspaceContext(context), captured = invitationInput("accept", { ...input, invitationId });
54237
+ const { invitationId: id, ...options } = captured;
54238
+ return (await this.sessionClient(email2, code, target)).acceptWorkspaceInvitation(target, id, options);
54239
+ }
54240
+ async createApiKey(email2, code, name, scopes, context) {
53347
54241
  const capturedScopes = scopes === undefined ? undefined : [...scopes];
53348
- return (await this.sessionClient(email, code, context)).createApiKey(name, capturedScopes);
54242
+ return (await this.sessionClient(email2, code, context)).createApiKey(name, capturedScopes);
53349
54243
  }
53350
- async listApiKeys(email, code, context) {
53351
- return (await this.sessionClient(email, code, context)).listApiKeys();
54244
+ async listApiKeys(email2, code, context) {
54245
+ return (await this.sessionClient(email2, code, context)).listApiKeys();
53352
54246
  }
53353
- async revokeApiKey(email, code, keyId, context) {
53354
- return (await this.sessionClient(email, code, context)).revokeApiKey(keyId);
54247
+ async revokeApiKey(email2, code, keyId, context) {
54248
+ return (await this.sessionClient(email2, code, context)).revokeApiKey(keyId);
53355
54249
  }
53356
- async updateProfile(email, code, input, context) {
54250
+ async updateProfile(email2, code, input, context) {
53357
54251
  const body = customerNamePatch(input, "displayName");
53358
- return (await this.sessionClient(email, code, context)).updateProfile({ displayName: body.displayName });
54252
+ return (await this.sessionClient(email2, code, context)).updateProfile({ displayName: body.displayName });
53359
54253
  }
53360
- async updateCurrentWorkspace(email, code, input, context) {
54254
+ async updateCurrentWorkspace(email2, code, input, context) {
53361
54255
  const body = customerNamePatch(input, "name");
53362
- return (await this.sessionClient(email, code, context)).updateCurrentWorkspace({ name: body.name });
54256
+ return (await this.sessionClient(email2, code, context)).updateCurrentWorkspace({ name: body.name });
53363
54257
  }
53364
- async listWorkspaceMembers(email, code, options = {}, context) {
54258
+ async listWorkspaceMembers(email2, code, options = {}, context) {
53365
54259
  workspaceMembersQuery(options);
53366
54260
  const captured = { ...options };
53367
- return (await this.sessionClient(email, code, context)).listWorkspaceMembers(captured);
54261
+ return (await this.sessionClient(email2, code, context)).listWorkspaceMembers(captured);
53368
54262
  }
53369
- async setWorkspaceMemberRole(email, code, membershipId, input, context) {
54263
+ async setWorkspaceMemberRole(email2, code, membershipId, input, context) {
53370
54264
  const captured = workspaceMemberRoleInput(membershipId, input);
53371
- return (await this.sessionClient(email, code, context)).setWorkspaceMemberRole(captured.membershipId, captured.body);
54265
+ return (await this.sessionClient(email2, code, context)).setWorkspaceMemberRole(captured.membershipId, captured.body);
54266
+ }
54267
+ async leaveWorkspace(email2, code, context, input) {
54268
+ const captured = workspaceLeaveInput(context, input);
54269
+ return (await this.sessionClient(email2, code, captured.context)).leaveWorkspace(captured.context, captured.input);
53372
54270
  }
53373
- async removeWorkspaceMember(email, code, membershipId, input, context) {
54271
+ async removeWorkspaceMember(email2, code, membershipId, input, context) {
53374
54272
  const captured = workspaceMemberRemovalInput(membershipId, input);
53375
- return (await this.sessionClient(email, code, context)).removeWorkspaceMember(captured.membershipId, captured.body);
54273
+ return (await this.sessionClient(email2, code, context)).removeWorkspaceMember(captured.membershipId, captured.body);
53376
54274
  }
53377
54275
  request(path, options) {
53378
54276
  if (!["/api/auth/login", "/api/auth/verify", "/api/auth/device/start", "/api/auth/device/token", "/api/auth/keys", "/api/auth/whoami"].includes(path))
@@ -53381,6 +54279,7 @@ class RemoteSkillsAuthClient {
53381
54279
  }
53382
54280
  }
53383
54281
  export {
54282
+ verifyContentHashFromEntries,
53384
54283
  validateRunLifecycleEvent,
53385
54284
  startedByFor,
53386
54285
  startSkillsServer,
@@ -53395,6 +54294,7 @@ export {
53395
54294
  runPointersOf,
53396
54295
  runLeaseSchema,
53397
54296
  runAdmissionSchema,
54297
+ revisionIdOf,
53398
54298
  resolveSkillsFleet,
53399
54299
  resolveSkillsApiOrigin,
53400
54300
  resolveServerConfig,
@@ -53408,6 +54308,7 @@ export {
53408
54308
  redactRunOutput,
53409
54309
  receiptId,
53410
54310
  protocolStateOf,
54311
+ packSkillBundle,
53411
54312
  noticeLocalSkillsMode,
53412
54313
  normalizeSkillsApiOrigin,
53413
54314
  normalizeRemoteSkillRunContract,
@@ -53419,6 +54320,7 @@ export {
53419
54320
  isTerminalStatus,
53420
54321
  isSkillsLocalOptIn,
53421
54322
  isActiveStatus,
54323
+ inspectSkillBundle,
53422
54324
  getServerSkillMd,
53423
54325
  getServerSkill,
53424
54326
  expiresAtFor,
@@ -53444,6 +54346,7 @@ export {
53444
54346
  createCancelService,
53445
54347
  createAwsEcsClient,
53446
54348
  configuredSkillsApiUrl,
54349
+ computeContentHashFromEntries,
53447
54350
  clientTokenFor,
53448
54351
  canonicalSystemDepsKey,
53449
54352
  canonicalJson,
@@ -53453,22 +54356,33 @@ export {
53453
54356
  assertDurableTarget,
53454
54357
  assertDurableStore,
53455
54358
  artifactStorageSeam,
54359
+ WorkspaceLeaveInputError,
54360
+ WorkspaceInvitationInputError,
53456
54361
  WorkspaceIdentityMismatchError,
53457
54362
  WorkspaceContextInputError,
53458
54363
  SqliteSkillsStore,
53459
54364
  SqliteRunExecutionStore,
53460
54365
  SqliteGovernanceStore,
53461
54366
  SkillsFleetCredentialError,
54367
+ SkillBundleInspectionError,
54368
+ SKILL_BUNDLE_INSPECTION_LIMITS,
53462
54369
  SKILLS_LOCAL_OPT_IN_ENV_KEYS,
53463
54370
  SKILLS_APP,
53464
54371
  SKILLS_API_URL_ENV,
53465
54372
  SKILLS_API_KEY_ENV,
53466
54373
  RemoteWorkspaceSelectionError,
53467
54374
  RemoteWorkspaceMemberError,
54375
+ RemoteWorkspaceLeaveUnconfirmedError,
54376
+ RemoteWorkspaceLeaveError,
54377
+ RemoteWorkspaceInvitationUnconfirmedError,
54378
+ RemoteWorkspaceInvitationReadError,
54379
+ RemoteWorkspaceInvitationError,
53468
54380
  RemoteSkillsClient,
53469
54381
  RemoteSkillsAuthClient,
53470
54382
  RemoteRouteUnsupportedError,
53471
54383
  RemoteRequestError,
54384
+ RemoteInvitationEmailUnconfirmedError,
54385
+ RemoteInvitationEmailError,
53472
54386
  RemoteCreditApprovalError,
53473
54387
  RemoteCapabilityUnavailableError,
53474
54388
  RUN_PROTOCOL_VERSION,
@@ -53483,6 +54397,7 @@ export {
53483
54397
  MemoryRunExecutionStore,
53484
54398
  MemoryGovernanceStore,
53485
54399
  LEGAL_TRANSITIONS,
54400
+ InvitationEmailInputError,
53486
54401
  ImageProfileResolutionError,
53487
54402
  HostedApiError,
53488
54403
  GovernanceError,
@@ -53498,6 +54413,8 @@ export {
53498
54413
  DEFAULT_RUN_LIMITS,
53499
54414
  DEFAULT_OUTPUT_GOVERNANCE,
53500
54415
  DEFAULT_IMAGE_PROFILES,
54416
+ ContentHashInputError,
54417
+ CONTENT_HASH_LIMITS,
53501
54418
  CLIENT_TOKEN_BYTES,
53502
54419
  ArtifactStorage
53503
54420
  };