Drive the OpenWork Cloud dashboard (Den Web / app.openworklabs.com) as a real user via headless Chrome CDP — sign up, create an org, invite teammates, teams, plugins with skills, marketplaces, team access grants, desktop handoff. Use for cloud tutorials, dashboard screenshots, demo walkthroughs, or when a task says "create an org / invite teammates / create a plugin / assign a marketplace" on OpenWork Cloud.
77
96%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
High
Do not use without reviewing
Drive the real OpenWork Cloud dashboard end-to-end as a user. Proven flow used
to produce packages/docs/cloud/team-quickstart.mdx — reuse it for
tutorial refreshes, demos, and screenshot runs.
Start a Den server sandbox (multi-org, dev mode skips email verification):
bash .devcontainer/test-server-on-daytona.sh dev --name <sandbox-name>
# capture the printed DEN_WEB_URL and DEN_API_URLstart-daytona-server.sh must pass DEN_ORG_MODE to den-web (not just
den-api); otherwise den-web defaults to single_org with private signup and
the UI silently routes new emails to the password step. Verify:
curl -s "$DEN_WEB_URL/api/runtime-config" # expect "orgMode":"multi_org"Launch a driver Chrome (2x screenshots, isolated profile):
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--headless=new --remote-debugging-port=9223 \
--user-data-dir=/tmp/opencode/chrome-profile \
--window-size=1440,960 --force-device-scale-factor=2 \
--hide-scrollbars --disable-gpu about:blank &Then use browser_navigate / browser_eval / browser_screenshot with
browser_url: "http://127.0.0.1:9223". The a11y snapshot is sparse in
headless — drive everything through browser_eval DOM queries instead.
Inputs: set values through the native setter so React sees them:
const set = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value").set;
set.call(input, "value"); input.dispatchEvent(new Event("input", { bubbles: true }));(Use HTMLTextAreaElement.prototype for textareas.)
Buttons: match on exact trimmed text. Beware prefix collisions — the submit "Sign up" vs "Sign up with Google" (clicking the latter on a sandbox yields "Provider not found").
Before every screenshot: hide the Next.js dev badge —
document.querySelector('nextjs-portal')?.style.display = 'none' — and wait
~1s for settle. Dev-only UI is safe to hide; never hide product UI.
Tab labels carry count badges (Teams2): match with
el.textContent.trim().replace(/\d+$/, "").
| Step | Route | Key labels |
|---|---|---|
| Email-first sign-in | / | "Start using OpenWork" → EMAIL → Next |
| Sign-up | / | "Create your account." → EMAIL/NAME/PASSWORD → Sign up |
| Create org | /organization | "Name your team." → Organization name → Continue |
| Onboarding checklist | /dashboard/onboarding | "Let's finish setting up {org}." |
| Members / invites | /dashboard/members | Add member → Email + Role → Send invite; rows show INVITED/Pending |
| Teams | /dashboard/members Teams tab | Create Team → name (placeholder "Core Engineering") + member card toggles → Create team |
| Plugins list | /dashboard/plugins | Create plugin |
| Plugin editor | /dashboard/plugins/new | name/description → + Skill (name, one-line description, markdown instructions) → Share: org-wide checkbox + Marketplace select → Create plugin |
| Marketplaces | /dashboard/marketplaces | New marketplace → Name + Description → Create marketplace |
| Marketplace access | /dashboard/marketplaces/<id> Members tab | "Who can access this": Everyone toggle, Add team, Add people |
Notes:
input[type="password"], click
Verify password; the pending action retries automatically.Invite tokens are readable as the owner: GET /api/den/v1/org →
invitations[].token. Join link: /join-org?invite=<token>.
POST $DEN_API_URL/api/auth/sign-up/email with the
invited email (dev mode returns a token immediately), then
POST /v1/orgs/invitations/accept with Authorization: Bearer <token> and
body {"id":"<inviteToken>"}.PATCH /api/den/v1/me/profile {"firstName":"…","lastName":"…"}
from that user's session.As a signed-in web user: POST /api/den/v1/auth/desktop-handoff (body {})
→ { grant }. Build the deep link yourself — the server-resolved
denBaseUrl can be an internal host:
openwork://den-auth?grant=<grant>&denBaseUrl=<urlencoded DEN_WEB_URL>Grants expire in 5 minutes and are single-use. Consuming it in Electron is
covered in the daytona-electron-den skill.
Public preview URLs get auth-walled after a while (redirect to Auth0 → fetches fail / "Not found."). Recovery:
daytona sandbox preview-url <sandbox> -p 3005 --expires 86400 # den-web
daytona sandbox preview-url <sandbox> -p 8788 --expires 86400 # den-apiThen restart the Den stack with the signed URLs as its public URLs —
Better-Auth rejects the new host with 403 "Invalid origen" otherwise (trusted
origens are derived from the public URLs at startup). Re-run
start-daytona-server.sh inside the sandbox with
DEN_WEB_PUBLIC_URL/DEN_API_PUBLIC_URL set to the signed URLs. Browser
cookies do not carry across hosts — sign in again.
The CLI eats flag-like tokens inside commands (mkdir -p, tail -30 lose
their flags). Prefer flag-free forms (cd /workspace && mkdir x,
cat file | tail) or pipe the file to stdout and process locally.
1345d51
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.