skillex 0.2.4 → 0.3.0
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/CHANGELOG.md +29 -0
- package/README.md +45 -23
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +67 -25
- package/dist/install.js +148 -35
- package/dist/markdown.d.ts +7 -0
- package/dist/markdown.js +193 -0
- package/dist/sync.js +90 -4
- package/dist/types.d.ts +16 -0
- package/dist/ui.d.ts +10 -5
- package/dist/ui.js +7 -4
- package/dist/web-ui.d.ts +31 -0
- package/dist/web-ui.js +461 -0
- package/dist-ui/assets/CatalogPage-BVGkKQWg.js +1 -0
- package/dist-ui/assets/SkillDetailPage-CbIWg9lz.js +1 -0
- package/dist-ui/assets/index-DXrW27jF.js +25 -0
- package/dist-ui/assets/index-K8hfZs7_.css +1 -0
- package/dist-ui/index.html +16 -0
- package/package.json +15 -5
package/dist/install.js
CHANGED
|
@@ -36,9 +36,6 @@ export async function initProject(options = {}) {
|
|
|
36
36
|
lockfile.sources = [toLockfileSource(source)];
|
|
37
37
|
}
|
|
38
38
|
lockfile.settings.autoSync = options.autoSync ?? lockfile.settings.autoSync;
|
|
39
|
-
if (lockfile.settings.autoSync && !lockfile.adapters.active) {
|
|
40
|
-
throw new InstallError("Auto-sync requires an active adapter. Use --adapter <id> or run in a detectable workspace.", "AUTO_SYNC_REQUIRES_ADAPTER");
|
|
41
|
-
}
|
|
42
39
|
lockfile.updatedAt = now();
|
|
43
40
|
await writeJson(statePaths.lockfilePath, lockfile);
|
|
44
41
|
// Create .gitignore for the local state directory on first init.
|
|
@@ -129,7 +126,8 @@ export async function installSkills(requestedSkillIds, options = {}) {
|
|
|
129
126
|
const autoSync = await maybeAutoSync(withAgentSkillsDir({
|
|
130
127
|
cwd,
|
|
131
128
|
scope: options.scope,
|
|
132
|
-
|
|
129
|
+
adapters: lockfile.adapters,
|
|
130
|
+
adapterOverride: options.adapter,
|
|
133
131
|
enabled: lockfile.settings.autoSync,
|
|
134
132
|
now,
|
|
135
133
|
changed: installedSkills.length > 0,
|
|
@@ -217,7 +215,8 @@ export async function updateInstalledSkills(requestedSkillIds, options = {}) {
|
|
|
217
215
|
const autoSync = await maybeAutoSync(withAgentSkillsDir({
|
|
218
216
|
cwd,
|
|
219
217
|
scope: options.scope,
|
|
220
|
-
|
|
218
|
+
adapters: lockfile.adapters,
|
|
219
|
+
adapterOverride: options.adapter,
|
|
221
220
|
enabled: lockfile.settings.autoSync,
|
|
222
221
|
now,
|
|
223
222
|
changed: updatedSkills.length > 0,
|
|
@@ -272,10 +271,13 @@ export async function removeSkills(requestedSkillIds, options = {}) {
|
|
|
272
271
|
}
|
|
273
272
|
lockfile.updatedAt = now();
|
|
274
273
|
await writeJson(statePaths.lockfilePath, lockfile);
|
|
275
|
-
const autoSync = await
|
|
274
|
+
const autoSync = await maybeSyncAfterRemove(withAgentSkillsDir({
|
|
276
275
|
cwd,
|
|
277
276
|
scope: options.scope,
|
|
278
|
-
|
|
277
|
+
adapters: lockfile.adapters,
|
|
278
|
+
adapterOverride: options.adapter,
|
|
279
|
+
syncHistory: lockfile.syncHistory,
|
|
280
|
+
legacySync: lockfile.sync,
|
|
279
281
|
enabled: lockfile.settings.autoSync,
|
|
280
282
|
now,
|
|
281
283
|
changed: removedSkills.length > 0,
|
|
@@ -312,55 +314,93 @@ export async function syncInstalledSkills(options = {}) {
|
|
|
312
314
|
}
|
|
313
315
|
const defaultSource = resolveSource(toCatalogSourceInput(options, resolvePrimarySourceOverride(options, existing)));
|
|
314
316
|
const lockfile = normalizeLockfile(existing, defaultSource, now);
|
|
315
|
-
const
|
|
316
|
-
if (
|
|
317
|
+
const adapterIds = resolveSyncAdapterIds(lockfile.adapters, options.adapter);
|
|
318
|
+
if (adapterIds.length === 0) {
|
|
317
319
|
throw new InstallError("No active adapter configured. Run: skillex init --adapter <id> or use --adapter.", "ACTIVE_ADAPTER_MISSING");
|
|
318
320
|
}
|
|
319
321
|
const skills = await loadInstalledSkillDocuments({
|
|
320
322
|
cwd,
|
|
321
323
|
lockfile,
|
|
322
324
|
});
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
325
|
+
const syncResults = [];
|
|
326
|
+
const diffParts = [];
|
|
327
|
+
for (const adapterId of adapterIds) {
|
|
328
|
+
const syncResult = await syncAdapterFiles({
|
|
329
|
+
cwd,
|
|
330
|
+
scope: statePaths.scope,
|
|
331
|
+
adapterId,
|
|
332
|
+
statePaths,
|
|
333
|
+
skills,
|
|
334
|
+
previousSkillIds: lockfile.syncHistory[adapterId]?.skillIds || lockfile.sync?.skillIds || [],
|
|
335
|
+
...(options.mode ? { mode: options.mode } : {}),
|
|
336
|
+
...(options.dryRun !== undefined ? { dryRun: options.dryRun } : {}),
|
|
337
|
+
});
|
|
338
|
+
syncResults.push(syncResult);
|
|
339
|
+
if (syncResult.diff.trim()) {
|
|
340
|
+
diffParts.push(syncResult.diff.trimEnd());
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const primarySync = syncResults[0];
|
|
344
|
+
if (!primarySync) {
|
|
345
|
+
throw new InstallError("No adapter configured for synchronization. Use --adapter <id> or work in a detectable workspace.", "ACTIVE_ADAPTER_MISSING");
|
|
346
|
+
}
|
|
333
347
|
if (options.dryRun) {
|
|
334
348
|
return {
|
|
335
349
|
statePaths,
|
|
336
350
|
sync: {
|
|
337
|
-
adapter:
|
|
338
|
-
targetPath:
|
|
351
|
+
adapter: primarySync.adapter,
|
|
352
|
+
targetPath: primarySync.targetPath,
|
|
339
353
|
},
|
|
354
|
+
syncs: syncResults.map((result) => ({
|
|
355
|
+
adapter: result.adapter,
|
|
356
|
+
targetPath: result.targetPath,
|
|
357
|
+
syncMode: result.syncMode,
|
|
358
|
+
changed: result.changed,
|
|
359
|
+
})),
|
|
340
360
|
skillCount: skills.length,
|
|
341
|
-
changed:
|
|
342
|
-
diff:
|
|
361
|
+
changed: syncResults.some((result) => result.changed),
|
|
362
|
+
diff: diffParts.length > 0 ? `${diffParts.join("\n\n")}\n` : "",
|
|
343
363
|
dryRun: true,
|
|
344
|
-
syncMode:
|
|
364
|
+
syncMode: primarySync.syncMode,
|
|
345
365
|
};
|
|
346
366
|
}
|
|
347
|
-
|
|
348
|
-
adapter:
|
|
349
|
-
targetPath:
|
|
367
|
+
const primaryMetadata = {
|
|
368
|
+
adapter: primarySync.adapter,
|
|
369
|
+
targetPath: primarySync.targetPath,
|
|
350
370
|
syncedAt: now(),
|
|
351
371
|
skillIds: skills.map((skill) => skill.id),
|
|
352
372
|
};
|
|
353
|
-
|
|
373
|
+
const nextSyncHistory = {
|
|
374
|
+
...lockfile.syncHistory,
|
|
375
|
+
[primarySync.adapter]: primaryMetadata,
|
|
376
|
+
};
|
|
377
|
+
for (const syncResult of syncResults.slice(1)) {
|
|
378
|
+
nextSyncHistory[syncResult.adapter] = {
|
|
379
|
+
adapter: syncResult.adapter,
|
|
380
|
+
targetPath: syncResult.targetPath,
|
|
381
|
+
syncedAt: now(),
|
|
382
|
+
skillIds: skills.map((skill) => skill.id),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
lockfile.sync = primaryMetadata;
|
|
386
|
+
lockfile.syncHistory = nextSyncHistory;
|
|
387
|
+
lockfile.syncMode = primarySync.syncMode;
|
|
354
388
|
lockfile.updatedAt = now();
|
|
355
389
|
await writeJson(statePaths.lockfilePath, lockfile);
|
|
356
390
|
return {
|
|
357
391
|
statePaths,
|
|
358
|
-
sync:
|
|
392
|
+
sync: primaryMetadata,
|
|
393
|
+
syncs: syncResults.map((result) => ({
|
|
394
|
+
adapter: result.adapter,
|
|
395
|
+
targetPath: result.targetPath,
|
|
396
|
+
syncMode: result.syncMode,
|
|
397
|
+
changed: result.changed,
|
|
398
|
+
})),
|
|
359
399
|
skillCount: skills.length,
|
|
360
|
-
changed:
|
|
361
|
-
diff:
|
|
400
|
+
changed: syncResults.some((result) => result.changed),
|
|
401
|
+
diff: diffParts.length > 0 ? `${diffParts.join("\n\n")}\n` : "",
|
|
362
402
|
dryRun: false,
|
|
363
|
-
syncMode:
|
|
403
|
+
syncMode: primarySync.syncMode,
|
|
364
404
|
};
|
|
365
405
|
}
|
|
366
406
|
catch (error) {
|
|
@@ -541,9 +581,10 @@ function createBaseLockfile(source, now) {
|
|
|
541
581
|
detected: [],
|
|
542
582
|
},
|
|
543
583
|
settings: {
|
|
544
|
-
autoSync:
|
|
584
|
+
autoSync: true,
|
|
545
585
|
},
|
|
546
586
|
sync: null,
|
|
587
|
+
syncHistory: {},
|
|
547
588
|
syncMode: null,
|
|
548
589
|
installed: {},
|
|
549
590
|
};
|
|
@@ -666,13 +707,35 @@ function normalizeLockfile(existing, source, now) {
|
|
|
666
707
|
detected: [...new Set(detectedAdapters.filter(Boolean))],
|
|
667
708
|
},
|
|
668
709
|
settings: {
|
|
669
|
-
autoSync:
|
|
710
|
+
autoSync: existing.settings?.autoSync ?? true,
|
|
670
711
|
},
|
|
671
712
|
sync: existing.sync || null,
|
|
713
|
+
syncHistory: normalizeSyncHistory(existing),
|
|
672
714
|
syncMode: existing.syncMode || null,
|
|
673
715
|
installed: existing.installed || {},
|
|
674
716
|
};
|
|
675
717
|
}
|
|
718
|
+
function normalizeSyncHistory(existing) {
|
|
719
|
+
const history = {};
|
|
720
|
+
const candidate = existing && "syncHistory" in existing && existing.syncHistory && typeof existing.syncHistory === "object"
|
|
721
|
+
? existing.syncHistory
|
|
722
|
+
: null;
|
|
723
|
+
if (candidate) {
|
|
724
|
+
for (const [adapterId, metadata] of Object.entries(candidate)) {
|
|
725
|
+
if (!metadata || typeof metadata !== "object") {
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
if (!("adapter" in metadata) || !("targetPath" in metadata) || !("syncedAt" in metadata)) {
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
history[adapterId] = metadata;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (existing?.sync?.adapter && !history[existing.sync.adapter]) {
|
|
735
|
+
history[existing.sync.adapter] = existing.sync;
|
|
736
|
+
}
|
|
737
|
+
return history;
|
|
738
|
+
}
|
|
676
739
|
/** Repos that are known placeholder values written by older versions and must be ignored. */
|
|
677
740
|
const PLACEHOLDER_REPOS = new Set(["owner/repo"]);
|
|
678
741
|
function getLockfileSources(existing, fallbackSource) {
|
|
@@ -835,15 +898,65 @@ async function maybeAutoSync(options) {
|
|
|
835
898
|
if (!options.enabled || !options.changed) {
|
|
836
899
|
return null;
|
|
837
900
|
}
|
|
901
|
+
if (resolveSyncAdapterIds(options.adapters, options.adapterOverride).length === 0) {
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
838
904
|
return syncInstalledSkills({
|
|
839
905
|
cwd: options.cwd,
|
|
840
906
|
scope: options.scope || DEFAULT_INSTALL_SCOPE,
|
|
841
907
|
...(options.agentSkillsDir ? { agentSkillsDir: options.agentSkillsDir } : {}),
|
|
842
|
-
...(options.
|
|
908
|
+
...(options.adapterOverride ? { adapter: options.adapterOverride } : {}),
|
|
843
909
|
...(options.mode ? { mode: options.mode } : {}),
|
|
844
910
|
now: options.now,
|
|
845
911
|
});
|
|
846
912
|
}
|
|
913
|
+
async function maybeSyncAfterRemove(options) {
|
|
914
|
+
if (!options.changed) {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
const adapters = new Set();
|
|
918
|
+
for (const adapterId of Object.keys(options.syncHistory || {})) {
|
|
919
|
+
adapters.add(adapterId);
|
|
920
|
+
}
|
|
921
|
+
if (options.legacySync?.adapter) {
|
|
922
|
+
adapters.add(options.legacySync.adapter);
|
|
923
|
+
}
|
|
924
|
+
if (options.adapterOverride) {
|
|
925
|
+
adapters.add(options.adapterOverride);
|
|
926
|
+
}
|
|
927
|
+
else if (options.enabled) {
|
|
928
|
+
for (const adapterId of resolveSyncAdapterIds(options.adapters)) {
|
|
929
|
+
adapters.add(adapterId);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
let result = null;
|
|
933
|
+
for (const adapterId of adapters) {
|
|
934
|
+
result = await syncInstalledSkills({
|
|
935
|
+
cwd: options.cwd,
|
|
936
|
+
scope: options.scope || DEFAULT_INSTALL_SCOPE,
|
|
937
|
+
...(options.agentSkillsDir ? { agentSkillsDir: options.agentSkillsDir } : {}),
|
|
938
|
+
adapter: adapterId,
|
|
939
|
+
...(options.mode ? { mode: options.mode } : {}),
|
|
940
|
+
now: options.now,
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
return result;
|
|
944
|
+
}
|
|
945
|
+
function resolveSyncAdapterIds(adapters, adapterOverride) {
|
|
946
|
+
if (adapterOverride) {
|
|
947
|
+
return [adapterOverride];
|
|
948
|
+
}
|
|
949
|
+
const adapterIds = [];
|
|
950
|
+
if (adapters.active) {
|
|
951
|
+
adapterIds.push(adapters.active);
|
|
952
|
+
}
|
|
953
|
+
for (const adapterId of adapters.detected || []) {
|
|
954
|
+
if (!adapterIds.includes(adapterId)) {
|
|
955
|
+
adapterIds.push(adapterId);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return adapterIds;
|
|
959
|
+
}
|
|
847
960
|
function toCatalogSourceInput(options, overrides = {}) {
|
|
848
961
|
const input = {};
|
|
849
962
|
if (options.owner) {
|
package/dist/markdown.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
function escapeHtml(value) {
|
|
2
|
+
return value
|
|
3
|
+
.replaceAll("&", "&")
|
|
4
|
+
.replaceAll("<", "<")
|
|
5
|
+
.replaceAll(">", ">")
|
|
6
|
+
.replaceAll('"', """)
|
|
7
|
+
.replaceAll("'", "'");
|
|
8
|
+
}
|
|
9
|
+
function renderInlineMarkdown(value) {
|
|
10
|
+
let html = escapeHtml(value);
|
|
11
|
+
html = html.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, (_match, label, url) => {
|
|
12
|
+
const safeLabel = escapeHtml(label);
|
|
13
|
+
const safeUrl = escapeHtml(url);
|
|
14
|
+
return `<a href="${safeUrl}" target="_blank" rel="noreferrer noopener">${safeLabel}</a>`;
|
|
15
|
+
});
|
|
16
|
+
html = html.replace(/`([^`]+)`/g, (_match, code) => `<code>${escapeHtml(code)}</code>`);
|
|
17
|
+
html = html.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
|
18
|
+
html = html.replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
19
|
+
return html;
|
|
20
|
+
}
|
|
21
|
+
function flushParagraph(paragraph, html) {
|
|
22
|
+
if (paragraph.length === 0) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
html.push(`<p>${paragraph.map((line) => renderInlineMarkdown(line)).join("<br>")}</p>`);
|
|
26
|
+
paragraph.length = 0;
|
|
27
|
+
}
|
|
28
|
+
function closeList(currentList, html) {
|
|
29
|
+
if (currentList.type) {
|
|
30
|
+
html.push(`</${currentList.type}>`);
|
|
31
|
+
currentList.type = null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function isTableSeparator(line) {
|
|
35
|
+
const trimmed = line.trim();
|
|
36
|
+
if (!trimmed.startsWith("|") || !trimmed.endsWith("|")) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return trimmed
|
|
40
|
+
.slice(1, -1)
|
|
41
|
+
.split("|")
|
|
42
|
+
.every((cell) => /^:?-{3,}:?$/.test(cell.trim()));
|
|
43
|
+
}
|
|
44
|
+
function splitTableRow(line) {
|
|
45
|
+
return line
|
|
46
|
+
.trim()
|
|
47
|
+
.slice(1, -1)
|
|
48
|
+
.split("|")
|
|
49
|
+
.map((cell) => renderInlineMarkdown(cell.trim()));
|
|
50
|
+
}
|
|
51
|
+
function consumeTable(lines, startIndex) {
|
|
52
|
+
const headerLine = lines[startIndex]?.trim();
|
|
53
|
+
const separatorLine = lines[startIndex + 1]?.trim();
|
|
54
|
+
if (!headerLine || !separatorLine || !headerLine.startsWith("|") || !headerLine.endsWith("|") || !isTableSeparator(separatorLine)) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const headers = splitTableRow(headerLine);
|
|
58
|
+
const rows = [];
|
|
59
|
+
let index = startIndex + 2;
|
|
60
|
+
while (index < lines.length) {
|
|
61
|
+
const candidate = lines[index]?.trim();
|
|
62
|
+
if (!candidate || !candidate.startsWith("|") || !candidate.endsWith("|")) {
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
rows.push(splitTableRow(candidate));
|
|
66
|
+
index += 1;
|
|
67
|
+
}
|
|
68
|
+
const html = [
|
|
69
|
+
"<div class=\"markdown-table-wrap\">",
|
|
70
|
+
"<table>",
|
|
71
|
+
"<thead>",
|
|
72
|
+
"<tr>",
|
|
73
|
+
...headers.map((header) => `<th>${header}</th>`),
|
|
74
|
+
"</tr>",
|
|
75
|
+
"</thead>",
|
|
76
|
+
"<tbody>",
|
|
77
|
+
...rows.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`),
|
|
78
|
+
"</tbody>",
|
|
79
|
+
"</table>",
|
|
80
|
+
"</div>",
|
|
81
|
+
].join("");
|
|
82
|
+
return {
|
|
83
|
+
html,
|
|
84
|
+
nextIndex: index - 1,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Renders a safe HTML representation for common Markdown constructs used by skills.
|
|
89
|
+
*
|
|
90
|
+
* @param markdown - Raw markdown source.
|
|
91
|
+
* @returns Sanitized HTML string.
|
|
92
|
+
*/
|
|
93
|
+
export function renderMarkdownToHtml(markdown) {
|
|
94
|
+
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
95
|
+
const html = [];
|
|
96
|
+
const paragraph = [];
|
|
97
|
+
const currentList = { type: null };
|
|
98
|
+
let inCodeFence = false;
|
|
99
|
+
let codeFenceLanguage = "";
|
|
100
|
+
const codeFenceLines = [];
|
|
101
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
102
|
+
const line = lines[index] ?? "";
|
|
103
|
+
const trimmed = line.trim();
|
|
104
|
+
if (trimmed.startsWith("```")) {
|
|
105
|
+
flushParagraph(paragraph, html);
|
|
106
|
+
closeList(currentList, html);
|
|
107
|
+
if (inCodeFence) {
|
|
108
|
+
const languageClass = codeFenceLanguage ? ` class="language-${escapeHtml(codeFenceLanguage)}"` : "";
|
|
109
|
+
html.push(`<pre><code${languageClass}>${escapeHtml(codeFenceLines.join("\n"))}</code></pre>`);
|
|
110
|
+
codeFenceLines.length = 0;
|
|
111
|
+
codeFenceLanguage = "";
|
|
112
|
+
inCodeFence = false;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
inCodeFence = true;
|
|
116
|
+
codeFenceLanguage = trimmed.slice(3).trim();
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (inCodeFence) {
|
|
121
|
+
codeFenceLines.push(line);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const table = consumeTable(lines, index);
|
|
125
|
+
if (table) {
|
|
126
|
+
flushParagraph(paragraph, html);
|
|
127
|
+
closeList(currentList, html);
|
|
128
|
+
html.push(table.html);
|
|
129
|
+
index = table.nextIndex;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (!trimmed) {
|
|
133
|
+
flushParagraph(paragraph, html);
|
|
134
|
+
closeList(currentList, html);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
|
|
138
|
+
if (headingMatch) {
|
|
139
|
+
flushParagraph(paragraph, html);
|
|
140
|
+
closeList(currentList, html);
|
|
141
|
+
const level = headingMatch[1]?.length ?? 1;
|
|
142
|
+
html.push(`<h${level}>${renderInlineMarkdown(headingMatch[2] ?? "")}</h${level}>`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const unorderedMatch = trimmed.match(/^[-*+]\s+(.+)$/);
|
|
146
|
+
if (unorderedMatch) {
|
|
147
|
+
flushParagraph(paragraph, html);
|
|
148
|
+
if (currentList.type !== "ul") {
|
|
149
|
+
closeList(currentList, html);
|
|
150
|
+
currentList.type = "ul";
|
|
151
|
+
html.push("<ul>");
|
|
152
|
+
}
|
|
153
|
+
html.push(`<li>${renderInlineMarkdown(unorderedMatch[1] ?? "")}</li>`);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const orderedMatch = trimmed.match(/^\d+\.\s+(.+)$/);
|
|
157
|
+
if (orderedMatch) {
|
|
158
|
+
flushParagraph(paragraph, html);
|
|
159
|
+
if (currentList.type !== "ol") {
|
|
160
|
+
closeList(currentList, html);
|
|
161
|
+
currentList.type = "ol";
|
|
162
|
+
html.push("<ol>");
|
|
163
|
+
}
|
|
164
|
+
html.push(`<li>${renderInlineMarkdown(orderedMatch[1] ?? "")}</li>`);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const quoteMatch = trimmed.match(/^>\s?(.*)$/);
|
|
168
|
+
if (quoteMatch) {
|
|
169
|
+
flushParagraph(paragraph, html);
|
|
170
|
+
closeList(currentList, html);
|
|
171
|
+
const quoteLines = [renderInlineMarkdown(quoteMatch[1] ?? "")];
|
|
172
|
+
while (index + 1 < lines.length) {
|
|
173
|
+
const next = lines[index + 1]?.trim() ?? "";
|
|
174
|
+
const nextQuote = next.match(/^>\s?(.*)$/);
|
|
175
|
+
if (!nextQuote) {
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
quoteLines.push(renderInlineMarkdown(nextQuote[1] ?? ""));
|
|
179
|
+
index += 1;
|
|
180
|
+
}
|
|
181
|
+
html.push(`<blockquote><p>${quoteLines.join("<br>")}</p></blockquote>`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
paragraph.push(trimmed);
|
|
185
|
+
}
|
|
186
|
+
if (inCodeFence) {
|
|
187
|
+
const languageClass = codeFenceLanguage ? ` class="language-${escapeHtml(codeFenceLanguage)}"` : "";
|
|
188
|
+
html.push(`<pre><code${languageClass}>${escapeHtml(codeFenceLines.join("\n"))}</code></pre>`);
|
|
189
|
+
}
|
|
190
|
+
flushParagraph(paragraph, html);
|
|
191
|
+
closeList(currentList, html);
|
|
192
|
+
return html.join("\n");
|
|
193
|
+
}
|
package/dist/sync.js
CHANGED
|
@@ -51,7 +51,16 @@ export async function syncAdapterFiles(options) {
|
|
|
51
51
|
try {
|
|
52
52
|
const prepared = await prepareSyncAdapterFiles(options);
|
|
53
53
|
if (!options.dryRun) {
|
|
54
|
-
if (prepared.
|
|
54
|
+
if (prepared.removeTarget) {
|
|
55
|
+
if (prepared.generatedSourcePath) {
|
|
56
|
+
await removePath(prepared.generatedSourcePath);
|
|
57
|
+
}
|
|
58
|
+
await removePath(prepared.absoluteTargetPath);
|
|
59
|
+
for (const cleanupPath of prepared.cleanupPaths) {
|
|
60
|
+
await removePath(cleanupPath);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (prepared.directoryEntries) {
|
|
55
64
|
await ensureDir(prepared.absoluteTargetPath);
|
|
56
65
|
const createLink = options.linkFactory || createSymlink;
|
|
57
66
|
let finalMode = prepared.syncMode;
|
|
@@ -144,8 +153,28 @@ export async function prepareSyncAdapterFiles(options) {
|
|
|
144
153
|
const autoInjectBlock = buildAutoInjectBlock(options.skills);
|
|
145
154
|
if (adapter.syncMode === "managed-block") {
|
|
146
155
|
const existing = (await readText(absoluteTargetPath, "")) || "";
|
|
147
|
-
const nextManaged =
|
|
148
|
-
|
|
156
|
+
const nextManaged = options.skills.length === 0
|
|
157
|
+
? upsertManagedBlock(existing, null)
|
|
158
|
+
: upsertManagedBlock(existing, wrapManagedBlock(MANAGED_START, MANAGED_END, body));
|
|
159
|
+
const nextContent = upsertAutoInjectBlock(nextManaged, options.skills.length === 0 ? null : autoInjectBlock);
|
|
160
|
+
if (nextContent === "") {
|
|
161
|
+
return {
|
|
162
|
+
adapter: adapter.id,
|
|
163
|
+
absoluteTargetPath,
|
|
164
|
+
targetPath,
|
|
165
|
+
cleanupPaths,
|
|
166
|
+
removeTarget: true,
|
|
167
|
+
changed: Boolean(normalizeComparableText(existing)) || cleanupPaths.length > 0,
|
|
168
|
+
currentContent: existing,
|
|
169
|
+
nextContent: "",
|
|
170
|
+
diff: createManagedBlockRemovalDiff({
|
|
171
|
+
targetPath,
|
|
172
|
+
currentContent: existing,
|
|
173
|
+
cleanupPaths: cleanupPaths.map((cleanupPath) => toDisplayPath(options.cwd, cleanupPath, options.statePaths.scope)),
|
|
174
|
+
}),
|
|
175
|
+
syncMode: "copy",
|
|
176
|
+
};
|
|
177
|
+
}
|
|
149
178
|
return {
|
|
150
179
|
adapter: adapter.id,
|
|
151
180
|
absoluteTargetPath,
|
|
@@ -160,6 +189,35 @@ export async function prepareSyncAdapterFiles(options) {
|
|
|
160
189
|
}
|
|
161
190
|
const nextContent = buildManagedFileContent(adapter.id, body, autoInjectBlock);
|
|
162
191
|
const requestedMode = options.mode || "symlink";
|
|
192
|
+
const generatedSourcePath = path.join(options.statePaths.generatedDirPath, adapter.id, path.basename(adapter.syncTarget));
|
|
193
|
+
if (options.skills.length === 0) {
|
|
194
|
+
const currentDescriptor = await describeTarget(absoluteTargetPath);
|
|
195
|
+
const currentVisibleContent = (await readText(absoluteTargetPath, "")) || "";
|
|
196
|
+
const generatedExists = await pathExists(generatedSourcePath);
|
|
197
|
+
return {
|
|
198
|
+
adapter: adapter.id,
|
|
199
|
+
absoluteTargetPath,
|
|
200
|
+
targetPath,
|
|
201
|
+
cleanupPaths,
|
|
202
|
+
removeTarget: true,
|
|
203
|
+
changed: Boolean(normalizeComparableText(currentDescriptor)) ||
|
|
204
|
+
Boolean(normalizeComparableText(currentVisibleContent)) ||
|
|
205
|
+
generatedExists ||
|
|
206
|
+
cleanupPaths.length > 0,
|
|
207
|
+
currentContent: currentDescriptor,
|
|
208
|
+
nextContent: "",
|
|
209
|
+
diff: createManagedFileRemovalDiff({
|
|
210
|
+
targetPath,
|
|
211
|
+
generatedPath: toDisplayPath(options.cwd, generatedSourcePath, options.statePaths.scope),
|
|
212
|
+
generatedExists,
|
|
213
|
+
currentDescriptor,
|
|
214
|
+
currentContent: currentVisibleContent,
|
|
215
|
+
cleanupPaths: cleanupPaths.map((cleanupPath) => toDisplayPath(options.cwd, cleanupPath, options.statePaths.scope)),
|
|
216
|
+
}),
|
|
217
|
+
syncMode: requestedMode,
|
|
218
|
+
generatedSourcePath,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
163
221
|
if (requestedMode === "copy") {
|
|
164
222
|
const existing = (await readText(absoluteTargetPath, "")) || "";
|
|
165
223
|
return {
|
|
@@ -174,7 +232,6 @@ export async function prepareSyncAdapterFiles(options) {
|
|
|
174
232
|
syncMode: "copy",
|
|
175
233
|
};
|
|
176
234
|
}
|
|
177
|
-
const generatedSourcePath = path.join(options.statePaths.generatedDirPath, adapter.id, path.basename(adapter.syncTarget));
|
|
178
235
|
const currentDescriptor = await describeTarget(absoluteTargetPath);
|
|
179
236
|
const currentVisibleContent = (await readText(absoluteTargetPath, "")) || "";
|
|
180
237
|
const nextDescriptor = `symlink -> ${toPosix(path.relative(path.dirname(absoluteTargetPath), generatedSourcePath))}\n`;
|
|
@@ -437,6 +494,35 @@ function createManagedFileDiff(context) {
|
|
|
437
494
|
}
|
|
438
495
|
return `${parts.join("\n")}\n`;
|
|
439
496
|
}
|
|
497
|
+
function createManagedBlockRemovalDiff(context) {
|
|
498
|
+
const parts = [];
|
|
499
|
+
if (normalizeComparableText(context.currentContent) !== "") {
|
|
500
|
+
parts.push(createTextDiff(context.currentContent, "", context.targetPath).trimEnd());
|
|
501
|
+
}
|
|
502
|
+
for (const cleanupPath of context.cleanupPaths) {
|
|
503
|
+
parts.push(`- remove ${cleanupPath}`);
|
|
504
|
+
}
|
|
505
|
+
if (parts.length === 0) {
|
|
506
|
+
return `Sem alteracoes em ${context.targetPath}.\n`;
|
|
507
|
+
}
|
|
508
|
+
return `${parts.join("\n")}\n`;
|
|
509
|
+
}
|
|
510
|
+
function createManagedFileRemovalDiff(context) {
|
|
511
|
+
const parts = [];
|
|
512
|
+
if (normalizeComparableText(context.currentDescriptor) !== "") {
|
|
513
|
+
parts.push(createTextDiff(context.currentDescriptor, "", context.targetPath).trimEnd());
|
|
514
|
+
}
|
|
515
|
+
if (normalizeComparableText(context.currentContent) !== "") {
|
|
516
|
+
parts.push(createTextDiff(context.currentContent, "", context.targetPath).trimEnd());
|
|
517
|
+
}
|
|
518
|
+
if (context.generatedExists) {
|
|
519
|
+
parts.push(`- remove ${context.generatedPath}`);
|
|
520
|
+
}
|
|
521
|
+
for (const cleanupPath of context.cleanupPaths) {
|
|
522
|
+
parts.push(`- remove ${cleanupPath}`);
|
|
523
|
+
}
|
|
524
|
+
return `${parts.join("\n")}\n`;
|
|
525
|
+
}
|
|
440
526
|
function createTextDiff(currentContent, nextContent, targetPath) {
|
|
441
527
|
if (normalizeComparableText(currentContent) === normalizeComparableText(nextContent)) {
|
|
442
528
|
return `Sem alteracoes em ${targetPath}.\n`;
|
package/dist/types.d.ts
CHANGED
|
@@ -172,6 +172,10 @@ export interface SyncMetadata {
|
|
|
172
172
|
syncedAt: string;
|
|
173
173
|
skillIds?: string[] | undefined;
|
|
174
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Per-adapter synchronization metadata persisted in the lockfile.
|
|
177
|
+
*/
|
|
178
|
+
export type SyncHistory = Record<string, SyncMetadata>;
|
|
175
179
|
/**
|
|
176
180
|
* Full workspace lockfile structure.
|
|
177
181
|
*/
|
|
@@ -183,6 +187,7 @@ export interface LockfileState {
|
|
|
183
187
|
adapters: LockfileAdapters;
|
|
184
188
|
settings: LockfileSettings;
|
|
185
189
|
sync: SyncMetadata | null;
|
|
190
|
+
syncHistory: SyncHistory;
|
|
186
191
|
syncMode: SyncWriteMode | null;
|
|
187
192
|
installed: Record<string, InstalledSkillMetadata>;
|
|
188
193
|
}
|
|
@@ -263,6 +268,7 @@ export interface PreparedSyncResult {
|
|
|
263
268
|
absoluteTargetPath: string;
|
|
264
269
|
targetPath: string;
|
|
265
270
|
cleanupPaths: string[];
|
|
271
|
+
removeTarget?: boolean | undefined;
|
|
266
272
|
changed: boolean;
|
|
267
273
|
currentContent: string;
|
|
268
274
|
nextContent: string;
|
|
@@ -291,6 +297,15 @@ export interface SyncPreview {
|
|
|
291
297
|
before: string;
|
|
292
298
|
after: string;
|
|
293
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* Per-adapter sync summary returned to callers.
|
|
302
|
+
*/
|
|
303
|
+
export interface SyncExecutionSummary {
|
|
304
|
+
adapter: string;
|
|
305
|
+
targetPath: string;
|
|
306
|
+
syncMode: SyncWriteMode;
|
|
307
|
+
changed: boolean;
|
|
308
|
+
}
|
|
294
309
|
/**
|
|
295
310
|
* Public sync result returned after writing or dry-run preparation.
|
|
296
311
|
*/
|
|
@@ -325,6 +340,7 @@ export interface SyncCommandResult {
|
|
|
325
340
|
adapter: string;
|
|
326
341
|
targetPath: string;
|
|
327
342
|
} | SyncMetadata;
|
|
343
|
+
syncs: SyncExecutionSummary[];
|
|
328
344
|
skillCount: number;
|
|
329
345
|
changed: boolean;
|
|
330
346
|
diff: string;
|