@deerdesign/deertools 1.0.2 → 1.0.4
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 +40 -20
- package/package.json +1 -1
- package/src/agent-detector.js +160 -0
- package/src/commands.js +22 -8
- package/src/credentials.js +27 -8
- package/src/registry.js +48 -16
package/README.md
CHANGED
|
@@ -6,13 +6,15 @@ Log in once from your computer, and grant your AI coding agent (Antigravity, Cur
|
|
|
6
6
|
|
|
7
7
|
### Supported Products
|
|
8
8
|
|
|
9
|
-
| Product Key | Name | Production URL | Description |
|
|
10
|
-
| :--- | :--- | :--- | :--- |
|
|
11
|
-
| `kosztorys` | DeerTools
|
|
12
|
-
| `katalog` | DeerTools
|
|
13
|
-
| `dostawy` | DeerTools
|
|
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 |
|
|
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).*
|
|
16
18
|
|
|
17
19
|
---
|
|
18
20
|
|
|
@@ -23,7 +25,7 @@ Requires **Node.js ≥ 18**. Zero runtime dependencies.
|
|
|
23
25
|
### Option 1: Run instantly with `npx` (No installation needed)
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
|
-
npx @deerdesign/deertools login
|
|
28
|
+
npx @deerdesign/deertools login catalog
|
|
27
29
|
```
|
|
28
30
|
|
|
29
31
|
### Option 2: Install globally
|
|
@@ -48,10 +50,16 @@ DeerTools CLI implements the official **OAuth 2.1 Authorization Code grant with
|
|
|
48
50
|
|
|
49
51
|
```bash
|
|
50
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
|
|
51
59
|
deertools login rendermind
|
|
52
60
|
|
|
53
61
|
# Request read + write scopes:
|
|
54
|
-
deertools login
|
|
62
|
+
deertools login catalog --write
|
|
55
63
|
|
|
56
64
|
# Interactive login across all 5 products sequentially:
|
|
57
65
|
deertools login all
|
|
@@ -63,14 +71,14 @@ deertools whoami
|
|
|
63
71
|
deertools whoami --json
|
|
64
72
|
|
|
65
73
|
# Revoke local credentials:
|
|
66
|
-
deertools logout
|
|
74
|
+
deertools logout catalog
|
|
67
75
|
```
|
|
68
76
|
|
|
69
77
|
### How it works under the hood
|
|
70
78
|
|
|
71
79
|
1. **RFC 8414 Discovery**: The CLI queries `/.well-known/oauth-authorization-server` on the product host to discover authorization and token endpoints.
|
|
72
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.
|
|
73
|
-
3. **Browser Consent**: The user's default browser opens to the product's `/oauth/authorize` page. The user authenticates (SSO /
|
|
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.
|
|
74
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.
|
|
75
83
|
5. **Direct Exchange**: The CLI sends a direct `POST` to the product's token endpoint (`/oauth-token`) with the code verifier.
|
|
76
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`).
|
|
@@ -83,13 +91,14 @@ deertools logout rendermind
|
|
|
83
91
|
|
|
84
92
|
```bash
|
|
85
93
|
# Read high-level llms.txt (public overview and authentication instructions):
|
|
94
|
+
deertools docs catalog
|
|
86
95
|
deertools docs rendermind
|
|
87
96
|
|
|
88
97
|
# Fetch full OpenAPI 3.0 JSON specification:
|
|
89
|
-
deertools docs
|
|
98
|
+
deertools docs catalog openapi
|
|
90
99
|
|
|
91
100
|
# List all available endpoints with required scopes:
|
|
92
|
-
deertools endpoints
|
|
101
|
+
deertools endpoints catalog
|
|
93
102
|
```
|
|
94
103
|
|
|
95
104
|
### 2. Invoke Product APIs
|
|
@@ -98,7 +107,7 @@ The CLI handles authorization headers automatically, rejecting any requests to n
|
|
|
98
107
|
|
|
99
108
|
```bash
|
|
100
109
|
# Call GET endpoint:
|
|
101
|
-
deertools api
|
|
110
|
+
deertools api catalog GET /api/v1/products --query category=flooring
|
|
102
111
|
|
|
103
112
|
# Call POST endpoint with JSON payload:
|
|
104
113
|
deertools api rendermind POST /api/v1/visualizations --data '{"prompt":"Modern Scandinavian kitchen"}'
|
|
@@ -110,11 +119,22 @@ deertools api rendermind POST /api/v1/visualizations --data-file request.json
|
|
|
110
119
|
### 3. Specialized Helpers
|
|
111
120
|
|
|
112
121
|
```bash
|
|
113
|
-
# Analyze architectural floor plan PDF and calculate cost estimate (Kosztorys):
|
|
122
|
+
# Analyze architectural floor plan PDF and calculate cost estimate (Estimate / Kosztorys):
|
|
114
123
|
deertools estimate --file floorplan.pdf --kitchen 6
|
|
115
124
|
|
|
116
|
-
#
|
|
117
|
-
deertools skill install
|
|
125
|
+
# Auto-detect installed AI agents (Antigravity, Claude, Codex, Hermes, Cursor) and install skills:
|
|
126
|
+
deertools skill install catalog
|
|
127
|
+
deertools skill install all
|
|
128
|
+
|
|
129
|
+
# Install for a specific agent platform:
|
|
130
|
+
deertools skill install all --agent claude # ~/.claude/skills/
|
|
131
|
+
deertools skill install all --agent codex # ~/.codex/skills/
|
|
132
|
+
deertools skill install all --agent hermes # ~/.hermes/skills/
|
|
133
|
+
deertools skill install all --agent cursor # .cursor/rules/*.mdc
|
|
134
|
+
deertools skill install all --agent antigravity # ~/.gemini/config/skills/
|
|
135
|
+
|
|
136
|
+
# Force install across all supported platforms simultaneously:
|
|
137
|
+
deertools skill install all --all-agents
|
|
118
138
|
```
|
|
119
139
|
|
|
120
140
|
---
|
|
@@ -124,9 +144,9 @@ deertools skill install rendermind
|
|
|
124
144
|
In CI pipelines or containerized agents where browser interaction is unavailable, provide API keys via environment variables:
|
|
125
145
|
|
|
126
146
|
```bash
|
|
127
|
-
export
|
|
128
|
-
export
|
|
129
|
-
export
|
|
147
|
+
export DEERTOOLS_CATALOG_KEY="deer_catalog_live_..." # or DEERTOOLS_KATALOG_KEY
|
|
148
|
+
export DEERTOOLS_ESTIMATE_KEY="deer_kosztorys_live_..." # or DEERTOOLS_KOSZTORYS_KEY
|
|
149
|
+
export DEERTOOLS_DELIVERIES_KEY="deer_dostawy_live_..." # or DEERTOOLS_DOSTAWY_KEY
|
|
130
150
|
export DEERTOOLS_SUPPLIER_KEY="deer_supplier_live_..."
|
|
131
151
|
export DEERTOOLS_RENDERMIND_KEY="deer_rendermind_live_..."
|
|
132
152
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Rejestr obsługiwanych platform agentów AI.
|
|
8
|
+
* Obsługuje: Google Antigravity / Gemini, Claude Code, OpenAI Codex, Hermes AI,
|
|
9
|
+
* Cursor IDE (.mdc), Windsurf / Codeium oraz lokalne projekty (.agents/skills).
|
|
10
|
+
*/
|
|
11
|
+
const KNOWN_AGENTS = [
|
|
12
|
+
{
|
|
13
|
+
id: 'antigravity',
|
|
14
|
+
name: 'Google Antigravity / Gemini',
|
|
15
|
+
detect: (home) => fs.existsSync(path.join(home, '.gemini')) || fs.existsSync(path.join(home, '.antigravity')),
|
|
16
|
+
getTarget: (home) => path.join(home, '.gemini', 'config', 'skills'),
|
|
17
|
+
format: 'skill_md',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'claude',
|
|
21
|
+
name: 'Claude Code',
|
|
22
|
+
detect: (home) => fs.existsSync(path.join(home, '.claude')),
|
|
23
|
+
getTarget: (home) => path.join(home, '.claude', 'skills'),
|
|
24
|
+
format: 'skill_md',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'codex',
|
|
28
|
+
name: 'OpenAI Codex',
|
|
29
|
+
detect: (home) => fs.existsSync(path.join(home, '.codex')),
|
|
30
|
+
getTarget: (home) => path.join(home, '.codex', 'skills'),
|
|
31
|
+
format: 'skill_md',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'hermes',
|
|
35
|
+
name: 'Hermes AI',
|
|
36
|
+
detect: (home) => fs.existsSync(path.join(home, '.hermes')) || fs.existsSync(path.join(home, '.config', 'hermes')) || Boolean(process.env.HERMES_HOME),
|
|
37
|
+
getTarget: (home) => process.env.HERMES_SKILLS_DIR || path.join(home, '.hermes', 'skills'),
|
|
38
|
+
format: 'skill_md',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'cursor',
|
|
42
|
+
name: 'Cursor IDE',
|
|
43
|
+
detect: (home, cwd) => fs.existsSync(path.join(cwd, '.cursor')) || fs.existsSync(path.join(home, '.cursor')),
|
|
44
|
+
getTarget: (home, cwd) => fs.existsSync(path.join(cwd, '.cursor')) ? path.join(cwd, '.cursor', 'rules') : path.join(home, '.cursor', 'rules'),
|
|
45
|
+
format: 'cursor_mdc',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'windsurf',
|
|
49
|
+
name: 'Windsurf / Codeium',
|
|
50
|
+
detect: (home, cwd) => fs.existsSync(path.join(cwd, '.windsurfrules')) || fs.existsSync(path.join(cwd, '.windsurf')) || fs.existsSync(path.join(home, '.codeium')),
|
|
51
|
+
getTarget: (home, cwd) => path.join(cwd, '.windsurf', 'rules'),
|
|
52
|
+
format: 'skill_md',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'project',
|
|
56
|
+
name: 'Bieżący projekt (.agents/skills)',
|
|
57
|
+
detect: (_home, cwd) => fs.existsSync(path.join(cwd, '.agents')),
|
|
58
|
+
getTarget: (_home, cwd) => path.join(cwd, '.agents', 'skills'),
|
|
59
|
+
format: 'skill_md',
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Wykrywa zainstalowane środowiska agentów lub filtruje według flagi --agent.
|
|
65
|
+
*/
|
|
66
|
+
function detectAgents(options = {}) {
|
|
67
|
+
const home = os.homedir();
|
|
68
|
+
const cwd = process.cwd();
|
|
69
|
+
|
|
70
|
+
// Wskazany bezpośredni katalog instalacji (--dir)
|
|
71
|
+
if (options.dir) {
|
|
72
|
+
return [{
|
|
73
|
+
id: 'custom',
|
|
74
|
+
name: 'Wskazany katalog (--dir)',
|
|
75
|
+
targetDir: path.resolve(options.dir),
|
|
76
|
+
format: 'skill_md',
|
|
77
|
+
}];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Wymuszenie instalacji do WSZYSTKICH obsługiwanych platform (--all-agents)
|
|
81
|
+
if (options['all-agents']) {
|
|
82
|
+
return KNOWN_AGENTS.map((spec) => ({
|
|
83
|
+
id: spec.id,
|
|
84
|
+
name: spec.name,
|
|
85
|
+
targetDir: spec.getTarget(home, cwd),
|
|
86
|
+
format: spec.format,
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Wskazanie konkretnego agenta (--agent claude / --agent codex / --agent hermes / etc.)
|
|
91
|
+
if (options.agent && options.agent !== 'all' && options.agent !== 'auto') {
|
|
92
|
+
const requested = options.agent.toLowerCase().trim();
|
|
93
|
+
const spec = KNOWN_AGENTS.find((a) => a.id === requested);
|
|
94
|
+
if (spec) {
|
|
95
|
+
return [{
|
|
96
|
+
id: spec.id,
|
|
97
|
+
name: spec.name,
|
|
98
|
+
targetDir: spec.getTarget(home, cwd),
|
|
99
|
+
format: spec.format,
|
|
100
|
+
}];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Automatyczne wykrywanie obecnych agentów
|
|
105
|
+
const matched = [];
|
|
106
|
+
for (const spec of KNOWN_AGENTS) {
|
|
107
|
+
try {
|
|
108
|
+
if (spec.detect(home, cwd)) {
|
|
109
|
+
matched.push({
|
|
110
|
+
id: spec.id,
|
|
111
|
+
name: spec.name,
|
|
112
|
+
targetDir: spec.getTarget(home, cwd),
|
|
113
|
+
format: spec.format,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
} catch (_) { /* ignore */ }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Fallback: jeśli nic nie wykryto, domyślnie Antigravity
|
|
120
|
+
if (matched.length === 0) {
|
|
121
|
+
matched.push({
|
|
122
|
+
id: 'antigravity',
|
|
123
|
+
name: 'Google Antigravity (~/.gemini)',
|
|
124
|
+
targetDir: path.join(home, '.gemini', 'config', 'skills'),
|
|
125
|
+
format: 'skill_md',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return matched;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Zapisuje zawartość skilla we właściwym formacie dla danej platformy:
|
|
134
|
+
* - 'skill_md': <targetDir>/<skillName>/SKILL.md (Antigravity, Claude, Codex, Hermes, Project)
|
|
135
|
+
* - 'cursor_mdc': <targetDir>/<skillName>.mdc (Cursor)
|
|
136
|
+
*/
|
|
137
|
+
function installSkillContent(agent, skillName, rawMarkdown) {
|
|
138
|
+
fs.mkdirSync(agent.targetDir, { recursive: true });
|
|
139
|
+
|
|
140
|
+
if (agent.format === 'cursor_mdc') {
|
|
141
|
+
const filePath = path.join(agent.targetDir, `${skillName}.mdc`);
|
|
142
|
+
let content = rawMarkdown;
|
|
143
|
+
if (!rawMarkdown.startsWith('---')) {
|
|
144
|
+
let desc = `DeerTools ${skillName} skill`;
|
|
145
|
+
const descMatch = rawMarkdown.match(/description:\s*([^\n]+)/);
|
|
146
|
+
if (descMatch) desc = descMatch[1].trim();
|
|
147
|
+
content = `---\ndescription: ${desc}\nglobs: *\nalwaysApply: false\n---\n\n${rawMarkdown}`;
|
|
148
|
+
}
|
|
149
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
150
|
+
return filePath;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const skillFolder = path.join(agent.targetDir, skillName);
|
|
154
|
+
fs.mkdirSync(skillFolder, { recursive: true });
|
|
155
|
+
const filePath = path.join(skillFolder, 'SKILL.md');
|
|
156
|
+
fs.writeFileSync(filePath, rawMarkdown, 'utf8');
|
|
157
|
+
return filePath;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = { KNOWN_AGENTS, detectAgents, installSkillContent };
|
package/src/commands.js
CHANGED
|
@@ -161,20 +161,34 @@ async function cmdEstimate(pos, flags) {
|
|
|
161
161
|
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
162
|
}
|
|
163
163
|
|
|
164
|
+
const { detectAgents, installSkillContent } = require('./agent-detector');
|
|
165
|
+
|
|
164
166
|
async function cmdSkill(pos, flags) {
|
|
165
|
-
if ((pos[0] || 'install') !== 'install') fail('Użycie: deertools skill install [produkt|all] [--dir <katalog
|
|
167
|
+
if ((pos[0] || 'install') !== 'install') fail('Użycie: deertools skill install [produkt|all] [--dir <katalog>] [--agent <antigravity|claude|cursor|project|all>]', 2);
|
|
166
168
|
const products = resolveProducts(pos[1]);
|
|
167
|
-
const
|
|
169
|
+
const agents = detectAgents({ dir: flags.dir, agent: flags.agent });
|
|
170
|
+
|
|
171
|
+
if (agents.length > 1 && !flags.json) {
|
|
172
|
+
out(`Wykryte środowiska agentów AI (${agents.length}):`);
|
|
173
|
+
for (const a of agents) out(` • ${a.name} → ${a.targetDir}`);
|
|
174
|
+
out('');
|
|
175
|
+
}
|
|
176
|
+
|
|
168
177
|
let installed = 0;
|
|
169
178
|
for (const product of products) {
|
|
170
179
|
const r = await fetchText(`${product.site}/skill/SKILL.md`);
|
|
171
180
|
if (!r.ok) { err(`– ${product.name}: brak skilla (${product.site}/skill/SKILL.md → ${r.status})`); continue; }
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
181
|
+
const skillName = parseFrontmatterName(r.text) || `deer-${product.id}`;
|
|
182
|
+
|
|
183
|
+
for (const agent of agents) {
|
|
184
|
+
try {
|
|
185
|
+
const dest = installSkillContent(agent, skillName, r.text);
|
|
186
|
+
out(`✓ [${agent.id}] ${product.name}: ${dest}`);
|
|
187
|
+
installed += 1;
|
|
188
|
+
} catch (e) {
|
|
189
|
+
err(`✗ [${agent.id}] ${product.name}: ${e.message}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
178
192
|
}
|
|
179
193
|
if (!installed) fail('Nie zainstalowano żadnego skilla.', 4);
|
|
180
194
|
}
|
package/src/credentials.js
CHANGED
|
@@ -37,20 +37,39 @@ function saveProduct(productId, entry) {
|
|
|
37
37
|
|
|
38
38
|
function removeProduct(productId) {
|
|
39
39
|
const data = readAll();
|
|
40
|
-
const
|
|
41
|
-
|
|
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
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
|
|
9
|
-
name: '
|
|
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
|
-
|
|
16
|
-
name: '
|
|
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:
|
|
30
|
+
fallbackApiBase: 'https://ftzaobeixglnssemugjf.supabase.co/functions/v1/api-gateway',
|
|
31
|
+
aliases: ['katalog'],
|
|
21
32
|
},
|
|
22
|
-
|
|
23
|
-
name: '
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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 };
|