@skild/core 0.10.19 → 0.11.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/dist/index.d.ts +5 -1
- package/dist/index.js +119 -4
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -67,6 +67,10 @@ interface InstallRecord {
|
|
|
67
67
|
sourceType: SourceType;
|
|
68
68
|
installedAt: string;
|
|
69
69
|
updatedAt?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Derived at runtime from platform/scope/name.
|
|
72
|
+
* Stored install records omit this field to avoid machine-specific paths.
|
|
73
|
+
*/
|
|
70
74
|
installDir: string;
|
|
71
75
|
contentHash: string;
|
|
72
76
|
hasSkillMd: boolean;
|
|
@@ -96,7 +100,7 @@ interface LockEntry {
|
|
|
96
100
|
registryUrl?: string;
|
|
97
101
|
installedAt: string;
|
|
98
102
|
updatedAt?: string;
|
|
99
|
-
installDir
|
|
103
|
+
installDir?: string;
|
|
100
104
|
contentHash: string;
|
|
101
105
|
}
|
|
102
106
|
interface Lockfile {
|
package/dist/index.js
CHANGED
|
@@ -95,6 +95,35 @@ function writeJsonFilePrivate(filePath, value) {
|
|
|
95
95
|
} catch {
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
function toPersistedInstallRecord(record) {
|
|
99
|
+
const { installDir: _installDir, installedDependencies, ...rest } = record;
|
|
100
|
+
const sanitizedDeps = installedDependencies?.map((dep) => {
|
|
101
|
+
const { installDir: _depInstallDir, ...depRest } = dep;
|
|
102
|
+
return depRest;
|
|
103
|
+
});
|
|
104
|
+
if (!sanitizedDeps) return rest;
|
|
105
|
+
return { ...rest, installedDependencies: sanitizedDeps };
|
|
106
|
+
}
|
|
107
|
+
function hydrateInstallRecord(installDir, record) {
|
|
108
|
+
const hydrated = { ...record, installDir };
|
|
109
|
+
if (hydrated.installedDependencies?.length) {
|
|
110
|
+
hydrated.installedDependencies = hydrated.installedDependencies.map((dep) => {
|
|
111
|
+
if (dep.sourceType === "inline") return dep;
|
|
112
|
+
return { ...dep, installDir: getSkillInstallDir(hydrated.platform, hydrated.scope, dep.name) };
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return hydrated;
|
|
116
|
+
}
|
|
117
|
+
function toPersistedLockEntry(entry) {
|
|
118
|
+
const { installDir: _installDir, ...rest } = entry;
|
|
119
|
+
return rest;
|
|
120
|
+
}
|
|
121
|
+
function sanitizeLockfile(lockfile) {
|
|
122
|
+
const entries = Object.fromEntries(
|
|
123
|
+
Object.entries(lockfile.entries).map(([name, entry]) => [name, toPersistedLockEntry(entry)])
|
|
124
|
+
);
|
|
125
|
+
return { ...lockfile, entries };
|
|
126
|
+
}
|
|
98
127
|
function loadOrCreateGlobalConfig() {
|
|
99
128
|
const filePath = getGlobalConfigPath();
|
|
100
129
|
const existing = readJsonFile(filePath);
|
|
@@ -121,7 +150,7 @@ function loadLockfile(lockPath) {
|
|
|
121
150
|
return readJsonFile(lockPath);
|
|
122
151
|
}
|
|
123
152
|
function saveLockfile(lockPath, lockfile) {
|
|
124
|
-
writeJsonFile(lockPath, lockfile);
|
|
153
|
+
writeJsonFile(lockPath, sanitizeLockfile(lockfile));
|
|
125
154
|
}
|
|
126
155
|
function getLockPath(scope) {
|
|
127
156
|
return scope === "project" ? getProjectLockPath() : getGlobalLockPath();
|
|
@@ -136,7 +165,7 @@ function loadOrCreateLockfile(scope) {
|
|
|
136
165
|
}
|
|
137
166
|
function upsertLockEntry(scope, entry) {
|
|
138
167
|
const lockfile = loadOrCreateLockfile(scope);
|
|
139
|
-
lockfile.entries[entry.name] = entry;
|
|
168
|
+
lockfile.entries[entry.name] = toPersistedLockEntry(entry);
|
|
140
169
|
lockfile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
141
170
|
saveLockfile(getLockPath(scope), lockfile);
|
|
142
171
|
}
|
|
@@ -148,12 +177,14 @@ function removeLockEntry(scope, name) {
|
|
|
148
177
|
}
|
|
149
178
|
function readInstallRecord(installDir) {
|
|
150
179
|
const filePath = getSkillInstallRecordPath(installDir);
|
|
151
|
-
|
|
180
|
+
const record = readJsonFile(filePath);
|
|
181
|
+
if (!record) return null;
|
|
182
|
+
return hydrateInstallRecord(installDir, record);
|
|
152
183
|
}
|
|
153
184
|
function writeInstallRecord(installDir, record) {
|
|
154
185
|
ensureDir(getSkillMetadataDir(installDir));
|
|
155
186
|
const filePath = getSkillInstallRecordPath(installDir);
|
|
156
|
-
writeJsonFile(filePath, record);
|
|
187
|
+
writeJsonFile(filePath, toPersistedInstallRecord(record));
|
|
157
188
|
}
|
|
158
189
|
|
|
159
190
|
// src/skill.ts
|
|
@@ -300,6 +331,7 @@ import fs6 from "fs";
|
|
|
300
331
|
import os2 from "os";
|
|
301
332
|
import path7 from "path";
|
|
302
333
|
import degit from "degit";
|
|
334
|
+
import simpleGit from "simple-git";
|
|
303
335
|
|
|
304
336
|
// src/source.ts
|
|
305
337
|
import path6 from "path";
|
|
@@ -460,6 +492,81 @@ function resetTargetDir(targetPath) {
|
|
|
460
492
|
removeDir(targetPath);
|
|
461
493
|
fs6.mkdirSync(targetPath, { recursive: true });
|
|
462
494
|
}
|
|
495
|
+
function splitSourceRef(source) {
|
|
496
|
+
const [base, ref] = source.split("#", 2);
|
|
497
|
+
return { base, ref: ref?.trim() || void 0 };
|
|
498
|
+
}
|
|
499
|
+
function parseGitCloneSpec(source) {
|
|
500
|
+
const trimmed = source.trim();
|
|
501
|
+
if (!trimmed) return null;
|
|
502
|
+
if (resolveLocalPath(trimmed)) return null;
|
|
503
|
+
const { base, ref } = splitSourceRef(trimmed);
|
|
504
|
+
const githubTreeWithPathMatch = base.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/);
|
|
505
|
+
if (githubTreeWithPathMatch) {
|
|
506
|
+
const [, owner, repo, branch, subpath] = githubTreeWithPathMatch;
|
|
507
|
+
return { url: `https://github.com/${owner}/${repo}.git`, ref: branch, subpath };
|
|
508
|
+
}
|
|
509
|
+
const githubTreeMatch = base.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)$/);
|
|
510
|
+
if (githubTreeMatch) {
|
|
511
|
+
const [, owner, repo, branch] = githubTreeMatch;
|
|
512
|
+
return { url: `https://github.com/${owner}/${repo}.git`, ref: branch };
|
|
513
|
+
}
|
|
514
|
+
const githubRepoMatch = base.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
515
|
+
if (githubRepoMatch) {
|
|
516
|
+
const [, owner, repoRaw] = githubRepoMatch;
|
|
517
|
+
const repo = repoRaw.replace(/\.git$/, "");
|
|
518
|
+
return { url: `https://github.com/${owner}/${repo}.git`, ref };
|
|
519
|
+
}
|
|
520
|
+
const gitlabTreeWithPathMatch = base.match(/gitlab\.com\/([^/]+)\/([^/]+)\/-\/tree\/([^/]+)\/(.+)/);
|
|
521
|
+
if (gitlabTreeWithPathMatch) {
|
|
522
|
+
const [, owner, repo, branch, subpath] = gitlabTreeWithPathMatch;
|
|
523
|
+
return { url: `https://gitlab.com/${owner}/${repo}.git`, ref: branch, subpath };
|
|
524
|
+
}
|
|
525
|
+
const gitlabTreeMatch = base.match(/gitlab\.com\/([^/]+)\/([^/]+)\/-\/tree\/([^/]+)$/);
|
|
526
|
+
if (gitlabTreeMatch) {
|
|
527
|
+
const [, owner, repo, branch] = gitlabTreeMatch;
|
|
528
|
+
return { url: `https://gitlab.com/${owner}/${repo}.git`, ref: branch };
|
|
529
|
+
}
|
|
530
|
+
const gitlabRepoMatch = base.match(/gitlab\.com\/([^/]+)\/([^/]+)/);
|
|
531
|
+
if (gitlabRepoMatch) {
|
|
532
|
+
const [, owner, repoRaw] = gitlabRepoMatch;
|
|
533
|
+
const repo = repoRaw.replace(/\.git$/, "");
|
|
534
|
+
return { url: `https://gitlab.com/${owner}/${repo}.git`, ref };
|
|
535
|
+
}
|
|
536
|
+
const shorthandMatch = base.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
|
|
537
|
+
if (shorthandMatch && !base.includes(":") && !base.startsWith(".") && !base.startsWith("/")) {
|
|
538
|
+
const [, owner, repo, subpath] = shorthandMatch;
|
|
539
|
+
return { url: `https://github.com/${owner}/${repo}.git`, ref, subpath };
|
|
540
|
+
}
|
|
541
|
+
if (/^(https?:|git@|ssh:)/i.test(base)) {
|
|
542
|
+
return { url: base, ref };
|
|
543
|
+
}
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
async function cloneWithGit(spec, targetPath) {
|
|
547
|
+
const cloneOptions = spec.ref ? ["--depth", "1", "--branch", spec.ref] : ["--depth", "1"];
|
|
548
|
+
const git = simpleGit();
|
|
549
|
+
if (!spec.subpath) {
|
|
550
|
+
resetTargetDir(targetPath);
|
|
551
|
+
await git.clone(spec.url, targetPath, cloneOptions);
|
|
552
|
+
return spec.url;
|
|
553
|
+
}
|
|
554
|
+
const tempRoot = createTempDir(path7.join(os2.tmpdir(), "skild-git"), extractSkillName(spec.url));
|
|
555
|
+
try {
|
|
556
|
+
await git.clone(spec.url, tempRoot, cloneOptions);
|
|
557
|
+
const resolvedSubpath = path7.resolve(tempRoot, spec.subpath);
|
|
558
|
+
const resolvedRoot = path7.resolve(tempRoot);
|
|
559
|
+
if (!resolvedSubpath.startsWith(`${resolvedRoot}${path7.sep}`)) {
|
|
560
|
+
throw new SkildError("INVALID_SOURCE", `Subpath escapes repository root: ${spec.subpath}`, { subpath: spec.subpath });
|
|
561
|
+
}
|
|
562
|
+
ensureInstallableDir(resolvedSubpath);
|
|
563
|
+
resetTargetDir(targetPath);
|
|
564
|
+
copyDir(resolvedSubpath, targetPath);
|
|
565
|
+
return spec.url;
|
|
566
|
+
} finally {
|
|
567
|
+
removeDir(tempRoot);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
463
570
|
async function cloneRemote(degitSrc, targetPath, mode) {
|
|
464
571
|
const emitter = degit(degitSrc, { force: true, verbose: false, mode });
|
|
465
572
|
await emitter.clone(targetPath);
|
|
@@ -512,6 +619,14 @@ async function materializeSourceToDir(input) {
|
|
|
512
619
|
copyDir(localPath, targetDir);
|
|
513
620
|
return { sourceType: "local", materializedFrom: localPath };
|
|
514
621
|
}
|
|
622
|
+
const gitSpec = parseGitCloneSpec(input.source);
|
|
623
|
+
if (gitSpec) {
|
|
624
|
+
try {
|
|
625
|
+
const materializedFrom2 = await cloneWithGit(gitSpec, targetDir);
|
|
626
|
+
return { sourceType, materializedFrom: materializedFrom2 };
|
|
627
|
+
} catch {
|
|
628
|
+
}
|
|
629
|
+
}
|
|
515
630
|
const degitPath = toDegitPath(input.source);
|
|
516
631
|
const materializedFrom = await cloneRemoteWithFallback(degitPath, targetDir);
|
|
517
632
|
return { sourceType, materializedFrom };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skild/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Skild core library (headless) for installing, validating, and managing Agent Skills locally.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"degit": "^2.8.4",
|
|
21
21
|
"js-yaml": "^4.1.0",
|
|
22
22
|
"semver": "^7.6.3",
|
|
23
|
+
"simple-git": "^3.27.0",
|
|
23
24
|
"tar": "^7.4.3"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|