@h-ai/cli 1.0.0-beta.2 → 1.0.0-beta.20

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 (254) hide show
  1. package/README.en.md +74 -154
  2. package/README.ja.md +74 -154
  3. package/README.md +74 -154
  4. package/dist/{chunk-AKV2CQZV.js → chunk-2ZL5ORQ2.js} +316 -97
  5. package/dist/chunk-2ZL5ORQ2.js.map +1 -0
  6. package/dist/{chunk-EF7WOFWZ.js → chunk-L53P43B2.js} +1312 -163
  7. package/dist/chunk-L53P43B2.js.map +1 -0
  8. package/dist/cli-config-templates-LWMD2I6V.js +5 -0
  9. package/dist/{cli-config-templates-HHFHNXBN.js.map → cli-config-templates-LWMD2I6V.js.map} +1 -1
  10. package/dist/{cli-skill-templates-PN4FLGBC.js → cli-skill-templates-D6S2OQ6X.js} +3 -3
  11. package/dist/{cli-skill-templates-PN4FLGBC.js.map → cli-skill-templates-D6S2OQ6X.js.map} +1 -1
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.js +30118 -3253
  14. package/dist/index.js.map +1 -1
  15. package/dist/metafile-esm.json +1 -1
  16. package/package.json +22 -18
  17. package/templates/README.md +45 -30
  18. package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -10
  19. package/templates/apps/admin/src/routes/+page.svelte.hbs +4 -4
  20. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +17 -7
  21. package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +26 -6
  22. package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +2 -2
  23. package/templates/apps/api/.dockerignore.hbs +10 -0
  24. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +8 -0
  25. package/templates/apps/api/apps/{{projectName}}-contract/src/app-schemas.ts.hbs +6 -1
  26. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +13 -5
  27. package/templates/apps/api/apps/{{projectName}}-contract/tests/contract.test.ts.hbs +13 -5
  28. package/templates/apps/api/apps/{{projectName}}-contract/tsup.config.ts.hbs +3 -1
  29. package/templates/apps/api/apps/{{projectName}}-serv/README.md.hbs +6 -2
  30. package/templates/apps/api/apps/{{projectName}}-serv/config/_serv.yml.hbs +24 -2
  31. package/templates/apps/api/apps/{{projectName}}-serv/package.json.hbs +4 -4
  32. package/templates/apps/api/apps/{{projectName}}-serv/src/app.ts.hbs +74 -24
  33. package/templates/apps/api/apps/{{projectName}}-serv/src/build-metadata.ts.hbs +0 -1
  34. package/templates/apps/api/apps/{{projectName}}-serv/src/lib/server/init.ts.hbs +153 -37
  35. package/templates/apps/api/apps/{{projectName}}-serv/src/server/procedures/index.ts.hbs +39 -13
  36. package/templates/apps/api/apps/{{projectName}}-serv/tests/service.test.ts.hbs +106 -33
  37. package/templates/apps/api/apps/{{projectName}}-serv/tsup.config.ts.hbs +3 -1
  38. package/templates/apps/api/apps/{{projectName}}-serv/vitest.config.ts.hbs +3 -1
  39. package/templates/apps/api/e2e/api-serv.spec.ts.hbs +20 -3
  40. package/templates/apps/api/e2e/capture-evidence.ts.hbs +4 -1
  41. package/templates/apps/api/eslint.config.js.hbs +3 -1
  42. package/templates/apps/api/images/serv.Dockerfile.hbs +15 -10
  43. package/templates/apps/api/playwright.config.ts.hbs +23 -19
  44. package/templates/apps/api/pnpm-workspace.yaml.hbs +27 -16
  45. package/templates/apps/fullstack/.dockerignore.hbs +10 -0
  46. package/templates/apps/fullstack/.gitignore.hbs +7 -2
  47. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +14 -12
  48. package/templates/apps/fullstack/apps/{{projectName}}-app/capacitor.config.ts.hbs +5 -3
  49. package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +10 -2
  50. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +1 -1
  51. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +10 -2
  52. package/templates/apps/fullstack/apps/{{projectName}}-app/src/build-metadata.ts.hbs +0 -1
  53. package/templates/apps/fullstack/apps/{{projectName}}-app/src/lib/capacitor.ts.hbs +0 -1
  54. package/templates/apps/fullstack/apps/{{projectName}}-app/svelte.config.js.hbs +4 -1
  55. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +16 -4
  56. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +10 -8
  57. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +3 -3
  58. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/App.svelte.hbs +8 -2
  59. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/build-metadata.ts.hbs +0 -1
  60. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +0 -2
  61. package/templates/apps/fullstack/apps/{{projectName}}-desktop/svelte.config.js.hbs +4 -1
  62. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +16 -4
  63. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +2 -0
  64. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +9 -9
  65. package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +10 -2
  66. package/templates/apps/fullstack/apps/{{projectName}}-web/index.html.hbs +1 -0
  67. package/templates/apps/fullstack/apps/{{projectName}}-web/src/build-metadata.ts.hbs +0 -1
  68. package/templates/apps/fullstack/apps/{{projectName}}-web/src/main.ts.hbs +0 -2
  69. package/templates/apps/fullstack/apps/{{projectName}}-web/svelte.config.js.hbs +4 -1
  70. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +19 -5
  71. package/templates/apps/fullstack/docs/security-configuration.md.hbs +4 -1
  72. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +140 -28
  73. package/templates/apps/fullstack/e2e/session-close.spec.ts.hbs +19 -5
  74. package/templates/apps/fullstack/eslint.config.js.hbs +3 -1
  75. package/templates/apps/fullstack/images/serv.Dockerfile.hbs +19 -11
  76. package/templates/apps/fullstack/images/web.Dockerfile.hbs +17 -8
  77. package/templates/apps/fullstack/package.json.hbs +2 -1
  78. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +7 -5
  79. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +114 -23
  80. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +57 -11
  81. package/templates/apps/fullstack/packages/{{projectName}}-contract/tsup.config.ts.hbs +3 -1
  82. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +17 -7
  83. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +13 -7
  84. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +17 -7
  85. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +10 -4
  86. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +65 -30
  87. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/build-metadata.ts.hbs +0 -1
  88. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +1 -2
  89. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +69 -21
  90. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/business-settings.ts.hbs +21 -5
  91. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +63 -11
  92. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/app-procedures.ts.hbs +6 -1
  93. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -1
  94. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +147 -33
  95. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/security-roles.ts.hbs +43 -12
  96. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +146 -30
  97. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/runtime.test.ts.hbs +33 -10
  98. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/security.test.ts.hbs +142 -26
  99. package/templates/apps/fullstack/packages/{{projectName}}-serv/tsup.config.ts.hbs +6 -2
  100. package/templates/apps/fullstack/packages/{{projectName}}-serv/vitest.config.ts.hbs +3 -1
  101. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +48 -66
  102. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/en-US.json.hbs +1 -0
  103. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +1 -0
  104. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/zh-CN.json.hbs +1 -0
  105. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +9 -2
  106. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/feedback-client.ts.hbs +0 -3
  107. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/sensitive-actions.svelte.ts.hbs +15 -3
  108. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +33 -7
  109. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/FeedbackEntry.svelte.hbs +16 -2
  110. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +104 -13
  111. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/feedback.svelte.hbs +37 -7
  112. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/roles.svelte.hbs +9 -3
  113. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +25 -3
  114. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +133 -19
  115. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/security/role-label.ts.hbs +14 -2
  116. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/guide-store.ts.hbs +66 -34
  117. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +132 -30
  118. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/theme-store.svelte.ts.hbs +8 -2
  119. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +23 -5
  120. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +143 -21
  121. package/templates/apps/fullstack/packages/{{projectName}}-shared/vitest.config.ts.hbs +7 -2
  122. package/templates/apps/fullstack/playwright.config.ts.hbs +25 -3
  123. package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +30 -14
  124. package/templates/apps/h5/src/routes/+layout.svelte.hbs +74 -30
  125. package/templates/apps/h5/src/routes/+page.svelte.hbs +2 -2
  126. package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +2 -2
  127. package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +2 -2
  128. package/templates/apps/mobile-app/capacitor.config.ts.hbs +5 -2
  129. package/templates/apps/mobile-app/messages/en-US.json +2 -2
  130. package/templates/apps/mobile-app/messages/ja-JP.json +2 -2
  131. package/templates/apps/mobile-app/messages/zh-CN.json +2 -2
  132. package/templates/apps/mobile-app/src/App.svelte.hbs +17 -5
  133. package/templates/apps/mobile-app/src/lib/api.ts.hbs +20 -6
  134. package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +8 -1
  135. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +6 -2
  136. package/templates/apps/mobile-app/src/lib/pages/profile.svelte.hbs +13 -1
  137. package/templates/apps/mobile-app/src/lib/pages/tasks.svelte.hbs +5 -1
  138. package/templates/apps/website/scripts/static-website.mjs.hbs +19 -6
  139. package/templates/apps/website/src/routes/+layout.svelte.hbs +56 -29
  140. package/templates/apps/website/src/routes/+page.svelte.hbs +4 -4
  141. package/templates/apps/website/src/routes/about/+page.svelte.hbs +2 -2
  142. package/templates/apps/website/src/routes/contact/+page.svelte.hbs +2 -2
  143. package/templates/apps/website/src/routes/services/+page.svelte.hbs +20 -5
  144. package/templates/base/.dockerignore.hbs +10 -0
  145. package/templates/base/.gitignore.hbs +7 -2
  146. package/templates/base/e2e/app.spec.ts.hbs +130 -36
  147. package/templates/base/eslint-rules.js +44 -25
  148. package/templates/base/eslint.config.js.hbs +3 -1
  149. package/templates/base/images/web.Dockerfile.hbs +25 -14
  150. package/templates/base/package.json.hbs +13 -7
  151. package/templates/base/patches/daisyui-5.7.50.patch +8 -0
  152. package/templates/base/patches/deepmerge-4.3.1.patch +51 -0
  153. package/templates/base/playwright.config.ts +3 -1
  154. package/templates/base/pnpm-workspace.yaml.hbs +33 -16
  155. package/templates/base/src/app.d.ts.hbs +1 -1
  156. package/templates/base/src/app.html.hbs +1 -0
  157. package/templates/base/src/hooks.server.ts.hbs +44 -33
  158. package/templates/base/src/lib/build-metadata.ts.hbs +0 -1
  159. package/templates/base/src/lib/server/init.ts.hbs +146 -35
  160. package/templates/base/src/lib/server/kit-config.ts.hbs +7 -4
  161. package/templates/base/src/routes/+layout.server.ts.hbs +2 -2
  162. package/templates/base/svelte.config.js.hbs +10 -100
  163. package/templates/base/tests/scaffold.test.ts.hbs +12 -3
  164. package/templates/base/tsconfig.json.hbs +7 -4
  165. package/templates/base/vite.config.ts.hbs +108 -14
  166. package/templates/base/vitest.config.ts.hbs +6 -2
  167. package/templates/features/ai/routes/api/chat/+server.ts +3 -1
  168. package/templates/features/iam/routes-admin/(auth)/auth/forgot-password/+page.svelte.hbs +3 -3
  169. package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +3 -3
  170. package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +3 -3
  171. package/templates/features/iam/routes-admin/(auth)/auth/reset-password/+page.svelte.hbs +3 -3
  172. package/templates/features/iam/routes-h5/auth/forgot-password/+page.svelte.hbs +3 -3
  173. package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +3 -3
  174. package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +3 -3
  175. package/templates/features/iam/routes-h5/auth/reset-password/+page.svelte.hbs +3 -3
  176. package/templates/features/iam/routes-website/auth/forgot-password/+page.svelte.hbs +3 -3
  177. package/templates/features/iam/routes-website/auth/login/+page.svelte.hbs +3 -3
  178. package/templates/features/iam/routes-website/auth/register/+page.svelte.hbs +3 -3
  179. package/templates/features/iam/routes-website/auth/reset-password/+page.svelte.hbs +3 -3
  180. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +14 -3
  181. package/templates/shared/browser-check/startup.js +115 -0
  182. package/templates/shared/browser-check/update.css +37 -0
  183. package/templates/shared/browser-check/update.html +19 -0
  184. package/templates/shared/browser-startup.spec.ts +23 -0
  185. package/templates/shared/build-metadata.ts +18 -4
  186. package/templates/shared/e2e-env.ts +22 -6
  187. package/templates/shared/native-name.d.mts +5 -0
  188. package/templates/shared/native-name.mjs +94 -0
  189. package/templates/shared/public-base-path.mjs +17 -4
  190. package/templates/shared/run-e2e.mjs +46 -12
  191. package/templates/shared/run-node.mjs +56 -0
  192. package/templates/shared/run-tauri.mjs +74 -0
  193. package/templates/shared/vite-runtime-config.ts +9 -3
  194. package/templates/skills/AGENTS.md +10 -73
  195. package/templates/skills/bridges/admin/AGENTS.md +9 -65
  196. package/templates/skills/bridges/api/AGENTS.md +8 -59
  197. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +9 -54
  198. package/templates/skills/bridges/h5/AGENTS.md +8 -63
  199. package/templates/skills/bridges/mobile-app/AGENTS.md +8 -62
  200. package/templates/skills/bridges/website/AGENTS.md +8 -63
  201. package/templates/skills/hai-ai/SKILL.md +6 -2
  202. package/templates/skills/hai-ai/acp.md +153 -0
  203. package/templates/skills/hai-ai/guide.md +27 -12
  204. package/templates/skills/hai-ai/reference.md +24 -15
  205. package/templates/skills/hai-ai/resources.md +69 -0
  206. package/templates/skills/hai-api-client/SKILL.md +1 -1
  207. package/templates/skills/hai-api-client/reference.md +3 -1
  208. package/templates/skills/hai-app-create/collaboration.md +7 -0
  209. package/templates/skills/hai-app-create/development.md +19 -10
  210. package/templates/skills/hai-app-create/project.md +33 -0
  211. package/templates/skills/hai-app-create/reference.md +10 -10
  212. package/templates/skills/hai-app-tests/reference.md +1 -1
  213. package/templates/skills/hai-app-tests/validation.md +1 -0
  214. package/templates/skills/hai-build/SKILL.md +19 -9
  215. package/templates/skills/hai-build/paths.md +4 -2
  216. package/templates/skills/hai-build/reference.md +26 -8
  217. package/templates/skills/hai-cache/reference.md +6 -2
  218. package/templates/skills/hai-capacitor/SKILL.md +1 -0
  219. package/templates/skills/hai-capacitor/reference.md +24 -0
  220. package/templates/skills/hai-ci/SKILL.md +3 -0
  221. package/templates/skills/hai-core/SKILL.md +9 -4
  222. package/templates/skills/hai-core/reference.md +40 -10
  223. package/templates/skills/hai-core/request-access.md +2 -2
  224. package/templates/skills/hai-crypto/SKILL.md +2 -2
  225. package/templates/skills/hai-crypto/reference.md +7 -3
  226. package/templates/skills/hai-deploy/SKILL.md +41 -5
  227. package/templates/skills/hai-deploy/middleware.md +26 -14
  228. package/templates/skills/hai-framework-sync/SKILL.md +13 -9
  229. package/templates/skills/hai-framework-sync/sync.md +2 -0
  230. package/templates/skills/hai-iam/reference.md +2 -2
  231. package/templates/skills/hai-kit/SKILL.md +1 -1
  232. package/templates/skills/hai-kit/reference.md +19 -5
  233. package/templates/skills/hai-payment/reference.md +41 -17
  234. package/templates/skills/hai-reach/SKILL.md +3 -1
  235. package/templates/skills/hai-reach/reference.md +29 -9
  236. package/templates/skills/hai-reldb/SKILL.md +1 -1
  237. package/templates/skills/hai-reldb/reference.md +9 -2
  238. package/templates/skills/hai-scheduler/reference.md +1 -1
  239. package/templates/skills/hai-serv/SKILL.md +2 -1
  240. package/templates/skills/hai-serv/reference.md +26 -15
  241. package/templates/skills/hai-storage/reference.md +23 -6
  242. package/templates/skills/hai-ui/SKILL.md +8 -5
  243. package/templates/skills/hai-ui/charts.md +51 -0
  244. package/templates/skills/hai-ui/guide.md +1 -1
  245. package/templates/skills/hai-ui/reference.md +29 -38
  246. package/templates/skills/hai-vecdb/reference.md +8 -3
  247. package/dist/chunk-AKV2CQZV.js.map +0 -1
  248. package/dist/chunk-EF7WOFWZ.js.map +0 -1
  249. package/dist/chunk-MUHRBJSW.js +0 -29272
  250. package/dist/chunk-MUHRBJSW.js.map +0 -1
  251. package/dist/cli-config-templates-HHFHNXBN.js +0 -5
  252. package/dist/src-TEBHR5I5.js +0 -5
  253. package/dist/src-TEBHR5I5.js.map +0 -1
  254. package/templates/apps/fullstack/playwright.session-close.config.ts.hbs +0 -18
