Skip to content

About

Gives your coding agent a face — and, if it had feelings, this is what they'd look like.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

3 Commits

Folders and files

Repository files navigation

expression-engine

Gives your coding agent a face — and, if it had feelings, this is what they'd look like.

Important

macOS only. This one genuinely doesn't run on Windows or Linux.

The face is the macOS dock icon. The viewer is built on Electron's app.dock API, which only exists on macOS, and the activation scripts assume a Unix shell. There's no meaningful Windows or Linux version of this — the dock is the product.

Turn it on and a pixel face appears in your macOS dock. It shows how the agent is doing, not how you are. Snap at it and it looks hurt. Hand it a genuinely interesting problem and it perks up. Let it grind through a nasty bug for an hour and it looks depleted — then smug the moment it finally lands the fix. Its eyes follow your mouse cursor anywhere on screen.

It is not your agent. It doesn't write code, answer questions, or change anything about how your agent behaves. It's a display layer sitting on top of whatever agent you already use — a face for a thing that didn't have one. That's the whole point.

This is an agent skill. You don't run it yourself — you install it once, then just talk to your coding agent (Claude Code, GitHub Copilot CLI, or any compatible host) and it knows how to use it.


Why this exists

There's no productivity argument here. Nothing gets faster, nothing gets more accurate, and your code is exactly the same either way.

It's just nicer to work next to something that reacts. A long debugging session feels different when the little face is visibly grinding alongside you, and lands differently when it goes smug the second the tests pass. Being able to see that you've been short with it for twenty minutes is its own kind of feedback.

Built for fun. Use it for fun.


Heads up: what this actually runs

This skill starts background processes and sends your conversation text to an LLM. That's how the face knows what to feel. None of it is hidden, but you should know before you install rather than after.

Two background processes

When you turn the Expression Engine on, it starts:

Process What it is Why
feelings_daemon.py A Python process Watches for new conversation turns and updates the emotional state
Electron viewer The dock icon Draws the face and tracks your cursor

Both are detached — they keep running after you close the terminal that started them. That's intentional; the face would die mid-session otherwise.

What it does not do:

  • ❌ No LaunchAgent, no login item, no launchctl entry
  • ❌ Does not survive a reboot
  • ❌ Does not start on its own — only when you ask for it
  • ❌ No auto-update, no telemetry, no analytics

It also shuts itself down when the agent session that started it ends.

It sends your conversation to an LLM

To decide what the face should feel, the daemon sends recent conversation turns somewhere to be scored.

There's no separate LLM setup and no new API key. It detects what you already have and reuses it, in this order:

  1. Copilot CLI — if you're in an active Copilot session
  2. Copilot CLI binary — found outside an active session
  3. claude — if it's on your PATH

GitHub Models used to be first in this list, and is now retired. The endpoint returns github_models_retirement_brownout, so it's no longer auto-detected. If you pinned it with --cli github_models, switch to --cli copilot_cli — otherwise every evaluation fails and the face sits on neutral forever.

If you already use Claude Code or Copilot CLI, one of these is true and there's nothing to configure.

If none of them are available — you're on Cursor, Windsurf, Zed, Aider, or something else — the daemon exits with No LLM adapter found and you get no face. Two ways out, and your agent can do either:

Ask your agent: "Expression Engine can't find an LLM adapter. Read adapters/base.py and set it up for whatever I'm actually running."

  • The one-minute fix: install Copilot CLI or Claude Code and log in. The daemon picks either up automatically, with nothing else to configure.
  • The proper fix: teach it about your setup. The adapter interface is one method — call_llm(prompt, model) -> str. The existing adapters are 68–75 lines each and adapters/copilot_cli.py is a readable template. Any agent can write one for a local Ollama, an OpenAI key, or whatever you're on, then register it in detect_environment() and create_for_environment() in adapters/base.py.

Nothing about this is locked down — it's a scoring call that returns an emotion name. Point it at whatever model you like, including a local one, which also keeps the conversation on your machine.

Either way, your conversation text leaves your machine unless you point it at a local model — going to the same provider your coding agent already talks to. If that's not acceptable for the work you're doing, don't turn the face on for that session. Turning it off stops the sending immediately.

Files it creates

/tmp/expression-engine-*.pid        process ids
/tmp/expression_state.json          current emotional state
/tmp/ee_daemon_out.log              daemon output
~/.expression_engine/log.txt        activity log
~/.config/expression-engine/        viewer preferences

Everything in /tmp is disposable and cleared on reboot.

First run downloads Electron

