@jay-framework/jay-stack-cli 0.21.0 → 0.22.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/agent-kit-template/designer/cli-commands.md +11 -13
- package/agent-kit-template/designer/routing.md +19 -7
- package/agent-kit-template/developer/cli-commands.md +17 -11
- package/agent-kit-template/developer/routing.md +19 -7
- package/agent-kit-template/devops/INSTRUCTIONS.md +50 -0
- package/agent-kit-template/plugin/INSTRUCTIONS.md +4 -4
- package/agent-kit-template/plugin/plugin-structure.md +9 -12
- package/agent-kit-template/plugin/setup-guide.md +171 -58
- package/dist/index.js +952 -73
- package/package.json +12 -11
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import YAML from "yaml";
|
|
|
10
10
|
import { getLogger, setDevLogger, createDevLogger } from "@jay-framework/logger";
|
|
11
11
|
import { parseJayFile, JAY_IMPORT_RESOLVER, generateElementDefinitionFile, ContractTagType, parseContract, generateElementFile, generateServerElementFile, htmlElementTagNameMap, loadLinkedContract, getLinkedContractDir } from "@jay-framework/compiler-jay-html";
|
|
12
12
|
import { JAY_CONTRACT_EXTENSION, JAY_EXTENSION, resolvePluginManifest, LOCAL_PLUGIN_PATH, JayAtomicType, JayEnumType, loadPluginManifest, RuntimeMode, GenerateTarget, findDynamicContract } from "@jay-framework/compiler-shared";
|
|
13
|
-
import { scanPlugins as scanPlugins$1, listContracts, materializeContracts } from "@jay-framework/stack-server-runtime";
|
|
13
|
+
import { scanPlugins as scanPlugins$1, listContracts, materializeContracts, SetupNeedsAnswerError } from "@jay-framework/stack-server-runtime";
|
|
14
14
|
import { listContracts as listContracts2, materializeContracts as materializeContracts2 } from "@jay-framework/stack-server-runtime";
|
|
15
15
|
import { Command } from "commander";
|
|
16
16
|
import chalk from "chalk";
|
|
@@ -18,9 +18,9 @@ import path$1 from "node:path";
|
|
|
18
18
|
import fs$1 from "node:fs/promises";
|
|
19
19
|
import { createRequire } from "module";
|
|
20
20
|
import { glob } from "glob";
|
|
21
|
-
import { parse } from "node-html-parser";
|
|
22
21
|
import fsSync from "node:fs";
|
|
23
22
|
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { input, confirm, select } from "@inquirer/prompts";
|
|
24
24
|
const DEFAULT_CONFIG = {
|
|
25
25
|
devServer: {
|
|
26
26
|
portRange: [3e3, 3100],
|
|
@@ -3022,6 +3022,769 @@ function checkParamsConsistency(paramsTypeNames, localInterfaces, contractImport
|
|
|
3022
3022
|
}
|
|
3023
3023
|
}
|
|
3024
3024
|
}
|
|
3025
|
+
const REJECTED_ITEM_FIELDS = ["kind", "parameters", "component", "allowedScopes"];
|
|
3026
|
+
const HTML_SOFT_LIMIT_BYTES = 8 * 1024;
|
|
3027
|
+
const HTML_HARD_LIMIT_BYTES = 32 * 1024;
|
|
3028
|
+
const FOLDER_PATH_MAX_SEGMENTS = 32;
|
|
3029
|
+
const BLOCKED_TAGS = /<\s*(script|iframe|object|embed)\b[^>]*>[\s\S]*?<\/\s*\1\s*>|<\s*(script|iframe|object|embed)\b[^>]*\/?>/gi;
|
|
3030
|
+
const EVENT_HANDLER_ATTR = /\s+on[a-z]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi;
|
|
3031
|
+
const JAVASCRIPT_URL = /\b(href|src|xlink:href)\s*=\s*("|')\s*javascript:/gi;
|
|
3032
|
+
function isRecord(value) {
|
|
3033
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3034
|
+
}
|
|
3035
|
+
function byteLengthUtf8(value) {
|
|
3036
|
+
return new TextEncoder().encode(value).length;
|
|
3037
|
+
}
|
|
3038
|
+
function detectUnsafeAddMenuHtmlFragment(html) {
|
|
3039
|
+
if (BLOCKED_TAGS.test(html)) {
|
|
3040
|
+
BLOCKED_TAGS.lastIndex = 0;
|
|
3041
|
+
return {
|
|
3042
|
+
code: "html-fragment-unsafe-markup",
|
|
3043
|
+
message: "html-fragment must not include script, iframe, object, or embed"
|
|
3044
|
+
};
|
|
3045
|
+
}
|
|
3046
|
+
BLOCKED_TAGS.lastIndex = 0;
|
|
3047
|
+
if (EVENT_HANDLER_ATTR.test(html)) {
|
|
3048
|
+
EVENT_HANDLER_ATTR.lastIndex = 0;
|
|
3049
|
+
return {
|
|
3050
|
+
code: "html-fragment-unsafe-markup",
|
|
3051
|
+
message: "html-fragment must not include inline event handler attributes"
|
|
3052
|
+
};
|
|
3053
|
+
}
|
|
3054
|
+
EVENT_HANDLER_ATTR.lastIndex = 0;
|
|
3055
|
+
if (JAVASCRIPT_URL.test(html)) {
|
|
3056
|
+
JAVASCRIPT_URL.lastIndex = 0;
|
|
3057
|
+
return {
|
|
3058
|
+
code: "html-fragment-unsafe-markup",
|
|
3059
|
+
message: "html-fragment must not include javascript: URLs"
|
|
3060
|
+
};
|
|
3061
|
+
}
|
|
3062
|
+
JAVASCRIPT_URL.lastIndex = 0;
|
|
3063
|
+
return null;
|
|
3064
|
+
}
|
|
3065
|
+
function sanitizeAddMenuHtmlFragment(html) {
|
|
3066
|
+
let result = html;
|
|
3067
|
+
result = result.replace(BLOCKED_TAGS, "");
|
|
3068
|
+
BLOCKED_TAGS.lastIndex = 0;
|
|
3069
|
+
result = result.replace(EVENT_HANDLER_ATTR, "");
|
|
3070
|
+
EVENT_HANDLER_ATTR.lastIndex = 0;
|
|
3071
|
+
result = result.replace(JAVASCRIPT_URL, "");
|
|
3072
|
+
JAVASCRIPT_URL.lastIndex = 0;
|
|
3073
|
+
return result.trim();
|
|
3074
|
+
}
|
|
3075
|
+
function catalogWarning(code, message, itemId, sourcePath) {
|
|
3076
|
+
return { code, message, ...itemId ? { itemId } : {}, ...sourcePath ? { sourcePath } : {} };
|
|
3077
|
+
}
|
|
3078
|
+
function requiredString(obj, field, itemPath, errors, code) {
|
|
3079
|
+
const value = obj[field];
|
|
3080
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
3081
|
+
errors.push({
|
|
3082
|
+
path: `${itemPath}.${field}`,
|
|
3083
|
+
message: "required non-empty string",
|
|
3084
|
+
...code ? { code } : {}
|
|
3085
|
+
});
|
|
3086
|
+
return null;
|
|
3087
|
+
}
|
|
3088
|
+
return value.trim();
|
|
3089
|
+
}
|
|
3090
|
+
function optionalString(obj, field) {
|
|
3091
|
+
const value = obj[field];
|
|
3092
|
+
if (value === void 0)
|
|
3093
|
+
return void 0;
|
|
3094
|
+
if (typeof value !== "string")
|
|
3095
|
+
return void 0;
|
|
3096
|
+
const trimmed = value.trim();
|
|
3097
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
3098
|
+
}
|
|
3099
|
+
function validateInteraction(raw, itemPath, errors) {
|
|
3100
|
+
if (raw === void 0)
|
|
3101
|
+
return void 0;
|
|
3102
|
+
if (!isRecord(raw)) {
|
|
3103
|
+
errors.push({
|
|
3104
|
+
path: itemPath,
|
|
3105
|
+
message: "interaction must be an object",
|
|
3106
|
+
code: "interaction-not-object"
|
|
3107
|
+
});
|
|
3108
|
+
return void 0;
|
|
3109
|
+
}
|
|
3110
|
+
const mode = raw.mode;
|
|
3111
|
+
if (mode !== "reference" && mode !== "stage-place") {
|
|
3112
|
+
errors.push({
|
|
3113
|
+
path: `${itemPath}.mode`,
|
|
3114
|
+
message: 'interaction.mode must be "reference" or "stage-place"',
|
|
3115
|
+
code: "interaction-invalid-mode"
|
|
3116
|
+
});
|
|
3117
|
+
return void 0;
|
|
3118
|
+
}
|
|
3119
|
+
return {
|
|
3120
|
+
mode,
|
|
3121
|
+
stagePromptTemplate: optionalString(raw, "stagePromptTemplate")
|
|
3122
|
+
};
|
|
3123
|
+
}
|
|
3124
|
+
function validatePresentation(raw, itemPath, errors) {
|
|
3125
|
+
if (raw === void 0)
|
|
3126
|
+
return void 0;
|
|
3127
|
+
if (!isRecord(raw)) {
|
|
3128
|
+
errors.push({
|
|
3129
|
+
path: itemPath,
|
|
3130
|
+
message: "presentation must be an object",
|
|
3131
|
+
code: "presentation-not-object"
|
|
3132
|
+
});
|
|
3133
|
+
return void 0;
|
|
3134
|
+
}
|
|
3135
|
+
const type = raw.type;
|
|
3136
|
+
if (type !== "image" && type !== "gif" && type !== "html-fragment") {
|
|
3137
|
+
errors.push({
|
|
3138
|
+
path: `${itemPath}.type`,
|
|
3139
|
+
message: 'presentation.type must be "image", "gif", or "html-fragment"',
|
|
3140
|
+
code: "presentation-unknown-type"
|
|
3141
|
+
});
|
|
3142
|
+
return void 0;
|
|
3143
|
+
}
|
|
3144
|
+
if (type === "image") {
|
|
3145
|
+
const src = requiredString(raw, "src", itemPath, errors, "presentation-missing-src");
|
|
3146
|
+
if (!src)
|
|
3147
|
+
return void 0;
|
|
3148
|
+
return { type: "image", src };
|
|
3149
|
+
}
|
|
3150
|
+
if (type === "gif") {
|
|
3151
|
+
const src = requiredString(raw, "src", itemPath, errors, "presentation-missing-src");
|
|
3152
|
+
if (!src)
|
|
3153
|
+
return void 0;
|
|
3154
|
+
return {
|
|
3155
|
+
type: "gif",
|
|
3156
|
+
src,
|
|
3157
|
+
poster: optionalString(raw, "poster")
|
|
3158
|
+
};
|
|
3159
|
+
}
|
|
3160
|
+
if ("src" in raw) {
|
|
3161
|
+
errors.push({
|
|
3162
|
+
path: `${itemPath}.src`,
|
|
3163
|
+
message: "html-fragment must use presentation.html, not presentation.src",
|
|
3164
|
+
code: "html-fragment-src-not-allowed"
|
|
3165
|
+
});
|
|
3166
|
+
}
|
|
3167
|
+
const htmlValue = raw.html;
|
|
3168
|
+
if (typeof htmlValue !== "string" || htmlValue.trim().length === 0) {
|
|
3169
|
+
errors.push({
|
|
3170
|
+
path: `${itemPath}.html`,
|
|
3171
|
+
message: "html-fragment requires non-empty presentation.html",
|
|
3172
|
+
code: "html-fragment-missing-html"
|
|
3173
|
+
});
|
|
3174
|
+
return void 0;
|
|
3175
|
+
}
|
|
3176
|
+
const unsafe = detectUnsafeAddMenuHtmlFragment(htmlValue);
|
|
3177
|
+
if (unsafe) {
|
|
3178
|
+
errors.push({
|
|
3179
|
+
path: `${itemPath}.html`,
|
|
3180
|
+
message: unsafe.message,
|
|
3181
|
+
code: unsafe.code
|
|
3182
|
+
});
|
|
3183
|
+
return void 0;
|
|
3184
|
+
}
|
|
3185
|
+
const sanitized = sanitizeAddMenuHtmlFragment(htmlValue);
|
|
3186
|
+
if (byteLengthUtf8(sanitized) > HTML_HARD_LIMIT_BYTES) {
|
|
3187
|
+
errors.push({
|
|
3188
|
+
path: `${itemPath}.html`,
|
|
3189
|
+
message: `html-fragment exceeds ${HTML_HARD_LIMIT_BYTES} bytes after sanitize`,
|
|
3190
|
+
code: "html-fragment-oversize-hard"
|
|
3191
|
+
});
|
|
3192
|
+
return void 0;
|
|
3193
|
+
}
|
|
3194
|
+
return { type: "html-fragment", html: htmlValue };
|
|
3195
|
+
}
|
|
3196
|
+
const BROWSE_SIZES = /* @__PURE__ */ new Set(["large", "medium", "small"]);
|
|
3197
|
+
function validateBrowse(raw, itemPath, errors) {
|
|
3198
|
+
if (raw === void 0)
|
|
3199
|
+
return void 0;
|
|
3200
|
+
if (!isRecord(raw)) {
|
|
3201
|
+
errors.push({
|
|
3202
|
+
path: itemPath,
|
|
3203
|
+
message: "browse must be an object",
|
|
3204
|
+
code: "browse-not-object"
|
|
3205
|
+
});
|
|
3206
|
+
return void 0;
|
|
3207
|
+
}
|
|
3208
|
+
const sizeRaw = raw.size;
|
|
3209
|
+
if (sizeRaw === void 0) {
|
|
3210
|
+
return {};
|
|
3211
|
+
}
|
|
3212
|
+
if (typeof sizeRaw !== "string" || !BROWSE_SIZES.has(sizeRaw)) {
|
|
3213
|
+
errors.push({
|
|
3214
|
+
path: `${itemPath}.size`,
|
|
3215
|
+
message: 'browse.size must be "large", "medium", or "small"',
|
|
3216
|
+
code: "browse-unknown-size"
|
|
3217
|
+
});
|
|
3218
|
+
return void 0;
|
|
3219
|
+
}
|
|
3220
|
+
return { size: sizeRaw };
|
|
3221
|
+
}
|
|
3222
|
+
function validateFolderPath(raw, itemPath, errors) {
|
|
3223
|
+
if (raw === void 0)
|
|
3224
|
+
return void 0;
|
|
3225
|
+
if (!Array.isArray(raw)) {
|
|
3226
|
+
errors.push({
|
|
3227
|
+
path: `${itemPath}.folderPath`,
|
|
3228
|
+
message: "folderPath must be an array of strings",
|
|
3229
|
+
code: "folder-path-not-array"
|
|
3230
|
+
});
|
|
3231
|
+
return void 0;
|
|
3232
|
+
}
|
|
3233
|
+
const segments = [];
|
|
3234
|
+
for (let index = 0; index < raw.length; index++) {
|
|
3235
|
+
const value = raw[index];
|
|
3236
|
+
if (typeof value !== "string") {
|
|
3237
|
+
errors.push({
|
|
3238
|
+
path: `${itemPath}.folderPath[${index}]`,
|
|
3239
|
+
message: "folderPath segments must be strings",
|
|
3240
|
+
code: "folder-path-segment-type"
|
|
3241
|
+
});
|
|
3242
|
+
return void 0;
|
|
3243
|
+
}
|
|
3244
|
+
const trimmed = value.trim();
|
|
3245
|
+
if (trimmed.length === 0) {
|
|
3246
|
+
errors.push({
|
|
3247
|
+
path: `${itemPath}.folderPath[${index}]`,
|
|
3248
|
+
message: "folderPath segments must be non-empty",
|
|
3249
|
+
code: "folder-path-empty-segment"
|
|
3250
|
+
});
|
|
3251
|
+
return void 0;
|
|
3252
|
+
}
|
|
3253
|
+
if (trimmed === ".." || trimmed.includes("/") || trimmed.includes("\\")) {
|
|
3254
|
+
errors.push({
|
|
3255
|
+
path: `${itemPath}.folderPath[${index}]`,
|
|
3256
|
+
message: 'folderPath segments must not contain ".." or path separators',
|
|
3257
|
+
code: "folder-path-invalid-segment"
|
|
3258
|
+
});
|
|
3259
|
+
return void 0;
|
|
3260
|
+
}
|
|
3261
|
+
if (segments.at(-1) === trimmed) {
|
|
3262
|
+
errors.push({
|
|
3263
|
+
path: `${itemPath}.folderPath[${index}]`,
|
|
3264
|
+
message: "folderPath must not contain duplicate adjacent segments",
|
|
3265
|
+
code: "folder-path-adjacent-duplicate"
|
|
3266
|
+
});
|
|
3267
|
+
return void 0;
|
|
3268
|
+
}
|
|
3269
|
+
segments.push(trimmed);
|
|
3270
|
+
}
|
|
3271
|
+
if (segments.length > FOLDER_PATH_MAX_SEGMENTS) {
|
|
3272
|
+
errors.push({
|
|
3273
|
+
path: `${itemPath}.folderPath`,
|
|
3274
|
+
message: `folderPath exceeds ${FOLDER_PATH_MAX_SEGMENTS} segments`,
|
|
3275
|
+
code: "folder-path-too-deep"
|
|
3276
|
+
});
|
|
3277
|
+
return void 0;
|
|
3278
|
+
}
|
|
3279
|
+
return segments.length > 0 ? segments : void 0;
|
|
3280
|
+
}
|
|
3281
|
+
function validateAddMenuItem(raw, itemPath) {
|
|
3282
|
+
const errors = [];
|
|
3283
|
+
if (!isRecord(raw)) {
|
|
3284
|
+
return {
|
|
3285
|
+
item: null,
|
|
3286
|
+
errors: [
|
|
3287
|
+
{ path: itemPath, message: "item must be an object", code: "item-not-object" }
|
|
3288
|
+
]
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
for (const field of REJECTED_ITEM_FIELDS) {
|
|
3292
|
+
if (field in raw) {
|
|
3293
|
+
errors.push({
|
|
3294
|
+
path: `${itemPath}.${field}`,
|
|
3295
|
+
message: `field "${field}" is not allowed in Add Menu catalog items`,
|
|
3296
|
+
code: `rejected-field-${field}`
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
const id = requiredString(raw, "id", itemPath, errors, "missing-id");
|
|
3301
|
+
const title = requiredString(raw, "title", itemPath, errors, "missing-title");
|
|
3302
|
+
const category = requiredString(raw, "category", itemPath, errors, "missing-category");
|
|
3303
|
+
const prompt = requiredString(raw, "prompt", itemPath, errors, "missing-prompt");
|
|
3304
|
+
if (errors.length > 0 || !id || !title || !category || !prompt) {
|
|
3305
|
+
return { item: null, errors };
|
|
3306
|
+
}
|
|
3307
|
+
const interaction = validateInteraction(raw.interaction, `${itemPath}.interaction`, errors);
|
|
3308
|
+
const presentation = validatePresentation(raw.presentation, `${itemPath}.presentation`, errors);
|
|
3309
|
+
const browse = validateBrowse(raw.browse, `${itemPath}.browse`, errors);
|
|
3310
|
+
const folderPath = validateFolderPath(raw.folderPath, itemPath, errors);
|
|
3311
|
+
if (errors.length > 0) {
|
|
3312
|
+
return { item: null, errors };
|
|
3313
|
+
}
|
|
3314
|
+
return {
|
|
3315
|
+
item: {
|
|
3316
|
+
id,
|
|
3317
|
+
title,
|
|
3318
|
+
category,
|
|
3319
|
+
prompt,
|
|
3320
|
+
pluginName: optionalString(raw, "pluginName"),
|
|
3321
|
+
packageName: optionalString(raw, "packageName"),
|
|
3322
|
+
subCategory: optionalString(raw, "subCategory"),
|
|
3323
|
+
...folderPath ? { folderPath } : {},
|
|
3324
|
+
thumbnail: optionalString(raw, "thumbnail"),
|
|
3325
|
+
...presentation ? { presentation } : {},
|
|
3326
|
+
...browse ? { browse } : {},
|
|
3327
|
+
...interaction ? { interaction } : {}
|
|
3328
|
+
},
|
|
3329
|
+
errors
|
|
3330
|
+
};
|
|
3331
|
+
}
|
|
3332
|
+
function validateAddMenuCatalogFile(raw, sourcePath) {
|
|
3333
|
+
const errors = [];
|
|
3334
|
+
if (!isRecord(raw)) {
|
|
3335
|
+
return {
|
|
3336
|
+
file: null,
|
|
3337
|
+
errors: [
|
|
3338
|
+
{
|
|
3339
|
+
path: sourcePath,
|
|
3340
|
+
message: "catalog file must be an object",
|
|
3341
|
+
code: "catalog-not-object"
|
|
3342
|
+
}
|
|
3343
|
+
]
|
|
3344
|
+
};
|
|
3345
|
+
}
|
|
3346
|
+
if (!Array.isArray(raw.items)) {
|
|
3347
|
+
return {
|
|
3348
|
+
file: null,
|
|
3349
|
+
errors: [
|
|
3350
|
+
{
|
|
3351
|
+
path: `${sourcePath}.items`,
|
|
3352
|
+
message: "items must be an array",
|
|
3353
|
+
code: "catalog-items-not-array"
|
|
3354
|
+
}
|
|
3355
|
+
]
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
const items = [];
|
|
3359
|
+
raw.items.forEach((entry, index) => {
|
|
3360
|
+
const result = validateAddMenuItem(entry, `${sourcePath}.items[${index}]`);
|
|
3361
|
+
errors.push(...result.errors);
|
|
3362
|
+
if (result.item)
|
|
3363
|
+
items.push(result.item);
|
|
3364
|
+
});
|
|
3365
|
+
if (items.length === 0 && errors.length > 0) {
|
|
3366
|
+
return { file: null, errors };
|
|
3367
|
+
}
|
|
3368
|
+
return { file: { items }, errors };
|
|
3369
|
+
}
|
|
3370
|
+
function normalizeAddMenuPresentation(item) {
|
|
3371
|
+
if (item.presentation)
|
|
3372
|
+
return item.presentation;
|
|
3373
|
+
const thumbnail = item.thumbnail?.trim();
|
|
3374
|
+
if (!thumbnail)
|
|
3375
|
+
return void 0;
|
|
3376
|
+
if (/\.gif$/i.test(thumbnail)) {
|
|
3377
|
+
return { type: "gif", src: thumbnail };
|
|
3378
|
+
}
|
|
3379
|
+
return { type: "image", src: thumbnail };
|
|
3380
|
+
}
|
|
3381
|
+
function normalizeAddMenuBrowseSize(item) {
|
|
3382
|
+
return item.browse?.size ?? "medium";
|
|
3383
|
+
}
|
|
3384
|
+
function hasSingleRootDiv(html) {
|
|
3385
|
+
const trimmed = html.trim();
|
|
3386
|
+
if (!trimmed.startsWith("<div"))
|
|
3387
|
+
return false;
|
|
3388
|
+
const openMatch = trimmed.match(/^<div\b[^>]*>/i);
|
|
3389
|
+
if (!openMatch)
|
|
3390
|
+
return false;
|
|
3391
|
+
const afterOpen = trimmed.slice(openMatch[0].length);
|
|
3392
|
+
const closeIdx = afterOpen.lastIndexOf("</div>");
|
|
3393
|
+
if (closeIdx < 0)
|
|
3394
|
+
return false;
|
|
3395
|
+
const tail = afterOpen.slice(closeIdx + "</div>".length).trim();
|
|
3396
|
+
return tail.length === 0;
|
|
3397
|
+
}
|
|
3398
|
+
function hasAtScopeStyle(html) {
|
|
3399
|
+
return /<style\b[^>]*>[\s\S]*@scope\b/i.test(html);
|
|
3400
|
+
}
|
|
3401
|
+
function lintHtmlFragment(item, sourcePath) {
|
|
3402
|
+
const errors = [];
|
|
3403
|
+
const warnings = [];
|
|
3404
|
+
const presentation = item.presentation;
|
|
3405
|
+
if (!presentation || presentation.type !== "html-fragment") {
|
|
3406
|
+
return { errors, warnings };
|
|
3407
|
+
}
|
|
3408
|
+
const rawHtml = presentation.html?.trim() ?? "";
|
|
3409
|
+
if (!rawHtml) {
|
|
3410
|
+
errors.push(
|
|
3411
|
+
catalogWarning(
|
|
3412
|
+
"html-fragment-missing-html",
|
|
3413
|
+
"html-fragment requires non-empty presentation.html",
|
|
3414
|
+
item.id,
|
|
3415
|
+
sourcePath
|
|
3416
|
+
)
|
|
3417
|
+
);
|
|
3418
|
+
return { errors, warnings };
|
|
3419
|
+
}
|
|
3420
|
+
const unsafe = detectUnsafeAddMenuHtmlFragment(rawHtml);
|
|
3421
|
+
if (unsafe) {
|
|
3422
|
+
errors.push(catalogWarning(unsafe.code, unsafe.message, item.id, sourcePath));
|
|
3423
|
+
return { errors, warnings };
|
|
3424
|
+
}
|
|
3425
|
+
const sanitized = sanitizeAddMenuHtmlFragment(rawHtml);
|
|
3426
|
+
const bytes = byteLengthUtf8(sanitized);
|
|
3427
|
+
if (bytes > HTML_HARD_LIMIT_BYTES) {
|
|
3428
|
+
errors.push(
|
|
3429
|
+
catalogWarning(
|
|
3430
|
+
"html-fragment-oversize-hard",
|
|
3431
|
+
`html-fragment exceeds ${HTML_HARD_LIMIT_BYTES} bytes after sanitize (${bytes})`,
|
|
3432
|
+
item.id,
|
|
3433
|
+
sourcePath
|
|
3434
|
+
)
|
|
3435
|
+
);
|
|
3436
|
+
} else if (bytes > HTML_SOFT_LIMIT_BYTES) {
|
|
3437
|
+
warnings.push(
|
|
3438
|
+
catalogWarning(
|
|
3439
|
+
"html-fragment-oversize-soft",
|
|
3440
|
+
`html-fragment exceeds ${HTML_SOFT_LIMIT_BYTES} bytes after sanitize (${bytes}) — consider trimming`,
|
|
3441
|
+
item.id,
|
|
3442
|
+
sourcePath
|
|
3443
|
+
)
|
|
3444
|
+
);
|
|
3445
|
+
}
|
|
3446
|
+
if (!hasSingleRootDiv(sanitized)) {
|
|
3447
|
+
warnings.push(
|
|
3448
|
+
catalogWarning(
|
|
3449
|
+
"html-fragment-missing-root-div",
|
|
3450
|
+
"html-fragment should use a single root <div>",
|
|
3451
|
+
item.id,
|
|
3452
|
+
sourcePath
|
|
3453
|
+
)
|
|
3454
|
+
);
|
|
3455
|
+
}
|
|
3456
|
+
if (!hasAtScopeStyle(sanitized)) {
|
|
3457
|
+
warnings.push(
|
|
3458
|
+
catalogWarning(
|
|
3459
|
+
"html-fragment-missing-at-scope",
|
|
3460
|
+
"html-fragment should include <style> with @scope for preview isolation",
|
|
3461
|
+
item.id,
|
|
3462
|
+
sourcePath
|
|
3463
|
+
)
|
|
3464
|
+
);
|
|
3465
|
+
}
|
|
3466
|
+
return { errors, warnings };
|
|
3467
|
+
}
|
|
3468
|
+
function lintGifPoster(item, sourcePath) {
|
|
3469
|
+
const presentation = normalizeAddMenuPresentation(item);
|
|
3470
|
+
if (presentation?.type !== "gif" || presentation.poster?.trim())
|
|
3471
|
+
return [];
|
|
3472
|
+
return [
|
|
3473
|
+
catalogWarning(
|
|
3474
|
+
"gif-missing-poster",
|
|
3475
|
+
`gif item "${item.id}" has no poster — add poster for prefers-reduced-motion accessibility`,
|
|
3476
|
+
item.id,
|
|
3477
|
+
sourcePath
|
|
3478
|
+
)
|
|
3479
|
+
];
|
|
3480
|
+
}
|
|
3481
|
+
function lintBrowseLargeWithoutPresentation(item, sourcePath) {
|
|
3482
|
+
if (normalizeAddMenuBrowseSize(item) !== "large")
|
|
3483
|
+
return [];
|
|
3484
|
+
if (normalizeAddMenuPresentation(item))
|
|
3485
|
+
return [];
|
|
3486
|
+
return [
|
|
3487
|
+
catalogWarning(
|
|
3488
|
+
"browse-large-without-presentation",
|
|
3489
|
+
`large browse item "${item.id}" has no presentation or thumbnail — add a preview`,
|
|
3490
|
+
item.id,
|
|
3491
|
+
sourcePath
|
|
3492
|
+
)
|
|
3493
|
+
];
|
|
3494
|
+
}
|
|
3495
|
+
function lintAddMenuCatalog(items, sourcePath) {
|
|
3496
|
+
const errors = [];
|
|
3497
|
+
const warnings = [];
|
|
3498
|
+
for (const item of items) {
|
|
3499
|
+
warnings.push(...lintGifPoster(item, sourcePath));
|
|
3500
|
+
warnings.push(...lintBrowseLargeWithoutPresentation(item, sourcePath));
|
|
3501
|
+
const htmlLint = lintHtmlFragment(item, sourcePath);
|
|
3502
|
+
errors.push(...htmlLint.errors);
|
|
3503
|
+
warnings.push(...htmlLint.warnings);
|
|
3504
|
+
}
|
|
3505
|
+
return { errors, warnings };
|
|
3506
|
+
}
|
|
3507
|
+
const ADD_MENU_VALIDATION_SUGGESTIONS = {
|
|
3508
|
+
"gif-missing-poster": "Add presentation.poster with a static image for prefers-reduced-motion accessibility",
|
|
3509
|
+
"html-fragment-missing-root-div": 'Wrap preview markup in a single root <div class="am-preview"> — see agent-kit/plugin/aiditor-add-menu.md',
|
|
3510
|
+
"html-fragment-missing-at-scope": "Add <style> with @scope (.am-preview) inside the root div — see agent-kit/plugin/aiditor-add-menu.md",
|
|
3511
|
+
"html-fragment-missing-html": "Set presentation.html with inline markup — external preview files are not supported",
|
|
3512
|
+
"html-fragment-src-not-allowed": "Remove presentation.src; use presentation.html for html-fragment previews",
|
|
3513
|
+
"html-fragment-unsafe-markup": "Remove scripts, event handlers, and javascript: URLs from presentation.html",
|
|
3514
|
+
"html-fragment-oversize-soft": "Trim inline html or use a gif preview for rich motion",
|
|
3515
|
+
"html-fragment-oversize-hard": "Reduce presentation.html below 32 KB after sanitize",
|
|
3516
|
+
"browse-large-without-presentation": "Add presentation or thumbnail — large browse tiles need a visual preview",
|
|
3517
|
+
"browse-unknown-size": "Set browse.size to large, medium, or small",
|
|
3518
|
+
"presentation-unknown-type": "Set presentation.type to image, gif, or html-fragment",
|
|
3519
|
+
"interaction-invalid-mode": "Set interaction.mode to reference (default) or stage-place",
|
|
3520
|
+
"folder-path-not-array": "folderPath must be a yaml array of folder name strings",
|
|
3521
|
+
"folder-path-segment-type": "Each folderPath entry must be a non-empty string",
|
|
3522
|
+
"folder-path-empty-segment": "Remove empty folderPath segments",
|
|
3523
|
+
"folder-path-invalid-segment": 'Use separate array elements per folder level — no "/" or "\\" in segment names',
|
|
3524
|
+
"folder-path-adjacent-duplicate": "Remove duplicate consecutive folderPath segments",
|
|
3525
|
+
"folder-path-too-deep": `Shorten folderPath to at most ${FOLDER_PATH_MAX_SEGMENTS} segments`,
|
|
3526
|
+
"rejected-field-kind": "Remove kind — express item intent in prompt text",
|
|
3527
|
+
"rejected-field-parameters": "Remove parameters — materialize values into prompt",
|
|
3528
|
+
"rejected-field-component": "Remove component — use prompt plus contract paths in agent-kit",
|
|
3529
|
+
"rejected-field-allowedScopes": "Remove allowedScopes — attachment scope is chosen in the AIditor UI",
|
|
3530
|
+
"add-menu-missing-agentkit-handler": "Declare agentkit in plugin.yaml pointing at an agent-kit handler export; run jay-stack agent-kit (project yarn agent-kit) to materialize catalogs — not jay-stack setup",
|
|
3531
|
+
"add-menu-legacy-setup-handler": "Move add-menu catalog writes from the setup handler to agentkit. Setup is for config/credentials only; run jay-stack agent-kit to generate agent-kit/aiditor/add-menu/*.yaml",
|
|
3532
|
+
"deprecated-nested-setup-keys": "Replace nested setup.handler / setup.references with flat keys: setup: <handler> and agentkit: <handler>"
|
|
3533
|
+
};
|
|
3534
|
+
const ADD_MENU_CATALOG_REL_PATHS = [
|
|
3535
|
+
"agent-kit/aiditor/add-menu.template.yaml",
|
|
3536
|
+
"agent-kit/aiditor/add-menu.yaml"
|
|
3537
|
+
];
|
|
3538
|
+
const CONTRIBUTOR_GUIDE = "agent-kit/plugin/aiditor-add-menu.md";
|
|
3539
|
+
const ADD_MENU_WRITE_MARKERS = [
|
|
3540
|
+
"agent-kit/aiditor/add-menu",
|
|
3541
|
+
"aiditor-add-menu-thumbnails",
|
|
3542
|
+
"writeAddMenuCatalog",
|
|
3543
|
+
"copyAiditorAddMenuThumbnails"
|
|
3544
|
+
];
|
|
3545
|
+
function isRelativeHandlerRef(value) {
|
|
3546
|
+
return value.startsWith("./") || value.startsWith("../");
|
|
3547
|
+
}
|
|
3548
|
+
function resolveModulePath$1(basePath) {
|
|
3549
|
+
for (const ext of ["", ".ts", ".js", "/index.ts", "/index.js"]) {
|
|
3550
|
+
const candidate = basePath + ext;
|
|
3551
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
3552
|
+
return candidate;
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
return void 0;
|
|
3556
|
+
}
|
|
3557
|
+
function collectTypeScriptFiles(dir, depth = 0) {
|
|
3558
|
+
if (depth > 4)
|
|
3559
|
+
return [];
|
|
3560
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
3561
|
+
const files = [];
|
|
3562
|
+
for (const entry of entries) {
|
|
3563
|
+
const fullPath = path.join(dir, entry.name);
|
|
3564
|
+
if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== "test") {
|
|
3565
|
+
files.push(...collectTypeScriptFiles(fullPath, depth + 1));
|
|
3566
|
+
} else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".js"))) {
|
|
3567
|
+
files.push(fullPath);
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
return files;
|
|
3571
|
+
}
|
|
3572
|
+
function resolveHandlerSourceFile(pluginPath, handlerRef, isNpmPackage) {
|
|
3573
|
+
if (isRelativeHandlerRef(handlerRef)) {
|
|
3574
|
+
return resolveModulePath$1(path.join(pluginPath, handlerRef)) ?? null;
|
|
3575
|
+
}
|
|
3576
|
+
const searchRoots = isNpmPackage ? [path.join(pluginPath, "lib"), path.join(pluginPath, "dist")] : [pluginPath];
|
|
3577
|
+
for (const root of searchRoots) {
|
|
3578
|
+
if (!fs.existsSync(root))
|
|
3579
|
+
continue;
|
|
3580
|
+
for (const file of collectTypeScriptFiles(root)) {
|
|
3581
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
3582
|
+
const definesHandler = new RegExp(
|
|
3583
|
+
`export\\s+(?:async\\s+)?function\\s+${handlerRef}\\b`
|
|
3584
|
+
).test(content);
|
|
3585
|
+
const definesDefaultNamed = new RegExp(
|
|
3586
|
+
`export\\s+default\\s+async\\s+function\\s+${handlerRef}\\b`
|
|
3587
|
+
).test(content);
|
|
3588
|
+
if (definesHandler || definesDefaultNamed) {
|
|
3589
|
+
return file;
|
|
3590
|
+
}
|
|
3591
|
+
const reExportMatch = content.match(
|
|
3592
|
+
new RegExp(
|
|
3593
|
+
`export\\s*\\{[^}]*\\b${handlerRef}\\b[^}]*\\}\\s*from\\s*['"]([^'"]+)['"]`
|
|
3594
|
+
)
|
|
3595
|
+
);
|
|
3596
|
+
if (reExportMatch) {
|
|
3597
|
+
const importSpec = reExportMatch[1].replace(/\.js$/, "");
|
|
3598
|
+
const resolved = resolveModulePath$1(path.resolve(path.dirname(file), importSpec));
|
|
3599
|
+
if (resolved)
|
|
3600
|
+
return resolved;
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
return null;
|
|
3605
|
+
}
|
|
3606
|
+
function extractBalancedBlock(source, openBraceIndex) {
|
|
3607
|
+
let depth = 0;
|
|
3608
|
+
for (let index = openBraceIndex; index < source.length; index++) {
|
|
3609
|
+
const char = source[index];
|
|
3610
|
+
if (char === "{")
|
|
3611
|
+
depth++;
|
|
3612
|
+
else if (char === "}") {
|
|
3613
|
+
depth--;
|
|
3614
|
+
if (depth === 0) {
|
|
3615
|
+
return source.slice(openBraceIndex, index + 1);
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
return null;
|
|
3620
|
+
}
|
|
3621
|
+
function findFunctionBodyOpenBrace(source, searchFrom) {
|
|
3622
|
+
const arrowMatch = /=>\s*\{/.exec(source.slice(searchFrom));
|
|
3623
|
+
const parenMatch = /\)\s*(?::[^{]+)?\{/.exec(source.slice(searchFrom));
|
|
3624
|
+
const candidates = [];
|
|
3625
|
+
if (arrowMatch?.index !== void 0) {
|
|
3626
|
+
candidates.push(searchFrom + arrowMatch.index + arrowMatch[0].length - 1);
|
|
3627
|
+
}
|
|
3628
|
+
if (parenMatch?.index !== void 0) {
|
|
3629
|
+
candidates.push(searchFrom + parenMatch.index + parenMatch[0].length - 1);
|
|
3630
|
+
}
|
|
3631
|
+
if (candidates.length === 0)
|
|
3632
|
+
return -1;
|
|
3633
|
+
return Math.min(...candidates);
|
|
3634
|
+
}
|
|
3635
|
+
function extractFunctionBody(source, functionName) {
|
|
3636
|
+
const patterns = [
|
|
3637
|
+
new RegExp(`export\\s+async\\s+function\\s+${functionName}\\b`),
|
|
3638
|
+
new RegExp(`export\\s+function\\s+${functionName}\\b`),
|
|
3639
|
+
new RegExp(`export\\s+default\\s+async\\s+function\\s+${functionName}\\b`),
|
|
3640
|
+
new RegExp(
|
|
3641
|
+
`export\\s+const\\s+${functionName}\\s*=\\s*async\\s*\\([^)]*\\)\\s*(?::[^{]+)?=>\\s*\\{`
|
|
3642
|
+
),
|
|
3643
|
+
new RegExp(
|
|
3644
|
+
`export\\s+const\\s+${functionName}\\s*=\\s*\\([^)]*\\)\\s*(?::[^{]+)?=>\\s*\\{`
|
|
3645
|
+
)
|
|
3646
|
+
];
|
|
3647
|
+
for (const pattern of patterns) {
|
|
3648
|
+
const match = pattern.exec(source);
|
|
3649
|
+
if (!match)
|
|
3650
|
+
continue;
|
|
3651
|
+
const braceIndex = findFunctionBodyOpenBrace(source, match.index);
|
|
3652
|
+
if (braceIndex === -1)
|
|
3653
|
+
continue;
|
|
3654
|
+
return extractBalancedBlock(source, braceIndex);
|
|
3655
|
+
}
|
|
3656
|
+
return null;
|
|
3657
|
+
}
|
|
3658
|
+
function extractDefaultExportFunctionBody(source) {
|
|
3659
|
+
const patterns = [
|
|
3660
|
+
/export\s+default\s+async\s+function\s+\w+\b/,
|
|
3661
|
+
/export\s+default\s+async\s+function\s*\(/,
|
|
3662
|
+
/export\s+default\s+async\s*\([^)]*\)\s*(?::[^{]+)?=>\s*\{/
|
|
3663
|
+
];
|
|
3664
|
+
for (const pattern of patterns) {
|
|
3665
|
+
const match = pattern.exec(source);
|
|
3666
|
+
if (!match)
|
|
3667
|
+
continue;
|
|
3668
|
+
const braceIndex = findFunctionBodyOpenBrace(source, match.index);
|
|
3669
|
+
if (braceIndex === -1)
|
|
3670
|
+
continue;
|
|
3671
|
+
return extractBalancedBlock(source, braceIndex);
|
|
3672
|
+
}
|
|
3673
|
+
return null;
|
|
3674
|
+
}
|
|
3675
|
+
function handlerBodyWritesAddMenuCatalog(body) {
|
|
3676
|
+
return ADD_MENU_WRITE_MARKERS.some((marker) => body.includes(marker));
|
|
3677
|
+
}
|
|
3678
|
+
function resolveSetupHandlerFunctionBody(pluginPath, handlerRef, isNpmPackage) {
|
|
3679
|
+
const sourceFile = resolveHandlerSourceFile(pluginPath, handlerRef, isNpmPackage);
|
|
3680
|
+
if (!sourceFile)
|
|
3681
|
+
return null;
|
|
3682
|
+
const source = fs.readFileSync(sourceFile, "utf-8");
|
|
3683
|
+
if (isRelativeHandlerRef(handlerRef)) {
|
|
3684
|
+
return extractDefaultExportFunctionBody(source) ?? extractFunctionBody(source, "setup") ?? extractFunctionBody(source, handlerRef);
|
|
3685
|
+
}
|
|
3686
|
+
return extractFunctionBody(source, handlerRef);
|
|
3687
|
+
}
|
|
3688
|
+
function suggestionForCode(code) {
|
|
3689
|
+
if (!code)
|
|
3690
|
+
return `See ${CONTRIBUTOR_GUIDE} for schema and validation rules`;
|
|
3691
|
+
return ADD_MENU_VALIDATION_SUGGESTIONS[code] ?? `See ${CONTRIBUTOR_GUIDE} for schema and validation rules`;
|
|
3692
|
+
}
|
|
3693
|
+
function mapSchemaError(error, catalogPath) {
|
|
3694
|
+
const code = error.code ?? "catalog-validation-error";
|
|
3695
|
+
return {
|
|
3696
|
+
type: "add-menu-catalog",
|
|
3697
|
+
code,
|
|
3698
|
+
message: `[${code}] ${error.message}`,
|
|
3699
|
+
location: error.path || catalogPath,
|
|
3700
|
+
suggestion: suggestionForCode(code)
|
|
3701
|
+
};
|
|
3702
|
+
}
|
|
3703
|
+
function mapLintFinding(finding, catalogPath, severity) {
|
|
3704
|
+
return {
|
|
3705
|
+
type: "add-menu-catalog",
|
|
3706
|
+
code: finding.code,
|
|
3707
|
+
message: `[${finding.code}] ${finding.message}`,
|
|
3708
|
+
location: finding.sourcePath ?? catalogPath,
|
|
3709
|
+
itemId: finding.itemId,
|
|
3710
|
+
suggestion: suggestionForCode(finding.code),
|
|
3711
|
+
...severity === "warning" ? {} : {}
|
|
3712
|
+
};
|
|
3713
|
+
}
|
|
3714
|
+
function pluginShipsAddMenuCatalog(context) {
|
|
3715
|
+
return ADD_MENU_CATALOG_REL_PATHS.some(
|
|
3716
|
+
(relPath) => fs.existsSync(path.join(context.pluginPath, relPath))
|
|
3717
|
+
);
|
|
3718
|
+
}
|
|
3719
|
+
function validateAddMenuAgentKitHandler(context, result) {
|
|
3720
|
+
if (!pluginShipsAddMenuCatalog(context))
|
|
3721
|
+
return;
|
|
3722
|
+
const agentKitHandler = context.manifest.agentkit;
|
|
3723
|
+
if (!agentKitHandler) {
|
|
3724
|
+
result.warnings.push({
|
|
3725
|
+
type: "add-menu-catalog",
|
|
3726
|
+
code: "add-menu-missing-agentkit-handler",
|
|
3727
|
+
message: "[add-menu-missing-agentkit-handler] Plugin ships add-menu catalog yaml but plugin.yaml has no agentkit handler — catalogs must be generated during jay-stack agent-kit (yarn agent-kit), not jay-stack setup",
|
|
3728
|
+
location: "plugin.yaml",
|
|
3729
|
+
suggestion: suggestionForCode("add-menu-missing-agentkit-handler")
|
|
3730
|
+
});
|
|
3731
|
+
}
|
|
3732
|
+
const setupHandler = typeof context.manifest.setup === "string" ? context.manifest.setup : void 0;
|
|
3733
|
+
if (!setupHandler)
|
|
3734
|
+
return;
|
|
3735
|
+
const setupBody = resolveSetupHandlerFunctionBody(
|
|
3736
|
+
context.pluginPath,
|
|
3737
|
+
setupHandler,
|
|
3738
|
+
context.isNpmPackage
|
|
3739
|
+
);
|
|
3740
|
+
if (!setupBody || !handlerBodyWritesAddMenuCatalog(setupBody))
|
|
3741
|
+
return;
|
|
3742
|
+
result.warnings.push({
|
|
3743
|
+
type: "add-menu-catalog",
|
|
3744
|
+
code: "add-menu-legacy-setup-handler",
|
|
3745
|
+
message: "[add-menu-legacy-setup-handler] setup handler writes add-menu catalogs — move catalog materialization to agentkit and run jay-stack agent-kit (yarn agent-kit); keep setup for config and credentials only",
|
|
3746
|
+
location: `plugin.yaml setup (${setupHandler})`,
|
|
3747
|
+
suggestion: suggestionForCode("add-menu-legacy-setup-handler")
|
|
3748
|
+
});
|
|
3749
|
+
}
|
|
3750
|
+
async function validateAddMenuCatalogFileAtPath(catalogPath, relPath, result) {
|
|
3751
|
+
let parsed;
|
|
3752
|
+
try {
|
|
3753
|
+
const content = await fs.promises.readFile(catalogPath, "utf-8");
|
|
3754
|
+
parsed = YAML.parse(content);
|
|
3755
|
+
} catch (error) {
|
|
3756
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3757
|
+
result.errors.push({
|
|
3758
|
+
type: "add-menu-catalog",
|
|
3759
|
+
code: "catalog-yaml-parse-error",
|
|
3760
|
+
message: `Invalid add-menu catalog YAML: ${message}`,
|
|
3761
|
+
location: relPath,
|
|
3762
|
+
suggestion: `Check YAML syntax in the add-menu catalog file. See ${CONTRIBUTOR_GUIDE}`
|
|
3763
|
+
});
|
|
3764
|
+
return;
|
|
3765
|
+
}
|
|
3766
|
+
const validated = validateAddMenuCatalogFile(parsed, relPath);
|
|
3767
|
+
result.errors.push(...validated.errors.map((error) => mapSchemaError(error, relPath)));
|
|
3768
|
+
if (!validated.file?.items.length) {
|
|
3769
|
+
return;
|
|
3770
|
+
}
|
|
3771
|
+
const linted = lintAddMenuCatalog(validated.file.items, relPath);
|
|
3772
|
+
result.errors.push(
|
|
3773
|
+
...linted.errors.map((finding) => mapLintFinding(finding, relPath, "error"))
|
|
3774
|
+
);
|
|
3775
|
+
result.warnings.push(
|
|
3776
|
+
...linted.warnings.map((finding) => mapLintFinding(finding, relPath, "warning"))
|
|
3777
|
+
);
|
|
3778
|
+
}
|
|
3779
|
+
async function validateAddMenuCatalog(context, result) {
|
|
3780
|
+
validateAddMenuAgentKitHandler(context, result);
|
|
3781
|
+
for (const relPath of ADD_MENU_CATALOG_REL_PATHS) {
|
|
3782
|
+
const catalogPath = path.join(context.pluginPath, relPath);
|
|
3783
|
+
if (!fs.existsSync(catalogPath))
|
|
3784
|
+
continue;
|
|
3785
|
+
await validateAddMenuCatalogFileAtPath(catalogPath, relPath, result);
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3025
3788
|
async function validatePlugin(options = {}) {
|
|
3026
3789
|
const pluginPath = options.pluginPath || process.cwd();
|
|
3027
3790
|
if (options.local) {
|
|
@@ -3088,6 +3851,7 @@ async function validatePluginPackage(pluginPath, options) {
|
|
|
3088
3851
|
if (pluginManifest.dynamic_contracts) {
|
|
3089
3852
|
await validateDynamicContracts(context, result);
|
|
3090
3853
|
}
|
|
3854
|
+
await validateAddMenuCatalog(context, result);
|
|
3091
3855
|
result.valid = result.errors.length === 0;
|
|
3092
3856
|
return result;
|
|
3093
3857
|
}
|
|
@@ -3437,25 +4201,32 @@ async function validateSchema(context, result) {
|
|
|
3437
4201
|
}
|
|
3438
4202
|
}
|
|
3439
4203
|
if (manifest.setup) {
|
|
3440
|
-
if (manifest.setup
|
|
4204
|
+
if (typeof manifest.setup !== "string") {
|
|
4205
|
+
result.errors.push({
|
|
4206
|
+
type: "schema",
|
|
4207
|
+
message: "Deprecated nested setup keys (setup.handler / setup.references) — use flat setup: and agentkit: in plugin.yaml",
|
|
4208
|
+
location: "plugin.yaml setup",
|
|
4209
|
+
suggestion: "Replace setup.handler with top-level setup: and setup.references with top-level agentkit:"
|
|
4210
|
+
});
|
|
4211
|
+
} else {
|
|
3441
4212
|
validateHandlerRef(
|
|
3442
|
-
manifest.setup
|
|
4213
|
+
manifest.setup,
|
|
3443
4214
|
"Setup handler",
|
|
3444
|
-
"plugin.yaml setup
|
|
3445
|
-
context,
|
|
3446
|
-
result
|
|
3447
|
-
);
|
|
3448
|
-
}
|
|
3449
|
-
if (manifest.setup.references) {
|
|
3450
|
-
validateHandlerRef(
|
|
3451
|
-
manifest.setup.references,
|
|
3452
|
-
"References handler",
|
|
3453
|
-
"plugin.yaml setup.references",
|
|
4215
|
+
"plugin.yaml setup",
|
|
3454
4216
|
context,
|
|
3455
4217
|
result
|
|
3456
4218
|
);
|
|
3457
4219
|
}
|
|
3458
4220
|
}
|
|
4221
|
+
if (manifest.agentkit) {
|
|
4222
|
+
validateHandlerRef(
|
|
4223
|
+
manifest.agentkit,
|
|
4224
|
+
"Agent-kit handler",
|
|
4225
|
+
"plugin.yaml agentkit",
|
|
4226
|
+
context,
|
|
4227
|
+
result
|
|
4228
|
+
);
|
|
4229
|
+
}
|
|
3459
4230
|
}
|
|
3460
4231
|
function checkExportExists(exportName, context) {
|
|
3461
4232
|
const packageJsonPath = path.join(context.pluginPath, "package.json");
|
|
@@ -3842,6 +4613,18 @@ async function validatePackageJson(context, result) {
|
|
|
3842
4613
|
suggestion: 'Add "./plugin.yaml": "./plugin.yaml" to exports field'
|
|
3843
4614
|
});
|
|
3844
4615
|
}
|
|
4616
|
+
const agentKitDir = path.join(context.pluginPath, "agent-kit");
|
|
4617
|
+
if (fs.existsSync(agentKitDir) && fs.statSync(agentKitDir).isDirectory()) {
|
|
4618
|
+
const filesArray = packageJson.files;
|
|
4619
|
+
if (!filesArray || !filesArray.includes("agent-kit")) {
|
|
4620
|
+
result.warnings.push({
|
|
4621
|
+
type: "export-mismatch",
|
|
4622
|
+
message: 'agent-kit directory exists but is not listed in package.json "files"',
|
|
4623
|
+
location: packageJsonPath,
|
|
4624
|
+
suggestion: 'Add "agent-kit" to the "files" array so agent-kit files are shipped with the package'
|
|
4625
|
+
});
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
3845
4628
|
} catch (error) {
|
|
3846
4629
|
result.errors.push({
|
|
3847
4630
|
type: "schema",
|
|
@@ -4233,38 +5016,18 @@ function extractRouteParams(filePath, pagesBase) {
|
|
|
4233
5016
|
}
|
|
4234
5017
|
return params;
|
|
4235
5018
|
}
|
|
4236
|
-
function
|
|
4237
|
-
const
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
}
|
|
4243
|
-
function extractJayParams(content) {
|
|
4244
|
-
const root = parse(content, {
|
|
4245
|
-
comment: true,
|
|
4246
|
-
blockTextElements: { script: true, style: true }
|
|
4247
|
-
});
|
|
4248
|
-
const head = root.querySelector("head");
|
|
4249
|
-
if (!head)
|
|
4250
|
-
return /* @__PURE__ */ new Set();
|
|
4251
|
-
const paramScripts = head.querySelectorAll('script[type="application/jay-params"]');
|
|
4252
|
-
if (paramScripts.length !== 1)
|
|
4253
|
-
return /* @__PURE__ */ new Set();
|
|
4254
|
-
const body = dedentYaml(paramScripts[0].textContent ?? "");
|
|
4255
|
-
if (!body)
|
|
4256
|
-
return /* @__PURE__ */ new Set();
|
|
4257
|
-
try {
|
|
4258
|
-
const parsed = YAML.parse(body);
|
|
4259
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
4260
|
-
return new Set(Object.keys(parsed));
|
|
5019
|
+
function extractHeadlessPropsParamNames(parsedFile) {
|
|
5020
|
+
const names = /* @__PURE__ */ new Set();
|
|
5021
|
+
for (const imp of parsedFile.headlessImports) {
|
|
5022
|
+
if (imp.headlessProps) {
|
|
5023
|
+
for (const key of Object.keys(imp.headlessProps)) {
|
|
5024
|
+
names.add(key);
|
|
5025
|
+
}
|
|
4261
5026
|
}
|
|
4262
|
-
return /* @__PURE__ */ new Set();
|
|
4263
|
-
} catch {
|
|
4264
|
-
return /* @__PURE__ */ new Set();
|
|
4265
5027
|
}
|
|
5028
|
+
return names;
|
|
4266
5029
|
}
|
|
4267
|
-
function checkRouteParams(parsedFile, filePath, pagesBase
|
|
5030
|
+
function checkRouteParams(parsedFile, filePath, pagesBase) {
|
|
4268
5031
|
const requiredParams = /* @__PURE__ */ new Set();
|
|
4269
5032
|
function collectParams(params) {
|
|
4270
5033
|
for (const p of params) {
|
|
@@ -4284,13 +5047,13 @@ function checkRouteParams(parsedFile, filePath, pagesBase, jayHtmlContent) {
|
|
|
4284
5047
|
if (requiredParams.size === 0)
|
|
4285
5048
|
return [];
|
|
4286
5049
|
const routeParams = extractRouteParams(filePath, pagesBase);
|
|
4287
|
-
const
|
|
4288
|
-
const availableParams = /* @__PURE__ */ new Set([...routeParams, ...
|
|
5050
|
+
const headlessProps = extractHeadlessPropsParamNames(parsedFile);
|
|
5051
|
+
const availableParams = /* @__PURE__ */ new Set([...routeParams, ...headlessProps]);
|
|
4289
5052
|
const warnings = [];
|
|
4290
5053
|
for (const param of requiredParams) {
|
|
4291
5054
|
if (!availableParams.has(param)) {
|
|
4292
5055
|
warnings.push(
|
|
4293
|
-
`Contract requires param "${param}" but the route does not provide it. Add a dynamic segment [${param}] to the route path or
|
|
5056
|
+
`Contract requires param "${param}" but the route does not provide it. Add a dynamic segment [${param}] to the route path or provide it in the headless component's YAML body.`
|
|
4294
5057
|
);
|
|
4295
5058
|
}
|
|
4296
5059
|
}
|
|
@@ -4514,6 +5277,7 @@ async function runPluginValidators(projectRoot, parsedFiles, errors, warnings) {
|
|
|
4514
5277
|
const ctx = {
|
|
4515
5278
|
filePath: relativePath,
|
|
4516
5279
|
body: parsed.body,
|
|
5280
|
+
css: parsed.css,
|
|
4517
5281
|
head: parsed.headMeta,
|
|
4518
5282
|
contract: resolvedPageContract ? {
|
|
4519
5283
|
name: resolvedPageContract.name,
|
|
@@ -4660,7 +5424,13 @@ async function validateJayFiles(options = {}) {
|
|
|
4660
5424
|
continue;
|
|
4661
5425
|
}
|
|
4662
5426
|
parsedFiles.push({ relativePath, parsed: parsedFile.val });
|
|
4663
|
-
|
|
5427
|
+
if (content.includes("application/jay-params")) {
|
|
5428
|
+
warnings.push({
|
|
5429
|
+
file: relativePath,
|
|
5430
|
+
message: '<script type="application/jay-params"> is deprecated. Move the values into the YAML body of the headless component that uses them. See agent-kit/developer/routing.md for details.'
|
|
5431
|
+
});
|
|
5432
|
+
}
|
|
5433
|
+
const routeParamWarnings = checkRouteParams(parsedFile.val, jayFile, scanDir);
|
|
4664
5434
|
for (const msg of routeParamWarnings) {
|
|
4665
5435
|
warnings.push({ file: relativePath, message: msg });
|
|
4666
5436
|
}
|
|
@@ -4784,11 +5554,25 @@ function printJayValidationResult(result, options) {
|
|
|
4784
5554
|
logger.important(chalk.blue(` Suggestion: ${error.suggestion}`));
|
|
4785
5555
|
}
|
|
4786
5556
|
}
|
|
5557
|
+
const fileGroups = /* @__PURE__ */ new Map();
|
|
4787
5558
|
for (const warning of warns) {
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
5559
|
+
const group = fileGroups.get(warning.file) || [];
|
|
5560
|
+
group.push(warning);
|
|
5561
|
+
fileGroups.set(warning.file, group);
|
|
5562
|
+
}
|
|
5563
|
+
for (const [file, groupWarns] of fileGroups) {
|
|
5564
|
+
logger.important(chalk.yellow(` ⚠ ${file}`));
|
|
5565
|
+
for (const warning of groupWarns) {
|
|
5566
|
+
if (warning.message) {
|
|
5567
|
+
logger.important(chalk.gray(` ${warning.message}`));
|
|
5568
|
+
}
|
|
5569
|
+
}
|
|
5570
|
+
const suggestions = [...new Set(groupWarns.map((w) => w.suggestion).filter(Boolean))];
|
|
5571
|
+
if (suggestions.length > 0) {
|
|
5572
|
+
logger.important(chalk.blue(` Suggestions:`));
|
|
5573
|
+
for (const s2 of suggestions) {
|
|
5574
|
+
logger.important(chalk.blue(` ${s2}`));
|
|
5575
|
+
}
|
|
4792
5576
|
}
|
|
4793
5577
|
}
|
|
4794
5578
|
}
|
|
@@ -4909,7 +5693,7 @@ async function runAgentKit(options) {
|
|
|
4909
5693
|
await ensureAgentKitDocs(projectRoot, options.force, options.mode);
|
|
4910
5694
|
await mergePluginAgentKitGuides(projectRoot, options.mode);
|
|
4911
5695
|
if (options.references !== false) {
|
|
4912
|
-
await
|
|
5696
|
+
await generatePluginAgentKit(projectRoot, options, initErrors, viteServer);
|
|
4913
5697
|
}
|
|
4914
5698
|
}
|
|
4915
5699
|
} finally {
|
|
@@ -5106,9 +5890,9 @@ async function mergePluginAgentKitGuides(projectRoot, mode) {
|
|
|
5106
5890
|
await fs$1.appendFile(instructionsPath, lines.join("\n"));
|
|
5107
5891
|
}
|
|
5108
5892
|
}
|
|
5109
|
-
async function
|
|
5110
|
-
const {
|
|
5111
|
-
const plugins = await
|
|
5893
|
+
async function generatePluginAgentKit(projectRoot, options, initErrors, viteServer) {
|
|
5894
|
+
const { discoverPluginsWithAgentKit, executePluginAgentKit } = await import("@jay-framework/stack-server-runtime");
|
|
5895
|
+
const plugins = await discoverPluginsWithAgentKit({
|
|
5112
5896
|
projectRoot,
|
|
5113
5897
|
verbose: options.verbose,
|
|
5114
5898
|
pluginFilter: options.plugin
|
|
@@ -5117,35 +5901,35 @@ async function generatePluginReferences(projectRoot, options, initErrors, viteSe
|
|
|
5117
5901
|
return;
|
|
5118
5902
|
const logger = getLogger();
|
|
5119
5903
|
logger.important("");
|
|
5120
|
-
logger.important(chalk.bold("Generating plugin
|
|
5904
|
+
logger.important(chalk.bold("Generating plugin agent-kit data..."));
|
|
5121
5905
|
for (const plugin of plugins) {
|
|
5122
5906
|
const pluginInitError = initErrors.get(plugin.name);
|
|
5123
5907
|
if (pluginInitError) {
|
|
5124
5908
|
logger.warn(
|
|
5125
5909
|
chalk.yellow(
|
|
5126
|
-
` ${plugin.name}:
|
|
5910
|
+
` ${plugin.name}: agent-kit skipped — init failed: ${pluginInitError.message}`
|
|
5127
5911
|
)
|
|
5128
5912
|
);
|
|
5129
5913
|
continue;
|
|
5130
5914
|
}
|
|
5131
5915
|
try {
|
|
5132
|
-
const result = await
|
|
5916
|
+
const result = await executePluginAgentKit(plugin, {
|
|
5133
5917
|
projectRoot,
|
|
5134
5918
|
force: options.force ?? false,
|
|
5135
5919
|
viteServer,
|
|
5136
5920
|
verbose: options.verbose
|
|
5137
5921
|
});
|
|
5138
|
-
if (result.
|
|
5922
|
+
if (result.agentKitCreated.length > 0) {
|
|
5139
5923
|
logger.important(chalk.green(` ${plugin.name}:`));
|
|
5140
|
-
for (const
|
|
5141
|
-
logger.important(chalk.gray(` ${
|
|
5924
|
+
for (const created of result.agentKitCreated) {
|
|
5925
|
+
logger.important(chalk.gray(` ${created}`));
|
|
5142
5926
|
}
|
|
5143
5927
|
if (result.message) {
|
|
5144
5928
|
logger.important(chalk.gray(` ${result.message}`));
|
|
5145
5929
|
}
|
|
5146
5930
|
}
|
|
5147
5931
|
} catch (error) {
|
|
5148
|
-
logger.warn(chalk.yellow(` ${plugin.name}:
|
|
5932
|
+
logger.warn(chalk.yellow(` ${plugin.name}: agent-kit skipped — ${error.message}`));
|
|
5149
5933
|
}
|
|
5150
5934
|
}
|
|
5151
5935
|
}
|
|
@@ -5175,7 +5959,7 @@ async function runAction(actionRef, options, projectRoot, initializeServices) {
|
|
|
5175
5959
|
process.exit(1);
|
|
5176
5960
|
}
|
|
5177
5961
|
const actionExport = actionRef.substring(slashIndex + 1);
|
|
5178
|
-
const
|
|
5962
|
+
const input2 = options.input ? JSON.parse(options.input) : {};
|
|
5179
5963
|
if (options.verbose) {
|
|
5180
5964
|
getLogger().info("Starting Vite for TypeScript support...");
|
|
5181
5965
|
}
|
|
@@ -5208,9 +5992,9 @@ async function runAction(actionRef, options, projectRoot, initializeServices) {
|
|
|
5208
5992
|
}
|
|
5209
5993
|
if (options.verbose) {
|
|
5210
5994
|
getLogger().info(`Executing action: ${matchedName}`);
|
|
5211
|
-
getLogger().info(`Input: ${JSON.stringify(
|
|
5995
|
+
getLogger().info(`Input: ${JSON.stringify(input2)}`);
|
|
5212
5996
|
}
|
|
5213
|
-
const result = await registry.execute(matchedName,
|
|
5997
|
+
const result = await registry.execute(matchedName, input2);
|
|
5214
5998
|
if (result.success) {
|
|
5215
5999
|
if (options.yaml) {
|
|
5216
6000
|
getLogger().important(YAML.stringify(result.data));
|
|
@@ -5323,6 +6107,69 @@ async function runParams(contractRef, options, projectRoot, initializeServices)
|
|
|
5323
6107
|
}
|
|
5324
6108
|
}
|
|
5325
6109
|
}
|
|
6110
|
+
function createInteractivePrompt() {
|
|
6111
|
+
return {
|
|
6112
|
+
async input(options) {
|
|
6113
|
+
return input({ message: options.message, validate: options.validate });
|
|
6114
|
+
},
|
|
6115
|
+
async confirm(options) {
|
|
6116
|
+
return confirm({ message: options.message, default: options.default });
|
|
6117
|
+
},
|
|
6118
|
+
async select(options) {
|
|
6119
|
+
return select({
|
|
6120
|
+
message: options.message,
|
|
6121
|
+
choices: options.choices.map((c) => ({ name: c.name, value: c.value }))
|
|
6122
|
+
});
|
|
6123
|
+
}
|
|
6124
|
+
};
|
|
6125
|
+
}
|
|
6126
|
+
function createAnswersFilePrompt(answers, pluginName) {
|
|
6127
|
+
return {
|
|
6128
|
+
async input(options) {
|
|
6129
|
+
const value = answers[options.key];
|
|
6130
|
+
if (value !== void 0)
|
|
6131
|
+
return value;
|
|
6132
|
+
throw new SetupNeedsAnswerError(pluginName, options.key, "input", options.message);
|
|
6133
|
+
},
|
|
6134
|
+
async confirm(options) {
|
|
6135
|
+
const value = answers[options.key];
|
|
6136
|
+
if (value !== void 0)
|
|
6137
|
+
return value === "true" || value === "yes";
|
|
6138
|
+
throw new SetupNeedsAnswerError(pluginName, options.key, "confirm", options.message);
|
|
6139
|
+
},
|
|
6140
|
+
async select(options) {
|
|
6141
|
+
const value = answers[options.key];
|
|
6142
|
+
if (value !== void 0)
|
|
6143
|
+
return value;
|
|
6144
|
+
throw new SetupNeedsAnswerError(
|
|
6145
|
+
pluginName,
|
|
6146
|
+
options.key,
|
|
6147
|
+
"select",
|
|
6148
|
+
options.message,
|
|
6149
|
+
options.choices
|
|
6150
|
+
);
|
|
6151
|
+
}
|
|
6152
|
+
};
|
|
6153
|
+
}
|
|
6154
|
+
function createDefaultPrompt(pluginName) {
|
|
6155
|
+
return {
|
|
6156
|
+
async input(options) {
|
|
6157
|
+
throw new SetupNeedsAnswerError(pluginName, options.key, "input", options.message);
|
|
6158
|
+
},
|
|
6159
|
+
async confirm(options) {
|
|
6160
|
+
throw new SetupNeedsAnswerError(pluginName, options.key, "confirm", options.message);
|
|
6161
|
+
},
|
|
6162
|
+
async select(options) {
|
|
6163
|
+
throw new SetupNeedsAnswerError(
|
|
6164
|
+
pluginName,
|
|
6165
|
+
options.key,
|
|
6166
|
+
"select",
|
|
6167
|
+
options.message,
|
|
6168
|
+
options.choices
|
|
6169
|
+
);
|
|
6170
|
+
}
|
|
6171
|
+
};
|
|
6172
|
+
}
|
|
5326
6173
|
async function runSetup(pluginFilter, options, projectRoot, initializeServices) {
|
|
5327
6174
|
let viteServer;
|
|
5328
6175
|
try {
|
|
@@ -5362,6 +6209,11 @@ async function runSetup(pluginFilter, options, projectRoot, initializeServices)
|
|
|
5362
6209
|
logger.info(chalk.yellow(`⚠️ ${name} init error: ${err.message}`));
|
|
5363
6210
|
}
|
|
5364
6211
|
}
|
|
6212
|
+
const interactive = options.interactive === true;
|
|
6213
|
+
let answersMap;
|
|
6214
|
+
if (options.answers) {
|
|
6215
|
+
answersMap = YAML.parse(fsSync.readFileSync(options.answers, "utf-8")) || {};
|
|
6216
|
+
}
|
|
5365
6217
|
let configured = 0;
|
|
5366
6218
|
let needsConfig = 0;
|
|
5367
6219
|
let errors = 0;
|
|
@@ -5370,11 +6222,14 @@ async function runSetup(pluginFilter, options, projectRoot, initializeServices)
|
|
|
5370
6222
|
if (plugin.setupDescription && options.verbose) {
|
|
5371
6223
|
logger.important(chalk.gray(` ${plugin.setupDescription}`));
|
|
5372
6224
|
}
|
|
6225
|
+
const prompt = interactive ? createInteractivePrompt() : answersMap ? createAnswersFilePrompt(answersMap, plugin.name) : createDefaultPrompt(plugin.name);
|
|
5373
6226
|
try {
|
|
5374
6227
|
const result = await executePluginSetup(plugin, {
|
|
5375
6228
|
projectRoot,
|
|
5376
6229
|
configDir,
|
|
5377
6230
|
force: options.force ?? false,
|
|
6231
|
+
interactive,
|
|
6232
|
+
prompt,
|
|
5378
6233
|
initError: initErrors.get(plugin.name),
|
|
5379
6234
|
viteServer,
|
|
5380
6235
|
verbose: options.verbose
|
|
@@ -5417,10 +6272,34 @@ async function runSetup(pluginFilter, options, projectRoot, initializeServices)
|
|
|
5417
6272
|
break;
|
|
5418
6273
|
}
|
|
5419
6274
|
} catch (error) {
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
logger.
|
|
6275
|
+
if (error instanceof SetupNeedsAnswerError) {
|
|
6276
|
+
needsConfig++;
|
|
6277
|
+
logger.important("");
|
|
6278
|
+
logger.important(chalk.yellow("setup-needs-answer:"));
|
|
6279
|
+
logger.important(chalk.yellow(` plugin: ${error.plugin}`));
|
|
6280
|
+
logger.important(chalk.yellow(` key: ${error.key}`));
|
|
6281
|
+
logger.important(chalk.yellow(` type: ${error.type}`));
|
|
6282
|
+
logger.important(chalk.yellow(` message: "${error.promptMessage}"`));
|
|
6283
|
+
if (error.choices) {
|
|
6284
|
+
logger.important(chalk.yellow(" choices:"));
|
|
6285
|
+
for (const c of error.choices) {
|
|
6286
|
+
logger.important(chalk.yellow(` - ${c.value}: ${c.name}`));
|
|
6287
|
+
}
|
|
6288
|
+
}
|
|
6289
|
+
logger.important("");
|
|
6290
|
+
logger.important(chalk.gray("Provide the answer via file:"));
|
|
6291
|
+
logger.important(chalk.gray(` jay-stack-cli setup --answers answers.yaml`));
|
|
6292
|
+
logger.important(chalk.gray(` answers.yaml format:`));
|
|
6293
|
+
logger.important(chalk.gray(` ${error.key}: "your-answer"`));
|
|
6294
|
+
logger.important("");
|
|
6295
|
+
logger.important(chalk.gray("Or run interactively:"));
|
|
6296
|
+
logger.important(chalk.gray(` jay-stack-cli setup --interactive`));
|
|
6297
|
+
} else {
|
|
6298
|
+
errors++;
|
|
6299
|
+
logger.important(chalk.red(` ❌ Setup failed: ${error.message}`));
|
|
6300
|
+
if (options.verbose) {
|
|
6301
|
+
logger.error(error.stack);
|
|
6302
|
+
}
|
|
5424
6303
|
}
|
|
5425
6304
|
}
|
|
5426
6305
|
logger.important("");
|
|
@@ -5501,11 +6380,11 @@ async function runCommand(commandRef, rawArgs, options, projectRoot, initializeS
|
|
|
5501
6380
|
}
|
|
5502
6381
|
process.exit(1);
|
|
5503
6382
|
}
|
|
5504
|
-
let
|
|
6383
|
+
let input2 = {};
|
|
5505
6384
|
if (command.metadata?.inputSchema) {
|
|
5506
6385
|
const flagDefs = commandSchemaToFlags(command.metadata.inputSchema);
|
|
5507
6386
|
const rawOptions = parseRawFlags(rawArgs, flagDefs);
|
|
5508
|
-
|
|
6387
|
+
input2 = parseInputFromFlags(rawOptions, command.metadata.inputSchema);
|
|
5509
6388
|
}
|
|
5510
6389
|
if (options.verbose) {
|
|
5511
6390
|
getLogger().info("Starting Vite for TypeScript support...");
|
|
@@ -5536,7 +6415,7 @@ async function runCommand(commandRef, rawArgs, options, projectRoot, initializeS
|
|
|
5536
6415
|
if (options.verbose) {
|
|
5537
6416
|
getLogger().info(`Executing ${command.pluginName}/${command.commandName}...`);
|
|
5538
6417
|
}
|
|
5539
|
-
const result = await executePluginCommand(command,
|
|
6418
|
+
const result = await executePluginCommand(command, input2, viteServer);
|
|
5540
6419
|
if (!result.success) {
|
|
5541
6420
|
process.exit(1);
|
|
5542
6421
|
}
|
|
@@ -5676,7 +6555,7 @@ program.command("validate-plugin").description("Validate a Jay Stack plugin pack
|
|
|
5676
6555
|
process.exit(1);
|
|
5677
6556
|
}
|
|
5678
6557
|
});
|
|
5679
|
-
program.command("setup [plugin]").description("Run plugin setup: config templates, credential validation, reference data").option("--force", "Force re-run (overwrite config templates and regenerate references)").option("-v, --verbose", "Show detailed output").action(async (plugin, options) => {
|
|
6558
|
+
program.command("setup [plugin]").description("Run plugin setup: config templates, credential validation, reference data").option("--force", "Force re-run (overwrite config templates and regenerate references)").option("--interactive", "Prompt for input via terminal (for humans)").option("--answers <file>", "Read answers from YAML file (for agents)").option("-v, --verbose", "Show detailed output").action(async (plugin, options) => {
|
|
5680
6559
|
await runSetup(plugin, options, process.cwd(), initializeServicesForCli);
|
|
5681
6560
|
});
|
|
5682
6561
|
program.command("agent-kit").description("Prepare agent kit: materialize contracts, generate references, write docs").option("-o, --output <dir>", "Output directory (default: agent-kit/materialized-contracts)").option("--yaml", "Output contract index as YAML to stdout").option("--list", "List contracts without writing files").option("--plugin <name>", "Filter to specific plugin").option("--dynamic-only", "Only process dynamic contracts").option("--force", "Force re-materialization").option("--no-references", "Skip reference data generation").option(
|