@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/README.md +22 -10
- package/package.json +1 -1
- package/src/agent-detector.js +127 -15
- package/src/cli.js +3 -0
- package/src/commands.js +128 -7
- package/src/mcp-stdio.js +106 -0
- package/src/skill-commands.js +547 -49
- package/src/skills-hub.js +159 -2
- package/src/team-commands.js +151 -0
package/src/skill-commands.js
CHANGED
|
@@ -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 ||
|
|
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
|
-
|
|
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
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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 „${
|
|
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 „${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
161
|
-
|
|
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
|
|
545
|
+
visibility,
|
|
546
|
+
scope,
|
|
547
|
+
namespace: flags.namespace || null,
|
|
548
|
+
team,
|
|
549
|
+
tags: cliTags,
|
|
231
550
|
});
|
|
232
551
|
|
|
233
552
|
if (!res.ok) {
|
|
234
|
-
|
|
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
|
-
|
|
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
|
};
|