@datarails-ai/dr-cli 0.0.1-locked → 1.6.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +608 -0
  2. package/dist/chunk-DWMCBGQH.js +242 -0
  3. package/dist/cli.js +64468 -0
  4. package/dist/kb-6UWI7Q32.js +21 -0
  5. package/dist/scopes-map.json +4412 -0
  6. package/dist/skills/connector-sandbox/SKILL.md +95 -0
  7. package/dist/skills/datarails-connect-workato/SKILL.md +140 -0
  8. package/dist/skills/datarails-connectors-onboard-connector/SKILL.md +813 -0
  9. package/dist/skills/dr-cli/SKILL.md +659 -0
  10. package/dist/skills/dr-cli/reference/admin.md +796 -0
  11. package/dist/skills/dr-cli/reference/budget-builder.md +151 -0
  12. package/dist/skills/dr-cli/reference/calc-vals.md +73 -0
  13. package/dist/skills/dr-cli/reference/calcmodels.md +76 -0
  14. package/dist/skills/dr-cli/reference/cash.md +61 -0
  15. package/dist/skills/dr-cli/reference/dashboards-widgets.md +85 -0
  16. package/dist/skills/dr-cli/reference/datamappers.md +150 -0
  17. package/dist/skills/dr-cli/reference/desk.md +472 -0
  18. package/dist/skills/dr-cli/reference/filebox-documents.md +373 -0
  19. package/dist/skills/dr-cli/reference/forecast.md +51 -0
  20. package/dist/skills/dr-cli/reference/integrations.md +57 -0
  21. package/dist/skills/dr-cli/reference/lookups.md +168 -0
  22. package/dist/skills/dr-cli/reference/metrics.md +512 -0
  23. package/dist/skills/dr-cli/reference/multi-product.md +180 -0
  24. package/dist/skills/dr-cli/reference/reconciliation-mec.md +76 -0
  25. package/dist/skills/dr-cli/reference/rh-config.md +84 -0
  26. package/dist/skills/dr-cli/reference/safety.md +143 -0
  27. package/dist/skills/dr-cli/reference/spend-control.md +206 -0
  28. package/dist/skills/dr-cli/reference/templates.md +163 -0
  29. package/dist/skills/dr-cli/reference/workflows.md +137 -0
  30. package/dist/skills/dr-error-enrichment/SKILL.md +80 -0
  31. package/dist/skills/dr-error-enrichment/reference/core.md +257 -0
  32. package/dist/skills/dr-error-enrichment/reference/groundcover.md +84 -0
  33. package/dist/skills/dr-error-enrichment/reference/jira-bugs.md +219 -0
  34. package/dist/skills/dr-error-enrichment/reference/safety-backup.md +122 -0
  35. package/dist/skills/dr-error-enrichment-author/SKILL.md +219 -0
  36. package/dist/skills/dr-error-enrichment-improve/SKILL.md +218 -0
  37. package/dist/skills/dr-error-enrichment-maintain/SKILL.md +237 -0
  38. package/dist/skills/dr-error-enrichment-reconcile/SKILL.md +167 -0
  39. package/dist/skills/dr-filebox-api-setup/SKILL.md +170 -0
  40. package/dist/skills/dr-switch-user/SKILL.md +395 -0
  41. package/dist/skills/dr-validate/SKILL.md +392 -0
  42. package/dist/skills/dr-validate/report-template.html +950 -0
  43. package/dist/skills/fx-conversion-matrix/SKILL.md +763 -0
  44. package/dist/skills/fx-conversion-matrix/scripts/_args.mjs +52 -0
  45. package/dist/skills/fx-conversion-matrix/scripts/_xlsx.mjs +241 -0
  46. package/dist/skills/fx-conversion-matrix/scripts/build-matrix.mjs +366 -0
  47. package/dist/skills/fx-conversion-matrix/scripts/gen-mappers.mjs +171 -0
  48. package/dist/skills/fx-conversion-matrix/scripts/rates-to-xlsx.mjs +172 -0
  49. package/dist/skills/fx-onboard/SKILL.md +325 -0
  50. package/dist/skills/mec-table-validation/SKILL.md +383 -0
  51. package/dist/skills/plugins.json +24 -0
  52. package/dist/skills/validate-workato-migration/SKILL.md +167 -0
  53. package/dist/skills/validate-workato-migration/reference/gotchas.md +16 -0
  54. package/dist/skills/validate-workato-migration/reference/staging-orgs.md +26 -0
  55. package/dist/skills/validate-workato-migration/scripts/compare_files.py +255 -0
  56. package/dist/zendeskKb-JPSHVOPH.js +243 -0
  57. package/package.json +56 -9
  58. package/scripts/postinstall.mjs +91 -0
