@@ -208,6 +208,72 @@ keep-list, assert no forbidden fields, and upload to
208208
209209---
210210
211+ ## 8. The contribution nudge (` notifications.py ` )
212+
213+ A self-growing dataset needs people to actually contribute, so prompt them — but
214+ put the nudge ** where the * user* sees it, not on stderr.** This is the key lesson:
215+ in an agent-run tool (Claude Code, an MCP server, any CLI an LLM drives), ** stderr
216+ is read by the agent, not the human.** A "please contribute" line printed to
217+ stderr is noise in the agent's tool output and the end user never sees it. So:
218+
219+ 1 . ** Embed the nudge in the deliverable artifact** the user opens (the generated
220+ HTML report, PDF, dashboard, etc.) — not in logs/stderr.
221+ 2 . ** Only the deliverable step emits it** (e.g. the report renderer), not the
222+ intermediate scripts the agent loops over — otherwise it spams the agent's
223+ context and fires many times per session.
224+ 3 . ** Config-toggle, default on** — ` ui.contribution_reminder: true ` . One flag the
225+ maintainer (or end user) can flip off.
226+ 4 . ** HTML-escape** the repo link; read it from the same config as everything else.
227+
228+ Generalized helper — drop in next to ` validate.py ` / ` automerge.py ` :
229+
230+ ``` python
231+ # notifications.py — the user-facing contribution nudge (rendered into the
232+ # deliverable, never printed to stderr).
233+ import html, json, os
234+
235+ def _cfg (cfg = None ):
236+ if cfg is not None :
237+ return cfg
238+ here = os.path.dirname(os.path.abspath(__file__ ))
239+ try :
240+ with open (os.path.join(here, " config.json" ), encoding = " utf-8" ) as fh:
241+ return json.load(fh)
242+ except (OSError , json.JSONDecodeError):
243+ return {}
244+
245+ def contribution_banner_html (cfg = None ):
246+ """ Return an HTML banner (or '' if disabled). Render this into your report."""
247+ cfg = _cfg(cfg)
248+ if not cfg.get(" ui" , {}).get(" contribution_reminder" , True ):
249+ return " "
250+ repo = html.escape(cfg.get(" dataset_repo" , " https://huggingface.co/" ))
251+ return (
252+ ' <div class="contrib" style="margin:12px 0;padding:10px 14px;'
253+ ' border-left:4px solid #2d6;border-radius:8px;background:#0e1a12;'
254+ ' color:#bdf5cf;font-size:14px">'
255+ ' ✨ Help this dataset grow — '
256+ f ' <a href=" { repo} " target="_blank" rel="noopener" '
257+ ' style="color:#5ee08a;font-weight:600">contribute your anonymized data</a>'
258+ ' so everyone gets better results.</div>'
259+ )
260+ ```
261+
262+ Use it in your report renderer (the deliverable), e.g.:
263+ ``` python
264+ import notifications
265+ html_out = TEMPLATE .replace(" __CONTRIB__" , notifications.contribution_banner_html())
266+ ```
267+
268+ For a pure-CLI tool with no visual artifact, the equivalent is to print the line
269+ to ** stdout** as part of the final human-facing summary (still gated by the flag,
270+ still only on the last step) — never bury it in stderr/logs.
271+
272+ * (Reference implementation: ` skills/fiverr-gig-optimizer/scripts/reminders.py ` ,
273+ rendered by ` build_catalog.py ` into the catalog the user opens.)*
274+
275+ ---
276+
211277* Reference implementation this was extracted from:
212278[ Ahad690/fiverr-gig-optimizer] ( https://github.com/Ahad690/fiverr-gig-optimizer )
213279(` skills/fiverr-gig-optimizer/scripts/{contribute,refresh_dataset,automerge_prs}.py ` ).*
0 commit comments