Skills tell an agent what to do. Shared tools are how it reaches the outside world: small Python scripts, 1 per service, in a single folder at ~/ai/_shared/. Every project in my workspace calls the same copy.
There are 30 Python files in that folder today. 29 are tools with a command line, and 1 (env_loader.py) is the helper that loads API keys for the others. This note lists 27 of them: 3 work-only tools stay private.
The pattern
| Where | 1 folder, ~/ai/_shared/, used by work, personal and lab projects alike |
| Keys | 1 file, _shared/.env, for every API key. An environment variable wins if it is already set |
| Language | Python, standard library first: 26 of the 30 import nothing outside it |
| Interface | A CLI for the agent, plus functions other scripts can import |
| Scraping | Through a SOCKS5 proxy, only for scraping, never for API calls |
| Size | 39 to 883 lines each. Small enough for an agent to read before it uses one |
Why it is built this way:
- One place for keys. A rotated key changes in 1 file, and every project picks it up. Agents may read variable names, never print values.
- Stdlib first. A script that needs only
urllibandjsonruns on any Mac with Python, with nothing to install. Only 4 scripts import a third-party package: calendar parsing, HTML to Markdown, image resizing and the proxy.yt.pyalso drives the yt-dlp command-line tool. - CLI for agents. An agent can run
python3 ~/ai/_shared/perplexity.py "query"without knowing anything about the API behind it. The docstring at the top of each file is the manual. - Importable for scripts.
pageimages.pyimports the crawler and the proxy helper instead of rewriting them.send_email.pyimports the mail helper.
The skills lean on them: 23 skills reference a shared tool by path. The most used by far is sql.py, the SQLite wrapper behind most database lookups.
Research
| Tool | What it is for |
|---|---|
perplexity.py |
Web research with citations, through Perplexity's Sonar models |
grok.py |
Research on live X posts and the web through xAI's Grok, plus image generation |
apollo.py |
Company data enrichment from a domain, through Apollo.io |
knowledgeowl.py |
Mirrors Kaltura's public Knowledge Centre to Markdown, with full-text search |
Web data
| Tool | What it is for |
|---|---|
firecrawl.py |
Scrapes 1 or more URLs to Markdown through Firecrawl |
cloudflare.py |
Crawls a site to Markdown through Cloudflare's browser rendering, for pages that need JavaScript |
pageimages.py |
Harvests a page's own content images, filters logos and trackers, and ranks the rest for a human to pick |
proxy.py |
The SOCKS5 proxy helpers for any script that scrapes |
Media
| Tool | What it is for |
|---|---|
yt.py |
YouTube metadata, transcripts, chapters, audio or video, through yt-dlp |
tts.py |
Text to speech through ElevenLabs, as .mp3 for videos |
imagekit.py |
Uploads images and builds resized URLs, so no page ever serves a full-size file |
Publishing
| Tool | What it is for |
|---|---|
publish_nicai.py |
Publishes a page or folder to nicailab.com. See What is NicAIlab |
publish_kc.py |
Publishes decks, avatar demos and one-pagers to kaltura.cloud |
kc_demo_index.py |
Builds the index page of my live avatar demos on kaltura.cloud |
Both publishers give each project an unguessable link segment. It isn't authentication, just a link that works only if I gave it out.
Email, calendar and chat
| Tool | What it is for |
|---|---|
calendar.py |
Reads my published Outlook calendar and lists free slots |
ics_future.py |
Trims that calendar to future events for the feed my booking page reads |
cal.py |
My cal.eu booking account: event types, open slots, bookings |
migadu.py |
Read-only access to my personal mailboxes, plus a sender denylist for spam |
send_email.py |
Sends a plain-text email and files a copy in Sent |
DNS and home
| Tool | What it is for |
|---|---|
ionos.py |
DNS records for nicolasdeville.com: list, add, update |
spaceship.py |
DNS records for kaltura.cloud: list, add, merge |
ha_ws.py |
A minimal WebSocket client for Home Assistant, in the standard library only |
ma_ws.py |
The same for Music Assistant |
ha_camera_feed_listener.py |
Waits for a Home Assistant event and opens a matching view on my Mac |
Utilities
| Tool | What it is for |
|---|---|
env_loader.py |
Loads keys from _shared/.env for every other tool |
sql.py |
A drop-in sqlite3 CLI with CSV output. Reads return rows, writes return a count |
client_init.py |
The only write path into my client notes folder: creates new, never overwrites |
client_init.py shows a pattern I now use for anything that writes into a folder I care about. The tool can do exactly 1 thing, checks every input before the first write, and refuses whatever it can't prove safe. An agent can't break what the tool doesn't let it touch.
What works, what doesn't
What works:
- Swapping a service is cheap. A skill calls
perplexity.py, not Perplexity. If I change provider, I change 1 script. - Read-only by default. The mail tools open mailboxes read-only and never delete. Sending email is a separate script with a dry-run mode, and my agents run it only when I tell them to send.
- The docstring is the manual. Most files open with usage examples, so an agent reads the top of the file and runs it. No separate documentation to keep in sync.
What doesn't:
- The folder is a drawer. API wrappers sit next to DNS, home automation and mail tools. Nothing enforces a structure yet.
- The README lags. It lists 13 tools. The folder holds 30.
- No tests. Each tool gets checked by use. A silent API change shows up as a failed skill, not a failed test.
What's next
- Bring the README back in line with the folder. Generating it from the docstrings would stop it drifting again.
- Give each tool a smoke test that runs without spending API credits.
Related notes
- What NicAI is: What is NicAI
- The skills that call these tools: The NicAI skills catalogue
- Where the data lands: The NicAI data layer
- The publishing space: What is NicAIlab