{"id":"3bb0840a-fb47-4fdd-b34e-2528efef0350","entityType":"agent","slug":"clawhub-chrischall-booli-fpx","name":"booli-fpx","canonicalUrl":"https://www.xpersona.co/agent/clawhub-chrischall-booli-fpx","canonicalPath":"/agent/clawhub-chrischall-booli-fpx","generatedAt":"2026-10-11T10:52:11.743Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-11T07:29:50.974Z","emptyReason":null},"description":"Query booli.se (Swedish property portal) from a shell with the fpx CLI (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas, search for-sale and sold listings (slutpriser), and read property detail via one-shot GraphQL calls through a signed-in browser tab. Use when you want Booli data without the MCP, in a script, or on a machine where the MCP isn't installed. Skill: booli-fpx Owner: chrischall Summary: Query booli.se (Swedish property portal) from a shell with the fpx CLI (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas, search for-sale and sold listings (slutpriser), and read property detail via one-shot GraphQL calls through a signed-in browser tab. Use when you want Booli data without the MCP, in a script, or on a machine where the MCP isn't in","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.1K downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s17cjx1a349nz5apaqp02vgz4h85728z:booli-fpx","sourceUrl":"https://clawhub.ai/chrischall/booli-fpx","homepage":"https://clawhub.ai/chrischall/skills/booli-fpx","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/chrischall/booli-fpx","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/chrischall/skills/booli-fpx","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":61,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Query booli.se (Swedish property portal) from a shell with the fpx CLI (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas, search for-sal"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T07:29:50.974Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T07:29:50.974Z","emptyReason":null},"stars":null,"forks":null,"downloads":1123,"packageName":null,"latestVersion":"2.1.8","tractionLabel":"1.1K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T07:29:50.907Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T07:29:50.974Z","lastCrawledAt":"2026-10-11T07:29:50.907Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T07:29:50.907Z","lastVerifiedAt":null,"highlights":[{"version":"2.1.8","createdAt":"2026-10-09T23:24:04.677Z","changelog":"- Removed the skill-card.md file. - No changes to functionality or documentation other than file cleanup.","fileCount":4,"zipByteSize":6198},{"version":"2.1.7","createdAt":"2026-10-07T13:36:54.512Z","changelog":"- Removed the redundant skill-card.md file. - No functional or user-facing changes; documentation and usage remain the same.","fileCount":4,"zipByteSize":6095},{"version":"2.1.6","createdAt":"2026-10-05T02:48:51.041Z","changelog":"- Removed the skill-card.md file. - No user-facing features or functionality changed.","fileCount":4,"zipByteSize":6116},{"version":"2.1.5","createdAt":"2026-10-03T01:42:01.277Z","changelog":"- Removed the file: skill-card.md - No changes to functionality or documentation content.","fileCount":4,"zipByteSize":6114},{"version":"2.1.4","createdAt":"2026-09-28T13:55:39.577Z","changelog":"- Documentation updated: references to the Transporter extension replaced with ContextMint Bridge, including updated setup instructions and links. - Clarified installation steps and requirements for the ContextMint Bridge extension. - Outdated references and instructions removed or revised for accuracy. - Obsolete file skill-card.md removed.","fileCount":4,"zipByteSize":6087},{"version":"2.1.3","createdAt":"2026-09-23T21:38:34.312Z","changelog":"- Removed the file skill-card.md. - No changes to skill logic or functionality.","fileCount":4,"zipByteSize":6037},{"version":"2.1.2","createdAt":"2026-09-23T15:41:17.809Z","changelog":"- Removed the file skill-card.md. - No other changes were made.","fileCount":4,"zipByteSize":6049},{"version":"2.1.1","createdAt":"2026-09-21T16:35:20.488Z","changelog":"- Removed the skill-card.md file. - No user-facing feature or behavior changes.","fileCount":4,"zipByteSize":6135}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17cjx1a349nz5apaqp02vgz4h85728z:booli-fpx","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-booli-fpx/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-booli-fpx/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-booli-fpx/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-booli-fpx/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-booli-fpx/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-booli-fpx/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-11T10:52:11.741Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-booli-fpx/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-booli-fpx/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-booli-fpx/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-booli-fpx/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-11T07:29:50.974Z","emptyReason":null},"readme":"Skill: booli-fpx\n\nOwner: chrischall\n\nSummary: Query booli.se (Swedish property portal) from a shell with the fpx CLI (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas, search for-sale and sold listings (slutpriser), and read property detail via one-shot GraphQL calls through a signed-in browser tab. Use when you want Booli data without the MCP, in a script, or on a machine where the MCP isn't installed.\n\nTags: latest:2.1.8\n\nVersion history:\n\nv2.1.8 | 2026-10-09T23:24:04.677Z | auto\n\n- Removed the skill-card.md file.\n- No changes to functionality or documentation other than file cleanup.\n\nv2.1.7 | 2026-10-07T13:36:54.512Z | auto\n\n- Removed the redundant skill-card.md file.\n- No functional or user-facing changes; documentation and usage remain the same.\n\nv2.1.6 | 2026-10-05T02:48:51.041Z | auto\n\n- Removed the skill-card.md file.\n- No user-facing features or functionality changed.\n\nv2.1.5 | 2026-10-03T01:42:01.277Z | auto\n\n- Removed the file: skill-card.md\n- No changes to functionality or documentation content.\n\nv2.1.4 | 2026-09-28T13:55:39.577Z | auto\n\n- Documentation updated: references to the Transporter extension replaced with ContextMint Bridge, including updated setup instructions and links.\n- Clarified installation steps and requirements for the ContextMint Bridge extension.\n- Outdated references and instructions removed or revised for accuracy.\n- Obsolete file skill-card.md removed.\n\nv2.1.3 | 2026-09-23T21:38:34.312Z | auto\n\n- Removed the file skill-card.md.\n- No changes to skill logic or functionality.\n\nv2.1.2 | 2026-09-23T15:41:17.809Z | auto\n\n- Removed the file skill-card.md. \n- No other changes were made.\n\nv2.1.1 | 2026-09-21T16:35:20.488Z | auto\n\n- Removed the skill-card.md file.\n- No user-facing feature or behavior changes.\n\nv2.1.0 | 2026-09-20T02:48:35.091Z | auto\n\n- Removed skill-card.md file.\n- No changes to core functionality or usage.\n- Version incremented to 2.1.0.\n\nv2.0.0 | 2026-09-17T17:51:00.943Z | auto\n\n- Removed the `skill-card.md` file.\n- No other functional changes; all usage and documentation remain the same.\n\nv1.3.3 | 2026-09-15T19:26:20.857Z | auto\n\n- Removed the file skill-card.md.\n- No functional or user-facing changes; this is a documentation cleanup release.\n\nv1.3.2 | 2026-09-14T14:10:06.529Z | auto\n\n- Removed the file skill-card.md.\n- No changes to functionality or user-facing documentation.\n- Housekeeping update to reduce redundant or unused files.\n\nv1.3.1 | 2026-09-10T17:53:32.391Z | auto\n\n- Removed the sample file skill-card.md.\n- No user-facing or functional changes; documentation cleanup only.\n\nv1.3.0 | 2026-09-04T22:23:04.127Z | auto\n\n- Removed the skill-card.md file.\n- No changes to functionality or documentation except for file removal.\n\nv1.2.1 | 2026-09-02T23:33:55.027Z | auto\n\nVersion 1.2.1 of booli-fpx\n\n- No file changes detected in this release.\n- Documentation and usage instructions remain unchanged.\n- No new features, bug fixes, or breaking changes in this version.\n\nv1.2.0 | 2026-09-02T23:26:45.647Z | auto\n\n- Removed the skill-card.md file.\n- No user-facing functional changes.\n\nv1.1.0 | 2026-08-29T13:53:36.663Z | auto\n\n- Removed redundant file: skill-card.md.\n- No functional or user-facing changes.\n\nv1.0.5 | 2026-08-28T21:06:54.030Z | auto\n\n- Removed the file skill-card.md.\n- No functional changes to the skill; documentation and usage remain unchanged.\n\nv1.0.4 | 2026-08-28T14:21:55.385Z | auto\n\n- Support for querying booli.se property data from the shell using the fpx CLI, without running the MCP server.\n- Enables area lookup, for-sale and sold listing searches, and property detail retrieval via one-shot GraphQL calls routed through a signed-in browser tab.\n- Requires the Transporter browser extension and a paired, open booli.se tab (no Booli login needed).\n- Includes clear setup instructions, exit codes, and practical jq recipes for processing responses.\n- Recommended for users needing Booli data in scripts or environments without the MCP installed.\n\nArchive index:\n\nArchive v2.1.8: 4 files, 6198 bytes\n\nFiles: references/graphql-queries.md (5826b), skill-card.md (2187b), SKILL.md (4178b), _meta.json (128b)\n\nFile v2.1.8:SKILL.md\n\n---\nname: booli-fpx\ndescription: >-\n  Query booli.se (Swedish property portal) from a shell with the fpx CLI\n  (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas,\n  search for-sale and sold listings (slutpriser), and read property detail via\n  one-shot GraphQL calls through a signed-in browser tab. Use when you want\n  Booli data without the MCP, in a script, or on a machine where the MCP isn't\n  installed.\n---\n\n# Booli via fpx (no MCP)\n\nBooli fronts `www.booli.se` — including `/graphql` — with a Cloudflare managed\nchallenge that 403s any plain `curl`/Node request. `fpx` routes the request\nthrough the user's own signed-in browser tab (the ContextMint Bridge extension),\nwhich has already cleared the challenge, so the same anonymous GraphQL query\nsucceeds. No Booli login is needed — just a normal open tab.\n\nThis is the same data the `booli_*` MCP tools return (money in **SEK**, areas\nin **m²**, rooms as a number), reached with one CLI call instead of a running\nserver.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli           # provides `fpx`\nfpx profile add booli --domain booli.se  # only the fetch capability is needed\nfpx pair -p booli                        # prints a pair code → approve in ContextMint Bridge\n```\n\nRequirements: the **ContextMint Bridge** browser extension installed (from\nhttps://github.com/nullnet-app/contextmint-bridge/releases — Chrome: load the chrome zip\nunpacked; Safari isn't available yet — use Chrome for now), with an open\n`www.booli.se` tab, and its Chrome **Site access** allowing `booli.se`.\nContextMint Bridge is the fetchproxy extension renamed, same maintainer; source at\nhttps://github.com/nullnet-app/contextmint-bridge (build it, or verify a release zip with its published `.sha256`).\nPairing persists — after the first approval every later `fpx` call reuses it.\n\n## Core call\n\nEvery query is a POST of `{\"query\": \"...\", \"variables\": {...}}` to the GraphQL\nendpoint. Write the body to a file (a heredoc avoids shell-quoting the GraphQL\nstring), then send it so stdout is the GraphQL JSON, ready for `jq`:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nReady-to-run query bodies (area lookup, for-sale search, sold search, property\ndetail) with `jq` recipes are in `references/graphql-queries.md`. Exhaustive\nfield lists live in the repo at `src/graphql.ts` — the operations here are\ncompact, live-verified subsets.\n\n## The one rule: resolve the area first\n\nBooli searches take a numeric **area id** (`areaId`), never a place name.\nAlways resolve the name first, take the `id`, then search:\n\n```sh\nprintf '{\"query\":\"query($s:String!){areaSuggestionSearch(search:$s){suggestions{id displayName}}}\",\"variables\":{\"s\":\"Nacka\"}}' > /tmp/a.json\nfpx post-json 'https://www.booli.se/graphql' @/tmp/a.json -p booli \\\n  | jq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n# 76      Nacka kommun\n```\n\nPass that `id` as the search input's `areaId` (a string). The `SearchRequest`\ninput needs `areaId`, `page` (1-based), `ascending`, `excludeAncestors: true`,\n`facets: []`, `filters: [{key,value}]`, and `sort` (`\"\"` = newest). Filters\nreuse the same keys the MCP does (`minRooms`, `maxListPrice`, `objectType`, …).\n\n## Exit codes (fetch verbs)\n\n- `0` — success (a GraphQL `errors` array can still ride in a `0` body; check `jq '.errors // empty'`).\n- `2` — bridge unavailable: extension not connected or pairing pending → run `fpx pair -p booli`, confirm a booli tab is open.\n- `3` — bot wall: the tab hasn't cleared Cloudflare → open/refresh a `www.booli.se` tab and retry.\n- `4` — upstream non-2xx from Booli.\n\n## Notes\n\n- Anonymous reads only — the same public GraphQL surface as the MCP; no account\n  data. Stay within booli.se's terms.\n- `fpx health -p booli` shows bridge connection state when a call fails.\n- Property detail is keyed by **residence id** — the number in a\n  `/bostad/<id>` URL, which is a search result's `url` field, **not** `booliId`.\n- This project is developed and maintained by AI (Claude).\n\nFile v2.1.8:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"booli-fpx\",\n  \"version\": \"2.1.8\",\n  \"publishedAt\": 1791588244677\n}\n\nFile v2.1.8:references/graphql-queries.md\n\n# Booli GraphQL queries for fpx\n\nReady-to-run bodies for `fpx post-json 'https://www.booli.se/graphql' @file -p booli`.\nEach block is the full POST body (`{\"query\",\"variables\"}`). All field names and\ninput shapes are live-verified against the endpoint; the exhaustive field\nselections live in the repo at `src/graphql.ts` (these are compact subsets).\n\nEndpoint: `https://www.booli.se/graphql` · Method: POST · anonymous.\nIntrospection is disabled — probe field names by sending a query and reading the\nvalidation errors (that's how these were pinned).\n\nWrite the body to a file (heredoc avoids shell-quoting the GraphQL string),\nthen send it:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nAlways check for GraphQL errors: `jq '.errors // empty'` (an errors array can\nride in an HTTP-200 body).\n\nNumeric fields come as `FormattedValue { raw, formatted }` — select `{ raw }`\nfor the number, `{ formatted }` for the display string.\n\n---\n\n## 1. Resolve an area → areaId (do this first)\n\n`search` is free text.\n\n```json\n{\n  \"query\": \"query Areas($s: String!) { areaSuggestionSearch(search: $s) { suggestions { id displayName } } }\",\n  \"variables\": { \"s\": \"Nacka\" }\n}\n```\n\n```sh\njq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n```\n\n## 2. Search for-sale listings\n\n`input` is a `SearchRequest`: `areaId` (string, from step 1), `page` (1-based),\n`ascending`, `excludeAncestors: true`, `facets: []`, `filters: [{key,value}]`,\n`sort` (`\"\"` = newest, else `listPrice` / `listSqmPrice` / `rooms` /\n`livingArea` / `rent` / `plotArea` / `published`). `result` mixes `Listing` and\n`Project` (new-construction) nodes — take the `... on Listing` ones.\n\nFilter keys (both key and value are strings): `objectType` (`Lägenhet`, `Villa`,\n`Kedjehus-Parhus-Radhus`, `Fritidshus`, `Gård`, `Tomt/Mark`), `minRooms`/`maxRooms`,\n`minLivingArea`/`maxLivingArea`, `minPlotArea`/`maxPlotArea`,\n`minListPrice`/`maxListPrice`, `minListSqmPrice`/`maxListSqmPrice`,\n`minConstructionYear`/`maxConstructionYear`, `isNewConstruction` (`1`/`0`).\n\n```json\n{\n  \"query\": \"query SearchForSale($input: SearchRequest) { searchForSale(input: $input) { totalCount pages result { __typename ... on Listing { booliId objectType tenureForm streetAddress descriptiveAreaName url rooms { raw } livingArea { raw } rent { raw } listPrice { raw formatted } listSqmPrice { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [\n        { \"key\": \"objectType\", \"value\": \"Lägenhet\" },\n        { \"key\": \"minRooms\", \"value\": \"3\" },\n        { \"key\": \"maxListPrice\", \"value\": \"6000000\" }\n      ],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\njq -r '.data.searchForSale | \"total=\\(.totalCount) pages=\\(.pages)\", (.result[] | select(.__typename==\"Listing\") | \"\\(.booliId)\\t\\(.listPrice.formatted // \"—\")\\t\\(.streetAddress)\")'\n```\n\n## 3. Search sold listings (slutpriser — the comps)\n\nSame `SearchRequest` input; result is `... on SoldProperty`. Sold-specific\nfilter keys: `minSoldPrice`/`maxSoldPrice`, `minSoldSqmPrice`/`maxSoldSqmPrice`,\n`minSoldDate`/`maxSoldDate` (YYYYMMDD). `soldPricePercentageDiff.raw` is the\nover/under-asking % (e.g. `-20`).\n\n```json\n{\n  \"query\": \"query SearchSold($input: SearchRequest) { searchSold(input: $input) { totalCount pages result { __typename ... on SoldProperty { booliId objectType streetAddress descriptiveAreaName url soldDate soldPrice { raw formatted } soldSqmPrice { raw } listPrice { raw } soldPricePercentageDiff { raw } rooms { raw } livingArea { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [{ \"key\": \"minSoldDate\", \"value\": \"20240101\" }],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\n# Median sold price across the page (raw kronor):\njq -r '[.data.searchSold.result[] | select(.__typename==\"SoldProperty\") | .soldPrice.raw] | sort | .[length/2|floor]'\n```\n\n## 4. Property detail by residence id\n\n`residenceId` is `ID!` — the number in a `/bostad/<id>` URL (a search result's\n`url` field), **not** `booliId`. `propertyByResidenceId` returns the `Property`\ninterface; Listing/SoldProperty-only fields need inline fragments. Works for\nboth active and sold properties (check `__typename`).\n\n```json\n{\n  \"query\": \"query Detail($residenceId: ID!) { propertyByResidenceId(residenceId: $residenceId) { __typename booliId residenceId objectType tenureForm streetAddress descriptiveAreaName constructionYear url rooms { raw } livingArea { raw } plotArea { raw } rent { raw } operatingCost { raw } floor { raw } location { region { municipalityName countyName } namedAreas } ... on Listing { published isNewConstruction upcomingSale listingUrl listPrice { raw formatted } listSqmPrice { raw } agency { name url } estimate { price { raw } } } ... on SoldProperty { soldPrice { raw formatted } soldSqmPrice { raw } soldDate soldPricePercentageDiff { raw } } } }\",\n  \"variables\": { \"residenceId\": \"4370936\" }\n}\n```\n\n```sh\njq '.data.propertyByResidenceId | {type: .__typename, addr: .streetAddress, rooms: .rooms.raw, m2: .livingArea.raw, price: (.listPrice.formatted // .soldPrice.formatted), area: .location.namedAreas[0]}'\n```\n\n---\n\n## Market stats\n\nBooli's GraphQL has no market-stats query — the `booli_market_stats` tool\nderives median/average sold price, price-per-m², and average over/under-asking %\nlocally from a `searchSold` page (§3). Reproduce with `jq` over `result` as\nshown above; check the count before trusting a thin median.\n\nFile v2.1.8:skill-card.md\n\n## Description:\n\nHelps agents query public Swedish property listings, sold prices, and property details on Booli through a browser-connected command-line tool.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and agents use this skill to look up Swedish areas, search active and sold property listings, and inspect property details without running a separate MCP server.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Installing an unverified CLI or browser extension can expose the browser environment.\n\nMitigation: Verify the @fetchproxy/cli package and ContextMint Bridge source or release before installing; limit extension site access to booli.se.\n\nRisk: Sensitive personal data in a GraphQL request is sent to Booli.\n\nMitigation: Avoid including sensitive personal data in request bodies unless you intend to send it to Booli.\n\nRisk: A successful command can still contain GraphQL errors or too few sold listings for reliable comparisons.\n\nMitigation: Check the response errors and sample count before relying on property results or market summaries.\n\n## Reference(s):\n\n- [Booli GraphQL query examples](references/graphql-queries.md)\n- [ClawHub skill release](https://clawhub.ai/chrischall/skills/booli-fpx)\n- [ContextMint Bridge source](https://github.com/nullnet-app/contextmint-bridge)\n- [ContextMint Bridge releases](https://github.com/nullnet-app/contextmint-bridge/releases)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration guidance, JSON data]\n\n**Output Format:** [Markdown guidance with shell commands and GraphQL JSON examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Property prices are in SEK and areas in m².]\n\n## Skill Version(s):\n\n2.1.8 (source: server-resolved ClawHub release)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.1.7: 4 files, 6095 bytes\n\nFiles: references/graphql-queries.md (5826b), skill-card.md (1867b), SKILL.md (4178b), _meta.json (128b)\n\nFile v2.1.7:SKILL.md\n\n---\nname: booli-fpx\ndescription: >-\n  Query booli.se (Swedish property portal) from a shell with the fpx CLI\n  (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas,\n  search for-sale and sold listings (slutpriser), and read property detail via\n  one-shot GraphQL calls through a signed-in browser tab. Use when you want\n  Booli data without the MCP, in a script, or on a machine where the MCP isn't\n  installed.\n---\n\n# Booli via fpx (no MCP)\n\nBooli fronts `www.booli.se` — including `/graphql` — with a Cloudflare managed\nchallenge that 403s any plain `curl`/Node request. `fpx` routes the request\nthrough the user's own signed-in browser tab (the ContextMint Bridge extension),\nwhich has already cleared the challenge, so the same anonymous GraphQL query\nsucceeds. No Booli login is needed — just a normal open tab.\n\nThis is the same data the `booli_*` MCP tools return (money in **SEK**, areas\nin **m²**, rooms as a number), reached with one CLI call instead of a running\nserver.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli           # provides `fpx`\nfpx profile add booli --domain booli.se  # only the fetch capability is needed\nfpx pair -p booli                        # prints a pair code → approve in ContextMint Bridge\n```\n\nRequirements: the **ContextMint Bridge** browser extension installed (from\nhttps://github.com/nullnet-app/contextmint-bridge/releases — Chrome: load the chrome zip\nunpacked; Safari isn't available yet — use Chrome for now), with an open\n`www.booli.se` tab, and its Chrome **Site access** allowing `booli.se`.\nContextMint Bridge is the fetchproxy extension renamed, same maintainer; source at\nhttps://github.com/nullnet-app/contextmint-bridge (build it, or verify a release zip with its published `.sha256`).\nPairing persists — after the first approval every later `fpx` call reuses it.\n\n## Core call\n\nEvery query is a POST of `{\"query\": \"...\", \"variables\": {...}}` to the GraphQL\nendpoint. Write the body to a file (a heredoc avoids shell-quoting the GraphQL\nstring), then send it so stdout is the GraphQL JSON, ready for `jq`:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nReady-to-run query bodies (area lookup, for-sale search, sold search, property\ndetail) with `jq` recipes are in `references/graphql-queries.md`. Exhaustive\nfield lists live in the repo at `src/graphql.ts` — the operations here are\ncompact, live-verified subsets.\n\n## The one rule: resolve the area first\n\nBooli searches take a numeric **area id** (`areaId`), never a place name.\nAlways resolve the name first, take the `id`, then search:\n\n```sh\nprintf '{\"query\":\"query($s:String!){areaSuggestionSearch(search:$s){suggestions{id displayName}}}\",\"variables\":{\"s\":\"Nacka\"}}' > /tmp/a.json\nfpx post-json 'https://www.booli.se/graphql' @/tmp/a.json -p booli \\\n  | jq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n# 76      Nacka kommun\n```\n\nPass that `id` as the search input's `areaId` (a string). The `SearchRequest`\ninput needs `areaId`, `page` (1-based), `ascending`, `excludeAncestors: true`,\n`facets: []`, `filters: [{key,value}]`, and `sort` (`\"\"` = newest). Filters\nreuse the same keys the MCP does (`minRooms`, `maxListPrice`, `objectType`, …).\n\n## Exit codes (fetch verbs)\n\n- `0` — success (a GraphQL `errors` array can still ride in a `0` body; check `jq '.errors // empty'`).\n- `2` — bridge unavailable: extension not connected or pairing pending → run `fpx pair -p booli`, confirm a booli tab is open.\n- `3` — bot wall: the tab hasn't cleared Cloudflare → open/refresh a `www.booli.se` tab and retry.\n- `4` — upstream non-2xx from Booli.\n\n## Notes\n\n- Anonymous reads only — the same public GraphQL surface as the MCP; no account\n  data. Stay within booli.se's terms.\n- `fpx health -p booli` shows bridge connection state when a call fails.\n- Property detail is keyed by **residence id** — the number in a\n  `/bostad/<id>` URL, which is a search result's `url` field, **not** `booliId`.\n- This project is developed and maintained by AI (Claude).\n\nFile v2.1.7:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"booli-fpx\",\n  \"version\": \"2.1.7\",\n  \"publishedAt\": 1791380214512\n}\n\nFile v2.1.7:references/graphql-queries.md\n\n# Booli GraphQL queries for fpx\n\nReady-to-run bodies for `fpx post-json 'https://www.booli.se/graphql' @file -p booli`.\nEach block is the full POST body (`{\"query\",\"variables\"}`). All field names and\ninput shapes are live-verified against the endpoint; the exhaustive field\nselections live in the repo at `src/graphql.ts` (these are compact subsets).\n\nEndpoint: `https://www.booli.se/graphql` · Method: POST · anonymous.\nIntrospection is disabled — probe field names by sending a query and reading the\nvalidation errors (that's how these were pinned).\n\nWrite the body to a file (heredoc avoids shell-quoting the GraphQL string),\nthen send it:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nAlways check for GraphQL errors: `jq '.errors // empty'` (an errors array can\nride in an HTTP-200 body).\n\nNumeric fields come as `FormattedValue { raw, formatted }` — select `{ raw }`\nfor the number, `{ formatted }` for the display string.\n\n---\n\n## 1. Resolve an area → areaId (do this first)\n\n`search` is free text.\n\n```json\n{\n  \"query\": \"query Areas($s: String!) { areaSuggestionSearch(search: $s) { suggestions { id displayName } } }\",\n  \"variables\": { \"s\": \"Nacka\" }\n}\n```\n\n```sh\njq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n```\n\n## 2. Search for-sale listings\n\n`input` is a `SearchRequest`: `areaId` (string, from step 1), `page` (1-based),\n`ascending`, `excludeAncestors: true`, `facets: []`, `filters: [{key,value}]`,\n`sort` (`\"\"` = newest, else `listPrice` / `listSqmPrice` / `rooms` /\n`livingArea` / `rent` / `plotArea` / `published`). `result` mixes `Listing` and\n`Project` (new-construction) nodes — take the `... on Listing` ones.\n\nFilter keys (both key and value are strings): `objectType` (`Lägenhet`, `Villa`,\n`Kedjehus-Parhus-Radhus`, `Fritidshus`, `Gård`, `Tomt/Mark`), `minRooms`/`maxRooms`,\n`minLivingArea`/`maxLivingArea`, `minPlotArea`/`maxPlotArea`,\n`minListPrice`/`maxListPrice`, `minListSqmPrice`/`maxListSqmPrice`,\n`minConstructionYear`/`maxConstructionYear`, `isNewConstruction` (`1`/`0`).\n\n```json\n{\n  \"query\": \"query SearchForSale($input: SearchRequest) { searchForSale(input: $input) { totalCount pages result { __typename ... on Listing { booliId objectType tenureForm streetAddress descriptiveAreaName url rooms { raw } livingArea { raw } rent { raw } listPrice { raw formatted } listSqmPrice { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [\n        { \"key\": \"objectType\", \"value\": \"Lägenhet\" },\n        { \"key\": \"minRooms\", \"value\": \"3\" },\n        { \"key\": \"maxListPrice\", \"value\": \"6000000\" }\n      ],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\njq -r '.data.searchForSale | \"total=\\(.totalCount) pages=\\(.pages)\", (.result[] | select(.__typename==\"Listing\") | \"\\(.booliId)\\t\\(.listPrice.formatted // \"—\")\\t\\(.streetAddress)\")'\n```\n\n## 3. Search sold listings (slutpriser — the comps)\n\nSame `SearchRequest` input; result is `... on SoldProperty`. Sold-specific\nfilter keys: `minSoldPrice`/`maxSoldPrice`, `minSoldSqmPrice`/`maxSoldSqmPrice`,\n`minSoldDate`/`maxSoldDate` (YYYYMMDD). `soldPricePercentageDiff.raw` is the\nover/under-asking % (e.g. `-20`).\n\n```json\n{\n  \"query\": \"query SearchSold($input: SearchRequest) { searchSold(input: $input) { totalCount pages result { __typename ... on SoldProperty { booliId objectType streetAddress descriptiveAreaName url soldDate soldPrice { raw formatted } soldSqmPrice { raw } listPrice { raw } soldPricePercentageDiff { raw } rooms { raw } livingArea { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [{ \"key\": \"minSoldDate\", \"value\": \"20240101\" }],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\n# Median sold price across the page (raw kronor):\njq -r '[.data.searchSold.result[] | select(.__typename==\"SoldProperty\") | .soldPrice.raw] | sort | .[length/2|floor]'\n```\n\n## 4. Property detail by residence id\n\n`residenceId` is `ID!` — the number in a `/bostad/<id>` URL (a search result's\n`url` field), **not** `booliId`. `propertyByResidenceId` returns the `Property`\ninterface; Listing/SoldProperty-only fields need inline fragments. Works for\nboth active and sold properties (check `__typename`).\n\n```json\n{\n  \"query\": \"query Detail($residenceId: ID!) { propertyByResidenceId(residenceId: $residenceId) { __typename booliId residenceId objectType tenureForm streetAddress descriptiveAreaName constructionYear url rooms { raw } livingArea { raw } plotArea { raw } rent { raw } operatingCost { raw } floor { raw } location { region { municipalityName countyName } namedAreas } ... on Listing { published isNewConstruction upcomingSale listingUrl listPrice { raw formatted } listSqmPrice { raw } agency { name url } estimate { price { raw } } } ... on SoldProperty { soldPrice { raw formatted } soldSqmPrice { raw } soldDate soldPricePercentageDiff { raw } } } }\",\n  \"variables\": { \"residenceId\": \"4370936\" }\n}\n```\n\n```sh\njq '.data.propertyByResidenceId | {type: .__typename, addr: .streetAddress, rooms: .rooms.raw, m2: .livingArea.raw, price: (.listPrice.formatted // .soldPrice.formatted), area: .location.namedAreas[0]}'\n```\n\n---\n\n## Market stats\n\nBooli's GraphQL has no market-stats query — the `booli_market_stats` tool\nderives median/average sold price, price-per-m², and average over/under-asking %\nlocally from a `searchSold` page (§3). Reproduce with `jq` over `result` as\nshown above; check the count before trusting a thin median.\n\nFile v2.1.7:skill-card.md\n\n## Description:\n\nHelps agents query public Booli property listings, sold prices, and property details from a shell without running the Booli MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and other users can look up Swedish property areas, for-sale and sold listings, and property details through an existing browser tab when they need Booli data in a script or without an MCP server.\n\n### Deployment Geography for Use:\n\nGlobal (property data covers Sweden)\n\n## Known Risks and Mitigations:\n\nRisk: The required browser extension can access sites permitted by its site-access setting.\n\nMitigation: Install the fpx CLI and ContextMint Bridge only if comfortable with them, and limit extension site access to booli.se.\n\nRisk: Automated property queries may exceed intended use of Booli's public data.\n\nMitigation: Query public property data only and comply with Booli's terms.\n\n## Reference(s):\n\n- [Booli FPX skill release](https://clawhub.ai/chrischall/skills/booli-fpx)\n- [Booli GraphQL query examples](artifact/references/graphql-queries.md)\n- [ContextMint Bridge releases](https://github.com/nullnet-app/contextmint-bridge/releases)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, JSON, Guidance]\n\n**Output Format:** [Markdown instructions and GraphQL JSON responses]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Prices are in SEK; floor area is in m².]\n\n## Skill Version(s):\n\n2.1.7 (source: ClawHub release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.1.6: 4 files, 6116 bytes\n\nFiles: references/graphql-queries.md (5826b), skill-card.md (1963b), SKILL.md (4178b), _meta.json (128b)\n\nFile v2.1.6:SKILL.md\n\n---\nname: booli-fpx\ndescription: >-\n  Query booli.se (Swedish property portal) from a shell with the fpx CLI\n  (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas,\n  search for-sale and sold listings (slutpriser), and read property detail via\n  one-shot GraphQL calls through a signed-in browser tab. Use when you want\n  Booli data without the MCP, in a script, or on a machine where the MCP isn't\n  installed.\n---\n\n# Booli via fpx (no MCP)\n\nBooli fronts `www.booli.se` — including `/graphql` — with a Cloudflare managed\nchallenge that 403s any plain `curl`/Node request. `fpx` routes the request\nthrough the user's own signed-in browser tab (the ContextMint Bridge extension),\nwhich has already cleared the challenge, so the same anonymous GraphQL query\nsucceeds. No Booli login is needed — just a normal open tab.\n\nThis is the same data the `booli_*` MCP tools return (money in **SEK**, areas\nin **m²**, rooms as a number), reached with one CLI call instead of a running\nserver.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli           # provides `fpx`\nfpx profile add booli --domain booli.se  # only the fetch capability is needed\nfpx pair -p booli                        # prints a pair code → approve in ContextMint Bridge\n```\n\nRequirements: the **ContextMint Bridge** browser extension installed (from\nhttps://github.com/nullnet-app/contextmint-bridge/releases — Chrome: load the chrome zip\nunpacked; Safari isn't available yet — use Chrome for now), with an open\n`www.booli.se` tab, and its Chrome **Site access** allowing `booli.se`.\nContextMint Bridge is the fetchproxy extension renamed, same maintainer; source at\nhttps://github.com/nullnet-app/contextmint-bridge (build it, or verify a release zip with its published `.sha256`).\nPairing persists — after the first approval every later `fpx` call reuses it.\n\n## Core call\n\nEvery query is a POST of `{\"query\": \"...\", \"variables\": {...}}` to the GraphQL\nendpoint. Write the body to a file (a heredoc avoids shell-quoting the GraphQL\nstring), then send it so stdout is the GraphQL JSON, ready for `jq`:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nReady-to-run query bodies (area lookup, for-sale search, sold search, property\ndetail) with `jq` recipes are in `references/graphql-queries.md`. Exhaustive\nfield lists live in the repo at `src/graphql.ts` — the operations here are\ncompact, live-verified subsets.\n\n## The one rule: resolve the area first\n\nBooli searches take a numeric **area id** (`areaId`), never a place name.\nAlways resolve the name first, take the `id`, then search:\n\n```sh\nprintf '{\"query\":\"query($s:String!){areaSuggestionSearch(search:$s){suggestions{id displayName}}}\",\"variables\":{\"s\":\"Nacka\"}}' > /tmp/a.json\nfpx post-json 'https://www.booli.se/graphql' @/tmp/a.json -p booli \\\n  | jq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n# 76      Nacka kommun\n```\n\nPass that `id` as the search input's `areaId` (a string). The `SearchRequest`\ninput needs `areaId`, `page` (1-based), `ascending`, `excludeAncestors: true`,\n`facets: []`, `filters: [{key,value}]`, and `sort` (`\"\"` = newest). Filters\nreuse the same keys the MCP does (`minRooms`, `maxListPrice`, `objectType`, …).\n\n## Exit codes (fetch verbs)\n\n- `0` — success (a GraphQL `errors` array can still ride in a `0` body; check `jq '.errors // empty'`).\n- `2` — bridge unavailable: extension not connected or pairing pending → run `fpx pair -p booli`, confirm a booli tab is open.\n- `3` — bot wall: the tab hasn't cleared Cloudflare → open/refresh a `www.booli.se` tab and retry.\n- `4` — upstream non-2xx from Booli.\n\n## Notes\n\n- Anonymous reads only — the same public GraphQL surface as the MCP; no account\n  data. Stay within booli.se's terms.\n- `fpx health -p booli` shows bridge connection state when a call fails.\n- Property detail is keyed by **residence id** — the number in a\n  `/bostad/<id>` URL, which is a search result's `url` field, **not** `booliId`.\n- This project is developed and maintained by AI (Claude).\n\nFile v2.1.6:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"booli-fpx\",\n  \"version\": \"2.1.6\",\n  \"publishedAt\": 1791168531041\n}\n\nFile v2.1.6:references/graphql-queries.md\n\n# Booli GraphQL queries for fpx\n\nReady-to-run bodies for `fpx post-json 'https://www.booli.se/graphql' @file -p booli`.\nEach block is the full POST body (`{\"query\",\"variables\"}`). All field names and\ninput shapes are live-verified against the endpoint; the exhaustive field\nselections live in the repo at `src/graphql.ts` (these are compact subsets).\n\nEndpoint: `https://www.booli.se/graphql` · Method: POST · anonymous.\nIntrospection is disabled — probe field names by sending a query and reading the\nvalidation errors (that's how these were pinned).\n\nWrite the body to a file (heredoc avoids shell-quoting the GraphQL string),\nthen send it:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nAlways check for GraphQL errors: `jq '.errors // empty'` (an errors array can\nride in an HTTP-200 body).\n\nNumeric fields come as `FormattedValue { raw, formatted }` — select `{ raw }`\nfor the number, `{ formatted }` for the display string.\n\n---\n\n## 1. Resolve an area → areaId (do this first)\n\n`search` is free text.\n\n```json\n{\n  \"query\": \"query Areas($s: String!) { areaSuggestionSearch(search: $s) { suggestions { id displayName } } }\",\n  \"variables\": { \"s\": \"Nacka\" }\n}\n```\n\n```sh\njq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n```\n\n## 2. Search for-sale listings\n\n`input` is a `SearchRequest`: `areaId` (string, from step 1), `page` (1-based),\n`ascending`, `excludeAncestors: true`, `facets: []`, `filters: [{key,value}]`,\n`sort` (`\"\"` = newest, else `listPrice` / `listSqmPrice` / `rooms` /\n`livingArea` / `rent` / `plotArea` / `published`). `result` mixes `Listing` and\n`Project` (new-construction) nodes — take the `... on Listing` ones.\n\nFilter keys (both key and value are strings): `objectType` (`Lägenhet`, `Villa`,\n`Kedjehus-Parhus-Radhus`, `Fritidshus`, `Gård`, `Tomt/Mark`), `minRooms`/`maxRooms`,\n`minLivingArea`/`maxLivingArea`, `minPlotArea`/`maxPlotArea`,\n`minListPrice`/`maxListPrice`, `minListSqmPrice`/`maxListSqmPrice`,\n`minConstructionYear`/`maxConstructionYear`, `isNewConstruction` (`1`/`0`).\n\n```json\n{\n  \"query\": \"query SearchForSale($input: SearchRequest) { searchForSale(input: $input) { totalCount pages result { __typename ... on Listing { booliId objectType tenureForm streetAddress descriptiveAreaName url rooms { raw } livingArea { raw } rent { raw } listPrice { raw formatted } listSqmPrice { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [\n        { \"key\": \"objectType\", \"value\": \"Lägenhet\" },\n        { \"key\": \"minRooms\", \"value\": \"3\" },\n        { \"key\": \"maxListPrice\", \"value\": \"6000000\" }\n      ],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\njq -r '.data.searchForSale | \"total=\\(.totalCount) pages=\\(.pages)\", (.result[] | select(.__typename==\"Listing\") | \"\\(.booliId)\\t\\(.listPrice.formatted // \"—\")\\t\\(.streetAddress)\")'\n```\n\n## 3. Search sold listings (slutpriser — the comps)\n\nSame `SearchRequest` input; result is `... on SoldProperty`. Sold-specific\nfilter keys: `minSoldPrice`/`maxSoldPrice`, `minSoldSqmPrice`/`maxSoldSqmPrice`,\n`minSoldDate`/`maxSoldDate` (YYYYMMDD). `soldPricePercentageDiff.raw` is the\nover/under-asking % (e.g. `-20`).\n\n```json\n{\n  \"query\": \"query SearchSold($input: SearchRequest) { searchSold(input: $input) { totalCount pages result { __typename ... on SoldProperty { booliId objectType streetAddress descriptiveAreaName url soldDate soldPrice { raw formatted } soldSqmPrice { raw } listPrice { raw } soldPricePercentageDiff { raw } rooms { raw } livingArea { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [{ \"key\": \"minSoldDate\", \"value\": \"20240101\" }],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\n# Median sold price across the page (raw kronor):\njq -r '[.data.searchSold.result[] | select(.__typename==\"SoldProperty\") | .soldPrice.raw] | sort | .[length/2|floor]'\n```\n\n## 4. Property detail by residence id\n\n`residenceId` is `ID!` — the number in a `/bostad/<id>` URL (a search result's\n`url` field), **not** `booliId`. `propertyByResidenceId` returns the `Property`\ninterface; Listing/SoldProperty-only fields need inline fragments. Works for\nboth active and sold properties (check `__typename`).\n\n```json\n{\n  \"query\": \"query Detail($residenceId: ID!) { propertyByResidenceId(residenceId: $residenceId) { __typename booliId residenceId objectType tenureForm streetAddress descriptiveAreaName constructionYear url rooms { raw } livingArea { raw } plotArea { raw } rent { raw } operatingCost { raw } floor { raw } location { region { municipalityName countyName } namedAreas } ... on Listing { published isNewConstruction upcomingSale listingUrl listPrice { raw formatted } listSqmPrice { raw } agency { name url } estimate { price { raw } } } ... on SoldProperty { soldPrice { raw formatted } soldSqmPrice { raw } soldDate soldPricePercentageDiff { raw } } } }\",\n  \"variables\": { \"residenceId\": \"4370936\" }\n}\n```\n\n```sh\njq '.data.propertyByResidenceId | {type: .__typename, addr: .streetAddress, rooms: .rooms.raw, m2: .livingArea.raw, price: (.listPrice.formatted // .soldPrice.formatted), area: .location.namedAreas[0]}'\n```\n\n---\n\n## Market stats\n\nBooli's GraphQL has no market-stats query — the `booli_market_stats` tool\nderives median/average sold price, price-per-m², and average over/under-asking %\nlocally from a `searchSold` page (§3). Reproduce with `jq` over `result` as\nshown above; check the count before trusting a thin median.\n\nFile v2.1.6:skill-card.md\n\n## Description:\n\nHelps agents query Swedish property listings, sold prices, and property details on Booli through browser-routed GraphQL calls using fpx.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and agents use this skill to look up Swedish property areas, search active and sold listings, and retrieve property details without running an MCP server.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Browser-extension routing can expose active browser sessions to the paired extension.\n\nMitigation: Verify the extension release or source, use a dedicated Chrome profile or tab with minimal sensitive sessions, and limit site access to booli.se.\n\nRisk: A small sample of sold listings can produce misleading market statistics.\n\nMitigation: Check the result count before relying on a derived median or average.\n\n## Reference(s):\n\n- [Booli GraphQL query examples](references/graphql-queries.md)\n- [Booli Fpx on ClawHub](https://clawhub.ai/chrischall/skills/booli-fpx)\n- [ContextMint Bridge source](https://github.com/nullnet-app/contextmint-bridge)\n- [ContextMint Bridge releases](https://github.com/nullnet-app/contextmint-bridge/releases)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Shell commands, Guidance]\n\n**Output Format:** [Markdown with shell and JSON examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Property prices in SEK and areas in square metres; live results depend on Booli and an available paired browser tab.]\n\n## Skill Version(s):\n\n2.1.6 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.1.5: 4 files, 6114 bytes\n\nFiles: references/graphql-queries.md (5826b), skill-card.md (1963b), SKILL.md (4178b), _meta.json (128b)\n\nFile v2.1.5:SKILL.md\n\n---\nname: booli-fpx\ndescription: >-\n  Query booli.se (Swedish property portal) from a shell with the fpx CLI\n  (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas,\n  search for-sale and sold listings (slutpriser), and read property detail via\n  one-shot GraphQL calls through a signed-in browser tab. Use when you want\n  Booli data without the MCP, in a script, or on a machine where the MCP isn't\n  installed.\n---\n\n# Booli via fpx (no MCP)\n\nBooli fronts `www.booli.se` — including `/graphql` — with a Cloudflare managed\nchallenge that 403s any plain `curl`/Node request. `fpx` routes the request\nthrough the user's own signed-in browser tab (the ContextMint Bridge extension),\nwhich has already cleared the challenge, so the same anonymous GraphQL query\nsucceeds. No Booli login is needed — just a normal open tab.\n\nThis is the same data the `booli_*` MCP tools return (money in **SEK**, areas\nin **m²**, rooms as a number), reached with one CLI call instead of a running\nserver.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli           # provides `fpx`\nfpx profile add booli --domain booli.se  # only the fetch capability is needed\nfpx pair -p booli                        # prints a pair code → approve in ContextMint Bridge\n```\n\nRequirements: the **ContextMint Bridge** browser extension installed (from\nhttps://github.com/nullnet-app/contextmint-bridge/releases — Chrome: load the chrome zip\nunpacked; Safari isn't available yet — use Chrome for now), with an open\n`www.booli.se` tab, and its Chrome **Site access** allowing `booli.se`.\nContextMint Bridge is the fetchproxy extension renamed, same maintainer; source at\nhttps://github.com/nullnet-app/contextmint-bridge (build it, or verify a release zip with its published `.sha256`).\nPairing persists — after the first approval every later `fpx` call reuses it.\n\n## Core call\n\nEvery query is a POST of `{\"query\": \"...\", \"variables\": {...}}` to the GraphQL\nendpoint. Write the body to a file (a heredoc avoids shell-quoting the GraphQL\nstring), then send it so stdout is the GraphQL JSON, ready for `jq`:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nReady-to-run query bodies (area lookup, for-sale search, sold search, property\ndetail) with `jq` recipes are in `references/graphql-queries.md`. Exhaustive\nfield lists live in the repo at `src/graphql.ts` — the operations here are\ncompact, live-verified subsets.\n\n## The one rule: resolve the area first\n\nBooli searches take a numeric **area id** (`areaId`), never a place name.\nAlways resolve the name first, take the `id`, then search:\n\n```sh\nprintf '{\"query\":\"query($s:String!){areaSuggestionSearch(search:$s){suggestions{id displayName}}}\",\"variables\":{\"s\":\"Nacka\"}}' > /tmp/a.json\nfpx post-json 'https://www.booli.se/graphql' @/tmp/a.json -p booli \\\n  | jq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n# 76      Nacka kommun\n```\n\nPass that `id` as the search input's `areaId` (a string). The `SearchRequest`\ninput needs `areaId`, `page` (1-based), `ascending`, `excludeAncestors: true`,\n`facets: []`, `filters: [{key,value}]`, and `sort` (`\"\"` = newest). Filters\nreuse the same keys the MCP does (`minRooms`, `maxListPrice`, `objectType`, …).\n\n## Exit codes (fetch verbs)\n\n- `0` — success (a GraphQL `errors` array can still ride in a `0` body; check `jq '.errors // empty'`).\n- `2` — bridge unavailable: extension not connected or pairing pending → run `fpx pair -p booli`, confirm a booli tab is open.\n- `3` — bot wall: the tab hasn't cleared Cloudflare → open/refresh a `www.booli.se` tab and retry.\n- `4` — upstream non-2xx from Booli.\n\n## Notes\n\n- Anonymous reads only — the same public GraphQL surface as the MCP; no account\n  data. Stay within booli.se's terms.\n- `fpx health -p booli` shows bridge connection state when a call fails.\n- Property detail is keyed by **residence id** — the number in a\n  `/bostad/<id>` URL, which is a search result's `url` field, **not** `booliId`.\n- This project is developed and maintained by AI (Claude).\n\nFile v2.1.5:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"booli-fpx\",\n  \"version\": \"2.1.5\",\n  \"publishedAt\": 1790991721277\n}\n\nFile v2.1.5:references/graphql-queries.md\n\n# Booli GraphQL queries for fpx\n\nReady-to-run bodies for `fpx post-json 'https://www.booli.se/graphql' @file -p booli`.\nEach block is the full POST body (`{\"query\",\"variables\"}`). All field names and\ninput shapes are live-verified against the endpoint; the exhaustive field\nselections live in the repo at `src/graphql.ts` (these are compact subsets).\n\nEndpoint: `https://www.booli.se/graphql` · Method: POST · anonymous.\nIntrospection is disabled — probe field names by sending a query and reading the\nvalidation errors (that's how these were pinned).\n\nWrite the body to a file (heredoc avoids shell-quoting the GraphQL string),\nthen send it:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nAlways check for GraphQL errors: `jq '.errors // empty'` (an errors array can\nride in an HTTP-200 body).\n\nNumeric fields come as `FormattedValue { raw, formatted }` — select `{ raw }`\nfor the number, `{ formatted }` for the display string.\n\n---\n\n## 1. Resolve an area → areaId (do this first)\n\n`search` is free text.\n\n```json\n{\n  \"query\": \"query Areas($s: String!) { areaSuggestionSearch(search: $s) { suggestions { id displayName } } }\",\n  \"variables\": { \"s\": \"Nacka\" }\n}\n```\n\n```sh\njq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n```\n\n## 2. Search for-sale listings\n\n`input` is a `SearchRequest`: `areaId` (string, from step 1), `page` (1-based),\n`ascending`, `excludeAncestors: true`, `facets: []`, `filters: [{key,value}]`,\n`sort` (`\"\"` = newest, else `listPrice` / `listSqmPrice` / `rooms` /\n`livingArea` / `rent` / `plotArea` / `published`). `result` mixes `Listing` and\n`Project` (new-construction) nodes — take the `... on Listing` ones.\n\nFilter keys (both key and value are strings): `objectType` (`Lägenhet`, `Villa`,\n`Kedjehus-Parhus-Radhus`, `Fritidshus`, `Gård`, `Tomt/Mark`), `minRooms`/`maxRooms`,\n`minLivingArea`/`maxLivingArea`, `minPlotArea`/`maxPlotArea`,\n`minListPrice`/`maxListPrice`, `minListSqmPrice`/`maxListSqmPrice`,\n`minConstructionYear`/`maxConstructionYear`, `isNewConstruction` (`1`/`0`).\n\n```json\n{\n  \"query\": \"query SearchForSale($input: SearchRequest) { searchForSale(input: $input) { totalCount pages result { __typename ... on Listing { booliId objectType tenureForm streetAddress descriptiveAreaName url rooms { raw } livingArea { raw } rent { raw } listPrice { raw formatted } listSqmPrice { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [\n        { \"key\": \"objectType\", \"value\": \"Lägenhet\" },\n        { \"key\": \"minRooms\", \"value\": \"3\" },\n        { \"key\": \"maxListPrice\", \"value\": \"6000000\" }\n      ],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\njq -r '.data.searchForSale | \"total=\\(.totalCount) pages=\\(.pages)\", (.result[] | select(.__typename==\"Listing\") | \"\\(.booliId)\\t\\(.listPrice.formatted // \"—\")\\t\\(.streetAddress)\")'\n```\n\n## 3. Search sold listings (slutpriser — the comps)\n\nSame `SearchRequest` input; result is `... on SoldProperty`. Sold-specific\nfilter keys: `minSoldPrice`/`maxSoldPrice`, `minSoldSqmPrice`/`maxSoldSqmPrice`,\n`minSoldDate`/`maxSoldDate` (YYYYMMDD). `soldPricePercentageDiff.raw` is the\nover/under-asking % (e.g. `-20`).\n\n```json\n{\n  \"query\": \"query SearchSold($input: SearchRequest) { searchSold(input: $input) { totalCount pages result { __typename ... on SoldProperty { booliId objectType streetAddress descriptiveAreaName url soldDate soldPrice { raw formatted } soldSqmPrice { raw } listPrice { raw } soldPricePercentageDiff { raw } rooms { raw } livingArea { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [{ \"key\": \"minSoldDate\", \"value\": \"20240101\" }],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\n# Median sold price across the page (raw kronor):\njq -r '[.data.searchSold.result[] | select(.__typename==\"SoldProperty\") | .soldPrice.raw] | sort | .[length/2|floor]'\n```\n\n## 4. Property detail by residence id\n\n`residenceId` is `ID!` — the number in a `/bostad/<id>` URL (a search result's\n`url` field), **not** `booliId`. `propertyByResidenceId` returns the `Property`\ninterface; Listing/SoldProperty-only fields need inline fragments. Works for\nboth active and sold properties (check `__typename`).\n\n```json\n{\n  \"query\": \"query Detail($residenceId: ID!) { propertyByResidenceId(residenceId: $residenceId) { __typename booliId residenceId objectType tenureForm streetAddress descriptiveAreaName constructionYear url rooms { raw } livingArea { raw } plotArea { raw } rent { raw } operatingCost { raw } floor { raw } location { region { municipalityName countyName } namedAreas } ... on Listing { published isNewConstruction upcomingSale listingUrl listPrice { raw formatted } listSqmPrice { raw } agency { name url } estimate { price { raw } } } ... on SoldProperty { soldPrice { raw formatted } soldSqmPrice { raw } soldDate soldPricePercentageDiff { raw } } } }\",\n  \"variables\": { \"residenceId\": \"4370936\" }\n}\n```\n\n```sh\njq '.data.propertyByResidenceId | {type: .__typename, addr: .streetAddress, rooms: .rooms.raw, m2: .livingArea.raw, price: (.listPrice.formatted // .soldPrice.formatted), area: .location.namedAreas[0]}'\n```\n\n---\n\n## Market stats\n\nBooli's GraphQL has no market-stats query — the `booli_market_stats` tool\nderives median/average sold price, price-per-m², and average over/under-asking %\nlocally from a `searchSold` page (§3). Reproduce with `jq` over `result` as\nshown above; check the count before trusting a thin median.\n\nFile v2.1.5:skill-card.md\n\n## Description:\n\nGuides agents in querying public Booli property listings, sold prices, and property details through a browser-connected command-line tool without running an MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and agents use this skill to resolve Swedish areas, search Booli listings and sold properties, and inspect property details from a shell or script without a running MCP server.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The browser bridge can send requests through an open Booli tab after pairing.\n\nMitigation: Verify the @fetchproxy/cli package and ContextMint Bridge release, scope the profile to booli.se, and approve pairing only if comfortable with requests through that tab.\n\nRisk: GraphQL errors can appear in a successful HTTP response.\n\nMitigation: Check the response's errors array before using listing or property data.\n\n## Reference(s):\n\n- [Booli GraphQL query examples](references/graphql-queries.md)\n- [ContextMint Bridge source](https://github.com/nullnet-app/contextmint-bridge)\n- [ContextMint Bridge releases](https://github.com/nullnet-app/contextmint-bridge/releases)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Code, Configuration instructions, Guidance]\n\n**Output Format:** [Markdown with shell commands, GraphQL JSON examples, and jq recipes]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Booli responses are JSON; prices are in SEK and areas are in square meters.]\n\n## Skill Version(s):\n\n2.1.5 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.1.4: 4 files, 6087 bytes\n\nFiles: references/graphql-queries.md (5826b), skill-card.md (1853b), SKILL.md (4178b), _meta.json (128b)\n\nFile v2.1.4:SKILL.md\n\n---\nname: booli-fpx\ndescription: >-\n  Query booli.se (Swedish property portal) from a shell with the fpx CLI\n  (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas,\n  search for-sale and sold listings (slutpriser), and read property detail via\n  one-shot GraphQL calls through a signed-in browser tab. Use when you want\n  Booli data without the MCP, in a script, or on a machine where the MCP isn't\n  installed.\n---\n\n# Booli via fpx (no MCP)\n\nBooli fronts `www.booli.se` — including `/graphql` — with a Cloudflare managed\nchallenge that 403s any plain `curl`/Node request. `fpx` routes the request\nthrough the user's own signed-in browser tab (the ContextMint Bridge extension),\nwhich has already cleared the challenge, so the same anonymous GraphQL query\nsucceeds. No Booli login is needed — just a normal open tab.\n\nThis is the same data the `booli_*` MCP tools return (money in **SEK**, areas\nin **m²**, rooms as a number), reached with one CLI call instead of a running\nserver.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli           # provides `fpx`\nfpx profile add booli --domain booli.se  # only the fetch capability is needed\nfpx pair -p booli                        # prints a pair code → approve in ContextMint Bridge\n```\n\nRequirements: the **ContextMint Bridge** browser extension installed (from\nhttps://github.com/nullnet-app/contextmint-bridge/releases — Chrome: load the chrome zip\nunpacked; Safari isn't available yet — use Chrome for now), with an open\n`www.booli.se` tab, and its Chrome **Site access** allowing `booli.se`.\nContextMint Bridge is the fetchproxy extension renamed, same maintainer; source at\nhttps://github.com/nullnet-app/contextmint-bridge (build it, or verify a release zip with its published `.sha256`).\nPairing persists — after the first approval every later `fpx` call reuses it.\n\n## Core call\n\nEvery query is a POST of `{\"query\": \"...\", \"variables\": {...}}` to the GraphQL\nendpoint. Write the body to a file (a heredoc avoids shell-quoting the GraphQL\nstring), then send it so stdout is the GraphQL JSON, ready for `jq`:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nReady-to-run query bodies (area lookup, for-sale search, sold search, property\ndetail) with `jq` recipes are in `references/graphql-queries.md`. Exhaustive\nfield lists live in the repo at `src/graphql.ts` — the operations here are\ncompact, live-verified subsets.\n\n## The one rule: resolve the area first\n\nBooli searches take a numeric **area id** (`areaId`), never a place name.\nAlways resolve the name first, take the `id`, then search:\n\n```sh\nprintf '{\"query\":\"query($s:String!){areaSuggestionSearch(search:$s){suggestions{id displayName}}}\",\"variables\":{\"s\":\"Nacka\"}}' > /tmp/a.json\nfpx post-json 'https://www.booli.se/graphql' @/tmp/a.json -p booli \\\n  | jq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n# 76      Nacka kommun\n```\n\nPass that `id` as the search input's `areaId` (a string). The `SearchRequest`\ninput needs `areaId`, `page` (1-based), `ascending`, `excludeAncestors: true`,\n`facets: []`, `filters: [{key,value}]`, and `sort` (`\"\"` = newest). Filters\nreuse the same keys the MCP does (`minRooms`, `maxListPrice`, `objectType`, …).\n\n## Exit codes (fetch verbs)\n\n- `0` — success (a GraphQL `errors` array can still ride in a `0` body; check `jq '.errors // empty'`).\n- `2` — bridge unavailable: extension not connected or pairing pending → run `fpx pair -p booli`, confirm a booli tab is open.\n- `3` — bot wall: the tab hasn't cleared Cloudflare → open/refresh a `www.booli.se` tab and retry.\n- `4` — upstream non-2xx from Booli.\n\n## Notes\n\n- Anonymous reads only — the same public GraphQL surface as the MCP; no account\n  data. Stay within booli.se's terms.\n- `fpx health -p booli` shows bridge connection state when a call fails.\n- Property detail is keyed by **residence id** — the number in a\n  `/bostad/<id>` URL, which is a search result's `url` field, **not** `booliId`.\n- This project is developed and maintained by AI (Claude).\n\nFile v2.1.4:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"booli-fpx\",\n  \"version\": \"2.1.4\",\n  \"publishedAt\": 1790603739577\n}\n\nFile v2.1.4:references/graphql-queries.md\n\n# Booli GraphQL queries for fpx\n\nReady-to-run bodies for `fpx post-json 'https://www.booli.se/graphql' @file -p booli`.\nEach block is the full POST body (`{\"query\",\"variables\"}`). All field names and\ninput shapes are live-verified against the endpoint; the exhaustive field\nselections live in the repo at `src/graphql.ts` (these are compact subsets).\n\nEndpoint: `https://www.booli.se/graphql` · Method: POST · anonymous.\nIntrospection is disabled — probe field names by sending a query and reading the\nvalidation errors (that's how these were pinned).\n\nWrite the body to a file (heredoc avoids shell-quoting the GraphQL string),\nthen send it:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nAlways check for GraphQL errors: `jq '.errors // empty'` (an errors array can\nride in an HTTP-200 body).\n\nNumeric fields come as `FormattedValue { raw, formatted }` — select `{ raw }`\nfor the number, `{ formatted }` for the display string.\n\n---\n\n## 1. Resolve an area → areaId (do this first)\n\n`search` is free text.\n\n```json\n{\n  \"query\": \"query Areas($s: String!) { areaSuggestionSearch(search: $s) { suggestions { id displayName } } }\",\n  \"variables\": { \"s\": \"Nacka\" }\n}\n```\n\n```sh\njq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n```\n\n## 2. Search for-sale listings\n\n`input` is a `SearchRequest`: `areaId` (string, from step 1), `page` (1-based),\n`ascending`, `excludeAncestors: true`, `facets: []`, `filters: [{key,value}]`,\n`sort` (`\"\"` = newest, else `listPrice` / `listSqmPrice` / `rooms` /\n`livingArea` / `rent` / `plotArea` / `published`). `result` mixes `Listing` and\n`Project` (new-construction) nodes — take the `... on Listing` ones.\n\nFilter keys (both key and value are strings): `objectType` (`Lägenhet`, `Villa`,\n`Kedjehus-Parhus-Radhus`, `Fritidshus`, `Gård`, `Tomt/Mark`), `minRooms`/`maxRooms`,\n`minLivingArea`/`maxLivingArea`, `minPlotArea`/`maxPlotArea`,\n`minListPrice`/`maxListPrice`, `minListSqmPrice`/`maxListSqmPrice`,\n`minConstructionYear`/`maxConstructionYear`, `isNewConstruction` (`1`/`0`).\n\n```json\n{\n  \"query\": \"query SearchForSale($input: SearchRequest) { searchForSale(input: $input) { totalCount pages result { __typename ... on Listing { booliId objectType tenureForm streetAddress descriptiveAreaName url rooms { raw } livingArea { raw } rent { raw } listPrice { raw formatted } listSqmPrice { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [\n        { \"key\": \"objectType\", \"value\": \"Lägenhet\" },\n        { \"key\": \"minRooms\", \"value\": \"3\" },\n        { \"key\": \"maxListPrice\", \"value\": \"6000000\" }\n      ],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\njq -r '.data.searchForSale | \"total=\\(.totalCount) pages=\\(.pages)\", (.result[] | select(.__typename==\"Listing\") | \"\\(.booliId)\\t\\(.listPrice.formatted // \"—\")\\t\\(.streetAddress)\")'\n```\n\n## 3. Search sold listings (slutpriser — the comps)\n\nSame `SearchRequest` input; result is `... on SoldProperty`. Sold-specific\nfilter keys: `minSoldPrice`/`maxSoldPrice`, `minSoldSqmPrice`/`maxSoldSqmPrice`,\n`minSoldDate`/`maxSoldDate` (YYYYMMDD). `soldPricePercentageDiff.raw` is the\nover/under-asking % (e.g. `-20`).\n\n```json\n{\n  \"query\": \"query SearchSold($input: SearchRequest) { searchSold(input: $input) { totalCount pages result { __typename ... on SoldProperty { booliId objectType streetAddress descriptiveAreaName url soldDate soldPrice { raw formatted } soldSqmPrice { raw } listPrice { raw } soldPricePercentageDiff { raw } rooms { raw } livingArea { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [{ \"key\": \"minSoldDate\", \"value\": \"20240101\" }],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\n# Median sold price across the page (raw kronor):\njq -r '[.data.searchSold.result[] | select(.__typename==\"SoldProperty\") | .soldPrice.raw] | sort | .[length/2|floor]'\n```\n\n## 4. Property detail by residence id\n\n`residenceId` is `ID!` — the number in a `/bostad/<id>` URL (a search result's\n`url` field), **not** `booliId`. `propertyByResidenceId` returns the `Property`\ninterface; Listing/SoldProperty-only fields need inline fragments. Works for\nboth active and sold properties (check `__typename`).\n\n```json\n{\n  \"query\": \"query Detail($residenceId: ID!) { propertyByResidenceId(residenceId: $residenceId) { __typename booliId residenceId objectType tenureForm streetAddress descriptiveAreaName constructionYear url rooms { raw } livingArea { raw } plotArea { raw } rent { raw } operatingCost { raw } floor { raw } location { region { municipalityName countyName } namedAreas } ... on Listing { published isNewConstruction upcomingSale listingUrl listPrice { raw formatted } listSqmPrice { raw } agency { name url } estimate { price { raw } } } ... on SoldProperty { soldPrice { raw formatted } soldSqmPrice { raw } soldDate soldPricePercentageDiff { raw } } } }\",\n  \"variables\": { \"residenceId\": \"4370936\" }\n}\n```\n\n```sh\njq '.data.propertyByResidenceId | {type: .__typename, addr: .streetAddress, rooms: .rooms.raw, m2: .livingArea.raw, price: (.listPrice.formatted // .soldPrice.formatted), area: .location.namedAreas[0]}'\n```\n\n---\n\n## Market stats\n\nBooli's GraphQL has no market-stats query — the `booli_market_stats` tool\nderives median/average sold price, price-per-m², and average over/under-asking %\nlocally from a `searchSold` page (§3). Reproduce with `jq` over `result` as\nshown above; check the count before trusting a thin median.\n\nFile v2.1.4:skill-card.md\n\n## Description:\n\nGuides agents through querying public Booli property listings, sold prices, and property details using the fpx CLI and a paired browser tab instead of a running MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and agents use this skill to resolve Swedish areas and retrieve public for-sale listings, sold-property comparisons, and property details from Booli through a browser-assisted CLI workflow.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The CLI and browser extension run requests through the user's browser context.\n\nMitigation: Trust the CLI and extension before installation, verify the extension source or release hash where possible, restrict the profile and site access to booli.se, and avoid sensitive logged-in sites.\n\n## Reference(s):\n\n- [ClawHub skill release](https://clawhub.ai/chrischall/skills/booli-fpx)\n- [Booli GraphQL query examples](references/graphql-queries.md)\n- [ContextMint Bridge source and releases](https://github.com/nullnet-app/contextmint-bridge)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration instructions, Guidance]\n\n**Output Format:** [Markdown with shell commands and JSON query examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Booli results are returned as JSON; prices are in SEK and area measurements are in square meters.]\n\n## Skill Version(s):\n\n2.1.4 (source: ClawHub release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.1.3: 4 files, 6037 bytes\n\nFiles: references/graphql-queries.md (5826b), skill-card.md (2187b), SKILL.md (3800b), _meta.json (128b)\n\nFile v2.1.3:SKILL.md\n\n---\nname: booli-fpx\ndescription: >-\n  Query booli.se (Swedish property portal) from a shell with the fpx CLI\n  (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas,\n  search for-sale and sold listings (slutpriser), and read property detail via\n  one-shot GraphQL calls through a signed-in browser tab. Use when you want\n  Booli data without the MCP, in a script, or on a machine where the MCP isn't\n  installed.\n---\n\n# Booli via fpx (no MCP)\n\nBooli fronts `www.booli.se` — including `/graphql` — with a Cloudflare managed\nchallenge that 403s any plain `curl`/Node request. `fpx` routes the request\nthrough the user's own signed-in browser tab (the Transporter extension),\nwhich has already cleared the challenge, so the same anonymous GraphQL query\nsucceeds. No Booli login is needed — just a normal open tab.\n\nThis is the same data the `booli_*` MCP tools return (money in **SEK**, areas\nin **m²**, rooms as a number), reached with one CLI call instead of a running\nserver.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli           # provides `fpx`\nfpx profile add booli --domain booli.se  # only the fetch capability is needed\nfpx pair -p booli                        # prints a pair code → approve in Transporter\n```\n\nRequirements: the **Transporter** browser extension installed, with an open\n`www.booli.se` tab, and its Chrome **Site access** allowing `booli.se`.\nPairing persists — after the first approval every later `fpx` call reuses it.\n\n## Core call\n\nEvery query is a POST of `{\"query\": \"...\", \"variables\": {...}}` to the GraphQL\nendpoint. Write the body to a file (a heredoc avoids shell-quoting the GraphQL\nstring), then send it so stdout is the GraphQL JSON, ready for `jq`:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nReady-to-run query bodies (area lookup, for-sale search, sold search, property\ndetail) with `jq` recipes are in `references/graphql-queries.md`. Exhaustive\nfield lists live in the repo at `src/graphql.ts` — the operations here are\ncompact, live-verified subsets.\n\n## The one rule: resolve the area first\n\nBooli searches take a numeric **area id** (`areaId`), never a place name.\nAlways resolve the name first, take the `id`, then search:\n\n```sh\nprintf '{\"query\":\"query($s:String!){areaSuggestionSearch(search:$s){suggestions{id displayName}}}\",\"variables\":{\"s\":\"Nacka\"}}' > /tmp/a.json\nfpx post-json 'https://www.booli.se/graphql' @/tmp/a.json -p booli \\\n  | jq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n# 76      Nacka kommun\n```\n\nPass that `id` as the search input's `areaId` (a string). The `SearchRequest`\ninput needs `areaId`, `page` (1-based), `ascending`, `excludeAncestors: true`,\n`facets: []`, `filters: [{key,value}]`, and `sort` (`\"\"` = newest). Filters\nreuse the same keys the MCP does (`minRooms`, `maxListPrice`, `objectType`, …).\n\n## Exit codes (fetch verbs)\n\n- `0` — success (a GraphQL `errors` array can still ride in a `0` body; check `jq '.errors // empty'`).\n- `2` — bridge unavailable: extension not connected or pairing pending → run `fpx pair -p booli`, confirm a booli tab is open.\n- `3` — bot wall: the tab hasn't cleared Cloudflare → open/refresh a `www.booli.se` tab and retry.\n- `4` — upstream non-2xx from Booli.\n\n## Notes\n\n- Anonymous reads only — the same public GraphQL surface as the MCP; no account\n  data. Stay within booli.se's terms.\n- `fpx health -p booli` shows bridge connection state when a call fails.\n- Property detail is keyed by **residence id** — the number in a\n  `/bostad/<id>` URL, which is a search result's `url` field, **not** `booliId`.\n- This project is developed and maintained by AI (Claude).\n\nFile v2.1.3:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"booli-fpx\",\n  \"version\": \"2.1.3\",\n  \"publishedAt\": 1790199514312\n}\n\nFile v2.1.3:references/graphql-queries.md\n\n# Booli GraphQL queries for fpx\n\nReady-to-run bodies for `fpx post-json 'https://www.booli.se/graphql' @file -p booli`.\nEach block is the full POST body (`{\"query\",\"variables\"}`). All field names and\ninput shapes are live-verified against the endpoint; the exhaustive field\nselections live in the repo at `src/graphql.ts` (these are compact subsets).\n\nEndpoint: `https://www.booli.se/graphql` · Method: POST · anonymous.\nIntrospection is disabled — probe field names by sending a query and reading the\nvalidation errors (that's how these were pinned).\n\nWrite the body to a file (heredoc avoids shell-quoting the GraphQL string),\nthen send it:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nAlways check for GraphQL errors: `jq '.errors // empty'` (an errors array can\nride in an HTTP-200 body).\n\nNumeric fields come as `FormattedValue { raw, formatted }` — select `{ raw }`\nfor the number, `{ formatted }` for the display string.\n\n---\n\n## 1. Resolve an area → areaId (do this first)\n\n`search` is free text.\n\n```json\n{\n  \"query\": \"query Areas($s: String!) { areaSuggestionSearch(search: $s) { suggestions { id displayName } } }\",\n  \"variables\": { \"s\": \"Nacka\" }\n}\n```\n\n```sh\njq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n```\n\n## 2. Search for-sale listings\n\n`input` is a `SearchRequest`: `areaId` (string, from step 1), `page` (1-based),\n`ascending`, `excludeAncestors: true`, `facets: []`, `filters: [{key,value}]`,\n`sort` (`\"\"` = newest, else `listPrice` / `listSqmPrice` / `rooms` /\n`livingArea` / `rent` / `plotArea` / `published`). `result` mixes `Listing` and\n`Project` (new-construction) nodes — take the `... on Listing` ones.\n\nFilter keys (both key and value are strings): `objectType` (`Lägenhet`, `Villa`,\n`Kedjehus-Parhus-Radhus`, `Fritidshus`, `Gård`, `Tomt/Mark`), `minRooms`/`maxRooms`,\n`minLivingArea`/`maxLivingArea`, `minPlotArea`/`maxPlotArea`,\n`minListPrice`/`maxListPrice`, `minListSqmPrice`/`maxListSqmPrice`,\n`minConstructionYear`/`maxConstructionYear`, `isNewConstruction` (`1`/`0`).\n\n```json\n{\n  \"query\": \"query SearchForSale($input: SearchRequest) { searchForSale(input: $input) { totalCount pages result { __typename ... on Listing { booliId objectType tenureForm streetAddress descriptiveAreaName url rooms { raw } livingArea { raw } rent { raw } listPrice { raw formatted } listSqmPrice { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [\n        { \"key\": \"objectType\", \"value\": \"Lägenhet\" },\n        { \"key\": \"minRooms\", \"value\": \"3\" },\n        { \"key\": \"maxListPrice\", \"value\": \"6000000\" }\n      ],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\njq -r '.data.searchForSale | \"total=\\(.totalCount) pages=\\(.pages)\", (.result[] | select(.__typename==\"Listing\") | \"\\(.booliId)\\t\\(.listPrice.formatted // \"—\")\\t\\(.streetAddress)\")'\n```\n\n## 3. Search sold listings (slutpriser — the comps)\n\nSame `SearchRequest` input; result is `... on SoldProperty`. Sold-specific\nfilter keys: `minSoldPrice`/`maxSoldPrice`, `minSoldSqmPrice`/`maxSoldSqmPrice`,\n`minSoldDate`/`maxSoldDate` (YYYYMMDD). `soldPricePercentageDiff.raw` is the\nover/under-asking % (e.g. `-20`).\n\n```json\n{\n  \"query\": \"query SearchSold($input: SearchRequest) { searchSold(input: $input) { totalCount pages result { __typename ... on SoldProperty { booliId objectType streetAddress descriptiveAreaName url soldDate soldPrice { raw formatted } soldSqmPrice { raw } listPrice { raw } soldPricePercentageDiff { raw } rooms { raw } livingArea { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [{ \"key\": \"minSoldDate\", \"value\": \"20240101\" }],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\n# Median sold price across the page (raw kronor):\njq -r '[.data.searchSold.result[] | select(.__typename==\"SoldProperty\") | .soldPrice.raw] | sort | .[length/2|floor]'\n```\n\n## 4. Property detail by residence id\n\n`residenceId` is `ID!` — the number in a `/bostad/<id>` URL (a search result's\n`url` field), **not** `booliId`. `propertyByResidenceId` returns the `Property`\ninterface; Listing/SoldProperty-only fields need inline fragments. Works for\nboth active and sold properties (check `__typename`).\n\n```json\n{\n  \"query\": \"query Detail($residenceId: ID!) { propertyByResidenceId(residenceId: $residenceId) { __typename booliId residenceId objectType tenureForm streetAddress descriptiveAreaName constructionYear url rooms { raw } livingArea { raw } plotArea { raw } rent { raw } operatingCost { raw } floor { raw } location { region { municipalityName countyName } namedAreas } ... on Listing { published isNewConstruction upcomingSale listingUrl listPrice { raw formatted } listSqmPrice { raw } agency { name url } estimate { price { raw } } } ... on SoldProperty { soldPrice { raw formatted } soldSqmPrice { raw } soldDate soldPricePercentageDiff { raw } } } }\",\n  \"variables\": { \"residenceId\": \"4370936\" }\n}\n```\n\n```sh\njq '.data.propertyByResidenceId | {type: .__typename, addr: .streetAddress, rooms: .rooms.raw, m2: .livingArea.raw, price: (.listPrice.formatted // .soldPrice.formatted), area: .location.namedAreas[0]}'\n```\n\n---\n\n## Market stats\n\nBooli's GraphQL has no market-stats query — the `booli_market_stats` tool\nderives median/average sold price, price-per-m², and average over/under-asking %\nlocally from a `searchSold` page (§3). Reproduce with `jq` over `result` as\nshown above; check the count before trusting a thin median.\n\nFile v2.1.3:skill-card.md\n\n## Description:\n\nQuery booli.se from a shell with the fpx CLI to resolve areas, search for-sale and sold listings, and read property detail through one-shot GraphQL calls using a signed-in browser tab.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and analysts use this skill to retrieve public Booli property data from shell workflows without running the booli-mcp server. It supports area lookup, active listing search, sold listing search, and property detail queries.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill relies on browser-tab routing and persistent fpx pairing to fetch Booli data.\n\nMitigation: Install the fpx CLI and Transporter extension only if this routing model is acceptable, keep the profile scoped to booli.se, and review pairing before use.\n\nRisk: Booli GraphQL responses may include errors even when the HTTP request succeeds.\n\nMitigation: Check the response for a GraphQL errors array before relying on returned property data.\n\nRisk: Use of public Booli data may be subject to Booli's terms of use.\n\nMitigation: Use the skill only for permitted public-data workflows and follow Booli's terms of use.\n\n## Reference(s):\n\n- [Booli GraphQL queries for fpx](references/graphql-queries.md)\n- [Booli GraphQL endpoint](https://www.booli.se/graphql)\n- [ClawHub skill page](https://clawhub.ai/chrischall/skills/booli-fpx)\n\n## Skill Output:\n\n**Output Type(s):** [Guidance, Shell commands, Configuration, JSON, Markdown]\n\n**Output Format:** [Markdown with inline shell commands and JSON GraphQL request bodies]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Guides agents to produce fpx commands and jq recipes for public Booli property data.]\n\n## Skill Version(s):\n\n2.1.3 (source: release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.1.2: 4 files, 6049 bytes\n\nFiles: references/graphql-queries.md (5826b), skill-card.md (2183b), SKILL.md (3800b), _meta.json (128b)\n\nFile v2.1.2:SKILL.md\n\n---\nname: booli-fpx\ndescription: >-\n  Query booli.se (Swedish property portal) from a shell with the fpx CLI\n  (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas,\n  search for-sale and sold listings (slutpriser), and read property detail via\n  one-shot GraphQL calls through a signed-in browser tab. Use when you want\n  Booli data without the MCP, in a script, or on a machine where the MCP isn't\n  installed.\n---\n\n# Booli via fpx (no MCP)\n\nBooli fronts `www.booli.se` — including `/graphql` — with a Cloudflare managed\nchallenge that 403s any plain `curl`/Node request. `fpx` routes the request\nthrough the user's own signed-in browser tab (the Transporter extension),\nwhich has already cleared the challenge, so the same anonymous GraphQL query\nsucceeds. No Booli login is needed — just a normal open tab.\n\nThis is the same data the `booli_*` MCP tools return (money in **SEK**, areas\nin **m²**, rooms as a number), reached with one CLI call instead of a running\nserver.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli           # provides `fpx`\nfpx profile add booli --domain booli.se  # only the fetch capability is needed\nfpx pair -p booli                        # prints a pair code → approve in Transporter\n```\n\nRequirements: the **Transporter** browser extension installed, with an open\n`www.booli.se` tab, and its Chrome **Site access** allowing `booli.se`.\nPairing persists — after the first approval every later `fpx` call reuses it.\n\n## Core call\n\nEvery query is a POST of `{\"query\": \"...\", \"variables\": {...}}` to the GraphQL\nendpoint. Write the body to a file (a heredoc avoids shell-quoting the GraphQL\nstring), then send it so stdout is the GraphQL JSON, ready for `jq`:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nReady-to-run query bodies (area lookup, for-sale search, sold search, property\ndetail) with `jq` recipes are in `references/graphql-queries.md`. Exhaustive\nfield lists live in the repo at `src/graphql.ts` — the operations here are\ncompact, live-verified subsets.\n\n## The one rule: resolve the area first\n\nBooli searches take a numeric **area id** (`areaId`), never a place name.\nAlways resolve the name first, take the `id`, then search:\n\n```sh\nprintf '{\"query\":\"query($s:String!){areaSuggestionSearch(search:$s){suggestions{id displayName}}}\",\"variables\":{\"s\":\"Nacka\"}}' > /tmp/a.json\nfpx post-json 'https://www.booli.se/graphql' @/tmp/a.json -p booli \\\n  | jq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n# 76      Nacka kommun\n```\n\nPass that `id` as the search input's `areaId` (a string). The `SearchRequest`\ninput needs `areaId`, `page` (1-based), `ascending`, `excludeAncestors: true`,\n`facets: []`, `filters: [{key,value}]`, and `sort` (`\"\"` = newest). Filters\nreuse the same keys the MCP does (`minRooms`, `maxListPrice`, `objectType`, …).\n\n## Exit codes (fetch verbs)\n\n- `0` — success (a GraphQL `errors` array can still ride in a `0` body; check `jq '.errors // empty'`).\n- `2` — bridge unavailable: extension not connected or pairing pending → run `fpx pair -p booli`, confirm a booli tab is open.\n- `3` — bot wall: the tab hasn't cleared Cloudflare → open/refresh a `www.booli.se` tab and retry.\n- `4` — upstream non-2xx from Booli.\n\n## Notes\n\n- Anonymous reads only — the same public GraphQL surface as the MCP; no account\n  data. Stay within booli.se's terms.\n- `fpx health -p booli` shows bridge connection state when a call fails.\n- Property detail is keyed by **residence id** — the number in a\n  `/bostad/<id>` URL, which is a search result's `url` field, **not** `booliId`.\n- This project is developed and maintained by AI (Claude).\n\nFile v2.1.2:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"booli-fpx\",\n  \"version\": \"2.1.2\",\n  \"publishedAt\": 1790178077809\n}\n\nFile v2.1.2:references/graphql-queries.md\n\n# Booli GraphQL queries for fpx\n\nReady-to-run bodies for `fpx post-json 'https://www.booli.se/graphql' @file -p booli`.\nEach block is the full POST body (`{\"query\",\"variables\"}`). All field names and\ninput shapes are live-verified against the endpoint; the exhaustive field\nselections live in the repo at `src/graphql.ts` (these are compact subsets).\n\nEndpoint: `https://www.booli.se/graphql` · Method: POST · anonymous.\nIntrospection is disabled — probe field names by sending a query and reading the\nvalidation errors (that's how these were pinned).\n\nWrite the body to a file (heredoc avoids shell-quoting the GraphQL string),\nthen send it:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nAlways check for GraphQL errors: `jq '.errors // empty'` (an errors array can\nride in an HTTP-200 body).\n\nNumeric fields come as `FormattedValue { raw, formatted }` — select `{ raw }`\nfor the number, `{ formatted }` for the display string.\n\n---\n\n## 1. Resolve an area → areaId (do this first)\n\n`search` is free text.\n\n```json\n{\n  \"query\": \"query Areas($s: String!) { areaSuggestionSearch(search: $s) { suggestions { id displayName } } }\",\n  \"variables\": { \"s\": \"Nacka\" }\n}\n```\n\n```sh\njq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n```\n\n## 2. Search for-sale listings\n\n`input` is a `SearchRequest`: `areaId` (string, from step 1), `page` (1-based),\n`ascending`, `excludeAncestors: true`, `facets: []`, `filters: [{key,value}]`,\n`sort` (`\"\"` = newest, else `listPrice` / `listSqmPrice` / `rooms` /\n`livingArea` / `rent` / `plotArea` / `published`). `result` mixes `Listing` and\n`Project` (new-construction) nodes — take the `... on Listing` ones.\n\nFilter keys (both key and value are strings): `objectType` (`Lägenhet`, `Villa`,\n`Kedjehus-Parhus-Radhus`, `Fritidshus`, `Gård`, `Tomt/Mark`), `minRooms`/`maxRooms`,\n`minLivingArea`/`maxLivingArea`, `minPlotArea`/`maxPlotArea`,\n`minListPrice`/`maxListPrice`, `minListSqmPrice`/`maxListSqmPrice`,\n`minConstructionYear`/`maxConstructionYear`, `isNewConstruction` (`1`/`0`).\n\n```json\n{\n  \"query\": \"query SearchForSale($input: SearchRequest) { searchForSale(input: $input) { totalCount pages result { __typename ... on Listing { booliId objectType tenureForm streetAddress descriptiveAreaName url rooms { raw } livingArea { raw } rent { raw } listPrice { raw formatted } listSqmPrice { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [\n        { \"key\": \"objectType\", \"value\": \"Lägenhet\" },\n        { \"key\": \"minRooms\", \"value\": \"3\" },\n        { \"key\": \"maxListPrice\", \"value\": \"6000000\" }\n      ],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\njq -r '.data.searchForSale | \"total=\\(.totalCount) pages=\\(.pages)\", (.result[] | select(.__typename==\"Listing\") | \"\\(.booliId)\\t\\(.listPrice.formatted // \"—\")\\t\\(.streetAddress)\")'\n```\n\n## 3. Search sold listings (slutpriser — the comps)\n\nSame `SearchRequest` input; result is `... on SoldProperty`. Sold-specific\nfilter keys: `minSoldPrice`/`maxSoldPrice`, `minSoldSqmPrice`/`maxSoldSqmPrice`,\n`minSoldDate`/`maxSoldDate` (YYYYMMDD). `soldPricePercentageDiff.raw` is the\nover/under-asking % (e.g. `-20`).\n\n```json\n{\n  \"query\": \"query SearchSold($input: SearchRequest) { searchSold(input: $input) { totalCount pages result { __typename ... on SoldProperty { booliId objectType streetAddress descriptiveAreaName url soldDate soldPrice { raw formatted } soldSqmPrice { raw } listPrice { raw } soldPricePercentageDiff { raw } rooms { raw } livingArea { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [{ \"key\": \"minSoldDate\", \"value\": \"20240101\" }],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\n# Median sold price across the page (raw kronor):\njq -r '[.data.searchSold.result[] | select(.__typename==\"SoldProperty\") | .soldPrice.raw] | sort | .[length/2|floor]'\n```\n\n## 4. Property detail by residence id\n\n`residenceId` is `ID!` — the number in a `/bostad/<id>` URL (a search result's\n`url` field), **not** `booliId`. `propertyByResidenceId` returns the `Property`\ninterface; Listing/SoldProperty-only fields need inline fragments. Works for\nboth active and sold properties (check `__typename`).\n\n```json\n{\n  \"query\": \"query Detail($residenceId: ID!) { propertyByResidenceId(residenceId: $residenceId) { __typename booliId residenceId objectType tenureForm streetAddress descriptiveAreaName constructionYear url rooms { raw } livingArea { raw } plotArea { raw } rent { raw } operatingCost { raw } floor { raw } location { region { municipalityName countyName } namedAreas } ... on Listing { published isNewConstruction upcomingSale listingUrl listPrice { raw formatted } listSqmPrice { raw } agency { name url } estimate { price { raw } } } ... on SoldProperty { soldPrice { raw formatted } soldSqmPrice { raw } soldDate soldPricePercentageDiff { raw } } } }\",\n  \"variables\": { \"residenceId\": \"4370936\" }\n}\n```\n\n```sh\njq '.data.propertyByResidenceId | {type: .__typename, addr: .streetAddress, rooms: .rooms.raw, m2: .livingArea.raw, price: (.listPrice.formatted // .soldPrice.formatted), area: .location.namedAreas[0]}'\n```\n\n---\n\n## Market stats\n\nBooli's GraphQL has no market-stats query — the `booli_market_stats` tool\nderives median/average sold price, price-per-m², and average over/under-asking %\nlocally from a `searchSold` page (§3). Reproduce with `jq` over `result` as\nshown above; check the count before trusting a thin median.\n\nFile v2.1.2:skill-card.md\n\n## Description:\n\nQuery booli.se property data from a shell with the fpx CLI, including area lookup, for-sale and sold listing searches, and property detail requests through a signed-in browser tab.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and analysts use this skill to make read-only Booli property lookups from shell workflows without running the booli MCP server. It helps resolve Booli area ids, search active and sold listings, and inspect property detail through GraphQL calls routed by fpx.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The workflow depends on the fpx CLI and Transporter browser extension making requests through the user's browser session.\n\nMitigation: Review those tools before global installation, approve pairing intentionally, and keep the fpx profile limited to booli.se.\n\nRisk: Booli requests should remain read-only and within Booli's terms.\n\nMitigation: Use the skill for anonymous property lookups only and avoid account data or write actions.\n\nRisk: GraphQL errors can appear in a successful HTTP response body.\n\nMitigation: Check the response for an errors array before using returned listing or property data.\n\n## Reference(s):\n\n- [Booli GraphQL queries for fpx](references/graphql-queries.md)\n- [Booli GraphQL endpoint](https://www.booli.se/graphql)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration instructions, Code, JSON, Guidance]\n\n**Output Format:** [Markdown with inline shell commands, JSON GraphQL request bodies, and jq examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Produces read-only request examples and troubleshooting guidance; command output is Booli GraphQL JSON.]\n\n## Skill Version(s):\n\n2.1.2 (source: ClawHub release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.1.1: 4 files, 6135 bytes\n\nFiles: references/graphql-queries.md (5826b), skill-card.md (2330b), SKILL.md (3800b), _meta.json (128b)\n\nFile v2.1.1:SKILL.md\n\n---\nname: booli-fpx\ndescription: >-\n  Query booli.se (Swedish property portal) from a shell with the fpx CLI\n  (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas,\n  search for-sale and sold listings (slutpriser), and read property detail via\n  one-shot GraphQL calls through a signed-in browser tab. Use when you want\n  Booli data without the MCP, in a script, or on a machine where the MCP isn't\n  installed.\n---\n\n# Booli via fpx (no MCP)\n\nBooli fronts `www.booli.se` — including `/graphql` — with a Cloudflare managed\nchallenge that 403s any plain `curl`/Node request. `fpx` routes the request\nthrough the user's own signed-in browser tab (the Transporter extension),\nwhich has already cleared the challenge, so the same anonymous GraphQL query\nsucceeds. No Booli login is needed — just a normal open tab.\n\nThis is the same data the `booli_*` MCP tools return (money in **SEK**, areas\nin **m²**, rooms as a number), reached with one CLI call instead of a running\nserver.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli           # provides `fpx`\nfpx profile add booli --domain booli.se  # only the fetch capability is needed\nfpx pair -p booli                        # prints a pair code → approve in Transporter\n```\n\nRequirements: the **Transporter** browser extension installed, with an open\n`www.booli.se` tab, and its Chrome **Site access** allowing `booli.se`.\nPairing persists — after the first approval every later `fpx` call reuses it.\n\n## Core call\n\nEvery query is a POST of `{\"query\": \"...\", \"variables\": {...}}` to the GraphQL\nendpoint. Write the body to a file (a heredoc avoids shell-quoting the GraphQL\nstring), then send it so stdout is the GraphQL JSON, ready for `jq`:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nReady-to-run query bodies (area lookup, for-sale search, sold search, property\ndetail) with `jq` recipes are in `references/graphql-queries.md`. Exhaustive\nfield lists live in the repo at `src/graphql.ts` — the operations here are\ncompact, live-verified subsets.\n\n## The one rule: resolve the area first\n\nBooli searches take a numeric **area id** (`areaId`), never a place name.\nAlways resolve the name first, take the `id`, then search:\n\n```sh\nprintf '{\"query\":\"query($s:String!){areaSuggestionSearch(search:$s){suggestions{id displayName}}}\",\"variables\":{\"s\":\"Nacka\"}}' > /tmp/a.json\nfpx post-json 'https://www.booli.se/graphql' @/tmp/a.json -p booli \\\n  | jq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n# 76      Nacka kommun\n```\n\nPass that `id` as the search input's `areaId` (a string). The `SearchRequest`\ninput needs `areaId`, `page` (1-based), `ascending`, `excludeAncestors: true`,\n`facets: []`, `filters: [{key,value}]`, and `sort` (`\"\"` = newest). Filters\nreuse the same keys the MCP does (`minRooms`, `maxListPrice`, `objectType`, …).\n\n## Exit codes (fetch verbs)\n\n- `0` — success (a GraphQL `errors` array can still ride in a `0` body; check `jq '.errors // empty'`).\n- `2` — bridge unavailable: extension not connected or pairing pending → run `fpx pair -p booli`, confirm a booli tab is open.\n- `3` — bot wall: the tab hasn't cleared Cloudflare → open/refresh a `www.booli.se` tab and retry.\n- `4` — upstream non-2xx from Booli.\n\n## Notes\n\n- Anonymous reads only — the same public GraphQL surface as the MCP; no account\n  data. Stay within booli.se's terms.\n- `fpx health -p booli` shows bridge connection state when a call fails.\n- Property detail is keyed by **residence id** — the number in a\n  `/bostad/<id>` URL, which is a search result's `url` field, **not** `booliId`.\n- This project is developed and maintained by AI (Claude).\n\nFile v2.1.1:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"booli-fpx\",\n  \"version\": \"2.1.1\",\n  \"publishedAt\": 1790008520488\n}\n\nFile v2.1.1:references/graphql-queries.md\n\n# Booli GraphQL queries for fpx\n\nReady-to-run bodies for `fpx post-json 'https://www.booli.se/graphql' @file -p booli`.\nEach block is the full POST body (`{\"query\",\"variables\"}`). All field names and\ninput shapes are live-verified against the endpoint; the exhaustive field\nselections live in the repo at `src/graphql.ts` (these are compact subsets).\n\nEndpoint: `https://www.booli.se/graphql` · Method: POST · anonymous.\nIntrospection is disabled — probe field names by sending a query and reading the\nvalidation errors (that's how these were pinned).\n\nWrite the body to a file (heredoc avoids shell-quoting the GraphQL string),\nthen send it:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nAlways check for GraphQL errors: `jq '.errors // empty'` (an errors array can\nride in an HTTP-200 body).\n\nNumeric fields come as `FormattedValue { raw, formatted }` — select `{ raw }`\nfor the number, `{ formatted }` for the display string.\n\n---\n\n## 1. Resolve an area → areaId (do this first)\n\n`search` is free text.\n\n```json\n{\n  \"query\": \"query Areas($s: String!) { areaSuggestionSearch(search: $s) { suggestions { id displayName } } }\",\n  \"variables\": { \"s\": \"Nacka\" }\n}\n```\n\n```sh\njq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n```\n\n## 2. Search for-sale listings\n\n`input` is a `SearchRequest`: `areaId` (string, from step 1), `page` (1-based),\n`ascending`, `excludeAncestors: true`, `facets: []`, `filters: [{key,value}]`,\n`sort` (`\"\"` = newest, else `listPrice` / `listSqmPrice` / `rooms` /\n`livingArea` / `rent` / `plotArea` / `published`). `result` mixes `Listing` and\n`Project` (new-construction) nodes — take the `... on Listing` ones.\n\nFilter keys (both key and value are strings): `objectType` (`Lägenhet`, `Villa`,\n`Kedjehus-Parhus-Radhus`, `Fritidshus`, `Gård`, `Tomt/Mark`), `minRooms`/`maxRooms`,\n`minLivingArea`/`maxLivingArea`, `minPlotArea`/`maxPlotArea`,\n`minListPrice`/`maxListPrice`, `minListSqmPrice`/`maxListSqmPrice`,\n`minConstructionYear`/`maxConstructionYear`, `isNewConstruction` (`1`/`0`).\n\n```json\n{\n  \"query\": \"query SearchForSale($input: SearchRequest) { searchForSale(input: $input) { totalCount pages result { __typename ... on Listing { booliId objectType tenureForm streetAddress descriptiveAreaName url rooms { raw } livingArea { raw } rent { raw } listPrice { raw formatted } listSqmPrice { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [\n        { \"key\": \"objectType\", \"value\": \"Lägenhet\" },\n        { \"key\": \"minRooms\", \"value\": \"3\" },\n        { \"key\": \"maxListPrice\", \"value\": \"6000000\" }\n      ],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\njq -r '.data.searchForSale | \"total=\\(.totalCount) pages=\\(.pages)\", (.result[] | select(.__typename==\"Listing\") | \"\\(.booliId)\\t\\(.listPrice.formatted // \"—\")\\t\\(.streetAddress)\")'\n```\n\n## 3. Search sold listings (slutpriser — the comps)\n\nSame `SearchRequest` input; result is `... on SoldProperty`. Sold-specific\nfilter keys: `minSoldPrice`/`maxSoldPrice`, `minSoldSqmPrice`/`maxSoldSqmPrice`,\n`minSoldDate`/`maxSoldDate` (YYYYMMDD). `soldPricePercentageDiff.raw` is the\nover/under-asking % (e.g. `-20`).\n\n```json\n{\n  \"query\": \"query SearchSold($input: SearchRequest) { searchSold(input: $input) { totalCount pages result { __typename ... on SoldProperty { booliId objectType streetAddress descriptiveAreaName url soldDate soldPrice { raw formatted } soldSqmPrice { raw } listPrice { raw } soldPricePercentageDiff { raw } rooms { raw } livingArea { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [{ \"key\": \"minSoldDate\", \"value\": \"20240101\" }],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\n# Median sold price across the page (raw kronor):\njq -r '[.data.searchSold.result[] | select(.__typename==\"SoldProperty\") | .soldPrice.raw] | sort | .[length/2|floor]'\n```\n\n## 4. Property detail by residence id\n\n`residenceId` is `ID!` — the number in a `/bostad/<id>` URL (a search result's\n`url` field), **not** `booliId`. `propertyByResidenceId` returns the `Property`\ninterface; Listing/SoldProperty-only fields need inline fragments. Works for\nboth active and sold properties (check `__typename`).\n\n```json\n{\n  \"query\": \"query Detail($residenceId: ID!) { propertyByResidenceId(residenceId: $residenceId) { __typename booliId residenceId objectType tenureForm streetAddress descriptiveAreaName constructionYear url rooms { raw } livingArea { raw } plotArea { raw } rent { raw } operatingCost { raw } floor { raw } location { region { municipalityName countyName } namedAreas } ... on Listing { published isNewConstruction upcomingSale listingUrl listPrice { raw formatted } listSqmPrice { raw } agency { name url } estimate { price { raw } } } ... on SoldProperty { soldPrice { raw formatted } soldSqmPrice { raw } soldDate soldPricePercentageDiff { raw } } } }\",\n  \"variables\": { \"residenceId\": \"4370936\" }\n}\n```\n\n```sh\njq '.data.propertyByResidenceId | {type: .__typename, addr: .streetAddress, rooms: .rooms.raw, m2: .livingArea.raw, price: (.listPrice.formatted // .soldPrice.formatted), area: .location.namedAreas[0]}'\n```\n\n---\n\n## Market stats\n\nBooli's GraphQL has no market-stats query — the `booli_market_stats` tool\nderives median/average sold price, price-per-m², and average over/under-asking %\nlocally from a `searchSold` page (§3). Reproduce with `jq` over `result` as\nshown above; check the count before trusting a thin median.\n\nFile v2.1.1:skill-card.md\n\n## Description:\n\nQuery booli.se from a shell with the fpx CLI to resolve areas, search for-sale and sold listings, and read property detail through browser-backed GraphQL calls.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and analysts use this skill to obtain public Booli real estate data from a shell when the booli MCP server is unavailable or unnecessary. It provides setup guidance, GraphQL request bodies, and jq recipes for area lookup, active listings, sold listings, and property detail.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill relies on the Transporter extension and a globally installed fpx CLI to send booli.se requests through the user's browser context.\n\nMitigation: Install only when that browser-backed workflow is acceptable, keep Chrome site access limited to booli.se, and use the dedicated booli fpx profile described by the skill.\n\nRisk: Automated querying of Booli data may be subject to Booli's terms and operational limits.\n\nMitigation: Review Booli's terms before automated querying and keep request volume appropriate for the intended use.\n\nRisk: GraphQL errors can appear in an HTTP-200 response body, and thin result sets can make derived market statistics unreliable.\n\nMitigation: Check the GraphQL errors field before using results and review result counts before trusting derived summaries.\n\n## Reference(s):\n\n- [Booli GraphQL queries for fpx](artifact/references/graphql-queries.md)\n- [Booli GraphQL endpoint](https://www.booli.se/graphql)\n\n## Skill Output:\n\n**Output Type(s):** [Guidance, Shell commands, Configuration, JSON]\n\n**Output Format:** [Markdown with shell and JSON code blocks]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Produces textual instructions and request bodies; executed commands return GraphQL JSON suitable for jq processing.]\n\n## Skill Version(s):\n\n2.1.1 (source: server release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.1.0: 4 files, 6137 bytes\n\nFiles: references/graphql-queries.md (5826b), skill-card.md (2429b), SKILL.md (3800b), _meta.json (128b)\n\nFile v2.1.0:SKILL.md\n\n---\nname: booli-fpx\ndescription: >-\n  Query booli.se (Swedish property portal) from a shell with the fpx CLI\n  (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas,\n  search for-sale and sold listings (slutpriser), and read property detail via\n  one-shot GraphQL calls through a signed-in browser tab. Use when you want\n  Booli data without the MCP, in a script, or on a machine where the MCP isn't\n  installed.\n---\n\n# Booli via fpx (no MCP)\n\nBooli fronts `www.booli.se` — including `/graphql` — with a Cloudflare managed\nchallenge that 403s any plain `curl`/Node request. `fpx` routes the request\nthrough the user's own signed-in browser tab (the Transporter extension),\nwhich has already cleared the challenge, so the same anonymous GraphQL query\nsucceeds. No Booli login is needed — just a normal open tab.\n\nThis is the same data the `booli_*` MCP tools return (money in **SEK**, areas\nin **m²**, rooms as a number), reached with one CLI call instead of a running\nserver.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli           # provides `fpx`\nfpx profile add booli --domain booli.se  # only the fetch capability is needed\nfpx pair -p booli                        # prints a pair code → approve in Transporter\n```\n\nRequirements: the **Transporter** browser extension installed, with an open\n`www.booli.se` tab, and its Chrome **Site access** allowing `booli.se`.\nPairing persists — after the first approval every later `fpx` call reuses it.\n\n## Core call\n\nEvery query is a POST of `{\"query\": \"...\", \"variables\": {...}}` to the GraphQL\nendpoint. Write the body to a file (a heredoc avoids shell-quoting the GraphQL\nstring), then send it so stdout is the GraphQL JSON, ready for `jq`:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nReady-to-run query bodies (area lookup, for-sale search, sold search, property\ndetail) with `jq` recipes are in `references/graphql-queries.md`. Exhaustive\nfield lists live in the repo at `src/graphql.ts` — the operations here are\ncompact, live-verified subsets.\n\n## The one rule: resolve the area first\n\nBooli searches take a numeric **area id** (`areaId`), never a place name.\nAlways resolve the name first, take the `id`, then search:\n\n```sh\nprintf '{\"query\":\"query($s:String!){areaSuggestionSearch(search:$s){suggestions{id displayName}}}\",\"variables\":{\"s\":\"Nacka\"}}' > /tmp/a.json\nfpx post-json 'https://www.booli.se/graphql' @/tmp/a.json -p booli \\\n  | jq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n# 76      Nacka kommun\n```\n\nPass that `id` as the search input's `areaId` (a string). The `SearchRequest`\ninput needs `areaId`, `page` (1-based), `ascending`, `excludeAncestors: true`,\n`facets: []`, `filters: [{key,value}]`, and `sort` (`\"\"` = newest). Filters\nreuse the same keys the MCP does (`minRooms`, `maxListPrice`, `objectType`, …).\n\n## Exit codes (fetch verbs)\n\n- `0` — success (a GraphQL `errors` array can still ride in a `0` body; check `jq '.errors // empty'`).\n- `2` — bridge unavailable: extension not connected or pairing pending → run `fpx pair -p booli`, confirm a booli tab is open.\n- `3` — bot wall: the tab hasn't cleared Cloudflare → open/refresh a `www.booli.se` tab and retry.\n- `4` — upstream non-2xx from Booli.\n\n## Notes\n\n- Anonymous reads only — the same public GraphQL surface as the MCP; no account\n  data. Stay within booli.se's terms.\n- `fpx health -p booli` shows bridge connection state when a call fails.\n- Property detail is keyed by **residence id** — the number in a\n  `/bostad/<id>` URL, which is a search result's `url` field, **not** `booliId`.\n- This project is developed and maintained by AI (Claude).\n\nFile v2.1.0:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"booli-fpx\",\n  \"version\": \"2.1.0\",\n  \"publishedAt\": 1789872515091\n}\n\nFile v2.1.0:references/graphql-queries.md\n\n# Booli GraphQL queries for fpx\n\nReady-to-run bodies for `fpx post-json 'https://www.booli.se/graphql' @file -p booli`.\nEach block is the full POST body (`{\"query\",\"variables\"}`). All field names and\ninput shapes are live-verified against the endpoint; the exhaustive field\nselections live in the repo at `src/graphql.ts` (these are compact subsets).\n\nEndpoint: `https://www.booli.se/graphql` · Method: POST · anonymous.\nIntrospection is disabled — probe field names by sending a query and reading the\nvalidation errors (that's how these were pinned).\n\nWrite the body to a file (heredoc avoids shell-quoting the GraphQL string),\nthen send it:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nAlways check for GraphQL errors: `jq '.errors // empty'` (an errors array can\nride in an HTTP-200 body).\n\nNumeric fields come as `FormattedValue { raw, formatted }` — select `{ raw }`\nfor the number, `{ formatted }` for the display string.\n\n---\n\n## 1. Resolve an area → areaId (do this first)\n\n`search` is free text.\n\n```json\n{\n  \"query\": \"query Areas($s: String!) { areaSuggestionSearch(search: $s) { suggestions { id displayName } } }\",\n  \"variables\": { \"s\": \"Nacka\" }\n}\n```\n\n```sh\njq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n```\n\n## 2. Search for-sale listings\n\n`input` is a `SearchRequest`: `areaId` (string, from step 1), `page` (1-based),\n`ascending`, `excludeAncestors: true`, `facets: []`, `filters: [{key,value}]`,\n`sort` (`\"\"` = newest, else `listPrice` / `listSqmPrice` / `rooms` /\n`livingArea` / `rent` / `plotArea` / `published`). `result` mixes `Listing` and\n`Project` (new-construction) nodes — take the `... on Listing` ones.\n\nFilter keys (both key and value are strings): `objectType` (`Lägenhet`, `Villa`,\n`Kedjehus-Parhus-Radhus`, `Fritidshus`, `Gård`, `Tomt/Mark`), `minRooms`/`maxRooms`,\n`minLivingArea`/`maxLivingArea`, `minPlotArea`/`maxPlotArea`,\n`minListPrice`/`maxListPrice`, `minListSqmPrice`/`maxListSqmPrice`,\n`minConstructionYear`/`maxConstructionYear`, `isNewConstruction` (`1`/`0`).\n\n```json\n{\n  \"query\": \"query SearchForSale($input: SearchRequest) { searchForSale(input: $input) { totalCount pages result { __typename ... on Listing { booliId objectType tenureForm streetAddress descriptiveAreaName url rooms { raw } livingArea { raw } rent { raw } listPrice { raw formatted } listSqmPrice { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [\n        { \"key\": \"objectType\", \"value\": \"Lägenhet\" },\n        { \"key\": \"minRooms\", \"value\": \"3\" },\n        { \"key\": \"maxListPrice\", \"value\": \"6000000\" }\n      ],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\njq -r '.data.searchForSale | \"total=\\(.totalCount) pages=\\(.pages)\", (.result[] | select(.__typename==\"Listing\") | \"\\(.booliId)\\t\\(.listPrice.formatted // \"—\")\\t\\(.streetAddress)\")'\n```\n\n## 3. Search sold listings (slutpriser — the comps)\n\nSame `SearchRequest` input; result is `... on SoldProperty`. Sold-specific\nfilter keys: `minSoldPrice`/`maxSoldPrice`, `minSoldSqmPrice`/`maxSoldSqmPrice`,\n`minSoldDate`/`maxSoldDate` (YYYYMMDD). `soldPricePercentageDiff.raw` is the\nover/under-asking % (e.g. `-20`).\n\n```json\n{\n  \"query\": \"query SearchSold($input: SearchRequest) { searchSold(input: $input) { totalCount pages result { __typename ... on SoldProperty { booliId objectType streetAddress descriptiveAreaName url soldDate soldPrice { raw formatted } soldSqmPrice { raw } listPrice { raw } soldPricePercentageDiff { raw } rooms { raw } livingArea { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [{ \"key\": \"minSoldDate\", \"value\": \"20240101\" }],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\n# Median sold price across the page (raw kronor):\njq -r '[.data.searchSold.result[] | select(.__typename==\"SoldProperty\") | .soldPrice.raw] | sort | .[length/2|floor]'\n```\n\n## 4. Property detail by residence id\n\n`residenceId` is `ID!` — the number in a `/bostad/<id>` URL (a search result's\n`url` field), **not** `booliId`. `propertyByResidenceId` returns the `Property`\ninterface; Listing/SoldProperty-only fields need inline fragments. Works for\nboth active and sold properties (check `__typename`).\n\n```json\n{\n  \"query\": \"query Detail($residenceId: ID!) { propertyByResidenceId(residenceId: $residenceId) { __typename booliId residenceId objectType tenureForm streetAddress descriptiveAreaName constructionYear url rooms { raw } livingArea { raw } plotArea { raw } rent { raw } operatingCost { raw } floor { raw } location { region { municipalityName countyName } namedAreas } ... on Listing { published isNewConstruction upcomingSale listingUrl listPrice { raw formatted } listSqmPrice { raw } agency { name url } estimate { price { raw } } } ... on SoldProperty { soldPrice { raw formatted } soldSqmPrice { raw } soldDate soldPricePercentageDiff { raw } } } }\",\n  \"variables\": { \"residenceId\": \"4370936\" }\n}\n```\n\n```sh\njq '.data.propertyByResidenceId | {type: .__typename, addr: .streetAddress, rooms: .rooms.raw, m2: .livingArea.raw, price: (.listPrice.formatted // .soldPrice.formatted), area: .location.namedAreas[0]}'\n```\n\n---\n\n## Market stats\n\nBooli's GraphQL has no market-stats query — the `booli_market_stats` tool\nderives median/average sold price, price-per-m², and average over/under-asking %\nlocally from a `searchSold` page (§3). Reproduce with `jq` over `result` as\nshown above; check the count before trusting a thin median.\n\nFile v2.1.0:skill-card.md\n\n## Description:\n\nQuery booli.se Swedish property data from a shell with the fpx CLI, including area resolution, for-sale listings, sold listings, and property detail through one-shot GraphQL calls from a signed-in browser tab.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and data users use this skill to query public Booli property listings and sold-property data from scripts or shell sessions without running the Booli MCP server. It is suited for one-off lookups, lightweight analysis, and reproducing MCP-style Booli queries with fpx and jq.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill relies on fpx and the Transporter browser extension with site access to booli.se.\n\nMitigation: Use a dedicated fpx profile for Booli, keep the browser extension site access limited to booli.se, and install only when this bridge behavior is acceptable.\n\nRisk: Temporary JSON request files may contain query details or parameters that users did not intend to keep.\n\nMitigation: Review temporary request files before execution and remove them when they are no longer needed.\n\nRisk: GraphQL responses can include an errors array even when the HTTP request succeeds.\n\nMitigation: Check jq '.errors // empty' and validate result counts before relying on listing or market-stat outputs.\n\n## Reference(s):\n\n- [Booli GraphQL queries for fpx](references/graphql-queries.md)\n- [Booli GraphQL endpoint](https://www.booli.se/graphql)\n- [ClawHub skill page](https://clawhub.ai/chrischall/skills/booli-fpx)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Shell commands, Configuration, Guidance]\n\n**Output Format:** [Markdown guidance with shell commands, JSON request bodies, and jq examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Produces executable fpx and jq examples for public Booli GraphQL queries; users should review temporary request files and GraphQL errors before relying on results.]\n\n## Skill Version(s):\n\n2.1.0 (source: server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.0.0: 4 files, 6133 bytes\n\nFiles: references/graphql-queries.md (5826b), skill-card.md (2408b), SKILL.md (3800b), _meta.json (128b)\n\nFile v2.0.0:SKILL.md\n\n---\nname: booli-fpx\ndescription: >-\n  Query booli.se (Swedish property portal) from a shell with the fpx CLI\n  (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas,\n  search for-sale and sold listings (slutpriser), and read property detail via\n  one-shot GraphQL calls through a signed-in browser tab. Use when you want\n  Booli data without the MCP, in a script, or on a machine where the MCP isn't\n  installed.\n---\n\n# Booli via fpx (no MCP)\n\nBooli fronts `www.booli.se` — including `/graphql` — with a Cloudflare managed\nchallenge that 403s any plain `curl`/Node request. `fpx` routes the request\nthrough the user's own signed-in browser tab (the Transporter extension),\nwhich has already cleared the challenge, so the same anonymous GraphQL query\nsucceeds. No Booli login is needed — just a normal open tab.\n\nThis is the same data the `booli_*` MCP tools return (money in **SEK**, areas\nin **m²**, rooms as a number), reached with one CLI call instead of a running\nserver.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli           # provides `fpx`\nfpx profile add booli --domain booli.se  # only the fetch capability is needed\nfpx pair -p booli                        # prints a pair code → approve in Transporter\n```\n\nRequirements: the **Transporter** browser extension installed, with an open\n`www.booli.se` tab, and its Chrome **Site access** allowing `booli.se`.\nPairing persists — after the first approval every later `fpx` call reuses it.\n\n## Core call\n\nEvery query is a POST of `{\"query\": \"...\", \"variables\": {...}}` to the GraphQL\nendpoint. Write the body to a file (a heredoc avoids shell-quoting the GraphQL\nstring), then send it so stdout is the GraphQL JSON, ready for `jq`:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nReady-to-run query bodies (area lookup, for-sale search, sold search, property\ndetail) with `jq` recipes are in `references/graphql-queries.md`. Exhaustive\nfield lists live in the repo at `src/graphql.ts` — the operations here are\ncompact, live-verified subsets.\n\n## The one rule: resolve the area first\n\nBooli searches take a numeric **area id** (`areaId`), never a place name.\nAlways resolve the name first, take the `id`, then search:\n\n```sh\nprintf '{\"query\":\"query($s:String!){areaSuggestionSearch(search:$s){suggestions{id displayName}}}\",\"variables\":{\"s\":\"Nacka\"}}' > /tmp/a.json\nfpx post-json 'https://www.booli.se/graphql' @/tmp/a.json -p booli \\\n  | jq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n# 76      Nacka kommun\n```\n\nPass that `id` as the search input's `areaId` (a string). The `SearchRequest`\ninput needs `areaId`, `page` (1-based), `ascending`, `excludeAncestors: true`,\n`facets: []`, `filters: [{key,value}]`, and `sort` (`\"\"` = newest). Filters\nreuse the same keys the MCP does (`minRooms`, `maxListPrice`, `objectType`, …).\n\n## Exit codes (fetch verbs)\n\n- `0` — success (a GraphQL `errors` array can still ride in a `0` body; check `jq '.errors // empty'`).\n- `2` — bridge unavailable: extension not connected or pairing pending → run `fpx pair -p booli`, confirm a booli tab is open.\n- `3` — bot wall: the tab hasn't cleared Cloudflare → open/refresh a `www.booli.se` tab and retry.\n- `4` — upstream non-2xx from Booli.\n\n## Notes\n\n- Anonymous reads only — the same public GraphQL surface as the MCP; no account\n  data. Stay within booli.se's terms.\n- `fpx health -p booli` shows bridge connection state when a call fails.\n- Property detail is keyed by **residence id** — the number in a\n  `/bostad/<id>` URL, which is a search result's `url` field, **not** `booliId`.\n- This project is developed and maintained by AI (Claude).\n\nFile v2.0.0:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"booli-fpx\",\n  \"version\": \"2.0.0\",\n  \"publishedAt\": 1789667460943\n}\n\nFile v2.0.0:references/graphql-queries.md\n\n# Booli GraphQL queries for fpx\n\nReady-to-run bodies for `fpx post-json 'https://www.booli.se/graphql' @file -p booli`.\nEach block is the full POST body (`{\"query\",\"variables\"}`). All field names and\ninput shapes are live-verified against the endpoint; the exhaustive field\nselections live in the repo at `src/graphql.ts` (these are compact subsets).\n\nEndpoint: `https://www.booli.se/graphql` · Method: POST · anonymous.\nIntrospection is disabled — probe field names by sending a query and reading the\nvalidation errors (that's how these were pinned).\n\nWrite the body to a file (heredoc avoids shell-quoting the GraphQL string),\nthen send it:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nAlways check for GraphQL errors: `jq '.errors // empty'` (an errors array can\nride in an HTTP-200 body).\n\nNumeric fields come as `FormattedValue { raw, formatted }` — select `{ raw }`\nfor the number, `{ formatted }` for the display string.\n\n---\n\n## 1. Resolve an area → areaId (do this first)\n\n`search` is free text.\n\n```json\n{\n  \"query\": \"query Areas($s: String!) { areaSuggestionSearch(search: $s) { suggestions { id displayName } } }\",\n  \"variables\": { \"s\": \"Nacka\" }\n}\n```\n\n```sh\njq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n```\n\n## 2. Search for-sale listings\n\n`input` is a `SearchRequest`: `areaId` (string, from step 1), `page` (1-based),\n`ascending`, `excludeAncestors: true`, `facets: []`, `filters: [{key,value}]`,\n`sort` (`\"\"` = newest, else `listPrice` / `listSqmPrice` / `rooms` /\n`livingArea` / `rent` / `plotArea` / `published`). `result` mixes `Listing` and\n`Project` (new-construction) nodes — take the `... on Listing` ones.\n\nFilter keys (both key and value are strings): `objectType` (`Lägenhet`, `Villa`,\n`Kedjehus-Parhus-Radhus`, `Fritidshus`, `Gård`, `Tomt/Mark`), `minRooms`/`maxRooms`,\n`minLivingArea`/`maxLivingArea`, `minPlotArea`/`maxPlotArea`,\n`minListPrice`/`maxListPrice`, `minListSqmPrice`/`maxListSqmPrice`,\n`minConstructionYear`/`maxConstructionYear`, `isNewConstruction` (`1`/`0`).\n\n```json\n{\n  \"query\": \"query SearchForSale($input: SearchRequest) { searchForSale(input: $input) { totalCount pages result { __typename ... on Listing { booliId objectType tenureForm streetAddress descriptiveAreaName url rooms { raw } livingArea { raw } rent { raw } listPrice { raw formatted } listSqmPrice { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [\n        { \"key\": \"objectType\", \"value\": \"Lägenhet\" },\n        { \"key\": \"minRooms\", \"value\": \"3\" },\n        { \"key\": \"maxListPrice\", \"value\": \"6000000\" }\n      ],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\njq -r '.data.searchForSale | \"total=\\(.totalCount) pages=\\(.pages)\", (.result[] | select(.__typename==\"Listing\") | \"\\(.booliId)\\t\\(.listPrice.formatted // \"—\")\\t\\(.streetAddress)\")'\n```\n\n## 3. Search sold listings (slutpriser — the comps)\n\nSame `SearchRequest` input; result is `... on SoldProperty`. Sold-specific\nfilter keys: `minSoldPrice`/`maxSoldPrice`, `minSoldSqmPrice`/`maxSoldSqmPrice`,\n`minSoldDate`/`maxSoldDate` (YYYYMMDD). `soldPricePercentageDiff.raw` is the\nover/under-asking % (e.g. `-20`).\n\n```json\n{\n  \"query\": \"query SearchSold($input: SearchRequest) { searchSold(input: $input) { totalCount pages result { __typename ... on SoldProperty { booliId objectType streetAddress descriptiveAreaName url soldDate soldPrice { raw formatted } soldSqmPrice { raw } listPrice { raw } soldPricePercentageDiff { raw } rooms { raw } livingArea { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [{ \"key\": \"minSoldDate\", \"value\": \"20240101\" }],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\n# Median sold price across the page (raw kronor):\njq -r '[.data.searchSold.result[] | select(.__typename==\"SoldProperty\") | .soldPrice.raw] | sort | .[length/2|floor]'\n```\n\n## 4. Property detail by residence id\n\n`residenceId` is `ID!` — the number in a `/bostad/<id>` URL (a search result's\n`url` field), **not** `booliId`. `propertyByResidenceId` returns the `Property`\ninterface; Listing/SoldProperty-only fields need inline fragments. Works for\nboth active and sold properties (check `__typename`).\n\n```json\n{\n  \"query\": \"query Detail($residenceId: ID!) { propertyByResidenceId(residenceId: $residenceId) { __typename booliId residenceId objectType tenureForm streetAddress descriptiveAreaName constructionYear url rooms { raw } livingArea { raw } plotArea { raw } rent { raw } operatingCost { raw } floor { raw } location { region { municipalityName countyName } namedAreas } ... on Listing { published isNewConstruction upcomingSale listingUrl listPrice { raw formatted } listSqmPrice { raw } agency { name url } estimate { price { raw } } } ... on SoldProperty { soldPrice { raw formatted } soldSqmPrice { raw } soldDate soldPricePercentageDiff { raw } } } }\",\n  \"variables\": { \"residenceId\": \"4370936\" }\n}\n```\n\n```sh\njq '.data.propertyByResidenceId | {type: .__typename, addr: .streetAddress, rooms: .rooms.raw, m2: .livingArea.raw, price: (.listPrice.formatted // .soldPrice.formatted), area: .location.namedAreas[0]}'\n```\n\n---\n\n## Market stats\n\nBooli's GraphQL has no market-stats query — the `booli_market_stats` tool\nderives median/average sold price, price-per-m², and average over/under-asking %\nlocally from a `searchSold` page (§3). Reproduce with `jq` over `result` as\nshown above; check the count before trusting a thin median.\n\nFile v2.0.0:skill-card.md\n\n## Description:\n\nQuery booli.se from a shell with the fpx CLI to resolve areas, search for-sale and sold listings, and read property detail through one-shot GraphQL calls via a signed-in browser tab.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and analysts use this skill to query public Booli property data from scripts or shell sessions without running the Booli MCP server. It helps resolve Booli area IDs, search active and sold Swedish property listings, and fetch property details as GraphQL JSON.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill depends on installing the external @fetchproxy/cli package globally and pairing it with the Transporter browser extension.\n\nMitigation: Confirm the package and extension are acceptable for the target environment before installation, and keep the browser bridge scoped to booli.se.\n\nRisk: Browser-bridge pairing persists after setup.\n\nMitigation: Use a dedicated browser profile or limited site access for Booli workflows, and remove or rotate the pairing when it is no longer needed.\n\nRisk: Booli requests must stay within the service's terms and rely on the user's browser session clearing site challenges.\n\nMitigation: Use the skill only for anonymous public reads within Booli's terms, and check GraphQL errors before relying on returned data.\n\n## Reference(s):\n\n- [Booli GraphQL queries for fpx](references/graphql-queries.md)\n- [Booli GraphQL endpoint](https://www.booli.se/graphql)\n- [ClawHub skill page](https://clawhub.ai/chrischall/skills/booli-fpx)\n\n## Skill Output:\n\n**Output Type(s):** [guidance, markdown, shell commands, configuration, JSON]\n\n**Output Format:** [Markdown guidance with shell commands, GraphQL request bodies, and jq examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Produces commands and query bodies for anonymous Booli GraphQL reads through fpx; command output is GraphQL JSON.]\n\n## Skill Version(s):\n\n2.0.0 (source: ClawHub release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.","readmeExcerpt":"Skill: booli-fpx Owner: chrischall Summary: Query booli.se (Swedish property portal) from a shell with the fpx CLI (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas, search for-sale and sold listings (slutpriser), and read property detail via one-shot GraphQL calls through a signed-in browser tab. Use when you want Booli data without the MCP, in a script, or on a machine where the MCP isn't in","codeSnippets":[],"executableExamples":[{"language":"sh","snippet":"npm install -g @fetchproxy/cli           # provides `fpx`\nfpx profile add booli --domain booli.se  # only the fetch capability is needed\nfpx pair -p booli                        # prints a pair code → approve in ContextMint Bridge"},{"language":"sh","snippet":"cat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'"},{"language":"sh","snippet":"printf '{\"query\":\"query($s:String!){areaSuggestionSearch(search:$s){suggestions{id displayName}}}\",\"variables\":{\"s\":\"Nacka\"}}' > /tmp/a.json\nfpx post-json 'https://www.booli.se/graphql' @/tmp/a.json -p booli \\\n  | jq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n# 76      Nacka kommun"},{"language":"sh","snippet":"cat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'"},{"language":"json","snippet":"{\n  \"query\": \"query Areas($s: String!) { areaSuggestionSearch(search: $s) { suggestions { id displayName } } }\",\n  \"variables\": { \"s\": \"Nacka\" }\n}"},{"language":"sh","snippet":"jq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: booli-fpx\ndescription: >-\n  Query booli.se (Swedish property portal) from a shell with the fpx CLI\n  (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas,\n  search for-sale and sold listings (slutpriser), and read property detail via\n  one-shot GraphQL calls through a signed-in browser tab. Use when you want\n  Booli data without the MCP, in a script, or on a machine where the MCP isn't\n  installed.\n---\n\n# Booli via fpx (no MCP)\n\nBooli fronts `www.booli.se` — including `/graphql` — with a Cloudflare managed\nchallenge that 403s any plain `curl`/Node request. `fpx` routes the request\nthrough the user's own signed-in browser tab (the ContextMint Bridge extension),\nwhich has already cleared the challenge, so the same anonymous GraphQL query\nsucceeds. No Booli login is needed — just a normal open tab.\n\nThis is the same data the `booli_*` MCP tools return (money in **SEK**, areas\nin **m²**, rooms as a number), reached with one CLI call instead of a running\nserver.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli           # provides `fpx`\nfpx profile add booli --domain booli.se  # only the fetch capability is needed\nfpx pair -p booli                        # prints a pair code → approve in ContextMint Bridge\n```\n\nRequirements: the **ContextMint Bridge** browser extension installed (from\nhttps://github.com/nullnet-app/contextmint-bridge/releases — Chrome: load the chrome zip\nunpacked; Safari isn't available yet — use Chrome for now), with an open\n`www.booli.se` tab, and its Chrome **Site access** allowing `booli.se`.\nContextMint Bridge is the fetchproxy extension renamed, same maintainer; source at\nhttps://github.com/nullnet-app/contextmint-bridge (build it, or verify a release zip with its published `.sha256`).\nPairing persists — after the first approval every later `fpx` call reuses it.\n\n## Core call\n\nEvery query is a POST of `{\"query\": \"...\", \"variables\": {...}}` to the GraphQL\nendpoint. Write the body to a file (a heredoc avoids shell-quoting the GraphQL\nstring), then send it so stdout is the GraphQL JSON, ready for `jq`:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nReady-to-run query bodies (area lookup, for-sale search, sold search, property\ndetail) with `jq` recipes are in `references/graphql-queries.md`. Exhaustive\nfield lists live in the repo at `src/graphql.ts` — the operations here are\ncompact, live-verified subsets.\n\n## The one rule: resolve the area first\n\nBooli searches take a numeric **area id** (`areaId`), never a place name.\nAlways resolve the name first, take the `id`, then search:\n\n```sh\nprintf '{\"query\":\"query($s:String!){areaSuggestionSearch(search:$s){suggestions{id displayName}}}\",\"variables\":{\"s\":\"Nacka\"}}' > /tmp/a.json\nfpx post-json 'https://www.booli.se/graphql' @/tmp/a.json -p booli \\\n  | jq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n# 76    "},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"booli-fpx\",\n  \"version\": \"2.1.8\",\n  \"publishedAt\": 1791588244677\n}"},{"path":"references/graphql-queries.md","content":"# Booli GraphQL queries for fpx\n\nReady-to-run bodies for `fpx post-json 'https://www.booli.se/graphql' @file -p booli`.\nEach block is the full POST body (`{\"query\",\"variables\"}`). All field names and\ninput shapes are live-verified against the endpoint; the exhaustive field\nselections live in the repo at `src/graphql.ts` (these are compact subsets).\n\nEndpoint: `https://www.booli.se/graphql` · Method: POST · anonymous.\nIntrospection is disabled — probe field names by sending a query and reading the\nvalidation errors (that's how these were pinned).\n\nWrite the body to a file (heredoc avoids shell-quoting the GraphQL string),\nthen send it:\n\n```sh\ncat > /tmp/bq.json <<'JSON'\n{ \"query\": \"…\", \"variables\": { … } }\nJSON\nfpx post-json 'https://www.booli.se/graphql' @/tmp/bq.json -p booli | jq '.data'\n```\n\nAlways check for GraphQL errors: `jq '.errors // empty'` (an errors array can\nride in an HTTP-200 body).\n\nNumeric fields come as `FormattedValue { raw, formatted }` — select `{ raw }`\nfor the number, `{ formatted }` for the display string.\n\n---\n\n## 1. Resolve an area → areaId (do this first)\n\n`search` is free text.\n\n```json\n{\n  \"query\": \"query Areas($s: String!) { areaSuggestionSearch(search: $s) { suggestions { id displayName } } }\",\n  \"variables\": { \"s\": \"Nacka\" }\n}\n```\n\n```sh\njq -r '.data.areaSuggestionSearch.suggestions[] | \"\\(.id)\\t\\(.displayName)\"'\n```\n\n## 2. Search for-sale listings\n\n`input` is a `SearchRequest`: `areaId` (string, from step 1), `page` (1-based),\n`ascending`, `excludeAncestors: true`, `facets: []`, `filters: [{key,value}]`,\n`sort` (`\"\"` = newest, else `listPrice` / `listSqmPrice` / `rooms` /\n`livingArea` / `rent` / `plotArea` / `published`). `result` mixes `Listing` and\n`Project` (new-construction) nodes — take the `... on Listing` ones.\n\nFilter keys (both key and value are strings): `objectType` (`Lägenhet`, `Villa`,\n`Kedjehus-Parhus-Radhus`, `Fritidshus`, `Gård`, `Tomt/Mark`), `minRooms`/`maxRooms`,\n`minLivingArea`/`maxLivingArea`, `minPlotArea`/`maxPlotArea`,\n`minListPrice`/`maxListPrice`, `minListSqmPrice`/`maxListSqmPrice`,\n`minConstructionYear`/`maxConstructionYear`, `isNewConstruction` (`1`/`0`).\n\n```json\n{\n  \"query\": \"query SearchForSale($input: SearchRequest) { searchForSale(input: $input) { totalCount pages result { __typename ... on Listing { booliId objectType tenureForm streetAddress descriptiveAreaName url rooms { raw } livingArea { raw } rent { raw } listPrice { raw formatted } listSqmPrice { raw } location { region { municipalityName } } } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"areaId\": \"76\",\n      \"page\": 1,\n      \"ascending\": false,\n      \"excludeAncestors\": true,\n      \"facets\": [],\n      \"filters\": [\n        { \"key\": \"objectType\", \"value\": \"Lägenhet\" },\n        { \"key\": \"minRooms\", \"value\": \"3\" },\n        { \"key\": \"maxListPrice\", \"value\": \"6000000\" }\n      ],\n      \"sort\": \"\"\n    }\n  }\n}\n```\n\n```sh\njq -r '.data.searchForSale | \"total=\\(.totalCount) pages=\\(.pages)\", (.result[] | select(.__typename==\"Listin"},{"path":"skill-card.md","content":"## Description:\n\nHelps agents query public Swedish property listings, sold prices, and property details on Booli through a browser-connected command-line tool.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and agents use this skill to look up Swedish areas, search active and sold property listings, and inspect property details without running a separate MCP server.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Installing an unverified CLI or browser extension can expose the browser environment.\n\nMitigation: Verify the @fetchproxy/cli package and ContextMint Bridge source or release before installing; limit extension site access to booli.se.\n\nRisk: Sensitive personal data in a GraphQL request is sent to Booli.\n\nMitigation: Avoid including sensitive personal data in request bodies unless you intend to send it to Booli.\n\nRisk: A successful command can still contain GraphQL errors or too few sold listings for reliable comparisons.\n\nMitigation: Check the response errors and sample count before relying on property results or market summaries.\n\n## Reference(s):\n\n- [Booli GraphQL query examples](references/graphql-queries.md)\n- [ClawHub skill release](https://clawhub.ai/chrischall/skills/booli-fpx)\n- [ContextMint Bridge source](https://github.com/nullnet-app/contextmint-bridge)\n- [ContextMint Bridge releases](https://github.com/nullnet-app/contextmint-bridge/releases)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration guidance, JSON data]\n\n**Output Format:** [Markdown guidance with shell commands and GraphQL JSON examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Property prices are in SEK and areas in m².]\n\n## Skill Version(s):\n\n2.1.8 (source: server-resolved ClawHub release)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment."}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"Query booli.se (Swedish property portal) from a shell with the fpx CLI (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas, search for-sale and sold listings (slutpriser), and read property detail via one-shot GraphQL calls through a signed-in browser tab. Use when you want Booli data without the MCP, in a script, or on a machine where the MCP isn't installed. Skill: booli-fpx Owner: chrischall Summary: Query booli.se (Swedish property portal) from a shell with the fpx CLI (@fetchproxy/cli) instead of running the booli-mcp server — resolve areas, search for-sale and sold listings (slutpriser), and read property detail via one-shot GraphQL calls through a signed-in browser tab. Use when you want Booli data without the MCP, in a script, or on a machine where the MCP isn't in","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1305,"uniquenessScore":46,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T07:29:50.974Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T07:29:50.974Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-11T10:52:11.743Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}