@deerdesign/deertools 1.2.23 → 1.2.25
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/package.json +1 -1
- package/src/commands.js +101 -55
- package/src/estimate-client.js +110 -0
- package/src/mcp-stdio.js +73 -25
package/package.json
CHANGED
package/src/commands.js
CHANGED
|
@@ -7,6 +7,7 @@ const { getProduct, resolveProducts, ALL_IDS } = require('./registry');
|
|
|
7
7
|
const { login, normalizeScope } = require('./login');
|
|
8
8
|
const { discover, fetchText, apiRequest, explain } = require('./http');
|
|
9
9
|
const { listOperations, parseFrontmatterName } = require('./openapi');
|
|
10
|
+
const { calculateEstimate, getEstimateMargins } = require('./estimate-client');
|
|
10
11
|
|
|
11
12
|
const out = (s = '') => process.stdout.write(`${s}\n`);
|
|
12
13
|
const err = (s = '') => process.stderr.write(`${s}\n`);
|
|
@@ -214,75 +215,120 @@ async function cmdApi(pos, flags) {
|
|
|
214
215
|
out(res.json ? JSON.stringify(res.json, null, flags.raw ? 0 : 2) : res.text);
|
|
215
216
|
}
|
|
216
217
|
|
|
217
|
-
async function cmdEstimate(pos, flags) {
|
|
218
|
+
async function cmdEstimate(pos, flags, options = {}) {
|
|
218
219
|
const sub = pos[0];
|
|
220
|
+
const write = options.out || out;
|
|
219
221
|
|
|
220
222
|
if (sub === 'calculate' || sub === 'calc') {
|
|
221
223
|
const area = Number(pos[1] || flags.area);
|
|
222
|
-
if (!area || isNaN(area)) fail('Użycie: deertools estimate calculate <metraż_m2> [smart_life|modern_life|high_life]', 2);
|
|
223
|
-
|
|
224
|
-
smart_life: { name: 'Smart Life', rate: 1290, flat: 58050, marginPct: 33.1 },
|
|
225
|
-
modern_life: { name: 'Modern Life', rate: 1640, flat: 73800, marginPct: 31.2 },
|
|
226
|
-
high_life: { name: 'High Life', rate: 1990, flat: 89550, marginPct: 22.7 },
|
|
227
|
-
};
|
|
228
|
-
const isFlat = area < 45;
|
|
224
|
+
if (!area || isNaN(area)) fail('Użycie: deertools estimate calculate <metraż_m2> [smart_life|modern_life|high_life] [--wet=<liczba>] [--doors=<liczba>] [--basis=avg|max]', 2);
|
|
225
|
+
|
|
229
226
|
const requested = pos[2] || flags.package;
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
227
|
+
const wetInput = flags.wet !== undefined ? Number(flags.wet) : (flags.wetZones !== undefined ? Number(flags.wetZones) : undefined);
|
|
228
|
+
const doorsInput = flags.doors !== undefined ? Number(flags.doors) : undefined;
|
|
229
|
+
|
|
230
|
+
const data = await calculateEstimate({
|
|
231
|
+
area_m2: area,
|
|
232
|
+
package: requested || undefined,
|
|
233
|
+
wet_zones: wetInput,
|
|
234
|
+
doors: doorsInput,
|
|
235
|
+
}, options);
|
|
236
|
+
|
|
237
|
+
const estimates = (data && data.estimates) ? data.estimates : [];
|
|
238
|
+
if (!estimates.length) {
|
|
239
|
+
fail('Nie otrzymano wyników kalkulacji z serwera Kosztorysu.', 4);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const results = estimates.map(e => ({
|
|
243
|
+
package: e.package,
|
|
244
|
+
package_key: e.slug,
|
|
245
|
+
slug: e.slug,
|
|
246
|
+
area_m2: e.area_m2,
|
|
247
|
+
price_per_m2_gross: e.price_per_m2_gross,
|
|
248
|
+
is_flat_rate: !!e.is_lump_sum,
|
|
249
|
+
is_lump_sum: !!e.is_lump_sum,
|
|
250
|
+
lump_sum_max_area_m2: e.lump_sum_max_area_m2,
|
|
251
|
+
base_gross_pln: e.base_gross,
|
|
252
|
+
base_net_pln: Math.round((e.base_gross / 1.08) * 100) / 100,
|
|
253
|
+
standard_wet_zones: e.standard_wet_zones,
|
|
254
|
+
wet_zones: e.wet_zones,
|
|
255
|
+
extra_wet_zones: Math.max(0, (e.wet_zones || 0) - (e.standard_wet_zones || 0)),
|
|
256
|
+
wet_surcharge_gross_pln: e.wet_zone_surcharge_gross,
|
|
257
|
+
wet_surcharge_net_pln: Math.round(((e.wet_zone_surcharge_gross || 0) / 1.08) * 100) / 100,
|
|
258
|
+
standard_doors: e.standard_doors,
|
|
259
|
+
doors: (e.standard_doors || 0) + (e.complexity_levels || 0),
|
|
260
|
+
complexity_levels: e.complexity_levels,
|
|
261
|
+
complexity_surcharge_label: e.complexity_surcharge_label || 'Dopłata z tytułu złożoności projektu',
|
|
262
|
+
complexity_surcharge_gross_pln: e.complexity_surcharge_gross,
|
|
263
|
+
complexity_surcharge_net_pln: Math.round(((e.complexity_surcharge_gross || 0) / 1.08) * 100) / 100,
|
|
264
|
+
total_gross_pln: e.total_gross,
|
|
265
|
+
total_net_pln: e.total_net,
|
|
266
|
+
vat_pln: e.vat_pln,
|
|
267
|
+
currency: e.currency || 'PLN',
|
|
268
|
+
max_discount_pct: e.max_discount_pct,
|
|
269
|
+
discount_locked: e.discount_locked,
|
|
270
|
+
source: 'Kosztorys Backend API (estimate-mcp)',
|
|
271
|
+
}));
|
|
272
|
+
|
|
273
|
+
if (flags.json) {
|
|
274
|
+
write(JSON.stringify(results.length === 1 ? results[0] : results, null, 2));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const isFlat = results.some(r => r.is_flat_rate);
|
|
279
|
+
write(`\nKalkulacja wykończenia dla metrażu ${area} m² (źródło: Kosztorys API${isFlat ? ', ryczałt do 40 m²' : ''}):`);
|
|
253
280
|
const fmt = (n) => new Intl.NumberFormat('pl-PL', { maximumFractionDigits: 0 }).format(Math.round(n));
|
|
254
281
|
for (const r of results) {
|
|
255
|
-
|
|
282
|
+
write(` ▶ ${r.package.padEnd(14)} ${fmt(r.total_gross_pln)} zł brutto (${fmt(r.total_net_pln)} zł netto + ${fmt(r.vat_pln)} zł VAT)`);
|
|
283
|
+
if (r.extra_wet_zones > 0 || r.complexity_levels > 0) {
|
|
284
|
+
write(` - Podstawa: ${fmt(r.base_gross_pln)} zł brutto`);
|
|
285
|
+
if (r.extra_wet_zones > 0) write(` - Dodatkowe strefy mokre (+${r.extra_wet_zones}): +${fmt(r.wet_surcharge_gross_pln)} zł brutto`);
|
|
286
|
+
if (r.complexity_levels > 0) write(` - ${r.complexity_surcharge_label} (+${r.complexity_levels} drzwi): +${fmt(r.complexity_surcharge_gross_pln)} zł brutto`);
|
|
287
|
+
}
|
|
256
288
|
}
|
|
257
289
|
return;
|
|
258
290
|
}
|
|
259
291
|
|
|
260
292
|
if (sub === 'margins') {
|
|
261
293
|
const area = Number(pos[1] || flags.area || 50);
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
294
|
+
|
|
295
|
+
const data = await getEstimateMargins({
|
|
296
|
+
area_m2: area,
|
|
297
|
+
}, options);
|
|
298
|
+
|
|
299
|
+
const estimates = (data && data.estimates) ? data.estimates : [];
|
|
300
|
+
if (!estimates.length) {
|
|
301
|
+
fail('Nie otrzymano reguł marżowych z serwera Kosztorysu.', 4);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const rows = estimates.map(e => ({
|
|
305
|
+
package: e.package,
|
|
306
|
+
slug: e.slug,
|
|
307
|
+
area_m2: e.area_m2,
|
|
308
|
+
base_gross_pln: e.base_gross,
|
|
309
|
+
base_net_pln: e.total_net,
|
|
310
|
+
is_flat_rate: !!e.is_lump_sum,
|
|
311
|
+
std_wet_zones: e.standard_wet_zones,
|
|
312
|
+
std_doors: e.standard_doors,
|
|
313
|
+
wet_surcharge_gross: e.wet_zone_surcharge_gross,
|
|
314
|
+
complexity_surcharge_label: e.complexity_surcharge_label || 'Dopłata z tytułu złożoności projektu',
|
|
315
|
+
complexity_surcharge_gross: e.complexity_surcharge_gross,
|
|
316
|
+
margin_rule_simplified: e.margin_rule_simplified,
|
|
317
|
+
margin_rule_quick: e.margin_rule_quick,
|
|
318
|
+
formula: e.formula,
|
|
319
|
+
note: e.note,
|
|
320
|
+
source: 'Kosztorys Backend API (estimate-mcp)',
|
|
321
|
+
}));
|
|
322
|
+
|
|
323
|
+
if (flags.json) { write(JSON.stringify(rows, null, 2)); return; }
|
|
324
|
+
write(`\nZasady wyliczania marży Deer Design (Nowy standard 2026 z Kosztorysu dla ${area} m²):`);
|
|
325
|
+
write(` • Kosztorys Uproszczony: ${rows[0]?.margin_rule_simplified}`);
|
|
326
|
+
write(` • Kosztorys Szybki: ${rows[0]?.margin_rule_quick}`);
|
|
327
|
+
write(` • Formuła marży: ${rows[0]?.formula}`);
|
|
328
|
+
write(` • Uwaga: ${rows[0]?.note}\n`);
|
|
284
329
|
for (const r of rows) {
|
|
285
|
-
|
|
330
|
+
write(` ▶ ${r.package.padEnd(14)} Podstawa: ${r.base_gross_pln} zł brutto (${r.base_net_pln} zł netto) | Std: ${r.std_wet_zones} strefa mokra, ${r.std_doors} drzwi`);
|
|
331
|
+
write(` Reguła marży: ${r.margin_rule_simplified}`);
|
|
286
332
|
}
|
|
287
333
|
return;
|
|
288
334
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const credentials = require('./credentials');
|
|
3
|
+
const { getProduct } = require('./registry');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Klient API / MCP dla Kosztorysu Deer Design.
|
|
7
|
+
* Wszystkie kalkulacje (stawki, progi ryczałtowe do 40 m², dopłaty za strefy mokre,
|
|
8
|
+
* dopłaty ze względu na złożoność projektu oraz reguły marżowe) są wykonywane
|
|
9
|
+
* wyłącznie po stronie serwera Kosztorysu (Supabase Edge Function: estimate-mcp).
|
|
10
|
+
* CLI nie zawiera lokalnie zakodowanych tabel cenowych ani algorytmów marży.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
async function callEstimateMcp(toolName, args = {}, options = {}) {
|
|
14
|
+
const product = getProduct('estimate');
|
|
15
|
+
const found = credentials.getKey(product);
|
|
16
|
+
const apiKey = options.apiKey || (found && found.key);
|
|
17
|
+
|
|
18
|
+
if (!apiKey && !options.allowAnonymous) {
|
|
19
|
+
const err = new Error(
|
|
20
|
+
`Nie jesteś zalogowany do Kosztorysu. Kalkulacja wymaga autoryzacji z serwera DeerTools.\nUruchom: deertools login estimate`
|
|
21
|
+
);
|
|
22
|
+
err.exitCode = 3;
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const endpoint = options.endpoint || product.mcpEndpoint || `${product.fallbackApiBase}/estimate-mcp`;
|
|
27
|
+
const fetchFn = options.fetchImpl || globalThis.fetch;
|
|
28
|
+
|
|
29
|
+
const payload = {
|
|
30
|
+
jsonrpc: '2.0',
|
|
31
|
+
id: Date.now(),
|
|
32
|
+
method: 'tools/call',
|
|
33
|
+
params: {
|
|
34
|
+
name: toolName,
|
|
35
|
+
arguments: args,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const headers = {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
Accept: 'application/json',
|
|
42
|
+
};
|
|
43
|
+
if (apiKey) {
|
|
44
|
+
headers['x-api-key'] = apiKey;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let res;
|
|
48
|
+
try {
|
|
49
|
+
res = await fetchFn(endpoint, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers,
|
|
52
|
+
body: JSON.stringify(payload),
|
|
53
|
+
});
|
|
54
|
+
} catch (netErr) {
|
|
55
|
+
const err = new Error(`Błąd połączenia z serwerem Kosztorysu (${endpoint}): ${netErr.message}`);
|
|
56
|
+
err.exitCode = 4;
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!res.ok) {
|
|
61
|
+
let errBody = '';
|
|
62
|
+
try {
|
|
63
|
+
errBody = await res.text();
|
|
64
|
+
} catch (_) {}
|
|
65
|
+
if (res.status === 401) {
|
|
66
|
+
const err = new Error(
|
|
67
|
+
`Brak dostępu do Kosztorysu (HTTP 401). Klucz wygasł lub jest niepoprawny.\nUruchom ponowne logowanie: deertools login estimate`
|
|
68
|
+
);
|
|
69
|
+
err.exitCode = 3;
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
const err = new Error(`Serwer Kosztorysu zwrócił błąd HTTP ${res.status}: ${errBody || res.statusText}`);
|
|
73
|
+
err.exitCode = 4;
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const json = await res.json();
|
|
78
|
+
if (json.error) {
|
|
79
|
+
const err = new Error(`Błąd kalkulacji Kosztorysu: ${json.error.message || JSON.stringify(json.error)}`);
|
|
80
|
+
err.exitCode = 4;
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (json.result?.structuredContent) {
|
|
85
|
+
return json.result.structuredContent;
|
|
86
|
+
}
|
|
87
|
+
if (json.result?.content?.[0]?.text) {
|
|
88
|
+
try {
|
|
89
|
+
return JSON.parse(json.result.content[0].text);
|
|
90
|
+
} catch (_) {
|
|
91
|
+
return json.result.content[0].text;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return json.result || {};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function calculateEstimate(params, options = {}) {
|
|
99
|
+
return callEstimateMcp('estimate_calculate_simple', params, options);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function getEstimateMargins(params, options = {}) {
|
|
103
|
+
return callEstimateMcp('estimate_get_margins', params, options);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
callEstimateMcp,
|
|
108
|
+
calculateEstimate,
|
|
109
|
+
getEstimateMargins,
|
|
110
|
+
};
|
package/src/mcp-stdio.js
CHANGED
|
@@ -5,6 +5,7 @@ const { ALL_PRODUCTS, getProduct } = require('./registry');
|
|
|
5
5
|
const { apiRequest } = require('./http');
|
|
6
6
|
const { checkSkillUpdates, updateSkills } = require('./skill-commands');
|
|
7
7
|
const skillsHub = require('./skills-hub');
|
|
8
|
+
const { calculateEstimate, getEstimateMargins } = require('./estimate-client');
|
|
8
9
|
|
|
9
10
|
const PROTOCOL_VERSION = '2024-11-05';
|
|
10
11
|
const SERVER_INFO = { name: 'deertools-stdio-bridge', version: '1.0.5' };
|
|
@@ -16,13 +17,19 @@ async function cmdMcpServe(pos, flags) {
|
|
|
16
17
|
process.stdout.write(JSON.stringify(msg) + '\n');
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
let pendingCount = 0;
|
|
21
|
+
let isClosed = false;
|
|
22
|
+
let closeResolve = null;
|
|
23
|
+
|
|
19
24
|
rl.on('line', async (line) => {
|
|
20
25
|
if (!line.trim()) return;
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
pendingCount++;
|
|
27
|
+
try {
|
|
28
|
+
let req;
|
|
29
|
+
try { req = JSON.parse(line); } catch (e) {
|
|
30
|
+
send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
26
33
|
|
|
27
34
|
const { id, method, params } = req;
|
|
28
35
|
|
|
@@ -47,12 +54,25 @@ async function cmdMcpServe(pos, flags) {
|
|
|
47
54
|
const tools = [
|
|
48
55
|
{
|
|
49
56
|
name: 'estimate_calculate',
|
|
50
|
-
description: 'Szybkie wyliczenie kosztu wykończenia mieszkania z podanego metrażu w pakietach Smart Life, Modern Life, High Life.',
|
|
57
|
+
description: 'Szybkie wyliczenie kosztu wykończenia mieszkania z podanego metrażu w pakietach Smart Life, Modern Life, High Life (kalkulacja po stronie serwera Kosztorys).',
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
properties: {
|
|
61
|
+
area_m2: { type: 'number', description: 'Metraż mieszkania w m²' },
|
|
62
|
+
package: { type: 'string', enum: ['smart_life', 'modern_life', 'high_life'], description: 'Pakiet wykończeniowy (opcjonalny — bez podania zwraca 3 pakiety)' },
|
|
63
|
+
wet_zones: { type: 'number', description: 'Liczba stref mokrych (np. łazienki, WC)' },
|
|
64
|
+
doors: { type: 'number', description: 'Liczba kompletów drzwi wewnętrznych' },
|
|
65
|
+
},
|
|
66
|
+
required: ['area_m2'],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'estimate_get_margins',
|
|
71
|
+
description: 'Pobiera reguły wyliczania marży i strukturę kosztorysową Deer Design dla zadanego metrażu z serwera Kosztorys.',
|
|
51
72
|
inputSchema: {
|
|
52
73
|
type: 'object',
|
|
53
74
|
properties: {
|
|
54
75
|
area_m2: { type: 'number', description: 'Metraż mieszkania w m²' },
|
|
55
|
-
package: { type: 'string', enum: ['smart_life', 'modern_life', 'high_life'], description: 'Pakiet wykończeniowy' },
|
|
56
76
|
},
|
|
57
77
|
required: ['area_m2'],
|
|
58
78
|
},
|
|
@@ -161,16 +181,16 @@ async function cmdMcpServe(pos, flags) {
|
|
|
161
181
|
try {
|
|
162
182
|
if (toolName === 'estimate_calculate') {
|
|
163
183
|
const area = Number(args.area_m2);
|
|
164
|
-
const pkg = args.package
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
184
|
+
const pkg = args.package;
|
|
185
|
+
const wetZones = args.wet_zones !== undefined ? Number(args.wet_zones) : undefined;
|
|
186
|
+
const doors = args.doors !== undefined ? Number(args.doors) : undefined;
|
|
187
|
+
|
|
188
|
+
const data = await calculateEstimate({
|
|
189
|
+
area_m2: area,
|
|
190
|
+
package: pkg,
|
|
191
|
+
wet_zones: wetZones,
|
|
192
|
+
doors: doors,
|
|
193
|
+
});
|
|
174
194
|
|
|
175
195
|
send({
|
|
176
196
|
jsonrpc: '2.0',
|
|
@@ -178,15 +198,26 @@ async function cmdMcpServe(pos, flags) {
|
|
|
178
198
|
result: {
|
|
179
199
|
content: [{
|
|
180
200
|
type: 'text',
|
|
181
|
-
text: JSON.stringify(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
201
|
+
text: JSON.stringify(data, null, 2),
|
|
202
|
+
}],
|
|
203
|
+
structuredContent: data,
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (toolName === 'estimate_get_margins') {
|
|
210
|
+
const area = Number(args.area_m2 || 50);
|
|
211
|
+
const data = await getEstimateMargins({ area_m2: area });
|
|
212
|
+
send({
|
|
213
|
+
jsonrpc: '2.0',
|
|
214
|
+
id,
|
|
215
|
+
result: {
|
|
216
|
+
content: [{
|
|
217
|
+
type: 'text',
|
|
218
|
+
text: JSON.stringify(data, null, 2),
|
|
189
219
|
}],
|
|
220
|
+
structuredContent: data,
|
|
190
221
|
},
|
|
191
222
|
});
|
|
192
223
|
return;
|
|
@@ -383,6 +414,23 @@ async function cmdMcpServe(pos, flags) {
|
|
|
383
414
|
}
|
|
384
415
|
|
|
385
416
|
send({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Method not found' } });
|
|
417
|
+
} finally {
|
|
418
|
+
pendingCount--;
|
|
419
|
+
if (isClosed && pendingCount === 0 && closeResolve) {
|
|
420
|
+
closeResolve();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
rl.on('close', () => {
|
|
426
|
+
isClosed = true;
|
|
427
|
+
if (pendingCount === 0 && closeResolve) {
|
|
428
|
+
closeResolve();
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
return new Promise((resolve) => {
|
|
433
|
+
closeResolve = resolve;
|
|
386
434
|
});
|
|
387
435
|
}
|
|
388
436
|
|