@@ -0,0 +1,472 @@
1
+ # Desk
2
+
3
+ `dr desk` fronts the Desk service. Most of it is outbound-email onboarding
4
+ (`/api/desk/v1/teams/...`) so the integrations team can set up a customer's
5
+ outbound email themselves; `ticket create` (`/api/desk/v1/tickets/`) opens a
6
+ ticket without the web UI. Before the onboarding commands
7
+ existed the documented procedure required an encryption script and a hand-written
8
+ `INSERT INTO smtp_connections`, which that team cannot run: no Key Vault access,
9
+ no database access, and the email gateway that holds the credentials has no
10
+ public ingress at all.
11
+
12
+ Desk is the edge-facing orchestrator in front of that gateway. It holds the
13
+ service-to-service token, so nothing here needs cluster access.
14
+
15
+ ## The two paths are not interchangeable
16
+
17
+ | | SendGrid domain auth | SMTP app password |
18
+ | --------------- | ---------------------------------------- | ----------------------------------------------------- |
19
+ | Scope | per organization | per (org, team) |
20
+ | Customer secret | none | the app password |
21
+ | Customer's work | 3 CNAMEs + 1 DMARC TXT | MFA, app password, forward rule, approve verification |
22
+ | Completion | asynchronous - DNS, then a 15-min poller | synchronous - a real SMTP handshake |
23
+ | Re-runnable | yes, idempotent | `set` only onto a soft-deleted row; an ACTIVE row 409s - use `smtp rotate`, which replaces the credential in place and never deactivates the row |
24
+
25
+ **The gateway picks SMTP ahead of SendGrid at send time and never reads
26
+ `sendgrid_domains` when choosing a sender.** So an SMTP connection silently
27
+ overrides a verified SendGrid domain for that team. Moving a customer from an app
28
+ password onto SendGrid means _removing the SMTP connection_, not just verifying
29
+ the domain - verifying alone changes nothing. `register` and `verify` both emit a
30
+ warning when an active SMTP connection exists; do not ignore it.
31
+
32
+ ## Auth - read this first
33
+
34
+ Every **onboarding** verb is admin-only, and "admin" means `SUPER_ADMIN` or
35
+ `ADMIN` on the target org's Django profile. (`ticket create` is the one
36
+ exception - see its own section.) Two consequences:
37
+
38
+ 1. **There is no `org_id` argument anywhere.** The org comes from the token, so
39
+ it is decided entirely by `--server` and `--account`. Check with
40
+ `dr whoami --server <s> --account <a>` before a write.
41
+ 2. **An integrations engineer is not an admin in a customer org**, so the normal
42
+ identity is an impersonation:
43
+
44
+ ```
45
+ dr login-as <customer-admin-email> --server US
46
+ dr desk teams list --server US --account <customer-admin-email>
47
+ ```
48
+
49
+ The impersonation JWT carries the _target's_ `role` claim, which is what Desk
50
+ maps, so this works where your own operator identity would 403.
51
+
52
+ Note `SUPPORT_SUPER_ADMIN` and `DR_CONNECT_ADMIN` are **not** admin as far as
53
+ Desk is concerned - only `SUPER_ADMIN` and `ADMIN` are. A support identity that
54
+ works elsewhere in the CLI can still 403 here.
55
+
56
+ ## SMTP app-password path
57
+
58
+ ```
59
+ dr desk teams list --server US --account <admin>
60
+ dr desk smtp set ap@acme.com --password-file ./pw --server US --account <admin>
61
+ ```
62
+
63
+ - `<team>` takes a **team id, a support email, or a team name** - whichever the
64
+ operator has in front of them. `teams list` returns all three, so nothing has
65
+ to be transcribed. Matching is exact and case-insensitive, email checked
66
+ first; it is deliberately not a substring match, because "AP" quietly
67
+ selecting "AP - EMEA" would write a customer credential to the wrong team. An
68
+ ambiguous or unknown **name or support email** lists the candidates and
69
+ refuses rather than guessing. A numeric id is passed straight through without
70
+ a lookup - it is unambiguous by construction, so the server is the authority
71
+ on whether it exists, and an id that does not resolve fails on the write.
72
+ - Resolution happens before the confirm token is minted, so the token binds the
73
+ numeric id. If a name starts resolving to a different team between the plan
74
+ and the redeem, the token stops working instead of writing to a new target.
75
+
76
+ - The password comes from `--password-file` or `--password-stdin`, **never** as
77
+ an argument. A credential in `argv` lands in shell history and in `ps` output
78
+ on a shared host. A single trailing newline is stripped; interior spaces are
79
+ kept, because providers display app passwords in groups of four and the server
80
+ normalises exactly that shape.
81
+ - `--smtp-username` defaults to the team's `support_email`. `--smtp-host` is
82
+ inferred for Gmail and O365 only; any other domain **must** pass it, because
83
+ Google Workspace on a custom domain is indistinguishable from any other host
84
+ by the address alone and a wrong host gives a confusing timeout instead of an
85
+ error.
86
+ - A team with no `support_email` shows as `(none set)` in `teams list` and cannot
87
+ be onboarded on this path yet - the username has nothing to default to.
88
+ - **`verified: false` on a 200 is the common outcome, not a bug.** The credential
89
+ is stored and the provider rejected it: usually MFA is off on the mailbox, or
90
+ an account password was supplied instead of an app password. Both are
91
+ customer-side fixes, which is why the row is deliberately kept rather than
92
+ rolled back - re-run after they fix it.
93
+ - Two-step confirm (Tier A: word-gated on prod). It chooses the team's outbound
94
+ sender, and this CLI has no delete verb, so there is no one-command inverse.
95
+ - **On prod, use `--password-file`, not `--password-stdin`, on Linux.** Tier A
96
+ means the confirm step wants a human, and on Linux that human is prompted on
97
+ the TTY - but `--password-stdin` has already consumed stdin to EOF by then.
98
+ The command **refuses this combination up front**, naming the flag and
99
+ pointing at `--password-file`, rather than draining the pipe and failing in
100
+ the gate with "must be confirmed by a person" or a bogus "word did not match".
101
+ The refusal applies on the plan phase as well as the redeem - the machine's
102
+ channel situation is the same either way - but only on production tenants and
103
+ only where no desktop dialog exists; macOS and Windows prompts are dialogs
104
+ that never touch stdin. Separately, `--password-stdin` with a TERMINAL on
105
+ stdin (a pasted command with no pipe attached) is refused everywhere: typing
106
+ the credential there would echo it to the screen and into scrollback.
107
+ - **Delete the password file when you are done.** The plan/confirm split means it
108
+ has to survive the token window, so it cannot be a process substitution - which
109
+ leaves a customer credential in plaintext in your working directory:
110
+
111
+ ```
112
+ dr desk smtp set 6 --password-file ./pw --server US --account <admin>
113
+ # ... redeem the token ...
114
+ rm ./pw
115
+ ```
116
+
117
+ ### Replacing a credential that is already stored
118
+
119
+ `set` refuses a team that already has an ACTIVE connection. `rotate` is the
120
+ in-place replacement - same password sources, same `--smtp-username` /
121
+ `--smtp-host` / `--smtp-port` flags, same two-step confirm gate:
122
+
123
+ ```
124
+ dr desk smtp rotate 6 --password-file ./pw --server US --account <admin>
125
+ # ... redeem the token ...
126
+ rm ./pw
127
+ ```
128
+
129
+ - The row is **never deactivated**, so the team keeps sending authenticated
130
+ throughout. Deactivating it first - the old recovery - drops outbound to
131
+ unauthenticated SendGrid until the new credential lands.
132
+ - Omitted flags keep what is **stored**, not what the support email would infer,
133
+ so a custom-domain Workspace team does not need `--smtp-host` again.
134
+ - It **404s when there is nothing to rotate** rather than creating a connection,
135
+ so a mistyped team is an error and not a silent onboarding.
136
+ - Tier A: the previous password is encrypted and not versioned, so there is no
137
+ way back to it.
138
+
139
+ ## Troubleshooting an existing connection
140
+
141
+ ```
142
+ dr desk smtp get ap@acme.com --server US --account <admin>
143
+ dr desk smtp test ap@acme.com --server US --account <admin>
144
+ ```
145
+
146
+ - `get` touches no mail provider, so it is safe to run while diagnosing. It
147
+ never returns the password.
148
+ - **`active: NO` means the row exists but does not route.** The gateway's read
149
+ returns soft-deleted rows while send-time selection skips them, so "there is a
150
+ connection" and "that connection sends the mail" are different questions. This
151
+ is the answer to "we deleted the SMTP connection but mail still goes over
152
+ SMTP" - check the org-wide row too.
153
+ - Both `get` and `test` are **team-scoped with no org-wide fallback**, while
154
+ send time _does_ fall back. So "no connection for this team" does not mean the
155
+ team has no SMTP sender - an org-wide row may be sending for it. Both messages
156
+ say so.
157
+ - `test` re-runs the real handshake **without re-submitting the password**. Use
158
+ it after the customer enables MFA on the existing password - the stored value
159
+ becomes valid without changing.
160
+ - A **regenerated** app password is a new value, so `test` cannot help: it
161
+ re-handshakes the stale stored one and fails, and no 409 appears on that path
162
+ to redirect you. Use `smtp rotate` to replace it in place.
163
+ - A rejection prints the provider's own error plus a likely cause, and prints
164
+ **no** cause when the error maps to nothing known - a wrong diagnosis sends
165
+ the operator back to the customer for the wrong thing.
166
+
167
+ ## Register the team's inbound address - the two-stage funnel dance
168
+
169
+ The address the team carries changes TWICE during onboarding, and the order is
170
+ the whole trick. The provider's forwarding-verification email is sent **to the
171
+ region funnel address itself** - the customer's mailbox appears nowhere in its
172
+ recipient headers - so that one email can only become a ticket while the team's
173
+ configured address IS the funnel address.
174
+
175
+ **Stage 1 - before the customer creates their forwarding rule**, put the
176
+ region's funnel address on the team:
177
+
178
+ ```
179
+ # <team> is the team NAME or numeric id - NOT the address being set.
180
+ # <REGION> and the address must name the SAME region - see below.
181
+ dr desk support-email set "AP" <REGION funnel address> --server <REGION> --account <admin>
182
+ ```
183
+
184
+ Each region has its own inbound funnel address. The live list is **not
185
+ reproduced here** — this reference ships inside the published npm package, and
186
+ the funnel addresses are real inbound mailboxes. Get the address for the region
187
+ you are onboarding from the internal Desk runbook, or from the desk owners,
188
+ before running the command above. Passing the wrong region's address silently
189
+ routes the customer's mail to another environment's funnel.
190
+
191
+ Sibling environments have no address of their own: **DEV-1 shares DEV's** and
192
+ **TEST-1 through TEST-7 share TEST's** (they inherit the family configuration).
193
+ A shared funnel is physically one mail route, so inbound onboarding on a
194
+ sibling environment lands wherever that shared funnel is pointed - check with
195
+ R&D before running the funnel dance on a sibling environment.
196
+
197
+ The customer now creates their auto-forward rule to that same funnel address;
198
+ the provider's verification email arrives **as a ticket on this team** in the
199
+ Desk UI. Approve the forwarding with the link/code from that ticket.
200
+
201
+ **Stage 2 - immediately after approving**, switch the team to the customer's
202
+ real mailbox:
203
+
204
+ ```
205
+ dr desk support-email set "AP" ap@acme.com --server US --account <admin>
206
+ ```
207
+
208
+ **Do not run `smtp set` between the stages.** Its username default follows the
209
+ team's address *at execute time*, so during the window it would store the
210
+ customer's credential under the funnel address - a sender no provider will
211
+ accept. Save the app password only after stage 2, or pass `--smtp-username`
212
+ explicitly.
213
+
214
+ Do not linger between the stages: the funnel address is a **unique registry
215
+ key**, so while it sits on this team, every inbound funnel email routes here. Forwarded customer mail keeps the customer's mailbox in its
216
+ original `To:` header, which is what routes it after stage 2 - the funnel
217
+ address is only ever needed to catch the one verification email. Stage 2
218
+ releases the key by deregistering the funnel address (observed live, not
219
+ inferred: the deregister fires on every address change).
220
+
221
+ **Only one onboarding per funnel address can run at a time - and siblings share
222
+ one** (DEV with DEV-1, TEST with TEST-1..7), so coordinate across the whole
223
+ family, not just your own environment. A second stage 1 while another team
224
+ holds the funnel address does NOT fail loudly - the
225
+ CLI reports success (the field write works), but the registry registration
226
+ collides in a background task, so the verification ticket lands on the OTHER
227
+ team (or, same-org, silently steals the funnel from it). If the verification
228
+ ticket does not appear on your team within a few minutes: on an environment
229
+ still awaiting the DR-52424 release, the release is the cause, not a collision;
230
+ otherwise assume a collision or a failed registration and escalate to R&D, who
231
+ can inspect and release the registry row. The CLI prints this same warning on
232
+ every stage-1 plan and success output, keyed on the funnel domain.
233
+
234
+ > **DR-52424 (inbound registration 401) - fixed, rollout-gated.** Before
235
+ > 2026-08-25, the monolith minted its gateway token without an org claim, so
236
+ > the registry write this command dispatches 401'd everywhere and inbound mail
237
+ > was silently dropped. The fix (dr-datarails-server #7358) is merged and
238
+ > verified end-to-end on DEV. On any environment that has not yet received a
239
+ > monolith release containing it, the old behaviour still applies - confirm a
240
+ > test email becomes a ticket before telling a customer inbound is live.
241
+
242
+ Two-step confirm, Tier A: a wrong address routes the customer's mail to another
243
+ team or nowhere, and this CLI has no deregister verb. The confirm token is bound
244
+ to the team **and** the address, so a token cannot be redeemed against a
245
+ different address than the one the plan step showed.
246
+
247
+ ## SendGrid domain-auth path
248
+
249
+ ```
250
+ dr desk sendgrid register acme.com --server US --account <admin>
251
+ # hand the printed records to the customer's IT team, then later:
252
+ dr desk sendgrid verify --server US --account <admin>
253
+ ```
254
+
255
+ - `register` is idempotent - a second run returns the existing record and says
256
+ `already registered`. It does not change how any mail is sent today (see the
257
+ precedence note above), which is why it is Tier B: two-step token, no word gate.
258
+ - **Four records, not three.** SendGrid returns three CNAMEs; the gateway appends
259
+ a DMARC TXT of its own. The `TYPE` column matters - telling a customer to add a
260
+ CNAME for the DMARC record is a step that cannot succeed.
261
+ - `status` reads stored state and does **not** contact SendGrid, so it carries no
262
+ per-record verdicts and an unverified domain always reads as
263
+ `records_missing` there. Use `verify` for the authoritative check; `status` will
264
+ say so.
265
+ - `verify` is not confirm-gated. It only refreshes state a server-side poller
266
+ refreshes every 15 minutes anyway, and it is the verb you run repeatedly while
267
+ waiting on a customer's DNS.
268
+ - `VALID` of `-` means SendGrid has not reported on that record, which is **not**
269
+ the same as failing. It is always `-` for `dmarc`, which SendGrid never
270
+ validates.
271
+ - `verification_state` is the field to branch on, not `is_verified`:
272
+ `not_registered`, `records_missing` (wait, or run verify), `records_invalid`
273
+ (the DNS is wrong - the response names which record and why), `verified`.
274
+ - With several domains registered, `status` and `verify` target the **most
275
+ recently registered** one. Pass `--domain <d>` to name an older one. This
276
+ matters mid-migration, when the org has a verified old domain and a fresh
277
+ unverified one.
278
+
279
+ ## Opening a ticket
280
+
281
+ ```
282
+ dr desk ticket create <team> --title "..." --requester-email someone@acme.com --server DEV
283
+ # ... redeem the token ...
284
+ ```
285
+
286
+ `<team>` takes a team id, a support email, or a team name - the same resolution
287
+ `smtp set` uses. Everything else is a flag: `--description` / `--description-file`,
288
+ `--priority urgent|medium|low`, `--ticket-type`, `--requester-name`,
289
+ `--assigned-to`, `--contact-id`, `--due-date`, `--attach`.
290
+
291
+ **`--attach <path>` (repeatable) is create-then-upload, not one call.** Desk has
292
+ no endpoint that creates a ticket with files: the ticket is one request and each
293
+ file is another (`POST /api/desk/v1/tickets/<id>/attachments/upload`). So the
294
+ requester's confirmation email is published *before* the files exist, a failed
295
+ upload exits non-zero naming which files landed, and retrying means attaching
296
+ from the UI - re-running the command would open a second ticket and email the
297
+ requester again.
298
+
299
+ Extensions (`.pdf .xlsx .xls .docx .doc .csv .png .jpg .jpeg .gif`) and the 25 MB
300
+ per-file cap are checked locally before the create, so a bad file costs no
301
+ ticket, no email and no token. The cap is deployment-overridable server-side, so
302
+ that check is an early failure, not a guarantee. The CLI also derives the
303
+ content type from the extension - an untyped upload would be stored as
304
+ `application/octet-stream` on both the row and the blob, permanently, where the
305
+ web composer records the real type. An EXTENSIONLESS file is rejected locally
306
+ even though the server would accept it (its check short-circuits on an empty
307
+ suffix): there is no extension to derive a type from, and the web UI rejects it
308
+ too.
309
+
310
+ **The description is also posted as the requester's first message by default**
311
+ (`--no-thread` opts out). `description` is a ticket column, but the UI's
312
+ conversation pane renders the separate `messages` table - so without this a
313
+ CLI-created ticket looks empty on screen even though the description stored
314
+ fine. This mirrors desk-ai's own inbound handler, which sets `description` AND
315
+ creates a message with the same body. It sends **no** email: the message is
316
+ `direction: inbound`, and every send path is behind
317
+ `not is_internal_note and direction == "outbound"`. Note `direction` is
318
+ optional in desk-ai's schema and a non-note defaults to OUTBOUND, so omitting
319
+ it would email the requester a second time - it is always sent explicitly.
320
+ Attachments link to that message, so the FILES pane groups them as "Received".
321
+
322
+ This does NOT clear the "requester is not linked to any record" banner:
323
+ `requester_match_status` is assigned only in desk-ai's inbound handler, so
324
+ requester matching never runs on the API create path at all. Use `--contact-id`.
325
+
326
+ **`--json` becomes `{ticket, attachments}` when `--attach` was used** - so a
327
+ partial failure is visible to a script. With no attachments it stays the bare
328
+ ticket.
329
+
330
+ Uploading is **team-scoped** (creating is org-scoped) and Desk answers **404, not
331
+ 403**, for a ticket the caller may not see. A routing rule that moves the new
332
+ ticket to another team therefore makes the upload say "not found" for an id just
333
+ printed; the CLI renders that as the team-access answer it is.
334
+
335
+ **This is the one Desk verb that is not admin-only - but that does not mean you
336
+ can skip `dr login-as`.** The route admits a caller who belongs to at least one
337
+ Desk team in the org, or who is a platform admin. `SUPER_ADMIN`/`ADMIN` is not
338
+ required the way it is for the onboarding verbs, but an integrations engineer's
339
+ own identity in a customer org has no Desk team memberships either, so it 403s
340
+ for that reason instead. The working identity in a customer org is still
341
+ `dr login-as <customer-admin>` then `--account <that-email>`.
342
+
343
+ The memberships are read from the monolith on every request, so a monolith
344
+ outage produces the same 403 for a genuine team member. If the identity is right,
345
+ retry before escalating.
346
+
347
+ The org is still taken from the token, so `--server` / `--account` decide which
348
+ customer gets the ticket.
349
+
350
+ **It emails the requester.** Desk publishes a ticket-confirmation message to
351
+ `--requester-email` off the same request, which is why the command is
352
+ confirm-gated and why production keeps the interactive word gate. The ticket can
353
+ be cancelled afterwards **in the Desk UI - this CLI has no cancel verb** - but
354
+ that undoes the row, not the message. Use your own address when testing.
355
+
356
+ If `<team>` is a numeric id, the CLI checks it against the org's teams first:
357
+ `tickets.team_id` has no foreign key, so a typo'd id creates a ticket on no
358
+ board that emails nobody, and the plan step would print it as a fact. That check
359
+ needs the admin-only teams list, so when it is unavailable (non-admin identity,
360
+ or the platform API is down) the command still runs and the plan says
361
+ `NOT VERIFIED` with the reason rather than blocking - `ticket create` itself
362
+ needs neither admin nor that list.
363
+
364
+ Four things can come back different from what you sent, and none of them is the
365
+ CLI dropping a flag - the command says so in its output when it happens:
366
+
367
+ - `--assigned-to` forces the status to `to_do` rather than `unassigned`.
368
+ - An omitted `--due-date` is back-filled from the SLA deadline the server
369
+ computes for that team and ticket type.
370
+ - A routing rule can **move the ticket to another team**. The confirmation email
371
+ then leaves from the destination team's mailbox, not the one you named.
372
+ - A routing rule can set the priority or the assignee - **but only for a flag you
373
+ did NOT pass.** desk-ai keys this off whether the field was present in the
374
+ request (`skip_priority` / `skip_assignment`), so passing `--priority` or
375
+ `--assigned-to` suppresses the corresponding rule for that ticket. That cuts
376
+ both ways: passing `--priority medium` is not the same as omitting it, because
377
+ it silences every Set-Priority rule.
378
+
379
+ Use `--description-file` for anything longer than a sentence. A 50 000-character
380
+ body in `argv` lands in shell history, and the pasted confirm command names the
381
+ file rather than inlining the prose.
382
+ ## Clone a reference org's Desk into another org (`desk clone`, DR-52763)
383
+
384
+ Demo provisioning: copy the reference demo org's Desk into an existing demo org
385
+ so it shows the same tickets, ticket screens and overview, with the reference
386
+ users "playing" the same parts. Everything is **match-or-create, nothing is
387
+ deleted or overwritten**, so the command is safe to re-run on a target that
388
+ already has people, teams and tickets.
389
+
390
+ ```
391
+ # 0. Desk is dark in an org until its feature flags are on. Copy the source
392
+ # org's set: `dr features orgs list <source-org>` and look for `desk`.
393
+ dr features orgs add 41 --flag desk__enable_product_v2 --flag desk__v1_5 \
394
+ --flag desk__ai_composer_v1_5 --flag desk_ai_mfe_enabled --server DEV-1 --account <you>
395
+ # ... redeem the token ...
396
+ dr login-as <target-org-admin> --server DEV-1
397
+ dr desk clone --source-org 32 --target-org 41 --dry-run --server DEV-1 --account <target-org-admin>
398
+ dr desk clone --source-org 32 --target-org 41 --server DEV-1 --account <target-org-admin>
399
+ dr desk clone --source-org 32 --target-org 41 --confirm-token <token> --server DEV-1 --account <target-org-admin>
400
+ ```
401
+
402
+ What it does, in order:
403
+
404
+ 1. **Users** (CLI, IT users API): every active source-org user, minus blocked
405
+ accounts, gets its e-mail moved to the target org's domain
406
+ (`ann@acme.com` -> `ann@demo-acme.com`). An existing target user with that
407
+ address is matched; otherwise the user is created with the same name and
408
+ role (`SUPER_ADMIN` becomes `ADMIN`), **no invite e-mail**. Domains come from
409
+ the organization records (`/api/it/organizations`), falling back to the most
410
+ common user e-mail domain only when the record is not readable;
411
+ `--source-domain` / `--target-domain` override both. **Demo orgs named like `demo.<name>.12.25` get `.com` appended**
412
+ (`ann@demo.brian.12.25.com`): the platform refuses an address whose last
413
+ label is numeric, exactly as it already names those orgs' support users. The
414
+ plan prints an `e-mail domain:` line when this happens; existing users on the
415
+ raw domain still match. Team support addresses follow the same rule. The
416
+ source **support user** is never created: it maps onto the target org's own
417
+ support user, so `support@<target>` joins the same teams it belongs to in the
418
+ reference org — that is the identity CS presents demos with, and without the
419
+ mapping it lands on the onboarding screen.
420
+ 2. **Teams + members** (desk-ai, through the monolith): source teams are matched
421
+ to target teams by name; missing ones are created with the same type and
422
+ the support address moved to the target domain — the platform registers the
423
+ new inbound mailbox and SendGrid domain as the team wizard would. Every
424
+ mapped member is added where missing.
425
+ 3. **Desk data** (desk-ai): tags, SLA, field configs (UDM mappings), routing
426
+ rules, contacts, tickets, messages, attachments, followers, audit history,
427
+ scheduled messages. Tickets already present in a target team are skipped
428
+ with their history, so a re-run never duplicates a conversation.
429
+
430
+ Feature flags are per org and `desk clone` does **not** copy them (they are IT
431
+ flags, not Desk data; an org created by the full `organizations export/import`
432
+ does inherit the source's flags, a hand-made org does not). A target org without them shows no Desk entry at all, or
433
+ the pre-v2 UI, and the clone looks broken when it is not. Enable them with your
434
+ own IT identity **before** the clone — `features orgs add` is a normal
435
+ two-step confirm — and check with `features orgs list <target-org>` that the
436
+ flags now appear. The four above are the set on dev-1 at the time of writing;
437
+ the source org's list is authoritative.
438
+
439
+ Identity: run it as an impersonated **admin of the target org** (`dr login-as`);
440
+ desk-ai accepts the impersonation token (or a `SUPER_ADMIN`) and refuses plain
441
+ org admins, because the call reads another org's data. Your own account must
442
+ have support access to the source org — that is what `can_login_as` uses to
443
+ list its users; "no users visible in source org" means it does not.
444
+
445
+ `--dry-run` skips the confirm gate, creates nothing on either side, and prints
446
+ the user plan plus the team/data report desk-ai computes inside a rolled-back
447
+ transaction. Without `--confirm-token` the real run prints the plan and stops.
448
+
449
+ Not cloned: platform permission groups and data permissions (only role + team
450
+ membership), UDM tables themselves (assumed present and identical in the
451
+ target), attachment blobs that fail to copy (the row keeps the source URL and
452
+ is counted in the report). Users are created without a password: demo personas
453
+ never log in; the presenter impersonates.
454
+
455
+ ## Foot-guns
456
+
457
+ - A `records_invalid` verdict is nearly always one of two customer-side causes:
458
+ a proxied CNAME (on Cloudflare it must be the grey cloud, not orange), or the
459
+ DNS provider appending the domain twice because the full hostname was pasted
460
+ into a field that wanted only the first label. The `next_action` text says so;
461
+ print it rather than re-wording it.
462
+ - `manual_steps` in every response is the human part of onboarding with the
463
+ customer's own values already filled in. It is written server-side so both
464
+ paths say the same thing - relay it verbatim.
465
+ - DNS propagation is usually minutes but can take 48 hours. `records_missing`
466
+ shortly after the customer says they are done is expected, not a failure.
467
+
468
+ ## Help Center
469
+
470
+ Customer-facing setup docs live in the Datarails Help Center under the email /
471
+ Desk folders - search with `dr kb search` before writing anything new for a
472
+ customer.