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
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Build a flat command reference for the root --help epilogue."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import types
|
|
7
|
+
from typing import Annotated, Any, Union, get_args, get_origin, get_type_hints
|
|
8
|
+
|
|
9
|
+
from cyclopts import App, Parameter
|
|
10
|
+
|
|
11
|
+
COL_WIDTH = 46
|
|
12
|
+
MAX_LINE = 78
|
|
13
|
+
MIN_DESC_WIDTH = 10
|
|
14
|
+
ELLIPSIS = "\u2026"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _is_bool_type(tp: object) -> bool:
|
|
18
|
+
if tp is bool:
|
|
19
|
+
return True
|
|
20
|
+
if tp is None:
|
|
21
|
+
return False
|
|
22
|
+
origin = get_origin(tp)
|
|
23
|
+
if origin is Union or isinstance(tp, types.UnionType):
|
|
24
|
+
return bool in get_args(tp)
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _display_len(s: str) -> int:
|
|
29
|
+
"""Return rendered width (Rich \\[ escapes render as single [)."""
|
|
30
|
+
return len(s.replace("\\[", "["))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _format_signature(func: Any, prefix_len: int, col_width: int) -> str:
|
|
34
|
+
sig = inspect.signature(func)
|
|
35
|
+
try:
|
|
36
|
+
hints = get_type_hints(func, include_extras=True)
|
|
37
|
+
except (TypeError, NameError):
|
|
38
|
+
hints = {}
|
|
39
|
+
|
|
40
|
+
required: list[str] = []
|
|
41
|
+
optional: list[str] = []
|
|
42
|
+
for pname, param in sig.parameters.items():
|
|
43
|
+
hint = hints.get(pname)
|
|
44
|
+
|
|
45
|
+
cli_param = None
|
|
46
|
+
base_type = hint
|
|
47
|
+
if hint is not None and get_origin(hint) is Annotated:
|
|
48
|
+
args = get_args(hint)
|
|
49
|
+
base_type = args[0]
|
|
50
|
+
for arg in args[1:]:
|
|
51
|
+
if isinstance(arg, Parameter):
|
|
52
|
+
cli_param = arg
|
|
53
|
+
break
|
|
54
|
+
|
|
55
|
+
is_bool = _is_bool_type(base_type)
|
|
56
|
+
has_default = param.default is not inspect.Parameter.empty
|
|
57
|
+
|
|
58
|
+
if param.kind == param.KEYWORD_ONLY:
|
|
59
|
+
if cli_param and cli_param.name:
|
|
60
|
+
names = cli_param.name if isinstance(cli_param.name, (list, tuple)) else [cli_param.name]
|
|
61
|
+
cli_name = names[0]
|
|
62
|
+
else:
|
|
63
|
+
cli_name = f"--{pname.replace('_', '-')}"
|
|
64
|
+
|
|
65
|
+
if has_default:
|
|
66
|
+
optional.append(f"\\[{cli_name}]")
|
|
67
|
+
elif is_bool:
|
|
68
|
+
required.append(cli_name)
|
|
69
|
+
else:
|
|
70
|
+
required.append(f"{cli_name} {pname.upper()}")
|
|
71
|
+
elif param.kind in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD):
|
|
72
|
+
label = pname.upper()
|
|
73
|
+
if has_default:
|
|
74
|
+
optional.append(f"\\[{label}]")
|
|
75
|
+
else:
|
|
76
|
+
required.append(label)
|
|
77
|
+
|
|
78
|
+
max_sig = col_width - prefix_len - 2
|
|
79
|
+
parts = required + optional
|
|
80
|
+
result = " ".join(parts)
|
|
81
|
+
while len(result) > max_sig and optional:
|
|
82
|
+
optional.pop()
|
|
83
|
+
parts = required + optional + ["..."]
|
|
84
|
+
result = " ".join(parts)
|
|
85
|
+
|
|
86
|
+
return result
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def build_command_reference(sub_apps: list[App]) -> str:
|
|
90
|
+
"""Walk sub-apps and build a formatted "All Commands" block."""
|
|
91
|
+
entries: list[tuple[str, str]] = []
|
|
92
|
+
for sub in sub_apps:
|
|
93
|
+
sub_name = sub.name[0] if sub.name else "?"
|
|
94
|
+
for cmd_name, cmd_app in sub.resolved_commands().items():
|
|
95
|
+
if cmd_name.startswith("-"):
|
|
96
|
+
continue
|
|
97
|
+
func = cmd_app.default_command
|
|
98
|
+
if func is None:
|
|
99
|
+
continue
|
|
100
|
+
prefix = f" {sub_name} {cmd_name} "
|
|
101
|
+
sig_str = _format_signature(func, prefix_len=len(prefix), col_width=COL_WIDTH)
|
|
102
|
+
doc = (func.__doc__ or "").strip().split("\n")[0]
|
|
103
|
+
left = f" {sub_name} {cmd_name}"
|
|
104
|
+
if sig_str:
|
|
105
|
+
left += f" {sig_str}"
|
|
106
|
+
entries.append((left, doc))
|
|
107
|
+
entries.append(("", ""))
|
|
108
|
+
|
|
109
|
+
if entries and entries[-1] == ("", ""):
|
|
110
|
+
entries.pop()
|
|
111
|
+
|
|
112
|
+
lines = ["All Commands:\n"]
|
|
113
|
+
for left, doc in entries:
|
|
114
|
+
if not left:
|
|
115
|
+
lines.append("")
|
|
116
|
+
else:
|
|
117
|
+
display_w = _display_len(left)
|
|
118
|
+
pad = max(2, COL_WIDTH - display_w)
|
|
119
|
+
avail = MAX_LINE - display_w - pad
|
|
120
|
+
if len(doc) > avail > MIN_DESC_WIDTH:
|
|
121
|
+
doc = doc[: avail - 1] + ELLIPSIS
|
|
122
|
+
lines.append(f"{left}{' ' * pad}{doc}")
|
|
123
|
+
return "\n".join(lines)
|
apollo_cli/output.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Output formatting — JSON and Markdown modes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from decimal import Decimal
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.markdown import Markdown
|
|
14
|
+
|
|
15
|
+
from apollo_cli.context import Context
|
|
16
|
+
|
|
17
|
+
# Rich console for stderr diagnostics and markdown rendering
|
|
18
|
+
console = Console(stderr=True)
|
|
19
|
+
stdout_console = Console()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def serialize(obj: Any) -> Any:
|
|
23
|
+
"""Convert an object to a JSON-serializable structure."""
|
|
24
|
+
if isinstance(obj, BaseModel):
|
|
25
|
+
return obj.model_dump(mode="json")
|
|
26
|
+
if isinstance(obj, datetime):
|
|
27
|
+
return obj.isoformat()
|
|
28
|
+
if isinstance(obj, Decimal):
|
|
29
|
+
return float(obj)
|
|
30
|
+
if isinstance(obj, list):
|
|
31
|
+
return [serialize(item) for item in obj]
|
|
32
|
+
if isinstance(obj, dict):
|
|
33
|
+
return {k: serialize(v) for k, v in obj.items()}
|
|
34
|
+
return obj
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def output_json(data: Any) -> None:
|
|
38
|
+
"""Print data as JSON to stdout."""
|
|
39
|
+
print(json.dumps(serialize(data), indent=2, default=str))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def output_markdown(text: str) -> None:
|
|
43
|
+
"""Render markdown text to the terminal via rich."""
|
|
44
|
+
stdout_console.print(Markdown(text))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def output(data: Any, *, ctx: Context, format_fn: Any = None) -> None:
|
|
48
|
+
"""Route output through the correct formatter.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
data: The data to output (Pydantic model, dict, list, etc.)
|
|
52
|
+
ctx: The global context (determines json vs markdown mode).
|
|
53
|
+
format_fn: Optional callable(data) -> str that returns markdown.
|
|
54
|
+
"""
|
|
55
|
+
if ctx.json_mode:
|
|
56
|
+
output_json(data)
|
|
57
|
+
else:
|
|
58
|
+
md = format_fn(data) if format_fn else generic_markdown(data)
|
|
59
|
+
output_markdown(md)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def output_list(
|
|
63
|
+
*,
|
|
64
|
+
items: list[Any],
|
|
65
|
+
total: int,
|
|
66
|
+
page: int,
|
|
67
|
+
limit: int,
|
|
68
|
+
ctx: Context,
|
|
69
|
+
format_fn: Any,
|
|
70
|
+
resource_name: str = "Results",
|
|
71
|
+
) -> None:
|
|
72
|
+
"""Output a paginated list of items.
|
|
73
|
+
|
|
74
|
+
In JSON mode, emits ``{items, total, page, limit}``.
|
|
75
|
+
In markdown mode, calls *format_fn* and appends a pagination footer.
|
|
76
|
+
"""
|
|
77
|
+
if ctx.json_mode:
|
|
78
|
+
output_json({"items": serialize(items), "total": total, "page": page, "limit": limit})
|
|
79
|
+
else:
|
|
80
|
+
md = format_fn(items, total=total, page=page)
|
|
81
|
+
if total > page * limit:
|
|
82
|
+
md += f"\n\n*Showing {len(items)} of {total} results. Use `--page {page + 1}` for next page.*"
|
|
83
|
+
elif total > 0:
|
|
84
|
+
md += f"\n\n*Showing {len(items)} of {total} results.*"
|
|
85
|
+
output_markdown(md)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def error(message: str, *, ctx: Context | None = None, code: str = "error", exit_code: int = 1) -> None:
|
|
89
|
+
"""Output an error and exit.
|
|
90
|
+
|
|
91
|
+
In JSON mode, writes a JSON error object to stdout.
|
|
92
|
+
In markdown mode, writes to stderr.
|
|
93
|
+
"""
|
|
94
|
+
if ctx and ctx.json_mode:
|
|
95
|
+
print(json.dumps({"error": message, "code": code}))
|
|
96
|
+
else:
|
|
97
|
+
console.print(f"[red]Error:[/red] {message}")
|
|
98
|
+
sys.exit(exit_code)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
# Generic markdown helpers
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def generic_markdown(data: Any) -> str:
|
|
107
|
+
"""Best-effort markdown rendering for arbitrary data."""
|
|
108
|
+
if isinstance(data, BaseModel):
|
|
109
|
+
return _model_to_md(data)
|
|
110
|
+
if isinstance(data, dict):
|
|
111
|
+
return _dict_to_md(data)
|
|
112
|
+
if isinstance(data, list):
|
|
113
|
+
if not data:
|
|
114
|
+
return "_No results._"
|
|
115
|
+
return "\n\n".join(generic_markdown(item) for item in data)
|
|
116
|
+
return str(data)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _model_to_md(model: BaseModel) -> str:
|
|
120
|
+
d = model.model_dump(exclude_none=True)
|
|
121
|
+
return _dict_to_md(d)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _dict_to_md(d: dict) -> str:
|
|
125
|
+
lines = ["| Field | Value |", "|-------|-------|"]
|
|
126
|
+
for key, value in d.items():
|
|
127
|
+
if isinstance(value, (dict, list)):
|
|
128
|
+
continue # skip complex nested fields in generic view
|
|
129
|
+
display_key = key.replace("_", " ").title()
|
|
130
|
+
lines.append(f"| {display_key} | {value} |")
|
|
131
|
+
return "\n".join(lines)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def md_table(rows: list[dict[str, str]], headers: list[tuple[str, str]]) -> str:
|
|
135
|
+
"""Build a markdown table from rows.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
rows: List of dicts with values to display.
|
|
139
|
+
headers: List of (header_label, key) tuples.
|
|
140
|
+
"""
|
|
141
|
+
if not rows:
|
|
142
|
+
return "_No results._"
|
|
143
|
+
|
|
144
|
+
header_line = "| " + " | ".join(h for h, _ in headers) + " |"
|
|
145
|
+
sep_line = "| " + " | ".join("---" for _ in headers) + " |"
|
|
146
|
+
lines = [header_line, sep_line]
|
|
147
|
+
for row in rows:
|
|
148
|
+
cells = [str(row.get(key, "")) for _, key in headers]
|
|
149
|
+
lines.append("| " + " | ".join(cells) + " |")
|
|
150
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# qodev-apollo-cli
|
|
2
|
+
|
|
3
|
+
Agent-friendly CLI for the Apollo.io API. Designed for AI coding agents with structured JSON output and predictable exit codes.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install qodev-apollo-cli
|
|
9
|
+
export APOLLO_API_KEY="your_api_key"
|
|
10
|
+
|
|
11
|
+
# Install skill files into the current workspace
|
|
12
|
+
qodev-apollo-cli install --skills
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Get your API key from [Apollo.io Settings → API](https://app.apollo.io/#/settings/integrations/api).
|
|
16
|
+
|
|
17
|
+
## Global Options
|
|
18
|
+
|
|
19
|
+
| Flag | Description |
|
|
20
|
+
|------|-------------|
|
|
21
|
+
| `--json` | Output as JSON (default: rich Markdown) |
|
|
22
|
+
| `--api-key` | Apollo API key (overrides APOLLO_API_KEY) |
|
|
23
|
+
| `--limit` | Results per page (default: 25) |
|
|
24
|
+
| `--page` | Page number (default: 1) |
|
|
25
|
+
|
|
26
|
+
## Command Reference
|
|
27
|
+
|
|
28
|
+
### contacts
|
|
29
|
+
|
|
30
|
+
| Command | Description |
|
|
31
|
+
|---------|-------------|
|
|
32
|
+
| `contacts search [--query Q] [--stage-id ID] [--linkedin-url URL]` | Search contacts |
|
|
33
|
+
| `contacts get ID` | Get contact details |
|
|
34
|
+
| `contacts create --first-name F --last-name L [--email E] [--title T] [--company C] [--linkedin-url URL]` | Create contact |
|
|
35
|
+
| `contacts update ID [--title T] [--label-ids IDS]` | Update contact |
|
|
36
|
+
| `contacts find-by-linkedin URL [--create] [--name N] [--stage-id ID]` | Find contact by LinkedIn URL |
|
|
37
|
+
| `contacts stages` | List all contact stages |
|
|
38
|
+
|
|
39
|
+
### accounts
|
|
40
|
+
|
|
41
|
+
| Command | Description |
|
|
42
|
+
|---------|-------------|
|
|
43
|
+
| `accounts search [--query Q] [--domain D]` | Search companies/accounts |
|
|
44
|
+
| `accounts get ID` | Get account details |
|
|
45
|
+
|
|
46
|
+
### deals
|
|
47
|
+
|
|
48
|
+
| Command | Description |
|
|
49
|
+
|---------|-------------|
|
|
50
|
+
| `deals search [--query Q] [--stage-id ID]` | Search opportunities/deals |
|
|
51
|
+
| `deals get ID` | Get deal details |
|
|
52
|
+
|
|
53
|
+
### pipelines
|
|
54
|
+
|
|
55
|
+
| Command | Description |
|
|
56
|
+
|---------|-------------|
|
|
57
|
+
| `pipelines list` | List all deal pipelines |
|
|
58
|
+
| `pipelines get ID` | Get pipeline details |
|
|
59
|
+
| `pipelines stages ID` | List stages in a pipeline |
|
|
60
|
+
|
|
61
|
+
### stages
|
|
62
|
+
|
|
63
|
+
| Command | Description |
|
|
64
|
+
|---------|-------------|
|
|
65
|
+
| `stages list` | List all contact stages |
|
|
66
|
+
| `stages get ID` | Get stage details |
|
|
67
|
+
|
|
68
|
+
### enrich
|
|
69
|
+
|
|
70
|
+
| Command | Description |
|
|
71
|
+
|---------|-------------|
|
|
72
|
+
| `enrich org DOMAIN` | Enrich organization by domain (FREE - no credits used) |
|
|
73
|
+
| `enrich person EMAIL` | Enrich person by email (1 credit per lookup) |
|
|
74
|
+
|
|
75
|
+
### people
|
|
76
|
+
|
|
77
|
+
| Command | Description |
|
|
78
|
+
|---------|-------------|
|
|
79
|
+
| `people search [--person-titles TITLES] [--q-organization-domains DOMAINS]` | Search people database |
|
|
80
|
+
|
|
81
|
+
### notes
|
|
82
|
+
|
|
83
|
+
| Command | Description |
|
|
84
|
+
|---------|-------------|
|
|
85
|
+
| `notes search [--contact-id ID]` | Search notes |
|
|
86
|
+
| `notes create --contact-ids IDS --note TEXT` | Create a note |
|
|
87
|
+
|
|
88
|
+
### tasks
|
|
89
|
+
|
|
90
|
+
| Command | Description |
|
|
91
|
+
|---------|-------------|
|
|
92
|
+
| `tasks search [--type TYPE] [--status STATUS]` | Search tasks |
|
|
93
|
+
| `tasks create --contact-ids IDS --note TEXT [--due-at DATE]` | Create a task |
|
|
94
|
+
|
|
95
|
+
### calls
|
|
96
|
+
|
|
97
|
+
| Command | Description |
|
|
98
|
+
|---------|-------------|
|
|
99
|
+
| `calls search` | Search call activities |
|
|
100
|
+
|
|
101
|
+
### emails
|
|
102
|
+
|
|
103
|
+
| Command | Description |
|
|
104
|
+
|---------|-------------|
|
|
105
|
+
| `emails search` | Search email activities |
|
|
106
|
+
|
|
107
|
+
### news
|
|
108
|
+
|
|
109
|
+
| Command | Description |
|
|
110
|
+
|---------|-------------|
|
|
111
|
+
| `news search [--categories CATS]` | Search news |
|
|
112
|
+
|
|
113
|
+
### jobs
|
|
114
|
+
|
|
115
|
+
| Command | Description |
|
|
116
|
+
|---------|-------------|
|
|
117
|
+
| `jobs search [--job-titles TITLES] [--company-domains DOMAINS]` | Search job postings |
|
|
118
|
+
|
|
119
|
+
### usage
|
|
120
|
+
|
|
121
|
+
| Command | Description |
|
|
122
|
+
|---------|-------------|
|
|
123
|
+
| `usage` | Show API usage stats and rate limits |
|
|
124
|
+
|
|
125
|
+
## Exit Codes
|
|
126
|
+
|
|
127
|
+
| Code | Meaning |
|
|
128
|
+
|------|---------|
|
|
129
|
+
| 0 | Success |
|
|
130
|
+
| 80 | Authentication error (invalid API key) |
|
|
131
|
+
| 81 | Rate limit exceeded |
|
|
132
|
+
| 82 | API error (server error, invalid request) |
|
|
133
|
+
| 83 | Validation error (missing required fields) |
|
|
134
|
+
|
|
135
|
+
## JSON Output
|
|
136
|
+
|
|
137
|
+
All commands support `--json` for structured output:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
qodev-apollo-cli --json contacts search --query "engineer" | jq '.items[0].name'
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Paginated responses include:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"items": [...],
|
|
148
|
+
"total": 142,
|
|
149
|
+
"page": 1,
|
|
150
|
+
"limit": 25
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Common Patterns
|
|
155
|
+
|
|
156
|
+
### Find and enrich a contact
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# Search by keyword
|
|
160
|
+
qodev-apollo-cli contacts search --query "jane smith"
|
|
161
|
+
|
|
162
|
+
# Get full details
|
|
163
|
+
qodev-apollo-cli contacts get <contact-id>
|
|
164
|
+
|
|
165
|
+
# Enrich person data (1 credit)
|
|
166
|
+
qodev-apollo-cli enrich person jane@example.com
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Pipeline management
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# List all pipelines
|
|
173
|
+
qodev-apollo-cli pipelines list
|
|
174
|
+
|
|
175
|
+
# Get stages in a pipeline
|
|
176
|
+
qodev-apollo-cli pipelines stages <pipeline-id>
|
|
177
|
+
|
|
178
|
+
# Search deals in specific stage
|
|
179
|
+
qodev-apollo-cli deals search --stage-id <stage-id>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### LinkedIn integration
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
# Find contact by LinkedIn URL
|
|
186
|
+
qodev-apollo-cli contacts find-by-linkedin "https://linkedin.com/in/janesmith"
|
|
187
|
+
|
|
188
|
+
# Auto-create if not found
|
|
189
|
+
qodev-apollo-cli contacts find-by-linkedin "https://linkedin.com/in/janesmith" --create
|
|
190
|
+
|
|
191
|
+
# Assign to stage on creation
|
|
192
|
+
qodev-apollo-cli contacts find-by-linkedin "https://linkedin.com/in/janesmith" \
|
|
193
|
+
--create --stage-id <stage-id>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## References
|
|
197
|
+
|
|
198
|
+
- [Contact Management Workflows](references/contact-workflows.md)
|
|
199
|
+
- [Deal Pipeline Workflows](references/deal-workflows.md)
|
|
200
|
+
- [Account Enrichment Patterns](references/account-workflows.md)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""AI agent skill files for Apollo CLI."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Reference documentation for Apollo CLI workflows."""
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Account Enrichment Patterns
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Apollo provides two types of enrichment:
|
|
6
|
+
- **Organization enrichment** (FREE - no credits used)
|
|
7
|
+
- **Person enrichment** (1 credit per lookup)
|
|
8
|
+
|
|
9
|
+
## Organization Enrichment (FREE)
|
|
10
|
+
|
|
11
|
+
Enrich company data by domain without using credits:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Basic enrichment
|
|
15
|
+
qodev-apollo-cli enrich org acme.com
|
|
16
|
+
|
|
17
|
+
# JSON output for scripting
|
|
18
|
+
qodev-apollo-cli --json enrich org acme.com
|
|
19
|
+
|
|
20
|
+
# Extract specific fields
|
|
21
|
+
qodev-apollo-cli --json enrich org acme.com | jq '{name, industry, employees: .estimated_num_employees}'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### What you get (FREE):
|
|
25
|
+
- Company name
|
|
26
|
+
- Industry classification
|
|
27
|
+
- Employee count estimate
|
|
28
|
+
- Company size category
|
|
29
|
+
- Website URL
|
|
30
|
+
- Social media profiles
|
|
31
|
+
- Founded year
|
|
32
|
+
- Revenue estimates
|
|
33
|
+
- Technologies used
|
|
34
|
+
|
|
35
|
+
## Account Search
|
|
36
|
+
|
|
37
|
+
Search for companies in Apollo's database:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Search by name
|
|
41
|
+
qodev-apollo-cli accounts search --query "technology"
|
|
42
|
+
|
|
43
|
+
# Search by domain
|
|
44
|
+
qodev-apollo-cli accounts search --domain "acme.com"
|
|
45
|
+
|
|
46
|
+
# Pagination
|
|
47
|
+
qodev-apollo-cli accounts search --query "software" --page 2 --limit 50
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Account Details
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Get full account details
|
|
54
|
+
qodev-apollo-cli accounts get <account-id>
|
|
55
|
+
|
|
56
|
+
# Extract specific data
|
|
57
|
+
qodev-apollo-cli --json accounts get <account-id> | \
|
|
58
|
+
jq '{name, domain, industry, employees: .estimated_num_employees}'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Bulk Enrichment
|
|
62
|
+
|
|
63
|
+
Enrich multiple organizations:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# From a list of domains
|
|
67
|
+
cat domains.txt | while read domain; do
|
|
68
|
+
echo "Enriching $domain..."
|
|
69
|
+
qodev-apollo-cli --json enrich org "$domain" > "data/${domain}.json"
|
|
70
|
+
sleep 1 # Rate limiting
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
# Extract key fields
|
|
74
|
+
for file in data/*.json; do
|
|
75
|
+
jq '{domain: .domain, name: .name, industry: .industry, employees: .estimated_num_employees}' "$file"
|
|
76
|
+
done | jq -s '.'
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Person Enrichment (1 credit)
|
|
80
|
+
|
|
81
|
+
Enrich person data by email:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Basic enrichment
|
|
85
|
+
qodev-apollo-cli enrich person jane@example.com
|
|
86
|
+
|
|
87
|
+
# JSON output
|
|
88
|
+
qodev-apollo-cli --json enrich person jane@example.com
|
|
89
|
+
|
|
90
|
+
# Extract contact details
|
|
91
|
+
qodev-apollo-cli --json enrich person jane@example.com | \
|
|
92
|
+
jq '{name, title, company: .organization_name, linkedin: .linkedin_url}'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### What you get (1 credit):
|
|
96
|
+
- Full name
|
|
97
|
+
- Job title
|
|
98
|
+
- Company
|
|
99
|
+
- Email verification
|
|
100
|
+
- Phone numbers (if available)
|
|
101
|
+
- LinkedIn profile
|
|
102
|
+
- Work history
|
|
103
|
+
- Education
|
|
104
|
+
|
|
105
|
+
## Rate Limits and Usage
|
|
106
|
+
|
|
107
|
+
Check your API usage:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# View usage stats
|
|
111
|
+
qodev-apollo-cli usage
|
|
112
|
+
|
|
113
|
+
# JSON output for monitoring
|
|
114
|
+
qodev-apollo-cli --json usage | jq '{credits_available, requests_used, request_limit}'
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Enrichment Workflows
|
|
118
|
+
|
|
119
|
+
### Pre-meeting research
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Enrich company (free)
|
|
123
|
+
qodev-apollo-cli enrich org acme.com > company_info.txt
|
|
124
|
+
|
|
125
|
+
# Search for contacts at company
|
|
126
|
+
qodev-apollo-cli --json accounts search --domain "acme.com" | \
|
|
127
|
+
jq -r '.items[0].id' | \
|
|
128
|
+
xargs -I {} qodev-apollo-cli contacts search --account-id {}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Lead qualification
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Enrich organization to check company size
|
|
135
|
+
company_size=$(qodev-apollo-cli --json enrich org "$domain" | \
|
|
136
|
+
jq -r '.estimated_num_employees')
|
|
137
|
+
|
|
138
|
+
if [ "$company_size" -gt 100 ]; then
|
|
139
|
+
echo "Qualified: Enterprise ($company_size employees)"
|
|
140
|
+
else
|
|
141
|
+
echo "Not qualified: SMB ($company_size employees)"
|
|
142
|
+
fi
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Data enrichment pipeline
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
#!/bin/bash
|
|
149
|
+
# Enrich contacts from CSV
|
|
150
|
+
|
|
151
|
+
while IFS=, read -r email domain; do
|
|
152
|
+
# Free org enrichment
|
|
153
|
+
org_data=$(qodev-apollo-cli --json enrich org "$domain")
|
|
154
|
+
|
|
155
|
+
# Paid person enrichment (1 credit)
|
|
156
|
+
person_data=$(qodev-apollo-cli --json enrich person "$email")
|
|
157
|
+
|
|
158
|
+
# Combine and save
|
|
159
|
+
echo "{\"organization\": $org_data, \"person\": $person_data}" >> enriched_data.jsonl
|
|
160
|
+
|
|
161
|
+
sleep 1 # Rate limiting
|
|
162
|
+
done < contacts.csv
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## JSON Output Examples
|
|
166
|
+
|
|
167
|
+
### Organization Enrichment
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"domain": "acme.com",
|
|
172
|
+
"name": "Acme Corp",
|
|
173
|
+
"industry": "Technology",
|
|
174
|
+
"estimated_num_employees": 500,
|
|
175
|
+
"website_url": "https://acme.com",
|
|
176
|
+
"founded_year": 2010,
|
|
177
|
+
"technologies": ["AWS", "React", "Python"]
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Person Enrichment
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"name": "Jane Smith",
|
|
186
|
+
"email": "jane@example.com",
|
|
187
|
+
"title": "VP Engineering",
|
|
188
|
+
"organization_name": "Acme Corp",
|
|
189
|
+
"linkedin_url": "https://linkedin.com/in/janesmith",
|
|
190
|
+
"phone_numbers": [
|
|
191
|
+
{"number": "+1-555-0100", "type": "mobile"}
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Best Practices
|
|
197
|
+
|
|
198
|
+
1. **Use free enrichment first**: Always enrich organizations (free) before enriching people (1 credit)
|
|
199
|
+
2. **Batch operations**: Process enrichment requests in batches with rate limiting
|
|
200
|
+
3. **Cache results**: Save enriched data to avoid duplicate lookups
|
|
201
|
+
4. **Monitor usage**: Check `qodev-apollo-cli usage` regularly to track credit consumption
|
|
202
|
+
5. **Validate domains**: Ensure domains are correctly formatted before enrichment
|