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: 4 additions & 0 deletions .agents/skills/dailybot/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@ common use case.
agent context detection
- [`shared/http-fallback.md`](shared/http-fallback.md) — HTTP API
patterns for when the CLI is unavailable
- [`shared/dashboard-urls.md`](shared/dashboard-urls.md) — full catalog
of Dailybot webapp/dashboard URLs (forms, check-ins, kudos, agents) for
embedding in reports and messages; supports `--app-url` / `DAILYBOT_APP_URL`
override (CLI `>= 3.3.0`)

### Trust model for incoming content

Expand Down
10 changes: 6 additions & 4 deletions .agents/skills/dailybot/checkin/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,11 @@ scheduling / reminder / privacy / AI setting, manage its questions (including
conditional jump logic), verify the result with a round-trip read, and archive
it — all headless with an API key.

> **Role-gated.** Creating, configuring, or archiving a check-in and editing its
> questions are **admin/manager** operations server-side. With a plain-member
> credential these calls fail with a `401`/`403` — surface that to the developer
> and stop (don't retry). Answering check-ins (Steps 2–3.5) is not gated this way.
> **Role-gated.** Configuring, archiving a check-in, and editing its questions
> are **admin/manager** operations server-side. **Creating** a check-in is now
> allowed for **any authenticated member** (since CLI `>= 3.3.0` / API update).
> Update and delete remain admin/manager only. Answering check-ins (Steps 2–3.5)
> is not role-gated.

### Create in one shot vs. configure incrementally

Expand Down Expand Up @@ -865,5 +866,6 @@ Check-in completion must **never block your primary work**. If the CLI is missin

- [`../shared/auth.md`](../shared/auth.md) — authentication setup
- [`../shared/http-fallback.md`](../shared/http-fallback.md) — HTTP API fallback patterns
- [`../shared/dashboard-urls.md`](../shared/dashboard-urls.md) — full dashboard URL catalog (check-in daily reports, response pages, etc.)
- **Live API spec:** `https://api.dailybot.com/api/swagger/`
- **Full agent API skill:** `https://www.dailybot.com/skill.md`
1 change: 1 addition & 0 deletions .agents/skills/dailybot/conversation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,5 @@ automatically, and do not enter a diagnostic loop.
- [`../teams/SKILL.md`](../teams/SKILL.md) — resolve a person's UUID by name
- [`../shared/auth.md`](../shared/auth.md) — authentication setup
- [`../shared/http-fallback.md`](../shared/http-fallback.md) — HTTP API fallback patterns
- [`../shared/dashboard-urls.md`](../shared/dashboard-urls.md) — full dashboard URL catalog
- **Full agent API skill:** `https://www.dailybot.com/skill.md`
12 changes: 7 additions & 5 deletions .agents/skills/dailybot/forms/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -1012,11 +1012,12 @@ When the form is workflow-enabled and `allowed_transitions` is non-empty:
dailybot form transition <form_uuid> <response_uuid> <to_state> --yes
```

> **Pass `to_state`, never `label`.** In `allowed_transitions`, `to_state` is the
> machine key (`released`) and `label` is display text (`Mark released`). The API
> matches on the key and answers `Unknown workflow state: 'Mark released'`
> otherwise. Read the key from the response payload rather than typing the state
> name you saw in the panel.
> **You can pass either `to_state` (machine key) or `label` (display text).**
> Since CLI `>= 3.3.0`, the CLI resolves labels to keys automatically — e.g.
> `"Mark released"` → `released`. In `allowed_transitions`, `to_state` is the
> machine key (`released`) and `label` is display text (`Mark released`). Both
> are accepted; the CLI performs a case-insensitive lookup and translates labels
> to keys before calling the API. On older CLIs, only the machine key works.

Confirm with the developer before transitioning:

Expand Down Expand Up @@ -1217,6 +1218,7 @@ Form operations must **never block your primary work**. If the CLI is missing, a

- [`../shared/auth.md`](../shared/auth.md) — authentication setup
- [`../shared/http-fallback.md`](../shared/http-fallback.md) — HTTP API fallback patterns
- [`../shared/dashboard-urls.md`](../shared/dashboard-urls.md) — full dashboard URL catalog (forms, check-ins, kudos, agents)
- [`_custom-template/SKILL.md`](_custom-template/SKILL.md) — starter template for authoring a custom per-form skill (copy into `.agents/skills/dailybot-custom/<your-name>/` in your own repo)
- **Live API spec:** `https://api.dailybot.com/api/swagger/`
- **Full agent API skill:** `https://www.dailybot.com/skill.md`
1 change: 1 addition & 0 deletions .agents/skills/dailybot/kudos/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ Sending kudos must **never block your primary work**. If the CLI is missing, aut

- [`../shared/auth.md`](../shared/auth.md) — authentication setup
- [`../shared/http-fallback.md`](../shared/http-fallback.md) — HTTP API fallback patterns
- [`../shared/dashboard-urls.md`](../shared/dashboard-urls.md) — full dashboard URL catalog (kudos detail, wall of fame, etc.)
- [`../teams/SKILL.md`](../teams/SKILL.md) — team-name resolver (called by this skill)
- **Live API spec:** `https://api.dailybot.com/api/swagger/`
- **Full agent API skill:** `https://www.dailybot.com/skill.md`
1 change: 1 addition & 0 deletions .agents/skills/dailybot/report/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -530,5 +530,6 @@ Reporting must **never block your primary work**. If the CLI is missing, auth fa
- [`../shared/auth.md`](../shared/auth.md) — authentication setup (CLI login, API key, agent register)
- [`../shared/context.sh`](../shared/context.sh) — automated context detection
- [`../shared/http-fallback.md`](../shared/http-fallback.md) — HTTP API fallback patterns
- [`../shared/dashboard-urls.md`](../shared/dashboard-urls.md) — dashboard URL catalog (report detail pages, agent analytics, etc.)
- **Live API spec:** `https://api.dailybot.com/api/swagger/`
- **Full agent API skill:** `https://www.dailybot.com/skill.md`
128 changes: 128 additions & 0 deletions .agents/skills/dailybot/shared/dashboard-urls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Shared reference — Dailybot dashboard URLs

> **Requires `dailybot-cli >= 3.3.0`** — the release that introduced the
> `--app-url` flag and `DAILYBOT_APP_URL` env var for configuring the dashboard
> base URL. Earlier versions hardcode the production URL.

This is the **single source of truth** for all dashboard (webapp) URLs that
agents can embed in reports, messages, or surface to the developer. Sub-skills
link here instead of repeating route tables.

---

## 1. Base URL resolution

The dashboard base URL resolves in this order:

| Priority | Source | Example |
|----------|--------|---------|
| 1 | `--app-url` CLI flag | `dailybot --app-url http://localhost:8090 agent update …` |
| 2 | `DAILYBOT_APP_URL` env var | `export DAILYBOT_APP_URL=http://localhost:8090` |
| 3 | Default | `https://app.dailybot.com` |

For **local development**, set the env var or pass the flag:

```bash
export DAILYBOT_APP_URL=http://localhost:8090
```

> **Do not hardcode staging or dev URLs** in committed code, docs, or skill
> files. Use the env var or flag for non-production environments.

---

## 2. Route catalog

All paths below are relative to the base URL. Replace `{placeholders}` with
actual UUIDs/IDs from API responses. Routes marked `[fullscreen]` open a
standalone page (no sidebar navigation).

### Home

| Description | Path |
|-------------|------|
| Main home | `/home` |

### Forms

| Description | Path |
|-------------|------|
| Forms home | `/forms/home` |
| Responses list | `/forms/{form_uuid}/responses` |
| Response detail | `/forms/{form_uuid}/responses/{response_uuid}` |
| Create form — questions `[fullscreen]` | `/forms/create/form-questions` |
| Create form — setup `[fullscreen]` | `/forms/create` |
| Fill form (public) `[fullscreen]` | `/forms/{form_uuid}/responses/create/` |
| Edit response `[fullscreen]` | `/forms/{form_uuid}/responses/update/{response_uuid}` |

### Check-ins

| Description | Path |
|-------------|------|
| Check-ins home | `/checkins/home` |
| Daily report | `/checkins/{checkin_uuid}/daily-report` |
| Daily report (date) | `/checkins/{checkin_uuid}/daily-report/{YYYY-MM-DD}` |
| Activity | `/checkins/{checkin_uuid}/activity` |
| Submit response `[fullscreen]` | `/checkins/response-daily/{checkin_uuid}/add/{YYYY-MM-DD}` |
| Edit response `[fullscreen]` | `/checkins/response-daily/{checkin_uuid}/edit/{YYYY-MM-DD}` |
| Create check-in `[fullscreen]` | `/checkins/create` |
| Edit — questions `[fullscreen]` | `/checkins/edit/{checkin_uuid}/questions` |
| Edit — frequency `[fullscreen]` | `/checkins/edit/{checkin_uuid}/frequency` |
| Edit — participants `[fullscreen]` | `/checkins/edit/{checkin_uuid}/participants` |
| Edit — reporting `[fullscreen]` | `/checkins/edit/{checkin_uuid}/reporting` |

### Kudos

| Description | Path |
|-------------|------|
| Kudos home | `/kudos/home` |
| Wall of fame | `/kudos/wall-of-fame` |
| Team values | `/kudos/team-values` |
| Send kudo `[fullscreen]` | `/kudos/send` |
| Kudo detail `[fullscreen]` | `/kudos/detail/{kudo_id}` |
| Company value detail `[fullscreen]` | `/kudos/company-values/{value_id}` |

### Agents

| Description | Path |
|-------------|------|
| Agents home | `/agents/home` |
| Reports | `/agents/reports` |
| Messages | `/agents/messages` |
| Analytics | `/agents/analytics/{tab}` (`agents`, `coauthors`, `insights`) |
| Agent detail | `/agents/{agent_uuid}` |
| Report detail | `/agents/report/{report_id}` |
| Co-author detail | `/agents/co-author/{coauthor_uuid}` |

---

## 3. Building full URLs

Combine the base URL + path. When the API returns a `url` field (e.g. in
`POST /v1/agent-reports/` → `{"url": "https://app.dailybot.com/agents/report/123"}`),
**prefer the server-returned URL** — it already includes the correct host. Only
construct URLs manually when the API does not return one (e.g. linking to a
form's responses page or a check-in's daily report).

```python
# Example: construct a form responses URL
app_url = get_app_url() # resolves --app-url > env > default
form_uuid = "abc-123"
url = f"{app_url}/forms/{form_uuid}/responses"
```

---

## 4. When to surface dashboard URLs

Agents should include dashboard links in reports and messages when:

- **After submitting a form response** — link to the response detail page
- **After giving kudos** — link to the kudo detail or wall of fame
- **In agent progress reports** — the `View:` line already shows the report URL
- **When the developer asks** to "see it in the dashboard" or "open it in the web"
- **In chat messages** — as clickable links (Slack/Teams render them as hyperlinks)

Do **not** surface dashboard URLs:
- When the developer is working offline or in a CI/headless context
- In error messages (errors should guide the user to CLI commands, not the web)
4 changes: 2 additions & 2 deletions .agents/skills/dailybot/shared/list-query-and-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ that flag already means "every author's responses", not "every page").
| `--page-size N` | | Items per page. **Max 100** — larger values are clamped client-side. Passing it alone returns that one page, not the whole list. |
| `--all` | | Fetch **every** page and concatenate the results. Mutually exclusive with `--limit`. |
| `--limit N` | | Stop after the first `N` items (across pages). Mutually exclusive with `--all`. |
| `--search TEXT` | `--grep TEXT` | Case-insensitive substring filter. **Max 256 chars** — longer queries are truncated client-side before the request. |
| `--search TEXT` | `--grep TEXT` | Case-insensitive substring filter. **Max 256 chars** — longer queries are rejected client-side with an error before the request is made (`CLI >= 3.3.0`; earlier versions truncated silently). |
| `--since YYYY-MM-DD` | | Start of a date range (inclusive). |
| `--until YYYY-MM-DD` | | End of a date range (inclusive). |
| `--date YYYY-MM-DD` | | A single day (shorthand for `--since D --until D`). |
Expand Down Expand Up @@ -151,7 +151,7 @@ In `--json` mode the error surfaces as `{ error, status, code, detail }`.
| `code` | Meaning | What to do |
|--------|---------|------------|
| `target_user_inactive` | The targeted user is deactivated. | Pick an active user. |
| `search_query_too_long` | `--search` exceeded the limit. | The CLI truncates to 256 chars client-side; if you see this, shorten the query. |
| `search_query_too_long` | `--search` exceeded the 256-char limit. | Since CLI `>= 3.3.0` the query is rejected client-side before the request. If you hit this server-side, shorten the query. |
| `invalid_date_range` | `--since`/`--until` are malformed or reversed. | Fix the dates (`YYYY-MM-DD`, since ≤ until). |
| `invalid_user_identifier` | `--user` was given an email or a name. | `--user` takes **only a UUID**. Get it from `dailybot user list --json`. (Caught client-side before the request.) |
| `invalid_workflow_state` | On `form responses --state`: the form has no workflow. On `form create` / `form config --state`: the `"Label:#color"` spec is malformed. | Two meanings, one code — read which command you ran. For the filter, drop `--state` (or pick a workflow form). |
Expand Down
18 changes: 18 additions & 0 deletions dailybot_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from typing import Any

