openyida 2026.9.23-beta.2 → 2026.9.23

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 (247) hide show
  1. package/README.md +9 -32
  2. package/bin/yida.js +11 -33
  3. package/lib/app/app-entry.js +144 -0
  4. package/lib/app/app-list.js +1 -0
  5. package/lib/app/application-style.js +67 -0
  6. package/lib/app/canvas-compile.js +7 -1
  7. package/lib/app/canvas-theme-guard.js +127 -10
  8. package/lib/app/check-prd-completeness.js +4 -15
  9. package/lib/app/compile.js +12 -2
  10. package/lib/app/create-form/args.js +37 -12
  11. package/lib/app/create-form/batch.js +41 -35
  12. package/lib/app/create-form.js +94 -57
  13. package/lib/app/custom-theme.js +30 -37
  14. package/lib/app/fix-theme.js +34 -0
  15. package/lib/app/form-field-validator.js +0 -36
  16. package/lib/app/get-schema.js +1 -6
  17. package/lib/app/publish.js +49 -7
  18. package/lib/app/services/canvas-page-compiler.js +3 -1
  19. package/lib/app/services/form-compiler.js +0 -2
  20. package/lib/app/theme-from-design.js +77 -87
  21. package/lib/app/theme-scope.js +76 -0
  22. package/lib/app/update-app.js +64 -26
  23. package/lib/app/update-form-config.js +9 -19
  24. package/lib/auth/token-auth.js +6 -57
  25. package/lib/auth/token-store.js +2 -26
  26. package/lib/core/cli-error.js +1 -0
  27. package/lib/core/command-manifest.js +121 -48
  28. package/lib/core/locales/en.js +47 -52
  29. package/lib/core/locales/zh.js +47 -52
  30. package/lib/core/sample.js +36 -14
  31. package/lib/core/source-repair.js +38 -0
  32. package/lib/core/theme-brand-scale.js +133 -0
  33. package/lib/core/update.js +1 -3
  34. package/lib/core/utils.js +0 -29
  35. package/lib/design/check-design.js +16 -6
  36. package/lib/design/document.js +37 -15
  37. package/lib/design/icon-contrast.js +1 -1
  38. package/lib/design/palette-contrast.js +77 -0
  39. package/lib/design-plan/confirmation.js +13 -0
  40. package/lib/design-plan/design-plan.js +65 -8
  41. package/lib/design-plan/entry-navigation.js +10 -2
  42. package/lib/design-plan/init.js +17 -10
  43. package/lib/design-plan/materialize.js +88 -58
  44. package/lib/design-plan/normalize.js +13 -14
  45. package/lib/design-plan/parallel.js +62 -13
  46. package/lib/design-plan/patch.js +21 -7
  47. package/lib/design-plan/preview.js +4 -1
  48. package/lib/design-plan/rebase.js +69 -0
  49. package/lib/design-plan/themes.js +16 -1
  50. package/lib/design-plan/validate.js +18 -3
  51. package/lib/design-plan/visual-policy.js +29 -4
  52. package/lib/page-config/save-share-config.js +5 -16
  53. package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +3 -3
  54. package/lib/samples/openyida-scaffold/canvas-nav/mixed.jsx +6 -4
  55. package/lib/samples/openyida-scaffold/canvas-nav/shared.jsx +5 -5
  56. package/lib/samples/openyida-scaffold/canvas-nav/side.jsx +1 -2
  57. package/lib/samples/openyida-scaffold/canvas-nav/sidebar.jsx +1 -1
  58. package/lib/samples/openyida-scaffold/canvas-nav/top.jsx +5 -4
  59. package/package.json +3 -11
  60. package/yida-skills/SKILL.md +3 -25
  61. package/yida-skills/references/official-example-schema-patterns.md +1 -1
  62. package/yida-skills/references/source-repair.md +10 -0
  63. package/yida-skills/references/yida-api.md +4 -0
  64. package/yida-skills/skills/yida-app/SKILL.md +15 -4
  65. package/yida-skills/skills/yida-app/references/entry-navigation.md +11 -9
  66. package/yida-skills/skills/yida-app/workflow/plan/step-4-deliver.md +3 -1
  67. package/yida-skills/skills/yida-app/workflow/plan/workflow.md +21 -5
  68. package/yida-skills/skills/yida-app/workflow/step-2-design.md +4 -4
  69. package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +2 -2
  70. package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +3 -3
  71. package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +25 -2
  72. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +3 -2
  73. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +1 -1
  74. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-theme-provider.md +4 -2
  75. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +140 -1
  76. package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +1 -1
  77. package/yida-skills/skills/yida-canvas-custom-page/references/waf-safe-authoring.md +45 -0
  78. package/yida-skills/skills/yida-canvas-custom-page/scripts/build-canvas-theme.js +2 -1
  79. package/yida-skills/skills/yida-canvas-custom-page/scripts/canvas-theme-provider.template.jsx +8 -1
  80. package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +7 -0
  81. package/yida-skills/skills/yida-create-form-page/SKILL.md +34 -43
  82. package/yida-skills/skills/yida-create-form-page/references/advanced-form-modes.md +5 -5
  83. package/yida-skills/skills/yida-create-form-page/references/association-form-field.md +4 -4
  84. package/yida-skills/skills/yida-create-form-page/references/batch-forms.md +2 -2
  85. package/yida-skills/skills/yida-create-form-page/references/field-definition-guide.md +4 -16
  86. package/yida-skills/skills/yida-create-form-page/references/form-field-properties.md +14 -37
  87. package/yida-skills/skills/yida-design/SKILL.md +16 -8
  88. package/yida-skills/skills/yida-design/references/application-style-library.md +93 -0
  89. package/yida-skills/skills/yida-design/references/application-theme-consistency.md +92 -7
  90. package/yida-skills/skills/yida-design/references/ask-human-interaction-contract.md +1 -1
  91. package/yida-skills/skills/yida-design/references/native-form-styles.md +208 -0
  92. package/yida-skills/skills/yida-design/references/navigation-decision.md +8 -6
  93. package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +136 -113
  94. package/yida-skills/skills/yida-design/references/theme/application-style-recipes.css +58 -0
  95. package/yida-skills/skills/yida-design/references/theme-selection.md +12 -11
  96. package/yida-skills/skills/yida-design/references/visual-decision-engine.md +1 -1
  97. package/yida-skills/skills/yida-design/scripts/validate_design_themes.py +52 -7
  98. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/SKILL.md +15 -3
  99. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-compact-schema.md +6 -2
  100. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-schema.md +5 -3
  101. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-design.md +3 -1
  102. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-theme-selection.md +22 -23
  103. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/render_build_plan.py +2 -0
  104. package/yida-skills/skills/yida-design/templates/design-themes/README.md +17 -10
  105. package/yida-skills/skills/yida-design/templates/design-themes/app-amber/app_theme.css +570 -0
  106. package/yida-skills/skills/yida-design/templates/design-themes/app-amber/design.md +238 -0
  107. package/yida-skills/skills/yida-design/templates/design-themes/app-amber/form-layout.json +15 -0
  108. package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/app_theme.css +570 -0
  109. package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/design.md +239 -0
  110. package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/form-layout.json +14 -0
  111. package/yida-skills/skills/yida-design/templates/design-themes/app-executive/app_theme.css +570 -0
  112. package/yida-skills/skills/yida-design/templates/design-themes/app-executive/design.md +239 -0
  113. package/yida-skills/skills/yida-design/templates/design-themes/app-executive/form-layout.json +13 -0
  114. package/yida-skills/skills/yida-design/templates/design-themes/app-finance/app_theme.css +570 -0
  115. package/yida-skills/skills/yida-design/templates/design-themes/app-finance/design.md +238 -0
  116. package/yida-skills/skills/yida-design/templates/design-themes/app-finance/form-layout.json +14 -0
  117. package/yida-skills/skills/yida-design/templates/design-themes/app-glass/app_theme.css +570 -0
  118. package/yida-skills/skills/yida-design/templates/design-themes/app-glass/design.md +239 -0
  119. package/yida-skills/skills/yida-design/templates/design-themes/app-glass/form-layout.json +14 -0
  120. package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/app_theme.css +570 -0
  121. package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/design.md +239 -0
  122. package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/form-layout.json +14 -0
  123. package/yida-skills/skills/yida-design/templates/design-themes/app-line/app_theme.css +570 -0
  124. package/yida-skills/skills/yida-design/templates/design-themes/app-line/design.md +239 -0
  125. package/yida-skills/skills/yida-design/templates/design-themes/app-line/form-layout.json +13 -0
  126. package/yida-skills/skills/yida-design/templates/design-themes/app-neon/app_theme.css +570 -0
  127. package/yida-skills/skills/yida-design/templates/design-themes/app-neon/design.md +237 -0
  128. package/yida-skills/skills/yida-design/templates/design-themes/app-neon/form-layout.json +14 -0
  129. package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/app_theme.css +570 -0
  130. package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/design.md +239 -0
  131. package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/form-layout.json +13 -0
  132. package/yida-skills/skills/yida-design/templates/design-themes/app-paper/app_theme.css +570 -0
  133. package/yida-skills/skills/yida-design/templates/design-themes/app-paper/design.md +237 -0
  134. package/yida-skills/skills/yida-design/templates/design-themes/app-paper/form-layout.json +13 -0
  135. package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/app_theme.css +570 -0
  136. package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/design.md +239 -0
  137. package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/form-layout.json +14 -0
  138. package/yida-skills/skills/yida-design/templates/design-themes/app-plum/app_theme.css +570 -0
  139. package/yida-skills/skills/yida-design/templates/design-themes/app-plum/design.md +239 -0
  140. package/yida-skills/skills/yida-design/templates/design-themes/app-plum/form-layout.json +14 -0
  141. package/yida-skills/skills/yida-design/templates/design-themes/app-pop/app_theme.css +570 -0
  142. package/yida-skills/skills/yida-design/templates/design-themes/app-pop/design.md +239 -0
  143. package/yida-skills/skills/yida-design/templates/design-themes/app-pop/form-layout.json +14 -0
  144. package/yida-skills/skills/yida-design/templates/design-themes/app-sage/app_theme.css +570 -0
  145. package/yida-skills/skills/yida-design/templates/design-themes/app-sage/design.md +239 -0
  146. package/yida-skills/skills/yida-design/templates/design-themes/app-sage/form-layout.json +14 -0
  147. package/yida-skills/skills/yida-design/templates/design-themes/app-teal/app_theme.css +570 -0
  148. package/yida-skills/skills/yida-design/templates/design-themes/app-teal/design.md +239 -0
  149. package/yida-skills/skills/yida-design/templates/design-themes/app-teal/form-layout.json +14 -0
  150. package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/app_theme.css +570 -0
  151. package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/design.md +238 -0
  152. package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/form-layout.json +14 -0
  153. package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/app_theme.css +570 -0
  154. package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/design.md +241 -0
  155. package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/form-layout.json +13 -0
  156. package/yida-skills/skills/yida-design/templates/design-themes/app-wire/app_theme.css +570 -0
  157. package/yida-skills/skills/yida-design/templates/design-themes/app-wire/design.md +238 -0
  158. package/yida-skills/skills/yida-design/templates/design-themes/app-wire/form-layout.json +14 -0
  159. package/yida-skills/skills/yida-design/templates/design-themes/basic-tokens.json +1 -7
  160. package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/app_theme.css +509 -0
  161. package/yida-skills/skills/yida-design/templates/design-themes/{dark-inset-hairline.md → dark-inset-hairline/design.md} +97 -58
  162. package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/form-layout.json +13 -0
  163. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/app_theme.css +517 -0
  164. package/yida-skills/skills/yida-design/templates/design-themes/{dark-rail-fine-lines.md → dark-rail-fine-lines/design.md} +98 -51
  165. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/form-layout.json +13 -0
  166. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-signal-panels/app_theme.css +515 -0
  167. package/yida-skills/skills/yida-design/templates/design-themes/{dark-rail-signal-panels.md → dark-rail-signal-panels/design.md} +59 -12
  168. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-signal-panels/form-layout.json +13 -0
  169. package/yida-skills/skills/yida-design/templates/design-themes/free-creative/app_theme.css +511 -0
  170. package/yida-skills/skills/yida-design/templates/design-themes/free-creative/design.md +202 -0
  171. package/yida-skills/skills/yida-design/templates/design-themes/free-creative/form-layout.json +13 -0
  172. package/yida-skills/skills/yida-design/templates/design-themes/graphite-bevel-grid/app_theme.css +511 -0
  173. package/yida-skills/skills/yida-design/templates/design-themes/{graphite-bevel-grid.md → graphite-bevel-grid/design.md} +55 -10
  174. package/yida-skills/skills/yida-design/templates/design-themes/graphite-bevel-grid/form-layout.json +13 -0
  175. package/yida-skills/skills/yida-design/templates/design-themes/hairline-shared-bands/app_theme.css +510 -0
  176. package/yida-skills/skills/yida-design/templates/design-themes/{hairline-shared-bands.md → hairline-shared-bands/design.md} +57 -12
  177. package/yida-skills/skills/yida-design/templates/design-themes/hairline-shared-bands/form-layout.json +13 -0
  178. package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/app_theme.css +510 -0
  179. package/yida-skills/skills/yida-design/templates/design-themes/{hairline-soft-blocks.md → hairline-soft-blocks/design.md} +52 -10
  180. package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/form-layout.json +13 -0
  181. package/yida-skills/skills/yida-design/templates/design-themes/index.json +399 -31
  182. package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/app_theme.css +520 -0
  183. package/yida-skills/skills/yida-design/templates/design-themes/{ink-glow-soft-panels.md → ink-glow-soft-panels/design.md} +53 -9
  184. package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/form-layout.json +13 -0
  185. package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/app_theme.css +510 -0
  186. package/yida-skills/skills/yida-design/templates/design-themes/{inset-frame-pixel-rhythm.md → inset-frame-pixel-rhythm/design.md} +55 -10
  187. package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/form-layout.json +13 -0
  188. package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/app_theme.css +517 -0
  189. package/yida-skills/skills/yida-design/templates/design-themes/{outlined-texture-duotone.md → outlined-texture-duotone/design.md} +56 -12
  190. package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/form-layout.json +13 -0
  191. package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/app_theme.css +510 -0
  192. package/yida-skills/skills/yida-design/templates/design-themes/{soft-inset-surfaces.md → soft-inset-surfaces/design.md} +54 -12
  193. package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/form-layout.json +13 -0
  194. package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/app_theme.css +509 -0
  195. package/yida-skills/skills/yida-design/templates/design-themes/{soft-outline-rhythm.md → soft-outline-rhythm/design.md} +54 -26
  196. package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/form-layout.json +13 -0
  197. package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/app_theme.css +517 -0
  198. package/yida-skills/skills/yida-design/templates/design-themes/{soft-rail-bold-band.md → soft-rail-bold-band/design.md} +94 -50
  199. package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/form-layout.json +13 -0
  200. package/yida-skills/skills/yida-design/templates/design-themes/soft-spectral-panels/app_theme.css +511 -0
  201. package/yida-skills/skills/yida-design/templates/design-themes/{soft-spectral-panels.md → soft-spectral-panels/design.md} +59 -13
  202. package/yida-skills/skills/yida-design/templates/design-themes/soft-spectral-panels/form-layout.json +13 -0
  203. package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/app_theme.css +530 -0
  204. package/yida-skills/skills/yida-design/templates/design-themes/{warm-canvas-contrast-panels.md → warm-canvas-contrast-panels/design.md} +55 -11
  205. package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/form-layout.json +13 -0
  206. package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/app_theme.css +511 -0
  207. package/yida-skills/skills/yida-design/templates/design-themes/{warm-rail-muted-panels.md → warm-rail-muted-panels/design.md} +94 -52
  208. package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/form-layout.json +13 -0
  209. package/yida-skills/skills/yida-design/workflow/output-design.md +44 -8
  210. package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +18 -5
  211. package/yida-skills/skills/yida-design/workflow/step-6-handoff.md +3 -1
  212. package/yida-skills/skills/yida-document-markdown/SKILL.md +0 -6
  213. package/yida-skills/skills/yida-export-conversation/SKILL.md +0 -1
  214. package/yida-skills/skills/yida-form-permission/SKILL.md +6 -0
  215. package/yida-skills/skills/yida-nav-shell/SKILL.md +5 -3
  216. package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +6 -0
  217. package/yida-skills/skills/yida-page-config/SKILL.md +9 -0
  218. package/yida-skills/skills/yida-prd/SKILL.md +2 -1
  219. package/yida-skills/skills/yida-prd/workflow/output-prd.md +2 -2
  220. package/yida-skills/skills/yida-prd/workflow/step-2-information-architecture.md +1 -1
  221. package/yida-skills/skills/yida-publish-page/SKILL.md +22 -18
  222. package/yida-skills/skills/yida-requirement-analysis/references/experience-groups.md +45 -9
  223. package/yida-skills/skills/yida-requirement-analysis/references/handoff.md +6 -6
  224. package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +17 -4
  225. package/yida-skills/skills/yida-tingji/SKILL.md +0 -4
  226. package/yida-skills/skills-index.json +4 -4
  227. package/lib/agent/PROTOCOL.md +0 -165
  228. package/lib/agent/ask-human.js +0 -151
  229. package/lib/agent/cmd.js +0 -403
  230. package/lib/agent/command-receipt.js +0 -84
  231. package/lib/agent/connection-store.js +0 -148
  232. package/lib/agent/diagnostic-log.js +0 -133
  233. package/lib/agent/frozen-bundle.js +0 -286
  234. package/lib/agent/managed-command-map.js +0 -117
  235. package/lib/agent/managed-context.js +0 -40
  236. package/lib/agent/managed-run.js +0 -96
  237. package/lib/agent/mutation-receipt.js +0 -82
  238. package/lib/agent/private-directory.js +0 -63
  239. package/lib/agent/provider-preflight.js +0 -236
  240. package/lib/agent/providers.js +0 -4
  241. package/lib/agent/runtime-catalog.json +0 -42
  242. package/lib/agent/runtime-manifest.js +0 -96
  243. package/lib/agent/runtime-package.js +0 -155
  244. package/lib/agent/stdio.js +0 -265
  245. package/lib/auth/profile-lock.js +0 -89
  246. package/yida-skills/references/local-agent-diagnostics.md +0 -154
  247. package/yida-skills/references/local-agent-preflight.md +0 -56
