@deerdesign/deertools 1.2.35 → 1.3.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deerdesign/deertools",
3
- "version": "1.2.35",
3
+ "version": "1.3.0",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -117,33 +117,54 @@ async function handleMcpMessage(msg, client = null, mode = 'catalog') {
117
117
 
118
118
  function runBaseLinkerMcpServer(options = {}) {
119
119
  const { mode = 'catalog' } = options;
120
- const rl = readline.createInterface({
121
- input: process.stdin,
122
- output: process.stdout,
123
- terminal: false
124
- });
120
+ return new Promise((resolve) => {
121
+ let pendingCount = 0;
122
+ let isClosed = false;
123
+ let closeResolve = null;
125
124
 
126
- rl.on('line', async line => {
127
- const trimmed = line.trim();
128
- if (!trimmed) return;
129
- try {
130
- const msg = JSON.parse(trimmed);
131
- const res = await handleMcpMessage(msg, null, mode);
132
- if (res) {
133
- process.stdout.write(JSON.stringify(res) + '\n');
125
+ const rl = readline.createInterface({
126
+ input: process.stdin,
127
+ output: process.stdout,
128
+ terminal: false
129
+ });
130
+
131
+ rl.on('line', async line => {
132
+ const trimmed = line.trim();
133
+ if (!trimmed) return;
134
+ pendingCount++;
135
+ try {
136
+ const msg = JSON.parse(trimmed);
137
+ const res = await handleMcpMessage(msg, null, mode);
138
+ if (res) {
139
+ process.stdout.write(JSON.stringify(res) + '\n');
140
+ }
141
+ } catch (err) {
142
+ const errRes = {
143
+ jsonrpc: '2.0',
144
+ id: null,
145
+ error: { code: -32700, message: 'Parse error: ' + err.message }
146
+ };
147
+ process.stdout.write(JSON.stringify(errRes) + '\n');
148
+ } finally {
149
+ pendingCount--;
150
+ if (isClosed && pendingCount === 0 && closeResolve) {
151
+ closeResolve();
152
+ }
134
153
  }
135
- } catch (err) {
136
- const errRes = {
137
- jsonrpc: '2.0',
138
- id: null,
139
- error: { code: -32700, message: 'Parse error: ' + err.message }
140
- };
141
- process.stdout.write(JSON.stringify(errRes) + '\n');
142
- }
143
- });
154
+ });
144
155
 
145
- process.on('SIGINT', () => process.exit(0));
146
- process.on('SIGTERM', () => process.exit(0));
156
+ rl.on('close', () => {
157
+ isClosed = true;
158
+ if (pendingCount === 0) {
159
+ resolve();
160
+ } else {
161
+ closeResolve = resolve;
162
+ }
163
+ });
164
+
165
+ process.on('SIGINT', () => { rl.close(); resolve(); });
166
+ process.on('SIGTERM', () => { rl.close(); resolve(); });
167
+ });
147
168
  }
148
169
 
149
170
  module.exports = {
@@ -79,8 +79,7 @@ Zgodnie ze standardem architektonicznym DeerTools CLI:
79
79
  }
80
80
 
81
81
  if (sub === 'mcp') {
82
- runBaseLinkerMcpServer({ mode: flags.all ? 'all' : 'catalog' });
83
- return;
82
+ return await runBaseLinkerMcpServer({ mode: flags.all ? 'all' : 'catalog' });
84
83
  }
85
84
 
86
85
  if (sub === 'sync-schema') {
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+ const { runCatalogMcpServer } = require('./mcp-server');
3
+
4
+ async function cmdCatalog(pos, flags) {
5
+ const sub = pos[0] || 'help';
6
+
7
+ if (sub === 'mcp') {
8
+ return await runCatalogMcpServer();
9
+ }
10
+
11
+ console.log(`Użycie deertools catalog:
12
+ deertools catalog mcp - Uruchamia serwer MCP Katalogu Connect (stdio)
13
+ `);
14
+ return 0;
15
+ }
16
+
17
+ module.exports = {
18
+ cmdCatalog
19
+ };
@@ -0,0 +1,178 @@
1
+ 'use strict';
2
+ const readline = require('readline');
3
+ const credentials = require('../credentials');
4
+ const { getProduct } = require('../registry');
5
+
6
+ const PROTOCOL_VERSION = '2024-11-05';
7
+ const SERVER_INFO = { name: 'deer-catalog-mcp', version: '1.3.0' };
8
+
9
+ const FALLBACK_CATALOG_TOOLS = [
10
+ {
11
+ name: 'catalog_search_products',
12
+ description: 'Wyszukuje produkty wykończeniowe w Katalogu Connect (~17 tys. pozycji) z filtrowaniem po pakiecie (Smart Life, Modern Life, High Life) i kategorii.',
13
+ inputSchema: {
14
+ type: 'object',
15
+ properties: {
16
+ query: { type: 'string' },
17
+ package: { type: 'string' },
18
+ category: { type: 'string' },
19
+ limit: { type: 'number' }
20
+ }
21
+ }
22
+ },
23
+ {
24
+ name: 'catalog_get_product',
25
+ description: 'Pobiera szczegóły pojedynczego produktu z Katalogu Connect po ID lub SKU.',
26
+ inputSchema: {
27
+ type: 'object',
28
+ properties: {
29
+ id: { type: 'string' },
30
+ sku: { type: 'string' }
31
+ }
32
+ }
33
+ },
34
+ {
35
+ name: 'catalog_list_categories',
36
+ description: 'Pobiera drzewo kategorii Katalogu Connect.',
37
+ inputSchema: { type: 'object', properties: {} }
38
+ }
39
+ ];
40
+
41
+ function getCatalogKey() {
42
+ const p = getProduct('catalog');
43
+ const cred = credentials.getKey(p);
44
+ if (cred && cred.key) return cred.key;
45
+ if (process.env.CATALOG_API_KEY) return process.env.CATALOG_API_KEY;
46
+ if (process.env.DEERTOOLS_CATALOG_KEY) return process.env.DEERTOOLS_CATALOG_KEY;
47
+ return null;
48
+ }
49
+
50
+ function ensureCatalogAuth() {
51
+ const key = getCatalogKey();
52
+ if (!key) {
53
+ throw new Error('Brak klucza API do Katalogu Connect. Zaloguj się poleceniem: deertools login catalog');
54
+ }
55
+ return key;
56
+ }
57
+
58
+ async function forwardToCatalogMcp(req) {
59
+ const apiKey = ensureCatalogAuth();
60
+ const p = getProduct('catalog');
61
+ const endpoint = p.mcpEndpoint || 'https://ftzaobeixglnssemugjf.supabase.co/functions/v1/catalog-mcp';
62
+
63
+ const res = await fetch(endpoint, {
64
+ method: 'POST',
65
+ headers: {
66
+ 'Content-Type': 'application/json',
67
+ 'x-api-key': apiKey,
68
+ 'Authorization': `Bearer ${apiKey}`
69
+ },
70
+ body: JSON.stringify(req)
71
+ });
72
+
73
+ const text = await res.text();
74
+ let json = null;
75
+ try { json = JSON.parse(text); } catch (_) {}
76
+
77
+ if (!res.ok) {
78
+ const errorMsg = (json && (json.message || json.error)) || text;
79
+ return {
80
+ jsonrpc: '2.0',
81
+ id: req.id || null,
82
+ error: { code: -32000, message: `Katalog Connect MCP (${res.status}): ${errorMsg}` }
83
+ };
84
+ }
85
+
86
+ return json !== null ? json : { jsonrpc: '2.0', id: req.id, result: { content: [{ type: 'text', text }] } };
87
+ }
88
+
89
+ async function handleMcpMessage(msg) {
90
+ if (!msg || typeof msg !== 'object') return null;
91
+ const { id, method } = msg;
92
+
93
+ if (method === 'initialize') {
94
+ return {
95
+ jsonrpc: '2.0',
96
+ id,
97
+ result: {
98
+ protocolVersion: PROTOCOL_VERSION,
99
+ capabilities: { tools: {} },
100
+ serverInfo: SERVER_INFO
101
+ }
102
+ };
103
+ }
104
+
105
+ if (method === 'notifications/initialized') {
106
+ return null;
107
+ }
108
+
109
+ if (method === 'tools/list') {
110
+ if (!getCatalogKey()) {
111
+ return { jsonrpc: '2.0', id, result: { tools: FALLBACK_CATALOG_TOOLS } };
112
+ }
113
+ try {
114
+ const upstream = await forwardToCatalogMcp(msg);
115
+ if (upstream && upstream.result && Array.isArray(upstream.result.tools)) {
116
+ return upstream;
117
+ }
118
+ } catch (_) {}
119
+ return { jsonrpc: '2.0', id, result: { tools: FALLBACK_CATALOG_TOOLS } };
120
+ }
121
+
122
+ // Forward tools/call to upstream
123
+ return await forwardToCatalogMcp(msg);
124
+ }
125
+
126
+ function runCatalogMcpServer() {
127
+ return new Promise((resolve) => {
128
+ let pendingCount = 0;
129
+ let isClosed = false;
130
+ let closeResolve = null;
131
+
132
+ const rl = readline.createInterface({
133
+ input: process.stdin,
134
+ output: process.stdout,
135
+ terminal: false
136
+ });
137
+
138
+ rl.on('line', async (line) => {
139
+ const trimmed = line.trim();
140
+ if (!trimmed) return;
141
+ pendingCount++;
142
+ try {
143
+ const msg = JSON.parse(trimmed);
144
+ const resp = await handleMcpMessage(msg);
145
+ if (resp) {
146
+ process.stdout.write(JSON.stringify(resp) + '\n');
147
+ }
148
+ } catch (e) {
149
+ process.stdout.write(JSON.stringify({
150
+ jsonrpc: '2.0',
151
+ id: null,
152
+ error: { code: -32700, message: `Parse error: ${e.message}` }
153
+ }) + '\n');
154
+ } finally {
155
+ pendingCount--;
156
+ if (isClosed && pendingCount === 0 && closeResolve) {
157
+ closeResolve();
158
+ }
159
+ }
160
+ });
161
+
162
+ rl.on('close', () => {
163
+ isClosed = true;
164
+ if (pendingCount === 0) {
165
+ resolve();
166
+ } else {
167
+ closeResolve = resolve;
168
+ }
169
+ });
170
+ process.on('SIGINT', () => { rl.close(); resolve(); });
171
+ process.on('SIGTERM', () => { rl.close(); resolve(); });
172
+ });
173
+ }
174
+
175
+ module.exports = {
176
+ handleMcpMessage,
177
+ runCatalogMcpServer
178
+ };
package/src/cli.js CHANGED
@@ -54,6 +54,12 @@ async function main(argv) {
54
54
  baselinker: c.cmdBaseLinker,
55
55
  rendermind: c.cmdRenderMind,
56
56
  supplier: c.cmdSupplier,
57
+ estimate: c.cmdEstimate,
58
+ kosztorys: c.cmdEstimate,
59
+ catalog: c.cmdCatalog,
60
+ katalog: c.cmdCatalog,
61
+ deliveries: c.cmdDeliveries,
62
+ dostawy: c.cmdDeliveries,
57
63
  update: require('./updater').cmdUpdate,
58
64
  // Bezpiecznik architektoniczny: blokada bezpośrednich wywołań REST API z CLI
59
65
  api: () => c.cmdBlockedDomain('api'),
@@ -72,7 +78,8 @@ async function main(argv) {
72
78
 
73
79
  const cmd = pos.shift() || 'help';
74
80
 
75
- const shouldCheck = !flags.json && cmd !== 'update' && process.env.NODE_ENV !== 'test';
81
+ const isMcp = cmd === 'mcp' || pos.includes('mcp') || pos.includes('serve');
82
+ const shouldCheck = !flags.json && !isMcp && cmd !== 'update' && process.env.NODE_ENV !== 'test';
76
83
  const updatePromise = shouldCheck ? require('./updater').checkUpdate().catch(() => null) : null;
77
84
 
78
85
  const finish = async (code = 0) => {
package/src/commands.js CHANGED
@@ -91,17 +91,6 @@ async function cmdLogin(pos, flags) {
91
91
  for (const product of products) {
92
92
  out(`\n=== ${product.name} ===`);
93
93
  try {
94
- if (product.id === 'skills') {
95
- const { loginSkillsBrowser } = require('./skills-login');
96
- const entry = await loginSkillsBrowser({
97
- port: flags.port ? Number(flags.port) : 0,
98
- openBrowser: flags['no-open'] ? null : undefined,
99
- log: out,
100
- });
101
- out(`✓ Zalogowano do DeerSkills Hub. Zakres: ${entry.scope}.`);
102
- out(` Przegląd umiejętności: deertools skill list | Synchronizacja: deertools skill sync`);
103
- continue;
104
- }
105
94
  const productScope = (product.id === 'rendermind' && !explicitRo && !flags.scope && !flags.admin)
106
95
  ? 'read write'
107
96
  : scope;
@@ -310,29 +299,47 @@ function resolveDeertoolsBin() {
310
299
  return 'deertools';
311
300
  }
312
301
 
313
- function buildMcpServersConfig(products, useFullBinPath = false) {
302
+ function buildMcpServersConfig(products, useFullBinPath = false, { remote = false } = {}) {
314
303
  const servers = {};
315
304
  const binCmd = useFullBinPath ? resolveDeertoolsBin() : 'deertools';
316
305
 
317
- // Produkty z natywnym Remote MCP w chmurze obsługującym x-api-key
318
- const remoteApiKeyProducts = new Set(['estimate', 'catalog', 'deliveries', 'supplier']);
319
-
320
- for (const p of products) {
321
- if (remoteApiKeyProducts.has(p.id) && p.mcpEndpoint) {
322
- const cred = credentials.getKey(p);
323
- const key = cred ? cred.key : `${p.keyPrefix}<twój_klucz_api>`;
324
- const srvKey = `deer-${p.id}`;
325
- servers[srvKey] = {
326
- url: p.mcpEndpoint,
327
- headers: {
328
- 'x-api-key': key,
329
- 'Authorization': `Bearer ${key}`,
330
- },
331
- };
306
+ if (remote) {
307
+ // Opcjonalny tryb Remote HTTP (--remote) z nagłówkami x-api-key
308
+ const remoteApiKeyProducts = new Set(['estimate', 'catalog', 'deliveries', 'supplier']);
309
+ for (const p of products) {
310
+ if (remoteApiKeyProducts.has(p.id) && p.mcpEndpoint) {
311
+ const cred = credentials.getKey(p);
312
+ const key = cred ? cred.key : `${p.keyPrefix}<twój_klucz_api>`;
313
+ const srvKey = `deer-${p.id}`;
314
+ servers[srvKey] = {
315
+ url: p.mcpEndpoint,
316
+ headers: {
317
+ 'x-api-key': key,
318
+ 'Authorization': `Bearer ${key}`,
319
+ },
320
+ };
321
+ }
332
322
  }
323
+ } else {
324
+ // Domyślny tryb Local stdio MCP dla wszystkich systemów DeerTools (działający lokalnie w imieniu użytkownika)
325
+ servers['deer-estimate'] = {
326
+ command: binCmd,
327
+ args: ['estimate', 'mcp'],
328
+ };
329
+ servers['deer-catalog'] = {
330
+ command: binCmd,
331
+ args: ['catalog', 'mcp'],
332
+ };
333
+ servers['deer-deliveries'] = {
334
+ command: binCmd,
335
+ args: ['deliveries', 'mcp'],
336
+ };
337
+ servers['deer-supplier'] = {
338
+ command: binCmd,
339
+ args: ['supplier', 'mcp'],
340
+ };
333
341
  }
334
342
 
335
- // Serwery MCP stdio (silniki z obsługą plików lokalnych CAD/Excel lub długich procesów AI)
336
343
  servers['deer-rendermind'] = {
337
344
  command: binCmd,
338
345
  args: ['rendermind', 'mcp'],
@@ -349,45 +356,9 @@ function buildMcpServersConfig(products, useFullBinPath = false) {
349
356
  return servers;
350
357
  }
351
358
 
352
- async function cmdMcp(pos, flags) {
353
- const sub = pos[0] || 'config';
354
- const products = resolveProducts(pos[1]);
355
-
356
- if (sub === 'list') {
357
- const servers = buildMcpServersConfig(products, false);
358
- if (flags.json) {
359
- out(JSON.stringify(servers, null, 2));
360
- return;
361
- }
362
- out('\n=== Zarejestrowane Serwery MCP Ekosystemu DeerTools ===\n');
363
- for (const [name, cfg] of Object.entries(servers)) {
364
- if (cfg.url) {
365
- out(` • ${name.padEnd(18)} [Remote HTTP] ${cfg.url}`);
366
- } else {
367
- out(` • ${name.padEnd(18)} [Local stdio] ${cfg.command} ${(cfg.args || []).join(' ')}`);
368
- }
369
- }
370
- out('\nAby zainstalować w Cursor i Antigravity: deertools mcp install\n');
371
- return;
372
- }
373
-
374
- if (sub === 'config') {
375
- const servers = buildMcpServersConfig(products, false);
376
- const config = { mcpServers: servers };
377
- if (flags.json) {
378
- out(JSON.stringify(config, null, 2));
379
- } else {
380
- out('\nKonfiguracja MCP dla DeerTools (Cursor IDE, Claude Desktop, Antigravity):');
381
- out(JSON.stringify(config, null, 2));
382
- out('\nAby zainstalować automatycznie w Cursorze i Antigravity, uruchom:');
383
- out(' deertools mcp install\n');
384
- }
385
- return;
386
- }
387
-
388
- function installMcpServers(customProducts = null, silent = false) {
359
+ function installMcpServers(customProducts = null, silent = false, { remote = false } = {}) {
389
360
  const prods = customProducts || resolveProducts('all');
390
- const servers = buildMcpServersConfig(prods, true);
361
+ const servers = buildMcpServersConfig(prods, true, { remote });
391
362
  let touched = 0;
392
363
 
393
364
  // 1. Cursor global (~/.cursor/mcp.json)
@@ -458,7 +429,24 @@ function installMcpServers(customProducts = null, silent = false) {
458
429
  }
459
430
  }
460
431
 
461
- // 5. Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json)
432
+ // 5. Antigravity AppData (~/.gemini/antigravity/mcp_config.json)
433
+ const antigravityAppData = path.join(os.homedir(), '.gemini', 'antigravity', 'mcp_config.json');
434
+ if (fs.existsSync(path.dirname(antigravityAppData))) {
435
+ try {
436
+ let cur = { mcpServers: {} };
437
+ if (fs.existsSync(antigravityAppData)) {
438
+ try { cur = JSON.parse(fs.readFileSync(antigravityAppData, 'utf8')); } catch (_) {}
439
+ }
440
+ cur.mcpServers = Object.assign({}, cur.mcpServers, servers);
441
+ fs.writeFileSync(antigravityAppData, JSON.stringify(cur, null, 2));
442
+ if (!silent) out(`✓ Antigravity AppData: ${antigravityAppData}`);
443
+ touched++;
444
+ } catch (e) {
445
+ if (!silent) err(`✗ Antigravity AppData (${antigravityAppData}): ${e.message}`);
446
+ }
447
+ }
448
+
449
+ // 6. Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json)
462
450
  const claudeDesktop = path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
463
451
  if (fs.existsSync(path.dirname(claudeDesktop))) {
464
452
  try {
@@ -484,7 +472,7 @@ function installMcpServers(customProducts = null, silent = false) {
484
472
  }
485
473
  }
486
474
 
487
- // 6. Claude Code CLI (~/.claude.json)
475
+ // 7. Claude Code CLI (~/.claude.json)
488
476
  const claudeCli = path.join(os.homedir(), '.claude.json');
489
477
  if (fs.existsSync(claudeCli)) {
490
478
  try {
@@ -508,7 +496,7 @@ function installMcpServers(customProducts = null, silent = false) {
508
496
  }
509
497
  }
510
498
 
511
- // 7. Workspace .mcp.json (Claude Code / Cowork / Standard MCP workspace)
499
+ // 8. Workspace .mcp.json (Claude Code / Cowork / Standard MCP workspace)
512
500
  const workspaceMcp = path.join(process.cwd(), '.mcp.json');
513
501
  try {
514
502
  let cur = {};
@@ -535,8 +523,45 @@ function installMcpServers(customProducts = null, silent = false) {
535
523
  return touched;
536
524
  }
537
525
 
526
+ async function cmdMcp(pos, flags) {
527
+ const sub = pos[0] || 'config';
528
+ const products = resolveProducts(pos[1]);
529
+ const remote = Boolean(flags.remote);
530
+
531
+ if (sub === 'list') {
532
+ const servers = buildMcpServersConfig(products, false, { remote });
533
+ if (flags.json) {
534
+ out(JSON.stringify(servers, null, 2));
535
+ return;
536
+ }
537
+ out('\n=== Zarejestrowane Serwery MCP Ekosystemu DeerTools ===\n');
538
+ for (const [name, cfg] of Object.entries(servers)) {
539
+ if (cfg.url) {
540
+ out(` • ${name.padEnd(18)} [Remote HTTP] ${cfg.url}`);
541
+ } else {
542
+ out(` • ${name.padEnd(18)} [Local stdio] ${cfg.command} ${(cfg.args || []).join(' ')}`);
543
+ }
544
+ }
545
+ out('\nAby zainstalować w Cursor i Antigravity: deertools mcp install\n');
546
+ return;
547
+ }
548
+
549
+ if (sub === 'config') {
550
+ const servers = buildMcpServersConfig(products, false, { remote });
551
+ const config = { mcpServers: servers };
552
+ if (flags.json) {
553
+ out(JSON.stringify(config, null, 2));
554
+ } else {
555
+ out('\nKonfiguracja MCP dla DeerTools (Cursor IDE, Claude Desktop, Antigravity):');
556
+ out(JSON.stringify(config, null, 2));
557
+ out('\nAby zainstalować automatycznie w Cursorze i Antigravity, uruchom:');
558
+ out(' deertools mcp install\n');
559
+ }
560
+ return;
561
+ }
562
+
538
563
  if (sub === 'install') {
539
- const touched = installMcpServers(products, false);
564
+ const touched = installMcpServers(products, false, { remote });
540
565
  if (!touched) fail('Nie znaleziono środowiska agenta do instalacji MCP.', 4);
541
566
  out(`\nPomyślnie skonfigurowano wszystkie 7 serwerów MCP dla produktów DeerTools!`);
542
567
  return;
@@ -647,6 +672,9 @@ const { cmdCompletion } = require('./completion');
647
672
  const { cmdBaseLinker } = require('./baselinker-commands');
648
673
  const { cmdRenderMind } = require('./rendermind/cli-commands');
649
674
  const { cmdSupplier } = require('./supplier/cli-commands');
675
+ const { cmdEstimate } = require('./estimate/cli-commands');
676
+ const { cmdCatalog } = require('./catalog/cli-commands');
677
+ const { cmdDeliveries } = require('./deliveries/cli-commands');
650
678
 
651
679
  const HELP = `
652
680
  DeerTools CLI — Identity, MCP & Skills Hub Manager dla ekosystemu Deer Design
@@ -696,7 +724,8 @@ Zasada architektoniczna:
696
724
  module.exports = {
697
725
  cmdLogin, cmdLogout, cmdWhoami, cmdSkill, cmdMcp,
698
726
  cmdDoctor, cmdAudit, cmdProfile, cmdCompletion,
699
- cmdBaseLinker, cmdRenderMind, cmdSupplier,
727
+ cmdBaseLinker, cmdRenderMind, cmdSupplier, cmdEstimate,
728
+ cmdCatalog, cmdDeliveries,
700
729
  cmdAgent, cmdTeam, cmdBlockedDomain,
701
730
  HELP, parseQuery,
702
731
  };
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+ const { runDeliveriesMcpServer } = require('./mcp-server');
3
+
4
+ async function cmdDeliveries(pos, flags) {
5
+ const sub = pos[0] || 'help';
6
+
7
+ if (sub === 'mcp') {
8
+ return await runDeliveriesMcpServer();
9
+ }
10
+
11
+ console.log(`Użycie deertools deliveries:
12
+ deertools deliveries mcp - Uruchamia serwer MCP Dostaw (stdio)
13
+ `);
14
+ return 0;
15
+ }
16
+
17
+ module.exports = {
18
+ cmdDeliveries
19
+ };