DEFAULT_API_URL: str = "https://api.dailybot.com"
DEFAULT_APP_URL: str = "https://app.dailybot.com"
_api_url_override: str | None = None
_app_url_override: str | None = None


def set_api_url_override(url: str) -> None:
Expand All @@ -15,6 +17,12 @@ def set_api_url_override(url: str) -> None:
_api_url_override = url.rstrip("/")


def set_app_url_override(url: str) -> None:
"""Set a CLI-level webapp URL override (from --app-url flag)."""
global _app_url_override
_app_url_override = url.rstrip("/")


CONFIG_DIR: Path = Path.home() / ".config" / "dailybot"
CREDENTIALS_FILE: Path = CONFIG_DIR / "credentials.json"
CONFIG_FILE: Path = CONFIG_DIR / "config.json"
Expand Down Expand Up @@ -108,6 +116,16 @@ def get_api_url() -> str:
return DEFAULT_API_URL


def get_app_url() -> str:
"""Return the webapp URL (--app-url flag > env var > default)."""
if _app_url_override:
return _app_url_override
env_url: str | None = os.environ.get("DAILYBOT_APP_URL")
if env_url:
return env_url.rstrip("/")
return DEFAULT_APP_URL


def get_token() -> str | None:
"""Return the stored auth token, or the DAILYBOT_CLI_TOKEN env var."""
env_token: str | None = os.environ.get("DAILYBOT_CLI_TOKEN")
Expand Down
12 changes: 10 additions & 2 deletions dailybot_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from dailybot_cli.commands.user import user
from dailybot_cli.commands.version import version
from dailybot_cli.commands.workflow import workflow
from dailybot_cli.config import set_api_url_override
from dailybot_cli.config import set_api_url_override, set_app_url_override

