@deerdesign/deertools 1.2.13 → 1.2.21
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 +22 -10
- package/package.json +1 -1
- package/src/agent-detector.js +127 -15
- package/src/cli.js +3 -0
- package/src/commands.js +128 -7
- package/src/mcp-stdio.js +106 -0
- package/src/skill-commands.js +547 -49
- package/src/skills-hub.js +159 -2
- package/src/team-commands.js +151 -0
package/src/skills-hub.js
CHANGED
|
@@ -211,7 +211,42 @@ function compareManifests(manifestA = [], manifestB = []) {
|
|
|
211
211
|
return { added, removed, modified };
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
|
|
214
|
+
const COMPANY_OFFICIAL_SKILLS = new Set([
|
|
215
|
+
'deer-presentation',
|
|
216
|
+
'deer-technical-documentation',
|
|
217
|
+
'deer-catalog',
|
|
218
|
+
'deer-kosztorys',
|
|
219
|
+
'deertools-dostawy',
|
|
220
|
+
'deertools-supplier-hub',
|
|
221
|
+
'rendermind',
|
|
222
|
+
'deer-skills-navigator',
|
|
223
|
+
'deer-diag-robot',
|
|
224
|
+
'deer-mobile-build',
|
|
225
|
+
]);
|
|
226
|
+
|
|
227
|
+
function extractFrontmatterTags(fm) {
|
|
228
|
+
const tags = [];
|
|
229
|
+
const inlineListMatch = fm.match(/^tags:\s*\[(.*?)\]/m);
|
|
230
|
+
if (inlineListMatch) {
|
|
231
|
+
return inlineListMatch[1].split(',').map((s) => s.trim().replace(/^['"]|['"]$/g, '')).filter(Boolean);
|
|
232
|
+
}
|
|
233
|
+
const blockMatch = fm.match(/^tags:\s*\n((?:\s*-[^\n]+\n?)+)/m);
|
|
234
|
+
if (blockMatch) {
|
|
235
|
+
const lines = blockMatch[1].split('\n');
|
|
236
|
+
for (const l of lines) {
|
|
237
|
+
const m = l.match(/^\s*-\s*(.+)$/);
|
|
238
|
+
if (m) tags.push(m[1].trim().replace(/^['"]|['"]$/g, ''));
|
|
239
|
+
}
|
|
240
|
+
return tags.filter(Boolean);
|
|
241
|
+
}
|
|
242
|
+
const inlineCommaMatch = fm.match(/^tags:\s*([^\n#]+)$/m);
|
|
243
|
+
if (inlineCommaMatch) {
|
|
244
|
+
return inlineCommaMatch[1].split(',').map((s) => s.trim().replace(/^['"]|['"]$/g, '')).filter(Boolean);
|
|
245
|
+
}
|
|
246
|
+
return [];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function pushSkill(skillDir, { version = null, bump = 'patch', changelog = null, visibility = null, scope = null, namespace = null, team = null, tags = null } = {}) {
|
|
215
250
|
const { mdPath, baseDir, manifest } = gatherSkillBundle(skillDir);
|
|
216
251
|
const skillContent = fs.readFileSync(mdPath, 'utf8');
|
|
217
252
|
|
|
@@ -236,6 +271,7 @@ async function pushSkill(skillDir, { version = null, bump = 'patch', changelog =
|
|
|
236
271
|
let slug = path.basename(baseDir);
|
|
237
272
|
let title = slug;
|
|
238
273
|
let description = '';
|
|
274
|
+
let frontmatterTags = [];
|
|
239
275
|
if (skillContent.startsWith('---')) {
|
|
240
276
|
const parts = skillContent.split('---', 2);
|
|
241
277
|
if (parts.length >= 2) {
|
|
@@ -246,9 +282,19 @@ async function pushSkill(skillDir, { version = null, bump = 'patch', changelog =
|
|
|
246
282
|
}
|
|
247
283
|
const descMatch = parts[1].match(/^description:\s*(.+)$/m);
|
|
248
284
|
if (descMatch) description = descMatch[1].trim();
|
|
285
|
+
frontmatterTags = extractFrontmatterTags(parts[1]);
|
|
249
286
|
}
|
|
250
287
|
}
|
|
251
288
|
|
|
289
|
+
let finalTags = [];
|
|
290
|
+
if (tags && Array.isArray(tags)) {
|
|
291
|
+
finalTags = tags.map((t) => String(t).trim()).filter(Boolean);
|
|
292
|
+
} else if (typeof tags === 'string') {
|
|
293
|
+
finalTags = tags.split(',').map((t) => t.trim()).filter(Boolean);
|
|
294
|
+
} else if (frontmatterTags.length > 0) {
|
|
295
|
+
finalTags = frontmatterTags;
|
|
296
|
+
}
|
|
297
|
+
|
|
252
298
|
// 3. Automatyczne ustalenie wersji, jeśli nie podano
|
|
253
299
|
let targetVersion = version;
|
|
254
300
|
if (!targetVersion) {
|
|
@@ -268,17 +314,29 @@ async function pushSkill(skillDir, { version = null, bump = 'patch', changelog =
|
|
|
268
314
|
finalChangelog = gitMsg || 'Aktualizacja paczki przez DeerTools CLI';
|
|
269
315
|
}
|
|
270
316
|
|
|
317
|
+
// 5. Wyznaczenie zakresu i widoczności:
|
|
318
|
+
// Oficjalne firmowe (prezentacje, dokumentacja techniczna, aplikacje) -> globalne i współdzielone.
|
|
319
|
+
// Zespołowe (--team) -> przypisane do zespołu i współdzielone w zespole (bez konieczności akceptacji admina).
|
|
320
|
+
// Wszystkie inne skille -> domyślnie prywatne we własnej przestrzeni użytkownika (@username/slug).
|
|
321
|
+
const isCompanyOfficial = COMPANY_OFFICIAL_SKILLS.has(slug);
|
|
322
|
+
const targetScope = scope || (isCompanyOfficial ? 'global' : (team ? 'team' : 'user'));
|
|
323
|
+
const targetVisibility = visibility || (isCompanyOfficial || team ? 'shared' : 'private');
|
|
324
|
+
|
|
271
325
|
const body = {
|
|
272
326
|
slug,
|
|
273
327
|
title,
|
|
274
328
|
summary: description,
|
|
275
329
|
version: targetVersion,
|
|
276
330
|
changelog: finalChangelog,
|
|
277
|
-
visibility,
|
|
331
|
+
visibility: targetVisibility,
|
|
278
332
|
skill_md: skillContent,
|
|
279
333
|
skill_md_content: skillContent,
|
|
280
334
|
files_manifest: manifest,
|
|
335
|
+
scope: targetScope,
|
|
336
|
+
tags: finalTags,
|
|
281
337
|
};
|
|
338
|
+
if (namespace) body.namespace = namespace;
|
|
339
|
+
if (team) body.team = team;
|
|
282
340
|
|
|
283
341
|
const res = await hubRequest('/skills/push', { method: 'POST', body });
|
|
284
342
|
if (res.ok && res.data) {
|
|
@@ -289,6 +347,62 @@ async function pushSkill(skillDir, { version = null, bump = 'patch', changelog =
|
|
|
289
347
|
return res;
|
|
290
348
|
}
|
|
291
349
|
|
|
350
|
+
/**
|
|
351
|
+
* Udostępnia skill wskazanemu zespołowi lub przywraca prywatność.
|
|
352
|
+
*/
|
|
353
|
+
async function shareSkill(slug, { team = null, makePrivate = false } = {}) {
|
|
354
|
+
const body = {
|
|
355
|
+
slug,
|
|
356
|
+
team: makePrivate ? 'private' : (team || null),
|
|
357
|
+
};
|
|
358
|
+
return hubRequest('/skills/share', { method: 'POST', body });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Pobiera listę zespołów w organizacji z DeerSkills Hub.
|
|
363
|
+
*/
|
|
364
|
+
async function listTeams() {
|
|
365
|
+
return hubRequest('/teams');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Pobiera listę członków zespołów z DeerSkills Hub.
|
|
370
|
+
*/
|
|
371
|
+
async function listTeamMembers(team = null) {
|
|
372
|
+
const qs = team ? `?team=${encodeURIComponent(team)}` : '';
|
|
373
|
+
return hubRequest(`/teams/members${qs}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Tworzy lub aktualizuje zespół w organizacji (wymaga Admin).
|
|
378
|
+
*/
|
|
379
|
+
async function configureTeam({ name, slug, description = '', icon = 'Users', color = '#FF6A00' }) {
|
|
380
|
+
return hubRequest('/teams', {
|
|
381
|
+
method: 'POST',
|
|
382
|
+
body: { name, slug, description, icon, color },
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Dodaje lub zmienia rolę członka zespołu (wymaga Admin lub Lider).
|
|
388
|
+
*/
|
|
389
|
+
async function addTeamMember({ team, email, role = 'member' }) {
|
|
390
|
+
return hubRequest('/teams/members', {
|
|
391
|
+
method: 'POST',
|
|
392
|
+
body: { team, email, role },
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Usuwa członka z zespołu (wymaga Admin lub Lider).
|
|
398
|
+
*/
|
|
399
|
+
async function removeTeamMember({ team, email }) {
|
|
400
|
+
return hubRequest('/teams/members/remove', {
|
|
401
|
+
method: 'POST',
|
|
402
|
+
body: { team, email },
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
292
406
|
async function listMcpServers() {
|
|
293
407
|
return hubRequest('/mcp-servers');
|
|
294
408
|
}
|
|
@@ -312,6 +426,39 @@ async function submitPromotion(skillSlug, proposedGlobalSlug, justification) {
|
|
|
312
426
|
});
|
|
313
427
|
}
|
|
314
428
|
|
|
429
|
+
/**
|
|
430
|
+
* Pobiera listę zgłoszeń Skill Peer Review z DeerSkills Hub.
|
|
431
|
+
*/
|
|
432
|
+
async function listReviews(params = {}) {
|
|
433
|
+
const query = new URLSearchParams();
|
|
434
|
+
if (params.status && params.status !== 'all') query.set('status', params.status);
|
|
435
|
+
if (params.skill_id) query.set('skill_id', params.skill_id);
|
|
436
|
+
const qs = query.toString();
|
|
437
|
+
return hubRequest(`/reviews${qs ? `?${qs}` : ''}`);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Zatwierdza (approve) lub odrzuca / prosi o poprawki (changes_requested) dla zgłoszenia w Peer Review.
|
|
442
|
+
*/
|
|
443
|
+
async function decideReview({ slug, reviewId, skillId, approve, notes = '' }) {
|
|
444
|
+
const body = {
|
|
445
|
+
slug,
|
|
446
|
+
review_id: reviewId,
|
|
447
|
+
skill_id: skillId,
|
|
448
|
+
approve: Boolean(approve),
|
|
449
|
+
notes: String(notes || ''),
|
|
450
|
+
};
|
|
451
|
+
return hubRequest('/reviews/decide', { method: 'POST', body });
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Zgłasza skill do weryfikacji zespołu / Peer Review w DeerSkills Hub.
|
|
456
|
+
*/
|
|
457
|
+
async function submitReview(slug) {
|
|
458
|
+
const body = { slug };
|
|
459
|
+
return hubRequest('/reviews/submit', { method: 'POST', body });
|
|
460
|
+
}
|
|
461
|
+
|
|
315
462
|
module.exports = {
|
|
316
463
|
getApiBase,
|
|
317
464
|
getApiKey,
|
|
@@ -328,4 +475,14 @@ module.exports = {
|
|
|
328
475
|
getProducts,
|
|
329
476
|
getDocs,
|
|
330
477
|
submitPromotion,
|
|
478
|
+
listReviews,
|
|
479
|
+
decideReview,
|
|
480
|
+
submitReview,
|
|
481
|
+
shareSkill,
|
|
482
|
+
listTeams,
|
|
483
|
+
listTeamMembers,
|
|
484
|
+
configureTeam,
|
|
485
|
+
addTeamMember,
|
|
486
|
+
removeTeamMember,
|
|
487
|
+
COMPANY_OFFICIAL_SKILLS,
|
|
331
488
|
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const skillsHub = require('./skills-hub');
|
|
3
|
+
|
|
4
|
+
async function cmdTeam(pos, flags, out, err) {
|
|
5
|
+
const sub = pos[0] || 'list';
|
|
6
|
+
|
|
7
|
+
// 1. Listowanie zespołów
|
|
8
|
+
if (sub === 'list' || sub === 'ls') {
|
|
9
|
+
out('Pobieram listę zespołów z DeerSkills Hub...');
|
|
10
|
+
const res = await skillsHub.listTeams();
|
|
11
|
+
if (!res.ok) {
|
|
12
|
+
err(`✗ Błąd pobierania zespołów (${res.status}): ${res.data?.error || 'Brak dostępu'}`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const items = Array.isArray(res.data) ? res.data : (res.data?.teams || []);
|
|
17
|
+
if (flags.json) {
|
|
18
|
+
out(JSON.stringify(items, null, 2));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
out(`\n=== Zespoły Deer Design (${items.length}) ===\n`);
|
|
23
|
+
for (const t of items) {
|
|
24
|
+
const slugBadge = `[${t.slug}]`;
|
|
25
|
+
const memCount = t.members_count !== undefined ? `${t.members_count} osób` : '';
|
|
26
|
+
const skillCount = t.skills_count !== undefined ? `${t.skills_count} skilli` : '';
|
|
27
|
+
const stats = [memCount, skillCount].filter(Boolean).join(' • ');
|
|
28
|
+
|
|
29
|
+
out(` • \x1b[1m${t.name}\x1b[0m \x1b[36m${slugBadge}\x1b[0m ${stats ? `(\x1b[33m${stats}\x1b[0m)` : ''}`);
|
|
30
|
+
if (t.description) out(` Opis: ${t.description}`);
|
|
31
|
+
out(` Komenda: \x1b[90mdeertools team members ${t.slug}\x1b[0m`);
|
|
32
|
+
out('');
|
|
33
|
+
}
|
|
34
|
+
out('─'.repeat(72));
|
|
35
|
+
out('Zarządzanie zespołami:');
|
|
36
|
+
out(' • Skład zespołu: deertools team members <zespół>');
|
|
37
|
+
out(' • Dodanie członka: deertools team add <zespół> <email> [--role lead|member]');
|
|
38
|
+
out(' • Usunięcie członka: deertools team remove <zespół> <email>');
|
|
39
|
+
out(' • Nowy zespół: deertools team create <nazwa> [--slug <slug>] [--desc "opis"]');
|
|
40
|
+
out(' • Udostępnienie: deertools skill share <slug> --team <zespół>');
|
|
41
|
+
out('─'.repeat(72) + '\n');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 2. Członkowie zespołu
|
|
46
|
+
if (sub === 'members' || sub === 'users') {
|
|
47
|
+
const teamSlug = pos[1] || flags.team || null;
|
|
48
|
+
out(teamSlug ? `Pobieram członków zespołu „${teamSlug}”...` : 'Pobieram listę członków wszystkich zespołów...');
|
|
49
|
+
const res = await skillsHub.listTeamMembers(teamSlug);
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
err(`✗ Błąd pobierania członków (${res.status}): ${res.data?.error || 'Brak dostępu'}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const items = Array.isArray(res.data) ? res.data : [];
|
|
56
|
+
if (flags.json) {
|
|
57
|
+
out(JSON.stringify(items, null, 2));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (items.length === 0) {
|
|
62
|
+
out(teamSlug ? `\nBrak przypisanych członków w zespole „${teamSlug}”.` : '\nBrak przypisanych członków.');
|
|
63
|
+
out(`Aby dodać pracownika: deertools team add ${teamSlug || '<zespół>'} <email>\n`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
out(`\n=== Członkowie Zespołów Deer Design (${items.length}) ===\n`);
|
|
68
|
+
for (const m of items) {
|
|
69
|
+
const teamName = m.teams?.name || m.teams?.slug || m.team_id;
|
|
70
|
+
const teamSlugStr = m.teams?.slug ? `[${m.teams.slug}]` : '';
|
|
71
|
+
const email = m.user_email || 'Brak adresu e-mail';
|
|
72
|
+
const roleBadge = m.role === 'lead'
|
|
73
|
+
? '\x1b[33m★ Lider zespołu\x1b[0m'
|
|
74
|
+
: '\x1b[36m● Członek\x1b[0m';
|
|
75
|
+
|
|
76
|
+
out(` • \x1b[1m${email.padEnd(36)}\x1b[0m ${roleBadge.padEnd(20)} ${teamName} \x1b[90m${teamSlugStr}\x1b[0m`);
|
|
77
|
+
}
|
|
78
|
+
out('');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 3. Dodanie członka
|
|
83
|
+
if (sub === 'add' || sub === 'invite') {
|
|
84
|
+
const team = pos[1] || flags.team;
|
|
85
|
+
const email = pos[2] || flags.email;
|
|
86
|
+
const role = flags.lead ? 'lead' : (flags.role || 'member');
|
|
87
|
+
|
|
88
|
+
if (!team || !email) {
|
|
89
|
+
throw new Error('Użycie: deertools team add <zespół> <email> [--role lead|member]');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
out(`Dodaję pracownika ${email} do zespołu „${team}” (rola: ${role})...`);
|
|
93
|
+
const res = await skillsHub.addTeamMember({ team, email, role });
|
|
94
|
+
if (!res.ok) {
|
|
95
|
+
throw new Error(`Nie udało się dodać członka do zespołu (${res.status}): ${res.data?.error || 'Błąd'}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
out(`\n\x1b[32m✓ Pracownik ${email} został pomyślnie dodany do zespołu „${team}” jako ${role === 'lead' ? 'Lider' : 'Członek'}.\x1b[0m`);
|
|
99
|
+
out(` Od teraz ma natychmiastowy dostęp do wszystkich skilli zespołu „${team}”.\n`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 4. Usunięcie członka
|
|
104
|
+
if (sub === 'remove' || sub === 'rm' || sub === 'delete') {
|
|
105
|
+
const team = pos[1] || flags.team;
|
|
106
|
+
const email = pos[2] || flags.email;
|
|
107
|
+
|
|
108
|
+
if (!team || !email) {
|
|
109
|
+
throw new Error('Użycie: deertools team remove <zespół> <email>');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
out(`Usuwam pracownika ${email} z zespołu „${team}”...`);
|
|
113
|
+
const res = await skillsHub.removeTeamMember({ team, email });
|
|
114
|
+
if (!res.ok) {
|
|
115
|
+
throw new Error(`Nie udało się usunąć członka z zespołu (${res.status}): ${res.data?.error || 'Błąd'}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
out(`\n\x1b[33m✓ Pracownik ${email} został usunięty z zespołu „${team}”.\x1b[0m\n`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 5. Tworzenie zespołu
|
|
123
|
+
if (sub === 'create' || sub === 'new') {
|
|
124
|
+
const name = pos[1] || flags.name;
|
|
125
|
+
if (!name) {
|
|
126
|
+
throw new Error('Użycie: deertools team create <nazwa> [--slug <identyfikator>] [--desc "opis"]');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const slug = flags.slug || name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
130
|
+
const description = flags.desc || flags.description || '';
|
|
131
|
+
const icon = flags.icon || 'Users';
|
|
132
|
+
const color = flags.color || '#FF6A00';
|
|
133
|
+
|
|
134
|
+
out(`Tworzę nowy zespół „${name}” (slug: ${slug})...`);
|
|
135
|
+
const res = await skillsHub.configureTeam({ name, slug, description, icon, color });
|
|
136
|
+
if (!res.ok) {
|
|
137
|
+
throw new Error(`Nie udało się utworzyć zespołu (${res.status}): ${res.data?.error || 'Błąd'}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
out(`\n\x1b[32m✓ Zespół „${name}” (${slug}) został pomyślnie utworzony w DeerSkills Hub.\x1b[0m`);
|
|
141
|
+
out(` Możesz teraz przypisywać do niego pracowników (deertools team add ${slug} <email>)`);
|
|
142
|
+
out(` oraz udostępniać skille (deertools skill share <slug> --team ${slug}).\n`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
throw new Error(`Nieznana podkomenda „team ${sub}”. Użyj: deertools team [list|members|add|remove|create]`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
cmdTeam,
|
|
151
|
+
};
|