monday-cli 0.3.0 → 0.5.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 (198) hide show
  1. package/CHANGELOG.md +719 -0
  2. package/README.md +208 -36
  3. package/dist/api/assets.d.ts +326 -0
  4. package/dist/api/assets.d.ts.map +1 -0
  5. package/dist/api/assets.js +519 -0
  6. package/dist/api/assets.js.map +1 -0
  7. package/dist/api/column-types.d.ts +13 -7
  8. package/dist/api/column-types.d.ts.map +1 -1
  9. package/dist/api/column-types.js +7 -3
  10. package/dist/api/column-types.js.map +1 -1
  11. package/dist/api/column-values.d.ts +8 -1
  12. package/dist/api/column-values.d.ts.map +1 -1
  13. package/dist/api/column-values.js +16 -6
  14. package/dist/api/column-values.js.map +1 -1
  15. package/dist/api/documents.d.ts +1652 -0
  16. package/dist/api/documents.d.ts.map +1 -0
  17. package/dist/api/documents.js +2411 -0
  18. package/dist/api/documents.js.map +1 -0
  19. package/dist/api/item-watch.d.ts +263 -0
  20. package/dist/api/item-watch.d.ts.map +1 -0
  21. package/dist/api/item-watch.js +709 -0
  22. package/dist/api/item-watch.js.map +1 -0
  23. package/dist/api/multipart-transport.d.ts +223 -0
  24. package/dist/api/multipart-transport.d.ts.map +1 -0
  25. package/dist/api/multipart-transport.js +274 -0
  26. package/dist/api/multipart-transport.js.map +1 -0
  27. package/dist/api/parallel-dispatch.d.ts +155 -0
  28. package/dist/api/parallel-dispatch.d.ts.map +1 -0
  29. package/dist/api/parallel-dispatch.js +243 -0
  30. package/dist/api/parallel-dispatch.js.map +1 -0
  31. package/dist/api/partial-success-bulk.d.ts +118 -60
  32. package/dist/api/partial-success-bulk.d.ts.map +1 -1
  33. package/dist/api/partial-success-bulk.js +137 -79
  34. package/dist/api/partial-success-bulk.js.map +1 -1
  35. package/dist/api/partial-success-mutation.d.ts +13 -1
  36. package/dist/api/partial-success-mutation.d.ts.map +1 -1
  37. package/dist/api/partial-success-mutation.js +5 -1
  38. package/dist/api/partial-success-mutation.js.map +1 -1
  39. package/dist/api/raw-write.d.ts +13 -4
  40. package/dist/api/raw-write.d.ts.map +1 -1
  41. package/dist/api/raw-write.js +22 -11
  42. package/dist/api/raw-write.js.map +1 -1
  43. package/dist/api/resolve-client.d.ts +11 -0
  44. package/dist/api/resolve-client.d.ts.map +1 -1
  45. package/dist/api/resolve-client.js +9 -1
  46. package/dist/api/resolve-client.js.map +1 -1
  47. package/dist/api/teams.d.ts +657 -0
  48. package/dist/api/teams.d.ts.map +1 -0
  49. package/dist/api/teams.js +880 -0
  50. package/dist/api/teams.js.map +1 -0
  51. package/dist/cli/run.d.ts +20 -0
  52. package/dist/cli/run.d.ts.map +1 -1
  53. package/dist/cli/run.js +1 -0
  54. package/dist/cli/run.js.map +1 -1
  55. package/dist/commands/board/column-create.d.ts +6 -5
  56. package/dist/commands/board/column-create.d.ts.map +1 -1
  57. package/dist/commands/board/column-create.js +9 -6
  58. package/dist/commands/board/column-create.js.map +1 -1
  59. package/dist/commands/completion.d.ts +188 -0
  60. package/dist/commands/completion.d.ts.map +1 -0
  61. package/dist/commands/completion.js +418 -0
  62. package/dist/commands/completion.js.map +1 -0
  63. package/dist/commands/doc/append-markdown.d.ts +117 -0
  64. package/dist/commands/doc/append-markdown.d.ts.map +1 -0
  65. package/dist/commands/doc/append-markdown.js +253 -0
  66. package/dist/commands/doc/append-markdown.js.map +1 -0
  67. package/dist/commands/doc/block-create.d.ts +114 -0
  68. package/dist/commands/doc/block-create.d.ts.map +1 -0
  69. package/dist/commands/doc/block-create.js +206 -0
  70. package/dist/commands/doc/block-create.js.map +1 -0
  71. package/dist/commands/doc/block-delete.d.ts +72 -0
  72. package/dist/commands/doc/block-delete.d.ts.map +1 -0
  73. package/dist/commands/doc/block-delete.js +161 -0
  74. package/dist/commands/doc/block-delete.js.map +1 -0
  75. package/dist/commands/doc/block-update.d.ts +75 -0
  76. package/dist/commands/doc/block-update.d.ts.map +1 -0
  77. package/dist/commands/doc/block-update.js +162 -0
  78. package/dist/commands/doc/block-update.js.map +1 -0
  79. package/dist/commands/doc/create-in-workspace.d.ts +76 -0
  80. package/dist/commands/doc/create-in-workspace.d.ts.map +1 -0
  81. package/dist/commands/doc/create-in-workspace.js +164 -0
  82. package/dist/commands/doc/create-in-workspace.js.map +1 -0
  83. package/dist/commands/doc/create-on-column.d.ts +71 -0
  84. package/dist/commands/doc/create-on-column.d.ts.map +1 -0
  85. package/dist/commands/doc/create-on-column.js +146 -0
  86. package/dist/commands/doc/create-on-column.js.map +1 -0
  87. package/dist/commands/doc/delete.d.ts +68 -0
  88. package/dist/commands/doc/delete.d.ts.map +1 -0
  89. package/dist/commands/doc/delete.js +146 -0
  90. package/dist/commands/doc/delete.js.map +1 -0
  91. package/dist/commands/doc/duplicate.d.ts +101 -0
  92. package/dist/commands/doc/duplicate.d.ts.map +1 -0
  93. package/dist/commands/doc/duplicate.js +191 -0
  94. package/dist/commands/doc/duplicate.js.map +1 -0
  95. package/dist/commands/doc/get.d.ts +46 -0
  96. package/dist/commands/doc/get.d.ts.map +1 -0
  97. package/dist/commands/doc/get.js +95 -0
  98. package/dist/commands/doc/get.js.map +1 -0
  99. package/dist/commands/doc/import-html.d.ts +125 -0
  100. package/dist/commands/doc/import-html.d.ts.map +1 -0
  101. package/dist/commands/doc/import-html.js +273 -0
  102. package/dist/commands/doc/import-html.js.map +1 -0
  103. package/dist/commands/doc/list.d.ts +86 -0
  104. package/dist/commands/doc/list.d.ts.map +1 -0
  105. package/dist/commands/doc/list.js +217 -0
  106. package/dist/commands/doc/list.js.map +1 -0
  107. package/dist/commands/doc/rename.d.ts +60 -0
  108. package/dist/commands/doc/rename.d.ts.map +1 -0
  109. package/dist/commands/doc/rename.js +135 -0
  110. package/dist/commands/doc/rename.js.map +1 -0
  111. package/dist/commands/index.d.ts.map +1 -1
  112. package/dist/commands/index.js +162 -0
  113. package/dist/commands/index.js.map +1 -1
  114. package/dist/commands/item/create.js +2 -2
  115. package/dist/commands/item/update.d.ts +1 -0
  116. package/dist/commands/item/update.d.ts.map +1 -1
  117. package/dist/commands/item/update.js +61 -0
  118. package/dist/commands/item/update.js.map +1 -1
  119. package/dist/commands/item/upload.d.ts +108 -0
  120. package/dist/commands/item/upload.d.ts.map +1 -0
  121. package/dist/commands/item/upload.js +370 -0
  122. package/dist/commands/item/upload.js.map +1 -0
  123. package/dist/commands/item/watch.d.ts +90 -0
  124. package/dist/commands/item/watch.d.ts.map +1 -0
  125. package/dist/commands/item/watch.js +342 -0
  126. package/dist/commands/item/watch.js.map +1 -0
  127. package/dist/commands/update/create.d.ts.map +1 -1
  128. package/dist/commands/update/create.js +6 -4
  129. package/dist/commands/update/create.js.map +1 -1
  130. package/dist/commands/update/edit.d.ts +4 -2
  131. package/dist/commands/update/edit.d.ts.map +1 -1
  132. package/dist/commands/update/edit.js +10 -6
  133. package/dist/commands/update/edit.js.map +1 -1
  134. package/dist/commands/update/reply.d.ts +4 -2
  135. package/dist/commands/update/reply.d.ts.map +1 -1
  136. package/dist/commands/update/reply.js +10 -6
  137. package/dist/commands/update/reply.js.map +1 -1
  138. package/dist/commands/update/upload.d.ts +69 -0
  139. package/dist/commands/update/upload.d.ts.map +1 -0
  140. package/dist/commands/update/upload.js +235 -0
  141. package/dist/commands/update/upload.js.map +1 -0
  142. package/dist/commands/user/_team-membership.d.ts +10 -0
  143. package/dist/commands/user/_team-membership.d.ts.map +1 -0
  144. package/dist/commands/user/_team-membership.js +88 -0
  145. package/dist/commands/user/_team-membership.js.map +1 -0
  146. package/dist/commands/user/team-add-members.d.ts +81 -0
  147. package/dist/commands/user/team-add-members.d.ts.map +1 -0
  148. package/dist/commands/user/team-add-members.js +186 -0
  149. package/dist/commands/user/team-add-members.js.map +1 -0
  150. package/dist/commands/user/team-create.d.ts +82 -0
  151. package/dist/commands/user/team-create.d.ts.map +1 -0
  152. package/dist/commands/user/team-create.js +206 -0
  153. package/dist/commands/user/team-create.js.map +1 -0
  154. package/dist/commands/user/team-delete.d.ts +56 -0
  155. package/dist/commands/user/team-delete.d.ts.map +1 -0
  156. package/dist/commands/user/team-delete.js +137 -0
  157. package/dist/commands/user/team-delete.js.map +1 -0
  158. package/dist/commands/user/team-get.d.ts +41 -0
  159. package/dist/commands/user/team-get.d.ts.map +1 -0
  160. package/dist/commands/user/team-get.js +87 -0
  161. package/dist/commands/user/team-get.js.map +1 -0
  162. package/dist/commands/user/team-list.d.ts +39 -0
  163. package/dist/commands/user/team-list.d.ts.map +1 -0
  164. package/dist/commands/user/team-list.js +90 -0
  165. package/dist/commands/user/team-list.js.map +1 -0
  166. package/dist/commands/user/team-remove-members.d.ts +71 -0
  167. package/dist/commands/user/team-remove-members.d.ts.map +1 -0
  168. package/dist/commands/user/team-remove-members.js +176 -0
  169. package/dist/commands/user/team-remove-members.js.map +1 -0
  170. package/dist/types/ids.d.ts +8 -0
  171. package/dist/types/ids.d.ts.map +1 -1
  172. package/dist/types/ids.js +53 -5
  173. package/dist/types/ids.js.map +1 -1
  174. package/dist/utils/mime.d.ts +24 -0
  175. package/dist/utils/mime.d.ts.map +1 -0
  176. package/dist/utils/mime.js +64 -0
  177. package/dist/utils/mime.js.map +1 -0
  178. package/dist/utils/output/envelope.d.ts +30 -0
  179. package/dist/utils/output/envelope.d.ts.map +1 -1
  180. package/dist/utils/output/envelope.js +26 -0
  181. package/dist/utils/output/envelope.js.map +1 -1
  182. package/dist/utils/output/ndjson.d.ts +25 -0
  183. package/dist/utils/output/ndjson.d.ts.map +1 -1
  184. package/dist/utils/output/ndjson.js +12 -0
  185. package/dist/utils/output/ndjson.js.map +1 -1
  186. package/dist/utils/parse-brand-list.d.ts +95 -0
  187. package/dist/utils/parse-brand-list.d.ts.map +1 -0
  188. package/dist/utils/parse-brand-list.js +96 -0
  189. package/dist/utils/parse-brand-list.js.map +1 -0
  190. package/dist/utils/signal.d.ts +42 -0
  191. package/dist/utils/signal.d.ts.map +1 -0
  192. package/dist/utils/signal.js +45 -0
  193. package/dist/utils/signal.js.map +1 -0
  194. package/dist/utils/source-content.d.ts +93 -0
  195. package/dist/utils/source-content.d.ts.map +1 -0
  196. package/dist/utils/source-content.js +120 -0
  197. package/dist/utils/source-content.js.map +1 -0
  198. package/package.json +1 -1