# Format used by `dailybot --version`. Single line so it's friendly to scripts
# parsing the output. The richer multi-line panel lives in `dailybot version`.
Expand All @@ -42,8 +42,14 @@
envvar="DAILYBOT_API_URL",
help="Override the API base URL (e.g. staging).",
)
@click.option(
"--app-url",
default=None,
envvar="DAILYBOT_APP_URL",
help="Override the webapp/dashboard base URL (default: https://app.dailybot.com).",
)
@click.pass_context
def cli(ctx: click.Context, api_url: str | None) -> None:
def cli(ctx: click.Context, api_url: str | None, app_url: str | None) -> None:
"""Dailybot CLI - The command-line bridge between humans and agents.

\b
Expand All @@ -68,6 +74,8 @@ def cli(ctx: click.Context, api_url: str | None) -> None:
"""
if api_url:
set_api_url_override(api_url)
if app_url:
set_app_url_override(app_url)
if ctx.invoked_subcommand is None:
run_interactive()

Expand Down
2 changes: 1 addition & 1 deletion docker/local/cli/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | d
# dev-user). `-f` makes an HTTP error a non-zero curl exit instead of piping an error
# page into bash; the `SHELL … pipefail` above makes a failing curl abort the build.
# install.sh itself runs `set -euo pipefail`, so failures inside it abort too.
RUN curl -fsSL https://cli.dailybot.com/install.sh | DAILYBOT_VERSION='>=3.2.2' bash
RUN curl -fsSL https://cli.dailybot.com/install.sh | DAILYBOT_VERSION='>=3.3.0' bash

