@deerdesign/deertools 1.0.1 → 1.0.3

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 CHANGED
@@ -1,63 +1,172 @@
1
- # deertools — DeerTools CLI
1
+ # @deerdesign/deertools
2
2
 
3
- Logujesz się raz z laptopa, a Twój agent AI dostaje dostęp do API produktów DeerTools: **w Twoim imieniu, nie szerzej niż Ty, w ograniczonym zakresie**.
3
+ > Official DeerTools CLI: Secure, scoped authentication and AI agent access for DeerTools product APIs.
4
4
 
5
- Produkty: `kosztorys`, `katalog`, `dostawy`, `supplier`, `rendermind`. Adresy produkcyjne: Kosztorys i Katalog na `https://connect.deerdesign.pl/kosztorys/` i `/katalog/`, Dostawy `https://ship.deergroup.pl/`, Supplier Hub `https://supplier.deergroup.pl/`, RenderMind `https://rendermind.deergroup.pl/`.
5
+ Log in once from your computer, and grant your AI coding agent (Antigravity, Cursor, Claude Code, Lovable, CLI agents) access to DeerTools APIs **on your behalf, scoped strictly to your permissions, with zero credentials exposed in URLs**.
6
6
 
7
- ## Instalacja na dowolnym komputerze
7
+ ### Supported Products
8
8
 
9
- Wymaga Node.js ≥ 18. Pakiet jest opublikowany w publicznym rejestrze npm:
9
+ | Product Command Key | Polish Alias | Name | Production URL | Description |
10
+ | :--- | :--- | :--- | :--- | :--- |
11
+ | `estimate` | `kosztorys` | DeerTools Estimate | `https://connect.deerdesign.pl/kosztorys/` | Floor plan PDF analysis, kitchen area detection, interior cost estimation |
12
+ | `catalog` | `katalog` | DeerTools Catalog | `https://connect.deerdesign.pl/katalog/` | Material catalog (Smart/Modern/High Life), category specs, products |
13
+ | `deliveries` | `dostawy` | DeerTools Deliveries | `https://ship.deergroup.pl/` | Logistics, site delivery schedules, supplier shipments, tracking |
14
+ | `supplier` | — | Supplier Hub | `https://supplier.deergroup.pl/` | Supplier orders, line items, claims, delivery status |
15
+ | `rendermind` | — | RenderMind | `https://rendermind.deergroup.pl/` | pCon 3D render pipelines, AI image generation, floor plan proxies |
16
+
17
+ *(Both English names like `catalog`, `estimate`, `deliveries` and legacy Polish names like `katalog`, `kosztorys`, `dostawy` are supported interchangeably).*
18
+
19
+ ---
20
+
21
+ ## Installation
22
+
23
+ Requires **Node.js ≥ 18**. Zero runtime dependencies.
24
+
25
+ ### Option 1: Run instantly with `npx` (No installation needed)
10
26
 
11
27
  ```bash
12
- # Opcja 1: Uruchomienie bezpośrednio bez wcześniejszej instalacji:
13
- npx @deerdesign/deertools login kosztorys
28
+ npx @deerdesign/deertools login catalog
29
+ ```
14
30
 
15
- # Opcja 2: Instalacja globalna w systemie (udostępnia komendy 'deertools' oraz 'deer'):
31
+ ### Option 2: Install globally
32
+
33
+ ```bash
16
34
  npm install -g @deerdesign/deertools
35
+ ```
36
+
37
+ This installs both the `deertools` binary and the convenient short alias `deer`:
38
+
39
+ ```bash
17
40
  deertools --help
18
- # lub krócej:
41
+ # or
19
42
  deer whoami
20
43
  ```
21
44
 
22
- ## Logowanie
45
+ ---
46
+
47
+ ## Authentication (OAuth 2.1 + PKCE S256)
48
+
49
+ DeerTools CLI implements the official **OAuth 2.1 Authorization Code grant with PKCE (RFC 8252, RFC 8414)**:
23
50
 
