primitive-admin 1.0.62 → 1.0.64

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 (344) hide show
  1. package/README.md +132 -42
  2. package/assets/skill/skills/primitive-platform/SKILL.md +142 -661
  3. package/dist/bin/primitive.js +121 -41
  4. package/dist/bin/primitive.js.map +1 -1
  5. package/dist/src/commands/admins.js +37 -50
  6. package/dist/src/commands/admins.js.map +1 -1
  7. package/dist/src/commands/analytics.js +178 -9
  8. package/dist/src/commands/analytics.js.map +1 -1
  9. package/dist/src/commands/apps.js +17 -7
  10. package/dist/src/commands/apps.js.map +1 -1
  11. package/dist/src/commands/auth-sessions.d.ts +7 -0
  12. package/dist/src/commands/auth-sessions.js +144 -0
  13. package/dist/src/commands/auth-sessions.js.map +1 -0
  14. package/dist/src/commands/auth.js +76 -116
  15. package/dist/src/commands/auth.js.map +1 -1
  16. package/dist/src/commands/blob-buckets.js +2 -1
  17. package/dist/src/commands/blob-buckets.js.map +1 -1
  18. package/dist/src/commands/catalog.js +26 -10
  19. package/dist/src/commands/catalog.js.map +1 -1
  20. package/dist/src/commands/collection-type-configs.js +3 -10
  21. package/dist/src/commands/collection-type-configs.js.map +1 -1
  22. package/dist/src/commands/collections.js +736 -19
  23. package/dist/src/commands/collections.js.map +1 -1
  24. package/dist/src/commands/config.js +13 -27
  25. package/dist/src/commands/config.js.map +1 -1
  26. package/dist/src/commands/connections.js +8 -9
  27. package/dist/src/commands/connections.js.map +1 -1
  28. package/dist/src/commands/cron-triggers.js +2 -1
  29. package/dist/src/commands/cron-triggers.js.map +1 -1
  30. package/dist/src/commands/database-type-configs.js +5 -12
  31. package/dist/src/commands/database-type-configs.js.map +1 -1
  32. package/dist/src/commands/databases.js +59 -72
  33. package/dist/src/commands/databases.js.map +1 -1
  34. package/dist/src/commands/documents.d.ts +58 -0
  35. package/dist/src/commands/documents.js +1623 -95
  36. package/dist/src/commands/documents.js.map +1 -1
  37. package/dist/src/commands/email-templates.js +5 -4
  38. package/dist/src/commands/email-templates.js.map +1 -1
  39. package/dist/src/commands/env.d.ts +1 -1
  40. package/dist/src/commands/env.js +106 -44
  41. package/dist/src/commands/env.js.map +1 -1
  42. package/dist/src/commands/feature-flags.js +2 -1
  43. package/dist/src/commands/feature-flags.js.map +1 -1
  44. package/dist/src/commands/functions.d.ts +20 -0
  45. package/dist/src/commands/functions.js +1630 -0
  46. package/dist/src/commands/functions.js.map +1 -0
  47. package/dist/src/commands/group-type-configs.js +2 -1
  48. package/dist/src/commands/group-type-configs.js.map +1 -1
  49. package/dist/src/commands/groups.js +30 -17
  50. package/dist/src/commands/groups.js.map +1 -1
  51. package/dist/src/commands/guides.js +17 -11
  52. package/dist/src/commands/guides.js.map +1 -1
  53. package/dist/src/commands/init.d.ts +24 -12
  54. package/dist/src/commands/init.js +198 -110
  55. package/dist/src/commands/init.js.map +1 -1
  56. package/dist/src/commands/integrations.js +65 -26
  57. package/dist/src/commands/integrations.js.map +1 -1
  58. package/dist/src/commands/locks.js +17 -2
  59. package/dist/src/commands/locks.js.map +1 -1
  60. package/dist/src/commands/metadata-category-configs.js +2 -1
  61. package/dist/src/commands/metadata-category-configs.js.map +1 -1
  62. package/dist/src/commands/metadata.js +8 -1
  63. package/dist/src/commands/metadata.js.map +1 -1
  64. package/dist/src/commands/prompts.js +106 -59
  65. package/dist/src/commands/prompts.js.map +1 -1
  66. package/dist/src/commands/rule-sets.js +3 -10
  67. package/dist/src/commands/rule-sets.js.map +1 -1
  68. package/dist/src/commands/scripts.d.ts +10 -0
  69. package/dist/src/commands/scripts.js +159 -25
  70. package/dist/src/commands/scripts.js.map +1 -1
  71. package/dist/src/commands/secrets.js +2 -1
  72. package/dist/src/commands/secrets.js.map +1 -1
  73. package/dist/src/commands/sessions.js +10 -9
  74. package/dist/src/commands/sessions.js.map +1 -1
  75. package/dist/src/commands/sync-app-settings.js +3 -5
  76. package/dist/src/commands/sync-app-settings.js.map +1 -1
  77. package/dist/src/commands/sync.d.ts +391 -44
  78. package/dist/src/commands/sync.js +2622 -534
  79. package/dist/src/commands/sync.js.map +1 -1
  80. package/dist/src/commands/tokens.js +2 -1
  81. package/dist/src/commands/tokens.js.map +1 -1
  82. package/dist/src/commands/users.js +19 -10
  83. package/dist/src/commands/users.js.map +1 -1
  84. package/dist/src/commands/vars.js +2 -1
  85. package/dist/src/commands/vars.js.map +1 -1
  86. package/dist/src/commands/waitlist.js +2 -1
  87. package/dist/src/commands/waitlist.js.map +1 -1
  88. package/dist/src/commands/webhooks.d.ts +29 -0
  89. package/dist/src/commands/webhooks.js +135 -64
  90. package/dist/src/commands/webhooks.js.map +1 -1
  91. package/dist/src/commands/workflows.d.ts +1 -29
  92. package/dist/src/commands/workflows.js +113 -179
  93. package/dist/src/commands/workflows.js.map +1 -1
  94. package/dist/src/lib/api-client.d.ts +628 -14
  95. package/dist/src/lib/api-client.js +776 -70
  96. package/dist/src/lib/api-client.js.map +1 -1
  97. package/dist/src/lib/app-settings-descriptor.d.ts +1 -1
  98. package/dist/src/lib/app-settings-descriptor.js +9 -1
  99. package/dist/src/lib/app-settings-descriptor.js.map +1 -1
  100. package/dist/src/lib/block-selector.d.ts +58 -0
  101. package/dist/src/lib/block-selector.js +92 -0
  102. package/dist/src/lib/block-selector.js.map +1 -0
  103. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +24 -54
  104. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +31 -153
  105. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -1
  106. package/dist/src/lib/collection-export.d.ts +184 -0
  107. package/dist/src/lib/collection-export.js +252 -0
  108. package/dist/src/lib/collection-export.js.map +1 -0
  109. package/dist/src/lib/config-json-field.d.ts +28 -0
  110. package/dist/src/lib/config-json-field.js +56 -0
  111. package/dist/src/lib/config-json-field.js.map +1 -0
  112. package/dist/src/lib/config-object-descriptor.js +88 -6
  113. package/dist/src/lib/config-object-descriptor.js.map +1 -1
  114. package/dist/src/lib/config-payload.d.ts +8 -1
  115. package/dist/src/lib/config-payload.js +49 -4
  116. package/dist/src/lib/config-payload.js.map +1 -1
  117. package/dist/src/lib/config-surface.d.ts +12 -1
  118. package/dist/src/lib/config-surface.js +71 -3
  119. package/dist/src/lib/config-surface.js.map +1 -1
  120. package/dist/src/lib/config.d.ts +31 -13
  121. package/dist/src/lib/config.js +34 -70
  122. package/dist/src/lib/config.js.map +1 -1
  123. package/dist/src/lib/credentials-store.d.ts +37 -22
  124. package/dist/src/lib/credentials-store.js +103 -97
  125. package/dist/src/lib/credentials-store.js.map +1 -1
  126. package/dist/src/lib/db-codegen/dbTypeIR.js +8 -0
  127. package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -1
  128. package/dist/src/lib/deprecation.d.ts +22 -0
  129. package/dist/src/lib/deprecation.js +43 -0
  130. package/dist/src/lib/deprecation.js.map +1 -0
  131. package/dist/src/lib/document-export-permissions.d.ts +30 -0
  132. package/dist/src/lib/document-export-permissions.js +54 -0
  133. package/dist/src/lib/document-export-permissions.js.map +1 -0
  134. package/dist/src/lib/document-ingest-artifact.d.ts +120 -0
  135. package/dist/src/lib/document-ingest-artifact.js +505 -0
  136. package/dist/src/lib/document-ingest-artifact.js.map +1 -0
  137. package/dist/src/lib/document-ingest-input.d.ts +51 -0
  138. package/dist/src/lib/document-ingest-input.js +132 -0
  139. package/dist/src/lib/document-ingest-input.js.map +1 -0
  140. package/dist/src/lib/document-ingest-rows.d.ts +137 -0
  141. package/dist/src/lib/document-ingest-rows.js +181 -0
  142. package/dist/src/lib/document-ingest-rows.js.map +1 -0
  143. package/dist/src/lib/document-ingest.d.ts +101 -0
  144. package/dist/src/lib/document-ingest.js +384 -0
  145. package/dist/src/lib/document-ingest.js.map +1 -0
  146. package/dist/src/lib/env-resolver-core.d.ts +128 -17
  147. package/dist/src/lib/env-resolver-core.js +204 -22
  148. package/dist/src/lib/env-resolver-core.js.map +1 -1
  149. package/dist/src/lib/env-resolver.d.ts +18 -3
  150. package/dist/src/lib/env-resolver.js +23 -3
  151. package/dist/src/lib/env-resolver.js.map +1 -1
  152. package/dist/src/lib/function-bundle.d.ts +147 -0
  153. package/dist/src/lib/function-bundle.js +341 -0
  154. package/dist/src/lib/function-bundle.js.map +1 -0
  155. package/dist/src/lib/function-collect.d.ts +123 -0
  156. package/dist/src/lib/function-collect.js +610 -0
  157. package/dist/src/lib/function-collect.js.map +1 -0
  158. package/dist/src/lib/function-db-types.d.ts +202 -0
  159. package/dist/src/lib/function-db-types.js +870 -0
  160. package/dist/src/lib/function-db-types.js.map +1 -0
  161. package/dist/src/lib/function-document-types.d.ts +144 -0
  162. package/dist/src/lib/function-document-types.js +370 -0
  163. package/dist/src/lib/function-document-types.js.map +1 -0
  164. package/dist/src/lib/function-grants-preflight.d.ts +64 -0
  165. package/dist/src/lib/function-grants-preflight.js +105 -0
  166. package/dist/src/lib/function-grants-preflight.js.map +1 -0
  167. package/dist/src/lib/function-log-lines.d.ts +76 -0
  168. package/dist/src/lib/function-log-lines.js +160 -0
  169. package/dist/src/lib/function-log-lines.js.map +1 -0
  170. package/dist/src/lib/function-log-row.d.ts +29 -0
  171. package/dist/src/lib/function-log-row.js +73 -0
  172. package/dist/src/lib/function-log-row.js.map +1 -0
  173. package/dist/src/lib/function-log-tail.d.ts +132 -0
  174. package/dist/src/lib/function-log-tail.js +262 -0
  175. package/dist/src/lib/function-log-tail.js.map +1 -0
  176. package/dist/src/lib/function-run.d.ts +289 -0
  177. package/dist/src/lib/function-run.js +389 -0
  178. package/dist/src/lib/function-run.js.map +1 -0
  179. package/dist/src/lib/function-schema-codegen.d.ts +143 -0
  180. package/dist/src/lib/function-schema-codegen.js +420 -0
  181. package/dist/src/lib/function-schema-codegen.js.map +1 -0
  182. package/dist/src/lib/function-sync.d.ts +459 -0
  183. package/dist/src/lib/function-sync.js +1258 -0
  184. package/dist/src/lib/function-sync.js.map +1 -0
  185. package/dist/src/lib/function-trigger-listing.d.ts +27 -0
  186. package/dist/src/lib/function-trigger-listing.js +70 -0
  187. package/dist/src/lib/function-trigger-listing.js.map +1 -0
  188. package/dist/src/lib/function-typecheck.d.ts +86 -0
  189. package/dist/src/lib/function-typecheck.js +370 -0
  190. package/dist/src/lib/function-typecheck.js.map +1 -0
  191. package/dist/src/lib/function-versions.d.ts +122 -0
  192. package/dist/src/lib/function-versions.js +182 -0
  193. package/dist/src/lib/function-versions.js.map +1 -0
  194. package/dist/src/lib/generated-allowlist.js +4 -0
  195. package/dist/src/lib/generated-allowlist.js.map +1 -1
  196. package/dist/src/lib/generated-config-surfaces.d.ts +2332 -78
  197. package/dist/src/lib/generated-config-surfaces.js +6979 -468
  198. package/dist/src/lib/generated-config-surfaces.js.map +1 -1
  199. package/dist/src/lib/generated-sdk-types.d.ts +12 -0
  200. package/dist/src/lib/generated-sdk-types.js +13 -0
  201. package/dist/src/lib/generated-sdk-types.js.map +1 -0
  202. package/dist/src/lib/init-adopt.js +2 -2
  203. package/dist/src/lib/init-adopt.js.map +1 -1
  204. package/dist/src/lib/init-assets.js +3 -3
  205. package/dist/src/lib/init-client-platforms.d.ts +14 -0
  206. package/dist/src/lib/init-client-platforms.js +71 -0
  207. package/dist/src/lib/init-client-platforms.js.map +1 -0
  208. package/dist/src/lib/init-config.d.ts +1 -1
  209. package/dist/src/lib/init-email-redirect-uris.d.ts +37 -0
  210. package/dist/src/lib/init-email-redirect-uris.js +46 -0
  211. package/dist/src/lib/init-email-redirect-uris.js.map +1 -0
  212. package/dist/src/lib/init-ios-links.d.ts +41 -0
  213. package/dist/src/lib/init-ios-links.js +67 -1
  214. package/dist/src/lib/init-ios-links.js.map +1 -1
  215. package/dist/src/lib/init-plan.d.ts +1 -1
  216. package/dist/src/lib/init-plan.js +1 -1
  217. package/dist/src/lib/init-xcode.d.ts +6 -5
  218. package/dist/src/lib/init-xcode.js +34 -10
  219. package/dist/src/lib/init-xcode.js.map +1 -1
  220. package/dist/src/lib/integration-selector.d.ts +42 -0
  221. package/dist/src/lib/integration-selector.js +46 -0
  222. package/dist/src/lib/integration-selector.js.map +1 -0
  223. package/dist/src/lib/ios-app-id.d.ts +34 -0
  224. package/dist/src/lib/ios-app-id.js +69 -0
  225. package/dist/src/lib/ios-app-id.js.map +1 -0
  226. package/dist/src/lib/list-options.d.ts +68 -0
  227. package/dist/src/lib/list-options.js +89 -0
  228. package/dist/src/lib/list-options.js.map +1 -0
  229. package/dist/src/lib/local-state.d.ts +1 -1
  230. package/dist/src/lib/local-state.js +1 -1
  231. package/dist/src/lib/local-test-cases.d.ts +63 -0
  232. package/dist/src/lib/local-test-cases.js +136 -0
  233. package/dist/src/lib/local-test-cases.js.map +1 -0
  234. package/dist/src/lib/log-inspection.d.ts +152 -5
  235. package/dist/src/lib/log-inspection.js +179 -2
  236. package/dist/src/lib/log-inspection.js.map +1 -1
  237. package/dist/src/lib/logout-admin-session.d.ts +33 -0
  238. package/dist/src/lib/logout-admin-session.js +70 -0
  239. package/dist/src/lib/logout-admin-session.js.map +1 -0
  240. package/dist/src/lib/output.d.ts +15 -0
  241. package/dist/src/lib/output.js +28 -0
  242. package/dist/src/lib/output.js.map +1 -1
  243. package/dist/src/lib/paginate.d.ts +15 -0
  244. package/dist/src/lib/paginate.js +17 -0
  245. package/dist/src/lib/paginate.js.map +1 -1
  246. package/dist/src/lib/project-config.d.ts +46 -21
  247. package/dist/src/lib/project-config.js +74 -47
  248. package/dist/src/lib/project-config.js.map +1 -1
  249. package/dist/src/lib/prompt-cost-format.d.ts +11 -0
  250. package/dist/src/lib/prompt-cost-format.js +41 -0
  251. package/dist/src/lib/prompt-cost-format.js.map +1 -0
  252. package/dist/src/lib/prompt-schema-codegen.d.ts +147 -0
  253. package/dist/src/lib/prompt-schema-codegen.js +462 -0
  254. package/dist/src/lib/prompt-schema-codegen.js.map +1 -0
  255. package/dist/src/lib/refresh-admin-credentials.d.ts +9 -1
  256. package/dist/src/lib/refresh-admin-credentials.js +22 -2
  257. package/dist/src/lib/refresh-admin-credentials.js.map +1 -1
  258. package/dist/src/lib/resolve-owner.d.ts +19 -0
  259. package/dist/src/lib/resolve-owner.js +20 -0
  260. package/dist/src/lib/resolve-owner.js.map +1 -0
  261. package/dist/src/lib/skill-installer.d.ts +49 -3
  262. package/dist/src/lib/skill-installer.js +275 -100
  263. package/dist/src/lib/skill-installer.js.map +1 -1
  264. package/dist/src/lib/snapshot-audit-source.d.ts +45 -0
  265. package/dist/src/lib/snapshot-audit-source.js +58 -0
  266. package/dist/src/lib/snapshot-audit-source.js.map +1 -0
  267. package/dist/src/lib/snapshot-audit-store.d.ts +52 -0
  268. package/dist/src/lib/snapshot-audit-store.js +196 -0
  269. package/dist/src/lib/snapshot-audit-store.js.map +1 -0
  270. package/dist/src/lib/snapshot-audit.d.ts +207 -0
  271. package/dist/src/lib/snapshot-audit.js +431 -0
  272. package/dist/src/lib/snapshot-audit.js.map +1 -0
  273. package/dist/src/lib/snapshot-build-rows.d.ts +60 -0
  274. package/dist/src/lib/snapshot-build-rows.js +87 -0
  275. package/dist/src/lib/snapshot-build-rows.js.map +1 -0
  276. package/dist/src/lib/snapshot-build.d.ts +50 -0
  277. package/dist/src/lib/snapshot-build.js +111 -0
  278. package/dist/src/lib/snapshot-build.js.map +1 -0
  279. package/dist/src/lib/snapshot-manifest-layout.d.ts +61 -0
  280. package/dist/src/lib/snapshot-manifest-layout.js +70 -0
  281. package/dist/src/lib/snapshot-manifest-layout.js.map +1 -0
  282. package/dist/src/lib/snapshots.d.ts +8 -9
  283. package/dist/src/lib/snapshots.js +20 -83
  284. package/dist/src/lib/snapshots.js.map +1 -1
  285. package/dist/src/lib/step-run-table.d.ts +43 -0
  286. package/dist/src/lib/step-run-table.js +129 -0
  287. package/dist/src/lib/step-run-table.js.map +1 -0
  288. package/dist/src/lib/storage-pending-retry.d.ts +26 -0
  289. package/dist/src/lib/storage-pending-retry.js +42 -0
  290. package/dist/src/lib/storage-pending-retry.js.map +1 -0
  291. package/dist/src/lib/swift-codegen/agentGenerator.d.ts +42 -0
  292. package/dist/src/lib/swift-codegen/agentGenerator.js +118 -0
  293. package/dist/src/lib/swift-codegen/agentGenerator.js.map +1 -0
  294. package/dist/src/lib/swift-codegen/banners.d.ts +24 -0
  295. package/dist/src/lib/swift-codegen/banners.js +25 -0
  296. package/dist/src/lib/swift-codegen/banners.js.map +1 -0
  297. package/dist/src/lib/swift-codegen/dbGenerator.js +19 -7
  298. package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -1
  299. package/dist/src/lib/swift-codegen/functionGenerator.d.ts +139 -0
  300. package/dist/src/lib/swift-codegen/functionGenerator.js +462 -0
  301. package/dist/src/lib/swift-codegen/functionGenerator.js.map +1 -0
  302. package/dist/src/lib/swift-codegen/generator.d.ts +7 -1
  303. package/dist/src/lib/swift-codegen/generator.js +28 -11
  304. package/dist/src/lib/swift-codegen/generator.js.map +1 -1
  305. package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +15 -0
  306. package/dist/src/lib/swift-codegen/schemaToSwift.js +21 -4
  307. package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -1
  308. package/dist/src/lib/sync-paths.d.ts +72 -55
  309. package/dist/src/lib/sync-paths.js +97 -100
  310. package/dist/src/lib/sync-paths.js.map +1 -1
  311. package/dist/src/lib/sync-resource-types.d.ts +23 -4
  312. package/dist/src/lib/sync-resource-types.js +106 -8
  313. package/dist/src/lib/sync-resource-types.js.map +1 -1
  314. package/dist/src/lib/sync-selectors.d.ts +45 -2
  315. package/dist/src/lib/sync-selectors.js +66 -5
  316. package/dist/src/lib/sync-selectors.js.map +1 -1
  317. package/dist/src/lib/template.d.ts +2 -2
  318. package/dist/src/lib/template.js +2 -2
  319. package/dist/src/lib/test-case-file-names.d.ts +40 -0
  320. package/dist/src/lib/test-case-file-names.js +91 -0
  321. package/dist/src/lib/test-case-file-names.js.map +1 -0
  322. package/dist/src/lib/test-case-keys.d.ts +1 -1
  323. package/dist/src/lib/test-case-keys.js +1 -1
  324. package/dist/src/lib/test-case-variables.d.ts +14 -0
  325. package/dist/src/lib/test-case-variables.js +42 -0
  326. package/dist/src/lib/test-case-variables.js.map +1 -1
  327. package/dist/src/lib/toml-database-config.js +19 -2
  328. package/dist/src/lib/toml-database-config.js.map +1 -1
  329. package/dist/src/lib/web-url.d.ts +40 -0
  330. package/dist/src/lib/web-url.js +76 -0
  331. package/dist/src/lib/web-url.js.map +1 -0
  332. package/dist/src/lib/webhook-deliver.d.ts +209 -0
  333. package/dist/src/lib/webhook-deliver.js +519 -0
  334. package/dist/src/lib/webhook-deliver.js.map +1 -0
  335. package/dist/src/lib/workflow-codegen/generator.d.ts +1 -1
  336. package/dist/src/lib/workflow-codegen/generator.js +1 -1
  337. package/dist/src/lib/workflow-toml-validator.d.ts +17 -8
  338. package/dist/src/lib/workflow-toml-validator.js +25 -12
  339. package/dist/src/lib/workflow-toml-validator.js.map +1 -1
  340. package/dist/src/lib/workflow-usage.d.ts +196 -0
  341. package/dist/src/lib/workflow-usage.js +310 -0
  342. package/dist/src/lib/workflow-usage.js.map +1 -0
  343. package/dist/src/types/index.d.ts +12 -2
  344. package/package.json +5 -4
