Turn a survey export (CSV/XLSX, e.g. Microsoft Forms) into a full report in one command: a branded multi-tab HTML dashboard — attributed responses, themes, per-person profiles, recommendations — plus a narrated recap video, deployed to a public URL.
Claude Code is the recommended way to run Walrus — it's the only environment that gets the full pipeline (parallel analysis agents, recap video, deploy):
/plugin marketplace add SSWConsulting/SSW.Walrus
/plugin install walrus@ssw-walrus
In any project, point the one entry-point skill at a survey export:
/walrus:generate-report path/to/survey.xlsx
/walrus:generate-report culture.csv worklife.csv focus on burnout
That runs everything — four analysis agents in parallel, deterministic
consolidation (bulk data extracted from the raw file, quotes verified against
it), the multi-tab HTML dashboard, the narrated recap video, and a surge.sh
deploy — and writes it all to surveys/<name>/<date>/ in your current
directory. The stages are also installed individually so you can iterate on one
without re-running the rest:
/walrus:process-survey— analysis → dashboard → deploy/walrus:record-walkthrough— recap video only (e.g. re-record after narration tweaks)/walrus:list-surveys— what's been processed
| Needed for | Requirement |
|---|---|
| Everything | python3 |
| XLSX input | pip install openpyxl (CSV needs nothing) |
| Recap video (optional) | ffmpeg, npm install + npx playwright install chromium in the plugin dir |
| Narrated voice (optional) | ELEVENLABS_API_KEY env var — absent ⇒ captioned silent video. LOGBOOK_VOICE picks the ElevenLabs voice id (a default voice is used when unset) |
| Public dashboard URL (optional) | npx surge login once (free) or SURGE_LOGIN + SURGE_TOKEN env vars — absent ⇒ the dashboard is reported as a local index.html path |
Everything optional degrades gracefully: with nothing but python3 you still
get the full dashboard as a local index.html.
The skills use the portable SKILL.md format, so Claude Code is not a hard requirement. Any TUI coding agent with shell access — OpenCode, Codex, Hermes Agent, Cursor CLI, Cline, and the rest — runs the whole pipeline fine: they execute the same scripts, record the same video, and deploy to surge just like Claude Code does. Install into whatever agents you use with one command:
npx skills add SSWConsulting/SSW.Walrus
(via skills.sh, which detects the agents on your machine). Claude Code stays the recommended path only because it runs the four analysis agents as parallel subagents; other agents run the same analyses sequentially — same output, a slower analysis phase.
| Claude Code | Other TUI agents¹ | Claude Cowork | claude.ai chat | |
|---|---|---|---|---|
| Install | plugin (above) | npx skills add SSWConsulting/SSW.Walrus |
Customize → Skills → upload | Settings → Capabilities → upload |
| Analysis + dashboard | ✅ parallel agents | ✅ sequential | ✅ sequential | |
| Recap video | ✅ | ✅ (if ffmpeg/Chromium present) | ❌ no ffmpeg/Chromium | |
| Surge deploy | ✅ | ✅ | ❌ no network |
¹ OpenCode, Codex, Hermes Agent, Cursor CLI, Cline, … These installs ship only
the skill folders, so on first run the skills fetch their helper scripts into
~/.cache/ssw-walrus (one shallow clone).
² claude.ai's code-execution sandbox has no network and no runtime package
installs, so the skills' script-fetch fallback can't work there — the skill
folder would need templates/ bundled inside it before upload. Not currently
packaged; ask if you need it.
Cloning this repo also works — the same skills are picked up from
.claude/skills/ when you run Claude Code inside it.
docs/how-it-works.md walks the pipeline end to end
(analysis → dashboard → recap → email), including the SSW branding and the
SSW.People profile-photo resolution used on the People tab and in the video.
# Process a local survey file
SURVEY_FILE=path/to/survey.csv docker compose up
# Or run Claude Code directly
claude -p "/process-survey path/to/survey.csv"Local runs deploy to surge.sh (after a one-time npx surge login); without surge
auth the dashboard is generated under surveys/{name}/{date}/dashboard/ and
reported as a local path rather than published. The notify step is Azure-pipeline-only.
Everything below is the hands-off weekly automation SSW runs internally — none of it is needed to use the skills above. Every Monday at 8am AEST, a Power Automate flow sweeps a SharePoint folder for new survey files, hands them to an Azure Container App Job running the Claude Code CLI, and a second flow emails the deployed dashboard link to leadership.
Ingress and egress run through Power Automate (standard connectors, under a
service account), so there is no Azure AD App Registration and no admin consent —
see docs/power-automate-setup.md.
Monday 8am AEST
│
▼
┌───────────────────────────────────┐
│ Power Automate — Flow A │ Recurrence trigger (Mon 8am AEST)
│ List SharePoint folder │
│ For each new CSV/XLSX: │
│ → blob: survey-inbox/{file} │
│ → queue: survey-processing │
│ → move file to Processed/ │ (dedup)
└───────────────┬───────────────────┘
▼
┌───────────────────────────────────┐
│ Function: ProcessSurveyQueue │ queue trigger → managed identity
│ Start Container App Job │
└───────────────┬───────────────────┘
▼
┌───────────────────────────────────┐
│ Container App Job (processor.js) │
│ 1. Download survey-inbox blob │
│ 2. Claude Code /process-survey │
│ 3. Dashboard → Azure $web │
│ 4. /record-walkthrough recap │ (best-effort; re-embeds + re-deploys)
│ 5. queue: survey-done │
└───────────────┬───────────────────┘
▼
┌───────────────────────────────────┐
│ Power Automate — Flow B │ survey-done queue trigger
│ Email the dashboard link │
└───────────────────────────────────┘
| Resource | Naming | Purpose |
|---|---|---|
| Managed Identity | id-walrus-{env} |
Auth for all Azure services |
| Key Vault | kv-walrus-{env} |
Secrets storage (RBAC-enabled) |
| Storage Account | sawalrus{env} |
Queues (survey-processing, survey-done) + Blobs (survey-inbox, survey-results) |
| Dashboard Storage | sawalrus{env}web |
Static website ($web) hosting for dashboards |
| Container App Env | ce-walrus-{env} |
Container runtime environment |
| Container App Job | job-walrus-{env} |
Claude Code processor |
| Function App | func-walrus-{env} |
Queue trigger → starts the Container App Job |
The container's managed identity is granted Storage Blob Data Contributor and
Storage Queue Data Contributor on sawalrus{env}, and Storage Blob Data
Contributor on sawalrus{env}web — so it reads/writes blobs and enqueues
survey-done without any keys in code.
The SharePoint ingress and the email delivery are two Power Automate flows owned by
a service account. Full build instructions (triggers, actions, connections, DLP
notes) are in docs/power-automate-setup.md.
After deployment, populate these in kv-walrus-{env}:
| Secret | Value |
|---|---|
anthropic-oauth-token |
Claude Code OAuth token |
elevenlabs-api-key |
ElevenLabs TTS key (optional — enables the recap video) |
- Azure CLI installed and logged in
- Resource group created
# Create resource group
az group create --name rg-walrus-staging --location australiaeast
# Deploy infrastructure
az deployment group create \
--resource-group rg-walrus-staging \
--template-file infra/main.bicep \
--parameters infra/staging.bicepparam # or infra/prod.bicepparam- Populate Key Vault secrets (see table above).
- Enable static website hosting on the dashboard storage account (one-off, data-plane):
az storage blob service-properties update \ --account-name sawalrusstagingweb \ --static-website --index-document index.html --404-document index.html
- Deploy Azure Functions:
cd azure-function && npm install func azure functionapp publish func-walrus-staging
- Grab the storage account key for the Power Automate connections:
az storage account keys list -n sawalrusstaging --query '[0].value' -o tsv - Build the two Power Automate flows — see
docs/power-automate-setup.md.
The azure-deploy.yml workflow builds the image server-side with az acr build
(keyless, via GitHub OIDC), deploys the Bicep, and publishes the Function App on
pushes to main.
Which subscription it deploys into is driven entirely by repo variables, so moving environments is a variable flip rather than a code change:
| Repo variable | Default | Notes |
|---|---|---|
WALRUS_ENVIRONMENT |
staging |
Also selects infra/{env}.bicepparam |
AZURE_RESOURCE_GROUP |
rg-walrus-staging |
Must already exist |
AZURE_SUBSCRIPTION_ID |
— | Target subscription |
AZURE_CLIENT_ID |
— | Deploy identity holding the federated credential |
AZURE_TENANT_ID |
— | SSW tenant |
ACR_NAME and FUNCTION_APP are derived from WALRUS_ENVIRONMENT using the
same naming convention as infra/modules/*.bicep, so they never drift.
| Variable | Source | Description |
|---|---|---|
INBOX_BLOB |
Queue message | Blob name in survey-inbox to process |
SURVEY_NAME |
Queue message | Sanitized survey name |
FILE_NAME |
Queue message | Original file name (with extension) |
STORAGE_ACCOUNT |
Bicep | Main storage account (inbox/results/done) |
DASHBOARD_STORAGE_ACCOUNT |
Bicep | Static website storage account for dashboards |
DASHBOARD_BASE_URL |
Bicep | Static website host used to build the public dashboard URL |
KEY_VAULT_URL |
Bicep | Key Vault URL (for the Claude OAuth token) |
CLAUDE_MODEL |
Bicep | Claude model (default: claude-opus-4-8) |
AZURE_CLIENT_ID |
Bicep | Managed identity client ID |
| Variable | Source | Description |
|---|---|---|
AzureWebJobsStorage |
Bicep | Functions runtime + survey-processing queue trigger |
AZURE_CLIENT_ID |
Bicep | Managed identity client ID (starts the job) |
CONTAINER_APP_JOB_NAME |
Bicep | Container App Job name |
RESOURCE_GROUP |
Bicep | Resource group name |