@keypro/cli 0.1.0 → 0.1.4

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/dist/index.js CHANGED
@@ -133,6 +133,15 @@ var KeyproClient = class {
133
133
  orderKeys(id) {
134
134
  return this.request("GET", `/api/v1/orders/${id}/keys`);
135
135
  }
136
+ orderCancel(id) {
137
+ return this.request("POST", `/api/v1/orders/${id}/cancel`);
138
+ }
139
+ orderPaymentPreview(id, newMethod) {
140
+ return this.request("POST", `/api/v1/orders/${id}/payment/preview`, { newMethod });
141
+ }
142
+ orderChangePayment(id, body) {
143
+ return this.request("POST", `/api/v1/orders/${id}/payment`, body);
144
+ }
136
145
  licenseKeys() {
137
146
  return this.request("GET", "/api/v1/license-keys");
138
147
  }
@@ -179,6 +188,9 @@ var KeyproClient = class {
179
188
  if (type) qs.set("type", type);
180
189
  return this.request("GET", `/api/v1/shipping/parcelshops?${qs}`);
181
190
  }
191
+ exchangeRate() {
192
+ return this.request("GET", "/api/v1/exchange-rate");
193
+ }
182
194
  };
183
195
  function createClient(opts) {
184
196
  return new KeyproClient(opts);
@@ -244,13 +256,15 @@ utmutato AI-agenteknek (Claude Code, Codex) szol.
244
256
 
245
257
  ## Setup
246
258
 
247
- 1. The user needs a KeyPro account (approved reseller) and an API key.
248
- Either run \`keypro login\` (interactive email + password, stores a fresh
249
- key) or create a key on the website under "API kulcsok" and store it:
250
- - env var: KEYPRO_API_KEY=kp_live_... (recommended for agents)
251
- - or config file: ~/.config/keypro/config.json
252
- 2. API base URL: production is the default. For the dev site use
253
- KEYPRO_API_BASE=https://dev.keypro.hu or \`keypro config set api-base ...\`.
259
+ 1. The user needs a KeyPro account (approved reseller) and an API key. Easiest:
260
+ \`keypro setup\` - interactive wizard (asks the server, then API key [default]
261
+ or email+password). Alternatives:
262
+ - \`keypro login\` (email + password, mints + stores a fresh key)
263
+ - a key made on the website under "API kulcsok", stored via
264
+ \`keypro config set api-key kp_live_...\`, the KEYPRO_API_KEY env var
265
+ (recommended for agents), or ~/.config/keypro/config.json
266
+ 2. API base URL: production is the default. For the dev site use \`keypro setup\`,
267
+ KEYPRO_API_BASE=https://dev.keypro.hu, or \`keypro config set api-base ...\`.
254
268
  3. Verify with: \`keypro whoami --json\`
255
269
 
256
270
  ## Output contract
@@ -296,7 +310,13 @@ for gls_parcelshop also \`--parcelshop <ID>\`
296
310
  ## Queries
297
311
 
298
312
  - \`keypro products search <query>\` / \`keypro products get <sku|id>\`
313
+ - \`keypro rate\` - current EUR/HUF rate the shop uses (net prices are stored in
314
+ EUR; HUF price = round(EUR * rate) to whole forint, EUR to 2 decimals)
299
315
  - \`keypro order list [--status <status>]\` / \`keypro order get <id>\`
316
+ - \`keypro order cancel <id>\` - cancel an UNPAID order (bacs / stripe / cod;
317
+ NOT 8-day cheque or already-paid orders)
318
+ - \`keypro order change-payment <id> --payment <method> [--yes]\` - change an
319
+ unpaid order's payment method (preview first; wallet/stripe move money)
300
320
  - \`keypro keys list [--order <id>]\` - delivered license keys
301
321
  - \`keypro invoices list [--order <id>]\` / \`keypro invoices get <id>\`
302
322
  (each invoice has a public \`downloadUrl\` PDF link)
@@ -309,7 +329,7 @@ for gls_parcelshop also \`--parcelshop <ID>\`
309
329
 
310
330
  Register the CLI as a native MCP toolset (recommended for Claude Code):
311
331
 
312
- claude mcp add keypro -- keypro mcp
332
+ claude mcp add keypro -- npx -y @keypro/cli mcp
313
333
 
314
334
  Auth comes from KEYPRO_API_KEY / config; there is no login tool over MCP.
315
335
  The keypro_order_create tool requires the confirmToken from
@@ -431,6 +451,8 @@ function eurFmt(value) {
431
451
  // src/mcp.ts
432
452
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
433
453
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
454
+
455
+ // src/mcp-tools.ts
434
456
  import { z } from "zod";
435
457
  var addressShape = z.object({
436
458
  firstName: z.string().optional(),
@@ -486,8 +508,7 @@ async function run(fn) {
486
508
  return errorResult(err);
487
509
  }
488
510
  }
489
- async function runMcpServer(client) {
490
- const server = new McpServer({ name: "keypro", version: "0.1.0" });
511
+ function registerKeyproTools(server, client) {
491
512
  server.registerTool(
492
513
  "keypro_whoami",
493
514
  {
@@ -496,6 +517,14 @@ async function runMcpServer(client) {
496
517
  },
497
518
  () => run(() => client.me())
498
519
  );
520
+ server.registerTool(
521
+ "keypro_exchange_rate",
522
+ {
523
+ description: "Current EUR->HUF exchange rate the shop uses and displays (ECB daily reference + 3% markup). Product net prices are stored in EUR; the HUF price = round(EUR * rate) to whole forint (EUR shown with 2 decimals). Call this to price products accurately in both currencies.",
524
+ inputSchema: {}
525
+ },
526
+ () => run(() => client.exchangeRate())
527
+ );
499
528
  server.registerTool(
500
529
  "keypro_products_search",
501
530
  {
@@ -574,6 +603,44 @@ async function runMcpServer(client) {
574
603
  },
575
604
  (args) => run(() => client.orderKeys(args.orderId))
576
605
  );
606
+ server.registerTool(
607
+ "keypro_order_cancel",
608
+ {
609
+ description: "Cancel an UNPAID order. Only orders awaiting payment can be cancelled: bacs (bank transfer / proforma), stripe (card checkout not completed), and cod (cash on delivery, still processing). 8-day-deferred (cheque) and already-paid orders CANNOT be cancelled. Idempotent.",
610
+ inputSchema: { orderId: z.number().int().positive() }
611
+ },
612
+ (args) => run(() => client.orderCancel(args.orderId))
613
+ );
614
+ server.registerTool(
615
+ "keypro_order_change_payment_preview",
616
+ {
617
+ description: "Preview changing an UNPAID order's payment method (only on-hold=bacs / pending=stripe orders qualify). Returns the recomputed totals for the new method (cheque adds +5%, cod adds a fixed fee; bacs/wallet/stripe add none) and a confirmToken. ALWAYS show the new totals to the user, then call keypro_order_change_payment.",
618
+ inputSchema: {
619
+ orderId: z.number().int().positive(),
620
+ newMethod: z.enum(["bacs", "cheque", "cod", "wallet", "stripe"])
621
+ }
622
+ },
623
+ (args) => run(() => client.orderPaymentPreview(args.orderId, args.newMethod))
624
+ );
625
+ server.registerTool(
626
+ "keypro_order_change_payment",
627
+ {
628
+ description: "Change an UNPAID order's payment method. Requires the confirmToken from keypro_order_change_payment_preview. wallet debits the KEP balance now and fulfils; cheque/cod add their fee and fulfil (final invoice + keys where due); bacs issues a proforma (awaits transfer); stripe charges the saved card or returns a payment link in payment.paymentUrl. Pass cardId (pm_...) to pick a specific card for stripe.",
629
+ inputSchema: {
630
+ orderId: z.number().int().positive(),
631
+ newMethod: z.enum(["bacs", "cheque", "cod", "wallet", "stripe"]),
632
+ confirmToken: z.string().describe("Token from keypro_order_change_payment_preview"),
633
+ cardId: z.string().optional().describe("Saved card id (pm_...) for stripe")
634
+ }
635
+ },
636
+ (args) => run(
637
+ () => client.orderChangePayment(args.orderId, {
638
+ newMethod: args.newMethod,
639
+ confirmToken: args.confirmToken,
640
+ cardId: args.cardId
641
+ })
642
+ )
643
+ );
577
644
  server.registerTool(
578
645
  "keypro_license_keys",
579
646
  {
@@ -648,15 +715,72 @@ async function runMcpServer(client) {
648
715
  },
649
716
  (args) => run(() => client.parcelshopsSearch(args.q, args.type))
650
717
  );
718
+ }
719
+
720
+ // src/mcp.ts
721
+ async function runMcpServer(client) {
722
+ const server = new McpServer({ name: "keypro", version: "0.1.4" });
723
+ registerKeyproTools(server, client);
651
724
  const transport = new StdioServerTransport();
652
725
  await server.connect(transport);
653
726
  }
654
727
 
728
+ // src/prompt.ts
729
+ var ESC = String.fromCharCode(27);
730
+ var BRACKETED_PASTE = new RegExp(ESC + "\\[20[01]~", "g");
731
+ function consumeHiddenChunk(current, chunk) {
732
+ const s = chunk.replace(BRACKETED_PASTE, "");
733
+ let value = current;
734
+ for (const ch of s) {
735
+ const code = ch.charCodeAt(0);
736
+ if (code === 10 || code === 13 || code === 4) {
737
+ return { value, status: "submit" };
738
+ }
739
+ if (code === 3) {
740
+ return { value, status: "cancel" };
741
+ }
742
+ if (code === 127 || code === 8) {
743
+ value = value.slice(0, -1);
744
+ } else {
745
+ value += ch;
746
+ }
747
+ }
748
+ return { value, status: "typing" };
749
+ }
750
+ async function promptHidden(question) {
751
+ process.stdout.write(question);
752
+ return new Promise((resolve, reject) => {
753
+ const stdin = process.stdin;
754
+ let value = "";
755
+ const wasRaw = stdin.isRaw;
756
+ if (stdin.isTTY) stdin.setRawMode(true);
757
+ stdin.resume();
758
+ const stop = () => {
759
+ stdin.off("data", onData);
760
+ if (stdin.isTTY) stdin.setRawMode(wasRaw ?? false);
761
+ stdin.pause();
762
+ process.stdout.write("\n");
763
+ };
764
+ const onData = (chunk) => {
765
+ const res = consumeHiddenChunk(value, chunk.toString("utf8"));
766
+ value = res.value;
767
+ if (res.status === "submit") {
768
+ stop();
769
+ resolve(value);
770
+ } else if (res.status === "cancel") {
771
+ stop();
772
+ reject(new Error("Megszak\xEDtva."));
773
+ }
774
+ };
775
+ stdin.on("data", onData);
776
+ });
777
+ }
778
+
655
779
  // src/index.ts
656
780
  var program = new Command();
657
781
  program.name("keypro").description(
658
782
  "KeyPro.hu B2B licencshop CLI - rendeles, szamlak, term\xE9kkulcsok, profil. AI-agent utmutato: keypro agent-docs"
659
- ).version("0.1.0").option("--json", "gepi (JSON) kimenet a stdout-ra", false).option("--api-key <kulcs>", "API kulcs (felulirja az env/config erteket)").option("--api-base <url>", "API kiszolgalo cime (alap: eles bolt)").hook("preAction", (thisCommand) => {
783
+ ).version("0.1.4").option("--json", "gepi (JSON) kimenet a stdout-ra", false).option("--api-key <kulcs>", "API kulcs (felulirja az env/config erteket)").option("--api-base <url>", "API kiszolgalo cime (alap: eles bolt)").hook("preAction", (thisCommand) => {
660
784
  setJsonMode(Boolean(thisCommand.optsWithGlobals().json));
661
785
  });
662
786
  function resolved(cmd) {
@@ -673,36 +797,6 @@ function clientFor(cmd, requireKey = true) {
673
797
  }
674
798
  return createClient({ apiBase: cfg.apiBase, apiKey: cfg.apiKey });
675
799
  }
676
- async function promptHidden(question) {
677
- process.stdout.write(question);
678
- return new Promise((resolve, reject) => {
679
- const stdin = process.stdin;
680
- let value = "";
681
- const wasRaw = stdin.isRaw;
682
- if (stdin.isTTY) stdin.setRawMode(true);
683
- stdin.resume();
684
- const onData = (chunk) => {
685
- const ch = chunk.toString("utf8");
686
- if (ch === "\n" || ch === "\r" || ch === "") {
687
- stdin.off("data", onData);
688
- if (stdin.isTTY) stdin.setRawMode(wasRaw ?? false);
689
- stdin.pause();
690
- process.stdout.write("\n");
691
- resolve(value);
692
- } else if (ch === "") {
693
- stdin.off("data", onData);
694
- if (stdin.isTTY) stdin.setRawMode(wasRaw ?? false);
695
- process.stdout.write("\n");
696
- reject(new Error("Megszak\xEDtva."));
697
- } else if (ch === "\x7F" || ch === "\b") {
698
- value = value.slice(0, -1);
699
- } else {
700
- value += ch;
701
- }
702
- };
703
- stdin.on("data", onData);
704
- });
705
- }
706
800
  async function promptText(question) {
707
801
  const rl = createInterface({ input: process.stdin, output: process.stdout });
708
802
  try {
@@ -766,6 +860,61 @@ program.command("whoami").description("a bejelentkezett fiok adatai").action(asy
766
860
  fail(err);
767
861
  }
768
862
  });
863
+ program.command("setup").description("interaktiv beallitas: szerver + hitelesites (API kulcs vagy jelszo)").action(async (_opts, cmd) => {
864
+ try {
865
+ const current = resolveConfig({});
866
+ const baseInput = (await promptText(`API szerver [${current.apiBase}]: `)).trim();
867
+ const apiBase = (baseInput || current.apiBase).replace(/\/$/, "");
868
+ if (!/^https?:\/\//.test(apiBase)) {
869
+ usageError("Az API szerver http(s) URL kell legyen, pl. https://keypro.hu");
870
+ }
871
+ const method = (await promptText(
872
+ "Hiteles\xEDt\xE9s - [1] API kulcs (aj\xE1nlott) [2] Email + jelsz\xF3 [1]: "
873
+ )).trim();
874
+ let apiKey;
875
+ if (method === "2") {
876
+ const email = await promptText("Email: ");
877
+ const password = await promptHidden("Jelsz\xF3: ");
878
+ const result = await createClient({ apiBase }).login(
879
+ email,
880
+ password,
881
+ "CLI setup"
882
+ );
883
+ apiKey = result.token;
884
+ } else {
885
+ const key2 = (await promptHidden("API kulcs (kp_live_...): ")).trim();
886
+ if (!key2.startsWith("kp_live_")) {
887
+ usageError(
888
+ "\xC9rv\xE9nytelen kulcs form\xE1tum (kp_live_...). Kulcsot a weben az /api-keys oldalon k\xE9sz\xEDthetsz."
889
+ );
890
+ }
891
+ apiKey = key2;
892
+ }
893
+ writeConfig({ apiBase, apiKey });
894
+ const me = await createClient({ apiBase, apiKey }).me();
895
+ output(
896
+ {
897
+ apiBase,
898
+ configPath: configPath(),
899
+ account: { id: me.id, email: me.email, role: me.role }
900
+ },
901
+ () => {
902
+ process.stdout.write(
903
+ `
904
+ Be\xE1ll\xEDtva.
905
+ Szerver: ${apiBase}
906
+ Fi\xF3k: ${me.email} (#${me.id}, ${me.role})
907
+ Config: ${configPath()}
908
+
909
+ Pr\xF3b\xE1ld ki: keypro whoami
910
+ `
911
+ );
912
+ }
913
+ );
914
+ } catch (err) {
915
+ fail(err);
916
+ }
917
+ });
769
918
  var config = program.command("config").description("CLI beallitasok");
770
919
  config.command("get").description("aktualis beallitasok").action((_opts, cmd) => {
771
920
  const cfg = resolved(cmd);
@@ -787,18 +936,29 @@ config.command("get").description("aktualis beallitasok").action((_opts, cmd) =>
787
936
  }
788
937
  );
789
938
  });
790
- config.command("set <kulcs> <ertek>").description("beallitas mentese (tamogatott kulcs: api-base)").action((key2, value) => {
791
- if (key2 !== "api-base") {
792
- usageError(`Ismeretlen be\xE1ll\xEDt\xE1s: ${key2}. T\xE1mogatott: api-base`);
939
+ config.command("set <kulcs> <ertek>").description("beallitas mentese (tamogatott kulcs: api-base, api-key)").action((key2, value) => {
940
+ if (key2 === "api-base") {
941
+ if (!/^https?:\/\//.test(value)) {
942
+ usageError("Az api-base http(s) URL kell legyen, pl. https://keypro.hu");
943
+ }
944
+ const next = writeConfig({ apiBase: value.replace(/\/$/, "") });
945
+ output({ apiBase: next.apiBase }, () => {
946
+ process.stdout.write(`API c\xEDm be\xE1ll\xEDtva: ${next.apiBase}
947
+ `);
948
+ });
949
+ return;
793
950
  }
794
- if (!/^https?:\/\//.test(value)) {
795
- usageError("Az api-base http(s) URL kell legyen, pl. https://keypro.hu");
951
+ if (key2 === "api-key") {
952
+ if (!value.startsWith("kp_live_")) {
953
+ usageError("\xC9rv\xE9nytelen kulcs form\xE1tum (kp_live_...).");
954
+ }
955
+ writeConfig({ apiKey: value });
956
+ output({ apiKeySet: true, configPath: configPath() }, () => {
957
+ process.stdout.write("API kulcs elmentve a configba.\n");
958
+ });
959
+ return;
796
960
  }
797
- const next = writeConfig({ apiBase: value.replace(/\/$/, "") });
798
- output({ apiBase: next.apiBase }, () => {
799
- process.stdout.write(`API c\xEDm be\xE1ll\xEDtva: ${next.apiBase}
800
- `);
801
- });
961
+ usageError(`Ismeretlen be\xE1ll\xEDt\xE1s: ${key2}. T\xE1mogatott: api-base, api-key`);
802
962
  });
803
963
  var key = program.command("key").description("API kulcsok kezelese");
804
964
  key.command("list").description("a fiok API kulcsai").action(async (_opts, cmd) => {
@@ -1154,6 +1314,75 @@ order.command("get <id>").description("rendeles reszletei").action(async (id, _o
1154
1314
  fail(err);
1155
1315
  }
1156
1316
  });
1317
+ order.command("cancel <id>").description("fizetetlen rendeles visszamondasa (torlese)").action(async (id, _opts, cmd) => {
1318
+ try {
1319
+ const result = await clientFor(cmd).orderCancel(Number(id));
1320
+ output(result, () => {
1321
+ const o = result.order;
1322
+ printKV([
1323
+ ["Rendel\xE9s", `#${o.number} (id: ${o.id})`],
1324
+ ["\xC1llapot", String(o.statusLabel)]
1325
+ ]);
1326
+ process.stdout.write(`
1327
+ ${result.note}
1328
+ `);
1329
+ });
1330
+ } catch (err) {
1331
+ fail(err);
1332
+ }
1333
+ });
1334
+ order.command("change-payment <id>").description("fizetetlen rendeles fizetesi modjanak modositasa (elonezet; --yes nelkul nem valt)").requiredOption("--payment <mod>", "uj fizetesi mod: bacs|cheque|cod|wallet|stripe").option("--card <pm_id>", "mentett kartya id (stripe-hoz, opcionalis)").option("--yes", "a valtas tenyleges vegrehajtasa", false).action(
1335
+ async (id, opts, cmd) => {
1336
+ try {
1337
+ const client = clientFor(cmd);
1338
+ const preview = await client.orderPaymentPreview(Number(id), opts.payment);
1339
+ if (!opts.yes) {
1340
+ output(
1341
+ { preview, hint: "Add hozz\xE1 a --yes kapcsol\xF3t a m\xF3dos\xEDt\xE1shoz." },
1342
+ () => {
1343
+ printKV([
1344
+ ["Jelenlegi m\xF3d", preview.currentMethod],
1345
+ ["\xDAj m\xF3d", preview.newMethod],
1346
+ ["\xDAj nett\xF3", eurFmt(preview.newTotals.netTotalEur)],
1347
+ ["\xDAj brutt\xF3", eurFmt(preview.newTotals.grossTotalEur)],
1348
+ ["D\xEDj-v\xE1ltoz\xE1s (brutt\xF3)", eurFmt(preview.feeDeltaEur)],
1349
+ [
1350
+ "KEP fedezet",
1351
+ preview.wallet ? preview.wallet.sufficient ? "el\xE9g" : "NEM el\xE9g" : null
1352
+ ]
1353
+ ]);
1354
+ process.stdout.write(
1355
+ "\nEz csak el\u0151n\xE9zet. Add hozz\xE1 a --yes kapcsol\xF3t a m\xF3dos\xEDt\xE1shoz.\n"
1356
+ );
1357
+ }
1358
+ );
1359
+ process.exit(1);
1360
+ }
1361
+ const result = await client.orderChangePayment(Number(id), {
1362
+ newMethod: opts.payment,
1363
+ confirmToken: preview.confirmToken,
1364
+ cardId: opts.card
1365
+ });
1366
+ output(result, () => {
1367
+ const o = result.order;
1368
+ const p = result.payment;
1369
+ printKV([
1370
+ ["Rendel\xE9s", `#${o.number} (id: ${o.id})`],
1371
+ ["\xC1llapot", String(o.statusLabel)],
1372
+ ["Fizet\xE9si m\xF3d", String(o.paymentMethodLabel)],
1373
+ ["Fizet\xE9s", String(p.note)],
1374
+ ["Fizet\xE9si link", p.paymentUrl ? String(p.paymentUrl) : null],
1375
+ [
1376
+ "KEP egyenleg",
1377
+ p.walletBalanceAfterEur !== void 0 ? eurFmt(Number(p.walletBalanceAfterEur)) : null
1378
+ ]
1379
+ ]);
1380
+ });
1381
+ } catch (err) {
1382
+ fail(err);
1383
+ }
1384
+ }
1385
+ );
1157
1386
  var keysCmd = program.command("keys").description("term\xE9kkulcsok");
