@deerdesign/deertools 1.2.25 → 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 -175
- package/src/completion.js +4 -43
- package/src/mcp-stdio.js +0 -154
- package/src/domain-commands.js +0 -288
- package/src/estimate-client.js +0 -110
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
|
@@ -7,7 +7,6 @@ const { getProduct, resolveProducts, ALL_IDS } = require('./registry');
|
|
|
7
7
|
const { login, normalizeScope } = require('./login');
|
|
8
8
|
const { discover, fetchText, apiRequest, explain } = require('./http');
|
|
9
9
|
const { listOperations, parseFrontmatterName } = require('./openapi');
|
|
10
|
-
const { calculateEstimate, getEstimateMargins } = require('./estimate-client');
|
|
11
10
|
|
|
12
11
|
const out = (s = '') => process.stdout.write(`${s}\n`);
|
|
13
12
|
const err = (s = '') => process.stderr.write(`${s}\n`);
|
|
@@ -215,153 +214,6 @@ async function cmdApi(pos, flags) {
|
|
|
215
214
|
out(res.json ? JSON.stringify(res.json, null, flags.raw ? 0 : 2) : res.text);
|
|
216
215
|
}
|
|
217
216
|
|
|
218
|
-
async function cmdEstimate(pos, flags, options = {}) {
|
|
219
|
-
const sub = pos[0];
|
|
220
|
-
const write = options.out || out;
|
|
221
|
-
|
|
222
|
-
if (sub === 'calculate' || sub === 'calc') {
|
|
223
|
-
const area = Number(pos[1] || flags.area);
|
|
224
|
-
if (!area || isNaN(area)) fail('Użycie: deertools estimate calculate <metraż_m2> [smart_life|modern_life|high_life] [--wet=<liczba>] [--doors=<liczba>] [--basis=avg|max]', 2);
|
|
225
|
-
|
|
226
|
-
const requested = pos[2] || flags.package;
|
|
227
|
-
const wetInput = flags.wet !== undefined ? Number(flags.wet) : (flags.wetZones !== undefined ? Number(flags.wetZones) : undefined);
|
|
228
|
-
const doorsInput = flags.doors !== undefined ? Number(flags.doors) : undefined;
|
|
229
|
-
|
|
230
|
-
const data = await calculateEstimate({
|
|
231
|
-
area_m2: area,
|
|
232
|
-
package: requested || undefined,
|
|
233
|
-
wet_zones: wetInput,
|
|
234
|
-
doors: doorsInput,
|
|
235
|
-
}, options);
|
|
236
|
-
|
|
237
|
-
const estimates = (data && data.estimates) ? data.estimates : [];
|
|
238
|
-
if (!estimates.length) {
|
|
239
|
-
fail('Nie otrzymano wyników kalkulacji z serwera Kosztorysu.', 4);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const results = estimates.map(e => ({
|
|
243
|
-
package: e.package,
|
|
244
|
-
package_key: e.slug,
|
|
245
|
-
slug: e.slug,
|
|
246
|
-
area_m2: e.area_m2,
|
|
247
|
-
price_per_m2_gross: e.price_per_m2_gross,
|
|
248
|
-
is_flat_rate: !!e.is_lump_sum,
|
|
249
|
-
is_lump_sum: !!e.is_lump_sum,
|
|
250
|
-
lump_sum_max_area_m2: e.lump_sum_max_area_m2,
|
|
251
|
-
base_gross_pln: e.base_gross,
|
|
252
|
-
base_net_pln: Math.round((e.base_gross / 1.08) * 100) / 100,
|
|
253
|
-
standard_wet_zones: e.standard_wet_zones,
|
|
254
|
-
wet_zones: e.wet_zones,
|
|
255
|
-
extra_wet_zones: Math.max(0, (e.wet_zones || 0) - (e.standard_wet_zones || 0)),
|
|
256
|
-
wet_surcharge_gross_pln: e.wet_zone_surcharge_gross,
|
|
257
|
-
wet_surcharge_net_pln: Math.round(((e.wet_zone_surcharge_gross || 0) / 1.08) * 100) / 100,
|
|
258
|
-
standard_doors: e.standard_doors,
|
|
259
|
-
doors: (e.standard_doors || 0) + (e.complexity_levels || 0),
|
|
260
|
-
complexity_levels: e.complexity_levels,
|
|
261
|
-
complexity_surcharge_label: e.complexity_surcharge_label || 'Dopłata z tytułu złożoności projektu',
|
|
262
|
-
complexity_surcharge_gross_pln: e.complexity_surcharge_gross,
|
|
263
|
-
complexity_surcharge_net_pln: Math.round(((e.complexity_surcharge_gross || 0) / 1.08) * 100) / 100,
|
|
264
|
-
total_gross_pln: e.total_gross,
|
|
265
|
-
total_net_pln: e.total_net,
|
|
266
|
-
vat_pln: e.vat_pln,
|
|
267
|
-
currency: e.currency || 'PLN',
|
|
268
|
-
max_discount_pct: e.max_discount_pct,
|
|
269
|
-
discount_locked: e.discount_locked,
|
|
270
|
-
source: 'Kosztorys Backend API (estimate-mcp)',
|
|
271
|
-
}));
|
|
272
|
-
|
|
273
|
-
if (flags.json) {
|
|
274
|
-
write(JSON.stringify(results.length === 1 ? results[0] : results, null, 2));
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const isFlat = results.some(r => r.is_flat_rate);
|
|
279
|
-
write(`\nKalkulacja wykończenia dla metrażu ${area} m² (źródło: Kosztorys API${isFlat ? ', ryczałt do 40 m²' : ''}):`);
|
|
280
|
-
const fmt = (n) => new Intl.NumberFormat('pl-PL', { maximumFractionDigits: 0 }).format(Math.round(n));
|
|
281
|
-
for (const r of results) {
|
|
282
|
-
write(` ▶ ${r.package.padEnd(14)} ${fmt(r.total_gross_pln)} zł brutto (${fmt(r.total_net_pln)} zł netto + ${fmt(r.vat_pln)} zł VAT)`);
|
|
283
|
-
if (r.extra_wet_zones > 0 || r.complexity_levels > 0) {
|
|
284
|
-
write(` - Podstawa: ${fmt(r.base_gross_pln)} zł brutto`);
|
|
285
|
-
if (r.extra_wet_zones > 0) write(` - Dodatkowe strefy mokre (+${r.extra_wet_zones}): +${fmt(r.wet_surcharge_gross_pln)} zł brutto`);
|
|
286
|
-
if (r.complexity_levels > 0) write(` - ${r.complexity_surcharge_label} (+${r.complexity_levels} drzwi): +${fmt(r.complexity_surcharge_gross_pln)} zł brutto`);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (sub === 'margins') {
|
|
293
|
-
const area = Number(pos[1] || flags.area || 50);
|
|
294
|
-
|
|
295
|
-
const data = await getEstimateMargins({
|
|
296
|
-
area_m2: area,
|
|
297
|
-
}, options);
|
|
298
|
-
|
|
299
|
-
const estimates = (data && data.estimates) ? data.estimates : [];
|
|
300
|
-
if (!estimates.length) {
|
|
301
|
-
fail('Nie otrzymano reguł marżowych z serwera Kosztorysu.', 4);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const rows = estimates.map(e => ({
|
|
305
|
-
package: e.package,
|
|
306
|
-
slug: e.slug,
|
|
307
|
-
area_m2: e.area_m2,
|
|
308
|
-
base_gross_pln: e.base_gross,
|
|
309
|
-
base_net_pln: e.total_net,
|
|
310
|
-
is_flat_rate: !!e.is_lump_sum,
|
|
311
|
-
std_wet_zones: e.standard_wet_zones,
|
|
312
|
-
std_doors: e.standard_doors,
|
|
313
|
-
wet_surcharge_gross: e.wet_zone_surcharge_gross,
|
|
314
|
-
complexity_surcharge_label: e.complexity_surcharge_label || 'Dopłata z tytułu złożoności projektu',
|
|
315
|
-
complexity_surcharge_gross: e.complexity_surcharge_gross,
|
|
316
|
-
margin_rule_simplified: e.margin_rule_simplified,
|
|
317
|
-
margin_rule_quick: e.margin_rule_quick,
|
|
318
|
-
formula: e.formula,
|
|
319
|
-
note: e.note,
|
|
320
|
-
source: 'Kosztorys Backend API (estimate-mcp)',
|
|
321
|
-
}));
|
|
322
|
-
|
|
323
|
-
if (flags.json) { write(JSON.stringify(rows, null, 2)); return; }
|
|
324
|
-
write(`\nZasady wyliczania marży Deer Design (Nowy standard 2026 z Kosztorysu dla ${area} m²):`);
|
|
325
|
-
write(` • Kosztorys Uproszczony: ${rows[0]?.margin_rule_simplified}`);
|
|
326
|
-
write(` • Kosztorys Szybki: ${rows[0]?.margin_rule_quick}`);
|
|
327
|
-
write(` • Formuła marży: ${rows[0]?.formula}`);
|
|
328
|
-
write(` • Uwaga: ${rows[0]?.note}\n`);
|
|
329
|
-
for (const r of rows) {
|
|
330
|
-
write(` ▶ ${r.package.padEnd(14)} Podstawa: ${r.base_gross_pln} zł brutto (${r.base_net_pln} zł netto) | Std: ${r.std_wet_zones} strefa mokra, ${r.std_doors} drzwi`);
|
|
331
|
-
write(` Reguła marży: ${r.margin_rule_simplified}`);
|
|
332
|
-
}
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const file = flags.file || (sub === 'plan' ? pos[1] : pos[0]);
|
|
337
|
-
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);
|
|
338
|
-
if (!fs.existsSync(file)) fail(`Plik nie istnieje: ${file}`, 2);
|
|
339
|
-
const bytes = fs.readFileSync(file);
|
|
340
|
-
if (bytes.length > 10 * 1024 * 1024) fail('Plik jest większy niż 10 MB (limit analizy rzutu).', 2);
|
|
341
|
-
const ext = path.extname(file).toLowerCase().slice(1);
|
|
342
|
-
const mime = { pdf: 'application/pdf', png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', webp: 'image/webp' }[ext];
|
|
343
|
-
if (!mime) fail('Obsługiwane formaty: PDF, PNG, JPG, WEBP.', 2);
|
|
344
|
-
const kitchen = flags.kitchen ? Number(String(flags.kitchen).replace(',', '.')) : 6;
|
|
345
|
-
if (!(kitchen >= 2 && kitchen <= 30)) fail('--kitchen musi być w zakresie 2–30 m².', 2);
|
|
346
|
-
const product = getProduct('kosztorys');
|
|
347
|
-
out(`Analizuję ${path.basename(file)} (aneks ${kitchen} m²) — to trwa do ok. minuty…`);
|
|
348
|
-
const res = await apiRequest(product, {
|
|
349
|
-
method: 'POST',
|
|
350
|
-
path: '/estimate-from-floor-plan',
|
|
351
|
-
body: { file_url: `data:${mime};base64,${bytes.toString('base64')}`, file_name: path.basename(file), file_type: mime, kitchen_open_area_m2: kitchen },
|
|
352
|
-
base: product.fallbackApiBase,
|
|
353
|
-
});
|
|
354
|
-
if (!res.ok) fail(explain(res.status, res.json, product), res.status === 401 ? 3 : 1);
|
|
355
|
-
if (flags.json) { out(JSON.stringify(res.json, null, 2)); return; }
|
|
356
|
-
const d = res.json || {};
|
|
357
|
-
const s = d.summary || {};
|
|
358
|
-
out(`\nŁączny metraż: ${s.total_area_m2} m² · pokoje: ${s.rooms} · aneks wydzielony: ${s.kitchen_open_area_m2} m²`);
|
|
359
|
-
for (const r of d.rooms || []) out(` • ${String(r.name).padEnd(26)} ${r.area_m2} m²`);
|
|
360
|
-
out('\nPakiety (brutto / netto):');
|
|
361
|
-
const fmt = (n) => new Intl.NumberFormat('pl-PL', { maximumFractionDigits: 0 }).format(Math.round(n));
|
|
362
|
-
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²)' : ''}`);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
217
|
const { detectAgents, installSkillContent } = require('./agent-detector');
|
|
366
218
|
const skillCommands = require('./skill-commands');
|
|
367
219
|
|
|
@@ -668,7 +520,6 @@ const { cmdDoctor } = require('./doctor');
|
|
|
668
520
|
const { cmdAudit } = require('./audit');
|
|
669
521
|
const { cmdProfile } = require('./profiles');
|
|
670
522
|
const { cmdCompletion } = require('./completion');
|
|
671
|
-
const { cmdCatalog, cmdDeliveries, cmdSupplier, cmdRendermind } = require('./domain-commands');
|
|
672
523
|
const { cmdBaseLinker } = require('./baselinker-commands');
|
|
673
524
|
|
|
674
525
|
const HELP = `
|
|
@@ -682,24 +533,7 @@ Dostęp i autoryzacja:
|
|
|
682
533
|
deertools whoami [produkt|all] [--json] Status logowania, zakres, ważność.
|
|
683
534
|
deertools profile [list|use <nazwa>|current] Przełączanie profili poświadczeń.
|
|
684
535
|
|
|
685
|
-
|
|
686
|
-
deertools catalog <search|get|categories|manufacturers> Zarządzanie Katalogiem Connect.
|
|
687
|
-
deertools deliveries <list|track|suppliers> Zarządzanie Delivery Hub.
|
|
688
|
-
deertools estimate <calculate|margins|plan> Kalkulacje i analiza rzutów PDF.
|
|
689
|
-
deertools supplier <discounts|markups|calculate> Rabaty, narzuty i łańcuch marżowy.
|
|
690
|
-
deertools rendermind <styles|get> Style i wizualizacje wnętrz.
|
|
691
|
-
|
|
692
|
-
BaseLinker MCP Proxy & Hub:
|
|
693
|
-
deertools baselinker auth [token] Zapisuje klucz API BaseLinkera w credentials.json.
|
|
694
|
-
deertools baselinker test Testuje autoryzację i listuje katalogi.
|
|
695
|
-
deertools baselinker serve [--port 3456] Lokalny serwer Web UI z dashboardem i MCP SSE.
|
|
696
|
-
deertools baselinker mcp [--all] Mostek MCP stdio (Cursor / Claude Desktop).
|
|
697
|
-
deertools baselinker sync-schema Pobiera i aktualizuje bazę 204 metod z api.baselinker.com.
|
|
698
|
-
deertools baselinker search <fraza> Przeszukuje bazę metod i schematów parametrów.
|
|
699
|
-
deertools baselinker call <metoda> [json] Wywołuje dowolną metodę BaseLinkera z CLI.
|
|
700
|
-
deertools baselinker catalog <search|get> Operacje katalogowe z poziomu terminala.
|
|
701
|
-
|
|
702
|
-
Integracje MCP i Agenci AI (DeerSkills Hub):
|
|
536
|
+
Zarządzanie skillami i zespołami (DeerSkills Hub):
|
|
703
537
|
deertools agent [list|select <agenty>|reset] Wykrywanie i konfiguracja platform agentów AI na PC.
|
|
704
538
|
deertools skill list [--tag <tag>] Przegląda skille w Hubie wraz ze statusem zainstalowania.
|
|
705
539
|
deertools skill sync [--agent <id>|-i|--force] Instaluje brakujące i aktualizuje obecne skille zespołu.
|
|
@@ -712,6 +546,13 @@ Integracje MCP i Agenci AI (DeerSkills Hub):
|
|
|
712
546
|
deertools skill review [list|approve|reject|submit] Zarządzanie procesem Peer Review i weryfikacją skilli zespołu.
|
|
713
547
|
deertools skill update [slug] Aktualizuje zainstalowane skille do najnowszej wersji.
|
|
714
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:
|
|
715
556
|
deertools mcp list Lista dostępnych serwerów MCP w chmurze.
|
|
716
557
|
deertools mcp config [--json] Generuje konfigurację Remote MCP dla Cursor i Antigravity.
|
|
717
558
|
deertools mcp install Instaluje serwery MCP w Cursor i Antigravity.
|
|
@@ -720,12 +561,15 @@ Integracje MCP i Agenci AI (DeerSkills Hub):
|
|
|
720
561
|
deertools endpoints <produkt> [--json] Lista endpointów REST z openapi.json.
|
|
721
562
|
deertools api <produkt> <METODA> </ścieżka> Wywołuje bezpośrednio API produktu.
|
|
722
563
|
|
|
723
|
-
|
|
724
|
-
deertools
|
|
725
|
-
deertools
|
|
726
|
-
deertools
|
|
727
|
-
deertools
|
|
728
|
-
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.
|
|
729
573
|
|
|
730
574
|
Narzędzia systemowe:
|
|
731
575
|
deertools update Sprawdza i podpowiada aktualizację CLI.
|
|
@@ -737,8 +581,8 @@ Zmienne środowiskowe: DEERTOOLS_<PRODUKT>_KEY, BASELINKER_TOKEN, DEERTOOLS_HOME
|
|
|
737
581
|
`;
|
|
738
582
|
|
|
739
583
|
module.exports = {
|
|
740
|
-
cmdLogin, cmdLogout, cmdWhoami, cmdDocs, cmdEndpoints, cmdApi,
|
|
741
|
-
cmdDoctor, cmdAudit, cmdProfile, cmdCompletion,
|
|
584
|
+
cmdLogin, cmdLogout, cmdWhoami, cmdDocs, cmdEndpoints, cmdApi, cmdSkill, cmdMcp,
|
|
585
|
+
cmdDoctor, cmdAudit, cmdProfile, cmdCompletion,
|
|
742
586
|
cmdBaseLinker, cmdAgent, cmdTeam,
|
|
743
587
|
HELP, parseQuery,
|
|
744
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
|
@@ -5,7 +5,6 @@ const { ALL_PRODUCTS, getProduct } = require('./registry');
|
|
|
5
5
|
const { apiRequest } = require('./http');
|
|
6
6
|
const { checkSkillUpdates, updateSkills } = require('./skill-commands');
|
|
7
7
|
const skillsHub = require('./skills-hub');
|
|
8
|
-
const { calculateEstimate, getEstimateMargins } = require('./estimate-client');
|
|
9
8
|
|
|
10
9
|
const PROTOCOL_VERSION = '2024-11-05';
|
|
11
10
|
const SERVER_INFO = { name: 'deertools-stdio-bridge', version: '1.0.5' };
|
|
@@ -52,68 +51,6 @@ async function cmdMcpServe(pos, flags) {
|
|
|
52
51
|
|
|
53
52
|
if (method === 'tools/list') {
|
|
54
53
|
const tools = [
|
|
55
|
-
{
|
|
56
|
-
name: 'estimate_calculate',
|
|
57
|
-
description: 'Szybkie wyliczenie kosztu wykończenia mieszkania z podanego metrażu w pakietach Smart Life, Modern Life, High Life (kalkulacja po stronie serwera Kosztorys).',
|
|
58
|
-
inputSchema: {
|
|
59
|
-
type: 'object',
|
|
60
|
-
properties: {
|
|
61
|
-
area_m2: { type: 'number', description: 'Metraż mieszkania w m²' },
|
|
62
|
-
package: { type: 'string', enum: ['smart_life', 'modern_life', 'high_life'], description: 'Pakiet wykończeniowy (opcjonalny — bez podania zwraca 3 pakiety)' },
|
|
63
|
-
wet_zones: { type: 'number', description: 'Liczba stref mokrych (np. łazienki, WC)' },
|
|
64
|
-
doors: { type: 'number', description: 'Liczba kompletów drzwi wewnętrznych' },
|
|
65
|
-
},
|
|
66
|
-
required: ['area_m2'],
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
name: 'estimate_get_margins',
|
|
71
|
-
description: 'Pobiera reguły wyliczania marży i strukturę kosztorysową Deer Design dla zadanego metrażu z serwera Kosztorys.',
|
|
72
|
-
inputSchema: {
|
|
73
|
-
type: 'object',
|
|
74
|
-
properties: {
|
|
75
|
-
area_m2: { type: 'number', description: 'Metraż mieszkania w m²' },
|
|
76
|
-
},
|
|
77
|
-
required: ['area_m2'],
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
name: 'catalog_search_products',
|
|
82
|
-
description: 'Wyszukiwanie produktów w Katalogu Connect Deer Design po frazie, kategorii i pakiecie.',
|
|
83
|
-
inputSchema: {
|
|
84
|
-
type: 'object',
|
|
85
|
-
properties: {
|
|
86
|
-
query: { type: 'string', description: 'Szukana fraza (nazwa, SKU, producent)' },
|
|
87
|
-
package: { type: 'string', enum: ['smart-life', 'modern-life', 'high-life'] },
|
|
88
|
-
limit: { type: 'number', default: 10 },
|
|
89
|
-
},
|
|
90
|
-
required: ['query'],
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
name: 'deliveries_list',
|
|
95
|
-
description: 'Pobieranie listy dostaw z systemu logistycznego Delivery Hub.',
|
|
96
|
-
inputSchema: {
|
|
97
|
-
type: 'object',
|
|
98
|
-
properties: {
|
|
99
|
-
status: { type: 'string', description: 'Filtruj po statusie dostawy' },
|
|
100
|
-
limit: { type: 'number', default: 20 },
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
name: 'supplier_calculate_price',
|
|
106
|
-
description: 'Kalkulator łańcucha marżowego: cena katalogowa -> rabat zakupu -> narzut sprzedaży -> marża zysku.',
|
|
107
|
-
inputSchema: {
|
|
108
|
-
type: 'object',
|
|
109
|
-
properties: {
|
|
110
|
-
catalog_price_net: { type: 'number', description: 'Cena katalogowa netto' },
|
|
111
|
-
discount_pct: { type: 'number', description: 'Rabat zakupu w % (np. 35)' },
|
|
112
|
-
markup_pct: { type: 'number', description: 'Narzut sprzedaży w % (np. 20)' },
|
|
113
|
-
},
|
|
114
|
-
required: ['catalog_price_net'],
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
54
|
{
|
|
118
55
|
name: 'deer_check_skill_updates',
|
|
119
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.',
|
|
@@ -179,97 +116,6 @@ async function cmdMcpServe(pos, flags) {
|
|
|
179
116
|
const args = params?.arguments || {};
|
|
180
117
|
|
|
181
118
|
try {
|
|
182
|
-
if (toolName === 'estimate_calculate') {
|
|
183
|
-
const area = Number(args.area_m2);
|
|
184
|
-
const pkg = args.package;
|
|
185
|
-
const wetZones = args.wet_zones !== undefined ? Number(args.wet_zones) : undefined;
|
|
186
|
-
const doors = args.doors !== undefined ? Number(args.doors) : undefined;
|
|
187
|
-
|
|
188
|
-
const data = await calculateEstimate({
|
|
189
|
-
area_m2: area,
|
|
190
|
-
package: pkg,
|
|
191
|
-
wet_zones: wetZones,
|
|
192
|
-
doors: doors,
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
send({
|
|
196
|
-
jsonrpc: '2.0',
|
|
197
|
-
id,
|
|
198
|
-
result: {
|
|
199
|
-
content: [{
|
|
200
|
-
type: 'text',
|
|
201
|
-
text: JSON.stringify(data, null, 2),
|
|
202
|
-
}],
|
|
203
|
-
structuredContent: data,
|
|
204
|
-
},
|
|
205
|
-
});
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (toolName === 'estimate_get_margins') {
|
|
210
|
-
const area = Number(args.area_m2 || 50);
|
|
211
|
-
const data = await getEstimateMargins({ area_m2: area });
|
|
212
|
-
send({
|
|
213
|
-
jsonrpc: '2.0',
|
|
214
|
-
id,
|
|
215
|
-
result: {
|
|
216
|
-
content: [{
|
|
217
|
-
type: 'text',
|
|
218
|
-
text: JSON.stringify(data, null, 2),
|
|
219
|
-
}],
|
|
220
|
-
structuredContent: data,
|
|
221
|
-
},
|
|
222
|
-
});
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (toolName === 'catalog_search_products') {
|
|
227
|
-
const p = getProduct('katalog');
|
|
228
|
-
const res = await apiRequest(p, { method: 'GET', path: '/products', query: { search: args.query, program: args.package, limit: args.limit || 10 } });
|
|
229
|
-
send({
|
|
230
|
-
jsonrpc: '2.0',
|
|
231
|
-
id,
|
|
232
|
-
result: { content: [{ type: 'text', text: JSON.stringify(res.json?.data || res.json || [], null, 2) }] },
|
|
233
|
-
});
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (toolName === 'deliveries_list') {
|
|
238
|
-
const p = getProduct('dostawy');
|
|
239
|
-
const res = await apiRequest(p, { method: 'GET', path: '/deliveries-api/deliveries', query: { status: args.status, limit: args.limit || 20 } });
|
|
240
|
-
send({
|
|
241
|
-
jsonrpc: '2.0',
|
|
242
|
-
id,
|
|
243
|
-
result: { content: [{ type: 'text', text: JSON.stringify(res.json || [], null, 2) }] },
|
|
244
|
-
});
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (toolName === 'supplier_calculate_price') {
|
|
249
|
-
const cat = Number(args.catalog_price_net);
|
|
250
|
-
const disc = Number(args.discount_pct || 30);
|
|
251
|
-
const mark = Number(args.markup_pct || 20);
|
|
252
|
-
const buy = cat * (1 - disc / 100);
|
|
253
|
-
const sell = buy * (1 + mark / 100);
|
|
254
|
-
send({
|
|
255
|
-
jsonrpc: '2.0',
|
|
256
|
-
id,
|
|
257
|
-
result: {
|
|
258
|
-
content: [{
|
|
259
|
-
type: 'text',
|
|
260
|
-
text: JSON.stringify({
|
|
261
|
-
catalog_price_net: cat,
|
|
262
|
-
buy_price_net: Math.round(buy * 100) / 100,
|
|
263
|
-
sell_price_net: Math.round(sell * 100) / 100,
|
|
264
|
-
margin_net: Math.round((sell - buy) * 100) / 100,
|
|
265
|
-
margin_pct: Math.round(((sell - buy) / sell) * 1000) / 10,
|
|
266
|
-
}, null, 2),
|
|
267
|
-
}],
|
|
268
|
-
},
|
|
269
|
-
});
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
119
|
if (toolName === 'deer_check_skill_updates') {
|
|
274
120
|
const updates = await checkSkillUpdates();
|
|
275
121
|
const outdated = updates.filter((u) => u.hasUpdate);
|
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 };
|
package/src/estimate-client.js
DELETED
|
@@ -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
|
-
};
|