@deerdesign/deertools 1.2.13 → 1.2.17

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
@@ -185,11 +185,12 @@ deertools profile list
185
185
  deertools profile use staging
186
186
 
187
187
  # 12. AI Agent Skills (DeerSkills Hub):
188
- deertools skill list # Wyświetl skille (oficjalne globalne i Twoje)
189
- deertools skill search "kosztorys" # Wyszukaj skille po nazwie, tagu lub opisie
190
- deertools skill install deer-kosztorys # Zainstaluj skill w ~/.gemini/antigravity/skills lub .agents
188
+ deertools skill list # Wyświetl listę skilli ze statusem zainstalowania w projekcie
189
+ deertools skill sync # Jedna komenda: instaluje brakujące i aktualizuje obecne skille zespołu
190
+ deertools skill install --all # To samo co skill sync (pełna synchronizacja zespołu)
191
+ deertools skill install deer-kosztorys # Zainstaluj konkretny skill w ~/.gemini/antigravity/skills lub .agents
191
192
  deertools skill install deer-kosztorys --version 1.0.0
192
- deertools skill install all # Zainstaluj wszystkie oficjalne skille Deer Design
193
+ deertools skill search "kosztorys" # Wyszukaj skille po nazwie, tagu lub opisie
193
194
  deertools skill push ./my-skill # Opublikuj nową wersję z automatycznym skanerem sekretów
194
195
  deertools skill push ./my-skill --bump minor # Automatyczne podbicie wersji SemVer
195
196
  deertools skill update deer-kosztorys # Zaktualizuj zainstalowany skill do najnowszej wersji
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deerdesign/deertools",
3
- "version": "1.2.13",
3
+ "version": "1.2.17",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -3,6 +3,27 @@ const fs = require('fs');
3
3
  const os = require('os');
4
4
  const path = require('path');
5
5
 
