pipefy-cli 0.3.0a1__py3-none-any.whl

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 (40) hide show
  1. pipefy_cli/__init__.py +7 -0
  2. pipefy_cli/_docs.py +3 -0
  3. pipefy_cli/auth.py +225 -0
  4. pipefy_cli/commands/__init__.py +5 -0
  5. pipefy_cli/commands/_common.py +359 -0
  6. pipefy_cli/commands/agent.py +358 -0
  7. pipefy_cli/commands/ai_automation.py +318 -0
  8. pipefy_cli/commands/attachment.py +122 -0
  9. pipefy_cli/commands/audit.py +63 -0
  10. pipefy_cli/commands/auth.py +519 -0
  11. pipefy_cli/commands/automation.py +529 -0
  12. pipefy_cli/commands/card.py +439 -0
  13. pipefy_cli/commands/email.py +169 -0
  14. pipefy_cli/commands/export.py +61 -0
  15. pipefy_cli/commands/field.py +113 -0
  16. pipefy_cli/commands/field_condition.py +201 -0
  17. pipefy_cli/commands/graphql.py +110 -0
  18. pipefy_cli/commands/introspect.py +110 -0
  19. pipefy_cli/commands/label.py +109 -0
  20. pipefy_cli/commands/member.py +126 -0
  21. pipefy_cli/commands/org.py +60 -0
  22. pipefy_cli/commands/phase.py +264 -0
  23. pipefy_cli/commands/pipe.py +167 -0
  24. pipefy_cli/commands/portal.py +871 -0
  25. pipefy_cli/commands/record.py +219 -0
  26. pipefy_cli/commands/relation.py +173 -0
  27. pipefy_cli/commands/report_org.py +241 -0
  28. pipefy_cli/commands/report_pipe.py +290 -0
  29. pipefy_cli/commands/table.py +151 -0
  30. pipefy_cli/commands/usage.py +100 -0
  31. pipefy_cli/commands/webhook.py +141 -0
  32. pipefy_cli/main.py +132 -0
  33. pipefy_cli/output/__init__.py +8 -0
  34. pipefy_cli/output/json_renderer.py +24 -0
  35. pipefy_cli/output/rich_renderer.py +81 -0
  36. pipefy_cli/settings.py +66 -0
  37. pipefy_cli-0.3.0a1.dist-info/METADATA +104 -0
  38. pipefy_cli-0.3.0a1.dist-info/RECORD +40 -0
  39. pipefy_cli-0.3.0a1.dist-info/WHEEL +4 -0
  40. pipefy_cli-0.3.0a1.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,439 @@
