primitive-admin 1.1.0-alpha.7 → 1.1.0-alpha.71

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 (438) hide show
  1. package/README.md +404 -80
  2. package/assets/skill/skills/primitive-platform/SKILL.md +808 -0
  3. package/dist/bin/primitive.d.ts +2 -0
  4. package/dist/bin/primitive.js +294 -21
  5. package/dist/bin/primitive.js.map +1 -1
  6. package/dist/src/commands/admins.d.ts +2 -0
  7. package/dist/src/commands/admins.js +138 -19
  8. package/dist/src/commands/admins.js.map +1 -1
  9. package/dist/src/commands/analytics.d.ts +2 -0
  10. package/dist/src/commands/analytics.js +544 -55
  11. package/dist/src/commands/analytics.js.map +1 -1
  12. package/dist/src/commands/apps.d.ts +2 -0
  13. package/dist/src/commands/apps.js +51 -96
  14. package/dist/src/commands/apps.js.map +1 -1
  15. package/dist/src/commands/auth.d.ts +2 -0
  16. package/dist/src/commands/auth.js +177 -7
  17. package/dist/src/commands/auth.js.map +1 -1
  18. package/dist/src/commands/blob-buckets.d.ts +2 -0
  19. package/dist/src/commands/blob-buckets.js +330 -0
  20. package/dist/src/commands/blob-buckets.js.map +1 -0
  21. package/dist/src/commands/catalog.d.ts +2 -0
  22. package/dist/src/commands/catalog.js +37 -38
  23. package/dist/src/commands/catalog.js.map +1 -1
  24. package/dist/src/commands/collection-type-configs.d.ts +2 -0
  25. package/dist/src/commands/collection-type-configs.js +92 -0
  26. package/dist/src/commands/collection-type-configs.js.map +1 -0
  27. package/dist/src/commands/collections.d.ts +2 -0
  28. package/dist/src/commands/collections.js +565 -0
  29. package/dist/src/commands/collections.js.map +1 -0
  30. package/dist/src/commands/comparisons.d.ts +2 -0
  31. package/dist/src/commands/comparisons.js +6 -6
  32. package/dist/src/commands/comparisons.js.map +1 -1
  33. package/dist/src/commands/config.d.ts +46 -0
  34. package/dist/src/commands/config.js +479 -0
  35. package/dist/src/commands/config.js.map +1 -0
  36. package/dist/src/commands/connections.d.ts +2 -0
  37. package/dist/src/commands/connections.js +100 -0
  38. package/dist/src/commands/connections.js.map +1 -0
  39. package/dist/src/commands/cron-triggers.d.ts +2 -0
  40. package/dist/src/commands/cron-triggers.js +265 -0
  41. package/dist/src/commands/cron-triggers.js.map +1 -0
  42. package/dist/src/commands/database-type-configs.d.ts +2 -0
  43. package/dist/src/commands/database-type-configs.js +171 -0
  44. package/dist/src/commands/database-type-configs.js.map +1 -0
  45. package/dist/src/commands/database-types.d.ts +2 -0
  46. package/dist/src/commands/database-types.js +471 -0
  47. package/dist/src/commands/database-types.js.map +1 -0
  48. package/dist/src/commands/databases.d.ts +65 -0
  49. package/dist/src/commands/databases.js +2140 -112
  50. package/dist/src/commands/databases.js.map +1 -1
  51. package/dist/src/commands/documents.d.ts +2 -0
  52. package/dist/src/commands/documents.js +1357 -19
  53. package/dist/src/commands/documents.js.map +1 -1
  54. package/dist/src/commands/email-templates.d.ts +2 -0
  55. package/dist/src/commands/email-templates.js +174 -0
  56. package/dist/src/commands/email-templates.js.map +1 -0
  57. package/dist/src/commands/env.d.ts +23 -0
  58. package/dist/src/commands/env.js +333 -0
  59. package/dist/src/commands/env.js.map +1 -0
  60. package/dist/src/commands/feature-flags.d.ts +14 -0
  61. package/dist/src/commands/feature-flags.js +116 -0
  62. package/dist/src/commands/feature-flags.js.map +1 -0
  63. package/dist/src/commands/group-type-configs.d.ts +2 -0
  64. package/dist/src/commands/group-type-configs.js +86 -0
  65. package/dist/src/commands/group-type-configs.js.map +1 -0
  66. package/dist/src/commands/groups.d.ts +2 -0
  67. package/dist/src/commands/groups.js +38 -99
  68. package/dist/src/commands/groups.js.map +1 -1
  69. package/dist/src/commands/guides.d.ts +223 -0
  70. package/dist/src/commands/guides.js +617 -65
  71. package/dist/src/commands/guides.js.map +1 -1
  72. package/dist/src/commands/init.d.ts +25 -0
  73. package/dist/src/commands/init.js +1605 -208
  74. package/dist/src/commands/init.js.map +1 -1
  75. package/dist/src/commands/integrations.d.ts +2 -0
  76. package/dist/src/commands/integrations.js +380 -178
  77. package/dist/src/commands/integrations.js.map +1 -1
  78. package/dist/src/commands/llm.d.ts +2 -0
  79. package/dist/src/commands/llm.js +4 -2
  80. package/dist/src/commands/llm.js.map +1 -1
  81. package/dist/src/commands/locks.d.ts +8 -0
  82. package/dist/src/commands/locks.js +160 -0
  83. package/dist/src/commands/locks.js.map +1 -0
  84. package/dist/src/commands/metadata-category-configs.d.ts +12 -0
  85. package/dist/src/commands/metadata-category-configs.js +112 -0
  86. package/dist/src/commands/metadata-category-configs.js.map +1 -0
  87. package/dist/src/commands/metadata.d.ts +2 -0
  88. package/dist/src/commands/metadata.js +281 -0
  89. package/dist/src/commands/metadata.js.map +1 -0
  90. package/dist/src/commands/prompts.d.ts +2 -0
  91. package/dist/src/commands/prompts.js +225 -584
  92. package/dist/src/commands/prompts.js.map +1 -1
  93. package/dist/src/commands/rule-sets.d.ts +3 -0
  94. package/dist/src/commands/rule-sets.js +272 -0
  95. package/dist/src/commands/rule-sets.js.map +1 -0
  96. package/dist/src/commands/scripts.d.ts +20 -0
  97. package/dist/src/commands/scripts.js +554 -0
  98. package/dist/src/commands/scripts.js.map +1 -0
  99. package/dist/src/commands/secrets.d.ts +2 -0
  100. package/dist/src/commands/secrets.js +108 -0
  101. package/dist/src/commands/secrets.js.map +1 -0
  102. package/dist/src/commands/sessions.d.ts +2 -0
  103. package/dist/src/commands/sessions.js +75 -0
  104. package/dist/src/commands/sessions.js.map +1 -0
  105. package/dist/src/commands/skill.d.ts +2 -0
  106. package/dist/src/commands/skill.js +29 -0
  107. package/dist/src/commands/skill.js.map +1 -0
  108. package/dist/src/commands/sync-app-settings.d.ts +158 -0
  109. package/dist/src/commands/sync-app-settings.js +330 -0
  110. package/dist/src/commands/sync-app-settings.js.map +1 -0
  111. package/dist/src/commands/sync.d.ts +2323 -0
  112. package/dist/src/commands/sync.js +15065 -843
  113. package/dist/src/commands/sync.js.map +1 -1
  114. package/dist/src/commands/tokens.d.ts +2 -0
  115. package/dist/src/commands/tokens.js +130 -21
  116. package/dist/src/commands/tokens.js.map +1 -1
  117. package/dist/src/commands/users.d.ts +2 -0
  118. package/dist/src/commands/users.js +532 -23
  119. package/dist/src/commands/users.js.map +1 -1
  120. package/dist/src/commands/vars.d.ts +8 -0
  121. package/dist/src/commands/vars.js +96 -0
  122. package/dist/src/commands/vars.js.map +1 -0
  123. package/dist/src/commands/waitlist.d.ts +2 -0
  124. package/dist/src/commands/waitlist.js +10 -10
  125. package/dist/src/commands/waitlist.js.map +1 -1
  126. package/dist/src/commands/webhooks.d.ts +2 -0
  127. package/dist/src/commands/webhooks.js +562 -0
  128. package/dist/src/commands/webhooks.js.map +1 -0
  129. package/dist/src/commands/workflows.d.ts +116 -0
  130. package/dist/src/commands/workflows.js +1583 -681
  131. package/dist/src/commands/workflows.js.map +1 -1
  132. package/dist/src/lib/access-rule-display.d.ts +21 -0
  133. package/dist/src/lib/access-rule-display.js +34 -0
  134. package/dist/src/lib/access-rule-display.js.map +1 -0
  135. package/dist/src/lib/api-client.d.ts +1936 -0
  136. package/dist/src/lib/api-client.js +1826 -138
  137. package/dist/src/lib/api-client.js.map +1 -1
  138. package/dist/src/lib/app-settings-descriptor.d.ts +263 -0
  139. package/dist/src/lib/app-settings-descriptor.js +575 -0
  140. package/dist/src/lib/app-settings-descriptor.js.map +1 -0
  141. package/dist/src/lib/auth-flow.d.ts +8 -0
  142. package/dist/src/lib/batch.d.ts +26 -0
  143. package/dist/src/lib/batch.js +32 -0
  144. package/dist/src/lib/batch.js.map +1 -0
  145. package/dist/src/lib/block-layout.d.ts +160 -0
  146. package/dist/src/lib/block-layout.js +451 -0
  147. package/dist/src/lib/block-layout.js.map +1 -0
  148. package/dist/src/lib/canonical-json.d.ts +12 -0
  149. package/dist/src/lib/canonical-json.js +35 -0
  150. package/dist/src/lib/canonical-json.js.map +1 -0
  151. package/dist/src/lib/channel.d.ts +30 -0
  152. package/dist/src/lib/channel.js +68 -0
  153. package/dist/src/lib/channel.js.map +1 -0
  154. package/dist/src/lib/cli-manifest.d.ts +68 -0
  155. package/dist/src/lib/cli-manifest.js +71 -0
  156. package/dist/src/lib/cli-manifest.js.map +1 -0
  157. package/dist/src/lib/codegen-shared/generatedFiles.d.ts +101 -0
  158. package/dist/src/lib/codegen-shared/generatedFiles.js +191 -0
  159. package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -0
  160. package/dist/src/lib/codegen-shared/prettierStable.d.ts +262 -0
  161. package/dist/src/lib/codegen-shared/prettierStable.js +610 -0
  162. package/dist/src/lib/codegen-shared/prettierStable.js.map +1 -0
  163. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +68 -0
  164. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +168 -0
  165. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -0
  166. package/dist/src/lib/config-object-descriptor.d.ts +127 -0
  167. package/dist/src/lib/config-object-descriptor.js +658 -0
  168. package/dist/src/lib/config-object-descriptor.js.map +1 -0
  169. package/dist/src/lib/config-payload.d.ts +85 -0
  170. package/dist/src/lib/config-payload.js +116 -0
  171. package/dist/src/lib/config-payload.js.map +1 -0
  172. package/dist/src/lib/config-surface.d.ts +130 -0
  173. package/dist/src/lib/config-surface.js +300 -0
  174. package/dist/src/lib/config-surface.js.map +1 -0
  175. package/dist/src/lib/config-toml.d.ts +10 -0
  176. package/dist/src/lib/config-toml.js +42 -0
  177. package/dist/src/lib/config-toml.js.map +1 -0
  178. package/dist/src/lib/config.d.ts +53 -0
  179. package/dist/src/lib/config.js +92 -53
  180. package/dist/src/lib/config.js.map +1 -1
  181. package/dist/src/lib/confirm-prompt.d.ts +83 -0
  182. package/dist/src/lib/confirm-prompt.js +110 -0
  183. package/dist/src/lib/confirm-prompt.js.map +1 -0
  184. package/dist/src/lib/constants.d.ts +11 -0
  185. package/dist/src/lib/constants.js +12 -0
  186. package/dist/src/lib/constants.js.map +1 -0
  187. package/dist/src/lib/crash-handlers.d.ts +20 -0
  188. package/dist/src/lib/crash-handlers.js +49 -0
  189. package/dist/src/lib/crash-handlers.js.map +1 -0
  190. package/dist/src/lib/credentials-store.d.ts +89 -0
  191. package/dist/src/lib/credentials-store.js +330 -0
  192. package/dist/src/lib/credentials-store.js.map +1 -0
  193. package/dist/src/lib/csv.d.ts +47 -0
  194. package/dist/src/lib/csv.js +172 -0
  195. package/dist/src/lib/csv.js.map +1 -0
  196. package/dist/src/lib/data-input.d.ts +23 -0
  197. package/dist/src/lib/data-input.js +50 -0
  198. package/dist/src/lib/data-input.js.map +1 -0
  199. package/dist/src/lib/db-codegen/dbFingerprint.d.ts +10 -0
  200. package/dist/src/lib/db-codegen/dbFingerprint.js +17 -0
  201. package/dist/src/lib/db-codegen/dbFingerprint.js.map +1 -0
  202. package/dist/src/lib/db-codegen/dbGenerator.d.ts +67 -0
  203. package/dist/src/lib/db-codegen/dbGenerator.js +170 -0
  204. package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -0
  205. package/dist/src/lib/db-codegen/dbNaming.d.ts +87 -0
  206. package/dist/src/lib/db-codegen/dbNaming.js +180 -0
  207. package/dist/src/lib/db-codegen/dbNaming.js.map +1 -0
  208. package/dist/src/lib/db-codegen/dbTemplates.d.ts +272 -0
  209. package/dist/src/lib/db-codegen/dbTemplates.js +480 -0
  210. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -0
  211. package/dist/src/lib/db-codegen/dbTsTypes.d.ts +73 -0
  212. package/dist/src/lib/db-codegen/dbTsTypes.js +139 -0
  213. package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -0
  214. package/dist/src/lib/db-codegen/dbTypeIR.d.ts +146 -0
  215. package/dist/src/lib/db-codegen/dbTypeIR.js +517 -0
  216. package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -0
  217. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.d.ts +112 -0
  218. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js +211 -0
  219. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js.map +1 -0
  220. package/dist/src/lib/env-resolver-core.d.ts +147 -0
  221. package/dist/src/lib/env-resolver-core.js +265 -0
  222. package/dist/src/lib/env-resolver-core.js.map +1 -0
  223. package/dist/src/lib/env-resolver.d.ts +84 -0
  224. package/dist/src/lib/env-resolver.js +133 -0
  225. package/dist/src/lib/env-resolver.js.map +1 -0
  226. package/dist/src/lib/fetch.d.ts +5 -0
  227. package/dist/src/lib/generated-allowlist.d.ts +28 -0
  228. package/dist/src/lib/generated-allowlist.js +277 -0
  229. package/dist/src/lib/generated-allowlist.js.map +1 -0
  230. package/dist/src/lib/generated-config-surfaces.d.ts +682 -0
  231. package/dist/src/lib/generated-config-surfaces.js +4058 -0
  232. package/dist/src/lib/generated-config-surfaces.js.map +1 -0
  233. package/dist/src/lib/generated-template-lint.d.ts +212 -0
  234. package/dist/src/lib/generated-template-lint.js +624 -0
  235. package/dist/src/lib/generated-template-lint.js.map +1 -0
  236. package/dist/src/lib/init-adopt.d.ts +16 -0
  237. package/dist/src/lib/init-adopt.js +34 -0
  238. package/dist/src/lib/init-adopt.js.map +1 -0
  239. package/dist/src/lib/init-assets.d.ts +39 -0
  240. package/dist/src/lib/init-assets.js +97 -0
  241. package/dist/src/lib/init-assets.js.map +1 -0
  242. package/dist/src/lib/init-config.d.ts +98 -0
  243. package/dist/src/lib/init-config.js +186 -0
  244. package/dist/src/lib/init-config.js.map +1 -0
  245. package/dist/src/lib/init-ios-links.d.ts +50 -0
  246. package/dist/src/lib/init-ios-links.js +153 -0
  247. package/dist/src/lib/init-ios-links.js.map +1 -0
  248. package/dist/src/lib/init-plan.d.ts +80 -0
  249. package/dist/src/lib/init-plan.js +95 -0
  250. package/dist/src/lib/init-plan.js.map +1 -0
  251. package/dist/src/lib/init-production-env.d.ts +48 -0
  252. package/dist/src/lib/init-production-env.js +59 -0
  253. package/dist/src/lib/init-production-env.js.map +1 -0
  254. package/dist/src/lib/init-schema.d.ts +74 -0
  255. package/dist/src/lib/init-schema.js +358 -0
  256. package/dist/src/lib/init-schema.js.map +1 -0
  257. package/dist/src/lib/init-xcode.d.ts +33 -0
  258. package/dist/src/lib/init-xcode.js +114 -0
  259. package/dist/src/lib/init-xcode.js.map +1 -0
  260. package/dist/src/lib/integration-request-config.d.ts +30 -0
  261. package/dist/src/lib/integration-request-config.js +145 -0
  262. package/dist/src/lib/integration-request-config.js.map +1 -0
  263. package/dist/src/lib/local-state.d.ts +55 -0
  264. package/dist/src/lib/local-state.js +167 -0
  265. package/dist/src/lib/local-state.js.map +1 -0
  266. package/dist/src/lib/log-inspection.d.ts +568 -0
  267. package/dist/src/lib/log-inspection.js +639 -0
  268. package/dist/src/lib/log-inspection.js.map +1 -0
  269. package/dist/src/lib/migration-nag.d.ts +49 -0
  270. package/dist/src/lib/migration-nag.js +163 -0
  271. package/dist/src/lib/migration-nag.js.map +1 -0
  272. package/dist/src/lib/object-status-filter.d.ts +22 -0
  273. package/dist/src/lib/object-status-filter.js +45 -0
  274. package/dist/src/lib/object-status-filter.js.map +1 -0
  275. package/dist/src/lib/output.d.ts +109 -0
  276. package/dist/src/lib/output.js +191 -8
  277. package/dist/src/lib/output.js.map +1 -1
  278. package/dist/src/lib/package-manager.d.ts +140 -0
  279. package/dist/src/lib/package-manager.js +305 -0
  280. package/dist/src/lib/package-manager.js.map +1 -0
  281. package/dist/src/lib/paginate.d.ts +83 -0
  282. package/dist/src/lib/paginate.js +95 -0
  283. package/dist/src/lib/paginate.js.map +1 -0
  284. package/dist/src/lib/platform-owned.d.ts +63 -0
  285. package/dist/src/lib/platform-owned.js +85 -0
  286. package/dist/src/lib/platform-owned.js.map +1 -0
  287. package/dist/src/lib/project-config.d.ts +97 -0
  288. package/dist/src/lib/project-config.js +217 -0
  289. package/dist/src/lib/project-config.js.map +1 -0
  290. package/dist/src/lib/query-operators.d.ts +43 -0
  291. package/dist/src/lib/query-operators.js +80 -0
  292. package/dist/src/lib/query-operators.js.map +1 -0
  293. package/dist/src/lib/record-filter.d.ts +18 -0
  294. package/dist/src/lib/record-filter.js +55 -0
  295. package/dist/src/lib/record-filter.js.map +1 -0
  296. package/dist/src/lib/refresh-admin-credentials.d.ts +65 -0
  297. package/dist/src/lib/refresh-admin-credentials.js +103 -0
  298. package/dist/src/lib/refresh-admin-credentials.js.map +1 -0
  299. package/dist/src/lib/resolve-init-dev-port.d.ts +56 -0
  300. package/dist/src/lib/resolve-init-dev-port.js +55 -0
  301. package/dist/src/lib/resolve-init-dev-port.js.map +1 -0
  302. package/dist/src/lib/resolve-init-server.d.ts +64 -0
  303. package/dist/src/lib/resolve-init-server.js +77 -0
  304. package/dist/src/lib/resolve-init-server.js.map +1 -0
  305. package/dist/src/lib/resolve-platform.d.ts +74 -0
  306. package/dist/src/lib/resolve-platform.js +105 -0
  307. package/dist/src/lib/resolve-platform.js.map +1 -0
  308. package/dist/src/lib/run-status.d.ts +19 -0
  309. package/dist/src/lib/run-status.generated.d.ts +39 -0
  310. package/dist/src/lib/run-status.generated.js +66 -0
  311. package/dist/src/lib/run-status.generated.js.map +1 -0
  312. package/dist/src/lib/run-status.js +19 -0
  313. package/dist/src/lib/run-status.js.map +1 -0
  314. package/dist/src/lib/server-text-normalization.d.ts +51 -0
  315. package/dist/src/lib/server-text-normalization.js +90 -0
  316. package/dist/src/lib/server-text-normalization.js.map +1 -0
  317. package/dist/src/lib/server-url.d.ts +22 -0
  318. package/dist/src/lib/server-url.js +33 -0
  319. package/dist/src/lib/server-url.js.map +1 -0
  320. package/dist/src/lib/signing-secret-status.d.ts +81 -0
  321. package/dist/src/lib/signing-secret-status.js +116 -0
  322. package/dist/src/lib/signing-secret-status.js.map +1 -0
  323. package/dist/src/lib/skill-installer.d.ts +25 -0
  324. package/dist/src/lib/skill-installer.js +266 -0
  325. package/dist/src/lib/skill-installer.js.map +1 -0
  326. package/dist/src/lib/snapshots.d.ts +99 -0
  327. package/dist/src/lib/snapshots.js +357 -0
  328. package/dist/src/lib/snapshots.js.map +1 -0
  329. package/dist/src/lib/swift-codegen/dbGenerator.d.ts +113 -0
  330. package/dist/src/lib/swift-codegen/dbGenerator.js +914 -0
  331. package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -0
  332. package/dist/src/lib/swift-codegen/dbSwiftTypes.d.ts +42 -0
  333. package/dist/src/lib/swift-codegen/dbSwiftTypes.js +100 -0
  334. package/dist/src/lib/swift-codegen/dbSwiftTypes.js.map +1 -0
  335. package/dist/src/lib/swift-codegen/generator.d.ts +94 -0
  336. package/dist/src/lib/swift-codegen/generator.js +440 -0
  337. package/dist/src/lib/swift-codegen/generator.js.map +1 -0
  338. package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +72 -0
  339. package/dist/src/lib/swift-codegen/schemaToSwift.js +644 -0
  340. package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -0
  341. package/dist/src/lib/swift-codegen/siblingSymbols.d.ts +94 -0
  342. package/dist/src/lib/swift-codegen/siblingSymbols.js +155 -0
  343. package/dist/src/lib/swift-codegen/siblingSymbols.js.map +1 -0
  344. package/dist/src/lib/swift-codegen/swiftNaming.d.ts +85 -0
  345. package/dist/src/lib/swift-codegen/swiftNaming.js +198 -0
  346. package/dist/src/lib/swift-codegen/swiftNaming.js.map +1 -0
  347. package/dist/src/lib/sync-dir-selector.d.ts +21 -0
  348. package/dist/src/lib/sync-dir-selector.js +30 -0
  349. package/dist/src/lib/sync-dir-selector.js.map +1 -0
  350. package/dist/src/lib/sync-paths.d.ts +111 -0
  351. package/dist/src/lib/sync-paths.js +198 -0
  352. package/dist/src/lib/sync-paths.js.map +1 -0
  353. package/dist/src/lib/sync-resource-types.d.ts +544 -0
  354. package/dist/src/lib/sync-resource-types.js +975 -0
  355. package/dist/src/lib/sync-resource-types.js.map +1 -0
  356. package/dist/src/lib/sync-selectors.d.ts +95 -0
  357. package/dist/src/lib/sync-selectors.js +228 -0
  358. package/dist/src/lib/sync-selectors.js.map +1 -0
  359. package/dist/src/lib/template.d.ts +170 -0
  360. package/dist/src/lib/template.js +484 -68
  361. package/dist/src/lib/template.js.map +1 -1
  362. package/dist/src/lib/test-case-keys.d.ts +29 -0
  363. package/dist/src/lib/test-case-keys.js +55 -0
  364. package/dist/src/lib/test-case-keys.js.map +1 -0
  365. package/dist/src/lib/test-case-variables.d.ts +15 -0
  366. package/dist/src/lib/test-case-variables.js +29 -0
  367. package/dist/src/lib/test-case-variables.js.map +1 -0
  368. package/dist/src/lib/token-inject.d.ts +56 -0
  369. package/dist/src/lib/token-inject.js +204 -0
  370. package/dist/src/lib/token-inject.js.map +1 -0
  371. package/dist/src/lib/toml-database-config.d.ts +123 -0
  372. package/dist/src/lib/toml-database-config.js +527 -0
  373. package/dist/src/lib/toml-database-config.js.map +1 -0
  374. package/dist/src/lib/toml-metadata-config.d.ts +151 -0
  375. package/dist/src/lib/toml-metadata-config.js +476 -0
  376. package/dist/src/lib/toml-metadata-config.js.map +1 -0
  377. package/dist/src/lib/toml-native-form.d.ts +46 -0
  378. package/dist/src/lib/toml-native-form.js +78 -0
  379. package/dist/src/lib/toml-native-form.js.map +1 -0
  380. package/dist/src/lib/toml-params-validator.d.ts +129 -0
  381. package/dist/src/lib/toml-params-validator.js +298 -0
  382. package/dist/src/lib/toml-params-validator.js.map +1 -0
  383. package/dist/src/lib/toml-scalar-edit.d.ts +43 -0
  384. package/dist/src/lib/toml-scalar-edit.js +283 -0
  385. package/dist/src/lib/toml-scalar-edit.js.map +1 -0
  386. package/dist/src/lib/user-selector.d.ts +24 -0
  387. package/dist/src/lib/user-selector.js +33 -0
  388. package/dist/src/lib/user-selector.js.map +1 -0
  389. package/dist/src/lib/version-check.d.ts +35 -0
  390. package/dist/src/lib/version-check.js +241 -0
  391. package/dist/src/lib/version-check.js.map +1 -0
  392. package/dist/src/lib/watch.d.ts +121 -0
  393. package/dist/src/lib/watch.js +169 -0
  394. package/dist/src/lib/watch.js.map +1 -0
  395. package/dist/src/lib/workflow-apply.d.ts +110 -0
  396. package/dist/src/lib/workflow-apply.js +164 -0
  397. package/dist/src/lib/workflow-apply.js.map +1 -0
  398. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.d.ts +129 -0
  399. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js +269 -0
  400. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js.map +1 -0
  401. package/dist/src/lib/workflow-codegen/generator.d.ts +96 -0
  402. package/dist/src/lib/workflow-codegen/generator.js +361 -0
  403. package/dist/src/lib/workflow-codegen/generator.js.map +1 -0
  404. package/dist/src/lib/workflow-codegen/invokerIR.d.ts +94 -0
  405. package/dist/src/lib/workflow-codegen/invokerIR.js +76 -0
  406. package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -0
  407. package/dist/src/lib/workflow-codegen/naming.d.ts +33 -0
  408. package/dist/src/lib/workflow-codegen/naming.js +81 -0
  409. package/dist/src/lib/workflow-codegen/naming.js.map +1 -0
  410. package/dist/src/lib/workflow-codegen/schemaToTs.d.ts +80 -0
  411. package/dist/src/lib/workflow-codegen/schemaToTs.js +303 -0
  412. package/dist/src/lib/workflow-codegen/schemaToTs.js.map +1 -0
  413. package/dist/src/lib/workflow-config-apply.d.ts +70 -0
  414. package/dist/src/lib/workflow-config-apply.js +137 -0
  415. package/dist/src/lib/workflow-config-apply.js.map +1 -0
  416. package/dist/src/lib/workflow-config-sidecar.d.ts +63 -0
  417. package/dist/src/lib/workflow-config-sidecar.js +96 -0
  418. package/dist/src/lib/workflow-config-sidecar.js.map +1 -0
  419. package/dist/src/lib/workflow-defaults.d.ts +29 -0
  420. package/dist/src/lib/workflow-defaults.js +41 -0
  421. package/dist/src/lib/workflow-defaults.js.map +1 -0
  422. package/dist/src/lib/workflow-fragments.d.ts +64 -0
  423. package/dist/src/lib/workflow-fragments.js +342 -0
  424. package/dist/src/lib/workflow-fragments.js.map +1 -0
  425. package/dist/src/lib/workflow-include-preserve.d.ts +76 -0
  426. package/dist/src/lib/workflow-include-preserve.js +286 -0
  427. package/dist/src/lib/workflow-include-preserve.js.map +1 -0
  428. package/dist/src/lib/workflow-payload.d.ts +98 -0
  429. package/dist/src/lib/workflow-payload.js +178 -0
  430. package/dist/src/lib/workflow-payload.js.map +1 -0
  431. package/dist/src/lib/workflow-toml-validator.d.ts +202 -0
  432. package/dist/src/lib/workflow-toml-validator.js +757 -0
  433. package/dist/src/lib/workflow-toml-validator.js.map +1 -0
  434. package/dist/src/types/index.d.ts +581 -0
  435. package/dist/src/validators.d.ts +65 -0
  436. package/dist/src/validators.js +64 -0
  437. package/dist/src/validators.js.map +1 -0
  438. package/package.json +32 -8
