@lumerahq/cli 0.31.0-dev.1 → 0.31.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.js
CHANGED
|
@@ -240,25 +240,25 @@ async function main() {
|
|
|
240
240
|
switch (command) {
|
|
241
241
|
// Resource commands
|
|
242
242
|
case "plan":
|
|
243
|
-
await import("./resources-
|
|
243
|
+
await import("./resources-FHL4IUQD.js").then((m) => m.plan(args.slice(1)));
|
|
244
244
|
break;
|
|
245
245
|
case "apply":
|
|
246
|
-
await import("./resources-
|
|
246
|
+
await import("./resources-FHL4IUQD.js").then((m) => m.apply(args.slice(1)));
|
|
247
247
|
break;
|
|
248
248
|
case "pull":
|
|
249
|
-
await import("./resources-
|
|
249
|
+
await import("./resources-FHL4IUQD.js").then((m) => m.pull(args.slice(1)));
|
|
250
250
|
break;
|
|
251
251
|
case "destroy":
|
|
252
|
-
await import("./resources-
|
|
252
|
+
await import("./resources-FHL4IUQD.js").then((m) => m.destroy(args.slice(1)));
|
|
253
253
|
break;
|
|
254
254
|
case "list":
|
|
255
|
-
await import("./resources-
|
|
255
|
+
await import("./resources-FHL4IUQD.js").then((m) => m.list(args.slice(1)));
|
|
256
256
|
break;
|
|
257
257
|
case "show":
|
|
258
|
-
await import("./resources-
|
|
258
|
+
await import("./resources-FHL4IUQD.js").then((m) => m.show(args.slice(1)));
|
|
259
259
|
break;
|
|
260
260
|
case "diff":
|
|
261
|
-
await import("./resources-
|
|
261
|
+
await import("./resources-FHL4IUQD.js").then((m) => m.diff(args.slice(1)));
|
|
262
262
|
break;
|
|
263
263
|
// Development
|
|
264
264
|
case "dev":
|
|
@@ -2661,6 +2661,9 @@ var SKILL_RESOURCE_CONTENT_TYPES = {
|
|
|
2661
2661
|
function skillResourceContentType(path) {
|
|
2662
2662
|
return SKILL_RESOURCE_CONTENT_TYPES[path.split(".").pop()?.toLowerCase() ?? ""] ?? "application/octet-stream";
|
|
2663
2663
|
}
|
|
2664
|
+
function foldSkillResourcePathPart(value) {
|
|
2665
|
+
return value.toUpperCase();
|
|
2666
|
+
}
|
|
2664
2667
|
function skillResourcePathError(value) {
|
|
2665
2668
|
if (!value || Buffer.byteLength(value) > SKILL_RESOURCE_MAX_PATH_BYTES) {
|
|
2666
2669
|
return `path must be at most ${SKILL_RESOURCE_MAX_PATH_BYTES} bytes`;
|
|
@@ -2675,7 +2678,7 @@ function skillResourcePathError(value) {
|
|
|
2675
2678
|
if (Buffer.byteLength(part) > SKILL_RESOURCE_MAX_PATH_PART_BYTES) {
|
|
2676
2679
|
return `path segments must be at most ${SKILL_RESOURCE_MAX_PATH_PART_BYTES} bytes`;
|
|
2677
2680
|
}
|
|
2678
|
-
if (part
|
|
2681
|
+
if (foldSkillResourcePathPart(part) === "SKILL.MD") return "SKILL.md is reserved";
|
|
2679
2682
|
}
|
|
2680
2683
|
return null;
|
|
2681
2684
|
}
|
|
@@ -2685,7 +2688,7 @@ function skillResourcePathsCollide(left, right) {
|
|
|
2685
2688
|
const limit = Math.min(leftParts.length, rightParts.length);
|
|
2686
2689
|
for (let i = 0; i < limit; i++) {
|
|
2687
2690
|
if (leftParts[i] === rightParts[i]) continue;
|
|
2688
|
-
return leftParts[i]
|
|
2691
|
+
return foldSkillResourcePathPart(leftParts[i] ?? "") === foldSkillResourcePathPart(rightParts[i] ?? "");
|
|
2689
2692
|
}
|
|
2690
2693
|
return true;
|
|
2691
2694
|
}
|
|
@@ -2844,6 +2847,13 @@ ${skill.summary}
|
|
|
2844
2847
|
${skill.instructions}
|
|
2845
2848
|
`;
|
|
2846
2849
|
}
|
|
2850
|
+
function skillMetadataChanges(local, remote) {
|
|
2851
|
+
const changes = [];
|
|
2852
|
+
if (local.name !== remote.name) changes.push("name");
|
|
2853
|
+
if (local.summary.trim() !== (remote.summary || "").trim()) changes.push("summary");
|
|
2854
|
+
if (local.instructions.trim() !== (remote.instructions || "").trim()) changes.push("instructions");
|
|
2855
|
+
return changes;
|
|
2856
|
+
}
|
|
2847
2857
|
function scopedProjectSkillSlug(appName, localSlug) {
|
|
2848
2858
|
return appName ? `${appName}:${localSlug}` : localSlug;
|
|
2849
2859
|
}
|
|
@@ -2945,10 +2955,7 @@ async function planSkills(api, localSkills, projectId, appName) {
|
|
|
2945
2955
|
});
|
|
2946
2956
|
continue;
|
|
2947
2957
|
}
|
|
2948
|
-
const changed =
|
|
2949
|
-
if (remote.name !== skill.name) changed.push("name");
|
|
2950
|
-
if ((remote.summary || "").trim() !== skill.summary.trim()) changed.push("summary");
|
|
2951
|
-
if ((remote.instructions || "").trim() !== skill.instructions.trim()) changed.push("instructions");
|
|
2958
|
+
const changed = skillMetadataChanges(skill, remote);
|
|
2952
2959
|
const resourceChanges = compareSkillResources(skill.resources, remote.resources ?? []);
|
|
2953
2960
|
if (!skillResourceChangesEmpty(resourceChanges)) {
|
|
2954
2961
|
changed.push(`resources (${skillResourceChangesSummary(resourceChanges)})`);
|
|
@@ -3066,6 +3073,15 @@ function validateRemoteSkillResources(resources) {
|
|
|
3066
3073
|
}
|
|
3067
3074
|
if (totalBytes > SKILL_RESOURCES_MAX_TOTAL_BYTES) throw new Error("remote skill resources exceed 100 MiB");
|
|
3068
3075
|
}
|
|
3076
|
+
function writeVerifiedSkillResource(resource, bytes, skillDir, source) {
|
|
3077
|
+
if (bytes.byteLength !== resource.size) throw new Error(`${resource.path}: ${source} size does not match`);
|
|
3078
|
+
if (createHash("sha256").update(bytes).digest("hex") !== resource.sha256) {
|
|
3079
|
+
throw new Error(`${resource.path}: ${source} checksum does not match`);
|
|
3080
|
+
}
|
|
3081
|
+
const destination = join2(skillDir, ...resource.path.split("/"));
|
|
3082
|
+
mkdirSync(dirname2(destination), { recursive: true });
|
|
3083
|
+
writeFileSync(destination, bytes);
|
|
3084
|
+
}
|
|
3069
3085
|
async function downloadSkillResource(resource, skillDir) {
|
|
3070
3086
|
if (!resource.url) throw new Error(`${resource.path}: download URL is unavailable`);
|
|
3071
3087
|
let response;
|
|
@@ -3075,14 +3091,7 @@ async function downloadSkillResource(resource, skillDir) {
|
|
|
3075
3091
|
throw new Error(`${resource.path}: download failed`);
|
|
3076
3092
|
}
|
|
3077
3093
|
if (!response.ok) throw new Error(`${resource.path}: download failed with HTTP ${response.status}`);
|
|
3078
|
-
|
|
3079
|
-
if (bytes.byteLength !== resource.size) throw new Error(`${resource.path}: downloaded size does not match`);
|
|
3080
|
-
if (createHash("sha256").update(bytes).digest("hex") !== resource.sha256) {
|
|
3081
|
-
throw new Error(`${resource.path}: downloaded checksum does not match`);
|
|
3082
|
-
}
|
|
3083
|
-
const destination = join2(skillDir, ...resource.path.split("/"));
|
|
3084
|
-
mkdirSync(dirname2(destination), { recursive: true });
|
|
3085
|
-
writeFileSync(destination, bytes);
|
|
3094
|
+
writeVerifiedSkillResource(resource, Buffer.from(await response.arrayBuffer()), skillDir, "downloaded");
|
|
3086
3095
|
}
|
|
3087
3096
|
function replaceSkillDirectory(stagingDir, skillDir) {
|
|
3088
3097
|
const backupDir = `${stagingDir}.previous`;
|
|
@@ -3096,7 +3105,7 @@ function replaceSkillDirectory(stagingDir, skillDir) {
|
|
|
3096
3105
|
}
|
|
3097
3106
|
if (hadExisting) rmSync(backupDir, { recursive: true, force: true });
|
|
3098
3107
|
}
|
|
3099
|
-
async function pullSkills(api, platformDir, filterName, projectId, appName) {
|
|
3108
|
+
async function pullSkills(api, platformDir, filterName, projectId, appName, checkedLocalSkills) {
|
|
3100
3109
|
if (!projectId) {
|
|
3101
3110
|
console.log(pc2.dim(" Skipped \u2014 project not registered (run `lumera register`)"));
|
|
3102
3111
|
return;
|
|
@@ -3108,12 +3117,26 @@ async function pullSkills(api, platformDir, filterName, projectId, appName) {
|
|
|
3108
3117
|
appName
|
|
3109
3118
|
);
|
|
3110
3119
|
const resolvedFilter = resolveProjectSkillLocalSlug(remoteSkills, filterName);
|
|
3120
|
+
const checkedLocalSkillsBySlug = new Map(checkedLocalSkills?.map((skill) => [skill.slug, skill]) ?? []);
|
|
3111
3121
|
for (const [localSlug, listedSkill] of remoteSkills) {
|
|
3112
3122
|
if (!SKILL_SLUG_RE.test(localSlug)) {
|
|
3113
3123
|
console.log(pc2.yellow(` \u26A0 Skipping ${listedSkill.name}: skill slug ${listedSkill.slug} cannot be represented in platform/skills`));
|
|
3114
3124
|
continue;
|
|
3115
3125
|
}
|
|
3116
3126
|
if (resolvedFilter && localSlug !== resolvedFilter) continue;
|
|
3127
|
+
const listedResources = [...listedSkill.resources ?? []].sort((a, b) => a.path.localeCompare(b.path));
|
|
3128
|
+
validateRemoteSkillResources(listedResources);
|
|
3129
|
+
let local = checkedLocalSkillsBySlug.get(localSlug);
|
|
3130
|
+
if (!checkedLocalSkills) {
|
|
3131
|
+
try {
|
|
3132
|
+
[local] = loadLocalSkills(platformDir, localSlug);
|
|
3133
|
+
} catch {
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
if (local && skillMetadataChanges(local, listedSkill).length === 0 && skillResourceChangesEmpty(compareSkillResources(local.resources, listedResources))) {
|
|
3137
|
+
console.log(pc2.green(" \u2713"), `${listedSkill.name} (unchanged)`);
|
|
3138
|
+
continue;
|
|
3139
|
+
}
|
|
3117
3140
|
const skill = await api.getAgentSkill(listedSkill.id, { resource_urls: "attachment" });
|
|
3118
3141
|
if (!(skill.summary || "").trim() || !(skill.instructions || "").trim()) {
|
|
3119
3142
|
console.log(pc2.yellow(` \u26A0 Skipping ${skill.name}: source-managed skills require both summary and instructions`));
|
|
@@ -3121,6 +3144,7 @@ async function pullSkills(api, platformDir, filterName, projectId, appName) {
|
|
|
3121
3144
|
}
|
|
3122
3145
|
const resources = [...skill.resources ?? []].sort((a, b) => a.path.localeCompare(b.path));
|
|
3123
3146
|
validateRemoteSkillResources(resources);
|
|
3147
|
+
const localResources = new Map(local?.resources.map((resource) => [resource.path, resource]) ?? []);
|
|
3124
3148
|
const skillDir = join2(skillsDir, localSlug);
|
|
3125
3149
|
mkdirSync(skillsDir, { recursive: true });
|
|
3126
3150
|
const stagingDir = mkdtempSync(join2(platformDir, `.skill-${localSlug}-pull-`));
|
|
@@ -3130,7 +3154,14 @@ async function pullSkills(api, platformDir, filterName, projectId, appName) {
|
|
|
3130
3154
|
summary: skill.summary || "",
|
|
3131
3155
|
instructions: skill.instructions || ""
|
|
3132
3156
|
}));
|
|
3133
|
-
for (const resource of resources)
|
|
3157
|
+
for (const resource of resources) {
|
|
3158
|
+
const localResource = localResources.get(resource.path);
|
|
3159
|
+
if (localResource?.size === resource.size && localResource.sha256 === resource.sha256) {
|
|
3160
|
+
writeVerifiedSkillResource(resource, readFileSync2(localResource.filePath), stagingDir, "local");
|
|
3161
|
+
} else {
|
|
3162
|
+
await downloadSkillResource(resource, stagingDir);
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3134
3165
|
replaceSkillDirectory(stagingDir, skillDir);
|
|
3135
3166
|
} finally {
|
|
3136
3167
|
rmSync(stagingDir, { recursive: true, force: true });
|
|
@@ -3565,7 +3596,7 @@ async function listResources(api, platformDir, filterType, appName, projectId) {
|
|
|
3565
3596
|
} else {
|
|
3566
3597
|
const resourceChanges = compareSkillResources(skill.resources, remote.resources ?? []);
|
|
3567
3598
|
const details = [
|
|
3568
|
-
|
|
3599
|
+
skillMetadataChanges(skill, remote).length > 0 ? "SKILL.md" : "",
|
|
3569
3600
|
skillResourceChangesEmpty(resourceChanges) ? "" : `resources (${skillResourceChangesSummary(resourceChanges)})`
|
|
3570
3601
|
].filter(Boolean).join(", ");
|
|
3571
3602
|
results.push(details ? { name: skill.name, type: "skills", status: "changed", details } : { name: skill.name, type: "skills", status: "synced" });
|
|
@@ -4077,7 +4108,7 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
|
|
|
4077
4108
|
console.log();
|
|
4078
4109
|
if (local && remote) {
|
|
4079
4110
|
const resourceChanges = compareSkillResources(local.resources, remote.resources ?? []);
|
|
4080
|
-
const changed = local
|
|
4111
|
+
const changed = skillMetadataChanges(local, remote).length > 0 || !skillResourceChangesEmpty(resourceChanges);
|
|
4081
4112
|
console.log(` Status: ${changed ? pc2.yellow("changed") : pc2.green("synced")}`);
|
|
4082
4113
|
if (!skillResourceChangesEmpty(resourceChanges)) {
|
|
4083
4114
|
console.log(` Resources: ${skillResourceChangesSummary(resourceChanges)}`);
|
|
@@ -4525,6 +4556,7 @@ async function pull(args) {
|
|
|
4525
4556
|
const api = createApiClient(void 0, void 0, appName);
|
|
4526
4557
|
const projectId = getProjectId(projectRoot);
|
|
4527
4558
|
const { type, name } = parseResource(filteredArgs[0]);
|
|
4559
|
+
let checkedLocalSkills;
|
|
4528
4560
|
if (!force) {
|
|
4529
4561
|
const conflicts = [];
|
|
4530
4562
|
let collections;
|
|
@@ -4539,16 +4571,16 @@ async function pull(args) {
|
|
|
4539
4571
|
await api.listAgentSkills({ project_id: projectId }),
|
|
4540
4572
|
appName
|
|
4541
4573
|
);
|
|
4542
|
-
|
|
4574
|
+
checkedLocalSkills = loadLocalSkills(
|
|
4543
4575
|
platformDir,
|
|
4544
4576
|
resolveProjectSkillLocalSlug(remoteBySlug, name || void 0)
|
|
4545
4577
|
);
|
|
4546
|
-
if (
|
|
4547
|
-
for (const skill of
|
|
4578
|
+
if (checkedLocalSkills.length > 0) {
|
|
4579
|
+
for (const skill of checkedLocalSkills) {
|
|
4548
4580
|
const remote = remoteBySlug.get(skill.slug);
|
|
4549
4581
|
if (!remote) continue;
|
|
4550
4582
|
const resourceChanges = compareSkillResources(skill.resources, remote.resources ?? []);
|
|
4551
|
-
if (
|
|
4583
|
+
if (skillMetadataChanges(skill, remote).length > 0 || resourceChanges.added.length > 0 || resourceChanges.replaced.length > 0) {
|
|
4552
4584
|
conflicts.push(`skills/${skill.slug}`);
|
|
4553
4585
|
}
|
|
4554
4586
|
}
|
|
@@ -4628,7 +4660,7 @@ async function pull(args) {
|
|
|
4628
4660
|
}
|
|
4629
4661
|
if (!type || type === "skills") {
|
|
4630
4662
|
console.log(pc2.bold(" Skills:"));
|
|
4631
|
-
await pullSkills(api, platformDir, name || void 0, projectId, appName);
|
|
4663
|
+
await pullSkills(api, platformDir, name || void 0, projectId, appName, checkedLocalSkills);
|
|
4632
4664
|
console.log();
|
|
4633
4665
|
}
|
|
4634
4666
|
if (!type || type === "agents") {
|