@enfyra/mcp-server 0.1.47 → 0.1.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/mcp-instructions.js +1 -1
- package/dist/lib/mcp-instructions.js.map +1 -1
- package/dist/lib/platform-operation-tools.d.ts +14 -0
- package/dist/lib/platform-operation-tools.js +224 -97
- package/dist/lib/platform-operation-tools.js.map +1 -1
- package/dist/lib/required-knowledge.js +6 -8
- package/dist/lib/required-knowledge.js.map +1 -1
- package/dist/lib/tool-routing.js +2 -2
- package/dist/lib/tool-routing.js.map +1 -1
- package/package.json +1 -1
|
@@ -22,7 +22,7 @@ export function buildMcpServerInstructions(apiBaseUrl, options = {}) {
|
|
|
22
22
|
'- For target sanity checks, call `get_enfyra_api_context`; do not load broad metadata only to confirm the API base.',
|
|
23
23
|
'- When the goal is clear but the tool path is not, call `discover_enfyra_workflows` with intent/risk/surface. Use `detail: "plan"` before writes, follow `primaryPath`, and treat `avoidTools` as hard negative-routing boundaries.',
|
|
24
24
|
'- Discover before deciding, then inspect narrowly. Use `inspect_table`, `inspect_route`, `inspect_feature`, `search_admin_extensions`, or `search_runtime_zone` for the exact table/route/feature/runtime artifact. Run broad discovery tools sequentially, not in parallel.',
|
|
25
|
-
'- Load examples only when needed with `get_enfyra_examples`. For OAuth use `get_enfyra_examples({ category: "oauth-setup" })`. For extension UI, call `get_extension_theme_contract` before writing or reviewing UI, use `build_extension_ui` after the extension acknowledgement for high-contract components such as drawers, modals, page shell headers/actions, permission gates, empty states, resource lists, FormEditor, Widget, menu/account panel registries, tabs, upload modals, and review, and call `get_theme_class_reference` only
|
|
25
|
+
'- Load examples only when needed with `get_enfyra_examples`. For OAuth use `get_enfyra_examples({ category: "oauth-setup" })`. For extension UI, call `get_extension_theme_contract` before writing or reviewing UI, use `build_extension_ui` after the extension acknowledgement for high-contract components and theme decisions such as drawers, modals, page shell headers/actions, permission gates, empty states, resource lists, FormEditor, Widget, menu/account panel registries, tabs, upload modals, theme classes/review, and full review, and call `get_theme_class_reference` only for theme internals or an explicitly requested full class map.',
|
|
26
26
|
'- Prefer the most specific business operation tool over raw metadata CRUD.',
|
|
27
27
|
'- Before any write, call `get_enfyra_required_knowledge({ scope })` with the right scope and pass returned ack keys into write tools. Choose scope by task domain:',
|
|
28
28
|
' • `schema` — table, column, relation, record CRUD, route permission, guard, field permission, column rule, cache reload, GraphQL enablement, log debug.',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-instructions.js","sourceRoot":"","sources":["../../src/lib/mcp-instructions.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,MAAM,UAAU,gBAAgB,CAAC,UAAU;IACzC,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACzD,OAAO;QACL,cAAc,EAAE,GAAG,IAAI,UAAU;QACjC,gBAAgB,EAAE,GAAG,IAAI,iBAAiB;KAC3C,CAAC;AACJ,CAAC;AAMD,MAAM,UAAU,0BAA0B,CAAC,UAAU,EAAE,UAAiC,EAAE;IACxF,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACzD,MAAM,EAAE,cAAc,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC1E,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,IAAI,CAAC;IAEvD,OAAO;QACL,eAAe;QACf,EAAE;QACF,gCAAgC,IAAI,KAAK;QACzC,wBAAwB,cAAc,YAAY,gBAAgB,KAAK;QACvE,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,EAAE;QACF,qBAAqB;QACrB,qJAAqJ;QACrJ,qHAAqH;QACrH,qOAAqO;QACrO,8QAA8Q;QAC9Q,
|
|
1
|
+
{"version":3,"file":"mcp-instructions.js","sourceRoot":"","sources":["../../src/lib/mcp-instructions.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,MAAM,UAAU,gBAAgB,CAAC,UAAU;IACzC,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACzD,OAAO;QACL,cAAc,EAAE,GAAG,IAAI,UAAU;QACjC,gBAAgB,EAAE,GAAG,IAAI,iBAAiB;KAC3C,CAAC;AACJ,CAAC;AAMD,MAAM,UAAU,0BAA0B,CAAC,UAAU,EAAE,UAAiC,EAAE;IACxF,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACzD,MAAM,EAAE,cAAc,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC1E,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,IAAI,CAAC;IAEvD,OAAO;QACL,eAAe;QACf,EAAE;QACF,gCAAgC,IAAI,KAAK;QACzC,wBAAwB,cAAc,YAAY,gBAAgB,KAAK;QACvE,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,EAAE;QACF,qBAAqB;QACrB,qJAAqJ;QACrJ,qHAAqH;QACrH,qOAAqO;QACrO,8QAA8Q;QAC9Q,ioBAAioB;QACjoB,4EAA4E;QAC5E,oKAAoK;QACpK,2JAA2J;QAC3J,uJAAuJ;QACvJ,8IAA8I;QAC9I,yDAAyD;QACzD,4EAA4E;QAC5E,sMAAsM;QACtM,mLAAmL;QACnL,8LAA8L;QAC9L,EAAE;QACF,0BAA0B;QAC1B,uHAAuH;QACvH,sLAAsL;QACtL,gLAAgL;QAChL,iLAAiL;QACjL,yLAAyL;QACzL,kKAAkK;QAClK,0HAA0H;QAC1H,4LAA4L;QAC5L,EAAE;QACF,+BAA+B;QAC/B,4HAA4H;QAC5H,4GAA4G;QAC5G,mIAAmI;QACnI,kJAAkJ;QAClJ,oNAAoN;QACpN,iKAAiK;QACjK,gIAAgI;QAChI,EAAE;QACF,+BAA+B;QAC/B,iIAAiI;QACjI,uJAAuJ;QACvJ,yOAAyO;QACzO,+JAA+J;QAC/J,iMAAiM;QACjM,wSAAwS;QACxS,qLAAqL;QACrL,kPAAkP;QAClP,+PAA+P;QAC/P,EAAE;QACF,4BAA4B;QAC5B,0MAA0M;QAC1M,oHAAoH;QACpH,kIAAkI;QAClI,8MAA8M;QAC9M,kNAAkN;QAClN,wLAAwL;QACxL,EAAE;QACF,uIAAuI;KACxI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -88,16 +88,30 @@ export declare function reviewExtensionUiContract(code: any): {
|
|
|
88
88
|
}[];
|
|
89
89
|
nextSteps: string[];
|
|
90
90
|
};
|
|
91
|
+
export declare function reviewExtensionThemeContract(code: any): {
|
|
92
|
+
action: string;
|
|
93
|
+
valid: boolean;
|
|
94
|
+
issueCount: number;
|
|
95
|
+
issues: {
|
|
96
|
+
severity: "error" | "warning";
|
|
97
|
+
rule: string;
|
|
98
|
+
message: string;
|
|
99
|
+
suggestion: string;
|
|
100
|
+
}[];
|
|
101
|
+
nextSteps: string[];
|
|
102
|
+
};
|
|
91
103
|
export declare function buildExtensionUiSnippet(kind: string, input?: AnyRecord): any;
|
|
92
104
|
export declare function validateExtensionCodeLocally(code: any): {
|
|
93
105
|
componentCasing: string;
|
|
94
106
|
fieldWidth: string;
|
|
107
|
+
themeContract: string;
|
|
95
108
|
};
|
|
96
109
|
export declare function validateExtensionCode(apiUrl: any, code: any, name: any): Promise<{
|
|
97
110
|
valid: boolean;
|
|
98
111
|
localChecks: {
|
|
99
112
|
componentCasing: string;
|
|
100
113
|
fieldWidth: string;
|
|
114
|
+
themeContract: string;
|
|
101
115
|
};
|
|
102
116
|
extensionId: any;
|
|
103
117
|
compiledLength: any;
|
|
@@ -881,6 +881,174 @@ export function reviewExtensionUiContract(code) {
|
|
|
881
881
|
: ['Snippet matches the checked modal/drawer contract rules. Still validate the final SFC before saving.'],
|
|
882
882
|
};
|
|
883
883
|
}
|
|
884
|
+
const THEME_CLASS_INTENTS = {
|
|
885
|
+
neutral_surface: {
|
|
886
|
+
classes: 'eapp-surface-card eapp-radius-panel border eapp-divider',
|
|
887
|
+
use: 'Ordinary cards, panels, KPI containers, list containers, detail blocks, and status blocks that should stay neutral.',
|
|
888
|
+
},
|
|
889
|
+
muted_surface: {
|
|
890
|
+
classes: 'eapp-surface-muted eapp-radius-panel',
|
|
891
|
+
use: 'Recessed areas, tracks, secondary panels, or subdued containers.',
|
|
892
|
+
},
|
|
893
|
+
flat_surface: {
|
|
894
|
+
classes: 'eapp-surface-flat',
|
|
895
|
+
use: 'Flush content areas that should follow the app surface without card chrome.',
|
|
896
|
+
},
|
|
897
|
+
hover_row: {
|
|
898
|
+
classes: 'eapp-surface-hover eapp-divider',
|
|
899
|
+
use: 'Clickable rows inside lists or tables.',
|
|
900
|
+
},
|
|
901
|
+
primary_identity: {
|
|
902
|
+
classes: 'eapp-primary-surface eapp-radius-panel border',
|
|
903
|
+
use: 'A selected/current entity, active plan, active package, or the single larger block representing current identity.',
|
|
904
|
+
},
|
|
905
|
+
primary_soft_icon_tile: {
|
|
906
|
+
classes: 'eapp-primary-soft eapp-icon-tile',
|
|
907
|
+
childClasses: 'eapp-primary-text',
|
|
908
|
+
use: 'Compact runtime-primary icon tiles, selected chips, and small identity accents.',
|
|
909
|
+
},
|
|
910
|
+
primary_progress: {
|
|
911
|
+
trackClasses: 'eapp-surface-muted eapp-radius-pill',
|
|
912
|
+
fillClasses: 'eapp-primary-solid',
|
|
913
|
+
use: 'Progress or meter fill controlled by the runtime primary color.',
|
|
914
|
+
},
|
|
915
|
+
status_success: {
|
|
916
|
+
classes: 'eapp-status-success-soft eapp-status-success-text eapp-status-success-border',
|
|
917
|
+
nuxtUi: { color: 'success', variant: 'soft' },
|
|
918
|
+
use: 'Small success/healthy badges, chips, or icons only.',
|
|
919
|
+
},
|
|
920
|
+
status_warning: {
|
|
921
|
+
classes: 'eapp-status-warning-soft eapp-status-warning-text eapp-status-warning-border',
|
|
922
|
+
nuxtUi: { color: 'warning', variant: 'soft' },
|
|
923
|
+
use: 'Small warning/attention badges, chips, or icons only.',
|
|
924
|
+
},
|
|
925
|
+
status_danger: {
|
|
926
|
+
classes: 'eapp-status-danger-soft eapp-status-danger-text eapp-status-danger-border',
|
|
927
|
+
nuxtUi: { color: 'error', variant: 'soft' },
|
|
928
|
+
use: 'Small danger/error/destructive badges, chips, or icons only.',
|
|
929
|
+
},
|
|
930
|
+
status_info: {
|
|
931
|
+
classes: 'eapp-status-info-soft eapp-status-info-text eapp-status-info-border',
|
|
932
|
+
nuxtUi: { color: 'info', variant: 'soft' },
|
|
933
|
+
use: 'Small informational badges, chips, or icons only.',
|
|
934
|
+
},
|
|
935
|
+
primary_action: {
|
|
936
|
+
nuxtUi: { color: 'primary', variant: 'solid' },
|
|
937
|
+
use: 'The single main action for the current scope.',
|
|
938
|
+
},
|
|
939
|
+
secondary_action: {
|
|
940
|
+
nuxtUi: { color: 'neutral', variant: 'outline' },
|
|
941
|
+
use: 'Visible secondary actions, refresh, filters, cancel, and navigation alternatives.',
|
|
942
|
+
},
|
|
943
|
+
ghost_navigation_action: {
|
|
944
|
+
nuxtUi: { color: 'neutral', variant: 'ghost' },
|
|
945
|
+
use: 'Back/navigation/icon actions that should not compete with the primary action.',
|
|
946
|
+
},
|
|
947
|
+
danger_action: {
|
|
948
|
+
nuxtUi: { color: 'error', variant: 'solid' },
|
|
949
|
+
use: 'Final destructive actions such as Delete or Remove.',
|
|
950
|
+
},
|
|
951
|
+
divider: {
|
|
952
|
+
classes: 'eapp-divider',
|
|
953
|
+
listClasses: 'eapp-divide-y',
|
|
954
|
+
use: 'Borders and row separators in extension UI.',
|
|
955
|
+
},
|
|
956
|
+
text: {
|
|
957
|
+
primary: 'eapp-text-primary',
|
|
958
|
+
secondary: 'eapp-text-secondary',
|
|
959
|
+
tertiary: 'eapp-text-tertiary',
|
|
960
|
+
quaternary: 'eapp-text-quaternary',
|
|
961
|
+
use: 'Copy hierarchy in extension UI.',
|
|
962
|
+
},
|
|
963
|
+
};
|
|
964
|
+
function buildExtensionThemeClasses(input = {}) {
|
|
965
|
+
const intent = String(input.intent || '').trim();
|
|
966
|
+
if (!intent || !(intent in THEME_CLASS_INTENTS)) {
|
|
967
|
+
return {
|
|
968
|
+
action: 'extension_theme_classes_listed',
|
|
969
|
+
validIntents: Object.keys(THEME_CLASS_INTENTS),
|
|
970
|
+
note: 'Call again with one intent to get the exact classes/props for that theme contract.',
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
const contract = THEME_CLASS_INTENTS[intent];
|
|
974
|
+
return {
|
|
975
|
+
action: 'extension_theme_classes_built',
|
|
976
|
+
intent,
|
|
977
|
+
contract,
|
|
978
|
+
hardRules: [
|
|
979
|
+
'Do not use raw CSS variable utilities such as text-[var(...)], bg-[var(...)], or border-[var(...)] in extension templates.',
|
|
980
|
+
'Do not use hardcoded Tailwind palettes such as bg-violet-*, text-cyan-*, bg-green-*, dark:bg-zinc-*, or hex/rgb/hsl colors.',
|
|
981
|
+
'Use status classes only for compact badges/icons/short text; keep large panels neutral.',
|
|
982
|
+
],
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
function collectExtensionThemeIssues(code) {
|
|
986
|
+
const source = String(code || '');
|
|
987
|
+
const issues = [];
|
|
988
|
+
const push = (severity, rule, message, suggestion) => issues.push({ severity, rule, message, suggestion });
|
|
989
|
+
const concretePalettes = [
|
|
990
|
+
'slate', 'gray', 'zinc', 'neutral', 'stone',
|
|
991
|
+
'red', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo',
|
|
992
|
+
'violet', 'purple', 'fuchsia', 'pink', 'rose',
|
|
993
|
+
].join('|');
|
|
994
|
+
const paletteClassPattern = new RegExp(`(?:^|[\\s"'])(?:dark:)?(?:bg|text|border|ring|divide|from|via|to)-(${concretePalettes})-(?:\\d{2,3}|950)(?:\\/\\d+)?`, 'i');
|
|
995
|
+
const rawCssVarPattern = /\b(?:bg|text|border|ring|divide|from|via|to)-\[\s*var\(--/i;
|
|
996
|
+
const neutralSemanticClassPattern = /\b(?:bg-default|bg-muted|border-default|divide-default|text-muted|text-dimmed)\b/;
|
|
997
|
+
const concreteNuxtUiColors = [
|
|
998
|
+
'slate', 'gray', 'zinc', 'stone',
|
|
999
|
+
'red', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo',
|
|
1000
|
+
'violet', 'purple', 'fuchsia', 'pink', 'rose',
|
|
1001
|
+
].join('|');
|
|
1002
|
+
const concreteNuxtColorPattern = new RegExp(`\\bcolor\\s*=\\s*["'](${concreteNuxtUiColors})["']`, 'i');
|
|
1003
|
+
if (rawCssVarPattern.test(source)) {
|
|
1004
|
+
push('error', 'raw-css-var-utility', 'Raw CSS variable utility classes are not allowed in generated extension templates.', 'Use eapp-* class tokens or Nuxt UI semantic color props by intent.');
|
|
1005
|
+
}
|
|
1006
|
+
if (paletteClassPattern.test(source)) {
|
|
1007
|
+
push('error', 'hardcoded-tailwind-palette', 'Hardcoded Tailwind palette classes are not allowed in themeable extension UI.', 'Use eapp-surface-*, eapp-primary-*, eapp-status-*, or Nuxt UI semantic colors.');
|
|
1008
|
+
}
|
|
1009
|
+
if (neutralSemanticClassPattern.test(source)) {
|
|
1010
|
+
push('error', 'nuxt-neutral-class', 'Nuxt UI neutral shortcut classes are not part of the extension theme contract.', 'Use eapp-surface-* and eapp-text-* classes instead.');
|
|
1011
|
+
}
|
|
1012
|
+
if (concreteNuxtColorPattern.test(source)) {
|
|
1013
|
+
push('error', 'concrete-nuxt-color', 'Concrete Nuxt UI palette colors are not allowed in generated extension UI.', 'Use color="primary|neutral|success|warning|error|info" by semantic intent.');
|
|
1014
|
+
}
|
|
1015
|
+
if (/#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})\b/i.test(source) || /\b(?:rgba?|hsla?|oklch|lab|lch)\s*\(/i.test(source)) {
|
|
1016
|
+
push('error', 'hardcoded-color-value', 'Hardcoded color values are not allowed in extension UI.', 'Use the Enfyra theme class contract instead of hex/rgb/hsl/oklch/lab/lch values.');
|
|
1017
|
+
}
|
|
1018
|
+
if (/\bstyle\s*=\s*["'][^"']*(?:color|background(?:-color)?|border-color)\s*:/i.test(source)) {
|
|
1019
|
+
push('error', 'inline-color-style', 'Inline color styles bypass the app theme contract.', 'Use eapp-* classes or Nuxt UI semantic props instead of inline color/background/border-color.');
|
|
1020
|
+
}
|
|
1021
|
+
if (/\bgradient\s*:\s*['"](?!none['"])[^'"]+['"]/.test(source) || /\bgradient\s*=\s*['"](?!none['"])[^'"]+['"]/.test(source)) {
|
|
1022
|
+
push('error', 'page-header-gradient', 'Generated operational extension pages must use PageHeader gradient "none" unless the user explicitly requests decoration.', 'Set gradient: "none" in usePageHeaderRegistry or omit decorative gradients.');
|
|
1023
|
+
}
|
|
1024
|
+
if (/<(?:article|section|div)\b[^>]*class=["'][^"']*(?:eapp-status-(?:success|warning|danger|info)-soft|bg-(?:success|warning|error|info)(?:\b|\/))/i.test(source)) {
|
|
1025
|
+
push('warning', 'large-status-surface', 'Status color appears on a large container.', 'Keep large panels neutral and put status color on a compact UBadge/icon/short text inside.');
|
|
1026
|
+
}
|
|
1027
|
+
const primaryActionCount = (source.match(/\bcolor\s*=\s*["']primary["']/g) || []).length;
|
|
1028
|
+
if (primaryActionCount > 2) {
|
|
1029
|
+
push('warning', 'primary-overuse', 'Many primary-colored controls were found.', 'Use primary only for the main action or identity accent; use neutral variants for secondary actions.');
|
|
1030
|
+
}
|
|
1031
|
+
const primarySurfaceCount = (source.match(/\beapp-primary-surface\b/g) || []).length;
|
|
1032
|
+
if (primarySurfaceCount > 3) {
|
|
1033
|
+
push('warning', 'primary-surface-overuse', 'eapp-primary-surface appears many times.', 'Use eapp-primary-surface only for selected/current identity blocks, not every card in a list/grid.');
|
|
1034
|
+
}
|
|
1035
|
+
if (/<(?:article|section|div)\b[^>]*class=["'][^"']*\bborder\b(?![^"']*\beapp-divider\b)/i.test(source)) {
|
|
1036
|
+
push('warning', 'bare-border-token', 'A bordered panel is missing eapp-divider.', 'Pair intentional borders with eapp-divider so borders follow the app theme contract.');
|
|
1037
|
+
}
|
|
1038
|
+
return issues;
|
|
1039
|
+
}
|
|
1040
|
+
export function reviewExtensionThemeContract(code) {
|
|
1041
|
+
const issues = collectExtensionThemeIssues(code);
|
|
1042
|
+
return {
|
|
1043
|
+
action: 'extension_theme_contract_reviewed',
|
|
1044
|
+
valid: issues.every((issue) => issue.severity !== 'error'),
|
|
1045
|
+
issueCount: issues.length,
|
|
1046
|
+
issues,
|
|
1047
|
+
nextSteps: issues.length
|
|
1048
|
+
? ['Use build_extension_ui kind=theme_classes to choose classes/props by intent, then patch/update the extension.']
|
|
1049
|
+
: ['Snippet matches the checked theme contract rules. Still validate the final SFC before saving.'],
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
884
1052
|
export function buildExtensionUiSnippet(kind, input = {}) {
|
|
885
1053
|
let result;
|
|
886
1054
|
switch (kind) {
|
|
@@ -920,11 +1088,28 @@ export function buildExtensionUiSnippet(kind, input = {}) {
|
|
|
920
1088
|
case 'upload_modal':
|
|
921
1089
|
result = buildExtensionUploadModalSnippet(input);
|
|
922
1090
|
break;
|
|
1091
|
+
case 'theme_classes':
|
|
1092
|
+
result = buildExtensionThemeClasses(input);
|
|
1093
|
+
break;
|
|
1094
|
+
case 'theme_review':
|
|
1095
|
+
if (!input?.code) {
|
|
1096
|
+
throw new Error('build_extension_ui kind=theme_review requires input.code.');
|
|
1097
|
+
}
|
|
1098
|
+
result = reviewExtensionThemeContract(input.code);
|
|
1099
|
+
break;
|
|
923
1100
|
case 'review':
|
|
924
1101
|
if (!input?.code) {
|
|
925
1102
|
throw new Error('build_extension_ui kind=review requires input.code.');
|
|
926
1103
|
}
|
|
927
|
-
|
|
1104
|
+
const uiReview = reviewExtensionUiContract(input.code);
|
|
1105
|
+
const themeReview = reviewExtensionThemeContract(input.code);
|
|
1106
|
+
result = {
|
|
1107
|
+
action: 'extension_ui_and_theme_contract_reviewed',
|
|
1108
|
+
valid: uiReview.valid && themeReview.valid,
|
|
1109
|
+
issueCount: uiReview.issueCount + themeReview.issueCount,
|
|
1110
|
+
ui: uiReview,
|
|
1111
|
+
theme: themeReview,
|
|
1112
|
+
};
|
|
928
1113
|
break;
|
|
929
1114
|
default:
|
|
930
1115
|
throw new Error(`Unsupported extension UI builder kind: ${kind}`);
|
|
@@ -946,53 +1131,36 @@ function getExtensionThemeContract() {
|
|
|
946
1131
|
'The extension is already mounted inside the Enfyra app shell. Do not add a duplicate page header, centered page wrapper, or root-level page padding.',
|
|
947
1132
|
'Page extensions should be full-bleed, responsive, and split large operations into focused pages or UTabs.',
|
|
948
1133
|
'Use usePageHeaderRegistry for the shell title and useHeaderActionRegistry/useSubHeaderActionRegistry for page actions.',
|
|
949
|
-
'Use
|
|
1134
|
+
'Use build_extension_ui kind=menu_notification for sidebar menu notification registration snippets.',
|
|
950
1135
|
'For shell menu notifications, first decide the signal source. Use a count only when the source already owns an exact count, such as a notification summary endpoint or bounded unread-notification query. Use a dot when a realtime event only proves that something new exists. Do not poll a domain list such as messages, tickets, orders, or jobs solely to decorate the menu; the destination page owns domain fetching.',
|
|
951
|
-
'Use
|
|
1136
|
+
'Use build_extension_ui kind=account_panel_item for account panel row registration snippets.',
|
|
952
1137
|
'For detail/form workflows that should stay left-aligned with empty space on the right, wrap the body in eapp-page-constrained; use eapp-page-constrained-wide only when the workflow genuinely needs more width.',
|
|
953
1138
|
'Card/list grids inside the default shell must account for the 280px desktop sidebar. Do not switch general card grids to three columns at lg; use md:grid-cols-2 xl:grid-cols-3 unless a local container proves three columns have enough width.',
|
|
954
1139
|
],
|
|
955
1140
|
theme: [
|
|
956
|
-
'
|
|
957
|
-
'
|
|
958
|
-
'
|
|
959
|
-
'
|
|
960
|
-
'The app runs on Tailwind v4. Short Tailwind color utilities are the canonical way to apply contract colors and ARE allowed: bg-primary, text-primary, border-primary, ring-primary, bg-success, text-error, etc., including opacity modifiers (bg-primary/10, ring-success/20) which v4 resolves via color-mix. They are generated from the token-backed config (primary -> --md-primary runtime, success/error/warning/info -> --st-* status, secondary -> --md-tertiary) so they follow the color picker and dark theme. Do NOT use raw CSS-variable utilities (text-[var(--*)], bg-[var(--*)], border-[var(--*)]), hardcoded hex, inline style colors, or concrete palette substitution (color="violet", from-cyan-*, text-violet-*, bg-green-*, bg-emerald-*, text-green-*, dark:bg-zinc-950). For intent surfaces with no Tailwind equivalent (selected identity block, soft/solid/subtle surface, divider, radius, modal chrome) use the eapp-* classes below; the app owns how they map to the active color picker value.',
|
|
961
|
-
'Use UButton color="primary" only for the single main action for the current scope. Refresh, back, navigation, filters, and secondary actions should be neutral variants unless they are the main mutation.',
|
|
962
|
-
'PageHeader gradient must be "none" for generated operational extensions unless the user explicitly asks for a decorative page accent. Do not hardcode cyan, violet, purple, blue, or green PageHeader gradients to force color variety.',
|
|
963
|
-
'Do not inject global CSS, create theme guards, redefine the app palette, or solve one extension by overriding the whole app shell.',
|
|
964
|
-
'For panels/cards, prefer eapp-surface-card, eapp-surface-hover, eapp-surface-muted, eapp-surface-flat, and eapp-divide-y. Use eapp-text-primary, eapp-text-secondary, eapp-text-tertiary, or eapp-text-quaternary for copy.',
|
|
965
|
-
'Never use Nuxt UI neutral semantic classes such as bg-default, bg-muted, border-default, divide-default, text-muted, text-dimmed, or hardcoded dark palettes such as dark:bg-zinc-950, bg-slate-*, text-gray-*, border-black, or black.',
|
|
966
|
-
'Never use bare border/divide-y for panels or rows: pair borders with eapp-divider or use eapp-divide-y for row separators.',
|
|
967
|
-
'Use radius tokens or mapped rounded utilities consistently: --radius-card for cards, --radius-panel for nested panels, --radius-control for buttons/inputs, --radius-subcontrol for compact inner controls, and --radius-pill for pills.',
|
|
968
|
-
'Status colors must remain readable in both themes and must stay scoped to badges, small icons, or short status text. Use UBadge/UAlert semantic colors or eapp-status-success-soft/text/border, eapp-status-warning-soft/text/border, eapp-status-danger-soft/text/border, eapp-status-info-soft/text/border, and eapp-status-neutral-soft/text/border. Do not read --badge-* variables directly from extension templates. Do not color large panels, alert-like success blocks, KPI cards, list containers, or reconciliation/attention blocks green/yellow/red because the status is good/warning/error; use neutral app surfaces for the block and place a small status badge/icon inside.',
|
|
969
|
-
'Keep dark and light contrast comparable. Do not make dark mode more neon or lower-contrast than light mode; prefer muted soft backgrounds with clear text and visible borders.',
|
|
1141
|
+
'Do not choose theme classes from memory. Decide the UI intent, then call build_extension_ui kind=theme_classes with that intent to receive the exact class/prop contract.',
|
|
1142
|
+
'Call build_extension_ui kind=theme_review or kind=review before saving extension UI; validate_extension_code and extension write tools also reject hard theme violations.',
|
|
1143
|
+
'Never fix one extension by injecting global CSS, redefining the app palette, or adding theme guards.',
|
|
1144
|
+
'Use get_theme_class_reference only when debugging theme internals or when the user explicitly asks for the full theme/class map.',
|
|
970
1145
|
],
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
},
|
|
990
|
-
{
|
|
991
|
-
intent: 'Selected/current entity or user-selected option where the whole block is the active identity.',
|
|
992
|
-
colorContract: 'Use runtime primary identity surface, but subtly.',
|
|
993
|
-
use: 'eapp-primary-surface plus optional eapp-primary-surface-hover; eapp-primary-soft/text for the icon or chip inside.',
|
|
994
|
-
avoid: 'Do not use eapp-primary-surface for every card in a grid/list or as a broad page background.',
|
|
995
|
-
},
|
|
1146
|
+
themeIntents: [
|
|
1147
|
+
'neutral_surface',
|
|
1148
|
+
'muted_surface',
|
|
1149
|
+
'flat_surface',
|
|
1150
|
+
'hover_row',
|
|
1151
|
+
'primary_identity',
|
|
1152
|
+
'primary_soft_icon_tile',
|
|
1153
|
+
'primary_progress',
|
|
1154
|
+
'status_success',
|
|
1155
|
+
'status_warning',
|
|
1156
|
+
'status_danger',
|
|
1157
|
+
'status_info',
|
|
1158
|
+
'primary_action',
|
|
1159
|
+
'secondary_action',
|
|
1160
|
+
'ghost_navigation_action',
|
|
1161
|
+
'danger_action',
|
|
1162
|
+
'divider',
|
|
1163
|
+
'text',
|
|
996
1164
|
],
|
|
997
1165
|
components: [
|
|
998
1166
|
'Use Nuxt UI/eApp components for normal controls: UButton, UInput, UTextarea, USelectMenu/USelect, USwitch, UCheckbox, UTabs, UBadge, UModal, and CommonDrawer when available.',
|
|
@@ -1000,7 +1168,7 @@ function getExtensionThemeContract() {
|
|
|
1000
1168
|
'Buttons should have stable geometry: hover may change color, border, or shadow but must not move the button or resize its content. Disabled buttons keep disabled cursor/visual state.',
|
|
1001
1169
|
'Inputs and textareas should not add hover movement or decorative hover states; focus, invalid, disabled, and loading states must be explicit.',
|
|
1002
1170
|
'For drawers, modals, page shell headers/actions, permission gates, empty states, resource lists, form editors, widgets, menu/account panel registries, tabs, and upload modals, call build_extension_ui with the matching kind after extension acknowledgement before patching raw Vue.',
|
|
1003
|
-
'Use build_extension_ui kind=review before saving generated snippets that include high-contract UI or native buttons.',
|
|
1171
|
+
'Use build_extension_ui kind=theme_classes for theme classes by intent, and kind=theme_review or kind=review before saving generated snippets that include high-contract UI, theme classes, or native buttons.',
|
|
1004
1172
|
'Extension validation rejects UInput, UTextarea, USelect, USelectMenu, UInputMenu, UInputNumber, UInputTags, UInputTime, and UInputDate without class="w-full" unless marked data-compact or data-inline.',
|
|
1005
1173
|
'Use UBadge or token-backed badge spans for status. Keep badges legible in both themes with tokenized background, text, and border.',
|
|
1006
1174
|
],
|
|
@@ -1031,7 +1199,7 @@ function getExtensionThemeContract() {
|
|
|
1031
1199
|
'The builder owns numeric id usage, reactive prop/event wiring, and page/widget ownership warnings.',
|
|
1032
1200
|
],
|
|
1033
1201
|
actionButtons: [
|
|
1034
|
-
'Use build_extension_ui kind=review for generated snippets with native buttons.',
|
|
1202
|
+
'Use build_extension_ui kind=review for generated snippets with native buttons or theme classes.',
|
|
1035
1203
|
'Validation/review catches missing type="button" and high-contract component mistakes before saving.',
|
|
1036
1204
|
],
|
|
1037
1205
|
},
|
|
@@ -1051,60 +1219,12 @@ function getExtensionThemeContract() {
|
|
|
1051
1219
|
'UI checks are only guidance; handlers/hooks must independently enforce owner/root-admin authorization.',
|
|
1052
1220
|
'Use the most specific business route or MCP tool. Do not write directly to raw tables when a domain route exists.',
|
|
1053
1221
|
],
|
|
1054
|
-
patternExamples: [
|
|
1055
|
-
{
|
|
1056
|
-
useWhen: 'Ordinary KPI, metric, or summary card where the whole card is not selected/current identity.',
|
|
1057
|
-
use: 'Neutral card surface; put runtime-primary only on a small identity icon tile, progress fill, or main CTA inside the card.',
|
|
1058
|
-
snippet: '<article class="eapp-surface-card p-4"><div class="flex items-start justify-between gap-3"><div><p class="text-sm eapp-text-tertiary">Metric</p><p class="mt-2 text-2xl font-semibold eapp-text-primary">{{ value }}</p></div><span class="eapp-primary-soft eapp-icon-tile"><UIcon name="lucide:square-stack" class="size-5 eapp-primary-text" /></span></div></article>',
|
|
1059
|
-
},
|
|
1060
|
-
{
|
|
1061
|
-
useWhen: 'Selected/current entity, active plan, chosen package, or the single block that represents the active identity.',
|
|
1062
|
-
use: 'eapp-primary-surface for the selected/current block, with eapp-primary-soft/text for compact icon parts.',
|
|
1063
|
-
snippet: '<article class="eapp-primary-surface eapp-primary-surface-hover eapp-radius-panel border p-4"><div class="flex items-center gap-3"><span class="eapp-primary-soft eapp-icon-tile"><UIcon name="lucide:box" class="size-5 eapp-primary-text" /></span><div><p class="font-semibold eapp-text-primary">{{ name }}</p><p class="text-sm eapp-text-tertiary">Currently selected</p></div></div></article>',
|
|
1064
|
-
},
|
|
1065
|
-
{
|
|
1066
|
-
useWhen: 'Progress, active tab indicator, selected segment fill, or primary visual meter.',
|
|
1067
|
-
use: 'Neutral track plus eapp-primary-solid fill so the app color picker controls the fill.',
|
|
1068
|
-
snippet: '<div class="h-1.5 overflow-hidden eapp-radius-pill eapp-surface-muted"><div class="eapp-primary-solid h-full" :style="{ width: progressWidth }"></div></div>',
|
|
1069
|
-
},
|
|
1070
|
-
{
|
|
1071
|
-
useWhen: 'Success, warning, error, info, healthy, running, failed, pending, or attention status.',
|
|
1072
|
-
use: 'UBadge/status badge tokens and optionally a small icon only. Keep large alert/panel/card backgrounds neutral unless the whole block is an identity block.',
|
|
1073
|
-
snippet: '<section class="eapp-surface-card p-4"><div class="flex items-center justify-between gap-3"><p class="font-semibold eapp-text-primary">Reconciliation</p><UBadge color="success" variant="soft">Healthy</UBadge></div><p class="mt-1 text-sm eapp-text-tertiary">Latest report found no mismatches.</p></section>',
|
|
1074
|
-
},
|
|
1075
|
-
{
|
|
1076
|
-
useWhen: 'List rows, table-like records, history rows, and secondary navigation rows.',
|
|
1077
|
-
use: 'Neutral row surface, tokenized dividers, hover surface-muted, with small status/identity chips inside.',
|
|
1078
|
-
snippet: '<div class="eapp-surface-card eapp-divide-y"><button class="flex w-full items-center justify-between px-4 py-3 text-left eapp-surface-hover"><span class="text-sm font-medium eapp-text-primary">{{ row.name }}</span><UBadge color="neutral" variant="soft">{{ row.state }}</UBadge></button></div>',
|
|
1079
|
-
},
|
|
1080
|
-
{
|
|
1081
|
-
useWhen: 'Primary action for the current scope, such as create/save/apply/open-current.',
|
|
1082
|
-
use: 'UButton color="primary" variant="solid"; secondary actions stay neutral.',
|
|
1083
|
-
snippet: '<div class="flex justify-end gap-2"><UButton color="neutral" variant="outline">Cancel</UButton><UButton color="primary" variant="solid" icon="lucide:save">Save</UButton></div>',
|
|
1084
|
-
},
|
|
1085
|
-
],
|
|
1086
|
-
compactExample: '<template><section class="min-h-full w-full space-y-4"><article class="eapp-surface-card p-4"><div class="flex items-start justify-between gap-3"><div><p class="text-sm eapp-text-tertiary">Neutral KPI</p><p class="mt-2 text-2xl font-semibold eapp-text-primary">24</p></div><span class="eapp-primary-soft eapp-icon-tile"><UIcon name="lucide:square-stack" class="size-5 eapp-primary-text" /></span></div><div class="mt-3 h-1.5 overflow-hidden eapp-radius-pill eapp-surface-muted"><div class="eapp-primary-solid h-full w-1/2"></div></div></article><section class="eapp-surface-card p-4"><div class="flex items-center justify-between gap-3"><p class="font-semibold eapp-text-primary">Status block stays neutral</p><UBadge color="success" variant="soft">Healthy</UBadge></div></section></section></template>',
|
|
1087
1222
|
shellNotificationContract: {
|
|
1088
1223
|
menu: 'useMenuNotificationRegistry().register({ id, target: { id?, path?, route? }, value?, color?, title?, order? }). value renders a count/chip; omitting value renders a dot. Parent menus sum numeric child values.',
|
|
1089
1224
|
accountPanel: 'useAccountPanelRegistry().register({ id, label, description, icon, count?, badge?, badgeColor?, expanded?, onToggle?, contentComponent? }). count is preferred over badge and the account trigger sums numeric visible item counts, capped at 99+.',
|
|
1090
1225
|
lifecycle: 'Register from global extensions for app-wide notification state; stable ids replace previous registrations and component-owned registrations are removed on unmount.',
|
|
1091
1226
|
reasoning: 'Counts and dots are different promises. A count says the shell knows an exact or bounded number from an appropriate notification/summary source. A dot says the shell only knows that new attention exists. Avoid fetching the destination domain list just to make a menu badge more precise.',
|
|
1092
1227
|
},
|
|
1093
|
-
contractAuthority: [
|
|
1094
|
-
'This is the authoritative Enfyra theme & color contract. Source of truth: documents/app/theme-color-contract.md. The app owns color through app/utils/primary-colors.ts (Material You seed-to-role generation), app/assets/css/theme.css (semantic variables and Nuxt UI ramps), app/assets/css/main.css (extension-safe semantic utilities), and app/app.config.ts (Nuxt UI component mapping). Pages and extensions only CONSUME classes/Nuxt UI props; they never define colors.',
|
|
1095
|
-
'Every color flows from two base layers: --md-* (Material You, runtime primary picker) and --st-* (status). Runtime primary roles are generated with SchemeTonalSpot. Success/warning/info stay fixed status quarts; error follows the generated Material error role through the single --danger-* lane. All Nuxt UI semantic colors (primary/secondary/success/warning/error/info/neutral) are re-pointed to these, so Nuxt UI is used per its docs but colors are decided by Enfyra. This applies to the shell, system pages, and compiled dynamic extensions.',
|
|
1096
|
-
'Call get_theme_class_reference for the full class->variable->Nuxt UI table when you need the exact class name or variable.',
|
|
1097
|
-
],
|
|
1098
|
-
classReference: {
|
|
1099
|
-
surfaces: ['eapp-surface-card (default card; --card-bg/--card-border)', 'eapp-surface-muted (recessed/track; --surface-muted)', 'eapp-surface-flat (flush; --surface-default)', 'eapp-surface-hover (clickable row hover)'],
|
|
1100
|
-
text: ['eapp-text-primary', 'eapp-text-secondary', 'eapp-text-tertiary', 'eapp-text-quaternary'],
|
|
1101
|
-
primaryIdentity: ['eapp-primary-solid (solid fill/meter)', 'eapp-primary-text (inline/icon)', 'eapp-primary-soft + -hover (compact chip/tile)', 'eapp-primary-subtle (stronger selected fill)', 'eapp-primary-surface + -hover (large selected identity block)', 'eapp-primary-border', 'eapp-primary-ring'],
|
|
1102
|
-
status: ['eapp-status-success|warning|danger|info|neutral -soft/-text/-border (badges/small icons/short text only)'],
|
|
1103
|
-
radius: ['eapp-radius-card', 'eapp-radius-panel', 'eapp-radius-control', 'eapp-radius-subcontrol', 'eapp-radius-pill'],
|
|
1104
|
-
dividers: ['eapp-divider', 'eapp-divide-y'],
|
|
1105
|
-
modal: ['eapp-modal-surface (modal content chrome; never surface-card)'],
|
|
1106
|
-
nuxtUiMapping: 'primary=--md-primary(runtime), secondary=--md-tertiary(runtime), success=--st-success, warning=--st-warning, error=--st-error, info=--st-info, neutral=neutral surfaces',
|
|
1107
|
-
},
|
|
1108
1228
|
};
|
|
1109
1229
|
}
|
|
1110
1230
|
function getThemeClassReference() {
|
|
@@ -1275,7 +1395,12 @@ export function validateExtensionCodeLocally(code) {
|
|
|
1275
1395
|
const first = missingFullWidthFields[0];
|
|
1276
1396
|
throw new Error(`Invalid extension field width: <${first.tag}> must include class="w-full" in Enfyra extensions unless it is intentionally compact with data-compact or data-inline. First offending snippet: ${first.snippet}`);
|
|
1277
1397
|
}
|
|
1278
|
-
|
|
1398
|
+
const themeReview = reviewExtensionThemeContract(code);
|
|
1399
|
+
const firstThemeError = themeReview.issues.find((issue) => issue.severity === 'error');
|
|
1400
|
+
if (firstThemeError) {
|
|
1401
|
+
throw new Error(`Invalid extension theme contract: ${firstThemeError.message} Rule: ${firstThemeError.rule}. ${firstThemeError.suggestion}`);
|
|
1402
|
+
}
|
|
1403
|
+
return { componentCasing: 'passed', fieldWidth: 'passed', themeContract: 'passed' };
|
|
1279
1404
|
}
|
|
1280
1405
|
export async function validateExtensionCode(apiUrl, code, name) {
|
|
1281
1406
|
const localChecks = validateExtensionCodeLocally(code);
|
|
@@ -2464,8 +2589,8 @@ async function runExtensionWorkflow(apiUrl, opts) {
|
|
|
2464
2589
|
'Call get_extension_theme_contract before generating or reviewing extension UI.',
|
|
2465
2590
|
'useApi returns refs { data, error, pending, status } plus execute/refresh; it does not auto-run. Pass query/body/filter/deep/aggregate as objects or computed objects, not JSON strings.',
|
|
2466
2591
|
'useNotify exposes success/error/warning/info(title, description?) async helpers; do not pass Nuxt toast object payloads to it.',
|
|
2467
|
-
'For high-contract UI, call build_extension_ui after extension acknowledgement before patching raw Vue: drawer, modal, page shell, permission gate, empty state, resource list, form editor, widget, menu notification, account panel item, tabs, upload modal, or review.',
|
|
2468
|
-
'Use build_extension_ui kind=review before saving generated snippets that include drawers, modals, fields, lists, tabs, upload modals, shell registry code, or native buttons.',
|
|
2592
|
+
'For high-contract UI, call build_extension_ui after extension acknowledgement before patching raw Vue: drawer, modal, page shell, permission gate, empty state, resource list, form editor, widget, menu notification, account panel item, tabs, upload modal, theme classes, theme review, or full review.',
|
|
2593
|
+
'Use build_extension_ui kind=theme_classes for theme classes by intent, and kind=theme_review or kind=review before saving generated snippets that include drawers, modals, fields, lists, tabs, upload modals, shell registry code, theme classes, or native buttons.',
|
|
2469
2594
|
'Extension validation rejects common field controls without class="w-full" unless intentionally marked data-compact or data-inline.',
|
|
2470
2595
|
'PermissionGate renders the permitted slot directly and is UX-only; backend permissions and owner checks remain authoritative.',
|
|
2471
2596
|
'For menu/account-panel notifications, use counts only when the signal source already owns an exact count; otherwise use a dot/chip for new attention.',
|
|
@@ -2575,7 +2700,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2575
2700
|
server.tool('build_extension_ui', [
|
|
2576
2701
|
'Lazy gateway for Enfyra admin extension UI builders.',
|
|
2577
2702
|
'Use this after get_enfyra_required_knowledge(scope="extension") when a high-contract extension UI snippet is needed.',
|
|
2578
|
-
'It keeps guided startup small by dispatching drawer, modal, page_shell, permission_gate, empty_state, resource_list, form_editor, widget, menu_notification, account_panel_item, tabs, upload_modal, or review internally instead of exposing every builder tool up front.',
|
|
2703
|
+
'It keeps guided startup small by dispatching drawer, modal, page_shell, permission_gate, empty_state, resource_list, form_editor, widget, menu_notification, account_panel_item, tabs, upload_modal, theme_classes, theme_review, or review internally instead of exposing every builder tool up front.',
|
|
2579
2704
|
].join(' '), {
|
|
2580
2705
|
kind: z.enum([
|
|
2581
2706
|
'drawer',
|
|
@@ -2590,9 +2715,11 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2590
2715
|
'account_panel_item',
|
|
2591
2716
|
'tabs',
|
|
2592
2717
|
'upload_modal',
|
|
2718
|
+
'theme_classes',
|
|
2719
|
+
'theme_review',
|
|
2593
2720
|
'review',
|
|
2594
2721
|
]).describe('Which extension UI contract builder/reviewer to run.'),
|
|
2595
|
-
input: z.record(z.any()).optional().default({}).describe('Builder input object. For kind=review, pass { code }.'),
|
|
2722
|
+
input: z.record(z.any()).optional().default({}).describe('Builder input object. For kind=theme_classes, pass { intent }. For kind=theme_review/review, pass { code }.'),
|
|
2596
2723
|
extensionKnowledgeAckKey: extensionKnowledgeAckParam(z),
|
|
2597
2724
|
}, async ({ kind, input, extensionKnowledgeAckKey }) => {
|
|
2598
2725
|
assertExtensionKnowledgeAck(extensionKnowledgeAckKey);
|