@@ -1,18 +1,21 @@
1
1
  import { checkbox, confirm, input, select } from "@inquirer/prompts";
2
2
  import { appendFileSync, existsSync, readFileSync, readdirSync, writeFileSync, } from "fs";
3
3
  import { basename, join, relative, resolve } from "path";
4
- import { CURRENT_CONFIG_VERSION, PROJECT_CONFIG_DIR, PROJECT_CONFIG_FILENAME, PROJECT_CONFIG_DISPLAY_NAME, findProjectRoot, saveProjectConfig, loadProjectConfigFromPath, } from "../lib/project-config.js";
4
+ import { CURRENT_CONFIG_VERSION, PROJECT_TREE_DIR, PROJECT_CONFIG_FILENAME, PROJECT_CONFIG_DISPLAY_NAME, findProjectRoot, saveProjectConfig, loadProjectConfigFromPath, } from "../lib/project-config.js";
5
5
  import { resolveAdoptedEnvironment } from "../lib/init-adopt.js";
6
6
  import { CANONICAL_SCHEMA_HEADER, FALLBACK_STARTER_SCHEMA, platformLabel, rootAgentsMarkdown, } from "../lib/init-assets.js";
7
7
  import { checkInitGuards, planInit } from "../lib/init-plan.js";
8
8
  import { resolveProductionRuntime } from "../lib/init-production-env.js";