24
51
  ```bash
25
- deertools login kosztorys # jeden produkt, zakres: tylko odczyt (read)
26
- deertools login katalog --write # odczyt i zapis
27
- deertools login all # kolejno wszystkie produkty
28
- deertools whoami # status, zakres, ważność
29
- deertools logout kosztorys
52
+ # Log in to a single product with read-only access (default):
53
+ deertools login catalog
54
+
55
+ # Log in to other products (in English or Polish):
56
+ deertools login estimate # or: deertools login kosztorys
57
+ deertools login deliveries # or: deertools login dostawy
58
+ deertools login supplier
59
+ deertools login rendermind
60
+
61
+ # Request read + write scopes:
62
+ deertools login catalog --write
63
+
64
+ # Interactive login across all 5 products sequentially:
65
+ deertools login all
66
+
67
+ # Check current authentication status and expiration:
68
+ deertools whoami
69
+
70
+ # Check status in machine-readable JSON format:
71
+ deertools whoami --json
72
+
73
+ # Revoke local credentials:
74
+ deertools logout catalog
30
75
  ```
31
76
 
32
- Jak to działa (OAuth 2.1, kod autoryzacyjny + PKCE, RFC 8252):
77
+ ### How it works under the hood
78
+
79
+ 1. **RFC 8414 Discovery**: The CLI queries `/.well-known/oauth-authorization-server` on the product host to discover authorization and token endpoints.
80
+ 2. **Loopback Server**: The CLI starts an ephemeral HTTP server on `127.0.0.1:<random-port>` and generates a cryptographically secure PKCE code verifier and S256 challenge.
81
+ 3. **Browser Consent**: The user's default browser opens to the product's `/oauth/authorize` page. The user authenticates (Microsoft SSO / SSO / Magic Link) and approves the requested scopes.
82
+ 4. **Single-Use Authorization Code**: The browser redirects to the loopback server with a short-lived authorization code (60-second TTL). No keys or tokens ever appear in query strings.
83
+ 5. **Direct Exchange**: The CLI sends a direct `POST` to the product's token endpoint (`/oauth-token`) with the code verifier.
84
+ 6. **Local Storage**: The product issues a scoped API key (`deer_<product>_live_...`) valid for 90 days. The CLI persists it in `~/.deertools/credentials.json` with strict POSIX file permissions (`0600`).
33
85
 
34
- 1. CLI uruchamia serwer na `127.0.0.1` (losowy port), otwiera przeglądarkę na stronie `…/oauth/authorize` produktu.
35
- 2. Logujesz się w produkcie (Microsoft w Kosztorysie; kod lub link z e-maila gdzie indziej) i klikasz „Zezwól na dostęp”. Możesz obniżyć zakres do samego odczytu.
36
- 3. Przeglądarka wraca do CLI wyłącznie z **jednorazowym kodem** (ważnym 60 s). Klucza API nie ma w adresie.
37
- 4. CLI wymienia kod na klucz bezpośrednio z serwerem produktu (dowód PKCE) i zapisuje go w `~/.deertools/credentials.json` z uprawnieniami `600`.
86
+ ---
38
87
 
39
- Klucz: `deer_<produkt>_live_…`, ważny 90 dni, unieważnisz go w produkcie (Ustawienia → Dostęp API). Zakres domyślny to **read**; `write` wymaga `--write` i zgody w przeglądarce. Adresy autoryzacji i wymiany kodu CLI pobiera z `…/.well-known/oauth-authorization-server` produktu (RFC 8414) i odmawia logowania, gdy produkt nie deklaruje PKCE (S256).
88
+ ## AI Agent Integration & Usage
40
89
 
41
- ## Użycie przez agenta
90
+ ### 1. Inspect Documentation & Specifications
42
91
 
43
92
  ```bash
44
- deertools docs <produkt> # llms.txt: czym jest produkt, jak się uwierzytelnić (publiczne)
45
- deertools docs <produkt> openapi # pełna specyfikacja OpenAPI
46
- deertools endpoints <produkt> # lista endpointów z zakresem (read/write)
47
- deertools api <produkt> GET /ścieżka --query k=v
48
- deertools api <produkt> POST /ścieżka --data '{"a":1}'
49
- deertools skill install [produkt|all] # instaluje skill agenta z produktu do ~/.gemini/config/skills
50
- deertools estimate --file rzut.pdf # wycena rzutu (Kosztorys)
93
+ # Read high-level llms.txt (public overview and authentication instructions):
94
+ deertools docs catalog
95
+ deertools docs rendermind
96
+
97
+ # Fetch full OpenAPI 3.0 JSON specification:
98
+ deertools docs catalog openapi
99
+
100
+ # List all available endpoints with required scopes:
101
+ deertools endpoints catalog
51
102
  ```
