primitive-admin 1.1.0-alpha.9 → 1.1.0

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 (591) hide show
  1. package/README.md +523 -109
  2. package/assets/skill/skills/primitive-platform/SKILL.md +289 -0
  3. package/dist/bin/primitive.d.ts +2 -0
  4. package/dist/bin/primitive.js +372 -21
  5. package/dist/bin/primitive.js.map +1 -1
  6. package/dist/src/commands/admins.d.ts +2 -0
  7. package/dist/src/commands/admins.js +136 -30
  8. package/dist/src/commands/admins.js.map +1 -1
  9. package/dist/src/commands/analytics.d.ts +2 -0
  10. package/dist/src/commands/analytics.js +713 -55
  11. package/dist/src/commands/analytics.js.map +1 -1
  12. package/dist/src/commands/apps.d.ts +2 -0
  13. package/dist/src/commands/apps.js +65 -100
  14. package/dist/src/commands/apps.js.map +1 -1
  15. package/dist/src/commands/auth-sessions.d.ts +7 -0
  16. package/dist/src/commands/auth-sessions.js +144 -0
  17. package/dist/src/commands/auth-sessions.js.map +1 -0
  18. package/dist/src/commands/auth.d.ts +2 -0
  19. package/dist/src/commands/auth.js +238 -108
  20. package/dist/src/commands/auth.js.map +1 -1
  21. package/dist/src/commands/blob-buckets.d.ts +2 -0
  22. package/dist/src/commands/blob-buckets.js +331 -0
  23. package/dist/src/commands/blob-buckets.js.map +1 -0
  24. package/dist/src/commands/catalog.d.ts +2 -0
  25. package/dist/src/commands/catalog.js +63 -48
  26. package/dist/src/commands/catalog.js.map +1 -1
  27. package/dist/src/commands/collection-type-configs.d.ts +2 -0
  28. package/dist/src/commands/collection-type-configs.js +85 -0
  29. package/dist/src/commands/collection-type-configs.js.map +1 -0
  30. package/dist/src/commands/collections.d.ts +2 -0
  31. package/dist/src/commands/collections.js +1282 -0
  32. package/dist/src/commands/collections.js.map +1 -0
  33. package/dist/src/commands/comparisons.d.ts +2 -0
  34. package/dist/src/commands/comparisons.js +6 -6
  35. package/dist/src/commands/comparisons.js.map +1 -1
  36. package/dist/src/commands/config.d.ts +46 -0
  37. package/dist/src/commands/config.js +465 -0
  38. package/dist/src/commands/config.js.map +1 -0
  39. package/dist/src/commands/connections.d.ts +2 -0
  40. package/dist/src/commands/connections.js +99 -0
  41. package/dist/src/commands/connections.js.map +1 -0
  42. package/dist/src/commands/cron-triggers.d.ts +2 -0
  43. package/dist/src/commands/cron-triggers.js +266 -0
  44. package/dist/src/commands/cron-triggers.js.map +1 -0
  45. package/dist/src/commands/database-type-configs.d.ts +2 -0
  46. package/dist/src/commands/database-type-configs.js +164 -0
  47. package/dist/src/commands/database-type-configs.js.map +1 -0
  48. package/dist/src/commands/database-types.d.ts +2 -0
  49. package/dist/src/commands/database-types.js +471 -0
  50. package/dist/src/commands/database-types.js.map +1 -0
  51. package/dist/src/commands/databases.d.ts +65 -0
  52. package/dist/src/commands/databases.js +1887 -243
  53. package/dist/src/commands/databases.js.map +1 -1
  54. package/dist/src/commands/documents.d.ts +60 -0
  55. package/dist/src/commands/documents.js +2888 -22
  56. package/dist/src/commands/documents.js.map +1 -1
  57. package/dist/src/commands/email-templates.d.ts +2 -0
  58. package/dist/src/commands/email-templates.js +175 -0
  59. package/dist/src/commands/email-templates.js.map +1 -0
  60. package/dist/src/commands/env.d.ts +23 -0
  61. package/dist/src/commands/env.js +395 -0
  62. package/dist/src/commands/env.js.map +1 -0
  63. package/dist/src/commands/feature-flags.d.ts +14 -0
  64. package/dist/src/commands/feature-flags.js +117 -0
  65. package/dist/src/commands/feature-flags.js.map +1 -0
  66. package/dist/src/commands/functions.d.ts +20 -0
  67. package/dist/src/commands/functions.js +1630 -0
  68. package/dist/src/commands/functions.js.map +1 -0
  69. package/dist/src/commands/group-type-configs.d.ts +2 -0
  70. package/dist/src/commands/group-type-configs.js +87 -0
  71. package/dist/src/commands/group-type-configs.js.map +1 -0
  72. package/dist/src/commands/groups.d.ts +2 -0
  73. package/dist/src/commands/groups.js +65 -113
  74. package/dist/src/commands/groups.js.map +1 -1
  75. package/dist/src/commands/guides.d.ts +223 -0
  76. package/dist/src/commands/guides.js +627 -69
  77. package/dist/src/commands/guides.js.map +1 -1
  78. package/dist/src/commands/init.d.ts +37 -0
  79. package/dist/src/commands/init.js +1693 -208
  80. package/dist/src/commands/init.js.map +1 -1
  81. package/dist/src/commands/integrations.d.ts +2 -0
  82. package/dist/src/commands/integrations.js +428 -187
  83. package/dist/src/commands/integrations.js.map +1 -1
  84. package/dist/src/commands/llm.d.ts +2 -0
  85. package/dist/src/commands/llm.js +4 -2
  86. package/dist/src/commands/llm.js.map +1 -1
  87. package/dist/src/commands/locks.d.ts +8 -0
  88. package/dist/src/commands/locks.js +175 -0
  89. package/dist/src/commands/locks.js.map +1 -0
  90. package/dist/src/commands/metadata-category-configs.d.ts +12 -0
  91. package/dist/src/commands/metadata-category-configs.js +113 -0
  92. package/dist/src/commands/metadata-category-configs.js.map +1 -0
  93. package/dist/src/commands/metadata.d.ts +2 -0
  94. package/dist/src/commands/metadata.js +288 -0
  95. package/dist/src/commands/metadata.js.map +1 -0
  96. package/dist/src/commands/prompts.d.ts +2 -0
  97. package/dist/src/commands/prompts.js +322 -634
  98. package/dist/src/commands/prompts.js.map +1 -1
  99. package/dist/src/commands/rule-sets.d.ts +3 -0
  100. package/dist/src/commands/rule-sets.js +139 -148
  101. package/dist/src/commands/rule-sets.js.map +1 -1
  102. package/dist/src/commands/scripts.d.ts +30 -0
  103. package/dist/src/commands/scripts.js +688 -0
  104. package/dist/src/commands/scripts.js.map +1 -0
  105. package/dist/src/commands/secrets.d.ts +2 -0
  106. package/dist/src/commands/secrets.js +109 -0
  107. package/dist/src/commands/secrets.js.map +1 -0
  108. package/dist/src/commands/sessions.d.ts +2 -0
  109. package/dist/src/commands/sessions.js +76 -0
  110. package/dist/src/commands/sessions.js.map +1 -0
  111. package/dist/src/commands/skill.d.ts +2 -0
  112. package/dist/src/commands/skill.js +29 -0
  113. package/dist/src/commands/skill.js.map +1 -0
  114. package/dist/src/commands/sync-app-settings.d.ts +158 -0
  115. package/dist/src/commands/sync-app-settings.js +328 -0
  116. package/dist/src/commands/sync-app-settings.js.map +1 -0
  117. package/dist/src/commands/sync.d.ts +2670 -0
  118. package/dist/src/commands/sync.js +17144 -834
  119. package/dist/src/commands/sync.js.map +1 -1
  120. package/dist/src/commands/tokens.d.ts +2 -0
  121. package/dist/src/commands/tokens.js +132 -22
  122. package/dist/src/commands/tokens.js.map +1 -1
  123. package/dist/src/commands/users.d.ts +2 -0
  124. package/dist/src/commands/users.js +542 -24
  125. package/dist/src/commands/users.js.map +1 -1
  126. package/dist/src/commands/vars.d.ts +8 -0
  127. package/dist/src/commands/vars.js +97 -0
  128. package/dist/src/commands/vars.js.map +1 -0
  129. package/dist/src/commands/waitlist.d.ts +2 -0
  130. package/dist/src/commands/waitlist.js +12 -11
  131. package/dist/src/commands/waitlist.js.map +1 -1
  132. package/dist/src/commands/webhooks.d.ts +31 -0
  133. package/dist/src/commands/webhooks.js +633 -0
  134. package/dist/src/commands/webhooks.js.map +1 -0
  135. package/dist/src/commands/workflows.d.ts +88 -0
  136. package/dist/src/commands/workflows.js +1568 -742
  137. package/dist/src/commands/workflows.js.map +1 -1
  138. package/dist/src/lib/access-rule-display.d.ts +21 -0
  139. package/dist/src/lib/access-rule-display.js +34 -0
  140. package/dist/src/lib/access-rule-display.js.map +1 -0
  141. package/dist/src/lib/api-client.d.ts +2550 -0
  142. package/dist/src/lib/api-client.js +2503 -160
  143. package/dist/src/lib/api-client.js.map +1 -1
  144. package/dist/src/lib/app-settings-descriptor.d.ts +263 -0
  145. package/dist/src/lib/app-settings-descriptor.js +583 -0
  146. package/dist/src/lib/app-settings-descriptor.js.map +1 -0
  147. package/dist/src/lib/auth-flow.d.ts +8 -0
  148. package/dist/src/lib/batch.d.ts +26 -0
  149. package/dist/src/lib/batch.js +32 -0
  150. package/dist/src/lib/batch.js.map +1 -0
  151. package/dist/src/lib/block-layout.d.ts +160 -0
  152. package/dist/src/lib/block-layout.js +451 -0
  153. package/dist/src/lib/block-layout.js.map +1 -0
  154. package/dist/src/lib/block-selector.d.ts +58 -0
  155. package/dist/src/lib/block-selector.js +92 -0
  156. package/dist/src/lib/block-selector.js.map +1 -0
  157. package/dist/src/lib/canonical-json.d.ts +12 -0
  158. package/dist/src/lib/canonical-json.js +35 -0
  159. package/dist/src/lib/canonical-json.js.map +1 -0
  160. package/dist/src/lib/channel.d.ts +30 -0
  161. package/dist/src/lib/channel.js +68 -0
  162. package/dist/src/lib/channel.js.map +1 -0
  163. package/dist/src/lib/cli-manifest.d.ts +68 -0
  164. package/dist/src/lib/cli-manifest.js +71 -0
  165. package/dist/src/lib/cli-manifest.js.map +1 -0
  166. package/dist/src/lib/codegen-shared/generatedFiles.d.ts +101 -0
  167. package/dist/src/lib/codegen-shared/generatedFiles.js +191 -0
  168. package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -0
  169. package/dist/src/lib/codegen-shared/prettierStable.d.ts +262 -0
  170. package/dist/src/lib/codegen-shared/prettierStable.js +610 -0
  171. package/dist/src/lib/codegen-shared/prettierStable.js.map +1 -0
  172. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +38 -0
  173. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +46 -0
  174. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -0
  175. package/dist/src/lib/collection-export.d.ts +184 -0
  176. package/dist/src/lib/collection-export.js +252 -0
  177. package/dist/src/lib/collection-export.js.map +1 -0
  178. package/dist/src/lib/config-json-field.d.ts +28 -0
  179. package/dist/src/lib/config-json-field.js +56 -0
  180. package/dist/src/lib/config-json-field.js.map +1 -0
  181. package/dist/src/lib/config-object-descriptor.d.ts +127 -0
  182. package/dist/src/lib/config-object-descriptor.js +740 -0
  183. package/dist/src/lib/config-object-descriptor.js.map +1 -0
  184. package/dist/src/lib/config-payload.d.ts +92 -0
  185. package/dist/src/lib/config-payload.js +161 -0
  186. package/dist/src/lib/config-payload.js.map +1 -0
  187. package/dist/src/lib/config-surface.d.ts +141 -0
  188. package/dist/src/lib/config-surface.js +368 -0
  189. package/dist/src/lib/config-surface.js.map +1 -0
  190. package/dist/src/lib/config-toml.d.ts +10 -0
  191. package/dist/src/lib/config-toml.js +42 -0
  192. package/dist/src/lib/config-toml.js.map +1 -0
  193. package/dist/src/lib/config.d.ts +71 -0
  194. package/dist/src/lib/config.js +71 -68
  195. package/dist/src/lib/config.js.map +1 -1
  196. package/dist/src/lib/confirm-prompt.d.ts +83 -0
  197. package/dist/src/lib/confirm-prompt.js +110 -0
  198. package/dist/src/lib/confirm-prompt.js.map +1 -0
  199. package/dist/src/lib/constants.d.ts +11 -0
  200. package/dist/src/lib/constants.js +12 -0
  201. package/dist/src/lib/constants.js.map +1 -0
  202. package/dist/src/lib/crash-handlers.d.ts +20 -0
  203. package/dist/src/lib/crash-handlers.js +49 -0
  204. package/dist/src/lib/crash-handlers.js.map +1 -0
  205. package/dist/src/lib/credentials-store.d.ts +104 -0
  206. package/dist/src/lib/credentials-store.js +336 -0
  207. package/dist/src/lib/credentials-store.js.map +1 -0
  208. package/dist/src/lib/csv.d.ts +47 -0
  209. package/dist/src/lib/csv.js +172 -0
  210. package/dist/src/lib/csv.js.map +1 -0
  211. package/dist/src/lib/data-input.d.ts +23 -0
  212. package/dist/src/lib/data-input.js +50 -0
  213. package/dist/src/lib/data-input.js.map +1 -0
  214. package/dist/src/lib/db-codegen/dbFingerprint.d.ts +10 -0
  215. package/dist/src/lib/db-codegen/dbFingerprint.js +17 -0
  216. package/dist/src/lib/db-codegen/dbFingerprint.js.map +1 -0
  217. package/dist/src/lib/db-codegen/dbGenerator.d.ts +67 -0
  218. package/dist/src/lib/db-codegen/dbGenerator.js +170 -0
  219. package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -0
  220. package/dist/src/lib/db-codegen/dbNaming.d.ts +87 -0
  221. package/dist/src/lib/db-codegen/dbNaming.js +180 -0
  222. package/dist/src/lib/db-codegen/dbNaming.js.map +1 -0
  223. package/dist/src/lib/db-codegen/dbTemplates.d.ts +272 -0
  224. package/dist/src/lib/db-codegen/dbTemplates.js +480 -0
  225. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -0
  226. package/dist/src/lib/db-codegen/dbTsTypes.d.ts +73 -0
  227. package/dist/src/lib/db-codegen/dbTsTypes.js +139 -0
  228. package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -0
  229. package/dist/src/lib/db-codegen/dbTypeIR.d.ts +146 -0
  230. package/dist/src/lib/db-codegen/dbTypeIR.js +525 -0
  231. package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -0
  232. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.d.ts +112 -0
  233. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js +211 -0
  234. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js.map +1 -0
  235. package/dist/src/lib/deprecation.d.ts +22 -0
  236. package/dist/src/lib/deprecation.js +43 -0
  237. package/dist/src/lib/deprecation.js.map +1 -0
  238. package/dist/src/lib/document-export-permissions.d.ts +30 -0
  239. package/dist/src/lib/document-export-permissions.js +54 -0
  240. package/dist/src/lib/document-export-permissions.js.map +1 -0
  241. package/dist/src/lib/document-ingest-artifact.d.ts +120 -0
  242. package/dist/src/lib/document-ingest-artifact.js +505 -0
  243. package/dist/src/lib/document-ingest-artifact.js.map +1 -0
  244. package/dist/src/lib/document-ingest-input.d.ts +51 -0
  245. package/dist/src/lib/document-ingest-input.js +132 -0
  246. package/dist/src/lib/document-ingest-input.js.map +1 -0
  247. package/dist/src/lib/document-ingest-rows.d.ts +137 -0
  248. package/dist/src/lib/document-ingest-rows.js +181 -0
  249. package/dist/src/lib/document-ingest-rows.js.map +1 -0
  250. package/dist/src/lib/document-ingest.d.ts +101 -0
  251. package/dist/src/lib/document-ingest.js +384 -0
  252. package/dist/src/lib/document-ingest.js.map +1 -0
  253. package/dist/src/lib/env-resolver-core.d.ts +258 -0
  254. package/dist/src/lib/env-resolver-core.js +447 -0
  255. package/dist/src/lib/env-resolver-core.js.map +1 -0
  256. package/dist/src/lib/env-resolver.d.ts +99 -0
  257. package/dist/src/lib/env-resolver.js +153 -0
  258. package/dist/src/lib/env-resolver.js.map +1 -0
  259. package/dist/src/lib/fetch.d.ts +5 -0
  260. package/dist/src/lib/function-bundle.d.ts +147 -0
  261. package/dist/src/lib/function-bundle.js +341 -0
  262. package/dist/src/lib/function-bundle.js.map +1 -0
  263. package/dist/src/lib/function-collect.d.ts +123 -0
  264. package/dist/src/lib/function-collect.js +610 -0
  265. package/dist/src/lib/function-collect.js.map +1 -0
  266. package/dist/src/lib/function-db-types.d.ts +202 -0
  267. package/dist/src/lib/function-db-types.js +870 -0
  268. package/dist/src/lib/function-db-types.js.map +1 -0
  269. package/dist/src/lib/function-document-types.d.ts +144 -0
  270. package/dist/src/lib/function-document-types.js +370 -0
  271. package/dist/src/lib/function-document-types.js.map +1 -0
  272. package/dist/src/lib/function-grants-preflight.d.ts +64 -0
  273. package/dist/src/lib/function-grants-preflight.js +105 -0
  274. package/dist/src/lib/function-grants-preflight.js.map +1 -0
  275. package/dist/src/lib/function-log-lines.d.ts +76 -0
  276. package/dist/src/lib/function-log-lines.js +160 -0
  277. package/dist/src/lib/function-log-lines.js.map +1 -0
  278. package/dist/src/lib/function-log-row.d.ts +29 -0
  279. package/dist/src/lib/function-log-row.js +73 -0
  280. package/dist/src/lib/function-log-row.js.map +1 -0
  281. package/dist/src/lib/function-log-tail.d.ts +132 -0
  282. package/dist/src/lib/function-log-tail.js +262 -0
  283. package/dist/src/lib/function-log-tail.js.map +1 -0
  284. package/dist/src/lib/function-run.d.ts +289 -0
  285. package/dist/src/lib/function-run.js +389 -0
  286. package/dist/src/lib/function-run.js.map +1 -0
  287. package/dist/src/lib/function-schema-codegen.d.ts +143 -0
  288. package/dist/src/lib/function-schema-codegen.js +420 -0
  289. package/dist/src/lib/function-schema-codegen.js.map +1 -0
  290. package/dist/src/lib/function-sync.d.ts +459 -0
  291. package/dist/src/lib/function-sync.js +1258 -0
  292. package/dist/src/lib/function-sync.js.map +1 -0
  293. package/dist/src/lib/function-trigger-listing.d.ts +27 -0
  294. package/dist/src/lib/function-trigger-listing.js +70 -0
  295. package/dist/src/lib/function-trigger-listing.js.map +1 -0
  296. package/dist/src/lib/function-typecheck.d.ts +86 -0
  297. package/dist/src/lib/function-typecheck.js +370 -0
  298. package/dist/src/lib/function-typecheck.js.map +1 -0
  299. package/dist/src/lib/function-versions.d.ts +122 -0
  300. package/dist/src/lib/function-versions.js +182 -0
  301. package/dist/src/lib/function-versions.js.map +1 -0
  302. package/dist/src/lib/generated-allowlist.d.ts +28 -0
  303. package/dist/src/lib/generated-allowlist.js +281 -0
  304. package/dist/src/lib/generated-allowlist.js.map +1 -0
  305. package/dist/src/lib/generated-config-surfaces.d.ts +2936 -0
  306. package/dist/src/lib/generated-config-surfaces.js +10569 -0
  307. package/dist/src/lib/generated-config-surfaces.js.map +1 -0
  308. package/dist/src/lib/generated-sdk-types.d.ts +12 -0
  309. package/dist/src/lib/generated-sdk-types.js +13 -0
  310. package/dist/src/lib/generated-sdk-types.js.map +1 -0
  311. package/dist/src/lib/generated-template-lint.d.ts +212 -0
  312. package/dist/src/lib/generated-template-lint.js +624 -0
  313. package/dist/src/lib/generated-template-lint.js.map +1 -0
  314. package/dist/src/lib/init-adopt.d.ts +16 -0
  315. package/dist/src/lib/init-adopt.js +34 -0
  316. package/dist/src/lib/init-adopt.js.map +1 -0
  317. package/dist/src/lib/init-assets.d.ts +39 -0
  318. package/dist/src/lib/init-assets.js +97 -0
  319. package/dist/src/lib/init-assets.js.map +1 -0
  320. package/dist/src/lib/init-client-platforms.d.ts +14 -0
  321. package/dist/src/lib/init-client-platforms.js +71 -0
  322. package/dist/src/lib/init-client-platforms.js.map +1 -0
  323. package/dist/src/lib/init-config.d.ts +98 -0
  324. package/dist/src/lib/init-config.js +186 -0
  325. package/dist/src/lib/init-config.js.map +1 -0
  326. package/dist/src/lib/init-email-redirect-uris.d.ts +37 -0
  327. package/dist/src/lib/init-email-redirect-uris.js +46 -0
  328. package/dist/src/lib/init-email-redirect-uris.js.map +1 -0
  329. package/dist/src/lib/init-ios-links.d.ts +91 -0
  330. package/dist/src/lib/init-ios-links.js +219 -0
  331. package/dist/src/lib/init-ios-links.js.map +1 -0
  332. package/dist/src/lib/init-plan.d.ts +80 -0
  333. package/dist/src/lib/init-plan.js +95 -0
  334. package/dist/src/lib/init-plan.js.map +1 -0
  335. package/dist/src/lib/init-production-env.d.ts +48 -0
  336. package/dist/src/lib/init-production-env.js +59 -0
  337. package/dist/src/lib/init-production-env.js.map +1 -0
  338. package/dist/src/lib/init-schema.d.ts +74 -0
  339. package/dist/src/lib/init-schema.js +358 -0
  340. package/dist/src/lib/init-schema.js.map +1 -0
  341. package/dist/src/lib/init-xcode.d.ts +34 -0
  342. package/dist/src/lib/init-xcode.js +138 -0
  343. package/dist/src/lib/init-xcode.js.map +1 -0
  344. package/dist/src/lib/integration-request-config.d.ts +30 -0
  345. package/dist/src/lib/integration-request-config.js +145 -0
  346. package/dist/src/lib/integration-request-config.js.map +1 -0
  347. package/dist/src/lib/integration-selector.d.ts +42 -0
  348. package/dist/src/lib/integration-selector.js +46 -0
  349. package/dist/src/lib/integration-selector.js.map +1 -0
  350. package/dist/src/lib/ios-app-id.d.ts +34 -0
  351. package/dist/src/lib/ios-app-id.js +69 -0
  352. package/dist/src/lib/ios-app-id.js.map +1 -0
  353. package/dist/src/lib/list-options.d.ts +68 -0
  354. package/dist/src/lib/list-options.js +89 -0
  355. package/dist/src/lib/list-options.js.map +1 -0
  356. package/dist/src/lib/local-state.d.ts +55 -0
  357. package/dist/src/lib/local-state.js +167 -0
  358. package/dist/src/lib/local-state.js.map +1 -0
  359. package/dist/src/lib/local-test-cases.d.ts +63 -0
  360. package/dist/src/lib/local-test-cases.js +136 -0
  361. package/dist/src/lib/local-test-cases.js.map +1 -0
  362. package/dist/src/lib/log-inspection.d.ts +715 -0
  363. package/dist/src/lib/log-inspection.js +816 -0
  364. package/dist/src/lib/log-inspection.js.map +1 -0
  365. package/dist/src/lib/logout-admin-session.d.ts +33 -0
  366. package/dist/src/lib/logout-admin-session.js +70 -0
  367. package/dist/src/lib/logout-admin-session.js.map +1 -0
  368. package/dist/src/lib/migration-nag.d.ts +49 -0
  369. package/dist/src/lib/migration-nag.js +163 -0
  370. package/dist/src/lib/migration-nag.js.map +1 -0
  371. package/dist/src/lib/object-status-filter.d.ts +22 -0
  372. package/dist/src/lib/object-status-filter.js +45 -0
  373. package/dist/src/lib/object-status-filter.js.map +1 -0
  374. package/dist/src/lib/output.d.ts +124 -0
  375. package/dist/src/lib/output.js +219 -8
  376. package/dist/src/lib/output.js.map +1 -1
  377. package/dist/src/lib/package-manager.d.ts +140 -0
  378. package/dist/src/lib/package-manager.js +305 -0
  379. package/dist/src/lib/package-manager.js.map +1 -0
  380. package/dist/src/lib/paginate.d.ts +98 -0
  381. package/dist/src/lib/paginate.js +112 -0
  382. package/dist/src/lib/paginate.js.map +1 -0
  383. package/dist/src/lib/platform-owned.d.ts +63 -0
  384. package/dist/src/lib/platform-owned.js +85 -0
  385. package/dist/src/lib/platform-owned.js.map +1 -0
  386. package/dist/src/lib/project-config.d.ts +122 -0
  387. package/dist/src/lib/project-config.js +244 -0
  388. package/dist/src/lib/project-config.js.map +1 -0
  389. package/dist/src/lib/prompt-cost-format.d.ts +11 -0
  390. package/dist/src/lib/prompt-cost-format.js +41 -0
  391. package/dist/src/lib/prompt-cost-format.js.map +1 -0
  392. package/dist/src/lib/prompt-schema-codegen.d.ts +147 -0
  393. package/dist/src/lib/prompt-schema-codegen.js +462 -0
  394. package/dist/src/lib/prompt-schema-codegen.js.map +1 -0
  395. package/dist/src/lib/query-operators.d.ts +43 -0
  396. package/dist/src/lib/query-operators.js +80 -0
  397. package/dist/src/lib/query-operators.js.map +1 -0
  398. package/dist/src/lib/record-filter.d.ts +18 -0
  399. package/dist/src/lib/record-filter.js +55 -0
  400. package/dist/src/lib/record-filter.js.map +1 -0
  401. package/dist/src/lib/refresh-admin-credentials.d.ts +73 -0
  402. package/dist/src/lib/refresh-admin-credentials.js +123 -0
  403. package/dist/src/lib/refresh-admin-credentials.js.map +1 -0
  404. package/dist/src/lib/resolve-init-dev-port.d.ts +56 -0
  405. package/dist/src/lib/resolve-init-dev-port.js +55 -0
  406. package/dist/src/lib/resolve-init-dev-port.js.map +1 -0
  407. package/dist/src/lib/resolve-init-server.d.ts +64 -0
  408. package/dist/src/lib/resolve-init-server.js +77 -0
  409. package/dist/src/lib/resolve-init-server.js.map +1 -0
  410. package/dist/src/lib/resolve-owner.d.ts +19 -0
  411. package/dist/src/lib/resolve-owner.js +20 -0
  412. package/dist/src/lib/resolve-owner.js.map +1 -0
  413. package/dist/src/lib/resolve-platform.d.ts +74 -0
  414. package/dist/src/lib/resolve-platform.js +105 -0
  415. package/dist/src/lib/resolve-platform.js.map +1 -0
  416. package/dist/src/lib/run-status.d.ts +19 -0
  417. package/dist/src/lib/run-status.generated.d.ts +39 -0
  418. package/dist/src/lib/run-status.generated.js +66 -0
  419. package/dist/src/lib/run-status.generated.js.map +1 -0
  420. package/dist/src/lib/run-status.js +19 -0
  421. package/dist/src/lib/run-status.js.map +1 -0
  422. package/dist/src/lib/server-text-normalization.d.ts +51 -0
  423. package/dist/src/lib/server-text-normalization.js +90 -0
  424. package/dist/src/lib/server-text-normalization.js.map +1 -0
  425. package/dist/src/lib/server-url.d.ts +22 -0
  426. package/dist/src/lib/server-url.js +33 -0
  427. package/dist/src/lib/server-url.js.map +1 -0
  428. package/dist/src/lib/signing-secret-status.d.ts +81 -0
  429. package/dist/src/lib/signing-secret-status.js +116 -0
  430. package/dist/src/lib/signing-secret-status.js.map +1 -0
  431. package/dist/src/lib/skill-installer.d.ts +71 -0
  432. package/dist/src/lib/skill-installer.js +441 -0
  433. package/dist/src/lib/skill-installer.js.map +1 -0
  434. package/dist/src/lib/snapshot-audit-source.d.ts +45 -0
  435. package/dist/src/lib/snapshot-audit-source.js +58 -0
  436. package/dist/src/lib/snapshot-audit-source.js.map +1 -0
  437. package/dist/src/lib/snapshot-audit-store.d.ts +52 -0
  438. package/dist/src/lib/snapshot-audit-store.js +196 -0
  439. package/dist/src/lib/snapshot-audit-store.js.map +1 -0
  440. package/dist/src/lib/snapshot-audit.d.ts +207 -0
  441. package/dist/src/lib/snapshot-audit.js +431 -0
  442. package/dist/src/lib/snapshot-audit.js.map +1 -0
  443. package/dist/src/lib/snapshot-build-rows.d.ts +60 -0
  444. package/dist/src/lib/snapshot-build-rows.js +87 -0
  445. package/dist/src/lib/snapshot-build-rows.js.map +1 -0
  446. package/dist/src/lib/snapshot-build.d.ts +50 -0
  447. package/dist/src/lib/snapshot-build.js +111 -0
  448. package/dist/src/lib/snapshot-build.js.map +1 -0
  449. package/dist/src/lib/snapshot-manifest-layout.d.ts +61 -0
  450. package/dist/src/lib/snapshot-manifest-layout.js +70 -0
  451. package/dist/src/lib/snapshot-manifest-layout.js.map +1 -0
  452. package/dist/src/lib/snapshots.d.ts +98 -0
  453. package/dist/src/lib/snapshots.js +294 -0
  454. package/dist/src/lib/snapshots.js.map +1 -0
  455. package/dist/src/lib/step-run-table.d.ts +43 -0
  456. package/dist/src/lib/step-run-table.js +129 -0
  457. package/dist/src/lib/step-run-table.js.map +1 -0
  458. package/dist/src/lib/storage-pending-retry.d.ts +26 -0
  459. package/dist/src/lib/storage-pending-retry.js +42 -0
  460. package/dist/src/lib/storage-pending-retry.js.map +1 -0
  461. package/dist/src/lib/swift-codegen/agentGenerator.d.ts +42 -0
  462. package/dist/src/lib/swift-codegen/agentGenerator.js +118 -0
  463. package/dist/src/lib/swift-codegen/agentGenerator.js.map +1 -0
  464. package/dist/src/lib/swift-codegen/banners.d.ts +24 -0
  465. package/dist/src/lib/swift-codegen/banners.js +25 -0
  466. package/dist/src/lib/swift-codegen/banners.js.map +1 -0
  467. package/dist/src/lib/swift-codegen/dbGenerator.d.ts +113 -0
  468. package/dist/src/lib/swift-codegen/dbGenerator.js +926 -0
  469. package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -0
  470. package/dist/src/lib/swift-codegen/dbSwiftTypes.d.ts +42 -0
  471. package/dist/src/lib/swift-codegen/dbSwiftTypes.js +100 -0
  472. package/dist/src/lib/swift-codegen/dbSwiftTypes.js.map +1 -0
  473. package/dist/src/lib/swift-codegen/functionGenerator.d.ts +139 -0
  474. package/dist/src/lib/swift-codegen/functionGenerator.js +462 -0
  475. package/dist/src/lib/swift-codegen/functionGenerator.js.map +1 -0
  476. package/dist/src/lib/swift-codegen/generator.d.ts +100 -0
  477. package/dist/src/lib/swift-codegen/generator.js +457 -0
  478. package/dist/src/lib/swift-codegen/generator.js.map +1 -0
  479. package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +87 -0
  480. package/dist/src/lib/swift-codegen/schemaToSwift.js +661 -0
  481. package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -0
  482. package/dist/src/lib/swift-codegen/siblingSymbols.d.ts +94 -0
  483. package/dist/src/lib/swift-codegen/siblingSymbols.js +155 -0
  484. package/dist/src/lib/swift-codegen/siblingSymbols.js.map +1 -0
  485. package/dist/src/lib/swift-codegen/swiftNaming.d.ts +85 -0
  486. package/dist/src/lib/swift-codegen/swiftNaming.js +198 -0
  487. package/dist/src/lib/swift-codegen/swiftNaming.js.map +1 -0
  488. package/dist/src/lib/sync-dir-selector.d.ts +21 -0
  489. package/dist/src/lib/sync-dir-selector.js +30 -0
  490. package/dist/src/lib/sync-dir-selector.js.map +1 -0
  491. package/dist/src/lib/sync-paths.d.ts +128 -0
  492. package/dist/src/lib/sync-paths.js +195 -0
  493. package/dist/src/lib/sync-paths.js.map +1 -0
  494. package/dist/src/lib/sync-resource-types.d.ts +563 -0
  495. package/dist/src/lib/sync-resource-types.js +1073 -0
  496. package/dist/src/lib/sync-resource-types.js.map +1 -0
  497. package/dist/src/lib/sync-selectors.d.ts +138 -0
  498. package/dist/src/lib/sync-selectors.js +289 -0
  499. package/dist/src/lib/sync-selectors.js.map +1 -0
  500. package/dist/src/lib/template.d.ts +170 -0
  501. package/dist/src/lib/template.js +484 -68
  502. package/dist/src/lib/template.js.map +1 -1
  503. package/dist/src/lib/test-case-file-names.d.ts +40 -0
  504. package/dist/src/lib/test-case-file-names.js +91 -0
  505. package/dist/src/lib/test-case-file-names.js.map +1 -0
  506. package/dist/src/lib/test-case-keys.d.ts +29 -0
  507. package/dist/src/lib/test-case-keys.js +55 -0
  508. package/dist/src/lib/test-case-keys.js.map +1 -0
  509. package/dist/src/lib/test-case-variables.d.ts +29 -0
  510. package/dist/src/lib/test-case-variables.js +71 -0
  511. package/dist/src/lib/test-case-variables.js.map +1 -0
  512. package/dist/src/lib/token-inject.d.ts +56 -0
  513. package/dist/src/lib/token-inject.js +204 -0
  514. package/dist/src/lib/token-inject.js.map +1 -0
  515. package/dist/src/lib/toml-database-config.d.ts +123 -0
  516. package/dist/src/lib/toml-database-config.js +544 -0
  517. package/dist/src/lib/toml-database-config.js.map +1 -0
  518. package/dist/src/lib/toml-metadata-config.d.ts +151 -0
  519. package/dist/src/lib/toml-metadata-config.js +476 -0
  520. package/dist/src/lib/toml-metadata-config.js.map +1 -0
  521. package/dist/src/lib/toml-native-form.d.ts +46 -0
  522. package/dist/src/lib/toml-native-form.js +78 -0
  523. package/dist/src/lib/toml-native-form.js.map +1 -0
  524. package/dist/src/lib/toml-params-validator.d.ts +129 -0
  525. package/dist/src/lib/toml-params-validator.js +298 -0
  526. package/dist/src/lib/toml-params-validator.js.map +1 -0
  527. package/dist/src/lib/toml-scalar-edit.d.ts +43 -0
  528. package/dist/src/lib/toml-scalar-edit.js +283 -0
  529. package/dist/src/lib/toml-scalar-edit.js.map +1 -0
  530. package/dist/src/lib/user-selector.d.ts +24 -0
  531. package/dist/src/lib/user-selector.js +33 -0
  532. package/dist/src/lib/user-selector.js.map +1 -0
  533. package/dist/src/lib/version-check.d.ts +35 -0
  534. package/dist/src/lib/version-check.js +241 -0
  535. package/dist/src/lib/version-check.js.map +1 -0
  536. package/dist/src/lib/watch.d.ts +121 -0
  537. package/dist/src/lib/watch.js +169 -0
  538. package/dist/src/lib/watch.js.map +1 -0
  539. package/dist/src/lib/web-url.d.ts +40 -0
  540. package/dist/src/lib/web-url.js +76 -0
  541. package/dist/src/lib/web-url.js.map +1 -0
  542. package/dist/src/lib/webhook-deliver.d.ts +209 -0
  543. package/dist/src/lib/webhook-deliver.js +519 -0
  544. package/dist/src/lib/webhook-deliver.js.map +1 -0
  545. package/dist/src/lib/workflow-apply.d.ts +110 -0
  546. package/dist/src/lib/workflow-apply.js +164 -0
  547. package/dist/src/lib/workflow-apply.js.map +1 -0
  548. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.d.ts +129 -0
  549. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js +269 -0
  550. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js.map +1 -0
  551. package/dist/src/lib/workflow-codegen/generator.d.ts +96 -0
  552. package/dist/src/lib/workflow-codegen/generator.js +361 -0
  553. package/dist/src/lib/workflow-codegen/generator.js.map +1 -0
  554. package/dist/src/lib/workflow-codegen/invokerIR.d.ts +94 -0
  555. package/dist/src/lib/workflow-codegen/invokerIR.js +76 -0
  556. package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -0
  557. package/dist/src/lib/workflow-codegen/naming.d.ts +33 -0
  558. package/dist/src/lib/workflow-codegen/naming.js +81 -0
  559. package/dist/src/lib/workflow-codegen/naming.js.map +1 -0
  560. package/dist/src/lib/workflow-codegen/schemaToTs.d.ts +80 -0
  561. package/dist/src/lib/workflow-codegen/schemaToTs.js +303 -0
  562. package/dist/src/lib/workflow-codegen/schemaToTs.js.map +1 -0
  563. package/dist/src/lib/workflow-config-apply.d.ts +70 -0
  564. package/dist/src/lib/workflow-config-apply.js +137 -0
  565. package/dist/src/lib/workflow-config-apply.js.map +1 -0
  566. package/dist/src/lib/workflow-config-sidecar.d.ts +63 -0
  567. package/dist/src/lib/workflow-config-sidecar.js +96 -0
  568. package/dist/src/lib/workflow-config-sidecar.js.map +1 -0
  569. package/dist/src/lib/workflow-defaults.d.ts +29 -0
  570. package/dist/src/lib/workflow-defaults.js +41 -0
  571. package/dist/src/lib/workflow-defaults.js.map +1 -0
  572. package/dist/src/lib/workflow-fragments.d.ts +64 -0
  573. package/dist/src/lib/workflow-fragments.js +342 -0
  574. package/dist/src/lib/workflow-fragments.js.map +1 -0
  575. package/dist/src/lib/workflow-include-preserve.d.ts +76 -0
  576. package/dist/src/lib/workflow-include-preserve.js +286 -0
  577. package/dist/src/lib/workflow-include-preserve.js.map +1 -0
  578. package/dist/src/lib/workflow-payload.d.ts +98 -0
  579. package/dist/src/lib/workflow-payload.js +178 -0
  580. package/dist/src/lib/workflow-payload.js.map +1 -0
  581. package/dist/src/lib/workflow-toml-validator.d.ts +211 -0
  582. package/dist/src/lib/workflow-toml-validator.js +770 -0
  583. package/dist/src/lib/workflow-toml-validator.js.map +1 -0
  584. package/dist/src/lib/workflow-usage.d.ts +196 -0
  585. package/dist/src/lib/workflow-usage.js +310 -0
  586. package/dist/src/lib/workflow-usage.js.map +1 -0
  587. package/dist/src/types/index.d.ts +591 -0
  588. package/dist/src/validators.d.ts +65 -0
  589. package/dist/src/validators.js +64 -0
  590. package/dist/src/validators.js.map +1 -0
  591. package/package.json +34 -9
