From in-optimize
Use when the user wants to translate a fresh @IntCyberDigest tweet (or any cybersecurity tweet) into a Danish LinkedIn post. Drives the discover → fetch → translate → /humanizer-da × 2 → confirm → post loop. Treats every cycle as one tweet at a time. Posting is gated behind explicit user confirmation. Self-arms a 2-hour cron on first invocation so the loop continues without a separate /loop call.
How this skill is triggered — by the user, by Claude, or both
Slash command
/in-optimize:cyber-newsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Goal: turn cybersecurity news from `@IntCyberDigest` (or another handle) into a humanized Danish LinkedIn post, severity-shaped, with the original images, posted slowly through Playwright.
Goal: turn cybersecurity news from @IntCyberDigest (or another handle) into a humanized Danish LinkedIn post, severity-shaped, with the original images, posted slowly through Playwright.
Before doing anything else, ensure a recurring cron is scheduled so the loop continues without the user calling /loop separately.
CronList./in-optimize:cyber-news, skip — already armed.CronCreate with:
cron="0 */2 * * *" (every 2 hours, on the hour, UTC by the local cron runner)prompt="/in-optimize:cyber-news"recurring=truedurable=true (persists to .claude/scheduled_tasks.json so it survives Claude restarts on the same machine)The cron only fires while a Claude REPL is running on this project. If the user closes Claude entirely, the loop stops. That's intentional — destructive posting needs a human at the confirm gate.
Run npm run cyber-news -- status --json and read postedToday and dailyCap. If postedToday >= dailyCap (default 3), stop the cycle. Print one line: daily cap reached (N/cap) — skipping this tick. Do nothing else; the cron will fire again later, and tomorrow the count resets.
This avoids burning a discover/fetch quota on a cycle that can't post anyway. The CLI's post subcommand also enforces the cap at runtime (so a manual post or --auto-post cron tick can't bypass it without --force), but checking here saves the work.
Discover — npm run cyber-news -- discover --json lists IDs newer than the high-water mark in state/cybernews/posted.json.
Pick one — default = oldest of the new (FIFO). Skip retweets if selfAuthored === false.
Fetch — npm run cyber-news -- fetch --id=<ID> --media-out=state/cybernews/media/<ID> writes media to disk and prints text + classification + hashtags.
Read & decide — strict curator rubric. A tweet is worth a slot if all three must-haves hold:
state/cybernews/log.jsonl to check.Bonus signals (push borderline candidates over the line): AI/ML-related, supply-chain, scales to many companies, has a memorable story hook (the goblin debugging post is a good example), or is a CVSS 9+ that affects tooling your audience uses.
Auto-skip any of: vendor PR, routine vuln-of-the-week with no Danish-business angle, duplicate angle to the last 3 posts, marketing/conference/CFP announcements.
If the tweet doesn't pass: don't write a draft; mark skipped with the reason in the log (reason: "not-novel", "audience-miss", "no-teachable-angle", "duplicate-angle", "vendor-pr") via state.markPosted(id, { outcome: "skipped", reason: ... }) and try the next candidate, OR end the cycle if no candidate qualifies.
Translate to simple Danish — write a draft sized by severity (see Severity shapes below). Use docs/cybernews-glossary.md for term choices. Save as state/cybernews/drafts/<ID>.md.
Run /humanizer-da twice — invoke the slash command on the draft file, twice in sequence. Each pass is independent; do not skip the second one.
Append hashtags — at the bottom of the post body, append the hashtags from step 3 (already includes #cybersikkerhed).
Append source link — bottom line: "Kilde: ".
Confirm — print the final draft + media list to the user. Wait for explicit "ja" / "go" / "post". Until v0.2 there is no auto-post.
Post via Playwright — drives logged-in Chromium, attaches images, paces with humanCursor.
Record — call the state writer with outcome: "posted" and the LinkedIn URL.
The classifier in src/cybernews/severity.ts returns one of info | notable | critical | zero-day. Match the LinkedIn shape to it:
Across all four: no em dashes, no " - " as a pause marker (humanizer-da hard rule).
Wired. Lives in src/cybernews/poster.ts + src/cybernews/selectors-li.ts + src/cybernews/post-flow.ts. Drives the same .profile/ user-data dir as the comment cleaner (one manual login covers both surfaces).
Invoke via:
npm run cyber-news -- post --id=<TWEETID> --draft=state/cybernews/drafts/<TWEETID>.md \
--media-dir=state/cybernews/media/<TWEETID> --severity=<info|notable|critical|zero-day>
Default behavior:
src/humanCursor.ts (bezier path, jitter).state/cybernews/media/<ID>/ (sorted by filename).go (or y / yes / ja) to submit. Anything else, including pressing ENTER alone or closing the terminal, aborts and records skipped.posted.json.--auto-post skips the confirmation gate (use only after a clean dry-run on the same draft).--dry-run prints the body + media list and records dryrun outcome without opening the browser.--daily-cap=N overrides the 3-per-day cap (use --daily-cap=0 to disable entirely).--force bypasses the daily cap for one run (one-off emergencies — say, a genuine zero-day on a day you've already used your slots).Pacing rules:
The 2-hour cron and the daily cap together mean at most 12 ticks per day, of which up to 3 land posts. The other 9 ticks become low-cost no-ops — they hit Step 0.5, see the cap is full or no novel candidates exist, and exit.
state/cybernews/:
posted.json — { id: { outcome, postedAt, severity, liUrl?, reason? } }. Atomic flush on each step.log.jsonl — append-only audit.media/<ID>/<ID>-N.<ext> — downloaded images / videos.drafts/<ID>.md — intermediate draft (pre & post humanizer).Outcomes: posted and skipped are terminal; failed is retryable; dryrun records preview-only runs.
src/cybernews/
state.ts ← posted.json + log.jsonl, BigInt high-water mark
fetch.ts ← syndication API, parser, media downloader
discover.ts ← timeline-profile parser (rate-limited)
severity.ts ← zero-day | critical | notable | info classifier
hashtags.ts ← #cybersikkerhed + signal/keyword tags, capped at 5
poster.ts ← (not yet) Playwright LinkedIn composer
selectors-li.ts ← (not yet) LinkedIn post composer selectors
src/cli-cybernews.ts ← thin CLI: discover | fetch | status
docs/cybernews-glossary.md ← Danish vocab + tone notes
All files ≤ 200 LOC.
state/cybernews/. It contains drafts and downloaded media..profile/ dir as the comment cleaner.skipped with the reason, move to next..claude/scheduled_tasks.json or run CronList + CronDelete then CronCreate with the new expression.CronList to find the job ID, then CronDelete it. The skill itself doesn't auto-stop.state/cybernews/diagnostics/.npx claudepluginhub cocodedk/in-optimizer --plugin in-optimizeGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.