@@ -0,0 +1,2411 @@
1
+ /**
2
+ * Workdocs read + mutation surface for the v0.4-M32 `monday doc
3
+ * list/get` verbs, the v0.5-M35 doc-level CRUD verbs, the v0.5-M36
4
+ * per-block CRUD verbs, and the v0.5-M37 doc-content import verbs
5
+ * (`cli-design.md` §2.7 + §4.3 + §13 v0.4/v0.5 entries;
6
+ * `v0.4-plan.md` §3 M32; `v0.5-plan.md` §3 M35 + §8 D7-D9;
7
+ * `v0.5-plan.md` §3 M36 + §8 D10-D11; `v0.5-plan.md` §3 M37 + §8
8
+ * D12-D13).
9
+ *
10
+ * **Wire surface (empirical probe 2026-05-14, API `2026-01`).** Two
11
+ * Monday GraphQL operations land here, both against `Query.docs(...)`:
12
+ *
13
+ * - **List variant** — `Query.docs(workspace_ids: [ID],
14
+ * order_by: DocsOrderBy, limit: Int, page: Int) → [Document]`.
15
+ * Page/limit pagination (NOT cursor — Monday's workdocs surface
16
+ * has no `items_page`-style cursor). Default `limit: 25` on the
17
+ * wire side; CLI caps `--limit` at `MAX_DOC_LIST_LIMIT = 100`
18
+ * to keep response sizes bounded (each Document is rich-text
19
+ * plus metadata, and a `--limit 500` request could blow past
20
+ * Monday's complexity budget on doc-heavy accounts). `--page`
21
+ * is 1-based.
22
+ * - **Get variant** — `Query.docs(ids: [ID!]) → [Document]` with
23
+ * the per-doc `blocks` selection hydrated. Returns at most one
24
+ * Document (single-id list). The CLI extracts the singleton
25
+ * index 0; an empty array surfaces `not_found` (D8 — Monday's
26
+ * wire collapses doesn't-exist + not-accessible into the same
27
+ * shape), while a null `docs` root surfaces `internal_error`
28
+ * with a drift hint (Monday's documented shape is `[Document]`,
29
+ * possibly empty, never null — null indicates wire-shape
30
+ * regression worth surfacing loudly). The
31
+ * `Document.blocks: [DocumentBlock]` selection adds significant
32
+ * payload, which is why `doc list` ships WITHOUT `blocks` and
33
+ * `doc get` is the per-doc body-hydrating path.
34
+ *
35
+ * **`Document` object — 14 fields.** Per the M32 empirical probe:
36
+ * `id` (ID!), `object_id` (ID!), `blocks` ([DocumentBlock]; null
37
+ * unless hydrated), `created_at` (Date, nullable), `created_by`
38
+ * (User, nullable — projected to the slim `{id, name}` shape for
39
+ * envelope compactness), `doc_folder_id` (ID, nullable),
40
+ * `doc_kind` (BoardKind!, returning `'public'`/`'private'`/
41
+ * `'share'` per the DocKind probe at API `2026-01` — non-null on
42
+ * the wire; the standalone `DocKind` enum exists but isn't
43
+ * returned by `Document.doc_kind`), `name` (String!),
44
+ * `relative_url` (String, nullable),
45
+ * `settings` (JSON, nullable), `updated_at` (Date, nullable),
46
+ * `url` (String, nullable absolute URL), `workspace` (Workspace,
47
+ * nullable — projected to `{id, name}`), `workspace_id` (ID,
48
+ * nullable). The `object_id` is Monday's internal opaque object
49
+ * identifier (distinct from `id`); both flow through verbatim.
50
+ *
51
+ * **BoardKind reuse for `Document.doc_kind`.** Monday's wire schema
52
+ * types `Document.doc_kind` as `BoardKind!` (NOT `DocKind!`) — the
53
+ * `BoardKind` enum is reused across `Board.kind` and
54
+ * `Document.doc_kind`, returning the same three string values
55
+ * (`public`/`private`/`share`). The standalone `DocKind` enum exists
56
+ * on the schema but isn't returned by `Document.doc_kind`; it's a
57
+ * wire-side detail with no agent-visible asymmetry (the CLI surface
58
+ * mirrors the wire string values verbatim). Not a new R-NEW-41
59
+ * consumer — the wire-vs-CLI projection is symmetric.
60
+ *
61
+ * **`DocumentBlock` — 9 fields.** Per the M32 probe: `id` (String!),
62
+ * `type` (String, nullable — block type like `'text'` / `'heading'`
63
+ * / `'list'` / etc., values stay verbatim from Monday's wire),
64
+ * `content` (JSON, nullable — block payload), `position` (Float,
65
+ * nullable — fractional ordering within doc), `parent_block_id`
66
+ * (String, nullable — for nested blocks), `doc_id` (ID, nullable),
67
+ * `created_at` (Date, nullable), `created_by` (User, nullable —
68
+ * projected to slim `{id, name}`), `updated_at` (Date, nullable).
69
+ * Block-content schema validity is NOT cross-checked by the CLI;
70
+ * Monday's wire is the source of truth for what blocks look like.
71
+ *
72
+ * **`DocsOrderBy` enum — 2 values.** `created_at` (most-recently-
73
+ * created first; Monday's documented `desc` ordering) and `used_at`
74
+ * (most-recently-viewed-by-current-user first; also `desc`). No
75
+ * ascending variant on Monday's wire — agents that need ascending
76
+ * sort the projection client-side. Default CLI behaviour: `created_
77
+ * at` (matches Monday's wire default at API `2026-01`).
78
+ *
79
+ * **No new ERROR_CODES (29 stays — D8 closure).** Doc-read failures
80
+ * route through existing codes:
81
+ *
82
+ * - `not_found` — `doc get <did>` against a non-existent or
83
+ * inaccessible doc ID. Monday's wire returns an empty `docs`
84
+ * array for both "doesn't exist" and "exists but token can't
85
+ * read it"; the CLI surfaces both as `not_found` with
86
+ * `details.doc_id`.
87
+ * - `usage_error` — argv-parse rejections (out-of-range `--limit`
88
+ * / `--page`, malformed `--workspace`, unknown `--order-by`).
89
+ * Caught at parse boundary BEFORE any wire call.
90
+ * - `validation_failed` — Monday-side rejection (very rare for
91
+ * reads; included for completeness).
92
+ * - `forbidden` / `unauthorized` — token lacks workdoc read scope.
93
+ *
94
+ * **Docs are live-only at v0.4-M32.** Per cli-design §8 cache scope,
95
+ * workdocs aren't cached — the `doc list` + `doc get` paths emit
96
+ * `meta.source: "live"` with `cache_age_seconds: null`. Workdocs
97
+ * are content-heavy + frequently human-edited, so caching would
98
+ * regularly surface stale prose. Mirrors `monday usage` (M22) +
99
+ * `monday status` (M22) + webhook list (M27) — diagnostics /
100
+ * volatile surfaces don't cache.
101
+ *
102
+ * **Runtime bodies landed at v0.4-M32 IMPL.** `listDocuments` +
103
+ * `getDocument` each issue a single `client.raw` round-trip with
104
+ * `operationName: 'ListDocs'` / `'GetDoc'` pinned literally at the
105
+ * fetcher boundary (R-NEW-37 W2 audit-point — operationNames are
106
+ * NOT caller-overridable). Responses parse through
107
+ * {@link documentSchema} / {@link documentWithBlocksSchema} via
108
+ * `unwrapOrThrow`, so payload drift surfaces `internal_error` with
109
+ * `details.issues`. The `doc get` empty-array case rewraps to
110
+ * `not_found` with `details.doc_id` per D8 (Monday's wire collapses
111
+ * "doesn't exist" + "not visible to token" into the same shape).
112
+ *
113
+ * **v0.5-M35 mutation surface (empirical probe 2026-05-15, API
114
+ * `2026-01`; v0.5 kickoff rounds 1-3).** Five CLI verbs land here
115
+ * at v0.5-M35, backed by four Monday GraphQL mutations —
116
+ * `create_doc` (2 CLI verbs because Monday's `CreateDocInput` is
117
+ * mutually-exclusive `board` vs `workspace` per D7) +
118
+ * `update_doc_name` + `delete_doc` + `duplicate_doc`. All four
119
+ * wire mutations are synchronous on Monday's wire — the v0.4-W1
120
+ * `dispatchPollingLoop` watch-item DOES NOT fire here.
121
+ *
122
+ * - **Create-in-workspace variant** — `create_doc(location:
123
+ * CreateDocInput!) → Document` with `location: { workspace:
124
+ * CreateDocWorkspaceInput { workspace_id!, name!, kind?:
125
+ * BoardKind, folder_id? } }`. CLI envelope OMITS the wire's
126
+ * `blocks` slot entirely (the base 13-field
127
+ * {@link documentSchema} M32 pin is the create projection).
128
+ * Monday returns `blocks: null` on a fresh create — agents
129
+ * needing block hydration (rare on a fresh create — usually
130
+ * the next step is content authoring) call `monday doc get
131
+ * <new-doc-id>` per the M32 read cadence.
132
+ * - **Create-on-column variant** — `create_doc(location:
133
+ * CreateDocInput!) → Document` with `location: { board:
134
+ * CreateDocBoardInput { column_id!, item_id! } }`.
135
+ * - **Rename variant** — `update_doc_name(docId: ID!,
136
+ * name: String!) → JSON` (opaque scalar). Returns wire-side
137
+ * opaque payload; the CLI projects to flat `{ doc_id: <input>,
138
+ * success: true }` envelope at the fetcher boundary per D9.
139
+ * The agent contract is the projected envelope shape, NOT the
140
+ * opaque Monday return value.
141
+ * - **Delete variant** — `delete_doc(docId: ID!) → JSON`
142
+ * (opaque). Same `{ doc_id, success }` projection per D9.
143
+ * Destructive gate per cli-design §3.1 (M35 verb requires
144
+ * `--yes`).
145
+ * - **Duplicate variant** — `duplicate_doc(docId: ID!,
146
+ * duplicateType?: DuplicateType) → JSON` (opaque). Same
147
+ * `{ doc_id, success }` projection per D9. `--with-updates`
148
+ * argv flag flips wire `duplicateType` from
149
+ * `duplicate_doc_with_content` (default) to
150
+ * `duplicate_doc_with_content_and_updates`. **No `--name <n>`
151
+ * rename slot per D8** — Monday's `duplicate_doc` mutation
152
+ * carries no name-override arg on the wire, so the CLI defers
153
+ * the rename-on-duplicate UX (an agent that needs a renamed
154
+ * duplicate pairs the verb with a follow-up `monday doc
155
+ * rename <new-id> --name <n>` call).
156
+ *
157
+ * **`CreateDocInput` is mutually-exclusive on Monday's wire.**
158
+ * Per Finding 6 + round-3 nested-inputs probe, supplying both
159
+ * `board` AND `workspace` slots fails server-side. The CLI's
160
+ * two-verb split (`monday doc create-in-workspace` vs `monday doc
161
+ * create-on-column`) flows the mutual-exclusion into the argv
162
+ * boundary — each verb's input schema declares the slot it
163
+ * supports, and there's no way to set both via the CLI surface.
164
+ * Single verb with `--workspace` / `--board` choosers was the
165
+ * D7 alternative; pre-flight ratified the two-verb shape per the
166
+ * agent-UX principle of "fewer ambiguous flags is clearer than
167
+ * one verb with mutually-exclusive choosers" (mirrors how the
168
+ * v0.4 cli-design ships separate `monday item upload` /
169
+ * `monday update upload` verbs for the same multipart wire path).
170
+ *
171
+ * **camelCase vs snake_case asymmetry across the doc-mutation
172
+ * surface (Finding 7).** Monday's `update_doc_name` /
173
+ * `delete_doc` / `duplicate_doc` mutations use **camelCase** arg
174
+ * names (`docId`, `duplicateType`) on the wire — distinct from
175
+ * the snake_case `doc_id` Monday uses for `Document` field names
176
+ * elsewhere on the schema. The fetcher boundary uses the wire's
177
+ * camelCase variable shape verbatim; the CLI argv stays kebab-case
178
+ * throughout (`--name <n>`, `--with-updates`); the error envelope
179
+ * `details.*` keys stay snake_case per cli-design §6.5 (e.g.
180
+ * `details.doc_id`). The asymmetry is wire-side and stays at the
181
+ * fetcher boundary; agents see camelCase nowhere. Cross-link to
182
+ * `docs/architecture.md` "Wire-vs-CLI semantics documentation
183
+ * conventions" — 4th supporting site for R-NEW-41 (camelCase vs
184
+ * snake_case arg-name asymmetry; R-v0.5-NEW-3 graduation candidate
185
+ * at v0.5-plan §22).
186
+ *
187
+ * **Opaque-JSON return shape (D9 closure).** 3 of 4 M35 mutations
188
+ * return Monday's `JSON` scalar — an untyped wire payload whose
189
+ * exact shape isn't pinned by introspection. The CLI projects to
190
+ * a flat `{ doc_id: string, success: true }` envelope at the
191
+ * fetcher boundary so agents read a uniform shape across rename /
192
+ * delete / duplicate; `success` is pinned literal-`true` because
193
+ * Monday surfaces failure via GraphQL `errors[]` (mapped to typed
194
+ * `ApiError`s upstream), not via a wire-side success flag.
195
+ *
196
+ * Per-fetcher null-payload semantics ARE asymmetric (round-1 P2-1
197
+ * closure):
198
+ *
199
+ * - **`renameDoc` (`update_doc_name`)** — present-but-null
200
+ * payload → success envelope. Monday's probe description
201
+ * carries NO "returns X" prose, so null is plausibly an
202
+ * empty-success indicator.
203
+ * - **`deleteDoc` (`delete_doc`)** — present-but-null payload
204
+ * → `not_found`. Probe description: "Returns success status
205
+ * and the deleted document ID" — null indicates the source
206
+ * doc was bogus or already-deleted.
207
+ * - **`duplicateDoc` (`duplicate_doc`)** — present-but-null
208
+ * payload → `not_found`. Probe description: "Returns the new
209
+ * document's ID on success" — null indicates the source
210
+ * wasn't duplicable.
211
+ *
212
+ * Missing-root-key cases for all three uniformly throw
213
+ * `internal_error` via `assertResponseFieldPresent` (schema
214
+ * drift). For `duplicate_doc`, the projected `doc_id` is the
215
+ * **NEWLY-CREATED doc's id** extracted from the opaque JSON via
216
+ * {@link extractDuplicateDocId} — defensive across multiple
217
+ * plausible wire shapes (bare string / number / record-with-`id`
218
+ * / `doc_id` / `new_doc_id`). Capturing a live duplicate-doc wire
219
+ * response would let a future revision narrow the helper's
220
+ * accepted shapes; today's helper is constructed against the
221
+ * read-only probe + Monday's wire description, not a live wire
222
+ * response.
223
+ *
224
+ * **No new ERROR_CODES at M35.** Existing codes route doc-mutation
225
+ * failures: `not_found` (rename/delete/duplicate against a
226
+ * non-existent or inaccessible doc), `usage_error` (argv-parse
227
+ * rejections — bad DocId, missing `--name`, unknown `--kind`),
228
+ * `validation_failed` (Monday-side rejection, e.g. workspace
229
+ * lacks doc-create permission), `forbidden`/`unauthorized` (token
230
+ * lacks workdoc write scope), `confirmation_required` (destructive
231
+ * gate on `doc delete` missing `--yes`).
232
+ *
233
+ * **Doc mutations are live-only at v0.5-M35.** Pure-mutation
234
+ * surfaces don't cache by definition (cli-design §8); `meta.source:
235
+ * "live"`. Dry-run paths emit `meta.source: "none"` per §6.4
236
+ * mutation-dry-run discipline.
237
+ *
238
+ * **Runtime bodies landed at v0.5-M35 IMPL.** All five fetchers
239
+ * issue a single `client.raw` round-trip with their pinned
240
+ * `operationName`; responses parse through the wrapping schemas
241
+ * via `unwrapOrThrow` + `assertResponseFieldPresent`, then either
242
+ * unwrap the per-Document payload (create variants) or project
243
+ * the opaque JSON return to the flat `{ doc_id, success: true }`
244
+ * envelope (rename / delete / duplicate per D9). The
245
+ * `duplicate_doc` projection extracts the new doc id from the
246
+ * opaque JSON via {@link extractDuplicateDocId} — defensive
247
+ * across common wire shapes (bare string, `{id}`, `{doc_id}`,
248
+ * `{new_doc_id}`); unrecognised shapes surface `internal_error`
249
+ * with a re-probe hint.
250
+ *
251
+ * **R-NEW-76 discipline preserved** across all 5 M35 command
252
+ * action bodies — `parseArgv` fires BEFORE `resolveClient` on
253
+ * every verb so invalid argv surfaces `usage_error` from the
254
+ * parse boundary, ahead of any `config_error` from a missing
255
+ * token. `doc delete` additionally calls `parseGlobalFlags` +
256
+ * `enforceDestructiveGate` BEFORE `resolveClient` (the gate
257
+ * needs the parsed global flags to check `--yes`; M10 round-1
258
+ * P2 invariant); the other four verbs let `resolveClient` parse
259
+ * global flags internally before `loadConfig` runs. M35 verbs
260
+ * do not consume comma-separated brand-list flags
261
+ * (`parseBrandedListArg` is an M34 team-writer / M32 `doc list
262
+ * --workspace` helper); the M35
263
+ * surface doesn't need it.
264
+ */
265
+ import { z } from 'zod';
266
+ import { ApiError } from '../utils/errors.js';
267
+ import { unwrapOrThrow } from '../utils/parse-boundary.js';
268
+ import { assertResponseFieldPresent } from './response-root.js';
269
+ /**
270
+ * Schema for a required JSON-scalar slot — the key must be present
271
+ * on the parsed object, but the value can be any JSON shape Monday
272
+ * surfaces (object / array / string / number / boolean / null).
273
+ *
274
+ * Bare `z.unknown()` treats a missing key as "present with value
275
+ * `undefined`", so a wire response that omits `Document.settings`
276
+ * or `DocumentBlock.content` would still pass the strict schema —
277
+ * silently weakening the 13-field / 9-field contract. The
278
+ * `.refine` rejects `undefined` explicitly so a missing key
279
+ * surfaces as a typed parse error at the IMPL response-parse
280
+ * boundary (will fold into `internal_error` via `unwrapOrThrow`),
281
+ * matching every other field's "present-but-typed" semantics.
282
+ *
283
+ * Mirrors the M27 `Webhook.config: z.string().nullable()` pin
284
+ * (config is always present, value can be null) but for JSON-
285
+ * shaped slots whose payload shape varies per surface.
286
+ */
287
+ const requiredJsonValueSchema = z.unknown().refine((v) => v !== undefined, {
288
+ message: 'required JSON value (may be null, but the key must be present)',
289
+ });
290
+ /**
291
+ * Inclusive range for the `--limit` argv slot on `monday doc list`.
292
+ * Default `25` matches Monday's wire-side default at API `2026-01`
293
+ * (per the M32 empirical probe `Query.docs.args.limit.description`:
294
+ * "Number of items to get, the default is 25"); ceiling `100` keeps
295
+ * worst-case response sizes bounded for doc-heavy accounts (a
296
+ * `--limit 500` request would multiply payload across 500 rich-text
297
+ * Document records, easily blowing Monday's complexity budget).
298
+ */
299
+ export const MIN_DOC_LIST_LIMIT = 1;
300
+ export const MAX_DOC_LIST_LIMIT = 100;
301
+ export const DEFAULT_DOC_LIST_LIMIT = 25;
302
+ /**
303
+ * Monday's `DocsOrderBy` enum vocabulary (empirical probe 2026-05-14,
304
+ * API `2026-01`; 2 values). Pinned at M32 pre-flight as a closed
305
+ * literal-union enum so unknown `--order-by` values reject at parse
306
+ * boundary with `usage_error`.
307
+ *
308
+ * Both values sort `desc` on Monday's wire (most-recent first); no
309
+ * ascending variant is exposed. Adding a third value to Monday's
310
+ * enum is a minor (additive) bump for the CLI — extend this list +
311
+ * the per-command flag help.
312
+ */
313
+ export const DOCS_ORDER_BY = ['created_at', 'used_at'];
314
+ export const docsOrderBySchema = z.enum(DOCS_ORDER_BY);
315
+ export const DEFAULT_DOCS_ORDER_BY = 'created_at';
316
+ /**
317
+ * Slim projection of Monday's `User` for the `Document.created_by` +
318
+ * `DocumentBlock.created_by` slots. Mirrors the M19 `account_tags`
319
+ * + M31 `Asset.uploaded_by` slim-User cadence: `{id, name}` only,
320
+ * keeping envelope size bounded. Full-User reads route through
321
+ * `monday user get <uid>`.
322
+ */
323
+ export const docUserSchema = z
324
+ .object({
325
+ id: z.string().min(1),
326
+ name: z.string().min(1),
327
+ })
328
+ .strict();
329
+ /**
330
+ * Slim projection of Monday's `Workspace` for the `Document.workspace`
331
+ * slot. Same `{id, name}` shape as {@link docUserSchema}. Full-
332
+ * Workspace reads route through `monday workspace get <wid>`.
333
+ */
334
+ export const docWorkspaceSchema = z
335
+ .object({
336
+ id: z.string().min(1),
337
+ name: z.string().min(1),
338
+ })
339
+ .strict();
340
+ /**
341
+ * `doc_kind` literal-union enum (3 values per the M32 probe + the
342
+ * DocKind introspection result). Monday's wire types this field as
343
+ * `BoardKind!` — see the module header's "BoardKind reuse" note for
344
+ * the wire-side type-name aliasing. The CLI surface mirrors the
345
+ * three string values verbatim with no projection drift.
346
+ */
347
+ export const DOC_KIND_VALUES = ['public', 'private', 'share'];
348
+ export const docKindSchema = z.enum(DOC_KIND_VALUES);
349
+ /**
350
+ * DocumentBlock projection — Monday's 9-field block shape per the
351
+ * M32 probe. `content` is `JSON` on the wire (block payload — schema
352
+ * varies per `type`); the CLI passes it through unmodified as
353
+ * `unknown` so agents introspect the per-block-type payload
354
+ * themselves (Monday's wire is the source of truth for the per-
355
+ * block-type schema).
356
+ *
357
+ * Only surfaces under `doc get` envelopes (the per-doc body-hydrating
358
+ * path); `doc list` envelopes ship Documents WITHOUT `blocks` per
359
+ * the D6 list-row-projection closure.
360
+ */
361
+ export const documentBlockSchema = z
362
+ .object({
363
+ id: z.string().min(1),
364
+ type: z.string().nullable(),
365
+ content: requiredJsonValueSchema,
366
+ position: z.number().nullable(),
367
+ parent_block_id: z.string().nullable(),
368
+ doc_id: z.string().nullable(),
369
+ created_at: z.string().nullable(),
370
+ created_by: docUserSchema.nullable(),
371
+ updated_at: z.string().nullable(),
372
+ })
373
+ .strict();
374
+ /**
375
+ * Base Document projection (13 fields — all of Monday's 14 minus
376
+ * `blocks`). Used as the list-row shape under `doc list` envelopes;
377
+ * appended with the required `blocks: [DocumentBlock]` slot for
378
+ * `doc get` envelopes via {@link documentWithBlocksSchema} (the
379
+ * extension makes `blocks` mandatory — `doc get` always hydrates
380
+ * the rich-text body per D6).
381
+ *
382
+ * `settings` is `JSON` on the wire (per-doc display/sharing config)
383
+ * — passed through as `unknown` for the same reason
384
+ * `DocumentBlock.content` is. Agents that need a specific settings
385
+ * key destructure client-side.
386
+ */
387
+ export const documentSchema = z
388
+ .object({
389
+ id: z.string().min(1),
390
+ object_id: z.string().min(1),
391
+ name: z.string().min(1),
392
+ doc_kind: docKindSchema,
393
+ url: z.string().nullable(),
394
+ relative_url: z.string().nullable(),
395
+ workspace_id: z.string().nullable(),
396
+ workspace: docWorkspaceSchema.nullable(),
397
+ doc_folder_id: z.string().nullable(),
398
+ created_at: z.string().nullable(),
399
+ created_by: docUserSchema.nullable(),
400
+ updated_at: z.string().nullable(),
401
+ settings: requiredJsonValueSchema,
402
+ })
403
+ .strict();
404
+ /**
405
+ * `doc get` projection — base Document + the `blocks` slot hydrated.
406
+ * Same shape as {@link documentSchema} with `blocks:
407
+ * [DocumentBlock]` appended (Monday's wire returns the block array
408
+ * directly under `Document.blocks`; the CLI surfaces it verbatim).
409
+ *
410
+ * `blocks` is non-null on the wire when the selection is requested;
411
+ * an empty doc surfaces `blocks: []` rather than `null`. The schema
412
+ * pins non-null for envelope predictability — an unexpected null
413
+ * surfaces `internal_error` at the IMPL parse boundary.
414
+ */
415
+ export const documentWithBlocksSchema = documentSchema
416
+ .extend({
417
+ blocks: z.array(documentBlockSchema),
418
+ })
419
+ .strict();
420
+ /**
421
+ * Output shape for `monday doc list [--workspace <wid>,...]
422
+ * [--order-by <created_at|used_at>] [--limit <n>] [--page <n>]`.
423
+ * Wrapped record (NOT bare array) because page/limit pagination
424
+ * surfaces pagination context inline rather than via `meta.cursor`
425
+ * (cli-design §6.1 cursor slot is for items_page-style surfaces;
426
+ * the workdocs wire has no cursor).
427
+ *
428
+ * `documents` — Monday's wire-ordered array (server-applied
429
+ * `created_at desc` or `used_at desc` per `--order-by`).
430
+ * `page` / `limit` — echoed inputs confirming what the wire saw.
431
+ * `returned_count` — `documents.length` cached for agent ergonomics.
432
+ * `has_more` — heuristic `returned_count === limit`; Monday's wire
433
+ * doesn't surface a total count, so "exactly `limit` rows returned"
434
+ * is the only signal that a follow-up page exists. Agents
435
+ * pessimistically re-fetch with `page + 1` if `has_more: true`.
436
+ */
437
+ export const docListOutputSchema = z
438
+ .object({
439
+ documents: z.array(documentSchema),
440
+ page: z.number().int().min(1),
441
+ limit: z
442
+ .number()
443
+ .int()
444
+ .min(MIN_DOC_LIST_LIMIT)
445
+ .max(MAX_DOC_LIST_LIMIT),
446
+ returned_count: z.number().int().min(0),
447
+ has_more: z.boolean(),
448
+ })
449
+ .strict()
450
+ // Pagination-invariant cross-field check (round-2 P2-1 fix +
451
+ // round-3 P2-1 guard). The schema-level field types/ranges don't
452
+ // enforce the documented invariants between `returned_count` /
453
+ // `documents.length` / `limit` / `has_more` — an IMPL bug could
454
+ // emit inconsistent pagination data and still pass output
455
+ // validation, then bleed agent-visible drift into the envelope.
456
+ // Pin the two invariants here so the unwrap-or-throw boundary
457
+ // catches violations at parse time:
458
+ //
459
+ // 1. `returned_count === documents.length` — the count field is
460
+ // the cached array length, not an independent counter.
461
+ // 2. `has_more === (returned_count === limit)` — Monday's wire
462
+ // surface doesn't expose a total-count, so "exactly `limit`
463
+ // rows returned" is the only signal that a follow-up page
464
+ // may exist (D9 closure).
465
+ //
466
+ // **Round-3 P2-1 fix — early-return guard.** Zod's `.superRefine`
467
+ // still runs even when scalar range checks above have produced
468
+ // "dirty" issues. Without this guard, a malformed input like
469
+ // `{ limit: 0, returned_count: 0, has_more: false }` would emit
470
+ // BOTH the legitimate `limit` range-floor violation AND a
471
+ // misleading `has_more` invariant violation (because `has_more
472
+ // === (returned_count === limit)` evaluates against the invalid
473
+ // limit value). Short-circuit when participating scalars are
474
+ // out-of-range so the user sees only the underlying range
475
+ // violation, not a derived inconsistency error stacked on top.
476
+ .superRefine((value, ctx) => {
477
+ if (value.limit < MIN_DOC_LIST_LIMIT ||
478
+ value.limit > MAX_DOC_LIST_LIMIT ||
479
+ value.returned_count < 0 ||
480
+ !Number.isInteger(value.limit) ||
481
+ !Number.isInteger(value.returned_count)) {
482
+ return;
483
+ }
484
+ if (value.returned_count !== value.documents.length) {
485
+ ctx.addIssue({
486
+ code: 'custom',
487
+ path: ['returned_count'],
488
+ message: `returned_count (${String(value.returned_count)}) must equal ` +
489
+ `documents.length (${String(value.documents.length)})`,
490
+ });
491
+ }
492
+ const expectedHasMore = value.returned_count === value.limit;
493
+ if (value.has_more !== expectedHasMore) {
494
+ ctx.addIssue({
495
+ code: 'custom',
496
+ path: ['has_more'],
497
+ message: `has_more (${String(value.has_more)}) must equal ` +
498
+ `(returned_count === limit) which is ${String(expectedHasMore)}`,
499
+ });
500
+ }
501
+ });
502
+ /**
503
+ * Output shape for `monday doc get <did>`. Direct unwrap of the
504
+ * single Document (with blocks hydrated) — matches the convention
505
+ * for read-one verbs (`monday board get <bid>` returns `data:
506
+ * <Board>`, `monday user get <uid>` returns `data: <User>`).
507
+ *
508
+ * The Document's own `id` field is the echoed input — no separate
509
+ * `doc_id` slot needed.
510
+ */
511
+ export const docGetOutputSchema = documentWithBlocksSchema;
512
+ /**
513
+ * GraphQL query document for `Query.docs(...)` listing variant.
514
+ * Operation name pinned literally to `ListDocs` and matches the
515
+ * wire `operationName` payload (R-NEW-37 W2 audit-point — caller-
516
+ * overridable operationName slots were closed at M27 IMPL round-1
517
+ * P2-1; M32 maintains the safely-by-construction shape).
518
+ *
519
+ * Selects every list-row field (no `blocks` selection — list rows
520
+ * project the 13-field base shape per D6). `workspace_ids:` typed
521
+ * as `[ID]` to mirror Monday's wire signature (the inner ID is
522
+ * nullable on Monday's side — an empirical-probe finding that
523
+ * doesn't show up in the contract today but stays preserved for
524
+ * future-proofing).
525
+ */
526
+ export const LIST_DOCS_QUERY = `
527
+ query ListDocs(
528
+ $workspaceIds: [ID],
529
+ $orderBy: DocsOrderBy,
530
+ $limit: Int,
531
+ $page: Int
532
+ ) {
533
+ docs(
534
+ workspace_ids: $workspaceIds,
535
+ order_by: $orderBy,
536
+ limit: $limit,
537
+ page: $page
538
+ ) {
539
+ id
540
+ object_id
541
+ name
542
+ doc_kind
543
+ url
544
+ relative_url
545
+ workspace_id
546
+ workspace { id name }
547
+ doc_folder_id
548
+ created_at
549
+ created_by { id name }
550
+ updated_at
551
+ settings
552
+ }
553
+ }
554
+ `;
555
+ /**
556
+ * GraphQL query document for `Query.docs(ids:)` single-id read
557
+ * variant. Operation name pinned to `GetDoc` (R-NEW-37 W2). Selects
558
+ * all base Document fields plus the `blocks` selection (the per-doc
559
+ * body-hydrating leg).
560
+ *
561
+ * Single-id wire shape — Monday returns `[Document]` (an array even
562
+ * for one id); the fetcher extracts index 0. An empty array
563
+ * (Monday's response for "doc not found" or "doc not visible to
564
+ * token") surfaces `not_found` with `details.doc_id`.
565
+ */
566
+ export const GET_DOC_QUERY = `
567
+ query GetDoc($ids: [ID!]!) {
568
+ docs(ids: $ids) {
569
+ id
570
+ object_id
571
+ name
572
+ doc_kind
573
+ url
574
+ relative_url
575
+ workspace_id
576
+ workspace { id name }
577
+ doc_folder_id
578
+ created_at
579
+ created_by { id name }
580
+ updated_at
581
+ settings
582
+ blocks {
583
+ id
584
+ type
585
+ content
586
+ position
587
+ parent_block_id
588
+ doc_id
589
+ created_at
590
+ created_by { id name }
591
+ updated_at
592
+ }
593
+ }
594
+ }
595
+ `;
596
+ /**
597
+ * Wrapping response schema for the `ListDocs` operation. Monday's
598
+ * documented wire shape is `[Document]` (an array, possibly empty)
599
+ * — never null. The wrapper accepts `null` defensively so a wire-
600
+ * shape regression parses cleanly and is rewrapped by the fetcher
601
+ * as `internal_error` with a drift hint (rather than faulting the
602
+ * parse with a confusing zod issue path). Both fetchers reject a
603
+ * null root post-parse; null is NEVER a `not_found` rewrap target
604
+ * — that's the empty-array D8 case in `getDocument`.
605
+ *
606
+ * `.loose()` mirrors the M27 `listWebhooksResponseSchema` cadence —
607
+ * Monday occasionally returns side-band debug keys (`extensions`,
608
+ * `account_id`) alongside the documented data root; the loose
609
+ * mode lets them pass without faulting the parse.
610
+ */
611
+ const listDocsResponseSchema = z
612
+ .object({
613
+ docs: z.array(documentSchema).nullable(),
614
+ })
615
+ .loose();
616
+ /**
617
+ * Wrapping response schema for the `GetDoc` operation. Same shape
618
+ * as the list variant but with `blocks` hydrated on every entry.
619
+ */
620
+ const getDocResponseSchema = z
621
+ .object({
622
+ docs: z.array(documentWithBlocksSchema).nullable(),
623
+ })
624
+ .loose();
625
+ /**
626
+ * Fetches the workdocs visible to the token via a single
627
+ * `Query.docs(...)` round-trip with `operationName: 'ListDocs'`
628
+ * (R-NEW-37 W2). Source is always `'live'` per cli-design §8 cache
629
+ * scope; workdocs aren't cached at v0.4 per D7.
630
+ *
631
+ * Variables map to Monday's wire `Query.docs(...)` args:
632
+ * `workspaceIds` → `workspace_ids: [ID]`; `orderBy` →
633
+ * `order_by: DocsOrderBy`; `limit` → `limit: Int`; `page` →
634
+ * `page: Int`. Omitted inputs drop the corresponding `$variable`
635
+ * so Monday's per-arg server-side default applies (rather than
636
+ * threading an explicit `null` that the wire treats as "field
637
+ * present").
638
+ *
639
+ * Echoed `page` / `limit` carry Monday's defaults when the caller
640
+ * omits them ({@link DEFAULT_DOC_LIST_LIMIT} for limit, `1` for
641
+ * page) so the envelope's pagination-invariant `.superRefine`
642
+ * sees consistent values regardless of which inputs the verb
643
+ * received.
644
+ *
645
+ * A null `docs` root surfaces `internal_error` (Monday's documented
646
+ * shape is `[Document]` even for empty accounts — the array, never
647
+ * null at this layer). Schema drift in the per-doc shape rewraps to
648
+ * `internal_error` with `details.issues` via `unwrapOrThrow`.
649
+ */
650
+ export const listDocuments = async (inputs) => {
651
+ const variables = {};
652
+ if (inputs.workspaceIds !== undefined) {
653
+ variables.workspaceIds = inputs.workspaceIds;
654
+ }
655
+ if (inputs.orderBy !== undefined) {
656
+ variables.orderBy = inputs.orderBy;
657
+ }
658
+ if (inputs.limit !== undefined) {
659
+ variables.limit = inputs.limit;
660
+ }
661
+ if (inputs.page !== undefined) {
662
+ variables.page = inputs.page;
663
+ }
664
+ const response = await inputs.client.raw(LIST_DOCS_QUERY, variables, { operationName: 'ListDocs' });
665
+ const parsed = unwrapOrThrow(listDocsResponseSchema.safeParse(response.data), {
666
+ context: 'Monday `Query.docs(ListDocs)` response',
667
+ hint: 'Monday may have amended the `Document` selection — re-probe and amend `src/api/documents.ts` if so',
668
+ });
669
+ if (parsed.docs === null) {
670
+ throw new ApiError('internal_error', 'Monday returned a null `docs` payload from ListDocs', {
671
+ details: {
672
+ hint: 'Monday\'s documented shape is `[Document]` (an array, possibly empty) — ' +
673
+ 'a null root indicates a wire change that needs re-probing',
674
+ },
675
+ });
676
+ }
677
+ return {
678
+ documents: parsed.docs,
679
+ page: inputs.page ?? 1,
680
+ limit: inputs.limit ?? DEFAULT_DOC_LIST_LIMIT,
681
+ source: 'live',
682
+ cacheAgeSeconds: null,
683
+ complexity: response.complexity,
684
+ };
685
+ };
686
+ /**
687
+ * Fetches a single workdoc by ID via a single `Query.docs(ids:)`
688
+ * round-trip with `operationName: 'GetDoc'` (R-NEW-37 W2). Returns
689
+ * the Document with `blocks` hydrated. Source is always `'live'`
690
+ * per cli-design §8 cache scope.
691
+ *
692
+ * Empty wire response (Monday's shape for "doc doesn't exist" OR
693
+ * "doc not visible to token") surfaces `not_found` with
694
+ * `details.doc_id` — no `forbidden` rewrap, because Monday's wire
695
+ * collapses the two cases into one shape and the CLI can't
696
+ * distinguish them per D8.
697
+ *
698
+ * A multi-element wire response (which Monday's `docs(ids:)`
699
+ * shouldn't return for a single-id query) surfaces `internal_error`
700
+ * as a defensive guard — the CLI assumes one doc per id and a
701
+ * count mismatch indicates a wire-shape regression worth surfacing
702
+ * loudly rather than silently dropping entries.
703
+ */
704
+ export const getDocument = async (inputs) => {
705
+ const response = await inputs.client.raw(GET_DOC_QUERY, { ids: [inputs.docId] }, { operationName: 'GetDoc' });
706
+ const parsed = unwrapOrThrow(getDocResponseSchema.safeParse(response.data), {
707
+ context: 'Monday `Query.docs(GetDoc)` response',
708
+ details: { doc_id: inputs.docId },
709
+ hint: 'Monday may have amended the `Document` / `DocumentBlock` selection — re-probe and amend `src/api/documents.ts` if so',
710
+ });
711
+ if (parsed.docs === null) {
712
+ throw new ApiError('internal_error', `Monday returned a null \`docs\` payload from GetDoc(${inputs.docId})`, {
713
+ details: {
714
+ doc_id: inputs.docId,
715
+ hint: 'Monday\'s documented shape is `[Document]` (an array, possibly empty) — ' +
716
+ 'a null root indicates a wire change that needs re-probing',
717
+ },
718
+ });
719
+ }
720
+ if (parsed.docs.length === 0) {
721
+ throw new ApiError('not_found', `workdoc ${inputs.docId} not found (does not exist or not visible to token)`, { details: { doc_id: inputs.docId } });
722
+ }
723
+ if (parsed.docs.length > 1) {
724
+ throw new ApiError('internal_error', `Monday returned ${String(parsed.docs.length)} docs for a single-id GetDoc query`, {
725
+ details: {
726
+ doc_id: inputs.docId,
727
+ hint: 'wire shape regression — re-probe `Query.docs(ids:)`',
728
+ },
729
+ });
730
+ }
731
+ const [document] = parsed.docs;
732
+ /* c8 ignore next 6 */
733
+ if (document === undefined) {
734
+ throw new ApiError('internal_error', `Monday returned a sparse docs array for GetDoc(${inputs.docId})`, { details: { doc_id: inputs.docId } });
735
+ }
736
+ return {
737
+ document,
738
+ source: 'live',
739
+ cacheAgeSeconds: null,
740
+ complexity: response.complexity,
741
+ };
742
+ };
743
+ // ===========================================================================
744
+ // v0.5-M35 doc-level CRUD mutation surface
745
+ // ===========================================================================
746
+ // All five fetchers below landed runtime bodies at v0.5-M35 IMPL. Each
747
+ // issues a single `client.raw` round-trip with its pinned `operationName`,
748
+ // parses via the wrapping response schema + `assertResponseFieldPresent`,
749
+ // and either unwraps the Document payload (create variants) or projects
750
+ // the opaque JSON return into the flat `{ doc_id, success: true }`
751
+ // envelope per D9 (rename / delete / duplicate).
752
+ /**
753
+ * Monday's `DuplicateType` enum vocabulary (empirical probe 2026-05-15,
754
+ * API `2026-01`; 2 values). Pinned at M35 pre-flight as a closed
755
+ * literal-union so unknown values reject at the parse boundary with
756
+ * `usage_error`.
757
+ *
758
+ * - `duplicate_doc_with_content` — clone the doc body only; comments
759
+ * and update history are NOT copied. Wire-side default when
760
+ * `duplicateType` is omitted.
761
+ * - `duplicate_doc_with_content_and_updates` — clone the doc body
762
+ * AND every comment / update thread attached to the source doc.
763
+ * Picks up Monday's "full backup" duplicate semantics.
764
+ *
765
+ * Maps to the CLI's boolean `--with-updates` flag at the M35
766
+ * `monday doc duplicate` argv boundary: absent → wire default
767
+ * (`duplicate_doc_with_content`, content-only); present → wire
768
+ * `duplicate_doc_with_content_and_updates`. The two-value enum
769
+ * stays internal to the fetcher — agents see a boolean opt-in, not
770
+ * the wire enum name.
771
+ *
772
+ * Adding a third value to Monday's enum is a minor (additive) bump
773
+ * for the CLI — extend this list + the fetcher mapping; the
774
+ * boolean argv stays.
775
+ */
776
+ export const DUPLICATE_TYPE_VALUES = [
777
+ 'duplicate_doc_with_content',
778
+ 'duplicate_doc_with_content_and_updates',
779
+ ];
780
+ export const duplicateTypeSchema = z.enum(DUPLICATE_TYPE_VALUES);
781
+ /**
782
+ * Projected envelope shape for the three opaque-JSON mutation
783
+ * results (`update_doc_name` / `delete_doc` / `duplicate_doc`).
784
+ * Per D9 closure, the CLI surfaces a flat `{ doc_id: string,
785
+ * success: true }` envelope so agents read a uniform shape across
786
+ * mutations — Monday's wire returns an opaque `JSON` scalar whose
787
+ * exact shape isn't pinned by introspection (the v0.5 doc-CRUD
788
+ * probe was read-only — no live mutation response was captured).
789
+ * The projection insulates agents from any wire-side shape drift;
790
+ * the runtime accepts plausible-defensive shapes today, and a
791
+ * future live wire response would narrow what the helper accepts.
792
+ *
793
+ * `success` is pinned to literal `true` because Monday surfaces
794
+ * failure via GraphQL `errors[]` (mapped to typed `ApiError`s at
795
+ * the transport layer); a JSON return that reaches this projection
796
+ * is by construction the success path. If Monday ever flips to a
797
+ * `success/error` result OBJECT shape for these mutations (mirroring
798
+ * `import_doc_from_html` / `add_content_to_doc_from_markdown` per
799
+ * Finding 6), the projection widens to a discriminated union — for
800
+ * today the literal-`true` form keeps the shape pinned.
801
+ *
802
+ * `doc_id` semantics differ per mutation:
803
+ *
804
+ * - `rename` / `delete` — echoes the input id (the operation
805
+ * targets that specific doc; success means it was renamed /
806
+ * deleted).
807
+ * - `duplicate` — emits the **NEWLY-CREATED** doc's id (Monday's
808
+ * `duplicate_doc` description: "Returns the new document's ID
809
+ * on success"; the fetcher extracts the new id from the
810
+ * opaque JSON via {@link extractDuplicateDocId}, which is
811
+ * defensive across plausible wire shapes today). The original
812
+ * source-doc id stays available via the argv positional.
813
+ */
814
+ export const docMutationResultSchema = z
815
+ .object({
816
+ doc_id: z.string().min(1),
817
+ success: z.literal(true),
818
+ })
819
+ .strict();
820
+ /**
821
+ * Output shape for `monday doc create-in-workspace --workspace
822
+ * <wid> --name <n> [--folder <fid>] [--kind public|private|share]`.
823
+ * Direct unwrap of the created Document — `data: <Document>` per
824
+ * cli-design §6.1 single-record convention. The envelope OMITS
825
+ * the `blocks` slot entirely (the base {@link documentSchema} M32
826
+ * pin is the 13-field projection without `blocks`).
827
+ *
828
+ * **Rationale for the omit.** Monday's wire returns
829
+ * `blocks: null` on a fresh `create_doc` because a freshly-
830
+ * created doc has no rich-text body yet (Monday hasn't
831
+ * materialised it). Surfacing a constant-null `blocks` slot on
832
+ * every create envelope would add agent-noise — every caller
833
+ * has to ignore it. Agents that need block hydration (rare on a
834
+ * fresh create — usually the next step is content authoring)
835
+ * call `monday doc get <new-doc-id>` per the M32 read cadence.
836
+ * The omit is a contract decision, not a schema-flexibility
837
+ * artifact.
838
+ */
839
+ export const docCreateInWorkspaceOutputSchema = documentSchema;
840
+ /**
841
+ * Output shape for `monday doc create-on-column --item <iid>
842
+ * --column <cid>`. Same shape as
843
+ * {@link docCreateInWorkspaceOutputSchema} — Monday's wire returns
844
+ * the full Document regardless of placement; the two-verb split
845
+ * (per D7) lives at the argv layer, not the response shape.
846
+ */
847
+ export const docCreateOnColumnOutputSchema = documentSchema;
848
+ /**
849
+ * Output shape for `monday doc rename <doc-id> --name <n>`.
850
+ * Projected from Monday's opaque `JSON` scalar return per D9 — the
851
+ * fetcher emits `{ doc_id: <echoed>, success: true }`. Agents key
852
+ * off `ok: true` for retry/idempotency reasoning; the literal
853
+ * `success: true` slot exists for envelope-snapshot stability and
854
+ * future-proofing (if Monday ever surfaces a wire-side success
855
+ * flag, the schema widens to read the wire value rather than
856
+ * always emitting `true`).
857
+ */
858
+ export const docRenameOutputSchema = docMutationResultSchema;
859
+ /**
860
+ * Output shape for `monday doc delete <doc-id> --yes`. Same
861
+ * `{ doc_id: <echoed>, success: true }` projection as rename per
862
+ * D9. The destructive-gate envelope sits at the action body —
863
+ * `confirmation_required` fires BEFORE this output schema is
864
+ * referenced (cli-design §3.1 #6).
865
+ */
866
+ export const docDeleteOutputSchema = docMutationResultSchema;
867
+ /**
868
+ * Output shape for `monday doc duplicate <doc-id> [--with-updates]`.
869
+ * Same flat `{ doc_id, success: true }` projection per D9, BUT the
870
+ * `doc_id` slot carries the **newly-created duplicate's id** — NOT
871
+ * the source-doc id. The verb's positional argv is the source-doc
872
+ * id; the wire returns the new id in its JSON payload, which
873
+ * {@link extractDuplicateDocId} pulls out (defensive across
874
+ * plausible shapes today; a future live wire response would
875
+ * narrow what the helper accepts).
876
+ *
877
+ * **No `--name <n>` slot per D8** — Monday's `duplicate_doc`
878
+ * mutation carries no rename-on-duplicate arg. Agents needing a
879
+ * renamed duplicate pair with a follow-up `monday doc rename
880
+ * <new-id> --name <n>` call.
881
+ */
882
+ export const docDuplicateOutputSchema = docMutationResultSchema;
883
+ /**
884
+ * GraphQL mutation document for `create_doc(location: {workspace:
885
+ * ...})`. Operation name pinned to `CreateDocInWorkspace`
886
+ * (R-NEW-37 W2 audit-point — operationNames NOT caller-overridable).
887
+ * Selects every base-Document field per the M32 cadence; `blocks`
888
+ * deliberately omitted (Monday's wire returns `blocks: null` on a
889
+ * fresh create — see the module header's create-variant note).
890
+ *
891
+ * `$input: CreateDocInput!` carries the mutually-exclusive wire
892
+ * shape; the verb's action body composes
893
+ * `{ workspace: { workspace_id, name, folder_id?, kind? } }` from
894
+ * the parsed argv. The `board` slot stays unset at the wire — the
895
+ * two-verb CLI split (per D7) makes the mutual-exclusion safely-
896
+ * by-construction.
897
+ */
898
+ export const CREATE_DOC_IN_WORKSPACE_MUTATION = `
899
+ mutation CreateDocInWorkspace($input: CreateDocInput!) {
900
+ create_doc(location: $input) {
901
+ id
902
+ object_id
903
+ name
904
+ doc_kind
905
+ url
906
+ relative_url
907
+ workspace_id
908
+ workspace { id name }
909
+ doc_folder_id
910
+ created_at
911
+ created_by { id name }
912
+ updated_at
913
+ settings
914
+ }
915
+ }
916
+ `;
917
+ /**
918
+ * GraphQL mutation document for `create_doc(location: {board: ...})`.
919
+ * Operation name pinned to `CreateDocOnColumn` (R-NEW-37 W2). Same
920
+ * Document selection as {@link CREATE_DOC_IN_WORKSPACE_MUTATION};
921
+ * `$input: CreateDocInput!` is composed as `{ board: { column_id,
922
+ * item_id } }` at the action body. Distinct named operation per
923
+ * verb so the wire-side `operationName` payload mirrors the CLI
924
+ * verb name verbatim (agents tracing wire traffic can identify the
925
+ * verb without parsing the doc body).
926
+ */
927
+ export const CREATE_DOC_ON_COLUMN_MUTATION = `
928
+ mutation CreateDocOnColumn($input: CreateDocInput!) {
929
+ create_doc(location: $input) {
930
+ id
931
+ object_id
932
+ name
933
+ doc_kind
934
+ url
935
+ relative_url
936
+ workspace_id
937
+ workspace { id name }
938
+ doc_folder_id
939
+ created_at
940
+ created_by { id name }
941
+ updated_at
942
+ settings
943
+ }
944
+ }
945
+ `;
946
+ /**
947
+ * GraphQL mutation document for `update_doc_name(docId, name) →
948
+ * JSON`. Operation name pinned to `UpdateDocName` (R-NEW-37 W2).
949
+ * The wire return type is the opaque `JSON` scalar — no selection
950
+ * set per GraphQL grammar (scalars are leaves). The CLI projects
951
+ * the opaque value to the flat `{ doc_id, success }` envelope at
952
+ * the fetcher boundary per D9.
953
+ *
954
+ * `docId` + `name` are camelCase on Monday's wire per Finding 7;
955
+ * the variable names mirror the wire shape verbatim. CLI argv is
956
+ * `<doc-id>` positional + `--name <n>` flag (kebab-case throughout).
957
+ */
958
+ export const UPDATE_DOC_NAME_MUTATION = `
959
+ mutation UpdateDocName($docId: ID!, $name: String!) {
960
+ update_doc_name(docId: $docId, name: $name)
961
+ }
962
+ `;
963
+ /**
964
+ * GraphQL mutation document for `delete_doc(docId) → JSON`.
965
+ * Operation name pinned to `DeleteDoc` (R-NEW-37 W2). Wire return
966
+ * is opaque JSON — same projection cadence as
967
+ * {@link UPDATE_DOC_NAME_MUTATION}.
968
+ *
969
+ * **Destructive-gate ordering.** The verb's action body MUST call
970
+ * `enforceDestructiveGate` BEFORE this fetcher per the M10 round-1
971
+ * P2 invariant. A missing `--yes` surfaces as
972
+ * `confirmation_required` from the action layer, never masked by
973
+ * `config_error` when no token is configured.
974
+ */
975
+ export const DELETE_DOC_MUTATION = `
976
+ mutation DeleteDoc($docId: ID!) {
977
+ delete_doc(docId: $docId)
978
+ }
979
+ `;
980
+ /**
981
+ * GraphQL mutation document for `duplicate_doc(docId,
982
+ * duplicateType?) → JSON`. Operation name pinned to `DuplicateDoc`
983
+ * (R-NEW-37 W2). `duplicateType` is the optional 2-value enum
984
+ * (see {@link DUPLICATE_TYPE_VALUES}); when omitted at the variable
985
+ * boundary, Monday's wire-side default (`duplicate_doc_with_content`,
986
+ * content-only) applies.
987
+ *
988
+ * The verb's `--with-updates` boolean argv maps to:
989
+ * absent → omit the variable entirely (wire default applies);
990
+ * present → `duplicateType: 'duplicate_doc_with_content_and_updates'`.
991
+ * The omit-vs-null discipline mirrors M34 `team-create`'s
992
+ * `is_guest_team` handling — Monday treats a `null` variable as
993
+ * "field present with null value" rather than "field omitted".
994
+ *
995
+ * Wire return is opaque JSON — same projection cadence as the
996
+ * rename + delete mutations.
997
+ */
998
+ export const DUPLICATE_DOC_MUTATION = `
999
+ mutation DuplicateDoc($docId: ID!, $duplicateType: DuplicateType) {
1000
+ duplicate_doc(docId: $docId, duplicateType: $duplicateType)
1001
+ }
1002
+ `;
1003
+ /**
1004
+ * Wrapping response schema for the `CreateDocInWorkspace` /
1005
+ * `CreateDocOnColumn` mutations. Monday's wire shape returns the
1006
+ * full Document on success; a `null` payload surfaces
1007
+ * `internal_error` (a successful `create_doc` must return the
1008
+ * created Document per Monday's documented contract — null
1009
+ * indicates a wire-shape regression worth surfacing loudly rather
1010
+ * than silently dropping the response).
1011
+ *
1012
+ * Shared between both create-variants because the wire returns
1013
+ * the same Document shape regardless of placement; the schema's
1014
+ * `.loose()` mode mirrors M27 / M32 / M34 — Monday occasionally
1015
+ * surfaces side-band debug keys (`extensions`, `account_id`)
1016
+ * alongside the documented data root, and the loose mode lets
1017
+ * them pass without faulting the parse.
1018
+ *
1019
+ * The `create_doc` slot widens to `unknown` so the wrapping parse
1020
+ * tolerates Monday's side-band keys and any rare `null` payload;
1021
+ * the post-parse layer pins the value against {@link documentSchema}
1022
+ * via `unwrapOrThrow` so per-field drift surfaces with structured
1023
+ * `details.issues` (mirrors M34 createTeam's two-stage parse).
1024
+ */
1025
+ const createDocResponseSchema = z
1026
+ .object({
1027
+ create_doc: z.unknown(),
1028
+ })
1029
+ .loose();
1030
+ /**
1031
+ * Wrapping response schema for the `UpdateDocName` mutation. The
1032
+ * `update_doc_name` root carries Monday's opaque `JSON` scalar —
1033
+ * `z.unknown()` accepts any wire shape (null / record / scalar)
1034
+ * and the projection layer extracts what it needs. Missing key
1035
+ * surfaces `internal_error` via `assertResponseFieldPresent`.
1036
+ */
1037
+ const updateDocNameResponseSchema = z
1038
+ .object({
1039
+ update_doc_name: z.unknown(),
1040
+ })
1041
+ .loose();
1042
+ /**
1043
+ * Wrapping response schema for the `DeleteDoc` mutation. Same
1044
+ * shape as {@link updateDocNameResponseSchema} — opaque JSON
1045
+ * scalar at the root; projection layer handles the extraction.
1046
+ * A `null` value surfaces `not_found` (mirrors M14 workspace-
1047
+ * delete + M34 team-delete cadence — id bogus / already deleted
1048
+ * by a concurrent caller).
1049
+ */
1050
+ const deleteDocResponseSchema = z
1051
+ .object({
1052
+ delete_doc: z.unknown(),
1053
+ })
1054
+ .loose();
1055
+ /**
1056
+ * Wrapping response schema for the `DuplicateDoc` mutation. Same
1057
+ * opaque-JSON shape as the rename/delete variants. Monday's wire
1058
+ * carries the newly-created doc id in this opaque JSON payload
1059
+ * (probe description: "Returns the new document's ID on
1060
+ * success"); the exact shape isn't pinned by introspection and
1061
+ * the v0.5 doc-CRUD probe was read-only. The fetcher extracts
1062
+ * the new id via {@link extractDuplicateDocId}, which accepts
1063
+ * multiple plausible shapes defensively (bare string / number /
1064
+ * record-with-`id` / `doc_id` / `new_doc_id`). A future live
1065
+ * wire response would let a follow-up commit narrow what the
1066
+ * helper accepts to the exact wire shape.
1067
+ */
1068
+ const duplicateDocResponseSchema = z
1069
+ .object({
1070
+ duplicate_doc: z.unknown(),
1071
+ })
1072
+ .loose();
1073
+ /**
1074
+ * Creates a workspace-scoped workdoc via `create_doc(location:
1075
+ * { workspace: {...} })` with `operationName:
1076
+ * 'CreateDocInWorkspace'` (R-NEW-37 W2). Returns the created
1077
+ * Document with `id` populated post-create.
1078
+ *
1079
+ * The fetcher composes `location: { workspace: { workspace_id,
1080
+ * name, folder_id?, kind? } }` from the inputs; `folderId` /
1081
+ * `kind` are omitted entirely from the wire payload when unset
1082
+ * (Monday's per-arg server-side defaults apply — null would be
1083
+ * treated as "field present" rather than "field omitted",
1084
+ * mirroring M34 `createTeam`'s discipline).
1085
+ *
1086
+ * A missing-key wire response surfaces `internal_error` via
1087
+ * `assertResponseFieldPresent`; a null payload surfaces
1088
+ * `internal_error` too — a successful `create_doc` mutation must
1089
+ * return the created Document per Monday's documented contract.
1090
+ */
1091
+ export const createDocInWorkspace = async (inputs) => {
1092
+ const workspaceInput = {
1093
+ workspace_id: inputs.workspaceId,
1094
+ name: inputs.name,
1095
+ };
1096
+ if (inputs.folderId !== undefined) {
1097
+ workspaceInput.folder_id = inputs.folderId;
1098
+ }
1099
+ if (inputs.kind !== undefined) {
1100
+ workspaceInput.kind = inputs.kind;
1101
+ }
1102
+ const response = await inputs.client.raw(CREATE_DOC_IN_WORKSPACE_MUTATION, { input: { workspace: workspaceInput } }, { operationName: 'CreateDocInWorkspace' });
1103
+ const data = unwrapOrThrow(createDocResponseSchema.safeParse(response.data), {
1104
+ context: 'Monday returned a malformed CreateDocInWorkspace response',
1105
+ details: { workspace_id: inputs.workspaceId, name: inputs.name },
1106
+ hint: 'this is a data-integrity error in Monday\'s response; verify ' +
1107
+ 'the response shape and update `createDocResponseSchema` if ' +
1108
+ 'Monday\'s contract has changed.',
1109
+ });
1110
+ assertResponseFieldPresent({
1111
+ data,
1112
+ key: 'create_doc',
1113
+ operationLabel: 'CreateDocInWorkspace',
1114
+ details: { workspace_id: inputs.workspaceId, name: inputs.name },
1115
+ nullHandling: 'caller_handles',
1116
+ });
1117
+ const rawDoc = data.create_doc;
1118
+ if (rawDoc === null || rawDoc === undefined) {
1119
+ throw new ApiError('internal_error', `Monday returned no document payload from create_doc(location: {workspace: ${inputs.workspaceId}}, name: ${JSON.stringify(inputs.name)}).`, { details: { workspace_id: inputs.workspaceId, name: inputs.name } });
1120
+ }
1121
+ const document = unwrapOrThrow(documentSchema.safeParse(rawDoc), {
1122
+ context: `Monday returned a malformed Document payload from create_doc (workspace ${inputs.workspaceId})`,
1123
+ details: { workspace_id: inputs.workspaceId, name: inputs.name },
1124
+ });
1125
+ return {
1126
+ document,
1127
+ source: 'live',
1128
+ cacheAgeSeconds: null,
1129
+ complexity: response.complexity,
1130
+ };
1131
+ };
1132
+ /**
1133
+ * Creates an item-scoped workdoc via `create_doc(location:
1134
+ * { board: {...} })` with `operationName: 'CreateDocOnColumn'`
1135
+ * (R-NEW-37 W2). Returns the created Document; the doc is
1136
+ * embedded into the named column of the named item.
1137
+ *
1138
+ * The fetcher composes `location: { board: { column_id, item_id
1139
+ * } }` from the inputs. Both slots are required on Monday's wire
1140
+ * (`CreateDocBoardInput.column_id!` + `CreateDocBoardInput.
1141
+ * item_id!`); the CLI verb's `--item <iid>` + `--column <cid>`
1142
+ * flags are both required at the parse boundary.
1143
+ *
1144
+ * Failure modes mirror the workspace variant: missing-key
1145
+ * response → `internal_error`; null payload → `internal_error`;
1146
+ * column not configured for docs → `validation_failed` (Monday-
1147
+ * side rejection — the CLI doesn't pre-check column-type
1148
+ * compatibility, mirroring M8's `change_column_value` cadence).
1149
+ */
1150
+ export const createDocOnColumn = async (inputs) => {
1151
+ const response = await inputs.client.raw(CREATE_DOC_ON_COLUMN_MUTATION, {
1152
+ input: {
1153
+ board: { item_id: inputs.itemId, column_id: inputs.columnId },
1154
+ },
1155
+ }, { operationName: 'CreateDocOnColumn' });
1156
+ const data = unwrapOrThrow(createDocResponseSchema.safeParse(response.data), {
1157
+ context: 'Monday returned a malformed CreateDocOnColumn response',
1158
+ details: { item_id: inputs.itemId, column_id: inputs.columnId },
1159
+ hint: 'this is a data-integrity error in Monday\'s response; verify ' +
1160
+ 'the response shape and update `createDocResponseSchema` if ' +
1161
+ 'Monday\'s contract has changed.',
1162
+ });
1163
+ assertResponseFieldPresent({
1164
+ data,
1165
+ key: 'create_doc',
1166
+ operationLabel: 'CreateDocOnColumn',
1167
+ details: { item_id: inputs.itemId, column_id: inputs.columnId },
1168
+ nullHandling: 'caller_handles',
1169
+ });
1170
+ const rawDoc = data.create_doc;
1171
+ if (rawDoc === null || rawDoc === undefined) {
1172
+ throw new ApiError('internal_error', `Monday returned no document payload from create_doc(location: {board: {item_id: ${inputs.itemId}, column_id: ${inputs.columnId}}}).`, { details: { item_id: inputs.itemId, column_id: inputs.columnId } });
1173
+ }
1174
+ const document = unwrapOrThrow(documentSchema.safeParse(rawDoc), {
1175
+ context: `Monday returned a malformed Document payload from create_doc (item ${inputs.itemId}, column ${inputs.columnId})`,
1176
+ details: { item_id: inputs.itemId, column_id: inputs.columnId },
1177
+ });
1178
+ return {
1179
+ document,
1180
+ source: 'live',
1181
+ cacheAgeSeconds: null,
1182
+ complexity: response.complexity,
1183
+ };
1184
+ };
1185
+ /**
1186
+ * Renames a workdoc via `update_doc_name(docId, name)` with
1187
+ * `operationName: 'UpdateDocName'` (R-NEW-37 W2). Projects
1188
+ * Monday's opaque JSON return into the flat
1189
+ * `{ doc_id: <echoed>, success: true }` envelope per D9.
1190
+ *
1191
+ * Failure modes:
1192
+ *
1193
+ * - **Missing `update_doc_name` key** → `internal_error` via
1194
+ * `assertResponseFieldPresent` (schema drift — Monday's
1195
+ * response shape regressed).
1196
+ * - **Present-but-null payload** → projected as success (the
1197
+ * `{doc_id, success: true}` envelope). Monday's probe
1198
+ * description for `update_doc_name` carries NO "returns X"
1199
+ * prose (unlike `delete_doc` which promises "success status
1200
+ * and the deleted document ID"), so a null wire payload is a
1201
+ * plausible empty-success indicator. If Monday returns a
1202
+ * typed error for non-existent doc IDs, that bubbles via
1203
+ * GraphQL `errors[]` (mapped to typed `ApiError`s upstream) —
1204
+ * a present-but-null JSON return reaching this projection is
1205
+ * by construction the success path. Distinct from
1206
+ * `deleteDoc` + `duplicateDoc` cadence which DO treat null
1207
+ * as `not_found` because their probe descriptions explicitly
1208
+ * promise a non-null return payload on success.
1209
+ * - **Typed Monday errors** (forbidden / not_found from a wire-
1210
+ * side `errors[]`) → bubble through the transport's error-
1211
+ * mapping layer.
1212
+ */
1213
+ export const renameDoc = async (inputs) => {
1214
+ const response = await inputs.client.raw(UPDATE_DOC_NAME_MUTATION, { docId: inputs.docId, name: inputs.name }, { operationName: 'UpdateDocName' });
1215
+ const data = unwrapOrThrow(updateDocNameResponseSchema.safeParse(response.data), {
1216
+ context: 'Monday returned a malformed UpdateDocName response',
1217
+ details: { doc_id: inputs.docId },
1218
+ hint: 'this is a data-integrity error in Monday\'s response; verify ' +
1219
+ 'the response shape and update `updateDocNameResponseSchema` ' +
1220
+ 'if Monday\'s contract has changed.',
1221
+ });
1222
+ assertResponseFieldPresent({
1223
+ data,
1224
+ key: 'update_doc_name',
1225
+ operationLabel: 'UpdateDocName',
1226
+ details: { doc_id: inputs.docId },
1227
+ nullHandling: 'caller_handles',
1228
+ });
1229
+ // Project Monday's opaque JSON return per D9 — agents see a
1230
+ // uniform `{ doc_id, success: true }` envelope regardless of
1231
+ // what's inside `data.update_doc_name`. Unlike `delete_doc` +
1232
+ // `duplicate_doc`, a present `null` is NOT remapped to
1233
+ // `not_found` (round-1 P2-1 closure — Monday's
1234
+ // `update_doc_name` probe description makes no return-shape
1235
+ // promise, so null is a plausible empty-success indicator).
1236
+ return {
1237
+ result: { doc_id: inputs.docId, success: true },
1238
+ source: 'live',
1239
+ cacheAgeSeconds: null,
1240
+ complexity: response.complexity,
1241
+ };
1242
+ };
1243
+ /**
1244
+ * Deletes a workdoc by ID via `delete_doc(docId)` with
1245
+ * `operationName: 'DeleteDoc'` (R-NEW-37 W2). Projects Monday's
1246
+ * opaque JSON return into the flat `{ doc_id: <echoed>,
1247
+ * success: true }` envelope per D9.
1248
+ *
1249
+ * A null `delete_doc` payload surfaces `not_found` — same
1250
+ * cadence as M34 team-delete + M14 workspace-delete. A missing
1251
+ * key surfaces `internal_error`.
1252
+ *
1253
+ * **Destructive-gate ordering.** The verb's action body MUST
1254
+ * call `enforceDestructiveGate` BEFORE this fetcher per the
1255
+ * M10 round-1 P2 invariant. A missing `--yes` surfaces as
1256
+ * `confirmation_required` from the action layer, never masked
1257
+ * by `config_error` when no token is configured.
1258
+ */
1259
+ export const deleteDoc = async (inputs) => {
1260
+ const response = await inputs.client.raw(DELETE_DOC_MUTATION, { docId: inputs.docId }, { operationName: 'DeleteDoc' });
1261
+ const data = unwrapOrThrow(deleteDocResponseSchema.safeParse(response.data), {
1262
+ context: 'Monday returned a malformed DeleteDoc response',
1263
+ details: { doc_id: inputs.docId },
1264
+ hint: 'this is a data-integrity error in Monday\'s response; verify ' +
1265
+ 'the response shape and update `deleteDocResponseSchema` if ' +
1266
+ 'Monday\'s contract has changed.',
1267
+ });
1268
+ assertResponseFieldPresent({
1269
+ data,
1270
+ key: 'delete_doc',
1271
+ operationLabel: 'DeleteDoc',
1272
+ details: { doc_id: inputs.docId },
1273
+ nullHandling: 'caller_handles',
1274
+ });
1275
+ const rawPayload = data.delete_doc;
1276
+ if (rawPayload === null || rawPayload === undefined) {
1277
+ throw new ApiError('not_found', `Monday returned no payload from delete_doc for doc ${inputs.docId}`, { details: { doc_id: inputs.docId } });
1278
+ }
1279
+ return {
1280
+ result: { doc_id: inputs.docId, success: true },
1281
+ source: 'live',
1282
+ cacheAgeSeconds: null,
1283
+ complexity: response.complexity,
1284
+ };
1285
+ };
1286
+ /**
1287
+ * Best-effort extractor for the new doc's id from
1288
+ * `duplicate_doc`'s opaque JSON return. Monday's wire description
1289
+ * says "Returns the new document's ID on success" but doesn't
1290
+ * pin the wire shape; the schema introspection types the return
1291
+ * as the opaque `JSON` scalar.
1292
+ *
1293
+ * Tries common shapes:
1294
+ *
1295
+ * - Bare string → that's the new id.
1296
+ * - Numeric scalar → stringify (Monday's wire mixes ID + Int
1297
+ * types; the CLI's brand stays string-shaped).
1298
+ * - Record with `id` / `doc_id` / `new_doc_id` field carrying a
1299
+ * non-empty string or number → extract.
1300
+ *
1301
+ * Anything else surfaces `internal_error` with a hint pointing
1302
+ * at re-probing `duplicate_doc` live so a future commit can
1303
+ * narrow the helper to Monday's actual wire shape.
1304
+ */
1305
+ const extractDuplicateDocId = (raw, sourceId) => {
1306
+ if (typeof raw === 'string' && raw.length > 0)
1307
+ return raw;
1308
+ if (typeof raw === 'number' && Number.isFinite(raw))
1309
+ return String(raw);
1310
+ if (typeof raw === 'object' && raw !== null && !Array.isArray(raw)) {
1311
+ const rec = raw;
1312
+ for (const key of ['id', 'doc_id', 'new_doc_id']) {
1313
+ const value = rec[key];
1314
+ if (typeof value === 'string' && value.length > 0)
1315
+ return value;
1316
+ if (typeof value === 'number' && Number.isFinite(value)) {
1317
+ return String(value);
1318
+ }
1319
+ }
1320
+ }
1321
+ throw new ApiError('internal_error', `duplicate_doc returned an opaque JSON payload the CLI could not extract a new doc id from (source ${sourceId})`, {
1322
+ details: {
1323
+ doc_id: sourceId,
1324
+ hint: 'Monday\'s `duplicate_doc` wire shape changed — re-probe via ' +
1325
+ '`scripts/probe/` and extend `extractDuplicateDocId` to cover ' +
1326
+ 'the new shape.',
1327
+ },
1328
+ });
1329
+ };
1330
+ /**
1331
+ * Duplicates a workdoc by ID via `duplicate_doc(docId,
1332
+ * duplicateType?)` with `operationName: 'DuplicateDoc'`
1333
+ * (R-NEW-37 W2). Projects Monday's opaque JSON return into the
1334
+ * flat `{ doc_id: <NEW>, success: true }` envelope per D9 — the
1335
+ * `doc_id` slot carries the **newly-created duplicate's id**,
1336
+ * NOT the input source-doc id (the source id stays accessible
1337
+ * via the verb's positional argv).
1338
+ *
1339
+ * The fetcher omits the `duplicateType` variable entirely when
1340
+ * `inputs.duplicateType` is unset so Monday's wire-side default
1341
+ * applies (the M34 `team-create` omit-vs-null discipline). A
1342
+ * null `duplicate_doc` payload surfaces `not_found` (source
1343
+ * doc id bogus or inaccessible); missing key surfaces
1344
+ * `internal_error`. The new-id extraction tolerates several
1345
+ * common shapes Monday's opaque JSON might carry (bare string,
1346
+ * `{id}`, `{doc_id}`, `{new_doc_id}`) — anything else surfaces
1347
+ * `internal_error` with a re-probe hint.
1348
+ */
1349
+ export const duplicateDoc = async (inputs) => {
1350
+ const variables = { docId: inputs.docId };
1351
+ if (inputs.duplicateType !== undefined) {
1352
+ variables.duplicateType = inputs.duplicateType;
1353
+ }
1354
+ const response = await inputs.client.raw(DUPLICATE_DOC_MUTATION, variables, { operationName: 'DuplicateDoc' });
1355
+ const data = unwrapOrThrow(duplicateDocResponseSchema.safeParse(response.data), {
1356
+ context: 'Monday returned a malformed DuplicateDoc response',
1357
+ details: { doc_id: inputs.docId },
1358
+ hint: 'this is a data-integrity error in Monday\'s response; verify ' +
1359
+ 'the response shape and update `duplicateDocResponseSchema` ' +
1360
+ 'if Monday\'s contract has changed.',
1361
+ });
1362
+ assertResponseFieldPresent({
1363
+ data,
1364
+ key: 'duplicate_doc',
1365
+ operationLabel: 'DuplicateDoc',
1366
+ details: { doc_id: inputs.docId },
1367
+ nullHandling: 'caller_handles',
1368
+ });
1369
+ const rawPayload = data.duplicate_doc;
1370
+ if (rawPayload === null || rawPayload === undefined) {
1371
+ throw new ApiError('not_found', `Monday returned no payload from duplicate_doc for source doc ${inputs.docId}`, { details: { doc_id: inputs.docId } });
1372
+ }
1373
+ const newDocId = extractDuplicateDocId(rawPayload, inputs.docId);
1374
+ return {
1375
+ result: { doc_id: newDocId, success: true },
1376
+ source: 'live',
1377
+ cacheAgeSeconds: null,
1378
+ complexity: response.complexity,
1379
+ };
1380
+ };
1381
+ // ===========================================================================
1382
+ // v0.5-M36 doc-block CRUD mutation surface
1383
+ // ===========================================================================
1384
+ // Three fetchers land the per-block CRUD wire surface against Monday's
1385
+ // `create_doc_block` / `update_doc_block` / `delete_doc_block` mutations.
1386
+ // Each issues a single `client.raw` round-trip with a literal-pinned
1387
+ // `operationName` (R-NEW-37 W2 safely-by-construction), parses the
1388
+ // wrapping response shape via a loose `z.object({...}).loose()` schema,
1389
+ // asserts the field presence via `assertResponseFieldPresent`, applies
1390
+ // the per-fetcher null-payload contract (per R-v0.5-NEW-11 — see
1391
+ // per-fetcher JSDoc), and unwraps the inner OBJECT via the shared
1392
+ // {@link documentBlockSchema} (create + update) or the new
1393
+ // {@link documentBlockIdOnlySchema} (delete). Each command action body
1394
+ // runs `parseArgv` (+ `parseJsonArg` for `--content` consumers +
1395
+ // `enforceDestructiveGate` on `block-delete --yes`) BEFORE
1396
+ // `resolveClient` so invalid argv surfaces `usage_error` from the parse
1397
+ // boundary (R-NEW-76 graduated discipline).
1398
+ //
1399
+ // Wire summary (empirical probe at `scripts/probe/v0.5-doc-mutations.ts` +
1400
+ // `v0.5-inputs-and-results.ts`, 2026-05-15, API `2026-01`):
1401
+ //
1402
+ // - `create_doc_block(doc_id: ID!, type: DocBlockContentType!,
1403
+ // content: JSON!, after_block_id: String, parent_block_id: String) →
1404
+ // DocumentBlock` (NON_NULL/OBJECT). Operation name `CreateDocBlock`
1405
+ // pinned literally per R-NEW-37 W2.
1406
+ // - `update_doc_block(block_id: String!, content: JSON!) →
1407
+ // DocumentBlock` (NON_NULL/OBJECT). Operation name `UpdateDocBlock`.
1408
+ // - `delete_doc_block(block_id: String!) → DocumentBlockIdOnly`
1409
+ // (NON_NULL/OBJECT with a single `id: String!` field). Operation
1410
+ // name `DeleteDocBlock`.
1411
+ //
1412
+ // **Snake_case wire arg names (Finding 7).** M36's three wire mutations
1413
+ // use SNAKE_CASE arg names (`doc_id`, `block_id`, `after_block_id`,
1414
+ // `parent_block_id`) — back to Monday's standard cadence after the
1415
+ // M35 camelCase asymmetry (`docId` / `duplicateType` on `update_doc_name`
1416
+ // / `delete_doc` / `duplicate_doc`). The GraphQL operation documents
1417
+ // below use camelCase variable names + snake_case wire arg names
1418
+ // (`doc_id: $docId`), matching the `Query.docs(workspace_ids:
1419
+ // $workspaceIds)` M32 cadence. Not a new R-NEW-41 supporting site —
1420
+ // M36 is the symmetric path; M35 was the asymmetric one.
1421
+ //
1422
+ // **OBJECT-return cadence (distinct from M35 opaque-JSON).** Unlike
1423
+ // M35's `update_doc_name` / `delete_doc` / `duplicate_doc` (3 of 4
1424
+ // returned the opaque `JSON` scalar driving the {@link extractDuplicateDocId}
1425
+ // helper + the `{doc_id, success: true}` projection per D9), all three
1426
+ // M36 wire mutations return typed OBJECT shapes. `create_doc_block` +
1427
+ // `update_doc_block` both return the full `DocumentBlock` (9 fields per
1428
+ // the M32 probe — id / type / content / position / parent_block_id /
1429
+ // doc_id / created_at / created_by / updated_at); the fetchers parse via
1430
+ // {@link documentBlockSchema} (reused from M32's `doc get` blocks-
1431
+ // hydration leg) and unwrap. `delete_doc_block` returns `DocumentBlockIdOnly`
1432
+ // — a single `{id: String!}` field — and the fetcher parses via the
1433
+ // new {@link documentBlockIdOnlySchema}. No opaque-JSON projection
1434
+ // helper needed; the OBJECT-return shape is pinned by schema.
1435
+ //
1436
+ // **Per-block `content` shape (D11 closure).** Wire `content: JSON!` —
1437
+ // an opaque per-`DocBlockContentType` shape varying across the 16 enum
1438
+ // values. The CLI accepts user-supplied `--content <json>` strings
1439
+ // unmodified (parsed once at the argv boundary via `parseJsonArg`;
1440
+ // R-NEW-42's 4th + 5th consumers) and passes the resulting JS value to
1441
+ // Monday's wire `JSON` scalar verbatim. Per-block-type content payload
1442
+ // shapes (one per `DocBlockContentType` value) are documented at
1443
+ // `docs/output-shapes.md` "Per-block content shapes" reference table —
1444
+ // cassette-sourced rows for the variants M36 IMPL exercises live, plus
1445
+ // TBD / inferred rows pending follow-up cassettes. Monday's wire stays
1446
+ // the source of truth, and agents that pass a shape-incompatible
1447
+ // `--content` for the chosen `--type` surface `validation_failed` from
1448
+ // Monday at the live path.
1449
+ //
1450
+ // **R-NEW-76 discipline preserved across all three M36 action bodies.**
1451
+ // `parseArgv` (+ `parseJsonArg` for `--content` consumers, +
1452
+ // `enforceDestructiveGate` on `block-delete --yes`) fires BEFORE
1453
+ // `resolveClient` so invalid argv surfaces `usage_error` from the parse
1454
+ // boundary, NOT `internal_error` from a later wire-call leg.
1455
+ /**
1456
+ * Monday's `DocBlockContentType` enum vocabulary (empirical probe
1457
+ * 2026-05-15, API `2026-01`; 16 closed values per Finding 8). Pinned
1458
+ * at M36 pre-flight as a closed literal-union enum so unknown
1459
+ * `--type` values reject at the argv-parse boundary with
1460
+ * `usage_error.details.issues[]` (D10 closure).
1461
+ *
1462
+ * Adding a 17th value to Monday's enum is a minor (additive) bump
1463
+ * for the CLI — extend this list + the per-command flag help; the
1464
+ * per-block content schema documentation in `docs/output-shapes.md`
1465
+ * "Per-block content shapes" reference table gains a new row for
1466
+ * the new variant once a live cassette pins its shape (extending
1467
+ * the table is additive at any future v0.5.x patch).
1468
+ */
1469
+ export const DOC_BLOCK_CONTENT_TYPE_VALUES = [
1470
+ 'bulleted_list',
1471
+ 'check_list',
1472
+ 'code',
1473
+ 'divider',
1474
+ 'image',
1475
+ 'large_title',
1476
+ 'layout',
1477
+ 'medium_title',
1478
+ 'normal_text',
1479
+ 'notice_box',
1480
+ 'numbered_list',
1481
+ 'page_break',
1482
+ 'quote',
1483
+ 'small_title',
1484
+ 'table',
1485
+ 'video',
1486
+ ];
1487
+ export const docBlockContentTypeSchema = z.enum(DOC_BLOCK_CONTENT_TYPE_VALUES);
1488
+ /**
1489
+ * `DocumentBlockIdOnly` projection — Monday's `delete_doc_block`
1490
+ * mutation return shape per the v0.5 empirical probe. Single-field
1491
+ * OBJECT carrying only the deleted block's id; the wire description
1492
+ * is "A monday.com doc block" but introspection pins exactly one
1493
+ * field. Mirrors `DocumentBlock.id`'s `String!` shape (non-empty,
1494
+ * opaque).
1495
+ */
1496
+ export const documentBlockIdOnlySchema = z
1497
+ .object({
1498
+ id: z.string().min(1),
1499
+ })
1500
+ .strict();
1501
+ /**
1502
+ * Output shape for `monday doc block-create <doc-id> --type <t>
1503
+ * --content <json> [--after <bid>] [--parent <bid>]`. Direct unwrap
1504
+ * of the created DocumentBlock — `data: <DocumentBlock>` per cli-
1505
+ * design §6.1 single-record convention. The 9-field `DocumentBlock`
1506
+ * schema reused from {@link documentBlockSchema} (M32's `doc get`
1507
+ * blocks-hydration leg).
1508
+ */
1509
+ export const docBlockCreateOutputSchema = documentBlockSchema;
1510
+ /**
1511
+ * Output shape for `monday doc block-update <block-id> --content
1512
+ * <json>`. Same 9-field `DocumentBlock` shape as block-create —
1513
+ * Monday's wire returns the updated block with content / type /
1514
+ * position / parent_block_id all reflecting post-update state.
1515
+ */
1516
+ export const docBlockUpdateOutputSchema = documentBlockSchema;
1517
+ /**
1518
+ * Output shape for `monday doc block-delete <block-id> --yes`.
1519
+ * Single-field `{id: <echoed-block-id>}` projection from Monday's
1520
+ * `DocumentBlockIdOnly` wire return — agents key off the echoed id
1521
+ * for retry/idempotency reasoning. Envelope is intentionally
1522
+ * narrower than the create/update variants because Monday's
1523
+ * `delete_doc_block` wire only returns the id (NOT the full
1524
+ * pre-delete block); the agent contract doesn't gain from
1525
+ * speculatively rehydrating the block on the way out.
1526
+ */
1527
+ export const docBlockDeleteOutputSchema = documentBlockIdOnlySchema;
1528
+ /**
1529
+ * GraphQL mutation document for `create_doc_block(doc_id, type,
1530
+ * content, after_block_id?, parent_block_id?) → DocumentBlock`.
1531
+ * Operation name pinned literally to `CreateDocBlock` (R-NEW-37
1532
+ * W2 audit-point — operationNames NOT caller-overridable). Wire
1533
+ * args are snake_case (Finding 7); variable names are camelCase
1534
+ * (TS convention).
1535
+ *
1536
+ * Selects every `DocumentBlock` field per the M32 probe. The 9-
1537
+ * field selection matches the `doc get` blocks-hydration leg so a
1538
+ * future create-then-get pipeline can compare envelopes byte-for-
1539
+ * byte.
1540
+ */
1541
+ export const CREATE_DOC_BLOCK_MUTATION = `
1542
+ mutation CreateDocBlock(
1543
+ $docId: ID!,
1544
+ $type: DocBlockContentType!,
1545
+ $content: JSON!,
1546
+ $afterBlockId: String,
1547
+ $parentBlockId: String
1548
+ ) {
1549
+ create_doc_block(
1550
+ doc_id: $docId,
1551
+ type: $type,
1552
+ content: $content,
1553
+ after_block_id: $afterBlockId,
1554
+ parent_block_id: $parentBlockId
1555
+ ) {
1556
+ id
1557
+ type
1558
+ content
1559
+ position
1560
+ parent_block_id
1561
+ doc_id
1562
+ created_at
1563
+ created_by { id name }
1564
+ updated_at
1565
+ }
1566
+ }
1567
+ `;
1568
+ /**
1569
+ * GraphQL mutation document for `update_doc_block(block_id,
1570
+ * content) → DocumentBlock`. Operation name pinned to
1571
+ * `UpdateDocBlock` (R-NEW-37 W2). Same 9-field `DocumentBlock`
1572
+ * selection as the create variant — Monday's wire returns the
1573
+ * full post-update block, not just a delta.
1574
+ *
1575
+ * No `--type` slot on `update_doc_block` (Monday's wire signature
1576
+ * has no `type` arg); agents that need to switch a block's content
1577
+ * type must `block-delete` + `block-create` (lossy: new id, new
1578
+ * position). The CLI surface mirrors the wire constraint exactly
1579
+ * — no client-side "change type" shim that papers over the
1580
+ * destructive recreate.
1581
+ */
1582
+ export const UPDATE_DOC_BLOCK_MUTATION = `
1583
+ mutation UpdateDocBlock($blockId: String!, $content: JSON!) {
1584
+ update_doc_block(block_id: $blockId, content: $content) {
1585
+ id
1586
+ type
1587
+ content
1588
+ position
1589
+ parent_block_id
1590
+ doc_id
1591
+ created_at
1592
+ created_by { id name }
1593
+ updated_at
1594
+ }
1595
+ }
1596
+ `;
1597
+ /**
1598
+ * GraphQL mutation document for `delete_doc_block(block_id) →
1599
+ * DocumentBlockIdOnly`. Operation name pinned to `DeleteDocBlock`
1600
+ * (R-NEW-37 W2). Single-field selection — Monday's wire return is
1601
+ * `DocumentBlockIdOnly` (`{id: String!}`).
1602
+ *
1603
+ * **Destructive-gate ordering.** The verb's action body MUST call
1604
+ * `enforceDestructiveGate` BEFORE this fetcher per the M10 round-1
1605
+ * P2 invariant. A missing `--yes` surfaces as
1606
+ * `confirmation_required` from the action layer, never masked by
1607
+ * `config_error` when no token is configured.
1608
+ */
1609
+ export const DELETE_DOC_BLOCK_MUTATION = `
1610
+ mutation DeleteDocBlock($blockId: String!) {
1611
+ delete_doc_block(block_id: $blockId) {
1612
+ id
1613
+ }
1614
+ }
1615
+ `;
1616
+ /**
1617
+ * Wrapping response schema for the `CreateDocBlock` mutation. The
1618
+ * `create_doc_block` slot widens to `unknown` so the wrapping
1619
+ * parse tolerates Monday's side-band debug keys (the `.loose()`
1620
+ * mode mirrors M27 / M32 / M34 / M35); the post-parse layer pins
1621
+ * the value against {@link documentBlockSchema} via
1622
+ * `unwrapOrThrow` so per-field drift surfaces with structured
1623
+ * `details.issues` (two-stage parse, mirrors M34 / M35 cadence).
1624
+ * Null payload semantics handled at the fetcher boundary (IMPL).
1625
+ */
1626
+ const createDocBlockResponseSchema = z
1627
+ .object({
1628
+ create_doc_block: z.unknown(),
1629
+ })
1630
+ .loose();
1631
+ /**
1632
+ * Wrapping response schema for the `UpdateDocBlock` mutation. Same
1633
+ * shape as {@link createDocBlockResponseSchema} — Monday's wire
1634
+ * returns the updated `DocumentBlock` under the
1635
+ * `update_doc_block` root.
1636
+ */
1637
+ const updateDocBlockResponseSchema = z
1638
+ .object({
1639
+ update_doc_block: z.unknown(),
1640
+ })
1641
+ .loose();
1642
+ /**
1643
+ * Wrapping response schema for the `DeleteDocBlock` mutation. The
1644
+ * `delete_doc_block` slot returns `DocumentBlockIdOnly` (`{id:
1645
+ * String!}`); the post-parse layer pins via
1646
+ * {@link documentBlockIdOnlySchema}. Null payload semantics
1647
+ * (treat as `not_found` per the standard delete cadence mirroring
1648
+ * M14 workspace-delete + M34 team-delete + M35 doc-delete) handled
1649
+ * at the fetcher boundary (IMPL).
1650
+ */
1651
+ const deleteDocBlockResponseSchema = z
1652
+ .object({
1653
+ delete_doc_block: z.unknown(),
1654
+ })
1655
+ .loose();
1656
+ /**
1657
+ * Creates a per-doc rich-text block via `create_doc_block(doc_id,
1658
+ * type, content, after_block_id?, parent_block_id?)` with
1659
+ * `operationName: 'CreateDocBlock'` (R-NEW-37 W2). Two-stage parse
1660
+ * mirrors M34 / M35 cadence: the loose wrapping schema tolerates
1661
+ * Monday's side-band debug keys, then the inner OBJECT pins via
1662
+ * {@link documentBlockSchema} so per-field drift surfaces with
1663
+ * structured `details.issues`.
1664
+ *
1665
+ * Failure modes:
1666
+ *
1667
+ * - **Missing `create_doc_block` key** → `internal_error` via
1668
+ * `assertResponseFieldPresent` (Monday's response shape
1669
+ * regressed).
1670
+ * - **Present-but-null payload** → `internal_error` (a successful
1671
+ * `create_doc_block` mutation must return the created block per
1672
+ * Monday's documented contract — null indicates a wire-shape
1673
+ * regression worth surfacing loudly; distinct from
1674
+ * {@link updateDocBlock} / {@link deleteDocBlock} which treat
1675
+ * null as `not_found` per their probe-description-derived
1676
+ * contracts at R-v0.5-NEW-11).
1677
+ * - **Inner OBJECT drift** → `internal_error` via `unwrapOrThrow`
1678
+ * on {@link documentBlockSchema} (per-field shape regression).
1679
+ *
1680
+ * Omits `afterBlockId` / `parentBlockId` variables when unset so
1681
+ * Monday's wire-side defaults apply (the M34 / M35 omit-vs-null
1682
+ * discipline — undefined-keyed JSON serialises to a missing key,
1683
+ * NOT `null`).
1684
+ */
1685
+ export const createDocBlock = async (inputs) => {
1686
+ const variables = {
1687
+ docId: inputs.docId,
1688
+ type: inputs.type,
1689
+ content: inputs.content,
1690
+ };
1691
+ if (inputs.afterBlockId !== undefined) {
1692
+ variables.afterBlockId = inputs.afterBlockId;
1693
+ }
1694
+ if (inputs.parentBlockId !== undefined) {
1695
+ variables.parentBlockId = inputs.parentBlockId;
1696
+ }
1697
+ const response = await inputs.client.raw(CREATE_DOC_BLOCK_MUTATION, variables, { operationName: 'CreateDocBlock' });
1698
+ const data = unwrapOrThrow(createDocBlockResponseSchema.safeParse(response.data), {
1699
+ context: 'Monday returned a malformed CreateDocBlock response',
1700
+ details: { doc_id: inputs.docId, type: inputs.type },
1701
+ hint: 'this is a data-integrity error in Monday\'s response; verify ' +
1702
+ 'the response shape and update `createDocBlockResponseSchema` ' +
1703
+ 'if Monday\'s contract has changed.',
1704
+ });
1705
+ assertResponseFieldPresent({
1706
+ data,
1707
+ key: 'create_doc_block',
1708
+ operationLabel: 'CreateDocBlock',
1709
+ details: { doc_id: inputs.docId, type: inputs.type },
1710
+ nullHandling: 'caller_handles',
1711
+ });
1712
+ const rawBlock = data.create_doc_block;
1713
+ if (rawBlock === null || rawBlock === undefined) {
1714
+ throw new ApiError('internal_error', `Monday returned no block payload from create_doc_block(doc_id: ${inputs.docId}, type: ${inputs.type}).`, { details: { doc_id: inputs.docId, type: inputs.type } });
1715
+ }
1716
+ const block = unwrapOrThrow(documentBlockSchema.safeParse(rawBlock), {
1717
+ context: `Monday returned a malformed DocumentBlock payload from create_doc_block (doc ${inputs.docId}, type ${inputs.type})`,
1718
+ details: { doc_id: inputs.docId, type: inputs.type },
1719
+ });
1720
+ return {
1721
+ block,
1722
+ source: 'live',
1723
+ cacheAgeSeconds: null,
1724
+ complexity: response.complexity,
1725
+ };
1726
+ };
1727
+ /**
1728
+ * Updates a per-doc rich-text block's content via
1729
+ * `update_doc_block(block_id, content)` with `operationName:
1730
+ * 'UpdateDocBlock'` (R-NEW-37 W2). No type-switching slot on the
1731
+ * wire — agents needing to change a block's content type
1732
+ * `block-delete` + `block-create` (lossy: new id, new position).
1733
+ *
1734
+ * Failure modes:
1735
+ *
1736
+ * - **Missing `update_doc_block` key** → `internal_error` via
1737
+ * `assertResponseFieldPresent` (schema drift).
1738
+ * - **Present-but-null payload** → `not_found` (the probe
1739
+ * description "Update a document block" promises the updated
1740
+ * block on success, so null reads as missing-record — block
1741
+ * bogus or not visible to the token; per R-v0.5-NEW-11
1742
+ * per-fetcher null-payload discipline).
1743
+ * - **Inner OBJECT drift** → `internal_error` via `unwrapOrThrow`
1744
+ * on {@link documentBlockSchema}.
1745
+ */
1746
+ export const updateDocBlock = async (inputs) => {
1747
+ const response = await inputs.client.raw(UPDATE_DOC_BLOCK_MUTATION, { blockId: inputs.blockId, content: inputs.content }, { operationName: 'UpdateDocBlock' });
1748
+ const data = unwrapOrThrow(updateDocBlockResponseSchema.safeParse(response.data), {
1749
+ context: 'Monday returned a malformed UpdateDocBlock response',
1750
+ details: { block_id: inputs.blockId },
1751
+ hint: 'this is a data-integrity error in Monday\'s response; verify ' +
1752
+ 'the response shape and update `updateDocBlockResponseSchema` ' +
1753
+ 'if Monday\'s contract has changed.',
1754
+ });
1755
+ assertResponseFieldPresent({
1756
+ data,
1757
+ key: 'update_doc_block',
1758
+ operationLabel: 'UpdateDocBlock',
1759
+ details: { block_id: inputs.blockId },
1760
+ nullHandling: 'caller_handles',
1761
+ });
1762
+ const rawBlock = data.update_doc_block;
1763
+ if (rawBlock === null || rawBlock === undefined) {
1764
+ throw new ApiError('not_found', `Monday returned no block payload from update_doc_block for block ${inputs.blockId}`, { details: { block_id: inputs.blockId } });
1765
+ }
1766
+ const block = unwrapOrThrow(documentBlockSchema.safeParse(rawBlock), {
1767
+ context: `Monday returned a malformed DocumentBlock payload from update_doc_block (block ${inputs.blockId})`,
1768
+ details: { block_id: inputs.blockId },
1769
+ });
1770
+ return {
1771
+ block,
1772
+ source: 'live',
1773
+ cacheAgeSeconds: null,
1774
+ complexity: response.complexity,
1775
+ };
1776
+ };
1777
+ /**
1778
+ * Deletes a per-doc rich-text block via `delete_doc_block(block_id)`
1779
+ * with `operationName: 'DeleteDocBlock'` (R-NEW-37 W2). Wire return
1780
+ * is `DocumentBlockIdOnly` (`{id: String!}`).
1781
+ *
1782
+ * Failure modes:
1783
+ *
1784
+ * - **Missing `delete_doc_block` key** → `internal_error` via
1785
+ * `assertResponseFieldPresent` (schema drift).
1786
+ * - **Present-but-null payload** → `not_found` (id bogus or
1787
+ * block already deleted by a concurrent caller; mirrors M14
1788
+ * workspace-delete + M34 team-delete + M35 doc-delete cadence;
1789
+ * `delete_doc_block`'s probe description "Delete a document
1790
+ * block" promises confirmation on success).
1791
+ * - **Inner OBJECT drift** → `internal_error` via `unwrapOrThrow`
1792
+ * on {@link documentBlockIdOnlySchema}.
1793
+ *
1794
+ * The action body's destructive gate fires BEFORE this fetcher per
1795
+ * the M10 round-1 P2 invariant.
1796
+ */
1797
+ export const deleteDocBlock = async (inputs) => {
1798
+ const response = await inputs.client.raw(DELETE_DOC_BLOCK_MUTATION, { blockId: inputs.blockId }, { operationName: 'DeleteDocBlock' });
1799
+ const data = unwrapOrThrow(deleteDocBlockResponseSchema.safeParse(response.data), {
1800
+ context: 'Monday returned a malformed DeleteDocBlock response',
1801
+ details: { block_id: inputs.blockId },
1802
+ hint: 'this is a data-integrity error in Monday\'s response; verify ' +
1803
+ 'the response shape and update `deleteDocBlockResponseSchema` ' +
1804
+ 'if Monday\'s contract has changed.',
1805
+ });
1806
+ assertResponseFieldPresent({
1807
+ data,
1808
+ key: 'delete_doc_block',
1809
+ operationLabel: 'DeleteDocBlock',
1810
+ details: { block_id: inputs.blockId },
1811
+ nullHandling: 'caller_handles',
1812
+ });
1813
+ const rawBlock = data.delete_doc_block;
1814
+ if (rawBlock === null || rawBlock === undefined) {
1815
+ throw new ApiError('not_found', `Monday returned no payload from delete_doc_block for block ${inputs.blockId}`, { details: { block_id: inputs.blockId } });
1816
+ }
1817
+ const block = unwrapOrThrow(documentBlockIdOnlySchema.safeParse(rawBlock), {
1818
+ context: `Monday returned a malformed DocumentBlockIdOnly payload from delete_doc_block (block ${inputs.blockId})`,
1819
+ details: { block_id: inputs.blockId },
1820
+ });
1821
+ return {
1822
+ block,
1823
+ source: 'live',
1824
+ cacheAgeSeconds: null,
1825
+ complexity: response.complexity,
1826
+ };
1827
+ };
1828
+ // ===========================================================================
1829
+ // v0.5-M37 doc-content import mutation surface
1830
+ // ===========================================================================
1831
+ // Two fetchers land the doc-content import wire surface against Monday's
1832
+ // `import_doc_from_html` + `add_content_to_doc_from_markdown` mutations.
1833
+ // Each issues a single `client.raw` round-trip with a literal-pinned
1834
+ // `operationName` (R-NEW-37 W2 safely-by-construction); the response shape
1835
+ // is a custom `{ success!, doc_id? | block_ids?, error? }` OBJECT (NOT the
1836
+ // opaque-JSON M35 cadence and NOT the typed-OBJECT M36 cadence — distinct
1837
+ // third return shape on the doc-mutation surface).
1838
+ //
1839
+ // **Runtime bodies landed at v0.5-M37 IMPL** (two-stage parse + custom-
1840
+ // OBJECT projection + success/failure routing per D12 + integration test
1841
+ // cassettes covering all 5 D12 branches per fetcher).
1842
+ //
1843
+ // Wire summary (empirical probe at `scripts/probe/v0.5-doc-mutations.ts` +
1844
+ // `v0.5-inputs-and-results.ts`, 2026-05-15, API `2026-01`):
1845
+ //
1846
+ // - `import_doc_from_html(html: String!, workspaceId: ID!,
1847
+ // kind: DocKind, folderId: ID, title: String) →
1848
+ // ImportDocFromHtmlResult` (NON_NULL/OBJECT with 3 fields:
1849
+ // `success: Boolean!`, `doc_id: String` (nullable), `error: String`
1850
+ // (nullable)). Operation name `ImportDocFromHtml` pinned literally
1851
+ // per R-NEW-37 W2.
1852
+ // - `add_content_to_doc_from_markdown(docId: ID!, markdown: String!,
1853
+ // afterBlockId: String) → DocBlocksFromMarkdownResult` (NON_NULL/
1854
+ // OBJECT with 3 fields: `success: Boolean!`, `block_ids:
1855
+ // [String!]` (nullable list of non-null strings), `error: String`
1856
+ // (nullable)). Operation name `AddContentToDocFromMarkdown` pinned.
1857
+ //
1858
+ // **camelCase wire arg names (Finding 7).** Both M37 mutations use
1859
+ // camelCase (`workspaceId`, `folderId`, `docId`, `afterBlockId`) on
1860
+ // Monday's wire — same asymmetry as M35's `update_doc_name` /
1861
+ // `delete_doc` / `duplicate_doc` (NOT the M36 snake_case symmetric
1862
+ // path). The GraphQL operation documents below use camelCase variable
1863
+ // names + camelCase wire arg names verbatim; CLI argv stays kebab-case
1864
+ // throughout (`--workspace <wid>`, `--folder <fid>`, `--after <bid>`);
1865
+ // error envelope `details.*` keys stay snake_case per cli-design §6.5
1866
+ // (`details.workspace_id`, `details.doc_id`). Documented as the 5th
1867
+ // supporting site for R-NEW-41 at the canonical `docs/architecture.md`
1868
+ // "Wire-vs-CLI semantics documentation conventions" section.
1869
+ //
1870
+ // **Custom-OBJECT projection contract (D12 closure).** Distinct third
1871
+ // return-shape on Monday's doc-mutation surface:
1872
+ //
1873
+ // - M35 `create_doc` returns full Document OBJECT (typed schema).
1874
+ // - M35 `update_doc_name` / `delete_doc` / `duplicate_doc` return
1875
+ // opaque `JSON` scalar (D9 projection to `{doc_id, success: true}`).
1876
+ // - M36 `create_doc_block` / `update_doc_block` return full
1877
+ // DocumentBlock OBJECT (typed schema); `delete_doc_block` returns
1878
+ // DocumentBlockIdOnly OBJECT.
1879
+ // - **M37 `import_doc_from_html` / `add_content_to_doc_from_markdown`
1880
+ // return a custom 3-field result OBJECT carrying a `success`
1881
+ // discriminator** + an optional payload (`doc_id` / `block_ids`) +
1882
+ // an optional `error` string (wire-side detail when `success:
1883
+ // false`).
1884
+ //
1885
+ // Per D12 closure, the fetcher projects the custom OBJECT into the
1886
+ // flat `{ doc_id, success: true }` (import-html) / `{ doc_id (echoed),
1887
+ // block_ids, success: true }` (append-markdown) envelope on the success
1888
+ // path, and throws typed `ApiError`s on failure:
1889
+ //
1890
+ // - **`success: true + payload present`** → success envelope. The
1891
+ // `data.success: true` slot pins literal-`true` so agents key off
1892
+ // it for retry/idempotency reasoning across the doc-mutation
1893
+ // surface uniformly (mirrors M35's `{doc_id, success: true}` cadence).
1894
+ // - **`success: false + populated `error`** → throw
1895
+ // `validation_failed` with `details.error: <wire-error-message>`.
1896
+ // Monday's `error` field carries the user-visible rejection reason
1897
+ // (invalid HTML, payload too large, markdown parse failure, etc.);
1898
+ // `validation_failed` is the right typed code because the wire-
1899
+ // side rejection is structural, NOT a transport or permission
1900
+ // error.
1901
+ // - **`success: false + empty/null `error`** → throw
1902
+ // `internal_error` with a wire-regression hint. Monday's contract
1903
+ // is "`error` is detailed message when `success: false`"; missing
1904
+ // `error` on a failed mutation indicates the wire shape regressed
1905
+ // (the field went missing OR Monday returned `success: false`
1906
+ // without populating the documented diagnostic slot).
1907
+ // - **`success: true + missing payload`** → throw `internal_error`
1908
+ // (Monday promises the new `doc_id` / `block_ids` on success).
1909
+ //
1910
+ // The exact per-fetcher null-payload contract decisions (per
1911
+ // R-v0.5-NEW-11 graduation candidate at 3rd supporting instance) are
1912
+ // derived from each mutation's probe-description return-shape promise:
1913
+ //
1914
+ // - `import_doc_from_html` probe description: "Returns the ID of
1915
+ // the newly created document on success" → null `doc_id` on
1916
+ // `success: true` IS a wire-regression signal (`internal_error`).
1917
+ // - `add_content_to_doc_from_markdown` probe description: "Returns
1918
+ // the IDs of the newly created blocks on success" → null
1919
+ // `block_ids` on `success: true` IS a wire-regression signal
1920
+ // (`internal_error`). An EMPTY (`[]`) `block_ids` array on
1921
+ // success is plausible for non-empty markdown that Monday's
1922
+ // parser collapses to zero structural blocks (comments-only,
1923
+ // whitespace post-parse, etc.); the schema accepts empty arrays
1924
+ // as success. Empty / whitespace-only `--markdown-string` is
1925
+ // rejected at the parse boundary so it can never reach the wire.
1926
+ //
1927
+ // **D13 closure: empirical wire-side payload-size threshold.** The
1928
+ // pre-flight `scripts/probe/v0.5-m37-size-limits.ts` probe (2026-05-17,
1929
+ // API `2026-01`) pinned an empirical wire-side rejection threshold
1930
+ // between **250KB (last-known-good) and 500KB (first-known-rejected)**
1931
+ // on BOTH `import_doc_from_html` (`html`) and
1932
+ // `add_content_to_doc_from_markdown` (`markdown`) slots — identical
1933
+ // threshold on both surfaces, consistent with a transport-layer
1934
+ // request-size cap (NOT a per-mutation schema constraint).
1935
+ //
1936
+ // **Wire rejection shape is unusual.** Oversized payloads do NOT
1937
+ // surface via the documented `{ success: false, error: '...' }`
1938
+ // envelope path. Instead, Monday's transport layer rejects the
1939
+ // request BEFORE the schema layer can return the typed envelope,
1940
+ // surfacing a generic GraphQL `errors[]` payload:
1941
+ //
1942
+ // ```
1943
+ // errors: [{ message: 'Internal Server Error',
1944
+ // extensions: { code: 'INTERNAL_SERVER_ERROR' } }]
1945
+ // ```
1946
+ //
1947
+ // This routes through the standard transport-layer `mapMondayError`
1948
+ // path and lands as `internal_error` — misleading for what is
1949
+ // fundamentally a payload-size issue. The CLI pre-empts this by
1950
+ // rejecting oversized `--html-string` / `--markdown-string` payloads
1951
+ // at the argv-parse boundary via {@link MAX_DOC_IMPORT_PAYLOAD_BYTES}
1952
+ // (the parse-time string-length `.refine()` fires `usage_error.
1953
+ // details.issues[{path: 'htmlString'|'markdownString', message:
1954
+ // '--html-string exceeds the 256000-byte wire-side limit ...'}]`
1955
+ // ahead of any wire dispatch — standard zod-issues envelope shape
1956
+ // emitted by `parseArgv`). The file-read path (`--html <file>` /
1957
+ // `--markdown <file>`) lands the same size guard at the runtime read
1958
+ // boundary (IMPL).
1959
+ //
1960
+ // **No new ERROR_CODES at M37.** Existing codes route doc-content-
1961
+ // import failures: `usage_error` (argv-parse rejections — mutual-
1962
+ // exclusion of file vs string source, empty payload, oversized
1963
+ // `--html-string` / `--markdown-string`), `validation_failed`
1964
+ // (Monday-side rejection via `success: false + error`), `internal_
1965
+ // error` (wire-shape regression OR `INTERNAL_SERVER_ERROR` for an
1966
+ // oversized payload that slipped past the parse-boundary check),
1967
+ // `not_found` (`append-markdown` against a non-existent or
1968
+ // inaccessible `<doc-id>`), `forbidden` / `unauthorized` (token
1969
+ // lacks workdoc-write scope).
1970
+ //
1971
+ // **0 destructive verbs at M37.** Both `doc import-html` + `doc
1972
+ // append-markdown` are content-creation surfaces — no `--yes`
1973
+ // destructive gate fires (M10 round-1 P2 invariant doesn't apply).
1974
+ //
1975
+ // **R-NEW-76 discipline preserved post-IMPL.** Both action bodies
1976
+ // run `parseArgv` BEFORE `resolveClient` so invalid argv surfaces
1977
+ // `usage_error` from the parse boundary, NOT `config_error` from
1978
+ // `loadConfig` when no token is configured.
1979
+ /**
1980
+ * Maximum byte-size for the inline `--html-string` / `--markdown-string`
1981
+ * payload at the argv-parse boundary (empirical probe 2026-05-17, API
1982
+ * `2026-01`; `scripts/probe/v0.5-m37-size-limits.report.txt`). Set
1983
+ * conservatively at the LAST-KNOWN-GOOD probe size (250KB) — the wire's
1984
+ * first-known-rejection sits at 500KB, so 250KB leaves the agent contract
1985
+ * comfortably below the threshold without leaving room for the wire-side
1986
+ * `INTERNAL_SERVER_ERROR` surprise.
1987
+ *
1988
+ * Bytes (NOT UTF-16 code units) — the parse-boundary check measures the
1989
+ * payload via `Buffer.byteLength(payload, 'utf8')` so multi-byte
1990
+ * characters in the payload count toward the limit the same way Monday's
1991
+ * transport layer sees them.
1992
+ *
1993
+ * Raising this constant is a contract bump: any agent that relied on
1994
+ * being rejected at the old limit might now silently succeed with a
1995
+ * larger payload (additive change). Lowering it is a tighter rejection
1996
+ * surface (potentially breaking — fold into a Codex review at the v0.6+
1997
+ * pre-flight that touches doc-content import). Future API versions may
1998
+ * shift Monday's wire threshold; re-running the probe at any v0.5.x or
1999
+ * v0.6 pre-flight that re-opens this surface pins the new threshold.
2000
+ */
2001
+ export const MAX_DOC_IMPORT_PAYLOAD_BYTES = 256_000;
2002
+ /**
2003
+ * Inner OBJECT schema for `import_doc_from_html`'s
2004
+ * `ImportDocFromHtmlResult` wire return shape — 3 fields per the
2005
+ * v0.5 introspection probe (`scripts/probe/v0.5-inputs-and-results.
2006
+ * report.txt`):
2007
+ *
2008
+ * - `success: Boolean!` — discriminator; pinned NON_NULL per
2009
+ * introspection.
2010
+ * - `doc_id: String` — nullable; populated on `success: true`,
2011
+ * null on `success: false` per Monday's documented contract.
2012
+ * Note Monday's wire types this slot as `String` (NOT `ID`!)
2013
+ * despite the value being a doc-id-shaped reference. The CLI
2014
+ * surfaces it as a brand-untyped string at the wire layer; the
2015
+ * CLI projection envelope re-brands via {@link DocIdSchema} on
2016
+ * the way out so agents read a `DocId`-shaped slot.
2017
+ * - `error: String` — nullable; populated on `success: false`,
2018
+ * null on `success: true`. Wire-side rejection reason (invalid
2019
+ * HTML, payload-too-large, etc.).
2020
+ *
2021
+ * `.strict()` mode — Monday's wire OBJECT shape is documented at
2022
+ * exactly 3 fields; an unknown key would indicate wire-shape
2023
+ * regression worth catching loudly (the wrapping `loose()` schema
2024
+ * tolerates side-band debug keys at the response root, not at the
2025
+ * inner OBJECT layer).
2026
+ */
2027
+ export const importDocFromHtmlResultSchema = z
2028
+ .object({
2029
+ success: z.boolean(),
2030
+ doc_id: z.string().min(1).nullable(),
2031
+ error: z.string().nullable(),
2032
+ })
2033
+ .strict();
2034
+ /**
2035
+ * Inner OBJECT schema for `add_content_to_doc_from_markdown`'s
2036
+ * `DocBlocksFromMarkdownResult` wire return shape — 3 fields per the
2037
+ * v0.5 introspection probe:
2038
+ *
2039
+ * - `success: Boolean!` — discriminator; NON_NULL.
2040
+ * - `block_ids: [String!]` — nullable LIST of NON_NULL strings;
2041
+ * populated on `success: true` with the newly-created block
2042
+ * ids (one per parsed markdown block); null on `success:
2043
+ * false`. EMPTY array on success is plausible for non-empty
2044
+ * markdown that Monday parses to zero convertible blocks (e.g.
2045
+ * comments-only input); the schema accepts empty. Empty /
2046
+ * whitespace-only `--markdown-string` rejects at the parse /
2047
+ * read boundary as `usage_error` and never reaches the wire.
2048
+ * - `error: String` — nullable; populated on `success: false`,
2049
+ * null on `success: true`.
2050
+ *
2051
+ * `.strict()` mode for the same reason as
2052
+ * {@link importDocFromHtmlResultSchema}.
2053
+ */
2054
+ export const docBlocksFromMarkdownResultSchema = z
2055
+ .object({
2056
+ success: z.boolean(),
2057
+ block_ids: z.array(z.string().min(1)).nullable(),
2058
+ error: z.string().nullable(),
2059
+ })
2060
+ .strict();
2061
+ /**
2062
+ * CLI projection envelope for `monday doc import-html` — flat
2063
+ * `{ doc_id, success: true }` shape mirroring M35's
2064
+ * {@link docMutationResultSchema} cadence so agents read a uniform
2065
+ * `{ doc_id, success }` shape across every doc-mutation success
2066
+ * envelope (rename / delete / duplicate / import-html). `doc_id`
2067
+ * carries the **NEWLY-CREATED** doc's id; `success` is literal-
2068
+ * `true` because failure paths throw typed `ApiError`s
2069
+ * (`validation_failed` for `success: false + error` per D12 / null
2070
+ * for wire regression) BEFORE this projection schema is referenced.
2071
+ *
2072
+ * No `error` slot — failures don't reach this envelope. Agents read
2073
+ * `error.code` + `error.details.error` from the failure envelope
2074
+ * instead.
2075
+ */
2076
+ export const docImportHtmlOutputSchema = z
2077
+ .object({
2078
+ doc_id: z.string().min(1),
2079
+ success: z.literal(true),
2080
+ })
2081
+ .strict();
2082
+ /**
2083
+ * CLI projection envelope for `monday doc append-markdown` —
2084
+ * `{ doc_id (echoed input), block_ids, success: true }`. Echoes
2085
+ * the input source-doc id so agents that pipe the append envelope
2086
+ * into a follow-up call (e.g. a `monday doc get <doc-id>` to verify
2087
+ * post-append state) have the parent doc context inline.
2088
+ * `block_ids` carries the wire's full list of NEWLY-CREATED block
2089
+ * ids (one per parsed markdown block) preserved in markdown-source
2090
+ * order. Empty array IS a valid success shape for non-empty markdown
2091
+ * that Monday parses to zero convertible blocks (e.g. comments-only
2092
+ * input); empty / whitespace-only `--markdown-string` rejects at the
2093
+ * parse / read boundary as `usage_error` and never reaches the wire.
2094
+ *
2095
+ * `success` is literal-`true` for the same reason as
2096
+ * {@link docImportHtmlOutputSchema}.
2097
+ */
2098
+ export const docAppendMarkdownOutputSchema = z
2099
+ .object({
2100
+ doc_id: z.string().min(1),
2101
+ block_ids: z.array(z.string().min(1)),
2102
+ success: z.literal(true),
2103
+ })
2104
+ .strict();
2105
+ /**
2106
+ * GraphQL mutation document for `import_doc_from_html(html,
2107
+ * workspaceId, kind?, folderId?, title?) → ImportDocFromHtmlResult`.
2108
+ * Operation name pinned literally to `ImportDocFromHtml` (R-NEW-37
2109
+ * W2 audit-point — operationNames NOT caller-overridable). All wire
2110
+ * args are camelCase (Finding 7); variable names match the wire
2111
+ * shape verbatim.
2112
+ *
2113
+ * Selects every `ImportDocFromHtmlResult` field per the introspection
2114
+ * probe — the 3-field selection lets the fetcher project the
2115
+ * discriminator + payload + error in a single round-trip.
2116
+ */
2117
+ export const IMPORT_DOC_FROM_HTML_MUTATION = `
2118
+ mutation ImportDocFromHtml(
2119
+ $html: String!,
2120
+ $workspaceId: ID!,
2121
+ $kind: DocKind,
2122
+ $folderId: ID,
2123
+ $title: String
2124
+ ) {
2125
+ import_doc_from_html(
2126
+ html: $html,
2127
+ workspaceId: $workspaceId,
2128
+ kind: $kind,
2129
+ folderId: $folderId,
2130
+ title: $title
2131
+ ) {
2132
+ success
2133
+ doc_id
2134
+ error
2135
+ }
2136
+ }
2137
+ `;
2138
+ /**
2139
+ * GraphQL mutation document for `add_content_to_doc_from_markdown(
2140
+ * docId, markdown, afterBlockId?) → DocBlocksFromMarkdownResult`.
2141
+ * Operation name pinned to `AddContentToDocFromMarkdown` (R-NEW-37
2142
+ * W2). camelCase wire arg names (Finding 7).
2143
+ *
2144
+ * `afterBlockId` is the optional opaque block-id positional anchor —
2145
+ * Monday inserts the parsed markdown blocks immediately after the
2146
+ * named block; absent → blocks land at the document tail (append-end
2147
+ * semantics, NOT append-head; Monday's probe description: "Use this
2148
+ * to append content to the end of a document or insert content after
2149
+ * a specific block").
2150
+ */
2151
+ export const ADD_CONTENT_TO_DOC_FROM_MARKDOWN_MUTATION = `
2152
+ mutation AddContentToDocFromMarkdown(
2153
+ $docId: ID!,
2154
+ $markdown: String!,
2155
+ $afterBlockId: String
2156
+ ) {
2157
+ add_content_to_doc_from_markdown(
2158
+ docId: $docId,
2159
+ markdown: $markdown,
2160
+ afterBlockId: $afterBlockId
2161
+ ) {
2162
+ success
2163
+ block_ids
2164
+ error
2165
+ }
2166
+ }
2167
+ `;
2168
+ /**
2169
+ * Wrapping response schema for the `ImportDocFromHtml` mutation. The
2170
+ * `import_doc_from_html` slot widens to `unknown` so the wrapping
2171
+ * parse tolerates Monday's side-band debug keys (the `.loose()` mode
2172
+ * mirrors M27 / M32 / M34 / M35 / M36); the post-parse layer pins
2173
+ * the value against {@link importDocFromHtmlResultSchema} via
2174
+ * `unwrapOrThrow` so per-field drift surfaces with structured
2175
+ * `details.issues` (two-stage parse, mirrors M34 / M35 / M36 cadence).
2176
+ */
2177
+ const importDocFromHtmlResponseSchema = z
2178
+ .object({
2179
+ import_doc_from_html: z.unknown(),
2180
+ })
2181
+ .loose();
2182
+ /**
2183
+ * Wrapping response schema for the `AddContentToDocFromMarkdown`
2184
+ * mutation. Same shape as {@link importDocFromHtmlResponseSchema} —
2185
+ * loose root tolerance + strict inner OBJECT pin at the post-parse
2186
+ * layer.
2187
+ */
2188
+ const addContentToDocFromMarkdownResponseSchema = z
2189
+ .object({
2190
+ add_content_to_doc_from_markdown: z.unknown(),
2191
+ })
2192
+ .loose();
2193
+ /**
2194
+ * Imports an HTML payload as a new workdoc via
2195
+ * `import_doc_from_html(html, workspaceId, kind?, folderId?, title?)`
2196
+ * with `operationName: 'ImportDocFromHtml'` (R-NEW-37 W2 — pinned
2197
+ * literally at the call site; NOT a caller-overridable input slot).
2198
+ *
2199
+ * Two-stage parse: the loose {@link importDocFromHtmlResponseSchema}
2200
+ * wrapping schema tolerates side-band keys at the response root;
2201
+ * `assertResponseFieldPresent` rewraps a missing key as
2202
+ * `internal_error`; the inner OBJECT pins via
2203
+ * {@link importDocFromHtmlResultSchema} (`.strict()` — drift surfaces
2204
+ * `internal_error` with structured `details.issues`).
2205
+ *
2206
+ * Custom-OBJECT projection per D12 (5 branches):
2207
+ *
2208
+ * - `success: true + doc_id present` → flat envelope
2209
+ * `{doc_id, success: true}` (mirrors M35
2210
+ * {@link docMutationResultSchema} cadence).
2211
+ * - `success: false + populated error` → throw `validation_failed`
2212
+ * with `details: {workspace_id, error, hint}` (Monday-side
2213
+ * rejection — invalid HTML, payload too large, etc.).
2214
+ * - `success: false + empty/null error` → throw `internal_error`
2215
+ * with a wire-regression hint (Monday's contract: `error` is
2216
+ * populated when `success: false`).
2217
+ * - `success: true + missing doc_id` → throw `internal_error`
2218
+ * (per-fetcher null-payload contract derived from Monday's probe
2219
+ * description "Returns the ID of the newly created document on
2220
+ * success" per R-v0.5-NEW-11).
2221
+ * - `success: true + null doc_id` → same as missing — wire-side
2222
+ * regression on the documented payload promise.
2223
+ *
2224
+ * Omits optional variables (`kind` / `folderId` / `title`) entirely
2225
+ * when unset so Monday's per-arg server-side defaults apply (the M34 /
2226
+ * M35 / M36 omit-vs-null discipline — undefined-keyed JSON serialises
2227
+ * to a missing key, NOT `null`).
2228
+ */
2229
+ export const importDocFromHtml = async (inputs) => {
2230
+ const variables = {
2231
+ html: inputs.html,
2232
+ workspaceId: inputs.workspaceId,
2233
+ };
2234
+ if (inputs.kind !== undefined) {
2235
+ variables.kind = inputs.kind;
2236
+ }
2237
+ if (inputs.folderId !== undefined) {
2238
+ variables.folderId = inputs.folderId;
2239
+ }
2240
+ if (inputs.title !== undefined) {
2241
+ variables.title = inputs.title;
2242
+ }
2243
+ const response = await inputs.client.raw(IMPORT_DOC_FROM_HTML_MUTATION, variables, { operationName: 'ImportDocFromHtml' });
2244
+ const data = unwrapOrThrow(importDocFromHtmlResponseSchema.safeParse(response.data), {
2245
+ context: 'Monday returned a malformed ImportDocFromHtml response',
2246
+ details: { workspace_id: inputs.workspaceId },
2247
+ hint: 'this is a data-integrity error in Monday\'s response; verify ' +
2248
+ 'the response shape and update `importDocFromHtmlResponseSchema` ' +
2249
+ 'if Monday\'s contract has changed.',
2250
+ });
2251
+ assertResponseFieldPresent({
2252
+ data,
2253
+ key: 'import_doc_from_html',
2254
+ operationLabel: 'ImportDocFromHtml',
2255
+ details: { workspace_id: inputs.workspaceId },
2256
+ nullHandling: 'caller_handles',
2257
+ });
2258
+ const rawResult = data.import_doc_from_html;
2259
+ if (rawResult === null || rawResult === undefined) {
2260
+ throw new ApiError('internal_error', `Monday returned no payload from import_doc_from_html for workspace ${inputs.workspaceId}.`, { details: { workspace_id: inputs.workspaceId } });
2261
+ }
2262
+ const parsed = unwrapOrThrow(importDocFromHtmlResultSchema.safeParse(rawResult), {
2263
+ context: `Monday returned a malformed ImportDocFromHtmlResult payload from import_doc_from_html (workspace ${inputs.workspaceId})`,
2264
+ details: { workspace_id: inputs.workspaceId },
2265
+ });
2266
+ if (!parsed.success) {
2267
+ if (parsed.error !== null && parsed.error.length > 0) {
2268
+ throw new ApiError('validation_failed', `Monday rejected import_doc_from_html: ${parsed.error}`, {
2269
+ details: {
2270
+ workspace_id: inputs.workspaceId,
2271
+ error: parsed.error,
2272
+ hint: 'Monday\'s wire-side rejection reason is verbatim in `error`; ' +
2273
+ 'inspect it for invalid HTML, payload-size, or permission issues.',
2274
+ },
2275
+ });
2276
+ }
2277
+ throw new ApiError('internal_error', `Monday returned success: false from import_doc_from_html without a populated error message (workspace ${inputs.workspaceId}).`, {
2278
+ details: {
2279
+ workspace_id: inputs.workspaceId,
2280
+ hint: 'wire-shape regression — Monday\'s contract is that `error` is ' +
2281
+ 'a non-empty string when `success: false`. Re-probe via ' +
2282
+ '`scripts/probe/v0.5-doc-mutations.ts` if this persists.',
2283
+ },
2284
+ });
2285
+ }
2286
+ if (parsed.doc_id === null) {
2287
+ throw new ApiError('internal_error', `Monday returned success: true from import_doc_from_html but a null doc_id (workspace ${inputs.workspaceId}).`, {
2288
+ details: {
2289
+ workspace_id: inputs.workspaceId,
2290
+ hint: 'wire-shape regression — Monday\'s probe description promises a ' +
2291
+ 'non-null `doc_id` on success. Re-probe via `scripts/probe/v0.5-' +
2292
+ 'doc-mutations.ts` if this persists.',
2293
+ },
2294
+ });
2295
+ }
2296
+ return {
2297
+ result: { doc_id: parsed.doc_id, success: true },
2298
+ source: 'live',
2299
+ cacheAgeSeconds: null,
2300
+ complexity: response.complexity,
2301
+ };
2302
+ };
2303
+ /**
2304
+ * Appends parsed-markdown blocks to an existing workdoc via
2305
+ * `add_content_to_doc_from_markdown(docId, markdown, afterBlockId?)`
2306
+ * with `operationName: 'AddContentToDocFromMarkdown'` (R-NEW-37 W2).
2307
+ *
2308
+ * Two-stage parse: the loose
2309
+ * {@link addContentToDocFromMarkdownResponseSchema} wrapping schema
2310
+ * tolerates side-band keys; `assertResponseFieldPresent` rewraps a
2311
+ * missing key as `internal_error`; the inner OBJECT pins via
2312
+ * {@link docBlocksFromMarkdownResultSchema}.
2313
+ *
2314
+ * Custom-OBJECT projection per D12 (5 branches):
2315
+ *
2316
+ * - `success: true + block_ids present (incl. EMPTY array)` →
2317
+ * success envelope `{doc_id (echoed), block_ids, success: true}`.
2318
+ * Empty `block_ids: []` IS a valid success shape per probe
2319
+ * finding — for non-empty markdown that Monday parses to zero
2320
+ * convertible blocks (e.g. comments-only payload Monday
2321
+ * collapses to zero structural blocks). Empty / whitespace-only
2322
+ * input is rejected at the parse / read boundary by
2323
+ * {@link readSourceContent}, so it can never reach this fetcher.
2324
+ * - `success: false + populated error` → throw `validation_failed`
2325
+ * with `details: {doc_id, error, hint}`.
2326
+ * - `success: false + empty/null error` → throw `internal_error`
2327
+ * with wire-regression hint.
2328
+ * - `success: true + null block_ids` → throw `internal_error`
2329
+ * (per-fetcher null-payload contract per R-v0.5-NEW-11 — Monday's
2330
+ * probe description "Returns the IDs of the newly created blocks
2331
+ * on success" promises a non-null array).
2332
+ *
2333
+ * Omits `afterBlockId` when undefined (M34 / M35 / M36 omit-vs-null
2334
+ * discipline).
2335
+ */
2336
+ export const addContentToDocFromMarkdown = async (inputs) => {
2337
+ const variables = {
2338
+ docId: inputs.docId,
2339
+ markdown: inputs.markdown,
2340
+ };
2341
+ if (inputs.afterBlockId !== undefined) {
2342
+ variables.afterBlockId = inputs.afterBlockId;
2343
+ }
2344
+ const response = await inputs.client.raw(ADD_CONTENT_TO_DOC_FROM_MARKDOWN_MUTATION, variables, { operationName: 'AddContentToDocFromMarkdown' });
2345
+ const data = unwrapOrThrow(addContentToDocFromMarkdownResponseSchema.safeParse(response.data), {
2346
+ context: 'Monday returned a malformed AddContentToDocFromMarkdown response',
2347
+ details: { doc_id: inputs.docId },
2348
+ hint: 'this is a data-integrity error in Monday\'s response; verify ' +
2349
+ 'the response shape and update ' +
2350
+ '`addContentToDocFromMarkdownResponseSchema` if Monday\'s ' +
2351
+ 'contract has changed.',
2352
+ });
2353
+ assertResponseFieldPresent({
2354
+ data,
2355
+ key: 'add_content_to_doc_from_markdown',
2356
+ operationLabel: 'AddContentToDocFromMarkdown',
2357
+ details: { doc_id: inputs.docId },
2358
+ nullHandling: 'caller_handles',
2359
+ });
2360
+ const rawResult = data.add_content_to_doc_from_markdown;
2361
+ if (rawResult === null || rawResult === undefined) {
2362
+ throw new ApiError('internal_error', `Monday returned no payload from add_content_to_doc_from_markdown for doc ${inputs.docId}.`, { details: { doc_id: inputs.docId } });
2363
+ }
2364
+ const parsed = unwrapOrThrow(docBlocksFromMarkdownResultSchema.safeParse(rawResult), {
2365
+ context: `Monday returned a malformed DocBlocksFromMarkdownResult payload from add_content_to_doc_from_markdown (doc ${inputs.docId})`,
2366
+ details: { doc_id: inputs.docId },
2367
+ });
2368
+ if (!parsed.success) {
2369
+ if (parsed.error !== null && parsed.error.length > 0) {
2370
+ throw new ApiError('validation_failed', `Monday rejected add_content_to_doc_from_markdown: ${parsed.error}`, {
2371
+ details: {
2372
+ doc_id: inputs.docId,
2373
+ error: parsed.error,
2374
+ hint: 'Monday\'s wire-side rejection reason is verbatim in `error`; ' +
2375
+ 'inspect it for invalid markdown, payload-size, doc visibility, ' +
2376
+ 'or permission issues.',
2377
+ },
2378
+ });
2379
+ }
2380
+ throw new ApiError('internal_error', `Monday returned success: false from add_content_to_doc_from_markdown without a populated error message (doc ${inputs.docId}).`, {
2381
+ details: {
2382
+ doc_id: inputs.docId,
2383
+ hint: 'wire-shape regression — Monday\'s contract is that `error` is ' +
2384
+ 'a non-empty string when `success: false`. Re-probe via ' +
2385
+ '`scripts/probe/v0.5-doc-mutations.ts` if this persists.',
2386
+ },
2387
+ });
2388
+ }
2389
+ if (parsed.block_ids === null) {
2390
+ throw new ApiError('internal_error', `Monday returned success: true from add_content_to_doc_from_markdown but a null block_ids list (doc ${inputs.docId}).`, {
2391
+ details: {
2392
+ doc_id: inputs.docId,
2393
+ hint: 'wire-shape regression — Monday\'s probe description promises a ' +
2394
+ 'non-null `block_ids` list on success (empty array is plausible ' +
2395
+ 'when the markdown parses to zero convertible blocks). Re-probe ' +
2396
+ 'via `scripts/probe/v0.5-doc-mutations.ts` if this persists.',
2397
+ },
2398
+ });
2399
+ }
2400
+ return {
2401
+ result: {
2402
+ doc_id: inputs.docId,
2403
+ block_ids: parsed.block_ids,
2404
+ success: true,
2405
+ },
2406
+ source: 'live',
2407
+ cacheAgeSeconds: null,
2408
+ complexity: response.complexity,
2409
+ };
2410
+ };
2411
+ //# sourceMappingURL=documents.js.map