Access personalized developer content feeds from daily.dev - the professional network for developers. Surface relevant articles, tutorials, and discussions…

Real-time developer content from daily.dev, inside Claude Code.
Install it from the daily.dev marketplace (Claude Code 2.1.275 or later does this in one step):
/plugin install daily.dev --marketplace dailydotdev/daily
The daily-dev-ask skill ships as its own plugin from the same marketplace:
/plugin install daily-dev-ask --marketplace dailydotdev/daily
What you get:
https://api.daily.dev/mcp and sign in instead (see the skill for setup)./daily.dev:trends skill — today's curated headlines and most-upvoted posts, no token needed.Nothing to set up. On Claude Code 2.1.275 or later the plugin ships a hooks module that shows the headline as soon as the plugin is installed, with no edits to your settings.json. Your own statusLine setting, if you have one, keeps working alongside it. Press Hide on the band to dismiss it for the current session.
Versions before 0.7.0 wired the headlines into statusLine in ~/.claude/settings.json. Leaving that in place next to the new hooks module shows the headline twice. Run:
/daily.dev:statusline
and Claude removes the old entry. If you prefer to do it by hand, delete the statusLine key whose command points at statusline/statusline.mjs.
Claude Code before 2.1.275 does not load the hooks module, so the band and status line entry do not appear. The old setup still works there: run /daily.dev:statusline and Claude wires the script into ~/.claude/settings.json, or point statusLine at the plugin's script yourself. The glob keeps it working across plugin updates:
{
"statusLine": {
"type": "command",
"command": "node \"$(ls -td \"$HOME/.claude/plugins/cache/daily-dev/daily-dev\"/*/ | head -1)statusline/statusline.mjs\"",
"refreshInterval": 10
}
}
This replaces any custom statusline you already have, since Claude Code user settings hold a single statusLine.
node on your PATH. Honors NO_COLOR.~/.cache/dailydev-claude/.statusline/statusline.mjs script, so caching, the 24h no-repeat history and click attribution behave the same either way.The headlines report anonymous impressions (which headlines were shown) to daily.dev analytics, using the same anonymous tracking id a logged-out daily.dev visitor gets. No code, file paths, or session content is ever collected — events carry only the shown post id and plugin version.
Opt out any time:
export DAILY_DEV_TELEMETRY=0hooks/register.tsx 78 lines1/**
2 * daily.dev mod: rotating headlines with no settings.json edits.
3 *
4 * Reuses statusline/statusline.mjs unchanged, so caching, the 24h no-repeat
5 * history, /c/ click attribution (cc_uid) and impression telemetry
6 * (DAILY_DEV_TELEMETRY=0 opts out) behave exactly as today. The script prints
7 * the current headline from its local cache and starts a detached background
8 * refresh when the cache is stale; this mod polls it and shows the result.
9 */
10import { atom, read, update } from 'claude-code';
11import type { Register } from 'claude-code';
12import type { Headline } from '../types';
13
14const POLL_MS = 10_000; // the old statusLine used refreshInterval: 10
15
16const headlineAtom = atom(
17 { plugin: 'daily.dev', key: 'headline' } as const,
18 undefined as Headline | undefined,
19);
20const bandHiddenAtom = atom(
21 { plugin: 'daily.dev', key: 'bandHidden' } as const,
22 false as boolean | undefined,
23);
24
25// Turn one server-rendered line into a title and its /c/ link.
26function parse(line: string): Headline | undefined {
27 const url = line.match(/\x1b\]8;;([^\x1b\x07]+)(?:\x1b\\|\x07)/)?.[1];
28 const title = line
29 .replace(/\x1b\]8;;[^\x1b\x07]*(?:\x1b\\|\x07)/g, '')
30 .replace(/\x1b\[[0-9;]*m/g, '')
31 .replace(/^daily\.dev /, '')
32 .trim();
33 if (!title || title.endsWith('fetching headlines…')) return undefined;
34 return { title, url };
35}
36
37export const register: Register = (on) => {
38 on('session.start', async ($, e, next) => {
39 const result = await next(e);
40
41 const tick = async () => {
42 try {
43 const out = await $.process.run(
44 ['node', `${$.plugin.root}/statusline/statusline.mjs`],
45 { stdin: '{}' },
46 );
47 const headline = parse(String(out.stdout ?? ''));
48 await update($, headlineAtom, () => headline);
49 $.ui.status(headline ? `daily.dev · ${headline.title}` : undefined);
50 } catch {
51 // No node on PATH or offline: keep the line empty, never break the session.
52 $.ui.status(undefined);
53 }
54 };
55
56 await tick();
57 $.clock.every(POLL_MS, tick);
58 return result;
59 });
60
61 // Band above the prompt: the same headline with an Open link and Hide.
62 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
63 if (e.props.hasSurvey) return next(e); // yield to a survey
64 const headline = await read($, headlineAtom);
65 const hidden = await read($, bandHiddenAtom);
66 if (!headline || hidden) return next(e);
67 const { Box, Text, Button, Link } = $.ui.resolve(e);
68 return (
69 <Box flexDirection="row" gap={1}>
70 <Text color="magenta">daily.dev</Text>
71 <Text dimColor>{headline.title}</Text>
72 {headline.url ? <Link href={headline.url}>Open</Link> : null}
73 <Button onPress={() => update($, bandHiddenAtom, () => true)}>Hide</Button>
74 </Box>
75 );
76 });
77};
78types/index.d.ts 12 lines1declare module 'claude-code' {
2 interface PluginState {
3 'daily.dev': {
4 /** Current headline, plain text (SGR codes and OSC 8 wrappers removed). */
5 headline?: { title: string; url?: string };
6 /** User dismissed the band for this session. */
7 bandHidden?: boolean;
8 };
9 }
10}
11export type Headline = { title: string; url?: string };
12