STEVE Strategic Technician for Every Voice Encounter
macOS · localhost Active IT Review

STEVE

A meeting intelligence and team operations platform for the Fullstory Strategic Solutions team. STEVE runs autonomously on macOS, connecting Google Calendar, Google Drive, Slack, Jira, Confluence, BigQuery, and Salesforce into a single coherent workflow.

Google Workspace Slack Jira + Confluence Salesforce BigQuery Gemini (Vertex AI) Claude (Anthropic) Gong (planned)

1 Capabilities & Features

Core Automation — Always Running in the Background

FeatureScriptScheduleWhat It Does
Transcript Pipelinerun.pyEvery 30 minScans Google Drive for new Gemini meeting notes, routes to AI summarization profile, creates structured Jira call-log tickets with action items
Morning Digestmorning-digest.py8 AM weekdaysBuilds a full daily briefing from today's calendar: past context per attendee, open Jira items, Slack canvas content, and Claude-synthesized prep notes — delivered as a Slack DM
1:1 Canvas Prepprep_one_on_one_canvases.py5 PM weekdaysCreates Slack canvases for each next-day 1:1, pre-loaded with recent DM history, Slack channel signals, and Steve Notes
Team Context Canvascanvas_manager.py8 AM + 6 PM weekdaysMaintains a long-lived Slack canvas with structured context (Current Focus, Active Deals, Open Items, Flags, Notes) for every team member; flags stale sections
Opportunity Auditopportunity_assist/audit_runner.pyDailyScans #ask-solutions + customer channels for team activity, cross-references Salesforce opportunity team membership, produces compliance findings
Daily Pipeline Datadaily_data_service.py6 AM weekdaysQueries BigQuery for open pipeline KPIs + closed deals; persists to SQLite for real-time UI rendering without live BQ calls
Slack Tunnelslack_tunnel.pyEvery 30 secLane↔STEVE private Slack relay: reads Lane's messages and replies via Claude — no approval gate
Slack Pollpoll_slack.pyEvery 15 minTwo-phase inbound DM handler: generates proposed replies, sends to Lane for approval before posting
Triage Agenttriage_agent.pyOn file changeLog synthesis → remediation canvas in Slack; WatchPath-triggered by new files in the polish directory

STEVE UI — On-Demand (make steve)

React + Node.js PWA at http://localhost:5173. Eight tabs:

Profiles
Browse, create, edit, and delete AI summarization profiles. Generate new profiles from a description using Claude. Split-panel refinement chat.
Opportunity Assist
Live compliance audit: per-opportunity status, team attachment, COMPLIANT / CLAIMED_NOT_LOGGED / UNCLAIMED state. Dry-run + live SF attachment.
Projects
PM project tracker: charter new projects, track milestones, decisions, artifacts, actions. Syncs to Jira + Confluence.
Talk to Steve
Claude-powered conversational interface with 20+ tools: calendar lookup, Slack context, Jira search, Salesforce queries, canvas creation.
Agenda
Generate pre-meeting Jira agendas for any calendar event on demand.
Weekly Report
View and generate EOW synthesis reports aggregating the week's call-log tickets.
Team Config
Edit team roster (name, email, Slack ID, role, SF role) without leaving the UI.
Logs
Tail and search LaunchAgent log output across all scheduled scripts.

2 System Architecture & Tech Stack

Tech Stack

LayerTechnology
SchedulingmacOS LaunchAgents (12 plist templates in plists/)
Backend — PythonPython 3.13+, stdlib only (no Django/Flask)
Backend — Node.jsNode.js 18+, better-sqlite3, native http/https modules
FrontendReact 18, Vite, CSS custom properties
Local DatabasesSQLite — steve.db (pipeline/opportunity), projects.db (PM)
AI — TranscriptsGoogle Gemini 2.0 Flash via Vertex AI Agent Engine (customer data boundary)
AI — SynthesisAnthropic Claude API (claude-sonnet-4-6) for prep notes, profiles, chat
SlackSlack Web API (bot token + user token); Canvas API
GoogleCalendar v3, Drive v3, Vertex AI Agent Engine, BigQuery (bq CLI subprocess)
Jira / ConfluenceAtlassian REST API v3 (Basic auth with API token)
SalesforceREST API v59.0 via OAuth 2.0 client credentials flow
GongREST API (credentials provisioned, integration planned)

