@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/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
+ };