@forumulate/cli 0.5.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/README.md +124 -5
  2. package/dist/api/args.d.ts +21 -0
  3. package/dist/api/args.d.ts.map +1 -0
  4. package/dist/api/args.js +81 -0
  5. package/dist/api/args.js.map +1 -0
  6. package/dist/api/command.d.ts +17 -0
  7. package/dist/api/command.d.ts.map +1 -0
  8. package/dist/api/command.js +182 -0
  9. package/dist/api/command.js.map +1 -0
  10. package/dist/api/describe.d.ts +5 -0
  11. package/dist/api/describe.d.ts.map +1 -0
  12. package/dist/api/describe.js +121 -0
  13. package/dist/api/describe.js.map +1 -0
  14. package/dist/api/http.d.ts +81 -0
  15. package/dist/api/http.d.ts.map +1 -0
  16. package/dist/api/http.js +183 -0
  17. package/dist/api/http.js.map +1 -0
  18. package/dist/api/operations.json +1 -0
  19. package/dist/api/output.d.ts +35 -0
  20. package/dist/api/output.d.ts.map +1 -0
  21. package/dist/api/output.js +176 -0
  22. package/dist/api/output.js.map +1 -0
  23. package/dist/api/request.d.ts +37 -0
  24. package/dist/api/request.d.ts.map +1 -0
  25. package/dist/api/request.js +230 -0
  26. package/dist/api/request.js.map +1 -0
  27. package/dist/api/send.d.ts +35 -0
  28. package/dist/api/send.d.ts.map +1 -0
  29. package/dist/api/send.js +85 -0
  30. package/dist/api/send.js.map +1 -0
  31. package/dist/api/spec.d.ts +96 -0
  32. package/dist/api/spec.d.ts.map +1 -0
  33. package/dist/api/spec.js +126 -0
  34. package/dist/api/spec.js.map +1 -0
  35. package/dist/commands/completion.d.ts +12 -0
  36. package/dist/commands/completion.d.ts.map +1 -0
  37. package/dist/commands/completion.js +169 -0
  38. package/dist/commands/completion.js.map +1 -0
  39. package/dist/commands/config.d.ts +33 -0
  40. package/dist/commands/config.d.ts.map +1 -0
  41. package/dist/commands/config.js +144 -0
  42. package/dist/commands/config.js.map +1 -0
  43. package/dist/commands/context.d.ts +35 -0
  44. package/dist/commands/context.d.ts.map +1 -0
  45. package/dist/commands/context.js +59 -0
  46. package/dist/commands/context.js.map +1 -0
  47. package/dist/commands/groups-api.d.ts +11 -0
  48. package/dist/commands/groups-api.d.ts.map +1 -0
  49. package/dist/commands/groups-api.js +140 -0
  50. package/dist/commands/groups-api.js.map +1 -0
  51. package/dist/commands/groups-community.d.ts +15 -0
  52. package/dist/commands/groups-community.d.ts.map +1 -0
  53. package/dist/commands/groups-community.js +344 -0
  54. package/dist/commands/groups-community.js.map +1 -0
  55. package/dist/commands/groups-content.d.ts +10 -0
  56. package/dist/commands/groups-content.d.ts.map +1 -0
  57. package/dist/commands/groups-content.js +331 -0
  58. package/dist/commands/groups-content.js.map +1 -0
  59. package/dist/commands/groups-docs.d.ts +12 -0
  60. package/dist/commands/groups-docs.d.ts.map +1 -0
  61. package/dist/commands/groups-docs.js +414 -0
  62. package/dist/commands/groups-docs.js.map +1 -0
  63. package/dist/commands/groups-org.d.ts +10 -0
  64. package/dist/commands/groups-org.d.ts.map +1 -0
  65. package/dist/commands/groups-org.js +405 -0
  66. package/dist/commands/groups-org.js.map +1 -0
  67. package/dist/commands/groups-site.d.ts +3 -0
  68. package/dist/commands/groups-site.d.ts.map +1 -0
  69. package/dist/commands/groups-site.js +237 -0
  70. package/dist/commands/groups-site.js.map +1 -0
  71. package/dist/commands/groups-spaces.d.ts +3 -0
  72. package/dist/commands/groups-spaces.d.ts.map +1 -0
  73. package/dist/commands/groups-spaces.js +273 -0
  74. package/dist/commands/groups-spaces.js.map +1 -0
  75. package/dist/commands/groups-types.d.ts +3 -0
  76. package/dist/commands/groups-types.d.ts.map +1 -0
  77. package/dist/commands/groups-types.js +251 -0
  78. package/dist/commands/groups-types.js.map +1 -0
  79. package/dist/commands/groups.d.ts +4 -0
  80. package/dist/commands/groups.d.ts.map +1 -0
  81. package/dist/commands/groups.js +71 -0
  82. package/dist/commands/groups.js.map +1 -0
  83. package/dist/commands/index.d.ts +25 -0
  84. package/dist/commands/index.d.ts.map +1 -0
  85. package/dist/commands/index.js +118 -0
  86. package/dist/commands/index.js.map +1 -0
  87. package/dist/commands/resource.d.ts +86 -0
  88. package/dist/commands/resource.d.ts.map +1 -0
  89. package/dist/commands/resource.js +314 -0
  90. package/dist/commands/resource.js.map +1 -0
  91. package/dist/content/library.d.ts +1 -0
  92. package/dist/content/library.d.ts.map +1 -1
  93. package/dist/content/library.js +1 -0
  94. package/dist/content/library.js.map +1 -1
  95. package/dist/dev/command.d.ts +41 -0
  96. package/dist/dev/command.d.ts.map +1 -0
  97. package/dist/dev/command.js +158 -0
  98. package/dist/dev/command.js.map +1 -0
  99. package/dist/help.d.ts +3 -0
  100. package/dist/help.d.ts.map +1 -0
  101. package/dist/help.js +159 -0
  102. package/dist/help.js.map +1 -0
  103. package/dist/publish/api.d.ts +30 -12
  104. package/dist/publish/api.d.ts.map +1 -1
  105. package/dist/publish/api.js +62 -10
  106. package/dist/publish/api.js.map +1 -1
  107. package/dist/publish/client.d.ts +19 -0
  108. package/dist/publish/client.d.ts.map +1 -0
  109. package/dist/publish/client.js +3 -0
  110. package/dist/publish/client.js.map +1 -0
  111. package/dist/publish/command.d.ts +31 -2
  112. package/dist/publish/command.d.ts.map +1 -1
  113. package/dist/publish/command.js +62 -15
  114. package/dist/publish/command.js.map +1 -1
  115. package/dist/publish/context.d.ts +4 -1
  116. package/dist/publish/context.d.ts.map +1 -1
  117. package/dist/publish/context.js.map +1 -1
  118. package/dist/publish/diff.d.ts +21 -0
  119. package/dist/publish/diff.d.ts.map +1 -0
  120. package/dist/publish/diff.js +96 -0
  121. package/dist/publish/diff.js.map +1 -0
  122. package/dist/publish/docs-apply.d.ts +8 -1
  123. package/dist/publish/docs-apply.d.ts.map +1 -1
  124. package/dist/publish/docs-apply.js +37 -10
  125. package/dist/publish/docs-apply.js.map +1 -1
  126. package/dist/publish/docs-plan.d.ts +16 -3
  127. package/dist/publish/docs-plan.d.ts.map +1 -1
  128. package/dist/publish/docs-plan.js +57 -6
  129. package/dist/publish/docs-plan.js.map +1 -1
  130. package/dist/publish/frontmatter.d.ts +1 -1
  131. package/dist/publish/frontmatter.d.ts.map +1 -1
  132. package/dist/publish/frontmatter.js +1 -0
  133. package/dist/publish/frontmatter.js.map +1 -1
  134. package/dist/publish/index.d.ts +12 -0
  135. package/dist/publish/index.d.ts.map +1 -0
  136. package/dist/publish/index.js +17 -0
  137. package/dist/publish/index.js.map +1 -0
  138. package/dist/publish/manifest.d.ts.map +1 -1
  139. package/dist/publish/manifest.js +23 -3
  140. package/dist/publish/manifest.js.map +1 -1
  141. package/dist/publish/names.d.ts +28 -0
  142. package/dist/publish/names.d.ts.map +1 -0
  143. package/dist/publish/names.js +80 -0
  144. package/dist/publish/names.js.map +1 -0
  145. package/dist/publish/posts.d.ts.map +1 -1
  146. package/dist/publish/posts.js +46 -13
  147. package/dist/publish/posts.js.map +1 -1
  148. package/dist/publish/reserved.d.ts +49 -0
  149. package/dist/publish/reserved.d.ts.map +1 -0
  150. package/dist/publish/reserved.js +118 -0
  151. package/dist/publish/reserved.js.map +1 -0
  152. package/dist/publish/rewrite.d.ts +12 -0
  153. package/dist/publish/rewrite.d.ts.map +1 -1
  154. package/dist/publish/rewrite.js +22 -0
  155. package/dist/publish/rewrite.js.map +1 -1
  156. package/dist/publish/scan-docs.d.ts +10 -0
  157. package/dist/publish/scan-docs.d.ts.map +1 -0
  158. package/dist/publish/scan-docs.js +160 -0
  159. package/dist/publish/scan-docs.js.map +1 -0
  160. package/dist/publish/scan.d.ts +21 -17
  161. package/dist/publish/scan.d.ts.map +1 -1
  162. package/dist/publish/scan.js +99 -232
  163. package/dist/publish/scan.js.map +1 -1
  164. package/dist/publish/slots.d.ts +39 -0
  165. package/dist/publish/slots.d.ts.map +1 -0
  166. package/dist/publish/slots.js +64 -0
  167. package/dist/publish/slots.js.map +1 -0
  168. package/dist/publish/tags.d.ts +9 -0
  169. package/dist/publish/tags.d.ts.map +1 -0
  170. package/dist/publish/tags.js +194 -0
  171. package/dist/publish/tags.js.map +1 -0
  172. package/dist/publish/types.d.ts +62 -0
  173. package/dist/publish/types.d.ts.map +1 -1
  174. package/dist/publish/types.js +0 -10
  175. package/dist/publish/types.js.map +1 -1
  176. package/dist/run.d.ts +16 -1
  177. package/dist/run.d.ts.map +1 -1
  178. package/dist/run.js +124 -117
  179. package/dist/run.js.map +1 -1
  180. package/dist/translations/command.d.ts +36 -0
  181. package/dist/translations/command.d.ts.map +1 -0
  182. package/dist/translations/command.js +411 -0
  183. package/dist/translations/command.js.map +1 -0
  184. package/dist/translations/extras.d.ts +29 -0
  185. package/dist/translations/extras.d.ts.map +1 -0
  186. package/dist/translations/extras.js +123 -0
  187. package/dist/translations/extras.js.map +1 -0
  188. package/dist/translations/files.d.ts +22 -0
  189. package/dist/translations/files.d.ts.map +1 -0
  190. package/dist/translations/files.js +75 -0
  191. package/dist/translations/files.js.map +1 -0
  192. package/dist/translations/sync.d.ts +89 -0
  193. package/dist/translations/sync.d.ts.map +1 -0
  194. package/dist/translations/sync.js +215 -0
  195. package/dist/translations/sync.js.map +1 -0
  196. package/dist/translations/target.d.ts +33 -0
  197. package/dist/translations/target.d.ts.map +1 -0
  198. package/dist/translations/target.js +54 -0
  199. package/dist/translations/target.js.map +1 -0
  200. package/dist/translations/xliff.d.ts +29 -0
  201. package/dist/translations/xliff.d.ts.map +1 -0
  202. package/dist/translations/xliff.js +62 -0
  203. package/dist/translations/xliff.js.map +1 -0
  204. package/package.json +6 -4
