Skip to main content

blumkin

blumkin

PyPI version Python 3.14+ License: MIT CI Release Please

Personal Microsoft 365 and Google Workspace skills CLI — named after Rose "Mrs. B" Blumkin, Berkshire's legendary operator.

blumkin turns your own calendar, mail, and chat into small, invokable skills any coding agent (Cursor, GitHub Copilot, Claude, …) — or a human — can run over the shell, instead of re-implementing OAuth and the Graph / Google API clients each time.

It acts as the signed-in user over delegated OAuth (public client + interactive browser). No server, no app-only permissions, no shared secret. --json on every command for agents.

What it does

One config file, one or more named profiles (Microsoft, Google, or both), selected with --profile or a tag. blumkin skills list --json is the live catalog:

Group Skills
auth login, logout, refresh, status
calendar today, view, freebusy, suggest, create, accept, cancel, update
mail inbox, list, get, folders, draft, update-draft, delete-draft, send-draft, reply, forward, signature, attachments (+ download)
chat (Teams / Google Chat) find, last, send, edit, delete, attachments (+ download)
docs create (a Markdown subset → a real Google Doc, or a .docx in OneDrive)
meeting (Microsoft) get, transcription
people resolve
mcp serve, install, status

Plus blumkin doctor (setup check), capabilities (structured capability/status discovery), skills / profiles (discovery), upgrade (self-update via pipx / uv tool / an editable checkout), and completion.

Reads work with the base scope set. Anything that reaches another person (mail send, calendar invite, chat message) needs an explicit --yes. Google support is at near-parity with Microsoft — the Google Workspace section below has the exact verb list and the handful of provider differences.

Install (blumkin on PATH)

pipx install blumkin
pipx ensurepath          # first pipx install only; opens a new shell

This always includes the MCP server (mcp) and, on macOS, keyring so tokens prefer OS Keychain storage over a plaintext file (token_storage = "auto" still falls back to the file if no real backend is reachable at runtime; blumkin doctor warns if that happens).

Then invoke the binary directly — not uv run blumkin:

blumkin --version                       # version, commit, and which binary answered
blumkin auth login                      # once per machine / when cache is cold
blumkin doctor                          # config, token cache, active scope set
blumkin skills list --json              # machine-readable catalog for agents
blumkin calendar today --json
blumkin mail inbox --top 10 --json
blumkin chat last --with "Sam Rivera" --n 3 --json

pipx puts blumkin in its bin dir (usually ~/.local/bin); pipx ensurepath makes sure that is on PATH.

