qodev-apollo-cli 0.1.0__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 (37) hide show
  1. apollo_cli/__init__.py +3 -0
  2. apollo_cli/__main__.py +5 -0
  3. apollo_cli/app.py +120 -0
  4. apollo_cli/commands/__init__.py +0 -0
  5. apollo_cli/commands/accounts.py +51 -0
  6. apollo_cli/commands/calls.py +54 -0
  7. apollo_cli/commands/contacts.py +147 -0
  8. apollo_cli/commands/deals.py +51 -0
  9. apollo_cli/commands/emails.py +37 -0
  10. apollo_cli/commands/enrich.py +34 -0
  11. apollo_cli/commands/install.py +59 -0
  12. apollo_cli/commands/jobs.py +36 -0
  13. apollo_cli/commands/news.py +35 -0
  14. apollo_cli/commands/notes.py +68 -0
  15. apollo_cli/commands/people.py +34 -0
  16. apollo_cli/commands/pipelines.py +106 -0
  17. apollo_cli/commands/tasks.py +79 -0
  18. apollo_cli/commands/usage.py +56 -0
  19. apollo_cli/context.py +35 -0
  20. apollo_cli/formatters/__init__.py +0 -0
  21. apollo_cli/formatters/accounts.py +62 -0
  22. apollo_cli/formatters/contacts.py +80 -0
  23. apollo_cli/formatters/deals.py +46 -0
  24. apollo_cli/formatters/generic.py +101 -0
  25. apollo_cli/help_reference.py +123 -0
  26. apollo_cli/output.py +150 -0
  27. apollo_cli/skills/SKILL.md +200 -0
  28. apollo_cli/skills/__init__.py +1 -0
  29. apollo_cli/skills/references/__init__.py +1 -0
  30. apollo_cli/skills/references/account-workflows.md +202 -0
  31. apollo_cli/skills/references/contact-workflows.md +110 -0
  32. apollo_cli/skills/references/deal-workflows.md +142 -0
  33. qodev_apollo_cli-0.1.0.dist-info/METADATA +224 -0
  34. qodev_apollo_cli-0.1.0.dist-info/RECORD +37 -0
  35. qodev_apollo_cli-0.1.0.dist-info/WHEEL +4 -0
  36. qodev_apollo_cli-0.1.0.dist-info/entry_points.txt +2 -0
  37. qodev_apollo_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
apollo_cli/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Apollo CLI — Agent-friendly command line interface for the Apollo API."""
2
+
3
+ __version__ = "0.1.0"
apollo_cli/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Allow running as `python -m apollo_cli`."""
2
+
3
+ from apollo_cli.app import main
4
+
5
+ main()
apollo_cli/app.py ADDED
@@ -0,0 +1,120 @@
1
+ """Root App definition, global options, and error handling."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from typing import Annotated
7
+
8
+ from cyclopts import App, Group, Parameter
9
+ from qodev_apollo_api import APIError, AuthenticationError, RateLimitError
10
+
11
+ import apollo_cli.context as _ctx
12
+
13
+ app = App(
14
+ name="apollo",
15
+ help="Agent-friendly CLI for the Apollo API.",
16
+ help_format="rich",
17
+ version_flags=[],
18
+ )
19
+
20
+ app.meta.group_parameters = Group("Global Options", sort_key=0)
21
+
22
+
23
+ # ---------------------------------------------------------------------------
24
+ # Import and register command groups
25
+ # ---------------------------------------------------------------------------
26
+ from apollo_cli.commands.accounts import accounts_app # noqa: E402
27
+ from apollo_cli.commands.calls import calls_app # noqa: E402
28
+ from apollo_cli.commands.contacts import contacts_app # noqa: E402
29
+ from apollo_cli.commands.deals import deals_app # noqa: E402
30
+ from apollo_cli.commands.emails import emails_app # noqa: E402
31
+ from apollo_cli.commands.enrich import enrich_app # noqa: E402
32
+ from apollo_cli.commands.install import install_app # noqa: E402
33
+ from apollo_cli.commands.jobs import jobs_app # noqa: E402
34
+ from apollo_cli.commands.news import news_app # noqa: E402
35
+ from apollo_cli.commands.notes import notes_app # noqa: E402
36
+ from apollo_cli.commands.people import people_app # noqa: E402
37
+ from apollo_cli.commands.pipelines import pipelines_app, stages_app # noqa: E402
38
+ from apollo_cli.commands.tasks import tasks_app # noqa: E402
39
+ from apollo_cli.commands.usage import usage_app # noqa: E402
40
+
41
+ # Register all sub-apps
42
+ _sub_apps = [
43
+ contacts_app,
44
+ accounts_app,
45
+ deals_app,
46
+ pipelines_app,
47
+ stages_app,
48
+ enrich_app,
49
+ people_app,
50
+ notes_app,
51
+ tasks_app,
52
+ calls_app,
53
+ emails_app,
54
+ news_app,
55
+ jobs_app,
56
+ usage_app,
57
+ install_app,
58
+ ]
59
+
60
+ for sub in _sub_apps:
61
+ app.command(sub)
62
+ sub.help_epilogue = "" # Prevent epilogue propagation
63
+
64
+ # Build dynamic help epilogue
65
+ from apollo_cli.help_reference import build_command_reference # noqa: E402
66
+
67
+ app.help_epilogue = build_command_reference(_sub_apps)
68
+
69
+
70
+ # ---------------------------------------------------------------------------
71
+ # Meta launcher — global options & error handling
72
+ # ---------------------------------------------------------------------------
73
+
74
+ EXIT_AUTH = 80
75
+ EXIT_RATE_LIMIT = 81
76
+ EXIT_API = 82
77
+ EXIT_VALIDATION = 83
78
+
79
+
80
+ @app.meta.default
81
+ def launcher(
82
+ *tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
83
+ json: Annotated[bool, Parameter(name="--json", help="Output as JSON", negative="")] = False,
84
+ api_key: Annotated[
85
+ str | None, Parameter(name="--api-key", help="Apollo API key (overrides APOLLO_API_KEY)", show=False)
86
+ ] = None,
87
+ limit: Annotated[int, Parameter(name="--limit", help="Results per page")] = 25,
88
+ page: Annotated[int, Parameter(name="--page", help="Page number")] = 1,
89
+ ) -> None:
90
+ """Apollo CLI — search contacts, accounts, deals, and more."""
91
+ # Configure the global context singleton before dispatching to subcommands.
92
+ _ctx.ctx.configure(json_mode=json, api_key=api_key, limit=limit, page=page)
93
+
94
+ try:
95
+ app(tokens)
96
+ except AuthenticationError as exc:
97
+ _handle_error(str(exc), code="authentication", exit_code=EXIT_AUTH)
98
+ except RateLimitError as exc:
99
+ msg = str(exc)
100
+ if exc.retry_after:
101
+ msg += f" (retry after {exc.retry_after}s)"
102
+ _handle_error(msg, code="rate_limit", exit_code=EXIT_RATE_LIMIT)
103
+ except APIError as exc:
104
+ _handle_error(str(exc), code="api_error", exit_code=EXIT_API)
105
+ except SystemExit:
106
+ raise
107
+ except KeyboardInterrupt:
108
+ sys.exit(130)
109
+ except Exception as exc:
110
+ _handle_error(f"Unexpected error: {exc}", code="unknown", exit_code=1)
111
+
112
+
113
+ def _handle_error(message: str, *, code: str, exit_code: int) -> None:
114
+ from apollo_cli.output import error
115
+
116
+ error(message, ctx=_ctx.ctx, code=code, exit_code=exit_code)
117
+
118
+
119
+ def main() -> None:
120
+ app.meta()
File without changes
@@ -0,0 +1,51 @@
1
+ """Accounts 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.accounts import format_account_detail, format_account_list
11
+ from apollo_cli.output import output, output_list
12
+
13
+ accounts_app = App(name="accounts", help="Manage accounts/companies.")
14
+
15
+
16
+ @accounts_app.command
17
+ async def search(
18
+ *,
19
+ query: Annotated[str, Parameter(name=["--query", "-q"], help="Search by organization name")] = "",
20
+ stage_id: Annotated[str | None, Parameter(name="--stage-id", help="Filter by account stage ID")] = None,
21
+ ) -> None:
22
+ """Search accounts by keyword or filter."""
23
+ filters: dict = {}
24
+ if query:
25
+ filters["q_organization_name"] = query
26
+ if stage_id:
27
+ filters["account_stage_ids"] = [stage_id]
28
+
29
+ async with ctx.client() as client:
30
+ result = await client.search_accounts(page=ctx.page, limit=ctx.limit, **filters)
31
+
32
+ output_list(
33
+ items=result.items,
34
+ total=result.total,
35
+ page=result.page,
36
+ limit=ctx.limit,
37
+ ctx=ctx,
38
+ format_fn=format_account_list,
39
+ resource_name="Accounts",
40
+ )
41
+
42
+
43
+ @accounts_app.command
44
+ async def get(
45
+ id: Annotated[str, Parameter(help="Account ID")],
46
+ ) -> None:
47
+ """Get account details by ID."""
48
+ async with ctx.client() as client:
49
+ account = await client.get_account(id)
50
+
51
+ output(account, ctx=ctx, format_fn=format_account_detail)
@@ -0,0 +1,54 @@
1
+ """Calls 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_json, output_list, output_markdown
12
+
13
+ calls_app = App(name="calls", help="Call activity.")
14
+
15
+ CALL_LIST_COLUMNS = [
16
+ ("ID", "id"),
17
+ ("Contact ID", "contact_id"),
18
+ ("Status", "status"),
19
+ ("Duration", "duration"),
20
+ ("Start", "start_time"),
21
+ ("Note", "note"),
22
+ ]
23
+
24
+
25
+ @calls_app.command
26
+ async def search() -> None:
27
+ """Search call activities."""
28
+ async with ctx.client() as client:
29
+ result = await client.search_calls(page=ctx.page, limit=ctx.limit)
30
+
31
+ output_list(
32
+ items=result.items,
33
+ total=result.total,
34
+ page=result.page,
35
+ limit=ctx.limit,
36
+ ctx=ctx,
37
+ format_fn=lambda items, **kw: list_table(items, CALL_LIST_COLUMNS, title="Calls", **kw),
38
+ resource_name="Calls",
39
+ )
40
+
41
+
42
+ @calls_app.command(name="list")
43
+ async def list_contact_calls(
44
+ contact_id: Annotated[str, Parameter(help="Contact ID")],
45
+ ) -> None:
46
+ """List calls for a specific contact."""
47
+ async with ctx.client() as client:
48
+ result = await client.list_contact_calls(contact_id)
49
+
50
+ if ctx.json_mode:
51
+ output_json(result)
52
+ else:
53
+ md = list_table(result, CALL_LIST_COLUMNS, title="Contact Calls", total=len(result), page=1)
54
+ output_markdown(md)
@@ -0,0 +1,147 @@
1
+ """Contacts 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.contacts import (
11
+ format_contact_detail,
12
+ format_contact_list,
13
+ format_stages_list,
14
+ )
15
+ from apollo_cli.output import output, output_list
16
+
17
+ contacts_app = App(name="contacts", help="Manage contacts.")
18
+
19
+
20
+ @contacts_app.command
21
+ async def search(
22
+ *,
23
+ query: Annotated[str, Parameter(name=["--query", "-q"], help="Search keyword")] = "",
24
+ stage_id: Annotated[str | None, Parameter(name="--stage-id", help="Filter by stage ID")] = None,
25
+ linkedin_url: Annotated[str | None, Parameter(name="--linkedin-url", help="Filter by LinkedIn URL")] = None,
26
+ ) -> None:
27
+ """Search contacts by keyword or filter."""
28
+ filters: dict = {}
29
+ if query:
30
+ filters["q_keywords"] = query
31
+ if stage_id:
32
+ filters["contact_stage_ids"] = [stage_id]
33
+ if linkedin_url:
34
+ filters["linkedin_url"] = linkedin_url
35
+
36
+ async with ctx.client() as client:
37
+ result = await client.search_contacts(page=ctx.page, limit=ctx.limit, **filters)
38
+
39
+ output_list(
40
+ items=result.items,
41
+ total=result.total,
42
+ page=result.page,
43
+ limit=ctx.limit,
44
+ ctx=ctx,
45
+ format_fn=format_contact_list,
46
+ resource_name="Contacts",
47
+ )
48
+
49
+
50
+ @contacts_app.command
51
+ async def get(
52
+ id: Annotated[str, Parameter(help="Contact ID")],
53
+ ) -> None:
54
+ """Get contact details by ID."""
55
+ async with ctx.client() as client:
56
+ contact = await client.get_contact(id)
57
+
58
+ output(contact, ctx=ctx, format_fn=format_contact_detail)
59
+
60
+
61
+ @contacts_app.command
62
+ async def create(
63
+ *,
64
+ first_name: Annotated[str, Parameter(name="--first-name", help="First name")],
65
+ last_name: Annotated[str, Parameter(name="--last-name", help="Last name")],
66
+ email: Annotated[str | None, Parameter(name="--email", help="Email address")] = None,
67
+ title: Annotated[str | None, Parameter(name="--title", help="Job title")] = None,
68
+ company: Annotated[str | None, Parameter(name="--company", help="Company name")] = None,
69
+ linkedin_url: Annotated[str | None, Parameter(name="--linkedin-url", help="LinkedIn URL")] = None,
70
+ ) -> None:
71
+ """Create a new contact."""
72
+ fields: dict = {}
73
+ if email:
74
+ fields["email"] = email
75
+ if title:
76
+ fields["title"] = title
77
+ if company:
78
+ fields["company_name"] = company
79
+ if linkedin_url:
80
+ fields["linkedin_url"] = linkedin_url
81
+
82
+ async with ctx.client() as client:
83
+ result = await client.create_contact(first_name, last_name, **fields)
84
+
85
+ output(result, ctx=ctx)
86
+
87
+
88
+ @contacts_app.command
89
+ async def update(
90
+ id: Annotated[str, Parameter(help="Contact ID")],
91
+ *,
92
+ title: Annotated[str | None, Parameter(name="--title", help="New job title")] = None,
93
+ label_ids: Annotated[str | None, Parameter(name="--label-ids", help="Comma-separated label IDs")] = None,
94
+ ) -> None:
95
+ """Update a contact's fields."""
96
+ fields: dict = {}
97
+ if title:
98
+ fields["title"] = title
99
+ if label_ids:
100
+ fields["label_ids"] = [lid.strip() for lid in label_ids.split(",")]
101
+
102
+ async with ctx.client() as client:
103
+ result = await client.update_contact(id, **fields)
104
+
105
+ output(result, ctx=ctx, format_fn=format_contact_detail)
106
+
107
+
108
+ @contacts_app.command(name="find-by-linkedin")
109
+ async def find_by_linkedin(
110
+ url: Annotated[str, Parameter(help="LinkedIn profile URL")],
111
+ *,
112
+ name: Annotated[str | None, Parameter(name="--name", help="Person's full name (for fallback search)")] = None,
113
+ create_flag: Annotated[bool, Parameter(name="--create", help="Auto-create if not found", negative="")] = False,
114
+ stage_id: Annotated[str | None, Parameter(name="--stage-id", help="Stage ID for auto-created contact")] = None,
115
+ ) -> None:
116
+ """Find a contact by LinkedIn URL with fallback strategies."""
117
+ async with ctx.client() as client:
118
+ contact_id = await client.find_contact_by_linkedin_url(
119
+ linkedin_url=url,
120
+ person_name=name,
121
+ create_if_missing=create_flag,
122
+ contact_stage_id=stage_id,
123
+ )
124
+
125
+ if contact_id:
126
+ output({"contact_id": contact_id}, ctx=ctx)
127
+ else:
128
+ from apollo_cli.output import error
129
+
130
+ error("Contact not found.", ctx=ctx, code="not_found", exit_code=1)
131
+
132
+
133
+ @contacts_app.command
134
+ async def stages() -> None:
135
+ """List contact pipeline stages."""
136
+ async with ctx.client() as client:
137
+ result = await client.get_contact_stages()
138
+
139
+ if ctx.json_mode:
140
+ from apollo_cli.output import output_json
141
+
142
+ output_json(result)
143
+ else:
144
+ from apollo_cli.output import output_markdown
145
+
146
+ md = format_stages_list(result, total=len(result), page=1)
147
+ output_markdown(md)
@@ -0,0 +1,51 @@
1
+ """Deals 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.deals import format_deal_detail, format_deal_list
11
+ from apollo_cli.output import output, output_list
12
+
13
+ deals_app = App(name="deals", help="Manage deals/opportunities.")
14
+
15
+
16
+ @deals_app.command
17
+ async def search(
18
+ *,
19
+ query: Annotated[str, Parameter(name=["--query", "-q"], help="Search keyword")] = "",
20
+ stage_id: Annotated[str | None, Parameter(name="--stage-id", help="Filter by deal stage ID")] = None,
21
+ ) -> None:
22
+ """Search deals by keyword or filter."""
23
+ filters: dict = {}
24
+ if query:
25
+ filters["q_keywords"] = query
26
+ if stage_id:
27
+ filters["opportunity_stage_ids"] = [stage_id]
28
+
29
+ async with ctx.client() as client:
30
+ result = await client.search_deals(page=ctx.page, limit=ctx.limit, **filters)
31
+
32
+ output_list(
33
+ items=result.items,
34
+ total=result.total,
35
+ page=result.page,
36
+ limit=ctx.limit,
37
+ ctx=ctx,
38
+ format_fn=format_deal_list,
39
+ resource_name="Deals",
40
+ )
41
+
42
+
43
+ @deals_app.command
44
+ async def get(
45
+ id: Annotated[str, Parameter(help="Deal ID")],
46
+ ) -> None:
47
+ """Get deal details by ID."""
48
+ async with ctx.client() as client:
49
+ deal = await client.get_deal(id)
50
+
51
+ output(deal, ctx=ctx, format_fn=format_deal_detail)
@@ -0,0 +1,37 @@
1
+ """Emails command group."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from cyclopts import App
6
+
7
+ from apollo_cli.context import ctx
8
+ from apollo_cli.formatters.generic import list_table
9
+ from apollo_cli.output import output_list
10
+
11
+ emails_app = App(name="emails", help="Email activity.")
12
+
13
+ EMAIL_LIST_COLUMNS = [
14
+ ("ID", "id"),
15
+ ("Subject", "subject"),
16
+ ("From", "from_email"),
17
+ ("To", "to_email"),
18
+ ("Status", "status"),
19
+ ("Created", "created_at"),
20
+ ]
21
+
22
+
23
+ @emails_app.command
24
+ async def search() -> None:
25
+ """Search email activities."""
26
+ async with ctx.client() as client:
27
+ result = await client.search_emails(page=ctx.page, limit=ctx.limit)
28
+
29
+ output_list(
30
+ items=result.items,
31
+ total=result.total,
32
+ page=result.page,
33
+ limit=ctx.limit,
34
+ ctx=ctx,
35
+ format_fn=lambda items, **kw: list_table(items, EMAIL_LIST_COLUMNS, title="Emails", **kw),
36
+ resource_name="Emails",
37
+ )
@@ -0,0 +1,34 @@
1
+ """Enrichment 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.output import output
11
+
12
+ enrich_app = App(name="enrich", help="Enrichment operations.")
13
+
14
+
15
+ @enrich_app.command
16
+ async def org(
17
+ domain: Annotated[str, Parameter(help="Company domain (e.g. apollo.io)")],
18
+ ) -> None:
19
+ """Enrich organization data by domain (FREE)."""
20
+ async with ctx.client() as client:
21
+ result = await client.enrich_organization(domain)
22
+
23
+ output(result, ctx=ctx)
24
+
25
+
26
+ @enrich_app.command
27
+ async def person(
28
+ email: Annotated[str, Parameter(help="Person's email address")],
29
+ ) -> None:
30
+ """Enrich person data by email (costs 1 credit)."""
31
+ async with ctx.client() as client:
32
+ result = await client.enrich_person(email)
33
+
34
+ output(result, ctx=ctx)
@@ -0,0 +1,59 @@
1
+ """Install CLI resources (skills for AI agents)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ from importlib.abc import Traversable
7
+ from importlib.resources import files
8
+ from pathlib import Path
9
+ from typing import Annotated
10
+
11
+ from cyclopts import App, Parameter
12
+
13
+ from apollo_cli.output import console
14
+
15
+ install_app = App(name="install", help="Install CLI resources.")
16
+
17
+
18
+ def _install_skills(target_root: Path | None = None) -> Path:
19
+ """Copy bundled skill files to .claude/skills/apollo/."""
20
+ root = target_root or Path.cwd()
21
+ dest = root / ".claude" / "skills" / "apollo"
22
+
23
+ source = files("apollo_cli") / "skills"
24
+
25
+ if dest.exists():
26
+ console.print(f"Replacing existing skills at {dest}")
27
+ shutil.rmtree(dest)
28
+ dest.mkdir(parents=True)
29
+
30
+ _copy_traversable(source, dest)
31
+ return dest
32
+
33
+
34
+ def _copy_traversable(source: Traversable, dest: Path) -> None:
35
+ """Recursively copy from a Traversable (importlib.resources) to a Path."""
36
+ for item in source.iterdir():
37
+ if item.name.startswith("__"):
38
+ continue
39
+ target = dest / item.name
40
+ if item.is_file():
41
+ target.write_bytes(item.read_bytes())
42
+ elif item.is_dir():
43
+ target.mkdir(exist_ok=True)
44
+ _copy_traversable(item, target)
45
+
46
+
47
+ @install_app.default
48
+ def install(
49
+ *,
50
+ skills: Annotated[bool, Parameter(name="--skills", help="Install AI agent skill files", negative="")] = False,
51
+ ) -> None:
52
+ """Install CLI resources into the current workspace."""
53
+ if not skills:
54
+ from apollo_cli.output import error
55
+
56
+ error("No install target specified. Use: qodev-apollo-cli install --skills", code="validation", exit_code=83)
57
+
58
+ dest = _install_skills()
59
+ console.print(f"[green]Installed skills to {dest}[/green]")
@@ -0,0 +1,36 @@
1
+ """Jobs 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_json, output_markdown
12
+
13
+ jobs_app = App(name="jobs", help="Job postings.")
14
+
15
+ JOBS_LIST_COLUMNS = [
16
+ ("Title", "title"),
17
+ ("Location", "location"),
18
+ ("Department", "department"),
19
+ ("URL", "url"),
20
+ ("Posted", "posted_at"),
21
+ ]
22
+
23
+
24
+ @jobs_app.command(name="list")
25
+ async def list_jobs(
26
+ account_id: Annotated[str, Parameter(help="Account ID")],
27
+ ) -> None:
28
+ """List job postings for an account."""
29
+ async with ctx.client() as client:
30
+ result = await client.list_account_jobs(account_id)
31
+
32
+ if ctx.json_mode:
33
+ output_json(result)
34
+ else:
35
+ md = list_table(result, JOBS_LIST_COLUMNS, title="Job Postings", total=len(result), page=1)
36
+ output_markdown(md)
@@ -0,0 +1,35 @@
1
+ """News 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_json, output_markdown
12
+
13
+ news_app = App(name="news", help="Account news.")
14
+
15
+ NEWS_LIST_COLUMNS = [
16
+ ("Title", "title"),
17
+ ("Category", "category"),
18
+ ("Published", "published_at"),
19
+ ("URL", "url"),
20
+ ]
21
+
22
+
23
+ @news_app.command(name="list")
24
+ async def list_news(
25
+ account_id: Annotated[str, Parameter(help="Account ID")],
26
+ ) -> None:
27
+ """List news articles for an account."""
28
+ async with ctx.client() as client:
29
+ result = await client.list_account_news(account_id)
30
+
31
+ if ctx.json_mode:
32
+ output_json(result)
33
+ else:
34
+ md = list_table(result, NEWS_LIST_COLUMNS, title="Account News", total=len(result), page=1)
35
+ output_markdown(md)