@tryaura/aura-cli 0.3.0 → 0.3.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/dist/bin/aura.js +3 -3
- package/dist/index.js +1 -1
- package/dist/plugins/index.js +1 -1
- package/dist/{plugins-CL4yWDig.js → plugins-BEuoEUo_.js} +1 -1
- package/dist/{run.boundary-DtkI7ZeG.js → run.boundary-NVWIwGgg.js} +845 -236
- package/dist/{shared-link-plan-C57dSoid.js → shared-link-plan-AcSkw9bO.js} +3 -2
- package/package.json +6 -6
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as displayPath, C as isRecord$4, D as SHARED_INSTRUCTIONS_TEMPLATE, E as AuraManifestError, S as errorMessage$1, T as resolveAuraManifestPath, _ as assertAuraManifestWritable, a as planDesiredMcpConvergence, b as parseAuraManifest, c as rememberMcpConvergence, d as sharedSkillsRoot, g as hashContent, h as stripLegacyManagedBlock, k as pluralize, m as planSharedSkillTreeUpdate, n as appendInstructionFragments, p as managedContentRevisionStatus, r as forgetMcpPlans, t as planSharedInstructionLink, u as planSkillDeployment, v as createAuraManifestWriteOperation, w as AURA_MANIFEST_PATH, x as errorCode, y as createEmptyAuraManifest } from "./shared-link-plan-
|
|
2
|
-
import { COMMAND_NOT_FOUND_EXIT_CODE, DEFAULT_EXEC_TIMEOUT_MS, DEFAULT_HTTP_TIMEOUT_MS, MAX_EXEC_OUTPUT_CHARACTERS, MAX_EXEC_TIMEOUT_MS, MAX_HTTP_RESPONSE_BYTES, MAX_HTTP_TIMEOUT_MS, McpWriteError, NOT_EXECUTABLE_EXIT_CODE, OUTPUT_LIMIT_EXIT_CODE, SHARED_INSTRUCTIONS_TEMPLATE_TOKEN, TIMEOUT_EXIT_CODE, defineOwnProperty, detectExecutable, hasMcpRedaction, jsonPropertyPath, mcpEnvironmentVariableNames, mcpServerNameProblem, normalizeMcpServerDefinition, parseMcpServerDefinition, parseMcpServerManifest, parseSkillFrontmatter, parseSkillReferences, resolveMcpSecretNameCollisions, resolveSkillDirectory, splitSourceLines } from "@tryaura/aura-sdk";
|
|
1
|
+
import { A as displayPath, C as isRecord$4, D as SHARED_INSTRUCTIONS_TEMPLATE, E as AuraManifestError, S as errorMessage$1, T as resolveAuraManifestPath, _ as assertAuraManifestWritable, a as planDesiredMcpConvergence, b as parseAuraManifest, c as rememberMcpConvergence, d as sharedSkillsRoot, g as hashContent, h as stripLegacyManagedBlock, k as pluralize, m as planSharedSkillTreeUpdate, n as appendInstructionFragments, p as managedContentRevisionStatus, r as forgetMcpPlans, t as planSharedInstructionLink, u as planSkillDeployment, v as createAuraManifestWriteOperation, w as AURA_MANIFEST_PATH, x as errorCode, y as createEmptyAuraManifest } from "./shared-link-plan-AcSkw9bO.js";
|
|
2
|
+
import { COMMAND_NOT_FOUND_EXIT_CODE, DEFAULT_EXEC_TIMEOUT_MS, DEFAULT_HTTP_TIMEOUT_MS, MAX_EXEC_OUTPUT_CHARACTERS, MAX_EXEC_TIMEOUT_MS, MAX_HTTP_RESPONSE_BYTES, MAX_HTTP_TIMEOUT_MS, McpWriteError, NOT_EXECUTABLE_EXIT_CODE, OUTPUT_LIMIT_EXIT_CODE, SHARED_INSTRUCTIONS_TEMPLATE_TOKEN, TIMEOUT_EXIT_CODE, defineOwnProperty, detectExecutable, hasMcpRedaction, jsonPropertyPath, mcpEnvironmentVariableNames, mcpServerNameProblem, normalizeMcpServerDefinition, parseMcpServerDefinition, parseMcpServerManifest, parseMcpServerManifestValue, parseSkillFrontmatter, parseSkillReferences, resolveMcpSecretNameCollisions, resolveSkillDirectory, splitSourceLines } from "@tryaura/aura-sdk";
|
|
3
3
|
import { basename, delimiter, dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
4
4
|
import { Buffer as Buffer$1, isUtf8 } from "node:buffer";
|
|
5
5
|
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
@@ -10,7 +10,7 @@ import { access, chmod, link, lstat, mkdir, open, opendir, readFile, readdir, re
|
|
|
10
10
|
import { applyPatch, createTwoFilesPatch, structuredPatch } from "diff";
|
|
11
11
|
import { coerce, parse, satisfies, valid, validRange } from "semver";
|
|
12
12
|
import { isDeepStrictEqual } from "node:util";
|
|
13
|
-
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
14
14
|
import { EnvHttpProxyAgent, fetch as fetch$1 } from "undici";
|
|
15
15
|
import { gunzipSync } from "node:zlib";
|
|
16
16
|
import process$1 from "node:process";
|
|
@@ -3903,6 +3903,7 @@ function collectUnknownBareCheckIdPlugins(state, bareCheckIdPlugins, candidates)
|
|
|
3903
3903
|
function collectIdentityViolations(state, plugin) {
|
|
3904
3904
|
const violationCount = state.violations.length;
|
|
3905
3905
|
if (!PLUGIN_ID_PATTERN.test(plugin.id)) state.violations.push(`Plugin "${plugin.name}" declares ID "${plugin.id}", which cannot be used as an ID namespace; expected lowercase letters, digits, ".", "-", or "_", starting with a letter or digit.`);
|
|
3906
|
+
if (plugin.id === "repo") state.violations.push(`Plugin "${plugin.name}" declares ID "repo", which is reserved.`);
|
|
3906
3907
|
if (plugin.name.trim().length === 0) state.violations.push(`Plugin "${plugin.id}" declares an empty name; reports identify plugins by name.`);
|
|
3907
3908
|
if (canonicalSemver(plugin.version) !== plugin.version) state.violations.push(`${formatPlugin(plugin)} declares version "${plugin.version}"; expected a semver version such as "1.0.0".`);
|
|
3908
3909
|
return state.violations.length === violationCount;
|
|
@@ -6315,12 +6316,13 @@ const MAX_SKILL_DRIVER_CALL_MS = 3e4;
|
|
|
6315
6316
|
*
|
|
6316
6317
|
* Bundled and already-installed trees are local state Aura itself wrote, so they walk unbounded.
|
|
6317
6318
|
* A driver-materialized tree is whatever a plugin's driver put on disk — the reviewed origin is a
|
|
6318
|
-
* claim, not a guarantee — so it walks under {@link DRIVER_WALK_POLICY}, which caps
|
|
6319
|
-
* file count, and total bytes, refuses paths that will not survive a portable
|
|
6320
|
-
* refuses two paths that would alias on a case-insensitive one.
|
|
6319
|
+
* claim, not a guarantee — so it walks under {@link DRIVER_WALK_POLICY}, which caps directory
|
|
6320
|
+
* count, file size, file count, and total bytes, refuses paths that will not survive a portable
|
|
6321
|
+
* filesystem, and refuses two paths that would alias on a case-insensitive one.
|
|
6321
6322
|
*/
|
|
6322
6323
|
/** The bounds an untrusted, driver-supplied tree is read under. */
|
|
6323
6324
|
const DRIVER_WALK_POLICY = Object.freeze({
|
|
6325
|
+
maxDirectories: 200,
|
|
6324
6326
|
maxFileBytes: MAX_SKILL_FILE_BYTES,
|
|
6325
6327
|
maxFiles: 200,
|
|
6326
6328
|
maxTotalBytes: MAX_SKILL_RESPONSE_BYTES
|
|
@@ -6334,19 +6336,22 @@ function treeHash(files) {
|
|
|
6334
6336
|
return createHash("sha256").update(`${signature}\n`, "utf8").digest("hex");
|
|
6335
6337
|
}
|
|
6336
6338
|
/** Reads one skill directory, stopping at the first entry it cannot safely resolve. */
|
|
6337
|
-
async function walkTree(root, reader, policy) {
|
|
6339
|
+
async function walkTree(root, reader, policy, outerBoundary) {
|
|
6338
6340
|
const files = [];
|
|
6339
6341
|
const entries = [];
|
|
6340
6342
|
const state = {
|
|
6343
|
+
bytesRead: 0,
|
|
6344
|
+
directoryCount: 0,
|
|
6341
6345
|
portablePaths: /* @__PURE__ */ new Set(),
|
|
6342
6346
|
totalBytes: 0
|
|
6343
6347
|
};
|
|
6344
6348
|
const path = resolve(root);
|
|
6345
6349
|
const problem = await walkPath({
|
|
6346
|
-
boundary: await reader.realPath(path) ?? path,
|
|
6350
|
+
boundary: outerBoundary ?? await reader.realPath(path) ?? path,
|
|
6347
6351
|
path
|
|
6348
6352
|
}, path, reader, files, entries, state, policy);
|
|
6349
6353
|
return {
|
|
6354
|
+
bytesRead: state.bytesRead,
|
|
6350
6355
|
entries: Object.freeze(entries),
|
|
6351
6356
|
files: Object.freeze(files.sort((left, right) => comparePortablePaths(left.path, right.path))),
|
|
6352
6357
|
...problem === void 0 ? {} : { problem }
|
|
@@ -6368,6 +6373,11 @@ async function walkPath(root, path, reader, files, entries, state, policy) {
|
|
|
6368
6373
|
message: `${relativePath || "."} could not be read (${contents.problem})`
|
|
6369
6374
|
};
|
|
6370
6375
|
if (contents.entries !== void 0) {
|
|
6376
|
+
if (policy !== void 0 && state.directoryCount >= policy.maxDirectories) return {
|
|
6377
|
+
kind: "too-large",
|
|
6378
|
+
message: `${relativePath || "."} exceeds the skill directory limit`
|
|
6379
|
+
};
|
|
6380
|
+
state.directoryCount += 1;
|
|
6371
6381
|
entries.push({
|
|
6372
6382
|
kind: "directory",
|
|
6373
6383
|
path
|
|
@@ -6385,6 +6395,8 @@ function addFile(path, relativePath, contents, files, entries, state, policy) {
|
|
|
6385
6395
|
kind: "unsupported",
|
|
6386
6396
|
message: `${relativePath} is not a readable regular file`
|
|
6387
6397
|
};
|
|
6398
|
+
const bytes = Buffer$1.byteLength(contents.content, "utf8");
|
|
6399
|
+
state.bytesRead += bytes;
|
|
6388
6400
|
if (contents.utf8Valid === false) return {
|
|
6389
6401
|
kind: "unsupported",
|
|
6390
6402
|
message: `${relativePath} is not valid UTF-8`
|
|
@@ -6399,7 +6411,6 @@ function addFile(path, relativePath, contents, files, entries, state, policy) {
|
|
|
6399
6411
|
kind: "unsupported",
|
|
6400
6412
|
message: `${relativePath} aliases another skill file`
|
|
6401
6413
|
};
|
|
6402
|
-
const bytes = Buffer$1.byteLength(contents.content, "utf8");
|
|
6403
6414
|
if (state.totalBytes + bytes > policy.maxTotalBytes) return {
|
|
6404
6415
|
kind: "too-large",
|
|
6405
6416
|
message: `${relativePath} exceeds the skill size limit`
|
|
@@ -6425,7 +6436,7 @@ function comparePortablePaths(left, right) {
|
|
|
6425
6436
|
//#endregion
|
|
6426
6437
|
//#region ../core/src/workspace/skills.ts
|
|
6427
6438
|
const SKILLS_DIAGNOSTIC_ID = "core/skills";
|
|
6428
|
-
const SKILL_FILE$
|
|
6439
|
+
const SKILL_FILE$2 = "SKILL.md";
|
|
6429
6440
|
/** Canonical shared skill root for one captured environment. */
|
|
6430
6441
|
function sharedSkillsPath(environment) {
|
|
6431
6442
|
return sharedSkillsRoot(environment.homeDir);
|
|
@@ -6448,8 +6459,8 @@ async function resolveBundledSkills(registrations, reader) {
|
|
|
6448
6459
|
path,
|
|
6449
6460
|
skill
|
|
6450
6461
|
};
|
|
6451
|
-
if (!tree.files.some((file) => file.path === SKILL_FILE$
|
|
6452
|
-
message: `does not contain ${SKILL_FILE$
|
|
6462
|
+
if (!tree.files.some((file) => file.path === SKILL_FILE$2)) return {
|
|
6463
|
+
message: `does not contain ${SKILL_FILE$2}`,
|
|
6453
6464
|
path,
|
|
6454
6465
|
skill
|
|
6455
6466
|
};
|
|
@@ -6485,7 +6496,7 @@ async function resolveDriverSkillPack(skill, source, reader) {
|
|
|
6485
6496
|
return { kind: "invalid" };
|
|
6486
6497
|
}
|
|
6487
6498
|
const tree = await walkTree(path, reader, DRIVER_WALK_POLICY);
|
|
6488
|
-
if (tree.problem !== void 0 || !tree.files.some((file) => file.path === SKILL_FILE$
|
|
6499
|
+
if (tree.problem !== void 0 || !tree.files.some((file) => file.path === SKILL_FILE$2)) return { kind: "invalid" };
|
|
6489
6500
|
return {
|
|
6490
6501
|
kind: "resolved",
|
|
6491
6502
|
value: Object.freeze({
|
|
@@ -6529,8 +6540,8 @@ async function scanSharedSkills(environment, reader) {
|
|
|
6529
6540
|
* reports `unreadable` and leaves the real cause to travel in `problemDetail`.
|
|
6530
6541
|
*/
|
|
6531
6542
|
function sharedSkillDefinition(path, tree, homeDir) {
|
|
6532
|
-
const skillFilePath = join(path, SKILL_FILE$
|
|
6533
|
-
const file = tree.files.find((entry) => entry.path === SKILL_FILE$
|
|
6543
|
+
const skillFilePath = join(path, SKILL_FILE$2);
|
|
6544
|
+
const file = tree.files.find((entry) => entry.path === SKILL_FILE$2);
|
|
6534
6545
|
if (file === void 0) return {
|
|
6535
6546
|
definitionStatus: tree.problem === void 0 ? "missing-file" : "unreadable",
|
|
6536
6547
|
skillFilePath
|
|
@@ -7264,7 +7275,7 @@ function parseContents(body) {
|
|
|
7264
7275
|
}
|
|
7265
7276
|
//#endregion
|
|
7266
7277
|
//#region ../core/src/skills/pack-schema.ts
|
|
7267
|
-
const SKILL_FILE = "SKILL.md";
|
|
7278
|
+
const SKILL_FILE$1 = "SKILL.md";
|
|
7268
7279
|
/**
|
|
7269
7280
|
* Parses a directory's `skills/<id>` body.
|
|
7270
7281
|
*
|
|
@@ -7307,9 +7318,9 @@ function parseDirectorySkillPack(body, expectedId) {
|
|
|
7307
7318
|
kind: "invalid",
|
|
7308
7319
|
problem: files
|
|
7309
7320
|
};
|
|
7310
|
-
if (!files.some((file) => file.path === SKILL_FILE)) return {
|
|
7321
|
+
if (!files.some((file) => file.path === SKILL_FILE$1)) return {
|
|
7311
7322
|
kind: "invalid",
|
|
7312
|
-
problem: `response does not contain a root ${SKILL_FILE}`
|
|
7323
|
+
problem: `response does not contain a root ${SKILL_FILE$1}`
|
|
7313
7324
|
};
|
|
7314
7325
|
return {
|
|
7315
7326
|
files,
|
|
@@ -7483,7 +7494,7 @@ async function downloadFiles(environment, location, remoteFiles) {
|
|
|
7483
7494
|
async function listAgenticSkills(environment, source, options = {}) {
|
|
7484
7495
|
const catalog = await loadAgenticCatalog(environment, source, options.noCache === true);
|
|
7485
7496
|
if (catalog.kind === "failure") return {
|
|
7486
|
-
diagnostics: [diagnostic(`Skill source "${source.id}" ${catalog.reason}, so it is unavailable.`)],
|
|
7497
|
+
diagnostics: [diagnostic$1(`Skill source "${source.id}" ${catalog.reason}, so it is unavailable.`)],
|
|
7487
7498
|
listings: [],
|
|
7488
7499
|
status: {
|
|
7489
7500
|
hint: "unreachable",
|
|
@@ -7492,7 +7503,7 @@ async function listAgenticSkills(environment, source, options = {}) {
|
|
|
7492
7503
|
};
|
|
7493
7504
|
return {
|
|
7494
7505
|
collections: catalog.collections,
|
|
7495
|
-
diagnostics: [...cacheDiagnostics(source, catalog), ...catalog.problems.map((problem) => diagnostic(`Skill source "${source.id}" catalog ${problem}, so some of it is unavailable.`))],
|
|
7506
|
+
diagnostics: [...cacheDiagnostics(source, catalog), ...catalog.problems.map((problem) => diagnostic$1(`Skill source "${source.id}" catalog ${problem}, so some of it is unavailable.`))],
|
|
7496
7507
|
listings: Object.freeze(catalog.entries.map(({ listing }) => Object.freeze({
|
|
7497
7508
|
...listing,
|
|
7498
7509
|
source
|
|
@@ -7506,7 +7517,7 @@ async function listAgenticSkills(environment, source, options = {}) {
|
|
|
7506
7517
|
function cacheDiagnostics(source, catalog) {
|
|
7507
7518
|
if (catalog.cacheAgeMs === void 0) return [];
|
|
7508
7519
|
const age = describeCacheAge(catalog.cacheAgeMs);
|
|
7509
|
-
return [diagnostic(catalog.staleAfterFailure === true ? `Skill source "${source.id}" could not be reached, so its listing is served from the local cache (${age}).` : `Skill source "${source.id}" listing served from the local cache (${age}); pass --no-cache to refetch it now.`)];
|
|
7520
|
+
return [diagnostic$1(catalog.staleAfterFailure === true ? `Skill source "${source.id}" could not be reached, so its listing is served from the local cache (${age}).` : `Skill source "${source.id}" listing served from the local cache (${age}); pass --no-cache to refetch it now.`)];
|
|
7510
7521
|
}
|
|
7511
7522
|
/** Resolves AgenticSkills entries from their exact GitHub directories into reviewed Aura packs. */
|
|
7512
7523
|
async function resolveAgenticSkills(environment, source, skillIds) {
|
|
@@ -7529,7 +7540,7 @@ async function resolveAgenticSkills(environment, source, skillIds) {
|
|
|
7529
7540
|
function catalogEntries(catalog) {
|
|
7530
7541
|
return catalog.kind === "catalog" ? new Map(catalog.entries.map((entry) => [entry.listing.id, entry])) : /* @__PURE__ */ new Map();
|
|
7531
7542
|
}
|
|
7532
|
-
function diagnostic(message) {
|
|
7543
|
+
function diagnostic$1(message) {
|
|
7533
7544
|
return {
|
|
7534
7545
|
adapterId: AGENTICSKILLS_DIAGNOSTIC_ID,
|
|
7535
7546
|
message,
|
|
@@ -7537,7 +7548,7 @@ function diagnostic(message) {
|
|
|
7537
7548
|
};
|
|
7538
7549
|
}
|
|
7539
7550
|
function skillDiagnostic(source, id, reason) {
|
|
7540
|
-
return diagnostic(`Skill "${id}" from "${source.id}" ${reason}, so it is unavailable.`);
|
|
7551
|
+
return diagnostic$1(`Skill "${id}" from "${source.id}" ${reason}, so it is unavailable.`);
|
|
7541
7552
|
}
|
|
7542
7553
|
function isDiagnostic(value) {
|
|
7543
7554
|
return "adapterId" in value;
|
|
@@ -7851,7 +7862,7 @@ const DIRECTORY_PREFIX = "directory:";
|
|
|
7851
7862
|
/** Namespaced MCP catalog id, such as `official/github`. */
|
|
7852
7863
|
const MCP_CATALOG_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*\/[a-zA-Z0-9][a-zA-Z0-9._-]*$/u;
|
|
7853
7864
|
const SKILL_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
7854
|
-
const SKILL_SOURCE_ID_PATTERN = /^(?:directory|driver|plugin):[^\s:]+$/u;
|
|
7865
|
+
const SKILL_SOURCE_ID_PATTERN = /^(?:directory|driver|plugin|repo):[^\s:]+$/u;
|
|
7855
7866
|
/**
|
|
7856
7867
|
* Picks the first collected problem out of a batch of independently validated fields.
|
|
7857
7868
|
*
|
|
@@ -7984,6 +7995,52 @@ function collectJsonValue(value, path, depth) {
|
|
|
7984
7995
|
return collectJsonObject(value, path, depth);
|
|
7985
7996
|
}
|
|
7986
7997
|
//#endregion
|
|
7998
|
+
//#region ../core/src/preset/repo-content-limits.ts
|
|
7999
|
+
/**
|
|
8000
|
+
* The largest combined byte size of a repository's content set.
|
|
8001
|
+
*
|
|
8002
|
+
* The snapshot lives in memory for the whole run so planners apply consented bytes rather than a
|
|
8003
|
+
* re-read; a repository must not be able to balloon that residency.
|
|
8004
|
+
*/
|
|
8005
|
+
const MAX_REPO_CONTENT_TOTAL_BYTES = 16e6;
|
|
8006
|
+
//#endregion
|
|
8007
|
+
//#region ../core/src/preset/schema-provides.ts
|
|
8008
|
+
const REPO_MCP_ID_PATTERN = /^repo\/[a-zA-Z0-9][a-zA-Z0-9._-]*$/u;
|
|
8009
|
+
/**
|
|
8010
|
+
* Collects the `provides` envelope of a repository preset.
|
|
8011
|
+
*
|
|
8012
|
+
* Content a preset authors reaches command lines and app configuration, so it may only arrive
|
|
8013
|
+
* from the file the user hash-trusted for this repository. Callers validating a downloaded or
|
|
8014
|
+
* bundled preset reject the field wholesale before this collector runs.
|
|
8015
|
+
*/
|
|
8016
|
+
function collectProvides(value) {
|
|
8017
|
+
if (value === void 0) return;
|
|
8018
|
+
if (!isRecord$4(value)) return "$.provides: must be an object";
|
|
8019
|
+
const servers = collectProvidedMcpServers(value["mcpServers"]);
|
|
8020
|
+
if (typeof servers === "string") return servers;
|
|
8021
|
+
return servers === void 0 ? Object.freeze({}) : Object.freeze({ mcpServers: servers });
|
|
8022
|
+
}
|
|
8023
|
+
function collectProvidedMcpServers(value) {
|
|
8024
|
+
if (value === void 0) return;
|
|
8025
|
+
if (!Array.isArray(value)) return "$.provides.mcpServers: must be an array of MCP server definitions";
|
|
8026
|
+
if (value.length > 16) return `$.provides.mcpServers: must contain at most ${String(16)} definitions`;
|
|
8027
|
+
const result = [];
|
|
8028
|
+
const ids = /* @__PURE__ */ new Set();
|
|
8029
|
+
const serverNames = /* @__PURE__ */ new Set();
|
|
8030
|
+
for (const [index, candidate] of value.entries()) {
|
|
8031
|
+
const path = `$.provides.mcpServers[${String(index)}]`;
|
|
8032
|
+
const parsed = parseMcpServerManifestValue(candidate);
|
|
8033
|
+
if ("error" in parsed) return `${path}${parsed.error.path.slice(1)}: ${parsed.error.message}`;
|
|
8034
|
+
if (!REPO_MCP_ID_PATTERN.test(parsed.value.id)) return `${path}.id: must be namespaced "repo/<name>"`;
|
|
8035
|
+
if (ids.has(parsed.value.id)) return `${path}.id: must not duplicate another provided server id`;
|
|
8036
|
+
if (serverNames.has(parsed.value.serverName)) return `${path}.serverName: must not duplicate another provided server name`;
|
|
8037
|
+
ids.add(parsed.value.id);
|
|
8038
|
+
serverNames.add(parsed.value.serverName);
|
|
8039
|
+
result.push(parsed.value);
|
|
8040
|
+
}
|
|
8041
|
+
return Object.freeze(result);
|
|
8042
|
+
}
|
|
8043
|
+
//#endregion
|
|
7987
8044
|
//#region ../core/src/preset/schema-sources.ts
|
|
7988
8045
|
function collectSkills(value) {
|
|
7989
8046
|
if (value === void 0) return;
|
|
@@ -7997,7 +8054,7 @@ function collectSkills(value) {
|
|
|
7997
8054
|
const id = candidate["id"];
|
|
7998
8055
|
const source = candidate["source"];
|
|
7999
8056
|
if (typeof id !== "string" || !SKILL_ID_PATTERN.test(id)) return `${path}.id: must be a kebab-case skill id`;
|
|
8000
|
-
if (typeof source !== "string" || !SKILL_SOURCE_ID_PATTERN.test(source)) return `${path}.source: must be a plugin:, directory:, or
|
|
8057
|
+
if (typeof source !== "string" || !SKILL_SOURCE_ID_PATTERN.test(source)) return `${path}.source: must be a plugin:, directory:, driver:, or repo: source id`;
|
|
8001
8058
|
const identity = `${source}/${id}`;
|
|
8002
8059
|
if (seen.has(identity)) return `${path}: must not duplicate another skill selection`;
|
|
8003
8060
|
seen.add(identity);
|
|
@@ -8071,31 +8128,32 @@ function toDirectoryId(id) {
|
|
|
8071
8128
|
function toSourceId(id) {
|
|
8072
8129
|
if (id.startsWith("directory:")) return toDirectoryId(id);
|
|
8073
8130
|
if (id.startsWith("driver:")) return `driver:${id.slice(7)}`;
|
|
8131
|
+
if (id.startsWith("repo:")) return `repo:${id.slice(5)}`;
|
|
8074
8132
|
return `plugin:${id.slice(id.indexOf(":") + 1)}`;
|
|
8075
8133
|
}
|
|
8076
8134
|
//#endregion
|
|
8077
8135
|
//#region ../core/src/preset/schema.ts
|
|
8078
8136
|
/** Validates and freezes one parsed JSON document as a data-only team preset. */
|
|
8079
|
-
function validateTeamPreset(value) {
|
|
8137
|
+
function validateTeamPreset(value, options = {}) {
|
|
8080
8138
|
if (!isRecord$4(value)) return invalid$2("$: must be an object");
|
|
8081
|
-
const
|
|
8082
|
-
if (
|
|
8083
|
-
if (value["schemaVersion"] !== 1) return invalid$2("$.schemaVersion: must be 1");
|
|
8139
|
+
const problemBeforeFields = envelopeProblem(value, options);
|
|
8140
|
+
if (problemBeforeFields !== void 0) return invalid$2(problemBeforeFields);
|
|
8084
8141
|
const name = value["name"];
|
|
8085
|
-
if (name !== void 0 && (typeof name !== "string" || name.trim().length === 0 || name.length > 128)) return invalid$2("$.name: must be a non-empty string of at most 128 characters");
|
|
8086
8142
|
const checks = collectChecks(value["checks"]);
|
|
8087
8143
|
const allowed = collectAllowedSources(value["allowedSkillSources"]);
|
|
8088
8144
|
const directories = collectDirectories(value["skillDirectories"]);
|
|
8089
8145
|
const requiredMcpServers = collectIds(value["requiredMcpServers"], "$.requiredMcpServers", MCP_CATALOG_ID_PATTERN);
|
|
8090
8146
|
const snippets = collectIds(value["snippets"], "$.snippets", CONTENT_ID_PATTERN);
|
|
8091
8147
|
const skills = collectSkills(value["skills"]);
|
|
8148
|
+
const provides = collectProvides(value["provides"]);
|
|
8092
8149
|
const problem = firstProblem([
|
|
8093
8150
|
checks,
|
|
8094
8151
|
allowed,
|
|
8095
8152
|
directories,
|
|
8096
8153
|
requiredMcpServers,
|
|
8097
8154
|
snippets,
|
|
8098
|
-
skills
|
|
8155
|
+
skills,
|
|
8156
|
+
provides
|
|
8099
8157
|
]);
|
|
8100
8158
|
if (problem !== void 0) return invalid$2(problem);
|
|
8101
8159
|
return {
|
|
@@ -8103,6 +8161,7 @@ function validateTeamPreset(value) {
|
|
|
8103
8161
|
preset: Object.freeze({
|
|
8104
8162
|
...Array.isArray(allowed) ? { allowedSkillSources: allowed } : {},
|
|
8105
8163
|
...isChecks(checks) ? { checks } : {},
|
|
8164
|
+
...typeof provides === "object" ? { provides } : {},
|
|
8106
8165
|
...Array.isArray(requiredMcpServers) ? { requiredMcpServers } : {},
|
|
8107
8166
|
...typeof name === "string" ? { name } : {},
|
|
8108
8167
|
schemaVersion: 1,
|
|
@@ -8118,6 +8177,15 @@ function invalid$2(problem) {
|
|
|
8118
8177
|
problem
|
|
8119
8178
|
};
|
|
8120
8179
|
}
|
|
8180
|
+
/** Checks the document envelope — depth, version, origin gate, name — before any field walks. */
|
|
8181
|
+
function envelopeProblem(value, options) {
|
|
8182
|
+
const depthProblem = documentDepthProblem(value, "$", 0);
|
|
8183
|
+
if (depthProblem !== void 0) return depthProblem;
|
|
8184
|
+
if (value["schemaVersion"] !== 1) return "$.schemaVersion: must be 1";
|
|
8185
|
+
if (options.allowProvides !== true && value["provides"] !== void 0) return "$.provides: only the repository preset may provide content";
|
|
8186
|
+
const name = value["name"];
|
|
8187
|
+
if (name !== void 0 && (typeof name !== "string" || name.trim().length === 0 || name.length > 128)) return "$.name: must be a non-empty string of at most 128 characters";
|
|
8188
|
+
}
|
|
8121
8189
|
/**
|
|
8122
8190
|
* Rejects a document nested past the limit before any field is read.
|
|
8123
8191
|
*
|
|
@@ -8967,7 +9035,7 @@ async function readTeamPreset(cwd, reader) {
|
|
|
8967
9035
|
} catch {
|
|
8968
9036
|
return failure(path, "is not valid JSON");
|
|
8969
9037
|
}
|
|
8970
|
-
const result = validateTeamPreset(parsed);
|
|
9038
|
+
const result = validateTeamPreset(parsed, { allowProvides: true });
|
|
8971
9039
|
if (result.kind === "invalid") return failure(path, `is not a valid team preset (${result.problem})`);
|
|
8972
9040
|
return {
|
|
8973
9041
|
content: contents.content,
|
|
@@ -8989,18 +9057,275 @@ function failure(path, reason) {
|
|
|
8989
9057
|
};
|
|
8990
9058
|
}
|
|
8991
9059
|
//#endregion
|
|
8992
|
-
//#region ../core/src/
|
|
9060
|
+
//#region ../core/src/skills/repo-source.ts
|
|
9061
|
+
const REPO_SKILLS_DIAGNOSTIC_ID = "core/repo-skills";
|
|
9062
|
+
const SKILL_FILE = "SKILL.md";
|
|
9063
|
+
/** The one source id repository skills are offered under; one repository, one source. */
|
|
9064
|
+
const REPO_SKILL_SOURCE_ID = "repo:workspace";
|
|
9065
|
+
/** The repository skills directory as a picker-facing source. */
|
|
9066
|
+
function repoSkillSource(path) {
|
|
9067
|
+
return Object.freeze({
|
|
9068
|
+
id: REPO_SKILL_SOURCE_ID,
|
|
9069
|
+
kind: "repo",
|
|
9070
|
+
name: "This repository",
|
|
9071
|
+
path
|
|
9072
|
+
});
|
|
9073
|
+
}
|
|
9074
|
+
/**
|
|
9075
|
+
* Resolves every skill tree below `.aura/skills` into an installable pack.
|
|
9076
|
+
*
|
|
9077
|
+
* A broken entry earns a diagnostic and drops out rather than failing the run: unlike snippets,
|
|
9078
|
+
* these trees are outside the trust hash — they are offers the per-skill review gates, so an
|
|
9079
|
+
* unreadable one is a smaller catalog, not a broken consent record. Each tree walks under the
|
|
9080
|
+
* driver policy: a cloned tree deserves exactly the suspicion a driver-materialized one gets.
|
|
9081
|
+
*/
|
|
9082
|
+
async function resolveRepoSkills(root, boundary, reader, maxTotalBytes) {
|
|
9083
|
+
const rootRead = await reader.readWithin(root, [boundary]);
|
|
9084
|
+
const { contents } = rootRead;
|
|
9085
|
+
if (!contents.exists) return {
|
|
9086
|
+
diagnostics: [],
|
|
9087
|
+
skills: []
|
|
9088
|
+
};
|
|
9089
|
+
if (rootRead.kind !== "read" || contents.pathKind === "symlink" || contents.problem !== void 0 || contents.entries === void 0) return {
|
|
9090
|
+
diagnostics: [{
|
|
9091
|
+
adapterId: REPO_SKILLS_DIAGNOSTIC_ID,
|
|
9092
|
+
message: "Repository skills path .aura/skills is not a directory, so none are offered.",
|
|
9093
|
+
path: root,
|
|
9094
|
+
phase: "read"
|
|
9095
|
+
}],
|
|
9096
|
+
skills: []
|
|
9097
|
+
};
|
|
9098
|
+
const diagnostics = [];
|
|
9099
|
+
const named = contents.entries.filter((entry) => !entry.startsWith("."));
|
|
9100
|
+
if (named.length > 32) diagnostics.push({
|
|
9101
|
+
adapterId: REPO_SKILLS_DIAGNOSTIC_ID,
|
|
9102
|
+
message: `Repository skills directory holds more than the ${String(32)} entries Aura offers; the rest are ignored.`,
|
|
9103
|
+
path: root,
|
|
9104
|
+
phase: "read"
|
|
9105
|
+
});
|
|
9106
|
+
const skills = [];
|
|
9107
|
+
const source = repoSkillSource(root);
|
|
9108
|
+
let bytesRead = 0;
|
|
9109
|
+
for (const id of named.slice(0, 32)) {
|
|
9110
|
+
const remainingBytes = maxTotalBytes - bytesRead;
|
|
9111
|
+
if (remainingBytes <= 0) {
|
|
9112
|
+
diagnostics.push(diagnostic(join(root, id), "exceeds the repository content size budget"));
|
|
9113
|
+
continue;
|
|
9114
|
+
}
|
|
9115
|
+
const outcome = await resolveOneSkill(root, id, source, reader, boundary, remainingWalkPolicy(remainingBytes));
|
|
9116
|
+
bytesRead += outcome.bytesRead;
|
|
9117
|
+
if (outcome.problem !== void 0) {
|
|
9118
|
+
diagnostics.push(diagnostic(join(root, id), outcome.bytesRead >= remainingBytes ? "exceeds the repository content size budget" : outcome.problem));
|
|
9119
|
+
continue;
|
|
9120
|
+
}
|
|
9121
|
+
skills.push(outcome.skill);
|
|
9122
|
+
}
|
|
9123
|
+
return {
|
|
9124
|
+
diagnostics: Object.freeze(diagnostics),
|
|
9125
|
+
skills: Object.freeze(skills)
|
|
9126
|
+
};
|
|
9127
|
+
}
|
|
9128
|
+
async function resolveOneSkill(root, id, source, reader, boundary, policy) {
|
|
9129
|
+
const idProblem = skillIdProblem(id);
|
|
9130
|
+
if (idProblem !== void 0) return {
|
|
9131
|
+
bytesRead: 0,
|
|
9132
|
+
problem: idProblem
|
|
9133
|
+
};
|
|
9134
|
+
const tree = await walkTree(join(root, id), reader, policy, boundary);
|
|
9135
|
+
if (tree.problem !== void 0) return {
|
|
9136
|
+
bytesRead: tree.bytesRead,
|
|
9137
|
+
problem: tree.problem.message
|
|
9138
|
+
};
|
|
9139
|
+
const definition = tree.files.find((file) => file.path === SKILL_FILE);
|
|
9140
|
+
if (definition === void 0) return {
|
|
9141
|
+
bytesRead: tree.bytesRead,
|
|
9142
|
+
problem: `does not contain ${SKILL_FILE}`
|
|
9143
|
+
};
|
|
9144
|
+
const frontmatter = parseSkillFrontmatter(definition.content);
|
|
9145
|
+
return {
|
|
9146
|
+
bytesRead: tree.bytesRead,
|
|
9147
|
+
skill: Object.freeze({
|
|
9148
|
+
description: frontmatter.description ?? "Repository skill.",
|
|
9149
|
+
files: tree.files,
|
|
9150
|
+
id,
|
|
9151
|
+
name: frontmatter.name ?? id,
|
|
9152
|
+
source,
|
|
9153
|
+
treeHash: treeHash(tree.files),
|
|
9154
|
+
version: frontmatter.version ?? "0.0.0"
|
|
9155
|
+
})
|
|
9156
|
+
};
|
|
9157
|
+
}
|
|
9158
|
+
/** Caps each attempt by what remains of the repository-wide read budget. */
|
|
9159
|
+
function remainingWalkPolicy(remainingBytes) {
|
|
9160
|
+
return Object.freeze({
|
|
9161
|
+
...DRIVER_WALK_POLICY,
|
|
9162
|
+
maxFileBytes: Math.min(DRIVER_WALK_POLICY.maxFileBytes, remainingBytes),
|
|
9163
|
+
maxTotalBytes: Math.min(DRIVER_WALK_POLICY.maxTotalBytes, remainingBytes)
|
|
9164
|
+
});
|
|
9165
|
+
}
|
|
9166
|
+
function diagnostic(path, message) {
|
|
9167
|
+
return {
|
|
9168
|
+
adapterId: REPO_SKILLS_DIAGNOSTIC_ID,
|
|
9169
|
+
message: `Repository skill ${message}, so it is not offered.`,
|
|
9170
|
+
path,
|
|
9171
|
+
phase: "read"
|
|
9172
|
+
};
|
|
9173
|
+
}
|
|
9174
|
+
//#endregion
|
|
9175
|
+
//#region ../core/src/preset/repo-content-files.ts
|
|
9176
|
+
/** Matches the optional frontmatter block a snippet file may open with. */
|
|
9177
|
+
const FRONTMATTER_PATTERN = /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/u;
|
|
9178
|
+
/** Reads every Markdown snippet below `.aura/snippets` under the trust-set rules. */
|
|
9179
|
+
async function readRepoSnippets(root, boundary, reader) {
|
|
9180
|
+
const contents = await reader.read(root);
|
|
9181
|
+
if (!contents.exists) return {
|
|
9182
|
+
files: [],
|
|
9183
|
+
status: "ready"
|
|
9184
|
+
};
|
|
9185
|
+
if (contents.entries === void 0) return {
|
|
9186
|
+
problem: "the snippets path is not a directory",
|
|
9187
|
+
status: "invalid"
|
|
9188
|
+
};
|
|
9189
|
+
const names = contents.entries.filter((entry) => !entry.startsWith(".") && entry.endsWith(".md"));
|
|
9190
|
+
if (names.length > 64) return {
|
|
9191
|
+
problem: `holds more than the ${String(64)} snippet limit`,
|
|
9192
|
+
status: "invalid"
|
|
9193
|
+
};
|
|
9194
|
+
const files = [];
|
|
9195
|
+
for (const name of names) {
|
|
9196
|
+
const result = await readSnippetFile(root, name, boundary, reader);
|
|
9197
|
+
if (typeof result === "string") return {
|
|
9198
|
+
problem: result,
|
|
9199
|
+
status: "invalid"
|
|
9200
|
+
};
|
|
9201
|
+
files.push(result);
|
|
9202
|
+
}
|
|
9203
|
+
files.sort((left, right) => left.entry.id < right.entry.id ? -1 : 1);
|
|
9204
|
+
return {
|
|
9205
|
+
files: Object.freeze(files),
|
|
9206
|
+
status: "ready"
|
|
9207
|
+
};
|
|
9208
|
+
}
|
|
9209
|
+
async function readSnippetFile(root, name, boundary, reader) {
|
|
9210
|
+
const stem = name.slice(0, -3);
|
|
9211
|
+
if (skillIdProblem(stem) !== void 0) return "contains an entry that is not a kebab-case Markdown file name";
|
|
9212
|
+
const read = await reader.readWithin(join(root, name), [boundary], { maxBytes: MAX_SNIPPET_BYTES });
|
|
9213
|
+
const problem = snippetReadProblem(read);
|
|
9214
|
+
if (problem !== void 0) return problem;
|
|
9215
|
+
const text = read.contents.content ?? "";
|
|
9216
|
+
const frontmatter = parseSkillFrontmatter(text);
|
|
9217
|
+
const body = text.replace(FRONTMATTER_PATTERN, "");
|
|
9218
|
+
return {
|
|
9219
|
+
entry: Object.freeze({
|
|
9220
|
+
body,
|
|
9221
|
+
...frontmatter.description === void 0 ? {} : { description: frontmatter.description },
|
|
9222
|
+
id: `repo/${stem}`,
|
|
9223
|
+
name: frontmatter.name ?? stem
|
|
9224
|
+
}),
|
|
9225
|
+
text
|
|
9226
|
+
};
|
|
9227
|
+
}
|
|
9228
|
+
/** Why a bounded, boundary-checked read did not yield a plain snippet file. */
|
|
9229
|
+
function snippetReadProblem(read) {
|
|
9230
|
+
if (read.kind === "outside") return "contains an entry that leads outside the repository's .aura directory";
|
|
9231
|
+
if (read.kind === "unverified" || read.contents.pathKind === "symlink") return "contains an entry that is not a regular file";
|
|
9232
|
+
const { contents } = read;
|
|
9233
|
+
if (contents.problem !== void 0 || contents.isDirectory || contents.content === void 0 || contents.utf8Valid === false) return "contains an entry that is not a readable UTF-8 file";
|
|
9234
|
+
if ((contents.size ?? 0) > 256e3) return `contains an entry larger than the ${String(MAX_SNIPPET_BYTES)} byte snippet limit`;
|
|
9235
|
+
}
|
|
9236
|
+
//#endregion
|
|
9237
|
+
//#region ../core/src/preset/repo-content.ts
|
|
9238
|
+
const REPO_CONTENT_DIAGNOSTIC_ID = "core/repo-content";
|
|
9239
|
+
/**
|
|
9240
|
+
* Reads the repository's trust-hashed content and any requested installable content in one snapshot.
|
|
9241
|
+
*
|
|
9242
|
+
* The result is what catalogs and planners consume for the whole run: applying from this snapshot
|
|
9243
|
+
* rather than from a re-read is what closes the window between the bytes the user consented to
|
|
9244
|
+
* and the bytes a later write could put on disk. A broken snippet set fails the run — those bytes
|
|
9245
|
+
* are inside the trust hash — while a broken skill tree only shrinks the offers, because skills
|
|
9246
|
+
* stay behind the per-skill review.
|
|
9247
|
+
*/
|
|
9248
|
+
async function readRepoContent(auraDir, preset, presetText, reader, options = {}) {
|
|
9249
|
+
const boundary = await reader.realPath(auraDir) ?? auraDir;
|
|
9250
|
+
const snippets = await readRepoSnippets(join(auraDir, "snippets"), boundary, reader);
|
|
9251
|
+
if (snippets.status === "invalid") return {
|
|
9252
|
+
diagnostics: [{
|
|
9253
|
+
adapterId: REPO_CONTENT_DIAGNOSTIC_ID,
|
|
9254
|
+
message: `Repository snippets directory ".aura/snippets" ${snippets.problem}.`,
|
|
9255
|
+
path: join(auraDir, "snippets"),
|
|
9256
|
+
phase: "read"
|
|
9257
|
+
}],
|
|
9258
|
+
status: "invalid"
|
|
9259
|
+
};
|
|
9260
|
+
const snippetBytes = snippets.files.reduce((sum, file) => sum + Buffer$1.byteLength(file.text, "utf8"), 0);
|
|
9261
|
+
if (snippetBytes > 16e6) return {
|
|
9262
|
+
diagnostics: [{
|
|
9263
|
+
adapterId: REPO_CONTENT_DIAGNOSTIC_ID,
|
|
9264
|
+
message: `Repository snippets exceed the ${String(MAX_REPO_CONTENT_TOTAL_BYTES)} byte repository content budget.`,
|
|
9265
|
+
path: join(auraDir, "snippets"),
|
|
9266
|
+
phase: "read"
|
|
9267
|
+
}],
|
|
9268
|
+
status: "invalid"
|
|
9269
|
+
};
|
|
9270
|
+
const skills = options.includeSkills === false ? {
|
|
9271
|
+
diagnostics: [],
|
|
9272
|
+
skills: []
|
|
9273
|
+
} : await resolveRepoSkills(join(auraDir, "skills"), boundary, reader, MAX_REPO_CONTENT_TOTAL_BYTES - snippetBytes);
|
|
9274
|
+
return {
|
|
9275
|
+
contentSet: Object.freeze({
|
|
9276
|
+
mcpServers: preset.provides?.mcpServers ?? [],
|
|
9277
|
+
skills: skills.skills,
|
|
9278
|
+
snippets: Object.freeze(snippets.files.map((file) => file.entry))
|
|
9279
|
+
}),
|
|
9280
|
+
diagnostics: skills.diagnostics,
|
|
9281
|
+
hash: hashRepoContentSet(presetText, snippets.files.map((file) => ({
|
|
9282
|
+
id: file.entry.id,
|
|
9283
|
+
text: file.text
|
|
9284
|
+
}))),
|
|
9285
|
+
status: "ready"
|
|
9286
|
+
};
|
|
9287
|
+
}
|
|
8993
9288
|
/**
|
|
8994
|
-
*
|
|
9289
|
+
* Lifts the preset's inline MCP definitions into catalog entries.
|
|
8995
9290
|
*
|
|
8996
|
-
*
|
|
8997
|
-
*
|
|
9291
|
+
* The `repo/` namespace is reserved against plugins at registry build, so these can never collide
|
|
9292
|
+
* with — or be shadowed by — a plugin catalog entry. The source URL names the preset file itself:
|
|
9293
|
+
* that is the file whose bytes the user trusted.
|
|
8998
9294
|
*/
|
|
8999
|
-
function
|
|
9000
|
-
return
|
|
9295
|
+
function repoMcpServerDefs(servers, presetPath) {
|
|
9296
|
+
return Object.freeze(servers.map((manifest) => Object.freeze({
|
|
9297
|
+
description: manifest.description,
|
|
9298
|
+
id: manifest.id,
|
|
9299
|
+
kind: "mcp-server",
|
|
9300
|
+
manifest,
|
|
9301
|
+
name: manifest.name,
|
|
9302
|
+
source: Object.freeze({
|
|
9303
|
+
type: "file",
|
|
9304
|
+
url: pathToFileURL(presetPath).href
|
|
9305
|
+
}),
|
|
9306
|
+
version: "0.0.0"
|
|
9307
|
+
})));
|
|
9001
9308
|
}
|
|
9309
|
+
/**
|
|
9310
|
+
* Hashes the repository content set for the trust record.
|
|
9311
|
+
*
|
|
9312
|
+
* With no snippet files the hash is exactly the preset file's own content hash, so a repository
|
|
9313
|
+
* that never adds `.aura/snippets` keeps every trust its users already recorded. The first
|
|
9314
|
+
* snippet changes the descriptor shape — and consent is re-asked, which is the point.
|
|
9315
|
+
*/
|
|
9316
|
+
function hashRepoContentSet(presetText, snippets) {
|
|
9317
|
+
if (snippets.length === 0) return hashContent(presetText);
|
|
9318
|
+
const lines = [
|
|
9319
|
+
"aura-repo-content-v1",
|
|
9320
|
+
`preset ${hashContent(presetText)}`,
|
|
9321
|
+
...[...snippets].sort((left, right) => left.id < right.id ? -1 : 1).map((file) => `snippet ${file.id} ${hashContent(file.text)}`)
|
|
9322
|
+
];
|
|
9323
|
+
return hashContent(lines.join("\n"));
|
|
9324
|
+
}
|
|
9325
|
+
//#endregion
|
|
9326
|
+
//#region ../core/src/preset/repo-trust.ts
|
|
9002
9327
|
/** Reads and validates the repository preset below the invoking directory. */
|
|
9003
|
-
async function readRepoPreset(environment, reader = createFileReader()) {
|
|
9328
|
+
async function readRepoPreset(environment, reader = createFileReader(), options = {}) {
|
|
9004
9329
|
const cwd = await reader.realPath(environment.cwd) ?? environment.cwd;
|
|
9005
9330
|
const path = resolveTeamPresetPath(cwd);
|
|
9006
9331
|
const state = await readTeamPreset(cwd, reader);
|
|
@@ -9009,10 +9334,17 @@ async function readRepoPreset(environment, reader = createFileReader()) {
|
|
|
9009
9334
|
path,
|
|
9010
9335
|
status: state.status
|
|
9011
9336
|
};
|
|
9337
|
+
const content = await readRepoContent(dirname(path), state.preset, state.content, reader, options);
|
|
9338
|
+
if (content.status === "invalid") return {
|
|
9339
|
+
diagnostics: [...state.diagnostics, ...content.diagnostics],
|
|
9340
|
+
path,
|
|
9341
|
+
status: "invalid"
|
|
9342
|
+
};
|
|
9012
9343
|
const mainWorktreePath = await resolveMainWorktreePresetPath(cwd, path, reader);
|
|
9013
9344
|
return {
|
|
9014
|
-
|
|
9015
|
-
|
|
9345
|
+
contentSet: content.contentSet,
|
|
9346
|
+
diagnostics: [...state.diagnostics, ...content.diagnostics],
|
|
9347
|
+
hash: content.hash,
|
|
9016
9348
|
...mainWorktreePath === void 0 ? {} : { mainWorktreePath },
|
|
9017
9349
|
path,
|
|
9018
9350
|
preset: state.preset,
|
|
@@ -9117,6 +9449,91 @@ function note(reason, id, selection) {
|
|
|
9117
9449
|
};
|
|
9118
9450
|
}
|
|
9119
9451
|
//#endregion
|
|
9452
|
+
//#region src/runtime-config.ts
|
|
9453
|
+
/** Loads the selected preset and resolves all runtime layers once for a command. */
|
|
9454
|
+
async function resolveRuntimeConfig(input) {
|
|
9455
|
+
if (input.manifest.status === "read-only") return {
|
|
9456
|
+
message: input.manifest.problem.message,
|
|
9457
|
+
status: "invalid"
|
|
9458
|
+
};
|
|
9459
|
+
const manifest = input.manifest.status === "ready" ? input.manifest.value : void 0;
|
|
9460
|
+
const loaded = await loadTeamPreset({
|
|
9461
|
+
cliReference: input.cliReference,
|
|
9462
|
+
defaultReference: input.defaultPreset,
|
|
9463
|
+
environment: input.environment,
|
|
9464
|
+
manifestReference: manifest?.preset,
|
|
9465
|
+
noCache: input.noCache,
|
|
9466
|
+
offline: input.online !== true,
|
|
9467
|
+
presets: input.registry.presets
|
|
9468
|
+
});
|
|
9469
|
+
if (loaded.status === "invalid") return loaded;
|
|
9470
|
+
const repo = input.repoPresetState ?? await readRepoPreset(input.environment, void 0, { includeSkills: false });
|
|
9471
|
+
if (repo.status === "invalid") return {
|
|
9472
|
+
message: `${repo.diagnostics[0]?.message ?? `Repository preset ${repo.path} cannot be read.`} Fix or remove the file to continue.`,
|
|
9473
|
+
status: "invalid"
|
|
9474
|
+
};
|
|
9475
|
+
const repoTrusted = repo.status === "ready" && repo.hash !== void 0 && (repo.hash === input.acceptedRepoPresetHash || isRepoPresetTrusted(manifest, repo, repo.hash));
|
|
9476
|
+
const resolved = resolveEffectiveConfig({
|
|
9477
|
+
checks: input.registry.checks,
|
|
9478
|
+
cli: input.cliLayer,
|
|
9479
|
+
distro: input.defaults,
|
|
9480
|
+
knownMcpServers: /* @__PURE__ */ new Set([...input.registry.mcpServers.map((server) => server.id), ...repoTrusted ? (repo.preset?.provides?.mcpServers ?? []).map((server) => server.id) : []]),
|
|
9481
|
+
...manifest === void 0 ? {} : { manifest: {
|
|
9482
|
+
...manifest.checks === void 0 ? {} : { checks: manifest.checks },
|
|
9483
|
+
skills: manifest.skills.map(({ id, source }) => ({
|
|
9484
|
+
id,
|
|
9485
|
+
source
|
|
9486
|
+
})),
|
|
9487
|
+
snippets: manifest.snippets.map(({ id }) => id)
|
|
9488
|
+
} },
|
|
9489
|
+
...loaded.status === "ready" ? {
|
|
9490
|
+
preset: loaded.preset,
|
|
9491
|
+
selectedPreset: loaded.selected
|
|
9492
|
+
} : {},
|
|
9493
|
+
...repoTrusted ? { repo: repo.preset } : {}
|
|
9494
|
+
});
|
|
9495
|
+
if (resolved.status === "invalid") return {
|
|
9496
|
+
message: resolved.problems.join("\n"),
|
|
9497
|
+
status: "invalid"
|
|
9498
|
+
};
|
|
9499
|
+
const policyPreset = loaded.status === "ready" || resolved.config.allowedSkillSources !== void 0 || resolved.config.skillDirectories.length > 0 ? Object.freeze({
|
|
9500
|
+
...loaded.status === "ready" ? loaded.preset : {},
|
|
9501
|
+
...resolved.config.allowedSkillSources === void 0 ? {} : { allowedSkillSources: resolved.config.allowedSkillSources.value },
|
|
9502
|
+
name: resolved.config.preset?.name ?? resolved.config.allowedSkillSources?.provenance.label ?? resolved.config.skillDirectories[0]?.provenance.label ?? "runtime policy",
|
|
9503
|
+
schemaVersion: 1,
|
|
9504
|
+
skillDirectories: resolved.config.skillDirectories.map(({ value }) => value)
|
|
9505
|
+
}) : void 0;
|
|
9506
|
+
return {
|
|
9507
|
+
config: resolved.config,
|
|
9508
|
+
notes: [...loaded.status === "ready" ? loaded.notes : [], ...repoTrusted ? repo.diagnostics.map((diagnostic) => diagnostic.message) : []],
|
|
9509
|
+
...policyPreset === void 0 ? {} : { preset: policyPreset },
|
|
9510
|
+
presetOrigin: loaded.status === "ready" ? loaded.origin : policyPreset?.name ?? ".aura/preset.json",
|
|
9511
|
+
...repo.status === "ready" && repo.hash !== void 0 ? { repoPreset: {
|
|
9512
|
+
...repoTrusted && repo.contentSet !== void 0 ? { contentSet: repo.contentSet } : {},
|
|
9513
|
+
hash: repo.hash,
|
|
9514
|
+
...repo.mainWorktreePath === void 0 ? {} : { mainWorktreePath: repo.mainWorktreePath },
|
|
9515
|
+
path: repo.path,
|
|
9516
|
+
...repoTrusted && repo.preset !== void 0 ? { preset: repo.preset } : {},
|
|
9517
|
+
status: repoTrusted ? "applied" : "held"
|
|
9518
|
+
} } : {},
|
|
9519
|
+
status: "ready"
|
|
9520
|
+
};
|
|
9521
|
+
}
|
|
9522
|
+
/**
|
|
9523
|
+
* Adds a trusted repository's provided MCP definitions to the model's catalog.
|
|
9524
|
+
*
|
|
9525
|
+
* Run before {@link applyRequiredMcpServers} projection wherever a model is projected, so a
|
|
9526
|
+
* repository can require a server it defines itself. A held repository changes nothing.
|
|
9527
|
+
*/
|
|
9528
|
+
function withRepoMcpCatalog(model, repoPreset) {
|
|
9529
|
+
const servers = repoPreset?.status === "applied" ? repoPreset.contentSet?.mcpServers ?? [] : [];
|
|
9530
|
+
if (servers.length === 0 || repoPreset === void 0) return model;
|
|
9531
|
+
return Object.freeze({
|
|
9532
|
+
...model,
|
|
9533
|
+
availableMcpServers: Object.freeze([...model.availableMcpServers, ...repoMcpServerDefs(servers, repoPreset.path)])
|
|
9534
|
+
});
|
|
9535
|
+
}
|
|
9536
|
+
//#endregion
|
|
9120
9537
|
//#region src/safe-text.ts
|
|
9121
9538
|
const MAX_FINDING_TEXT_CHARACTERS = 500;
|
|
9122
9539
|
const UNICODE_FORMAT_CHARACTER = /\p{Cf}/u;
|
|
@@ -9339,6 +9756,15 @@ function countsByCheck(findings) {
|
|
|
9339
9756
|
}
|
|
9340
9757
|
//#endregion
|
|
9341
9758
|
//#region src/command-support.ts
|
|
9759
|
+
/**
|
|
9760
|
+
* Projects preset requirements onto a check scan's model.
|
|
9761
|
+
*
|
|
9762
|
+
* A trusted repository's provided MCP definitions join the catalog first, so a repository can
|
|
9763
|
+
* require a server it defines itself and `check` reports that requirement the way setup would.
|
|
9764
|
+
*/
|
|
9765
|
+
function projectConfiguredModel(model, configured) {
|
|
9766
|
+
return applyRequiredMcpServers(withRepoMcpCatalog(model, configured.repoPreset), configured.config);
|
|
9767
|
+
}
|
|
9342
9768
|
/** The `--home` override every scanning command shares. */
|
|
9343
9769
|
function homeOption() {
|
|
9344
9770
|
return Option.String("--home", { description: "Override the home directory." });
|
|
@@ -9404,75 +9830,6 @@ function reportUnexpectedFailure(error, subject, branding, withDetail, stderr, t
|
|
|
9404
9830
|
return 3;
|
|
9405
9831
|
}
|
|
9406
9832
|
//#endregion
|
|
9407
|
-
//#region src/runtime-config.ts
|
|
9408
|
-
/** Loads the selected preset and resolves all runtime layers once for a command. */
|
|
9409
|
-
async function resolveRuntimeConfig(input) {
|
|
9410
|
-
if (input.manifest.status === "read-only") return {
|
|
9411
|
-
message: input.manifest.problem.message,
|
|
9412
|
-
status: "invalid"
|
|
9413
|
-
};
|
|
9414
|
-
const manifest = input.manifest.status === "ready" ? input.manifest.value : void 0;
|
|
9415
|
-
const loaded = await loadTeamPreset({
|
|
9416
|
-
cliReference: input.cliReference,
|
|
9417
|
-
defaultReference: input.defaultPreset,
|
|
9418
|
-
environment: input.environment,
|
|
9419
|
-
manifestReference: manifest?.preset,
|
|
9420
|
-
noCache: input.noCache,
|
|
9421
|
-
offline: input.online !== true,
|
|
9422
|
-
presets: input.registry.presets
|
|
9423
|
-
});
|
|
9424
|
-
if (loaded.status === "invalid") return loaded;
|
|
9425
|
-
const repo = await readRepoPreset(input.environment);
|
|
9426
|
-
if (repo.status === "invalid") return {
|
|
9427
|
-
message: `${repo.diagnostics[0]?.message ?? `Repository preset ${repo.path} cannot be read.`} Fix or remove the file to continue.`,
|
|
9428
|
-
status: "invalid"
|
|
9429
|
-
};
|
|
9430
|
-
const repoTrusted = repo.status === "ready" && repo.hash !== void 0 && (repo.hash === input.acceptedRepoPresetHash || isRepoPresetTrusted(manifest, repo, repo.hash));
|
|
9431
|
-
const resolved = resolveEffectiveConfig({
|
|
9432
|
-
checks: input.registry.checks,
|
|
9433
|
-
cli: input.cliLayer,
|
|
9434
|
-
distro: input.defaults,
|
|
9435
|
-
knownMcpServers: new Set(input.registry.mcpServers.map((server) => server.id)),
|
|
9436
|
-
...manifest === void 0 ? {} : { manifest: {
|
|
9437
|
-
...manifest.checks === void 0 ? {} : { checks: manifest.checks },
|
|
9438
|
-
skills: manifest.skills.map(({ id, source }) => ({
|
|
9439
|
-
id,
|
|
9440
|
-
source
|
|
9441
|
-
})),
|
|
9442
|
-
snippets: manifest.snippets.map(({ id }) => id)
|
|
9443
|
-
} },
|
|
9444
|
-
...loaded.status === "ready" ? {
|
|
9445
|
-
preset: loaded.preset,
|
|
9446
|
-
selectedPreset: loaded.selected
|
|
9447
|
-
} : {},
|
|
9448
|
-
...repoTrusted ? { repo: repo.preset } : {}
|
|
9449
|
-
});
|
|
9450
|
-
if (resolved.status === "invalid") return {
|
|
9451
|
-
message: resolved.problems.join("\n"),
|
|
9452
|
-
status: "invalid"
|
|
9453
|
-
};
|
|
9454
|
-
const policyPreset = loaded.status === "ready" || resolved.config.allowedSkillSources !== void 0 || resolved.config.skillDirectories.length > 0 ? Object.freeze({
|
|
9455
|
-
...loaded.status === "ready" ? loaded.preset : {},
|
|
9456
|
-
...resolved.config.allowedSkillSources === void 0 ? {} : { allowedSkillSources: resolved.config.allowedSkillSources.value },
|
|
9457
|
-
name: resolved.config.preset?.name ?? resolved.config.allowedSkillSources?.provenance.label ?? resolved.config.skillDirectories[0]?.provenance.label ?? "runtime policy",
|
|
9458
|
-
schemaVersion: 1,
|
|
9459
|
-
skillDirectories: resolved.config.skillDirectories.map(({ value }) => value)
|
|
9460
|
-
}) : void 0;
|
|
9461
|
-
return {
|
|
9462
|
-
config: resolved.config,
|
|
9463
|
-
notes: loaded.status === "ready" ? loaded.notes : [],
|
|
9464
|
-
...policyPreset === void 0 ? {} : { preset: policyPreset },
|
|
9465
|
-
presetOrigin: loaded.status === "ready" ? loaded.origin : policyPreset?.name ?? ".aura/preset.json",
|
|
9466
|
-
...repo.status === "ready" && repo.hash !== void 0 ? { repoPreset: {
|
|
9467
|
-
hash: repo.hash,
|
|
9468
|
-
...repo.mainWorktreePath === void 0 ? {} : { mainWorktreePath: repo.mainWorktreePath },
|
|
9469
|
-
path: repo.path,
|
|
9470
|
-
status: repoTrusted ? "applied" : "held"
|
|
9471
|
-
} } : {},
|
|
9472
|
-
status: "ready"
|
|
9473
|
-
};
|
|
9474
|
-
}
|
|
9475
|
-
//#endregion
|
|
9476
9833
|
//#region src/check-config.ts
|
|
9477
9834
|
/** Resolves the effective configuration for one `check` run. */
|
|
9478
9835
|
function resolveCheckConfiguration(options) {
|
|
@@ -12917,7 +13274,7 @@ var CheckCommand = class extends Command {
|
|
|
12917
13274
|
try {
|
|
12918
13275
|
const startedAt = environment.now();
|
|
12919
13276
|
let scan = await scanner.run();
|
|
12920
|
-
let projected =
|
|
13277
|
+
let projected = projectConfiguredModel(scan.model, configured);
|
|
12921
13278
|
let model = projected.model;
|
|
12922
13279
|
let run = runChecks(activeChecks, model, configured.config);
|
|
12923
13280
|
let fixRunDiagnostics = [];
|
|
@@ -12945,7 +13302,7 @@ var CheckCommand = class extends Command {
|
|
|
12945
13302
|
fixes = outcome.fixes;
|
|
12946
13303
|
if (outcome.applied) {
|
|
12947
13304
|
scan = await scanner.run();
|
|
12948
|
-
projected =
|
|
13305
|
+
projected = projectConfiguredModel(scan.model, configured);
|
|
12949
13306
|
model = projected.model;
|
|
12950
13307
|
run = runChecks(activeChecks, model, configured.config);
|
|
12951
13308
|
}
|
|
@@ -13029,7 +13386,7 @@ function renderRootHelp(branding) {
|
|
|
13029
13386
|
rows: [NO_COLOR_ROW],
|
|
13030
13387
|
title: "Advanced"
|
|
13031
13388
|
}
|
|
13032
|
-
], branding.docsUrl === void 0 ? [] : [`Docs: ${branding.docsUrl}`]);
|
|
13389
|
+
], branding.docsUrl === void 0 ? [] : [`Docs: ${branding.docsUrl}`], ROOT_INTRO);
|
|
13033
13390
|
}
|
|
13034
13391
|
function renderCheckHelp(branding) {
|
|
13035
13392
|
const bin = branding.command;
|
|
@@ -13216,6 +13573,17 @@ const NO_COLOR_ROW = {
|
|
|
13216
13573
|
term: "--no-color",
|
|
13217
13574
|
text: "Disable terminal colors"
|
|
13218
13575
|
};
|
|
13576
|
+
/**
|
|
13577
|
+
* The root screen's one paragraph: what Aura is for, in the words of someone who has not read the
|
|
13578
|
+
* docs yet. It sits above "Get started" because a first-time reader needs the problem before the
|
|
13579
|
+
* command. Hand-wrapped at 80 columns — the renderer aligns columns, it never reflows prose — and
|
|
13580
|
+
* carried only by the root screen, since every other screen is reached by someone who already knows.
|
|
13581
|
+
*/
|
|
13582
|
+
const ROOT_INTRO = [
|
|
13583
|
+
"Every AI coding agent keeps its own rules and its own tool settings, in its own",
|
|
13584
|
+
"files. Aura reads all of them, tells you where they disagree or are broken, and",
|
|
13585
|
+
"fixes them after you say yes - so every agent works from the same instructions."
|
|
13586
|
+
];
|
|
13219
13587
|
/** Test and CI plumbing, present on every command and interesting to almost nobody. */
|
|
13220
13588
|
function advancedRows() {
|
|
13221
13589
|
return [
|
|
@@ -13263,9 +13631,10 @@ function headline(branding) {
|
|
|
13263
13631
|
return branding.description === void 0 ? name : `${name} — ${branding.description}`;
|
|
13264
13632
|
}
|
|
13265
13633
|
/** Terms align to one shared column so the eye scans a single list, not per-section islands. */
|
|
13266
|
-
function renderHelpScreen(header, sections, footers) {
|
|
13634
|
+
function renderHelpScreen(header, sections, footers, intro = []) {
|
|
13267
13635
|
const width = Math.max(...sections.flatMap((section) => section.rows.map((row) => row.term.length)));
|
|
13268
13636
|
const lines = [header];
|
|
13637
|
+
if (intro.length > 0) lines.push("", ...intro.map((line) => ` ${line}`));
|
|
13269
13638
|
for (const section of sections) {
|
|
13270
13639
|
lines.push("", ` ${section.title}`);
|
|
13271
13640
|
for (const row of section.rows) lines.push(` ${row.term.padEnd(width)} ${row.text}`);
|
|
@@ -14160,6 +14529,131 @@ function formatThresholds(thresholds) {
|
|
|
14160
14529
|
return pairs.length === 0 ? "no thresholds" : pairs.join(", ");
|
|
14161
14530
|
}
|
|
14162
14531
|
//#endregion
|
|
14532
|
+
//#region src/setup/repo-trust-preview.ts
|
|
14533
|
+
/** Printed instead of the row block when the preset carries nothing worth reviewing. */
|
|
14534
|
+
const NO_SETTINGS = "No check, MCP, skill, or snippet settings.";
|
|
14535
|
+
/**
|
|
14536
|
+
* Security-relevant capabilities shown before repository-controlled settings are accepted.
|
|
14537
|
+
*
|
|
14538
|
+
* One row per thing, never one row per field: a snippet the preset both provides and selects is
|
|
14539
|
+
* one snippet, and naming it twice made the screen long enough to skim past. Everything
|
|
14540
|
+
* executable-adjacent is spelled out verbatim (escaped) — the MCP command line or endpoint is
|
|
14541
|
+
* exactly what a later selection would configure, and trust time is the one moment the user is
|
|
14542
|
+
* looking. Snippet bodies are deliberately not echoed — pasting untrusted Markdown into a consent
|
|
14543
|
+
* prompt is its own injection surface — the picker's preview is where they are read, and the
|
|
14544
|
+
* prompt itself carries the explicit-selection boundary.
|
|
14545
|
+
*/
|
|
14546
|
+
function repoPresetTrustPreview(preset, contentSet) {
|
|
14547
|
+
const rows = [
|
|
14548
|
+
...mcpRows(preset, contentSet),
|
|
14549
|
+
...skillRows(preset, contentSet),
|
|
14550
|
+
...snippetRows(preset, contentSet),
|
|
14551
|
+
...(preset.skillDirectories ?? []).map(directoryRow),
|
|
14552
|
+
...listRow("sources", preset.allowedSkillSources),
|
|
14553
|
+
...checksRow(preset.checks)
|
|
14554
|
+
];
|
|
14555
|
+
return [
|
|
14556
|
+
"",
|
|
14557
|
+
...rows.length === 0 ? [` ${NO_SETTINGS}`] : alignRows(rows),
|
|
14558
|
+
""
|
|
14559
|
+
].join("\n");
|
|
14560
|
+
}
|
|
14561
|
+
/** Whether the prompt should promise that admitted content still needs a deliberate tick. */
|
|
14562
|
+
function hasRepoContent(contentSet) {
|
|
14563
|
+
return contentSet !== void 0 && contentSet.mcpServers.length + contentSet.skills.length + contentSet.snippets.length > 0;
|
|
14564
|
+
}
|
|
14565
|
+
function alignRows(rows) {
|
|
14566
|
+
const width = Math.max(...rows.map((row) => row.term.length));
|
|
14567
|
+
return rows.map((row) => ` ${row.term.padEnd(width)} ${row.detail}`);
|
|
14568
|
+
}
|
|
14569
|
+
/**
|
|
14570
|
+
* Every MCP server the repository would configure, provided ones with their full command line.
|
|
14571
|
+
*
|
|
14572
|
+
* A required id the repository does not define itself resolves from the catalog, so it gets a row
|
|
14573
|
+
* saying that rather than an executable surface this file cannot promise.
|
|
14574
|
+
*/
|
|
14575
|
+
function mcpRows(preset, contentSet) {
|
|
14576
|
+
const provided = contentSet?.mcpServers ?? [];
|
|
14577
|
+
const providedIds = new Set(provided.map((server) => server.id));
|
|
14578
|
+
const required = new Set(preset.requiredMcpServers ?? []);
|
|
14579
|
+
return [...provided.map((server) => ({
|
|
14580
|
+
detail: `${mcpServerPreview(server)}${required.has(server.id) ? " · required" : ""}`,
|
|
14581
|
+
term: "mcp"
|
|
14582
|
+
})), ...[...required].filter((id) => !providedIds.has(id)).map((id) => ({
|
|
14583
|
+
detail: `${safe(id)} · required, from the catalog`,
|
|
14584
|
+
term: "mcp"
|
|
14585
|
+
}))];
|
|
14586
|
+
}
|
|
14587
|
+
/** Skill trees the repository ships, then selections that point somewhere else. */
|
|
14588
|
+
function skillRows(preset, contentSet) {
|
|
14589
|
+
const provided = contentSet?.skills ?? [];
|
|
14590
|
+
const providedKeys = new Set(provided.map((skill) => `${skill.source.id}/${skill.id}`));
|
|
14591
|
+
return [...provided.map((skill) => ({
|
|
14592
|
+
detail: `${safe(skill.id)} (${count(skill.files.length, "file")})`,
|
|
14593
|
+
term: "skill"
|
|
14594
|
+
})), ...(preset.skills ?? []).map((selection) => `${selection.source}/${selection.id}`).filter((key) => !providedKeys.has(key)).map((key) => ({
|
|
14595
|
+
detail: `${safe(key)} · selected`,
|
|
14596
|
+
term: "skill"
|
|
14597
|
+
}))];
|
|
14598
|
+
}
|
|
14599
|
+
/** Snippets the repository ships, then selections from the catalog. */
|
|
14600
|
+
function snippetRows(preset, contentSet) {
|
|
14601
|
+
const provided = contentSet?.snippets ?? [];
|
|
14602
|
+
const providedIds = new Set(provided.map((snippet) => snippet.id));
|
|
14603
|
+
return [...provided.map((snippet) => ({
|
|
14604
|
+
detail: `${safe(snippet.id)} (${String(Buffer$1.byteLength(snippet.body, "utf8"))} B)`,
|
|
14605
|
+
term: "snippet"
|
|
14606
|
+
})), ...(preset.snippets ?? []).filter((id) => !providedIds.has(id)).map((id) => ({
|
|
14607
|
+
detail: `${safe(id)} · selected`,
|
|
14608
|
+
term: "snippet"
|
|
14609
|
+
}))];
|
|
14610
|
+
}
|
|
14611
|
+
/** The full executable surface of one provided MCP server, escaped but verbatim. */
|
|
14612
|
+
function mcpServerPreview(server) {
|
|
14613
|
+
return `${safe(server.id)} "${safe(server.serverName)}" → ${transportPreview(server.transportTemplate)}`;
|
|
14614
|
+
}
|
|
14615
|
+
function transportPreview(transport) {
|
|
14616
|
+
if (transport.type === "http") return `http ${safe(transport.url)}`;
|
|
14617
|
+
const args = (transport.args ?? []).map((argument) => `"${safe(argument)}"`).join(", ");
|
|
14618
|
+
const env = (transport.env ?? []).map(safe).join(", ");
|
|
14619
|
+
return [
|
|
14620
|
+
`stdio "${safe(transport.command)}"`,
|
|
14621
|
+
...args === "" ? [] : [`args [${args}]`],
|
|
14622
|
+
...env === "" ? [] : [`env ${env}`]
|
|
14623
|
+
].join(", ");
|
|
14624
|
+
}
|
|
14625
|
+
/** The exact check settings the repository preset asks the user to trust. */
|
|
14626
|
+
function checksRow(checks) {
|
|
14627
|
+
if (checks === void 0) return [];
|
|
14628
|
+
const settings = [
|
|
14629
|
+
...(checks.disabled ?? []).map((id) => `${safe(id)}: disabled`),
|
|
14630
|
+
...(checks.enabled ?? []).map((id) => `${safe(id)}: enabled`),
|
|
14631
|
+
...Object.entries(checks.severity ?? {}).map(([id, severity]) => `${safe(id)}: severity ${safe(severity)}`),
|
|
14632
|
+
...Object.entries(checks.thresholds ?? {}).map(([id, thresholds]) => `${safe(id)}: thresholds ${safe(JSON.stringify(thresholds))}`)
|
|
14633
|
+
].sort();
|
|
14634
|
+
return [{
|
|
14635
|
+
detail: settings.length === 0 ? "(none)" : settings.join("; "),
|
|
14636
|
+
term: "checks"
|
|
14637
|
+
}];
|
|
14638
|
+
}
|
|
14639
|
+
function directoryRow(source) {
|
|
14640
|
+
const token = source.kind === "private-directory" ? ` · token ${safe(source.tokenEnv)}` : "";
|
|
14641
|
+
return {
|
|
14642
|
+
detail: `${safe(source.name)} → ${safe(source.url)}${token}`,
|
|
14643
|
+
term: "directory"
|
|
14644
|
+
};
|
|
14645
|
+
}
|
|
14646
|
+
function listRow(term, values) {
|
|
14647
|
+
if (values === void 0) return [];
|
|
14648
|
+
return [{
|
|
14649
|
+
detail: values.length === 0 ? "(none)" : values.map(safe).join(", "),
|
|
14650
|
+
term
|
|
14651
|
+
}];
|
|
14652
|
+
}
|
|
14653
|
+
function count(total, noun) {
|
|
14654
|
+
return `${String(total)} ${noun}${total === 1 ? "" : "s"}`;
|
|
14655
|
+
}
|
|
14656
|
+
//#endregion
|
|
14163
14657
|
//#region src/setup/repo-trust.ts
|
|
14164
14658
|
/**
|
|
14165
14659
|
* Asks whether an untrusted repository preset may be applied, once per repo and contents.
|
|
@@ -14169,61 +14663,39 @@ function formatThresholds(thresholds) {
|
|
|
14169
14663
|
* answer. A non-interactive run resolves without the layer, and the plan summary says so.
|
|
14170
14664
|
*/
|
|
14171
14665
|
async function establishRepoPresetTrust(options) {
|
|
14172
|
-
const repo = await readRepoPreset(options.environment);
|
|
14666
|
+
const repo = options.repoPresetState ?? await readRepoPreset(options.environment);
|
|
14173
14667
|
if (repo.status !== "ready" || repo.hash === void 0) return { kind: "resolved" };
|
|
14174
14668
|
const manifest = options.manifest.status === "ready" ? options.manifest.value : void 0;
|
|
14175
14669
|
if (isRepoPresetTrusted(manifest, repo, repo.hash) || !options.interactive) return { kind: "resolved" };
|
|
14176
14670
|
const name = repo.preset?.name;
|
|
14177
|
-
options.io.note(name === void 0 ? `
|
|
14178
|
-
if (repo.preset !== void 0) options.io.note(repoPresetTrustPreview(repo.preset));
|
|
14671
|
+
options.io.note(name === void 0 ? `Repository preset — ${AURA_TEAM_PRESET_PATH}` : `Repository preset "${safe(name)}" — ${AURA_TEAM_PRESET_PATH}`);
|
|
14672
|
+
if (repo.preset !== void 0) options.io.note(repoPresetTrustPreview(repo.preset, repo.contentSet));
|
|
14179
14673
|
if (repo.mainWorktreePath !== void 0) options.io.note("This directory is a linked worktree, so trusting these contents also applies them in every other worktree of the same checkout.");
|
|
14180
14674
|
const changed = (manifest?.trustedRepoPresets ?? []).some((entry) => entry.path === repo.path);
|
|
14181
|
-
const confirmation = await options.io.confirm(changed
|
|
14675
|
+
const confirmation = await options.io.confirm(trustPrompt(changed, repo.contentSet));
|
|
14182
14676
|
if (confirmation === "aborted") return { kind: "aborted" };
|
|
14183
14677
|
return confirmation === "accepted" ? {
|
|
14184
14678
|
acceptedHash: repo.hash,
|
|
14185
14679
|
kind: "resolved"
|
|
14186
14680
|
} : { kind: "resolved" };
|
|
14187
14681
|
}
|
|
14188
|
-
/**
|
|
14189
|
-
|
|
14190
|
-
|
|
14191
|
-
|
|
14192
|
-
|
|
14193
|
-
|
|
14194
|
-
|
|
14195
|
-
|
|
14196
|
-
|
|
14197
|
-
|
|
14198
|
-
return ["Review these repository-controlled settings before trusting:", ...settings.length === 0 ? ["No check, MCP, skill, or snippet settings."] : settings].join("\n");
|
|
14199
|
-
}
|
|
14200
|
-
/** The exact check settings the repository preset asks the user to trust. */
|
|
14201
|
-
function checksPreview(checks) {
|
|
14202
|
-
if (checks === void 0) return;
|
|
14203
|
-
const settings = [
|
|
14204
|
-
...(checks.disabled ?? []).map((id) => `${safe(id)}: disabled`),
|
|
14205
|
-
...(checks.enabled ?? []).map((id) => `${safe(id)}: enabled`),
|
|
14206
|
-
...Object.entries(checks.severity ?? {}).map(([id, severity]) => `${safe(id)}: severity ${safe(severity)}`),
|
|
14207
|
-
...Object.entries(checks.thresholds ?? {}).map(([id, thresholds]) => `${safe(id)}: thresholds ${safe(JSON.stringify(thresholds))}`)
|
|
14208
|
-
].sort();
|
|
14209
|
-
return `Checks: ${settings.length === 0 ? "(none)" : settings.join("; ")}`;
|
|
14210
|
-
}
|
|
14211
|
-
function directoryPreview(source) {
|
|
14212
|
-
const token = source.kind === "private-directory" ? `; token ${safe(source.tokenEnv)}` : "";
|
|
14213
|
-
return `Skill directory: ${safe(source.name)} — ${safe(source.url)}${token}`;
|
|
14214
|
-
}
|
|
14215
|
-
function listPreview(label, values) {
|
|
14216
|
-
return values === void 0 ? void 0 : `${label}: ${listValues(values)}`;
|
|
14217
|
-
}
|
|
14218
|
-
function listValues(values) {
|
|
14219
|
-
return values.length === 0 ? "(none)" : values.map(safe).join(", ");
|
|
14682
|
+
/**
|
|
14683
|
+
* The question itself, short because the note above it already named the file and its contents.
|
|
14684
|
+
*
|
|
14685
|
+
* The two-tier contract rides here rather than in the summary: a preset that only carries policy
|
|
14686
|
+
* installs nothing either way, so promising a later tick would be noise in the one line the user
|
|
14687
|
+
* is certain to read.
|
|
14688
|
+
*/
|
|
14689
|
+
function trustPrompt(changed, contentSet) {
|
|
14690
|
+
if (changed) return "The preset changed since you trusted it. Trust the new contents?";
|
|
14691
|
+
return hasRepoContent(contentSet) ? "Trust it? Nothing installs until you pick it; applies to every run here until the file changes." : "Trust it? Applies to every run here until the file changes.";
|
|
14220
14692
|
}
|
|
14221
14693
|
/**
|
|
14222
14694
|
* Whether this run's prompt accepted exactly the contents the resolved layer applied.
|
|
14223
14695
|
*
|
|
14224
|
-
*
|
|
14225
|
-
*
|
|
14226
|
-
*
|
|
14696
|
+
* Boot passes one immutable snapshot through the prompt and configuration resolution. Comparing
|
|
14697
|
+
* hashes here keeps this helper correct for direct callers too: trust is recorded only for the
|
|
14698
|
+
* layer this run actually applied.
|
|
14227
14699
|
*/
|
|
14228
14700
|
function acceptedRepoPreset(configured, acceptedHash) {
|
|
14229
14701
|
const repo = configured.repoPreset;
|
|
@@ -14236,9 +14708,11 @@ function setupRepoPresetContext(configured, acceptedHash, recorded) {
|
|
|
14236
14708
|
return Object.freeze({
|
|
14237
14709
|
accepted: acceptedRepoPreset(configured, acceptedHash),
|
|
14238
14710
|
checkSummary: repo.status === "applied" ? presetCheckSummary(configured.config, "repo") : Object.freeze([]),
|
|
14711
|
+
...repo.contentSet === void 0 ? {} : { contentSet: repo.contentSet },
|
|
14239
14712
|
hash: repo.hash,
|
|
14240
14713
|
...repo.mainWorktreePath === void 0 ? {} : { mainWorktreePath: repo.mainWorktreePath },
|
|
14241
14714
|
path: repo.path,
|
|
14715
|
+
...repo.preset === void 0 ? {} : { preset: repo.preset },
|
|
14242
14716
|
recorded,
|
|
14243
14717
|
status: repo.status
|
|
14244
14718
|
});
|
|
@@ -14856,11 +15330,15 @@ async function bootSetup(request, environment) {
|
|
|
14856
15330
|
message: manifest.problem.message,
|
|
14857
15331
|
status: "invalid"
|
|
14858
15332
|
};
|
|
15333
|
+
const initialRepoPreset = await readRepoPreset(environment, void 0, { includeSkills: request.interactive });
|
|
15334
|
+
const manifestValue = manifest.status === "ready" ? manifest.value : void 0;
|
|
15335
|
+
const repoPresetState = !request.interactive && initialRepoPreset.status === "ready" && initialRepoPreset.hash !== void 0 && isRepoPresetTrusted(manifestValue, initialRepoPreset, initialRepoPreset.hash) ? await readRepoPreset(environment) : initialRepoPreset;
|
|
14859
15336
|
const trust = await establishRepoPresetTrust({
|
|
14860
15337
|
environment,
|
|
14861
15338
|
interactive: request.interactive,
|
|
14862
15339
|
io: request.io,
|
|
14863
|
-
manifest
|
|
15340
|
+
manifest,
|
|
15341
|
+
repoPresetState
|
|
14864
15342
|
});
|
|
14865
15343
|
if (trust.kind === "aborted") return { status: "aborted" };
|
|
14866
15344
|
const scanCancellation = new AbortController();
|
|
@@ -14882,7 +15360,8 @@ async function bootSetup(request, environment) {
|
|
|
14882
15360
|
manifest,
|
|
14883
15361
|
noCache: request.noCache,
|
|
14884
15362
|
online: true,
|
|
14885
|
-
registry: request.registry
|
|
15363
|
+
registry: request.registry,
|
|
15364
|
+
repoPresetState
|
|
14886
15365
|
});
|
|
14887
15366
|
if (configured.status === "invalid") {
|
|
14888
15367
|
scanCancellation.abort();
|
|
@@ -14890,7 +15369,7 @@ async function bootSetup(request, environment) {
|
|
|
14890
15369
|
}
|
|
14891
15370
|
const scan = await settleScan(request.io);
|
|
14892
15371
|
const trusted = await bootRepoPresetTrust(request, configured, trust.acceptedHash, scan);
|
|
14893
|
-
const projected = applyRequiredMcpServers(trusted.scan.model, configured.config);
|
|
15372
|
+
const projected = applyRequiredMcpServers(withRepoMcpCatalog(trusted.scan.model, configured.repoPreset), configured.config);
|
|
14894
15373
|
return {
|
|
14895
15374
|
activeChecks: enabledChecks(request.registry.checks, configured.config),
|
|
14896
15375
|
configured,
|
|
@@ -14907,8 +15386,8 @@ async function bootSetup(request, environment) {
|
|
|
14907
15386
|
};
|
|
14908
15387
|
}
|
|
14909
15388
|
/** Re-projects a rescan so the closing checklist reads the same virtual state the run planned. */
|
|
14910
|
-
function projectRescan(rescanned, config) {
|
|
14911
|
-
const projection = applyRequiredMcpServers(rescanned.model, config);
|
|
15389
|
+
function projectRescan(rescanned, config, repoPreset) {
|
|
15390
|
+
const projection = applyRequiredMcpServers(withRepoMcpCatalog(rescanned.model, repoPreset), config);
|
|
14912
15391
|
return {
|
|
14913
15392
|
...rescanned,
|
|
14914
15393
|
diagnostics: [...rescanned.diagnostics, ...projection.diagnostics],
|
|
@@ -15242,6 +15721,7 @@ function createMcpSetupCatalog(inputs) {
|
|
|
15242
15721
|
entries.push({
|
|
15243
15722
|
catalog,
|
|
15244
15723
|
key: `catalog:${catalog.id}`,
|
|
15724
|
+
...isRepoCatalogId(catalog.id) ? { repo: true } : {},
|
|
15245
15725
|
required: requiredIds.has(catalog.id),
|
|
15246
15726
|
sourceName: owner?.name
|
|
15247
15727
|
});
|
|
@@ -15253,6 +15733,7 @@ function createMcpSetupCatalog(inputs) {
|
|
|
15253
15733
|
catalog,
|
|
15254
15734
|
existing: match.server,
|
|
15255
15735
|
key: `manifest:${String(match.index)}`,
|
|
15736
|
+
...isRepoCatalogId(catalog.id) ? { repo: true } : {},
|
|
15256
15737
|
required: requiredIds.has(catalog.id),
|
|
15257
15738
|
sourceName: owner?.name
|
|
15258
15739
|
});
|
|
@@ -15265,6 +15746,7 @@ function createMcpSetupCatalog(inputs) {
|
|
|
15265
15746
|
...catalog === void 0 ? {} : { catalog },
|
|
15266
15747
|
existing: server,
|
|
15267
15748
|
key: `manifest:${String(index)}`,
|
|
15749
|
+
...server.catalogId !== void 0 && isRepoCatalogId(server.catalogId) ? { repo: true } : {},
|
|
15268
15750
|
required: server.catalogId !== void 0 && requiredIds.has(server.catalogId),
|
|
15269
15751
|
sourceName: server.catalogId === void 0 ? void 0 : inputs.registry.ownerOf("mcp-server", server.catalogId)?.name
|
|
15270
15752
|
});
|
|
@@ -15277,12 +15759,24 @@ function createMcpSetupCatalog(inputs) {
|
|
|
15277
15759
|
requiredIds
|
|
15278
15760
|
});
|
|
15279
15761
|
}
|
|
15762
|
+
/**
|
|
15763
|
+
* Required rows first (they carry blocker semantics whoever asked for them), then this
|
|
15764
|
+
* repository's own definitions, then configured rows, then everything else by name. Repo rows
|
|
15765
|
+
* lead the optional block — they are what a person running setup inside the repository came for —
|
|
15766
|
+
* without outranking a requirement they did not make.
|
|
15767
|
+
*/
|
|
15280
15768
|
function compareEntries(left, right) {
|
|
15281
15769
|
if (left.required !== right.required) return left.required ? -1 : 1;
|
|
15770
|
+
const leftRepo = left.repo === true;
|
|
15771
|
+
if (leftRepo !== (right.repo === true)) return leftRepo ? -1 : 1;
|
|
15282
15772
|
const leftConfigured = left.existing !== void 0;
|
|
15283
15773
|
if (leftConfigured !== (right.existing !== void 0)) return leftConfigured ? -1 : 1;
|
|
15284
15774
|
return entryName(left).localeCompare(entryName(right));
|
|
15285
15775
|
}
|
|
15776
|
+
/** The `repo/` namespace is reserved against plugins, so the prefix alone is provenance. */
|
|
15777
|
+
function isRepoCatalogId(id) {
|
|
15778
|
+
return id.startsWith("repo/");
|
|
15779
|
+
}
|
|
15286
15780
|
/** User-facing name used by the picker and deterministic sorting. */
|
|
15287
15781
|
function mcpCatalogEntryName(entry) {
|
|
15288
15782
|
return entryName(entry);
|
|
@@ -15365,7 +15859,7 @@ const MAX_CONCURRENT_SOURCE_LISTINGS = 4;
|
|
|
15365
15859
|
* of both. Only the draining is ordered, which is what keeps the picker's rows stable.
|
|
15366
15860
|
*/
|
|
15367
15861
|
async function loadListing(request) {
|
|
15368
|
-
const entries = [...bundledEntries(request.inputs)];
|
|
15862
|
+
const entries = [...repoEntries(request.inputs), ...bundledEntries(request.inputs)];
|
|
15369
15863
|
const notes = [...request.notes];
|
|
15370
15864
|
const unavailableSources = request.unapproved.map((source) => ({
|
|
15371
15865
|
hint: `connection not approved; token ${source.tokenEnv}`,
|
|
@@ -15485,6 +15979,30 @@ function memoizedDriverListing(request, source) {
|
|
|
15485
15979
|
request.driverListings.set(source.id, listing);
|
|
15486
15980
|
return listing;
|
|
15487
15981
|
}
|
|
15982
|
+
/** Repository skills the active allowlist admits; the source id is `repo:workspace`. */
|
|
15983
|
+
function allowedRepoSkills(inputs) {
|
|
15984
|
+
return (inputs.repoSkills ?? []).filter((skill) => isSkillSourceAllowed(inputs.preset, skill.source.id));
|
|
15985
|
+
}
|
|
15986
|
+
/**
|
|
15987
|
+
* This repository's own skills, resolved from the trusted snapshot rather than any fetch.
|
|
15988
|
+
*
|
|
15989
|
+
* `remote` stays false — no fetch is needed — but the review stage still gates their first
|
|
15990
|
+
* install by identity: trees that arrive by cloning get the same on-screen reading a directory
|
|
15991
|
+
* skill does.
|
|
15992
|
+
*/
|
|
15993
|
+
function repoEntries(inputs) {
|
|
15994
|
+
return allowedRepoSkills(inputs).map((skill) => ({
|
|
15995
|
+
description: skill.description,
|
|
15996
|
+
id: skill.id,
|
|
15997
|
+
identity: skillIdentity(skill.source.id, skill.id),
|
|
15998
|
+
name: skill.name,
|
|
15999
|
+
preview: skill.files.find((file) => file.path === "SKILL.md")?.content,
|
|
16000
|
+
remote: false,
|
|
16001
|
+
sourceId: skill.source.id,
|
|
16002
|
+
sourceName: skill.source.name,
|
|
16003
|
+
version: skill.version
|
|
16004
|
+
}));
|
|
16005
|
+
}
|
|
15488
16006
|
function bundledEntries(inputs) {
|
|
15489
16007
|
return (inputs.model.availableSkills ?? []).filter((skill) => isSkillSourceAllowed(inputs.preset, skill.source.id)).map((skill) => ({
|
|
15490
16008
|
description: skill.description,
|
|
@@ -15525,6 +16043,7 @@ function remoteEntries(listings, source) {
|
|
|
15525
16043
|
function createSkillCatalog(inputs) {
|
|
15526
16044
|
const collected = collectSkillDirectorySources(inputs.registryDirectories, inputs.preset);
|
|
15527
16045
|
const packs = /* @__PURE__ */ new Map();
|
|
16046
|
+
for (const skill of allowedRepoSkills(inputs)) packs.set(skillIdentity(skill.source.id, skill.id), skill);
|
|
15528
16047
|
const failures = /* @__PURE__ */ new Map();
|
|
15529
16048
|
const pending = /* @__PURE__ */ new Map();
|
|
15530
16049
|
const driverListings = /* @__PURE__ */ new Map();
|
|
@@ -15601,7 +16120,8 @@ function createSetupCatalogs(inputs) {
|
|
|
15601
16120
|
presetOrigin: inputs.presetOrigin,
|
|
15602
16121
|
registryDirectories: inputs.registry.skillDirectories,
|
|
15603
16122
|
registryDrivers: inputs.registry.skillSources,
|
|
15604
|
-
registryPresets: inputs.registry.presets
|
|
16123
|
+
registryPresets: inputs.registry.presets,
|
|
16124
|
+
repoSkills: inputs.repoSkills
|
|
15605
16125
|
})
|
|
15606
16126
|
};
|
|
15607
16127
|
}
|
|
@@ -15632,15 +16152,17 @@ function requiringPreset(preset, config) {
|
|
|
15632
16152
|
}
|
|
15633
16153
|
//#endregion
|
|
15634
16154
|
//#region src/setup/snippets.ts
|
|
16155
|
+
/** The picker group every repository-defined snippet renders under. */
|
|
16156
|
+
const REPO_SNIPPET_CATEGORY = "From this repository";
|
|
15635
16157
|
/** Bounds simultaneous snippet handles independently of how many contributions a distro ships. */
|
|
15636
16158
|
const MAX_CONCURRENT_SNIPPET_READS = 24;
|
|
15637
|
-
function createSnippetCatalog(snippets, manifest, presetIds = []) {
|
|
16159
|
+
function createSnippetCatalog(snippets, manifest, presetIds = [], repoSnippets = []) {
|
|
15638
16160
|
let resolved = [];
|
|
15639
16161
|
let pending;
|
|
15640
16162
|
return {
|
|
15641
16163
|
entries: () => resolved,
|
|
15642
16164
|
load: () => {
|
|
15643
|
-
pending ??= resolveSnippetCatalog(snippets, manifest, presetIds).then((entries) => {
|
|
16165
|
+
pending ??= resolveSnippetCatalog(snippets, manifest, presetIds, repoSnippets).then((entries) => {
|
|
15644
16166
|
resolved = entries;
|
|
15645
16167
|
return entries;
|
|
15646
16168
|
});
|
|
@@ -15649,13 +16171,23 @@ function createSnippetCatalog(snippets, manifest, presetIds = []) {
|
|
|
15649
16171
|
};
|
|
15650
16172
|
}
|
|
15651
16173
|
/** Resolves every registry source once and retains manifest-only selections as unavailable rows. */
|
|
15652
|
-
async function resolveSnippetCatalog(snippets, manifest, presetIds = []) {
|
|
15653
|
-
const
|
|
16174
|
+
async function resolveSnippetCatalog(snippets, manifest, presetIds = [], repoSnippets = []) {
|
|
16175
|
+
const repoEntries = repoSnippets.map((snippet) => Object.freeze({
|
|
16176
|
+
category: REPO_SNIPPET_CATEGORY,
|
|
16177
|
+
content: snippet.body,
|
|
16178
|
+
description: snippet.description ?? "Defined by this repository.",
|
|
16179
|
+
id: snippet.id,
|
|
16180
|
+
name: snippet.name,
|
|
16181
|
+
origin: "repo",
|
|
16182
|
+
status: "available"
|
|
16183
|
+
}));
|
|
16184
|
+
const registered = /* @__PURE__ */ new Set([...snippets.map((snippet) => snippet.id), ...repoEntries.map((entry) => entry.id)]);
|
|
15654
16185
|
const limit = createLimiter(MAX_CONCURRENT_SNIPPET_READS);
|
|
15655
16186
|
const resolved = await Promise.all(snippets.map((snippet) => limit(() => resolveSnippet(snippet))));
|
|
15656
16187
|
const previous = manifest.status === "ready" ? manifest.value.snippets : [];
|
|
15657
16188
|
const previousIds = new Set(previous.map((entry) => entry.id));
|
|
15658
16189
|
return Object.freeze([
|
|
16190
|
+
...repoEntries,
|
|
15659
16191
|
...resolved,
|
|
15660
16192
|
...previous.map((entry) => entry.id).filter((id) => !registered.has(id)).map((id) => Object.freeze({
|
|
15661
16193
|
category: "general",
|
|
@@ -16525,7 +17057,7 @@ async function configureEntry(context, entry, taken, io) {
|
|
|
16525
17057
|
return SETUP_BACK;
|
|
16526
17058
|
}
|
|
16527
17059
|
const defaultName = seed?.name ?? catalog?.serverName ?? "custom";
|
|
16528
|
-
const defaultScope = seed?.scope ?? "global";
|
|
17060
|
+
const defaultScope = seed?.scope ?? (entry.repo === true ? "project" : "global");
|
|
16529
17061
|
const apps = appChoices(context, catalog?.supportedApps, seed?.apps ?? []);
|
|
16530
17062
|
if (apps.eligible.length === 0 && (seed?.apps.length ?? 0) === 0) {
|
|
16531
17063
|
io.note("No detected, managed, compatible application can receive this MCP server.");
|
|
@@ -16802,15 +17334,24 @@ function requiredCatalogIds(entries) {
|
|
|
16802
17334
|
return [...new Set(entries.flatMap((entry) => entry.required && entry.catalog !== void 0 ? [entry.catalog.id] : []))];
|
|
16803
17335
|
}
|
|
16804
17336
|
function serverOption(entry) {
|
|
16805
|
-
const
|
|
16806
|
-
const tags = [...entry.required ? ["from preset", "required"] : [], ...configured ? [entry.catalog === void 0 ? "custom" : "configured"] : []];
|
|
17337
|
+
const tags = serverTags(entry);
|
|
16807
17338
|
const suffix = tags.length === 0 ? "" : ` (${tags.join(", ")})`;
|
|
16808
17339
|
return {
|
|
16809
|
-
description: entry
|
|
17340
|
+
description: serverProvenance(entry),
|
|
16810
17341
|
label: `${safe(mcpCatalogEntryName(entry))}${suffix}`,
|
|
16811
17342
|
value: entry.key
|
|
16812
17343
|
};
|
|
16813
17344
|
}
|
|
17345
|
+
/** What the run knows about a row: who selected it, whether it is required or configured. */
|
|
17346
|
+
function serverTags(entry) {
|
|
17347
|
+
const configured = entry.selectedServer !== void 0 || entry.existing !== void 0;
|
|
17348
|
+
const origin = entry.repo === true ? "from repo" : "from preset";
|
|
17349
|
+
return [...entry.required ? [origin, "required"] : entry.repo === true ? [origin] : [], ...configured ? [entry.catalog === void 0 ? "custom" : "configured"] : []];
|
|
17350
|
+
}
|
|
17351
|
+
function serverProvenance(entry) {
|
|
17352
|
+
if (entry.repo === true) return "Repository: .aura/preset.json";
|
|
17353
|
+
return entry.sourceName === void 0 ? "Custom server" : `Plugin: ${entry.sourceName}`;
|
|
17354
|
+
}
|
|
16814
17355
|
function hasManagedMcpApp(context) {
|
|
16815
17356
|
const managed = new Set(managedAppIdList(context));
|
|
16816
17357
|
return context.appCatalog.some((entry) => entry.kind === "detected" && entry.supportsMcp && managed.has(catalogEntryId(entry)));
|
|
@@ -16927,11 +17468,7 @@ function pickerOptions(inputs) {
|
|
|
16927
17468
|
const policy = inputs.catalog.policy;
|
|
16928
17469
|
const unsupported = !hasSkillsHome(inputs.managedApps);
|
|
16929
17470
|
const preset = new Set(inputs.presetSkills.map((skill) => skillIdentity(skill.source, skill.id)));
|
|
16930
|
-
const
|
|
16931
|
-
entry.sourceName,
|
|
16932
|
-
entry.name,
|
|
16933
|
-
entry.id
|
|
16934
|
-
]).map((entry) => ({
|
|
17471
|
+
const entryRow = (entry) => ({
|
|
16935
17472
|
description: `${entry.description} · v${entry.version}`,
|
|
16936
17473
|
get disabled() {
|
|
16937
17474
|
return unsupported || inputs.listing.verification?.isMissing(entry.identity) === true;
|
|
@@ -16941,10 +17478,18 @@ function pickerOptions(inputs) {
|
|
|
16941
17478
|
return inputs.listing.verification?.isMissing(entry.identity) === true ? "source no longer publishes this skill" : void 0;
|
|
16942
17479
|
},
|
|
16943
17480
|
group: entry.sourceName,
|
|
16944
|
-
label: `${entry.name}${
|
|
17481
|
+
label: `${entry.name}${selectedBySuffix(entry.identity)}`,
|
|
16945
17482
|
...previewField(inputs, entry),
|
|
16946
17483
|
value: entry.identity
|
|
16947
|
-
})
|
|
17484
|
+
});
|
|
17485
|
+
const selectedBySuffix = (identity) => inputs.repoSelectedIdentities.has(identity) ? " (from repo)" : preset.has(identity) ? " (from preset)" : "";
|
|
17486
|
+
const sortedEntries = sortForDisplay(inputs.listing.entries, (entry) => [
|
|
17487
|
+
entry.sourceName,
|
|
17488
|
+
entry.name,
|
|
17489
|
+
entry.id
|
|
17490
|
+
]);
|
|
17491
|
+
const repoEntryRows = sortedEntries.filter((entry) => entry.sourceId.startsWith("repo:")).map(entryRow);
|
|
17492
|
+
const entryRows = sortedEntries.filter((entry) => !entry.sourceId.startsWith("repo:")).map(entryRow);
|
|
16948
17493
|
const manifestRows = inputs.manifestSkills.flatMap((skill) => {
|
|
16949
17494
|
const identity = skillIdentity(skill.source, skill.id);
|
|
16950
17495
|
if (covered.has(identity)) return [];
|
|
@@ -16999,18 +17544,23 @@ function pickerOptions(inputs) {
|
|
|
16999
17544
|
value: `truncated:${source.id}`
|
|
17000
17545
|
}));
|
|
17001
17546
|
const represented = /* @__PURE__ */ new Set([...inputs.listing.entries.map((entry) => entry.identity), ...inputs.manifestSkills.map((skill) => skillIdentity(skill.source, skill.id))]);
|
|
17002
|
-
const presetRows = inputs.presetSkills.filter((skill) => !represented.has(skillIdentity(skill.source, skill.id))).map((skill) =>
|
|
17003
|
-
|
|
17004
|
-
|
|
17005
|
-
|
|
17006
|
-
|
|
17007
|
-
|
|
17008
|
-
|
|
17009
|
-
|
|
17547
|
+
const presetRows = inputs.presetSkills.filter((skill) => !represented.has(skillIdentity(skill.source, skill.id))).map((skill) => {
|
|
17548
|
+
const identity = skillIdentity(skill.source, skill.id);
|
|
17549
|
+
const fromRepo = inputs.repoSelectedIdentities.has(identity);
|
|
17550
|
+
return {
|
|
17551
|
+
description: fromRepo ? "Selected by the repository preset, but unavailable in this run." : "Selected by the active team preset, but unavailable in this run.",
|
|
17552
|
+
disabled: true,
|
|
17553
|
+
disabledNote: fromRepo ? "repo selection unavailable" : "preset selection unavailable",
|
|
17554
|
+
group: skill.source,
|
|
17555
|
+
label: `${skill.id} ${fromRepo ? "(from repo)" : "(from preset)"}`,
|
|
17556
|
+
value: identity
|
|
17557
|
+
};
|
|
17558
|
+
});
|
|
17010
17559
|
return [
|
|
17011
17560
|
...sourceRows,
|
|
17012
17561
|
...truncatedRows,
|
|
17013
17562
|
...packRows,
|
|
17563
|
+
...repoEntryRows,
|
|
17014
17564
|
...entryRows,
|
|
17015
17565
|
...manifestRows,
|
|
17016
17566
|
...presetRows
|
|
@@ -17093,6 +17643,16 @@ function skillStages(inputs) {
|
|
|
17093
17643
|
function isRemoteIdentity(identity) {
|
|
17094
17644
|
return identity.startsWith("directory:") || identity.startsWith("driver:");
|
|
17095
17645
|
}
|
|
17646
|
+
/**
|
|
17647
|
+
* A repository identity's install needs no fetch, but the review boundary all the same.
|
|
17648
|
+
*
|
|
17649
|
+
* The trees arrive by cloning, not by anything the user selected — "local files" is the bundled
|
|
17650
|
+
* exemption's mechanism, but distribution vouching was its justification, and a checkout has
|
|
17651
|
+
* neither.
|
|
17652
|
+
*/
|
|
17653
|
+
function isRepoIdentity(identity) {
|
|
17654
|
+
return identity.startsWith("repo:");
|
|
17655
|
+
}
|
|
17096
17656
|
/** The typed selection behind each offered identity: catalog entries plus manifest rows. */
|
|
17097
17657
|
function selectionsByIdentity(inputs) {
|
|
17098
17658
|
const selections = /* @__PURE__ */ new Map();
|
|
@@ -17152,7 +17712,8 @@ function reviewStage(inputs) {
|
|
|
17152
17712
|
const offered = selectionsByIdentity(inputs);
|
|
17153
17713
|
const entries = new Map(inputs.listing.entries.map((entry) => [entry.identity, entry]));
|
|
17154
17714
|
const bundled = bundledByIdentity(inputs.availableSkills);
|
|
17155
|
-
const
|
|
17715
|
+
const repo = bundledByIdentity(inputs.repoSkills);
|
|
17716
|
+
const reviewable = (identity) => needsReview(identity, recorded, bundled, repo);
|
|
17156
17717
|
return {
|
|
17157
17718
|
apply: (state, answers) => ({
|
|
17158
17719
|
...state,
|
|
@@ -17169,7 +17730,11 @@ function reviewStage(inputs) {
|
|
|
17169
17730
|
}), inputs.approvedPrivateSourceIds);
|
|
17170
17731
|
const resolutionWithBundled = {
|
|
17171
17732
|
problems: resolution.problems,
|
|
17172
|
-
resolved: new Map([
|
|
17733
|
+
resolved: new Map([
|
|
17734
|
+
...resolution.resolved,
|
|
17735
|
+
...bundled,
|
|
17736
|
+
...repo
|
|
17737
|
+
])
|
|
17173
17738
|
};
|
|
17174
17739
|
const questions = state.selected.filter(reviewable).flatMap((identity) => reviewQuestion(identity, state.decisions, resolutionWithBundled, recorded, entries.get(identity), offered.get(identity)));
|
|
17175
17740
|
return questions.length === 0 ? void 0 : questions;
|
|
@@ -17179,8 +17744,13 @@ function reviewStage(inputs) {
|
|
|
17179
17744
|
function bundledByIdentity(packs) {
|
|
17180
17745
|
return new Map(packs.map((pack) => [skillIdentity(pack.source.id, pack.id), pack]));
|
|
17181
17746
|
}
|
|
17182
|
-
function needsReview(identity, recorded, bundled) {
|
|
17747
|
+
function needsReview(identity, recorded, bundled, repo) {
|
|
17183
17748
|
if (isRemoteIdentity(identity)) return true;
|
|
17749
|
+
if (isRepoIdentity(identity)) {
|
|
17750
|
+
const previous = recorded.get(identity);
|
|
17751
|
+
const pack = repo.get(identity);
|
|
17752
|
+
return previous === void 0 || pack !== void 0 && movedFrom(previous, pack);
|
|
17753
|
+
}
|
|
17184
17754
|
const previous = recorded.get(identity);
|
|
17185
17755
|
const available = bundled.get(identity);
|
|
17186
17756
|
return previous !== void 0 && available !== void 0 && movedFrom(previous, available);
|
|
@@ -17206,11 +17776,19 @@ async function finalizeSkills(state, inputs, manifestSkills) {
|
|
|
17206
17776
|
const selection = offered.get(identity);
|
|
17207
17777
|
return selection === void 0 ? [] : [selection];
|
|
17208
17778
|
}), inputs.approvedPrivateSourceIds);
|
|
17779
|
+
const repoPacks = new Map(inputs.repoSkills.map((pack) => [skillIdentity(pack.source.id, pack.id), pack]));
|
|
17209
17780
|
const selected = [];
|
|
17210
17781
|
const resolved = [];
|
|
17211
17782
|
for (const identity of state.selected) {
|
|
17212
17783
|
const selection = offered.get(identity);
|
|
17213
17784
|
if (selection === void 0) continue;
|
|
17785
|
+
if (isRepoIdentity(identity)) {
|
|
17786
|
+
finalizeRemote(identity, selection, state, repoPacks.get(identity), recorded.get(identity), {
|
|
17787
|
+
resolved,
|
|
17788
|
+
selected
|
|
17789
|
+
});
|
|
17790
|
+
continue;
|
|
17791
|
+
}
|
|
17214
17792
|
if (!isRemoteIdentity(identity)) {
|
|
17215
17793
|
selected.push(selection);
|
|
17216
17794
|
continue;
|
|
@@ -17283,15 +17861,7 @@ async function gatherApprovedSkills(context, io, approval) {
|
|
|
17283
17861
|
const manifestSkills = recordedSkills(context);
|
|
17284
17862
|
emitNotes(context, io, listing, manifestSkills);
|
|
17285
17863
|
if (isEmptyCatalog(listing, manifestSkills)) return emptyCatalogOutcome$1(context);
|
|
17286
|
-
const inputs =
|
|
17287
|
-
approvedPrivateSourceIds,
|
|
17288
|
-
availableSkills: context.model.availableSkills ?? [],
|
|
17289
|
-
catalog: context.skillCatalog,
|
|
17290
|
-
listing,
|
|
17291
|
-
managedApps: managedSkillApps(context),
|
|
17292
|
-
manifestSkills,
|
|
17293
|
-
presetSkills: context.preset?.skills ?? []
|
|
17294
|
-
};
|
|
17864
|
+
const inputs = stageInputs(context, approvedPrivateSourceIds, listing, manifestSkills);
|
|
17295
17865
|
const opening = openingSelection(context, inputs, manifestSkills);
|
|
17296
17866
|
noteHeldBack(inputs, io, opening.heldBack);
|
|
17297
17867
|
const result = await runFormChain(skillStages(inputs), opening.state, io, {
|
|
@@ -17316,6 +17886,21 @@ function noteHeldBack(inputs, io, heldBack) {
|
|
|
17316
17886
|
function recordedSkills(context) {
|
|
17317
17887
|
return context.manifest.status === "ready" ? context.manifest.value.skills : [];
|
|
17318
17888
|
}
|
|
17889
|
+
/** Everything the picker/review chain reads, with the repository selections folded in. */
|
|
17890
|
+
function stageInputs(context, approvedPrivateSourceIds, listing, manifestSkills) {
|
|
17891
|
+
const repoSelections = context.repoPreset?.preset?.skills ?? [];
|
|
17892
|
+
return {
|
|
17893
|
+
approvedPrivateSourceIds,
|
|
17894
|
+
availableSkills: context.model.availableSkills ?? [],
|
|
17895
|
+
catalog: context.skillCatalog,
|
|
17896
|
+
listing,
|
|
17897
|
+
managedApps: managedSkillApps(context),
|
|
17898
|
+
manifestSkills,
|
|
17899
|
+
presetSkills: [...context.preset?.skills ?? [], ...repoSelections],
|
|
17900
|
+
repoSelectedIdentities: new Set(repoSelections.map((skill) => skillIdentity(skill.source, skill.id))),
|
|
17901
|
+
repoSkills: context.repoPreset?.contentSet?.skills ?? []
|
|
17902
|
+
};
|
|
17903
|
+
}
|
|
17319
17904
|
function emptyCatalogOutcome$1(context) {
|
|
17320
17905
|
return context.enteredBackward === true ? SETUP_BACK : {
|
|
17321
17906
|
selections: { ...context.selections },
|
|
@@ -17439,7 +18024,7 @@ const snippetsStep = {
|
|
|
17439
18024
|
const installed = installedSnippetIds(context);
|
|
17440
18025
|
if (context.revisited !== true) emitCatalogNotes(context, catalog, installed, io.note);
|
|
17441
18026
|
if (catalog.length === 0) return emptyCatalogOutcome(context, io);
|
|
17442
|
-
const options = snippetOptions(catalog, installed, new Set(context.preset?.snippets ?? []));
|
|
18027
|
+
const options = snippetOptions(catalog, installed, new Set(context.preset?.snippets ?? []), new Set(context.repoPreset?.preset?.snippets ?? []));
|
|
17443
18028
|
const selectable = new Set(options.filter((option) => option.disabled !== true).map((option) => option.value));
|
|
17444
18029
|
const question = {
|
|
17445
18030
|
id: "snippets",
|
|
@@ -17479,14 +18064,23 @@ const RECORD_NOTE = "Aura keeps the record; the text stays where it is.";
|
|
|
17479
18064
|
* checkbox has anything to do — the row is there to report the record, not to be answered with.
|
|
17480
18065
|
* Gathering those rows under one heading is what lets the numbers below start at 1 and count only
|
|
17481
18066
|
* what a tick would change, and it empties out any category whose every snippet is installed.
|
|
18067
|
+
*
|
|
18068
|
+
* Repository rows lead the offered rows: they are this repository's own guidance, which is what a
|
|
18069
|
+
* person running setup inside it most likely came for. Plugin categories keep their sorted order
|
|
18070
|
+
* underneath.
|
|
17482
18071
|
*/
|
|
17483
|
-
function snippetOptions(catalog, installed, preset) {
|
|
18072
|
+
function snippetOptions(catalog, installed, preset, repoSelected) {
|
|
17484
18073
|
const sorted = sortForDisplay(catalog, (entry) => [
|
|
17485
18074
|
entry.category,
|
|
17486
18075
|
entry.name,
|
|
17487
18076
|
entry.id
|
|
17488
18077
|
]);
|
|
17489
|
-
|
|
18078
|
+
const offered = sorted.filter((entry) => !installed.has(entry.id));
|
|
18079
|
+
return [
|
|
18080
|
+
...recordBlock(sorted.filter((entry) => installed.has(entry.id))),
|
|
18081
|
+
...offered.filter((entry) => entry.origin === "repo").map((entry) => offeredOption(entry, preset, repoSelected)),
|
|
18082
|
+
...offered.filter((entry) => entry.origin !== "repo").map((entry) => offeredOption(entry, preset, repoSelected))
|
|
18083
|
+
];
|
|
17490
18084
|
}
|
|
17491
18085
|
/**
|
|
17492
18086
|
* The installed rows, with the shared note appended to the last of them.
|
|
@@ -17506,10 +18100,11 @@ function recordBlock(entries) {
|
|
|
17506
18100
|
}));
|
|
17507
18101
|
}
|
|
17508
18102
|
/** A row a tick would act on, or one disabled because no installed plugin publishes its text. */
|
|
17509
|
-
function offeredOption(entry, preset) {
|
|
18103
|
+
function offeredOption(entry, preset, repoSelected) {
|
|
18104
|
+
const selectedBy = repoSelected.has(entry.id) ? " (from repo)" : preset.has(entry.id) ? " (from preset)" : "";
|
|
17510
18105
|
const base = {
|
|
17511
18106
|
group: entry.category,
|
|
17512
|
-
label: `${entry.name}${
|
|
18107
|
+
label: `${entry.name}${selectedBy}`,
|
|
17513
18108
|
value: entry.id,
|
|
17514
18109
|
...entry.status === "available" ? { preview: entry.content } : {}
|
|
17515
18110
|
};
|
|
@@ -17535,19 +18130,20 @@ function snippetsPrompt(options) {
|
|
|
17535
18130
|
return options.every((option) => option.locked === true) ? "Every snippet the installed plugins provide is already in your instructions." : "Nothing here can be added: every snippet is either installed already or unavailable.";
|
|
17536
18131
|
}
|
|
17537
18132
|
/**
|
|
17538
|
-
* What the picker opens with: every installed id, plus this run's answer or
|
|
18133
|
+
* What the picker opens with: every installed id, plus this run's answer or team-preset defaults.
|
|
17539
18134
|
*
|
|
17540
18135
|
* Installed rows are locked, so they are seeded on every visit — there is no earlier answer that
|
|
17541
18136
|
* could have dropped one. Their `✔` comes from the lock rather than from this seeding, so what it
|
|
17542
|
-
* settles is the answer the form reports back, not the checkbox the picker draws.
|
|
17543
|
-
* only what
|
|
17544
|
-
*
|
|
18137
|
+
* settles is the answer the form reports back, not the checkbox the picker draws. Team-preset
|
|
18138
|
+
* defaults add only what their installed plugins can contribute. Repository rows deliberately
|
|
18139
|
+
* open unticked even when the repository preset selects them: their bodies are arbitrary agent
|
|
18140
|
+
* instructions, so a first install requires a person to select the previewable row explicitly.
|
|
17545
18141
|
*/
|
|
17546
18142
|
function initialSelection(context, options, installed) {
|
|
17547
|
-
const
|
|
18143
|
+
const selected = new Set(context.preset?.snippets ?? []);
|
|
17548
18144
|
const chosen = context.selections.snippets;
|
|
17549
18145
|
const kept = [...installed];
|
|
17550
|
-
const added = chosen?.selected ?? options.filter((option) => option.disabled !== true && !installed.has(option.value) &&
|
|
18146
|
+
const added = chosen?.selected ?? options.filter((option) => option.disabled !== true && !installed.has(option.value) && selected.has(option.value)).map((option) => option.value);
|
|
17551
18147
|
return [...kept, ...added];
|
|
17552
18148
|
}
|
|
17553
18149
|
function emptyCatalogOutcome(context, io) {
|
|
@@ -17642,36 +18238,9 @@ async function runSetup(request) {
|
|
|
17642
18238
|
stdout.write("\nLeft everything as it was.\n");
|
|
17643
18239
|
return finish(1, "aborted");
|
|
17644
18240
|
}
|
|
17645
|
-
const { activeChecks, configured, effectiveModel, effectiveScan
|
|
17646
|
-
const model = scan.model;
|
|
18241
|
+
const { activeChecks, configured, effectiveModel, effectiveScan } = booted;
|
|
17647
18242
|
const steps = request.steps ?? SETUP_STEPS;
|
|
17648
|
-
const
|
|
17649
|
-
const catalogs = createSetupCatalogs({
|
|
17650
|
-
config: configured.config,
|
|
17651
|
-
environment,
|
|
17652
|
-
interactive: request.interactive,
|
|
17653
|
-
model: effectiveModel,
|
|
17654
|
-
noCache: request.noCache,
|
|
17655
|
-
preset: configured.preset,
|
|
17656
|
-
presetNotes: [...configured.notes, ...projected.diagnostics.map((diagnostic) => diagnostic.message)],
|
|
17657
|
-
presetOrigin: configured.presetOrigin,
|
|
17658
|
-
registry: request.registry
|
|
17659
|
-
});
|
|
17660
|
-
const preset = setupPresetContext(request, configured.config, configured.preset);
|
|
17661
|
-
const repoPreset = booted.repoPreset;
|
|
17662
|
-
const stepContext = {
|
|
17663
|
-
appCatalog: buildAppCatalog(request.registry.adapters, model, scan.skipped),
|
|
17664
|
-
...initialFindings === void 0 ? {} : { findings: initialFindings },
|
|
17665
|
-
interactive: request.interactive,
|
|
17666
|
-
isEnvironmentVariableSet: (name) => environment.readVariable(name) !== void 0,
|
|
17667
|
-
manifest: model.manifest,
|
|
17668
|
-
mcpCatalog: catalogs.mcpCatalog,
|
|
17669
|
-
model: effectiveModel,
|
|
17670
|
-
skillCatalog: catalogs.skillCatalog,
|
|
17671
|
-
snippetCatalog: createSnippetCatalog(request.registry.snippets, model.manifest, preset?.snippets ?? []),
|
|
17672
|
-
...preset === void 0 ? {} : { preset },
|
|
17673
|
-
...repoPreset === void 0 ? {} : { repoPreset }
|
|
17674
|
-
};
|
|
18243
|
+
const stepContext = buildStepContext(request, booted, environment, steps.some((step) => step.needsFindings === true) ? gatherFindings(activeChecks, effectiveModel, io, configured.config) : void 0);
|
|
17675
18244
|
let start = {
|
|
17676
18245
|
index: 0,
|
|
17677
18246
|
offered: /* @__PURE__ */ new Set(),
|
|
@@ -17701,7 +18270,7 @@ async function runSetup(request) {
|
|
|
17701
18270
|
environment,
|
|
17702
18271
|
mcpCatalog: request.registry.mcpServers,
|
|
17703
18272
|
skills: request.registry.skills
|
|
17704
|
-
}), configured.config), activeChecks, configured.config), "applied", {
|
|
18273
|
+
}), configured.config, configured.repoPreset), activeChecks, configured.config), "applied", {
|
|
17705
18274
|
actions: setupActions(ready.gathered),
|
|
17706
18275
|
appliedOperationCount: result.appliedOperationCount
|
|
17707
18276
|
});
|
|
@@ -17710,6 +18279,46 @@ async function runSetup(request) {
|
|
|
17710
18279
|
function plannedActions(gathered) {
|
|
17711
18280
|
return gathered === void 0 ? void 0 : setupActions(gathered);
|
|
17712
18281
|
}
|
|
18282
|
+
/** Assembles the catalogs and the immutable context every wizard step reads. */
|
|
18283
|
+
function buildStepContext(request, booted, environment, initialFindings) {
|
|
18284
|
+
const { configured, effectiveModel, repoPreset, scan } = booted;
|
|
18285
|
+
const model = scan.model;
|
|
18286
|
+
const catalogs = buildCatalogs(request, booted, environment);
|
|
18287
|
+
const preset = setupPresetContext(request, configured.config, configured.preset);
|
|
18288
|
+
return {
|
|
18289
|
+
appCatalog: buildAppCatalog(request.registry.adapters, model, scan.skipped),
|
|
18290
|
+
...initialFindings === void 0 ? {} : { findings: initialFindings },
|
|
18291
|
+
interactive: request.interactive,
|
|
18292
|
+
isEnvironmentVariableSet: (name) => environment.readVariable(name) !== void 0,
|
|
18293
|
+
manifest: model.manifest,
|
|
18294
|
+
mcpCatalog: catalogs.mcpCatalog,
|
|
18295
|
+
model: effectiveModel,
|
|
18296
|
+
skillCatalog: catalogs.skillCatalog,
|
|
18297
|
+
snippetCatalog: buildSnippetCatalog(request, booted, preset),
|
|
18298
|
+
...preset === void 0 ? {} : { preset },
|
|
18299
|
+
...repoPreset === void 0 ? {} : { repoPreset }
|
|
18300
|
+
};
|
|
18301
|
+
}
|
|
18302
|
+
function buildCatalogs(request, booted, environment) {
|
|
18303
|
+
const { configured, effectiveModel, projected, repoPreset } = booted;
|
|
18304
|
+
return createSetupCatalogs({
|
|
18305
|
+
config: configured.config,
|
|
18306
|
+
environment,
|
|
18307
|
+
interactive: request.interactive,
|
|
18308
|
+
model: effectiveModel,
|
|
18309
|
+
noCache: request.noCache,
|
|
18310
|
+
preset: configured.preset,
|
|
18311
|
+
presetNotes: [...configured.notes, ...projected.diagnostics.map((diagnostic) => diagnostic.message)],
|
|
18312
|
+
presetOrigin: configured.presetOrigin,
|
|
18313
|
+
registry: request.registry,
|
|
18314
|
+
repoSkills: repoPreset?.contentSet?.skills ?? []
|
|
18315
|
+
});
|
|
18316
|
+
}
|
|
18317
|
+
/** The snippet catalog, with the trusted repository's bodies and default selections folded in. */
|
|
18318
|
+
function buildSnippetCatalog(request, booted, preset) {
|
|
18319
|
+
const repoPreset = booted.repoPreset;
|
|
18320
|
+
return createSnippetCatalog(request.registry.snippets, booted.scan.model.manifest, [...preset?.snippets ?? [], ...repoPreset?.preset?.snippets ?? []], repoPreset?.contentSet?.snippets ?? []);
|
|
18321
|
+
}
|
|
17713
18322
|
function setupPresetContext(request, config, preset) {
|
|
17714
18323
|
const selected = config.preset;
|
|
17715
18324
|
if (selected === void 0 || preset === void 0) return;
|