6
+ const { execSync } = require('child_process');
7
+ const readline = require('readline');
8
+ const { readConfig, writeConfig } = require('./profiles');
9
+
10
+ function isBinaryInPath(cmd) {
11
+ try {
12
+ const check = process.platform === 'win32' ? `where ${cmd} 2>nul` : `which ${cmd} 2>/dev/null`;
13
+ execSync(check, { stdio: 'ignore', timeout: 800 });
14
+ return true;
15
+ } catch (_) {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ function isAppInstalled(appName) {
21
+ if (process.platform === 'darwin') {
22
+ return fs.existsSync(`/Applications/${appName}.app`) || fs.existsSync(path.join(os.homedir(), `Applications/${appName}.app`));
23
+ }
24
+ return false;
25
+ }
26
+
6
27
  /**
7
28
  * Rejestr obsługiwanych platform agentów AI.
8
29
  * Obsługuje: Google Antigravity / Gemini, Claude Code, OpenAI Codex, Hermes AI,
@@ -19,35 +40,35 @@ const KNOWN_AGENTS = [
19
40
  {
20
41
  id: 'claude',
21
42
  name: 'Claude Code',
22
- detect: (home) => fs.existsSync(path.join(home, '.claude')),
43
+ detect: (home) => fs.existsSync(path.join(home, '.claude')) || isBinaryInPath('claude'),
23
44
  getTarget: (home) => path.join(home, '.claude', 'skills'),
24
45
  format: 'skill_md',
25
46
  },
26
47
  {
27
48
  id: 'codex',
28
49
  name: 'OpenAI Codex',
29
- detect: (home) => fs.existsSync(path.join(home, '.codex')),
50
+ detect: (home) => fs.existsSync(path.join(home, '.codex')) || isBinaryInPath('codex'),
30
51
  getTarget: (home) => path.join(home, '.codex', 'skills'),
31
52
  format: 'skill_md',
32
53
  },
33
54
  {
34
55
  id: 'hermes',
35
56
  name: 'Hermes AI',
36
- detect: (home) => fs.existsSync(path.join(home, '.hermes')) || fs.existsSync(path.join(home, '.config', 'hermes')) || Boolean(process.env.HERMES_HOME),
57
+ detect: (home) => fs.existsSync(path.join(home, '.hermes')) || fs.existsSync(path.join(home, '.config', 'hermes')) || Boolean(process.env.HERMES_HOME) || isBinaryInPath('hermes'),
37
58
  getTarget: (home) => process.env.HERMES_SKILLS_DIR || path.join(home, '.hermes', 'skills'),
38
59
  format: 'skill_md',
39
60
  },
40
61
  {
41
62
  id: 'cursor',
42
63
  name: 'Cursor IDE',
43
- detect: (home, cwd) => fs.existsSync(path.join(cwd, '.cursor')) || fs.existsSync(path.join(home, '.cursor')),
64
+ detect: (home, cwd) => fs.existsSync(path.join(cwd, '.cursor')) || fs.existsSync(path.join(home, '.cursor')) || isAppInstalled('Cursor'),
44
65
  getTarget: (home, cwd) => fs.existsSync(path.join(cwd, '.cursor')) ? path.join(cwd, '.cursor', 'rules') : path.join(home, '.cursor', 'rules'),
45
66
  format: 'cursor_mdc',
46
67
  },
47
68
  {
48
69
  id: 'windsurf',
49
70
  name: 'Windsurf / Codeium',
50
- detect: (home, cwd) => fs.existsSync(path.join(cwd, '.windsurfrules')) || fs.existsSync(path.join(cwd, '.windsurf')) || fs.existsSync(path.join(home, '.codeium')),
71
+ detect: (home, cwd) => fs.existsSync(path.join(cwd, '.windsurfrules')) || fs.existsSync(path.join(cwd, '.windsurf')) || fs.existsSync(path.join(home, '.codeium')) || isAppInstalled('Windsurf'),
51
72
  getTarget: (home, cwd) => path.join(cwd, '.windsurf', 'rules'),
52
73
  format: 'skill_md',
53
74
  },
@@ -61,7 +82,40 @@ const KNOWN_AGENTS = [
61
82
  ];
62
83
 
63
84
  /**
64
- * Wykrywa zainstalowane środowiska agentów lub filtruje według flagi --agent.
85
+ * Zwraca listę skonfigurowanych agentów użytkownika na tym PC.
86
+ */
87
+ function getSelectedAgents() {
88
+ if (process.env.DEERTOOLS_AGENTS) {
89
+ const fromEnv = process.env.DEERTOOLS_AGENTS.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean);
90
+ if (fromEnv.length > 0) return fromEnv;
91
+ }
92
+ const cfg = readConfig();
93
+ if (cfg.selectedAgents && Array.isArray(cfg.selectedAgents) && cfg.selectedAgents.length > 0) {
94
+ return cfg.selectedAgents.map((s) => String(s).trim().toLowerCase());
95
+ }
96
+ return null;
97
+ }
98
+
99
+ /**
100
+ * Zapisuje listę wybranych agentów użytkownika w konfiguracji ~/.deertools/config.json.
101
+ */
102
+ function setSelectedAgents(agentIds) {
103
+ const cfg = readConfig();
104
+ cfg.selectedAgents = agentIds.map((s) => String(s).trim().toLowerCase());
105
+ writeConfig(cfg);
106
+ }
107
+
108
+ /**
109
+ * Czyści zapisaną listę agentów (przywraca auto-detekcję wszystkich obecnych na PC).
110
+ */
111
+ function clearSelectedAgents() {
112
+ const cfg = readConfig();
113
+ delete cfg.selectedAgents;
114
+ writeConfig(cfg);
115
+ }
116
+
117
+ /**
118
+ * Wykrywa zainstalowane środowiska agentów lub filtruje według flagi --agent / konfiguracji.
65
119
  */
66
120
  function detectAgents(options = {}) {
67
121
  const home = os.homedir();
@@ -77,7 +131,7 @@ function detectAgents(options = {}) {
77
131
  }];
78
132
  }
79
133
 
