@deerdesign/deertools 1.0.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/commands.js CHANGED
@@ -23,6 +23,24 @@ function needProduct(flags, pos) {
23
23
  }
24
24
 
25
25
  async function cmdLogin(pos, flags) {
26
+ if (pos[0] === 'baselinker' || pos[0] === 'bl') {
27
+ out(`\n=== BaseLinker ===`);
28
+ try {
29
+ const { loginBaseLinkerBrowser } = require('./baselinker');
30
+ const entry = await loginBaseLinkerBrowser({
31
+ port: flags.port ? Number(flags.port) : 0,
32
+ openBrowser: flags['no-open'] ? null : undefined,
33
+ log: out
34
+ });
35
+ out(`✓ Zalogowano do BaseLinker. Token zapisany w credentials.json.`);
36
+ out(` Narzędzia katalogu: deertools baselinker catalog search <id> | MCP: deertools baselinker mcp`);
37
+ return;
38
+ } catch (e) {
39
+ err(`✗ BaseLinker: ${e.message}`);
40
+ fail('Nie udało się zalogować do BaseLinker.', 1);
41
+ }
42
+ }
43
+
26
44
  const products = resolveProducts(pos[0]);
27
45
  const scope = flags.write ? 'read write' : normalizeScope(flags.scope || 'read');
28
46
  let failures = 0;
@@ -41,13 +59,39 @@ async function cmdLogin(pos, flags) {
41
59
  }
42
60
 
43
61
  async function cmdLogout(pos) {
62
+ if (pos[0] === 'baselinker' || pos[0] === 'bl') {
63
+ out(credentials.removeProduct('baselinker') ? `✓ Wylogowano z BaseLinker (token usunięty lokalnie).` : `– BaseLinker: nie byłeś zalogowany.`);
64
+ return;
65
+ }
44
66
  for (const product of resolveProducts(pos[0])) {
45
67
  out(credentials.removeProduct(product.id) ? `✓ Wylogowano z ${product.name} (klucz usunięty lokalnie).` : `– ${product.name}: nie byłeś zalogowany.`);
46
68
  }
69
+ if (pos[0] === 'all') {
70
+ if (credentials.removeProduct('baselinker')) {
71
+ out('✓ Wylogowano z BaseLinker (token usunięty lokalnie).');
72
+ }
73
+ }
47
74
  out('Klucz możesz też unieważnić w produkcie: Ustawienia → Dostęp API.');
48
75
  }
49
76
 
50
77
  async function cmdWhoami(pos, flags) {
78
+ if (pos[0] === 'baselinker' || pos[0] === 'bl') {
79
+ const blToken = credentials.getBaseLinkerToken();
80
+ const rows = [{
81
+ id: 'baselinker',
82
+ status: blToken ? 'zalogowany' : 'niezalogowany',
83
+ key: blToken ? credentials.mask(blToken) : null,
84
+ email: blToken ? 'X-BLToken' : null,
85
+ scope: blToken ? 'api catalog' : null,
86
+ expires: blToken ? 'bez limitu' : null,
87
+ source: blToken ? (process.env.BASELINKER_TOKEN ? 'env:BASELINKER_TOKEN' : 'file') : null
88
+ }];
89
+ if (flags.json) { out(JSON.stringify(rows, null, 2)); return; }
90
+ out(`baselinker ${rows[0].status}`);
91
+ if (rows[0].key) out(`${''.padEnd(11)} klucz ${rows[0].key} · zakres api catalog · źródło ${rows[0].source}`);
92
+ return;
93
+ }
94
+
51
95
  const rows = [];
52
96
  for (const product of resolveProducts(pos[0])) {
53
97
  const found = credentials.getKey(product);
@@ -64,6 +108,22 @@ async function cmdWhoami(pos, flags) {
64
108
  };
65
109
  rows.push(row);
66
110
  }
111
+
112
+ if (!pos[0] || pos[0] === 'all') {
113
+ const blToken = credentials.getBaseLinkerToken();
114
+ if (blToken) {
115
+ rows.push({
116
+ id: 'baselinker',
117
+ status: 'zalogowany',
118
+ key: credentials.mask(blToken),
119
+ email: 'X-BLToken',
120
+ scope: 'api catalog',
121
+ expires: 'bez limitu',
122
+ source: process.env.BASELINKER_TOKEN ? 'env:BASELINKER_TOKEN' : 'file'
123
+ });
124
+ }
125
+ }
126
+
67
127
  if (flags.json) { out(JSON.stringify(rows, null, 2)); return; }
68
128
  for (const r of rows) {
69
129
  out(`${r.id.padEnd(11)} ${r.status}`);
@@ -132,8 +192,80 @@ async function cmdApi(pos, flags) {
132
192
  }
133
193
 
134
194
  async function cmdEstimate(pos, flags) {
135
- const file = flags.file || pos[0];
136
- if (!file) fail('Użycie: deertools estimate --file <rzut.pdf> [--kitchen 6]', 2);
195
+ const sub = pos[0];
196
+
197
+ if (sub === 'calculate' || sub === 'calc') {
198
+ const area = Number(pos[1] || flags.area);
199
+ if (!area || isNaN(area)) fail('Użycie: deertools estimate calculate <metraż_m2> [smart_life|modern_life|high_life]', 2);
200
+ const pkgs = {
201
+ smart_life: { name: 'Smart Life', rate: 1290, flat: 58050, marginPct: 33.1 },
202
+ modern_life: { name: 'Modern Life', rate: 1640, flat: 73800, marginPct: 31.2 },
203
+ high_life: { name: 'High Life', rate: 1990, flat: 89550, marginPct: 22.7 },
204
+ };
205
+ const isFlat = area < 45;
206
+ const requested = pos[2] || flags.package;
207
+ const selected = requested ? [pkgs[requested] || pkgs.modern_life] : Object.values(pkgs);
208
+
209
+ const results = selected.map(p => {
210
+ const gross = isFlat ? p.flat : Math.round(area * p.rate);
211
+ const net = Math.round(gross / 1.08);
212
+ const vat = gross - net;
213
+ const profitNet = Math.round(net * (p.marginPct / 100));
214
+ return {
215
+ package: p.name,
216
+ area_m2: area,
217
+ price_per_m2_gross: p.rate,
218
+ total_gross_pln: gross,
219
+ total_net_pln: net,
220
+ vat_pln: vat,
221
+ is_flat_rate: isFlat,
222
+ ref_margin_pct: p.marginPct,
223
+ est_profit_net_pln: profitNet,
224
+ };
225
+ });
226
+
227
+ if (flags.json) { out(JSON.stringify(results.length === 1 ? results[0] : results, null, 2)); return; }
228
+
229
+ out(`\nKalkulacja wykończenia dla metrażu ${area} m²${isFlat ? ' (stawka ryczałtowa < 45 m²)' : ''}:`);
230
+ const fmt = (n) => new Intl.NumberFormat('pl-PL', { maximumFractionDigits: 0 }).format(Math.round(n));
231
+ for (const r of results) {
232
+ 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)`);
233
+ }
234
+ return;
235
+ }
236
+
237
+ if (sub === 'margins') {
238
+ const area = Number(pos[1] || flags.area || 50);
239
+ const pkgs = [
240
+ { name: 'Smart Life', rate: 1290, margin: 33.1 },
241
+ { name: 'Modern Life', rate: 1640, margin: 31.2 },
242
+ { name: 'High Life', rate: 1990, margin: 22.7 },
243
+ ];
244
+ const isFlat = area < 45;
245
+ const rows = pkgs.map(p => {
246
+ const gross = isFlat ? (p.rate * 45) : (area * p.rate);
247
+ const net = gross / 1.08;
248
+ const profit = net * (p.margin / 100);
249
+ const cost = net - profit;
250
+ return {
251
+ package: p.name,
252
+ gross: Math.round(gross),
253
+ net: Math.round(net),
254
+ cost: Math.round(cost),
255
+ profit: Math.round(profit),
256
+ margin: p.margin,
257
+ };
258
+ });
259
+ if (flags.json) { out(JSON.stringify(rows, null, 2)); return; }
260
+ out(`\nAnaliza marż referencyjnych Deer Design dla ${area} m²:`);
261
+ for (const r of rows) {
262
+ out(` • ${r.package.padEnd(14)} Przychód netto: ${r.net} zł · Koszt: ${r.cost} zł · Zysk: ${r.profit} zł (${r.margin}%)`);
263
+ }
264
+ return;
265
+ }
266
+
267
+ const file = flags.file || (sub === 'plan' ? pos[1] : pos[0]);
268
+ 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);
137
269
  if (!fs.existsSync(file)) fail(`Plik nie istnieje: ${file}`, 2);
138
270
  const bytes = fs.readFileSync(file);
139
271
  if (bytes.length > 10 * 1024 * 1024) fail('Plik jest większy niż 10 MB (limit analizy rzutu).', 2);
@@ -161,43 +293,192 @@ async function cmdEstimate(pos, flags) {
161
293
  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²)' : ''}`);
162
294
  }
163
295
 
296
+ const { detectAgents, installSkillContent } = require('./agent-detector');
297
+ const skillCommands = require('./skill-commands');
298
+
164
299
  async function cmdSkill(pos, flags) {
165
- if ((pos[0] || 'install') !== 'install') fail('Użycie: deertools skill install [produkt|all] [--dir <katalog skilli>]', 2);
300
+ const sub = pos[0] || 'install';
301
+ if (sub === 'list') {
302
+ return skillCommands.cmdSkillList(flags, out, err);
303
+ }
304
+ if (sub === 'search') {
305
+ return skillCommands.cmdSkillSearch(pos[1] || flags.query || '', flags, out, err);
306
+ }
307
+ if (sub === 'push') {
308
+ return skillCommands.cmdSkillPush(pos[1], flags, out, err);
309
+ }
310
+ if (sub === 'update') {
311
+ return skillCommands.cmdSkillUpdate(flags, out, err);
312
+ }
313
+ if (sub === 'promote') {
314
+ return skillCommands.cmdSkillPromote(pos[1], flags, out, err);
315
+ }
316
+ if (sub === 'install') {
317
+ return skillCommands.cmdSkillInstall(pos[1], flags, out, err);
318
+ }
319
+ fail('Użycie:\n deertools skill list [--tag <tag>]\n deertools skill search <fraza>\n deertools skill install <slug[@version]>\n deertools skill push [katalog] [--version <x.y.z>]\n deertools skill update\n deertools skill promote <slug>', 2);
320
+ }
321
+
322
+ async function cmdMcp(pos, flags) {
323
+ const sub = pos[0] || 'config';
166
324
  const products = resolveProducts(pos[1]);
167
- const dir = flags.dir || path.join(os.homedir(), '.gemini', 'config', 'skills');
168
- let installed = 0;
169
- for (const product of products) {
170
- const r = await fetchText(`${product.site}/skill/SKILL.md`);
171
- if (!r.ok) { err(`– ${product.name}: brak skilla (${product.site}/skill/SKILL.md → ${r.status})`); continue; }
172
- const name = parseFrontmatterName(r.text) || `deer-${product.id}`;
173
- const target = path.join(dir, name);
174
- fs.mkdirSync(target, { recursive: true });
175
- fs.writeFileSync(path.join(target, 'SKILL.md'), r.text);
176
- out(`✓ ${product.name}: ${path.join(target, 'SKILL.md')}`);
177
- installed += 1;
178
- }
179
- if (!installed) fail('Nie zainstalowano żadnego skilla.', 4);
325
+ const servers = {};
326
+
327
+ for (const p of products) {
328
+ if (!p.mcpEndpoint) continue;
329
+ const cred = credentials.getKey(p);
330
+ const key = cred ? cred.key : `${p.keyPrefix}<twój_klucz_api>`;
331
+ const srvKey = `deer-${p.id}`;
332
+ servers[srvKey] = {
333
+ url: p.mcpEndpoint,
334
+ headers: {
335
+ 'x-api-key': key,
336
+ 'Authorization': `Bearer ${key}`,
337
+ },
338
+ };
339
+ }
340
+
341
+ if (sub === 'config') {
342
+ const config = { mcpServers: servers };
343
+ if (flags.json) {
344
+ out(JSON.stringify(config, null, 2));
345
+ } else {
346
+ out('\nKonfiguracja Remote MCP dla DeerTools (np. Cursor IDE, Claude Desktop, Antigravity):');
347
+ out(JSON.stringify(config, null, 2));
348
+ out('\nAby zainstalować automatycznie w Cursorze i projektach, uruchom:');
349
+ out(' deertools mcp install\n');
350
+ }
351
+ return;
352
+ }
353
+
354
+ if (sub === 'install') {
355
+ let touched = 0;
356
+ // 1. Cursor global (~/.cursor/mcp.json)
357
+ const cursorGlobal = path.join(os.homedir(), '.cursor', 'mcp.json');
358
+ try {
359
+ let cur = { mcpServers: {} };
360
+ if (fs.existsSync(cursorGlobal)) {
361
+ try { cur = JSON.parse(fs.readFileSync(cursorGlobal, 'utf8')); } catch (_) {}
362
+ }
363
+ cur.mcpServers = Object.assign({}, cur.mcpServers, servers);
364
+ fs.mkdirSync(path.dirname(cursorGlobal), { recursive: true });
365
+ fs.writeFileSync(cursorGlobal, JSON.stringify(cur, null, 2));
366
+ out(`✓ Cursor IDE: ${cursorGlobal}`);
367
+ touched++;
368
+ } catch (e) {
369
+ err(`✗ Cursor IDE (${cursorGlobal}): ${e.message}`);
370
+ }
371
+
372
+ // 2. Cursor workspace (.cursor/mcp.json)
373
+ const workspaceCursor = path.join(process.cwd(), '.cursor', 'mcp.json');
374
+ if (fs.existsSync(path.join(process.cwd(), '.cursor')) || fs.existsSync(path.join(process.cwd(), '.git'))) {
375
+ try {
376
+ let cur = { mcpServers: {} };
377
+ if (fs.existsSync(workspaceCursor)) {
378
+ try { cur = JSON.parse(fs.readFileSync(workspaceCursor, 'utf8')); } catch (_) {}
379
+ }
380
+ cur.mcpServers = Object.assign({}, cur.mcpServers, servers);
381
+ fs.mkdirSync(path.dirname(workspaceCursor), { recursive: true });
382
+ fs.writeFileSync(workspaceCursor, JSON.stringify(cur, null, 2));
383
+ out(`✓ Cursor Workspace: ${workspaceCursor}`);
384
+ touched++;
385
+ } catch (e) {
386
+ err(`✗ Cursor Workspace (${workspaceCursor}): ${e.message}`);
387
+ }
388
+ }
389
+
390
+ // 3. Antigravity / Gemini (.agents/mcp_config.json)
391
+ const agentsConfig = path.join(process.cwd(), '.agents', 'mcp_config.json');
392
+ if (fs.existsSync(path.dirname(agentsConfig))) {
393
+ try {
394
+ let cur = { mcpServers: {} };
395
+ if (fs.existsSync(agentsConfig)) {
396
+ try { cur = JSON.parse(fs.readFileSync(agentsConfig, 'utf8')); } catch (_) {}
397
+ }
398
+ cur.mcpServers = Object.assign({}, cur.mcpServers, servers);
399
+ fs.writeFileSync(agentsConfig, JSON.stringify(cur, null, 2));
400
+ out(`✓ Antigravity / Agent Workspace: ${agentsConfig}`);
401
+ touched++;
402
+ } catch (e) {
403
+ err(`✗ Antigravity (${agentsConfig}): ${e.message}`);
404
+ }
405
+ }
406
+
407
+ if (!touched) fail('Nie znaleziono środowiska agenta do instalacji MCP.', 4);
408
+ out(`\nPomyślnie zintegrowano serwery MCP dla produktów DeerTools!`);
409
+ return;
410
+ }
411
+
412
+ if (sub === 'serve') {
413
+ const { cmdMcpServe } = require('./mcp-stdio');
414
+ return cmdMcpServe(pos, flags);
415
+ }
416
+
417
+ fail('Użycie: deertools mcp [config|install|serve] [produkt|all] [--json]', 2);
180
418
  }
181
419
 
420
+ const { cmdDoctor } = require('./doctor');
421
+ const { cmdAudit } = require('./audit');
422
+ const { cmdProfile } = require('./profiles');
423
+ const { cmdCompletion } = require('./completion');
424
+ const { cmdCatalog, cmdDeliveries, cmdSupplier, cmdRendermind } = require('./domain-commands');
425
+ const { cmdBaseLinker } = require('./baselinker-commands');
426
+
182
427
  const HELP = `
183
- DeerTools CLI — logowanie z laptopa i dostęp agenta AI do API produktów DeerTools
428
+ DeerTools CLI — ekosystem narzędzi dla architektów i agentów AI Deer Design
184
429
 
185
430
  Produkty: ${ALL_IDS.join(', ')}
186
431
 
187
- deertools login <produkt|all> [--write] [--scope "read write"] [--no-open] [--name <komputer>]
188
- Loguje przez przeglądarkę i zapisuje klucz API (zakres domyślnie tylko odczyt: read).
189
- Klucz działa w Twoim imieniu, nie ma więcej uprawnień niż Ty, wygasa po 90 dniach.
190
- deertools logout [produkt|all] Usuwa lokalny klucz.
191
- deertools whoami [produkt|all] [--json] Status logowania, zakres, ważność.
192
- deertools docs <produkt> [llms|openapi|skill] Dokumentacja dla agenta (publiczna).
193
- deertools endpoints <produkt> [--json] Lista endpointów z zakresem (z openapi.json).
194
- deertools api <produkt> <METODA> </ścieżka> [--query k=v] [--data JSON | --data-file plik]
195
- Wywołuje API produktu z Twoim kluczem (tylko adres produktu, klucz nie trafia nigdzie indziej).
196
- deertools estimate --file <rzut.pdf> [--kitchen 6] [--json] Wycena rzutu (Kosztorys).
197
- deertools skill install [produkt|all] [--dir <katalog>] Instaluje skill agenta z produktu.
198
-
199
- Zmienne środowiskowe (CI/agenty): DEERTOOLS_<PRODUKT>_KEY, DEERTOOLS_HOME.
200
- Kody wyjścia: 0 ok · 1 błąd żądania · 2 błędne użycie · 3 brak/wygasłe logowanie · 4 brak dokumentacji lub adresu API.
432
+ Dostęp i autoryzacja:
433
+ deertools login <produkt|all> [--write] [--no-open] Logowanie OAuth przez przeglądarkę.
434
+ deertools logout [produkt|all] Usuwa lokalny klucz.
435
+ deertools whoami [produkt|all] [--json] Status logowania, zakres, ważność.
436
+ deertools profile [list|use <nazwa>|current] Przełączanie profili poświadczeń.
437
+
438
+ Komendy domenowe:
439
+ deertools catalog <search|get|categories|manufacturers> Zarządzanie Katalogiem Connect.
440
+ deertools deliveries <list|track|suppliers> Zarządzanie Delivery Hub.
441
+ deertools estimate <calculate|margins|plan> Kalkulacje i analiza rzutów PDF.
442
+ deertools supplier <discounts|markups|calculate> Rabaty, narzuty i łańcuch marżowy.
443
+ deertools rendermind <styles|get> Style i wizualizacje wnętrz.
444
+
445
+ BaseLinker MCP Proxy & Hub:
446
+ deertools baselinker auth [token] Zapisuje klucz API BaseLinkera w credentials.json.
447
+ deertools baselinker test Testuje autoryzację i listuje katalogi.
448
+ deertools baselinker serve [--port 3456] Lokalny serwer Web UI z dashboardem i MCP SSE.
449
+ deertools baselinker mcp [--all] Mostek MCP stdio (Cursor / Claude Desktop).
450
+ deertools baselinker sync-schema Pobiera i aktualizuje bazę 204 metod z api.baselinker.com.
451
+ deertools baselinker search <fraza> Przeszukuje bazę metod i schematów parametrów.
452
+ deertools baselinker call <metoda> [json] Wywołuje dowolną metodę BaseLinkera z CLI.
453
+ deertools baselinker catalog <search|get> Operacje katalogowe z poziomu terminala.
454
+
455
+ Integracje MCP i Agenci AI (DeerSkills Hub):
456
+ deertools skill list [--tag <tag>] Przegląda skille globalne i użytkownika w DeerSkills Hub.
457
+ deertools skill search <fraza> Wyszukuje umiejętności po słowach kluczowych.
458
+ deertools skill install <slug[@wersja]> Instaluje skill w ~/.gemini/antigravity/skills lub .agents.
459
+ deertools skill push [katalog] [--bump patch|minor] Publikuje nową wersję z automatycznym skanerem sekretów.
460
+ deertools skill update [slug] Aktualizuje zainstalowane skille do najnowszej wersji.
461
+ deertools skill promote <slug> Zgłasza nominację skilla do oficjalnego katalogu globalnego.
462
+ deertools mcp list Lista dostępnych serwerów MCP w chmurze.
463
+ deertools mcp config [--json] Generuje konfigurację Remote MCP dla Cursor i Antigravity.
464
+ deertools mcp install Instaluje serwery MCP w Cursor i Antigravity.
465
+ deertools mcp serve Lokalny mostek MCP stdio (Claude Desktop / Ollama).
466
+ deertools docs <produkt> [llms|openapi|skill] Pobiera dokumentację dla agentów.
467
+ deertools endpoints <produkt> [--json] Lista endpointów REST z openapi.json.
468
+ deertools api <produkt> <METODA> </ścieżka> Wywołuje bezpośrednio API produktu.
469
+
470
+ Narzędzia systemowe:
471
+ deertools doctor [--json] Pełna diagnostyka połączeń, portów i kluczy.
472
+ deertools audit [--limit 25] [--clear] [--json] Historia operacji i wywołań API.
473
+ deertools completion [install|zsh|bash] Autouzupełnianie w powłoce po wciśnięciu Tab.
474
+
475
+ Zmienne środowiskowe: DEERTOOLS_<PRODUKT>_KEY, BASELINKER_TOKEN, DEERTOOLS_HOME, DEERTOOLS_PROFILE.
201
476
  `;
202
477
 
203
- module.exports = { cmdLogin, cmdLogout, cmdWhoami, cmdDocs, cmdEndpoints, cmdApi, cmdEstimate, cmdSkill, HELP, parseQuery };
478
+ module.exports = {
479
+ cmdLogin, cmdLogout, cmdWhoami, cmdDocs, cmdEndpoints, cmdApi, cmdEstimate, cmdSkill, cmdMcp,
480
+ cmdDoctor, cmdAudit, cmdProfile, cmdCompletion, cmdCatalog, cmdDeliveries, cmdSupplier, cmdRendermind,
481
+ cmdBaseLinker,
482
+ HELP, parseQuery,
483
+ };
484
+
@@ -0,0 +1,189 @@
1
+ 'use strict';
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+ const { ALL_IDS } = require('./registry');
6
+
7
+ function generateZshCompletion() {
8
+ return `#compdef deertools deer
9
+
10
+ _deertools() {
11
+ local curcontext="$curcontext" state line
12
+ typeset -A opt_args
13
+
14
+ local -a commands
15
+ commands=(
16
+ 'login:Logowanie do produktów przez przeglądarkę i OAuth'
17
+ 'logout:Wylogowanie i usunięcie lokalnych kluczy'
18
+ 'whoami:Status zalogowania, ważność tokenów i zakresy'
19
+ 'docs:Pobranie dokumentacji produktu dla agentów AI'
20
+ 'endpoints:Lista endpointów REST z openapi.json'
21
+ 'api:Wywołanie API z automatycznym nagłówkiem autoryzacji'
22
+ 'catalog:Zarządzanie produktami w Katalogu Connect'
23
+ 'deliveries:Zarządzanie dostawami, paczkami i WZ'
24
+ 'estimate:Kalkulator kosztorysowania i analiza rzutów'
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'
28
+ 'mcp:Konfiguracja i instalacja serwerów Remote MCP'
29
+ 'doctor:Diagnostyka środowiska, połączeń i uprawnień'
30
+ 'audit:Historia wywołań API i log operacji'
31
+ 'profile:Przełączanie profili użytkownika / środowisk'
32
+ 'completion:Generowanie skryptu autouzupełniania powłoki'
33
+ 'baselinker:Lokalny mostek MCP, Web UI i katalog BaseLinkera'
34
+ )
35
+
36
+ local -a products
37
+ products=( ${ALL_IDS.map(p => `'${p}'`).join(' ')} 'all' )
38
+
39
+ _arguments -C \\
40
+ '--help[Pokaż pomoc]' \\
41
+ '--version[Pokaż wersję]' \\
42
+ '--json[Wyjście w formacie JSON]' \\
43
+ '--profile[Wybierz profil poświadczeń]:profile:' \\
44
+ '1: :->cmd' \\
45
+ '*:: :->args'
46
+
47
+ case $state in
48
+ cmd)
49
+ _describe -t commands 'deertools komendy' commands
50
+ ;;
51
+ args)
52
+ case $line[1] in
53
+ login|logout|docs|endpoints)
54
+ _values 'produkty' ${ALL_IDS.map(p => `'${p}'`).join(' ')} 'all'
55
+ ;;
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
+ baselinker)
77
+ 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
+ _describe -t sub 'baselinker podkomendy' sub
79
+ ;;
80
+ skill)
81
+ _values 'akcja' 'install'
82
+ ;;
83
+ mcp)
84
+ local -a sub=( 'config:Wyświetl JSON konfiguracji MCP' 'install:Zainstaluj w Cursor i Antigravity' 'serve:Uruchom lokalny mostek MCP stdio' )
85
+ _describe -t sub 'mcp podkomendy' sub
86
+ ;;
87
+ esac
88
+ ;;
89
+ esac
90
+ }
91
+
92
+ _deertools "$@"
93
+ `;
94
+ }
95
+
96
+ function generateBashCompletion() {
97
+ return `#!/usr/bin/env bash
98
+ _deertools_completions() {
99
+ local cur prev opts
100
+ COMPREPLY=()
101
+ cur="\${COMP_WORDS[COMP_CWORD]}"
102
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
103
+
104
+ opts="login logout whoami docs endpoints api catalog deliveries estimate supplier rendermind baselinker skill mcp doctor audit profile completion --help --version --json"
105
+
106
+ case "$prev" in
107
+ login|logout|docs|endpoints)
108
+ COMPREPLY=( $(compgen -W "${ALL_IDS.join(' ')} all" -- "$cur") )
109
+ return 0
110
+ ;;
111
+ baselinker)
112
+ COMPREPLY=( $(compgen -W "auth test serve mcp sync-schema search call catalog" -- "$cur") )
113
+ return 0
114
+ ;;
115
+ mcp)
116
+ COMPREPLY=( $(compgen -W "config install serve" -- "$cur") )
117
+ return 0
118
+ ;;
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
+ esac
136
+
137
+ if [[ "\$cur" == -* ]]; then
138
+ COMPREPLY=( $(compgen -W "--help --version --json --write --scope --profile" -- "$cur") )
139
+ return 0
140
+ fi
141
+
142
+ COMPREPLY=( $(compgen -W "\$opts" -- "\$cur") )
143
+ }
144
+ complete -F _deertools_completions deertools deer
145
+ `;
146
+ }
147
+
148
+ async function cmdCompletion(pos, flags) {
149
+ const shell = (pos[0] || (process.env.SHELL ? path.basename(process.env.SHELL) : 'zsh')).toLowerCase();
150
+
151
+ if (pos[0] === 'install') {
152
+ const userShell = process.env.SHELL ? path.basename(process.env.SHELL) : 'zsh';
153
+ const isZsh = userShell.includes('zsh');
154
+ const rcFile = isZsh ? path.join(os.homedir(), '.zshrc') : path.join(os.homedir(), '.bashrc');
155
+
156
+ const completionDir = path.join(os.homedir(), '.deertools', 'completion');
157
+ fs.mkdirSync(completionDir, { recursive: true });
158
+
159
+ const completionScript = isZsh ? generateZshCompletion() : generateBashCompletion();
160
+ const targetFile = path.join(completionDir, isZsh ? '_deertools' : 'deertools.bash');
161
+ fs.writeFileSync(targetFile, completionScript, 'utf8');
162
+
163
+ const loaderLine = isZsh
164
+ ? `fpath=(~/.deertools/completion $fpath); autoload -Uz compinit && compinit -u # deertools completion`
165
+ : `source ~/.deertools/completion/deertools.bash # deertools completion`;
166
+
167
+ let rcContent = '';
168
+ if (fs.existsSync(rcFile)) {
169
+ rcContent = fs.readFileSync(rcFile, 'utf8');
170
+ }
171
+
172
+ if (!rcContent.includes('deertools completion')) {
173
+ fs.appendFileSync(rcFile, `\n# DeerTools CLI Autocompletion\n${loaderLine}\n`);
174
+ process.stdout.write(`✓ Zainstalowano autouzupełnianie w ${rcFile}\n`);
175
+ process.stdout.write(` Aby aktywować natychmiast, wykonaj:\n source ${rcFile}\n`);
176
+ } else {
177
+ process.stdout.write(`✓ Autouzupełnianie jest już skonfigurowane w ${rcFile}\n`);
178
+ }
179
+ return;
180
+ }
181
+
182
+ if (shell.includes('bash')) {
183
+ process.stdout.write(generateBashCompletion());
184
+ } else {
185
+ process.stdout.write(generateZshCompletion());
186
+ }
187
+ }
188
+
189
+ module.exports = { cmdCompletion, generateZshCompletion, generateBashCompletion };
@@ -3,11 +3,16 @@ const fs = require('fs');
3
3
  const os = require('os');
4
4
  const path = require('path');
5
5
 
6
+ let activeProfile = 'default';
7
+ function setProfile(p) { if (p) activeProfile = p; }
8
+ function getProfile() { return process.env.DEERTOOLS_PROFILE || activeProfile; }
9
+
6
10
  function homeDir() {
7
11
  return process.env.DEERTOOLS_HOME || path.join(os.homedir(), '.deertools');
8
12
  }
9
13
  function credentialsFile() {
10
- return path.join(homeDir(), 'credentials.json');
14
+ const p = getProfile();
15
+ return p && p !== 'default' ? path.join(homeDir(), `credentials.${p}.json`) : path.join(homeDir(), 'credentials.json');
11
16
  }
12
17
 
13
18
  function readAll() {
@@ -84,4 +89,36 @@ function mask(key) {
84
89
  return `${key.slice(0, 12)}…${key.slice(-4)}`;
85
90
  }
86
91
 
87
- module.exports = { homeDir, credentialsFile, readAll, saveProduct, removeProduct, getKey, expiryInfo, mask };
92
+ function getBaseLinkerToken() {
93
+ if (process.env.BASELINKER_TOKEN) return process.env.BASELINKER_TOKEN;
94
+ if (process.env.BL_TOKEN) return process.env.BL_TOKEN;
95
+ if (process.env.DEERTOOLS_BASELINKER_KEY) return process.env.DEERTOOLS_BASELINKER_KEY;
96
+ const products = readAll().products;
97
+ if (products.baselinker && products.baselinker.token) return products.baselinker.token;
98
+ if (products.baselinker && products.baselinker.key) return products.baselinker.key;
99
+ return null;
100
+ }
101
+
102
+ function saveBaseLinkerToken(token) {
103
+ const data = readAll();
104
+ data.products.baselinker = {
105
+ token,
106
+ updated_at: new Date().toISOString()
107
+ };
108
+ writeAll(data);
109
+ }
110
+
111
+ module.exports = {
112
+ homeDir,
113
+ credentialsFile,
114
+ readAll,
115
+ saveProduct,
116
+ removeProduct,
117
+ getKey,
118
+ expiryInfo,
119
+ mask,
120
+ setProfile,
121
+ getProfile,
122
+ getBaseLinkerToken,
123
+ saveBaseLinkerToken
124
+ };