@deerdesign/deertools 1.2.23 → 1.2.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -33
- package/package.json +1 -1
- package/src/cli.js +2 -3
- package/src/commands.js +19 -129
- package/src/completion.js +4 -43
- package/src/mcp-stdio.js +28 -134
- package/src/domain-commands.js +0 -288
package/README.md
CHANGED
|
@@ -127,9 +127,9 @@ deertools api rendermind POST /api/v1/visualizations --data '{"prompt":"Modern S
|
|
|
127
127
|
deertools api rendermind POST /api/v1/visualizations --data-file request.json
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
### 3.
|
|
130
|
+
### 3. Developer & Environment Utilities
|
|
131
131
|
|
|
132
|
-
DeerTools CLI
|
|
132
|
+
DeerTools CLI focuses strictly on authentication, Remote MCP orchestration, and DeerSkills Hub management. Domain calculations and business logic are hosted directly on the product platforms (Kosztorys, Katalog, Dostawy, Supplier Hub, RenderMind) and accessed via their own REST APIs and Remote MCP endpoints.
|
|
133
133
|
|
|
134
134
|
```bash
|
|
135
135
|
# 1. Environment & API Doctor:
|
|
@@ -139,31 +139,7 @@ deertools doctor --json
|
|
|
139
139
|
# 2. Shell Autocompletion (Zsh, Bash, Fish):
|
|
140
140
|
deertools completion install # automatically configures ~/.zshrc or ~/.bashrc
|
|
141
141
|
|
|
142
|
-
# 3.
|
|
143
|
-
deertools catalog search "bateria nero" --package modern-life --limit 10
|
|
144
|
-
deertools catalog get <sku_or_uuid>
|
|
145
|
-
deertools catalog categories
|
|
146
|
-
deertools catalog manufacturers
|
|
147
|
-
|
|
148
|
-
# 4. Deliveries (Logistics & Shipments):
|
|
149
|
-
deertools deliveries list --status active
|
|
150
|
-
deertools deliveries track <tracking_number>
|
|
151
|
-
deertools deliveries suppliers
|
|
152
|
-
|
|
153
|
-
# 5. Estimate (Cost Calculations & Floor Plan AI):
|
|
154
|
-
deertools estimate calculate 64.5 modern_life
|
|
155
|
-
deertools estimate margins 64.5
|
|
156
|
-
deertools estimate --file floorplan.pdf --kitchen 6
|
|
157
|
-
|
|
158
|
-
# 6. Supplier Hub (Discounts & Price Chains):
|
|
159
|
-
deertools supplier discounts Flügger --postal_code 00-001
|
|
160
|
-
deertools supplier markups warszawa
|
|
161
|
-
deertools supplier calculate 1000 --discount 35 --markup 25
|
|
162
|
-
|
|
163
|
-
# 7. RenderMind (Interior Styles & Presets):
|
|
164
|
-
deertools rendermind styles
|
|
165
|
-
|
|
166
|
-
# 8. BaseLinker Hub & MCP Proxy:
|
|
142
|
+
# 3. BaseLinker Hub & MCP Proxy:
|
|
167
143
|
deertools baselinker auth <X-BLToken> # Zapisz token w credentials.json
|
|
168
144
|
deertools baselinker test # Sprawdź połączenie i listę katalogów
|
|
169
145
|
deertools baselinker serve --port 3456 # Uruchom lokalny dashboard Web UI + MCP SSE
|
|
@@ -173,18 +149,18 @@ deertools baselinker search "relations" # Wyszukaj metody i schematy par
|
|
|
173
149
|
deertools baselinker call getOrders # Bezpośrednie wywołanie API z terminala
|
|
174
150
|
deertools baselinker catalog search 105 "dąb" # Wyszukaj produkty w katalogu BaseLinkera
|
|
175
151
|
|
|
176
|
-
#
|
|
152
|
+
# 4. Local Stdio MCP Bridge (for Claude Desktop, Ollama, local LLM tools):
|
|
177
153
|
deertools mcp serve
|
|
178
154
|
|
|
179
|
-
#
|
|
155
|
+
# 5. Audit Log:
|
|
180
156
|
deertools audit --limit 25
|
|
181
157
|
deertools audit --clear
|
|
182
158
|
|
|
183
|
-
#
|
|
159
|
+
# 6. Multi-Profile Management:
|
|
184
160
|
deertools profile list
|
|
185
161
|
deertools profile use staging
|
|
186
162
|
|
|
187
|
-
#
|
|
163
|
+
# 7. AI Agent Skills (DeerSkills Hub):
|
|
188
164
|
deertools skill list # Wyświetl listę skilli ze statusem zainstalowania w projekcie
|
|
189
165
|
deertools skill sync # Jedna komenda: instaluje brakujące i aktualizuje obecne skille zespołu
|
|
190
166
|
deertools skill install --all # To samo co skill sync (pełna synchronizacja zespołu)
|
|
@@ -200,7 +176,7 @@ deertools skill update deer-kosztorys # Zaktualizuj zainstalowany skill d
|
|
|
200
176
|
deertools skill update # Zaktualizuj wszystkie zainstalowane skille
|
|
201
177
|
deertools skill promote my-skill # Zgłoś wniosek do admina o nominację do globalnego katalogu
|
|
202
178
|
|
|
203
|
-
#
|
|
179
|
+
# 8. Team Management (Konfiguracja Zespołów):
|
|
204
180
|
deertools team list # Lista zespołów, liczba członków i skilli
|
|
205
181
|
deertools team members architekci # Członkowie i role w zespole architekci
|
|
206
182
|
deertools team add architekci jan@deer.pl # Dodaj członka do zespołu (rola member)
|
|
@@ -208,7 +184,7 @@ deertools team add architekci ewa@deer.pl --role lead # Dodaj lidera zespołu
|
|
|
208
184
|
deertools team remove architekci jan@deer.pl # Usuń członka z zespołu
|
|
209
185
|
deertools team create "Projektanci Wnętrz" --slug projektanci # Utwórz nowy zespół
|
|
210
186
|
|
|
211
|
-
#
|
|
187
|
+
# 9. AI Agent MCP Registry & Configuration:
|
|
212
188
|
deertools mcp list # Lista dostępnych serwerów MCP w ekosystemie
|
|
213
189
|
deertools mcp config # Wygeneruj konfigurację JSON dla Cursor, Claude, Antigravity
|
|
214
190
|
deertools mcp install # Automatycznie dopisz konfigurację do narzędzi AI
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -31,13 +31,12 @@ async function main(argv) {
|
|
|
31
31
|
|
|
32
32
|
const table = {
|
|
33
33
|
login: c.cmdLogin, logout: c.cmdLogout, whoami: c.cmdWhoami, docs: c.cmdDocs,
|
|
34
|
-
endpoints: c.cmdEndpoints, api: c.cmdApi,
|
|
34
|
+
endpoints: c.cmdEndpoints, api: c.cmdApi, skill: c.cmdSkill,
|
|
35
35
|
sync: (pos, flags) => c.cmdSkill(['sync', ...pos], flags),
|
|
36
36
|
agent: c.cmdAgent, agents: c.cmdAgent,
|
|
37
37
|
team: c.cmdTeam, teams: c.cmdTeam,
|
|
38
38
|
mcp: c.cmdMcp, doctor: c.cmdDoctor, audit: c.cmdAudit, profile: c.cmdProfile,
|
|
39
|
-
completion: c.cmdCompletion,
|
|
40
|
-
supplier: c.cmdSupplier, rendermind: c.cmdRendermind, baselinker: c.cmdBaseLinker,
|
|
39
|
+
completion: c.cmdCompletion, baselinker: c.cmdBaseLinker,
|
|
41
40
|
update: require('./updater').cmdUpdate,
|
|
42
41
|
};
|
|
43
42
|
|
package/src/commands.js
CHANGED
|
@@ -214,108 +214,6 @@ async function cmdApi(pos, flags) {
|
|
|
214
214
|
out(res.json ? JSON.stringify(res.json, null, flags.raw ? 0 : 2) : res.text);
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
async function cmdEstimate(pos, flags) {
|
|
218
|
-
const sub = pos[0];
|
|
219
|
-
|
|
220
|
-
if (sub === 'calculate' || sub === 'calc') {
|
|
221
|
-
const area = Number(pos[1] || flags.area);
|
|
222
|
-
if (!area || isNaN(area)) fail('Użycie: deertools estimate calculate <metraż_m2> [smart_life|modern_life|high_life]', 2);
|
|
223
|
-
const pkgs = {
|
|
224
|
-
smart_life: { name: 'Smart Life', rate: 1290, flat: 58050, marginPct: 33.1 },
|
|
225
|
-
modern_life: { name: 'Modern Life', rate: 1640, flat: 73800, marginPct: 31.2 },
|
|
226
|
-
high_life: { name: 'High Life', rate: 1990, flat: 89550, marginPct: 22.7 },
|
|
227
|
-
};
|
|
228
|
-
const isFlat = area < 45;
|
|
229
|
-
const requested = pos[2] || flags.package;
|
|
230
|
-
const selected = requested ? [pkgs[requested] || pkgs.modern_life] : Object.values(pkgs);
|
|
231
|
-
|
|
232
|
-
const results = selected.map(p => {
|
|
233
|
-
const gross = isFlat ? p.flat : Math.round(area * p.rate);
|
|
234
|
-
const net = Math.round(gross / 1.08);
|
|
235
|
-
const vat = gross - net;
|
|
236
|
-
const profitNet = Math.round(net * (p.marginPct / 100));
|
|
237
|
-
return {
|
|
238
|
-
package: p.name,
|
|
239
|
-
area_m2: area,
|
|
240
|
-
price_per_m2_gross: p.rate,
|
|
241
|
-
total_gross_pln: gross,
|
|
242
|
-
total_net_pln: net,
|
|
243
|
-
vat_pln: vat,
|
|
244
|
-
is_flat_rate: isFlat,
|
|
245
|
-
ref_margin_pct: p.marginPct,
|
|
246
|
-
est_profit_net_pln: profitNet,
|
|
247
|
-
};
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
if (flags.json) { out(JSON.stringify(results.length === 1 ? results[0] : results, null, 2)); return; }
|
|
251
|
-
|
|
252
|
-
out(`\nKalkulacja wykończenia dla metrażu ${area} m²${isFlat ? ' (stawka ryczałtowa < 45 m²)' : ''}:`);
|
|
253
|
-
const fmt = (n) => new Intl.NumberFormat('pl-PL', { maximumFractionDigits: 0 }).format(Math.round(n));
|
|
254
|
-
for (const r of results) {
|
|
255
|
-
out(` ▶ ${r.package.padEnd(14)} ${fmt(r.total_gross_pln)} zł brutto (${fmt(r.total_net_pln)} zł netto + ${fmt(r.vat_pln)} zł VAT)`);
|
|
256
|
-
}
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (sub === 'margins') {
|
|
261
|
-
const area = Number(pos[1] || flags.area || 50);
|
|
262
|
-
const pkgs = [
|
|
263
|
-
{ name: 'Smart Life', rate: 1290, margin: 33.1 },
|
|
264
|
-
{ name: 'Modern Life', rate: 1640, margin: 31.2 },
|
|
265
|
-
{ name: 'High Life', rate: 1990, margin: 22.7 },
|
|
266
|
-
];
|
|
267
|
-
const isFlat = area < 45;
|
|
268
|
-
const rows = pkgs.map(p => {
|
|
269
|
-
const gross = isFlat ? (p.rate * 45) : (area * p.rate);
|
|
270
|
-
const net = gross / 1.08;
|
|
271
|
-
const profit = net * (p.margin / 100);
|
|
272
|
-
const cost = net - profit;
|
|
273
|
-
return {
|
|
274
|
-
package: p.name,
|
|
275
|
-
gross: Math.round(gross),
|
|
276
|
-
net: Math.round(net),
|
|
277
|
-
cost: Math.round(cost),
|
|
278
|
-
profit: Math.round(profit),
|
|
279
|
-
margin: p.margin,
|
|
280
|
-
};
|
|
281
|
-
});
|
|
282
|
-
if (flags.json) { out(JSON.stringify(rows, null, 2)); return; }
|
|
283
|
-
out(`\nAnaliza marż referencyjnych Deer Design dla ${area} m²:`);
|
|
284
|
-
for (const r of rows) {
|
|
285
|
-
out(` • ${r.package.padEnd(14)} Przychód netto: ${r.net} zł · Koszt: ${r.cost} zł · Zysk: ${r.profit} zł (${r.margin}%)`);
|
|
286
|
-
}
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const file = flags.file || (sub === 'plan' ? pos[1] : pos[0]);
|
|
291
|
-
if (!file) fail('Użycie:\n deertools estimate calculate <metraż> [pakiet]\n deertools estimate margins <metraż>\n deertools estimate --file <rzut.pdf> [--kitchen 6]', 2);
|
|
292
|
-
if (!fs.existsSync(file)) fail(`Plik nie istnieje: ${file}`, 2);
|
|
293
|
-
const bytes = fs.readFileSync(file);
|
|
294
|
-
if (bytes.length > 10 * 1024 * 1024) fail('Plik jest większy niż 10 MB (limit analizy rzutu).', 2);
|
|
295
|
-
const ext = path.extname(file).toLowerCase().slice(1);
|
|
296
|
-
const mime = { pdf: 'application/pdf', png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', webp: 'image/webp' }[ext];
|
|
297
|
-
if (!mime) fail('Obsługiwane formaty: PDF, PNG, JPG, WEBP.', 2);
|
|
298
|
-
const kitchen = flags.kitchen ? Number(String(flags.kitchen).replace(',', '.')) : 6;
|
|
299
|
-
if (!(kitchen >= 2 && kitchen <= 30)) fail('--kitchen musi być w zakresie 2–30 m².', 2);
|
|
300
|
-
const product = getProduct('kosztorys');
|
|
301
|
-
out(`Analizuję ${path.basename(file)} (aneks ${kitchen} m²) — to trwa do ok. minuty…`);
|
|
302
|
-
const res = await apiRequest(product, {
|
|
303
|
-
method: 'POST',
|
|
304
|
-
path: '/estimate-from-floor-plan',
|
|
305
|
-
body: { file_url: `data:${mime};base64,${bytes.toString('base64')}`, file_name: path.basename(file), file_type: mime, kitchen_open_area_m2: kitchen },
|
|
306
|
-
base: product.fallbackApiBase,
|
|
307
|
-
});
|
|
308
|
-
if (!res.ok) fail(explain(res.status, res.json, product), res.status === 401 ? 3 : 1);
|
|
309
|
-
if (flags.json) { out(JSON.stringify(res.json, null, 2)); return; }
|
|
310
|
-
const d = res.json || {};
|
|
311
|
-
const s = d.summary || {};
|
|
312
|
-
out(`\nŁączny metraż: ${s.total_area_m2} m² · pokoje: ${s.rooms} · aneks wydzielony: ${s.kitchen_open_area_m2} m²`);
|
|
313
|
-
for (const r of d.rooms || []) out(` • ${String(r.name).padEnd(26)} ${r.area_m2} m²`);
|
|
314
|
-
out('\nPakiety (brutto / netto):');
|
|
315
|
-
const fmt = (n) => new Intl.NumberFormat('pl-PL', { maximumFractionDigits: 0 }).format(Math.round(n));
|
|
316
|
-
for (const p of d.packages || []) out(` ▶ ${String(p.name).padEnd(12)} ${fmt(p.total_gross)} zł / ${fmt(p.total_net)} zł${p.is_lump_sum ? ' (ryczałt < 45 m²)' : ''}`);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
217
|
const { detectAgents, installSkillContent } = require('./agent-detector');
|
|
320
218
|
const skillCommands = require('./skill-commands');
|
|
321
219
|
|
|
@@ -622,7 +520,6 @@ const { cmdDoctor } = require('./doctor');
|
|
|
622
520
|
const { cmdAudit } = require('./audit');
|
|
623
521
|
const { cmdProfile } = require('./profiles');
|
|
624
522
|
const { cmdCompletion } = require('./completion');
|
|
625
|
-
const { cmdCatalog, cmdDeliveries, cmdSupplier, cmdRendermind } = require('./domain-commands');
|
|
626
523
|
const { cmdBaseLinker } = require('./baselinker-commands');
|
|
627
524
|
|
|
628
525
|
const HELP = `
|
|
@@ -636,24 +533,7 @@ Dostęp i autoryzacja:
|
|
|
636
533
|
deertools whoami [produkt|all] [--json] Status logowania, zakres, ważność.
|
|
637
534
|
deertools profile [list|use <nazwa>|current] Przełączanie profili poświadczeń.
|
|
638
535
|
|
|
639
|
-
|
|
640
|
-
deertools catalog <search|get|categories|manufacturers> Zarządzanie Katalogiem Connect.
|
|
641
|
-
deertools deliveries <list|track|suppliers> Zarządzanie Delivery Hub.
|
|
642
|
-
deertools estimate <calculate|margins|plan> Kalkulacje i analiza rzutów PDF.
|
|
643
|
-
deertools supplier <discounts|markups|calculate> Rabaty, narzuty i łańcuch marżowy.
|
|
644
|
-
deertools rendermind <styles|get> Style i wizualizacje wnętrz.
|
|
645
|
-
|
|
646
|
-
BaseLinker MCP Proxy & Hub:
|
|
647
|
-
deertools baselinker auth [token] Zapisuje klucz API BaseLinkera w credentials.json.
|
|
648
|
-
deertools baselinker test Testuje autoryzację i listuje katalogi.
|
|
649
|
-
deertools baselinker serve [--port 3456] Lokalny serwer Web UI z dashboardem i MCP SSE.
|
|
650
|
-
deertools baselinker mcp [--all] Mostek MCP stdio (Cursor / Claude Desktop).
|
|
651
|
-
deertools baselinker sync-schema Pobiera i aktualizuje bazę 204 metod z api.baselinker.com.
|
|
652
|
-
deertools baselinker search <fraza> Przeszukuje bazę metod i schematów parametrów.
|
|
653
|
-
deertools baselinker call <metoda> [json] Wywołuje dowolną metodę BaseLinkera z CLI.
|
|
654
|
-
deertools baselinker catalog <search|get> Operacje katalogowe z poziomu terminala.
|
|
655
|
-
|
|
656
|
-
Integracje MCP i Agenci AI (DeerSkills Hub):
|
|
536
|
+
Zarządzanie skillami i zespołami (DeerSkills Hub):
|
|
657
537
|
deertools agent [list|select <agenty>|reset] Wykrywanie i konfiguracja platform agentów AI na PC.
|
|
658
538
|
deertools skill list [--tag <tag>] Przegląda skille w Hubie wraz ze statusem zainstalowania.
|
|
659
539
|
deertools skill sync [--agent <id>|-i|--force] Instaluje brakujące i aktualizuje obecne skille zespołu.
|
|
@@ -666,6 +546,13 @@ Integracje MCP i Agenci AI (DeerSkills Hub):
|
|
|
666
546
|
deertools skill review [list|approve|reject|submit] Zarządzanie procesem Peer Review i weryfikacją skilli zespołu.
|
|
667
547
|
deertools skill update [slug] Aktualizuje zainstalowane skille do najnowszej wersji.
|
|
668
548
|
deertools skill promote <slug> Zgłasza nominację skilla do oficjalnego katalogu globalnego.
|
|
549
|
+
deertools team [list] Przegląda listę zespołów, liczbę członków i przypisanych skilli.
|
|
550
|
+
deertools team members [zespół] Lista członków zespołów i ich ról (Lider / Członek).
|
|
551
|
+
deertools team add <zespół> <email> [--role lead] Dodaje lub aktualizuje rolę pracownika w zespole.
|
|
552
|
+
deertools team remove <zespół> <email> Usuwa pracownika ze wskazanego zespołu.
|
|
553
|
+
deertools team create <nazwa> [--slug <s>] [--desc] Tworzy nowy zespół w organizacji Deer Design.
|
|
554
|
+
|
|
555
|
+
Integracja MCP i inspekcja API produktów:
|
|
669
556
|
deertools mcp list Lista dostępnych serwerów MCP w chmurze.
|
|
670
557
|
deertools mcp config [--json] Generuje konfigurację Remote MCP dla Cursor i Antigravity.
|
|
671
558
|
deertools mcp install Instaluje serwery MCP w Cursor i Antigravity.
|
|
@@ -674,12 +561,15 @@ Integracje MCP i Agenci AI (DeerSkills Hub):
|
|
|
674
561
|
deertools endpoints <produkt> [--json] Lista endpointów REST z openapi.json.
|
|
675
562
|
deertools api <produkt> <METODA> </ścieżka> Wywołuje bezpośrednio API produktu.
|
|
676
563
|
|
|
677
|
-
|
|
678
|
-
deertools
|
|
679
|
-
deertools
|
|
680
|
-
deertools
|
|
681
|
-
deertools
|
|
682
|
-
deertools
|
|
564
|
+
BaseLinker Hub & Proxy:
|
|
565
|
+
deertools baselinker auth [token] Zapisuje klucz API BaseLinkera w credentials.json.
|
|
566
|
+
deertools baselinker test Testuje autoryzację i listuje katalogi.
|
|
567
|
+
deertools baselinker serve [--port 3456] Lokalny serwer Web UI z dashboardem i MCP SSE.
|
|
568
|
+
deertools baselinker mcp [--all] Mostek MCP stdio (Cursor / Claude Desktop).
|
|
569
|
+
deertools baselinker sync-schema Pobiera i aktualizuje bazę 204 metod z api.baselinker.com.
|
|
570
|
+
deertools baselinker search <fraza> Przeszukuje bazę metod i schematów parametrów.
|
|
571
|
+
deertools baselinker call <metoda> [json] Wywołuje dowolną metodę BaseLinkera z CLI.
|
|
572
|
+
deertools baselinker catalog <search|get> Operacje katalogowe z poziomu terminala.
|
|
683
573
|
|
|
684
574
|
Narzędzia systemowe:
|
|
685
575
|
deertools update Sprawdza i podpowiada aktualizację CLI.
|
|
@@ -691,8 +581,8 @@ Zmienne środowiskowe: DEERTOOLS_<PRODUKT>_KEY, BASELINKER_TOKEN, DEERTOOLS_HOME
|
|
|
691
581
|
`;
|
|
692
582
|
|
|
693
583
|
module.exports = {
|
|
694
|
-
cmdLogin, cmdLogout, cmdWhoami, cmdDocs, cmdEndpoints, cmdApi,
|
|
695
|
-
cmdDoctor, cmdAudit, cmdProfile, cmdCompletion,
|
|
584
|
+
cmdLogin, cmdLogout, cmdWhoami, cmdDocs, cmdEndpoints, cmdApi, cmdSkill, cmdMcp,
|
|
585
|
+
cmdDoctor, cmdAudit, cmdProfile, cmdCompletion,
|
|
696
586
|
cmdBaseLinker, cmdAgent, cmdTeam,
|
|
697
587
|
HELP, parseQuery,
|
|
698
588
|
};
|
package/src/completion.js
CHANGED
|
@@ -19,12 +19,9 @@ _deertools() {
|
|
|
19
19
|
'docs:Pobranie dokumentacji produktu dla agentów AI'
|
|
20
20
|
'endpoints:Lista endpointów REST z openapi.json'
|
|
21
21
|
'api:Wywołanie API z automatycznym nagłówkiem autoryzacji'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'supplier:Zarządzanie rabatami, marżami i promocjami'
|
|
26
|
-
'rendermind:Wizualizacje AI wnętrz i reguły pCon'
|
|
27
|
-
'skill:Instalacja skilla agenta w wykrytych IDE'
|
|
22
|
+
'skill:Zarządzanie skillami, pakietami i synchronizacją'
|
|
23
|
+
'team:Zarządzanie zespołami i uprawnieniami w DeerSkills Hub'
|
|
24
|
+
'agent:Wykrywanie i konfiguracja platform agentów AI'
|
|
28
25
|
'mcp:Konfiguracja i instalacja serwerów Remote MCP'
|
|
29
26
|
'doctor:Diagnostyka środowiska, połączeń i uprawnień'
|
|
30
27
|
'audit:Historia wywołań API i log operacji'
|
|
@@ -53,26 +50,6 @@ _deertools() {
|
|
|
53
50
|
login|logout|docs|endpoints)
|
|
54
51
|
_values 'produkty' ${ALL_IDS.map(p => `'${p}'`).join(' ')} 'all'
|
|
55
52
|
;;
|
|
56
|
-
catalog)
|
|
57
|
-
local -a sub=( 'search:Wyszukaj produkty' 'get:Szczegóły produktu po SKU/ID' 'sync:Uruchom synchronizację Baselinker' 'categories:Lista kategorii' 'manufacturers:Lista producentów' )
|
|
58
|
-
_describe -t sub 'katalog podkomendy' sub
|
|
59
|
-
;;
|
|
60
|
-
deliveries)
|
|
61
|
-
local -a sub=( 'list:Lista dostaw' 'get:Pobierz szczegóły dostawy' 'track:Śledź paczkę po numerze' 'wz:Wygeneruj dokument WZ' 'suppliers:Lista dostawców' 'warehouses:Lista magazynów' )
|
|
62
|
-
_describe -t sub 'dostawy podkomendy' sub
|
|
63
|
-
;;
|
|
64
|
-
supplier)
|
|
65
|
-
local -a sub=( 'discounts:Lista rabatów zakupowych' 'markups:Lista narzutów sprzedaży' 'calculate:Kalkulator marży i ceny efektywnej' 'promotions:Lista promocji' )
|
|
66
|
-
_describe -t sub 'supplier podkomendy' sub
|
|
67
|
-
;;
|
|
68
|
-
estimate)
|
|
69
|
-
local -a sub=( 'calculate:Szybka kalkulacja z metrażu' 'plan:Analiza rzutu PDF/obrazu' 'margins:Kalkulator marży referencyjnych' 'list:Lista zapisanych kosztorysów' )
|
|
70
|
-
_describe -t sub 'kosztorys podkomendy' sub
|
|
71
|
-
;;
|
|
72
|
-
rendermind)
|
|
73
|
-
local -a sub=( 'styles:Lista dostępnych stylów wnętrz' 'get:Status zadania renderowania' 'render:Wygeneruj wizualizację ze szkicu' )
|
|
74
|
-
_describe -t sub 'rendermind podkomendy' sub
|
|
75
|
-
;;
|
|
76
53
|
baselinker)
|
|
77
54
|
local -a sub=( 'auth:Zapisz token X-BLToken' 'test:Testuj połączenie' 'serve:Uruchom lokalny serwer Web UI' 'mcp:Uruchom mostek MCP stdio' 'sync-schema:Synchronizuj bazę metod' 'search:Wyszukaj metodę' 'call:Wywołaj metodę' 'catalog:Operacje katalogowe' )
|
|
78
55
|
_describe -t sub 'baselinker podkomendy' sub
|
|
@@ -101,7 +78,7 @@ _deertools_completions() {
|
|
|
101
78
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
102
79
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
103
80
|
|
|
104
|
-
opts="login logout whoami docs endpoints api
|
|
81
|
+
opts="login logout whoami docs endpoints api baselinker skill team agent mcp doctor audit profile completion --help --version --json"
|
|
105
82
|
|
|
106
83
|
case "$prev" in
|
|
107
84
|
login|logout|docs|endpoints)
|
|
@@ -116,22 +93,6 @@ _deertools_completions() {
|
|
|
116
93
|
COMPREPLY=( $(compgen -W "config install serve" -- "$cur") )
|
|
117
94
|
return 0
|
|
118
95
|
;;
|
|
119
|
-
catalog)
|
|
120
|
-
COMPREPLY=( $(compgen -W "search get sync categories manufacturers" -- "$cur") )
|
|
121
|
-
return 0
|
|
122
|
-
;;
|
|
123
|
-
deliveries)
|
|
124
|
-
COMPREPLY=( $(compgen -W "list get track wz suppliers warehouses" -- "$cur") )
|
|
125
|
-
return 0
|
|
126
|
-
;;
|
|
127
|
-
estimate)
|
|
128
|
-
COMPREPLY=( $(compgen -W "calculate plan margins list" -- "$cur") )
|
|
129
|
-
return 0
|
|
130
|
-
;;
|
|
131
|
-
supplier)
|
|
132
|
-
COMPREPLY=( $(compgen -W "discounts markups calculate promotions" -- "$cur") )
|
|
133
|
-
return 0
|
|
134
|
-
;;
|
|
135
96
|
esac
|
|
136
97
|
|
|
137
98
|
if [[ "\$cur" == -* ]]; then
|
package/src/mcp-stdio.js
CHANGED
|
@@ -16,13 +16,19 @@ async function cmdMcpServe(pos, flags) {
|
|
|
16
16
|
process.stdout.write(JSON.stringify(msg) + '\n');
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
let pendingCount = 0;
|
|
20
|
+
let isClosed = false;
|
|
21
|
+
let closeResolve = null;
|
|
22
|
+
|
|
19
23
|
rl.on('line', async (line) => {
|
|
20
24
|
if (!line.trim()) return;
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
pendingCount++;
|
|
26
|
+
try {
|
|
27
|
+
let req;
|
|
28
|
+
try { req = JSON.parse(line); } catch (e) {
|
|
29
|
+
send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
26
32
|
|
|
27
33
|
const { id, method, params } = req;
|
|
28
34
|
|
|
@@ -45,55 +51,6 @@ async function cmdMcpServe(pos, flags) {
|
|
|
45
51
|
|
|
46
52
|
if (method === 'tools/list') {
|
|
47
53
|
const tools = [
|
|
48
|
-
{
|
|
49
|
-
name: 'estimate_calculate',
|
|
50
|
-
description: 'Szybkie wyliczenie kosztu wykończenia mieszkania z podanego metrażu w pakietach Smart Life, Modern Life, High Life.',
|
|
51
|
-
inputSchema: {
|
|
52
|
-
type: 'object',
|
|
53
|
-
properties: {
|
|
54
|
-
area_m2: { type: 'number', description: 'Metraż mieszkania w m²' },
|
|
55
|
-
package: { type: 'string', enum: ['smart_life', 'modern_life', 'high_life'], description: 'Pakiet wykończeniowy' },
|
|
56
|
-
},
|
|
57
|
-
required: ['area_m2'],
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: 'catalog_search_products',
|
|
62
|
-
description: 'Wyszukiwanie produktów w Katalogu Connect Deer Design po frazie, kategorii i pakiecie.',
|
|
63
|
-
inputSchema: {
|
|
64
|
-
type: 'object',
|
|
65
|
-
properties: {
|
|
66
|
-
query: { type: 'string', description: 'Szukana fraza (nazwa, SKU, producent)' },
|
|
67
|
-
package: { type: 'string', enum: ['smart-life', 'modern-life', 'high-life'] },
|
|
68
|
-
limit: { type: 'number', default: 10 },
|
|
69
|
-
},
|
|
70
|
-
required: ['query'],
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
name: 'deliveries_list',
|
|
75
|
-
description: 'Pobieranie listy dostaw z systemu logistycznego Delivery Hub.',
|
|
76
|
-
inputSchema: {
|
|
77
|
-
type: 'object',
|
|
78
|
-
properties: {
|
|
79
|
-
status: { type: 'string', description: 'Filtruj po statusie dostawy' },
|
|
80
|
-
limit: { type: 'number', default: 20 },
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
name: 'supplier_calculate_price',
|
|
86
|
-
description: 'Kalkulator łańcucha marżowego: cena katalogowa -> rabat zakupu -> narzut sprzedaży -> marża zysku.',
|
|
87
|
-
inputSchema: {
|
|
88
|
-
type: 'object',
|
|
89
|
-
properties: {
|
|
90
|
-
catalog_price_net: { type: 'number', description: 'Cena katalogowa netto' },
|
|
91
|
-
discount_pct: { type: 'number', description: 'Rabat zakupu w % (np. 35)' },
|
|
92
|
-
markup_pct: { type: 'number', description: 'Narzut sprzedaży w % (np. 20)' },
|
|
93
|
-
},
|
|
94
|
-
required: ['catalog_price_net'],
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
54
|
{
|
|
98
55
|
name: 'deer_check_skill_updates',
|
|
99
56
|
description: 'Sprawdza czy zainstalowane lokalnie skille AI DeerTools posiadają nowsze wersje w DeerSkills Hub. Zwraca listę skilli z dostępnymi aktualizacjami i ich changelogami.',
|
|
@@ -159,86 +116,6 @@ async function cmdMcpServe(pos, flags) {
|
|
|
159
116
|
const args = params?.arguments || {};
|
|
160
117
|
|
|
161
118
|
try {
|
|
162
|
-
if (toolName === 'estimate_calculate') {
|
|
163
|
-
const area = Number(args.area_m2);
|
|
164
|
-
const pkg = args.package || 'modern_life';
|
|
165
|
-
const pkgs = {
|
|
166
|
-
smart_life: { name: 'Smart Life', rate: 1290, flat: 58050 },
|
|
167
|
-
modern_life: { name: 'Modern Life', rate: 1640, flat: 73800 },
|
|
168
|
-
high_life: { name: 'High Life', rate: 1990, flat: 89550 },
|
|
169
|
-
};
|
|
170
|
-
const p = pkgs[pkg] || pkgs.modern_life;
|
|
171
|
-
const isFlat = area < 45;
|
|
172
|
-
const gross = isFlat ? p.flat : Math.round(area * p.rate);
|
|
173
|
-
const net = Math.round(gross / 1.08);
|
|
174
|
-
|
|
175
|
-
send({
|
|
176
|
-
jsonrpc: '2.0',
|
|
177
|
-
id,
|
|
178
|
-
result: {
|
|
179
|
-
content: [{
|
|
180
|
-
type: 'text',
|
|
181
|
-
text: JSON.stringify({
|
|
182
|
-
package: p.name,
|
|
183
|
-
area_m2: area,
|
|
184
|
-
total_gross_pln: gross,
|
|
185
|
-
total_net_pln: net,
|
|
186
|
-
is_flat_rate: isFlat,
|
|
187
|
-
currency: 'PLN',
|
|
188
|
-
}, null, 2),
|
|
189
|
-
}],
|
|
190
|
-
},
|
|
191
|
-
});
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (toolName === 'catalog_search_products') {
|
|
196
|
-
const p = getProduct('katalog');
|
|
197
|
-
const res = await apiRequest(p, { method: 'GET', path: '/products', query: { search: args.query, program: args.package, limit: args.limit || 10 } });
|
|
198
|
-
send({
|
|
199
|
-
jsonrpc: '2.0',
|
|
200
|
-
id,
|
|
201
|
-
result: { content: [{ type: 'text', text: JSON.stringify(res.json?.data || res.json || [], null, 2) }] },
|
|
202
|
-
});
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (toolName === 'deliveries_list') {
|
|
207
|
-
const p = getProduct('dostawy');
|
|
208
|
-
const res = await apiRequest(p, { method: 'GET', path: '/deliveries-api/deliveries', query: { status: args.status, limit: args.limit || 20 } });
|
|
209
|
-
send({
|
|
210
|
-
jsonrpc: '2.0',
|
|
211
|
-
id,
|
|
212
|
-
result: { content: [{ type: 'text', text: JSON.stringify(res.json || [], null, 2) }] },
|
|
213
|
-
});
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (toolName === 'supplier_calculate_price') {
|
|
218
|
-
const cat = Number(args.catalog_price_net);
|
|
219
|
-
const disc = Number(args.discount_pct || 30);
|
|
220
|
-
const mark = Number(args.markup_pct || 20);
|
|
221
|
-
const buy = cat * (1 - disc / 100);
|
|
222
|
-
const sell = buy * (1 + mark / 100);
|
|
223
|
-
send({
|
|
224
|
-
jsonrpc: '2.0',
|
|
225
|
-
id,
|
|
226
|
-
result: {
|
|
227
|
-
content: [{
|
|
228
|
-
type: 'text',
|
|
229
|
-
text: JSON.stringify({
|
|
230
|
-
catalog_price_net: cat,
|
|
231
|
-
buy_price_net: Math.round(buy * 100) / 100,
|
|
232
|
-
sell_price_net: Math.round(sell * 100) / 100,
|
|
233
|
-
margin_net: Math.round((sell - buy) * 100) / 100,
|
|
234
|
-
margin_pct: Math.round(((sell - buy) / sell) * 1000) / 10,
|
|
235
|
-
}, null, 2),
|
|
236
|
-
}],
|
|
237
|
-
},
|
|
238
|
-
});
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
119
|
if (toolName === 'deer_check_skill_updates') {
|
|
243
120
|
const updates = await checkSkillUpdates();
|
|
244
121
|
const outdated = updates.filter((u) => u.hasUpdate);
|
|
@@ -383,6 +260,23 @@ async function cmdMcpServe(pos, flags) {
|
|
|
383
260
|
}
|
|
384
261
|
|
|
385
262
|
send({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Method not found' } });
|
|
263
|
+
} finally {
|
|
264
|
+
pendingCount--;
|
|
265
|
+
if (isClosed && pendingCount === 0 && closeResolve) {
|
|
266
|
+
closeResolve();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
rl.on('close', () => {
|
|
272
|
+
isClosed = true;
|
|
273
|
+
if (pendingCount === 0 && closeResolve) {
|
|
274
|
+
closeResolve();
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return new Promise((resolve) => {
|
|
279
|
+
closeResolve = resolve;
|
|
386
280
|
});
|
|
387
281
|
}
|
|
388
282
|
|
package/src/domain-commands.js
DELETED
|
@@ -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 };
|