@youzan-cloud/zan-saas-cli 0.0.0-stage → 0.1.0-beta.4

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 (329) hide show
  1. package/DEPENDENCIES.json +50 -0
  2. package/INSTALL.md +79 -0
  3. package/LICENSE +21 -0
  4. package/NOTICE +1 -0
  5. package/README.md +151 -2
  6. package/THIRD_PARTY_NOTICES.txt +162 -0
  7. package/bin/zan-saas-cli.js +28 -0
  8. package/internal/registry/index.js +123 -0
  9. package/internal/registry/manifest.json +424 -0
  10. package/npm-shrinkwrap.json +86 -0
  11. package/package.json +33 -4
  12. package/skills/README.md +30 -0
  13. package/skills/zan-saas/SKILL.md +54 -0
  14. package/skills/zan-saas/references/host-entry.md +25 -0
  15. package/skills/zan-saas-access/SKILL.md +37 -0
  16. package/skills/zan-saas-access/references/role-list.md +11 -0
  17. package/skills/zan-saas-access/references/staff-perms-get.md +11 -0
  18. package/skills/zan-saas-aftersale/SKILL.md +31 -0
  19. package/skills/zan-saas-aftersale/references/case-list.md +13 -0
  20. package/skills/zan-saas-aftersale/references/case-scan.md +31 -0
  21. package/skills/zan-saas-benefit/SKILL.md +46 -0
  22. package/skills/zan-saas-benefit/references/card-get.md +7 -0
  23. package/skills/zan-saas-benefit/references/card-grant-list.md +11 -0
  24. package/skills/zan-saas-benefit/references/card-list.md +7 -0
  25. package/skills/zan-saas-benefit/references/customer-card-get.md +7 -0
  26. package/skills/zan-saas-benefit/references/customer-card-list.md +7 -0
  27. package/skills/zan-saas-coupon/SKILL.md +41 -0
  28. package/skills/zan-saas-coupon/references/activity-get.md +11 -0
  29. package/skills/zan-saas-coupon/references/activity-list.md +15 -0
  30. package/skills/zan-saas-coupon/references/voucher-get.md +13 -0
  31. package/skills/zan-saas-coupon/references/voucher-list.md +25 -0
  32. package/skills/zan-saas-customer/SKILL.md +73 -0
  33. package/skills/zan-saas-customer/references/customer-scan.md +11 -0
  34. package/skills/zan-saas-customer/references/segment-list.md +22 -0
  35. package/skills/zan-saas-fulfillment/SKILL.md +36 -0
  36. package/skills/zan-saas-fulfillment/references/delivery-get.md +11 -0
  37. package/skills/zan-saas-fulfillment/references/shipment-list.md +11 -0
  38. package/skills/zan-saas-fulfillment/references/waybill-get.md +9 -0
  39. package/skills/zan-saas-interaction/SKILL.md +37 -0
  40. package/skills/zan-saas-interaction/references/lottery-get.md +11 -0
  41. package/skills/zan-saas-interaction/references/lottery-list.md +19 -0
  42. package/skills/zan-saas-item/SKILL.md +61 -0
  43. package/skills/zan-saas-item/references/attribute-list.md +11 -0
  44. package/skills/zan-saas-item/references/brand-list.md +7 -0
  45. package/skills/zan-saas-item/references/classification-get.md +7 -0
  46. package/skills/zan-saas-item/references/classification-list.md +7 -0
  47. package/skills/zan-saas-item/references/group-list.md +9 -0
  48. package/skills/zan-saas-item/references/item-content.md +9 -0
  49. package/skills/zan-saas-item/references/item-get.md +9 -0
  50. package/skills/zan-saas-item/references/item-search.md +23 -0
  51. package/skills/zan-saas-item/references/product-identity.md +11 -0
  52. package/skills/zan-saas-member/SKILL.md +41 -0
  53. package/skills/zan-saas-member/references/customer-level-get.md +7 -0
  54. package/skills/zan-saas-member/references/growth-log-list.md +17 -0
  55. package/skills/zan-saas-member/references/membership-get.md +7 -0
  56. package/skills/zan-saas-order/SKILL.md +56 -0
  57. package/skills/zan-saas-order/references/order-get.md +26 -0
  58. package/skills/zan-saas-order/references/order-list.md +25 -0
  59. package/skills/zan-saas-order/references/order-scan.md +35 -0
  60. package/skills/zan-saas-org/SKILL.md +37 -0
  61. package/skills/zan-saas-org/references/staff-get.md +15 -0
  62. package/skills/zan-saas-org/references/staff-role-get.md +11 -0
  63. package/skills/zan-saas-org/references/unit-get.md +11 -0
  64. package/skills/zan-saas-org/references/unit-list.md +15 -0
  65. package/skills/zan-saas-points/SKILL.md +42 -0
  66. package/skills/zan-saas-points/references/account-get.md +7 -0
  67. package/skills/zan-saas-points/references/log-list.md +17 -0
  68. package/skills/zan-saas-points/references/rule-list.md +2 -0
  69. package/skills/zan-saas-privacy/SKILL.md +30 -0
  70. package/skills/zan-saas-privacy/references/field-decrypt.md +19 -0
  71. package/skills/zan-saas-shared/SKILL.md +94 -0
  72. package/skills/zan-saas-shared/references/authentication.md +52 -0
  73. package/skills/zan-saas-shared/references/value-processing.md +27 -0
  74. package/skills/zan-saas-stock/SKILL.md +28 -0
  75. package/skills/zan-saas-stock/references/log-list.md +11 -0
  76. package/skills/zan-saas-valuecard/SKILL.md +37 -0
  77. package/skills/zan-saas-valuecard/references/card-get.md +13 -0
  78. package/skills/zan-saas-valuecard/references/card-list.md +11 -0
  79. package/src/auth/browser.js +28 -0
  80. package/src/auth/callback-server.js +99 -0
  81. package/src/auth/config.js +53 -0
  82. package/src/auth/credential-store.js +109 -0
  83. package/src/auth/oauth-client.js +105 -0
  84. package/src/auth/pkce.js +28 -0
  85. package/src/auth/session.js +178 -0
  86. package/src/auth/tool.js +25 -0
  87. package/src/cli/command-paths.js +126 -0
  88. package/src/cli/context-help.js +102 -0
  89. package/src/cli/diagnostic-command.js +57 -0
  90. package/src/cli/domain-catalog.js +42 -0
  91. package/src/cli/help-text.js +32 -0
  92. package/src/cli/native-loader.js +120 -0
  93. package/src/cli/native-path.js +22 -0
  94. package/src/cli/parser-policy.js +141 -0
  95. package/src/cli/program.js +134 -0
  96. package/src/cli/skill-catalog.js +241 -0
  97. package/src/commands/native/access/commands.index.json +24 -0
  98. package/src/commands/native/access/domain.json +3 -0
  99. package/src/commands/native/access/role/list/command.js +141 -0
  100. package/src/commands/native/access/role/list/schema.json +393 -0
  101. package/src/commands/native/access/staff-perms/get/command.js +119 -0
  102. package/src/commands/native/access/staff-perms/get/schema.json +703 -0
  103. package/src/commands/native/aftersale/case/get/command.js +104 -0
  104. package/src/commands/native/aftersale/case/get/exact-json.js +52 -0
  105. package/src/commands/native/aftersale/case/get/money.js +28 -0
  106. package/src/commands/native/aftersale/case/get/projection.js +120 -0
  107. package/src/commands/native/aftersale/case/get/query.js +72 -0
  108. package/src/commands/native/aftersale/case/get/schema.json +134 -0
  109. package/src/commands/native/aftersale/case/list/command.js +135 -0
  110. package/src/commands/native/aftersale/case/list/schema.json +1095 -0
  111. package/src/commands/native/aftersale/case/scan/command.js +110 -0
  112. package/src/commands/native/aftersale/case/scan/query.js +62 -0
  113. package/src/commands/native/aftersale/case/scan/scan.js +78 -0
  114. package/src/commands/native/aftersale/case/scan/schema.json +964 -0
  115. package/src/commands/native/aftersale/case/scan/search.js +108 -0
  116. package/src/commands/native/aftersale/commands.index.json +42 -0
  117. package/src/commands/native/aftersale/domain.json +3 -0
  118. package/src/commands/native/aftersale/refund/list/command.js +97 -0
  119. package/src/commands/native/aftersale/refund/list/money.js +25 -0
  120. package/src/commands/native/aftersale/refund/list/projection.js +50 -0
  121. package/src/commands/native/aftersale/refund/list/query.js +73 -0
  122. package/src/commands/native/aftersale/refund/list/schema.json +130 -0
  123. package/src/commands/native/aftersale/shared/query.js +58 -0
  124. package/src/commands/native/aftersale/shared/search.js +128 -0
  125. package/src/commands/native/benefit/card-definition/get/command.js +110 -0
  126. package/src/commands/native/benefit/card-definition/get/schema.json +545 -0
  127. package/src/commands/native/benefit/card-definition/list/command.js +106 -0
  128. package/src/commands/native/benefit/card-definition/list/schema.json +690 -0
  129. package/src/commands/native/benefit/card-grant/list/command.js +135 -0
  130. package/src/commands/native/benefit/card-grant/list/schema.json +758 -0
  131. package/src/commands/native/benefit/commands.index.json +51 -0
  132. package/src/commands/native/benefit/customer-card/get/command.js +110 -0
  133. package/src/commands/native/benefit/customer-card/get/schema.json +340 -0
  134. package/src/commands/native/benefit/customer-card/list/command.js +103 -0
  135. package/src/commands/native/benefit/customer-card/list/schema.json +731 -0
  136. package/src/commands/native/benefit/domain.json +3 -0
  137. package/src/commands/native/coupon/activity/get/command.js +183 -0
  138. package/src/commands/native/coupon/activity/get/schema.json +624 -0
  139. package/src/commands/native/coupon/activity/list/command.js +169 -0
  140. package/src/commands/native/coupon/activity/list/schema.json +845 -0
  141. package/src/commands/native/coupon/commands.index.json +42 -0
  142. package/src/commands/native/coupon/domain.json +3 -0
  143. package/src/commands/native/coupon/shared/query.js +15 -0
  144. package/src/commands/native/coupon/voucher/get/command.js +113 -0
  145. package/src/commands/native/coupon/voucher/get/schema.json +147 -0
  146. package/src/commands/native/coupon/voucher/list/command.js +155 -0
  147. package/src/commands/native/coupon/voucher/list/schema.json +772 -0
  148. package/src/commands/native/customer/commands.index.json +96 -0
  149. package/src/commands/native/customer/customer/get/adapter.js +82 -0
  150. package/src/commands/native/customer/customer/get/command.js +106 -0
  151. package/src/commands/native/customer/customer/get/schema.json +958 -0
  152. package/src/commands/native/customer/customer/list/command.js +78 -0
  153. package/src/commands/native/customer/customer/list/input.js +145 -0
  154. package/src/commands/native/customer/customer/list/query.js +103 -0
  155. package/src/commands/native/customer/customer/list/schema.json +969 -0
  156. package/src/commands/native/customer/customer/scan/command.js +80 -0
  157. package/src/commands/native/customer/customer/scan/scan.js +92 -0
  158. package/src/commands/native/customer/customer/scan/schema.json +211 -0
  159. package/src/commands/native/customer/customer-tag/get/command.js +166 -0
  160. package/src/commands/native/customer/customer-tag/get/schema.json +317 -0
  161. package/src/commands/native/customer/customer-tag/list/command.js +143 -0
  162. package/src/commands/native/customer/customer-tag/list/schema.json +259 -0
  163. package/src/commands/native/customer/domain.json +3 -0
  164. package/src/commands/native/customer/segment/list/command.js +96 -0
  165. package/src/commands/native/customer/segment/list/query.js +98 -0
  166. package/src/commands/native/customer/segment/list/schema.json +135 -0
  167. package/src/commands/native/customer/segment-customer/list/command.js +171 -0
  168. package/src/commands/native/customer/segment-customer/list/schema.json +123 -0
  169. package/src/commands/native/customer/shared/search-input.js +179 -0
  170. package/src/commands/native/customer/shared/search-page.js +54 -0
  171. package/src/commands/native/customer/tag/get/command.js +73 -0
  172. package/src/commands/native/customer/tag/get/schema.json +339 -0
  173. package/src/commands/native/customer/tagcategory/get/command.js +80 -0
  174. package/src/commands/native/customer/tagcategory/get/query.js +98 -0
  175. package/src/commands/native/customer/tagcategory/get/schema.json +339 -0
  176. package/src/commands/native/customer/tagcategory/list/command.js +127 -0
  177. package/src/commands/native/customer/tagcategory/list/schema.json +394 -0
  178. package/src/commands/native/fulfillment/commands.index.json +33 -0
  179. package/src/commands/native/fulfillment/delivery/get/command.js +107 -0
  180. package/src/commands/native/fulfillment/delivery/get/projection.js +142 -0
  181. package/src/commands/native/fulfillment/delivery/get/schema.json +173 -0
  182. package/src/commands/native/fulfillment/domain.json +3 -0
  183. package/src/commands/native/fulfillment/shipment/list/command.js +83 -0
  184. package/src/commands/native/fulfillment/shipment/list/projection.js +130 -0
  185. package/src/commands/native/fulfillment/shipment/list/schema.json +752 -0
  186. package/src/commands/native/fulfillment/waybill/get/command.js +193 -0
  187. package/src/commands/native/fulfillment/waybill/get/response.js +68 -0
  188. package/src/commands/native/fulfillment/waybill/get/schema.json +190 -0
  189. package/src/commands/native/interaction/commands.index.json +24 -0
  190. package/src/commands/native/interaction/domain.json +3 -0
  191. package/src/commands/native/interaction/lottery/get/command.js +61 -0
  192. package/src/commands/native/interaction/lottery/get/query.js +152 -0
  193. package/src/commands/native/interaction/lottery/get/schema.json +1004 -0
  194. package/src/commands/native/interaction/lottery/list/command.js +176 -0
  195. package/src/commands/native/interaction/lottery/list/schema.json +898 -0
  196. package/src/commands/native/item/attribute/list/command.js +122 -0
  197. package/src/commands/native/item/attribute/list/schema.json +727 -0
  198. package/src/commands/native/item/brand/list/command.js +114 -0
  199. package/src/commands/native/item/brand/list/schema.json +725 -0
  200. package/src/commands/native/item/classification/get/command.js +87 -0
  201. package/src/commands/native/item/classification/get/schema.json +596 -0
  202. package/src/commands/native/item/classification/list/command.js +105 -0
  203. package/src/commands/native/item/classification/list/schema.json +676 -0
  204. package/src/commands/native/item/commands.index.json +87 -0
  205. package/src/commands/native/item/domain.json +1 -0
  206. package/src/commands/native/item/group/list/command.js +122 -0
  207. package/src/commands/native/item/group/list/schema.json +809 -0
  208. package/src/commands/native/item/item/get/command.js +122 -0
  209. package/src/commands/native/item/item/get/schema.json +3799 -0
  210. package/src/commands/native/item/item/list/command.js +7 -0
  211. package/src/commands/native/item/item/list/schema.json +1118 -0
  212. package/src/commands/native/item/item/scan/command.js +7 -0
  213. package/src/commands/native/item/item/scan/schema.json +1046 -0
  214. package/src/commands/native/item/item-content/get/command.js +54 -0
  215. package/src/commands/native/item/item-content/get/schema.json +677 -0
  216. package/src/commands/native/item/shared/api.js +31 -0
  217. package/src/commands/native/item/shared/content.js +20 -0
  218. package/src/commands/native/item/shared/diagnostics.js +61 -0
  219. package/src/commands/native/item/shared/h5.js +63 -0
  220. package/src/commands/native/item/shared/identity.js +62 -0
  221. package/src/commands/native/item/shared/normalize.js +49 -0
  222. package/src/commands/native/item/shared/scope.js +22 -0
  223. package/src/commands/native/item/shared/search-command.js +61 -0
  224. package/src/commands/native/item/shared/search-options.js +150 -0
  225. package/src/commands/native/item/shared/search.js +158 -0
  226. package/src/commands/native/member/commands.index.json +60 -0
  227. package/src/commands/native/member/customer-level/get/command.js +122 -0
  228. package/src/commands/native/member/customer-level/get/schema.json +300 -0
  229. package/src/commands/native/member/domain.json +3 -0
  230. package/src/commands/native/member/growth/get/command.js +64 -0
  231. package/src/commands/native/member/growth/get/schema.json +283 -0
  232. package/src/commands/native/member/growth-log/list/command.js +203 -0
  233. package/src/commands/native/member/growth-log/list/schema.json +695 -0
  234. package/src/commands/native/member/level/get/command.js +70 -0
  235. package/src/commands/native/member/level/get/query.js +125 -0
  236. package/src/commands/native/member/level/get/schema.json +339 -0
  237. package/src/commands/native/member/level/list/command.js +50 -0
  238. package/src/commands/native/member/level/list/query.js +109 -0
  239. package/src/commands/native/member/level/list/schema.json +104 -0
  240. package/src/commands/native/member/membership/get/command.js +78 -0
  241. package/src/commands/native/member/membership/get/schema.json +283 -0
  242. package/src/commands/native/order/commands.index.json +33 -0
  243. package/src/commands/native/order/domain.json +3 -0
  244. package/src/commands/native/order/order/get/command.js +155 -0
  245. package/src/commands/native/order/order/get/schema.json +1167 -0
  246. package/src/commands/native/order/order/list/command.js +230 -0
  247. package/src/commands/native/order/order/list/schema.json +1363 -0
  248. package/src/commands/native/order/order/scan/command.js +148 -0
  249. package/src/commands/native/order/order/scan/scan.js +89 -0
  250. package/src/commands/native/order/order/scan/schema.json +1445 -0
  251. package/src/commands/native/order/shared/projection.js +183 -0
  252. package/src/commands/native/order/shared/query.js +84 -0
  253. package/src/commands/native/order/shared/search.js +112 -0
  254. package/src/commands/native/org/commands.index.json +42 -0
  255. package/src/commands/native/org/domain.json +3 -0
  256. package/src/commands/native/org/shared/single-staff.js +115 -0
  257. package/src/commands/native/org/staff/get/command.js +148 -0
  258. package/src/commands/native/org/staff/get/schema.json +114 -0
  259. package/src/commands/native/org/staff-role/get/command.js +45 -0
  260. package/src/commands/native/org/staff-role/get/schema.json +93 -0
  261. package/src/commands/native/org/unit/get/command.js +131 -0
  262. package/src/commands/native/org/unit/get/schema.json +478 -0
  263. package/src/commands/native/org/unit/list/command.js +187 -0
  264. package/src/commands/native/org/unit/list/schema.json +828 -0
  265. package/src/commands/native/points/account/get/command.js +65 -0
  266. package/src/commands/native/points/account/get/schema.json +303 -0
  267. package/src/commands/native/points/commands.index.json +33 -0
  268. package/src/commands/native/points/domain.json +3 -0
  269. package/src/commands/native/points/log/list/command.js +141 -0
  270. package/src/commands/native/points/log/list/schema.json +761 -0
  271. package/src/commands/native/points/rule/list/command.js +82 -0
  272. package/src/commands/native/points/rule/list/schema.json +294 -0
  273. package/src/commands/native/privacy/commands.index.json +15 -0
  274. package/src/commands/native/privacy/domain.json +3 -0
  275. package/src/commands/native/privacy/field/decrypt/command.js +185 -0
  276. package/src/commands/native/privacy/field/decrypt/schema.json +656 -0
  277. package/src/commands/native/privacy/shared/order-source.js +33 -0
  278. package/src/commands/native/stock/commands.index.json +15 -0
  279. package/src/commands/native/stock/domain.json +3 -0
  280. package/src/commands/native/stock/log/list/command.js +207 -0
  281. package/src/commands/native/stock/log/list/schema.json +922 -0
  282. package/src/commands/native/valuecard/card/get/command.js +107 -0
  283. package/src/commands/native/valuecard/card/get/schema.json +683 -0
  284. package/src/commands/native/valuecard/card/list/command.js +136 -0
  285. package/src/commands/native/valuecard/card/list/pagination.js +23 -0
  286. package/src/commands/native/valuecard/card/list/schema.json +780 -0
  287. package/src/commands/native/valuecard/commands.index.json +24 -0
  288. package/src/commands/native/valuecard/domain.json +3 -0
  289. package/src/commands/native/valuecard/shared/projection.js +57 -0
  290. package/src/commands/system/api.js +47 -0
  291. package/src/commands/system/auth.js +118 -0
  292. package/src/commands/system/contracts.js +373 -0
  293. package/src/commands/system/doctor.js +24 -0
  294. package/src/commands/system/install.js +41 -0
  295. package/src/commands/system/printer.js +16 -0
  296. package/src/commands/system/schema.js +162 -0
  297. package/src/commands/system/shop-guidance.js +9 -0
  298. package/src/commands/system/shop.js +237 -0
  299. package/src/commands/system/skills.js +49 -0
  300. package/src/execution/beijing-date-time.js +29 -0
  301. package/src/execution/beijing-time.js +21 -0
  302. package/src/execution/bounded-input.js +42 -0
  303. package/src/execution/command-io.js +137 -0
  304. package/src/execution/command-policy.js +41 -0
  305. package/src/execution/command-schema.js +131 -0
  306. package/src/execution/datetime.js +83 -0
  307. package/src/execution/error-details.js +399 -0
  308. package/src/execution/error-model.js +82 -0
  309. package/src/execution/exact-json.js +80 -0
  310. package/src/execution/list-total.js +10 -0
  311. package/src/execution/money.js +59 -0
  312. package/src/execution/parser-error.js +66 -0
  313. package/src/execution/privacy.js +197 -0
  314. package/src/execution/public-schema.js +52 -0
  315. package/src/execution/query-api.js +101 -0
  316. package/src/execution/range.js +84 -0
  317. package/src/execution/request-state.js +55 -0
  318. package/src/execution/schema-completeness.js +116 -0
  319. package/src/execution/shop-query.js +54 -0
  320. package/src/execution/target-shop-default.js +23 -0
  321. package/src/execution/target-shop.js +49 -0
  322. package/src/identity/context-store.js +54 -0
  323. package/src/identity/context.js +38 -0
  324. package/src/installation/install.js +158 -0
  325. package/src/installation/npm-error.js +93 -0
  326. package/src/installation/skill.js +113 -0
  327. package/src/transport/account-api.js +152 -0
  328. package/src/transport/api.js +199 -0
  329. package/src/transport/shop-app.js +63 -0
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ const { Command, Option } = require('commander');
4
+ const { withSystemSchema } = require('./contracts');
5
+ const { install } = require('../../installation/install');
6
+
7
+ function createInstallCommand() {
8
+ return withSystemSchema(new Command('install')
9
+ .description('安装 CLI 与 Agent 入口 Skill')
10
+ .option('--host <names>', '指定 Agent,多个用逗号分隔:workbuddy、codebuddy、codex、claude-code、cursor、trae')
11
+ .addOption(new Option('--scope <scope>', '安装范围,默认个人目录').choices(['user', 'project']).default('user'))
12
+ .option('-y, --yes', '非交互安装;未指定 host 时使用检测到的 Agent')
13
+ .option('--directory <dir>', '其他宿主的 Skill 目录,与 host 二选一')
14
+ .option('--package <file>', '本地验收使用的 npm .tgz 文件,依赖仍可能联网下载')
15
+ .option('--dry-run', '只显示安装计划,不下载或写入')
16
+ .option('--json', '输出 JSON 安装结果')
17
+ .addHelpText('after', `
18
+ 默认检测并选择 Agent,持久安装同版 CLI,再安装轻量入口。
19
+ WorkBuddy、CodeBuddy、Codex、Claude Code、Cursor、Trae(中国版)支持个人与项目目录。
20
+ WorkBuddy 个人目录为 ~/.workbuddy/skills,CodeBuddy 为 ~/.codebuddy/skills;两者项目目录均为 .codebuddy/skills。
21
+ 现有本项目开发链接可自动迁移;同名其他命令或已修改的 Skill 保留并报错。
22
+ 安装不执行登录、选店或业务查询。普通 npm 安装也不会自动修改宿主配置。
23
+
24
+ 示例:
25
+ npx @youzan-cloud/zan-saas-cli@latest install
26
+ zan-saas install --yes --host workbuddy
27
+ zan-saas install --yes --host codex,claude-code --scope project
28
+ `)
29
+ .action(async options => {
30
+ const result = await install(options);
31
+ if (options.json || options.dryRun) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
32
+ else {
33
+ process.stdout.write(`安装完成:zan-saas ${result.version}\n`);
34
+ for (const skill of result.skills) process.stdout.write(` ${skill.host}:${skill.path}\n`);
35
+ process.stdout.write('入口文件已就绪,尚未验证 Agent 是否加载。\n');
36
+ process.stdout.write(`${result.nextStep}\n`);
37
+ }
38
+ }), 'install');
39
+ }
40
+
41
+ module.exports = { createInstallCommand };
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ const { Option } = require('commander');
4
+
5
+ // Terminals get a short sentence; pipes and agents keep the JSON contract. `extra` only feeds the text.
6
+ function createPrinter(text) {
7
+ return (kind, format) => (value, extra) => {
8
+ const human = (format || (process.stdout.isTTY ? 'text' : 'json')) === 'text';
9
+ process.stdout.write(`${human ? text[kind](value, extra) : JSON.stringify(value)}\n`);
10
+ };
11
+ }
12
+
13
+ const formatOption = () => new Option('--format <format>', 'text 或 json,终端默认 text,管道默认 json')
14
+ .choices(['text', 'json']);
15
+
16
+ module.exports = { createPrinter, formatOption };
@@ -0,0 +1,162 @@
1
+ 'use strict';
2
+
3
+ const { Command, Option } = require('commander');
4
+ const { commandContract, publishedDomains } = require('../../cli/command-paths');
5
+ const { commandSchema } = require('../../execution/command-schema');
6
+ const { publicCommandSchema } = require('../../execution/public-schema');
7
+ const { isContextHelp } = require('../../cli/context-help');
8
+ const { withSystemSchema } = require('./contracts');
9
+ const { parserErrorModel } = require('../../execution/parser-error');
10
+
11
+ function visibleCommands(program) {
12
+ const commands = [];
13
+ function visit(parent, path = []) {
14
+ for (const command of parent.commands) {
15
+ if (isContextHelp(command)) continue;
16
+ const parts = [...path, command.name()];
17
+ if (command.commands.length) visit(command, parts);
18
+ if (command._actionHandler) {
19
+ const key = parts.join('.');
20
+ commandContract(key, command);
21
+ commands.push({ path: key, command });
22
+ }
23
+ }
24
+ }
25
+ visit(program);
26
+ return commands;
27
+ }
28
+
29
+ function describeCommand(path, command) {
30
+ const { kind, risk, skill, at, availability } = commandContract(path, command);
31
+ return {
32
+ path,
33
+ command: `zan-saas ${path.replaceAll('.', ' ')}`,
34
+ usage: `zan-saas ${path.replaceAll('.', ' ')} ${command.usage()}`.trim(),
35
+ description: command.description(),
36
+ kind,
37
+ risk,
38
+ availability,
39
+ ...(skill ? { skill } : {}),
40
+ ...(at ? { at } : {}),
41
+ arguments: command.registeredArguments.map(argument => ({
42
+ name: argument.name(),
43
+ description: argument.description,
44
+ required: argument.required,
45
+ variadic: argument.variadic
46
+ })),
47
+ options: command.options.map(option => ({
48
+ flags: option.flags,
49
+ description: option.description,
50
+ required: option.mandatory,
51
+ ...(option.defaultValue !== undefined ? { default: option.defaultValue } : {}),
52
+ ...(option.argChoices ? { choices: option.argChoices } : {})
53
+ })),
54
+ ...publicCommandSchema(commandSchema(command)),
55
+ parserError: structuredClone(parserErrorModel)
56
+ };
57
+ }
58
+
59
+ function describeDomain(domain, commands) {
60
+ return {
61
+ path: domain.name,
62
+ description: domain.description,
63
+ kind: 'domain',
64
+ skill: domain.skill,
65
+ parserError: structuredClone(parserErrorModel),
66
+ commands: commands.filter(entry => entry.path.startsWith(`${domain.name}.`))
67
+ .map(entry => describeCommand(entry.path, entry.command))
68
+ };
69
+ }
70
+
71
+ function summarizeCommand(contract) {
72
+ return Object.fromEntries(['path', 'command', 'description', 'kind', 'risk', 'availability', 'skill', 'at']
73
+ .filter(key => Object.hasOwn(contract, key)).map(key => [key, contract[key]]));
74
+ }
75
+
76
+ function summarize(result) {
77
+ if (result.kind === 'domain') {
78
+ const { parserError, ...domain } = result;
79
+ return { ...domain, summary: true, commands: result.commands.map(summarizeCommand) };
80
+ }
81
+ if (result.path) return { ...summarizeCommand(result), summary: true };
82
+ return { summary: true,
83
+ domains: result.domains.map(({ commands, parserError, ...domain }) => domain),
84
+ commands: result.commands.map(summarizeCommand) };
85
+ }
86
+
87
+ // Keep complete referenced definitions, including transitive and cyclic references. Filtering only
88
+ // the selected model without this closure would leave a valid-looking but unusable contract.
89
+ function referencedDefinitions(value, definitions = {}) {
90
+ const selected = {};
91
+ const visit = node => {
92
+ if (!node || typeof node !== 'object') return;
93
+ if (typeof node.$ref === 'string' && node.$ref.startsWith('#/definitions/')) {
94
+ const name = node.$ref.slice('#/definitions/'.length).split('/')[0]
95
+ .replaceAll('~1', '/').replaceAll('~0', '~');
96
+ if (!Object.hasOwn(selected, name)) {
97
+ if (!Object.hasOwn(definitions, name)) throw new Error(`Missing schema definition: ${name}`);
98
+ Object.defineProperty(selected, name, { value: definitions[name], enumerable: true });
99
+ visit(definitions[name]);
100
+ }
101
+ }
102
+ for (const child of Object.values(node)) visit(child);
103
+ };
104
+ visit(value);
105
+ return selected;
106
+ }
107
+
108
+ function selectSection(contract, section) {
109
+ if (!Object.hasOwn(contract, section)) {
110
+ throw new Error(`Command ${contract.path} has no ${section} model`);
111
+ }
112
+ const result = { ...summarizeCommand(contract), schemaVersion: contract.schemaVersion,
113
+ section, [section]: contract[section] };
114
+ if (section === 'input') Object.assign(result, {
115
+ usage: contract.usage, arguments: contract.arguments, options: contract.options
116
+ });
117
+ if (contract.notes) result.notes = contract.notes;
118
+ if (['input', 'output'].includes(section) && contract.modules) result.modules = contract.modules;
119
+ if (section === 'output' && contract.success) result.success = contract.success;
120
+ if (section === 'error' && contract.errors) result.errors = contract.errors;
121
+ if (section === 'error') result.parserError = structuredClone(parserErrorModel);
122
+ const definitions = referencedDefinitions(result, contract.definitions);
123
+ if (Object.keys(definitions).length) result.definitions = definitions;
124
+ return result;
125
+ }
126
+
127
+ function createSchemaCommand(getProgram, preparePath = () => {}) {
128
+ return withSystemSchema(new Command('schema')
129
+ .description('查看命令的参数、输出与约束')
130
+ .argument('[path...]', '命令路径,使用空格或点分隔')
131
+ .option('--summary', '只列命令、描述、适用位置、风险和 Skill,不展开字段模型')
132
+ .option('--section <section>', '仅查看叶子命令的 input、output、preview 或 error 及所需引用定义')
133
+ .addOption(new Option('--format <format>', 'json 或 pretty,仅改变缩进')
134
+ .choices(['json', 'pretty']).default('json'))
135
+ .action((path, options) => {
136
+ if (options.summary && options.section) throw new Error('--summary 与 --section 不能同时使用');
137
+ if (options.section && !['input', 'output', 'preview', 'error'].includes(options.section)) {
138
+ throw new Error('--section 必须是 input、output、preview 或 error');
139
+ }
140
+ const key = (path || []).join('.').replaceAll(' ', '.');
141
+ preparePath(key);
142
+ const commands = visibleCommands(getProgram());
143
+ const found = commands.find(entry => entry.path === key);
144
+ const domains = publishedDomains(getProgram());
145
+ const domain = domains.find(entry => entry.name === key);
146
+ if (key && !found && !domain) throw new Error(`Command not published: ${key}`);
147
+ if (options.section && !found) throw new Error('--section 需要指定完整叶子命令路径');
148
+ let result = found
149
+ ? describeCommand(found.path, found.command)
150
+ : domain ? describeDomain(domain, commands)
151
+ : {
152
+ parserError: structuredClone(parserErrorModel),
153
+ domains: domains.map(entry => describeDomain(entry, commands)),
154
+ commands: commands.map(entry => describeCommand(entry.path, entry.command))
155
+ };
156
+ if (options.summary) result = summarize(result);
157
+ if (options.section) result = selectSection(result, options.section);
158
+ process.stdout.write(`${JSON.stringify(result, null, options.format === 'pretty' ? 2 : 0)}\n`);
159
+ }), 'schema');
160
+ }
161
+
162
+ module.exports = { createSchemaCommand, visibleCommands, describeCommand, referencedDefinitions, selectSection };
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ const shopSelectionHint = [
4
+ '查看可选店铺:zan-saas shop list',
5
+ '选择或切换店铺:zan-saas shop use <kdtId>,将 <kdtId> 替换为目标店铺 ID。',
6
+ '确认当前店铺:zan-saas shop current'
7
+ ].join('\n');
8
+
9
+ module.exports = { shopSelectionHint };
@@ -0,0 +1,237 @@
1
+ 'use strict';
2
+
3
+ const { Command, InvalidArgumentError } = require('commander');
4
+ const account = require('../../transport/account-api');
5
+ const { oauthConfig } = require('../../auth/config');
6
+ const { readContext } = require('../../identity/context');
7
+ const {
8
+ contextInput, deleteContext, readSavedContext, saveContext
9
+ } = require('../../identity/context-store');
10
+ const { withSystemSchema } = require('./contracts');
11
+ const { createPrinter, formatOption } = require('./printer');
12
+ const { shopSelectionHint } = require('./shop-guidance');
13
+
14
+ const idPattern = /^[1-9]\d*$/;
15
+ const orgLabels = { hq: '总部', shop: '单店' };
16
+ const roleLabel = role => account.shopRoles[role] || '未知类型';
17
+ const envNote = '环境变量 ZAN_SAAS_CONTEXT 已设置,业务命令仍以它为准。';
18
+
19
+ function positiveInteger(name, max = Number.MAX_SAFE_INTEGER) {
20
+ return value => {
21
+ if (!idPattern.test(value) || Number(value) > max) {
22
+ throw new InvalidArgumentError(`${name} 必须是 1 到 ${max} 之间的整数。`);
23
+ }
24
+ return Number(value);
25
+ };
26
+ }
27
+
28
+ function lineOfBusinesses(value) {
29
+ const codes = value.split(',').map(code => code.trim());
30
+ if (!codes.every(code => idPattern.test(code) && Number.isSafeInteger(Number(code)))) {
31
+ throw new InvalidArgumentError('--line-of-business 必须是逗号分隔的正整数业务线编码。');
32
+ }
33
+ return codes.map(Number);
34
+ }
35
+
36
+ function shopIdOf(env) {
37
+ const state = readContext(contextInput(env));
38
+ return state.status === 'ready' ? state.context.shopId : null;
39
+ }
40
+
41
+ function requestShopIdOf(env) {
42
+ const value = env.ZAN_SAAS_KDT_ID;
43
+ return typeof value === 'string' && idPattern.test(value) && Number.isSafeInteger(Number(value))
44
+ ? value : null;
45
+ }
46
+
47
+ // Checks a few shops at a time; a failed check keeps the shop rather than hiding it by mistake.
48
+ async function checkStaffStatuses(items, env, concurrency = 5) {
49
+ const checks = new Array(items.length);
50
+ let next = 0;
51
+ const worker = async () => {
52
+ while (next < items.length) {
53
+ const index = next++;
54
+ try {
55
+ checks[index] = { status: await account.staffStatus(items[index].kdtId, env) };
56
+ } catch (error) {
57
+ checks[index] = { error };
58
+ }
59
+ }
60
+ };
61
+ await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, worker));
62
+ return checks;
63
+ }
64
+
65
+ // Hides the shops where the user's staff account is disabled, unless --all; total stays the server's count.
66
+ async function listShops(options = {}, env = process.env) {
67
+ const name = typeof options.name === 'string' ? options.name.trim() : '';
68
+ const page = await account.listShops({
69
+ ...(name ? { name } : {}), pageNum: options.pageNo ?? 1, pageSize: options.pageSize ?? 20,
70
+ lineOfBusinesses: options.lineOfBusiness ?? account.defaultLineOfBusinesses
71
+ }, env);
72
+ // Staff filtering may hide the entire page; only the server's unfiltered total determines continuation.
73
+ const hasMore = page.total === null ? null :
74
+ BigInt(page.pageNum) * BigInt(page.pageSize) < BigInt(page.total);
75
+ Object.assign(page, { hasMore,
76
+ nextPageNo: hasMore === true && page.pageNum < Number.MAX_SAFE_INTEGER ? page.pageNum + 1 : null,
77
+ windowLimitReached: hasMore !== false && page.pageNum === Number.MAX_SAFE_INTEGER });
78
+ if (options.all) return { ...page, totalScope: 'before_staff_filter', hidden: 0,
79
+ hiddenReason: null, nextStep: null, unchecked: [], uncheckedReason: null };
80
+ const checks = await checkStaffStatuses(page.items, env);
81
+ const items = page.items.filter((_, index) => checks[index].status !== account.staffStatuses.disabled);
82
+ const unchecked = page.items.filter((_, index) => checks[index].error).map(item => item.kdtId);
83
+ const failed = checks.find(check => check.error);
84
+ const hidden = page.items.length - items.length;
85
+ return {
86
+ ...page, items, totalScope: 'before_staff_filter', hidden,
87
+ hiddenReason: hidden ? 'staff_disabled' : null,
88
+ nextStep: hidden ? '本页隐藏了员工账号已停用的店铺;可加 --all 查看,但不能据此获得业务访问权限。' : null,
89
+ unchecked,
90
+ uncheckedReason: failed ? failed.error.message : null
91
+ };
92
+ }
93
+
94
+ // Confirms the shop through the user's own shop list before saving it as the context.
95
+ async function useShop(kdtId, env = process.env) {
96
+ if (typeof kdtId !== 'string' || !idPattern.test(kdtId) || !Number.isSafeInteger(Number(kdtId))) {
97
+ throw new Error('kdtId 必须是正整数店铺 ID。');
98
+ }
99
+ const user = await account.whoami(env);
100
+ const page = await account.listShops({ includeKdtIds: [kdtId], pageNum: 1, pageSize: 1 }, env);
101
+ const shop = page.items.find(item => item.kdtId === kdtId);
102
+ if (!shop) {
103
+ throw new Error(`当前账号下没有可选店铺 ${kdtId}。执行 zan-saas shop list 查看可选店铺。`);
104
+ }
105
+ if (!shop.orgType) {
106
+ throw new Error(`店铺 ${kdtId} 是${roleLabel(shop.shopRole)},暂时只能选择总部或单店。`);
107
+ }
108
+ // A failed check does not block: the business gateway still verifies the user against the shop.
109
+ const status = await account.staffStatus(kdtId, env).catch(() => null);
110
+ if (status === account.staffStatuses.disabled) {
111
+ throw new Error(`你在店铺 ${kdtId} 的员工账号已停用,不能选择。`);
112
+ }
113
+ const context = saveContext({ userId: user.userId, hq: kdtId, shopId: kdtId, orgType: shop.orgType },
114
+ { shopName: shop.shopName }, env);
115
+ return { context, shopName: shop.shopName, overriddenByEnv: Boolean(env.ZAN_SAAS_CONTEXT) };
116
+ }
117
+
118
+ function currentShop(env = process.env) {
119
+ const savedEntry = readSavedContext(env);
120
+ const saved = savedEntry ? { context: savedEntry.context, shopName: savedEntry.shopName } : null;
121
+ const requestShopId = requestShopIdOf(env);
122
+ if (env.ZAN_SAAS_CONTEXT) {
123
+ const state = readContext(env.ZAN_SAAS_CONTEXT);
124
+ return { source: 'env', status: state.status, context: state.context ?? null, saved, requestShopId };
125
+ }
126
+ return saved ? { source: 'saved', status: 'ready', context: saved.context, saved, requestShopId } :
127
+ { source: 'missing', status: 'missing', context: null, saved: null, requestShopId };
128
+ }
129
+
130
+ function clearShop(env = process.env) {
131
+ return { cleared: deleteContext(env), server: oauthConfig(env).server };
132
+ }
133
+
134
+ const describe = (context, shopName) =>
135
+ `${shopName || '未命名店铺'}(kdtId ${context.shopId},${orgLabels[context.orgType]})`;
136
+
137
+ const text = {
138
+ list: (value, current) => {
139
+ const lines = value.items.map(item => `${item.kdtId === current ? '*' : ' '} ${item.kdtId} ` +
140
+ `${item.shopName || '未命名店铺'} 类型:${roleLabel(item.shopRole)}${item.orgType ? '' : '(暂不可选)'}`);
141
+ if (lines.length) lines.push(value.total === null
142
+ ? `总数未知,第 ${value.pageNum} 页。* 为当前店铺。`
143
+ : `匹配总数 ${value.total} 个(员工停用过滤前),第 ${value.pageNum} 页。* 为当前店铺。`);
144
+ else if (!value.hidden) lines.push(value.total === 0 ? '没有找到店铺。' : '本页没有可显示的店铺。');
145
+ if (value.hidden) lines.push(`本页隐藏了 ${value.hidden} 个你已被停用的店铺,加 --all 显示全部。`);
146
+ if (value.unchecked?.length) {
147
+ lines.push(`${value.unchecked.length} 个店铺的员工状态查询失败,未过滤:${value.uncheckedReason}`);
148
+ }
149
+ if (value.hasMore === null) lines.push('总数未知,无法确认是否查完;可缩小名称筛选继续查找。');
150
+ if (Number.isSafeInteger(value.nextPageNo)) {
151
+ lines.push(`还有下一页;保留本次筛选和每页数量,加 --page-no ${value.nextPageNo} 继续。`);
152
+ }
153
+ if (value.windowLimitReached) lines.push('已达 CLI 页码上限,不能继续翻页;请缩小名称筛选。');
154
+ if (value.items.some(item => item.orgType)) lines.push(shopSelectionHint);
155
+ return lines.join('\n');
156
+ },
157
+ use: value => `${value.overriddenByEnv ? '已保存店铺' : '已切换到店铺'} ${describe(value.context, value.shopName)}。` +
158
+ (value.overriddenByEnv ? `\n${envNote}` : '') +
159
+ '\n确认当前店铺:zan-saas shop current',
160
+ current: value => {
161
+ const requestNote = value.requestShopId
162
+ ? `\n环境变量 ZAN_SAAS_KDT_ID 设置为 ${value.requestShopId};具体请求店铺以所用命令和目标参数为准。`
163
+ : '';
164
+ if (value.source === 'env') {
165
+ const head = value.status === 'ready'
166
+ ? `当前上下文来自环境变量 ZAN_SAAS_CONTEXT(kdtId ${value.context.shopId},${orgLabels[value.context.orgType]})。`
167
+ : '环境变量 ZAN_SAAS_CONTEXT 无效,业务命令会拒绝执行。';
168
+ return `${value.saved ? `${head}\n已保存的店铺 ${describe(value.saved.context, value.saved.shopName)} 被它覆盖。` : head}${requestNote}`;
169
+ }
170
+ if (value.source === 'saved') {
171
+ return `当前店铺:${describe(value.context, value.saved.shopName)}${requestNote}\n${shopSelectionHint}`;
172
+ }
173
+ return `未选择店铺。${requestNote}\n${shopSelectionHint}`;
174
+ },
175
+ clear: value => (value.cleared ? '已清除保存的店铺。' : '没有保存的店铺。')
176
+ };
177
+ const printer = createPrinter(text);
178
+
179
+ const defaults = {
180
+ list: options => listShops(options), use: kdtId => useShop(kdtId), current: () => currentShop(),
181
+ clear: () => clearShop(), currentShopId: () => shopIdOf(process.env)
182
+ };
183
+
184
+ function createShopCommand(impl = defaults) {
185
+ const shop = new Command('shop').description('查看可用店铺并设置当前店铺')
186
+ .addHelpText('after', `
187
+ 登录成功后,首次选店或切换店铺使用同一流程,无需重新登录:
188
+ 1. 查看可选店铺:zan-saas shop list
189
+ 2. 用户选定目标后:zan-saas shop use <kdtId>
190
+ 3. 确认当前店铺:zan-saas shop current
191
+ 将 <kdtId> 替换为目标店铺 ID;店铺较多时可用 shop list --name <keyword> 查找。
192
+ 浏览器中切换店铺不会改变 CLI 保存的选择。
193
+ ZAN_SAAS_CONTEXT 会覆盖保存的店铺,切换后以 shop current 显示的实际来源为准。
194
+ `);
195
+ withSystemSchema(shop.command('list')
196
+ .description('分页查看当前用户的店铺,无需预先选择店铺')
197
+ .option('--name <keyword>', '按店铺名称关键字筛选')
198
+ .option('--page-no <n>', '页码,从 1 开始', positiveInteger('--page-no'), 1)
199
+ .option('--page-size <n>', '每页数量,1 到 50', positiveInteger('--page-size', 50), 20)
200
+ .option('--line-of-business <codes>', '逗号分隔的业务线编码,默认微商城、教育、酒店、餐饮、美业、零售',
201
+ lineOfBusinesses)
202
+ .option('--all', '显示全部店铺,包括你已被停用的店铺')
203
+ .addOption(formatOption())
204
+ .addHelpText('after', `
205
+ 结果按总部聚合,每行是一个总部或单店。目前只能选择总部或单店。
206
+ 默认逐个查询你在本页店铺的员工状态,隐藏已停用的店铺;查询失败的店铺保留。
207
+ total 是员工停用过滤前的匹配总数;null 表示未知,空页或短页不表示已查完。
208
+ hasMore 为 true 时,保留筛选和每页数量,按 nextPageNo 继续;为 null 时无法确认是否查完。
209
+ windowLimitReached 为 true 表示达到 CLI 可表示的页码上限,请缩小筛选;不自动翻页。
210
+ 人机交接:Agent 展示可选店铺名称和类型,由用户选择;内部使用对应 kdtId;Agent 再执行 zan-saas shop use <kdtId>,并用 zan-saas shop current 核对实际生效的店铺,不自行猜选店铺或自动选第一家。
211
+ 调用账号接口网关,默认生产环境,ZAN_SAAS_ACCOUNT_API_BASE_URL 可以覆盖。
212
+ `)
213
+ .action(async options => {
214
+ printer('list', options.format)(await impl.list(options), impl.currentShopId());
215
+ }), 'shop.list');
216
+ withSystemSchema(shop.command('use')
217
+ .description('选择当前用户的店铺并保存为上下文')
218
+ .argument('<kdtId>', 'shop list 返回的总部或单店 kdtId')
219
+ .addOption(formatOption())
220
+ .addHelpText('after', `
221
+ 先确认店铺属于当前账号,再把 userId、hq、shopId、orgType 保存到本地;业务命令随后用它选择店铺。
222
+ 已有有效上下文时沿用当前店铺;用户按店名明确选择且唯一匹配,或确认候选后,使用对应 kdtId 执行;本地上下文不是业务接口授权证明。
223
+ 环境变量 ZAN_SAAS_CONTEXT 设置时优先于保存的店铺。
224
+ `)
225
+ .action(async (kdtId, options) => { printer('use', options.format)(await impl.use(kdtId)); }), 'shop.use');
226
+ withSystemSchema(shop.command('current')
227
+ .description('查看业务命令使用的店铺上下文,不访问网络')
228
+ .addOption(formatOption())
229
+ .action(options => { printer('current', options.format)(impl.current()); }), 'shop.current');
230
+ withSystemSchema(shop.command('clear')
231
+ .description('清除本机保存的店铺上下文,不访问网络')
232
+ .addOption(formatOption())
233
+ .action(options => { printer('clear', options.format)(impl.clear()); }), 'shop.clear');
234
+ return shop;
235
+ }
236
+
237
+ module.exports = { clearShop, createShopCommand, currentShop, listShops, text, useShop };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const { Command } = require('commander');
4
+ const { readPublishedSkillFile } = require('../../cli/skill-catalog');
5
+ const { skillsRoot } = require('../../cli/domain-catalog');
6
+ const { withSystemSchema } = require('./contracts');
7
+
8
+ function createSkillsCommand(catalog, root = skillsRoot) {
9
+ const skills = new Command('skills').description('查看使用指引')
10
+ .addHelpText('after', `
11
+ 示例:
12
+ zan-saas skills list
13
+ zan-saas skills read zan-saas
14
+ `);
15
+ withSystemSchema(skills.command('list')
16
+ .description('列出当前安装版附带的使用指引')
17
+ .action(() => {
18
+ const entries = catalog.map(({ name, type, domain, description }) => ({
19
+ name, ...(domain ? { domain } : {}), description, type
20
+ }));
21
+ process.stdout.write(`${JSON.stringify({ skills: entries })}\n`);
22
+ }), 'skills.list');
23
+ withSystemSchema(skills.command('read')
24
+ .description('读取已发布的使用指引或其参考文档')
25
+ .argument('<skill>', 'skills list 返回的指引名称')
26
+ .argument('[file]', '相对 Markdown 路径,默认 SKILL.md')
27
+ .addHelpText('after', `
28
+ 指引名称和文件路径是两个独立参数,不要用同一对引号包住。
29
+
30
+ 示例:
31
+ zan-saas skills read zan-saas
32
+ zan-saas skills read zan-saas references/host-entry.md
33
+ `)
34
+ .action((skill, file) => {
35
+ if (/\s/.test(skill)) {
36
+ const parts = skill.trim().split(/\s+/);
37
+ const entry = file === undefined && parts.length === 2 &&
38
+ catalog.find(entry => entry.name === parts[0] && entry.files.includes(parts[1]));
39
+ const example = entry && /^[a-zA-Z0-9._/-]+$/.test(parts[1])
40
+ ? `正确写法:zan-saas skills read ${entry.name} ${parts[1]}`
41
+ : '用法:zan-saas skills read <skill> [file]';
42
+ throw new Error(`请将 Skill 名称和文件路径分开传入,不要用同一对引号包住。${example}`);
43
+ }
44
+ process.stdout.write(readPublishedSkillFile(root, catalog, skill, file));
45
+ }), 'skills.read');
46
+ return skills;
47
+ }
48
+
49
+ module.exports = { createSkillsCommand };
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ const fullMilliseconds = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})\.(\d{3})$/;
4
+
5
+ function isBeijingDateTimeMs(value) {
6
+ if (typeof value !== 'string') return false;
7
+ const match = fullMilliseconds.exec(value);
8
+ if (!match || match[0] !== value) return false;
9
+ const [year, month, day, hour, minute, second, millisecond] = match.slice(1).map(Number);
10
+ if (year < 1 || month < 1 || month > 12 || day < 1 || hour > 23 || minute > 59 || second > 59) {
11
+ return false;
12
+ }
13
+ const calendar = new Date(0);
14
+ calendar.setUTCFullYear(year, month - 1, day);
15
+ calendar.setUTCHours(hour, minute, second, millisecond);
16
+ return calendar.getUTCFullYear() === year && calendar.getUTCMonth() === month - 1 &&
17
+ calendar.getUTCDate() === day;
18
+ }
19
+
20
+ function fromBeijingSeconds(value) {
21
+ if (typeof value !== 'string' || value.length !== 19 || !/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(value)) {
22
+ throw new TypeError('Invalid Beijing date-time');
23
+ }
24
+ const result = `${value}.000`;
25
+ if (!isBeijingDateTimeMs(result)) throw new TypeError('Invalid Beijing calendar date');
26
+ return result;
27
+ }
28
+
29
+ module.exports = { isBeijingDateTimeMs, fromBeijingSeconds };
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ function formatBeijingMilliseconds(milliseconds) {
4
+ if (!Number.isSafeInteger(milliseconds)) throw new Error('invalid milliseconds');
5
+ const shifted = milliseconds + 8 * 3600 * 1000;
6
+ if (!Number.isSafeInteger(shifted)) throw new Error('invalid milliseconds');
7
+ const text = new Date(shifted).toISOString();
8
+ if (!/^\d{4}-/.test(text)) throw new Error('date outside four-digit year range');
9
+ return text.slice(0, 23).replace('T', ' ');
10
+ }
11
+
12
+ function isBeijingDateTimeMilliseconds(value) {
13
+ if (!/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}$/.test(value)) return false;
14
+ try {
15
+ return formatBeijingMilliseconds(Date.parse(value.replace(' ', 'T') + '+08:00')) === value;
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ module.exports = { formatBeijingMilliseconds, isBeijingDateTimeMilliseconds };
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const { constants } = require('node:fs');
5
+ const { TextDecoder } = require('node:util');
6
+ const { parseExactJson } = require('./exact-json');
7
+
8
+ const INPUT_LIMIT = 64 * 1024;
9
+
10
+ async function readJsonInput(filename, { stdin = process.stdin, limit = INPUT_LIMIT } = {}) {
11
+ let handle;
12
+ const chunks = [];
13
+ let bytes = 0;
14
+ function append(chunk) {
15
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
16
+ bytes += buffer.length;
17
+ if (bytes > limit) throw new Error('input limit');
18
+ chunks.push(buffer);
19
+ }
20
+ try {
21
+ if (filename === '-') {
22
+ if (stdin.isTTY) throw new Error('input required');
23
+ for await (const chunk of stdin) append(chunk);
24
+ } else {
25
+ handle = await fs.open(filename, constants.O_RDONLY | constants.O_NONBLOCK);
26
+ const stat = await handle.stat();
27
+ if (!stat.isFile() || stat.size > limit) throw new Error('input file');
28
+ while (true) {
29
+ const buffer = Buffer.alloc(Math.min(4096, limit - bytes + 1));
30
+ const { bytesRead } = await handle.read(buffer);
31
+ if (bytesRead === 0) break;
32
+ append(buffer.subarray(0, bytesRead));
33
+ }
34
+ }
35
+ const text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(Buffer.concat(chunks));
36
+ return parseExactJson(text, undefined, { rejectDuplicates: true });
37
+ } finally {
38
+ if (handle) await handle.close();
39
+ }
40
+ }
41
+
42
+ module.exports = { readJsonInput, INPUT_LIMIT };