the-office-cli 1.0.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.
File without changes
the_office_cli/a2a.py ADDED
@@ -0,0 +1,93 @@
1
+ # *[Michael hands out business cards at the conference]*
2
+
3
+ """
4
+ a2a.py — The business card.
5
+
6
+ The Agent Card for the A2A protocol. When another agent
7
+ asks "who are you and what can you do?" — this is what
8
+ they get back. Skills are derived from the command
9
+ registry so the card is always in sync with the CLI.
10
+ """
11
+
12
+ from the_office_cli.config import get_api_url
13
+ from the_office_cli.utils.registry import get_registry
14
+
15
+ PROTOCOL_VERSION = "0.2.1"
16
+ AGENT_VERSION = "1.0.0"
17
+
18
+ def _to_json_schema(fields: dict) -> dict:
19
+ """Turn the CLI's inline input/output shape into proper
20
+ JSON Schema — required fields lifted to the top."""
21
+ properties: dict = {}
22
+ required: list[str] = []
23
+ for field_name, field_def in fields.items():
24
+ if isinstance(field_def, dict):
25
+ prop = {k: v for k, v in field_def.items() if k != "required"}
26
+ properties[field_name] = prop
27
+ if field_def.get("required"):
28
+ required.append(field_name)
29
+ else:
30
+ properties[field_name] = field_def
31
+ schema = {"type": "object", "properties": properties}
32
+ if required:
33
+ schema["required"] = required
34
+ return schema
35
+
36
+ def _to_skill(config: dict) -> dict:
37
+ """Turn a registered command into an A2A skill — id,
38
+ name, description, schemas, example invocation."""
39
+ s = config["schema"]
40
+ full_name = config.get("full_name") or (
41
+ f"{config.get('_group', '')} {config['name']}".strip()
42
+ )
43
+ return {
44
+ "id": full_name.replace(" ", "-"),
45
+ "name": full_name.title(),
46
+ "description": config.get("description", ""),
47
+ "tags": [config.get("_group", "")],
48
+ "inputSchema": _to_json_schema(s.get("input", {})),
49
+ "outputSchema": _to_json_schema(s.get("output", {})),
50
+ "examples": [f"the-office {full_name}"],
51
+ }
52
+
53
+ def _build_card(base_url: str) -> dict:
54
+ """Print a fresh business card with today's skills."""
55
+ return {
56
+ "protocolVersion": PROTOCOL_VERSION,
57
+ "id": "the-office",
58
+ "name": "the-office",
59
+ "description": "The World's Best Sales Agent CLI.",
60
+ "url": base_url,
61
+ "version": AGENT_VERSION,
62
+ "provider": {
63
+ "organization": "The Office",
64
+ },
65
+ "capabilities": {
66
+ "streaming": False,
67
+ "pushNotifications": False,
68
+ "extendedAgentCard": False,
69
+ "cardSigning": False,
70
+ "taskStore": False,
71
+ },
72
+ "interfaces": [
73
+ {"protocolBinding": "JSONRPC", "url": f"{base_url}/rpc"},
74
+ ],
75
+ "securitySchemes": {
76
+ "bearerJwt": {
77
+ "type": "http",
78
+ "scheme": "bearer",
79
+ "bearerFormat": "JWT",
80
+ },
81
+ },
82
+ "security": [{"bearerJwt": []}],
83
+ "defaultInputModes": ["application/json"],
84
+ "defaultOutputModes": ["application/json"],
85
+ "skills": [_to_skill(cfg) for cfg in get_registry()],
86
+ }
87
+
88
+ def get_agent_card(base_url: str | None = None) -> dict:
89
+ """Hand out the business card — URL defaults to the API
90
+ the CLI is currently pointed at."""
91
+ return _build_card(base_url or get_api_url())
92
+
93
+ # Card handed out.
File without changes
@@ -0,0 +1,179 @@
1
+ # *[sets up the projector in the conference room]*
2
+
3
+ """
4
+ decorators.py — Sales training.
5
+
6
+ Before a command hits the floor, it goes through
7
+ training — learns the script (validation), does a
8
+ dry run (preview), and gets cleared for live calls.
9
+ """
10
+
11
+ import json
12
+ import sys
13
+ from typing import Callable
14
+
15
+ import click
16
+
17
+ from the_office_cli.cli.flags import (
18
+ confirm_option,
19
+ dry_run_option,
20
+ idempotency_key_option,
21
+ schema_option,
22
+ )
23
+ from the_office_cli.network.errors import ApiError
24
+ from the_office_cli.network import session
25
+ from the_office_cli.network.session import set_idempotency_key
26
+ from the_office_cli.utils.output import EXIT_SUCCESS, error_stderr, output_stdout
27
+ from the_office_cli.utils.schema import print_schema
28
+
29
+ def _read_stdin(schema: dict, stdin_error: str) -> dict:
30
+ """Read the training materials. If the basics are
31
+ missing, you're not ready for the floor."""
32
+ body = session.input_stdin()
33
+ if not body:
34
+ error_stderr("INVALID_INPUT", stdin_error)
35
+ for field_name, field_def in schema.get("input", {}).items():
36
+ if (
37
+ isinstance(field_def, dict)
38
+ and field_def.get("required")
39
+ and field_name not in body
40
+ ):
41
+ error_stderr("INVALID_INPUT", f"Missing required field: {field_name}")
42
+ return body
43
+
44
+ def _print_dry_run(command: str, schema: dict, kwargs: dict) -> None:
45
+ """Roleplay the call without a real client on the
46
+ line — practice round, then stop."""
47
+ click.echo(
48
+ json.dumps(
49
+ {
50
+ "status": "dry_run",
51
+ "command": command,
52
+ "would_call": {
53
+ "method": schema["method"],
54
+ "endpoint": schema["endpoint"],
55
+ },
56
+ "with_options": {
57
+ k: v for k, v in kwargs.items() if v is not None
58
+ },
59
+ }
60
+ )
61
+ )
62
+ sys.exit(EXIT_SUCCESS)
63
+
64
+ def _require_confirm(command: str) -> None:
65
+ """Firing a client is permanent. Say it out loud
66
+ first. DELETEs need --confirm."""
67
+ error_stderr("CONFIRMATION_REQUIRED", f"Pass --confirm to execute: {command}")
68
+
69
+ def _execute(fn: Callable, kwargs: dict) -> None:
70
+ """Training's over. Make the real call. If it lands,
71
+ print the win. If not, file the loss."""
72
+ try:
73
+ result = fn(**kwargs)
74
+ result = _apply_pagination(result, kwargs)
75
+ output_stdout(result)
76
+ except ApiError as e:
77
+ error_stderr(
78
+ e.code,
79
+ str(e),
80
+ exit_code=e.exit_code,
81
+ retry=e.retry,
82
+ retry_after=e.retry_after,
83
+ )
84
+ except SystemExit:
85
+ raise
86
+ except Exception as e:
87
+ error_stderr("UNKNOWN_ERROR", str(e))
88
+
89
+ def _apply_pagination(result: dict, kwargs: dict) -> dict:
90
+ """When the call returns a long list, add page numbers
91
+ so the caller knows where they are."""
92
+ if "items" not in result:
93
+ return result
94
+ page = kwargs.get("page")
95
+ per_page = kwargs.get("per_page")
96
+ if page is None or per_page is None:
97
+ return result
98
+ items = result["items"]
99
+ result["page"] = page
100
+ result["per_page"] = per_page
101
+ if isinstance(items, list):
102
+ result["has_next"] = len(items) >= per_page
103
+ return result
104
+
105
+ def command(
106
+ *,
107
+ name: str,
108
+ description: str,
109
+ schema: dict,
110
+ full_name: str | None = None,
111
+ input_stdin: bool = False,
112
+ stdin_error: str = "Pipe JSON to stdin",
113
+ ) -> Callable[[Callable], click.Command]:
114
+ """Sales training — turn a plain function into a command
115
+ that knows the script and can handle errors."""
116
+
117
+ def decorator(fn: Callable) -> click.Command:
118
+ user_params = list(reversed(getattr(fn, "__click_params__", [])))
119
+ params: list[click.Parameter] = list(user_params)
120
+
121
+ params.append(dry_run_option())
122
+
123
+ method = schema.get("method", "GET")
124
+ if method in ("POST", "PUT"):
125
+ params.append(idempotency_key_option())
126
+ if method == "DELETE":
127
+ params.append(confirm_option())
128
+
129
+ config: dict = {
130
+ "name": name,
131
+ "description": description,
132
+ "schema": schema,
133
+ "full_name": full_name,
134
+ }
135
+
136
+ params.append(schema_option(_schema_callback(config)))
137
+
138
+ command_name = full_name or name
139
+
140
+ def callback(**kwargs):
141
+ dry_run = kwargs.pop("dry_run", False)
142
+ confirm = kwargs.pop("confirm", False)
143
+ idempotency_key = kwargs.pop("idempotency_key", None)
144
+ set_idempotency_key(idempotency_key)
145
+
146
+ if input_stdin:
147
+ kwargs["_body"] = _read_stdin(schema, stdin_error)
148
+
149
+ if dry_run:
150
+ _print_dry_run(command_name, schema, kwargs)
151
+
152
+ if method == "DELETE" and not confirm:
153
+ _require_confirm(command_name)
154
+
155
+ _execute(fn, kwargs)
156
+
157
+ cmd = click.Command(
158
+ name=name,
159
+ callback=callback,
160
+ params=params,
161
+ help=description,
162
+ )
163
+ cmd._command_config = config
164
+ return cmd
165
+
166
+ return decorator
167
+
168
+ def _schema_callback(config: dict):
169
+ """Hand out the product sheet before training starts
170
+ — print the contract and exit."""
171
+
172
+ def cb(ctx, _param, value):
173
+ if not value or ctx.resilient_parsing:
174
+ return
175
+ print_schema(config)
176
+
177
+ return cb
178
+
179
+ # Training complete. You're cleared for the floor.
@@ -0,0 +1,54 @@
1
+ # *[opens the employee handbook to the rules page]*
2
+
3
+ """
4
+ flags.py — The employee handbook.
5
+
6
+ Standard flags every command carries — schema, dry-run,
7
+ idempotency, confirm. Added at build time so nobody
8
+ has to remember them.
9
+ """
10
+
11
+ import click
12
+
13
+ def schema_option(callback) -> click.Option:
14
+ """--schema — show the command's full contract and exit
15
+ before anything runs."""
16
+ return click.Option(
17
+ ["--schema"],
18
+ is_flag=True,
19
+ hidden=True,
20
+ is_eager=True,
21
+ expose_value=False,
22
+ callback=callback,
23
+ )
24
+
25
+ def dry_run_option() -> click.Option:
26
+ """--dry-run — rehearse the call without dialing."""
27
+ return click.Option(
28
+ ["--dry-run", "dry_run"],
29
+ is_flag=True,
30
+ help="Validate inputs and show what would execute, no API call",
31
+ )
32
+
33
+ def idempotency_key_option() -> click.Option:
34
+ """--idempotency-key — a receipt number so retries don't
35
+ send the same thing twice."""
36
+ return click.Option(
37
+ ["--idempotency-key", "idempotency_key"],
38
+ hidden=True,
39
+ help=(
40
+ "UUID for safe retries — same key = same result, "
41
+ "prevents double-sends"
42
+ ),
43
+ )
44
+
45
+ def confirm_option() -> click.Option:
46
+ """--confirm — required for DELETEs. No accidental
47
+ shredding."""
48
+ return click.Option(
49
+ ["--confirm"],
50
+ is_flag=True,
51
+ help="Confirm the destructive operation",
52
+ )
53
+
54
+ # Handbook closed.
@@ -0,0 +1,211 @@
1
+ # *[taps the directory board by the elevator]*
2
+
3
+ """
4
+ cli/main.py — The sales floor directory.
5
+
6
+ Every command is listed here under its department. Pure
7
+ wiring — no business logic, just which group owns what.
8
+ """
9
+
10
+ from importlib.metadata import version
11
+
12
+ import click
13
+
14
+ from the_office_cli.commands import (
15
+ account,
16
+ auth,
17
+ campaign,
18
+ dashboard,
19
+ lead,
20
+ linkedin,
21
+ info,
22
+ outreach,
23
+ report,
24
+ settings,
25
+ task,
26
+ user,
27
+ )
28
+ from the_office_cli.utils.registry import register
29
+
30
+
31
+ @click.group()
32
+ @click.version_option(version("the-office-cli"))
33
+ def cli() -> None:
34
+ """The Office — AI Sales Agent CLI. JSON in, JSON out."""
35
+
36
+
37
+ # Auth
38
+ register(cli, auth.login, group_name="auth")
39
+ register(cli, auth.logout, group_name="auth")
40
+ register(cli, auth.whoami, group_name="auth")
41
+ register(cli, auth.create_account, group_name="auth")
42
+ register(cli, auth.fire_drill, group_name="auth")
43
+
44
+ # Campaign
45
+ campaign_group = click.Group("campaign", help="Campaign management (8 commands)")
46
+ register(campaign_group, campaign.list_)
47
+ register(campaign_group, campaign.get_)
48
+ register(campaign_group, campaign.update)
49
+ register(campaign_group, campaign.delete_)
50
+ register(campaign_group, campaign.status)
51
+
52
+ create_group = click.Group("create", help="Create campaign (custom | ai | signals)")
53
+ register(create_group, campaign.create_custom, group_name="campaign")
54
+ register(create_group, campaign.create_ai, group_name="campaign")
55
+ register(create_group, campaign.create_signals, group_name="campaign")
56
+ campaign_group.add_command(create_group)
57
+
58
+ cli.add_command(campaign_group)
59
+
60
+ # Lead
61
+ lead_group = click.Group("lead", help="Lead management (6 commands)")
62
+ register(lead_group, lead.list_)
63
+ register(lead_group, lead.get_)
64
+ register(lead_group, lead.create)
65
+ register(lead_group, lead.update)
66
+ register(lead_group, lead.delete_)
67
+ register(lead_group, lead.batch_create)
68
+ cli.add_command(lead_group)
69
+
70
+ # LinkedIn
71
+ linkedin_group = click.Group("linkedin", help="LinkedIn via Unipile API (20 commands)")
72
+
73
+ register(linkedin_group, linkedin.retrieve_own_profile)
74
+
75
+ # LinkedIn connect account
76
+ linkedin_connect_group = click.Group("connect", help="Connect LinkedIn account")
77
+ register(linkedin_connect_group, linkedin.connect_account, group_name="linkedin")
78
+ linkedin_group.add_command(linkedin_connect_group)
79
+
80
+ # LinkedIn reconnect account
81
+ linkedin_reconnect_group = click.Group("reconnect", help="Reconnect LinkedIn account")
82
+ register(linkedin_reconnect_group, linkedin.reconnect_account, group_name="linkedin")
83
+ linkedin_group.add_command(linkedin_reconnect_group)
84
+
85
+ # LinkedIn check account
86
+ linkedin_check_group = click.Group("check", help="Check LinkedIn connection status")
87
+ register(linkedin_check_group, linkedin.check_account, group_name="linkedin")
88
+ linkedin_group.add_command(linkedin_check_group)
89
+
90
+ # LinkedIn retrieve profile and company
91
+ linkedin_retrieve_group = click.Group("retrieve", help="Retrieve profiles and companies")
92
+ register(linkedin_retrieve_group, linkedin.retrieve_profile, group_name="linkedin")
93
+ register(linkedin_retrieve_group, linkedin.retrieve_company, group_name="linkedin")
94
+ linkedin_group.add_command(linkedin_retrieve_group)
95
+
96
+ # LinkedIn send invitation and message
97
+ linkedin_send_group = click.Group("send", help="Send invitations and messages")
98
+ register(linkedin_send_group, linkedin.send_invite, group_name="linkedin")
99
+ register(linkedin_send_group, linkedin.send_message, group_name="linkedin")
100
+ linkedin_group.add_command(linkedin_send_group)
101
+
102
+ # LinkedIn list relations, chats, and messages
103
+ linkedin_list_group = click.Group("list", help="List connections, chats, and messages")
104
+ register(linkedin_list_group, linkedin.list_relations, group_name="linkedin")
105
+ register(linkedin_list_group, linkedin.list_chats, group_name="linkedin")
106
+ register(linkedin_list_group, linkedin.list_messages, group_name="linkedin")
107
+
108
+ # LinkedIn list user messages
109
+ linkedin_list_user_group = click.Group("user", help="Per-user message history")
110
+ register(linkedin_list_user_group, linkedin.list_user_messages, group_name="linkedin")
111
+ linkedin_list_group.add_command(linkedin_list_user_group)
112
+
113
+ linkedin_group.add_command(linkedin_list_group)
114
+
115
+ # LinkedIn start chat
116
+ linkedin_start_group = click.Group("start", help="Start new conversations")
117
+ register(linkedin_start_group, linkedin.start_chat, group_name="linkedin")
118
+ linkedin_group.add_command(linkedin_start_group)
119
+
120
+ # LinkedIn search parameters
121
+ search_group = click.Group("search", help="LinkedIn search (7 subcommands)")
122
+ register(search_group, linkedin.search_parameters, group_name="linkedin")
123
+
124
+ # LinkedIn search classic leads, accounts, posts, jobs
125
+ classic_group = click.Group("classic", help="Classic LinkedIn search -- leads, accounts, posts, jobs")
126
+ register(classic_group, linkedin.search_classic_leads, group_name="linkedin")
127
+ register(classic_group, linkedin.search_classic_accounts, group_name="linkedin")
128
+ register(classic_group, linkedin.search_classic_posts, group_name="linkedin")
129
+ register(classic_group, linkedin.search_classic_jobs, group_name="linkedin")
130
+ search_group.add_command(classic_group)
131
+
132
+ # LinkedIn search sales navigator leads, accounts
133
+ sales_navigator_group = click.Group("sales", help="Sales Navigator search")
134
+ navigator_group = click.Group("navigator", help="Sales Navigator search -- leads, accounts")
135
+ register(navigator_group, linkedin.search_sales_navigator_leads, group_name="linkedin")
136
+ register(navigator_group, linkedin.search_sales_navigator_accounts, group_name="linkedin")
137
+ sales_navigator_group.add_command(navigator_group)
138
+ search_group.add_command(sales_navigator_group)
139
+
140
+ linkedin_group.add_command(search_group)
141
+ cli.add_command(linkedin_group)
142
+
143
+ # Accounts
144
+ account_group = click.Group("account", help="Target company accounts (3 commands)")
145
+ register(account_group, account.list_)
146
+ register(account_group, account.get_)
147
+ register(account_group, account.delete_)
148
+ cli.add_command(account_group)
149
+
150
+ # Dashboard
151
+ register(dashboard.group, dashboard.funnel)
152
+ register(dashboard.group, dashboard.conversations)
153
+ cli.add_command(dashboard.group)
154
+
155
+ # Report
156
+ report_group = click.Group("report", help="Performance report (1 command)")
157
+ register(report_group, report.generate)
158
+ cli.add_command(report_group)
159
+
160
+ # Outreach
161
+ outreach_group = click.Group("outreach", help="Outreach tracking (1 command)")
162
+ register(outreach_group, outreach.list_)
163
+ cli.add_command(outreach_group)
164
+
165
+ # User
166
+ user_group = click.Group("user", help="Team member management (5 commands)")
167
+ register(user_group, user.list_)
168
+ register(user_group, user.get_)
169
+ register(user_group, user.create)
170
+ register(user_group, user.update)
171
+ register(user_group, user.delete_)
172
+ cli.add_command(user_group)
173
+
174
+ # Settings
175
+ settings_group = click.Group("settings", help="View and update settings (2 commands)")
176
+ register(settings_group, settings.get_)
177
+ register(settings_group, settings.update)
178
+ cli.add_command(settings_group)
179
+
180
+ # Task
181
+ task_group = click.Group("task", help="Celery task triggers and polling (6 commands)")
182
+
183
+ # Task find leads
184
+ task_find_group = click.Group("find", help="Find and source leads")
185
+ register(task_find_group, task.find_leads, group_name="task")
186
+ task_group.add_command(task_find_group)
187
+
188
+ # Task send invites
189
+ task_send_group = click.Group("send", help="Send outreach")
190
+ register(task_send_group, task.send_invites, group_name="task")
191
+ task_group.add_command(task_send_group)
192
+
193
+ # Task follow up
194
+ register(task_group, task.follow_up)
195
+
196
+ # Task sync accepted
197
+ task_sync_group = click.Group("sync", help="Sync LinkedIn data")
198
+ register(task_sync_group, task.sync_accepted, group_name="task")
199
+ register(task_sync_group, task.sync_conversations, group_name="task")
200
+ task_group.add_command(task_sync_group)
201
+
202
+ # Task status
203
+ register(task_group, task.status)
204
+
205
+ cli.add_command(task_group)
206
+
207
+ # Info
208
+ register(cli, info.tour, group_name="info")
209
+ cli.add_command(info.completion)
210
+
211
+ # Directory posted.
File without changes
@@ -0,0 +1,137 @@
1
+ # *[highlights a company name on the whiteboard]*
2
+
3
+ """
4
+ account.py — The target accounts.
5
+
6
+ The companies your signals campaigns are watching.
7
+ List them, pull one up, or remove it from the board.
8
+ """
9
+
10
+ import click
11
+
12
+ from the_office_cli.cli.decorators import command
13
+ from the_office_cli.network.session import delete, get
14
+
15
+ @command(
16
+ name="list",
17
+ description="Use this command to list all target accounts tracked by signals campaigns, with their buying signals tracked, findings, citations, and confidence levels. Prefer `lead list` when the user wants leads instead of accounts.",
18
+ schema={
19
+ "endpoint": "/accounts", "method": "GET",
20
+ "input": {
21
+ "page": {"type": "integer", "default": 1},
22
+ "per_page": {"type": "integer", "default": 10000},
23
+ },
24
+ "output": {
25
+ "id": {"type": "integer"},
26
+ "campaign_id": {"type": "integer"},
27
+ "campaign": {"type": "string"},
28
+ "website": {"type": "string", "format": "uri"},
29
+ "company_name": {"type": "string"},
30
+ "logo": {"type": "string", "format": "uri"},
31
+ "description": {"type": "string"},
32
+ "days_back": {"type": "integer"},
33
+ "response": {"type": "string"},
34
+ "citations": {"type": "array"},
35
+ "response_summary": {"type": "string"},
36
+ "created_at": {"type": "string", "format": "date-time"},
37
+ "updated_at": {"type": "string", "format": "date-time"},
38
+ },
39
+ "example": {
40
+ "input": {"page": 1},
41
+ "output": {
42
+ "id": 1,
43
+ "campaign_id": 6,
44
+ "campaign": "Vance Refrigeration Takedown",
45
+ "website": "https://vancerefrigeration.com",
46
+ "company_name": "Vance Refrigeration",
47
+ "logo": "https://logo.clearbit.com/vancerefrigeration.com",
48
+ "description": "Bob Vance, Vance Refrigeration. They keep things cold. We keep things personal.",
49
+ "days_back": 365,
50
+ "response": "Vance Refrigeration is a regional commercial HVAC supplier based in Scranton, PA...",
51
+ "citations": [
52
+ {"url": "https://vancerefrigeration.com/about", "title": "About Vance Refrigeration"},
53
+ ],
54
+ "response_summary": "Regional supplier, 50 employees, expanding to Wilkes-Barre.",
55
+ "created_at": "2026-03-15T09:00:00+00:00",
56
+ "updated_at": "2026-04-08T11:30:00+00:00",
57
+ },
58
+ },
59
+ },
60
+ )
61
+ @click.option("--page", type=click.INT, default=1, help="Page number for pagination (default: 1)")
62
+ @click.option("--per-page", "per_page", type=click.INT, default=10000, help="Results per page (default: 10000)")
63
+ def list_(page, per_page, **_):
64
+ return {
65
+ "object": "AccountList",
66
+ "items": get("/accounts", params={"page": page, "per_page": per_page}),
67
+ }
68
+
69
+ @command(
70
+ name="get",
71
+ description="Use this command to get a target account and its buying signals with findings, citations, and confidence levels.",
72
+ schema={
73
+ "endpoint": "/accounts/<id>", "method": "GET",
74
+ "input": {"id": {"type": "integer", "required": True}},
75
+ "output": {
76
+ "id": {"type": "integer"},
77
+ "campaign_id": {"type": "integer"},
78
+ "website": {"type": "string", "format": "uri"},
79
+ "company_name": {"type": "string"},
80
+ "logo": {"type": "string", "format": "uri"},
81
+ "description": {"type": "string"},
82
+ "days_back": {"type": "integer"},
83
+ "response": {"type": "string"},
84
+ "citations": {"type": "array"},
85
+ "response_gemini": {"type": "string"},
86
+ "citations_gemini": {"type": "array"},
87
+ "response_summary": {"type": "string"},
88
+ "created_at": {"type": "string", "format": "date-time"},
89
+ "updated_at": {"type": "string", "format": "date-time"},
90
+ },
91
+ "example": {
92
+ "input": {"id": 1},
93
+ "output": {
94
+ "id": 1,
95
+ "campaign_id": 6,
96
+ "website": "https://vancerefrigeration.com",
97
+ "company_name": "Vance Refrigeration",
98
+ "logo": "https://logo.clearbit.com/vancerefrigeration.com",
99
+ "description": "Bob Vance, Vance Refrigeration. They keep things cold. We keep things personal.",
100
+ "days_back": 365,
101
+ "response": "Vance Refrigeration is a regional commercial HVAC supplier...",
102
+ "citations": [
103
+ {"url": "https://vancerefrigeration.com/about", "title": "About"},
104
+ ],
105
+ "response_gemini": "Based on recent data, Vance Refrigeration has been expanding operations...",
106
+ "citations_gemini": [
107
+ {"url": "https://scrantontimes.com/business/vance-expansion", "title": "Vance announces Wilkes-Barre warehouse"},
108
+ ],
109
+ "response_summary": "Bob Vance runs a tight ship. 50 employees, growing 12% YoY. Expanding warehouse operations.",
110
+ "created_at": "2026-03-15T09:00:00+00:00",
111
+ "updated_at": "2026-04-08T11:30:00+00:00",
112
+ },
113
+ },
114
+ },
115
+ )
116
+ @click.option("--id", type=click.INT, required=True)
117
+ def get_(id, **_):
118
+ return {"object": "Account", **get(f"/accounts/{id}")}
119
+
120
+ @command(
121
+ name="delete",
122
+ description="Use this command to permanently delete a target account.",
123
+ schema={
124
+ "endpoint": "/accounts/<id>", "method": "DELETE",
125
+ "input": {"id": {"type": "integer", "required": True}},
126
+ "output": {"message": {"type": "string"}},
127
+ "example": {
128
+ "input": {"id": 2},
129
+ "output": {"message": "Account deleted successfully!"},
130
+ },
131
+ },
132
+ )
133
+ @click.option("--id", type=click.INT, required=True)
134
+ def delete_(id, **_):
135
+ return {"object": "Account", **delete(f"/accounts/{id}")}
136
+
137
+ # Accounts reviewed.