@deerdesign/deertools 1.2.26 → 1.2.32
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 +55 -62
- package/package.json +4 -3
- package/src/baselinker-commands.js +21 -94
- package/src/cli.js +29 -8
- package/src/commands.js +169 -138
- package/src/completion.js +24 -18
- package/src/embedding.js +59 -0
- package/src/rendermind/cli-commands.js +31 -0
- package/src/rendermind/client.js +393 -0
- package/src/rendermind/mcp-server.js +305 -0
- package/src/skill-commands.js +420 -24
- package/src/skills-hub.js +78 -2
- package/src/supplier/cli-commands.js +32 -0
- package/src/supplier/mcp-server.js +360 -0
- package/src/tui.js +24 -39
package/src/skill-commands.js
CHANGED
|
@@ -6,6 +6,7 @@ const skillsHub = require('./skills-hub');
|
|
|
6
6
|
const { parseFrontmatterName } = require('./openapi');
|
|
7
7
|
const { resolveProducts } = require('./registry');
|
|
8
8
|
const { fetchText } = require('./http');
|
|
9
|
+
const embedding = require('./embedding');
|
|
9
10
|
|
|
10
11
|
const { compareSemver } = require('./updater');
|
|
11
12
|
|
|
@@ -92,6 +93,82 @@ function readSkillVersion(filePath, lock, skill) {
|
|
|
92
93
|
return '1.0.0';
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Skanuje lokalne katalogi w poszukiwaniu skilli na dysku.
|
|
98
|
+
* Zwraca mapę slug -> { slug, version, dir, filePath, summary }.
|
|
99
|
+
*/
|
|
100
|
+
function scanLocalSkills(cwd = process.cwd(), agents = [], hubSlugs = null) {
|
|
101
|
+
const localSkills = new Map();
|
|
102
|
+
|
|
103
|
+
function inspectDir(skillDir, isAgentDir = false) {
|
|
104
|
+
const skillMd = path.join(skillDir, 'SKILL.md');
|
|
105
|
+
if (!fs.existsSync(skillMd)) return;
|
|
106
|
+
try {
|
|
107
|
+
const content = fs.readFileSync(skillMd, 'utf8');
|
|
108
|
+
let slug = path.basename(skillDir);
|
|
109
|
+
let version = '1.0.0';
|
|
110
|
+
let summary = '';
|
|
111
|
+
if (content.startsWith('---')) {
|
|
112
|
+
const parts = content.split('---', 2);
|
|
113
|
+
if (parts.length >= 2) {
|
|
114
|
+
const nameMatch = parts[1].match(/^name:\s*(.+)$/m);
|
|
115
|
+
if (nameMatch) slug = nameMatch[1].trim();
|
|
116
|
+
const verMatch = parts[1].match(/^version:\s*['"]?([0-9]+\.[0-9]+(\.[0-9]+)?(-[a-zA-Z0-9.]+)?)/im);
|
|
117
|
+
if (verMatch) version = verMatch[1].trim();
|
|
118
|
+
const descMatch = parts[1].match(/^description:\s*(.+)$/m);
|
|
119
|
+
if (descMatch) summary = descMatch[1].trim();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Jeśli skanujemy katalog zewnętrznego agenta (~/.gemini/...): bierzemy tylko skille Deer Design lub znane w Hubie
|
|
124
|
+
if (isAgentDir) {
|
|
125
|
+
const isDeer = slug.startsWith('deer') || slug.startsWith('rendermind') || slug.startsWith('@');
|
|
126
|
+
const isKnown = hubSlugs && (hubSlugs.has(slug) || hubSlugs.has(slug.replace(/^@[^/]+\//, '')));
|
|
127
|
+
if (!isDeer && !isKnown) return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!localSkills.has(slug)) {
|
|
131
|
+
localSkills.set(slug, {
|
|
132
|
+
slug,
|
|
133
|
+
version,
|
|
134
|
+
dir: skillDir,
|
|
135
|
+
filePath: skillMd,
|
|
136
|
+
summary,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
} catch (_) {}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 1. Sprawdź .agents/skills w bieżącym projekcie
|
|
143
|
+
const projectAgentsSkills = path.join(cwd, '.agents', 'skills');
|
|
144
|
+
if (fs.existsSync(projectAgentsSkills)) {
|
|
145
|
+
try {
|
|
146
|
+
const entries = fs.readdirSync(projectAgentsSkills, { withFileTypes: true });
|
|
147
|
+
for (const ent of entries) {
|
|
148
|
+
if (ent.isDirectory() && !ent.name.startsWith('.')) {
|
|
149
|
+
inspectDir(path.join(projectAgentsSkills, ent.name), false);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch (_) {}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 2. Sprawdź katalogi wykrytych agentów (tylko skille powiązane z Hubem lub Deer Design)
|
|
156
|
+
for (const agent of agents) {
|
|
157
|
+
if (agent.targetDir && fs.existsSync(agent.targetDir)) {
|
|
158
|
+
try {
|
|
159
|
+
const entries = fs.readdirSync(agent.targetDir, { withFileTypes: true });
|
|
160
|
+
for (const ent of entries) {
|
|
161
|
+
if (ent.isDirectory() && !ent.name.startsWith('.')) {
|
|
162
|
+
inspectDir(path.join(agent.targetDir, ent.name), true);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch (_) {}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return localSkills;
|
|
170
|
+
}
|
|
171
|
+
|
|
95
172
|
/**
|
|
96
173
|
* Bada stan instalacji i wersję skilla u wszystkich zadanych agentów.
|
|
97
174
|
*/
|
|
@@ -101,6 +178,7 @@ function inspectSkillForAgents(agents, skill, lock) {
|
|
|
101
178
|
let upToDateCount = 0;
|
|
102
179
|
let outdatedCount = 0;
|
|
103
180
|
let missingCount = 0;
|
|
181
|
+
let aheadCount = 0;
|
|
104
182
|
|
|
105
183
|
for (const agent of agents) {
|
|
106
184
|
const filePath = findSkillFileForAgent(agent, skill);
|
|
@@ -122,7 +200,7 @@ function inspectSkillForAgents(agents, skill, lock) {
|
|
|
122
200
|
const installedVer = readSkillVersion(filePath, lock, skill);
|
|
123
201
|
const cmp = compareSemver(installedVer, skill.latest_version);
|
|
124
202
|
|
|
125
|
-
if (cmp
|
|
203
|
+
if (cmp === 0) {
|
|
126
204
|
agentStatuses[agent.id] = {
|
|
127
205
|
agentId: agent.id,
|
|
128
206
|
agentName: agent.name,
|
|
@@ -133,6 +211,19 @@ function inspectSkillForAgents(agents, skill, lock) {
|
|
|
133
211
|
badge: `\x1b[32m[✓ ${agent.id}: v${installedVer}]\x1b[0m`,
|
|
134
212
|
};
|
|
135
213
|
upToDateCount += 1;
|
|
214
|
+
} else if (cmp > 0) {
|
|
215
|
+
agentStatuses[agent.id] = {
|
|
216
|
+
agentId: agent.id,
|
|
217
|
+
agentName: agent.name,
|
|
218
|
+
installed: true,
|
|
219
|
+
version: installedVer,
|
|
220
|
+
status: 'ahead',
|
|
221
|
+
hub_version: skill.latest_version,
|
|
222
|
+
filePath,
|
|
223
|
+
badge: `\x1b[35m[★ ${agent.id}: v${installedVer} > Hub v${skill.latest_version}]\x1b[0m`,
|
|
224
|
+
};
|
|
225
|
+
upToDateCount += 1;
|
|
226
|
+
aheadCount += 1;
|
|
136
227
|
} else {
|
|
137
228
|
agentStatuses[agent.id] = {
|
|
138
229
|
agentId: agent.id,
|
|
@@ -152,7 +243,10 @@ function inspectSkillForAgents(agents, skill, lock) {
|
|
|
152
243
|
let overallBadge = '\x1b[90m[○ Niezainstalowany]\x1b[0m';
|
|
153
244
|
|
|
154
245
|
if (installedCount > 0) {
|
|
155
|
-
if (
|
|
246
|
+
if (aheadCount > 0) {
|
|
247
|
+
overallStatus = 'ahead';
|
|
248
|
+
overallBadge = `\x1b[35m[★ Nowszy lokalnie (${aheadCount})]\x1b[0m`;
|
|
249
|
+
} else if (installedCount === agents.length && outdatedCount === 0) {
|
|
156
250
|
overallStatus = 'up_to_date';
|
|
157
251
|
overallBadge = `\x1b[32m[✓ Aktualny (${installedCount}/${agents.length})]\x1b[0m`;
|
|
158
252
|
} else if (outdatedCount > 0) {
|
|
@@ -170,27 +264,148 @@ function inspectSkillForAgents(agents, skill, lock) {
|
|
|
170
264
|
upToDateCount,
|
|
171
265
|
outdatedCount,
|
|
172
266
|
missingCount,
|
|
267
|
+
aheadCount,
|
|
173
268
|
overallStatus,
|
|
174
269
|
overallBadge,
|
|
175
270
|
};
|
|
176
271
|
}
|
|
177
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Sprawdza czy dany skill pasuje do zapytania tekstowego.
|
|
275
|
+
* Przeszukuje kompleksowo:
|
|
276
|
+
* 1. Nazwę (slug, full_slug, title)
|
|
277
|
+
* 2. Opis (summary / description - pełny tekst opisu z SKILL.md)
|
|
278
|
+
* 3. Tagi (zarówno całe tagi, jak i dopasowania cząstkowe, np. 'matrycowy' w 'cennik-matrycowy')
|
|
279
|
+
* 4. Kategorie (nazwa kategorii oraz jej slug)
|
|
280
|
+
* 5. Namespace oraz autora (owner_name, owner_email)
|
|
281
|
+
*/
|
|
282
|
+
function matchSkillQuery(skill, query) {
|
|
283
|
+
if (!query) return true;
|
|
284
|
+
const q = String(query).toLowerCase().trim();
|
|
285
|
+
if (!q) return true;
|
|
286
|
+
|
|
287
|
+
// 1. Nazwa, slug, full_slug, title
|
|
288
|
+
if (skill.slug && String(skill.slug).toLowerCase().includes(q)) return true;
|
|
289
|
+
if (skill.full_slug && String(skill.full_slug).toLowerCase().includes(q)) return true;
|
|
290
|
+
if (skill.title && String(skill.title).toLowerCase().includes(q)) return true;
|
|
291
|
+
|
|
292
|
+
// 2. Opis (summary / description)
|
|
293
|
+
if (skill.summary && String(skill.summary).toLowerCase().includes(q)) return true;
|
|
294
|
+
if (skill.description && String(skill.description).toLowerCase().includes(q)) return true;
|
|
295
|
+
|
|
296
|
+
// 3. Tagi (zarówno dopasowanie pełne, jak i podciąg wewnątrz tagu)
|
|
297
|
+
if (Array.isArray(skill.tags)) {
|
|
298
|
+
for (const tag of skill.tags) {
|
|
299
|
+
if (String(tag).toLowerCase().includes(q)) return true;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// 4. Kategoria
|
|
304
|
+
if (skill.categories) {
|
|
305
|
+
if (skill.categories.name && String(skill.categories.name).toLowerCase().includes(q)) return true;
|
|
306
|
+
if (skill.categories.slug && String(skill.categories.slug).toLowerCase().includes(q)) return true;
|
|
307
|
+
}
|
|
308
|
+
if (skill.category && typeof skill.category === 'string' && skill.category.toLowerCase().includes(q)) return true;
|
|
309
|
+
|
|
310
|
+
// 5. Namespace i właściciel
|
|
311
|
+
if (skill.namespace && String(skill.namespace).toLowerCase().includes(q)) return true;
|
|
312
|
+
if (skill.owner_name && String(skill.owner_name).toLowerCase().includes(q)) return true;
|
|
313
|
+
if (skill.owner_email && String(skill.owner_email).toLowerCase().includes(q)) return true;
|
|
314
|
+
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
|
|
178
318
|
async function cmdSkillList(flags, out, err) {
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
query: flags.query,
|
|
182
|
-
tag: flags.tag,
|
|
183
|
-
category: flags.category,
|
|
184
|
-
scope: flags.scope,
|
|
185
|
-
limit: flags.limit || 100,
|
|
186
|
-
});
|
|
319
|
+
const searchQuery = flags.query ? String(flags.query).trim() : '';
|
|
320
|
+
const isSearch = Boolean(searchQuery);
|
|
187
321
|
|
|
188
|
-
if (!
|
|
189
|
-
|
|
190
|
-
|
|
322
|
+
if (!flags.json) {
|
|
323
|
+
if (isSearch) {
|
|
324
|
+
out(`Wyszukuję skille w DeerSkills Hub dla frazy „${searchQuery}” (hybrydowo: wektory + tekst)...`);
|
|
325
|
+
} else {
|
|
326
|
+
out('Pobieram listę skilli z DeerSkills Hub...');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
let items = [];
|
|
331
|
+
|
|
332
|
+
if (isSearch) {
|
|
333
|
+
// 1. Serwerowe wyszukiwanie wektorowo-hybrydowe (PostgreSQL pgvector + trigram + keyword boost)
|
|
334
|
+
let queryEmbedding = null;
|
|
335
|
+
if (flags.semantic) {
|
|
336
|
+
if (!flags.json) out('Generuję wektor semantyczny zapytania...');
|
|
337
|
+
queryEmbedding = embedding.computeEmbedding(searchQuery);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const hybridRes = await skillsHub.searchSkillsHybrid({
|
|
341
|
+
queryText: searchQuery,
|
|
342
|
+
queryEmbedding,
|
|
343
|
+
threshold: queryEmbedding ? 0.1 : 0.05,
|
|
344
|
+
count: flags.limit || 50,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
if (hybridRes.ok && Array.isArray(hybridRes.data)) {
|
|
348
|
+
items = hybridRes.data;
|
|
349
|
+
|
|
350
|
+
// 2. Jeśli zapytanie tekstowe dało 0 wyników, a dostępne jest lokalne generowanie wektorów — ponów semantycznie
|
|
351
|
+
if (items.length === 0 && !flags.semantic && embedding.isEmbeddingAvailable()) {
|
|
352
|
+
const autoEmb = embedding.computeEmbedding(searchQuery);
|
|
353
|
+
if (autoEmb) {
|
|
354
|
+
const semRes = await skillsHub.searchSkillsHybrid({
|
|
355
|
+
queryText: searchQuery,
|
|
356
|
+
queryEmbedding: autoEmb,
|
|
357
|
+
threshold: 0.12,
|
|
358
|
+
count: flags.limit || 50,
|
|
359
|
+
});
|
|
360
|
+
if (semRes.ok && Array.isArray(semRes.data) && semRes.data.length > 0) {
|
|
361
|
+
items = semRes.data;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (flags.tag) {
|
|
367
|
+
items = items.filter((s) => Array.isArray(s.tags) && s.tags.includes(flags.tag));
|
|
368
|
+
}
|
|
369
|
+
if (flags.category) {
|
|
370
|
+
const cat = String(flags.category).toLowerCase();
|
|
371
|
+
items = items.filter((s) => s.category_slug === cat || (s.category_name && s.category_name.toLowerCase().includes(cat)));
|
|
372
|
+
}
|
|
373
|
+
if (flags.scope) {
|
|
374
|
+
items = items.filter((s) => s.scope === flags.scope);
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
// Fallback do pobierania pełnej listy i lokalnego przeszukiwania
|
|
378
|
+
const res = await skillsHub.listSkills({
|
|
379
|
+
tag: flags.tag,
|
|
380
|
+
category: flags.category,
|
|
381
|
+
scope: flags.scope,
|
|
382
|
+
limit: flags.limit || 200,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
if (!res.ok) {
|
|
386
|
+
err(`✗ Błąd pobierania skilli (${res.status}): ${res.data?.error || 'Brak dostępu'}`);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const allItems = Array.isArray(res.data) ? res.data : (res.data?.skills || []);
|
|
391
|
+
items = allItems.filter((s) => matchSkillQuery(s, searchQuery));
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
const res = await skillsHub.listSkills({
|
|
395
|
+
tag: flags.tag,
|
|
396
|
+
category: flags.category,
|
|
397
|
+
scope: flags.scope,
|
|
398
|
+
limit: flags.limit || 200,
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
if (!res.ok) {
|
|
402
|
+
err(`✗ Błąd pobierania skilli (${res.status}): ${res.data?.error || 'Brak dostępu'}`);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
items = Array.isArray(res.data) ? res.data : (res.data?.skills || []);
|
|
191
407
|
}
|
|
192
408
|
|
|
193
|
-
const items = Array.isArray(res.data) ? res.data : (res.data?.skills || []);
|
|
194
409
|
const agents = detectAgents({
|
|
195
410
|
dir: flags.dir,
|
|
196
411
|
agent: flags.agent,
|
|
@@ -203,6 +418,9 @@ async function cmdSkillList(flags, out, err) {
|
|
|
203
418
|
const insp = inspectSkillForAgents(agents, s, lock);
|
|
204
419
|
return {
|
|
205
420
|
...s,
|
|
421
|
+
similarity: s.similarity != null ? s.similarity : null,
|
|
422
|
+
vector_score: s.vector_score != null ? s.vector_score : null,
|
|
423
|
+
text_score: s.text_score != null ? s.text_score : null,
|
|
206
424
|
overall_status: insp.overallStatus,
|
|
207
425
|
agents: Object.fromEntries(
|
|
208
426
|
Object.entries(insp.agents).map(([id, data]) => [
|
|
@@ -234,9 +452,22 @@ async function cmdSkillList(flags, out, err) {
|
|
|
234
452
|
};
|
|
235
453
|
}
|
|
236
454
|
|
|
237
|
-
|
|
455
|
+
const titleHeader = isSearch
|
|
456
|
+
? `=== Wyniki wyszukiwania w DeerSkills Hub dla „${searchQuery}” (${items.length}) ===`
|
|
457
|
+
: `=== Dostępne Skille w DeerSkills Hub (${items.length}) ===`;
|
|
458
|
+
out(`\n${titleHeader}\n`);
|
|
459
|
+
|
|
460
|
+
if (items.length === 0 && isSearch) {
|
|
461
|
+
out(` Nie znaleziono żadnego skilla pasującego do frazy „${searchQuery}”.`);
|
|
462
|
+
out(` (Przeszukano: wektory semantyczne, nazwy/slugi, opisy [summary], tagi oraz kategorie).`);
|
|
463
|
+
out(`\n💡 Wskazówka: Uruchom \x1b[36mdeertools skill list\x1b[0m aby zobaczyć wszystkie dostępne skille.`);
|
|
464
|
+
out('─'.repeat(78) + '\n');
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
238
468
|
for (const s of items) {
|
|
239
|
-
const
|
|
469
|
+
const namespace = s.namespace || (s.full_slug?.startsWith('@') ? s.full_slug.split('/')[0].slice(1) : '');
|
|
470
|
+
const badge = s.scope === 'global' ? '★ GLOBAL' : (namespace ? `@${namespace}` : (s.scope === 'team' ? '[Team]' : '[User]'));
|
|
240
471
|
const latestVer = s.latest_version ? `v${s.latest_version}` : 'v1.0.0';
|
|
241
472
|
const insp = inspectSkillForAgents(agents, s, lock);
|
|
242
473
|
|
|
@@ -248,8 +479,12 @@ async function cmdSkillList(flags, out, err) {
|
|
|
248
479
|
}
|
|
249
480
|
|
|
250
481
|
const agentBadges = agents.map((a) => insp.agents[a.id].badge).join(' ');
|
|
482
|
+
const simBadge = (s.similarity != null && isSearch)
|
|
483
|
+
? `\x1b[36m[Trafność: ${Math.round(s.similarity * 100)}%]\x1b[0m`
|
|
484
|
+
: '';
|
|
251
485
|
|
|
252
|
-
|
|
486
|
+
const fullSlug = s.full_slug || s.slug || '';
|
|
487
|
+
out(` • ${fullSlug.padEnd(36)} ${latestVer.padEnd(10)} ${badge.padEnd(14)} ${insp.overallBadge}${simBadge ? ' ' + simBadge : ''}`);
|
|
253
488
|
out(` Agenci: ${agentBadges}`);
|
|
254
489
|
if (s.summary) out(` ${s.summary.slice(0, 95)}...`);
|
|
255
490
|
if (s.tags && s.tags.length) out(` Tagi: ${s.tags.join(', ')}`);
|
|
@@ -279,9 +514,17 @@ async function cmdSkillList(flags, out, err) {
|
|
|
279
514
|
out(` • ${(a.name + ' (' + a.id + '):').padEnd(36)} ${badgeText}`);
|
|
280
515
|
}
|
|
281
516
|
|
|
517
|
+
const anyAhead = items.some((s) => {
|
|
518
|
+
const insp = inspectSkillForAgents(agents, s, lock);
|
|
519
|
+
return insp.overallStatus === 'ahead';
|
|
520
|
+
});
|
|
521
|
+
|
|
282
522
|
if (anyOutdated || anyMissing) {
|
|
283
523
|
out(`\n💡 Aby zainstalować brakujące i zaktualizować wszystkie skille u agentów:`);
|
|
284
524
|
out(` \x1b[36mdeertools skill sync\x1b[0m (lub: \x1b[36mdeertools sync\x1b[0m)`);
|
|
525
|
+
} else if (anyAhead) {
|
|
526
|
+
out(`\n💡 Wykryto skille nowsze lokalnie niż w Hubie:`);
|
|
527
|
+
out(` \x1b[35mdeertools sync --push\x1b[0m (aby wypchnąć je do DeerSkills Hub)`);
|
|
285
528
|
} else {
|
|
286
529
|
out(`\n✓ Wszystkie skille zespołu (${items.length}) są zainstalowane i aktualne u wszystkich wykrytych agentów.`);
|
|
287
530
|
}
|
|
@@ -327,6 +570,18 @@ async function installSingleSkill(slug, requestedVersion, flags, out, err, { sil
|
|
|
327
570
|
|
|
328
571
|
let count = 0;
|
|
329
572
|
for (const agent of agents) {
|
|
573
|
+
// Bezpieczeństwo: jeśli lokalna wersja na dysku jest nowsza niż w Hubie, chroń przed nadpisaniem (wymagaj --force)
|
|
574
|
+
const existingFile = findSkillFileForAgent(agent, skill);
|
|
575
|
+
if (existingFile && !flags.force && !requestedVersion) {
|
|
576
|
+
const currentDiskVer = readSkillVersion(existingFile, readLock(flags.dir), skill);
|
|
577
|
+
if (currentDiskVer && compareSemver(currentDiskVer, versionData.version) > 0) {
|
|
578
|
+
if (!silentSummary) {
|
|
579
|
+
out(` \x1b[35m★ [${agent.id}]\x1b[0m Pominięto instalację ${skill.slug}: wersja na dysku (v${currentDiskVer}) jest nowsza niż w Hubie (v${versionData.version}). Użyj --force aby nadpisać.`);
|
|
580
|
+
}
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
330
585
|
try {
|
|
331
586
|
const dest = installSkillContent(agent, skill.slug, versionData.skill_md_content);
|
|
332
587
|
if (!silentSummary) {
|
|
@@ -372,8 +627,10 @@ async function installSingleSkill(slug, requestedVersion, flags, out, err, { sil
|
|
|
372
627
|
|
|
373
628
|
/**
|
|
374
629
|
* Instaluje lub synchronizuje wszystkie skille zespołu z DeerSkills Hub.
|
|
630
|
+
* Wspiera flagę --push (lub --auto-push): automatycznie publikuje nowsze lokalne wersje do Hubu.
|
|
375
631
|
*/
|
|
376
632
|
async function cmdSkillSync(flags, out, err) {
|
|
633
|
+
const shouldPush = Boolean(flags.push || flags['auto-push']);
|
|
377
634
|
out('Pobieram pełną listę skilli zespołu z DeerSkills Hub...');
|
|
378
635
|
const res = await skillsHub.listSkills({ limit: 100 });
|
|
379
636
|
if (!res.ok) {
|
|
@@ -399,14 +656,20 @@ async function cmdSkillSync(flags, out, err) {
|
|
|
399
656
|
return;
|
|
400
657
|
}
|
|
401
658
|
|
|
402
|
-
const
|
|
659
|
+
const cwd = flags.dir || process.cwd();
|
|
660
|
+
const lock = readLock(cwd);
|
|
661
|
+
const hubSlugs = new Set(items.flatMap((s) => [s.slug, s.full_slug, s.slug.replace(/^@[^/]+\//, '')]));
|
|
662
|
+
const localSkills = scanLocalSkills(cwd, agents, hubSlugs);
|
|
663
|
+
|
|
403
664
|
out(`Platformy docelowe: \x1b[36m${agents.map((a) => a.name || a.id).join(', ')}\x1b[0m`);
|
|
404
|
-
out(`Rozpoczynam synchronizację ${items.length} skilli zespołu
|
|
665
|
+
out(`Rozpoczynam synchronizację ${items.length} skilli zespołu...${shouldPush ? ' \x1b[35m[Tryb dwukierunkowy: --push]\x1b[0m' : ''}\n`);
|
|
405
666
|
|
|
406
667
|
let installedCount = 0;
|
|
407
668
|
let updatedCount = 0;
|
|
408
669
|
let unchangedCount = 0;
|
|
409
670
|
let failedCount = 0;
|
|
671
|
+
let pushedCount = 0;
|
|
672
|
+
const aheadSkills = [];
|
|
410
673
|
|
|
411
674
|
for (const s of items) {
|
|
412
675
|
const slug = s.slug;
|
|
@@ -415,16 +678,74 @@ async function cmdSkillSync(flags, out, err) {
|
|
|
415
678
|
const currentVer = current?.version;
|
|
416
679
|
const latestVer = s.latest_version || '1.0.0';
|
|
417
680
|
|
|
418
|
-
|
|
419
|
-
|
|
681
|
+
// Sprawdź wersję lokalną na dysku u agentów lub w .agents/skills
|
|
682
|
+
const localSkill = localSkills.get(slug) || localSkills.get(fullSlug) || localSkills.get(slug.replace(/^@[^/]+\//, ''));
|
|
683
|
+
let localDiskVer = localSkill?.version || null;
|
|
684
|
+
|
|
685
|
+
if (!localDiskVer) {
|
|
686
|
+
for (const agent of agents) {
|
|
687
|
+
const fp = findSkillFileForAgent(agent, s);
|
|
688
|
+
if (fp) {
|
|
689
|
+
const v = readSkillVersion(fp, lock, s);
|
|
690
|
+
if (v) { localDiskVer = v; break; }
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const effectiveLocalVer = localDiskVer || currentVer;
|
|
696
|
+
const cmpLocalToHub = effectiveLocalVer ? compareSemver(effectiveLocalVer, latestVer) : -1;
|
|
697
|
+
|
|
698
|
+
// 1. Ochrona bezpieczeństwa: Lokalna wersja jest nowsza niż w Hubie (AHEAD)
|
|
699
|
+
if (cmpLocalToHub > 0) {
|
|
700
|
+
const pushDir = localSkill?.dir || (localSkill?.filePath ? path.dirname(localSkill.filePath) : null) || path.join(cwd, '.agents', 'skills', slug);
|
|
701
|
+
aheadSkills.push({
|
|
702
|
+
slug: s.full_slug || s.slug,
|
|
703
|
+
localVersion: effectiveLocalVer,
|
|
704
|
+
hubVersion: latestVer,
|
|
705
|
+
dir: pushDir,
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
if (shouldPush) {
|
|
709
|
+
try {
|
|
710
|
+
out(` \x1b[35m↑ PUSH\x1b[0m ${s.full_slug.padEnd(36)} v${effectiveLocalVer} → Hub (wypychanie lokalnej nowości)...`);
|
|
711
|
+
const pushRes = await skillsHub.pushSkill(pushDir, {
|
|
712
|
+
version: effectiveLocalVer,
|
|
713
|
+
changelog: flags.changelog || `Synchronizacja dwukierunkowa: wypchnięto lokalną wersję v${effectiveLocalVer}`,
|
|
714
|
+
});
|
|
715
|
+
if (pushRes.ok) {
|
|
716
|
+
out(` \x1b[32m✓ PUSHED\x1b[0m ${s.full_slug.padEnd(34)} v${effectiveLocalVer} opublikowano w DeerSkills Hub.`);
|
|
717
|
+
pushedCount += 1;
|
|
718
|
+
lock.skills[slug] = {
|
|
719
|
+
fullSlug: s.full_slug,
|
|
720
|
+
version: effectiveLocalVer,
|
|
721
|
+
installedAt: new Date().toISOString(),
|
|
722
|
+
};
|
|
723
|
+
writeLock(lock, cwd);
|
|
724
|
+
} else {
|
|
725
|
+
out(` \x1b[31m✗ PUSH BŁĄD\x1b[0m ${s.full_slug}: ${pushRes.data?.error || 'Błąd publikacji'}`);
|
|
726
|
+
}
|
|
727
|
+
} catch (pe) {
|
|
728
|
+
out(` \x1b[31m✗ PUSH BŁĄD\x1b[0m ${s.full_slug}: ${pe.message}`);
|
|
729
|
+
}
|
|
730
|
+
} else {
|
|
731
|
+
out(` \x1b[35m★\x1b[0m ${s.full_slug.padEnd(38)} v${effectiveLocalVer.padEnd(8)} [Lokalnie nowszy niż w Hubie (Hub: v${latestVer})]`);
|
|
732
|
+
}
|
|
733
|
+
unchangedCount += 1;
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// 2. Wersje identyczne - aktualny
|
|
738
|
+
if (effectiveLocalVer && cmpLocalToHub === 0 && !flags.force) {
|
|
739
|
+
out(` \x1b[32m✓\x1b[0m ${s.full_slug.padEnd(38)} v${effectiveLocalVer.padEnd(8)} [Aktualny]`);
|
|
420
740
|
unchangedCount += 1;
|
|
421
741
|
continue;
|
|
422
742
|
}
|
|
423
743
|
|
|
744
|
+
// 3. Wersja w Hubie nowsza niż lokalna - pobieramy downstream
|
|
424
745
|
try {
|
|
425
746
|
await installSingleSkill(s.full_slug || s.slug, latestVer, flags, out, err, { silentSummary: true });
|
|
426
|
-
if (
|
|
427
|
-
out(` \x1b[33m↑\x1b[0m ${s.full_slug.padEnd(38)} v${latestVer.padEnd(8)} [Zaktualizowano z v${
|
|
747
|
+
if (effectiveLocalVer) {
|
|
748
|
+
out(` \x1b[33m↑\x1b[0m ${s.full_slug.padEnd(38)} v${latestVer.padEnd(8)} [Zaktualizowano z v${effectiveLocalVer}]`);
|
|
428
749
|
updatedCount += 1;
|
|
429
750
|
} else {
|
|
430
751
|
out(` \x1b[36m+\x1b[0m ${s.full_slug.padEnd(38)} v${latestVer.padEnd(8)} [Zainstalowano]`);
|
|
@@ -436,17 +757,89 @@ async function cmdSkillSync(flags, out, err) {
|
|
|
436
757
|
}
|
|
437
758
|
}
|
|
438
759
|
|
|
760
|
+
// 4. Sprawdź lokalne skille, których w ogóle nie ma w Hubie
|
|
761
|
+
const unpushedLocalSkills = [];
|
|
762
|
+
for (const [lSlug, lk] of localSkills.entries()) {
|
|
763
|
+
if (!hubSlugs.has(lSlug)) {
|
|
764
|
+
unpushedLocalSkills.push(lk);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (unpushedLocalSkills.length > 0) {
|
|
769
|
+
for (const ul of unpushedLocalSkills) {
|
|
770
|
+
if (shouldPush) {
|
|
771
|
+
try {
|
|
772
|
+
out(` \x1b[35m↑ PUSH\x1b[0m ${ul.slug.padEnd(36)} v${ul.version} → Hub (nowy skill lokalny)...`);
|
|
773
|
+
const pushRes = await skillsHub.pushSkill(ul.dir, {
|
|
774
|
+
version: ul.version,
|
|
775
|
+
changelog: flags.changelog || `Inicjalna publikacja skilla lokalnego v${ul.version}`,
|
|
776
|
+
});
|
|
777
|
+
if (pushRes.ok) {
|
|
778
|
+
out(` \x1b[32m✓ PUSHED\x1b[0m ${ul.slug.padEnd(34)} v${ul.version} opublikowano w DeerSkills Hub.`);
|
|
779
|
+
pushedCount += 1;
|
|
780
|
+
} else {
|
|
781
|
+
out(` \x1b[31m✗ PUSH BŁĄD\x1b[0m ${ul.slug}: ${pushRes.data?.error || 'Błąd publikacji'}`);
|
|
782
|
+
}
|
|
783
|
+
} catch (pe) {
|
|
784
|
+
out(` \x1b[31m✗ PUSH BŁĄD\x1b[0m ${ul.slug}: ${pe.message}`);
|
|
785
|
+
}
|
|
786
|
+
} else {
|
|
787
|
+
out(` \x1b[90m○\x1b[0m ${ul.slug.padEnd(38)} v${ul.version.padEnd(8)} [Tylko lokalnie (brak w Hubie)]`);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
439
792
|
out('\n' + '─'.repeat(72));
|
|
440
793
|
out(`\x1b[1m✓ Podsumowanie synchronizacji ekosystemu Deer Design:\x1b[0m`);
|
|
441
794
|
out(` • Nowo zainstalowane: \x1b[36m${installedCount}\x1b[0m`);
|
|
442
795
|
out(` • Zaktualizowane: \x1b[33m${updatedCount}\x1b[0m`);
|
|
443
796
|
out(` • Aktualne bez zmian: \x1b[32m${unchangedCount}\x1b[0m`);
|
|
797
|
+
if (pushedCount > 0) {
|
|
798
|
+
out(` • Wypchnięte do Hubu: \x1b[35m${pushedCount}\x1b[0m`);
|
|
799
|
+
}
|
|
444
800
|
if (failedCount > 0) {
|
|
445
801
|
out(` • Błędy instalacji: \x1b[31m${failedCount}\x1b[0m`);
|
|
446
802
|
}
|
|
447
803
|
out(` • Agenci docelowi: ${agents.map((a) => a.id).join(', ')}`);
|
|
448
804
|
out(` • Zapisano w rejestrze: ${LOCK_FILE}`);
|
|
449
|
-
out('─'.repeat(72)
|
|
805
|
+
out('─'.repeat(72));
|
|
806
|
+
|
|
807
|
+
// Alert i komunikat o możliwości wypchnięcia (jeśli nie użyto --push, a wykryto lokalne nowości)
|
|
808
|
+
if (!shouldPush && (aheadSkills.length > 0 || unpushedLocalSkills.length > 0)) {
|
|
809
|
+
out('\n\x1b[35m┌─────────────────────────────────────────────────────────────────────────────┐\x1b[0m');
|
|
810
|
+
out('\x1b[35m│\x1b[0m \x1b[1m💡 WYKRYTO SKILLE Z LOKALNYMI ZMIANAMI GOTOWYMI DO WYPCHNIĘCIA (AHEAD)\x1b[0m \x1b[35m│\x1b[0m');
|
|
811
|
+
out('\x1b[35m├─────────────────────────────────────────────────────────────────────────────┤\x1b[0m');
|
|
812
|
+
out('\x1b[35m│\x1b[0m Domyślny \x1b[36msync\x1b[0m pobiera nowości z Hubu i chroni Twoje lokalne pliki. \x1b[35m│\x1b[0m');
|
|
813
|
+
out('\x1b[35m│\x1b[0m Posiadasz skille nowsze niż w rejestrze firmowym DeerSkills Hub: \x1b[35m│\x1b[0m');
|
|
814
|
+
|
|
815
|
+
const allNotices = [];
|
|
816
|
+
for (const a of aheadSkills) {
|
|
817
|
+
allNotices.push(` • ${a.slug}: lokalnie v${a.localVersion} > w Hubie v${a.hubVersion}`);
|
|
818
|
+
}
|
|
819
|
+
for (const u of unpushedLocalSkills) {
|
|
820
|
+
allNotices.push(` • ${u.slug}: lokalnie v${u.version} (nowy, brak w Hubie)`);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const maxToShow = 6;
|
|
824
|
+
for (let i = 0; i < Math.min(allNotices.length, maxToShow); i++) {
|
|
825
|
+
out(`\x1b[35m│\x1b[0m \x1b[33m${allNotices[i].padEnd(73)}\x1b[0m\x1b[35m│\x1b[0m`);
|
|
826
|
+
}
|
|
827
|
+
if (allNotices.length > maxToShow) {
|
|
828
|
+
const moreText = ` ... i jeszcze ${allNotices.length - maxToShow} innych skilli z lokalnymi zmianami`;
|
|
829
|
+
out(`\x1b[35m│\x1b[0m \x1b[90m${moreText.padEnd(73)}\x1b[0m\x1b[35m│\x1b[0m`);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
out('\x1b[35m├─────────────────────────────────────────────────────────────────────────────┤\x1b[0m');
|
|
833
|
+
out('\x1b[35m│\x1b[0m \x1b[1mAby wypchnąć zmiany do Hubu dla zespołu / firmy:\x1b[0m \x1b[35m│\x1b[0m');
|
|
834
|
+
if (aheadSkills.length > 0) {
|
|
835
|
+
out(`\x1b[35m│\x1b[0m ▶ Pojedynczo: \x1b[36mdeertools skill push ${aheadSkills[0].dir}\x1b[0m`);
|
|
836
|
+
}
|
|
837
|
+
out('\x1b[35m│\x1b[0m ▶ Automatycznie (dwukierunkowo): \x1b[35m│\x1b[0m');
|
|
838
|
+
out('\x1b[35m│\x1b[0m \x1b[32mdeertools sync --push\x1b[0m (lub: \x1b[32mdeertools skill sync --push\x1b[0m) \x1b[35m│\x1b[0m');
|
|
839
|
+
out('\x1b[35m└─────────────────────────────────────────────────────────────────────────────┘\x1b[0m\n');
|
|
840
|
+
} else {
|
|
841
|
+
out('');
|
|
842
|
+
}
|
|
450
843
|
}
|
|
451
844
|
|
|
452
845
|
async function cmdSkillInstall(target, flags, out, err) {
|
|
@@ -985,4 +1378,7 @@ module.exports = {
|
|
|
985
1378
|
cmdSkillShare,
|
|
986
1379
|
checkSkillUpdates,
|
|
987
1380
|
updateSkills,
|
|
1381
|
+
scanLocalSkills,
|
|
1382
|
+
inspectSkillForAgents,
|
|
1383
|
+
matchSkillQuery,
|
|
988
1384
|
};
|