lofty-obs-health
v1.2.1
Published
Lofty CRM account health signals — Claude Code plugin
Downloads
985
Maintainers
Readme
lofty-obs-health
Claude Code plugin for querying Lofty CRM account health signals.
Install
This package is published to npm so it can be installed as a Claude Code plugin via:
/plugin marketplace add https://lofty-internal-tools.s3.us-west-1.amazonaws.com/plugins/obs-health/marketplace.json
/plugin install obs-health@lofty-internal-toolsThe plugin internally calls a Lambda Function URL (private to Lofty). On first use, Claude Code prompts the user for the API key (kept in 1Password).
Source code: https://github.com/<org>/CS-Platform/tree/main/signal/obs_health (private)
Using the CLI
obs-health # list all available signals (live catalog)
obs-health <signal-name> <id> # run a signal against an email or numeric userId
obs-health config --api-key <KEY> # one-time setupThe CLI doesn't enumerate signals locally — Lambda is the single source of truth. Run obs-health with no args to fetch the live catalog (name + verbose description per signal). New signals appear automatically without any plugin update.
When asking Claude (via Cowork or Claude Code), it will run obs-health first to discover the catalog, pick the right signal, then run obs-health <signal-name> <id> for you. The SKILL.md instructs that flow.
What we built
┌─→ Cowork (Claude Desktop) ─────┐
│ zip upload │
│ │
Users ──┼─→ Claude Code ──────────────────┼─→ Lambda (private, VPC)
│ /plugin install │ │
│ │ ├─→ Internal APIs
└─→ Terminal CLI ─────────────────┘ └─→ Internal DB
./obs-health-plugin/bin/obs-health
OR npm install -g lofty-obs-healthThe 5 files in this plugin
obs-health-plugin/
├── package.json ← for npm
├── .claude-plugin/
│ ├── plugin.json ← for Claude Code (plugin identity)
│ └── marketplace.json ← for Claude Code (catalog)
├── bin/
│ └── obs-health ← for the OS shell (the real program)
└── skills/
└── obs-health/
└── SKILL.md ← for Claude (the AI model)Each file in one sentence
| File | One-liner |
|------|-----------|
| package.json | "Hi npm — ship me as [email protected]. The binary is bin/obs-health." |
| .claude-plugin/marketplace.json | "Hi Claude Code — here's the catalog. Plugin obs-health lives on npm as lofty-obs-health." |
| .claude-plugin/plugin.json | "Hi Claude Code — I'm the plugin named obs-health, version 1.0.5." |
| skills/obs-health/SKILL.md | "Hi Claude AI — when a user asks about Lofty CRM accounts, run these commands like this." |
| bin/obs-health | The actual Python program. Reads config, calls Lambda, prints JSON. |
Why each file is required
| Tool | Speaks | Can it read other formats? |
|------|--------|----------------------------|
| npm | package.json only | No — knows nothing about plugins |
| Claude Code (catalog) | marketplace.json only | No — needs this to discover plugins |
| Claude Code (plugin) | plugin.json only | No — package.json isn't its format |
| Claude (the AI) | Markdown (SKILL.md) only | No — doesn't read JSON metadata |
| OS shell | Executable file with shebang | No — needs an actual program to run |
Each tool has its own format. None overlap. That's why we have all 5.
Mental shortcut
package.json = "shipping label" (npm)
marketplace.json = "directory listing" (Claude Code)
plugin.json = "name tag" (Claude Code)
SKILL.md = "user manual for the AI" (Claude AI)
bin/obs-health = "the actual product" (the OS)Five audiences, five formats, five files. The plugin works because each audience gets exactly what it expects.
Order they're consumed during a user install
USER: /plugin marketplace add <S3 URL>
↓
1. Claude Code reads marketplace.json ← "obs-health is available, get it from npm"
↓
USER: /plugin install obs-health@lofty-internal-tools
↓
2. Claude Code calls npm install lofty-obs-health
↓
3. npm reads package.json ← knows what to download
↓
4. npm downloads + extracts the tarball
↓
5. Claude Code reads plugin.json ← knows plugin metadata
↓
6. Claude Code mounts:
- bin/obs-health on PATH ← OS can now run obs-health
- skills/obs-health/SKILL.md ← Claude AI now knows how to use it
↓
USER: asks question
↓
7. Claude AI reads SKILL.md ← decides to run obs-health
8. OS shell runs bin/obs-health ← actual work happensFront door vs back room
The plugin is just the front door — the actual logic lives elsewhere.
What's in the plugin (front door)
obs-health-plugin/ ← packaging, not logic
├── package.json # how to ship via npm
├── .claude-plugin/
│ ├── plugin.json # name + version
│ └── marketplace.json # catalog
├── bin/obs-health # thin curl wrapper (~100 lines)
└── skills/obs-health/SKILL.md # instructions for ClaudeThe plugin is ~12 KB total. No SQL, no API endpoints, no business logic. It just curls a Lambda URL.
What's behind the scenes (the real logic)
AWS Lambda function "obs-health":
└── code/
├── lambda_handler.py # router + manifest endpoint + auth
├── shared.py # _api_get, _db_query helpers
└── signals/ # ALL THE BUSINESS LOGIC
├── account/user.py # SQL queries, CRM API calls
├── account/mls.py # MLS lookup logic
├── account/domain.py # vanity domain + subdomain
├── leads/count.py # lead counting logic
├── leads/import_record.py # CSV import history
├── leads/property_alert.py # property alert adoption %
├── automation/email.py # Gmail/Outlook detection
├── automation/routing.py # routing rules + priority cross-ref
├── automation/smart_plan.py # plan filters, scope rules
├── numbers/company.py # phone DB queries
└── numbers/ai.py # AI number + dialer cross-validationThe list of signals above is illustrative — Lambda's lambda_handler.py registers them in a single COMMANDS dict that's served live via the manifest endpoint. The CLI and SKILL.md never enumerate signals; they ask Lambda.
This is where:
- SQL queries hit the internal DB
- Internal CRM APIs are called
- Business rules live (e.g. "exclude trash leads", "cross-validate dialer")
- Secrets are loaded from Lambda env vars + Secrets Manager
This code is private. It's only in your Lambda function, not in the npm package, not in any public git remote.
Visual: front door vs back room
┌──── PUBLIC (anyone on internet) ───────────────────────┐
│ │
│ npm (lofty-obs-health): │
│ bin/obs-health ← thin passthrough CLI │
│ SKILL.md ← Claude AI instructions │
│ │
│ This is what users install. │
│ Useless without the API key. │
│ Doesn't enumerate signals — asks Lambda for them. │
└────────────────────────────────────────────────────────┘
│
│ HTTPS + x-api-key header
▼
┌──── PRIVATE (your AWS account only) ───────────────────┐
│ │
│ AWS Lambda: │
│ lambda_handler.py ← entrypoint │
│ signals/*.py ← BUSINESS LOGIC │
│ shared.py ← DB + API helpers │
│ │
│ Lambda env vars: │
│ CRM_API_TOKEN, DB_PASS, etc. │
│ │
│ AWS Secrets Manager: │
│ (api-key secret) │
└────────────────────────────────────────────────────────┘Why this split is good design
- Public package = safe to be public — nothing in it can do harm. It's basically a curl wrapper.
- Logic stays private — your SQL, internal API endpoints, business rules never leave AWS.
- New signals = zero user action — the CLI is signal-agnostic. Adding a new signal to Lambda's
COMMANDSdict is enough; every CSM picks it up on their nextobs-healthdiscovery call. No npm publish, no/plugin update, no Cowork zip re-upload. - Updates = rare — bug fix in
signals/*.py? Just./deploy.sh lambda. Users don't reinstall. The plugin only needs republishing for CLI-level changes (new flags, HTTP fixes) — exceptionally rare. - Scales to many clients — could write iOS app, web dashboard, Slack bot, all hitting the same Lambda. None of them need to know the business logic.
