@h-ai/cli 0.1.0-alpha.66 → 0.1.0-alpha.67

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 (125) hide show
  1. package/README.en.md +66 -257
  2. package/README.ja.md +66 -257
  3. package/README.md +66 -257
  4. package/dist/{chunk-QYX6V7H2.js → chunk-7DSURTJ4.js} +22 -79
  5. package/dist/chunk-7DSURTJ4.js.map +1 -0
  6. package/dist/{chunk-FWN5B54L.js → chunk-AOQ4N657.js} +182 -10
  7. package/dist/chunk-AOQ4N657.js.map +1 -0
  8. package/dist/cli-config-templates-6AHNUGBN.js +5 -0
  9. package/dist/{cli-config-templates-3XSFIXBP.js.map → cli-config-templates-6AHNUGBN.js.map} +1 -1
  10. package/dist/{cli-skill-templates-TSYEQTCY.js → cli-skill-templates-FC3QRKDO.js} +3 -3
  11. package/dist/{cli-skill-templates-TSYEQTCY.js.map → cli-skill-templates-FC3QRKDO.js.map} +1 -1
  12. package/dist/index.js +435 -247
  13. package/dist/index.js.map +1 -1
  14. package/dist/{src-RDQ2EIBT.js → src-2V6VP4I5.js} +553 -119
  15. package/dist/src-2V6VP4I5.js.map +1 -0
  16. package/package.json +8 -8
  17. package/templates/README.md +48 -145
  18. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +1 -1
  19. package/templates/apps/api/.dockerignore.hbs +14 -7
  20. package/templates/apps/api/.gitignore.hbs +7 -0
  21. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
  22. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +28 -7
  23. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -2
  24. package/templates/apps/api/hai.build.json.hbs +1 -1
  25. package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
  26. package/templates/apps/api/package.json.hbs +5 -0
  27. package/templates/apps/fullstack/.dockerignore.hbs +14 -9
  28. package/templates/apps/fullstack/.gitignore.hbs +7 -0
  29. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +23 -5
  30. package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
  31. package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
  32. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +1 -1
  33. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +22 -4
  34. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
  35. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +23 -5
  36. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +1 -1
  37. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +11 -1
  38. package/templates/apps/fullstack/hai.build.json.hbs +2 -2
  39. package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
  40. package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
  41. package/templates/apps/fullstack/package.json.hbs +2 -0
  42. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
  43. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +27 -6
  44. package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
  45. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +32 -11
  46. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +2 -2
  47. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +3 -3
  48. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +2 -2
  49. package/templates/apps/h5/src/routes/+layout.svelte.hbs +2 -2
  50. package/templates/apps/mobile-app/src/App.svelte.hbs +2 -2
  51. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
  52. package/templates/apps/website/src/routes/+layout.svelte.hbs +3 -3
  53. package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
  54. package/templates/base/.dockerignore.hbs +14 -8
  55. package/templates/base/.gitignore.hbs +7 -0
  56. package/templates/base/e2e/app.spec.ts.hbs +10 -0
  57. package/templates/base/hai.build.json.hbs +1 -1
  58. package/templates/base/{Dockerfile.hbs → images/app.Dockerfile.hbs} +19 -17
  59. package/templates/base/package.json.hbs +8 -3
  60. package/templates/base/vite.config.ts.hbs +3 -1
  61. package/templates/shared/.gitattributes +6 -0
  62. package/templates/skills/AGENTS.md +3 -3
  63. package/templates/skills/bridges/admin/AGENTS.md +1 -1
  64. package/templates/skills/bridges/api/AGENTS.md +1 -1
  65. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +1 -1
  66. package/templates/skills/bridges/h5/AGENTS.md +1 -1
  67. package/templates/skills/bridges/mobile-app/AGENTS.md +1 -1
  68. package/templates/skills/bridges/website/AGENTS.md +1 -1
  69. package/templates/skills/hai-api-client/SKILL.md +14 -161
  70. package/templates/skills/hai-api-client/reference.md +166 -0
  71. package/templates/skills/hai-api-contract/SKILL.md +15 -273
  72. package/templates/skills/hai-api-contract/reference.md +278 -0
  73. package/templates/skills/hai-app-create/SKILL.md +2 -2
  74. package/templates/skills/hai-app-create/development.md +4 -4
  75. package/templates/skills/hai-app-create/reference.md +5 -4
  76. package/templates/skills/hai-app-review/SKILL.md +1 -1
  77. package/templates/skills/hai-app-tests/SKILL.md +4 -3
  78. package/templates/skills/hai-app-tests/reference.md +2 -2
  79. package/templates/skills/hai-app-tests/validation.md +31 -0
  80. package/templates/skills/hai-audit/SKILL.md +17 -142
  81. package/templates/skills/hai-audit/reference.md +145 -0
  82. package/templates/skills/hai-build/SKILL.md +14 -8
  83. package/templates/skills/hai-build/reference.md +10 -9
  84. package/templates/skills/hai-cache/SKILL.md +15 -182
  85. package/templates/skills/hai-cache/reference.md +187 -0
  86. package/templates/skills/hai-capacitor/SKILL.md +4 -3
  87. package/templates/skills/hai-capacitor/reference.md +6 -4
  88. package/templates/skills/hai-ci/SKILL.md +2 -0
  89. package/templates/skills/hai-core/SKILL.md +3 -3
  90. package/templates/skills/hai-crypto/SKILL.md +16 -252
  91. package/templates/skills/hai-crypto/reference.md +256 -0
  92. package/templates/skills/hai-datapipe/SKILL.md +15 -196
  93. package/templates/skills/hai-datapipe/reference.md +202 -0
  94. package/templates/skills/hai-deploy/SKILL.md +20 -55
  95. package/templates/skills/hai-deploy/middleware.md +5 -11
  96. package/templates/skills/hai-framework-sync/SKILL.md +2 -2
  97. package/templates/skills/hai-framework-sync/sync.md +1 -1
  98. package/templates/skills/hai-iam/SKILL.md +1 -3
  99. package/templates/skills/hai-kit/SKILL.md +12 -167
  100. package/templates/skills/hai-kit/reference.md +172 -0
  101. package/templates/skills/hai-payment/SKILL.md +19 -251
  102. package/templates/skills/hai-payment/reference.md +256 -0
  103. package/templates/skills/hai-pr-review/SKILL.md +4 -4
  104. package/templates/skills/hai-reach/SKILL.md +2 -2
  105. package/templates/skills/hai-reldb/SKILL.md +1 -1
  106. package/templates/skills/hai-scheduler/SKILL.md +15 -195
  107. package/templates/skills/hai-scheduler/reference.md +202 -0
  108. package/templates/skills/hai-serv/SKILL.md +2 -2
  109. package/templates/skills/hai-spec-implement/SKILL.md +1 -1
  110. package/templates/skills/hai-spec-implement/workflow.md +3 -3
  111. package/templates/skills/hai-storage/SKILL.md +17 -238
  112. package/templates/skills/hai-storage/reference.md +243 -0
  113. package/templates/skills/hai-ui/SKILL.md +1 -1
  114. package/templates/skills/hai-ui/reference.md +9 -5
  115. package/templates/skills/hai-vecdb/SKILL.md +2 -2
  116. package/dist/chunk-FWN5B54L.js.map +0 -1
  117. package/dist/chunk-QYX6V7H2.js.map +0 -1
  118. package/dist/cli-config-templates-3XSFIXBP.js +0 -5
  119. package/dist/src-RDQ2EIBT.js.map +0 -1
  120. package/templates/apps/api/Dockerfile.hbs +0 -35
  121. package/templates/apps/fullstack/Dockerfile.hbs +0 -57
  122. /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
  123. /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
  124. /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
  125. /package/templates/base/{docker → images/web}/nginx.conf.hbs +0 -0
