dailybot-cli 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 DailyBot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.4
2
+ Name: dailybot-cli
3
+ Version: 0.1.0
4
+ Summary: DailyBot CLI - Submit check-in updates from your terminal
5
+ Author-email: DailyBot <support@dailybot.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://www.dailybot.com
8
+ Project-URL: Documentation, https://docs.dailybot.com
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Build Tools
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: click>=8.1.0
23
+ Requires-Dist: httpx>=0.25.0
24
+ Requires-Dist: questionary>=2.0.0
25
+ Requires-Dist: rich>=13.0.0
26
+ Dynamic: license-file
27
+
28
+ # DailyBot CLI
29
+
30
+ A command-line interface for [DailyBot](https://www.dailybot.com) that lets **people** and **software agents** share progress updates, blockers, and feedback — straight from the terminal.
31
+
32
+ Whether you're a developer who lives in the terminal or an AI agent running in a CI pipeline, the DailyBot CLI gives you a fast way to submit check-ins without leaving your workflow.
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install dailybot-cli
38
+ ```
39
+
40
+ Or via the install script:
41
+
42
+ ```bash
43
+ curl -sSL https://cli.dailybot.com/install.sh | bash
44
+ ```
45
+
46
+ Requires Python 3.9+.
47
+
48
+ ## For humans
49
+
50
+ Authenticate once with your DailyBot email, then submit updates and check pending check-ins right from your terminal.
51
+
52
+ ```bash
53
+ # Log in (one-time setup, email OTP)
54
+ dailybot login
55
+
56
+ # See what check-ins are waiting for you
57
+ dailybot status
58
+
59
+ # Submit a free-text update
60
+ dailybot update "Finished the auth module, starting on tests."
61
+
62
+ # Or use structured fields
63
+ dailybot update --done "Auth module" --doing "Tests" --blocked "None"
64
+ ```
65
+
66
+ Run `dailybot` with no arguments to enter **interactive mode** — if you're not logged in yet, it will walk you through authentication first, then let you submit updates step by step.
67
+
68
+ ## For agents
69
+
70
+ Any software agent — AI coding assistants, CI jobs, deploy scripts, bots — can report activity through the CLI. This lets teams get visibility into what automated processes are doing, alongside human updates.
71
+
72
+ Authenticate with any of these methods (checked in this order):
73
+
74
+ ```bash
75
+ # Option 1: Environment variable (CI pipelines, one-off scripts)
76
+ export DAILYBOT_API_KEY=your-key
77
+
78
+ # Option 2: Store the key on disk (recommended for dev machines)
79
+ dailybot config key=your-key
80
+
81
+ # Option 3: Use your login session (no API key needed)
82
+ dailybot login
83
+ ```
84
+
85
+ Then run agent commands:
86
+
87
+ ```bash
88
+ # Report a deployment
89
+ dailybot agent update "Deployed v2.1 to staging"
90
+
91
+ # Name the agent so the team knows who's reporting
92
+ dailybot agent update "Built feature X" --name "Claude Code"
93
+
94
+ # Include structured data
95
+ dailybot agent update "Tests passed" --name "CI Bot" --json-data '{"suite": "integration", "passed": 42}'
96
+
97
+ # Report agent health
98
+ dailybot agent health --ok --message "All systems go" --name "Claude Code"
99
+ dailybot agent health --fail --message "DB unreachable" --name "CI Bot"
100
+
101
+ # Check agent health status
102
+ dailybot agent health --status --name "Claude Code"
103
+
104
+ # Register a webhook to receive messages
105
+ dailybot agent webhook register --url https://my-server.com/hook --secret my-token --name "Claude Code"
106
+
107
+ # Unregister a webhook
108
+ dailybot agent webhook unregister --name "Claude Code"
109
+
110
+ # Send a message to an agent
111
+ dailybot agent message send --to "Claude Code" --content "Review PR #42"
112
+ dailybot agent message send --to "Claude Code" --content "Do X" --type command
113
+
114
+ # List messages for an agent
115
+ dailybot agent message list --name "Claude Code"
116
+ dailybot agent message list --name "Claude Code" --pending
117
+ ```
118
+
119
+ ## Commands
120
+
121
+ | Command | Description |
122
+ |---|---|
123
+ | `dailybot login` | Authenticate with email OTP |
124
+ | `dailybot logout` | Log out and revoke token |
125
+ | `dailybot status` | Show pending check-ins for today |
126
+ | `dailybot update` | Submit a check-in update (free-text or structured) |
127
+ | `dailybot config` | Get, set, or remove a stored setting (e.g. API key) |
128
+ | `dailybot agent update` | Submit an agent activity report (API key or login) |
129
+ | `dailybot agent health` | Report or query agent health status (API key or login) |
130
+ | `dailybot agent webhook register` | Register a webhook for the agent (API key or login) |
131
+ | `dailybot agent webhook unregister` | Unregister the agent's webhook (API key or login) |
132
+ | `dailybot agent message send` | Send a message to an agent (API key or login) |
133
+ | `dailybot agent message list` | List messages for an agent (API key or login) |
134
+
135
+ Run `dailybot --help` for full details on any command.
136
+
137
+ ## Development
138
+
139
+ ```bash
140
+ pip install -e ".[dev]"
141
+ pytest
142
+ ```
143
+
144
+ ## License
145
+
146
+ [MIT](LICENSE)
@@ -0,0 +1,119 @@
1
+ # DailyBot CLI
2
+
3
+ A command-line interface for [DailyBot](https://www.dailybot.com) that lets **people** and **software agents** share progress updates, blockers, and feedback — straight from the terminal.
4
+
5
+ Whether you're a developer who lives in the terminal or an AI agent running in a CI pipeline, the DailyBot CLI gives you a fast way to submit check-ins without leaving your workflow.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install dailybot-cli
11
+ ```
12
+
13
+ Or via the install script:
14
+
15
+ ```bash
16
+ curl -sSL https://cli.dailybot.com/install.sh | bash
17
+ ```
18
+
19
+ Requires Python 3.9+.
20
+
21
+ ## For humans
22
+
23
+ Authenticate once with your DailyBot email, then submit updates and check pending check-ins right from your terminal.
24
+
25
+ ```bash
26
+ # Log in (one-time setup, email OTP)
27
+ dailybot login
28
+
29
+ # See what check-ins are waiting for you
30
+ dailybot status
31
+
32
+ # Submit a free-text update
33
+ dailybot update "Finished the auth module, starting on tests."
34
+
35
+ # Or use structured fields
36
+ dailybot update --done "Auth module" --doing "Tests" --blocked "None"
37
+ ```
38
+
39
+ Run `dailybot` with no arguments to enter **interactive mode** — if you're not logged in yet, it will walk you through authentication first, then let you submit updates step by step.
40
+
41
+ ## For agents
42
+
43
+ Any software agent — AI coding assistants, CI jobs, deploy scripts, bots — can report activity through the CLI. This lets teams get visibility into what automated processes are doing, alongside human updates.
44
+
45
+ Authenticate with any of these methods (checked in this order):
46
+
47
+ ```bash
48
+ # Option 1: Environment variable (CI pipelines, one-off scripts)
49
+ export DAILYBOT_API_KEY=your-key
50
+
51
+ # Option 2: Store the key on disk (recommended for dev machines)
52
+ dailybot config key=your-key
53
+
54
+ # Option 3: Use your login session (no API key needed)
55
+ dailybot login
56
+ ```
57
+
58
+ Then run agent commands:
59
+
60
+ ```bash
61
+ # Report a deployment
62
+ dailybot agent update "Deployed v2.1 to staging"
63
+
64
+ # Name the agent so the team knows who's reporting
65
+ dailybot agent update "Built feature X" --name "Claude Code"
66
+
67
+ # Include structured data
68
+ dailybot agent update "Tests passed" --name "CI Bot" --json-data '{"suite": "integration", "passed": 42}'
69
+
70
+ # Report agent health
71
+ dailybot agent health --ok --message "All systems go" --name "Claude Code"
72
+ dailybot agent health --fail --message "DB unreachable" --name "CI Bot"
73
+
74
+ # Check agent health status
75
+ dailybot agent health --status --name "Claude Code"
76
+
77
+ # Register a webhook to receive messages
78
+ dailybot agent webhook register --url https://my-server.com/hook --secret my-token --name "Claude Code"
79
+
80
+ # Unregister a webhook
81
+ dailybot agent webhook unregister --name "Claude Code"
82
+
83
+ # Send a message to an agent
84
+ dailybot agent message send --to "Claude Code" --content "Review PR #42"
85
+ dailybot agent message send --to "Claude Code" --content "Do X" --type command
86
+
87
+ # List messages for an agent
88
+ dailybot agent message list --name "Claude Code"
89
+ dailybot agent message list --name "Claude Code" --pending
90
+ ```
91
+
92
+ ## Commands
93
+
94
+ | Command | Description |
95
+ |---|---|
96
+ | `dailybot login` | Authenticate with email OTP |
97
+ | `dailybot logout` | Log out and revoke token |
98
+ | `dailybot status` | Show pending check-ins for today |
99
+ | `dailybot update` | Submit a check-in update (free-text or structured) |
100
+ | `dailybot config` | Get, set, or remove a stored setting (e.g. API key) |
101
+ | `dailybot agent update` | Submit an agent activity report (API key or login) |
102
+ | `dailybot agent health` | Report or query agent health status (API key or login) |
103
+ | `dailybot agent webhook register` | Register a webhook for the agent (API key or login) |
104
+ | `dailybot agent webhook unregister` | Unregister the agent's webhook (API key or login) |
105
+ | `dailybot agent message send` | Send a message to an agent (API key or login) |
106
+ | `dailybot agent message list` | List messages for an agent (API key or login) |
107
+
108
+ Run `dailybot --help` for full details on any command.
109
+
110
+ ## Development
111
+
112
+ ```bash
113
+ pip install -e ".[dev]"
114
+ pytest
115
+ ```
116
+
117
+ ## License
118
+
119
+ [MIT](LICENSE)
@@ -0,0 +1,3 @@
1
+ """DailyBot CLI - Submit check-in updates from your terminal."""
2
+
3
+ __version__: str = "0.1.0"
@@ -0,0 +1,303 @@
1
+ """HTTP client for DailyBot CLI API endpoints."""
2
+
3
+ from typing import Any, Optional
4
+
5
+ import httpx
6
+
7
+ from dailybot_cli.config import get_api_key, get_api_url, get_token
8
+
9
+
10
+ class APIError(Exception):
11
+ """Raised when the API returns a non-success response."""
12
+
13
+ def __init__(self, status_code: int, detail: str) -> None:
14
+ self.status_code: int = status_code
15
+ self.detail: str = detail
16
+ super().__init__(f"API error {status_code}: {detail}")
17
+
18
+
19
+ class DailyBotClient:
20
+ """HTTP client for the DailyBot /v1/cli/* API endpoints."""
21
+
22
+ def __init__(
23
+ self,
24
+ api_url: Optional[str] = None,
25
+ token: Optional[str] = None,
26
+ api_key: Optional[str] = None,
27
+ timeout: float = 30.0,
28
+ ) -> None:
29
+ self.api_url: str = (api_url or get_api_url()).rstrip("/")
30
+ self.token: Optional[str] = token or get_token()
31
+ self.api_key: Optional[str] = api_key or get_api_key()
32
+ self.timeout: float = timeout
33
+ self._agent_auth_mode: Optional[str] = None
34
+
35
+ def _headers(self, authenticated: bool = True) -> dict[str, str]:
36
+ """Build request headers."""
37
+ headers: dict[str, str] = {
38
+ "Content-Type": "application/json",
39
+ "Accept": "application/json",
40
+ }
41
+ if authenticated and self.token:
42
+ headers["Authorization"] = f"Bearer {self.token}"
43
+ return headers
44
+
45
+ def _agent_headers(self) -> dict[str, str]:
46
+ """Build headers for agent authentication (API key preferred, then Bearer)."""
47
+ headers: dict[str, str] = {
48
+ "Content-Type": "application/json",
49
+ "Accept": "application/json",
50
+ }
51
+ if self.api_key:
52
+ headers["X-API-KEY"] = self.api_key
53
+ self._agent_auth_mode = "api_key"
54
+ elif self.token:
55
+ headers["Authorization"] = f"Bearer {self.token}"
56
+ self._agent_auth_mode = "bearer"
57
+ else:
58
+ self._agent_auth_mode = None
59
+ return headers
60
+
61
+ def _handle_response(self, response: httpx.Response) -> dict[str, Any]:
62
+ """Parse API response and raise on errors."""
63
+ if response.status_code >= 400:
64
+ try:
65
+ body: dict[str, Any] = response.json()
66
+ detail: str = body.get("detail", body.get("error", str(body)))
67
+ except Exception:
68
+ detail = response.text or f"HTTP {response.status_code}"
69
+ if response.status_code in (401, 403) and self._agent_auth_mode == "bearer":
70
+ detail = "Session expired. Run 'dailybot login' to re-authenticate."
71
+ raise APIError(status_code=response.status_code, detail=detail)
72
+ if response.status_code == 204:
73
+ return {}
74
+ return response.json() # type: ignore[no-any-return]
75
+
76
+ # --- Auth endpoints ---
77
+
78
+ def request_code(self, email: str) -> dict[str, Any]:
79
+ """POST /v1/cli/auth/request-code/"""
80
+ response: httpx.Response = httpx.post(
81
+ f"{self.api_url}/v1/cli/auth/request-code/",
82
+ json={"email": email},
83
+ headers=self._headers(authenticated=False),
84
+ timeout=self.timeout,
85
+ )
86
+ return self._handle_response(response)
87
+
88
+ def verify_code(
89
+ self,
90
+ email: str,
91
+ code: str,
92
+ organization_id: Optional[int] = None,
93
+ ) -> dict[str, Any]:
94
+ """POST /v1/cli/auth/verify-code/"""
95
+ payload: dict[str, Any] = {"email": email, "code": code}
96
+ if organization_id is not None:
97
+ payload["organization_id"] = organization_id
98
+ response: httpx.Response = httpx.post(
99
+ f"{self.api_url}/v1/cli/auth/verify-code/",
100
+ json=payload,
101
+ headers=self._headers(authenticated=False),
102
+ timeout=self.timeout,
103
+ )
104
+ return self._handle_response(response)
105
+
106
+ def auth_status(self) -> dict[str, Any]:
107
+ """GET /v1/cli/auth/status/"""
108
+ response: httpx.Response = httpx.get(
109
+ f"{self.api_url}/v1/cli/auth/status/",
110
+ headers=self._headers(),
111
+ timeout=self.timeout,
112
+ )
113
+ return self._handle_response(response)
114
+
115
+ def logout(self) -> dict[str, Any]:
116
+ """POST /v1/cli/auth/logout/"""
117
+ response: httpx.Response = httpx.post(
118
+ f"{self.api_url}/v1/cli/auth/logout/",
119
+ headers=self._headers(),
120
+ timeout=self.timeout,
121
+ )
122
+ return self._handle_response(response)
123
+
124
+ # --- Update/Status endpoints ---
125
+
126
+ def submit_update(
127
+ self,
128
+ message: Optional[str] = None,
129
+ done: Optional[str] = None,
130
+ doing: Optional[str] = None,
131
+ blocked: Optional[str] = None,
132
+ ) -> dict[str, Any]:
133
+ """POST /v1/cli/updates/"""
134
+ payload: dict[str, str] = {}
135
+ if message:
136
+ payload["message"] = message
137
+ if done:
138
+ payload["done"] = done
139
+ if doing:
140
+ payload["doing"] = doing
141
+ if blocked:
142
+ payload["blocked"] = blocked
143
+ response: httpx.Response = httpx.post(
144
+ f"{self.api_url}/v1/cli/updates/",
145
+ json=payload,
146
+ headers=self._headers(),
147
+ timeout=120.0,
148
+ )
149
+ return self._handle_response(response)
150
+
151
+ def get_status(self) -> dict[str, Any]:
152
+ """GET /v1/cli/status/"""
153
+ response: httpx.Response = httpx.get(
154
+ f"{self.api_url}/v1/cli/status/",
155
+ headers=self._headers(),
156
+ timeout=self.timeout,
157
+ )
158
+ return self._handle_response(response)
159
+
160
+ # --- Agent endpoints ---
161
+
162
+ def submit_agent_report(
163
+ self,
164
+ agent_name: str,
165
+ content: str,
166
+ structured: Optional[dict[str, Any]] = None,
167
+ metadata: Optional[dict[str, Any]] = None,
168
+ ) -> dict[str, Any]:
169
+ """POST /v1/agent-reports/"""
170
+ payload: dict[str, Any] = {
171
+ "agent_name": agent_name,
172
+ "content": content,
173
+ }
174
+ if structured:
175
+ payload["structured"] = structured
176
+ if metadata:
177
+ payload["metadata"] = metadata
178
+ response: httpx.Response = httpx.post(
179
+ f"{self.api_url}/v1/agent-reports/",
180
+ json=payload,
181
+ headers=self._agent_headers(),
182
+ timeout=self.timeout,
183
+ )
184
+ return self._handle_response(response)
185
+
186
+ def submit_agent_health(
187
+ self,
188
+ agent_name: str,
189
+ ok: bool,
190
+ message: Optional[str] = None,
191
+ ) -> dict[str, Any]:
192
+ """POST /v1/agent-health/"""
193
+ payload: dict[str, Any] = {
194
+ "agent_name": agent_name,
195
+ "ok": ok,
196
+ }
197
+ if message:
198
+ payload["message"] = message
199
+ response: httpx.Response = httpx.post(
200
+ f"{self.api_url}/v1/agent-health/",
201
+ json=payload,
202
+ headers=self._agent_headers(),
203
+ timeout=self.timeout,
204
+ )
205
+ return self._handle_response(response)
206
+
207
+ def get_agent_health(self, agent_name: str) -> dict[str, Any]:
208
+ """GET /v1/agent-health/?agent_name=..."""
209
+ response: httpx.Response = httpx.get(
210
+ f"{self.api_url}/v1/agent-health/",
211
+ params={"agent_name": agent_name},
212
+ headers=self._agent_headers(),
213
+ timeout=self.timeout,
214
+ )
215
+ return self._handle_response(response)
216
+
217
+ # --- Agent webhook endpoints ---
218
+
219
+ def register_agent_webhook(
220
+ self,
221
+ agent_name: str,
222
+ webhook_url: str,
223
+ webhook_secret: Optional[str] = None,
224
+ ) -> dict[str, Any]:
225
+ """POST /v1/agent-webhook/"""
226
+ payload: dict[str, Any] = {
227
+ "agent_name": agent_name,
228
+ "webhook_url": webhook_url,
229
+ }
230
+ if webhook_secret:
231
+ payload["webhook_secret"] = webhook_secret
232
+ response: httpx.Response = httpx.post(
233
+ f"{self.api_url}/v1/agent-webhook/",
234
+ json=payload,
235
+ headers=self._agent_headers(),
236
+ timeout=self.timeout,
237
+ )
238
+ return self._handle_response(response)
239
+
240
+ def unregister_agent_webhook(self, agent_name: str) -> dict[str, Any]:
241
+ """DELETE /v1/agent-webhook/"""
242
+ response: httpx.Response = httpx.request(
243
+ "DELETE",
244
+ f"{self.api_url}/v1/agent-webhook/",
245
+ json={"agent_name": agent_name},
246
+ headers=self._agent_headers(),
247
+ timeout=self.timeout,
248
+ )
249
+ return self._handle_response(response)
250
+
251
+ # --- Agent message endpoints ---
252
+
253
+ def send_agent_message(
254
+ self,
255
+ agent_name: str,
256
+ content: str,
257
+ message_type: Optional[str] = None,
258
+ metadata: Optional[dict[str, Any]] = None,
259
+ expires_at: Optional[str] = None,
260
+ sender_type: Optional[str] = None,
261
+ sender_name: Optional[str] = None,
262
+ ) -> dict[str, Any]:
263
+ """POST /v1/agent-messages/"""
264
+ payload: dict[str, Any] = {
265
+ "agent_name": agent_name,
266
+ "content": content,
267
+ }
268
+ if message_type:
269
+ payload["message_type"] = message_type
270
+ if metadata:
271
+ payload["metadata"] = metadata
272
+ if expires_at:
273
+ payload["expires_at"] = expires_at
274
+ if sender_type:
275
+ payload["sender_type"] = sender_type
276
+ if sender_name:
277
+ payload["sender_name"] = sender_name
278
+ response: httpx.Response = httpx.post(
279
+ f"{self.api_url}/v1/agent-messages/",
280
+ json=payload,
281
+ headers=self._agent_headers(),
282
+ timeout=self.timeout,
283
+ )
284
+ return self._handle_response(response)
285
+
286
+ def get_agent_messages(
287
+ self,
288
+ agent_name: str,
289
+ delivered: Optional[bool] = None,
290
+ ) -> list[dict[str, Any]]:
291
+ """GET /v1/agent-messages/?agent_name=..."""
292
+ params: dict[str, str] = {"agent_name": agent_name}
293
+ if delivered is not None:
294
+ params["delivered"] = "true" if delivered else "false"
295
+ response: httpx.Response = httpx.get(
296
+ f"{self.api_url}/v1/agent-messages/",
297
+ params=params,
298
+ headers=self._agent_headers(),
299
+ timeout=self.timeout,
300
+ )
301
+ if response.status_code >= 400:
302
+ self._handle_response(response)
303
+ return response.json() # type: ignore[no-any-return]
@@ -0,0 +1 @@
1
+ """DailyBot CLI commands."""