@cullumco/cadence 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,7 +21,7 @@
21
21
  "agents",
22
22
  "claude-code"
23
23
  ],
24
- "homepage": "https://cullumco.github.io/cadence/",
24
+ "homepage": "https://cadence.cullum.co/",
25
25
  "repository": "https://github.com/cullumco/cadence",
26
26
  "license": "MIT"
27
27
  }
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "cadence",
3
3
  "displayName": "Cadence",
4
- "version": "0.1.4",
4
+ "version": "0.1.6",
5
5
  "description": "Ambient context for Claude Code: embodied signals, cadence dials, and finish-line guardrails. macOS-only alpha.",
6
6
  "author": {
7
7
  "name": "Cullum&Co",
8
8
  "url": "https://cullum.co"
9
9
  },
10
- "homepage": "https://cullumco.github.io/cadence/",
10
+ "homepage": "https://cadence.cullum.co/",
11
11
  "repository": "https://github.com/cullumco/cadence",
12
12
  "license": "MIT",
13
13
  "keywords": [
package/README.md CHANGED
@@ -9,10 +9,12 @@ listening to, what you told it, how you want it to respond — into every prompt
9
9
  then asks Claude to *read your prompt through that lens*. The agent stops being
10
10
  deaf to the room.
11
11
 
12
- **macOS-only (alpha).** Most signals read the Mac around you; other platforms
13
- degrade to self-report + dials + time/git.
12
+ **Built for the Mac (alpha).** The richest ambient signals read the Mac around
13
+ you. Other platforms still get the dial-movers prompt intent, self-report,
14
+ git, time/day, typing tempo — and can link Spotify for music
15
+ (`cadence spotify connect`).
14
16
 
15
- A [Cullum&Co](https://cullum.co) project · [cullumco.github.io/cadence](https://cullumco.github.io/cadence/)
17
+ A [Cullum&Co](https://cullum.co) project · [cadence.cullum.co](https://cadence.cullum.co)
16
18
 
17
19
  ## What it does
18
20
 
@@ -45,25 +47,28 @@ defers to what you actually typed.
45
47
  options, a trade-off table, and a closing "Would you like me to implement one
46
48
  of these?"
47
49
 
48
- **With Cadence, shipping cadence** — hardcore at 3 commits/hr, state set to
49
- `"ship mode"` → `{ pace=fast posture=decisive proactivity=act-freely }`. You
50
- get the call, made: exponential backoff with jitter, three attempts, here's
51
- the diff, tests pass.
50
+ **With Cadence, shipping cadence** — hardcore at 3 commits/hr, a "let's ship
51
+ it" earlier in your prompt (or `cadence report "ship mode"`)
52
+ `{ pace=fast posture=decisive proactivity=act-freely }`. You get the call,
53
+ made: exponential backoff with jitter, three attempts, here's the diff, tests
54
+ pass.
52
55
 
53
- **With Cadence, thinking cadence** — ambient music, state set to
54
- `"thinking through tradeoffs"` → `{ pace=deliberate posture=exploratory }`.
55
- You get the options laid out patiently, trade-offs actually explored, no
56
- pressure to pick one yet.
56
+ **With Cadence, thinking cadence** — ambient music, "thinking through
57
+ tradeoffs" in your own words → `{ pace=deliberate posture=exploratory }`. You
58
+ get the options laid out patiently, trade-offs actually explored, no pressure
59
+ to pick one yet.
57
60
 
58
- Same words. The room around them changed, and the agent finally saw it.
61
+ Same words. The room around them changed, and the agent finally saw it. No
62
+ setup required for the intent read — your prompt itself is a signal; a
63
+ deliberate self-report just outranks it.
59
64
 
60
65
  ## How it works
61
66
 
62
67
  **Signals → dials → a reframe lens.**
63
68
 
64
69
  1. **Signals** — what Cadence can sense right now:
65
- - **ambient** — time of day, day of week, weather (opt-in), battery, machine
66
- uptime/load, dark mode, displays, wifi, Focus/DND. Mostly zero-setup;
70
+ - **environment** — time of day, day of week, weather (opt-in), battery, machine
71
+ uptime/load, dark mode, displays, wifi (opt-in), Focus/DND. Mostly zero-setup;
67
72
  time/day work everywhere, the Mac-context probes are macOS. The one signal
68
73
  that's always there: `context: friday afternoon, rainy, focus on`.
69
74
  (Focus detection reads the DND database directly, so it needs your
@@ -75,13 +80,19 @@ Same words. The room around them changed, and the agent finally saw it.
75
80
  the hook payload: `activity: { min_since_prompt=45 prompt_len=123 }`.
76
81
  - **music** — what's playing (via macOS now-playing, any player), turned into
77
82
  a clean *vibe* (mood words) via [MusicBrainz](https://musicbrainz.org). No
78
- Spotify login, no API key, no Premium.
79
- - **self-report** what you tell it: `cadence state "two beers, shipping"`.
80
-
81
- Time/day, self-report, and git move the dials (git reads *what you're
82
- doing*: 3+ commits/hr fast pace, mid-conflict verify-first); the rest
83
- render as context the agent reads (flavor). Self-report always outranks
84
- inference "I'm shipping" beats a mid-conflict read.
83
+ Spotify login, no API key, no Premium on macOS. Off the Mac, link Spotify
84
+ once with `cadence spotify connect` (browser OAuth, opt-in). Music moves
85
+ three dials — energy → pace + posture, organic texture → warm tone.
86
+ - **self-report** what you tell it: `cadence report "two beers, shipping"`.
87
+ - **intent** read from the prompt you just typed: "let's ship this" →
88
+ decisive/act-freely, "help me debug" verify-first. Cross-platform, no
89
+ setup; this is what makes the same prompt read differently per room.
90
+
91
+ Time/day, self-report, git, and prompt intent move the dials (git reads
92
+ *what you're doing*: 3+ commits/hr → fast pace, mid-conflict → verify-first);
93
+ the rest render as context the agent reads (flavor). Self-report outranks
94
+ prompt intent outranks git — your deliberate "I'm shipping" beats a stray
95
+ "ship" in a prompt, which beats a mid-conflict read.
85
96
  2. **Dials** — four independent knobs, each `low | medium | high`, inferred from
86
97
  the signals (or pinned by you):
87
98
  - **pace** — deliberate ↔ fast
@@ -92,17 +103,20 @@ Same words. The room around them changed, and the agent finally saw it.
92
103
  *read* your prompt. Generated fresh each time; always ends "if my words
93
104
  clearly mean otherwise, follow my words."
94
105
 
95
- The dials are independent on purpose — high-energy-but-mellow music can read as
106
+ The dials move mostly independently — high-energy-but-mellow music reads as
96
107
  "fast pace, warm tone," something a single ship/think/debug label could never
97
- express.
108
+ express. Music is the deliberate exception: it moves pace, posture, and tone
109
+ together (you move *with* the music) but never proactivity — whether to act
110
+ without checking in stays your call, not the soundtrack's.
98
111
 
99
112
  ## Requirements
100
113
 
101
- - **macOS.** Cadence is mac-only for the alpha: music (AppleScript
102
- now-playing), battery, dark mode, displays, wifi, and Focus/DND all read the
103
- Mac around you. On other platforms it still runs self-report, dials,
104
- time/day, and git work anywhere, the rest degrade silently but the product
105
- is built for the Mac.
114
+ - **Built for the Mac.** The richest ambient probes music via AppleScript
115
+ now-playing, battery, dark mode, displays, wifi (opt-in), Focus/DND, focused app
116
+ read the Mac around you. On other platforms Cadence still runs and still
117
+ moves the dials: prompt intent, self-report, git, time/day, and typing tempo
118
+ work anywhere, Spotify can be linked for music, and the Mac-only probes
119
+ degrade silently.
106
120
  - **Node 20+**
107
121
  - Claude Code for the alpha adapter
108
122
 
@@ -114,15 +128,20 @@ In Claude Code:
114
128
  /plugin marketplace add cullumco/cadence
115
129
  /plugin install cadence@cadence
116
130
  /reload-plugins
117
- /cadence:try
131
+ /cadence:setup
118
132
  ```
119
133
 
120
- Then set a self-report so you can feel the difference:
134
+ `/cadence:setup` is a short conversation, not a wizard tell Claude how you
135
+ work, pick which signals you're willing to share, and see exactly what gets
136
+ injected. Or skip it and just set a self-report:
121
137
 
122
138
  ```text
123
139
  /cadence:state shipping, locked in
124
140
  ```
125
141
 
142
+ Change your mind anytime: `/cadence:pause` silences everything instantly,
143
+ `/cadence:resume` brings it back.
144
+
126
145
  Alpha testers running from source — while `@cullumco/cadence` is pending npm
127
146
  publish — see [`ALPHA.md`](ALPHA.md).
128
147
 
@@ -138,14 +157,22 @@ track, looks the artist's vibe up on MusicBrainz once, and caches it forever at
138
157
  `~/.cadence/vibe-cache.json`. If nothing's playing, the music signal is simply
139
158
  absent.
140
159
 
160
+ **Off macOS?** Run `cadence spotify connect <clientId>` once: register a Spotify
161
+ app (add `http://127.0.0.1:8888/callback` as a redirect URI), and Cadence does
162
+ the browser OAuth and stores a refresh token. From then on it reads your
163
+ currently-playing track cross-platform — identity only, vibe still from
164
+ MusicBrainz, no audio-features.
165
+
141
166
  ## Daily use
142
167
 
143
168
  ```bash
144
- cadence state "two beers, shipping" # set self-reported state (expires in 4h)
145
- cadence state # print current self-report
169
+ cadence report "two beers, shipping" # set self-reported state (expires in 2h)
170
+ cadence report # print current self-report
146
171
  cadence clear # clear it
147
172
  cadence test # preview exactly what the hook would inject
148
173
  cadence signals # every signal — live value, or why it's absent
174
+ cadence pause # silence all hooks (state survives untouched)
175
+ cadence resume # start reading the room again
149
176
  ```
150
177
 
151
178
  `cadence signals` is the legibility view: it never goes silent. Every signal
@@ -153,13 +180,23 @@ Cadence knows how to read is listed with its live value, or the exact reason
153
180
  it's absent — opt-in not taken, below a render threshold, missing permission
154
181
  (Focus needs Full Disk Access), or platform-gated.
155
182
 
156
- From inside Claude Code, the plugin skill gives the same self-report path:
183
+ From inside Claude Code, the plugin skills cover the same ground without
184
+ leaving the conversation:
157
185
 
158
186
  ```text
187
+ /cadence:setup # guided, conversational setup — shape the
188
+ # influence and pick your opt-in signals
159
189
  /cadence:state two beers, shipping
160
- /cadence:try
190
+ /cadence:try # what is Cadence seeing right now?
191
+ /cadence:pause # instant silence — prompts go through untouched
192
+ /cadence:resume # back to reading the room
161
193
  ```
162
194
 
195
+ `/cadence:setup` is the recommended first run inside Claude Code: instead of a
196
+ fixed wizard, you tell Claude how you work in plain language and it drives the
197
+ CLI for you — state, dial pins, and which opt-in signals you're willing to
198
+ share.
199
+
163
200
  ### Driving the dials by hand
164
201
 
165
202
  The dials are inferred, but you can pin any of them — your pin wins, the rest
@@ -200,52 +237,63 @@ state through other agent surfaces.
200
237
 
201
238
  ## Alpha release checklist
202
239
 
203
- Before publishing:
240
+ `@cullumco/cadence` is live on npm; each release is the same gated flow:
204
241
 
205
242
  ```bash
206
- npm run verify:alpha
207
- npm publish --dry-run
208
- npm run release:alpha
243
+ npm run verify:alpha # build + plugin validate + tests + dry-pack + install smoke test
244
+ npm run release:alpha # the full gate, then npm publish
245
+ # then: write .github/releases/vX.Y.Z.md and dispatch the Release workflow —
246
+ # it creates the tag + GitHub Release from that file
247
+ gh workflow run Release -f tag=vX.Y.Z -f target=<bump-commit>
209
248
  ```
210
249
 
211
- The package is scoped and configured for public npm publish via
212
- `publishConfig.access = "public"`. The repo already ships
213
- `.claude-plugin/marketplace.json`, so once `@cullumco/cadence` lands on npm,
214
- the canonical install at the top of this README starts working end-to-end.
215
-
216
- A GitHub Actions workflow exists at `.github/workflows/alpha.yml` but is
217
- currently disabled — re-enable with `gh workflow enable Alpha` when you want
218
- the gate to run on every push to `main`.
250
+ CI runs the same gate on every push/PR to `main`
251
+ (`.github/workflows/verify.yml`); `.github/workflows/release.yml` turns each
252
+ release into a tag + GitHub Release whose body is the committed notes file.
219
253
 
220
254
  ## What's next
221
255
 
222
256
  See [`BACKLOG.md`](BACKLOG.md). Highlights:
223
257
 
224
- - **Git nudges** — the highest-value next step: built but dormant, they move
225
- the dials from *what you said* to *what you're actually doing*.
226
- - **More signals** candidates on the bench:
227
- - **calendar density** — a meeting in 20 minutes should read as `pace=fast,
228
- posture=decisive`; a clear afternoon as room to explore.
229
- - **typing tempo** — prompt rhythm beyond length: rapid-fire short prompts vs.
230
- one long considered one.
231
- - **focused app** — what's frontmost next to the terminal (docs? a profiler?
232
- Slack?).
258
+ - **Git nudges** — *shipped:* they move the dials from *what you said* to *what
259
+ you're actually doing* (`3+ commits/hr fast pace`, `mid-conflict
260
+ verify-first`), applied below self-report so your explicit word still wins.
261
+ - **Prompt intent** — *shipped:* ship/think/debug read straight from the prompt
262
+ you just typed, so the "same prompt, different room" behavior fires without a
263
+ separate `cadence report` step.
264
+ - **Opt-in signals** — anything privacy-adjacent stays off until you turn it on
265
+ (`cadence enable <signal>`):
266
+ - **typing tempo** — *shipped (opt-in):* prompt rhythm beyond length —
267
+ rapid-fire short prompts read fast, one long considered prompt reads
268
+ deliberate.
269
+ - **focused app** — *shipped (opt-in, macOS):* the frontmost non-terminal app
270
+ (a browser, Slack, a PDF) renders as flavor. Read at prompt-submit, so it
271
+ only speaks when something other than your terminal/IDE is genuinely in
272
+ front. Flavor for now; a dial nudge stays a candidate.
273
+ - **esoteric flavor** — *shipped (opt-in):* `moon` phase (computed offline)
274
+ and a daily `horoscope` for your sign. Render-only — they color the room,
275
+ they never steer the work.
233
276
  - **deeper Focus** — manual + scheduled Focus detection ship now; geofenced/
234
277
  iPhone-synced Focus leaves no local trace and stays undetectable.
235
- - **After-the-fact injection** — the first cut ships: a `PostToolUse` hook
236
- watches git-ish commands and speaks exactly once when the repo enters or
237
- leaves a merge/rebase conflict ("this is debug now" / "conflict resolved").
238
- Next material events: failing-test transitions, reset/force-push thrash.
239
- - **Opt-in flavor providers** horoscope, moon phase, for those who want them.
278
+ - **Calendar density** — intentionally *not* built: Cadence targets solo
279
+ builders deep in a project, not people racing between meetings.
280
+ - **After-the-fact injection** shipped: a `PostToolUse` hook watches git-ish
281
+ commands and speaks once per transition — entering/leaving a merge/rebase
282
+ conflict, and destructive-op thrash (reset --hard streaks, force-pushes).
283
+ Next material event: failing-test transitions.
240
284
 
241
285
  ## Caveats
242
286
 
243
- - **macOS-only.** The alpha targets the Mac: music, battery, dark mode,
244
- displays, wifi, and Focus are all macOS probes. Other platforms get
245
- self-report + dials + time/git; everything else degrades silently.
246
- - **Spotify's Web API is not used** and not needed Spotify deprecated audio
247
- features for new apps (2024) and gated dev-mode behind Premium (2026). Cadence
248
- reads what's playing at the OS level instead.
287
+ - **Built for the Mac.** The richest ambient probes (music-via-OS, battery,
288
+ dark mode, displays, wifi (opt-in), Focus, focused app) are macOS. Other platforms
289
+ keep the dial-movers — intent, self-report, git, time/day, typing tempo,
290
+ linked Spotify and the rest degrades silently.
291
+ - **Spotify's audio-features API is not used** Spotify deprecated it for new
292
+ apps (2024) and gated dev-mode behind Premium (2026), so vibe comes from
293
+ MusicBrainz, not Spotify. On macOS, Cadence reads what's playing at the OS
294
+ level (no Spotify account at all). The only Spotify API call is the still-live
295
+ `currently-playing` endpoint, and only if you opt in via `cadence spotify` to
296
+ get music off the Mac — identity only, never audio-features.
249
297
 
250
298
  ## License
251
299
 
package/dist/cadence.js CHANGED
@@ -18,9 +18,10 @@ const LEVELS = ["low", "medium", "high"];
18
18
  *
19
19
  * Signals you can read (any subset present):
20
20
  * report { text } ← you said it; trust it most
21
+ * intent { kind } ← read from the prompt you just typed
21
22
  * music { vibe, energy } ← energy 0–1 drives pace; vibe colors tone
22
- * git { commitsLastHour, ... } ← work rhythm (when the provider lands)
23
- * activity { minSinceLastPrompt, promptLength }
23
+ * git { commitsLastHour, ... } ← work rhythm
24
+ * activity { minSinceLastPrompt, promptLength, tempo }
24
25
  *
25
26
  * A working baseline is below so it runs end-to-end. The mapping is yours.
26
27
  * ───────────────────────────────────────────────────────────────────────── */
@@ -35,8 +36,9 @@ export function deriveCadence(state) {
35
36
  const music = state.signals.find((s) => s.source === "music");
36
37
  const report = state.signals.find((s) => s.source === "self_report");
37
38
  const git = state.signals.find((s) => s.source === "git");
39
+ const intent = state.signals.find((s) => s.source === "intent");
38
40
  const activity = state.signals.find((s) => s.source === "activity");
39
- const ambient = state.signals.find((s) => s.source === "ambient");
41
+ const environment = state.signals.find((s) => s.source === "environment");
40
42
  // Start neutral; each signal nudges individual dials.
41
43
  const c = {
42
44
  pace: "medium",
@@ -44,31 +46,40 @@ export function deriveCadence(state) {
44
46
  posture: "medium",
45
47
  proactivity: "medium",
46
48
  };
47
- // ── ambient → soft nudges FIRST (weakest), so stronger signals below win ──
49
+ // ── environment → soft nudges FIRST (weakest), so stronger signals below win ──
48
50
  // Atmosphere, not orders: it colors the default, then music/self-report/git
49
51
  // can override. "It's late" shouldn't beat "I'm shipping."
50
- if (ambient) {
51
- if (ambient.hour >= 22 || ambient.hour < 6)
52
+ if (environment) {
53
+ if (environment.hour >= 22 || environment.hour < 6)
52
54
  c.pace = "low"; // late → gentler
53
- if (ambient.partOfDay === "early morning")
55
+ if (environment.partOfDay === "early morning")
54
56
  c.pace = "low"; // easing in
55
- if (ambient.isWeekend)
57
+ if (environment.isWeekend)
56
58
  c.tone = "low"; // looser on weekends
57
- if (ambient.weather && /rain|snow|fog|storm|cloud/.test(ambient.weather)) {
59
+ if (environment.weather && /rain|snow|fog|storm|cloud/.test(environment.weather)) {
58
60
  c.tone = "low"; // gloomy out → warmer in
59
61
  }
60
- if (ambient.onBattery)
62
+ if (environment.onBattery)
61
63
  c.pace = "high"; // mobile/untethered → quick hits
62
64
  }
63
- // ── music energy → pace (only pace; leave tone/posture to other signals) ──
65
+ // ── music → pace + posture + tone (move WITH the music) ───────────────────
66
+ // Deliberately moves three dials, not one: a track has a tempo (pace), an
67
+ // intensity (decisive vs. spacious posture), and a texture (warm tone). It
68
+ // leaves PROACTIVITY alone — whether to act without checking in is the user's
69
+ // call (self-report/intent/git), never the soundtrack's. See CLAUDE.md.
64
70
  if (music?.energy != null) {
65
71
  if (music.energy >= 0.7)
66
- c.pace = "high";
72
+ c.pace = "high"; // driving → fast
67
73
  else if (music.energy <= 0.4)
68
- c.pace = "low";
74
+ c.pace = "low"; // mellow → deliberate
75
+ if (music.energy >= 0.75)
76
+ c.posture = "high"; // high intensity → decisive momentum
77
+ else if (music.energy <= 0.35)
78
+ c.posture = "low"; // ambient → spacious, exploratory
69
79
  }
70
- // mellow/organic vibe words warm the tone
71
- if (music?.vibe && /\b(calm|chilled|ethereal|romantic|warm)\b/.test(music.vibe)) {
80
+ // organic/acoustic texture, or mellow vibe words, warm the tone
81
+ if ((music?.acoustic != null && music.acoustic >= 0.5) ||
82
+ (music?.vibe && /\b(calm|chilled|ethereal|romantic|warm|sexy)\b/.test(music.vibe))) {
72
83
  c.tone = "low";
73
84
  }
74
85
  // ── git → pace / proactivity (what you're DOING, not what you said) ───────
@@ -81,6 +92,29 @@ export function deriveCadence(state) {
81
92
  if (git.conflicted)
82
93
  c.proactivity = "low"; // verify, don't barrel
83
94
  }
95
+ // ── prompt intent → posture / proactivity / tone (what you JUST typed) ────
96
+ // Read from the live prompt, so the "same prompt, different room" behavior
97
+ // fires without a separate CLI step. Stronger than git (what you're doing),
98
+ // weaker than self-report below (a deliberate, out-of-band declaration), so
99
+ // an explicit `cadence report "thinking"` still beats a stray "ship it".
100
+ if (intent?.kind) {
101
+ if (intent.kind === "ship") {
102
+ c.posture = "high";
103
+ c.proactivity = "high";
104
+ c.pace = "high";
105
+ }
106
+ else if (intent.kind === "think") {
107
+ c.posture = "low";
108
+ c.pace = "low";
109
+ }
110
+ else if (intent.kind === "debug") {
111
+ c.posture = "low";
112
+ c.proactivity = "low";
113
+ }
114
+ else if (intent.kind === "focus") {
115
+ c.tone = "high";
116
+ }
117
+ }
84
118
  // ── self-report → posture / proactivity / tone (you know your state) ──────
85
119
  if (report) {
86
120
  const t = report.text.toLowerCase();
@@ -103,8 +137,15 @@ export function deriveCadence(state) {
103
137
  c.tone = "high";
104
138
  }
105
139
  // Still-dormant candidate nudges (see BACKLOG):
106
- // ambient focus on → proactivity high (heads-down = fewer check-ins)
107
- // ── activity → pace (returning from a break = slow back down) ─────────────
140
+ // environment focus on → proactivity high (heads-down = fewer check-ins)
141
+ // ── activity → pace (motor tempo + return-from-break) ─────────────────────
142
+ // typing tempo (opt-in): rapid-fire short prompts read as fast; one long
143
+ // considered prompt reads as deliberate. Only set when the user opted in.
144
+ if (activity?.tempo === "rapid")
145
+ c.pace = "high";
146
+ else if (activity?.tempo === "considered")
147
+ c.pace = "low";
148
+ // a long gap since the last prompt = returning from a break = slow back down.
108
149
  if (activity?.minSinceLastPrompt != null && activity.minSinceLastPrompt > 30) {
109
150
  c.pace = "low";
110
151
  }