diff --git a/.env.cloud.example b/.env.cloud.example index e2c2463..1826e83 100644 --- a/.env.cloud.example +++ b/.env.cloud.example @@ -4,6 +4,9 @@ # cp .env.cloud.example .env # # labs/cloud/00-set-up.md, step 2, has the snctl command for each address. +# In an environment the organizers created for your team, the values come from +# your row in their environment sheet, and you create the API key yourself: +# labs/cloud/00-set-up-team-card.md, step 2. # (The Local course writes its own .env: see labs/local/00-set-up.md.) # -------------------------------------------------- from the organizers -- diff --git a/README.md b/README.md index be52d1f..3691374 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,9 @@ The same five labs, on two stacks. | | [Cloud course](labs/cloud/README.md) | [Local course](labs/local/README.md) | |---|---|---| | Runs on | StreamNative Cloud: your own instance, with a Kafka cluster, a SQL workspace, and an agent workspace | Your laptop: [Ursa for Kafka](https://openlakestream.org/docs/ursa-for-kafka), [RisingWave](https://risingwave.com), and the Orca Agent Engine (`ork local`) | -| You need | A StreamNative Cloud login with your own instance, from the hackathon organizers | Docker and an Anthropic API key | +| You need | A StreamNative Cloud login from the hackathon organizers, with a team environment they created or an instance of your own | Docker and an Anthropic API key | | Time | About 40 minutes | About 45 minutes, plus image downloads | -| Start | [Lab 0: Set up](labs/cloud/00-set-up.md) | [Lab 0: Set up](labs/local/00-set-up.md) | +| Start | [Lab 0: Set up](labs/cloud/00-set-up.md), or [from a team card](labs/cloud/00-set-up-team-card.md) | [Lab 0: Set up](labs/local/00-set-up.md) | At the hackathon, take the Cloud course: see [Before you arrive](docs/before-you-arrive.md). Without a StreamNative Cloud diff --git a/docs/before-you-arrive.md b/docs/before-you-arrive.md index cd9deed..95c4b29 100644 --- a/docs/before-you-arrive.md +++ b/docs/before-you-arrive.md @@ -30,7 +30,8 @@ Everyone also needs: - [`jq`](https://jqlang.org/download/), for the checks in every lab. - [`snctl`](https://docs.streamnative.io/tools/cli/snctl/snctl-overview) (the StreamNative Cloud CLI): `brew install streamnative/streamnative/snctl`. Lab 0 - uses it to read your instance's addresses and to create your topic. + uses it to read your instance's addresses and to create your topic. In an + environment the organizers created for your team (step 4) you do not need it. ## 1. Get the code @@ -73,6 +74,13 @@ Every line should say `PASS`. ## 4. Set up your instance +**Did the organizers create your team's environment?** Then skip this step and +create nothing in the console: your Kafka cluster, SQL workspace, and agent +workspace already exist. The organizers share an environment sheet with a row +for your team, and +[Lab 0: Set up from a team card](../labs/cloud/00-set-up-team-card.md) turns +that row into your `.env`. + The organizers add you to the hackathon organization on StreamNative Cloud, give you an **instance** of your own, and make a **service account** in it. They give you its name and its **API key**: keep the key to yourself. diff --git a/docs/tutor.md b/docs/tutor.md index 58b6693..298350e 100644 --- a/docs/tutor.md +++ b/docs/tutor.md @@ -49,7 +49,7 @@ last row works in any agent that can read a file. Started with no request, the tutor asks three things: ```text -1. Course: Cloud (your own instance on StreamNative Cloud) or Local (everything on your laptop)? +1. Course: Cloud (on StreamNative Cloud, in a team environment the organizers created or in your own instance: say which) or Local (everything on your laptop)? 2. Path: CLI, Python, or TypeScript? 3. What now: start at Lab 0, resume at a lab, quiz me on a lab, or check my setup? ``` @@ -59,6 +59,7 @@ Started with no request, the tutor asks three things: | You want to | Say | |---|---| | Take a course from the start | `Start the Cloud course on the Python path.` | +| Start in a team environment | `Start the Cloud course on the Python path. My team has a row in the organizers' environment sheet.` | | Pick up where you stopped | `Resume the Local course at Lab 3. I'm on TypeScript.` | | Be quizzed | `Quiz me on Lab 2.` | | Find out why something fails | `Check my setup.` Or paste the error. | diff --git a/labs/README.md b/labs/README.md index e6e4cbd..25daf5a 100644 --- a/labs/README.md +++ b/labs/README.md @@ -5,7 +5,7 @@ Two courses teach the same five labs on two stacks. Pick one. | | [Cloud course](cloud/README.md) | [Local course](local/README.md) | |---|---|---| | Runs on | StreamNative Cloud: your own instance, with a Kafka cluster, a SQL workspace, and an agent workspace | Your laptop: Ursa for Kafka, RisingWave, and the Orca Agent Engine | -| You need | A StreamNative Cloud login with your own instance, from the hackathon organizers | Docker and an Anthropic API key | +| You need | A StreamNative Cloud login from the hackathon organizers, with a team environment they created or an instance of your own | Docker and an Anthropic API key | | Time | About 40 minutes | About 45 minutes, plus the image downloads | | Take it when | You are at the event | You have no StreamNative Cloud instance, or you want to see every part run | diff --git a/labs/cloud/00-set-up-team-card.md b/labs/cloud/00-set-up-team-card.md new file mode 100644 index 0000000..94e954d --- /dev/null +++ b/labs/cloud/00-set-up-team-card.md @@ -0,0 +1,316 @@ +# Lab 0: Set up from a team card + +**Cloud course** · 8 minutes, plus 5 on your own · CLI, Python, or TypeScript + +The organizers created an environment for your team on StreamNative Cloud. You +build your **team card** from it, put it in `.env`, load the login stream into +your team's Kafka cluster, and run the doctor. When this lab is done, the Agent +Engine, Kafka, and Schema Registry on your card all answer, and your topic +holds 246 logins. + +Your team is not in the organizers' environment sheet? +[Lab 0: Set up](00-set-up.md) starts from an instance of your own instead. Both +end in the same place, and Lab 1 is the same after either. + +## Before you start + +- The organizers created your team's environment and shared the **environment + sheet** with you. On its **Team Environments** tab, one row is your team's: + the names and addresses of a Kafka cluster, a SQL workspace that imports it, + an agent workspace, and a service account. All of it already exists. In this + lab you create one thing, an API key, and you do not need `snctl`. +- You know your team's number: it is the **Team ID** of your row. +- You can log in to the StreamNative Cloud console, in the organization your + row names. You create your API key there in step 2, and Labs 2 and 3 use the + same login. +- You cloned this repository and opened a terminal in it. The terminal runs + `bash`: on Windows that is WSL or Git Bash, on every path, because the checks + are `bash` commands. +- You have `git`, [`ork`](https://github.com/orca-ae/orca-cli) v0.6.0 or newer, + and [`jq`](https://jqlang.org/download/). `ork` does the first MCP login in + Lab 3 for all three paths, and `ork` with `jq` runs the checks in every lab. + +## Step 1: Install your path + +Pick **one** path. Your teammate can pick a different one. + +**Python** (3.11 or newer) + +```bash +cd python +python3 -m venv .venv +source .venv/bin/activate # Git Bash on Windows: source .venv/Scripts/activate +pip install -r requirements.txt +``` + +**TypeScript** (Node.js 20 or newer) + +```bash +cd typescript +npm install +``` + +**CLI**: `ork` and `jq` are all the labs need. Set up Python or TypeScript as +above too: the doctor, the seeder, and the data injector come from one of them. + +### Check + +Run the doctor without the network. Every line says `PASS`. + +| Python or CLI | TypeScript | +|---|---| +| `python doctor.py --offline` | `npm run doctor -- --offline` | + +```text +PASS Python 3.11+ 3.13 +PASS package runorca +PASS package confluent-kafka[avro] +PASS package python-dotenv +PASS ork found +PASS jq found + +All good: you're ready. +``` + +## Step 2: Build your team card in `.env` + +Your team card is your team's row in the environment sheet, plus an API key +that you create. `.env` is where you write it down. + +Open a second terminal at the repository root and copy the template: + +```bash +cp .env.cloud.example .env +``` + +`.env` is git-ignored. It will hold your key: do not commit it or paste it +anywhere. + +**From the sheet.** Open the environment sheet on the **Team Environments** tab +and find the row with your **Team ID**. Six lines of `.env` come from that row: + +| `.env` line | Column in your row | Write it as | +|---|---|---| +| `SN_SERVICE_ACCOUNT` | **Service Account**, with **StreamNative Cloud Organization** | `@.auth.streamnative.cloud`. A cell that already ends in `.auth.streamnative.cloud` goes in as it is | +| `ORCA_BASE_URL` | **Agent Workspace Endpoint** | `https://` and the host, with no `/v1` | +| `KAFKA_BOOTSTRAP_SERVERS` | **Broker URL** | as it is: the host and its port, `:9093` | +| `SCHEMA_REGISTRY_URL` | **Schema Registry URL** | as it is, with `https://` | +| `SN_MCP_URL` | **SQL Workspace MCP Endpoint** | as it is | +| `SN_SQL_DATABASE` | **SQL Database** | as it is. It is the database you open in Lab 2, and it is not your SQL workspace's name | + +A cell you need is empty? Ask a facilitator. One value you can build yourself: +the MCP endpoint is +`https://mcp.streamnative.cloud/mcp/x//sqlworkspace.compute.streamnative.io/`, +from two other cells of your row. + +**Your API key.** The sheet holds no keys. You create one in the StreamNative +Cloud console, for the service account in your row: + +1. Log in to the console, in the organization your row names. +2. Open the organization's **Settings**. Under **Access & Control**, click + **Service Accounts**, then click your team's service account. +3. Click **Create API key**. Give the key a **Name** that nobody else in the + organization uses, in lowercase letters, digits, and dashes: your team and + your name work, such as `team07-ana`. Leave **Expiration** at 30 days, and + click **Create**. +4. The next window shows the key, once. Click **Copy**, paste the key into + `.env` as `SN_API_KEY` with nothing in front of it, then click **Close**. If + you lose the key, create another. + +If **Create API key** is greyed out, your login may not create keys: ask a +facilitator. + +Leave the other lines as they are: the MCP server uses a separate browser login +in Lab 3, so `SN_MCP_AUTH=oauth` stays, and `SN_MCP_OAUTH_ISSUER` stays empty. + +Your finished card has these seven lines filled in: + +```text +SN_API_KEY= +SN_SERVICE_ACCOUNT=@.auth.streamnative.cloud +ORCA_BASE_URL=https:// +KAFKA_BOOTSTRAP_SERVERS=:9093 +SCHEMA_REGISTRY_URL=https:// +SN_MCP_URL=https://mcp.streamnative.cloud/mcp/x//sqlworkspace.compute.streamnative.io/ +SN_SQL_DATABASE= +``` + +**Two people share one environment.** Your teammate fills in the same six lines +from the same row, and can create a key of their own for the same service +account. You both work in the same Kafka cluster and the same SQL database. +Your agents stay apart: each is named after its owner's OS user name, like +`hello-agent-ana`. If the two of you have the same user name, each set +`PARTICIPANT` in `.env` to a name of your own. In Lab 2 the view and the table +are created once for the team: if your teammate got there first, the `CREATE` +statement says they already exist, and you go on to the step's check. The reset +script that Lab 4 mentions drops them for both of you, so agree before one of +you runs it. + +### Check + +One authenticated read of your Agent Engine. It prints `true` when the endpoint +and the key on your card are accepted. + +```bash +./lab-ork agent list -o json | jq -e 'has("data")' +``` + +Before you filled in `.env`, the same command says +`Missing ORCA_BASE_URL, SN_API_KEY` instead: the two values it needs. + +## Step 3: Load the login stream + +The course reads one topic in your team's Kafka cluster, +`security.login_events`. It needs the login stream once, for the whole team. +**One of you** runs the seeder, in your path's folder: + +| Python | TypeScript | CLI | +|---|---|---| +| `python seed.py` | `npm run seed` | `(cd ../python && .venv/bin/python seed.py)` or `(cd ../typescript && npm run seed)` | + +It prints one of two lines, and both are fine. A topic that was empty is now +loaded: + +```text +Loaded 246 logins for 91 accounts into security.login_events. +``` + +A topic that the organizers or your teammate loaded before you is left as it is: + +```text +security.login_events already holds 246 events, so it is seeded. To load another copy anyway: python seed.py --force +``` + +Do not use `--force`: a second copy would double every count in Lab 2. The +number is higher than 246 once someone on your team has run Lab 3, and the +TypeScript seeder ends the line with `npm run seed -- --force`. + +The seeder replays [`data/login_events.jsonl`](../../data/login_events.jsonl): +synthetic logins at a fictional bank, with their timestamps moved to now. It +also registers the topic's Avro schema, which SQL Workspace needs in Lab 2. + +If it says `The topic security.login_events does not exist yet`, the topic was +not created with your environment. Raise your hand, or create it yourself with +`snctl`: [Lab 0: Set up](00-set-up.md), step 3. + +### Check + +Run the doctor. It checks your laptop, then each service on your card, and a +failed check prints its fix on the next line. + +| Python | TypeScript | CLI | +|---|---|---| +| `python doctor.py` | `npm run doctor` | `(cd ../python && .venv/bin/python doctor.py)` or `(cd ../typescript && npm run doctor)` | + +After the lines from step 1, it prints: + +```text +PASS .env cloud stack, participant: ana +PASS ORCA_BASE_URL https://... +PASS Agent Engine API key accepted +PASS Kafka security.login_events has 1 partition(s) +PASS Schema Registry security.login_events-value v1 +PASS login topic schema has account_id, event_time, ip_address, result, failure_reason +WAIT MCP OAuth no tutorial vault yet + next: Nothing to do now: Lab 3 opens your browser to authorize the MCP server. Run doctor again after it. + +You're ready. 1 check(s) wait for a later lab. +``` + +`WAIT` is not a failure. The MCP server needs a login that only Lab 3 can do. +On a topic that nobody has loaded yet, the `Schema Registry` line fails before +this step: the seeder is what registers the schema. + +Still failing after two tries? Raise your hand, or see +[Troubleshooting](troubleshooting.md). + +## Check your understanding + +**1. The doctor prints `WAIT MCP OAuth`. What should you do?** + +- A. Fix it now: the doctor has to print only `PASS`. +- B. Nothing yet: Lab 3 does the browser login this check waits for. +- C. Create a new API key. + +
+Answer + +**B.** The MCP server wants an OAuth login, and the lab script does it the first +time the agent needs the server. `WAIT` means "not ready, and not your mistake". +A real problem prints `FAIL` and its fix. + +
+ +**2. The seeder says `security.login_events already holds 246 events, so it is seeded`. What should you do?** + +- A. Run it again with `--force`, so that your copy is loaded too. +- B. Nothing: the stream is there already, loaded by the organizers or by your teammate. +- C. Ask for a new Kafka cluster. + +
+Answer + +**B.** Your team shares one topic, and it needs the 246 logins once. A second +copy would double every count in Lab 2. + +
+ +**3. What does `./lab-ork` add to `ork`?** + +- A. It is a different CLI with its own commands. +- B. It points `ork` at your Agent Engine with the key from `.env`, and fills in the ids your scripts saved. +- C. It runs the lab's steps for you. + +
+Answer + +**B.** Everything after `./lab-ork` goes to `ork` as you typed it. The wrapper +only supplies the endpoint, the credential, and the four `@..._id` words. + +
+ +## Try it yourself + +Make the doctor fail on purpose, so you know what a failure looks like before a +real one. Change one value in `.env` so that a check fails, read the fix the +doctor prints, then put the value back. + +### Check + +The doctor ends on the "ready" line again. + +```bash +(cd python && .venv/bin/python doctor.py) | tail -n 1 +``` + +On the TypeScript path, use `npm --prefix typescript run doctor | tail -n 1`. + +
+Solution + +Add `/v1` to the end of `ORCA_BASE_URL` and run the doctor: + +```text +FAIL ORCA_BASE_URL https:///v1 + fix: Use the host root only: ORCA_BASE_URL=https:// +``` + +The doctor does not call an endpoint it can see is wrong. It skips the Agent +Engine check, tells you the exact value to use, and ends with +`1 check(s) failed`. Remove the `/v1` and run it again. + +
+ +## Recap + +- `.env` holds your team card: six values from your team's row in the + environment sheet, and an API key you created for your team's service + account. It is git-ignored. +- Your team shares that environment. The login stream is loaded once, and the + seeder refuses a second copy. +- The doctor checks each service on the card and prints the fix for a failure. +- `./lab-ork` is how you look at your Agent Engine from the terminal. + +## What's next + +[Lab 1: Hello, agent](01-hello-agent.md) diff --git a/labs/cloud/README.md b/labs/cloud/README.md index bbe4b05..36ead96 100644 --- a/labs/cloud/README.md +++ b/labs/cloud/README.md @@ -25,6 +25,7 @@ flowchart LR | Lab | Time | Where | You | The idea | |---|---|---|---|---| | [0. Set up](00-set-up.md) | 10 min | terminal | Fill in `.env` from your instance, load the topic, run the doctor | Check service access before you build on it | +| or [0. Set up from a team card](00-set-up-team-card.md) | 8 min | terminal | Build your team card from the organizers' environment sheet, load the topic, run the doctor | The same, when the organizers created your environment | | [1. Hello, agent](01-hello-agent.md) | 5 min | CLI / Python / TS | Create an agent and chat | Agent, environment, session, events | | [2. Hello, streaming SQL](02-streaming-sql.md) | 8 min | SQL Workspace | Build a materialized view over the topic | Context that keeps itself fresh | | [3. Agent + live context](03-live-context.md) | 9 min | CLI / Python / TS | Give the agent SQL tools, inject new data | The answer changes with the data | @@ -43,8 +44,14 @@ your own. - One path installed, plus `ork`, `jq`, and `snctl`. All of this is in [Before you arrive](../../docs/before-you-arrive.md). -Start with [Lab 0: Set up](00-set-up.md). If something goes wrong, see -[Troubleshooting](troubleshooting.md). How labs and checks work is in +**Is your team in the organizers' environment sheet?** Then they created all +of this for your team. Your team's row has the addresses, and you create an API +key yourself: together they are your team card. You need only your login, one +path, `ork`, and `jq`, and you start with +[Lab 0: Set up from a team card](00-set-up-team-card.md). + +Otherwise, start with [Lab 0: Set up](00-set-up.md). If something goes wrong, +see [Troubleshooting](troubleshooting.md). How labs and checks work is in [The labs](../README.md), and a coding agent can [tutor you through the course](../../docs/tutor.md). @@ -60,6 +67,16 @@ cluster was Serverless; the SQL workspace ran RisingWave 3.1.0-alpha. - **Lab 0**: every `snctl` lookup, the topic, the seeder, and the doctor, on the Python path. On the TypeScript path, the doctor, and the seeder against the topic once it was loaded. +- **Lab 0 from a team card**: added on 6 October 2026 and run that day against + the same test instance, with the seven lines of step 2 filled in. On the + Python path: every step and check, and the failing doctor run its solution + shows. On the TypeScript path, and with the CLI column's commands: the doctor + and the seeder. The topic was loaded already (274 events, after earlier Lab 3 + runs), so the seeder printed its "already holds" line each time. Nothing in + Lab 0 reads `SN_SQL_DATABASE`. Not run from this page: reading the values + from an environment sheet, creating the API key (its steps follow the + console's labels as of 3 October 2026; the key used was an existing one), the + seeder on an empty topic, and an environment the organizers created. - **Lab 2**: every statement and check, through `psql`. The console was not used. - **Labs 1, 3 and 4**: every step and check, on all three paths, with the model diff --git a/labs/cloud/troubleshooting.md b/labs/cloud/troubleshooting.md index cdd81dd..4dbfbfa 100644 --- a/labs/cloud/troubleshooting.md +++ b/labs/cloud/troubleshooting.md @@ -14,7 +14,7 @@ Still stuck after two tries? Raise your hand. | Symptom | Fix | |---|---| | `pip install -r requirements.txt`: `No matching distribution found for runorca==0.3.0` | The `python3` that made your virtual environment is older than the course needs: on macOS, Apple's own is 3.9. Install Python 3.11 or newer, then make the environment again with it. In `python/`: `rm -rf .venv`, then the install commands from Lab 0 with that Python's name in place of `python3`, for example `python3.13 -m venv .venv`. | -| Doctor: `Agent Engine HTTP 401/403` | The key was rejected. A key created before its permissions must be re-created: ask a facilitator. | +| Doctor: `Agent Engine HTTP 401/403` | The key was rejected. A key created before its permissions must be re-created. With a team card, create a new key yourself (Lab 0, step 2) and put it in `SN_API_KEY`; otherwise ask a facilitator. | | Doctor: `Kafka ... authentication` | `SN_SERVICE_ACCOUNT` must be the full principal, `@.auth.streamnative.cloud`; `SN_API_KEY` is the raw key. | | Doctor: `Kafka security.login_events: not found` | The topic is not there yet. Create it and load it: Lab 0, step 3. | | Doctor: `Schema Registry ... not found` | The schema is registered when you load the topic: Lab 0, step 3. | diff --git a/scripts/check-labs.sh b/scripts/check-labs.sh index f1270f6..91bb9f8 100755 --- a/scripts/check-labs.sh +++ b/scripts/check-labs.sh @@ -18,7 +18,8 @@ # ## What's next # # Relative links are checked in README.md, labs/, docs/ and skills/. The lab -# pages the tutor skill names have to exist in both courses. +# pages the tutor skill names have to exist in both courses; a page it names +# with its course folder (labs/cloud/), in that course. # Prints one line per problem and exits 1 if there is any. set -euo pipefail @@ -135,6 +136,11 @@ if [ -f "$skill" ]; then printf '%s names %s, but labs/%s/%s does not exist\n' "$skill" "$page" "$course" "$page" >>"$problems" done done < <(grep -o '`[0-9a-z-]*\.md`' "$skill" | tr -d '`' | sort -u) + # A page it names with its course folder is in that course only. + # shellcheck disable=SC2016 # the backticks are Markdown, not a command + while IFS= read -r page; do + [ -f "$page" ] || printf '%s names %s, but it does not exist\n' "$skill" "$page" >>"$problems" + done < <(grep -o '`labs/[a-z]*/[0-9a-z-]*\.md`' "$skill" | tr -d '`' | sort -u) fi if [ -s "$problems" ]; then diff --git a/scripts/tests/run.sh b/scripts/tests/run.sh index 333006d..f6b4312 100755 --- a/scripts/tests/run.sh +++ b/scripts/tests/run.sh @@ -267,6 +267,21 @@ test_the_tutor_skill_names_only_lab_pages_that_exist() { check "it passes once both courses have every page the tutor names" [ "$STATUS" -eq 0 ] } +test_the_tutor_skill_can_name_a_page_only_one_course_has() { + fresh_root tutor-one-course + two_labs + mkdir -p "$R/labs/local" "$R/skills/data-agent-tutor" + cp "$R"/labs/cloud/*.md "$R/labs/local/" + # shellcheck disable=SC2016 # the backticks are Markdown, not a command + printf 'Both courses: `00-set-up.md`. Cloud only: `labs/cloud/00-set-up-team-card.md`.\n' >"$R/skills/data-agent-tutor/SKILL.md" + lint + check "a page the tutor names with its course folder fails while it is missing" [ "$STATUS" -eq 1 ] + check "naming the skill and the page" says "skills/data-agent-tutor/SKILL.md names labs/cloud/00-set-up-team-card.md" + lab 00-set-up-team-card.md 's/^# Lab 1: Hello, agent/# Lab 0: Set up from a team card/' + lint + check "it passes once that course has the page; the other course does not need one" [ "$STATUS" -eq 0 ] +} + for t in $(declare -F | awk '{print $3}' | grep '^test_'); do "$t"; done printf '\n%d passed, %d failed\n' "$PASSED" "$FAILED" diff --git a/skills/data-agent-tutor/SKILL.md b/skills/data-agent-tutor/SKILL.md index d1b097c..b64cf47 100644 --- a/skills/data-agent-tutor/SKILL.md +++ b/skills/data-agent-tutor/SKILL.md @@ -42,15 +42,30 @@ pages, in `labs/cloud/` and in `labs/local/`: `00-set-up.md` · `01-hello-agent.md` · `02-streaming-sql.md` · `03-live-context.md` · `04-act-with-approval.md` · `troubleshooting.md` +**Lab 0 of the Cloud course has two pages.** Which one is theirs depends on one +thing: whether the organizers created their team's environment and listed it in +the environment sheet they shared. That row, with an API key the learner +creates, is their **team card**. + +- **Their team has a row in the sheet**: `labs/cloud/00-set-up-team-card.md`. +- **It has none**, and they look up their own instance with `snctl`: `00-set-up.md`. +- **They have not said**: ask only this, and wait: "Did the organizers create + your team's environment, with a row for your team in their environment + sheet?" + +Labs 1 to 4 are the same pages after either one. + - **They named no course, or no lab and no task**: reply with only this menu. - 1. Course: **Cloud** (your own instance on StreamNative Cloud) or **Local** (everything on your laptop)? + 1. Course: **Cloud** (on StreamNative Cloud, in a team environment the organizers created or in your own instance: say which) or **Local** (everything on your laptop)? 2. Path: **CLI**, **Python**, or **TypeScript**? 3. What now: **start** at Lab 0, **resume** at a lab, **quiz me** on a lab, or **check my setup**? - **They pasted an error or a `FAIL` line**: give the fix the troubleshooting page has for that symptom, as a step message. - **They named a lab**: send its first step now. Do not ask them to confirm what they already said. Ask for their path only when the step you are about - to send has one column per path and they have not named theirs. + to send has one column per path and they have not named theirs. Lab 0 of the + Cloud course comes after the environment sheet question, when they have not + said. If there is no `labs/` folder in the working directory or above it, say so, ask them to open you in their clone of the repository, and stop. Never teach a lab