9
9
  import { checkClientWiring, composeSchemaContent, declaredModelNames, findClientSchema, wireClientToSchema, writeCanonicalSchema, } from "../lib/init-schema.js";
10
10
  import { declareSharedProjectConfig } from "../lib/init-xcode.js";
11
- import { deriveCallbackScheme, linkOptInGuidance, missingUrlTypeGuidance, stampCallbackScheme, } from "../lib/init-ios-links.js";
11
+ import { deriveCallbackScheme, linkOptInGuidance, missingUrlTypeGuidance, stampCallbackScheme, universalLinkGuidance, } from "../lib/init-ios-links.js";
12
+ import { appHasBothPlatforms, detectClientPlatform, discoverClientPlatforms, } from "../lib/init-client-platforms.js";
13
+ import { planEmailRedirectUriMerge } from "../lib/init-email-redirect-uris.js";
12
14
  import { getExplicitEnvName } from "../lib/env-resolver.js";
13
15
  import { ApiClient } from "../lib/api-client.js";
14
16
  import { browserLogin } from "../lib/auth-flow.js";
15
- import { loadCredentials, setCurrentApp } from "../lib/config.js";
17
+ import { loadCredentials } from "../lib/config.js";
18
+ import { allowGlobalCredentials } from "../lib/credentials-store.js";
16
19
  import { isProjectMode, seedCredentialsForEnvInProject, } from "../lib/credentials-store.js";
