mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
* feat(contrib): add od-contribute skill for non-coder contributors
Adds a Claude Code skill at .claude/skills/od-contribute/ that walks any OD
user — including non-coders — through a first-PR contribution flow:
- Ship a Skill / Design System made with OD
- Translate README / QUICKSTART / CONTRIBUTING to a new language
- Fix a typo / dead link / write a use-case blog post
- Report a high-quality bug (issue path, no PR)
The skill replaces the test-driven dev-loop of auto-github-contributor with
type-specific no-code validators (frontmatter parse, markdown link check,
code-fence balance, structural overlap with reference DESIGN.md files), so
artifact-only contributions don't have to pretend to be code.
This commit only adds files under .claude/ — no product code, no build
config, no runtime dependencies. .gitignore is amended with three explicit
exceptions so the skill is tracked while personal Claude state (sessions,
settings, etc.) stays ignored as before.
Next steps (separate PRs):
- Wire the OD app to mount this skill for its embedded agent
- Add a "Ship to GitHub" UI button in OD that invokes /od-contribute
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* feat(contrib): English-by-default skill + zip installer for non-coders
Two follow-ups to the initial od-contribute skill:
1. Skill content is now English with an explicit instruction at the top
telling the agent to mirror the user's chat language for every user-
facing prompt. Generated artifacts (PR titles, commit messages, PR/
issue body) stay English regardless — GitHub convention.
2. tools/od-contribute-installer/ ships a cross-platform installer that
drops the skill into every supported agent's home dir without the
user opening a terminal:
install.command macOS double-click
install.bat Windows double-click
install.sh Linux
Targets covered:
~/.claude/skills/od-contribute/ Claude Code (native)
~/.claude/commands/od-contribute.md Claude Code slash command
~/.agents/skills/od-contribute/ Codex CLI (canonical)
~/.codex/skills/od-contribute/ Codex CLI (legacy, only
written if ~/.codex/ exists)
Verified Codex CLI reads the same SKILL.md frontmatter format as
Claude Code (source: openai/codex codex-rs/core-skills/src/loader.rs).
Added agents/openai.yaml sidecar inside the skill for Codex picker UX.
3. build-zip.sh produces od-contribute-installer.zip (~37KB) from the
in-repo skill. The zip is meant to be hosted as a GitHub Release
asset; the marketing site button points at:
github.com/nexu-io/open-design/releases/latest/download/od-contribute-installer.zip
(See tools/od-contribute-installer/HOSTING.md for the manual release
recipe; CI workflow can come later.)
The zip itself is gitignored — distribute via Releases, not source.
Still no product code touched, no build config changed.
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* refactor(contrib): drop zip installer; ship single curl one-liner
Replace tools/od-contribute-installer/ (4 install scripts + zip build
machinery) with a single self-bootstrapping tools/install-od-contribute.sh.
User flow becomes:
1. Click button on opendesign.so
2. Modal shows: paste this into your AI agent's chat:
curl -sSL https://raw.githubusercontent.com/nexu-io/open-design/main/tools/install-od-contribute.sh | bash
3. Agent runs it via its Bash tool. User never touches a terminal.
4. /od-contribute is live in their next chat.
Why this is better than the zip approach:
* Zero downloads visible to the user — no .zip in their Downloads folder
* Zero unzip step
* Zero terminal window flash (the agent's Bash tool runs in-process)
* Zero per-OS installer files (.command/.bat/.sh) to maintain
* Auto-updates: re-running the one-liner pulls the latest skill from main
The script downloads only the skill subtree (.claude/skills/od-contribute/
and .claude/commands/od-contribute.md) from a GitHub tarball — no `git`
dependency, just curl + tar (universally available).
Targets remain the same:
~/.claude/skills/od-contribute/
~/.claude/commands/od-contribute.md
~/.agents/skills/od-contribute/
~/.codex/skills/od-contribute/ (only if ~/.codex/ exists)
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* chore(contrib): remove leftover zip artifact
Build artifact accidentally committed in the previous commit.
Cleaning up so the binary doesn't live in git history.
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* fix(contrib): make skill work in sandboxed agents (Codex.app, Cursor)
macOS App Sandbox apps like Codex.app cannot reach the system keychain
where `gh auth login` stores the GitHub token by default. Result: the
skill's check-prereqs.sh fails on `gh auth status` with a misleading
"not authenticated" error, even when gh works fine in the user's regular
shell.
Two changes:
1. config.sh: if GH_TOKEN isn't set in the env, fall back to reading a
.gh-token file at the skill root. Lets a user (or the OD app, or a
future OAuth Device Flow bootstrapper) drop a token there once and
have every skill script pick it up automatically.
2. check-prereqs.sh: accept GH_TOKEN-from-env as a valid auth path
alongside `gh auth status`. When neither works, the error hint now
shows BOTH options:
A) gh auth login from a regular terminal (any agent)
B) gh auth token > <skill>/.gh-token (sandboxed agents)
Verified: in my local Claude Code (where gh has keychain access), the
keychain path still wins and nothing changes. With GH_TOKEN exported,
check-prereqs.sh succeeds without even consulting gh auth status.
Future: implement OAuth Device Flow inside the skill so non-coder users
hitting this in Codex.app can authenticate by clicking a link, no
terminal involved. That's a separate PR.
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* chore(contrib): move install script into skill folder (CI policy fix)
The repo's tools/ directory has a strict allowlist policy enforced by
scripts/guard.ts — only AGENTS.md, dev/, pack/, and serve/ are permitted
top-level entries. Moving install-od-contribute.sh out of tools/ and into
.claude/skills/od-contribute/install.sh:
- Satisfies the guard policy (no scripts/guard.ts edit needed)
- Co-locates the install script with the skill it installs (cleaner
mental model: skill folder is self-contained)
- The install URL stays inside the gitignore exception we already
established for .claude/skills/od-contribute/
Public install URL changes from
raw.githubusercontent.com/.../main/tools/install-od-contribute.sh
to
raw.githubusercontent.com/.../main/.claude/skills/od-contribute/install.sh
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* fix(contrib): address @nettee/looper review feedback (3 blocking issues)
Three real bugs caught by the looper review bot, all fixed:
1) create-pr.sh:48 — git diff missed untracked files
`git diff --quiet || git diff --cached --quiet` ignored untracked paths,
so the most common contribution shape (a brand-new Skill folder, a new
translation file, a new doc) hit the else branch and pushed an empty
commit. Replaced with `git status --porcelain` which sees untracked,
plus a post-stage sanity check via `git diff --cached --quiet` so we
skip the commit cleanly if everything turned out to be in .gitignore.
2) validate-skill-submission.sh:34 — frontmatter parse too lenient
The awk fence-counter accepted `---` anywhere in the file as the
opening fence. A SKILL.md with prose before the YAML block parsed as
"valid frontmatter" by this script while the actual loaders (Claude
Code + codex-rs/core-skills) required the fence on line 1 and would
reject it. Added an explicit head -n 1 check so leading prose is
rejected with a clear error before awk runs.
3) check-prereqs.sh:87 — gh api user failure swallowed
`GH_USER="$(gh api user --jq .login 2>/dev/null || echo '?')"` set
GH_USER to literal "?" when the API call failed (revoked token,
missing 'repo' scope, network), then the script exited READY=1.
Downstream that propagated to TARGET_FORK="?/open-design" and
blew up at push time.
Dropped the `|| echo '?'` fallback. An empty GH_USER now triggers a
structured error with three common causes and the recovery command,
and exits 2.
While here, also fixed a related bug: this script sources config.sh
which has `set -euo pipefail`, so -e leaked in and aborted the
script silently the moment any check failed (instead of accumulating
diagnostics like the original auto-github-contributor design
intended). Added explicit `set +e; set -uo pipefail` after sourcing
to restore the "keep checking past failures" behavior the comment
on line 7 promised.
Smoke-tested all four fixes locally:
- create-pr.sh: git status --porcelain correctly sees untracked files
- validator: rejects SKILL.md starting with prose, passes well-formed
- check-prereqs.sh: with stubbed gh that fails `gh api user`, now
exits 2 with the structured error (was: silent exit 1)
- check-prereqs.sh: happy path on real machine unchanged
Thanks @nettee for the careful review.
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* fix(contrib): macOS Bash 3.2 + over-strict link validator (review round 2)
Two more blocking issues from the looper review, plus one related bug I
caught while re-testing on real OD docs.
1) discover-i18n-gaps.sh: removed Bash 4 dep (declare -A)
macOS still ships Bash 3.2.57 by default and most agent-spawned bash
subprocesses inherit that. `declare -A SEEN_LANG=()` failed with
`declare: -A: invalid option`, crashing Step 3b before any translation
target could be shown.
Replaced the associative array with a newline-delimited string set
(\n<lang>\n bracket form to avoid prefix-overlap false matches like
zh vs zh-CN). Verified end-to-end on /bin/bash 3.2.57 against the
actual OD repo: returns the correct 28 stale-translation rows
across the four English source docs.
Also fixed a latent path-stripping bug in the same loop: `find`
emits `./README.zh-CN.md` with leading `./`, so `${path#README.}`
wasn't stripping the prefix at all. Switched to basename-first.
2) validate-markdown.sh: --reference flag for i18n / docs-edit flows
The validator was treating every relative link target as a file path
and failing on slugs like `skills/blog-post/` that are website
router routes, not files in the checkout. A structure-preserving
translation of README.md couldn't pass even when the user changed
nothing except language.
Added --reference <orig> flag. The validator now builds a "known
already-broken" set of refs from the source file and excuses those
in the new file. Newly-introduced broken refs still fail.
Without --reference (e.g. brand-new blog file with no prior version),
the relative-ref check is skipped entirely with a SKIP note — since
we can't tell route slugs from file paths in isolation, failing
would be wrong. Code-fence balance + external-link health still run.
Updated SKILL.md so the i18n branch (3b.6) and the docs branch
(3c.6) call validate-markdown.sh with --reference pointing at the
English source / HEAD revision respectively.
3) (caught while testing) URL extraction regex too loose
`grep -oE 'https?://[^) ]+'` was capturing trailing quotes from HTML
<img src="..."> tags in OD's README, e.g.
https://cms-assets.youmind.com/.../foo.jpg"
The trailing `"` made the curl HEAD return 404. Tightened the
character class to also stop at `"`, `'`, `<`, `>`, `[`, `]`.
With this fix, README.md now passes all checks (20 external links
verified 2xx/3xx).
Smoke-tested on macOS /bin/bash 3.2.57 with the actual nexu-io/open-design
working copy. All four scenarios behave correctly:
- README.md without --reference → SKIP relative-ref check, PASS overall
- README.md with --reference itself → 34 refs excused as pre-existing, PASS
- Newly-introduced broken ref → FAIL (regression catch preserved)
- Old test cases (skill validator, prereq check) → still pass
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* fix(contrib): preserve .gh-token across install.sh reruns
`install_skill_to()` did `rm -rf $dest` before copying in the new skill,
which wiped any user-local state files. The most consequential one is
`.gh-token` — sandboxed agents (Codex.app, Cursor) write a GitHub token
there because they can't reach the macOS keychain (see check-prereqs.sh's
hint and config.sh's fallback path).
Effect: the documented upgrade path ("re-run the curl one-liner to pull
the latest skill") would silently lose the token on every refresh, and
the very next /od-contribute run would fail at the prereq gate with
"no GitHub credentials available", forcing the user back through manual
token setup. This affects exactly the audience the PR is aimed at.
Fix: stash any file in PRESERVE=(.gh-token) to a tempdir before rm -rf,
restore after the copy, re-chmod 600 on the way back. Test:
1. Pre-seed .gh-token in all three target dirs
2. Run installer
3. Verify all three tokens still present, contents unchanged, perms 600
Centralized the preserved-state list as PRESERVE=() so future per-user
state (e.g. an OAuth-flow-saved refresh token) only has to be added in
one place.
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* fix(contrib): i18n stale false-positive + tier markdown link check (round 4)
Two more blocking issues from looper, both real.
1) discover-i18n-gaps.sh: false-stale on same-commit translations
`git log --since=@<epoch>` is INCLUSIVE of the boundary epoch, so when
the English source and a translation get touched in the SAME commit
(a very common pattern: bulk i18n refresh, structural edits applied
across all locales), the shared commit was counted toward
english_commits_since_translation. Result: an already-current
translation was reported with `status="stale", english_commits_since_
translation=1`, and Step 3b would suggest it for refresh — driving
users into no-op PRs.
Reproduced exactly per looper's case: README.md and README.uk.md both
have last commit
|
||
|---|---|---|
| .. | ||
| od-contribute | ||