@deerdesign/deertools 1.2.17 → 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 +17 -6
- package/package.json +1 -1
- package/src/cli.js +1 -0
- package/src/commands.js +33 -3
- package/src/mcp-stdio.js +106 -0
- package/src/skill-commands.js +201 -4
- package/src/skills-hub.js +159 -2
- package/src/team-commands.js +151 -0
package/README.md
CHANGED
|
@@ -192,12 +192,23 @@ deertools skill install deer-kosztorys # Zainstaluj konkretny skill w ~/.g
|
|
|
192
192
|
deertools skill install deer-kosztorys --version 1.0.0
|
|
193
193
|
deertools skill search "kosztorys" # Wyszukaj skille po nazwie, tagu lub opisie
|
|
194
194
|
deertools skill push ./my-skill # Opublikuj nową wersję z automatycznym skanerem sekretów
|
|
195
|
-
deertools skill push ./my-skill --
|
|
195
|
+
deertools skill push ./my-skill --tags "wycena, kosztorys, marże" # Dodaj tagi przy publikacji
|
|
196
|
+
deertools skill push ./my-skill --team architekci # Publikacja bezpośrednio do zespołu
|
|
197
|
+
deertools skill share my-skill --team architekci # Udostępnij skill dla wskazanego zespołu
|
|
198
|
+
deertools skill share my-skill --private # Cofnij udostępnienie (tylko dla autora)
|
|
196
199
|
deertools skill update deer-kosztorys # Zaktualizuj zainstalowany skill do najnowszej wersji
|
|
197
200
|
deertools skill update # Zaktualizuj wszystkie zainstalowane skille
|
|
198
201
|
deertools skill promote my-skill # Zgłoś wniosek do admina o nominację do globalnego katalogu
|
|
199
202
|
|
|
200
|
-
# 13.
|
|
203
|
+
# 13. Team Management (Konfiguracja Zespołów):
|
|
204
|
+
deertools team list # Lista zespołów, liczba członków i skilli
|
|
205
|
+
deertools team members architekci # Członkowie i role w zespole architekci
|
|
206
|
+
deertools team add architekci jan@deer.pl # Dodaj członka do zespołu (rola member)
|
|
207
|
+
deertools team add architekci ewa@deer.pl --role lead # Dodaj lidera zespołu
|
|
208
|
+
deertools team remove architekci jan@deer.pl # Usuń członka z zespołu
|
|
209
|
+
deertools team create "Projektanci Wnętrz" --slug projektanci # Utwórz nowy zespół
|
|
210
|
+
|
|
211
|
+
# 14. AI Agent MCP Registry & Configuration:
|
|
201
212
|
deertools mcp list # Lista dostępnych serwerów MCP w ekosystemie
|
|
202
213
|
deertools mcp config # Wygeneruj konfigurację JSON dla Cursor, Claude, Antigravity
|
|
203
214
|
deertools mcp install # Automatycznie dopisz konfigurację do narzędzi AI
|
|
@@ -234,10 +245,10 @@ deertools mcp install # Automatycznie dopisz konfiguracj
|
|
|
234
245
|
|
|
235
246
|
**DeerSkills Hub** (`https://skills.deergroup.pl`) to centralna platforma dystrybucji, wersjonowania i zarządzania umiejętnościami agentów AI (Skills) oraz serwerami MCP w ekosystemie Deer Design:
|
|
236
247
|
|
|
237
|
-
1. **Struktura i
|
|
238
|
-
- **Oficjalne Skille (`global`)**: Zweryfikowane,
|
|
239
|
-
- **Skille
|
|
240
|
-
- **
|
|
248
|
+
1. **Trzypoziomowa Struktura Uprawnień i Widoczności**:
|
|
249
|
+
- **Oficjalne Skille Firmowe (`global`)**: Zweryfikowane narzędzia firmowe, widoczne dla wszystkich pracowników w organizacji, instalowane domyślnie.
|
|
250
|
+
- **Skille Zespołowe (`team`)**: Dedykowane dla konkretnego zespołu (np. Architekci, Doradcy Klienta, Kosztorysy, Logistyka, IT-Dev). Widoczne i instalowalne **wyłącznie** przez członków danego zespołu. Zespoły i role są w pełni konfigurowalne przez `deertools team` oraz portal Web.
|
|
251
|
+
- **Prywatne Skille Użytkownika (`user`)**: Każdy pracownik publikuje pod własną przestrzenią `@uzytkownik/nazwa-skilla`. Są w 100% prywatne (niewidoczne dla innych), dopóki autor nie zdecyduje się ich udostępnić swojemu zespołowi (`deertools skill share --team <slug>`) lub zgłosić do publikacji globalnej (`deertools skill promote`).
|
|
241
252
|
2. **Automatyczny Skaner Sekretów**:
|
|
242
253
|
- Każde wywołanie `deertools skill push` automatycznie skanuje pliki skilla w poszukiwaniu niebezpiecznych tokenów (`deer_*_live_`, `ghp_`, `sk-`, `X-BLToken`, haseł).
|
|
243
254
|
- Próba wysłania niezanominizowanych kluczy kończy się natychmiastową blokadą z kodem błędu i listą wykrytych linii.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -34,6 +34,7 @@ async function main(argv) {
|
|
|
34
34
|
endpoints: c.cmdEndpoints, api: c.cmdApi, estimate: c.cmdEstimate, skill: c.cmdSkill,
|
|
35
35
|
sync: (pos, flags) => c.cmdSkill(['sync', ...pos], flags),
|
|
36
36
|
agent: c.cmdAgent, agents: c.cmdAgent,
|
|
37
|
+
team: c.cmdTeam, teams: c.cmdTeam,
|
|
37
38
|
mcp: c.cmdMcp, doctor: c.cmdDoctor, audit: c.cmdAudit, profile: c.cmdProfile,
|
|
38
39
|
completion: c.cmdCompletion, catalog: c.cmdCatalog, deliveries: c.cmdDeliveries,
|
|
39
40
|
supplier: c.cmdSupplier, rendermind: c.cmdRendermind, baselinker: c.cmdBaseLinker,
|
package/src/commands.js
CHANGED
|
@@ -406,11 +406,27 @@ async function cmdSkill(pos, flags) {
|
|
|
406
406
|
if (sub === 'promote') {
|
|
407
407
|
return skillCommands.cmdSkillPromote(pos[1], flags, out, err);
|
|
408
408
|
}
|
|
409
|
+
if (sub === 'review' || sub === 'reviews') {
|
|
410
|
+
return skillCommands.cmdSkillReview(pos.slice(1), flags, out, err);
|
|
411
|
+
}
|
|
412
|
+
if (sub === 'approve') {
|
|
413
|
+
return skillCommands.cmdSkillReview(['approve', ...pos.slice(1)], flags, out, err);
|
|
414
|
+
}
|
|
415
|
+
if (sub === 'reject') {
|
|
416
|
+
return skillCommands.cmdSkillReview(['reject', ...pos.slice(1)], flags, out, err);
|
|
417
|
+
}
|
|
418
|
+
if (sub === 'share') {
|
|
419
|
+
return skillCommands.cmdSkillShare(pos[1], flags, out, err);
|
|
420
|
+
}
|
|
421
|
+
if (sub === 'team' || sub === 'teams') {
|
|
422
|
+
const { cmdTeam: runTeam } = require('./team-commands');
|
|
423
|
+
return runTeam(pos.slice(1), flags, out, err);
|
|
424
|
+
}
|
|
409
425
|
if (sub === 'install') {
|
|
410
426
|
const target = pos[1] || (flags.all ? '--all' : null);
|
|
411
427
|
return skillCommands.cmdSkillInstall(target, flags, out, err);
|
|
412
428
|
}
|
|
413
|
-
fail('Użycie:\n deertools skill login [token]\n deertools skill sync [--force]\n deertools skill bundle <architekci|kosztorysy|logistyka|sprzedaz|it>\n deertools skill list [--tag <tag>]\n deertools skill search <fraza>\n deertools skill install [--all|<slug[@version]>]\n deertools skill check\n deertools skill update [slug]\n deertools skill versions <slug>\n deertools skill diff <slug> [wersjaA] [wersjaB]\n deertools skill push [katalog] [--bump patch|minor|major]
|
|
429
|
+
fail('Użycie:\n deertools skill login [token]\n deertools skill sync [--force]\n deertools skill bundle <architekci|kosztorysy|logistyka|sprzedaz|it>\n deertools skill list [--tag <tag>]\n deertools skill search <fraza>\n deertools skill install [--all|<slug[@version]>]\n deertools skill check\n deertools skill update [slug]\n deertools skill versions <slug>\n deertools skill diff <slug> [wersjaA] [wersjaB]\n deertools skill push [katalog] [--team <zespół>] [--own|--global] [--bump patch|minor|major]\n deertools skill share <slug> --team <zespół>\n deertools skill review [list|approve|reject|submit]\n deertools skill promote <slug>', 2);
|
|
414
430
|
}
|
|
415
431
|
|
|
416
432
|
|
|
@@ -597,6 +613,11 @@ async function cmdAgent(pos, flags) {
|
|
|
597
613
|
fail(`Nieznana podkomenda „agent ${action}”. Użyj: deertools agent [list|select <agenty>|reset|current]`, 2);
|
|
598
614
|
}
|
|
599
615
|
|
|
616
|
+
async function cmdTeam(pos, flags) {
|
|
617
|
+
const { cmdTeam: runTeam } = require('./team-commands');
|
|
618
|
+
return runTeam(pos, flags, out, err);
|
|
619
|
+
}
|
|
620
|
+
|
|
600
621
|
const { cmdDoctor } = require('./doctor');
|
|
601
622
|
const { cmdAudit } = require('./audit');
|
|
602
623
|
const { cmdProfile } = require('./profiles');
|
|
@@ -640,7 +661,9 @@ Integracje MCP i Agenci AI (DeerSkills Hub):
|
|
|
640
661
|
deertools skill search <fraza> Wyszukuje umiejętności po słowach kluczowych.
|
|
641
662
|
deertools skill versions <slug> Historia wydań z changelogami z commitów i datami.
|
|
642
663
|
deertools skill diff <slug> [wersjaA] [wersjaB] Porównanie zmian w plikach paczki i treści SKILL.md.
|
|
643
|
-
deertools skill push [katalog] [--
|
|
664
|
+
deertools skill push [katalog] [--team <zespół>] Publikuje do własnej przestrzeni, zespołu lub globalnie.
|
|
665
|
+
deertools skill share <slug> --team <zespół> Udostępnia skill prywatny wskazanemu zespołowi.
|
|
666
|
+
deertools skill review [list|approve|reject|submit] Zarządzanie procesem Peer Review i weryfikacją skilli zespołu.
|
|
644
667
|
deertools skill update [slug] Aktualizuje zainstalowane skille do najnowszej wersji.
|
|
645
668
|
deertools skill promote <slug> Zgłasza nominację skilla do oficjalnego katalogu globalnego.
|
|
646
669
|
deertools mcp list Lista dostępnych serwerów MCP w chmurze.
|
|
@@ -651,6 +674,13 @@ Integracje MCP i Agenci AI (DeerSkills Hub):
|
|
|
651
674
|
deertools endpoints <produkt> [--json] Lista endpointów REST z openapi.json.
|
|
652
675
|
deertools api <produkt> <METODA> </ścieżka> Wywołuje bezpośrednio API produktu.
|
|
653
676
|
|
|
677
|
+
Zarządzanie zespołami (DeerSkills Hub Teams):
|
|
678
|
+
deertools team [list] Przegląda listę zespołów, liczbę członków i przypisanych skilli.
|
|
679
|
+
deertools team members [zespół] Lista członków zespołów i ich ról (Lider / Członek).
|
|
680
|
+
deertools team add <zespół> <email> [--role lead] Dodaje lub aktualizuje rolę pracownika w zespole.
|
|
681
|
+
deertools team remove <zespół> <email> Usuwa pracownika ze wskazanego zespołu.
|
|
682
|
+
deertools team create <nazwa> [--slug <s>] [--desc] Tworzy nowy zespół w organizacji Deer Design.
|
|
683
|
+
|
|
654
684
|
Narzędzia systemowe:
|
|
655
685
|
deertools update Sprawdza i podpowiada aktualizację CLI.
|
|
656
686
|
deertools doctor [--json] Pełna diagnostyka połączeń, portów i kluczy.
|
|
@@ -663,7 +693,7 @@ Zmienne środowiskowe: DEERTOOLS_<PRODUKT>_KEY, BASELINKER_TOKEN, DEERTOOLS_HOME
|
|
|
663
693
|
module.exports = {
|
|
664
694
|
cmdLogin, cmdLogout, cmdWhoami, cmdDocs, cmdEndpoints, cmdApi, cmdEstimate, cmdSkill, cmdMcp,
|
|
665
695
|
cmdDoctor, cmdAudit, cmdProfile, cmdCompletion, cmdCatalog, cmdDeliveries, cmdSupplier, cmdRendermind,
|
|
666
|
-
cmdBaseLinker, cmdAgent,
|
|
696
|
+
cmdBaseLinker, cmdAgent, cmdTeam,
|
|
667
697
|
HELP, parseQuery,
|
|
668
698
|
};
|
|
669
699
|
|
package/src/mcp-stdio.js
CHANGED
|
@@ -4,6 +4,7 @@ const credentials = require('./credentials');
|
|
|
4
4
|
const { ALL_PRODUCTS, getProduct } = require('./registry');
|
|
5
5
|
const { apiRequest } = require('./http');
|
|
6
6
|
const { checkSkillUpdates, updateSkills } = require('./skill-commands');
|
|
7
|
+
const skillsHub = require('./skills-hub');
|
|
7
8
|
|
|
8
9
|
const PROTOCOL_VERSION = '2024-11-05';
|
|
9
10
|
const SERVER_INFO = { name: 'deertools-stdio-bridge', version: '1.0.5' };
|
|
@@ -113,6 +114,40 @@ async function cmdMcpServe(pos, flags) {
|
|
|
113
114
|
required: ['confirmed'],
|
|
114
115
|
},
|
|
115
116
|
},
|
|
117
|
+
{
|
|
118
|
+
name: 'deer_list_skill_reviews',
|
|
119
|
+
description: 'Pobiera kolejkę zgłoszeń skilli do Peer Review z DeerSkills Hub (zgłoszenia oczekujące, zatwierdzone lub odesłane do poprawek).',
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
properties: {
|
|
123
|
+
status: { type: 'string', enum: ['pending', 'approved', 'changes_requested', 'all'], description: 'Status zgłoszenia (domyślnie pending - oczekujące)' },
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'deer_decide_skill_review',
|
|
129
|
+
description: 'Rozpatrzenie zgłoszonego skilla w Peer Review (zatwierdzenie do katalogu globalnego lub odesłanie do poprawek z uwagami) przez leada/admina.',
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
properties: {
|
|
133
|
+
slug: { type: 'string', description: 'Identyfikator (slug) zgłoszonego skilla, np. deer-technical-documentation' },
|
|
134
|
+
approve: { type: 'boolean', description: 'true = zatwierdź (Approve) i przenieś do katalogu globalnego, false = odeślij do poprawek (Request Changes)' },
|
|
135
|
+
notes: { type: 'string', description: 'Uwagi i wytyczne dla autora (obowiązkowe przy odrzuceniu/poprawkach)' },
|
|
136
|
+
},
|
|
137
|
+
required: ['slug', 'approve'],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'deer_submit_skill_review',
|
|
142
|
+
description: 'Zgłoszenie własnego opublikowanego skilla do weryfikacji zespołu / Peer Review w DeerSkills Hub.',
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: 'object',
|
|
145
|
+
properties: {
|
|
146
|
+
slug: { type: 'string', description: 'Identyfikator (slug) skilla do zgłoszenia, np. @mariusz.szostak/moj-skill' },
|
|
147
|
+
},
|
|
148
|
+
required: ['slug'],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
116
151
|
];
|
|
117
152
|
|
|
118
153
|
send({ jsonrpc: '2.0', id, result: { tools } });
|
|
@@ -269,6 +304,77 @@ async function cmdMcpServe(pos, flags) {
|
|
|
269
304
|
return;
|
|
270
305
|
}
|
|
271
306
|
|
|
307
|
+
if (toolName === 'deer_list_skill_reviews') {
|
|
308
|
+
const res = await skillsHub.listReviews({ status: args.status || 'pending' });
|
|
309
|
+
if (!res.ok) {
|
|
310
|
+
send({ jsonrpc: '2.0', id, error: { code: -32000, message: res.data?.error || `Błąd pobierania (${res.status})` } });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
send({
|
|
314
|
+
jsonrpc: '2.0',
|
|
315
|
+
id,
|
|
316
|
+
result: { content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }] },
|
|
317
|
+
});
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (toolName === 'deer_decide_skill_review') {
|
|
322
|
+
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(args.slug);
|
|
323
|
+
const res = await skillsHub.decideReview({
|
|
324
|
+
slug: isUuid ? null : args.slug,
|
|
325
|
+
reviewId: isUuid ? args.slug : null,
|
|
326
|
+
approve: Boolean(args.approve),
|
|
327
|
+
notes: args.notes || '',
|
|
328
|
+
});
|
|
329
|
+
if (!res.ok) {
|
|
330
|
+
send({ jsonrpc: '2.0', id, error: { code: -32000, message: res.data?.error || `Błąd rozpatrywania (${res.status})` } });
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
send({
|
|
334
|
+
jsonrpc: '2.0',
|
|
335
|
+
id,
|
|
336
|
+
result: {
|
|
337
|
+
content: [{
|
|
338
|
+
type: 'text',
|
|
339
|
+
text: JSON.stringify({
|
|
340
|
+
success: true,
|
|
341
|
+
decision: args.approve ? 'approved' : 'changes_requested',
|
|
342
|
+
skill: res.data?.skill || res.data,
|
|
343
|
+
notes: args.notes || null,
|
|
344
|
+
message: args.approve
|
|
345
|
+
? `Skill został pomyślnie zatwierdzony i opublikowany w katalogu globalnym zespołu.`
|
|
346
|
+
: `Zgłoszenie zostało odesłane do poprawek z uwagami dla autora.`,
|
|
347
|
+
}, null, 2),
|
|
348
|
+
}],
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (toolName === 'deer_submit_skill_review') {
|
|
355
|
+
const res = await skillsHub.submitReview(args.slug);
|
|
356
|
+
if (!res.ok) {
|
|
357
|
+
send({ jsonrpc: '2.0', id, error: { code: -32000, message: res.data?.error || `Błąd zgłaszania (${res.status})` } });
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
send({
|
|
361
|
+
jsonrpc: '2.0',
|
|
362
|
+
id,
|
|
363
|
+
result: {
|
|
364
|
+
content: [{
|
|
365
|
+
type: 'text',
|
|
366
|
+
text: JSON.stringify({
|
|
367
|
+
success: true,
|
|
368
|
+
status: 'in_review',
|
|
369
|
+
skill: args.slug,
|
|
370
|
+
message: `Skill ${args.slug} został pomyślnie zgłoszony do Peer Review zespołu.`,
|
|
371
|
+
}, null, 2),
|
|
372
|
+
}],
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
272
378
|
send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Tool not found: ${toolName}` } });
|
|
273
379
|
} catch (e) {
|
|
274
380
|
send({ jsonrpc: '2.0', id, error: { code: -32000, message: e.message } });
|
package/src/skill-commands.js
CHANGED
|
@@ -523,15 +523,41 @@ async function cmdSkillPush(dir, flags, out, err) {
|
|
|
523
523
|
const targetDir = dir || process.cwd();
|
|
524
524
|
out(`Analizuję folder skilla: ${targetDir}...`);
|
|
525
525
|
|
|
526
|
+
const isUserScope = Boolean(flags.own || flags.user || flags['own-space'] || flags.space === 'user');
|
|
527
|
+
const isGlobalScope = Boolean(flags.global || flags.space === 'global');
|
|
528
|
+
const isTeamScope = Boolean(flags.team || flags.space === 'team');
|
|
529
|
+
const scope = isUserScope ? 'user' : (isGlobalScope ? 'global' : (isTeamScope ? 'team' : null));
|
|
530
|
+
const team = flags.team || flags.t || null;
|
|
531
|
+
|
|
532
|
+
const visibility = flags.private ? 'private' : (flags.shared || flags.public ? 'shared' : null);
|
|
533
|
+
|
|
534
|
+
let cliTags = null;
|
|
535
|
+
if (flags.tags) {
|
|
536
|
+
cliTags = String(flags.tags).split(',').map((t) => t.trim()).filter(Boolean);
|
|
537
|
+
} else if (flags.tag) {
|
|
538
|
+
cliTags = (Array.isArray(flags.tag) ? flags.tag : [flags.tag]).flatMap((t) => String(t).split(',')).map((t) => t.trim()).filter(Boolean);
|
|
539
|
+
}
|
|
540
|
+
|
|
526
541
|
const res = await skillsHub.pushSkill(targetDir, {
|
|
527
542
|
version: flags.version || null,
|
|
528
543
|
bump: flags.bump || (flags.major ? 'major' : flags.minor ? 'minor' : 'patch'),
|
|
529
544
|
changelog: flags.changelog || flags.message || flags.m || null,
|
|
530
|
-
visibility
|
|
545
|
+
visibility,
|
|
546
|
+
scope,
|
|
547
|
+
namespace: flags.namespace || null,
|
|
548
|
+
team,
|
|
549
|
+
tags: cliTags,
|
|
531
550
|
});
|
|
532
551
|
|
|
533
552
|
if (!res.ok) {
|
|
534
|
-
|
|
553
|
+
const errMsg = res.data?.error || '';
|
|
554
|
+
if (res.status === 403 && (errMsg.includes('global') || errMsg.includes('Administrator') || errMsg.includes('firmow') || isGlobalScope)) {
|
|
555
|
+
throw new Error(`BŁĄD UPRAWNIEŃ (403): Tylko Administrator może publikować do przestrzeni firmowej (global).
|
|
556
|
+
Jako pracownik możesz publikować do swojej przestrzeni prywatnej (@twoj-login/slug) lub do zespołu (--team <zespół>).
|
|
557
|
+
Aby zgłosić swój skill do katalogu firmowego, użyj polecenia:
|
|
558
|
+
deertools skill promote <nazwa-skilla>`);
|
|
559
|
+
}
|
|
560
|
+
throw new Error(`Nie udało się opublikować skilla (${res.status}): ${errMsg || 'Błąd serwera'}`);
|
|
535
561
|
}
|
|
536
562
|
|
|
537
563
|
const skill = res.data.skill || res.data || {};
|
|
@@ -541,17 +567,77 @@ async function cmdSkillPush(dir, flags, out, err) {
|
|
|
541
567
|
? res.data.pushed_manifest_count
|
|
542
568
|
: (res.data.version?.files_manifest?.length || 0);
|
|
543
569
|
|
|
570
|
+
let scopeLabel;
|
|
571
|
+
if (skill.scope === 'global') {
|
|
572
|
+
scopeLabel = '★ Globalna firmy (katalog oficjalny)';
|
|
573
|
+
} else if (skill.scope === 'team' || team) {
|
|
574
|
+
scopeLabel = `👥 Zespół: \x1b[36m${team || skill.team_id || 'przypisany'}\x1b[0m`;
|
|
575
|
+
} else {
|
|
576
|
+
scopeLabel = `@${skill.namespace || 'własna'}`;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
let visibilityLabel;
|
|
580
|
+
if (skill.scope === 'team' || team) {
|
|
581
|
+
visibilityLabel = '\x1b[32m👥 Współdzielona z zespołem (natychmiastowy dostęp dla członków zespołu)\x1b[0m';
|
|
582
|
+
} else if (skill.visibility === 'private' || skill.scope === 'user') {
|
|
583
|
+
visibilityLabel = '\x1b[33m🔒 Prywatna (dostępna tylko dla Ciebie)\x1b[0m';
|
|
584
|
+
} else {
|
|
585
|
+
visibilityLabel = '\x1b[32m🌐 Współdzielona z zespołem\x1b[0m';
|
|
586
|
+
}
|
|
587
|
+
|
|
544
588
|
out(`\n✓ Pomyślnie opublikowano skill w DeerSkills Hub:`);
|
|
545
589
|
out(` • Identyfikator: \x1b[1m${skill.full_slug || skill.slug}\x1b[0m`);
|
|
590
|
+
out(` • Przestrzeń: ${scopeLabel}`);
|
|
546
591
|
out(` • Wersja: \x1b[32mv${versionNum}\x1b[0m`);
|
|
547
592
|
if (changelog && changelog !== versionNum) {
|
|
548
593
|
out(` • Changelog: \x1b[33m${changelog}\x1b[0m`);
|
|
549
594
|
}
|
|
550
595
|
out(` • Paczka: ${manifestCount + 1} plików (SKILL.md + ${manifestCount} plików w podkatalogach)`);
|
|
551
|
-
|
|
596
|
+
if (skill.tags && Array.isArray(skill.tags) && skill.tags.length > 0) {
|
|
597
|
+
out(` • Tagi: ${skill.tags.map((t) => `\x1b[36m#${t}\x1b[0m`).join(' ')}`);
|
|
598
|
+
}
|
|
599
|
+
out(` • Widoczność: ${visibilityLabel}`);
|
|
552
600
|
out(`\nAby zainstalować tę wersję w projekcie:\n \x1b[36mdeertools skill install ${skill.full_slug || skill.slug}@${versionNum}\x1b[0m\n`);
|
|
553
601
|
}
|
|
554
602
|
|
|
603
|
+
/**
|
|
604
|
+
* Udostępnia skill wskazanemu zespołowi lub przywraca prywatność.
|
|
605
|
+
*/
|
|
606
|
+
async function cmdSkillShare(slug, flags, out, err) {
|
|
607
|
+
if (!slug) throw new Error('Podaj slug skilla, np. deertools skill share my-skill --team architekci');
|
|
608
|
+
const team = flags.team || flags.t || null;
|
|
609
|
+
const makePrivate = Boolean(flags.private || flags.unshare);
|
|
610
|
+
|
|
611
|
+
if (!team && !makePrivate) {
|
|
612
|
+
let availableTeams = '';
|
|
613
|
+
try {
|
|
614
|
+
const teamsRes = await skillsHub.listTeams();
|
|
615
|
+
if (Array.isArray(teamsRes.data)) {
|
|
616
|
+
availableTeams = teamsRes.data.map((t) => ` • \x1b[36m${t.slug.padEnd(14)}\x1b[0m (${t.name})`).join('\n');
|
|
617
|
+
}
|
|
618
|
+
} catch (_) {}
|
|
619
|
+
if (!availableTeams) {
|
|
620
|
+
availableTeams = ' • architekci, kosztorysy, logistyka, sprzedaz, it-dev';
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
throw new Error(`Podaj zespół do udostępnienia (--team <nazwa>) lub flagę --private.\nDostępne zespoły:\n${availableTeams}`);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
out(`Aktualizuję udostępnienie skilla „${slug}”...`);
|
|
627
|
+
const res = await skillsHub.shareSkill(slug, { team, makePrivate });
|
|
628
|
+
if (!res.ok) {
|
|
629
|
+
throw new Error(`Nie udało się zaktualizować udostępnienia (${res.status}): ${res.data?.error || 'Błąd'}`);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (makePrivate) {
|
|
633
|
+
out(`\n\x1b[33m🔒 Skill „${slug}” jest teraz prywatny (dostępny wyłącznie dla Ciebie).\x1b[0m\n`);
|
|
634
|
+
} else {
|
|
635
|
+
out(`\n\x1b[32m✓ Skill „${slug}” został pomyślnie udostępniony dla zespołu: \x1b[1m${team}\x1b[0m\x1b[0m`);
|
|
636
|
+
out(` Wszyscy członkowie zespołu mają do niego natychmiastowy dostęp bez konieczności akceptacji admina.`);
|
|
637
|
+
out(` Mogą go zainstalować za pomocą: \x1b[36mdeertools skill install ${slug}\x1b[0m\n`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
555
641
|
async function cmdSkillVersions(slug, flags, out, err) {
|
|
556
642
|
if (!slug) throw new Error('Podaj slug skilla, np. deertools skill versions deer-technical-documentation');
|
|
557
643
|
out(`Pobieram historię wydań dla skilla „${slug}”...`);
|
|
@@ -772,7 +858,116 @@ async function cmdSkillPromote(slug, flags, out, err) {
|
|
|
772
858
|
}
|
|
773
859
|
|
|
774
860
|
out('✓ Wniosek o nominację został pomyślnie przesłany do administratorów.');
|
|
775
|
-
out(' Możesz śledzić status w panelu DeerSkills: https://skills.deergroup.pl/settings');
|
|
861
|
+
out(' Możesz śledzić status w panelu DeerSkills: https://skills.deergroup.pl/settings\n');
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
async function cmdSkillReview(args, flags, out, err) {
|
|
865
|
+
const sub = String(args[0] || 'list').toLowerCase();
|
|
866
|
+
|
|
867
|
+
// 1. Zgłoszenie do review
|
|
868
|
+
if (sub === 'submit') {
|
|
869
|
+
const slug = args[1] || flags.slug;
|
|
870
|
+
if (!slug) throw new Error('Podaj slug skilla do zgłoszenia, np. deertools skill review submit @mariusz.szostak/moj-skill');
|
|
871
|
+
out(`Zgłaszam skill „${slug}” do Peer Review w DeerSkills Hub...`);
|
|
872
|
+
const res = await skillsHub.submitReview(slug);
|
|
873
|
+
if (!res.ok) {
|
|
874
|
+
throw new Error(`Błąd zgłoszenia (${res.status}): ${res.data?.error || 'Brak uprawnień'}`);
|
|
875
|
+
}
|
|
876
|
+
out(`\n✓ Skill „${slug}” został pomyślnie zgłoszony do Peer Review!`);
|
|
877
|
+
out(` Status: \x1b[33mOczekuje na weryfikację (in_review)\x1b[0m`);
|
|
878
|
+
out(` Zatwierdzenie przez leada udostępnia skill w zespole, a zatwierdzenie przez Administratora publikuje do katalogu firmowego.\n`);
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// 2. Zatwierdzenie (Approve)
|
|
883
|
+
if (sub === 'approve' || sub === 'accept') {
|
|
884
|
+
const target = args[1] || flags.slug || flags.id;
|
|
885
|
+
if (!target) throw new Error('Podaj identyfikator lub slug zgłoszonego skilla, np. deertools skill review approve deer-technical-documentation');
|
|
886
|
+
const notes = flags.notes || flags.message || flags.m || '';
|
|
887
|
+
out(`Zatwierdzam zgłoszenie dla „${target}”...`);
|
|
888
|
+
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(target);
|
|
889
|
+
const res = await skillsHub.decideReview({
|
|
890
|
+
slug: isUuid ? null : target,
|
|
891
|
+
reviewId: isUuid ? target : null,
|
|
892
|
+
approve: true,
|
|
893
|
+
notes,
|
|
894
|
+
});
|
|
895
|
+
if (!res.ok) {
|
|
896
|
+
throw new Error(`Błąd zatwierdzania (${res.status}): ${res.data?.error || 'Brak uprawnień lub błąd serwera'}`);
|
|
897
|
+
}
|
|
898
|
+
const skill = res.data?.skill || res.data || {};
|
|
899
|
+
const isGlobal = skill.scope === 'global';
|
|
900
|
+
out(`\n\x1b[32m✓ Skill ${skill.full_slug || skill.title || target} został ZATWIERDZONY w Peer Review!\x1b[0m`);
|
|
901
|
+
out(` • Status: \x1b[32mZweryfikowany (approved / verified)\x1b[0m`);
|
|
902
|
+
out(` • Zasięg: ${isGlobal ? '★ Globalny (przestrzeń firmowa)' : `👥 Zespołowy: ${skill.team_id || 'zespół'}`}`);
|
|
903
|
+
out(` • Wersja: v${skill.latest_version}`);
|
|
904
|
+
if (notes) out(` • Notatka: ${notes}`);
|
|
905
|
+
out(`\nKażdy agent może teraz zainstalować skill:\n \x1b[36mdeertools skill install ${skill.full_slug || skill.slug}\x1b[0m\n`);
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// 3. Odrzucenie / prośba o poprawki (Request Changes)
|
|
910
|
+
if (sub === 'reject' || sub === 'changes' || sub === 'request-changes') {
|
|
911
|
+
const target = args[1] || flags.slug || flags.id;
|
|
912
|
+
if (!target) throw new Error('Podaj identyfikator lub slug zgłoszonego skilla, np. deertools skill review reject deer-technical-documentation --notes "Popraw opis formatu"');
|
|
913
|
+
const notes = flags.notes || flags.message || flags.m || '';
|
|
914
|
+
if (!notes || notes.trim().length === 0) {
|
|
915
|
+
throw new Error('Podaj uwagi i uzasadnienie dla autora (--notes "Treść uwag").');
|
|
916
|
+
}
|
|
917
|
+
out(`Odsyłam zgłoszenie do poprawek dla „${target}”...`);
|
|
918
|
+
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(target);
|
|
919
|
+
const res = await skillsHub.decideReview({
|
|
920
|
+
slug: isUuid ? null : target,
|
|
921
|
+
reviewId: isUuid ? target : null,
|
|
922
|
+
approve: false,
|
|
923
|
+
notes,
|
|
924
|
+
});
|
|
925
|
+
if (!res.ok) {
|
|
926
|
+
throw new Error(`Błąd odsyłania (${res.status}): ${res.data?.error || 'Brak uprawnień lub błąd serwera'}`);
|
|
927
|
+
}
|
|
928
|
+
out(`\n\x1b[33m✓ Wysłano prośbę o poprawki (Request Changes) dla ${target}.\x1b[0m`);
|
|
929
|
+
out(` Autor otrzymał uwagi w notyfikacji i statusie skilla: "${notes}".\n`);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// 4. Listowanie kolejki review (Domyślne)
|
|
934
|
+
out(`Pobieram kolejkę Peer Review z DeerSkills Hub...`);
|
|
935
|
+
const res = await skillsHub.listReviews({
|
|
936
|
+
status: flags.status || (flags.all ? 'all' : 'pending'),
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
if (!res.ok) {
|
|
940
|
+
throw new Error(`Błąd pobierania listy review (${res.status}): ${res.data?.error || 'Brak dostępu'}`);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
const items = Array.isArray(res.data) ? res.data : (res.data?.reviews || []);
|
|
944
|
+
if (items.length === 0) {
|
|
945
|
+
out('\n✓ Kolejka Peer Review jest pusta. Wszystkie zgłoszone skille zostały rozpatrzone.\n');
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
out(`\n=== Kolejka zgłoszeń Skill Peer Review (${items.length}) ===\n`);
|
|
950
|
+
for (const r of items) {
|
|
951
|
+
const s = r.skills || {};
|
|
952
|
+
const slug = s.full_slug || s.slug || r.skill_id;
|
|
953
|
+
const author = r.requester_email || s.owner_email || s.owner_name || 'Nieznany';
|
|
954
|
+
const dateStr = new Date(r.created_at || Date.now()).toLocaleDateString('pl-PL');
|
|
955
|
+
const statusBadge = r.status === 'pending'
|
|
956
|
+
? '\x1b[33m[⏳ Oczekuje na zatwierdzenie]\x1b[0m'
|
|
957
|
+
: r.status === 'approved'
|
|
958
|
+
? '\x1b[32m[✓ Zatwierdzony]\x1b[0m'
|
|
959
|
+
: '\x1b[31m[✕ Wymaga poprawek]\x1b[0m';
|
|
960
|
+
|
|
961
|
+
out(` • \x1b[1m${slug}\x1b[0m (v${r.version || s.latest_version}) ${statusBadge}`);
|
|
962
|
+
out(` Autor: ${author} (${dateStr})`);
|
|
963
|
+
if (s.summary) out(` Opis: ${s.summary.slice(0, 90)}...`);
|
|
964
|
+
if (r.review_notes) out(` Uwagi: \x1b[33m${r.review_notes}\x1b[0m`);
|
|
965
|
+
if (r.status === 'pending') {
|
|
966
|
+
out(` Decyzja: \x1b[36mdeertools skill review approve ${slug}\x1b[0m`);
|
|
967
|
+
out(` \x1b[31mdeertools skill review reject ${slug} --notes "..."\x1b[0m`);
|
|
968
|
+
}
|
|
969
|
+
out('');
|
|
970
|
+
}
|
|
776
971
|
}
|
|
777
972
|
|
|
778
973
|
module.exports = {
|
|
@@ -786,6 +981,8 @@ module.exports = {
|
|
|
786
981
|
cmdSkillPromote,
|
|
787
982
|
cmdSkillVersions,
|
|
788
983
|
cmdSkillDiff,
|
|
984
|
+
cmdSkillReview,
|
|
985
|
+
cmdSkillShare,
|
|
789
986
|
checkSkillUpdates,
|
|
790
987
|
updateSkills,
|
|
791
988
|
};
|
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
|
+
};
|