@@ -1,15 +1,880 @@
1
1
  import { ApiClient } from "../lib/api-client.js";
2
2
  import { resolveAppId } from "../lib/config.js";
3
- import { success, error, info, formatTable, formatDate, json, } from "../lib/output.js";
3
+ import { success, error, info, formatTable, formatId, formatDate, json, keyValue, result as printResult, warn, } from "../lib/output.js";
4
+ import { confirmPrompt } from "../lib/confirm-prompt.js";
5
+ import { parseDataOption } from "../lib/data-input.js";
6
+ import { parseFilterOptions } from "../lib/record-filter.js";
7
+ import { ulid } from "ulid";
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ /**
11
+ * Resolve a `--owner` value to a userId. A value containing "@" is looked up
12
+ * by exact email through the member-accessible `users/lookup` endpoint
13
+ * (issue #2763); anything else is already a user id and is passed through.
14
+ * Failures throw with the server's own message — a permission problem must
15
+ * not read as "no such user", which is how the old admin-only lookup
16
+ * reported it.
17
+ */
18
+ async function resolveOwnerUserId(client, appId, owner) {
19
+ if (!owner.includes("@"))
20
+ return owner;
21
+ const lookup = await client.lookupUserByEmail(appId, owner);
22
+ if (!lookup?.exists || !lookup.user) {
23
+ throw new Error(`No app user found with email "${owner}" in app ${appId}.`);
24
+ }
25
+ info(`Resolved owner email to userId: ${lookup.user.userId}`);
26
+ return lookup.user.userId;
27
+ }
4
28
  export function registerDocumentsCommands(program) {
5
29
  const documents = program
6
30
  .command("documents")
7
31
  .description("Manage documents within an app")
8
32
  .addHelpText("after", `
9
33
  Examples:
34
+ $ primitive documents list --user-id <user-id>
35
+ $ primitive documents get <document-id>
36
+ $ primitive documents create "My Doc"
37
+ $ primitive documents create "My Doc" --owner user@example.com
38
+ $ primitive documents delete <document-id> -y
39
+ $ primitive documents permissions list <document-id>
40
+ $ primitive documents permissions grant <document-id> --user-id <user-id> --permission reader
41
+ $ primitive documents permissions revoke <document-id> <user-id>
42
+ $ primitive documents records models <document-id>
43
+ $ primitive documents records describe <document-id> <model-name>
44
+ $ primitive documents records query <document-id> <model-name> --filter '{"status":"open"}'
45
+ $ primitive documents records count <document-id> <model-name>
46
+ $ primitive documents records save <document-id> <model-name> --data '{"qty":10}'
47
+ $ primitive documents records patch <document-id> <model-name> <record-id> --data '{"qty":11}'
48
+ $ primitive documents records delete <document-id> <model-name> <record-id> -y
49
+ $ primitive documents records aggregate <document-id> <model-name> --op avg --field price
50
+ $ primitive documents records bulk <document-id> --data-file ops.json -y
51
+ $ primitive documents dump <document-id>
52
+ $ primitive documents stats <document-id>
10
53
  $ primitive documents transfer-owner <document-id> <new-owner-id>
11
54
  $ primitive documents group-permissions list <document-id>
12
55
  `);
56
+ // ---- List a user's documents ----
57
+ // No app-wide enumeration: `--user-id` is required (the admin token has no
58
+ // "self"). Reads the per-user permission partition via the admin endpoint.
59
+ documents
60
+ .command("list")
61
+ .description("List a user's documents")
62
+ .requiredOption("--user-id <id>", "User ID whose documents to list")
63
+ .option("--app <app-id>", "App ID")
64
+ .option("--json", "Output as JSON")
65
+ .action(async (options) => {
66
+ const resolvedAppId = resolveAppId(undefined, options);
67
+ const client = new ApiClient();
68
+ try {
69
+ const list = await client.listAdminDocuments(resolvedAppId, options.userId);
70
+ if (options.json) {
71
+ json(list);
72
+ return;
73
+ }
74
+ if (list.length === 0) {
75
+ info("No documents found.");
76
+ return;
77
+ }
78
+ console.log(formatTable(list, [
79
+ { header: "DOCUMENT_ID", key: "documentId", format: formatId },
80
+ { header: "TITLE", key: "title", flex: true },
81
+ { header: "PERMISSION", key: "permission" },
82
+ { header: "GRANTED", key: "grantedAt", format: formatDate },
83
+ ]));
84
+ }
85
+ catch (err) {
86
+ error(err.message);
87
+ process.exit(1);
88
+ }
89
+ });
90
+ // ---- Get a single document's metadata ----
91
+ documents
92
+ .command("get")
93
+ .description("Show a document's metadata and the caller's access to it")
94
+ .argument("<document-id>", "Document ID")
95
+ .option("--app <app-id>", "App ID")
96
+ .option("--json", "Output as JSON")
97
+ .action(async (documentId, options) => {
98
+ const resolvedAppId = resolveAppId(undefined, options);
99
+ const client = new ApiClient();
100
+ try {
101
+ const doc = await client.getDocument(resolvedAppId, documentId);
102
+ if (options.json) {
103
+ json(doc);
104
+ return;
105
+ }
106
+ keyValue("Document ID", doc.documentId);
107
+ keyValue("Title", doc.title);
108
+ keyValue("Created By", doc.createdBy);
109
+ keyValue("Created", formatDate(doc.createdAt));
110
+ keyValue("Modified", formatDate(doc.modifiedAt ?? doc.lastModified));
111
+ // `GET documents/:id` reports the caller's own access, not the
112
+ // document's grant/alias/collection inventory — printing counts for
113
+ // fields the endpoint never returns read as a definitive "0" for
114
+ // documents that demonstrably have them. Use
115
+ // `documents permissions list` for the grant list.
116
+ if (doc.permission)
117
+ keyValue("Your Permission", doc.permission);
118
+ if (doc.accessSource)
119
+ keyValue("Access Source", doc.accessSource);
120
+ if (doc.linkAccess)
121
+ keyValue("Link Access", doc.linkAccess);
122
+ }
123
+ catch (err) {
124
+ error(err.message);
125
+ process.exit(1);
126
+ }
127
+ });
128
+ // ---- Create a document ----
129
+ // The same `POST /documents` every client uses (#2763). Who ends up owning
130
+ // the document is the server's call, decided from the token's identity —
131
+ // the help below states that matrix rather than the CLI guessing at a role.
132
+ documents
133
+ .command("create")
134
+ .description("Create a document")
135
+ .argument("<title>", "Document title")
136
+ .option("--app <app-id>", "App ID")
137
+ .option("--owner <userId-or-email>", "User ID or email to own the document (super-admin or assigned-console-admin tokens only)")
138
+ .option("--json", "Output as JSON")
139
+ .addHelpText("after", `
140
+ Ownership:
141
+ An app-user token — member, admin, or owner — always creates the document
142
+ owned by the caller: the server ignores --owner for those tokens.
143
+ A super-admin token, or a console-admin token assigned to this app, acts
144
+ through an admin shadow app user — without --owner that shadow user owns the
145
+ document, with --owner the named user does.
146
+ A console admin not assigned to this app has no access to the app at all.
147
+
148
+ An email given to --owner is resolved to a user id before anything is
149
+ created; a user who is not in the app fails the command without creating a
150
+ document.
151
+ `)
152
+ .action(async (title, options) => {
153
+ const resolvedAppId = resolveAppId(undefined, options);
154
+ const client = new ApiClient();
155
+ let ownerUserId;
156
+ if (options.owner) {
157
+ try {
158
+ ownerUserId = await resolveOwnerUserId(client, resolvedAppId, options.owner);
159
+ }
160
+ catch (err) {
161
+ error(err.message);
162
+ process.exit(1);
163
+ }
164
+ }
165
+ try {
166
+ const result = await client.createDocument(resolvedAppId, {
167
+ title,
168
+ ...(ownerUserId ? { createdBy: ownerUserId } : {}),
169
+ });
170
+ if (options.json) {
171
+ json(result);
172
+ return;
173
+ }
174
+ success("Document created.");
175
+ keyValue("Document ID", result.documentId);
176
+ keyValue("Title", result.title);
177
+ keyValue("Owner", result.createdBy);
178
+ }
179
+ catch (err) {
180
+ error(err.message);
181
+ process.exit(1);
182
+ }
183
+ });
184
+ // ---- Delete a document ----
185
+ // The runtime-resource delete verb (#2756): the server endpoint cascades
186
+ // through records, Yjs history, blobs, aliases and permissions, so an
187
+ // operator resetting an app's data no longer needs a one-off SDK script.
188
+ // Who may run it is the server's decision, so the help below states that
189
+ // model (#2763) rather than leaving it to be discovered from a 403.
190
+ documents
191
+ .command("delete")
192
+ .description("Delete a document and all its data (records, history, blobs, aliases, permissions)")
193
+ .argument("<document-id>", "Document ID")
194
+ .option("--app <app-id>", "App ID")
195
+ .option("-y, --yes", "Skip confirmation prompt")
196
+ .option("--json", "Output as JSON")
197
+ .addHelpText("after", `
198
+ Permissions (enforced by the server, not the CLI):
199
+ The document's owner, the app owner, and super-admin or assigned-console-admin
200
+ tokens — which act with app-owner authority — delete directly.
201
+ Everyone else, including app-role admins, can delete only when a containing
202
+ collection's document.delete rule allows it: an app admin cannot delete a
203
+ standalone document (one in no collection) they do not own.
204
+ The app's root document cannot be deleted.
205
+ `)
206
+ .action(async (documentId, options) => {
207
+ const resolvedAppId = resolveAppId(undefined, options);
208
+ if (!options.yes) {
209
+ let confirm;
210
+ try {
211
+ confirm = await confirmPrompt(`Delete document ${documentId}? This permanently deletes its records, ` +
212
+ `update history, blobs, aliases, and permissions. This cannot be undone.`);
213
+ }
214
+ catch (err) {
215
+ error(err.message);
216
+ process.exit(1);
217
+ }
218
+ if (!confirm) {
219
+ info("Cancelled.");
220
+ return;
221
+ }
222
+ }
223
+ const client = new ApiClient();
224
+ try {
225
+ const result = await client.deleteDocument(resolvedAppId, documentId);
226
+ if (options.json) {
227
+ json(result);
228
+ return;
229
+ }
230
+ success(`Document ${documentId} deleted.`);
231
+ }
232
+ catch (err) {
233
+ // Server refusals (404 not found, 403 root document / owner-only, the
234
+ // non-owner fail-closed 503) are printed verbatim, never swallowed.
235
+ error(err.message);
236
+ process.exit(1);
237
+ }
238
+ });
239
+ // ---- User-level permissions subcommand group ----
240
+ const permissions = documents
241
+ .command("permissions")
242
+ .description("Manage user-level permissions on a document");
243
+ // List user-level permissions
244
+ permissions
245
+ .command("list")
246
+ .description("List user-level permissions for a document")
247
+ .argument("<document-id>", "Document ID")
248
+ .option("--app <app-id>", "App ID")
249
+ .option("--json", "Output as JSON")
250
+ .action(async (documentId, options) => {
251
+ const resolvedAppId = resolveAppId(undefined, options);
252
+ const client = new ApiClient();
253
+ try {
254
+ const result = await client.listDocumentPermissions(resolvedAppId, documentId);
255
+ const list = Array.isArray(result) ? result : result?.permissions ?? [];
256
+ if (options.json) {
257
+ json(list);
258
+ return;
259
+ }
260
+ if (list.length === 0) {
261
+ info("No permissions found.");
262
+ return;
263
+ }
264
+ console.log(formatTable(list, [
265
+ { header: "USER_ID", key: "userId", format: formatId },
266
+ { header: "EMAIL", key: "email", flex: true },
267
+ { header: "PERMISSION", key: "permission" },
268
+ { header: "GRANTED", key: "grantedAt", format: formatDate },
269
+ ]));
270
+ }
271
+ catch (err) {
272
+ error(err.message);
273
+ process.exit(1);
274
+ }
275
+ });
276
+ // Grant (or update) a user-level permission
277
+ permissions
278
+ .command("grant")
279
+ .description("Grant or update a user's permission on a document")
280
+ .argument("<document-id>", "Document ID")
281
+ .option("--user-id <id>", "User ID to grant the permission to")
282
+ .option("--email <email>", "Email of the user to grant the permission to")
283
+ .requiredOption("--permission <permission>", "Permission level: reader or read-write")
284
+ .option("--app <app-id>", "App ID")
285
+ .option("--json", "Output as JSON")
286
+ .action(async (documentId, options) => {
287
+ if (!options.userId && !options.email) {
288
+ error("Either --user-id or --email is required.");
289
+ process.exit(1);
290
+ }
291
+ if (options.userId && options.email) {
292
+ error("Provide only one of --user-id or --email, not both.");
293
+ process.exit(1);
294
+ }
295
+ if (!["reader", "read-write"].includes(options.permission)) {
296
+ error("Invalid --permission. Must be 'reader' or 'read-write'.");
297
+ process.exit(1);
298
+ }
299
+ const resolvedAppId = resolveAppId(undefined, options);
300
+ const client = new ApiClient();
301
+ try {
302
+ const entry = options.userId
303
+ ? { userId: options.userId, permission: options.permission }
304
+ : { email: options.email, permission: options.permission };
305
+ const result = await client.grantDocumentPermission(resolvedAppId, documentId, [entry]);
306
+ if (options.json) {
307
+ json(result);
308
+ return;
309
+ }
310
+ const target = options.userId ? options.userId : options.email;
311
+ success(`Permission '${options.permission}' granted to ${target}.`);
312
+ }
313
+ catch (err) {
314
+ error(err.message);
315
+ process.exit(1);
316
+ }
317
+ });
318
+ // Revoke a user-level permission
319
+ permissions
320
+ .command("revoke")
321
+ .description("Revoke a user's permission on a document")
322
+ .argument("<document-id>", "Document ID")
323
+ .argument("[user-id]", "User ID to revoke (or use --email)")
324
+ .option("--email <email>", "Email of the user to revoke (alternative to <user-id>)")
325
+ .option("--app <app-id>", "App ID")
326
+ .option("-y, --yes", "Skip confirmation prompt")
327
+ .action(async (documentId, userId, options) => {
328
+ if (!userId && !options.email) {
329
+ error("Either a <user-id> argument or --email is required.");
330
+ process.exit(1);
331
+ }
332
+ if (userId && options.email) {
333
+ error("Provide only one of <user-id> or --email, not both.");
334
+ process.exit(1);
335
+ }
336
+ const target = userId ?? options.email;
337
+ const resolvedAppId = resolveAppId(undefined, options);
338
+ if (!options.yes) {
339
+ let confirm;
340
+ try {
341
+ confirm = await confirmPrompt(`Revoke permission for ${target} on document ${documentId}?`);
342
+ }
343
+ catch (err) {
344
+ error(err.message);
345
+ process.exit(1);
346
+ }
347
+ if (!confirm) {
348
+ info("Cancelled.");
349
+ return;
350
+ }
351
+ }
352
+ const client = new ApiClient();
353
+ try {
354
+ await client.revokeDocumentPermission(resolvedAppId, documentId, {
355
+ userId,
356
+ email: options.email,
357
+ });
358
+ success(`Permission revoked for ${target}.`);
359
+ }
360
+ catch (err) {
361
+ error(err.message);
362
+ process.exit(1);
363
+ }
364
+ });
365
+ // ---- Records introspection subcommand group ----
366
+ const records = documents
367
+ .command("records")
368
+ .description("Inspect a document's models and records");
369
+ // List model names
370
+ records
371
+ .command("models")
372
+ .description("List model names in a document")
373
+ .argument("<document-id>", "Document ID")
374
+ .option("--app <app-id>", "App ID")
375
+ .option("--json", "Output as JSON")
376
+ .action(async (documentId, options) => {
377
+ const resolvedAppId = resolveAppId(undefined, options);
378
+ const client = new ApiClient();
379
+ try {
380
+ const result = await client.getDocumentSchema(resolvedAppId, documentId);
381
+ const models = result?.modelNames ?? Object.keys(result?.schema?.models ?? {});
382
+ if (options.json) {
383
+ json(models);
384
+ return;
385
+ }
386
+ if (models.length === 0) {
387
+ info("No models found.");
388
+ return;
389
+ }
390
+ for (const m of models) {
391
+ console.log(` ${m}`);
392
+ }
393
+ }
394
+ catch (err) {
395
+ error(err.message);
396
+ process.exit(1);
397
+ }
398
+ });
399
+ // Describe a model's fields and indexes
400
+ records
401
+ .command("describe")
402
+ .description("Show a model's fields and indexes")
403
+ .argument("<document-id>", "Document ID")
404
+ .argument("<model-name>", "Model name")
405
+ .option("--app <app-id>", "App ID")
406
+ .option("--json", "Output as JSON")
407
+ .action(async (documentId, modelName, options) => {
408
+ const resolvedAppId = resolveAppId(undefined, options);
409
+ const client = new ApiClient();
410
+ try {
411
+ const result = await client.getDocumentSchema(resolvedAppId, documentId);
412
+ const model = result?.schema?.models?.[modelName];
413
+ if (!model) {
414
+ if (options.json) {
415
+ json(null);
416
+ return;
417
+ }
418
+ info(`Model '${modelName}' not found in this document.`);
419
+ return;
420
+ }
421
+ const fieldsObj = model.fields ?? {};
422
+ const rows = Object.entries(fieldsObj).map(([name, meta]) => ({
423
+ field: name,
424
+ type: meta?.type ?? "unknown",
425
+ indexed: meta?.indexed ? "yes" : "",
426
+ unique: meta?.unique ? "yes" : "",
427
+ required: meta?.required ? "yes" : "",
428
+ }));
429
+ if (options.json) {
430
+ json({ fields: fieldsObj, constraints: model.constraints ?? {} });
431
+ return;
432
+ }
433
+ if (rows.length === 0) {
434
+ info("No fields detected for this model.");
435
+ return;
436
+ }
437
+ console.log(formatTable(rows, [
438
+ { header: "FIELD", key: "field", flex: true },
439
+ { header: "TYPE", key: "type" },
440
+ { header: "INDEXED", key: "indexed" },
441
+ { header: "UNIQUE", key: "unique" },
442
+ { header: "REQUIRED", key: "required" },
443
+ ]));
444
+ }
445
+ catch (err) {
446
+ error(err.message);
447
+ process.exit(1);
448
+ }
449
+ });
450
+ // Query records in a document model
451
+ records
452
+ .command("query")
453
+ .description("Query records in a document model")
454
+ .argument("<document-id>", "Document ID")
455
+ .argument("<model-name>", "Model name to query")
456
+ .option("--app <app-id>", "App ID")
457
+ .option("--filter <json>", "Filter as JSON (e.g. '{\"status\":\"open\"}')")
458
+ .option("--filter-file <path>", "Read filter from a JSON or TOML file")
459
+ .option("--limit <n>", "Maximum number of records to return (max 100)", parseInt)
460
+ .option("--cursor <cursor>", "Pagination cursor from a previous query")
461
+ .option("--json", "Output as JSON")
462
+ .action(async (documentId, modelName, options) => {
463
+ const resolvedAppId = resolveAppId(undefined, options);
464
+ const filter = parseFilterOptions(options);
465
+ const client = new ApiClient();
466
+ try {
467
+ const result = await client.queryDocumentRecords(resolvedAppId, documentId, modelName, { filter, limit: options.limit, cursor: options.cursor });
468
+ if (options.json) {
469
+ json(result);
470
+ return;
471
+ }
472
+ const items = result.items;
473
+ if (items.length === 0) {
474
+ info("No records found.");
475
+ return;
476
+ }
477
+ const allKeys = new Set();
478
+ for (const rec of items) {
479
+ for (const key of Object.keys(rec))
480
+ allKeys.add(key);
481
+ }
482
+ const orderedKeys = [];
483
+ if (allKeys.has("id")) {
484
+ orderedKeys.push("id");
485
+ allKeys.delete("id");
486
+ }
487
+ orderedKeys.push(...[...allKeys].sort());
488
+ const columns = orderedKeys.map((key) => ({
489
+ header: key.toUpperCase(),
490
+ key,
491
+ format: (v) => {
492
+ if (v === null || v === undefined)
493
+ return "—";
494
+ if (typeof v === "object")
495
+ return JSON.stringify(v);
496
+ return String(v);
497
+ },
498
+ }));
499
+ console.log(formatTable(items, columns));
500
+ if (result.nextCursor) {
501
+ info(`More results available. Use --cursor ${result.nextCursor}`);
502
+ }
503
+ }
504
+ catch (err) {
505
+ error(err.message);
506
+ process.exit(1);
507
+ }
508
+ });
509
+ // Get a single record by id (mirrors `databases records get`, issue #2357).
510
+ records
511
+ .command("get")
512
+ .description("Get a single record by id from a document model")
513
+ .argument("<document-id>", "Document ID")
514
+ .argument("<model-name>", "Model name")
515
+ .argument("<record-id>", "Record ID")
516
+ .option("--app <app-id>", "App ID")
517
+ .option("--json", "Output as JSON")
518
+ .action(async (documentId, modelName, recordId, options) => {
519
+ const resolvedAppId = resolveAppId(undefined, options);
520
+ const client = new ApiClient();
521
+ try {
522
+ // A single-record fetch is a query filtered by primary-key id (there is
523
+ // no dedicated get endpoint — reuse queryDocumentRecords, exactly as
524
+ // the `databases records get` twin reuses queryDatabaseRecords).
525
+ const result = await client.queryDocumentRecords(resolvedAppId, documentId, modelName, { filter: { id: recordId }, limit: 1 });
526
+ const record = result.items[0];
527
+ if (options.json) {
528
+ json(record ?? null);
529
+ return;
530
+ }
531
+ if (!record) {
532
+ info(`No record found with id ${recordId} in ${modelName}.`);
533
+ return;
534
+ }
535
+ // `get` is a data command, so its fields go to stdout via `result()`
536
+ // — the #711 split (`result()` = the data the caller asked for,
537
+ // `keyValue()` = a post-action diagnostic summary). Issue #2437; the
538
+ // databases twin renders identically.
539
+ for (const [key, value] of Object.entries(record)) {
540
+ const rendered = value === null || value === undefined
541
+ ? "—"
542
+ : typeof value === "object"
543
+ ? JSON.stringify(value)
544
+ : String(value);
545
+ printResult(key, rendered);
546
+ }
547
+ }
548
+ catch (err) {
549
+ error(err.message);
550
+ process.exit(1);
551
+ }
552
+ });
553
+ // Count records in a document model
554
+ records
555
+ .command("count")
556
+ .description("Count records in a document model")
557
+ .argument("<document-id>", "Document ID")
558
+ .argument("<model-name>", "Model name to count")
559
+ .option("--app <app-id>", "App ID")
560
+ .option("--filter <json>", "Filter as JSON (e.g. '{\"status\":\"open\"}')")
561
+ .option("--filter-file <path>", "Read filter from a JSON or TOML file")
562
+ .option("--json", "Output as JSON")
563
+ .action(async (documentId, modelName, options) => {
564
+ const resolvedAppId = resolveAppId(undefined, options);
565
+ const filter = parseFilterOptions(options);
566
+ const client = new ApiClient();
567
+ try {
568
+ const result = await client.countDocumentRecords(resolvedAppId, documentId, modelName, { filter });
569
+ if (options.json) {
570
+ json(result);
571
+ return;
572
+ }
573
+ console.log(` ${result.count}`);
574
+ }
575
+ catch (err) {
576
+ error(err.message);
577
+ process.exit(1);
578
+ }
579
+ });
580
+ // Aggregate records in a document model (issue #2437). Mirrors
581
+ // `databases records aggregate` flag for flag and rendering for rendering —
582
+ // including the deliberate `keyValue()` for the ungrouped scalar, so the two
583
+ // twins keep printing the same thing.
584
+ const AGGREGATE_OPS = ["count", "sum", "avg", "min", "max"];
585
+ records
586
+ .command("aggregate")
587
+ .description("Aggregate records in a document model (count/sum/avg/min/max, optional group-by)")
588
+ .argument("<document-id>", "Document ID")
589
+ .argument("<model-name>", "Model name")
590
+ .option("--app <app-id>", "App ID")
591
+ .requiredOption("--op <operation>", `Aggregate operation: ${AGGREGATE_OPS.join("|")}`)
592
+ .option("--field <field>", "Field to aggregate (required for sum/avg/min/max)")
593
+ .option("--group-by <field>", "Group results by this plain field (repeatable; StringSet fields are not supported)", (value, previous = []) => previous.concat(value), [])
594
+ .option("--filter <json>", "Filter as JSON")
595
+ .option("--filter-file <path>", "Read filter from a JSON or TOML file")
596
+ .option("--json", "Output as JSON")
597
+ .action(async (documentId, modelName, options) => {
598
+ const resolvedAppId = resolveAppId(undefined, options);
599
+ if (!AGGREGATE_OPS.includes(options.op)) {
600
+ error(`Invalid --op "${options.op}". Expected one of: ${AGGREGATE_OPS.join(", ")}.`);
601
+ process.exit(1);
602
+ }
603
+ const operation = { type: options.op };
604
+ if (options.op === "count") {
605
+ if (options.field) {
606
+ error("--field is not valid for --op count.");
607
+ process.exit(1);
608
+ }
609
+ }
610
+ else {
611
+ if (!options.field) {
612
+ error(`--op ${options.op} requires --field.`);
613
+ process.exit(1);
614
+ }
615
+ operation.field = options.field;
616
+ }
617
+ const filter = parseFilterOptions(options);
618
+ const groupBy = options.groupBy || [];
619
+ const client = new ApiClient();
620
+ try {
621
+ const response = await client.aggregateDocumentRecords(resolvedAppId, documentId, modelName, { operations: [operation], groupBy, filter });
622
+ if (options.json) {
623
+ json(response);
624
+ return;
625
+ }
626
+ const aggregated = response?.result;
627
+ // Same labelling rule as the databases twin: the field name is
628
+ // reproduced verbatim, never case-folded (issue #2357).
629
+ const valueLabel = options.op === "count"
630
+ ? "COUNT"
631
+ : `${options.op.toUpperCase()}(${options.field})`;
632
+ const scalarLabel = options.op === "count"
633
+ ? "count"
634
+ : `${options.op.toLowerCase()}(${options.field})`;
635
+ if (groupBy.length === 0) {
636
+ const value = (aggregated ?? {})[options.op === "count" ? "count" : `${options.op}_${options.field}`];
637
+ keyValue(scalarLabel, value === null || value === undefined ? "—" : String(value));
638
+ return;
639
+ }
640
+ if (aggregated &&
641
+ typeof aggregated === "object" &&
642
+ !Array.isArray(aggregated) &&
643
+ Object.values(aggregated).every((v) => typeof v !== "object" || v === null)) {
644
+ const rows = Object.entries(aggregated).map(([group, value]) => ({
645
+ group,
646
+ value: value === null || value === undefined ? "—" : String(value),
647
+ }));
648
+ console.log(formatTable(rows, [
649
+ { header: "GROUP", key: "group" },
650
+ { header: valueLabel, key: "value" },
651
+ ]));
652
+ }
653
+ else {
654
+ console.log(JSON.stringify(aggregated ?? {}, null, 2));
655
+ }
656
+ }
657
+ catch (err) {
658
+ error(err.message);
659
+ process.exit(1);
660
+ }
661
+ });
662
+ // ---- Record writes (#1964 Phase 4) ----
663
+ // Server-side writes through the document facade: Yjs-first, CRDT-safe,
664
+ // broadcast to connected clients. Requires read-write or higher on the
665
+ // document (or an admin token). `--data` / `--data-file` are validated
666
+ // CLI-side before any request is sent.
667
+ // `--data`/`--data-file` parsing (including the object-shape check and the
668
+ // both-flags rule) is the shared `parseDataOption` helper, so these verbs
669
+ // behave and diagnose identically to `metadata` and `databases records`.
670
+ // Create or replace a record
671
+ records
672
+ .command("save")
673
+ .description("Create or replace a record in a document model")
674
+ .argument("<document-id>", "Document ID")
675
+ .argument("<model-name>", "Model name to write to")
676
+ .option("--app <app-id>", "App ID")
677
+ .option("--id <record-id>", "Record ID (a ULID is generated when omitted)")
678
+ .option("--data <json>", "Record fields as JSON (e.g. '{\"qty\":10}')")
679
+ .option("--data-file <file>", "Read record fields from a JSON file")
680
+ .option("--upsert-on <field>", "Update the record whose <field> matches instead of creating")
681
+ .option("--json", "Output as JSON")
682
+ .action(async (documentId, modelName, options) => {
683
+ const data = parseDataOption(options, "record fields");
684
+ const resolvedAppId = resolveAppId(undefined, options);
685
+ const recordId = options.id || ulid();
686
+ const writeOptions = options.upsertOn ? { upsertOn: options.upsertOn } : undefined;
687
+ const client = new ApiClient();
688
+ try {
689
+ const result = await client.saveDocumentRecord(resolvedAppId, documentId, modelName, { id: recordId, data, ...(writeOptions ? { options: writeOptions } : {}) });
690
+ if (options.json) {
691
+ json(result);
692
+ return;
693
+ }
694
+ success(`Record saved: ${result.record?.id ?? recordId}`);
695
+ }
696
+ catch (err) {
697
+ error(err.message);
698
+ process.exit(1);
699
+ }
700
+ });
701
+ // Merge fields into an existing record
702
+ records
703
+ .command("patch")
704
+ .description("Merge fields into an existing record in a document model")
705
+ .argument("<document-id>", "Document ID")
706
+ .argument("<model-name>", "Model name")
707
+ .argument("<record-id>", "Record ID to patch")
708
+ .option("--app <app-id>", "App ID")
709
+ .option("--data <json>", "Fields to merge as JSON (e.g. '{\"status\":\"closed\"}')")
710
+ .option("--data-file <file>", "Read fields from a JSON file")
711
+ .option("--json", "Output as JSON")
712
+ .action(async (documentId, modelName, recordId, options) => {
713
+ const data = parseDataOption(options, "fields to merge");
714
+ const resolvedAppId = resolveAppId(undefined, options);
715
+ const client = new ApiClient();
716
+ try {
717
+ const result = await client.patchDocumentRecord(resolvedAppId, documentId, modelName, recordId, { data });
718
+ if (options.json) {
719
+ json(result);
720
+ return;
721
+ }
722
+ success(`Record patched: ${recordId}`);
723
+ }
724
+ catch (err) {
725
+ error(err.message);
726
+ process.exit(1);
727
+ }
728
+ });
729
+ // Delete a record
730
+ records
731
+ .command("delete")
732
+ .description("Delete a record from a document model")
733
+ .argument("<document-id>", "Document ID")
734
+ .argument("<model-name>", "Model name")
735
+ .argument("<record-id>", "Record ID to delete")
736
+ .option("--app <app-id>", "App ID")
737
+ .option("-y, --yes", "Skip confirmation prompt")
738
+ .option("--json", "Output as JSON")
739
+ .action(async (documentId, modelName, recordId, options) => {
740
+ const resolvedAppId = resolveAppId(undefined, options);
741
+ if (!options.yes) {
742
+ const confirmed = await confirmPrompt(`Delete record ${recordId} from ${modelName} in document ${documentId}?`);
743
+ if (!confirmed) {
744
+ info("Cancelled.");
745
+ return;
746
+ }
747
+ }
748
+ const client = new ApiClient();
749
+ try {
750
+ const result = await client.deleteDocumentRecord(resolvedAppId, documentId, modelName, recordId);
751
+ if (options.json) {
752
+ json(result);
753
+ return;
754
+ }
755
+ // A delete of a missing record is a silent no-op server-side, so
756
+ // this reports the request outcome, not record existence.
757
+ success(`Record deleted: ${recordId}`);
758
+ }
759
+ catch (err) {
760
+ error(err.message);
761
+ process.exit(1);
762
+ }
763
+ });
764
+ // Apply an atomic multi-model operations blob
765
+ records
766
+ .command("bulk")
767
+ .description("Apply an atomic create/patch/delete operations blob to a document")
768
+ .argument("<document-id>", "Document ID")
769
+ .requiredOption("--data-file <file>", "JSON file with { operations: [...] } or a bare operations array. " +
770
+ "Each op is { model, action: create|patch|delete, id, data, precondition? } " +
771
+ "— `data` holds the record fields and is required (non-empty) on " +
772
+ "create/patch, not allowed on delete; a create `id` must be a 26-char " +
773
+ "uppercase Crockford ULID")
774
+ .option("--app <app-id>", "App ID")
775
+ .option("-y, --yes", "Skip confirmation prompt")
776
+ .option("--json", "Output as JSON")
777
+ .action(async (documentId, options) => {
778
+ const parsed = parseDataOption(options, "operations blob", {
779
+ allowArray: true,
780
+ });
781
+ const operations = Array.isArray(parsed) ? parsed : parsed?.operations;
782
+ if (!Array.isArray(operations) || operations.length === 0) {
783
+ error("--data-file must contain { operations: [...] } or a non-empty operations array.");
784
+ process.exit(1);
785
+ }
786
+ const resolvedAppId = resolveAppId(undefined, options);
787
+ if (!options.yes) {
788
+ const confirmed = await confirmPrompt(`Apply ${operations.length} operation(s) atomically to document ${documentId}?`);
789
+ if (!confirmed) {
790
+ info("Cancelled.");
791
+ return;
792
+ }
793
+ }
794
+ const client = new ApiClient();
795
+ try {
796
+ const result = await client.bulkDocumentRecords(resolvedAppId, documentId, operations);
797
+ if (options.json) {
798
+ json(result);
799
+ return;
800
+ }
801
+ success(`Applied ${result.applied} operation(s): ` +
802
+ `${result.added.length} added, ${result.updated.length} updated, ${result.deleted} deleted.`);
803
+ }
804
+ catch (err) {
805
+ error(err.message);
806
+ process.exit(1);
807
+ }
808
+ });
809
+ // Dump a document's records grouped by model (CLI-side composition)
810
+ documents
811
+ .command("dump")
812
+ .description("Dump a document's records grouped by model as JSON")
813
+ .argument("<document-id>", "Document ID")
814
+ .option("--app <app-id>", "App ID")
815
+ .option("--output <file>", "Write the JSON dump to a file instead of stdout")
816
+ .option("--json", "Output as JSON (default)")
817
+ .action(async (documentId, options) => {
818
+ const resolvedAppId = resolveAppId(undefined, options);
819
+ const client = new ApiClient();
820
+ try {
821
+ // Model names come from the schema; records are assembled from paged
822
+ // reads per model (not a single atomic snapshot — the paged path is
823
+ // the contract, same pattern as `databases export`).
824
+ const schema = await client.getDocumentSchema(resolvedAppId, documentId);
825
+ const modelNames = schema?.modelNames ?? Object.keys(schema?.schema?.models ?? {});
826
+ const grouped = {};
827
+ for (const modelName of modelNames) {
828
+ const records = [];
829
+ let cursor;
830
+ do {
831
+ const page = await client.queryDocumentRecords(resolvedAppId, documentId, modelName, { limit: 100, cursor });
832
+ records.push(...page.items);
833
+ cursor = page.nextCursor;
834
+ } while (cursor);
835
+ grouped[modelName] = records;
836
+ }
837
+ const output = JSON.stringify(grouped, null, 2);
838
+ if (options.output) {
839
+ fs.writeFileSync(options.output, output);
840
+ success(`Document dump written to ${options.output}`);
841
+ return;
842
+ }
843
+ console.log(output);
844
+ }
845
+ catch (err) {
846
+ error(err.message);
847
+ process.exit(1);
848
+ }
849
+ });
850
+ // Document statistics (platform-vocabulary projection)
851
+ documents
852
+ .command("stats")
853
+ .description("Show a document's record/model/blob counts and approximate size")
854
+ .argument("<document-id>", "Document ID")
855
+ .option("--app <app-id>", "App ID")
856
+ .option("--json", "Output as JSON")
857
+ .action(async (documentId, options) => {
858
+ const resolvedAppId = resolveAppId(undefined, options);
859
+ const client = new ApiClient();
860
+ try {
861
+ const result = await client.getDocumentStats(resolvedAppId, documentId);
862
+ if (options.json) {
863
+ json(result);
864
+ return;
865
+ }
866
+ keyValue("Document ID", result.documentId);
867
+ keyValue("Records", result.recordCount);
868
+ keyValue("Models", result.modelCount);
869
+ keyValue("Blobs", result.blobCount);
870
+ keyValue("Size (bytes, approx)", result.sizeBytes);
871
+ keyValue("Last modified", result.lastModifiedAt ? formatDate(result.lastModifiedAt) : "—");
872
+ }
873
+ catch (err) {
874
+ error(err.message);
875
+ process.exit(1);
876
+ }
877
+ });
13
878
  // Transfer document ownership
14
879
  documents
15
880
  .command("transfer-owner")
@@ -43,15 +908,14 @@ Examples:
43
908
  process.exit(1);
44
909
  }
