@hasna/skills 0.5.2 → 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/README.md +169 -1
- package/bin/index.js +1182 -623
- package/bin/mcp.js +28155 -26920
- package/bin/migrate.js +172 -1
- package/bin/server.js +187 -1
- package/bin/worker.js +225 -40
- package/dist/cli/commands/invitation-recovery.d.ts +2 -0
- package/dist/cli/commands/invitation-verification.d.ts +8 -0
- package/dist/cli/commands/workspace-invitations.d.ts +2 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +987 -165
- package/dist/lib/invitation-customer-action.d.ts +14 -0
- package/dist/lib/invitation-recovery-target.d.ts +5 -0
- package/dist/lib/remote-auth.d.ts +12 -0
- package/dist/lib/remote-client.d.ts +12 -0
- package/dist/lib/remote-invitation-recovery.d.ts +62 -0
- package/dist/lib/remote-invitations.d.ts +128 -0
- package/dist/lib/skill-bundle.d.ts +54 -1
- package/dist/lib/skill-entry-path.d.ts +6 -0
- package/dist/lib/skill-hash.d.ts +33 -0
- package/dist/mcp/index.d.ts +0 -1
- package/dist/mcp/invitation-recovery.d.ts +2 -0
- package/dist/mcp/remote-invitation-tools.d.ts +2 -0
- package/dist/sdk/index.d.ts +3 -0
- package/dist/sdk/index.js +932 -105
- package/dist/sdk/registry.d.ts +6 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1463,8 +1463,45 @@ function validateRegistryConsistency(registry, skillsDir) {
|
|
|
1463
1463
|
import { createHash } from "crypto";
|
|
1464
1464
|
import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
|
|
1465
1465
|
import { join as join5, sep } from "path";
|
|
1466
|
+
|
|
1467
|
+
// src/lib/skill-entry-path.ts
|
|
1468
|
+
class SkillEntryPaths {
|
|
1469
|
+
files = new Set;
|
|
1470
|
+
directories = new Set;
|
|
1471
|
+
add(path, maxBytes, invalid, limit) {
|
|
1472
|
+
if (path.length > maxBytes)
|
|
1473
|
+
limit();
|
|
1474
|
+
const encoded = new TextEncoder().encode(path);
|
|
1475
|
+
if (encoded.byteLength > maxBytes)
|
|
1476
|
+
limit();
|
|
1477
|
+
if (new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(encoded) !== path)
|
|
1478
|
+
invalid("Invalid UTF-8 entry path");
|
|
1479
|
+
if (!path || /[\\:\x00-\x1f\x7f]/u.test(path))
|
|
1480
|
+
invalid("Unsafe entry path");
|
|
1481
|
+
if (path.split("/").some((segment) => !segment || segment === "." || segment === ".."))
|
|
1482
|
+
invalid("Unsafe entry path segment");
|
|
1483
|
+
const key = path.normalize("NFC").toLowerCase().normalize("NFC");
|
|
1484
|
+
if (this.files.has(key) || this.directories.has(key))
|
|
1485
|
+
invalid("Duplicate or conflicting entry path");
|
|
1486
|
+
const parents = key.split("/");
|
|
1487
|
+
parents.pop();
|
|
1488
|
+
while (parents.length) {
|
|
1489
|
+
const parent = parents.join("/");
|
|
1490
|
+
if (this.files.has(parent))
|
|
1491
|
+
invalid("Conflicting entry file ancestor");
|
|
1492
|
+
this.directories.add(parent);
|
|
1493
|
+
parents.pop();
|
|
1494
|
+
}
|
|
1495
|
+
this.files.add(key);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// src/lib/skill-hash.ts
|
|
1466
1500
|
var CONTENT_HASH_ALGORITHM = "sha256";
|
|
1467
1501
|
var HASH_EXCLUDE_DIRS = new Set([".git", "node_modules", "dist", "build", ".turbo"]);
|
|
1502
|
+
function excludedHashEntry(name, directory) {
|
|
1503
|
+
return name.startsWith(".") || directory && HASH_EXCLUDE_DIRS.has(name);
|
|
1504
|
+
}
|
|
1468
1505
|
var HASH_COVERAGE = [
|
|
1469
1506
|
"SKILL.md",
|
|
1470
1507
|
"skill.json",
|
|
@@ -1546,7 +1583,7 @@ function collectDirectory(files, dir, rel) {
|
|
|
1546
1583
|
if (stats.isSymbolicLink())
|
|
1547
1584
|
continue;
|
|
1548
1585
|
if (stats.isDirectory()) {
|
|
1549
|
-
if (
|
|
1586
|
+
if (excludedHashEntry(entry, true))
|
|
1550
1587
|
continue;
|
|
1551
1588
|
collectDirectory(files, absolute, childRel);
|
|
1552
1589
|
} else if (stats.isFile()) {
|
|
@@ -1556,28 +1593,242 @@ function collectDirectory(files, dir, rel) {
|
|
|
1556
1593
|
}
|
|
1557
1594
|
function collectFile(files, absolute, rel) {
|
|
1558
1595
|
const buffer = readFileSync4(absolute);
|
|
1596
|
+
files.push(normalizeBundleFile(rel.split(sep).join("/"), buffer));
|
|
1597
|
+
}
|
|
1598
|
+
function normalizeBundleFile(rel, buffer) {
|
|
1559
1599
|
if (rel === "skill.json") {
|
|
1560
|
-
|
|
1561
|
-
return;
|
|
1600
|
+
return { rel, content: new TextEncoder().encode(canonicalizeManifest(new TextDecoder().decode(buffer))) };
|
|
1562
1601
|
}
|
|
1563
1602
|
if (looksLikeText(buffer)) {
|
|
1564
1603
|
const normalized = normalizeLineEndings(new TextDecoder().decode(buffer));
|
|
1565
|
-
|
|
1566
|
-
return;
|
|
1604
|
+
return { rel, content: new TextEncoder().encode(normalized) };
|
|
1567
1605
|
}
|
|
1568
|
-
|
|
1606
|
+
return { rel, content: buffer };
|
|
1569
1607
|
}
|
|
1570
1608
|
function computeContentHash(skillPath) {
|
|
1609
|
+
return hashBundleFiles(collectBundleFiles(skillPath));
|
|
1610
|
+
}
|
|
1611
|
+
function* bundleHashParts(files) {
|
|
1612
|
+
for (const file of files) {
|
|
1613
|
+
yield new TextEncoder().encode(file.rel);
|
|
1614
|
+
yield new TextEncoder().encode(`\x00${file.content.length}\x00`);
|
|
1615
|
+
yield file.content;
|
|
1616
|
+
yield new TextEncoder().encode("\x00");
|
|
1617
|
+
}
|
|
1618
|
+
yield new TextEncoder().encode("\x00");
|
|
1619
|
+
}
|
|
1620
|
+
function hashBundleFiles(files) {
|
|
1571
1621
|
const hash = createHash(CONTENT_HASH_ALGORITHM);
|
|
1572
|
-
for (const
|
|
1573
|
-
hash.update(
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1622
|
+
for (const part of bundleHashParts(files))
|
|
1623
|
+
hash.update(part);
|
|
1624
|
+
return hash.digest("hex");
|
|
1625
|
+
}
|
|
1626
|
+
async function hashBundleFilesCooperatively(files, check) {
|
|
1627
|
+
const hash = createHash(CONTENT_HASH_ALGORITHM);
|
|
1628
|
+
let bytesSinceYield = 0;
|
|
1629
|
+
for (const part of bundleHashParts(files)) {
|
|
1630
|
+
for (let offset = 0;offset < part.byteLength; offset += 64 * 1024) {
|
|
1631
|
+
check();
|
|
1632
|
+
const chunk = part.subarray(offset, offset + 64 * 1024);
|
|
1633
|
+
hash.update(chunk);
|
|
1634
|
+
bytesSinceYield += chunk.byteLength;
|
|
1635
|
+
if (bytesSinceYield >= 256 * 1024) {
|
|
1636
|
+
await new Promise((resolve2) => setImmediate(resolve2));
|
|
1637
|
+
bytesSinceYield = 0;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1577
1640
|
}
|
|
1578
|
-
|
|
1641
|
+
check();
|
|
1579
1642
|
return hash.digest("hex");
|
|
1580
1643
|
}
|
|
1644
|
+
var CONTENT_HASH_LIMITS = Object.freeze({
|
|
1645
|
+
entries: 1024,
|
|
1646
|
+
rawBytes: 64 * 1024 * 1024,
|
|
1647
|
+
normalizedBytes: 64 * 1024 * 1024,
|
|
1648
|
+
fileBytes: 16 * 1024 * 1024,
|
|
1649
|
+
normalizedFileBytes: 16 * 1024 * 1024,
|
|
1650
|
+
pathBytes: 100,
|
|
1651
|
+
manifestBytes: 16 * 1024,
|
|
1652
|
+
manifestDepth: 64,
|
|
1653
|
+
timeoutMs: 5000
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1656
|
+
class ContentHashInputError extends Error {
|
|
1657
|
+
code;
|
|
1658
|
+
constructor(code, message) {
|
|
1659
|
+
super(message);
|
|
1660
|
+
this.code = code;
|
|
1661
|
+
this.name = "ContentHashInputError";
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
function invalidContent(message = "Invalid content hash input") {
|
|
1665
|
+
throw new ContentHashInputError("CONTENT_HASH_INVALID", message);
|
|
1666
|
+
}
|
|
1667
|
+
function contentLimit(message) {
|
|
1668
|
+
throw new ContentHashInputError("CONTENT_HASH_LIMIT", message);
|
|
1669
|
+
}
|
|
1670
|
+
function contentRecord(value, allowed) {
|
|
1671
|
+
if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
|
|
1672
|
+
invalidContent();
|
|
1673
|
+
const result = Object.create(null);
|
|
1674
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
1675
|
+
if (typeof key !== "string" || !allowed.includes(key))
|
|
1676
|
+
invalidContent();
|
|
1677
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
1678
|
+
if (!descriptor || !("value" in descriptor))
|
|
1679
|
+
invalidContent("Accessor content hash input is unsupported");
|
|
1680
|
+
result[key] = descriptor.value;
|
|
1681
|
+
}
|
|
1682
|
+
return result;
|
|
1683
|
+
}
|
|
1684
|
+
function contentOptions(options) {
|
|
1685
|
+
const record = contentRecord(options, ["limits", "signal"]);
|
|
1686
|
+
const limits = { ...CONTENT_HASH_LIMITS };
|
|
1687
|
+
if (record.limits !== undefined) {
|
|
1688
|
+
const supplied = contentRecord(record.limits, Object.keys(limits));
|
|
1689
|
+
for (const key of Object.keys(supplied)) {
|
|
1690
|
+
const value = supplied[key];
|
|
1691
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0 || value > limits[key])
|
|
1692
|
+
contentLimit("Invalid content hash limit");
|
|
1693
|
+
limits[key] = value;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
if (record.signal !== undefined && !(record.signal instanceof AbortSignal))
|
|
1697
|
+
invalidContent("Invalid content hash signal");
|
|
1698
|
+
return { limits, signal: record.signal };
|
|
1699
|
+
}
|
|
1700
|
+
var typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
|
|
1701
|
+
var byteLengthOf = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteLength").get;
|
|
1702
|
+
var bufferOf = Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer").get;
|
|
1703
|
+
function snapshotContentEntries(entries, limits, check) {
|
|
1704
|
+
if (!Array.isArray(entries))
|
|
1705
|
+
invalidContent("Content hash entries must be an array");
|
|
1706
|
+
if (entries.length > limits.entries)
|
|
1707
|
+
contentLimit("Content hash entry limit exceeded");
|
|
1708
|
+
if (Reflect.ownKeys(entries).length !== entries.length + 1)
|
|
1709
|
+
invalidContent("Invalid content hash entry array");
|
|
1710
|
+
const snapshot = [];
|
|
1711
|
+
const paths = new SkillEntryPaths;
|
|
1712
|
+
let rawBytes = 0;
|
|
1713
|
+
for (let index = 0;index < entries.length; index++) {
|
|
1714
|
+
check();
|
|
1715
|
+
const descriptor = Object.getOwnPropertyDescriptor(entries, String(index));
|
|
1716
|
+
if (!descriptor || !("value" in descriptor))
|
|
1717
|
+
invalidContent("Invalid content hash entry array");
|
|
1718
|
+
const entry = contentRecord(descriptor.value, ["path", "bytes", "mode"]);
|
|
1719
|
+
if (typeof entry.path !== "string" || typeof entry.mode !== "number" || !Number.isInteger(entry.mode) || entry.mode < 0 || entry.mode > 511)
|
|
1720
|
+
invalidContent("Invalid regular-file content hash entry");
|
|
1721
|
+
paths.add(entry.path, limits.pathBytes, invalidContent, () => contentLimit("Content hash path limit exceeded"));
|
|
1722
|
+
if (!(entry.bytes instanceof Uint8Array) || !ArrayBuffer.isView(entry.bytes))
|
|
1723
|
+
invalidContent("Content hash entry requires bytes");
|
|
1724
|
+
const size = byteLengthOf.call(entry.bytes);
|
|
1725
|
+
if (!(bufferOf.call(entry.bytes) instanceof ArrayBuffer))
|
|
1726
|
+
invalidContent("Shared content hash bytes are unsupported");
|
|
1727
|
+
if (size > limits.fileBytes || rawBytes + size > limits.rawBytes)
|
|
1728
|
+
contentLimit("Content hash raw byte limit exceeded");
|
|
1729
|
+
if (entry.path === "skill.json" && size > limits.manifestBytes)
|
|
1730
|
+
contentLimit("Content hash manifest byte limit exceeded");
|
|
1731
|
+
rawBytes += size;
|
|
1732
|
+
const bytes = new Uint8Array(new ArrayBuffer(size));
|
|
1733
|
+
bytes.set(entry.bytes);
|
|
1734
|
+
snapshot.push({ path: entry.path, bytes, mode: entry.mode });
|
|
1735
|
+
}
|
|
1736
|
+
check();
|
|
1737
|
+
return snapshot;
|
|
1738
|
+
}
|
|
1739
|
+
function coveredContentPath(path) {
|
|
1740
|
+
const segments = path.split("/");
|
|
1741
|
+
if (!HASH_COVERAGE.includes(segments[0]))
|
|
1742
|
+
return false;
|
|
1743
|
+
return !segments.slice(1).some((segment, index) => excludedHashEntry(segment, index < segments.length - 2));
|
|
1744
|
+
}
|
|
1745
|
+
function boundedManifest(raw, maxDepth) {
|
|
1746
|
+
let parsed;
|
|
1747
|
+
try {
|
|
1748
|
+
parsed = JSON.parse(raw);
|
|
1749
|
+
} catch {
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1752
|
+
const pending = [{ value: parsed, depth: 1 }];
|
|
1753
|
+
while (pending.length) {
|
|
1754
|
+
const { value, depth } = pending.pop();
|
|
1755
|
+
if (!value || typeof value !== "object")
|
|
1756
|
+
continue;
|
|
1757
|
+
if (depth > maxDepth)
|
|
1758
|
+
contentLimit("Content hash manifest depth limit exceeded");
|
|
1759
|
+
for (const child of Object.values(value))
|
|
1760
|
+
pending.push({ value: child, depth: depth + 1 });
|
|
1761
|
+
}
|
|
1762
|
+
return parsed;
|
|
1763
|
+
}
|
|
1764
|
+
async function hashContentEntries(entries, options) {
|
|
1765
|
+
const { limits, signal } = contentOptions(options);
|
|
1766
|
+
const deadline = performance.now() + limits.timeoutMs;
|
|
1767
|
+
let terminal;
|
|
1768
|
+
const abort = () => {
|
|
1769
|
+
terminal ??= new ContentHashInputError("CONTENT_HASH_ABORTED", "Content hashing aborted");
|
|
1770
|
+
};
|
|
1771
|
+
const timer = setTimeout(() => {
|
|
1772
|
+
terminal ??= new ContentHashInputError("CONTENT_HASH_TIMEOUT", "Content hashing deadline exceeded");
|
|
1773
|
+
}, limits.timeoutMs);
|
|
1774
|
+
const check = () => {
|
|
1775
|
+
if (signal?.aborted)
|
|
1776
|
+
abort();
|
|
1777
|
+
if (terminal)
|
|
1778
|
+
throw terminal;
|
|
1779
|
+
if (performance.now() >= deadline)
|
|
1780
|
+
throw new ContentHashInputError("CONTENT_HASH_TIMEOUT", "Content hashing deadline exceeded");
|
|
1781
|
+
};
|
|
1782
|
+
try {
|
|
1783
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
1784
|
+
check();
|
|
1785
|
+
const snapshot = snapshotContentEntries(entries, limits, check);
|
|
1786
|
+
const normalized = [];
|
|
1787
|
+
let normalizedBytes = 0;
|
|
1788
|
+
let manifest;
|
|
1789
|
+
await new Promise((resolve2) => setImmediate(resolve2));
|
|
1790
|
+
for (const entry of snapshot) {
|
|
1791
|
+
check();
|
|
1792
|
+
if (!coveredContentPath(entry.path))
|
|
1793
|
+
continue;
|
|
1794
|
+
if (entry.path === "skill.json")
|
|
1795
|
+
manifest = boundedManifest(new TextDecoder().decode(entry.bytes), limits.manifestDepth);
|
|
1796
|
+
const file = normalizeBundleFile(entry.path, entry.bytes);
|
|
1797
|
+
check();
|
|
1798
|
+
if (file.content.byteLength > limits.normalizedFileBytes || normalizedBytes + file.content.byteLength > limits.normalizedBytes)
|
|
1799
|
+
contentLimit("Content hash normalized byte limit exceeded");
|
|
1800
|
+
normalizedBytes += file.content.byteLength;
|
|
1801
|
+
normalized.push(file);
|
|
1802
|
+
await new Promise((resolve2) => setImmediate(resolve2));
|
|
1803
|
+
}
|
|
1804
|
+
normalized.sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
|
|
1805
|
+
check();
|
|
1806
|
+
return { hash: await hashBundleFilesCooperatively(normalized, check), manifest };
|
|
1807
|
+
} catch (error) {
|
|
1808
|
+
if (error instanceof ContentHashInputError)
|
|
1809
|
+
throw error;
|
|
1810
|
+
throw new ContentHashInputError("CONTENT_HASH_INVALID", "Invalid content hash input");
|
|
1811
|
+
} finally {
|
|
1812
|
+
clearTimeout(timer);
|
|
1813
|
+
signal?.removeEventListener("abort", abort);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
async function computeContentHashFromEntries(entries, options = {}) {
|
|
1817
|
+
return (await hashContentEntries(entries, options)).hash;
|
|
1818
|
+
}
|
|
1819
|
+
async function verifyContentHashFromEntries(entries, options = {}) {
|
|
1820
|
+
const { hash, manifest } = await hashContentEntries(entries, options);
|
|
1821
|
+
const provenance = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? manifest.provenance : undefined;
|
|
1822
|
+
const value = provenance && typeof provenance === "object" && !Array.isArray(provenance) ? provenance.content_hash : undefined;
|
|
1823
|
+
if (value !== undefined && typeof value !== "string")
|
|
1824
|
+
invalidContent("Invalid content hash declaration");
|
|
1825
|
+
const declaredHash = value?.trim() || undefined;
|
|
1826
|
+
if (!declaredHash)
|
|
1827
|
+
return { declared: false, valid: false };
|
|
1828
|
+
if (!/^[a-f0-9]{64}$/.test(declaredHash))
|
|
1829
|
+
return { declared: true, valid: false, declaredHash };
|
|
1830
|
+
return { declared: true, valid: hash === declaredHash, declaredHash, computedHash: hash };
|
|
1831
|
+
}
|
|
1581
1832
|
function verifyContentHash(skillPath, manifest) {
|
|
1582
1833
|
const declaredHash = manifest?.provenance?.content_hash?.trim() || undefined;
|
|
1583
1834
|
if (!declaredHash)
|
|
@@ -10254,11 +10505,174 @@ function primitiveHaystack(primitive) {
|
|
|
10254
10505
|
function clone(value) {
|
|
10255
10506
|
return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
|
|
10256
10507
|
}
|
|
10257
|
-
// src/lib/remote-
|
|
10508
|
+
// src/lib/remote-invitations.ts
|
|
10509
|
+
class WorkspaceInvitationInputError extends Error {
|
|
10510
|
+
code = "INVITATION_INPUT_INVALID";
|
|
10511
|
+
constructor() {
|
|
10512
|
+
super("Provide only the documented invitation fields, exact lowercase IDs, expected generation and explicit confirmation. Issue and resend require your stable idempotency key.");
|
|
10513
|
+
this.name = "WorkspaceInvitationInputError";
|
|
10514
|
+
}
|
|
10515
|
+
}
|
|
10516
|
+
var invitationFailures = {
|
|
10517
|
+
INVALID_REQUEST: [400, "Invitation parameters were refused."],
|
|
10518
|
+
ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
|
|
10519
|
+
INTERACTIVE_SESSION_REQUIRED: [403, "Fresh interactive sign-in is required."],
|
|
10520
|
+
WORKSPACE_ADMIN_REQUIRED: [403, "A current workspace owner or admin is required."],
|
|
10521
|
+
INVITATION_FORBIDDEN: [403, "Your current role cannot manage this invitation."],
|
|
10522
|
+
INVITATION_UNAVAILABLE: [404, "Invitation is unavailable for this account."],
|
|
10523
|
+
INVITATION_CHANGED: [409, "Invitation changed. Read its current generation before another action."],
|
|
10524
|
+
INVITATION_EXISTS: [409, "A pending invitation already exists. Read current invitations."],
|
|
10525
|
+
ALREADY_MEMBER: [409, "An active membership already exists. An invitation cannot change its role."],
|
|
10526
|
+
IDEMPOTENCY_CONFLICT: [409, "This request key was used for different invitation parameters. Reconcile the original request."],
|
|
10527
|
+
INVITATION_LIMIT: [429, "Invitation limit reached. Wait before issuing or resending."],
|
|
10528
|
+
INVITATION_BUSY: [503, "Invitation is busy. Read its state before another action."],
|
|
10529
|
+
INVITATION_DELIVERY_UNAVAILABLE: [503, "Invitation email delivery is unavailable."]
|
|
10530
|
+
};
|
|
10531
|
+
|
|
10532
|
+
class RemoteWorkspaceInvitationError extends Error {
|
|
10533
|
+
code;
|
|
10534
|
+
status;
|
|
10535
|
+
constructor(code) {
|
|
10536
|
+
super(invitationFailures[code][1]);
|
|
10537
|
+
this.code = code;
|
|
10538
|
+
this.name = "RemoteWorkspaceInvitationError";
|
|
10539
|
+
this.status = invitationFailures[code][0];
|
|
10540
|
+
}
|
|
10541
|
+
}
|
|
10542
|
+
|
|
10543
|
+
class RemoteWorkspaceInvitationUnconfirmedError extends Error {
|
|
10544
|
+
code = "INVITATION_UNCONFIRMED";
|
|
10545
|
+
constructor() {
|
|
10546
|
+
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.");
|
|
10547
|
+
this.name = "RemoteWorkspaceInvitationUnconfirmedError";
|
|
10548
|
+
}
|
|
10549
|
+
}
|
|
10550
|
+
|
|
10551
|
+
class RemoteWorkspaceInvitationReadError extends Error {
|
|
10552
|
+
code = "INVITATION_READ_FAILED";
|
|
10553
|
+
constructor() {
|
|
10554
|
+
super("Unable to read a valid invitation result. Check the selected server, account, current membership and permissions.");
|
|
10555
|
+
this.name = "RemoteWorkspaceInvitationReadError";
|
|
10556
|
+
}
|
|
10557
|
+
}
|
|
10558
|
+
function invitationFailure(value, status) {
|
|
10559
|
+
if (!record(value) || typeof value.code !== "string" || !Object.hasOwn(invitationFailures, value.code))
|
|
10560
|
+
return null;
|
|
10561
|
+
const code = value.code;
|
|
10562
|
+
return invitationFailures[code][0] === status ? code : null;
|
|
10563
|
+
}
|
|
10258
10564
|
var record = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
10259
10565
|
var uuid = (v) => typeof v === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v);
|
|
10260
|
-
var text = (v, max = 1024) => typeof v === "string" && !!v.trim() && v.length <= max && !/[\p{Cc}\p{Cs}\u2028\u2029]/u.test(v);
|
|
10261
10566
|
var role = (v) => typeof v === "string" && ["owner", "admin", "member", "viewer"].includes(v);
|
|
10567
|
+
var email = (v) => typeof v === "string" && v.length <= 254 && !/[\p{Cc}\p{Cs}\u2028\u2029\s]/u.test(v) && /^[^@]+@[^@]+\.[^@]+$/.test(v);
|
|
10568
|
+
var timestamp = (v) => typeof v === "string" && v.length <= 40 && /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d{1,6})?(?:Z|[+-]\d\d:\d\d)$/.test(v) && Number.isFinite(Date.parse(v));
|
|
10569
|
+
var inputFailure = () => {
|
|
10570
|
+
throw new WorkspaceInvitationInputError;
|
|
10571
|
+
};
|
|
10572
|
+
function invitationInput(action, input) {
|
|
10573
|
+
if (!record(input))
|
|
10574
|
+
return inputFailure();
|
|
10575
|
+
const keys = {
|
|
10576
|
+
list: ["after"],
|
|
10577
|
+
get: ["invitationId"],
|
|
10578
|
+
issue: ["email", "role", "idempotencyKey", "confirm"],
|
|
10579
|
+
resend: ["invitationId", "expectedGeneration", "idempotencyKey", "confirm"],
|
|
10580
|
+
revoke: ["invitationId", "expectedGeneration", "confirm"],
|
|
10581
|
+
accept: ["invitationId", "token", "confirm"]
|
|
10582
|
+
};
|
|
10583
|
+
if (Object.keys(input).some((key) => !keys[action].includes(key)) || action !== "list" && keys[action].some((key) => !Object.hasOwn(input, key)))
|
|
10584
|
+
return inputFailure();
|
|
10585
|
+
const value = { ...input };
|
|
10586
|
+
if (action === "list") {
|
|
10587
|
+
if (value.after !== undefined && !uuid(value.after))
|
|
10588
|
+
return inputFailure();
|
|
10589
|
+
}
|
|
10590
|
+
if (["get", "resend", "revoke", "accept"].includes(action) && !uuid(value.invitationId))
|
|
10591
|
+
return inputFailure();
|
|
10592
|
+
if (!["list", "get"].includes(action) && value.confirm !== true)
|
|
10593
|
+
return inputFailure();
|
|
10594
|
+
if (["issue", "resend"].includes(action) && !uuid(value.idempotencyKey))
|
|
10595
|
+
return inputFailure();
|
|
10596
|
+
if (action === "issue") {
|
|
10597
|
+
if (typeof value.email !== "string")
|
|
10598
|
+
return inputFailure();
|
|
10599
|
+
value.email = value.email.trim().toLowerCase();
|
|
10600
|
+
if (!email(value.email) || !role(value.role))
|
|
10601
|
+
return inputFailure();
|
|
10602
|
+
}
|
|
10603
|
+
if (["resend", "revoke"].includes(action) && (!Number.isInteger(value.expectedGeneration) || Number(value.expectedGeneration) < 1 || Number(value.expectedGeneration) > 10))
|
|
10604
|
+
return inputFailure();
|
|
10605
|
+
if (action === "accept" && (typeof value.token !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(value.token)))
|
|
10606
|
+
return inputFailure();
|
|
10607
|
+
return value;
|
|
10608
|
+
}
|
|
10609
|
+
function invitationRequest(action, input) {
|
|
10610
|
+
const base = "/api/v1/workspace/invitations", value = input;
|
|
10611
|
+
const { confirm: _confirm, invitationId: id, ...body } = value;
|
|
10612
|
+
if (action === "list")
|
|
10613
|
+
return { path: base + (value.after ? `?after=${value.after}` : ""), method: "GET" };
|
|
10614
|
+
if (action === "get")
|
|
10615
|
+
return { path: `${base}/${id}`, method: "GET" };
|
|
10616
|
+
if (action === "accept")
|
|
10617
|
+
return { path: "/api/v1/account/invitations/accept", method: "POST", body: JSON.stringify({ invitationId: id, token: value.token }) };
|
|
10618
|
+
return { path: action === "issue" ? base : `${base}/${id}${action === "resend" ? "/resend" : ""}`, method: action === "revoke" ? "DELETE" : "POST", body: JSON.stringify(body) };
|
|
10619
|
+
}
|
|
10620
|
+
function invalid() {
|
|
10621
|
+
throw new RemoteWorkspaceInvitationReadError;
|
|
10622
|
+
}
|
|
10623
|
+
function projection(v, organizationId) {
|
|
10624
|
+
if (!record(v) || !uuid(v.id) || v.organizationId !== organizationId || !email(v.email) || v.email !== v.email.trim().toLowerCase() || !role(v.role) || !Number.isInteger(v.generation) || Number(v.generation) < 1 || Number(v.generation) > 10 || typeof v.status !== "string" || !["pending", "expired", "accepted", "revoked"].includes(v.status) || !timestamp(v.expiresAt) || !timestamp(v.createdAt) || !record(v.delivery) || typeof v.delivery.state !== "string" || !["queued", "sending", "uncertain", "provider_accepted", "failed", "cancelled"].includes(v.delivery.state) || !Number.isInteger(v.delivery.attempts) || Number(v.delivery.attempts) < 0 || Number(v.delivery.attempts) > 5)
|
|
10625
|
+
return invalid();
|
|
10626
|
+
return {
|
|
10627
|
+
id: v.id,
|
|
10628
|
+
organizationId,
|
|
10629
|
+
email: v.email,
|
|
10630
|
+
role: v.role,
|
|
10631
|
+
generation: Number(v.generation),
|
|
10632
|
+
status: v.status,
|
|
10633
|
+
expiresAt: v.expiresAt,
|
|
10634
|
+
createdAt: v.createdAt,
|
|
10635
|
+
delivery: { state: v.delivery.state, attempts: Number(v.delivery.attempts) }
|
|
10636
|
+
};
|
|
10637
|
+
}
|
|
10638
|
+
function parseInvitationResult(action, value, input, organizationId) {
|
|
10639
|
+
if (!record(value))
|
|
10640
|
+
return invalid();
|
|
10641
|
+
const request = input;
|
|
10642
|
+
if (action === "accept") {
|
|
10643
|
+
if (!uuid(value.organizationId) || !uuid(value.membershipId) || value.accepted !== true || typeof value.changed !== "boolean")
|
|
10644
|
+
return invalid();
|
|
10645
|
+
return { organizationId: value.organizationId, membershipId: value.membershipId, accepted: true, changed: value.changed };
|
|
10646
|
+
}
|
|
10647
|
+
if (action === "list") {
|
|
10648
|
+
if (value.organizationId !== organizationId || !Array.isArray(value.invitations) || value.invitations.length > 50 || value.nextCursor !== null && !uuid(value.nextCursor))
|
|
10649
|
+
return invalid();
|
|
10650
|
+
const invitations = value.invitations.map((v) => projection(v, organizationId));
|
|
10651
|
+
if (invitations.some((v, n) => v.id <= String(n ? invitations[n - 1].id : request.after ?? "")) || value.nextCursor !== null && (invitations.length !== 50 || value.nextCursor !== invitations.at(-1)?.id))
|
|
10652
|
+
return invalid();
|
|
10653
|
+
return { organizationId, invitations, nextCursor: value.nextCursor };
|
|
10654
|
+
}
|
|
10655
|
+
const invitation = projection(value.invitation, organizationId);
|
|
10656
|
+
if (action !== "issue" && invitation.id !== request.invitationId)
|
|
10657
|
+
return invalid();
|
|
10658
|
+
if (action === "get")
|
|
10659
|
+
return { invitation };
|
|
10660
|
+
if (typeof value.changed !== "boolean")
|
|
10661
|
+
return invalid();
|
|
10662
|
+
if (action === "issue" && (invitation.email !== request.email || invitation.role !== request.role || value.changed && invitation.generation !== 1))
|
|
10663
|
+
return invalid();
|
|
10664
|
+
if (action === "resend" && (value.changed ? invitation.generation !== Number(request.expectedGeneration) + 1 : invitation.generation <= Number(request.expectedGeneration)))
|
|
10665
|
+
return invalid();
|
|
10666
|
+
if (action === "revoke" && (invitation.status !== "revoked" || invitation.generation < Number(request.expectedGeneration) || value.changed && invitation.generation !== request.expectedGeneration))
|
|
10667
|
+
return invalid();
|
|
10668
|
+
return { invitation, changed: value.changed };
|
|
10669
|
+
}
|
|
10670
|
+
|
|
10671
|
+
// src/lib/remote-workspace-selection.ts
|
|
10672
|
+
var record2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
10673
|
+
var uuid2 = (v) => typeof v === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v);
|
|
10674
|
+
var text = (v, max = 1024) => typeof v === "string" && !!v.trim() && v.length <= max && !/[\p{Cc}\p{Cs}\u2028\u2029]/u.test(v);
|
|
10675
|
+
var role2 = (v) => typeof v === "string" && ["owner", "admin", "member", "viewer"].includes(v);
|
|
10262
10676
|
var invalidWorkspaceResult = "The server returned an invalid workspace selection result.";
|
|
10263
10677
|
|
|
10264
10678
|
class WorkspaceContextInputError extends Error {
|
|
@@ -10275,48 +10689,48 @@ class WorkspaceIdentityMismatchError extends Error {
|
|
|
10275
10689
|
}
|
|
10276
10690
|
}
|
|
10277
10691
|
function workspaceExpectedUserId(value) {
|
|
10278
|
-
if (!
|
|
10692
|
+
if (!uuid2(value))
|
|
10279
10693
|
throw new WorkspaceContextInputError;
|
|
10280
10694
|
return value;
|
|
10281
10695
|
}
|
|
10282
10696
|
function workspaceContext(value) {
|
|
10283
|
-
if (!
|
|
10697
|
+
if (!record2(value) || Object.keys(value).sort().join(",") !== "membershipId,userId" || !uuid2(value.userId) || !uuid2(value.membershipId))
|
|
10284
10698
|
throw new WorkspaceContextInputError;
|
|
10285
10699
|
return { userId: value.userId, membershipId: value.membershipId };
|
|
10286
10700
|
}
|
|
10287
|
-
function
|
|
10701
|
+
function invalid2() {
|
|
10288
10702
|
throw new Error(invalidWorkspaceResult);
|
|
10289
10703
|
}
|
|
10290
10704
|
function organization(v) {
|
|
10291
|
-
if (!
|
|
10292
|
-
return
|
|
10705
|
+
if (!record2(v) || !uuid2(v.id) || !text(v.slug) || !text(v.name))
|
|
10706
|
+
return invalid2();
|
|
10293
10707
|
return { id: v.id, slug: v.slug, name: v.name };
|
|
10294
10708
|
}
|
|
10295
10709
|
function parseAccountWorkspaces(value) {
|
|
10296
|
-
if (!
|
|
10297
|
-
return
|
|
10710
|
+
if (!record2(value) || !Array.isArray(value.workspaces) || !value.workspaces.length || value.workspaces.length > 1000)
|
|
10711
|
+
return invalid2();
|
|
10298
10712
|
const workspaces = value.workspaces.map((v) => {
|
|
10299
|
-
if (!
|
|
10300
|
-
return
|
|
10713
|
+
if (!record2(v) || !uuid2(v.membershipId) || !role2(v.role) || typeof v.current !== "boolean")
|
|
10714
|
+
return invalid2();
|
|
10301
10715
|
return { membershipId: v.membershipId, organization: organization(v.organization), role: v.role, current: v.current };
|
|
10302
10716
|
});
|
|
10303
10717
|
if (workspaces.filter((w) => w.current).length !== 1 || new Set(workspaces.map((w) => w.membershipId)).size !== workspaces.length || new Set(workspaces.map((w) => w.organization.id)).size !== workspaces.length)
|
|
10304
|
-
return
|
|
10718
|
+
return invalid2();
|
|
10305
10719
|
return { workspaces };
|
|
10306
10720
|
}
|
|
10307
10721
|
function parseWorkspaceIdentity(value, expectedUserId) {
|
|
10308
|
-
if (!
|
|
10309
|
-
return
|
|
10722
|
+
if (!record2(value))
|
|
10723
|
+
return invalid2();
|
|
10310
10724
|
const user = value.user;
|
|
10311
|
-
if (!
|
|
10312
|
-
return
|
|
10725
|
+
if (!record2(user) || !uuid2(user.id) || !uuid2(user.membershipId) || !text(user.email, 320) || !(user.displayName === null || text(user.displayName)) || !role2(user.role))
|
|
10726
|
+
return invalid2();
|
|
10313
10727
|
if (user.id !== expectedUserId)
|
|
10314
10728
|
throw new WorkspaceIdentityMismatchError;
|
|
10315
10729
|
return { user: { id: user.id, membershipId: user.membershipId, email: user.email, displayName: user.displayName, role: user.role }, organization: organization(value.organization) };
|
|
10316
10730
|
}
|
|
10317
10731
|
function sessionToken(value) {
|
|
10318
10732
|
if (typeof value !== "string" || !value || value.length > 8192 || /[^\x21-\x7e]/.test(value) || value.startsWith("sk_"))
|
|
10319
|
-
return
|
|
10733
|
+
return invalid2();
|
|
10320
10734
|
return value;
|
|
10321
10735
|
}
|
|
10322
10736
|
function parseWorkspaceSession(value, expected) {
|
|
@@ -10326,9 +10740,9 @@ function parseWorkspaceSession(value, expected) {
|
|
|
10326
10740
|
return { token: sessionToken(value.token), ...identity };
|
|
10327
10741
|
}
|
|
10328
10742
|
function parseWorkspaceLogin(value, expectedUserId) {
|
|
10329
|
-
const user =
|
|
10330
|
-
if (!
|
|
10331
|
-
return
|
|
10743
|
+
const user = record2(value) && value.user;
|
|
10744
|
+
if (!record2(value) || !record2(user) || !uuid2(user.id))
|
|
10745
|
+
return invalid2();
|
|
10332
10746
|
if (expectedUserId !== undefined && user.id !== expectedUserId)
|
|
10333
10747
|
throw new WorkspaceIdentityMismatchError;
|
|
10334
10748
|
return { token: sessionToken(value.token), userId: user.id };
|
|
@@ -10342,18 +10756,18 @@ var workspaceSelectionFailures = {
|
|
|
10342
10756
|
WORKSPACE_BUSY: [503, "Workspace is busy. Refresh before retrying."]
|
|
10343
10757
|
};
|
|
10344
10758
|
function workspaceSelectionFailure(value, status) {
|
|
10345
|
-
if (!
|
|
10759
|
+
if (!record2(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceSelectionFailures, value.code))
|
|
10346
10760
|
return null;
|
|
10347
10761
|
const code = value.code;
|
|
10348
10762
|
return workspaceSelectionFailures[code][0] === status ? code : null;
|
|
10349
10763
|
}
|
|
10350
10764
|
|
|
10351
10765
|
// src/lib/remote-workspace.ts
|
|
10352
|
-
var
|
|
10766
|
+
var record3 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
10353
10767
|
var cursor = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,512}$/.test(value);
|
|
10354
|
-
var
|
|
10768
|
+
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);
|
|
10355
10769
|
function workspaceMembersQuery(options = {}) {
|
|
10356
|
-
if (!
|
|
10770
|
+
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))
|
|
10357
10771
|
throw new Error("Use a roster limit from 1 to 100 and an unchanged continuation cursor.");
|
|
10358
10772
|
const query = new URLSearchParams;
|
|
10359
10773
|
if (options.limit !== undefined)
|
|
@@ -10362,14 +10776,14 @@ function workspaceMembersQuery(options = {}) {
|
|
|
10362
10776
|
query.set("cursor", options.cursor);
|
|
10363
10777
|
return query.size ? `?${query}` : "";
|
|
10364
10778
|
}
|
|
10365
|
-
function
|
|
10779
|
+
function timestamp2(value) {
|
|
10366
10780
|
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$/.test(value))
|
|
10367
10781
|
return false;
|
|
10368
10782
|
const time = Date.parse(value);
|
|
10369
10783
|
return Number.isFinite(time) && new Date(time).toISOString().slice(0, 23) === value.slice(0, 23);
|
|
10370
10784
|
}
|
|
10371
10785
|
function parseMember(row, fail) {
|
|
10372
|
-
if (!
|
|
10786
|
+
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))
|
|
10373
10787
|
return fail();
|
|
10374
10788
|
return {
|
|
10375
10789
|
membershipId: row.membershipId,
|
|
@@ -10389,12 +10803,12 @@ class WorkspaceMemberInputError extends Error {
|
|
|
10389
10803
|
}
|
|
10390
10804
|
}
|
|
10391
10805
|
function mutationInput(membershipId, input, roleChange) {
|
|
10392
|
-
if (typeof membershipId !== "string" || !
|
|
10806
|
+
if (typeof membershipId !== "string" || !uuid3(membershipId) || membershipId !== membershipId.toLowerCase() || !record3(input) || Object.keys(input).sort().join(",") !== (roleChange ? "expectedRole,role" : "expectedRole"))
|
|
10393
10807
|
throw new WorkspaceMemberInputError;
|
|
10394
|
-
const expectedRole = input.expectedRole,
|
|
10395
|
-
if (!isRole(expectedRole) || roleChange && !isRole(
|
|
10808
|
+
const expectedRole = input.expectedRole, role3 = roleChange ? input.role : undefined;
|
|
10809
|
+
if (!isRole(expectedRole) || roleChange && !isRole(role3))
|
|
10396
10810
|
throw new WorkspaceMemberInputError;
|
|
10397
|
-
return { membershipId, role:
|
|
10811
|
+
return { membershipId, role: role3, expectedRole };
|
|
10398
10812
|
}
|
|
10399
10813
|
function workspaceMemberRoleInput(membershipId, input) {
|
|
10400
10814
|
const value = mutationInput(membershipId, input, true);
|
|
@@ -10405,19 +10819,19 @@ function workspaceMemberRemovalInput(membershipId, input) {
|
|
|
10405
10819
|
return { membershipId: value.membershipId, body: { expectedRole: value.expectedRole } };
|
|
10406
10820
|
}
|
|
10407
10821
|
var invalidMemberResult = "The server returned an invalid workspace member result. Refresh the roster before another action.";
|
|
10408
|
-
function parseWorkspaceMemberRoleResult(value, membershipId,
|
|
10822
|
+
function parseWorkspaceMemberRoleResult(value, membershipId, role3) {
|
|
10409
10823
|
const fail = () => {
|
|
10410
10824
|
throw new Error(invalidMemberResult);
|
|
10411
10825
|
};
|
|
10412
|
-
if (!
|
|
10826
|
+
if (!record3(value) || !uuid3(value.organizationId) || typeof value.changed !== "boolean")
|
|
10413
10827
|
return fail();
|
|
10414
10828
|
const member = parseMember(value.member, fail);
|
|
10415
|
-
if (member.membershipId !== membershipId || member.role !==
|
|
10829
|
+
if (member.membershipId !== membershipId || member.role !== role3)
|
|
10416
10830
|
return fail();
|
|
10417
10831
|
return { organizationId: value.organizationId, member, changed: value.changed };
|
|
10418
10832
|
}
|
|
10419
10833
|
function parseWorkspaceMemberRemovalResult(value, membershipId) {
|
|
10420
|
-
if (!
|
|
10834
|
+
if (!record3(value) || !uuid3(value.organizationId) || value.membershipId !== membershipId || value.removed !== true || typeof value.alreadyRemoved !== "boolean")
|
|
10421
10835
|
throw new Error(invalidMemberResult);
|
|
10422
10836
|
return { organizationId: value.organizationId, membershipId, removed: true, alreadyRemoved: value.alreadyRemoved };
|
|
10423
10837
|
}
|
|
@@ -10434,7 +10848,7 @@ var workspaceMemberFailures = {
|
|
|
10434
10848
|
MEMBERSHIP_BUSY: [503, "Membership is busy. Refresh the roster before another action."]
|
|
10435
10849
|
};
|
|
10436
10850
|
function workspaceMemberFailure(value, status) {
|
|
10437
|
-
if (!
|
|
10851
|
+
if (!record3(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceMemberFailures, value.code))
|
|
10438
10852
|
return null;
|
|
10439
10853
|
const code = value.code;
|
|
10440
10854
|
return workspaceMemberFailures[code][0] === status ? code : null;
|
|
@@ -10443,7 +10857,7 @@ function parseWorkspaceMembersPage(value) {
|
|
|
10443
10857
|
const fail = () => {
|
|
10444
10858
|
throw new Error("The server returned an invalid workspace roster.");
|
|
10445
10859
|
};
|
|
10446
|
-
if (!
|
|
10860
|
+
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)
|
|
10447
10861
|
return fail();
|
|
10448
10862
|
const members = value.members.map((row) => parseMember(row, fail));
|
|
10449
10863
|
if (new Set(members.map((row) => row.membershipId)).size !== members.length)
|
|
@@ -10517,44 +10931,44 @@ function getApiUrl(action, env = process.env, options = {}) {
|
|
|
10517
10931
|
// src/lib/remote-run-contract.ts
|
|
10518
10932
|
var REMOTE_SKILL_RUN_CONTRACT_VERSION = 1;
|
|
10519
10933
|
function normalizeRemoteSkillRunContract(payload, fallbackSkill) {
|
|
10520
|
-
const
|
|
10934
|
+
const record4 = isRecord3(payload) ? payload : {};
|
|
10521
10935
|
return {
|
|
10522
10936
|
contractVersion: REMOTE_SKILL_RUN_CONTRACT_VERSION,
|
|
10523
|
-
...pickString(
|
|
10524
|
-
skill: pickStringValue(
|
|
10525
|
-
...pickString(
|
|
10526
|
-
...pickString(
|
|
10527
|
-
...pickNumber(
|
|
10528
|
-
...pickString(
|
|
10529
|
-
...pickString(
|
|
10530
|
-
...pickString(
|
|
10531
|
-
...pickString(
|
|
10532
|
-
...pickNumber(
|
|
10533
|
-
...pickString(
|
|
10534
|
-
...hasOwn(
|
|
10535
|
-
...pickString(
|
|
10536
|
-
...pickString(
|
|
10537
|
-
...pickString(
|
|
10538
|
-
...pickString(
|
|
10539
|
-
...hasOwn(
|
|
10937
|
+
...pickString(record4, "id"),
|
|
10938
|
+
skill: pickStringValue(record4, "skill") ?? fallbackSkill,
|
|
10939
|
+
...pickString(record4, "requestedSlug"),
|
|
10940
|
+
...pickString(record4, "status"),
|
|
10941
|
+
...pickNumber(record4, "exitCode"),
|
|
10942
|
+
...pickString(record4, "correlationId"),
|
|
10943
|
+
...pickString(record4, "createdAt"),
|
|
10944
|
+
...pickString(record4, "startedAt"),
|
|
10945
|
+
...pickString(record4, "completedAt"),
|
|
10946
|
+
...pickNumber(record4, "durationMs"),
|
|
10947
|
+
...pickString(record4, "outputType"),
|
|
10948
|
+
...hasOwn(record4, "outputPreview") ? { outputPreview: record4.outputPreview } : {},
|
|
10949
|
+
...pickString(record4, "errorCode"),
|
|
10950
|
+
...pickString(record4, "errorMessage"),
|
|
10951
|
+
...pickString(record4, "error"),
|
|
10952
|
+
...pickString(record4, "code"),
|
|
10953
|
+
...hasOwn(record4, "details") ? { details: record4.details } : {}
|
|
10540
10954
|
};
|
|
10541
10955
|
}
|
|
10542
10956
|
function isRecord3(value) {
|
|
10543
10957
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
10544
10958
|
}
|
|
10545
|
-
function hasOwn(
|
|
10546
|
-
return Object.prototype.hasOwnProperty.call(
|
|
10959
|
+
function hasOwn(record4, key) {
|
|
10960
|
+
return Object.prototype.hasOwnProperty.call(record4, key);
|
|
10547
10961
|
}
|
|
10548
|
-
function pickString(
|
|
10549
|
-
const value = pickStringValue(
|
|
10962
|
+
function pickString(record4, key) {
|
|
10963
|
+
const value = pickStringValue(record4, key);
|
|
10550
10964
|
return value === undefined ? {} : { [key]: value };
|
|
10551
10965
|
}
|
|
10552
|
-
function pickStringValue(
|
|
10553
|
-
const value =
|
|
10966
|
+
function pickStringValue(record4, key) {
|
|
10967
|
+
const value = record4[key];
|
|
10554
10968
|
return typeof value === "string" ? value : undefined;
|
|
10555
10969
|
}
|
|
10556
|
-
function pickNumber(
|
|
10557
|
-
const value =
|
|
10970
|
+
function pickNumber(record4, key) {
|
|
10971
|
+
const value = record4[key];
|
|
10558
10972
|
return typeof value === "number" && Number.isFinite(value) ? { [key]: value } : {};
|
|
10559
10973
|
}
|
|
10560
10974
|
|
|
@@ -11032,6 +11446,53 @@ class RemoteSkillsClient {
|
|
|
11032
11446
|
}
|
|
11033
11447
|
return parseWorkspaceLeaveResult(body, captured.context.membershipId, identity.organization.id);
|
|
11034
11448
|
}
|
|
11449
|
+
listWorkspaceInvitations(context, options = {}) {
|
|
11450
|
+
return this.requestWorkspaceInvitation(context, "list", options);
|
|
11451
|
+
}
|
|
11452
|
+
getWorkspaceInvitation(context, invitationId) {
|
|
11453
|
+
return this.requestWorkspaceInvitation(context, "get", { invitationId });
|
|
11454
|
+
}
|
|
11455
|
+
issueWorkspaceInvitation(context, input) {
|
|
11456
|
+
return this.requestWorkspaceInvitation(context, "issue", input);
|
|
11457
|
+
}
|
|
11458
|
+
resendWorkspaceInvitation(context, invitationId, input) {
|
|
11459
|
+
return this.requestWorkspaceInvitation(context, "resend", { ...input, invitationId });
|
|
11460
|
+
}
|
|
11461
|
+
revokeWorkspaceInvitation(context, invitationId, input) {
|
|
11462
|
+
return this.requestWorkspaceInvitation(context, "revoke", { ...input, invitationId });
|
|
11463
|
+
}
|
|
11464
|
+
acceptWorkspaceInvitation(context, invitationId, input) {
|
|
11465
|
+
return this.requestWorkspaceInvitation(context, "accept", { ...input, invitationId });
|
|
11466
|
+
}
|
|
11467
|
+
async requestWorkspaceInvitation(context, action, input) {
|
|
11468
|
+
const target = workspaceContext(context), captured = invitationInput(action, input);
|
|
11469
|
+
const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
|
|
11470
|
+
const identityValue = await connection.requestWorkspaceSelection("/api/auth/whoami");
|
|
11471
|
+
if (!identityValue || typeof identityValue !== "object" || identityValue.authMethod !== "jwt")
|
|
11472
|
+
throw new RemoteWorkspaceInvitationError("INTERACTIVE_SESSION_REQUIRED");
|
|
11473
|
+
const identity = parseWorkspaceIdentity(identityValue, target.userId);
|
|
11474
|
+
if (identity.user.membershipId !== target.membershipId)
|
|
11475
|
+
throw new WorkspaceIdentityMismatchError;
|
|
11476
|
+
const request = invitationRequest(action, captured), read = action === "list" || action === "get";
|
|
11477
|
+
let response, value;
|
|
11478
|
+
try {
|
|
11479
|
+
response = await connection.request(request.path, { method: request.method, ...request.body ? { body: request.body } : {}, credentials: "omit" });
|
|
11480
|
+
value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, response.ok ? 64 * 1024 : 4096)));
|
|
11481
|
+
} catch {
|
|
11482
|
+
throw read ? new RemoteWorkspaceInvitationReadError : new RemoteWorkspaceInvitationUnconfirmedError;
|
|
11483
|
+
}
|
|
11484
|
+
if (!response.ok) {
|
|
11485
|
+
const code = invitationFailure(value, response.status);
|
|
11486
|
+
if (code)
|
|
11487
|
+
throw new RemoteWorkspaceInvitationError(code);
|
|
11488
|
+
throw read ? new RemoteWorkspaceInvitationReadError : new RemoteWorkspaceInvitationUnconfirmedError;
|
|
11489
|
+
}
|
|
11490
|
+
try {
|
|
11491
|
+
return parseInvitationResult(action, value, captured, identity.organization.id);
|
|
11492
|
+
} catch {
|
|
11493
|
+
throw read ? new RemoteWorkspaceInvitationReadError : new RemoteWorkspaceInvitationUnconfirmedError;
|
|
11494
|
+
}
|
|
11495
|
+
}
|
|
11035
11496
|
async listApiKeys() {
|
|
11036
11497
|
return this.arrayResponse("/api/auth/keys");
|
|
11037
11498
|
}
|
|
@@ -11282,13 +11743,13 @@ class RemoteSkillsClient {
|
|
|
11282
11743
|
return normalizeUpdatedSincePage(await response.json());
|
|
11283
11744
|
}
|
|
11284
11745
|
}
|
|
11285
|
-
function requireOptionalString(
|
|
11286
|
-
if (
|
|
11746
|
+
function requireOptionalString(record4, field) {
|
|
11747
|
+
if (record4[field] === undefined)
|
|
11287
11748
|
return;
|
|
11288
|
-
if (typeof
|
|
11749
|
+
if (typeof record4[field] !== "string") {
|
|
11289
11750
|
throw new Error(`Remote payload did not match the expected contract (${field} must be a string when present)`);
|
|
11290
11751
|
}
|
|
11291
|
-
return
|
|
11752
|
+
return record4[field];
|
|
11292
11753
|
}
|
|
11293
11754
|
var INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
|
|
11294
11755
|
function isVersionRecord(value) {
|
|
@@ -11311,19 +11772,19 @@ function normalizePin(entry) {
|
|
|
11311
11772
|
if (!entry || typeof entry !== "object") {
|
|
11312
11773
|
throw new Error("Remote pin payload did not match the expected contract (expected an object)");
|
|
11313
11774
|
}
|
|
11314
|
-
const
|
|
11315
|
-
const slug = typeof
|
|
11775
|
+
const record4 = entry;
|
|
11776
|
+
const slug = typeof record4.slug === "string" && record4.slug.trim() ? record4.slug.trim() : undefined;
|
|
11316
11777
|
if (!slug) {
|
|
11317
11778
|
throw new Error("Remote pin payload did not match the expected contract (missing slug)");
|
|
11318
11779
|
}
|
|
11319
11780
|
let metadata;
|
|
11320
|
-
if (
|
|
11321
|
-
if (!
|
|
11781
|
+
if (record4.metadata !== undefined) {
|
|
11782
|
+
if (!record4.metadata || typeof record4.metadata !== "object" || Array.isArray(record4.metadata)) {
|
|
11322
11783
|
throw new Error("Remote pin payload did not match the expected contract (metadata must be a JSON object when present)");
|
|
11323
11784
|
}
|
|
11324
|
-
metadata =
|
|
11785
|
+
metadata = record4.metadata;
|
|
11325
11786
|
}
|
|
11326
|
-
const pinnedAt = requireOptionalString(
|
|
11787
|
+
const pinnedAt = requireOptionalString(record4, "pinnedAt");
|
|
11327
11788
|
return {
|
|
11328
11789
|
slug,
|
|
11329
11790
|
...pinnedAt !== undefined ? { pinnedAt } : {},
|
|
@@ -11340,16 +11801,16 @@ function normalizeSkillSummary(entry) {
|
|
|
11340
11801
|
if (!entry || typeof entry !== "object") {
|
|
11341
11802
|
throw new Error("Remote skill payload did not match the expected contract (expected an object)");
|
|
11342
11803
|
}
|
|
11343
|
-
const
|
|
11344
|
-
const slug = typeof
|
|
11804
|
+
const record4 = entry;
|
|
11805
|
+
const slug = typeof record4.slug === "string" && record4.slug.trim() ? record4.slug.trim() : undefined;
|
|
11345
11806
|
if (!slug) {
|
|
11346
11807
|
throw new Error("Remote skill payload did not match the expected contract (missing slug)");
|
|
11347
11808
|
}
|
|
11348
11809
|
return {
|
|
11349
11810
|
slug,
|
|
11350
|
-
...requireOptionalString(
|
|
11351
|
-
...requireOptionalString(
|
|
11352
|
-
...requireOptionalString(
|
|
11811
|
+
...requireOptionalString(record4, "name") !== undefined ? { name: requireOptionalString(record4, "name") } : {},
|
|
11812
|
+
...requireOptionalString(record4, "version") !== undefined ? { version: requireOptionalString(record4, "version") } : {},
|
|
11813
|
+
...requireOptionalString(record4, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record4, "updatedAt") } : {}
|
|
11353
11814
|
};
|
|
11354
11815
|
}
|
|
11355
11816
|
function normalizeSkillSummaryList(payload) {
|
|
@@ -11402,12 +11863,12 @@ function normalizeUpdatedSincePage(payload) {
|
|
|
11402
11863
|
if (!payload || typeof payload !== "object") {
|
|
11403
11864
|
throw new Error("Updated-since payload did not match the expected contract (expected an object)");
|
|
11404
11865
|
}
|
|
11405
|
-
const
|
|
11406
|
-
if (!Array.isArray(
|
|
11866
|
+
const record4 = payload;
|
|
11867
|
+
if (!Array.isArray(record4.skills)) {
|
|
11407
11868
|
throw new Error("Updated-since payload did not match the expected contract (missing skills array)");
|
|
11408
11869
|
}
|
|
11409
|
-
const skills =
|
|
11410
|
-
const nextCursor =
|
|
11870
|
+
const skills = record4.skills.map(normalizeSkillSummary);
|
|
11871
|
+
const nextCursor = record4.nextCursor === undefined || record4.nextCursor === null ? null : record4.nextCursor;
|
|
11411
11872
|
if (nextCursor !== null && typeof nextCursor !== "string") {
|
|
11412
11873
|
throw new Error("Updated-since payload did not match the expected contract (nextCursor must be a string or absent)");
|
|
11413
11874
|
}
|
|
@@ -11621,10 +12082,6 @@ function recordScheduleRun(id, status, targetDir) {
|
|
|
11621
12082
|
schedule.nextRun = getNextRun(schedule.cron, now)?.toISOString();
|
|
11622
12083
|
saveSchedules(data, targetDir);
|
|
11623
12084
|
}
|
|
11624
|
-
// src/lib/pull.ts
|
|
11625
|
-
import { existsSync as existsSync15, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, readFileSync as readFileSync15, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "fs";
|
|
11626
|
-
import { dirname as dirname6, join as join17 } from "path";
|
|
11627
|
-
|
|
11628
12085
|
// src/lib/revision.ts
|
|
11629
12086
|
import { createHash as createHash4 } from "crypto";
|
|
11630
12087
|
var REVISION_ID_PATTERN = /^[0-9a-f]{64}$/;
|
|
@@ -11644,14 +12101,18 @@ function revisionIdOf(content) {
|
|
|
11644
12101
|
});
|
|
11645
12102
|
return createHash4("sha256").update(canonical).digest("hex");
|
|
11646
12103
|
}
|
|
11647
|
-
function revisionIdOfRecord(
|
|
11648
|
-
return revisionIdOf(
|
|
12104
|
+
function revisionIdOfRecord(record4) {
|
|
12105
|
+
return revisionIdOf(record4);
|
|
11649
12106
|
}
|
|
12107
|
+
// src/lib/pull.ts
|
|
12108
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, readFileSync as readFileSync15, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "fs";
|
|
12109
|
+
import { dirname as dirname6, join as join17 } from "path";
|
|
11650
12110
|
|
|
11651
12111
|
// src/lib/skill-bundle.ts
|
|
11652
12112
|
import { createHash as createHash5 } from "crypto";
|
|
11653
12113
|
import { readFileSync as readFileSync14, readdirSync as readdirSync9, statSync as statSync9 } from "fs";
|
|
11654
12114
|
import { join as join16, relative as relative3 } from "path";
|
|
12115
|
+
import { createGunzip } from "zlib";
|
|
11655
12116
|
var BLOCK = 512;
|
|
11656
12117
|
var ANY_SEGMENT_EXCLUDES = new Set([
|
|
11657
12118
|
".git",
|
|
@@ -11974,6 +12435,227 @@ function concat(chunks) {
|
|
|
11974
12435
|
}
|
|
11975
12436
|
return merged;
|
|
11976
12437
|
}
|
|
12438
|
+
var SKILL_BUNDLE_INSPECTION_LIMITS = Object.freeze({
|
|
12439
|
+
compressedBytes: 16 * 1024 * 1024,
|
|
12440
|
+
decompressedBytes: 64 * 1024 * 1024,
|
|
12441
|
+
entries: 1024,
|
|
12442
|
+
fileBytes: 16 * 1024 * 1024,
|
|
12443
|
+
pathBytes: 100,
|
|
12444
|
+
timeoutMs: 5000
|
|
12445
|
+
});
|
|
12446
|
+
|
|
12447
|
+
class SkillBundleInspectionError extends Error {
|
|
12448
|
+
code;
|
|
12449
|
+
constructor(code, message) {
|
|
12450
|
+
super(message);
|
|
12451
|
+
this.code = code;
|
|
12452
|
+
this.name = "SkillBundleInspectionError";
|
|
12453
|
+
}
|
|
12454
|
+
}
|
|
12455
|
+
function invalidBundle(message) {
|
|
12456
|
+
throw new SkillBundleInspectionError("BUNDLE_INVALID", message);
|
|
12457
|
+
}
|
|
12458
|
+
function inspectionLimits(options) {
|
|
12459
|
+
const limits = { ...SKILL_BUNDLE_INSPECTION_LIMITS };
|
|
12460
|
+
for (const key of Object.keys(options.limits ?? {})) {
|
|
12461
|
+
if (!Object.hasOwn(limits, key))
|
|
12462
|
+
throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Unknown bundle limit");
|
|
12463
|
+
const field = key;
|
|
12464
|
+
const value = options.limits[field];
|
|
12465
|
+
if (!Number.isSafeInteger(value) || value <= 0 || value > limits[field]) {
|
|
12466
|
+
throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle limits must be positive integers within the hard ceilings");
|
|
12467
|
+
}
|
|
12468
|
+
limits[field] = value;
|
|
12469
|
+
}
|
|
12470
|
+
return limits;
|
|
12471
|
+
}
|
|
12472
|
+
async function inspectSkillBundle(bundle, options = {}) {
|
|
12473
|
+
const signal = options.signal;
|
|
12474
|
+
const limits = inspectionLimits(options);
|
|
12475
|
+
const deadline = performance.now() + limits.timeoutMs;
|
|
12476
|
+
const check = () => {
|
|
12477
|
+
if (signal?.aborted)
|
|
12478
|
+
throw new SkillBundleInspectionError("BUNDLE_ABORTED", "Bundle inspection aborted");
|
|
12479
|
+
if (performance.now() >= deadline)
|
|
12480
|
+
throw new SkillBundleInspectionError("BUNDLE_TIMEOUT", "Bundle inspection deadline exceeded");
|
|
12481
|
+
};
|
|
12482
|
+
check();
|
|
12483
|
+
if (bundle.byteLength > limits.compressedBytes)
|
|
12484
|
+
throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Compressed bundle exceeds byte limit");
|
|
12485
|
+
const snapshot = ownBytes(bundle);
|
|
12486
|
+
check();
|
|
12487
|
+
const sha2562 = sha256Hex(snapshot);
|
|
12488
|
+
check();
|
|
12489
|
+
const parser = new BoundedTarReader(limits, check);
|
|
12490
|
+
const streamOptions = { chunkSize: 16 * 1024, highWaterMark: 16 * 1024 };
|
|
12491
|
+
const decoder = createGunzip(streamOptions);
|
|
12492
|
+
let terminalError;
|
|
12493
|
+
const stop = (code) => {
|
|
12494
|
+
terminalError ??= new SkillBundleInspectionError(code, code === "BUNDLE_ABORTED" ? "Bundle inspection aborted" : "Bundle inspection deadline exceeded");
|
|
12495
|
+
decoder.destroy(terminalError);
|
|
12496
|
+
};
|
|
12497
|
+
const onAbort = () => stop("BUNDLE_ABORTED");
|
|
12498
|
+
const timer = setTimeout(() => stop("BUNDLE_TIMEOUT"), Math.max(1, deadline - performance.now()));
|
|
12499
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
12500
|
+
let decompressedByteSize = 0;
|
|
12501
|
+
let bytesSinceYield = 0;
|
|
12502
|
+
try {
|
|
12503
|
+
check();
|
|
12504
|
+
decoder.end(snapshot);
|
|
12505
|
+
for await (const chunk of decoder) {
|
|
12506
|
+
check();
|
|
12507
|
+
decompressedByteSize += chunk.byteLength;
|
|
12508
|
+
if (decompressedByteSize > limits.decompressedBytes)
|
|
12509
|
+
throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Decompressed bundle exceeds byte limit");
|
|
12510
|
+
parser.push(chunk);
|
|
12511
|
+
bytesSinceYield += chunk.byteLength;
|
|
12512
|
+
if (bytesSinceYield >= 256 * 1024) {
|
|
12513
|
+
await new Promise((resolve2) => setTimeout(resolve2, 0));
|
|
12514
|
+
bytesSinceYield = 0;
|
|
12515
|
+
check();
|
|
12516
|
+
}
|
|
12517
|
+
}
|
|
12518
|
+
check();
|
|
12519
|
+
const entries = parser.finish();
|
|
12520
|
+
return {
|
|
12521
|
+
entries,
|
|
12522
|
+
sha256: sha2562,
|
|
12523
|
+
compressedByteSize: snapshot.byteLength,
|
|
12524
|
+
decompressedByteSize,
|
|
12525
|
+
unpackedByteSize: parser.fileBytes,
|
|
12526
|
+
fileCount: entries.length
|
|
12527
|
+
};
|
|
12528
|
+
} catch (error) {
|
|
12529
|
+
if (terminalError)
|
|
12530
|
+
throw terminalError;
|
|
12531
|
+
if (error instanceof SkillBundleInspectionError)
|
|
12532
|
+
throw error;
|
|
12533
|
+
throw new SkillBundleInspectionError("BUNDLE_INVALID", "Invalid or truncated gzip bundle");
|
|
12534
|
+
} finally {
|
|
12535
|
+
clearTimeout(timer);
|
|
12536
|
+
signal?.removeEventListener("abort", onAbort);
|
|
12537
|
+
decoder.destroy();
|
|
12538
|
+
}
|
|
12539
|
+
}
|
|
12540
|
+
|
|
12541
|
+
class BoundedTarReader {
|
|
12542
|
+
limits;
|
|
12543
|
+
check;
|
|
12544
|
+
header = new Uint8Array(BLOCK);
|
|
12545
|
+
headerOffset = 0;
|
|
12546
|
+
pending;
|
|
12547
|
+
bodyOffset = 0;
|
|
12548
|
+
padding = 0;
|
|
12549
|
+
zeroBlocks = 0;
|
|
12550
|
+
entries = [];
|
|
12551
|
+
paths = new SkillEntryPaths;
|
|
12552
|
+
fileBytes = 0;
|
|
12553
|
+
constructor(limits, check) {
|
|
12554
|
+
this.limits = limits;
|
|
12555
|
+
this.check = check;
|
|
12556
|
+
}
|
|
12557
|
+
push(chunk) {
|
|
12558
|
+
let offset = 0;
|
|
12559
|
+
while (offset < chunk.byteLength) {
|
|
12560
|
+
this.check();
|
|
12561
|
+
if (this.pending) {
|
|
12562
|
+
const count = Math.min(this.pending.bytes.byteLength - this.bodyOffset, chunk.byteLength - offset);
|
|
12563
|
+
this.pending.bytes.set(chunk.subarray(offset, offset + count), this.bodyOffset);
|
|
12564
|
+
offset += count;
|
|
12565
|
+
this.bodyOffset += count;
|
|
12566
|
+
if (this.bodyOffset === this.pending.bytes.byteLength) {
|
|
12567
|
+
this.entries.push(this.pending);
|
|
12568
|
+
this.pending = undefined;
|
|
12569
|
+
}
|
|
12570
|
+
} else if (this.padding) {
|
|
12571
|
+
const count = Math.min(this.padding, chunk.byteLength - offset);
|
|
12572
|
+
if (chunk.subarray(offset, offset + count).some((byte) => byte !== 0))
|
|
12573
|
+
invalidBundle("Nonzero tar body padding");
|
|
12574
|
+
offset += count;
|
|
12575
|
+
this.padding -= count;
|
|
12576
|
+
} else {
|
|
12577
|
+
const count = Math.min(BLOCK - this.headerOffset, chunk.byteLength - offset);
|
|
12578
|
+
this.header.set(chunk.subarray(offset, offset + count), this.headerOffset);
|
|
12579
|
+
offset += count;
|
|
12580
|
+
this.headerOffset += count;
|
|
12581
|
+
if (this.headerOffset === BLOCK) {
|
|
12582
|
+
this.readHeader();
|
|
12583
|
+
this.headerOffset = 0;
|
|
12584
|
+
}
|
|
12585
|
+
}
|
|
12586
|
+
}
|
|
12587
|
+
}
|
|
12588
|
+
finish() {
|
|
12589
|
+
this.check();
|
|
12590
|
+
if (this.pending || this.padding || this.headerOffset || this.zeroBlocks < 2)
|
|
12591
|
+
invalidBundle("Truncated tar bundle");
|
|
12592
|
+
return this.entries;
|
|
12593
|
+
}
|
|
12594
|
+
readHeader() {
|
|
12595
|
+
this.check();
|
|
12596
|
+
const h = this.header;
|
|
12597
|
+
if (h.every((byte) => byte === 0)) {
|
|
12598
|
+
this.zeroBlocks++;
|
|
12599
|
+
return;
|
|
12600
|
+
}
|
|
12601
|
+
if (this.zeroBlocks)
|
|
12602
|
+
invalidBundle("Nonzero tar data after terminator");
|
|
12603
|
+
if (this.entries.length >= this.limits.entries)
|
|
12604
|
+
throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle entry limit exceeded");
|
|
12605
|
+
let checksum = 0;
|
|
12606
|
+
for (let i = 0;i < BLOCK; i++)
|
|
12607
|
+
checksum += i >= 148 && i < 156 ? 32 : h[i];
|
|
12608
|
+
if (tarOctal(h.subarray(148, 156)) !== checksum)
|
|
12609
|
+
invalidBundle("Invalid tar header checksum");
|
|
12610
|
+
if (new TextDecoder().decode(h.subarray(257, 265)) !== "ustar\x00" + "00")
|
|
12611
|
+
invalidBundle("Unsupported tar format");
|
|
12612
|
+
if (h[156] !== 48 && h[156] !== 0 || h.subarray(157, 257).some((b) => b !== 0) || h.subarray(345).some((b) => b !== 0))
|
|
12613
|
+
invalidBundle("Unsupported tar entry or path prefix");
|
|
12614
|
+
const mode = tarOctal(h.subarray(100, 108));
|
|
12615
|
+
if (mode > 511)
|
|
12616
|
+
invalidBundle("Unsupported tar permission bits");
|
|
12617
|
+
tarOctal(h.subarray(108, 116));
|
|
12618
|
+
tarOctal(h.subarray(116, 124));
|
|
12619
|
+
tarOctal(h.subarray(136, 148));
|
|
12620
|
+
const size = tarOctal(h.subarray(124, 136));
|
|
12621
|
+
if (size > this.limits.fileBytes || this.fileBytes + size > this.limits.decompressedBytes) {
|
|
12622
|
+
throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle file byte limit exceeded");
|
|
12623
|
+
}
|
|
12624
|
+
const name = h.subarray(0, 100);
|
|
12625
|
+
const end = name.indexOf(0);
|
|
12626
|
+
if (end !== -1 && name.subarray(end).some((b) => b !== 0))
|
|
12627
|
+
invalidBundle("Invalid tar path padding");
|
|
12628
|
+
const raw = end === -1 ? name : name.subarray(0, end);
|
|
12629
|
+
if (raw.byteLength > this.limits.pathBytes)
|
|
12630
|
+
throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle path byte limit exceeded");
|
|
12631
|
+
let path;
|
|
12632
|
+
try {
|
|
12633
|
+
path = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(raw);
|
|
12634
|
+
} catch {
|
|
12635
|
+
return invalidBundle("Invalid UTF-8 bundle path");
|
|
12636
|
+
}
|
|
12637
|
+
this.paths.add(path, this.limits.pathBytes, invalidBundle, () => {
|
|
12638
|
+
throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle path byte limit exceeded");
|
|
12639
|
+
});
|
|
12640
|
+
this.fileBytes += size;
|
|
12641
|
+
this.pending = { path, mode, bytes: new Uint8Array(new ArrayBuffer(size)) };
|
|
12642
|
+
this.bodyOffset = 0;
|
|
12643
|
+
this.padding = (BLOCK - size % BLOCK) % BLOCK;
|
|
12644
|
+
if (!size) {
|
|
12645
|
+
this.entries.push(this.pending);
|
|
12646
|
+
this.pending = undefined;
|
|
12647
|
+
}
|
|
12648
|
+
}
|
|
12649
|
+
}
|
|
12650
|
+
function tarOctal(field) {
|
|
12651
|
+
const text2 = new TextDecoder().decode(field);
|
|
12652
|
+
if (!/^[0-7]+[\0 ]*$/.test(text2))
|
|
12653
|
+
invalidBundle("Invalid tar octal field");
|
|
12654
|
+
const value = Number.parseInt(text2, 8);
|
|
12655
|
+
if (!Number.isSafeInteger(value))
|
|
12656
|
+
invalidBundle("Tar integer is out of range");
|
|
12657
|
+
return value;
|
|
12658
|
+
}
|
|
11977
12659
|
|
|
11978
12660
|
// src/lib/skill-version.ts
|
|
11979
12661
|
var SKILL_VERSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
|
|
@@ -12352,16 +13034,16 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
12352
13034
|
}
|
|
12353
13035
|
return { path: target, created };
|
|
12354
13036
|
}
|
|
12355
|
-
function writePullMarker(dir,
|
|
13037
|
+
function writePullMarker(dir, record4) {
|
|
12356
13038
|
const marker = {
|
|
12357
13039
|
managedBy: "@hasna/skills",
|
|
12358
|
-
skill:
|
|
12359
|
-
source:
|
|
12360
|
-
...
|
|
12361
|
-
...
|
|
12362
|
-
...
|
|
12363
|
-
...
|
|
12364
|
-
...
|
|
13040
|
+
skill: record4.skill,
|
|
13041
|
+
source: record4.source ?? "pull",
|
|
13042
|
+
...record4.version ? { version: record4.version } : {},
|
|
13043
|
+
...record4.contentHash ? { contentHash: record4.contentHash } : {},
|
|
13044
|
+
...record4.sourceCommit ? { sourceCommit: record4.sourceCommit } : {},
|
|
13045
|
+
...record4.signature ? { signature: record4.signature } : {},
|
|
13046
|
+
...record4.revisionId ? { revisionId: record4.revisionId } : {},
|
|
12365
13047
|
syncedAt: new Date().toISOString()
|
|
12366
13048
|
};
|
|
12367
13049
|
writeFileSync8(join17(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
|
|
@@ -12376,19 +13058,19 @@ async function safeMeta(client, slug) {
|
|
|
12376
13058
|
}
|
|
12377
13059
|
if (!raw || typeof raw !== "object")
|
|
12378
13060
|
return null;
|
|
12379
|
-
const
|
|
12380
|
-
const kind =
|
|
12381
|
-
const tags = Array.isArray(
|
|
13061
|
+
const record4 = raw;
|
|
13062
|
+
const kind = record4.kind === "instruction" || record4.kind === "executable" ? record4.kind : undefined;
|
|
13063
|
+
const tags = Array.isArray(record4.tags) ? record4.tags.filter((tag) => typeof tag === "string" && tag.trim().length > 0) : undefined;
|
|
12382
13064
|
return {
|
|
12383
|
-
...str(
|
|
12384
|
-
...str(
|
|
12385
|
-
...str(
|
|
13065
|
+
...str(record4.displayName) ? { displayName: str(record4.displayName) } : {},
|
|
13066
|
+
...str(record4.description) ? { description: str(record4.description) } : {},
|
|
13067
|
+
...str(record4.category) ? { category: str(record4.category) } : {},
|
|
12386
13068
|
...tags && tags.length ? { tags } : {},
|
|
12387
|
-
...str(
|
|
13069
|
+
...str(record4.version) ? { version: str(record4.version) } : {},
|
|
12388
13070
|
...kind ? { kind } : {},
|
|
12389
|
-
...REVISION_ID_PATTERN.test(str(
|
|
12390
|
-
...typeof
|
|
12391
|
-
...str(
|
|
13071
|
+
...REVISION_ID_PATTERN.test(str(record4.revisionId) ?? "") ? { revisionId: str(record4.revisionId) } : {},
|
|
13072
|
+
...typeof record4.skillMd === "string" && record4.skillMd.length > 0 ? { skillMd: record4.skillMd } : {},
|
|
13073
|
+
...str(record4.publishedSource) ? { publishedSource: str(record4.publishedSource) } : {}
|
|
12392
13074
|
};
|
|
12393
13075
|
}
|
|
12394
13076
|
function pickCorpusOptions(options) {
|
|
@@ -12397,8 +13079,8 @@ function pickCorpusOptions(options) {
|
|
|
12397
13079
|
function extractSlug(entry) {
|
|
12398
13080
|
if (!entry || typeof entry !== "object")
|
|
12399
13081
|
return;
|
|
12400
|
-
const
|
|
12401
|
-
return str(
|
|
13082
|
+
const record4 = entry;
|
|
13083
|
+
return str(record4.slug) ?? str(record4.name);
|
|
12402
13084
|
}
|
|
12403
13085
|
function dedupe(values) {
|
|
12404
13086
|
return [...new Set(values)];
|
|
@@ -12524,7 +13206,7 @@ import { dirname as dirname7, relative as relative4 } from "path";
|
|
|
12524
13206
|
// package.json
|
|
12525
13207
|
var package_default = {
|
|
12526
13208
|
name: "@hasna/skills",
|
|
12527
|
-
version: "0.5.
|
|
13209
|
+
version: "0.5.3",
|
|
12528
13210
|
description: "Skills library for AI coding agents",
|
|
12529
13211
|
type: "module",
|
|
12530
13212
|
bin: {
|
|
@@ -14035,7 +14717,7 @@ class SkillsPostgresSyncStore {
|
|
|
14035
14717
|
}
|
|
14036
14718
|
async upsertRecords(records) {
|
|
14037
14719
|
let count = 0;
|
|
14038
|
-
for (const
|
|
14720
|
+
for (const record4 of records) {
|
|
14039
14721
|
await this.client.query([
|
|
14040
14722
|
"INSERT INTO skills_sync_records",
|
|
14041
14723
|
"(scope, kind, id, updated_at, deleted_at, source, payload)",
|
|
@@ -14046,13 +14728,13 @@ class SkillsPostgresSyncStore {
|
|
|
14046
14728
|
"source = EXCLUDED.source,",
|
|
14047
14729
|
"payload = EXCLUDED.payload"
|
|
14048
14730
|
].join(" "), [
|
|
14049
|
-
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
14055
|
-
JSON.stringify(
|
|
14731
|
+
record4.scope,
|
|
14732
|
+
record4.kind,
|
|
14733
|
+
record4.id,
|
|
14734
|
+
record4.updatedAt,
|
|
14735
|
+
record4.deletedAt ?? null,
|
|
14736
|
+
record4.source ?? null,
|
|
14737
|
+
JSON.stringify(record4.payload)
|
|
14056
14738
|
]);
|
|
14057
14739
|
count += 1;
|
|
14058
14740
|
}
|
|
@@ -14994,6 +15676,101 @@ function writeStationHydration(options) {
|
|
|
14994
15676
|
manifestPath: hydrationManifestPath
|
|
14995
15677
|
};
|
|
14996
15678
|
}
|
|
15679
|
+
// src/lib/remote-invitation-recovery.ts
|
|
15680
|
+
class InvitationEmailInputError extends Error {
|
|
15681
|
+
code = "INVITATION_EMAIL_INPUT_INVALID";
|
|
15682
|
+
constructor() {
|
|
15683
|
+
super("Use an explicit Skills API URL, exact invitation and retained challenge IDs, secret input, and deliberate confirmation.");
|
|
15684
|
+
this.name = "InvitationEmailInputError";
|
|
15685
|
+
}
|
|
15686
|
+
}
|
|
15687
|
+
var refusals = {
|
|
15688
|
+
INVALID_REQUEST: [400, "Invitation recovery parameters were refused."],
|
|
15689
|
+
ORIGIN_REQUIRED: [403, "Invitation recovery requires the configured site origin."],
|
|
15690
|
+
INVITATION_PROOF_UNAVAILABLE: [401, "Invitation proof is unavailable. Sign in or explicitly request another recovery code."],
|
|
15691
|
+
RATE_LIMITED: [429, "Invitation verification is rate limited. Wait before another deliberate action."],
|
|
15692
|
+
INVITATION_BUSY: [503, "Invitation is busy. Sign in to check membership before another deliberate action."],
|
|
15693
|
+
INVITATION_DELIVERY_UNAVAILABLE: [503, "Invitation recovery is unavailable on this service."]
|
|
15694
|
+
};
|
|
15695
|
+
|
|
15696
|
+
class RemoteInvitationEmailError extends Error {
|
|
15697
|
+
code;
|
|
15698
|
+
status;
|
|
15699
|
+
constructor(code) {
|
|
15700
|
+
super(refusals[code][1]);
|
|
15701
|
+
this.code = code;
|
|
15702
|
+
this.name = "RemoteInvitationEmailError";
|
|
15703
|
+
this.status = refusals[code][0];
|
|
15704
|
+
}
|
|
15705
|
+
}
|
|
15706
|
+
|
|
15707
|
+
class RemoteInvitationEmailUnconfirmedError extends Error {
|
|
15708
|
+
action;
|
|
15709
|
+
code = "INVITATION_EMAIL_UNCONFIRMED";
|
|
15710
|
+
constructor(action) {
|
|
15711
|
+
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.");
|
|
15712
|
+
this.action = action;
|
|
15713
|
+
this.name = "RemoteInvitationEmailUnconfirmedError";
|
|
15714
|
+
}
|
|
15715
|
+
}
|
|
15716
|
+
var record4 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
15717
|
+
var uuid4 = (v) => typeof v === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v);
|
|
15718
|
+
function invitationEmailIds(invitationId, challengeId) {
|
|
15719
|
+
if (!uuid4(invitationId) || !uuid4(challengeId))
|
|
15720
|
+
throw new InvitationEmailInputError;
|
|
15721
|
+
return { invitationId, challengeId };
|
|
15722
|
+
}
|
|
15723
|
+
function invitationEmailInput(action, input) {
|
|
15724
|
+
const keys = ["invitationId", "token", "challengeId", "confirm", ...action === "accept" ? ["code"] : []];
|
|
15725
|
+
if (!record4(input) || Object.keys(input).length !== keys.length || keys.some((key) => !Object.hasOwn(input, key)))
|
|
15726
|
+
throw new InvitationEmailInputError;
|
|
15727
|
+
const value = { ...input };
|
|
15728
|
+
const ids = invitationEmailIds(value.invitationId, value.challengeId);
|
|
15729
|
+
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)))
|
|
15730
|
+
throw new InvitationEmailInputError;
|
|
15731
|
+
return { ...ids, token: value.token, confirm: true, ...action === "accept" ? { code: value.code } : {} };
|
|
15732
|
+
}
|
|
15733
|
+
async function requestInvitationEmail(origin, action, input) {
|
|
15734
|
+
const value = invitationEmailInput(action, input);
|
|
15735
|
+
let target;
|
|
15736
|
+
try {
|
|
15737
|
+
target = normalizeSkillsApiOrigin(origin);
|
|
15738
|
+
} catch {
|
|
15739
|
+
throw new InvitationEmailInputError;
|
|
15740
|
+
}
|
|
15741
|
+
const body = JSON.stringify({ invitationId: value.invitationId, token: value.token, challengeId: value.challengeId, ...action === "accept" ? { code: value.code } : {} });
|
|
15742
|
+
try {
|
|
15743
|
+
const response = await fetch(`${target}/api/v1/account/invitations/email-${action}`, {
|
|
15744
|
+
method: "POST",
|
|
15745
|
+
headers: { "Content-Type": "application/json" },
|
|
15746
|
+
body,
|
|
15747
|
+
credentials: "omit",
|
|
15748
|
+
redirect: "error",
|
|
15749
|
+
cache: "no-store",
|
|
15750
|
+
referrerPolicy: "no-referrer",
|
|
15751
|
+
signal: AbortSignal.timeout(15000)
|
|
15752
|
+
});
|
|
15753
|
+
const parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(await readBoundedResponse(response, 4096)));
|
|
15754
|
+
if (!response.ok && record4(parsed) && typeof parsed.code === "string" && Object.hasOwn(refusals, parsed.code)) {
|
|
15755
|
+
const code = parsed.code;
|
|
15756
|
+
if (response.status === refusals[code][0])
|
|
15757
|
+
throw new RemoteInvitationEmailError(code);
|
|
15758
|
+
}
|
|
15759
|
+
if (!record4(parsed))
|
|
15760
|
+
throw new RemoteInvitationEmailUnconfirmedError(action);
|
|
15761
|
+
if (action === "challenge" && response.status === 202 && parsed.challengeId === value.challengeId && parsed.expiresIn === 600 && typeof parsed.message === "string" && parsed.message.length <= 256) {
|
|
15762
|
+
return { challengeId: value.challengeId, message: "If this invitation is eligible, a verification code will arrive. Delivery is not confirmed.", expiresIn: 600 };
|
|
15763
|
+
}
|
|
15764
|
+
if (action === "accept" && response.status === 200 && uuid4(parsed.organizationId) && uuid4(parsed.membershipId) && parsed.accepted === true && parsed.changed === true && parsed.signInRequired === true) {
|
|
15765
|
+
return { organizationId: parsed.organizationId, membershipId: parsed.membershipId, accepted: true, changed: true, signInRequired: true };
|
|
15766
|
+
}
|
|
15767
|
+
} catch (error) {
|
|
15768
|
+
if (error instanceof RemoteInvitationEmailError)
|
|
15769
|
+
throw error;
|
|
15770
|
+
}
|
|
15771
|
+
throw new RemoteInvitationEmailUnconfirmedError(action);
|
|
15772
|
+
}
|
|
15773
|
+
|
|
14997
15774
|
// src/lib/remote-auth.ts
|
|
14998
15775
|
var MAX_ERROR_DETAIL_LENGTH = 200;
|
|
14999
15776
|
|
|
@@ -15034,10 +15811,10 @@ async function requestAuthApi(instance, path, options) {
|
|
|
15034
15811
|
const text2 = await res.text();
|
|
15035
15812
|
const body = text2 ? parseJsonBody(text2) : {};
|
|
15036
15813
|
if (!res.ok) {
|
|
15037
|
-
const
|
|
15038
|
-
const detail = typeof
|
|
15039
|
-
const error = typeof
|
|
15040
|
-
const code = typeof
|
|
15814
|
+
const record5 = isRecord5(body) ? body : {};
|
|
15815
|
+
const detail = typeof record5.detail === "string" ? record5.detail : undefined;
|
|
15816
|
+
const error = typeof record5.error === "string" ? record5.error : undefined;
|
|
15817
|
+
const code = typeof record5.code === "string" ? record5.code : undefined;
|
|
15041
15818
|
throw new HostedApiError(detail || error || `${res.status} ${res.statusText}`, {
|
|
15042
15819
|
status: res.status,
|
|
15043
15820
|
code,
|
|
@@ -15071,11 +15848,17 @@ class RemoteSkillsAuthClient {
|
|
|
15071
15848
|
constructor(apiUrl) {
|
|
15072
15849
|
this.apiOrigin = normalizeSkillsApiOrigin(apiUrl);
|
|
15073
15850
|
}
|
|
15074
|
-
|
|
15075
|
-
return this.
|
|
15851
|
+
requestInvitationEmailChallenge(input) {
|
|
15852
|
+
return requestInvitationEmail(this.apiOrigin, "challenge", input);
|
|
15853
|
+
}
|
|
15854
|
+
acceptInvitationEmailChallenge(input) {
|
|
15855
|
+
return requestInvitationEmail(this.apiOrigin, "accept", input);
|
|
15856
|
+
}
|
|
15857
|
+
requestCode(email2) {
|
|
15858
|
+
return this.request("/api/auth/login", { method: "POST", body: JSON.stringify({ email: email2 }) });
|
|
15076
15859
|
}
|
|
15077
|
-
verifyCode(
|
|
15078
|
-
return this.request("/api/auth/verify", { method: "POST", body: JSON.stringify({ email, code }) });
|
|
15860
|
+
verifyCode(email2, code) {
|
|
15861
|
+
return this.request("/api/auth/verify", { method: "POST", body: JSON.stringify({ email: email2, code }) });
|
|
15079
15862
|
}
|
|
15080
15863
|
startDevice() {
|
|
15081
15864
|
return this.request("/api/auth/device/start", { method: "POST", body: JSON.stringify({ client: "skills-sdk" }) });
|
|
@@ -15083,34 +15866,34 @@ class RemoteSkillsAuthClient {
|
|
|
15083
15866
|
pollDevice(deviceCode) {
|
|
15084
15867
|
return this.request("/api/auth/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) });
|
|
15085
15868
|
}
|
|
15086
|
-
async sessionClient(
|
|
15869
|
+
async sessionClient(email2, code, context) {
|
|
15087
15870
|
if (context !== undefined) {
|
|
15088
15871
|
const target = workspaceContext(context), apiOrigin2 = this.apiOrigin;
|
|
15089
|
-
const session = await this.switchWorkspace(
|
|
15872
|
+
const session = await this.switchWorkspace(email2, code, target);
|
|
15090
15873
|
return new RemoteSkillsClient(session.token, apiOrigin2);
|
|
15091
15874
|
}
|
|
15092
15875
|
const apiOrigin = this.apiOrigin;
|
|
15093
|
-
if (!
|
|
15876
|
+
if (!email2.includes("@") || !/^\d{6}$/.test(code))
|
|
15094
15877
|
throw new Error("Fresh email and six-digit verification code are required to manage this account");
|
|
15095
|
-
const login = await this.verifyCode(
|
|
15878
|
+
const login = await this.verifyCode(email2, code);
|
|
15096
15879
|
if (!login || typeof login.token !== "string" || !login.token)
|
|
15097
15880
|
throw new Error("The server did not return an authorized account session");
|
|
15098
15881
|
return new RemoteSkillsClient(login.token, apiOrigin);
|
|
15099
15882
|
}
|
|
15100
|
-
async listAccountWorkspaces(
|
|
15101
|
-
const login = await this.workspaceLogin(
|
|
15883
|
+
async listAccountWorkspaces(email2, code, expectedUserId) {
|
|
15884
|
+
const login = await this.workspaceLogin(email2, code, expectedUserId);
|
|
15102
15885
|
const result = await new RemoteSkillsClient(login.token, login.apiOrigin).listAccountWorkspaces(login.userId);
|
|
15103
15886
|
return { userId: login.userId, ...result };
|
|
15104
15887
|
}
|
|
15105
|
-
async switchWorkspace(
|
|
15888
|
+
async switchWorkspace(email2, code, context) {
|
|
15106
15889
|
const target = workspaceContext(context);
|
|
15107
|
-
const login = await this.workspaceLogin(
|
|
15890
|
+
const login = await this.workspaceLogin(email2, code, target.userId);
|
|
15108
15891
|
return new RemoteSkillsClient(login.token, login.apiOrigin).switchWorkspace(target);
|
|
15109
15892
|
}
|
|
15110
|
-
async workspaceLogin(
|
|
15893
|
+
async workspaceLogin(email2, code, expectedUserId) {
|
|
15111
15894
|
const expected = expectedUserId === undefined ? undefined : workspaceExpectedUserId(expectedUserId);
|
|
15112
15895
|
const apiOrigin = this.apiOrigin;
|
|
15113
|
-
if (typeof
|
|
15896
|
+
if (typeof email2 !== "string" || !email2.includes("@") || typeof code !== "string" || !/^\d{6}$/.test(code))
|
|
15114
15897
|
throw new Error("Fresh email and six-digit verification code are required to manage this account");
|
|
15115
15898
|
let response;
|
|
15116
15899
|
try {
|
|
@@ -15120,7 +15903,7 @@ class RemoteSkillsAuthClient {
|
|
|
15120
15903
|
credentials: "omit",
|
|
15121
15904
|
signal: AbortSignal.timeout(15000),
|
|
15122
15905
|
headers: { "Content-Type": "application/json" },
|
|
15123
|
-
body: JSON.stringify({ email, code })
|
|
15906
|
+
body: JSON.stringify({ email: email2, code })
|
|
15124
15907
|
});
|
|
15125
15908
|
} catch {
|
|
15126
15909
|
throw new HostedApiError("Unable to verify the Skills account.");
|
|
@@ -15137,40 +15920,67 @@ class RemoteSkillsAuthClient {
|
|
|
15137
15920
|
}
|
|
15138
15921
|
return { ...parseWorkspaceLogin(value, expected), apiOrigin };
|
|
15139
15922
|
}
|
|
15140
|
-
async
|
|
15923
|
+
async listWorkspaceInvitations(email2, code, context, options = {}) {
|
|
15924
|
+
const target = workspaceContext(context), captured = invitationInput("list", options);
|
|
15925
|
+
return (await this.sessionClient(email2, code, target)).listWorkspaceInvitations(target, captured);
|
|
15926
|
+
}
|
|
15927
|
+
async getWorkspaceInvitation(email2, code, context, invitationId) {
|
|
15928
|
+
const target = workspaceContext(context), captured = invitationInput("get", { invitationId });
|
|
15929
|
+
return (await this.sessionClient(email2, code, target)).getWorkspaceInvitation(target, captured.invitationId);
|
|
15930
|
+
}
|
|
15931
|
+
async issueWorkspaceInvitation(email2, code, context, input) {
|
|
15932
|
+
const target = workspaceContext(context), captured = invitationInput("issue", input);
|
|
15933
|
+
return (await this.sessionClient(email2, code, target)).issueWorkspaceInvitation(target, captured);
|
|
15934
|
+
}
|
|
15935
|
+
async resendWorkspaceInvitation(email2, code, context, invitationId, input) {
|
|
15936
|
+
const target = workspaceContext(context), captured = invitationInput("resend", { ...input, invitationId });
|
|
15937
|
+
const { invitationId: id, ...options } = captured;
|
|
15938
|
+
return (await this.sessionClient(email2, code, target)).resendWorkspaceInvitation(target, id, options);
|
|
15939
|
+
}
|
|
15940
|
+
async revokeWorkspaceInvitation(email2, code, context, invitationId, input) {
|
|
15941
|
+
const target = workspaceContext(context), captured = invitationInput("revoke", { ...input, invitationId });
|
|
15942
|
+
const { invitationId: id, ...options } = captured;
|
|
15943
|
+
return (await this.sessionClient(email2, code, target)).revokeWorkspaceInvitation(target, id, options);
|
|
15944
|
+
}
|
|
15945
|
+
async acceptWorkspaceInvitation(email2, code, context, invitationId, input) {
|
|
15946
|
+
const target = workspaceContext(context), captured = invitationInput("accept", { ...input, invitationId });
|
|
15947
|
+
const { invitationId: id, ...options } = captured;
|
|
15948
|
+
return (await this.sessionClient(email2, code, target)).acceptWorkspaceInvitation(target, id, options);
|
|
15949
|
+
}
|
|
15950
|
+
async createApiKey(email2, code, name, scopes, context) {
|
|
15141
15951
|
const capturedScopes = scopes === undefined ? undefined : [...scopes];
|
|
15142
|
-
return (await this.sessionClient(
|
|
15952
|
+
return (await this.sessionClient(email2, code, context)).createApiKey(name, capturedScopes);
|
|
15143
15953
|
}
|
|
15144
|
-
async listApiKeys(
|
|
15145
|
-
return (await this.sessionClient(
|
|
15954
|
+
async listApiKeys(email2, code, context) {
|
|
15955
|
+
return (await this.sessionClient(email2, code, context)).listApiKeys();
|
|
15146
15956
|
}
|
|
15147
|
-
async revokeApiKey(
|
|
15148
|
-
return (await this.sessionClient(
|
|
15957
|
+
async revokeApiKey(email2, code, keyId, context) {
|
|
15958
|
+
return (await this.sessionClient(email2, code, context)).revokeApiKey(keyId);
|
|
15149
15959
|
}
|
|
15150
|
-
async updateProfile(
|
|
15960
|
+
async updateProfile(email2, code, input, context) {
|
|
15151
15961
|
const body = customerNamePatch(input, "displayName");
|
|
15152
|
-
return (await this.sessionClient(
|
|
15962
|
+
return (await this.sessionClient(email2, code, context)).updateProfile({ displayName: body.displayName });
|
|
15153
15963
|
}
|
|
15154
|
-
async updateCurrentWorkspace(
|
|
15964
|
+
async updateCurrentWorkspace(email2, code, input, context) {
|
|
15155
15965
|
const body = customerNamePatch(input, "name");
|
|
15156
|
-
return (await this.sessionClient(
|
|
15966
|
+
return (await this.sessionClient(email2, code, context)).updateCurrentWorkspace({ name: body.name });
|
|
15157
15967
|
}
|
|
15158
|
-
async listWorkspaceMembers(
|
|
15968
|
+
async listWorkspaceMembers(email2, code, options = {}, context) {
|
|
15159
15969
|
workspaceMembersQuery(options);
|
|
15160
15970
|
const captured = { ...options };
|
|
15161
|
-
return (await this.sessionClient(
|
|
15971
|
+
return (await this.sessionClient(email2, code, context)).listWorkspaceMembers(captured);
|
|
15162
15972
|
}
|
|
15163
|
-
async setWorkspaceMemberRole(
|
|
15973
|
+
async setWorkspaceMemberRole(email2, code, membershipId, input, context) {
|
|
15164
15974
|
const captured = workspaceMemberRoleInput(membershipId, input);
|
|
15165
|
-
return (await this.sessionClient(
|
|
15975
|
+
return (await this.sessionClient(email2, code, context)).setWorkspaceMemberRole(captured.membershipId, captured.body);
|
|
15166
15976
|
}
|
|
15167
|
-
async leaveWorkspace(
|
|
15977
|
+
async leaveWorkspace(email2, code, context, input) {
|
|
15168
15978
|
const captured = workspaceLeaveInput(context, input);
|
|
15169
|
-
return (await this.sessionClient(
|
|
15979
|
+
return (await this.sessionClient(email2, code, captured.context)).leaveWorkspace(captured.context, captured.input);
|
|
15170
15980
|
}
|
|
15171
|
-
async removeWorkspaceMember(
|
|
15981
|
+
async removeWorkspaceMember(email2, code, membershipId, input, context) {
|
|
15172
15982
|
const captured = workspaceMemberRemovalInput(membershipId, input);
|
|
15173
|
-
return (await this.sessionClient(
|
|
15983
|
+
return (await this.sessionClient(email2, code, context)).removeWorkspaceMember(captured.membershipId, captured.body);
|
|
15174
15984
|
}
|
|
15175
15985
|
request(path, options) {
|
|
15176
15986
|
if (!["/api/auth/login", "/api/auth/verify", "/api/auth/device/start", "/api/auth/device/token", "/api/auth/keys", "/api/auth/whoami"].includes(path))
|
|
@@ -15187,6 +15997,7 @@ export {
|
|
|
15187
15997
|
writeManagedAgentSkill,
|
|
15188
15998
|
writeCorpusSkill,
|
|
15189
15999
|
walkEntries,
|
|
16000
|
+
verifyContentHashFromEntries,
|
|
15190
16001
|
verifyContentHash,
|
|
15191
16002
|
validateToolPrimitiveCoverage,
|
|
15192
16003
|
validateStationId,
|
|
@@ -15223,6 +16034,7 @@ export {
|
|
|
15223
16034
|
sanitizePublicDiscoveryText,
|
|
15224
16035
|
runSkill,
|
|
15225
16036
|
runPortableSkill,
|
|
16037
|
+
revisionIdOf,
|
|
15226
16038
|
resolveSyncAgents,
|
|
15227
16039
|
resolveStorageConfig,
|
|
15228
16040
|
resolveSkillsNativeStorageConfig,
|
|
@@ -15350,6 +16162,7 @@ export {
|
|
|
15350
16162
|
createMcpContractManifest,
|
|
15351
16163
|
createLocalSkillManifest,
|
|
15352
16164
|
configuredSkillsApiUrl,
|
|
16165
|
+
computeContentHashFromEntries,
|
|
15353
16166
|
computeContentHash,
|
|
15354
16167
|
completeSkillRun,
|
|
15355
16168
|
clearRegistryCache,
|
|
@@ -15361,6 +16174,7 @@ export {
|
|
|
15361
16174
|
addSchedule,
|
|
15362
16175
|
adaptSkillMdForAgent,
|
|
15363
16176
|
WorkspaceLeaveInputError,
|
|
16177
|
+
WorkspaceInvitationInputError,
|
|
15364
16178
|
WorkspaceIdentityMismatchError,
|
|
15365
16179
|
WorkspaceContextInputError,
|
|
15366
16180
|
TOOL_PRIMITIVE_SCHEMA_VERSION,
|
|
@@ -15398,10 +16212,15 @@ export {
|
|
|
15398
16212
|
RemoteWorkspaceMemberError,
|
|
15399
16213
|
RemoteWorkspaceLeaveUnconfirmedError,
|
|
15400
16214
|
RemoteWorkspaceLeaveError,
|
|
16215
|
+
RemoteWorkspaceInvitationUnconfirmedError,
|
|
16216
|
+
RemoteWorkspaceInvitationReadError,
|
|
16217
|
+
RemoteWorkspaceInvitationError,
|
|
15401
16218
|
RemoteSkillsClient,
|
|
15402
16219
|
RemoteSkillsAuthClient,
|
|
15403
16220
|
RemoteRouteUnsupportedError,
|
|
15404
16221
|
RemoteRequestError,
|
|
16222
|
+
RemoteInvitationEmailUnconfirmedError,
|
|
16223
|
+
RemoteInvitationEmailError,
|
|
15405
16224
|
RemoteCreditApprovalError,
|
|
15406
16225
|
RemoteCapabilityUnavailableError,
|
|
15407
16226
|
REMOTE_SKILL_RUN_CONTRACT_VERSION,
|
|
@@ -15413,8 +16232,11 @@ export {
|
|
|
15413
16232
|
PORTABLE_SKILL_DEFAULT_VERSION,
|
|
15414
16233
|
MissingSkillsFleetError,
|
|
15415
16234
|
MCP_CONTRACT_SCHEMA_VERSION,
|
|
16235
|
+
InvitationEmailInputError,
|
|
15416
16236
|
HostedApiError,
|
|
15417
16237
|
DEFAULT_EXPORT_DIR,
|
|
16238
|
+
ContentHashInputError,
|
|
16239
|
+
CONTENT_HASH_LIMITS,
|
|
15418
16240
|
CATEGORIES,
|
|
15419
16241
|
BASIC_SKILL_NAMES,
|
|
15420
16242
|
ARTICLE_GENERATION_SLUG,
|