@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.
@@ -7,6 +7,8 @@ const { parseFrontmatterName } = require('./openapi');
7
7
  const { resolveProducts } = require('./registry');
8
8
  const { fetchText } = require('./http');
9
9
 
10
+ const { compareSemver } = require('./updater');
11
+
10
12
  const LOCK_FILE = '.deertools-lock.json';
11
13
 
12
14
  function getLockFilePath(cwd = process.cwd()) {
@@ -38,14 +40,149 @@ function writeLock(data, cwd = process.cwd()) {
38
40
  } catch (_) {}
39
41
  }
40
42
 
43
+ /**
44
+ * Szuka pliku skilla na dysku dla danego agenta.
45
+ */
46
+ function findSkillFileForAgent(agent, skill) {
47
+ const slug = skill.slug;
48
+ const fullSlug = skill.full_slug || slug;
49
+ const shortSlug = slug.replace(/^@[^/]+\//, '');
50
+
51
+ if (agent.format === 'cursor_mdc') {
52
+ const candidates = [
53
+ path.join(agent.targetDir, `${slug}.mdc`),
54
+ path.join(agent.targetDir, `${shortSlug}.mdc`),
55
+ path.join(agent.targetDir, `${fullSlug.replace(/\//g, '-')}.mdc`),
56
+ ];
57
+ for (const c of candidates) {
58
+ if (fs.existsSync(c)) return c;
59
+ }
60
+ return null;
61
+ }
62
+
63
+ const candidates = [
64
+ path.join(agent.targetDir, slug, 'SKILL.md'),
65
+ path.join(agent.targetDir, shortSlug, 'SKILL.md'),
66
+ path.join(agent.targetDir, fullSlug, 'SKILL.md'),
67
+ ];
68
+ for (const c of candidates) {
69
+ if (fs.existsSync(c)) return c;
70
+ }
71
+ return null;
72
+ }
73
+
74
+ /**
75
+ * Wyciąga wersję z pliku SKILL.md lub .mdc z fallbackiem na lockfile.
76
+ */
77
+ function readSkillVersion(filePath, lock, skill) {
78
+ if (!filePath || !fs.existsSync(filePath)) return null;
79
+ try {
80
+ const head = fs.readFileSync(filePath, 'utf8').slice(0, 2048);
81
+ const m = head.match(/[\r\n]version:\s*['"]?([0-9]+\.[0-9]+(\.[0-9]+)?(-[a-zA-Z0-9.]+)?)/i)
82
+ || head.match(/^version:\s*['"]?([0-9]+\.[0-9]+(\.[0-9]+)?(-[a-zA-Z0-9.]+)?)/im);
83
+ if (m && m[1]) return m[1];
84
+ const vMatch = head.match(/v([0-9]+\.[0-9]+(\.[0-9]+)?)/i);
85
+ if (vMatch && vMatch[1]) return vMatch[1];
86
+ } catch (_) {}
87
+
88
+ if (lock?.skills) {
89
+ const lockEntry = lock.skills[skill.slug] || lock.skills[skill.full_slug];
90
+ if (lockEntry && lockEntry.version) return lockEntry.version;
91
+ }
92
+ return '1.0.0';
93
+ }
94
+
95
+ /**
96
+ * Bada stan instalacji i wersję skilla u wszystkich zadanych agentów.
97
+ */
98
+ function inspectSkillForAgents(agents, skill, lock) {
99
+ const agentStatuses = {};
100
+ let installedCount = 0;
101
+ let upToDateCount = 0;
102
+ let outdatedCount = 0;
103
+ let missingCount = 0;
104
+
105
+ for (const agent of agents) {
106
+ const filePath = findSkillFileForAgent(agent, skill);
107
+ if (!filePath) {
108
+ agentStatuses[agent.id] = {
109
+ agentId: agent.id,
110
+ agentName: agent.name,
111
+ installed: false,
112
+ version: null,
113
+ status: 'missing',
114
+ filePath: null,
115
+ badge: `\x1b[90m[○ ${agent.id}: brak]\x1b[0m`,
116
+ };
117
+ missingCount += 1;
118
+ continue;
119
+ }
120
+
121
+ installedCount += 1;
122
+ const installedVer = readSkillVersion(filePath, lock, skill);
123
+ const cmp = compareSemver(installedVer, skill.latest_version);
124
+
125
+ if (cmp >= 0) {
126
+ agentStatuses[agent.id] = {
127
+ agentId: agent.id,
128
+ agentName: agent.name,
129
+ installed: true,
130
+ version: installedVer,
131
+ status: 'up_to_date',
132
+ filePath,
133
+ badge: `\x1b[32m[✓ ${agent.id}: v${installedVer}]\x1b[0m`,
134
+ };
135
+ upToDateCount += 1;
136
+ } else {
137
+ agentStatuses[agent.id] = {
138
+ agentId: agent.id,
139
+ agentName: agent.name,
140
+ installed: true,
141
+ version: installedVer,
142
+ status: 'outdated',
143
+ target_version: skill.latest_version,
144
+ filePath,
145
+ badge: `\x1b[33m[↑ ${agent.id}: v${installedVer} → v${skill.latest_version}]\x1b[0m`,
146
+ };
147
+ outdatedCount += 1;
148
+ }
149
+ }
150
+
151
+ let overallStatus = 'missing';
152
+ let overallBadge = '\x1b[90m[○ Niezainstalowany]\x1b[0m';
153
+
154
+ if (installedCount > 0) {
155
+ if (installedCount === agents.length && outdatedCount === 0) {
156
+ overallStatus = 'up_to_date';
157
+ overallBadge = `\x1b[32m[✓ Aktualny (${installedCount}/${agents.length})]\x1b[0m`;
158
+ } else if (outdatedCount > 0) {
159
+ overallStatus = 'outdated';
160
+ overallBadge = `\x1b[33m[↑ Wymaga aktualizacji (${outdatedCount})]\x1b[0m`;
161
+ } else {
162
+ overallStatus = 'partial';
163
+ overallBadge = `\x1b[36m[◐ Częściowy (${installedCount}/${agents.length})]\x1b[0m`;
164
+ }
165
+ }
166
+
167
+ return {
168
+ agents: agentStatuses,
169
+ installedCount,
170
+ upToDateCount,
171
+ outdatedCount,
172
+ missingCount,
173
+ overallStatus,
174
+ overallBadge,
175
+ };
176
+ }
177
+
41
178
  async function cmdSkillList(flags, out, err) {
42
- out('Pobieram listę skilli z DeerSkills Hub...');
179
+ if (!flags.json) out('Pobieram listę skilli z DeerSkills Hub...');
43
180
  const res = await skillsHub.listSkills({
44
181
  query: flags.query,
45
182
  tag: flags.tag,
46
183
  category: flags.category,
47
184
  scope: flags.scope,
48
- limit: flags.limit || 50,
185
+ limit: flags.limit || 100,
49
186
  });
50
187
 
51
188
  if (!res.ok) {
@@ -54,66 +191,133 @@ async function cmdSkillList(flags, out, err) {
54
191
  }
55
192
 
56
193
  const items = Array.isArray(res.data) ? res.data : (res.data?.skills || []);
194
+ const agents = detectAgents({
195
+ dir: flags.dir,
196
+ agent: flags.agent,
197
+ 'all-agents': flags['all-agents'],
198
+ });
199
+ const lock = readLock(flags.dir);
200
+
57
201
  if (flags.json) {
58
- out(JSON.stringify(items, null, 2));
202
+ const enriched = items.map((s) => {
203
+ const insp = inspectSkillForAgents(agents, s, lock);
204
+ return {
205
+ ...s,
206
+ overall_status: insp.overallStatus,
207
+ agents: Object.fromEntries(
208
+ Object.entries(insp.agents).map(([id, data]) => [
209
+ id,
210
+ {
211
+ installed: data.installed,
212
+ version: data.version,
213
+ status: data.status,
214
+ target_version: data.target_version || null,
215
+ file_path: data.filePath,
216
+ },
217
+ ])
218
+ ),
219
+ };
220
+ });
221
+ out(JSON.stringify(enriched, null, 2));
59
222
  return;
60
223
  }
61
224
 
225
+ const agentSummary = {};
226
+ for (const a of agents) {
227
+ agentSummary[a.id] = {
228
+ name: a.name,
229
+ targetDir: a.targetDir,
230
+ total: items.length,
231
+ upToDate: 0,
232
+ outdated: 0,
233
+ missing: 0,
234
+ };
235
+ }
236
+
62
237
  out(`\n=== Dostępne Skille w DeerSkills Hub (${items.length}) ===\n`);
63
238
  for (const s of items) {
64
239
  const badge = s.scope === 'global' ? '★ GLOBAL' : `@${s.namespace}`;
65
- const ver = s.latest_version ? `v${s.latest_version}` : 'v1.0.0';
66
- out(` • ${s.full_slug.padEnd(35)} [${ver}] [${badge}]`);
67
- if (s.summary) out(` ${s.summary.slice(0, 90)}...`);
240
+ const latestVer = s.latest_version ? `v${s.latest_version}` : 'v1.0.0';
241
+ const insp = inspectSkillForAgents(agents, s, lock);
242
+
243
+ for (const a of agents) {
244
+ const st = insp.agents[a.id];
245
+ if (st.status === 'up_to_date') agentSummary[a.id].upToDate += 1;
246
+ else if (st.status === 'outdated') agentSummary[a.id].outdated += 1;
247
+ else agentSummary[a.id].missing += 1;
248
+ }
249
+
250
+ const agentBadges = agents.map((a) => insp.agents[a.id].badge).join(' ');
251
+
252
+ out(` • ${s.full_slug.padEnd(36)} ${latestVer.padEnd(10)} ${badge.padEnd(14)} ${insp.overallBadge}`);
253
+ out(` Agenci: ${agentBadges}`);
254
+ if (s.summary) out(` ${s.summary.slice(0, 95)}...`);
68
255
  if (s.tags && s.tags.length) out(` Tagi: ${s.tags.join(', ')}`);
69
256
  out('');
70
257
  }
258
+
259
+ out('─'.repeat(78));
260
+ out(`📊 Stan platform agentów AI / lokalnego środowiska agenta:`);
261
+ let anyOutdated = false;
262
+ let anyMissing = false;
263
+
264
+ for (const a of agents) {
265
+ const sum = agentSummary[a.id];
266
+ if (sum.outdated > 0) anyOutdated = true;
267
+ if (sum.missing > 0) anyMissing = true;
268
+
269
+ let badgeText = '';
270
+ if (sum.upToDate === sum.total) {
271
+ badgeText = `\x1b[32m${sum.upToDate}/${sum.total} aktualne\x1b[0m`;
272
+ } else {
273
+ const parts = [];
274
+ if (sum.upToDate > 0) parts.push(`\x1b[32m${sum.upToDate} aktualne\x1b[0m`);
275
+ if (sum.outdated > 0) parts.push(`\x1b[33m${sum.outdated} do aktualizacji\x1b[0m`);
276
+ if (sum.missing > 0) parts.push(`\x1b[90m${sum.missing} brak\x1b[0m`);
277
+ badgeText = parts.join(', ');
278
+ }
279
+ out(` • ${(a.name + ' (' + a.id + '):').padEnd(36)} ${badgeText}`);
280
+ }
281
+
282
+ if (anyOutdated || anyMissing) {
283
+ out(`\n💡 Aby zainstalować brakujące i zaktualizować wszystkie skille u agentów:`);
284
+ out(` \x1b[36mdeertools skill sync\x1b[0m (lub: \x1b[36mdeertools sync\x1b[0m)`);
285
+ } else {
286
+ out(`\n✓ Wszystkie skille zespołu (${items.length}) są zainstalowane i aktualne u wszystkich wykrytych agentów.`);
287
+ }
288
+ out('─'.repeat(78) + '\n');
71
289
  }
72
290
 
73
291
  async function cmdSkillSearch(query, flags, out, err) {
74
292
  return cmdSkillList({ ...flags, query }, out, err);
75
293
  }
76
294
 
77
- async function cmdSkillInstall(target, flags, out, err) {
78
- let [slug, requestedVersion] = String(target || '').split('@');
79
- slug = slug.trim();
80
-
81
- // Jeśli brak argumentu lub "all" lub nazwa produktu Deer, zachowaj kompatybilność wsteczną
82
- if (!slug || slug === 'all' || ['estimate', 'catalog', 'deliveries', 'supplier', 'rendermind'].includes(slug)) {
83
- const products = resolveProducts(slug || 'all');
84
- const agents = detectAgents({ dir: flags.dir, agent: flags.agent });
85
- let installed = 0;
86
-
87
- for (const product of products) {
88
- const r = await fetchText(`${product.site}/skill/SKILL.md`);
89
- if (!r.ok) continue;
90
- const skillName = parseFrontmatterName(r.text) || `deer-${product.id}`;
91
- for (const agent of agents) {
92
- try {
93
- const dest = installSkillContent(agent, skillName, r.text);
94
- out(`✓ [${agent.id}] ${product.name}: ${dest}`);
95
- installed += 1;
96
- } catch (e) {
97
- err(`✗ [${agent.id}] ${product.name}: ${e.message}`);
98
- }
99
- }
100
- }
101
- if (!installed) throw new Error('Nie zainstalowano żadnego skilla produktowego.');
102
- return;
103
- }
295
+ /**
296
+ * Instaluje pojedynczy skill z DeerSkills Hub.
297
+ */
298
+ async function installSingleSkill(slug, requestedVersion, flags, out, err, { silentSummary = false } = {}) {
299
+ const legacyMap = {
300
+ estimate: 'deer-kosztorys',
301
+ catalog: 'deer-catalog',
302
+ deliveries: 'deertools-dostawy',
303
+ supplier: 'deertools-supplier-hub',
304
+ rendermind: 'rendermind',
305
+ };
306
+ const resolvedSlug = legacyMap[slug] || slug;
104
307
 
105
- // Instalacja konkretnego skilla z DeerSkills Hub
106
- out(`Pobieram skill „${slug}”${requestedVersion ? ` w wersji ${requestedVersion}` : ''} z DeerSkills Hub...`);
107
- const res = await skillsHub.getSkill(slug, requestedVersion);
308
+ if (!silentSummary) {
309
+ out(`Pobieram skill „${resolvedSlug}”${requestedVersion ? ` w wersji ${requestedVersion}` : ''} z DeerSkills Hub...`);
310
+ }
311
+ const res = await skillsHub.getSkill(resolvedSlug, requestedVersion);
108
312
 
109
313
  if (!res.ok || !res.data?.skill) {
110
- throw new Error(`Nie znaleziono skilla „${slug}” w DeerSkills Hub (${res.status}): ${res.data?.error || 'Błąd'}`);
314
+ throw new Error(`Nie znaleziono skilla „${resolvedSlug}” w DeerSkills Hub (${res.status}): ${res.data?.error || 'Błąd'}`);
111
315
  }
112
316
 
113
317
  const skill = res.data.skill;
114
318
  const versionData = res.data.selected_version || res.data.version || (res.data.versions && res.data.versions[0]);
115
319
  if (!versionData) {
116
- throw new Error(`Brak opublikowanej wersji dla skilla „${slug}”.`);
320
+ throw new Error(`Brak opublikowanej wersji dla skilla „${resolvedSlug}”.`);
117
321
  }
118
322
  const agents = detectAgents({ dir: flags.dir, agent: flags.agent });
119
323
 
@@ -125,12 +329,14 @@ async function cmdSkillInstall(target, flags, out, err) {
125
329
  for (const agent of agents) {
126
330
  try {
127
331
  const dest = installSkillContent(agent, skill.slug, versionData.skill_md_content);
128
- out(`✓ [${agent.id}] Zainstalowano ${skill.full_slug} (${versionData.version}) → ${dest}`);
332
+ if (!silentSummary) {
333
+ out(`✓ [${agent.id}] Zainstalowano ${skill.full_slug} (${versionData.version}) → ${dest}`);
334
+ }
129
335
  count += 1;
130
336
 
131
337
  // Jeśli są dodatkowe pliki w manifeście, zapisz je obok SKILL.md
132
338
  if (versionData.files_manifest && Array.isArray(versionData.files_manifest)) {
133
- const baseDir = path.dirname(dest);
339
+ const baseDir = agent.format === 'cursor_mdc' ? path.join(agent.targetDir, skill.slug) : path.dirname(dest);
134
340
  for (const file of versionData.files_manifest) {
135
341
  const targetFile = path.join(baseDir, file.path);
136
342
  fs.mkdirSync(path.dirname(targetFile), { recursive: true });
@@ -147,18 +353,112 @@ async function cmdSkillInstall(target, flags, out, err) {
147
353
  }
148
354
 
149
355
  // Zapis do .deertools-lock.json
150
- const lock = readLock();
356
+ const lock = readLock(flags.dir);
151
357
  lock.skills[skill.slug] = {
152
358
  fullSlug: skill.full_slug,
153
359
  version: versionData.version,
154
360
  installedAt: new Date().toISOString(),
155
361
  sha256: versionData.sha256_checksum,
156
362
  };
157
- writeLock(lock);
158
- out(`✓ Zaktualizowano ${LOCK_FILE} (wersja ${versionData.version}).`);
363
+ writeLock(lock, flags.dir);
364
+
365
+ if (!silentSummary) {
366
+ out(`✓ Zaktualizowano ${LOCK_FILE} (wersja ${versionData.version}).`);
367
+ printSkillSummary(skill, versionData, out);
368
+ }
159
369
 
160
- // Podsumowanie możliwości skilla dla użytkownika i Agenta AI
161
- printSkillSummary(skill, versionData, out);
370
+ return { skill, versionData, count };
371
+ }
372
+
373
+ /**
374
+ * Instaluje lub synchronizuje wszystkie skille zespołu z DeerSkills Hub.
375
+ */
376
+ async function cmdSkillSync(flags, out, err) {
377
+ out('Pobieram pełną listę skilli zespołu z DeerSkills Hub...');
378
+ const res = await skillsHub.listSkills({ limit: 100 });
379
+ if (!res.ok) {
380
+ err(`✗ Błąd pobierania katalogu skilli (${res.status}): ${res.data?.error || 'Brak dostępu'}`);
381
+ return;
382
+ }
383
+ const items = Array.isArray(res.data) ? res.data : (res.data?.skills || []);
384
+ if (items.length === 0) {
385
+ out('Brak skilli w DeerSkills Hub.');
386
+ return;
387
+ }
388
+
389
+ let agents = detectAgents({ dir: flags.dir, agent: flags.agent });
390
+ if (flags.i || flags.interactive || flags.select) {
391
+ const { promptSelectAgents } = require('./agent-detector');
392
+ const available = detectAgents({ dir: flags.dir, 'ignore-selected': true });
393
+ agents = await promptSelectAgents(available);
394
+ flags = { ...flags, agent: agents.map((a) => a.id).join(',') };
395
+ }
396
+
397
+ if (agents.length === 0) {
398
+ err('✗ Nie wybrano ani nie wykryto żadnego środowiska agenta AI.');
399
+ return;
400
+ }
401
+
402
+ const lock = readLock(flags.dir);
403
+ out(`Platformy docelowe: \x1b[36m${agents.map((a) => a.name || a.id).join(', ')}\x1b[0m`);
404
+ out(`Rozpoczynam synchronizację ${items.length} skilli zespołu...\n`);
405
+
406
+ let installedCount = 0;
407
+ let updatedCount = 0;
408
+ let unchangedCount = 0;
409
+ let failedCount = 0;
410
+
411
+ for (const s of items) {
412
+ const slug = s.slug;
413
+ const fullSlug = s.full_slug;
414
+ const current = lock.skills?.[slug] || lock.skills?.[fullSlug];
415
+ const currentVer = current?.version;
416
+ const latestVer = s.latest_version || '1.0.0';
417
+
418
+ if (currentVer && currentVer === latestVer && !flags.force) {
419
+ out(` \x1b[32m✓\x1b[0m ${s.full_slug.padEnd(38)} v${currentVer.padEnd(8)} [Aktualny]`);
420
+ unchangedCount += 1;
421
+ continue;
422
+ }
423
+
424
+ try {
425
+ await installSingleSkill(s.full_slug || s.slug, latestVer, flags, out, err, { silentSummary: true });
426
+ if (currentVer) {
427
+ out(` \x1b[33m↑\x1b[0m ${s.full_slug.padEnd(38)} v${latestVer.padEnd(8)} [Zaktualizowano z v${currentVer}]`);
428
+ updatedCount += 1;
429
+ } else {
430
+ out(` \x1b[36m+\x1b[0m ${s.full_slug.padEnd(38)} v${latestVer.padEnd(8)} [Zainstalowano]`);
431
+ installedCount += 1;
432
+ }
433
+ } catch (e) {
434
+ out(` \x1b[31m✗\x1b[0m ${s.full_slug.padEnd(38)} Błąd: ${e.message}`);
435
+ failedCount += 1;
436
+ }
437
+ }
438
+
439
+ out('\n' + '─'.repeat(72));
440
+ out(`\x1b[1m✓ Podsumowanie synchronizacji ekosystemu Deer Design:\x1b[0m`);
441
+ out(` • Nowo zainstalowane: \x1b[36m${installedCount}\x1b[0m`);
442
+ out(` • Zaktualizowane: \x1b[33m${updatedCount}\x1b[0m`);
443
+ out(` • Aktualne bez zmian: \x1b[32m${unchangedCount}\x1b[0m`);
444
+ if (failedCount > 0) {
445
+ out(` • Błędy instalacji: \x1b[31m${failedCount}\x1b[0m`);
446
+ }
447
+ out(` • Agenci docelowi: ${agents.map((a) => a.id).join(', ')}`);
448
+ out(` • Zapisano w rejestrze: ${LOCK_FILE}`);
449
+ out('─'.repeat(72) + '\n');
450
+ }
451
+
452
+ async function cmdSkillInstall(target, flags, out, err) {
453
+ let [slug, requestedVersion] = String(target || '').split('@');
454
+ slug = (slug || '').trim();
455
+
456
+ // Jeśli brak argumentu lub "all" / "--all" / "team" / "*" lub flaga --all, synchronizuj wszystkie
457
+ if (!slug || slug === 'all' || slug === '--all' || slug === 'team' || slug === '*' || flags.all) {
458
+ return cmdSkillSync(flags, out, err);
459
+ }
460
+
461
+ await installSingleSkill(slug, requestedVersion, flags, out, err);
162
462
  }
163
463
 
164
464
  function extractCapabilities(content, summary = '') {
@@ -223,15 +523,41 @@ async function cmdSkillPush(dir, flags, out, err) {
223
523
  const targetDir = dir || process.cwd();
224
524
  out(`Analizuję folder skilla: ${targetDir}...`);
225
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
+
226
541
  const res = await skillsHub.pushSkill(targetDir, {
227
542
  version: flags.version || null,
228
543
  bump: flags.bump || (flags.major ? 'major' : flags.minor ? 'minor' : 'patch'),
229
544
  changelog: flags.changelog || flags.message || flags.m || null,
230
- visibility: flags.private ? 'private' : 'shared',
545
+ visibility,
546
+ scope,
547
+ namespace: flags.namespace || null,
548
+ team,
549
+ tags: cliTags,
231
550
  });
232
551
 
233
552
  if (!res.ok) {
234
- 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'}`);
235
561
  }
236
562
 
237
563
  const skill = res.data.skill || res.data || {};
@@ -241,17 +567,77 @@ async function cmdSkillPush(dir, flags, out, err) {
241
567
  ? res.data.pushed_manifest_count
242
568
  : (res.data.version?.files_manifest?.length || 0);
243
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
+
244
588
  out(`\n✓ Pomyślnie opublikowano skill w DeerSkills Hub:`);
245
589
  out(` • Identyfikator: \x1b[1m${skill.full_slug || skill.slug}\x1b[0m`);
590
+ out(` • Przestrzeń: ${scopeLabel}`);
246
591
  out(` • Wersja: \x1b[32mv${versionNum}\x1b[0m`);
247
592
  if (changelog && changelog !== versionNum) {
248
593
  out(` • Changelog: \x1b[33m${changelog}\x1b[0m`);
249
594
  }
250
595
  out(` • Paczka: ${manifestCount + 1} plików (SKILL.md + ${manifestCount} plików w podkatalogach)`);
251
- 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}`);
252
600
  out(`\nAby zainstalować tę wersję w projekcie:\n \x1b[36mdeertools skill install ${skill.full_slug || skill.slug}@${versionNum}\x1b[0m\n`);
253
601
  }
254
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
+
255
641
  async function cmdSkillVersions(slug, flags, out, err) {
256
642
  if (!slug) throw new Error('Podaj slug skilla, np. deertools skill versions deer-technical-documentation');
257
643
  out(`Pobieram historię wydań dla skilla „${slug}”...`);
@@ -472,19 +858,131 @@ async function cmdSkillPromote(slug, flags, out, err) {
472
858
  }
473
859
 
474
860
  out('✓ Wniosek o nominację został pomyślnie przesłany do administratorów.');
475
- 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
+ }
476
971
  }
477
972
 
478
973
  module.exports = {
479
974
  cmdSkillList,
480
975
  cmdSkillSearch,
481
976
  cmdSkillInstall,
977
+ cmdSkillSync,
482
978
  cmdSkillPush,
483
979
  cmdSkillCheck,
484
980
  cmdSkillUpdate,
485
981
  cmdSkillPromote,
486
982
  cmdSkillVersions,
487
983
  cmdSkillDiff,
984
+ cmdSkillReview,
985
+ cmdSkillShare,
488
986
  checkSkillUpdates,
489
987
  updateSkills,
490
988
  };