Better with Kent
Extend the agent you already use · search · execute · packages
x.com/kentcdodds/status/2067635082173489159 · 70+ replies
Why don't you have a personal AI assistant?
I asked X · reply
"I use Claude Chat and Claude Cowork with integrations. Cowork has scheduled tasks now. I already pay a lot for the subscription, and I don't have to maintain it myself. It works for me."
@kevinswiber · x.com/kevinswiber/status/2067664036872040811
search · execute · saved
packages to APIs, home, jobs
Other harnesses exist — today is how I wired Kody.
Not dozens of tool schemas loaded into every context window.
flowchart LR
archHost["MCP host\nCursor / Codex · two tools"]
archSearch["search\ncatalog"]
archExecute["execute\nplayground"]
subgraph archRuntime["Kody runtime · persists"]
direction TB
archPackages["packages\nsaved exports"]
archCaps["capabilities\nmemory · retrievers"]
end
archConnectors["connectors\nhome:default · remote"]
archHome["home\nBond · Lutron · Sonos"]
archWorld["OAuth · APIs · Workers"]
archHost --> archSearch
archHost --> archExecute
archSearch -->|"surfaces"| archCaps
archSearch -->|"finds"| archPackages
archExecute -->|"imports"| archPackages
archExecute -->|"codemode"| archCaps
archPackages <-->|"fetch & act"| archWorld
archCaps --> archConnectors
archConnectors --> archHome
archCaps --> archWorld
Connectors bridge your LAN — shades, lights, speakers — without forty home MCP tools on the host.
exports, optional jobs, and retrieverssearch — not just exports to importkody:@scope/pkg/exportWhen exploratory execute code works, I publish a package.
You don't have to automate — sometimes a saved export is the whole win.
The pipeline
packages.invoke from another package,
a job, or an external webhook into Kody
Shades story — three prompts: fix now · package it · morning plan schedules today's moves.
flowchart LR demoIssue["GitHub issue filed"] --> demoExecute["execute"] demoExecute --> demoLutron["Lutron accents"] demoExecute --> demoBond["Bond shade"] demoExecute --> demoSonos["Office Sonos"] demoExecute --> demoRestore["restore colors + shade + stop"]
Cold open on camera — automated GitHub path, not a prompt you'd type.
Kitchen West — close the shade when afternoon sun glares off the counter.
Phase 1 · fix it now
Kent The sun is glaring off of the kitchen counter into my face. Please close the shades to stop the glare.
MCP · search
{
"query": "bond shade kitchen west close position home connector",
"limit": 8,
"memoryContext": {
"task": "Close shades to stop sun glaring off the kitchen counter",
"entities": ["kitchen counter", "glare", "shades", "Kitchen West"]
}
}
Sample: slides/samples/search-query-glare-phase1.json
bond_shade_set_positiondeviceId · deviceName · positionMCP · search — one capability
{
"entity": "home_default_bond_shade_set_position:capability"
}
Sample: slides/samples/search-entity-bond-shade.json
MCP · execute
import { codemode } from 'kody:runtime'
const KITCHEN_WEST = { deviceId: '4a0522ccbb052cb1', deviceName: 'Kitchen West' }
export default async function main() {
await codemode.home_default_bond_shade_set_position({
...KITCHEN_WEST,
position: 0,
})
return { ok: true, position: 0, device: KITCHEN_WEST.deviceName }
}
Sample: slides/samples/execute-close-shades.ts
{
"ok": true,
"position": 0,
"device": "Kitchen West"
}
Shades closed — glare stopped.
Sample: slides/samples/execute-result-close-shades.json
Phase 2 · make it reusable
Kent Why don't we make a package for controlling the shades to make that easier?
close-shades.ts
import { codemode } from 'kody:runtime'
const KITCHEN_WEST = { deviceId: '4a0522ccbb052cb1', deviceName: 'Kitchen West' }
export default async function closeShades() {
await codemode.home_default_bond_shade_set_position({
...KITCHEN_WEST,
position: 0,
})
return { ok: true, position: 0, device: KITCHEN_WEST.deviceName }
}
Import path: kody:@kentcdodds/kitchen-shade-control/close-shades · sample:
close-shades.ts
package.json
{
"name": "@kentcdodds/kitchen-shade-control",
"exports": {
"./close-shades": "./close-shades.ts"
},
"kody": {
"id": "kitchen-shade-control",
"description": "Close Kitchen West Bond shade to stop afternoon counter glare.",
"tags": ["home", "shades", "bond", "kitchen"]
}
}
Sample: package-kitchen-shade-control-package.json
Phase 2 · package source
README.md · what Kody surfaces in search
# @kentcdodds/kitchen-shade-control
Close the Kitchen West Bond shade when afternoon sun glares off the counter.
## Intent
This package exists so Kent can close the Kitchen West shade with one import
instead of repeating device id and codemode calls every time the counter glare
prompt comes up.
Sample: package-kitchen-shade-control-readme.md
MCP · package_save + repo workflow
@kentcdodds/kitchen-shade-controlpackage.json · README · exports
Samples: package-kitchen-shade-control-package.json ·
package-kitchen-shade-control-readme.md
MCP · execute — import the published export
import closeShades from 'kody:@kentcdodds/kitchen-shade-control/close-shades'
export default async function main() {
return await closeShades()
}
Sample: slides/samples/invoke-close-shades.ts
Phase 3 · automate it
Kent Why don't we do this: automatically close and open the shades to avoid the sun ever reflecting into my kitchen?
scheduleKitchenGlareDay · sun geometry · one-shot workflows
export default async function scheduleKitchenGlareDay() {
const day = new Date().toISOString().slice(0, 10)
const closeAt = await findGlareCloseTime()
const openAt = await findGlareOpenTime()
await workflows.create({
exportName: './run-glare-event',
runAt: closeAt,
idempotencyKey: `kitchen-glare-close:${day}`,
params: { action: 'close' },
})
// mirror workflows.create for openAt · action: 'open'
return { closeAt, openAt }
}
Samples: schedule-kitchen-glare-day.ts · run-glare-event.ts
package.json · kody.jobs
"jobs": {
"daily-plan": {
"entry": "./jobs/daily-plan.ts",
"schedule": {
"type": "cron",
"expression": "0 10 * * *"
},
"timezone": "UTC"
}
}
Morning daily-plan runs this · Kody syncs on publish ·
run-glare-event at each scheduled time.
Same pipeline · many times
Same pipeline · many times
kody:@scope/pkgAlso: Miami weather → Spotify playlist — same composition pattern.
Kent's instance
home:default 126 · built-in 72
380 exports · 13 hosted apps
Snapshot 2026-06-17 · slides/samples/kody-instance-stats.json
Kent's instance
25 enabled · 17 disabled
OAuth · secret refs · user values
Same workflow as separate MCP tools → context noise + more failure points.
Piggyback what you already pay for
Kevin's Cowork answer on slide 3 — fair if that's enough for you.
OAuth · saved accounts · search discovery
search surfaces integrations before you build — GitHub · Google · Spotify…heykody.dev — OAuth in browser · tokens server-sidekody:@kentcdodds/google-products · multi-account YouTubefetch · host injects on
approved domains only
Secrets & permissions
MCP · execute
export default async function main() {
const response = await fetch(
'https://api.github.com/user/repos?per_page=5',
{
headers: {
Authorization: 'Bearer {{secret:githubPat|scope=user}}',
Accept: 'application/vnd.github+json',
},
},
)
const repos = await response.json()
return { count: repos.length }
}
Sample: execute-secret-reference.ts
Secrets & permissions
MCP · execute → error · first fetch to a new domain
{
"error": "Secrets require host approval: [...]",
"errorDetails": {
"kind": "host_approval_required_batch",
"missingApprovals": [
{
"secretName": "githubPat",
"host": "api.github.com"
}
]
}
}
Sample: execute-host-approval-error.json
Secrets & permissions
createAuthenticatedFetch('spotify') — tokens stay server-side · no secret
placeholder in code
heykody.dev · agent names the integration · you approved hosts
when you connectedSecrets & permissions
MCP · execute
import { createAuthenticatedFetch } from 'kody:runtime'
export default async function main() {
const fetchSpotify = await createAuthenticatedFetch('spotify')
const response = await fetchSpotify(
'https://api.spotify.com/v1/me/player',
)
const player = await response.json()
return { isPlaying: player?.is_playing ?? false }
}
Sample: execute-oauth-fetch.ts
Better with Kent
Subscribe · comment · get better with me