17
20
  import { getCurrentEnvironment, getProjectConfig } from "../lib/env-resolver.js";
18
21
  import { normalizeServerUrl } from "../lib/channel.js";
@@ -26,30 +29,86 @@ import { divider, error, info, keyValue, success, warn, } from "../lib/output.js
26
29
  import { installCommand, PRIMITIVE_CLI_PACKAGE } from "../lib/package-manager.js";
27
30
  import { gitInitialCommit, gitInitRepo, installPnpmGlobally, installPrimitiveCli, isDirectoryEmpty, isPnpmInstalled, isPortFree, isPrimitiveCliInstalled, isXcodeInstalled, iosTemplateUrl, vueTemplateUrl, resolveTemplateBranch, prepareTemplate, runPnpmInstall, runSwiftPackageResolve, updateEnvFile, updateEnvPort, configurePrimitiveJson, updateViteConfig, } from "../lib/template.js";
28
31
  /**
29
- * The .gitignore lines `init` writes for Primitive CLI local state.
32
+ * The origin a local dev server is served from. ONE spelling: the app's CORS
33
+ * origins, its `emailRedirectUris` seed and the environment's `webUrl` all
34
+ * name this, and a second spelling would be a 400 on the scaffolded app's
35
+ * first sign-in (#2982).
36
+ */
37
+ export function localDevOrigin(port) {
38
+ return `http://localhost:${port}`;
39
+ }
40
+ /**
41
+ * Point an existing environment at the app's web counterpart.
30
42
  *
31
- * Pre-pull sync snapshots (issue #578) are per-machine local recovery state and
32
- * must never be committed. They can land in three places depending on how the
33
- * sync dir resolves (see `resolveSnapshotsRoot` in `lib/sync-paths.ts`):
34
- * - project mode: `<projectRoot>/.primitive/sync-backups/<env>/<appId>/`
35
- * - legacy `--dir <p>`: `<p>/.snapshots/` (sibling of the user dir)
36
- * - no-project fallback: `./config-snapshots/`
37
- * The bare `.snapshots/` and `config-snapshots/` entries (no leading slash)
38
- * match those directories wherever `--dir` points, so backup copies — including
39
- * the restored `.primitive-sync.json` baseline — can never surface as untracked
40
- * files in a tracked custom config dir.
43
+ * Returns false — and writes nothing — when there is no config to write, the
44
+ * environment is not in it, or the file cannot be read: an adopting run does
45
+ * not own the project config, and a seed is never worth failing an init over.
41
46
  */
