@enfyra/mcp-server 0.1.69 → 0.1.71

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.
Files changed (248) hide show
  1. package/README.md +1 -1
  2. package/dist/lib/config-local-adapters.d.ts +18 -0
  3. package/dist/lib/config-local-adapters.js +271 -0
  4. package/dist/lib/config-local-adapters.js.map +1 -0
  5. package/dist/lib/config-local-contracts.d.ts +28 -0
  6. package/dist/lib/config-local-contracts.js +200 -0
  7. package/dist/lib/config-local-contracts.js.map +1 -0
  8. package/dist/lib/config-local-prompts.d.ts +10 -0
  9. package/dist/lib/config-local-prompts.js +185 -0
  10. package/dist/lib/config-local-prompts.js.map +1 -0
  11. package/dist/lib/config-local-types.d.ts +23 -0
  12. package/dist/lib/config-local-types.js +2 -0
  13. package/dist/lib/config-local-types.js.map +1 -0
  14. package/dist/lib/config-local.js +3 -649
  15. package/dist/lib/config-local.js.map +1 -1
  16. package/dist/lib/discovery-tools.d.ts +4 -0
  17. package/dist/lib/discovery-tools.js +546 -0
  18. package/dist/lib/discovery-tools.js.map +1 -0
  19. package/dist/lib/dynamic-repository-builder.d.ts +1 -1
  20. package/dist/lib/dynamic-script-context-contract.d.ts +100 -0
  21. package/dist/lib/dynamic-script-context-contract.js +163 -0
  22. package/dist/lib/dynamic-script-context-contract.js.map +1 -0
  23. package/dist/lib/enfyra-mcp-server.d.ts +3 -0
  24. package/dist/lib/enfyra-mcp-server.js +79 -0
  25. package/dist/lib/enfyra-mcp-server.js.map +1 -0
  26. package/dist/lib/enfyra-tool-logic.d.ts +7 -0
  27. package/dist/lib/enfyra-tool-logic.js +8 -0
  28. package/dist/lib/enfyra-tool-logic.js.map +1 -0
  29. package/dist/lib/enfyra-tool-types.d.ts +27 -0
  30. package/dist/lib/enfyra-tool-types.js +2 -0
  31. package/dist/lib/enfyra-tool-types.js.map +1 -0
  32. package/dist/lib/extension-api-builders.d.ts +17 -0
  33. package/dist/lib/extension-api-builders.js +235 -0
  34. package/dist/lib/extension-api-builders.js.map +1 -0
  35. package/dist/lib/extension-component-builders.d.ts +87 -0
  36. package/dist/lib/extension-component-builders.js +623 -0
  37. package/dist/lib/extension-component-builders.js.map +1 -0
  38. package/dist/lib/extension-contract-review.d.ts +26 -0
  39. package/dist/lib/extension-contract-review.js +187 -0
  40. package/dist/lib/extension-contract-review.js.map +1 -0
  41. package/dist/lib/extension-theme-contract.d.ts +63 -0
  42. package/dist/lib/extension-theme-contract.js +417 -0
  43. package/dist/lib/extension-theme-contract.js.map +1 -0
  44. package/dist/lib/identity-tools.d.ts +4 -0
  45. package/dist/lib/identity-tools.js +46 -0
  46. package/dist/lib/identity-tools.js.map +1 -0
  47. package/dist/lib/log-tools.d.ts +4 -0
  48. package/dist/lib/log-tools.js +65 -0
  49. package/dist/lib/log-tools.js.map +1 -0
  50. package/dist/lib/mcp-example-connect.d.ts +9 -0
  51. package/dist/lib/mcp-example-connect.js +442 -0
  52. package/dist/lib/mcp-example-connect.js.map +1 -0
  53. package/dist/lib/mcp-example-extensions.d.ts +9 -0
  54. package/dist/lib/mcp-example-extensions.js +729 -0
  55. package/dist/lib/mcp-example-extensions.js.map +1 -0
  56. package/dist/lib/mcp-example-files.d.ts +9 -0
  57. package/dist/lib/mcp-example-files.js +51 -0
  58. package/dist/lib/mcp-example-files.js.map +1 -0
  59. package/dist/lib/mcp-example-flows.d.ts +9 -0
  60. package/dist/lib/mcp-example-flows.js +76 -0
  61. package/dist/lib/mcp-example-flows.js.map +1 -0
  62. package/dist/lib/mcp-example-graphql.d.ts +9 -0
  63. package/dist/lib/mcp-example-graphql.js +57 -0
  64. package/dist/lib/mcp-example-graphql.js.map +1 -0
  65. package/dist/lib/mcp-example-handlers-hooks.d.ts +9 -0
  66. package/dist/lib/mcp-example-handlers-hooks.js +328 -0
  67. package/dist/lib/mcp-example-handlers-hooks.js.map +1 -0
  68. package/dist/lib/mcp-example-oauth-setup.d.ts +9 -0
  69. package/dist/lib/mcp-example-oauth-setup.js +41 -0
  70. package/dist/lib/mcp-example-oauth-setup.js.map +1 -0
  71. package/dist/lib/mcp-example-permissions-rls.d.ts +9 -0
  72. package/dist/lib/mcp-example-permissions-rls.js +210 -0
  73. package/dist/lib/mcp-example-permissions-rls.js.map +1 -0
  74. package/dist/lib/mcp-example-queries-deep.d.ts +9 -0
  75. package/dist/lib/mcp-example-queries-deep.js +220 -0
  76. package/dist/lib/mcp-example-queries-deep.js.map +1 -0
  77. package/dist/lib/mcp-example-schema-relations.d.ts +9 -0
  78. package/dist/lib/mcp-example-schema-relations.js +148 -0
  79. package/dist/lib/mcp-example-schema-relations.js.map +1 -0
  80. package/dist/lib/mcp-example-websocket.d.ts +9 -0
  81. package/dist/lib/mcp-example-websocket.js +103 -0
  82. package/dist/lib/mcp-example-websocket.js.map +1 -0
  83. package/dist/lib/mcp-examples.d.ts +5 -5
  84. package/dist/lib/mcp-examples.js +22 -2394
  85. package/dist/lib/mcp-examples.js.map +1 -1
  86. package/dist/lib/method-tools.d.ts +4 -0
  87. package/dist/lib/method-tools.js +217 -0
  88. package/dist/lib/method-tools.js.map +1 -0
  89. package/dist/lib/package-tools.d.ts +4 -0
  90. package/dist/lib/package-tools.js +98 -0
  91. package/dist/lib/package-tools.js.map +1 -0
  92. package/dist/lib/platform-data-operations.d.ts +24 -0
  93. package/dist/lib/platform-data-operations.js +71 -0
  94. package/dist/lib/platform-data-operations.js.map +1 -0
  95. package/dist/lib/platform-endpoint-workflow.d.ts +42 -0
  96. package/dist/lib/platform-endpoint-workflow.js +382 -0
  97. package/dist/lib/platform-endpoint-workflow.js.map +1 -0
  98. package/dist/lib/platform-extension-source.d.ts +487 -0
  99. package/dist/lib/platform-extension-source.js +524 -0
  100. package/dist/lib/platform-extension-source.js.map +1 -0
  101. package/dist/lib/platform-extension-tools.d.ts +1 -0
  102. package/dist/lib/platform-extension-tools.js +416 -0
  103. package/dist/lib/platform-extension-tools.js.map +1 -0
  104. package/dist/lib/platform-extension-ui.d.ts +4 -0
  105. package/dist/lib/platform-extension-ui.js +5 -0
  106. package/dist/lib/platform-extension-ui.js.map +1 -0
  107. package/dist/lib/platform-extension-workflow.d.ts +54 -0
  108. package/dist/lib/platform-extension-workflow.js +194 -0
  109. package/dist/lib/platform-extension-workflow.js.map +1 -0
  110. package/dist/lib/platform-flow-operations.d.ts +554 -0
  111. package/dist/lib/platform-flow-operations.js +317 -0
  112. package/dist/lib/platform-flow-operations.js.map +1 -0
  113. package/dist/lib/platform-flow-tools.d.ts +1 -0
  114. package/dist/lib/platform-flow-tools.js +248 -0
  115. package/dist/lib/platform-flow-tools.js.map +1 -0
  116. package/dist/lib/platform-operation-logic.d.ts +10 -0
  117. package/dist/lib/platform-operation-logic.js +11 -0
  118. package/dist/lib/platform-operation-logic.js.map +1 -0
  119. package/dist/lib/platform-operation-tools.d.ts +1 -324
  120. package/dist/lib/platform-operation-tools.js +13 -4620
  121. package/dist/lib/platform-operation-tools.js.map +1 -1
  122. package/dist/lib/platform-operation-types.d.ts +46 -0
  123. package/dist/lib/platform-operation-types.js +2 -0
  124. package/dist/lib/platform-operation-types.js.map +1 -0
  125. package/dist/lib/platform-policy-tools.d.ts +1 -0
  126. package/dist/lib/platform-policy-tools.js +248 -0
  127. package/dist/lib/platform-policy-tools.js.map +1 -0
  128. package/dist/lib/platform-resource-operations.d.ts +124 -0
  129. package/dist/lib/platform-resource-operations.js +109 -0
  130. package/dist/lib/platform-resource-operations.js.map +1 -0
  131. package/dist/lib/platform-resource-tools.d.ts +1 -0
  132. package/dist/lib/platform-resource-tools.js +70 -0
  133. package/dist/lib/platform-resource-tools.js.map +1 -0
  134. package/dist/lib/platform-route-operations.d.ts +222 -0
  135. package/dist/lib/platform-route-operations.js +290 -0
  136. package/dist/lib/platform-route-operations.js.map +1 -0
  137. package/dist/lib/platform-route-tools.d.ts +1 -0
  138. package/dist/lib/platform-route-tools.js +307 -0
  139. package/dist/lib/platform-route-tools.js.map +1 -0
  140. package/dist/lib/platform-shared-operations.d.ts +7 -0
  141. package/dist/lib/platform-shared-operations.js +4 -0
  142. package/dist/lib/platform-shared-operations.js.map +1 -0
  143. package/dist/lib/platform-websocket-tools.d.ts +1 -0
  144. package/dist/lib/platform-websocket-tools.js +70 -0
  145. package/dist/lib/platform-websocket-tools.js.map +1 -0
  146. package/dist/lib/record-tools.d.ts +4 -0
  147. package/dist/lib/record-tools.js +418 -0
  148. package/dist/lib/record-tools.js.map +1 -0
  149. package/dist/lib/required-knowledge.d.ts +1 -1
  150. package/dist/lib/required-knowledge.js +3 -2
  151. package/dist/lib/required-knowledge.js.map +1 -1
  152. package/dist/lib/route-access-tools.d.ts +4 -0
  153. package/dist/lib/route-access-tools.js +172 -0
  154. package/dist/lib/route-access-tools.js.map +1 -0
  155. package/dist/lib/route-definition-tools.d.ts +4 -0
  156. package/dist/lib/route-definition-tools.js +285 -0
  157. package/dist/lib/route-definition-tools.js.map +1 -0
  158. package/dist/lib/route-inspection-tools.d.ts +4 -0
  159. package/dist/lib/route-inspection-tools.js +319 -0
  160. package/dist/lib/route-inspection-tools.js.map +1 -0
  161. package/dist/lib/route-tools.d.ts +1 -0
  162. package/dist/lib/route-tools.js +9 -0
  163. package/dist/lib/route-tools.js.map +1 -0
  164. package/dist/lib/runtime-zone-registry.d.ts +27 -0
  165. package/dist/lib/runtime-zone-registry.js +81 -0
  166. package/dist/lib/runtime-zone-registry.js.map +1 -0
  167. package/dist/lib/runtime-zone-search.d.ts +303 -0
  168. package/dist/lib/runtime-zone-search.js +596 -0
  169. package/dist/lib/runtime-zone-search.js.map +1 -0
  170. package/dist/lib/runtime-zone-tools.d.ts +2 -307
  171. package/dist/lib/runtime-zone-tools.js +2 -674
  172. package/dist/lib/runtime-zone-tools.js.map +1 -1
  173. package/dist/lib/schema-column-tools.d.ts +3 -0
  174. package/dist/lib/schema-column-tools.js +80 -0
  175. package/dist/lib/schema-column-tools.js.map +1 -0
  176. package/dist/lib/schema-mutation-coordinator.d.ts +28 -0
  177. package/dist/lib/schema-mutation-coordinator.js +154 -0
  178. package/dist/lib/schema-mutation-coordinator.js.map +1 -0
  179. package/dist/lib/schema-relation-contracts.d.ts +39 -0
  180. package/dist/lib/schema-relation-contracts.js +160 -0
  181. package/dist/lib/schema-relation-contracts.js.map +1 -0
  182. package/dist/lib/schema-relation-tools.d.ts +3 -0
  183. package/dist/lib/schema-relation-tools.js +61 -0
  184. package/dist/lib/schema-relation-tools.js.map +1 -0
  185. package/dist/lib/schema-table-contracts.d.ts +28 -0
  186. package/dist/lib/schema-table-contracts.js +263 -0
  187. package/dist/lib/schema-table-contracts.js.map +1 -0
  188. package/dist/lib/schema-table-tools.d.ts +3 -0
  189. package/dist/lib/schema-table-tools.js +297 -0
  190. package/dist/lib/schema-table-tools.js.map +1 -0
  191. package/dist/lib/schema-tool-operations.d.ts +150 -0
  192. package/dist/lib/schema-tool-operations.js +468 -0
  193. package/dist/lib/schema-tool-operations.js.map +1 -0
  194. package/dist/lib/schema-tool-types.d.ts +38 -0
  195. package/dist/lib/schema-tool-types.js +2 -0
  196. package/dist/lib/schema-tool-types.js.map +1 -0
  197. package/dist/lib/schema-verification.d.ts +14 -0
  198. package/dist/lib/schema-verification.js +105 -0
  199. package/dist/lib/schema-verification.js.map +1 -0
  200. package/dist/lib/script-tools.d.ts +4 -0
  201. package/dist/lib/script-tools.js +127 -0
  202. package/dist/lib/script-tools.js.map +1 -0
  203. package/dist/lib/system-tools.d.ts +4 -0
  204. package/dist/lib/system-tools.js +41 -0
  205. package/dist/lib/system-tools.js.map +1 -0
  206. package/dist/lib/table-tool-logic.d.ts +5 -0
  207. package/dist/lib/table-tool-logic.js +6 -0
  208. package/dist/lib/table-tool-logic.js.map +1 -0
  209. package/dist/lib/table-tools.d.ts +1 -84
  210. package/dist/lib/table-tools.js +7 -1522
  211. package/dist/lib/table-tools.js.map +1 -1
  212. package/dist/lib/tool-catalog.d.ts +2 -1
  213. package/dist/lib/tool-catalog.js +23 -2
  214. package/dist/lib/tool-catalog.js.map +1 -1
  215. package/dist/lib/tool-metadata-operations.d.ts +27 -0
  216. package/dist/lib/tool-metadata-operations.js +93 -0
  217. package/dist/lib/tool-metadata-operations.js.map +1 -0
  218. package/dist/lib/tool-permission-profile.d.ts +37 -0
  219. package/dist/lib/tool-permission-profile.js +106 -0
  220. package/dist/lib/tool-permission-profile.js.map +1 -0
  221. package/dist/lib/tool-record-operations.d.ts +102 -0
  222. package/dist/lib/tool-record-operations.js +260 -0
  223. package/dist/lib/tool-record-operations.js.map +1 -0
  224. package/dist/lib/tool-route-inspection.d.ts +47 -0
  225. package/dist/lib/tool-route-inspection.js +168 -0
  226. package/dist/lib/tool-route-inspection.js.map +1 -0
  227. package/dist/lib/tool-routing.d.ts +3 -417
  228. package/dist/lib/tool-routing.js +3 -1038
  229. package/dist/lib/tool-routing.js.map +1 -1
  230. package/dist/lib/tool-runtime-config.d.ts +33 -0
  231. package/dist/lib/tool-runtime-config.js +237 -0
  232. package/dist/lib/tool-runtime-config.js.map +1 -0
  233. package/dist/lib/tool-script-operations.d.ts +34 -0
  234. package/dist/lib/tool-script-operations.js +153 -0
  235. package/dist/lib/tool-script-operations.js.map +1 -0
  236. package/dist/lib/workflow-definitions.d.ts +366 -0
  237. package/dist/lib/workflow-definitions.js +703 -0
  238. package/dist/lib/workflow-definitions.js.map +1 -0
  239. package/dist/lib/workflow-planner.d.ts +45 -0
  240. package/dist/lib/workflow-planner.js +338 -0
  241. package/dist/lib/workflow-planner.js.map +1 -0
  242. package/dist/lib/workflow-types.d.ts +39 -0
  243. package/dist/lib/workflow-types.js +2 -0
  244. package/dist/lib/workflow-types.js.map +1 -0
  245. package/dist/mcp-server-entry.d.ts +0 -3
  246. package/dist/mcp-server-entry.js +2 -3249
  247. package/dist/mcp-server-entry.js.map +1 -1
  248. package/package.json +1 -1