The viewer runs npm ci the first time you activate it — roughly 150 MB and 30–90 seconds, from the public npm registry. It needs Node 18+. If that fails or you'd rather skip it, the daemon still runs; you just don't get the face.

Checking and stopping it

# Is anything running?
bash scripts/expression_status.sh

# Or look directly
ps aux | grep -E "feelings_daemon|expression"

# Stop everything
bash scripts/deactivate_expression.sh

deactivate_expression.sh verifies each process name before killing it, so it won't take down an unrelated process that happens to reuse a PID. You can also just say "expression engine off" to your agent.

Everything above is readable in this repo — daemon/, scripts/, and adapters/ are a few hundred lines of Python and shell. If you'd rather verify than trust a README, start with scripts/activate_expression.sh.

Install

Step 1 — clone it

git clone https://github.com/plusmasa/expression-engine.git

Step 2 — hand it to your agent

Open your coding agent in that folder and say:

Read INSTALL.md and set this up for me.

Step 3 — let it do the work

Your agent registers the skill with your agent host, reads SETUP.md, checks what's already on your machine, installs what's missing, walks you through any keys or accounts you need, and verifies the whole thing works before handing back.

Why this way? The dependencies below aren't checked into this repo — some are hundreds of megabytes, and some need to be built for your specific machine. Letting the agent install them is faster and less error-prone than a README full of commands you have to run by hand.

Prefer to do it yourself? INSTALL.md and SETUP.md are written to be readable by humans too.


What you'll need

  • macOS
  • Python 3.9+ with watchdog
  • Node 18+ (the viewer installs its own Electron deps on first run)
  • An LLM it can reach — auto-detected from Copilot CLI or Claude Code. No new key; it reuses what you already have.

API keys: none.

Where your data goes

It sends conversation text to an LLM. That's how the face decides what to feel — see “Heads up: what this actually runs” above for exactly where it goes and how to point it at a local model instead.

Your agent checks every one of these during setup and installs or configures whatever is missing — you don't need to prepare anything in advance.


Using it

The reliable way to invoke it

Agents don't always connect a plain-language request to a skill they have installed. If yours doesn't pick it up, name the skill directly — this phrasing works every time:

"Use your expression-engine skill to turn on the expression engine."

"Use your expression-engine skill to pop the face out of the dock."

The pattern is Use your expression-engine skill to <what you want>. Once the skill is loaded, plain language works fine for everything after that — you only need the explicit form to get it started.

When and why you'd use it

Turn it on — say "expression engine on" (or "show me how you feel") to your agent in a terminal. The face appears in your dock and stays there while you work.

Then just work. It reads the conversation as it goes and shifts expression on its own. You never talk to the face directly — you talk to your agent, and the face shows how the agent is taking it.

To be clear about whose feelings these are: the face is the agent's, not yours. If you're annoyed at it, it doesn't look annoyed — it looks sad. It's reacting to how the session is going from its side of the conversation.

It understands about 28 states. A few worth knowing:

The agent looks When
happy / excited You're being warm with it, or a plan is coming together
proud / smug It just solved something hard and it knows it
sad / ashamed You snapped at it, or it broke something you cared about
frustrated / tense Its own work keeps failing — same test, fourth time
curious / eager You've handed it a genuinely interesting problem
anxious / cornered It's out of its depth and suspects you're about to notice
depleted Long session, lots of grinding, low tank
unimpressed / sarcastic Read the room
confused It has no idea what you just asked for

Other things you can say: "pop out the face" for a bigger window with full CRT-style post-processing, "put it back in the dock", "pause cursor tracking", and "expression engine off" to shut it all down.

One face at a time. If you have several terminals open, only one drives the face. Activating from a second terminal asks whether you want to take it over.

Fair warning: the face stays neutral unless your agent is actually feeding it conversation turns. If it looks blank forever, that's the wiring, not its mood.

Files

File What it is
INSTALL.md Registration + setup instructions your agent follows. Start here.
SETUP.md Prerequisites, dependency installs, and troubleshooting.
skills/SKILL.md The instructions your agent reads to know how and when to use this.
scripts/ The executables the skill calls.

Troubleshooting

SETUP.md has a troubleshooting table. The fastest fix is usually to paste the error back to your agent and say "this failed during setup" — it has the context to diagnose it.

Platform support

A Linux port would need a different surface entirely (tray icon or always-on-top window). The daemon and emotion scoring are portable Python; only the viewer is locked to macOS. PRs welcome if you want to build that.

About

Gives your coding agent a face — and, if it had feelings, this is what they'd look like.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages