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.
- apollo_cli/__init__.py +3 -0
- apollo_cli/__main__.py +5 -0
- apollo_cli/app.py +120 -0
- apollo_cli/commands/__init__.py +0 -0
- apollo_cli/commands/accounts.py +51 -0
- apollo_cli/commands/calls.py +54 -0
- apollo_cli/commands/contacts.py +147 -0
- apollo_cli/commands/deals.py +51 -0
- apollo_cli/commands/emails.py +37 -0
- apollo_cli/commands/enrich.py +34 -0
- apollo_cli/commands/install.py +59 -0
- apollo_cli/commands/jobs.py +36 -0
- apollo_cli/commands/news.py +35 -0
- apollo_cli/commands/notes.py +68 -0
- apollo_cli/commands/people.py +34 -0
- apollo_cli/commands/pipelines.py +106 -0
- apollo_cli/commands/tasks.py +79 -0
- apollo_cli/commands/usage.py +56 -0
- apollo_cli/context.py +35 -0
- apollo_cli/formatters/__init__.py +0 -0
- apollo_cli/formatters/accounts.py +62 -0
- apollo_cli/formatters/contacts.py +80 -0
- apollo_cli/formatters/deals.py +46 -0
- apollo_cli/formatters/generic.py +101 -0
- apollo_cli/help_reference.py +123 -0
- apollo_cli/output.py +150 -0
- apollo_cli/skills/SKILL.md +200 -0
- apollo_cli/skills/__init__.py +1 -0
- apollo_cli/skills/references/__init__.py +1 -0
- apollo_cli/skills/references/account-workflows.md +202 -0
- apollo_cli/skills/references/contact-workflows.md +110 -0
- apollo_cli/skills/references/deal-workflows.md +142 -0
- qodev_apollo_cli-0.1.0.dist-info/METADATA +224 -0
- qodev_apollo_cli-0.1.0.dist-info/RECORD +37 -0
- qodev_apollo_cli-0.1.0.dist-info/WHEEL +4 -0
- qodev_apollo_cli-0.1.0.dist-info/entry_points.txt +2 -0
- qodev_apollo_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
apollo_cli/__init__.py
ADDED
apollo_cli/__main__.py
ADDED
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)
|