@deerdesign/deertools 1.2.17 → 1.2.22

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 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 --bump minor # Automatyczne podbicie wersji SemVer
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. AI Agent MCP Registry & Configuration:
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 Uprawnienia**:
238
- - **Oficjalne Skille (`global`)**: Zweryfikowane, wersjonowane semantycznie (`v1.0.0`) narzędzia firmowe, zarządzane wyłącznie przez Administratorów.
239
- - **Skille Użytkowników (`user`)**: Każdy pracownik może tworzyć i wersjonować własne skille pod przestrzenią `@uzytkownik/nazwa-skilla`.
240
- - **Widoczność**: `shared` (dostępne wewnątrz firmy dla zalogowanych) lub `private` (widoczne tylko dla autora).
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deerdesign/deertools",
3
- "version": "1.2.17",
3
+ "version": "1.2.22",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
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] [--changelog "opis"]\n deertools skill promote <slug>', 2);
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] [--bump patch|minor] Publikuje nową wersję z automatycznym skanerem sekretów.
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 } });
@@ -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: flags.private ? 'private' : 'shared',
545
+ visibility,
546
+ scope,
547
+ namespace: flags.namespace || null,
548
+ team,
549
+ tags: cliTags,
531
550
  });
532
551
 
533
552
  if (!res.ok) {
534
- throw new Error(`Nie udało się opublikować skilla (${res.status}): ${res.data?.error || 'Błąd serwera'}`);
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
- out(` • Widoczność: ${skill.visibility || 'shared'}`);
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
- async function pushSkill(skillDir, { version = null, bump = 'patch', changelog = null, visibility = 'shared' } = {}) {
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
+ };