package/README.md CHANGED
@@ -34,11 +34,107 @@ The SDK (`ForumulateClient.fromEnv()`) and the MCP server ([`@forumulate/mcp`](h
34
34
  ## Quickstart
35
35
 
36
36
  ```sh
37
- forumulate orgs # id slug name
38
- forumulate spaces acme # an org id or its slug
39
- forumulate items acme <spaceId> # what is in a space, and where its content lives
37
+ forumulate config set org acme # the default for every --org
38
+ forumulate orgs list # id slug name
39
+ forumulate spaces list # the spaces in acme
40
+ forumulate items list --space docs # what is in a space, and where its content lives
41
+ forumulate api list --search webhook # every API operation, by summary
40
42
  ```
41
43
 
44
+ ## Resource commands
45
+
46
+ Every group takes the same verbs (`list`, `get`, `create`, `update`, `delete`, and `publish` where a thing has a draft), the same scope flags (`--org`, `--space`, `--library`, each an id or a slug), and `--json` for the API's answer untouched. `forumulate <group> --help` lists a group's verbs and the operation each one calls.
47
+
48
+ | Group | Verbs |
49
+ | --- | --- |
50
+ | `orgs list\|get\|create\|update\|delete`, `orgs deepl status\|set\|clear`, `orgs strings list\|set\|remove` | Your organizations, their DeepL key, and their own wording for interface strings. |
51
+ | `members list\|update\|remove\|sign-out\|reassign` | People in an org. |
52
+ | `invites list\|create\|bulk\|revoke\|pending\|accept\|redeem\|join`, `invites links list\|create\|revoke` | Invitations you send (one, many, or a shareable link) and the ones waiting for you. |
53
+ | `groups …`, `roles …`, `service-accounts` / `bots` `… tokens list\|create\|revoke` | Groups and join requests, custom roles, and non-human members with their tokens. |
54
+ | `tokens` / `pats` `list\|scopes\|create\|rotate\|rename\|revoke` | Your personal access tokens, by id or by name. |
55
+ | `webhooks list\|events\|create\|update\|delete\|test\|rotate-secret\|deliveries\|replay` | Endpoints, their deliveries, and replays. |
56
+ | `extensions …`, `audit list\|actions\|export` | Installed extensions and the marketplace; the audit log (export as CSV). |
57
+ | `spaces list\|overview\|get\|create\|update\|delete\|restore\|purge\|duplicate\|move\|rename\|access\|preview`, `spaces slugs list\|forget` | Spaces in an org: slugs, moves, copies and the trash. |
58
+ | `snapshots list\|create\|restore\|delete`, `shape get\|set\|patch\|preset\|kinds …` | Space snapshots, and a space's shape override. |
59
+ | `items` / `pages` `list\|get\|content\|create\|update\|write\|section\|fields\|chunks\|verify\|publish\|delete` | Any piece in a space. `--file page.md` fills the body; front matter sets `title` and `slug`. |
60
+ | `docs list\|get\|save\|publish\|unpublish\|history\|diff\|rollback\|move\|duplicate\|…`, `docs schedule get\|set\|cancel`, `docs checkpoints …`, `docs trash …`, `docs import zip\|docx\|url\|files`, `docs locales …`, `docs search` | A docs space's pages: drafts, revisions, scheduled publishing, rollback, the trash, imports and languages. |
61
+ | `collections …`, `versions list\|create\|update\|default\|delete\|settings\|configure` | Collections and their page order; docs versions. |
62
+ | `change-requests` / `crs` `list\|open\|review\|approve\|merge\|close\|rebase\|comment\|…`, `crs reviewers …`, `crs rules get\|set` | Proposed docs changes, review and merge rules. |
63
+ | `openapi list\|register\|resync\|lint\|import\|generate\|…`, `openapi sdk languages\|get\|download` | OpenAPI specs behind reference pages, and the SDKs generated from them. |
64
+ | `sdks packages\|register\|publish\|publishes`, `sdks registries list\|set\|test\|remove` | Publishing those SDKs to package registries. |
65
+ | `blog`, `forum`, `changelog` `list\|get\|create\|update\|publish\|delete\|…` | Articles, topics and releases; scheduling, pinning, settings, flags, polls, replies. `create --file post.md --publish`. |
66
+ | `blog` / `forum` / `changelog` / `status` `translations list\|get\|put\|remove\|auto` | One piece's translations at its own door. `translations` (below) is the whole-space workflow. |
67
+ | `roadmap board\|get\|submit\|update\|status\|vote\|comment\|merge\|proxy-votes …` | A roadmap space. |
68
+ | `status board`, `status incidents …`, `status components …`, `status channels …`, `status groups get\|set` | A status space: incidents, components, where they are announced. |
69
+ | `events …`, `course outline\|enrol\|complete`, `careers …`, `forms …` | Events and RSVPs, course progress, job applications, forms and submissions (CSV export). |
70
+ | `libraries list\|get\|create\|update\|delete`, `libraries members\|locales\|model\|keys …` | Headless CMS libraries: grants, languages, the model, delivery keys. |
71
+ | `entries list\|types\|get\|create\|update\|fields\|patch\|publish\|bulk\|trash\|restore\|lock …\|locale get\|set` | A library's entries. |
72
+ | `media …`, `releases list\|create\|add\|schedule\|publish\|rollback\|…`, `environments` / `envs list\|fork\|compare\|promote` | Library media, content releases, and sandbox environments. |
73
+ | `domains list\|add\|setup\|verify\|activate\|www\|allow-ips\|redirect\|remove\|…`, `assets list\|all\|upload\|download\|delete` | Custom domains on a space (by id or hostname), and its uploaded files. |
74
+ | `theme get\|set\|patch\|export\|import`, `theme draft get\|set\|patch\|apply\|discard\|publish` | A space's theme: kept in git with `export`/`import`, staged as a draft, then published. |
75
+ | `sites …`, `sites domains …`, `sites theme export\|import`, `sites draft …` | Sites: several spaces under one domain and theme. |
76
+ | `github status\|connect\|import\|export\|pull-request` | Sync a docs space with a repository. |
77
+ | `reader page\|markdown\|pdf\|version`, `reader blog index\|article\|rss\|json-feed\|…` | What a reader is served, as the public API answers it (`--out` for a PDF or feed). |
78
+ | `space-types list\|get\|presets\|slots` | What each kind of space is. |
79
+ | `search <query…>`, `search semantic\|recent\|tags\|tag\|gaps\|reindex` | Unified search across docs, forum and blog, and the searches that found nothing. |
80
+
81
+ Flags are derived from the API itself: every query parameter of the operation (`--limit`, `--state`) and every top-level body field (`--name`, `--visibility`) is a flag, and `--data`/`--field` work as they do under `forumulate api`. A Markdown file with front matter fills an item, post, topic or release: `title` and `slug` come from the front matter unless you pass them.
82
+
83
+ ```sh
84
+ forumulate spaces create Guides --slug guides --type DOCS --visibility PUBLIC
85
+ forumulate items create --space guides --file getting-started.md --publish
86
+ forumulate webhooks create https://hooks.example.com/f --events page.published,post.created
87
+ forumulate webhooks deliveries <webhookId> && forumulate webhooks replay <webhookId> <deliveryId>
88
+ forumulate tokens rotate ci-deploy --graceMinutes 60
89
+ forumulate status incidents create "API latency" --space status --severity minor
90
+ ```
91
+
92
+ `delete`, `remove` and `revoke` ask first; `--yes` skips the question, and with no terminal (CI) `--yes` is required. `--dry-run` prints the request instead of sending it. A command whose answer is a file (an SDK archive, a PDF, a CSV export) writes it to `--out <file>`, and one that uploads (`assets upload`, `media upload`, `docs import zip`) takes the local path as its argument.
93
+
94
+ ```sh
95
+ forumulate docs schedule set <pageId> 2026-11-01T09:00:00Z --space guides
96
+ forumulate docs rollback <pageId> <revisionId> --mode draft --space guides
97
+ forumulate crs open "Rate limits rewrite" --space guides && forumulate crs merge <crId> --space guides
98
+ forumulate openapi resync <specId> --space reference --background true
99
+ forumulate releases create "Spring launch" && forumulate releases schedule <releaseId> 2026-03-01T08:00:00Z
100
+ forumulate theme export --space docs --json > theme.json && forumulate theme import --space docs --data @theme.json
101
+ forumulate blog translations auto <postId> es --space blog
102
+ forumulate audit export --from 2026-10-01 --out audit.csv
103
+ ```
104
+
105
+ ## `forumulate api`: every operation
106
+
107
+ The resource commands cover the daily work. `forumulate api` covers the rest — every operation in the API, from a copy of the OpenAPI document bundled with the CLI.
108
+
109
+ ```sh
110
+ forumulate api list --tag Webhook # operationId, method, path, summary
111
+ forumulate api describe Space_create # parameters as flags, the body as fields
112
+ forumulate api Space_list acme --limit 20 # path params positionally…
113
+ forumulate api Space_get --orgId acme --spaceId <id> # …or by name (--org-id works too)
114
+ forumulate api GET /orgs/{orgId}/members --paginate --json
115
+ forumulate api POST /orgs/acme/spaces --data @space.json
116
+ cat body.json | forumulate api Item_putContent acme <spaceId> <itemId> --data -
117
+ forumulate api Space_update acme <spaceId> --field settings.overview="Start here"
118
+ ```
119
+
120
+ - **Parameters**: `--<name> value` for path, query and header parameters; a repeated flag is joined with commas (how every array parameter here is spelled). Path parameters may be positional, in path order; `orgId` defaults to `--org`, `FORUMULATE_ORG`, then `forumulate config`. `-p name=value` sets one whose name collides with a CLI flag.
121
+ - **Body**: `--data '{…}'`, `--data @file.json` or `--data -` (stdin), then `--field a.b=value` (typed by the body schema; `a:=<json>` for raw JSON), then any `--<field>` the body declares. Multipart operations take `--form name=value` and `--form file=@path`.
122
+ - **Output**: a list prints as a table with a footer saying whether there is more (`--cursor <next>`), a single resource as `key value` lines; `--json` prints the body as received; `--paginate` follows `page.nextCursor` to the end and prints one merged list; `--columns a,b` picks table columns; `-i` adds the status and headers.
123
+ - **Transport**: `Forumulate-Version: 2026-12-01` on every request (`--api-version` or `forumulate config set api-version` to pin another), an `Idempotency-Key` on every write that declares one (the same key on a retry), and retries for a 429 (honouring `Retry-After`) and for a 502/503/504 on a request that is safe to repeat.
124
+ - **Exit status** is 0 on a 2xx and 1 on anything else, including a usage error, so it works in a pipeline.
125
+
126
+ A route newer than your copy of the CLI still works as `forumulate api <METHOD> <path>`; it is sent as written.
127
+
128
+ ## Configuration and completion
129
+
130
+ ```sh
131
+ forumulate config set org acme # also: space, library, api-version
132
+ forumulate config list # value and where it came from
133
+ eval "$(forumulate completion zsh)" # or bash; fish: forumulate completion fish > ~/.config/fish/completions/forumulate.fish
134
+ ```
135
+
136
+ A flag beats the environment (`FORUMULATE_ORG`, `FORUMULATE_SPACE`, `FORUMULATE_LIBRARY`, `FORUMULATE_API_VERSION`), and the environment beats `~/.config/forumulate/config.json`. Completion covers commands, verbs, operationIds and each operation's flags.
137
+
42
138
  ## Common tasks
43
139
 
44
140
  **1. Publish a folder of Markdown (docs-as-code).** One folder per collection, nested folders as parent pages, front matter for title, slug, description, icon, tags, order and any other page field, images uploaded, links rewritten. Blog and changelog folders become posts. Re-runs change only what changed; `--prune` removes what the folder no longer has; the space is read back afterwards. An optional `forumulate.yaml` sets the space's settings and theme, the site, an OpenAPI reference and status monitors. See [Publish a folder of Markdown from git](https://forumulate.com/guides/publish-from-git) and `examples/publish-docs.yml` for GitHub Actions.
@@ -49,6 +145,14 @@ forumulate publish ./docs --org acme --space docs --prune
49
145
  forumulate publish . --only getting-started,theme # just these
50
146
  ```
51
147
 
148
+ **1b. Sync a folder (content as code).** `forumulate sync <folder>` is the same engine as a plan by default: `--apply` writes it, `--diff` shows what changes inside each post or page, `--prune` removes what has no file. A `forumulate.yml` maps folders to spaces: blog posts, docs pages, FAQ questions (with `short-answer:` and `related:` slots), changelog releases and blog categories (`tags:` in the manifest or `_tags.yml`: label, colour, cover, icon, parent). Matched by slug, images uploaded once by content hash, `README`/`LICENSE`/`_drafts/`/dotfiles skipped — and a second run writes nothing. See [Content as code](https://forumulate.com/guides/content-as-code).
149
+
150
+ ```sh
151
+ forumulate sync . # the plan
152
+ forumulate sync . --diff # the plan, with paragraph and field diffs
153
+ forumulate sync . --apply --prune # do it, read it back
154
+ ```
155
+
52
156
  **2. Publish one page by id.**
53
157
 
54
158
  ```sh
@@ -58,7 +162,7 @@ forumulate publish acme <spaceId> <pageId>
58
162
  **3. Search docs, forum and blog together.**
59
163
 
60
164
  ```sh
61
- forumulate search acme rate limits
165
+ forumulate search rate limits --org acme # or, older form: forumulate search acme rate limits
62
166
  ```
63
167
 
64
168
  **4. Snapshot a space before a big change.**
@@ -74,7 +178,19 @@ forumulate domains acme <spaceId> # each domain and its
74
178
  forumulate domains verify acme <spaceId> docs.acme.dev # re-check, and print the DNS still missing
75
179
  ```
76
180
 
77
- Add `--json` to any command for raw JSON, `--version` for the version, and `forumulate help` for the full list. Every command exits non-zero on failure, so it works in a pipeline.
181
+ **6. Manage translations.** Use `forumulate translations`, or its alias `forumulate i18n`. It covers every translation capability: a space's or library's languages, coverage, a file round trip, machine translation with your own key, chrome labels, interface overrides, names and the switcher. See [Translations everywhere](https://forumulate.com/guides/translations-everywhere).
182
+
183
+ ```sh
184
+ forumulate translations locales set es=Español fr --space docs # declare exactly these
185
+ forumulate translations status --space docs --fail-on missing,outdated # CI gate (exit 1)
186
+ forumulate translations pull --space docs --include-missing # translations/<locale>/<slug>.md
187
+ forumulate translations push --space docs --dry-run # the diff; drop --dry-run to apply
188
+ forumulate translations auto --space docs --locale es # your DeepL key; missing + outdated
189
+ forumulate translations labels pull --space docs --format xliff # for your TMS
190
+ forumulate translations overrides push overrides.json --prune
191
+ ```
192
+
193
+ Add `--json` to any command for raw JSON, `--version` for the version, and `forumulate help` for the full list. Every command exits non-zero on failure, so it works in a pipeline. The older positional forms — `spaces <orgId>`, `items <orgId> <spaceId>`, `search <orgId> <query…>`, `domains [verify] <orgId> <spaceId>` — still work and print exactly what they did.
78
194
 
79
195
  ### In CI
80
196
 
@@ -83,6 +199,9 @@ Add `--json` to any command for raw JSON, `--version` for the version, and `foru
83
199
  - run: npx -y @forumulate/cli publish acme ${{ vars.DOCS_SPACE_ID }} ./docs
84
200
  env:
85
201
  FORUMULATE_TOKEN: ${{ secrets.FORUMULATE_TOKEN }}
202
+ - run: npx -y @forumulate/cli translations status --org acme --space docs --fail-on missing,outdated
203
+ env:
204
+ FORUMULATE_TOKEN: ${{ secrets.FORUMULATE_TOKEN }}
86
205
  ```
87
206
 
88
207
  ## `forumulate conformance`
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Argument parsing for `forumulate api` and the resource commands.
3
+ *
4
+ * The older commands parse against a fixed list of value flags. These cannot:
5
+ * `forumulate api` takes a flag per API parameter, and there are a thousand of
6
+ * them. So the rule is inverted — every `--flag` takes the next token as its
7
+ * value unless it is a known switch, the next token is itself a flag, or the
8
+ * flag is written `--flag=value`. `--no-x` is `x=false`.
9
+ */
10
+ export interface Parsed {
11
+ positionals: string[];
12
+ /** Every value given for a flag, in order (a flag may repeat). */
13
+ flags: Map<string, string[]>;
14
+ }
15
+ /** Flags that never take a value. */
16
+ export declare const SWITCHES: Set<string>;
17
+ export declare function parseArgs(argv: string[], switches?: Set<string>): Parsed;
18
+ export declare const flag: (p: Parsed, name: string) => string | undefined;
19
+ export declare const has: (p: Parsed, name: string) => boolean;
20
+ export declare const all: (p: Parsed, name: string) => string[];
21
+ //# sourceMappingURL=args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/api/args.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,WAAW,MAAM;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,kEAAkE;IAClE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAC9B;AAED,qCAAqC;AACrC,eAAO,MAAM,QAAQ,aAanB,CAAC;AAYH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,QAAQ,GAAE,GAAG,CAAC,MAAM,CAAY,GAAG,MAAM,CAsClF;AAED,eAAO,MAAM,IAAI,GAAI,GAAG,MAAM,EAAE,MAAM,MAAM,KAAG,MAAM,GAAG,SAAsC,CAAC;AAC/F,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,EAAE,MAAM,MAAM,KAAG,OAG7C,CAAC;AACF,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,EAAE,MAAM,MAAM,KAAG,MAAM,EAA6B,CAAC"}
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.all = exports.has = exports.flag = exports.SWITCHES = void 0;
4
+ exports.parseArgs = parseArgs;
5
+ /** Flags that never take a value. */
6
+ exports.SWITCHES = new Set([
7
+ 'json',
8
+ 'paginate',
9
+ 'yes',
10
+ 'y',
11
+ 'help',
12
+ 'h',
13
+ 'dry-run',
14
+ 'verbose',
15
+ 'include-headers',
16
+ 'publish',
17
+ 'all',
18
+ 'force',
19
+ ]);
20
+ const SHORT = {
21
+ f: 'field',
22
+ H: 'header',
23
+ d: 'data',
24
+ p: 'param',
25
+ y: 'yes',
26
+ h: 'help',
27
+ i: 'include-headers',
28
+ };
29
+ function parseArgs(argv, switches = exports.SWITCHES) {
30
+ const positionals = [];
31
+ const flags = new Map();
32
+ const add = (k, v) => flags.set(k, [...(flags.get(k) ?? []), v]);
33
+ for (let i = 0; i < argv.length; i++) {
34
+ const a = argv[i];
35
+ if (a === '--') {
36
+ positionals.push(...argv.slice(i + 1));
37
+ break;
38
+ }
39
+ let name;
40
+ if (a.startsWith('--') && a.length > 2)
41
+ name = a.slice(2);
42
+ else if (/^-[a-zA-Z]$/.test(a))
43
+ name = SHORT[a.slice(1)] ?? a.slice(1);
44
+ if (name === undefined) {
45
+ positionals.push(a);
46
+ continue;
47
+ }
48
+ const eq = name.indexOf('=');
49
+ if (eq > 0) {
50
+ add(name.slice(0, eq), name.slice(eq + 1));
51
+ continue;
52
+ }
53
+ if (switches.has(name)) {
54
+ add(name, 'true');
55
+ continue;
56
+ }
57
+ if (name.startsWith('no-')) {
58
+ add(name.slice(3), 'false');
59
+ continue;
60
+ }
61
+ const next = argv[i + 1];
62
+ // `--data -` is stdin, so a lone dash is a value, not a flag.
63
+ if (next !== undefined && (next === '-' || !next.startsWith('-') || /^-\d/.test(next))) {
64
+ add(name, next);
65
+ i++;
66
+ }
67
+ else
68
+ add(name, 'true');
69
+ }
70
+ return { positionals, flags };
71
+ }
72
+ const flag = (p, name) => p.flags.get(name)?.at(-1);
73
+ exports.flag = flag;
74
+ const has = (p, name) => {
75
+ const v = (0, exports.flag)(p, name);
76
+ return v !== undefined && v !== 'false';
77
+ };
78
+ exports.has = has;
79
+ const all = (p, name) => p.flags.get(name) ?? [];
80
+ exports.all = all;
81
+ //# sourceMappingURL=args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/api/args.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { CommandDeps } from '../commands/context';
2
+ /**
3
+ * `forumulate api` — every operation in the API, from the bundled spec.
4
+ *
5
+ * forumulate api list [--tag Space] [--search webhook]
6
+ * forumulate api describe Space_create
7
+ * forumulate api Space_list acme --limit 20
8
+ * forumulate api GET /orgs/{orgId}/spaces acme --paginate
9
+ * forumulate api POST /orgs/acme/spaces --data @space.json
10
+ *
11
+ * The resource commands (`spaces`, `webhooks`, …) cover what people do daily;
12
+ * this covers everything else, so a route that ships today is callable today
13
+ * with no CLI release — `operations.test.ts` walks the spec to keep it so.
14
+ */
15
+ export declare const API_HELP = "forumulate api \u2014 call any operation in the Forumulate API\n\nUsage:\n forumulate api list [--tag <tag>] [--search <words>] [--method GET]\n forumulate api describe <operationId | METHOD path> [--json]\n forumulate api <operationId> [path params\u2026] [flags]\n forumulate api <METHOD> <path> [path params\u2026] [flags]\n\nParameters:\n --<name> <value> a path, query or header parameter (--orgId, --org-id)\n -p, --param name=value the same, for a name that collides with a flag below\n path params may also be given positionally, in path order; orgId defaults\n to --org / FORUMULATE_ORG / `forumulate config set org \u2026`\n\nBody:\n -d, --data '{\u2026}' | @file.json | - JSON body (- reads stdin)\n -f, --field a.b=value one field, typed by the body schema (a:=<json> raw)\n --<field> <value> a top-level body field the operation declares\n --form name=value | name=@file multipart operations (uploads)\n\nOutput and transport:\n --json the response body, untouched\n --paginate follow page.nextCursor to the end (one merged list)\n --max-pages <n> stop after n pages (default 1000)\n --columns a,b,c table columns for a list answer\n -i, --include-headers print the status line and response headers\n -H, --header 'Name: v' an extra request header\n --api-version <date> Forumulate-Version to send (default 2026-12-01)\n --idempotency-key <k> instead of the generated one\n --dry-run print the request, send nothing\n\nExit status is 0 on a 2xx and 1 otherwise (usage errors included).";
16
+ export declare function runApi(argv: string[], deps: CommandDeps): Promise<number>;
17
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/api/command.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,QAAQ,+kDA+B8C,CAAC;AAIpE,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAgC/E"}
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.API_HELP = void 0;
4
+ exports.runApi = runApi;
5
+ const args_1 = require("./args");
6
+ const describe_1 = require("./describe");
7
+ const output_1 = require("./output");
8
+ const send_1 = require("./send");
9
+ const spec_1 = require("./spec");
10
+ const request_1 = require("./request");
11
+ /**
12
+ * `forumulate api` — every operation in the API, from the bundled spec.
13
+ *
14
+ * forumulate api list [--tag Space] [--search webhook]
15
+ * forumulate api describe Space_create
16
+ * forumulate api Space_list acme --limit 20
17
+ * forumulate api GET /orgs/{orgId}/spaces acme --paginate
18
+ * forumulate api POST /orgs/acme/spaces --data @space.json
19
+ *
20
+ * The resource commands (`spaces`, `webhooks`, …) cover what people do daily;
21
+ * this covers everything else, so a route that ships today is callable today
22
+ * with no CLI release — `operations.test.ts` walks the spec to keep it so.
23
+ */
24
+ exports.API_HELP = `forumulate api — call any operation in the Forumulate API
25
+
26
+ Usage:
27
+ forumulate api list [--tag <tag>] [--search <words>] [--method GET]
28
+ forumulate api describe <operationId | METHOD path> [--json]
29
+ forumulate api <operationId> [path params…] [flags]
30
+ forumulate api <METHOD> <path> [path params…] [flags]
31
+
32
+ Parameters:
33
+ --<name> <value> a path, query or header parameter (--orgId, --org-id)
34
+ -p, --param name=value the same, for a name that collides with a flag below
35
+ path params may also be given positionally, in path order; orgId defaults
36
+ to --org / FORUMULATE_ORG / \`forumulate config set org …\`
37
+
38
+ Body:
39
+ -d, --data '{…}' | @file.json | - JSON body (- reads stdin)
40
+ -f, --field a.b=value one field, typed by the body schema (a:=<json> raw)
41
+ --<field> <value> a top-level body field the operation declares
42
+ --form name=value | name=@file multipart operations (uploads)
43
+
44
+ Output and transport:
45
+ --json the response body, untouched
46
+ --paginate follow page.nextCursor to the end (one merged list)
47
+ --max-pages <n> stop after n pages (default 1000)
48
+ --columns a,b,c table columns for a list answer
49
+ -i, --include-headers print the status line and response headers
50
+ -H, --header 'Name: v' an extra request header
51
+ --api-version <date> Forumulate-Version to send (default 2026-12-01)
52
+ --idempotency-key <k> instead of the generated one
53
+ --dry-run print the request, send nothing
54
+
55
+ Exit status is 0 on a 2xx and 1 otherwise (usage errors included).`;
56
+ const API_SWITCHES = new Set(['json', 'paginate', 'include-headers', 'dry-run', 'help', 'verbose']);
57
+ async function runApi(argv, deps) {
58
+ const parsed = (0, args_1.parseArgs)(argv, API_SWITCHES);
59
+ const [first, ...rest] = parsed.positionals;
60
+ const json = (0, args_1.has)(parsed, 'json');
61
+ if (!first || first === 'help' || ((0, args_1.has)(parsed, 'help') && first !== 'describe')) {
62
+ deps.out(exports.API_HELP);
63
+ return first || (0, args_1.has)(parsed, 'help') ? 0 : 1;
64
+ }
65
+ try {
66
+ if (first === 'list' || first === 'ls')
67
+ return list(parsed, rest, deps, json);
68
+ if (first === 'describe') {
69
+ const target = resolveOp(rest);
70
+ if (!target)
71
+ return usage(deps, 'api describe <operationId | METHOD path>', rest.join(' '));
72
+ if (json)
73
+ deps.out(JSON.stringify({ ...target.op, b: undefined, body: target.op.b ? (0, describe_1.expand)(target.op.b) : undefined }, null, 2));
74
+ else
75
+ for (const l of (0, describe_1.describe)(target.op))
76
+ deps.out(l);
77
+ return 0;
78
+ }
79
+ return await invoke(parsed, deps, json);
80
+ }
81
+ catch (e) {
82
+ if (e instanceof request_1.UsageError) {
83
+ deps.err(`Usage: ${e.message}`);
84
+ return 1;
85
+ }
86
+ throw e;
87
+ }
88
+ }
89
+ function usage(deps, form, got) {
90
+ if (got)
91
+ deps.err(`No operation matches "${got}". Try: forumulate api list --search <words>`);
92
+ deps.err(`Usage: forumulate ${form}`);
93
+ return 1;
94
+ }
95
+ /** `Space_list` or `GET /orgs/{orgId}/spaces` or `GET /orgs/acme/spaces`. */
96
+ function resolveOp(words) {
97
+ const [a, b] = words;
98
+ if (!a)
99
+ return undefined;
100
+ if ((0, spec_1.isMethod)(a) && b?.startsWith('/')) {
101
+ const hit = (0, spec_1.findByPath)(a, b);
102
+ return hit && { ...hit, used: 2 };
103
+ }
104
+ const op = (0, spec_1.findById)(a);
105
+ return op && { op, values: {}, used: 1 };
106
+ }
107
+ function list(parsed, rest, deps, json) {
108
+ const ops = (0, spec_1.filterOps)({
109
+ tag: (0, args_1.flag)(parsed, 'tag'),
110
+ search: [(0, args_1.flag)(parsed, 'search'), ...rest].filter(Boolean).join(' ') || undefined,
111
+ method: (0, args_1.flag)(parsed, 'method'),
112
+ });
113
+ if (json) {
114
+ deps.out(JSON.stringify(ops.map((o) => ({ operationId: o.id, method: o.m, path: o.p, summary: o.s, tags: o.t })), null, 2));
115
+ return 0;
116
+ }
117
+ if (ops.length === 0) {
118
+ deps.err('No operations match.');
119
+ return 1;
120
+ }
121
+ for (const l of (0, output_1.table)(ops.map((o) => ({ operationId: o.id, method: o.m, path: o.p, summary: o.s ?? '' })), ['operationId', 'method', 'path', 'summary']))
122
+ deps.out(l);
123
+ deps.out(`-- ${ops.length} of ${(0, spec_1.loadIndex)().ops.length} operations`);
124
+ return 0;
125
+ }
126
+ async function invoke(parsed, deps, json) {
127
+ const words = parsed.positionals;
128
+ const target = resolveOp(words);
129
+ const src = {
130
+ readFile: deps.readFile,
131
+ readStdin: deps.readStdin,
132
+ defaultOrg: (0, args_1.flag)(parsed, 'org') ?? deps.defaultOrg,
133
+ };
134
+ let request;
135
+ let op;
136
+ if (target) {
137
+ op = target.op;
138
+ ({ request } = await (0, request_1.planRequest)(op, parsed, words.slice(target.used), src, target.values));
139
+ }
140
+ else if ((0, spec_1.isMethod)(words[0]) && words[1]?.startsWith('/')) {
141
+ // A path the bundled spec does not know (a route newer than this CLI):
142
+ // send it as written, unknown flags as query parameters.
143
+ const query = {};
144
+ for (const [k, v] of parsed.flags)
145
+ if (!request_1.API_FLAGS.has(k))
146
+ query[k] = v.join(',');
147
+ const fake = {
148
+ id: `${words[0].toUpperCase()} ${words[1]}`,
149
+ m: words[0].toUpperCase(),
150
+ p: words[1].split('?')[0],
151
+ b: {},
152
+ };
153
+ const withQuery = {
154
+ ...parsed,
155
+ flags: new Map([...parsed.flags].filter(([k]) => request_1.API_FLAGS.has(k))),
156
+ };
157
+ ({ request } = await (0, request_1.planRequest)(fake, withQuery, words.slice(2), src));
158
+ request.query = {
159
+ ...Object.fromEntries(new URLSearchParams(words[1].split('?')[1] ?? '')),
160
+ ...query,
161
+ };
162
+ deps.err(`note: ${request.method} ${fake.p} is not in this CLI's bundled spec; sending it as written.`);
163
+ }
164
+ else {
165
+ return usage(deps, 'api <operationId | METHOD path> [flags]', words.join(' '));
166
+ }
167
+ const http = deps.http;
168
+ if ((0, args_1.has)(parsed, 'dry-run') && !op?.q?.some((p) => p.n === 'dryRun')) {
169
+ (0, send_1.printRequest)(http, request, deps);
170
+ return 0;
171
+ }
172
+ await (0, send_1.sendAndPrint)(http, request, {
173
+ json,
174
+ paginate: (0, args_1.has)(parsed, 'paginate'),
175
+ maxPages: Number((0, args_1.flag)(parsed, 'max-pages') ?? 1000),
176
+ columns: columns(parsed),
177
+ includeHeaders: (0, args_1.has)(parsed, 'include-headers'),
178
+ }, deps);
179
+ return 0;
180
+ }
181
+ const columns = (p) => (0, args_1.flag)(p, 'columns')?.split(',').filter(Boolean);
182
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/api/command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { type Operation, type SchemaNode } from './spec';
2
+ export declare function describe(op: Operation): string[];
3
+ /** The body schema as JSON, refs expanded a few levels — `describe --json`. */
4
+ export declare function expand(node: SchemaNode | undefined, depth?: number): unknown;
5
+ //# sourceMappingURL=describe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"describe.d.ts","sourceRoot":"","sources":["../../src/api/describe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqC,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;AAyD5F,wBAAgB,QAAQ,CAAC,EAAE,EAAE,SAAS,GAAG,MAAM,EAAE,CA0BhD;AAED,+EAA+E;AAC/E,wBAAgB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,EAAE,KAAK,SAAI,GAAG,OAAO,CAmBvE"}
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.describe = describe;
4
+ exports.expand = expand;
5
+ const spec_1 = require("./spec");
6
+ /**
7
+ * `forumulate api describe <operationId>`: what to pass, in the CLI's own terms.
8
+ *
9
+ * Each parameter is printed as the flag that sets it, and the body as an
10
+ * indented field list with types, required marks and enums — enough to write
11
+ * `--field` flags or a `--data` file without opening the reference.
12
+ */
13
+ /** An enum of 40 event names is a paragraph, not a type: show a few and say how many. */
14
+ function enumText(values) {
15
+ const shown = values
16
+ .slice(0, 8)
17
+ .map((v) => JSON.stringify(v))
18
+ .join('|');
19
+ return values.length > 8
20
+ ? `${shown}|… (${values.length} values; describe --json lists all)`
21
+ : shown;
22
+ }
23
+ function typeOf(node, depth = 0) {
24
+ const n = (0, spec_1.resolveRef)(node);
25
+ if (!n)
26
+ return 'any';
27
+ if (n.c !== undefined)
28
+ return JSON.stringify(n.c);
29
+ if (n.e)
30
+ return enumText(n.e);
31
+ if (n.any)
32
+ return [...new Set(n.any.map((v) => typeOf(v, depth + 1)))].join(' | ');
33
+ if (n.t === 'array')
34
+ return `${typeOf(n.i, depth + 1)}[]`;
35
+ return (n.t ?? (n.p ? 'object' : 'any')) + (n.n ? '?' : '');
36
+ }
37
+ function fields(node, indent, depth, out) {
38
+ const n = (0, spec_1.resolveRef)(node);
39
+ if (!n || depth > 3)
40
+ return;
41
+ if (n.any) {
42
+ const objects = n.any.map((v) => (0, spec_1.resolveRef)(v)).filter((v) => v?.p);
43
+ if (objects.length > 1) {
44
+ objects.forEach((v, i) => {
45
+ out.push(`${indent}one of (${i + 1}/${objects.length})${v.d ? ` — ${v.d}` : ''}`);
46
+ fields(v, `${indent} `, depth + 1, out);
47
+ });
48
+ return;
49
+ }
50
+ if (objects.length === 1)
51
+ return fields(objects[0], indent, depth, out);
52
+ }
53
+ for (const part of n.all ?? [])
54
+ fields(part, indent, depth + 1, out);
55
+ const required = new Set(n.r ?? []);
56
+ for (const [k, v] of Object.entries(n.p ?? {})) {
57
+ const child = (0, spec_1.resolveRef)(v);
58
+ out.push(`${indent}${k}${required.has(k) ? '*' : ''} ${typeOf(v)}${child?.d ? ` — ${child.d}` : ''}`);
59
+ const inner = child?.t === 'array' ? (0, spec_1.resolveRef)(child.i) : child;
60
+ if (inner?.p && depth < 2)
61
+ fields(inner, `${indent} `, depth + 1, out);
62
+ }
63
+ }
64
+ function describe(op) {
65
+ const out = [`${op.id} ${op.m} ${op.p}${op.dep ? ' (deprecated)' : ''}`];
66
+ if (op.s)
67
+ out.push(` ${op.s}`);
68
+ if (op.t?.length)
69
+ out.push(` tags: ${op.t.join(', ')}`);
70
+ const params = op.q ?? [];
71
+ const path = (0, spec_1.pathParams)(op);
72
+ out.push('', 'Usage:', ` forumulate api ${op.id}${path.map((p) => ` <${p}>`).join('')} [flags]`);
73
+ if (params.length) {
74
+ out.push('', 'Parameters:');
75
+ for (const p of params) {
76
+ const t = p.e ? enumText(p.e) : (p.t ?? 'string');
77
+ out.push(` --${p.n}${p.r ? '*' : ''} ${p.in} ${t}${p.d ? ` — ${p.d}` : ''}`);
78
+ }
79
+ }
80
+ if (op.b) {
81
+ out.push('', `Body (${op.mp ? 'multipart/form-data: --form name=value | name=@file' : 'JSON: --data, --field, or --<field>'})${op.br ? ', required' : ''}:`);
82
+ fields(op.b, ' ', 0, out);
83
+ }
84
+ if (op.ik)
85
+ out.push('', 'Sends an Idempotency-Key (override with --idempotency-key).');
86
+ if (params.some((p) => p.n === 'cursor'))
87
+ out.push('Paged: --paginate follows page.nextCursor to the end.');
88
+ out.push('', '* required');
89
+ return out;
90
+ }
91
+ /** The body schema as JSON, refs expanded a few levels — `describe --json`. */
92
+ function expand(node, depth = 0) {
93
+ const n = (0, spec_1.resolveRef)(node, (0, spec_1.loadIndex)());
94
+ if (!n)
95
+ return {};
96
+ if (depth > 6)
97
+ return { type: n.t ?? 'object' };
98
+ const out = {};
99
+ if (n.t)
100
+ out.type = n.t;
101
+ if (n.d)
102
+ out.description = n.d;
103
+ if (n.e)
104
+ out.enum = n.e;
105
+ if (n.c !== undefined)
106
+ out.const = n.c;
107
+ if (n.n)
108
+ out.nullable = true;
109
+ if (n.r)
110
+ out.required = n.r;
111
+ if (n.p)
112
+ out.properties = Object.fromEntries(Object.entries(n.p).map(([k, v]) => [k, expand(v, depth + 1)]));
113
+ if (n.i)
114
+ out.items = expand(n.i, depth + 1);
115
+ if (n.any)
116
+ out.anyOf = n.any.map((v) => expand(v, depth + 1));
117
+ if (n.all)
118
+ out.allOf = n.all.map((v) => expand(v, depth + 1));
119
+ return out;
120
+ }
121
+ //# sourceMappingURL=describe.js.map