@keypro/cli 0.1.0

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 ADDED
@@ -0,0 +1,1473 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { createInterface } from "readline/promises";
5
+ import { writeFileSync as writeFileSync2 } from "fs";
6
+ import { randomUUID } from "crypto";
7
+ import { Command } from "commander";
8
+
9
+ // src/client.ts
10
+ var KeyproApiError = class extends Error {
11
+ constructor(status, code, message, details) {
12
+ super(message);
13
+ this.status = status;
14
+ this.code = code;
15
+ this.details = details;
16
+ this.name = "KeyproApiError";
17
+ }
18
+ status;
19
+ code;
20
+ details;
21
+ };
22
+ var KeyproClient = class {
23
+ constructor(opts) {
24
+ this.opts = opts;
25
+ }
26
+ opts;
27
+ async request(method, path, body, extraHeaders) {
28
+ const headers = {
29
+ accept: "application/json",
30
+ ...extraHeaders
31
+ };
32
+ if (this.opts.apiKey) headers.authorization = `Bearer ${this.opts.apiKey}`;
33
+ if (body !== void 0) headers["content-type"] = "application/json";
34
+ let response;
35
+ try {
36
+ response = await fetch(`${this.opts.apiBase}${path}`, {
37
+ method,
38
+ headers,
39
+ body: body === void 0 ? void 0 : JSON.stringify(body)
40
+ });
41
+ } catch (err) {
42
+ throw new KeyproApiError(
43
+ 0,
44
+ "network_error",
45
+ `Nem siker\xFClt el\xE9rni a szervert (${this.opts.apiBase}): ${err instanceof Error ? err.message : String(err)}`
46
+ );
47
+ }
48
+ let payload;
49
+ try {
50
+ payload = await response.json();
51
+ } catch {
52
+ throw new KeyproApiError(
53
+ response.status,
54
+ "invalid_response",
55
+ `A szerver nem JSON v\xE1laszt adott (HTTP ${response.status}). J\xF3 az api-base be\xE1ll\xEDt\xE1s?`
56
+ );
57
+ }
58
+ const envelope = payload;
59
+ if (envelope.ok === true && envelope.data !== void 0) {
60
+ return envelope.data;
61
+ }
62
+ const error = envelope.error ?? {};
63
+ throw new KeyproApiError(
64
+ response.status,
65
+ error.code ?? "unknown_error",
66
+ error.message ?? `Ismeretlen hiba (HTTP ${response.status}).`,
67
+ error.details
68
+ );
69
+ }
70
+ // --- Auth / kulcsok ---
71
+ login(email, password, name) {
72
+ return this.request("POST", "/api/v1/auth/login", { email, password, name });
73
+ }
74
+ me() {
75
+ return this.request("GET", "/api/v1/me");
76
+ }
77
+ keysList() {
78
+ return this.request(
79
+ "GET",
80
+ "/api/v1/keys"
81
+ );
82
+ }
83
+ keyRevoke(keyId) {
84
+ return this.request(
85
+ "DELETE",
86
+ `/api/v1/keys/${keyId}`
87
+ );
88
+ }
89
+ // --- Termekek ---
90
+ productsSearch(params) {
91
+ const qs = new URLSearchParams();
92
+ if (params.q) qs.set("q", params.q);
93
+ if (params.category) qs.set("category", params.category);
94
+ if (params.onSale) qs.set("on_sale", "true");
95
+ if (params.sort) qs.set("sort", params.sort);
96
+ if (params.limit) qs.set("limit", String(params.limit));
97
+ if (params.offset) qs.set("offset", String(params.offset));
98
+ const suffix = qs.size > 0 ? `?${qs}` : "";
99
+ return this.request("GET", `/api/v1/products${suffix}`);
100
+ }
101
+ productGet(key2) {
102
+ return this.request(
103
+ "GET",
104
+ `/api/v1/products/${encodeURIComponent(key2)}`
105
+ );
106
+ }
107
+ // --- Rendelesek ---
108
+ orderPreview(req) {
109
+ return this.request("POST", "/api/v1/orders/preview", req);
110
+ }
111
+ orderCreate(req, idempotencyKey) {
112
+ return this.request(
113
+ "POST",
114
+ "/api/v1/orders",
115
+ req,
116
+ idempotencyKey ? { "idempotency-key": idempotencyKey } : void 0
117
+ );
118
+ }
119
+ ordersList(params) {
120
+ const qs = new URLSearchParams();
121
+ if (params.status) qs.set("status", params.status);
122
+ if (params.limit) qs.set("limit", String(params.limit));
123
+ if (params.offset) qs.set("offset", String(params.offset));
124
+ const suffix = qs.size > 0 ? `?${qs}` : "";
125
+ return this.request(
126
+ "GET",
127
+ `/api/v1/orders${suffix}`
128
+ );
129
+ }
130
+ orderGet(id) {
131
+ return this.request("GET", `/api/v1/orders/${id}`);
132
+ }
133
+ orderKeys(id) {
134
+ return this.request("GET", `/api/v1/orders/${id}/keys`);
135
+ }
136
+ licenseKeys() {
137
+ return this.request("GET", "/api/v1/license-keys");
138
+ }
139
+ // --- Szamlak ---
140
+ invoicesList(params) {
141
+ const qs = new URLSearchParams();
142
+ if (params.orderId) qs.set("order_id", String(params.orderId));
143
+ if (params.limit) qs.set("limit", String(params.limit));
144
+ if (params.offset) qs.set("offset", String(params.offset));
145
+ const suffix = qs.size > 0 ? `?${qs}` : "";
146
+ return this.request(
147
+ "GET",
148
+ `/api/v1/invoices${suffix}`
149
+ );
150
+ }
151
+ invoiceGet(id) {
152
+ return this.request(
153
+ "GET",
154
+ `/api/v1/invoices/${id}`
155
+ );
156
+ }
157
+ // --- Profil / wallet / kartyak / csomagpontok ---
158
+ profileGet() {
159
+ return this.request(
160
+ "GET",
161
+ "/api/v1/profile"
162
+ );
163
+ }
164
+ profileUpdate(patch) {
165
+ return this.request("PATCH", "/api/v1/profile", patch);
166
+ }
167
+ wallet(params = {}) {
168
+ const qs = new URLSearchParams();
169
+ if (params.limit) qs.set("limit", String(params.limit));
170
+ if (params.offset) qs.set("offset", String(params.offset));
171
+ const suffix = qs.size > 0 ? `?${qs}` : "";
172
+ return this.request("GET", `/api/v1/wallet${suffix}`);
173
+ }
174
+ cardsList() {
175
+ return this.request("GET", "/api/v1/cards");
176
+ }
177
+ parcelshopsSearch(q, type) {
178
+ const qs = new URLSearchParams({ q });
179
+ if (type) qs.set("type", type);
180
+ return this.request("GET", `/api/v1/shipping/parcelshops?${qs}`);
181
+ }
182
+ };
183
+ function createClient(opts) {
184
+ return new KeyproClient(opts);
185
+ }
186
+
187
+ // src/config.ts
188
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
189
+ import { homedir } from "os";
190
+ import { join } from "path";
191
+ var DEFAULT_API_BASE = "https://keypro.hu";
192
+ function configDir() {
193
+ const xdg = process.env.XDG_CONFIG_HOME;
194
+ return join(xdg && xdg.length > 0 ? xdg : join(homedir(), ".config"), "keypro");
195
+ }
196
+ function configPath() {
197
+ return join(configDir(), "config.json");
198
+ }
199
+ function readConfig() {
200
+ try {
201
+ if (!existsSync(configPath())) return {};
202
+ const raw = JSON.parse(readFileSync(configPath(), "utf8"));
203
+ if (typeof raw !== "object" || raw === null) return {};
204
+ const obj = raw;
205
+ return {
206
+ apiKey: typeof obj.apiKey === "string" ? obj.apiKey : void 0,
207
+ apiBase: typeof obj.apiBase === "string" ? obj.apiBase : void 0
208
+ };
209
+ } catch {
210
+ return {};
211
+ }
212
+ }
213
+ function writeConfig(patch) {
214
+ const next = { ...readConfig(), ...patch };
215
+ const clean = Object.fromEntries(
216
+ Object.entries(next).filter(([, v]) => v !== void 0)
217
+ );
218
+ mkdirSync(configDir(), { recursive: true });
219
+ writeFileSync(configPath(), JSON.stringify(clean, null, 2) + "\n", {
220
+ mode: 384
221
+ });
222
+ try {
223
+ chmodSync(configPath(), 384);
224
+ } catch {
225
+ }
226
+ return clean;
227
+ }
228
+ function resolveConfig(flags) {
229
+ const file = readConfig();
230
+ const apiBase = (flags.apiBase ?? process.env.KEYPRO_API_BASE ?? file.apiBase ?? DEFAULT_API_BASE).replace(/\/$/, "");
231
+ if (flags.apiKey) return { apiBase, apiKey: flags.apiKey, keySource: "flag" };
232
+ if (process.env.KEYPRO_API_KEY) {
233
+ return { apiBase, apiKey: process.env.KEYPRO_API_KEY, keySource: "env" };
234
+ }
235
+ if (file.apiKey) return { apiBase, apiKey: file.apiKey, keySource: "config" };
236
+ return { apiBase, apiKey: null, keySource: "none" };
237
+ }
238
+
239
+ // src/agent-docs.ts
240
+ var AGENT_DOCS = `# KeyPro CLI - AI agent guide
241
+
242
+ Magyar: ez a KeyPro.hu B2B licencshop parancssori eszkoze; az alabbi angol
243
+ utmutato AI-agenteknek (Claude Code, Codex) szol.
244
+
245
+ ## Setup
246
+
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 ...\`.
254
+ 3. Verify with: \`keypro whoami --json\`
255
+
256
+ ## Output contract
257
+
258
+ - Every command supports \`--json\`: machine-readable data on stdout.
259
+ - Errors go to stderr; in --json mode they are JSON with a stable
260
+ \`error.code\` (snake_case English). Key on \`code\`, not on the Hungarian
261
+ \`message\`.
262
+ - Exit codes: 0 success, 1 API/business error, 2 usage error, 3 auth error.
263
+
264
+ ## Ordering flow (IMPORTANT)
265
+
266
+ Ordering is a two-step preview + confirm flow to prevent accidental orders:
267
+
268
+ 1. \`keypro order preview --item SKU=QTY --payment bacs --json\`
269
+ Returns priced lines, fees, shipping, totals and a \`confirmToken\`
270
+ (valid 15 minutes, bound to items + payment method + gross total).
271
+ ALWAYS show the totals to the user before ordering.
272
+ 2. \`keypro order create --item SKU=QTY --payment bacs --yes --json\`
273
+ Without \`--yes\` the command only prints the preview and exits with
274
+ code 1. With \`--yes\` it re-runs the preview and submits with the fresh
275
+ confirmToken. If prices changed between preview and create, the server
276
+ rejects with \`confirm_token_invalid\` and returns the new totals in
277
+ \`error.details\` - re-run preview and show the user the new total.
278
+ 3. Retries: pass \`--idempotency-key <any-unique-string>\` - the same key
279
+ never creates a second order (the response has \`idempotentReplay: true\`).
280
+
281
+ Payment methods (\`--payment\`):
282
+ - \`bacs\` bank transfer: order goes on-hold, a proforma invoice
283
+ (dijbekero) is issued; keys are delivered after payment arrives.
284
+ - \`cheque\` 8-day payment terms (+5% fee on net product total).
285
+ - \`cod\` cash on delivery (physical shipments only, +1.5 EUR fee).
286
+ - \`wallet\` KEP balance (net total deducted immediately).
287
+ - \`card\` saved bank card (Stripe, off-session). If the bank requires
288
+ 3DS or there is no saved card, the response contains \`payment.paymentUrl\`
289
+ - give this link to the user to open in a browser (valid ~1 hour).
290
+ Select a specific card with \`--card pm_...\` (see \`keypro cards list\`).
291
+
292
+ Physical products need \`--shipping gls_hd|gls_parcelshop|combine_free\`;
293
+ for gls_parcelshop also \`--parcelshop <ID>\`
294
+ (search: \`keypro parcelshops search <city|zip>\`).
295
+
296
+ ## Queries
297
+
298
+ - \`keypro products search <query>\` / \`keypro products get <sku|id>\`
299
+ - \`keypro order list [--status <status>]\` / \`keypro order get <id>\`
300
+ - \`keypro keys list [--order <id>]\` - delivered license keys
301
+ - \`keypro invoices list [--order <id>]\` / \`keypro invoices get <id>\`
302
+ (each invoice has a public \`downloadUrl\` PDF link)
303
+ - \`keypro wallet\` / \`keypro wallet transactions\` - KEP balance + history
304
+ - \`keypro profile get\` / \`keypro profile set billing.city=Budapest ...\`
305
+ (sections: contact.*, billing.*, shipping.*)
306
+ - \`keypro cards list\` - saved cards (add new cards on the website only)
307
+
308
+ ## MCP server mode
309
+
310
+ Register the CLI as a native MCP toolset (recommended for Claude Code):
311
+
312
+ claude mcp add keypro -- keypro mcp
313
+
314
+ Auth comes from KEYPRO_API_KEY / config; there is no login tool over MCP.
315
+ The keypro_order_create tool requires the confirmToken from
316
+ keypro_order_preview - same safety flow as the CLI.
317
+
318
+ ## Error codes
319
+
320
+ unauthorized, forbidden_scope, rate_limited, validation_failed, not_found,
321
+ unknown_product, coupon_invalid, shipping_required, invalid_parcelshop,
322
+ insufficient_wallet_balance, confirm_required, confirm_token_invalid,
323
+ invalid_card, stripe_unavailable, account_pending, account_inactive,
324
+ invalid_credentials, network_error, internal
325
+ `;
326
+
327
+ // src/items.ts
328
+ function parseItemSpec(spec) {
329
+ const raw = spec.trim();
330
+ if (!raw) throw new Error("\xDCres t\xE9tel-megad\xE1s.");
331
+ const eq = raw.lastIndexOf("=");
332
+ const idPart = eq === -1 ? raw : raw.slice(0, eq);
333
+ const qtyPart = eq === -1 ? "" : raw.slice(eq + 1);
334
+ let qty = 1;
335
+ if (qtyPart !== "") {
336
+ qty = Number(qtyPart);
337
+ if (!Number.isInteger(qty) || qty < 1 || qty > 999) {
338
+ throw new Error(
339
+ `\xC9rv\xE9nytelen darabsz\xE1m: "${qtyPart}" (1-999 k\xF6z\xF6tti eg\xE9sz sz\xE1m kell).`
340
+ );
341
+ }
342
+ }
343
+ if (idPart.toLowerCase().startsWith("id:")) {
344
+ const productId = Number(idPart.slice(3));
345
+ if (!Number.isInteger(productId) || productId < 1) {
346
+ throw new Error(`\xC9rv\xE9nytelen term\xE9k-azonos\xEDt\xF3: "${idPart}".`);
347
+ }
348
+ return { productId, qty };
349
+ }
350
+ if (idPart.length === 0) {
351
+ throw new Error(`Hi\xE1nyz\xF3 cikksz\xE1m a t\xE9telben: "${spec}".`);
352
+ }
353
+ return { sku: idPart, qty };
354
+ }
355
+
356
+ // src/output.ts
357
+ var jsonMode = false;
358
+ function setJsonMode(on) {
359
+ jsonMode = on;
360
+ }
361
+ function output(data, human) {
362
+ if (jsonMode) {
363
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
364
+ } else {
365
+ human();
366
+ }
367
+ }
368
+ function fail(err) {
369
+ if (err instanceof KeyproApiError) {
370
+ if (jsonMode) {
371
+ process.stderr.write(
372
+ JSON.stringify(
373
+ {
374
+ ok: false,
375
+ error: { code: err.code, message: err.message, details: err.details }
376
+ },
377
+ null,
378
+ 2
379
+ ) + "\n"
380
+ );
381
+ } else {
382
+ process.stderr.write(`Hiba (${err.code}): ${err.message}
383
+ `);
384
+ if (err.details !== void 0) {
385
+ process.stderr.write(`R\xE9szletek: ${JSON.stringify(err.details)}
386
+ `);
387
+ }
388
+ }
389
+ process.exit(err.status === 401 || err.status === 403 ? 3 : 1);
390
+ }
391
+ const message = err instanceof Error ? err.message : String(err);
392
+ if (jsonMode) {
393
+ process.stderr.write(
394
+ JSON.stringify({ ok: false, error: { code: "cli_error", message } }) + "\n"
395
+ );
396
+ } else {
397
+ process.stderr.write(`Hiba: ${message}
398
+ `);
399
+ }
400
+ process.exit(1);
401
+ }
402
+ function usageError(message) {
403
+ process.stderr.write(`${message}
404
+ `);
405
+ process.exit(2);
406
+ }
407
+ function printKV(rows) {
408
+ const width = Math.max(...rows.map(([k]) => k.length));
409
+ for (const [key2, value] of rows) {
410
+ if (value === null || value === void 0 || value === "") continue;
411
+ process.stdout.write(`${key2.padEnd(width)} ${value}
412
+ `);
413
+ }
414
+ }
415
+ function printTable(headers, rows) {
416
+ const cells = rows.map(
417
+ (row) => row.map((cell) => cell === null || cell === void 0 ? "-" : String(cell))
418
+ );
419
+ const widths = headers.map(
420
+ (h, i) => Math.max(h.length, ...cells.map((row) => (row[i] ?? "").length))
421
+ );
422
+ const line = (row) => row.map((cell, i) => cell.padEnd(widths[i])).join(" ") + "\n";
423
+ process.stdout.write(line(headers));
424
+ process.stdout.write(line(widths.map((w) => "-".repeat(w))));
425
+ for (const row of cells) process.stdout.write(line(row));
426
+ }
427
+ function eurFmt(value) {
428
+ return `${value.toFixed(2)} EUR`;
429
+ }
430
+
431
+ // src/mcp.ts
432
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
433
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
434
+ import { z } from "zod";
435
+ var addressShape = z.object({
436
+ firstName: z.string().optional(),
437
+ lastName: z.string().optional(),
438
+ company: z.string().optional(),
439
+ address1: z.string().optional(),
440
+ address2: z.string().optional(),
441
+ city: z.string().optional(),
442
+ postcode: z.string().optional(),
443
+ state: z.string().optional(),
444
+ country: z.string().optional(),
445
+ email: z.string().optional(),
446
+ phone: z.string().optional()
447
+ }).optional();
448
+ var orderRequestShape = {
449
+ items: z.array(
450
+ z.object({
451
+ sku: z.string().optional().describe("Product SKU (either sku or productId)"),
452
+ productId: z.number().int().positive().optional(),
453
+ qty: z.number().int().min(1).max(999).default(1)
454
+ })
455
+ ).min(1).describe("Order lines"),
456
+ paymentMethod: z.enum(["bacs", "cheque", "cod", "wallet", "stripe"]).describe(
457
+ "bacs=bank transfer (proforma first), cheque=8-day terms (+5%), cod=cash on delivery, wallet=KEP balance, stripe=saved card"
458
+ ),
459
+ shippingMethodId: z.enum(["gls_hd", "gls_parcelshop", "combine_free"]).optional().describe("Required when the cart contains physical products"),
460
+ parcelshopId: z.string().optional().describe("GLS pickup point id (required for gls_parcelshop)"),
461
+ couponCode: z.string().optional(),
462
+ currency: z.enum(["EUR", "HUF"]).default("EUR"),
463
+ billing: addressShape.describe(
464
+ "Per-field billing address overrides (defaults come from the user profile)"
465
+ ),
466
+ shipping: addressShape.describe(
467
+ "Separate shipping address (omit to ship to the billing address)"
468
+ ),
469
+ taxNumber: z.string().optional(),
470
+ cardId: z.string().optional().describe("Saved card id (pm_...) for stripe payments; omit for default card")
471
+ };
472
+ function jsonResult(data) {
473
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
474
+ }
475
+ function errorResult(err) {
476
+ 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) };
477
+ return {
478
+ content: [{ type: "text", text: JSON.stringify({ error: payload }) }],
479
+ isError: true
480
+ };
481
+ }
482
+ async function run(fn) {
483
+ try {
484
+ return jsonResult(await fn());
485
+ } catch (err) {
486
+ return errorResult(err);
487
+ }
488
+ }
489
+ async function runMcpServer(client) {
490
+ const server = new McpServer({ name: "keypro", version: "0.1.0" });
491
+ server.registerTool(
492
+ "keypro_whoami",
493
+ {
494
+ description: "The authenticated KeyPro account (email, company, role, KEP wallet balance, API key scopes).",
495
+ inputSchema: {}
496
+ },
497
+ () => run(() => client.me())
498
+ );
499
+ server.registerTool(
500
+ "keypro_products_search",
501
+ {
502
+ description: "Search the KeyPro product catalog by name or SKU. Returns id, sku, name, net EUR price.",
503
+ inputSchema: {
504
+ q: z.string().optional().describe("Search text (name or SKU)"),
505
+ category: z.string().optional().describe("Category slug filter"),
506
+ onSale: z.boolean().optional().describe("Only discounted products"),
507
+ limit: z.number().int().min(1).max(100).optional()
508
+ }
509
+ },
510
+ (args) => run(
511
+ () => client.productsSearch({
512
+ q: args.q,
513
+ category: args.category,
514
+ onSale: args.onSale,
515
+ limit: args.limit
516
+ })
517
+ )
518
+ );
519
+ server.registerTool(
520
+ "keypro_product_get",
521
+ {
522
+ description: "Product details by id, slug or SKU, including the caller's effective unit price with discounts.",
523
+ inputSchema: { key: z.string().describe("Product id, slug or SKU") }
524
+ },
525
+ (args) => run(() => client.productGet(args.key))
526
+ );
527
+ server.registerTool(
528
+ "keypro_order_preview",
529
+ {
530
+ 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.",
531
+ inputSchema: orderRequestShape
532
+ },
533
+ (args) => run(() => client.orderPreview(args))
534
+ );
535
+ server.registerTool(
536
+ "keypro_order_create",
537
+ {
538
+ 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.",
539
+ inputSchema: {
540
+ ...orderRequestShape,
541
+ confirmToken: z.string().describe("Token from keypro_order_preview"),
542
+ idempotencyKey: z.string().optional().describe("Unique retry-dedup key; the same key never creates a second order")
543
+ }
544
+ },
545
+ (args) => run(() => {
546
+ const { idempotencyKey, ...request } = args;
547
+ return client.orderCreate(request, idempotencyKey);
548
+ })
549
+ );
550
+ server.registerTool(
551
+ "keypro_orders_list",
552
+ {
553
+ description: "List the user's orders (newest first), optional status filter.",
554
+ inputSchema: {
555
+ status: z.string().optional().describe("e.g. pending, processing, completed"),
556
+ limit: z.number().int().min(1).max(100).optional()
557
+ }
558
+ },
559
+ (args) => run(() => client.ordersList({ status: args.status, limit: args.limit }))
560
+ );
561
+ server.registerTool(
562
+ "keypro_order_get",
563
+ {
564
+ description: "Order details: items, totals, status, invoices, and a payment link if the order is awaiting card payment.",
565
+ inputSchema: { orderId: z.number().int().positive() }
566
+ },
567
+ (args) => run(() => client.orderGet(args.orderId))
568
+ );
569
+ server.registerTool(
570
+ "keypro_order_keys",
571
+ {
572
+ description: "License keys delivered for one order. COD orders release keys only after completion.",
573
+ inputSchema: { orderId: z.number().int().positive() }
574
+ },
575
+ (args) => run(() => client.orderKeys(args.orderId))
576
+ );
577
+ server.registerTool(
578
+ "keypro_license_keys",
579
+ {
580
+ description: "All delivered license keys of the user, grouped by product.",
581
+ inputSchema: {}
582
+ },
583
+ () => run(() => client.licenseKeys())
584
+ );
585
+ server.registerTool(
586
+ "keypro_invoices_list",
587
+ {
588
+ description: "List invoices/proformas (each has a public downloadUrl PDF link). Optional order filter.",
589
+ inputSchema: {
590
+ orderId: z.number().int().positive().optional(),
591
+ limit: z.number().int().min(1).max(100).optional()
592
+ }
593
+ },
594
+ (args) => run(() => client.invoicesList({ orderId: args.orderId, limit: args.limit }))
595
+ );
596
+ server.registerTool(
597
+ "keypro_invoice_get",
598
+ {
599
+ description: "One invoice with totals and public downloadUrl.",
600
+ inputSchema: { invoiceId: z.number().int().positive() }
601
+ },
602
+ (args) => run(() => client.invoiceGet(args.invoiceId))
603
+ );
604
+ server.registerTool(
605
+ "keypro_profile_get",
606
+ {
607
+ description: "The account profile: contact data, billing and shipping address.",
608
+ inputSchema: {}
609
+ },
610
+ () => run(() => client.profileGet())
611
+ );
612
+ server.registerTool(
613
+ "keypro_profile_update",
614
+ {
615
+ 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.",
616
+ inputSchema: {
617
+ fields: z.record(z.string(), z.string()).describe("Field name -> new value map (flat API field names)")
618
+ }
619
+ },
620
+ (args) => run(() => client.profileUpdate(args.fields))
621
+ );
622
+ server.registerTool(
623
+ "keypro_wallet",
624
+ {
625
+ description: "KEP wallet balance (net EUR) and transaction history (topup/payment/refund/bonus with running balance).",
626
+ inputSchema: {
627
+ limit: z.number().int().min(1).max(100).optional().describe("History length")
628
+ }
629
+ },
630
+ (args) => run(() => client.wallet({ limit: args.limit }))
631
+ );
632
+ server.registerTool(
633
+ "keypro_cards_list",
634
+ {
635
+ description: "Saved bank cards (brand, last4, default flag). New cards can only be added on the website.",
636
+ inputSchema: {}
637
+ },
638
+ () => run(() => client.cardsList())
639
+ );
640
+ server.registerTool(
641
+ "keypro_parcelshops_search",
642
+ {
643
+ description: "Search GLS pickup points (city, zip prefix or name). Use the returned id as parcelshopId for gls_parcelshop shipping.",
644
+ inputSchema: {
645
+ q: z.string().describe("City, zip prefix or name"),
646
+ type: z.enum(["parcel-shop", "parcel-locker", "all"]).optional()
647
+ }
648
+ },
649
+ (args) => run(() => client.parcelshopsSearch(args.q, args.type))
650
+ );
651
+ const transport = new StdioServerTransport();
652
+ await server.connect(transport);
653
+ }
654
+
655
+ // src/index.ts
656
+ var program = new Command();
657
+ program.name("keypro").description(
658
+ "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) => {
660
+ setJsonMode(Boolean(thisCommand.optsWithGlobals().json));
661
+ });
662
+ function resolved(cmd) {
663
+ const opts = cmd.optsWithGlobals();
664
+ return resolveConfig({ apiKey: opts.apiKey, apiBase: opts.apiBase });
665
+ }
666
+ function clientFor(cmd, requireKey = true) {
667
+ const cfg = resolved(cmd);
668
+ if (requireKey && !cfg.apiKey) {
669
+ process.stderr.write(
670
+ "Nincs API kulcs be\xE1ll\xEDtva. Futtasd: keypro login, vagy add meg a KEYPRO_API_KEY k\xF6rnyezeti v\xE1ltoz\xF3t / a --api-key kapcsol\xF3t.\n"
671
+ );
672
+ process.exit(3);
673
+ }
674
+ return createClient({ apiBase: cfg.apiBase, apiKey: cfg.apiKey });
675
+ }
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
+ async function promptText(question) {
707
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
708
+ try {
709
+ return (await rl.question(question)).trim();
710
+ } finally {
711
+ rl.close();
712
+ }
713
+ }
714
+ program.command("login").description("bejelentkezes email + jelszoval; uj API kulcsot ment a configba").option("--email <email>").option("--password <jelszo>", "jelszo (ha nincs megadva, rejtve keri be)").option("--name <nev>", "a letrejovo API kulcs cimkeje", "CLI login").action(async (opts, cmd) => {
715
+ try {
716
+ const cfg = resolved(cmd);
717
+ const email = opts.email ?? await promptText("Email: ");
718
+ const password = opts.password ?? await promptHidden("Jelsz\xF3: ");
719
+ const client = createClient({ apiBase: cfg.apiBase });
720
+ const result = await client.login(email, password, opts.name);
721
+ writeConfig({ apiKey: result.token, apiBase: cfg.apiBase });
722
+ output(
723
+ { prefix: result.prefix, keyId: result.keyId, scopes: result.scopes, apiBase: cfg.apiBase, configPath: configPath() },
724
+ () => {
725
+ process.stdout.write(
726
+ `Sikeres bejelentkez\xE9s. \xDAj API kulcs mentve (${result.prefix}...) ide: ${configPath()}
727
+ `
728
+ );
729
+ }
730
+ );
731
+ } catch (err) {
732
+ fail(err);
733
+ }
734
+ });
735
+ program.command("logout").description("a mentett API kulcs torlese a configbol").option("--revoke", "a kulcs visszavonasa a szerveren is", false).action(async (opts, cmd) => {
736
+ try {
737
+ if (opts.revoke) {
738
+ const client = clientFor(cmd);
739
+ const me = await client.me();
740
+ await client.keyRevoke(me.key.id);
741
+ }
742
+ writeConfig({ apiKey: void 0 });
743
+ output({ loggedOut: true, revoked: opts.revoke }, () => {
744
+ process.stdout.write(
745
+ opts.revoke ? "Kijelentkezve, a kulcs a szerveren is visszavonva.\n" : "Kijelentkezve (a kulcs t\xF6r\xF6lve a configb\xF3l).\n"
746
+ );
747
+ });
748
+ } catch (err) {
749
+ fail(err);
750
+ }
751
+ });
752
+ program.command("whoami").description("a bejelentkezett fiok adatai").action(async (_opts, cmd) => {
753
+ try {
754
+ const me = await clientFor(cmd).me();
755
+ output(me, () => {
756
+ printKV([
757
+ ["Fi\xF3k", `${me.email} (#${me.id})`],
758
+ ["C\xE9g", me.companyName],
759
+ ["Szerep", me.role],
760
+ ["KEP egyenleg", eurFmt(me.walletBalanceEurNet)],
761
+ ["API kulcs", `${me.key.prefix}... (${me.key.name})`],
762
+ ["Jogosults\xE1gok", me.key.scopes.join(", ")]
763
+ ]);
764
+ });
765
+ } catch (err) {
766
+ fail(err);
767
+ }
768
+ });
769
+ var config = program.command("config").description("CLI beallitasok");
770
+ config.command("get").description("aktualis beallitasok").action((_opts, cmd) => {
771
+ const cfg = resolved(cmd);
772
+ const file = readConfig();
773
+ output(
774
+ {
775
+ apiBase: cfg.apiBase,
776
+ apiKeySet: cfg.apiKey !== null,
777
+ keySource: cfg.keySource,
778
+ configPath: configPath(),
779
+ configFile: { apiBase: file.apiBase, apiKeySet: Boolean(file.apiKey) }
780
+ },
781
+ () => {
782
+ printKV([
783
+ ["API c\xEDm", cfg.apiBase],
784
+ ["API kulcs", cfg.apiKey ? `be\xE1ll\xEDtva (forr\xE1s: ${cfg.keySource})` : "nincs"],
785
+ ["Config f\xE1jl", configPath()]
786
+ ]);
787
+ }
788
+ );
789
+ });
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`);
793
+ }
794
+ if (!/^https?:\/\//.test(value)) {
795
+ usageError("Az api-base http(s) URL kell legyen, pl. https://keypro.hu");
796
+ }
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
+ });
802
+ });
803
+ var key = program.command("key").description("API kulcsok kezelese");
804
+ key.command("list").description("a fiok API kulcsai").action(async (_opts, cmd) => {
805
+ try {
806
+ const { keys } = await clientFor(cmd).keysList();
807
+ output({ keys }, () => {
808
+ printTable(
809
+ ["ID", "Prefix", "N\xE9v", "Jogok", "Utolj\xE1ra haszn\xE1lva", "\xC1llapot"],
810
+ keys.map((k) => [
811
+ String(k.id),
812
+ `${k.prefix}...`,
813
+ String(k.name),
814
+ k.scopes.join(","),
815
+ k.lastUsedAt ? String(k.lastUsedAt).slice(0, 10) : "-",
816
+ k.revokedAt ? "visszavonva" : "akt\xEDv"
817
+ ])
818
+ );
819
+ });
820
+ } catch (err) {
821
+ fail(err);
822
+ }
823
+ });
824
+ key.command("revoke <id>").description("API kulcs visszavonasa").action(async (id, _opts, cmd) => {
825
+ try {
826
+ const result = await clientFor(cmd).keyRevoke(Number(id));
827
+ output(result, () => {
828
+ process.stdout.write(`A(z) ${id} kulcs visszavonva.
829
+ `);
830
+ });
831
+ } catch (err) {
832
+ fail(err);
833
+ }
834
+ });
835
+ var products = program.command("products").description("termek-katalogus");
836
+ products.command("search [query]").description("termekkereses nev vagy cikkszam alapjan").option("--category <slug>", "kategoria-szures").option("--on-sale", "csak akcios termekek", false).option("--sort <mod>", "rendezes: name|price_asc|price_desc|newest").option("--limit <n>", "talalatok szama (max 100)", "25").action(
837
+ async (query, opts, cmd) => {
838
+ try {
839
+ const result = await clientFor(cmd).productsSearch({
840
+ q: query,
841
+ category: opts.category,
842
+ onSale: opts.onSale,
843
+ sort: opts.sort,
844
+ limit: Number(opts.limit)
845
+ });
846
+ output(result, () => {
847
+ printTable(
848
+ ["ID", "SKU", "N\xE9v", "Nett\xF3 EUR", "Akci\xF3s"],
849
+ result.products.map((p) => [
850
+ String(p.id),
851
+ p.sku ? String(p.sku) : "-",
852
+ String(p.name),
853
+ Number(p.netPriceEur).toFixed(2),
854
+ p.onSale ? "igen" : ""
855
+ ])
856
+ );
857
+ process.stdout.write(`
858
+ \xD6sszesen: ${result.total} tal\xE1lat
859
+ `);
860
+ });
861
+ } catch (err) {
862
+ fail(err);
863
+ }
864
+ }
865
+ );
866
+ products.command("get <skuVagyId>").description("termek reszletei (id, slug vagy cikkszam)").action(async (keyArg, _opts, cmd) => {
867
+ try {
868
+ const p = await clientFor(cmd).productGet(keyArg);
869
+ output(p, () => {
870
+ printKV([
871
+ ["Term\xE9k", `${p.name} (#${p.id})`],
872
+ ["SKU", p.sku],
873
+ ["Lista nett\xF3 \xE1r", `${Number(p.listNetPriceEur).toFixed(2)} EUR`],
874
+ ["Akci\xF3s", p.onSale ? "igen" : "nem"],
875
+ ["A te nett\xF3 egys\xE9g\xE1rad", `${Number(p.yourUnitNetEur).toFixed(2)} EUR`],
876
+ [
877
+ "Kedvezm\xE9nyed",
878
+ Number(p.yourDiscountPercent) > 0 ? `${p.yourDiscountPercent}%` : null
879
+ ],
880
+ ["Sz\xE1ll\xEDt\xE1st ig\xE9nyel", p.isVirtual ? "nem (digit\xE1lis)" : "igen (fizikai)"]
881
+ ]);
882
+ });
883
+ } catch (err) {
884
+ fail(err);
885
+ }
886
+ });
887
+ var ADDRESS_FLAG_FIELDS = [
888
+ ["first-name", "firstName"],
889
+ ["last-name", "lastName"],
890
+ ["company", "company"],
891
+ ["address1", "address1"],
892
+ ["address2", "address2"],
893
+ ["city", "city"],
894
+ ["postcode", "postcode"],
895
+ ["state", "state"],
896
+ ["country", "country"],
897
+ ["email", "email"],
898
+ ["phone", "phone"]
899
+ ];
900
+ function collect(value, prev) {
901
+ return [...prev, value];
902
+ }
903
+ function addOrderOptions(cmd) {
904
+ cmd.option(
905
+ "--item <tetel>",
906
+ "tetel: SKU=DB vagy id:SZAM=DB (tobbszor is megadhato)",
907
+ collect,
908
+ []
909
+ ).option(
910
+ "--payment <mod>",
911
+ "fizetesi mod: bacs|cheque|cod|wallet|card"
912
+ ).option("--shipping <mod>", "szallitasi mod: gls_hd|gls_parcelshop|combine_free").option("--parcelshop <id>", "GLS atveteli pont azonosito (gls_parcelshop eseten)").option("--coupon <kod>", "kuponkod").option("--currency <penznem>", "EUR vagy HUF", "EUR").option("--tax-number <adoszam>", "adoszam (ceges szamlahoz)").option("--card <pm_id>", "mentett kartya azonosito (card fizetesnel)");
913
+ for (const [flag] of ADDRESS_FLAG_FIELDS) {
914
+ cmd.option(`--billing-${flag} <ertek>`);
915
+ cmd.option(`--shipping-${flag} <ertek>`);
916
+ }
917
+ return cmd;
918
+ }
919
+ function camel(prefix, flag) {
920
+ return prefix + flag.split("-").map((part) => part[0].toUpperCase() + part.slice(1)).join("");
921
+ }
922
+ function buildOrderRequest(opts) {
923
+ const itemSpecs = opts.item ?? [];
924
+ if (itemSpecs.length === 0) {
925
+ usageError("Legal\xE1bb egy --item kell (pl. --item OFF2021=2). Keres\xE9s: keypro products search");
926
+ }
927
+ const payment = String(opts.payment ?? "");
928
+ const methodMap = {
929
+ bacs: "bacs",
930
+ cheque: "cheque",
931
+ cod: "cod",
932
+ wallet: "wallet",
933
+ card: "stripe",
934
+ stripe: "stripe"
935
+ };
936
+ const paymentMethod = methodMap[payment];
937
+ if (!paymentMethod) {
938
+ usageError("Adj meg fizet\xE9si m\xF3dot: --payment bacs|cheque|cod|wallet|card");
939
+ }
940
+ let items;
941
+ try {
942
+ items = itemSpecs.map(parseItemSpec);
943
+ } catch (err) {
944
+ usageError(err instanceof Error ? err.message : String(err));
945
+ }
946
+ const address = (prefix) => {
947
+ const out = {};
948
+ let any = false;
949
+ for (const [flag, field] of ADDRESS_FLAG_FIELDS) {
950
+ const value = opts[camel(prefix, flag)];
951
+ if (typeof value === "string") {
952
+ out[field] = value;
953
+ any = true;
954
+ }
955
+ }
956
+ return any ? out : void 0;
957
+ };
958
+ const currency = String(opts.currency ?? "EUR").toUpperCase();
959
+ if (currency !== "EUR" && currency !== "HUF") {
960
+ usageError("A --currency EUR vagy HUF lehet.");
961
+ }
962
+ return {
963
+ items,
964
+ paymentMethod,
965
+ shippingMethodId: opts.shipping,
966
+ parcelshopId: opts.parcelshop,
967
+ couponCode: opts.coupon,
968
+ currency,
969
+ billing: address("billing"),
970
+ shipping: address("shipping"),
971
+ taxNumber: opts.taxNumber,
972
+ cardId: opts.card
973
+ };
974
+ }
975
+ function printPreview(preview) {
976
+ printTable(
977
+ ["Term\xE9k", "Db", "Nett\xF3 egys\xE9g\xE1r", "Nett\xF3 \xF6sszesen"],
978
+ preview.lines.map((line) => [
979
+ String(line.name),
980
+ String(line.qty),
981
+ Number(line.unitNetEur).toFixed(2),
982
+ Number(line.lineNetEur).toFixed(2)
983
+ ])
984
+ );
985
+ process.stdout.write("\n");
986
+ const rows = [
987
+ ["Fizet\xE9si m\xF3d", String(preview.payment.label ?? "")]
988
+ ];
989
+ for (const fee of preview.payment.fees ?? []) {
990
+ rows.push([fee.label, eurFmt(fee.netEur)]);
991
+ }
992
+ if (preview.shipping) {
993
+ rows.push([
994
+ `Sz\xE1ll\xEDt\xE1s (${preview.shipping.label})`,
995
+ eurFmt(Number(preview.shipping.netEur ?? 0))
996
+ ]);
997
+ }
998
+ if (preview.coupon) {
999
+ rows.push([
1000
+ `Kupon (${preview.coupon.code})`,
1001
+ `-${eurFmt(Number(preview.coupon.discountNetEur ?? 0))}`
1002
+ ]);
1003
+ }
1004
+ rows.push(["Nett\xF3 v\xE9g\xF6sszeg", eurFmt(preview.totals.netTotalEur)]);
1005
+ rows.push(["\xC1FA (27%)", eurFmt(preview.totals.taxTotalEur)]);
1006
+ rows.push(["Brutt\xF3 v\xE9g\xF6sszeg", eurFmt(preview.totals.grossTotalEur)]);
1007
+ if (preview.currency === "HUF") {
1008
+ rows.push(["Fizetend\u0151", `${preview.displayGrossTotal} HUF`]);
1009
+ }
1010
+ if (preview.wallet) {
1011
+ rows.push([
1012
+ "KEP egyenleg",
1013
+ `${eurFmt(preview.wallet.balanceEurNet)} (${preview.wallet.sufficient ? "fedezi" : "NEM fedezi"})`
1014
+ ]);
1015
+ }
1016
+ printKV(rows);
1017
+ }
1018
+ var order = program.command("order").description("rendelesek");
1019
+ addOrderOptions(
1020
+ order.command("preview").description("rendeles-elonezet: vegosszegek iras nelkul + confirmToken")
1021
+ ).action(async (opts, cmd) => {
1022
+ try {
1023
+ const request = buildOrderRequest(opts);
1024
+ const preview = await clientFor(cmd).orderPreview(request);
1025
+ output(preview, () => {
1026
+ printPreview(preview);
1027
+ process.stdout.write(
1028
+ "\nMegrendel\xE9s: ugyanezekkel a kapcsol\xF3kkal futtasd az order create --yes parancsot.\n"
1029
+ );
1030
+ });
1031
+ } catch (err) {
1032
+ fail(err);
1033
+ }
1034
+ });
1035
+ addOrderOptions(
1036
+ order.command("create").description("rendeles leadasa (elobb elonezet; --yes nelkul nem rendel)")
1037
+ ).option("--yes", "a rendeles tenyleges leadasa", false).option(
1038
+ "--idempotency-key <kulcs>",
1039
+ "retry-dedup kulcs: ugyanazzal a kulccsal nem jon letre masodik rendeles"
1040
+ ).action(async (opts, cmd) => {
1041
+ try {
1042
+ const request = buildOrderRequest(opts);
1043
+ const client = clientFor(cmd);
1044
+ const preview = await client.orderPreview(request);
1045
+ if (!opts.yes) {
1046
+ output({ preview, hint: "Add hozz\xE1 a --yes kapcsol\xF3t a megrendel\xE9shez." }, () => {
1047
+ printPreview(preview);
1048
+ process.stdout.write(
1049
+ "\nEz csak el\u0151n\xE9zet volt. Add hozz\xE1 a --yes kapcsol\xF3t a megrendel\xE9shez.\n"
1050
+ );
1051
+ });
1052
+ process.exit(1);
1053
+ }
1054
+ const idempotencyKey = opts.idempotencyKey ?? `cli-${randomUUID()}`;
1055
+ const result = await client.orderCreate(
1056
+ { ...request, confirmToken: preview.confirmToken },
1057
+ idempotencyKey
1058
+ );
1059
+ output(result, () => {
1060
+ const orderData = result.order;
1061
+ printKV([
1062
+ ["Rendel\xE9s", `#${orderData.number} (id: ${orderData.id})`],
1063
+ ["\xC1llapot", orderData.statusLabel ?? ""],
1064
+ ["Fizet\xE9s", result.payment.note],
1065
+ [
1066
+ "Fizet\xE9si link",
1067
+ result.payment.paymentUrl ? result.payment.paymentUrl : null
1068
+ ],
1069
+ [
1070
+ "KEP egyenleg",
1071
+ result.payment.walletBalanceAfterEur !== void 0 ? eurFmt(result.payment.walletBalanceAfterEur) : null
1072
+ ],
1073
+ [
1074
+ "K\xE9zbes\xEDtett kulcsok",
1075
+ result.deliveredKeyCount > 0 ? `${result.deliveredKeyCount} db (keypro keys list --order ${orderData.id})` : null
1076
+ ],
1077
+ [
1078
+ "Sz\xE1ml\xE1k",
1079
+ result.invoices.length > 0 ? result.invoices.map((inv) => `${inv.typeLabel}: ${inv.downloadUrl ?? "k\xE9sz\xFCl"}`).join("; ") : null
1080
+ ]
1081
+ ]);
1082
+ if (result.payment.paymentUrl) {
1083
+ process.stdout.write(
1084
+ "\nFONTOS: a fizet\xE9shez nyisd meg a fenti fizet\xE9si linket a b\xF6ng\xE9sz\u0151ben (kb. 1 \xF3r\xE1ig \xE9rv\xE9nyes).\n"
1085
+ );
1086
+ }
1087
+ });
1088
+ } catch (err) {
1089
+ fail(err);
1090
+ }
1091
+ });
1092
+ order.command("list").description("rendelesek listaja").option("--status <statusz>", "szures statuszra (pl. processing, completed)").option("--limit <n>", "darabszam", "25").action(async (opts, cmd) => {
1093
+ try {
1094
+ const result = await clientFor(cmd).ordersList({
1095
+ status: opts.status,
1096
+ limit: Number(opts.limit)
1097
+ });
1098
+ output(result, () => {
1099
+ printTable(
1100
+ ["ID", "Sz\xE1m", "D\xE1tum", "\xC1llapot", "Fizet\xE9s", "Brutt\xF3 EUR", "T\xE9telek"],
1101
+ result.orders.map((o) => [
1102
+ String(o.id),
1103
+ `#${o.number}`,
1104
+ String(o.createdAt).slice(0, 10),
1105
+ String(o.statusLabel),
1106
+ String(o.paymentMethodLabel),
1107
+ Number(o.grossTotalEur).toFixed(2),
1108
+ o.itemNames.join(", ").slice(0, 60)
1109
+ ])
1110
+ );
1111
+ });
1112
+ } catch (err) {
1113
+ fail(err);
1114
+ }
1115
+ });
1116
+ order.command("get <id>").description("rendeles reszletei").action(async (id, _opts, cmd) => {
1117
+ try {
1118
+ const result = await clientFor(cmd).orderGet(Number(id));
1119
+ output(result, () => {
1120
+ const o = result.order;
1121
+ printKV([
1122
+ ["Rendel\xE9s", `#${o.number} (id: ${o.id})`],
1123
+ ["\xC1llapot", String(o.statusLabel)],
1124
+ ["Fizet\xE9si m\xF3d", String(o.paymentMethodLabel)],
1125
+ ["D\xE1tum", String(o.createdAt).slice(0, 10)],
1126
+ ["Nett\xF3", eurFmt(Number(o.netTotalEur))],
1127
+ ["Brutt\xF3", eurFmt(Number(o.grossTotalEur))],
1128
+ ["Fizet\xE9si link", result.paymentUrl]
1129
+ ]);
1130
+ process.stdout.write("\n");
1131
+ printTable(
1132
+ ["T\xE9tel", "Db", "Nett\xF3 \xF6sszesen"],
1133
+ o.items.map((item) => [
1134
+ String(item.name),
1135
+ String(item.qty),
1136
+ Number(item.lineNetEur).toFixed(2)
1137
+ ])
1138
+ );
1139
+ if (result.invoices.length > 0) {
1140
+ process.stdout.write("\nSz\xE1ml\xE1k:\n");
1141
+ printTable(
1142
+ ["ID", "T\xEDpus", "Sz\xE1m", "\xC1llapot", "Let\xF6lt\xE9s"],
1143
+ result.invoices.map((inv) => [
1144
+ String(inv.id),
1145
+ String(inv.typeLabel),
1146
+ inv.number ? String(inv.number) : "-",
1147
+ String(inv.statusLabel),
1148
+ inv.downloadUrl ? String(inv.downloadUrl) : "-"
1149
+ ])
1150
+ );
1151
+ }
1152
+ });
1153
+ } catch (err) {
1154
+ fail(err);
1155
+ }
1156
+ });
1157
+ var keysCmd = program.command("keys").description("term\xE9kkulcsok");
1158
+ keysCmd.command("list").description("kezbesitett term\xE9kkulcsok (osszes vagy egy rendelese)").option("--order <id>", "csak az adott rendeles kulcsai").action(async (opts, cmd) => {
1159
+ try {
1160
+ const client = clientFor(cmd);
1161
+ if (opts.order) {
1162
+ const result = await client.orderKeys(Number(opts.order));
1163
+ output(result, () => {
1164
+ if (result.keys.length === 0 && result.licenses.length === 0) {
1165
+ process.stdout.write(
1166
+ "Ehhez a rendel\xE9shez m\xE9g nincs k\xE9zbes\xEDtett kulcs (fizet\xE9sre vagy feldolgoz\xE1sra v\xE1r).\n"
1167
+ );
1168
+ return;
1169
+ }
1170
+ printTable(
1171
+ ["Term\xE9k", "Kulcs", "K\xE9zbes\xEDtve"],
1172
+ [
1173
+ ...result.keys.map((k) => [
1174
+ k.productName,
1175
+ k.keyValue,
1176
+ k.deliveredAt ? String(k.deliveredAt).slice(0, 10) : "-"
1177
+ ]),
1178
+ ...result.licenses.map((lic) => [
1179
+ String(lic.productName ?? "-"),
1180
+ lic.keyValue ? String(lic.keyValue) : "(kulcs a weben: /view-license-keys)",
1181
+ String(lic.statusLabel)
1182
+ ])
1183
+ ]
1184
+ );
1185
+ });
1186
+ } else {
1187
+ const result = await client.licenseKeys();
1188
+ output(result, () => {
1189
+ for (const group of result.products) {
1190
+ process.stdout.write(`
1191
+ ${group.productName}
1192
+ `);
1193
+ printTable(
1194
+ ["Kulcs", "Rendel\xE9s", "K\xE9zbes\xEDtve"],
1195
+ group.keys.map((k) => [
1196
+ String(k.keyValue),
1197
+ k.orderNumber ? `#${k.orderNumber}` : "-",
1198
+ k.deliveredAt ? String(k.deliveredAt).slice(0, 10) : "-"
1199
+ ])
1200
+ );
1201
+ }
1202
+ if (result.products.length === 0) {
1203
+ process.stdout.write("M\xE9g nincs k\xE9zbes\xEDtett term\xE9kkulcsod.\n");
1204
+ }
1205
+ });
1206
+ }
1207
+ } catch (err) {
1208
+ fail(err);
1209
+ }
1210
+ });
1211
+ var invoicesCmd = program.command("invoices").description("szamlak, dijbekerok");
1212
+ invoicesCmd.command("list").description("bizonylatok listaja").option("--order <id>", "csak az adott rendeles bizonylatai").option("--limit <n>", "darabszam", "25").action(async (opts, cmd) => {
1213
+ try {
1214
+ const result = await clientFor(cmd).invoicesList({
1215
+ orderId: opts.order ? Number(opts.order) : void 0,
1216
+ limit: Number(opts.limit)
1217
+ });
1218
+ output(result, () => {
1219
+ printTable(
1220
+ ["ID", "T\xEDpus", "Sz\xE1m", "Rendel\xE9s", "\xC1llapot", "Brutt\xF3 EUR", "Let\xF6lt\xE9s"],
1221
+ result.invoices.map((inv) => [
1222
+ String(inv.id),
1223
+ String(inv.typeLabel),
1224
+ inv.number ? String(inv.number) : "-",
1225
+ inv.orderNumber ? `#${inv.orderNumber}` : "-",
1226
+ String(inv.statusLabel),
1227
+ Number(inv.grossTotalEur).toFixed(2),
1228
+ inv.downloadUrl ? String(inv.downloadUrl) : "-"
1229
+ ])
1230
+ );
1231
+ });
1232
+ } catch (err) {
1233
+ fail(err);
1234
+ }
1235
+ });
1236
+ invoicesCmd.command("get <id>").description("bizonylat reszletei; --download: mentes fajlba").option("--download <fajl>", "a bizonylat letoltese a megadott fajlba").action(async (id, opts, cmd) => {
1237
+ try {
1238
+ const { invoice } = await clientFor(cmd).invoiceGet(Number(id));
1239
+ if (opts.download) {
1240
+ const url = invoice.downloadUrl;
1241
+ if (!url) {
1242
+ fail(new Error("Ehhez a bizonylathoz nincs let\xF6lthet\u0151 dokumentum."));
1243
+ }
1244
+ const response = await fetch(url);
1245
+ if (!response.ok) {
1246
+ fail(new Error(`A let\xF6lt\xE9s nem siker\xFClt (HTTP ${response.status}).`));
1247
+ }
1248
+ const buffer = Buffer.from(await response.arrayBuffer());
1249
+ writeFileSync2(opts.download, buffer);
1250
+ output({ invoice, savedTo: opts.download, bytes: buffer.length }, () => {
1251
+ process.stdout.write(`Mentve: ${opts.download} (${buffer.length} b\xE1jt)
1252
+ `);
1253
+ });
1254
+ return;
1255
+ }
1256
+ output({ invoice }, () => {
1257
+ printKV([
1258
+ ["Bizonylat", `${invoice.typeLabel} ${invoice.number ?? "(m\xE9g sz\xE1mozatlan)"}`],
1259
+ ["Rendel\xE9s", invoice.orderNumber ? `#${invoice.orderNumber}` : null],
1260
+ ["\xC1llapot", String(invoice.statusLabel)],
1261
+ ["Nett\xF3", eurFmt(Number(invoice.netTotalEur))],
1262
+ ["\xC1FA", eurFmt(Number(invoice.vatEur))],
1263
+ ["Brutt\xF3", eurFmt(Number(invoice.grossTotalEur))],
1264
+ ["Let\xF6lt\xE9s", invoice.downloadUrl]
1265
+ ]);
1266
+ });
1267
+ } catch (err) {
1268
+ fail(err);
1269
+ }
1270
+ });
1271
+ var profile = program.command("profile").description("fiok torzsadatok");
1272
+ profile.command("get").description("kontakt + szamlazasi + szallitasi adatok").action(async (_opts, cmd) => {
1273
+ try {
1274
+ const { profile: p } = await clientFor(cmd).profileGet();
1275
+ output({ profile: p }, () => {
1276
+ const billing = p.billing;
1277
+ const shipping = p.shipping;
1278
+ printKV([
1279
+ ["Email", String(p.email)],
1280
+ ["C\xE9g", p.companyName],
1281
+ ["Ad\xF3sz\xE1m", p.taxNumber],
1282
+ ["N\xE9v", p.firstName],
1283
+ ["Telefon", p.phone],
1284
+ ["Weboldal", p.website]
1285
+ ]);
1286
+ process.stdout.write("\nSz\xE1ml\xE1z\xE1si c\xEDm:\n");
1287
+ printKV(
1288
+ Object.entries(billing).map(([k, v]) => [` billing.${k}`, v])
1289
+ );
1290
+ process.stdout.write("\nSz\xE1ll\xEDt\xE1si c\xEDm:\n");
1291
+ printKV(
1292
+ Object.entries(shipping).map(([k, v]) => [` shipping.${k}`, v])
1293
+ );
1294
+ });
1295
+ } catch (err) {
1296
+ fail(err);
1297
+ }
1298
+ });
1299
+ var CONTACT_FIELD_MAP = {
1300
+ firstName: "firstName",
1301
+ phone: "phone",
1302
+ website: "website",
1303
+ company: "companyName",
1304
+ taxNumber: "taxNumber"
1305
+ };
1306
+ var ADDRESS_FIELD_NAMES = [
1307
+ "firstName",
1308
+ "lastName",
1309
+ "company",
1310
+ "address1",
1311
+ "address2",
1312
+ "city",
1313
+ "postcode",
1314
+ "state",
1315
+ "country",
1316
+ "email",
1317
+ "phone"
1318
+ ];
1319
+ profile.command("set <mezo=ertek...>").description(
1320
+ "adatmodositas, pl.: keypro profile set billing.city=Budapest contact.phone=+36201234567 (ures ertek torol)"
1321
+ ).action(async (pairs, _opts, cmd) => {
1322
+ try {
1323
+ const patch = {};
1324
+ for (const pair of pairs) {
1325
+ const eq = pair.indexOf("=");
1326
+ if (eq === -1) {
1327
+ usageError(`Hib\xE1s megad\xE1s: "${pair}" (mezo=ertek form\xE1t v\xE1runk).`);
1328
+ }
1329
+ const keyName = pair.slice(0, eq).trim();
1330
+ const value = pair.slice(eq + 1);
1331
+ const [section, field] = keyName.split(".", 2);
1332
+ if (!field) {
1333
+ usageError(
1334
+ `Hib\xE1s mez\u0151: "${keyName}". Form\xE1tum: contact.*, billing.*, shipping.* (pl. billing.city)`
1335
+ );
1336
+ }
1337
+ if (section === "contact") {
1338
+ const mapped = CONTACT_FIELD_MAP[field];
1339
+ if (!mapped) {
1340
+ usageError(
1341
+ `Ismeretlen contact mez\u0151: ${field}. Lehets\xE9ges: ${Object.keys(CONTACT_FIELD_MAP).join(", ")}`
1342
+ );
1343
+ }
1344
+ patch[mapped] = value;
1345
+ } else if (section === "billing" || section === "shipping") {
1346
+ if (!ADDRESS_FIELD_NAMES.includes(field)) {
1347
+ usageError(
1348
+ `Ismeretlen ${section} mez\u0151: ${field}. Lehets\xE9ges: ${ADDRESS_FIELD_NAMES.join(", ")}`
1349
+ );
1350
+ }
1351
+ if (section === "shipping" && field === "email") {
1352
+ usageError("A sz\xE1ll\xEDt\xE1si c\xEDmhez nincs email mez\u0151.");
1353
+ }
1354
+ patch[section + field[0].toUpperCase() + field.slice(1)] = value;
1355
+ } else {
1356
+ usageError(
1357
+ `Ismeretlen szekci\xF3: ${section}. Lehets\xE9ges: contact, billing, shipping`
1358
+ );
1359
+ }
1360
+ }
1361
+ const result = await clientFor(cmd).profileUpdate(patch);
1362
+ output(result, () => {
1363
+ process.stdout.write(
1364
+ `Friss\xEDtve: ${result.updated.join(", ")}
1365
+ `
1366
+ );
1367
+ });
1368
+ } catch (err) {
1369
+ fail(err);
1370
+ }
1371
+ });
1372
+ var wallet = program.command("wallet").description("KEP egyenleg");
1373
+ wallet.action(async (_opts, cmd) => {
1374
+ try {
1375
+ const result = await clientFor(cmd).wallet({ limit: 1 });
1376
+ output({ balanceEurNet: result.balanceEurNet }, () => {
1377
+ printKV([["KEP egyenleg (nett\xF3)", eurFmt(result.balanceEurNet)]]);
1378
+ });
1379
+ } catch (err) {
1380
+ fail(err);
1381
+ }
1382
+ });
1383
+ wallet.command("transactions").description("KEP tranzakcio-tortenet").option("--limit <n>", "darabszam", "25").action(async (opts, cmd) => {
1384
+ try {
1385
+ const result = await clientFor(cmd).wallet({ limit: Number(opts.limit) });
1386
+ output(result, () => {
1387
+ printKV([["KEP egyenleg (nett\xF3)", eurFmt(result.balanceEurNet)]]);
1388
+ process.stdout.write("\n");
1389
+ printTable(
1390
+ ["D\xE1tum", "T\xEDpus", "\xD6sszeg EUR", "Egyenleg ut\xE1na", "Rendel\xE9s", "Megjegyz\xE9s"],
1391
+ result.transactions.map((tx) => [
1392
+ String(tx.createdAt).slice(0, 10),
1393
+ String(tx.typeLabel),
1394
+ Number(tx.amountEur).toFixed(2),
1395
+ Number(tx.balanceAfterEur).toFixed(2),
1396
+ tx.orderNumber ? `#${tx.orderNumber}` : "-",
1397
+ tx.description ? String(tx.description) : "-"
1398
+ ])
1399
+ );
1400
+ });
1401
+ } catch (err) {
1402
+ fail(err);
1403
+ }
1404
+ });
1405
+ var cards = program.command("cards").description("mentett bankkartyak");
1406
+ cards.command("list").description("mentett kartyak (kartyat felvenni a weben lehet)").action(async (_opts, cmd) => {
1407
+ try {
1408
+ const result = await clientFor(cmd).cardsList();
1409
+ output(result, () => {
1410
+ if (!result.stripeEnabled) {
1411
+ process.stdout.write(
1412
+ "A k\xE1rty\xE1s fizet\xE9s ezen a k\xF6rnyezeten nincs bek\xF6tve.\n"
1413
+ );
1414
+ return;
1415
+ }
1416
+ if (result.cards.length === 0) {
1417
+ process.stdout.write(
1418
+ "Nincs mentett k\xE1rtya. K\xE1rty\xE1t a weben tudsz menteni: /payment-methods\n"
1419
+ );
1420
+ return;
1421
+ }
1422
+ printTable(
1423
+ ["ID", "K\xE1rtya", "Lej\xE1rat", "Alap\xE9rtelmezett"],
1424
+ result.cards.map((card) => [
1425
+ card.id,
1426
+ `${card.brand} **** ${card.last4}`,
1427
+ `${card.expMonth}/${card.expYear}`,
1428
+ card.isDefault ? "igen" : ""
1429
+ ])
1430
+ );
1431
+ });
1432
+ } catch (err) {
1433
+ fail(err);
1434
+ }
1435
+ });
1436
+ var parcelshops = program.command("parcelshops").description("GLS atveteli pontok");
1437
+ parcelshops.command("search <keresoszo>").description("csomagpont/automata kereses (varos, iranyitoszam vagy nev)").option("--type <tipus>", "parcel-shop | parcel-locker | all", "all").action(async (query, opts, cmd) => {
1438
+ try {
1439
+ const result = await clientFor(cmd).parcelshopsSearch(query, opts.type);
1440
+ output(result, () => {
1441
+ printTable(
1442
+ ["ID", "N\xE9v", "T\xEDpus", "C\xEDm"],
1443
+ result.parcelshops.map((p) => [
1444
+ String(p.id),
1445
+ String(p.name),
1446
+ p.type === "parcel-locker" ? "automata" : "csomagpont",
1447
+ `${p.postcode} ${p.city}, ${p.address}`
1448
+ ])
1449
+ );
1450
+ if (result.truncated) {
1451
+ process.stdout.write("\n(a lista csonkolva - pontos\xEDtsd a keres\xE9st)\n");
1452
+ }
1453
+ });
1454
+ } catch (err) {
1455
+ fail(err);
1456
+ }
1457
+ });
1458
+ program.command("agent-docs").description("beepitett utmutato AI-agenteknek (angol)").action(() => {
1459
+ process.stdout.write(AGENT_DOCS);
1460
+ });
1461
+ program.command("mcp").description("MCP stdio szerver mod (Claude Code: claude mcp add keypro -- keypro mcp)").action(async (_opts, cmd) => {
1462
+ const cfg = resolved(cmd);
1463
+ if (!cfg.apiKey) {
1464
+ process.stderr.write(
1465
+ "Nincs API kulcs. Futtasd elobb: keypro login (vagy KEYPRO_API_KEY env).\n"
1466
+ );
1467
+ process.exit(3);
1468
+ }
1469
+ await runMcpServer(createClient({ apiBase: cfg.apiBase, apiKey: cfg.apiKey }));
1470
+ });
1471
+ program.parseAsync(process.argv).catch((err) => {
1472
+ fail(err);
1473
+ });