basketeer 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,7 +23,7 @@ Run your weekly shop from code, the terminal, or an AI agent. Everything but sig
23
23
  <br>
24
24
 
25
25
  [![CI](https://github.com/tobyandrews1985/basketeer/actions/workflows/ci.yml/badge.svg)](https://github.com/tobyandrews1985/basketeer/actions/workflows/ci.yml)
26
- [![tests](https://img.shields.io/badge/tests-71%20passing-brightgreen.svg)](tests/)
26
+ [![tests](https://img.shields.io/badge/tests-75%20passing-brightgreen.svg)](tests/)
27
27
  [![license: MIT](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
28
28
  [![node >=18](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
29
29
 
@@ -39,7 +39,7 @@ Tesco has no public API, and the usual approach (scraping the DOM) shatters on t
39
39
  - **Agent-ready.** A stdio MCP server lets Claude or any MCP client run the shop. Read-only and destructive tools are annotated, and checkout never pays.
40
40
  - **Typed and lean.** One fully-typed client you import; the CLI and MCP server are built on it. The data path imports no third-party packages (the three runtime deps — commander, the MCP SDK, and zod — are pulled only by the CLI and MCP server). It is pure `fetch` with no Node-only APIs, so it runs on Node and Node-compatible runtimes.
41
41
  - **Safe.** `checkout()` stops at the payment URL. A human finishes 3-D Secure in a browser, by design.
42
- - **Tested.** 71 tests across the data plane and its parsers.
42
+ - **Tested.** 75 tests across the data plane and its parsers.
43
43
 
44
44
  ## Nutrition, the part nothing else has
45
45
 
@@ -260,7 +260,7 @@ Personal-account interoperability automation: your account, your data. The clien
260
260
 
261
261
  ```bash
262
262
  npm install
263
- npm test # 71 tests: vitest unit + regression + smoke
263
+ npm test # 75 tests: vitest unit + regression + smoke
264
264
  npm run build # clean build to dist/
265
265
  npm run example:lookup
266
266
  ```
package/dist/cli.js CHANGED
@@ -8,10 +8,10 @@
8
8
  * to stderr with a non-zero exit. Per the SDK's ethics ceiling, `checkout` only
9
9
  * hands back a payment URL — a human completes payment in a browser.
10
10
  */
11
- import { fileURLToPath } from "node:url";
12
11
  import { Command, InvalidArgumentError } from "commander";
13
12
  import { BrowserAuthBackend } from "./auth/browser/playwright.js";
14
13
  import { Basketeer, BasketeerError, FileTokenStore } from "./index.js";
14
+ import { isMainModule } from "./is-main.js";
15
15
  /** Pretty-print any JSON-serialisable value to stdout. */
16
16
  function emit(value) {
17
17
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
@@ -201,7 +201,7 @@ program
201
201
  });
202
202
  // --- entrypoint -------------------------------------------------------------
203
203
  // Only parse argv when run as a binary, so the module is importable (tests).
204
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
204
+ if (isMainModule(import.meta.url)) {
205
205
  program.parseAsync().catch((err) => {
206
206
  const name = err instanceof BasketeerError ? err.name : "Error";
207
207
  const message = err instanceof Error ? err.message : String(err);
@@ -0,0 +1,8 @@
1
+ /**
2
+ * True when `moduleUrl` (pass `import.meta.url`) is the entrypoint the process
3
+ * was launched with. Resolves symlinks first, so it still holds when the module
4
+ * runs via an installed bin — npx, a global install, or `node_modules/.bin` —
5
+ * where `process.argv[1]` is a symlink to the real file and a raw `===` against
6
+ * `fileURLToPath(import.meta.url)` would wrongly be false.
7
+ */
8
+ export declare function isMainModule(moduleUrl: string): boolean;
@@ -0,0 +1,20 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ /**
4
+ * True when `moduleUrl` (pass `import.meta.url`) is the entrypoint the process
5
+ * was launched with. Resolves symlinks first, so it still holds when the module
6
+ * runs via an installed bin — npx, a global install, or `node_modules/.bin` —
7
+ * where `process.argv[1]` is a symlink to the real file and a raw `===` against
8
+ * `fileURLToPath(import.meta.url)` would wrongly be false.
9
+ */
10
+ export function isMainModule(moduleUrl) {
11
+ const entry = process.argv[1];
12
+ if (!entry)
13
+ return false;
14
+ try {
15
+ return realpathSync(entry) === fileURLToPath(moduleUrl);
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
@@ -22,12 +22,12 @@
22
22
  * Run: `basketeer-mcp` (stdio). Configure it as an MCP server in your agent.
23
23
  */
24
24
  import { createHash } from "node:crypto";
25
- import { fileURLToPath } from "node:url";
26
25
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
27
26
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
28
27
  import { z } from "zod";
29
28
  import { BrowserAuthBackend } from "./auth/browser/playwright.js";
30
29
  import { Basketeer, BasketeerError, FileTokenStore } from "./index.js";
30
+ import { isMainModule } from "./is-main.js";
31
31
  /** Wrap a value as MCP JSON text content. */
32
32
  function json(value) {
33
33
  return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
@@ -55,7 +55,7 @@ export function confirmToken(parts) {
55
55
  * Pure: no filesystem/auth/transport side effects — safe to call from a test.
56
56
  */
57
57
  export function buildServer(client) {
58
- const server = new McpServer({ name: "basketeer", version: "0.1.0" });
58
+ const server = new McpServer({ name: "basketeer", version: "0.1.1" });
59
59
  // --- reads (anonymous OK) -------------------------------------------------
60
60
  server.tool("basketeer_search", "Search the Tesco grocery catalogue. Returns matching products with SKU, title, price, and any offers.", {
61
61
  query: z.string().describe("Search terms, e.g. 'semi skimmed milk'."),
@@ -153,7 +153,7 @@ export function buildServer(client) {
153
153
  // Only build the client (which reads ~/.basketeer/session.json) and attach the
154
154
  // stdio transport when run as a binary, so the module can be imported (e.g. by a
155
155
  // smoke test) without any filesystem/auth side effects or starting the server.
156
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
156
+ if (isMainModule(import.meta.url)) {
157
157
  const store = new FileTokenStore();
158
158
  const client = new Basketeer({
159
159
  session: await store.load(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "basketeer",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Typed, pure-HTTP TypeScript SDK for your own Tesco grocery account — search, basket, delivery slots, and orders over plain fetch. Browser only for login + payment.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -56,12 +56,16 @@
56
56
  "keywords": [
57
57
  "tesco",
58
58
  "groceries",
59
+ "grocery",
60
+ "supermarket",
61
+ "nutrition",
59
62
  "automation",
60
63
  "graphql",
61
64
  "client",
62
65
  "mcp",
63
66
  "cli",
64
- "ai-agent"
67
+ "ai-agent",
68
+ "uk"
65
69
  ],
66
70
  "dependencies": {
67
71
  "@modelcontextprotocol/sdk": "^1.0.0",