@@ -2,13 +2,238 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
2
  import { homedir } from "os";
3
3
  import { join, basename } from "path";
4
4
  import { error, info, warn, formatTable, json, keyValue, } from "../lib/output.js";
5
+ import { getServerUrl } from "../lib/config.js";
6
+ import { resolveChannelBranch } from "../lib/channel.js";
7
+ import { wholeListEnvelope } from "../lib/paginate.js";
5
8
  const GUIDES_CACHE_DIR = process.env.PRIMITIVE_CONFIG_DIR
6
9
  ? join(process.env.PRIMITIVE_CONFIG_DIR, "guides")
7
10
  : join(homedir(), ".primitive", "guides");
8
11
  const CACHE_META_FILE = join(GUIDES_CACHE_DIR, "cache-meta.json");
9
- const GITHUB_RAW_BASE = "https://raw.githubusercontent.com/Primitive-Labs/primitive-docs/main/guides";
12
+ // The public publish-only site repo. Guides are pushed there (alongside the
13
+ // built docs site) by primitive-docs/scripts/publish-gh-pages.mjs; the docs
14
+ // SOURCE lives in the js-bao-wss monorepo, which is private and therefore
15
+ // can't serve raw URLs to end users. (The alpha channel's browsable site is
16
+ // served from `main/alpha/`; guides still come from the `alpha` branch.)
17
+ const GITHUB_RAW_ROOT = "https://raw.githubusercontent.com/Primitive-Labs/primitive-docs-site";
18
+ const DEFAULT_DOCS_BRANCH = "main";
10
19
  const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
