primitive-admin 1.2.0-alpha.0 → 1.2.0-alpha.10

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 (412) hide show
  1. package/README.md +108 -136
  2. package/assets/skill/skills/primitive-platform/SKILL.md +37 -4
  3. package/dist/bin/primitive.js +30 -35
  4. package/dist/bin/primitive.js.map +1 -1
  5. package/dist/src/commands/agent-sessions.d.ts +133 -0
  6. package/dist/src/commands/agent-sessions.js +792 -0
  7. package/dist/src/commands/agent-sessions.js.map +1 -0
  8. package/dist/src/commands/analytics.js +0 -148
  9. package/dist/src/commands/analytics.js.map +1 -1
  10. package/dist/src/commands/apps-children.d.ts +54 -0
  11. package/dist/src/commands/apps-children.js +631 -0
  12. package/dist/src/commands/apps-children.js.map +1 -0
  13. package/dist/src/commands/apps.d.ts +6 -0
  14. package/dist/src/commands/apps.js +131 -9
  15. package/dist/src/commands/apps.js.map +1 -1
  16. package/dist/src/commands/auth-sessions.d.ts +3 -3
  17. package/dist/src/commands/auth-sessions.js +117 -23
  18. package/dist/src/commands/auth-sessions.js.map +1 -1
  19. package/dist/src/commands/auth.js +460 -36
  20. package/dist/src/commands/auth.js.map +1 -1
  21. package/dist/src/commands/collections.js +316 -256
  22. package/dist/src/commands/collections.js.map +1 -1
  23. package/dist/src/commands/config.d.ts +1 -1
  24. package/dist/src/commands/config.js +60 -28
  25. package/dist/src/commands/config.js.map +1 -1
  26. package/dist/src/commands/connections.js +12 -17
  27. package/dist/src/commands/connections.js.map +1 -1
  28. package/dist/src/commands/database-type-configs.js +2 -76
  29. package/dist/src/commands/database-type-configs.js.map +1 -1
  30. package/dist/src/commands/databases.d.ts +9 -0
  31. package/dist/src/commands/databases.js +228 -566
  32. package/dist/src/commands/databases.js.map +1 -1
  33. package/dist/src/commands/db3-types.d.ts +15 -0
  34. package/dist/src/commands/db3-types.js +157 -0
  35. package/dist/src/commands/db3-types.js.map +1 -0
  36. package/dist/src/commands/db3.d.ts +12 -0
  37. package/dist/src/commands/db3.js +1052 -0
  38. package/dist/src/commands/db3.js.map +1 -0
  39. package/dist/src/commands/documents.d.ts +121 -2
  40. package/dist/src/commands/documents.js +1601 -1251
  41. package/dist/src/commands/documents.js.map +1 -1
  42. package/dist/src/commands/env.d.ts +9 -1
  43. package/dist/src/commands/env.js +164 -49
  44. package/dist/src/commands/env.js.map +1 -1
  45. package/dist/src/commands/function-trigger-commands.d.ts +28 -0
  46. package/dist/src/commands/function-trigger-commands.js +341 -0
  47. package/dist/src/commands/function-trigger-commands.js.map +1 -0
  48. package/dist/src/commands/functions.js +297 -24
  49. package/dist/src/commands/functions.js.map +1 -1
  50. package/dist/src/commands/groups.js +150 -49
  51. package/dist/src/commands/groups.js.map +1 -1
  52. package/dist/src/commands/guides.js +5 -5
  53. package/dist/src/commands/init.d.ts +7 -0
  54. package/dist/src/commands/init.js +23 -5
  55. package/dist/src/commands/init.js.map +1 -1
  56. package/dist/src/commands/integrations.js +14 -8
  57. package/dist/src/commands/integrations.js.map +1 -1
  58. package/dist/src/commands/metadata-category-configs.d.ts +2 -2
  59. package/dist/src/commands/metadata-category-configs.js +2 -2
  60. package/dist/src/commands/prompts.d.ts +7 -0
  61. package/dist/src/commands/prompts.js +48 -5
  62. package/dist/src/commands/prompts.js.map +1 -1
  63. package/dist/src/commands/scripts.d.ts +9 -17
  64. package/dist/src/commands/scripts.js +24 -479
  65. package/dist/src/commands/scripts.js.map +1 -1
  66. package/dist/src/commands/sessions.js +3 -8
  67. package/dist/src/commands/sessions.js.map +1 -1
  68. package/dist/src/commands/sync-app-settings.d.ts +36 -8
  69. package/dist/src/commands/sync-app-settings.js +109 -23
  70. package/dist/src/commands/sync-app-settings.js.map +1 -1
  71. package/dist/src/commands/sync.d.ts +436 -613
  72. package/dist/src/commands/sync.js +2604 -5977
  73. package/dist/src/commands/sync.js.map +1 -1
  74. package/dist/src/commands/tokens.js +4 -9
  75. package/dist/src/commands/tokens.js.map +1 -1
  76. package/dist/src/commands/users.js +77 -2
  77. package/dist/src/commands/users.js.map +1 -1
  78. package/dist/src/commands/workflows.d.ts +5 -83
  79. package/dist/src/commands/workflows.js +57 -2282
  80. package/dist/src/commands/workflows.js.map +1 -1
  81. package/dist/src/lib/aggregate-output.d.ts +25 -0
  82. package/dist/src/lib/aggregate-output.js +53 -0
  83. package/dist/src/lib/aggregate-output.js.map +1 -0
  84. package/dist/src/lib/api-client.d.ts +681 -581
  85. package/dist/src/lib/api-client.js +880 -851
  86. package/dist/src/lib/api-client.js.map +1 -1
  87. package/dist/src/lib/app-settings-descriptor.d.ts +47 -1
  88. package/dist/src/lib/app-settings-descriptor.js +114 -10
  89. package/dist/src/lib/app-settings-descriptor.js.map +1 -1
  90. package/dist/src/lib/auth-fixtures.d.ts +44 -0
  91. package/dist/src/lib/auth-fixtures.js +61 -0
  92. package/dist/src/lib/auth-fixtures.js.map +1 -0
  93. package/dist/src/lib/auth-flow.d.ts +41 -1
  94. package/dist/src/lib/auth-flow.js +70 -16
  95. package/dist/src/lib/auth-flow.js.map +1 -1
  96. package/dist/src/lib/block-layout.d.ts +12 -23
  97. package/dist/src/lib/block-layout.js +26 -92
  98. package/dist/src/lib/block-layout.js.map +1 -1
  99. package/dist/src/lib/block-selector.d.ts +2 -2
  100. package/dist/src/lib/block-selector.js +2 -2
  101. package/dist/src/lib/child-apps-local.d.ts +164 -0
  102. package/dist/src/lib/child-apps-local.js +258 -0
  103. package/dist/src/lib/child-apps-local.js.map +1 -0
  104. package/dist/src/lib/ci-session-request.d.ts +22 -0
  105. package/dist/src/lib/ci-session-request.js +51 -0
  106. package/dist/src/lib/ci-session-request.js.map +1 -0
  107. package/dist/src/lib/codegen-shared/generatedFiles.d.ts +10 -8
  108. package/dist/src/lib/codegen-shared/generatedFiles.js +21 -15
  109. package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -1
  110. package/dist/src/lib/codegen-shared/prettierStable.d.ts +1 -1
  111. package/dist/src/lib/codegen-shared/prettierStable.js +1 -1
  112. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +2 -2
  113. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +2 -2
  114. package/dist/src/lib/collection-export.d.ts +76 -15
  115. package/dist/src/lib/collection-export.js +116 -23
  116. package/dist/src/lib/collection-export.js.map +1 -1
  117. package/dist/src/lib/collection-principal.d.ts +22 -0
  118. package/dist/src/lib/collection-principal.js +35 -0
  119. package/dist/src/lib/collection-principal.js.map +1 -0
  120. package/dist/src/lib/config-object-descriptor.d.ts +3 -22
  121. package/dist/src/lib/config-object-descriptor.js +56 -291
  122. package/dist/src/lib/config-object-descriptor.js.map +1 -1
  123. package/dist/src/lib/config-payload.js +2 -3
  124. package/dist/src/lib/config-payload.js.map +1 -1
  125. package/dist/src/lib/config-surface.d.ts +5 -7
  126. package/dist/src/lib/config-surface.js +18 -40
  127. package/dist/src/lib/config-surface.js.map +1 -1
  128. package/dist/src/lib/connections-list.d.ts +10 -0
  129. package/dist/src/lib/connections-list.js +18 -0
  130. package/dist/src/lib/connections-list.js.map +1 -0
  131. package/dist/src/lib/credentials-home.d.ts +58 -0
  132. package/dist/src/lib/credentials-home.js +204 -0
  133. package/dist/src/lib/credentials-home.js.map +1 -0
  134. package/dist/src/lib/credentials-lock.d.ts +88 -0
  135. package/dist/src/lib/credentials-lock.js +302 -0
  136. package/dist/src/lib/credentials-lock.js.map +1 -0
  137. package/dist/src/lib/credentials-store.d.ts +179 -24
  138. package/dist/src/lib/credentials-store.js +712 -77
  139. package/dist/src/lib/credentials-store.js.map +1 -1
  140. package/dist/src/lib/csv.d.ts +8 -9
  141. package/dist/src/lib/csv.js +3 -4
  142. package/dist/src/lib/csv.js.map +1 -1
  143. package/dist/src/lib/custodian.d.ts +8 -0
  144. package/dist/src/lib/custodian.js +18 -0
  145. package/dist/src/lib/custodian.js.map +1 -0
  146. package/dist/src/lib/db-codegen/dbGenerator.js +5 -16
  147. package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -1
  148. package/dist/src/lib/db-codegen/dbNaming.d.ts +5 -13
  149. package/dist/src/lib/db-codegen/dbNaming.js +5 -17
  150. package/dist/src/lib/db-codegen/dbNaming.js.map +1 -1
  151. package/dist/src/lib/db-codegen/dbTemplates.d.ts +7 -56
  152. package/dist/src/lib/db-codegen/dbTemplates.js +25 -125
  153. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
  154. package/dist/src/lib/db-codegen/dbTsTypes.d.ts +2 -2
  155. package/dist/src/lib/db-codegen/dbTsTypes.js +2 -2
  156. package/dist/src/lib/db-codegen/dbTypeIR.d.ts +67 -2
  157. package/dist/src/lib/db-codegen/dbTypeIR.js +13 -2
  158. package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -1
  159. package/dist/src/lib/db3-export.d.ts +231 -0
  160. package/dist/src/lib/db3-export.js +510 -0
  161. package/dist/src/lib/db3-export.js.map +1 -0
  162. package/dist/src/lib/db3-index-output.d.ts +27 -0
  163. package/dist/src/lib/db3-index-output.js +49 -0
  164. package/dist/src/lib/db3-index-output.js.map +1 -0
  165. package/dist/src/lib/document-export-lookups.d.ts +31 -0
  166. package/dist/src/lib/document-export-lookups.js +53 -0
  167. package/dist/src/lib/document-export-lookups.js.map +1 -0
  168. package/dist/src/lib/document-export-permissions.d.ts +7 -5
  169. package/dist/src/lib/document-export-permissions.js +7 -5
  170. package/dist/src/lib/document-export-permissions.js.map +1 -1
  171. package/dist/src/lib/document-export-record-log.d.ts +57 -0
  172. package/dist/src/lib/document-export-record-log.js +215 -0
  173. package/dist/src/lib/document-export-record-log.js.map +1 -0
  174. package/dist/src/lib/document-ingest-artifact.d.ts +26 -2
  175. package/dist/src/lib/document-ingest-artifact.js +42 -8
  176. package/dist/src/lib/document-ingest-artifact.js.map +1 -1
  177. package/dist/src/lib/document-ingest-format2.d.ts +36 -0
  178. package/dist/src/lib/document-ingest-format2.js +78 -0
  179. package/dist/src/lib/document-ingest-format2.js.map +1 -0
  180. package/dist/src/lib/document-ingest-input.d.ts +9 -0
  181. package/dist/src/lib/document-ingest-input.js +28 -1
  182. package/dist/src/lib/document-ingest-input.js.map +1 -1
  183. package/dist/src/lib/document-ingest-rows.d.ts +16 -0
  184. package/dist/src/lib/document-ingest-rows.js +17 -0
  185. package/dist/src/lib/document-ingest-rows.js.map +1 -1
  186. package/dist/src/lib/document-ingest.d.ts +33 -1
  187. package/dist/src/lib/document-ingest.js +49 -0
  188. package/dist/src/lib/document-ingest.js.map +1 -1
  189. package/dist/src/lib/document-schema-rows.d.ts +42 -0
  190. package/dist/src/lib/document-schema-rows.js +53 -0
  191. package/dist/src/lib/document-schema-rows.js.map +1 -0
  192. package/dist/src/lib/env-resolver-core.d.ts +70 -2
  193. package/dist/src/lib/env-resolver-core.js +137 -20
  194. package/dist/src/lib/env-resolver-core.js.map +1 -1
  195. package/dist/src/lib/env-resolver.d.ts +14 -2
  196. package/dist/src/lib/env-resolver.js +32 -10
  197. package/dist/src/lib/env-resolver.js.map +1 -1
  198. package/dist/src/lib/function-authorize-check.d.ts +142 -0
  199. package/dist/src/lib/function-authorize-check.js +310 -0
  200. package/dist/src/lib/function-authorize-check.js.map +1 -0
  201. package/dist/src/lib/function-authorize-view.d.ts +15 -0
  202. package/dist/src/lib/function-authorize-view.js +50 -0
  203. package/dist/src/lib/function-authorize-view.js.map +1 -0
  204. package/dist/src/lib/function-bundle.d.ts +38 -18
  205. package/dist/src/lib/function-bundle.js +96 -35
  206. package/dist/src/lib/function-bundle.js.map +1 -1
  207. package/dist/src/lib/function-collect.d.ts +75 -12
  208. package/dist/src/lib/function-collect.js +427 -12
  209. package/dist/src/lib/function-collect.js.map +1 -1
  210. package/dist/src/lib/function-db-types.d.ts +29 -0
  211. package/dist/src/lib/function-db-types.js +117 -7
  212. package/dist/src/lib/function-db-types.js.map +1 -1
  213. package/dist/src/lib/function-document-types.d.ts +5 -3
  214. package/dist/src/lib/function-document-types.js +10 -14
  215. package/dist/src/lib/function-document-types.js.map +1 -1
  216. package/dist/src/lib/function-lint.d.ts +34 -0
  217. package/dist/src/lib/function-lint.js +112 -0
  218. package/dist/src/lib/function-lint.js.map +1 -0
  219. package/dist/src/lib/function-log-row.js +21 -2
  220. package/dist/src/lib/function-log-row.js.map +1 -1
  221. package/dist/src/lib/function-run.d.ts +16 -21
  222. package/dist/src/lib/function-run.js +37 -30
  223. package/dist/src/lib/function-run.js.map +1 -1
  224. package/dist/src/lib/function-schema-codegen.d.ts +1 -1
  225. package/dist/src/lib/function-schema-codegen.js +5 -5
  226. package/dist/src/lib/function-schema-codegen.js.map +1 -1
  227. package/dist/src/lib/function-source-roots.d.ts +67 -0
  228. package/dist/src/lib/function-source-roots.js +148 -0
  229. package/dist/src/lib/function-source-roots.js.map +1 -0
  230. package/dist/src/lib/function-sync.d.ts +108 -22
  231. package/dist/src/lib/function-sync.js +127 -39
  232. package/dist/src/lib/function-sync.js.map +1 -1
  233. package/dist/src/lib/function-trigger-listing.d.ts +11 -6
  234. package/dist/src/lib/function-trigger-listing.js +32 -11
  235. package/dist/src/lib/function-trigger-listing.js.map +1 -1
  236. package/dist/src/lib/function-typecheck.d.ts +37 -2
  237. package/dist/src/lib/function-typecheck.js +210 -28
  238. package/dist/src/lib/function-typecheck.js.map +1 -1
  239. package/dist/src/lib/function-versions.d.ts +2 -0
  240. package/dist/src/lib/function-versions.js +1 -0
  241. package/dist/src/lib/function-versions.js.map +1 -1
  242. package/dist/src/lib/generated-allowlist.js +0 -20
  243. package/dist/src/lib/generated-allowlist.js.map +1 -1
  244. package/dist/src/lib/generated-config-surfaces.d.ts +703 -201
  245. package/dist/src/lib/generated-config-surfaces.js +2335 -2033
  246. package/dist/src/lib/generated-config-surfaces.js.map +1 -1
  247. package/dist/src/lib/generated-sdk-types.d.ts +1 -1
  248. package/dist/src/lib/generated-sdk-types.js +1 -1
  249. package/dist/src/lib/generated-sdk-types.js.map +1 -1
  250. package/dist/src/lib/init-adopt.js +12 -3
  251. package/dist/src/lib/init-adopt.js.map +1 -1
  252. package/dist/src/lib/init-login-note.d.ts +13 -0
  253. package/dist/src/lib/init-login-note.js +22 -0
  254. package/dist/src/lib/init-login-note.js.map +1 -0
  255. package/dist/src/lib/json-value.d.ts +6 -0
  256. package/dist/src/lib/json-value.js +13 -0
  257. package/dist/src/lib/json-value.js.map +1 -0
  258. package/dist/src/lib/load-routes.d.ts +33 -0
  259. package/dist/src/lib/load-routes.js +35 -0
  260. package/dist/src/lib/load-routes.js.map +1 -0
  261. package/dist/src/lib/local-state.d.ts +38 -7
  262. package/dist/src/lib/local-state.js +172 -49
  263. package/dist/src/lib/local-state.js.map +1 -1
  264. package/dist/src/lib/local-test-cases.d.ts +5 -2
  265. package/dist/src/lib/local-test-cases.js +7 -4
  266. package/dist/src/lib/local-test-cases.js.map +1 -1
  267. package/dist/src/lib/log-inspection.d.ts +42 -4
  268. package/dist/src/lib/log-inspection.js +37 -5
  269. package/dist/src/lib/log-inspection.js.map +1 -1
  270. package/dist/src/lib/login-exchange-check.d.ts +18 -0
  271. package/dist/src/lib/login-exchange-check.js +27 -0
  272. package/dist/src/lib/login-exchange-check.js.map +1 -0
  273. package/dist/src/lib/login-scope-lines.d.ts +25 -0
  274. package/dist/src/lib/login-scope-lines.js +45 -0
  275. package/dist/src/lib/login-scope-lines.js.map +1 -0
  276. package/dist/src/lib/logout-admin-session.d.ts +2 -1
  277. package/dist/src/lib/logout-admin-session.js +7 -1
  278. package/dist/src/lib/logout-admin-session.js.map +1 -1
  279. package/dist/src/lib/object-status-filter.d.ts +9 -1
  280. package/dist/src/lib/object-status-filter.js +9 -1
  281. package/dist/src/lib/object-status-filter.js.map +1 -1
  282. package/dist/src/lib/output.js +2 -0
  283. package/dist/src/lib/output.js.map +1 -1
  284. package/dist/src/lib/paginate.d.ts +0 -5
  285. package/dist/src/lib/paginate.js +0 -5
  286. package/dist/src/lib/paginate.js.map +1 -1
  287. package/dist/src/lib/pin-refusal-hint.d.ts +12 -0
  288. package/dist/src/lib/pin-refusal-hint.js +35 -0
  289. package/dist/src/lib/pin-refusal-hint.js.map +1 -0
  290. package/dist/src/lib/primitive-env-plugin.d.ts +99 -0
  291. package/dist/src/lib/primitive-env-plugin.js +408 -0
  292. package/dist/src/lib/primitive-env-plugin.js.map +1 -0
  293. package/dist/src/lib/project-config.d.ts +7 -0
  294. package/dist/src/lib/project-config.js +22 -1
  295. package/dist/src/lib/project-config.js.map +1 -1
  296. package/dist/src/lib/project-login-apps.d.ts +8 -0
  297. package/dist/src/lib/project-login-apps.js +27 -0
  298. package/dist/src/lib/project-login-apps.js.map +1 -0
  299. package/dist/src/lib/prompt-schema-codegen.d.ts +10 -3
  300. package/dist/src/lib/prompt-schema-codegen.js +52 -11
  301. package/dist/src/lib/prompt-schema-codegen.js.map +1 -1
  302. package/dist/src/lib/pull-write.d.ts +31 -0
  303. package/dist/src/lib/pull-write.js +49 -0
  304. package/dist/src/lib/pull-write.js.map +1 -0
  305. package/dist/src/lib/push-failure-report.d.ts +45 -0
  306. package/dist/src/lib/push-failure-report.js +57 -0
  307. package/dist/src/lib/push-failure-report.js.map +1 -0
  308. package/dist/src/lib/records-verbs.d.ts +69 -0
  309. package/dist/src/lib/records-verbs.js +40 -0
  310. package/dist/src/lib/records-verbs.js.map +1 -0
  311. package/dist/src/lib/refresh-admin-credentials.d.ts +7 -0
  312. package/dist/src/lib/refresh-admin-credentials.js +16 -4
  313. package/dist/src/lib/refresh-admin-credentials.js.map +1 -1
  314. package/dist/src/lib/root-import-decision.d.ts +65 -0
  315. package/dist/src/lib/root-import-decision.js +109 -0
  316. package/dist/src/lib/root-import-decision.js.map +1 -0
  317. package/dist/src/lib/run-status.d.ts +1 -1
  318. package/dist/src/lib/run-status.generated.d.ts +2 -2
  319. package/dist/src/lib/run-status.generated.js +3 -1
  320. package/dist/src/lib/run-status.generated.js.map +1 -1
  321. package/dist/src/lib/run-status.js +1 -1
  322. package/dist/src/lib/schema-codegen/generated-schema-descriptor.d.ts +129 -0
  323. package/dist/src/lib/schema-codegen/generated-schema-descriptor.js +269 -0
  324. package/dist/src/lib/schema-codegen/generated-schema-descriptor.js.map +1 -0
  325. package/dist/src/lib/schema-codegen/naming.d.ts +35 -0
  326. package/dist/src/lib/schema-codegen/naming.js +83 -0
  327. package/dist/src/lib/schema-codegen/naming.js.map +1 -0
  328. package/dist/src/lib/schema-codegen/schemaToTs.d.ts +82 -0
  329. package/dist/src/lib/schema-codegen/schemaToTs.js +305 -0
  330. package/dist/src/lib/schema-codegen/schemaToTs.js.map +1 -0
  331. package/dist/src/lib/scope-request.d.ts +84 -0
  332. package/dist/src/lib/scope-request.js +235 -0
  333. package/dist/src/lib/scope-request.js.map +1 -0
  334. package/dist/src/lib/session-id.d.ts +9 -0
  335. package/dist/src/lib/session-id.js +28 -0
  336. package/dist/src/lib/session-id.js.map +1 -0
  337. package/dist/src/lib/snapshot-build-rows.d.ts +21 -0
  338. package/dist/src/lib/snapshot-build-rows.js +22 -0
  339. package/dist/src/lib/snapshot-build-rows.js.map +1 -1
  340. package/dist/src/lib/snapshot-build.js +8 -3
  341. package/dist/src/lib/snapshot-build.js.map +1 -1
  342. package/dist/src/lib/snapshots.d.ts +17 -1
  343. package/dist/src/lib/snapshots.js +50 -2
  344. package/dist/src/lib/snapshots.js.map +1 -1
  345. package/dist/src/lib/swift-codegen/agentGenerator.d.ts +6 -2
  346. package/dist/src/lib/swift-codegen/agentGenerator.js +57 -5
  347. package/dist/src/lib/swift-codegen/agentGenerator.js.map +1 -1
  348. package/dist/src/lib/swift-codegen/banners.d.ts +34 -7
  349. package/dist/src/lib/swift-codegen/banners.js +37 -7
  350. package/dist/src/lib/swift-codegen/banners.js.map +1 -1
  351. package/dist/src/lib/swift-codegen/dbGenerator.d.ts +4 -3
  352. package/dist/src/lib/swift-codegen/dbGenerator.js +14 -117
  353. package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -1
  354. package/dist/src/lib/swift-codegen/functionGenerator.d.ts +3 -3
  355. package/dist/src/lib/swift-codegen/functionGenerator.js +23 -18
  356. package/dist/src/lib/swift-codegen/functionGenerator.js.map +1 -1
  357. package/dist/src/lib/swift-codegen/generator.d.ts +5 -5
  358. package/dist/src/lib/swift-codegen/generator.js +35 -40
  359. package/dist/src/lib/swift-codegen/generator.js.map +1 -1
  360. package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +3 -3
  361. package/dist/src/lib/swift-codegen/schemaToSwift.js +4 -4
  362. package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -1
  363. package/dist/src/lib/swift-codegen/swiftKeys.d.ts +19 -0
  364. package/dist/src/lib/swift-codegen/swiftKeys.js +31 -0
  365. package/dist/src/lib/swift-codegen/swiftKeys.js.map +1 -0
  366. package/dist/src/lib/sync-paths.d.ts +35 -0
  367. package/dist/src/lib/sync-paths.js +57 -5
  368. package/dist/src/lib/sync-paths.js.map +1 -1
  369. package/dist/src/lib/sync-resource-types.d.ts +26 -26
  370. package/dist/src/lib/sync-resource-types.js +61 -189
  371. package/dist/src/lib/sync-resource-types.js.map +1 -1
  372. package/dist/src/lib/sync-selectors.d.ts +5 -1
  373. package/dist/src/lib/sync-selectors.js +23 -13
  374. package/dist/src/lib/sync-selectors.js.map +1 -1
  375. package/dist/src/lib/test-case-file-names.d.ts +3 -5
  376. package/dist/src/lib/test-case-file-names.js +3 -5
  377. package/dist/src/lib/test-case-file-names.js.map +1 -1
  378. package/dist/src/lib/toml-database-config.d.ts +15 -75
  379. package/dist/src/lib/toml-database-config.js +17 -306
  380. package/dist/src/lib/toml-database-config.js.map +1 -1
  381. package/dist/src/lib/transient-retry.d.ts +43 -0
  382. package/dist/src/lib/transient-retry.js +94 -0
  383. package/dist/src/lib/transient-retry.js.map +1 -0
  384. package/dist/src/lib/vars-override.d.ts +105 -0
  385. package/dist/src/lib/vars-override.js +143 -0
  386. package/dist/src/lib/vars-override.js.map +1 -0
  387. package/dist/src/lib/webhook-capture.d.ts +43 -0
  388. package/dist/src/lib/webhook-capture.js +91 -0
  389. package/dist/src/lib/webhook-capture.js.map +1 -0
  390. package/dist/src/lib/webhook-deliver.d.ts +18 -20
  391. package/dist/src/lib/webhook-deliver.js +34 -89
  392. package/dist/src/lib/webhook-deliver.js.map +1 -1
  393. package/dist/src/lib/webhook-test-run.d.ts +32 -0
  394. package/dist/src/lib/webhook-test-run.js +108 -0
  395. package/dist/src/lib/webhook-test-run.js.map +1 -0
  396. package/dist/src/lib/workflow-codegen/generator.d.ts +7 -7
  397. package/dist/src/lib/workflow-codegen/generator.js +16 -23
  398. package/dist/src/lib/workflow-codegen/generator.js.map +1 -1
  399. package/dist/src/lib/workflow-codegen/invokerIR.d.ts +6 -12
  400. package/dist/src/lib/workflow-codegen/invokerIR.js +4 -5
  401. package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -1
  402. package/dist/src/lib/workflow-toml-validator.d.ts +2 -0
  403. package/dist/src/lib/workflow-toml-validator.js +28 -1
  404. package/dist/src/lib/workflow-toml-validator.js.map +1 -1
  405. package/dist/src/types/index.d.ts +30 -37
  406. package/dist/src/validators.d.ts +7 -55
  407. package/dist/src/validators.js +7 -53
  408. package/dist/src/validators.js.map +1 -1
  409. package/dist/src/vite.d.ts +17 -0
  410. package/dist/src/vite.js +18 -0
  411. package/dist/src/vite.js.map +1 -0
  412. package/package.json +20 -12