package/README.md CHANGED
@@ -118,6 +118,7 @@ The agent can then call OpenYida commands to create the application, generate so
118
118
  | [Codex](https://openai.com/codex/) | Full support |
119
119
  | [Claude Code](https://claude.ai/code) | Full support |
120
120
  | [MuleRun](https://mulerun.com) | Full support |
121
+ | [OpenCode](https://opencode.ai) | Full support |
121
122
  | [Cursor](https://cursor.com/) | Full support |
122
123
  | [Visual Studio Code](https://code.visualstudio.com/) | Full support |
123
124
  | QwenWork(千问办公) | Full support |
@@ -182,7 +183,7 @@ openyida get-schema APP_XXX FORM_XXX --compact --resolve-fields "Customer Name,S
182
183
  openyida get-schema APP_XXX --all --output-dir .cache/schemas
183
184
  ```
184
185
 
185
- Form definitions support 19 business field types plus verified presentation/layout components. Prefer `Divider` for section titles and `ColumnContainer` (mapped to `ColumnsLayout` + `Column`) for multi-column layout; use `GroupContainer` / `PageSection` only when an actual grouping container is needed.
186
+ Native forms use a component tree: business fields capture data, tabs switch content, action groups trigger business actions, images establish visual focus, status areas provide feedback, and `Divider` plus `ColumnContainer` organize hierarchy and columns. `create-form` field definitions create 19 business field types plus `Divider` and `ColumnContainer` (mapped to `ColumnsLayout` + `Column`), while updates retain other existing components.
186
187
 
187
188
  ### Custom Page Development
188
189
 
@@ -392,13 +393,15 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
392
393
 
393
394
  | Command | Description |
394
395
  |---------|-------------|
396
+ | `openyida app-entry get <appType> [--json]` | Read frontend and management runtime entries |
397
+ | `openyida app-entry set <appType> [--frontend <url>\|--frontend-page <formUuid>] [--management <url>\|--management-page <formUuid>] [--clear-frontend] [--clear-management] [--json]` | Register application entries with partial update and readback |
395
398
  | `openyida app-list [--type managed\|created] [--page N] [--size N]` | Page through apps I manage or created |
396
399
  | `openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]` | Query enterprise efficiency overview and detail reports |
397
400
  | `openyida create-app "<name>"\|--name <name> [options] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a Yida app |
398
401
  | `openyida design-plan catalog [--json]` | List available planning themes and page patterns |
399
402
  | `openyida design-plan init <requirement-brief.json> [--theme-id <id>] [--output-dir <dir>] [--json]` | Initialize a plan draft from confirmed requirements |
400
403
  | `openyida design-plan preview <build-plan.json> --part-file <module.json> [--json]` | Update plan drafts by module |
401
- | `openyida design-plan materialize <build-plan.json> [--from-preview \| --business-file <json> --visual-file <json>] [--output-dir <dir>] [--check] [--json]` | Generate or update plan documents and theme with consistency checks |
404
+ | `openyida design-plan materialize <build-plan.json> [--from-preview \| --business-file <json> --visual-file <json>] [--output-dir <dir>] [--rebase-parts] [--check] [--json]` | Generate or update plan documents and theme with consistency checks |
402
405
  | `openyida design-plan patch <build-plan.json> --set <path=value> [--set <path=value> ...] [--materialize] [--output-dir <dir>] [--json]` | Patch plan fields and optionally sync documents and theme |
403
406
  | `openyida update-app <appType> [--name "..."] [--desc "..."] [--icon <name>] [--icon-color <color>] [--colour <key>] [--theme-color <color>] [--theme-file <css>] [--nav-theme light\|dark\|white\|gray] [--logo-source appIcon\|customImage] [--layout side\|top\|l_shape] [--hide-app-nav\|--show-app-nav]` | Update app info |
404
407
  | `openyida app-online <appType> [--to-ding-app-center] [--show-app-center]` | Enable a Yida app |
@@ -414,7 +417,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
414
417
  | Command | Description |
415
418
  |---------|-------------|
416
419
  | `openyida create-form batch <appType> <plan.json> [--concurrency 1..4] [--check] [--json]` | Create forms concurrently by dependency |
417
- | `openyida create-form create <appType> "<formTitle>" <fieldsJsonFile> [--icon auto\|<iconName>] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a form page |
420
+ | `openyida create-form create <appType> "<formTitle>" <fieldsJsonFile> [--layout single\|double\|card\|section] [--theme default\|compact\|comfortable] [--label-align top\|left\|right] [--icon auto\|<iconName>] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a form page |
418
421
  | `openyida create-form icons [--json]` | List available form navigation icons |
419
422
  | `openyida create-form validate-fields <fieldsJsonOrFile> [--json]` | Validate form field JSON locally |
420
423
  | `openyida create-form update <appType> <formUuid> (<changesJsonOrFile> \| --data-file <changesJsonOrFile>) [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Update a form page |
@@ -434,8 +437,8 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
434
437
  | `openyida create-page <appType> "<name>" [--mode dashboard] [--hide-nav] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a custom display page |
435
438
  | `openyida build-page <sourceFile> [--output file\|--write]` | Build Yida-compatible page source |
436
439
  | `openyida check-page <src> [--compat] [--json]` | Check custom page standards |
437
- | `openyida compile <src> [--canvas] [--compat] [--skip-lint] [--json]` | Compile custom page locally |
438
- | `openyida publish <src> <appType> <formUuid> [--health-check] [--force] [--canvas] [--compat] [--skip-lint] [--auto-nav-order] [--open\|--no-open] [--json]` | Compile and publish custom page |
440
+ | `openyida compile <src> [--canvas] [--compat] [--skip-lint] [--strict-theme\|--allow-fixed-brand] [--json]` | Compile custom page locally |
441
+ | `openyida publish <src> <appType> <formUuid> [--health-check] [--force] [--canvas] [--compat] [--skip-lint] [--fix-theme] [--strict-theme] [--allow-fixed-brand] [--auto-nav-order] [--open\|--no-open] [--json]` | Compile and publish custom page |
439
442
  | `openyida update-form-config <appType> <formUuid> <true\|false\|keep> "<title>" [--locale zh_CN\|en_US\|ja_JP]` | Update form configuration |
440
443
  | `openyida get-form-config <appType> <formUuid> [--json]` | Query form configuration |
441
444
 
@@ -519,11 +522,10 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
519
522
  |---------|-------------|
520
523
  | `openyida commands [--json]` | Output machine-readable command manifest |
521
524
  | `openyida agent-capabilities [--json] [--summary-json\|--compact]` | Output one-shot agent capability snapshot |
522
- | `openyida agent <doctor\|diagnose\|status\|connect\|run\|disconnect\|logout> [options]` | Connect a local Agent (Go runtime, development preview) |
523
525
  | `openyida a2a <serve\|agent-card> [options]` | Start local read-only A2A adapter or print Agent Card |
524
526
  | `openyida bridge start [--token <pair-token>] [--port 6736] [--origin https://demo.aliwork.com] [--open\|--no-open]` | Start OpenYida local web bridge service |
525
527
  | `openyida copy [--force]` | Copy project working directory |
526
- | `openyida sample [--list] [<skill> <name>] [--output <file>] [--var KEY=VALUE ...] [--design-file <design.md>]` | Output code samples/templates |
528
+ | `openyida sample [--list] [<skill> <name>] [--output <file-or-directory>] [--var KEY=VALUE ...] [--design-file <design.md>] [--style-id <themeId\|free-creative>]` | Output code samples/templates |
527
529
  | `openyida doctor [--fix]` | Environment diagnostics & auto-fix |
528
530
  | `openyida db-seq-fix [--fix]` | Detect and repair PostgreSQL sequence drift |
529
531
  | `openyida formula evaluate <formula\|file> [--schema file]` | Static-check Yida formula syntax and field refs |
@@ -588,31 +590,6 @@ Form field definitions can include `alias` or `componentAlias` to populate Yida
588
590
 
589
591
  `openyida integration create` supports form events (`insert`, `update`, `delete`, `comment`) and approval events (`processFinish`, `activityTask`; aliases: `approval`, `approvalNode`). Approval events require `--approval-actions agree,disagree,terminated`; `activityTask` also requires `--approval-node-ids <nodeId,...>`. Passing `--process-code` selects a full graph replacement and now requires explicit `--replace`; it is not a safe update. `integration update` currently performs capability detection only: because full platform `processJson` + `viewJson` readback is unproven, it writes a redacted local probe artifact and returns `PLATFORM_PROBE_REQUIRED` before authentication, spec reading, or remote writes. It does not edit a flow.
590
592
 
591
- ### Local Agent development preview
592
-
593
- `openyida agent` is a thin Node launcher for a separately supplied Go runtime. Node does not connect to the agent service or manage provider sessions. Platform runtime packages are **not published** in this preview; no Go executable is discovered on `PATH` and no provider is installed or invoked by Node.
594
-
595
- The Local Agent Beta production allowlist contains macOS and Windows only. Linux remains supported by the ordinary OpenYida CLI and by the explicit development-runtime workflow below, but Linux Local Agent packages are not published and production `connect`/`run` are rejected until native acceptance is complete.
596
-
597
- ```bash
598
- openyida agent doctor --json
599
- openyida agent doctor --development-runtime --runtime-path /absolute/path/openyida-runtime --runtime-manifest /absolute/path/runtime-manifest.json
600
- openyida agent connect --development-runtime --runtime-path /absolute/path/openyida-runtime --runtime-manifest /absolute/path/runtime-manifest.json --endpoint https://agent.example.test --endpoint-id configured-platform-id
601
- openyida agent run --development-runtime --runtime-path /absolute/path/openyida-runtime --runtime-manifest /absolute/path/runtime-manifest.json --endpoint https://agent.example.test --endpoint-id configured-platform-id --provider qoder --provider-path /absolute/path/qodercli
602
- ```
603
-
604
- Development manifest v1 specifies runtime version, protocol `1`, Node-style OS/architecture names, and `binary: {file, sha256}`; it requires the explicit `--development-runtime` flag. This is an integrity check, **not a publisher signature**. The production loader requires a strictly validated v2 manifest, exact approved platform-package version/digest, and an Ed25519 signature over the original manifest bytes using trust roots bundled in the Node host. The release catalog is currently empty: no production signing key or package is implied to exist. See the [launcher contract](lib/agent/PROTOCOL.md).
605
-
606
- `connect` supports two equivalent enrollment entries. CLI-first opens the Yida browser approval page and returns through a state/PKCE-protected loopback callback. Web-first copies one five-minute command containing `--enroll`; the token is single-use and carries no business authority. Neither path reads or uploads an ordinary OpenYida login profile.
607
-
608
- One installation may connect to multiple organizations. Node keeps only a shared Installation ID at the installation root; each organization authorization gets an isolated private Connection state directory, device credentials, journals, control socket, and frozen bundle. `agent connect` keeps the new Connection online in the foreground, while `agent run`, `status`, `disconnect`, and `logout` enumerate every valid Connection. Each Connection registers all locally discovered Qoder and Codex Runtimes, and the web UI selects computer, CLI, and model.
609
-
610
- For each attempt, Go injects `OPENYIDA_MANAGED_RUN=1`, the cloud user/corp/app/run/attempt context, and a fixed-value Tianshu task grant with an initial 60-minute lease. The CLI uses that grant instead of local auth and sends scope-correlation headers. While the authenticated device connection and server-side Run remain active, yida-agent can renew the same bearer server-to-server; the CLI cannot renew it and never receives a task refresh token. Tianshu independently checks the grant against the requested app and rejects application creation, identity/environment changes, arbitrary batches, and cross-application requests. `formUuid` remains optional per-turn page-focus context, so one app-bound session can work across pages in that application. Without `OPENYIDA_MANAGED_RUN`, existing login, token refresh, and command behavior are unchanged.
611
-
612
- Before `connect` or `run`, Node now freezes the verified runtime bytes, CLI, full production dependency closure, and packaged Skills into a private content-addressed bundle. Source changes during copying fail closed. Existing bundles are verified, never silently repaired or garbage-collected by Node, and survive replacement/removal of the original npm directory. Go owns active/session references and version activation; the external Node executable and provider installations are not copied or managed by npm. A separate parent-watch pipe and a 15-second stop grace prevent normal config EOF from being mistaken for parent death; an unconfirmed forced stop remains unknown.
613
-
614
- This is not an OS sandbox or an authorization boundary against arbitrary shell code. Production signing/release infrastructure, full Go/provider recovery and upgrade acceptance, and OS/architecture-specific provider E2E are not claimed by these fake-process tests. Auto-update is disabled for `agent` and managed invocations; ordinary CLI commands neither spawn nor download the runtime.
615
-
616
593
  ## Agent Skills
617
594
 
618
595
  The `yida-skills/` directory is the source skill library used by OpenYida during development and distributed with the npm package.
package/bin/yida.js CHANGED
@@ -24,8 +24,6 @@ const { COMMAND_GROUPS, buildCommandManifest, findCommandSuggestion } = require(
24
24
  const command = process.argv[2];
25
25
  const args = process.argv.slice(3);
26
26
  const rawArgs = process.argv.slice(3);
27
- const { beginCommandReceipt } = require('../lib/agent/command-receipt');
28
- let commandReceipt;
29
27
 
30
28
  function shouldUseEnvManagement(argsList) {
31
29
  const subCommand = argsList[0];
@@ -573,26 +571,6 @@ function assertNoUnsupportedLegacyLoginFlags(...argLists) {
573
571
  }
574
572
 
575
573
  async function main() {
576
- const { assertManagedInvocation, resolveManagedHelp } = require('../lib/agent/managed-run');
577
- const { readManagedContext } = require('../lib/agent/managed-context');
578
- if (readManagedContext() && [command, ...rawArgs].some(arg => arg === '--help' || arg === '-h')) {
579
- assertManagedInvocation(command, rawArgs);
580
- const entries = resolveManagedHelp(command, rawArgs, { t, version: currentVersion });
581
- // Managed help is static and local: no Receipt, auth, update, first-run
582
- // guide or business command module may run on this path.
583
- commandReceipt = { finish() {} };
584
- if (rawArgs.includes('--json')) {
585
- console.log(JSON.stringify({ name: 'openyida', version: currentVersion, commands: entries }, null, 2));
586
- } else {
587
- printCommandUsage(...entries.flatMap(entry => [entry.usage, entry.description, ...(entry.examples || [])]));
588
- }
589
- return;
590
- }
591
- commandReceipt = beginCommandReceipt({ command, args: rawArgs });
592
- process.once('exit', code => {
593
- try {commandReceipt.finish(code);} catch {process.exitCode = 1;}
594
- });
595
- assertManagedInvocation(command, rawArgs);
596
574
  applyQuietFlag();
597
575
  applyGlobalEnvironmentFlags();
598
576
 
@@ -641,12 +619,6 @@ async function main() {
641
619
  break;
642
620
  }
643
621
 
644
- case 'agent': {
645
- const { run } = require('../lib/agent/cmd');
646
- await run(args);
647
- break;
648
- }
649
-
650
622
  case 'mcp': {
651
623
  const { runStdioServer } = require('../lib/mcp/server');
652
624
  runStdioServer();
@@ -761,6 +733,12 @@ async function main() {
761
733
  break;
762
734
  }
763
735
 
736
+ case 'app-entry': {
737
+ const { run } = require('../lib/app/app-entry');
738
+ await run(args);
739
+ break;
740
+ }
741
+
764
742
  case 'app-list': {
765
743
  const { run } = require('../lib/app/app-list');
766
744
  await run(args);
@@ -916,8 +894,8 @@ async function main() {
916
894
  if (args.length < 4) {
917
895
  throwCliUsage(t('cli.form_config_usage'), t('cli.form_config_example'));
918
896
  }
919
- const { run: runUpdateFormConfig } = require('../lib/app/update-form-config');
920
- await runUpdateFormConfig(args);
897
+ process.argv = [process.argv[0], process.argv[1], ...args];
898
+ require('../lib/app/update-form-config');
921
899
  break;
922
900
  }
923
901
 
@@ -1303,9 +1281,7 @@ async function main() {
1303
1281
  }
1304
1282
 
1305
1283
  main()
1306
- .then(() => { commandReceipt.finish(process.exitCode || 0); })
1307
1284
  .catch((err) => {
1308
- try {commandReceipt?.finish(err && err.exitCode ? err.exitCode : 1);} catch { /* Intent remains recoverable. */ }
1309
1285
  if (shouldUseStructuredErrorOutput(err, args)) {
1310
1286
  console.error(JSON.stringify(toErrorPayload(err), null, 2));
1311
1287
  } else if (isCliError(err)) {
@@ -1316,5 +1292,7 @@ main()
1316
1292
  } else {
1317
1293
  warn(t('cli.exec_failed', err.message));
1318
1294
  }
1319
- process.exit(err && err.exitCode ? err.exitCode : 1);
1295
+ // Let pending stdout/stderr writes drain, especially large JSON diagnostics
1296
+ // sent through pipes on macOS/Linux. process.exit() can truncate them.
1297
+ process.exitCode = err && err.exitCode ? err.exitCode : 1;
1320
1298
  });
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ const { createAuthRef, createYidaClient, unwrapYidaResponse } = require('../core/yida-client');
4
+ const { t } = require('../core/i18n');
5
+ const { throwUsage } = require('../core/command-errors');
6
+ const { fetchFormPageList } = require('./form-navigation');
7
+
8
+ /** Keep complete runtime URLs; the server verifies tenant and resource ownership. */
9
+ function normalizeUrl(value, appType) {
10
+ const input = String(value || '').trim();
11
+ if (!input) { throw new Error(t('app_entry.invalid_url')); }
12
+ // eslint-disable-next-line no-control-regex -- reject control characters in persisted URLs
13
+ if (input.length > 2048 || /[\\\s\x00-\x1f\x7f]/.test(input)) {
14
+ throw new Error(t('app_entry.invalid_url'));
15
+ }
16
+ const match = /^https?:\/\/[^/?#]+(\/[^?#]*)(?:\?([^#]*))?(?:#(.*))?$/i.exec(input);
17
+ if (!match) { throw new Error(t('app_entry.invalid_url')); }
18
+ const url = new URL(input);
19
+ const route = match[1];
20
+ const prefix = `/${appType}/`;
21
+ const parts = route.startsWith(prefix) ? route.slice(prefix.length).split('/') : [];
22
+ const short = /^\/(?:s\/[A-Za-z0-9_-]+|o\/[A-Za-z0-9_-]+(?:\/FORM-[A-Za-z0-9_-]+)?)$/.test(route);
23
+ const runtime = ['custom', 'workbench', 'submission', 'manage'].includes(parts[0]) &&
24
+ ((parts.length === 1 && parts[0] === 'workbench') ||
25
+ (parts.length === 2 && /^[A-Za-z0-9_-]+$/.test(parts[1])));
26
+ if (url.username || url.password || url.pathname !== route || (!short && !runtime)) {
27
+ throw new Error(t('app_entry.invalid_url'));
28
+ }
29
+ if (match[2] !== undefined) {
30
+ const seen = new Set();
31
+ for (const parameter of match[2].split('&')) {
32
+ const pair = /^(corpid|locale|viewUuid|processCode|hideLeftNav|noShowTopBottom)=([A-Za-z0-9_-]+)$/.exec(parameter);
33
+ if (!pair || seen.has(pair[1]) ||
34
+ (['hideLeftNav', 'noShowTopBottom'].includes(pair[1]) && !/^(true|false|y|n|0|1)$/.test(pair[2]))) {
35
+ throw new Error(t('app_entry.invalid_url'));
36
+ }
37
+ seen.add(pair[1]);
38
+ }
39
+ }
40
+ if (match[3] !== undefined && !/^[A-Za-z0-9_/-]+$/.test(match[3])) {
41
+ throw new Error(t('app_entry.invalid_url'));
42
+ }
43
+ return input;
44
+ }
45
+
46
+ /** Resolve a page identifier into the canonical entry URL the platform expects. */
47
+ async function resolveEntryUrl(authRef, appType, key, formUuid) {
48
+ if (!/^[A-Za-z0-9_-]+$/.test(String(formUuid || ''))) {
49
+ throw new Error(t('app_entry.invalid_url'));
50
+ }
51
+ const forms = await fetchFormPageList(appType, authRef);
52
+ const form = Array.isArray(forms) ? forms.find((item) => item.formUuid === formUuid) : null;
53
+ if (!form) { throw new Error(t('app_entry.page_not_in_app', formUuid)); }
54
+ if (key === 'frontend') {
55
+ if (String(form.formType).toLowerCase() !== 'display') { throw new Error(t('app_entry.frontend_display_required', formUuid)); }
56
+ if (!form.pathName) { throw new Error(t('app_entry.page_offline', formUuid)); }
57
+ return normalizeUrl(`${authRef.baseUrl}/${appType}/custom/${formUuid}`, appType);
58
+ }
59
+ return normalizeUrl(`${authRef.baseUrl}/${appType}/workbench/${formUuid}`, appType);
60
+ }
61
+
62
+ /** Reject duplicate or ambiguous flags; omissions never clear stored entries. */
63
+ function parseArgs(args) {
64
+ const [action, appType, ...flags] = args;
65
+ if (!['get', 'set'].includes(action) || !/^[A-Za-z0-9_-]+$/.test(appType || '')) {
66
+ throwUsage(t('app_entry.usage'));
67
+ }
68
+ const values = {};
69
+ for (let index = 0; index < flags.length; index++) {
70
+ const flag = flags[index];
71
+ if (flag === '--json') {
72
+ continue;
73
+ }
74
+ const match = /^--(clear-)?(frontend|management)(-page)?$/.exec(flag);
75
+ if (action !== 'set' || !match || (match[1] && match[3]) ||
76
+ Object.prototype.hasOwnProperty.call(values, match[2])) {
77
+ throwUsage(t('app_entry.usage'));
78
+ }
79
+ const kind = match[1] ? 'clear' : (match[3] ? 'page' : 'url');
80
+ const value = kind === 'clear' ? null : flags[++index];
81
+ if (kind !== 'clear' && (!value || value.startsWith('--'))) {
82
+ throwUsage(t('app_entry.usage'));
83
+ }
84
+ values[match[2]] = { kind, value };
85
+ }
86
+ if (action === 'set' && !Object.keys(values).length) {
87
+ throwUsage(t('app_entry.usage'));
88
+ }
89
+ return { action, appType, values };
90
+ }
91
+
92
+ /** Read the revision used for a conditional partial write. */
93
+ async function readEntries(client, appType) {
94
+ const result = unwrapYidaResponse(await client.get(`/${appType}/query/app/getAccessEntries.json`));
95
+ if (!result || typeof result.revision !== 'string' || !result.revision ||
96
+ !result.accessEntries || typeof result.accessEntries !== 'object' || Array.isArray(result.accessEntries)) {
97
+ throw new Error(t('app_entry.invalid_response'));
98
+ }
99
+ return result;
100
+ }
101
+
102
+ /** Persist only supplied entries and verify them through a separate read. */
103
+ async function run(args = []) {
104
+ if (args.includes('--help') || args.includes('-h')) {
105
+ console.log(t('app_entry.usage'));
106
+ return;
107
+ }
108
+ const { action, appType, values } = parseArgs(args);
109
+ const authRef = createAuthRef();
110
+ const client = createYidaClient({ authRef });
111
+ const patch = {};
112
+ for (const [key, spec] of Object.entries(values)) {
113
+ if (spec.kind === 'clear') {
114
+ patch[key] = null;
115
+ } else if (spec.kind === 'page') {
116
+ patch[key] = { url: await resolveEntryUrl(authRef, appType, key, spec.value) };
117
+ } else {
118
+ const url = normalizeUrl(spec.value, appType);
119
+ patch[key] = { url };
120
+ }
121
+ }
122
+ let result = await readEntries(client, appType);
123
+ if (action === 'set') {
124
+ unwrapYidaResponse(await client.postForm(`/${appType}/query/app/saveAccessEntries.json`, {
125
+ accessEntries: JSON.stringify(patch), revision: result.revision,
126
+ }));
127
+ result = await readEntries(client, appType);
128
+ for (const [key, entry] of Object.entries(patch)) {
129
+ if ((result.accessEntries[key]?.url || '') !== (entry?.url || '')) {
130
+ throw new Error(t('app_entry.readback_failed'));
131
+ }
132
+ }
133
+ }
134
+ const urls = {};
135
+ for (const key of ['frontend', 'management']) {
136
+ if (result.accessEntries[key]?.url) {
137
+ urls[key] = normalizeUrl(result.accessEntries[key].url, appType);
138
+ }
139
+ }
140
+ console.log(JSON.stringify({ appType, ...result, urls }, null, 2));
141
+ return result;
142
+ }
143
+
144
+ module.exports = { run, parseArgs, normalizeUrl, resolveEntryUrl };
@@ -84,6 +84,7 @@ function formatApp(app, baseUrl) {
84
84
  appName: (app.appName && app.appName.zh_CN) || '',
85
85
  appType: app.appType || '',
86
86
  systemLink: app.systemLink || '',
87
+ ...(app.accessEntries ? { accessEntries: app.accessEntries } : {}),
87
88
  ...buildApplicationEntryUrls({ appType: app.appType, systemLink: app.systemLink, baseUrl }),
88
89
  };
89
90
  }
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { CliError } = require('../core/cli-error');
6
+ const { t } = require('../core/i18n');
7
+ const { DESIGN_SKILL_ROOT, loadThemeIndex } = require('../design-plan/themes');
8
+ const START = '/* OPENYIDA APPLICATION STYLE RECIPES START';
9
+ const END = '/* OPENYIDA APPLICATION STYLE RECIPES END */';
10
+ const CREATIVE_TOKENS = [
11
+ '--pod-page-bg-color', '--pod-card-bg-color', '--color-text1-4',
12
+ '--form-element-medium-corner', '--form-element-medium-height',
13
+ '--pod-page-footer-bg-color', '--pod-sticky-footer-box-shadow',
14
+ '--oyd-content-width', '--oyd-content-padding', '--oyd-field-gap',
15
+ '--oyd-heading-font', '--oyd-heading-size', '--oyd-rule-style',
16
+ ];
17
+
18
+ function validateCreativeDirection(direction, tokens) {
19
+ const missing = ['businessRationale', 'composition', 'typography', 'material', 'formLayout']
20
+ .filter(key => typeof direction?.[key] !== 'string' || !direction[key].trim());
21
+ missing.push(...CREATIVE_TOKENS.filter(key => typeof tokens?.[key] !== 'string' || !tokens[key].trim()));
22
+ if (missing.length) {
23
+ throw new CliError(t('design_document.invalid', 'creativeDirection', missing.join(', ')), {
24
+ code: 'APPLICATION_STYLE_CREATIVE_INCOMPLETE', details: { missing },
25
+ });
26
+ }
27
+ }
28
+
29
+ function applyApplicationStyle(css, metadata, tokens) {
30
+ const style = metadata.applicationStyle;
31
+ if (style && (style.recipe !== 'application-style-v1' || !['template', 'creative'].includes(style.mode))) {
32
+ throw new CliError(t('design_document.invalid', 'applicationStyle', 'APPLICATION_STYLE_INVALID'), { code: 'APPLICATION_STYLE_INVALID' });
33
+ }
34
+ if (style?.mode === 'creative') {validateCreativeDirection(metadata.creativeDirection, tokens);}
35
+ const first = css.indexOf(START);
36
+ const last = css.indexOf(END);
37
+ if (first >= 0 && last < first) {
38
+ throw new CliError(t('design_document.invalid', 'app_theme.css', 'APPLICATION_STYLE_CSS_INVALID'), { code: 'APPLICATION_STYLE_CSS_INVALID' });
39
+ }
40
+ const recipe = style
41
+ ? fs.readFileSync(path.join(DESIGN_SKILL_ROOT, 'references/theme/application-style-recipes.css'), 'utf8').trim()
42
+ : '';
43
+ // Only the managed recipe block is replaced. User-authored rules stay intact.
44
+ if (first >= 0) {return css.slice(0, first) + recipe + css.slice(last + END.length);}
45
+ return recipe ? `${css.trimEnd()}\n\n${recipe}\n` : css;
46
+ }
47
+
48
+ function exportApplicationStyle(id, outputDirectory) {
49
+ const theme = loadThemeIndex().themes.find(item => item.themeId === id);
50
+ if (!theme) {throw new CliError(t('design_document.invalid', id || '--style-id', 'APPLICATION_STYLE_UNKNOWN'), { code: 'APPLICATION_STYLE_UNKNOWN' });}
51
+ const directory = path.resolve(outputDirectory);
52
+ const assets = [['design.md', theme.templatePath], ['app_theme.css', theme.cssTemplatePath], ['form-layout.json', theme.formLayoutPath]];
53
+ const outputs = assets.map(([name, relative]) => {
54
+ const source = path.resolve(DESIGN_SKILL_ROOT, relative);
55
+ const target = path.join(directory, name);
56
+ const root = path.resolve(DESIGN_SKILL_ROOT, 'templates/design-themes') + path.sep;
57
+ if (!source.startsWith(root) || fs.existsSync(target)) {
58
+ throw new CliError(t('design_document.invalid', target, 'APPLICATION_STYLE_OUTPUT_CONFLICT'), { code: 'APPLICATION_STYLE_OUTPUT_CONFLICT' });
59
+ }
60
+ return { source, target, content: fs.readFileSync(source, 'utf8') };
61
+ });
62
+ fs.mkdirSync(directory, { recursive: true });
63
+ for (const item of outputs) {fs.writeFileSync(item.target, item.content, { flag: 'wx' });}
64
+ return { success: true, mode: theme.mode, themeId: id, authoringRequired: true, outputs: outputs.map(item => item.target) };
65
+ }
66
+
67
+ module.exports = { applyApplicationStyle, validateCreativeDirection, CREATIVE_TOKENS, exportApplicationStyle };
@@ -976,7 +976,12 @@ function compileCanvasLocal(source, options = {}) {
976
976
  assertCanvasNavigationStructure(source, options);
977
977
  assertCanvasNavigationPaths(source, options);
978
978
  assertInlineCssStructure(source, options);
979
- assertCanvasThemeStructure(source, options);
979
+ const warnings = [];
980
+ assertCanvasThemeStructure(source, { ...options, onThemeWarning(message, details) {
981
+ warnings.push({ ...details, message });
982
+ if (options.onThemeWarning) { options.onThemeWarning(message, details); }
983
+ else { require('../core/chalk').warn(message); }
984
+ } });
980
985
 
981
986
  const importedModules = extractImportedModules(source);
982
987
 
@@ -1036,6 +1041,7 @@ function compileCanvasLocal(source, options = {}) {
1036
1041
  return {
1037
1042
  runtimeCode,
1038
1043
  importedModules: JSON.stringify(importedModules),
1044
+ warnings,
1039
1045
  };
1040
1046
  }
1041
1047
 
@@ -2,8 +2,32 @@
2
2
 
3
3
  const Babel = require('@babel/standalone');
4
4
  const { CliError } = require('../core/cli-error');
5
+ const { warn } = require('../core/chalk');
5
6
  const { t } = require('../core/i18n');
6
7
 
8
+ // Whether provably fixed brand overrides should be downgraded from a hard
9
+ // failure to a warning while preserving the source override.
10
+ function isTruthy(value) {
11
+ return ['1', 'true', 'yes', 'on'].includes(String(value || '').trim().toLowerCase());
12
+ }
13
+
14
+ function allowFixedBrand(options = {}, env = process.env) {
15
+ return options.allowFixedBrand === true || isTruthy(env.OPENYIDA_CANVAS_ALLOW_FIXED_BRAND);
16
+ }
17
+
18
+ // Whether fixed brand overrides should hard-block publishing. Off by default so
19
+ // the guard warns and retains fixed overrides. Teams
20
+ // that want CI enforcement opt in via --strict-theme / OPENYIDA_CANVAS_STRICT_THEME.
21
+ function strictFixedBrand(options = {}, env = process.env) {
22
+ return options.strictBrand === true || isTruthy(env.OPENYIDA_CANVAS_STRICT_THEME);
23
+ }
24
+
25
+ function emitThemeWarning(options, message, details) {
26
+ if (typeof options.onThemeWarning === 'function') { options.onThemeWarning(message, details); return; }
27
+ warn(message);
28
+ }
29
+
30
+
7
31
  // Check the maintained theme contract without executing business code.
8
32
  // Dynamic component factories and runtime branches still need browser verification.
9
33
  function assertCanvasThemeStructure(source, options = {}) {
@@ -23,6 +47,10 @@ function assertCanvasThemeStructure(source, options = {}) {
23
47
  const traverse = Babel.packages.traverse.default || Babel.packages.traverse;
24
48
  assertBrandOverrides(ast, traverse, options);
25
49
  const functions = new Map();
50
+ const migrationSites = [];
51
+ const migrationEdges = [];
52
+ const migrationUses = options.requireThemeProvider
53
+ ? new Set(collectBrandOverrides(ast, traverse).map(item => item.usageStart)) : null;
26
54
  let program;
27
55
  let defaultExport;
28
56
  const fail = (kind, node) => {
@@ -64,6 +92,17 @@ function assertCanvasThemeStructure(source, options = {}) {
64
92
  return null;
65
93
  }
66
94
 
95
+ function insideProvider(p) {
96
+ let parent = p.isJSXOpeningElement() ? p.parentPath.parentPath : p.parentPath;
97
+ for (; parent && !parent.isFunction(); parent = parent.parentPath) {
98
+ if (parent.isJSXElement()) {
99
+ const component = resolveFunction(parent.get('openingElement.name'));
100
+ if (functions.get(component)?.name === 'CanvasThemeProvider') { return true; }
101
+ }
102
+ }
103
+ return false;
104
+ }
105
+
67
106
  traverse(ast, {
68
107
  CallExpression(p) {
69
108
  const owner = functions.get(p.getFunctionParent()?.node);
@@ -75,6 +114,9 @@ function assertCanvasThemeStructure(source, options = {}) {
75
114
  }
76
115
  if (!owner) { return; }
77
116
  if (target) { owner.calls.push(target); }
117
+ if (migrationUses && target) {
118
+ migrationEdges.push({ from: p.getFunctionParent().node, to: target, covered: insideProvider(p) });
119
+ }
78
120
  const callee = p.node.callee;
79
121
  if (callee.type === 'MemberExpression' && callee.object.name === 'React' && callee.property.name === 'createElement') {
80
122
  const component = resolveFunction(p.get('arguments.0'));
@@ -85,11 +127,19 @@ function assertCanvasThemeStructure(source, options = {}) {
85
127
  const owner = functions.get(p.getFunctionParent()?.node);
86
128
  const component = resolveFunction(p.get('name'));
87
129
  if (owner && component) { owner.renders.push(component); }
130
+ if (migrationUses) {
131
+ const from = p.getFunctionParent()?.node;
132
+ if (component && from) { migrationEdges.push({ from, to: component, covered: insideProvider(p) }); }
133
+ if (migrationUses.has(p.node.start)) { migrationSites.push({ owner: from, covered: insideProvider(p), node: p.node }); }
134
+ }
88
135
  },
89
136
  });
90
137
 
91
138
  const entry = resolveFunction(program.scope.getBinding('YidaComp')?.path) || resolveFunction(defaultExport);
92
- if (!entry) { return; }
139
+ if (!entry) {
140
+ if (options.requireThemeProvider) { fail('provider_missing', defaultExport?.node); }
141
+ return;
142
+ }
93
143
 
94
144
  // Calling a hook before returning a Provider cannot read that Provider.
95
145
  const directSeen = new Set();
@@ -118,14 +168,36 @@ function assertCanvasThemeStructure(source, options = {}) {
118
168
  }
119
169
  visit(entry);
120
170
  const hasProvider = [...functions.values()].some(info => info.name === 'CanvasThemeProvider');
121
- if (!providerMounted && (hasProvider || firstHook)) {
171
+ if (!providerMounted && (options.requireThemeProvider || hasProvider || firstHook)) {
122
172
  fail('provider_missing', firstHook || entry);
123
173
  }
174
+ if (migrationUses) {
175
+ // A sibling provider does not supply context to the migrated controls.
176
+ // Unknown/dynamic render paths are left for manual migration.
177
+ const checked = new Map();
178
+ const checkScope = (node, covered) => {
179
+ const states = checked.get(node) || new Set();
180
+ if (states.has(covered)) { return; }
181
+ states.add(covered); checked.set(node, states);
182
+ for (const site of migrationSites.filter(site => site.owner === node)) {
183
+ if (!covered && !site.covered) { fail('provider_missing', site.node); }
184
+ }
185
+ migrationEdges.filter(edge => edge.from === node).forEach(edge => checkScope(edge.to, covered || edge.covered));
186
+ };
187
+ checkScope(entry, false);
188
+ for (const site of migrationSites) {
189
+ if (!checked.has(site.owner)) { fail('provider_missing', site.node); }
190
+ }
191
+ }
124
192
  }
125
193
 
126
- // Only reject provably fixed brand colors passed to antd's ConfigProvider.
127
- // Semantic colors, layout tokens and dynamic theme resolvers remain supported.
128
- function assertBrandOverrides(ast, traverse, options) {
194
+ // Collect every provably fixed brand color passed to antd's ConfigProvider.
195
+ // Semantic colors, layout tokens and dynamic theme resolvers are ignored.
196
+ // Each entry keeps the offending property's char offsets so the theme codemod
197
+ // can surgically remove it while the guard reuses the same detection to fail.
198
+ function collectBrandOverrides(ast, traverse) {
199
+ const violations = [];
200
+ let usageStart;
129
201
  const resolve = (p, seen = new Set()) => {
130
202
  if (!p?.node || seen.has(p.node)) { return null; }
131
203
  seen.add(p.node);
@@ -147,10 +219,12 @@ function assertBrandOverrides(ast, traverse, options) {
147
219
  const color = val.isStringLiteral() ? val.node.value : val.node.quasis[0].value.cooked;
148
220
  if (!color) { return; }
149
221
  const line = val.node.loc?.start.line || 1;
150
- throw new CliError(t('publish.canvas_theme_fixed_brand', line), {
151
- code: 'OPENYIDA_CANVAS_THEME_FIXED_BRAND',
152
- details: { stage: 'canvas_compile', sourcePath: options.sourcePath || '', line, field, value: color,
153
- sample: 'openyida sample openyida-page-template canvas-theme' },
222
+ // Remove the override at its ConfigProvider usage site, not the resolved literal.
223
+ const property = p?.parentPath?.isObjectProperty() ? p.parentPath.node : null;
224
+ violations.push({
225
+ field, color, line, usageStart,
226
+ start: property ? property.start : val.node.start,
227
+ end: property ? property.end : val.node.end,
154
228
  });
155
229
  };
156
230
  const tokens = p => properties(p).forEach(item => {
@@ -167,6 +241,7 @@ function assertBrandOverrides(ast, traverse, options) {
167
241
  if (!(imported.isImportSpecifier() && imported.node.imported.name === 'ConfigProvider')
168
242
  && !(imported.isImportNamespaceSpecifier() && name.node.property?.name === 'ConfigProvider')) { return; }
169
243
  const attribute = p.get('attributes').find(attr => attr.isJSXAttribute() && attr.node.name.name === 'theme');
244
+ usageStart = p.node.start;
170
245
  const theme = attribute?.get('value.expression');
171
246
  tokens(prop(theme, 'token'));
172
247
  const components = prop(theme, 'components');
@@ -179,6 +254,48 @@ function assertBrandOverrides(ast, traverse, options) {
179
254
  }
180
255
  },
181
256
  });
257
+ return violations;
258
+ }
259
+
260
+ // Fixed brand colors passed to antd's ConfigProvider are relaxed by default:
261
+ // Warn and preserve the literal override; this does not install an app theme.
262
+ // Strict mode blocks these overrides; allow-fixed-brand explicitly preserves them.
263
+ function assertBrandOverrides(ast, traverse, options) {
264
+ const explicitlyAllowed = allowFixedBrand(options);
265
+ const strict = strictFixedBrand(options);
266
+ for (const { field, color, line } of collectBrandOverrides(ast, traverse)) {
267
+ const details = { code: 'OPENYIDA_CANVAS_THEME_FIXED_BRAND', sourcePath: options.sourcePath || '',
268
+ line, field, value: color, themeConsistency: 'fixed_override',
269
+ nextAction: { type: 'review_theme_override', sample: 'openyida sample openyida-page-template canvas-theme' } };
270
+ if (explicitlyAllowed) {
271
+ emitThemeWarning(options, t('publish.canvas_theme_fixed_brand_allowed', line, field), details);
272
+ continue;
273
+ }
274
+ if (!strict) {
275
+ emitThemeWarning(options, t('publish.canvas_theme_fixed_brand_relaxed', line, field), details);
276
+ continue;
277
+ }
278
+ throw new CliError(t('publish.canvas_theme_fixed_brand', line), {
279
+ code: 'OPENYIDA_CANVAS_THEME_FIXED_BRAND',
280
+ details: { stage: 'canvas_compile', sourcePath: options.sourcePath || '', line, field, value: color,
281
+ retryable: false, retrySafe: true, sideEffectState: 'none',
282
+ nextAction: { type: 'edit_source_then_recheck' },
283
+ sample: 'openyida sample openyida-page-template canvas-theme' },
284
+ });
285
+ }
286
+ }
287
+
288
+ // Parse a canvas source and return removable fixed-brand overrides (with char
289
+ // offsets). Returns [] when the source has no theme markers or fails to parse,
290
+ // mirroring the guard's own gate so the codemod never acts on unrelated code.
291
+ function collectFixedBrandViolations(source) {
292
+ if (!/CanvasThemeProvider|useCanvasThemeContext|CanvasThemeContext|@canvas-theme-provider|antd/.test(source)) { return []; }
293
+ let ast;
294
+ try {
295
+ ast = Babel.packages.parser.parse(source, { sourceType: 'module', plugins: ['jsx', 'typescript'] });
296
+ } catch { return []; }
297
+ const traverse = Babel.packages.traverse.default || Babel.packages.traverse;
298
+ return collectBrandOverrides(ast, traverse);
182
299
  }
183
300
 
184
- module.exports = { assertCanvasThemeStructure };
301
+ module.exports = { assertCanvasThemeStructure, collectFixedBrandViolations };