Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .agents/skills/dailybot/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ machine — permissions, consent guarantees, and a self-audit you can run — is

## What it does

Thirteen coordinated capabilities, with smart routing between them:
Fourteen coordinated capabilities, with smart routing between them:

Copy link
Copy Markdown

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-labels to .agents/docs/skills_agents_catalog.md — not in this diff).


| Capability | Sub-skill | When it fires |
|------------|-----------|---------------|
Expand All @@ -67,6 +67,7 @@ Thirteen coordinated capabilities, with smart routing between them:
| **Teams** | `dailybot-teams` | List teams, inspect members, resolve a team name → UUID (used as a resolver by other skills) — **plus account context**: `dailybot me` (who am I / role), `dailybot org` (which org), and `dailybot user get` (one user's profile) |
| **Forms** | `dailybot-forms` | List, submit, update, or transition forms — including workflow-state forms with audience permissions (`form list` is now **org-scoped** by default, with `--mine` to narrow to your own; list + responses support pagination / search / date filters) — **plus authoring**: create/configure a form (workflow states, permissions, anonymous/public/approval, ChatOps command) and manage its questions |
| **Workflows** | `dailybot-workflow` | Developer wants to **read or trigger** the org's workflows — `workflow list` (paginated/searchable, with `--filter api_trigger`), `workflow get`, and `workflow trigger` (fire an API-triggerable workflow with an optional JSON payload). Creating/editing workflows is web-app only. Plan-gated |
| **Labels** | `dailybot-labels` | Create org Labels and assign them to forms, check-ins, and workflows (`label assign` / `label batch`) — same chip picker as the web app |
| **Report channels** | `dailybot-channels` | Discover report-channel UUIDs to attach to forms/check-ins with `--report-channel` |
| **Per-repo API keys** | `dailybot-env` | Configure `.dailybot/env.json` — an **opt-in, gitignored** file that carries API keys + URLs for one or more environments (live, local, staging) so the developer can be "logged into different orgs in different repos". `dailybot env add / use / show / list / remove / off / on`. Pack baseline (`>= 3.8.0`) |

Expand Down Expand Up @@ -242,6 +243,7 @@ the full step-by-step workflow.
| "list / search / browse my forms (or kudos, or workflows) with pagination", "only the first N", "since last week", "grep for retro" | The matching sub-skill — all share [`shared/list-query-and-errors.md`](shared/list-query-and-errors.md) for the query flags |
| "who am I?", "what's my role?", "which org am I in?", "show a user's profile" | **Teams** → read [`teams/SKILL.md`](teams/SKILL.md) § Step 4.5 (`me` / `org` / `user get`) |
| "browse kudos", "kudos I received / gave", "org kudos stats", "who's on the wall of fame?" | **Kudos** → read [`kudos/SKILL.md`](kudos/SKILL.md) § Browsing kudos |
| "add a label to a form / check-in / workflow", "tag this standup with Sprint", "assign organization labels" | **Labels** → read [`labels/SKILL.md`](labels/SKILL.md) |
| "list my workflows", "show workflows", "what's in workflow X?" | **Workflows** → read [`workflow/SKILL.md`](workflow/SKILL.md) |
| "trigger the deploy workflow", "fire automation X", "run workflow `<uuid>`", "trigger workflow with payload" | **Workflows** → read [`workflow/SKILL.md`](workflow/SKILL.md) § Step 4 (Trigger) |
| "which channels can Dailybot post to?", "list report channels", "I need a channel UUID for the form / check-in" | **Channels** → read [`channels/SKILL.md`](channels/SKILL.md) |
Expand Down
69 changes: 69 additions & 0 deletions .agents/skills/dailybot/labels/SKILL.md
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 main / PyPI it will mislead agents into looking for a feature branch.

Replace with a dailybot-cli >= X.Y.Z floor (or "requires a CLI build that includes label assign") once the release version is known.

`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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 https://staging-api.dailybot.com, while AGENTS.md, docs/CONFIGURATION.md, and other skills use https://staging.dailybot.com. Failure mode: agents copy the skill's URL and get connection failures against the documented staging host.

Align on the same host the rest of the CLI docs use (staging.dailybot.com), unless staging-api is the intentionally correct API host — then update the other docs instead.

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.
209 changes: 209 additions & 0 deletions dailybot_cli/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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] = {}
Expand All @@ -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,
Expand Down Expand Up @@ -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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enrichment kwargs never reach the Click list commands.

list_forms / list_checkins / list_workflows accept labels / featured / prioritize_featured, but execute_form_list, form list, check-in listing, and workflow list still call the client without those kwargs — no --labels / --featured / --prioritize-featured flags exist. Failure mode: CORE-2362 filtering is unreachable from the CLI users run; only a direct DailyBotClient caller can use it.

Wire the flags through the list commands (and execute_form_list) or narrow the PR description to "client prep only" so agents do not assume dailybot form list --labels … works.

featured: bool | None = None,
prioritize_featured: bool | None = None,
) -> list[dict[str, Any]]:
"""GET /v1/forms/ — optionally expand questions, search, and page.

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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]:
Expand Down
Loading
Loading