Read ~/.claude/skills/shared/bash-rules.md for bash command constraints.
Context
- Chrome extension: !
test -f manifest.json && echo yes || echo no
- Manifest version: !
sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' manifest.json 2>/dev/null | head -1
- Repo: !
gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || echo NONE
- Latest release: !
gh release view --json tagName --jq .tagName 2>/dev/null || echo NONE
- Release zip assets: !
gh release view --json assets --jq ".assets[].name" 2>/dev/null || echo NONE
- API credentials: !
test -f ~/.claude/skills/publish-chrome-extension/config/cws.conf && echo PRESENT || echo MISSING
- Extension ID: !
grep -rhoE 'chromewebstore\.google\.com/detail/[^/]+/[a-p]{32}' --include='*.md' --exclude-dir=node_modules . 2>/dev/null | grep -oE '[a-p]{32}' | head -1 || echo NONE
1. Check preconditions
- Chrome extension must be
yes — otherwise STOP: this skill republishes Chrome extensions (manifest.json at the repo root) and nothing else.
- Repo must not be
NONE — the zip comes from a GitHub release, so the repo needs a GitHub remote.
- Latest release must not be
NONE — if it is, tell the user to run /release first and stop.
2. First-time credential setup
If API credentials is MISSING, walk the user through the one-time OAuth setup in
references/cws-api-setup.md (Google Cloud project, Chrome Web Store API, Desktop-app OAuth
client, then cws.sh init / auth / exchange). Do not proceed until cws.conf exists.
If cws.sh later fails with a missing/expired REFRESH_TOKEN error, re-run only the
auth + exchange steps from that reference — the client credentials stay valid.
3. Resolve the extension ID
Extension ID in Context is scraped from the project's own Web Store install link — the
32-character trailing segment of https://chromewebstore.google.com/detail/<slug>/<ID>, which a
published extension normally puts in its README or docs. That link is user-facing, so it cannot go
stale unnoticed the way a private copy would.
If it is NONE, ask the user for the ID (the same trailing segment, also visible in the Developer
Dashboard edit URL). Offer to add the install link to the README while you're there, so the next
run finds it.
This skill deliberately stores nothing about the listing. The dashboard-only fields — single
purpose, permission justifications, store description, data usage, privacy policy URL — cannot be
read back through the API, so any repo copy of them is an unverifiable cache that drifts silently
the moment someone edits the dashboard directly. The dashboard is the authority; step 6 derives
what needs checking from git and the store instead. Do not reintroduce a listing file.
4. Pick the release
- Default to Latest release from Context; if the user named a different tag when invoking the
skill, use that instead.
- Cross-check: the tag (
vX.Y.Z) should match Manifest version (X.Y.Z) — after a normal
/release flow they agree. On mismatch, warn the user and ask whether to continue (they may be
intentionally republishing an older tag).
- Release zip assets must contain exactly one
.zip for that release; if several, ask which.
5. Download and verify the zip
- Run:
mkdir -p /tmp/cws-publish
- Run:
gh release download <tag> --pattern "*.zip" --dir /tmp/cws-publish --clobber
- Verify the package version inside the zip matches the tag:
unzip -p /tmp/cws-publish/<asset>.zip manifest.json — check its "version". On mismatch,
STOP and report: the release asset doesn't contain what the tag claims.
6. Review listing info for staleness
The dashboard blocks submission when the listing info doesn't cover the new package, and the API
cannot fix it. Catch the gaps now, before the upload — by diffing what the package claims against
the version the store already holds:
- Read the store's current version. Run:
bash ~/.claude/skills/publish-chrome-extension/scripts/cws.sh status <extension-id>
crxVersion is the version the store holds — call it LIVE.
uploadState says what LIVE is: NOT_FOUND means no draft is pending, so LIVE is the
published package. Anything else means LIVE is a draft that was uploaded and never submitted;
say so, because the real published version is then older.
- If no
v<LIVE> tag exists locally, run git fetch --tags and retry. If it still doesn't
exist, ask the user which tag corresponds to the store version.
- Diff the manifest since LIVE. Run:
git diff v<LIVE>..HEAD -- manifest.json
- Permission added to
permissions or host_permissions → the dashboard has no
justification for it and will reject the submission. Draft one with the user, grounded in
what the code actually uses the permission for, and have them paste it into the dashboard's
Privacy practices tab and Save draft there before continuing.
- Permission removed → tell the user to delete its now-orphaned justification in the
dashboard.
description changed → the store Description usually mirrors the manifest's, and the
Single purpose statement usually mirrors both. Show the diff and ask whether the dashboard
text (and the project's README/docs tagline) needs the same edit.
- Single purpose. Show the user-visible changes since LIVE —
git log v<LIVE>..HEAD --oneline,
feat: subjects — and ask whether the dashboard's Single purpose statement still covers
everything the extension now does. Read the current statement from the dashboard rather than
from anything in the repo; if it needs changing, draft the new text together and have the user
paste it in.
- Data usage. If the release introduces remote code or starts collecting/transmitting any
user-data category (rare), the dashboard's Data usage form needs updating to match.
- Wait for the user to confirm every dashboard edit is saved before continuing.
7. Upload
- Present the plan — extension ID, tag, zip filename, version — and ask for confirmation.
- Run:
bash ~/.claude/skills/publish-chrome-extension/scripts/cws.sh upload <extension-id> /tmp/cws-publish/<asset>.zip
- Inspect the JSON response
uploadState:
SUCCESS → continue
IN_PROGRESS → the upload is processing server-side; continue (publish will pick it up)
FAILURE / NOT_FOUND → show every itemError entry and STOP
8. Submit for review
- Ask the user to confirm submitting the uploaded draft for Web Store review (this is the
point of no return — a submitted version can only be cancelled from the dashboard).
- Run:
bash ~/.claude/skills/publish-chrome-extension/scripts/cws.sh publish <extension-id>
- Report the response
status (e.g. OK, ITEM_PENDING_REVIEW) and statusDetail verbatim.
A 400 "Publish condition not met … Privacy practices" means step 6 missed something — fix it
in the dashboard and re-run the publish command.
9. Report
Print:
- the dashboard URL:
https://chrome.google.com/webstore/devconsole (review progress lives there)
- the public listing URL:
https://chromewebstore.google.com/detail/<extension-id>
- a reminder that review typically takes from a few hours to a few days; the new version goes
live automatically once approved
- that
cws.sh status <extension-id> reports what the store actually holds — it is the answer to
"which version is published", so nothing needs recording in the repo
Out of scope
- Do NOT create a new Web Store listing — the extension must already exist in the dashboard
- Do NOT edit store listing content in the dashboard itself — every dashboard change is the
user's manual step (the API has no endpoint for it)
- Do NOT store a copy of the listing text or the published version in the repo — see step 3
- Do NOT bump versions, build zips, or create tags/releases — that's the
/release skill
- Do NOT monitor the review — it can take days; the skill ends at submission
1---2name: publish-chrome-extension3description: Republish a new release of an existing Chrome extension to the Chrome Web Store: downloads the release zip from GitHub, checks what changed since the version the store already holds, uploads the zip via the Web Store API, and submits it for review. TRIGGER when: the user wants to publish, republish, or upload a new extension version to the Chrome Web Store, typically right after /release. DO NOT TRIGGER when: creating a brand-new store listing, editing store listing content (description, screenshots), or building/tagging a release (use /release for that).4---56Read `~/.claude/skills/shared/bash-rules.md` for bash command constraints.78## Context9- Chrome extension: !`test -f manifest.json && echo yes || echo no`10- Manifest version: !`sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' manifest.json 2>/dev/null | head -1`11- Repo: !`gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || echo NONE`12- Latest release: !`gh release view --json tagName --jq .tagName 2>/dev/null || echo NONE`13- Release zip assets: !`gh release view --json assets --jq ".assets[].name" 2>/dev/null || echo NONE`14- API credentials: !`test -f ~/.claude/skills/publish-chrome-extension/config/cws.conf && echo PRESENT || echo MISSING`15- Extension ID: !`grep -rhoE 'chromewebstore\.google\.com/detail/[^/]+/[a-p]{32}' --include='*.md' --exclude-dir=node_modules . 2>/dev/null | grep -oE '[a-p]{32}' | head -1 || echo NONE`1617## 1. Check preconditions1819- **Chrome extension** must be `yes` — otherwise **STOP**: this skill republishes Chrome extensions (`manifest.json` at the repo root) and nothing else.20- **Repo** must not be `NONE` — the zip comes from a GitHub release, so the repo needs a GitHub remote.21- **Latest release** must not be `NONE` — if it is, tell the user to run `/release` first and stop.2223## 2. First-time credential setup2425If **API credentials** is MISSING, walk the user through the one-time OAuth setup in26`references/cws-api-setup.md` (Google Cloud project, Chrome Web Store API, Desktop-app OAuth27client, then `cws.sh init` / `auth` / `exchange`). Do not proceed until `cws.conf` exists.2829If `cws.sh` later fails with a missing/expired `REFRESH_TOKEN` error, re-run only the30`auth` + `exchange` steps from that reference — the client credentials stay valid.3132## 3. Resolve the extension ID3334**Extension ID** in Context is scraped from the project's own Web Store install link — the3532-character trailing segment of `https://chromewebstore.google.com/detail/<slug>/<ID>`, which a36published extension normally puts in its README or docs. That link is user-facing, so it cannot go37stale unnoticed the way a private copy would.3839If it is `NONE`, ask the user for the ID (the same trailing segment, also visible in the Developer40Dashboard edit URL). Offer to add the install link to the README while you're there, so the next41run finds it.4243This skill deliberately stores **nothing** about the listing. The dashboard-only fields — single44purpose, permission justifications, store description, data usage, privacy policy URL — cannot be45read back through the API, so any repo copy of them is an unverifiable cache that drifts silently46the moment someone edits the dashboard directly. The dashboard is the authority; step 6 derives47what needs checking from git and the store instead. Do not reintroduce a listing file.4849## 4. Pick the release5051- Default to **Latest release** from Context; if the user named a different tag when invoking the52 skill, use that instead.53- Cross-check: the tag (`vX.Y.Z`) should match **Manifest version** (`X.Y.Z`) — after a normal54 `/release` flow they agree. On mismatch, warn the user and ask whether to continue (they may be55 intentionally republishing an older tag).56- **Release zip assets** must contain exactly one `.zip` for that release; if several, ask which.5758## 5. Download and verify the zip59601. Run: `mkdir -p /tmp/cws-publish`612. Run: `gh release download <tag> --pattern "*.zip" --dir /tmp/cws-publish --clobber`623. Verify the package version inside the zip matches the tag:63 `unzip -p /tmp/cws-publish/<asset>.zip manifest.json` — check its `"version"`. On mismatch,64 **STOP** and report: the release asset doesn't contain what the tag claims.6566## 6. Review listing info for staleness6768The dashboard blocks submission when the listing info doesn't cover the new package, and the API69cannot fix it. Catch the gaps now, before the upload — by diffing what the package claims against70the version the store already holds:71721. **Read the store's current version.** Run:73 `bash ~/.claude/skills/publish-chrome-extension/scripts/cws.sh status <extension-id>`74 - `crxVersion` is the version the store holds — call it LIVE.75 - `uploadState` says what LIVE is: `NOT_FOUND` means no draft is pending, so LIVE is the76 published package. Anything else means LIVE is a draft that was uploaded and never submitted;77 say so, because the real published version is then older.78 - If no `v<LIVE>` tag exists locally, run `git fetch --tags` and retry. If it still doesn't79 exist, ask the user which tag corresponds to the store version.802. **Diff the manifest since LIVE.** Run: `git diff v<LIVE>..HEAD -- manifest.json`81 - **Permission added** to `permissions` or `host_permissions` → the dashboard has no82 justification for it and will reject the submission. Draft one with the user, grounded in83 what the code actually uses the permission for, and have them paste it into the dashboard's84 Privacy practices tab and **Save draft** there before continuing.85 - **Permission removed** → tell the user to delete its now-orphaned justification in the86 dashboard.87 - **`description` changed** → the store Description usually mirrors the manifest's, and the88 Single purpose statement usually mirrors both. Show the diff and ask whether the dashboard89 text (and the project's README/docs tagline) needs the same edit.903. **Single purpose.** Show the user-visible changes since LIVE — `git log v<LIVE>..HEAD --oneline`,91 `feat:` subjects — and ask whether the dashboard's Single purpose statement still covers92 everything the extension now does. Read the current statement from the dashboard rather than93 from anything in the repo; if it needs changing, draft the new text together and have the user94 paste it in.954. **Data usage.** If the release introduces remote code or starts collecting/transmitting any96 user-data category (rare), the dashboard's Data usage form needs updating to match.975. Wait for the user to confirm every dashboard edit is saved before continuing.9899## 7. Upload1001011. Present the plan — extension ID, tag, zip filename, version — and ask for confirmation.1022. Run: `bash ~/.claude/skills/publish-chrome-extension/scripts/cws.sh upload <extension-id> /tmp/cws-publish/<asset>.zip`1033. Inspect the JSON response `uploadState`:104 - `SUCCESS` → continue105 - `IN_PROGRESS` → the upload is processing server-side; continue (publish will pick it up)106 - `FAILURE` / `NOT_FOUND` → show every `itemError` entry and **STOP**107108## 8. Submit for review1091101. Ask the user to confirm submitting the uploaded draft for Web Store review (this is the111 point of no return — a submitted version can only be cancelled from the dashboard).1122. Run: `bash ~/.claude/skills/publish-chrome-extension/scripts/cws.sh publish <extension-id>`1133. Report the response `status` (e.g. `OK`, `ITEM_PENDING_REVIEW`) and `statusDetail` verbatim.114 A 400 "Publish condition not met … Privacy practices" means step 6 missed something — fix it115 in the dashboard and re-run the publish command.116117## 9. Report118119Print:120- the dashboard URL: `https://chrome.google.com/webstore/devconsole` (review progress lives there)121- the public listing URL: `https://chromewebstore.google.com/detail/<extension-id>`122- a reminder that review typically takes from a few hours to a few days; the new version goes123 live automatically once approved124- that `cws.sh status <extension-id>` reports what the store actually holds — it is the answer to125 "which version is published", so nothing needs recording in the repo126127## Out of scope128129- Do NOT create a new Web Store listing — the extension must already exist in the dashboard130- Do NOT edit store listing content in the dashboard itself — every dashboard change is the131 user's manual step (the API has no endpoint for it)132- Do NOT store a copy of the listing text or the published version in the repo — see step 3133- Do NOT bump versions, build zips, or create tags/releases — that's the `/release` skill134- Do NOT monitor the review — it can take days; the skill ends at submission