qodev-apollo-cli 1.4.0__tar.gz → 1.5.0__tar.gz

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 (55) hide show
  1. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/CHANGELOG.md +12 -0
  2. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/PKG-INFO +5 -4
  3. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/README.md +2 -1
  4. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/pyproject.toml +2 -2
  5. qodev_apollo_cli-1.5.0/src/apollo_cli/commands/tasks.py +144 -0
  6. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/tests/test_commands.py +80 -0
  7. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/uv.lock +2 -2
  8. qodev_apollo_cli-1.4.0/src/apollo_cli/commands/tasks.py +0 -80
  9. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/.github/workflows/ci.yml +0 -0
  10. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/.github/workflows/publish.yml +0 -0
  11. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/.gitignore +0 -0
  12. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/LICENSE +0 -0
  13. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/__init__.py +0 -0
  14. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/__main__.py +0 -0
  15. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/app.py +0 -0
  16. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/commands/__init__.py +0 -0
  17. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/commands/accounts.py +0 -0
  18. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/commands/calls.py +0 -0
  19. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/commands/contacts.py +0 -0
  20. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/commands/conversations.py +0 -0
  21. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/commands/custom_fields.py +0 -0
  22. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/commands/deals.py +0 -0
  23. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/commands/emails.py +0 -0
  24. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/commands/enrich.py +0 -0
  25. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/commands/install.py +0 -0
  26. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/commands/jobs.py +0 -0
  27. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/commands/notes.py +0 -0
  28. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/commands/people.py +0 -0
  29. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/commands/pipelines.py +0 -0
  30. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/commands/usage.py +0 -0
  31. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/context.py +0 -0
  32. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/formatters/__init__.py +0 -0
  33. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/formatters/accounts.py +0 -0
  34. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/formatters/contacts.py +0 -0
  35. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/formatters/conversations.py +0 -0
  36. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/formatters/deals.py +0 -0
  37. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/formatters/generic.py +0 -0
  38. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/formatters/people.py +0 -0
  39. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/help_reference.py +0 -0
  40. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/linkedin.py +0 -0
  41. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/output.py +0 -0
  42. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/skills/SKILL.md +0 -0
  43. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/skills/__init__.py +0 -0
  44. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/skills/references/__init__.py +0 -0
  45. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/skills/references/account-workflows.md +0 -0
  46. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/skills/references/contact-workflows.md +0 -0
  47. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/skills/references/deal-workflows.md +0 -0
  48. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/src/apollo_cli/util.py +0 -0
  49. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/tests/conftest.py +0 -0
  50. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/tests/test_context.py +0 -0
  51. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/tests/test_conversations_formatter.py +0 -0
  52. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/tests/test_install.py +0 -0
  53. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/tests/test_linkedin.py +0 -0
  54. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/tests/test_output.py +0 -0
  55. {qodev_apollo_cli-1.4.0 → qodev_apollo_cli-1.5.0}/tests/test_util.py +0 -0
@@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.5.0] - 2026-08-31
10
+
11
+ ### Added
12
+
13
+ - `tasks create` accepts `--user-id`, `--due-at` and `--title`. Apollo rejects task
14
+ creation with `"Invalid user or creator id"` unless an owner is passed, and there
15
+ was previously no way to set a due date from the CLI at all.
16
+ - `tasks connect --contact-id` queues a LinkedIn connection request, **by default
17
+ without a message**. On a `linkedin_step_connect` task the note travels with the
18
+ invitation, so `--note` is omitted unless you pass one; internal context goes in
19
+ `--title`, which the contact never sees.
20
+
9
21
  ## [1.4.0] - 2026-07-10
10
22
 
11
23
  ### Fixed
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: qodev-apollo-cli
3
- Version: 1.4.0
3
+ Version: 1.5.0
4
4
  Summary: Agent-friendly CLI for the Apollo API
5
5
  Project-URL: Homepage, https://github.com/qodevai/apollo-cli
6
6
  Project-URL: Repository, https://github.com/qodevai/apollo-cli
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.13
19
19
  Classifier: Typing :: Typed
20
20
  Requires-Python: >=3.11
21
21
  Requires-Dist: cyclopts>=3.0