1
+ """Card subcommands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import typer
8
+ from pipefy_sdk import (
9
+ CardSearch,
10
+ CommentInput,
11
+ DeleteCommentInput,
12
+ PipefyClient,
13
+ UpdateCommentInput,
14
+ copy_card_search,
15
+ )
16
+ from pydantic import ValidationError
17
+
18
+ from pipefy_cli.commands._common import (
19
+ _CARDS_PAGE_SIZE_MAX,
20
+ _CARDS_PAGE_SIZE_MIN,
21
+ ID_POSITIONAL_CONTEXT_SETTINGS,
22
+ confirm_destructive,
23
+ format_card_get_transport_query_error,
24
+ parse_json_value,
25
+ resource_id_argument,
26
+ run_cli_command,
27
+ validate_cards_page_size,
28
+ validate_optional_resource_id,
29
+ )
30
+
31
+ card_app = typer.Typer(help="Card operations.", no_args_is_help=True)
32
+ comment_app = typer.Typer(help="Card comments.", no_args_is_help=True)
33
+
34
+
35
+ def _parse_card_search_json(raw: str | None) -> CardSearch | None:
36
+ """Parse ``--search`` into a ``CardSearch`` (unknown keys dropped, MCP-aligned)."""
37
+ if raw is None or raw.strip() == "":
38
+ return None
39
+ parsed = parse_json_value(raw, "--search")
40
+ if not isinstance(parsed, dict):
41
+ raise typer.BadParameter("--search must be a JSON object")
42
+ return copy_card_search(parsed)
43
+
44
+
45
+ def _parse_fields_json(raw: str | None) -> dict[str, Any] | list[dict[str, Any]] | None:
46
+ if raw is None or raw.strip() == "":
47
+ return None
48
+ parsed = parse_json_value(raw, "--fields")
49
+ if isinstance(parsed, dict | list):
50
+ return parsed
51
+ raise typer.BadParameter("--fields must be a JSON object or array")
52
+
53
+
54
+ def _apply_create_card_title_warning(
55
+ result: dict[str, Any], *, requested_title: str | None
56
+ ) -> dict[str, Any]:
57
+ if not requested_title:
58
+ return result
59
+ card_node = (result.get("createCard") or {}).get("card")
60
+ if not isinstance(card_node, dict):
61
+ return result
62
+ if card_node.get("title") == requested_title:
63
+ return result
64
+ warned = dict(result)
65
+ warned["title_warning"] = (
66
+ "Card created but title was not applied as expected "
67
+ f"(response title={card_node.get('title')!r}, requested={requested_title!r})."
68
+ )
69
+ return warned
70
+
71
+
72
+ def _parse_field_updates_json(raw: str | None) -> list[dict[str, Any]] | None:
73
+ if raw is None or raw.strip() == "":
74
+ return None
75
+ parsed = parse_json_value(raw, "--field-updates")
76
+ if not isinstance(parsed, list):
77
+ raise typer.BadParameter("--field-updates must be a JSON array")
78
+ return parsed
79
+
80
+
81
+ def _split_csv_ids(raw: str | None) -> list[str | int] | None:
82
+ if raw is None or not raw.strip():
83
+ return None
84
+ parts = [p.strip() for p in raw.split(",") if p.strip()]
85
+ if not parts:
86
+ return None
87
+ out: list[str | int] = []
88
+ for p in parts:
89
+ if p.isdigit():
90
+ out.append(int(p))
91
+ else:
92
+ out.append(p)
93
+ return out
94
+
95
+
96
+ @card_app.command("get", context_settings=ID_POSITIONAL_CONTEXT_SETTINGS)
97
+ def card_get(
98
+ ctx: typer.Context,
99
+ card_id: str = resource_id_argument(help="Card id."),
100
+ json_out: bool = typer.Option(
101
+ False,
102
+ "--json",
103
+ "-j",
104
+ help="Print machine-readable JSON to stdout.",
105
+ ),
106
+ include_fields: bool = typer.Option(
107
+ False,
108
+ "--include-fields",
109
+ help="Include custom field name/value pairs on the card.",
110
+ ),
111
+ ) -> None:
112
+ """Fetch a card by id."""
113
+
114
+ async def factory(client: PipefyClient):
115
+ return await client.get_card(card_id, include_fields=include_fields)
116
+
117
+ run_cli_command(
118
+ ctx,
119
+ json_out,
120
+ factory,
121
+ format_transport_query_error=format_card_get_transport_query_error,
122
+ )
123
+
124
+
125
+ @card_app.command("list")
126
+ def card_list(
127
+ ctx: typer.Context,
128
+ pipe_id: str = typer.Option(..., "--pipe", help="Pipe id whose cards to load."),
129
+ title: str | None = typer.Option(
130
+ None,
131
+ "--title",
132
+ "-t",
133
+ help="Filter by title substring (merged into search; same shortcut as MCP get_cards).",
134
+ ),
135
+ search_json: str | None = typer.Option(
136
+ None,
137
+ "--search",
138
+ help="Optional JSON object: CardSearch keys (title, assignee_ids, label_ids, ignore_ids, include_done, inbox_emails_read). Unknown keys are ignored.",
139
+ ),
140
+ include_fields: bool = typer.Option(
141
+ False,
142
+ "--include-fields",
143
+ help="Include each card's custom fields (name, value).",
144
+ ),
145
+ first: int | None = typer.Option(
146
+ None,
147
+ "--first",
148
+ help=f"Max cards per page ({_CARDS_PAGE_SIZE_MIN}-{_CARDS_PAGE_SIZE_MAX}).",
149
+ ),
150
+ after: str | None = typer.Option(
151
+ None,
152
+ "--after",
153
+ help="Pagination cursor (pageInfo.endCursor from a previous page).",
154
+ ),
155
+ json_out: bool = typer.Option(
156
+ False,
157
+ "--json",
158
+ "-j",
159
+ help="Print machine-readable JSON to stdout.",
160
+ ),
161
+ ) -> None:
162
+ """List cards in a pipe (get_cards): browse, filter, and paginate.
163
+
164
+ Use ``card find`` when matching a single custom field value (find_cards).
165
+ """
166
+
167
+ search_from_json = _parse_card_search_json(search_json)
168
+ merged: CardSearch = dict(search_from_json) if search_from_json is not None else {}
169
+ if title is not None and title.strip():
170
+ merged["title"] = title.strip()
171
+ effective_search: CardSearch | None = merged if merged else None
172
+ first_validated = validate_cards_page_size(first)
173
+
174
+ async def factory(client: PipefyClient):
175
+ return await client.get_cards(
176
+ pipe_id,
177
+ effective_search,
178
+ include_fields=include_fields,
179
+ first=first_validated,
180
+ after=after,
181
+ )
182
+
183
+ run_cli_command(ctx, json_out, factory)
184
+
185
+
186
+ @card_app.command("find")
187
+ def card_find(
188
+ ctx: typer.Context,
189
+ pipe_id: str = typer.Option(..., "--pipe", help="Pipe id to search in."),
190
+ field_id: str = typer.Option(
191
+ ...,
192
+ "--field",
193
+ help="Field id (slug) from start form or phase fields.",
194
+ ),
195
+ field_value: str = typer.Option(
196
+ ...,
197
+ "--value",
198
+ help="Value to match for that field.",
199
+ ),
200
+ include_fields: bool = typer.Option(
201
+ False,
202
+ "--include-fields",
203
+ help="Include each card's custom fields.",
204
+ ),
205
+ first: int | None = typer.Option(
206
+ None,
207
+ "--first",
208
+ help="Max cards per page.",
209
+ ),
210
+ after: str | None = typer.Option(
211
+ None,
212
+ "--after",
213
+ help="Pagination cursor (pageInfo.endCursor).",
214
+ ),
215
+ json_out: bool = typer.Option(
216
+ False,
217
+ "--json",
218
+ "-j",
219
+ help="Print machine-readable JSON to stdout.",
220
+ ),
221
+ ) -> None:
222
+ """Find cards in a pipe where a custom field equals a value."""
223
+
224
+ async def factory(client: PipefyClient):
225
+ return await client.find_cards(
226
+ pipe_id,
227
+ field_id,
228
+ field_value,
229
+ include_fields=include_fields,
230
+ first=first,
231
+ after=after,
232
+ )
233
+
234
+ run_cli_command(ctx, json_out, factory)
235
+
236
+
237
+ @card_app.command("create", context_settings=ID_POSITIONAL_CONTEXT_SETTINGS)
238
+ def card_create(
239
+ ctx: typer.Context,
240
+ pipe_id: str = resource_id_argument(help="Pipe id."),
241
+ title: str | None = typer.Option(
242
+ None,
243
+ "--title",
244
+ "-t",
245
+ help="Optional title (sent on CreateCardInput when set).",
246
+ ),
247
+ phase_id: str | None = typer.Option(
248
+ None,
249
+ "--phase-id",
250
+ help=(
251
+ "Target phase id (CreateCardInput.phase_id). "
252
+ "Creates the card in that phase instead of the start form."
253
+ ),
254
+ ),
255
+ fields_json: str | None = typer.Option(
256
+ None,
257
+ "--fields",
258
+ help="JSON object or array: field values for createCard.",
259
+ ),
260
+ json_out: bool = typer.Option(
261
+ False,
262
+ "--json",
263
+ "-j",
264
+ help="Print machine-readable JSON to stdout.",
265
+ ),
266
+ ) -> None:
267
+ """Create a card in a pipe (start-form or phase fields via --fields JSON when required)."""
268
+
269
+ fields = _parse_fields_json(fields_json)
270
+ phase_id = validate_optional_resource_id(phase_id, "phase_id")
271
+
272
+ async def factory(client: PipefyClient):
273
+ payload = fields if fields is not None else {}
274
+ create_kwargs: dict[str, Any] = {}
275
+ if phase_id is not None:
276
+ create_kwargs["phase_id"] = phase_id
277
+ if title:
278
+ create_kwargs["title"] = title
279
+ result = await client.create_card(pipe_id, payload, **create_kwargs)
280
+ return _apply_create_card_title_warning(result, requested_title=title)
281
+
282
+ run_cli_command(ctx, json_out, factory)
283
+
284
+
285
+ @card_app.command("update", context_settings=ID_POSITIONAL_CONTEXT_SETTINGS)
286
+ def card_update(
287
+ ctx: typer.Context,
288
+ card_id: str = resource_id_argument(help="Card id."),
289
+ title: str | None = typer.Option(None, "--title", "-t"),
290
+ due_date: str | None = typer.Option(None, "--due-date"),
291
+ assignee_ids: str | None = typer.Option(
292
+ None,
293
+ "--assignee-ids",
294
+ help="Comma-separated assignee user ids.",
295
+ ),
296
+ label_ids: str | None = typer.Option(
297
+ None,
298
+ "--label-ids",
299
+ help="Comma-separated label ids.",
300
+ ),
301
+ field_updates_json: str | None = typer.Option(
302
+ None,
303
+ "--field-updates",
304
+ help=(
305
+ "JSON array of field update objects for updateFieldsValues. "
306
+ 'Each object: {"field_id" (or "fieldId"): "<slug>", "value": <v>}.'
307
+ ),
308
+ ),
309
+ json_out: bool = typer.Option(False, "--json", "-j"),
310
+ ) -> None:
311
+ """Update a card (title, assignees, labels, due date, and/or field updates)."""
312
+
313
+ field_updates = _parse_field_updates_json(field_updates_json)
314
+
315
+ async def factory(client: PipefyClient):
316
+ return await client.update_card(
317
+ card_id,
318
+ title=title,
319
+ assignee_ids=_split_csv_ids(assignee_ids),
320
+ label_ids=_split_csv_ids(label_ids),
321
+ due_date=due_date,
322
+ field_updates=field_updates,
323
+ )
324
+
325
+ run_cli_command(ctx, json_out, factory)
326
+
327
+
328
+ @card_app.command("delete", context_settings=ID_POSITIONAL_CONTEXT_SETTINGS)
329
+ def card_delete(
330
+ ctx: typer.Context,
331
+ card_id: str = resource_id_argument(help="Card id."),
332
+ yes: bool = typer.Option(
333
+ False,
334
+ "--yes",
335
+ "-y",
336
+ help="Skip confirmation prompt.",
337
+ ),
338
+ json_out: bool = typer.Option(False, "--json", "-j"),
339
+ ) -> None:
340
+ """Delete a card permanently."""
341
+
342
+ confirm_destructive(yes=yes, description=f"card {card_id}")
343
+
344
+ async def factory(client: PipefyClient):
345
+ return await client.delete_card(card_id)
346
+
347
+ run_cli_command(ctx, json_out, factory)
348
+
349
+
350
+ @card_app.command("move", context_settings=ID_POSITIONAL_CONTEXT_SETTINGS)
351
+ def card_move(
352
+ ctx: typer.Context,
353
+ card_id: str = resource_id_argument(help="Card id."),
354
+ phase_id: str = typer.Option(
355
+ ...,
356
+ "--phase",
357
+ help="Destination phase id.",
358
+ ),
359
+ json_out: bool = typer.Option(False, "--json", "-j"),
360
+ ) -> None:
361
+ """Move a card to another phase."""
362
+
363
+ async def factory(client: PipefyClient):
364
+ return await client.move_card_to_phase(card_id, phase_id)
365
+
366
+ run_cli_command(ctx, json_out, factory)
367
+
368
+
369
+ @comment_app.command("add", context_settings=ID_POSITIONAL_CONTEXT_SETTINGS)
370
+ def card_comment_add(
371
+ ctx: typer.Context,
372
+ card_id: str = resource_id_argument(help="Card id."),
373
+ text: str = typer.Argument(..., help="Comment body (1-1000 characters)."),
374
+ json_out: bool = typer.Option(False, "--json", "-j"),
375
+ ) -> None:
376
+ """Add a text comment to a card."""
377
+
378
+ try:
379
+ validated = CommentInput(card_id=card_id, text=text)
380
+ except ValidationError as exc:
381
+ typer.echo(str(exc), err=True)
382
+ raise typer.Exit(2) from exc
383
+
384
+ async def factory(client: PipefyClient):
385
+ return await client.add_card_comment(validated.card_id, validated.text)
386
+
387
+ run_cli_command(ctx, json_out, factory)
388
+
389
+
390
+ @comment_app.command("update", context_settings=ID_POSITIONAL_CONTEXT_SETTINGS)
391
+ def card_comment_update(
392
+ ctx: typer.Context,
393
+ comment_id: str = resource_id_argument(help="Comment id."),
394
+ text: str = typer.Argument(..., help="New comment text."),
395
+ json_out: bool = typer.Option(False, "--json", "-j"),
396
+ ) -> None:
397
+ """Update an existing comment by id."""
398
+
399
+ try:
400
+ validated = UpdateCommentInput(comment_id=comment_id, text=text)
401
+ except ValidationError as exc:
402
+ typer.echo(str(exc), err=True)
403
+ raise typer.Exit(2) from exc
404
+
405
+ async def factory(client: PipefyClient):
406
+ return await client.update_comment(validated.comment_id, validated.text)
407
+
408
+ run_cli_command(ctx, json_out, factory)
409
+
410
+
411
+ @comment_app.command("delete", context_settings=ID_POSITIONAL_CONTEXT_SETTINGS)
412
+ def card_comment_delete(
413
+ ctx: typer.Context,
414
+ comment_id: str = resource_id_argument(help="Comment id."),
415
+ yes: bool = typer.Option(
416
+ False,
417
+ "--yes",
418
+ "-y",
419
+ help="Skip confirmation prompt.",
420
+ ),
421
+ json_out: bool = typer.Option(False, "--json", "-j"),
422
+ ) -> None:
423
+ """Delete a comment by id."""
424
+
425
+ try:
426
+ validated = DeleteCommentInput(comment_id=comment_id)
427
+ except ValidationError as exc:
428
+ typer.echo(str(exc), err=True)
429
+ raise typer.Exit(2) from exc
430
+
431
+ confirm_destructive(yes=yes, description=f"comment {validated.comment_id}")
432
+
433
+ async def factory(client: PipefyClient):
434
+ return await client.delete_comment(validated.comment_id)
435
+
436
+ run_cli_command(ctx, json_out, factory)
437
+
438
+
439
+ card_app.add_typer(comment_app, name="comment")
@@ -0,0 +1,169 @@
1
+ """Card inbox emails and org email templates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+ from pipefy_sdk import PipefyClient
7
+
8
+ from pipefy_cli.commands._common import parse_json_object, run_cli_command
9
+
10
+ email_app = typer.Typer(help="Inbox emails and email templates.", no_args_is_help=True)
11
+ inbox_app = typer.Typer(help="Card inbox (sent/received).", no_args_is_help=True)
12
+ template_app = typer.Typer(
13
+ help="Email templates bound to a pipe or table (repo).", no_args_is_help=True
14
+ )
15
+
16
+
17
+ @inbox_app.command("list")
18
+ def email_inbox_list(
19
+ ctx: typer.Context,
20
+ card_id: str = typer.Option(..., "--card", help="Card id with inbox enabled."),
21
+ email_type: str | None = typer.Option(
22
+ None,
23
+ "--type",
24
+ help="Optional filter: sent or received.",
25
+ ),
26
+ json_out: bool = typer.Option(
27
+ False,
28
+ "--json",
29
+ "-j",
30
+ help="Print machine-readable JSON to stdout.",
31
+ ),
32
+ ) -> None:
33
+ """List inbox emails for a card (``get_card_inbox_emails``)."""
34
+
35
+ async def factory(client: PipefyClient):
36
+ return await client.get_card_inbox_emails(card_id, email_type=email_type)
37
+
38
+ run_cli_command(ctx, json_out, factory)
39
+
40
+
41
+ @inbox_app.command("send")
42
+ def email_inbox_send(
43
+ ctx: typer.Context,
44
+ card_id: str = typer.Option(..., "--card", help="Card id."),
45
+ to: str = typer.Option(
46
+ ...,
47
+ "--to",
48
+ help="Recipient emails (comma-separated).",
49
+ ),
50
+ subject: str = typer.Option(..., "--subject", "-s", help="Email subject."),
51
+ body: str = typer.Option(..., "--body", "-b", help="Plain-text body."),
52
+ from_email: str = typer.Option(
53
+ ...,
54
+ "--from-email",
55
+ help="Sender email address (required by API).",
56
+ ),
57
+ extra: str | None = typer.Option(
58
+ None,
59
+ "--extra",
60
+ help="Optional JSON object: extra CreateAndSendInboxEmailInput fields.",
61
+ ),
62
+ json_out: bool = typer.Option(
63
+ False,
64
+ "--json",
65
+ "-j",
66
+ help="Print machine-readable JSON to stdout.",
67
+ ),
68
+ ) -> None:
69
+ """Send an inbox email from a card (``send_inbox_email``)."""
70
+ recipients = [e.strip() for e in to.split(",") if e.strip()]
71
+ if not recipients:
72
+ raise typer.BadParameter("--to must list at least one email.")
73
+ extra_obj = parse_json_object(extra, "--extra") or {}
74
+
75
+ async def factory(client: PipefyClient):
76
+ return await client.send_inbox_email(
77
+ card_id,
78
+ recipients,
79
+ subject,
80
+ body,
81
+ from_=from_email,
82
+ **extra_obj,
83
+ )
84
+
85
+ run_cli_command(ctx, json_out, factory)
86
+
87
+
88
+ @template_app.command("list")
89
+ def email_template_list(
90
+ ctx: typer.Context,
91
+ repo_id: str = typer.Option(
92
+ ...,
93
+ "--repo",
94
+ help="Pipe or table id whose templates to list.",
95
+ ),
96
+ name: str | None = typer.Option(
97
+ None,
98
+ "--name",
99
+ help="Optional substring filter on template name.",
100
+ ),
101
+ first: int = typer.Option(50, "--first", help="Max templates to return."),
102
+ json_out: bool = typer.Option(
103
+ False,
104
+ "--json",
105
+ "-j",
106
+ help="Print machine-readable JSON to stdout.",
107
+ ),
108
+ ) -> None:
109
+ """List email templates for a repo (``get_email_templates``)."""
110
+
111
+ async def factory(client: PipefyClient):
112
+ return await client.get_email_templates(
113
+ repo_id, filter_by_name=name, first=first
114
+ )
115
+
116
+ run_cli_command(ctx, json_out, factory)
117
+
118
+
119
+ @template_app.command("send")
120
+ def email_template_send(
121
+ ctx: typer.Context,
122
+ card_id: str = typer.Option(..., "--card", help="Card id (inbox sender context)."),
123
+ template_id: str = typer.Option(
124
+ ...,
125
+ "--template",
126
+ help="Email template id.",
127
+ ),
128
+ to: str | None = typer.Option(
129
+ None,
130
+ "--to",
131
+ help="Optional comma-separated override recipients.",
132
+ ),
133
+ from_email: str | None = typer.Option(
134
+ None,
135
+ "--from-email",
136
+ help="Optional sender override.",
137
+ ),
138
+ extra: str | None = typer.Option(
139
+ None,
140
+ "--extra",
141
+ help="Optional JSON object: extra send fields (cc, bcc, repoId, etc.).",
142
+ ),
143
+ json_out: bool = typer.Option(
144
+ False,
145
+ "--json",
146
+ "-j",
147
+ help="Print machine-readable JSON to stdout.",
148
+ ),
149
+ ) -> None:
150
+ """Send using an email template (``send_email_with_template``)."""
151
+ to_list: list[str] | None = None
152
+ if to is not None and to.strip():
153
+ to_list = [e.strip() for e in to.split(",") if e.strip()]
154
+ extra_obj = parse_json_object(extra, "--extra") or {}
155
+
156
+ async def factory(client: PipefyClient):
157
+ return await client.send_email_with_template(
158
+ card_id,
159
+ template_id,
160
+ to=to_list,
161
+ from_=from_email,
162
+ **extra_obj,
163
+ )
164
+
165
+ run_cli_command(ctx, json_out, factory)
166
+
167
+
168
+ email_app.add_typer(inbox_app, name="inbox")
169
+ email_app.add_typer(template_app, name="template")
@@ -0,0 +1,61 @@
1
+ """Automation job exports."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+ from pipefy_sdk import PipefyClient
7
+
8
+ from pipefy_cli.commands._common import (
9
+ ID_POSITIONAL_CONTEXT_SETTINGS,
10
+ resource_id_argument,
11
+ run_cli_command,
12
+ )
13
+
14
+ export_app = typer.Typer(
15
+ help="Bulk exports (automation jobs).",
16
+ no_args_is_help=True,
17
+ )
18
+
19
+
20
+ @export_app.command("automation-jobs")
21
+ def export_automation_jobs(
22
+ ctx: typer.Context,
23
+ organization: str = typer.Option(
24
+ ..., "--organization", "--org", help="Organization id."
25
+ ),
26
+ period: str = typer.Option(
27
+ ...,
28
+ "--period",
29
+ help="current_month | last_month | last_3_months",
30
+ ),
31
+ json_out: bool = typer.Option(
32
+ False, "--json", "-j", help="Print machine-readable JSON to stdout."
33
+ ),
34
+ ) -> None:
35
+ """Queue automation jobs export (``export_automation_jobs``)."""
36
+
37
+ async def factory(client: PipefyClient):
38
+ return await client.export_automation_jobs(organization, period)
39
+
40
+ run_cli_command(ctx, json_out, factory)
41
+
42
+
43
+ @export_app.command(
44
+ "automation-jobs-csv", context_settings=ID_POSITIONAL_CONTEXT_SETTINGS
45
+ )
46
+ def export_automation_jobs_csv(
47
+ ctx: typer.Context,
48
+ export_id: str = resource_id_argument(help="Export id after status is finished."),
49
+ json_out: bool = typer.Option(
50
+ False,
51
+ "--json",
52
+ "-j",
53
+ help="Print JSON envelope with CSV text; default is Rich table output.",
54
+ ),
55
+ ) -> None:
56
+ """Download finished export as CSV text (``get_automation_jobs_export_csv``)."""
57
+
58
+ async def factory(client: PipefyClient):
59
+ return await client.get_automation_jobs_export_csv(export_id)
60
+
61
+ run_cli_command(ctx, json_out, factory)