diff --git a/.env.evs.example b/.env.evs.example new file mode 100644 index 00000000..383ed7cc --- /dev/null +++ b/.env.evs.example @@ -0,0 +1,13 @@ +# Shared CastLabs EVS credentials for Widevine VMP signing (macOS DRM). +# +# Copy this file to .env.evs and fill in the values from the team password +# manager. run.sh sources .env.evs (gitignored) and uses these to authenticate +# the CastLabs EVS CLI non-interactively, so DRM signing works with no per-dev +# signup or prompts. Leave them blank to fall back to a one-time interactive +# signup instead. NEVER commit .env.evs. +# +# castlabs-evs reads these names directly from the environment (used by both +# `account reauth` and `vmp sign-pkg`). To wire signed RELEASES too, set the +# same two values as GitHub Actions secrets and reauth in release-macos.yml. +EVS_ACCOUNT_NAME= +EVS_PASSWD= diff --git a/backend/run.sh b/backend/run.sh index 65be43ad..8f76629a 100755 --- a/backend/run.sh +++ b/backend/run.sh @@ -53,6 +53,13 @@ if [[ $? -ne 0 ]]; then exit 1 fi +# Dev-only deps (pytest + vulture for linter/lint.py). Gated on OPENSWARM_DEV +# so packaged builds never pull these in — mirrors the --reload gating below. +if [[ "${OPENSWARM_DEV:-}" == "1" && -f requirements-dev.txt ]]; then + echo "OPENSWARM_DEV=1 detected — installing dev dependencies..." + pip3 install -r requirements-dev.txt +fi + # --- Start the backend server --- # IMPORTANT: --reload-exclude must cover every path the running backend # itself may WRITE to. Without this, the App Builder agent writing into diff --git a/electron/scripts/sign-vmp.sh b/electron/scripts/sign-vmp.sh index ba5eb833..d2468d12 100755 --- a/electron/scripts/sign-vmp.sh +++ b/electron/scripts/sign-vmp.sh @@ -36,6 +36,14 @@ fi # Always fix symlinks first (npm git installs strip them) fix_framework_symlinks +# Honor the dev-loop opt-out set by run.sh's EVS preflight (covers the "user +# chose skip" and non-interactive cases). Symlinks are already repaired above; +# we only skip the signing/auth network calls. +if [ "${OPENSWARM_SKIP_VMP:-}" = "1" ]; then + echo "[vmp] OPENSWARM_SKIP_VMP=1 - skipping VMP signing (DRM limited to previews)" + exit 0 +fi + if ! python3 -c "import castlabs_evs" 2>/dev/null; then echo "[vmp] castlabs-evs not installed. Install with: pip3 install --user castlabs-evs" echo "[vmp] Skipping VMP signing — DRM playback will be limited" diff --git a/linter/config/config.json b/linter/config/config.json index 2f4d0f21..cbf22b10 100644 --- a/linter/config/config.json +++ b/linter/config/config.json @@ -1,8 +1,8 @@ { "enabled": { - "max-file-lines": true, - "max-folder-items": true, - "import-cycles": true, + "max-file-lines": false, + "max-folder-items": false, + "import-cycles": false, "no-nested-imports": false, "vulture": true, "eslint": false, @@ -54,109 +54,11 @@ "openswarm_debug.egg-info" ], "exceptions": { - "max-file-lines": [ - "backend/apps/agents/agent_manager.py", - "backend/apps/agents/agents.py", - "backend/apps/agents/proxy/anthropic_proxy.py", - "backend/apps/agents/proxy/anthropic_to_openai.py", - "backend/apps/agents/browser/browser_agent.py", - "backend/apps/agents/browser_agent_mcp_server.py", - "backend/apps/agents/browser/browser_schema.py", - "backend/apps/agents/manager/prompt/prompt_context.py", - "backend/apps/agents/providers/pricing.py", - "backend/apps/agents/providers/registry.py", - "backend/apps/dashboards/dashboards.py", - "backend/apps/discord_mcp_shim/server.py", - "backend/apps/mcp_registry/mcp_registry.py", - "backend/apps/nine_router/oauth.py", - "backend/apps/nine_router/process.py", - "backend/apps/nine_router/sync_custom.py", - "backend/apps/outputs/outputs.py", - "backend/apps/outputs/runtime.py", - "backend/apps/outputs/view_builder_templates.py", - "backend/apps/outputs/webapp_template/frontend/src/shared/styles/ThemeContext.tsx", - "backend/apps/service/client.py", - "backend/apps/service/service.py", - "backend/apps/settings/settings.py", - "backend/apps/skills/skills.py", - "backend/apps/subscription/router.py", - "backend/apps/tools_lib/tools_lib.py", - "backend/apps/web/web.py", - "backend/main.py", - "backend/tests/test_disconnect_resilience.py", - "backend/tests/test_outputs_runtime_cleanup.py", - "backend/tests/test_service.py", - "backend/tests/test_v2_invariants.py", - "backend/tests/test_v2_label_logic.py", - "electron/affiliateTracking.test.js", - "electron/main.js", - "frontend/src/app/Main.tsx", - "frontend/src/app/components/editor/CommandPicker.tsx", - "frontend/src/app/components/editor/DirectoryBrowser.tsx", - "frontend/src/app/components/overlays/DynamicIsland.tsx", - "frontend/src/app/components/overlays/GlobalSearchPalette.tsx", - "frontend/src/app/components/Layout/AppShell.tsx", - "frontend/src/app/components/Onboarding/OnboardingPanel.tsx", - "frontend/src/app/components/Onboarding/ac/AgenticCursor.tsx", - "frontend/src/app/components/Onboarding/ac/acRuntime.ts", - "frontend/src/app/components/overlays/PlanPicker.tsx", - "frontend/src/app/components/editor/RichPromptEditor.tsx", - "frontend/src/app/components/overlays/SignInGate.tsx", - "frontend/src/app/components/editor/useDomElementSelector.ts", - "frontend/src/app/pages/AgentChat/AgentChat.tsx", - "frontend/src/app/pages/AgentChat/shell/ApprovalBar.tsx", - "frontend/src/app/pages/AgentChat/shell/BrowserAgentInlineFeed.tsx", - "frontend/src/app/pages/AgentChat/bubbles/MessageBubble.tsx", - "frontend/src/app/pages/AgentChat/parsing/toolLabels.ts", - "frontend/src/app/pages/Analytics/PixelChart.tsx", - "frontend/src/app/pages/Commands/Commands.tsx", - "frontend/src/app/pages/Dashboard/cards/AgentCard.tsx", - "frontend/src/app/pages/Dashboard/cards/BrowserAgentOverlay.tsx", - "frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx", - "frontend/src/app/pages/Dashboard/canvas/DashboardHeader.tsx", - "frontend/src/app/pages/Dashboard/DashboardToolbar.tsx", - "frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx", - "frontend/src/app/pages/Dashboard/cards/NoteCard.tsx", - "frontend/src/app/pages/Dashboard/hooks/interaction/useCanvasControls.ts", - "frontend/src/app/pages/DashboardSelection/DashboardSelection.tsx", - "frontend/src/app/pages/Modes/Modes.tsx", - "frontend/src/app/pages/Skills/SkillBuilderChat.tsx", - "frontend/src/app/pages/Skills/Skills.tsx", - "frontend/src/app/pages/Views/ViewEditor.tsx", - "frontend/src/app/pages/Views/ViewPreview.tsx", - "frontend/src/app/pages/Views/useIframeElementSelector.ts", - "frontend/src/shared/browserCommandHandler.ts", - "frontend/src/shared/state/agentsSlice.ts", - "frontend/src/shared/state/dashboardLayoutSlice.ts", - "frontend/src/shared/ws/WebSocketManager.ts" - ], - "max-folder-items": [ - "backend", - "backend/apps", - "backend/apps/agents", - "backend/apps/agents/core", - "backend/apps/outputs", - "backend/apps/tools_lib", - "backend/tests", - "frontend/src/shared", - "frontend/src/shared/state", - "frontend/src/shared/hooks", - "frontend/src/app/pages", - "frontend/src/app/pages/AgentChat", - "frontend/src/app/pages/AgentChat/ChatInput", - "frontend/src/app/pages/AgentChat/ChatInput/hooks", - "frontend/src/app/pages/AgentChat/mcp-cards", - "frontend/src/app/pages/Settings/sections", - "frontend/src/app/pages/Views", - "frontend/src/app/components/Onboarding", - "frontend/src/app/components/Onboarding/steps", - "scripts", - "electron", - "linter/checks" - ], + "max-file-lines": [], + "max-folder-items": [], "no-nested-imports": [], "import-cycles": [], - "vulture": ["backend/tests/*"], + "vulture": [], "endpoints": [], "classes": [] } diff --git a/linter/config/vulture_whitelist.py b/linter/config/vulture_whitelist.py index 0183932e..fdb626e2 100644 --- a/linter/config/vulture_whitelist.py +++ b/linter/config/vulture_whitelist.py @@ -2,59 +2,4 @@ # frameworks, entry points, and external consumers. # # Pass this file as an argument to vulture alongside source directories. -# Each bare name tells vulture "this symbol is intentionally used." - -# backend/main.py — entry points referenced by string, not direct call -main -app - -# FastAPI route handlers — registered via decorators, called by framework -pull_structure -push_structure -reset_color -reset_emoji -check - -# FastAPI lifespan context managers — passed to SubApp constructor -debugger_lifespan -health_lifespan - -# debug.py — module replaces itself with the debug() function via -# sys.modules[__name__] = debug, consumed by external packages -debug - -# ---- OpenSwarm additions (eric/linter-integration) ---- -# These are intentional false positives: symbols vulture can't see being -# used because the use is dynamic, a monkey-patch, or a kept-for-compat alias. - -# google_workspace_mcp_shim/run.py: runtime monkey-patch of a third-party -# module attribute (gauth.get_credentials = _patched_get_credentials). -get_credentials - -# outputs/view_builder_templates.py: deliberate backward-compat alias, -# kept so older importers don't snap a stale copy. The comment there explains why. -VIEW_BUILDER_SKILL - -# browser_agent.py: `for turn in range(MAX_TURNS)` loop counter we don't read. -turn - -# service.py: tuple-unpack byproducts of _compute_delta(); only cost_delta is -# consumed, the token/request deltas are computed but not summed yet. -prompt_delta -completion_delta -requests_delta - -# ---- Suspected genuinely-dead, whitelisted to keep the linter additive-only ---- -# This task is tooling-only and must not edit backend source, so these stay -# whitelisted rather than deleted. They have zero call sites today; a future -# non-additive cleanup pass should remove the definitions and these lines. -thinking_params_for -_resolve_model -load_output -submit_state -get_provider_credentials - -# outputs.py + dashboards.py: model field assigned in Python but read only via -# model_dump() serialization to the frontend (drives preview sort order), so vulture -# can't see the read. -preview_updated_at +# Each bare name tells vulture "this symbol is intentionally used." \ No newline at end of file diff --git a/run.sh b/run.sh index 1059bc8e..296452b7 100755 --- a/run.sh +++ b/run.sh @@ -97,6 +97,61 @@ if lsof -ti :8324 >/dev/null 2>&1; then sleep 0.3 fi +# --- EVS / Widevine VMP auth preflight (macOS only) --- +# DRM playback needs a CastLabs VMP signature, which needs a CastLabs EVS +# account. Only the ACCOUNT auth is interactive, so settle it HERE, before the +# backend/frontend background logs start streaming to this terminal (otherwise +# the prompt interleaves with [backend] output). Hybrid model: +# 1. Shared team account: creds from gitignored .env.evs (zero-touch, CI-friendly). +# 2. Per-dev fallback: skippable interactive signup when no shared creds exist. +# Once a token is cached locally, neither path prompts again. Fail-soft and fully +# skippable; skipping only limits DRM media to previews. OPENSWARM_SKIP_VMP=1 +# silences the whole step. The actual signing still runs later via sign-vmp.sh, +# non-interactively, because the token (or env creds) are in place by then. +if [[ "$OSTYPE" == "darwin"* && "${OPENSWARM_SKIP_VMP:-}" != "1" ]]; then + # castlabs-evs reads EVS_ACCOUNT_NAME / EVS_PASSWD from the env for both + # reauth and sign-pkg, so sourcing the team file is all the wiring needed. + EVS_ENV_FILE="$PROJECT_ROOT/.env.evs" + [[ -f "$EVS_ENV_FILE" ]] && { set -a; source "$EVS_ENV_FILE"; set +a; } + + if ! python3 -c "import castlabs_evs" 2>/dev/null; then + echo -e "${YELLOW}${BOLD}[vmp]${RESET} Installing castlabs-evs (one-time)..." + pip3 install --user --quiet castlabs-evs 2>/dev/null || true + fi + + # Short connect timeout so an offline dev isn't stalled 60s at boot. + EVS_TO=(--connect-timeout 10 --auth-timeout 10) + if python3 -c "import castlabs_evs" 2>/dev/null; then + if python3 -m castlabs_evs.account -n "${EVS_TO[@]}" refresh /dev/null 2>&1; then + # Cached token still valid: nothing to do. + echo -e "${YELLOW}${BOLD}[vmp]${RESET} EVS already authenticated - DRM signing enabled." + elif [[ -n "${EVS_ACCOUNT_NAME:-}" && -n "${EVS_PASSWD:-}" ]]; then + # Path 1: shared account, fully non-interactive. + if python3 -m castlabs_evs.account -n "${EVS_TO[@]}" reauth /dev/null 2>&1; then + echo -e "${YELLOW}${BOLD}[vmp]${RESET} EVS authenticated (shared account) - DRM signing enabled." + else + echo -e "${YELLOW}${BOLD}[vmp]${RESET} EVS auth failed - check .env.evs creds. Continuing (DRM = previews)." + fi + elif [[ -t 0 ]]; then + # Path 2: no shared creds, interactive terminal: offer skippable signup. + echo "" + echo -e "${YELLOW}${BOLD}[vmp]${RESET} First-time DRM signing setup (free CastLabs account, ~1 min)." + echo -e "${YELLOW}${BOLD}[vmp]${RESET} Enables full DRM playback. Skip to do it later, or share a .env.evs." + read -r -p " Set up now? [Enter = yes, s = skip]: " _evs_ans + if [[ "$_evs_ans" == "s" || "$_evs_ans" == "S" ]]; then + echo -e "${YELLOW}${BOLD}[vmp]${RESET} Skipped - DRM limited to previews (OPENSWARM_SKIP_VMP=1 to silence)." + export OPENSWARM_SKIP_VMP=1 + else + python3 -m castlabs_evs.account signup \ + || echo -e "${YELLOW}${BOLD}[vmp]${RESET} Signup didn't complete - continuing without DRM signing." + fi + else + # No creds and no TTY (piped / CI-like): never block, just skip. + echo -e "${YELLOW}${BOLD}[vmp]${RESET} No EVS creds and non-interactive shell - skipping DRM signing (previews only)." + fi + fi +fi + # --- Start backend --- # Mark this as a dev launch so backend/run.sh enables --reload. Packaged # builds never run this top-level script (Electron spawns backend