22
- Requires-Dist: qodev-apollo-api>=0.5.0
22
+ Requires-Dist: qodev-apollo-api>=0.6.0
23
23
  Requires-Dist: rich>=13.0
24
24
  Provides-Extra: dev
25
25
  Requires-Dist: mypy>=1.13.0; extra == 'dev'
@@ -111,7 +111,8 @@ $ qodev-apollo-cli usage
111
111
  | **notes** | `search` | Search notes (`--contact-id`, `--account-id`, `--opportunity-id`) |
112
112
  | | `create` | Create a note (`--contact-ids`, `--account-ids`, `--opportunity-ids`, `--content`) |
113
113
  | **tasks** | `search` | Search tasks (`--type`, `--status`) |
114
- | | `create` | Create a task (`--contact-ids`, `--note`, `--due-at`) |
114
+ | | `create` | Create a task (`--contact-ids`, `--note`, `--user-id`, `--due-at`, `--title`) |
115
+ | | `connect` | Queue a LinkedIn connection request, no message by default (`--contact-id`, `--note`, `--title`) |
115
116
  | **calls** | `search` | Search call activities |
116
117
  | **conversations** | `search` | Search recorded conversations (`--query`) |
117
118
  | | `get` | Get a conversation with transcript and AI summary |
@@ -80,7 +80,8 @@ $ qodev-apollo-cli usage
80
80
  | **notes** | `search` | Search notes (`--contact-id`, `--account-id`, `--opportunity-id`) |
81
81
  | | `create` | Create a note (`--contact-ids`, `--account-ids`, `--opportunity-ids`, `--content`) |
82
82
  | **tasks** | `search` | Search tasks (`--type`, `--status`) |
83
- | | `create` | Create a task (`--contact-ids`, `--note`, `--due-at`) |
83
+ | | `create` | Create a task (`--contact-ids`, `--note`, `--user-id`, `--due-at`, `--title`) |
84
+ | | `connect` | Queue a LinkedIn connection request, no message by default (`--contact-id`, `--note`, `--title`) |
84
85
  | **calls** | `search` | Search call activities |
85
86
  | **conversations** | `search` | Search recorded conversations (`--query`) |
86
87
  | | `get` | Get a conversation with transcript and AI summary |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "qodev-apollo-cli"
3
- version = "1.4.0"
3
+ version = "1.5.0"
4
4
  description = "Agent-friendly CLI for the Apollo API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -20,7 +20,7 @@ classifiers = [
20
20
  dependencies = [
21
21
  "cyclopts>=3.0",
22
22
  "rich>=13.0",
23
- "qodev-apollo-api>=0.5.0",
23
+ "qodev-apollo-api>=0.6.0",
24
24
  ]
25
25
 
26
26
  [project.optional-dependencies]
