From 83d30f7717423de6580c75827d543a501815fb4e Mon Sep 17 00:00:00 2001 From: MRDula Date: Mon, 20 Apr 2026 13:50:08 -0400 Subject: [PATCH 1/2] =?UTF-8?q?feat(auth):=20self-service=20signup=20?= =?UTF-8?q?=E2=80=94=20router=20mints=20per-PM=20API=20keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the onboarding piece the v2 architecture was missing: PMs now go from nothing to a working Claude Code connection without ever touching the CRM admin UI. router/signup.py — two endpoints on the public router: GET /signup → minimal dark-themed HTML page (name/email/password/token) POST /signup → orchestrates the signup dance: 1. validate REVA_SIGNUP_TOKEN (constant-time) 2. POST nakatomi/auth/signup (creates user) 3. POST nakatomi/workspace/members with admin token (adds user to the Rev A workspace) 4. POST nakatomi/workspace/api-keys with admin token (mints a per-user nk_... key) 5. return {api_key, user_id, workspace_slug, mcp_url} plugin/install.sh — interactive wizard when REVA_MCP_URL is set but REVA_API_KEY is not. Reads name/email/password/token from stdin, POSTs via a stdlib urllib.request (no curl/jq dependency), captures the key, and saves it to both ~/.reva-turbo/config.yaml and ~/.claude/mcp.json. railway/template.yaml — new shared vars REVA_SIGNUP_TOKEN and NAKATOMI_ADMIN_TOKEN. Signup stays disabled (503) until both are set. docs/AUTH.md — full principal matrix, flow diagrams, rotation story, known gaps (no email verify yet, no SSO). Co-Authored-By: Claude Opus 4.7 --- README.md | 14 +- docs/AUTH.md | 168 ++++++++++++++++ plugin/install.sh | 51 +++++ railway/template.yaml | 11 ++ services/mcp-router/.env.example | 18 ++ services/mcp-router/router/main.py | 5 + services/mcp-router/router/signup.py | 274 +++++++++++++++++++++++++++ 7 files changed, 536 insertions(+), 5 deletions(-) create mode 100644 docs/AUTH.md create mode 100644 services/mcp-router/router/signup.py diff --git a/README.md b/README.md index a491bf5..24f1470 100644 --- a/README.md +++ b/README.md @@ -56,16 +56,20 @@ RevOps-RevAMfg/ ## For end users (Rev A PMs) -Your admin deploys the backend once. Then you run one command on your machine: +Your admin deploys the backend once and shares the router URL + a one-time signup token. Then you run: ```bash curl -fsSL https://raw.githubusercontent.com/mrdulasolutions/RevOps-RevAMfg/main/plugin/install.sh \ - | REVA_MCP_URL=https://.up.railway.app/mcp \ - REVA_API_KEY=nk_... \ - bash + | REVA_MCP_URL=https://.up.railway.app/mcp bash ``` -Restart Claude Code, then `/reva-turbo:revmyengine`. The engine is now connected to the shared CRM and memory — everything you log is available to the whole team. +The installer drops into a short wizard that prompts for your name, email, a password (12+ chars — you'll only need it to reset your key), and the signup token. Under the hood it calls the router's `/signup` endpoint, which mints a personal `nk_...` API key scoped to your user and writes it into `~/.claude/mcp.json`. + +Prefer the browser? Visit `https://.up.railway.app/signup` instead and you'll get the same key + an exact install command to paste. + +Restart Claude Code, then `/reva-turbo:revmyengine`. The engine is now connected to the shared CRM and memory — everything you log is available to the whole team, and every action is attributed to your user on the Nakatomi timeline. + +See [`docs/AUTH.md`](./docs/AUTH.md) for the full auth flow and rotation story. ## For admins (MrDula Solutions) diff --git a/docs/AUTH.md b/docs/AUTH.md new file mode 100644 index 0000000..9a549b4 --- /dev/null +++ b/docs/AUTH.md @@ -0,0 +1,168 @@ +# Authentication and Signup + +End-to-end: how a Rev A PM goes from "never heard of REVA-OPS" to a +working Claude Code connection in about 90 seconds. + +## Two principals, two paths + +| Principal | Credentials | How they get them | +|-----------------|-----------------------------------|--------------------------------------------| +| Admin (MrDula) | Railway account + admin `nk_...` | `./railway/deploy.sh` prints both | +| PM (Rev A user) | Personal `nk_...` API key | Self-serve via `/signup` page **or** `install.sh` wizard | + +There is no shared-key-for-the-team path. Every PM has their own key so +Nakatomi's timeline attributes activities, notes, and deal moves to the +right person. + +## Admin flow — one-time + +```bash +./railway/deploy.sh --project-name reva-ops --admin-email admin@reva-mfg.com +``` + +`deploy.sh` does this (and prints all of it): + +1. Creates the Railway project. +2. Provisions Postgres, FalkorDB, Qdrant. +3. Deploys `mcp-router`, `nakatomi-backend`, `automem-backend` from their source repos. +4. Waits for migrations to run inside Nakatomi on boot. +5. Runs `python -m scripts.seed` on `nakatomi-backend` → creates: + - The Rev A workspace (`slug: reva`) + - An admin user with `role: owner` + - An initial admin API key (`nk_...`) +6. Runs `services/nakatomi-backend/seed/reva.py` → installs the Rev A + pipeline + custom-field manifest. +7. Sets two shared env vars on the `mcp-router` service: + - `NAKATOMI_ADMIN_TOKEN` = the admin API key from step 5 + - `REVA_SIGNUP_TOKEN` = a freshly generated shared signup gate +8. Prints the public MCP URL, admin email / password / API key, and the + signup token. + +The admin keeps the admin key. The signup token is what they share with +PMs during onboarding (Slack DM, 1Password, whatever). + +## PM flow — one-time per PM + +### Option 1 — terminal wizard (recommended) + +```bash +curl -fsSL https://raw.githubusercontent.com/mrdulasolutions/RevOps-RevAMfg/main/plugin/install.sh \ + | REVA_MCP_URL=https://.up.railway.app/mcp bash +``` + +Because `REVA_API_KEY` is *not* set, `install.sh` drops into the wizard: + +``` +Your name : Jane Doe +Work email : jane@reva-mfg.com +Password (12+ chars) : ************ +Signup token : (paste what the admin sent) +``` + +Under the hood it POSTs to `/signup`, captures the returned +`nk_...` key, and writes it into `~/.claude/mcp.json`. Restart Claude +Code. Done. + +### Option 2 — browser + +Visit `https://.up.railway.app/signup`. Same inputs, same +result. The page displays the key once and gives you the exact +`install.sh` command to run with `REVA_API_KEY` pre-filled. + +### Option 3 — pre-shared key + +If an admin already has a key they want to hand you: + +```bash +curl -fsSL https://.../plugin/install.sh \ + | REVA_MCP_URL=... REVA_API_KEY=nk_... bash +``` + +## What `/signup` actually does + +Nakatomi's native `POST /auth/signup` only creates fresh workspaces — +there's no public "join an existing workspace" endpoint. The router +stitches the flow together: + +``` +PM → POST /signup {name, email, password, signup_token} + │ + │ router: validate signup_token (constant-time compare) + ▼ + POST nakatomi/auth/signup ← public; creates user + + throwaway personal workspace + │ returns user_id + ▼ + POST nakatomi/workspace/members ← admin token; adds user to Rev A + (headers: Authorization: Bearer , + X-Workspace: reva) + │ + ▼ + POST nakatomi/workspace/api-keys ← admin token; mints key for user + │ returns plaintext key (only time it's ever returned) + ▼ + PM ← {api_key, user_id, workspace_slug, mcp_url} +``` + +The throwaway personal workspace is never referenced — it's a +side-effect of Nakatomi's signup contract. Harmless. + +## Rotation + +- **Signup token** — redeploy `mcp-router` with a new `REVA_SIGNUP_TOKEN` + env. Old token stops working instantly. Existing PMs are unaffected + (their keys are minted; they don't need the signup token anymore). +- **PM API key** — Nakatomi's `/workspace/api-keys/{id}` DELETE endpoint + revokes. PM re-runs the signup wizard. +- **Admin token** — admin mints a replacement via Nakatomi's own API or + through the router's admin calls (not yet exposed), then updates + `NAKATOMI_ADMIN_TOKEN` on the router. + +## What `install.sh` writes + +After a successful signup: + +``` +~/.claude/mcp.json ← adds "reva" MCP server entry +~/.reva-turbo/config.yaml ← reva_mcp_url + reva_api_key +~/.claude/skills/reva-turbo ← symlink to the cloned plugin dir +``` + +`~/.claude/mcp.json` after install: + +```json +{ + "mcpServers": { + "reva": { + "type": "http", + "url": "https://.up.railway.app/mcp", + "headers": { "Authorization": "Bearer nk_xxx..." } + } + } +} +``` + +Claude Code picks that up on next restart. + +## Security notes + +- `/signup` requires `REVA_SIGNUP_TOKEN` AND `NAKATOMI_ADMIN_TOKEN` to + be set on the router. Missing either disables the endpoint (returns + 503). A pristine deploy with no admin interaction is closed by default. +- Passwords are sent over TLS to the router, forwarded over the Railway + private network to Nakatomi, and hashed with bcrypt before storage. +- The signup token is compared with `secrets.compare_digest` — no + timing leaks. +- API keys are stored hashed (SHA-256) in Nakatomi; plaintext is only + returned at mint time. Losing the key means minting a new one. +- The admin token never leaves the router (not forwarded to the PM, + not in logs, not in the MCP response). If a router container image is + compromised, rotate `NAKATOMI_ADMIN_TOKEN`. + +## Known gaps (tracked for v2.1) + +- No email verification. Signup token is the only gate. Fine for an + internal Rev A team; not OK for public deploys. +- No password reset flow in the router. If a PM forgets, admin has to + mint a new key via Nakatomi directly and hand it over. +- No SSO (SAML/OIDC). Tracked in `docs/ROADMAP.md`. diff --git a/plugin/install.sh b/plugin/install.sh index 87d6a99..dee886e 100755 --- a/plugin/install.sh +++ b/plugin/install.sh @@ -128,6 +128,57 @@ if [ -n "${REVA_API_KEY:-}" ] && [ -x "$CONFIG_CMD" ]; then say "Saved reva_api_key to config.yaml" fi +# ── Step 6a: interactive signup wizard ────────────────────────────────── +# If REVA_MCP_URL is set but REVA_API_KEY is not, offer to mint one. The +# router hosts /signup that takes {name, email, password, signup_token} +# and returns an API key. We POST directly so the whole flow happens in +# the terminal. +if [ -n "${REVA_MCP_URL:-}" ] && [ -z "${REVA_API_KEY:-}" ] && [ -t 0 ]; then + # Derive the signup URL from the MCP URL (strip trailing /mcp* suffix). + SIGNUP_URL="${REVA_MCP_URL%/mcp*}/signup" + say "No REVA_API_KEY provided — running signup wizard." + say " Signup endpoint: $SIGNUP_URL" + + printf "Your name : "; read -r REVA_NAME + printf "Work email : "; read -r REVA_EMAIL + printf "Password (12+ chars) : "; stty -echo 2>/dev/null; read -r REVA_PASSWORD; stty echo 2>/dev/null; printf "\n" + printf "Signup token : "; read -r REVA_TOKEN + + if [ -z "${REVA_NAME}" ] || [ -z "${REVA_EMAIL}" ] || [ -z "${REVA_PASSWORD}" ] || [ -z "${REVA_TOKEN}" ]; then + say "Signup skipped — one or more fields empty. Re-run with REVA_API_KEY=... to finish." + elif command -v python3 >/dev/null 2>&1; then + REVA_API_KEY="$(python3 - "$SIGNUP_URL" "$REVA_NAME" "$REVA_EMAIL" "$REVA_PASSWORD" "$REVA_TOKEN" <<'PY' +import json, sys, urllib.request, urllib.error +url, name, email, password, token = sys.argv[1:] +body = json.dumps({ + "display_name": name, "email": email, + "password": password, "signup_token": token, +}).encode() +req = urllib.request.Request(url, data=body, headers={"Content-Type": "application/json"}, method="POST") +try: + with urllib.request.urlopen(req, timeout=30) as r: + data = json.loads(r.read()) + print(data["api_key"]) +except urllib.error.HTTPError as e: + body = e.read().decode(errors="replace") + sys.stderr.write(f"signup failed: {e.code} {body}\n") + sys.exit(1) +except Exception as e: + sys.stderr.write(f"signup failed: {e}\n") + sys.exit(1) +PY +)" + if [ -n "$REVA_API_KEY" ]; then + say "✓ API key minted and will be saved to ~/.claude/mcp.json" + [ -x "$CONFIG_CMD" ] && "$CONFIG_CMD" set reva_api_key "$REVA_API_KEY" + else + say "Signup failed — see error above. Retry later with REVA_API_KEY=... set." + fi + else + say "python3 not found — cannot run signup wizard. Visit $SIGNUP_URL in your browser instead." + fi +fi + # ── Step 7: register REVA MCP in Claude Code's mcp.json ───────────────── # Only when both URL and key are set. We write JSON by hand (no jq # dependency) using a tiny Python one-liner if Python is available, else diff --git a/railway/template.yaml b/railway/template.yaml index be8e21a..0581088 100644 --- a/railway/template.yaml +++ b/railway/template.yaml @@ -52,6 +52,11 @@ services: AUTOMEM_API_TOKEN: ${{ shared.AUTOMEM_API_TOKEN }} CRM_TOOL_PREFIX: crm MEM_TOOL_PREFIX: mem + # Self-service signup (PM onboarding via /signup) + REVA_SIGNUP_TOKEN: ${{ shared.REVA_SIGNUP_TOKEN }} + NAKATOMI_ADMIN_TOKEN: ${{ shared.NAKATOMI_ADMIN_TOKEN }} + REVA_WORKSPACE_SLUG: reva + PUBLIC_MCP_URL: https://${{ RAILWAY_PUBLIC_DOMAIN }}/mcp # 2. Nakatomi (CRM) — internal only. - name: nakatomi-backend @@ -100,3 +105,9 @@ sharedVariables: - name: OPENAI_API_KEY description: "Optional — enables real embeddings." required: false + - name: REVA_SIGNUP_TOKEN + description: "Shared signup gate PMs need to mint a key. Rotate by redeploying." + generator: "hex:16" + - name: NAKATOMI_ADMIN_TOKEN + description: "Admin nk_... key for the Rev A workspace. Set by deploy.sh after the admin user is seeded." + required: false diff --git a/services/mcp-router/.env.example b/services/mcp-router/.env.example index e79b3a9..95d6b03 100644 --- a/services/mcp-router/.env.example +++ b/services/mcp-router/.env.example @@ -23,3 +23,21 @@ MEM_TOOL_PREFIX=mem # Timeouts (seconds) UPSTREAM_TIMEOUT=30 UPSTREAM_CONNECT_TIMEOUT=5 + +# ── Self-service signup (PM onboarding) ──────────────────────────────────── +# Both values must be set to enable /signup. Leave blank to disable. +# +# REVA_SIGNUP_TOKEN Shared signup gate — the admin shares this with PMs +# during onboarding. Rotate it by redeploying with a +# new value. Generate: openssl rand -hex 16 +# NAKATOMI_ADMIN_TOKEN Admin nk_... API key (role=owner) for the Rev A +# workspace. The router uses this to add new users +# as members and mint their personal API keys. +# REVA_WORKSPACE_SLUG Slug of the Rev A workspace in Nakatomi (default: reva). +# PUBLIC_MCP_URL Public URL the signup page tells users to point +# clients at. Set automatically by Railway (usually +# https:///mcp). +REVA_SIGNUP_TOKEN= +NAKATOMI_ADMIN_TOKEN= +REVA_WORKSPACE_SLUG=reva +PUBLIC_MCP_URL= diff --git a/services/mcp-router/router/main.py b/services/mcp-router/router/main.py index 7aea368..e037c63 100644 --- a/services/mcp-router/router/main.py +++ b/services/mcp-router/router/main.py @@ -12,6 +12,7 @@ from mcp.server.transport_security import TransportSecuritySettings from .config import settings +from .signup import router as signup_router from .tools import crm, cross, memory @@ -42,6 +43,9 @@ def create_app() -> FastAPI: mcp = build_mcp() app.mount("/mcp", mcp.streamable_http_app()) + # Self-service signup (GET /signup HTML, POST /signup JSON) + app.include_router(signup_router) + @app.get("/health") async def health() -> JSONResponse: return JSONResponse({"ok": True, "service": "reva-mcp-router"}) @@ -52,6 +56,7 @@ async def index() -> JSONResponse: { "service": "reva-mcp-router", "mcp_endpoint": "/mcp/", + "signup_page": "/signup", "tool_prefixes": { "crm": settings.crm_tool_prefix, "memory": settings.mem_tool_prefix, diff --git a/services/mcp-router/router/signup.py b/services/mcp-router/router/signup.py new file mode 100644 index 0000000..7279776 --- /dev/null +++ b/services/mcp-router/router/signup.py @@ -0,0 +1,274 @@ +"""Self-service signup flow for Rev A PMs. + +Nakatomi's ``POST /auth/signup`` always creates a fresh user + workspace. +There's no native "join an existing workspace" endpoint, so we stitch the +dance together here: + +1. Caller supplies email / password / name / signup_token. +2. We validate ``signup_token`` against the router's env. +3. We call Nakatomi's ``POST /auth/signup`` to create the user (and a + throwaway personal workspace — it's cheap and harmless). +4. We use the router's admin token to ``POST /workspace/members`` into + the Rev A workspace. +5. We use the admin token to mint a per-user API key scoped to Rev A. +6. We return the key and the public MCP URL. + +The admin token and signup token are set on the router at deploy time +(see ``railway/template.yaml``). The user never sees either. +""" + +from __future__ import annotations + +import logging +import secrets +from typing import Any + +import httpx +from fastapi import APIRouter, HTTPException +from fastapi.responses import HTMLResponse +from pydantic import BaseModel, EmailStr, Field + +from .config import settings + +log = logging.getLogger("reva.signup") + +router = APIRouter() + + +# --------------------------------------------------------------------------- +# Settings that live *only* for signup. Kept here (not in config.py) so the +# rest of the router stays unaware of admin credentials. +# --------------------------------------------------------------------------- + +import os # noqa: E402 + +REVA_SIGNUP_TOKEN = os.environ.get("REVA_SIGNUP_TOKEN", "") +REVA_WORKSPACE_SLUG = os.environ.get("REVA_WORKSPACE_SLUG", "reva") +NAKATOMI_ADMIN_TOKEN = os.environ.get("NAKATOMI_ADMIN_TOKEN", "") +PUBLIC_MCP_URL = os.environ.get("PUBLIC_MCP_URL", "") # optional hint for the UI + +SIGNUP_ENABLED = bool(REVA_SIGNUP_TOKEN and NAKATOMI_ADMIN_TOKEN) + + +class SignupRequest(BaseModel): + email: EmailStr + password: str = Field(min_length=12) + display_name: str = Field(min_length=1, max_length=80) + signup_token: str + key_name: str | None = None # label for the API key; defaults to the display name + + +class SignupResponse(BaseModel): + api_key: str + user_id: str + workspace_slug: str + mcp_url: str + + +# --------------------------------------------------------------------------- +# POST /signup — does the dance. +# --------------------------------------------------------------------------- + + +@router.post("/signup", response_model=SignupResponse) +async def signup(req: SignupRequest) -> SignupResponse: + if not SIGNUP_ENABLED: + raise HTTPException( + status_code=503, + detail="signup is not configured on this deploy " + "(REVA_SIGNUP_TOKEN / NAKATOMI_ADMIN_TOKEN unset)", + ) + if not secrets.compare_digest(req.signup_token, REVA_SIGNUP_TOKEN): + raise HTTPException(status_code=403, detail="invalid signup token") + + # Throwaway personal workspace for the new user — Nakatomi requires one + # at signup time. Uses a random slug so re-runs don't collide. + personal_slug = f"personal-{secrets.token_hex(4)}" + + timeout = httpx.Timeout(settings.upstream_timeout, connect=settings.upstream_connect_timeout) + + async with httpx.AsyncClient(timeout=timeout) as client: + # 1. Create user (public endpoint; no auth header). + r = await client.post( + f"{settings.nakatomi_internal_url.rstrip('/')}/auth/signup", + json={ + "email": req.email, + "password": req.password, + "display_name": req.display_name, + "workspace_name": f"{req.display_name}'s space", + "workspace_slug": personal_slug, + }, + ) + if r.status_code == 409: + raise HTTPException(status_code=409, detail=_extract_detail(r, "email already registered")) + if r.status_code >= 400: + log.error("upstream signup failed: %s %s", r.status_code, r.text[:200]) + raise HTTPException(status_code=502, detail="upstream signup failed") + tok = r.json() + user_id: str = tok["user_id"] + + # 2. Add user to the Rev A workspace. + admin_headers = { + "Authorization": f"Bearer {NAKATOMI_ADMIN_TOKEN}", + "X-Workspace": REVA_WORKSPACE_SLUG, + } + r = await client.post( + f"{settings.nakatomi_internal_url.rstrip('/')}/workspace/members", + headers=admin_headers, + json={"email": req.email, "role": "member"}, + ) + # 409 means they're already a member — fine, continue to key mint. + if r.status_code not in (200, 201, 409): + log.error("add member failed: %s %s", r.status_code, r.text[:200]) + raise HTTPException(status_code=502, detail="failed to add to Rev A workspace") + + # 3. Mint an API key scoped to the Rev A workspace + this user. + r = await client.post( + f"{settings.nakatomi_internal_url.rstrip('/')}/workspace/api-keys", + headers=admin_headers, + json={ + "user_id": user_id, + "name": req.key_name or f"{req.display_name} (auto)", + "role": "member", + }, + ) + if r.status_code >= 400: + log.error("api-key mint failed: %s %s", r.status_code, r.text[:200]) + raise HTTPException(status_code=502, detail="failed to mint API key") + key_row = r.json() + api_key: str = key_row["key"] # plaintext, only returned once + + return SignupResponse( + api_key=api_key, + user_id=user_id, + workspace_slug=REVA_WORKSPACE_SLUG, + mcp_url=PUBLIC_MCP_URL or "/mcp", + ) + + +def _extract_detail(resp: httpx.Response, fallback: str) -> str: + try: + d = resp.json().get("detail") + if isinstance(d, str) and d: + return d + except Exception: # noqa: BLE001 + pass + return fallback + + +# --------------------------------------------------------------------------- +# GET /signup — human-friendly HTML page (calls POST /signup via fetch). +# --------------------------------------------------------------------------- + + +SIGNUP_HTML = """ + + + + +REVA-OPS · Get your API key + + + +
+