80
- // Wymuszenie instalacji do WSZYSTKICH obsługiwanych platform (--all-agents)
134
+ // Wymuszenie instalacji do WSZYSTKICH znanych platform (--all-agents)
81
135
  if (options['all-agents']) {
82
136
  return KNOWN_AGENTS.map((spec) => ({
83
137
  id: spec.id,
@@ -87,21 +141,35 @@ function detectAgents(options = {}) {
87
141
  }));
88
142
  }
89
143
 
90
- // Wskazanie konkretnego agenta (--agent claude / --agent codex / --agent hermes / etc.)
144
+ // Wskazanie konkretnego agenta lub listy po przecinku (--agent claude,antigravity)
91
145
  if (options.agent && options.agent !== 'all' && options.agent !== 'auto') {
92
- const requested = options.agent.toLowerCase().trim();
93
- const spec = KNOWN_AGENTS.find((a) => a.id === requested);
94
- if (spec) {
95
- return [{
146
+ const requested = options.agent.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean);
147
+ const matched = KNOWN_AGENTS.filter((spec) => requested.includes(spec.id));
148
+ if (matched.length > 0) {
149
+ return matched.map((spec) => ({
150
+ id: spec.id,
151
+ name: spec.name,
152
+ targetDir: spec.getTarget(home, cwd),
153
+ format: spec.format,
154
+ }));
155
+ }
156
+ }
157
+
158
+ // Jeśli użytkownik ma zapisaną konfigurację wybranych agentów na tym PC:
159
+ const configured = getSelectedAgents();
160
+ if (configured && configured.length > 0 && !options['ignore-selected']) {
161
+ const matched = KNOWN_AGENTS.filter((spec) => configured.includes(spec.id));
162
+ if (matched.length > 0) {
163
+ return matched.map((spec) => ({
96
164
  id: spec.id,
97
165
  name: spec.name,
98
166
  targetDir: spec.getTarget(home, cwd),
99
167
  format: spec.format,
100
- }];
168
+ }));
101
169
  }
102
170
  }
103
171
 
104
- // Automatyczne wykrywanie obecnych agentów
172
+ // Automatyczne wykrywanie obecnych agentów na PC
105
173
  const matched = [];
106
174
  for (const spec of KNOWN_AGENTS) {
107
175
  try {
@@ -157,4 +225,48 @@ function installSkillContent(agent, skillName, rawMarkdown) {
157
225
  return filePath;
158
226
  }
159
227
 
160
- module.exports = { KNOWN_AGENTS, detectAgents, installSkillContent };
228
+ /**
229
+ * Interaktywny prompt pytający użytkownika o wybór platform agentów w terminalu.
230
+ */
231
+ async function promptSelectAgents(availableAgents) {
232
+ if (!process.stdin.isTTY) return availableAgents;
233
+ return new Promise((resolve) => {
234
+ const rl = readline.createInterface({
235
+ input: process.stdin,
236
+ output: process.stdout,
237
+ });
238
+
239
+ process.stdout.write(`\nWykryto platformy agentów AI na tym komputerze:\n`);
240
+ availableAgents.forEach((a, i) => {
241
+ process.stdout.write(` [${i + 1}] ${a.name} (${a.id})\n`);
242
+ });
243
+ process.stdout.write(`\nWybierz platformy (np. 1, 3 lub 'all') [domyślnie: wszystkie wykryte]: `);
244
+
245
+ rl.question('', (answer) => {
246
+ rl.close();
247
+ const trimmed = (answer || '').trim().toLowerCase();
248
+ if (!trimmed || trimmed === 'all') {
249
+ resolve(availableAgents);
250
+ return;
251
+ }
252
+ const parts = trimmed.split(/[\s,]+/).map((p) => parseInt(p, 10)).filter((n) => !isNaN(n) && n >= 1 && n <= availableAgents.length);
253
+ if (parts.length === 0) {
254
+ resolve(availableAgents);
255
+ return;
256
+ }
257
+ const selected = parts.map((n) => availableAgents[n - 1]);
258
+ resolve(selected);
259
+ });
260
+ });
261
+ }
262
+
263
+ module.exports = {
264
+ KNOWN_AGENTS,
265
+ detectAgents,
266
+ installSkillContent,
267
+ getSelectedAgents,
268
+ setSelectedAgents,
269
+ clearSelectedAgents,
270
+ promptSelectAgents,
271
+ isBinaryInPath,
272
+ };
package/src/cli.js CHANGED
@@ -32,6 +32,8 @@ async function main(argv) {
32
32
  const table = {
33
33
  login: c.cmdLogin, logout: c.cmdLogout, whoami: c.cmdWhoami, docs: c.cmdDocs,
34
34
  endpoints: c.cmdEndpoints, api: c.cmdApi, estimate: c.cmdEstimate, skill: c.cmdSkill,
35
+ sync: (pos, flags) => c.cmdSkill(['sync', ...pos], flags),
36
+ agent: c.cmdAgent, agents: c.cmdAgent,
35
37
  mcp: c.cmdMcp, doctor: c.cmdDoctor, audit: c.cmdAudit, profile: c.cmdProfile,
36
38
  completion: c.cmdCompletion, catalog: c.cmdCatalog, deliveries: c.cmdDeliveries,
37
39
  supplier: c.cmdSupplier, rendermind: c.cmdRendermind, baselinker: c.cmdBaseLinker,
package/src/commands.js CHANGED
@@ -379,6 +379,9 @@ async function cmdSkill(pos, flags) {
379
379
  if (sub === 'bundle') {
380
380
  return cmdSkillBundle(pos[1], flags);
381
381
  }
382
+ if (sub === 'sync') {
383
+ return skillCommands.cmdSkillSync(flags, out, err);
384
+ }
382
385
  if (sub === 'list') {
383
386
  return skillCommands.cmdSkillList(flags, out, err);
384
387
  }
@@ -404,9 +407,10 @@ async function cmdSkill(pos, flags) {
404
407
  return skillCommands.cmdSkillPromote(pos[1], flags, out, err);
405
408
  }
406
409
  if (sub === 'install') {
407
- return skillCommands.cmdSkillInstall(pos[1], flags, out, err);
410
+ const target = pos[1] || (flags.all ? '--all' : null);
411
+ return skillCommands.cmdSkillInstall(target, flags, out, err);
408
412
  }
409
- fail('Użycie:\n deertools skill login [token]\n deertools skill bundle <architekci|kosztorysy|logistyka|sprzedaz|it>\n deertools skill list [--tag <tag>]\n deertools skill search <fraza>\n deertools skill install <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);
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);
410
414
  }
411
415
 
412
416
 
@@ -508,6 +512,91 @@ async function cmdMcp(pos, flags) {
508
512
  fail('Użycie: deertools mcp [config|install|serve] [produkt|all] [--json]', 2);
509
513
  }
510
514
 
515
+ async function cmdAgent(pos, flags) {
516
+ const {
517
+ KNOWN_AGENTS,
518
+ detectAgents,
519
+ getSelectedAgents,
520
+ setSelectedAgents,
521
+ clearSelectedAgents,
522
+ promptSelectAgents,
523
+ } = require('./agent-detector');
524
+ const action = pos[0] || 'list';
525
+
526
+ if (action === 'list') {
527
+ const selected = getSelectedAgents();
528
+ const detected = detectAgents({ 'ignore-selected': true });
529
+ const detectedIds = new Set(detected.map((a) => a.id));
530
+
531
+ out(`\n=== Platformy Agentów AI (DeerTools) ===\n`);
532
+ for (const a of KNOWN_AGENTS) {
533
+ const isDetected = detectedIds.has(a.id);
534
+ const isActive = selected ? selected.includes(a.id) : isDetected;
535
+
536
+ let statusBadge = '\x1b[90m[○ Niewykryty na PC]\x1b[0m';
537
+ if (isDetected) statusBadge = '\x1b[32m[✓ Wykryty na PC]\x1b[0m';
538
+
539
+ let activeBadge = '';
540
+ if (isActive) {
541
+ activeBadge = ' \x1b[36m(AKTYWNY DLA SKILLI)\x1b[0m';
542
+ }
543
+
544
+ out(` • ${a.id.padEnd(16)} ${(a.name).padEnd(32)} ${statusBadge}${activeBadge}`);
545
+ out(` Ścieżka: ${a.getTarget(os.homedir(), process.cwd())}`);
546
+ out('');
547
+ }
548
+
549
+ out('─'.repeat(72));
550
+ if (selected && selected.length > 0) {
551
+ out(`📌 Wybrane platformy na tym PC: \x1b[36m${selected.join(', ')}\x1b[0m (zapisane w ~/.deertools/config.json)`);
552
+ out(` Skille instalują się tylko u wybranych agentów.`);
553
+ out(` Aby przywrócić auto-wykrywanie wszystkich: \x1b[33mdeertools agent reset\x1b[0m`);
554
+ } else {
555
+ out(`⚡ Tryb: \x1b[32mAutomatyczne wykrywanie wszystkich platform obecnych na komputerze\x1b[0m`);
556
+ out(` Aby ograniczyć synchronizację do wybranych agentów:`);
557
+ out(` \x1b[36mdeertools agent select antigravity,cursor\x1b[0m`);
558
+ }
559
+ out('─'.repeat(72) + '\n');
560
+ return;
561
+ }
562
+
563
+ if (action === 'select' || action === 'set') {
564
+ let targets = pos.slice(1).join(',').split(',').map((s) => s.trim().toLowerCase()).filter(Boolean);
565
+ if (targets.length === 0) {
566
+ const detected = detectAgents({ 'ignore-selected': true });
567
+ const picked = await promptSelectAgents(detected);
568
+ targets = picked.map((a) => a.id);
569
+ }
570
+
571
+ const validIds = KNOWN_AGENTS.map((a) => a.id);
572
+ const validTargets = targets.filter((id) => validIds.includes(id));
573
+
574
+ if (validTargets.length === 0) {
575
+ fail(`Błąd: Podaj poprawne identyfikatory agentów: ${validIds.join(', ')}`, 2);
576
+ }
577
+
578
+ setSelectedAgents(validTargets);
579
+ out(`✓ Zapisano aktywnych agentów na tym komputerze: \x1b[36m${validTargets.join(', ')}\x1b[0m`);
580
+ out(`Od teraz deertools skill sync i skill install instalują skille tylko dla tych platform.`);
581
+ out(`Aby przywrócić instalację u wszystkich wykrytych: deertools agent reset\n`);
582
+ return;
583
+ }
584
+
585
+ if (action === 'reset' || action === 'clear') {
586
+ clearSelectedAgents();
587
+ out(`✓ Zresetowano konfigurację agentów. Przywrócono automatyczne wykrywanie obecnych na PC.\n`);
588
+ return;
589
+ }
590
+
591
+ if (action === 'current') {
592
+ const active = detectAgents({});
593
+ out(active.map((a) => a.id).join(','));
594
+ return;
595
+ }
596
+
597
+ fail(`Nieznana podkomenda „agent ${action}”. Użyj: deertools agent [list|select <agenty>|reset|current]`, 2);
598
+ }
599
+
511
600
  const { cmdDoctor } = require('./doctor');
512
601
  const { cmdAudit } = require('./audit');
513
602
  const { cmdProfile } = require('./profiles');
@@ -544,9 +633,11 @@ BaseLinker MCP Proxy & Hub:
544
633
  deertools baselinker catalog <search|get> Operacje katalogowe z poziomu terminala.
545
634
 
546
635
  Integracje MCP i Agenci AI (DeerSkills Hub):
547
- deertools skill list [--tag <tag>] Przegląda skille globalne i użytkownika w DeerSkills Hub.
636
+ deertools agent [list|select <agenty>|reset] Wykrywanie i konfiguracja platform agentów AI na PC.
637
+ deertools skill list [--tag <tag>] Przegląda skille w Hubie wraz ze statusem zainstalowania.
638
+ deertools skill sync [--agent <id>|-i|--force] Instaluje brakujące i aktualizuje obecne skille zespołu.
639
+ deertools skill install [--all|<slug[@wersja]>] Instaluje wszystkie skille (--all) lub wskazany skill.
548
640
  deertools skill search <fraza> Wyszukuje umiejętności po słowach kluczowych.
549
- deertools skill install <slug[@wersja]> Instaluje skill w ~/.gemini/antigravity/skills lub .agents.
550
641
  deertools skill versions <slug> Historia wydań z changelogami z commitów i datami.
551
642
  deertools skill diff <slug> [wersjaA] [wersjaB] Porównanie zmian w plikach paczki i treści SKILL.md.
552
643
  deertools skill push [katalog] [--bump patch|minor] Publikuje nową wersję z automatycznym skanerem sekretów.
@@ -566,13 +657,13 @@ Narzędzia systemowe:
566
657
  deertools audit [--limit 25] [--clear] [--json] Historia operacji i wywołań API.
567
658
  deertools completion [install|zsh|bash] Autouzupełnianie w powłoce po wciśnięciu Tab.
568
659
 
569
- Zmienne środowiskowe: DEERTOOLS_<PRODUKT>_KEY, BASELINKER_TOKEN, DEERTOOLS_HOME, DEERTOOLS_PROFILE.
660
+ Zmienne środowiskowe: DEERTOOLS_<PRODUKT>_KEY, BASELINKER_TOKEN, DEERTOOLS_HOME, DEERTOOLS_PROFILE, DEERTOOLS_AGENTS.
570
661
  `;
571
662
 
572
663
  module.exports = {
573
664
  cmdLogin, cmdLogout, cmdWhoami, cmdDocs, cmdEndpoints, cmdApi, cmdEstimate, cmdSkill, cmdMcp,
574
665
  cmdDoctor, cmdAudit, cmdProfile, cmdCompletion, cmdCatalog, cmdDeliveries, cmdSupplier, cmdRendermind,
575
- cmdBaseLinker,
666
+ cmdBaseLinker, cmdAgent,
576
667
  HELP, parseQuery,
577
668
  };
578
669
 
@@ -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
+ }
369
+
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
+ }
159
460
 
160
- // Podsumowanie możliwości skilla dla użytkownika i Agenta AI
161
- printSkillSummary(skill, versionData, out);
461
+ await installSingleSkill(slug, requestedVersion, flags, out, err);
162
462
  }
163
463
 
164
464
  function extractCapabilities(content, summary = '') {
@@ -479,6 +779,7 @@ module.exports = {
479
779
  cmdSkillList,
480
780
  cmdSkillSearch,
481
781
  cmdSkillInstall,
782
+ cmdSkillSync,
482
783
  cmdSkillPush,
483
784
  cmdSkillCheck,
484
785
  cmdSkillUpdate,