Skill: pvoo/marktplaats (version 0.3.0)

--- name: marktplaats description: Search Marktplaats.nl classifieds across all categories with filtering support. homepage: https://www.marktplaats.nl metadata: {"clawdbot":{"emoji":"🇳🇱","requires":{"bins":["node"]}}} --- # Marktplaats Skill Search any Marktplaats category, filter by condition/delivery, list categories, and fetch listing details. ## CLI ```bash npm install -g {baseDir} # Search marktplaats-search "<query>" [options] -n, --limit <num> Number of results (default: 10, max: 100) -c, --category <id> Category ID (top-level) --min-price <cents> Minimum price in euro cents --max-price <cents> Maximum price in euro cents --sort <relevance|date|price-asc|price-desc> --param key=value Filter by attribute (repeatable) --details [target] Fetch details for "first" result or URL/ID --json Output raw JSON # Categories marktplaats-categories # main categories marktplaats-categories <id> # sub-categories for a category --json Output raw JSON ``` ## Filters Common filters work with `--param`: | Filter | Values | |--------|--------| | `condition` | Nieuw, Refurbished, Zo goed als nieuw, Gebruikt, Niet werkend | | `delivery` | Ophalen, Verzenden | | `buyitnow` | true (Direct Kopen only) | English aliases also work: `new`, `used`, `like-new`, `pickup`, `shipping` ## Examples ```bash # New laptops only marktplaats-search "laptop" --param condition=Nieuw # Used cameras with shipping marktplaats-search "camera" --param condition=Gebruikt --param delivery=Verzenden # Cars under €15k marktplaats-search "bmw 330d" --category 96 --max-price 1500000 # Furniture, pickup only marktplaats-search "eettafel" --param delivery=Ophalen --sort price-asc # Get details for first result marktplaats-search "iphone" -n 1 --details first # List all categories marktplaats-categories # BMW sub-categories marktplaats-categories 96 ``` ## Programmatic API (ESM) ```js import { searchListings, fetchCategories, getListingDetails } from '{baseDir}'; // Search with filters const results = await searchListings({ query: 'espresso machine', params: { condition: 'Nieuw', delivery: 'Verzenden' }, limit: 10, }); // Get categories const categories = await fetchCategories(); // top-level const bmw = await fetchCategories(96); // BMW sub-categories // Fetch listing details const details = await getListingDetails(results.listings[0].vipUrl); ``` ## Notes - Prices are in **euro cents** (€15,000 = 1500000) - Results include full URLs to listings - Use `--json` to see all available facets and filter keys - Filter hints are shown after search results

path: README.md
content: # Marktplaats CLI & JS Client

Search Marktplaats.nl classifieds with a clean CLI and JavaScript client. Works for all categories, supports attribute filtering, and includes a detail fetcher.

## Install

```bash
npm install -g .
# or use locally with: npx marktplaats-search "road bike"
```

Requires Node 18+ (uses built-in `fetch`).

## CLI

### Search

```bash
marktplaats-search "<query>" [options]

Options:
  -n, --limit <num>         Number of results (default 10, max 100)
  -c, --category <id>       Category ID (top-level)
  --min-price <cents>       Minimum price in euro cents
  --max-price <cents>       Maximum price in euro cents
  --sort <relevance|date|price-asc|price-desc>
  --param key=value         Filter by attribute (repeatable)
  --details [target]        Fetch detail page for "first" result or a URL/ID
  --json                    Output raw JSON from the API
  -h, --help                Show help
```

### Filters

Filter results with `--param`:

| Filter | Values |
|--------|--------|
| `condition` | Nieuw, Refurbished, Zo goed als nieuw, Gebruikt, Niet werkend |
| `delivery` | Ophalen, Verzenden |
| `buyitnow` | true (Direct Kopen only) |

English aliases work too: `new`, `used`, `like-new`, `broken`, `pickup`, `shipping`

### Examples

```bash
# New laptops only
marktplaats-search "laptop" --param condition=Nieuw

# Used cameras with shipping
marktplaats-search "camera" --param condition=Gebruikt --param delivery=Verzenden

# BMW under €15k, sorted by price
marktplaats-search "bmw 330d" --category 96 --max-price 1500000 --sort price-asc

# Furniture, pickup only
marktplaats-search "eettafel" --param delivery=Ophalen

# Get full details for first result
marktplaats-search "iphone" -n 1 --details first
```

### Categories

```bash
marktplaats-categories            # main categories
marktplaats-categories <id>       # sub-categories

Options:
  --json        Output raw JSON
  -h, --help    Show help
```

Examples:

```bash
marktplaats-categories           # Show all 36 main categories
marktplaats-categories 91        # Cars sub-categories (BMW, Audi, etc.)
marktplaats-categories 504       # Home & Living sub-categories
```

## Programmatic API

```js
import { searchListings, fetchCategories, getListingDetails } from 'marktplaats';

// Search with filters
const results = await searchListings({
  query: 'espresso machine',
  limit: 10,
  params: { condition: 'Nieuw', delivery: 'Verzenden' },
});

console.log(`Found ${results.total} results`);
results.listings.forEach(listing => {
  console.log(`${listing.title} - ${listing.priceDisplay}`);
});

// Get categories
const categories = await fetchCategories();  // top-level
const bmw = await fetchCategories(96);       // BMW sub-categories

// Fetch listing details
const details = await getListingDetails(results.listings[0].vipUrl);
console.log(details.description);
```

