@deerdesign/deertools 1.2.32 → 1.2.34
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 +93 -2
- package/package.json +1 -1
- package/src/baselinker/cache.js +306 -0
- package/src/baselinker/catalog-tools.js +82 -12
- package/src/commands.js +140 -54
- package/src/doctor.js +5 -1
package/README.md
CHANGED
|
@@ -179,12 +179,103 @@ deertools team create "Interior Designers" --slug designers # Create new organiz
|
|
|
179
179
|
|
|
180
180
|
# 9. AI Agent MCP Registry & Configuration:
|
|
181
181
|
deertools mcp list # List available MCP servers across the ecosystem
|
|
182
|
-
deertools mcp config #
|
|
183
|
-
deertools mcp install # Automatically write MCP config
|
|
182
|
+
deertools mcp config # Print JSON MCP configuration for manual pasting
|
|
183
|
+
deertools mcp install # Automatically write & update MCP config across ALL 7 agent targets
|
|
184
|
+
deertools doctor # Verify MCP connectivity, auth tokens and client config files
|
|
184
185
|
```
|
|
185
186
|
|
|
186
187
|
---
|
|
187
188
|
|
|
189
|
+
## AI Agent MCP Configuration & Tooling (Cursor, Antigravity, Claude)
|
|
190
|
+
|
|
191
|
+
`@deerdesign/deertools` provides 7 official MCP servers covering the complete Deer Design ecosystem:
|
|
192
|
+
1. `deer-estimate`: Floor plan PDF analysis, kitchen detection, interior cost calculations.
|
|
193
|
+
2. `deer-catalog`: 17k+ finishing materials (Smart/Modern/High Life), technical specs, filtering.
|
|
194
|
+
3. `deer-deliveries`: Site delivery tracking, supplier shipments, warehouse WZ generation.
|
|
195
|
+
4. `deer-supplier`: Supplier orders, purchase discounts, sales markups, commercial terms.
|
|
196
|
+
5. `deer-rendermind`: Interior rendering, pCon CAD visualization, product photorealistic renders.
|
|
197
|
+
6. `deer-baselinker`: Full BaseLinker catalog CRUD, pricing batch updates, Smart Cache.
|
|
198
|
+
7. `deer-skills`: DeerSkills Hub registry, skill search, download, and review pipelines.
|
|
199
|
+
|
|
200
|
+
### 1. Automatic Zero-Config Setup (Recommended)
|
|
201
|
+
|
|
202
|
+
You don't need to manually edit JSON files. Simply log in or run:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
deertools login all # Automatically logs in and syncs MCP configs across all clients
|
|
206
|
+
# or at any time:
|
|
207
|
+
deertools mcp install # Re-generates and writes configuration to all 7 targets
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`deertools mcp install` automatically discovers and writes to all 7 supported AI environments:
|
|
211
|
+
|
|
212
|
+
| Target Environment | Configuration Path | Supported Tools |
|
|
213
|
+
| :--- | :--- | :--- |
|
|
214
|
+
| **Workspace Standard** | `<project-root>/.mcp.json` | Claude Code, Claude Desktop Cowork, Windsurf, Zed |
|
|
215
|
+
| **Claude Desktop (macOS)** | `~/Library/Application Support/Claude/claude_desktop_config.json` | Claude Desktop native MCP |
|
|
216
|
+
| **Claude Code CLI** | `~/.claude.json` | Claude Code terminal assistant |
|
|
217
|
+
| **Cursor IDE (Global)** | `~/.cursor/mcp.json` | Cursor Agent & Composer |
|
|
218
|
+
| **Cursor Workspace** | `<project-root>/.cursor/mcp.json` | Cursor project-scoped |
|
|
219
|
+
| **Antigravity Workspace** | `<project-root>/.agents/mcp_config.json` | Google Antigravity / Gemini Agent |
|
|
220
|
+
| **Antigravity Global** | `~/.gemini/config/mcp_config.json` | Google Antigravity global profile |
|
|
221
|
+
|
|
222
|
+
Verify all clients at any time with:
|
|
223
|
+
```bash
|
|
224
|
+
deertools doctor
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
### 2. Manual Configuration & Claude Integration Details
|
|
230
|
+
|
|
231
|
+
If configuring manually or adding to existing setups:
|
|
232
|
+
|
|
233
|
+
#### A. Project-level `.mcp.json` (Recommended for Claude Code & Cowork)
|
|
234
|
+
Create or edit `.mcp.json` in your project root:
|
|
235
|
+
|
|
236
|
+
```json
|
|
237
|
+
{
|
|
238
|
+
"mcpServers": {
|
|
239
|
+
"deer-rendermind": {
|
|
240
|
+
"command": "/Users/<your-user>/.local/bin/deertools",
|
|
241
|
+
"args": ["rendermind", "mcp"],
|
|
242
|
+
"env": {
|
|
243
|
+
"PATH": "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/<your-user>/.local/bin"
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
"deer-baselinker": {
|
|
247
|
+
"command": "/Users/<your-user>/.local/bin/deertools",
|
|
248
|
+
"args": ["baselinker", "mcp"],
|
|
249
|
+
"env": {
|
|
250
|
+
"PATH": "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/<your-user>/.local/bin"
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
"deer-catalog": {
|
|
254
|
+
"url": "https://ftzaobeixglnssemugjf.supabase.co/functions/v1/catalog-mcp",
|
|
255
|
+
"headers": {
|
|
256
|
+
"x-api-key": "<your-catalog-key>",
|
|
257
|
+
"Authorization": "Bearer <your-catalog-key>"
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
"deer-estimate": {
|
|
261
|
+
"url": "https://vlemuxlubrowuuxpgllk.supabase.co/functions/v1/estimate-mcp",
|
|
262
|
+
"headers": {
|
|
263
|
+
"x-api-key": "<your-estimate-key>",
|
|
264
|
+
"Authorization": "Bearer <your-estimate-key>"
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
> [!IMPORTANT]
|
|
272
|
+
> **Key Rules for Claude Desktop & Claude Code:**
|
|
273
|
+
> 1. **PATH Inheritance on macOS**: Desktop GUI applications (like Claude Desktop) do NOT inherit your terminal's shell `$PATH` (`~/.local/bin`, `/usr/local/bin`). Always specify the absolute binary path (e.g. `~/.local/bin/deertools`) and include the `env.PATH` block as shown above so `node` and child processes can be spawned without errors.
|
|
274
|
+
> 2. **Project Trust Dialog**: When opening a workspace with a project-level `.mcp.json`, Claude displays a security consent prompt (*"Allow MCP servers from .mcp.json?"*). You must click **Allow / Always Allow** to activate the tools.
|
|
275
|
+
> 3. **Session Restart Required**: MCP server tools are registered strictly when a conversation session is initialized. If you configure MCP while a session is already active, start a **New Chat (`Cmd + N`)** or restart the Claude application (`Cmd + Q`). MCP tools cannot be hot-reloaded into an ongoing conversation.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
188
279
|
## BaseLinker MCP Proxy & Catalog Management
|
|
189
280
|
|
|
190
281
|
`@deerdesign/deertools` includes a built-in, local **BaseLinker MCP Proxy & Hub** providing deep catalog management:
|
package/package.json
CHANGED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const { getClient } = require('./client');
|
|
6
|
+
|
|
7
|
+
const CACHE_DIR = path.join(os.homedir(), '.deertools', 'cache');
|
|
8
|
+
const TTL_HOURS = 24;
|
|
9
|
+
|
|
10
|
+
// Zdefiniowane stałe katalogu Kosztorys Deer Design
|
|
11
|
+
const DEER_KOSZTORYS_CATALOG_ID = 81186;
|
|
12
|
+
const ROOT_CAT_USLUGI = 5578342;
|
|
13
|
+
const ROOT_CAT_PRODUKTY = 8769388;
|
|
14
|
+
const DEFAULT_PRICE_GROUP_ID = '49162'; // Domyślna grupa cenowa (PLN brutto)
|
|
15
|
+
|
|
16
|
+
function ensureCacheDir() {
|
|
17
|
+
if (!fs.existsSync(CACHE_DIR)) {
|
|
18
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true, mode: 0o700 });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getCachePath(inventoryId) {
|
|
23
|
+
ensureCacheDir();
|
|
24
|
+
return path.join(CACHE_DIR, `inventory_${inventoryId}.json`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getMetadataPath(inventoryId) {
|
|
28
|
+
ensureCacheDir();
|
|
29
|
+
return path.join(CACHE_DIR, `inventory_${inventoryId}.meta.json`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let inMemoryCache = {};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Wczytuje cache dla danego katalogu (z pamięci podręcznej lub z dysku).
|
|
36
|
+
*/
|
|
37
|
+
function loadCache(inventoryId = DEER_KOSZTORYS_CATALOG_ID) {
|
|
38
|
+
const invId = Number(inventoryId) || DEER_KOSZTORYS_CATALOG_ID;
|
|
39
|
+
if (inMemoryCache[invId]) {
|
|
40
|
+
return inMemoryCache[invId];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const filePath = getCachePath(invId);
|
|
44
|
+
if (!fs.existsSync(filePath)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
50
|
+
const data = JSON.parse(raw);
|
|
51
|
+
inMemoryCache[invId] = data;
|
|
52
|
+
return data;
|
|
53
|
+
} catch (err) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Zapisuje cache na dysk i do pamięci.
|
|
60
|
+
*/
|
|
61
|
+
function saveCache(inventoryId, cacheData) {
|
|
62
|
+
const invId = Number(inventoryId);
|
|
63
|
+
ensureCacheDir();
|
|
64
|
+
const filePath = getCachePath(invId);
|
|
65
|
+
fs.writeFileSync(filePath, JSON.stringify(cacheData), { encoding: 'utf8', mode: 0o600 });
|
|
66
|
+
inMemoryCache[invId] = cacheData;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Buduje mapę kategorii z przynależnością do gałęzi (Usługi vs Produkty).
|
|
71
|
+
*/
|
|
72
|
+
function buildCategoryHierarchy(categories = []) {
|
|
73
|
+
const catMap = {};
|
|
74
|
+
for (const c of categories) {
|
|
75
|
+
catMap[c.category_id] = { ...c, branch: 'Inne' };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getRootBranch(catId) {
|
|
79
|
+
let curr = catMap[catId];
|
|
80
|
+
let depth = 0;
|
|
81
|
+
while (curr && depth < 10) {
|
|
82
|
+
if (curr.category_id === ROOT_CAT_USLUGI || curr.parent_id === ROOT_CAT_USLUGI) {
|
|
83
|
+
return 'Usługi';
|
|
84
|
+
}
|
|
85
|
+
if (curr.category_id === ROOT_CAT_PRODUKTY || curr.parent_id === ROOT_CAT_PRODUKTY) {
|
|
86
|
+
return 'Produkty';
|
|
87
|
+
}
|
|
88
|
+
if (curr.parent_id === 0) {
|
|
89
|
+
if (curr.name?.toLowerCase().includes('usług') || curr.name?.toLowerCase().includes('uslug')) return 'Usługi';
|
|
90
|
+
if (curr.name?.toLowerCase().includes('produkt')) return 'Produkty';
|
|
91
|
+
return curr.name;
|
|
92
|
+
}
|
|
93
|
+
curr = catMap[curr.parent_id];
|
|
94
|
+
depth++;
|
|
95
|
+
}
|
|
96
|
+
return 'Inne';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (const c of categories) {
|
|
100
|
+
catMap[c.category_id].branch = getRootBranch(c.category_id);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return catMap;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Pełna lub przyrostowa synchronizacja produktów z BaseLinker do lokalnego cache.
|
|
108
|
+
*/
|
|
109
|
+
async function syncCatalogCache(inventoryId = DEER_KOSZTORYS_CATALOG_ID, force = false, client = null) {
|
|
110
|
+
const invId = Number(inventoryId) || DEER_KOSZTORYS_CATALOG_ID;
|
|
111
|
+
const existing = loadCache(invId);
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
|
|
114
|
+
if (!force && existing && existing.synced_at) {
|
|
115
|
+
const ageHours = (now - existing.synced_at) / (1000 * 3600);
|
|
116
|
+
if (ageHours < TTL_HOURS) {
|
|
117
|
+
return {
|
|
118
|
+
cached: true,
|
|
119
|
+
age_hours: Math.round(ageHours * 10) / 10,
|
|
120
|
+
total_products: Object.keys(existing.products || {}).length,
|
|
121
|
+
synced_at: new Date(existing.synced_at).toISOString(),
|
|
122
|
+
message: `Cache dla katalogu ${invId} jest aktualny (${Math.round(ageHours)}h temu). Użyj force=true aby wymusić odświeżenie.`
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const bl = client || getClient();
|
|
128
|
+
|
|
129
|
+
// 1. Pobierz kategorie
|
|
130
|
+
const catRes = await bl.call('getInventoryCategories', { inventory_id: invId });
|
|
131
|
+
const rawCategories = catRes.categories || [];
|
|
132
|
+
const categories = buildCategoryHierarchy(rawCategories);
|
|
133
|
+
|
|
134
|
+
// 2. Pobierz produkty strona po stronie
|
|
135
|
+
const products = {};
|
|
136
|
+
let page = 1;
|
|
137
|
+
let hasMore = true;
|
|
138
|
+
|
|
139
|
+
while (hasMore) {
|
|
140
|
+
const listRes = await bl.call('getInventoryProductsList', {
|
|
141
|
+
inventory_id: invId,
|
|
142
|
+
page
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const pageProducts = listRes.products || {};
|
|
146
|
+
const count = Object.keys(pageProducts).length;
|
|
147
|
+
if (count === 0) {
|
|
148
|
+
hasMore = false;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (const [id, p] of Object.entries(pageProducts)) {
|
|
153
|
+
const catId = p.category_id || 0;
|
|
154
|
+
const catObj = categories[catId];
|
|
155
|
+
const branch = catObj?.branch || (p.sku?.startsWith('DdUsl') ? 'Usługi' : 'Produkty');
|
|
156
|
+
const priceBrutto = p.prices?.[DEFAULT_PRICE_GROUP_ID] ?? Object.values(p.prices || {})[0] ?? 0;
|
|
157
|
+
|
|
158
|
+
products[id] = {
|
|
159
|
+
id: Number(id),
|
|
160
|
+
sku: p.sku || '',
|
|
161
|
+
ean: p.ean || '',
|
|
162
|
+
name: p.name || '',
|
|
163
|
+
category_id: catId,
|
|
164
|
+
category_name: catObj?.name || '',
|
|
165
|
+
branch,
|
|
166
|
+
price_brutto: Number(priceBrutto) || 0,
|
|
167
|
+
stock: p.stock || {}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (count < 1000) {
|
|
172
|
+
hasMore = false;
|
|
173
|
+
} else {
|
|
174
|
+
page++;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const cachePayload = {
|
|
179
|
+
inventory_id: invId,
|
|
180
|
+
synced_at: now,
|
|
181
|
+
total_products: Object.keys(products).length,
|
|
182
|
+
total_categories: rawCategories.length,
|
|
183
|
+
categories,
|
|
184
|
+
products
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
saveCache(invId, cachePayload);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
cached: false,
|
|
191
|
+
total_products: Object.keys(products).length,
|
|
192
|
+
total_categories: rawCategories.length,
|
|
193
|
+
synced_at: new Date(now).toISOString(),
|
|
194
|
+
message: `Pomyślnie zsynchronizowano katalog ${invId}: ${Object.keys(products).length} pozycji.`
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Błyskawiczne przeszukiwanie lokalnego cache produktów (2-5ms).
|
|
200
|
+
*/
|
|
201
|
+
function searchCatalogCache(inventoryId = DEER_KOSZTORYS_CATALOG_ID, opts = {}) {
|
|
202
|
+
const invId = Number(inventoryId) || DEER_KOSZTORYS_CATALOG_ID;
|
|
203
|
+
const cache = loadCache(invId);
|
|
204
|
+
if (!cache || !cache.products) {
|
|
205
|
+
return null; // Brak cache, fallback do live API
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const {
|
|
209
|
+
query = '',
|
|
210
|
+
filter_name = '',
|
|
211
|
+
filter_sku = '',
|
|
212
|
+
filter_ean = '',
|
|
213
|
+
filter_category_id = null,
|
|
214
|
+
branch = 'wszystko', // 'wszystko' | 'uslugi' | 'produkty'
|
|
215
|
+
page = 1,
|
|
216
|
+
limit = 50
|
|
217
|
+
} = opts;
|
|
218
|
+
|
|
219
|
+
const searchTokens = (query || filter_name || '')
|
|
220
|
+
.toLowerCase()
|
|
221
|
+
.trim()
|
|
222
|
+
.split(/\s+/)
|
|
223
|
+
.filter(t => t.length > 0);
|
|
224
|
+
|
|
225
|
+
const skuQuery = (filter_sku || '').toLowerCase().trim();
|
|
226
|
+
const eanQuery = (filter_ean || '').trim();
|
|
227
|
+
const targetCatId = filter_category_id ? Number(filter_category_id) : null;
|
|
228
|
+
const targetBranch = branch ? branch.toLowerCase() : 'wszystko';
|
|
229
|
+
|
|
230
|
+
const results = [];
|
|
231
|
+
|
|
232
|
+
for (const p of Object.values(cache.products)) {
|
|
233
|
+
// Filtrowanie gałęzi
|
|
234
|
+
if (targetBranch === 'uslugi' || targetBranch === 'usługi') {
|
|
235
|
+
if (p.branch !== 'Usługi' && !p.sku?.startsWith('DdUsl')) continue;
|
|
236
|
+
} else if (targetBranch === 'produkty') {
|
|
237
|
+
if (p.branch === 'Usługi' || p.sku?.startsWith('DdUsl')) continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Filtrowanie kategorii
|
|
241
|
+
if (targetCatId && p.category_id !== targetCatId) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Filtrowanie SKU
|
|
246
|
+
if (skuQuery && !p.sku.toLowerCase().includes(skuQuery)) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Filtrowanie EAN
|
|
251
|
+
if (eanQuery && !p.ean.includes(eanQuery)) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Filtrowanie po tokenach słownych (każde słowo musi pasować w nazwie lub SKU)
|
|
256
|
+
if (searchTokens.length > 0) {
|
|
257
|
+
const haystack = `${p.name} ${p.sku} ${p.category_name}`.toLowerCase();
|
|
258
|
+
const allMatch = searchTokens.every(token => haystack.includes(token));
|
|
259
|
+
if (!allMatch) continue;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
results.push(p);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Paginacja
|
|
266
|
+
const startIndex = (page - 1) * limit;
|
|
267
|
+
const paginated = results.slice(startIndex, startIndex + limit);
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
from_cache: true,
|
|
271
|
+
cache_synced_at: new Date(cache.synced_at).toISOString(),
|
|
272
|
+
total_found: results.length,
|
|
273
|
+
page,
|
|
274
|
+
limit,
|
|
275
|
+
total_pages: Math.ceil(results.length / limit) || 1,
|
|
276
|
+
products: paginated
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Aktualizacja ceny w lokalnym cache po udanej operacji write-through w BaseLinker.
|
|
282
|
+
*/
|
|
283
|
+
function updateProductPriceInCache(inventoryId = DEER_KOSZTORYS_CATALOG_ID, productId, priceGroupId, newPrice) {
|
|
284
|
+
const invId = Number(inventoryId) || DEER_KOSZTORYS_CATALOG_ID;
|
|
285
|
+
const cache = loadCache(invId);
|
|
286
|
+
if (!cache || !cache.products) return;
|
|
287
|
+
|
|
288
|
+
const p = cache.products[productId];
|
|
289
|
+
if (p) {
|
|
290
|
+
if (String(priceGroupId) === DEFAULT_PRICE_GROUP_ID || !priceGroupId) {
|
|
291
|
+
p.price_brutto = Number(newPrice);
|
|
292
|
+
}
|
|
293
|
+
saveCache(invId, cache);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
module.exports = {
|
|
298
|
+
DEER_KOSZTORYS_CATALOG_ID,
|
|
299
|
+
ROOT_CAT_USLUGI,
|
|
300
|
+
ROOT_CAT_PRODUKTY,
|
|
301
|
+
loadCache,
|
|
302
|
+
saveCache,
|
|
303
|
+
syncCatalogCache,
|
|
304
|
+
searchCatalogCache,
|
|
305
|
+
updateProductPriceInCache
|
|
306
|
+
};
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const { getClient } = require('./client');
|
|
3
3
|
const { searchMethods, getMethodSchema } = require('./crawler');
|
|
4
|
+
const {
|
|
5
|
+
DEER_KOSZTORYS_CATALOG_ID,
|
|
6
|
+
syncCatalogCache,
|
|
7
|
+
searchCatalogCache,
|
|
8
|
+
updateProductPriceInCache
|
|
9
|
+
} = require('./cache');
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
12
|
* Definicje narzędzi MCP dla zaawansowanego zarządzania katalogiem BaseLinker.
|
|
@@ -20,21 +26,39 @@ const CATALOG_MCP_TOOLS = [
|
|
|
20
26
|
required: ['inventory_id', 'product_id']
|
|
21
27
|
}
|
|
22
28
|
},
|
|
29
|
+
{
|
|
30
|
+
name: 'bl_catalog_sync_cache',
|
|
31
|
+
description: 'Pobiera i odświeża lokalny szybki cache produktów i kategorii wybranego katalogu BaseLinker (np. 81186 Kosztorys) do błyskawicznego przeszukiwania lokalnego (2-5ms) bez zużywania limitów API (100 req/min).',
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
inventory_id: { type: 'integer', default: 81186, description: 'ID katalogu BaseLinker (domyślnie 81186 Kosztorys).' },
|
|
36
|
+
force: { type: 'boolean', default: false, description: 'Czy wymusić pełne pobranie nawet jeśli cache jest świeży (< 24h).' }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
23
40
|
{
|
|
24
41
|
name: 'bl_catalog_search_products',
|
|
25
|
-
description: '
|
|
42
|
+
description: 'Błyskawiczne wyszukiwanie i filtrowanie produktów/usług z katalogu BaseLinker (lokalny cache 2ms lub live API). Obsługuje wyszukiwanie frazowe, podział na gałąź Usługi (DdUsl) vs Produkty oraz filtry SKU/kategorii.',
|
|
26
43
|
inputSchema: {
|
|
27
44
|
type: 'object',
|
|
28
45
|
properties: {
|
|
29
|
-
inventory_id: { type: 'integer', description: 'ID katalogu BaseLinker.' },
|
|
30
|
-
|
|
46
|
+
inventory_id: { type: 'integer', default: 81186, description: 'ID katalogu BaseLinker (domyślnie 81186 Kosztorys).' },
|
|
47
|
+
query: { type: 'string', description: 'Dowolna fraza wyszukiwania (szuka po słowach w nazwie, SKU i kategorii).' },
|
|
48
|
+
branch: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
enum: ['wszystko', 'uslugi', 'produkty'],
|
|
51
|
+
default: 'wszystko',
|
|
52
|
+
description: 'Filtruj gałąź katalogu: "uslugi" (gałąź Usługi ID 5578342 / SKU DdUsl), "produkty" (gałąź Produkty ID 8769388 / armatura, płytki, podłogi), "wszystko".'
|
|
53
|
+
},
|
|
54
|
+
filter_category_id: { type: 'integer', description: 'Opcjonalne filtrowanie po dokładnym ID kategorii.' },
|
|
31
55
|
filter_manufacturer_id: { type: 'integer', description: 'Opcjonalne filtrowanie po ID producenta.' },
|
|
32
|
-
filter_sku: { type: 'string', description: 'Opcjonalne wyszukiwanie po SKU produktu.' },
|
|
56
|
+
filter_sku: { type: 'string', description: 'Opcjonalne wyszukiwanie po SKU produktu (np. DdUsl247, A5D186ENB0).' },
|
|
33
57
|
filter_ean: { type: 'string', description: 'Opcjonalne wyszukiwanie po kodzie EAN.' },
|
|
34
58
|
filter_name: { type: 'string', description: 'Opcjonalne wyszukiwanie po fragmencie nazwy.' },
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
59
|
+
use_cache: { type: 'boolean', default: true, description: 'Domyślnie true (błyskawiczne przeszukiwanie lokalnego cache 2ms). Ustaw false, aby wymusić zapytanie live do API.' },
|
|
60
|
+
page: { type: 'integer', default: 1, description: 'Numer strony wyników.' }
|
|
61
|
+
}
|
|
38
62
|
}
|
|
39
63
|
},
|
|
40
64
|
{
|
|
@@ -56,7 +80,9 @@ const CATALOG_MCP_TOOLS = [
|
|
|
56
80
|
height: { type: 'number', description: 'Wysokość w cm.' },
|
|
57
81
|
width: { type: 'number', description: 'Szerokość w cm.' },
|
|
58
82
|
length: { type: 'number', description: 'Długość w cm.' },
|
|
59
|
-
prices: { type: 'object', description: 'Ceny brutto wg grup cenowych, np. {"
|
|
83
|
+
prices: { type: 'object', description: 'Ceny brutto wg grup cenowych, np. {"49162": 149.99}.' },
|
|
84
|
+
average_cost: { type: 'number', description: 'Koszt własny / stawka robocizny wykonawcy netto.' },
|
|
85
|
+
average_landed_cost: { type: 'number', description: 'Średni koszt dodatkowy / koszt materiału budowlanego dodatkowego netto z zapasem.' },
|
|
60
86
|
stock: { type: 'object', description: 'Stany magazynowe wg magazynów, np. {"bl_123": 25}.' },
|
|
61
87
|
tags: { type: 'array', items: { type: 'string' }, description: 'Lista tagów przypisanych do produktu.' }
|
|
62
88
|
},
|
|
@@ -278,14 +304,49 @@ async function handleCatalogToolCall(toolName, args, client = null) {
|
|
|
278
304
|
return product;
|
|
279
305
|
}
|
|
280
306
|
|
|
307
|
+
case 'bl_catalog_sync_cache': {
|
|
308
|
+
const { inventory_id = DEER_KOSZTORYS_CATALOG_ID, force = false } = args;
|
|
309
|
+
return await syncCatalogCache(inventory_id, force, bl);
|
|
310
|
+
}
|
|
311
|
+
|
|
281
312
|
case 'bl_catalog_search_products': {
|
|
282
|
-
const {
|
|
313
|
+
const {
|
|
314
|
+
inventory_id = DEER_KOSZTORYS_CATALOG_ID,
|
|
315
|
+
query,
|
|
316
|
+
branch = 'wszystko',
|
|
317
|
+
filter_category_id,
|
|
318
|
+
filter_manufacturer_id,
|
|
319
|
+
filter_sku,
|
|
320
|
+
filter_ean,
|
|
321
|
+
filter_name,
|
|
322
|
+
use_cache = true,
|
|
323
|
+
page = 1
|
|
324
|
+
} = args;
|
|
325
|
+
|
|
326
|
+
// 1. Spróbuj lokalnego błyskawicznego przeszukania cache (2ms)
|
|
327
|
+
if (use_cache !== false) {
|
|
328
|
+
const cachedRes = searchCatalogCache(inventory_id, {
|
|
329
|
+
query: query || filter_name,
|
|
330
|
+
filter_name,
|
|
331
|
+
filter_sku,
|
|
332
|
+
filter_ean,
|
|
333
|
+
filter_category_id,
|
|
334
|
+
branch,
|
|
335
|
+
page,
|
|
336
|
+
limit: 50
|
|
337
|
+
});
|
|
338
|
+
if (cachedRes) {
|
|
339
|
+
return cachedRes;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// 2. Fallback do live API BaseLinkera
|
|
283
344
|
const params = { inventory_id, page };
|
|
284
345
|
if (filter_category_id) params.filter_category_id = filter_category_id;
|
|
285
346
|
if (filter_manufacturer_id) params.filter_manufacturer_id = filter_manufacturer_id;
|
|
286
347
|
if (filter_sku) params.filter_sku = filter_sku;
|
|
287
348
|
if (filter_ean) params.filter_ean = filter_ean;
|
|
288
|
-
if (filter_name) params.filter_name = filter_name;
|
|
349
|
+
if (filter_name || query) params.filter_name = filter_name || query;
|
|
289
350
|
return await bl.call('getInventoryProductsList', params);
|
|
290
351
|
}
|
|
291
352
|
|
|
@@ -407,11 +468,20 @@ async function handleCatalogToolCall(toolName, args, client = null) {
|
|
|
407
468
|
}
|
|
408
469
|
|
|
409
470
|
case 'bl_catalog_update_prices_batch': {
|
|
410
|
-
const { inventory_id, products } = args;
|
|
411
|
-
|
|
471
|
+
const { inventory_id = DEER_KOSZTORYS_CATALOG_ID, products } = args;
|
|
472
|
+
const res = await bl.call('updateInventoryProductsPrices', {
|
|
412
473
|
inventory_id,
|
|
413
474
|
products
|
|
414
475
|
});
|
|
476
|
+
// Write-through cache update
|
|
477
|
+
try {
|
|
478
|
+
for (const [prodId, groupPrices] of Object.entries(products || {})) {
|
|
479
|
+
for (const [groupId, priceVal] of Object.entries(groupPrices || {})) {
|
|
480
|
+
updateProductPriceInCache(inventory_id, prodId, groupId, priceVal);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
} catch (_) {}
|
|
484
|
+
return res;
|
|
415
485
|
}
|
|
416
486
|
|
|
417
487
|
case 'bl_catalog_update_stock_batch': {
|
package/src/commands.js
CHANGED
|
@@ -52,7 +52,8 @@ async function cmdLogin(pos, flags) {
|
|
|
52
52
|
log: out
|
|
53
53
|
});
|
|
54
54
|
out(`✓ Zalogowano do BaseLinker. Token zapisany w credentials.json.`);
|
|
55
|
-
|
|
55
|
+
installMcpServers(null, true);
|
|
56
|
+
out(` Serwery MCP: automatycznie zaktualizowano konfigurację dla Cursor, Antigravity i Claude.`);
|
|
56
57
|
return;
|
|
57
58
|
} catch (e) {
|
|
58
59
|
err(`✗ BaseLinker: ${e.message}`);
|
|
@@ -67,6 +68,7 @@ async function cmdLogin(pos, flags) {
|
|
|
67
68
|
const token = pos[1] || flags.token;
|
|
68
69
|
await loginSkillsDirect(token);
|
|
69
70
|
out(`✓ Zalogowano do DeerSkills Hub. Klucz zapisany w credentials.json.`);
|
|
71
|
+
installMcpServers(null, true);
|
|
70
72
|
out(` Przegląd umiejętności: deertools skill list | Synchronizacja: deertools skill sync`);
|
|
71
73
|
return;
|
|
72
74
|
} catch (e) {
|
|
@@ -105,23 +107,26 @@ async function cmdLogin(pos, flags) {
|
|
|
105
107
|
: scope;
|
|
106
108
|
const entry = await login(product, { scope: productScope, port: flags.port ? Number(flags.port) : 0, clientName: flags.name, openBrowser: flags['no-open'] ? null : undefined, log: out });
|
|
107
109
|
out(`✓ Zalogowano${entry.email ? ` jako ${entry.email}` : ''}. Zakres: ${entry.scope}${entry.expires_at ? `, ważny do ${entry.expires_at.slice(0, 10)}` : ''}.`);
|
|
108
|
-
out(` Konfiguracja MCP: deertools mcp install | Synchronizacja skilli: deertools skill sync`);
|
|
109
110
|
} catch (e) {
|
|
110
111
|
failures += 1;
|
|
111
112
|
err(`✗ ${product.name}: ${e.message}`);
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
if (failures) fail(`Nie udało się zalogować do ${failures} z ${products.length} produktów.`, 1);
|
|
116
|
+
out(`\n[Auto-Sync] Automatycznie zaktualizowano konfigurację serwerów MCP dla Cursor, Antigravity, Claude i .mcp.json.`);
|
|
117
|
+
installMcpServers(null, true);
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
async function cmdLogout(pos) {
|
|
118
121
|
if (pos[0] === 'baselinker' || pos[0] === 'bl') {
|
|
119
122
|
out(credentials.removeProduct('baselinker') ? `✓ Wylogowano z BaseLinker (token usunięty lokalnie).` : `– BaseLinker: nie byłeś zalogowany.`);
|
|
123
|
+
installMcpServers(null, true);
|
|
120
124
|
return;
|
|
121
125
|
}
|
|
122
126
|
for (const product of resolveProducts(pos[0])) {
|
|
123
127
|
out(credentials.removeProduct(product.id) ? `✓ Wylogowano z ${product.name} (klucz usunięty lokalnie).` : `– ${product.name}: nie byłeś zalogowany.`);
|
|
124
128
|
}
|
|
129
|
+
installMcpServers(null, true);
|
|
125
130
|
if (pos[0] === 'all') {
|
|
126
131
|
if (credentials.removeProduct('baselinker')) {
|
|
127
132
|
out('✓ Wylogowano z BaseLinker (token usunięty lokalnie).');
|
|
@@ -384,77 +389,158 @@ async function cmdMcp(pos, flags) {
|
|
|
384
389
|
return;
|
|
385
390
|
}
|
|
386
391
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
+
function installMcpServers(customProducts = null, silent = false) {
|
|
393
|
+
const prods = customProducts || resolveProducts('all');
|
|
394
|
+
const servers = buildMcpServersConfig(prods, true);
|
|
395
|
+
let touched = 0;
|
|
396
|
+
|
|
397
|
+
// 1. Cursor global (~/.cursor/mcp.json)
|
|
398
|
+
const cursorGlobal = path.join(os.homedir(), '.cursor', 'mcp.json');
|
|
399
|
+
try {
|
|
400
|
+
let cur = { mcpServers: {} };
|
|
401
|
+
if (fs.existsSync(cursorGlobal)) {
|
|
402
|
+
try { cur = JSON.parse(fs.readFileSync(cursorGlobal, 'utf8')); } catch (_) {}
|
|
403
|
+
}
|
|
404
|
+
cur.mcpServers = Object.assign({}, cur.mcpServers, servers);
|
|
405
|
+
fs.mkdirSync(path.dirname(cursorGlobal), { recursive: true });
|
|
406
|
+
fs.writeFileSync(cursorGlobal, JSON.stringify(cur, null, 2));
|
|
407
|
+
if (!silent) out(`✓ Cursor IDE: ${cursorGlobal}`);
|
|
408
|
+
touched++;
|
|
409
|
+
} catch (e) {
|
|
410
|
+
if (!silent) err(`✗ Cursor IDE (${cursorGlobal}): ${e.message}`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// 2. Cursor workspace (.cursor/mcp.json)
|
|
414
|
+
const workspaceCursor = path.join(process.cwd(), '.cursor', 'mcp.json');
|
|
415
|
+
if (fs.existsSync(path.join(process.cwd(), '.cursor')) || fs.existsSync(path.join(process.cwd(), '.git'))) {
|
|
392
416
|
try {
|
|
393
417
|
let cur = { mcpServers: {} };
|
|
394
|
-
if (fs.existsSync(
|
|
395
|
-
try { cur = JSON.parse(fs.readFileSync(
|
|
418
|
+
if (fs.existsSync(workspaceCursor)) {
|
|
419
|
+
try { cur = JSON.parse(fs.readFileSync(workspaceCursor, 'utf8')); } catch (_) {}
|
|
396
420
|
}
|
|
397
421
|
cur.mcpServers = Object.assign({}, cur.mcpServers, servers);
|
|
398
|
-
fs.mkdirSync(path.dirname(
|
|
399
|
-
fs.writeFileSync(
|
|
400
|
-
out(`✓ Cursor
|
|
422
|
+
fs.mkdirSync(path.dirname(workspaceCursor), { recursive: true });
|
|
423
|
+
fs.writeFileSync(workspaceCursor, JSON.stringify(cur, null, 2));
|
|
424
|
+
if (!silent) out(`✓ Cursor Workspace: ${workspaceCursor}`);
|
|
401
425
|
touched++;
|
|
402
426
|
} catch (e) {
|
|
403
|
-
err(`✗ Cursor
|
|
427
|
+
if (!silent) err(`✗ Cursor Workspace (${workspaceCursor}): ${e.message}`);
|
|
404
428
|
}
|
|
429
|
+
}
|
|
405
430
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
414
|
-
cur.mcpServers = Object.assign({}, cur.mcpServers, servers);
|
|
415
|
-
fs.mkdirSync(path.dirname(workspaceCursor), { recursive: true });
|
|
416
|
-
fs.writeFileSync(workspaceCursor, JSON.stringify(cur, null, 2));
|
|
417
|
-
out(`✓ Cursor Workspace: ${workspaceCursor}`);
|
|
418
|
-
touched++;
|
|
419
|
-
} catch (e) {
|
|
420
|
-
err(`✗ Cursor Workspace (${workspaceCursor}): ${e.message}`);
|
|
431
|
+
// 3. Antigravity / Gemini Workspace (.agents/mcp_config.json)
|
|
432
|
+
const agentsConfig = path.join(process.cwd(), '.agents', 'mcp_config.json');
|
|
433
|
+
if (fs.existsSync(path.dirname(agentsConfig))) {
|
|
434
|
+
try {
|
|
435
|
+
let cur = { mcpServers: {} };
|
|
436
|
+
if (fs.existsSync(agentsConfig)) {
|
|
437
|
+
try { cur = JSON.parse(fs.readFileSync(agentsConfig, 'utf8')); } catch (_) {}
|
|
421
438
|
}
|
|
439
|
+
cur.mcpServers = Object.assign({}, cur.mcpServers, servers);
|
|
440
|
+
fs.writeFileSync(agentsConfig, JSON.stringify(cur, null, 2));
|
|
441
|
+
if (!silent) out(`✓ Antigravity / Agent Workspace: ${agentsConfig}`);
|
|
442
|
+
touched++;
|
|
443
|
+
} catch (e) {
|
|
444
|
+
if (!silent) err(`✗ Antigravity (${agentsConfig}): ${e.message}`);
|
|
422
445
|
}
|
|
446
|
+
}
|
|
423
447
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
448
|
+
// 4. Antigravity Global (~/.gemini/config/mcp_config.json)
|
|
449
|
+
const geminiGlobal = path.join(os.homedir(), '.gemini', 'config', 'mcp_config.json');
|
|
450
|
+
if (fs.existsSync(path.dirname(geminiGlobal))) {
|
|
451
|
+
try {
|
|
452
|
+
let cur = { mcpServers: {} };
|
|
453
|
+
if (fs.existsSync(geminiGlobal)) {
|
|
454
|
+
try { cur = JSON.parse(fs.readFileSync(geminiGlobal, 'utf8')); } catch (_) {}
|
|
455
|
+
}
|
|
456
|
+
cur.mcpServers = Object.assign({}, cur.mcpServers, servers);
|
|
457
|
+
fs.writeFileSync(geminiGlobal, JSON.stringify(cur, null, 2));
|
|
458
|
+
if (!silent) out(`✓ Antigravity Global: ${geminiGlobal}`);
|
|
459
|
+
touched++;
|
|
460
|
+
} catch (e) {
|
|
461
|
+
if (!silent) err(`✗ Antigravity Global (${geminiGlobal}): ${e.message}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// 5. Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json)
|
|
466
|
+
const claudeDesktop = path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
467
|
+
if (fs.existsSync(path.dirname(claudeDesktop))) {
|
|
468
|
+
try {
|
|
469
|
+
let cur = {};
|
|
470
|
+
if (fs.existsSync(claudeDesktop)) {
|
|
471
|
+
try { cur = JSON.parse(fs.readFileSync(claudeDesktop, 'utf8')); } catch (_) {}
|
|
472
|
+
}
|
|
473
|
+
const claudeServers = {};
|
|
474
|
+
for (const [k, v] of Object.entries(servers)) {
|
|
475
|
+
claudeServers[k] = { ...v };
|
|
476
|
+
if (v.command) {
|
|
477
|
+
claudeServers[k].env = {
|
|
478
|
+
PATH: '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:' + path.join(os.homedir(), '.local', 'bin')
|
|
479
|
+
};
|
|
431
480
|
}
|
|
432
|
-
cur.mcpServers = Object.assign({}, cur.mcpServers, servers);
|
|
433
|
-
fs.writeFileSync(agentsConfig, JSON.stringify(cur, null, 2));
|
|
434
|
-
out(`✓ Antigravity / Agent Workspace: ${agentsConfig}`);
|
|
435
|
-
touched++;
|
|
436
|
-
} catch (e) {
|
|
437
|
-
err(`✗ Antigravity (${agentsConfig}): ${e.message}`);
|
|
438
481
|
}
|
|
482
|
+
cur.mcpServers = Object.assign({}, cur.mcpServers || {}, claudeServers);
|
|
483
|
+
fs.writeFileSync(claudeDesktop, JSON.stringify(cur, null, 2));
|
|
484
|
+
if (!silent) out(`✓ Claude Desktop: ${claudeDesktop}`);
|
|
485
|
+
touched++;
|
|
486
|
+
} catch (e) {
|
|
487
|
+
if (!silent) err(`✗ Claude Desktop (${claudeDesktop}): ${e.message}`);
|
|
439
488
|
}
|
|
489
|
+
}
|
|
440
490
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
491
|
+
// 6. Claude Code CLI (~/.claude.json)
|
|
492
|
+
const claudeCli = path.join(os.homedir(), '.claude.json');
|
|
493
|
+
if (fs.existsSync(claudeCli)) {
|
|
494
|
+
try {
|
|
495
|
+
let cur = {};
|
|
496
|
+
try { cur = JSON.parse(fs.readFileSync(claudeCli, 'utf8')); } catch (_) {}
|
|
497
|
+
const claudeServers = {};
|
|
498
|
+
for (const [k, v] of Object.entries(servers)) {
|
|
499
|
+
claudeServers[k] = { ...v };
|
|
500
|
+
if (v.command) {
|
|
501
|
+
claudeServers[k].env = {
|
|
502
|
+
PATH: '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:' + path.join(os.homedir(), '.local', 'bin')
|
|
503
|
+
};
|
|
448
504
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
505
|
+
}
|
|
506
|
+
cur.mcpServers = Object.assign({}, cur.mcpServers || {}, claudeServers);
|
|
507
|
+
fs.writeFileSync(claudeCli, JSON.stringify(cur, null, 2));
|
|
508
|
+
if (!silent) out(`✓ Claude Code CLI: ${claudeCli}`);
|
|
509
|
+
touched++;
|
|
510
|
+
} catch (e) {
|
|
511
|
+
if (!silent) err(`✗ Claude Code CLI (${claudeCli}): ${e.message}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// 7. Workspace .mcp.json (Claude Code / Cowork / Standard MCP workspace)
|
|
516
|
+
const workspaceMcp = path.join(process.cwd(), '.mcp.json');
|
|
517
|
+
try {
|
|
518
|
+
let cur = {};
|
|
519
|
+
if (fs.existsSync(workspaceMcp)) {
|
|
520
|
+
try { cur = JSON.parse(fs.readFileSync(workspaceMcp, 'utf8')); } catch (_) {}
|
|
521
|
+
}
|
|
522
|
+
const claudeServers = {};
|
|
523
|
+
for (const [k, v] of Object.entries(servers)) {
|
|
524
|
+
claudeServers[k] = { ...v };
|
|
525
|
+
if (v.command) {
|
|
526
|
+
claudeServers[k].env = {
|
|
527
|
+
PATH: '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:' + path.join(os.homedir(), '.local', 'bin')
|
|
528
|
+
};
|
|
455
529
|
}
|
|
456
530
|
}
|
|
531
|
+
cur.mcpServers = Object.assign({}, cur.mcpServers || {}, claudeServers);
|
|
532
|
+
fs.writeFileSync(workspaceMcp, JSON.stringify(cur, null, 2));
|
|
533
|
+
if (!silent) out(`✓ Workspace MCP (.mcp.json): ${workspaceMcp}`);
|
|
534
|
+
touched++;
|
|
535
|
+
} catch (e) {
|
|
536
|
+
if (!silent) err(`✗ Workspace MCP (${workspaceMcp}): ${e.message}`);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return touched;
|
|
540
|
+
}
|
|
457
541
|
|
|
542
|
+
if (sub === 'install') {
|
|
543
|
+
const touched = installMcpServers(products, false);
|
|
458
544
|
if (!touched) fail('Nie znaleziono środowiska agenta do instalacji MCP.', 4);
|
|
459
545
|
out(`\nPomyślnie skonfigurowano wszystkie 7 serwerów MCP dla produktów DeerTools!`);
|
|
460
546
|
return;
|
package/src/doctor.js
CHANGED
|
@@ -254,7 +254,11 @@ async function cmdDoctor(pos, flags) {
|
|
|
254
254
|
const mcpLocations = [
|
|
255
255
|
{ name: 'Cursor IDE (global)', path: path.join(os.homedir(), '.cursor', 'mcp.json') },
|
|
256
256
|
{ name: 'Cursor Workspace', path: path.join(process.cwd(), '.cursor', 'mcp.json') },
|
|
257
|
-
{ name: 'Antigravity
|
|
257
|
+
{ name: 'Antigravity Workspace', path: path.join(process.cwd(), '.agents', 'mcp_config.json') },
|
|
258
|
+
{ name: 'Antigravity Global', path: path.join(os.homedir(), '.gemini', 'config', 'mcp_config.json') },
|
|
259
|
+
{ name: 'Claude Desktop', path: path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json') },
|
|
260
|
+
{ name: 'Claude Code CLI', path: path.join(os.homedir(), '.claude.json') },
|
|
261
|
+
{ name: 'Workspace MCP (.mcp.json)', path: path.join(process.cwd(), '.mcp.json') },
|
|
258
262
|
];
|
|
259
263
|
for (const loc of mcpLocations) {
|
|
260
264
|
if (fs.existsSync(loc.path)) {
|