AI-assisted development

Day treats an AI agent as a full developer: every launch embeds the dayscript engine, and the day CLI exposes it as MCP tools an agent can call: build, relaunch, tap, type, assert, screenshot. The agent doesn’t guess whether its change worked; it drives the running app and looks at the pixels, on every platform you target.

This guide walks that loop end to end with Claude Code in a plain terminal: scaffold an app, have the agent add a weather page, script the page with dayscript, and put the script in GitHub CI. The getting started page covers the same ground with VS Code’s agent mode; everything below needs only two CLIs.

0. Install the two CLIs

cargo install day-cli        # the day CLI (see getting-started for toolkit prerequisites)
npm install -g @anthropic-ai/claude-code
day doctor                   # confirms your desktop toolkit is ready

1. Scaffold the app

day new app skycheck --toolkit macos-appkit --no-input
cd skycheck
day launch -p macos-appkit

You get a running native app with a sidebar, a home page, localized strings, a dayscript smoke test (dayscript/smoke.yaml), and, most importantly here, an AGENTS.md that teaches any coding agent the project’s conventions: where pages live, how routes register, that every control gets a stable .id(), and that new strings go into every locale.

2. Give Claude Code the day tools

claude mcp add day -- day --project . mcp-server
claude

day mcp-server (docs: agent surface) exposes ten tools: day_metadata, day_build, day_launch, day_relaunch, day_drive, day_screenshot, and friends. The important two are day_relaunch, which returns compile errors inside the tool result so the agent fixes and retries on its own, and day_drive, whose screenshots come back as images the agent can actually see.

3. Add a weather page, by prompt

In the Claude Code session:

Add a “weather” page to the sidebar: a city picker (Lisbon, Nairobi, Osaka), a large temperature label, a one-line conditions label, and a Refresh button that simulates a reload with day::sleep. Use demo data, no network. Give every control a stable id (weather-city, weather-temp, weather-conditions, weather-refresh), localize every string in all locales, then relaunch and show me a screenshot of the page.

Watch the loop the scaffolded AGENTS.md prescribes: day_metadata first, then the edits, a day_relaunch (fixing anything the compiler says), then a day_drive that navigates to the page and hands back a screenshot. The page it lands on will be a normal Day page. The shape to expect, abridged:

pub(crate) fn weather_page() -> AnyPiece {
    let city = Signal::new(0usize);
    let cities = ["Lisbon", "Nairobi", "Osaka"]; // res::str keys in the real page
    let temps = ["18 °C", "24 °C", "11 °C"];
    column((
        label(crate::res::str::weather_title()).font(Font::Title).id("weather-title"),
        picker(cities.iter().cloned(), city).id("weather-city"),
        label(move || temps[city.get()].to_string())
            .font(Font::LargeTitle)
            .id("weather-temp"),
        // …conditions label, and a Refresh button whose action is
        // day::task(async move { day::sleep(600).await; /* set signals */ })
    ))
    .spacing(12.0)
    .padding(16.0)
    .any()
}

If the result isn’t right, say so in the same session (“the temperature should update when the city changes”) and the agent re-drives the app to prove the fix. You never leave the terminal, and you never take its word for it.

4. Script it: dayscript

Now freeze that verification into a script anyone can rerun: human, agent, or CI. Ask the agent to write it, or drop this in as dayscript/weather.yaml:

flow:
  - wait_for: { id: nav }
  - navigate: { route: weather }
  - assert_route: { route: weather }
  - assert_visible: { id: weather-title }

  # The picker drives a signal; the labels read it; assert the round trip.
  - select: { id: weather-city, index: 1 }
  - assert_text: { id: weather-temp, text: "24 °C" }
  - tap: { id: weather-refresh }
  - assert_visible: { id: weather-conditions }

  - screenshot: weather
day launch -p macos-appkit --script dayscript/weather.yaml
day launch -p macos-appkit --script dayscript/weather.yaml --variant dark --env DAY_THEME=dark
day launch -p macos-appkit --script dayscript/weather.yaml --variant fr --locale fr

Each run drives the real app and writes content-checked captures under build/day/screenshots/<target>/<variant>/, the same mechanism that produces the localized gallery on this site. For strings that vary by locale, assert by Fluent key (assert_text: { id: …, key: … }) instead of literal text and one script passes in every language.

5. Put it in CI

A minimal GitHub workflow that builds the app headlessly and runs the script on every push: the Linux leg is the cheapest always-on regression gate:

name: ci
on: [push, pull_request]
jobs:
  walkthrough:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - run: |
          sudo apt-get update
          sudo apt-get install -y --no-install-recommends \
            libgtk-4-dev libadwaita-1-dev pkg-config xvfb
      - run: cargo install day-cli
      - name: Drive the app
        run: |
          xvfb-run -a -s "-screen 0 1000x720x24" \
            day launch -p linux-gtk --script dayscript/weather.yaml
      - uses: actions/upload-artifact@v4
        with:
          name: screenshots
          path: build/day/screenshots

A failed assertion is a red build; the uploaded captures show reviewers what the app actually looked like. From here, add targets to the matrix as your app grows (day new’s scaffold works unchanged on all twelve), or adopt the fuller multi-platform workflow Day itself publishes in daybrite/actions.

Where to go next