11
20
  const CLIENT_PACKAGE_NAME = "js-bao-wss-client";
21
+ // Channel resolution now lives in lib/channel.ts, shared with the template
22
+ // download in `primitive init` (issue #1934). Re-exported here because the
23
+ // public shapes originated in this module (issue #1463).
24
+ export { normalizeServerUrl, isValidBranchName } from "../lib/channel.js";
25
+ /**
26
+ * Resolve which branch of the published site repo to fetch guides from.
27
+ *
28
+ * Branches of primitive-docs-site are named after the deploy environment
29
+ * whose docs they carry (published as part of each deploy — see
30
+ * primitive-docs/scripts/publish-gh-pages.mjs): `main` = production,
31
+ * `alpha` = the alpha environment. Precedence (originally issue #1463,
32
+ * repointed at consolidation): the undocumented `PRIMITIVE_GUIDES_BRANCH`
33
+ * override (validated as a plausible git ref), then alpha server-URL
34
+ * detection, then `main`. See `resolveChannelBranch` for the shared logic.
35
+ */
36
+ export function resolveDocsBranch(serverUrl, override = process.env.PRIMITIVE_GUIDES_BRANCH) {
37
+ return resolveChannelBranch(serverUrl, override, "PRIMITIVE_GUIDES_BRANCH");
38
+ }
39
+ /**
40
+ * Resolve the docs branch for the current invocation, reading the target from
41
+ * `getServerUrl()`. Logged-out / legacy-no-creds makes `getServerUrl()` throw;
42
+ * we treat that as "no target" and fall back to `main` (the override still
43
+ * applies). Only the branch decision is wrapped here — an invalid override
44
+ * still throws out to the command's error handler.
45
+ */
46
+ function resolveDocsBranchForInvocation() {
47
+ let serverUrl;
48
+ try {
49
+ serverUrl = getServerUrl();
50
+ }
51
+ catch {
52
+ serverUrl = undefined;
53
+ }
54
+ return resolveDocsBranch(serverUrl);
55
+ }
56
+ // Language aliases — map the agent's natural vocabulary onto canonical values.
57
+ const LANGUAGE_ALIASES = {
58
+ typescript: "ts",
59
+ javascript: "ts",
60
+ js: "ts",
61
+ };
62
+ /**
63
+ * Normalize a `--language` flag value: lowercase, trim, and map known aliases
64
+ * (typescript/javascript/js → ts). Unknown values pass through unchanged.
65
+ *
66
+ * NOTE: guide flags are intentionally NOT validated against a fixed enum. An
67
+ * unknown/typo'd value simply matches no variant and falls back to the guide's
68
+ * default `file` (the "never fail" behavior from #977). This DIVERGES from
69
+ * `init --platform`, which validates and exits for unsupported values
70
+ * (cli/src/commands/init.ts) — that divergence is deliberate and guide-specific.
71
+ */
72
+ export function normalizeLanguage(value) {
73
+ if (value === undefined)
74
+ return undefined;
75
+ const lower = value.trim().toLowerCase();
76
+ if (lower === "")
77
+ return undefined;
78
+ return LANGUAGE_ALIASES[lower] ?? lower;
79
+ }
80
+ /**
81
+ * Normalize a `--platform` flag value: lowercase + trim. No aliases for now.
82
+ * Like `normalizeLanguage`, unknown values pass through and fall back to the
83
+ * default `file` rather than erroring (guide-specific "never fail" behavior).
84
+ */
85
+ export function normalizePlatform(value) {
86
+ if (value === undefined)
87
+ return undefined;
88
+ const lower = value.trim().toLowerCase();
89
+ if (lower === "")
90
+ return undefined;
91
+ return lower;
92
+ }
93
+ /**
94
+ * Select the guide variant filename for a requested (language, platform).
95
+ *
96
+ * Implemented as ordered `find()` passes — NOT a wildcard scoring system —
97
+ * with the no-flags case special-cased to the default `file`:
98
+ *
99
+ * 0. No flags at all → guide.file (deterministic "default")
100
+ * 1. Exact pair → variant pins BOTH requested dims
101
+ * 2. Language-only → variant pins language; platform agnostic
102
+ * 3. Platform-only → variant pins platform; language agnostic
103
+ * 4. Default → guide.file
104
+ *
105
+ * An omitted REQUEST dimension acts as a wildcard during matching (a
106
+ * `--language swift` request matches a swift variant regardless of platform).
107
+ * This function NEVER throws — an unknown/typo'd/unavailable combination simply
108
+ * falls through to the guide's default `file`.
109
+ */
110
+ export function selectVariant(guide, request) {
111
+ const reqLang = request.language;
112
+ const reqPlat = request.platform;
113
+ const variants = guide.variants ?? [];
114
+ // 0. No flags supplied at all → deterministically serve the default file.
115
+ if (reqLang === undefined && reqPlat === undefined) {
116
+ return { file: guide.file, matchedOn: "default" };
117
+ }
118
+ // A variant "matches" a dimension if the request omits it (wildcard) OR the
119
+ // variant pins it to exactly the requested value. A variant that omits a
120
+ // dimension is agnostic and matches any requested value for that dimension.
121
+ const langMatches = (v) => reqLang === undefined || v.language === undefined || v.language === reqLang;
122
+ const platMatches = (v) => reqPlat === undefined || v.platform === undefined || v.platform === reqPlat;
123
+ // 1. Exact pair: variant pins both requested dimensions to the request.
124
+ if (reqLang !== undefined && reqPlat !== undefined) {
125
+ const exact = variants.find((v) => v.language === reqLang && v.platform === reqPlat);
126
+ if (exact)
127
+ return { file: exact.file, matchedOn: "exact" };
128
+ }
129
+ // 2. Language requested. Prefer a platform-agnostic variant that pins the
130
+ // language (reported as "language" — pinned one dim, agnostic on the
131
+ // other). If none exists, honor the omitted-platform wildcard and fall
132
+ // back to a pair-specific variant that still satisfies the request
133
+ // (e.g. only `{swift,ios}` published + `--language swift`). That fallback
134
+ // pins BOTH dimensions to satisfy the request, so it's reported as
135
+ // "exact" — the variant is as specific as an exact-pair hit.
136
+ if (reqLang !== undefined) {
137
+ const langAgnostic = variants.find((v) => v.language === reqLang && v.platform === undefined);
138
+ if (langAgnostic)
139
+ return { file: langAgnostic.file, matchedOn: "language" };
140
+ const langWildcard = variants.find((v) => v.language === reqLang && platMatches(v));
141
+ if (langWildcard)
142
+ return { file: langWildcard.file, matchedOn: "exact" };
143
+ }
144
+ // 3. Platform requested (symmetric to pass 2). Prefer a language-agnostic
145
+ // variant that pins the platform ("platform"); otherwise honor the
146
+ // omitted-language wildcard and fall back to a pair-specific variant
147
+ // satisfying the request, reported as "exact".
148
+ if (reqPlat !== undefined) {
149
+ const platAgnostic = variants.find((v) => v.platform === reqPlat && v.language === undefined);
150
+ if (platAgnostic)
151
+ return { file: platAgnostic.file, matchedOn: "platform" };
152
+ const platWildcard = variants.find((v) => v.platform === reqPlat && langMatches(v));
153
+ if (platWildcard)
154
+ return { file: platWildcard.file, matchedOn: "exact" };
155
+ }
156
+ // 4. Nothing matched → the guide's default file (never throws).
157
+ return { file: guide.file, matchedOn: "default" };
158
+ }
159
+ /**
160
+ * The global set of languages a manifest supports. Derived (never hardcoded)
161
+ * from: every guide variant's `language`, the manifest-level `defaults.language`
162
+ * (falling back to `"ts"` when absent), and every `platforms[*].language`. Used
163
+ * to validate `--language` and to render the `guides list` legend / `--json`.
164
+ *
165
+ * Derived per-request from the manifest ACTUALLY loaded (so it stays correct
166
+ * for stale-cache / `--guide-version` fallbacks), and grows automatically as
167
+ * new languages appear in the manifest — no CLI change needed.
168
+ */
169
+ export function deriveLanguages(manifest) {
170
+ const languages = new Set();
171
+ for (const guide of manifest.guides ?? []) {
172
+ for (const variant of guide.variants ?? []) {
173
+ if (variant.language !== undefined)
174
+ languages.add(variant.language);
175
+ }
176
+ }
177
+ languages.add(manifest.defaults?.language ?? "ts");
178
+ for (const entry of Object.values(manifest.platforms ?? {})) {
179
+ if (entry.language)
180
+ languages.add(entry.language);
181
+ }
182
+ return languages;
183
+ }
184
+ /**
185
+ * Validate the requested `(language, platform)` against the loaded manifest and
186
+ * resolve the effective language (issue #1219). Behavior:
187
+ *
188
+ * - Normalizes both values first (lowercase + trim, aliases, `"" → undefined`)
189
+ * via the existing `normalizeLanguage`/`normalizePlatform` helpers, then
190
+ * validates the NORMALIZED value.
191
+ * - `--language` is validated against `deriveLanguages(manifest)` whether or
192
+ * not a `platforms` block exists (the set is derivable from any manifest).
193
+ * - `--platform` is validated + inferred ONLY when `manifest.platforms` is
194
+ * present. With the block, an unknown platform is a hard error and a known
195
+ * platform infers `language = platforms[p].language` (an explicit
196
+ * `--language` always wins the language dimension). WITHOUT the block, the
197
+ * platform passes through unvalidated and uninferred — today's non-enforcing
198
+ * behavior, so older manifests / stale cache don't start hard-failing.
199
+ * - Unknown values produce a hard-error message mirroring `init --platform`'s
200
+ * style, plus a cross-hint when the bad value names the other dimension
201
+ * (`--platform swift` → "Did you mean --language swift?", and vice versa).
202
+ */
203
+ export function validateAndResolveRequest(manifest, rawLanguage, rawPlatform) {
204
+ const language = normalizeLanguage(rawLanguage);
205
+ const platform = normalizePlatform(rawPlatform);
206
+ const supportedLanguages = deriveLanguages(manifest);
207
+ const platforms = manifest.platforms; // may be undefined (back-compat)
208
+ // Validate --language (independent of the platforms block).
209
+ if (language !== undefined && !supportedLanguages.has(language)) {
210
+ const supported = [...supportedLanguages].sort().join(", ");
211
+ let message = `Unknown language "${language}". Supported languages: ${supported}`;
212
+ // Cross-hint: the value names a known platform, not a language.
213
+ if (platforms && platforms[language]) {
214
+ message += `. Did you mean --platform ${language}?`;
215
+ }
216
+ return { error: message };
217
+ }
218
+ let effectiveLanguage = language;
219
+ // Validate + infer --platform — active ONLY when the platforms block exists.
220
+ if (platform !== undefined && platforms !== undefined) {
221
+ if (!platforms[platform]) {
222
+ const supported = Object.keys(platforms).sort().join(", ");
223
+ let message = `Unknown platform "${platform}". Supported platforms: ${supported}`;
224
+ // Cross-hint: the value names a known language, not a platform
225
+ // (the reporter's exact `--platform swift` case).
226
+ if (supportedLanguages.has(platform)) {
227
+ message += `. Did you mean --language ${platform}?`;
228
+ }
229
+ return { error: message };
230
+ }
231
+ // Infer the language from the platform; an explicit --language wins.
232
+ effectiveLanguage = language ?? platforms[platform].language;
233
+ }
234
+ // else: no platforms block → back-compat pass-through (no validate, no infer).
235
+ return { language: effectiveLanguage, platform };
236
+ }
12
237
  function detectClientVersion() {
13
238
  const clientPkgPath = join(process.cwd(), "node_modules", CLIENT_PACKAGE_NAME, "package.json");
14
239
  try {
@@ -56,18 +281,21 @@ function formatClientVersion(info) {
56
281
  function formatGuidesVersion(info) {
57
282
  return info.version;
58
283
  }
59
- function getVersionCacheDir(version) {
60
- return join(GUIDES_CACHE_DIR, version);
284
+ // Cache paths are segmented by branch (#1463): ~/.primitive/guides/{branch}/
285
+ // {version}/... so a `next` fetch never overwrites the `main` cached copy of
286
+ // the same version+filename (and vice versa).
287
+ function getVersionCacheDir(branch, version) {
288
+ return join(GUIDES_CACHE_DIR, branch, version);
61
289
  }
62
- function getManifestPath(version) {
63
- return join(getVersionCacheDir(version), "manifest.json");
290
+ function getManifestPath(branch, version) {
291
+ return join(getVersionCacheDir(branch, version), "manifest.json");
64
292
  }
65
- function getGuidesCacheDir(version) {
66
- return join(getVersionCacheDir(version), "guides");
293
+ function getGuidesCacheDir(branch, version) {
294
+ return join(getVersionCacheDir(branch, version), "guides");
67
295
  }
68
- function ensureCacheDir(version) {
69
- const versionDir = getVersionCacheDir(version);
70
- const guidesDir = getGuidesCacheDir(version);
296
+ function ensureCacheDir(branch, version) {
297
+ const versionDir = getVersionCacheDir(branch, version);
298
+ const guidesDir = getGuidesCacheDir(branch, version);
71
299
  if (!existsSync(versionDir)) {
72
300
  mkdirSync(versionDir, { recursive: true });
73
301
  }
@@ -98,11 +326,15 @@ function isCacheExpired(fetchedAt) {
98
326
  const fetchedTime = new Date(fetchedAt).getTime();
99
327
  return Date.now() - fetchedTime > CACHE_TTL_MS;
100
328
  }
101
- function getManifestUrl(version) {
102
- return `${GITHUB_RAW_BASE}/${version}/guides.json`;
329
+ /** The raw-GitHub base for a site-repo branch, e.g. `.../primitive-docs-site/main/guides`. */
330
+ function githubRawBase(branch) {
331
+ return `${GITHUB_RAW_ROOT}/${branch}/guides`;
103
332
  }
104
- function getGuideUrl(version, fileName) {
105
- return `${GITHUB_RAW_BASE}/${version}/${fileName}`;
333
+ export function buildManifestUrl(branch, version) {
334
+ return `${githubRawBase(branch)}/${version}/guides.json`;
335
+ }
336
+ export function buildGuideUrl(branch, version, fileName) {
337
+ return `${githubRawBase(branch)}/${version}/${fileName}`;
106
338
  }
107
339
  async function fetchWithTimeout(url, timeoutMs = 10000) {
108
340
  const controller = new AbortController();
@@ -115,11 +347,26 @@ async function fetchWithTimeout(url, timeoutMs = 10000) {
115
347
  clearTimeout(timeout);
116
348
  }
117
349
  }
118
- async function fetchManifestForVersion(version, forceRefresh = false) {
119
- ensureCacheDir(version);
350
+ /**
351
+ * Thrown by `fetchManifest` when a branch/version genuinely has no published
352
+ * manifest — every candidate manifest URL returned 404. Distinct from a
353
+ * transport or server error (timeout, DNS, 5xx), which `fetchManifest` lets
354
+ * propagate as a plain `Error`. `resolveManifest` relies on this distinction:
355
+ * only a truly-absent branch may fall back to `main`; a transient failure must
356
+ * surface so an alpha/preview invocation is never silently downgraded to
357
+ * production `main` docs on a flake (#1463).
358
+ */
359
+ export class ManifestNotFoundError extends Error {
360
+ constructor(message) {
361
+ super(message);
362
+ this.name = "ManifestNotFoundError";
363
+ }
364
+ }
365
+ async function fetchManifestForVersion(branch, version, forceRefresh = false) {
366
+ ensureCacheDir(branch, version);
120
367
  const meta = loadCacheMeta();
121
- const manifestPath = getManifestPath(version);
122
- const cacheExpired = isCacheExpired(meta.manifestFetchedAt?.[version]);
368
+ const manifestPath = getManifestPath(branch, version);
369
+ const cacheExpired = isCacheExpired(meta.manifestFetchedAt?.[branch]?.[version]);
123
370
  const hasCachedManifest = existsSync(manifestPath);
124
371
  // If cache is valid and not forcing refresh, use it
125
372
  if (!forceRefresh && !cacheExpired && hasCachedManifest) {
@@ -132,11 +379,11 @@ async function fetchManifestForVersion(version, forceRefresh = false) {
132
379
  }
133
380
  }
134
381
  // Try to fetch from network
135
- const manifestUrl = getManifestUrl(version);
382
+ const manifestUrl = buildManifestUrl(branch, version);
136
383
  try {
137
384
  const response = await fetchWithTimeout(manifestUrl);
138
385
  if (response.status === 404) {
139
- return null; // Version doesn't exist
386
+ return null; // Version (or branch) doesn't exist
140
387
  }
141
388
  if (!response.ok) {
142
389
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
@@ -145,7 +392,8 @@ async function fetchManifestForVersion(version, forceRefresh = false) {
145
392
  // Save to cache
146
393
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
147
394
  meta.manifestFetchedAt = meta.manifestFetchedAt || {};
148
- meta.manifestFetchedAt[version] = new Date().toISOString();
395
+ meta.manifestFetchedAt[branch] = meta.manifestFetchedAt[branch] || {};
396
+ meta.manifestFetchedAt[branch][version] = new Date().toISOString();
149
397
  saveCacheMeta(meta);
150
398
  return { manifest, fromCache: false, stale: false };
151
399
  }
@@ -163,15 +411,15 @@ async function fetchManifestForVersion(version, forceRefresh = false) {
163
411
  throw new Error(`Failed to fetch guides manifest: ${err.message}`);
164
412
  }
165
413
  }
166
- async function fetchManifest(versionInfo, forceRefresh = false) {
414
+ async function fetchManifest(branch, versionInfo, forceRefresh = false) {
167
415
  // Try the requested version first
168
- const result = await fetchManifestForVersion(versionInfo.version, forceRefresh);
416
+ const result = await fetchManifestForVersion(branch, versionInfo.version, forceRefresh);
169
417
  if (result) {
170
418
  return { ...result, versionInfo };
171
419
  }
172
420
  // Version not found, fall back to latest (unless already trying latest)
173
421
  if (versionInfo.version !== "latest") {
174
- const fallbackResult = await fetchManifestForVersion("latest", forceRefresh);
422
+ const fallbackResult = await fetchManifestForVersion(branch, "latest", forceRefresh);
175
423
  if (fallbackResult) {
176
424
  const fallbackInfo = {
177
425
  version: "latest",
@@ -182,30 +430,133 @@ async function fetchManifest(versionInfo, forceRefresh = false) {
182
430
  return { ...fallbackResult, versionInfo: fallbackInfo };
183
431
  }
184
432
  }
185
- throw new Error(`Failed to fetch guides manifest for ${versionInfo.version}`);
433
+ // Every candidate URL 404'd — the branch/version is genuinely absent. Signal
434
+ // this with a typed error so `resolveManifest` can tell it apart from a
435
+ // transport/server error (which arrives as a plain `Error` from
436
+ // `fetchManifestForVersion`) and only fall back to `main` in the former case.
437
+ throw new ManifestNotFoundError(`Failed to fetch guides manifest for ${versionInfo.version}`);
438
+ }
439
+ /**
440
+ * Fetch the manifest for `requestedBranch`, falling back to `main` **only when
441
+ * the non-`main` branch has no published manifest** — i.e. every candidate URL
442
+ * 404s and `fetchManifest` throws a `ManifestNotFoundError`. That fallback
443
+ * covers a `PRIMITIVE_GUIDES_BRANCH` override that names a nonexistent
444
+ * branch.
445
+ *
446
+ * A transport or server error (timeout, DNS, 5xx) on the requested branch is
447
+ * NOT a missing branch — it arrives as a plain `Error` and is re-thrown, never
448
+ * downgraded to `main`. Silently serving production `main` docs for an
449
+ * alpha/preview invocation because of a transient flake would be wrong: a
450
+ * preview run must fail loudly rather than quietly return the wrong branch's
451
+ * docs (#1463).
452
+ */
453
+ export async function resolveManifest(requestedBranch, versionInfo, forceRefresh = false) {
454
+ try {
455
+ const result = await fetchManifest(requestedBranch, versionInfo, forceRefresh);
456
+ return { ...result, branch: requestedBranch, requestedBranch, branchFellBack: false };
457
+ }
458
+ catch (err) {
459
+ // Only a genuinely-absent branch/version (a 404 on every candidate URL) may
460
+ // fall back to `main`. A transport/server error must surface — see the
461
+ // function doc.
462
+ if (!(err instanceof ManifestNotFoundError)) {
463
+ throw err;
464
+ }
465
+ if (requestedBranch === DEFAULT_DOCS_BRANCH) {
466
+ throw err; // already on main; nothing to fall back to
467
+ }
468
+ const result = await fetchManifest(DEFAULT_DOCS_BRANCH, versionInfo, forceRefresh);
469
+ return {
470
+ ...result,
471
+ branch: DEFAULT_DOCS_BRANCH,
472
+ requestedBranch,
473
+ branchFellBack: true,
474
+ };
475
+ }
186
476
  }
187
- async function fetchGuide(guide, version, forceRefresh = false) {
188
- ensureCacheDir(version);
477
+ /**
478
+ * One stderr line reporting the served docs branch, only when a non-`main`
479
+ * branch was requested (alpha detection or the override) — a plain `main`
480
+ * target prints nothing so stdout/stderr stay clean for the common case
481
+ * (#1463, behavior 8). Uses `info()` (stderr) so guide content on stdout is
482
+ * never polluted.
483
+ */
484
+ function reportDocsBranch(resolved) {
485
+ if (resolved.requestedBranch === DEFAULT_DOCS_BRANCH)
486
+ return;
487
+ if (resolved.branchFellBack) {
488
+ info(`Guides branch: ${resolved.requestedBranch} unavailable; using main`);
489
+ }
490
+ else {
491
+ info(`Guides branch: ${resolved.requestedBranch}`);
492
+ }
493
+ }
494
+ /**
495
+ * Internal error thrown by `fetchGuide` on a non-ok guide-file response. NOT
496
+ * exported. `status` lets the `get` action distinguish a 404 (the cached
497
+ * manifest may name a file that was renamed/removed upstream — heal by
498
+ * refetching the manifest) from a transport error (keep the stale-cache
499
+ * fallback). `staleContent` carries the on-disk copy for the failed filename,
500
+ * if any, so the action can serve it as the option-(b) last resort (#1034).
501
+ */
502
+ class GuideFetchError extends Error {
503
+ status;
504
+ selectedFile;
505
+ staleContent;
506
+ constructor(status, message, selectedFile, staleContent) {
507
+ super(message);
508
+ this.status = status;
509
+ this.selectedFile = selectedFile;
510
+ this.staleContent = staleContent;
511
+ this.name = "GuideFetchError";
512
+ }
513
+ }
514
+ /** Read an on-disk cached guide file, or undefined if absent/corrupted. */
515
+ function readCachedGuide(cachedPath) {
516
+ if (!existsSync(cachedPath))
517
+ return undefined;
518
+ try {
519
+ return readFileSync(cachedPath, "utf-8");
520
+ }
521
+ catch {
522
+ return undefined; // corrupted cache
523
+ }
524
+ }
525
+ async function fetchGuide(guide, branch, version, request = {}, forceRefresh = false) {
526
+ ensureCacheDir(branch, version);
189
527
  const meta = loadCacheMeta();
190
- const guideFileName = basename(guide.file);
191
- const cachedPath = join(getGuidesCacheDir(version), guideFileName);
192
- const fetchedAt = meta.guidesFetchedAt?.[version]?.[guide.topic];
528
+ // Resolve the variant filename for the requested (language, platform).
529
+ const { file: selectedFile } = selectVariant(guide, request);
530
+ const guideFileName = basename(selectedFile);
531
+ const cachedPath = join(getGuidesCacheDir(branch, version), guideFileName);
532
+ // Freshness is keyed by branch + the RESOLVED filename (not topic): a `ts`
533
+ // fetch cannot mark the `swift` variant fresh (#977), and a `next` fetch
534
+ // cannot mark the `main` copy of the same file fresh (#1463, behavior 7).
535
+ const fetchedAt = meta.guidesFetchedAt?.[branch]?.[version]?.[guideFileName];
193
536
  const cacheExpired = isCacheExpired(fetchedAt);
194
537
  const hasCachedGuide = existsSync(cachedPath);
195
538
  // If cache is valid and not forcing refresh, use it
196
539
  if (!forceRefresh && !cacheExpired && hasCachedGuide) {
197
540
  try {
198
541
  const content = readFileSync(cachedPath, "utf-8");
199
- return { content, fromCache: true, stale: false };
542
+ return { content, fromCache: true, stale: false, selectedFile };
200
543
  }
201
544
  catch {
202
545
  // Cache corrupted, will fetch fresh
203
546
  }
204
547
  }
205
548
  // Try to fetch from network
206
- const guideUrl = getGuideUrl(version, guide.file);
549
+ const guideUrl = buildGuideUrl(branch, version, selectedFile);
207
550
  try {
208
551
  const response = await fetchWithTimeout(guideUrl);
552
+ if (response.status === 404) {
553
+ // The cache-derived filename no longer exists upstream (a docs rename /
554
+ // removal). Let the 404 escape to the `get` action so it can refetch the
555
+ // manifest and re-resolve — heal must take priority over serving stale.
556
+ // Attach the on-disk copy (if any) so the action can serve it as the
557
+ // option-(b) last resort when the heal can't help (#1034).
558
+ throw new GuideFetchError(404, `Failed to fetch guide "${guide.topic}": HTTP 404: ${response.statusText}`, selectedFile, readCachedGuide(cachedPath));
559
+ }
209
560
  if (!response.ok) {
210
561
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
211
562
  }
@@ -213,40 +564,101 @@ async function fetchGuide(guide, version, forceRefresh = false) {
213
564
  // Save to cache
214
565
  writeFileSync(cachedPath, content);
215
566
  meta.guidesFetchedAt = meta.guidesFetchedAt || {};
216
- meta.guidesFetchedAt[version] = meta.guidesFetchedAt[version] || {};
217
- meta.guidesFetchedAt[version][guide.topic] = new Date().toISOString();
567
+ meta.guidesFetchedAt[branch] = meta.guidesFetchedAt[branch] || {};
568
+ meta.guidesFetchedAt[branch][version] = meta.guidesFetchedAt[branch][version] || {};
569
+ meta.guidesFetchedAt[branch][version][guideFileName] = new Date().toISOString();
218
570
  saveCacheMeta(meta);
219
- return { content, fromCache: false, stale: false };
571
+ return { content, fromCache: false, stale: false, selectedFile };
220
572
  }
221
573
  catch (err) {
222
- // Network failed, try stale cache
223
- if (hasCachedGuide) {
224
- try {
225
- const content = readFileSync(cachedPath, "utf-8");
226
- return { content, fromCache: true, stale: true };
227
- }
228
- catch {
229
- // Cache corrupted
230
- }
574
+ // A 404 must reach the action (heal takes priority over stale) — re-throw it
575
+ // unchanged rather than serving the stale copy here.
576
+ if (err instanceof GuideFetchError && err.status === 404) {
577
+ throw err;
578
+ }
579
+ // Non-404 (transport error / 5xx): serve the stale on-disk copy if present
580
+ // (Fork F — transport problems are not manifest skew), else throw.
581
+ const stale = readCachedGuide(cachedPath);
582
+ if (stale !== undefined) {
583
+ return { content: stale, fromCache: true, stale: true, selectedFile };
231
584
  }
232
585
  throw new Error(`Failed to fetch guide "${guide.topic}": ${err.message}`);
233
586
  }
234
587
  }
588
+ /** Normalize a guide's variants for display / --json output. */
589
+ function normalizeVariants(guide) {
590
+ return (guide.variants ?? []).map((v) => ({
591
+ ...(v.language !== undefined ? { language: v.language } : {}),
592
+ ...(v.platform !== undefined ? { platform: v.platform } : {}),
593
+ file: v.file,
594
+ }));
595
+ }
596
+ /**
597
+ * Compact rendering of the LANGUAGES a guide offers, for the `guides list`
598
+ * table (issue #1219 renamed the ambiguous `COMBINATIONS` column to
599
+ * `LANGUAGES`). Lists each variant's `language`, de-duplicated and order-
600
+ * preserving; a guide with no language-pinned variants renders `default`.
601
+ * Platforms are surfaced separately as a manifest-level legend, since they are
602
+ * a manifest-wide map rather than a per-guide attribute.
603
+ */
604
+ export function formatLanguages(guide) {
605
+ const variants = guide.variants ?? [];
606
+ if (variants.length === 0)
607
+ return "default";
608
+ const languages = variants
609
+ .map((v) => v.language)
610
+ .filter((l) => l !== undefined);
611
+ if (languages.length === 0)
612
+ return "default";
613
+ return [...new Set(languages)].join("; ");
614
+ }
615
+ /**
616
+ * Render the manifest-level platform→language legend for `guides list`, e.g.
617
+ * `web -> ts, ios -> swift, macos -> swift`. When the manifest has no
618
+ * `platforms` block (older manifests / stale cache, before
619
+ * Primitive-Labs/primitive-docs#194), platforms aren't modeled yet, so this
620
+ * reports `none defined yet` rather than implying a map that doesn't exist.
621
+ */
622
+ function formatPlatformsLegend(manifest) {
623
+ const platforms = manifest.platforms;
624
+ if (!platforms || Object.keys(platforms).length === 0) {
625
+ return "none defined yet";
626
+ }
627
+ return Object.entries(platforms)
628
+ .map(([platform, entry]) => `${platform} -> ${entry.language}`)
629
+ .join(", ");
630
+ }
235
631
  export function registerGuidesCommands(program) {
236
632
  const guides = program
237
633
  .command("guides")
238
634
  .description("Access Primitive how-to guides for building apps")
239
635
  .addHelpText("after", `
240
636
  Examples:
241
- $ primitive guides list # List available guides
242
- $ primitive guides list --json # List as JSON for programmatic use
243
- $ primitive guides get documents # Fetch and display the documents guide
244
- $ primitive guides get workflows # Fetch and display the workflows guide
245
- $ primitive guides list --version 1 # List guides for client v1
637
+ $ primitive guides list # List available guides + languages
638
+ $ primitive guides list --json # List as JSON for programmatic use
639
+ $ primitive guides get documents # Fetch the default (TS) documents guide
640
+ $ primitive guides get documents --language swift # Fetch the Swift variant
641
+ $ primitive guides get documents --platform ios # Fetch the platform's language (e.g. Swift)
642
+ $ primitive guides list --guide-version 1 # List guides for client v1
643
+ $ primitive guides list --guide-version latest # List guides for the latest line
644
+
645
+ Language & platform:
646
+ --language <ts|swift|...> fetches a specific language variant of a guide.
647
+ Aliases: typescript/javascript/js -> ts.
648
+ --platform <web|ios|macos|...> selects the language a platform targets: the
649
+ guides manifest maps each platform to its language (e.g. ios -> swift), so
650
+ 'guides get documents --platform ios' returns the Swift guide. An explicit
651
+ --language always wins the language dimension.
652
+ Unknown --language values (always), and unknown --platform values (once the
653
+ manifest publishes its platform map), are rejected with a clear error listing
654
+ the supported values — mirroring 'init --platform'. Run 'guides list' to see
655
+ the supported languages and platforms.
246
656
 
247
657
  Versioning:
248
658
  By default, guides are fetched for the detected ${CLIENT_PACKAGE_NAME} version.
249
- Use --version to explicitly request a specific major version.
659
+ Use --guide-version to explicitly request a specific major version (e.g. 1, 2,
660
+ or 'latest'). (The flag is --guide-version, not --version, because the root
661
+ 'primitive --version' prints the CLI version and would otherwise swallow it.)
250
662
  Falls back to 'latest' if the version is not found.
251
663
 
252
664
  Cache:
@@ -259,19 +671,38 @@ Cache:
259
671
  .description("List available guides")
260
672
  .option("--json", "Output as JSON")
261
673
  .option("--refresh", "Force refresh from network")
262
- .option("--version <version>", "Fetch guides for a specific major version (e.g., 1, 2, or 'latest')")
674
+ .option("--guide-version <version>", "Fetch guides for a specific major version (e.g., 1, 2, or 'latest')")
675
+ .option("--language <lang>", "Language to validate (ts, swift, ...; informational on list)")
676
+ .option("--platform <platform>", "Platform to validate (web, ios, macos, ...; informational on list)")
263
677
  .action(async (options) => {
264
678
  try {
265
- const requestedVersion = resolveVersion(options.version);
266
- const { manifest, stale, versionInfo } = await fetchManifest(requestedVersion, options.refresh);
679
+ const requestedVersion = resolveVersion(options.guideVersion);
680
+ const requestedBranch = resolveDocsBranchForInvocation();
681
+ const resolved = await resolveManifest(requestedBranch, requestedVersion, options.refresh);
682
+ const { manifest, stale, versionInfo, branch } = resolved;
683
+ // Validate the requested dimensions against the loaded manifest (same
684
+ // rules as `get`, for one mental model — Q3). They have no per-row
685
+ // effect on `list`, but an unknown value should fail loudly here too.
686
+ const resolution = validateAndResolveRequest(manifest, options.language, options.platform);
687
+ if (resolution.error) {
688
+ error(resolution.error);
689
+ process.exit(1);
690
+ }
691
+ const reqLanguage = normalizeLanguage(options.language);
692
+ const reqPlatform = normalizePlatform(options.platform);
693
+ const supportedLanguages = [...deriveLanguages(manifest)].sort();
694
+ // Report the served docs branch (stderr) when alpha/override is involved.
695
+ reportDocsBranch(resolved);
267
696
  if (stale) {
268
697
  warn("Using stale cache (network unavailable)");
269
698
  }
270
699
  if (options.json) {
700
+ // The one list envelope (#3646): the guides are `items`, and the
701
+ // resolution context a caller needs to interpret them — which
702
+ // version and branch answered, which dimensions were asked for —
703
+ // sits beside the envelope's three keys.
271
704
  json({
272
- version: versionInfo.version,
273
- versionSource: versionInfo.source,
274
- guides: manifest.guides.map((g) => ({
705
+ ...wholeListEnvelope(manifest.guides.map((g) => ({
275
706
  topic: g.topic,
276
707
  description: g.description,
277
708
  keywords: g.keywords,
@@ -279,7 +710,16 @@ Cache:
279
710
  concepts: g.concepts,
280
711
  prerequisites: g.prerequisites,
281
712
  relatedGuides: g.relatedGuides,
282
- })),
713
+ availableVariants: normalizeVariants(g),
714
+ }))),
715
+ version: versionInfo.version,
716
+ versionSource: versionInfo.source,
717
+ guidesBranch: branch,
718
+ defaults: manifest.defaults ?? null,
719
+ platforms: manifest.platforms ?? null,
720
+ supportedLanguages,
721
+ language: reqLanguage ?? null,
722
+ platform: reqPlatform ?? null,
283
723
  });
284
724
  return;
285
725
  }
@@ -289,14 +729,41 @@ Cache:
289
729
  }
290
730
  keyValue("Client version", formatClientVersion(versionInfo));
291
731
  keyValue("Guides version", formatGuidesVersion(versionInfo));
732
+ // Manifest-level legend: spell out languages and the platform→language
733
+ // map explicitly, so `--language`/`--platform` are no longer ambiguous.
734
+ keyValue("Languages", supportedLanguages.join(", "));
735
+ keyValue("Platforms", formatPlatformsLegend(manifest));
292
736
  console.log("");
293
- console.log(formatTable(manifest.guides, [
737
+ const rows = manifest.guides.map((g) => ({
738
+ topic: g.topic,
739
+ description: g.description,
740
+ languages: formatLanguages(g),
741
+ }));
742
+ // Size DESCRIPTION from the live terminal width so wide terminals show
743
+ // the full description (the guides.json contract budgets descriptions
744
+ // at <=100 chars; DESCRIPTION_MAX mirrors that cap — see
745
+ // primitive-docs `scripts/sync-guides-json.mjs`) while the table never
746
+ // wraps. Floor 48 keeps today's readable width on an 80-col terminal;
747
+ // on a non-TTY (piped) the column is sized to the cap so full
748
+ // descriptions reach `grep`/agents. `truncate` defensively clips a
749
+ // description that violates the upstream contract.
750
+ const DESCRIPTION_MAX = 100;
751
+ console.log(formatTable(rows, [
294
752
  { header: "TOPIC", key: "topic" },
295
- { header: "DESCRIPTION", key: "description", width: 60 },
753
+ {
754
+ header: "DESCRIPTION",
755
+ key: "description",
756
+ flex: true,
757
+ truncate: true,
758
+ flexFloor: 48,
759
+ flexMax: DESCRIPTION_MAX,
760
+ },
761
+ { header: "LANGUAGES", key: "languages" },
296
762
  ]));
297
763
  console.log("");
298
- keyValue("Cache location", getVersionCacheDir(versionInfo.version));
299
- info("Use 'primitive guides get <topic>' to fetch a guide.");
764
+ keyValue("Cache location", getVersionCacheDir(branch, versionInfo.version));
765
+ info("Use 'primitive guides get <topic> --language <ts|swift>' to fetch a guide.");
766
+ info("Example: primitive guides get documents --language swift");
300
767
  }
301
768
  catch (err) {
302
769
  error(err.message);
@@ -310,22 +777,50 @@ Cache:
310
777
  .argument("<topic>", "Guide topic (e.g., documents, workflows, prompts)")
311
778
  .option("--json", "Output metadata as JSON instead of content")
312
779
  .option("--refresh", "Force refresh from network")
313
- .option("--version <version>", "Fetch guides for a specific major version (e.g., 1, 2, or 'latest')")
780
+ .option("--guide-version <version>", "Fetch guides for a specific major version (e.g., 1, 2, or 'latest')")
781
+ .option("--language <lang>", "Language variant to fetch (ts, swift, ...; aliases typescript/javascript/js -> ts)")
782
+ .option("--platform <platform>", "Platform whose language to fetch (web, ios, macos, ...; inferred from the manifest)")
314
783
  .action(async (topic, options) => {
315
784
  try {
316
- const requestedVersion = resolveVersion(options.version);
317
- const { manifest, stale: manifestStale, versionInfo } = await fetchManifest(requestedVersion, options.refresh);
785
+ const requestedVersion = resolveVersion(options.guideVersion);
786
+ const requestedBranch = resolveDocsBranchForInvocation();
787
+ const resolved = await resolveManifest(requestedBranch, requestedVersion, options.refresh);
788
+ const { manifest, fromCache, stale: manifestStale, versionInfo, branch } = resolved;
318
789
  const guide = manifest.guides.find((g) => g.topic.toLowerCase() === topic.toLowerCase());
319
790
  if (!guide) {
320
791
  const availableTopics = manifest.guides.map((g) => g.topic).join(", ");
321
792
  error(`Guide "${topic}" not found. Available topics: ${availableTopics}`);
322
793
  process.exit(1);
323
794
  }
795
+ // Validate both flags against the loaded manifest and resolve the
796
+ // effective language (issue #1219). An unknown value is a hard error;
797
+ // `--platform ios` infers its language (e.g. swift) when the manifest
798
+ // publishes a `platforms` block, while an explicit `--language` wins.
799
+ const resolution = validateAndResolveRequest(manifest, options.language, options.platform);
800
+ if (resolution.error) {
801
+ error(resolution.error);
802
+ process.exit(1);
803
+ }
804
+ // `reqLanguage` is the EFFECTIVE language after platform inference; pass
805
+ // it to both the JSON selection and the content fetch so they can't
806
+ // desynchronize.
807
+ const reqLanguage = resolution.language;
808
+ const reqPlatform = resolution.platform;
809
+ const requestedAnyDimension = normalizeLanguage(options.language) !== undefined ||
810
+ normalizePlatform(options.platform) !== undefined;
811
+ const selection = selectVariant(guide, {
812
+ language: reqLanguage,
813
+ platform: reqPlatform,
814
+ });
815
+ // Report the served docs branch (stderr) when alpha/override is involved.
816
+ reportDocsBranch(resolved);
324
817
  if (options.json) {
325
- // Output just metadata
818
+ // Output just metadata — does NOT fetch content. `selectedFile` is the
819
+ // file that WOULD be served for this request (see selection above).
326
820
  json({
327
821
  version: versionInfo.version,
328
822
  versionSource: versionInfo.source,
823
+ guidesBranch: branch,
329
824
  topic: guide.topic,
330
825
  description: guide.description,
331
826
  keywords: guide.keywords,
@@ -333,16 +828,79 @@ Cache:
333
828
  concepts: guide.concepts,
334
829
  prerequisites: guide.prerequisites,
335
830
  relatedGuides: guide.relatedGuides,
336
- cacheLocation: join(getGuidesCacheDir(versionInfo.version), basename(guide.file)),
831
+ language: reqLanguage ?? null,
832
+ platform: reqPlatform ?? null,
833
+ defaultPlatform: manifest.defaults?.platform ?? null,
834
+ selectedFile: selection.file,
835
+ matchedOn: selection.matchedOn,
836
+ availableVariants: normalizeVariants(guide),
837
+ cacheLocation: join(getGuidesCacheDir(branch, versionInfo.version), basename(selection.file)),
337
838
  });
338
839
  return;
339
840
  }
340
- const { content, stale: guideStale } = await fetchGuide(guide, versionInfo.version, options.refresh);
341
- if (manifestStale || guideStale) {
841
+ const request = { language: reqLanguage, platform: reqPlatform };
842
+ let result;
843
+ try {
844
+ result = await fetchGuide(guide, branch, versionInfo.version, request, options.refresh);
845
+ }
846
+ catch (err) {
847
+ // Only a 404 is healable here. Transport errors already served stale
848
+ // (or threw) inside fetchGuide; rethrow anything that isn't a 404.
849
+ if (!(err instanceof GuideFetchError && err.status === 404)) {
850
+ throw err;
851
+ }
852
+ // Heal only when the manifest came from cache — then the named file
853
+ // may have been renamed/removed upstream while our manifest is stale.
854
+ // Force a manifest refetch, re-find the topic, and retry the file
855
+ // ONCE. A fresh-from-network manifest (incl. --refresh) makes the 404
856
+ // authoritative, so we skip straight to the last resort below (#1034).
857
+ if (fromCache) {
858
+ try {
859
+ // Heal on the SAME branch the manifest resolved on, so the
860
+ // manifest and guide never disagree (#1463, behavior 4).
861
+ const refreshed = await fetchManifest(branch, requestedVersion, /* forceRefresh */ true);
862
+ const freshGuide = refreshed.manifest.guides.find((g) => g.topic.toLowerCase() === topic.toLowerCase());
863
+ if (freshGuide) {
864
+ // Re-calling fetchGuide recomputes selectVariant + the cache path
865
+ // for the (possibly new) filename — no extra resolve site. If the
866
+ // filename is unchanged it just 404s again and falls through.
867
+ const retried = await fetchGuide(freshGuide, branch, refreshed.versionInfo.version, request, options.refresh);
868
+ if (basename(retried.selectedFile) !== basename(err.selectedFile)) {
869
+ warn(`Guide "${topic}" file moved upstream; refreshed manifest and retried.`);
870
+ }
871
+ result = retried;
872
+ }
873
+ }
874
+ catch {
875
+ // Heal failed (manifest refetch failed / retry 404'd / topic gone)
876
+ // → fall through to the option-(b) last resort on the ORIGINAL error.
877
+ }
878
+ }
879
+ // Option (b) last resort — applies to ALL 404s, not just cached-manifest
880
+ // ones, so `--refresh`-on-404 still serves an old on-disk copy as it
881
+ // does today. Serve the old copy if we have one; otherwise surface the
882
+ // original terminal 404.
883
+ if (!result) {
884
+ if (err.staleContent !== undefined) {
885
+ result = { content: err.staleContent, stale: true, selectedFile: err.selectedFile };
886
+ }
887
+ else {
888
+ throw err;
889
+ }
890
+ }
891
+ }
892
+ if (manifestStale || result.stale) {
342
893
  warn("Using stale cache (network unavailable)");
343
894
  }
895
+ // Transparency note (issue #1219): a language/platform was requested but
896
+ // this topic has no variant pinning it, so the default file was served.
897
+ // This closes the residual silent-fallback footgun beyond `--platform`.
898
+ // The note goes to STDERR (via `info`) so stdout stays clean for piping.
899
+ if (requestedAnyDimension && selection.matchedOn === "default") {
900
+ info(`No ${reqLanguage ?? reqPlatform} variant for "${guide.topic}"; served the default guide.`);
901
+ }
344
902
  // Print the guide content directly to stdout
345
- console.log(content);
903
+ console.log(result.content);
346
904
  }
347
905
  catch (err) {
348
906
  error(err.message);