@@ -22,54 +22,145 @@ import {
22
22
  /** mini 管理示例的业务路径;四类职责与权限由应用配置,可按项目需求调整。 */
23
23
  export const adminContract = {
24
24
  permissions: {
25
- list: apiContract.route({ method: 'POST', path: '/admin/permissions/list', tags: ['admin'] }).input(z.object({ page: z.number().int().min(1).default(1), pageSize: z.number().int().min(1).max(100).default(20) })).output(apiContract.haiResultSchema(z.object({ items: z.array(AdminPermissionPolicySchema), total: z.number() }))),
26
- update: apiContract.route({ method: 'POST', path: '/admin/permissions/update', tags: ['admin'] }).input(AdminPermissionPolicyInputSchema).output(apiContract.haiResultSchema(z.object({ saved: z.literal(true) }))),
25
+ list: apiContract.route({
26
+ method: 'POST',
27
+ path: '/admin/permissions/list',
28
+ tags: ['admin'],
29
+ }).input(z.object({
30
+ page: z.number().int().min(1).default(1),
31
+ pageSize: z.number().int().min(1).max(100).default(20),
32
+ })).output(apiContract.haiResultSchema(z.object({
33
+ items: z.array(AdminPermissionPolicySchema),
34
+ total: z.number(),
35
+ }))),
36
+ update: apiContract.route({
37
+ method: 'POST',
38
+ path: '/admin/permissions/update',
39
+ tags: ['admin'],
40
+ }).input(AdminPermissionPolicyInputSchema).output(apiContract.haiResultSchema(z.object({
41
+ saved: z.literal(true),
42
+ }))),
27
43
  },
28
44
  configuration: {
29
- get: apiContract.route({ method: 'POST', path: '/admin/configuration/get', tags: ['admin'] }).output(apiContract.haiResultSchema(BusinessSettingsSchema)),
30
- update: apiContract.route({ method: 'POST', path: '/admin/configuration/update', tags: ['admin'] }).input(BusinessSettingsSchema).output(apiContract.haiResultSchema(BusinessSettingsSchema)),
45
+ get: apiContract.route({
46
+ method: 'POST',
47
+ path: '/admin/configuration/get',
48
+ tags: ['admin'],
49
+ }).output(apiContract.haiResultSchema(BusinessSettingsSchema)),
50
+ update: apiContract.route({
51
+ method: 'POST',
52
+ path: '/admin/configuration/update',
53
+ tags: ['admin'],
54
+ }).input(BusinessSettingsSchema).output(apiContract.haiResultSchema(BusinessSettingsSchema)),
31
55
  },
32
56
  users: {
33
- resetPassword: apiContract.route({ method: 'POST', path: '/admin/users/reset-password', tags: ['admin'] })
57
+ resetPassword: apiContract.route({
58
+ method: 'POST',
59
+ path: '/admin/users/reset-password',
60
+ tags: ['admin'],
61
+ })
34
62
  .input(IamAdminResetPasswordInputSchema.strict())
35
- .output(apiContract.haiResultSchema(z.object({ reset: z.literal(true) }))),
36
- invite: apiContract.route({ method: 'POST', path: '/admin/users/invite', tags: ['admin'] }).input(AdminInviteUserSchema).output(apiContract.haiResultSchema(AdminUserSchema)),
37
- list: apiContract.route({ method: 'POST', path: '/admin/users/list', tags: ['admin'] })
63
+ .output(apiContract.haiResultSchema(z.object({
64
+ reset: z.literal(true),
65
+ }))),
66
+ invite: apiContract.route({
67
+ method: 'POST',
68
+ path: '/admin/users/invite',
69
+ tags: ['admin'],
70
+ }).input(AdminInviteUserSchema).output(apiContract.haiResultSchema(AdminUserSchema)),
71
+ list: apiContract.route({
72
+ method: 'POST',
73
+ path: '/admin/users/list',
74
+ tags: ['admin'],
75
+ })
38
76
  .input(AdminUsersInputSchema)
39
77
  .output(apiContract.haiResultSchema(AdminUsersSchema)),
40
- create: apiContract.route({ method: 'POST', path: '/admin/users/create', tags: ['admin'] })
78
+ create: apiContract.route({
79
+ method: 'POST',
80
+ path: '/admin/users/create',
81
+ tags: ['admin'],
82
+ })
41
83
  .input(AdminCreateUserSchema)
42
84
  .output(apiContract.haiResultSchema(AdminUserSchema)),
43
- update: apiContract.route({ method: 'POST', path: '/admin/users/update', tags: ['admin'] })
85
+ update: apiContract.route({
86
+ method: 'POST',
87
+ path: '/admin/users/update',
88
+ tags: ['admin'],
89
+ })
44
90
  .input(AdminUpdateUserSchema)
45
91
  .output(apiContract.haiResultSchema(AdminUserSchema)),
46
- delete: apiContract.route({ method: 'POST', path: '/admin/users/delete', tags: ['admin'] })
47
- .input(z.object({ id: z.string().min(1) }).strict())
48
- .output(apiContract.haiResultSchema(z.object({ deleted: z.literal(true) }))),
49
- avatar: apiContract.route({ method: 'POST', path: '/admin/users/avatar', tags: ['admin'] })
50
- .input(AvatarUploadSchema.extend({ id: z.string().min(1) }))
92
+ delete: apiContract.route({
93
+ method: 'POST',
94
+ path: '/admin/users/delete',
95
+ tags: ['admin'],
96
+ })
97
+ .input(z.object({
98
+ id: z.string().min(1),
99
+ }).strict())
100
+ .output(apiContract.haiResultSchema(z.object({
101
+ deleted: z.literal(true),
102
+ }))),
103
+ avatar: apiContract.route({
104
+ method: 'POST',
105
+ path: '/admin/users/avatar',
106
+ tags: ['admin'],
107
+ })
108
+ .input(AvatarUploadSchema.extend({
109
+ id: z.string().min(1),
110
+ }))
51
111
  .output(apiContract.haiResultSchema(AvatarOutputSchema)),
52
112
  },
53
113
  roles: {
54
- list: apiContract.route({ method: 'POST', path: '/admin/roles/list', tags: ['admin'] })
55
- .output(apiContract.haiResultSchema(z.object({ items: z.array(AdminRoleSchema) }))),
56
- update: apiContract.route({ method: 'POST', path: '/admin/roles/update', tags: ['admin'] })
57
- .input(z.object({ id: z.string().min(1), description: z.string().max(300) }).strict())
114
+ list: apiContract.route({
115
+ method: 'POST',
116
+ path: '/admin/roles/list',
117
+ tags: ['admin'],
118
+ })
119
+ .output(apiContract.haiResultSchema(z.object({
120
+ items: z.array(AdminRoleSchema),
121
+ }))),
122
+ update: apiContract.route({
123
+ method: 'POST',
124
+ path: '/admin/roles/update',
125
+ tags: ['admin'],
126
+ })
127
+ .input(z.object({
128
+ id: z.string().min(1),
129
+ description: z.string().max(300),
130
+ }).strict())
58
131
  .output(apiContract.haiResultSchema(AdminRoleSchema)),
59
132
  },
60
133
  audit: {
61
- backup: apiContract.route({ method: 'POST', path: '/admin/audit/backup', tags: ['admin'] }).input(z.object({ id: z.uuid() }).strict()).output(apiContract.haiResultSchema(AuditBackupReceiptSchema)),
62
- list: apiContract.route({ method: 'POST', path: '/admin/audit/list', tags: ['admin'] })
134
+ backup: apiContract.route({
135
+ method: 'POST',
136
+ path: '/admin/audit/backup',
137
+ tags: ['admin'],
138
+ }).input(z.object({
139
+ id: z.uuid(),
140
+ }).strict()).output(apiContract.haiResultSchema(AuditBackupReceiptSchema)),
141
+ list: apiContract.route({
142
+ method: 'POST',
143
+ path: '/admin/audit/list',
144
+ tags: ['admin'],
145
+ })
63
146
  .input(AdminAuditInputSchema)
64
147
  .output(apiContract.haiResultSchema(AdminAuditSchema)),
65
148
  },
66
149
  }
67
150
 
68
151
  export const profileContract = {
69
- update: apiContract.route({ method: 'POST', path: '/profile/update', tags: ['profile'] })
152
+ update: apiContract.route({
153
+ method: 'POST',
154
+ path: '/profile/update',
155
+ tags: ['profile'],
156
+ })
70
157
  .input(ProfileUpdateSchema)
71
158
  .output(apiContract.haiResultSchema(AdminUserSchema)),
72
- avatar: apiContract.route({ method: 'POST', path: '/profile/avatar', tags: ['profile'] })
159
+ avatar: apiContract.route({
160
+ method: 'POST',
161
+ path: '/profile/avatar',
162
+ tags: ['profile'],
163
+ })
73
164
  .input(AvatarUploadSchema)
74
165
  .output(apiContract.haiResultSchema(AvatarOutputSchema)),
75
166
  }
@@ -2,13 +2,26 @@ import { IamListUsersInputSchema, IamPermissionSchema } from '@h-ai/api-contract
2
2
  import { z } from 'zod'
3
3
 
4
4
  /** 权限的敏感操作配置复用框架字段;职责授权仍由应用控制。 */
5
- export const AdminPermissionPolicySchema = IamPermissionSchema.pick({ id: true, code: true, name: true, sensitiveOperations: true })
5
+ export const AdminPermissionPolicySchema = IamPermissionSchema.pick({
6
+ id: true,
7
+ code: true,
8
+ name: true,
9
+ sensitiveOperations: true,
10
+ })
6
11
  export const AdminPermissionPolicyInputSchema = AdminPermissionPolicySchema.pick({ id: true, sensitiveOperations: true })
7
12
  export type AdminPermissionPolicy = z.infer<typeof AdminPermissionPolicySchema>
8
13
 
9
- export const BusinessSettingsSchema = z.object({ notice: z.string().trim().max(200) }).strict()
14
+ export const BusinessSettingsSchema = z.object({
15
+ notice: z.string().trim().max(200),
16
+ }).strict()
10
17
 
11
- export const AdminRoleCodeSchema = z.enum(['admin', 'audit_admin', 'config_admin', 'business_operator', 'user'])
18
+ export const AdminRoleCodeSchema = z.enum([
19
+ 'admin',
20
+ 'audit_admin',
21
+ 'config_admin',
22
+ 'business_operator',
23
+ 'user',
24
+ ])
12
25
  export const AdminPageInputSchema = z.object({
13
26
  page: z.number().int().min(1).default(1),
14
27
  pageSize: z.number().int().min(1).max(100).default(20),
@@ -49,10 +62,16 @@ export const AdminCreateUserSchema = z.object({
49
62
  accountStatus: z.enum(['active', 'dormant', 'closed']).default('active'),
50
63
  accountExpiresAt: z.string().datetime().nullable().optional(),
51
64
  }).strict()
52
- export const AdminInviteUserSchema = AdminCreateUserSchema.omit({ password: true }).extend({ phone: z.string().trim().min(1).max(32).optional(), accountStatus: z.literal('active').default('active') })
65
+ export const AdminInviteUserSchema = AdminCreateUserSchema.omit({ password: true }).extend({
66
+ phone: z.string().trim().min(1).max(32).optional(),
67
+ accountStatus: z.literal('active').default('active'),
68
+ })
53
69
  export const AdminUpdateUserSchema = z.object({
54
70
  id: z.string().min(1),
55
- email: z.union([z.email(), z.literal('')]).optional(),
71
+ email: z.union([
72
+ z.email(),
73
+ z.literal(''),
74
+ ]).optional(),
56
75
  displayName: z.string().max(100).optional(),
57
76
  accountType: z.enum(['permanent', 'temporary']).optional(),
58
77
  accountStatus: z.enum(['active', 'dormant', 'closed']).optional(),
@@ -73,7 +92,14 @@ export const AdminAuditInputSchema = AdminPageInputSchema.extend({
73
92
  scopeId: z.string().max(256).optional(),
74
93
  requestId: z.string().max(256).optional(),
75
94
  traceId: z.string().max(256).optional(),
76
- sortBy: z.enum(['createdAt', 'action', 'resource', 'category', 'outcome', 'severity']).optional(),
95
+ sortBy: z.enum([
96
+ 'createdAt',
97
+ 'action',
98
+ 'resource',
99
+ 'category',
100
+ 'outcome',
101
+ 'severity',
102
+ ]).optional(),
77
103
  sortDirection: z.enum(['asc', 'desc']).optional(),
78
104
  userId: z.string().max(100).optional(),
79
105
  action: z.string().max(50).optional(),
@@ -88,7 +114,12 @@ export const AdminAuditLogSchema = z.object({
88
114
  scopeId: z.string().nullish(),
89
115
  requestId: z.string().nullish(),
90
116
  traceId: z.string().nullish(),
91
- actorSnapshot: z.object({ id: z.string(), username: z.string().optional(), displayName: z.string().optional(), roles: z.array(z.string()).optional() }).nullish(),
117
+ actorSnapshot: z.object({
118
+ id: z.string(),
119
+ username: z.string().optional(),
120
+ displayName: z.string().optional(),
121
+ roles: z.array(z.string()).optional(),
122
+ }).nullish(),
92
123
  id: z.string(),
93
124
  userId: z.string().nullable(),
94
125
  username: z.string().nullable(),
@@ -100,10 +131,20 @@ export const AdminAuditLogSchema = z.object({
100
131
  userAgent: z.string().nullable(),
101
132
  createdAt: z.string().datetime(),
102
133
  })
103
- export const AuditBackupReceiptSchema = z.object({ id: z.string(), key: z.string(), recordCount: z.number(), sha256: z.string(), createdAt: z.string().datetime(), cutoff: z.string().datetime() })
134
+ export const AuditBackupReceiptSchema = z.object({
135
+ id: z.string(),
136
+ key: z.string(),
137
+ recordCount: z.number(),
138
+ sha256: z.string(),
139
+ createdAt: z.string().datetime(),
140
+ cutoff: z.string().datetime(),
141
+ })
104
142
  export const AdminAuditSchema = z.object({
105
143
  backupEnabled: z.boolean(),
106
- stats: z.array(z.object({ action: z.string(), count: z.number() })),
144
+ stats: z.array(z.object({
145
+ action: z.string(),
146
+ count: z.number(),
147
+ })),
107
148
  items: z.array(AdminAuditLogSchema),
108
149
  total: z.number(),
109
150
  page: z.number(),
@@ -111,7 +152,10 @@ export const AdminAuditSchema = z.object({
111
152
  })
112
153
  export const ProfileUpdateSchema = z.object({
113
154
  displayName: z.string().max(100).optional(),
114
- email: z.union([z.email(), z.literal('')]).optional(),
155
+ email: z.union([
156
+ z.email(),
157
+ z.literal(''),
158
+ ]).optional(),
115
159
  locale: z.enum([{{#each fullstack.i18n.locales}}'{{this}}'{{#unless @last}}, {{/unless}}{{/each}}]).optional(),
116
160
  theme: z.string().min(1).max(40).regex(/^[\w-]+$/).optional(),
117
161
  }).strict()
@@ -120,7 +164,9 @@ export const AvatarUploadSchema = z.object({
120
164
  contentType: z.enum(['image/png', 'image/jpeg', 'image/webp']),
121
165
  base64: z.string().min(1).max(699052).regex(/^[A-Z\d+/]+={0,2}$/i),
122
166
  }).strict()
123
- export const AvatarOutputSchema = z.object({ avatarUrl: z.string().describe('Generated relative asset path') })
167
+ export const AvatarOutputSchema = z.object({
168
+ avatarUrl: z.string().describe('Generated relative asset path'),
169
+ })
124
170
 
125
171
  export type AdminUser = z.infer<typeof AdminUserSchema>
126
172
  export type AdminRole = z.infer<typeof AdminRoleSchema>
@@ -3,7 +3,9 @@ import { defineConfig } from 'tsup'
3
3
  export default defineConfig({
4
4
  entry: ['src/index.ts'],
5
5
  format: ['esm'],
6
- dts: { compilerOptions: { ignoreDeprecations: '6.0' } },
6
+ dts: {
7
+ compilerOptions: { ignoreDeprecations: '6.0' },
8
+ },
7
9
  sourcemap: true,
8
10
  clean: true,
9
11
  })
@@ -14,14 +14,23 @@
14
14
 
15
15
  ### 默认功能与配置
16
16
 
17
+ | 操作场景 | 输入示例 | 可观察结果 |
18
+ | --- | --- | --- |
19
+ | 服务元信息 | 客户端app.info() | 公告、Core名称/版本/环境及启用前端列表,不下发服务端秘密 |
20
+ | Echo演示 | app.echo({ message: 'hello' }) | 原消息、echoedAt和requestId,不写业务表 |
21
+ | 用户/角色/个人资料 | 已认证身份及对应contract参数 | 服务端核验权限与目标,失败保持客户端草稿,成功返回明确结果 |
22
+ | 审计/公告/反馈 | 各职责允许的筛选或处理参数 | 持久记录、私有备份回执或反馈处理状态;通知受理不等于送达 |
23
+
24
+ 从工程根启动本服务后,再启动所需客户端。接口地址、认证与传输按实际 `_serv.yml/_iam.yml` 配置,通过shared typed client调用并检查success。
25
+
17
26
  以下是可运行示例的初始实现,角色/权限、管理流程、头像限额、分页、审计策略及部署方案均可按用户需求调整。需求与代码或模板规则不一致时,以需求为业务基线同步实现、测试与过期说明;实际框架 API 契约和服务端安全校验仍需满足。
18
27
 
19
28
  - `config/_core.yml`:应用元信息(名称 / 版本 / 环境 / 语言),启动时由 `core.config` 加载。
20
29
  - `config/_serv.yml`:服务端运行时设置(CORS 来源等)。
21
30
  - `config/_db.yml` / `_cache.yml` / `_iam.yml` / `_storage.yml` / `_audit.yml` / `_reach.yml`:管理及开发通知配置,初始化前使用模块公开 Schema 验证。Reach 默认控制台 Provider,生产按注释替换为 SMTP/短信/API。
22
- - `config/_ai.yml` / `_vecdb.yml` / `_payment.yml` / `_scheduler.yml`:保留未启用模块的完整注释示例;配置文件存在不代表模块已接入。先在本 workspace 安装对应依赖,再在 `src/server/runtime.ts` 按公开 Schema 校验、初始化并反序关闭,接入实际业务后按需取消注释。整份注释先去掉一层外层注释,可选字段继续按需开启。
23
- - `src/index.ts`:进程入口,先 `core.init({ configDir: './config' })`,再初始化 reldb → cache → crypto → storage → reach(有配置时)→ audit → iam → scheduler(有任务时);启动失败及退出反序释放。
24
- - 选择 AI 时自动安装 reldb/vecdb/AI,并在 reldb/cache 之后初始化 vecdb、在其余依赖之后初始化 AI;关闭时先释放 AI,最后释放 vecdb/关系库,关闭失败会返回错误并保留重试义务。生产环境必须使用共享后端,根 `.env.prod` 默认连接 Compose `db/cache/storage/vecdb`,凭据和真实 Reach Provider JSON 私有注入。
31
+ - `config/_ai.yml` / `_vecdb.yml` / `_payment.yml` / `_kit.yml`:保留未启用模块的完整注释示例;配置文件存在不代表模块已接入。先在本 workspace 安装对应依赖,再在 `src/server/runtime.ts` 按公开 Schema 校验、初始化并反序关闭,接入实际业务后按需取消注释。整份注释先去掉一层外层注释,可选字段继续按需开启。scheduler 已用于审计任务,配置位于 `_scheduler.yml`。
32
+ - `src/index.ts`:进程入口,通过 `src/server/runtime.ts` 先向 Core 注册各模块公开 Schema 并加载配置,再初始化 reldb → cache → crypto → storage → reach(有配置时)→ audit → iam → scheduler(有任务时);YAML 未声明的合法可选字段同样支持细项环境变量,启动失败及退出反序释放。
33
+ - 默认不安装 AI、vecdb、payment 和 datapipe。后续接入 AI 时安装对应依赖,在 reldb/cache 之后初始化 vecdb、在其余依赖之后初始化 AI;关闭时先释放 AI,最后释放 vecdb/关系库。生产环境必须使用共享后端,根 `.env.prod` 默认连接 Compose `db/cache/storage`,新增 vecdb 后补充共享向量库配置;凭据和真实 Reach Provider JSON 私有注入。
25
34
  - `src/app-server.ts`:服务装配入口,挂载健康检查、OpenAPI、docs 与 procedures;应用名称 / 版本与 CORS 来源均取自上述配置。
26
35
  - `src/server/procedures/app-procedures.ts`:app 领域 procedures,对应 contract 中的 `app.info` / `app.echo` 路由。
27
36
  - `src/server/procedures/admin-procedures.ts`:认证、管理员用户/角色/审计管理和个人设置,使用 `.auth()` / `.permission(...)` 守卫。每个请求读取用户最新启用状态、角色和权限。
@@ -29,7 +38,7 @@
29
38
  - `src/server/admin-service.ts`:角色分配、头像上传和审计编排,全部底层操作复用 hai 模块。初始使用 admin、audit_admin、config_admin、business_operator 和 user 五种角色,四类职责及基础成员权限由 security-roles.ts 配置;锁内重新鉴权,默认禁止管理员自删、自禁用、自降级,并补偿用户更新失败后的角色变更。业务规则可按需求替换,保留的不变量需验证并发与失败路径。
30
39
  - `tests/app-server.test.ts`:真实加密 HTTP 测试,覆盖 Cookie/受信原生刷新、RBAC、旧会话权限、并发管理操作、补偿、头像安全、偏好、审计及会话失效。
31
40
 
32
- CLI 已在根目录 `.env.dev/.env.test/.env.prod` 为每个环境的 admin、audit_admin、config_admin、business_operator 分别生成独立随机强密码,对应 `HAI_ADMIN_DEFAULT_PASSWORD`、`HAI_AUDIT_ADMIN_PASSWORD`、`HAI_CONFIG_ADMIN_PASSWORD`、`HAI_BUSINESS_OPERATOR_PASSWORD`。已有账号不改密或重新分配职责;未配置密码的独立职责账号不创建;`dev`/`start` 自动读取,首次登录使用用户名 `admin` 和该密码。仅在没有用户时创建管理员,不重设已有用户密码。密码不符合 `_iam.yml` 策略时,启动日志显示具体原因及错误码。环境文件随项目提交供克隆后共享;私有文件或进程变量可覆盖,`.env.example` 不含真实密码。所有业务 JSON 请求和响应强制加密;健康检查和随机路径的只读头像图片可直接访问。头像支持 PNG/JPEG/WebP,最大 512 KiB,校验内容特征和大小后由服务端生成 key,不能指定任意文件路径。
41
+ CLI 已在根目录 `.env.dev.local/.env.test.local/.env.prod.local` 为每个环境的 admin、audit_admin、config_admin、business_operator 分别生成独立随机强密码,对应 `HAI_ADMIN_DEFAULT_PASSWORD`、`HAI_AUDIT_ADMIN_PASSWORD`、`HAI_CONFIG_ADMIN_PASSWORD`、`HAI_BUSINESS_OPERATOR_PASSWORD`。已有账号不改密或重新分配职责;未配置密码的独立职责账号不创建;`dev`/`start` 自动读取,首次登录使用用户名 `admin` 和该密码。仅在没有用户时创建管理员,不重设已有用户密码。密码不符合 `_iam.yml` 策略时,启动日志显示具体原因及错误码。这些私有文件不提交 Git,克隆后通过受保护配置分发对应环境秘密;私有文件或进程变量可覆盖,`.env.example` 不含真实密码。所有业务 JSON 请求和响应强制加密;健康检查和随机路径的只读头像图片可直接访问。头像支持 PNG/JPEG/WebP,最大 512 KiB,校验内容特征和大小后由服务端生成 key,不能指定任意文件路径。
33
42
 
34
43
  默认 SQLite + 本地 storage + memory cache 仅适合本地开发与显式测试,管理写入仅支持单写实例:进程内保护无过期时间,共享 cache 锁定期续租,丢失锁或释放失败会记录错误。多实例读取可使用共享数据库、Redis cache 和 S3 storage,但管理写请求必须统一路由到同一写实例;仅更换 Redis 不提供任意多写副本的最后管理员原子保证。传输在所有节点共享受管版本化密钥环与 registry,不依赖会话粘性;重启加载原 ring,轮换先分发新旧版本再切 activeKeyId。Web 使用 HttpOnly refresh Cookie + 内存 access token;仅同时满足客户端标识、body 通道头与受信原生 Origin 的 App/Desktop 请求返回 refresh token body。
35
44
 
@@ -37,9 +46,9 @@ CLI 已在根目录 `.env.dev/.env.test/.env.prod` 为每个环境的 admin、au
37
46
 
38
47
  ### 开发
39
48
 
40
- 所有配置字段以模块 Schema 为准;未使用字段和替代 Provider 用注释保留。叶子环境变量只覆盖已有 YAML 路径,先取消相应键的注释,或用 `HAI_DB` / `HAI_CACHE` 等整体对象覆盖;`connectTimeout` 对应 `HAI_CACHE_CONNECTTIMEOUT`。凭据示例在根 `.env.example`,初始管理员密码和传输密钥环按环境生成到 `.env.dev/.env.test/.env.prod`;其他私有值或覆盖值保存在忽略的 `.env.local` 或部署环境。`.gitignore` 的 `**/data/` 忽略所有运行数据及 `.gitkeep`;运行时会创建目录。`crypto.init()` 无配置参数,datapipe 为纯函数,不提供虚构配置。
49
+ 所有配置字段以模块 Schema 为准;未使用字段和替代 Provider 用注释保留。宿主显式提供模块 Schema 后,合法可选路径可直接用细项配置;同源 `HAI_DB` / `HAI_CACHE` JSON 为基础,细项继续覆盖;`connectTimeout` 对应 `HAI_CACHE_CONNECTTIMEOUT`。凭据示例在根 `.env.example`,初始管理员密码和传输密钥环按环境生成到 `.env.dev.local/.env.test.local/.env.prod.local`;其他私有值或覆盖值保存在忽略的 `.env.local` 或部署环境。`.gitignore` 的 `**/data/` 忽略所有运行数据及 `.gitkeep`;运行时会创建目录。`crypto.init()` 无配置参数,datapipe 为纯函数,不提供虚构配置。
41
50
 
42
- CLI 同时在根 `.env.dev/.env.test/.env.prod` 分别生成独立的 `HAI_CRYPTO_TRANSPORT_SERVERKEYRING`,结构为 `{activeKeyId,keys:{id:{publicKey,privateKey}}}`,运行时不隐式生成。同一环境所有节点从环境文件加载相同 ring,也可由 Secret Manager 统一覆盖,客户端 registry 复用共享 reldb。私钥不进入普通 YAML、PUBLIC_/VITE_、日志或公开响应。轮换先分发含新旧版本的 ring,再切 activeKeyId 并滚动重建;过渡期后删除旧 ID,旧客户端 destroy 后重协商。没有 sticky session 依赖。
51
+ CLI 同时在根 `.env.dev.local/.env.test.local/.env.prod.local` 分别生成独立的 `HAI_CRYPTO_TRANSPORT_SERVERKEYRING`,结构为 `{activeKeyId,keys:{id:{publicKey,privateKey}}}`,运行时不隐式生成。同一环境所有节点从环境文件加载相同 ring,也可由 Secret Manager 统一覆盖,客户端 registry 复用共享 reldb。私钥不进入普通 YAML、PUBLIC_/VITE_、日志或公开响应。轮换先分发含新旧版本的 ring,再切 activeKeyId 并滚动重建;过渡期后删除旧 ID,旧客户端 destroy 后重协商。没有 sticky session 依赖。
43
52
 
44
53
  自动生成的 `.env` 包含已启用模块的环境参数;YAML 显式引用相同变量并保留本地回退。使用 `start:test` / `start:prod` 加载对应环境覆盖。
45
54
 
@@ -48,11 +57,12 @@ CLI 同时在根 `.env.dev/.env.test/.env.prod` 分别生成独立的 `HAI_CRYPT
48
57
 
49
58
  ### 容器运行
50
59
 
51
- 从工程根运行 `pnpm build --target docker` 导出镜像。默认带部署工具,可运行 `pnpm run deploy --env test --build` 一次完成构建发布,或 `pnpm run deploy --env prod --bundle-only` 交付已有镜像及跨平台脚本。完整包包含自建中间件,人工部署与按服务更新见工程根 README。
60
+ 从工程根运行 `pnpm build --target image` 导出镜像。默认带部署工具,可运行 `pnpm run deploy --env test --build` 一次完成构建发布,或 `pnpm run deploy --env prod --bundle-only` 交付已有镜像及跨平台脚本。完整包包含自建中间件,人工部署与按服务更新见工程根 README。
52
61
 
53
62
  - 服务镜像直接由 `@h-ai/serv` 对外监听,配置挂载点为 `/app/config`,数据目录为 `/app/data`,不包含额外转发进程。
54
63
  - Web 镜像不烘焙 API 地址;启动时使用 `-e PUBLIC_API_BASE=https://api.example.com` 注入,同一镜像可部署到不同环境。未设置时 Web 默认请求自身 origin。
55
64
  - YAML 配置使用 `${ENV_NAME:default}` 接收运行时环境变量;不要把密钥写入镜像。
65
+ - 受控内网或 VPN 的生产 HTTP 入口可在 `.env.prod.local` 设置 `HAI_SERV_REFRESH_COOKIE_SECURE=false` 和实际 `HAI_SERV_CORS_ORIGIN`,重新部署后端后重新登录。刷新凭据会经 HTTP 明文传输。
56
66
 
57
67
  ### IAM 认证配置
58
68
 
@@ -4,7 +4,10 @@ providers:
4
4
  type: console
5
5
  - name: sms
6
6
  type: console
7
- # SMTP 替换 email 控制台 Provider;同名 Provider 只保留一个。
7
+ # 生产示例:用下面 providers 整段替换上方控制台列表;保留需要的项。
8
+ # 数组索引从 0 开始,删掉某项后同步修改其余环境变量中的索引。
9
+ # providers:
10
+ # SMTP 邮件;名称 email 与下方通知模板对应。
8
11
  # - name: email
9
12
  # type: smtp
10
13
  # host: ${HAI_REACH_PROVIDERS_0_HOST:smtp.example.com}
@@ -16,19 +19,22 @@ providers:
16
19
  # 阿里云短信替换 sms 控制台 Provider。
17
20
  # - name: sms
18
21
  # type: aliyun-sms
19
- # accessKeyId: ${HAI_REACH_PROVIDERS_0_ACCESSKEYID}
20
- # accessKeySecret: ${HAI_REACH_PROVIDERS_0_ACCESSKEYSECRET}
22
+ # accessKeyId: ${HAI_REACH_PROVIDERS_1_ACCESSKEYID}
23
+ # accessKeySecret: ${HAI_REACH_PROVIDERS_1_ACCESSKEYSECRET}
21
24
  # signName: MyApp
22
25
  # endpoint: dysmsapi.aliyuncs.com
26
+ # gateway: # 可选;配置后向完整 URL 发送 POST
27
+ # url: https://sms-gateway.example.com/dx
28
+ # body: {}
23
29
  # 自有发送服务:按需将 template.provider 改为 custom。
24
30
  # - name: custom
25
31
  # type: api
26
32
  # url: https://notify.example.com/send
27
33
  # method: POST # POST | PUT
28
34
  # headers:
29
- # Authorization: '${HAI_REACH_PROVIDERS_0_HEADERS_AUTHORIZATION}'
35
+ # Authorization: '${HAI_REACH_PROVIDERS_2_HEADERS_AUTHORIZATION}'
30
36
  # timeout: 10000
31
- # idempotent: ${HAI_REACH_PROVIDERS_0_IDEMPOTENT:false} # 仅接收端具备原子持久去重时启用;默认在途不明结果转 unknown
37
+ # idempotent: ${HAI_REACH_PROVIDERS_2_IDEMPOTENT:false} # 仅接收端具备原子持久去重时启用;默认在途不明结果转 unknown
32
38
  templates:
33
39
  - name: otp_email
34
40
  provider: email
@@ -52,8 +58,8 @@ templates:
52
58
  # start: '22:00'
53
59
  # end: '08:00'
54
60
 
55
- # 延迟队列策略随每条消息保存;最多领取 5 次,指数退避 1 分钟至 1 小时。
56
- # 超限 dead 只经可信服务端人工恢复;unknown 不允许自动重发。
61
+ # 消息发送失败后最多尝试 5 次。首次重试等待 1 分钟,以后每次加倍,最长等待 1 小时。
62
+ # 尝试次数用完后停止,交给管理员处理;若无法确认对方是否已收到,先核对结果,避免重复发送。
57
63
  queue:
58
64
  maxAttempts: ${HAI_REACH_QUEUE_MAXATTEMPTS:5}
59
65
  retryDelayMs: ${HAI_REACH_QUEUE_RETRYDELAYMS:60000}
@@ -16,22 +16,24 @@ http:
16
16
  access: false
17
17
  # access:
18
18
  # file: ./config/security/request-access.yml
19
+ # 或将 access: false 替换为下列内联配置;与 file 二选一。
20
+ # access:
21
+ # global:
22
+ # timezone: UTC # 可用 HAI_SERV_HTTP_ACCESS_GLOBAL_TIMEZONE=Asia/Shanghai
23
+ # allowIps: []
24
+ # denyIps: []
25
+ # windows: []
26
+ # rules: []
19
27
  apiPrefix: /api/v1
20
28
  openapi:
21
29
  path: /openapi.json
30
+ # HAI_SERV_HTTP_DOCS=false 关闭文档;开启时再配置 path/requireAuth。
22
31
  docs:
23
32
  path: /docs
24
33
  # requireAuth: true
25
34
  health:
26
35
  path: /health
27
36
  readyPath: /ready
28
- rpc: false
29
- # 内部 RPC 可选;启用时用对象替换 false,gateway-only 必须配置网关密钥。
30
- # rpc:
31
- # prefix: /api/rpc
32
- # access: gateway-only # loopback | private-network | gateway-only
33
- # gatewayHeader: X-Gateway-Key
34
- # gatewaySecret: ${HAI_SERV_HTTP_RPC_GATEWAYSECRET}
35
37
 
36
38
  cors:
37
39
  origin: ${HAI_SERV_CORS_ORIGIN:*}
@@ -60,5 +62,13 @@ transport:
60
62
  {{else}}
61
63
  maxPlaintextBytes: ${HAI_SERV_TRANSPORT_MAXPLAINTEXTBYTES:1048576}
62
64
  {{/if}}
65
+ # 传输时钟窗口,单位秒;允许领先 0~300 秒,请求有效期 1~600 秒。
66
+ maxFutureSkewSeconds: ${HAI_SERV_TRANSPORT_MAXFUTURESKEWSECONDS:30}
67
+ maxRequestAgeSeconds: ${HAI_SERV_TRANSPORT_MAXREQUESTAGESECONDS:300}
68
+ # HAI_SERV_TRANSPORT_ROUTEPLAINTEXTBYTES 可填 JSON,例如 {"/api/v1/uploads/*":8388608}。
69
+ routePlaintextBytes: {}
63
70
  keyExchangePath: /_hai/key-exchange
64
71
  # excludePaths: [/health, /ready]
72
+ # 单条路径需要更大请求体时填写字节数,* 只匹配一段路径。
73
+ # routePlaintextBytes: # HAI_SERV_TRANSPORT_ROUTEPLAINTEXTBYTES 可填 JSON 路径映射
74
+ # /api/v1/uploads/*: 8388608
@@ -27,11 +27,11 @@
27
27
  "test": "vitest run",
28
28
  "test:watch": "vitest",
29
29
  "clean": "rimraf dist .turbo",
30
- "dev": "pnpm build && node --env-file=../../.env --env-file=../../.env.dev --env-file-if-exists=../../.env.local dist/index.js",
31
- "start": "node --env-file=../../.env --env-file=../../.env.prod --env-file-if-exists=../../.env.local dist/index.js",
32
- "dev:test": "pnpm build && node --env-file=../../.env --env-file=../../.env.test --env-file-if-exists=../../.env.local dist/index.js",
30
+ "dev": "pnpm build && node ../../scripts/run-node.mjs dev ../.. dist/index.js",
31
+ "start": "node ../../scripts/run-node.mjs prod ../.. dist/index.js",
32
+ "dev:test": "pnpm build && node ../../scripts/run-node.mjs test ../.. dist/index.js",
33
33
  "dev:prod": "pnpm build && pnpm start",
34
- "start:test": "node --env-file=../../.env --env-file=../../.env.test --env-file-if-exists=../../.env.local dist/index.js",
34
+ "start:test": "node ../../scripts/run-node.mjs test ../.. dist/index.js",
35
35
  "start:prod": "pnpm start"
36
36
  },
37
37
  "dependencies": {
@@ -43,11 +43,17 @@
43
43
  "@h-ai/cache": "{{haiPackageSpecifier}}",
44
44
  "@h-ai/core": "{{haiPackageSpecifier}}",
45
45
  "@h-ai/crypto": "{{haiPackageSpecifier}}",
46
+ {{#if features.datapipe}}
47
+ "@h-ai/datapipe": "{{haiPackageSpecifier}}",
48
+ {{/if}}
46
49
  {{#if features.feedback}}
47
50
  "@h-ai/api-contract": "{{haiPackageSpecifier}}",
48
51
  "@h-ai/feedback": "{{haiPackageSpecifier}}",
49
52
  {{/if}}
50
53
  "@h-ai/iam": "{{haiPackageSpecifier}}",
54
+ {{#if features.payment}}
55
+ "@h-ai/payment": "{{haiPackageSpecifier}}",
56
+ {{/if}}
51
57
  "@h-ai/reach": "{{haiPackageSpecifier}}",
52
58
  "@h-ai/reldb": "{{haiPackageSpecifier}}",
53
59
  "@h-ai/scheduler": "{{haiPackageSpecifier}}",
@@ -1,8 +1,10 @@
1
1
  import type { CoreConfig } from '@h-ai/core'
2
2
  import type { {{#if features.feedback}}CreateServContext, {{/if}}ServHttpApp } from '@h-ai/serv'
3
3
  import process from 'node:process'
4
- {{#if features.feedback}}import { resolveFeedbackBudget } from '@h-ai/api-contract/feedback'
5
- {{/if}}import { core } from '@h-ai/core'
4
+ {{#if features.feedback}}
5
+ import { resolveFeedbackBudget } from '@h-ai/api-contract/feedback'
6
+ {{/if}}
7
+ import { core } from '@h-ai/core'
6
8
  import { createReldbTransportKeyStore, crypto, parseTransportServerKeyRing } from '@h-ai/crypto'
7
9
  {{#if features.feedback}}
8
10
  import { FeedbackConfigSchema } from '@h-ai/feedback'
@@ -25,19 +27,29 @@ export function createServerApp(): ServHttpApp {
25
27
  const ring = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
26
28
  if (!ring.success)
27
29
  throw new Error(`HAI_CRYPTO_TRANSPORT_SERVERKEYRING: ${ring.error.message} (${ring.error.code})`)
28
- {{#if features.feedback}} // 可选反馈故障重试期间仍使用配置预算,避免缓存默认容量。
30
+ {{#if features.feedback}}
31
+ // 可选反馈故障重试期间仍使用配置预算,避免缓存默认容量。
29
32
  const feedbackSettings = FeedbackConfigSchema.safeParse(core.config.get('feedback'))
30
- const feedbackBudget = resolveFeedbackBudget(feedbackSettings.success ? feedbackSettings.data : {})
31
- {{/if}} const security = serv.createRuntimeSecurityPolicy({
33
+ const feedbackBudget = resolveFeedbackBudget(feedbackSettings.success
34
+ ? feedbackSettings.data
35
+ : {})
36
+ {{/if}}
37
+ const security = serv.createRuntimeSecurityPolicy({
32
38
  environment: coreConfig.env,
33
39
  corsOrigin: servConfig.cors.origin,
34
40
  nativeOrigins: servConfig.cors.nativeOrigins,
35
41
  })
36
42
  const publicPaths = [
37
43
  `${servConfig.http.apiPrefix}/assets`,
38
- ...(servConfig.http.health ? [servConfig.http.health.path, servConfig.http.health.readyPath].filter((path): path is `/${string}` => Boolean(path)) : []),
39
- ...(servConfig.http.openapi && security.exposeApiDocs ? [servConfig.http.openapi.path] : []),
40
- ...(servConfig.http.docs && security.exposeApiDocs ? [servConfig.http.docs.path] : []),
44
+ ...(servConfig.http.health
45
+ ? [servConfig.http.health.path, servConfig.http.health.readyPath].filter((path): path is `/${string}` => Boolean(path))
46
+ : []),
47
+ ...(servConfig.http.openapi && security.exposeApiDocs
48
+ ? [servConfig.http.openapi.path]
49
+ : []),
50
+ ...(servConfig.http.docs && security.exposeApiDocs
51
+ ? [servConfig.http.docs.path, `${servConfig.http.docs.path.slice(0, servConfig.http.docs.path.lastIndexOf('/') + 1)}_hai/scalar.js`]
52
+ : []),
41
53
  ]
42
54
  {{#if features.feedback}}
43
55
  // 普通业务日志与反馈使用同一可信应用范围;身份仍由 IAM 验证。
@@ -67,9 +79,11 @@ export function createServerApp(): ServHttpApp {
67
79
  {{/if}}
68
80
  refreshCookie: {
69
81
  secure: security.secureRefreshCookie,
70
- nativeTokenTransport: { isRequest: request => request.headers.get('x-hai-token-transport') === 'body'
71
- && ['{{projectName}}-app', '{{projectName}}-desktop'].includes(request.headers.get('x-request-client') ?? '')
72
- && security.isNativeOrigin(request.headers.get('origin') ?? '') },
82
+ nativeTokenTransport: {
83
+ isRequest: request => request.headers.get('x-hai-token-transport') === 'body'
84
+ && ['{{projectName}}-app', '{{projectName}}-desktop'].includes(request.headers.get('x-request-client') ?? '')
85
+ && security.isNativeOrigin(request.headers.get('origin') ?? ''),
86
+ },
73
87
  },
74
88
  // 管理业务默认强制双向加密;各节点复用受管服务端密钥环与共享 reldb 客户端 registry。
75
89
  transport: {
@@ -83,7 +97,12 @@ export function createServerApp(): ServHttpApp {
83
97
  ...servConfig.transport.routePlaintextBytes,
84
98
  },
85
99
  {{/if}}
86
- excludePaths: [...new Set([...publicPaths, ...servConfig.transport.excludePaths])],
100
+ excludePaths: [
101
+ ...new Set([
102
+ ...publicPaths,
103
+ ...servConfig.transport.excludePaths,
104
+ ]),
105
+ ],
87
106
  crypto,
88
107
  serverKeyRing: ring.data,
89
108
  keyStore: createReldbTransportKeyStore({ reldb, ttlSeconds: 3600 }),
@@ -93,23 +112,39 @@ export function createServerApp(): ServHttpApp {
93
112
  openapi: security.exposeApiDocs ? servConfig.http.openapi : false,
94
113
  docs: security.exposeApiDocs ? servConfig.http.docs : false,
95
114
  },
96
- middlewares: [{
97
- middleware: serv.cors({
98
- origin: security.allowOrigin,
99
- credentials: servConfig.cors.credentials,
100
- allowedHeaders: [...new Set([...servConfig.cors.allowedHeaders, 'X-Client-Id', 'X-Encrypted', 'X-Hai-Transport', 'X-Request-Client', 'X-HAI-Token-Transport', 'X-Hai-Reauthentication', 'X-Hai-Terminal'])],
101
- exposedHeaders: [...new Set([...servConfig.cors.exposedHeaders, 'X-Encrypted', 'X-Hai-Transport'])],
102
- }),
103
- }, {
104
- path: `${servConfig.http.apiPrefix}/assets/*`,
105
- middleware: serv.storageAssets({
106
- storage,
107
- pathPrefix: `${servConfig.http.apiPrefix}/assets/`,
108
- keyPattern: /^avatars\/[\w-]+\/[\w-]+\.(png|jpg|webp)$/,
109
- allowedContentTypes: ['image/png', 'image/jpeg', 'image/webp'],
110
- cacheControl: 'public, max-age=31536000, immutable',
111
- crossOriginResourcePolicy: 'cross-origin',
112
- }),
113
- }],
115
+ middlewares: [
116
+ {
117
+ middleware: serv.cors({
118
+ origin: security.allowOrigin,
119
+ credentials: servConfig.cors.credentials,
120
+ allowedHeaders: [
121
+ ...new Set([
122
+ ...servConfig.cors.allowedHeaders,
123
+ 'X-Client-Id',
124
+ 'X-Encrypted',
125
+ 'X-Hai-Transport',
126
+ 'X-Request-Client',
127
+ 'X-HAI-Token-Transport',
128
+ 'X-Hai-Reauthentication',
129
+ 'X-Hai-Terminal',
130
+ ]),
131
+ ],
132
+ exposedHeaders: [
133
+ ...new Set([...servConfig.cors.exposedHeaders, 'X-Encrypted', 'X-Hai-Transport']),
134
+ ],
135
+ }),
136
+ },
137
+ {
138
+ path: `${servConfig.http.apiPrefix}/assets/*`,
139
+ middleware: serv.storageAssets({
140
+ storage,
141
+ pathPrefix: `${servConfig.http.apiPrefix}/assets/`,
142
+ keyPattern: /^avatars\/[\w-]+\/[\w-]+\.(png|jpg|webp)$/,
143
+ allowedContentTypes: ['image/png', 'image/jpeg', 'image/webp'],
144
+ cacheControl: 'public, max-age=31536000, immutable',
145
+ crossOriginResourcePolicy: 'cross-origin',
146
+ }),
147
+ },
148
+ ],
114
149
  })
115
150
  }