Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
75c8873
feat: add country selection for AT/DE support
udondan May 30, 2026
c9dd26c
docs: update README and SKILL.md for AT/DE country support
udondan May 30, 2026
8954ba4
docs: combine retailer table with AT/DE columns in SKILL.md
udondan May 30, 2026
8dd2d2e
docs: align retailer table columns in SKILL.md
udondan May 30, 2026
f64c91c
feat: add tests for getApiBase
udondan May 30, 2026
b45ef9c
chore: update package description for AT/DE support
udondan May 30, 2026
8d021cf
chore: drop CH — api.marktguru.ch does not exist
udondan May 30, 2026
bbda886
chore: remove Switzerland from package description
udondan May 30, 2026
d52f2cf
fix: pass country to extractApiKey during auto-login in search
udondan May 30, 2026
8277f3f
fix: clear API key when country changes in set-country
udondan May 30, 2026
beb63ac
fix: only report apiKeyCleared:true in JSON when a key was actually c…
udondan May 30, 2026
4ac9f76
fix: validate country code before interpolating into URLs
udondan May 30, 2026
37840bd
chore: use fr as unsupported country test case instead of de.evil.com
udondan May 30, 2026
6f65c21
Merge pull request #1 from udondan/worktree-feat+country-selection
udondan May 30, 2026
4cf577e
chore: set up release-please, npm trusted publishing and publish as @…
udondan Sep 17, 2026
2fc2c58
chore(deps): update dependency @types/node to v20.19.43 (#4)
renovate[bot] Sep 17, 2026
f53424a
chore: add npm keywords for package discoverability (#6)
udondan Sep 17, 2026
83f74d9
chore(deps): update dependency header-generator to v2.1.88 (#7)
renovate[bot] Sep 17, 2026
ddc9b4a
chore(deps): update dependency tsx to v4.23.13 (#8)
renovate[bot] Sep 17, 2026
0d8deab
chore(deps): update dependency @types/node to v22 (#9)
renovate[bot] Sep 17, 2026
cfb06c0
chore(deps): update dependency commander to v15 (#10)
renovate[bot] Sep 17, 2026
1217052
chore: lint with eslint and prettier, drop oxlint (#12)
udondan Sep 17, 2026
95d2a73
fix: keep --version in sync with the released version (#13)
udondan Sep 17, 2026
0be5083
chore: cache npm downloads in CI and stop automerging majors (#14)
udondan Sep 17, 2026
ef509ce
chore: add AGENTS.md with contributor guidance for coding agents (#15)
udondan Sep 17, 2026
9992cec
chore(deps): update dependency @types/node to v24 (#16)
renovate[bot] Sep 17, 2026
51a06f4
chore: move SKILL.md into skills/ and document npx skills install (#17)
udondan Sep 17, 2026
79edddb
chore(main): release 1.0.0 (#3)
udondan Sep 17, 2026
fdb70d0
chore: drop header-generator dependency (#18)
udondan Sep 18, 2026
b769fcd
chore(main): release 1.0.1 (#19)
udondan Sep 18, 2026
977c1d3
chore(deps): update dependency @types/node to v24.13.5 (#20)
renovate[bot] Sep 19, 2026
a53c5be
chore(deps): update dependency prettier to v3.9.8 (#21)
renovate[bot] Sep 20, 2026
cbf24d6
chore(deps): update dependency eslint to v10.11.0 (#22)
renovate[bot] Sep 22, 2026
86f6338
chore(deps): update dependency @types/node to v24.13.6 (#23)
renovate[bot] Sep 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/automerge-schedule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
name: Schedule Release Automerge

on:
schedule:
- cron: "0 9 * * 1" # Every Monday at 9am UTC
workflow_dispatch:

jobs:
automerge:
if: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
runs-on: ubuntu-latest
steps:
- name: Enable auto-merge on release-please PR
env:
GH_TOKEN: ${{ secrets.OVERRIDE_TOKEN }}
run: |
PRS=$(gh pr list \
--repo "${{ github.repository }}" \
--label "autorelease: pending" \
--base "${{ github.event.repository.default_branch }}" \
--state open \
--json number)

PR_COUNT=$(printf '%s' "$PRS" | jq 'length')

if [ "$PR_COUNT" -gt 1 ]; then
echo "Expected at most one open release-please PR, found $PR_COUNT"
exit 1
fi

PR=$(printf '%s' "$PRS" | jq -r '.[0].number // empty')

if [ -n "$PR" ]; then
gh pr merge "$PR" --auto --squash --delete-branch \
--repo "${{ github.repository }}"
echo "Auto-merge enabled on PR #$PR"
else
echo "No open release-please PR found"
fi
18 changes: 0 additions & 18 deletions .github/workflows/ci.yml

This file was deleted.

20 changes: 20 additions & 0 deletions .github/workflows/pr-conventional-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: Conventional PR Title

on:
pull_request_target:
types:
- opened
- reopened
- edited
- synchronize

jobs:
conventional-pr-title:
runs-on: ubuntu-latest
permissions:
statuses: write
steps:
- uses: aslafy-z/conventional-pr-title-action@v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
36 changes: 36 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
name: Publish packages

on:
push:
tags:
- "v*"
workflow_dispatch:

jobs:
publish:
runs-on: ubuntu-latest

permissions:
id-token: write
contents: read

steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 1

- uses: actions/setup-node@v7
with:
node-version: 24.x
registry-url: https://registry.npmjs.org

- name: Install dependencies
run: npm ci

- name: Test
run: npm test

- name: Publish to npm
run: npm publish --provenance --access public
20 changes: 20 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: release-please

on:
workflow_dispatch:
push:
branches:
- main

permissions:
contents: write
pull-requests: write

jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v5
with:
token: ${{ secrets.OVERRIDE_TOKEN }}
61 changes: 61 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
name: Test

permissions:
contents: read

concurrency:
group: test
cancel-in-progress: false

on:
pull_request:
branches:
- main
push:
branches:
- main
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest

defaults:
run:
shell: bash

steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 1

- uses: actions/setup-node@v7
with:
node-version: 24.x

- name: Cache node modules
id: cache-npm
uses: actions/cache@v6
env:
cache-name: cache-node-modules
with:
path: .npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-

- name: Install dependencies
run: make install

- name: Linting
run: make eslint

- name: Test
run: make test

- name: Validate package
run: make validate-package
12 changes: 5 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
scripts/try-http-login.*
node_modules/
.claude
.npm/
*.tgz
dist/
node_modules/
npm-debug.log*
pnpm-debug.log*
yarn-error.log*
.DS_Store
AGENTS.md
CLAUDE.md
scripts/try-http-login.*
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dist
node_modules
package-lock.json
CHANGELOG.md
tests/fixtures
11 changes: 11 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"singleQuote": true,
"overrides": [
{
"files": ["*.yaml", "*.yml"],
"options": {
"singleQuote": false
}
}
]
}
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "1.0.1"
}
105 changes: 105 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# AGENTS.md

Guidance for coding agents working in this repository.

## Commands

The Make targets are exactly what CI runs (`.github/workflows/test.yml`), so
prefer them:

```bash
make install # npm clean-install --prefer-offline --cache .npm
make eslint # lint
make format # prettier --write .
make test # build, then run the test suite
make validate-package # npm pack and assert the tarball contents
```

Running the CLI locally — the `--` separator is required to pass flags through
npm:

```bash
npm run dev -- --help # tsx, straight from TypeScript
npm start -- --help # from dist/, needs a build first
```

Single test file / single test case:

```bash
npm run build && node --test tests/query.test.js
npm run build && node --test --test-name-pattern "<name>" tests/query.test.js
```

## Architecture

ESM throughout (`"type": "module"`), TypeScript compiled to `dist/` with
`NodeNext` resolution — relative imports must carry the `.js` extension.

`src/cli.ts` is the Commander entry point and the `bin` target. It delegates to
a command module in `src/commands/`, which calls `ensureApiKey` (reads
`src/config.ts`, falls through to `src/auth.ts` when no key is stored), then
`search()` in `src/api.ts`, then renders through `formatResultsText` or
`simplifyOffer` in `src/commands/search.ts`. `src/query.ts` is pure and
I/O-free, which is why it carries most of the test coverage.

Results go to stdout; `--explain` and every warning go to stderr, so `--json`
output stays pipeable.

## Constraints

These are the things that are easy to break without noticing.

- **Tests run against `dist/`.** `tests/*.test.js` import `../dist/*.js`, so a
stale build silently tests old code. `npm test` runs `npm run build` first for
exactly this reason — never invoke bare `node --test`.
- **release-please owns the version.** `src/cli.ts` carries
`.version('0.1.0') // x-release-please-version`; the trailing comment is
load-bearing and matches `extra-files` in `release-please-config.json`. Never
hand-edit the version in `package.json`, `src/cli.ts`, or
`.release-please-manifest.json`.
- **Country validation is a security guard.** The country code is interpolated
into a hostname, so `getApiBase()` and `extractApiKey()` hard-validate against
`VALID_COUNTRIES` before building a URL. Do not relax this. Only `at` and `de`
exist.
- **API keys are country-scoped.** `set-country` deliberately clears `apiKey`
when the country actually changes, and `search` auto-logs-in using the
configured country.
- **`login` is scraping, not an account login.** It fetches the public site,
regex-scans the HTML and boot scripts for an embedded key, and brute-force
validates candidates against the live search endpoint. It makes real network
requests, cannot be unit-tested offline, and breaks whenever Marktguru ships a
new frontend bundle. `tests/live.test.js` runs the built CLI end to end
against the live sites for both countries (isolated `HOME`), so `make test`
needs network access and fails when the scraper breaks.
- **The key is stored in plaintext** at `~/.marktguru/config.json`. The `config`
and `login` output truncates it — preserve that.
- **`--retailer` filters client-side** on `advertisers[].name`, which is why
`runSearch` over-fetches 100 results and rewrites `totalResults` to the
post-filter count.
- **Two different default limits:** 10 in `src/commands/search.ts`, 20 in the
`search()` fallback in `src/api.ts`.
- **ESLint covers only `src/` TypeScript** via the `tsconfig-lint.json` project.
The JS tests are not linted, and a new `.ts` file outside `src/` makes
type-aware linting fail.
- **Exact dependency versions only** — no `^` or `~`.
- **`make validate-package` asserts a hardcoded file list.** Extend it when a
new shipped entrypoint is added. It currently omits `dist/commands/*.js` even
though those ship.

## Release flow

PRs are squash-merged, so the **PR title must be a Conventional Commit** — it
becomes the changelog entry, and `pr-conventional-title.yml` enforces it.
Renovate forces `chore:` for dependency bumps and does not automerge majors.
release-please opens a release PR on every push to `main`; a Monday 09:00 UTC
cron automerges the single PR labeled `autorelease: pending`, so releases go out
weekly rather than per merge. The resulting `v*` tag triggers
`npm publish --provenance` via npm trusted publishing (no `NPM_TOKEN`).

## Keep in sync

`skills/marktguru-grocery-deals/SKILL.md` documents _using_ the CLI for agents
that consume it; `README.md` documents the same surface for humans. Changing a
command or flag means updating both. The directory name has to match the
skill's frontmatter `name`, and the `skills/<name>/SKILL.md` layout is what the
`skills` CLI discovers and installs.
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Changelog

## [1.0.1](https://github.com/udondan/marktguru-cli/compare/v1.0.0...v1.0.1) (2026-09-18)


### Miscellaneous Chores

* drop header-generator dependency ([fdb70d0](https://github.com/udondan/marktguru-cli/commit/fdb70d072693d2ac99c33ccc54060a61c13d40a1))

## [1.0.0](https://github.com/udondan/marktguru-cli/compare/v0.1.0...v1.0.0) (2026-09-17)


### Features

* add country selection for AT/DE support ([6f65c21](https://github.com/udondan/marktguru-cli/commit/6f65c216014e935f7cbfe168e2e4deff1326e6ab))
* add country selection for AT/DE support ([75c8873](https://github.com/udondan/marktguru-cli/commit/75c8873a661381d11c225400a41ad594c3af90c9))
* add tests for getApiBase ([f64c91c](https://github.com/udondan/marktguru-cli/commit/f64c91cc0106f9e4aaebe607d11a19b9143014a9))


### Bug Fixes

* clear API key when country changes in set-country ([8277f3f](https://github.com/udondan/marktguru-cli/commit/8277f3f28f0177b2617564b1e3d035496eef3d5d))
* keep --version in sync with the released version ([#13](https://github.com/udondan/marktguru-cli/issues/13)) ([95d2a73](https://github.com/udondan/marktguru-cli/commit/95d2a7373f5994fd92d6bad5e14eb89fd8eb1066))
* only report apiKeyCleared:true in JSON when a key was actually cleared ([beb63ac](https://github.com/udondan/marktguru-cli/commit/beb63ac1584e3b28bc05936c1969b7d8c7c6ca59))
* pass country to extractApiKey during auto-login in search ([d52f2cf](https://github.com/udondan/marktguru-cli/commit/d52f2cf28059922826958fedd8ea0e23c16db73f))
* validate country code before interpolating into URLs ([4ac9f76](https://github.com/udondan/marktguru-cli/commit/4ac9f76a7f4fcad0a5f9da829ffdfd5c856a1ca1))


### Miscellaneous Chores

* set up release-please, npm trusted publishing and publish as @udondan/marktguru-cli ([#2](https://github.com/udondan/marktguru-cli/issues/2)) ([4cf577e](https://github.com/udondan/marktguru-cli/commit/4cf577e30fe57269e9a344ad3483078424d884d3))
1 change: 1 addition & 0 deletions CLAUDE.md
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
MIT License

Copyright (c) 2026 Manuel Maly
Copyright (c) 2026 Daniel Schroeder

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
Loading