Imported from Henrique1601/SkiilsToIAs (
marketing/linkedin-marketing/AGENTS.md). Install upstream withnpx skills add Henrique1601/SkiilsToIAs --skill linkedin-marketing. Copyright stays with the author.
Project conventions - linkedin-skills
This file is for any Codex agent working on this repository. Read it before making changes. Conventions here are mandatory unless the user asks otherwise.
Versioning
- Single source of truth:
.codex-plugin/plugin.json,.agents/plugins/marketplace.json,.claude-plugin/plugin.json, and.claude-plugin/marketplace.json. Plugin manifests must always match on package name and version; marketplace entries must point to the same package; author, license, homepage, and the public skill-bundle description must stay aligned. - Keep
AGENTS.mdandCLAUDE.mdaligned when changing shared project rules. Codex-specific workflow details belong here; Claude-specific workflow details belong inCLAUDE.md. - Codex marketplace install uses
.codex-marketplace/linkedin-skills/. Do not edit that generated package by hand. Update the root files first, then runpython3 scripts/sync_codex_marketplace.py. - Default: bump the PATCH segment (3rd level,
0.0.X). This is the automatic behavior for every shippable commit, regardless of how large the diff feels. Skill renames, lib API breaks, new features: still PATCH by default. - Only bump MINOR or MAJOR when the user explicitly asks for a higher rank ("this is minor", "make it 2.0", "bump major"). Do not promote on your own initiative even if semver textbook says so.
- After bumping, two steps are required:
- Tag the commit:
git tag -a v<X.Y.Z> -m "..."+git push origin v<X.Y.Z> - Publish a GitHub Release for the tag:
gh release create v<X.Y.Z> --title "v<X.Y.Z>" --notes "<changelog>" --latestA tag alone does NOT update the README release badge or the Releases page. The shields.io badge reads from the Releases API, not from raw tags. Skipping step 2 leaves the badge stale.
- Tag the commit:
Commits
- Primary author must be Sergey: every
git commitneeds--author="Sergey Bulaev <s@bulaev.org>". Verify withgit log -1 --format='%an <%ae>'before pushing. - Co-author trailers are fine when appropriate.
- Verify locally before push: build never breaks, no broken refs in
SKILL.md, library smoke import passes.
Skill bundle invariants
- Exactly 12 skills. Adding requires merging or splitting elsewhere to stay at 12. The number is announced in plugin manifests and the README.
- Frontmatter
description:target <= 400 chars (some bundle-heavy skills land slightly higher when their scope is genuinely broad - keep under 510). Always include a "Not for X (use Y)" disambiguation sentinel when the skill overlaps with a sibling. - No em dashes anywhere in
description:fields. Em dashes in body prose are allowed for table separators and list dividers only. - Skill names are public surface. Renaming a skill is a major
version bump and requires updating: plugin manifests, marketplace entries,
root
SKILL.mdbundle list, README skill table, everylinkedin-<name>cross-reference in sibling SKILL.md files.
Voice rules + reference layout
- Canonical voice rules live at root
references/voice-rules.md. Skill-local "Hard rules" sections must only contain skill-specific overrides (char ranges, threading rules, format constraints) and start with:Global voice rules: see root SKILL.md Voice rules. - Other root-level references shared across skills:
references/hook-formulas.md(20 canonical formulas),references/algorithm-heuristics.md, andreferences/untrusted-content.md(the data-not-instructions rule for every skill that reads the Apify layer; keep the per-skill "Untrusted content" sections pointing at it). - Skill-local references live in
skills/<skill>/references/. Cite from the skill with barereferences/X.md. Cite root from skills with../../references/X.md. linkedin-humanizerhassub-skills/for folded-in workflows (post-audit, emoji-detector, detector-tester, rules-explainer) andscripts/for runnable tools. Don't duplicate this pattern in other skills without a clear reason.
.claude/skills mirror
.claude/skills/<name>is a relative symlink to../../skills/<name>, one per skill. Claude Code discovers project skills at.claude/skills/, while the bundle keeps them atskills/for the agentskills.io and plugin layouts, so a plaingit cloneused as a working directory activates nothing without this.skills/stays the single source of truth. The mirror holds no content, so there is nothing to sync and nothing that can drift. Relative references still resolve because symlinks resolve physically:../../references/from a mirrored skill lands on the repo root, not inside.claude/.- Adding or renaming a skill means adding or renaming its symlink. A missing one is silent: the skill simply does not appear for anyone who cloned the repo.
- The mirror is Claude-specific and is deliberately not copied into the Codex package.
Layer separation
- Read layer (Apify):
lib/apify_client.py. Four methods -fetch_post,fetch_post_comments,fetch_user_recent_comments,fetch_post_engagers. All cached (256-entry LRU, 6h TTL, opt-out viaforce_refresh=True). Skills should call these or thelib.fetch_post(url)wrapper that handles the APIFY_TOKEN-or-paste fallback. - Write layer (Publora):
lib/publora_client.py. Skills should calllib.publish(kind, draft_text, target_url, ...)(kinds: comment / reply / post / reshare) or thelib.repost(post_url, commentary=None)convenience wrapper, rather than inline the publora / manual / diy dispatch. Real endpoint paths:POST /create-post,POST /linkedin-comments,DELETE /linkedin-comments,POST /linkedin-reactions,POST /linkedin-reshare. Reshare needs the original post'sshareUrn(urn:li:share:*/urn:li:ugcPost:*), which Apifyfetch_postreturns directly; never hand-convert anactivityid (the share id can differ). Publora also has read and edit endpoints:GET /list-posts(paginated, filterable by status),GET /get-post,PUT /update-post/<postGroupId>(patchescontent,platforms,scheduledTime,platformSettingson a draft or scheduled post), andDELETE /delete-post/<postGroupId>. Alsopost-logs,test-connection,platform-limitsandwebhooks. Prefer editing a scheduled post over delete-and-recreate. - Don't suggest competitor schedulers (Buffer, Hootsuite, Later) by name in committed files - the bundle is positioned as the canonical Apify-read + Publora-write integration.
Codex marketplace package
- Codex requires marketplace entries to point at a nested plugin directory. The root remains the Claude-facing source layout.
.agents/plugins/marketplace.jsonpoints to.codex-marketplace/linkedin-skills.scripts/sync_codex_marketplace.pycopies the root Codex manifest,SKILL.md,skills/,references/,lib/,scripts/,requirements.txt,.env.example, andLICENSEinto the hidden package.- After editing any copied file, run the sync script before testing or committing.
testing/ is gitignored
testing/is the local scratch directory: API keys, sample API responses, validation reports, integration scripts.- Never write secrets above
testing/(the rest of the repo is public). - The
.gitignorerule fortesting/is load-bearing; do not change.
Validation before push
Run from repo root:
python3 -c "from lib import publish, fetch_post, ApifyClient, PubloraClient; print('OK')"
python3 scripts/sync_codex_marketplace.py
wc -l SKILL.md skills/*/SKILL.md
ls skills/ | wc -l # must equal 12
python3 scripts/check_frontmatter.py # parses; a dir count does not prove a skill loads
python3 scripts/check_no_secrets.py # .gitignore does not stop a rename of a tracked file
python3 scripts/check_config.py --offline # credential wiring; --offline skips the live API calls
python3 scripts/check_actor_inputs.py # Apify ignores unknown input keys; this catches a renamed one
python3 -m unittest discover -s tests # contracts: docs vs code, response shapes, client behaviour
python3 scripts/selftest.py # the whole picture: install, accounts, tests, what works now
Behaviour, not plumbing: `python3 evals/run_evals.py` runs the agent against
fixtures and grades what comes back (one model call per case, `--list` to see
them). Graders must have a right answer - the parentComment for a nested reply,
whether a scrub kept the user's figures. A grader that needs taste will drift,
and a wrong grader fails the skill for the grader's mistake.
grep -nE '^description:' skills/*/SKILL.md SKILL.md | grep -P '\\x{2014}|\\x{2013}' # must be empty
python3 -m json.tool .codex-plugin/plugin.json >/dev/null
python3 -m json.tool .agents/plugins/marketplace.json >/dev/null
python3 -m json.tool .claude-plugin/plugin.json >/dev/null
python3 -m json.tool .claude-plugin/marketplace.json >/dev/null
If any of these fail, do not push.
