@hasna/skills 0.5.3 → 0.5.5
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 +72 -7
- package/bin/index.js +1429 -209
- package/bin/mcp.js +1545 -40
- package/bin/migrate.js +13 -4
- package/bin/server.js +81 -19
- package/bin/worker.js +37 -7
- package/dist/cli/commands/private-publications.d.ts +2 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +756 -24
- package/dist/lib/fleet-credentials.d.ts +6 -1
- package/dist/lib/home-adoption.d.ts +2 -7
- package/dist/lib/mcp-contracts.d.ts +1 -0
- package/dist/lib/private-publication-customer.d.ts +10 -0
- package/dist/lib/private-publication-recovery.d.ts +43 -0
- package/dist/lib/remote-account.d.ts +5 -0
- package/dist/lib/remote-auth.d.ts +3 -0
- package/dist/lib/remote-client.d.ts +9 -2
- package/dist/lib/remote-private-publications.d.ts +74 -0
- package/dist/lib/remote-quote-errors.d.ts +12 -0
- package/dist/mcp/private-publication-tools.d.ts +2 -0
- package/dist/sdk/execution/dispatchers/ecs.d.ts +25 -14
- package/dist/sdk/index.d.ts +4 -1
- package/dist/sdk/index.js +938 -164
- package/dist/server/types.d.ts +2 -0
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -36860,7 +36860,7 @@ var package_default;
|
|
|
36860
36860
|
var init_package = __esm(() => {
|
|
36861
36861
|
package_default = {
|
|
36862
36862
|
name: "@hasna/skills",
|
|
36863
|
-
version: "0.5.
|
|
36863
|
+
version: "0.5.5",
|
|
36864
36864
|
description: "Skills library for AI coding agents",
|
|
36865
36865
|
type: "module",
|
|
36866
36866
|
bin: {
|
|
@@ -39472,6 +39472,186 @@ function hashBundleFiles(files) {
|
|
|
39472
39472
|
hash.update(part);
|
|
39473
39473
|
return hash.digest("hex");
|
|
39474
39474
|
}
|
|
39475
|
+
async function hashBundleFilesCooperatively(files, check) {
|
|
39476
|
+
const hash = createHash(CONTENT_HASH_ALGORITHM);
|
|
39477
|
+
let bytesSinceYield = 0;
|
|
39478
|
+
for (const part of bundleHashParts(files)) {
|
|
39479
|
+
for (let offset = 0;offset < part.byteLength; offset += 64 * 1024) {
|
|
39480
|
+
check();
|
|
39481
|
+
const chunk2 = part.subarray(offset, offset + 64 * 1024);
|
|
39482
|
+
hash.update(chunk2);
|
|
39483
|
+
bytesSinceYield += chunk2.byteLength;
|
|
39484
|
+
if (bytesSinceYield >= 256 * 1024) {
|
|
39485
|
+
await new Promise((resolve3) => setImmediate(resolve3));
|
|
39486
|
+
bytesSinceYield = 0;
|
|
39487
|
+
}
|
|
39488
|
+
}
|
|
39489
|
+
}
|
|
39490
|
+
check();
|
|
39491
|
+
return hash.digest("hex");
|
|
39492
|
+
}
|
|
39493
|
+
function invalidContent(message = "Invalid content hash input") {
|
|
39494
|
+
throw new ContentHashInputError("CONTENT_HASH_INVALID", message);
|
|
39495
|
+
}
|
|
39496
|
+
function contentLimit(message) {
|
|
39497
|
+
throw new ContentHashInputError("CONTENT_HASH_LIMIT", message);
|
|
39498
|
+
}
|
|
39499
|
+
function contentRecord(value, allowed) {
|
|
39500
|
+
if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
|
|
39501
|
+
invalidContent();
|
|
39502
|
+
const result2 = Object.create(null);
|
|
39503
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
39504
|
+
if (typeof key !== "string" || !allowed.includes(key))
|
|
39505
|
+
invalidContent();
|
|
39506
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
39507
|
+
if (!descriptor || !("value" in descriptor))
|
|
39508
|
+
invalidContent("Accessor content hash input is unsupported");
|
|
39509
|
+
result2[key] = descriptor.value;
|
|
39510
|
+
}
|
|
39511
|
+
return result2;
|
|
39512
|
+
}
|
|
39513
|
+
function contentOptions(options) {
|
|
39514
|
+
const record = contentRecord(options, ["limits", "signal"]);
|
|
39515
|
+
const limits = { ...CONTENT_HASH_LIMITS };
|
|
39516
|
+
if (record.limits !== undefined) {
|
|
39517
|
+
const supplied = contentRecord(record.limits, Object.keys(limits));
|
|
39518
|
+
for (const key of Object.keys(supplied)) {
|
|
39519
|
+
const value = supplied[key];
|
|
39520
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0 || value > limits[key])
|
|
39521
|
+
contentLimit("Invalid content hash limit");
|
|
39522
|
+
limits[key] = value;
|
|
39523
|
+
}
|
|
39524
|
+
}
|
|
39525
|
+
if (record.signal !== undefined && !(record.signal instanceof AbortSignal))
|
|
39526
|
+
invalidContent("Invalid content hash signal");
|
|
39527
|
+
return { limits, signal: record.signal };
|
|
39528
|
+
}
|
|
39529
|
+
function snapshotContentEntries(entries, limits, check) {
|
|
39530
|
+
if (!Array.isArray(entries))
|
|
39531
|
+
invalidContent("Content hash entries must be an array");
|
|
39532
|
+
if (entries.length > limits.entries)
|
|
39533
|
+
contentLimit("Content hash entry limit exceeded");
|
|
39534
|
+
if (Reflect.ownKeys(entries).length !== entries.length + 1)
|
|
39535
|
+
invalidContent("Invalid content hash entry array");
|
|
39536
|
+
const snapshot = [];
|
|
39537
|
+
const paths = new SkillEntryPaths;
|
|
39538
|
+
let rawBytes = 0;
|
|
39539
|
+
for (let index = 0;index < entries.length; index++) {
|
|
39540
|
+
check();
|
|
39541
|
+
const descriptor = Object.getOwnPropertyDescriptor(entries, String(index));
|
|
39542
|
+
if (!descriptor || !("value" in descriptor))
|
|
39543
|
+
invalidContent("Invalid content hash entry array");
|
|
39544
|
+
const entry = contentRecord(descriptor.value, ["path", "bytes", "mode"]);
|
|
39545
|
+
if (typeof entry.path !== "string" || typeof entry.mode !== "number" || !Number.isInteger(entry.mode) || entry.mode < 0 || entry.mode > 511)
|
|
39546
|
+
invalidContent("Invalid regular-file content hash entry");
|
|
39547
|
+
paths.add(entry.path, limits.pathBytes, invalidContent, () => contentLimit("Content hash path limit exceeded"));
|
|
39548
|
+
if (!(entry.bytes instanceof Uint8Array) || !ArrayBuffer.isView(entry.bytes))
|
|
39549
|
+
invalidContent("Content hash entry requires bytes");
|
|
39550
|
+
const size2 = byteLengthOf.call(entry.bytes);
|
|
39551
|
+
if (!(bufferOf.call(entry.bytes) instanceof ArrayBuffer))
|
|
39552
|
+
invalidContent("Shared content hash bytes are unsupported");
|
|
39553
|
+
if (size2 > limits.fileBytes || rawBytes + size2 > limits.rawBytes)
|
|
39554
|
+
contentLimit("Content hash raw byte limit exceeded");
|
|
39555
|
+
if (entry.path === "skill.json" && size2 > limits.manifestBytes)
|
|
39556
|
+
contentLimit("Content hash manifest byte limit exceeded");
|
|
39557
|
+
rawBytes += size2;
|
|
39558
|
+
const bytes = new Uint8Array(new ArrayBuffer(size2));
|
|
39559
|
+
bytes.set(entry.bytes);
|
|
39560
|
+
snapshot.push({ path: entry.path, bytes, mode: entry.mode });
|
|
39561
|
+
}
|
|
39562
|
+
check();
|
|
39563
|
+
return snapshot;
|
|
39564
|
+
}
|
|
39565
|
+
function coveredContentPath(path) {
|
|
39566
|
+
const segments = path.split("/");
|
|
39567
|
+
if (!HASH_COVERAGE.includes(segments[0]))
|
|
39568
|
+
return false;
|
|
39569
|
+
return !segments.slice(1).some((segment, index) => excludedHashEntry(segment, index < segments.length - 2));
|
|
39570
|
+
}
|
|
39571
|
+
function boundedManifest(raw, maxDepth) {
|
|
39572
|
+
let parsed;
|
|
39573
|
+
try {
|
|
39574
|
+
parsed = JSON.parse(raw);
|
|
39575
|
+
} catch {
|
|
39576
|
+
return;
|
|
39577
|
+
}
|
|
39578
|
+
const pending = [{ value: parsed, depth: 1 }];
|
|
39579
|
+
while (pending.length) {
|
|
39580
|
+
const { value, depth } = pending.pop();
|
|
39581
|
+
if (!value || typeof value !== "object")
|
|
39582
|
+
continue;
|
|
39583
|
+
if (depth > maxDepth)
|
|
39584
|
+
contentLimit("Content hash manifest depth limit exceeded");
|
|
39585
|
+
for (const child of Object.values(value))
|
|
39586
|
+
pending.push({ value: child, depth: depth + 1 });
|
|
39587
|
+
}
|
|
39588
|
+
return parsed;
|
|
39589
|
+
}
|
|
39590
|
+
async function hashContentEntries(entries, options) {
|
|
39591
|
+
const { limits, signal } = contentOptions(options);
|
|
39592
|
+
const deadline = performance.now() + limits.timeoutMs;
|
|
39593
|
+
let terminal;
|
|
39594
|
+
const abort = () => {
|
|
39595
|
+
terminal ??= new ContentHashInputError("CONTENT_HASH_ABORTED", "Content hashing aborted");
|
|
39596
|
+
};
|
|
39597
|
+
const timer = setTimeout(() => {
|
|
39598
|
+
terminal ??= new ContentHashInputError("CONTENT_HASH_TIMEOUT", "Content hashing deadline exceeded");
|
|
39599
|
+
}, limits.timeoutMs);
|
|
39600
|
+
const check = () => {
|
|
39601
|
+
if (signal?.aborted)
|
|
39602
|
+
abort();
|
|
39603
|
+
if (terminal)
|
|
39604
|
+
throw terminal;
|
|
39605
|
+
if (performance.now() >= deadline)
|
|
39606
|
+
throw new ContentHashInputError("CONTENT_HASH_TIMEOUT", "Content hashing deadline exceeded");
|
|
39607
|
+
};
|
|
39608
|
+
try {
|
|
39609
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
39610
|
+
check();
|
|
39611
|
+
const snapshot = snapshotContentEntries(entries, limits, check);
|
|
39612
|
+
const normalized = [];
|
|
39613
|
+
let normalizedBytes = 0;
|
|
39614
|
+
let manifest;
|
|
39615
|
+
await new Promise((resolve3) => setImmediate(resolve3));
|
|
39616
|
+
for (const entry of snapshot) {
|
|
39617
|
+
check();
|
|
39618
|
+
if (!coveredContentPath(entry.path))
|
|
39619
|
+
continue;
|
|
39620
|
+
if (entry.path === "skill.json")
|
|
39621
|
+
manifest = boundedManifest(new TextDecoder().decode(entry.bytes), limits.manifestDepth);
|
|
39622
|
+
const file = normalizeBundleFile(entry.path, entry.bytes);
|
|
39623
|
+
check();
|
|
39624
|
+
if (file.content.byteLength > limits.normalizedFileBytes || normalizedBytes + file.content.byteLength > limits.normalizedBytes)
|
|
39625
|
+
contentLimit("Content hash normalized byte limit exceeded");
|
|
39626
|
+
normalizedBytes += file.content.byteLength;
|
|
39627
|
+
normalized.push(file);
|
|
39628
|
+
await new Promise((resolve3) => setImmediate(resolve3));
|
|
39629
|
+
}
|
|
39630
|
+
normalized.sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
|
|
39631
|
+
check();
|
|
39632
|
+
return { hash: await hashBundleFilesCooperatively(normalized, check), manifest };
|
|
39633
|
+
} catch (error) {
|
|
39634
|
+
if (error instanceof ContentHashInputError)
|
|
39635
|
+
throw error;
|
|
39636
|
+
throw new ContentHashInputError("CONTENT_HASH_INVALID", "Invalid content hash input");
|
|
39637
|
+
} finally {
|
|
39638
|
+
clearTimeout(timer);
|
|
39639
|
+
signal?.removeEventListener("abort", abort);
|
|
39640
|
+
}
|
|
39641
|
+
}
|
|
39642
|
+
async function verifyContentHashFromEntries(entries, options = {}) {
|
|
39643
|
+
const { hash, manifest } = await hashContentEntries(entries, options);
|
|
39644
|
+
const provenance = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? manifest.provenance : undefined;
|
|
39645
|
+
const value = provenance && typeof provenance === "object" && !Array.isArray(provenance) ? provenance.content_hash : undefined;
|
|
39646
|
+
if (value !== undefined && typeof value !== "string")
|
|
39647
|
+
invalidContent("Invalid content hash declaration");
|
|
39648
|
+
const declaredHash = value?.trim() || undefined;
|
|
39649
|
+
if (!declaredHash)
|
|
39650
|
+
return { declared: false, valid: false };
|
|
39651
|
+
if (!/^[a-f0-9]{64}$/.test(declaredHash))
|
|
39652
|
+
return { declared: true, valid: false, declaredHash };
|
|
39653
|
+
return { declared: true, valid: hash === declaredHash, declaredHash, computedHash: hash };
|
|
39654
|
+
}
|
|
39475
39655
|
function verifyContentHash(skillPath, manifest) {
|
|
39476
39656
|
const declaredHash = manifest?.provenance?.content_hash?.trim() || undefined;
|
|
39477
39657
|
if (!declaredHash)
|
|
@@ -39506,7 +39686,7 @@ function hashSkillMarkdown(content) {
|
|
|
39506
39686
|
function hashSkillMarkdownFile(path) {
|
|
39507
39687
|
return hashSkillMarkdown(readFileSync5(path, "utf-8"));
|
|
39508
39688
|
}
|
|
39509
|
-
var CONTENT_HASH_ALGORITHM = "sha256", HASH_EXCLUDE_DIRS, HASH_COVERAGE, CONTENT_HASH_LIMITS, typedArrayPrototype, byteLengthOf, bufferOf;
|
|
39689
|
+
var CONTENT_HASH_ALGORITHM = "sha256", HASH_EXCLUDE_DIRS, HASH_COVERAGE, CONTENT_HASH_LIMITS, ContentHashInputError, typedArrayPrototype, byteLengthOf, bufferOf;
|
|
39510
39690
|
var init_skill_hash = __esm(() => {
|
|
39511
39691
|
HASH_EXCLUDE_DIRS = new Set([".git", "node_modules", "dist", "build", ".turbo"]);
|
|
39512
39692
|
HASH_COVERAGE = [
|
|
@@ -39531,6 +39711,14 @@ var init_skill_hash = __esm(() => {
|
|
|
39531
39711
|
manifestDepth: 64,
|
|
39532
39712
|
timeoutMs: 5000
|
|
39533
39713
|
});
|
|
39714
|
+
ContentHashInputError = class ContentHashInputError extends Error {
|
|
39715
|
+
code;
|
|
39716
|
+
constructor(code, message) {
|
|
39717
|
+
super(message);
|
|
39718
|
+
this.code = code;
|
|
39719
|
+
this.name = "ContentHashInputError";
|
|
39720
|
+
}
|
|
39721
|
+
};
|
|
39534
39722
|
typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
|
|
39535
39723
|
byteLengthOf = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteLength").get;
|
|
39536
39724
|
bufferOf = Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer").get;
|
|
@@ -43841,7 +44029,7 @@ import { isIP as isIP2 } from "net";
|
|
|
43841
44029
|
import { spawnSync } from "child_process";
|
|
43842
44030
|
import { closeSync, fstatSync, openSync, readFileSync as readFileSync11 } from "fs";
|
|
43843
44031
|
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
|
|
43844
|
-
import { createRequire } from "module";
|
|
44032
|
+
import { createRequire as createRequire2 } from "module";
|
|
43845
44033
|
import { hostname as osHostname } from "os";
|
|
43846
44034
|
import { isAbsolute as isAbsolute3, join as join17 } from "path";
|
|
43847
44035
|
function envToken(name) {
|
|
@@ -43962,7 +44150,7 @@ function readAppConfigFile(path) {
|
|
|
43962
44150
|
const uid = process.getuid?.() ?? process.geteuid?.();
|
|
43963
44151
|
if (uid !== undefined && before2.uid !== uid)
|
|
43964
44152
|
unsafe("the file is not owned by the current user");
|
|
43965
|
-
if (before2.size >
|
|
44153
|
+
if (before2.size > MAX_CREDENTIAL_FILE_BYTES2)
|
|
43966
44154
|
unsafe("the file exceeds the size limit");
|
|
43967
44155
|
const bytes = readFileSync11(fd);
|
|
43968
44156
|
const after2 = fstatSync(fd);
|
|
@@ -44046,13 +44234,13 @@ function sealCredential(fields) {
|
|
|
44046
44234
|
configurable: false
|
|
44047
44235
|
});
|
|
44048
44236
|
}
|
|
44049
|
-
Object.defineProperty(sealed,
|
|
44237
|
+
Object.defineProperty(sealed, INSPECT_CUSTOM2, {
|
|
44050
44238
|
value: () => ({ ...visible, apiKey: "[redacted]" }),
|
|
44051
44239
|
enumerable: false,
|
|
44052
44240
|
writable: false,
|
|
44053
44241
|
configurable: false
|
|
44054
44242
|
});
|
|
44055
|
-
Object.defineProperty(sealed,
|
|
44243
|
+
Object.defineProperty(sealed, CREDENTIAL_SEAL2, {
|
|
44056
44244
|
value: true,
|
|
44057
44245
|
enumerable: false,
|
|
44058
44246
|
writable: false,
|
|
@@ -44071,7 +44259,7 @@ function firstEnvValue(env3, keys2) {
|
|
|
44071
44259
|
return null;
|
|
44072
44260
|
}
|
|
44073
44261
|
function isAmbientEnvironment(env3) {
|
|
44074
|
-
return env3 === process.env || env3[
|
|
44262
|
+
return env3 === process.env || env3[AMBIENT_ENVIRONMENT2] === true;
|
|
44075
44263
|
}
|
|
44076
44264
|
function defaultKeychainRunner(argv) {
|
|
44077
44265
|
const result2 = spawnSync(KEYCHAIN_SECURITY_BIN, [...argv], {
|
|
@@ -44165,7 +44353,7 @@ function snapshotClientEnvironment(name, env3) {
|
|
|
44165
44353
|
snapshot[key] = descriptor.value;
|
|
44166
44354
|
}
|
|
44167
44355
|
if (ambient) {
|
|
44168
|
-
Object.defineProperty(snapshot,
|
|
44356
|
+
Object.defineProperty(snapshot, AMBIENT_ENVIRONMENT2, {
|
|
44169
44357
|
value: true,
|
|
44170
44358
|
enumerable: false,
|
|
44171
44359
|
writable: false,
|
|
@@ -44322,7 +44510,7 @@ async function completePointerCredential(name, pointerResolution, env3 = process
|
|
|
44322
44510
|
}
|
|
44323
44511
|
let secretsSdk;
|
|
44324
44512
|
try {
|
|
44325
|
-
secretsSdk =
|
|
44513
|
+
secretsSdk = requireSecretsSdk2(SECRETS_PACKAGE_SPECIFIER2);
|
|
44326
44514
|
} catch {
|
|
44327
44515
|
throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets SDK (@hasna/secrets) is not installed ` + `in this process. A vault pointer is TERMINAL: install @hasna/secrets to resolve it, or unset ${pointerEnvKey}.`, [pointerEnvKey]);
|
|
44328
44516
|
}
|
|
@@ -44468,7 +44656,7 @@ function toV1BaseUrl(apiUrl) {
|
|
|
44468
44656
|
url.pathname = `${path}/v1`;
|
|
44469
44657
|
return url.toString().replace(/\/+$/, "");
|
|
44470
44658
|
}
|
|
44471
|
-
var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4,
|
|
44659
|
+
var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES2, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM2, CREDENTIAL_SEAL2, AMBIENT_ENVIRONMENT2, SECRETS_PACKAGE_SPECIFIER2, requireSecretsSdk2, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, IDEMPOTENT_METHODS2, AUTHORITY_OVERRIDE_HEADERS2;
|
|
44472
44660
|
var init_transport = __esm(() => {
|
|
44473
44661
|
CredentialResolutionError = class CredentialResolutionError extends Error {
|
|
44474
44662
|
appName;
|
|
@@ -44488,21 +44676,21 @@ var init_transport = __esm(() => {
|
|
|
44488
44676
|
this.path = path;
|
|
44489
44677
|
}
|
|
44490
44678
|
};
|
|
44491
|
-
|
|
44679
|
+
MAX_CREDENTIAL_FILE_BYTES2 = 64 * 1024;
|
|
44492
44680
|
SAFE_APP_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
44493
44681
|
SAFE_PROFILE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
|
|
44494
44682
|
ILLEGAL_IN_HEADER_VALUE = /[^\t\x20-\x7e]/;
|
|
44495
44683
|
VAULT_POINTER_SHAPE = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-_.]*){2,}$/;
|
|
44496
44684
|
CREDENTIAL_SHAPED_KEY = /(?:^|_)(?:API_KEY|KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)(?:_|$)/;
|
|
44497
|
-
|
|
44498
|
-
|
|
44499
|
-
|
|
44500
|
-
|
|
44501
|
-
|
|
44685
|
+
INSPECT_CUSTOM2 = Symbol.for("nodejs.util.inspect.custom");
|
|
44686
|
+
CREDENTIAL_SEAL2 = Symbol.for("hasna:contracts:sealedCredential");
|
|
44687
|
+
AMBIENT_ENVIRONMENT2 = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
44688
|
+
SECRETS_PACKAGE_SPECIFIER2 = "@hasna/" + "secrets";
|
|
44689
|
+
requireSecretsSdk2 = createRequire2(import.meta.url);
|
|
44502
44690
|
ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
|
|
44503
44691
|
DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
44504
|
-
|
|
44505
|
-
|
|
44692
|
+
IDEMPOTENT_METHODS2 = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
44693
|
+
AUTHORITY_OVERRIDE_HEADERS2 = new Set([
|
|
44506
44694
|
"host",
|
|
44507
44695
|
":authority",
|
|
44508
44696
|
"forwarded",
|
|
@@ -44634,6 +44822,7 @@ __export(exports_fleet_credentials, {
|
|
|
44634
44822
|
skillsCredentialOrReason: () => skillsCredentialOrReason,
|
|
44635
44823
|
skillsCredentialFiles: () => skillsCredentialFiles,
|
|
44636
44824
|
skillsCredentialFilePath: () => skillsCredentialFilePath,
|
|
44825
|
+
skillsApiRequestUrl: () => skillsApiRequestUrl,
|
|
44637
44826
|
selectsSkillsLocalMode: () => selectsSkillsLocalMode,
|
|
44638
44827
|
resolveSkillsFleet: () => resolveSkillsFleet,
|
|
44639
44828
|
resolveSkillsConnection: () => resolveSkillsConnection,
|
|
@@ -44674,7 +44863,9 @@ function normalizeSkillsApiOrigin(apiUrl) {
|
|
|
44674
44863
|
throw new SkillsFleetCredentialError("A Skills API URL must use HTTPS (or loopback HTTP), without credentials, query or fragment", "INVALID_API_URL");
|
|
44675
44864
|
}
|
|
44676
44865
|
const pathname = url.pathname.replace(/\/+$/, "");
|
|
44677
|
-
if (
|
|
44866
|
+
if (url.origin === "https://api.hasna.com" && pathname === "/skills/v1") {
|
|
44867
|
+
url.pathname = "/skills";
|
|
44868
|
+
} else if (pathname === "/api" || pathname === "/api/v1") {
|
|
44678
44869
|
url.pathname = "/";
|
|
44679
44870
|
} else if (pathname.endsWith("/api/v1")) {
|
|
44680
44871
|
url.pathname = pathname.slice(0, -"/api/v1".length) || "/";
|
|
@@ -44683,6 +44874,21 @@ function normalizeSkillsApiOrigin(apiUrl) {
|
|
|
44683
44874
|
}
|
|
44684
44875
|
return url.toString().replace(/\/+$/, "");
|
|
44685
44876
|
}
|
|
44877
|
+
function skillsApiRequestUrl(apiUrl, route) {
|
|
44878
|
+
const origin = normalizeSkillsApiOrigin(apiUrl);
|
|
44879
|
+
if (!route.startsWith("/api/") || route.includes("#") || route.includes("\\")) {
|
|
44880
|
+
throw new SkillsFleetCredentialError("Invalid Skills API route", "INVALID_API_URL");
|
|
44881
|
+
}
|
|
44882
|
+
if (origin === "https://api.hasna.com/skills") {
|
|
44883
|
+
if (route === "/api/auth/whoami")
|
|
44884
|
+
return `${origin}/v1/auth/whoami`;
|
|
44885
|
+
if (!route.startsWith("/api/v1/")) {
|
|
44886
|
+
throw new SkillsFleetCredentialError("The internal Skills gateway has no established login contract yet. Select an explicitly configured instance with supported authentication.", "GATEWAY_AUTH_UNAVAILABLE");
|
|
44887
|
+
}
|
|
44888
|
+
return `${origin}${route.slice("/api".length)}`;
|
|
44889
|
+
}
|
|
44890
|
+
return `${origin}${route}`;
|
|
44891
|
+
}
|
|
44686
44892
|
function configuredSkillsApiUrl(env3 = process.env, keychain, profile) {
|
|
44687
44893
|
const declared = SKILLS_API_URL_ENV_KEYS.filter((key) => env3[key] !== undefined).map((key) => ({ key, value: env3[key] }));
|
|
44688
44894
|
for (const entry of declared) {
|
|
@@ -48903,7 +49109,14 @@ function getConfiguredApiUrl(env3 = process.env) {
|
|
|
48903
49109
|
}
|
|
48904
49110
|
function buildSkillsApiUrl(apiUrl, endpoint = "/skills") {
|
|
48905
49111
|
const url = new URL(apiUrl);
|
|
49112
|
+
if (url.origin === "https://api.hasna.com" && /^\/skills\/(?:api\/)?v1\/skills\/?$/.test(url.pathname)) {
|
|
49113
|
+
url.pathname = "/skills";
|
|
49114
|
+
apiUrl = url.toString();
|
|
49115
|
+
}
|
|
48906
49116
|
const cleanEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
|
|
49117
|
+
if (normalizeSkillsApiOrigin(apiUrl) === "https://api.hasna.com/skills") {
|
|
49118
|
+
return skillsApiRequestUrl(apiUrl, `/api/v1${cleanEndpoint}`);
|
|
49119
|
+
}
|
|
48907
49120
|
const pathname = url.pathname.replace(/\/+$/, "");
|
|
48908
49121
|
const apiBase = /\/api(?:\/v1)?\/skills$/.test(pathname) ? pathname.slice(0, -"/skills".length) : pathname;
|
|
48909
49122
|
if (/\/api(?:\/v1)?$/.test(apiBase)) {
|
|
@@ -49744,8 +49957,17 @@ function creditCount(value) {
|
|
|
49744
49957
|
}
|
|
49745
49958
|
return value;
|
|
49746
49959
|
}
|
|
49960
|
+
function runQuoteReceipt(value) {
|
|
49961
|
+
if (value === undefined)
|
|
49962
|
+
return;
|
|
49963
|
+
if (typeof value !== "string" || !value.length || Buffer.byteLength(value, "utf8") > 4096) {
|
|
49964
|
+
throw new Error("Invalid quote receipt");
|
|
49965
|
+
}
|
|
49966
|
+
return value;
|
|
49967
|
+
}
|
|
49747
49968
|
function parseRemoteRunQuote(value) {
|
|
49748
49969
|
const quote = object(value);
|
|
49970
|
+
runQuoteReceipt(quote.quoteReceipt);
|
|
49749
49971
|
const pricing = object(quote.pricing);
|
|
49750
49972
|
if (typeof quote.skill !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(quote.skill))
|
|
49751
49973
|
throw new Error("Invalid quoted skill");
|
|
@@ -49931,6 +50153,72 @@ function parseUpdatedWorkspace(value) {
|
|
|
49931
50153
|
return { organization: { id: organization2.id, slug: organization2.slug, name: organization2.name } };
|
|
49932
50154
|
}
|
|
49933
50155
|
|
|
50156
|
+
// src/lib/remote-quote-errors.ts
|
|
50157
|
+
async function readQuoteUnavailableCode(response) {
|
|
50158
|
+
const maximum = 16 * 1024;
|
|
50159
|
+
const length = response.headers.get("content-length");
|
|
50160
|
+
if (response.status !== 503 || response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() !== "application/json" || length !== null && (!/^\d+$/.test(length) || Number(length) > maximum)) {
|
|
50161
|
+
response.body?.cancel().catch(() => {});
|
|
50162
|
+
return null;
|
|
50163
|
+
}
|
|
50164
|
+
const reader = response.body?.getReader();
|
|
50165
|
+
if (!reader)
|
|
50166
|
+
return null;
|
|
50167
|
+
let timer;
|
|
50168
|
+
let deadlineExceeded = false;
|
|
50169
|
+
const expired = Symbol("quote body deadline");
|
|
50170
|
+
const deadline = new Promise((resolve3) => {
|
|
50171
|
+
timer = setTimeout(() => {
|
|
50172
|
+
deadlineExceeded = true;
|
|
50173
|
+
resolve3(expired);
|
|
50174
|
+
reader.cancel().catch(() => {});
|
|
50175
|
+
}, 1500);
|
|
50176
|
+
});
|
|
50177
|
+
const chunks = [];
|
|
50178
|
+
let size2 = 0;
|
|
50179
|
+
try {
|
|
50180
|
+
while (true) {
|
|
50181
|
+
const next = await Promise.race([reader.read(), deadline]);
|
|
50182
|
+
if (next === expired || deadlineExceeded)
|
|
50183
|
+
return null;
|
|
50184
|
+
if (next.done)
|
|
50185
|
+
break;
|
|
50186
|
+
size2 += next.value.byteLength;
|
|
50187
|
+
if (size2 > maximum)
|
|
50188
|
+
return null;
|
|
50189
|
+
chunks.push(next.value);
|
|
50190
|
+
}
|
|
50191
|
+
const bytes = new Uint8Array(size2);
|
|
50192
|
+
let offset = 0;
|
|
50193
|
+
for (const chunk2 of chunks) {
|
|
50194
|
+
bytes.set(chunk2, offset);
|
|
50195
|
+
offset += chunk2.byteLength;
|
|
50196
|
+
}
|
|
50197
|
+
const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
50198
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
50199
|
+
return null;
|
|
50200
|
+
const code = value.code;
|
|
50201
|
+
return typeof code === "string" && Object.hasOwn(quoteUnavailableMessages, code) ? code : null;
|
|
50202
|
+
} catch {
|
|
50203
|
+
return null;
|
|
50204
|
+
} finally {
|
|
50205
|
+
clearTimeout(timer);
|
|
50206
|
+
reader.cancel().catch(() => {});
|
|
50207
|
+
reader.releaseLock();
|
|
50208
|
+
}
|
|
50209
|
+
}
|
|
50210
|
+
var quoteUnavailableMessages;
|
|
50211
|
+
var init_remote_quote_errors = __esm(() => {
|
|
50212
|
+
quoteUnavailableMessages = Object.freeze({
|
|
50213
|
+
HOSTED_PROVIDER_UNAVAILABLE: "Hosted execution is temporarily unavailable on this Skills instance.",
|
|
50214
|
+
HOSTED_CONNECTORS_UNAVAILABLE: "Hosted connector execution is unavailable on this Skills instance.",
|
|
50215
|
+
SKILL_IMPLEMENTATION_UNAVAILABLE: "This skill has no hosted execution implementation.",
|
|
50216
|
+
HOSTED_PRICING_UNAVAILABLE: "Hosted execution is unavailable while this skill's pricing is reviewed.",
|
|
50217
|
+
RUNTIME_ALLOWLIST_REQUIRED: "Hosted execution is unavailable until this Skills instance enables its skill catalog.",
|
|
50218
|
+
RUNTIME_SKILL_NOT_ALLOWED: "This skill is not enabled for hosted execution on this Skills instance."
|
|
50219
|
+
});
|
|
50220
|
+
});
|
|
50221
|
+
|
|
49934
50222
|
// src/lib/remote-client.ts
|
|
49935
50223
|
var exports_remote_client = {};
|
|
49936
50224
|
__export(exports_remote_client, {
|
|
@@ -49941,6 +50229,7 @@ __export(exports_remote_client, {
|
|
|
49941
50229
|
RemoteSkillsClient: () => RemoteSkillsClient,
|
|
49942
50230
|
RemoteRouteUnsupportedError: () => RemoteRouteUnsupportedError,
|
|
49943
50231
|
RemoteRequestError: () => RemoteRequestError,
|
|
50232
|
+
RemoteQuoteUnavailableError: () => RemoteQuoteUnavailableError,
|
|
49944
50233
|
RemoteCapabilityUnavailableError: () => RemoteCapabilityUnavailableError
|
|
49945
50234
|
});
|
|
49946
50235
|
|
|
@@ -49953,7 +50242,7 @@ class RemoteSkillsClient {
|
|
|
49953
50242
|
this.apiUrl = normalizeSkillsApiOrigin(apiUrl);
|
|
49954
50243
|
}
|
|
49955
50244
|
async request(path, options) {
|
|
49956
|
-
return fetch(
|
|
50245
|
+
return fetch(skillsApiRequestUrl(this.apiUrl, path), {
|
|
49957
50246
|
...options,
|
|
49958
50247
|
redirect: "error",
|
|
49959
50248
|
credentials: "omit",
|
|
@@ -49976,6 +50265,11 @@ class RemoteSkillsClient {
|
|
|
49976
50265
|
throw new RemoteRouteUnsupportedError(routePath, response.status, this.apiUrl);
|
|
49977
50266
|
}
|
|
49978
50267
|
if (!response.ok) {
|
|
50268
|
+
if (opts.quoteRefusal && options?.method === "POST" && /^\/api\/v1\/skills\/[^/?#]+\/quote$/.test(routePath) && response.status === 503) {
|
|
50269
|
+
const code = await readQuoteUnavailableCode(response);
|
|
50270
|
+
if (code)
|
|
50271
|
+
throw new RemoteQuoteUnavailableError(routePath, code);
|
|
50272
|
+
}
|
|
49979
50273
|
if (path === "/api/v1/billing/checkout" && options?.method === "POST" && response.status === 503 && await responseBodyCarriesCode(response, ["SUBSCRIPTION_CHECKOUT_UNAVAILABLE"])) {
|
|
49980
50274
|
throw new RemoteCapabilityUnavailableError;
|
|
49981
50275
|
}
|
|
@@ -50008,6 +50302,7 @@ class RemoteSkillsClient {
|
|
|
50008
50302
|
return { status: res.status, body };
|
|
50009
50303
|
}
|
|
50010
50304
|
async submitRun(slug, input, args, approval = {}) {
|
|
50305
|
+
const quoteReceipt = runQuoteReceipt(approval.quoteReceipt);
|
|
50011
50306
|
if (approval.idempotencyKey !== undefined && !/^[A-Za-z0-9._:-]{1,128}$/.test(approval.idempotencyKey))
|
|
50012
50307
|
throw new Error("Idempotency key must be 1-128 URL-safe characters");
|
|
50013
50308
|
if (approval.maxCostCents !== undefined)
|
|
@@ -50024,16 +50319,21 @@ class RemoteSkillsClient {
|
|
|
50024
50319
|
...approval.maxCredits !== undefined ? { maxCredits: approval.maxCredits } : {},
|
|
50025
50320
|
...approval.maxCostCents !== undefined ? { maxCostCents: approval.maxCostCents } : {},
|
|
50026
50321
|
...approval.idempotencyKey !== undefined ? { idempotencyKey: approval.idempotencyKey } : {},
|
|
50027
|
-
...approval.inputFiles !== undefined ? { files: approval.inputFiles } : {}
|
|
50322
|
+
...approval.inputFiles !== undefined ? { files: approval.inputFiles } : {},
|
|
50323
|
+
...quoteReceipt !== undefined ? { quoteReceipt } : {}
|
|
50028
50324
|
})
|
|
50029
50325
|
});
|
|
50326
|
+
if (!res.ok) {
|
|
50327
|
+
res.body?.cancel().catch(() => {});
|
|
50328
|
+
throw new RemoteRequestError(`/api/v1/runs/${encodeURIComponent(slug)}`, res.status);
|
|
50329
|
+
}
|
|
50030
50330
|
return normalizeRemoteSkillRunContract(await res.json(), slug);
|
|
50031
50331
|
}
|
|
50032
|
-
async quoteRun(slug, input = {}, args = []) {
|
|
50332
|
+
async quoteRun(slug, input = {}, args = [], files) {
|
|
50033
50333
|
const response = await this.requestNewRoute(`/api/v1/skills/${encodeURIComponent(slug)}/quote`, {
|
|
50034
50334
|
method: "POST",
|
|
50035
|
-
body: JSON.stringify({ input, args })
|
|
50036
|
-
});
|
|
50335
|
+
body: JSON.stringify({ input, args, ...files === undefined ? {} : { files } })
|
|
50336
|
+
}, { quoteRefusal: true });
|
|
50037
50337
|
return parseRemoteRunQuote(await response.json());
|
|
50038
50338
|
}
|
|
50039
50339
|
getCapabilities() {
|
|
@@ -50048,17 +50348,24 @@ class RemoteSkillsClient {
|
|
|
50048
50348
|
return this.capabilities;
|
|
50049
50349
|
}
|
|
50050
50350
|
async submitQuotedRun(slug, input = {}, args = [], approval = {}) {
|
|
50351
|
+
runQuoteReceipt(approval.quoteReceipt);
|
|
50352
|
+
({ input, args, approval } = JSON.parse(JSON.stringify({ input, args, approval })));
|
|
50051
50353
|
const maximum = creditCount(approval.maxCredits ?? approval.maxCostCents ?? 0);
|
|
50052
50354
|
if (approval.maxCostCents !== undefined && approval.maxCostCents !== maximum)
|
|
50053
50355
|
throw new Error("Credit approval fields disagree");
|
|
50054
|
-
const quote = await this.quoteRun(slug, input, args);
|
|
50055
|
-
if (quote.pricing.costCents > maximum)
|
|
50356
|
+
const quote = approval.quoteReceipt === undefined ? await this.quoteRun(slug, input, args, approval.inputFiles?.length ? approval.inputFiles : undefined) : undefined;
|
|
50357
|
+
if (quote && quote.pricing.costCents > maximum)
|
|
50056
50358
|
throw new RemoteCreditApprovalError(quote.pricing.costCents, maximum);
|
|
50057
50359
|
const capabilities = await this.getCapabilities();
|
|
50058
50360
|
if (!capabilities.capabilities.includes("runs.submit") || capabilities.billing?.boundedRunApproval !== true || capabilities.billing.unit !== "credits") {
|
|
50059
50361
|
throw new Error("The configured server does not support bounded credit approval; refusing remote submission");
|
|
50060
50362
|
}
|
|
50061
|
-
return this.submitRun(quote
|
|
50363
|
+
return this.submitRun(quote?.skill ?? slug, input, args, {
|
|
50364
|
+
...approval,
|
|
50365
|
+
maxCredits: maximum,
|
|
50366
|
+
maxCostCents: maximum,
|
|
50367
|
+
...(quote?.quoteReceipt ?? approval.quoteReceipt) === undefined ? {} : { quoteReceipt: quote?.quoteReceipt ?? approval.quoteReceipt }
|
|
50368
|
+
});
|
|
50062
50369
|
}
|
|
50063
50370
|
async getIdentity() {
|
|
50064
50371
|
return (await this.requestNewRoute("/api/auth/whoami")).json();
|
|
@@ -50346,6 +50653,10 @@ class RemoteSkillsClient {
|
|
|
50346
50653
|
return { id: artifactId, fileName: String(artifact.fileName ?? artifactId), bytes, byteSize: bytes.byteLength, sha256: artifact.sha256 };
|
|
50347
50654
|
}
|
|
50348
50655
|
async submitQuotedRunWithFiles(slug, input, args, files, approval = {}) {
|
|
50656
|
+
runQuoteReceipt(approval.quoteReceipt);
|
|
50657
|
+
({ input, args, approval } = JSON.parse(JSON.stringify({ input, args, approval })));
|
|
50658
|
+
describeRemoteFiles(files);
|
|
50659
|
+
files = files.map((file) => ({ name: file.name, contentType: file.contentType, bytes: new Uint8Array(file.bytes) }));
|
|
50349
50660
|
const inputFiles = describeRemoteFiles(files);
|
|
50350
50661
|
if (files.length && !(await this.getCapabilities()).capabilities.includes("runs.uploads"))
|
|
50351
50662
|
throw new Error("The configured server does not support input uploads");
|
|
@@ -50409,7 +50720,7 @@ class RemoteSkillsClient {
|
|
|
50409
50720
|
const headers = { Authorization: `Bearer ${this.apiKey}` };
|
|
50410
50721
|
if (ifMatch)
|
|
50411
50722
|
headers["If-Match"] = ifMatch;
|
|
50412
|
-
return fetch(
|
|
50723
|
+
return fetch(skillsApiRequestUrl(this.apiUrl, "/api/v1/skills"), {
|
|
50413
50724
|
method: "POST",
|
|
50414
50725
|
headers,
|
|
50415
50726
|
body: form,
|
|
@@ -50632,7 +50943,7 @@ async function createRemoteSkillsClient(env3 = process.env) {
|
|
|
50632
50943
|
function createRemoteSkillsClientReadOnly(env3 = process.env) {
|
|
50633
50944
|
return createRemoteSkillsClient(env3);
|
|
50634
50945
|
}
|
|
50635
|
-
var RemoteRouteUnsupportedError, RemoteRequestError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
|
|
50946
|
+
var RemoteRouteUnsupportedError, RemoteRequestError, RemoteQuoteUnavailableError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
|
|
50636
50947
|
var init_remote_client = __esm(() => {
|
|
50637
50948
|
init_remote_invitations();
|
|
50638
50949
|
init_remote_workspace_leave();
|
|
@@ -50643,6 +50954,7 @@ var init_remote_client = __esm(() => {
|
|
|
50643
50954
|
init_fleet_credentials();
|
|
50644
50955
|
init_remote_account();
|
|
50645
50956
|
init_remote_files();
|
|
50957
|
+
init_remote_quote_errors();
|
|
50646
50958
|
RemoteRouteUnsupportedError = class RemoteRouteUnsupportedError extends Error {
|
|
50647
50959
|
path;
|
|
50648
50960
|
status;
|
|
@@ -50665,6 +50977,17 @@ var init_remote_client = __esm(() => {
|
|
|
50665
50977
|
this.name = "RemoteRequestError";
|
|
50666
50978
|
}
|
|
50667
50979
|
};
|
|
50980
|
+
RemoteQuoteUnavailableError = class RemoteQuoteUnavailableError extends RemoteRequestError {
|
|
50981
|
+
code;
|
|
50982
|
+
constructor(path, code) {
|
|
50983
|
+
super(path, 503);
|
|
50984
|
+
this.code = code;
|
|
50985
|
+
if (!Object.hasOwn(quoteUnavailableMessages, code))
|
|
50986
|
+
throw new Error("Unknown quote refusal code");
|
|
50987
|
+
this.name = "RemoteQuoteUnavailableError";
|
|
50988
|
+
this.message = quoteUnavailableMessages[code];
|
|
50989
|
+
}
|
|
50990
|
+
};
|
|
50668
50991
|
RemoteWorkspaceMemberError = class RemoteWorkspaceMemberError extends RemoteRequestError {
|
|
50669
50992
|
code;
|
|
50670
50993
|
constructor(path, code) {
|
|
@@ -50720,6 +51043,7 @@ var init_revision = __esm(() => {
|
|
|
50720
51043
|
import { createHash as createHash4 } from "crypto";
|
|
50721
51044
|
import { readFileSync as readFileSync13, readdirSync as readdirSync9, statSync as statSync10 } from "fs";
|
|
50722
51045
|
import { join as join19, relative as relative2 } from "path";
|
|
51046
|
+
import { createGunzip } from "zlib";
|
|
50723
51047
|
function isDotenvFile(lower) {
|
|
50724
51048
|
if (lower === ".env" || lower.startsWith(".env."))
|
|
50725
51049
|
return true;
|
|
@@ -50922,6 +51246,88 @@ function concat2(chunks) {
|
|
|
50922
51246
|
function invalidBundle(message) {
|
|
50923
51247
|
throw new SkillBundleInspectionError("BUNDLE_INVALID", message);
|
|
50924
51248
|
}
|
|
51249
|
+
function inspectionLimits(options) {
|
|
51250
|
+
const limits = { ...SKILL_BUNDLE_INSPECTION_LIMITS };
|
|
51251
|
+
for (const key of Object.keys(options.limits ?? {})) {
|
|
51252
|
+
if (!Object.hasOwn(limits, key))
|
|
51253
|
+
throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Unknown bundle limit");
|
|
51254
|
+
const field = key;
|
|
51255
|
+
const value = options.limits[field];
|
|
51256
|
+
if (!Number.isSafeInteger(value) || value <= 0 || value > limits[field]) {
|
|
51257
|
+
throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle limits must be positive integers within the hard ceilings");
|
|
51258
|
+
}
|
|
51259
|
+
limits[field] = value;
|
|
51260
|
+
}
|
|
51261
|
+
return limits;
|
|
51262
|
+
}
|
|
51263
|
+
async function inspectSkillBundle(bundle, options = {}) {
|
|
51264
|
+
const signal = options.signal;
|
|
51265
|
+
const limits = inspectionLimits(options);
|
|
51266
|
+
const deadline = performance.now() + limits.timeoutMs;
|
|
51267
|
+
const check = () => {
|
|
51268
|
+
if (signal?.aborted)
|
|
51269
|
+
throw new SkillBundleInspectionError("BUNDLE_ABORTED", "Bundle inspection aborted");
|
|
51270
|
+
if (performance.now() >= deadline)
|
|
51271
|
+
throw new SkillBundleInspectionError("BUNDLE_TIMEOUT", "Bundle inspection deadline exceeded");
|
|
51272
|
+
};
|
|
51273
|
+
check();
|
|
51274
|
+
if (bundle.byteLength > limits.compressedBytes)
|
|
51275
|
+
throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Compressed bundle exceeds byte limit");
|
|
51276
|
+
const snapshot = ownBytes(bundle);
|
|
51277
|
+
check();
|
|
51278
|
+
const sha2562 = sha256Hex(snapshot);
|
|
51279
|
+
check();
|
|
51280
|
+
const parser = new BoundedTarReader(limits, check);
|
|
51281
|
+
const streamOptions = { chunkSize: 16 * 1024, highWaterMark: 16 * 1024 };
|
|
51282
|
+
const decoder = createGunzip(streamOptions);
|
|
51283
|
+
let terminalError;
|
|
51284
|
+
const stop = (code) => {
|
|
51285
|
+
terminalError ??= new SkillBundleInspectionError(code, code === "BUNDLE_ABORTED" ? "Bundle inspection aborted" : "Bundle inspection deadline exceeded");
|
|
51286
|
+
decoder.destroy(terminalError);
|
|
51287
|
+
};
|
|
51288
|
+
const onAbort = () => stop("BUNDLE_ABORTED");
|
|
51289
|
+
const timer = setTimeout(() => stop("BUNDLE_TIMEOUT"), Math.max(1, deadline - performance.now()));
|
|
51290
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
51291
|
+
let decompressedByteSize = 0;
|
|
51292
|
+
let bytesSinceYield = 0;
|
|
51293
|
+
try {
|
|
51294
|
+
check();
|
|
51295
|
+
decoder.end(snapshot);
|
|
51296
|
+
for await (const chunk2 of decoder) {
|
|
51297
|
+
check();
|
|
51298
|
+
decompressedByteSize += chunk2.byteLength;
|
|
51299
|
+
if (decompressedByteSize > limits.decompressedBytes)
|
|
51300
|
+
throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Decompressed bundle exceeds byte limit");
|
|
51301
|
+
parser.push(chunk2);
|
|
51302
|
+
bytesSinceYield += chunk2.byteLength;
|
|
51303
|
+
if (bytesSinceYield >= 256 * 1024) {
|
|
51304
|
+
await new Promise((resolve3) => setTimeout(resolve3, 0));
|
|
51305
|
+
bytesSinceYield = 0;
|
|
51306
|
+
check();
|
|
51307
|
+
}
|
|
51308
|
+
}
|
|
51309
|
+
check();
|
|
51310
|
+
const entries = parser.finish();
|
|
51311
|
+
return {
|
|
51312
|
+
entries,
|
|
51313
|
+
sha256: sha2562,
|
|
51314
|
+
compressedByteSize: snapshot.byteLength,
|
|
51315
|
+
decompressedByteSize,
|
|
51316
|
+
unpackedByteSize: parser.fileBytes,
|
|
51317
|
+
fileCount: entries.length
|
|
51318
|
+
};
|
|
51319
|
+
} catch (error) {
|
|
51320
|
+
if (terminalError)
|
|
51321
|
+
throw terminalError;
|
|
51322
|
+
if (error instanceof SkillBundleInspectionError)
|
|
51323
|
+
throw error;
|
|
51324
|
+
throw new SkillBundleInspectionError("BUNDLE_INVALID", "Invalid or truncated gzip bundle");
|
|
51325
|
+
} finally {
|
|
51326
|
+
clearTimeout(timer);
|
|
51327
|
+
signal?.removeEventListener("abort", onAbort);
|
|
51328
|
+
decoder.destroy();
|
|
51329
|
+
}
|
|
51330
|
+
}
|
|
50925
51331
|
|
|
50926
51332
|
class BoundedTarReader {
|
|
50927
51333
|
limits;
|
|
@@ -53795,7 +54201,7 @@ var init_init = __esm(() => {
|
|
|
53795
54201
|
});
|
|
53796
54202
|
|
|
53797
54203
|
// src/lib/home-adoption.ts
|
|
53798
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync9, readdirSync as readdirSync10, readFileSync as readFileSync18, rmSync as rmSync6, statSync as statSync11, writeFileSync as writeFileSync10 } from "fs";
|
|
54204
|
+
import { constants as constants3, closeSync as closeSync3, fstatSync as fstatSync3, lstatSync as lstatSync5, openSync as openSync3, readSync as readSync2, existsSync as existsSync22, mkdirSync as mkdirSync9, readdirSync as readdirSync10, readFileSync as readFileSync18, rmSync as rmSync6, statSync as statSync11, writeFileSync as writeFileSync10 } from "fs";
|
|
53799
54205
|
import { homedir as homedir5 } from "os";
|
|
53800
54206
|
import { join as join24 } from "path";
|
|
53801
54207
|
function indexCanonicalCorpus(corpusRoot) {
|
|
@@ -53947,6 +54353,41 @@ function adoptUnmarkedHomes(options = {}) {
|
|
|
53947
54353
|
}
|
|
53948
54354
|
return { ...scan, applied: true, rollbackFile };
|
|
53949
54355
|
}
|
|
54356
|
+
function pruneOwnership(dir) {
|
|
54357
|
+
let descriptor;
|
|
54358
|
+
try {
|
|
54359
|
+
const directory = lstatSync5(dir);
|
|
54360
|
+
if (!directory.isDirectory())
|
|
54361
|
+
return;
|
|
54362
|
+
const markerPath = join24(dir, SYNC_MARKER_FILE), markerPathBefore = lstatSync5(markerPath);
|
|
54363
|
+
if (!markerPathBefore.isFile())
|
|
54364
|
+
return;
|
|
54365
|
+
descriptor = openSync3(markerPath, constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK);
|
|
54366
|
+
const before2 = fstatSync3(descriptor);
|
|
54367
|
+
if (!before2.isFile() || before2.dev !== markerPathBefore.dev || before2.ino !== markerPathBefore.ino || before2.size !== markerPathBefore.size || before2.mtimeMs !== markerPathBefore.mtimeMs || before2.ctimeMs !== markerPathBefore.ctimeMs || before2.size < 1 || before2.size > 65536)
|
|
54368
|
+
return;
|
|
54369
|
+
const bytes = Buffer.alloc(before2.size + 1);
|
|
54370
|
+
let length = 0;
|
|
54371
|
+
while (length < bytes.length) {
|
|
54372
|
+
const count = readSync2(descriptor, bytes, length, bytes.length - length, length);
|
|
54373
|
+
if (!count)
|
|
54374
|
+
break;
|
|
54375
|
+
length += count;
|
|
54376
|
+
}
|
|
54377
|
+
const after2 = fstatSync3(descriptor), current = lstatSync5(dir), markerPathAfter = lstatSync5(markerPath);
|
|
54378
|
+
if (!markerPathAfter.isFile() || markerPathAfter.dev !== before2.dev || markerPathAfter.ino !== before2.ino || markerPathAfter.size !== before2.size || markerPathAfter.mtimeMs !== before2.mtimeMs || markerPathAfter.ctimeMs !== before2.ctimeMs || length !== before2.size || after2.size !== before2.size || after2.mtimeMs !== before2.mtimeMs || after2.ctimeMs !== before2.ctimeMs || !current.isDirectory() || current.dev !== directory.dev || current.ino !== directory.ino)
|
|
54379
|
+
return;
|
|
54380
|
+
const text2 = bytes.subarray(0, length).toString("utf8"), marker = JSON.parse(text2);
|
|
54381
|
+
if (!isSkillsOwnershipMarker(marker))
|
|
54382
|
+
return;
|
|
54383
|
+
return { marker, identity: JSON.stringify([directory.dev, directory.ino, before2.dev, before2.ino, before2.mtimeMs, before2.ctimeMs, text2]) };
|
|
54384
|
+
} catch {
|
|
54385
|
+
return;
|
|
54386
|
+
} finally {
|
|
54387
|
+
if (descriptor !== undefined)
|
|
54388
|
+
closeSync3(descriptor);
|
|
54389
|
+
}
|
|
54390
|
+
}
|
|
53950
54391
|
function pruneStrayHomes(options = {}) {
|
|
53951
54392
|
const homeDir2 = options.homeDir ?? homedir5();
|
|
53952
54393
|
const corpusRoot = resolveCorpusRoot(options);
|
|
@@ -53954,6 +54395,7 @@ function pruneStrayHomes(options = {}) {
|
|
|
53954
54395
|
const agents = options.agents?.length ? options.agents : [...SYNC_AGENTS];
|
|
53955
54396
|
const names = selectedHomeNames(options, index, homeDir2, agents);
|
|
53956
54397
|
const candidates = [];
|
|
54398
|
+
const ownership = new Map;
|
|
53957
54399
|
for (const agent of agents) {
|
|
53958
54400
|
const home = agentGlobalSkillsDir(agent, homeDir2);
|
|
53959
54401
|
if (!existsSync22(home))
|
|
@@ -53976,20 +54418,13 @@ function pruneStrayHomes(options = {}) {
|
|
|
53976
54418
|
} catch {
|
|
53977
54419
|
continue;
|
|
53978
54420
|
}
|
|
53979
|
-
const markerPath = join24(dir, SYNC_MARKER_FILE);
|
|
53980
|
-
if (!existsSync22(markerPath))
|
|
53981
|
-
continue;
|
|
53982
54421
|
if (index.has(skill))
|
|
53983
54422
|
continue;
|
|
53984
|
-
|
|
53985
|
-
|
|
53986
|
-
const parsed = JSON.parse(readFileSync18(markerPath, "utf-8"));
|
|
53987
|
-
if (!parsed || typeof parsed !== "object" || parsed.managedBy !== SYNC_MARKER_MANAGED_BY)
|
|
53988
|
-
continue;
|
|
53989
|
-
marker = parsed;
|
|
53990
|
-
} catch {
|
|
54423
|
+
const owned = pruneOwnership(dir);
|
|
54424
|
+
if (!owned)
|
|
53991
54425
|
continue;
|
|
53992
|
-
|
|
54426
|
+
const marker = owned.marker;
|
|
54427
|
+
ownership.set(dir, owned.identity);
|
|
53993
54428
|
const skillMdPath = join24(dir, "SKILL.md");
|
|
53994
54429
|
const hash = existsSync22(skillMdPath) ? hashSkillMarkdownFile(skillMdPath) : "";
|
|
53995
54430
|
candidates.push({ agent, skill, home, path: dir, hash, marker });
|
|
@@ -54000,10 +54435,14 @@ function pruneStrayHomes(options = {}) {
|
|
|
54000
54435
|
}
|
|
54001
54436
|
const appDir = options.homeDir ? join24(options.homeDir, ".hasna", "skills") : getDataDir();
|
|
54002
54437
|
const rollbackFile = writeRollbackRecord("prune", candidates.map(({ agent, skill, path, hash, marker }) => ({ agent, skill, path, hash, marker })), appDir);
|
|
54438
|
+
let pruned = 0;
|
|
54003
54439
|
for (const candidate of candidates) {
|
|
54440
|
+
if (pruneOwnership(candidate.path)?.identity !== ownership.get(candidate.path))
|
|
54441
|
+
continue;
|
|
54004
54442
|
rmSync6(candidate.path, { recursive: true, force: true });
|
|
54443
|
+
pruned++;
|
|
54005
54444
|
}
|
|
54006
|
-
return { candidates, pruned
|
|
54445
|
+
return { candidates, pruned, dryRun: false, rollbackFile };
|
|
54007
54446
|
}
|
|
54008
54447
|
var CONFLICTS_LEDGER_FILE = "conflicts.json", ROLLBACK_DIRNAME = "rollback";
|
|
54009
54448
|
var init_home_adoption = __esm(() => {
|
|
@@ -54131,7 +54570,7 @@ var init_home_census = __esm(() => {
|
|
|
54131
54570
|
});
|
|
54132
54571
|
|
|
54133
54572
|
// src/cli/env-assignment.ts
|
|
54134
|
-
import { closeSync as
|
|
54573
|
+
import { closeSync as closeSync4, constants as constants4, fstatSync as fstatSync4, ftruncateSync, lstatSync as lstatSync6, openSync as openSync4, readFileSync as readFileSync20, writeSync } from "fs";
|
|
54135
54574
|
function serializeValue(value) {
|
|
54136
54575
|
if (controls.test(value))
|
|
54137
54576
|
throw new EnvAssignmentError("Environment values cannot contain control characters or newlines.");
|
|
@@ -54210,7 +54649,7 @@ function setEnvAssignment(path, assignment) {
|
|
|
54210
54649
|
try {
|
|
54211
54650
|
const previous = (() => {
|
|
54212
54651
|
try {
|
|
54213
|
-
return
|
|
54652
|
+
return lstatSync6(path);
|
|
54214
54653
|
} catch (error) {
|
|
54215
54654
|
if (error.code === "ENOENT")
|
|
54216
54655
|
return;
|
|
@@ -54219,8 +54658,8 @@ function setEnvAssignment(path, assignment) {
|
|
|
54219
54658
|
})();
|
|
54220
54659
|
if (previous && !previous.isFile())
|
|
54221
54660
|
throw new EnvAssignmentError("The project .env must be a regular file, not a symlink or special file.");
|
|
54222
|
-
descriptor =
|
|
54223
|
-
const opened =
|
|
54661
|
+
descriptor = openSync4(path, previous ? constants4.O_RDWR | constants4.O_NOFOLLOW | constants4.O_NONBLOCK : constants4.O_WRONLY | constants4.O_CREAT | constants4.O_EXCL | constants4.O_NOFOLLOW, 384);
|
|
54662
|
+
const opened = fstatSync4(descriptor);
|
|
54224
54663
|
if (!opened.isFile() || previous && (opened.ino !== previous.ino || opened.dev !== previous.dev)) {
|
|
54225
54664
|
throw new EnvAssignmentError("The project .env changed while opening it; retry with a regular file.");
|
|
54226
54665
|
}
|
|
@@ -54247,7 +54686,7 @@ function setEnvAssignment(path, assignment) {
|
|
|
54247
54686
|
throw new EnvAssignmentError("Cannot safely read or write the project .env file.");
|
|
54248
54687
|
} finally {
|
|
54249
54688
|
if (descriptor !== undefined)
|
|
54250
|
-
|
|
54689
|
+
closeSync4(descriptor);
|
|
54251
54690
|
}
|
|
54252
54691
|
}
|
|
54253
54692
|
var INVALID_LAYOUT = "Cannot safely update .env with multiline or unsupported assignments. Use single-line KEY=VALUE entries or edit the file manually.", controls, EnvAssignmentError;
|
|
@@ -63446,11 +63885,11 @@ var require_codegen = __commonJS((exports) => {
|
|
|
63446
63885
|
const rhs = this.rhs === undefined ? "" : ` = ${this.rhs}`;
|
|
63447
63886
|
return `${varKind} ${this.name}${rhs};` + _n;
|
|
63448
63887
|
}
|
|
63449
|
-
optimizeNames(names,
|
|
63888
|
+
optimizeNames(names, constants5) {
|
|
63450
63889
|
if (!names[this.name.str])
|
|
63451
63890
|
return;
|
|
63452
63891
|
if (this.rhs)
|
|
63453
|
-
this.rhs = optimizeExpr(this.rhs, names,
|
|
63892
|
+
this.rhs = optimizeExpr(this.rhs, names, constants5);
|
|
63454
63893
|
return this;
|
|
63455
63894
|
}
|
|
63456
63895
|
get names() {
|
|
@@ -63468,10 +63907,10 @@ var require_codegen = __commonJS((exports) => {
|
|
|
63468
63907
|
render({ _n }) {
|
|
63469
63908
|
return `${this.lhs} = ${this.rhs};` + _n;
|
|
63470
63909
|
}
|
|
63471
|
-
optimizeNames(names,
|
|
63910
|
+
optimizeNames(names, constants5) {
|
|
63472
63911
|
if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
|
|
63473
63912
|
return;
|
|
63474
|
-
this.rhs = optimizeExpr(this.rhs, names,
|
|
63913
|
+
this.rhs = optimizeExpr(this.rhs, names, constants5);
|
|
63475
63914
|
return this;
|
|
63476
63915
|
}
|
|
63477
63916
|
get names() {
|
|
@@ -63537,8 +63976,8 @@ var require_codegen = __commonJS((exports) => {
|
|
|
63537
63976
|
optimizeNodes() {
|
|
63538
63977
|
return `${this.code}` ? this : undefined;
|
|
63539
63978
|
}
|
|
63540
|
-
optimizeNames(names,
|
|
63541
|
-
this.code = optimizeExpr(this.code, names,
|
|
63979
|
+
optimizeNames(names, constants5) {
|
|
63980
|
+
this.code = optimizeExpr(this.code, names, constants5);
|
|
63542
63981
|
return this;
|
|
63543
63982
|
}
|
|
63544
63983
|
get names() {
|
|
@@ -63568,12 +64007,12 @@ var require_codegen = __commonJS((exports) => {
|
|
|
63568
64007
|
}
|
|
63569
64008
|
return nodes.length > 0 ? this : undefined;
|
|
63570
64009
|
}
|
|
63571
|
-
optimizeNames(names,
|
|
64010
|
+
optimizeNames(names, constants5) {
|
|
63572
64011
|
const { nodes } = this;
|
|
63573
64012
|
let i = nodes.length;
|
|
63574
64013
|
while (i--) {
|
|
63575
64014
|
const n = nodes[i];
|
|
63576
|
-
if (n.optimizeNames(names,
|
|
64015
|
+
if (n.optimizeNames(names, constants5))
|
|
63577
64016
|
continue;
|
|
63578
64017
|
subtractNames(names, n.names);
|
|
63579
64018
|
nodes.splice(i, 1);
|
|
@@ -63630,12 +64069,12 @@ var require_codegen = __commonJS((exports) => {
|
|
|
63630
64069
|
return;
|
|
63631
64070
|
return this;
|
|
63632
64071
|
}
|
|
63633
|
-
optimizeNames(names,
|
|
64072
|
+
optimizeNames(names, constants5) {
|
|
63634
64073
|
var _a;
|
|
63635
|
-
this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names,
|
|
63636
|
-
if (!(super.optimizeNames(names,
|
|
64074
|
+
this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names, constants5);
|
|
64075
|
+
if (!(super.optimizeNames(names, constants5) || this.else))
|
|
63637
64076
|
return;
|
|
63638
|
-
this.condition = optimizeExpr(this.condition, names,
|
|
64077
|
+
this.condition = optimizeExpr(this.condition, names, constants5);
|
|
63639
64078
|
return this;
|
|
63640
64079
|
}
|
|
63641
64080
|
get names() {
|
|
@@ -63660,10 +64099,10 @@ var require_codegen = __commonJS((exports) => {
|
|
|
63660
64099
|
render(opts) {
|
|
63661
64100
|
return `for(${this.iteration})` + super.render(opts);
|
|
63662
64101
|
}
|
|
63663
|
-
optimizeNames(names,
|
|
63664
|
-
if (!super.optimizeNames(names,
|
|
64102
|
+
optimizeNames(names, constants5) {
|
|
64103
|
+
if (!super.optimizeNames(names, constants5))
|
|
63665
64104
|
return;
|
|
63666
|
-
this.iteration = optimizeExpr(this.iteration, names,
|
|
64105
|
+
this.iteration = optimizeExpr(this.iteration, names, constants5);
|
|
63667
64106
|
return this;
|
|
63668
64107
|
}
|
|
63669
64108
|
get names() {
|
|
@@ -63701,10 +64140,10 @@ var require_codegen = __commonJS((exports) => {
|
|
|
63701
64140
|
render(opts) {
|
|
63702
64141
|
return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
|
|
63703
64142
|
}
|
|
63704
|
-
optimizeNames(names,
|
|
63705
|
-
if (!super.optimizeNames(names,
|
|
64143
|
+
optimizeNames(names, constants5) {
|
|
64144
|
+
if (!super.optimizeNames(names, constants5))
|
|
63706
64145
|
return;
|
|
63707
|
-
this.iterable = optimizeExpr(this.iterable, names,
|
|
64146
|
+
this.iterable = optimizeExpr(this.iterable, names, constants5);
|
|
63708
64147
|
return this;
|
|
63709
64148
|
}
|
|
63710
64149
|
get names() {
|
|
@@ -63749,11 +64188,11 @@ var require_codegen = __commonJS((exports) => {
|
|
|
63749
64188
|
(_b = this.finally) === null || _b === undefined || _b.optimizeNodes();
|
|
63750
64189
|
return this;
|
|
63751
64190
|
}
|
|
63752
|
-
optimizeNames(names,
|
|
64191
|
+
optimizeNames(names, constants5) {
|
|
63753
64192
|
var _a, _b;
|
|
63754
|
-
super.optimizeNames(names,
|
|
63755
|
-
(_a = this.catch) === null || _a === undefined || _a.optimizeNames(names,
|
|
63756
|
-
(_b = this.finally) === null || _b === undefined || _b.optimizeNames(names,
|
|
64193
|
+
super.optimizeNames(names, constants5);
|
|
64194
|
+
(_a = this.catch) === null || _a === undefined || _a.optimizeNames(names, constants5);
|
|
64195
|
+
(_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants5);
|
|
63757
64196
|
return this;
|
|
63758
64197
|
}
|
|
63759
64198
|
get names() {
|
|
@@ -64027,7 +64466,7 @@ var require_codegen = __commonJS((exports) => {
|
|
|
64027
64466
|
function addExprNames(names, from) {
|
|
64028
64467
|
return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
|
|
64029
64468
|
}
|
|
64030
|
-
function optimizeExpr(expr, names,
|
|
64469
|
+
function optimizeExpr(expr, names, constants5) {
|
|
64031
64470
|
if (expr instanceof code_1.Name)
|
|
64032
64471
|
return replaceName(expr);
|
|
64033
64472
|
if (!canOptimize(expr))
|
|
@@ -64042,14 +64481,14 @@ var require_codegen = __commonJS((exports) => {
|
|
|
64042
64481
|
return items;
|
|
64043
64482
|
}, []));
|
|
64044
64483
|
function replaceName(n) {
|
|
64045
|
-
const c =
|
|
64484
|
+
const c = constants5[n.str];
|
|
64046
64485
|
if (c === undefined || names[n.str] !== 1)
|
|
64047
64486
|
return n;
|
|
64048
64487
|
delete names[n.str];
|
|
64049
64488
|
return c;
|
|
64050
64489
|
}
|
|
64051
64490
|
function canOptimize(e) {
|
|
64052
|
-
return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 &&
|
|
64491
|
+
return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants5[c.str] !== undefined);
|
|
64053
64492
|
}
|
|
64054
64493
|
}
|
|
64055
64494
|
function subtractNames(names, from) {
|
|
@@ -71665,7 +72104,7 @@ var MCP_CONTRACT_SCHEMA_VERSION = 1, stringSchema = (description) => ({
|
|
|
71665
72104
|
type: "array",
|
|
71666
72105
|
items,
|
|
71667
72106
|
...description ? { description } : {}
|
|
71668
|
-
}), skillNameInput, optionalAgentInput, scopeInput, runInputSchema, runArgsSchema, errorSchema, skillSummarySchema, toolPrimitiveSummarySchema, skillToolDependencySchema, validationMessageSchema, validationOutputSchema, installOutputSchema, runOutputSchema, toolContracts, remoteCustomerContracts, contracts, resourceContracts;
|
|
72107
|
+
}), skillNameInput, optionalAgentInput, scopeInput, runInputSchema, runArgsSchema, errorSchema, skillSummarySchema, toolPrimitiveSummarySchema, skillToolDependencySchema, validationMessageSchema, validationOutputSchema, installOutputSchema, runOutputSchema, toolContracts, remoteCustomerContracts, publicationUuidSchema, publicationVerification, privatePublicationContracts, contracts, resourceContracts;
|
|
71669
72108
|
var init_mcp_contracts = __esm(() => {
|
|
71670
72109
|
init_remote_customer_operations();
|
|
71671
72110
|
skillNameInput = stringSchema("skill name or alias.");
|
|
@@ -72171,7 +72610,7 @@ var init_mcp_contracts = __esm(() => {
|
|
|
72171
72610
|
name: "run_skill",
|
|
72172
72611
|
title: "Run Skill",
|
|
72173
72612
|
description: "Run a skill locally or through a configured remote runner. Returns compact stdout/stderr previews and run summaries by default; pass detail:true for full records.",
|
|
72174
|
-
params: ["name", "input?", "args?", "detail?", "remote?", "maxCredits?", "maxCostCents?", "idempotency_key?", "files?"],
|
|
72613
|
+
params: ["name", "input?", "args?", "detail?", "remote?", "maxCredits?", "maxCostCents?", "quoteReceipt?", "idempotency_key?", "files?"],
|
|
72175
72614
|
category: "execution",
|
|
72176
72615
|
sideEffects: "local-process-or-remote-run",
|
|
72177
72616
|
stable: true,
|
|
@@ -72183,6 +72622,7 @@ var init_mcp_contracts = __esm(() => {
|
|
|
72183
72622
|
remote: { type: "boolean", description: "Use the configured server catalog." },
|
|
72184
72623
|
maxCredits: { type: "integer", minimum: 0, description: "Maximum explicitly approved integer credits; omitted permits only free remote runs." },
|
|
72185
72624
|
maxCostCents: { type: "integer", minimum: 0, description: "Legacy alias for maxCredits; both must agree." },
|
|
72625
|
+
quoteReceipt: { type: "string", minLength: 1, maxLength: 4096, description: "Opaque approved quote receipt, at most 4096 UTF-8 bytes. Preserve it and the quoted input/args unchanged; never refresh after confirmation." },
|
|
72186
72626
|
idempotency_key: { type: "string", pattern: "^[A-Za-z0-9._:-]{1,128}$", description: "Stable retry key for the same remote submission." },
|
|
72187
72627
|
files: { type: "array", maxItems: 10, items: objectSchema({ name: stringSchema("Safe basename"), base64: { type: "string", maxLength: 1398104 }, contentType: stringSchema("MIME type") }, ["name", "base64"]), description: "Inline remote inputs, at most 1 MiB combined." }
|
|
72188
72628
|
}, ["name"]),
|
|
@@ -72466,12 +72906,17 @@ var init_mcp_contracts = __esm(() => {
|
|
|
72466
72906
|
name: "quote_skill",
|
|
72467
72907
|
title: "Quote Remote Skill",
|
|
72468
72908
|
description: "Get a server credit quote without submitting a run.",
|
|
72469
|
-
params: ["name", "input?", "args?"],
|
|
72909
|
+
params: ["name", "input?", "args?", "files?"],
|
|
72470
72910
|
category: "execution",
|
|
72471
72911
|
sideEffects: "none",
|
|
72472
72912
|
stable: true,
|
|
72473
|
-
inputSchema: objectSchema({
|
|
72474
|
-
|
|
72913
|
+
inputSchema: objectSchema({
|
|
72914
|
+
name: skillNameInput,
|
|
72915
|
+
input: runInputSchema,
|
|
72916
|
+
args: runArgsSchema,
|
|
72917
|
+
files: { type: "array", maxItems: 10, items: objectSchema({ name: stringSchema("Safe basename"), base64: { type: "string", maxLength: 1398104 }, contentType: stringSchema("MIME type") }, ["name", "base64"]), description: "Same inline files to submit after approval, at most 1 MiB combined." }
|
|
72918
|
+
}, ["name"]),
|
|
72919
|
+
outputSchema: objectSchema({ skill: stringSchema("Canonical server skill."), pricing: objectSchema({}, [], "Quoted integer credits.", true), quoteReceipt: { type: "string", minLength: 1, maxLength: 4096, description: "Opaque server quote binding, at most 4096 UTF-8 bytes; preserve verbatim for approval." } }, ["skill", "pricing"], undefined, true)
|
|
72475
72920
|
});
|
|
72476
72921
|
remoteCustomerContracts.push({
|
|
72477
72922
|
name: "download_run_artifact",
|
|
@@ -72484,7 +72929,47 @@ var init_mcp_contracts = __esm(() => {
|
|
|
72484
72929
|
inputSchema: objectSchema({ run_id: stringSchema("Run identifier."), artifact_id: stringSchema("Artifact identifier.") }, ["run_id", "artifact_id"]),
|
|
72485
72930
|
outputSchema: objectSchema({ id: stringSchema("Artifact identifier."), fileName: stringSchema("Artifact file name."), base64: stringSchema("Verified bytes."), sha256: stringSchema("SHA256 digest."), byteSize: { type: "integer", minimum: 0 } }, ["id", "fileName", "base64", "sha256", "byteSize"])
|
|
72486
72931
|
});
|
|
72487
|
-
|
|
72932
|
+
publicationUuidSchema = { type: "string", pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$" };
|
|
72933
|
+
publicationVerification = {
|
|
72934
|
+
email: { type: "string", format: "email", maxLength: 254 },
|
|
72935
|
+
code: { type: "string", pattern: "^\\d{6}$" },
|
|
72936
|
+
userId: publicationUuidSchema,
|
|
72937
|
+
membershipId: publicationUuidSchema,
|
|
72938
|
+
recoveryDirectory: { type: "string", maxLength: 4096, description: "Absolute host-local recovery directory without symbolic links." }
|
|
72939
|
+
};
|
|
72940
|
+
privatePublicationContracts = [
|
|
72941
|
+
{ name: "publish_private_skill", title: "Publish private skill", extras: {
|
|
72942
|
+
directory: { type: "string", maxLength: 4096, description: "Absolute local skill source directory." },
|
|
72943
|
+
skillId: publicationUuidSchema,
|
|
72944
|
+
expectedCurrentVersionId: { oneOf: [publicationUuidSchema, { type: "null" }] },
|
|
72945
|
+
idempotencyKey: publicationUuidSchema,
|
|
72946
|
+
confirm: { const: true },
|
|
72947
|
+
waitMs: { type: "integer", minimum: 0, maximum: 300000 }
|
|
72948
|
+
}, required: ["directory", "skillId", "expectedCurrentVersionId", "confirm"] },
|
|
72949
|
+
{ name: "get_private_publication", title: "Get private publication", extras: {}, required: [] },
|
|
72950
|
+
{ name: "resume_private_publication", title: "Resume private publication", extras: { confirm: { const: true }, waitMs: { type: "integer", minimum: 0, maximum: 300000 } }, required: ["confirm"] },
|
|
72951
|
+
{ name: "cancel_private_publication", title: "Cancel private publication", extras: { confirm: { const: true } }, required: ["confirm"] }
|
|
72952
|
+
].map((operation) => ({
|
|
72953
|
+
name: operation.name,
|
|
72954
|
+
title: operation.title,
|
|
72955
|
+
description: "Manage private source publication with fresh workspace verification and durable host-local recovery. Upload consent and current version comparison are explicit; private execution remains unavailable.",
|
|
72956
|
+
params: [...Object.keys(publicationVerification), ...Object.keys(operation.extras)],
|
|
72957
|
+
category: "storage",
|
|
72958
|
+
sideEffects: "filesystem",
|
|
72959
|
+
stable: true,
|
|
72960
|
+
inputSchema: objectSchema({ ...publicationVerification, ...operation.extras }, [...Object.keys(publicationVerification), ...operation.required]),
|
|
72961
|
+
outputSchema: objectSchema({
|
|
72962
|
+
recoveryDirectory: { type: "string" },
|
|
72963
|
+
skillId: publicationUuidSchema,
|
|
72964
|
+
intentId: { oneOf: [publicationUuidSchema, { type: "null" }] },
|
|
72965
|
+
state: { type: "string" },
|
|
72966
|
+
versionId: { oneOf: [publicationUuidSchema, { type: "null" }] },
|
|
72967
|
+
committed: { type: "boolean" },
|
|
72968
|
+
executionEnabled: { const: false },
|
|
72969
|
+
nextAction: { type: "string" }
|
|
72970
|
+
}, ["recoveryDirectory", "skillId", "intentId", "state", "versionId", "committed", "executionEnabled", "nextAction"])
|
|
72971
|
+
}));
|
|
72972
|
+
contracts = [...toolContracts, ...remoteCustomerContracts, ...privatePublicationContracts].sort((a, b) => a.name.localeCompare(b.name));
|
|
72488
72973
|
resourceContracts = [
|
|
72489
72974
|
{
|
|
72490
72975
|
uri: "skills://mcp/contracts",
|
|
@@ -72999,11 +73484,12 @@ function registerOperationTools(server) {
|
|
|
72999
73484
|
detail: exports_external.boolean().optional(),
|
|
73000
73485
|
maxCostCents: exports_external.number().int().min(0).max(2147483647).optional().describe("Maximum integer credits explicitly approved by the user for a remote run; omitted permits only free runs"),
|
|
73001
73486
|
maxCredits: exports_external.number().int().min(0).max(2147483647).optional(),
|
|
73487
|
+
quoteReceipt: exports_external.string().min(1).max(4096).refine((value) => Buffer.byteLength(value, "utf8") <= 4096, "Quote receipt exceeds 4096 UTF-8 bytes").optional().describe("Opaque receipt from the approved quote; send unchanged with the same input, args and files. Never refresh after confirmation."),
|
|
73002
73488
|
remote: exports_external.boolean().optional().describe("Use the configured server catalog, including skills not installed locally"),
|
|
73003
73489
|
idempotency_key: exports_external.string().regex(/^[A-Za-z0-9._:-]{1,128}$/).optional().describe("Reuse for the same approved submission after an interrupted response"),
|
|
73004
73490
|
files: exports_external.array(exports_external.object({ name: exports_external.string(), base64: exports_external.string().max(1398104), contentType: exports_external.string().optional() })).max(10).optional().describe("Inline remote inputs, at most 1 MiB combined; use CLI or SDK for larger files")
|
|
73005
73491
|
}
|
|
73006
|
-
}, async ({ name, input, args, detail, maxCostCents, maxCredits, remote, idempotency_key, files }) => {
|
|
73492
|
+
}, async ({ name, input, args, detail, maxCostCents, maxCredits, quoteReceipt, remote, idempotency_key, files }) => {
|
|
73007
73493
|
const skill = remote ? { name, serverOwned: true } : getSkill(name);
|
|
73008
73494
|
if (!skill) {
|
|
73009
73495
|
return mcpError("SKILL_NOT_FOUND", `Skill '${name}' not found`, findSimilarSkills(name));
|
|
@@ -73044,7 +73530,7 @@ function registerOperationTools(server) {
|
|
|
73044
73530
|
try {
|
|
73045
73531
|
const { RemoteSkillsClient: RemoteSkillsClient2 } = await Promise.resolve().then(() => (init_remote_client(), exports_remote_client));
|
|
73046
73532
|
const client = new RemoteSkillsClient2(routing.apiKey, routing.apiOrigin);
|
|
73047
|
-
const run = await client.submitQuotedRunWithFiles(skillName, runInput, runArgs, inputFiles, { maxCredits, maxCostCents, idempotencyKey: idempotency_key ?? runContext.record.id });
|
|
73533
|
+
const run = await client.submitQuotedRunWithFiles(skillName, runInput, runArgs, inputFiles, { maxCredits, maxCostCents, quoteReceipt, idempotencyKey: idempotency_key ?? runContext.record.id });
|
|
73048
73534
|
if (run.error) {
|
|
73049
73535
|
writeRunLogs(runContext, "", String(run.error) + `
|
|
73050
73536
|
`);
|
|
@@ -74065,7 +74551,7 @@ async function requestInvitationEmail(origin, action, input) {
|
|
|
74065
74551
|
}
|
|
74066
74552
|
const body = JSON.stringify({ invitationId: value.invitationId, token: value.token, challengeId: value.challengeId, ...action === "accept" ? { code: value.code } : {} });
|
|
74067
74553
|
try {
|
|
74068
|
-
const response = await fetch(
|
|
74554
|
+
const response = await fetch(skillsApiRequestUrl(target, `/api/v1/account/invitations/email-${action}`), {
|
|
74069
74555
|
method: "POST",
|
|
74070
74556
|
headers: { "Content-Type": "application/json" },
|
|
74071
74557
|
body,
|
|
@@ -74140,14 +74626,318 @@ var init_remote_invitation_recovery = __esm(() => {
|
|
|
74140
74626
|
};
|
|
74141
74627
|
});
|
|
74142
74628
|
|
|
74629
|
+
// src/lib/remote-private-publications.ts
|
|
74630
|
+
import { createHash as createHash7 } from "crypto";
|
|
74631
|
+
function checkedPublicationDeclaration(value) {
|
|
74632
|
+
if (!exact(value, ["idempotencyKey", "version", "expectedCurrentVersionId", "manifestText", "archiveSha256", "archiveByteSize"]) || !publicationUuid(value.idempotencyKey) || !(value.expectedCurrentVersionId === null || publicationUuid(value.expectedCurrentVersionId)) || typeof value.version !== "string" || !value.version || value.version.length > 128 || /[\p{Cc}\p{Cs}]/u.test(value.version) || typeof value.manifestText !== "string" || Buffer.byteLength(value.manifestText) > 16384 || !hash(value.archiveSha256) || !Number.isSafeInteger(value.archiveByteSize) || Number(value.archiveByteSize) < 1 || Number(value.archiveByteSize) > PRIVATE_PUBLICATION_MAX_BYTES)
|
|
74633
|
+
return bad();
|
|
74634
|
+
try {
|
|
74635
|
+
const manifest = JSON.parse(value.manifestText);
|
|
74636
|
+
if (!record7(manifest) || manifest.version !== value.version || validatePortableManifestContract(manifest, { strict: true }).length || !hash(manifest.provenance?.content_hash))
|
|
74637
|
+
return bad();
|
|
74638
|
+
} catch {
|
|
74639
|
+
return bad();
|
|
74640
|
+
}
|
|
74641
|
+
return Object.freeze({ ...value });
|
|
74642
|
+
}
|
|
74643
|
+
function checkedPublicationView(value, skillId, intentId, declaration) {
|
|
74644
|
+
if (!exact(value, ["id", "skillId", "version", "expectedCurrentVersionId", "archiveSha256", "archiveByteSize", "state", "expiresAt", "createdAt", "versionId"]) || !publicationUuid(value.id) || value.skillId !== skillId || intentId !== undefined && value.id !== intentId || typeof value.version !== "string" || !value.version || value.version.length > 128 || /[\p{Cc}\p{Cs}]/u.test(value.version) || !(value.expectedCurrentVersionId === null || publicationUuid(value.expectedCurrentVersionId)) || !hash(value.archiveSha256) || !Number.isSafeInteger(value.archiveByteSize) || Number(value.archiveByteSize) < 1 || Number(value.archiveByteSize) > PRIVATE_PUBLICATION_MAX_BYTES || typeof value.state !== "string" || !["awaiting_upload", "queued", "verifying", "needs_attention", "committed", "rejected", "cancelled", "expired"].includes(value.state) || !date4(value.expiresAt) || !date4(value.createdAt) || !(value.versionId === null || publicationUuid(value.versionId)) || value.state === "committed" && value.versionId === null)
|
|
74645
|
+
return invalid3();
|
|
74646
|
+
if (declaration && ["version", "expectedCurrentVersionId", "archiveSha256", "archiveByteSize"].some((k) => value[k] !== declaration[k]))
|
|
74647
|
+
return invalid3();
|
|
74648
|
+
return Object.freeze({ ...value });
|
|
74649
|
+
}
|
|
74650
|
+
async function boundedJson(url, init, token, mutation, budget = 15000, signal) {
|
|
74651
|
+
const controller = new AbortController;
|
|
74652
|
+
let reader;
|
|
74653
|
+
let response;
|
|
74654
|
+
const timeout = new PrivatePublicationError("PUBLICATION_UNCONFIRMED", "No confirmed publication result. Inspect the saved intent before retrying.", mutation);
|
|
74655
|
+
let timer;
|
|
74656
|
+
let abort;
|
|
74657
|
+
try {
|
|
74658
|
+
return await Promise.race([(async () => {
|
|
74659
|
+
if (signal?.aborted)
|
|
74660
|
+
throw timeout;
|
|
74661
|
+
response = await fetch(url, {
|
|
74662
|
+
...init,
|
|
74663
|
+
redirect: "error",
|
|
74664
|
+
credentials: "omit",
|
|
74665
|
+
signal: controller.signal,
|
|
74666
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }
|
|
74667
|
+
});
|
|
74668
|
+
const length = response.headers.get("content-length");
|
|
74669
|
+
if (length !== null && (!/^\d+$/.test(length) || Number(length) > 65536))
|
|
74670
|
+
return invalid3();
|
|
74671
|
+
reader = response.body?.getReader();
|
|
74672
|
+
const chunks = [];
|
|
74673
|
+
let size2 = 0;
|
|
74674
|
+
if (reader)
|
|
74675
|
+
while (true) {
|
|
74676
|
+
const part = await reader.read();
|
|
74677
|
+
if (part.done)
|
|
74678
|
+
break;
|
|
74679
|
+
size2 += part.value.byteLength;
|
|
74680
|
+
if (size2 > 65536)
|
|
74681
|
+
return invalid3();
|
|
74682
|
+
chunks.push(part.value);
|
|
74683
|
+
}
|
|
74684
|
+
if (controller.signal.aborted)
|
|
74685
|
+
throw timeout;
|
|
74686
|
+
const bytes = Buffer.concat(chunks);
|
|
74687
|
+
let body;
|
|
74688
|
+
try {
|
|
74689
|
+
body = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
74690
|
+
} catch {
|
|
74691
|
+
return invalid3();
|
|
74692
|
+
}
|
|
74693
|
+
if (!response.ok) {
|
|
74694
|
+
const code = record7(body) && typeof body.code === "string" && Object.hasOwn(failures, body.code) ? body.code : null;
|
|
74695
|
+
if (code && failures[code][0] === response.status)
|
|
74696
|
+
throw new PrivatePublicationError(code, failures[code][1], mutation && code === "PUBLICATION_UNCERTAIN", response.status);
|
|
74697
|
+
throw new PrivatePublicationError("PUBLICATION_REQUEST_FAILED", "The publication request was refused. Inspect its status before retrying.", mutation && response.status >= 500, response.status);
|
|
74698
|
+
}
|
|
74699
|
+
return body;
|
|
74700
|
+
})(), new Promise((_, reject2) => {
|
|
74701
|
+
abort = () => {
|
|
74702
|
+
controller.abort();
|
|
74703
|
+
reject2(timeout);
|
|
74704
|
+
};
|
|
74705
|
+
timer = setTimeout(abort, Math.max(1, Math.min(15000, budget)));
|
|
74706
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
74707
|
+
})]);
|
|
74708
|
+
} catch (error2) {
|
|
74709
|
+
if (error2 instanceof PrivatePublicationError) {
|
|
74710
|
+
if (mutation && error2.code === "INVALID_PUBLICATION_RESPONSE")
|
|
74711
|
+
throw timeout;
|
|
74712
|
+
throw error2;
|
|
74713
|
+
}
|
|
74714
|
+
throw timeout;
|
|
74715
|
+
} finally {
|
|
74716
|
+
if (timer)
|
|
74717
|
+
clearTimeout(timer);
|
|
74718
|
+
if (abort)
|
|
74719
|
+
signal?.removeEventListener("abort", abort);
|
|
74720
|
+
controller.abort();
|
|
74721
|
+
if (reader)
|
|
74722
|
+
reader.cancel().catch(() => {});
|
|
74723
|
+
else
|
|
74724
|
+
response?.body?.cancel().catch(() => {});
|
|
74725
|
+
}
|
|
74726
|
+
}
|
|
74727
|
+
|
|
74728
|
+
class RemotePrivatePublicationsClient {
|
|
74729
|
+
apiOrigin;
|
|
74730
|
+
organizationId;
|
|
74731
|
+
userId;
|
|
74732
|
+
membershipId;
|
|
74733
|
+
#token;
|
|
74734
|
+
constructor(apiUrl, session) {
|
|
74735
|
+
const checked = parseWorkspaceSession(session, { userId: session?.user?.id, membershipId: session?.user?.membershipId });
|
|
74736
|
+
if (checked.user.role === "viewer")
|
|
74737
|
+
throw new PrivatePublicationError("PUBLICATION_FORBIDDEN", failures.PUBLICATION_FORBIDDEN[1]);
|
|
74738
|
+
this.apiOrigin = normalizeSkillsApiOrigin(apiUrl);
|
|
74739
|
+
this.#token = checked.token;
|
|
74740
|
+
this.organizationId = checked.organization.id;
|
|
74741
|
+
this.userId = checked.user.id;
|
|
74742
|
+
this.membershipId = checked.user.membershipId;
|
|
74743
|
+
Object.freeze(this);
|
|
74744
|
+
}
|
|
74745
|
+
async getCapability(options = {}) {
|
|
74746
|
+
if (options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 1 || options.timeoutMs > 15000))
|
|
74747
|
+
return bad();
|
|
74748
|
+
const response = await boundedJson(skillsApiRequestUrl(this.apiOrigin, "/api/v1/capabilities"), {}, this.#token, false, options.timeoutMs, options.signal);
|
|
74749
|
+
const p = record7(response) && response.privatePublishing;
|
|
74750
|
+
if (!record7(response) || response.contractVersion !== 1 || response.apiVersion !== 1 || !exact(p, ["contractVersion", "enabled", "authentication", "maxArchiveBytes", "uploadMaxTtlSeconds", "executionEnabled"]) || p.contractVersion !== 1 || typeof p.enabled !== "boolean" || p.authentication !== "interactive-session" || p.maxArchiveBytes !== PRIVATE_PUBLICATION_MAX_BYTES || p.uploadMaxTtlSeconds !== 300 || p.executionEnabled !== false)
|
|
74751
|
+
throw new PrivatePublicationError("PUBLICATION_CONTRACT_UNAVAILABLE", "This server does not support the hosted private publication contract.");
|
|
74752
|
+
return Object.freeze({ ...p });
|
|
74753
|
+
}
|
|
74754
|
+
async#gate(enabled, options = {}) {
|
|
74755
|
+
if (!(await this.getCapability(options)).enabled && enabled)
|
|
74756
|
+
throw new PrivatePublicationError("PUBLICATION_CAPABILITY_UNAVAILABLE", failures.PUBLICATION_CAPABILITY_UNAVAILABLE[1]);
|
|
74757
|
+
}
|
|
74758
|
+
#path(skillId, intentId) {
|
|
74759
|
+
if (!publicationUuid(skillId) || intentId !== undefined && !publicationUuid(intentId))
|
|
74760
|
+
return bad();
|
|
74761
|
+
return skillsApiRequestUrl(this.apiOrigin, `/api/v1/skills/${skillId}/publication-uploads${intentId ? `/${intentId}` : ""}`);
|
|
74762
|
+
}
|
|
74763
|
+
async#view(path, method2, skillId, intentId, declaration, options = {}) {
|
|
74764
|
+
const value = await boundedJson(path, { method: method2, ...method2 === "GET" ? {} : { body: JSON.stringify(declaration ?? {}) } }, this.#token, method2 !== "GET", options.timeoutMs, options.signal);
|
|
74765
|
+
try {
|
|
74766
|
+
if (!record7(value) || !Object.keys(value).every((k) => k === "upload" || k === "changed") || value.changed !== undefined && typeof value.changed !== "boolean")
|
|
74767
|
+
return invalid3();
|
|
74768
|
+
return checkedPublicationView(value.upload, skillId, intentId, declaration);
|
|
74769
|
+
} catch (error2) {
|
|
74770
|
+
if (method2 !== "GET")
|
|
74771
|
+
throw new PrivatePublicationError("PUBLICATION_UNCONFIRMED", "The publication result could not be confirmed. Reconcile the saved intent before another action.", true);
|
|
74772
|
+
throw error2;
|
|
74773
|
+
}
|
|
74774
|
+
}
|
|
74775
|
+
async begin(skillId, input) {
|
|
74776
|
+
const path = this.#path(skillId), declaration = checkedPublicationDeclaration(input);
|
|
74777
|
+
await this.#gate(true);
|
|
74778
|
+
return this.#view(path, "POST", skillId, undefined, declaration);
|
|
74779
|
+
}
|
|
74780
|
+
async get(skillId, intentId, options = {}) {
|
|
74781
|
+
const path = this.#path(skillId, intentId), until = Date.now() + (options.timeoutMs ?? 15000);
|
|
74782
|
+
await this.#gate(false, options);
|
|
74783
|
+
return this.#view(path, "GET", skillId, intentId, undefined, { ...options, timeoutMs: Math.max(1, until - Date.now()) });
|
|
74784
|
+
}
|
|
74785
|
+
async finalize(skillId, intentId) {
|
|
74786
|
+
const path = this.#path(skillId, intentId);
|
|
74787
|
+
await this.#gate(true);
|
|
74788
|
+
return this.#view(`${path}/finalize`, "POST", skillId, intentId);
|
|
74789
|
+
}
|
|
74790
|
+
async cancel(skillId, intentId) {
|
|
74791
|
+
const path = this.#path(skillId, intentId);
|
|
74792
|
+
await this.#gate(false);
|
|
74793
|
+
return this.#view(path, "DELETE", skillId, intentId);
|
|
74794
|
+
}
|
|
74795
|
+
async upload(skillId, intent, bytes) {
|
|
74796
|
+
const captured = checkedPublicationView(intent, skillId), path = this.#path(skillId, captured.id);
|
|
74797
|
+
if (!(bytes instanceof Uint8Array) || bytes.byteLength !== captured.archiveByteSize)
|
|
74798
|
+
return bad();
|
|
74799
|
+
const owned = Buffer.from(bytes);
|
|
74800
|
+
if (captured.state !== "awaiting_upload" || owned.byteLength !== captured.archiveByteSize || publicationSha256(owned) !== captured.archiveSha256)
|
|
74801
|
+
return bad();
|
|
74802
|
+
await this.#gate(true);
|
|
74803
|
+
const value = await boundedJson(`${path}/upload-url`, { method: "POST", body: "{}" }, this.#token, false);
|
|
74804
|
+
const upload = this.#upload(value, captured);
|
|
74805
|
+
const controller = new AbortController;
|
|
74806
|
+
let timer;
|
|
74807
|
+
const uncertain = () => new PrivatePublicationError("PUBLICATION_UPLOAD_UNCONFIRMED", "Upload acceptance is uncertain. Resume this saved intent to finalize and inspect it; do not upload again.", true);
|
|
74808
|
+
try {
|
|
74809
|
+
await Promise.race([(async () => {
|
|
74810
|
+
const response = await fetch(upload.uploadUrl, { method: "PUT", headers: upload.headers, body: owned, redirect: "error", credentials: "omit", signal: controller.signal });
|
|
74811
|
+
response.body?.cancel().catch(() => {});
|
|
74812
|
+
if (response.status !== 200 || controller.signal.aborted)
|
|
74813
|
+
throw uncertain();
|
|
74814
|
+
})(), new Promise((_, reject2) => {
|
|
74815
|
+
timer = setTimeout(() => {
|
|
74816
|
+
controller.abort();
|
|
74817
|
+
reject2(uncertain());
|
|
74818
|
+
}, 30000);
|
|
74819
|
+
})]);
|
|
74820
|
+
} catch {
|
|
74821
|
+
throw uncertain();
|
|
74822
|
+
} finally {
|
|
74823
|
+
if (timer)
|
|
74824
|
+
clearTimeout(timer);
|
|
74825
|
+
controller.abort();
|
|
74826
|
+
}
|
|
74827
|
+
}
|
|
74828
|
+
#upload(value, intent) {
|
|
74829
|
+
if (!exact(value, ["upload"]) || !exact(value.upload, ["method", "uploadUrl", "headers", "expiresAt"]))
|
|
74830
|
+
return invalid3();
|
|
74831
|
+
const p = value.upload;
|
|
74832
|
+
if (p.method !== "PUT" || typeof p.uploadUrl !== "string" || p.uploadUrl.length > 8192 || /[\x00-\x20\x7f]/.test(p.uploadUrl) || !date4(p.expiresAt) || Date.parse(p.expiresAt) - Date.now() < 1000 || Date.parse(p.expiresAt) - Date.now() > 300000 || Date.parse(p.expiresAt) > Date.parse(intent.expiresAt) || !exact(p.headers, ["content-type", "content-length", "x-amz-checksum-sha256", "x-amz-expected-bucket-owner"]) || p.headers["content-type"] !== "application/gzip" || p.headers["content-length"] !== String(intent.archiveByteSize) || p.headers["x-amz-checksum-sha256"] !== Buffer.from(intent.archiveSha256, "hex").toString("base64") || typeof p.headers["x-amz-expected-bucket-owner"] !== "string" || !/^\d{12}$/.test(p.headers["x-amz-expected-bucket-owner"]))
|
|
74833
|
+
return invalid3();
|
|
74834
|
+
let url;
|
|
74835
|
+
try {
|
|
74836
|
+
url = new URL(p.uploadUrl);
|
|
74837
|
+
} catch {
|
|
74838
|
+
return invalid3();
|
|
74839
|
+
}
|
|
74840
|
+
if (url.protocol !== "https:" || url.port || url.username || url.password || url.hash || !/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]\.s3\.[a-z]{2}(?:-[a-z]+)+-[1-9]\.amazonaws\.com$/.test(url.hostname) || url.pathname !== `/private-publication-staging/${this.organizationId}/${intent.id}/bundle.tgz` || !url.search || url.searchParams.get("X-Amz-Algorithm") !== "AWS4-HMAC-SHA256" || url.searchParams.get("X-Amz-SignedHeaders") !== "content-length;content-type;host;x-amz-checksum-sha256;x-amz-expected-bucket-owner" || !/^[a-f0-9]{64}$/.test(url.searchParams.get("X-Amz-Signature") ?? ""))
|
|
74841
|
+
return invalid3();
|
|
74842
|
+
const queryKeys = ["X-Amz-Algorithm", "X-Amz-Credential", "X-Amz-Date", "X-Amz-Expires", "X-Amz-Security-Token", "X-Amz-Signature", "X-Amz-SignedHeaders"];
|
|
74843
|
+
if ([...url.searchParams.keys()].sort().join(",") !== queryKeys.sort().join(","))
|
|
74844
|
+
return invalid3();
|
|
74845
|
+
const issued = url.searchParams.get("X-Amz-Date"), ttl = url.searchParams.get("X-Amz-Expires"), credential = url.searchParams.get("X-Amz-Credential");
|
|
74846
|
+
const timestamp3 = /^(\d{4})(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)Z$/.exec(issued);
|
|
74847
|
+
const region = url.hostname.split(".s3.")[1].split(".amazonaws.com")[0];
|
|
74848
|
+
if (!timestamp3 || !/^[1-9]\d{0,2}$/.test(ttl) || Number(ttl) > 300 || !/^[A-Z0-9]{16,128}\//.test(credential) || credential.split("/").slice(1).join("/") !== `${issued.slice(0, 8)}/${region}/s3/aws4_request` || !/^[\x21-\x7e]{1,4096}$/.test(url.searchParams.get("X-Amz-Security-Token")))
|
|
74849
|
+
return invalid3();
|
|
74850
|
+
const issuedAt = Date.parse(`${timestamp3[1]}-${timestamp3[2]}-${timestamp3[3]}T${timestamp3[4]}:${timestamp3[5]}:${timestamp3[6]}Z`);
|
|
74851
|
+
if (!Number.isFinite(issuedAt) || issuedAt > Date.now() + 1000 || issuedAt + Number(ttl) * 1000 !== Date.parse(p.expiresAt))
|
|
74852
|
+
return invalid3();
|
|
74853
|
+
return { method: "PUT", uploadUrl: url.href, expiresAt: p.expiresAt, headers: Object.freeze({ ...p.headers }) };
|
|
74854
|
+
}
|
|
74855
|
+
async wait(skillId, intentId, options = {}) {
|
|
74856
|
+
const timeout = options.timeoutMs ?? 60000;
|
|
74857
|
+
if (!Number.isSafeInteger(timeout) || timeout < 0 || timeout > 300000)
|
|
74858
|
+
return bad();
|
|
74859
|
+
const until = Date.now() + timeout;
|
|
74860
|
+
let previous;
|
|
74861
|
+
while (true) {
|
|
74862
|
+
if (options.signal?.aborted)
|
|
74863
|
+
throw new PrivatePublicationError("PUBLICATION_WAIT_ABORTED", "Stopped waiting. The server publication continues; inspect the saved intent.");
|
|
74864
|
+
let view;
|
|
74865
|
+
try {
|
|
74866
|
+
view = await this.get(skillId, intentId, { timeoutMs: timeout === 0 ? 15000 : Math.max(1, Math.min(15000, until - Date.now())), signal: options.signal });
|
|
74867
|
+
} catch (error2) {
|
|
74868
|
+
if (previous && Date.now() >= until && !options.signal?.aborted)
|
|
74869
|
+
return previous;
|
|
74870
|
+
throw error2;
|
|
74871
|
+
}
|
|
74872
|
+
previous = view;
|
|
74873
|
+
if (!["queued", "verifying"].includes(view.state) || Date.now() >= until)
|
|
74874
|
+
return view;
|
|
74875
|
+
await new Promise((resolve3) => {
|
|
74876
|
+
const timer = setTimeout(done, Math.min(1000, until - Date.now()));
|
|
74877
|
+
function done() {
|
|
74878
|
+
clearTimeout(timer);
|
|
74879
|
+
options.signal?.removeEventListener("abort", done);
|
|
74880
|
+
resolve3();
|
|
74881
|
+
}
|
|
74882
|
+
options.signal?.addEventListener("abort", done, { once: true });
|
|
74883
|
+
});
|
|
74884
|
+
}
|
|
74885
|
+
}
|
|
74886
|
+
}
|
|
74887
|
+
var PRIVATE_PUBLICATION_MAX_BYTES, failures, PrivatePublicationError, bad = () => {
|
|
74888
|
+
throw new PrivatePublicationError("INVALID_PUBLICATION_INPUT", "Invalid publication input or recovery data.");
|
|
74889
|
+
}, invalid3 = () => {
|
|
74890
|
+
throw new PrivatePublicationError("INVALID_PUBLICATION_RESPONSE", "The server returned an invalid publication result.");
|
|
74891
|
+
}, publicationUuid = (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), hash = (v) => typeof v === "string" && /^[a-f0-9]{64}$/.test(v), record7 = (v) => !!v && typeof v === "object" && !Array.isArray(v), exact = (v, keys2) => record7(v) && Object.keys(v).sort().join(",") === keys2.sort().join(","), date4 = (v) => typeof v === "string" && /^\d{4}-\d\d-\d\dT[0-9:.]+(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/.test(v) && Number.isFinite(Date.parse(v)), publicationSha256 = (bytes) => createHash7("sha256").update(bytes).digest("hex");
|
|
74892
|
+
var init_remote_private_publications = __esm(() => {
|
|
74893
|
+
init_fleet_credentials();
|
|
74894
|
+
init_remote_workspace_selection();
|
|
74895
|
+
init_skill_contract();
|
|
74896
|
+
PRIVATE_PUBLICATION_MAX_BYTES = 16 * 1024 * 1024;
|
|
74897
|
+
failures = {
|
|
74898
|
+
INVALID_REQUEST: [400, "The publication request is invalid."],
|
|
74899
|
+
SESSION_EXPIRED: [401, "Sign in again to manage this publication."],
|
|
74900
|
+
ACCOUNT_UNAVAILABLE: [403, "The account is unavailable."],
|
|
74901
|
+
INTERACTIVE_SESSION_REQUIRED: [403, "An interactive account session is required."],
|
|
74902
|
+
PUBLICATION_FORBIDDEN: [403, "This session cannot manage the publication."],
|
|
74903
|
+
PUBLICATION_ENTITLEMENT_REQUIRED: [403, "The workspace is not entitled to publish private skills."],
|
|
74904
|
+
PUBLICATION_UNAVAILABLE: [404, "The publication is unavailable to this session."],
|
|
74905
|
+
MANIFEST_NAME_MISMATCH: [409, "The manifest name does not match the selected skill."],
|
|
74906
|
+
IDEMPOTENCY_CONFLICT: [409, "This request key already identifies different publication bytes. Use the saved recovery directory."],
|
|
74907
|
+
CURRENT_VERSION_CHANGED: [409, "The current version changed. Inspect it before explicitly starting another publication."],
|
|
74908
|
+
VERSION_EXISTS: [409, "This version already exists."],
|
|
74909
|
+
VERSION_RESERVED: [409, "This version is reserved by another publication."],
|
|
74910
|
+
PUBLICATION_COMMITTED: [409, "The publication is already committed."],
|
|
74911
|
+
PUBLICATION_UPLOAD_UNAVAILABLE: [409, "This publication cannot receive another upload."],
|
|
74912
|
+
PUBLICATION_LIMIT: [429, "The workspace publication limit has been reached."],
|
|
74913
|
+
PUBLICATION_BUSY: [503, "The publication is busy. Reconcile the saved intent before retrying."],
|
|
74914
|
+
PUBLICATION_UNCERTAIN: [503, "The publication outcome is uncertain. Reconcile the saved intent."],
|
|
74915
|
+
PUBLICATION_CAPABILITY_UNAVAILABLE: [503, "Private publishing is not enabled on this server."],
|
|
74916
|
+
PUBLICATION_SIGNING_UNAVAILABLE: [503, "Upload authorization is temporarily unavailable. Keep the same intent."]
|
|
74917
|
+
};
|
|
74918
|
+
PrivatePublicationError = class PrivatePublicationError extends Error {
|
|
74919
|
+
code;
|
|
74920
|
+
uncertain;
|
|
74921
|
+
status;
|
|
74922
|
+
constructor(code, message, uncertain = false, status) {
|
|
74923
|
+
super(message);
|
|
74924
|
+
this.code = code;
|
|
74925
|
+
this.uncertain = uncertain;
|
|
74926
|
+
this.status = status;
|
|
74927
|
+
this.name = "PrivatePublicationError";
|
|
74928
|
+
}
|
|
74929
|
+
};
|
|
74930
|
+
});
|
|
74931
|
+
|
|
74143
74932
|
// src/lib/remote-auth.ts
|
|
74144
74933
|
async function requestAuthApi(instance, path, options) {
|
|
74145
74934
|
const url = normalizeSkillsApiOrigin(instance);
|
|
74146
74935
|
const safeUrl = url;
|
|
74147
|
-
const
|
|
74936
|
+
const requestUrl = skillsApiRequestUrl(url, path);
|
|
74937
|
+
const endpoint = `${(options?.method || "GET").toUpperCase()} ${requestUrl}`;
|
|
74148
74938
|
let res;
|
|
74149
74939
|
try {
|
|
74150
|
-
res = await fetch(
|
|
74940
|
+
res = await fetch(requestUrl, {
|
|
74151
74941
|
...options,
|
|
74152
74942
|
redirect: "error",
|
|
74153
74943
|
signal: options?.signal ?? AbortSignal.timeout(15000),
|
|
@@ -74162,10 +74952,10 @@ async function requestAuthApi(instance, path, options) {
|
|
|
74162
74952
|
const text2 = await res.text();
|
|
74163
74953
|
const body = text2 ? parseJsonBody(text2) : {};
|
|
74164
74954
|
if (!res.ok) {
|
|
74165
|
-
const
|
|
74166
|
-
const detail = typeof
|
|
74167
|
-
const error2 = typeof
|
|
74168
|
-
const code = typeof
|
|
74955
|
+
const record8 = isRecord5(body) ? body : {};
|
|
74956
|
+
const detail = typeof record8.detail === "string" ? record8.detail : undefined;
|
|
74957
|
+
const error2 = typeof record8.error === "string" ? record8.error : undefined;
|
|
74958
|
+
const code = typeof record8.code === "string" ? record8.code : undefined;
|
|
74169
74959
|
throw new HostedApiError(detail || error2 || `${res.status} ${res.statusText}`, {
|
|
74170
74960
|
status: res.status,
|
|
74171
74961
|
code,
|
|
@@ -74199,6 +74989,10 @@ class RemoteSkillsAuthClient {
|
|
|
74199
74989
|
constructor(apiUrl) {
|
|
74200
74990
|
this.apiOrigin = normalizeSkillsApiOrigin(apiUrl);
|
|
74201
74991
|
}
|
|
74992
|
+
async openPrivatePublications(email3, code, context) {
|
|
74993
|
+
const origin = this.apiOrigin, captured = workspaceContext(context);
|
|
74994
|
+
return new RemotePrivatePublicationsClient(origin, await this.switchWorkspace(email3, code, captured));
|
|
74995
|
+
}
|
|
74202
74996
|
requestInvitationEmailChallenge(input) {
|
|
74203
74997
|
return requestInvitationEmail(this.apiOrigin, "challenge", input);
|
|
74204
74998
|
}
|
|
@@ -74246,9 +75040,10 @@ class RemoteSkillsAuthClient {
|
|
|
74246
75040
|
const apiOrigin = this.apiOrigin;
|
|
74247
75041
|
if (typeof email3 !== "string" || !email3.includes("@") || typeof code !== "string" || !/^\d{6}$/.test(code))
|
|
74248
75042
|
throw new Error("Fresh email and six-digit verification code are required to manage this account");
|
|
75043
|
+
const requestUrl = skillsApiRequestUrl(apiOrigin, "/api/auth/verify");
|
|
74249
75044
|
let response;
|
|
74250
75045
|
try {
|
|
74251
|
-
response = await fetch(
|
|
75046
|
+
response = await fetch(requestUrl, {
|
|
74252
75047
|
method: "POST",
|
|
74253
75048
|
redirect: "error",
|
|
74254
75049
|
credentials: "omit",
|
|
@@ -74342,6 +75137,7 @@ class RemoteSkillsAuthClient {
|
|
|
74342
75137
|
var MAX_ERROR_DETAIL_LENGTH = 200, HostedApiError;
|
|
74343
75138
|
var init_remote_auth = __esm(() => {
|
|
74344
75139
|
init_remote_invitation_recovery();
|
|
75140
|
+
init_remote_private_publications();
|
|
74345
75141
|
init_remote_invitations();
|
|
74346
75142
|
init_remote_workspace_leave();
|
|
74347
75143
|
init_remote_workspace_selection();
|
|
@@ -74369,11 +75165,11 @@ var init_remote_auth = __esm(() => {
|
|
|
74369
75165
|
});
|
|
74370
75166
|
|
|
74371
75167
|
// src/lib/workspace-profile.ts
|
|
74372
|
-
import { constants as
|
|
75168
|
+
import { constants as constants5, closeSync as closeSync5, fstatSync as fstatSync5, lstatSync as lstatSync7, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync4, openSync as openSync5, readFileSync as readFileSync25, renameSync as renameSync6, rmSync as rmSync7, writeFileSync as writeFileSync14 } from "fs";
|
|
74373
75169
|
import { dirname as dirname10, join as join32 } from "path";
|
|
74374
75170
|
function stat(path) {
|
|
74375
75171
|
try {
|
|
74376
|
-
return
|
|
75172
|
+
return lstatSync7(path);
|
|
74377
75173
|
} catch (error2) {
|
|
74378
75174
|
if (error2.code === "ENOENT")
|
|
74379
75175
|
return null;
|
|
@@ -74383,14 +75179,14 @@ function stat(path) {
|
|
|
74383
75179
|
function safeText(file) {
|
|
74384
75180
|
if (stat(file) === null)
|
|
74385
75181
|
return null;
|
|
74386
|
-
const fd =
|
|
75182
|
+
const fd = openSync5(file, constants5.O_RDONLY | constants5.O_NOFOLLOW | constants5.O_NONBLOCK);
|
|
74387
75183
|
try {
|
|
74388
|
-
const s =
|
|
75184
|
+
const s = fstatSync5(fd);
|
|
74389
75185
|
if (!s.isFile() || s.size > 65536 || ![256, 384].includes(s.mode & 4095) || process.getuid && s.uid !== process.getuid())
|
|
74390
75186
|
return fail2("The selected profile must use bounded owner-only regular files.");
|
|
74391
75187
|
return readFileSync25(fd, "utf8");
|
|
74392
75188
|
} finally {
|
|
74393
|
-
|
|
75189
|
+
closeSync5(fd);
|
|
74394
75190
|
}
|
|
74395
75191
|
}
|
|
74396
75192
|
function checkIdentityMetadata(file, identity2) {
|
|
@@ -74680,9 +75476,325 @@ var init_remote_invitation_tools = __esm(() => {
|
|
|
74680
75476
|
init_helpers();
|
|
74681
75477
|
});
|
|
74682
75478
|
|
|
75479
|
+
// src/lib/private-publication-customer.ts
|
|
75480
|
+
async function privatePublicationSession(email3, code, requested) {
|
|
75481
|
+
const target = await captureProfileWorkspace("Manage private publications");
|
|
75482
|
+
const context = requested ?? target.context;
|
|
75483
|
+
if (!context || !publicationUuid(context.userId) || !publicationUuid(context.membershipId) || target.context && (context.userId !== target.context.userId || context.membershipId !== target.context.membershipId))
|
|
75484
|
+
throw new PrivatePublicationError("PUBLICATION_CONTEXT_REQUIRED", "Provide the observed user and membership IDs, or select an enrolled workspace profile.");
|
|
75485
|
+
target.unchanged();
|
|
75486
|
+
const client = await new RemoteSkillsAuthClient(target.origin).openPrivatePublications(email3, code, context);
|
|
75487
|
+
target.unchanged();
|
|
75488
|
+
return client;
|
|
75489
|
+
}
|
|
75490
|
+
function privatePublicationCustomerError(error2) {
|
|
75491
|
+
return error2 instanceof PrivatePublicationError ? { error: error2.message, code: error2.code, uncertain: error2.uncertain } : { error: "The publication action could not be confirmed. Preserve the recovery directory and inspect the same intent; credentials were not changed.", code: "PUBLICATION_ACTION_UNCONFIRMED", uncertain: true };
|
|
75492
|
+
}
|
|
75493
|
+
var init_private_publication_customer = __esm(() => {
|
|
75494
|
+
init_remote_auth();
|
|
75495
|
+
init_remote_private_publications();
|
|
75496
|
+
init_workspace_profile();
|
|
75497
|
+
});
|
|
75498
|
+
|
|
75499
|
+
// src/lib/private-publication-recovery.ts
|
|
75500
|
+
import { constants as constants6, closeSync as closeSync6, fsyncSync, fstatSync as fstatSync6, lstatSync as lstatSync8, mkdirSync as mkdirSync15, openSync as openSync6, readSync as readSync3, realpathSync as realpathSync2, renameSync as renameSync7, unlinkSync as unlinkSync2, writeFileSync as writeFileSync15 } from "fs";
|
|
75501
|
+
import { dirname as dirname11, isAbsolute as isAbsolute4, join as join33, resolve as resolve3 } from "path";
|
|
75502
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
75503
|
+
function safeDirectory(directory) {
|
|
75504
|
+
if (!isAbsolute4(directory) || directory !== resolve3(directory) || realpathSync2(directory) !== directory)
|
|
75505
|
+
return fail3();
|
|
75506
|
+
for (let path = directory;; path = dirname11(path)) {
|
|
75507
|
+
const stat2 = lstatSync8(path);
|
|
75508
|
+
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
75509
|
+
return fail3();
|
|
75510
|
+
if (path === dirname11(path))
|
|
75511
|
+
break;
|
|
75512
|
+
}
|
|
75513
|
+
const own = lstatSync8(directory);
|
|
75514
|
+
if ((own.mode & 63) !== 0 || process.getuid && own.uid !== process.getuid())
|
|
75515
|
+
return fail3();
|
|
75516
|
+
return { dev: own.dev, ino: own.ino };
|
|
75517
|
+
}
|
|
75518
|
+
function unchangedDirectory(directory, identity2) {
|
|
75519
|
+
const now3 = safeDirectory(directory);
|
|
75520
|
+
if (now3.dev !== identity2.dev || now3.ino !== identity2.ino)
|
|
75521
|
+
return fail3();
|
|
75522
|
+
}
|
|
75523
|
+
function readOwned(directory, name, max2) {
|
|
75524
|
+
const identity2 = safeDirectory(directory), file = join33(directory, name);
|
|
75525
|
+
const fd = openSync6(file, constants6.O_RDONLY | constants6.O_NOFOLLOW);
|
|
75526
|
+
try {
|
|
75527
|
+
const stat2 = fstatSync6(fd);
|
|
75528
|
+
if (!stat2.isFile() || stat2.nlink !== 1 || stat2.size > max2 || stat2.size < 1 || (stat2.mode & 63) !== 0 || process.getuid && stat2.uid !== process.getuid())
|
|
75529
|
+
return fail3();
|
|
75530
|
+
const buffer = Buffer.alloc(Math.min(max2, stat2.size) + 1);
|
|
75531
|
+
let length = 0;
|
|
75532
|
+
while (length < buffer.length) {
|
|
75533
|
+
const count = readSync3(fd, buffer, length, buffer.length - length, length);
|
|
75534
|
+
if (count === 0)
|
|
75535
|
+
break;
|
|
75536
|
+
length += count;
|
|
75537
|
+
}
|
|
75538
|
+
const bytes = buffer.subarray(0, length), after2 = fstatSync6(fd);
|
|
75539
|
+
unchangedDirectory(directory, identity2);
|
|
75540
|
+
if (bytes.length !== stat2.size || stat2.size !== after2.size || stat2.mtimeMs !== after2.mtimeMs || stat2.ctimeMs !== after2.ctimeMs)
|
|
75541
|
+
return fail3();
|
|
75542
|
+
return bytes;
|
|
75543
|
+
} finally {
|
|
75544
|
+
closeSync6(fd);
|
|
75545
|
+
}
|
|
75546
|
+
}
|
|
75547
|
+
function writeOwned(directory, name, bytes) {
|
|
75548
|
+
const fd = openSync6(join33(directory, name), constants6.O_WRONLY | constants6.O_CREAT | constants6.O_EXCL | constants6.O_NOFOLLOW, 384);
|
|
75549
|
+
try {
|
|
75550
|
+
writeFileSync15(fd, bytes);
|
|
75551
|
+
fsyncSync(fd);
|
|
75552
|
+
} finally {
|
|
75553
|
+
closeSync6(fd);
|
|
75554
|
+
}
|
|
75555
|
+
}
|
|
75556
|
+
function save(directory, value) {
|
|
75557
|
+
const identity2 = safeDirectory(directory), temporary = `.receipt-${randomUUID3()}.json`;
|
|
75558
|
+
writeOwned(directory, temporary, JSON.stringify(value) + `
|
|
75559
|
+
`);
|
|
75560
|
+
unchangedDirectory(directory, identity2);
|
|
75561
|
+
renameSync7(join33(directory, temporary), join33(directory, "receipt.json"));
|
|
75562
|
+
const fd = openSync6(directory, constants6.O_RDONLY | constants6.O_NOFOLLOW);
|
|
75563
|
+
try {
|
|
75564
|
+
fsyncSync(fd);
|
|
75565
|
+
} finally {
|
|
75566
|
+
closeSync6(fd);
|
|
75567
|
+
}
|
|
75568
|
+
}
|
|
75569
|
+
function bind2(client, receipt) {
|
|
75570
|
+
if (["apiOrigin", "organizationId", "userId", "membershipId"].some((k) => client[k] !== receipt[k]))
|
|
75571
|
+
throw new PrivatePublicationError("PUBLICATION_IDENTITY_CHANGED", "The fresh session does not match the recovery directory's server, account and membership.");
|
|
75572
|
+
}
|
|
75573
|
+
async function locked(directory, action) {
|
|
75574
|
+
const identity2 = safeDirectory(directory), file = join33(directory, "operation.lock");
|
|
75575
|
+
let fd;
|
|
75576
|
+
try {
|
|
75577
|
+
fd = openSync6(file, constants6.O_WRONLY | constants6.O_CREAT | constants6.O_EXCL | constants6.O_NOFOLLOW, 384);
|
|
75578
|
+
} catch {
|
|
75579
|
+
throw new PrivatePublicationError("PUBLICATION_RECOVERY_BUSY", "Another operation holds this recovery directory. If it crashed, confirm that process has stopped before explicitly removing operation.lock and resuming.");
|
|
75580
|
+
}
|
|
75581
|
+
const lock = fstatSync6(fd);
|
|
75582
|
+
try {
|
|
75583
|
+
writeFileSync15(fd, JSON.stringify({ pid: process.pid }) + `
|
|
75584
|
+
`);
|
|
75585
|
+
fsyncSync(fd);
|
|
75586
|
+
return await action();
|
|
75587
|
+
} finally {
|
|
75588
|
+
closeSync6(fd);
|
|
75589
|
+
unchangedDirectory(directory, identity2);
|
|
75590
|
+
const now3 = lstatSync8(file);
|
|
75591
|
+
if (now3.dev !== lock.dev || now3.ino !== lock.ino || !now3.isFile() || now3.isSymbolicLink())
|
|
75592
|
+
fail3();
|
|
75593
|
+
unlinkSync2(file);
|
|
75594
|
+
}
|
|
75595
|
+
}
|
|
75596
|
+
function readPrivatePublicationRecovery(directory) {
|
|
75597
|
+
try {
|
|
75598
|
+
const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(readOwned(directory, "receipt.json", 65536)));
|
|
75599
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).sort().join(",") !== ["contractVersion", "apiOrigin", "organizationId", "userId", "membershipId", "skillId", "declaration", "phase", "intent"].sort().join(",") || value.contractVersion !== 1 || ![value.organizationId, value.userId, value.membershipId, value.skillId].every(publicationUuid) || typeof value.apiOrigin !== "string" || normalizeSkillsApiOrigin(value.apiOrigin) !== value.apiOrigin || !["prepared", "begin_uncertain", "awaiting_upload", "upload_uncertain", "uploaded", "finalize_uncertain", "observed"].includes(value.phase))
|
|
75600
|
+
return fail3();
|
|
75601
|
+
value.declaration = checkedPublicationDeclaration(value.declaration);
|
|
75602
|
+
if (value.intent !== null)
|
|
75603
|
+
value.intent = checkedPublicationView(value.intent, value.skillId, undefined, value.declaration);
|
|
75604
|
+
if (value.intent === null && !["prepared", "begin_uncertain"].includes(value.phase))
|
|
75605
|
+
return fail3();
|
|
75606
|
+
const bytes = readOwned(directory, "bundle.tgz", PRIVATE_PUBLICATION_MAX_BYTES);
|
|
75607
|
+
if (bytes.length !== value.declaration.archiveByteSize || publicationSha256(bytes) !== value.declaration.archiveSha256)
|
|
75608
|
+
return fail3();
|
|
75609
|
+
return { receipt: value, bytes };
|
|
75610
|
+
} catch {
|
|
75611
|
+
return fail3();
|
|
75612
|
+
}
|
|
75613
|
+
}
|
|
75614
|
+
async function preparePrivatePublication(client, sourceDirectory, recoveryDirectory, input) {
|
|
75615
|
+
if (!publicationUuid(input.skillId) || !(input.expectedCurrentVersionId === null || publicationUuid(input.expectedCurrentVersionId)) || input.idempotencyKey !== undefined && !publicationUuid(input.idempotencyKey))
|
|
75616
|
+
return fail3();
|
|
75617
|
+
const packed = packSkillBundle(sourceDirectory, { maxUnpackedBytes: 32 * 1024 * 1024 });
|
|
75618
|
+
const inspected = await inspectSkillBundle(packed.bytes, { limits: { compressedBytes: PRIVATE_PUBLICATION_MAX_BYTES } });
|
|
75619
|
+
const manifest = inspected.entries.find((entry) => entry.path === "skill.json");
|
|
75620
|
+
if (!manifest || manifest.bytes.length > 16384 || !(await verifyContentHashFromEntries(inspected.entries)).valid)
|
|
75621
|
+
throw new PrivatePublicationError("PUBLICATION_BUNDLE_INVALID", "The skill must have a valid skill.json with its current content hash. Validate the skill before publishing.");
|
|
75622
|
+
const manifestText = new TextDecoder("utf-8", { fatal: true }).decode(manifest.bytes);
|
|
75623
|
+
const declaration = checkedPublicationDeclaration({
|
|
75624
|
+
idempotencyKey: input.idempotencyKey ?? randomUUID3(),
|
|
75625
|
+
version: JSON.parse(manifestText).version,
|
|
75626
|
+
expectedCurrentVersionId: input.expectedCurrentVersionId,
|
|
75627
|
+
manifestText,
|
|
75628
|
+
archiveSha256: inspected.sha256,
|
|
75629
|
+
archiveByteSize: packed.bytes.length
|
|
75630
|
+
});
|
|
75631
|
+
const receipt = {
|
|
75632
|
+
contractVersion: 1,
|
|
75633
|
+
apiOrigin: client.apiOrigin,
|
|
75634
|
+
organizationId: client.organizationId,
|
|
75635
|
+
userId: client.userId,
|
|
75636
|
+
membershipId: client.membershipId,
|
|
75637
|
+
skillId: input.skillId,
|
|
75638
|
+
declaration,
|
|
75639
|
+
phase: "prepared",
|
|
75640
|
+
intent: null
|
|
75641
|
+
};
|
|
75642
|
+
if (!isAbsolute4(recoveryDirectory) || resolve3(recoveryDirectory) !== recoveryDirectory || realpathSync2(dirname11(recoveryDirectory)) !== dirname11(recoveryDirectory))
|
|
75643
|
+
return fail3();
|
|
75644
|
+
mkdirSync15(recoveryDirectory, { mode: 448 });
|
|
75645
|
+
safeDirectory(recoveryDirectory);
|
|
75646
|
+
const parent = openSync6(dirname11(recoveryDirectory), constants6.O_RDONLY | constants6.O_NOFOLLOW);
|
|
75647
|
+
try {
|
|
75648
|
+
fsyncSync(parent);
|
|
75649
|
+
} finally {
|
|
75650
|
+
closeSync6(parent);
|
|
75651
|
+
}
|
|
75652
|
+
writeOwned(recoveryDirectory, "bundle.tgz", packed.bytes);
|
|
75653
|
+
save(recoveryDirectory, receipt);
|
|
75654
|
+
return receipt;
|
|
75655
|
+
}
|
|
75656
|
+
function privatePublicationResult(directory, receipt) {
|
|
75657
|
+
const state = receipt.intent?.state ?? receipt.phase, committed = state === "committed";
|
|
75658
|
+
const nextAction = committed ? "The version is published. Private execution remains unavailable." : ["rejected", "cancelled", "expired"].includes(state) ? "This intent is terminal. Inspect the result before explicitly preparing another version." : state === "needs_attention" ? "Keep this intent and contact the service operator; do not create a replacement or upload again." : receipt.phase === "upload_uncertain" ? "Run publication resume with this recovery directory to finalize the same intent without another upload." : receipt.intent ? "Run publication status or resume with this recovery directory; cancel explicitly if you want to stop." : "Run publication resume with this recovery directory to reconcile the identical request key and declaration.";
|
|
75659
|
+
return {
|
|
75660
|
+
recoveryDirectory: directory,
|
|
75661
|
+
skillId: receipt.skillId,
|
|
75662
|
+
intentId: receipt.intent?.id ?? null,
|
|
75663
|
+
state,
|
|
75664
|
+
versionId: receipt.intent?.versionId ?? null,
|
|
75665
|
+
committed,
|
|
75666
|
+
executionEnabled: false,
|
|
75667
|
+
nextAction
|
|
75668
|
+
};
|
|
75669
|
+
}
|
|
75670
|
+
async function continuePrivatePublication(client, directory, options) {
|
|
75671
|
+
if (options.confirm !== true)
|
|
75672
|
+
throw new PrivatePublicationError("PUBLICATION_CONFIRM_REQUIRED", "Explicit upload confirmation is required.");
|
|
75673
|
+
if (options.waitMs !== undefined && (!Number.isSafeInteger(options.waitMs) || options.waitMs < 0 || options.waitMs > 300000))
|
|
75674
|
+
return fail3();
|
|
75675
|
+
return locked(directory, () => continueLocked(client, directory, options));
|
|
75676
|
+
}
|
|
75677
|
+
async function continueLocked(client, directory, options) {
|
|
75678
|
+
const { receipt, bytes } = readPrivatePublicationRecovery(directory);
|
|
75679
|
+
bind2(client, receipt);
|
|
75680
|
+
if (!receipt.intent) {
|
|
75681
|
+
receipt.phase = "begin_uncertain";
|
|
75682
|
+
save(directory, receipt);
|
|
75683
|
+
receipt.intent = await client.begin(receipt.skillId, receipt.declaration);
|
|
75684
|
+
receipt.phase = "awaiting_upload";
|
|
75685
|
+
save(directory, receipt);
|
|
75686
|
+
} else {
|
|
75687
|
+
receipt.intent = checkedPublicationView(await client.get(receipt.skillId, receipt.intent.id), receipt.skillId, receipt.intent.id, receipt.declaration);
|
|
75688
|
+
save(directory, receipt);
|
|
75689
|
+
}
|
|
75690
|
+
if (receipt.intent.state === "awaiting_upload") {
|
|
75691
|
+
if (receipt.phase === "awaiting_upload") {
|
|
75692
|
+
receipt.phase = "upload_uncertain";
|
|
75693
|
+
save(directory, receipt);
|
|
75694
|
+
try {
|
|
75695
|
+
await client.upload(receipt.skillId, receipt.intent, bytes);
|
|
75696
|
+
} catch (error2) {
|
|
75697
|
+
if (error2 instanceof PrivatePublicationError && !error2.uncertain) {
|
|
75698
|
+
receipt.phase = "awaiting_upload";
|
|
75699
|
+
save(directory, receipt);
|
|
75700
|
+
}
|
|
75701
|
+
throw error2;
|
|
75702
|
+
}
|
|
75703
|
+
receipt.phase = "uploaded";
|
|
75704
|
+
save(directory, receipt);
|
|
75705
|
+
}
|
|
75706
|
+
receipt.phase = "finalize_uncertain";
|
|
75707
|
+
save(directory, receipt);
|
|
75708
|
+
receipt.intent = checkedPublicationView(await client.finalize(receipt.skillId, receipt.intent.id), receipt.skillId, receipt.intent.id, receipt.declaration);
|
|
75709
|
+
receipt.phase = "observed";
|
|
75710
|
+
save(directory, receipt);
|
|
75711
|
+
}
|
|
75712
|
+
if (options.waitMs !== undefined && options.waitMs > 0 && ["queued", "verifying"].includes(receipt.intent.state)) {
|
|
75713
|
+
receipt.intent = checkedPublicationView(await client.wait(receipt.skillId, receipt.intent.id, { timeoutMs: options.waitMs }), receipt.skillId, receipt.intent.id, receipt.declaration);
|
|
75714
|
+
receipt.phase = "observed";
|
|
75715
|
+
save(directory, receipt);
|
|
75716
|
+
}
|
|
75717
|
+
return privatePublicationResult(directory, receipt);
|
|
75718
|
+
}
|
|
75719
|
+
async function inspectPrivatePublication(client, directory, cancel = false) {
|
|
75720
|
+
return locked(directory, () => inspectLocked(client, directory, cancel));
|
|
75721
|
+
}
|
|
75722
|
+
async function inspectLocked(client, directory, cancel) {
|
|
75723
|
+
const { receipt } = readPrivatePublicationRecovery(directory);
|
|
75724
|
+
bind2(client, receipt);
|
|
75725
|
+
if (receipt.intent) {
|
|
75726
|
+
receipt.intent = checkedPublicationView(await (cancel ? client.cancel(receipt.skillId, receipt.intent.id) : client.get(receipt.skillId, receipt.intent.id)), receipt.skillId, receipt.intent.id, receipt.declaration);
|
|
75727
|
+
save(directory, receipt);
|
|
75728
|
+
} else if (cancel)
|
|
75729
|
+
throw new PrivatePublicationError("PUBLICATION_INTENT_UNKNOWN", "Reconcile the saved begin request with publication resume before cancelling its intent.");
|
|
75730
|
+
return privatePublicationResult(directory, receipt);
|
|
75731
|
+
}
|
|
75732
|
+
var fail3 = () => {
|
|
75733
|
+
throw new PrivatePublicationError("PUBLICATION_RECOVERY_INVALID", "The recovery directory is invalid or changed. Preserve it and inspect the existing intent; do not start a replacement automatically.");
|
|
75734
|
+
};
|
|
75735
|
+
var init_private_publication_recovery = __esm(() => {
|
|
75736
|
+
init_skill_bundle();
|
|
75737
|
+
init_skill_hash();
|
|
75738
|
+
init_remote_private_publications();
|
|
75739
|
+
init_fleet_credentials();
|
|
75740
|
+
});
|
|
75741
|
+
|
|
75742
|
+
// src/mcp/private-publication-tools.ts
|
|
75743
|
+
import { isAbsolute as isAbsolute5 } from "path";
|
|
75744
|
+
function registerPrivatePublicationTools(server) {
|
|
75745
|
+
const uuid6 = exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/);
|
|
75746
|
+
const directory = exports_external.string().max(4096).refine(isAbsolute5, "Use an absolute local directory");
|
|
75747
|
+
const verification = { email: exports_external.string().email().max(254), code: exports_external.string().regex(/^\d{6}$/), userId: uuid6, membershipId: uuid6, recoveryDirectory: directory };
|
|
75748
|
+
const description = "Manage a private source publication using fresh workspace-bound verification and a host-local recovery directory. Publishing requires explicit UUID current-version comparison and upload consent. Resume reuses immutable saved bytes and intent; an uncertain PUT is finalized for server verification, never uploaded twice. Sessions and signed URLs are not returned or persisted. Private execution is unavailable. Host request history may contain the supplied verification code.";
|
|
75749
|
+
const handler = (action) => async (value) => {
|
|
75750
|
+
try {
|
|
75751
|
+
const client = await privatePublicationSession(String(value.email), String(value.code), { userId: String(value.userId), membershipId: String(value.membershipId) });
|
|
75752
|
+
const recovery = String(value.recoveryDirectory);
|
|
75753
|
+
if (action === "publish")
|
|
75754
|
+
await preparePrivatePublication(client, String(value.directory), recovery, { skillId: String(value.skillId), expectedCurrentVersionId: value.expectedCurrentVersionId, idempotencyKey: value.idempotencyKey });
|
|
75755
|
+
const result2 = action === "publish" || action === "resume" ? await continuePrivatePublication(client, recovery, { confirm: true, waitMs: value.waitMs }) : await inspectPrivatePublication(client, recovery, action === "cancel");
|
|
75756
|
+
return mcpJson(result2);
|
|
75757
|
+
} catch (error2) {
|
|
75758
|
+
const result2 = privatePublicationCustomerError(error2);
|
|
75759
|
+
return mcpError(result2.code, result2.error);
|
|
75760
|
+
}
|
|
75761
|
+
};
|
|
75762
|
+
server.registerTool("publish_private_skill", {
|
|
75763
|
+
title: "Publish private skill",
|
|
75764
|
+
description,
|
|
75765
|
+
annotations: { destructiveHint: true, readOnlyHint: false, idempotentHint: false },
|
|
75766
|
+
inputSchema: exports_external.object({ ...verification, directory, skillId: uuid6, expectedCurrentVersionId: uuid6.nullable(), idempotencyKey: uuid6.optional(), confirm: exports_external.literal(true), waitMs: exports_external.number().int().min(0).max(300000).optional() }).strict()
|
|
75767
|
+
}, handler("publish"));
|
|
75768
|
+
server.registerTool("get_private_publication", {
|
|
75769
|
+
title: "Get private publication",
|
|
75770
|
+
description,
|
|
75771
|
+
annotations: { destructiveHint: false, readOnlyHint: true, idempotentHint: true },
|
|
75772
|
+
inputSchema: exports_external.object(verification).strict()
|
|
75773
|
+
}, handler("get"));
|
|
75774
|
+
server.registerTool("resume_private_publication", {
|
|
75775
|
+
title: "Resume private publication",
|
|
75776
|
+
description,
|
|
75777
|
+
annotations: { destructiveHint: true, readOnlyHint: false, idempotentHint: true },
|
|
75778
|
+
inputSchema: exports_external.object({ ...verification, confirm: exports_external.literal(true), waitMs: exports_external.number().int().min(0).max(300000).optional() }).strict()
|
|
75779
|
+
}, handler("resume"));
|
|
75780
|
+
server.registerTool("cancel_private_publication", {
|
|
75781
|
+
title: "Cancel private publication",
|
|
75782
|
+
description,
|
|
75783
|
+
annotations: { destructiveHint: true, readOnlyHint: false, idempotentHint: true },
|
|
75784
|
+
inputSchema: exports_external.object({ ...verification, confirm: exports_external.literal(true) }).strict()
|
|
75785
|
+
}, handler("cancel"));
|
|
75786
|
+
}
|
|
75787
|
+
var init_private_publication_tools = __esm(() => {
|
|
75788
|
+
init_zod();
|
|
75789
|
+
init_private_publication_customer();
|
|
75790
|
+
init_private_publication_recovery();
|
|
75791
|
+
init_helpers();
|
|
75792
|
+
});
|
|
75793
|
+
|
|
74683
75794
|
// src/mcp/remote-customer-tools.ts
|
|
74684
75795
|
function registerRemoteCustomerTools(server) {
|
|
74685
75796
|
registerRemoteInvitationTools(server);
|
|
75797
|
+
registerPrivatePublicationTools(server);
|
|
74686
75798
|
const memberRole = exports_external.enum(["owner", "admin", "member", "viewer"]);
|
|
74687
75799
|
const memberInput = {
|
|
74688
75800
|
membershipId: exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/),
|
|
@@ -74800,8 +75912,16 @@ function registerRemoteCustomerTools(server) {
|
|
|
74800
75912
|
server.registerTool("quote_skill", {
|
|
74801
75913
|
title: "Quote Remote Skill",
|
|
74802
75914
|
description: "Get the configured server's credit quote without submitting a run.",
|
|
74803
|
-
inputSchema: {
|
|
74804
|
-
|
|
75915
|
+
inputSchema: {
|
|
75916
|
+
name: exports_external.string(),
|
|
75917
|
+
input: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
|
|
75918
|
+
args: exports_external.array(exports_external.string()).optional(),
|
|
75919
|
+
files: exports_external.array(exports_external.object({ name: exports_external.string(), base64: exports_external.string().max(1398104), contentType: exports_external.string().optional() })).max(10).optional().describe("The same inline files to submit after approval; quoted descriptors bind their exact bytes, names and types.")
|
|
75920
|
+
}
|
|
75921
|
+
}, ({ name, input, args, files }) => callRemote((client) => {
|
|
75922
|
+
const descriptors = describeRemoteFiles(decodeRemoteFiles(files ?? []));
|
|
75923
|
+
return client.quoteRun(name, input, args, descriptors.length ? descriptors : undefined);
|
|
75924
|
+
}));
|
|
74805
75925
|
server.registerTool("download_run_artifact", {
|
|
74806
75926
|
title: "Download Verified Run Artifact",
|
|
74807
75927
|
description: "Return verified artifact bytes as base64 (at most 1 MiB); use the CLI for larger files.",
|
|
@@ -74835,6 +75955,7 @@ async function freshAccount(action, operation) {
|
|
|
74835
75955
|
}
|
|
74836
75956
|
var init_remote_customer_tools = __esm(() => {
|
|
74837
75957
|
init_remote_invitation_tools();
|
|
75958
|
+
init_private_publication_tools();
|
|
74838
75959
|
init_zod();
|
|
74839
75960
|
init_remote_auth();
|
|
74840
75961
|
init_workspace_profile();
|
|
@@ -74842,6 +75963,7 @@ var init_remote_customer_tools = __esm(() => {
|
|
|
74842
75963
|
init_remote_client();
|
|
74843
75964
|
init_remote_workspace_leave();
|
|
74844
75965
|
init_helpers();
|
|
75966
|
+
init_remote_files();
|
|
74845
75967
|
});
|
|
74846
75968
|
|
|
74847
75969
|
// src/mcp/server.ts
|
|
@@ -74929,9 +76051,9 @@ var init_mcp2 = __esm(() => {
|
|
|
74929
76051
|
});
|
|
74930
76052
|
|
|
74931
76053
|
// src/cli/commands/runtime-mcp.ts
|
|
74932
|
-
import { existsSync as existsSync30, mkdirSync as
|
|
76054
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync16, readFileSync as readFileSync26, writeFileSync as writeFileSync16 } from "fs";
|
|
74933
76055
|
import { homedir as homedir7 } from "os";
|
|
74934
|
-
import { dirname as
|
|
76056
|
+
import { dirname as dirname12, join as join34 } from "path";
|
|
74935
76057
|
async function handleMcp(options) {
|
|
74936
76058
|
if (options.register) {
|
|
74937
76059
|
let agents;
|
|
@@ -74974,24 +76096,24 @@ async function registerMcpForAgent(agent, command) {
|
|
|
74974
76096
|
case "codex":
|
|
74975
76097
|
return registerCodexMcp(command);
|
|
74976
76098
|
case "gemini":
|
|
74977
|
-
return registerJsonMcpServer(agent,
|
|
76099
|
+
return registerJsonMcpServer(agent, join34(homedir7(), ".gemini", "settings.json"), "mcpServers", {
|
|
74978
76100
|
command,
|
|
74979
76101
|
args: []
|
|
74980
76102
|
});
|
|
74981
76103
|
case "pi":
|
|
74982
|
-
return registerJsonMcpServer(agent,
|
|
76104
|
+
return registerJsonMcpServer(agent, join34(homedir7(), ".pi", "agent", "mcp.json"), "mcpServers", {
|
|
74983
76105
|
command,
|
|
74984
76106
|
args: []
|
|
74985
76107
|
});
|
|
74986
76108
|
case "opencode":
|
|
74987
76109
|
return registerOpenCodeMcp(command);
|
|
74988
76110
|
case "cursor":
|
|
74989
|
-
return registerJsonMcpServer(agent,
|
|
76111
|
+
return registerJsonMcpServer(agent, join34(homedir7(), ".cursor", "mcp.json"), "mcpServers", {
|
|
74990
76112
|
command,
|
|
74991
76113
|
args: []
|
|
74992
76114
|
});
|
|
74993
76115
|
case "windsurf":
|
|
74994
|
-
return registerJsonMcpServer(agent,
|
|
76116
|
+
return registerJsonMcpServer(agent, join34(homedir7(), ".windsurf", "mcp.json"), "mcpServers", {
|
|
74995
76117
|
command,
|
|
74996
76118
|
args: []
|
|
74997
76119
|
});
|
|
@@ -75014,7 +76136,7 @@ async function registerClaudeMcp(command) {
|
|
|
75014
76136
|
if (exitCode === 0) {
|
|
75015
76137
|
return { agent: "claude", success: true, command: cliCommand };
|
|
75016
76138
|
}
|
|
75017
|
-
const fallback = registerJsonMcpServer("claude",
|
|
76139
|
+
const fallback = registerJsonMcpServer("claude", join34(homedir7(), ".claude", ".mcp.json"), "mcpServers", {
|
|
75018
76140
|
command,
|
|
75019
76141
|
args: []
|
|
75020
76142
|
});
|
|
@@ -75024,7 +76146,7 @@ async function registerClaudeMcp(command) {
|
|
|
75024
76146
|
error: fallback.success ? undefined : `claude exited with ${exitCode}: ${(stderr || stdout).trim()}`
|
|
75025
76147
|
};
|
|
75026
76148
|
} catch (err) {
|
|
75027
|
-
const fallback = registerJsonMcpServer("claude",
|
|
76149
|
+
const fallback = registerJsonMcpServer("claude", join34(homedir7(), ".claude", ".mcp.json"), "mcpServers", {
|
|
75028
76150
|
command,
|
|
75029
76151
|
args: []
|
|
75030
76152
|
});
|
|
@@ -75036,7 +76158,7 @@ async function registerClaudeMcp(command) {
|
|
|
75036
76158
|
}
|
|
75037
76159
|
}
|
|
75038
76160
|
function registerCodexMcp(command) {
|
|
75039
|
-
const path =
|
|
76161
|
+
const path = join34(homedir7(), ".codex", "config.toml");
|
|
75040
76162
|
const config2 = `[mcp_servers.${MCP_SERVER_NAME}]
|
|
75041
76163
|
command = ${JSON.stringify(command)}`;
|
|
75042
76164
|
try {
|
|
@@ -75048,7 +76170,7 @@ command = ${JSON.stringify(command)}`;
|
|
|
75048
76170
|
}
|
|
75049
76171
|
}
|
|
75050
76172
|
function registerOpenCodeMcp(command) {
|
|
75051
|
-
const path =
|
|
76173
|
+
const path = join34(homedir7(), ".config", "opencode", "opencode.json");
|
|
75052
76174
|
const config2 = JSON.stringify({
|
|
75053
76175
|
$schema: "https://opencode.ai/config.json",
|
|
75054
76176
|
mcp: {
|
|
@@ -75105,8 +76227,8 @@ function writeJsonObject(path, data) {
|
|
|
75105
76227
|
`);
|
|
75106
76228
|
}
|
|
75107
76229
|
function writeTextFile(path, content) {
|
|
75108
|
-
|
|
75109
|
-
|
|
76230
|
+
mkdirSync16(dirname12(path), { recursive: true });
|
|
76231
|
+
writeFileSync16(path, content.endsWith(`
|
|
75110
76232
|
`) ? content : `${content}
|
|
75111
76233
|
`);
|
|
75112
76234
|
}
|
|
@@ -75136,7 +76258,7 @@ function findCommandOnPath(command) {
|
|
|
75136
76258
|
for (const dir of pathValue.split(":")) {
|
|
75137
76259
|
if (!dir)
|
|
75138
76260
|
continue;
|
|
75139
|
-
const candidate =
|
|
76261
|
+
const candidate = join34(dir, command);
|
|
75140
76262
|
if (existsSync30(candidate))
|
|
75141
76263
|
return candidate;
|
|
75142
76264
|
}
|
|
@@ -75182,7 +76304,7 @@ async function execute(options, action) {
|
|
|
75182
76304
|
if (options.json)
|
|
75183
76305
|
console.log(JSON.stringify({
|
|
75184
76306
|
error: message,
|
|
75185
|
-
...error2 instanceof RemoteCapabilityUnavailableError ? { code: error2.code, status: error2.status } : {}
|
|
76307
|
+
...error2 instanceof RemoteCapabilityUnavailableError || error2 instanceof RemoteQuoteUnavailableError ? { code: error2.code, status: error2.status } : {}
|
|
75186
76308
|
}));
|
|
75187
76309
|
else
|
|
75188
76310
|
console.error(message);
|
|
@@ -75198,8 +76320,8 @@ var exports_runtime = {};
|
|
|
75198
76320
|
__export(exports_runtime, {
|
|
75199
76321
|
registerRuntime: () => registerRuntime
|
|
75200
76322
|
});
|
|
75201
|
-
import { lstatSync as
|
|
75202
|
-
import { basename as basename5, isAbsolute as
|
|
76323
|
+
import { lstatSync as lstatSync9, mkdirSync as mkdirSync17, readFileSync as readFileSync27, realpathSync as realpathSync3, writeFileSync as writeFileSync17 } from "fs";
|
|
76324
|
+
import { basename as basename5, isAbsolute as isAbsolute6, join as join35 } from "path";
|
|
75203
76325
|
import { createInterface } from "readline";
|
|
75204
76326
|
function registerRuntime(parent) {
|
|
75205
76327
|
parent.command("run").argument("<skill>", "Skill name").argument("[args...]", "Arguments to pass to the skill").allowUnknownOption(true).passThroughOptions(true).option("--json", "Output result as JSON", false).option("--remote", "Run on the configured server, using its catalog and quote", false).option("--yes", "Approve the server's quoted credit cost for this run", false).option("--idempotency-key <key>", "Reuse this key when retrying the same remote submission").option("--file <path>", "Attach a local input file to a remote run (repeatable)", (value, prior) => [...prior, value], []).option("--wait", "Poll remote runs until a terminal status", false).option("--poll-interval-ms <ms>", "Remote polling interval in milliseconds", "1000").option("--poll-timeout-ms <ms>", "Maximum time to wait for a remote run", "300000").description("Run a skill directly").action(async (name, args2, options) => handleRun(name, args2, options));
|
|
@@ -75282,14 +76404,14 @@ Updating ${name}...
|
|
|
75282
76404
|
async function readUpdatedVersion(bunExecutable) {
|
|
75283
76405
|
const output = await readUpdateCommand([bunExecutable, "pm", "bin", "-g"]);
|
|
75284
76406
|
const globalBin = output.replace(/\r?\n$/, "");
|
|
75285
|
-
if (!globalBin || !
|
|
76407
|
+
if (!globalBin || !isAbsolute6(globalBin) || /[\r\n\0]/.test(globalBin))
|
|
75286
76408
|
throw new Error("Update bin discovery failed");
|
|
75287
76409
|
const installed = Bun.which("skills", { PATH: globalBin, cwd: globalBin });
|
|
75288
76410
|
const selected = Bun.which("skills");
|
|
75289
76411
|
if (!installed || !selected)
|
|
75290
76412
|
throw new Error("Updated command is unavailable");
|
|
75291
|
-
const selectedPath =
|
|
75292
|
-
if (selectedPath !==
|
|
76413
|
+
const selectedPath = realpathSync3(selected);
|
|
76414
|
+
if (selectedPath !== realpathSync3(installed))
|
|
75293
76415
|
throw new Error("Updated command is shadowed on PATH");
|
|
75294
76416
|
const version2 = (await readUpdateCommand([selectedPath, "--version"])).trim();
|
|
75295
76417
|
if (!new RegExp(SEMVER_PATTERN).test(version2))
|
|
@@ -75418,14 +76540,14 @@ function requireHttpUrl(value) {
|
|
|
75418
76540
|
}
|
|
75419
76541
|
function promptLine(question) {
|
|
75420
76542
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
75421
|
-
return new Promise((
|
|
76543
|
+
return new Promise((resolve4) => {
|
|
75422
76544
|
let settled = false;
|
|
75423
76545
|
const finish = (answer) => {
|
|
75424
76546
|
if (settled)
|
|
75425
76547
|
return;
|
|
75426
76548
|
settled = true;
|
|
75427
76549
|
rl.close();
|
|
75428
|
-
|
|
76550
|
+
resolve4(answer);
|
|
75429
76551
|
};
|
|
75430
76552
|
rl.once("SIGINT", () => finish(null));
|
|
75431
76553
|
rl.once("close", () => finish(null));
|
|
@@ -75468,20 +76590,23 @@ async function handleRun(name, args2, options) {
|
|
|
75468
76590
|
}
|
|
75469
76591
|
let client;
|
|
75470
76592
|
let approvedCredits = 0;
|
|
76593
|
+
let quoteReceipt;
|
|
75471
76594
|
let inputFiles = [];
|
|
75472
76595
|
if (routing.route === "remote") {
|
|
75473
76596
|
try {
|
|
75474
76597
|
parsePollingOptions(options);
|
|
75475
76598
|
inputFiles = (options.file ?? []).map((path) => {
|
|
75476
|
-
const info =
|
|
76599
|
+
const info = lstatSync9(path);
|
|
75477
76600
|
if (!info.isFile() || info.size > 20 * 1024 * 1024)
|
|
75478
76601
|
throw new Error("Input must be a regular file no larger than 20 MiB");
|
|
75479
76602
|
return { name: basename5(path), bytes: new Uint8Array(readFileSync27(path)) };
|
|
75480
76603
|
});
|
|
75481
76604
|
describeRemoteFiles(inputFiles);
|
|
75482
76605
|
client = new RemoteSkillsClient(routing.apiKey, routing.apiOrigin);
|
|
75483
|
-
const
|
|
76606
|
+
const descriptors = describeRemoteFiles(inputFiles);
|
|
76607
|
+
const quote = await client.quoteRun(skill.name, {}, args2, descriptors.length ? descriptors : undefined);
|
|
75484
76608
|
approvedCredits = quote.pricing.costCents;
|
|
76609
|
+
quoteReceipt = quote.quoteReceipt;
|
|
75485
76610
|
if (approvedCredits > 0 && !options.yes) {
|
|
75486
76611
|
if (options.json || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
75487
76612
|
throw new Error(`CREDIT_APPROVAL_REQUIRED: This run costs ${approvedCredits} credits. Review skills quote, then rerun with --yes before the skill name.`);
|
|
@@ -75530,6 +76655,7 @@ async function handleRun(name, args2, options) {
|
|
|
75530
76655
|
try {
|
|
75531
76656
|
const run = await client.submitQuotedRunWithFiles(skill.name, {}, args2, inputFiles, {
|
|
75532
76657
|
maxCredits: approvedCredits,
|
|
76658
|
+
quoteReceipt,
|
|
75533
76659
|
idempotencyKey: options.idempotencyKey ?? runContext.record.id
|
|
75534
76660
|
});
|
|
75535
76661
|
if (run.error) {
|
|
@@ -75847,7 +76973,7 @@ async function handleExportsDownload(runId, options) {
|
|
|
75847
76973
|
const canonicalSkill = typeof remoteRun.skill === "string" ? remoteRun.skill : "remote";
|
|
75848
76974
|
const requestedSkill = typeof remoteRun.requestedSlug === "string" && remoteRun.requestedSlug.trim() ? remoteRun.requestedSlug : canonicalSkill;
|
|
75849
76975
|
const exportDir = getRunExportDir(runId, requestedSkill);
|
|
75850
|
-
|
|
76976
|
+
mkdirSync17(exportDir, { recursive: true });
|
|
75851
76977
|
const downloaded = [];
|
|
75852
76978
|
for (const artifact of artifacts) {
|
|
75853
76979
|
const artifactId = String(artifact.id || "");
|
|
@@ -75855,9 +76981,9 @@ async function handleExportsDownload(runId, options) {
|
|
|
75855
76981
|
continue;
|
|
75856
76982
|
const verified = await client.getVerifiedRunArtifact(runId, artifactId);
|
|
75857
76983
|
const relativePath = safeArtifactRelativePath(typeof artifact.relativePath === "string" ? artifact.relativePath : artifact.fileName, String(artifact.fileName || artifactId));
|
|
75858
|
-
const outputPath =
|
|
76984
|
+
const outputPath = join35(exportDir, relativePath);
|
|
75859
76985
|
ensureSafeExportParent(exportDir, relativePath);
|
|
75860
|
-
|
|
76986
|
+
writeFileSync17(outputPath, verified.bytes, { flag: "wx", mode: 384 });
|
|
75861
76987
|
downloaded.push({ id: artifactId, path: outputPath, byteSize: verified.byteSize });
|
|
75862
76988
|
}
|
|
75863
76989
|
const payload = {
|
|
@@ -75894,14 +77020,14 @@ function ensureSafeExportParent(root, relativePath) {
|
|
|
75894
77020
|
let parent = root;
|
|
75895
77021
|
for (const part of ["", ...parts]) {
|
|
75896
77022
|
if (part)
|
|
75897
|
-
parent =
|
|
77023
|
+
parent = join35(parent, part);
|
|
75898
77024
|
try {
|
|
75899
|
-
if (!
|
|
77025
|
+
if (!lstatSync9(parent).isDirectory() || lstatSync9(parent).isSymbolicLink())
|
|
75900
77026
|
throw new Error("Unsafe artifact directory");
|
|
75901
77027
|
} catch (error2) {
|
|
75902
77028
|
if (error2.code !== "ENOENT")
|
|
75903
77029
|
throw error2;
|
|
75904
|
-
|
|
77030
|
+
mkdirSync17(parent, { mode: 448 });
|
|
75905
77031
|
}
|
|
75906
77032
|
}
|
|
75907
77033
|
}
|
|
@@ -75929,7 +77055,7 @@ async function pollRemoteRun(client, runId, options) {
|
|
|
75929
77055
|
const remaining = deadline - Date.now();
|
|
75930
77056
|
if (remaining <= 0)
|
|
75931
77057
|
break;
|
|
75932
|
-
await new Promise((
|
|
77058
|
+
await new Promise((resolve4) => setTimeout(resolve4, Math.min(options.intervalMs, remaining)));
|
|
75933
77059
|
}
|
|
75934
77060
|
return {
|
|
75935
77061
|
run: current ?? { id: runId, status: "queued" },
|
|
@@ -76239,7 +77365,7 @@ var init_completion = __esm(() => {
|
|
|
76239
77365
|
// src/lib/portable-snapshot-filter.ts
|
|
76240
77366
|
import { readdirSync as readdirSync16, statSync as statSync17 } from "fs";
|
|
76241
77367
|
import { homedir as homedir8 } from "os";
|
|
76242
|
-
import { join as
|
|
77368
|
+
import { join as join36, sep as sep3 } from "path";
|
|
76243
77369
|
function isExcludedSkillFileName(fileName) {
|
|
76244
77370
|
if (EXCLUDE_FILE_NAMES.has(fileName)) {
|
|
76245
77371
|
return true;
|
|
@@ -76262,16 +77388,16 @@ function isPortableWithinSkill(relativeParts) {
|
|
|
76262
77388
|
function homePathFor(definition, homesRoot) {
|
|
76263
77389
|
const home = homesRoot ?? homedir8();
|
|
76264
77390
|
if (definition.subClass === "skills" || definition.subClass === "custom") {
|
|
76265
|
-
return
|
|
77391
|
+
return join36(skillsDataRootForHome(home), definition.name);
|
|
76266
77392
|
}
|
|
76267
77393
|
if (definition.agent === "opencode") {
|
|
76268
|
-
return
|
|
77394
|
+
return join36(home, ".config", "opencode", "skills");
|
|
76269
77395
|
}
|
|
76270
|
-
return
|
|
77396
|
+
return join36(home, `.${definition.agent}`, "skills");
|
|
76271
77397
|
}
|
|
76272
77398
|
function destinationFor(definition, stationId, relativePath) {
|
|
76273
|
-
const category = definition.subClass === "agent-homes" ?
|
|
76274
|
-
return
|
|
77399
|
+
const category = definition.subClass === "agent-homes" ? join36("agent-homes", definition.agent ?? "") : definition.name;
|
|
77400
|
+
return join36("resources", stationId, "skills", category, ...relativePath.split(sep3));
|
|
76275
77401
|
}
|
|
76276
77402
|
function walkEntries(absoluteRoot) {
|
|
76277
77403
|
let entries;
|
|
@@ -76282,7 +77408,7 @@ function walkEntries(absoluteRoot) {
|
|
|
76282
77408
|
}
|
|
76283
77409
|
const output = [];
|
|
76284
77410
|
for (const entry of entries) {
|
|
76285
|
-
const childFull =
|
|
77411
|
+
const childFull = join36(absoluteRoot, entry.name);
|
|
76286
77412
|
if (entry.isSymbolicLink()) {
|
|
76287
77413
|
output.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
|
|
76288
77414
|
continue;
|
|
@@ -76293,7 +77419,7 @@ function walkEntries(absoluteRoot) {
|
|
|
76293
77419
|
}
|
|
76294
77420
|
const nested = walkEntries(childFull);
|
|
76295
77421
|
for (const item of nested) {
|
|
76296
|
-
output.push({ ...item, relativePath:
|
|
77422
|
+
output.push({ ...item, relativePath: join36(entry.name, item.relativePath) });
|
|
76297
77423
|
}
|
|
76298
77424
|
continue;
|
|
76299
77425
|
}
|
|
@@ -76386,22 +77512,22 @@ var init_portable_snapshot_filter = __esm(() => {
|
|
|
76386
77512
|
});
|
|
76387
77513
|
|
|
76388
77514
|
// src/lib/station-snapshot.ts
|
|
76389
|
-
import { createHash as
|
|
77515
|
+
import { createHash as createHash8 } from "crypto";
|
|
76390
77516
|
import {
|
|
76391
77517
|
copyFileSync as copyFileSync2,
|
|
76392
|
-
mkdirSync as
|
|
77518
|
+
mkdirSync as mkdirSync18,
|
|
76393
77519
|
readFileSync as readFileSync28,
|
|
76394
77520
|
statSync as statSync18,
|
|
76395
|
-
writeFileSync as
|
|
77521
|
+
writeFileSync as writeFileSync18
|
|
76396
77522
|
} from "fs";
|
|
76397
|
-
import { dirname as
|
|
77523
|
+
import { dirname as dirname14, isAbsolute as isAbsolute7, relative as relative5, resolve as resolve4, sep as sep4 } from "path";
|
|
76398
77524
|
function validateStationId(stationId) {
|
|
76399
77525
|
if (!/^[a-z0-9-]+$/.test(stationId)) {
|
|
76400
77526
|
throw new StationSnapshotError("INVALID_STATION", `station id must be a slug, got: ${stationId}`);
|
|
76401
77527
|
}
|
|
76402
77528
|
}
|
|
76403
77529
|
function sha256File(filePath) {
|
|
76404
|
-
return
|
|
77530
|
+
return createHash8("sha256").update(readFileSync28(filePath)).digest("hex");
|
|
76405
77531
|
}
|
|
76406
77532
|
function scanHome(definition, homesRoot) {
|
|
76407
77533
|
const homePath = homePathFor(definition, homesRoot);
|
|
@@ -76472,7 +77598,7 @@ function humanHomes(scanned) {
|
|
|
76472
77598
|
}));
|
|
76473
77599
|
}
|
|
76474
77600
|
function writeStationSnapshot(options) {
|
|
76475
|
-
const repoRoot =
|
|
77601
|
+
const repoRoot = resolve4(options.repoRoot ?? process.cwd());
|
|
76476
77602
|
const { scanned, plans, totalBytes } = planStationSnapshot(options);
|
|
76477
77603
|
const manifestFiles = plans.map((plan) => ({
|
|
76478
77604
|
relativePath: plan.source.relativePath,
|
|
@@ -76498,9 +77624,9 @@ function writeStationSnapshot(options) {
|
|
|
76498
77624
|
const conflicts = [];
|
|
76499
77625
|
const untouched = [];
|
|
76500
77626
|
for (const plan of plans) {
|
|
76501
|
-
const destination =
|
|
77627
|
+
const destination = resolve4(repoRoot, plan.destination);
|
|
76502
77628
|
const destinationRelative = relative5(repoRoot, destination);
|
|
76503
|
-
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep4) ||
|
|
77629
|
+
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep4) || isAbsolute7(destinationRelative)) {
|
|
76504
77630
|
throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
|
|
76505
77631
|
}
|
|
76506
77632
|
let existingDigest = null;
|
|
@@ -76521,8 +77647,8 @@ function writeStationSnapshot(options) {
|
|
|
76521
77647
|
}
|
|
76522
77648
|
let written = 0;
|
|
76523
77649
|
for (const plan of untouched) {
|
|
76524
|
-
const destination =
|
|
76525
|
-
|
|
77650
|
+
const destination = resolve4(repoRoot, plan.destination);
|
|
77651
|
+
mkdirSync18(dirname14(destination), { recursive: true });
|
|
76526
77652
|
copyFileSync2(plan.source.fullPath, destination);
|
|
76527
77653
|
written += 1;
|
|
76528
77654
|
}
|
|
@@ -76540,9 +77666,9 @@ function writeStationSnapshot(options) {
|
|
|
76540
77666
|
},
|
|
76541
77667
|
files: manifestFiles
|
|
76542
77668
|
};
|
|
76543
|
-
const manifestPath =
|
|
76544
|
-
|
|
76545
|
-
|
|
77669
|
+
const manifestPath = resolve4(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
|
|
77670
|
+
mkdirSync18(dirname14(manifestPath), { recursive: true });
|
|
77671
|
+
writeFileSync18(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
76546
77672
|
`);
|
|
76547
77673
|
return {
|
|
76548
77674
|
...base2,
|
|
@@ -76574,7 +77700,7 @@ __export(exports_create_sync_config, {
|
|
|
76574
77700
|
registerCreateSync: () => registerCreateSync
|
|
76575
77701
|
});
|
|
76576
77702
|
import { existsSync as existsSync31 } from "fs";
|
|
76577
|
-
import { join as
|
|
77703
|
+
import { join as join37 } from "path";
|
|
76578
77704
|
function registerCreateSync(parent) {
|
|
76579
77705
|
const configCmd = parent.command("config").description("Manage skills configuration");
|
|
76580
77706
|
configCmd.command("show", { isDefault: true }).option("--json", "Output as JSON", false).description("Show current merged configuration").action((options) => {
|
|
@@ -76680,7 +77806,7 @@ function handleCreate(name, options) {
|
|
|
76680
77806
|
console.log(source_default.green(`\u2713 Created custom skill '${result2.name}' at ${result2.path}`));
|
|
76681
77807
|
console.log(source_default.dim(` Category: ${result2.manifest.category}`));
|
|
76682
77808
|
console.log(source_default.dim(` Tags: ${result2.manifest.tags?.join(", ")}`));
|
|
76683
|
-
console.log(` ${source_default.cyan("Edit:")} ${
|
|
77809
|
+
console.log(` ${source_default.cyan("Edit:")} ${join37(result2.path, "src", "index.ts")}`);
|
|
76684
77810
|
console.log(` ${source_default.cyan("Run:")} skills run ${result2.name} --help`);
|
|
76685
77811
|
}
|
|
76686
77812
|
} catch (error2) {
|
|
@@ -76956,30 +78082,30 @@ var init_create_sync_config = __esm(() => {
|
|
|
76956
78082
|
});
|
|
76957
78083
|
|
|
76958
78084
|
// src/lib/station-hydrate.ts
|
|
76959
|
-
import { createHash as
|
|
78085
|
+
import { createHash as createHash9 } from "crypto";
|
|
76960
78086
|
import {
|
|
76961
78087
|
copyFileSync as copyFileSync3,
|
|
76962
|
-
mkdirSync as
|
|
78088
|
+
mkdirSync as mkdirSync19,
|
|
76963
78089
|
readdirSync as readdirSync17,
|
|
76964
78090
|
readFileSync as readFileSync29,
|
|
76965
78091
|
statSync as statSync19,
|
|
76966
|
-
writeFileSync as
|
|
78092
|
+
writeFileSync as writeFileSync19
|
|
76967
78093
|
} from "fs";
|
|
76968
|
-
import { dirname as
|
|
76969
|
-
function
|
|
78094
|
+
import { dirname as dirname15, join as join38, resolve as resolve5, sep as sep5 } from "path";
|
|
78095
|
+
function fail4(code, message, detail = []) {
|
|
76970
78096
|
throw new StationSnapshotError(code, message, detail);
|
|
76971
78097
|
}
|
|
76972
78098
|
function snapshotRootFor(repoRoot, stationId) {
|
|
76973
|
-
return
|
|
78099
|
+
return join38(repoRoot, "resources", stationId, "skills");
|
|
76974
78100
|
}
|
|
76975
78101
|
function readSnapshotManifest(repoRoot, stationId) {
|
|
76976
78102
|
const snapshotRoot = snapshotRootFor(repoRoot, stationId);
|
|
76977
|
-
const manifestPath =
|
|
78103
|
+
const manifestPath = join38(snapshotRoot, "sync-manifest.json");
|
|
76978
78104
|
let manifest;
|
|
76979
78105
|
try {
|
|
76980
78106
|
manifest = JSON.parse(readFileSync29(manifestPath, "utf8"));
|
|
76981
78107
|
} catch (error2) {
|
|
76982
|
-
|
|
78108
|
+
fail4("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error2.message}`);
|
|
76983
78109
|
}
|
|
76984
78110
|
const sourceSnapshotSha = sha256File(manifestPath);
|
|
76985
78111
|
return { manifest, manifestPath, sourceSnapshotSha };
|
|
@@ -77001,7 +78127,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
77001
78127
|
const hashMismatches = [];
|
|
77002
78128
|
const skippedByRule = [];
|
|
77003
78129
|
for (const agent of SYNC_AGENTS) {
|
|
77004
|
-
const agentRoot =
|
|
78130
|
+
const agentRoot = join38(snapshotRoot, "agent-homes", agent);
|
|
77005
78131
|
let identEntries;
|
|
77006
78132
|
try {
|
|
77007
78133
|
identEntries = readdirSync17(agentRoot, { withFileTypes: true });
|
|
@@ -77012,7 +78138,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
77012
78138
|
if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
|
|
77013
78139
|
continue;
|
|
77014
78140
|
}
|
|
77015
|
-
const identRoot =
|
|
78141
|
+
const identRoot = join38(agentRoot, identEntry.name);
|
|
77016
78142
|
const entries = walkEntries(identRoot);
|
|
77017
78143
|
for (const entry of entries) {
|
|
77018
78144
|
const relativeParts = [identEntry.name, ...entry.relativePath.split(sep5)];
|
|
@@ -77086,10 +78212,10 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
77086
78212
|
}
|
|
77087
78213
|
}
|
|
77088
78214
|
if (symlinks.length > 0) {
|
|
77089
|
-
|
|
78215
|
+
fail4("SYMLINKS_REFUSED", `${symlinks.length} symlink(s) inside the snapshot; symlinks are refused (fail closed)`);
|
|
77090
78216
|
}
|
|
77091
78217
|
if (hashMismatches.length > 0) {
|
|
77092
|
-
|
|
78218
|
+
fail4("MANIFEST_HASH_MISMATCH", `${hashMismatches.length} snapshot file(s) no longer match their sync-manifest sha256; ` + "stale or tampered content is refused (fail closed), nothing written \u2014 re-run sync to refresh the manifest", hashMismatches.map((mismatch) => `agent-homes/${mismatch.agent}/${mismatch.ident}/${mismatch.relativePath}`));
|
|
77093
78219
|
}
|
|
77094
78220
|
const byIdent = new Map;
|
|
77095
78221
|
for (const candidate of candidates) {
|
|
@@ -77159,12 +78285,12 @@ function skillSha256(skill) {
|
|
|
77159
78285
|
return sha256File(skill.files[0].winner.fullPath);
|
|
77160
78286
|
}
|
|
77161
78287
|
const joined = skill.files.map((file) => sha256File(file.winner.fullPath));
|
|
77162
|
-
return
|
|
78288
|
+
return createHash9("sha256").update(joined.sort().join(`
|
|
77163
78289
|
`)).digest("hex");
|
|
77164
78290
|
}
|
|
77165
78291
|
function writeStationHydration(options) {
|
|
77166
|
-
const repoRoot =
|
|
77167
|
-
const cacheRoot =
|
|
78292
|
+
const repoRoot = resolve5(options.repoRoot ?? process.cwd());
|
|
78293
|
+
const cacheRoot = resolve5(options.cacheRoot ?? resolveCorpusRoot());
|
|
77168
78294
|
const plan = planStationHydration(options.stationId, repoRoot);
|
|
77169
78295
|
const resultSkills = plan.winners.map((skill) => ({
|
|
77170
78296
|
ident: skill.ident,
|
|
@@ -77197,7 +78323,7 @@ function writeStationHydration(options) {
|
|
|
77197
78323
|
const toWrite = [];
|
|
77198
78324
|
for (const skill of plan.winners) {
|
|
77199
78325
|
for (const file of skill.files) {
|
|
77200
|
-
const destination =
|
|
78326
|
+
const destination = join38(cacheRoot, skill.ident, file.withinIdent);
|
|
77201
78327
|
const digest = sha256File(file.winner.fullPath);
|
|
77202
78328
|
let existingDigest = null;
|
|
77203
78329
|
try {
|
|
@@ -77214,11 +78340,11 @@ function writeStationHydration(options) {
|
|
|
77214
78340
|
}
|
|
77215
78341
|
}
|
|
77216
78342
|
if (conflicts.length > 0) {
|
|
77217
|
-
|
|
78343
|
+
fail4("CONFLICT", `${conflicts.length} conflict(s); terminal non-acceptance, nothing written`, conflicts);
|
|
77218
78344
|
}
|
|
77219
78345
|
let written = 0;
|
|
77220
78346
|
for (const entry of toWrite) {
|
|
77221
|
-
|
|
78347
|
+
mkdirSync19(dirname15(entry.destination), { recursive: true });
|
|
77222
78348
|
copyFileSync3(entry.fullPath, entry.destination);
|
|
77223
78349
|
written += 1;
|
|
77224
78350
|
}
|
|
@@ -77239,9 +78365,9 @@ function writeStationHydration(options) {
|
|
|
77239
78365
|
},
|
|
77240
78366
|
skills: resultSkills
|
|
77241
78367
|
};
|
|
77242
|
-
const hydrationManifestPath =
|
|
77243
|
-
|
|
77244
|
-
|
|
78368
|
+
const hydrationManifestPath = join38(dirname15(cacheRoot), `hydration-${options.stationId}.json`);
|
|
78369
|
+
mkdirSync19(dirname15(hydrationManifestPath), { recursive: true });
|
|
78370
|
+
writeFileSync19(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
|
|
77245
78371
|
`);
|
|
77246
78372
|
return {
|
|
77247
78373
|
...base2,
|
|
@@ -77669,8 +78795,8 @@ var init_schedule = __esm(() => {
|
|
|
77669
78795
|
});
|
|
77670
78796
|
|
|
77671
78797
|
// src/lib/registry-sync.ts
|
|
77672
|
-
import { mkdirSync as
|
|
77673
|
-
import { dirname as
|
|
78798
|
+
import { mkdirSync as mkdirSync20, writeFileSync as writeFileSync20 } from "fs";
|
|
78799
|
+
import { dirname as dirname16, relative as relative6 } from "path";
|
|
77674
78800
|
function createRegistrySyncArtifact(options = {}) {
|
|
77675
78801
|
const profile = options.profile ?? "all";
|
|
77676
78802
|
const includeDocs = options.includeDocs ?? true;
|
|
@@ -77727,8 +78853,8 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
77727
78853
|
};
|
|
77728
78854
|
}
|
|
77729
78855
|
function writeRegistrySyncArtifact(path, artifact) {
|
|
77730
|
-
|
|
77731
|
-
|
|
78856
|
+
mkdirSync20(dirname16(path), { recursive: true });
|
|
78857
|
+
writeFileSync20(path, `${JSON.stringify(artifact, null, 2)}
|
|
77732
78858
|
`);
|
|
77733
78859
|
}
|
|
77734
78860
|
function buildDocs(name) {
|
|
@@ -77760,12 +78886,12 @@ function registerRegistry(parent) {
|
|
|
77760
78886
|
async function writeJson2(value, space) {
|
|
77761
78887
|
const text2 = `${JSON.stringify(value, null, space)}
|
|
77762
78888
|
`;
|
|
77763
|
-
await new Promise((
|
|
78889
|
+
await new Promise((resolve6, reject2) => {
|
|
77764
78890
|
process.stdout.write(text2, (error2) => {
|
|
77765
78891
|
if (error2)
|
|
77766
78892
|
reject2(error2);
|
|
77767
78893
|
else
|
|
77768
|
-
|
|
78894
|
+
resolve6();
|
|
77769
78895
|
});
|
|
77770
78896
|
});
|
|
77771
78897
|
}
|
|
@@ -77793,10 +78919,10 @@ async function handleRegistrySync(options) {
|
|
|
77793
78919
|
await writeJson2(artifact, 2);
|
|
77794
78920
|
return;
|
|
77795
78921
|
}
|
|
77796
|
-
const
|
|
78922
|
+
const invalid4 = artifact.summary.invalidSkillCount ?? "not checked";
|
|
77797
78923
|
console.log(source_default.green(`Registry sync artifact written to ${options.output}`));
|
|
77798
78924
|
console.log(source_default.dim(` Skills: ${artifact.summary.skillCount}`));
|
|
77799
|
-
console.log(source_default.dim(` Invalid: ${
|
|
78925
|
+
console.log(source_default.dim(` Invalid: ${invalid4}`));
|
|
77800
78926
|
}
|
|
77801
78927
|
function registerPull(parent) {
|
|
77802
78928
|
parent.command("pull").argument("[names...]", "Skills to pull from the configured instance (name or name@version)").option("--all", "Pull every skill the instance serves", false).option("--for-machine", "Prepare this machine with the instance's full catalog (implies --all)", false).option("--json", "Output results as JSON", false).description("Fetch skills from the configured Skills instance into this machine's corpus").action(async (names, options) => {
|
|
@@ -77903,7 +79029,7 @@ __export(exports_publish, {
|
|
|
77903
79029
|
import { execFileSync } from "child_process";
|
|
77904
79030
|
import { existsSync as existsSync32, readFileSync as readFileSync30 } from "fs";
|
|
77905
79031
|
import { hostname as hostname2 } from "os";
|
|
77906
|
-
import { join as
|
|
79032
|
+
import { join as join39 } from "path";
|
|
77907
79033
|
function registerPublish(parent) {
|
|
77908
79034
|
parent.command("push").argument("<name>", "Name of a skill in the local corpus (~/.hasna/skills/installed or the migrated ~/.hasna/skills/skills)").option("--version <version>", "Override the version recorded on the instance").option("--force-new-version", "If name@version already exists with different content, publish as the next patch version", false).option("--dry-run", "Pack and validate without uploading", false).option("--json", "Output result as JSON", false).description("Publish a local skill to the configured skills instance").action(async (name, options) => {
|
|
77909
79035
|
try {
|
|
@@ -77950,7 +79076,7 @@ async function pushSkill(name, options = {}) {
|
|
|
77950
79076
|
}
|
|
77951
79077
|
const packed = packSkillBundle(skill.path, { maxUnpackedBytes: MAX_UNPACKED_BYTES });
|
|
77952
79078
|
const versionManifest = buildVersionManifest(skill.path, packed);
|
|
77953
|
-
const skillMdPath =
|
|
79079
|
+
const skillMdPath = join39(skill.path, "SKILL.md");
|
|
77954
79080
|
const skillMd = existsSync32(skillMdPath) ? readFileSync30(skillMdPath, "utf-8") : undefined;
|
|
77955
79081
|
const base2 = {
|
|
77956
79082
|
slug: skill.name,
|
|
@@ -78016,21 +79142,21 @@ async function readPublishRevision(client, slug) {
|
|
|
78016
79142
|
throw new PushSkillError("Publishing was refused because the current skill revision could not be verified.", ["Check the configured instance and connection, then retry the push. No upload was attempted."]);
|
|
78017
79143
|
}
|
|
78018
79144
|
const body = lookup.body;
|
|
78019
|
-
const
|
|
78020
|
-
const nestedError =
|
|
78021
|
-
const code = typeof
|
|
79145
|
+
const record8 = body !== null && typeof body === "object" && !Array.isArray(body) ? body : undefined;
|
|
79146
|
+
const nestedError = record8?.error;
|
|
79147
|
+
const code = typeof record8?.code === "string" ? record8.code : nestedError !== null && typeof nestedError === "object" && !Array.isArray(nestedError) ? nestedError.code : undefined;
|
|
78022
79148
|
if (lookup.status === 404 && code === "SKILL_NOT_FOUND")
|
|
78023
79149
|
return;
|
|
78024
79150
|
if (lookup.status < 200 || lookup.status >= 300) {
|
|
78025
79151
|
throw new PushSkillError(`Publishing was refused because the current skill lookup failed: HTTP ${lookup.status}.`, ["Only an explicit SKILL_NOT_FOUND response establishes an initial publish. Check access and server compatibility before retrying."]);
|
|
78026
79152
|
}
|
|
78027
|
-
const revision =
|
|
78028
|
-
if (
|
|
78029
|
-
if (
|
|
79153
|
+
const revision = record8?.revisionId;
|
|
79154
|
+
if (record8?.publicationState === "catalogue-only") {
|
|
79155
|
+
if (record8.name === slug && (record8.slug === undefined || record8.slug === slug) && revision === null)
|
|
78030
79156
|
return;
|
|
78031
79157
|
throw new PushSkillError("Publishing was refused because the catalogue-only response had contradictory identity or revision state.", ["Check the configured instance and server compatibility. No upload was attempted."]);
|
|
78032
79158
|
}
|
|
78033
|
-
if (
|
|
79159
|
+
if (record8?.slug !== slug || typeof revision !== "string" || revision.length === 0 || revision !== revision.trim() || !/^[\x21-\x7e]+$/.test(revision)) {
|
|
78034
79160
|
throw new PushSkillError("Publishing was refused because the current skill response did not contain a matching slug and valid revision.", ["Check the configured instance and server compatibility. No upload was attempted."]);
|
|
78035
79161
|
}
|
|
78036
79162
|
return revision;
|
|
@@ -78191,7 +79317,7 @@ async function readCode() {
|
|
|
78191
79317
|
function promptCode() {
|
|
78192
79318
|
const { stdin, stderr: output } = process;
|
|
78193
79319
|
const { isRaw: wasRaw, readableFlowing: wasFlowing } = stdin;
|
|
78194
|
-
return new Promise((
|
|
79320
|
+
return new Promise((resolve6) => {
|
|
78195
79321
|
let value = "", settled = false;
|
|
78196
79322
|
const finish = (answer) => {
|
|
78197
79323
|
if (settled)
|
|
@@ -78208,7 +79334,7 @@ function promptCode() {
|
|
|
78208
79334
|
`);
|
|
78209
79335
|
if (answer === null)
|
|
78210
79336
|
process.exitCode = 130;
|
|
78211
|
-
|
|
79337
|
+
resolve6(answer);
|
|
78212
79338
|
};
|
|
78213
79339
|
const cancel = () => finish(null);
|
|
78214
79340
|
const keypress = (text2, key) => {
|
|
@@ -78248,6 +79374,81 @@ var init_customer_verification = __esm(() => {
|
|
|
78248
79374
|
};
|
|
78249
79375
|
});
|
|
78250
79376
|
|
|
79377
|
+
// src/cli/commands/private-publications.ts
|
|
79378
|
+
var exports_private_publications = {};
|
|
79379
|
+
__export(exports_private_publications, {
|
|
79380
|
+
registerPrivatePublications: () => registerPrivatePublications
|
|
79381
|
+
});
|
|
79382
|
+
import { resolve as resolve6 } from "path";
|
|
79383
|
+
function verifyOptions(options, action) {
|
|
79384
|
+
if (options.userId === undefined !== (options.membershipId === undefined) || options.userId !== undefined && (!publicationUuid(options.userId) || !publicationUuid(options.membershipId)))
|
|
79385
|
+
throw new PrivatePublicationError("PUBLICATION_CONTEXT_REQUIRED", "Provide both observed --user-id and --membership-id UUIDs.");
|
|
79386
|
+
if ((action === "publish" || action === "resume" || action === "cancel") && options.confirm !== true)
|
|
79387
|
+
throw new PrivatePublicationError("PUBLICATION_CONFIRM_REQUIRED", "Use --confirm to approve this publication action.");
|
|
79388
|
+
if (action === "publish" && (!publicationUuid(options.skillId) || Boolean(options.expectEmpty) === (options.expectedCurrentVersion !== undefined) || options.expectedCurrentVersion !== undefined && !publicationUuid(options.expectedCurrentVersion) || options.idempotencyKey !== undefined && !publicationUuid(options.idempotencyKey)))
|
|
79389
|
+
throw new PrivatePublicationError("INVALID_PUBLICATION_INPUT", "Provide --skill-id and exactly one of --expect-empty or --expected-current-version; optional --idempotency-key must be a UUID.");
|
|
79390
|
+
if (options.waitSeconds !== undefined && (!/^(?:0|[1-9]\d{0,2})$/.test(options.waitSeconds) || Number(options.waitSeconds) > 300))
|
|
79391
|
+
throw new PrivatePublicationError("INVALID_PUBLICATION_INPUT", "Use --wait-seconds from 0 to 300.");
|
|
79392
|
+
if (!options.codeStdin && (options.json || !process.stdin.isTTY || !process.stderr.isTTY))
|
|
79393
|
+
throw new PrivatePublicationError("PUBLICATION_CODE_REQUIRED", "Use --code-stdin with a fresh code for noninteractive publishing.");
|
|
79394
|
+
}
|
|
79395
|
+
function print2(result2, json) {
|
|
79396
|
+
if (json)
|
|
79397
|
+
console.log(JSON.stringify(result2));
|
|
79398
|
+
else {
|
|
79399
|
+
console.log(`Publication: ${result2.state}`);
|
|
79400
|
+
console.log(`Recovery: ${result2.recoveryDirectory}`);
|
|
79401
|
+
console.log(result2.nextAction);
|
|
79402
|
+
}
|
|
79403
|
+
if (!result2.committed && !["cancelled", "expired"].includes(result2.state))
|
|
79404
|
+
process.exitCode = 2;
|
|
79405
|
+
}
|
|
79406
|
+
function registerPrivatePublications(parent) {
|
|
79407
|
+
const group = parent.command("publication").description("Publish and reconcile private source versions on a compatible hosted server; execution is separate");
|
|
79408
|
+
const publishCommand = group.command("publish").allowExcessArguments(false).description("Publish private source with explicit current-version comparison").requiredOption("--recovery-dir <directory>", "New recovery directory for publish; existing directory for other actions").requiredOption("--email <email>", "Account email for fresh interactive verification").option("--user-id <uuid>", "Observed account UUID (required without an enrolled named profile)").option("--membership-id <uuid>", "Observed workspace membership UUID").option("--code-stdin", "Read a fresh six-digit verification code from stdin").option("--json", "Output a safe result without session credentials or upload URLs").option("--confirm", "Explicitly approve this action").argument("<directory>", "Validated local skill directory").requiredOption("--skill-id <uuid>", "Existing private/team skill UUID").option("--expected-current-version <uuid>", "Compare against the observed current version UUID").option("--expect-empty", "Require the skill to have no current version").option("--idempotency-key <uuid>", "Stable intent key; generated and saved before begin when omitted").option("--wait-seconds <seconds>", "Bounded status wait from 0 to 300", "60");
|
|
79409
|
+
const statusCommand = group.command("status").allowExcessArguments(false).description("Inspect the exact saved publication").requiredOption("--recovery-dir <directory>", "New recovery directory for publish; existing directory for other actions").requiredOption("--email <email>", "Account email for fresh interactive verification").option("--user-id <uuid>", "Observed account UUID (required without an enrolled named profile)").option("--membership-id <uuid>", "Observed workspace membership UUID").option("--code-stdin", "Read a fresh six-digit verification code from stdin").option("--json", "Output a safe result without session credentials or upload URLs");
|
|
79410
|
+
const resumeCommand = group.command("resume").allowExcessArguments(false).description("Reconcile the saved intent without creating a replacement").requiredOption("--recovery-dir <directory>", "New recovery directory for publish; existing directory for other actions").requiredOption("--email <email>", "Account email for fresh interactive verification").option("--user-id <uuid>", "Observed account UUID (required without an enrolled named profile)").option("--membership-id <uuid>", "Observed workspace membership UUID").option("--code-stdin", "Read a fresh six-digit verification code from stdin").option("--json", "Output a safe result without session credentials or upload URLs").option("--confirm", "Explicitly approve this action").option("--wait-seconds <seconds>", "Bounded status wait from 0 to 300", "60");
|
|
79411
|
+
const cancelCommand = group.command("cancel").allowExcessArguments(false).description("Cancel the exact saved publication").requiredOption("--recovery-dir <directory>", "New recovery directory for publish; existing directory for other actions").requiredOption("--email <email>", "Account email for fresh interactive verification").option("--user-id <uuid>", "Observed account UUID (required without an enrolled named profile)").option("--membership-id <uuid>", "Observed workspace membership UUID").option("--code-stdin", "Read a fresh six-digit verification code from stdin").option("--json", "Output a safe result without session credentials or upload URLs").option("--confirm", "Explicitly approve this action");
|
|
79412
|
+
for (const [action, command] of [["publish", publishCommand], ["status", statusCommand], ["resume", resumeCommand], ["cancel", cancelCommand]]) {
|
|
79413
|
+
command.action(async (...args2) => {
|
|
79414
|
+
const options = action === "publish" ? args2[1] : args2[0];
|
|
79415
|
+
try {
|
|
79416
|
+
verifyOptions(options, action);
|
|
79417
|
+
let code;
|
|
79418
|
+
if (options.codeStdin)
|
|
79419
|
+
code = await readCode();
|
|
79420
|
+
else {
|
|
79421
|
+
await new RemoteSkillsAuthClient(getApiUrl("Publish private skills")).requestCode(options.email);
|
|
79422
|
+
code = await promptCode();
|
|
79423
|
+
}
|
|
79424
|
+
if (code === null)
|
|
79425
|
+
return;
|
|
79426
|
+
const context = options.userId ? { userId: options.userId, membershipId: options.membershipId } : undefined;
|
|
79427
|
+
const client = await privatePublicationSession(options.email, code, context), directory = resolve6(options.recoveryDir);
|
|
79428
|
+
if (action === "publish")
|
|
79429
|
+
await preparePrivatePublication(client, resolve6(args2[0]), directory, { skillId: options.skillId, expectedCurrentVersionId: options.expectedCurrentVersion ?? null, idempotencyKey: options.idempotencyKey });
|
|
79430
|
+
const result2 = action === "publish" || action === "resume" ? await continuePrivatePublication(client, directory, { confirm: true, waitMs: Number(options.waitSeconds) * 1000 }) : await inspectPrivatePublication(client, directory, action === "cancel");
|
|
79431
|
+
print2(result2, options.json);
|
|
79432
|
+
} catch (error2) {
|
|
79433
|
+
const result2 = privatePublicationCustomerError(error2);
|
|
79434
|
+
if (options.json)
|
|
79435
|
+
console.log(JSON.stringify(result2));
|
|
79436
|
+
else
|
|
79437
|
+
console.error(result2.error);
|
|
79438
|
+
process.exitCode = 1;
|
|
79439
|
+
}
|
|
79440
|
+
});
|
|
79441
|
+
}
|
|
79442
|
+
}
|
|
79443
|
+
var init_private_publications = __esm(() => {
|
|
79444
|
+
init_remote_auth();
|
|
79445
|
+
init_auth_store();
|
|
79446
|
+
init_private_publication_customer();
|
|
79447
|
+
init_private_publication_recovery();
|
|
79448
|
+
init_remote_private_publications();
|
|
79449
|
+
init_customer_verification();
|
|
79450
|
+
});
|
|
79451
|
+
|
|
78251
79452
|
// src/cli/commands/workspace-selection.ts
|
|
78252
79453
|
async function codeFor(client, options) {
|
|
78253
79454
|
if (!options.email?.includes("@"))
|
|
@@ -78326,7 +79527,7 @@ __export(exports_auth, {
|
|
|
78326
79527
|
import { createInterface as createInterface2 } from "readline";
|
|
78327
79528
|
function prompt(question) {
|
|
78328
79529
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
78329
|
-
return new Promise((
|
|
79530
|
+
return new Promise((resolve7) => {
|
|
78330
79531
|
let settled = false;
|
|
78331
79532
|
const finish = (answer) => {
|
|
78332
79533
|
if (settled)
|
|
@@ -78335,7 +79536,7 @@ function prompt(question) {
|
|
|
78335
79536
|
rl.close();
|
|
78336
79537
|
if (answer === null)
|
|
78337
79538
|
process.exitCode = 130;
|
|
78338
|
-
|
|
79539
|
+
resolve7(answer);
|
|
78339
79540
|
};
|
|
78340
79541
|
rl.once("SIGINT", () => finish(null));
|
|
78341
79542
|
rl.once("close", () => finish(null));
|
|
@@ -78437,7 +79638,7 @@ function printWhoami(payload) {
|
|
|
78437
79638
|
console.log(source_default.dim("(offline \u2014 showing cached info)"));
|
|
78438
79639
|
}
|
|
78439
79640
|
function sleep(ms) {
|
|
78440
|
-
return new Promise((
|
|
79641
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
78441
79642
|
}
|
|
78442
79643
|
function browserCommand(url) {
|
|
78443
79644
|
if (process.platform === "darwin")
|
|
@@ -78930,7 +80131,7 @@ function promptInvitationProof(kind) {
|
|
|
78930
80131
|
const label = kind === "token" ? "invitation token" : "recovery code";
|
|
78931
80132
|
const character = kind === "token" ? /^[A-Za-z0-9_-]$/ : /^\d$/;
|
|
78932
80133
|
const { stdin, stderr: output } = process, wasRaw = stdin.isRaw, wasFlowing = stdin.readableFlowing;
|
|
78933
|
-
return new Promise((
|
|
80134
|
+
return new Promise((resolve7) => {
|
|
78934
80135
|
let value = "", settled = false, overflow = false;
|
|
78935
80136
|
const finish = (answer) => {
|
|
78936
80137
|
if (settled)
|
|
@@ -78947,7 +80148,7 @@ function promptInvitationProof(kind) {
|
|
|
78947
80148
|
`);
|
|
78948
80149
|
if (answer === null)
|
|
78949
80150
|
process.exitCode = 130;
|
|
78950
|
-
|
|
80151
|
+
resolve7(answer);
|
|
78951
80152
|
};
|
|
78952
80153
|
const cancel = () => finish(null);
|
|
78953
80154
|
const keypress = (text2, key) => {
|
|
@@ -79519,8 +80720,8 @@ var init_storage = __esm(() => {
|
|
|
79519
80720
|
});
|
|
79520
80721
|
|
|
79521
80722
|
// src/lib/registry-reconcile.ts
|
|
79522
|
-
import { existsSync as existsSync33, readFileSync as readFileSync31, statSync as statSync20, writeFileSync as
|
|
79523
|
-
import { join as
|
|
80723
|
+
import { existsSync as existsSync33, readFileSync as readFileSync31, statSync as statSync20, writeFileSync as writeFileSync21 } from "fs";
|
|
80724
|
+
import { join as join40 } from "path";
|
|
79524
80725
|
function isDirectory2(path) {
|
|
79525
80726
|
try {
|
|
79526
80727
|
return statSync20(path).isDirectory();
|
|
@@ -79531,11 +80732,11 @@ function isDirectory2(path) {
|
|
|
79531
80732
|
function migrationNeeded(options) {
|
|
79532
80733
|
if (options.rootDir)
|
|
79533
80734
|
return false;
|
|
79534
|
-
const appDir = options.homeDir ?
|
|
79535
|
-
return !(isOwnerLayoutMigrated(appDir) && isDirectory2(
|
|
80735
|
+
const appDir = options.homeDir ? join40(options.homeDir, ".hasna", "skills") : getDataDir();
|
|
80736
|
+
return !(isOwnerLayoutMigrated(appDir) && isDirectory2(join40(appDir, SKILLS_CACHE_DIRNAME)));
|
|
79536
80737
|
}
|
|
79537
80738
|
function readBaseline(skillDir) {
|
|
79538
|
-
const markerPath =
|
|
80739
|
+
const markerPath = join40(skillDir, PULL_MARKER_FILE);
|
|
79539
80740
|
if (!existsSync33(markerPath))
|
|
79540
80741
|
return;
|
|
79541
80742
|
try {
|
|
@@ -79551,7 +80752,7 @@ function readBaseline(skillDir) {
|
|
|
79551
80752
|
}
|
|
79552
80753
|
}
|
|
79553
80754
|
function readCursor(root) {
|
|
79554
|
-
const path =
|
|
80755
|
+
const path = join40(root, SYNC_CURSOR_FILE);
|
|
79555
80756
|
if (!existsSync33(path))
|
|
79556
80757
|
return { runCount: 0 };
|
|
79557
80758
|
try {
|
|
@@ -79564,21 +80765,21 @@ function readCursor(root) {
|
|
|
79564
80765
|
function resolveCorpusRootReadOnly(options) {
|
|
79565
80766
|
if (options.rootDir)
|
|
79566
80767
|
return { root: options.rootDir, migrationPending: false };
|
|
79567
|
-
const appDir = options.homeDir ?
|
|
79568
|
-
const cache3 =
|
|
80768
|
+
const appDir = options.homeDir ? join40(options.homeDir, ".hasna", "skills") : getDataDirReadOnly();
|
|
80769
|
+
const cache3 = join40(appDir, SKILLS_CACHE_DIRNAME);
|
|
79569
80770
|
if (isOwnerLayoutMigrated(appDir) && isDirectory2(cache3)) {
|
|
79570
80771
|
return { root: cache3, migrationPending: false };
|
|
79571
80772
|
}
|
|
79572
|
-
return { root:
|
|
80773
|
+
return { root: join40(appDir, INSTALLED_SKILLS_DIRNAME), migrationPending: true };
|
|
79573
80774
|
}
|
|
79574
|
-
function remoteRowToSkill(
|
|
79575
|
-
const slug = typeof
|
|
80775
|
+
function remoteRowToSkill(record8) {
|
|
80776
|
+
const slug = typeof record8.slug === "string" ? record8.slug : typeof record8.name === "string" ? record8.name : undefined;
|
|
79576
80777
|
if (!slug)
|
|
79577
80778
|
return;
|
|
79578
80779
|
return {
|
|
79579
80780
|
slug,
|
|
79580
|
-
version: typeof
|
|
79581
|
-
sha256: typeof
|
|
80781
|
+
version: typeof record8.version === "string" ? record8.version : undefined,
|
|
80782
|
+
sha256: typeof record8.bundleSha256 === "string" && record8.bundleSha256 ? record8.bundleSha256 : undefined
|
|
79582
80783
|
};
|
|
79583
80784
|
}
|
|
79584
80785
|
function recheckLocalSide(plannedLocal, localDir, ops = {
|
|
@@ -79703,7 +80904,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
79703
80904
|
for (const slug of allSlugs) {
|
|
79704
80905
|
const local = locals.get(slug);
|
|
79705
80906
|
const remote = remotes.get(slug);
|
|
79706
|
-
const baseline = local ? readBaseline(
|
|
80907
|
+
const baseline = local ? readBaseline(join40(root, slug)) : undefined;
|
|
79707
80908
|
const { state, reason } = classifySkill(local, remote, baseline);
|
|
79708
80909
|
let { action, reason: actionReason } = resolveAction(state, direction, conflict);
|
|
79709
80910
|
if (state === "remote-only" && isDigestless(remote)) {
|
|
@@ -79762,7 +80963,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
79762
80963
|
try {
|
|
79763
80964
|
await pushSkill(slug, { rootDir: root, client });
|
|
79764
80965
|
const pushed = locals.get(slug);
|
|
79765
|
-
writePullMarker(
|
|
80966
|
+
writePullMarker(join40(root, slug), {
|
|
79766
80967
|
skill: slug,
|
|
79767
80968
|
...pushed?.version ? { version: pushed.version } : {},
|
|
79768
80969
|
...pushed?.sha256 ? { contentHash: pushed.sha256 } : {},
|
|
@@ -79834,7 +81035,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
79834
81035
|
runCount: readCursor(root).runCount + 1,
|
|
79835
81036
|
summary
|
|
79836
81037
|
};
|
|
79837
|
-
|
|
81038
|
+
writeFileSync21(join40(root, SYNC_CURSOR_FILE), `${JSON.stringify(cursor2, null, 2)}
|
|
79838
81039
|
`);
|
|
79839
81040
|
return {
|
|
79840
81041
|
corpusRoot: root,
|
|
@@ -84984,6 +86185,7 @@ import { spawn } from "child_process";
|
|
|
84984
86185
|
import { request as nodeHttpRequest } from "http";
|
|
84985
86186
|
import { request as nodeHttpsRequest } from "https";
|
|
84986
86187
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
86188
|
+
import { createRequire } from "module";
|
|
84987
86189
|
function getPathValue(input, path) {
|
|
84988
86190
|
return path.split(".").reduce((value, part) => {
|
|
84989
86191
|
if (value && typeof value === "object" && part in value) {
|
|
@@ -86081,6 +87283,22 @@ function setPath(input, path, replacement) {
|
|
|
86081
87283
|
if (last2 && last2 in cursor)
|
|
86082
87284
|
cursor[last2] = replacement;
|
|
86083
87285
|
}
|
|
87286
|
+
var MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
|
|
87287
|
+
var INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
|
|
87288
|
+
var CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
|
|
87289
|
+
var AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
87290
|
+
var SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
|
|
87291
|
+
var requireSecretsSdk = createRequire(import.meta.url);
|
|
87292
|
+
var IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
87293
|
+
var AUTHORITY_OVERRIDE_HEADERS = new Set([
|
|
87294
|
+
"host",
|
|
87295
|
+
":authority",
|
|
87296
|
+
"forwarded",
|
|
87297
|
+
"x-forwarded-host",
|
|
87298
|
+
"x-original-host"
|
|
87299
|
+
]);
|
|
87300
|
+
var MAX_ENVELOPE_BYTES = 256 * 1024;
|
|
87301
|
+
var MAX_REQUEST_BYTES = MAX_ENVELOPE_BYTES * 2 + 8192;
|
|
86084
87302
|
function createEvent(input) {
|
|
86085
87303
|
return {
|
|
86086
87304
|
id: input.id ?? randomUUID2(),
|
|
@@ -87780,6 +88998,8 @@ registerPull2(program2);
|
|
|
87780
88998
|
registerVersions2(program2);
|
|
87781
88999
|
var { registerPublish: registerPublish2 } = await Promise.resolve().then(() => (init_publish(), exports_publish));
|
|
87782
89000
|
registerPublish2(program2);
|
|
89001
|
+
var { registerPrivatePublications: registerPrivatePublications2 } = await Promise.resolve().then(() => (init_private_publications(), exports_private_publications));
|
|
89002
|
+
registerPrivatePublications2(program2);
|
|
87783
89003
|
var { registerAuth: registerAuth2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
|
|
87784
89004
|
registerAuth2(program2);
|
|
87785
89005
|
var { registerCustomerProfileCommands: registerCustomerProfileCommands2 } = await Promise.resolve().then(() => (init_customer_profile(), exports_customer_profile));
|