@deerdesign/deertools 1.0.4 → 1.2.1
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 +133 -16
- package/docs/prezentacja-it-deertools.html +645 -0
- package/docs/prezentacja-it-deertools.md +182 -0
- package/package.json +2 -1
- package/src/audit.js +65 -0
- package/src/baselinker/browser-login.js +214 -0
- package/src/baselinker/catalog-tools.js +457 -0
- package/src/baselinker/client.js +136 -0
- package/src/baselinker/crawler.js +157 -0
- package/src/baselinker/index.js +24 -0
- package/src/baselinker/mcp-server.js +153 -0
- package/src/baselinker/methods-index.json +1226 -0
- package/src/baselinker/schema-cache.json +6342 -0
- package/src/baselinker/ui.js +386 -0
- package/src/baselinker-commands.js +180 -0
- package/src/cli.js +22 -2
- package/src/commands.js +302 -35
- package/src/completion.js +189 -0
- package/src/credentials.js +39 -2
- package/src/doctor.js +286 -0
- package/src/domain-commands.js +288 -0
- package/src/formatters.js +50 -0
- package/src/mcp-stdio.js +197 -0
- package/src/profiles.js +78 -0
- package/src/registry.js +22 -1
- package/src/skill-commands.js +223 -0
- package/src/skills-hub.js +201 -0
- package/src/tui.js +139 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# DeerTools CLI & MCP Ecosystem
|
|
2
|
+
## Prezentacja Architektoniczna dla Zespołu IT
|
|
3
|
+
|
|
4
|
+
> **Wersja:** 1.1.0 · Node.js 18+
|
|
5
|
+
> **Organizacja:** [github.com/DeerDesign/deertools-cli](https://github.com/DeerDesign/deertools-cli)
|
|
6
|
+
> **Paczka npm:** `@deerdesign/deertools`
|
|
7
|
+
> **Wersja interaktywna HTML:** [docs/prezentacja-it-deertools.html](./prezentacja-it-deertools.html)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Slajd 1 · Cover
|
|
12
|
+
### DeerTools CLI & MCP Ecosystem
|
|
13
|
+
*Standard narzędziowy i mostek protokołu MCP dla zespołów IT oraz Agentów AI*
|
|
14
|
+
|
|
15
|
+
- **Zakres:** Architektura zero-dependency, bezpieczeństwo OAuth 2.0 PKCE, integracja 5 domen Deer oraz lokalny silnik BaseLinker Hub & MCP Proxy.
|
|
16
|
+
- **Autorzy:** Deer Design AI & Architecture Team
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Slajd 2 · Agenda
|
|
21
|
+
|
|
22
|
+
1. **Filozofia Zero-Dependency Engine:** silnik Node.js >= 18 bez podatności supply-chain.
|
|
23
|
+
2. **Dystrybucja i instalacja:** npm global, ad-hoc npx, klonowanie repozytorium GitHub.
|
|
24
|
+
3. **Model Bezpieczeństwa:** OAuth 2.0 Authorization Code Flow + PKCE S256, uprawnienia 0600, obsługa CI/CD.
|
|
25
|
+
4. **Ekosystem 5 domen:** Kosztorys, Katalog, Dostawy, Supplier Hub, RenderMind.
|
|
26
|
+
5. **BaseLinker Hub & MCP (v1.1.0):** 204 metody API offline, Token Bucket Rate Limiter (95 req/min), lokalny Web Hub.
|
|
27
|
+
6. **Integracja z Agentami AI:** konfiguracja Cursor IDE, Antigravity i Claude Desktop.
|
|
28
|
+
7. **Narzędzia IT:** `doctor`, `audit`, `profile` i autouzupełnianie powłoki.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Slajd 3 · Architektura & Zero-Dependency Engine
|
|
33
|
+
|
|
34
|
+
- **0 zewnętrznych zależności w `package.json`:**
|
|
35
|
+
- 100% wykorzystanie wbudowanych modułów standardowych: `node:http`, `node:crypto`, `node:fs`, `node:test`.
|
|
36
|
+
- Zero ryzyka ataku na łańcuch dostaw (supply-chain attack) i zero problemów licencyjnych.
|
|
37
|
+
- Czas startu binarki < 50ms.
|
|
38
|
+
- **Dwustronna rola w ekosystemie IT:**
|
|
39
|
+
- **Dla Inżyniera / DevOpsa:** wygodne narzędzie wiersza poleceń do zarządzania kluczami, audytu, przełączania profili oraz weryfikacji zdrowia serwisów (`doctor`).
|
|
40
|
+
- **Dla Agenta AI (Cursor / Antigravity / Claude):** natywny serwer protokołu MCP (Model Context Protocol) komunikujący się przez strumienie stdio (JSON-RPC) lub Server-Sent Events (SSE).
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Slajd 4 · Instalacja & Wdrożenie
|
|
45
|
+
|
|
46
|
+
### 1. Globalna instalacja (zalecana dla deweloperów):
|
|
47
|
+
```bash
|
|
48
|
+
npm install -g @deerdesign/deertools
|
|
49
|
+
|
|
50
|
+
# Rejestruje 2 aliasy w systemie:
|
|
51
|
+
deertools --version # 1.1.0
|
|
52
|
+
deer whoami # skrócony alias
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. Uruchomienie ad-hoc bez instalacji:
|
|
56
|
+
```bash
|
|
57
|
+
npx @deerdesign/deertools doctor
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 3. Klonowanie repozytorium (wersja deweloperska):
|
|
61
|
+
```bash
|
|
62
|
+
git clone https://github.com/DeerDesign/deertools-cli.git
|
|
63
|
+
cd deertools-cli
|
|
64
|
+
npm test # uruchamia 31/31 wbudowanych testów
|
|
65
|
+
npm link # podpina lokalną wersję do ścieżki PATH
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Wymagania:** Node.js >= 18.0.0 (macOS, Linux, Windows WSL).
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Slajd 5 · Bezpieczeństwo & Model Poświadczeń
|
|
73
|
+
|
|
74
|
+
### Przepływ OAuth 2.0 PKCE S256 (RFC 7636):
|
|
75
|
+
1. CLI generuje kryptograficznie bezpieczny `code_verifier` (43+ znaki) oraz jego hash `code_challenge` (SHA-256 base64url).
|
|
76
|
+
2. Otwiera przeglądarkę i uruchamia lokalny serwer loopback pod adresem `127.0.0.1:3000`.
|
|
77
|
+
3. Po autoryzacji serwer loopback odbiera jednorazowy `code` i dokonuje bezpośredniej wymiany na klucz API w bezpiecznym kanale back-channel HTTPS.
|
|
78
|
+
4. **Klucze i tokeny nigdy nie trafiają do parametrów URL przeglądarki ani do historii.**
|
|
79
|
+
|
|
80
|
+
### Ochrona kluczy na dysku & CI/CD:
|
|
81
|
+
- Poświadczenia zapisywane są w pliku `~/.deertools/credentials.json` z restrykcyjnymi uprawnieniami pliku **0600** (dostępny wyłącznie dla procesu właściciela).
|
|
82
|
+
- W środowiskach bezgłowych (Headless) i pipeline'ach CI/CD klucze można przekazywać przez zmienne środowiskowe:
|
|
83
|
+
```bash
|
|
84
|
+
export DEERTOOLS_CATALOG_KEY="deer_catalog_live_..."
|
|
85
|
+
export DEERTOOLS_ESTIMATE_KEY="deer_kosztorys_live_..."
|
|
86
|
+
export DEERTOOLS_DELIVERIES_KEY="deer_dostawy_live_..."
|
|
87
|
+
export DEERTOOLS_SUPPLIER_KEY="deer_supplier_live_..."
|
|
88
|
+
export DEERTOOLS_RENDERMIND_KEY="deer_rendermind_live_..."
|
|
89
|
+
export DEERTOOLS_BASELINKER_TOKEN="4000000-..."
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Slajd 6 · Ekosystem 5 Domen DeerTools
|
|
95
|
+
|
|
96
|
+
| Domena | ID CLI | Opis i Możliwości |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| **Kosztorys** | `estimate` | Kalkulacja marż i kosztów wykończenia dla pakietów Smart, Modern i High Life. Algorytmy obmiarów i wyceny. |
|
|
99
|
+
| **Katalog** | `catalog` | Dostęp do bazy ~17 000 produktów wykończeniowych, atrybuty techniczne, warianty i synchronizacja cen. |
|
|
100
|
+
| **Dostawy** | `deliveries` | Delivery Hub: dokumenty WZ, magazyny, statusy kurierskie, zarządzanie dostawami na budowy. |
|
|
101
|
+
| **Supplier Hub** | `supplier` | Łańcuch marżowy, rabaty producenckie, promocje sprzedaży i reguły cenowe dla lokalizacji. |
|
|
102
|
+
| **RenderMind** | `rendermind` | Silnik fotorealistycznych renderów wnętrz, estymacje z rzutów PDF i analiza szkiców pCon. |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Slajd 7 · BaseLinker Hub & Proxy MCP (Nowość w v1.1.0)
|
|
107
|
+
|
|
108
|
+
- **204 Metody API offline:**
|
|
109
|
+
- 93 metody katalogowe (`Product catalog`), 51 metod zamówień, 21 metod zwrotów, 16 metod kurierskich.
|
|
110
|
+
- Kompletny zrzut parametrów, typów i opisów z oficjalnej dokumentacji BaseLinkera zapisany w lokalnym cache.
|
|
111
|
+
- **Token Bucket Rate Limiter:**
|
|
112
|
+
- Sztywny bufor **95 req / 60 sekund** zapobiega przekroczeniu limitu 100 req/min i błędom HTTP 429.
|
|
113
|
+
- **Wysokopoziomowe narzędzia katalogu:**
|
|
114
|
+
- Budowanie relacji produktów (`related`, `substitution`, `upsell`, `downsell`, `crosssell`).
|
|
115
|
+
- Zarządzanie galerią zdjęć na pozycjach 0–15 (URL i base64).
|
|
116
|
+
- Warianty, zestawy (bundle), kategorie, producenci i tagi.
|
|
117
|
+
- Masowe aktualizacje cen i stanów magazynowych do 1000 sztuk w jednym wywołaniu.
|
|
118
|
+
- **Lokalny serwer Web Hub:**
|
|
119
|
+
- Uruchomienie: `deertools baselinker serve --port 3456`
|
|
120
|
+
- Dashboard przeglądarkowy z formularzem tokena, testem połączenia i strumieniem zdarzeń SSE dla Agenta AI.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Slajd 8 · Integracja z Agentami AI (MCP Protocol 2024-11-05)
|
|
125
|
+
|
|
126
|
+
### Automatyczna instalacja konfiguracji:
|
|
127
|
+
```bash
|
|
128
|
+
deertools mcp install # Rejestruje serwery MCP w plikach konfiguracyjnych IDE
|
|
129
|
+
deertools skill install all # Instaluje definicje umiejętności (SKILL.md) dla agentów
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Obsługiwane środowiska:
|
|
133
|
+
1. **Cursor IDE:**
|
|
134
|
+
- Globalnie: `~/.cursor/mcp.json`
|
|
135
|
+
- Projektowo: `.cursor/mcp.json`
|
|
136
|
+
2. **Google Antigravity / Gemini Workspace:**
|
|
137
|
+
- Projektowo: `.agents/mcp_config.json`
|
|
138
|
+
3. **Claude Desktop / Ollama / Inne narzędzia LLM:**
|
|
139
|
+
- Dedykowany mostek stdio: `deertools mcp serve` lub `deertools baselinker mcp`.
|
|
140
|
+
|
|
141
|
+
### Ochrona budżetu kontekstowego LLM:
|
|
142
|
+
- Domyślnie serwer eksponuje **15 kluczowych narzędzi wysokopoziomowych** + uniwersalne `bl_search_methods` i `bl_call_api`.
|
|
143
|
+
- Opcjonalna flaga `--all` eksponuje pełne 219 narzędzi dla modeli o bardzo dużym oknie kontekstowym.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Slajd 9 · Diagnostyka, Audyt i Multi-Profiling
|
|
148
|
+
|
|
149
|
+
### 1. `deertools doctor`
|
|
150
|
+
- Kompleksowa diagnostyka środowiska wykonawczego:
|
|
151
|
+
- Wersja Node.js i stan uprawnień pliku poświadczeń (0600).
|
|
152
|
+
- Sprawdzenie wolnych portów lokalnych (3000 dla OAuth, 3456 dla BaseLinkera).
|
|
153
|
+
- Test łączności i czasy ping HTTP do wszystkich 5 serwisów produkcyjnych i BaseLinkera.
|
|
154
|
+
- Sprawdzenie ważności tokenów i kluczy API.
|
|
155
|
+
- Test poprawności bazy schematów (204 metody) oraz handshake JSON-RPC MCP.
|
|
156
|
+
- Weryfikacja rejestracji serwerów w plikach konfiguracyjnych agentów.
|
|
157
|
+
- Flaga `--json` umożliwia wpięcie w systemy automatycznego monitoringu IT.
|
|
158
|
+
|
|
159
|
+
### 2. `deertools audit`
|
|
160
|
+
- Lokalny rejestr wywołań API przez agentów AI.
|
|
161
|
+
- Monitorowanie metody, czasu wykonania, parametrów i kodów odpowiedzi.
|
|
162
|
+
- Opcje: `deertools audit --limit 50`, `deertools audit --clear`.
|
|
163
|
+
|
|
164
|
+
### 3. `deertools profile`
|
|
165
|
+
- Zarządzanie wieloma profilami poświadczeń (np. produkcja, staging, dev-lokalny).
|
|
166
|
+
- Komendy: `deertools profile list`, `deertools profile add <nazwa>`, `deertools profile use <nazwa>`.
|
|
167
|
+
|
|
168
|
+
### 4. `deertools completion`
|
|
169
|
+
- Generowanie skryptów autouzupełniania komend i flag dla powłoki:
|
|
170
|
+
```bash
|
|
171
|
+
eval "$(deertools completion zsh)" # lub bash
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Slajd 10 · Podsumowanie
|
|
177
|
+
|
|
178
|
+
- **Bezpieczeństwo przede wszystkim:** Brak zewnętrznych zależności npm, bezpieczny OAuth PKCE, pliki 0600.
|
|
179
|
+
- **Standaryzacja AI:** Jedno spójne CLI i serwer MCP łączący całą infrastrukturę Deer Design i BaseLinkera.
|
|
180
|
+
- **Gotowe do użycia:** Repozytorium na GitHubie, pełny zestaw 31 testów automatycznych, CI/CD i dokumentacja.
|
|
181
|
+
|
|
182
|
+
**Repozytorium:** [https://github.com/DeerDesign/deertools-cli](https://github.com/DeerDesign/deertools-cli)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deerdesign/deertools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"files": [
|
|
36
36
|
"bin",
|
|
37
37
|
"src",
|
|
38
|
+
"docs",
|
|
38
39
|
"README.md"
|
|
39
40
|
],
|
|
40
41
|
"engines": {
|
package/src/audit.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const { c, formatTable } = require('./formatters');
|
|
6
|
+
|
|
7
|
+
function getAuditFilePath() {
|
|
8
|
+
const home = process.env.DEERTOOLS_HOME || path.join(os.homedir(), '.deertools');
|
|
9
|
+
return path.join(home, 'audit.log');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function logAudit(entry) {
|
|
13
|
+
try {
|
|
14
|
+
const file = getAuditFilePath();
|
|
15
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
16
|
+
const line = JSON.stringify({
|
|
17
|
+
ts: new Date().toISOString(),
|
|
18
|
+
...entry,
|
|
19
|
+
}) + '\n';
|
|
20
|
+
fs.appendFileSync(file, line, { mode: 0o600 });
|
|
21
|
+
} catch (_) {
|
|
22
|
+
// Non-blocking
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function cmdAudit(pos, flags) {
|
|
27
|
+
const file = getAuditFilePath();
|
|
28
|
+
if (flags.clear) {
|
|
29
|
+
if (fs.existsSync(file)) fs.unlinkSync(file);
|
|
30
|
+
process.stdout.write('✓ Historia audytu została wyczyszczona.\n');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!fs.existsSync(file)) {
|
|
35
|
+
process.stdout.write('Brak wpisów audytu (plik ~/.deertools/audit.log nie istnieje).\n');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const raw = fs.readFileSync(file, 'utf8').trim().split('\n').filter(Boolean);
|
|
40
|
+
const limit = flags.limit ? Number(flags.limit) : 25;
|
|
41
|
+
const entries = raw.slice(-limit).map(l => {
|
|
42
|
+
try { return JSON.parse(l); } catch (_) { return null; }
|
|
43
|
+
}).filter(Boolean).reverse();
|
|
44
|
+
|
|
45
|
+
if (flags.json) {
|
|
46
|
+
process.stdout.write(JSON.stringify(entries, null, 2) + '\n');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
process.stdout.write(`\n${c.bold}Historia operacji CLI i API (ostatnie ${entries.length}):${c.reset}\n`);
|
|
51
|
+
const headers = ['Czas', 'Produkt', 'Metoda', 'Ścieżka', 'Status', 'Czas (ms)'];
|
|
52
|
+
const rows = entries.map(e => [
|
|
53
|
+
e.ts ? e.ts.slice(11, 19) : '',
|
|
54
|
+
e.product || 'system',
|
|
55
|
+
e.method || 'CLI',
|
|
56
|
+
e.path || e.action || '',
|
|
57
|
+
e.status != null ? (e.status >= 200 && e.status < 300 ? `${c.green}${e.status}${c.reset}` : `${c.red}${e.status}${c.reset}`) : `${c.gray}–${c.reset}`,
|
|
58
|
+
e.duration_ms != null ? `${e.duration_ms}ms` : '',
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
process.stdout.write(formatTable(headers, rows) + '\n\n');
|
|
62
|
+
process.stdout.write(`${c.dim}Wyczyść historię: deertools audit --clear${c.reset}\n`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { logAudit, cmdAudit };
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const http = require('http');
|
|
3
|
+
const { exec } = require('child_process');
|
|
4
|
+
const { saveBaseLinkerToken } = require('../credentials');
|
|
5
|
+
const { BaseLinkerClient } = require('./client');
|
|
6
|
+
|
|
7
|
+
function openBrowser(url) {
|
|
8
|
+
const cmd = process.platform === 'win32' ? `start "" "${url}"` : process.platform === 'darwin' ? `open "${url}"` : `xdg-open "${url}"`;
|
|
9
|
+
exec(cmd, () => {});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function renderLoginPage(error = null, success = false, inventoriesCount = 0) {
|
|
13
|
+
return `<!DOCTYPE html>
|
|
14
|
+
<html lang="pl">
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="UTF-8">
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
18
|
+
<title>DeerTools · Logowanie do BaseLinker</title>
|
|
19
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
20
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
21
|
+
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
22
|
+
<style>
|
|
23
|
+
:root {
|
|
24
|
+
--bg: #0B0F17;
|
|
25
|
+
--card-bg: rgba(22, 29, 43, 0.85);
|
|
26
|
+
--card-border: rgba(255, 255, 255, 0.08);
|
|
27
|
+
--primary: #C5A880;
|
|
28
|
+
--primary-hover: #D4BA94;
|
|
29
|
+
--text: #F1F5F9;
|
|
30
|
+
--text-muted: #94A3B8;
|
|
31
|
+
--success: #10B981;
|
|
32
|
+
--danger: #EF4444;
|
|
33
|
+
}
|
|
34
|
+
* { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Plus Jakarta Sans', sans-serif; }
|
|
35
|
+
body {
|
|
36
|
+
background: var(--bg); color: var(--text); min-height: 100vh;
|
|
37
|
+
display: flex; align-items: center; justify-content: center; padding: 24px 16px;
|
|
38
|
+
}
|
|
39
|
+
.card {
|
|
40
|
+
background: var(--card-bg); border: 1px solid var(--card-border); border-radius: 20px;
|
|
41
|
+
padding: 36px; max-width: 480px; width: 100%; box-shadow: 0 24px 60px rgba(0,0,0,0.5);
|
|
42
|
+
backdrop-filter: blur(16px); text-align: left;
|
|
43
|
+
}
|
|
44
|
+
.logo-badge { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
|
|
45
|
+
.badge {
|
|
46
|
+
background: rgba(197, 168, 128, 0.15); color: var(--primary);
|
|
47
|
+
padding: 4px 10px; border-radius: 999px; font-size: 12px; font-weight: 600;
|
|
48
|
+
border: 1px solid rgba(197, 168, 128, 0.3);
|
|
49
|
+
}
|
|
50
|
+
h1 { font-size: 22px; font-weight: 700; color: #fff; margin-bottom: 8px; letter-spacing: -0.5px; }
|
|
51
|
+
p { font-size: 14px; color: var(--text-muted); line-height: 1.5; margin-bottom: 20px; }
|
|
52
|
+
.info-box {
|
|
53
|
+
background: rgba(11, 15, 23, 0.6); border: 1px solid var(--card-border);
|
|
54
|
+
border-radius: 10px; padding: 12px 14px; font-size: 13px; color: var(--text-muted); margin-bottom: 20px;
|
|
55
|
+
}
|
|
56
|
+
.info-box b { color: #fff; }
|
|
57
|
+
.alert {
|
|
58
|
+
padding: 12px 14px; border-radius: 10px; font-size: 13px; margin-bottom: 20px; font-weight: 500;
|
|
59
|
+
}
|
|
60
|
+
.alert-danger { background: rgba(239, 68, 68, 0.15); border: 1px solid rgba(239, 68, 68, 0.3); color: #FCA5A5; }
|
|
61
|
+
.alert-success { background: rgba(16, 185, 129, 0.15); border: 1px solid rgba(16, 185, 129, 0.3); color: #6EE7B7; text-align: center; }
|
|
62
|
+
.form-group { margin-bottom: 20px; }
|
|
63
|
+
label { display: block; font-size: 13px; font-weight: 600; color: var(--text); margin-bottom: 8px; }
|
|
64
|
+
input[type="password"] {
|
|
65
|
+
width: 100%; background: rgba(11, 15, 23, 0.9); border: 1px solid var(--card-border);
|
|
66
|
+
border-radius: 10px; padding: 12px 16px; color: #fff; font-size: 15px; outline: none;
|
|
67
|
+
transition: all 0.2s;
|
|
68
|
+
}
|
|
69
|
+
input[type="password"]:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(197, 168, 128, 0.2); }
|
|
70
|
+
button {
|
|
71
|
+
width: 100%; padding: 13px 20px; border-radius: 10px; font-size: 14px; font-weight: 700;
|
|
72
|
+
cursor: pointer; border: none; background: var(--primary); color: #000; transition: all 0.2s;
|
|
73
|
+
}
|
|
74
|
+
button:hover { background: var(--primary-hover); transform: translateY(-1px); }
|
|
75
|
+
</style>
|
|
76
|
+
</head>
|
|
77
|
+
<body>
|
|
78
|
+
<div class="card">
|
|
79
|
+
<div class="logo-badge">
|
|
80
|
+
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="#C5A880" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>
|
|
81
|
+
<span class="badge">DeerTools Local Auth</span>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
${success ? `
|
|
85
|
+
<div class="alert alert-success">
|
|
86
|
+
<h2 style="font-size: 18px; margin-bottom: 6px; color: #fff;">✓ Zalogowano pomyślnie!</h2>
|
|
87
|
+
<p style="margin-bottom: 8px; color: inherit;">Połączenie z BaseLinker aktywne (katalogi: <b>${inventoriesCount}</b>).</p>
|
|
88
|
+
<p style="font-size: 12px; margin-bottom: 0;">Klucz zapisany w <code>~/.deertools/credentials.json</code>.<br>Możesz zamknąć tę kartę przeglądarki.</p>
|
|
89
|
+
</div>
|
|
90
|
+
` : `
|
|
91
|
+
<h1>Logowanie do BaseLinker</h1>
|
|
92
|
+
<p>Podaj swój klucz API BaseLinkera, aby umożliwić agentom AI bezpieczny dostęp do Twojego katalogu produktów.</p>
|
|
93
|
+
|
|
94
|
+
<div class="info-box">
|
|
95
|
+
Gdzie znaleźć klucz? Zaloguj się w panelu BaseLinkera i przejdź do zakładki: <b>Konto / inne → Moje konto → API</b>.
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
${error ? `<div class="alert alert-danger">${error}</div>` : ''}
|
|
99
|
+
|
|
100
|
+
<form method="POST" action="/submit">
|
|
101
|
+
<div class="form-group">
|
|
102
|
+
<label for="token">Klucz API (X-BLToken)</label>
|
|
103
|
+
<input type="password" id="token" name="token" placeholder="Wklej token np. 3001234-45678-ABCDEF..." autofocus required>
|
|
104
|
+
</div>
|
|
105
|
+
<button type="submit">Zaloguj i połącz</button>
|
|
106
|
+
</form>
|
|
107
|
+
`}
|
|
108
|
+
</div>
|
|
109
|
+
</body>
|
|
110
|
+
</html>`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Uruchamia lokalny serwer logowania dla BaseLinkera i otwiera przeglądarkę,
|
|
115
|
+
* działając identycznie jak logowanie OAuth pozostałych produktów.
|
|
116
|
+
*/
|
|
117
|
+
async function loginBaseLinkerBrowser(opts = {}) {
|
|
118
|
+
const timeoutMs = opts.timeoutMs || 5 * 60 * 1000;
|
|
119
|
+
const open = opts.openBrowser === undefined ? openBrowser : opts.openBrowser;
|
|
120
|
+
const log = opts.log || console.log;
|
|
121
|
+
|
|
122
|
+
return new Promise((resolve, reject) => {
|
|
123
|
+
let finished = false;
|
|
124
|
+
|
|
125
|
+
const server = http.createServer(async (req, res) => {
|
|
126
|
+
const parsedUrl = new URL(req.url, `http://${req.headers.host || '127.0.0.1'}`);
|
|
127
|
+
|
|
128
|
+
if (req.method === 'GET') {
|
|
129
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
130
|
+
res.end(renderLoginPage());
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (req.method === 'POST' && parsedUrl.pathname === '/submit') {
|
|
135
|
+
let body = '';
|
|
136
|
+
req.on('data', chunk => body += chunk);
|
|
137
|
+
req.on('end', async () => {
|
|
138
|
+
try {
|
|
139
|
+
const formData = new URLSearchParams(body);
|
|
140
|
+
const token = formData.get('token')?.trim();
|
|
141
|
+
if (!token) {
|
|
142
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
143
|
+
res.end(renderLoginPage('Proszę podać token API.'));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Weryfikacja tokena z oficjalnym API BaseLinkera
|
|
148
|
+
let test;
|
|
149
|
+
if (opts.verifyToken) {
|
|
150
|
+
test = await opts.verifyToken(token);
|
|
151
|
+
} else {
|
|
152
|
+
const client = new BaseLinkerClient(token);
|
|
153
|
+
test = await client.testConnection();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!test.ok) {
|
|
157
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
158
|
+
res.end(renderLoginPage(`Błąd połączenia z BaseLinkerem: ${test.error}`));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Zapisz poświadczenia
|
|
163
|
+
saveBaseLinkerToken(token);
|
|
164
|
+
|
|
165
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
166
|
+
res.end(renderLoginPage(null, true, (test.inventories || []).length));
|
|
167
|
+
|
|
168
|
+
const entry = {
|
|
169
|
+
key: token,
|
|
170
|
+
scope: 'api catalog',
|
|
171
|
+
source: 'file',
|
|
172
|
+
inventories: test.inventories
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
setTimeout(() => done(null, entry), 500);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
178
|
+
res.end(renderLoginPage(`Wystąpił błąd: ${err.message}`));
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
res.writeHead(404);
|
|
185
|
+
res.end('Not Found');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const timer = setTimeout(() => {
|
|
189
|
+
done(new Error('Upłynął czas oczekiwania na logowanie do BaseLinker (5 min).'));
|
|
190
|
+
}, timeoutMs);
|
|
191
|
+
|
|
192
|
+
if (opts.signal) {
|
|
193
|
+
opts.signal.addEventListener('abort', () => done(new Error('Aborted')));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function done(err, entry) {
|
|
197
|
+
if (finished) return;
|
|
198
|
+
finished = true;
|
|
199
|
+
clearTimeout(timer);
|
|
200
|
+
server.close();
|
|
201
|
+
if (err) reject(err); else resolve(entry);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
server.on('error', err => done(err));
|
|
205
|
+
server.listen(opts.port || 0, '127.0.0.1', () => {
|
|
206
|
+
const port = server.address().port;
|
|
207
|
+
const loginUrl = `http://127.0.0.1:${port}/`;
|
|
208
|
+
log(`Otwieram przeglądarkę pod adresem:\n${loginUrl}\nAby się zalogować, wklej swój token API BaseLinker (X-BLToken).`);
|
|
209
|
+
if (open) open(loginUrl);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = { loginBaseLinkerBrowser };
|