42
- export const GITIGNORE_LOCAL_STATE_ENTRIES = [
43
- ".primitive/credentials.json",
44
- // This machine's Primitive environment selection (`primitive env use`).
45
- // The committed `defaultEnvironment` is the team default; the selection is
46
- // per-machine and must never be committable (#2873).
47
- ".primitive/local.json",
48
- ".primitive/sync/**/.primitive-sync.json",
49
- ".primitive/sync-backups/",
50
- ".snapshots/",
51
- "config-snapshots/",
52
- ];
47
+ export function seedEnvironmentWebUrl(projectRoot, environment, webUrl) {
48
+ const configPath = join(projectRoot, PROJECT_TREE_DIR, PROJECT_CONFIG_FILENAME);
49
+ if (!existsSync(configPath))
50
+ return false;
51
+ let config;
52
+ try {
53
+ config = loadProjectConfigFromPath(configPath);
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ const entry = config.environments[environment];
59
+ if (!entry)
60
+ return false;
61
+ if (entry.webUrl === webUrl)
62
+ return true;
63
+ entry.webUrl = webUrl;
64
+ saveProjectConfig(configPath, config);
65
+ return true;
66
+ }
67
+ /**
68
+ * Make sure this app allows the dev sign-in callback, so pointing the
69
+ * environment at the web client cannot break email sign-in (#2982).
70
+ *
71
+ * Returns true when the callback is on the app's `emailRedirectUris` — already
72
+ * there, or merged in here. Returns false when it is not and this run must not
73
+ * put it there: no credentials to ask the server with, an app whose allow-list
74
+ * would be NARROWED by writing one (see `planEmailRedirectUriMerge`), or an API
75
+ * call that failed. A false answer costs nothing but the automatic seed: the
76
+ * caller prints the manual step instead, and the app keeps working code-only.
77
+ */
78
+ async function devCallbackIsAllowListed(appId, callbackUri, credentials) {
79
+ if (!credentials)
80
+ return false;
81
+ try {
82
+ const apiClient = new ApiClient();
83
+ const settings = await apiClient.getAppSettings(appId);
84
+ const plan = planEmailRedirectUriMerge(settings?.emailRedirectUris, callbackUri);
85
+ if (plan.action === "already-listed")
86
+ return true;
87
+ if (plan.action === "leave-alone") {
88
+ warn(`Not touching this app's sign-in link allow-list: ${plan.reason}.`);
89
+ return false;
90
+ }
91
+ await apiClient.updateAppSettings(appId, { emailRedirectUris: plan.uris });
92
+ success(`Allow-listed the dev sign-in link: ${callbackUri}`);
93
+ return true;
94
+ }
95
+ catch (err) {
96
+ warn(`Could not check this app's sign-in link allow-list: ${err.message}`);
97
+ return false;
98
+ }
99
+ }
100
+ /**
101
+ * The .gitignore lines `init` writes for Primitive CLI local state — one line,
102
+ * with no per-file exception (#3153, intent criterion 2).
103
+ *
104
+ * That is the whole point of the move: `primitive/` is committed (the config
105
+ * and every environment's TOML, including the shared `.sync-state.json`
106
+ * baseline) and `.primitive/` is machine-local (credentials, this machine's
107
+ * `primitive env use` selection, pre-pull snapshot backups). Before the move
108
+ * one directory meant both things, so the list needed six entries and a
109
+ * comment explaining which files inside it were committed anyway.
110
+ */
111
+ export const GITIGNORE_LOCAL_STATE_ENTRIES = [".primitive/"];
53
112
  /**
54
113
  * Ensures the project root's .gitignore lists the Primitive CLI's local state.
55
114
  *
@@ -58,47 +117,19 @@ export const GITIGNORE_LOCAL_STATE_ENTRIES = [
58
117
  * per-machine state a new client's environment produces cannot be committed.
59
118
  */
60
119
  export function ensureGitignoreLocalState(targetDir) {
61
- // Ensure .primitive/ local state (credentials, per-machine sync state) is
62
- // gitignored. The config.json itself lives in .primitive/ too, but we
63
- // gitignore only the sensitive / per-machine artifacts — config.json and
64
- // the exported TOML files under .primitive/sync/<env>/<appId>/ are meant
65
- // to be committed. Only `.primitive-sync.json` (the auto-managed sync
66
- // state baseline) is per-machine local state; everything else in the
67
- // sync directory is the version-controllable export.
68
- //
69
- // Legacy note: an earlier version of init wrote `.primitive/sync/` here,
70
- // which incorrectly hid the TOML export tree from `git status`. If we see
71
- // that legacy entry, replace it with the targeted pattern in-place so
72
- // users who re-run init pick up the fix.
120
+ // One rule: `.primitive/` is machine-local and never committed, `primitive/`
121
+ // is the committed tree. Narrower `.primitive/...` entries left over from an
122
+ // older init are harmless subsets of this line, so they are left alone — the
123
+ // migration document owns tidying an adopted project's .gitignore.
73
124
  const gitignorePath = join(targetDir, ".gitignore");
74
- const LEGACY_SYNC_IGNORE = ".primitive/sync/";
75
125
  const gitignoreEntries = [...GITIGNORE_LOCAL_STATE_ENTRIES];
76
126
  let linesToAdd = [];
77
127
  if (existsSync(gitignorePath)) {
78
- const existing = readFileSync(gitignorePath, "utf-8");
79
- const trimmedLines = existing.split(/\r?\n/).map((l) => l.trim());
80
- // Migrate the legacy `.primitive/sync/` entry to the targeted pattern,
81
- // if present. Preserve original line endings/formatting elsewhere.
82
- if (trimmedLines.includes(LEGACY_SYNC_IGNORE)) {
83
- const rewritten = existing
84
- .split(/\r?\n/)
85
- .map((line) => line.trim() === LEGACY_SYNC_IGNORE
86
- ? ".primitive/sync/**/.primitive-sync.json"
87
- : line)
88
- .join("\n");
89
- try {
90
- writeFileSync(gitignorePath, rewritten);
91
- }
92
- catch {
93
- // Non-fatal
94
- }
95
- }
96
- // Re-read after potential migration so we don't double-add entries.
97
- const refreshed = readFileSync(gitignorePath, "utf-8")
128
+ const existing = readFileSync(gitignorePath, "utf-8")
98
129
  .split(/\r?\n/)
99
130
  .map((l) => l.trim());
100
131
  for (const entry of gitignoreEntries) {
101
- if (!refreshed.includes(entry)) {
132
+ if (!existing.includes(entry)) {
102
133
  linesToAdd.push(entry);
103
134
  }
104
135
  }
@@ -117,13 +148,13 @@ export function ensureGitignoreLocalState(targetDir) {
117
148
  }
118
149
  }
119
150
  /**
120
- * Writes .primitive/config.json into the project directory and ensures the
151
+ * Writes primitive/config.json into the project directory and ensures the
121
152
  * .gitignore entry for local state exists. Safe to call in a directory that
122
153
  * already has a config — in that case we add/update the "dev" environment
123
154
  * instead of overwriting the file.
124
155
  */
125
156
  async function scaffoldProjectConfig(opts) {
126
- const configPath = join(opts.targetDir, PROJECT_CONFIG_DIR, PROJECT_CONFIG_FILENAME);
157
+ const configPath = join(opts.targetDir, PROJECT_TREE_DIR, PROJECT_CONFIG_FILENAME);
127
158
  let config;
128
159
  if (existsSync(configPath)) {
129
160
  // Respect an existing config: merge in the new dev env if missing.
@@ -218,7 +249,7 @@ async function ensureAuthenticated(credentials, serverUrl, serverChanged, contex
218
249
  * matches `serverUrl`, so the project-mode "not logged in" remedy can name a
219
250
  * real, working environment instead of a placeholder. Comparison uses the
220
251
  * same canonicalization as `resolveInitServer` (trailing-slash-insensitive)
221
- * so `https://host/` in `.primitive/config.json` still matches.
252
+ * so `https://host/` in `primitive/config.json` still matches.
222
253
  */
223
254
  function findEnvironmentForServer(serverUrl) {
224
255
  const project = getProjectConfig();
@@ -294,9 +325,9 @@ Examples:
294
325
 
295
326
  One app, several clients:
296
327
  Selecting more than one platform scaffolds them side by side under one project:
297
- each client in its own directory (web/, ios/), with .primitive/config.json, the
328
+ each client in its own directory (web/, ios/), with primitive/config.json, the
298
329
  git repository and the shared models/models.toml at the root. Run inside a
299
- directory that already has an ancestor .primitive/config.json and init ADDS the
330
+ directory that already has an ancestor primitive/config.json and init ADDS the
300
331
  client to that app instead: it reuses the selected environment's app, writes no
301
332
  nested .primitive/ or .git/, makes no commit, and leaves the files for you to
302
333
  review and commit in the outer repository.
@@ -347,6 +378,11 @@ This command will:
347
378
  .action(handleInit);
348
379
  }
349
380
  async function handleInit(projectDirArg, options) {
381
+ // `init` is what CREATES a project, so by definition it runs before one
382
+ // exists — and it has to call the API to do it. One of the four commands
383
+ // permitted to reach the global credentials file with no project in scope
384
+ // (#3154, intent criterion 5).
385
+ allowGlobalCredentials();
350
386
  // Validate an explicitly-passed `--platform` flag up front. Only `web` and
351
387
  // `ios` are supported (#1009 removed `macos`), and the flag names a LIST —
352
388
  // `--platform web,ios` scaffolds both clients of one app (#2890). An omitted
@@ -448,7 +484,7 @@ async function handleInit(projectDirArg, options) {
448
484
  const defaultAppName = projectDir === "." ? basename(targetDir) : basename(projectDir);
449
485
  // ── Which layout is this run producing? (#2890) ────────────────────────
450
486
  //
451
- // A `.primitive/config.json` in an ANCESTOR of the target means this app
487
+ // A `primitive/config.json` in an ANCESTOR of the target means this app
452
488
  // already exists and this run adds a client to it. Passing the ancestor only
453
489
  // when it differs from the target keeps `primitive init .` at a project root
454
490
  // on its existing merge behavior.
@@ -633,7 +669,7 @@ async function handleInit(projectDirArg, options) {
633
669
  }
634
670
  else {
635
671
  error(`This project has no environment configured for ${serverUrl}. ` +
636
- `Add one (\`primitive env add <name> --api-url ${serverUrl}\`) and ` +
672
+ `Add one (\`primitive env add <name> --api-url ${serverUrl} --app-id <app-id>\`) and ` +
637
673
  `re-run with \`-e <name>${serverFlag}\`, or re-run init outside the project directory.`);
638
674
  }
639
675
  }
@@ -699,6 +735,14 @@ async function handleInit(projectDirArg, options) {
699
735
  let credentials = existingCredentials;
700
736
  let appId;
701
737
  let appName;
738
+ /**
739
+ * Whether THIS run created the app. A created app is seeded with
740
+ * `http://localhost:5173/oauth/callback` in `emailRedirectUris`; an adopted
741
+ * one carries whatever allow-list it already had, which is why the combined
742
+ * app's `webUrl` seed asks the server before pointing the sign-in link at a
743
+ * dev callback (#2982).
744
+ */
745
+ let appWasCreatedByThisRun = false;
702
746
  const preparedTemplates = new Map();
703
747
  const cleanupPreparedTemplate = () => {
704
748
  for (const prepared of preparedTemplates.values()) {
@@ -847,6 +891,7 @@ async function handleInit(projectDirArg, options) {
847
891
  throw new Error("App created but no appId returned");
848
892
  }
849
893
  appId = id;
894
+ appWasCreatedByThisRun = true;
850
895
  success(`App created: ${appName}`);
851
896
  keyValue("App ID", appId);
852
897
  }
@@ -1024,20 +1069,7 @@ async function handleInit(projectDirArg, options) {
1024
1069
  // Non-fatal: git may not be installed, or already a git repo
1025
1070
  }
1026
1071
  }
1027
- // Step 4.5: Update CLI to point at this app. Adopting does not: the app is
1028
- // already the project's, and the CLI's global selection is not this run's to
1029
- // move.
1030
- if (plan.ownsApp) {
1031
- try {
1032
- setCurrentApp(appId, appName);
1033
- info(`CLI now using app: ${appName} (${appId})`);
1034
- }
1035
- catch (err) {
1036
- // Not critical - user may not be logged in (e.g. used --use-existing-app-id without login)
1037
- warn(`Could not set current app in CLI config: ${err.message}`);
1038
- }
1039
- }
1040
- // Step 4.6: Scaffold .primitive/config.json with a "dev" environment bound
1072
+ // Step 4.6: Scaffold primitive/config.json with a "dev" environment bound
1041
1073
  // to this app, and seed its credential slot with the session we just
1042
1074
  // authenticated — so `primitive` commands run from inside the project pick
1043
1075
  // up project mode without a second login.
@@ -1052,10 +1084,10 @@ async function handleInit(projectDirArg, options) {
1052
1084
  if (credentials) {
1053
1085
  seedCredentialsForEnvInProject(plan.projectRoot, "dev", credentials);
1054
1086
  }
1055
- success(`Project environment "dev" configured (.primitive/config.json).`);
1087
+ success(`Project environment "dev" configured (primitive/config.json).`);
1056
1088
  }
1057
1089
  catch (err) {
1058
- warn(`Could not scaffold .primitive/config.json: ${err.message}`);
1090
+ warn(`Could not scaffold primitive/config.json: ${err.message}`);
1059
1091
  }
1060
1092
  }
1061
1093
  else {
@@ -1117,6 +1149,23 @@ async function handleInit(projectDirArg, options) {
1117
1149
  * candidate scheme rather than a registered one.
1118
1150
  */
1119
1151
  const iosCallbackSchemes = [];
1152
+ /**
1153
+ * An app with BOTH clients emails ONE https sign-in link — the web app's
1154
+ * callback, which opens the iOS app where it is installed (#2982). "Both"
1155
+ * counts the clients already in the repository, including one that IS the
1156
+ * project root (the flat single-client layout gaining its second platform).
1157
+ */
1158
+ const combinedApp = appHasBothPlatforms(plan.clients.map((client) => client.platform), discoverClientPlatforms(plan.projectRoot));
1159
+ /** The web dev origin this run settled on, for the environment's `webUrl`. */
1160
+ let webDevOrigin = null;
1161
+ /**
1162
+ * Whether that origin's `/oauth/callback` is known to be on the app's
1163
+ * magic-link allow-list already — because the app was created here with the
1164
+ * default-port seed, or because the dev-port step merged it (#2982). Anything
1165
+ * else has to be asked about before the environment points a sign-in link at
1166
+ * it.
1167
+ */
1168
+ let devCallbackAllowListed = false;
1120
1169
  for (const client of plan.clients) {
1121
1170
  if (client.platform === "ios") {
1122
1171
  // ── Apple (SwiftUI) path ────────────────────────────────────────
@@ -1129,13 +1178,13 @@ async function handleInit(projectDirArg, options) {
1129
1178
  if (resolve(client.dir) !== resolve(plan.projectRoot)) {
1130
1179
  const declared = declareSharedProjectConfig(client.dir, plan.projectRoot);
1131
1180
  if (declared.length > 0) {
1132
- success(`Xcode build phases read ${relative(client.dir, join(plan.projectRoot, ".primitive", "config.json"))}.`);
1181
+ success(`Xcode build phases read ${relative(client.dir, join(plan.projectRoot, PROJECT_TREE_DIR, PROJECT_CONFIG_FILENAME))}.`);
1133
1182
  }
1134
1183
  }
1135
1184
  // Step 5: Generate primitive.json from the environment.
1136
1185
  //
1137
1186
  // In a current template the file is a build product, written by
1138
- // `scripts/resolve-primitive-config.sh` from `.primitive/config.json`
1187
+ // `scripts/resolve-primitive-config.sh` from `primitive/config.json`
1139
1188
  // (#2873) — it walks up to the project root, so a client inside a
1140
1189
  // composed repository resolves the same one every other command does. A
1141
1190
  // template that predates the change has no such script and a tracked
@@ -1150,7 +1199,7 @@ async function handleInit(projectDirArg, options) {
1150
1199
  environmentName: adopted?.name,
1151
1200
  });
1152
1201
  if (result.outcome === "generated") {
1153
- success("primitive.json generated from .primitive/config.json.");
1202
+ success("primitive.json generated from primitive/config.json.");
1154
1203
  }
1155
1204
  else if (result.outcome === "filled") {
1156
1205
  success("primitive.json configured.");
@@ -1163,7 +1212,7 @@ async function handleInit(projectDirArg, options) {
1163
1212
  // Neither the resolve script nor a template primitive.json — an
1164
1213
  // unexpectedly old or trimmed template. The remedy is still never
1165
1214
  // "type the app ID somewhere else": it is already in
1166
- // .primitive/config.json, which is where every reader looks.
1215
+ // primitive/config.json, which is where every reader looks.
1167
1216
  warn("This template has neither scripts/resolve-primitive-config.sh nor a primitive.json.");
1168
1217
  warn(`The project config holds the app ID; update the template to generate primitive.json from it.`);
1169
1218
  }
@@ -1219,7 +1268,7 @@ async function handleInit(projectDirArg, options) {
1219
1268
  // Step 5: Fill in an OLD template's .env identity keys.
1220
1269
  //
1221
1270
  // A current template does not author them at all — `primitiveEnv()` reads
1222
- // `.primitive/config.json`, which it finds by walking up — so "no keys to
1271
+ // `primitive/config.json`, which it finds by walking up — so "no keys to
1223
1272
  // replace" is the expected outcome, not a problem (#2873). The fill-in
1224
1273
  // survives only so a new CLI still produces a working scaffold from a
1225
1274
  // template that predates the change.
@@ -1227,7 +1276,7 @@ async function handleInit(projectDirArg, options) {
1227
1276
  try {
1228
1277
  const envResult = updateEnvFile(client.dir, appId, serverUrl, appName, production);
1229
1278
  if (!envResult.envUpdated) {
1230
- info("No .env file in this template — identity comes from .primitive/config.json.");
1279
+ info("No .env file in this template — identity comes from primitive/config.json.");
1231
1280
  }
1232
1281
  else {
1233
1282
  success("Environment configured.");
@@ -1243,7 +1292,7 @@ async function handleInit(projectDirArg, options) {
1243
1292
  // ERR_USE_AFTER_CLOSE into the catch below (#2720).
1244
1293
  try {
1245
1294
  const defaultPort = DEFAULT_DEV_PORT;
1246
- const defaultOrigin = `http://localhost:${defaultPort}`;
1295
+ const defaultOrigin = localDevOrigin(defaultPort);
1247
1296
  const resolvedPort = await resolveDevPort({
1248
1297
  configPort: initConfig?.dev_port,
1249
1298
  hasConfig: initConfig != null,
@@ -1271,11 +1320,18 @@ async function handleInit(projectDirArg, options) {
1271
1320
  },
1272
1321
  });
1273
1322
  const chosenPort = resolvedPort.port;
1323
+ // The same origin the app's CORS list and `emailRedirectUris` are seeded
1324
+ // with, so a combined app's first sign-in link is already allow-listed.
1325
+ webDevOrigin = localDevOrigin(chosenPort);
1326
+ // …but only for an app this run created: that seed happens at creation.
1327
+ // An adopted app on the default port has been through neither the seed
1328
+ // nor the port step below, so nothing here can vouch for its allow-list.
1329
+ devCallbackAllowListed = appWasCreatedByThisRun && chosenPort === defaultPort;
1274
1330
  if (resolvedPort.source === "config") {
1275
1331
  info(`Dev server port: ${chosenPort} (from config)`);
1276
1332
  }
1277
1333
  if (chosenPort !== defaultPort) {
1278
- const devOrigin = `http://localhost:${chosenPort}`;
1334
+ const devOrigin = localDevOrigin(chosenPort);
1279
1335
  // Pin the port in vite.config.ts and update .env. A vite config whose
1280
1336
  // shape we can't patch must be said out loud — otherwise the dev
1281
1337
  // server keeps 5173 while .env and the redirect URIs point elsewhere.
@@ -1351,6 +1407,9 @@ async function handleInit(projectDirArg, options) {
1351
1407
  };
1352
1408
  }
1353
1409
  await apiClient.updateAppSettings(appId, settingsUpdate);
1410
+ // This merge is what puts the chosen port's callback on the
1411
+ // allow-list, whoever created the app (#2982).
1412
+ devCallbackAllowListed = true;
1354
1413
  success(`Dev port set to ${chosenPort}: ${viteUpdated ? "vite.config.ts, " : ""}.env, CORS, redirect URIs, and base URL updated.`);
1355
1414
  }
1356
1415
  catch (err) {
@@ -1448,6 +1507,32 @@ async function handleInit(projectDirArg, options) {
1448
1507
  }
1449
1508
  }
1450
1509
  }
1510
+ // The combined app's dev sign-in link (#2982): the environment learns the
1511
+ // web origin this run settled on, so the iOS client's emailed link opens the
1512
+ // local web app instead of a scheme that is dead in the Simulator. Only when
1513
+ // this run owns the project config — an adopting run gets the guidance and
1514
+ // decides for itself.
1515
+ //
1516
+ // And only once the app actually allows that callback. Setting `webUrl` is
1517
+ // what turns a code-only email into a link, and a link the server refuses is
1518
+ // a 400 on every sign-in request — strictly worse than the code-only default
1519
+ // this app has today. An app created here was seeded with the callback; an
1520
+ // adopted one is asked about, and when the answer is no or unavailable the
1521
+ // seed is skipped in favour of printing the step.
1522
+ let seededWebOrigin = null;
1523
+ let unverifiedDevCallback = null;
1524
+ if (combinedApp && plan.writesProjectConfig && webDevOrigin) {
1525
+ const devCallback = `${webDevOrigin}/oauth/callback`;
1526
+ const allowed = devCallbackAllowListed ||
1527
+ (await devCallbackIsAllowListed(appId, devCallback, credentials));
1528
+ if (allowed && seedEnvironmentWebUrl(plan.projectRoot, "dev", webDevOrigin)) {
1529
+ seededWebOrigin = webDevOrigin;
1530
+ success(`Environment "dev" points at the web client: webUrl ${webDevOrigin}`);
1531
+ }
1532
+ else if (!allowed) {
1533
+ unverifiedDevCallback = devCallback;
1534
+ }
1535
+ }
1451
1536
  // Step 10: Offer to install Claude Code skill if not already installed
1452
1537
  try {
1453
1538
  const { isSkillInstalled, installSkillExplicit, checkSkillStatus } = await import("../lib/skill-installer.js");
@@ -1505,12 +1590,25 @@ async function handleInit(projectDirArg, options) {
1505
1590
  }
1506
1591
  divider();
1507
1592
  // Email sign-in already works for the scaffolded app — code-only, no app
1508
- // settings — so this is an opt-in, not a step (#2969).
1509
- for (const { scheme, registered } of iosCallbackSchemes) {
1510
- for (const line of linkOptInGuidance(scheme, { registered }))
1593
+ // settings — so this is an opt-in, not a step (#2969). An app with both
1594
+ // clients gets the https link instead, which needs a different checklist
1595
+ // (#2982).
1596
+ if (combinedApp) {
1597
+ for (const line of universalLinkGuidance({
1598
+ webOrigin: seededWebOrigin ?? undefined,
1599
+ unverifiedDevCallback: unverifiedDevCallback ?? undefined,
1600
+ })) {
1511
1601
  info(line);
1602
+ }
1512
1603
  divider();
1513
1604
  }
1605
+ else {
1606
+ for (const { scheme, registered } of iosCallbackSchemes) {
1607
+ for (const line of linkOptInGuidance(scheme, { registered }))
1608
+ info(line);
1609
+ divider();
1610
+ }
1611
+ }
1514
1612
  if (plan.mode === "add-client") {
1515
1613
  info(`No .primitive/ and no .git/ were created inside the new client — this app has one of each, at ${plan.projectRoot}.`);
1516
1614
  info(`The new files are uncommitted: review them with \`git status\` in ${plan.projectRoot} and commit when you are happy.`);
@@ -1550,7 +1648,7 @@ async function handleInit(projectDirArg, options) {
1550
1648
  }
1551
1649
  /** Where a project root's environments live, for the production derivation. */
1552
1650
  function readProjectEnvironments(projectRoot) {
1553
- const configPath = join(projectRoot, PROJECT_CONFIG_DIR, PROJECT_CONFIG_FILENAME);
1651
+ const configPath = join(projectRoot, PROJECT_TREE_DIR, PROJECT_CONFIG_FILENAME);
1554
1652
  if (!existsSync(configPath))
1555
1653
  return undefined;
1556
1654
  try {
@@ -1581,22 +1679,12 @@ function discoverExistingClients(projectRoot) {
1581
1679
  for (const entry of entries) {
1582
1680
  if (!entry.isDirectory() || entry.name.startsWith("."))
1583
1681
  continue;
1584
- const dir = join(projectRoot, entry.name);
1585
- const hasXcodeProject = (() => {
1586
- try {
1587
- return readdirSync(dir).some((name) => name.endsWith(".xcodeproj"));
1588
- }
1589
- catch {
1590
- return false;
1591
- }
1592
- })();
1593
- if (existsSync(join(dir, "Package.swift")) || hasXcodeProject) {
1594
- found.push({ platform: "ios", dir: entry.name });
1595
- }
1596
- else if (existsSync(join(dir, "package.json")) &&
1597
- existsSync(join(dir, "vite.config.ts"))) {
1598
- found.push({ platform: "web", dir: entry.name });
1599
- }
1682
+ // The same template signals the both-platforms predicate reads, so the
1683
+ // layout description and the universal-link wiring cannot disagree about
1684
+ // what counts as a client.
1685
+ const platform = detectClientPlatform(join(projectRoot, entry.name));
1686
+ if (platform)
1687
+ found.push({ platform, dir: entry.name });
1600
1688
  }
1601
1689
  return found;
1602
1690
  }