@deerdesign/deertools 1.0.3 → 1.2.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/README.md +134 -7
- package/docs/prezentacja-it-deertools.html +645 -0
- package/docs/prezentacja-it-deertools.md +182 -0
- package/package.json +2 -1
- package/src/agent-detector.js +160 -0
- package/src/audit.js +65 -0
- package/src/baselinker/browser-login.js +214 -0
- package/src/baselinker/catalog-tools.js +457 -0
- package/src/baselinker/client.js +136 -0
- package/src/baselinker/crawler.js +157 -0
- package/src/baselinker/index.js +24 -0
- package/src/baselinker/mcp-server.js +153 -0
- package/src/baselinker/methods-index.json +1226 -0
- package/src/baselinker/schema-cache.json +6342 -0
- package/src/baselinker/ui.js +386 -0
- package/src/baselinker-commands.js +180 -0
- package/src/cli.js +22 -2
- package/src/commands.js +313 -32
- package/src/completion.js +189 -0
- package/src/credentials.js +39 -2
- package/src/doctor.js +286 -0
- package/src/domain-commands.js +288 -0
- package/src/formatters.js +50 -0
- package/src/mcp-stdio.js +197 -0
- package/src/profiles.js +78 -0
- package/src/registry.js +22 -1
- package/src/skill-commands.js +223 -0
- package/src/skills-hub.js +201 -0
- package/src/tui.js +139 -0
package/src/mcp-stdio.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const readline = require('readline');
|
|
3
|
+
const credentials = require('./credentials');
|
|
4
|
+
const { ALL_PRODUCTS } = require('./registry');
|
|
5
|
+
const { apiRequest } = require('./http');
|
|
6
|
+
|
|
7
|
+
const PROTOCOL_VERSION = '2024-11-05';
|
|
8
|
+
const SERVER_INFO = { name: 'deertools-stdio-bridge', version: '1.0.5' };
|
|
9
|
+
|
|
10
|
+
async function cmdMcpServe(pos, flags) {
|
|
11
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
|
|
12
|
+
|
|
13
|
+
function send(msg) {
|
|
14
|
+
process.stdout.write(JSON.stringify(msg) + '\n');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
rl.on('line', async (line) => {
|
|
18
|
+
if (!line.trim()) return;
|
|
19
|
+
let req;
|
|
20
|
+
try { req = JSON.parse(line); } catch (e) {
|
|
21
|
+
send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { id, method, params } = req;
|
|
26
|
+
|
|
27
|
+
if (method === 'initialize') {
|
|
28
|
+
send({
|
|
29
|
+
jsonrpc: '2.0',
|
|
30
|
+
id,
|
|
31
|
+
result: {
|
|
32
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
33
|
+
serverInfo: SERVER_INFO,
|
|
34
|
+
capabilities: { tools: {} },
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (method === 'notifications/initialized') {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (method === 'tools/list') {
|
|
45
|
+
const tools = [
|
|
46
|
+
{
|
|
47
|
+
name: 'estimate_calculate',
|
|
48
|
+
description: 'Szybkie wyliczenie kosztu wykończenia mieszkania z podanego metrażu w pakietach Smart Life, Modern Life, High Life.',
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
area_m2: { type: 'number', description: 'Metraż mieszkania w m²' },
|
|
53
|
+
package: { type: 'string', enum: ['smart_life', 'modern_life', 'high_life'], description: 'Pakiet wykończeniowy' },
|
|
54
|
+
},
|
|
55
|
+
required: ['area_m2'],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'catalog_search_products',
|
|
60
|
+
description: 'Wyszukiwanie produktów w Katalogu Connect Deer Design po frazie, kategorii i pakiecie.',
|
|
61
|
+
inputSchema: {
|
|
62
|
+
type: 'object',
|
|
63
|
+
properties: {
|
|
64
|
+
query: { type: 'string', description: 'Szukana fraza (nazwa, SKU, producent)' },
|
|
65
|
+
package: { type: 'string', enum: ['smart-life', 'modern-life', 'high-life'] },
|
|
66
|
+
limit: { type: 'number', default: 10 },
|
|
67
|
+
},
|
|
68
|
+
required: ['query'],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'deliveries_list',
|
|
73
|
+
description: 'Pobieranie listy dostaw z systemu logistycznego Delivery Hub.',
|
|
74
|
+
inputSchema: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
status: { type: 'string', description: 'Filtruj po statusie dostawy' },
|
|
78
|
+
limit: { type: 'number', default: 20 },
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'supplier_calculate_price',
|
|
84
|
+
description: 'Kalkulator łańcucha marżowego: cena katalogowa -> rabat zakupu -> narzut sprzedaży -> marża zysku.',
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: 'object',
|
|
87
|
+
properties: {
|
|
88
|
+
catalog_price_net: { type: 'number', description: 'Cena katalogowa netto' },
|
|
89
|
+
discount_pct: { type: 'number', description: 'Rabat zakupu w % (np. 35)' },
|
|
90
|
+
markup_pct: { type: 'number', description: 'Narzut sprzedaży w % (np. 20)' },
|
|
91
|
+
},
|
|
92
|
+
required: ['catalog_price_net'],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
send({ jsonrpc: '2.0', id, result: { tools } });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (method === 'tools/call') {
|
|
102
|
+
const toolName = params?.name;
|
|
103
|
+
const args = params?.arguments || {};
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
if (toolName === 'estimate_calculate') {
|
|
107
|
+
const area = Number(args.area_m2);
|
|
108
|
+
const pkg = args.package || 'modern_life';
|
|
109
|
+
const pkgs = {
|
|
110
|
+
smart_life: { name: 'Smart Life', rate: 1290, flat: 58050 },
|
|
111
|
+
modern_life: { name: 'Modern Life', rate: 1640, flat: 73800 },
|
|
112
|
+
high_life: { name: 'High Life', rate: 1990, flat: 89550 },
|
|
113
|
+
};
|
|
114
|
+
const p = pkgs[pkg] || pkgs.modern_life;
|
|
115
|
+
const isFlat = area < 45;
|
|
116
|
+
const gross = isFlat ? p.flat : Math.round(area * p.rate);
|
|
117
|
+
const net = Math.round(gross / 1.08);
|
|
118
|
+
|
|
119
|
+
send({
|
|
120
|
+
jsonrpc: '2.0',
|
|
121
|
+
id,
|
|
122
|
+
result: {
|
|
123
|
+
content: [{
|
|
124
|
+
type: 'text',
|
|
125
|
+
text: JSON.stringify({
|
|
126
|
+
package: p.name,
|
|
127
|
+
area_m2: area,
|
|
128
|
+
total_gross_pln: gross,
|
|
129
|
+
total_net_pln: net,
|
|
130
|
+
is_flat_rate: isFlat,
|
|
131
|
+
currency: 'PLN',
|
|
132
|
+
}, null, 2),
|
|
133
|
+
}],
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (toolName === 'catalog_search_products') {
|
|
140
|
+
const p = getProduct('katalog');
|
|
141
|
+
const res = await apiRequest(p, { method: 'GET', path: '/products', query: { search: args.query, program: args.package, limit: args.limit || 10 } });
|
|
142
|
+
send({
|
|
143
|
+
jsonrpc: '2.0',
|
|
144
|
+
id,
|
|
145
|
+
result: { content: [{ type: 'text', text: JSON.stringify(res.json?.data || res.json || [], null, 2) }] },
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (toolName === 'deliveries_list') {
|
|
151
|
+
const p = getProduct('dostawy');
|
|
152
|
+
const res = await apiRequest(p, { method: 'GET', path: '/deliveries-api/deliveries', query: { status: args.status, limit: args.limit || 20 } });
|
|
153
|
+
send({
|
|
154
|
+
jsonrpc: '2.0',
|
|
155
|
+
id,
|
|
156
|
+
result: { content: [{ type: 'text', text: JSON.stringify(res.json || [], null, 2) }] },
|
|
157
|
+
});
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (toolName === 'supplier_calculate_price') {
|
|
162
|
+
const cat = Number(args.catalog_price_net);
|
|
163
|
+
const disc = Number(args.discount_pct || 30);
|
|
164
|
+
const mark = Number(args.markup_pct || 20);
|
|
165
|
+
const buy = cat * (1 - disc / 100);
|
|
166
|
+
const sell = buy * (1 + mark / 100);
|
|
167
|
+
send({
|
|
168
|
+
jsonrpc: '2.0',
|
|
169
|
+
id,
|
|
170
|
+
result: {
|
|
171
|
+
content: [{
|
|
172
|
+
type: 'text',
|
|
173
|
+
text: JSON.stringify({
|
|
174
|
+
catalog_price_net: cat,
|
|
175
|
+
buy_price_net: Math.round(buy * 100) / 100,
|
|
176
|
+
sell_price_net: Math.round(sell * 100) / 100,
|
|
177
|
+
margin_net: Math.round((sell - buy) * 100) / 100,
|
|
178
|
+
margin_pct: Math.round(((sell - buy) / sell) * 1000) / 10,
|
|
179
|
+
}, null, 2),
|
|
180
|
+
}],
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Tool not found: ${toolName}` } });
|
|
187
|
+
} catch (e) {
|
|
188
|
+
send({ jsonrpc: '2.0', id, error: { code: -32000, message: e.message } });
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
send({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Method not found' } });
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = { cmdMcpServe };
|
package/src/profiles.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const { c } = require('./formatters');
|
|
6
|
+
|
|
7
|
+
function getConfigDir() {
|
|
8
|
+
return process.env.DEERTOOLS_HOME || path.join(os.homedir(), '.deertools');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getConfigFile() {
|
|
12
|
+
return path.join(getConfigDir(), 'config.json');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readConfig() {
|
|
16
|
+
const f = getConfigFile();
|
|
17
|
+
if (!fs.existsSync(f)) return { activeProfile: 'default', profiles: { default: {} } };
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
20
|
+
} catch (_) {
|
|
21
|
+
return { activeProfile: 'default', profiles: { default: {} } };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function writeConfig(cfg) {
|
|
26
|
+
const dir = getConfigDir();
|
|
27
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
28
|
+
fs.writeFileSync(getConfigFile(), JSON.stringify(cfg, null, 2), { mode: 0o600 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getActiveProfile() {
|
|
32
|
+
if (process.env.DEERTOOLS_PROFILE) return process.env.DEERTOOLS_PROFILE;
|
|
33
|
+
return readConfig().activeProfile || 'default';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function cmdProfile(pos, flags) {
|
|
37
|
+
const action = pos[0] || 'list';
|
|
38
|
+
const cfg = readConfig();
|
|
39
|
+
|
|
40
|
+
if (action === 'list') {
|
|
41
|
+
const active = getActiveProfile();
|
|
42
|
+
process.stdout.write(`\n${c.bold}Profile poświadczeń DeerTools:${c.reset}\n`);
|
|
43
|
+
const profiles = Object.keys(cfg.profiles || { default: {} });
|
|
44
|
+
if (!profiles.includes('default')) profiles.unshift('default');
|
|
45
|
+
for (const p of profiles) {
|
|
46
|
+
const isCur = p === active;
|
|
47
|
+
const marker = isCur ? `${c.green}* ${c.bold}` : ' ';
|
|
48
|
+
process.stdout.write(`${marker}${p}${isCur ? ' (aktywny)' : ''}${c.reset}\n`);
|
|
49
|
+
}
|
|
50
|
+
process.stdout.write(`\nAby przełączyć profil: deertools profile use <nazwa>\n`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (action === 'use') {
|
|
55
|
+
const target = pos[1];
|
|
56
|
+
if (!target) {
|
|
57
|
+
process.stderr.write('Błąd: Podaj nazwę profilu: deertools profile use <nazwa>\n');
|
|
58
|
+
process.exitCode = 2;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
cfg.activeProfile = target;
|
|
62
|
+
if (!cfg.profiles) cfg.profiles = {};
|
|
63
|
+
if (!cfg.profiles[target]) cfg.profiles[target] = {};
|
|
64
|
+
writeConfig(cfg);
|
|
65
|
+
process.stdout.write(`✓ Przełączono na profil: ${c.bold}${target}${c.reset}\n`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (action === 'current') {
|
|
70
|
+
process.stdout.write(`${getActiveProfile()}\n`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
process.stderr.write('Użycie: deertools profile [list|use <nazwa>|current]\n');
|
|
75
|
+
process.exitCode = 2;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { cmdProfile, getActiveProfile, readConfig, writeConfig };
|
package/src/registry.js
CHANGED
|
@@ -18,6 +18,7 @@ const PRODUCTS = {
|
|
|
18
18
|
tokenUrl: 'https://vlemuxlubrowuuxpgllk.supabase.co/functions/v1/oauth-token',
|
|
19
19
|
keyPrefix: 'deer_kosztorys_live_',
|
|
20
20
|
fallbackApiBase: 'https://vlemuxlubrowuuxpgllk.supabase.co/functions/v1',
|
|
21
|
+
mcpEndpoint: 'https://vlemuxlubrowuuxpgllk.supabase.co/functions/v1/estimate-mcp',
|
|
21
22
|
aliases: ['kosztorys', 'costs', 'cost'],
|
|
22
23
|
},
|
|
23
24
|
catalog: {
|
|
@@ -28,6 +29,7 @@ const PRODUCTS = {
|
|
|
28
29
|
tokenUrl: 'https://ftzaobeixglnssemugjf.supabase.co/functions/v1/oauth-token',
|
|
29
30
|
keyPrefix: 'deer_catalog_live_',
|
|
30
31
|
fallbackApiBase: 'https://ftzaobeixglnssemugjf.supabase.co/functions/v1/api-gateway',
|
|
32
|
+
mcpEndpoint: 'https://ftzaobeixglnssemugjf.supabase.co/functions/v1/catalog-mcp',
|
|
31
33
|
aliases: ['katalog'],
|
|
32
34
|
},
|
|
33
35
|
deliveries: {
|
|
@@ -38,6 +40,7 @@ const PRODUCTS = {
|
|
|
38
40
|
tokenUrl: 'https://ddyfbvogkumvscckotvb.supabase.co/functions/v1/oauth-token',
|
|
39
41
|
keyPrefix: 'deer_dostawy_live_',
|
|
40
42
|
fallbackApiBase: 'https://ddyfbvogkumvscckotvb.supabase.co/functions/v1',
|
|
43
|
+
mcpEndpoint: 'https://ddyfbvogkumvscckotvb.supabase.co/functions/v1/mcp-server',
|
|
41
44
|
aliases: ['dostawy', 'shipping', 'delivery', 'ship'],
|
|
42
45
|
},
|
|
43
46
|
supplier: {
|
|
@@ -48,6 +51,7 @@ const PRODUCTS = {
|
|
|
48
51
|
tokenUrl: 'https://hubnxvzxdgocbtmkfdsz.supabase.co/functions/v1/oauth-token',
|
|
49
52
|
keyPrefix: 'deer_supplier_live_',
|
|
50
53
|
fallbackApiBase: 'https://hubnxvzxdgocbtmkfdsz.supabase.co/functions/v1/public-api',
|
|
54
|
+
mcpEndpoint: 'https://hubnxvzxdgocbtmkfdsz.supabase.co/functions/v1/mcp',
|
|
51
55
|
aliases: [],
|
|
52
56
|
},
|
|
53
57
|
rendermind: {
|
|
@@ -58,8 +62,20 @@ const PRODUCTS = {
|
|
|
58
62
|
tokenUrl: 'https://lkljsuptgyktkwojnwbu.supabase.co/functions/v1/oauth-token',
|
|
59
63
|
keyPrefix: 'deer_rendermind_live_',
|
|
60
64
|
fallbackApiBase: 'https://lkljsuptgyktkwojnwbu.supabase.co/functions/v1/rendermind-api',
|
|
65
|
+
mcpEndpoint: 'https://lkljsuptgyktkwojnwbu.supabase.co/functions/v1/mcp',
|
|
61
66
|
aliases: [],
|
|
62
67
|
},
|
|
68
|
+
skills: {
|
|
69
|
+
name: 'DeerSkills',
|
|
70
|
+
localName: 'DeerSkills Hub',
|
|
71
|
+
site: 'https://skills.deergroup.pl',
|
|
72
|
+
authorizeUrl: 'https://skills.deergroup.pl/oauth/authorize',
|
|
73
|
+
tokenUrl: 'https://ezspmlsnmxuhtzvwrnoj.supabase.co/functions/v1/oauth-token',
|
|
74
|
+
keyPrefix: 'deer_skills_live_',
|
|
75
|
+
fallbackApiBase: 'https://ezspmlsnmxuhtzvwrnoj.supabase.co/functions/v1/skills-api',
|
|
76
|
+
mcpEndpoint: 'https://ezspmlsnmxuhtzvwrnoj.supabase.co/functions/v1/skills-mcp',
|
|
77
|
+
aliases: ['hub', 'skill', 'umiejetnosci'],
|
|
78
|
+
},
|
|
63
79
|
};
|
|
64
80
|
|
|
65
81
|
const ALIASES = {
|
|
@@ -71,6 +87,9 @@ const ALIASES = {
|
|
|
71
87
|
shipping: 'deliveries',
|
|
72
88
|
delivery: 'deliveries',
|
|
73
89
|
ship: 'deliveries',
|
|
90
|
+
hub: 'skills',
|
|
91
|
+
skill: 'skills',
|
|
92
|
+
umiejetnosci: 'skills',
|
|
74
93
|
};
|
|
75
94
|
|
|
76
95
|
const ALL_IDS = Object.keys(PRODUCTS);
|
|
@@ -91,4 +110,6 @@ function resolveProducts(arg) {
|
|
|
91
110
|
return String(arg).split(',').map((s) => getProduct(s.trim()));
|
|
92
111
|
}
|
|
93
112
|
|
|
94
|
-
|
|
113
|
+
const ALL_PRODUCTS = ALL_IDS.map(getProduct);
|
|
114
|
+
|
|
115
|
+
module.exports = { PRODUCTS, ALIASES, ALL_IDS, ALL_PRODUCTS, getProduct, resolveProducts };
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { detectAgents, installSkillContent } = require('./agent-detector');
|
|
5
|
+
const skillsHub = require('./skills-hub');
|
|
6
|
+
const { parseFrontmatterName } = require('./openapi');
|
|
7
|
+
const { resolveProducts } = require('./registry');
|
|
8
|
+
const { fetchText } = require('./http');
|
|
9
|
+
|
|
10
|
+
const LOCK_FILE = '.deertools-lock.json';
|
|
11
|
+
|
|
12
|
+
function readLock(cwd = process.cwd()) {
|
|
13
|
+
const p = path.join(cwd, LOCK_FILE);
|
|
14
|
+
if (fs.existsSync(p)) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
17
|
+
} catch (_) {}
|
|
18
|
+
}
|
|
19
|
+
return { version: 1, skills: {} };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function writeLock(data, cwd = process.cwd()) {
|
|
23
|
+
const p = path.join(cwd, LOCK_FILE);
|
|
24
|
+
fs.writeFileSync(p, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function cmdSkillList(flags, out, err) {
|
|
28
|
+
out('Pobieram listę skilli z DeerSkills Hub...');
|
|
29
|
+
const res = await skillsHub.listSkills({
|
|
30
|
+
query: flags.query,
|
|
31
|
+
tag: flags.tag,
|
|
32
|
+
category: flags.category,
|
|
33
|
+
scope: flags.scope,
|
|
34
|
+
limit: flags.limit || 50,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
err(`✗ Błąd pobierania skilli (${res.status}): ${res.data?.error || 'Brak dostępu'}`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const items = res.data?.skills || [];
|
|
43
|
+
if (flags.json) {
|
|
44
|
+
out(JSON.stringify(items, null, 2));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
out(`\n=== Dostępne Skille w DeerSkills Hub (${items.length}) ===\n`);
|
|
49
|
+
for (const s of items) {
|
|
50
|
+
const badge = s.scope === 'global' ? '★ GLOBAL' : `@${s.namespace}`;
|
|
51
|
+
const ver = s.latest_version ? `v${s.latest_version}` : 'v1.0.0';
|
|
52
|
+
out(` • ${s.full_slug.padEnd(35)} [${ver}] [${badge}]`);
|
|
53
|
+
if (s.summary) out(` ${s.summary.slice(0, 90)}...`);
|
|
54
|
+
if (s.tags && s.tags.length) out(` Tagi: ${s.tags.join(', ')}`);
|
|
55
|
+
out('');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function cmdSkillSearch(query, flags, out, err) {
|
|
60
|
+
return cmdSkillList({ ...flags, query }, out, err);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function cmdSkillInstall(target, flags, out, err) {
|
|
64
|
+
let [slug, requestedVersion] = String(target || '').split('@');
|
|
65
|
+
slug = slug.trim();
|
|
66
|
+
|
|
67
|
+
// Jeśli brak argumentu lub "all" lub nazwa produktu Deer, zachowaj kompatybilność wsteczną
|
|
68
|
+
if (!slug || slug === 'all' || ['estimate', 'catalog', 'deliveries', 'supplier', 'rendermind'].includes(slug)) {
|
|
69
|
+
const products = resolveProducts(slug || 'all');
|
|
70
|
+
const agents = detectAgents({ dir: flags.dir, agent: flags.agent });
|
|
71
|
+
let installed = 0;
|
|
72
|
+
|
|
73
|
+
for (const product of products) {
|
|
74
|
+
const r = await fetchText(`${product.site}/skill/SKILL.md`);
|
|
75
|
+
if (!r.ok) continue;
|
|
76
|
+
const skillName = parseFrontmatterName(r.text) || `deer-${product.id}`;
|
|
77
|
+
for (const agent of agents) {
|
|
78
|
+
try {
|
|
79
|
+
const dest = installSkillContent(agent, skillName, r.text);
|
|
80
|
+
out(`✓ [${agent.id}] ${product.name}: ${dest}`);
|
|
81
|
+
installed += 1;
|
|
82
|
+
} catch (e) {
|
|
83
|
+
err(`✗ [${agent.id}] ${product.name}: ${e.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!installed) throw new Error('Nie zainstalowano żadnego skilla produktowego.');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Instalacja konkretnego skilla z DeerSkills Hub
|
|
92
|
+
out(`Pobieram skill „${slug}”${requestedVersion ? ` w wersji ${requestedVersion}` : ''} z DeerSkills Hub...`);
|
|
93
|
+
const res = await skillsHub.getSkill(slug, requestedVersion);
|
|
94
|
+
|
|
95
|
+
if (!res.ok || !res.data?.skill) {
|
|
96
|
+
throw new Error(`Nie znaleziono skilla „${slug}” w DeerSkills Hub (${res.status}): ${res.data?.error || 'Błąd'}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const skill = res.data.skill;
|
|
100
|
+
const versionData = res.data.version;
|
|
101
|
+
const agents = detectAgents({ dir: flags.dir, agent: flags.agent });
|
|
102
|
+
|
|
103
|
+
if (agents.length === 0) {
|
|
104
|
+
throw new Error('Nie wykryto żadnego środowiska agenta AI w bieżącym projekcie.');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let count = 0;
|
|
108
|
+
for (const agent of agents) {
|
|
109
|
+
try {
|
|
110
|
+
const dest = installSkillContent(agent, skill.slug, versionData.skill_md_content);
|
|
111
|
+
out(`✓ [${agent.id}] Zainstalowano ${skill.full_slug} (${versionData.version}) → ${dest}`);
|
|
112
|
+
count += 1;
|
|
113
|
+
|
|
114
|
+
// Jeśli są dodatkowe pliki w manifeście, zapisz je obok SKILL.md
|
|
115
|
+
if (versionData.files_manifest && Array.isArray(versionData.files_manifest)) {
|
|
116
|
+
const baseDir = path.dirname(dest);
|
|
117
|
+
for (const file of versionData.files_manifest) {
|
|
118
|
+
const targetFile = path.join(baseDir, file.path);
|
|
119
|
+
fs.mkdirSync(path.dirname(targetFile), { recursive: true });
|
|
120
|
+
fs.writeFileSync(targetFile, file.content, 'utf8');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} catch (e) {
|
|
124
|
+
err(`✗ [${agent.id}] ${skill.slug}: ${e.message}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Zapis do .deertools-lock.json
|
|
129
|
+
const lock = readLock();
|
|
130
|
+
lock.skills[skill.slug] = {
|
|
131
|
+
fullSlug: skill.full_slug,
|
|
132
|
+
version: versionData.version,
|
|
133
|
+
installedAt: new Date().toISOString(),
|
|
134
|
+
sha256: versionData.sha256_checksum,
|
|
135
|
+
};
|
|
136
|
+
writeLock(lock);
|
|
137
|
+
out(`✓ Zaktualizowano ${LOCK_FILE} (wersja ${versionData.version}).`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function cmdSkillPush(dir, flags, out, err) {
|
|
141
|
+
const targetDir = dir || process.cwd();
|
|
142
|
+
out(`Analizuję folder skilla: ${targetDir}...`);
|
|
143
|
+
|
|
144
|
+
const res = await skillsHub.pushSkill(targetDir, {
|
|
145
|
+
version: flags.version || '1.0.0',
|
|
146
|
+
changelog: flags.changelog || 'Aktualizacja przez DeerTools CLI',
|
|
147
|
+
visibility: flags.private ? 'private' : 'shared',
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (!res.ok) {
|
|
151
|
+
throw new Error(`Nie udało się opublikować skilla (${res.status}): ${res.data?.error || 'Błąd serwera'}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
out(`✓ Pomyślnie opublikowano skill w DeerSkills Hub:`);
|
|
155
|
+
out(` • Pełny identyfikator: ${res.data.skill.full_slug}`);
|
|
156
|
+
out(` • Wersja: ${res.data.version.version}`);
|
|
157
|
+
out(` • Widoczność: ${res.data.skill.visibility}`);
|
|
158
|
+
out(` • Komenda do instalacji przez innych:`);
|
|
159
|
+
out(` deertools skill install ${res.data.skill.full_slug}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function cmdSkillUpdate(flags, out, err) {
|
|
163
|
+
const lock = readLock();
|
|
164
|
+
const installedSkills = Object.keys(lock.skills || {});
|
|
165
|
+
|
|
166
|
+
if (installedSkills.length === 0) {
|
|
167
|
+
out('Brak zainstalowanych skilli w pliku .deertools-lock.json.');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
out(`Sprawdzam aktualizacje dla ${installedSkills.length} skilli...`);
|
|
172
|
+
let updatedCount = 0;
|
|
173
|
+
|
|
174
|
+
for (const slug of installedSkills) {
|
|
175
|
+
const current = lock.skills[slug];
|
|
176
|
+
try {
|
|
177
|
+
const res = await skillsHub.getSkill(slug);
|
|
178
|
+
if (res.ok && res.data?.version) {
|
|
179
|
+
const latestVer = res.data.version.version;
|
|
180
|
+
if (latestVer !== current.version) {
|
|
181
|
+
out(`Aktualizacja ${slug}: ${current.version} → ${latestVer}...`);
|
|
182
|
+
await cmdSkillInstall(`${slug}@${latestVer}`, flags, out, err);
|
|
183
|
+
updatedCount += 1;
|
|
184
|
+
} else {
|
|
185
|
+
out(` • ${slug} (${current.version}) — aktualny.`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
} catch (e) {
|
|
189
|
+
err(`✗ Błąd sprawdzania ${slug}: ${e.message}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (updatedCount > 0) {
|
|
194
|
+
out(`\n✓ Zaktualizowano pomyślnie ${updatedCount} skilli.`);
|
|
195
|
+
} else {
|
|
196
|
+
out('\nWszystkie zainstalowane skille są w najnowszych wersjach.');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function cmdSkillPromote(slug, flags, out, err) {
|
|
201
|
+
if (!slug) throw new Error('Podaj slug skilla do nominacji, np. deertools skill promote moj-skill');
|
|
202
|
+
const proposed = flags.to || slug.replace(/^@[^/]+\//, '');
|
|
203
|
+
const justification = flags.reason || 'Dopracowany skill rekomendowany do oficjalnego katalogu Deer Design.';
|
|
204
|
+
|
|
205
|
+
out(`Wysyłam wniosek o nominację skilla „${slug}” do katalogu globalnego jako „deer-${proposed}”...`);
|
|
206
|
+
const res = await skillsHub.submitPromotion(slug, `deer-${proposed}`, justification);
|
|
207
|
+
|
|
208
|
+
if (!res.ok) {
|
|
209
|
+
throw new Error(`Błąd zgłaszania nominacji (${res.status}): ${res.data?.error || 'Błąd'}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
out('✓ Wniosek o nominację został pomyślnie przesłany do administratorów.');
|
|
213
|
+
out(' Możesz śledzić status w panelu DeerSkills: https://skills.deergroup.pl/settings');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
module.exports = {
|
|
217
|
+
cmdSkillList,
|
|
218
|
+
cmdSkillSearch,
|
|
219
|
+
cmdSkillInstall,
|
|
220
|
+
cmdSkillPush,
|
|
221
|
+
cmdSkillUpdate,
|
|
222
|
+
cmdSkillPromote,
|
|
223
|
+
};
|