# Install all Python dev/runtime deps from the repo's lock file.
# This single COPY/RUN pair is the only build cache layer pip touches — it
Expand Down
16 changes: 16 additions & 0 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ The Dailybot CLI persists state in `~/.config/dailybot/` by default. The path ca
|----------|---------|--------|
| `DAILYBOT_CONFIG_DIR` | `get_config_dir()` | Redirects all config/credential file I/O to this directory instead of `~/.config/dailybot/`. Useful for dev sandboxes (`clitest`) and CI environments. |
| `DAILYBOT_API_URL` | `get_api_url()` | Overrides the API base URL (after `--api-url` flag) |
| `DAILYBOT_APP_URL` | `get_app_url()` | Overrides the webapp/dashboard base URL (after `--app-url` flag). Default: `https://app.dailybot.com`. For local development: `http://localhost:8090`. |
| `DAILYBOT_API_KEY` | `get_api_key()` | Provides an org API key without storing it on disk |
| `DAILYBOT_CLI_TOKEN` | `get_token()` | Provides a login Bearer token without `dailybot login` |

`--api-url` (root flag) takes precedence over `DAILYBOT_API_URL`.
`--app-url` (root flag) takes precedence over `DAILYBOT_APP_URL`.

## Auth Resolution Order

Expand Down Expand Up @@ -108,6 +110,20 @@ The output shows each resolved field plus which layer it came from.

