skillex 0.3.1 → 0.4.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 +262 -1
- package/README.md +57 -10
- package/dist/auto-sync.d.ts +66 -0
- package/dist/auto-sync.js +91 -0
- package/dist/catalog.js +5 -29
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +247 -141
- package/dist/confirm.js +3 -1
- package/dist/direct-github.d.ts +60 -0
- package/dist/direct-github.js +177 -0
- package/dist/doctor.d.ts +31 -0
- package/dist/doctor.js +172 -0
- package/dist/downloader.d.ts +42 -0
- package/dist/downloader.js +41 -0
- package/dist/fs.d.ts +21 -1
- package/dist/fs.js +30 -3
- package/dist/http.d.ts +28 -7
- package/dist/http.js +143 -42
- package/dist/install.d.ts +23 -9
- package/dist/install.js +75 -348
- package/dist/lockfile.d.ts +46 -0
- package/dist/lockfile.js +169 -0
- package/dist/output.d.ts +11 -0
- package/dist/output.js +49 -0
- package/dist/recommended.d.ts +13 -0
- package/dist/recommended.js +21 -0
- package/dist/runner.js +9 -9
- package/dist/skill.d.ts +2 -0
- package/dist/skill.js +3 -0
- package/dist/sync.js +12 -9
- package/dist/types.d.ts +39 -0
- package/dist/types.js +28 -0
- package/dist/ui.js +1 -1
- package/dist/user-config.d.ts +5 -0
- package/dist/user-config.js +22 -1
- package/dist/web-ui.js +5 -0
- package/dist-ui/assets/CatalogPage-CbtMTkxd.js +1 -0
- package/dist-ui/assets/CatalogPage-W5MqylAz.css +1 -0
- package/dist-ui/assets/DoctorPage-oUZyX91t.js +1 -0
- package/dist-ui/assets/Skeleton-B_xm5L3P.js +1 -0
- package/dist-ui/assets/Skeleton-_Ooiw1nN.css +1 -0
- package/dist-ui/assets/SkillDetailPage-5JHQLq3q.js +1 -0
- package/dist-ui/assets/SkillDetailPage-CBAaWpcc.css +1 -0
- package/dist-ui/assets/{index-UBECch6X.css → index-CWm7zQTg.css} +1 -1
- package/dist-ui/assets/index-I0b-syhc.js +26 -0
- package/dist-ui/assets/recommended-D_i10hwH.js +1 -0
- package/dist-ui/index.html +2 -2
- package/package.json +2 -2
- package/dist-ui/assets/CatalogPage-B_qic36n.js +0 -1
- package/dist-ui/assets/SkillDetailPage-BJ3onKk4.js +0 -1
- package/dist-ui/assets/index-DN-z--cR.js +0 -25
package/dist/install.js
CHANGED
|
@@ -1,13 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install / update / remove orchestration for catalog and direct-GitHub skills.
|
|
3
|
+
*
|
|
4
|
+
* Historically this module owned every install-related concern. Lockfile
|
|
5
|
+
* shape, direct-GitHub install, auto-sync, and the shared file downloader
|
|
6
|
+
* have been extracted into focused modules. This file now contains only
|
|
7
|
+
* orchestration and re-exports the moved symbols so existing imports keep
|
|
8
|
+
* working until callers migrate to the canonical paths.
|
|
9
|
+
*
|
|
10
|
+
* Re-export shim → canonical module mapping:
|
|
11
|
+
* - lockfile shape and source-list helpers → ./lockfile.js
|
|
12
|
+
* - direct GitHub parsing / fetch / download → ./direct-github.js
|
|
13
|
+
* - auto-sync orchestration → ./auto-sync.js
|
|
14
|
+
* - shared per-file download helper → ./downloader.js
|
|
15
|
+
*/
|
|
1
16
|
import * as path from "node:path";
|
|
2
|
-
import { DEFAULT_INSTALL_SCOPE, DEFAULT_REF, DEFAULT_REPO, getScopedStatePaths, } from "./config.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { fetchOptionalJson, fetchOptionalText, fetchText } from "./http.js";
|
|
6
|
-
import { buildRawGitHubUrl, loadCatalog, resolveSource } from "./catalog.js";
|
|
17
|
+
import { DEFAULT_AGENT_SKILLS_DIR, DEFAULT_INSTALL_SCOPE, DEFAULT_REF, DEFAULT_REPO, getScopedStatePaths, } from "./config.js";
|
|
18
|
+
import { ensureDir, isPathInside, pathExists, readJson, removePath, writeJson, writeText } from "./fs.js";
|
|
19
|
+
import { loadCatalog, resolveSource } from "./catalog.js";
|
|
7
20
|
import { resolveAdapterState } from "./adapters.js";
|
|
8
21
|
import { loadInstalledSkillDocuments, syncAdapterFiles } from "./sync.js";
|
|
9
|
-
import {
|
|
22
|
+
import { downloadSkillFiles, writeDownloadedManifest, } from "./downloader.js";
|
|
23
|
+
import { createBaseLockfile, dedupeSources, getLockfileSources, normalizeLockfile, normalizeSyncHistory, parseCatalogSource, PLACEHOLDER_REPOS, toLockfileSource, } from "./lockfile.js";
|
|
24
|
+
import { confirmDirectInstall, downloadDirectGitHubSkill, fetchDirectGitHubSkill, normalizeDirectManifest, parseDirectGitHubRef, parseGitHubSource, } from "./direct-github.js";
|
|
25
|
+
import { maybeAutoSync, maybeSyncAfterRemove, resolveSyncAdapterIds, } from "./auto-sync.js";
|
|
10
26
|
import { CliError, InstallError } from "./types.js";
|
|
27
|
+
// Re-export everything moved to focused modules so existing imports keep working.
|
|
28
|
+
export { createBaseLockfile, dedupeSources, getLockfileSources, normalizeLockfile, normalizeSyncHistory, parseCatalogSource, PLACEHOLDER_REPOS, toLockfileSource, } from "./lockfile.js";
|
|
29
|
+
export { confirmDirectInstall, downloadDirectGitHubSkill, fetchDirectGitHubSkill, normalizeDirectManifest, parseDirectGitHubRef, parseGitHubSource, } from "./direct-github.js";
|
|
30
|
+
export { maybeAutoSync, maybeSyncAfterRemove, resolveSyncAdapterIds, } from "./auto-sync.js";
|
|
31
|
+
export { downloadSkillFiles, writeDownloadedManifest, } from "./downloader.js";
|
|
11
32
|
/**
|
|
12
33
|
* Initializes the local Skillex workspace state.
|
|
13
34
|
*
|
|
@@ -105,7 +126,12 @@ export async function installSkills(requestedSkillIds, options = {}) {
|
|
|
105
126
|
}
|
|
106
127
|
}
|
|
107
128
|
else if (!directRefs.length) {
|
|
108
|
-
throw new InstallError(
|
|
129
|
+
throw new InstallError([
|
|
130
|
+
"No install target provided. Pick one of:",
|
|
131
|
+
" • skillex install <skill-id> [<skill-id> ...] from a configured catalog source",
|
|
132
|
+
" • skillex install --all install every skill in the catalog",
|
|
133
|
+
" • skillex install owner/repo[@ref] --trust install directly from a GitHub repo",
|
|
134
|
+
].join("\n"), "INSTALL_REQUIRES_SKILL");
|
|
109
135
|
}
|
|
110
136
|
for (const directRef of directRefs) {
|
|
111
137
|
if (!options.trust) {
|
|
@@ -132,7 +158,7 @@ export async function installSkills(requestedSkillIds, options = {}) {
|
|
|
132
158
|
now,
|
|
133
159
|
changed: installedSkills.length > 0,
|
|
134
160
|
mode: options.mode,
|
|
135
|
-
}, options.agentSkillsDir));
|
|
161
|
+
}, options.agentSkillsDir), syncInstalledSkills);
|
|
136
162
|
return {
|
|
137
163
|
installedCount: installedSkills.length,
|
|
138
164
|
installedSkills,
|
|
@@ -221,7 +247,7 @@ export async function updateInstalledSkills(requestedSkillIds, options = {}) {
|
|
|
221
247
|
now,
|
|
222
248
|
changed: updatedSkills.length > 0,
|
|
223
249
|
mode: options.mode,
|
|
224
|
-
}, options.agentSkillsDir));
|
|
250
|
+
}, options.agentSkillsDir), syncInstalledSkills);
|
|
225
251
|
return {
|
|
226
252
|
statePaths,
|
|
227
253
|
updatedSkills,
|
|
@@ -265,13 +291,17 @@ export async function removeSkills(requestedSkillIds, options = {}) {
|
|
|
265
291
|
missingSkills.push(skillId);
|
|
266
292
|
continue;
|
|
267
293
|
}
|
|
268
|
-
|
|
294
|
+
const resolvedSkillPath = resolveInstalledSkillPath(cwd, metadata.path);
|
|
295
|
+
if (!isPathInside(resolvedSkillPath, statePaths.skillsDirPath)) {
|
|
296
|
+
throw new InstallError(`Refusing to remove "${skillId}": lockfile path "${metadata.path}" resolves outside the managed skills store (${statePaths.skillsDirPath}).`, "INSTALL_PATH_UNSAFE");
|
|
297
|
+
}
|
|
298
|
+
await removePath(resolvedSkillPath);
|
|
269
299
|
delete lockfile.installed[skillId];
|
|
270
300
|
removedSkills.push(skillId);
|
|
271
301
|
}
|
|
272
302
|
lockfile.updatedAt = now();
|
|
273
303
|
await writeJson(statePaths.lockfilePath, lockfile);
|
|
274
|
-
const
|
|
304
|
+
const autoSyncs = await maybeSyncAfterRemove(withAgentSkillsDir({
|
|
275
305
|
cwd,
|
|
276
306
|
scope: options.scope,
|
|
277
307
|
adapters: lockfile.adapters,
|
|
@@ -282,12 +312,13 @@ export async function removeSkills(requestedSkillIds, options = {}) {
|
|
|
282
312
|
now,
|
|
283
313
|
changed: removedSkills.length > 0,
|
|
284
314
|
mode: options.mode,
|
|
285
|
-
}, options.agentSkillsDir));
|
|
315
|
+
}, options.agentSkillsDir), syncInstalledSkills);
|
|
286
316
|
return {
|
|
287
317
|
statePaths,
|
|
288
318
|
removedSkills,
|
|
289
319
|
missingSkills,
|
|
290
|
-
autoSync,
|
|
320
|
+
autoSync: autoSyncs?.[0] ?? null,
|
|
321
|
+
autoSyncs: autoSyncs ?? [],
|
|
291
322
|
};
|
|
292
323
|
}
|
|
293
324
|
catch (error) {
|
|
@@ -550,244 +581,24 @@ export async function listProjectSources(options = {}) {
|
|
|
550
581
|
const fallbackSource = resolveSource(toCatalogSourceInput(options, resolvePrimarySourceOverride(options, existing)));
|
|
551
582
|
return getLockfileSources(existing, fallbackSource);
|
|
552
583
|
}
|
|
553
|
-
/**
|
|
554
|
-
* Parses a direct GitHub install reference in `owner/repo[@ref]` format.
|
|
555
|
-
*
|
|
556
|
-
* @param input - User-supplied install argument.
|
|
557
|
-
* @returns Parsed direct GitHub reference or `null` when the value is not a direct ref.
|
|
558
|
-
*/
|
|
559
|
-
export function parseDirectGitHubRef(input) {
|
|
560
|
-
if (!input || input.startsWith("http://") || input.startsWith("https://")) {
|
|
561
|
-
return null;
|
|
562
|
-
}
|
|
563
|
-
const match = input.trim().match(/^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:@(.+))?$/);
|
|
564
|
-
if (!match) {
|
|
565
|
-
return null;
|
|
566
|
-
}
|
|
567
|
-
return {
|
|
568
|
-
owner: match[1],
|
|
569
|
-
repo: match[2],
|
|
570
|
-
ref: match[3] || "main",
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
function createBaseLockfile(source, now) {
|
|
574
|
-
return {
|
|
575
|
-
formatVersion: 1,
|
|
576
|
-
createdAt: now(),
|
|
577
|
-
updatedAt: now(),
|
|
578
|
-
sources: [toLockfileSource(source)],
|
|
579
|
-
adapters: {
|
|
580
|
-
active: null,
|
|
581
|
-
detected: [],
|
|
582
|
-
},
|
|
583
|
-
settings: {
|
|
584
|
-
autoSync: true,
|
|
585
|
-
},
|
|
586
|
-
sync: null,
|
|
587
|
-
syncHistory: {},
|
|
588
|
-
syncMode: null,
|
|
589
|
-
installed: {},
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
584
|
async function downloadSkill(skill, catalog, skillsDirPath) {
|
|
593
585
|
const skillTargetDir = path.join(skillsDirPath, skill.id);
|
|
594
|
-
await
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
}
|
|
603
|
-
await writeDownloadedManifest(skillTargetDir, {
|
|
586
|
+
await downloadSkillFiles({
|
|
587
|
+
repo: catalog.repo,
|
|
588
|
+
ref: catalog.ref,
|
|
589
|
+
skillRelPath: skill.path,
|
|
590
|
+
files: skill.files,
|
|
591
|
+
targetDir: skillTargetDir,
|
|
592
|
+
});
|
|
593
|
+
const downloaded = {
|
|
604
594
|
...skill,
|
|
605
595
|
source: {
|
|
606
596
|
repo: catalog.repo,
|
|
607
597
|
ref: catalog.ref,
|
|
608
598
|
path: skill.path,
|
|
609
599
|
},
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
async function downloadDirectGitHubSkill(skill, skillsDirPath) {
|
|
613
|
-
const skillTargetDir = path.join(skillsDirPath, skill.manifest.id);
|
|
614
|
-
await removePath(skillTargetDir);
|
|
615
|
-
await ensureDir(skillTargetDir);
|
|
616
|
-
for (const relativePath of skill.manifest.files) {
|
|
617
|
-
const remotePath = skill.manifest.path ? path.posix.join(skill.manifest.path, relativePath) : relativePath;
|
|
618
|
-
const rawUrl = buildRawGitHubUrl(skill.repo, skill.ref, remotePath);
|
|
619
|
-
const content = await fetchText(rawUrl, { headers: { Accept: "text/plain" } });
|
|
620
|
-
await writeText(path.join(skillTargetDir, relativePath), content);
|
|
621
|
-
}
|
|
622
|
-
await writeDownloadedManifest(skillTargetDir, {
|
|
623
|
-
...skill.manifest,
|
|
624
|
-
source: {
|
|
625
|
-
repo: skill.repo,
|
|
626
|
-
ref: skill.ref,
|
|
627
|
-
path: skill.manifest.path,
|
|
628
|
-
},
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
async function writeDownloadedManifest(skillTargetDir, manifest) {
|
|
632
|
-
await writeJson(path.join(skillTargetDir, "skill.json"), manifest);
|
|
633
|
-
}
|
|
634
|
-
async function fetchDirectGitHubSkill(reference) {
|
|
635
|
-
const repoId = `${reference.owner}/${reference.repo}`;
|
|
636
|
-
const manifestUrl = buildRawGitHubUrl(repoId, reference.ref, "skill.json");
|
|
637
|
-
const manifest = await fetchOptionalJson(manifestUrl, {
|
|
638
|
-
headers: { Accept: "application/json" },
|
|
639
|
-
});
|
|
640
|
-
if (manifest) {
|
|
641
|
-
return {
|
|
642
|
-
repo: repoId,
|
|
643
|
-
ref: reference.ref,
|
|
644
|
-
source: `github:${repoId}@${reference.ref}`,
|
|
645
|
-
manifest: normalizeDirectManifest(manifest, reference),
|
|
646
|
-
};
|
|
647
|
-
}
|
|
648
|
-
const skillMarkdown = await fetchOptionalText(buildRawGitHubUrl(repoId, reference.ref, "SKILL.md"), {
|
|
649
|
-
headers: { Accept: "text/plain" },
|
|
650
|
-
});
|
|
651
|
-
if (!skillMarkdown) {
|
|
652
|
-
throw new InstallError(`No skill.json or SKILL.md found at ${repoId}@${reference.ref}.`, "DIRECT_SKILL_NOT_FOUND");
|
|
653
|
-
}
|
|
654
|
-
const frontmatter = parseSkillFrontmatter(skillMarkdown);
|
|
655
|
-
return {
|
|
656
|
-
repo: repoId,
|
|
657
|
-
ref: reference.ref,
|
|
658
|
-
source: `github:${repoId}@${reference.ref}`,
|
|
659
|
-
manifest: {
|
|
660
|
-
id: normalizeRepoSkillId(reference.repo),
|
|
661
|
-
name: frontmatter.name || toTitleCase(reference.repo),
|
|
662
|
-
version: "0.1.0",
|
|
663
|
-
description: frontmatter.description || `Skill instalada diretamente de ${repoId}.`,
|
|
664
|
-
author: reference.owner,
|
|
665
|
-
tags: [],
|
|
666
|
-
compatibility: [],
|
|
667
|
-
entry: "SKILL.md",
|
|
668
|
-
path: "",
|
|
669
|
-
files: ["SKILL.md"],
|
|
670
|
-
},
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
function normalizeDirectManifest(manifest, reference) {
|
|
674
|
-
return {
|
|
675
|
-
id: manifest.id || normalizeRepoSkillId(reference.repo),
|
|
676
|
-
name: manifest.name || toTitleCase(reference.repo),
|
|
677
|
-
version: manifest.version || "0.1.0",
|
|
678
|
-
description: manifest.description || `Skill instalada diretamente de ${reference.owner}/${reference.repo}.`,
|
|
679
|
-
author: manifest.author || reference.owner,
|
|
680
|
-
tags: Array.isArray(manifest.tags) ? manifest.tags : [],
|
|
681
|
-
compatibility: Array.isArray(manifest.compatibility) ? manifest.compatibility : [],
|
|
682
|
-
entry: manifest.entry || "SKILL.md",
|
|
683
|
-
path: manifest.path || "",
|
|
684
|
-
files: Array.isArray(manifest.files) && manifest.files.length > 0 ? manifest.files : [manifest.entry || "SKILL.md"],
|
|
685
|
-
...(manifest.scripts ? { scripts: manifest.scripts } : {}),
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
function normalizeLockfile(existing, source, now) {
|
|
689
|
-
if (!existing) {
|
|
690
|
-
return createBaseLockfile(source, now);
|
|
691
|
-
}
|
|
692
|
-
const detectedAdapters = Array.isArray(existing.adapters)
|
|
693
|
-
? existing.adapters
|
|
694
|
-
: Array.isArray(existing.adapters?.detected)
|
|
695
|
-
? existing.adapters.detected
|
|
696
|
-
: [];
|
|
697
|
-
const activeAdapter = Array.isArray(existing.adapters)
|
|
698
|
-
? existing.adapters[0] || null
|
|
699
|
-
: existing.adapters?.active || detectedAdapters[0] || null;
|
|
700
|
-
return {
|
|
701
|
-
formatVersion: Number(existing.formatVersion || 1),
|
|
702
|
-
createdAt: existing.createdAt || now(),
|
|
703
|
-
updatedAt: existing.updatedAt || now(),
|
|
704
|
-
sources: getLockfileSources(existing, source),
|
|
705
|
-
adapters: {
|
|
706
|
-
active: activeAdapter,
|
|
707
|
-
detected: [...new Set(detectedAdapters.filter(Boolean))],
|
|
708
|
-
},
|
|
709
|
-
settings: {
|
|
710
|
-
autoSync: existing.settings?.autoSync ?? true,
|
|
711
|
-
},
|
|
712
|
-
sync: existing.sync || null,
|
|
713
|
-
syncHistory: normalizeSyncHistory(existing),
|
|
714
|
-
syncMode: existing.syncMode || null,
|
|
715
|
-
installed: existing.installed || {},
|
|
716
|
-
};
|
|
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
|
-
}
|
|
739
|
-
/** Repos that are known placeholder values written by older versions and must be ignored. */
|
|
740
|
-
const PLACEHOLDER_REPOS = new Set(["owner/repo"]);
|
|
741
|
-
function getLockfileSources(existing, fallbackSource) {
|
|
742
|
-
const legacyCatalog = getLegacyCatalog(existing);
|
|
743
|
-
const configuredSources = Array.isArray(existing?.sources)
|
|
744
|
-
? existing.sources
|
|
745
|
-
.filter((entry) => Boolean(entry?.repo))
|
|
746
|
-
.filter((entry) => !PLACEHOLDER_REPOS.has(entry.repo))
|
|
747
|
-
.map((entry) => ({
|
|
748
|
-
repo: entry.repo,
|
|
749
|
-
ref: entry.ref || DEFAULT_REF,
|
|
750
|
-
...(entry.label ? { label: entry.label } : {}),
|
|
751
|
-
}))
|
|
752
|
-
: [];
|
|
753
|
-
if (configuredSources.length > 0) {
|
|
754
|
-
return dedupeSources(configuredSources);
|
|
755
|
-
}
|
|
756
|
-
if (legacyCatalog?.repo && !PLACEHOLDER_REPOS.has(legacyCatalog.repo)) {
|
|
757
|
-
return dedupeSources([
|
|
758
|
-
{
|
|
759
|
-
repo: legacyCatalog.repo,
|
|
760
|
-
ref: legacyCatalog.ref || DEFAULT_REF,
|
|
761
|
-
},
|
|
762
|
-
]);
|
|
763
|
-
}
|
|
764
|
-
return [toLockfileSource(fallbackSource)];
|
|
765
|
-
}
|
|
766
|
-
function getLegacyCatalog(existing) {
|
|
767
|
-
if (!existing || !("catalog" in existing)) {
|
|
768
|
-
return null;
|
|
769
|
-
}
|
|
770
|
-
const legacyState = existing;
|
|
771
|
-
return legacyState.catalog || null;
|
|
772
|
-
}
|
|
773
|
-
function dedupeSources(sources) {
|
|
774
|
-
const unique = new Map();
|
|
775
|
-
for (const source of sources) {
|
|
776
|
-
const key = `${source.repo}@${source.ref}`;
|
|
777
|
-
if (!unique.has(key)) {
|
|
778
|
-
unique.set(key, source);
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
return [...unique.values()];
|
|
782
|
-
}
|
|
783
|
-
function toLockfileSource(source, label) {
|
|
784
|
-
return {
|
|
785
|
-
repo: source.repo,
|
|
786
|
-
ref: source.ref,
|
|
787
|
-
...((label || source.repo === DEFAULT_REPO) && (label || source.repo === DEFAULT_REPO)
|
|
788
|
-
? { label: label || "official" }
|
|
789
|
-
: {}),
|
|
790
600
|
};
|
|
601
|
+
await writeDownloadedManifest(skillTargetDir, downloaded);
|
|
791
602
|
}
|
|
792
603
|
function resolvePrimarySourceOverride(options, existing) {
|
|
793
604
|
const sources = getLockfileSources(existing, resolveSource(toCatalogSourceInput(options)));
|
|
@@ -849,16 +660,6 @@ async function resolveInstalledCatalogSelection(skillId, sourceRef, options, loc
|
|
|
849
660
|
}
|
|
850
661
|
return null;
|
|
851
662
|
}
|
|
852
|
-
function parseCatalogSource(source) {
|
|
853
|
-
const match = source.match(/^catalog:([^@]+\/[^@]+)@(.+)$/);
|
|
854
|
-
if (!match) {
|
|
855
|
-
return null;
|
|
856
|
-
}
|
|
857
|
-
return {
|
|
858
|
-
repo: match[1],
|
|
859
|
-
ref: match[2],
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
663
|
function buildInstalledMetadata(skill, context) {
|
|
863
664
|
return {
|
|
864
665
|
name: skill.name,
|
|
@@ -894,69 +695,6 @@ function getNow(options) {
|
|
|
894
695
|
function toPosix(value) {
|
|
895
696
|
return value.split(path.sep).join("/");
|
|
896
697
|
}
|
|
897
|
-
async function maybeAutoSync(options) {
|
|
898
|
-
if (!options.enabled || !options.changed) {
|
|
899
|
-
return null;
|
|
900
|
-
}
|
|
901
|
-
if (resolveSyncAdapterIds(options.adapters, options.adapterOverride).length === 0) {
|
|
902
|
-
return null;
|
|
903
|
-
}
|
|
904
|
-
return syncInstalledSkills({
|
|
905
|
-
cwd: options.cwd,
|
|
906
|
-
scope: options.scope || DEFAULT_INSTALL_SCOPE,
|
|
907
|
-
...(options.agentSkillsDir ? { agentSkillsDir: options.agentSkillsDir } : {}),
|
|
908
|
-
...(options.adapterOverride ? { adapter: options.adapterOverride } : {}),
|
|
909
|
-
...(options.mode ? { mode: options.mode } : {}),
|
|
910
|
-
now: options.now,
|
|
911
|
-
});
|
|
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
|
-
}
|
|
960
698
|
function toCatalogSourceInput(options, overrides = {}) {
|
|
961
699
|
const input = {};
|
|
962
700
|
if (options.owner) {
|
|
@@ -997,36 +735,6 @@ function resolveStatePathsForOptions(cwd, options) {
|
|
|
997
735
|
function resolveInstalledSkillPath(cwd, skillPath) {
|
|
998
736
|
return path.isAbsolute(skillPath) ? skillPath : path.resolve(cwd, skillPath);
|
|
999
737
|
}
|
|
1000
|
-
async function confirmDirectInstall(skillRef, options) {
|
|
1001
|
-
const warning = `Warning: ${skillRef} will be installed directly from GitHub and has not been verified by the active catalog.`;
|
|
1002
|
-
(options.warn || console.error)(warning);
|
|
1003
|
-
const confirm = options.confirm || (() => confirmAction("Continuar com a instalacao direta?"));
|
|
1004
|
-
const accepted = await confirm();
|
|
1005
|
-
if (!accepted) {
|
|
1006
|
-
throw new InstallError("Instalacao direta cancelada pelo usuario.", "INSTALL_CANCELLED");
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
function parseGitHubSource(source) {
|
|
1010
|
-
if (!source.startsWith("github:")) {
|
|
1011
|
-
return null;
|
|
1012
|
-
}
|
|
1013
|
-
const withoutPrefix = source.slice("github:".length);
|
|
1014
|
-
const separatorIndex = withoutPrefix.lastIndexOf("@");
|
|
1015
|
-
if (separatorIndex <= 0) {
|
|
1016
|
-
return null;
|
|
1017
|
-
}
|
|
1018
|
-
return parseDirectGitHubRef(`${withoutPrefix.slice(0, separatorIndex)}@${withoutPrefix.slice(separatorIndex + 1)}`);
|
|
1019
|
-
}
|
|
1020
|
-
function normalizeRepoSkillId(repo) {
|
|
1021
|
-
return repo.trim().toLowerCase();
|
|
1022
|
-
}
|
|
1023
|
-
function toTitleCase(skillId) {
|
|
1024
|
-
return skillId
|
|
1025
|
-
.split("-")
|
|
1026
|
-
.filter(Boolean)
|
|
1027
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
1028
|
-
.join(" ");
|
|
1029
|
-
}
|
|
1030
738
|
function toInstallError(error, fallbackMessage) {
|
|
1031
739
|
if (error instanceof InstallError) {
|
|
1032
740
|
return error;
|
|
@@ -1034,6 +742,25 @@ function toInstallError(error, fallbackMessage) {
|
|
|
1034
742
|
if (error instanceof CliError) {
|
|
1035
743
|
return new InstallError(error.message, error.code);
|
|
1036
744
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
745
|
+
// Preserve the underlying error code when present (HttpError, NodeJS.ErrnoException, etc.)
|
|
746
|
+
// so callers can react programmatically to specific failure modes such as
|
|
747
|
+
// HTTP_RATE_LIMIT, HTTP_TIMEOUT, EACCES, ENOENT, etc.
|
|
748
|
+
const underlyingCode = error && typeof error === "object" && "code" in error
|
|
749
|
+
? String(error.code)
|
|
750
|
+
: null;
|
|
751
|
+
const baseMessage = error instanceof Error ? error.message : String(error);
|
|
752
|
+
const annotatedMessage = underlyingCode
|
|
753
|
+
? `${fallbackMessage}: ${baseMessage} (${underlyingCode})`
|
|
754
|
+
: `${fallbackMessage}: ${baseMessage}`;
|
|
755
|
+
return new InstallError(annotatedMessage, underlyingCode || "INSTALL_ERROR");
|
|
756
|
+
}
|
|
757
|
+
// Silence unused-import warnings while keeping the symbols available as named re-exports.
|
|
758
|
+
void DEFAULT_AGENT_SKILLS_DIR;
|
|
759
|
+
void DEFAULT_REF;
|
|
760
|
+
void DEFAULT_REPO;
|
|
761
|
+
void dedupeSources;
|
|
762
|
+
void normalizeSyncHistory;
|
|
763
|
+
void normalizeDirectManifest;
|
|
764
|
+
void createBaseLockfile;
|
|
765
|
+
void PLACEHOLDER_REPOS;
|
|
766
|
+
void resolveSyncAdapterIds;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lockfile shape, normalization, source-list management, and migration helpers.
|
|
3
|
+
*
|
|
4
|
+
* Keeps `install.ts` focused on install/update/remove orchestration. All
|
|
5
|
+
* callers should import from here directly; `install.ts` re-exports these
|
|
6
|
+
* symbols for backward compatibility with existing test imports.
|
|
7
|
+
*/
|
|
8
|
+
import type { CatalogSource, LockfileSource, LockfileState, NowFn, SyncHistory } from "./types.js";
|
|
9
|
+
/** Repos that are known placeholder values written by older versions and must be ignored. */
|
|
10
|
+
export declare const PLACEHOLDER_REPOS: Set<string>;
|
|
11
|
+
/**
|
|
12
|
+
* Builds an empty lockfile seeded with a single source and `autoSync` on by default.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createBaseLockfile(source: CatalogSource, now: NowFn): LockfileState;
|
|
15
|
+
/**
|
|
16
|
+
* Normalizes a possibly-legacy lockfile shape into the current `LockfileState`.
|
|
17
|
+
* Handles arrays-of-strings adapters from very old versions, missing
|
|
18
|
+
* `syncHistory`, and the deprecated single `sync` field.
|
|
19
|
+
*/
|
|
20
|
+
export declare function normalizeLockfile(existing: LockfileState | null, source: CatalogSource, now: NowFn): LockfileState;
|
|
21
|
+
/**
|
|
22
|
+
* Coerces the historic and current shapes of `syncHistory` into the
|
|
23
|
+
* normalized form, including a fallback that rebuilds the history from a
|
|
24
|
+
* legacy single-`sync` field.
|
|
25
|
+
*/
|
|
26
|
+
export declare function normalizeSyncHistory(existing: LockfileState | null): SyncHistory;
|
|
27
|
+
/**
|
|
28
|
+
* Resolves the configured source list, dropping placeholders and falling back
|
|
29
|
+
* to legacy single-catalog metadata when no `sources` array exists.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getLockfileSources(existing: LockfileState | null, fallbackSource: CatalogSource): LockfileSource[];
|
|
32
|
+
/**
|
|
33
|
+
* Deduplicates a source list keyed by `${repo}@${ref}`, preserving the
|
|
34
|
+
* first occurrence (which carries any label).
|
|
35
|
+
*/
|
|
36
|
+
export declare function dedupeSources(sources: LockfileSource[]): LockfileSource[];
|
|
37
|
+
/**
|
|
38
|
+
* Converts a `CatalogSource` to a `LockfileSource`, attaching a label only
|
|
39
|
+
* when one is explicitly provided or when the source is the default
|
|
40
|
+
* first-party repo (which gets the `official` label automatically).
|
|
41
|
+
*/
|
|
42
|
+
export declare function toLockfileSource(source: CatalogSource, label?: string): LockfileSource;
|
|
43
|
+
/**
|
|
44
|
+
* Parses a `catalog:owner/repo@ref` source string into a `LockfileSource`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function parseCatalogSource(source: string): LockfileSource | null;
|