45
910
  if (!options.yes) {
46
- const inquirer = await import("inquirer");
47
- const { confirm } = await inquirer.default.prompt([
48
- {
49
- type: "confirm",
50
- name: "confirm",
51
- message: `Transfer ownership of document ${documentId} to user ${newOwnerId}? This cannot be undone.`,
52
- default: false,
53
- },
54
- ]);
911
+ let confirm;
912
+ try {
913
+ confirm = await confirmPrompt(`Transfer ownership of document ${documentId} to user ${newOwnerId}? This cannot be undone.`);
914
+ }
915
+ catch (err) {
916
+ error(err.message);
917
+ process.exit(1);
918
+ }
55
919
  if (!confirm) {
56
920
  info("Cancelled.");
57
921
  return;
@@ -138,6 +1002,282 @@ Examples:
138
1002
  process.exit(1);
139
1003
  }
140
1004
  });
1005
+ // ---- Link access ("anyone with the link") ----
1006
+ const linkAccess = documents
1007
+ .command("link-access")
1008
+ .description('Manage "anyone with the link" access on a document');
1009
+ linkAccess
1010
+ .command("get")
1011
+ .description("Show the current link-access level for a document")
1012
+ .argument("<document-id>", "Document ID")
1013
+ .option("--app <app-id>", "App ID")
1014
+ .option("--json", "Output as JSON")
1015
+ .action(async (documentId, options) => {
1016
+ const resolvedAppId = resolveAppId(undefined, options);
1017
+ const client = new ApiClient();
1018
+ try {
1019
+ const state = await client.getDocumentLinkAccess(resolvedAppId, documentId);
1020
+ const level = state?.linkAccess ?? null;
1021
+ if (options.json) {
1022
+ json({ documentId, linkAccess: level });
1023
+ return;
1024
+ }
1025
+ if (!level) {
1026
+ info("Link access is off for this document.");
1027
+ return;
1028
+ }
1029
+ keyValue("Document", documentId);
1030
+ keyValue("Link access", level);
1031
+ }
1032
+ catch (err) {
1033
+ error(err.message);
1034
+ process.exit(1);
1035
+ }
1036
+ });
1037
+ linkAccess
1038
+ .command("set")
1039
+ .description("Turn on link access at a given level")
1040
+ .argument("<document-id>", "Document ID")
1041
+ .requiredOption("--level <level>", "Link access level: reader or read-write")
1042
+ .option("--app <app-id>", "App ID")
1043
+ .option("--json", "Output as JSON")
1044
+ .action(async (documentId, options) => {
1045
+ const resolvedAppId = resolveAppId(undefined, options);
1046
+ const client = new ApiClient();
1047
+ try {
1048
+ const result = await client.setDocumentLinkAccess(resolvedAppId, documentId, options.level);
1049
+ if (options.json) {
1050
+ json(result);
1051
+ return;
1052
+ }
1053
+ success(`Link access set to '${options.level}' for document ${documentId}.`);
1054
+ }
1055
+ catch (err) {
1056
+ error(err.message);
1057
+ process.exit(1);
1058
+ }
1059
+ });
1060
+ linkAccess
1061
+ .command("clear")
1062
+ .description("Turn off link access")
1063
+ .argument("<document-id>", "Document ID")
1064
+ .option("--app <app-id>", "App ID")
1065
+ .option("--json", "Output as JSON")
1066
+ .action(async (documentId, options) => {
1067
+ const resolvedAppId = resolveAppId(undefined, options);
1068
+ const client = new ApiClient();
1069
+ try {
1070
+ const result = await client.clearDocumentLinkAccess(resolvedAppId, documentId);
1071
+ if (options.json) {
1072
+ json(result);
1073
+ return;
1074
+ }
1075
+ success(`Link access turned off for document ${documentId}.`);
1076
+ }
1077
+ catch (err) {
1078
+ error(err.message);
1079
+ process.exit(1);
1080
+ }
1081
+ });
1082
+ // ---- Export / Import commands ----
1083
+ documents
1084
+ .command("export")
1085
+ .description("Export a document (Yjs state, blobs, permissions, aliases)")
1086
+ .argument("[app-id]", "App ID (uses current app if not specified)")
1087
+ .argument("<document-id>", "Document ID to export")
1088
+ .option("--app <app-id>", "App ID")
1089
+ .option("--output <dir>", "Output directory", "./primitive-export")
1090
+ .option("--no-blobs", "Skip blob data")
1091
+ .option("--json", "Output as JSON")
1092
+ .action(async (first, second, options) => {
1093
+ let resolvedAppId;
1094
+ let documentId;
1095
+ if (second && !second.startsWith("-")) {
1096
+ resolvedAppId = resolveAppId(first, options);
1097
+ documentId = second;
1098
+ }
1099
+ else {
1100
+ resolvedAppId = resolveAppId(undefined, options);
1101
+ documentId = first;
1102
+ }
1103
+ if (!documentId) {
1104
+ error("Document ID is required.");
1105
+ process.exit(1);
1106
+ }
1107
+ const client = new ApiClient();
1108
+ try {
1109
+ await exportSingleDocument(client, resolvedAppId, documentId, options.output, options.blobs !== false ? true : false, options.json);
1110
+ }
1111
+ catch (err) {
1112
+ error(err.message);
1113
+ process.exit(1);
1114
+ }
1115
+ });
1116
+ documents
1117
+ .command("export-all")
1118
+ .description("Export all documents for a user")
1119
+ .argument("[app-id]", "App ID (uses current app if not specified)")
1120
+ .requiredOption("--user-id <id>", "User ID to export documents for")
1121
+ .option("--app <app-id>", "App ID")
1122
+ .option("--owned-only", "Only export documents owned by the user")
1123
+ .option("--output <dir>", "Output directory", "./primitive-export")
1124
+ .option("--no-blobs", "Skip blob data")
1125
+ .option("--json", "Output as JSON")
1126
+ .action(async (appIdArg, options) => {
1127
+ const resolvedAppId = resolveAppId(appIdArg, options);
1128
+ const client = new ApiClient();
1129
+ const includeBlobs = options.blobs !== false;
1130
+ try {
1131
+ const documents = await client.listAdminDocuments(resolvedAppId, options.userId);
1132
+ let docList = Array.isArray(documents) ? documents : [];
1133
+ if (options.ownedOnly) {
1134
+ docList = docList.filter((d) => d.permission === "owner");
1135
+ }
1136
+ if (docList.length === 0) {
1137
+ info("No documents found.");
1138
+ return;
1139
+ }
1140
+ info(`Exporting ${docList.length} document(s)...`);
1141
+ const exportedIds = [];
1142
+ for (const doc of docList) {
1143
+ const docId = doc.documentId || doc.id;
1144
+ try {
1145
+ await exportSingleDocument(client, resolvedAppId, docId, options.output, includeBlobs, false);
1146
+ exportedIds.push(docId);
1147
+ }
1148
+ catch (err) {
1149
+ warn(`Failed to export ${docId}: ${err.message}`);
1150
+ }
1151
+ }
1152
+ // Write manifest
1153
+ const manifestPath = path.join(options.output, "manifest.json");
1154
+ const manifest = {
1155
+ version: 1,
1156
+ exportedAt: new Date().toISOString(),
1157
+ sourceAppId: resolvedAppId,
1158
+ documentCount: exportedIds.length,
1159
+ documents: exportedIds,
1160
+ };
1161
+ fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
1162
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
1163
+ if (options.json) {
1164
+ json(manifest);
1165
+ }
1166
+ else {
1167
+ success(`Exported ${exportedIds.length} document(s) to ${options.output}`);
1168
+ }
1169
+ }
1170
+ catch (err) {
1171
+ error(err.message);
1172
+ process.exit(1);
1173
+ }
1174
+ });
1175
+ documents
1176
+ .command("import")
1177
+ .description("Import documents from an export directory")
1178
+ .argument("[app-id]", "App ID (uses current app if not specified)")
1179
+ .argument("<path>", "Path to export directory or single document directory")
1180
+ .option("--app <app-id>", "App ID")
1181
+ .option("--aliases <mode>", "Alias conflict handling: overwrite or skip", "skip")
1182
+ .option("--overwrite", "Replace existing documents if IDs collide")
1183
+ .option("--dry-run", "Show what would be imported without making changes")
1184
+ .option("--owner <userId-or-email>", "User ID or email to set as document owner (admin only)")
1185
+ .option("--json", "Output as JSON")
1186
+ .action(async (first, second, options) => {
1187
+ let resolvedAppId;
1188
+ let importPath;
1189
+ if (second && !second.startsWith("-")) {
1190
+ resolvedAppId = resolveAppId(first, options);
1191
+ importPath = second;
1192
+ }
1193
+ else {
1194
+ resolvedAppId = resolveAppId(undefined, options);
1195
+ importPath = first;
1196
+ }
1197
+ if (!importPath) {
1198
+ error("Import path is required.");
1199
+ process.exit(1);
1200
+ }
1201
+ const client = new ApiClient();
1202
+ // Resolve --owner to a userId through the one shared lookup (#2763).
1203
+ let ownerUserId;
1204
+ if (options.owner) {
1205
+ try {
1206
+ ownerUserId = await resolveOwnerUserId(client, resolvedAppId, options.owner);
1207
+ }
1208
+ catch (err) {
1209
+ error(err.message);
1210
+ process.exit(1);
1211
+ }
1212
+ }
1213
+ try {
1214
+ // Determine if this is a manifest-based export or a single document
1215
+ const manifestPath = path.join(importPath, "manifest.json");
1216
+ let docDirs = [];
1217
+ if (fs.existsSync(manifestPath)) {
1218
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
1219
+ for (const docId of manifest.documents) {
1220
+ const docDir = path.join(importPath, "documents", docId);
1221
+ if (fs.existsSync(docDir)) {
1222
+ docDirs.push(docDir);
1223
+ }
1224
+ }
1225
+ }
1226
+ else if (fs.existsSync(path.join(importPath, "metadata.json"))) {
1227
+ // Single document directory
1228
+ docDirs = [importPath];
1229
+ }
1230
+ else if (fs.existsSync(path.join(importPath, "documents"))) {
1231
+ // Export directory without manifest — discover documents
1232
+ const entries = fs.readdirSync(path.join(importPath, "documents"), { withFileTypes: true });
1233
+ for (const entry of entries) {
1234
+ if (entry.isDirectory()) {
1235
+ docDirs.push(path.join(importPath, "documents", entry.name));
1236
+ }
1237
+ }
1238
+ }
1239
+ else {
1240
+ error("No valid export data found at the specified path.");
1241
+ process.exit(1);
1242
+ }
1243
+ if (docDirs.length === 0) {
1244
+ info("No documents to import.");
1245
+ return;
1246
+ }
1247
+ const summary = {
1248
+ created: 0,
1249
+ updated: 0,
1250
+ skipped: 0,
1251
+ blobsUploaded: 0,
1252
+ aliasesSet: 0,
1253
+ aliasesSkipped: 0,
1254
+ };
1255
+ for (const docDir of docDirs) {
1256
+ await importSingleDocument(client, resolvedAppId, docDir, options.overwrite || false, options.aliases || "skip", options.dryRun || false, summary, ownerUserId);
1257
+ }
1258
+ if (options.json) {
1259
+ json(summary);
1260
+ }
1261
+ else {
1262
+ success(`Import complete:`);
1263
+ keyValue(" Documents created", String(summary.created));
1264
+ if (summary.updated > 0)
1265
+ keyValue(" Documents updated", String(summary.updated));
1266
+ if (summary.skipped > 0)
1267
+ keyValue(" Documents skipped", String(summary.skipped));
1268
+ if (summary.blobsUploaded > 0)
1269
+ keyValue(" Blobs uploaded", String(summary.blobsUploaded));
1270
+ if (summary.aliasesSet > 0)
1271
+ keyValue(" Aliases set", String(summary.aliasesSet));
1272
+ if (summary.aliasesSkipped > 0)
1273
+ keyValue(" Aliases skipped", String(summary.aliasesSkipped));
1274
+ }
1275
+ }
1276
+ catch (err) {
1277
+ error(err.message);
1278
+ process.exit(1);
1279
+ }
1280
+ });
141
1281
  // Revoke group permission