@@ -31,7 +31,7 @@
31
31
  {{/if}}
32
32
  </svelte:head>
33
33
 
34
- <section class='py-16 px-4 max-w-4xl mx-auto'>
34
+ <section class='mx-auto w-full max-w-[120rem] px-4 py-16 sm:px-6 lg:px-8'>
35
35
  {{#if hasI18n}}
36
36
  <PageHeader title={m.page_services_title()} description={m.page_services_desc()} />
37
37
  {{else}}
@@ -1,19 +1,25 @@
1
- .git
1
+ **/.git
2
+ **/node_modules
3
+ **/.pnpm-store
4
+ **/.cache
5
+ **/.svelte-kit
6
+ **/.turbo
7
+ **/build
8
+ **/dist
9
+ **/coverage
10
+ **/playwright-report
11
+ **/test-results
12
+ **/src-tauri/target
13
+ **/hash-core/target
14
+ tmp
2
15
  **/*.tar
3
16
  .github
4
17
  .agents
5
18
  .claude
6
19
  .codex
7
20
  .opencode
8
- .svelte-kit
9
- .turbo
10
21
  android
11
- build
12
- dist
13
22
  ios
14
- node_modules
15
- playwright-report
16
- test-results
17
23
  **/data
18
24
  *.log
19
25
  # 仅保留公共基础 .env;分环境配置、私有覆盖及已有交付包不进入构建上下文。
@@ -32,3 +32,10 @@ playwright-report/
32
32
  # 本机私有配置;公共 .env 与 dev/test/prod 环境文件纳入版本控制。
33
33
  .env.local
34
34
  .env.*.local
35
+
36
+ # 构建器生成的安装输入快照
37
+ **/images/dependencies/
38
+
39
+ # 项目本地缓存
40
+ **/.pnpm-store/
41
+ **/.cache/
@@ -23,6 +23,16 @@ test('home page renders from browser', async ({ page }) => {
23
23
  await expect(page.locator('main, section').first()).toBeVisible()
24
24
  await expect(page.locator('header').first()).toBeVisible()
25
25
  await expect(page.locator('footer').first()).toBeAttached()
26
+ // 大屏使用可用空间,窄屏由组件内部滚动,不能撑宽整个页面。
27
+ for (const width of [390, 1440, 1920, 2560, 3840]) {
28
+ await page.setViewportSize({ width, height: 960 })
29
+ const availableWidth = await page.evaluate(() => document.documentElement.clientWidth)
30
+ expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
31
+ const shell = page.locator('.hai-mobile-shell')
32
+ if (await shell.count())
33
+ expect((await shell.boundingBox())!.width).toBeGreaterThan(availableWidth - 2)
34
+ }
35
+ await page.setViewportSize({ width: 390, height: 844 })
26
36
  {{#if_eq appType 'h5'}}
27
37
  // 在手机宽度验证导航真正切换路由,避免样式失效但首页渲染测试仍通过。
28
38
  const nav = page.locator('nav')
@@ -1 +1 @@
1
- { "images": [{ "name": "app", "target": "runtime", "containerPort": {{#if isSvelteOnlyApp}}8080{{else}}3000{{/if}} }] }
1
+ { "images": [{ "name": "app", "dockerfile": "images/app.Dockerfile", "containerPort": {{#if isSvelteOnlyApp}}8080{{else}}3000{{/if}} }] }
@@ -1,48 +1,51 @@
1
- # syntax=docker/dockerfile:1.7
2
1
  {{#if isSvelteOnlyApp}}
3
- FROM node:24-bookworm-slim AS build
4
- ARG SQLITE_PREBUILD_MIRROR
5
- ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
2
+ FROM node:24.22.1-bookworm-slim AS build
6
3
 
7
4
  ENV PNPM_HOME=/pnpm
8
5
  ENV PATH=$PNPM_HOME:$PATH
9
6
  ENV CI=true
7
+ ENV ONNXRUNTIME_NODE_INSTALL_CUDA=skip
10
8
  WORKDIR /workspace
11
9
 
12
- RUN corepack enable && corepack prepare pnpm@12.11.2 --activate
10
+ RUN corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config set store-dir /pnpm/store
11
+ COPY pnpm-lock.yaml pnpm-workspace.yaml ./
12
+ COPY images/dependencies/ ./
13
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
13
14
  COPY . .
14
- RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
15
15
  RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
16
16
  RUN pnpm compile
17
17
 
18
- FROM nginxinc/nginx-unprivileged:1.29-alpine AS runtime
18
+ FROM nginxinc/nginx-unprivileged:1.29.8-alpine AS runtime
19
19
 
20
- COPY --chmod=755 docker/19-runtime-config.sh /docker-entrypoint.d/19-hai-runtime-config.sh
21
- COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
20
+ COPY --chmod=755 images/web/19-runtime-config.sh /docker-entrypoint.d/19-hai-runtime-config.sh
21
+ COPY images/web/nginx.conf /etc/nginx/conf.d/default.conf
22
22
  COPY --from=build /workspace/dist /usr/share/nginx/html
23
23
 
24
24
  EXPOSE 8080
25
25
  HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
26
26
  CMD wget -q -O /dev/null http://127.0.0.1:8080/health || exit 1
27
27
  {{else}}
28
- FROM node:24-bookworm-slim AS build
29
- ARG SQLITE_PREBUILD_MIRROR
28
+ FROM node:24.22.1-bookworm-slim AS build
29
+ ARG SQLITE_PREBUILD_MIRROR=
30
30
  ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
31
31
 
32
32
  ENV PNPM_HOME=/pnpm
33
33
  ENV PATH=$PNPM_HOME:$PATH
34
34
  ENV CI=true
35
+ ENV ONNXRUNTIME_NODE_INSTALL_CUDA=skip
35
36
  WORKDIR /workspace
36
37
 
37
- RUN corepack enable && corepack prepare pnpm@12.11.2 --activate
38
+ RUN corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config set store-dir /pnpm/store
39
+ COPY pnpm-lock.yaml pnpm-workspace.yaml ./
40
+ COPY images/dependencies/ ./
41
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
38
42
  COPY . .
39
- RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
40
43
  RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
41
44
  RUN pnpm compile
42
- RUN if [ -d node_modules/better-sqlite3 ]; then cd node_modules/better-sqlite3; for attempt in 1 2 3; do node -e "require('node:child_process').execFileSync(process.execPath,[require.resolve('prebuild-install/bin.js'),'--verbose'],{stdio:'inherit'})" && break; [ "$attempt" = 3 ] && exit 1; sleep 3; done; fi
43
- RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm pm deploy --filter {{projectName}} --prod /prod/app
45
+ RUN --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds if [ -d node_modules/better-sqlite3 ]; then cd node_modules/better-sqlite3; for attempt in 1 2 3; do node -e "require('node:child_process').execFileSync(process.execPath,[require.resolve('prebuild-install/bin.js'),'--verbose'],{stdio:'inherit'})" && break; [ "$attempt" = 3 ] && exit 1; sleep 3; done; fi
46
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds pnpm pm deploy --filter {{projectName}} --prod /prod/app
44
47
 
45
- FROM node:24-bookworm-slim AS runtime-base
48
+ FROM node:24.22.1-bookworm-slim AS runtime-base
46
49
 
47
50
  ENV NODE_ENV=production
48
51
  ENV HOST=0.0.0.0
@@ -58,5 +61,4 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
58
61
  CMD node -e "fetch('http://127.0.0.1:'+process.env.PORT+'/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
59
62
  CMD ["node", "build"]
60
63
 
61
- FROM runtime-base AS runtime
62
64
  {{/if}}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "engines": { "node": "^22.22.2 || ^24.15.0 || >=26.0.0" },
3
3
  "name": "{{projectName}}",
4
- "version": "0.1.0-alpha.66",
4
+ "version": "0.1.0-alpha.67",
5
5
  "private": true,
6
6
  "packageManager": "pnpm@12.11.2",
7
7
  "description": "{{#if_eq appType 'admin'}}Enterprise management system built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'website'}}Enterprise website built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'h5'}}Mobile H5 app built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'api'}}REST API service built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'mobile-app'}}Mobile app built with hai Agent Framework{{/if_eq}}",
@@ -25,7 +25,9 @@
25
25
  "dev:prod": "vite dev --mode prod",
26
26
  "compile": "{{#if hasI18n}}pnpm paraglide:compile && {{/if}}vite build",
27
27
  "build": "hai build",
28
+ {{#if features.deploy}}
28
29
  "deploy": "hai deploy",
30
+ {{/if}}
29
31
  {{#unless isSvelteOnlyApp}}
30
32
  "start": "node --env-file=.env --env-file=.env.prod --env-file-if-exists=.env.local build/index.js",
31
33
  "start:test": "node --env-file=.env --env-file=.env.test --env-file-if-exists=.env.local build/index.js",
@@ -73,16 +75,19 @@
73
75
  "@h-ai/payment": "{{haiPackageSpecifier}}"{{/if}}{{#if features.vecdb}},
74
76
  "@h-ai/vecdb": "{{haiPackageSpecifier}}"{{/if}}{{#if features.datapipe}},
75
77
  "@h-ai/datapipe": "{{haiPackageSpecifier}}"{{/if}}{{#if features.scheduler}},
76
- "@h-ai/scheduler": "{{haiPackageSpecifier}}"{{/if}}{{#if features.deploy}},
77
- "@h-ai/deploy": "{{haiPackageSpecifier}}"{{/if}}{{#if features.api-client}},
78
+ "@h-ai/scheduler": "{{haiPackageSpecifier}}"{{/if}}{{#if features.api-client}},
78
79
  "@h-ai/api-client": "{{haiPackageSpecifier}}"{{/if}}{{#if features.capacitor}},
79
80
  "@h-ai/capacitor": "{{haiPackageSpecifier}}"{{/if}}{{#if isCapacitorApp}}{{#unless features.api-client}},
80
81
  "@h-ai/api-client": "{{haiPackageSpecifier}}"{{/unless}}{{#unless features.capacitor}},
81
82
  "@h-ai/capacitor": "{{haiPackageSpecifier}}"{{/unless}}{{/if}},
83
+ "sm-crypto": "0.5.7",
82
84
  "zod": "{{versions.zod}}"
83
85
  {{/if}}
84
86
  },
85
87
  "devDependencies": {
88
+ {{#if features.deploy}}
89
+ "@h-ai/deploy": "{{haiPackageSpecifier}}",
90
+ {{/if}}
86
91
  "@h-ai/cli": "{{haiPackageSpecifier}}",
87
92
  {{#if isSvelteOnlyApp}}
88
93
  "@h-ai/core": "{{haiPackageSpecifier}}",
@@ -80,7 +80,9 @@ export default defineConfig(({ mode, command }) => {
80
80
  {{/if}}
81
81
  {{#unless isSvelteOnlyApp}}
82
82
  ssr: {
83
- noExternal: [/@h-ai\//],
83
+ noExternal: ['@h-ai/ui', '@h-ai/kit'],
84
+ // sm-crypto 同时列入生产依赖,避免 adapter-node 再次打包其动态 require。
85
+ external: ['@h-ai/core', 'sm-crypto'],
84
86
  },
85
87
  {{/unless}}
86
88
  server: {
@@ -3,3 +3,9 @@
3
3
  *.sh.hbs text eol=lf
4
4
  *.envsh text eol=lf
5
5
  *.envsh.hbs text eol=lf
6
+
7
+ # Linux 容器引擎读取忽略规则和构建文件时统一使用 LF。
8
+ .dockerignore text eol=lf
9
+ *.dockerignore text eol=lf
10
+ Dockerfile text eol=lf
11
+ *.Dockerfile text eol=lf
@@ -41,7 +41,7 @@
41
41
  1. 先搜索现有实现、引用点、测试和 README。
42
42
  2. 小步修改,优先保持目录结构、公开 API 和脚本稳定。
43
43
  3. 改动公共类型、配置、模板或共享逻辑时,同步更新所有调用方与文档。
44
- 4. 每次改动后按影响范围运行质量门禁并记录结果。
44
+ 4. 一轮修改完成后按影响范围收集全部可执行门禁结果,汇总问题再集中修复、复测;普通失败不立即从头重跑。
45
45
 
46
46
  ## 代码注释与示例
47
47
 
@@ -52,7 +52,7 @@
52
52
 
53
53
  ## 质量门禁
54
54
 
55
- 先核对 package.json,按影响范围执行已有脚本;文档检查引用,UI/路由变更再运行 E2E。未运行项说明原因。
55
+ 先核对 package.json,按 [分轮验证与集中修复](.agents/skills/hai-app-tests/validation.md) 收集适用检查与日志,再归并修复、复测失败及受影响项;普通失败不中断独立检查,真实前置阻塞才暂停依赖项。文档检查引用,UI/路由变更加 E2E,未运行项说明原因。
56
56
 
57
57
  ```bash
58
58
  pnpm typecheck
@@ -64,7 +64,7 @@ pnpm test:e2e
64
64
 
65
65
  ## 完成条件
66
66
 
67
- - typecheck / lint / build / test / test:e2e 状态明确;失败必须先修复或明确说明原因。
67
+ - typecheck / lint / build / test / test:e2e 状态明确;整轮收集后集中修复,最终必需门禁对最终代码有效通过,未执行或阻塞明确原因,不能宣称完成。
68
68
  - README、i18n、测试、代码注释与实现保持一致。
69
69
  - 最终回复说明执行过的门禁、已同步的文档 / 依赖方,以及未完成项。
70
70
 
@@ -60,7 +60,7 @@
60
60
 
61
61
  ## 质量门禁
62
62
 
63
- 先核对 package.json,按影响范围执行已有脚本;文档检查引用,UI/路由变更再运行 E2E。未运行项说明原因。
63
+ 先核对 package.json,按 [分轮验证与集中修复](.agents/skills/hai-app-tests/validation.md) 收集适用检查与日志,再归并修复、复测失败及受影响项;普通失败不中断独立检查,真实前置阻塞才暂停依赖项。文档检查引用,UI/路由变更加 E2E,未运行项说明原因。
64
64
 
65
65
  ```bash
66
66
  pnpm typecheck
@@ -57,7 +57,7 @@
57
57
 
58
58
  ## 质量门禁
59
59
 
60
- 先核对 package.json,按影响范围执行已有脚本;文档检查引用,UI/路由变更再运行 E2E。未运行项说明原因。
60
+ 先核对 package.json,按 [分轮验证与集中修复](.agents/skills/hai-app-tests/validation.md) 收集适用检查与日志,再归并修复、复测失败及受影响项;普通失败不中断独立检查,真实前置阻塞才暂停依赖项。文档检查引用,UI/路由变更加 E2E,未运行项说明原因。
61
61
 
62
62
  ```bash
63
63
  pnpm typecheck
@@ -74,7 +74,7 @@ Fullstack 多包工程入口。先读 README、根 workspace 脚本与相关 pac
74
74
 
75
75
  ## 质量门禁
76
76
 
77
- 按影响范围执行,跨包契约从根工作区验证:
77
+ 按 [分轮验证与集中修复](.agents/skills/hai-app-tests/validation.md) 收集一轮适用门禁及日志,汇总后集中修复、复测失败及受影响项;普通失败不中断独立检查,真实前置阻塞才暂停依赖项。跨包契约从根工作区验证:
78
78
 
79
79
  ```bash
80
80
  pnpm typecheck
@@ -60,7 +60,7 @@
60
60
 
61
61
  ## 质量门禁
62
62
 
63
- 先核对 package.json,按影响范围执行已有脚本;文档检查引用,UI/路由变更再运行 E2E。未运行项说明原因。
63
+ 先核对 package.json,按 [分轮验证与集中修复](.agents/skills/hai-app-tests/validation.md) 收集适用检查与日志,再归并修复、复测失败及受影响项;普通失败不中断独立检查,真实前置阻塞才暂停依赖项。文档检查引用,UI/路由变更加 E2E,未运行项说明原因。
64
64
 
65
65
  ```bash
66
66
  pnpm typecheck
@@ -71,7 +71,7 @@ pnpm cap:build:android:release
71
71
 
72
72
  ## 质量门禁
73
73
 
74
- 先核对 package.json,按影响范围执行已有脚本;文档检查引用,UI/路由变更再运行 E2E。未运行项说明原因。
74
+ 先核对 package.json,按 [分轮验证与集中修复](.agents/skills/hai-app-tests/validation.md) 收集适用检查与日志,再归并修复、复测失败及受影响项;普通失败不中断独立检查,真实前置阻塞才暂停依赖项。文档检查引用,UI/路由变更加 E2E,未运行项说明原因。
75
75
 
76
76
  ```bash
77
77
  pnpm typecheck
@@ -60,7 +60,7 @@
60
60
 
61
61
  ## 质量门禁
62
62
 
63
- 先核对 package.json,按影响范围执行已有脚本;文档检查引用,UI/路由变更再运行 E2E。未运行项说明原因。
63
+ 先核对 package.json,按 [分轮验证与集中修复](.agents/skills/hai-app-tests/validation.md) 收集适用检查与日志,再归并修复、复测失败及受影响项;普通失败不中断独立检查,真实前置阻塞才暂停依赖项。文档检查引用,UI/路由变更加 E2E,未运行项说明原因。
64
64
 
65
65
  ```bash
66
66
  pnpm typecheck
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: hai-api-client
3
- description: "使用 @h-ai/api-client 调用 typed API,管理 Token、刷新与加密传输。"
3
+ description: "接入或排查 @h-ai/api-client typed API、登录 Token、自动刷新与加密传输;适用于 Web、SSR 和原生客户端。"
4
4
  ---
5
5
 
6
6
  # hai-api-client
@@ -15,167 +15,20 @@ description: "使用 @h-ai/api-client 调用 typed API,管理 Token、刷新
15
15
  | 输出 | typed client;领域响应为 HaiResult |
16
16
  | 限制 | 先 init,结束 close;浏览器 Cookie 与服务端配置匹配,原生端使用安全 TokenStorage。不要在多用户 SSR 中共享带用户 token 的单例。 |
17
17
 
18
- > `@h-ai/api-client` 是跨端 typed API client。公共 HTTP API 由 `@h-ai/api-contract` 定义,服务端由 `@h-ai/serv` 挂载,客户端直接调用 `apiClient.<domain>.<group>.<operation>()`。
18
+ ## 使用路径
19
19
 
20
- ## 使用步骤
20
+ 1. 读取服务端 contract、apiPrefix、CORS、Cookie 与 transport 配置,选择默认客户端或 `apiClient.create(contract)`。
21
+ 2. 浏览器使用 Cookie/内存;SSR 按请求创建客户端,原生使用安全 TokenStorage,不跨用户复用单例。
22
+ 3. 检查 init 与每次业务调用的 `success`。登录成功后设置 tokens;优先使用 `createAuthActions` 连接认证 UI。
23
+ 4. 同步服务端与客户端的刷新、加密和错误处理;切换配置前 close,任务结束释放客户端。
24
+ 5. 验证登录、刷新重试、失败、退出与会话恢复。
21
25
 
22
- ### 1. 初始化
26
+ ## 按需参考
23
27
 
24
- 浏览器默认使用 httpOnly cookie 存储(推荐):
28
+ 按本次操作读取对应小节;以已安装版本的公开类型核验签名,示例中的业务变量由应用提供。
25
29
 
26
- ```ts
27
- import { apiClient } from '@h-ai/api-client'
28
-
29
- await apiClient.init({
30
- baseUrl: 'https://api.example.com/api/v1',
31
- auth: {},
32
- })
33
- ```
34
-
35
- 启用传输加密时,只传顶层 `crypto` 句柄;api-client 内部自动调用 `crypto.transport.createClient()`:
36
-
37
- ```ts
38
- import { apiClient } from '@h-ai/api-client'
39
- import { crypto } from '@h-ai/crypto'
40
-
41
- await crypto.init()
42
-
43
- await apiClient.init({
44
- baseUrl: 'https://api.example.com/api/v1',
45
- auth: {},
46
- transport: { crypto }, // 默认协商路径:/api/v1/_hai/key-exchange
47
- })
48
- ```
49
-
50
- App/Capacitor 场景传入自定义 TokenStorage:
51
-
52
- ```ts
53
- import { createCapacitorTokenStorage } from '@h-ai/capacitor'
54
-
55
- await apiClient.init({
56
- baseUrl: 'https://api.example.com/api/v1',
57
- auth: { storage: createCapacitorTokenStorage() },
58
- })
59
- ```
60
-
61
- > `createCapacitorTokenStorage()` 仅在 Android/iOS 原生环境 + `@aparajita/capacitor-secure-storage` 已安装时持久化 token;纯 Web 不会回退到 `localStorage`。
62
-
63
- SSR / Node.js 测试场景请显式传入内存存储:
64
-
65
- ```ts
66
- import { apiClient } from '@h-ai/api-client'
67
-
68
- await apiClient.init({
69
- baseUrl: 'https://api.example.com/api/v1',
70
- auth: { storage: apiClient.tokenStorage.memory() },
71
- })
72
- ```
73
-
74
- ### 2. 调用 typed API
75
-
76
- ```ts
77
- const login = await apiClient.iam.auth.login({ identifier: 'alice', password: 'secret' })
78
- if (login.success) {
79
- // 默认 httpOnly cookie 模式下,refresh token 由服务端 Set-Cookie 管理。
80
- await apiClient.auth.setTokens(login.data.tokens)
81
- }
82
-
83
- const me = await apiClient.iam.auth.currentUser()
84
- if (me.success) {
85
- // currentUser 返回服务端最新角色/权限;不要把权限快照持久化到 localStorage。
86
- const { roles, permissions, ...user } = me.data
87
- }
88
- const upload = await apiClient.storage.presignedUrls.createUpload({ key: 'avatar.png' })
89
- const answer = await apiClient.ai.chats.sendMessage({ message: 'hello' })
90
- ```
91
-
92
- ### 3. 自定义 contract
93
-
94
- ```ts
95
- import { apiClient } from '@h-ai/api-client'
96
- import { apiContract } from '@h-ai/api-contract'
97
-
98
- const contract = apiContract.create({ iam: apiContract.iam })
99
- const client = apiClient.create(contract)
100
- await client.init({ baseUrl: 'https://api.example.com/api/v1' })
101
- ```
102
-
103
- `client.config` / `apiClient.config` 返回的是脱敏后的运行配置快照;`baseUrl` 的内嵌凭证与 `headers.authorization` 等敏感值会自动隐藏。
104
-
105
- ### 4. 关闭
106
-
107
- ```ts
108
- await apiClient.close()
109
- ```
110
-
111
- > 已初始化后再次 `init()` 会返回 `CONFIG_ERROR`,不会覆盖现有 client;切换 `baseUrl` / `fetch` / `transport` 前必须先 `close()`,避免旧 transport 会话泄漏。
112
-
113
- ## Token Storage 适配器
114
-
115
- | 工厂调用 | 存储位置 | 适用场景 |
116
- | --- | --- | --- |
117
- | `apiClient.tokenStorage.httpOnlyCookie()` | httpOnly cookie(服务端管理) | **默认**;浏览器端推荐,refresh token 不暴露给 JS |
118
- | `apiClient.tokenStorage.memory()` | 内存 | SSR / Node.js 单元测试(需显式传入) |
119
- | `apiClient.tokenStorage.localStorage()` | localStorage | 非敏感场景(有 XSS 风险,生产不推荐) |
120
-
121
- ### httpOnly Cookie 模式
122
-
123
- Access token 存在内存中,refresh token 由服务端通过 `HttpOnly` cookie 管理,浏览器 JS 无法读取,可有效防止 XSS 窃取 token。
124
-
125
- **前提**:服务端需配置 `serv.createApp({ iam, refreshCookie: {} })`,参见 `hai-serv` skill。
126
-
127
- 启用 `transport` 时,登录、业务请求以及 401 后的自动刷新都会复用同一个加密 fetch 链路;不要在业务代码中手写明文 `/auth/refresh` 请求。
128
-
129
- 自动刷新识别真实 HTTP 401(`Response.status`),以及 HTTP 200 的有效失败 HaiResult(`success=false`、字符串 `error.code/message`、`error.httpStatus=401`),成功后只重试一次原请求。内置登录、注册与配置的 `auth.refreshPath` 排除自动刷新,403/500 等其它失败也不刷新。serv 普通 procedure 的 HTTP 状态仍为 200;调用方须检查最终 `success/error.code` 并处理刷新失败后的登录失效。
130
-
131
- ```ts
132
- import { apiClient } from '@h-ai/api-client'
133
-
134
- // apiClient.tokenStorage.httpOnlyCookie() 是默认存储,无需显式指定
135
- await apiClient.init({
136
- baseUrl: 'https://api.example.com/api/v1',
137
- auth: {},
138
- })
139
-
140
- // 登录后设置 access token(refresh token 由服务端写入 cookie,前端无需操作)
141
- const login = await apiClient.iam.auth.login({ identifier: 'alice', password: 'secret' })
142
- if (login.success) {
143
- await apiClient.auth.setTokens(login.data.tokens)
144
- }
145
- ```
146
-
147
- ## 核心 API
148
-
149
- | API | 用途 |
150
- | --- | --- |
151
- | `apiClient.init(config)` | 初始化默认 API Service client;重复初始化返回 `CONFIG_ERROR` |
152
- | `apiClient.close()` | 清理 client 状态 |
153
- | `apiClient.auth.setTokens(tokens)` | 写入 access token;非 httpOnly 存储会同时写入 refresh token |
154
- | `apiClient.auth.clear()` | 清理 Token |
155
- | `apiClient.auth.onTokenRefreshed(cb)` | 监听刷新结果 |
156
- | `apiClient.create(contract)` | 创建自定义 typed client |
157
- | `apiClient.tokenStorage.{memory,localStorage,httpOnlyCookie}()` | TokenStorage 工厂 |
158
-
159
- ### Transport 配置
160
-
161
- | 字段 | 说明 |
162
- | --- | --- |
163
- | `transport.crypto` | 已初始化的 `@h-ai/crypto` 实例 |
164
- | `transport.keyExchangePath` | 相对于 `baseUrl` 的协商子路径,默认 `/_hai/key-exchange` |
165
-
166
- 服务端必须对应启用 `serv.createApp({ transport: { crypto } })`。
167
- 自动刷新会沿用 `transport.encryptedFetch`,并在 httpOnly cookie 模式下发送空请求体 + `credentials: 'include'`,由服务端从 cookie 读取 refresh token。
168
-
169
- ## 常见模式
170
-
171
- - `baseUrl` 通常包含服务端 `apiPrefix`,例如 `/api/v1`。
172
- - 业务错误不 throw,统一判断 `HaiResult.success`。
173
- - 客户端不导入服务端 `procedures` 或 `@h-ai/serv`。
174
- - 客户端不导入 `@h-ai/crypto` 的内部 transport 工厂,只通过 `transport: { crypto }` 装配。
175
- - 前端只依赖 `@h-ai/api-client` 和 contract 类型,不依赖业务模块实现。
176
- - httpOnly cookie 模式需服务端与客户端同步配置,不支持跨域刷新(`SameSite=Strict`)。
177
-
178
-
179
- ## 配置驱动认证页面
180
-
181
- `apiClient.createAuthActions(client)` 为含标准 iam contract 的客户端提供 publicConfig、login、register、sendOtp、requestPasswordReset、confirmPasswordReset。login 输入使用 identifier/password/method/code,method 为 password、ldap、email、sms。成功认证自动调用既有安全 TokenStorage;应用只需把动作传给 AuthPanel,并在 onauthenticated 中刷新自己的用户状态或导航。
30
+ - [使用步骤](reference.md#使用步骤)
31
+ - [Token Storage 适配器](reference.md#token-storage-适配器)
32
+ - [核心 API](reference.md#核心-api)
33
+ - [常见模式](reference.md#常见模式)
34
+ - [配置驱动认证页面](reference.md#配置驱动认证页面)