@deerdesign/deertools 1.2.25 → 1.2.27

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.
@@ -1,288 +0,0 @@
1
- 'use strict';
2
- const fs = require('fs');
3
- const path = require('path');
4
- const { getProduct } = require('./registry');
5
- const { apiRequest, explain } = require('./http');
6
- const { c, formatTable } = require('./formatters');
7
- const { logAudit } = require('./audit');
8
-
9
- function out(s = '') { process.stdout.write(`${s}\n`); }
10
- function err(s = '') { process.stderr.write(`${s}\n`); }
11
- function fail(msg, code = 1) {
12
- const e = new Error(msg);
13
- e.exitCode = code;
14
- throw e;
15
- }
16
-
17
- // ==================== KATALOG ====================
18
- async function cmdCatalog(pos, flags) {
19
- const action = pos[0] || 'help';
20
- const product = getProduct('katalog');
21
-
22
- if (action === 'search') {
23
- const query = pos.slice(1).join(' ') || flags.q || flags.search || '';
24
- if (!query) fail('Użycie: deertools catalog search <fraza> [--package smart-life|modern-life|high-life] [--limit 20]', 2);
25
-
26
- const q = { search: query };
27
- if (flags.package || flags.program) q.program = flags.package || flags.program;
28
- if (flags.limit) q.limit = flags.limit;
29
- if (flags.category) q.category = flags.category;
30
-
31
- const t0 = Date.now();
32
- const res = await apiRequest(product, { method: 'GET', path: '/products', query: q });
33
- logAudit({ product: 'katalog', method: 'GET', path: '/products', status: res.status, duration_ms: Date.now() - t0 });
34
-
35
- if (!res.ok) fail(explain(res.status, res.json, product), res.status === 401 ? 3 : 1);
36
- const data = res.json || {};
37
- const items = data.data || [];
38
-
39
- if (flags.json) { out(JSON.stringify(data, null, 2)); return; }
40
- if (flags.quiet || flags.q) { for (const it of items) out(it.sku || it.id); return; }
41
-
42
- out(`\n${c.bold}Wyniki wyszukiwania w Katalogu (${items.length} z ${data.count || items.length}):${c.reset}`);
43
- const headers = ['SKU', 'Nazwa produktu', 'Kategoria', 'Cena brutto', 'Pakiety'];
44
- const rows = items.map(it => [
45
- it.sku || '',
46
- it.name || '',
47
- it.category_name || (it.category && it.category.name) || '',
48
- it.price != null ? `${it.price.toFixed(2)} zł` : '(ukryta)',
49
- (it.packages || []).join(', ') || ((it.tags || []).filter(t => ['SMART', 'MODERN', 'HIGH'].includes(t)).join(', ')),
50
- ]);
51
- out(formatTable(headers, rows));
52
- return;
53
- }
54
-
55
- if (action === 'get') {
56
- const idOrSku = pos[1];
57
- if (!idOrSku) fail('Użycie: deertools catalog get <id_lub_sku>', 2);
58
- const res = await apiRequest(product, { method: 'GET', path: `/products/${encodeURIComponent(idOrSku)}` });
59
- if (!res.ok) fail(explain(res.status, res.json, product), res.status === 401 ? 3 : 1);
60
- if (flags.json) { out(JSON.stringify(res.json, null, 2)); return; }
61
- const p = res.json || {};
62
- out(`\n${c.bold}${p.name || 'Produkt'}${c.reset} (SKU: ${p.sku})`);
63
- out(` Cena: ${p.price != null ? `${p.price} zł` : '(brak)'} · Kategoria: ${p.category?.name || p.category_id || '–'}`);
64
- out(` Producent: ${p.manufacturer?.name || p.manufacturer_id || '–'} · EAN: ${p.ean || '–'}`);
65
- out(` Zdjęcia (${(p.images || []).length}): ${(p.images || []).slice(0, 3).join(', ')}`);
66
- return;
67
- }
68
-
69
- if (action === 'categories') {
70
- const res = await apiRequest(product, { method: 'GET', path: '/categories' });
71
- if (!res.ok) fail(explain(res.status, res.json, product), res.status === 401 ? 3 : 1);
72
- if (flags.json) { out(JSON.stringify(res.json, null, 2)); return; }
73
- const cats = res.json?.data || res.json || [];
74
- out(`\n${c.bold}Kategorie wykończeniowe Deer Design (${cats.length}):${c.reset}`);
75
- for (const cat of cats.slice(0, 30)) {
76
- out(` • ${String(cat.name).padEnd(30)} ${c.dim}(slug: ${cat.slug || cat.id})${c.reset}`);
77
- }
78
- return;
79
- }
80
-
81
- if (action === 'manufacturers') {
82
- const res = await apiRequest(product, { method: 'GET', path: '/manufacturers' });
83
- if (!res.ok) fail(explain(res.status, res.json, product), res.status === 401 ? 3 : 1);
84
- if (flags.json) { out(JSON.stringify(res.json, null, 2)); return; }
85
- const mfs = res.json?.data || res.json || [];
86
- out(`\n${c.bold}Producenci w Katalogu Connect (${mfs.length}):${c.reset}`);
87
- for (const m of mfs.slice(0, 35)) {
88
- out(` • ${String(m.name).padEnd(28)} ${m.country ? `[${m.country}]` : ''}`);
89
- }
90
- return;
91
- }
92
-
93
- out(`\n${c.bold}Polecenia Katalogu Connect:${c.reset}
94
- deertools catalog search <fraza> [--package modern-life] [--json]
95
- deertools catalog get <id_lub_sku> [--json]
96
- deertools catalog categories [--json]
97
- deertools catalog manufacturers [--json]
98
- `);
99
- }
100
-
101
- // ==================== DOSTAWY ====================
102
- async function cmdDeliveries(pos, flags) {
103
- const action = pos[0] || 'list';
104
- const product = getProduct('dostawy');
105
-
106
- if (action === 'list') {
107
- const q = {};
108
- if (flags.status) q.status = flags.status;
109
- if (flags.limit) q.limit = flags.limit;
110
- if (flags.warehouse) q.warehouse_id = flags.warehouse;
111
-
112
- const t0 = Date.now();
113
- const res = await apiRequest(product, { method: 'GET', path: '/deliveries-api/deliveries', query: q });
114
- logAudit({ product: 'dostawy', method: 'GET', path: '/deliveries-api/deliveries', status: res.status, duration_ms: Date.now() - t0 });
115
-
116
- if (!res.ok) fail(explain(res.status, res.json, product), res.status === 401 ? 3 : 1);
117
- const data = res.json || [];
118
- const list = Array.isArray(data) ? data : (data.data || []);
119
-
120
- if (flags.json) { out(JSON.stringify(data, null, 2)); return; }
121
- if (flags.quiet || flags.q) { for (const d of list) out(d.id); return; }
122
-
123
- out(`\n${c.bold}Dostawy DeerGroup (${list.length}):${c.reset}`);
124
- const headers = ['ID', 'Data / Miesiąc', 'Status', 'Dostawca', 'Magazyn'];
125
- const rows = list.map(d => [
126
- d.id ? d.id.slice(0, 8) + '…' : '',
127
- d.month && d.year ? `${d.month}/${d.year}` : (d.delivery_date ? d.delivery_date.slice(0, 10) : '–'),
128
- d.status || 'nowa',
129
- d.supplier?.name || d.supplier_name || '–',
130
- d.warehouse?.name || d.warehouse_name || '–',
131
- ]);
132
- out(formatTable(headers, rows));
133
- return;
134
- }
135
-
136
- if (action === 'track') {
137
- const tracking = pos[1] || flags.number;
138
- if (!tracking) fail('Użycie: deertools deliveries track <numer_listu_lub_paczki>', 2);
139
- const res = await apiRequest(product, { method: 'GET', path: `/deliveries-api/packages/by-tracking/${encodeURIComponent(tracking)}` });
140
- if (!res.ok) fail(explain(res.status, res.json, product), res.status === 401 ? 3 : 1);
141
- if (flags.json) { out(JSON.stringify(res.json, null, 2)); return; }
142
- const pkg = res.json || {};
143
- out(`\n${c.bold}Status paczki: ${tracking}${c.reset}`);
144
- out(` Kurier: ${pkg.courier || '–'} · Status: ${pkg.status || 'w drodze'}`);
145
- out(` Dostawa ID: ${pkg.delivery_id || '–'}`);
146
- return;
147
- }
148
-
149
- if (action === 'suppliers') {
150
- const res = await apiRequest(product, { method: 'GET', path: '/suppliers-api/suppliers' });
151
- if (!res.ok) fail(explain(res.status, res.json, product), res.status === 401 ? 3 : 1);
152
- if (flags.json) { out(JSON.stringify(res.json, null, 2)); return; }
153
- const sups = res.json?.data || res.json || [];
154
- out(`\n${c.bold}Dostawcy w Delivery Hub (${sups.length}):${c.reset}`);
155
- for (const s of sups.slice(0, 30)) {
156
- out(` • ${String(s.name).padEnd(30)} ${s.email || ''}`);
157
- }
158
- return;
159
- }
160
-
161
- out(`\n${c.bold}Polecenia systemu Dostaw:${c.reset}
162
- deertools deliveries list [--status active] [--warehouse UUID] [--json]
163
- deertools deliveries track <numer_śledzenia> [--json]
164
- deertools deliveries suppliers [--json]
165
- `);
166
- }
167
-
168
- // ==================== SUPPLIER HUB ====================
169
- async function cmdSupplier(pos, flags) {
170
- const action = pos[0] || 'help';
171
- const product = getProduct('supplier');
172
-
173
- if (action === 'discounts') {
174
- const manufacturer = pos[1] || flags.manufacturer;
175
- const q = {};
176
- if (manufacturer) q.manufacturer = manufacturer;
177
- if (flags.postal_code) q.postal_code = flags.postal_code;
178
-
179
- const res = await apiRequest(product, { method: 'GET', path: '/discounts', query: q });
180
- if (!res.ok) fail(explain(res.status, res.json, product), res.status === 401 ? 3 : 1);
181
- if (flags.json) { out(JSON.stringify(res.json, null, 2)); return; }
182
- const list = res.json?.data || res.json || [];
183
- out(`\n${c.bold}Rabaty zakupowe dostawców (${list.length}):${c.reset}`);
184
- const headers = ['Producent', 'Dostawca', 'Rabat %', 'Kolekcja / Warunek'];
185
- const rows = list.map(d => [
186
- d.manufacturer_name || d.manufacturer || '–',
187
- d.supplier_name || d.supplier || '–',
188
- d.discount_pct != null ? `${d.discount_pct}%` : `${d.value || 0}%`,
189
- d.collection || d.notes || '–',
190
- ]);
191
- out(formatTable(headers, rows));
192
- return;
193
- }
194
-
195
- if (action === 'markups') {
196
- const location = pos[1] || flags.location;
197
- const q = {};
198
- if (location) q.location = location;
199
-
200
- const res = await apiRequest(product, { method: 'GET', path: '/markups', query: q });
201
- if (!res.ok) fail(explain(res.status, res.json, product), res.status === 401 ? 3 : 1);
202
- if (flags.json) { out(JSON.stringify(res.json, null, 2)); return; }
203
- const list = res.json?.data || res.json || [];
204
- out(`\n${c.bold}Narzuty sprzedaży (${list.length}):${c.reset}`);
205
- const headers = ['Lokalizacja', 'Pakiet', 'Narzut %', 'Kategoria'];
206
- const rows = list.map(m => [
207
- m.location || 'ogólna',
208
- m.package || 'wszystkie',
209
- `${m.markup_pct != null ? m.markup_pct : (m.value || 0)}%`,
210
- m.category || '–',
211
- ]);
212
- out(formatTable(headers, rows));
213
- return;
214
- }
215
-
216
- if (action === 'calculate') {
217
- const catalogPrice = Number(pos[1] || flags.price);
218
- if (!catalogPrice || isNaN(catalogPrice)) fail('Użycie: deertools supplier calculate <cena_katalogowa_netto> [--discount 35] [--markup 25]', 2);
219
-
220
- const discountPct = Number(pos[2] || flags.discount || 30);
221
- const markupPct = Number(pos[3] || flags.markup || 20);
222
-
223
- const buyNet = catalogPrice * (1 - discountPct / 100);
224
- const sellNet = buyNet * (1 + markupPct / 100);
225
- const marginNet = sellNet - buyNet;
226
- const marginPct = (marginNet / sellNet) * 100;
227
-
228
- const result = {
229
- catalogPriceNet: catalogPrice,
230
- discountPct,
231
- buyPriceNet: Math.round(buyNet * 100) / 100,
232
- markupPct,
233
- sellPriceNet: Math.round(sellNet * 100) / 100,
234
- marginNet: Math.round(marginNet * 100) / 100,
235
- marginPct: Math.round(marginPct * 10) / 10,
236
- };
237
-
238
- if (flags.json) { out(JSON.stringify(result, null, 2)); return; }
239
-
240
- out(`\n${c.bold}Symulacja łańcucha marżowego DeerTools:${c.reset}`);
241
- out(` Cena katalogowa producenta: ${c.bold}${catalogPrice.toFixed(2)} zł netto${c.reset}`);
242
- out(` Rabat zakupu dostawcy (${discountPct}%): -${(catalogPrice - buyNet).toFixed(2)} zł`);
243
- out(` --------------------------------------------------`);
244
- out(` Twój koszt zakupu netto: ${c.bold}${buyNet.toFixed(2)} zł netto${c.reset}`);
245
- out(` Narzut sprzedaży (${markupPct}%): +${(sellNet - buyNet).toFixed(2)} zł`);
246
- out(` --------------------------------------------------`);
247
- out(` Sugerowana cena sprzedaży: ${c.green}${c.bold}${sellNet.toFixed(2)} zł netto${c.reset}`);
248
- out(` Zysk z marży: ${c.green}${marginNet.toFixed(2)} zł (${marginPct.toFixed(1)}%)${c.reset}\n`);
249
- return;
250
- }
251
-
252
- out(`\n${c.bold}Polecenia Supplier Hub:${c.reset}
253
- deertools supplier discounts [producent] [--postal_code 00-001] [--json]
254
- deertools supplier markups [lokalizacja] [--json]
255
- deertools supplier calculate <cena_katalogowa> [--discount 30] [--markup 20] [--json]
256
- `);
257
- }
258
-
259
- // ==================== RENDERMIND ====================
260
- async function cmdRendermind(pos, flags) {
261
- const action = pos[0] || 'styles';
262
- const product = getProduct('rendermind');
263
-
264
- if (action === 'styles') {
265
- const res = await apiRequest(product, { method: 'GET', path: '/styles' }).catch(() => ({ ok: false }));
266
- const defaultStyles = [
267
- { name: 'Japandi', palette: 'Jasne drewno, beże, biel, len, ceramika' },
268
- { name: 'Modern Classic', palette: 'Sztukateria, mosiądz, marmur, welur' },
269
- { name: 'Loft / Industrial', palette: 'Cegła, czarna stal, surowy beton' },
270
- { name: 'Skandynawski', palette: 'Biel, jasny dąb, pastele, naturalne światło' },
271
- { name: 'Minimalistyczny', palette: 'Gładkie fronty, monolityczne płaszczyzny, ukryte detale' },
272
- ];
273
- const styles = (res.ok && res.json?.styles) ? res.json.styles : defaultStyles;
274
- if (flags.json) { out(JSON.stringify(styles, null, 2)); return; }
275
- out(`\n${c.bold}Dostępne style wykończeniowe RenderMind:${c.reset}`);
276
- for (const s of styles) {
277
- out(` • ${c.bold}${String(s.name).padEnd(20)}${c.reset} ${s.palette || s.description || ''}`);
278
- }
279
- return;
280
- }
281
-
282
- out(`\n${c.bold}Polecenia RenderMind:${c.reset}
283
- deertools rendermind styles [--json]
284
- deertools rendermind get <job_id> [--json]
285
- `);
286
- }
287
-
288
- module.exports = { cmdCatalog, cmdDeliveries, cmdSupplier, cmdRendermind };
@@ -1,110 +0,0 @@
1
- 'use strict';
2
- const credentials = require('./credentials');
3
- const { getProduct } = require('./registry');
4
-
5
- /**
6
- * Klient API / MCP dla Kosztorysu Deer Design.
7
- * Wszystkie kalkulacje (stawki, progi ryczałtowe do 40 m², dopłaty za strefy mokre,
8
- * dopłaty ze względu na złożoność projektu oraz reguły marżowe) są wykonywane
9
- * wyłącznie po stronie serwera Kosztorysu (Supabase Edge Function: estimate-mcp).
10
- * CLI nie zawiera lokalnie zakodowanych tabel cenowych ani algorytmów marży.
11
- */
12
-
13
- async function callEstimateMcp(toolName, args = {}, options = {}) {
14
- const product = getProduct('estimate');
15
- const found = credentials.getKey(product);
16
- const apiKey = options.apiKey || (found && found.key);
17
-
18
- if (!apiKey && !options.allowAnonymous) {
19
- const err = new Error(
20
- `Nie jesteś zalogowany do Kosztorysu. Kalkulacja wymaga autoryzacji z serwera DeerTools.\nUruchom: deertools login estimate`
21
- );
22
- err.exitCode = 3;
23
- throw err;
24
- }
25
-
26
- const endpoint = options.endpoint || product.mcpEndpoint || `${product.fallbackApiBase}/estimate-mcp`;
27
- const fetchFn = options.fetchImpl || globalThis.fetch;
28
-
29
- const payload = {
30
- jsonrpc: '2.0',
31
- id: Date.now(),
32
- method: 'tools/call',
33
- params: {
34
- name: toolName,
35
- arguments: args,
36
- },
37
- };
38
-
39
- const headers = {
40
- 'Content-Type': 'application/json',
41
- Accept: 'application/json',
42
- };
43
- if (apiKey) {
44
- headers['x-api-key'] = apiKey;
45
- }
46
-
47
- let res;
48
- try {
49
- res = await fetchFn(endpoint, {
50
- method: 'POST',
51
- headers,
52
- body: JSON.stringify(payload),
53
- });
54
- } catch (netErr) {
55
- const err = new Error(`Błąd połączenia z serwerem Kosztorysu (${endpoint}): ${netErr.message}`);
56
- err.exitCode = 4;
57
- throw err;
58
- }
59
-
60
- if (!res.ok) {
61
- let errBody = '';
62
- try {
63
- errBody = await res.text();
64
- } catch (_) {}
65
- if (res.status === 401) {
66
- const err = new Error(
67
- `Brak dostępu do Kosztorysu (HTTP 401). Klucz wygasł lub jest niepoprawny.\nUruchom ponowne logowanie: deertools login estimate`
68
- );
69
- err.exitCode = 3;
70
- throw err;
71
- }
72
- const err = new Error(`Serwer Kosztorysu zwrócił błąd HTTP ${res.status}: ${errBody || res.statusText}`);
73
- err.exitCode = 4;
74
- throw err;
75
- }
76
-
77
- const json = await res.json();
78
- if (json.error) {
79
- const err = new Error(`Błąd kalkulacji Kosztorysu: ${json.error.message || JSON.stringify(json.error)}`);
80
- err.exitCode = 4;
81
- throw err;
82
- }
83
-
84
- if (json.result?.structuredContent) {
85
- return json.result.structuredContent;
86
- }
87
- if (json.result?.content?.[0]?.text) {
88
- try {
89
- return JSON.parse(json.result.content[0].text);
90
- } catch (_) {
91
- return json.result.content[0].text;
92
- }
93
- }
94
-
95
- return json.result || {};
96
- }
97
-
98
- async function calculateEstimate(params, options = {}) {
99
- return callEstimateMcp('estimate_calculate_simple', params, options);
100
- }
101
-
102
- async function getEstimateMargins(params, options = {}) {
103
- return callEstimateMcp('estimate_get_margins', params, options);
104
- }
105
-
106
- module.exports = {
107
- callEstimateMcp,
108
- calculateEstimate,
109
- getEstimateMargins,
110
- };