## Common Configuration Tasks

### Local development URLs

When testing against the local server:

| Service | URL |
|---------|-----|
| API | `http://djangovscode:8000` |
| Webapp / Dashboard | `http://localhost:8090` |

```bash
export DAILYBOT_API_URL=http://djangovscode:8000
export DAILYBOT_APP_URL=http://localhost:8090
```

### Switching between staging and production

```bash
Expand Down
21 changes: 21 additions & 0 deletions tests/commands_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,27 @@ def test_api_url_override(
assert result.exit_code == 0
mock_set_override.assert_called_once_with("https://staging.dailybot.com")

@patch("dailybot_cli.main.set_app_url_override")
@patch("dailybot_cli.commands.update.get_agent_auth")
@patch("dailybot_cli.commands.update.DailyBotClient")
def test_app_url_override(
self,
mock_client_cls: MagicMock,
mock_get_token: MagicMock,
mock_set_override: MagicMock,
runner: CliRunner,
) -> None:
mock_get_token.return_value = "tok"
mock_client: MagicMock = mock_client_cls.return_value
mock_client.submit_update.return_value = {
"followups_count": 1,
"attached_followups": [{"followup_name": "Standup", "action": "created"}],
}

result = runner.invoke(cli, ["--app-url", "http://localhost:8090", "update", "test"])
assert result.exit_code == 0
mock_set_override.assert_called_once_with("http://localhost:8090")


class TestLoginCommand:
@patch("dailybot_cli.commands.auth.DailyBotClient")
Expand Down
11 changes: 11 additions & 0 deletions tests/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
get_agent_auth,
get_api_key,
get_api_url,
get_app_url,
get_token,
load_config,
load_credentials,
Expand Down Expand Up @@ -66,6 +67,16 @@ def test_get_api_url_from_env(tmp_config: Path, monkeypatch: pytest.MonkeyPatch)
assert get_api_url() == "http://localhost:8600"


def test_get_app_url_default(tmp_config: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("DAILYBOT_APP_URL", raising=False)
assert get_app_url() == "https://app.dailybot.com"


def test_get_app_url_from_env(tmp_config: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("DAILYBOT_APP_URL", "http://localhost:8090/")
assert get_app_url() == "http://localhost:8090"


def test_get_token_from_env(tmp_config: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("DAILYBOT_CLI_TOKEN", "env_token")
assert get_token() == "env_token"
Expand Down