1158
1387
  keysCmd.command("list").description("kezbesitett term\xE9kkulcsok (osszes vagy egy rendelese)").option("--order <id>", "csak az adott rendeles kulcsai").action(async (opts, cmd) => {
1159
1388
  try {
@@ -1369,6 +1598,22 @@ profile.command("set <mezo=ertek...>").description(
1369
1598
  fail(err);
1370
1599
  }
1371
1600
  });
1601
+ program.command("rate").description("Aktu\xE1lis EUR/HUF \xE1rfolyam (amit a webshop haszn\xE1l \xE9s megjelen\xEDt)").action(async (_opts, cmd) => {
1602
+ try {
1603
+ const result = await clientFor(cmd).exchangeRate();
1604
+ output(result, () => {
1605
+ printKV([
1606
+ ["EUR -> HUF", `${result.rate.toFixed(2)} Ft`],
1607
+ ["ECB referencia (fel\xE1r n\xE9lk\xFCl)", `${result.referenceRate.toFixed(2)} Ft`],
1608
+ ["Fel\xE1r", `${result.markupPct}%`],
1609
+ ["1 HUF -> EUR", result.hufToEur.toFixed(6)],
1610
+ ["Forr\xE1s", result.source]
1611
+ ]);
1612
+ });
1613
+ } catch (err) {
1614
+ fail(err);
1615
+ }
1616
+ });
1372
1617
  var wallet = program.command("wallet").description("KEP egyenleg");
1373
1618
  wallet.action(async (_opts, cmd) => {
1374
1619
  try {
@@ -0,0 +1,258 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+
3
+ /**
4
+ * Vekony HTTP kliens a KeyPro /api/v1 vegpontokhoz (nativ fetch).
5
+ * Minden uzleti logika a szerveren el; itt csak a boritek ({ ok, data |
6
+ * error }) kibontasa es a hibak tipusositasa tortenik. A parancsok ES az
7
+ * MCP szerver is ezt a klienst hasznalja.
8
+ */
9
+ declare class KeyproApiError extends Error {
10
+ status: number;
11
+ code: string;
12
+ details?: unknown | undefined;
13
+ constructor(status: number, code: string, message: string, details?: unknown | undefined);
14
+ }
15
+ interface OrderItemInput {
16
+ sku?: string;
17
+ productId?: number;
18
+ qty: number;
19
+ }
20
+ interface AddressInput {
21
+ firstName?: string;
22
+ lastName?: string;
23
+ company?: string;
24
+ address1?: string;
25
+ address2?: string;
26
+ city?: string;
27
+ postcode?: string;
28
+ state?: string;
29
+ country?: string;
30
+ email?: string;
31
+ phone?: string;
32
+ }
33
+ interface OrderRequestInput {
34
+ items: OrderItemInput[];
35
+ paymentMethod: "bacs" | "cheque" | "cod" | "wallet" | "stripe";
36
+ shippingMethodId?: "gls_hd" | "gls_parcelshop" | "combine_free";
37
+ parcelshopId?: string;
38
+ couponCode?: string;
39
+ currency?: "EUR" | "HUF";
40
+ billing?: AddressInput;
41
+ shipping?: AddressInput;
42
+ taxNumber?: string;
43
+ cardId?: string;
44
+ }
45
+ interface KeyproClientOptions {
46
+ apiBase: string;
47
+ apiKey?: string | null;
48
+ }
49
+ declare class KeyproClient {
50
+ private readonly opts;
51
+ constructor(opts: KeyproClientOptions);
52
+ private request;
53
+ login(email: string, password: string, name?: string): Promise<{
54
+ token: string;
55
+ keyId: number;
56
+ prefix: string;
57
+ scopes: string[];
58
+ name: string;
59
+ }>;
60
+ me(): Promise<{
61
+ id: number;
62
+ email: string;
63
+ companyName: string | null;
64
+ firstName: string | null;
65
+ role: string;
66
+ walletBalanceEurNet: number;
67
+ key: {
68
+ id: number;
69
+ prefix: string;
70
+ name: string;
71
+ scopes: string[];
72
+ };
73
+ }>;
74
+ keysList(): Promise<{
75
+ keys: Array<Record<string, unknown>>;
76
+ }>;
77
+ keyRevoke(keyId: number): Promise<{
78
+ revoked: boolean;
79
+ keyId: number;
80
+ }>;
81
+ productsSearch(params: {
82
+ q?: string;
83
+ category?: string;
84
+ onSale?: boolean;
85
+ sort?: string;
86
+ limit?: number;
87
+ offset?: number;
88
+ }): Promise<{
89
+ total: number;
90
+ products: Array<Record<string, unknown>>;
91
+ }>;
92
+ productGet(key: string): Promise<Record<string, unknown>>;
93
+ orderPreview(req: OrderRequestInput): Promise<{
94
+ lines: Array<Record<string, unknown>>;
95
+ totals: Record<string, number>;
96
+ payment: Record<string, unknown>;
97
+ shipping: Record<string, unknown> | null;
98
+ coupon: Record<string, unknown> | null;
99
+ currency: string;
100
+ eurRate: number;
101
+ displayGrossTotal: number;
102
+ wallet: {
103
+ balanceEurNet: number;
104
+ sufficient: boolean;
105
+ } | null;
106
+ confirmToken: string;
107
+ confirmTokenExpiresAt: string;
108
+ }>;
109
+ orderCreate(req: OrderRequestInput & {
110
+ confirmToken: string;
111
+ }, idempotencyKey?: string): Promise<{
112
+ order: Record<string, unknown>;
113
+ invoices: Array<Record<string, unknown>>;
114
+ deliveredKeyCount: number;
115
+ payment: {
116
+ method: string;
117
+ charged: boolean;
118
+ paymentUrl?: string;
119
+ declineCode?: string | null;
120
+ walletBalanceAfterEur?: number;
121
+ note: string;
122
+ };
123
+ idempotentReplay: boolean;
124
+ }>;
125
+ ordersList(params: {
126
+ status?: string;
127
+ limit?: number;
128
+ offset?: number;
129
+ }): Promise<{
130
+ orders: Array<Record<string, unknown>>;
131
+ }>;
132
+ orderGet(id: number): Promise<{
133
+ order: Record<string, unknown>;
134
+ invoices: Array<Record<string, unknown>>;
135
+ paymentUrl: string | null;
136
+ }>;
137
+ orderKeys(id: number): Promise<{
138
+ orderId: number;
139
+ orderStatus: string;
140
+ keys: Array<{
141
+ productId: number;
142
+ productName: string;
143
+ keyValue: string;
144
+ deliveredAt: string | null;
145
+ }>;
146
+ licenses: Array<Record<string, unknown>>;
147
+ }>;
148
+ orderCancel(id: number): Promise<{
149
+ order: Record<string, unknown>;
150
+ invoices: Array<Record<string, unknown>>;
151
+ cancelled: boolean;
152
+ alreadyCancelled: boolean;
153
+ note: string;
154
+ }>;
155
+ orderPaymentPreview(id: number, newMethod: string): Promise<{
156
+ currentMethod: string;
157
+ newMethod: string;
158
+ newTotals: {
159
+ netTotalEur: number;
160
+ grossTotalEur: number;
161
+ };
162
+ feeDeltaEur: number;
163
+ fees: Array<{
164
+ label: string;
165
+ netEur: number;
166
+ }>;
167
+ confirmToken: string;
168
+ confirmTokenExpiresAt: string;
169
+ wallet: {
170
+ balanceEurNet: number;
171
+ sufficient: boolean;
172
+ } | null;
173
+ }>;
174
+ orderChangePayment(id: number, body: {
175
+ newMethod: string;
176
+ confirmToken: string;
177
+ cardId?: string;
178
+ }): Promise<{
179
+ order: Record<string, unknown>;
180
+ invoices: Array<Record<string, unknown>>;
181
+ payment: Record<string, unknown>;
182
+ }>;
183
+ licenseKeys(): Promise<{
184
+ products: Array<{
185
+ productId: number;
186
+ productName: string;
187
+ keys: Array<Record<string, unknown>>;
188
+ }>;
189
+ }>;
190
+ invoicesList(params: {
191
+ orderId?: number;
192
+ limit?: number;
193
+ offset?: number;
194
+ }): Promise<{
195
+ invoices: Array<Record<string, unknown>>;
196
+ }>;
197
+ invoiceGet(id: number): Promise<{
198
+ invoice: Record<string, unknown>;
199
+ }>;
200
+ profileGet(): Promise<{
201
+ profile: Record<string, unknown>;
202
+ }>;
203
+ profileUpdate(patch: Record<string, unknown>): Promise<{
204
+ updated: string[];
205
+ profile: Record<string, unknown>;
206
+ }>;
207
+ wallet(params?: {
208
+ limit?: number;
209
+ offset?: number;
210
+ }): Promise<{
211
+ balanceEurNet: number;
212
+ transactions: Array<Record<string, unknown>>;
213
+ }>;
214
+ cardsList(): Promise<{
215
+ stripeEnabled: boolean;
216
+ cards: Array<{
217
+ id: string;
218
+ brand: string;
219
+ last4: string;
220
+ expMonth: number;
221
+ expYear: number;
222
+ isDefault: boolean;
223
+ }>;
224
+ }>;
225
+ parcelshopsSearch(q: string, type?: string): Promise<{
226
+ truncated: boolean;
227
+ parcelshops: Array<Record<string, unknown>>;
228
+ }>;
229
+ exchangeRate(): Promise<{
230
+ base: string;
231
+ quote: string;
232
+ rate: number;
233
+ eurToHuf: number;
234
+ hufToEur: number;
235
+ referenceRate: number;
236
+ markupPct: number;
237
+ source: string;
238
+ rounding: {
239
+ HUF: number;
240
+ EUR: number;
241
+ };
242
+ note: string;
243
+ }>;
244
+ }
245
+ declare function createClient(opts: KeyproClientOptions): KeyproClient;
246
+
247
+ /**
248
+ * Megosztott MCP tool-registry: a KeyPro muveletek nativ MCP tool-kent, ugyanazon
249
+ * a KeyproClient-en keresztul. Ezt hasznalja a CLI stdio szerver (mcp.ts) ES a
250
+ * webes tavoli MCP route (src/app/mcp/route.ts) - igy a tool-definiciok nem
251
+ * csusznak szet. A McpServer csak tipuskent kell (type-only import), igy ez a
252
+ * modul nem huzza be a stdio transportot a webes buildbe.
253
+ */
254
+
255
+ /** A KeyPro MCP tool-jait regisztralja a megadott McpServer-re. */
256
+ declare function registerKeyproTools(server: McpServer, client: KeyproClient): void;
257
+
258
+ export { KeyproApiError, KeyproClient, type KeyproClientOptions, createClient, registerKeyproTools };
@@ -0,0 +1,461 @@
1
+ // src/mcp-tools.ts
2
+ import { z } from "zod";
3
+
4
+ // src/client.ts
5
+ var KeyproApiError = class extends Error {
6
+ constructor(status, code, message, details) {
7
+ super(message);
8
+ this.status = status;
9
+ this.code = code;
10
+ this.details = details;
11
+ this.name = "KeyproApiError";
12
+ }
13
+ status;
14
+ code;
15
+ details;
16
+ };
17
+ var KeyproClient = class {
18
+ constructor(opts) {
19
+ this.opts = opts;
20
+ }
21
+ opts;
22
+ async request(method, path, body, extraHeaders) {
23
+ const headers = {
24
+ accept: "application/json",
25
+ ...extraHeaders
26
+ };
27
+ if (this.opts.apiKey) headers.authorization = `Bearer ${this.opts.apiKey}`;
28
+ if (body !== void 0) headers["content-type"] = "application/json";
29
+ let response;
30
+ try {
31
+ response = await fetch(`${this.opts.apiBase}${path}`, {
32
+ method,
33
+ headers,
34
+ body: body === void 0 ? void 0 : JSON.stringify(body)
35
+ });
36
+ } catch (err) {
37
+ throw new KeyproApiError(
38
+ 0,
39
+ "network_error",
40
+ `Nem siker\xFClt el\xE9rni a szervert (${this.opts.apiBase}): ${err instanceof Error ? err.message : String(err)}`
41
+ );
42
+ }
43
+ let payload;
44
+ try {
45
+ payload = await response.json();
46
+ } catch {
47
+ throw new KeyproApiError(
48
+ response.status,
49
+ "invalid_response",
50
+ `A szerver nem JSON v\xE1laszt adott (HTTP ${response.status}). J\xF3 az api-base be\xE1ll\xEDt\xE1s?`
51
+ );
52
+ }
53
+ const envelope = payload;
54
+ if (envelope.ok === true && envelope.data !== void 0) {
55
+ return envelope.data;
56
+ }
57
+ const error = envelope.error ?? {};
58
+ throw new KeyproApiError(
59
+ response.status,
60
+ error.code ?? "unknown_error",
61
+ error.message ?? `Ismeretlen hiba (HTTP ${response.status}).`,
62
+ error.details
63
+ );
64
+ }
65
+ // --- Auth / kulcsok ---
66
+ login(email, password, name) {
67
+ return this.request("POST", "/api/v1/auth/login", { email, password, name });
68
+ }
69
+ me() {
70
+ return this.request("GET", "/api/v1/me");
71
+ }
72
+ keysList() {
73
+ return this.request(
74
+ "GET",
75
+ "/api/v1/keys"
76
+ );
77
+ }
78
+ keyRevoke(keyId) {
79
+ return this.request(
80
+ "DELETE",
81
+ `/api/v1/keys/${keyId}`
82
+ );
83
+ }
84
+ // --- Termekek ---
85
+ productsSearch(params) {
86
+ const qs = new URLSearchParams();
87
+ if (params.q) qs.set("q", params.q);
88
+ if (params.category) qs.set("category", params.category);
89
+ if (params.onSale) qs.set("on_sale", "true");
90
+ if (params.sort) qs.set("sort", params.sort);
91
+ if (params.limit) qs.set("limit", String(params.limit));
92
+ if (params.offset) qs.set("offset", String(params.offset));
93
+ const suffix = qs.size > 0 ? `?${qs}` : "";
94
+ return this.request("GET", `/api/v1/products${suffix}`);
95
+ }
96
+ productGet(key) {
97
+ return this.request(
98
+ "GET",
99
+ `/api/v1/products/${encodeURIComponent(key)}`
100
+ );
101
+ }
102
+ // --- Rendelesek ---
103
+ orderPreview(req) {
104
+ return this.request("POST", "/api/v1/orders/preview", req);
105
+ }
106
+ orderCreate(req, idempotencyKey) {
107
+ return this.request(
108
+ "POST",
109
+ "/api/v1/orders",
110
+ req,
111
+ idempotencyKey ? { "idempotency-key": idempotencyKey } : void 0
112
+ );
113
+ }
114
+ ordersList(params) {
115
+ const qs = new URLSearchParams();
116
+ if (params.status) qs.set("status", params.status);
117
+ if (params.limit) qs.set("limit", String(params.limit));
118
+ if (params.offset) qs.set("offset", String(params.offset));
119
+ const suffix = qs.size > 0 ? `?${qs}` : "";
120
+ return this.request(
121
+ "GET",
122
+ `/api/v1/orders${suffix}`
123
+ );
124
+ }
125
+ orderGet(id) {
126
+ return this.request("GET", `/api/v1/orders/${id}`);
127
+ }
128
+ orderKeys(id) {
129
+ return this.request("GET", `/api/v1/orders/${id}/keys`);
130
+ }
131
+ orderCancel(id) {
132
+ return this.request("POST", `/api/v1/orders/${id}/cancel`);
133
+ }
134
+ orderPaymentPreview(id, newMethod) {
135
+ return this.request("POST", `/api/v1/orders/${id}/payment/preview`, { newMethod });
136
+ }
137
+ orderChangePayment(id, body) {
138
+ return this.request("POST", `/api/v1/orders/${id}/payment`, body);
139
+ }
140
+ licenseKeys() {
141
+ return this.request("GET", "/api/v1/license-keys");
142
+ }
143
+ // --- Szamlak ---
144
+ invoicesList(params) {
145
+ const qs = new URLSearchParams();
146
+ if (params.orderId) qs.set("order_id", String(params.orderId));
147
+ if (params.limit) qs.set("limit", String(params.limit));
148
+ if (params.offset) qs.set("offset", String(params.offset));
149
+ const suffix = qs.size > 0 ? `?${qs}` : "";
150
+ return this.request(
151
+ "GET",
152
+ `/api/v1/invoices${suffix}`
153
+ );
154
+ }
155
+ invoiceGet(id) {
156
+ return this.request(
157
+ "GET",
158
+ `/api/v1/invoices/${id}`
159
+ );
160
+ }
161
+ // --- Profil / wallet / kartyak / csomagpontok ---
162
+ profileGet() {
163
+ return this.request(
164
+ "GET",
165
+ "/api/v1/profile"
166
+ );
167
+ }
168
+ profileUpdate(patch) {
169
+ return this.request("PATCH", "/api/v1/profile", patch);
170
+ }
171
+ wallet(params = {}) {
172
+ const qs = new URLSearchParams();
173
+ if (params.limit) qs.set("limit", String(params.limit));
174
+ if (params.offset) qs.set("offset", String(params.offset));
175
+ const suffix = qs.size > 0 ? `?${qs}` : "";
176
+ return this.request("GET", `/api/v1/wallet${suffix}`);
177
+ }
178
+ cardsList() {
179
+ return this.request("GET", "/api/v1/cards");
180
+ }
181
+ parcelshopsSearch(q, type) {
182
+ const qs = new URLSearchParams({ q });
183
+ if (type) qs.set("type", type);
184
+ return this.request("GET", `/api/v1/shipping/parcelshops?${qs}`);
185
+ }
186
+ exchangeRate() {
187
+ return this.request("GET", "/api/v1/exchange-rate");
188
+ }
189
+ };
190
+ function createClient(opts) {
191
+ return new KeyproClient(opts);
192
+ }
193
+
194
+ // src/mcp-tools.ts
195
+ var addressShape = z.object({
196
+ firstName: z.string().optional(),
197
+ lastName: z.string().optional(),
198
+ company: z.string().optional(),
199
+ address1: z.string().optional(),
200
+ address2: z.string().optional(),
201
+ city: z.string().optional(),
202
+ postcode: z.string().optional(),
203
+ state: z.string().optional(),
204
+ country: z.string().optional(),
205
+ email: z.string().optional(),
206
+ phone: z.string().optional()
207
+ }).optional();
208
+ var orderRequestShape = {
209
+ items: z.array(
210
+ z.object({
211
+ sku: z.string().optional().describe("Product SKU (either sku or productId)"),
212
+ productId: z.number().int().positive().optional(),
213
+ qty: z.number().int().min(1).max(999).default(1)
214
+ })
215
+ ).min(1).describe("Order lines"),
216
+ paymentMethod: z.enum(["bacs", "cheque", "cod", "wallet", "stripe"]).describe(
217
+ "bacs=bank transfer (proforma first), cheque=8-day terms (+5%), cod=cash on delivery, wallet=KEP balance, stripe=saved card"
218
+ ),
219
+ shippingMethodId: z.enum(["gls_hd", "gls_parcelshop", "combine_free"]).optional().describe("Required when the cart contains physical products"),
220
+ parcelshopId: z.string().optional().describe("GLS pickup point id (required for gls_parcelshop)"),
221
+ couponCode: z.string().optional(),
222
+ currency: z.enum(["EUR", "HUF"]).default("EUR"),
223
+ billing: addressShape.describe(
224
+ "Per-field billing address overrides (defaults come from the user profile)"
225
+ ),
226
+ shipping: addressShape.describe(
227
+ "Separate shipping address (omit to ship to the billing address)"
228
+ ),
229
+ taxNumber: z.string().optional(),
230
+ cardId: z.string().optional().describe("Saved card id (pm_...) for stripe payments; omit for default card")
231
+ };
232
+ function jsonResult(data) {
233
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
234
+ }
235
+ function errorResult(err) {
236
+ const payload = err instanceof KeyproApiError ? { code: err.code, message: err.message, details: err.details } : { code: "cli_error", message: err instanceof Error ? err.message : String(err) };
237
+ return {
238
+ content: [{ type: "text", text: JSON.stringify({ error: payload }) }],
239
+ isError: true
240
+ };
241
+ }
242
+ async function run(fn) {
243
+ try {
244
+ return jsonResult(await fn());
245
+ } catch (err) {
246
+ return errorResult(err);
247
+ }
248
+ }
249
+ function registerKeyproTools(server, client) {
250
+ server.registerTool(
251
+ "keypro_whoami",
252
+ {
253
+ description: "The authenticated KeyPro account (email, company, role, KEP wallet balance, API key scopes).",
254
+ inputSchema: {}
255
+ },
256
+ () => run(() => client.me())
257
+ );
258
+ server.registerTool(
259
+ "keypro_exchange_rate",
260
+ {
261
+ description: "Current EUR->HUF exchange rate the shop uses and displays (ECB daily reference + 3% markup). Product net prices are stored in EUR; the HUF price = round(EUR * rate) to whole forint (EUR shown with 2 decimals). Call this to price products accurately in both currencies.",
262
+ inputSchema: {}
263
+ },
264
+ () => run(() => client.exchangeRate())
265
+ );
266
+ server.registerTool(
267
+ "keypro_products_search",
268
+ {
269
+ description: "Search the KeyPro product catalog by name or SKU. Returns id, sku, name, net EUR price.",
270
+ inputSchema: {
271
+ q: z.string().optional().describe("Search text (name or SKU)"),
272
+ category: z.string().optional().describe("Category slug filter"),
273
+ onSale: z.boolean().optional().describe("Only discounted products"),
274
+ limit: z.number().int().min(1).max(100).optional()
275
+ }
276
+ },
277
+ (args) => run(
278
+ () => client.productsSearch({
279
+ q: args.q,
280
+ category: args.category,
281
+ onSale: args.onSale,
282
+ limit: args.limit
283
+ })
284
+ )
285
+ );
286
+ server.registerTool(
287
+ "keypro_product_get",
288
+ {
289
+ description: "Product details by id, slug or SKU, including the caller's effective unit price with discounts.",
290
+ inputSchema: { key: z.string().describe("Product id, slug or SKU") }
291
+ },
292
+ (args) => run(() => client.productGet(args.key))
293
+ );
294
+ server.registerTool(
295
+ "keypro_order_preview",
296
+ {
297
+ description: "Preview an order WITHOUT placing it: priced lines, fees, shipping, totals, and a confirmToken (valid 15 minutes). ALWAYS show the returned totals to the user and get their approval before calling keypro_order_create.",
298
+ inputSchema: orderRequestShape
299
+ },
300
+ (args) => run(() => client.orderPreview(args))
301
+ );
302
+ server.registerTool(
303
+ "keypro_order_create",
304
+ {
305
+ description: "Place an order. Requires the confirmToken from keypro_order_preview (same items, payment method and total). If the response contains payment.paymentUrl, give that link to the user to finish paying in a browser. Pass idempotencyKey to make retries safe.",
306
+ inputSchema: {
307
+ ...orderRequestShape,
308
+ confirmToken: z.string().describe("Token from keypro_order_preview"),
309
+ idempotencyKey: z.string().optional().describe("Unique retry-dedup key; the same key never creates a second order")
310
+ }
311
+ },
312
+ (args) => run(() => {
313
+ const { idempotencyKey, ...request } = args;
314
+ return client.orderCreate(request, idempotencyKey);
315
+ })
316
+ );
317
+ server.registerTool(
318
+ "keypro_orders_list",
319
+ {
320
+ description: "List the user's orders (newest first), optional status filter.",
321
+ inputSchema: {
322
+ status: z.string().optional().describe("e.g. pending, processing, completed"),
323
+ limit: z.number().int().min(1).max(100).optional()
324
+ }
325
+ },
326
+ (args) => run(() => client.ordersList({ status: args.status, limit: args.limit }))
327
+ );
328
+ server.registerTool(
329
+ "keypro_order_get",
330
+ {
331
+ description: "Order details: items, totals, status, invoices, and a payment link if the order is awaiting card payment.",
332
+ inputSchema: { orderId: z.number().int().positive() }
333
+ },
334
+ (args) => run(() => client.orderGet(args.orderId))
335
+ );
336
+ server.registerTool(
337
+ "keypro_order_keys",
338
+ {
339
+ description: "License keys delivered for one order. COD orders release keys only after completion.",
340
+ inputSchema: { orderId: z.number().int().positive() }
341
+ },
342
+ (args) => run(() => client.orderKeys(args.orderId))
343
+ );
344
+ server.registerTool(
345
+ "keypro_order_cancel",
346
+ {
347
+ description: "Cancel an UNPAID order. Only orders awaiting payment can be cancelled: bacs (bank transfer / proforma), stripe (card checkout not completed), and cod (cash on delivery, still processing). 8-day-deferred (cheque) and already-paid orders CANNOT be cancelled. Idempotent.",
348
+ inputSchema: { orderId: z.number().int().positive() }
349
+ },
350
+ (args) => run(() => client.orderCancel(args.orderId))
351
+ );
352
+ server.registerTool(
353
+ "keypro_order_change_payment_preview",
354
+ {
355
+ description: "Preview changing an UNPAID order's payment method (only on-hold=bacs / pending=stripe orders qualify). Returns the recomputed totals for the new method (cheque adds +5%, cod adds a fixed fee; bacs/wallet/stripe add none) and a confirmToken. ALWAYS show the new totals to the user, then call keypro_order_change_payment.",
356
+ inputSchema: {
357
+ orderId: z.number().int().positive(),
358
+ newMethod: z.enum(["bacs", "cheque", "cod", "wallet", "stripe"])
359
+ }
360
+ },
361
+ (args) => run(() => client.orderPaymentPreview(args.orderId, args.newMethod))
362
+ );
363
+ server.registerTool(
364
+ "keypro_order_change_payment",
365
+ {
366
+ description: "Change an UNPAID order's payment method. Requires the confirmToken from keypro_order_change_payment_preview. wallet debits the KEP balance now and fulfils; cheque/cod add their fee and fulfil (final invoice + keys where due); bacs issues a proforma (awaits transfer); stripe charges the saved card or returns a payment link in payment.paymentUrl. Pass cardId (pm_...) to pick a specific card for stripe.",
367
+ inputSchema: {
368
+ orderId: z.number().int().positive(),
369
+ newMethod: z.enum(["bacs", "cheque", "cod", "wallet", "stripe"]),
370
+ confirmToken: z.string().describe("Token from keypro_order_change_payment_preview"),
371
+ cardId: z.string().optional().describe("Saved card id (pm_...) for stripe")
372
+ }
373
+ },
374
+ (args) => run(
375
+ () => client.orderChangePayment(args.orderId, {
376
+ newMethod: args.newMethod,
377
+ confirmToken: args.confirmToken,
378
+ cardId: args.cardId
379
+ })
380
+ )
381
+ );
382
+ server.registerTool(
383
+ "keypro_license_keys",
384
+ {
385
+ description: "All delivered license keys of the user, grouped by product.",
386
+ inputSchema: {}
387
+ },
388
+ () => run(() => client.licenseKeys())
389
+ );
390
+ server.registerTool(
391
+ "keypro_invoices_list",
392
+ {
393
+ description: "List invoices/proformas (each has a public downloadUrl PDF link). Optional order filter.",
394
+ inputSchema: {
395
+ orderId: z.number().int().positive().optional(),
396
+ limit: z.number().int().min(1).max(100).optional()
397
+ }
398
+ },
399
+ (args) => run(() => client.invoicesList({ orderId: args.orderId, limit: args.limit }))
400
+ );
401
+ server.registerTool(
402
+ "keypro_invoice_get",
403
+ {
404
+ description: "One invoice with totals and public downloadUrl.",
405
+ inputSchema: { invoiceId: z.number().int().positive() }
406
+ },
407
+ (args) => run(() => client.invoiceGet(args.invoiceId))
408
+ );
409
+ server.registerTool(
410
+ "keypro_profile_get",
411
+ {
412
+ description: "The account profile: contact data, billing and shipping address.",
413
+ inputSchema: {}
414
+ },
415
+ () => run(() => client.profileGet())
416
+ );
417
+ server.registerTool(
418
+ "keypro_profile_update",
419
+ {
420
+ description: "Update profile fields (partial). Allowed keys: firstName, phone, website, companyName, taxNumber, billingFirstName, billingLastName, billingCompany, billingAddress1, billingAddress2, billingCity, billingPostcode, billingState, billingCountry, billingEmail, billingPhone, and the same shipping* fields (no shippingEmail). Empty string clears a field.",
421
+ inputSchema: {
422
+ fields: z.record(z.string(), z.string()).describe("Field name -> new value map (flat API field names)")
423
+ }
424
+ },
425
+ (args) => run(() => client.profileUpdate(args.fields))
426
+ );
427
+ server.registerTool(
428
+ "keypro_wallet",
429
+ {
430
+ description: "KEP wallet balance (net EUR) and transaction history (topup/payment/refund/bonus with running balance).",
431
+ inputSchema: {
432
+ limit: z.number().int().min(1).max(100).optional().describe("History length")
433
+ }
434
+ },
435
+ (args) => run(() => client.wallet({ limit: args.limit }))
436
+ );
437
+ server.registerTool(
438
+ "keypro_cards_list",
439
+ {
440
+ description: "Saved bank cards (brand, last4, default flag). New cards can only be added on the website.",
441
+ inputSchema: {}
442
+ },
443
+ () => run(() => client.cardsList())
444
+ );
445
+ server.registerTool(
446
+ "keypro_parcelshops_search",
447
+ {
448
+ description: "Search GLS pickup points (city, zip prefix or name). Use the returned id as parcelshopId for gls_parcelshop shipping.",
449
+ inputSchema: {
450
+ q: z.string().describe("City, zip prefix or name"),
451
+ type: z.enum(["parcel-shop", "parcel-locker", "all"]).optional()
452
+ }
453
+ },
454
+ (args) => run(() => client.parcelshopsSearch(args.q, args.type))
455
+ );
456
+ }
457
+ export {
458
+ KeyproApiError,
459
+ createClient,
460
+ registerKeyproTools
461
+ };
package/package.json CHANGED
@@ -1,12 +1,18 @@
1
1
  {
2
2
  "name": "@keypro/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.4",
4
4
  "description": "KeyPro.hu B2B szoftverlicenc webshop CLI: rendeles leadas, rendelesek/szamlak/termekkulcsok lekerdezese, profil kezeles. AI-ugynok (Claude Code, Codex) barat: --json kimenet es beepitett MCP szerver (keypro mcp).",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "keypro": "dist/index.js"
9
9
  },
10
+ "exports": {
11
+ "./mcp-tools": {
12
+ "types": "./dist/mcp-tools.d.ts",
13
+ "import": "./dist/mcp-tools.js"
14
+ }
15
+ },
10
16
  "files": [
11
17
  "dist",
12
18
  "AGENTS.md",