Setup embedded app verify
Skill mrmarufpro/shopify-embedded-app-verify/skills/setup-embedded-app-verify
One-time setup for the Shopify embedded-app verify loop in a project. Use when the user asks to set up / configure the verify loop, or when the run-embedded-app-verify skill finds no project config.From its SKILL.md
npx -y skills add mrmarufpro/shopify-embedded-app-verify --skill setup-embedded-app-verifyAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
6.2 KB, as published. Nobody here has run it
Set up the embedded-app verify loop
Goal: a CDP-enabled, Shopify-authenticated browser plus a project config file.
1. Show the resolved developer config
The plugin config is substituted into this skill at load time:
- browser:
${user_config.browser} - mode:
${user_config.mode} - cdp_port:
${user_config.cdp_port}
A blank value (or a literal dollar-brace placeholder) means the default
applies: chrome / profile / 9222. Tell the user the resolved values and that
they can be changed anytime via /plugin → shopify-embedded-app-verify →
configure (changes reach skills after /reload-plugins or a session restart).
Do NOT read CLAUDE_PLUGIN_OPTION_* env vars — they are only set for plugin
subprocesses such as hooks, never for Bash commands you run.
2. Ensure the browser
Run:
node ${CLAUDE_PLUGIN_ROOT}/scripts/ensure-browser.mjs --browser "${user_config.browser}" --mode "${user_config.mode}" --port "${user_config.cdp_port}" --url "https://admin.shopify.com"
Note the BROWSER_STATE: line in the output — launched-at-url,
launched, or reused — step 3 branches on it.
- Exit 0 → continue.
CDP_BLOCKED_DEFAULT_PROFILE→ explain: this browser refuses CDP on its default profile (Chrome/Edge 136+). The user must switch the plugin's mode option to "profile" (dedicated automation profile, one-time login). Stop.BROWSER_MISMATCH→ the CDP port is already served by a browser that is not the configured one (and not one this plugin launched). Show the message verbatim — the user must quit that browser or change the plugin's browser/cdp_port option. Stop. (A stale automation browser from an earlier run is handled automatically — the script quits it and relaunches.)- Other non-zero → show the message verbatim; stop.
3. Open the verify window and check Shopify authentication
Never open tabs in the developer's own windows.
Preflight said BROWSER_STATE: launched-at-url (profile mode): the
browser opened directly at https://admin.shopify.com — its only tab IS
the verify window. browser_tabs (action: list), select that tab, grab
its targetId with browser_run_code_unsafe, and continue to the auth
check below:
async (page) => {
const session = await page.context().newCDPSession(page);
const { targetInfo } = await session.send("Target.getTargetInfo");
await session.detach();
return targetInfo.targetId;
}
Preflight said BROWSER_STATE: launched or reused: the browser's
tabs belong to the developer (an attach-mode relaunch restores their
session — those tabs are NOT yours, whatever they show, even an admin
dashboard). browser_tabs (action: list), then:
Case A — the list shows only blank tabs (about:blank /
chrome://new-tab-page — a browser nobody is using): reuse that startup
window instead of opening a second one. Select it, grab its targetId with
browser_run_code_unsafe:
async (page) => {
const session = await page.context().newCDPSession(page);
const { targetInfo } = await session.send("Target.getTargetInfo");
await session.detach();
return targetInfo.targetId;
}
then browser_navigate it to https://admin.shopify.com.
Case B — any non-blank tabs exist (attach mode / browser already in
use): open a dedicated verify window with browser_run_code_unsafe (the
code is invoked with the current page as its single argument):
async (page) => {
const session = await page.context().newCDPSession(page);
const { targetId } = await session.send("Target.createTarget", {
url: "https://admin.shopify.com",
newWindow: true,
});
await session.detach();
return targetId;
}
Then browser_tabs (action: list) and select the newly added entry — the
tab that was not in the list before creation (new tabs are appended at the
end); do not pick the first URL match, the developer may already have an
admin tab open.
In all cases: save the targetId — step 5 closes the window with it.
- Lands on a store dashboard (
admin.shopify.com/store/...) → authenticated. - Redirects to
accounts.shopify.comlogin → tell the user: "Log into the Shopify admin in the verify window that just opened — the session persists." (profile mode: once per browser — each browser gets its own automation profile.) Wait for their confirmation, then re-check.
Keep the verify window open — the smoke test (step 5) reuses it.
4. Write the project config
appHandle: readhandlefrom the project'sshopify.app.toml. Multiple toml files (e.g.shopify.app.<env>.toml) → ask which one is the dev app.storeDomain: ask the user for the dev store subdomain (<storeDomain>.myshopify.com→ store part only, e.g.store-seo-app-test).- Write
.claude/shopify-verify.json:
{
"appHandle": "<from toml>",
"storeDomain": "<from user>",
"iframeSelector": "iframe[name=\"app-iframe\"]"
}
5. Smoke test
- In the verify window from step 3, navigate to
https://admin.shopify.com/store/<storeDomain>/apps/<appHandle>. - Wait for the app iframe; take a screenshot; confirm the app rendered.
- 404 → the app is not installed on this store; point the user to the dev server output's install link.
- Iframe loads an error/tunnel page → the app's dev server is not running; tell the user to check it (starting it is their job — never start or curl-probe it yourself). The verify loop needs it running, but setup itself is complete.
- Report the result and delete the screenshot.
- Close the verify window with
browser_run_code_unsafeand the targetId saved in step 3 (neverbrowser_closeor close-by-index — both act on the current tab and misfire when the developer has many tabs open; if the tool errors because its own page just closed, the window did close):
async (page) => {
const session = await page.context().newCDPSession(page);
await session.send("Target.closeTarget", { targetId: "<targetId from step 3>" });
}