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

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 (164) hide show
  1. package/README.en.md +40 -4
  2. package/README.ja.md +40 -4
  3. package/README.md +40 -4
  4. package/dist/{src-2V6VP4I5.js → chunk-FI56GONY.js} +32 -3103
  5. package/dist/chunk-FI56GONY.js.map +1 -0
  6. package/dist/chunk-MBPB37FR.js +1348 -0
  7. package/dist/chunk-MBPB37FR.js.map +1 -0
  8. package/dist/{chunk-7DSURTJ4.js → chunk-ZFNBUF77.js} +327 -199
  9. package/dist/chunk-ZFNBUF77.js.map +1 -0
  10. package/dist/cli-config-templates-W2LLPMB6.js +5 -0
  11. package/dist/{cli-config-templates-6AHNUGBN.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
  12. package/dist/{cli-skill-templates-FC3QRKDO.js → cli-skill-templates-US5CCAJ4.js} +3 -3
  13. package/dist/{cli-skill-templates-FC3QRKDO.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
  14. package/dist/index.js +920 -336
  15. package/dist/index.js.map +1 -1
  16. package/dist/metafile-esm.json +1 -0
  17. package/dist/src-DSJJXM2F.js +3849 -0
  18. package/dist/src-DSJJXM2F.js.map +1 -0
  19. package/package.json +9 -8
  20. package/templates/apps/admin/messages/ja-JP.json +38 -0
  21. package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
  22. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
  23. package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
  24. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
  25. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +5 -1
  26. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
  27. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
  28. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
  29. package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
  30. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
  31. package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
  32. package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
  33. package/templates/apps/api/package.json.hbs +1 -1
  34. package/templates/apps/api/playwright.config.ts.hbs +15 -7
  35. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +1 -1
  36. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
  37. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
  38. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +1 -1
  39. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +1 -1
  40. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
  41. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
  42. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
  43. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +1 -1
  44. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
  45. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +1 -1
  46. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +19 -8
  47. package/templates/apps/fullstack/package.json.hbs +1 -1
  48. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
  49. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
  50. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
  51. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +5 -2
  52. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
  53. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
  54. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
  55. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
  56. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
  57. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
  58. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
  59. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
  60. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
  61. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
  62. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
  63. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +9 -4
  64. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
  65. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
  66. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +5 -5
  67. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
  68. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +1 -1
  69. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
  70. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +11 -12
  71. package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
  72. package/templates/apps/h5/messages/ja-JP.json +40 -0
  73. package/templates/apps/h5/src/routes/+layout.svelte.hbs +14 -3
  74. package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
  75. package/templates/apps/mobile-app/src/App.svelte.hbs +13 -3
  76. package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
  77. package/templates/apps/website/messages/ja-JP.json +32 -0
  78. package/templates/apps/website/src/routes/+layout.svelte.hbs +10 -2
  79. package/templates/base/e2e/app.spec.ts.hbs +13 -8
  80. package/templates/base/package.json.hbs +4 -1
  81. package/templates/base/playwright.config.ts +15 -4
  82. package/templates/base/src/hooks.server.ts.hbs +15 -6
  83. package/templates/base/src/lib/server/init.ts.hbs +139 -30
  84. package/templates/base/vite.config.ts.hbs +2 -2
  85. package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
  86. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
  87. package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
  88. package/templates/i18n/project.inlang/settings.json +1 -1
  89. package/templates/shared/e2e-env.ts +98 -0
  90. package/templates/shared/run-e2e.mjs +51 -12
  91. package/templates/skills/AGENTS.md +4 -0
  92. package/templates/skills/bridges/admin/AGENTS.md +5 -1
  93. package/templates/skills/bridges/api/AGENTS.md +2 -0
  94. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +4 -0
  95. package/templates/skills/bridges/h5/AGENTS.md +5 -1
  96. package/templates/skills/bridges/mobile-app/AGENTS.md +5 -1
  97. package/templates/skills/bridges/website/AGENTS.md +5 -1
  98. package/templates/skills/hai-ai/SKILL.md +8 -2
  99. package/templates/skills/hai-ai/guide.md +134 -60
  100. package/templates/skills/hai-ai/reference.md +51 -11
  101. package/templates/skills/hai-api-client/SKILL.md +2 -0
  102. package/templates/skills/hai-api-client/reference.md +17 -5
  103. package/templates/skills/hai-api-contract/SKILL.md +1 -0
  104. package/templates/skills/hai-api-contract/reference.md +4 -2
  105. package/templates/skills/hai-app-create/SKILL.md +4 -0
  106. package/templates/skills/hai-app-create/development.md +20 -4
  107. package/templates/skills/hai-app-create/reference.md +2 -1
  108. package/templates/skills/hai-app-review/SKILL.md +6 -2
  109. package/templates/skills/hai-app-review/differential.md +34 -0
  110. package/templates/skills/hai-app-tests/SKILL.md +3 -1
  111. package/templates/skills/hai-app-tests/reference.md +4 -4
  112. package/templates/skills/hai-app-tests/validation.md +10 -3
  113. package/templates/skills/hai-audit/SKILL.md +10 -8
  114. package/templates/skills/hai-audit/reference.md +3 -0
  115. package/templates/skills/hai-build/reference.md +6 -4
  116. package/templates/skills/hai-cache/SKILL.md +9 -3
  117. package/templates/skills/hai-cache/reference.md +53 -11
  118. package/templates/skills/hai-capacitor/SKILL.md +3 -1
  119. package/templates/skills/hai-capacitor/reference.md +9 -5
  120. package/templates/skills/hai-core/SKILL.md +4 -0
  121. package/templates/skills/hai-core/reference.md +25 -5
  122. package/templates/skills/hai-crypto/SKILL.md +10 -2
  123. package/templates/skills/hai-crypto/reference.md +64 -10
  124. package/templates/skills/hai-datapipe/SKILL.md +2 -0
  125. package/templates/skills/hai-datapipe/reference.md +3 -1
  126. package/templates/skills/hai-deploy/SKILL.md +9 -4
  127. package/templates/skills/hai-deploy/middleware.md +51 -37
  128. package/templates/skills/hai-iam/SKILL.md +24 -8
  129. package/templates/skills/hai-iam/reference.md +181 -154
  130. package/templates/skills/hai-kit/SKILL.md +23 -7
  131. package/templates/skills/hai-kit/reference.md +42 -24
  132. package/templates/skills/hai-payment/SKILL.md +13 -11
  133. package/templates/skills/hai-payment/reference.md +80 -41
  134. package/templates/skills/hai-pr-review/SKILL.md +21 -88
  135. package/templates/skills/hai-reach/SKILL.md +5 -2
  136. package/templates/skills/hai-reach/reference.md +83 -22
  137. package/templates/skills/hai-reldb/SKILL.md +5 -2
  138. package/templates/skills/hai-reldb/reference.md +68 -25
  139. package/templates/skills/hai-scheduler/SKILL.md +8 -5
  140. package/templates/skills/hai-scheduler/reference.md +38 -24
  141. package/templates/skills/hai-serv/SKILL.md +23 -8
  142. package/templates/skills/hai-serv/reference.md +123 -87
  143. package/templates/skills/hai-spec-implement/SKILL.md +4 -0
  144. package/templates/skills/hai-spec-implement/evaluation.md +18 -0
  145. package/templates/skills/hai-spec-implement/handoff.md +47 -0
  146. package/templates/skills/hai-spec-implement/templates.md +5 -0
  147. package/templates/skills/hai-spec-implement/workflow.md +2 -0
  148. package/templates/skills/hai-spec-review/SKILL.md +4 -0
  149. package/templates/skills/hai-storage/SKILL.md +8 -2
  150. package/templates/skills/hai-storage/reference.md +64 -25
  151. package/templates/skills/hai-ui/SKILL.md +16 -2
  152. package/templates/skills/hai-ui/guide.md +3 -3
  153. package/templates/skills/hai-ui/reference.md +17 -6
  154. package/templates/skills/hai-ui-design/SKILL.md +31 -0
  155. package/templates/skills/hai-ui-design/patterns.md +26 -0
  156. package/templates/skills/hai-ui-review/SKILL.md +30 -0
  157. package/templates/skills/hai-ui-review/checklist.md +22 -0
  158. package/templates/skills/hai-vecdb/SKILL.md +12 -8
  159. package/templates/skills/hai-vecdb/reference.md +33 -3
  160. package/dist/chunk-7DSURTJ4.js.map +0 -1
  161. package/dist/chunk-AOQ4N657.js +0 -640
  162. package/dist/chunk-AOQ4N657.js.map +0 -1
  163. package/dist/cli-config-templates-6AHNUGBN.js +0 -5
  164. package/dist/src-2V6VP4I5.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { generateConfigFile, runtimeEnvironmentVariables, renderEnvironment, moduleEnvironmentVariables, parameterizeConfig } from './chunk-7DSURTJ4.js';
3
- import { cliM } from './chunk-AOQ4N657.js';
2
+ import { generateConfigFile, runtimeEnvironmentVariables, renderEnvironment, generateProductionEnvironment, moduleEnvironmentVariables, parameterizeConfig } from './chunk-ZFNBUF77.js';
3
+ import { cliM } from './chunk-MBPB37FR.js';
4
+ import { external_exports } from './chunk-FI56GONY.js';
4
5
  import './chunk-2H7UOFLK.js';
5
6
  import process4 from 'process';
6
7
  import { cac } from 'cac';
@@ -10,15 +11,17 @@ import { fileURLToPath, pathToFileURL } from 'url';
10
11
  import { wrapAnsi } from 'fast-wrap-ansi';
11
12
  import fse from 'fs-extra';
12
13
  import Handlebars2 from 'handlebars';
14
+ import { parseEnv } from 'util';
13
15
  import ora from 'ora';
14
16
  import prompts from 'prompts';
15
17
  import { Buffer } from 'buffer';
16
18
  import { execSync, execFileSync, spawn } from 'child_process';
17
- import { randomBytes, createHash } from 'crypto';
18
- import { stringify, parseDocument, isScalar, isMap } from 'yaml';
19
+ import { randomBytes, createECDH, createHash, randomUUID } from 'crypto';
20
+ import { stringify, parse, parseDocument, isScalar, isMap } from 'yaml';
19
21
  import { readFileSync, existsSync, mkdirSync, mkdtempSync, createReadStream, writeFileSync, renameSync, rmSync, readdirSync, copyFileSync } from 'fs';
20
22
  import { createRequire } from 'module';
21
23
  import { core } from '@h-ai/core';
24
+ import { glob } from 'fs/promises';
22
25
 
23
26
  var CAMEL_CASE_SEPARATOR_REGEX = /[-_\s]+(.)?/g;
24
27
  var LEADING_CHAR_REGEX = /^./;
@@ -429,6 +432,8 @@ function buildAppHaiCatalogPackages(options) {
429
432
  const packageNames = ["@h-ai/cli"];
430
433
  if (options.isSvelteOnlyApp) {
431
434
  packageNames.push("@h-ai/api-client", "@h-ai/api-contract", "@h-ai/capacitor", "@h-ai/ui");
435
+ if (options.featureMap.crypto)
436
+ packageNames.push("@h-ai/crypto");
432
437
  return pickCatalogPackages(packageNames, HAI_CATALOG_PACKAGES);
433
438
  }
434
439
  packageNames.push("@h-ai/core", "@h-ai/kit");
@@ -470,7 +475,7 @@ function buildAppHaiCatalogPackages(options) {
470
475
  packageNames.push("@h-ai/capacitor");
471
476
  return pickCatalogPackages(packageNames, HAI_CATALOG_PACKAGES);
472
477
  }
473
- function buildFullstackCatalogPackages(frontends, deploy = false) {
478
+ function buildFullstackCatalogPackages(frontends, deploy = false, features = {}) {
474
479
  const packageNames = [
475
480
  ...FULLSTACK_ROOT_CATALOG_PACKAGE_NAMES,
476
481
  "@h-ai/cli",
@@ -486,6 +491,10 @@ function buildFullstackCatalogPackages(frontends, deploy = false) {
486
491
  packageNames.push(...FULLSTACK_DESKTOP_CATALOG_PACKAGE_NAMES);
487
492
  if (deploy)
488
493
  packageNames.push("@h-ai/deploy");
494
+ if (features.ai)
495
+ packageNames.push("@h-ai/ai");
496
+ if (features.vecdb)
497
+ packageNames.push("@h-ai/vecdb");
489
498
  return pickCatalogPackages(packageNames, FULLSTACK_CATALOG_PACKAGES);
490
499
  }
491
500
  var FEATURE_ROUTE_DIRS = {
@@ -591,8 +600,12 @@ function buildTemplateContext(options) {
591
600
  featureMap.cache = true;
592
601
  featureMap.crypto = true;
593
602
  }
594
- if (featureMap.audit || featureMap.scheduler || featureMap.payment) {
603
+ if (featureMap.audit || featureMap.scheduler || featureMap.payment || featureMap.crypto) {
604
+ featureMap.db = true;
605
+ }
606
+ if (featureMap.ai) {
595
607
  featureMap.db = true;
608
+ featureMap.vecdb = true;
596
609
  }
597
610
  const isApi = options.appType === "api";
598
611
  const isFullstack = options.appType === "fullstack";
@@ -603,7 +616,7 @@ function buildTemplateContext(options) {
603
616
  const selectedFrontends = options.frontends ?? DEFAULT_FULLSTACK_FRONTENDS;
604
617
  let haiCatalogPackages;
605
618
  if (isFullstack) {
606
- haiCatalogPackages = buildFullstackCatalogPackages(selectedFrontends, featureMap.deploy);
619
+ haiCatalogPackages = buildFullstackCatalogPackages(selectedFrontends, featureMap.deploy, featureMap);
607
620
  } else if (isApi) {
608
621
  haiCatalogPackages = buildApiWorkspaceCatalogPackages(featureMap);
609
622
  } else {
@@ -695,9 +708,8 @@ function buildFullstackTemplateContext(projectName, frontendsInput, moduleConfig
695
708
  function resolveI18nContext(moduleConfigs) {
696
709
  const coreLocale = moduleConfigs?.core?.defaultLocale ?? "zh-CN";
697
710
  const baseLocale = moduleConfigs?.fullstack?.i18n?.baseLocale ?? coreLocale;
698
- const fallbackLocale = baseLocale === "en-US" ? "zh-CN" : "en-US";
699
711
  const requested = moduleConfigs?.fullstack?.i18n?.locales;
700
- const locales = requested && requested.length > 0 ? [...new Set(requested)] : [baseLocale, fallbackLocale];
712
+ const locales = requested && requested.length > 0 ? [...new Set(requested)] : [.../* @__PURE__ */ new Set([baseLocale, "zh-CN", "en-US", "ja-JP"])];
701
713
  if (!locales.includes(baseLocale)) {
702
714
  locales.unshift(baseLocale);
703
715
  }
@@ -779,6 +791,7 @@ async function generateFromTemplates(projectPath, context) {
779
791
  }
780
792
  await renderDynamicFiles(root, projectPath, context);
781
793
  await fse.copy(path2.join(root, "shared", "run-e2e.mjs"), path2.join(projectPath, "scripts", "run-e2e.mjs"));
794
+ await fse.copy(path2.join(root, "shared", "e2e-env.ts"), path2.join(projectPath, "scripts", "e2e-env.ts"));
782
795
  await fse.ensureDir(path2.join(projectPath, "images", "dependencies"));
783
796
  await fse.writeFile(path2.join(projectPath, "images", "dependencies", ".gitkeep"), "");
784
797
  await fse.copy(path2.join(root, "shared", ".gitattributes"), path2.join(projectPath, ".gitattributes"));
@@ -794,6 +807,9 @@ async function generateFromTemplates(projectPath, context) {
794
807
  const i18nDir = path2.join(root, "i18n");
795
808
  if (await fse.pathExists(i18nDir)) {
796
809
  await fse.copy(i18nDir, projectPath, { overwrite: true });
810
+ const settingsPath = path2.join(projectPath, "project.inlang", "settings.json");
811
+ const settings = await fse.readJson(settingsPath);
812
+ await fse.writeJson(settingsPath, { ...settings, baseLocale: context.defaultLocale }, { spaces: 2 });
797
813
  }
798
814
  }
799
815
  if (includeBaseTemplate) {
@@ -921,136 +937,242 @@ var RE_HTTPS_URL = /^https:\/\/.+/;
921
937
  var RE_GIT_SSH_URL = /^git@.+:.+/;
922
938
  var APP_TYPES = {
923
939
  "admin": {
924
- name: "\u7BA1\u7406\u540E\u53F0",
925
- description: "\u4F01\u4E1A\u7EA7\u7BA1\u7406\u7CFB\u7EDF\uFF0C\u542B IAM\u3001\u6570\u636E\u5E93\u3001\u5B8C\u6574 UI",
940
+ get name() {
941
+ return cliM("cli_appAdminName");
942
+ },
943
+ get description() {
944
+ return cliM("cli_appAdminDescription");
945
+ },
926
946
  defaultFeatures: ["iam", "db", "cache", "crypto"]
927
947
  },
928
948
  "website": {
929
- name: "\u4F01\u4E1A\u5B98\u7F51",
930
- description: "SSR/SSG \u4F01\u4E1A\u5B98\u7F51\uFF0CSEO \u53CB\u597D\u3001\u54CD\u5E94\u5F0F\u5E03\u5C40",
949
+ get name() {
950
+ return cliM("cli_appWebsiteName");
951
+ },
952
+ get description() {
953
+ return cliM("cli_appWebsiteDescription");
954
+ },
931
955
  defaultFeatures: ["db", "cache"]
932
956
  },
933
957
  "h5": {
934
- name: "H5 \u5E94\u7528",
935
- description: "\u79FB\u52A8\u7AEF H5 \u5E94\u7528\uFF0C\u89E6\u6478\u4F18\u5316\u3001PWA \u652F\u6301",
958
+ get name() {
959
+ return cliM("cli_appH5Name");
960
+ },
961
+ get description() {
962
+ return cliM("cli_appH5Description");
963
+ },
936
964
  defaultFeatures: ["db", "cache"]
937
965
  },
938
966
  "api": {
939
- name: "API \u670D\u52A1",
940
- description: "typed API workspace\uFF0C\u542B contract + service \u53CC\u5DE5\u7A0B\u3001\u65E0 UI",
967
+ get name() {
968
+ return cliM("cli_appApiName");
969
+ },
970
+ get description() {
971
+ return cliM("cli_appApiDescription");
972
+ },
941
973
  defaultFeatures: ["db", "cache"]
942
974
  },
943
975
  "mobile-app": {
944
- name: "\u79FB\u52A8\u5E94\u7528",
945
- description: "\u79FB\u52A8\u7AEF\u5E94\u7528\uFF08Capacitor + Svelte 5 + Vite\uFF09\uFF0C\u53EF\u6253\u5305\u4E3A Android/iOS",
976
+ get name() {
977
+ return cliM("cli_appMobileName");
978
+ },
979
+ get description() {
980
+ return cliM("cli_appMobileDescription");
981
+ },
946
982
  defaultFeatures: ["api-client", "capacitor"]
947
983
  },
948
984
  "fullstack": {
949
- name: "\u524D\u540E\u7AEF\u5206\u79BB\u5DE5\u7A0B",
950
- description: "\u591A\u7AEF mini \u7BA1\u7406\u7CFB\u7EDF\uFF0C\u5305\u542B\u7528\u6237\u3001\u89D2\u8272\u3001\u5BA1\u8BA1\u3001\u4E2A\u4EBA\u8BBE\u7F6E\u4E0E\u53CC\u5411\u4F20\u8F93\u52A0\u5BC6",
985
+ get name() {
986
+ return cliM("cli_appFullstackName");
987
+ },
988
+ get description() {
989
+ return cliM("cli_appFullstackDescription");
990
+ },
951
991
  defaultFeatures: ["iam", "db", "cache", "crypto", "storage", "audit"]
952
992
  }
953
993
  };
954
994
  var FRONTEND_TARGETS = {
955
- web: { name: "Web", description: "\u6D4F\u89C8\u5668 Web \u5E94\u7528", implemented: true },
956
- app: { name: "App", description: "\u79FB\u52A8\u7AEF Capacitor App / H5 \u524D\u7AEF", implemented: true },
957
- miniapp: { name: "\u5C0F\u7A0B\u5E8F", description: "\u9884\u7559\u76EE\u6807\uFF0C\u5F53\u524D\u53EA\u751F\u6210\u8BF4\u660E\u5360\u4F4D", implemented: false },
958
- desktop: { name: "\u684C\u9762\u7AEF", description: "Tauri \u684C\u9762\u7AEF\u524D\u7AEF\u5DE5\u7A0B", implemented: true }
995
+ web: { name: "Web", get description() {
996
+ return cliM("cli_frontendWebDescription");
997
+ }, implemented: true },
998
+ app: { name: "App", get description() {
999
+ return cliM("cli_frontendAppDescription");
1000
+ }, implemented: true },
1001
+ miniapp: {
1002
+ get name() {
1003
+ return cliM("cli_frontendMiniappName");
1004
+ },
1005
+ get description() {
1006
+ return cliM("cli_frontendMiniappDescription");
1007
+ },
1008
+ implemented: false
1009
+ },
1010
+ desktop: {
1011
+ get name() {
1012
+ return cliM("cli_frontendDesktopName");
1013
+ },
1014
+ get description() {
1015
+ return cliM("cli_frontendDesktopDescription");
1016
+ },
1017
+ implemented: true
1018
+ }
959
1019
  };
960
1020
  var DEFAULT_FULLSTACK_FRONTENDS2 = ["web", "app", "desktop"];
961
1021
  var FEATURES = {
962
1022
  "iam": {
963
1023
  id: "iam",
964
- name: "\u8EAB\u4EFD\u4E0E\u8BBF\u95EE\u7BA1\u7406",
965
- description: "Session/JWT \u4F1A\u8BDD\u7BA1\u7406\u3001RBAC \u6743\u9650\u63A7\u5236",
1024
+ get name() {
1025
+ return cliM("cli_featureIamName");
1026
+ },
1027
+ get description() {
1028
+ return cliM("cli_featureIamDescription");
1029
+ },
966
1030
  packages: ["@h-ai/iam"],
967
1031
  dependencies: ["crypto", "db", "cache"]
968
1032
  },
969
1033
  "db": {
970
1034
  id: "db",
971
- name: "\u6570\u636E\u5E93",
972
- description: "\u591A\u6570\u636E\u5E93\u652F\u6301 (SQLite/PostgreSQL/MySQL)",
1035
+ get name() {
1036
+ return cliM("cli_featureDbName");
1037
+ },
1038
+ get description() {
1039
+ return cliM("cli_featureDbDescription");
1040
+ },
973
1041
  packages: ["@h-ai/reldb"]
974
1042
  },
975
1043
  "cache": {
976
1044
  id: "cache",
977
- name: "\u7F13\u5B58",
978
- description: "Redis / \u5185\u5B58\u7F13\u5B58",
1045
+ get name() {
1046
+ return cliM("cli_featureCacheName");
1047
+ },
1048
+ get description() {
1049
+ return cliM("cli_featureCacheDescription");
1050
+ },
979
1051
  packages: ["@h-ai/cache"]
980
1052
  },
981
1053
  "ai": {
982
1054
  id: "ai",
983
- name: "AI \u96C6\u6210",
984
- description: "LLM \u9002\u914D\u5668\u3001MCP \u534F\u8BAE\u3001\u6280\u80FD\u7CFB\u7EDF\u3001\u6D41\u5F0F\u54CD\u5E94",
985
- packages: ["@h-ai/ai"]
1055
+ get name() {
1056
+ return cliM("cli_featureAiName");
1057
+ },
1058
+ get description() {
1059
+ return cliM("cli_featureAiDescription");
1060
+ },
1061
+ packages: ["@h-ai/ai"],
1062
+ dependencies: ["db", "vecdb"]
986
1063
  },
987
1064
  "storage": {
988
1065
  id: "storage",
989
- name: "\u6587\u4EF6\u5B58\u50A8",
990
- description: "\u672C\u5730\u5B58\u50A8\u3001S3/OSS/COS \u4E91\u5B58\u50A8",
1066
+ get name() {
1067
+ return cliM("cli_featureStorageName");
1068
+ },
1069
+ get description() {
1070
+ return cliM("cli_featureStorageDescription");
1071
+ },
991
1072
  packages: ["@h-ai/storage"]
992
1073
  },
993
1074
  "crypto": {
994
1075
  id: "crypto",
995
- name: "\u52A0\u5BC6\u6A21\u5757",
996
- description: "\u56FD\u5BC6 SM2/SM3/SM4\u3001Argon2 \u5BC6\u7801\u54C8\u5E0C",
997
- packages: ["@h-ai/crypto"]
1076
+ get name() {
1077
+ return cliM("cli_featureCryptoName");
1078
+ },
1079
+ get description() {
1080
+ return cliM("cli_featureCryptoDescription");
1081
+ },
1082
+ packages: ["@h-ai/crypto"],
1083
+ dependencies: ["db"]
998
1084
  },
999
1085
  "audit": {
1000
1086
  id: "audit",
1001
- name: "\u5BA1\u8BA1\u65E5\u5FD7",
1002
- description: "\u64CD\u4F5C\u5BA1\u8BA1\u3001\u5B89\u5168\u8FFD\u8E2A",
1087
+ get name() {
1088
+ return cliM("cli_featureAuditName");
1089
+ },
1090
+ get description() {
1091
+ return cliM("cli_featureAuditDescription");
1092
+ },
1003
1093
  packages: ["@h-ai/audit"],
1004
1094
  dependencies: ["db"]
1005
1095
  },
1006
1096
  "reach": {
1007
1097
  id: "reach",
1008
- name: "\u89E6\u8FBE\u670D\u52A1",
1009
- description: "\u90AE\u4EF6\u3001\u77ED\u4FE1\u3001\u5FAE\u4FE1\u7B49\u6D88\u606F\u89E6\u8FBE",
1098
+ get name() {
1099
+ return cliM("cli_featureReachName");
1100
+ },
1101
+ get description() {
1102
+ return cliM("cli_featureReachDescription");
1103
+ },
1010
1104
  packages: ["@h-ai/reach"]
1011
1105
  },
1012
1106
  "payment": {
1013
1107
  id: "payment",
1014
- name: "\u7EDF\u4E00\u652F\u4ED8",
1015
- description: "\u5FAE\u4FE1\u652F\u4ED8\u3001\u652F\u4ED8\u5B9D\u3001Stripe \u7EDF\u4E00\u63A5\u5165",
1108
+ get name() {
1109
+ return cliM("cli_featurePaymentName");
1110
+ },
1111
+ get description() {
1112
+ return cliM("cli_featurePaymentDescription");
1113
+ },
1016
1114
  packages: ["@h-ai/payment"],
1017
1115
  dependencies: ["db"]
1018
1116
  },
1019
1117
  "vecdb": {
1020
1118
  id: "vecdb",
1021
- name: "\u5411\u91CF\u6570\u636E\u5E93",
1022
- description: "\u5411\u91CF\u68C0\u7D22\u4E0E\u76F8\u4F3C\u5EA6\u641C\u7D22\uFF08LanceDB / pgvector / Qdrant\uFF09",
1119
+ get name() {
1120
+ return cliM("cli_featureVecdbName");
1121
+ },
1122
+ get description() {
1123
+ return cliM("cli_featureVecdbDescription");
1124
+ },
1023
1125
  packages: ["@h-ai/vecdb"]
1024
1126
  },
1025
1127
  "datapipe": {
1026
1128
  id: "datapipe",
1027
- name: "\u6570\u636E\u5904\u7406\u7BA1\u9053",
1028
- description: "\u6587\u672C\u6E05\u6D17\u3001\u5206\u5757\u4E0E\u53EF\u7EC4\u5408\u5904\u7406\u6D41\u6C34\u7EBF",
1129
+ get name() {
1130
+ return cliM("cli_featureDatapipeName");
1131
+ },
1132
+ get description() {
1133
+ return cliM("cli_featureDatapipeDescription");
1134
+ },
1029
1135
  packages: ["@h-ai/datapipe"]
1030
1136
  },
1031
1137
  "scheduler": {
1032
1138
  id: "scheduler",
1033
- name: "\u4EFB\u52A1\u8C03\u5EA6",
1034
- description: "Cron \u4EFB\u52A1\u6CE8\u518C\u3001\u6267\u884C\u4E0E\u65E5\u5FD7\u8FFD\u8E2A",
1139
+ get name() {
1140
+ return cliM("cli_featureSchedulerName");
1141
+ },
1142
+ get description() {
1143
+ return cliM("cli_featureSchedulerDescription");
1144
+ },
1035
1145
  packages: ["@h-ai/scheduler"],
1036
1146
  dependencies: ["db"]
1037
1147
  },
1038
1148
  "deploy": {
1039
1149
  id: "deploy",
1040
- name: "\u81EA\u52A8\u5316\u90E8\u7F72",
1041
- description: "\u7EDF\u4E00\u90E8\u7F72\u7F16\u6392\u4E0E\u591A\u5E73\u53F0\u53D1\u5E03\u652F\u6301",
1150
+ get name() {
1151
+ return cliM("cli_featureDeployName");
1152
+ },
1153
+ get description() {
1154
+ return cliM("cli_featureDeployDescription");
1155
+ },
1042
1156
  packages: ["@h-ai/deploy"]
1043
1157
  },
1044
1158
  "api-client": {
1045
1159
  id: "api-client",
1046
- name: "HTTP \u5BA2\u6237\u7AEF",
1047
- description: "\u7EDF\u4E00 API \u8C03\u7528\u3001\u9519\u8BEF\u5904\u7406\u4E0E\u6D41\u5F0F\u8BF7\u6C42\u652F\u6301",
1160
+ get name() {
1161
+ return cliM("cli_featureApiClientName");
1162
+ },
1163
+ get description() {
1164
+ return cliM("cli_featureApiClientDescription");
1165
+ },
1048
1166
  packages: ["@h-ai/api-client"]
1049
1167
  },
1050
1168
  "capacitor": {
1051
1169
  id: "capacitor",
1052
- name: "\u79FB\u52A8\u7AEF\u80FD\u529B",
1053
- description: "Capacitor \u539F\u751F\u6865\u63A5\u80FD\u529B\uFF08\u504F\u79FB\u52A8\u5E94\u7528\uFF09",
1170
+ get name() {
1171
+ return cliM("cli_featureCapacitorName");
1172
+ },
1173
+ get description() {
1174
+ return cliM("cli_featureCapacitorDescription");
1175
+ },
1054
1176
  packages: ["@h-ai/capacitor"]
1055
1177
  }
1056
1178
  };
@@ -1074,17 +1196,23 @@ var PROJECT_NAME_REGEX = /^[a-z0-9-]+$/;
1074
1196
  var PROJECT_TEMPLATES = {
1075
1197
  minimal: {
1076
1198
  name: "minimal",
1077
- description: "\u6700\u5C0F\u6A21\u677F \u2014 \u4EC5\u6838\u5FC3\u529F\u80FD",
1199
+ get description() {
1200
+ return cliM("cli_templateMinimalDescription");
1201
+ },
1078
1202
  features: []
1079
1203
  },
1080
1204
  default: {
1081
1205
  name: "default",
1082
- description: "\u6807\u51C6\u6A21\u677F \u2014 IAM + \u6570\u636E\u5E93 + \u7F13\u5B58",
1206
+ get description() {
1207
+ return cliM("cli_templateDefaultDescription");
1208
+ },
1083
1209
  features: ["iam", "db", "cache", "crypto"]
1084
1210
  },
1085
1211
  full: {
1086
1212
  name: "full",
1087
- description: "\u5B8C\u6574\u6A21\u677F \u2014 \u6240\u6709\u529F\u80FD",
1213
+ get description() {
1214
+ return cliM("cli_templateFullDescription");
1215
+ },
1088
1216
  features: [
1089
1217
  "iam",
1090
1218
  "db",
@@ -1105,7 +1233,9 @@ var PROJECT_TEMPLATES = {
1105
1233
  },
1106
1234
  custom: {
1107
1235
  name: "custom",
1108
- description: "\u81EA\u5B9A\u4E49 \u2014 \u9009\u62E9\u9700\u8981\u7684\u529F\u80FD",
1236
+ get description() {
1237
+ return cliM("cli_templateCustomDescription");
1238
+ },
1109
1239
  features: []
1110
1240
  }
1111
1241
  };
@@ -1113,28 +1243,28 @@ async function createProject(options) {
1113
1243
  const spinner = ora();
1114
1244
  try {
1115
1245
  const resolvedOptions = await resolveOptions(options);
1116
- if (!PROJECT_NAME_REGEX.test(resolvedOptions.name)) {
1117
- writeCliError(chalk.red(`\u9879\u76EE\u540D\u79F0\u4E0D\u5408\u6CD5: "${resolvedOptions.name}"\uFF0C\u53EA\u80FD\u5305\u542B\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u8FDE\u5B57\u7B26`));
1118
- return;
1119
- }
1246
+ if (!PROJECT_NAME_REGEX.test(resolvedOptions.name))
1247
+ throw new Error(cliM("cli_createInvalidName", { params: { name: resolvedOptions.name } }));
1120
1248
  const projectPath = path2.resolve(resolvedOptions.cwd ?? ".", resolvedOptions.name);
1121
1249
  if (await fileExists(projectPath)) {
1122
1250
  const { overwrite } = await prompts({
1123
1251
  type: "confirm",
1252
+ active: cliM("cli_yes"),
1253
+ inactive: cliM("cli_no"),
1124
1254
  name: "overwrite",
1125
- message: `\u76EE\u5F55 ${resolvedOptions.name} \u5DF2\u5B58\u5728\uFF0C\u662F\u5426\u8986\u76D6\uFF1F`,
1255
+ message: cliM("cli_createOverwrite", { params: { name: resolvedOptions.name } }),
1126
1256
  initial: false
1127
1257
  });
1128
1258
  if (!overwrite) {
1129
- writeCliMessage(chalk.yellow("\u5DF2\u53D6\u6D88"));
1259
+ writeCliMessage(chalk.yellow(cliM("cli_cancelled")));
1130
1260
  return;
1131
1261
  }
1132
1262
  await fse.remove(projectPath);
1133
1263
  }
1134
- spinner.start("\u521B\u5EFA\u9879\u76EE\u76EE\u5F55...");
1264
+ spinner.start(cliM("cli_createDirectory"));
1135
1265
  await fse.ensureDir(projectPath);
1136
1266
  spinner.succeed();
1137
- spinner.start("\u751F\u6210\u9879\u76EE\u6587\u4EF6...");
1267
+ spinner.start(cliM("cli_createFiles"));
1138
1268
  const context = buildTemplateContext({
1139
1269
  name: resolvedOptions.name,
1140
1270
  appType: resolvedOptions.appType,
@@ -1146,7 +1276,7 @@ async function createProject(options) {
1146
1276
  });
1147
1277
  await generateFromTemplates(projectPath, context);
1148
1278
  {
1149
- const { generateConfigFile: generateConfigFile2, generateEnvExample } = await import('./cli-config-templates-6AHNUGBN.js');
1279
+ const { generateConfigFile: generateConfigFile2, generateEnvExample, generateProductionEnvironment: generateProductionEnvironment2 } = await import('./cli-config-templates-W2LLPMB6.js');
1150
1280
  const configs = resolvedOptions.moduleConfigs;
1151
1281
  const isApiWorkspace = resolvedOptions.appType === "api";
1152
1282
  const isFullstack = resolvedOptions.appType === "fullstack";
@@ -1172,7 +1302,7 @@ async function createProject(options) {
1172
1302
  if (/^_.*\.yml$/.test(file))
1173
1303
  configContents[file.slice(1, -4)] = await fse.readFile(path2.join(configDir, file), "utf8");
1174
1304
  }
1175
- const envFeatures = resolvedOptions.features;
1305
+ const envFeatures = isFullstack ? [.../* @__PURE__ */ new Set([...resolvedOptions.features, "reach"])] : resolvedOptions.features;
1176
1306
  const envContents = generateEnvExample(envFeatures, configs, isFullstack, configContents);
1177
1307
  let envExample = isApiWorkspace ? envContents : isFullstack ? `${envContents}
1178
1308
  # First-start administrator password; must satisfy the IAM password policy.
@@ -1205,29 +1335,36 @@ ${runtimeExample.split("\n").map((line) => line && !line.startsWith("#") ? `# ${
1205
1335
  ${sharedVariables.join("\n")}
1206
1336
  `);
1207
1337
  for (const [mode, environment] of [["dev", "development"], ["test", "test"], ["prod", "production"]]) {
1208
- await writeFile(path2.join(envDirectory, `.env.${mode}`), `# \u516C\u5171 ${mode} \u73AF\u5883\u914D\u7F6E\uFF1B\u672C\u673A\u79C1\u6709\u503C\u653E .env.local\uFF0C\u8FDB\u7A0B\u73AF\u5883\u53D8\u91CF\u4F18\u5148\u3002
1338
+ const values = mode === "prod" ? generateProductionEnvironment2(envFeatures, configs) : `# \u516C\u5171 ${mode} \u73AF\u5883\u914D\u7F6E\uFF1B\u672C\u673A\u79C1\u6709\u503C\u653E .env.local\uFF0C\u8FDB\u7A0B\u73AF\u5883\u53D8\u91CF\u4F18\u5148\u3002
1209
1339
  HAI_CORE_ENV=${environment}
1210
- HAI_CORE_DEBUG=false
1211
- HAI_CORE_LOGGING_LEVEL=${mode === "test" ? "warn" : mode === "dev" ? "debug" : "info"}
1212
- `);
1340
+ HAI_CORE_LOGGING_LEVEL=${mode === "test" ? "warn" : "debug"}
1341
+ `;
1342
+ await writeFile(path2.join(envDirectory, `.env.${mode}`), values);
1213
1343
  }
1344
+ const privateEntries = [];
1214
1345
  if (isFullstack) {
1215
1346
  const password = `Aa1!${randomBytes(24).toString("base64url")}`;
1216
- await fse.writeFile(path2.join(projectPath, ".env.local"), `# \u672C\u673A\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u63D0\u4EA4 Git\u3002
1217
- HAI_ADMIN_DEFAULT_PASSWORD=${password}
1218
- `, { mode: 384 });
1219
- } else {
1220
- await writeFile(path2.join(envDirectory, ".env.local"), "# \u672C\u673A\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u63D0\u4EA4 Git\uFF1B\u8986\u76D6\u516C\u5171\u57FA\u7840\u548C\u73AF\u5883\u914D\u7F6E\u3002\n");
1347
+ privateEntries.push(`HAI_ADMIN_DEFAULT_PASSWORD=${password}`);
1221
1348
  }
1349
+ if (isFullstack || isApiWorkspace && resolvedOptions.features.includes("crypto")) {
1350
+ const key = createECDH("SM2");
1351
+ key.generateKeys();
1352
+ const id = `bootstrap-${randomBytes(6).toString("hex")}`;
1353
+ const ring = { activeKeyId: id, keys: { [id]: { publicKey: key.getPublicKey("hex"), privateKey: key.getPrivateKey("hex").padStart(64, "0") } } };
1354
+ privateEntries.push(`HAI_CRYPTO_TRANSPORT_SERVERKEYRING='${JSON.stringify(ring)}'`);
1355
+ }
1356
+ await fse.writeFile(path2.join(envDirectory, ".env.local"), `# \u672C\u673A\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u63D0\u4EA4 Git\uFF1B\u90E8\u7F72\u51ED\u636E\u7531\u76EE\u6807\u73AF\u5883\u5355\u72EC\u63D0\u4F9B\u3002
1357
+ ${privateEntries.join("\n")}
1358
+ `, { mode: 384 });
1222
1359
  }
1223
- const { generateSkillFiles } = await import('./cli-skill-templates-FC3QRKDO.js');
1360
+ const { generateSkillFiles } = await import('./cli-skill-templates-US5CCAJ4.js');
1224
1361
  await generateSkillFiles(
1225
1362
  projectPath,
1226
1363
  resolvedOptions.features,
1227
1364
  resolvedOptions.appType,
1228
1365
  resolvedOptions.name
1229
1366
  );
1230
- const appTypeLabel = APP_TYPES[resolvedOptions.appType].name;
1367
+ const appTypeLabel = cliM(`cli_app${{ "admin": "Admin", "website": "Website", "h5": "H5", "api": "Api", "mobile-app": "Mobile", "fullstack": "Fullstack" }[resolvedOptions.appType]}Name`, { locale: "zh-CN" });
1231
1368
  const readmeContent = resolvedOptions.appType === "fullstack" ? generateFullstackReadme(resolvedOptions.name, resolvedOptions.packageManager, resolvedOptions.frontends) : resolvedOptions.appType === "api" ? generateApiWorkspaceReadme(resolvedOptions.name, resolvedOptions.packageManager) : generateReadme(resolvedOptions.name, appTypeLabel, resolvedOptions.packageManager, resolvedOptions.appType);
1232
1369
  await writeFile(
1233
1370
  path2.join(projectPath, "README.md"),
@@ -1235,20 +1372,20 @@ HAI_ADMIN_DEFAULT_PASSWORD=${password}
1235
1372
  );
1236
1373
  spinner.succeed();
1237
1374
  if (resolvedOptions.git) {
1238
- spinner.start("\u521D\u59CB\u5316 Git \u4ED3\u5E93...");
1375
+ spinner.start(cliM("cli_createGit"));
1239
1376
  execSync("git init", { cwd: projectPath, stdio: "ignore" });
1240
1377
  if (resolvedOptions.gitRemote) {
1241
1378
  const gitRemote = resolvedOptions.gitRemote;
1242
1379
  if (!RE_HTTPS_URL.test(gitRemote) && !RE_GIT_SSH_URL.test(gitRemote)) {
1243
1380
  spinner.fail();
1244
- throw new Error(`Invalid gitRemote URL: ${gitRemote}`);
1381
+ throw new Error(cliM("cli_createRemoteFailure", { params: { remote: gitRemote } }));
1245
1382
  }
1246
1383
  execFileSync("git", ["remote", "add", "origin", gitRemote], { cwd: projectPath, stdio: "ignore" });
1247
1384
  }
1248
1385
  spinner.succeed();
1249
1386
  }
1250
1387
  if (resolvedOptions.install) {
1251
- spinner.start(`\u5B89\u88C5\u4F9D\u8D56 (${resolvedOptions.packageManager})...`);
1388
+ spinner.start(cliM("cli_createInstall", { params: { manager: resolvedOptions.packageManager } }));
1252
1389
  installDependencies(projectPath, resolvedOptions.packageManager);
1253
1390
  spinner.succeed();
1254
1391
  }
@@ -1263,10 +1400,10 @@ async function resolveOptions(options) {
1263
1400
  const useDefaults = options.yes ?? false;
1264
1401
  writeCliMessage("");
1265
1402
  writeCliMessage(chalk.bold.cyan(" \u{1F680} hai Agent Framework"));
1266
- writeCliMessage(chalk.gray(" AI-Native \xB7 Configuration-Driven \xB7 Security-First"));
1403
+ writeCliMessage(chalk.gray(cliM("cli_createTagline")));
1267
1404
  writeCliMessage("");
1268
1405
  const onCancel = () => {
1269
- writeCliMessage(chalk.red("\n\u5DF2\u53D6\u6D88"));
1406
+ writeCliMessage(chalk.red(cliM("cli_cancelledLine")));
1270
1407
  process4.exit(1);
1271
1408
  };
1272
1409
  let projectName = options.name;
@@ -1276,13 +1413,13 @@ async function resolveOptions(options) {
1276
1413
  const { name } = await prompts({
1277
1414
  type: "text",
1278
1415
  name: "name",
1279
- message: "\u9879\u76EE\u540D\u79F0:",
1416
+ message: cliM("cli_createNamePrompt"),
1280
1417
  initial: "my-app",
1281
1418
  validate: (value) => {
1282
1419
  if (!value.trim())
1283
- return "\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A";
1420
+ return cliM("cli_createNameRequired");
1284
1421
  if (!PROJECT_NAME_REGEX.test(value))
1285
- return "\u9879\u76EE\u540D\u79F0\u53EA\u80FD\u5305\u542B\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u8FDE\u5B57\u7B26";
1422
+ return cliM("cli_createNamePattern");
1286
1423
  return true;
1287
1424
  }
1288
1425
  }, { onCancel });
@@ -1295,8 +1432,9 @@ async function resolveOptions(options) {
1295
1432
  const { appType } = await prompts({
1296
1433
  type: "select",
1297
1434
  name: "appType",
1298
- message: "\u5E94\u7528\u7C7B\u578B:",
1299
- hint: cliM("cli_selectHint"),
1435
+ message: cliM("cli_createTypePrompt"),
1436
+ hint: `
1437
+ ${formatPromptDescription(cliM("cli_selectHint"))}`,
1300
1438
  choices: Object.entries(APP_TYPES).map(([key, t]) => ({
1301
1439
  title: t.name,
1302
1440
  description: formatPromptDescription(t.description),
@@ -1318,8 +1456,9 @@ async function resolveOptions(options) {
1318
1456
  const { template } = await prompts({
1319
1457
  type: "select",
1320
1458
  name: "template",
1321
- message: "\u9009\u62E9\u6A21\u677F:",
1322
- hint: cliM("cli_selectHint"),
1459
+ message: cliM("cli_createTemplatePrompt"),
1460
+ hint: `
1461
+ ${formatPromptDescription(cliM("cli_selectHint"))}`,
1323
1462
  choices: Object.values(PROJECT_TEMPLATES).map((t) => ({
1324
1463
  title: t.name,
1325
1464
  description: formatPromptDescription(t.description),
@@ -1333,7 +1472,9 @@ async function resolveOptions(options) {
1333
1472
  let selectedFeatures = [];
1334
1473
  const appTypeDefaults = APP_TYPES[selectedAppType].defaultFeatures;
1335
1474
  if (selectedAppType === "fullstack") {
1336
- selectedFeatures = resolveFeatureDependencies(appTypeDefaults);
1475
+ selectedFeatures = resolveFeatureDependencies([...appTypeDefaults, ...options.features ?? []]);
1476
+ } else if (options.features) {
1477
+ selectedFeatures = resolveFeatureDependencies(options.features);
1337
1478
  } else if (selectedTemplate === "custom" && !options.features && useDefaults) {
1338
1479
  selectedFeatures = resolveFeatureDependencies(appTypeDefaults);
1339
1480
  } else if (selectedTemplate === "custom" && !options.features) {
@@ -1348,13 +1489,12 @@ async function resolveOptions(options) {
1348
1489
  name: "features",
1349
1490
  message: cliM("cli_featuresPrompt"),
1350
1491
  choices: featureChoices,
1351
- hint: cliM("cli_multiselectHint"),
1492
+ hint: `
1493
+ ${formatPromptDescription(cliM("cli_multiselectHint"))}`,
1352
1494
  instructions: false
1353
1495
  }, { onCancel });
1354
1496
  selectedFeatures = features || [];
1355
1497
  selectedFeatures = resolveFeatureDependencies(selectedFeatures);
1356
- } else if (selectedTemplate === "custom" && options.features) {
1357
- selectedFeatures = resolveFeatureDependencies(options.features);
1358
1498
  } else {
1359
1499
  const templateFeatures = PROJECT_TEMPLATES[selectedTemplate]?.features || [];
1360
1500
  const merged = /* @__PURE__ */ new Set([...templateFeatures, ...appTypeDefaults]);
@@ -1370,8 +1510,10 @@ async function resolveOptions(options) {
1370
1510
  } else if (addExamples === void 0) {
1371
1511
  const { examples } = await prompts({
1372
1512
  type: "confirm",
1513
+ active: cliM("cli_yes"),
1514
+ inactive: cliM("cli_no"),
1373
1515
  name: "examples",
1374
- message: "\u662F\u5426\u6DFB\u52A0\u793A\u4F8B\u4EE3\u7801?",
1516
+ message: cliM("cli_createExamplesPrompt"),
1375
1517
  initial: true
1376
1518
  }, { onCancel });
1377
1519
  addExamples = examples;
@@ -1384,10 +1526,12 @@ async function resolveOptions(options) {
1384
1526
  if (!packageManager) {
1385
1527
  const { pm } = await prompts({
1386
1528
  type: "select",
1529
+ hint: `
1530
+ ${formatPromptDescription(cliM("cli_selectHint"))}`,
1387
1531
  name: "pm",
1388
- message: "\u5305\u7BA1\u7406\u5668:",
1532
+ message: cliM("cli_createManagerPrompt"),
1389
1533
  choices: [
1390
- { title: "pnpm (\u63A8\u8350)", value: "pnpm" },
1534
+ { title: cliM("cli_createPnpmRecommended"), value: "pnpm" },
1391
1535
  { title: "npm", value: "npm" },
1392
1536
  { title: "yarn", value: "yarn" }
1393
1537
  ],
@@ -1401,8 +1545,10 @@ async function resolveOptions(options) {
1401
1545
  } else if (install === void 0) {
1402
1546
  const { doInstall } = await prompts({
1403
1547
  type: "confirm",
1548
+ active: cliM("cli_yes"),
1549
+ inactive: cliM("cli_no"),
1404
1550
  name: "doInstall",
1405
- message: "\u662F\u5426\u5B89\u88C5\u4F9D\u8D56?",
1551
+ message: cliM("cli_createInstallPrompt"),
1406
1552
  initial: true
1407
1553
  }, { onCancel });
1408
1554
  install = doInstall;
@@ -1413,8 +1559,10 @@ async function resolveOptions(options) {
1413
1559
  } else if (git === void 0) {
1414
1560
  const { initGit } = await prompts({
1415
1561
  type: "confirm",
1562
+ active: cliM("cli_yes"),
1563
+ inactive: cliM("cli_no"),
1416
1564
  name: "initGit",
1417
- message: "\u662F\u5426\u521D\u59CB\u5316 Git?",
1565
+ message: cliM("cli_createGitPrompt"),
1418
1566
  initial: true
1419
1567
  }, { onCancel });
1420
1568
  git = initGit;
@@ -1426,14 +1574,14 @@ async function resolveOptions(options) {
1426
1574
  const { remote } = await prompts({
1427
1575
  type: "text",
1428
1576
  name: "remote",
1429
- message: "Git \u4ED3\u5E93\u5730\u5740 (\u53EF\u9009\uFF0C\u56DE\u8F66\u8DF3\u8FC7):",
1577
+ message: cliM("cli_createRemotePrompt"),
1430
1578
  initial: "",
1431
1579
  validate: (value) => {
1432
1580
  if (!value.trim())
1433
1581
  return true;
1434
1582
  if (RE_HTTPS_URL.test(value) || RE_GIT_SSH_URL.test(value))
1435
1583
  return true;
1436
- return "\u8BF7\u8F93\u5165\u5408\u6CD5\u7684 Git \u4ED3\u5E93\u5730\u5740 (https://... \u6216 git@...:...)";
1584
+ return cliM("cli_createRemoteInvalid");
1437
1585
  }
1438
1586
  }, { onCancel });
1439
1587
  gitRemote = remote?.trim() || "";
@@ -1494,7 +1642,9 @@ async function resolveFrontendTargets(frontends, useDefaults, onCancel) {
1494
1642
  value: id,
1495
1643
  selected: DEFAULT_FULLSTACK_FRONTENDS2.includes(id)
1496
1644
  })),
1497
- hint: cliM("cli_multiselectHint"),
1645
+ // 独立行显示帮助,避免宽字符让 prompts 按字符数清除时遗留折行。
1646
+ hint: `
1647
+ ${formatPromptDescription(cliM("cli_multiselectHint"))}`,
1498
1648
  instructions: false
1499
1649
  }, { onCancel });
1500
1650
  return normalizeFrontendTargets(selectedFrontends ?? DEFAULT_FULLSTACK_FRONTENDS2);
@@ -1526,12 +1676,12 @@ function resolveFeatureDependencies(features) {
1526
1676
  async function promptModuleConfigs(features, projectName) {
1527
1677
  const configs = {};
1528
1678
  const onCancel = () => {
1529
- writeCliMessage(chalk.red("\n\u5DF2\u53D6\u6D88"));
1679
+ writeCliMessage(chalk.red(cliM("cli_cancelledLine")));
1530
1680
  process4.exit(1);
1531
1681
  };
1532
1682
  writeCliMessage("");
1533
- writeCliMessage(chalk.bold(" \u2699 \u6A21\u5757\u914D\u7F6E"));
1534
- writeCliMessage(chalk.gray(" \u8BBE\u7F6E\u5173\u952E\u914D\u7F6E\u9879\uFF08\u56DE\u8F66\u4F7F\u7528\u9ED8\u8BA4\u503C\uFF09"));
1683
+ writeCliMessage(chalk.bold(cliM("cli_createModuleConfig")));
1684
+ writeCliMessage(chalk.gray(cliM("cli_createModuleConfigHint")));
1535
1685
  writeCliMessage("");
1536
1686
  configs.core = await promptCoreConfig(projectName, onCancel);
1537
1687
  if (features.includes("db")) {
@@ -1552,27 +1702,32 @@ async function promptModuleConfigs(features, projectName) {
1552
1702
  return configs;
1553
1703
  }
1554
1704
  async function promptCoreConfig(projectName, onCancel) {
1555
- writeCliMessage(chalk.cyan(" [core] \u5E94\u7528\u57FA\u7840\u914D\u7F6E"));
1705
+ writeCliMessage(chalk.cyan(cliM("cli_createCoreConfig")));
1556
1706
  const { defaultLocale } = await prompts({
1557
1707
  type: "select",
1708
+ hint: `
1709
+ ${formatPromptDescription(cliM("cli_selectHint"))}`,
1558
1710
  name: "defaultLocale",
1559
- message: "\u9ED8\u8BA4\u8BED\u8A00:",
1711
+ message: cliM("cli_createLocalePrompt"),
1560
1712
  choices: [
1561
- { title: "\u4E2D\u6587 (zh-CN)", value: "zh-CN" },
1562
- { title: "English (en-US)", value: "en-US" }
1713
+ { title: cliM("cli_createChineseLocale"), value: "zh-CN" },
1714
+ { title: cliM("cli_createEnglishLocale"), value: "en-US" },
1715
+ { title: cliM("cli_createJapaneseLocale"), value: "ja-JP" }
1563
1716
  ],
1564
1717
  initial: 0
1565
1718
  }, { onCancel });
1566
1719
  return { name: projectName, defaultLocale };
1567
1720
  }
1568
1721
  async function promptDbConfig(projectName, onCancel) {
1569
- writeCliMessage(chalk.cyan(" [db] \u6570\u636E\u5E93\u914D\u7F6E"));
1722
+ writeCliMessage(chalk.cyan(cliM("cli_createDbConfig")));
1570
1723
  const { type } = await prompts({
1571
1724
  type: "select",
1725
+ hint: `
1726
+ ${formatPromptDescription(cliM("cli_selectHint"))}`,
1572
1727
  name: "type",
1573
- message: "\u6570\u636E\u5E93\u7C7B\u578B:",
1728
+ message: cliM("cli_createDbTypePrompt"),
1574
1729
  choices: [
1575
- { title: "SQLite (\u96F6\u914D\u7F6E\uFF0C\u9002\u5408\u5F00\u53D1)", value: "sqlite" },
1730
+ { title: cliM("cli_createSqliteOption"), value: "sqlite" },
1576
1731
  { title: "PostgreSQL", value: "postgresql" },
1577
1732
  { title: "MySQL", value: "mysql" }
1578
1733
  ],
@@ -1582,26 +1737,28 @@ async function promptDbConfig(projectName, onCancel) {
1582
1737
  const { database } = await prompts({
1583
1738
  type: "text",
1584
1739
  name: "database",
1585
- message: "SQLite \u6570\u636E\u5E93\u8DEF\u5F84:",
1740
+ message: cliM("cli_createSqlitePathPrompt"),
1586
1741
  initial: "./data/app.db"
1587
1742
  }, { onCancel });
1588
1743
  return { type, database };
1589
1744
  }
1590
1745
  const answers = await prompts([
1591
- { type: "text", name: "host", message: `${type === "postgresql" ? "PostgreSQL" : "MySQL"} \u4E3B\u673A:`, initial: "localhost" },
1592
- { type: "number", name: "port", message: "\u7AEF\u53E3:", initial: type === "postgresql" ? 5432 : 3306 },
1593
- { type: "text", name: "database", message: "\u6570\u636E\u5E93\u540D\u79F0:", initial: projectName }
1746
+ { type: "text", name: "host", message: cliM("cli_createDbHostPrompt", { params: { backend: type === "postgresql" ? "PostgreSQL" : "MySQL" } }), initial: "localhost" },
1747
+ { type: "number", name: "port", message: cliM("cli_createPortPrompt"), initial: type === "postgresql" ? 5432 : 3306 },
1748
+ { type: "text", name: "database", message: cliM("cli_createDatabasePrompt"), initial: projectName }
1594
1749
  ], { onCancel });
1595
1750
  return { type, host: answers.host, port: answers.port, database: answers.database };
1596
1751
  }
1597
1752
  async function promptCacheConfig(onCancel) {
1598
- writeCliMessage(chalk.cyan(" [cache] \u7F13\u5B58\u914D\u7F6E"));
1753
+ writeCliMessage(chalk.cyan(cliM("cli_createCacheConfig")));
1599
1754
  const { type } = await prompts({
1600
1755
  type: "select",
1756
+ hint: `
1757
+ ${formatPromptDescription(cliM("cli_selectHint"))}`,
1601
1758
  name: "type",
1602
- message: "\u7F13\u5B58\u7C7B\u578B:",
1759
+ message: cliM("cli_createCacheTypePrompt"),
1603
1760
  choices: [
1604
- { title: "\u5185\u5B58\u7F13\u5B58 (\u96F6\u914D\u7F6E\uFF0C\u9002\u5408\u5F00\u53D1)", value: "memory" },
1761
+ { title: cliM("cli_createMemoryOption"), value: "memory" },
1605
1762
  { title: "Redis", value: "redis" }
1606
1763
  ],
1607
1764
  initial: 0
@@ -1609,27 +1766,29 @@ async function promptCacheConfig(onCancel) {
1609
1766
  if (type === "memory")
1610
1767
  return { type };
1611
1768
  const answers = await prompts([
1612
- { type: "text", name: "host", message: "Redis \u4E3B\u673A:", initial: "localhost" },
1613
- { type: "number", name: "port", message: "Redis \u7AEF\u53E3:", initial: 6379 }
1769
+ { type: "text", name: "host", message: cliM("cli_createRedisHostPrompt"), initial: "localhost" },
1770
+ { type: "number", name: "port", message: cliM("cli_createRedisPortPrompt"), initial: 6379 }
1614
1771
  ], { onCancel });
1615
1772
  return { type, host: answers.host, port: answers.port };
1616
1773
  }
1617
1774
  async function promptIamConfig(onCancel) {
1618
- writeCliMessage(chalk.cyan(" [iam] \u8BA4\u8BC1\u6388\u6743\u914D\u7F6E"));
1775
+ writeCliMessage(chalk.cyan(cliM("cli_createIamConfig")));
1619
1776
  const answers = await prompts([
1620
- { type: "confirm", name: "loginPassword", message: "\u542F\u7528\u5BC6\u7801\u767B\u5F55?", initial: true },
1621
- { type: "confirm", name: "loginOtp", message: "\u542F\u7528 OTP \u9A8C\u8BC1\u7801\u767B\u5F55?", initial: false }
1777
+ { type: "confirm", active: cliM("cli_yes"), inactive: cliM("cli_no"), name: "loginPassword", message: cliM("cli_createPasswordPrompt"), initial: true },
1778
+ { type: "confirm", active: cliM("cli_yes"), inactive: cliM("cli_no"), name: "loginOtp", message: cliM("cli_createOtpPrompt"), initial: false }
1622
1779
  ], { onCancel });
1623
1780
  return { loginPassword: answers.loginPassword, loginOtp: answers.loginOtp };
1624
1781
  }
1625
1782
  async function promptStorageConfig(onCancel) {
1626
- writeCliMessage(chalk.cyan(" [storage] \u6587\u4EF6\u5B58\u50A8\u914D\u7F6E"));
1783
+ writeCliMessage(chalk.cyan(cliM("cli_createStorageConfig")));
1627
1784
  const { type } = await prompts({
1628
1785
  type: "select",
1786
+ hint: `
1787
+ ${formatPromptDescription(cliM("cli_selectHint"))}`,
1629
1788
  name: "type",
1630
- message: "\u5B58\u50A8\u7C7B\u578B:",
1789
+ message: cliM("cli_createStorageTypePrompt"),
1631
1790
  choices: [
1632
- { title: "\u672C\u5730\u5B58\u50A8 (\u96F6\u914D\u7F6E\uFF0C\u9002\u5408\u5F00\u53D1)", value: "local" },
1791
+ { title: cliM("cli_createLocalStorageOption"), value: "local" },
1633
1792
  { title: "S3 / MinIO / OSS", value: "s3" }
1634
1793
  ],
1635
1794
  initial: 0
@@ -1638,7 +1797,7 @@ async function promptStorageConfig(onCancel) {
1638
1797
  const { localPath } = await prompts({
1639
1798
  type: "text",
1640
1799
  name: "localPath",
1641
- message: "\u5B58\u50A8\u8DEF\u5F84:",
1800
+ message: cliM("cli_createStoragePathPrompt"),
1642
1801
  initial: "./data/uploads"
1643
1802
  }, { onCancel });
1644
1803
  return { type, localPath };
@@ -1646,15 +1805,17 @@ async function promptStorageConfig(onCancel) {
1646
1805
  return { type };
1647
1806
  }
1648
1807
  async function promptAiConfig(onCancel) {
1649
- writeCliMessage(chalk.cyan(" [ai] AI \u96C6\u6210\u914D\u7F6E"));
1808
+ writeCliMessage(chalk.cyan(cliM("cli_createAiConfig")));
1650
1809
  const { defaultProvider } = await prompts({
1651
1810
  type: "select",
1811
+ hint: `
1812
+ ${formatPromptDescription(cliM("cli_selectHint"))}`,
1652
1813
  name: "defaultProvider",
1653
- message: "AI Provider:",
1814
+ message: cliM("cli_createProviderPrompt"),
1654
1815
  choices: [
1655
1816
  { title: "OpenAI", value: "openai" },
1656
1817
  { title: "Anthropic (Claude)", value: "anthropic" },
1657
- { title: "\u5176\u4ED6 (OpenAI \u517C\u5BB9)", value: "custom" }
1818
+ { title: cliM("cli_createCompatibleAiOption"), value: "custom" }
1658
1819
  ],
1659
1820
  initial: 0
1660
1821
  }, { onCancel });
@@ -1662,7 +1823,7 @@ async function promptAiConfig(onCancel) {
1662
1823
  const { model } = await prompts({
1663
1824
  type: "text",
1664
1825
  name: "model",
1665
- message: "\u9ED8\u8BA4\u6A21\u578B:",
1826
+ message: cliM("cli_createModelPrompt"),
1666
1827
  initial: defaultModel
1667
1828
  }, { onCancel });
1668
1829
  return { defaultProvider, model };
@@ -1758,6 +1919,8 @@ function generateEnvironmentReadme() {
1758
1919
 
1759
1920
  \u8FD0\u884C\u53C2\u6570\u5728 \`.env.example\` \u4E2D\u9010\u9879\u8BF4\u660E\u7528\u9014\u3001\u679A\u4E3E\u548C\u8303\u56F4\u3002\`.env\` \u4E3A\u516C\u5171\u57FA\u7840\uFF0C\`.env.dev\` / \`.env.test\` / \`.env.prod\` \u4E3A\u73AF\u5883\u8986\u76D6\uFF0C\u5747\u63D0\u4EA4 Git\uFF1B\`.env.local\` \u4E3A\u672C\u673A\u79C1\u6709\u8986\u76D6\uFF0C\u4E0D\u63D0\u4EA4 Git\u3002\u4F18\u5148\u7EA7\u4E3A\u8FDB\u7A0B\u73AF\u5883 > \`.env.local\` > \u73AF\u5883\u6587\u4EF6 > \`.env\`\u3002YAML \u663E\u5F0F\u5F15\u7528 \`HAI_<\u914D\u7F6E\u540D>_<\u8DEF\u5F84>\` \u5E76\u4FDD\u7559\u672C\u5730\u56DE\u9000\uFF0CcamelCase \u4E0D\u62C6\u8BCD\u3002\u5B8C\u6574\u547D\u4EE4\u3001\u52A0\u8F7D\u4F18\u5148\u7EA7\u548C Provider \u5207\u6362\u89C1 [\u73AF\u5883\u914D\u7F6E](.agents/skills/hai-app-create/development.md#\u73AF\u5883\u914D\u7F6E)\u3002
1760
1921
 
1922
+ \u751F\u4EA7 .env.prod \u6309\u5DF2\u542F\u7528\u6A21\u5757\u9009\u62E9\u5171\u4EAB PostgreSQL\uFF08\u660E\u786E\u9009\u62E9 MySQL \u65F6\u4FDD\u7559\uFF09\u3001Redis\u3001S3\u3001Qdrant\uFF0C\u9ED8\u8BA4\u4F7F\u7528 Compose \u5185\u90E8\u670D\u52A1\u540D db/cache/storage/vecdb\u3002\u5BC6\u7801\u3001S3 \u51ED\u636E\u3001\u6A21\u578B Key \u4E0E\u53D7\u4FDD\u62A4 Qdrant \u7684 Key \u53EA\u7531\u79C1\u6709\u73AF\u5883\u6CE8\u5165\uFF1B\u5BB9\u5668\u5916\u8FD0\u884C\u8986\u76D6\u5185\u90E8\u8FDE\u63A5\u5730\u5740\u3002\u751F\u4EA7 Reach \u5FC5\u987B\u6CE8\u5165\u771F\u5B9E HAI_REACH_PROVIDERS JSON\uFF0C\u516C\u5171\u7A7A\u6570\u7EC4\u4F1A\u62D2\u7EDD\u542F\u52A8\u3002AI \u81EA\u52A8\u8865\u5168 db+vecdb \u4F9D\u8D56\uFF0C\u5E76\u5728\u4E8C\u8005\u5C31\u7EEA\u540E\u521D\u59CB\u5316\uFF0C\u5931\u8D25\u9006\u5E8F\u6E05\u7406\u3002
1923
+
1761
1924
  | \u7528\u9014 | \u547D\u4EE4 |
1762
1925
  | --- | --- |
1763
1926
  | \u5F00\u53D1 / \u6D4B\u8BD5\u6A21\u5F0F / \u751F\u4EA7\u6A21\u5F0F\u9884\u89C8 | \`pnpm dev\` / \`dev:test\` / \`dev:prod\` |
@@ -1792,7 +1955,8 @@ ${pm} exec hai deploy ${appDir} --env prod --services web
1792
1955
  \u6784\u5EFA\u4E0D\u8BFB\u53D6\u90E8\u7F72\u73AF\u5883\uFF1B\u4EA7\u7269\u4F4D\u4E8E .hai/build/docker/<buildId>\uFF0C\u7528 --artifact-dir \u7CBE\u786E\u9009\u62E9\u3002
1793
1956
  \u5E94\u7528\u6839 package.json.version \u9A71\u52A8\u7248\u672C\uFF0C\u540C\u7248\u672C\u5141\u8BB8\u91CD\u65B0\u6784\u5EFA\u548C\u53D1\u5E03\uFF1B\u6E05\u5355\u8BB0\u5F55\u72EC\u7ACB\u6784\u5EFA\u7F16\u53F7\u4E0E\u6821\u9A8C\u548C\u3002
1794
1957
  \u5B8C\u6574\u79BB\u7EBF\u5305\u81EA\u52A8\u643A\u5E26\u81EA\u5EFA\u4E2D\u95F4\u4EF6\u955C\u50CF\uFF1B\u51C6\u5907\u4E2D\u95F4\u4EF6\u955C\u50CF\u9700\u8981\u6784\u5EFA\u673A\u6709\u53EF\u7528 Podman/Docker\uFF0C\u7F3A\u955C\u50CF\u65F6\u5728\u6253\u5305\u9636\u6BB5\u62C9\u53D6\u3002
1795
- \u670D\u52A1\u5668\u9700\u8981 Linux\u3001Docker/Podman\u3001Docker Compose v2 \u6216 podman-compose 1.6.0 \u53CA\u4EE5\u4E0A\u3001sh\u3001tar\u3001sha256sum\u3001awk\u3001curl\uFF0C\u542F\u52A8\u9636\u6BB5\u4E0D\u8054\u7F51\u62C9\u53D6\u3002
1958
+ \u670D\u52A1\u5668\u9700\u8981 Linux\u3001Docker/Podman\u3001Docker Compose v2 \u6216 podman-compose 1.6.0 \u53CA\u4EE5\u4E0A\u3001sh\u3001tar\u3001sha256sum\u3001awk\u3001curl\u3001util-linux flock \u548C\u652F\u6301 flock \u7684\u6587\u4EF6\u7CFB\u7EDF\uFF0C\u542F\u52A8\u9636\u6BB5\u4E0D\u8054\u7F51\u62C9\u53D6\u3002
1959
+ \u56FA\u5B9A .deploy.lock \u5185\u6838\u9501\u8986\u76D6\u90E8\u7F72\u53CA\u6062\u590D\uFF1B\u7981\u6B62\u5220\u9664\u6216\u66FF\u6362\uFF0C\u6240\u6709\u6301\u6709\u8FDB\u7A0B\u9000\u51FA\u6216\u91CD\u542F\u540E\u81EA\u52A8\u91CA\u653E\u3002\u5F3A\u6740\u540E\u987B\u6838\u5BF9\u7ED3\u679C\u6587\u4EF6\u3001current \u548C\u5BB9\u5668\u72B6\u6001\uFF0C\u518D\u7528\u65B0\u5305\u6062\u590D\u3002
1796
1960
  tar \u65C1\u7684\u90E8\u7F72\u76EE\u5F55\u63D0\u4F9B deploy.ps1\uFF08Windows\uFF09\u548C deploy.sh\uFF08Linux/macOS\uFF09\uFF0C\u6309\u5DF2\u4FDD\u5B58\u7684 SSH \u53C2\u6570\u4E0A\u4F20\u3001\u5BFC\u5165\u3001\u542F\u52A8\u5E76\u9A8C\u8BC1 HTTP\u3002
1797
1961
  \u5B8C\u5168\u9694\u79BB\u65F6\u624B\u5DE5\u642C\u8FD0 tar\uFF0C\u72EC\u7ACB\u76EE\u5F55\u89E3\u5305\u540E\u6267\u884C sh apply.sh\uFF0C\u6216\u4F20\u5165\u90E8\u7F72\u76EE\u5F55\u8986\u76D6\u9ED8\u8BA4\u4F4D\u7F6E\u3002
1798
1962
  \u90E8\u5206\u66F4\u65B0\u8981\u6C42\u5DF2\u6709\u6210\u529F\u7684\u5B8C\u6574\u90E8\u7F72\uFF0C\u4FDD\u7559\u672A\u9009\u670D\u52A1\u53CA\u6570\u636E\u5377\uFF1B\u5931\u8D25\u8FD4\u56DE\u975E\u96F6\u9000\u51FA\u7801\uFF0C\u5DF2\u6709\u90E8\u7F72\u5C1D\u8BD5\u6062\u590D\u9009\u4E2D\u670D\u52A1\u3002
@@ -1817,7 +1981,7 @@ function generateReadme(name, appTypeLabel, pm, appType) {
1817
1981
 
1818
1982
  ### \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
1819
1983
 
1820
- \u9875\u9762\u7EDF\u4E00\u7EE7\u627F\u6839\u5E03\u5C40\u7684 header/footer\u3002${appType === "mobile-app" ? "\u9875\u9762\u653E src/lib/pages/**/*.svelte\uFF0Chash \u8DEF\u7531\u81EA\u52A8\u53D1\u73B0\uFF0C\u5237\u65B0\u4E0E\u524D\u8FDB\u540E\u9000\u6062\u590D\u5F53\u524D\u9875\u3002" : "\u4F7F\u7528 SvelteKit \u81EA\u52A8\u6587\u4EF6\u8DEF\u7531\uFF0C\u65B0\u589E +page.svelte \u65E0\u9700\u7EF4\u62A4\u8DEF\u7531\u8868\uFF1B\u5237\u65B0\u5B9A\u4F4D\u4E0E\u670D\u52A1\u7AEF\u9274\u6743\u4FDD\u6301\u4E00\u81F4\u3002"}
1984
+ \u9875\u9762\u7EDF\u4E00\u7EE7\u627F\u6839\u5E03\u5C40\u7684 header/footer\u3002${appType === "mobile-app" ? "\u9875\u9762\u653E src/lib/pages/**/*.svelte\uFF0Chash \u8DEF\u7531\u81EA\u52A8\u53D1\u73B0\uFF0C\u5237\u65B0\u4E0E\u524D\u8FDB\u540E\u9000\u6062\u590D\u5F53\u524D\u9875\u3002" : "\u4F7F\u7528 SvelteKit \u81EA\u52A8\u6587\u4EF6\u8DEF\u7531\uFF0C\u65B0\u589E +page.svelte \u65E0\u9700\u7EF4\u62A4\u8DEF\u7531\u8868\uFF1B\u5237\u65B0\u5B9A\u4F4D\u4E0E\u670D\u52A1\u7AEF\u9274\u6743\u4FDD\u6301\u4E00\u81F4\u3002"}${appType === "mobile-app" ? "\n\n\u9009\u62E9 IAM \u6216 crypto \u7684\u79FB\u52A8\u5BA2\u6237\u7AEF\u9ED8\u8BA4\u542F\u7528\u52A0\u5BC6 transport\uFF0C\u5148\u521D\u59CB\u5316 crypto\uFF0C\u518D\u521D\u59CB\u5316 typed API client\uFF1B\u7ED3\u675F\u65F6\u6309\u76F8\u53CD\u987A\u5E8F\u5173\u95ED\u3002\u4EC5\u660E\u786E\u8FDE\u63A5\u5339\u914D\u7684\u660E\u6587 API \u65F6\u8BBE\u7F6E PUBLIC_API_TRANSPORT=off\uFF1BPUBLIC_API_KEY_EXCHANGE_PATH \u4EC5\u8986\u76D6\u516C\u5F00\u534F\u5546\u5B50\u8DEF\u5F84\u3002\u63E1\u624B\u5931\u8D25\u4E0D\u4F1A\u9000\u56DE\u660E\u6587\uFF0C\u670D\u52A1\u7AEF\u79C1\u94A5\u4E0D\u8FDB\u5165\u5BA2\u6237\u7AEF\u3002\u672A\u9009\u62E9 crypto \u7684\u5BA2\u6237\u7AEF\u4FDD\u7559\u660E\u6587 API \u6A21\u5F0F\u3002" : ""}
1821
1985
 
1822
1986
  ### \u5F00\u53D1
1823
1987
 
@@ -1961,9 +2125,11 @@ CLI \u5DF2\u5728\u6839\u76EE\u5F55\u751F\u6210 \`.env.local\`\uFF0C\u5176\u4E2D
1961
2125
 
1962
2126
  \u672C\u5730\u5F00\u53D1\u9ED8\u8BA4 \`PUBLIC_API_BASE=http://127.0.0.1:3000\`\uFF0CVite \u4F7F\u7528\u540C\u6E90\u4EE3\u7406\uFF1B\u90E8\u7F72\u65F6\u6539\u4E3A\u5B9E\u9645\u516C\u7F51 API Origin\uFF0CAndroid \u6A21\u62DF\u5668\u8BBF\u95EE\u5BBF\u4E3B\u673A\u65F6\u4F7F\u7528 \`http://10.0.2.2:3000\`\u3002\u4E0D\u8981\u5C06\u5BC6\u94A5\u653E\u5165\u4EFB\u4F55 \`PUBLIC_\` \u6216 \`VITE_\` \u53D8\u91CF\u3002
1963
2127
 
2128
+ \u670D\u52A1\u7AEF\u7248\u672C\u5316\u4F20\u8F93\u5BC6\u94A5\u73AF\u5728\u521B\u5EFA\u65F6\u4E00\u6B21\u6027\u5199\u5165\u88AB\u5FFD\u7565\u7684 \`.env.local\`\uFF0C\u53D8\u91CF\u4E3A \`HAI_CRYPTO_TRANSPORT_SERVERKEYRING\`\uFF1B\u8FD0\u884C\u65F6\u4E0D\u751F\u6210\u5BC6\u94A5\u3002\u90E8\u7F72\u7531 Secret Manager \u5411\u6240\u6709\u8282\u70B9\u5206\u53D1\u540C\u4E00\u53D7\u7BA1 ring\uFF0C\u914D\u5408\u5171\u4EAB registry\u3002\u79C1\u94A5\u4E0D\u8FDB\u5165\u516C\u5F00\u914D\u7F6E\u3001PUBLIC_/VITE_ \u6216\u65E5\u5FD7\u3002
2129
+
1964
2130
  \u6240\u6709\u4E1A\u52A1 JSON \u8BF7\u6C42\u548C\u54CD\u5E94\u9ED8\u8BA4\u901A\u8FC7 \`@h-ai/crypto\` transport \u53CC\u5411\u52A0\u5BC6\uFF08\u542B\u767B\u5F55\u3001\u5237\u65B0\u3001\u5934\u50CF\u4E0A\u4F20\uFF09\uFF1B\u524D\u7AEF\u7EDF\u4E00\u4F7F\u7528\u5171\u4EAB typed API client\u3002Web \u8BBF\u95EE\u4EE4\u724C\u4EC5\u5B58\u5185\u5B58\u3001\u5237\u65B0\u4EE4\u724C\u7531 HttpOnly Cookie \u4FDD\u5B58\uFF1B\u53D7\u4FE1\u539F\u751F\u7AEF\u4F7F\u7528\u5E73\u53F0\u5B89\u5168\u5B58\u50A8\u6216\u5185\u5B58\u3002\u5934\u50CF\u4E3A\u968F\u673A\u8DEF\u5F84\u53EA\u8BFB\u56FE\u7247\u8D44\u6E90\uFF0C\u652F\u6301 PNG/JPEG/WebP\uFF0C\u6700\u5927 512 KiB\u3002\u5065\u5EB7\u68C0\u67E5\u548C\u53EA\u8BFB\u5934\u50CF\u56FE\u7247\u53EF\u76F4\u63A5\u8BBF\u95EE\u3002
1965
2131
 
1966
- \u5E94\u7528\u663E\u793A\u540D\u79F0\u3001\u7248\u672C\u3001\u73AF\u5883\u3001\u9ED8\u8BA4\u8BED\u8A00\u548C\u65E5\u5FD7\u8BBE\u7F6E\u6765\u81EA\u540E\u7AEF \`config/_core.yml\`\uFF0C\u524D\u7AEF\u901A\u8FC7 \`app.info\` \u83B7\u53D6\u5B89\u5168\u5143\u4FE1\u606F\u3002\u9ED8\u8BA4 SQLite\u3001\u672C\u5730\u5B58\u50A8\u4E0E\u5185\u5B58\u7F13\u5B58\u9002\u5408\u5355\u5B9E\u4F8B\uFF0C\u7BA1\u7406\u5199\u5165\u4EC5\u652F\u6301\u5355\u5199\u5B9E\u4F8B\uFF1B\u591A\u5B9E\u4F8B\u8BFB\u53D6\u53EF\u4F7F\u7528\u5171\u4EAB\u6570\u636E\u5E93\u3001Redis cache \u548C S3 \u5B58\u50A8\uFF0C\u4F46\u7BA1\u7406\u5199\u8BF7\u6C42\u987B\u7EDF\u4E00\u8DEF\u7531\u5230\u540C\u4E00\u5199\u5B9E\u4F8B\u3002\u53E6\u9700\u4F1A\u8BDD\u7C98\u6027\u8DEF\u7531\u4F7F\u8BF7\u6C42\u547D\u4E2D\u540C\u4E00\u8FDB\u7A0B\u5185\u4F20\u8F93\u79C1\u94A5\uFF0C\u670D\u52A1\u91CD\u542F\u540E\u5BA2\u6237\u7AEF\u91CD\u65B0\u534F\u5546\u3002\u751F\u4EA7\u9700\u663E\u5F0F\u914D\u7F6E\u5141\u8BB8\u7684 CORS Origin\u3002\u4E1A\u52A1\u63D0\u4EA4\u540E\u7684\u5BA1\u8BA1\u5199\u5165\u91C7\u7528\u6700\u5927\u52AA\u529B\uFF0C\u5931\u8D25\u8BB0\u5F55\u7ED3\u6784\u5316\u9519\u8BEF\u65E5\u5FD7\uFF0C\u4FDD\u7559\u5DF2\u5B8C\u6210\u4E1A\u52A1\u7684\u539F\u59CB\u7ED3\u679C\u3002
2132
+ \u5E94\u7528\u663E\u793A\u540D\u79F0\u3001\u7248\u672C\u3001\u73AF\u5883\u3001\u9ED8\u8BA4\u8BED\u8A00\u548C\u65E5\u5FD7\u8BBE\u7F6E\u6765\u81EA\u540E\u7AEF \`config/_core.yml\`\uFF0C\u524D\u7AEF\u901A\u8FC7 \`app.info\` \u83B7\u53D6\u5B89\u5168\u5143\u4FE1\u606F\u3002\u5F00\u53D1\u9ED8\u8BA4 SQLite\u3001\u672C\u5730\u5B58\u50A8\u4E0E\u5185\u5B58\u7F13\u5B58\uFF1B\u751F\u4EA7\u542F\u52A8\u62D2\u7EDD\u8FD9\u4E9B\u672C\u5730 Provider\uFF0C\u5FC5\u987B\u5C06\u6240\u6709\u8282\u70B9\u8FDE\u63A5\u5230\u540C\u4E00\u5171\u4EAB\u6570\u636E\u5E93\u3001Redis \u4E0E S3\u3002\u4F20\u8F93\u5FC5\u987B\u5728\u6240\u6709\u8282\u70B9\u6CE8\u5165\u76F8\u540C\u53D7\u7BA1\u7248\u672C\u5316\u5BC6\u94A5\u73AF\u4E0E\u5171\u4EAB registry\uFF0C\u65E0\u9700\u4F1A\u8BDD\u7C98\u6027\uFF1B\u91CD\u542F\u8BFB\u53D6\u539F ring\uFF0C\u8F6E\u6362\u5148\u5206\u53D1\u65B0\u65E7\u7248\u672C\u518D\u5207 activeKeyId\u3002\u751F\u4EA7\u9700\u663E\u5F0F\u914D\u7F6E\u5141\u8BB8\u7684 CORS Origin\u3002\u4E1A\u52A1\u63D0\u4EA4\u540E\u7684\u5BA1\u8BA1\u5199\u5165\u91C7\u7528\u6700\u5927\u52AA\u529B\uFF0C\u5931\u8D25\u8BB0\u5F55\u7ED3\u6784\u5316\u9519\u8BEF\u65E5\u5FD7\uFF0C\u4FDD\u7559\u5DF2\u5B8C\u6210\u4E1A\u52A1\u7684\u539F\u59CB\u7ED3\u679C\u3002
1967
2133
 
1968
2134
  ### \u914D\u7F6E\u6269\u5C55
1969
2135
 
@@ -2031,10 +2197,10 @@ Apache-2.0
2031
2197
  }
2032
2198
  function printCompletionMessage(options) {
2033
2199
  writeCliMessage("");
2034
- writeCliMessage(chalk.green("\u2714 \u9879\u76EE\u521B\u5EFA\u6210\u529F\uFF01"));
2200
+ writeCliMessage(chalk.green(cliM("cli_createSuccess")));
2035
2201
  writeCliMessage("");
2036
2202
  if (options.features.length > 0) {
2037
- writeCliMessage(chalk.bold("\u5DF2\u542F\u7528\u529F\u80FD:"));
2203
+ writeCliMessage(chalk.bold(cliM("cli_createFeatures")));
2038
2204
  for (const featureId of options.features) {
2039
2205
  const feature = FEATURES[featureId];
2040
2206
  if (feature) {
@@ -2043,7 +2209,7 @@ function printCompletionMessage(options) {
2043
2209
  }
2044
2210
  writeCliMessage("");
2045
2211
  }
2046
- writeCliMessage("\u4E0B\u4E00\u6B65\uFF1A");
2212
+ writeCliMessage(cliM("cli_nextSteps"));
2047
2213
  writeCliMessage(chalk.cyan(` cd ${options.name}`));
2048
2214
  if (options.appType === "fullstack")
2049
2215
  writeCliMessage(chalk.cyan(cliM("cli_adminPasswordSaved")));
@@ -2084,25 +2250,61 @@ async function detectProject(cwd) {
2084
2250
  // src/commands/cli-add.ts
2085
2251
  var MODULE_MAP = {
2086
2252
  // iam 依赖 crypto(password hash)/ db(user 存储)/ cache(session 缓存)
2087
- "iam": { packages: ["@h-ai/iam"], deps: ["crypto", "db", "cache"], configKey: "iam", description: "\u8EAB\u4EFD\u4E0E\u8BBF\u95EE\u7BA1\u7406" },
2253
+ "iam": { packages: ["@h-ai/iam"], deps: ["crypto", "db", "cache"], configKey: "iam", get description() {
2254
+ return cliM("cli_featureIamName");
2255
+ } },
2088
2256
  // 'db' 和 'reldb' 都映射到 @h-ai/reldb,兼容用户习惯(hai add db 和 hai add reldb 均可)
2089
- "db": { packages: ["@h-ai/reldb"], configKey: "db", description: "\u6570\u636E\u5E93" },
2090
- "reldb": { packages: ["@h-ai/reldb"], configKey: "db", description: "\u6570\u636E\u5E93" },
2091
- "cache": { packages: ["@h-ai/cache"], configKey: "cache", description: "\u7F13\u5B58" },
2092
- "ai": { packages: ["@h-ai/ai"], configKey: "ai", description: "AI \u96C6\u6210" },
2093
- "storage": { packages: ["@h-ai/storage"], configKey: "storage", description: "\u6587\u4EF6\u5B58\u50A8" },
2094
- "crypto": { packages: ["@h-ai/crypto"], description: "\u52A0\u5BC6\u6A21\u5757" },
2095
- "kit": { packages: ["@h-ai/kit"], description: "SvelteKit \u96C6\u6210\u5DE5\u5177" },
2096
- "ui": { packages: ["@h-ai/ui"], description: "UI \u7EC4\u4EF6\u5E93" },
2097
- "deploy": { packages: ["@h-ai/deploy"], configKey: "deploy", description: "\u81EA\u52A8\u5316\u90E8\u7F72" },
2098
- "vecdb": { packages: ["@h-ai/vecdb"], configKey: "vecdb", description: "\u5411\u91CF\u6570\u636E\u5E93" },
2099
- "datapipe": { packages: ["@h-ai/datapipe"], description: "\u6570\u636E\u5904\u7406\u7BA1\u9053" },
2100
- "reach": { packages: ["@h-ai/reach"], configKey: "reach", description: "\u6D88\u606F\u63A8\u9001" },
2101
- "scheduler": { packages: ["@h-ai/scheduler"], deps: ["db"], configKey: "scheduler", description: "\u4EFB\u52A1\u8C03\u5EA6" },
2102
- "audit": { packages: ["@h-ai/audit"], deps: ["db"], configKey: "audit", description: "\u5BA1\u8BA1\u65E5\u5FD7" },
2103
- "payment": { packages: ["@h-ai/payment"], configKey: "payment", description: "\u652F\u4ED8" },
2104
- "capacitor": { packages: ["@h-ai/capacitor"], description: "\u79FB\u52A8\u7AEF\u80FD\u529B" },
2105
- "api-client": { packages: ["@h-ai/api-client"], description: "HTTP \u5BA2\u6237\u7AEF" }
2257
+ "db": { packages: ["@h-ai/reldb"], configKey: "db", get description() {
2258
+ return cliM("cli_featureDbName");
2259
+ } },
2260
+ "reldb": { packages: ["@h-ai/reldb"], configKey: "db", get description() {
2261
+ return cliM("cli_featureDbName");
2262
+ } },
2263
+ "cache": { packages: ["@h-ai/cache"], configKey: "cache", get description() {
2264
+ return cliM("cli_featureCacheName");
2265
+ } },
2266
+ "ai": { packages: ["@h-ai/ai"], deps: ["db", "vecdb"], configKey: "ai", get description() {
2267
+ return cliM("cli_featureAiName");
2268
+ } },
2269
+ "storage": { packages: ["@h-ai/storage"], configKey: "storage", get description() {
2270
+ return cliM("cli_featureStorageName");
2271
+ } },
2272
+ "crypto": { packages: ["@h-ai/crypto"], deps: ["db"], get description() {
2273
+ return cliM("cli_featureCryptoName");
2274
+ } },
2275
+ "kit": { packages: ["@h-ai/kit"], get description() {
2276
+ return cliM("cli_featureKitName");
2277
+ } },
2278
+ "ui": { packages: ["@h-ai/ui"], get description() {
2279
+ return cliM("cli_featureUiName");
2280
+ } },
2281
+ "deploy": { packages: ["@h-ai/deploy"], configKey: "deploy", get description() {
2282
+ return cliM("cli_featureDeployName");
2283
+ } },
2284
+ "vecdb": { packages: ["@h-ai/vecdb"], configKey: "vecdb", get description() {
2285
+ return cliM("cli_featureVecdbName");
2286
+ } },
2287
+ "datapipe": { packages: ["@h-ai/datapipe"], get description() {
2288
+ return cliM("cli_featureDatapipeName");
2289
+ } },
2290
+ "reach": { packages: ["@h-ai/reach"], configKey: "reach", get description() {
2291
+ return cliM("cli_featureReachName");
2292
+ } },
2293
+ "scheduler": { packages: ["@h-ai/scheduler"], deps: ["db"], configKey: "scheduler", get description() {
2294
+ return cliM("cli_featureSchedulerName");
2295
+ } },
2296
+ "audit": { packages: ["@h-ai/audit"], deps: ["db"], configKey: "audit", get description() {
2297
+ return cliM("cli_featureAuditName");
2298
+ } },
2299
+ "payment": { packages: ["@h-ai/payment"], configKey: "payment", get description() {
2300
+ return cliM("cli_featurePaymentName");
2301
+ } },
2302
+ "capacitor": { packages: ["@h-ai/capacitor"], get description() {
2303
+ return cliM("cli_featureCapacitorName");
2304
+ } },
2305
+ "api-client": { packages: ["@h-ai/api-client"], get description() {
2306
+ return cliM("cli_featureApiClientName");
2307
+ } }
2106
2308
  };
2107
2309
  async function addModule(options) {
2108
2310
  const spinner = ora();
@@ -2110,7 +2312,7 @@ async function addModule(options) {
2110
2312
  try {
2111
2313
  const project = await detectProject(cwd);
2112
2314
  if (!project) {
2113
- writeCliError(chalk.red("\u672A\u68C0\u6D4B\u5230\u9879\u76EE\uFF08\u627E\u4E0D\u5230 package.json\uFF09"));
2315
+ writeCliError(chalk.red(cliM("cli_projectNotFound")));
2114
2316
  return;
2115
2317
  }
2116
2318
  let moduleName = options.module;
@@ -2121,13 +2323,14 @@ async function addModule(options) {
2121
2323
  value: key
2122
2324
  }));
2123
2325
  if (choices.length === 0) {
2124
- writeCliMessage(chalk.green("\u6240\u6709\u6A21\u5757\u5DF2\u5B89\u88C5\uFF01"));
2326
+ writeCliMessage(chalk.green(cliM("cli_addAllInstalled")));
2125
2327
  return;
2126
2328
  }
2127
2329
  const { selected } = await prompts({
2128
2330
  type: "select",
2331
+ hint: cliM("cli_selectHint"),
2129
2332
  name: "selected",
2130
- message: "\u9009\u62E9\u8981\u6DFB\u52A0\u7684\u6A21\u5757:",
2333
+ message: cliM("cli_addModulePrompt"),
2131
2334
  choices
2132
2335
  });
2133
2336
  if (!selected)
@@ -2136,11 +2339,11 @@ async function addModule(options) {
2136
2339
  }
2137
2340
  const moduleDef = MODULE_MAP[moduleName];
2138
2341
  if (!moduleDef) {
2139
- writeCliError(chalk.red(`\u672A\u77E5\u6A21\u5757: ${moduleName}`));
2140
- writeCliMessage(`\u53EF\u7528\u6A21\u5757: ${Object.keys(MODULE_MAP).join(", ")}`);
2342
+ writeCliError(chalk.red(cliM("cli_addUnknownModule", { params: { module: moduleName } })));
2343
+ writeCliMessage(cliM("cli_addAvailableModules", { params: { modules: Object.keys(MODULE_MAP).join(", ") } }));
2141
2344
  return;
2142
2345
  }
2143
- const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-FC3QRKDO.js');
2346
+ const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-US5CCAJ4.js');
2144
2347
  if (moduleDef.packages.every((p) => project.haiPackages.includes(p))) {
2145
2348
  const generatedFiles2 = await generateProjectAiSupport(
2146
2349
  cwd,
@@ -2151,12 +2354,12 @@ async function addModule(options) {
2151
2354
  }
2152
2355
  );
2153
2356
  if (generatedFiles2.length > 0) {
2154
- writeCliMessage(chalk.yellow(`\u6A21\u5757 ${moduleName} \u5DF2\u5B89\u88C5\uFF0C\u5DF2\u8865\u5168 AI \u652F\u6301\u6587\u4EF6`));
2357
+ writeCliMessage(chalk.yellow(cliM("cli_addSupportCompleted", { params: { module: moduleName } })));
2155
2358
  for (const generatedFile of generatedFiles2) {
2156
- writeCliMessage(chalk.gray(` + ${generatedFile} (AI support)`));
2359
+ writeCliMessage(chalk.gray(cliM("cli_addSupportFile", { params: { file: generatedFile } })));
2157
2360
  }
2158
2361
  } else {
2159
- writeCliMessage(chalk.yellow(`\u6A21\u5757 ${moduleName} \u5DF2\u5B89\u88C5`));
2362
+ writeCliMessage(chalk.yellow(cliM("cli_addAlreadyInstalled", { params: { module: moduleName } })));
2160
2363
  }
2161
2364
  return;
2162
2365
  }
@@ -2171,7 +2374,7 @@ async function addModule(options) {
2171
2374
  }
2172
2375
  }
2173
2376
  }
2174
- spinner.start("\u66F4\u65B0 package.json...");
2377
+ spinner.start(cliM("cli_addUpdateManifest"));
2175
2378
  const pkgPath = path2.join(cwd, "package.json");
2176
2379
  const pkg = await fse.readJson(pkgPath);
2177
2380
  if (!pkg.dependencies)
@@ -2192,12 +2395,12 @@ async function addModule(options) {
2192
2395
  const configDir = path2.join(cwd, "config");
2193
2396
  const configPath = path2.join(configDir, `_${moduleDef.configKey}.yml`);
2194
2397
  if (!await fileExists(configPath)) {
2195
- spinner.start(`\u751F\u6210\u914D\u7F6E\u6587\u4EF6 config/_${moduleDef.configKey}.yml...`);
2398
+ spinner.start(cliM("cli_addConfig", { params: { config: moduleDef.configKey } }));
2196
2399
  const content = generateConfigFile(moduleDef.configKey);
2197
2400
  await writeFile(configPath, content);
2198
2401
  spinner.succeed();
2199
2402
  } else {
2200
- writeCliMessage(chalk.gray(`\u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728: config/_${moduleDef.configKey}.yml`));
2403
+ writeCliMessage(chalk.gray(cliM("cli_addConfigExists", { params: { config: moduleDef.configKey } })));
2201
2404
  }
2202
2405
  }
2203
2406
  if (moduleDef.deps) {
@@ -2208,7 +2411,7 @@ async function addModule(options) {
2208
2411
  if (!await fileExists(depConfigPath)) {
2209
2412
  const content = generateConfigFile(depDef.configKey);
2210
2413
  await writeFile(depConfigPath, content);
2211
- writeCliMessage(chalk.gray(` + config/_${depDef.configKey}.yml (\u4F9D\u8D56)`));
2414
+ writeCliMessage(chalk.gray(cliM("cli_addDependencyConfig", { params: { config: depDef.configKey } })));
2212
2415
  }
2213
2416
  }
2214
2417
  }
@@ -2224,28 +2427,36 @@ async function addModule(options) {
2224
2427
  );
2225
2428
  if (generatedFiles.length > 0) {
2226
2429
  for (const generatedFile of generatedFiles) {
2227
- writeCliMessage(chalk.gray(` + ${generatedFile} (AI support)`));
2430
+ writeCliMessage(chalk.gray(cliM("cli_addSupportFile", { params: { file: generatedFile } })));
2228
2431
  }
2229
2432
  }
2433
+ const productionPath = path2.join(cwd, ".env.prod");
2434
+ const previousProduction = await fileExists(productionPath) ? await fse.readFile(productionPath, "utf8") : "";
2435
+ const configured = new Set([...previousProduction.matchAll(/^([A-Z][A-Z0-9_]*)=/gm)].map((match) => match[1]));
2436
+ const previousValues = parseEnv(previousProduction);
2437
+ const production = generateProductionEnvironment([moduleName, ...moduleDef.deps ?? []], previousValues.HAI_DB_TYPE === "mysql" ? { db: { type: "mysql", database: previousValues.HAI_DB_DATABASE } } : void 0).split("\n").filter((line) => !/^[A-Z][A-Z0-9_]*=/.test(line) || !configured.has(line.split("=")[0]));
2438
+ await writeFile(productionPath, `${previousProduction.trimEnd()}
2439
+
2440
+ ${production.join("\n")}`);
2230
2441
  const doInstall = options.install !== false;
2231
2442
  if (doInstall) {
2232
- spinner.start("\u5B89\u88C5\u4F9D\u8D56...");
2443
+ spinner.start(cliM("cli_addInstall"));
2233
2444
  const { execSync: execSync2 } = await import('child_process');
2234
2445
  const pm = await detectPackageManager(cwd);
2235
2446
  execSync2(`${pm} install`, { cwd, stdio: "ignore" });
2236
2447
  spinner.succeed();
2237
2448
  }
2238
2449
  writeCliMessage("");
2239
- writeCliMessage(chalk.green(`\u2714 \u6A21\u5757 ${moduleName} \u6DFB\u52A0\u6210\u529F\uFF01`));
2450
+ writeCliMessage(chalk.green(cliM("cli_addSuccess", { params: { module: moduleName } })));
2240
2451
  writeCliMessage("");
2241
- writeCliMessage("\u5DF2\u6DFB\u52A0\u7684\u5305:");
2452
+ writeCliMessage(cliM("cli_addPackages"));
2242
2453
  for (const pkg2 of allPackages) {
2243
2454
  writeCliMessage(chalk.cyan(` ${pkg2}`));
2244
2455
  }
2245
2456
  writeCliMessage("");
2246
2457
  } catch (error) {
2247
2458
  spinner.fail();
2248
- writeCliError(chalk.red("\u6DFB\u52A0\u6A21\u5757\u5931\u8D25:"), { error });
2459
+ writeCliError(chalk.red(cliM("cli_addFailed")), { error });
2249
2460
  throw error;
2250
2461
  }
2251
2462
  }
@@ -2446,38 +2657,133 @@ function prepareDependencyInputs(cwd) {
2446
2657
  function basenameSafe(directory) {
2447
2658
  return directory.slice(dirname(directory).length + 1).toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+/, "") || "app";
2448
2659
  }
2449
- function displayDeployValue(name, value) {
2450
- if (/PASSWORD|SECRET|TOKEN|APIKEY|PRIVATEKEY|ACCESSKEY|AUTHORIZATION/i.test(name.replace(/[^a-z0-9]/gi, "")) || /(?:^|_)PASS$/i.test(name) || value.includes("PRIVATE KEY"))
2451
- return cliM("cli_deploy_secret");
2452
- try {
2453
- const parsed2 = JSON.parse(value);
2454
- if (parsed2 && typeof parsed2 === "object") {
2455
- const sanitize = (object) => {
2456
- if (Array.isArray(object))
2457
- return object.map(sanitize);
2458
- if (object && typeof object === "object")
2459
- return Object.fromEntries(Object.entries(object).map(([key, item]) => [key, /PASSWORD|SECRET|TOKEN|APIKEY|PRIVATEKEY|ACCESSKEY|AUTHORIZATION/i.test(key.replace(/[^a-z0-9]/gi, "")) || /(?:^|_)PASS$/i.test(key) ? cliM("cli_deploy_secret") : typeof item === "string" ? displayDeployValue(key, item) : sanitize(item)]));
2460
- return object;
2461
- };
2462
- return JSON.stringify(sanitize(parsed2));
2463
- }
2464
- } catch {
2465
- }
2466
- try {
2467
- const url = new URL(value);
2468
- if (url.username || url.password) {
2469
- url.username = "***";
2470
- url.password = "***";
2471
- }
2472
- for (const key of url.searchParams.keys()) {
2473
- if (/password|secret|token|key|authorization/i.test(key))
2474
- url.searchParams.set(key, "***");
2475
- }
2476
- return url.toString();
2477
- } catch {
2478
- return value;
2479
- }
2480
- }
2660
+
2661
+ // ../reldb/dist/chunk-SDS2ZT3V.js
2662
+ external_exports.enum(["sqlite", "postgresql", "mysql"]);
2663
+ var ReldbOperationLogConfigSchema = external_exports.object({
2664
+ read: external_exports.boolean().default(false),
2665
+ write: external_exports.boolean().default(false),
2666
+ /** 安全数量摘要的最大字符数;0 表示省略摘要,永不记录 SQL 原文或参数值。 */
2667
+ maxLength: external_exports.number().int().min(0).default(1e3),
2668
+ level: external_exports.enum(["info", "debug", "trace"]).default("debug")
2669
+ });
2670
+ var OperationLogConfigFieldSchema = {
2671
+ operationLog: ReldbOperationLogConfigSchema.optional()
2672
+ };
2673
+ var PoolConfigSchema = external_exports.object({
2674
+ /** PostgreSQL 已建立连接的最小保留数(默认 1,不主动预建) */
2675
+ min: external_exports.number().int().min(0).default(1),
2676
+ /** 最大连接数(默认 10) */
2677
+ max: external_exports.number().int().min(1).default(10),
2678
+ /** PostgreSQL 空闲连接超时,毫秒(默认 30000;0 禁用) */
2679
+ idleTimeout: external_exports.number().int().min(0).default(3e4),
2680
+ /** 获取连接超时,毫秒(默认10000);PostgreSQL >=0,0禁用;MySQL排队+建连总期限为1–2147483647 */
2681
+ acquireTimeout: external_exports.number().int().min(0).default(1e4)
2682
+ });
2683
+ var MysqlPoolConfigSchema = external_exports.object({
2684
+ /** 最大连接数(默认 10) */
2685
+ max: external_exports.number().int().min(1).default(10),
2686
+ /** 获取连接超时,毫秒(默认10000);PostgreSQL >=0,0禁用;MySQL排队+建连总期限为1–2147483647 */
2687
+ acquireTimeout: external_exports.number().int().min(1).max(2147483647).default(1e4),
2688
+ /** MySQL 等待队列最大请求数(默认 1000;正整数,不支持无限队列) */
2689
+ queueLimit: external_exports.number().int().min(1).default(1e3)
2690
+ }).strict();
2691
+ var SslConfigSchema = external_exports.union([
2692
+ external_exports.boolean(),
2693
+ external_exports.enum(["require", "prefer", "allow", "disable"]),
2694
+ external_exports.record(external_exports.string(), external_exports.unknown())
2695
+ ]);
2696
+ var MysqlSslConfigSchema = external_exports.union([
2697
+ external_exports.literal(false),
2698
+ external_exports.object({
2699
+ /** MySQL 是否校验证书链(默认 true);false 明确放弃服务端认证 */
2700
+ rejectUnauthorized: external_exports.boolean().default(true),
2701
+ /** MySQL 是否验证 DNS 主机身份(默认 true);仅证书链验证开启时有效 */
2702
+ verifyIdentity: external_exports.boolean().default(true),
2703
+ /** MySQL 信任的 CA,PEM 内容;省略时使用 Node 信任库 */
2704
+ ca: external_exports.string().min(1).optional(),
2705
+ /** MySQL 客户端证书,PEM 内容;必须同时提供 key */
2706
+ cert: external_exports.string().min(1).optional(),
2707
+ /** MySQL 客户端私钥,PEM 内容;必须同时提供 cert */
2708
+ key: external_exports.string().min(1).optional()
2709
+ }).strict().refine((ssl) => Boolean(ssl.cert) === Boolean(ssl.key), { path: ["key"] })
2710
+ ]);
2711
+ var SqliteOptionsSchema = external_exports.object({
2712
+ /** 是否启用 WAL 模式(默认 true),可提高并发读写性能 */
2713
+ walMode: external_exports.boolean().default(true),
2714
+ /** 是否只读模式(默认 false) */
2715
+ readonly: external_exports.boolean().default(false),
2716
+ /**
2717
+ * 执行方式(默认 sync)。
2718
+ *
2719
+ * - `sync`:主线程直接执行 better-sqlite3(同步阻塞主线程事件循环)。
2720
+ * - `worker`:在专用 worker 线程持有唯一连接执行,把阻塞移出主线程。
2721
+ * 注意 WAL 只改善数据库锁并发,无法避免 better-sqlite3 同步调用占用调用线程。
2722
+ */
2723
+ executor: external_exports.enum(["sync", "worker"]).default("sync"),
2724
+ /** worker 执行方式下等待连接打开的超时(毫秒,默认 10000) */
2725
+ workerConnectTimeoutMs: external_exports.number().int().positive().default(1e4)
2726
+ });
2727
+ var MysqlOptionsSchema = external_exports.object({
2728
+ /** 字符集(默认 utf8mb4),推荐使用 utf8mb4 支持完整 Unicode */
2729
+ charset: external_exports.string().default("utf8mb4")
2730
+ }).strict();
2731
+ var NetworkConfigSchema = external_exports.object({
2732
+ /**
2733
+ * 连接字符串(可选)
2734
+ * 如果提供 url,将优先使用,忽略 host/port 等字段
2735
+ */
2736
+ url: external_exports.string().optional(),
2737
+ /** 数据库主机地址(默认 localhost) */
2738
+ host: external_exports.string().default("localhost"),
2739
+ /**
2740
+ * 数据库端口
2741
+ * - PostgreSQL 默认 5432
2742
+ * - MySQL 默认 3306
2743
+ */
2744
+ port: external_exports.number().int().min(1).max(65535).optional(),
2745
+ /** 数据库名称 */
2746
+ database: external_exports.string(),
2747
+ /** 数据库用户名 */
2748
+ user: external_exports.string().optional(),
2749
+ /** 数据库密码 */
2750
+ password: external_exports.string().optional(),
2751
+ /** SSL/TLS 连接配置 */
2752
+ ssl: SslConfigSchema.optional(),
2753
+ /** 连接池配置 */
2754
+ pool: PoolConfigSchema.optional()
2755
+ });
2756
+ var SqliteConfigSchema = external_exports.object({
2757
+ type: external_exports.literal("sqlite"),
2758
+ /** 数据库文件路径(如 './data.db')或 ':memory:'(内存数据库) */
2759
+ database: external_exports.string(),
2760
+ /** SQLite 特定选项 */
2761
+ sqlite: SqliteOptionsSchema.optional(),
2762
+ ...OperationLogConfigFieldSchema
2763
+ });
2764
+ var PostgresqlConfigSchema = NetworkConfigSchema.extend({
2765
+ type: external_exports.literal("postgresql"),
2766
+ ...OperationLogConfigFieldSchema
2767
+ });
2768
+ var MysqlConfigSchema = NetworkConfigSchema.extend({
2769
+ type: external_exports.literal("mysql"),
2770
+ /** 网络数据库URL;MySQL空串表示使用分字段,非空仅mysql://且拒绝query/fragment,优先于host/port/database/user/password */
2771
+ url: external_exports.union([external_exports.literal("").transform(() => void 0), external_exports.url({ protocol: /^mysql$/ }).refine((value) => !value.includes("?") && !value.includes("#"))]).optional(),
2772
+ /** 数据库:SQLite文件路径或:memory:;PostgreSQL必填名称;MySQL无URL时必填,URL模式从URL读取 */
2773
+ database: external_exports.string().min(1).optional(),
2774
+ /** TLS:PostgreSQL使用其模式;MySQL仅false或严格对象,空对象默认校验证书链和DNS身份 */
2775
+ ssl: MysqlSslConfigSchema.optional(),
2776
+ /** 连接池配置:PostgreSQL支持min/idleTimeout;MySQL仅max/acquireTimeout/queueLimit,拒绝其他字段 */
2777
+ pool: MysqlPoolConfigSchema.optional(),
2778
+ /** MySQL 特定选项 */
2779
+ mysql: MysqlOptionsSchema.optional(),
2780
+ ...OperationLogConfigFieldSchema
2781
+ }).refine((config) => Boolean(config.url) || Boolean(config.database), { path: ["database"] });
2782
+ var ReldbConfigSchema = external_exports.discriminatedUnion("type", [
2783
+ SqliteConfigSchema,
2784
+ PostgresqlConfigSchema,
2785
+ MysqlConfigSchema
2786
+ ]);
2481
2787
  var CLOUD_FIELDS = {
2482
2788
  "neon": ["apiKey"],
2483
2789
  "upstash": ["email", "apiKey"],
@@ -2523,7 +2829,7 @@ async function ask(question) {
2523
2829
  throw new Error(cliM("cli_deploy_cancelled"));
2524
2830
  return answer.value;
2525
2831
  }
2526
- function validateField(value, field) {
2832
+ function validateField(value, field, backend) {
2527
2833
  if (value === "")
2528
2834
  return field.required ? cliM("cli_deploy_required") : true;
2529
2835
  if (field.kind === "port")
@@ -2541,8 +2847,8 @@ function validateField(value, field) {
2541
2847
  }
2542
2848
  if (field.name === "ssl") {
2543
2849
  const parsed2 = parseDocument(value);
2544
- const ssl = parsed2.errors.length ? void 0 : parsed2.toJS();
2545
- return typeof ssl === "boolean" || ["require", "prefer", "allow", "disable"].includes(String(ssl)) || ssl !== null && typeof ssl === "object" && !Array.isArray(ssl) || cliM("cli_deploy_invalid_value");
2850
+ const schema = backend === "mysql" ? MysqlSslConfigSchema : SslConfigSchema;
2851
+ return parsed2.errors.length === 0 && schema.safeParse(parsed2.toJS()).success || cliM("cli_deploy_invalid_value");
2546
2852
  }
2547
2853
  if (field.name === "headers") {
2548
2854
  try {
@@ -2561,8 +2867,8 @@ function ensureConfigNode(appDir, requirement, backend) {
2561
2867
  return;
2562
2868
  const directory = join(appDir, "config");
2563
2869
  mkdirSync(directory, { recursive: true });
2564
- const path7 = join(directory, `_${requirement.configName}.yml`);
2565
- const document = parseDocument(existsSync(path7) ? readFileSync(path7, "utf8") : "{}\n");
2870
+ const path9 = join(directory, `_${requirement.configName}.yml`);
2871
+ const document = parseDocument(existsSync(path9) ? readFileSync(path9, "utf8") : "{}\n");
2566
2872
  if (document.errors.length)
2567
2873
  throw new Error(cliM("cli_deploy_invalid_value"));
2568
2874
  if (requirement.configPath.length && !document.hasIn(requirement.configPath))
@@ -2574,8 +2880,8 @@ function ensureConfigNode(appDir, requirement, backend) {
2574
2880
  document.setIn(["login", "ldap"], `\${HAI_IAM_LOGIN_LDAP:false}`);
2575
2881
  }
2576
2882
  const content = document.toString();
2577
- if (!existsSync(path7) || readFileSync(path7, "utf8") !== content)
2578
- writeFileSync(path7, content);
2883
+ if (!existsSync(path9) || readFileSync(path9, "utf8") !== content)
2884
+ writeFileSync(path9, content);
2579
2885
  }
2580
2886
  function connectionEnv(requirement, backend, config) {
2581
2887
  if (requirement.service === "ldap")
@@ -2658,7 +2964,7 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
2658
2964
  }
2659
2965
  services[requirement.service] = cloud;
2660
2966
  } else {
2661
- if (mode === "default" && (requirement.service === "sms" || backendName === "api"))
2967
+ if (mode === "default" && ["aliyun-sms", "api"].includes(backendName))
2662
2968
  throw new Error(cliM("cli_deploy_cloud_unsupported", { params: { backend: backend.label } }));
2663
2969
  const connection = mode === "custom" && backendName === current.backend ? { ...requirement.config } : requirement.configName === "reach" ? { name: requirement.config.name ?? requirement.service } : {};
2664
2970
  for (const field of backend.fields) {
@@ -2666,22 +2972,27 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
2666
2972
  const initial = previous2 === void 0 ? field.defaultValue ?? "" : typeof previous2 === "object" ? JSON.stringify(previous2) : String(previous2);
2667
2973
  const variable = `${requirement.envPrefix}_${field.name.toUpperCase()}`;
2668
2974
  const info = moduleEnvironmentVariables(requirement.configName).find((item) => item.name === variable);
2669
- const value = mode === "custom" && !yes ? String(await ask({ type: field.secret ? "password" : "text", name: "value", message: cliM("cli_deploy_field", { params: { name: variable, description: info?.description ?? field.name, constraint: info?.constraint ?? field.kind ?? "string" } }), initial, validate: (input) => validateField(String(input), field) })) : initial;
2975
+ const value = mode === "custom" && !yes ? String(await ask({ type: field.secret ? "password" : "text", name: "value", message: cliM("cli_deploy_field", { params: { description: info?.description ?? field.name, constraint: info?.constraint ?? field.kind ?? "string" } }), initial, validate: (input) => validateField(String(input), field, backendName) })) : initial;
2976
+ if (field.name === "ssl" && validateField(value, field, backendName) !== true)
2977
+ throw new Error(cliM("cli_deploy_invalid_value"));
2670
2978
  if (value !== "") {
2671
2979
  connection[field.name] = field.kind === "number" || field.kind === "port" ? Number(value) : field.kind === "boolean" ? value === "true" : field.name === "headers" || field.name === "ssl" ? parseDocument(value).toJS() : value;
2672
2980
  } else {
2673
2981
  delete connection[field.name];
2674
2982
  }
2675
2983
  }
2984
+ if (requirement.service === "db") {
2985
+ const validated = ReldbConfigSchema.safeParse({ ...connection, type: backendName });
2986
+ if (!validated.success)
2987
+ throw new Error(cliM("cli_deploy_config_invalid", { params: { path: join(scan.appDir ?? ".", "config", "_db.yml"), fields: validated.error.issues.map((issue) => issue.path.join(".")).join(", ") } }));
2988
+ }
2676
2989
  Object.assign(updates, connectionEnv(requirement, backendName, connection));
2677
2990
  }
2678
2991
  ensureConfigNode(scan.appDir ?? ".", requirement, backendName);
2679
2992
  const saved = module.saveDeploymentEnvironment(environment, updates);
2680
2993
  if (!saved.success)
2681
2994
  throw new Error(saved.error.message);
2682
- for (const [name, value] of Object.entries(updates)) {
2683
- writeCliMessage(cliM("cli_deploy_saved", { params: { name, value: displayDeployValue(name, value) } }));
2684
- }
2995
+ writeCliMessage(cliM("cli_deploy_saved", { params: { service: requirement.service } }));
2685
2996
  }
2686
2997
  config.services = Object.keys(services).length ? services : void 0;
2687
2998
  return modes;
@@ -2697,14 +3008,19 @@ async function configureDeployTarget(module, config, environment, nonInteractive
2697
3008
  if (existing.type && existing.type !== "docker-ssh")
2698
3009
  throw new Error(cliM("cli_deploy_bundle_provider"));
2699
3010
  config.provider = { type: "docker-ssh", remote: { runtime: "auto" }, expose: { hostPort: 3e3 }, ...existing };
3011
+ const candidate = module.ProviderConfigSchema.safeParse(config.provider);
3012
+ if (!candidate.success && candidate.error.issues.some((issue) => issue.path[0] === "ssh")) {
3013
+ const provider2 = configObject(config.provider);
3014
+ delete provider2.ssh;
3015
+ }
2700
3016
  }
2701
3017
  const current = module.ProviderConfigSchema.safeParse(config.provider);
2702
3018
  if (current.success && (bundleOnly || current.data.type !== "docker-ssh" || current.data.ssh)) {
2703
3019
  config.provider = current.data;
2704
- writeCliMessage(cliM("cli_deploy_target_reused", { params: { target: current.data.type === "docker-ssh" ? current.data.ssh ? `${current.data.ssh.username}@${current.data.ssh.host}:${current.data.ssh.port}` : cliM("cli_deploy_bundle_local") : "Vercel" } }));
3020
+ writeCliMessage(bundleOnly ? cliM("cli_deploy_bundle_target") : cliM("cli_deploy_target_reused", { params: { target: current.data.type === "docker-ssh" && current.data.ssh ? `${current.data.ssh.username}@${current.data.ssh.host}:${current.data.ssh.port}` : "Vercel" } }));
2705
3021
  return;
2706
3022
  }
2707
- if (nonInteractive)
3023
+ if (nonInteractive || bundleOnly)
2708
3024
  throw new Error(cliM("cli_deploy_target_missing", { params: { fields: current.success ? "provider.ssh" : current.error.issues.map((issue) => `provider.${issue.path.join(".")}`).join(", ") } }));
2709
3025
  const original = configObject(config.provider);
2710
3026
  const platform = await prompts({
@@ -2810,7 +3126,7 @@ async function deployCommand(options) {
2810
3126
  localModule = localRequire.resolve("@h-ai/deploy");
2811
3127
  } catch {
2812
3128
  }
2813
- deployModule = localModule ? await import(pathToFileURL(localModule).href) : await import('./src-2V6VP4I5.js');
3129
+ deployModule = localModule ? await import(pathToFileURL(localModule).href) : await import('./src-DSJJXM2F.js');
2814
3130
  } catch {
2815
3131
  writeCliError(chalk.red(cliM("cli_deploy_module_missing")));
2816
3132
  writeCliMessage(chalk.cyan(" pnpm add -D @h-ai/deploy"));
@@ -2948,38 +3264,29 @@ async function deployCommand(options) {
2948
3264
  serviceModes
2949
3265
  });
2950
3266
  if (!deployResult.success) {
3267
+ const execution = deployModule.getDeployCommandState(deployResult.error);
3268
+ if (execution) {
3269
+ const localKey = execution.localTermination === "not-started" ? "cli_deploy_local_not_started" : execution.localTermination === "confirmed" ? "cli_deploy_local_confirmed" : "cli_deploy_local_unknown";
3270
+ writeCliMessage(cliM(localKey));
3271
+ if (execution.remoteExecution)
3272
+ writeCliMessage(cliM(execution.remoteExecution === "finished" ? "cli_deploy_remote_finished" : "cli_deploy_remote_unknown"));
3273
+ if (execution.deploymentId && execution.resultFile)
3274
+ writeCliMessage(cliM("cli_deploy_result_check", { params: { id: execution.deploymentId, path: execution.resultFile } }));
3275
+ }
2951
3276
  if (deployResult.error.ext?.recovery) {
2952
3277
  writeCliMessage(deployResult.error.suggestion ?? cliM("cli_deploy_command_failed"));
2953
3278
  }
2954
3279
  throw new Error(cliM("cli_deploy_failed", { params: { message: deployResult.error.message } }));
2955
3280
  }
2956
3281
  spinner.succeed(chalk.green(cliM(deployResult.data.status === "packaged" ? "cli_deploy_bundle_ready" : "cli_deploy_success")));
2957
- for (const key of Object.keys(process4.env)) {
2958
- if (originalEnv[key] === void 0)
2959
- delete process4.env[key];
2960
- }
2961
- Object.assign(process4.env, originalEnv);
2962
- const completedEnvironment = deployModule.loadDeploymentEnvironment({ environment: environment.data.environment, envDir: environment.data.envDir });
2963
- if (!completedEnvironment.success)
2964
- throw new Error(completedEnvironment.error.message);
2965
- for (const requirement of scan.middlewares ?? []) {
2966
- if ((serviceModes[requirement.envPrefix] ?? serviceModes[requirement.service]) === "keep")
2967
- continue;
2968
- const value = completedEnvironment.data.values[requirement.envPrefix];
2969
- if (value !== void 0)
2970
- writeCliMessage(cliM("cli_deploy_saved", { params: { name: requirement.envPrefix, value: displayDeployValue(requirement.envPrefix, value) } }));
2971
- }
2972
3282
  writeCliMessage("");
2973
- if (deployResult.data.bundlePath)
3283
+ if (deployResult.data.bundlePath && (deployResult.data.status !== "packaged" || !deployResult.data.instructions))
2974
3284
  writeCliMessage(cliM("cli_deploy_bundle_path", { params: { path: deployResult.data.bundlePath } }));
2975
3285
  if (deployResult.data.status === "packaged")
2976
3286
  writeCliMessage(deployResult.data.instructions ?? "");
2977
3287
  else
2978
3288
  writeCliMessage(chalk.cyan(cliM("cli_deploy_url", { params: { url: deployResult.data.url } })));
2979
3289
  writeCliMessage(chalk.gray(cliM("cli_deploy_id", { params: { id: deployResult.data.deploymentId } })));
2980
- if (deployResult.data.envVarsSet.length > 0) {
2981
- writeCliMessage(chalk.gray(cliM("cli_deploy_env", { params: { names: deployResult.data.envVarsSet.join(", ") } })));
2982
- }
2983
3290
  writeCliMessage("");
2984
3291
  } catch (error) {
2985
3292
  spinner.fail(chalk.red(cliM("cli_deploy_command_failed")));
@@ -3003,29 +3310,213 @@ async function deployCommand(options) {
3003
3310
  restoreProjectLogger?.();
3004
3311
  }
3005
3312
  }
3313
+ function appendAt(source, marker, entry) {
3314
+ if (source.split(marker).length !== 2)
3315
+ throw new Error(cliM("cli_generateUnsupportedAggregator"));
3316
+ if (source.split(/\r?\n/).some((line) => line.trim() === entry.trim()))
3317
+ return source;
3318
+ const moduleName = /^import .+ from ['"](.+)['"]$/.exec(entry)?.[1];
3319
+ if (moduleName) {
3320
+ for (const match of source.matchAll(/^import (?!type\b)[^\r\n]+ from ['"](\.[^'"]+)['"]\r?$/gm)) {
3321
+ if (match[1].localeCompare(moduleName) > 0)
3322
+ return `${source.slice(0, match.index)}${entry}
3323
+ ${source.slice(match.index)}`;
3324
+ }
3325
+ }
3326
+ return source.replace(marker, `${entry}
3327
+ ${marker}`);
3328
+ }
3329
+ async function generateServApi(context, target) {
3330
+ const contracts = target.packages.filter((pkg) => pkg.dependencies["@h-ai/api-contract"] && pkg.dependencies.zod && pkg.name in target.target.dependencies);
3331
+ if (contracts.length !== 1)
3332
+ throw new Error(cliM("cli_generateUnsupportedAggregator"));
3333
+ const contract = contracts[0];
3334
+ const contractSources = await fse.readdir(path2.join(contract.directory, "src"));
3335
+ const aggregators = [];
3336
+ for (const name of contractSources.filter((name2) => name2.endsWith(".ts"))) {
3337
+ const file = path2.join(contract.directory, "src", name);
3338
+ const content = await fse.readFile(file, "utf8");
3339
+ if (content.includes("// @hai-generator:contracts:entries"))
3340
+ aggregators.push({ path: file, content });
3341
+ }
3342
+ if (aggregators.length !== 1)
3343
+ throw new Error(cliM("cli_generateUnsupportedAggregator"));
3344
+ const aggregator = aggregators[0];
3345
+ const contractIndex = path2.join(contract.directory, "src/index.ts");
3346
+ const procedureIndex = path2.join(target.target.directory, "src/server/procedures/index.ts");
3347
+ const indexSource = await fse.readFile(contractIndex, "utf8");
3348
+ const procedureSource = await fse.readFile(procedureIndex, "utf8");
3349
+ const contractName = `${context.camelCase}Contract`;
3350
+ const factoryName = `create${context.pascalCase}Procedures`;
3351
+ const domainMarker = `// @hai-generator:domain ${context.camelCase}`;
3352
+ const contractFile = path2.join(contract.directory, "src", `${context.kebabCase}-contract.ts`);
3353
+ const procedureFile = path2.join(target.target.directory, "src/server/procedures", `${context.kebabCase}-procedures.ts`);
3354
+ const importLine = `import { ${contractName} } from './${context.kebabCase}-contract.js'`;
3355
+ const entryLine = ` ${context.camelCase}: ${contractName},`;
3356
+ const factoryImport = `import { ${factoryName} } from './${context.kebabCase}-procedures.js'`;
3357
+ const domainProperty = new RegExp(`^\\s*(?:${context.camelCase}|['"]${context.camelCase}['"]):`, "m");
3358
+ if (domainProperty.test(aggregator.content) || domainProperty.test(procedureSource)) {
3359
+ for (const file of [contractFile, procedureFile]) {
3360
+ if (!await fse.pathExists(file) || !(await fse.readFile(file, "utf8")).includes(domainMarker))
3361
+ throw new Error(cliM("cli_generateDomainExists", { params: { name: context.camelCase } }));
3362
+ }
3363
+ }
3364
+ const contractContent = `${domainMarker}
3365
+ import { apiContract } from '@h-ai/api-contract'
3366
+ import { z } from 'zod'
3367
+
3368
+ /**
3369
+ * ${context.pascalCase} \u793A\u4F8B\u5951\u7EA6\uFF1AGET \u5217\u8868\u4E0E POST \u6821\u9A8C\u56DE\u663E\uFF0C\u4E0D\u6267\u884C\u6301\u4E45\u5199\u5165\u3002
3370
+ * @example client.${context.camelCase}.create({ name: 'Example' })
3371
+ */
3372
+ export const ${contractName} = {
3373
+ list: apiContract
3374
+ .route({ method: 'GET', path: '/${context.kebabCase}', operationId: '${context.camelCase}.list', tags: ['${context.kebabCase}'] })
3375
+ .output(apiContract.haiResultSchema(z.object({ items: z.array(z.object({ name: z.string() })) }))),
3376
+ create: apiContract
3377
+ .route({ method: 'POST', path: '/${context.kebabCase}', operationId: '${context.camelCase}.create', tags: ['${context.kebabCase}'] })
3378
+ .input(z.object({ name: z.string().min(1) }))
3379
+ .output(apiContract.haiResultSchema(z.object({ name: z.string() }))),
3380
+ }
3381
+ `;
3382
+ const procedureContent = `${domainMarker}
3383
+ import type { ServContext } from '@h-ai/serv'
3384
+ import { ok } from '@h-ai/core'
3385
+ import { serv } from '@h-ai/serv'
3386
+ import { ${contractName} } from '${contract.name}'
3387
+
3388
+ /**
3389
+ * \u521B\u5EFA ${context.pascalCase} \u793A\u4F8B\u8FC7\u7A0B\uFF1B\u65B0\u589E\u6301\u4E45\u5199\u5165\u524D\u63A5\u5165\u6388\u6743\u4E0E\u4E1A\u52A1\u670D\u52A1\u3002
3390
+ * @returns \u4E0E\u5171\u4EAB\u5951\u7EA6\u4E00\u81F4\u7684\u516C\u5F00\u8FC7\u7A0B\uFF1B\u793A\u4F8B\u4E0D\u8BBF\u95EE\u7528\u6237\u6570\u636E\u3002
3391
+ * @example const procedures = { ${context.camelCase}: ${factoryName}() }
3392
+ */
3393
+ export function ${factoryName}() {
3394
+ return serv
3395
+ .implement(${contractName})
3396
+ .context<ServContext>()
3397
+ .route('list', () => ok({ items: [] }))
3398
+ .route('create', ({ input }) => ok({ name: input.name }))
3399
+ .build()
3400
+ }
3401
+ `;
3402
+ return [
3403
+ { path: contractFile, content: contractContent },
3404
+ { path: procedureFile, content: procedureContent },
3405
+ { path: aggregator.path, previousContent: aggregator.content, content: appendAt(appendAt(aggregator.content, "// @hai-generator:contracts:imports", importLine), " // @hai-generator:contracts:entries", entryLine) },
3406
+ { path: contractIndex, previousContent: indexSource, content: appendAt(indexSource, "// @hai-generator:contracts:exports", `export * from './${context.kebabCase}-contract.js'`) },
3407
+ { path: procedureIndex, previousContent: procedureSource, content: appendAt(appendAt(procedureSource, "// @hai-generator:procedures:imports", factoryImport), " // @hai-generator:procedures:entries", ` ${context.camelCase}: ${factoryName}(),`) }
3408
+ ];
3409
+ }
3410
+ function isInside(root, file) {
3411
+ const relative2 = path2.relative(root, file);
3412
+ return relative2 !== ".." && !relative2.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative2);
3413
+ }
3414
+ async function readPackage(directory) {
3415
+ if (!await fse.pathExists(path2.join(directory, "package.json")))
3416
+ return void 0;
3417
+ const value = await fse.readJson(path2.join(directory, "package.json"));
3418
+ if (!value || typeof value !== "object" || Array.isArray(value))
3419
+ throw new Error(cliM("cli_generateUnsupportedProject"));
3420
+ const dependencies = {};
3421
+ for (const key of ["dependencies", "devDependencies"]) {
3422
+ const entries = key in value ? Reflect.get(value, key) : void 0;
3423
+ if (entries && typeof entries === "object" && !Array.isArray(entries)) {
3424
+ for (const [name2, version] of Object.entries(entries)) {
3425
+ if (typeof version === "string")
3426
+ dependencies[name2] = version;
3427
+ }
3428
+ }
3429
+ }
3430
+ const name = "name" in value && typeof value.name === "string" ? value.name : path2.basename(directory);
3431
+ if (!/^(?:@[\w.-]+\/)?[\w.-]+$/.test(name))
3432
+ throw new Error(cliM("cli_generateUnsupportedProject"));
3433
+ return { directory, name, dependencies };
3434
+ }
3435
+ function generateArchitecture(pkg) {
3436
+ if (pkg.dependencies["@sveltejs/kit"])
3437
+ return "sveltekit";
3438
+ if (pkg.dependencies["@h-ai/serv"])
3439
+ return "serv";
3440
+ return pkg.dependencies.svelte ? "svelte" : "other";
3441
+ }
3442
+ async function resolveGenerateTarget(options) {
3443
+ const cwd = await fse.realpath(path2.resolve(options.cwd ?? "."));
3444
+ let root = cwd;
3445
+ for (let current = cwd; ; current = path2.dirname(current)) {
3446
+ if (await fse.pathExists(path2.join(current, "pnpm-workspace.yaml"))) {
3447
+ root = current;
3448
+ break;
3449
+ }
3450
+ if (path2.dirname(current) === current)
3451
+ break;
3452
+ }
3453
+ const packages = [];
3454
+ const addPackage = async (directory) => {
3455
+ if (!isInside(root, directory))
3456
+ throw new Error(cliM("cli_generateOutsideWorkspace"));
3457
+ const real = await fse.realpath(directory);
3458
+ if (!isInside(root, real))
3459
+ throw new Error(cliM("cli_generateOutsideWorkspace"));
3460
+ const pkg = await readPackage(real);
3461
+ if (pkg && !packages.some((value) => value.directory === real))
3462
+ packages.push(pkg);
3463
+ };
3464
+ await addPackage(root);
3465
+ if (await fse.pathExists(path2.join(root, "pnpm-workspace.yaml"))) {
3466
+ const workspace = parse(await fse.readFile(path2.join(root, "pnpm-workspace.yaml"), "utf8"));
3467
+ const patterns = workspace && typeof workspace === "object" && "packages" in workspace && Array.isArray(workspace.packages) ? workspace.packages : [];
3468
+ for (const pattern of patterns) {
3469
+ if (typeof pattern !== "string" || pattern.startsWith("!") || pattern.split(/[\\/]/).includes("..") || path2.isAbsolute(pattern))
3470
+ throw new Error(cliM("cli_generateUnsupportedProject"));
3471
+ for await (const manifest of glob(`${pattern}/package.json`, { cwd: root, exclude: ["**/node_modules/**"] }))
3472
+ await addPackage(path2.dirname(path2.resolve(root, manifest)));
3473
+ }
3474
+ }
3475
+ if (cwd !== root)
3476
+ await addPackage(cwd);
3477
+ const selected = options.workspace ? packages.filter((pkg) => pkg.name === options.workspace || pkg.directory === path2.resolve(root, options.workspace)) : packages.filter((pkg) => pkg.directory === cwd);
3478
+ const applicable = (pkg) => {
3479
+ const architecture = generateArchitecture(pkg);
3480
+ return options.type === "api" ? architecture === "serv" || architecture === "sveltekit" && Boolean(pkg.dependencies["@h-ai/kit"]) && Boolean(pkg.dependencies.zod) : options.type === "page" ? architecture === "sveltekit" : options.type === "component" ? ["sveltekit", "svelte"].includes(architecture) : Object.keys(pkg.dependencies).some((name) => name.startsWith("@h-ai/"));
3481
+ };
3482
+ const candidates = options.workspace || selected.length === 1 && (cwd !== root || applicable(selected[0])) ? selected.filter(applicable) : packages.filter(applicable);
3483
+ if (candidates.length !== 1)
3484
+ throw new Error(cliM(candidates.length > 1 ? "cli_generateAmbiguousWorkspace" : "cli_generateUnsupportedProject"));
3485
+ const target = candidates[0];
3486
+ if (generateArchitecture(target) === "sveltekit" && (options.type === "page" || options.type === "api")) {
3487
+ for (const name of ["svelte.config.js", "svelte.config.mjs", "svelte.config.cjs", "svelte.config.ts"]) {
3488
+ const configFile = path2.join(target.directory, name);
3489
+ if (await fse.pathExists(configFile) && /\bfiles\s*[:.[(]/.test(await fse.readFile(configFile, "utf8")))
3490
+ throw new Error(cliM("cli_generateCustomRoutes"));
3491
+ }
3492
+ }
3493
+ return { root, target, packages };
3494
+ }
3495
+
3496
+ // src/commands/cli-generate.ts
3006
3497
  var GENERATORS = {
3007
3498
  page: {
3008
- description: "SvelteKit \u9875\u9762",
3499
+ description: () => cliM("cli_generatePageDescription"),
3009
3500
  defaultPath: "src/routes",
3010
3501
  generate: generatePage
3011
3502
  },
3012
3503
  component: {
3013
- description: "Svelte \u7EC4\u4EF6",
3504
+ description: () => cliM("cli_generateComponentDescription"),
3014
3505
  defaultPath: "src/lib/components",
3015
3506
  generate: generateComponent
3016
3507
  },
3017
3508
  api: {
3018
- description: "API \u7AEF\u70B9",
3509
+ description: () => cliM("cli_generateApiDescription"),
3019
3510
  defaultPath: "src/routes/api",
3020
3511
  generate: generateApi
3021
3512
  },
3022
3513
  model: {
3023
- description: "\u6570\u636E\u6A21\u578B",
3514
+ description: () => cliM("cli_generateModelDescription"),
3024
3515
  defaultPath: "src/lib/models",
3025
3516
  generate: generateModel
3026
3517
  },
3027
3518
  migration: {
3028
- description: "\u6570\u636E\u5E93\u8FC1\u79FB",
3519
+ description: () => cliM("cli_generateMigrationDescription"),
3029
3520
  defaultPath: "migrations",
3030
3521
  generate: generateMigration
3031
3522
  }
@@ -3034,55 +3525,43 @@ async function generate(options) {
3034
3525
  const spinner = ora();
3035
3526
  const cwd = options.cwd ?? ".";
3036
3527
  try {
3037
- const project = await detectProject(cwd);
3038
- if (!project?.isHaiProject) {
3039
- writeCliMessage(chalk.yellow("\u8B66\u544A: \u5F53\u524D\u76EE\u5F55\u4E0D\u662F hai \u9879\u76EE"));
3040
- }
3041
3528
  const resolvedOptions = await resolveGenerateOptions(options);
3042
3529
  const generator = GENERATORS[resolvedOptions.type];
3043
- if (!generator) {
3044
- throw new Error(`\u672A\u77E5\u7684\u751F\u6210\u5668\u7C7B\u578B: ${resolvedOptions.type}`);
3045
- }
3046
3530
  if (!/^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/i.test(resolvedOptions.name))
3047
3531
  throw new Error(cliM("cli_invalidGenerateName"));
3048
- const outputDir = path2.resolve(cwd, resolvedOptions.output || generator.defaultPath);
3049
- const resolvedCwd = path2.resolve(cwd);
3050
- if (!outputDir.startsWith(resolvedCwd + path2.sep) && outputDir !== resolvedCwd) {
3051
- throw new Error(cliM("cli_generateOutsideWorkspace"));
3532
+ const target = await resolveGenerateTarget(resolvedOptions);
3533
+ const architecture = generateArchitecture(target.target);
3534
+ const outputDir = path2.resolve(target.target.directory, resolvedOptions.output || generator.defaultPath);
3535
+ if (["page", "api"].includes(resolvedOptions.type) && architecture === "sveltekit") {
3536
+ const routes = path2.join(target.target.directory, "src/routes");
3537
+ if (!outputDir.startsWith(routes + path2.sep) && outputDir !== routes)
3538
+ throw new Error(cliM("cli_generateInvalidRouteOutput"));
3052
3539
  }
3540
+ if (resolvedOptions.type === "api" && architecture === "serv" && resolvedOptions.output)
3541
+ throw new Error(cliM("cli_generateInvalidRouteOutput"));
3542
+ const relativeOutput = path2.relative(target.root, outputDir);
3543
+ if (relativeOutput === ".." || relativeOutput.startsWith(`..${path2.sep}`) || path2.isAbsolute(relativeOutput))
3544
+ throw new Error(cliM("cli_generateOutsideWorkspace"));
3053
3545
  const context = createTemplateContext(resolvedOptions.name, {
3054
- projectName: project?.name
3546
+ projectName: target.target.name
3055
3547
  });
3056
- spinner.start(`\u751F\u6210 ${generator.description}...`);
3057
- const files = await generator.generate(context, outputDir);
3058
- const realCwd = await fse.realpath(resolvedCwd);
3059
- for (const file of files) {
3060
- if (!resolvedOptions.force && await fse.pathExists(file.path))
3061
- throw new Error(cliM("cli_generateExists", { params: { path: file.path } }));
3062
- let ancestor = file.path;
3063
- while (!await fse.pathExists(ancestor))
3064
- ancestor = path2.dirname(ancestor);
3065
- const realAncestor = await fse.realpath(ancestor);
3066
- const relative2 = path2.relative(realCwd, realAncestor);
3067
- if (relative2 === ".." || relative2.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative2))
3068
- throw new Error(cliM("cli_generateOutsideWorkspace"));
3069
- }
3070
- for (const file of files) {
3071
- await fse.ensureDir(path2.dirname(file.path));
3072
- await fse.writeFile(file.path, file.content, { encoding: "utf-8", flag: resolvedOptions.force ? "w" : "wx" });
3073
- }
3074
- spinner.succeed();
3548
+ spinner.start(cliM("cli_generateProgress", { params: { description: generator.description() } }));
3549
+ const files = resolvedOptions.type === "api" && architecture === "serv" ? await generateServApi(context, target) : await generator.generate(context, outputDir);
3550
+ await writeGeneratedFiles(files, target.root, resolvedOptions.force);
3551
+ spinner.succeed(cliM("cli_generateComplete"));
3075
3552
  writeCliMessage("");
3076
- writeCliMessage(chalk.green("\u2714 \u751F\u6210\u5B8C\u6210\uFF01"));
3077
- writeCliMessage("");
3078
- writeCliMessage("\u521B\u5EFA\u7684\u6587\u4EF6:");
3553
+ writeCliMessage(cliM("cli_generateFiles"));
3079
3554
  files.forEach((file) => {
3080
3555
  writeCliMessage(chalk.cyan(` ${path2.relative(cwd, file.path)}`));
3081
3556
  });
3082
3557
  writeCliMessage("");
3083
3558
  } catch (error) {
3084
- spinner.fail();
3085
- writeCliError(chalk.red("\u751F\u6210\u5931\u8D25:"), { error });
3559
+ spinner.fail(cliM("cli_generateFailed"));
3560
+ writeCliError(cliM("cli_generateFailureDetail"), { error });
3561
+ if (error instanceof AggregateError) {
3562
+ for (const cause of error.errors)
3563
+ writeCliError(cliM("cli_generateFailureDetail"), { error: cause });
3564
+ }
3086
3565
  throw error;
3087
3566
  }
3088
3567
  }
@@ -3092,9 +3571,9 @@ async function resolveGenerateOptions(options) {
3092
3571
  questions.push({
3093
3572
  type: "select",
3094
3573
  name: "type",
3095
- message: "\u9009\u62E9\u751F\u6210\u7C7B\u578B:",
3574
+ message: cliM("cli_generateTypePrompt"),
3096
3575
  choices: Object.entries(GENERATORS).map(([key, value]) => ({
3097
- title: `${key} - ${value.description}`,
3576
+ title: `${key} - ${value.description()}`,
3098
3577
  value: key
3099
3578
  }))
3100
3579
  });
@@ -3103,24 +3582,102 @@ async function resolveGenerateOptions(options) {
3103
3582
  questions.push({
3104
3583
  type: "text",
3105
3584
  name: "name",
3106
- message: "\u540D\u79F0:",
3585
+ message: cliM("cli_generateNamePrompt"),
3107
3586
  validate: (value) => {
3108
3587
  if (!value.trim())
3109
- return "\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A";
3588
+ return cliM("cli_generateNameRequired");
3110
3589
  return true;
3111
3590
  }
3112
3591
  });
3113
3592
  }
3114
- const answers = questions.length > 0 ? await prompts(questions) : {};
3593
+ const answers = questions.length > 0 ? await prompts(questions, { onCancel: () => {
3594
+ throw new Error(cliM("cli_generateCancelled"));
3595
+ } }) : {};
3596
+ const type = options.type || ("type" in answers ? answers.type : void 0);
3597
+ const name = options.name || ("name" in answers ? answers.name : void 0);
3598
+ if (!isGeneratorType(type))
3599
+ throw new Error(cliM("cli_generateUnknownType", { params: { type: String(type ?? "") } }));
3600
+ if (typeof name !== "string" || !name.trim())
3601
+ throw new Error(cliM("cli_generateNameRequired"));
3115
3602
  return {
3116
- type: options.type || answers.type,
3117
- name: options.name || answers.name,
3603
+ type,
3604
+ name,
3118
3605
  output: options.output ?? "",
3119
3606
  force: options.force ?? false,
3120
3607
  verbose: options.verbose ?? false,
3121
- cwd: options.cwd ?? "."
3608
+ cwd: options.cwd ?? ".",
3609
+ workspace: options.workspace ?? ""
3122
3610
  };
3123
3611
  }
3612
+ function isGeneratorType(value) {
3613
+ return typeof value === "string" && Object.hasOwn(GENERATORS, value);
3614
+ }
3615
+ async function writeGeneratedFiles(files, root, force) {
3616
+ if (new Set(files.map((file) => path2.resolve(file.path))).size !== files.length)
3617
+ throw new Error(cliM("cli_generateUnsupportedAggregator"));
3618
+ const previous = /* @__PURE__ */ new Map();
3619
+ const stages = /* @__PURE__ */ new Map();
3620
+ const written = [];
3621
+ const failures = [];
3622
+ for (const file of files) {
3623
+ const relativeFile = path2.relative(root, file.path);
3624
+ if (relativeFile === ".." || relativeFile.startsWith(`..${path2.sep}`) || path2.isAbsolute(relativeFile))
3625
+ throw new Error(cliM("cli_generateOutsideWorkspace"));
3626
+ let ancestor = file.path;
3627
+ while (!await fse.pathExists(ancestor))
3628
+ ancestor = path2.dirname(ancestor);
3629
+ const relative2 = path2.relative(root, await fse.realpath(ancestor));
3630
+ if (relative2 === ".." || relative2.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative2))
3631
+ throw new Error(cliM("cli_generateOutsideWorkspace"));
3632
+ const content = await fse.pathExists(file.path) ? await fse.readFile(file.path, "utf8") : void 0;
3633
+ if (file.previousContent !== void 0 ? content !== file.previousContent : content !== void 0 && !force)
3634
+ throw new Error(cliM("cli_generateExists", { params: { path: file.path } }));
3635
+ previous.set(file.path, content);
3636
+ }
3637
+ try {
3638
+ for (const file of files) {
3639
+ await fse.ensureDir(path2.dirname(file.path));
3640
+ const stage = path2.join(path2.dirname(file.path), `.${path2.basename(file.path)}.${randomUUID()}.tmp`);
3641
+ stages.set(file.path, stage);
3642
+ await fse.writeFile(stage, file.content, { encoding: "utf8", flag: "wx" });
3643
+ }
3644
+ for (const file of files) {
3645
+ const current = await fse.pathExists(file.path) ? await fse.readFile(file.path, "utf8") : void 0;
3646
+ if (current !== previous.get(file.path))
3647
+ throw new Error(cliM("cli_generateExists", { params: { path: file.path } }));
3648
+ await fse.rename(stages.get(file.path), file.path);
3649
+ written.push(file);
3650
+ }
3651
+ } catch (error) {
3652
+ failures.push(error);
3653
+ for (const file of written.reverse()) {
3654
+ try {
3655
+ if (await fse.readFile(file.path, "utf8") !== file.content)
3656
+ throw new Error(cliM("cli_generateExists", { params: { path: file.path } }));
3657
+ const original = previous.get(file.path);
3658
+ if (original === void 0)
3659
+ await fse.unlink(file.path);
3660
+ else
3661
+ await fse.writeFile(file.path, original, "utf8");
3662
+ } catch (rollbackError) {
3663
+ failures.push(rollbackError);
3664
+ }
3665
+ }
3666
+ } finally {
3667
+ for (const stage of stages.values()) {
3668
+ try {
3669
+ await fse.unlink(stage);
3670
+ } catch (error) {
3671
+ if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT")
3672
+ failures.push(error);
3673
+ }
3674
+ }
3675
+ }
3676
+ if (failures.length > 1)
3677
+ throw new AggregateError(failures, cliM("cli_generateRollbackFailed"));
3678
+ if (failures.length === 1)
3679
+ throw failures[0];
3680
+ }
3124
3681
  async function generatePage(context, outputDir) {
3125
3682
  const files = [];
3126
3683
  const pageDir = path2.join(outputDir, context.kebabCase);
@@ -3297,19 +3854,45 @@ export function down() {
3297
3854
  return files;
3298
3855
  }
3299
3856
  var MODULE_CONFIG_MAP = {
3300
- "@h-ai/core": { configKey: "core", label: "Core" },
3301
- "@h-ai/serv": { configKey: "serv", label: "Service" },
3302
- "@h-ai/reldb": { configKey: "db", label: "Database" },
3303
- "@h-ai/cache": { configKey: "cache", label: "Cache" },
3304
- "@h-ai/iam": { configKey: "iam", label: "IAM" },
3305
- "@h-ai/storage": { configKey: "storage", label: "Storage" },
3306
- "@h-ai/ai": { configKey: "ai", label: "AI" },
3307
- "@h-ai/vecdb": { configKey: "vecdb", label: "VecDB" },
3308
- "@h-ai/reach": { configKey: "reach", label: "Reach" },
3309
- "@h-ai/scheduler": { configKey: "scheduler", label: "Scheduler" },
3310
- "@h-ai/audit": { configKey: "audit", label: "Audit" },
3311
- "@h-ai/payment": { configKey: "payment", label: "Payment" },
3312
- "@h-ai/deploy": { configKey: "deploy", label: "Deploy" }
3857
+ "@h-ai/core": { configKey: "core", get label() {
3858
+ return cliM("cli_coreLabel");
3859
+ } },
3860
+ "@h-ai/serv": { configKey: "serv", get label() {
3861
+ return cliM("cli_serviceLabel");
3862
+ } },
3863
+ "@h-ai/reldb": { configKey: "db", get label() {
3864
+ return cliM("cli_featureDbName");
3865
+ } },
3866
+ "@h-ai/cache": { configKey: "cache", get label() {
3867
+ return cliM("cli_featureCacheName");
3868
+ } },
3869
+ "@h-ai/iam": { configKey: "iam", get label() {
3870
+ return cliM("cli_featureIamName");
3871
+ } },
3872
+ "@h-ai/storage": { configKey: "storage", get label() {
3873
+ return cliM("cli_featureStorageName");
3874
+ } },
3875
+ "@h-ai/ai": { configKey: "ai", get label() {
3876
+ return cliM("cli_featureAiName");
3877
+ } },
3878
+ "@h-ai/vecdb": { configKey: "vecdb", get label() {
3879
+ return cliM("cli_featureVecdbName");
3880
+ } },
3881
+ "@h-ai/reach": { configKey: "reach", get label() {
3882
+ return cliM("cli_featureReachName");
3883
+ } },
3884
+ "@h-ai/scheduler": { configKey: "scheduler", get label() {
3885
+ return cliM("cli_featureSchedulerName");
3886
+ } },
3887
+ "@h-ai/audit": { configKey: "audit", get label() {
3888
+ return cliM("cli_featureAuditName");
3889
+ } },
3890
+ "@h-ai/payment": { configKey: "payment", get label() {
3891
+ return cliM("cli_featurePaymentName");
3892
+ } },
3893
+ "@h-ai/deploy": { configKey: "deploy", get label() {
3894
+ return cliM("cli_featureDeployName");
3895
+ } }
3313
3896
  };
3314
3897
  async function validateProject(cwd) {
3315
3898
  const project = await detectProject(cwd);
@@ -3335,16 +3918,16 @@ async function initProject(options) {
3335
3918
  const spinner = ora();
3336
3919
  const cwd = options.cwd ?? ".";
3337
3920
  try {
3338
- spinner.start("\u68C0\u6D4B\u9879\u76EE...");
3921
+ spinner.start(cliM("cli_initDetecting"));
3339
3922
  const result = await validateProject(cwd);
3340
3923
  if (!result) {
3341
- spinner.fail(chalk.red("\u672A\u68C0\u6D4B\u5230\u9879\u76EE\uFF08\u627E\u4E0D\u5230 package.json\uFF09"));
3924
+ spinner.fail(chalk.red(cliM("cli_projectNotFound")));
3342
3925
  return;
3343
3926
  }
3344
3927
  const { project, existing, missing } = result;
3345
- spinner.succeed(`\u68C0\u6D4B\u5230\u9879\u76EE: ${chalk.cyan(project.name)}`);
3928
+ spinner.succeed(cliM("cli_initDetected", { params: { name: chalk.cyan(project.name) } }));
3346
3929
  if (project.isHaiProject) {
3347
- const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-FC3QRKDO.js');
3930
+ const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-US5CCAJ4.js');
3348
3931
  const generatedFiles = await generateProjectAiSupport(cwd, resolveSkillNamesForPackages(project.haiPackages), {
3349
3932
  overwriteSkills: false,
3350
3933
  overwriteBridgeFiles: false
@@ -3354,30 +3937,30 @@ async function initProject(options) {
3354
3937
  }
3355
3938
  }
3356
3939
  writeCliMessage("");
3357
- writeCliMessage(chalk.bold("\u9879\u76EE\u72B6\u6001:"));
3358
- writeCliMessage(` \u540D\u79F0: ${chalk.cyan(project.name)}`);
3359
- writeCliMessage(` \u7248\u672C: ${project.version}`);
3360
- writeCliMessage(` \u5DF2\u5B89\u88C5\u6A21\u5757: ${project.haiPackages.length > 0 ? project.haiPackages.map((p) => chalk.cyan(p)).join(", ") : chalk.gray("\u65E0")}`);
3940
+ writeCliMessage(chalk.bold(cliM("cli_initStatus")));
3941
+ writeCliMessage(cliM("cli_initName", { params: { name: chalk.cyan(project.name) } }));
3942
+ writeCliMessage(cliM("cli_initVersion", { params: { version: project.version } }));
3943
+ writeCliMessage(cliM("cli_initModules", { params: { modules: project.haiPackages.length > 0 ? project.haiPackages.map((p) => chalk.cyan(p)).join(", ") : chalk.gray(cliM("cli_none")) } }));
3361
3944
  writeCliMessage("");
3362
3945
  if (existing.length > 0) {
3363
- writeCliMessage(chalk.bold("\u5DF2\u6709\u914D\u7F6E:"));
3946
+ writeCliMessage(chalk.bold(cliM("cli_initExisting")));
3364
3947
  for (const key of existing) {
3365
3948
  writeCliMessage(chalk.green(` \u2714 config/_${key}.yml`));
3366
3949
  }
3367
3950
  }
3368
3951
  if (missing.length === 0 && !options.force) {
3369
3952
  writeCliMessage("");
3370
- writeCliMessage(chalk.green("\u2714 \u6240\u6709\u914D\u7F6E\u6587\u4EF6\u5DF2\u5C31\u7EEA\uFF01"));
3953
+ writeCliMessage(chalk.green(cliM("cli_initReady")));
3371
3954
  return;
3372
3955
  }
3373
3956
  if (missing.length > 0) {
3374
3957
  writeCliMessage("");
3375
- writeCliMessage(chalk.bold("\u7F3A\u5931\u914D\u7F6E:"));
3958
+ writeCliMessage(chalk.bold(cliM("cli_initMissing")));
3376
3959
  for (const item of missing) {
3377
3960
  writeCliMessage(chalk.yellow(` \u2718 config/_${item.configKey}.yml (${item.label})`));
3378
3961
  }
3379
3962
  writeCliMessage("");
3380
- spinner.start("\u751F\u6210\u7F3A\u5931\u914D\u7F6E\u6587\u4EF6...");
3963
+ spinner.start(cliM("cli_initGenerating"));
3381
3964
  let generated = 0;
3382
3965
  for (const item of missing) {
3383
3966
  const configPath = path2.join(cwd, "config", `_${item.configKey}.yml`);
@@ -3385,11 +3968,11 @@ async function initProject(options) {
3385
3968
  await writeFile(configPath, content);
3386
3969
  generated++;
3387
3970
  }
3388
- spinner.succeed(`\u5DF2\u751F\u6210 ${generated} \u4E2A\u914D\u7F6E\u6587\u4EF6`);
3971
+ spinner.succeed(cliM("cli_initGenerated", { params: { count: generated } }));
3389
3972
  }
3390
3973
  if (options.force && existing.length > 0) {
3391
3974
  writeCliMessage("");
3392
- spinner.start("\u5F3A\u5236\u91CD\u65B0\u751F\u6210\u6240\u6709\u914D\u7F6E\u6587\u4EF6...");
3975
+ spinner.start(cliM("cli_initRegenerating"));
3393
3976
  let regenerated = 0;
3394
3977
  for (const pkg of project.haiPackages) {
3395
3978
  const mapping = MODULE_CONFIG_MAP[pkg];
@@ -3400,25 +3983,25 @@ async function initProject(options) {
3400
3983
  await writeFile(configPath, content);
3401
3984
  regenerated++;
3402
3985
  }
3403
- spinner.succeed(`\u5DF2\u91CD\u65B0\u751F\u6210 ${regenerated} \u4E2A\u914D\u7F6E\u6587\u4EF6`);
3986
+ spinner.succeed(cliM("cli_initRegenerated", { params: { count: regenerated } }));
3404
3987
  }
3405
3988
  const coreConfigPath = path2.join(cwd, "config", "_core.yml");
3406
3989
  if (!await fileExists(coreConfigPath)) {
3407
- spinner.start("\u751F\u6210\u6838\u5FC3\u914D\u7F6E config/_core.yml...");
3990
+ spinner.start(cliM("cli_initCore"));
3408
3991
  const content = generateConfigFile("core");
3409
3992
  await writeFile(coreConfigPath, content);
3410
3993
  spinner.succeed();
3411
3994
  }
3412
3995
  writeCliMessage("");
3413
- writeCliMessage(chalk.green("\u2714 \u9879\u76EE\u914D\u7F6E\u521D\u59CB\u5316\u5B8C\u6210\uFF01"));
3996
+ writeCliMessage(chalk.green(cliM("cli_initSuccess")));
3414
3997
  writeCliMessage("");
3415
- writeCliMessage(chalk.gray("\u4E0B\u4E00\u6B65:"));
3416
- writeCliMessage(chalk.gray(" 1. \u7F16\u8F91 config/ \u4E2D\u7684\u914D\u7F6E\u6587\u4EF6"));
3417
- writeCliMessage(chalk.gray(" 2. \u8FD0\u884C pnpm dev \u542F\u52A8\u5F00\u53D1\u670D\u52A1"));
3998
+ writeCliMessage(chalk.gray(cliM("cli_nextSteps")));
3999
+ writeCliMessage(chalk.gray(cliM("cli_initEditConfig")));
4000
+ writeCliMessage(chalk.gray(cliM("cli_initStartDev")));
3418
4001
  writeCliMessage("");
3419
4002
  } catch (error) {
3420
4003
  spinner.fail();
3421
- writeCliError(chalk.red("\u521D\u59CB\u5316\u5931\u8D25:"), { error });
4004
+ writeCliError(chalk.red(cliM("cli_initFailed")), { error });
3422
4005
  throw error;
3423
4006
  }
3424
4007
  }
@@ -3475,12 +4058,13 @@ cli.command("init", cliM("cli_help_init")).option("-f, --force", cliM("cli_help_
3475
4058
  process4.exit(1);
3476
4059
  }
3477
4060
  });
3478
- cli.command("generate [type] [name]", cliM("cli_help_generate")).alias("g").option("-o, --output <path>", cliM("cli_help_output")).option("-f, --force", cliM("cli_help_force")).action(async (type, name, options) => {
4061
+ cli.command("generate [type] [name]", cliM("cli_help_generate")).option("-w, --workspace <name>", cliM("cli_help_workspace")).alias("g").option("-o, --output <path>", cliM("cli_help_output")).option("-f, --force", cliM("cli_help_force")).action(async (type, name, options) => {
3479
4062
  try {
3480
4063
  await generate({
3481
4064
  type,
3482
4065
  name: name ?? "",
3483
4066
  output: options.output,
4067
+ ...options.workspace ? { workspace: String(options.workspace) } : {},
3484
4068
  force: options.force,
3485
4069
  verbose: options.verbose,
3486
4070
  cwd: options.cwd
@@ -3522,17 +4106,17 @@ cli.command("deploy [appDir]", cliM("cli_help_deploy")).option("--env <environme
3522
4106
  process4.exitCode = 1;
3523
4107
  }
3524
4108
  });
3525
- cli.command("g:page <name>", cliM("cli_help_generatePage")).action(async (name, options) => {
3526
- await generate({ type: "page", name, cwd: options.cwd });
4109
+ cli.command("g:page <name>", cliM("cli_help_generatePage")).option("-w, --workspace <name>", cliM("cli_help_workspace")).action(async (name, options) => {
4110
+ await generate({ type: "page", name, cwd: options.cwd, ...options.workspace ? { workspace: String(options.workspace) } : {} });
3527
4111
  });
3528
- cli.command("g:component <name>", cliM("cli_help_generateComponent")).action(async (name, options) => {
3529
- await generate({ type: "component", name, cwd: options.cwd });
4112
+ cli.command("g:component <name>", cliM("cli_help_generateComponent")).option("-w, --workspace <name>", cliM("cli_help_workspace")).action(async (name, options) => {
4113
+ await generate({ type: "component", name, cwd: options.cwd, ...options.workspace ? { workspace: String(options.workspace) } : {} });
3530
4114
  });
3531
- cli.command("g:api <name>", cliM("cli_help_generateApi")).action(async (name, options) => {
3532
- await generate({ type: "api", name, cwd: options.cwd });
4115
+ cli.command("g:api <name>", cliM("cli_help_generateApi")).option("-w, --workspace <name>", cliM("cli_help_workspace")).action(async (name, options) => {
4116
+ await generate({ type: "api", name, cwd: options.cwd, ...options.workspace ? { workspace: String(options.workspace) } : {} });
3533
4117
  });
3534
- cli.command("g:model <name>", cliM("cli_help_generateModel")).action(async (name, options) => {
3535
- await generate({ type: "model", name, cwd: options.cwd });
4118
+ cli.command("g:model <name>", cliM("cli_help_generateModel")).option("-w, --workspace <name>", cliM("cli_help_workspace")).action(async (name, options) => {
4119
+ await generate({ type: "model", name, cwd: options.cwd, ...options.workspace ? { workspace: String(options.workspace) } : {} });
3536
4120
  });
3537
4121
  cli.version(VERSION, "-V, --version");
3538
4122
  cli.help();