## API Reference

### searchListings(options)

Search for listings.

**Options:**
- `query` (string, required) - Search query
- `limit` (number) - Max results (default: 10, max: 100)
- `categoryId` (number) - Category ID filter
- `minPrice` (number) - Min price in cents
- `maxPrice` (number) - Max price in cents
- `sort` ('relevance' | 'date' | 'price-asc' | 'price-desc')
- `params` (object) - Attribute filters like `{ condition: 'Nieuw' }`

**Returns:** `{ listings, total, facets, categories, raw }`

### fetchCategories(parentId?)

Get category options.

**Returns:** `{ categories, facets, raw }`

### getListingDetails(urlOrPath)

Fetch details from a listing page.

**Returns:** `{ url, description, priceDisplay, images, structuredData, contentLength }`

## Notes

- Prices are in **euro cents** (€15,000 = 1500000)
- Results include full clickable URLs
- Use `--json` to explore all available facets and filter options
- Filter hints are shown after each search

## Testing

```bash
npm test
```

## License

MIT


path: bin/marktplaats-categories.js
content: #!/usr/bin/env node
import { fetchCategories } from '../src/index.js';

const HELP_TEXT = `
Marktplaats categories explorer

Usage:
  marktplaats-categories            Show all main categories
  marktplaats-categories <id>       Show sub-categories for the given category

Options:
  --json          Output raw JSON
  -h, --help      Show this help

Examples:
  marktplaats-categories
  marktplaats-categories 91        # Sub-categories for Cars
  marktplaats-categories 504       # Sub-categories for Home & Living
`.trim();

/**
 * Render category rows to stdout.
 * @param {Array} categories Category list from the API.
 */
function printCategories(categories) {
  if (!categories || categories.length === 0) {
    console.log('No categories returned.');
    return;
  }
  for (const cat of categories) {
    const label = cat.label || cat.name || 'Unnamed';
    console.log(`${String(cat.id).padEnd(8)} ${label}`);
  }
}

/**
 * Render a short list of available filters from facets.
 * @param {Array} facets Facet list from the API.
 */
function printFilters(facets) {
  if (!facets || facets.length === 0) return;
  console.log('\nFilters you can use in search (pass with --param):');
  for (const facet of facets.slice(0, 8)) {
    const label = facet.label || facet.key || 'Filter';
    const param = facet.queryParameterName || facet.urlParameterName || facet.key || 'param';
    const values = facet.categories?.slice(0, 4)?.map((c) => c.label || c.name)?.join(' | ')
      || facet.attributeGroup?.slice(0, 4)?.map((a) => a.attributeValueLabel)?.join(' | ')
      || '';
    console.log(`  ${label} → use ?${param}=... ${values ? `(${values}${facet.categories?.length > 4 || facet.attributeGroup?.length > 4 ? ' ...' : ''})` : ''}`);
  }
}

async function main() {
  const argv = process.argv.slice(2);
  const showHelp = argv.includes('-h') || argv.includes('--help');
  const json = argv.includes('--json');
  const parentArg = argv.find((arg) => !arg.startsWith('-'));
  const parentId = parentArg ? Number(parentArg) : undefined;

  if (showHelp) {
    console.log(HELP_TEXT);
    process.exit(0);
  }

  if (parentArg && Number.isNaN(parentId)) {
    console.error('Category ID must be a number.');
    process.exit(1);
  }

  try {
    const result = await fetchCategories(parentId);
    if (json) {
      console.log(JSON.stringify(result.raw, null, 2));
      process.exit(0);
    }

    if (parentId == null) {
      console.log('Main categories:\n');
    } else {
      console.log(`Sub-categories for ${parentId}:\n`);
    }

    printCategories(result.categories);
    printFilters(result.facets);

    if (parentId == null) {
      console.log('\nTip: marktplaats-search "<query>" --json shows facets for additional filters.');
    } else {
      console.log('\nTip: Use --param <key>=<value> with marktplaats-search using the filter keys above.');
    }
  } catch (error) {
    console.error(`Error: ${error.message}`);
    process.exit(1);
  }
}

main();


path: package.json
content: {
  "name": "marktplaats",
  "version": "0.3.0",
  "description": "Marktplaats search CLI and lightweight JS client for all categories.",
  "type": "module",
  "bin": {
    "marktplaats-search": "./bin/marktplaats-search.js",
    "marktplaats-categories": "./bin/marktplaats-categories.js"
  },
  "exports": "./src/index.js",
  "files": [
    "bin",
    "src",
    "README.md",
    "LICENSE",
    "SKILL.md"
  ],
  "scripts": {
    "test": "node ./bin/marktplaats-search.js --help && node ./bin/marktplaats-categories.js --help"
  },
  "keywords": [
    "marktplaats",
    "classifieds",
    "search",
    "cli"
  ],
  "engines": {
    "node": ">=18"
  },
  "license": "MIT",
  "author": "Paul Voorschot"
}


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: clean (LOW)