@microsoft/teamsfx-core 3.1.3 → 3.1.4-alpha.26ce7a8dda.0
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/build/common/templates-config.json +4 -4
- package/build/component/configManager/actionInjector.d.ts +13 -3
- package/build/component/configManager/actionInjector.d.ts.map +1 -1
- package/build/component/configManager/actionInjector.js +88 -5
- package/build/component/configManager/actionInjector.js.map +1 -1
- package/build/component/driver/teamsApp/utils/utils.d.ts.map +1 -1
- package/build/component/driver/teamsApp/utils/utils.js +1 -18
- package/build/component/driver/teamsApp/utils/utils.js.map +1 -1
- package/build/component/generator/openApiSpec/helper.d.ts +2 -2
- package/build/component/generator/openApiSpec/helper.d.ts.map +1 -1
- package/build/component/generator/openApiSpec/helper.js +65 -5
- package/build/component/generator/openApiSpec/helper.js.map +1 -1
- package/build/component/generator/openPlugin/authResolver.d.ts +2 -2
- package/build/component/generator/openPlugin/authResolver.d.ts.map +1 -1
- package/build/component/generator/openPlugin/authResolver.js +17 -7
- package/build/component/generator/openPlugin/authResolver.js.map +1 -1
- package/build/component/generator/openPlugin/errors.d.ts +4 -0
- package/build/component/generator/openPlugin/errors.d.ts.map +1 -0
- package/build/component/generator/openPlugin/errors.js +13 -0
- package/build/component/generator/openPlugin/errors.js.map +1 -0
- package/build/component/generator/openPlugin/exporter.d.ts +9 -6
- package/build/component/generator/openPlugin/exporter.d.ts.map +1 -1
- package/build/component/generator/openPlugin/exporter.js +502 -53
- package/build/component/generator/openPlugin/exporter.js.map +1 -1
- package/build/component/generator/openPlugin/fileSystem.d.ts +28 -0
- package/build/component/generator/openPlugin/fileSystem.d.ts.map +1 -0
- package/build/component/generator/openPlugin/fileSystem.js +181 -0
- package/build/component/generator/openPlugin/fileSystem.js.map +1 -0
- package/build/component/generator/openPlugin/iconStrategy.d.ts.map +1 -1
- package/build/component/generator/openPlugin/iconStrategy.js +30 -14
- package/build/component/generator/openPlugin/iconStrategy.js.map +1 -1
- package/build/component/generator/openPlugin/importer.d.ts.map +1 -1
- package/build/component/generator/openPlugin/importer.js +75 -12
- package/build/component/generator/openPlugin/importer.js.map +1 -1
- package/build/component/generator/openPlugin/mapper.d.ts +3 -2
- package/build/component/generator/openPlugin/mapper.d.ts.map +1 -1
- package/build/component/generator/openPlugin/mapper.js +188 -29
- package/build/component/generator/openPlugin/mapper.js.map +1 -1
- package/build/component/generator/openPlugin/parser.d.ts.map +1 -1
- package/build/component/generator/openPlugin/parser.js +264 -70
- package/build/component/generator/openPlugin/parser.js.map +1 -1
- package/build/component/generator/openPlugin/skillValidation.d.ts +2 -0
- package/build/component/generator/openPlugin/skillValidation.d.ts.map +1 -0
- package/build/component/generator/openPlugin/skillValidation.js +63 -0
- package/build/component/generator/openPlugin/skillValidation.js.map +1 -0
- package/build/component/generator/openPlugin/spec.d.ts +67 -0
- package/build/component/generator/openPlugin/spec.d.ts.map +1 -0
- package/build/component/generator/openPlugin/spec.js +157 -0
- package/build/component/generator/openPlugin/spec.js.map +1 -0
- package/build/component/generator/openPlugin/textUtils.d.ts +1 -1
- package/build/component/generator/openPlugin/textUtils.d.ts.map +1 -1
- package/build/component/generator/openPlugin/textUtils.js +8 -7
- package/build/component/generator/openPlugin/textUtils.js.map +1 -1
- package/build/component/generator/openPlugin/types.d.ts +76 -14
- package/build/component/generator/openPlugin/types.d.ts.map +1 -1
- package/build/component/generator/openPlugin/validation.d.ts +17 -0
- package/build/component/generator/openPlugin/validation.d.ts.map +1 -0
- package/build/component/generator/openPlugin/validation.js +563 -0
- package/build/component/generator/openPlugin/validation.js.map +1 -0
- package/build/component/utils/common.d.ts +1 -9
- package/build/component/utils/common.d.ts.map +1 -1
- package/build/component/utils/common.js +7 -37
- package/build/component/utils/common.js.map +1 -1
- package/build/component/utils/envFunctionUtils.d.ts +3 -8
- package/build/component/utils/envFunctionUtils.d.ts.map +1 -1
- package/build/component/utils/envFunctionUtils.js +46 -131
- package/build/component/utils/envFunctionUtils.js.map +1 -1
- package/build/component/utils/envUtil.d.ts.map +1 -1
- package/build/component/utils/envUtil.js +7 -6
- package/build/component/utils/envUtil.js.map +1 -1
- package/build/component/utils/mcpAuthScaffolder.d.ts +6 -0
- package/build/component/utils/mcpAuthScaffolder.d.ts.map +1 -1
- package/build/component/utils/mcpAuthScaffolder.js +17 -7
- package/build/component/utils/mcpAuthScaffolder.js.map +1 -1
- package/build/core/FxCore.d.ts +3 -2
- package/build/core/FxCore.d.ts.map +1 -1
- package/build/core/FxCore.declarativeAgent.d.ts +8 -2
- package/build/core/FxCore.declarativeAgent.d.ts.map +1 -1
- package/build/core/FxCore.declarativeAgent.js +109 -6
- package/build/core/FxCore.declarativeAgent.js.map +1 -1
- package/build/core/FxCore.js +42 -3
- package/build/core/FxCore.js.map +1 -1
- package/build/core/FxCore.openPlugin.d.ts +7 -6
- package/build/core/FxCore.openPlugin.d.ts.map +1 -1
- package/build/core/FxCore.openPlugin.js +7 -6
- package/build/core/FxCore.openPlugin.js.map +1 -1
- package/build/error/common.d.ts +9 -0
- package/build/error/common.d.ts.map +1 -1
- package/build/error/common.js +40 -1
- package/build/error/common.js.map +1 -1
- package/build/question/generator.d.ts.map +1 -1
- package/build/question/generator.js +9 -9
- package/build/question/generator.js.map +1 -1
- package/build/question/inputs/AddAuthActionInputs.d.ts +6 -0
- package/build/question/inputs/AddAuthActionInputs.d.ts.map +1 -1
- package/build/question/inputs/AddPluginInputs.d.ts +15 -1
- package/build/question/inputs/AddPluginInputs.d.ts.map +1 -1
- package/build/question/options/AddAuthActionOptions.d.ts.map +1 -1
- package/build/question/options/AddAuthActionOptions.js +19 -0
- package/build/question/options/AddAuthActionOptions.js.map +1 -1
- package/build/question/options/AddPluginOptions.d.ts.map +1 -1
- package/build/question/options/AddPluginOptions.js +45 -1
- package/build/question/options/AddPluginOptions.js.map +1 -1
- package/build/question/options/ExportOpenPluginOptions.js +2 -2
- package/build/question/options/ExportOpenPluginOptions.js.map +1 -1
- package/build/question/options/ImportOpenPluginOptions.js +4 -4
- package/build/question/options/ImportOpenPluginOptions.js.map +1 -1
- package/build/question/other.d.ts +2 -0
- package/build/question/other.d.ts.map +1 -1
- package/build/question/other.js +174 -8
- package/build/question/other.js.map +1 -1
- package/build/question/questionNames.d.ts +6 -0
- package/build/question/questionNames.d.ts.map +1 -1
- package/build/question/questionNames.js +6 -0
- package/build/question/questionNames.js.map +1 -1
- package/build/question/scaffold/vsc/rootNode.js +1 -1
- package/build/question/scaffold/vsc/rootNode.js.map +1 -1
- package/build/question/scaffold/vsc/teamsProjectTypeNode.d.ts +2 -0
- package/build/question/scaffold/vsc/teamsProjectTypeNode.d.ts.map +1 -1
- package/build/question/scaffold/vsc/teamsProjectTypeNode.js +31 -1
- package/build/question/scaffold/vsc/teamsProjectTypeNode.js.map +1 -1
- package/build/ui/visitor.d.ts.map +1 -1
- package/build/ui/visitor.js +1 -0
- package/build/ui/visitor.js.map +1 -1
- package/build/v4/mcp/mcpAuthAction.d.ts +2 -1
- package/build/v4/mcp/mcpAuthAction.d.ts.map +1 -1
- package/build/v4/mcp/mcpAuthAction.js +32 -5
- package/build/v4/mcp/mcpAuthAction.js.map +1 -1
- package/build/v4/mcp/mcpAuthScaffold.d.ts +3 -0
- package/build/v4/mcp/mcpAuthScaffold.d.ts.map +1 -1
- package/build/v4/mcp/mcpAuthScaffold.js +61 -38
- package/build/v4/mcp/mcpAuthScaffold.js.map +1 -1
- package/build/v4/runtime/steps/mcpAuth.d.ts.map +1 -1
- package/build/v4/runtime/steps/mcpAuth.js +15 -4
- package/build/v4/runtime/steps/mcpAuth.js.map +1 -1
- package/package.json +37 -37
- package/resource/package.nls.json +30 -0
- package/templates/configs/local/typescript/.gitignore +1 -0
- package/templates/configs/playground/typescript/.gitignore +4 -0
- package/templates/fallback/common.zip +0 -0
- package/templates/fallback/csharp.zip +0 -0
- package/templates/fallback/js.zip +0 -0
- package/templates/fallback/python.zip +0 -0
- package/templates/fallback/ts.zip +0 -0
- package/templates/metadata.zip +0 -0
- package/templates/v4/floor.json +1 -1
- package/templates/v4/templates-metadata.zip +0 -0
- package/templates/v4/templates.zip +0 -0
|
@@ -8,57 +8,115 @@ const tslib_1 = require("tslib");
|
|
|
8
8
|
const teamsfx_api_1 = require("@microsoft/teamsfx-api");
|
|
9
9
|
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
10
10
|
const path = tslib_1.__importStar(require("path"));
|
|
11
|
+
const localizeUtils_1 = require("../../../common/localizeUtils");
|
|
12
|
+
const stringUtils_1 = require("../../../common/stringUtils");
|
|
13
|
+
const fileSystem_1 = require("./fileSystem");
|
|
14
|
+
const errors_1 = require("./errors");
|
|
15
|
+
const skillValidation_1 = require("./skillValidation");
|
|
16
|
+
const spec_1 = require("./spec");
|
|
17
|
+
const validation_1 = require("./validation");
|
|
11
18
|
exports.OPEN_PLUGIN_EXPORT_SOURCE = "OpenPluginExport";
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
const ATK_MCP_TOOL_DESCRIPTIONS_DIR = ".microsoft-agents-toolkit/mcp-tool-descriptions";
|
|
20
|
+
function localizedExportUserError(name, key, message) {
|
|
21
|
+
return new teamsfx_api_1.UserError({
|
|
22
|
+
source: exports.OPEN_PLUGIN_EXPORT_SOURCE,
|
|
23
|
+
name,
|
|
24
|
+
message: message ?? (0, localizeUtils_1.getDefaultString)(key),
|
|
25
|
+
displayMessage: (0, localizeUtils_1.getLocalizedString)(key),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
18
28
|
/**
|
|
19
29
|
* Export an ATK project (folder containing appPackage/manifest.json plus the
|
|
20
|
-
* usual agentSkills/agentConnectors layout) into an
|
|
21
|
-
* directory. The output is structured so that
|
|
22
|
-
* <output>` reconstructs an equivalent ATK
|
|
23
|
-
*
|
|
24
|
-
* `
|
|
30
|
+
* usual agentSkills/agentConnectors layout) into an Agent Plugins v1.0.0
|
|
31
|
+
* directory (https://agent-plugins.org/). The output is structured so that
|
|
32
|
+
* `atk import agentplugin --path <output>` reconstructs an equivalent ATK
|
|
33
|
+
* project; fields with no native Agent Plugins equivalent are preserved
|
|
34
|
+
* verbatim under `extensions["com.microsoft.agents-toolkit"]` in plugin.json.
|
|
35
|
+
*
|
|
36
|
+
* Output is always spec-compliant 1.0.0: plugin.json in the plugin root, and
|
|
37
|
+
* mcp.json (not .mcp.json) for MCP servers.
|
|
25
38
|
*/
|
|
26
39
|
async function exportOpenPlugin(inputs) {
|
|
27
40
|
try {
|
|
28
41
|
if (!inputs.path) {
|
|
29
|
-
return (0, teamsfx_api_1.err)(
|
|
42
|
+
return (0, teamsfx_api_1.err)(localizedExportUserError("MissingProjectPath", "core.openPluginExport.missingProjectPath"));
|
|
43
|
+
}
|
|
44
|
+
const projectRoot = await (0, fileSystem_1.resolvePluginRoot)(inputs.path);
|
|
45
|
+
const inspectedAppPackage = await (0, fileSystem_1.inspectPathWithinRoot)(projectRoot, "appPackage", "directory");
|
|
46
|
+
if (inspectedAppPackage.status === "outside" || inspectedAppPackage.status === "wrong-kind") {
|
|
47
|
+
return (0, teamsfx_api_1.err)(localizedExportUserError("InvalidProjectStructure", "core.openPluginExport.invalidProjectStructure", "appPackage must be a directory contained within the project root."));
|
|
48
|
+
}
|
|
49
|
+
if (inspectedAppPackage.status === "missing") {
|
|
50
|
+
return (0, teamsfx_api_1.err)(localizedExportUserError("ManifestNotFound", "core.openPluginExport.manifestNotFound", `appPackage/manifest.json not found under ${projectRoot}.`));
|
|
30
51
|
}
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return (0, teamsfx_api_1.err)(new teamsfx_api_1.UserError(exports.OPEN_PLUGIN_EXPORT_SOURCE, "ManifestNotFound", `appPackage/manifest.json not found under ${projectRoot}.`));
|
|
52
|
+
const appPackageDir = inspectedAppPackage.path;
|
|
53
|
+
const inspectedManifest = await (0, fileSystem_1.inspectPathWithinRoot)(projectRoot, path.relative(projectRoot, path.join(appPackageDir, "manifest.json")), "file");
|
|
54
|
+
if (inspectedManifest.status === "outside" || inspectedManifest.status === "wrong-kind") {
|
|
55
|
+
return (0, teamsfx_api_1.err)(localizedExportUserError("InvalidProjectStructure", "core.openPluginExport.invalidProjectStructure", "appPackage/manifest.json must be a regular file contained within the project root."));
|
|
36
56
|
}
|
|
57
|
+
if (inspectedManifest.status === "missing") {
|
|
58
|
+
return (0, teamsfx_api_1.err)(localizedExportUserError("ManifestNotFound", "core.openPluginExport.manifestNotFound", `appPackage/manifest.json not found under ${projectRoot}.`));
|
|
59
|
+
}
|
|
60
|
+
const manifestPath = inspectedManifest.path;
|
|
37
61
|
const manifestRaw = await fs_extra_1.default.readJSON(manifestPath);
|
|
38
|
-
if (!
|
|
39
|
-
return (0, teamsfx_api_1.err)(
|
|
62
|
+
if (!(0, validation_1.isRecord)(manifestRaw)) {
|
|
63
|
+
return (0, teamsfx_api_1.err)(localizedExportUserError("InvalidManifest", "core.openPluginExport.invalidManifest", `appPackage/manifest.json is not a JSON object: ${manifestPath}.`));
|
|
64
|
+
}
|
|
65
|
+
if (manifestRaw.version !== undefined && typeof manifestRaw.version !== "string") {
|
|
66
|
+
return (0, teamsfx_api_1.err)(localizedExportUserError("InvalidAgentPluginManifest", "core.openPluginExport.invalidAgentPluginManifest", "Cannot export an Agent Plugins 1.0.0 manifest: version must be a string."));
|
|
67
|
+
}
|
|
68
|
+
const parsedManifest = parseTeamsLikeManifest(manifestRaw);
|
|
69
|
+
if (typeof parsedManifest === "string") {
|
|
70
|
+
return (0, teamsfx_api_1.err)(localizedExportUserError("InvalidManifest", "core.openPluginExport.invalidManifest", parsedManifest));
|
|
40
71
|
}
|
|
41
|
-
const manifest =
|
|
72
|
+
const manifest = parsedManifest;
|
|
42
73
|
const warnings = [];
|
|
43
|
-
|
|
74
|
+
if (inputs.manifestKind && inputs.manifestKind !== "open-plugin") {
|
|
75
|
+
warnings.push(`--manifest-kind '${inputs.manifestKind}' is ignored. Agent Plugins 1.0.0 mandates ` +
|
|
76
|
+
`'${spec_1.PLUGIN_MANIFEST_FILE}' in the plugin root, so alternate manifest locations are no ` +
|
|
77
|
+
`longer emitted.`);
|
|
78
|
+
}
|
|
44
79
|
const pluginName = derivePluginName(manifest);
|
|
45
|
-
const defaultOutput = path.join(process.cwd(), `${pluginName}-
|
|
80
|
+
const defaultOutput = path.join(process.cwd(), `${pluginName}-agentplugin`);
|
|
46
81
|
const outputPath = path.resolve(inputs.output ?? defaultOutput);
|
|
82
|
+
if (await (0, fileSystem_1.hasLinkedPathSegment)(outputPath)) {
|
|
83
|
+
return (0, teamsfx_api_1.err)(localizedExportUserError("InvalidOutputPath", "core.openPluginExport.invalidOutputPath", `Output path must not be a symbolic link or junction: ${outputPath}.`));
|
|
84
|
+
}
|
|
85
|
+
const invalidDeveloperUrl = validateDeveloperUrls(manifest);
|
|
86
|
+
if (invalidDeveloperUrl)
|
|
87
|
+
return (0, teamsfx_api_1.err)(invalidDeveloperUrl);
|
|
88
|
+
const mcpToolDescriptions = await prepareMcpToolDescriptions(appPackageDir, manifest, warnings);
|
|
89
|
+
const commandNames = await prepareCommands(appPackageDir, warnings);
|
|
90
|
+
const preflightPluginJson = buildPluginJson(manifest, pluginName, mcpToolDescriptions);
|
|
91
|
+
try {
|
|
92
|
+
(0, validation_1.parseAgentPluginManifest)(preflightPluginJson);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (!(error instanceof errors_1.OpenPluginInputError))
|
|
96
|
+
throw error;
|
|
97
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
98
|
+
return (0, teamsfx_api_1.err)(localizedExportUserError("InvalidAgentPluginManifest", "core.openPluginExport.invalidAgentPluginManifest", `Cannot export an Agent Plugins 1.0.0 manifest: ${detail}`));
|
|
99
|
+
}
|
|
100
|
+
const invalidMcpUrl = validateRemoteMcpUrls(manifest);
|
|
101
|
+
if (invalidMcpUrl)
|
|
102
|
+
return (0, teamsfx_api_1.err)(invalidMcpUrl);
|
|
103
|
+
const destinationCollision = validateExportDestinationKeys(manifest, mcpToolDescriptions, commandNames);
|
|
104
|
+
if (destinationCollision)
|
|
105
|
+
return (0, teamsfx_api_1.err)(destinationCollision);
|
|
47
106
|
if (await fs_extra_1.default.pathExists(outputPath)) {
|
|
48
107
|
const entries = await fs_extra_1.default.readdir(outputPath);
|
|
49
108
|
if (entries.length > 0) {
|
|
50
|
-
return (0, teamsfx_api_1.err)(
|
|
109
|
+
return (0, teamsfx_api_1.err)(localizedExportUserError("OutputDirectoryNotEmpty", "core.openPluginExport.outputDirectoryNotEmpty", `Output directory is not empty: ${outputPath}. Choose a different --output path or empty the directory.`));
|
|
51
110
|
}
|
|
52
111
|
}
|
|
53
112
|
await fs_extra_1.default.ensureDir(outputPath);
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
const manifestOut = path.join(outputPath,
|
|
57
|
-
await fs_extra_1.default.ensureDir(path.dirname(manifestOut));
|
|
113
|
+
await copyMcpToolDescriptions(outputPath, mcpToolDescriptions);
|
|
114
|
+
const pluginJson = buildPluginJson(manifest, pluginName, mcpToolDescriptions);
|
|
115
|
+
const manifestOut = path.join(outputPath, spec_1.PLUGIN_MANIFEST_FILE);
|
|
58
116
|
await fs_extra_1.default.writeJSON(manifestOut, pluginJson, { spaces: 2 });
|
|
59
117
|
await writeMcpJson(outputPath, manifest, warnings);
|
|
60
118
|
await copySkills(outputPath, appPackageDir, manifest, warnings);
|
|
61
|
-
await copyCommands(outputPath, appPackageDir, warnings);
|
|
119
|
+
await copyCommands(outputPath, appPackageDir, commandNames, warnings);
|
|
62
120
|
await copyIcons(outputPath, appPackageDir, warnings);
|
|
63
121
|
return (0, teamsfx_api_1.ok)({ outputPath, warnings });
|
|
64
122
|
}
|
|
@@ -66,36 +124,236 @@ async function exportOpenPlugin(inputs) {
|
|
|
66
124
|
if (e instanceof teamsfx_api_1.UserError || e instanceof teamsfx_api_1.SystemError) {
|
|
67
125
|
return (0, teamsfx_api_1.err)(e);
|
|
68
126
|
}
|
|
127
|
+
if (e instanceof errors_1.OpenPluginInputError) {
|
|
128
|
+
return (0, teamsfx_api_1.err)(localizedExportUserError("InvalidProjectPath", "core.openPluginExport.invalidProjectPath", e.message));
|
|
129
|
+
}
|
|
69
130
|
const message = e instanceof Error ? e.message : String(e);
|
|
70
131
|
return (0, teamsfx_api_1.err)(new teamsfx_api_1.SystemError({
|
|
71
132
|
source: exports.OPEN_PLUGIN_EXPORT_SOURCE,
|
|
72
133
|
name: "ExportOpenPluginFailed",
|
|
73
134
|
message,
|
|
74
|
-
displayMessage:
|
|
135
|
+
displayMessage: (0, localizeUtils_1.getLocalizedString)("core.openPluginExport.failed"),
|
|
75
136
|
}));
|
|
76
137
|
}
|
|
77
138
|
}
|
|
139
|
+
function parseTeamsLikeManifest(value) {
|
|
140
|
+
const manifest = {};
|
|
141
|
+
const schema = readOptionalString(value, "$schema");
|
|
142
|
+
const manifestVersion = readOptionalString(value, "manifestVersion");
|
|
143
|
+
const version = readOptionalString(value, "version");
|
|
144
|
+
const id = readOptionalString(value, "id");
|
|
145
|
+
const packageName = readOptionalString(value, "packageName");
|
|
146
|
+
const accentColor = readOptionalString(value, "accentColor");
|
|
147
|
+
for (const parsed of [schema, manifestVersion, version, id, packageName, accentColor]) {
|
|
148
|
+
if (parsed && typeof parsed !== "string")
|
|
149
|
+
return parsed.message;
|
|
150
|
+
}
|
|
151
|
+
if (typeof schema === "string")
|
|
152
|
+
manifest.$schema = schema;
|
|
153
|
+
if (typeof manifestVersion === "string")
|
|
154
|
+
manifest.manifestVersion = manifestVersion;
|
|
155
|
+
if (typeof version === "string")
|
|
156
|
+
manifest.version = version;
|
|
157
|
+
if (typeof id === "string")
|
|
158
|
+
manifest.id = id;
|
|
159
|
+
if (typeof packageName === "string")
|
|
160
|
+
manifest.packageName = packageName;
|
|
161
|
+
if (typeof accentColor === "string")
|
|
162
|
+
manifest.accentColor = accentColor;
|
|
163
|
+
const developer = parseStringObject(value.developer, "developer", [
|
|
164
|
+
"name",
|
|
165
|
+
"websiteUrl",
|
|
166
|
+
"privacyUrl",
|
|
167
|
+
"termsOfUseUrl",
|
|
168
|
+
]);
|
|
169
|
+
if (typeof developer === "string")
|
|
170
|
+
return developer;
|
|
171
|
+
if (developer)
|
|
172
|
+
manifest.developer = developer;
|
|
173
|
+
const name = parseTextPair(value.name, "name");
|
|
174
|
+
if (typeof name === "string")
|
|
175
|
+
return name;
|
|
176
|
+
if (name)
|
|
177
|
+
manifest.name = name;
|
|
178
|
+
const description = parseTextPair(value.description, "description");
|
|
179
|
+
if (typeof description === "string")
|
|
180
|
+
return description;
|
|
181
|
+
if (description)
|
|
182
|
+
manifest.description = description;
|
|
183
|
+
if (value.agentSkills !== undefined) {
|
|
184
|
+
if (!Array.isArray(value.agentSkills))
|
|
185
|
+
return "manifest 'agentSkills' must be an array.";
|
|
186
|
+
const agentSkills = [];
|
|
187
|
+
for (const [index, rawSkill] of value.agentSkills.entries()) {
|
|
188
|
+
if (!(0, validation_1.isRecord)(rawSkill))
|
|
189
|
+
return `manifest 'agentSkills[${index}]' must be an object.`;
|
|
190
|
+
const folder = readOptionalString(rawSkill, "folder");
|
|
191
|
+
if (folder && typeof folder !== "string")
|
|
192
|
+
return folder.message;
|
|
193
|
+
agentSkills.push(folder ? { folder } : {});
|
|
194
|
+
}
|
|
195
|
+
manifest.agentSkills = agentSkills;
|
|
196
|
+
}
|
|
197
|
+
if (value.agentConnectors !== undefined) {
|
|
198
|
+
if (!Array.isArray(value.agentConnectors)) {
|
|
199
|
+
return "manifest 'agentConnectors' must be an array.";
|
|
200
|
+
}
|
|
201
|
+
const agentConnectors = [];
|
|
202
|
+
for (const [index, rawConnector] of value.agentConnectors.entries()) {
|
|
203
|
+
const parsedConnector = parseAgentConnector(rawConnector, index);
|
|
204
|
+
if (typeof parsedConnector === "string")
|
|
205
|
+
return parsedConnector;
|
|
206
|
+
agentConnectors.push(parsedConnector);
|
|
207
|
+
}
|
|
208
|
+
manifest.agentConnectors = agentConnectors;
|
|
209
|
+
}
|
|
210
|
+
return manifest;
|
|
211
|
+
}
|
|
212
|
+
function readOptionalString(record, field) {
|
|
213
|
+
const value = record[field];
|
|
214
|
+
if (value === undefined)
|
|
215
|
+
return undefined;
|
|
216
|
+
if (typeof value !== "string")
|
|
217
|
+
return { message: `manifest '${field}' must be a string.` };
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
function parseStringObject(value, field, fields) {
|
|
221
|
+
if (value === undefined)
|
|
222
|
+
return undefined;
|
|
223
|
+
if (!(0, validation_1.isRecord)(value))
|
|
224
|
+
return `manifest '${field}' must be an object.`;
|
|
225
|
+
const parsed = {};
|
|
226
|
+
for (const item of fields) {
|
|
227
|
+
const result = readOptionalString(value, item);
|
|
228
|
+
if (result && typeof result !== "string") {
|
|
229
|
+
return `manifest '${field}.${item}' must be a string.`;
|
|
230
|
+
}
|
|
231
|
+
if (typeof result === "string")
|
|
232
|
+
parsed[item] = result;
|
|
233
|
+
}
|
|
234
|
+
return parsed;
|
|
235
|
+
}
|
|
236
|
+
function parseTextPair(value, field) {
|
|
237
|
+
if (value === undefined)
|
|
238
|
+
return undefined;
|
|
239
|
+
if (!(0, validation_1.isRecord)(value))
|
|
240
|
+
return `manifest '${field}' must be an object.`;
|
|
241
|
+
const pair = {};
|
|
242
|
+
const short = readOptionalString(value, "short");
|
|
243
|
+
const full = readOptionalString(value, "full");
|
|
244
|
+
if (short && typeof short !== "string")
|
|
245
|
+
return `manifest '${field}.short' must be a string.`;
|
|
246
|
+
if (full && typeof full !== "string")
|
|
247
|
+
return `manifest '${field}.full' must be a string.`;
|
|
248
|
+
if (typeof short === "string")
|
|
249
|
+
pair.short = short;
|
|
250
|
+
if (typeof full === "string")
|
|
251
|
+
pair.full = full;
|
|
252
|
+
return pair;
|
|
253
|
+
}
|
|
254
|
+
function parseAgentConnector(value, index) {
|
|
255
|
+
if (!(0, validation_1.isRecord)(value))
|
|
256
|
+
return `manifest 'agentConnectors[${index}]' must be an object.`;
|
|
257
|
+
const connector = {};
|
|
258
|
+
const id = readOptionalString(value, "id");
|
|
259
|
+
const displayName = readOptionalString(value, "displayName");
|
|
260
|
+
const description = readOptionalString(value, "description");
|
|
261
|
+
for (const parsed of [id, displayName, description]) {
|
|
262
|
+
if (parsed && typeof parsed !== "string")
|
|
263
|
+
return parsed.message;
|
|
264
|
+
}
|
|
265
|
+
if (typeof id === "string")
|
|
266
|
+
connector.id = id;
|
|
267
|
+
if (value.reusable !== undefined) {
|
|
268
|
+
if (typeof value.reusable !== "boolean") {
|
|
269
|
+
return `manifest 'agentConnectors[${index}].reusable' must be a boolean.`;
|
|
270
|
+
}
|
|
271
|
+
connector.reusable = value.reusable;
|
|
272
|
+
}
|
|
273
|
+
if (typeof displayName === "string")
|
|
274
|
+
connector.displayName = displayName;
|
|
275
|
+
if (typeof description === "string")
|
|
276
|
+
connector.description = description;
|
|
277
|
+
if (value.toolSource === undefined)
|
|
278
|
+
return connector;
|
|
279
|
+
if (!(0, validation_1.isRecord)(value.toolSource))
|
|
280
|
+
return `manifest 'agentConnectors[${index}].toolSource' must be an object.`;
|
|
281
|
+
connector.toolSource = {};
|
|
282
|
+
if (value.toolSource.remoteMcpServer === undefined)
|
|
283
|
+
return connector;
|
|
284
|
+
if (!(0, validation_1.isRecord)(value.toolSource.remoteMcpServer)) {
|
|
285
|
+
return `manifest 'agentConnectors[${index}].toolSource.remoteMcpServer' must be an object.`;
|
|
286
|
+
}
|
|
287
|
+
const remote = {};
|
|
288
|
+
const serverUrl = readOptionalString(value.toolSource.remoteMcpServer, "mcpServerUrl");
|
|
289
|
+
if (serverUrl && typeof serverUrl !== "string")
|
|
290
|
+
return serverUrl.message;
|
|
291
|
+
if (typeof serverUrl === "string")
|
|
292
|
+
remote.mcpServerUrl = serverUrl;
|
|
293
|
+
const rawMcpToolDescription = value.toolSource.remoteMcpServer.mcpToolDescription;
|
|
294
|
+
if (rawMcpToolDescription !== undefined) {
|
|
295
|
+
if (!(0, validation_1.isRecord)(rawMcpToolDescription)) {
|
|
296
|
+
return `manifest 'agentConnectors[${index}].toolSource.remoteMcpServer.mcpToolDescription' must be an object.`;
|
|
297
|
+
}
|
|
298
|
+
const file = readOptionalString(rawMcpToolDescription, "file");
|
|
299
|
+
if (file && typeof file !== "string")
|
|
300
|
+
return file.message;
|
|
301
|
+
const normalizedFile = typeof file === "string" ? (0, spec_1.normalizePortableRelativePath)(file) : undefined;
|
|
302
|
+
if (typeof file === "string" && normalizedFile === undefined) {
|
|
303
|
+
return `manifest 'agentConnectors[${index}].toolSource.remoteMcpServer.mcpToolDescription.file' must be a relative path.`;
|
|
304
|
+
}
|
|
305
|
+
remote.mcpToolDescription = {};
|
|
306
|
+
if (normalizedFile !== undefined)
|
|
307
|
+
remote.mcpToolDescription.file = normalizedFile;
|
|
308
|
+
}
|
|
309
|
+
const rawAuthorization = value.toolSource.remoteMcpServer.authorization;
|
|
310
|
+
if (rawAuthorization !== undefined) {
|
|
311
|
+
if (!(0, validation_1.isRecord)(rawAuthorization)) {
|
|
312
|
+
return `manifest 'agentConnectors[${index}].toolSource.remoteMcpServer.authorization' must be an object.`;
|
|
313
|
+
}
|
|
314
|
+
const type = readOptionalString(rawAuthorization, "type");
|
|
315
|
+
const referenceId = readOptionalString(rawAuthorization, "referenceId");
|
|
316
|
+
if (type && typeof type !== "string")
|
|
317
|
+
return type.message;
|
|
318
|
+
if (referenceId && typeof referenceId !== "string")
|
|
319
|
+
return referenceId.message;
|
|
320
|
+
remote.authorization = {};
|
|
321
|
+
if (typeof type === "string")
|
|
322
|
+
remote.authorization.type = type;
|
|
323
|
+
if (typeof referenceId === "string")
|
|
324
|
+
remote.authorization.referenceId = referenceId;
|
|
325
|
+
}
|
|
326
|
+
connector.toolSource.remoteMcpServer = remote;
|
|
327
|
+
return connector;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Derive a plugin name that satisfies the Agent Plugins 1.0.0 `name`
|
|
331
|
+
* constraint (1-64 chars, `^(?!.*(?:--|\.\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$`).
|
|
332
|
+
*/
|
|
78
333
|
function derivePluginName(manifest) {
|
|
79
334
|
const short = manifest.name?.short?.trim();
|
|
80
335
|
const full = manifest.name?.full?.trim();
|
|
81
|
-
const fromName =
|
|
82
|
-
const slug =
|
|
336
|
+
const fromName = short ?? full ?? "";
|
|
337
|
+
const slug = (0, spec_1.normalizePluginName)(fromName, "");
|
|
83
338
|
if (slug)
|
|
84
339
|
return slug;
|
|
85
340
|
if (manifest.packageName) {
|
|
86
341
|
const last = manifest.packageName.split(".").pop();
|
|
87
|
-
|
|
88
|
-
|
|
342
|
+
const fromPackage = (0, spec_1.normalizePluginName)(last ?? "", "");
|
|
343
|
+
if (fromPackage)
|
|
344
|
+
return fromPackage;
|
|
89
345
|
}
|
|
90
346
|
return "exported-plugin";
|
|
91
347
|
}
|
|
92
|
-
function buildPluginJson(manifest, pluginName) {
|
|
348
|
+
function buildPluginJson(manifest, pluginName, mcpToolDescriptions) {
|
|
93
349
|
const author = {};
|
|
94
350
|
if (manifest.developer?.name)
|
|
95
351
|
author.name = manifest.developer.name;
|
|
96
352
|
if (manifest.developer?.websiteUrl)
|
|
97
353
|
author.url = manifest.developer.websiteUrl;
|
|
354
|
+
// Key order mirrors the published schema: $schema and name first.
|
|
98
355
|
const pluginJson = {
|
|
356
|
+
$schema: spec_1.PLUGIN_SCHEMA_URL,
|
|
99
357
|
name: pluginName,
|
|
100
358
|
version: manifest.version ?? "1.0.0",
|
|
101
359
|
description: manifest.description?.full ?? manifest.description?.short ?? pluginName,
|
|
@@ -151,6 +409,12 @@ function buildPluginJson(manifest, pluginName) {
|
|
|
151
409
|
override.displayName = connector.displayName;
|
|
152
410
|
if (connector.description)
|
|
153
411
|
override.description = connector.description;
|
|
412
|
+
if (connector.reusable !== undefined)
|
|
413
|
+
override.reusable = connector.reusable;
|
|
414
|
+
const mcpToolDescription = mcpToolDescriptions.get(connector.id);
|
|
415
|
+
if (mcpToolDescription) {
|
|
416
|
+
override.mcpToolDescription = { ...mcpToolDescription.extension };
|
|
417
|
+
}
|
|
154
418
|
const auth = connector.toolSource?.remoteMcpServer?.authorization;
|
|
155
419
|
if (auth?.type && isAuthorizationType(auth.type)) {
|
|
156
420
|
override.authorization = { type: auth.type };
|
|
@@ -158,19 +422,138 @@ function buildPluginJson(manifest, pluginName) {
|
|
|
158
422
|
override.authorization.referenceId = auth.referenceId;
|
|
159
423
|
}
|
|
160
424
|
if (Object.keys(override).length > 0) {
|
|
161
|
-
connectorOverrides
|
|
425
|
+
(0, spec_1.setRecordValue)(connectorOverrides, connector.id, override);
|
|
162
426
|
}
|
|
163
427
|
}
|
|
164
428
|
if (Object.keys(connectorOverrides).length > 0) {
|
|
165
429
|
extension.agentConnectors = connectorOverrides;
|
|
166
430
|
}
|
|
431
|
+
// Agent Plugins 1.0.0 closes the manifest schema (additionalProperties:
|
|
432
|
+
// false), so client data must be namespaced under `extensions` rather than
|
|
433
|
+
// written to a top-level `x-...` key.
|
|
167
434
|
if (Object.keys(extension).length > 0) {
|
|
168
|
-
pluginJson[
|
|
435
|
+
pluginJson.extensions = { [spec_1.ATK_EXTENSION_NAMESPACE]: extension };
|
|
169
436
|
}
|
|
170
437
|
return pluginJson;
|
|
171
438
|
}
|
|
439
|
+
async function prepareMcpToolDescriptions(appPackageDir, manifest, warnings) {
|
|
440
|
+
const prepared = new Map();
|
|
441
|
+
for (const [index, connector] of (manifest.agentConnectors ?? []).entries()) {
|
|
442
|
+
const id = connector.id;
|
|
443
|
+
const description = connector.toolSource?.remoteMcpServer?.mcpToolDescription;
|
|
444
|
+
if (!id || description === undefined)
|
|
445
|
+
continue;
|
|
446
|
+
if (description.file === undefined) {
|
|
447
|
+
prepared.set(id, { extension: {} });
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
const sourceFile = await (0, fileSystem_1.readFileWithinRoot)(appPackageDir, description.file);
|
|
451
|
+
if (sourceFile.status !== "ok") {
|
|
452
|
+
warnings.push(`Connector '${id}' MCP tool-description file '${description.file}' is not a safe regular file and was skipped.`);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
const source = path.posix.join(ATK_MCP_TOOL_DESCRIPTIONS_DIR, `${index}.json`);
|
|
456
|
+
prepared.set(id, {
|
|
457
|
+
extension: { file: description.file, source },
|
|
458
|
+
contents: sourceFile.contents,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
return prepared;
|
|
462
|
+
}
|
|
463
|
+
async function copyMcpToolDescriptions(outputPath, prepared) {
|
|
464
|
+
for (const description of prepared.values()) {
|
|
465
|
+
if (!description.contents || !description.extension.source)
|
|
466
|
+
continue;
|
|
467
|
+
const destination = path.join(outputPath, description.extension.source);
|
|
468
|
+
await fs_extra_1.default.ensureDir(path.dirname(destination));
|
|
469
|
+
await fs_extra_1.default.writeFile(destination, description.contents);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
172
472
|
function isAuthorizationType(value) {
|
|
173
|
-
return value === "None" ||
|
|
473
|
+
return (value === "None" ||
|
|
474
|
+
value === "OAuthPluginVault" ||
|
|
475
|
+
value === "ApiKeyPluginVault" ||
|
|
476
|
+
value === "DynamicClientRegistration" ||
|
|
477
|
+
value === "AzureKeyVault");
|
|
478
|
+
}
|
|
479
|
+
function validateExportDestinationKeys(manifest, mcpToolDescriptions, commandNames) {
|
|
480
|
+
const connectorIds = new Set();
|
|
481
|
+
for (const connector of manifest.agentConnectors ?? []) {
|
|
482
|
+
if (!connector.id)
|
|
483
|
+
continue;
|
|
484
|
+
if (connectorIds.has(connector.id)) {
|
|
485
|
+
return localizedExportUserError("InvalidManifest", "core.openPluginExport.invalidManifest", `Duplicate connector id '${connector.id}' cannot be exported.`);
|
|
486
|
+
}
|
|
487
|
+
connectorIds.add(connector.id);
|
|
488
|
+
}
|
|
489
|
+
const skillNames = new Set();
|
|
490
|
+
for (const skill of manifest.agentSkills ?? []) {
|
|
491
|
+
if (!skill.folder)
|
|
492
|
+
continue;
|
|
493
|
+
const name = path.basename(skill.folder.replace(/^\.\//, "")).toLowerCase();
|
|
494
|
+
if (skillNames.has(name)) {
|
|
495
|
+
return localizedExportUserError("InvalidManifest", "core.openPluginExport.invalidManifest", `Multiple Agent Skills resolve to the export destination '${name}'.`);
|
|
496
|
+
}
|
|
497
|
+
skillNames.add(name);
|
|
498
|
+
}
|
|
499
|
+
const reservedPaths = [
|
|
500
|
+
"manifest.json",
|
|
501
|
+
"color.png",
|
|
502
|
+
"outline.png",
|
|
503
|
+
"skills",
|
|
504
|
+
...[...skillNames].map((name) => path.posix.join("skills", name)),
|
|
505
|
+
...commandNames.map((name) => path.posix.join("commands", name)),
|
|
506
|
+
];
|
|
507
|
+
const destinations = new Map();
|
|
508
|
+
for (const description of mcpToolDescriptions.values()) {
|
|
509
|
+
const file = description.extension.file;
|
|
510
|
+
if (!file || !description.contents)
|
|
511
|
+
continue;
|
|
512
|
+
const reservedPath = reservedPaths.find((candidate) => (0, spec_1.portablePathsConflict)(file, candidate));
|
|
513
|
+
if (reservedPath) {
|
|
514
|
+
return localizedExportUserError("InvalidManifest", "core.openPluginExport.invalidManifest", `MCP tool-description path '${file}' collides with generated output '${reservedPath}'.`);
|
|
515
|
+
}
|
|
516
|
+
const destinationKey = file.toLowerCase();
|
|
517
|
+
const existing = destinations.get(destinationKey);
|
|
518
|
+
if (existing) {
|
|
519
|
+
if (existing.path !== file || !existing.contents.equals(description.contents)) {
|
|
520
|
+
return localizedExportUserError("InvalidManifest", "core.openPluginExport.invalidManifest", `MCP tool-description paths '${existing.path}' and '${file}' collide.`);
|
|
521
|
+
}
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
for (const destination of destinations.values()) {
|
|
525
|
+
if ((0, spec_1.portablePathsConflict)(file, destination.path)) {
|
|
526
|
+
return localizedExportUserError("InvalidManifest", "core.openPluginExport.invalidManifest", `MCP tool-description paths '${destination.path}' and '${file}' collide.`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
destinations.set(destinationKey, { path: file, contents: description.contents });
|
|
530
|
+
}
|
|
531
|
+
return undefined;
|
|
532
|
+
}
|
|
533
|
+
function validateRemoteMcpUrls(manifest) {
|
|
534
|
+
for (const connector of manifest.agentConnectors ?? []) {
|
|
535
|
+
const url = connector.toolSource?.remoteMcpServer?.mcpServerUrl;
|
|
536
|
+
if (url === undefined)
|
|
537
|
+
continue;
|
|
538
|
+
const validationError = (0, validation_1.getRemoteMcpUrlError)(url);
|
|
539
|
+
if (validationError) {
|
|
540
|
+
return localizedExportUserError("InvalidMcpServerUrl", "core.openPluginExport.invalidMcpServerUrl", `Connector '${connector.id ?? "(unnamed)"}' cannot be exported: ${validationError}`);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return undefined;
|
|
544
|
+
}
|
|
545
|
+
function validateDeveloperUrls(manifest) {
|
|
546
|
+
const urls = [
|
|
547
|
+
["websiteUrl", manifest.developer?.websiteUrl],
|
|
548
|
+
["privacyUrl", manifest.developer?.privacyUrl],
|
|
549
|
+
["termsOfUseUrl", manifest.developer?.termsOfUseUrl],
|
|
550
|
+
];
|
|
551
|
+
for (const [field, value] of urls) {
|
|
552
|
+
if (value !== undefined && !(0, stringUtils_1.isValidHttpUrl)(value)) {
|
|
553
|
+
return localizedExportUserError("InvalidManifest", "core.openPluginExport.invalidManifest", `Developer '${field}' must be a valid HTTP(S) URL.`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return undefined;
|
|
174
557
|
}
|
|
175
558
|
async function writeMcpJson(outputPath, manifest, warnings) {
|
|
176
559
|
const servers = {};
|
|
@@ -181,12 +564,14 @@ async function writeMcpJson(outputPath, manifest, warnings) {
|
|
|
181
564
|
warnings.push(`Skipping connector '${connector.id ?? "(unnamed)"}' during export: only remoteMcpServer connectors with a URL are supported.`);
|
|
182
565
|
continue;
|
|
183
566
|
}
|
|
184
|
-
|
|
567
|
+
// "http" is not an Agent Plugins transport; 1.0.0 defines stdio,
|
|
568
|
+
// streamable-http and (legacy) sse.
|
|
569
|
+
(0, spec_1.setRecordValue)(servers, id, { type: spec_1.DEFAULT_REMOTE_MCP_TYPE, url: remote.mcpServerUrl });
|
|
185
570
|
}
|
|
186
571
|
if (Object.keys(servers).length === 0) {
|
|
187
572
|
return;
|
|
188
573
|
}
|
|
189
|
-
await fs_extra_1.default.writeJSON(path.join(outputPath,
|
|
574
|
+
await fs_extra_1.default.writeJSON(path.join(outputPath, spec_1.MCP_CONFIG_FILE), { $schema: spec_1.MCP_SCHEMA_URL, mcpServers: servers }, { spaces: 2 });
|
|
190
575
|
}
|
|
191
576
|
async function copySkills(outputPath, appPackageDir, manifest, warnings) {
|
|
192
577
|
const skillsRefs = manifest.agentSkills ?? [];
|
|
@@ -195,39 +580,103 @@ async function copySkills(outputPath, appPackageDir, manifest, warnings) {
|
|
|
195
580
|
}
|
|
196
581
|
const destRoot = path.join(outputPath, "skills");
|
|
197
582
|
await fs_extra_1.default.ensureDir(destRoot);
|
|
583
|
+
const trustedAppPackageDir = appPackageDir;
|
|
198
584
|
for (const skill of skillsRefs) {
|
|
199
585
|
if (!skill.folder)
|
|
200
586
|
continue;
|
|
201
587
|
const rel = skill.folder.replace(/^\.\//, "");
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
if (relativeToPackage.startsWith("..") || path.isAbsolute(relativeToPackage)) {
|
|
588
|
+
const inspectedSkill = await (0, fileSystem_1.inspectPathWithinRoot)(trustedAppPackageDir, rel, "directory");
|
|
589
|
+
if (inspectedSkill.status === "outside") {
|
|
205
590
|
warnings.push(`Skill folder '${skill.folder}' resolves outside appPackage; skipped.`);
|
|
206
591
|
continue;
|
|
207
592
|
}
|
|
208
|
-
if (
|
|
593
|
+
if (inspectedSkill.status === "missing") {
|
|
209
594
|
warnings.push(`Skill folder referenced by manifest not found on disk: ${skill.folder}`);
|
|
210
595
|
continue;
|
|
211
596
|
}
|
|
597
|
+
if (inspectedSkill.status === "wrong-kind") {
|
|
598
|
+
warnings.push(`Skill folder '${skill.folder}' is not a directory; skipped.`);
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
212
601
|
const name = path.basename(rel);
|
|
213
|
-
await
|
|
602
|
+
const skillMd = await (0, fileSystem_1.inspectPathWithinRoot)(trustedAppPackageDir, path.relative(trustedAppPackageDir, path.join(inspectedSkill.path, "SKILL.md")), "file");
|
|
603
|
+
if (skillMd.status !== "ok") {
|
|
604
|
+
warnings.push(`Skill folder '${skill.folder}' does not contain a safe SKILL.md; skipped.`);
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
const validationError = await (0, skillValidation_1.getAgentSkillValidationError)(name, skillMd.path);
|
|
608
|
+
if (validationError) {
|
|
609
|
+
warnings.push(`Skill folder '${skill.folder}' is invalid: ${validationError}; skipped.`);
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
await (0, fileSystem_1.copyDirectoryWithoutSymbolicLinks)(inspectedSkill.path, path.join(destRoot, name), (relativePath, resolvesOutside) => {
|
|
613
|
+
warnings.push(`Skipped symbolic link '${relativePath}' while exporting skill '${name}'${resolvesOutside ? " because it resolves outside the skill root" : ""}.`);
|
|
614
|
+
});
|
|
214
615
|
}
|
|
215
616
|
}
|
|
216
|
-
async function
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
617
|
+
async function prepareCommands(appPackageDir, warnings) {
|
|
618
|
+
const trustedAppPackageDir = appPackageDir;
|
|
619
|
+
const inspectedCommands = await (0, fileSystem_1.inspectPathWithinRoot)(trustedAppPackageDir, "commands", "directory");
|
|
620
|
+
if (inspectedCommands.status === "outside") {
|
|
621
|
+
warnings.push("Commands directory resolves outside appPackage; skipped.");
|
|
622
|
+
return [];
|
|
623
|
+
}
|
|
624
|
+
if (inspectedCommands.status === "wrong-kind") {
|
|
625
|
+
warnings.push("Commands path is not a directory; skipped.");
|
|
626
|
+
return [];
|
|
627
|
+
}
|
|
628
|
+
if (inspectedCommands.status === "missing")
|
|
629
|
+
return [];
|
|
630
|
+
const commandNames = [];
|
|
631
|
+
const entries = await fs_extra_1.default.readdir(inspectedCommands.path, { withFileTypes: true });
|
|
632
|
+
for (const entry of entries) {
|
|
633
|
+
if (entry.isSymbolicLink()) {
|
|
634
|
+
const linkedCommand = await (0, fileSystem_1.inspectPathWithinRoot)(trustedAppPackageDir, path.relative(trustedAppPackageDir, path.join(inspectedCommands.path, entry.name)), "file");
|
|
635
|
+
warnings.push(linkedCommand.status === "outside"
|
|
636
|
+
? `Command '${entry.name}' resolves outside appPackage; skipped.`
|
|
637
|
+
: `Command '${entry.name}' is a symbolic link; skipped.`);
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
if (!entry.name.toLowerCase().endsWith(".md"))
|
|
641
|
+
continue;
|
|
642
|
+
const command = await (0, fileSystem_1.inspectPathWithinRoot)(trustedAppPackageDir, path.relative(trustedAppPackageDir, path.join(inspectedCommands.path, entry.name)), "file");
|
|
643
|
+
if (command.status === "outside") {
|
|
644
|
+
warnings.push(`Command '${entry.name}' resolves outside appPackage; skipped.`);
|
|
645
|
+
}
|
|
646
|
+
else if (command.status === "ok") {
|
|
647
|
+
commandNames.push(entry.name);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return commandNames;
|
|
651
|
+
}
|
|
652
|
+
async function copyCommands(outputPath, appPackageDir, commandNames, warnings) {
|
|
653
|
+
for (const commandName of commandNames) {
|
|
654
|
+
const command = await (0, fileSystem_1.inspectPathWithinRoot)(appPackageDir, path.join("commands", commandName), "file");
|
|
655
|
+
if (command.status !== "ok") {
|
|
656
|
+
warnings.push(`Command '${commandName}' is no longer a safe regular file; skipped.`);
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
const destination = path.join(outputPath, "commands", commandName);
|
|
660
|
+
await fs_extra_1.default.ensureDir(path.dirname(destination));
|
|
661
|
+
await fs_extra_1.default.copy(command.path, destination);
|
|
662
|
+
}
|
|
221
663
|
}
|
|
222
664
|
async function copyIcons(outputPath, appPackageDir, warnings) {
|
|
665
|
+
const trustedAppPackageDir = appPackageDir;
|
|
223
666
|
for (const icon of ["color.png", "outline.png"]) {
|
|
224
|
-
const
|
|
225
|
-
if (
|
|
226
|
-
await fs_extra_1.default.copy(
|
|
667
|
+
const inspectedIcon = await (0, fileSystem_1.inspectPathWithinRoot)(trustedAppPackageDir, icon, "file");
|
|
668
|
+
if (inspectedIcon.status === "ok") {
|
|
669
|
+
await fs_extra_1.default.copy(inspectedIcon.path, path.join(outputPath, icon));
|
|
227
670
|
}
|
|
228
|
-
else {
|
|
671
|
+
else if (inspectedIcon.status === "missing") {
|
|
229
672
|
warnings.push(`Icon file not found in appPackage: ${icon}`);
|
|
230
673
|
}
|
|
674
|
+
else if (inspectedIcon.status === "outside") {
|
|
675
|
+
warnings.push(`Icon file '${icon}' resolves outside appPackage; skipped.`);
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
warnings.push(`Icon path '${icon}' is not a regular file; skipped.`);
|
|
679
|
+
}
|
|
231
680
|
}
|
|
232
681
|
}
|
|
233
682
|
//# sourceMappingURL=exporter.js.map
|