REVA-OPS

+

Get your personal API key. This key connects Claude Code (and any MCP client) to the Rev A CRM + memory.

+ +
+ + + + + + + + + + + + + +
+ +
+ +

+ Already have a key? Point your MCP client at /mcp + with header Authorization: Bearer <your key>. +

+
+ + +""" + + +@router.get("/signup", response_class=HTMLResponse) +async def signup_page() -> HTMLResponse: + if not SIGNUP_ENABLED: + return HTMLResponse( + "

Signup not configured

Ask the admin to set " + "REVA_SIGNUP_TOKEN and NAKATOMI_ADMIN_TOKEN " + "on the mcp-router service.

", + status_code=503, + ) + return HTMLResponse(SIGNUP_HTML) From f55181c5bb2735f40dc77a78a967aa72baf9874e Mon Sep 17 00:00:00 2001 From: MRDula Date: Mon, 20 Apr 2026 14:34:39 -0400 Subject: [PATCH 2/2] ci: update workflow paths for v2 monorepo plugin/ layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit install.sh, setup, bin/, and skills/ all moved under plugin/ in the v2 restructure — but the CI workflow still referenced the old root paths and failed with "bash: install.sh: No such file or directory" on both ubuntu and macos runners. - install-matrix: bash install.sh -> bash plugin/install.sh (both steps) - lint-shell: shellcheck paths re-rooted to plugin/ REVA_TURBO_DIR still points at \${{ github.workspace }}; install.sh's find_plugin_root() detects the plugin/ subdir automatically. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/install.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index a1dd208..e5d5d96 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -26,7 +26,7 @@ jobs: env: REVA_TURBO_DIR: ${{ github.workspace }} REVA_TURBO_SKIP_GIT: "1" - run: bash install.sh + run: bash plugin/install.sh - name: Verify install artifacts shell: bash @@ -45,7 +45,7 @@ jobs: env: REVA_TURBO_DIR: ${{ github.workspace }} REVA_TURBO_SKIP_GIT: "1" - run: bash install.sh + run: bash plugin/install.sh lint-shell: name: shellcheck (install.sh + bin/) @@ -56,8 +56,8 @@ jobs: run: sudo apt-get update && sudo apt-get install -y shellcheck - name: Run shellcheck run: | - shellcheck install.sh setup bin/reva-turbo-* \ - skills/*/bin/*.sh 2>&1 | tee shellcheck.log || true + shellcheck plugin/install.sh plugin/setup plugin/bin/reva-turbo-* \ + plugin/skills/*/bin/*.sh 2>&1 | tee shellcheck.log || true # Fail on SC2000-series errors only (real bugs, not style): if grep -E "error:" shellcheck.log; then echo "shellcheck found errors"