Repository navigation
Expand file tree
/
Copy pathjustfile
More file actions
258 lines (221 loc) · 15.2 KB
/
Copy pathjustfile
File metadata and controls
258 lines (221 loc) · 15.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# Switch development commands.
# Install just: brew install just
# Usage: just <recipe> (run `just` with no args to list all recipes)
set dotenv-load := true
# ── List available recipes ─────────────────────────────────────────────────────
default:
@just --list
# ── Environment setup ──────────────────────────────────────────────────────────
# Generate a .env from .env.example with freshly generated secrets. The example
# ships every secret field BLANK on purpose so no known default credential (the
# old admin/admin) can reach a running stack; this recipe fills them with random
# values. Refuses to clobber an existing .env so it never rotates live secrets.
init-env:
#!/usr/bin/env bash
set -euo pipefail
if [ -e .env ]; then
echo "✋ .env already exists — refusing to overwrite it." >&2
echo " Delete it first if you really want to regenerate every secret." >&2
exit 1
fi
cp .env.example .env
deploy/local/generate-env-secrets.sh .env local
echo "✅ Wrote .env with freshly generated secrets."
echo " Gateway admin login: $(grep '^GATEWAY_ADMIN_EMAIL=' .env | cut -d= -f2-) / $(grep '^GATEWAY_ADMIN_PASSWORD=' .env | cut -d= -f2-)"
echo " The stack binds to 127.0.0.1 only (set SWITCH_BIND_ADDR to expose it)."
# ── Dev infrastructure ─────────────────────────────────────────────────────────
# Refuses an `.env` written before the two-role split, rather than starting a
# stack that cannot work. Without DB_OWNER_USER the postgres container comes up
# with an empty POSTGRES_USER, and the failure surfaces several steps later as
# something that looks unrelated. `just init-env` fills both for a new `.env`
# and deliberately never touches an existing one, so an `.env` from before this
# change is the ordinary case rather than an exotic one — worth one check here
# instead of a puzzle for whoever hits it.
up:
#!/usr/bin/env bash
set -euo pipefail
if [ -z "${DB_OWNER_USER:-}" ] || [ -z "${DB_OWNER_PASSWORD:-}" ]; then
echo "✋ No DB_OWNER_USER / DB_OWNER_PASSWORD in .env." >&2
echo " Switch runs as a restricted database role now, and the schema" >&2
echo " owner is configured separately. Add both (see .env.example)," >&2
echo " set DB_USER=switch_app, and run 'just reset' so the Postgres" >&2
echo " volume is rebuilt with the runtime role created — 'init-db'" >&2
echo " only creates it on a fresh volume." >&2
echo " docs/old/LOCAL_DEVELOPMENT.md, 'Two database roles, not one'." >&2
exit 1
fi
docker compose -f deploy/local/docker-compose.yml --project-directory . up -d --build
down:
docker compose -f deploy/local/docker-compose.yml --project-directory . down
reset:
docker compose -f deploy/local/docker-compose.yml --project-directory . down -v
# ── Run switch-core locally ────────────────────────────────────────────────────
# The Python project lives in core/; `--project core` selects that environment
# while keeping the repo root as the working directory. Tool configs are passed explicitly since the repo root no longer
# holds pyproject.toml / alembic.ini.
run:
GATEWAY_COOKIE_SECURE="${GATEWAY_COOKIE_SECURE:-false}" uv run --project core python -m switch_core.main
# ── Run switch-core as a stand-in for Switch Cloud ────────────────────────────
# For testing Switch Console against this checkout instead of whatever a shared
# deployment runs. Needs `just up` first. Invitation e-mails go to the Mailpit
# catcher, and their links point at :8000 so that pasting one into the Console
# matches the stand-in. docs/old/LOCAL_DEVELOPMENT.md, "A local Switch Cloud".
# Run switch-core as Switch Cloud, with invitation e-mail caught at :8025
local-cloud:
docker compose -f deploy/local/docker-compose.yml --project-directory . --profile mail up -d mailpit
GATEWAY_SMTP_HOST=127.0.0.1 GATEWAY_SMTP_PORT=1025 GATEWAY_SMTP_TLS=none \
GATEWAY_SMTP_USERNAME= GATEWAY_SMTP_PASSWORD= \
GATEWAY_SMTP_FROM="Switch <invites@switch.local>" \
FRONTEND_BASE_URL=http://localhost:8000 GATEWAY_COOKIE_SECURE=false \
uv run --project core python -m switch_core.main
# Run Switch Console with "Switch Cloud" pointing at `just local-cloud`, in
# its own data directory so other dev builds' databases are left alone
local-cloud-console:
cd console && SWITCH_CLOUD_URL=http://localhost:8000 SWITCH_CONSOLE_USER_DATA_DIR=switchdash-local-cloud pnpm dev
# Made by the gateway admin from .env, so it joins the admin's workspace, as
# every admin-made account does. Use the admin's domain (switch.local by
# default) for an account that should be able to join by domain.
# just local-cloud-user ada@switch.local "Ada Lovelace" <password>
# Create an account on `just local-cloud` to sign in with
local-cloud-user email name password:
#!/usr/bin/env bash
set -euo pipefail
jar="$(mktemp)"
trap 'rm -f "$jar"' EXIT
curl -fsS -c "$jar" -H 'Content-Type: application/json' \
-d "$(jq -n --arg e "$GATEWAY_ADMIN_EMAIL" --arg p "$GATEWAY_ADMIN_PASSWORD" '{email: $e, password: $p}')" \
http://localhost:8000/gateway/auth/login >/dev/null
curl -fsS -b "$jar" -H 'Content-Type: application/json' \
-d "$(jq -n --arg e '{{ email }}' --arg n '{{ name }}' --arg p '{{ password }}' '{email: $e, name: $n, password: $p}')" \
http://localhost:8000/gateway/users | jq .
# ── Format code with ruff ──────────────────────────────────────────────────────
# Run from the repo root so ruff's hierarchical config discovery applies the
# right config per file (core/, the sub-projects that carry their own, and
# the root ruff.toml fallback).
format:
uv run --project core ruff format .
uv run --project core ruff check --fix .
# ── Check code with ruff (no changes) ─────────────────────────────────────────
check:
uv run --project core ruff format --check .
uv run --project core ruff check .
# ── Run mypy type checks ──────────────────────────────────────────────────────
typecheck:
uv run --project core mypy --config-file core/pyproject.toml core/switch_core/
# ── Regenerate everything declared in artifacts.yaml ──────────────────────────
# artifacts.yaml is the only authored copy of what each artifact is and what it
# speaks. Each artifact needs it compiled in, so the per-language modules are
# generated rather than kept in step by hand.
artifacts:
uv run --project core python scripts/gen_artifacts.py
# ── Verify the registry, the generated modules and the declared versions ──────
# Fails when artifacts.yaml changed without regenerating, when a generated
# module was hand-edited, or when a file a packaging ecosystem owns (pyproject,
# package.json) disagrees with the registry.
artifacts-check:
uv run --project core python scripts/gen_artifacts.py --check
# ── Record the official templates' revisions, at release time ─────────────────
# Adds a revision to revisions.json for each official template (switch-buddy,
# the Switch expert) whose files changed since its last one; commit it with the
# version bump. The release workflow refuses a release tag while any changed.
record-template-revisions:
uv run --project core python -m switch_core.official_templates.revisions record-all
# ── Build the Teams app package an operator uploads ───────────────────────────
# Every route into a tenant wants a .zip — Developer Portal Import app, Upload a
# custom app, the admin centre — and none takes a bare manifest.json. Pass the
# Azure Bot app id and it fills the three places it has to match.
# just teams-app-package --app-id <guid> --public-host teams.example.com
teams-app-package *args:
uv run --project core python scripts/build_teams_app_package.py {{ args }}
# ── Run alembic migrations ─────────────────────────────────────────────────────
migrate:
uv run --project core alembic -c core/alembic.ini upgrade head
# ── Generate a new alembic migration ──────────────────────────────────────────
migration msg:
uv run --project core alembic -c core/alembic.ini revision --autogenerate -m "{{ msg }}"
# ── Run tests ──────────────────────────────────────────────────────────────────
test *args:
uv run --project core pytest -c core/pyproject.toml core/tests/ {{ args }}
# ── End-to-end proof for "follow the conversation" ─────────────────────────────
# Plays every journey as two people in a fresh local Mattermost against
# switch-core from this checkout, then writes a report with the server's log
# line for each message, plus screenshots. Needs Docker and Chrome, and your
# local stack stopped. `--video` also records a captioned demo (needs ffmpeg);
# `--keep` leaves it running to try by hand. Output: build/e2e/follow-the-conversation.
e2e-follow-ups *args:
core/tests/e2e/follow_the_conversation/run.sh {{ args }}
# ── End-to-end harness, fast lane, on a throwaway stack ────────────────────────
# Fresh Postgres and Mattermost, switch-core from this checkout, then the
# provider-free scenarios in console/tooling-e2e, then teardown. Needs Docker
# and your local stack stopped. `--keep` leaves it running; arguments after
# `--` go to vitest. Output: build/e2e/ephemeral.
e2e-fast *args:
console/tooling-e2e/scripts/ephemeral-stack.sh {{ args }}
# ── End-to-end harness, provider lane, on a throwaway stack ────────────────────
# As e2e-fast, plus a headless agent controller that runs the agent's sessions
# with a real provider CLI (installed and signed in, or its key in the file
# SWITCH_E2E_PROVIDER_ENV_FILE names). SWITCH_E2E_AGENT_TYPE picks the provider.
# Spends real model calls.
e2e-provider *args:
SWITCH_E2E_LANE=provider console/tooling-e2e/scripts/ephemeral-stack.sh {{ args }}
# ── End-to-end harness, browser lane, on a throwaway stack ─────────────────────
# As e2e-fast, plus the gateway UI built from gateway/ and served on :4173, then
# the browser scenarios in headless Chromium (installed by Playwright on first
# run). Screenshots of failed pages: build/e2e/ephemeral/screenshots.
e2e-browser *args:
SWITCH_E2E_LANE=browser console/tooling-e2e/scripts/ephemeral-stack.sh {{ args }}
# ── End-to-end harness, agentic lane, on a throwaway stack ─────────────────────
# As e2e-provider, then the scenario files in console/tooling-e2e/agentic: a
# driver model plays the human, a judge model grades the conversation. Needs
# SWITCH_E2E_DRIVER_MODEL, SWITCH_E2E_JUDGE_MODEL (exact model ids) and
# SWITCH_E2E_AGENTIC_BUDGET_USD. Spends real model calls, up to that limit.
e2e-agentic *args:
SWITCH_E2E_LANE=agentic console/tooling-e2e/scripts/ephemeral-stack.sh {{ args }}
# ── Regenerate the server surface the E2E coverage gate reads ─────────────────
# Every agent operation and gateway route, written to
# console/tooling-e2e/surface/server-surface.json. CI fails when it is stale;
# after regenerating, the gate (pnpm test in console/tooling-e2e) says what a
# new operation or route needs: a scenario, or an exemption with its reason.
# Regenerate the server surface snapshot the E2E coverage gate reads
e2e-surface:
uv run --project core python scripts/e2e_surface.py
# ── Run integration tests (real Postgres via testcontainers) ───────────────────
# DOCKER_HOST is auto-resolved from the active docker context in conftest, so this
# works under Docker Desktop / OrbStack / colima without extra setup.
test-integration *args:
uv run --project core pytest -c core/pyproject.toml core/tests/integration -m integration {{ args }}
# ── Run the connection-model benchmark (real Postgres, real socket) ────────────
# Reports connection/process/CPU/RSS/latency figures for the revision it is run
# on. It measures rather than asserts, so it is excluded from `just test`.
bench *args:
uv run --project core pytest -c core/pyproject.toml core/tests/benchmarks -m benchmark -s {{ args }}
# ── Gateway UI ─────────────────────────────────────────────────────────────────
gateway-install:
cd gateway && npm install
gateway-dev:
cd gateway && npm run dev
gateway-build:
cd gateway && npm run build
gateway-test:
cd gateway && npm test
# ── Standalone deployment (all-in-one Docker, no host toolchain) ──────────────
# Repo users build from source: the build override re-adds the `build:` blocks
# so images come from the working tree, not GHCR. All profiles are enabled to
# bring up the full all-in-one stack (Mattermost bridge + gateway).
standalone-up:
docker compose -f deploy/local/standalone-docker-compose.yml -f deploy/local/standalone-docker-compose.build.yml --profile collab --profile gateway --project-directory . up -d --build
standalone-down:
docker compose -f deploy/local/standalone-docker-compose.yml --profile collab --profile gateway --project-directory . down
standalone-reset:
#!/usr/bin/env bash
set -euo pipefail
read -r -p "⚠️ This deletes ALL standalone data volumes (rooms, messages, agents, users). Continue? [y/N] " ans
[[ "$ans" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 1; }
docker compose -f deploy/local/standalone-docker-compose.yml --profile collab --profile gateway --project-directory . down -v
# ── Documentation ──────────────────────────────────────────────────────────────
# Clones the docs repository unless --source names a checkout of it:
# just sync-docs --source ../docs
# Everything under docs/official is rewritten, so edit the source pages instead.
# Convert the published Switch pages into docs/official as Markdown
sync-docs *args:
python3 scripts/sync_docs.py {{ args }}