@hasna/skills 0.4.0 → 0.5.1
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 +220 -5
- package/bin/index.js +7747 -5645
- package/bin/mcp.js +1493 -431
- package/bin/migrate.js +148 -40
- package/bin/server.js +53 -83
- package/bin/worker.js +41 -73
- package/dist/admin-contract.d.ts +37 -19
- package/dist/admin-contract.js +1 -1
- package/dist/cli/cli.test-utils.d.ts +10 -8
- package/dist/cli/commands/customer-profile.d.ts +2 -0
- package/dist/cli/commands/customer-verification.d.ts +5 -0
- package/dist/cli/commands/tool-primitives.d.ts +1 -1
- package/dist/cli/commands/workspace-member-mutations.d.ts +2 -0
- package/dist/cli/commands/workspace-members.d.ts +2 -0
- package/dist/cli/commands/workspace-selection.d.ts +11 -0
- package/dist/cli/env-assignment.d.ts +9 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +841 -167
- package/dist/lib/agent-sync.d.ts +13 -8
- package/dist/lib/api-url.d.ts +4 -3
- package/dist/lib/app-home.d.ts +0 -1
- package/dist/lib/client-types.d.ts +75 -0
- package/dist/lib/credential-state.d.ts +12 -0
- package/dist/lib/fleet-credentials.d.ts +41 -15
- package/dist/lib/home-adoption.d.ts +2 -0
- package/dist/lib/home-census.d.ts +3 -1
- package/dist/lib/local-opt-in.d.ts +24 -0
- package/dist/lib/portable-skills-files.d.ts +6 -2
- package/dist/lib/read-access.d.ts +83 -0
- package/dist/lib/remote-auth.d.ts +23 -3
- package/dist/lib/remote-client.d.ts +45 -5
- package/dist/lib/remote-profile.d.ts +26 -0
- package/dist/lib/remote-registry.d.ts +7 -3
- package/dist/lib/remote-workspace-selection.d.ts +58 -0
- package/dist/lib/remote-workspace.d.ts +76 -0
- package/dist/lib/skillinfo.d.ts +1 -1
- package/dist/lib/workspace-profile.d.ts +49 -0
- package/dist/mcp/helpers.d.ts +22 -0
- package/dist/mcp/index.d.ts +16 -0
- package/dist/sdk/governance-store.d.ts +1 -0
- package/dist/sdk/index.d.ts +8 -2
- package/dist/sdk/index.js +1312 -297
- package/dist/sdk/outputs.d.ts +0 -11
- package/dist/sdk/runs.d.ts +1 -1
- package/dist/storage.js +6 -40
- package/docs/skill-standard.md +30 -2
- package/package.json +6 -4
- package/dist/lib/instance-credentials-race.fixture.d.ts +0 -1
package/dist/index.js
CHANGED
|
@@ -111,12 +111,7 @@ import { homedir } from "os";
|
|
|
111
111
|
import { join, resolve } from "path";
|
|
112
112
|
import { homedir as pathsResolverHomedir } from "os";
|
|
113
113
|
import { join as pathsResolverJoin } from "path";
|
|
114
|
-
var
|
|
115
|
-
config: "HASNA_CONFIG_HOME",
|
|
116
|
-
data: "HASNA_DATA_HOME",
|
|
117
|
-
state: "HASNA_STATE_HOME",
|
|
118
|
-
cache: "HASNA_CACHE_HOME"
|
|
119
|
-
};
|
|
114
|
+
var PATHS_RESOLVER_DATA_ENV = "HASNA_DATA_HOME";
|
|
120
115
|
var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
121
116
|
function pathsResolverAssertApp(app) {
|
|
122
117
|
if (typeof app !== "string" || app.length === 0) {
|
|
@@ -126,48 +121,19 @@ function pathsResolverAssertApp(app) {
|
|
|
126
121
|
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
127
122
|
}
|
|
128
123
|
}
|
|
129
|
-
function
|
|
130
|
-
if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
|
|
131
|
-
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
function pathsResolverBaseDir(kind, options) {
|
|
135
|
-
pathsResolverAssertKind(kind);
|
|
124
|
+
function pathsResolverDataBaseDir(options) {
|
|
136
125
|
const env = options.env ?? process.env;
|
|
137
|
-
const override = env[
|
|
126
|
+
const override = env[PATHS_RESOLVER_DATA_ENV];
|
|
138
127
|
if (typeof override === "string" && override.length > 0)
|
|
139
128
|
return override;
|
|
140
129
|
const home = options.home ?? pathsResolverHomedir();
|
|
141
130
|
const platform = options.platform ?? process.platform;
|
|
142
|
-
|
|
143
|
-
switch (kind) {
|
|
144
|
-
case "config":
|
|
145
|
-
case "data":
|
|
146
|
-
return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
|
|
147
|
-
case "cache":
|
|
148
|
-
return pathsResolverJoin(home, "Library", "Caches", "Hasna");
|
|
149
|
-
case "state":
|
|
150
|
-
return pathsResolverJoin(home, "Library", "Logs", "Hasna");
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
switch (kind) {
|
|
154
|
-
case "config":
|
|
155
|
-
return pathsResolverJoin(home, ".config", "hasna");
|
|
156
|
-
case "data":
|
|
157
|
-
return pathsResolverJoin(home, ".local", "share", "hasna");
|
|
158
|
-
case "state":
|
|
159
|
-
return pathsResolverJoin(home, ".local", "state", "hasna");
|
|
160
|
-
case "cache":
|
|
161
|
-
return pathsResolverJoin(home, ".cache", "hasna");
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
function pathsResolverResolve(kind, options) {
|
|
165
|
-
pathsResolverAssertApp(options.app);
|
|
166
|
-
const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
|
|
167
|
-
return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
|
|
131
|
+
return platform === "darwin" ? pathsResolverJoin(home, "Library", "Application Support", "Hasna") : pathsResolverJoin(home, ".local", "share", "hasna");
|
|
168
132
|
}
|
|
169
133
|
function dataDir(options) {
|
|
170
|
-
|
|
134
|
+
pathsResolverAssertApp(options.app);
|
|
135
|
+
const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
|
|
136
|
+
return pathsResolverJoin(pathsResolverDataBaseDir(options), appSegment);
|
|
171
137
|
}
|
|
172
138
|
var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
|
|
173
139
|
var HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME";
|
|
@@ -1838,14 +1804,27 @@ function normalizePortableSkillName(name) {
|
|
|
1838
1804
|
}
|
|
1839
1805
|
return normalized;
|
|
1840
1806
|
}
|
|
1807
|
+
function normalizeNewPortableSkillName(name) {
|
|
1808
|
+
normalizePortableSkillName(name);
|
|
1809
|
+
const normalized = name.trim().replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1810
|
+
if (!normalized)
|
|
1811
|
+
throw new Error(`Invalid skill name '${name}'. Include letters or numbers.`);
|
|
1812
|
+
return normalized;
|
|
1813
|
+
}
|
|
1841
1814
|
function readPortableSkillManifest(skillPath, fallbackName = basename(skillPath)) {
|
|
1815
|
+
return readManifest(skillPath, fallbackName, normalizePortableSkillName);
|
|
1816
|
+
}
|
|
1817
|
+
function readPortableSkillManifestForImport(skillPath) {
|
|
1818
|
+
return readManifest(skillPath, basename(skillPath), normalizeNewPortableSkillName);
|
|
1819
|
+
}
|
|
1820
|
+
function readManifest(skillPath, fallbackName, normalizeName) {
|
|
1842
1821
|
const skillJsonPath = join6(skillPath, "skill.json");
|
|
1843
1822
|
const skillMdPath = join6(skillPath, "SKILL.md");
|
|
1844
1823
|
const pkgPath = join6(skillPath, "package.json");
|
|
1845
1824
|
const jsonManifest = existsSync6(skillJsonPath) ? readJsonObject(skillJsonPath) : undefined;
|
|
1846
1825
|
const frontmatter = existsSync6(skillMdPath) ? parseSkillFrontmatter(readFileSync5(skillMdPath, "utf-8")) ?? undefined : undefined;
|
|
1847
1826
|
const pkg = existsSync6(pkgPath) ? readJsonObject(pkgPath) : undefined;
|
|
1848
|
-
const name =
|
|
1827
|
+
const name = normalizeName(stringField(jsonManifest, "name") ?? frontmatter?.name ?? stringValue(pkg?.name) ?? fallbackName);
|
|
1849
1828
|
const description = stringField(jsonManifest, "description") ?? frontmatter?.description ?? stringValue(pkg?.description) ?? `${name} skill`;
|
|
1850
1829
|
const version = readDeclaredSkillVersion(skillPath) ?? PORTABLE_SKILL_DEFAULT_VERSION;
|
|
1851
1830
|
const kind = parseSkillKind(stringField(jsonManifest, "kind") ?? frontmatter?.kind);
|
|
@@ -2093,10 +2072,45 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
|
|
|
2093
2072
|
};
|
|
2094
2073
|
if (!existsSync6(join6(skillPath, "SKILL.md"))) {
|
|
2095
2074
|
writeFileSync2(join6(skillPath, "SKILL.md"), renderSkillMd(next));
|
|
2075
|
+
} else {
|
|
2076
|
+
const path = join6(skillPath, "SKILL.md");
|
|
2077
|
+
const content = readFileSync5(path, "utf8");
|
|
2078
|
+
const declaredName = parseSkillFrontmatter(content)?.name;
|
|
2079
|
+
if (declaredName && declaredName !== next.name) {
|
|
2080
|
+
writeFileSync2(path, renameInstructionFrontmatter(content, next.name));
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
const packagePath = join6(skillPath, "package.json");
|
|
2084
|
+
if (existsSync6(packagePath)) {
|
|
2085
|
+
const pkg = readJsonObject(packagePath);
|
|
2086
|
+
if (typeof pkg.name === "string" && pkg.name !== next.name) {
|
|
2087
|
+
writeFileSync2(packagePath, `${JSON.stringify({ ...pkg, name: next.name }, null, 2)}
|
|
2088
|
+
`);
|
|
2089
|
+
}
|
|
2096
2090
|
}
|
|
2097
2091
|
writeSkillJsonWithHash(skillPath, next);
|
|
2098
2092
|
return readPortableSkillManifest(skillPath, next.name);
|
|
2099
2093
|
}
|
|
2094
|
+
function renameInstructionFrontmatter(content, name) {
|
|
2095
|
+
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?=\r?\n|$)/);
|
|
2096
|
+
const names = frontmatter?.[1]?.match(/^[ \t]*name[ \t]*:[^\r\n]*/gm) ?? [];
|
|
2097
|
+
const declaration = names.length === 1 ? names[0].match(/^(name[ \t]*:[ \t]*)(.*?)([ \t]*)$/) : null;
|
|
2098
|
+
const scalar = declaration?.[2] ?? "";
|
|
2099
|
+
let simple = /^[a-zA-Z0-9_.@/ -]+$/.test(scalar);
|
|
2100
|
+
if (scalar.startsWith('"')) {
|
|
2101
|
+
try {
|
|
2102
|
+
simple = typeof JSON.parse(scalar) === "string";
|
|
2103
|
+
} catch {
|
|
2104
|
+
simple = false;
|
|
2105
|
+
}
|
|
2106
|
+
} else if (scalar.startsWith("'"))
|
|
2107
|
+
simple = /^'[^'\r\n]*'$/.test(scalar);
|
|
2108
|
+
if (!frontmatter || !declaration || !simple) {
|
|
2109
|
+
throw new Error("Cannot rename instruction SKILL.md: use one unambiguous top-level name scalar in frontmatter.");
|
|
2110
|
+
}
|
|
2111
|
+
const renamed = frontmatter[0].replace(/^name[ \t]*:[^\r\n]*/m, () => `${declaration[1]}${name}${declaration[3]}`);
|
|
2112
|
+
return renamed + content.slice(frontmatter[0].length);
|
|
2113
|
+
}
|
|
2100
2114
|
function copySkillDirectory(source, destination) {
|
|
2101
2115
|
const resolvedSource = lstatSync2(source).isSymbolicLink() ? realpathSync(source) : source;
|
|
2102
2116
|
mkdirSync2(destination, { recursive: true });
|
|
@@ -2504,7 +2518,7 @@ function isOfficialSkillName(name) {
|
|
|
2504
2518
|
return OFFICIAL_SKILL_NAMES.has(name);
|
|
2505
2519
|
}
|
|
2506
2520
|
function scaffoldPortableSkill(name, options = {}) {
|
|
2507
|
-
const skillName =
|
|
2521
|
+
const skillName = normalizeNewPortableSkillName(name);
|
|
2508
2522
|
const root = getPortableSkillsRoot(options);
|
|
2509
2523
|
const skillPath = join7(root, skillName);
|
|
2510
2524
|
if (existsSync7(skillPath)) {
|
|
@@ -2574,9 +2588,9 @@ function portPortableSkill(sourcePath, options = {}) {
|
|
|
2574
2588
|
if (!existsSync7(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
|
|
2575
2589
|
throw new Error(`Skill source directory not found: ${sourcePath}`);
|
|
2576
2590
|
}
|
|
2577
|
-
const inferred =
|
|
2591
|
+
const inferred = readPortableSkillManifestForImport(absoluteSource);
|
|
2578
2592
|
const explicitName = options.name != null;
|
|
2579
|
-
const skillName =
|
|
2593
|
+
const skillName = normalizeNewPortableSkillName(options.name ?? inferred.name);
|
|
2580
2594
|
if (isOfficialSkillName(skillName) && !options.allowShadow) {
|
|
2581
2595
|
const sourceSlug = safeNormalizeName(basename2(absoluteSource));
|
|
2582
2596
|
const via = explicitName ? `Name '${skillName}' matches a bundled official skill.` : `Inferred name '${skillName}'${sourceSlug && sourceSlug !== skillName ? ` (from source folder '${basename2(absoluteSource)}')` : ""} matches a bundled official skill.`;
|
|
@@ -3191,6 +3205,7 @@ import { fileURLToPath } from "url";
|
|
|
3191
3205
|
import {
|
|
3192
3206
|
cpSync as cpSync3,
|
|
3193
3207
|
existsSync as existsSync9,
|
|
3208
|
+
lstatSync as lstatSync3,
|
|
3194
3209
|
mkdirSync as mkdirSync4,
|
|
3195
3210
|
mkdtempSync as mkdtempSync2,
|
|
3196
3211
|
readFileSync as readFileSync7,
|
|
@@ -3212,6 +3227,20 @@ var SYNC_AGENTS = ["claude", "codewith", "codex", "opencode", "cursor"];
|
|
|
3212
3227
|
var SKILLS_SOURCE_ENV = "SKILLS_SOURCE";
|
|
3213
3228
|
var SYNC_MARKER_FILE = ".hasna-skills.json";
|
|
3214
3229
|
var SYNC_MARKER_MANAGED_BY = "@hasna/skills";
|
|
3230
|
+
function isSkillsOwnershipMarker(marker) {
|
|
3231
|
+
return typeof marker === "object" && marker !== null && Object.hasOwn(marker, "managedBy") && marker.managedBy === SYNC_MARKER_MANAGED_BY;
|
|
3232
|
+
}
|
|
3233
|
+
function hasSkillsOwnershipMarker(dir) {
|
|
3234
|
+
const path = join9(dir, SYNC_MARKER_FILE);
|
|
3235
|
+
try {
|
|
3236
|
+
if (!lstatSync3(path).isFile())
|
|
3237
|
+
return false;
|
|
3238
|
+
const marker = JSON.parse(readFileSync7(path, "utf8"));
|
|
3239
|
+
return isSkillsOwnershipMarker(marker);
|
|
3240
|
+
} catch {
|
|
3241
|
+
return false;
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3215
3244
|
function isSyncAgent(value) {
|
|
3216
3245
|
return SYNC_AGENTS.includes(value);
|
|
3217
3246
|
}
|
|
@@ -3395,7 +3424,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
3395
3424
|
const skillMdPath = join9(dir, "SKILL.md");
|
|
3396
3425
|
const markerPath = join9(dir, SYNC_MARKER_FILE);
|
|
3397
3426
|
const dirExists = existsSync9(dir);
|
|
3398
|
-
const managed =
|
|
3427
|
+
const managed = hasSkillsOwnershipMarker(dir);
|
|
3399
3428
|
const hasSkillMd = existsSync9(skillMdPath);
|
|
3400
3429
|
if (dirExists && !managed && !hasSkillMd) {
|
|
3401
3430
|
return {
|
|
@@ -3408,7 +3437,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
3408
3437
|
return {
|
|
3409
3438
|
action: "skip",
|
|
3410
3439
|
path: skillMdPath,
|
|
3411
|
-
reason: "an unmanaged SKILL.md already exists here (hand-authored); pass --force to overwrite"
|
|
3440
|
+
reason: existsSync9(markerPath) ? "an unmanaged SKILL.md already exists here (invalid or foreign ownership marker); pass --force to overwrite" : "an unmanaged SKILL.md already exists here (hand-authored); pass --force to overwrite"
|
|
3412
3441
|
};
|
|
3413
3442
|
}
|
|
3414
3443
|
if (dirExists && managed && hasSkillMd && !options.force && isPointerSkillMd(skillMd)) {
|
|
@@ -3485,7 +3514,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
3485
3514
|
}
|
|
3486
3515
|
function removeManagedAgentSkill(skill, agent, homeDir = homedir2()) {
|
|
3487
3516
|
const dir = join9(agentGlobalSkillsDir(agent, homeDir), skill);
|
|
3488
|
-
if (!
|
|
3517
|
+
if (!hasSkillsOwnershipMarker(dir))
|
|
3489
3518
|
return false;
|
|
3490
3519
|
rmSync2(dir, { recursive: true, force: true });
|
|
3491
3520
|
return true;
|
|
@@ -3861,7 +3890,7 @@ function removeSkillForAgent(name, options) {
|
|
|
3861
3890
|
const canonicalName = getCanonicalSkillName(name);
|
|
3862
3891
|
const scope = options.scope ?? "global";
|
|
3863
3892
|
const dir = getAgentSkillPath(canonicalName, options.agent, scope, options.projectDir);
|
|
3864
|
-
if (!
|
|
3893
|
+
if (!hasSkillsOwnershipMarker(dir))
|
|
3865
3894
|
return false;
|
|
3866
3895
|
rmSync3(dir, { recursive: true, force: true });
|
|
3867
3896
|
return true;
|
|
@@ -4288,7 +4317,7 @@ async function runSkill(name, args, options = {}) {
|
|
|
4288
4317
|
}
|
|
4289
4318
|
const proc = Bun.spawn(["bun", "run", entryPath, ...args], {
|
|
4290
4319
|
cwd: skillPath,
|
|
4291
|
-
stdout: options.stdio === "pipe" ? "pipe" : "inherit",
|
|
4320
|
+
stdout: options.stdio === "pipe" ? "pipe" : options.stdio === "stderr" ? 2 : "inherit",
|
|
4292
4321
|
stderr: options.stdio === "pipe" ? "pipe" : "inherit",
|
|
4293
4322
|
stdin: "inherit",
|
|
4294
4323
|
env: { ...process.env, ...options.env }
|
|
@@ -9106,7 +9135,7 @@ var AUTHORITY_OVERRIDE_HEADERS = new Set([
|
|
|
9106
9135
|
]);
|
|
9107
9136
|
|
|
9108
9137
|
// src/lib/instance-credentials.ts
|
|
9109
|
-
import { closeSync as closeSync2, constants, fstatSync as fstatSync2, lstatSync as
|
|
9138
|
+
import { closeSync as closeSync2, constants, fstatSync as fstatSync2, lstatSync as lstatSync4, openSync as openSync2, readSync } from "fs";
|
|
9110
9139
|
var SKILLS_BOUND_API_URL = "HASNA_SKILLS_BOUND_API_URL";
|
|
9111
9140
|
function selectedSkillsProfile(env, explicit) {
|
|
9112
9141
|
const selected = explicit ?? env.HASNA_PROFILE;
|
|
@@ -9122,7 +9151,7 @@ function skillsProfileCredentialFiles(env, explicit) {
|
|
|
9122
9151
|
}
|
|
9123
9152
|
function fileIdentity(file) {
|
|
9124
9153
|
try {
|
|
9125
|
-
return
|
|
9154
|
+
return lstatSync4(file);
|
|
9126
9155
|
} catch (error) {
|
|
9127
9156
|
if (["ENOENT", "ENOTDIR"].includes(error.code ?? ""))
|
|
9128
9157
|
return null;
|
|
@@ -9193,6 +9222,28 @@ function readSkillsInstanceMetadata(file) {
|
|
|
9193
9222
|
return { apiUrl: urls[0], binding: values.get(SKILLS_BOUND_API_URL) };
|
|
9194
9223
|
}
|
|
9195
9224
|
|
|
9225
|
+
// src/lib/local-opt-in.ts
|
|
9226
|
+
var SKILLS_LOCAL_OPT_IN_ENV_KEYS = ["HASNA_SKILLS_LOCAL", "SKILLS_LOCAL"];
|
|
9227
|
+
function isSkillsLocalOptIn(env = process.env) {
|
|
9228
|
+
return SKILLS_LOCAL_OPT_IN_ENV_KEYS.some((key) => (env[key] ?? "").trim() !== "");
|
|
9229
|
+
}
|
|
9230
|
+
function skillsAuthorityEnvKeys() {
|
|
9231
|
+
const keys = clientTransportEnvKeys("skills");
|
|
9232
|
+
return [
|
|
9233
|
+
...keys.apiUrlKeys,
|
|
9234
|
+
...keys.apiKeyKeys,
|
|
9235
|
+
credentialOverrideEnvKey("skills"),
|
|
9236
|
+
credentialPointerEnvKey("skills"),
|
|
9237
|
+
CREDENTIAL_PROFILE_ENV_KEY
|
|
9238
|
+
];
|
|
9239
|
+
}
|
|
9240
|
+
function hasSkillsEnvAuthorityIntent(env = process.env) {
|
|
9241
|
+
return skillsAuthorityEnvKeys().some((key) => (env[key] ?? "").trim() !== "");
|
|
9242
|
+
}
|
|
9243
|
+
function selectsSkillsLocalMode(env = process.env) {
|
|
9244
|
+
return !hasSkillsEnvAuthorityIntent(env) && isSkillsLocalOptIn(env);
|
|
9245
|
+
}
|
|
9246
|
+
|
|
9196
9247
|
// src/lib/fleet-credentials.ts
|
|
9197
9248
|
var SKILLS_APP = "skills";
|
|
9198
9249
|
var ENV_KEYS = clientTransportEnvKeys(SKILLS_APP);
|
|
@@ -9212,6 +9263,9 @@ class SkillsFleetCredentialError extends Error {
|
|
|
9212
9263
|
function isCredentialResolutionError(error) {
|
|
9213
9264
|
return error instanceof CredentialResolutionError || typeof error === "object" && error !== null && error.name === "CredentialResolutionError";
|
|
9214
9265
|
}
|
|
9266
|
+
function isSkillsFleetCredentialError(error) {
|
|
9267
|
+
return error instanceof SkillsFleetCredentialError || typeof error === "object" && error !== null && error.name === "SkillsFleetCredentialError";
|
|
9268
|
+
}
|
|
9215
9269
|
function asSkillsFleetCredentialError(error) {
|
|
9216
9270
|
if (!isCredentialResolutionError(error))
|
|
9217
9271
|
return null;
|
|
@@ -9278,7 +9332,7 @@ function noticeLocalSkillsMode(write = (line) => console.error(line)) {
|
|
|
9278
9332
|
if (localNoticePrinted)
|
|
9279
9333
|
return;
|
|
9280
9334
|
localNoticePrinted = true;
|
|
9281
|
-
write(`skills: local mode
|
|
9335
|
+
write(`skills: local mode (${SKILLS_LOCAL_OPT_IN_ENV_KEYS[0]}=1) \u2014 running on this machine against the bundled corpus.`);
|
|
9282
9336
|
}
|
|
9283
9337
|
function resolveSkillsFleet(env = process.env, options = {}) {
|
|
9284
9338
|
try {
|
|
@@ -9315,13 +9369,16 @@ function snapshotSkillsOptions(env, options) {
|
|
|
9315
9369
|
} } };
|
|
9316
9370
|
}
|
|
9317
9371
|
function resolveSkillsFleetOrThrow(env, options) {
|
|
9372
|
+
if (selectsSkillsLocalMode(env))
|
|
9373
|
+
return { mode: "local", apiOrigin: null, apiKey: null };
|
|
9318
9374
|
const assertFilesUnchanged = captureSkillsCredentialFiles(skillsProfileCredentialFiles(env, options.credentials?.profile));
|
|
9319
9375
|
const configured = configuredSkillsApiUrl(env, options.credentials?.keychain, options.credentials?.profile);
|
|
9320
9376
|
const credential = resolveCredential(SKILLS_APP, env, options.credentials);
|
|
9321
9377
|
if (!credential) {
|
|
9322
|
-
if (!configured)
|
|
9323
|
-
|
|
9324
|
-
|
|
9378
|
+
if (!configured) {
|
|
9379
|
+
throw new SkillsFleetCredentialError(`No API key resolved and no Skills API URL is configured \u2014 failing closed ` + `(local mode is opt-in only: set ${SKILLS_LOCAL_OPT_IN_ENV_KEYS[0]}=1 to run on this machine). ` + `Looked in ${credentialLocations(env)}. Sign in with: skills auth login`);
|
|
9380
|
+
}
|
|
9381
|
+
throw new SkillsFleetCredentialError(`${configured.source} points this CLI at a Skills service but no API key resolved \u2014 refusing to run locally instead. ` + `Looked in ${credentialLocations(env)}. Sign in with: skills auth login`);
|
|
9325
9382
|
}
|
|
9326
9383
|
const apiOrigin = normalizeSkillsApiOrigin(configured?.value ?? defaultFleetGatewayBaseUrl(SKILLS_APP));
|
|
9327
9384
|
toV1BaseUrl(apiOrigin);
|
|
@@ -9343,6 +9400,10 @@ function resolveSkillsFleetOrThrow(env, options) {
|
|
|
9343
9400
|
}
|
|
9344
9401
|
return { ...base, apiKey: credential.apiKey, apiKeyPointer: null };
|
|
9345
9402
|
}
|
|
9403
|
+
function credentialLocations(env) {
|
|
9404
|
+
const files = skillsCredentialFiles(env).join(" or ") || "no credentials file (HOME is unset)";
|
|
9405
|
+
return `hasna.credentials.${SKILLS_APP}.api-key (macOS Keychain, account HASNA_STATION or the host name), ${files}, and ${SKILLS_API_KEY_ENV}`;
|
|
9406
|
+
}
|
|
9346
9407
|
function assertCredentialInstance(credential, apiOrigin, env, options) {
|
|
9347
9408
|
let bound;
|
|
9348
9409
|
if (credential.tier === "disk" || credential.tier === "profile") {
|
|
@@ -9396,7 +9457,7 @@ async function skillsCredentialOrReason(env = process.env, options = {}) {
|
|
|
9396
9457
|
const connection = await resolveSkillsConnection(env, options);
|
|
9397
9458
|
return connection ? { apiKey: connection.apiKey, apiOrigin: connection.apiOrigin, reason: null } : { apiKey: null, apiOrigin: null, reason: null };
|
|
9398
9459
|
} catch (error) {
|
|
9399
|
-
if (error
|
|
9460
|
+
if (isSkillsFleetCredentialError(error)) {
|
|
9400
9461
|
return { apiKey: null, apiOrigin: null, reason: error.message };
|
|
9401
9462
|
}
|
|
9402
9463
|
throw error;
|
|
@@ -9408,7 +9469,14 @@ function resolveSkillsApiOrigin(env = process.env, options = {}) {
|
|
|
9408
9469
|
toV1BaseUrl(configured.value);
|
|
9409
9470
|
return { origin: normalizeSkillsApiOrigin(configured.value), source: configured.source };
|
|
9410
9471
|
}
|
|
9411
|
-
|
|
9472
|
+
let fleet;
|
|
9473
|
+
try {
|
|
9474
|
+
fleet = resolveSkillsFleet(env, options);
|
|
9475
|
+
} catch (error) {
|
|
9476
|
+
if (isSkillsFleetCredentialError(error))
|
|
9477
|
+
return null;
|
|
9478
|
+
throw error;
|
|
9479
|
+
}
|
|
9412
9480
|
return fleet.mode === "hosted" ? { origin: fleet.apiOrigin, source: fleet.apiUrlSource } : null;
|
|
9413
9481
|
}
|
|
9414
9482
|
function requireSkillsApiOrigin(action = "This command", env = process.env, options = {}) {
|
|
@@ -9640,12 +9708,21 @@ function parseRemoteContract(schema, payload, message) {
|
|
|
9640
9708
|
}
|
|
9641
9709
|
async function remoteRequestHeaders(options) {
|
|
9642
9710
|
const headers = new Headers({ Accept: "application/json" });
|
|
9643
|
-
const token = options.authToken !== undefined ? options.authToken : await
|
|
9711
|
+
const token = options.authToken !== undefined ? options.authToken : await ambientTokenFor(options.apiUrl);
|
|
9644
9712
|
const trimmed = token?.trim();
|
|
9645
9713
|
if (trimmed)
|
|
9646
9714
|
headers.set("Authorization", `Bearer ${trimmed}`);
|
|
9647
9715
|
return headers;
|
|
9648
9716
|
}
|
|
9717
|
+
async function ambientTokenFor(callerApiUrl) {
|
|
9718
|
+
const connection = await resolveSkillsConnection();
|
|
9719
|
+
if (!connection)
|
|
9720
|
+
return null;
|
|
9721
|
+
if (callerApiUrl !== undefined && normalizeSkillsApiOrigin(callerApiUrl) !== connection.apiOrigin) {
|
|
9722
|
+
throw new SkillsFleetCredentialError(`The Skills credential resolved for ${connection.apiOrigin} is never sent to a caller-supplied apiUrl ` + `(${normalizeSkillsApiOrigin(callerApiUrl)}). Pass an explicit authToken for that instance, or authToken: null ` + `for an unauthenticated read; no credential was sent.`, "INSTANCE_CREDENTIAL_MISMATCH");
|
|
9723
|
+
}
|
|
9724
|
+
return connection.apiKey;
|
|
9725
|
+
}
|
|
9649
9726
|
async function fetchRemoteJson(url, options) {
|
|
9650
9727
|
const fetchImpl = options.fetchImpl || fetch;
|
|
9651
9728
|
const headers = await remoteRequestHeaders(options);
|
|
@@ -10177,6 +10254,202 @@ function primitiveHaystack(primitive) {
|
|
|
10177
10254
|
function clone(value) {
|
|
10178
10255
|
return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
|
|
10179
10256
|
}
|
|
10257
|
+
// src/lib/remote-workspace-selection.ts
|
|
10258
|
+
var record = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
10259
|
+
var uuid = (v) => typeof v === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v);
|
|
10260
|
+
var text = (v, max = 1024) => typeof v === "string" && !!v.trim() && v.length <= max && !/[\p{Cc}\p{Cs}\u2028\u2029]/u.test(v);
|
|
10261
|
+
var role = (v) => typeof v === "string" && ["owner", "admin", "member", "viewer"].includes(v);
|
|
10262
|
+
var invalidWorkspaceResult = "The server returned an invalid workspace selection result.";
|
|
10263
|
+
|
|
10264
|
+
class WorkspaceContextInputError extends Error {
|
|
10265
|
+
constructor() {
|
|
10266
|
+
super("Provide the observed user ID and exact lowercase membership ID.");
|
|
10267
|
+
this.name = "WorkspaceContextInputError";
|
|
10268
|
+
}
|
|
10269
|
+
}
|
|
10270
|
+
|
|
10271
|
+
class WorkspaceIdentityMismatchError extends Error {
|
|
10272
|
+
constructor() {
|
|
10273
|
+
super("The verified account does not match the requested workspace context.");
|
|
10274
|
+
this.name = "WorkspaceIdentityMismatchError";
|
|
10275
|
+
}
|
|
10276
|
+
}
|
|
10277
|
+
function workspaceExpectedUserId(value) {
|
|
10278
|
+
if (!uuid(value))
|
|
10279
|
+
throw new WorkspaceContextInputError;
|
|
10280
|
+
return value;
|
|
10281
|
+
}
|
|
10282
|
+
function workspaceContext(value) {
|
|
10283
|
+
if (!record(value) || Object.keys(value).sort().join(",") !== "membershipId,userId" || !uuid(value.userId) || !uuid(value.membershipId))
|
|
10284
|
+
throw new WorkspaceContextInputError;
|
|
10285
|
+
return { userId: value.userId, membershipId: value.membershipId };
|
|
10286
|
+
}
|
|
10287
|
+
function invalid() {
|
|
10288
|
+
throw new Error(invalidWorkspaceResult);
|
|
10289
|
+
}
|
|
10290
|
+
function organization(v) {
|
|
10291
|
+
if (!record(v) || !uuid(v.id) || !text(v.slug) || !text(v.name))
|
|
10292
|
+
return invalid();
|
|
10293
|
+
return { id: v.id, slug: v.slug, name: v.name };
|
|
10294
|
+
}
|
|
10295
|
+
function parseAccountWorkspaces(value) {
|
|
10296
|
+
if (!record(value) || !Array.isArray(value.workspaces) || !value.workspaces.length || value.workspaces.length > 1000)
|
|
10297
|
+
return invalid();
|
|
10298
|
+
const workspaces = value.workspaces.map((v) => {
|
|
10299
|
+
if (!record(v) || !uuid(v.membershipId) || !role(v.role) || typeof v.current !== "boolean")
|
|
10300
|
+
return invalid();
|
|
10301
|
+
return { membershipId: v.membershipId, organization: organization(v.organization), role: v.role, current: v.current };
|
|
10302
|
+
});
|
|
10303
|
+
if (workspaces.filter((w) => w.current).length !== 1 || new Set(workspaces.map((w) => w.membershipId)).size !== workspaces.length || new Set(workspaces.map((w) => w.organization.id)).size !== workspaces.length)
|
|
10304
|
+
return invalid();
|
|
10305
|
+
return { workspaces };
|
|
10306
|
+
}
|
|
10307
|
+
function parseWorkspaceIdentity(value, expectedUserId) {
|
|
10308
|
+
if (!record(value))
|
|
10309
|
+
return invalid();
|
|
10310
|
+
const user = value.user;
|
|
10311
|
+
if (!record(user) || !uuid(user.id) || !uuid(user.membershipId) || !text(user.email, 320) || !(user.displayName === null || text(user.displayName)) || !role(user.role))
|
|
10312
|
+
return invalid();
|
|
10313
|
+
if (user.id !== expectedUserId)
|
|
10314
|
+
throw new WorkspaceIdentityMismatchError;
|
|
10315
|
+
return { user: { id: user.id, membershipId: user.membershipId, email: user.email, displayName: user.displayName, role: user.role }, organization: organization(value.organization) };
|
|
10316
|
+
}
|
|
10317
|
+
function sessionToken(value) {
|
|
10318
|
+
if (typeof value !== "string" || !value || value.length > 8192 || /[^\x21-\x7e]/.test(value) || value.startsWith("sk_"))
|
|
10319
|
+
return invalid();
|
|
10320
|
+
return value;
|
|
10321
|
+
}
|
|
10322
|
+
function parseWorkspaceSession(value, expected) {
|
|
10323
|
+
const identity = parseWorkspaceIdentity(value, expected.userId);
|
|
10324
|
+
if (identity.user.membershipId !== expected.membershipId)
|
|
10325
|
+
throw new WorkspaceIdentityMismatchError;
|
|
10326
|
+
return { token: sessionToken(value.token), ...identity };
|
|
10327
|
+
}
|
|
10328
|
+
function parseWorkspaceLogin(value, expectedUserId) {
|
|
10329
|
+
const user = record(value) && value.user;
|
|
10330
|
+
if (!record(value) || !record(user) || !uuid(user.id))
|
|
10331
|
+
return invalid();
|
|
10332
|
+
if (expectedUserId !== undefined && user.id !== expectedUserId)
|
|
10333
|
+
throw new WorkspaceIdentityMismatchError;
|
|
10334
|
+
return { token: sessionToken(value.token), userId: user.id };
|
|
10335
|
+
}
|
|
10336
|
+
var workspaceSelectionFailures = {
|
|
10337
|
+
INVALID_WORKSPACE_SELECTION: [400, "Provide only the exact membership ID from your workspace list."],
|
|
10338
|
+
SESSION_EXPIRED: [401, "Sign in again before selecting a workspace."],
|
|
10339
|
+
ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
|
|
10340
|
+
INTERACTIVE_SESSION_REQUIRED: [403, "Interactive sign-in is required to select a workspace."],
|
|
10341
|
+
WORKSPACE_UNAVAILABLE: [404, "Workspace is unavailable. Refresh your workspace list."],
|
|
10342
|
+
WORKSPACE_BUSY: [503, "Workspace is busy. Refresh before retrying."]
|
|
10343
|
+
};
|
|
10344
|
+
function workspaceSelectionFailure(value, status) {
|
|
10345
|
+
if (!record(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceSelectionFailures, value.code))
|
|
10346
|
+
return null;
|
|
10347
|
+
const code = value.code;
|
|
10348
|
+
return workspaceSelectionFailures[code][0] === status ? code : null;
|
|
10349
|
+
}
|
|
10350
|
+
|
|
10351
|
+
// src/lib/remote-workspace.ts
|
|
10352
|
+
var record2 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
10353
|
+
var cursor = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,512}$/.test(value);
|
|
10354
|
+
var uuid2 = (value) => typeof value === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(value);
|
|
10355
|
+
function workspaceMembersQuery(options = {}) {
|
|
10356
|
+
if (!record2(options) || Object.keys(options).some((key) => key !== "limit" && key !== "cursor") || options.limit !== undefined && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 100) || options.cursor !== undefined && !cursor(options.cursor))
|
|
10357
|
+
throw new Error("Use a roster limit from 1 to 100 and an unchanged continuation cursor.");
|
|
10358
|
+
const query = new URLSearchParams;
|
|
10359
|
+
if (options.limit !== undefined)
|
|
10360
|
+
query.set("limit", String(options.limit));
|
|
10361
|
+
if (options.cursor !== undefined)
|
|
10362
|
+
query.set("cursor", options.cursor);
|
|
10363
|
+
return query.size ? `?${query}` : "";
|
|
10364
|
+
}
|
|
10365
|
+
function timestamp(value) {
|
|
10366
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$/.test(value))
|
|
10367
|
+
return false;
|
|
10368
|
+
const time = Date.parse(value);
|
|
10369
|
+
return Number.isFinite(time) && new Date(time).toISOString().slice(0, 23) === value.slice(0, 23);
|
|
10370
|
+
}
|
|
10371
|
+
function parseMember(row, fail) {
|
|
10372
|
+
if (!record2(row) || !uuid2(row.membershipId) || !uuid2(row.userId) || typeof row.email !== "string" || !row.email || !(row.displayName === null || typeof row.displayName === "string") || !isRole(row.role) || !timestamp(row.createdAt))
|
|
10373
|
+
return fail();
|
|
10374
|
+
return {
|
|
10375
|
+
membershipId: row.membershipId,
|
|
10376
|
+
userId: row.userId,
|
|
10377
|
+
email: row.email,
|
|
10378
|
+
displayName: row.displayName,
|
|
10379
|
+
role: row.role,
|
|
10380
|
+
createdAt: row.createdAt
|
|
10381
|
+
};
|
|
10382
|
+
}
|
|
10383
|
+
var isRole = (value) => typeof value === "string" && ["owner", "admin", "member", "viewer"].includes(value);
|
|
10384
|
+
|
|
10385
|
+
class WorkspaceMemberInputError extends Error {
|
|
10386
|
+
constructor() {
|
|
10387
|
+
super("Use an unchanged lowercase membership ID and the exact role and expected-role parameters from the roster.");
|
|
10388
|
+
this.name = "WorkspaceMemberInputError";
|
|
10389
|
+
}
|
|
10390
|
+
}
|
|
10391
|
+
function mutationInput(membershipId, input, roleChange) {
|
|
10392
|
+
if (typeof membershipId !== "string" || !uuid2(membershipId) || membershipId !== membershipId.toLowerCase() || !record2(input) || Object.keys(input).sort().join(",") !== (roleChange ? "expectedRole,role" : "expectedRole"))
|
|
10393
|
+
throw new WorkspaceMemberInputError;
|
|
10394
|
+
const expectedRole = input.expectedRole, role2 = roleChange ? input.role : undefined;
|
|
10395
|
+
if (!isRole(expectedRole) || roleChange && !isRole(role2))
|
|
10396
|
+
throw new WorkspaceMemberInputError;
|
|
10397
|
+
return { membershipId, role: role2, expectedRole };
|
|
10398
|
+
}
|
|
10399
|
+
function workspaceMemberRoleInput(membershipId, input) {
|
|
10400
|
+
const value = mutationInput(membershipId, input, true);
|
|
10401
|
+
return { membershipId: value.membershipId, body: { role: value.role, expectedRole: value.expectedRole } };
|
|
10402
|
+
}
|
|
10403
|
+
function workspaceMemberRemovalInput(membershipId, input) {
|
|
10404
|
+
const value = mutationInput(membershipId, input, false);
|
|
10405
|
+
return { membershipId: value.membershipId, body: { expectedRole: value.expectedRole } };
|
|
10406
|
+
}
|
|
10407
|
+
var invalidMemberResult = "The server returned an invalid workspace member result. Refresh the roster before another action.";
|
|
10408
|
+
function parseWorkspaceMemberRoleResult(value, membershipId, role2) {
|
|
10409
|
+
const fail = () => {
|
|
10410
|
+
throw new Error(invalidMemberResult);
|
|
10411
|
+
};
|
|
10412
|
+
if (!record2(value) || !uuid2(value.organizationId) || typeof value.changed !== "boolean")
|
|
10413
|
+
return fail();
|
|
10414
|
+
const member = parseMember(value.member, fail);
|
|
10415
|
+
if (member.membershipId !== membershipId || member.role !== role2)
|
|
10416
|
+
return fail();
|
|
10417
|
+
return { organizationId: value.organizationId, member, changed: value.changed };
|
|
10418
|
+
}
|
|
10419
|
+
function parseWorkspaceMemberRemovalResult(value, membershipId) {
|
|
10420
|
+
if (!record2(value) || !uuid2(value.organizationId) || value.membershipId !== membershipId || value.removed !== true || typeof value.alreadyRemoved !== "boolean")
|
|
10421
|
+
throw new Error(invalidMemberResult);
|
|
10422
|
+
return { organizationId: value.organizationId, membershipId, removed: true, alreadyRemoved: value.alreadyRemoved };
|
|
10423
|
+
}
|
|
10424
|
+
var workspaceMemberFailures = {
|
|
10425
|
+
INVALID_REQUEST: [400, "Provide the exact membership role parameters."],
|
|
10426
|
+
ACCOUNT_UNAVAILABLE: [403, "Account is unavailable."],
|
|
10427
|
+
INTERACTIVE_SESSION_REQUIRED: [403, "Fresh interactive sign-in is required."],
|
|
10428
|
+
WORKSPACE_ADMIN_REQUIRED: [403, "A current workspace owner or admin is required."],
|
|
10429
|
+
MEMBERSHIP_ACTION_FORBIDDEN: [403, "Your current workspace role cannot perform this membership action."],
|
|
10430
|
+
MEMBERSHIP_NOT_FOUND: [404, "Membership was not found in the current workspace."],
|
|
10431
|
+
SELF_REMOVAL_UNAVAILABLE: [409, "Leaving your own workspace is not available through member removal."],
|
|
10432
|
+
MEMBERSHIP_ROLE_CHANGED: [409, "The member role changed. Refresh the roster before another action."],
|
|
10433
|
+
LAST_OWNER_REQUIRED: [409, "The workspace must retain at least one active owner."],
|
|
10434
|
+
MEMBERSHIP_BUSY: [503, "Membership is busy. Refresh the roster before another action."]
|
|
10435
|
+
};
|
|
10436
|
+
function workspaceMemberFailure(value, status) {
|
|
10437
|
+
if (!record2(value) || typeof value.code !== "string" || !Object.hasOwn(workspaceMemberFailures, value.code))
|
|
10438
|
+
return null;
|
|
10439
|
+
const code = value.code;
|
|
10440
|
+
return workspaceMemberFailures[code][0] === status ? code : null;
|
|
10441
|
+
}
|
|
10442
|
+
function parseWorkspaceMembersPage(value) {
|
|
10443
|
+
const fail = () => {
|
|
10444
|
+
throw new Error("The server returned an invalid workspace roster.");
|
|
10445
|
+
};
|
|
10446
|
+
if (!record2(value) || !uuid2(value.organizationId) || !Array.isArray(value.members) || value.members.length > 100 || !(value.nextCursor === null || cursor(value.nextCursor)) || !value.members.length && value.nextCursor !== null)
|
|
10447
|
+
return fail();
|
|
10448
|
+
const members = value.members.map((row) => parseMember(row, fail));
|
|
10449
|
+
if (new Set(members.map((row) => row.membershipId)).size !== members.length)
|
|
10450
|
+
return fail();
|
|
10451
|
+
return { organizationId: value.organizationId, members, nextCursor: value.nextCursor };
|
|
10452
|
+
}
|
|
10180
10453
|
// src/lib/auth-store.ts
|
|
10181
10454
|
function getApiUrl(action, env = process.env, options = {}) {
|
|
10182
10455
|
return requireSkillsApiOrigin(action, env, options);
|
|
@@ -10185,44 +10458,44 @@ function getApiUrl(action, env = process.env, options = {}) {
|
|
|
10185
10458
|
// src/lib/remote-run-contract.ts
|
|
10186
10459
|
var REMOTE_SKILL_RUN_CONTRACT_VERSION = 1;
|
|
10187
10460
|
function normalizeRemoteSkillRunContract(payload, fallbackSkill) {
|
|
10188
|
-
const
|
|
10461
|
+
const record3 = isRecord3(payload) ? payload : {};
|
|
10189
10462
|
return {
|
|
10190
10463
|
contractVersion: REMOTE_SKILL_RUN_CONTRACT_VERSION,
|
|
10191
|
-
...pickString(
|
|
10192
|
-
skill: pickStringValue(
|
|
10193
|
-
...pickString(
|
|
10194
|
-
...pickString(
|
|
10195
|
-
...pickNumber(
|
|
10196
|
-
...pickString(
|
|
10197
|
-
...pickString(
|
|
10198
|
-
...pickString(
|
|
10199
|
-
...pickString(
|
|
10200
|
-
...pickNumber(
|
|
10201
|
-
...pickString(
|
|
10202
|
-
...hasOwn(
|
|
10203
|
-
...pickString(
|
|
10204
|
-
...pickString(
|
|
10205
|
-
...pickString(
|
|
10206
|
-
...pickString(
|
|
10207
|
-
...hasOwn(
|
|
10464
|
+
...pickString(record3, "id"),
|
|
10465
|
+
skill: pickStringValue(record3, "skill") ?? fallbackSkill,
|
|
10466
|
+
...pickString(record3, "requestedSlug"),
|
|
10467
|
+
...pickString(record3, "status"),
|
|
10468
|
+
...pickNumber(record3, "exitCode"),
|
|
10469
|
+
...pickString(record3, "correlationId"),
|
|
10470
|
+
...pickString(record3, "createdAt"),
|
|
10471
|
+
...pickString(record3, "startedAt"),
|
|
10472
|
+
...pickString(record3, "completedAt"),
|
|
10473
|
+
...pickNumber(record3, "durationMs"),
|
|
10474
|
+
...pickString(record3, "outputType"),
|
|
10475
|
+
...hasOwn(record3, "outputPreview") ? { outputPreview: record3.outputPreview } : {},
|
|
10476
|
+
...pickString(record3, "errorCode"),
|
|
10477
|
+
...pickString(record3, "errorMessage"),
|
|
10478
|
+
...pickString(record3, "error"),
|
|
10479
|
+
...pickString(record3, "code"),
|
|
10480
|
+
...hasOwn(record3, "details") ? { details: record3.details } : {}
|
|
10208
10481
|
};
|
|
10209
10482
|
}
|
|
10210
10483
|
function isRecord3(value) {
|
|
10211
10484
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
10212
10485
|
}
|
|
10213
|
-
function hasOwn(
|
|
10214
|
-
return Object.prototype.hasOwnProperty.call(
|
|
10486
|
+
function hasOwn(record3, key) {
|
|
10487
|
+
return Object.prototype.hasOwnProperty.call(record3, key);
|
|
10215
10488
|
}
|
|
10216
|
-
function pickString(
|
|
10217
|
-
const value = pickStringValue(
|
|
10489
|
+
function pickString(record3, key) {
|
|
10490
|
+
const value = pickStringValue(record3, key);
|
|
10218
10491
|
return value === undefined ? {} : { [key]: value };
|
|
10219
10492
|
}
|
|
10220
|
-
function pickStringValue(
|
|
10221
|
-
const value =
|
|
10493
|
+
function pickStringValue(record3, key) {
|
|
10494
|
+
const value = record3[key];
|
|
10222
10495
|
return typeof value === "string" ? value : undefined;
|
|
10223
10496
|
}
|
|
10224
|
-
function pickNumber(
|
|
10225
|
-
const value =
|
|
10497
|
+
function pickNumber(record3, key) {
|
|
10498
|
+
const value = record3[key];
|
|
10226
10499
|
return typeof value === "number" && Number.isFinite(value) ? { [key]: value } : {};
|
|
10227
10500
|
}
|
|
10228
10501
|
|
|
@@ -10361,6 +10634,37 @@ async function readBoundedResponse(response, maximum) {
|
|
|
10361
10634
|
return bytes;
|
|
10362
10635
|
}
|
|
10363
10636
|
|
|
10637
|
+
// src/lib/remote-profile.ts
|
|
10638
|
+
function customerNamePatch(input, field) {
|
|
10639
|
+
if (!isRecord4(input) || Object.keys(input).length !== 1 || !Object.hasOwn(input, field))
|
|
10640
|
+
throw new Error("Provide only the requested name field.");
|
|
10641
|
+
const value = input[field];
|
|
10642
|
+
if (typeof value !== "string" || /[\p{Cc}\p{Cs}\u2028\u2029]/u.test(value) || !value.trim() || [...value.trim()].length > 100) {
|
|
10643
|
+
throw new Error("Use a name of 1\u2013100 characters without control characters or newlines.");
|
|
10644
|
+
}
|
|
10645
|
+
return { [field]: value.trim() };
|
|
10646
|
+
}
|
|
10647
|
+
function isRecord4(value) {
|
|
10648
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
10649
|
+
}
|
|
10650
|
+
function string(value) {
|
|
10651
|
+
return typeof value === "string" && value.length > 0;
|
|
10652
|
+
}
|
|
10653
|
+
function parseUpdatedProfile(value) {
|
|
10654
|
+
const user = isRecord4(value) && value.user;
|
|
10655
|
+
if (!isRecord4(user) || !string(user.id) || !string(user.email) || !(user.displayName === null || typeof user.displayName === "string") || typeof user.role !== "string" || !["owner", "admin", "member", "viewer"].includes(user.role)) {
|
|
10656
|
+
throw new Error("The server returned an invalid account profile.");
|
|
10657
|
+
}
|
|
10658
|
+
return { user: { id: user.id, email: user.email, displayName: user.displayName, role: user.role } };
|
|
10659
|
+
}
|
|
10660
|
+
function parseUpdatedWorkspace(value) {
|
|
10661
|
+
const organization2 = isRecord4(value) && value.organization;
|
|
10662
|
+
if (!isRecord4(organization2) || !string(organization2.id) || !string(organization2.slug) || !string(organization2.name)) {
|
|
10663
|
+
throw new Error("The server returned an invalid workspace.");
|
|
10664
|
+
}
|
|
10665
|
+
return { organization: { id: organization2.id, slug: organization2.slug, name: organization2.name } };
|
|
10666
|
+
}
|
|
10667
|
+
|
|
10364
10668
|
// src/lib/remote-client.ts
|
|
10365
10669
|
class RemoteRouteUnsupportedError extends Error {
|
|
10366
10670
|
path;
|
|
@@ -10378,14 +10682,43 @@ class RemoteRouteUnsupportedError extends Error {
|
|
|
10378
10682
|
class RemoteRequestError extends Error {
|
|
10379
10683
|
path;
|
|
10380
10684
|
status;
|
|
10381
|
-
constructor(path, status,
|
|
10382
|
-
super(`Remote request to ${path} failed: HTTP ${status}
|
|
10685
|
+
constructor(path, status, _statusText) {
|
|
10686
|
+
super(`Remote request to ${path} failed: HTTP ${status}`);
|
|
10383
10687
|
this.path = path;
|
|
10384
10688
|
this.status = status;
|
|
10385
10689
|
this.name = "RemoteRequestError";
|
|
10386
10690
|
}
|
|
10387
10691
|
}
|
|
10388
10692
|
|
|
10693
|
+
class RemoteWorkspaceMemberError extends RemoteRequestError {
|
|
10694
|
+
code;
|
|
10695
|
+
constructor(path, code) {
|
|
10696
|
+
super(path, workspaceMemberFailures[code][0]);
|
|
10697
|
+
this.code = code;
|
|
10698
|
+
this.name = "RemoteWorkspaceMemberError";
|
|
10699
|
+
this.message = workspaceMemberFailures[code][1];
|
|
10700
|
+
}
|
|
10701
|
+
}
|
|
10702
|
+
|
|
10703
|
+
class RemoteWorkspaceSelectionError extends RemoteRequestError {
|
|
10704
|
+
code;
|
|
10705
|
+
constructor(path, code) {
|
|
10706
|
+
super(path, workspaceSelectionFailures[code][0]);
|
|
10707
|
+
this.code = code;
|
|
10708
|
+
this.name = "RemoteWorkspaceSelectionError";
|
|
10709
|
+
this.message = workspaceSelectionFailures[code][1];
|
|
10710
|
+
}
|
|
10711
|
+
}
|
|
10712
|
+
|
|
10713
|
+
class RemoteCapabilityUnavailableError extends RemoteRequestError {
|
|
10714
|
+
code = "SUBSCRIPTION_CHECKOUT_UNAVAILABLE";
|
|
10715
|
+
constructor() {
|
|
10716
|
+
super("/api/v1/billing/checkout", 503);
|
|
10717
|
+
this.name = "RemoteCapabilityUnavailableError";
|
|
10718
|
+
this.message = "Subscription checkout is unavailable on the configured Skills server. " + "Use skills credits packs to view credit packs, or skills billing portal to manage an existing subscription.";
|
|
10719
|
+
}
|
|
10720
|
+
}
|
|
10721
|
+
|
|
10389
10722
|
class RemoteSkillsClient {
|
|
10390
10723
|
apiUrl;
|
|
10391
10724
|
apiKey;
|
|
@@ -10398,6 +10731,7 @@ class RemoteSkillsClient {
|
|
|
10398
10731
|
return fetch(`${this.apiUrl}${path}`, {
|
|
10399
10732
|
...options,
|
|
10400
10733
|
redirect: "error",
|
|
10734
|
+
credentials: "omit",
|
|
10401
10735
|
signal: options?.signal ?? AbortSignal.timeout(15000),
|
|
10402
10736
|
headers: {
|
|
10403
10737
|
Authorization: `Bearer ${this.apiKey}`,
|
|
@@ -10413,9 +10747,14 @@ class RemoteSkillsClient {
|
|
|
10413
10747
|
if (response.status === 404 && opts.domainNotFoundCodes?.length && await responseBodyCarriesCode(response, opts.domainNotFoundCodes)) {
|
|
10414
10748
|
return response;
|
|
10415
10749
|
}
|
|
10750
|
+
response.body?.cancel().catch(() => {});
|
|
10416
10751
|
throw new RemoteRouteUnsupportedError(routePath, response.status, this.apiUrl);
|
|
10417
10752
|
}
|
|
10418
10753
|
if (!response.ok) {
|
|
10754
|
+
if (path === "/api/v1/billing/checkout" && options?.method === "POST" && response.status === 503 && await responseBodyCarriesCode(response, ["SUBSCRIPTION_CHECKOUT_UNAVAILABLE"])) {
|
|
10755
|
+
throw new RemoteCapabilityUnavailableError;
|
|
10756
|
+
}
|
|
10757
|
+
response.body?.cancel().catch(() => {});
|
|
10419
10758
|
throw new RemoteRequestError(routePath, response.status, response.statusText);
|
|
10420
10759
|
}
|
|
10421
10760
|
return response;
|
|
@@ -10499,6 +10838,117 @@ class RemoteSkillsClient {
|
|
|
10499
10838
|
async getIdentity() {
|
|
10500
10839
|
return (await this.requestNewRoute("/api/auth/whoami")).json();
|
|
10501
10840
|
}
|
|
10841
|
+
async listAccountWorkspaces(expectedUserId) {
|
|
10842
|
+
const expected = expectedUserId === undefined ? undefined : workspaceExpectedUserId(expectedUserId);
|
|
10843
|
+
const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
|
|
10844
|
+
let identity;
|
|
10845
|
+
if (expected !== undefined) {
|
|
10846
|
+
const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
|
|
10847
|
+
if (!value || typeof value !== "object" || value.authMethod !== "jwt")
|
|
10848
|
+
throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces", "INTERACTIVE_SESSION_REQUIRED");
|
|
10849
|
+
identity = parseWorkspaceIdentity(value, expected);
|
|
10850
|
+
}
|
|
10851
|
+
const result = parseAccountWorkspaces(await connection.requestWorkspaceSelection("/api/v1/account/workspaces"));
|
|
10852
|
+
const current = result.workspaces.find((workspace) => workspace.current);
|
|
10853
|
+
if (identity && (current.membershipId !== identity.user.membershipId || current.organization.id !== identity.organization.id))
|
|
10854
|
+
throw new WorkspaceIdentityMismatchError;
|
|
10855
|
+
return result;
|
|
10856
|
+
}
|
|
10857
|
+
async switchWorkspace(context) {
|
|
10858
|
+
const target = workspaceContext(context);
|
|
10859
|
+
const connection = new RemoteSkillsClient(this.apiKey, this.apiUrl);
|
|
10860
|
+
const value = await connection.requestWorkspaceSelection("/api/auth/whoami");
|
|
10861
|
+
if (!value || typeof value !== "object" || value.authMethod !== "jwt")
|
|
10862
|
+
throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces/switch", "INTERACTIVE_SESSION_REQUIRED");
|
|
10863
|
+
parseWorkspaceIdentity(value, target.userId);
|
|
10864
|
+
const selected = parseWorkspaceSession(await connection.requestWorkspaceSelection("/api/v1/account/workspaces/switch", {
|
|
10865
|
+
method: "POST",
|
|
10866
|
+
body: JSON.stringify({ membershipId: target.membershipId })
|
|
10867
|
+
}), target);
|
|
10868
|
+
const verified = await new RemoteSkillsClient(selected.token, connection.apiUrl).requestWorkspaceSelection("/api/auth/whoami");
|
|
10869
|
+
if (!verified || typeof verified !== "object" || verified.authMethod !== "jwt")
|
|
10870
|
+
throw new RemoteWorkspaceSelectionError("/api/v1/account/workspaces/switch", "INTERACTIVE_SESSION_REQUIRED");
|
|
10871
|
+
const identity = parseWorkspaceIdentity(verified, target.userId);
|
|
10872
|
+
if (identity.user.membershipId !== target.membershipId || identity.organization.id !== selected.organization.id)
|
|
10873
|
+
throw new WorkspaceIdentityMismatchError;
|
|
10874
|
+
return { token: selected.token, ...identity };
|
|
10875
|
+
}
|
|
10876
|
+
async requestWorkspaceSelection(path, options) {
|
|
10877
|
+
let response;
|
|
10878
|
+
try {
|
|
10879
|
+
response = await this.request(path, { ...options, credentials: "omit" });
|
|
10880
|
+
} catch {
|
|
10881
|
+
throw new Error("Unable to reach the Skills workspace API.");
|
|
10882
|
+
}
|
|
10883
|
+
let value;
|
|
10884
|
+
try {
|
|
10885
|
+
value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, response.ok ? 1024 * 1024 : 4096)));
|
|
10886
|
+
} catch {
|
|
10887
|
+
if (response.ok)
|
|
10888
|
+
throw new Error(invalidWorkspaceResult);
|
|
10889
|
+
}
|
|
10890
|
+
if (!response.ok) {
|
|
10891
|
+
const code = workspaceSelectionFailure(value, response.status);
|
|
10892
|
+
if (code)
|
|
10893
|
+
throw new RemoteWorkspaceSelectionError(path, code);
|
|
10894
|
+
if (response.status === 404 || response.status === 405)
|
|
10895
|
+
throw new RemoteRouteUnsupportedError(path, response.status, this.apiUrl);
|
|
10896
|
+
throw new RemoteRequestError(path, response.status);
|
|
10897
|
+
}
|
|
10898
|
+
return value;
|
|
10899
|
+
}
|
|
10900
|
+
async updateProfile(input) {
|
|
10901
|
+
const body = customerNamePatch(input, "displayName");
|
|
10902
|
+
return parseUpdatedProfile(await (await this.requestNewRoute("/api/v1/account/profile", { method: "PATCH", body: JSON.stringify(body) })).json());
|
|
10903
|
+
}
|
|
10904
|
+
async updateCurrentWorkspace(input) {
|
|
10905
|
+
const body = customerNamePatch(input, "name");
|
|
10906
|
+
return parseUpdatedWorkspace(await (await this.requestNewRoute("/api/v1/workspaces/current", { method: "PATCH", body: JSON.stringify(body) })).json());
|
|
10907
|
+
}
|
|
10908
|
+
async listWorkspaceMembers(options = {}) {
|
|
10909
|
+
const query = workspaceMembersQuery(options);
|
|
10910
|
+
const requestedCursor = options.cursor;
|
|
10911
|
+
const response = await this.requestNewRoute(`/api/v1/workspace/members${query}`);
|
|
10912
|
+
let value;
|
|
10913
|
+
try {
|
|
10914
|
+
value = await response.json();
|
|
10915
|
+
} catch {
|
|
10916
|
+
throw new Error("The server returned an invalid workspace roster.");
|
|
10917
|
+
}
|
|
10918
|
+
const page = parseWorkspaceMembersPage(value);
|
|
10919
|
+
if (requestedCursor !== undefined && page.nextCursor === requestedCursor)
|
|
10920
|
+
throw new Error("The server returned an invalid workspace roster.");
|
|
10921
|
+
return page;
|
|
10922
|
+
}
|
|
10923
|
+
async setWorkspaceMemberRole(membershipId, input) {
|
|
10924
|
+
const captured = workspaceMemberRoleInput(membershipId, input);
|
|
10925
|
+
const value = await this.requestWorkspaceMember(captured.membershipId, "PATCH", captured.body);
|
|
10926
|
+
return parseWorkspaceMemberRoleResult(value, captured.membershipId, captured.body.role);
|
|
10927
|
+
}
|
|
10928
|
+
async removeWorkspaceMember(membershipId, input) {
|
|
10929
|
+
const captured = workspaceMemberRemovalInput(membershipId, input);
|
|
10930
|
+
return parseWorkspaceMemberRemovalResult(await this.requestWorkspaceMember(captured.membershipId, "DELETE", captured.body), captured.membershipId);
|
|
10931
|
+
}
|
|
10932
|
+
async requestWorkspaceMember(membershipId, method, body) {
|
|
10933
|
+
const path = `/api/v1/workspace/members/${membershipId}`;
|
|
10934
|
+
const response = await this.request(path, { method, body: JSON.stringify(body) });
|
|
10935
|
+
let value;
|
|
10936
|
+
try {
|
|
10937
|
+
value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, response.ok ? 64 * 1024 : 4096)));
|
|
10938
|
+
} catch {
|
|
10939
|
+
if (response.ok)
|
|
10940
|
+
throw new Error(invalidMemberResult);
|
|
10941
|
+
}
|
|
10942
|
+
if (!response.ok) {
|
|
10943
|
+
const code = workspaceMemberFailure(value, response.status);
|
|
10944
|
+
if (code)
|
|
10945
|
+
throw new RemoteWorkspaceMemberError(path, code);
|
|
10946
|
+
if (response.status === 404 || response.status === 405)
|
|
10947
|
+
throw new RemoteRouteUnsupportedError(path, response.status, this.apiUrl);
|
|
10948
|
+
throw new RemoteRequestError(path, response.status);
|
|
10949
|
+
}
|
|
10950
|
+
return value;
|
|
10951
|
+
}
|
|
10502
10952
|
async listApiKeys() {
|
|
10503
10953
|
return this.arrayResponse("/api/auth/keys");
|
|
10504
10954
|
}
|
|
@@ -10690,8 +11140,10 @@ class RemoteSkillsClient {
|
|
|
10690
11140
|
return [];
|
|
10691
11141
|
if (!response.ok)
|
|
10692
11142
|
throw new Error(`versions request failed: ${response.status}`);
|
|
10693
|
-
const body = await response
|
|
10694
|
-
|
|
11143
|
+
const body = await readSkillVersionPayload(response);
|
|
11144
|
+
if (!isVersionRecord(body) || !Array.isArray(body.versions) || body.slug !== undefined && body.slug !== slug)
|
|
11145
|
+
throw new Error(INVALID_SKILL_VERSION_RESPONSE);
|
|
11146
|
+
return body.versions.map((entry) => normalizeSkillVersion(entry, slug));
|
|
10695
11147
|
}
|
|
10696
11148
|
async getSkillVersion(slug, version) {
|
|
10697
11149
|
const response = await this.requestNewRoute(`/api/v1/skills/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}`, undefined, { domainNotFoundCodes: ["SKILL_NOT_FOUND", "SKILL_VERSION_NOT_FOUND"] });
|
|
@@ -10699,7 +11151,7 @@ class RemoteSkillsClient {
|
|
|
10699
11151
|
return null;
|
|
10700
11152
|
if (!response.ok)
|
|
10701
11153
|
throw new Error(`version request failed: ${response.status}`);
|
|
10702
|
-
return await response
|
|
11154
|
+
return normalizeSkillVersion(await readSkillVersionPayload(response), slug, version);
|
|
10703
11155
|
}
|
|
10704
11156
|
async listPins() {
|
|
10705
11157
|
const response = await this.requestNewRoute("/api/v1/pins");
|
|
@@ -10747,31 +11199,48 @@ class RemoteSkillsClient {
|
|
|
10747
11199
|
return normalizeUpdatedSincePage(await response.json());
|
|
10748
11200
|
}
|
|
10749
11201
|
}
|
|
10750
|
-
function requireOptionalString(
|
|
10751
|
-
if (
|
|
11202
|
+
function requireOptionalString(record3, field) {
|
|
11203
|
+
if (record3[field] === undefined)
|
|
10752
11204
|
return;
|
|
10753
|
-
if (typeof
|
|
11205
|
+
if (typeof record3[field] !== "string") {
|
|
10754
11206
|
throw new Error(`Remote payload did not match the expected contract (${field} must be a string when present)`);
|
|
10755
11207
|
}
|
|
10756
|
-
return
|
|
11208
|
+
return record3[field];
|
|
11209
|
+
}
|
|
11210
|
+
var INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
|
|
11211
|
+
function isVersionRecord(value) {
|
|
11212
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
11213
|
+
}
|
|
11214
|
+
async function readSkillVersionPayload(response) {
|
|
11215
|
+
try {
|
|
11216
|
+
return await response.json();
|
|
11217
|
+
} catch {
|
|
11218
|
+
throw new Error(INVALID_SKILL_VERSION_RESPONSE);
|
|
11219
|
+
}
|
|
11220
|
+
}
|
|
11221
|
+
function normalizeSkillVersion(entry, slug, version) {
|
|
11222
|
+
if (!isVersionRecord(entry) || typeof entry.slug !== "string" || !entry.slug.trim() || entry.slug !== slug || typeof entry.version !== "string" || !entry.version.trim() || version !== undefined && entry.version !== version || typeof entry.bundleSha256 !== "string" || !/^[a-f0-9]{64}$/i.test(entry.bundleSha256) || typeof entry.bundleByteSize !== "number" || !Number.isSafeInteger(entry.bundleByteSize) || entry.bundleByteSize < 0 || typeof entry.createdAt !== "string" || !entry.createdAt.trim() || entry.current !== undefined && typeof entry.current !== "boolean" || entry.storageKind !== undefined && typeof entry.storageKind !== "string" || entry.manifest !== undefined && !isVersionRecord(entry.manifest)) {
|
|
11223
|
+
throw new Error(INVALID_SKILL_VERSION_RESPONSE);
|
|
11224
|
+
}
|
|
11225
|
+
return entry;
|
|
10757
11226
|
}
|
|
10758
11227
|
function normalizePin(entry) {
|
|
10759
11228
|
if (!entry || typeof entry !== "object") {
|
|
10760
11229
|
throw new Error("Remote pin payload did not match the expected contract (expected an object)");
|
|
10761
11230
|
}
|
|
10762
|
-
const
|
|
10763
|
-
const slug = typeof
|
|
11231
|
+
const record3 = entry;
|
|
11232
|
+
const slug = typeof record3.slug === "string" && record3.slug.trim() ? record3.slug.trim() : undefined;
|
|
10764
11233
|
if (!slug) {
|
|
10765
11234
|
throw new Error("Remote pin payload did not match the expected contract (missing slug)");
|
|
10766
11235
|
}
|
|
10767
11236
|
let metadata;
|
|
10768
|
-
if (
|
|
10769
|
-
if (!
|
|
11237
|
+
if (record3.metadata !== undefined) {
|
|
11238
|
+
if (!record3.metadata || typeof record3.metadata !== "object" || Array.isArray(record3.metadata)) {
|
|
10770
11239
|
throw new Error("Remote pin payload did not match the expected contract (metadata must be a JSON object when present)");
|
|
10771
11240
|
}
|
|
10772
|
-
metadata =
|
|
11241
|
+
metadata = record3.metadata;
|
|
10773
11242
|
}
|
|
10774
|
-
const pinnedAt = requireOptionalString(
|
|
11243
|
+
const pinnedAt = requireOptionalString(record3, "pinnedAt");
|
|
10775
11244
|
return {
|
|
10776
11245
|
slug,
|
|
10777
11246
|
...pinnedAt !== undefined ? { pinnedAt } : {},
|
|
@@ -10788,16 +11257,16 @@ function normalizeSkillSummary(entry) {
|
|
|
10788
11257
|
if (!entry || typeof entry !== "object") {
|
|
10789
11258
|
throw new Error("Remote skill payload did not match the expected contract (expected an object)");
|
|
10790
11259
|
}
|
|
10791
|
-
const
|
|
10792
|
-
const slug = typeof
|
|
11260
|
+
const record3 = entry;
|
|
11261
|
+
const slug = typeof record3.slug === "string" && record3.slug.trim() ? record3.slug.trim() : undefined;
|
|
10793
11262
|
if (!slug) {
|
|
10794
11263
|
throw new Error("Remote skill payload did not match the expected contract (missing slug)");
|
|
10795
11264
|
}
|
|
10796
11265
|
return {
|
|
10797
11266
|
slug,
|
|
10798
|
-
...requireOptionalString(
|
|
10799
|
-
...requireOptionalString(
|
|
10800
|
-
...requireOptionalString(
|
|
11267
|
+
...requireOptionalString(record3, "name") !== undefined ? { name: requireOptionalString(record3, "name") } : {},
|
|
11268
|
+
...requireOptionalString(record3, "version") !== undefined ? { version: requireOptionalString(record3, "version") } : {},
|
|
11269
|
+
...requireOptionalString(record3, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record3, "updatedAt") } : {}
|
|
10801
11270
|
};
|
|
10802
11271
|
}
|
|
10803
11272
|
function normalizeSkillSummaryList(payload) {
|
|
@@ -10807,26 +11276,55 @@ function normalizeSkillSummaryList(payload) {
|
|
|
10807
11276
|
return payload.map(normalizeSkillSummary);
|
|
10808
11277
|
}
|
|
10809
11278
|
async function responseBodyCarriesCode(response, codes) {
|
|
11279
|
+
const reader = response.body?.getReader();
|
|
11280
|
+
if (!reader)
|
|
11281
|
+
return false;
|
|
11282
|
+
const maximum = 8 * 1024;
|
|
11283
|
+
let deadline;
|
|
11284
|
+
const expired = new Promise((_, reject) => {
|
|
11285
|
+
deadline = setTimeout(() => reject(new Error("Error response read deadline exceeded")), 1000);
|
|
11286
|
+
});
|
|
10810
11287
|
try {
|
|
10811
|
-
const
|
|
10812
|
-
|
|
11288
|
+
const chunks = [];
|
|
11289
|
+
let size = 0;
|
|
11290
|
+
while (true) {
|
|
11291
|
+
const next = await Promise.race([reader.read(), expired]);
|
|
11292
|
+
if (next.done)
|
|
11293
|
+
break;
|
|
11294
|
+
size += next.value.byteLength;
|
|
11295
|
+
if (size > maximum)
|
|
11296
|
+
return false;
|
|
11297
|
+
chunks.push(next.value);
|
|
11298
|
+
}
|
|
11299
|
+
const bytes = new Uint8Array(size);
|
|
11300
|
+
let offset = 0;
|
|
11301
|
+
for (const chunk of chunks) {
|
|
11302
|
+
bytes.set(chunk, offset);
|
|
11303
|
+
offset += chunk.byteLength;
|
|
11304
|
+
}
|
|
11305
|
+
const payload = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
11306
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload) || !Object.hasOwn(payload, "code"))
|
|
10813
11307
|
return false;
|
|
10814
11308
|
const code = payload.code;
|
|
10815
11309
|
return typeof code === "string" && codes.includes(code);
|
|
10816
11310
|
} catch {
|
|
10817
11311
|
return false;
|
|
11312
|
+
} finally {
|
|
11313
|
+
clearTimeout(deadline);
|
|
11314
|
+
reader.cancel().catch(() => {});
|
|
11315
|
+
reader.releaseLock();
|
|
10818
11316
|
}
|
|
10819
11317
|
}
|
|
10820
11318
|
function normalizeUpdatedSincePage(payload) {
|
|
10821
11319
|
if (!payload || typeof payload !== "object") {
|
|
10822
11320
|
throw new Error("Updated-since payload did not match the expected contract (expected an object)");
|
|
10823
11321
|
}
|
|
10824
|
-
const
|
|
10825
|
-
if (!Array.isArray(
|
|
11322
|
+
const record3 = payload;
|
|
11323
|
+
if (!Array.isArray(record3.skills)) {
|
|
10826
11324
|
throw new Error("Updated-since payload did not match the expected contract (missing skills array)");
|
|
10827
11325
|
}
|
|
10828
|
-
const skills =
|
|
10829
|
-
const nextCursor =
|
|
11326
|
+
const skills = record3.skills.map(normalizeSkillSummary);
|
|
11327
|
+
const nextCursor = record3.nextCursor === undefined || record3.nextCursor === null ? null : record3.nextCursor;
|
|
10830
11328
|
if (nextCursor !== null && typeof nextCursor !== "string") {
|
|
10831
11329
|
throw new Error("Updated-since payload did not match the expected contract (nextCursor must be a string or absent)");
|
|
10832
11330
|
}
|
|
@@ -11063,8 +11561,8 @@ function revisionIdOf(content) {
|
|
|
11063
11561
|
});
|
|
11064
11562
|
return createHash4("sha256").update(canonical).digest("hex");
|
|
11065
11563
|
}
|
|
11066
|
-
function revisionIdOfRecord(
|
|
11067
|
-
return revisionIdOf(
|
|
11564
|
+
function revisionIdOfRecord(record3) {
|
|
11565
|
+
return revisionIdOf(record3);
|
|
11068
11566
|
}
|
|
11069
11567
|
|
|
11070
11568
|
// src/lib/skill-bundle.ts
|
|
@@ -11590,7 +12088,7 @@ function provenRevision(meta, slug, bundle) {
|
|
|
11590
12088
|
}
|
|
11591
12089
|
function reconcileTombstone(slug, corpusOptions) {
|
|
11592
12090
|
const target = join17(getPortableSkillsRoot(corpusOptions), slug);
|
|
11593
|
-
if (!
|
|
12091
|
+
if (!hasSkillsOwnershipMarker(target)) {
|
|
11594
12092
|
return { name: slug, success: true, tombstoned: true, removed: false, leftInPlace: true };
|
|
11595
12093
|
}
|
|
11596
12094
|
rmSync4(target, { recursive: true, force: true });
|
|
@@ -11771,16 +12269,16 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
11771
12269
|
}
|
|
11772
12270
|
return { path: target, created };
|
|
11773
12271
|
}
|
|
11774
|
-
function writePullMarker(dir,
|
|
12272
|
+
function writePullMarker(dir, record3) {
|
|
11775
12273
|
const marker = {
|
|
11776
12274
|
managedBy: "@hasna/skills",
|
|
11777
|
-
skill:
|
|
11778
|
-
source:
|
|
11779
|
-
...
|
|
11780
|
-
...
|
|
11781
|
-
...
|
|
11782
|
-
...
|
|
11783
|
-
...
|
|
12275
|
+
skill: record3.skill,
|
|
12276
|
+
source: record3.source ?? "pull",
|
|
12277
|
+
...record3.version ? { version: record3.version } : {},
|
|
12278
|
+
...record3.contentHash ? { contentHash: record3.contentHash } : {},
|
|
12279
|
+
...record3.sourceCommit ? { sourceCommit: record3.sourceCommit } : {},
|
|
12280
|
+
...record3.signature ? { signature: record3.signature } : {},
|
|
12281
|
+
...record3.revisionId ? { revisionId: record3.revisionId } : {},
|
|
11784
12282
|
syncedAt: new Date().toISOString()
|
|
11785
12283
|
};
|
|
11786
12284
|
writeFileSync8(join17(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
|
|
@@ -11795,19 +12293,19 @@ async function safeMeta(client, slug) {
|
|
|
11795
12293
|
}
|
|
11796
12294
|
if (!raw || typeof raw !== "object")
|
|
11797
12295
|
return null;
|
|
11798
|
-
const
|
|
11799
|
-
const kind =
|
|
11800
|
-
const tags = Array.isArray(
|
|
12296
|
+
const record3 = raw;
|
|
12297
|
+
const kind = record3.kind === "instruction" || record3.kind === "executable" ? record3.kind : undefined;
|
|
12298
|
+
const tags = Array.isArray(record3.tags) ? record3.tags.filter((tag) => typeof tag === "string" && tag.trim().length > 0) : undefined;
|
|
11801
12299
|
return {
|
|
11802
|
-
...str(
|
|
11803
|
-
...str(
|
|
11804
|
-
...str(
|
|
12300
|
+
...str(record3.displayName) ? { displayName: str(record3.displayName) } : {},
|
|
12301
|
+
...str(record3.description) ? { description: str(record3.description) } : {},
|
|
12302
|
+
...str(record3.category) ? { category: str(record3.category) } : {},
|
|
11805
12303
|
...tags && tags.length ? { tags } : {},
|
|
11806
|
-
...str(
|
|
12304
|
+
...str(record3.version) ? { version: str(record3.version) } : {},
|
|
11807
12305
|
...kind ? { kind } : {},
|
|
11808
|
-
...REVISION_ID_PATTERN.test(str(
|
|
11809
|
-
...typeof
|
|
11810
|
-
...str(
|
|
12306
|
+
...REVISION_ID_PATTERN.test(str(record3.revisionId) ?? "") ? { revisionId: str(record3.revisionId) } : {},
|
|
12307
|
+
...typeof record3.skillMd === "string" && record3.skillMd.length > 0 ? { skillMd: record3.skillMd } : {},
|
|
12308
|
+
...str(record3.publishedSource) ? { publishedSource: str(record3.publishedSource) } : {}
|
|
11811
12309
|
};
|
|
11812
12310
|
}
|
|
11813
12311
|
function pickCorpusOptions(options) {
|
|
@@ -11816,8 +12314,8 @@ function pickCorpusOptions(options) {
|
|
|
11816
12314
|
function extractSlug(entry) {
|
|
11817
12315
|
if (!entry || typeof entry !== "object")
|
|
11818
12316
|
return;
|
|
11819
|
-
const
|
|
11820
|
-
return str(
|
|
12317
|
+
const record3 = entry;
|
|
12318
|
+
return str(record3.slug) ?? str(record3.name);
|
|
11821
12319
|
}
|
|
11822
12320
|
function dedupe(values) {
|
|
11823
12321
|
return [...new Set(values)];
|
|
@@ -11943,7 +12441,7 @@ import { dirname as dirname7, relative as relative4 } from "path";
|
|
|
11943
12441
|
// package.json
|
|
11944
12442
|
var package_default = {
|
|
11945
12443
|
name: "@hasna/skills",
|
|
11946
|
-
version: "0.
|
|
12444
|
+
version: "0.5.1",
|
|
11947
12445
|
description: "Skills library for AI coding agents",
|
|
11948
12446
|
type: "module",
|
|
11949
12447
|
bin: {
|
|
@@ -11975,6 +12473,7 @@ var package_default = {
|
|
|
11975
12473
|
files: [
|
|
11976
12474
|
"dist/",
|
|
11977
12475
|
"!dist/**/*.test.d.ts",
|
|
12476
|
+
"!dist/**/*.fixture.d.ts",
|
|
11978
12477
|
"!dist/test-preload.d.ts",
|
|
11979
12478
|
"!dist/platform",
|
|
11980
12479
|
"bin/",
|
|
@@ -12000,8 +12499,9 @@ var package_default = {
|
|
|
12000
12499
|
migrate: "bun run ./src/server/migrate.ts",
|
|
12001
12500
|
typecheck: "tsc --noEmit",
|
|
12002
12501
|
"verify:release": "bun run scripts/release-guard.ts",
|
|
12502
|
+
"verify:consumer-types": "bun run scripts/consumer-types.ts",
|
|
12003
12503
|
prepare: "bun run build:js",
|
|
12004
|
-
prepack: "bun run build && bun run verify:release",
|
|
12504
|
+
prepack: "bun run build && bun run verify:release && bun run verify:consumer-types",
|
|
12005
12505
|
prepublishOnly: "bun run typecheck && bun run test"
|
|
12006
12506
|
},
|
|
12007
12507
|
keywords: [
|
|
@@ -12023,6 +12523,7 @@ var package_default = {
|
|
|
12023
12523
|
author: "Hasna",
|
|
12024
12524
|
license: "Apache-2.0",
|
|
12025
12525
|
devDependencies: {
|
|
12526
|
+
"@hasna/contracts": "1.0.2",
|
|
12026
12527
|
"@types/bun": "1.3.14",
|
|
12027
12528
|
"@types/node": "25.2.3",
|
|
12028
12529
|
"@types/react": "^18.2.0",
|
|
@@ -12034,8 +12535,7 @@ var package_default = {
|
|
|
12034
12535
|
dependencies: {
|
|
12035
12536
|
"@aws-sdk/client-ecs": "^3.1079.0",
|
|
12036
12537
|
"@aws-sdk/client-s3": "^3.1079.0",
|
|
12037
|
-
"@hasna/
|
|
12038
|
-
"@hasna/events": "0.1.16",
|
|
12538
|
+
"@hasna/events": "0.1.18",
|
|
12039
12539
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
12040
12540
|
chalk: "^5.3.0",
|
|
12041
12541
|
commander: "^12.1.0",
|
|
@@ -12540,6 +13040,104 @@ var toolContracts = [
|
|
|
12540
13040
|
dependencies: objectSchema({}, [], "Package dependencies.", true)
|
|
12541
13041
|
})
|
|
12542
13042
|
},
|
|
13043
|
+
{
|
|
13044
|
+
name: "update_account_profile",
|
|
13045
|
+
title: "Update Account Display Name",
|
|
13046
|
+
description: "Update your display name with fresh email verification on the configured server.",
|
|
13047
|
+
params: ["name", "email", "code"],
|
|
13048
|
+
category: "execution",
|
|
13049
|
+
sideEffects: "local-process-or-remote-run",
|
|
13050
|
+
stable: true,
|
|
13051
|
+
inputSchema: objectSchema({ name: stringSchema("Display name, 1\u2013100 characters"), email: { type: "string", format: "email" }, code: { type: "string", pattern: "^\\d{6}$" } }, ["name", "email", "code"]),
|
|
13052
|
+
outputSchema: objectSchema({ user: objectSchema({ id: stringSchema("Account identifier."), email: stringSchema("Account email."), displayName: stringSchema("Display name."), role: stringSchema("Current workspace role.") }, ["id", "email", "displayName", "role"]) }, ["user"])
|
|
13053
|
+
},
|
|
13054
|
+
{
|
|
13055
|
+
name: "update_workspace_name",
|
|
13056
|
+
title: "Update Workspace Name",
|
|
13057
|
+
description: "Update the current workspace name as an owner/admin with fresh email verification.",
|
|
13058
|
+
params: ["name", "email", "code"],
|
|
13059
|
+
category: "execution",
|
|
13060
|
+
sideEffects: "local-process-or-remote-run",
|
|
13061
|
+
stable: true,
|
|
13062
|
+
inputSchema: objectSchema({ name: stringSchema("Workspace name, 1\u2013100 characters"), email: { type: "string", format: "email" }, code: { type: "string", pattern: "^\\d{6}$" } }, ["name", "email", "code"]),
|
|
13063
|
+
outputSchema: objectSchema({ organization: objectSchema({ id: stringSchema("Workspace identifier."), slug: stringSchema("Stable workspace slug."), name: stringSchema("Workspace name.") }, ["id", "slug", "name"]) }, ["organization"])
|
|
13064
|
+
},
|
|
13065
|
+
{
|
|
13066
|
+
name: "set_workspace_member_role",
|
|
13067
|
+
title: "Set Current Workspace Member Role",
|
|
13068
|
+
description: "Set an exact membership incarnation's role with its observed expectedRole and fresh verification; no automatic retry.",
|
|
13069
|
+
params: ["membershipId", "role", "expectedRole", "email", "code"],
|
|
13070
|
+
category: "execution",
|
|
13071
|
+
sideEffects: "local-process-or-remote-run",
|
|
13072
|
+
stable: true,
|
|
13073
|
+
inputSchema: objectSchema({
|
|
13074
|
+
membershipId: { type: "string", pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$" },
|
|
13075
|
+
role: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
|
|
13076
|
+
expectedRole: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
|
|
13077
|
+
email: { type: "string", format: "email" },
|
|
13078
|
+
code: { type: "string", pattern: "^\\d{6}$" }
|
|
13079
|
+
}, ["membershipId", "role", "expectedRole", "email", "code"]),
|
|
13080
|
+
outputSchema: objectSchema({
|
|
13081
|
+
organizationId: stringSchema("Current workspace identifier."),
|
|
13082
|
+
changed: { type: "boolean" },
|
|
13083
|
+
member: objectSchema({
|
|
13084
|
+
membershipId: stringSchema("Membership incarnation."),
|
|
13085
|
+
userId: stringSchema("Account identifier."),
|
|
13086
|
+
email: stringSchema("Member email."),
|
|
13087
|
+
displayName: { oneOf: [{ type: "string" }, { type: "null" }] },
|
|
13088
|
+
role: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
|
|
13089
|
+
createdAt: stringSchema("Exact server timestamp including microseconds.")
|
|
13090
|
+
}, ["membershipId", "userId", "email", "displayName", "role", "createdAt"])
|
|
13091
|
+
}, ["organizationId", "member", "changed"])
|
|
13092
|
+
},
|
|
13093
|
+
{
|
|
13094
|
+
name: "remove_workspace_member",
|
|
13095
|
+
title: "Remove Current Workspace Member",
|
|
13096
|
+
description: "Remove exactly this membership incarnation using its observed expectedRole and fresh verification; self-removal is unavailable.",
|
|
13097
|
+
params: ["membershipId", "expectedRole", "email", "code"],
|
|
13098
|
+
category: "execution",
|
|
13099
|
+
sideEffects: "local-process-or-remote-run",
|
|
13100
|
+
stable: true,
|
|
13101
|
+
inputSchema: objectSchema({
|
|
13102
|
+
membershipId: { type: "string", pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$" },
|
|
13103
|
+
expectedRole: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
|
|
13104
|
+
email: { type: "string", format: "email" },
|
|
13105
|
+
code: { type: "string", pattern: "^\\d{6}$" }
|
|
13106
|
+
}, ["membershipId", "expectedRole", "email", "code"]),
|
|
13107
|
+
outputSchema: objectSchema({
|
|
13108
|
+
organizationId: stringSchema("Current workspace identifier."),
|
|
13109
|
+
membershipId: stringSchema("Removed membership incarnation."),
|
|
13110
|
+
removed: { type: "boolean", const: true },
|
|
13111
|
+
alreadyRemoved: { type: "boolean" }
|
|
13112
|
+
}, ["organizationId", "membershipId", "removed", "alreadyRemoved"])
|
|
13113
|
+
},
|
|
13114
|
+
{
|
|
13115
|
+
name: "list_workspace_members",
|
|
13116
|
+
title: "List Current Workspace Members",
|
|
13117
|
+
description: "Read one current-workspace roster page with fresh owner/admin email verification; saved credentials stay unchanged.",
|
|
13118
|
+
params: ["email", "code", "limit?", "cursor?"],
|
|
13119
|
+
category: "execution",
|
|
13120
|
+
sideEffects: "local-process-or-remote-run",
|
|
13121
|
+
stable: true,
|
|
13122
|
+
inputSchema: objectSchema({
|
|
13123
|
+
email: { type: "string", format: "email" },
|
|
13124
|
+
code: { type: "string", pattern: "^\\d{6}$" },
|
|
13125
|
+
limit: { type: "integer", minimum: 1, maximum: 100 },
|
|
13126
|
+
cursor: { type: "string", pattern: "^[A-Za-z0-9_-]{1,512}$" }
|
|
13127
|
+
}, ["email", "code"]),
|
|
13128
|
+
outputSchema: objectSchema({
|
|
13129
|
+
organizationId: stringSchema("Current workspace identifier."),
|
|
13130
|
+
members: arraySchema(objectSchema({
|
|
13131
|
+
membershipId: stringSchema("Membership incarnation."),
|
|
13132
|
+
userId: stringSchema("Account identifier."),
|
|
13133
|
+
email: stringSchema("Member email."),
|
|
13134
|
+
displayName: { oneOf: [{ type: "string" }, { type: "null" }] },
|
|
13135
|
+
role: { type: "string", enum: ["owner", "admin", "member", "viewer"] },
|
|
13136
|
+
createdAt: stringSchema("Exact server timestamp including microseconds.")
|
|
13137
|
+
}, ["membershipId", "userId", "email", "displayName", "role", "createdAt"])),
|
|
13138
|
+
nextCursor: { oneOf: [{ type: "string" }, { type: "null" }] }
|
|
13139
|
+
}, ["organizationId", "members", "nextCursor"])
|
|
13140
|
+
},
|
|
12543
13141
|
{
|
|
12544
13142
|
name: "list_api_keys",
|
|
12545
13143
|
title: "List API Keys",
|
|
@@ -13354,7 +13952,7 @@ class SkillsPostgresSyncStore {
|
|
|
13354
13952
|
}
|
|
13355
13953
|
async upsertRecords(records) {
|
|
13356
13954
|
let count = 0;
|
|
13357
|
-
for (const
|
|
13955
|
+
for (const record3 of records) {
|
|
13358
13956
|
await this.client.query([
|
|
13359
13957
|
"INSERT INTO skills_sync_records",
|
|
13360
13958
|
"(scope, kind, id, updated_at, deleted_at, source, payload)",
|
|
@@ -13365,13 +13963,13 @@ class SkillsPostgresSyncStore {
|
|
|
13365
13963
|
"source = EXCLUDED.source,",
|
|
13366
13964
|
"payload = EXCLUDED.payload"
|
|
13367
13965
|
].join(" "), [
|
|
13368
|
-
|
|
13369
|
-
|
|
13370
|
-
|
|
13371
|
-
|
|
13372
|
-
|
|
13373
|
-
|
|
13374
|
-
JSON.stringify(
|
|
13966
|
+
record3.scope,
|
|
13967
|
+
record3.kind,
|
|
13968
|
+
record3.id,
|
|
13969
|
+
record3.updatedAt,
|
|
13970
|
+
record3.deletedAt ?? null,
|
|
13971
|
+
record3.source ?? null,
|
|
13972
|
+
JSON.stringify(record3.payload)
|
|
13375
13973
|
]);
|
|
13376
13974
|
count += 1;
|
|
13377
13975
|
}
|
|
@@ -14350,13 +14948,13 @@ async function requestAuthApi(instance, path, options) {
|
|
|
14350
14948
|
apiUrl: safeUrl
|
|
14351
14949
|
});
|
|
14352
14950
|
}
|
|
14353
|
-
const
|
|
14354
|
-
const body =
|
|
14951
|
+
const text2 = await res.text();
|
|
14952
|
+
const body = text2 ? parseJsonBody(text2) : {};
|
|
14355
14953
|
if (!res.ok) {
|
|
14356
|
-
const
|
|
14357
|
-
const detail = typeof
|
|
14358
|
-
const error = typeof
|
|
14359
|
-
const code = typeof
|
|
14954
|
+
const record3 = isRecord5(body) ? body : {};
|
|
14955
|
+
const detail = typeof record3.detail === "string" ? record3.detail : undefined;
|
|
14956
|
+
const error = typeof record3.error === "string" ? record3.error : undefined;
|
|
14957
|
+
const code = typeof record3.code === "string" ? record3.code : undefined;
|
|
14360
14958
|
throw new HostedApiError(detail || error || `${res.status} ${res.statusText}`, {
|
|
14361
14959
|
status: res.status,
|
|
14362
14960
|
code,
|
|
@@ -14367,21 +14965,21 @@ async function requestAuthApi(instance, path, options) {
|
|
|
14367
14965
|
}
|
|
14368
14966
|
return body;
|
|
14369
14967
|
}
|
|
14370
|
-
function parseJsonBody(
|
|
14968
|
+
function parseJsonBody(text2) {
|
|
14371
14969
|
try {
|
|
14372
|
-
return JSON.parse(
|
|
14970
|
+
return JSON.parse(text2);
|
|
14373
14971
|
} catch {
|
|
14374
|
-
return { detail: condenseErrorBody(
|
|
14972
|
+
return { detail: condenseErrorBody(text2) };
|
|
14375
14973
|
}
|
|
14376
14974
|
}
|
|
14377
|
-
function condenseErrorBody(
|
|
14378
|
-
const stripped = /<[a-z!/]/i.test(
|
|
14975
|
+
function condenseErrorBody(text2) {
|
|
14976
|
+
const stripped = /<[a-z!/]/i.test(text2) ? text2.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]*>/g, " ") : text2;
|
|
14379
14977
|
const collapsed = stripped.replace(/\s+/g, " ").trim();
|
|
14380
14978
|
if (collapsed.length <= MAX_ERROR_DETAIL_LENGTH)
|
|
14381
14979
|
return collapsed;
|
|
14382
14980
|
return `${collapsed.slice(0, MAX_ERROR_DETAIL_LENGTH - 1).trimEnd()}\u2026`;
|
|
14383
14981
|
}
|
|
14384
|
-
function
|
|
14982
|
+
function isRecord5(value) {
|
|
14385
14983
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
14386
14984
|
}
|
|
14387
14985
|
|
|
@@ -14402,22 +15000,90 @@ class RemoteSkillsAuthClient {
|
|
|
14402
15000
|
pollDevice(deviceCode) {
|
|
14403
15001
|
return this.request("/api/auth/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) });
|
|
14404
15002
|
}
|
|
14405
|
-
async sessionClient(email, code) {
|
|
15003
|
+
async sessionClient(email, code, context) {
|
|
15004
|
+
if (context !== undefined) {
|
|
15005
|
+
const target = workspaceContext(context), apiOrigin2 = this.apiOrigin;
|
|
15006
|
+
const session = await this.switchWorkspace(email, code, target);
|
|
15007
|
+
return new RemoteSkillsClient(session.token, apiOrigin2);
|
|
15008
|
+
}
|
|
15009
|
+
const apiOrigin = this.apiOrigin;
|
|
14406
15010
|
if (!email.includes("@") || !/^\d{6}$/.test(code))
|
|
14407
|
-
throw new Error("Fresh email and six-digit verification code are required to manage
|
|
15011
|
+
throw new Error("Fresh email and six-digit verification code are required to manage this account");
|
|
14408
15012
|
const login = await this.verifyCode(email, code);
|
|
14409
15013
|
if (!login || typeof login.token !== "string" || !login.token)
|
|
14410
15014
|
throw new Error("The server did not return an authorized account session");
|
|
14411
|
-
return new RemoteSkillsClient(login.token,
|
|
15015
|
+
return new RemoteSkillsClient(login.token, apiOrigin);
|
|
15016
|
+
}
|
|
15017
|
+
async listAccountWorkspaces(email, code, expectedUserId) {
|
|
15018
|
+
const login = await this.workspaceLogin(email, code, expectedUserId);
|
|
15019
|
+
const result = await new RemoteSkillsClient(login.token, login.apiOrigin).listAccountWorkspaces(login.userId);
|
|
15020
|
+
return { userId: login.userId, ...result };
|
|
15021
|
+
}
|
|
15022
|
+
async switchWorkspace(email, code, context) {
|
|
15023
|
+
const target = workspaceContext(context);
|
|
15024
|
+
const login = await this.workspaceLogin(email, code, target.userId);
|
|
15025
|
+
return new RemoteSkillsClient(login.token, login.apiOrigin).switchWorkspace(target);
|
|
15026
|
+
}
|
|
15027
|
+
async workspaceLogin(email, code, expectedUserId) {
|
|
15028
|
+
const expected = expectedUserId === undefined ? undefined : workspaceExpectedUserId(expectedUserId);
|
|
15029
|
+
const apiOrigin = this.apiOrigin;
|
|
15030
|
+
if (typeof email !== "string" || !email.includes("@") || typeof code !== "string" || !/^\d{6}$/.test(code))
|
|
15031
|
+
throw new Error("Fresh email and six-digit verification code are required to manage this account");
|
|
15032
|
+
let response;
|
|
15033
|
+
try {
|
|
15034
|
+
response = await fetch(`${apiOrigin}/api/auth/verify`, {
|
|
15035
|
+
method: "POST",
|
|
15036
|
+
redirect: "error",
|
|
15037
|
+
credentials: "omit",
|
|
15038
|
+
signal: AbortSignal.timeout(15000),
|
|
15039
|
+
headers: { "Content-Type": "application/json" },
|
|
15040
|
+
body: JSON.stringify({ email, code })
|
|
15041
|
+
});
|
|
15042
|
+
} catch {
|
|
15043
|
+
throw new HostedApiError("Unable to verify the Skills account.");
|
|
15044
|
+
}
|
|
15045
|
+
if (!response.ok) {
|
|
15046
|
+
response.body?.cancel().catch(() => {});
|
|
15047
|
+
throw new HostedApiError("Unable to verify the Skills account.", { status: response.status });
|
|
15048
|
+
}
|
|
15049
|
+
let value;
|
|
15050
|
+
try {
|
|
15051
|
+
value = JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, 64 * 1024)));
|
|
15052
|
+
} catch {
|
|
15053
|
+
throw new HostedApiError("The server returned an invalid account verification result.");
|
|
15054
|
+
}
|
|
15055
|
+
return { ...parseWorkspaceLogin(value, expected), apiOrigin };
|
|
15056
|
+
}
|
|
15057
|
+
async createApiKey(email, code, name, scopes, context) {
|
|
15058
|
+
const capturedScopes = scopes === undefined ? undefined : [...scopes];
|
|
15059
|
+
return (await this.sessionClient(email, code, context)).createApiKey(name, capturedScopes);
|
|
15060
|
+
}
|
|
15061
|
+
async listApiKeys(email, code, context) {
|
|
15062
|
+
return (await this.sessionClient(email, code, context)).listApiKeys();
|
|
15063
|
+
}
|
|
15064
|
+
async revokeApiKey(email, code, keyId, context) {
|
|
15065
|
+
return (await this.sessionClient(email, code, context)).revokeApiKey(keyId);
|
|
15066
|
+
}
|
|
15067
|
+
async updateProfile(email, code, input, context) {
|
|
15068
|
+
const body = customerNamePatch(input, "displayName");
|
|
15069
|
+
return (await this.sessionClient(email, code, context)).updateProfile({ displayName: body.displayName });
|
|
15070
|
+
}
|
|
15071
|
+
async updateCurrentWorkspace(email, code, input, context) {
|
|
15072
|
+
const body = customerNamePatch(input, "name");
|
|
15073
|
+
return (await this.sessionClient(email, code, context)).updateCurrentWorkspace({ name: body.name });
|
|
14412
15074
|
}
|
|
14413
|
-
async
|
|
14414
|
-
|
|
15075
|
+
async listWorkspaceMembers(email, code, options = {}, context) {
|
|
15076
|
+
workspaceMembersQuery(options);
|
|
15077
|
+
const captured = { ...options };
|
|
15078
|
+
return (await this.sessionClient(email, code, context)).listWorkspaceMembers(captured);
|
|
14415
15079
|
}
|
|
14416
|
-
async
|
|
14417
|
-
|
|
15080
|
+
async setWorkspaceMemberRole(email, code, membershipId, input, context) {
|
|
15081
|
+
const captured = workspaceMemberRoleInput(membershipId, input);
|
|
15082
|
+
return (await this.sessionClient(email, code, context)).setWorkspaceMemberRole(captured.membershipId, captured.body);
|
|
14418
15083
|
}
|
|
14419
|
-
async
|
|
14420
|
-
|
|
15084
|
+
async removeWorkspaceMember(email, code, membershipId, input, context) {
|
|
15085
|
+
const captured = workspaceMemberRemovalInput(membershipId, input);
|
|
15086
|
+
return (await this.sessionClient(email, code, context)).removeWorkspaceMember(captured.membershipId, captured.body);
|
|
14421
15087
|
}
|
|
14422
15088
|
request(path, options) {
|
|
14423
15089
|
if (!["/api/auth/login", "/api/auth/verify", "/api/auth/device/start", "/api/auth/device/token", "/api/auth/keys", "/api/auth/whoami"].includes(path))
|
|
@@ -14461,6 +15127,7 @@ export {
|
|
|
14461
15127
|
sha256File,
|
|
14462
15128
|
setSkillDisabled,
|
|
14463
15129
|
setScheduleEnabled,
|
|
15130
|
+
selectsSkillsLocalMode,
|
|
14464
15131
|
searchSkills,
|
|
14465
15132
|
scaffoldPortableSkill,
|
|
14466
15133
|
saveProjectConfig,
|
|
@@ -14521,6 +15188,7 @@ export {
|
|
|
14521
15188
|
listPinnedSkills,
|
|
14522
15189
|
listMcpToolContracts,
|
|
14523
15190
|
isSyncAgent,
|
|
15191
|
+
isSkillsLocalOptIn,
|
|
14524
15192
|
isRegularFile,
|
|
14525
15193
|
isPortableWithinSkill,
|
|
14526
15194
|
isGatewayBackedSkill,
|
|
@@ -14605,6 +15273,8 @@ export {
|
|
|
14605
15273
|
agentGlobalSkillsDir,
|
|
14606
15274
|
addSchedule,
|
|
14607
15275
|
adaptSkillMdForAgent,
|
|
15276
|
+
WorkspaceIdentityMismatchError,
|
|
15277
|
+
WorkspaceContextInputError,
|
|
14608
15278
|
TOOL_PRIMITIVE_SCHEMA_VERSION,
|
|
14609
15279
|
TOOL_PRIMITIVES,
|
|
14610
15280
|
StationSnapshotError,
|
|
@@ -14628,6 +15298,7 @@ export {
|
|
|
14628
15298
|
SKILLS_PROJECT_DIR,
|
|
14629
15299
|
SKILLS_NATIVE_STORAGE_FALLBACK_ENV,
|
|
14630
15300
|
SKILLS_NATIVE_STORAGE_ENV,
|
|
15301
|
+
SKILLS_LOCAL_OPT_IN_ENV_KEYS,
|
|
14631
15302
|
SKILLS_CLI_MCP_PARITY,
|
|
14632
15303
|
SKILLS_APP,
|
|
14633
15304
|
SKILLS_API_URL_ENV_KEYS,
|
|
@@ -14635,11 +15306,14 @@ export {
|
|
|
14635
15306
|
SKILLS_API_KEY_ENV_KEYS,
|
|
14636
15307
|
SKILLS_API_KEY_ENV,
|
|
14637
15308
|
SKILLS,
|
|
15309
|
+
RemoteWorkspaceSelectionError,
|
|
15310
|
+
RemoteWorkspaceMemberError,
|
|
14638
15311
|
RemoteSkillsClient,
|
|
14639
15312
|
RemoteSkillsAuthClient,
|
|
14640
15313
|
RemoteRouteUnsupportedError,
|
|
14641
15314
|
RemoteRequestError,
|
|
14642
15315
|
RemoteCreditApprovalError,
|
|
15316
|
+
RemoteCapabilityUnavailableError,
|
|
14643
15317
|
REMOTE_SKILL_RUN_CONTRACT_VERSION,
|
|
14644
15318
|
REFUSED_SCANNER_FLAGGED,
|
|
14645
15319
|
PullSkillError,
|