Module Map

directory
meetings-automation/
├── run.py                        Core transcript pipeline (30-min LaunchAgent)
├── morning-digest.py             Daily briefing (8am LaunchAgent)
├── pre_meeting_agenda.py         Pre-meeting Slack reminder (15 min before)
├── prep_one_on_one_canvases.py   1:1 canvas prep (5pm LaunchAgent)
├── canvas_manager.py             Team context canvas management
├── slack_context.py              Shared Slack library (tokens, roster, DM history)
├── slack_tunnel.py               Lane↔STEVE relay (30s LaunchAgent)
├── poll_slack.py                 Inbound DM handler + approval workflow (15m LaunchAgent)
├── triage_agent.py               Log triage → canvas (WatchPath trigger)
├── sfdc_client.py                Salesforce REST client (OAuth client credentials)
├── sfdc_attachment.py            SF Opportunity Team auto-attachment
├── pm_sync.py                    PM project → Jira/Confluence sync
│
├── opportunity_assist/           Opportunity compliance + pipeline analytics
│   ├── audit_runner.py           Compliance audit orchestrator
│   ├── bq_client.py              BigQuery access via bq CLI subprocess
│   ├── daily_data_service.py     Daily BQ → SQLite ingestion
│   ├── db.py                     SQLite schema + read/write API
│   ├── slack_scanner.py          Slack channel scanning for team activity
│   ├── team_matcher.py           Member resolution: email/emoji/slack_id
│   └── ...
│
├── profiles/                     AI summarization profile .md files
├── adk/                          Vertex AI Agent Engine (Gemini ADK agent)
├── plists/                       LaunchAgent plist templates (12 agents)
├── mcp/                          MCP server for Claude Code integration
├── ui/                           React + Node.js STEVE UI
│   ├── serve.js                  Node.js API server, Jira proxy, SQLite access
│   └── src/                      React components (20+ files)
└── scripts/                      install-launchagents.sh

Runtime Data Flows

data flows
Google Drive (Meet Recordings)  →  run.py  →  Vertex AI (Gemini)  →  Jira ticket + Slack DM
Google Calendar                 →  morning-digest.py  →  Claude  →  Slack digest DM
Slack channels                  →  opportunity_assist/  →  BigQuery / Salesforce  →  SQLite + Canvas
STEVE UI (localhost:5173)       →  serve.js  →  Claude / Jira / Slack / Salesforce
Lane Slack DM → STEVE bot  →  poll_slack.py (approval)  or  slack_tunnel.py (direct)

3 STEVE Dataflow Architecture

3.1 — Core Transcript Pipeline (run.py)

flowchart TD
    A([LaunchAgent fires\nevery 30 minutes]) --> B[Load config.json\n+ ~/.jira-token\n+ state.json]
    B --> C{Network ready?\nwait_for_network}
    C -- No --> Z1([Exit — launchd will retry])
    C -- Yes --> D[Scan Google Drive\nMeet Recordings via Drive API]
    D --> E{New .gdoc files\nsince lastRun?}
    E -- No --> Z2([Exit — nothing to process])
    E -- Yes --> F[For each new transcript]
    F --> G[Export doc as plain text\nDrive API /export]
    G --> H{Profile selection\nkeyword match on title}
    H --> I[agent_discover_meetings\nVertex AI Agent Engine]
    I -- Success --> J[Agent Engine: Gemini\nsummarizes transcript\nwith profile instructions]
    I -- Failure --> K[direct_discover_meetings\nCalendar API fallback]
    K --> J
    J --> L[Parse json_meetings block\nextract attendees + actions]
    L --> M{Dedup check:\nticket exists today?}
    M -- Duplicate --> N[Update existing ticket\nor skip]
    M -- New --> O[Create Jira call-log ticket\nADF description + labels]
    O --> P[Resolve attendees\n→ Jira account IDs]
    P --> Q[Set Relevant Party field\n+ Security Level]
    Q --> R{1:1 detected?}
    R -- Yes --> S[Set Jira security to\nRelevant Party Only]
    R -- No --> T[Create subtasks\nfor each action item]
    S --> T
    T --> U[Post Slack notification\nwith ticket link]
    U --> V[Update state.json\nmark file processed]
    V --> F
      