On macOS, the wheel also bundles a small compiled blumkin-agent daemon (issue #328/#339 - an ssh-agent-style background process that can cache decrypted Microsoft/Google credentials behind a local presence check so re-verification happens roughly once per token_reverify_after window (default 24h) instead of on every command). The cache is best-effort and macOS-only: non-macOS platforms, token_reverify_after = 0 / "never", or an unavailable agent all fall back to the direct keychain/file backend. It ships as a prebuilt universal2 binary; installing from PyPI needs no Rust toolchain. Building from a clone does (see "From a clone" below) - without one, or on non-macOS platforms, blumkin still works fully, just without that feature (blumkin agent status reports it as unavailable).

Upgrade

blumkin upgrade

Detects how blumkin is installed and acts to match: pipx upgrade blumkin for a pipx app, uv tool upgrade blumkin for a uv tool, and for an editable -e <path> install (or a bare source checkout) the git pull --ff-only plus --force reinstall a package upgrade cannot do — printed to run yourself, or run for you with blumkin upgrade --yes. from: / to: are the on-disk blumkin --version before and after; an unmanaged install is reported, not touched.

From a clone (developing blumkin)

uv sync --group dev
uv tool install -e .        # editable; `blumkin` now points at the checkout

blumkin --version reports the checkout's commit. blumkin upgrade recognises the editable install and prints (or, with --yes, runs) git pull --ff-only followed by uv tool install -e . --force to re-bake the metadata; blumkin doctor warns when a pull left the installed version stale.

On macOS, uv sync/uv tool install compile the blumkin-agent daemon (see rust-agent/README.md) via a hatchling build hook (hatch_build.py) — a Rust toolchain (brew install rust or rustup) must be on PATH. No toolchain, or a non-macOS platform, degrades gracefully: the install still succeeds, just without that binary.

To expose every skill as a typed MCP tool for MCP-aware agents, run blumkin mcp install — a guided setup that registers blumkin mcp serve with Claude Code, Cursor, and the GitHub Copilot CLI, confirming each. mcp is a core dependency (no extra install step needed). See docs/agent-integration.md.

To use blumkin from agent sessions in other repos (Cursor personal skill, or Copilot CLI instructions), see docs/agent-integration.md. For cutting a release, see docs/RELEASING.md.

Discovering commands

Every group and leaf command has --help with a description and worked examples:

blumkin --help                     # top-level map, common workflows, exit codes
blumkin calendar --help            # a group's commands + typical flows
blumkin calendar create --help     # one command: args, notes, example invocations

blumkin skills list --json is the machine-readable catalog for agents.

Shell completion

blumkin completion <bash|zsh|fish> prints a completion script. Let blumkin install it to the per-user completion dir (idempotent; --force to overwrite, --json reports the path):

blumkin completion bash --install
blumkin completion zsh --install    # then put its dir on $fpath before compinit
blumkin completion fish --install

Or place it yourself — bash-completion v2 lazy-loads this path (no rc edit):

blumkin completion bash > ~/.local/share/bash-completion/completions/blumkin.bash
blumkin completion fish > ~/.config/fish/completions/blumkin.fish

…or source it from your rc file:

blumkin completion bash > ~/.blumkin-complete.bash
echo 'source ~/.blumkin-complete.bash' >> ~/.bashrc
blumkin completion zsh > ~/.blumkin-complete.zsh
echo 'source ~/.blumkin-complete.zsh' >> ~/.zshrc

Open a new shell afterwards. The script calls back into blumkin for completions, so keep it on PATH.

Config (~/.config/blumkin/)

Create ~/.config/blumkin/config.toml (mode 0600). One file, named profiles — one per account, Microsoft or Google — selected with --profile, a tags entry, or BLUMKIN_PROFILE (see blumkin profiles list --json). With more than one profile, blumkin fails closed rather than guessing which account to act as.

default_profile = "work"

[profiles.work]
client_id = "<entra-public-client-id>"
tenant_id = "<your-entra-tenant>"
default_tz = "<IANA timezone, e.g. America/New_York>"
provider = "microsoft"
tags = ["@work", "work", "microsoft", "m365"]
token_storage = "auto"         # auto | keyring | file
token_reverify_after = "24h"   # 30m, 24h, 7d, 1w; 0 / "never" disables agent-mode

[profiles.personal]
provider = "google"
default_tz = "<IANA timezone>"
google_oauth_client_file = "~/path/to/google-oauth-desktop-client.json"
tags = ["@personal", "personal", "google", "gmail"]

config.toml must use [profiles.<name>]; flat top-level keys are not supported (a config with none configures zero profiles rather than an implicit one).

A top-level [preferences] table sets font_name / font_size / html_email (default true) for every profile; [profiles.<name>.preferences] overrides one profile. A profile value that disagrees with the top-level one for the same key still wins, but blumkin warns about it on stderr:

[preferences]
font_name = "Calibri"
font_size = 11
html_email = true

[profiles.personal.preferences]
font_size = 13  # overrides just this key for this profile

Set tenant_id, default_tz, and provider in the profile table (there are no org-specific code defaults). provider defaults to microsoft when omitted. token_storage defaults to "auto". token_reverify_after defaults to "24h" and accepts the same compact duration forms blumkin already uses elsewhere (30m, 24h, 7d, 1w); 0 or "never" disables agent-mode for that profile entirely.

Interactive browser auth is public-client only (client_id; plus tenant_id for Microsoft). Do not set a client secret for these flows. Set tenant_id to your tenant's specific GUID or verified domain, never common / organizations / consumers - see the app-registration hardening checklist in docs/SECURITY-AT-A-GLANCE.md before your first login.

account_type defaults to "organizational". Set account_type = "personal" on a Microsoft profile that logs into a personal Microsoft account (outlook.com / live.com / hotmail.com, an MSA). blumkin never infers this from tenant_id - set it explicitly. Personal accounts cannot be granted Teams/People-directory Graph scopes, so blumkin drops Chat.Read, Chat.ReadWrite, OnlineMeetings.ReadWrite, and People.Read from the requested scope set for that profile, and chat.*, meeting.*, and people.resolve fail closed with a usage error explaining why. Calendar, mail, and mail auto-reply/signature skills are unaffected. A personal-account profile also needs tenant_id = "consumers" (or "common") since personal accounts have no dedicated tenant GUID - that reopens the wider device-code-phishing surface the hardening checklist warns against for work/school tenants, so only set it for a profile you know is a personal account. Setting account_type and tenant_id alone is not enough: the Entra app registration's "Supported account types" also has to allow personal Microsoft accounts (either "Personal Microsoft accounts" only, or the multi-tenant + personal option) - the single-tenant "this organizational directory only" setting the hardening checklist recommends for work/school profiles will refuse a personal-account sign-in outright, regardless of account_type / tenant_id. Register a separate app for personal-account profiles rather than loosening a work/school registration's account-type setting. See docs/SECURITY-AT-A-GLANCE.md. For a full walkthrough starting from a bare personal Microsoft account with no Entra directory yet (Azure Free signup, app registration, first login, troubleshooting), see docs/microsoft-personal-setup.md.

Microsoft token cache files (under profiles/<name>/):

  • msal_token_cache.json
  • auth_record.json

BLUMKIN_CONFIG_DIR overrides the config directory (not a profile) — useful for an isolated setup or tests. Never commit anything under the config dir.

Google Workspace (provider = "google")

Full walkthrough: docs/google-setup.md (Console project, APIs, consent screen / test users, Desktop client JSON, named profile, login, smoke, troubleshooting).

Short form — point the profile at your Google Cloud Desktop OAuth client JSON (the Console download). That file holds client_id / client_secret; do not put the secret in toml or environment variables. Download that JSON when you create the client — the secret is shown once and Google will not let you re-download it later (recovery means Reset secret / a new client; see docs/google-setup.md §A.4):

[profiles.personal]
provider = "google"
default_tz = "..."
google_oauth_client_file = "~/path/to/google-oauth-desktop-client.json"
tags = ["@personal", "personal", "google", "gmail"]

blumkin auth login records the signed-in address as email = "..." in that profile the first time (display only - it is never used to pick a profile, and never rewritten afterwards). blumkin profiles list shows it, so two profiles are tellable apart at a glance; blumkin doctor warns if the profile is later signed in as a different account.

Optional: set client_id in toml as well; when omitted it is read from the JSON. Keep the client JSON mode 0600 and outside the repo.

Optional non-secret overrides (issue #368): google_auth_uri, google_token_uri, and google_redirect_uris may also be set in toml, each taking precedence over the same field in the Desktop client JSON, which in turn takes precedence over blumkin's own defaults (https://accounts.google.com/o/oauth2/auth, https://oauth2.googleapis.com/token, ["http://localhost"]). Most setups never need these — they exist for non-standard endpoints/redirects.

Optional: vault client_secret in the OS keychain instead of a file (issue #368). client_secret is never accepted in toml, but it no longer has to live in the Desktop client JSON either. The guided path does both the ingest-and-split in one step:

blumkin --profile personal auth setup --yes \
  --from-file /path/to/google-oauth-desktop-client.json

Or vault it directly with:

blumkin auth set-app-secret --kind google_client_secret --from-file /path/to/google-oauth-desktop-client.json

Once vaulted, it takes precedence over any client_secret still in the file, and google_oauth_client_file itself becomes optional: with client_id (and, if needed, google_auth_uri / google_token_uri / google_redirect_uris) set in toml, the profile no longer needs the Desktop JSON at all. blumkin auth set-app-secret --kind google_client_secret --delete removes the vaulted value (the Desktop JSON, if still configured, keeps working as before).

Coverage. Google runs auth, all of calendar (update attaches a Meet link instead of a Teams link; create takes the same --repeat recurrence flags), all of mail reads and writes, people resolve (own contacts, plus the Workspace directory on a Workspace account), chat find / last / send / edit / delete / attachments, and docs create (a native Google Doc via the Docs API; --folder targets a folder blumkin itself created). meeting get / meeting transcription are stubbed by design — Google Meet transcript access is deliberately not implemented (docs/DECISIONS.md D8). A few option-level combinations also still fail closed on a Google profile: mail list / mail inbox --importance / --has-attachments / --orderby, and calendar suggest --treat-tentative free. Everything else fails closed with a clear error.

Provider differences.

  • mail folders lists Gmail labels that act as folders; mail list --folder still takes the well-known names.
  • Mail writes need the gmail.compose scope — re-run blumkin auth login once after upgrading or those calls exit 4 (missing_scope).
  • Chat attachments are listed but Drive-backed files are not downloadable.
  • A returned draft id is the Gmail draft id; attachments[].id is null (Gmail carries attachments inside the raw message).

Token file (written by blumkin auth login): profiles/<name>/google_token.json. graph_timeout_seconds in toml bounds Google HTTP and token-refresh calls too. Never commit any of these files.

Local document reads. blumkin docs read --path ./file.pdf --json reads a local PDF/DOCX/XLSX/image already on disk (for example from drive download, drive export, mail attachments download, or chat attachments download). It refuses files larger than 25 MB. Install only the extras you need:

uv tool install -e '.[pdf]'
uv tool install -e '.[xlsx]'

--ocr is only reachable through the PDF code path, so it needs pdf too - .[ocr] alone cannot read a PDF:

uv tool install -e '.[pdf,ocr]'

Images (.png/.jpg/.jpeg/.tif/.tiff/.bmp/.webp) are always OCR'd - there is no text layer to fall back to, so --ocr is implicit for them and cannot be passed. They only need the ocr extra (not pdf), and only the tesseract binary (not poppler, which is PDF-only):

uv tool install -e '.[ocr]'

The ocr extra also needs system binaries on macOS - tesseract for both PDF and image OCR, poppler for PDF page rendering only (image OCR does not need poppler):

brew install tesseract poppler

Prompt-injection flagging. docs read and mail get scan extracted text/body content for common prompt-injection patterns (instruction-override phrasing, zero-width/bidi-override characters, link-label/URL mismatches, role-impersonation blocks, base64 blobs next to "decode/execute" triggers). Detection is heuristic and advisory only: it never blocks, redacts, or alters the returned content. A match adds an injection_warning field to the JSON payload (null when clean) and a ⚠️ POSSIBLE PROMPT INJECTION DETECTED banner to human-formatted output, so the caller can decide whether to act on the content. See issue #282 for the full design and deferred scope (no LLM-based detection, no hard-block/confirmation wiring in v1).

Tests

uv run pytest -m 'not live'                        # CI-equivalent (mocks / offline)
BLUMKIN_LIVE=1 uv run pytest -m live               # live Graph reads + silent refresh
BLUMKIN_LIVE_GOOGLE=1 uv run pytest -m live_google # live Google reads (Google profile)

Live tests need ~/.config/blumkin/ by default (override with BLUMKIN_CONFIG_DIR): config.toml, token cache, auth record, and a usable refresh token. The live_google tests instead need a logged-in Google profile (provider = "google"). Never commit those files.

Security

blumkin acts as you over delegated OAuth — no server, no shared secret, no one else's data. Auth and config live only under ~/.config/blumkin/ and are never committed.

  • docs/SECURITY-AT-A-GLANCE.md — one page: what it touches, the auth model, blast radius, how releases are trusted.
  • SECURITY.md — full policy, response targets, and private vulnerability reporting.

Docs

License

MIT © 2026 Henrique Andrade (GitHub's thehcma) — see LICENSE.

Release files for blumkin 1.9.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for blumkin 1.9.0
File Size Uploaded
blumkin-1.9.0.tar.gz 995.2 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for blumkin 1.9.0
File Interpreter ABI Platform
blumkin-1.9.0-py3-none-any.whl Python 3 none any Details
blumkin-1.9.0-cp314-cp314-macosx_14_0_universal2.whl CPython 3.14 CPython 3.14 macOS 14.0+ universal2 (ARM64, x86-64) Details

Total release size: 2.2 MB

Release files / blumkin-1.9.0.tar.gz

Download URL blumkin-1.9.0.tar.gz
Size 995.2 kB
Tags Source
SHA-256 checksum
How to use checksums
d9d223b2bf9daad2438471d3bfb4786e66b1efe410de80a731d6c2178b767586
BLAKE2b-256 checksum
How to use checksums
ed895641c31d6470bd80a5f02f9117367d00dd5bd8d976e69124814137b780ab
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / blumkin-1.9.0-py3-none-any.whl

Download URL blumkin-1.9.0-py3-none-any.whl
Size 376.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c589fac60e3993251f450c146cb85b7cb246a84436cb9c542dc058c6f958e672
BLAKE2b-256 checksum
How to use checksums
3cd06e6bb8bcda88eee64198afa458a86c2ffc2a41f36c18c4372f54233556d5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / blumkin-1.9.0-cp314-cp314-macosx_14_0_universal2.whl

Download URL blumkin-1.9.0-cp314-cp314-macosx_14_0_universal2.whl
Size 866.4 kB
Tags CPython 3.14 macOS 14.0+ universal2 (ARM64, x86-64)
SHA-256 checksum
How to use checksums
8762ae1da8474cc08f8039872825063cc7a43a7ed893095a57c5f3698fc1521a
BLAKE2b-256 checksum
How to use checksums
664fb8af5ddf923a4357c66852e5a3bb3bae12bfe120d38288ed8b5d28c2f7e1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

1.9.1

3 release files

This release

1.9.0 This release

3 release files

1.8.1

3 release files

1.8.0

3 release files

1.7.0

3 release files

1.6.0

3 release files

1.5.0

3 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page