{"id":49569459,"url":"https://github.com/bug0inc/passmark","last_synced_at":"2026-05-20T03:01:05.772Z","repository":{"id":347998939,"uuid":"1195492297","full_name":"bug0inc/passmark","owner":"bug0inc","description":"The open-source Playwright library for AI browser regression testing with intelligent caching, auto-healing, and multi-model verification.","archived":false,"fork":false,"pushed_at":"2026-05-17T10:08:01.000Z","size":455,"stargazers_count":772,"open_issues_count":18,"forks_count":157,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-05-17T12:29:27.542Z","etag":null,"topics":["ai","ai-agents","ai-testing","aigateway","aisdk","browser-testing","e2e-testing","playwright","qa","qa-automation","qaautomation","regression-testing","testing","typescript","vercel"],"latest_commit_sha":null,"homepage":"https://passmark.dev","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bug0inc.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-29T18:22:06.000Z","updated_at":"2026-05-16T07:15:12.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bug0inc/passmark","commit_stats":null,"previous_names":["bug0inc/passmark"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bug0inc/passmark","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bug0inc%2Fpassmark","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bug0inc%2Fpassmark/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bug0inc%2Fpassmark/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bug0inc%2Fpassmark/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bug0inc","download_url":"https://codeload.github.com/bug0inc/passmark/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bug0inc%2Fpassmark/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33243972,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-19T15:49:41.270Z","status":"online","status_checked_at":"2026-05-20T02:00:07.149Z","response_time":356,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","ai-agents","ai-testing","aigateway","aisdk","browser-testing","e2e-testing","playwright","qa","qa-automation","qaautomation","regression-testing","testing","typescript","vercel"],"created_at":"2026-05-03T13:06:49.551Z","updated_at":"2026-05-20T03:01:05.759Z","avatar_url":"https://github.com/bug0inc.png","language":"TypeScript","funding_links":[],"categories":["Natural Language Test Authoring"],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\n    \u003cimg src=\"https://res.cloudinary.com/dkanxf2cg/image/upload/v1776252765/passmark-logo_cj0qbz.png\" alt=\"Passmark\" width=\"500\" /\u003e\n    \u003cbr\u003e\n    \u003csmall\u003eThe open-source Playwright library for AI regression testing.\u003c/small\u003e\n\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://x.com/bug0inc\"\u003e\u003cimg src=\"https://img.shields.io/badge/follow-%40bug0inc-black?logo=x\" alt=\"Follow on X\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://www.linkedin.com/company/bug0\"\u003e\u003cimg src=\"https://img.shields.io/badge/LinkedIn-bug0-blue?logo=linkedin\" alt=\"LinkedIn\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://bug0.com\"\u003e\u003cimg src=\"https://img.shields.io/badge/website-bug0.com-brightgreen\" alt=\"Website\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://www.npmjs.com/package/passmark\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/passmark\" alt=\"npm package version\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/bug0inc/passmark/blob/main/LICENSE.md\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-FSL--1.1--ALv2-blue\" alt=\"License\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nPassmark covers your browser regression testing end-to-end and **helps you catch regressions early. Fast.**\n\nIt uses AI models to execute natural language browser steps via Playwright, with intelligent caching, auto-healing, and multi-model assertion verification. Your tests stay stable without needing to update AI prompts or retrain models.\n\n## Quick Start\n\n```bash\nnpm init playwright@latest passmark-project # select the default options and set language to TypeScript\ncd passmark-project\nnpm install passmark\n```\n\nWe need at least one model from Anthropic and one from Google to use Passmark's multi-model consensus features. Set the required environment variables in `.env`:\n\n```\nANTHROPIC_API_KEY=sk-ant-...\nGOOGLE_GENERATIVE_AI_API_KEY=AIza...\n```\n\nAlternatively, you can use an AI gateway like Vercel AI Gateway or OpenRouter to route requests to multiple providers without managing individual API keys. If you choose this option, set `AI_GATEWAY_API_KEY` (for Vercel) or `OPENROUTER_API_KEY` (for OpenRouter) instead.\n\nYou can also route requests through Cloudflare AI Gateway for observability, caching, and rate limiting. Unlike Vercel/OpenRouter, Cloudflare is a proxy (not a reseller), so you still need your own `ANTHROPIC_API_KEY` / `GOOGLE_GENERATIVE_AI_API_KEY` alongside `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_AI_GATEWAY` (and `CLOUDFLARE_AI_GATEWAY_API_KEY` if the gateway has authentication enabled).\n\nSet your Playwright project to read `.env` by adding the following to `playwright.config.ts`  (after `import { defineConfig, devices } from '@playwright/test';`):\n\n```typescript\nimport dotenv from 'dotenv';\nimport path from 'path';\n\ndotenv.config({ path: path.resolve(__dirname, '.env') });\n```\n\nMake sure you install `dotenv` by running `npm install dotenv`.\n\nNow, paste the following code into `tests/example.spec.ts`:\n\n```typescript\nimport { test, expect } from \"@playwright/test\";\nimport { runSteps } from \"passmark\";\n\ntest.use({\n  headless: !!process.env.CI,\n});\n\ntest(\"Shopping cart tests\", async ({ page }) =\u003e {\n  test.setTimeout(60_000); // increase timeout for AI execution\n  await runSteps({\n    page,\n    userFlow: \"Add product to cart\",\n    steps: [\n      { description: \"Navigate to https://demo.vercel.store\" },\n      { description: \"Click Acme Circles T-Shirt\" },\n      { description: \"Select color\", data: { value: \"White\" } },\n      { description: \"Select size\", data: { value: \"S\" } },\n      { description: \"Add to cart\", waitUntil: \"My Cart is visible\" },\n    ],\n    assertions: [{ assertion: \"You can see My Cart with Acme Circles T-Shirt\" }],\n    test,\n    expect\n  });\n});\n```\n\nIf you are using an AI gateway, you can add the following to the above code:\n\n```typescript\nimport { runSteps, configure } from \"passmark\";\n\nconfigure({\n  ai: {\n    gateway: \"vercel\" // or \"openrouter\" or \"cloudflare\"\n    // Set AI_GATEWAY_API_KEY (Vercel), OPENROUTER_API_KEY (OpenRouter), or\n    // CLOUDFLARE_ACCOUNT_ID + CLOUDFLARE_AI_GATEWAY (+ CLOUDFLARE_AI_GATEWAY_API_KEY\n    // if the gateway is authenticated) in your .env file. Cloudflare also requires\n    // the upstream provider keys (ANTHROPIC_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY).\n  }\n});\n```\n\nTo run the test, use:\n\n```bash\nnpx playwright test example.spec.ts --project chromium\n```\n\nAfter the test completes, you can run `npx playwright show-report` to see a detailed report of the test execution, including an AI summary at the top, provided by Passmark.\n\n### Using CUA mode (OpenAI computer-use agent)\n\nBy default Passmark uses ARIA accessibility snapshots. For visual, screenshot-driven automation via OpenAI's computer-use agent, opt in with `mode: \"cua\"`:\n\n```typescript\nimport { configure } from \"passmark\";\n\nconfigure({\n  ai: {\n    mode: \"cua\",\n    gateway: \"none\", // CUA requires direct OpenAI access\n  },\n});\n```\n\nSet `OPENAI_API_KEY` in your `.env`. Then you can write tests like this:\n\n```typescript\ntest(\"Shopping cart tests\", async ({ page }) =\u003e {\n  await runSteps({\n    page,\n    userFlow: \"Add product to cart\",\n    steps: [\n      { description: \"Navigate to https://demo.vercel.store\" },\n      { description: \"Click Acme Circles T-Shirt\" },\n      { description: \"Select color\", data: { value: \"White\" } },\n      { description: \"Add to cart\", waitUntil: \"My Cart is visible\" },\n    ],\n    test,\n    expect,\n  });\n});\n```\n\nNotes:\n\n- CUA mode uses OpenAI's `gpt-5.5` + built-in `computer` tool. The CUA model is currently locked and not user-configurable.\n- Redis step caching is skipped in CUA mode because coordinate actions aren't portable across viewport sizes.\n- `gateway: \"vercel\" | \"openrouter\" | \"cloudflare\"` is not compatible with CUA — the Responses-API `computer` tool is only exposed on direct OpenAI access.\n- Account requirements: your OpenAI API key must have access to the CUA model and the built-in `computer` tool on the Responses API.\n\n#### Per-step overrides (hybrid runs)\n\nThe same `ai` shape accepted by `configure()` can also be passed at the `runSteps`/`runUserFlow` call level **and** on individual `Step`s. This lets you mix snapshot steps (cheap, cacheable, OpenRouter/Vercel/etc.) with CUA steps (visual, direct OpenAI) in a single run. Precedence: `step.ai` ▶ call-level `ai` ▶ global `configure()`.\n\n```typescript\nconfigure({ ai: { gateway: \"openrouter\" } }); // most steps go through OpenRouter\n\nawait runSteps({\n  page, test, expect,\n  userFlow: \"Buy product on sale\",\n  steps: [\n    { description: \"Navigate to /products\" },                     // OpenRouter snapshot\n    {\n      description: \"Drag the price slider to $40\",\n      ai: { mode: \"cua\", gateway: \"none\" },                       // CUA for this step only\n    },\n    { description: \"Click Add to cart\" },                         // back to OpenRouter snapshot\n  ],\n});\n```\n\nSet `OPENAI_API_KEY` whenever any step opts into `mode: \"cua\"`. CUA steps still require `gateway: \"none\"`; mixing CUA with a non-`none` gateway throws at the per-step level for the same reason it does globally.\n\n## Features\n\n- **Core Execution** — `runSteps()` and `runUserFlow()` for flexible test orchestration in natural language, with smart caching and auto-healing\n- **Multi-Model Assertion Engine** — Consensus-based validation using Claude and Gemini, with an arbiter model to resolve disagreements\n- **Video Assertions** — Opt in per-assertion to record the full step run and evaluate the assertion against the whole video via Gemini's Files API. Useful for ephemeral UI (toasts, snackbars) that a single screenshot may miss\n- **Redis-Based Step Caching** — Cache-first execution with AI fallback and automatic self-healing when cached steps fail\n- **Configurable AI Models** — 8 dedicated model slots for step execution, assertions, extraction, and more\n- **AI Gateway Support** — Route requests through Vercel AI Gateway, OpenRouter, Cloudflare AI Gateway, or connect directly to provider SDKs\n- **Dynamic Placeholders** — Inject values at runtime with `{{run.*}}`, `{{global.*}}`, `{{data.*}}`, and `{{email.*}}` expressions for repeatable and data-driven tests\n- **Email Extraction** — Pluggable email provider interface with a built-in emailsink provider\n- **AI-Powered Data Extraction** — Extract structured values from page snapshots and URLs using AI\n- **Smart Wait Conditions** — AI-evaluated wait conditions with exponential backoff. No rigid selectors or time-based waits needed.\n- **Secure Script Runner** — AST-validated Playwright script execution with an allowlisted API surface\n- **Telemetry** — Optional Axiom and OpenTelemetry tracing via environment variables\n- **Structured Logging** — Pino-based logger with configurable log levels\n- **Global Configuration** — Single `configure()` entry point for models, gateway, email provider, and upload path\n\n## Core Functions\n\n### `runSteps(options: RunStepsOptions)`\n\nExecutes a sequence of steps using AI with caching. Each step is described in natural language and executed via Playwright.\n\n```typescript\nawait runSteps({\n  page,\n  userFlow: \"Checkout Flow\",\n  steps: [\n    { description: \"Add item to cart\" },\n    { description: \"Go to checkout\" },\n    { description: \"Fill in shipping details\", data: { value: \"123 Main St\" } },\n  ],\n  assertions: [{ assertion: \"Order confirmation is displayed\" }],\n  test,\n  expect,\n});\n```\n\n### `runUserFlow(options: UserFlowOptions)`\n\nRuns a complete user flow as a single AI agent call. Best for exploratory testing where exact steps are flexible.\n\n```typescript\nconst result = await runUserFlow({\n  page,\n  userFlow: \"Complete a purchase\",\n  steps: \"Navigate to store, add an item, checkout with test card\",\n  effort: \"high\", // by default \"low\" uses gemini-3-flash for faster execution; \"high\" uses gemini-3.1-pro-preview for deeper thinking\n});\n```\n\n### `assert(options: AssertionOptions)`\n\nMulti-model consensus assertion. Runs Claude and Gemini in parallel; if they disagree, a third model arbitrates.\n\n```typescript\nconst result = await assert({\n  page,\n  assertion: \"The dashboard shows 3 active projects\",\n  expect,\n});\n```\n\n### Video Assertions\n\nFor UI that's only visible for a second or two — toast messages, snackbar confirmations, transient banners — a single end-of-flow screenshot often misses the evidence. Set `video: true` on an assertion inside `runSteps` and Passmark will record the entire step run with `page.screencast`, upload the resulting `.webm` to Gemini's Files API, and evaluate the assertion against the full video:\n\n```typescript\nawait runSteps({\n  page,\n  userFlow: \"Add to cart\",\n  steps: [\n    { description: \"Click Acme Circles T-Shirt\" },\n    { description: \"Add to cart\" },\n  ],\n  assertions: [\n    { assertion: \"An 'Added to cart' toast appears\", video: true },\n  ],\n  test,\n  expect,\n});\n```\n\nNotes:\n\n- Recording spans the **entire** step run (start of first step to end of last step). One recording is shared across all `video: true` assertions in the same `runSteps` call.\n- The video file is written to `/tmp/passmark-recordings/` by default and deleted automatically after the assertions consume it. Override via `configure({ videoDir: \"/your/path\" })`.\n- This path uses **only Gemini** (no Claude/Gemini consensus) since Claude doesn't accept video. The model is `gemini-3-flash-preview`.\n- Video assertions go **directly** to Gemini's Files API regardless of any configured `gateway` — file URIs are tied to the uploading Google account, so the gateway can't proxy them. You must set `GOOGLE_GENERATIVE_AI_API_KEY` (or `GEMINI_API_KEY`) even when the rest of your stack runs through Vercel / OpenRouter / Cloudflare.\n- If `page.screencast.start()` fails (rare), video assertions silently fall back to the regular screenshot/snapshot path so the run still completes.\n\n## Configuration\n\nCall `configure()` once before using any functions:\n\n```typescript\nimport { configure } from \"passmark\";\n\nconfigure({\n  ai: {\n    gateway: \"none\", // \"none\" (default), \"vercel\", \"openrouter\", or \"cloudflare\"\n    models: {\n      stepExecution: \"google/gemini-3-flash\",\n      utility: \"google/gemini-2.5-flash\",\n    },\n  },\n  uploadBasePath: \"./uploads\",\n});\n```\n\n## Environment Variables\n\n| Variable | Required | Default | Description |\n|----------|----------|---------|-------------|\n| `REDIS_URL` | No | - | Redis connection URL for step caching and global state. Can also be set via `configure({ redis: { url } })`, which takes precedence. |\n| `ANTHROPIC_API_KEY` | Yes | - | Anthropic API key for Claude models |\n| `GOOGLE_GENERATIVE_AI_API_KEY` | Yes | - | Google API key for Gemini models. Also required for `video: true` assertions regardless of gateway (file URIs are tied to the uploading account). |\n| `OPENAI_API_KEY` | No | - | OpenAI API key for OpenAI models (required for CUA mode; must have Responses-API `computer` tool access) |\n| `AI_GATEWAY_API_KEY` | If gateway=vercel | - | Vercel AI Gateway API key |\n| `OPENROUTER_API_KEY` | If gateway=openrouter | - | OpenRouter API key |\n| `CLOUDFLARE_ACCOUNT_ID` | If gateway=cloudflare | - | Cloudflare account ID that owns the AI Gateway |\n| `CLOUDFLARE_AI_GATEWAY` | If gateway=cloudflare | - | Cloudflare AI Gateway name (slug) |\n| `CLOUDFLARE_AI_GATEWAY_API_KEY` | If gateway=cloudflare and the gateway is authenticated | - | Cloudflare AI Gateway token (sent as `cf-aig-authorization`) |\n| `AXIOM_TOKEN` | No | - | Axiom token for OpenTelemetry tracing. Can also be set via `configure({ telemetry: { axiomToken } })`, which takes precedence. |\n| `AXIOM_DATASET` | No | - | Axiom dataset for trace storage. Can also be set via `configure({ telemetry: { axiomDataset } })`, which takes precedence. |\n| `PASSMARK_LOG_LEVEL` | No | `info` | Log level: `debug`, `info`, `warn`, `error`, `silent` |\n\n## Model Configuration\n\nAll models are configurable via `configure({ ai: { models: { ... } } })`:\n\n| Key | Default | Used For |\n|-----|---------|----------|\n| `stepExecution` | `google/gemini-3-flash` | Executing individual steps |\n| `userFlowLow` | `google/gemini-3-flash-preview` | User flow execution (low effort) |\n| `userFlowHigh` | `google/gemini-3.1-pro-preview` | User flow execution (high effort) |\n| `assertionPrimary` | `anthropic/claude-4.5-haiku` | Primary assertion model (Claude) |\n| `assertionSecondary` | `google/gemini-3-flash` | Secondary assertion model (Gemini) |\n| `assertionArbiter` | `google/gemini-3.1-pro-preview` | Arbiter for assertion disagreements |\n| `utility` | `google/gemini-2.5-flash` | Data extraction, wait conditions |\n| `cua` | `gpt-5.5` | CUA mode — OpenAI Responses API with the built-in `computer` tool |\n\n## Caching\n\nPassmark caches successful step actions in Redis. On subsequent runs, cached steps execute directly without AI calls, dramatically reducing latency and cost.\n\nProvide the connection via `configure({ redis: { url } })` or the `REDIS_URL` env var (configure value wins). Without either, caching, `{{global.*}}` placeholders, and project data are disabled.\n\n- Steps are cached by `userFlow` + `step.description`\n- Set `bypassCache: true` on individual steps or the entire run to force AI execution\n- Cache is automatically bypassed on Playwright retries\n- Caching only applies to `runSteps`. As of now, only those AI executions that are single-step are cached as multi-step actions can vary widely and are less likely to be identical on subsequent runs. We're exploring ways to safely cache multi-step flows.\n\n## Telemetry\n\nTelemetry is opt-in. Either set the `AXIOM_TOKEN` and `AXIOM_DATASET` env vars, or pass them through `configure()`:\n\n```typescript\nconfigure({\n  telemetry: {\n    axiomToken: process.env.MY_AXIOM_TOKEN,\n    axiomDataset: \"passmark-traces\",\n  },\n});\n```\n\n`configure()` values take precedence over env vars. Without either, telemetry is a no-op. All AI calls are wrapped with `withSpan` for observability.\n\nConfigure Axiom to get a rich dashboard like this:\n\n![Axiom Dashboard](https://res.cloudinary.com/dkanxf2cg/image/upload/v1774866500/axiom-logs_d4p7h9.png)\n\n## Email Extraction\n\nConfigure an email provider for testing flows that involve email verification. By default, you can use the `emailsink` provider, which provides disposable email addresses and an API to fetch received emails. The free tier doesn't need any credentials, but for more reliability and flexible rate limits, you can sign up for an account and use your `EMAILSINK_API_KEY`. Reach out to us if you want to get an API key.\n\n```typescript\nimport { configure } from \"passmark\";\nimport { emailsinkProvider } from \"passmark/providers/emailsink\";\n\nconfigure({\n  email: emailsinkProvider({ apiKey: process.env.EMAILSINK_API_KEY }),\n});\n```\n\nOr implement a custom provider:\n\n```typescript\nconfigure({\n  email: {\n    domain: \"my-test-mail.com\",\n    extractContent: async ({ email, prompt }) =\u003e {\n      // Fetch and extract content from your email service\n      return extractedValue;\n    },\n  },\n});\n```\n\nUse in steps with the `{{email.*}}` placeholder pattern:\n\n```typescript\n{\n  description: \"Enter the verification code\",\n  data: { value: \"{{email.otp:get the 6 digit verification code:{{run.dynamicEmail}}}}\" }\n}\n```\n\n## Placeholder System\n\nDynamic values can be injected into step data using placeholders:\n\n| Pattern | Scope | Description |\n|---------|-------|-------------|\n| `{{run.email}}` | Single test | Random email (faker) |\n| `{{run.dynamicEmail}}` | Single test | Email using configured domain |\n| `{{run.fullName}}` | Single test | Random full name |\n| `{{run.shortid}}` | Single test | Short unique ID |\n| `{{run.phoneNumber}}` | Single test | Random phone number |\n| `{{global.email}}` | All tests in an execution | Shared across runSteps calls with same `executionId` |\n| `{{global.dynamicEmail}}` | All tests in an execution | Shared dynamic email |\n| `{{data.key}}` | Per project | Stored in Redis, managed via project settings |\n| `{{email.type:prompt}}` | Resolved lazily | Extract content from received email |\n\n## Architecture Overview\n\n```\nStep Request\n    |\n    v\n[Cache Check] --hit--\u003e [Execute Cached Action] --success--\u003e Done\n    |                          |\n    miss                     fail (auto-heal)\n    |                          |\n    v                          v\n[AI Execution] ---------\u003e [Cache Result]\n    |\n    v\n[Assertions] (Claude + Gemini consensus)\n```\n\n## Known Limitations\n\n- Tests are not comprehensive at the moment. We welcome contributions to expand test coverage, especially around edge cases and failure modes.\n\n## Contributing\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup, code style, and PR workflow.\n\n## License\n\n[FSL-1.1-Apache-2.0](./LICENSE.md) - Functional Source License, Version 1.1, with Apache 2.0 future license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbug0inc%2Fpassmark","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbug0inc%2Fpassmark","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbug0inc%2Fpassmark/lists"}