@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.
- package/README.md +608 -0
- package/dist/chunk-DWMCBGQH.js +242 -0
- package/dist/cli.js +64468 -0
- package/dist/kb-6UWI7Q32.js +21 -0
- package/dist/scopes-map.json +4412 -0
- package/dist/skills/connector-sandbox/SKILL.md +95 -0
- package/dist/skills/datarails-connect-workato/SKILL.md +140 -0
- package/dist/skills/datarails-connectors-onboard-connector/SKILL.md +813 -0
- package/dist/skills/dr-cli/SKILL.md +659 -0
- package/dist/skills/dr-cli/reference/admin.md +796 -0
- package/dist/skills/dr-cli/reference/budget-builder.md +151 -0
- package/dist/skills/dr-cli/reference/calc-vals.md +73 -0
- package/dist/skills/dr-cli/reference/calcmodels.md +76 -0
- package/dist/skills/dr-cli/reference/cash.md +61 -0
- package/dist/skills/dr-cli/reference/dashboards-widgets.md +85 -0
- package/dist/skills/dr-cli/reference/datamappers.md +150 -0
- package/dist/skills/dr-cli/reference/desk.md +472 -0
- package/dist/skills/dr-cli/reference/filebox-documents.md +373 -0
- package/dist/skills/dr-cli/reference/forecast.md +51 -0
- package/dist/skills/dr-cli/reference/integrations.md +57 -0
- package/dist/skills/dr-cli/reference/lookups.md +168 -0
- package/dist/skills/dr-cli/reference/metrics.md +512 -0
- package/dist/skills/dr-cli/reference/multi-product.md +180 -0
- package/dist/skills/dr-cli/reference/reconciliation-mec.md +76 -0
- package/dist/skills/dr-cli/reference/rh-config.md +84 -0
- package/dist/skills/dr-cli/reference/safety.md +143 -0
- package/dist/skills/dr-cli/reference/spend-control.md +206 -0
- package/dist/skills/dr-cli/reference/templates.md +163 -0
- package/dist/skills/dr-cli/reference/workflows.md +137 -0
- package/dist/skills/dr-error-enrichment/SKILL.md +80 -0
- package/dist/skills/dr-error-enrichment/reference/core.md +257 -0
- package/dist/skills/dr-error-enrichment/reference/groundcover.md +84 -0
- package/dist/skills/dr-error-enrichment/reference/jira-bugs.md +219 -0
- package/dist/skills/dr-error-enrichment/reference/safety-backup.md +122 -0
- package/dist/skills/dr-error-enrichment-author/SKILL.md +219 -0
- package/dist/skills/dr-error-enrichment-improve/SKILL.md +218 -0
- package/dist/skills/dr-error-enrichment-maintain/SKILL.md +237 -0
- package/dist/skills/dr-error-enrichment-reconcile/SKILL.md +167 -0
- package/dist/skills/dr-filebox-api-setup/SKILL.md +170 -0
- package/dist/skills/dr-switch-user/SKILL.md +395 -0
- package/dist/skills/dr-validate/SKILL.md +392 -0
- package/dist/skills/dr-validate/report-template.html +950 -0
- package/dist/skills/fx-conversion-matrix/SKILL.md +763 -0
- package/dist/skills/fx-conversion-matrix/scripts/_args.mjs +52 -0
- package/dist/skills/fx-conversion-matrix/scripts/_xlsx.mjs +241 -0
- package/dist/skills/fx-conversion-matrix/scripts/build-matrix.mjs +366 -0
- package/dist/skills/fx-conversion-matrix/scripts/gen-mappers.mjs +171 -0
- package/dist/skills/fx-conversion-matrix/scripts/rates-to-xlsx.mjs +172 -0
- package/dist/skills/fx-onboard/SKILL.md +325 -0
- package/dist/skills/mec-table-validation/SKILL.md +383 -0
- package/dist/skills/plugins.json +24 -0
- package/dist/skills/validate-workato-migration/SKILL.md +167 -0
- package/dist/skills/validate-workato-migration/reference/gotchas.md +16 -0
- package/dist/skills/validate-workato-migration/reference/staging-orgs.md +26 -0
- package/dist/skills/validate-workato-migration/scripts/compare_files.py +255 -0
- package/dist/zendeskKb-JPSHVOPH.js +243 -0
- package/package.json +56 -9
- 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.
|