3.2 — Morning Digest Pipeline (morning-digest.py)

flowchart TD
    A([LaunchAgent fires\n8:00 AM weekdays]) --> B[Load config.json\nteam.json\nSlack tokens]
    B --> C[Fetch today's calendar\nGoogle Calendar API]
    C --> D[Filter: recurring + has attendees\nskip solo/cancelled/all-day]
    D --> E[For each meeting]
    E --> F{1:1 meeting?\n2 attendees}
    F -- Yes --> G[Search for 1:1 canvas\nSlack search.files]
    G --> H{Canvas found?}
    H -- Yes --> I[Use canvas content\nskip channel scan]
    H -- No --> J[Fetch DM history\nwith team member]
    J --> K[Scan team channels\nfor person context]
    K --> I
    F -- No --> L[Scan team channels\nfor all attendees]
    L --> I
    I --> M[Fetch Steve Notes\nDM-to-self scan]
    M --> N[Fetch open Jira items\nassigned to user]
    N --> O[Fetch past meeting summaries]
    O --> P{Pipeline data?\nSQLite steve.db}
    P -- Yes --> Q[Include pipeline KPIs]
    P -- No --> R[Skip pipeline section]
    Q --> S[Claude API synthesis\ngenerate prep note]
    R --> S
    S --> T[Collect all meetings]
    T --> U[Render full digest]
    U --> V{Slack configured?}
    V -- Yes --> W[Post digest to\nuser self-DM]
    V -- No --> X[Write to log file]
      

3.3 — Opportunity Assist / Compliance Audit

flowchart TD
    A([Triggered: UI button\nor LaunchAgent]) --> B[Load team.json\nfilter: direct_reports + self]
    B --> C[Phase A: Formal requests\nScan #ask-solutions channel]
    C --> D[For each message:\nparse SF opportunity URL]
    D --> E{SF URL found?}
    E -- Yes --> F[Resolve opportunity ID]
    F --> G[Query BigQuery\nsfdc_opportunityteammember]
    G --> H{Team member\non SF Opportunity Team?}
    H -- Yes --> I[Mark COMPLIANT]
    H -- No --> J[Mark CLAIMED_NOT_LOGGED]
    E -- No --> K[Check emoji reactions\nmatch_emoji_to_member]
    K --> L{Team member emoji?}
    L -- Yes --> M[Mark INFORMAL_NOT_LOGGED]
    L -- No --> N[Mark UNCLAIMED]
    C --> O[Phase B: Channel activity scan]
    O --> P[search_member_channels\nSlack search.messages API]
    P --> Q[Find win-, int-, ext- channels]
    Q --> R[Audit each customer channel]
    R --> S[Cross-reference SF opportunity team]
    I --> T[Consolidate → AuditLog]
    J --> T
    M --> T
    N --> T
    S --> T
    T --> U[Persist to SQLite\naudit_runs table]
    U --> V[Stream results to UI\nSSE progress events]
    V --> W{Auto-attach enabled?}
    W -- Yes --> X[sfdc_attachment.py:\nadd member to SF Opportunity Team]
    W -- No --> Y[Show in UI for\nmanual review]
      

3.4 — 1:1 Canvas Preparation (prep_one_on_one_canvases.py)

flowchart TD
    A([LaunchAgent fires\n5:00 PM weekdays]) --> B[Compute next business day]
    B --> C[Fetch calendar\nGoogle Calendar API]
    C --> D[Detect 1:1 meetings:\nrecurring + 2 attendees\n+ team member match]
    D --> E[For each 1:1]
    E --> F[Read Team Context Canvas\nvia canvas_manager]
    F --> G[canvas_manager.read_person\nget person section]
    G --> H[Fetch last 60 DMs\nbetween user and person]
    H --> I[Scan team channels\nfor person context]
    I --> J[Fetch Steve Notes\n@person forwards]
    J --> K{Canvas already exists\nfor this date?}
    K -- Yes --> L[Skip — no duplicate]
    K -- No --> M[Claude API synthesis\nState of Mind, What's Live\nOpen Items, Agenda Hooks]
    M --> N[canvases.create API\npost canvas to Slack]
    N --> O[DM link to user\nself-DM channel]
    O --> E
      

3.5 — UI Architecture (serve.js)

flowchart LR
    Browser["Browser\nReact/Vite\nlocalhost:5173"] <-->|HTTP + SSE| Server["serve.js\nNode.js HTTP server\nlocalhost:5173"]
    Server <-->|Basic Auth| Jira["Jira REST API v3\nfullstory.atlassian.net"]
    Server <-->|Bearer token| Slack["Slack Web API\nslack.com/api/*"]
    Server <-->|Bearer token| Anthropic["Anthropic Claude API\napi.anthropic.com"]
    Server <-->|execFile python3| Python["Python scripts\nrun.py, morning-digest.py\nopportunity_assist/*"]
    Server <-->|SQL| DB1["projects.db\nSQLite PM data"]
    Server <-->|SQL| DB2["steve.db\nSQLite pipeline data"]
    Python <-->|REST| GCP["Google APIs\nCalendar, Drive\nVertex AI Agent Engine"]
    Python <-->|bq CLI| BQ["BigQuery\nfs-biz-intel\nSalesforce mirror tables"]
    Python <-->|OAuth| SF["Salesforce REST\nfullstory.my.salesforce.com"]
      

3.6 — MCP Server — Claude Code Integration

mcp/steve-integrations/index.js is a standalone Node.js MCP server loaded by Claude Code CLI and desktop as a subprocess. It provides 20+ STEVE tools directly inside Claude Code conversations — distinct from the serve.js UI server.

CategoryTools
Jiracreate_jira_issue, get_jira_issue, search_jira, transition_jira_issue
Confluencecreate_confluence_page, get_confluence_page, update_confluence_page
Slackpost_slack_message, read_channel, search_public, get_person_slack_context
PM Projectscharter_project, list_pm_projects, get_pm_project_detail, find_projects_for_attendees
Directorylookup_employee, get_directory_changes
STEVEget_whats_new, get_slack_thread
⚠ Migration Note
The MCP server uses stdio transport and does not read config.json. All identity values (JIRA_EMAIL, JIRA_HOST, Confluence page IDs, Slack channel IDs) are hardcoded. Any team member running Claude Code with this MCP server is currently making API calls authenticated as lane@fullstory.com.

3.7 — Inbound Slack Workflows

poll_slack.py — Two-Phase Approval (every 15 min)

flowchart TD
    A([LaunchAgent fires\nevery 15 minutes]) --> B[Load bot + user tokens\nscan pending proposals from SQLite]
    B --> C[Phase 1: Detect new\ninbound DMs to STEVE bot]
    C --> D{New message?\nnot from Lane}
    D -- Yes --> E[Claude API: generate\nproposed reply]
    E --> F[Post proposal to Lane\nbot-DM with approve/reject prompt]
    F --> G[Store proposal in SQLite]
    D -- No --> H[Phase 2: Check\npending proposals]
    G --> H
    H --> I{Lane replied\nto proposal thread?}
    I -- ok --> J[Send proposed reply\nto original sender]
    I -- custom text --> K[Send Lane's text\nto original sender]
    I -- no/dismiss --> L[Drop proposal\ndelete from SQLite]
    I -- No reply yet --> M([Wait for next run])
      

slack_tunnel.py — Direct Relay (every 30 sec)

flowchart TD
    A([LaunchAgent fires\nevery 30 seconds]) --> B[Load tunnel_state.json\nthread_ts + last_lane_ts]
    B --> C{Active tunnel session?\nthread_ts set?}
    C -- No --> Z([Exit])
    C -- Yes --> D[Fetch thread replies\nuser token im:history]
    D --> E{New Lane message\nsince last_lane_ts?}
    E -- No --> Z2([Exit])
    E -- Yes --> F[Build multi-turn context\nfrom thread history]
    F --> G[Claude API call\nno approval gate]
    G --> H[Post reply to thread\nbot token chat.postMessage]
    H --> I[Update last_lane_ts\nin tunnel_state.json]
      

4 Fullstory IT Migration Guide

This section documents every change required to migrate STEVE from a single-user macOS localhost application to a centralized, company-wide hosted service.

4.1 — Architecture Gap Summary

ConcernCurrent StateRequired for Multi-Tenant
SchedulingmacOS LaunchAgentsLinux cron / Kubernetes CronJobs / Cloud Scheduler
IdentitySingle user; hardcoded lane@fullstory.comOAuth2/OIDC; per-user identity claims
SecretsLocal files (~/.jira-token, etc.)HashiCorp Vault / GCP Secret Manager / AWS Secrets Manager
DatabasesPer-machine SQLitePostgreSQL / Cloud SQL (shared, multi-tenant)
File storageLocal ~/.meetings-automation/Cloud Storage bucket or shared NFS
Google DriveLocal Drive for Desktop mountDirect Drive API calls using service account
Slack identitySingle bot + user tokenPer-user Slack OAuth; shared bot token for workspace actions
BigQueryPer-user bq CLI + gcloud ADCService account with BigQuery Data Viewer
NetworkZscaler SSL intercept workaroundsDirect egress — remove all SSL bypass code

4.2 — Hardcoded Variables — Complete Inventory

Audit Note
An initial pass of this inventory (reading ~15 files) missed 10 values and undercounted file coverage for every value listed. A full harness sweep across 23+ files was required to produce the complete picture below.

4.2.1 — User Identity (Per-User Dynamic)

ValueOccurrencesOverride?Fix
lane@fullstory.com22 across 8+ files incl. mcp/index.js, pm_sync.py, confluence_updater.py, adk/agent.py (deployed)PartialAdd loadConfig() to non-compliant files
5cc358c66fbf5a10040d3b81 (Jira account ID)run.py:145, morning-digest.py:80, config.template.jsonPartialRemove fallback constant; fail loudly if config absent
U8M7CQ2GM (Slack user ID)12 across serve.js, poll_slack.py, slack_tunnel.py, triage_agent.pyNoneLoad from config.json user.slack_user_id
D8M4563M2 (self-DM channel)slack_context.py, serve.js ×3, triage_agent.py, mcp/index.jsPartialDerive via conversations.open everywhere else
D8LHB6W1X (Slackbot DM)slack_context.py:39NoneDerive dynamically or remove
D0AN7PQQR1T (bot-app DM)slack_tunnel.py:37NoneResolve via conversations.open at startup
calendarId=lane@fullstory.comadk/agent.py system prompt (deployed GCP artifact)NonePass per-request in agent payload; requires adk/deploy.py update
Team roster (8 members)adk/agent.py system prompt (deployed GCP artifact)NoneSerialize from team.json per-request; requires redeployment
GoogleDrive-lane@fullstory.comrun.py:64 (path fragment)NoneDerive from user.email or switch to API-only

4.2.2 — Workspace / Org Identity (Per-Org Dynamic)

ValueOccurrencesOverride?Fix
T02FE3LFK (Slack workspace)17 across serve.js, morning-digest.py, poll_slack.py, slack_tunnel.py, triage_agent.pyNoneAdd to config.json as slack.workspace_id
fullstory.atlassian.net71 occurrences across 6+ filesPartialAdd loadConfig() to serve.js and mcp/index.js
C09K2QUJD60 (#ask-solutions)audit_runner.py:35, mcp/index.jsNoneAdd to config.json as slack.ask_solutions_channel_id
fullstory.my.salesforce.comsfdc_client.py:28NoneAdd to config.json as salesforce.login_url

4.2.3 — Confluence Page IDs (Entirely Absent from Prior Documentation)

ValueMeaningFilesOverride?
30769334SPECOPS Confluence homepageserve.js, mcp/index.js, confluence_updater.pyNone
190251010PM master index pageserve.js ×3, mcp/index.jsNone

4.2.4 — GCP / Infrastructure (Environment-Driven)

ValueLocationOverride?Fix
fs-playpenrun.py, morning-digest.pyYesAlready in config.json gemini.project
fs-biz-intelbq_client.py:12NoneAdd to config.json as gcp.bq_project
3167755122029625344 (Agent Engine ID)run.py:93NoneAdd to config.json as gcp.agent_engine_resource_id
877462458422 (GCP project number)run.pyPartialWire _load_config() to replace the constant

4.3 — Infrastructure Changes Required

Scheduling: LaunchAgents → Cloud Scheduler

yaml — example Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
  name: steve-morning-digest
spec:
  schedule: "0 8 * * 1-5"   # 8 AM weekdays (per-user timezone required)
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: morning-digest
            image: gcr.io/fs-specops/steve-python:latest
            command: ["python3", "morning-digest.py"]
            envFrom:
            - secretRef:
                name: steve-user-credentials

ADK Agent: System Prompt Contains Deployed Identity

Highest Severity Identity Binding
adk/agent.py is the source definition of a deployed Vertex AI Agent Engine instance on GCP. The calendarId=lane@fullstory.com and the full 8-person team roster are baked into the live system instructions. Changing either requires editing the file and running python adk/deploy.py update. Until that executes, the live agent calls Lane's calendar regardless of which user triggers summarization.

MCP Server: stdio vs. Hosted

Option A (fix for current per-machine model): Add loadConfig() at MCP server startup. Each employee's machine reads their own config. Fixes the active auth bug immediately.

Option B (multi-tenant): Replace stdio transport with a hosted HTTP MCP server with OAuth2. Users authenticate via Fullstory SSO; the server calls downstream APIs on their behalf. Correct long-term architecture, separate infrastructure project.

Secrets: Local Files → Vault

mapping
~/.jira-token              → Vault: secret/steve/{user_id}/jira_token
~/.slack-bot-token         → Vault: secret/steve/shared/slack_bot_token
~/.slack-user-token        → Vault: secret/steve/{user_id}/slack_user_token
~/.anthropic-api-key       → Vault: secret/steve/shared/anthropic_api_key
~/.sfdc-credentials.json   → Vault: secret/steve/shared/salesforce_credentials
~/.gong-token              → Vault: secret/steve/shared/gong_credentials

SSL Bypass: Conditionalize for Cloud

All Google API calls use ctx.check_hostname = False; ctx.verify_mode = ssl.CERT_NONE to bypass Zscaler TLS inspection on Fullstory laptops. A cloud-hosted instance must use standard TLS. Gate on STEVE_ENV=local environment variable.

bash — find all occurrences
grep -rn "CERT_NONE\|check_hostname = False" .

5 IT Permissions Matrix & Enterprise Hardening

5.1 — Per-User Permissions

Run make it-request to generate a pre-filled request with your name and email.

ServicePermissionScope / RoleWhy
GCProles/serviceusage.serviceUsageConsumerProject: fs-playpenEnables Calendar API, Drive API, and direct Vertex AI Gemini calls
JiraExisting org accessProject: STRATMust be a member of STRAT project
JiraSecurity scheme accessScheme: "Relevant Party Only"Required for 1:1 tickets to be visible to creator
SlackUser OAuth tokenchannels:history, groups:history, im:history, im:write, files:read, search:read, canvases:write, users:readCanvas creation, DM history, channel scanning
GongAPI Access Key + SecretGong admin generatesTranscript pull; currently blocked

5.2 — Team-Wide Permissions (One-Time)

ItemActionWhy
Jira STRATEnable "1:1 Participants Only" security schemeRestricts 1:1 summaries to participants only
Jira STRATEnable "Relevant Party" field (customfield_11518)Auto-populates ticket visibility
Vertex AI Agent EngineGrant Vertex AI User role on project 877462458422Access to shared STEVE summarization agent
SalesforceConnected App with client_credentials OAuth flowRequired for sfdc_client.py; see REVOPS-25107
BigQueryroles/bigquery.dataViewer on fs-biz-intelPipeline/opportunity analytics

5.3 — Slack App Scopes

TokenScopeUsed ByWhy
Bot Token
xoxb-*
chat:writeAll scriptsPosting DMs and channel messages
channels:historyslack_context.pyReading context channels
files:readslack_context.pyReading canvases
users:readslack_context.pyResolving display names
User Token
xoxp-*
channels:history, groups:historyslack_context.pyChannels where bot is not a member
im:historyslack_context.pyDM history with team members
im:writeserve.jsOpening DM channels programmatically
search:readslack_context.py, serve.jsSearching messages and files
canvases:writecanvas_manager.py, serve.jsCreating and updating Slack canvases
files:readserve.jsReading canvas content

5.4 — Enterprise Hardening Requirements

Secrets Management
Replace Path.home() / ".token" patterns with a secrets.get(key) abstraction calling GCP Secret Manager or Vault. Rotate annually; rotate Slack + Jira tokens on offboarding.
RBAC
Users read/write own data only. Team leads read direct reports. Admins manage config. 1:1 tickets enforce Relevant Party Only at API level, not just Jira UI.
Data Compliance Boundary
Raw transcripts → Gemini only (enforced in code). Claude receives only Gemini-produced summaries. Must be enforced as a code review gate, not just documentation.
Network Boundaries
VPC egress-only to: slack.com, fullstory.atlassian.net, googleapis.com, aiplatform.googleapis.com, api.anthropic.com, fullstory.my.salesforce.com, bigquery.googleapis.com
Rate Limiting
Current: 30 Slack calls/60s per process. Multi-user multiplies volume — implement Redis-based distributed token bucket shared across all users.
Input Validation
BigQuery queries in bq_client.py use f-string interpolation with user-controlled data. Convert to parameterized queries before multi-tenant deployment.

6 Fullstory Employee Quickstart

Prerequisites

bash — verify prerequisites
uname                        # → Darwin (macOS required)
brew --version               # → Homebrew 4.x
python3 --version            # → 3.13+ (brew install python@3.14)
node --version               # → 18+ (brew install node)
gcloud --version             # → google-cloud-sdk (brew install --cask google-cloud-sdk)
claude --version             # → Claude Code CLI

Step 1 — Clone and Open with Claude Code

bash
git clone git@github.com:fullstorydev/mn.git ~/Documents/mn
cd ~/Documents/mn/users/lane2day/meetings-automation
claude   # reads CLAUDE.md automatically — fully context-aware

Step 2 — Let Claude Code Run Setup

Claude Code Quickstart
Once Claude Code is open in the directory, say: "Run make setup for me and guide me through each step". Claude Code knows the full STEVE codebase and will walk you through every credential, run make test, and generate your IT permissions request.

Step 3 — Manual Setup (if preferred)

bash
make setup          # interactive wizard: profile → credentials → install → UI
make test           # validate all API connections
make it-request     # print IT permissions request (copy into a ticket)
make steve          # open STEVE UI at http://localhost:5173

Step 4 — Configure Your Team Roster

json — ~/.meetings-automation/team.json
{
  "team": [
    {
      "name": "Full Name",
      "email": "name@fullstory.com",
      "slack_id": "UXXXXXXXXXX",
      "slack_handle": "firstname",
      "role": "direct_report",
      "relationship": "direct_report",
      "aliases": ["firstname@fullstory.com"]
    }
  ],
  "context_channels": [
    { "id": "CXXXXXXXXXX", "name": "your-team-channel" }
  ],
  "steve_dm_channel_id": "DXXXXXXXXXX"
}

Claude Code — Day-to-Day Prompts

prompts
What did the morning digest pick up today?
The canvas for my 1:1 didn't get created — what went wrong?
Add Jordan Smith (jordan@fullstory.com, slack ID U123456789) as a direct report
Generate a pre-meeting agenda for my call with the Acme Corp team tomorrow
Run the opportunity audit for the last 30 days and show me what's unclaimed

7 Configuration Reference

jsonc — ~/.meetings-automation/config.json
{
  "user": {
    "name":             "Your Full Name",
    "email":            "you@fullstory.com",
    "jira_account_id":  "...",   // from /rest/api/3/myself
    "slack_user_id":    "UXXXXXXXXXX",
    "calendar_id":      "primary",
    "role":             "manager"  // manager|se|mobile-se|web-se|data-specialist
  },
  "gemini": {
    "project": "fs-playpen",
    "region":  "us-central1",
    "model":   "gemini-2.0-flash"
  },
  "jira": {
    "base_url":             "https://fullstory.atlassian.net",
    "project":              "STRAT",
    "team_lead_account_id": "your-jira-account-id"
  },
  "gcp": {
    "quota_project":        "fs-playpen",
    "agent_engine_project": "project-364429034474444891",
    "agent_engine_number":  "877462458422"
  }
}

8 Credentials Reference

FilePurposeHow to Obtain
~/.jira-tokenJira API tokenid.atlassian.com → Security → API tokens
~/.slack-bot-tokenSlack bot token (line 1) + Slack user ID (line 2)Slack app → OAuth & Permissions → install
~/.slack-user-tokenSlack user OAuth tokenSlack app → user token scopes → install
~/.slack-webhook-urlSlack incoming webhook (simpler alternative)Slack app → Incoming Webhooks
~/.anthropic-api-keyAnthropic Claude API keyconsole.anthropic.com/account/keys
~/.sfdc-credentials.jsonSalesforce Connected App OAuthIT / RevOps: see REVOPS-25107
~/.gong-tokenGong API Key (line 1) + Secret (line 2)Gong admin request
gcloud ADCGoogle Calendar, Drive, Vertex AIgcloud auth login --enable-gdrive-access --update-adc

9 Commands Reference

bash
make setup          # First-time interactive setup wizard
make configure      # Update profile (name, email, IDs)
make credentials    # Update API tokens only
make install        # Re-install scripts + LaunchAgents after code updates
make venv           # Create/update Python venv and install dependencies
make test           # Test all API connections + show LaunchAgent status
make it-request     # Print IT permissions request
make status         # Installation status + last run info + recent logs
make steve          # Open STEVE UI at http://localhost:5173
make ui-dev         # Start UI in hot-reload dev mode
make slack-setup    # Configure Slack (guided walkthrough)
make slack-test     # Send a test Slack notification
make push           # Ship STEVE changes via auto-merge PR
make pull           # Sync worktree with latest master
make uninstall      # Remove LaunchAgents + scripts (keeps credentials)

10 Profile System

Summarization profiles are markdown files in profiles/ with YAML frontmatter. They define AI instructions for specific meeting types. Profile selection uses keyword matching on the meeting title.

Profile Dimensions

DimensionOptionsEffect
deliveryinternal / externalExternal = customer-safe language, no internal strategy leaks
audience1:1 / group1:1 = coaching/career lens; group = decisions/alignment lens
org_focusspecops / services-solutions / product / salesShapes which stakeholders and outcomes are emphasized

Included Profiles

ProfileKeywordsTypeDeliveryAudience
1-to-1-weekly.md1:1, one-on-onetranscriptinternal1:1
se-round-table.mdround table, se roundtranscriptinternalgroup
strategic-solutions-weekly-sync.mdspecops weekly, team synctranscriptinternalgroup
services-solutions-wbr.mdwbr, weekly business reviewtranscriptinternalgroup
external-sales-discovery.mddiscovery, first touchtranscriptexternalgroup
external-customer-deal-work.md(default)transcriptexternalgroup
lees-eow.mdeow, end of weeksynthesisinternalgroup

11 Troubleshooting

SymptomCauseFix
make test shows ❌ on Google Calendar or Vertex AIIT hasn't granted serviceUsageConsumer on fs-playpenRun make it-request, submit IT ticket. STEVE still works via Agent Engine fallback.
LaunchAgents not runningScripts not registeredmake install → make test. Check ~/.meetings-automation/logs/
"No transcript available" on every ticketDrive API returning 403IT blocker. STEVE creates placeholder ticket; fills when transcript available. Check logs.
Duplicate Jira ticketsWrong jira_account_id in configVerify config.json user.jira_account_id matches your Jira account ID from /rest/api/3/myself
Canvas URLs brokenWrong Slack workspace IDT02FE3LFK is hardcoded — currently requires a code change (see §4.2.2)
Slack "not_in_channel" errorsBot not in channelInvite STEVE bot: /invite @STEVE, or use user token (default for most calls)
BigQuery data not loadinggcloud ADC lacks BQ accessbq query --project_id=fs-biz-intel --use_legacy_sql=false 'SELECT 1'
Salesforce auth failingCredentials file missing or Connected App not configuredCheck ~/.sfdc-credentials.json. Connected App needs client_credentials flow — see REVOPS-25107.
STEVE UI won't openServer stuck or npm missingmake ui-stop && make steve. If npm error: cd ui && npm install

✦ Data Compliance Summary

Data TypeLLM AllowedStored Where
Raw meeting transcriptsGemini only (Vertex AI Agent Engine)Google Drive (Google's custody)
Gemini-produced summariesClaude OKJira ticket descriptions
Pre-meeting prep notesClaude OKSlack canvas / DM
Team context (DM excerpts)Claude OKSlack canvas (ephemeral)
Pipeline / deal dataClaude OKSQLite steve.db (local)
Customer names / Salesforce dataClaude OK (no raw PII)SQLite + Jira labels