Integration and automation tooling for Hillspire IT Operations.
Hillspire supports a family office spanning ~30 legal entities, 21 Google Workspace organizations, one shared Okta tenant and a long tail of SaaS. Most of the work in this repo exists because a task that is trivial for one organization becomes a genuine engineering problem when it has to be right for all of them, every time, without a human remembering which console to open.
So this is not a scripts folder. It is a set of small, single-purpose services and jobs that each keep one thing true:
A plain-English catalogue of everything below, written for people rather than
engineers, lives in Confluence: IT Projects & Operations → 3.I Coding
Documents → IT Automation & AI Tooling.
A formatted PDF edition is at docs/AUTOMATION_AND_AGENTS_CATALOG.pdf.
| Integration | What it does | Status |
|---|---|---|
harry_helpdesk_agent |
Harry. Natural-language Slack agent for software access. Files JSM requests into the helpdesk team’s two request types, answers how-to questions from 25 curated ITOPS guides, walks people through setup step by step with the guides’ own screenshots, enforces the KnowBe4 training gate and the team’s per-app justification bar, and raises a diagnosed ticket with a human when it cannot finish the job. Claude Opus 5 tool loop, 11 tools, no keyword matching anywhere. | Live |
google_workspace_admin_assistant |
Greg. Slack agent answering plain-English Workspace admin questions across all 21 organizations. Claude Opus 5 tool loop over ~20 read-only tools. Strictly read-only, enforced by tests. | Live |
slack_helpdesk_module |
Helpdesk Tasks. Mailbox and offboarding actions from one Slack modal, routed to the right Workspace tenant by email domain. Cloud Tasks worker, fail-closed super-admin guardrail, bulk CSV mode. | Live |
slack_helpdesk_module/audit_publisher |
Every helpdesk action → BigQuery → a monthly Confluence page, rebuilt from source each run. | Live |
All run unattended on GitHub Actions. Each exits non-zero when something needs a person, so a failed run is itself the alert.
| Integration | Cadence | What it keeps true |
|---|---|---|
outage_slack_ping |
5 min | Sally. 16 vendor status feeds → Slack, on incident open and resolve only. |
okta_audit_log |
15 min | Okta System Log → a running Confluence audit page (groups, apps, assignments, admin grants). |
okta-api-ping |
hourly + ~10 days | Ava. Keeps 16 HR→Okta provisioning tokens from dying at Okta’s 30-day idle limit; logs every key created or revoked. |
domain_expiry_sync |
monthly | Registrar (GoDaddy, RDAP fallback) → the Expiration Date on each domain in JSM Assets. |
| Integration | What it does | Status |
|---|---|---|
okta_license_cleanup |
Buckets every Okta account against HR data to find genuinely reclaimable seats. Read-only; deactivation is separate, opt-in tooling with an undo. | Complete |
okta_entity_status |
Stamps a for-profit / non-profit attribute on every active user from their login domain. | Complete |
okta_profile_sync |
One-time profile backfill for accounts predating the TriNet integration. | Complete |
okta_group_scrape |
Read-only CSV export of every Okta group and membership. | Complete |
Not yet merged to main. Check out the branch to see the code and its README.
| Integration | Branch | What it does |
|---|---|---|
carl_contracts_agent |
feat/carl-contracts-agent |
Carl. Private, DM-only Slack agent for read-only application, executed-contract, and aggregate account-activity questions from 1Password SaaS Manager (Trelica). Three enforced data roles, exact feature-gated read scopes, and no contract documents or unsupported actual-spend claims. |
okta_title_sync + slack_title_audit + _shared |
feat/okta-title-nudge |
Corrected every Okta job title ahead of Slack’s switch to SCIM-managed titles, then forced the push to land tenant-wide — plus a read-only audit proving which users it reached. Also carries the shared HTTP/secrets/Slack/Okta/report modules. |
okta_policy_replicate |
feat/okta_policy_replicate |
Copies a parent org’s Okta security policies into a new spinoff tenant, remapping every cross-org ID by name. Password policy applied live Aug 2026. |
Kept for reference; not being worked on.
| Integration | Why it stopped |
|---|---|
chief_of_staff_assistant |
Per-user calendar/inbox Slack assistant. Validated in sandbox; pilot paused. |
phishing_check_assistant (feat/phishing-check-assistant) |
Self-serve phishing triage POC. Dropped — infosec already runs an equivalent capability. |
okta_helpdesk_agent (feat/okta-helpdesk-agent) |
The original /helpdesk POC. Superseded by Harry, which shipped instead. |
| Path | Contents |
|---|---|
sophos/ |
Sophos Endpoint removal, install, and migration-validation PowerShell. |
windows/ |
Windows imaging, taskbar management, and endpoint helper scripts. |
web_dev/ |
Rise web properties and Apps Script widgets — fellow directory, events calendar, dynamic handbook, newsletter and birthday widgets. |
Four conventions run through everything here. They are worth knowing before adding an integration, because they are the reason the tools behave predictably.
Secrets never live on disk. Scripts only ever read process.env. The
invocation decides where the value came from — op run with 1Password on a
laptop, Actions secrets in CI, Secret Manager bound at container start on Cloud
Run. The same script runs unchanged in all three, and nothing has to be edited
per environment.
Employee data stays outside the working tree. HR exports and run reports
contain names, emails, titles and employee IDs. A tracked .gitignore only
protects the branches it exists on — a file protected on a feature branch is
untracked and unignored on main, one git add -A from being committed. So
integrations default both their input and their output to
~/Documents/hr-exports/<integration>/, outside the repo entirely. The root
.gitignore is a second line of defence, not the first.
Read-only tools have no write methods. Where a tool is meant to read, the
client it uses has no write capability at all — the shared Slack client has no
post method, the Workspace assistant has no mutating call path, and a unit test
fails the build if a non-.readonly scope is ever added. It is a property of the
code, not a rule someone has to remember.
Anything that writes, dry-runs first. Every write tool defaults to (or
prominently offers) --dry-run, is idempotent so a partial failure just needs
another pass, and drops a JSON audit log plus a Confluence-ready report next to
the run. okta_policy_replicate inverts the default and requires --apply,
because it writes a tenant’s authentication policy rather than a data field.
integrations/<name>/ one integration per folder
README.md what it does, setup, flags, and what bites
CLAUDE.md working brief: live state and decisions (where present)
HANDOFF.md operational state for deployed services (where present)
.env.example required config, with no real values
.github/workflows/ the scheduled jobs
docs/ repo-wide documentation
Read a deployed integration’s HANDOFF.md before touching it — it holds the
hard-won operational facts (live revision, required Cloud Run flags, gotchas).
Python integrations expect a virtualenv at the repo root; Node integrations use Node 18+ and install per folder.
# Python (3.13)
python3.13 -m venv .venv
.venv/bin/python -m pip install -r integrations/<name>/requirements.txt
# Node
cd integrations/<name> && npm install
Several Node tools deliberately use only Node built-ins and need no install at all.
npm run dry-run # or: python <script>.py --dry-run
op run --env-file=./secrets.env.tpl -- npm run run-live
Read the dry-run output before the live run. Mint a fresh API token for the run and revoke it when you are done — that revocation, not deleting a file, is the control that matters.
Short-lived feat/ or fix/ branches, one per change, merged to main through
a PR, branch deleted after. No long-lived development branch.
git switch -c feat/thing origin/main
# ... work, commit ...
git push -u origin feat/thing && gh pr create
Because the repo is worked on from more than one machine, push before you leave one and pull before you start on the other.
integrations/<name>/ with a README.md that covers what it does, setup,
every flag, and — most usefully — the things that bite..env.example listing every variable, with no real values.process.env only. Never write a token to disk.~/Documents/hr-exports/<name>/..github/workflows/ and make it exit
non-zero when a human needs to look.