diff --git a/.agents/skills/dailybot/SKILL.md b/.agents/skills/dailybot/SKILL.md index 9753d31..1fbf99c 100644 --- a/.agents/skills/dailybot/SKILL.md +++ b/.agents/skills/dailybot/SKILL.md @@ -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 diff --git a/.agents/skills/dailybot/checkin/SKILL.md b/.agents/skills/dailybot/checkin/SKILL.md index 4a71246..a2de235 100644 --- a/.agents/skills/dailybot/checkin/SKILL.md +++ b/.agents/skills/dailybot/checkin/SKILL.md @@ -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 @@ -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` diff --git a/.agents/skills/dailybot/conversation/SKILL.md b/.agents/skills/dailybot/conversation/SKILL.md index 98be830..f51747a 100644 --- a/.agents/skills/dailybot/conversation/SKILL.md +++ b/.agents/skills/dailybot/conversation/SKILL.md @@ -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` diff --git a/.agents/skills/dailybot/forms/SKILL.md b/.agents/skills/dailybot/forms/SKILL.md index d8db742..d4e8a3a 100644 --- a/.agents/skills/dailybot/forms/SKILL.md +++ b/.agents/skills/dailybot/forms/SKILL.md @@ -1012,11 +1012,12 @@ When the form is workflow-enabled and `allowed_transitions` is non-empty: dailybot form transition --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: @@ -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//` in your own repo) - **Live API spec:** `https://api.dailybot.com/api/swagger/` - **Full agent API skill:** `https://www.dailybot.com/skill.md` diff --git a/.agents/skills/dailybot/kudos/SKILL.md b/.agents/skills/dailybot/kudos/SKILL.md index 2ed887d..4cbf88c 100644 --- a/.agents/skills/dailybot/kudos/SKILL.md +++ b/.agents/skills/dailybot/kudos/SKILL.md @@ -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` diff --git a/.agents/skills/dailybot/report/SKILL.md b/.agents/skills/dailybot/report/SKILL.md index a5b7e3c..b1807e6 100644 --- a/.agents/skills/dailybot/report/SKILL.md +++ b/.agents/skills/dailybot/report/SKILL.md @@ -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` diff --git a/.agents/skills/dailybot/shared/dashboard-urls.md b/.agents/skills/dailybot/shared/dashboard-urls.md new file mode 100644 index 0000000..1eb00f5 --- /dev/null +++ b/.agents/skills/dailybot/shared/dashboard-urls.md @@ -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) diff --git a/.agents/skills/dailybot/shared/list-query-and-errors.md b/.agents/skills/dailybot/shared/list-query-and-errors.md index d1d5d34..3469c13 100644 --- a/.agents/skills/dailybot/shared/list-query-and-errors.md +++ b/.agents/skills/dailybot/shared/list-query-and-errors.md @@ -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`). | @@ -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). | diff --git a/dailybot_cli/config.py b/dailybot_cli/config.py index 9834483..53707b5 100644 --- a/dailybot_cli/config.py +++ b/dailybot_cli/config.py @@ -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: @@ -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" @@ -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") diff --git a/dailybot_cli/main.py b/dailybot_cli/main.py index b4a58aa..7113eab 100644 --- a/dailybot_cli/main.py +++ b/dailybot_cli/main.py @@ -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`. @@ -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 @@ -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() diff --git a/docker/local/cli/Dockerfile b/docker/local/cli/Dockerfile index 2cdabfe..0e4afee 100644 --- a/docker/local/cli/Dockerfile +++ b/docker/local/cli/Dockerfile @@ -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 diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 8a4f251..d245709 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -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 @@ -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 diff --git a/tests/commands_test.py b/tests/commands_test.py index a69f643..b5a597e 100644 --- a/tests/commands_test.py +++ b/tests/commands_test.py @@ -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") diff --git a/tests/config_test.py b/tests/config_test.py index 67cff09..584f9e0 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -11,6 +11,7 @@ get_agent_auth, get_api_key, get_api_url, + get_app_url, get_token, load_config, load_credentials, @@ -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"