52
103
 
53
- `deertools api` wysyła klucz **wyłącznie** pod adres API produktu (tylko ścieżki względne) i podpowiada, co zrobić przy 401, 403 i 429. Kody wyjścia: `0` ok · `1` błąd żądania · `2` błędne użycie · `3` brak lub wygasłe logowanie · `4` brak dokumentacji lub adresu API.
104
+ ### 2. Invoke Product APIs
54
105
 
55
- Zmienne środowiskowe dla CI i agentów: `DEERTOOLS_<PRODUKT>_KEY` (np. `DEERTOOLS_KATALOG_KEY`), `DEERTOOLS_HOME` (katalog poświadczeń).
106
+ The CLI handles authorization headers automatically, rejecting any requests to non-whitelisted hosts:
56
107
 
57
- ## Rozwój i publikacja
108
+ ```bash
109
+ # Call GET endpoint:
110
+ deertools api catalog GET /api/v1/products --query category=flooring
111
+
112
+ # Call POST endpoint with JSON payload:
113
+ deertools api rendermind POST /api/v1/visualizations --data '{"prompt":"Modern Scandinavian kitchen"}'
114
+
115
+ # Read payload from file:
116
+ deertools api rendermind POST /api/v1/visualizations --data-file request.json
117
+ ```
118
+
119
+ ### 3. Specialized Helpers
120
+
121
+ ```bash
122
+ # Analyze architectural floor plan PDF and calculate cost estimate (Estimate / Kosztorys):
123
+ deertools estimate --file floorplan.pdf --kitchen 6
124
+
125
+ # Install product agent skills into ~/.gemini/config/skills:
126
+ deertools skill install catalog
127
+ deertools skill install rendermind
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Headless & CI/CD Environments
133
+
134
+ In CI pipelines or containerized agents where browser interaction is unavailable, provide API keys via environment variables:
58
135
 
59
136
  ```bash
60
- npm test # testy węzła (node --test), bez zależności
137
+ export DEERTOOLS_CATALOG_KEY="deer_catalog_live_..." # or DEERTOOLS_KATALOG_KEY
138
+ export DEERTOOLS_ESTIMATE_KEY="deer_kosztorys_live_..." # or DEERTOOLS_KOSZTORYS_KEY
139
+ export DEERTOOLS_DELIVERIES_KEY="deer_dostawy_live_..." # or DEERTOOLS_DOSTAWY_KEY
140
+ export DEERTOOLS_SUPPLIER_KEY="deer_supplier_live_..."
141
+ export DEERTOOLS_RENDERMIND_KEY="deer_rendermind_live_..."
142
+
143
+ # Custom credentials directory:
144
+ export DEERTOOLS_HOME="/custom/path/.deertools"
61
145
  ```
62
146
 
63
- Publikacja (GitHub Packages): zawartość tego katalogu jest korzeniem repozytorium `DeerDesign/deertools-cli`. Zwiększ wersję w `package.json`, zatwierdź i wypchnij tag `v<wersja>`; workflow `.github/workflows/publish-package.yml` uruchomi testy, sprawdzi zgodność tagu z wersją i opublikuje pakiet. Ręcznie: `npm login --scope=@deerdesign --registry=https://npm.pkg.github.com`, potem `npm publish` (token z `write:packages`).
147
+ ---
148
+
149
+ ## Exit Codes
150
+
151
+ | Code | Meaning |
152
+ | :--- | :--- |
153
+ | `0` | Success |
154
+ | `1` | Request or API failure (e.g. HTTP 4xx, 5xx) |
155
+ | `2` | Invalid CLI arguments or usage syntax |
156
+ | `3` | Missing or expired credentials (run `deertools login`) |
157
+ | `4` | Missing OpenAPI spec, documentation, or host resolution error |
158
+
159
+ ---
160
+
161
+ ## Development & Testing
162
+
163
+ DeerTools CLI is written in pure, dependency-free modern Node.js using built-in modules (`node:crypto`, `node:http`, `node:fs`, `node:test`).
164
+
165
+ ```bash
166
+ # Run test suite:
167
+ npm test
168
+ ```
169
+
170
+ ## License
171
+
172
+ MIT © Deer Design AI Team
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deerdesign/deertools",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -37,20 +37,39 @@ function saveProduct(productId, entry) {
37
37
 