142
1282
  groupPermissions
143
1283
  .command("revoke")
@@ -150,15 +1290,14 @@ Examples:
150
1290
  .action(async (documentId, groupType, groupId, options) => {
151
1291
  const resolvedAppId = resolveAppId(undefined, options);
152
1292
  if (!options.yes) {
153
- const inquirer = await import("inquirer");
154
- const { confirm } = await inquirer.default.prompt([
155
- {
156
- type: "confirm",
157
- name: "confirm",
158
- message: `Revoke permission for group ${groupType}/${groupId} on document ${documentId}?`,
159
- default: false,
160
- },
161
- ]);
1293
+ let confirm;
1294
+ try {
1295
+ confirm = await confirmPrompt(`Revoke permission for group ${groupType}/${groupId} on document ${documentId}?`);
1296
+ }
1297
+ catch (err) {
1298
+ error(err.message);
1299
+ process.exit(1);
1300
+ }
162
1301
  if (!confirm) {
163
1302
  info("Cancelled.");
164
1303
  return;
@@ -175,4 +1314,203 @@ Examples:
175
1314
  }
176
1315
  });
177
1316
  }
1317
+ // ============================================
1318
+ // Helper functions for export/import
1319
+ // ============================================
1320
+ async function exportSingleDocument(client, appId, documentId, outputDir, includeBlobs, jsonOutput) {
1321
+ const docDir = path.join(outputDir, "documents", documentId);
1322
+ fs.mkdirSync(docDir, { recursive: true });
1323
+ // 1. Fetch document metadata
1324
+ const docMeta = await client.getDocument(appId, documentId);
1325
+ // 2. Export Yjs state
1326
+ const stateResult = await client.exportDocumentState(appId, documentId);
1327
+ fs.writeFileSync(path.join(docDir, "document.yjs"), Buffer.from(stateResult.state, "base64"));
1328
+ // 3. Fetch permissions
1329
+ let permissions = [];
1330
+ try {
1331
+ const permResult = await client.listDocumentPermissions(appId, documentId);
1332
+ permissions = Array.isArray(permResult) ? permResult : permResult?.permissions || [];
1333
+ }
1334
+ catch {
1335
+ // Permissions endpoint might not return data for all docs
1336
+ }
1337
+ // 4. Fetch invitations
1338
+ let invitations = [];
1339
+ try {
1340
+ const invResult = await client.listDocumentInvitations(appId, documentId);
1341
+ invitations = Array.isArray(invResult) ? invResult : invResult?.invitations || [];
1342
+ }
1343
+ catch {
1344
+ // May not have invitations
1345
+ }
1346
+ // Merge permissions and invitations into a single list with email identifiers
1347
+ const permissionsExport = [
1348
+ ...permissions.map((p) => ({
1349
+ email: p.email,
1350
+ permission: p.permission,
1351
+ grantedAt: p.grantedAt,
1352
+ })),
1353
+ ...invitations
1354
+ .filter((i) => !i.accepted && new Date(i.expiresAt) > new Date())
1355
+ .map((i) => ({
1356
+ email: i.email,
1357
+ permission: i.permission,
1358
+ source: "invitation",
1359
+ status: "pending",
1360
+ })),
1361
+ ];
1362
+ fs.writeFileSync(path.join(docDir, "permissions.json"), JSON.stringify(permissionsExport, null, 2));
1363
+ // 5. Fetch aliases (user-scoped only)
1364
+ let aliases = [];
1365
+ try {
1366
+ const aliasResult = await client.listDocumentAliases(appId, documentId);
1367
+ aliases = (Array.isArray(aliasResult) ? aliasResult : aliasResult?.aliases || [])
1368
+ .filter((a) => (a.scope || a.aliasScope) === "user");
1369
+ }
1370
+ catch {
1371
+ // May not have aliases
1372
+ }
1373
+ // 6. Write metadata
1374
+ const metadata = {
1375
+ documentId: docMeta.documentId || documentId,
1376
+ title: docMeta.title,
1377
+ tags: docMeta.tags || [],
1378
+ createdAt: docMeta.createdAt,
1379
+ createdBy: docMeta.createdByEmail || docMeta.createdBy,
1380
+ aliases: aliases.map((a) => ({
1381
+ aliasScope: a.scope || a.aliasScope,
1382
+ aliasKey: a.aliasKey,
1383
+ })),
1384
+ };
1385
+ fs.writeFileSync(path.join(docDir, "metadata.json"), JSON.stringify(metadata, null, 2));
1386
+ // 7. Blobs
1387
+ if (includeBlobs) {
1388
+ let blobs = [];
1389
+ try {
1390
+ const blobResult = await client.listDocumentBlobs(appId, documentId);
1391
+ blobs = Array.isArray(blobResult) ? blobResult : blobResult?.items || blobResult?.blobs || [];
1392
+ }
1393
+ catch {
1394
+ // May not have blobs
1395
+ }
1396
+ if (blobs.length > 0) {
1397
+ const blobsDir = path.join(docDir, "blobs");
1398
+ fs.mkdirSync(blobsDir, { recursive: true });
1399
+ const blobIndex = blobs.map((b) => ({
1400
+ blobId: b.blobId,
1401
+ filename: b.filename,
1402
+ contentType: b.contentType,
1403
+ sha256: b.sha256,
1404
+ numBytes: b.numBytes,
1405
+ }));
1406
+ fs.writeFileSync(path.join(blobsDir, "index.json"), JSON.stringify(blobIndex, null, 2));
1407
+ // Download blobs (sequentially to avoid overwhelming the server)
1408
+ for (const blob of blobs) {
1409
+ const data = await client.downloadBlob(appId, documentId, blob.blobId);
1410
+ fs.writeFileSync(path.join(blobsDir, `${blob.blobId}.bin`), data);
1411
+ }
1412
+ }
1413
+ }
1414
+ if (jsonOutput) {
1415
+ json({
1416
+ documentId,
1417
+ title: metadata.title,
1418
+ stateBytes: stateResult.byteLength,
1419
+ permissions: permissionsExport.length,
1420
+ aliases: aliases.length,
1421
+ exportPath: docDir,
1422
+ });
1423
+ }
1424
+ else {
1425
+ success(`Exported document ${documentId} (${metadata.title || "untitled"}) to ${docDir}`);
1426
+ }
1427
+ }
1428
+ async function importSingleDocument(client, appId, docDir, overwrite, aliasMode, dryRun, summary, ownerUserId) {
1429
+ const metadataPath = path.join(docDir, "metadata.json");
1430
+ if (!fs.existsSync(metadataPath)) {
1431
+ warn(`Skipping ${docDir}: no metadata.json found`);
1432
+ summary.skipped++;
1433
+ return;
1434
+ }
1435
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
1436
+ const documentId = metadata.documentId;
1437
+ if (dryRun) {
1438
+ info(`[dry-run] Would import document ${documentId} (${metadata.title || "untitled"})`);
1439
+ summary.created++;
1440
+ return;
1441
+ }
1442
+ // Check if document exists
1443
+ let exists = false;
1444
+ try {
1445
+ await client.getDocument(appId, documentId);
1446
+ exists = true;
1447
+ }
1448
+ catch {
1449
+ exists = false;
1450
+ }
1451
+ if (exists && !overwrite) {
1452
+ warn(`Skipping ${documentId}: already exists (use --overwrite to replace)`);
1453
+ summary.skipped++;
1454
+ return;
1455
+ }
1456
+ // Create document if it doesn't exist
1457
+ if (!exists) {
1458
+ await client.createDocument(appId, {
1459
+ title: metadata.title || "Imported Document",
1460
+ documentId,
1461
+ tags: metadata.tags,
1462
+ ...(ownerUserId ? { createdBy: ownerUserId } : {}),
1463
+ });
1464
+ summary.created++;
1465
+ }
1466
+ else {
1467
+ summary.updated++;
1468
+ }
1469
+ // Import Yjs state
1470
+ const yjsPath = path.join(docDir, "document.yjs");
1471
+ if (fs.existsSync(yjsPath)) {
1472
+ const stateBuffer = fs.readFileSync(yjsPath);
1473
+ const stateBase64 = stateBuffer.toString("base64");
1474
+ await client.importDocumentState(appId, documentId, stateBase64);
1475
+ }
1476
+ // Upload blobs
1477
+ const blobIndexPath = path.join(docDir, "blobs", "index.json");
1478
+ if (fs.existsSync(blobIndexPath)) {
1479
+ const blobIndex = JSON.parse(fs.readFileSync(blobIndexPath, "utf-8"));
1480
+ for (const blob of blobIndex) {
1481
+ const blobPath = path.join(docDir, "blobs", `${blob.blobId}.bin`);
1482
+ if (fs.existsSync(blobPath)) {
1483
+ const data = fs.readFileSync(blobPath);
1484
+ await client.uploadBlob(appId, documentId, blob.blobId, data, {
1485
+ filename: blob.filename,
1486
+ contentType: blob.contentType,
1487
+ sha256: blob.sha256,
1488
+ });
1489
+ summary.blobsUploaded++;
1490
+ }
1491
+ }
1492
+ }
1493
+ // Permissions are exported for reference but not restored during import.
1494
+ // The importing admin is the new owner; sharing is managed in the target app.
1495
+ // Restore aliases
1496
+ if (metadata.aliases && Array.isArray(metadata.aliases)) {
1497
+ for (const alias of metadata.aliases) {
1498
+ if (alias.aliasScope !== "user")
1499
+ continue;
1500
+ if (!ownerUserId) {
1501
+ summary.aliasesSkipped++;
1502
+ continue;
1503
+ }
1504
+ try {
1505
+ await client.setDocumentAlias(appId, alias.aliasScope, alias.aliasKey, documentId, ownerUserId, aliasMode === "skip" // mustNotExist: true for skip mode, false for overwrite
1506
+ );
1507
+ summary.aliasesSet++;
1508
+ }
1509
+ catch {
1510
+ summary.aliasesSkipped++;
1511
+ }
1512
+ }
1513
+ }
1514
+ info(`Imported document ${documentId} (${metadata.title || "untitled"})`);
1515
+ }
178
1516
  //# sourceMappingURL=documents.js.map