@@ -7,11 +7,13 @@
7
7
  *
8
8
  * Mode A (project mode): credentials live at
9
9
  * <projectRoot>/.primitive/credentials.json
10
- * with per-environment slots:
10
+ * with one login per server (#4512), keyed by normalized server URL:
11
11
  * {
12
- * "environments": {
13
- * "dev": { "accessToken": "...", "refreshToken": "...", ... },
14
- * "prod": { ... }
12
+ * "servers": {
13
+ * "https://api.example.com": {
14
+ * "login": { "accessToken": "...", "refreshToken": "...", ... },
15
+ * "leftovers": { "staging": { ... } }
16
+ * }
15
17
  * }
16
18
  * }
17
19
  *
@@ -33,16 +35,51 @@
33
35
  * project either. The last two are the divergence from the intent's "login and
34
36
  * init and nothing else", adjudicated on #3154 as finding F1.
35
37
  *
38
+ * A fifth, `apps delete`, says it pinned to a server (#4905,
39
+ * `allowGlobalCredentialsForServer()`): the server `PRIMITIVE_SERVER_URL`
40
+ * names, or production when it is unset, as `login` resolves it. The global
41
+ * login is used only when that server issued it, so the command still never
42
+ * acts against whatever server the last `login` targeted.
43
+ *
36
44
  * The opt-in is per ACTION, not per argv: `primitive --env prod login` and
37
45
  * `primitive login` reach the same line, so there is no flag shape for a
38
46
  * pre-parse to miss. And it only PERMITS the global file — inside a project
39
- * all four still use the per-environment slots.
47
+ * all five still use the project's file.
48
+ *
49
+ * Every environment on a server — committed or machine-local — reads and
50
+ * writes that server's one login, so one login and one logout cover them all
51
+ * (#4512). A server's `leftovers` are logins an older CLI stored per
52
+ * environment: never read, revoked by `logout`.
53
+ *
54
+ * Before #4512 a login lived in a per-environment slot under `environments`,
55
+ * a child environment (#4511) using its parent's. Such a slot is read only
56
+ * while the file has no entry for its server. The first write for a server
57
+ * creates the entry and moves the server's legacy slots out: a mere copy of
58
+ * the login being written is dropped, any other becomes a leftover. From then
59
+ * on a cleared login stays cleared; no legacy slot is read for that server.
60
+ *
61
+ * A login records the server that issued it (#4492), and credentials from
62
+ * another server than the environment's are never stored. A legacy slot
63
+ * recording another server than the invoking environment's is never used (its
64
+ * tokens are not sent); a legacy slot that records no server is read against
65
+ * the environment's `apiUrl`.
66
+ *
67
+ * In a linked git worktree the project's file is the main worktree's
68
+ * (#4492, `credentials-home.ts`), so one login serves every worktree of the
69
+ * repository that holds the project. Every project-mode write is a locked
70
+ * read-modify-write with an atomic replace (`credentials-lock.ts`), because
71
+ * several processes — one per worktree — write that one file.
40
72
  */
41
73
  import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "fs";
42
74
  import { homedir } from "os";
43
- import { join } from "path";
44
- import { getCurrentEnvironment, getProjectConfig } from "./env-resolver.js";
45
- import { missingConfigMessage, PrimitiveEnvError } from "./env-resolver-core.js";
75
+ import { dirname, join } from "path";
76
+ import { getCurrentEnvironment, getProjectConfig, } from "./env-resolver.js";
77
+ import { missingConfigMessage, PROJECT_CONFIG_FILENAME, PROJECT_TREE_DIR, PrimitiveEnvError, readLocalState, } from "./env-resolver-core.js";
78
+ import { DEFAULT_SERVER_URL, PRODUCTION_SERVER_URL } from "./constants.js";
79
+ import { normalizeServerUrl } from "./channel.js";
80
+ import { credentialsRootFor, ignoredLocalCredentialsFile, oldGitHint, otherSharingProjectRoots, sharedWorktreeCount, } from "./credentials-home.js";
81
+ import { acquireCredentialsLock, fileVersion, replaceFileUnderLock } from "./credentials-lock.js";
82
+ import { sessionIdOfToken } from "./session-id.js";
46
83
  export const PROJECT_LOCAL_DIR = ".primitive";
47
84
  export const PROJECT_CREDENTIALS_FILENAME = "credentials.json";
48
85
  /**
@@ -86,17 +123,30 @@ export function isProjectMode() {
86
123
  */
87
124
  export function getCredentialsFilePath() {
88
125
  const env = resolveCurrentEnvironment();
89
- if (env) {
90
- return join(env.projectRoot, PROJECT_LOCAL_DIR, PROJECT_CREDENTIALS_FILENAME);
91
- }
126
+ if (env)
127
+ return credentialsFileForProject(env.projectRoot);
92
128
  return getLegacyCredentialsFile();
93
129
  }
94
130
  /**
95
- * Set by the four actions that may read or write the global credentials file
131
+ * The credentials file of the project at `projectRoot`: its own
132
+ * `.primitive/credentials.json`, or in a linked git worktree the main
133
+ * worktree's for the same project (#4492).
134
+ */
135
+ export function credentialsFileForProject(projectRoot) {
136
+ return join(credentialsRootFor(projectRoot), PROJECT_LOCAL_DIR, PROJECT_CREDENTIALS_FILENAME);
137
+ }
138
+ /**
139
+ * Set by the actions that may read or write the global credentials file
96
140
  * with no project in scope. Process-wide, because one invocation runs one
97
141
  * command; never reset, because a command does not stop being `login`.
98
142
  */
99
143
  let globalCredentialsAllowed = false;
144
+ /**
145
+ * The server a pinned allowance names (#4905): the global login is used only
146
+ * when this server issued it, and only its credentials are written. Null for
147
+ * the unpinned allowance of the login family.
148
+ */
149
+ let globalCredentialsServer = null;
100
150
  /**
101
151
  * Declare that THIS command may fall back to `~/.primitive/credentials.json`
102
152
  * when no project is in scope. Called at the top of `login`, `logout`, `init`
@@ -105,6 +155,17 @@ let globalCredentialsAllowed = false;
105
155
  export function allowGlobalCredentials() {
106
156
  globalCredentialsAllowed = true;
107
157
  }
158
+ /**
159
+ * Declare that THIS command may use the global credentials file when no
160
+ * project is in scope, but only for `serverUrl`: a stored login another server
161
+ * issued is refused with `server-mismatch` before any of its tokens is sent,
162
+ * credentials from another server are never written, and `PRIMITIVE_TOKEN` is
163
+ * sent to `serverUrl`. Called by `apps delete` (#4905), and nowhere else.
164
+ */
165
+ export function allowGlobalCredentialsForServer(serverUrl) {
166
+ globalCredentialsAllowed = true;
167
+ globalCredentialsServer = trimSlash(serverUrl);
168
+ }
108
169
  /**
109
170
  * Drop the declaration again. One invocation runs one command, so nothing in
110
171
  * the CLI calls this — it exists so a test file can exercise both sides of the
@@ -112,6 +173,25 @@ export function allowGlobalCredentials() {
112
173
  */
113
174
  export function resetGlobalCredentialsAllowance() {
114
175
  globalCredentialsAllowed = false;
176
+ globalCredentialsServer = null;
177
+ }
178
+ /**
179
+ * The login command for the global login of `serverUrl`, up to its `--app`
180
+ * value: prefixed with `PRIMITIVE_SERVER_URL=<server>` unless the server is
181
+ * production, which `login` targets when the variable is unset.
182
+ */
183
+ export function globalLoginCommand(serverUrl) {
184
+ const prefix = sameServer(serverUrl, PRODUCTION_SERVER_URL) ? "" : `PRIMITIVE_SERVER_URL=${serverUrl} `;
185
+ return `${prefix}primitive login --app`;
186
+ }
187
+ /** The refusal of a global login another server than the pinned one issued. */
188
+ function globalReadMismatch(stored, pinned) {
189
+ const issuer = stored ?? "a server it does not record";
190
+ const target = stored ? ` To target ${stored}, set PRIMITIVE_SERVER_URL=${stored}.` : "";
191
+ return new PrimitiveEnvError("server-mismatch", `The login in ${getLegacyCredentialsFile()} was issued by ${issuer}, but this command targets ` +
192
+ `${pinned} (PRIMITIVE_SERVER_URL, or production when it is unset); its tokens were not sent.` +
193
+ `${target} To sign in to ${pinned}, run '${globalLoginCommand(pinned)} <app-id>' (or --app all); ` +
194
+ `it replaces the stored login.`);
115
195
  }
116
196
  /**
117
197
  * Resolves the current environment. If an environment CAN'T be resolved
@@ -169,6 +249,7 @@ function legacyCredentialsRecord(credentials) {
169
249
  role: credentials.role,
170
250
  name: credentials.name,
171
251
  globalAdminAppId: credentials.globalAdminAppId,
252
+ stepUp: credentials.stepUp,
172
253
  };
173
254
  }
174
255
  /**
@@ -176,28 +257,318 @@ function legacyCredentialsRecord(credentials) {
176
257
  * an empty shape when the file is missing or unreadable.
177
258
  */
178
259
  function loadProjectCredentialsFile(projectRoot) {
179
- const path = join(projectRoot, PROJECT_LOCAL_DIR, PROJECT_CREDENTIALS_FILENAME);
260
+ const path = credentialsFileForProject(projectRoot);
180
261
  const data = readJsonFile(path);
181
- if (!data || typeof data !== "object" || !data.environments) {
182
- return { environments: {} };
183
- }
262
+ const isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
263
+ if (!isRecord(data))
264
+ return { environments: {}, servers: {} };
265
+ const file = {
266
+ environments: isRecord(data.environments) ? data.environments : {},
267
+ servers: isRecord(data.servers) ? data.servers : {},
268
+ };
184
269
  // EVERY slot, not just the one about to be written: each write reserializes
185
270
  // the whole file, so a stale key anywhere would survive forever.
186
- for (const slot of Object.values(data.environments)) {
271
+ for (const slot of Object.values(file.environments)) {
187
272
  stripRetiredCurrentApp(slot);
188
273
  }
189
- return data;
274
+ for (const entry of Object.values(file.servers)) {
275
+ stripRetiredCurrentApp(entry?.login);
276
+ for (const slot of Object.values(entry?.leftovers ?? {}))
277
+ stripRetiredCurrentApp(slot);
278
+ }
279
+ return file;
190
280
  }
191
- function writeProjectCredentialsFile(projectRoot, file) {
192
- const dir = join(projectRoot, PROJECT_LOCAL_DIR);
281
+ /**
282
+ * The one way a project's credentials file is written: take the lock, read
283
+ * the file, apply `mutate`, and — when it reports a change — replace the file
284
+ * atomically while still holding the lock. Throws when the lock stays held by
285
+ * another writer, or is lost or overtaken by another writer's commit before
286
+ * this one; the file is then unchanged.
287
+ */
288
+ function updateProjectCredentialsFile(projectRoot, mutate) {
289
+ const path = credentialsFileForProject(projectRoot);
290
+ const dir = dirname(path);
193
291
  ensureDir(dir);
194
- const path = join(dir, PROJECT_CREDENTIALS_FILENAME);
195
- writeFileSync(path, JSON.stringify(file, null, 2), { mode: 0o600 });
292
+ const lock = acquireCredentialsLock(dir);
293
+ try {
294
+ const readVersion = fileVersion(path);
295
+ const file = loadProjectCredentialsFile(projectRoot);
296
+ if (mutate(file))
297
+ replaceFileUnderLock(lock, path, JSON.stringify(file, null, 2), readVersion);
298
+ }
299
+ finally {
300
+ lock.release();
301
+ }
302
+ }
303
+ const trimSlash = (url) => url.replace(/\/+$/, "");
304
+ /**
305
+ * Whether two server URLs name the same server: scheme and host compare
306
+ * case-insensitively and the path with trailing slashes removed. A URL that
307
+ * does not parse compares as its trailing-slash-trimmed string.
308
+ */
309
+ export function sameServer(a, b) {
310
+ try {
311
+ const x = new URL(a);
312
+ const y = new URL(b);
313
+ return (x.protocol.toLowerCase() === y.protocol.toLowerCase() &&
314
+ x.host.toLowerCase() === y.host.toLowerCase() &&
315
+ trimSlash(x.pathname) === trimSlash(y.pathname));
316
+ }
317
+ catch {
318
+ return trimSlash(a) === trimSlash(b);
319
+ }
320
+ }
321
+ /** The refusal of credentials issued by another server than the slot's environment's. */
322
+ function writeMismatch(envName, incoming, configured) {
323
+ return new PrimitiveEnvError("server-mismatch", `These credentials were issued by ${incoming}, but the "${envName}" login is for ${configured}; ` +
324
+ `nothing was stored. Run 'primitive -e ${envName} login' to sign in to ${configured}, or run ` +
325
+ `this command outside a project or from an environment that points at ${incoming}.`);
326
+ }
327
+ /** Whether a stored slot records a server other than `apiUrl`. */
328
+ function recordsOtherServer(stored, apiUrl) {
329
+ return stored.serverUrl !== undefined && !sameServer(stored.serverUrl, apiUrl);
330
+ }
331
+ /** The slot record for `credentials`, recording the server that issued them. */
332
+ function storedRecord(credentials) {
333
+ return {
334
+ serverUrl: credentials.serverUrl,
335
+ accessToken: credentials.accessToken,
336
+ refreshToken: credentials.refreshToken,
337
+ expiresAt: credentials.expiresAt,
338
+ adminId: credentials.adminId,
339
+ email: credentials.email,
340
+ role: credentials.role,
341
+ name: credentials.name,
342
+ globalAdminAppId: credentials.globalAdminAppId,
343
+ stepUp: credentials.stepUp,
344
+ };
345
+ }
346
+ /**
347
+ * The slot holding `env`'s login (#4511). A child environment uses its
348
+ * parent's slot, and the children of a slot are the machine-local entries of
349
+ * its tree on its server. A machine-local entry on another server is not a
350
+ * child: a login for one server says nothing about another, so it keeps a slot
351
+ * of its own.
352
+ */
353
+ function loginSlotOf(env) {
354
+ const own = { slot: env.name, children: [], serverUrl: env.config.apiUrl };
355
+ const project = getProjectConfig();
356
+ const parent = project?.environments[env.tree];
357
+ if (!project || !parent)
358
+ return own;
359
+ const onParentServer = (apiUrl) => sameServer(apiUrl, parent.apiUrl);
360
+ if (env.local && !onParentServer(env.config.apiUrl))
361
+ return own;
362
+ const children = Object.entries(readLocalState(env.projectConfigPath, project).environments)
363
+ .filter(([, entry]) => entry.tree === env.tree && onParentServer(entry.apiUrl))
364
+ .map(([name]) => name);
365
+ return { slot: env.tree, children, serverUrl: parent.apiUrl };
366
+ }
367
+ /** Whether two stored logins hold the same server session. */
368
+ function sameSession(a, b) {
369
+ const sid = sessionIdOfToken(a.accessToken);
370
+ if (sid !== undefined)
371
+ return sid === sessionIdOfToken(b.accessToken);
372
+ return Boolean(a.accessToken) && a.accessToken === b.accessToken;
373
+ }
374
+ /** The expiry (`exp`, in seconds) a JWT carries, or undefined. */
375
+ function expiryOfToken(token) {
376
+ if (!token)
377
+ return undefined;
196
378
  try {
197
- chmodSync(path, 0o600);
379
+ const payload = JSON.parse(Buffer.from(token.split(".")[1] ?? "", "base64url").toString("utf-8"));
380
+ return typeof payload?.exp === "number" ? payload.exp : undefined;
198
381
  }
199
382
  catch {
200
- // Best-effort on Windows / weird filesystems
383
+ return undefined;
384
+ }
385
+ }
386
+ /**
387
+ * Whether `slot`'s refresh token may outlive `login`'s: a copy refreshed in
388
+ * the child since it was made. It can still revoke the session after the
389
+ * parent's token has expired, so it is not thrown away unrevoked. Only an
390
+ * identical token, or one known to expire no later, is said not to.
391
+ */
392
+ function outlivesRefresh(slot, login) {
393
+ if (slot.refreshToken === login.refreshToken)
394
+ return false;
395
+ const mine = expiryOfToken(slot.refreshToken);
396
+ const theirs = expiryOfToken(login.refreshToken);
397
+ return mine === undefined || theirs === undefined || mine > theirs;
398
+ }
399
+ /**
400
+ * Whether `slot` holds a live step-up `login` does not: one a `login --scope
401
+ * admin` in the child stored beside the copied login. A step-up is a session
402
+ * of its own, which revoking the login's does not end.
403
+ */
404
+ function ownStepUp(slot, login) {
405
+ const stepUp = slot.stepUp;
406
+ if (!stepUp || new Date(stepUp.expiresAt).getTime() <= Date.now())
407
+ return false;
408
+ const sessionOf = (s) => s.sessionId || sessionIdOfToken(s.accessToken) || s.accessToken;
409
+ return !login.stepUp || sessionOf(stepUp) !== sessionOf(login.stepUp);
410
+ }
411
+ /**
412
+ * The child slots that are mere copies of `login`: written when a child was
413
+ * registered before children used their parent's login, holding its session
414
+ * and nothing a logout could still need. A child slot holding another
415
+ * session, a step-up of its own, or a refresh token that may outlive the
416
+ * parent's is not one: it stays (it is never read) until `logout` revokes it.
417
+ */
418
+ function copiedChildSlots(file, login, children) {
419
+ return children.filter((name) => {
420
+ const slot = file.environments[name];
421
+ return (slot !== undefined &&
422
+ sameSession(slot, login) &&
423
+ !outlivesRefresh(slot, login) &&
424
+ !ownStepUp(slot, login));
425
+ });
426
+ }
427
+ /**
428
+ * Removes the {@link copiedChildSlots} of the login in `slot`, deciding again
429
+ * under the lock against the file as it is then. Best-effort, so a read never
430
+ * fails on a file it cannot rewrite or a lock it cannot take.
431
+ */
432
+ function dropCopiedChildSlots(projectRoot, file, slot, children) {
433
+ const login = file.environments[slot];
434
+ if (!login || copiedChildSlots(file, login, children).length === 0)
435
+ return;
436
+ try {
437
+ updateProjectCredentialsFile(projectRoot, (current) => {
438
+ const currentLogin = current.environments[slot];
439
+ if (!currentLogin)
440
+ return false;
441
+ const copies = copiedChildSlots(current, currentLogin, children);
442
+ for (const name of copies)
443
+ delete current.environments[name];
444
+ return copies.length > 0;
445
+ });
446
+ }
447
+ catch {
448
+ // The copies are ignored either way; the next read tries again.
449
+ }
450
+ }
451
+ /**
452
+ * The key of `apiUrl`'s login in `servers` (#4512): scheme, host and port,
453
+ * and the path without trailing slashes. A URL that does not parse keys as
454
+ * its trailing-slash-trimmed string.
455
+ */
456
+ function serverKey(apiUrl) {
457
+ return normalizeServerUrl(apiUrl) ?? trimSlash(apiUrl);
458
+ }
459
+ /**
460
+ * The server of every environment of the project whose config is at
461
+ * `configPath`, committed and machine-local, by name in config order. Null
462
+ * when the config cannot be read, so a caller never takes "no environment"
463
+ * for an answer it did not get.
464
+ */
465
+ function environmentServers(configPath) {
466
+ const config = readJsonFile(configPath);
467
+ if (!config?.environments || typeof config.environments !== "object")
468
+ return null;
469
+ const servers = {};
470
+ for (const [name, entry] of Object.entries(config.environments)) {
471
+ if (typeof entry?.apiUrl === "string")
472
+ servers[name] = entry.apiUrl;
473
+ }
474
+ try {
475
+ for (const [name, entry] of Object.entries(readLocalState(configPath).environments)) {
476
+ if (!(name in servers))
477
+ servers[name] = entry.apiUrl;
478
+ }
479
+ }
480
+ catch {
481
+ // An unreadable local state names no machine-local environment.
482
+ }
483
+ return servers;
484
+ }
485
+ /** The project config of a project root, where `init` and `env remove` find it. */
486
+ function configPathOf(projectRoot) {
487
+ return join(projectRoot, PROJECT_TREE_DIR, PROJECT_CONFIG_FILENAME);
488
+ }
489
+ /**
490
+ * The legacy slots of `file` that belong to `server`: one recording that
491
+ * server, and one named after an environment on it, whatever it records. The
492
+ * latter is the slot that environment read; one recording another server was
493
+ * never used there, and keeps its server so `logout` revokes it where it was
494
+ * issued.
495
+ */
496
+ function legacySlotsOn(file, server, servers) {
497
+ const on = (apiUrl) => apiUrl !== undefined && serverKey(apiUrl) === server;
498
+ return Object.entries(file.environments)
499
+ .filter(([name, slot]) => on(slot.serverUrl) || on(servers?.[name]))
500
+ .map(([name]) => name);
501
+ }
502
+ /**
503
+ * Create `servers[server]` for the login about to be written, moving the
504
+ * server's legacy slots out of `environments`. A slot holding `login`'s
505
+ * session that nothing a logout needs is dropped (an identical or older
506
+ * refresh token, no live step-up of its own, #4511's rules); any other slot
507
+ * becomes a leftover. No-op once the server has an entry.
508
+ */
509
+ function migrateServer(file, server, login, servers) {
510
+ const existing = file.servers[server];
511
+ if (existing)
512
+ return existing;
513
+ const leftovers = {};
514
+ for (const name of legacySlotsOn(file, server, servers)) {
515
+ const slot = file.environments[name];
516
+ const copy = sameSession(slot, login) && !outlivesRefresh(slot, login) && !ownStepUp(slot, login);
517
+ if (!copy)
518
+ leftovers[name] = slot;
519
+ delete file.environments[name];
520
+ }
521
+ const entry = Object.keys(leftovers).length > 0 ? { leftovers } : {};
522
+ file.servers[server] = entry;
523
+ return entry;
524
+ }
525
+ /** The variable a CI job puts its admin token in (#3890). */
526
+ export const PRIMITIVE_TOKEN_VAR = "PRIMITIVE_TOKEN";
527
+ /**
528
+ * The partition header the live test harness needs when no credentials file
529
+ * names one. Deployed servers ignore the header for admin identity.
530
+ */
531
+ const PARTITION_VAR = "PRIMITIVE_GLOBAL_ADMIN_APP_ID";
532
+ /**
533
+ * The partition a login exchanges in when it builds on no stored login: the
534
+ * one `PRIMITIVE_GLOBAL_ADMIN_APP_ID` names, which only the live test harness
535
+ * sets. Deployed servers ignore the header for admin identity.
536
+ */
537
+ export function partitionFromEnvironment() {
538
+ return process.env[PARTITION_VAR]?.trim() || undefined;
539
+ }
540
+ /** The token in `PRIMITIVE_TOKEN`, or null when it is unset or blank. */
541
+ export function environmentToken() {
542
+ const value = process.env[PRIMITIVE_TOKEN_VAR]?.trim();
543
+ return value ? value : null;
544
+ }
545
+ /**
546
+ * Credentials made from `PRIMITIVE_TOKEN` for `serverUrl` (#3890): the token
547
+ * verbatim (an opaque CI token or a JWT), no refresh token, an expiry far
548
+ * enough away that no refresh is ever attempted, and no identity, which the
549
+ * server answers for. Null when the variable is unset or blank.
550
+ */
551
+ function environmentCredentials(serverUrl) {
552
+ const token = environmentToken();
553
+ if (!token)
554
+ return null;
555
+ const partition = process.env[PARTITION_VAR]?.trim();
556
+ return {
557
+ source: "env",
558
+ serverUrl,
559
+ accessToken: token,
560
+ refreshToken: "",
561
+ expiresAt: "9999-12-31T23:59:59.000Z",
562
+ adminId: "",
563
+ email: "",
564
+ role: "admin",
565
+ ...(partition ? { globalAdminAppId: partition } : {}),
566
+ };
567
+ }
568
+ /** Refuse to write credentials `PRIMITIVE_TOKEN` supplied. */
569
+ function assertStorable(credentials) {
570
+ if (credentials.source === "env") {
571
+ throw new Error("Credentials from PRIMITIVE_TOKEN are never written to disk.");
201
572
  }
202
573
  }
203
574
  /**
@@ -205,21 +576,72 @@ function writeProjectCredentialsFile(projectRoot, file) {
205
576
  * legacy global file. Returns the standard Credentials shape so existing
206
577
  * callers don't need to change.
207
578
  *
208
- * In project mode, we blend the per-env stored creds with the serverUrl
209
- * and appId from primitive/config.json to produce the Credentials the rest of
210
- * the CLI expects.
579
+ * In project mode, we blend the server's stored login with the serverUrl
580
+ * from primitive/config.json to produce the Credentials the rest of the CLI
581
+ * expects (#4512). Before the server's first write a legacy slot is read
582
+ * instead: the environment's own, or its parent's for a child environment
583
+ * (#4511), and any child slot found to be a copy of that login is removed.
584
+ *
585
+ * `PRIMITIVE_TOKEN` (#3890), when set, takes precedence over any stored
586
+ * login and no file is read. It does not loosen the outside-a-project
587
+ * refusal: the environment is resolved first, exactly as for a stored login.
588
+ *
589
+ * Outside a project under a server pin (#4905), the global login is returned
590
+ * only when the pinned server issued it, and `PRIMITIVE_TOKEN` is for the
591
+ * pinned server.
211
592
  */
212
593
  export function loadCredentialsStore() {
213
594
  const env = resolveCurrentEnvironment();
595
+ const fromEnvironment = environmentCredentials(env ? env.config.apiUrl : globalCredentialsServer ?? DEFAULT_SERVER_URL.replace(/\/$/, ""));
596
+ if (fromEnvironment)
597
+ return fromEnvironment;
214
598
  if (!env) {
215
- return loadLegacyCredentials();
599
+ const stored = loadLegacyCredentials();
600
+ if (stored &&
601
+ globalCredentialsServer !== null &&
602
+ (!stored.serverUrl || !sameServer(stored.serverUrl, globalCredentialsServer))) {
603
+ throw globalReadMismatch(stored.serverUrl, globalCredentialsServer);
604
+ }
605
+ return stored;
216
606
  }
217
607
  const file = loadProjectCredentialsFile(env.projectRoot);
218
- const stored = file.environments[env.name];
608
+ const entry = file.servers[serverKey(env.config.apiUrl)];
609
+ if (entry)
610
+ return entry.login ? storedCredentials(env.config.apiUrl, entry.login) : null;
611
+ const { slot, children } = loginSlotOf(env);
612
+ const stored = file.environments[slot];
219
613
  if (!stored)
220
614
  return null;
615
+ if (recordsOtherServer(stored, env.config.apiUrl)) {
616
+ throw new PrimitiveEnvError("server-mismatch", `The "${env.name}" login in ${getCredentialsFilePath()} was issued by ${stored.serverUrl}, ` +
617
+ `but "${env.name}" now points at ${env.config.apiUrl}; its tokens were not sent. ` +
618
+ `Run 'primitive -e ${env.name} login' to sign in there.`);
619
+ }
620
+ dropCopiedChildSlots(env.projectRoot, file, slot, children);
621
+ return storedCredentials(env.config.apiUrl, stored);
622
+ }
623
+ /**
624
+ * The current environment's legacy login when its slot records another server
625
+ * than the environment's, blended with the server it RECORDS; null otherwise,
626
+ * and always once the environment's server has a login entry (#4512). For
627
+ * `logout` alone, which revokes such a login at the server that issued it and
628
+ * so never sends its tokens anywhere else.
629
+ */
630
+ export function mismatchedLogin() {
631
+ const env = resolveCurrentEnvironment();
632
+ if (!env || environmentToken())
633
+ return null;
634
+ const file = loadProjectCredentialsFile(env.projectRoot);
635
+ if (file.servers[serverKey(env.config.apiUrl)])
636
+ return null;
637
+ const stored = file.environments[loginSlotOf(env).slot];
638
+ if (!stored || !recordsOtherServer(stored, env.config.apiUrl))
639
+ return null;
640
+ return storedCredentials(stored.serverUrl, stored);
641
+ }
642
+ function storedCredentials(serverUrl, stored) {
221
643
  return {
222
- serverUrl: env.config.apiUrl,
644
+ serverUrl,
223
645
  accessToken: stored.accessToken,
224
646
  refreshToken: stored.refreshToken,
225
647
  expiresAt: stored.expiresAt,
@@ -228,18 +650,114 @@ export function loadCredentialsStore() {
228
650
  role: stored.role,
229
651
  name: stored.name,
230
652
  globalAdminAppId: stored.globalAdminAppId,
653
+ ...(stored.stepUp ? { stepUp: stored.stepUp } : {}),
231
654
  };
232
655
  }
233
656
  /**
234
- * Saves credentials. In project mode, writes to the per-env slot in the
235
- * project credentials file. In legacy mode, writes the global file.
657
+ * The environments that share the current environment's login (#4512): every
658
+ * environment of the project on its server, committed and machine-local, in
659
+ * config order; and the committed environment whose tree the current one
660
+ * uses, which names the login. Null outside a project, where there is one
661
+ * global login.
662
+ */
663
+ export function loginEnvironments() {
664
+ const env = resolveCurrentEnvironment();
665
+ if (!env)
666
+ return null;
667
+ const server = serverKey(env.config.apiUrl);
668
+ const servers = environmentServers(env.projectConfigPath) ?? {};
669
+ const environments = Object.entries(servers)
670
+ .filter(([, apiUrl]) => serverKey(apiUrl) === server)
671
+ .map(([name]) => name);
672
+ return { tree: env.tree, environments: environments.length > 0 ? environments : [env.name] };
673
+ }
674
+ /** The current project's root, or null outside a project or when it cannot be resolved. */
675
+ function currentProjectRoot() {
676
+ try {
677
+ return getCurrentEnvironment()?.projectRoot ?? null;
678
+ }
679
+ catch {
680
+ return null;
681
+ }
682
+ }
683
+ /**
684
+ * What a login, logout or not-logged-in refusal adds about a linked git
685
+ * worktree (#4492). Each is null when it has nothing to say: outside a
686
+ * project, and where the project's login is its own.
687
+ */
688
+ export const worktreeLoginNotes = {
689
+ /** After a login stored in another worktree's file: where it went. */
690
+ storedElsewhere() {
691
+ const root = currentProjectRoot();
692
+ if (!root || credentialsRootFor(root) === root)
693
+ return null;
694
+ return `Stored in ${credentialsFileForProject(root)}, shared with every git worktree of this repository that holds this project.`;
695
+ },
696
+ /** After a logout of `slot`'s login, when more than one worktree uses it. */
697
+ sharedLogout(slot) {
698
+ const root = currentProjectRoot();
699
+ if (!root || sharedWorktreeCount(root) < 2)
700
+ return null;
701
+ return `The ${slot} login is shared by every git worktree of this repository that holds this project, so all of them are signed out.`;
702
+ },
703
+ /** A linked worktree's own credentials file, which is never read. */
704
+ ignoredFile() {
705
+ const root = currentProjectRoot();
706
+ const ignored = root ? ignoredLocalCredentialsFile(root) : null;
707
+ if (!root || !ignored)
708
+ return null;
709
+ return `Ignored ${ignored}: this worktree uses the shared login in ${credentialsFileForProject(root)}. You can delete the ignored file.`;
710
+ },
711
+ /** After "not logged in", when git is why a linked worktree has no login. */
712
+ notLoggedIn() {
713
+ const root = currentProjectRoot();
714
+ return root ? oldGitHint(root) : null;
715
+ },
716
+ };
717
+ /**
718
+ * The logins on the current environment's server that no command reads: the
719
+ * server's leftovers (#4512), or before its first write the legacy slots on
720
+ * it other than the one the environment reads — sessions signed in to another
721
+ * environment, and copies that kept a step-up or a refresh token of their own
722
+ * (#4511). `logout` revokes them before {@link clearLeftoverLogins} removes
723
+ * them. Empty outside a project.
724
+ */
725
+ export function leftoverLogins() {
726
+ const env = resolveCurrentEnvironment();
727
+ if (!env)
728
+ return [];
729
+ const file = loadProjectCredentialsFile(env.projectRoot);
730
+ const server = serverKey(env.config.apiUrl);
731
+ const entry = file.servers[server];
732
+ const stored = entry
733
+ ? Object.values(entry.leftovers ?? {})
734
+ : legacyLeftovers(file, env).map((name) => file.environments[name]);
735
+ return stored.map((slot) => storedCredentials(slot.serverUrl ?? env.config.apiUrl, slot));
736
+ }
737
+ /** The legacy slots on `env`'s server other than the one it reads. */
738
+ function legacyLeftovers(file, env) {
739
+ const own = loginSlotOf(env).slot;
740
+ return legacySlotsOn(file, serverKey(env.config.apiUrl), environmentServers(env.projectConfigPath)).filter((name) => name !== own);
741
+ }
742
+ /**
743
+ * Saves credentials. In project mode, writes the login of the environment's
744
+ * server (#4512), migrating the server's legacy slots at its first write. In
745
+ * legacy mode, writes the global file.
236
746
  *
237
- * The serverUrl on the incoming Credentials is used as-is for legacy
238
- * mode, and ignored in project mode (the env's apiUrl is canonical).
747
+ * In project mode the login records the incoming `serverUrl`, which must be
748
+ * the same server as the environment's `apiUrl`: credentials from another
749
+ * server are refused with `server-mismatch` and nothing is written (#4492).
750
+ * In legacy mode under a server pin (#4905), so are credentials from another
751
+ * server than the pinned one.
239
752
  */
240
753
  export function saveCredentialsStore(credentials) {
754
+ assertStorable(credentials);
241
755
  const env = resolveCurrentEnvironment();
242
756
  if (!env) {
757
+ if (globalCredentialsServer !== null && !sameServer(credentials.serverUrl, globalCredentialsServer)) {
758
+ throw new PrimitiveEnvError("server-mismatch", `These credentials were issued by ${credentials.serverUrl}, but this command targets ` +
759
+ `${globalCredentialsServer}; nothing was stored.`);
760
+ }
243
761
  ensureDir(getLegacyConfigDir());
244
762
  writeFileSync(getLegacyCredentialsFile(), JSON.stringify(legacyCredentialsRecord(credentials), null, 2), { mode: 0o600 });
245
763
  try {
@@ -250,22 +768,29 @@ export function saveCredentialsStore(credentials) {
250
768
  }
251
769
  return;
252
770
  }
253
- const file = loadProjectCredentialsFile(env.projectRoot);
254
- file.environments[env.name] = {
255
- accessToken: credentials.accessToken,
256
- refreshToken: credentials.refreshToken,
257
- expiresAt: credentials.expiresAt,
258
- adminId: credentials.adminId,
259
- email: credentials.email,
260
- role: credentials.role,
261
- name: credentials.name,
262
- globalAdminAppId: credentials.globalAdminAppId,
263
- };
264
- writeProjectCredentialsFile(env.projectRoot, file);
771
+ if (!sameServer(credentials.serverUrl, env.config.apiUrl)) {
772
+ throw writeMismatch(env.name, credentials.serverUrl, env.config.apiUrl);
773
+ }
774
+ writeServerLogin(env.projectRoot, environmentServers(env.projectConfigPath), credentials);
775
+ }
776
+ /** Write `credentials` as their server's login in the project at `projectRoot`. */
777
+ function writeServerLogin(projectRoot, servers, credentials) {
778
+ const record = storedRecord(credentials);
779
+ const server = serverKey(credentials.serverUrl);
780
+ updateProjectCredentialsFile(projectRoot, (file) => {
781
+ migrateServer(file, server, record, servers).login = record;
782
+ return true;
783
+ });
265
784
  }
266
785
  /**
267
786
  * Clears credentials for the current environment (project mode) or
268
- * deletes the legacy file (legacy mode).
787
+ * deletes the legacy file (legacy mode). In project mode that is the login of
788
+ * the environment's server; its entry and leftovers stay, so a legacy slot is
789
+ * never read for the server again (#4512). Before the server's first write it
790
+ * is the legacy slot the environment reads, and the child slots holding only
791
+ * that login's session (#4511), which its caller has just revoked. A slot
792
+ * holding another session or a step-up of its own stays for `logout` to
793
+ * revoke ({@link clearLeftoverLogins}).
269
794
  */
270
795
  export function clearCredentialsStore() {
271
796
  const env = resolveCurrentEnvironment();
@@ -280,57 +805,167 @@ export function clearCredentialsStore() {
280
805
  }
281
806
  return;
282
807
  }
808
+ if (!existsSync(credentialsFileForProject(env.projectRoot)))
809
+ return;
810
+ const server = serverKey(env.config.apiUrl);
811
+ const { slot, children } = loginSlotOf(env);
812
+ updateProjectCredentialsFile(env.projectRoot, (file) => {
813
+ const entry = file.servers[server];
814
+ if (entry) {
815
+ if (!entry.login)
816
+ return false;
817
+ delete entry.login;
818
+ return true;
819
+ }
820
+ const login = file.environments[slot];
821
+ if (!login)
822
+ return false;
823
+ for (const name of children) {
824
+ const child = file.environments[name];
825
+ if (child && sameSession(child, login) && !ownStepUp(child, login)) {
826
+ delete file.environments[name];
827
+ }
828
+ }
829
+ delete file.environments[slot];
830
+ return true;
831
+ });
832
+ }
833
+ /**
834
+ * Removes the logins {@link leftoverLogins} returned, once `logout` has
835
+ * revoked them: the server's leftovers, or before its first write the other
836
+ * legacy slots on it. No-op outside a project.
837
+ */
838
+ export function clearLeftoverLogins() {
839
+ const env = resolveCurrentEnvironment();
840
+ if (!env)
841
+ return;
842
+ const server = serverKey(env.config.apiUrl);
283
843
  const file = loadProjectCredentialsFile(env.projectRoot);
284
- delete file.environments[env.name];
285
- writeProjectCredentialsFile(env.projectRoot, file);
844
+ const entry = file.servers[server];
845
+ if (entry ? !entry.leftovers : legacyLeftovers(file, env).length === 0)
846
+ return;
847
+ updateProjectCredentialsFile(env.projectRoot, (current) => {
848
+ const currentEntry = current.servers[server];
849
+ if (currentEntry) {
850
+ if (!currentEntry.leftovers)
851
+ return false;
852
+ delete currentEntry.leftovers;
853
+ return true;
854
+ }
855
+ const names = legacyLeftovers(current, env);
856
+ for (const name of names)
857
+ delete current.environments[name];
858
+ return names.length > 0;
859
+ });
286
860
  }
287
861
  /**
288
- * Removes the credentials slot for the named environment in a specific
289
- * project's credentials file. Used by `env remove` so stale tokens from a
290
- * previously-removed environment can never be silently re-used if the same
291
- * name is later added back. Never throws.
862
+ * Removes what a removed environment leaves in a specific project's
863
+ * credentials file, so stale tokens from a previously-removed environment can
864
+ * never be silently re-used if the same name is later added back: a legacy
865
+ * slot or leftover of its name, and the login of the removed environment's
866
+ * server `removedApiUrl` when no remaining environment uses it (#4512). No
867
+ * other server's entry is touched: the file is shared by every worktree
868
+ * (#4492), and a server this worktree does not use may be another worktree's
869
+ * machine-local environment's. For the same reason the removed server's
870
+ * login stays while an environment of any worktree sharing the file uses it,
871
+ * or while that cannot be told. Used by `env remove`, after the environment
872
+ * left the config at `configPath`. Returns whether anything was removed.
873
+ * Never throws.
292
874
  */
293
- export function clearCredentialsForEnvInProject(projectRoot, envName) {
875
+ export function clearCredentialsForEnvInProject(projectRoot, envName, configPath = configPathOf(projectRoot), removedApiUrl) {
294
876
  try {
295
- const file = loadProjectCredentialsFile(projectRoot);
296
- if (file.environments[envName]) {
297
- delete file.environments[envName];
298
- writeProjectCredentialsFile(projectRoot, file);
877
+ const removed = removeCredentialsSlotInProject(projectRoot, envName);
878
+ if (removedApiUrl === undefined)
879
+ return removed;
880
+ const server = serverKey(removedApiUrl);
881
+ const others = otherSharingProjectRoots(projectRoot);
882
+ if (!others)
883
+ return removed;
884
+ for (const path of [configPath, ...others.map(configPathOf)]) {
885
+ const servers = environmentServers(path);
886
+ if (!servers)
887
+ return removed;
888
+ if (Object.values(servers).some((apiUrl) => serverKey(apiUrl) === server))
889
+ return removed;
299
890
  }
891
+ if (!loadProjectCredentialsFile(projectRoot).servers[server])
892
+ return removed;
893
+ updateProjectCredentialsFile(projectRoot, (file) => {
894
+ if (!file.servers[server])
895
+ return false;
896
+ delete file.servers[server];
897
+ return true;
898
+ });
899
+ return true;
300
900
  }
301
901
  catch {
302
902
  // Best-effort — removal is not a hard requirement for env removal to succeed.
903
+ return false;
303
904
  }
304
905
  }
906
+ /** Whether `file` holds a legacy slot or a leftover named `envName`. */
907
+ function holdsSlotOf(file, envName) {
908
+ return (file.environments[envName] !== undefined ||
909
+ Object.values(file.servers).some((entry) => entry.leftovers?.[envName] !== undefined));
910
+ }
911
+ /**
912
+ * Removes the legacy slot and the leftovers named `envName` in a specific
913
+ * project's credentials file; a server's login, which every environment on
914
+ * the server uses, is never one of them (#4512). Returns false when there was
915
+ * none; throws when there was one and the file could not be rewritten, so a
916
+ * caller that must not lose track of a login can tell the two apart.
917
+ */
918
+ export function removeCredentialsSlotInProject(projectRoot, envName) {
919
+ if (!holdsSlotOf(loadProjectCredentialsFile(projectRoot), envName))
920
+ return false;
921
+ let removed = false;
922
+ updateProjectCredentialsFile(projectRoot, (file) => {
923
+ removed = holdsSlotOf(file, envName);
924
+ delete file.environments[envName];
925
+ for (const entry of Object.values(file.servers)) {
926
+ if (!entry.leftovers?.[envName])
927
+ continue;
928
+ delete entry.leftovers[envName];
929
+ if (Object.keys(entry.leftovers).length === 0)
930
+ delete entry.leftovers;
931
+ }
932
+ return removed;
933
+ });
934
+ return removed;
935
+ }
305
936
  /**
306
- * Writes the credentials slot for the named environment in a specific
307
- * project's credentials file. Used by `primitive init` to carry the session
308
- * it just authenticated into the freshly scaffolded project, so the first
937
+ * Writes `credentials` as their server's login in a specific project's
938
+ * credentials file (#4512). Used by `primitive init` to carry the session it
939
+ * just authenticated into the freshly scaffolded project, so the first
309
940
  * in-project CLI command works without a second `primitive login`.
310
941
  */
311
- export function seedCredentialsForEnvInProject(projectRoot, envName, credentials) {
312
- const file = loadProjectCredentialsFile(projectRoot);
313
- file.environments[envName] = {
314
- accessToken: credentials.accessToken,
315
- refreshToken: credentials.refreshToken,
316
- expiresAt: credentials.expiresAt,
317
- adminId: credentials.adminId,
318
- email: credentials.email,
319
- role: credentials.role,
320
- name: credentials.name,
321
- globalAdminAppId: credentials.globalAdminAppId,
322
- };
323
- writeProjectCredentialsFile(projectRoot, file);
942
+ export function seedCredentialsForServerInProject(projectRoot, credentials) {
943
+ assertStorable(credentials);
944
+ writeServerLogin(projectRoot, environmentServers(configPathOf(projectRoot)), credentials);
324
945
  }
325
946
  /**
326
- * Lists all environments that have stored credentials in project mode.
327
- * Returns an empty array in legacy mode.
947
+ * Lists the environments of the project that have a login to use: every
948
+ * environment on a server holding a login, and before a server's first write
949
+ * the environments with a legacy slot of their own. Returns an empty array in
950
+ * legacy mode.
328
951
  */
329
952
  export function listAuthenticatedEnvironments() {
330
953
  const env = resolveCurrentEnvironment();
331
954
  if (!env)
332
955
  return [];
333
956
  const file = loadProjectCredentialsFile(env.projectRoot);
334
- return Object.keys(file.environments);
957
+ const servers = environmentServers(env.projectConfigPath) ?? {};
958
+ const names = new Set();
959
+ for (const [name, apiUrl] of Object.entries(servers)) {
960
+ const entry = file.servers[serverKey(apiUrl)];
961
+ if (entry ? entry.login : file.environments[name])
962
+ names.add(name);
963
+ }
964
+ for (const name of Object.keys(file.environments)) {
965
+ const apiUrl = servers[name];
966
+ if (apiUrl === undefined || !file.servers[serverKey(apiUrl)])
967
+ names.add(name);
968
+ }
969
+ return [...names];
335
970
  }
336
971
  //# sourceMappingURL=credentials-store.js.map