38
38
  function removeProduct(productId) {
39
39
  const data = readAll();
40
- const existed = Boolean(data.products[productId]);
41
- delete data.products[productId];
40
+ const ids = [productId];
41
+ try {
42
+ const { getProduct } = require('./registry');
43
+ const p = getProduct(productId);
44
+ if (p.aliases) ids.push(...p.aliases);
45
+ } catch (_) {}
46
+ let existed = false;
47
+ for (const id of ids) {
48
+ if (data.products[id]) {
49
+ existed = true;
50
+ delete data.products[id];
51
+ }
52
+ }
42
53
  writeAll(data);
43
54
  return existed;
44
55
  }
45
56
 
46
57
  /** Klucz z zmiennej środowiskowej ma pierwszeństwo (CI, agenty); zwraca też źródło. */
47
58
  function getKey(product) {
48
- const envName = `DEERTOOLS_${product.id.toUpperCase()}_KEY`;
49
- const fromEnv = process.env[envName] || (process.env.DEER_API_KEY && process.env.DEER_API_KEY.startsWith(product.keyPrefix) ? process.env.DEER_API_KEY : '');
50
- if (fromEnv) return { key: fromEnv, source: `env:${envName}`, entry: null };
51
- const entry = readAll().products[product.id];
52
- if (!entry || !entry.key) return null;
53
- return { key: entry.key, source: 'file', entry };
59
+ const idsToCheck = [product.id, ...(product.aliases || [])];
60
+ for (const pid of idsToCheck) {
61
+ const envName = `DEERTOOLS_${pid.toUpperCase()}_KEY`;
62
+ if (process.env[envName]) return { key: process.env[envName], source: `env:${envName}`, entry: null };
63
+ }
64
+ if (process.env.DEER_API_KEY && process.env.DEER_API_KEY.startsWith(product.keyPrefix)) {
65
+ return { key: process.env.DEER_API_KEY, source: 'env:DEER_API_KEY', entry: null };
66
+ }
67
+ const products = readAll().products;
68
+ for (const pid of idsToCheck) {
69
+ const entry = products[pid];
70
+ if (entry && entry.key) return { key: entry.key, source: 'file', entry };
71
+ }
72
+ return null;
54
73
  }
55
74
 