@@ -0,0 +1,144 @@
1
+ """Tasks command group."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated
6
+
7
+ from cyclopts import App, Parameter
8
+
9
+ from apollo_cli.context import ctx
10
+ from apollo_cli.formatters.generic import list_table
11
+ from apollo_cli.output import output, output_list
12
+ from apollo_cli.util import parse_comma_list
13
+
14
+ tasks_app = App(name="tasks", help="Task management.")
15
+
16
+
17
+ def _task_extras(*, user_id: str | None, due_at: str | None, title: str | None) -> dict[str, str]:
18
+ """Collect the optional task fields, omitting the ones left unset."""
19
+ extra: dict[str, str] = {}
20
+ if user_id is not None:
21
+ extra["user_id"] = user_id
22
+ if due_at is not None:
23
+ extra["due_at"] = due_at
24
+ if title is not None:
25
+ extra["title"] = title
26
+ return extra
27
+
28
+
29
+ TASK_LIST_COLUMNS = [
30
+ ("ID", "id"),
31
+ ("Subject", "subject"),
32
+ ("Type", "type"),
33
+ ("Priority", "priority"),
34
+ ("Status", "status"),
35
+ ("Due", "due_at"),
36
+ ]
37
+
38
+
39
+ @tasks_app.command
40
+ async def search(
41
+ *,
42
+ type: Annotated[str | None, Parameter(name="--type", help="Filter by task type (call, action_item, etc.)")] = None,
43
+ contact_id: Annotated[str | None, Parameter(name="--contact-id", help="Filter by contact ID")] = None,
44
+ ) -> None:
45
+ """Search tasks."""
46
+ filters: dict = {}
47
+ if type:
48
+ filters["task_type_cds"] = [type]
49
+ if contact_id:
50
+ filters["contact_ids"] = [contact_id]
51
+
52
+ async with ctx.client() as client:
53
+ result = await client.search_tasks(page=ctx.page, limit=ctx.limit, **filters)
54
+
55
+ output_list(
56
+ items=result.items,
57
+ total=result.total,
58
+ page=result.page,
59
+ limit=ctx.limit,
60
+ ctx=ctx,
61
+ format_fn=lambda items, **kw: list_table(items, TASK_LIST_COLUMNS, title="Tasks", **kw),
62
+ resource_name="Tasks",
63
+ )
64
+
65
+
66
+ @tasks_app.command
67
+ async def create(
68
+ *,
69
+ contact_ids: Annotated[str, Parameter(name="--contact-ids", help="Comma-separated contact IDs")],
70
+ note: Annotated[str | None, Parameter(name="--note", help="Task description")] = None,
71
+ type: Annotated[str, Parameter(name="--type", help="Task type")] = "action_item",
72
+ priority: Annotated[str, Parameter(name="--priority", help="Priority (high, medium, low)")] = "medium",
73
+ user_id: Annotated[
74
+ str | None,
75
+ Parameter(name="--user-id", help="Task owner. Apollo rejects creation without a valid owner"),
76
+ ] = None,
77
+ due_at: Annotated[
78
+ str | None, Parameter(name="--due-at", help="Due date, ISO 8601 (e.g. 2026-09-22T08:00:00Z)")
79
+ ] = None,
80
+ title: Annotated[
81
+ str | None, Parameter(name="--title", help="Task title shown in Apollo (internal, never sent)")
82
+ ] = None,
83
+ ) -> None:
84
+ """Create a new task."""
85
+ ids = parse_comma_list(contact_ids)
86
+ extra = _task_extras(user_id=user_id, due_at=due_at, title=title)
87
+
88
+ async with ctx.client() as client:
89
+ result = await client.create_task(contact_ids=ids, note=note, type=type, priority=priority, **extra)
90
+
91
+ output(result, ctx=ctx)
92
+
93
+
94
+ @tasks_app.command
95
+ async def connect(
96
+ *,
97
+ contact_id: Annotated[str, Parameter(name="--contact-id", help="Contact to send the request to")],
98
+ note: Annotated[
99
+ str | None,
100
+ Parameter(name="--note", help="Text sent WITH the invitation. Omitted (default) = no message"),
101
+ ] = None,
102
+ user_id: Annotated[
103
+ str | None,
104
+ Parameter(name="--user-id", help="Task owner. Apollo rejects creation without a valid owner"),
105
+ ] = None,
106
+ due_at: Annotated[
107
+ str | None, Parameter(name="--due-at", help="Due date, ISO 8601 (e.g. 2026-09-22T08:00:00Z)")
108
+ ] = None,
109
+ title: Annotated[
110
+ str | None, Parameter(name="--title", help="Task title shown in Apollo (internal, never sent)")
111
+ ] = None,
112
+ priority: Annotated[str, Parameter(name="--priority", help="Priority (high, medium, low)")] = "high",
113
+ ) -> None:
114
+ """Queue a LinkedIn connection request, by default without a message.
115
+
116
+ On a linkedin_step_connect task the note travels with the invitation, so
117
+ --note is omitted by default and anything you pass there is seen by the
118
+ recipient. Put internal context in --title; the contact never sees it.
119
+ """
120
+ extra = _task_extras(user_id=user_id, due_at=due_at, title=title)
121
+
122
+ async with ctx.client() as client:
123
+ result = await client.create_task(
124
+ contact_ids=[contact_id],
125
+ note=note,
126
+ type="linkedin_step_connect",
127
+ priority=priority,
128
+ **extra,
129
+ )
130
+
131
+ output(result, ctx=ctx)
132
+
133
+
134
+ @tasks_app.command
135
+ async def complete(
136
+ id: Annotated[str, Parameter(help="Task ID")],
137
+ *,
138
+ note: Annotated[str | None, Parameter(name="--note", help="Completion note")] = None,
139
+ ) -> None:
140
+ """Mark a task as completed."""
141
+ async with ctx.client() as client:
142
+ result = await client.complete_task(id, note=note)
143
+
144
+ output(result, ctx=ctx)
@@ -726,3 +726,83 @@ class TestNotesCommands:
726
726
  assert call_kwargs["contact_ids"] == ["c1"]
727
727
  assert call_kwargs["account_ids"] == ["a1"]
728
728
  assert call_kwargs["opportunity_ids"] == ["d1"]
729
+
730
+
731
+ class TestTasksCommands:
732
+ @pytest.mark.asyncio
733
+ async def test_create_passes_owner_due_and_title(self) -> None:
734
+ """--user-id / --due-at / --title reach the client."""
735
+ mock_client = MagicMock()
736
+ mock_client.create_task = AsyncMock(return_value={"id": "t1"})
737
+
738
+ _ctx.ctx.configure(json_mode=True, api_key="test-key", limit=25, page=1)
739
+
740
+ with patch.object(_ctx.ctx, "client", return_value=MockAsyncContextManager(mock_client)):
741
+ from apollo_cli.commands.tasks import create
742
+
743
+ await create(
744
+ contact_ids="c1",
745
+ note="Follow up",
746
+ user_id="u1",
747
+ due_at="2026-09-22T08:00:00Z",
748
+ title="Internal title",
749
+ )
750
+
751
+ kwargs = mock_client.create_task.call_args.kwargs
752
+ assert kwargs["user_id"] == "u1"
753
+ assert kwargs["due_at"] == "2026-09-22T08:00:00Z"
754
+ assert kwargs["title"] == "Internal title"
755
+
756
+ @pytest.mark.asyncio
757
+ async def test_create_omits_unset_optionals(self) -> None:
758
+ """Optional task fields stay out of the call when not given."""
759
+ mock_client = MagicMock()
760
+ mock_client.create_task = AsyncMock(return_value={"id": "t2"})
761
+
762
+ _ctx.ctx.configure(json_mode=True, api_key="test-key", limit=25, page=1)
763
+
764
+ with patch.object(_ctx.ctx, "client", return_value=MockAsyncContextManager(mock_client)):
765
+ from apollo_cli.commands.tasks import create
766
+
767
+ await create(contact_ids="c1")
768
+
769
+ kwargs = mock_client.create_task.call_args.kwargs
770
+ assert kwargs["note"] is None
771
+ assert "user_id" not in kwargs
772
+ assert "due_at" not in kwargs
773
+ assert "title" not in kwargs
774
+
775
+ @pytest.mark.asyncio
776
+ async def test_connect_sends_empty_note(self) -> None:
777
+ """A connection request carries no message and defaults to high priority."""
778
+ mock_client = MagicMock()
779
+ mock_client.create_task = AsyncMock(return_value={"id": "t3"})
780
+
781
+ _ctx.ctx.configure(json_mode=True, api_key="test-key", limit=25, page=1)
782
+
783
+ with patch.object(_ctx.ctx, "client", return_value=MockAsyncContextManager(mock_client)):
784
+ from apollo_cli.commands.tasks import connect
785
+
786
+ await connect(contact_id="c1", user_id="u1", title="Connect Jane Doe")
787
+
788
+ kwargs = mock_client.create_task.call_args.kwargs
789
+ assert kwargs["contact_ids"] == ["c1"]
790
+ assert kwargs["note"] is None
791
+ assert kwargs["type"] == "linkedin_step_connect"
792
+ assert kwargs["priority"] == "high"
793
+ assert kwargs["title"] == "Connect Jane Doe"
794
+
795
+ @pytest.mark.asyncio
796
+ async def test_connect_forwards_a_note_when_given(self) -> None:
797
+ """--note is supported; it travels with the invitation when supplied."""
798
+ mock_client = MagicMock()
799
+ mock_client.create_task = AsyncMock(return_value={"id": "t4"})
800
+
801
+ _ctx.ctx.configure(json_mode=True, api_key="test-key", limit=25, page=1)
802
+
803
+ with patch.object(_ctx.ctx, "client", return_value=MockAsyncContextManager(mock_client)):
804
+ from apollo_cli.commands.tasks import connect
805
+
806
+ await connect(contact_id="c1", note="Hi Jane, ...")
807
+
808
+ assert mock_client.create_task.call_args.kwargs["note"] == "Hi Jane, ..."
@@ -472,7 +472,7 @@ wheels = [
472
472
 
473
473
  [[package]]
474
474
  name = "qodev-apollo-api"
475
- version = "0.5.0"
475
+ version = "0.6.0"
476
476
  source = { directory = "../apollo-api" }
477
477
  dependencies = [
478
478
  { name = "httpx" },
@@ -494,7 +494,7 @@ provides-extras = ["dev"]
494
494
 
495
495
  [[package]]
496
496
  name = "qodev-apollo-cli"
497
- version = "1.4.0"
497
+ version = "1.5.0"
498
498
  source = { editable = "." }
499
499
  dependencies = [
500
500
  { name = "cyclopts" },
@@ -1,80 +0,0 @@
1
- """Tasks command group."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import Annotated
6
-
7
- from cyclopts import App, Parameter
8
-
9
- from apollo_cli.context import ctx
10
- from apollo_cli.formatters.generic import list_table
11
- from apollo_cli.output import output, output_list
12
- from apollo_cli.util import parse_comma_list
13
-
14
- tasks_app = App(name="tasks", help="Task management.")
15
-
16
- TASK_LIST_COLUMNS = [
17
- ("ID", "id"),
18
- ("Subject", "subject"),
19
- ("Type", "type"),
20
- ("Priority", "priority"),
21
- ("Status", "status"),
22
- ("Due", "due_at"),
23
- ]
24
-
25
-
26
- @tasks_app.command
27
- async def search(
28
- *,
29
- type: Annotated[str | None, Parameter(name="--type", help="Filter by task type (call, action_item, etc.)")] = None,
30
- contact_id: Annotated[str | None, Parameter(name="--contact-id", help="Filter by contact ID")] = None,
31
- ) -> None:
32
- """Search tasks."""
33
- filters: dict = {}
34
- if type:
35
- filters["task_type_cds"] = [type]
36
- if contact_id:
37
- filters["contact_ids"] = [contact_id]
38
-
39
- async with ctx.client() as client:
40
- result = await client.search_tasks(page=ctx.page, limit=ctx.limit, **filters)
41
-
42
- output_list(
43
- items=result.items,
44
- total=result.total,
45
- page=result.page,
46
- limit=ctx.limit,
47
- ctx=ctx,
48
- format_fn=lambda items, **kw: list_table(items, TASK_LIST_COLUMNS, title="Tasks", **kw),
49
- resource_name="Tasks",
50
- )
51
-
52
-
53
- @tasks_app.command
54
- async def create(
55
- *,
56
- contact_ids: Annotated[str, Parameter(name="--contact-ids", help="Comma-separated contact IDs")],
57
- note: Annotated[str, Parameter(name="--note", help="Task description")],
58
- type: Annotated[str, Parameter(name="--type", help="Task type")] = "action_item",
59
- priority: Annotated[str, Parameter(name="--priority", help="Priority (high, medium, low)")] = "medium",
60
- ) -> None:
61
- """Create a new task."""
62
- ids = parse_comma_list(contact_ids)
63
-
64
- async with ctx.client() as client:
65
- result = await client.create_task(contact_ids=ids, note=note, type=type, priority=priority)
66
-
67
- output(result, ctx=ctx)
68
-
69
-
70
- @tasks_app.command
71
- async def complete(
72
- id: Annotated[str, Parameter(help="Task ID")],
73
- *,
74
- note: Annotated[str | None, Parameter(name="--note", help="Completion note")] = None,
75
- ) -> None:
76
- """Mark a task as completed."""
77
- async with ctx.client() as client:
78
- result = await client.complete_task(id, note=note)
79
-
80
- output(result, ctx=ctx)