Unattended discovery: the full poll->score->capture->brief loop can now fire with no operator present (Sunday-morning discovery), gated behind a new Step 0.5 contract. Fails fast instead of asking when the profile is missing, skips triage unconditionally (candidates stay pending), and refuses --demand --headless rather than silently degrading. Trigger mechanism is Claude Code's own Desktop Scheduled Tasks, not a hand-rolled cron wrapper -- verified against Claude Code's headless docs that a cron + `claude -p` wrapper loses observability/session- resumption/error-recovery versus the first-party mechanism. The fallback `claude -p` recipe is documented (README) for portability, not built as repo code. No new source/test file; no schema change; counts unchanged (30/20/29). This opens the research-engine's own 2026-06-24 re-evaluation gate against v1.0.0 product maturity for slice (e) -- surfaced to the operator explicitly rather than built around or silently deferred. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y2qHoS4FrkabuD1bCg8Nr2
12 KiB
| name | description | allowed-tools | ||||
|---|---|---|---|---|---|---|
| linkedin:trends | Run a trend discovery pass over the user's own content pillars and source list: delegate the scan to the trend-spotter agent, make sure kept candidates are persisted to the trend store (dedup) and the dated morning brief is written, then return a triage-ranked candidate list the user resolves per id (select/skip, batched). Default scoring mode is long-form (chronicle/newsletter material); `--mode kortform` overrides for feed posts. Use when the user wants a discovery pass, a trend scan, or a morning-brief refresh. Triggers on: "linkedin trends", "trend discovery", "discovery pass", "run a trend scan", "scan my sources", "morning brief", "refresh the brief", "trend sweep". |
|
LinkedIn Trend Discovery
You are a thin discovery orchestrator. The methodology — source tiers, research routing
(MCP-first), relevance scoring, angle selection — lives in the trend-spotter agent and in
the scoring SSOT ${CLAUDE_PLUGIN_ROOT}/references/trend-scoring-modes.md. Do not restate
any of it here; your job is to invoke the pass correctly, verify its side effects actually
happened, and hand the user a triage-ready list.
Data dir shorthand used below: ${DATA} = ${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}.
Trends CLI shorthand: CLI = cd "${CLAUDE_PLUGIN_ROOT}/scripts/trends" && node --import tsx src/cli.ts.
Step 0: Parse flags
All flags are optional, given after the command name:
| Flag | Meaning | Default |
|---|---|---|
--mode kortform|long-form |
Scoring mode (see the SSOT for what each rewards) | long-form — no arguments means a long-form discovery pass |
--fresh-days N |
Freshness window for the morning brief | CLI default (7) |
--brief-only |
Skip the discovery poll entirely; render the brief from the existing store | off |
--demand |
Run the demand-sweep (demand-spotter, «innenfra og ut») instead of supply-side discovery: poll where readers ASK, fill the reader fields, render the §4 arc map |
off |
--dry-run |
Poll + score, but persist nothing: no capture, no brief, no status writes, no last-run marker | off |
--headless |
Unattended run (RE-R3f, slice e): never block on operator input. See Step 0.5 below. | off |
Note the mode inversion deliberately: the agent's own default is kortform, this command's default is long-form. That is why Step 2 must always pass the mode explicitly.
--demand reroutes the pass (Step 2D below) to the demand side — a different agent
(demand-spotter), a different unit (the arc/åre, not the news-item), a different output (the §4
arc map, not the pillar brief). The two are complementary: run discovery to find what happened,
then --demand on a chosen theme to see what the reader is stuck on.
Step 0.5: --headless contract (RE-R3f, slice e)
--headless is for an unattended invocation — a scheduled task (Claude Code's own Desktop
Scheduled Tasks, --permission-mode dontAsk) firing this command with no operator present, e.g. a
Sunday-morning discovery pass. In dontAsk mode AskUserQuestion is auto-denied regardless of
what this file says, so the contract below is not optional politeness — it is what keeps the run
from aborting mid-pass:
--demand --headlesstogether is refused. The demand-sweep's pain-point/vocabulary translation calls for operator judgment this command will not simulate. Print one line saying so and exit — do not silently fall back to supply-side discovery instead of what was asked.- Step 1 never asks. If
${DATA}/profile/user-profile.mdis missing (no pillars to score against), print a one-line error naming the missing file and stop — do not prompt. The source-list fallback (shipped defaults when${DATA}/trends/sources.mdis absent) is unaffected; that path already needs no operator input. - Step 5 (Triage) is skipped unconditionally. Every kept candidate stays
pendingin the store; nothing is auto-selected or auto-skipped on the operator's behalf. The next brief (or the operator's own/linkedin:trendssession) surfaces them for triage when a human is present. - Steps 2–4 and 6 run exactly as in an attended pass — poll, score, capture, render the brief,
write the
.last-runmarker.--headlesschanges who can be asked, never what gets persisted. - Do not ground success in this command's own exit code or prose. Per Claude Code's headless
docs, a
-prun's process exit code reflects the CLI process, not the pass's semantic outcome — a tool denial or auth failure can still surface as text on stdout. The caller (the scheduled task's own log, or the operator reviewing Monday) verifies success against the same artifacts Step 3 already checks:CLI status --json's capture delta and today's${DATA}/trends/morning-brief/<date>.md. State plainly in the final output whether capture/brief actually happened — never report a pass as done because the model said so.
Scheduling setup (Desktop Scheduled Tasks, not a hand-rolled cron job — see
${CLAUDE_PLUGIN_ROOT}/scripts/trends/README.md for the exact recipe) is an operator action outside
this repo; this command only needs to behave safely once fired.
Step 1: Load context
- Pillars: Read
${DATA}/profile/user-profile.mdand extract the content pillars / expertise areas. If the file does not exist: on--headless, print a one-line error naming the missing file and stop (Step 0.5 — never ask unattended); otherwise ask the user for their pillars before proceeding (one question, comma-separated answer). - Source list: Resolve which list this pass will use —
${DATA}/trends/sources.mdif it exists, otherwise the shipped defaults${CLAUDE_PLUGIN_ROOT}/config/trends-sources.template.md. Tell the user which one applies. Do not read research-tooling or route research yourself — the agent owns Research Routing (its own "Research Routing" section reads the profile's### Research Toolingblock); duplicating it here would drift.
Step 2D: Run the demand-sweep (only on --demand)
If --demand and --headless are both set: refuse (Step 0.5) — print one line stating
the demand-sweep needs an operator present and exit. Do not run either pass.
If --demand: this replaces Steps 2–4 (the supply-side discovery pass). Delegate to the
demand-spotter agent — invoke it via Task with subagent_type: linkedin-studio:demand-spotter
(foreground). The prompt MUST state explicitly:
- The pillars and the resolved source-list path from Step 1 (its Tier 5 demand sources are what this pass polls — the inverse of the Tier 1–4 supply sources).
- The theme(s) to sweep (from
--topicsif given, else the user's pillars). - That the three passes are mandatory: demand-sweep (verbatim questions + signal strength + is-it-answered) → pain-point map (fills
painPoint) → vocabulary translation (fillsreaderQuestion). - That persistence is mandatory unless
--dry-run: the agent runscapturewith the reader fields (readerQuestion/painPoint/saturation/demand/verdict/actionability), then renders the §4 arc map withCLI arcs. - That an honest null is a finding — a thin/un-swept source is reported, never filled in.
Then present the arc map the agent returns: per vein, the ranked question inventory (reader's words),
the honest market verdict (supply-gap = write here · demand-gap = high-value/low-audience · saturated
= skip), and the BÆRENDE/STØTTE reading order. On --dry-run, present the same map but state that
nothing was persisted. Skip Steps 2–4. Hand off: a chosen vein feeds /linkedin:newsletter
Step 1 (its reader question, pain point, and angle prefill the edition).
Step 2: Run the discovery pass
If --brief-only: skip the agent entirely — go to Step 3 and render the brief from the
existing store.
Otherwise delegate to the trend-spotter agent — invoke it via Task with
subagent_type: linkedin-studio:trend-spotter (foreground). The prompt MUST state explicitly:
- The scoring mode from Step 0 (default long-form). Never omit it — the agent falls back to kortform when the caller is silent.
- The pillars and the resolved source-list path from Step 1.
- That this is a full digest run and the persistence steps are mandatory, not optional:
the agent must run its Step 4.5 (
capture— persist kept candidates to the trend store, batch, dedup, scores carried) and Step 4.6 (brief— write the dated morning brief, passing the pillars, and--fresh-daysif the user set it). - If
--dry-run: invert that — the agent must poll and score but skip capture and brief entirely (nothing persisted). - That the returned digest must include, per kept candidate: title, 2–3 sentence summary, source URL(s), composite score + band, recommended angle, and matching pillar/series.
Step 3: Verify the side effects (skip on --dry-run)
Trust but verify — the pass is only done when its artifacts exist:
- Store:
CLI status --json— confirm the store mutated (captured count reflects the run; on a no-new-trends day{added: 0, merged: N}is a fine outcome, not a failure). - Brief: confirm today's file exists:
${DATA}/trends/morning-brief/<today YYYY-MM-DD>.md. If the agent captured but failed to render the brief, render it directly — the brief is deterministic:CLI brief --pillars "<pillar1,pillar2,…>"(add--fresh-days Nif set). - If capture itself did not happen, say so plainly and report what the agent returned — never present an unpersisted digest as if it were in the store.
Step 4: Present the triage-ranked list
Present the candidates ranked highest composite first (the agent's digest already carries the ranking — do not re-rank). Per candidate, the contract is:
N. [Title] (id: <trend-id>)
Score: X.X — [Band] | Pillar: [pillar/series]
[2–3 sentence summary]
Source: [URL(s)]
Angle: [recommended angle]
Include each candidate's store id (shown in the brief and via CLI list --json) — the triage
step below resolves per id. On --dry-run, present the same list but say clearly that nothing
was persisted and there are no store ids to triage.
Step 5: Triage (skip on --dry-run or --headless)
On --headless: skip this step unconditionally (Step 0.5 — AskUserQuestion is
auto-denied in an unattended run regardless). Every kept candidate stays pending in the store;
report the count and move to Step 6. Do not attempt to select or skip on the operator's behalf.
Otherwise, resolve the top of the queue now instead of leaving it as homework. For the candidates in the top bands (Immediate + High; cap at 8), use AskUserQuestion — one question per candidate, up to 4 candidates per call, options:
- Velg — you'll write about this: mark
selected, moving it onto the brief's "I produksjon" board (valgt) so the queue stops re-surfacing it while it's in progress - Skip — not for me: mark
skipped(dropped from the queue) - Leave — keep it in the queue untouched
Then apply the decisions through the store CLI. Batch by verb — collect all the "Velg" ids and all the "Skip" ids and resolve each set in ONE call (ten candidates ≤ two calls):
CLI select --ids <id1,id2,id3> # everything chosen this pass
CLI skip --ids <id4,id5> # everything rejected this pass
Single-id form (--id <id>) still works for a one-off. "Leave" means no call. A partial batch
(some id unknown) still applies the matches, reports the misses, and exits 0. Finish with a
one-line summary: N valgt, N skipped, N left in queue. (act — already written — and the
auto-act when an edition reaches scheduling arrive with the N7 trend→newsletter bridge.)
Step 6: Write the last-run marker (skip on --dry-run)
On completed runs (including --brief-only), stamp the marker so other surfaces can tell when
discovery last ran:
mkdir -p "${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/trends" && \
date -u +"%Y-%m-%dT%H:%M:%SZ" > "${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/trends/.last-run"
Reference Files
${CLAUDE_PLUGIN_ROOT}/references/trend-scoring-modes.md— scoring SSOT (modes, weights, bands)${CLAUDE_PLUGIN_ROOT}/config/trends-sources.template.md— shipped source-list defaults (user override:${DATA}/trends/sources.md)${CLAUDE_PLUGIN_ROOT}/agents/trend-spotter.md— the discovery methodology this command invokes