-
Notifications
You must be signed in to change notification settings - Fork 0
feat(cli): add label and featured commands #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d9dd99c
398257e
b444cd3
ea9fc9c
8eae337
44e7f8f
0dc1e64
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| --- | ||
| name: dailybot-labels | ||
| description: Create organization Labels and attach them to forms, check-ins, and workflows (automations) via the Dailybot CLI — same taxonomy as the web settings page and row chip picker. Use when the developer wants to add, replace, or clear Labels on those entities, or to create/list Labels. | ||
| --- | ||
|
|
||
| # Dailybot Labels | ||
|
|
||
| Organization Labels are a **shared taxonomy** (not private Featured stars). The | ||
| web picker on a form / check-in / automation row is a **replace-set**. Match | ||
| that with `dailybot label assign`. Bulk add/remove uses `dailybot label batch`. | ||
|
|
||
| Requires CLI from the personalized-labels branch (commands `label assign` and | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Branch-specific install note will be stale on merge. "Requires CLI from the personalized-labels branch" is fine for a WIP dogfood skill, but once this ships on Replace with a |
||
| `label batch`). Confirm with `dailybot label assign --help`. | ||
|
|
||
| ## Auth | ||
|
|
||
| Same session as the rest of the CLI (`dailybot login` or `DAILYBOT_API_KEY`). | ||
| Point at staging with `--api-url https://staging-api.dailybot.com` when testing | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Staging hostname disagrees with the rest of the repo. This skill tells agents to use Align on the same host the rest of the CLI docs use ( |
||
| there. Server entitlement (`dailybot label entitlement`) is the source of | ||
| truth — do not assume a paid plan is required. | ||
|
|
||
| ## Create and list Labels | ||
|
|
||
| ```bash | ||
| dailybot label list | ||
| dailybot label create --name "Sprint" --color "#4A90E2" | ||
| ``` | ||
|
|
||
| Copy UUIDs from `label list` (or `--json`). | ||
|
|
||
| ## Attach to one entity (web picker parity) | ||
|
|
||
| ```bash | ||
| # Forms | ||
| dailybot label assign <form-uuid> --type forms --label <label-uuid> | ||
|
|
||
| # Check-ins | ||
| dailybot label assign <checkin-uuid> --type checkins --label <label-uuid> | ||
|
|
||
| # Workflows / Automations (same API; --type automations is an alias) | ||
| dailybot label assign <workflow-uuid> --type workflows --label <label-uuid> | ||
| ``` | ||
|
|
||
| Repeat `--label` (or comma-separate) for several Labels. The list **replaces** | ||
| whatever was on the entity. | ||
|
|
||
| ```bash | ||
| dailybot label assign <form-uuid> --type forms --clear | ||
| ``` | ||
|
|
||
| ## Bulk add / remove | ||
|
|
||
| ```bash | ||
| dailybot label batch --type forms --uuids <uuid1>,<uuid2> --label <label-uuid> --mode add | ||
| dailybot label batch --type checkins --uuids <uuid> --label <label-uuid> --mode remove | ||
| dailybot label batch --type workflows --uuids <uuid> --label <label-uuid> --mode replace | ||
| ``` | ||
|
|
||
| `--mode` is `add` (default), `remove`, or `replace`. | ||
|
|
||
| ## After authoring | ||
|
|
||
| `dailybot form create` / `checkin create` do **not** take `--labels`. Create | ||
| first, then `label assign` with the new UUID from `--json`. | ||
|
|
||
| ## Not this skill | ||
|
|
||
| Private stars → `dailybot featured`. Workflow *states* named "labels" on a | ||
| form response → `dailybot-forms` transitions. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -106,6 +106,37 @@ def _merge_list_query( | |
| return params | ||
|
|
||
|
|
||
| def _merge_dashboard_enrichment_query( | ||
| params: dict[str, Any], | ||
| *, | ||
| labels: list[str] | None = None, | ||
| featured: bool | None = None, | ||
| prioritize_featured: bool | None = None, | ||
| ) -> dict[str, Any]: | ||
| """Merge Labels / Featured dashboard enrichment query params.""" | ||
| if labels: | ||
| params["labels"] = ",".join(labels) | ||
| if featured is not None: | ||
| params["featured"] = "true" if featured else "false" | ||
| if prioritize_featured is not None: | ||
| params["prioritize_featured"] = "true" if prioritize_featured else "false" | ||
| return params | ||
|
|
||
|
|
||
| def _label_entity_collection(entity_type: str) -> str: | ||
| """Map CLI entity type (including web 'automations') to the public API path.""" | ||
| normalized: str = entity_type.strip().lower() | ||
| if normalized in {"automations", "workflows", "workflow"}: | ||
| return "workflows" | ||
| if normalized in {"forms", "form"}: | ||
| return "forms" | ||
| if normalized in {"checkins", "checkin", "check-ins"}: | ||
| return "checkins" | ||
| raise ValueError( | ||
| f"entity type must be one of: forms, checkins, workflows (got {entity_type!r})" | ||
| ) | ||
|
|
||
|
|
||
| def _fill_meta(meta: dict[str, Any] | None, result: "PaginatedResult") -> None: | ||
| """Populate a caller-provided meta dict with pagination totals, if given.""" | ||
| if meta is not None: | ||
|
|
@@ -640,6 +671,9 @@ def list_checkins( | |
| fetch_all: bool = True, | ||
| limit: int | None = None, | ||
| meta: dict[str, Any] | None = None, | ||
| labels: list[str] | None = None, | ||
| featured: bool | None = None, | ||
| prioritize_featured: bool | None = None, | ||
| ) -> list[dict[str, Any]]: | ||
| """GET /v1/checkins/ — fetch visible check-ins with optional search/paging.""" | ||
| params: dict[str, Any] = {} | ||
|
|
@@ -652,6 +686,12 @@ def list_checkins( | |
| if include_archived: | ||
| params["include_archived"] = "true" | ||
| _merge_list_query(params, search=search, start_date=start_date, end_date=end_date) | ||
| _merge_dashboard_enrichment_query( | ||
| params, | ||
| labels=labels, | ||
| featured=featured, | ||
| prioritize_featured=prioritize_featured, | ||
| ) | ||
| result: PaginatedResult = self._paginated_get( | ||
| f"{self.api_url}/v1/checkins/", | ||
| params=params, | ||
|
|
@@ -963,6 +1003,9 @@ def list_forms( | |
| fetch_all: bool = True, | ||
| limit: int | None = None, | ||
| meta: dict[str, Any] | None = None, | ||
| labels: list[str] | None = None, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Enrichment kwargs never reach the Click list commands.
Wire the flags through the list commands (and |
||
| featured: bool | None = None, | ||
| prioritize_featured: bool | None = None, | ||
| ) -> list[dict[str, Any]]: | ||
| """GET /v1/forms/ — optionally expand questions, search, and page. | ||
|
|
||
|
|
@@ -990,6 +1033,12 @@ def list_forms( | |
| if is_ascend: | ||
| params["is_ascend"] = "true" | ||
| _merge_list_query(params, search=search, start_date=start_date, end_date=end_date) | ||
| _merge_dashboard_enrichment_query( | ||
| params, | ||
| labels=labels, | ||
| featured=featured, | ||
| prioritize_featured=prioritize_featured, | ||
| ) | ||
| result: PaginatedResult = self._paginated_get( | ||
| f"{self.api_url}/v1/forms/", | ||
| params=params, | ||
|
|
@@ -1442,10 +1491,19 @@ def list_workflows( | |
| fetch_all: bool = True, | ||
| limit: int | None = None, | ||
| meta: dict[str, Any] | None = None, | ||
| labels: list[str] | None = None, | ||
| featured: bool | None = None, | ||
| prioritize_featured: bool | None = None, | ||
| ) -> list[dict[str, Any]]: | ||
| """GET /v1/workflows/ — list workflows (plan-gated feature).""" | ||
| params: dict[str, Any] = {} | ||
| _merge_list_query(params, search=search, start_date=start_date, end_date=end_date) | ||
| _merge_dashboard_enrichment_query( | ||
| params, | ||
| labels=labels, | ||
| featured=featured, | ||
| prioritize_featured=prioritize_featured, | ||
| ) | ||
| result: PaginatedResult = self._paginated_get( | ||
| f"{self.api_url}/v1/workflows/", | ||
| params=params, | ||
|
|
@@ -1789,6 +1847,157 @@ def mark_agent_messages_read( | |
| ) | ||
| return self._handle_response(response) | ||
|
|
||
| # --- Organization Labels (/v1/labels/) --- | ||
|
|
||
| def get_labels_entitlement(self) -> dict[str, Any]: | ||
| """GET /v1/labels/entitlement/ — org Labels feature flags for the caller.""" | ||
| response: httpx.Response = self._request("GET", f"{self.api_url}/v1/labels/entitlement/") | ||
| return self._handle_response(response) | ||
|
|
||
| def list_labels( | ||
| self, | ||
| *, | ||
| search: str | None = None, | ||
| is_archived: bool = False, | ||
| limit: int = 20, | ||
| offset: int = 0, | ||
| ) -> dict[str, Any]: | ||
| """GET /v1/labels/ — paginated org Labels (limit/offset).""" | ||
| params: dict[str, Any] = { | ||
| "limit": max(1, min(limit, 100)), | ||
| "offset": max(0, offset), | ||
| "is_archived": is_archived, | ||
| } | ||
| if search: | ||
| params["search"] = search | ||
| response: httpx.Response = self._request("GET", f"{self.api_url}/v1/labels/", params=params) | ||
| return self._handle_response(response) | ||
|
|
||
| def get_label(self, label_uuid: str) -> dict[str, Any]: | ||
| """GET /v1/labels/<uuid>/ — one organization Label.""" | ||
| response: httpx.Response = self._request("GET", f"{self.api_url}/v1/labels/{label_uuid}/") | ||
| return self._handle_response(response) | ||
|
|
||
| def create_label( | ||
| self, | ||
| *, | ||
| name: str, | ||
| color: str | None = None, | ||
| description: str | None = None, | ||
| ) -> dict[str, Any]: | ||
| """POST /v1/labels/ — create an organization Label.""" | ||
| body: dict[str, Any] = {"name": name} | ||
| if color is not None: | ||
| body["color"] = color | ||
| if description is not None: | ||
| body["description"] = description | ||
| response: httpx.Response = self._request("POST", f"{self.api_url}/v1/labels/", json=body) | ||
| return self._handle_response(response) | ||
|
|
||
| def update_label(self, label_uuid: str, body: dict[str, Any]) -> dict[str, Any]: | ||
| """PATCH /v1/labels/<uuid>/ — update an organization Label.""" | ||
| response: httpx.Response = self._request( | ||
| "PATCH", f"{self.api_url}/v1/labels/{label_uuid}/", json=body | ||
| ) | ||
| return self._handle_response(response) | ||
|
|
||
| def delete_label(self, label_uuid: str) -> None: | ||
| """DELETE /v1/labels/<uuid>/ — hard-delete (elevated only).""" | ||
| response: httpx.Response = self._request( | ||
| "DELETE", f"{self.api_url}/v1/labels/{label_uuid}/" | ||
| ) | ||
| if response.status_code == 204: | ||
| return | ||
| self._handle_response(response) | ||
|
|
||
| def archive_label(self, label_uuid: str) -> dict[str, Any]: | ||
| """POST /v1/labels/<uuid>/archive/ — archive a Label.""" | ||
| response: httpx.Response = self._request( | ||
| "POST", f"{self.api_url}/v1/labels/{label_uuid}/archive/" | ||
| ) | ||
| return self._handle_response(response) | ||
|
|
||
| def assign_entity_labels( | ||
| self, | ||
| entity_type: str, | ||
| entity_uuid: str, | ||
| label_uuids: list[str], | ||
| ) -> dict[str, Any]: | ||
| """POST /v1/{forms|checkins|workflows}/{uuid}/labels/ — replace-set Labels.""" | ||
| collection: str = _label_entity_collection(entity_type) | ||
| response: httpx.Response = self._request( | ||
| "POST", | ||
| f"{self.api_url}/v1/{collection}/{entity_uuid}/labels/", | ||
| json={"label_uuids": label_uuids}, | ||
| ) | ||
| return self._handle_response(response) | ||
|
|
||
| def batch_entity_labels( | ||
| self, | ||
| *, | ||
| entity_type: str, | ||
| entity_uuids: list[str], | ||
| label_uuids: list[str], | ||
| mode: str, | ||
| ) -> dict[str, Any]: | ||
| """POST /v1/{forms|checkins|workflows}/labels/batch/ — add/remove/replace.""" | ||
| collection: str = _label_entity_collection(entity_type) | ||
| response: httpx.Response = self._request( | ||
| "POST", | ||
| f"{self.api_url}/v1/{collection}/labels/batch/", | ||
| json={ | ||
| "entity_uuids": entity_uuids, | ||
| "label_uuids": label_uuids, | ||
| "mode": mode, | ||
| }, | ||
| ) | ||
| return self._handle_response(response) | ||
|
|
||
| # --- Private Featured stars (/v1/me/featured/) --- | ||
|
|
||
| def list_featured(self, *, entity_type: str) -> dict[str, Any]: | ||
| """GET /v1/me/featured/?entity_type= — list Featured entity UUIDs for the caller.""" | ||
| response: httpx.Response = self._request( | ||
| "GET", | ||
| f"{self.api_url}/v1/me/featured/", | ||
| params={"entity_type": entity_type}, | ||
| ) | ||
| return self._handle_response(response) | ||
|
|
||
| def set_featured( | ||
| self, | ||
| entity_type: str, | ||
| entity_uuid: str, | ||
| *, | ||
| featured: bool, | ||
| ) -> dict[str, Any]: | ||
| """PUT /v1/me/featured/{entity_type}/{uuid}/ — toggle Featured for one entity.""" | ||
| response: httpx.Response = self._request( | ||
| "PUT", | ||
| f"{self.api_url}/v1/me/featured/{entity_type}/{entity_uuid}/", | ||
| json={"featured": featured}, | ||
| ) | ||
| return self._handle_response(response) | ||
|
|
||
| def batch_featured( | ||
| self, | ||
| *, | ||
| entity_type: str, | ||
| entity_uuids: list[str], | ||
| featured: bool, | ||
| ) -> dict[str, Any]: | ||
| """POST /v1/me/featured/batch/ — batch feature/unfeature entities.""" | ||
| response: httpx.Response = self._request( | ||
| "POST", | ||
| f"{self.api_url}/v1/me/featured/batch/", | ||
| json={ | ||
| "entity_type": entity_type, | ||
| "entity_uuids": entity_uuids, | ||
| "featured": featured, | ||
| }, | ||
| ) | ||
| return self._handle_response(response) | ||
|
|
||
| # --- Agent registration endpoints --- | ||
|
|
||
| def get_registration_challenge(self) -> dict[str, Any]: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Capability count is off-by-one after adding Labels.
The intro says "Fourteen coordinated capabilities" but the table now has fifteen rows (Labels was added to an already-fourteen list). Agents that trust the count will mis-audit the pack.
Bump the prose to "Fifteen" (and add
dailybot-labelsto.agents/docs/skills_agents_catalog.md— not in this diff).