56
75
  function expiryInfo(entry) {
package/src/registry.js CHANGED
@@ -1,53 +1,85 @@
1
1
  'use strict';
2
2
  /**
3
- * Rejestr produktów DeerTools. Każdy produkt ma własny adres autoryzacji, własny prefiks klucza
4
- * i własne API. Adres bazowy API i lista endpointów pochodzą z publicznego openapi.json produktu
5
- * (wykrywanego z llms.txt), więc CLI nie zna endpointów na sztywno.
3
+ * DeerTools Product Registry.
4
+ *
5
+ * Primary CLI product identifiers are in English:
6
+ * - estimate (Polish alias: kosztorys)
7
+ * - catalog (Polish alias: katalog)
8
+ * - deliveries (Polish alias: dostawy)
9
+ * - supplier
10
+ * - rendermind
6
11
  */
7
12
  const PRODUCTS = {
8
- kosztorys: {
9
- name: 'Kosztorys',
13
+ estimate: {
14
+ name: 'Estimate',
15
+ localName: 'Kosztorys',
10
16
  site: 'https://connect.deerdesign.pl/kosztorys',
11
17
  authorizeUrl: 'https://connect.deerdesign.pl/kosztorys/oauth/authorize',
18
+ tokenUrl: 'https://vlemuxlubrowuuxpgllk.supabase.co/functions/v1/oauth-token',
12
19
  keyPrefix: 'deer_kosztorys_live_',
13
20
  fallbackApiBase: 'https://vlemuxlubrowuuxpgllk.supabase.co/functions/v1',
21
+ aliases: ['kosztorys', 'costs', 'cost'],
14
22
  },
15
- katalog: {
16
- name: 'Katalog Connect',
23
+ catalog: {
24
+ name: 'Catalog',
25
+ localName: 'Katalog Connect',
17
26
  site: 'https://connect.deerdesign.pl/katalog',
18
27
  authorizeUrl: 'https://connect.deerdesign.pl/katalog/oauth/authorize',
28
+ tokenUrl: 'https://ftzaobeixglnssemugjf.supabase.co/functions/v1/oauth-token',
19
29
  keyPrefix: 'deer_catalog_live_',
20
- fallbackApiBase: null,
30
+ fallbackApiBase: 'https://ftzaobeixglnssemugjf.supabase.co/functions/v1/api-gateway',
31
+ aliases: ['katalog'],
21
32
  },
22
- dostawy: {
23
- name: 'Dostawy',
33
+ deliveries: {
34
+ name: 'Deliveries',
35
+ localName: 'Dostawy',
24
36
  site: 'https://ship.deergroup.pl',
25
37
  authorizeUrl: 'https://ship.deergroup.pl/oauth/authorize',
38
+ tokenUrl: 'https://ddyfbvogkumvscckotvb.supabase.co/functions/v1/oauth-token',
26
39
  keyPrefix: 'deer_dostawy_live_',
27
- fallbackApiBase: null,
40
+ fallbackApiBase: 'https://ddyfbvogkumvscckotvb.supabase.co/functions/v1',
41
+ aliases: ['dostawy', 'shipping', 'delivery', 'ship'],
28
42
  },
29
43
  supplier: {
30
44
  name: 'Supplier Hub',
45
+ localName: 'Supplier Hub',
31
46
  site: 'https://supplier.deergroup.pl',
32
47
  authorizeUrl: 'https://supplier.deergroup.pl/oauth/authorize',
48
+ tokenUrl: 'https://hubnxvzxdgocbtmkfdsz.supabase.co/functions/v1/oauth-token',
33
49
  keyPrefix: 'deer_supplier_live_',
34
- fallbackApiBase: null,
50
+ fallbackApiBase: 'https://hubnxvzxdgocbtmkfdsz.supabase.co/functions/v1/public-api',
51
+ aliases: [],
35
52
  },
36
53
  rendermind: {
37
54
  name: 'RenderMind',
55
+ localName: 'RenderMind',
38
56
  site: 'https://rendermind.deergroup.pl',
39
57
  authorizeUrl: 'https://rendermind.deergroup.pl/oauth/authorize',
58
+ tokenUrl: 'https://lkljsuptgyktkwojnwbu.supabase.co/functions/v1/oauth-token',
40
59
  keyPrefix: 'deer_rendermind_live_',
41
- fallbackApiBase: null,
60
+ fallbackApiBase: 'https://lkljsuptgyktkwojnwbu.supabase.co/functions/v1/rendermind-api',
61
+ aliases: [],
42
62
  },
43
63
  };
44
64
 
65
+ const ALIASES = {
66
+ kosztorys: 'estimate',
67
+ costs: 'estimate',
68
+ cost: 'estimate',
69
+ katalog: 'catalog',
70
+ dostawy: 'deliveries',
71
+ shipping: 'deliveries',
72
+ delivery: 'deliveries',
73
+ ship: 'deliveries',
74
+ };
75
+
45
76
  const ALL_IDS = Object.keys(PRODUCTS);
46
77
 
47
78
  function getProduct(id) {
48
- const key = String(id || '').toLowerCase();
79
+ let key = String(id || '').toLowerCase().trim();
80
+ if (ALIASES[key]) key = ALIASES[key];
49
81
  if (!PRODUCTS[key]) {
50
- const err = new Error(`Nieznany produkt „${id}”. Dostępne: ${ALL_IDS.join(', ')}.`);
82
+ const err = new Error(`Nieznany produkt „${id}”. Dostępne: ${ALL_IDS.join(', ')} (lub po polsku: kosztorys, katalog, dostawy).`);
51
83
  err.exitCode = 2;
52
84
  throw err;
53
85
  }
@@ -59,4 +91,4 @@ function resolveProducts(arg) {
59
91
  return String(arg).split(',').map((s) => getProduct(s.trim()));
60
92
  }
61
93
 
62
- module.exports = { PRODUCTS, ALL_IDS, getProduct, resolveProducts };
94
+ module.exports = { PRODUCTS, ALIASES, ALL_IDS, getProduct, resolveProducts };