@@ -0,0 +1,623 @@
1
+ import { readTemplateBlocks, } from './platform-extension-source.js';
2
+ const AUTO_INJECTED_EXTENSION_COMPONENT_TAGS = [
3
+ 'CommonDrawer',
4
+ 'CommonModal',
5
+ 'EmptyState',
6
+ 'FormEditor',
7
+ 'FormEditorLazy',
8
+ 'NuxtLink',
9
+ 'PermissionGate',
10
+ 'UBadge',
11
+ 'UButton',
12
+ 'UCheckbox',
13
+ 'UDropdownMenu',
14
+ 'UForm',
15
+ 'UFormField',
16
+ 'UIcon',
17
+ 'UInput',
18
+ 'UInputMenu',
19
+ 'UInputNumber',
20
+ 'UInputTags',
21
+ 'UInputTime',
22
+ 'UInputDate',
23
+ 'UModal',
24
+ 'USelect',
25
+ 'USelectMenu',
26
+ 'USkeleton',
27
+ 'USwitch',
28
+ 'UTabs',
29
+ 'UTextarea',
30
+ 'UTooltip',
31
+ 'Widget',
32
+ ];
33
+ export const AUTO_INJECTED_EXTENSION_COMPONENT_BY_LOWERCASE = new Map(AUTO_INJECTED_EXTENSION_COMPONENT_TAGS.map((tag) => [tag.toLowerCase(), tag]));
34
+ export const FULL_WIDTH_EXTENSION_FIELD_TAGS = [
35
+ 'UInput',
36
+ 'UTextarea',
37
+ 'USelect',
38
+ 'USelectMenu',
39
+ 'UInputMenu',
40
+ 'UInputNumber',
41
+ 'UInputTags',
42
+ 'UInputTime',
43
+ 'UInputDate',
44
+ ];
45
+ const FULL_WIDTH_EXTENSION_FIELD_PATTERN = new RegExp(`<(${FULL_WIDTH_EXTENSION_FIELD_TAGS.join('|')})(\\s[^<>]*?)(\\/?)>`, 'g');
46
+ function escapeSingleQuoted(value) {
47
+ return String(value ?? '').replace(/\\/g, '\\\\').replace(/'/g, "\\'");
48
+ }
49
+ export function quoteJsString(value) {
50
+ return `'${escapeSingleQuoted(value)}'`;
51
+ }
52
+ function normalizeVueBodySnippet(body) {
53
+ let code = String(body || '').trim();
54
+ const changes = [];
55
+ code = code.replace(FULL_WIDTH_EXTENSION_FIELD_PATTERN, (full, tag, attrs, slash) => {
56
+ if (/\bdata-compact\b/.test(attrs) || /\bdata-inline\b/.test(attrs))
57
+ return full;
58
+ if (/\bclass=/.test(attrs)) {
59
+ const nextAttrs = attrs.replace(/class="([^"]*)"/, (classMatch, classes) => {
60
+ if (String(classes).split(/\s+/).includes('w-full'))
61
+ return classMatch;
62
+ changes.push(`Added w-full to ${tag}.`);
63
+ return `class="${classes} w-full"`;
64
+ });
65
+ if (nextAttrs !== attrs)
66
+ return `<${tag}${nextAttrs}${slash}>`;
67
+ return full;
68
+ }
69
+ changes.push(`Added class="w-full" to ${tag}.`);
70
+ return `<${tag} class="w-full"${attrs}${slash}>`;
71
+ });
72
+ code = code.replace(/<button(\s[^>]*)?>/g, (full, attrs = '') => {
73
+ if (/\btype=/.test(attrs))
74
+ return full;
75
+ changes.push('Added type="button" to a native button.');
76
+ return `<button type="button"${attrs}>`;
77
+ });
78
+ return { code, changes: Array.from(new Set(changes)) };
79
+ }
80
+ function findMissingFullWidthFieldControls(code) {
81
+ const violations = [];
82
+ for (const template of readTemplateBlocks(code)) {
83
+ let match;
84
+ FULL_WIDTH_EXTENSION_FIELD_PATTERN.lastIndex = 0;
85
+ while ((match = FULL_WIDTH_EXTENSION_FIELD_PATTERN.exec(template))) {
86
+ const [snippet, tag, attrs] = match;
87
+ if (/\bdata-compact\b/.test(attrs) || /\bdata-inline\b/.test(attrs))
88
+ continue;
89
+ const classMatch = attrs.match(/\bclass="([^"]*)"/);
90
+ if (!classMatch || !classMatch[1].split(/\s+/).includes('w-full')) {
91
+ violations.push({ tag, snippet: snippet.length > 160 ? `${snippet.slice(0, 157)}...` : snippet });
92
+ }
93
+ }
94
+ }
95
+ return violations;
96
+ }
97
+ function indentLines(code, spaces = 2) {
98
+ const pad = ' '.repeat(spaces);
99
+ return String(code || '')
100
+ .split('\n')
101
+ .map((line) => (line.trim() ? `${pad}${line}` : line))
102
+ .join('\n');
103
+ }
104
+ function buildFooterActionObject(action) {
105
+ if (!action)
106
+ return null;
107
+ if (typeof action === 'string')
108
+ return action;
109
+ const entries = [];
110
+ if (action.labelExpression)
111
+ entries.push(`label: ${action.labelExpression}`);
112
+ else if (action.label)
113
+ entries.push(`label: ${quoteJsString(action.label)}`);
114
+ if (action.icon)
115
+ entries.push(`icon: ${quoteJsString(action.icon)}`);
116
+ if (action.loading)
117
+ entries.push(`loading: ${action.loading}`);
118
+ if (action.disabled)
119
+ entries.push(`disabled: ${action.disabled}`);
120
+ if (action.color)
121
+ entries.push(`color: ${quoteJsString(action.color)}`);
122
+ if (action.variant)
123
+ entries.push(`variant: ${quoteJsString(action.variant)}`);
124
+ if (action.tone)
125
+ entries.push(`tone: ${quoteJsString(action.tone)}`);
126
+ if (action.onClick)
127
+ entries.push(`onClick: ${action.onClick}`);
128
+ return `{ ${entries.join(', ')} }`;
129
+ }
130
+ function titleMarkup(title, titleExpression) {
131
+ if (titleExpression)
132
+ return `{{ ${titleExpression} }}`;
133
+ return String(title || 'Untitled');
134
+ }
135
+ export function buildExtensionDrawerSnippet(input) {
136
+ const normalized = normalizeVueBodySnippet(input.body || '');
137
+ const titleContent = titleMarkup(input.title, input.titleExpression);
138
+ const model = input.model || 'drawerOpen';
139
+ const attrs = [
140
+ `v-model="${model}"`,
141
+ `direction="${input.direction || 'right'}"`,
142
+ ];
143
+ if (input.nested)
144
+ attrs.push('nested');
145
+ const cancelAction = input.cancelAction === false
146
+ ? null
147
+ : buildFooterActionObject(input.cancelAction || { label: 'Cancel', onClick: `() => (${model} = false)` });
148
+ const primaryAction = buildFooterActionObject(input.primaryAction);
149
+ const dangerAction = buildFooterActionObject(input.dangerAction);
150
+ if (cancelAction)
151
+ attrs.push(`:cancel-action="${cancelAction}"`);
152
+ if (primaryAction)
153
+ attrs.push(`:primary-action="${primaryAction}"`);
154
+ if (dangerAction)
155
+ attrs.push(`:danger-action="${dangerAction}"`);
156
+ if (input.footerHint)
157
+ attrs.push(`footer-hint="${escapeSingleQuoted(input.footerHint).replace(/"/g, '&quot;')}"`);
158
+ const snippet = [
159
+ '<CommonDrawer',
160
+ ...attrs.map((attr) => ` ${attr}`),
161
+ '>',
162
+ ' <template #header>',
163
+ ` <h2 class="text-lg font-semibold eapp-text-primary">${titleContent}</h2>`,
164
+ ' </template>',
165
+ '',
166
+ ' <template #body>',
167
+ indentLines(normalized.code, 4),
168
+ ' </template>',
169
+ '</CommonDrawer>',
170
+ ].join('\n');
171
+ const warnings = [];
172
+ if (!primaryAction)
173
+ warnings.push('Editing/create drawers usually need primaryAction for Save/Create.');
174
+ return {
175
+ action: 'extension_drawer_built',
176
+ component: 'CommonDrawer',
177
+ snippet,
178
+ normalizedBodyChanges: normalized.changes,
179
+ warnings,
180
+ contract: [
181
+ 'Use #header and #body slots; do not use a title prop.',
182
+ 'Use primaryAction for Save/Create and dangerAction for destructive edit actions.',
183
+ 'Body form controls are normalized to class="w-full" unless intentionally compact.',
184
+ 'Native buttons in the body are normalized to type="button".',
185
+ ],
186
+ };
187
+ }
188
+ export function buildExtensionModalSnippet(input) {
189
+ const normalized = normalizeVueBodySnippet(input.body || '');
190
+ const titleContent = titleMarkup(input.title, input.titleExpression);
191
+ const model = input.model || 'modalOpen';
192
+ const tag = input.alias === 'UModal' ? 'UModal' : 'CommonModal';
193
+ const attrs = [`v-model:open="${model}"`];
194
+ const cancelAction = input.cancelAction === false
195
+ ? null
196
+ : buildFooterActionObject(input.cancelAction || { label: 'Cancel', onClick: `() => (${model} = false)` });
197
+ const primaryAction = buildFooterActionObject(input.primaryAction);
198
+ const dangerAction = buildFooterActionObject(input.dangerAction);
199
+ if (cancelAction)
200
+ attrs.push(`:cancel-action="${cancelAction}"`);
201
+ if (primaryAction)
202
+ attrs.push(`:primary-action="${primaryAction}"`);
203
+ if (dangerAction)
204
+ attrs.push(`:danger-action="${dangerAction}"`);
205
+ if (input.footerHint)
206
+ attrs.push(`footer-hint="${escapeSingleQuoted(input.footerHint).replace(/"/g, '&quot;')}"`);
207
+ const snippet = [
208
+ `<${tag}`,
209
+ ...attrs.map((attr) => ` ${attr}`),
210
+ '>',
211
+ ' <template #header>',
212
+ ` <h2 class="text-lg font-semibold eapp-text-primary">${titleContent}</h2>`,
213
+ ' </template>',
214
+ '',
215
+ ' <template #body>',
216
+ indentLines(normalized.code, 4),
217
+ ' </template>',
218
+ `</${tag}>`,
219
+ ].join('\n');
220
+ const warnings = [];
221
+ if (!primaryAction && !dangerAction)
222
+ warnings.push('Mutation or confirmation modals usually need primaryAction or dangerAction for the final action.');
223
+ return {
224
+ action: 'extension_modal_built',
225
+ component: tag,
226
+ snippet,
227
+ normalizedBodyChanges: normalized.changes,
228
+ warnings,
229
+ contract: [
230
+ 'Use v-model:open and #header/#body slots; do not use a title prop.',
231
+ 'Use primaryAction for ordinary final actions and dangerAction for destructive confirmation.',
232
+ 'Body form controls are normalized to class="w-full" unless intentionally compact.',
233
+ 'Native buttons in the body are normalized to type="button".',
234
+ ],
235
+ };
236
+ }
237
+ function jsObjectLiteral(entries) {
238
+ return `{ ${entries.filter(Boolean).join(', ')} }`;
239
+ }
240
+ function jsArrayLiteral(values) {
241
+ return `[${(values || []).map(quoteJsString).join(', ')}]`;
242
+ }
243
+ function attrStaticOrBound(name, value, expression) {
244
+ if (expression)
245
+ return `:${name}="${expression}"`;
246
+ if (value === undefined || value === null || value === '')
247
+ return null;
248
+ return `${name}="${String(value).replace(/"/g, '&quot;')}"`;
249
+ }
250
+ function buildHeaderActionLiteral(action) {
251
+ const bareAssignment = String(action.onClick || '').match(/^\s*\(\s*\)\s*=>\s*\(?\s*([A-Za-z_$][\w$]*)\s*=(?!=)/);
252
+ if (bareAssignment) {
253
+ throw new Error(`Invalid header action onClick: assign ${bareAssignment[1]}.value inside script callbacks, or pass a handler name. Template ref auto-unwrapping does not apply in registry callbacks.`);
254
+ }
255
+ const entries = [
256
+ action.id ? `id: ${quoteJsString(action.id)}` : null,
257
+ action.label ? `label: ${quoteJsString(action.label)}` : null,
258
+ action.icon ? `icon: ${quoteJsString(action.icon)}` : null,
259
+ `color: ${quoteJsString(action.color || 'neutral')}`,
260
+ `variant: ${quoteJsString(action.variant || 'outline')}`,
261
+ action.loading ? `loading: ${action.loading}` : null,
262
+ action.disabled ? `disabled: ${action.disabled}` : null,
263
+ action.to ? `to: ${quoteJsString(action.to)}` : null,
264
+ action.onClick ? `onClick: ${action.onClick}` : null,
265
+ typeof action.order === 'number' ? `order: ${action.order}` : null,
266
+ action.side ? `side: ${quoteJsString(action.side)}` : null,
267
+ ];
268
+ return jsObjectLiteral(entries);
269
+ }
270
+ export function buildExtensionPageShellSnippet(input) {
271
+ const title = input.titleExpression || quoteJsString(input.title || 'Untitled');
272
+ const headerEntries = [
273
+ `title: ${title}`,
274
+ input.description ? `description: ${quoteJsString(input.description)}` : null,
275
+ input.leadingIcon ? `leadingIcon: ${quoteJsString(input.leadingIcon)}` : null,
276
+ `gradient: ${quoteJsString(input.gradient || 'none')}`,
277
+ `variant: ${quoteJsString(input.variant || 'minimal')}`,
278
+ ];
279
+ const actions = Array.isArray(input.headerActions) ? input.headerActions : [];
280
+ const lines = [
281
+ 'const { registerPageHeader } = usePageHeaderRegistry();',
282
+ `registerPageHeader(${jsObjectLiteral(headerEntries)});`,
283
+ ];
284
+ if (actions.length) {
285
+ lines.push('const { register: registerHeaderActions } = useHeaderActionRegistry();');
286
+ lines.push(`onMounted(() => {\n registerHeaderActions([\n${actions.map((action) => ` ${buildHeaderActionLiteral(action)}`).join(',\n')}\n ]);\n});`);
287
+ }
288
+ return {
289
+ action: 'extension_page_shell_built',
290
+ snippet: lines.join('\n'),
291
+ contract: [
292
+ 'Use usePageHeaderRegistry so the app shell renders the page header.',
293
+ 'Use useHeaderActionRegistry for toolbar actions instead of rendering duplicate page headers or local top bars; register dynamic extension actions in onMounted after setup state exists.',
294
+ 'Use primary solid only for the main scope action; secondary actions default to neutral outline.',
295
+ ],
296
+ };
297
+ }
298
+ export function buildExtensionPermissionGateSnippet(input) {
299
+ const normalized = normalizeVueBodySnippet(input.body || '<slot />');
300
+ let condition;
301
+ if (input.condition) {
302
+ condition = input.condition;
303
+ }
304
+ else if (input.route) {
305
+ const methods = Array.isArray(input.methods) && input.methods.length ? input.methods : ['GET'];
306
+ condition = `{ or: [{ route: ${quoteJsString(input.route)}, methods: [${methods.map(quoteJsString).join(', ')}] }] }`;
307
+ }
308
+ else {
309
+ condition = 'null';
310
+ }
311
+ const snippet = [
312
+ `<PermissionGate :condition="${condition}">`,
313
+ indentLines(normalized.code, 2),
314
+ '</PermissionGate>',
315
+ ].join('\n');
316
+ return {
317
+ action: 'extension_permission_gate_built',
318
+ component: 'PermissionGate',
319
+ snippet,
320
+ normalizedBodyChanges: normalized.changes,
321
+ warnings: condition === 'null' ? ['No condition/route was provided. PermissionGate with null condition permits the slot.'] : [],
322
+ contract: [
323
+ 'PermissionGate is only operator UX; backend route permissions and owner checks remain authoritative.',
324
+ 'PermissionGate renders its slot directly and should not be used as a layout wrapper.',
325
+ ],
326
+ };
327
+ }
328
+ export function buildExtensionEmptyStateSnippet(input) {
329
+ const action = input.action
330
+ ? `\n :action="${buildFooterActionObject(input.action)}"`
331
+ : '';
332
+ return {
333
+ action: 'extension_empty_state_built',
334
+ component: 'EmptyState',
335
+ snippet: `<EmptyState\n title="${String(input.title || 'No items found').replace(/"/g, '&quot;')}"\n description="${String(input.description || '').replace(/"/g, '&quot;')}"\n icon="${input.icon || 'lucide:inbox'}"\n size="${input.size || 'sm'}"\n variant="${input.variant || 'naked'}"${action}\n/>`,
336
+ contract: [
337
+ 'Dynamic extensions expose the app empty-state component as EmptyState.',
338
+ 'Use variant="naked" inside framed panels/lists and outline/subtle for standalone framed empty surfaces.',
339
+ ],
340
+ };
341
+ }
342
+ export function buildExtensionResourceListSnippet(input) {
343
+ const itemsExpression = input.itemsExpression || 'items';
344
+ const itemName = input.itemName || 'item';
345
+ const keyExpression = input.keyExpression || `${itemName}.id`;
346
+ const titleExpression = input.titleExpression || `${itemName}.title || ${quoteJsString('Untitled')}`;
347
+ const descriptionExpression = input.descriptionExpression || `${itemName}.description`;
348
+ const iconExpression = input.iconExpression || quoteJsString(input.icon || 'lucide:file-text');
349
+ const onClick = input.onClick ? `\n :on-click="() => ${input.onClick}"` : '';
350
+ const stats = input.statsExpression ? `\n :stats="${input.statsExpression}"` : '';
351
+ const actions = input.actionsExpression ? `\n :actions="${input.actionsExpression}"` : '';
352
+ const topBadge = input.topBadgeExpression ? `\n :top-badge="${input.topBadgeExpression}"` : '';
353
+ const itemsPerPageExpression = input.itemsPerPageExpression || '0';
354
+ const pageModel = String(itemsPerPageExpression) !== '0'
355
+ ? `\n v-model:page="${input.pageExpression || 'page'}"`
356
+ : '';
357
+ const frame = [
358
+ `<CommonResourceListFrame${pageModel}`,
359
+ ` :loading="${input.loadingExpression || 'pending'}"`,
360
+ ` :has-items="${itemsExpression}.length > 0"`,
361
+ ` :total="${input.totalExpression || `${itemsExpression}.length`}"`,
362
+ ` :items-per-page="${itemsPerPageExpression}"`,
363
+ ` empty-title="${String(input.emptyTitle || 'No items found').replace(/"/g, '&quot;')}"`,
364
+ ` empty-description="${String(input.emptyDescription || '').replace(/"/g, '&quot;')}"`,
365
+ ` empty-icon="${input.emptyIcon || 'lucide:inbox'}"`,
366
+ '>',
367
+ ` <CommonResourceListItem`,
368
+ ` v-for="${itemName} in ${itemsExpression}"`,
369
+ ` :key="${keyExpression}"`,
370
+ ` :title="${titleExpression}"`,
371
+ ` :description="${descriptionExpression}"`,
372
+ ` :icon="${iconExpression}"`,
373
+ ' icon-color="primary"',
374
+ `${stats}${actions}${topBadge}${onClick}`,
375
+ ' />',
376
+ '</CommonResourceListFrame>',
377
+ ].join('\n');
378
+ const snippet = input.constrained === false
379
+ ? frame
380
+ : ['<section class="eapp-page-constrained-wide space-y-4">', indentLines(frame, 2), '</section>'].join('\n');
381
+ return {
382
+ action: 'extension_resource_list_built',
383
+ components: ['CommonResourceListFrame', 'CommonResourceListItem'],
384
+ snippet,
385
+ contract: [
386
+ 'Use CommonResourceListFrame and CommonResourceListItem for operational lists instead of ad hoc cards.',
387
+ 'CommonResourceListFrame supports extension default slots. It renders rows when loading is false and hasItems is true; inspect the source artifact, hasItems/items expressions, and API response shape before replacing it.',
388
+ 'Keep first-load skeleton, empty state, and pagination owned by the frame.',
389
+ 'Keep search and filter controls in a separate compact surface before the list; do not wrap filters and all rows in one oversized card.',
390
+ 'Keep operational list pages constrained with eapp-page-constrained-wide unless the workflow intentionally owns a full-bleed canvas.',
391
+ 'Use explicit bounded list data and natural pagination/search outside this snippet when the domain list can grow.',
392
+ ],
393
+ };
394
+ }
395
+ export function buildExtensionResourceGridSnippet(input) {
396
+ const itemsExpression = input.itemsExpression || 'items';
397
+ const itemName = input.itemName || 'item';
398
+ const keyExpression = input.keyExpression || `${itemName}.id`;
399
+ const defaultBody = [
400
+ `<h2 class="font-semibold eapp-text-primary">{{ ${itemName}.title || 'Untitled' }}</h2>`,
401
+ `<p v-if="${itemName}.description" class="text-sm eapp-text-secondary line-clamp-2">{{ ${itemName}.description }}</p>`,
402
+ ].join('\n');
403
+ const normalized = normalizeVueBodySnippet(input.cardBody || defaultBody);
404
+ const frame = [
405
+ '<CommonResourceListFrame',
406
+ ' variant="plain"',
407
+ ` :loading="${input.loadingExpression || 'pending'}"`,
408
+ ` :has-items="${itemsExpression}.length > 0"`,
409
+ ` :total="${input.totalExpression || `${itemsExpression}.length`}"`,
410
+ ` :items-per-page="${input.itemsPerPageExpression || '0'}"`,
411
+ ` empty-title="${String(input.emptyTitle || 'No items found').replace(/"/g, '&quot;')}"`,
412
+ ` empty-description="${String(input.emptyDescription || '').replace(/"/g, '&quot;')}"`,
413
+ ` empty-icon="${input.emptyIcon || 'lucide:inbox'}"`,
414
+ '>',
415
+ ' <div class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">',
416
+ ` <UCard v-for="${itemName} in ${itemsExpression}" :key="${keyExpression}" class="h-full eapp-surface-card eapp-radius-panel border eapp-divider">`,
417
+ ' <div class="flex h-full flex-col gap-4">',
418
+ indentLines(normalized.code, 8),
419
+ ' </div>',
420
+ ' </UCard>',
421
+ ' </div>',
422
+ '</CommonResourceListFrame>',
423
+ ].join('\n');
424
+ const constrained = input.constrained === false
425
+ ? frame
426
+ : ['<section class="eapp-page-constrained-wide space-y-4">', indentLines(frame, 2), '</section>'].join('\n');
427
+ return {
428
+ action: 'extension_resource_grid_built',
429
+ component: 'CommonResourceListFrame',
430
+ snippet: constrained,
431
+ normalizedBodyChanges: normalized.changes,
432
+ contract: [
433
+ 'Use this card grid for dashboard/workboard/catalog collections; use resource_list for dense operational rows.',
434
+ 'The default desktop layout uses three columns only at xl because the admin sidebar consumes viewport width.',
435
+ 'Keep the page constrained unless the workflow intentionally owns a canvas or other full-bleed surface.',
436
+ 'Keep card actions inside cardBody and align them with flex layout rather than floating them at the viewport edge.',
437
+ ],
438
+ };
439
+ }
440
+ export function buildExtensionFormEditorSnippet(input) {
441
+ const tag = input.lazy === false ? 'FormEditor' : 'FormEditorLazy';
442
+ const attrs = [
443
+ `v-model="${input.model || 'form'}"`,
444
+ `v-model:errors="${input.errors || 'errors'}"`,
445
+ attrStaticOrBound('table-name', input.tableName, input.tableNameExpression),
446
+ input.mode ? `mode="${input.mode}"` : null,
447
+ input.loadingExpression ? `:loading="${input.loadingExpression}"` : null,
448
+ input.layout ? `layout="${input.layout}"` : null,
449
+ input.includes?.length ? `:includes="${jsArrayLiteral(input.includes)}"` : null,
450
+ input.excluded?.length ? `:excluded="${jsArrayLiteral(input.excluded)}"` : null,
451
+ input.sectionsExpression ? `:sections="${input.sectionsExpression}"` : null,
452
+ input.fieldMapExpression ? `:field-map="${input.fieldMapExpression}"` : null,
453
+ input.virtualFieldsExpression ? `:virtual-fields="${input.virtualFieldsExpression}"` : null,
454
+ input.currentRecordIdExpression ? `:current-record-id="${input.currentRecordIdExpression}"` : null,
455
+ input.hasChangedHandler ? `@has-changed="${input.hasChangedHandler}"` : null,
456
+ input.virtualFieldEmitHandler ? `@virtual-field-emit="${input.virtualFieldEmitHandler}"` : null,
457
+ ].filter(Boolean);
458
+ const snippet = [
459
+ `<${tag}`,
460
+ ...attrs.map((attr) => ` ${attr}`),
461
+ '/>',
462
+ ].join('\n');
463
+ return {
464
+ action: 'extension_form_editor_built',
465
+ component: tag,
466
+ snippet,
467
+ contract: [
468
+ 'Prefer FormEditor/FormEditorLazy for direct table-backed forms instead of hand-built UInput/UTextarea fields.',
469
+ 'Use v-model for record state and v-model:errors for validation errors.',
470
+ 'Use includes/sections to keep generated forms focused; do not expose compiledCode or unrelated system fields.',
471
+ 'Use fieldMap only for behavior/renderer overrides such as code fields or custom labels.',
472
+ ],
473
+ };
474
+ }
475
+ export function buildExtensionWidgetSnippet(input) {
476
+ const attrs = [`:id="${typeof input.id === 'number' ? input.id : quoteJsString(input.id)}"`];
477
+ for (const [key, value] of Object.entries(input.props || {})) {
478
+ attrs.push(`:${key}="${value}"`);
479
+ }
480
+ for (const [event, handler] of Object.entries(input.events || {})) {
481
+ attrs.push(`@${event}="${handler}"`);
482
+ }
483
+ return {
484
+ action: 'extension_widget_built',
485
+ component: 'Widget',
486
+ snippet: `<Widget ${attrs.join(' ')} />`,
487
+ warnings: typeof input.id === 'number' ? [] : ['Widget ids should be numeric enfyra_extension ids; do not pass extension name or extensionId string.'],
488
+ contract: [
489
+ 'Widget :id is the numeric enfyra_extension id, not name or extensionId.',
490
+ 'Pass safe props/events; keep page-level mutation and modal ownership in the page unless the widget intentionally owns the full workflow.',
491
+ ],
492
+ };
493
+ }
494
+ export function buildExtensionMenuNotificationSnippet(input) {
495
+ const targetEntries = [
496
+ input.targetId !== undefined ? `id: ${quoteJsString(input.targetId)}` : null,
497
+ input.path ? `path: ${quoteJsString(input.path)}` : null,
498
+ input.route ? `route: ${quoteJsString(input.route)}` : null,
499
+ ];
500
+ const entries = [
501
+ `id: ${quoteJsString(input.id || 'extension-menu-notification')}`,
502
+ `target: ${jsObjectLiteral(targetEntries)}`,
503
+ input.valueExpression ? `value: ${input.valueExpression}` : input.value !== undefined ? `value: ${quoteJsString(input.value)}` : null,
504
+ `color: ${quoteJsString(input.color || 'primary')}`,
505
+ input.title ? `title: ${quoteJsString(input.title)}` : null,
506
+ typeof input.order === 'number' ? `order: ${input.order}` : null,
507
+ ];
508
+ return {
509
+ action: 'extension_menu_notification_built',
510
+ snippet: [
511
+ 'const { register: registerMenuNotification } = useMenuNotificationRegistry();',
512
+ `registerMenuNotification(${jsObjectLiteral(entries)});`,
513
+ ].join('\n'),
514
+ contract: [
515
+ 'Use count/value only when the signal source already owns an exact or bounded count.',
516
+ 'Omit value for a dot-only notification when realtime only proves new attention exists.',
517
+ 'Do not fetch destination domain lists solely to decorate the menu.',
518
+ ],
519
+ };
520
+ }
521
+ export function buildExtensionAccountPanelSnippet(input) {
522
+ const entries = [
523
+ `id: ${quoteJsString(input.id || 'extension-account-panel-item')}`,
524
+ typeof input.order === 'number' ? `order: ${input.order}` : null,
525
+ input.label ? `label: ${quoteJsString(input.label)}` : null,
526
+ input.description ? `description: ${quoteJsString(input.description)}` : null,
527
+ input.icon ? `icon: ${quoteJsString(input.icon)}` : null,
528
+ input.countExpression ? `count: ${input.countExpression}` : input.count !== undefined ? `count: ${quoteJsString(input.count)}` : null,
529
+ input.badgeExpression ? `badge: ${input.badgeExpression}` : input.badge !== undefined ? `badge: ${quoteJsString(input.badge)}` : null,
530
+ input.badgeColor ? `badgeColor: ${quoteJsString(input.badgeColor)}` : null,
531
+ input.trailingIcon ? `trailingIcon: ${quoteJsString(input.trailingIcon)}` : null,
532
+ input.expandedExpression ? `expanded: ${input.expandedExpression}` : null,
533
+ input.contentComponent ? `contentComponent: ${input.contentComponent}` : null,
534
+ input.contentPropsExpression ? `contentProps: ${input.contentPropsExpression}` : null,
535
+ input.onClick ? `onClick: ${input.onClick}` : null,
536
+ input.onToggle ? `onToggle: ${input.onToggle}` : null,
537
+ ];
538
+ return {
539
+ action: 'extension_account_panel_item_built',
540
+ snippet: [
541
+ 'const { register: registerAccountPanelItem } = useAccountPanelRegistry();',
542
+ `registerAccountPanelItem(${jsObjectLiteral(entries)});`,
543
+ ].join('\n'),
544
+ contract: [
545
+ 'Prefer data-driven account panel rows over fully custom row components.',
546
+ 'Use count for notification-style chips; count takes precedence over badge.',
547
+ 'Use onClick for direct actions and onToggle/contentComponent for expandable inline UI.',
548
+ ],
549
+ };
550
+ }
551
+ export function buildExtensionTabsSnippet(input) {
552
+ const model = input.model || 'activeTab';
553
+ const items = input.itemsExpression || 'tabs';
554
+ const body = input.body || '<div>{{ item.label }}</div>';
555
+ const snippet = [
556
+ `<UTabs v-model="${model}" :items="${items}" class="w-full">`,
557
+ ' <template #content="{ item }">',
558
+ indentLines(normalizeVueBodySnippet(body).code, 4),
559
+ ' </template>',
560
+ '</UTabs>',
561
+ ].join('\n');
562
+ return {
563
+ action: 'extension_tabs_built',
564
+ component: 'UTabs',
565
+ snippet,
566
+ contract: [
567
+ 'Use app-level UTabs chrome instead of custom tab bars.',
568
+ 'Do not add local full-width bottom borders/dividers to tab lists.',
569
+ 'Keep tab items data-driven and render panel content through #content.',
570
+ ],
571
+ };
572
+ }
573
+ export function buildExtensionUploadModalSnippet(input) {
574
+ const model = input.model || 'showUploadModal';
575
+ const attrs = [
576
+ `v-model="${model}"`,
577
+ `title="${String(input.title || 'Upload Files').replace(/"/g, '&quot;')}"`,
578
+ `accept="${input.accept || '*/*'}"`,
579
+ input.multiple !== false ? ':multiple="true"' : ':multiple="false"',
580
+ input.maxSizeExpression ? `:max-size="${input.maxSizeExpression}"` : ':max-size="10 * 1024 * 1024"',
581
+ input.loadingExpression ? `:loading="${input.loadingExpression}"` : null,
582
+ input.uploadProgressExpression ? `:upload-progress="${input.uploadProgressExpression}"` : null,
583
+ input.fileProgressExpression ? `:file-progress="${input.fileProgressExpression}"` : null,
584
+ input.dragText ? `drag-text="${String(input.dragText).replace(/"/g, '&quot;')}"` : null,
585
+ input.acceptText ? `accept-text="${String(input.acceptText).replace(/"/g, '&quot;')}"` : null,
586
+ input.uploadText ? `upload-text="${String(input.uploadText).replace(/"/g, '&quot;')}"` : null,
587
+ input.uploadingText ? `uploading-text="${String(input.uploadingText).replace(/"/g, '&quot;')}"` : null,
588
+ `@upload="${input.uploadHandler || 'handleUpload'}"`,
589
+ input.errorHandler ? `@error="${input.errorHandler}"` : null,
590
+ ].filter(Boolean);
591
+ const headerContent = input.headerContent ? [
592
+ '>',
593
+ ' <template #header-content>',
594
+ indentLines(normalizeVueBodySnippet(input.headerContent).code, 4),
595
+ ' </template>',
596
+ '</CommonUploadModal>',
597
+ ] : ['/>'];
598
+ return {
599
+ action: 'extension_upload_modal_built',
600
+ component: 'CommonUploadModal',
601
+ snippet: [
602
+ '<CommonUploadModal',
603
+ ...attrs.map((attr) => ` ${attr}`),
604
+ ...headerContent,
605
+ ].join('\n'),
606
+ companionSnippet: [
607
+ 'const {',
608
+ ' uploadProgress,',
609
+ ' trackedUploadProgressById,',
610
+ ' beginTrackedUploadProgress,',
611
+ ' getUploadProgressHeaders,',
612
+ ' resetUploadProgress,',
613
+ '} = useFileUploadProgress();',
614
+ ].join('\n'),
615
+ contract: [
616
+ 'Use useFileUploadProgress for admin-socket upload progress.',
617
+ 'Send x-enfyra-upload-id via getUploadProgressHeaders(id) for each uploaded file.',
618
+ 'For multi-file uploads, call the useApi batch files path once, pass per-file headers through headersByIndex, and map each upload id to fileProgress[index].',
619
+ 'CommonUploadModal owns selected-file rows and per-row progress chrome.',
620
+ ],
621
+ };
622
+ }
623
+ //# sourceMappingURL=extension-component-builders.js.map