@lumerahq/cli 0.31.0-dev.0 → 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/{chunk-AEZLQMFF.js → chunk-B6Y5CECC.js} +2 -2
- package/dist/{chunk-ODANFLL7.js → chunk-HS63O4MH.js} +93 -11
- package/dist/{chunk-2K4ZYGCE.js → chunk-IVII7OZZ.js} +1 -1
- package/dist/{chunk-E7YZ6QS6.js → chunk-ZJ7M7JD3.js} +29 -0
- package/dist/{deps-ZDRNDMUF.js → deps-SIZ77NYE.js} +6 -4
- package/dist/{dev-S5JVAXDD.js → dev-7ZQRZJXX.js} +3 -3
- package/dist/{functions-DZCNHDRJ.js → functions-WPHGKE54.js} +1 -1
- package/dist/index.js +14 -14
- package/dist/{init-QVR42DUW.js → init-WE722M4X.js} +1 -1
- package/dist/{project-3HITJUM4.js → project-BDRAEZWE.js} +1 -1
- package/dist/{register-G3YK77HE.js → register-ZXNNU3NJ.js} +1 -1
- package/dist/{resources-I6JSKGOX.js → resources-FHL4IUQD.js} +378 -76
- package/dist/{run-MSAMUYQH.js → run-7KJPBOIY.js} +1 -1
- package/package.json +1 -1
- package/templates/default/package.json +8 -1
- package/templates/default/pnpm-lock.yaml +172 -183
- package/templates/default/pyproject.toml +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
deploy
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-IVII7OZZ.js";
|
|
4
4
|
import {
|
|
5
5
|
syncDeps
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-HS63O4MH.js";
|
|
7
7
|
import {
|
|
8
8
|
loadEnv
|
|
9
9
|
} from "./chunk-2CR762KB.js";
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
createApiClient,
|
|
13
13
|
isApiErrorStatus,
|
|
14
14
|
validateCollectionNameLength
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-ZJ7M7JD3.js";
|
|
16
16
|
import {
|
|
17
17
|
findProjectRoot,
|
|
18
18
|
getApiUrl,
|
|
@@ -31,8 +31,9 @@ import "./chunk-PNKVD2UK.js";
|
|
|
31
31
|
import pc2 from "picocolors";
|
|
32
32
|
import prompts from "prompts";
|
|
33
33
|
import { execFileSync, execSync } from "child_process";
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
34
|
+
import { createHash } from "crypto";
|
|
35
|
+
import { existsSync as existsSync2, lstatSync, mkdirSync, mkdtempSync, readdirSync as readdirSync2, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync } from "fs";
|
|
36
|
+
import { dirname as dirname2, join as join2, resolve } from "path";
|
|
36
37
|
|
|
37
38
|
// src/lib/lint/index.ts
|
|
38
39
|
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
@@ -2496,6 +2497,7 @@ async function applyApp(args) {
|
|
|
2496
2497
|
const accessLevel = getFlagValue(args, "access-level");
|
|
2497
2498
|
const validAccessLevels = [
|
|
2498
2499
|
"org_members_with_permission",
|
|
2500
|
+
"specific_people",
|
|
2499
2501
|
"org_members",
|
|
2500
2502
|
"invited",
|
|
2501
2503
|
"signed_in",
|
|
@@ -2642,6 +2644,133 @@ var SKILL_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
|
2642
2644
|
var SKILL_NAME_MAX_LENGTH = 100;
|
|
2643
2645
|
var SKILL_SLUG_MAX_LENGTH = 100;
|
|
2644
2646
|
var SKILL_SUMMARY_MAX_LENGTH = 500;
|
|
2647
|
+
var SKILL_RESOURCE_MAX_COUNT = 20;
|
|
2648
|
+
var SKILL_RESOURCE_MAX_BYTES = 10 * 1024 * 1024;
|
|
2649
|
+
var SKILL_RESOURCES_MAX_TOTAL_BYTES = 100 * 1024 * 1024;
|
|
2650
|
+
var SKILL_RESOURCE_MAX_PATH_BYTES = 1024;
|
|
2651
|
+
var SKILL_RESOURCE_MAX_PATH_PART_BYTES = 255;
|
|
2652
|
+
var SKILL_RESOURCE_CONTENT_TYPES = {
|
|
2653
|
+
bmp: "image/bmp",
|
|
2654
|
+
gif: "image/gif",
|
|
2655
|
+
jpeg: "image/jpeg",
|
|
2656
|
+
jpg: "image/jpeg",
|
|
2657
|
+
pdf: "application/pdf",
|
|
2658
|
+
png: "image/png",
|
|
2659
|
+
webp: "image/webp"
|
|
2660
|
+
};
|
|
2661
|
+
function skillResourceContentType(path) {
|
|
2662
|
+
return SKILL_RESOURCE_CONTENT_TYPES[path.split(".").pop()?.toLowerCase() ?? ""] ?? "application/octet-stream";
|
|
2663
|
+
}
|
|
2664
|
+
function foldSkillResourcePathPart(value) {
|
|
2665
|
+
return value.toUpperCase();
|
|
2666
|
+
}
|
|
2667
|
+
function skillResourcePathError(value) {
|
|
2668
|
+
if (!value || Buffer.byteLength(value) > SKILL_RESOURCE_MAX_PATH_BYTES) {
|
|
2669
|
+
return `path must be at most ${SKILL_RESOURCE_MAX_PATH_BYTES} bytes`;
|
|
2670
|
+
}
|
|
2671
|
+
if (value.startsWith("/") || value.endsWith("/") || value.includes("\\")) {
|
|
2672
|
+
return "path must be relative and slash-separated";
|
|
2673
|
+
}
|
|
2674
|
+
if (/\p{Cc}/u.test(value)) return "path cannot contain control characters";
|
|
2675
|
+
for (const part of value.split("/")) {
|
|
2676
|
+
if (!part || part === "." || part === "..") return "path cannot contain empty, dot, or parent segments";
|
|
2677
|
+
if (part !== part.trim()) return "path segments cannot start or end with whitespace";
|
|
2678
|
+
if (Buffer.byteLength(part) > SKILL_RESOURCE_MAX_PATH_PART_BYTES) {
|
|
2679
|
+
return `path segments must be at most ${SKILL_RESOURCE_MAX_PATH_PART_BYTES} bytes`;
|
|
2680
|
+
}
|
|
2681
|
+
if (foldSkillResourcePathPart(part) === "SKILL.MD") return "SKILL.md is reserved";
|
|
2682
|
+
}
|
|
2683
|
+
return null;
|
|
2684
|
+
}
|
|
2685
|
+
function skillResourcePathsCollide(left, right) {
|
|
2686
|
+
const leftParts = left.split("/");
|
|
2687
|
+
const rightParts = right.split("/");
|
|
2688
|
+
const limit = Math.min(leftParts.length, rightParts.length);
|
|
2689
|
+
for (let i = 0; i < limit; i++) {
|
|
2690
|
+
if (leftParts[i] === rightParts[i]) continue;
|
|
2691
|
+
return foldSkillResourcePathPart(leftParts[i] ?? "") === foldSkillResourcePathPart(rightParts[i] ?? "");
|
|
2692
|
+
}
|
|
2693
|
+
return true;
|
|
2694
|
+
}
|
|
2695
|
+
function localSkillResources(skillDir) {
|
|
2696
|
+
const resources = [];
|
|
2697
|
+
const errors = [];
|
|
2698
|
+
let totalBytes = 0;
|
|
2699
|
+
let fileCount = 0;
|
|
2700
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
2701
|
+
const scan = (directory, parentParts) => {
|
|
2702
|
+
const names = readdirSync2(directory, { encoding: "buffer" }).map((rawName) => {
|
|
2703
|
+
try {
|
|
2704
|
+
return decoder.decode(rawName);
|
|
2705
|
+
} catch {
|
|
2706
|
+
errors.push(`${parentParts.join("/") || "."}: file names must be valid UTF-8`);
|
|
2707
|
+
return null;
|
|
2708
|
+
}
|
|
2709
|
+
}).filter((name) => name !== null).sort();
|
|
2710
|
+
for (const name of names) {
|
|
2711
|
+
if (parentParts.length === 0 && name === "SKILL.md") continue;
|
|
2712
|
+
const parts = [...parentParts, name];
|
|
2713
|
+
const resourcePath = parts.join("/");
|
|
2714
|
+
const filePath = join2(directory, name);
|
|
2715
|
+
const stat = lstatSync(filePath);
|
|
2716
|
+
if (stat.isSymbolicLink()) {
|
|
2717
|
+
errors.push(`${resourcePath}: symlinks are not supported`);
|
|
2718
|
+
continue;
|
|
2719
|
+
}
|
|
2720
|
+
if (stat.isDirectory()) {
|
|
2721
|
+
scan(filePath, parts);
|
|
2722
|
+
continue;
|
|
2723
|
+
}
|
|
2724
|
+
if (!stat.isFile()) {
|
|
2725
|
+
errors.push(`${resourcePath}: only regular files are supported`);
|
|
2726
|
+
continue;
|
|
2727
|
+
}
|
|
2728
|
+
fileCount++;
|
|
2729
|
+
totalBytes += stat.size;
|
|
2730
|
+
const pathError = skillResourcePathError(resourcePath);
|
|
2731
|
+
if (pathError) {
|
|
2732
|
+
errors.push(`${resourcePath}: ${pathError}`);
|
|
2733
|
+
continue;
|
|
2734
|
+
}
|
|
2735
|
+
if (stat.size > SKILL_RESOURCE_MAX_BYTES) {
|
|
2736
|
+
errors.push(`${resourcePath}: file exceeds the 10 MiB limit`);
|
|
2737
|
+
continue;
|
|
2738
|
+
}
|
|
2739
|
+
const conflict = resources.find((resource) => skillResourcePathsCollide(resource.path, resourcePath));
|
|
2740
|
+
if (conflict) {
|
|
2741
|
+
errors.push(`${resourcePath}: path conflicts ignoring letter case with ${conflict.path}`);
|
|
2742
|
+
continue;
|
|
2743
|
+
}
|
|
2744
|
+
const sha256 = createHash("sha256").update(readFileSync2(filePath)).digest("hex");
|
|
2745
|
+
resources.push({ path: resourcePath, filePath, size: stat.size, sha256 });
|
|
2746
|
+
}
|
|
2747
|
+
};
|
|
2748
|
+
scan(skillDir, []);
|
|
2749
|
+
if (fileCount > SKILL_RESOURCE_MAX_COUNT) errors.push(`resources: a skill can have at most ${SKILL_RESOURCE_MAX_COUNT} files`);
|
|
2750
|
+
if (totalBytes > SKILL_RESOURCES_MAX_TOTAL_BYTES) errors.push("resources: files can total at most 100 MiB");
|
|
2751
|
+
return { resources: resources.sort((a, b) => a.path.localeCompare(b.path)), errors };
|
|
2752
|
+
}
|
|
2753
|
+
function compareSkillResources(local, remote) {
|
|
2754
|
+
const localByPath = new Map(local.map((resource) => [resource.path, resource]));
|
|
2755
|
+
const remoteByPath = new Map(remote.map((resource) => [resource.path, resource]));
|
|
2756
|
+
const added = [...localByPath.keys()].filter((path) => !remoteByPath.has(path)).sort();
|
|
2757
|
+
const replaced = [...localByPath].filter(([path, resource]) => {
|
|
2758
|
+
const current = remoteByPath.get(path);
|
|
2759
|
+
return current && (current.size !== resource.size || current.sha256 !== resource.sha256);
|
|
2760
|
+
}).map(([path]) => path).sort();
|
|
2761
|
+
const deleted = [...remoteByPath.keys()].filter((path) => !localByPath.has(path)).sort();
|
|
2762
|
+
return { added, replaced, deleted };
|
|
2763
|
+
}
|
|
2764
|
+
function skillResourceChangesEmpty(changes) {
|
|
2765
|
+
return changes.added.length === 0 && changes.replaced.length === 0 && changes.deleted.length === 0;
|
|
2766
|
+
}
|
|
2767
|
+
function skillResourceChangesSummary(changes) {
|
|
2768
|
+
return [
|
|
2769
|
+
...changes.added.map((path) => `+${path}`),
|
|
2770
|
+
...changes.replaced.map((path) => `~${path}`),
|
|
2771
|
+
...changes.deleted.map((path) => `-${path}`)
|
|
2772
|
+
].join(", ");
|
|
2773
|
+
}
|
|
2645
2774
|
function loadLocalSkills(platformDir, filterName) {
|
|
2646
2775
|
const skillsDir = join2(platformDir, "skills");
|
|
2647
2776
|
if (!existsSync2(skillsDir)) return [];
|
|
@@ -2654,11 +2783,19 @@ function loadLocalSkills(platformDir, filterName) {
|
|
|
2654
2783
|
errors.push(`${entry.name}: directory name must be a lowercase slug`);
|
|
2655
2784
|
continue;
|
|
2656
2785
|
}
|
|
2657
|
-
const
|
|
2786
|
+
const skillDir = join2(skillsDir, entry.name);
|
|
2787
|
+
const path = join2(skillDir, "SKILL.md");
|
|
2658
2788
|
if (!existsSync2(path)) {
|
|
2659
2789
|
errors.push(`${entry.name}: missing SKILL.md`);
|
|
2660
2790
|
continue;
|
|
2661
2791
|
}
|
|
2792
|
+
const skillMarkdownStat = lstatSync(path);
|
|
2793
|
+
if (skillMarkdownStat.isSymbolicLink() || !skillMarkdownStat.isFile()) {
|
|
2794
|
+
errors.push(`${entry.name}: SKILL.md must be a regular file`);
|
|
2795
|
+
continue;
|
|
2796
|
+
}
|
|
2797
|
+
const { resources, errors: resourceErrors } = localSkillResources(skillDir);
|
|
2798
|
+
errors.push(...resourceErrors.map((error3) => `${entry.name}: ${error3}`));
|
|
2662
2799
|
const content = readFileSync2(path, "utf-8");
|
|
2663
2800
|
const separator = /\r?\n---\r?\n/.exec(content);
|
|
2664
2801
|
const parts = separator ? [content.slice(0, separator.index), content.slice(separator.index + separator[0].length)] : [content];
|
|
@@ -2684,7 +2821,7 @@ function loadLocalSkills(platformDir, filterName) {
|
|
|
2684
2821
|
if (summary.length > SKILL_SUMMARY_MAX_LENGTH) errors.push(`${entry.name}: summary must be at most ${SKILL_SUMMARY_MAX_LENGTH} characters`);
|
|
2685
2822
|
if (!instructions) errors.push(`${entry.name}: SKILL.md must include instructions after ---`);
|
|
2686
2823
|
if (name && name.length <= SKILL_NAME_MAX_LENGTH && summary && summary.length <= SKILL_SUMMARY_MAX_LENGTH && instructions) {
|
|
2687
|
-
skills.push({ slug: entry.name, name, summary, instructions });
|
|
2824
|
+
skills.push({ slug: entry.name, name, summary, instructions, resources });
|
|
2688
2825
|
}
|
|
2689
2826
|
}
|
|
2690
2827
|
if (errors.length > 0) {
|
|
@@ -2710,6 +2847,13 @@ ${skill.summary}
|
|
|
2710
2847
|
${skill.instructions}
|
|
2711
2848
|
`;
|
|
2712
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
|
+
}
|
|
2713
2857
|
function scopedProjectSkillSlug(appName, localSlug) {
|
|
2714
2858
|
return appName ? `${appName}:${localSlug}` : localSlug;
|
|
2715
2859
|
}
|
|
@@ -2728,6 +2872,13 @@ function remoteSkillsByLocalSlug(remoteSkills, appName) {
|
|
|
2728
2872
|
}
|
|
2729
2873
|
return byLocalSlug;
|
|
2730
2874
|
}
|
|
2875
|
+
function resolveProjectSkillLocalSlug(remoteSkills, selector) {
|
|
2876
|
+
if (!selector) return void 0;
|
|
2877
|
+
for (const [localSlug, skill] of remoteSkills) {
|
|
2878
|
+
if (selector === localSlug || selector === skill.slug || selector === skill.name) return localSlug;
|
|
2879
|
+
}
|
|
2880
|
+
return selector;
|
|
2881
|
+
}
|
|
2731
2882
|
function projectSkillRefCandidates(refs, appName) {
|
|
2732
2883
|
return [...new Set(refs.flatMap((ref) => appName && SKILL_SLUG_RE.test(ref) ? [scopedProjectSkillSlug(appName, ref), ref] : [ref]))];
|
|
2733
2884
|
}
|
|
@@ -2794,13 +2945,21 @@ async function planSkills(api, localSkills, projectId, appName) {
|
|
|
2794
2945
|
for (const skill of localSkills) {
|
|
2795
2946
|
const remote = remoteBySlug.get(skill.slug);
|
|
2796
2947
|
if (!remote) {
|
|
2797
|
-
|
|
2948
|
+
const resourceSummary = skillResourceChangesSummary(compareSkillResources(skill.resources, []));
|
|
2949
|
+
changes.push({
|
|
2950
|
+
type: "create",
|
|
2951
|
+
resource: "skill",
|
|
2952
|
+
id: skill.slug,
|
|
2953
|
+
name: skill.name,
|
|
2954
|
+
details: resourceSummary ? `resources (${resourceSummary})` : void 0
|
|
2955
|
+
});
|
|
2798
2956
|
continue;
|
|
2799
2957
|
}
|
|
2800
|
-
const changed =
|
|
2801
|
-
|
|
2802
|
-
if ((
|
|
2803
|
-
|
|
2958
|
+
const changed = skillMetadataChanges(skill, remote);
|
|
2959
|
+
const resourceChanges = compareSkillResources(skill.resources, remote.resources ?? []);
|
|
2960
|
+
if (!skillResourceChangesEmpty(resourceChanges)) {
|
|
2961
|
+
changed.push(`resources (${skillResourceChangesSummary(resourceChanges)})`);
|
|
2962
|
+
}
|
|
2804
2963
|
if (changed.length > 0) {
|
|
2805
2964
|
changes.push({
|
|
2806
2965
|
type: "update",
|
|
@@ -2808,12 +2967,64 @@ async function planSkills(api, localSkills, projectId, appName) {
|
|
|
2808
2967
|
id: skill.slug,
|
|
2809
2968
|
name: skill.name,
|
|
2810
2969
|
details: `changed: ${changed.join(", ")}`,
|
|
2811
|
-
textDiffs: changed.includes("instructions") || changed.includes("summary") ? [{ field: "SKILL.md", oldText: skillMarkdown({
|
|
2970
|
+
textDiffs: changed.includes("instructions") || changed.includes("summary") ? [{ field: "SKILL.md", oldText: skillMarkdown({ name: remote.name, summary: remote.summary || "", instructions: remote.instructions || "" }), newText: skillMarkdown(skill) }] : void 0
|
|
2812
2971
|
});
|
|
2813
2972
|
}
|
|
2814
2973
|
}
|
|
2815
2974
|
return changes;
|
|
2816
2975
|
}
|
|
2976
|
+
async function uploadSkillResource(api, skillID, resource) {
|
|
2977
|
+
const bytes = readFileSync2(resource.filePath);
|
|
2978
|
+
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
2979
|
+
if (bytes.byteLength !== resource.size || sha256 !== resource.sha256) {
|
|
2980
|
+
throw new Error(`${resource.path} changed while apply was running`);
|
|
2981
|
+
}
|
|
2982
|
+
const metadata = {
|
|
2983
|
+
path: resource.path,
|
|
2984
|
+
size: resource.size,
|
|
2985
|
+
content_type: skillResourceContentType(resource.path),
|
|
2986
|
+
sha256: resource.sha256
|
|
2987
|
+
};
|
|
2988
|
+
const upload = await api.startAgentSkillResourceUpload(skillID, metadata);
|
|
2989
|
+
const headers = { ...upload.headers ?? {} };
|
|
2990
|
+
if (!Object.keys(headers).some((name) => name.toLowerCase() === "content-type")) {
|
|
2991
|
+
headers["Content-Type"] = metadata.content_type;
|
|
2992
|
+
}
|
|
2993
|
+
const response = await fetchWithRetry(upload.upload_url, {
|
|
2994
|
+
method: "PUT",
|
|
2995
|
+
headers,
|
|
2996
|
+
body: bytes
|
|
2997
|
+
}, { attempts: 3, retryMethods: ["PUT"] });
|
|
2998
|
+
if (!response.ok) throw new Error(`${resource.path}: upload failed with HTTP ${response.status}`);
|
|
2999
|
+
await api.completeAgentSkillResourceUpload(skillID, upload.upload_id, metadata);
|
|
3000
|
+
}
|
|
3001
|
+
async function reconcileSkillResources(api, skill, localResources) {
|
|
3002
|
+
let remoteResources = skill.resources ?? [];
|
|
3003
|
+
let changes = compareSkillResources(localResources, remoteResources);
|
|
3004
|
+
if (changes.deleted.length > 0) {
|
|
3005
|
+
const deletions = changes.deleted;
|
|
3006
|
+
const expectedByPath = new Map(remoteResources.map((resource) => [resource.path, resource]));
|
|
3007
|
+
remoteResources = (await api.getAgentSkill(skill.id)).resources ?? [];
|
|
3008
|
+
for (const resourcePath of deletions) {
|
|
3009
|
+
const expected = expectedByPath.get(resourcePath);
|
|
3010
|
+
const current = remoteResources.find((resource) => resource.path === resourcePath);
|
|
3011
|
+
if (!current) continue;
|
|
3012
|
+
if (!expected || current.size !== expected.size || current.sha256 !== expected.sha256) {
|
|
3013
|
+
throw new Error(`${resourcePath} changed while apply was running`);
|
|
3014
|
+
}
|
|
3015
|
+
const result = await api.deleteAgentSkillResource(skill.id, resourcePath);
|
|
3016
|
+
remoteResources = result.resources;
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
changes = compareSkillResources(localResources, remoteResources);
|
|
3020
|
+
if (changes.deleted.length > 0) throw new Error("remote resources changed while apply was running");
|
|
3021
|
+
const uploadPaths = /* @__PURE__ */ new Set([...changes.added, ...changes.replaced]);
|
|
3022
|
+
const remoteByPath = new Map(remoteResources.map((resource) => [resource.path, resource]));
|
|
3023
|
+
const uploads = localResources.filter((resource) => uploadPaths.has(resource.path)).sort(
|
|
3024
|
+
(a, b) => a.size - (remoteByPath.get(a.path)?.size ?? 0) - (b.size - (remoteByPath.get(b.path)?.size ?? 0))
|
|
3025
|
+
);
|
|
3026
|
+
for (const resource of uploads) await uploadSkillResource(api, skill.id, resource);
|
|
3027
|
+
}
|
|
2817
3028
|
async function applySkills(api, localSkills, projectId, appName) {
|
|
2818
3029
|
if (localSkills.length === 0) return 0;
|
|
2819
3030
|
validateProjectSkillStorageSlugs(localSkills, appName);
|
|
@@ -2824,21 +3035,19 @@ async function applySkills(api, localSkills, projectId, appName) {
|
|
|
2824
3035
|
for (const skill of localSkills) {
|
|
2825
3036
|
const remote = remoteBySlug.get(skill.slug);
|
|
2826
3037
|
try {
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
console.log(pc2.green(" \u2713"), `${skill.name} (created)`);
|
|
2841
|
-
}
|
|
3038
|
+
const saved = remote ? await api.updateAgentSkill(remote.id, {
|
|
3039
|
+
name: skill.name,
|
|
3040
|
+
summary: skill.summary,
|
|
3041
|
+
instructions: skill.instructions
|
|
3042
|
+
}) : await api.createAgentSkill({
|
|
3043
|
+
name: skill.name,
|
|
3044
|
+
summary: skill.summary,
|
|
3045
|
+
instructions: skill.instructions,
|
|
3046
|
+
slug: scopedProjectSkillSlug(appName, skill.slug),
|
|
3047
|
+
project_id: scopedProjectID
|
|
3048
|
+
});
|
|
3049
|
+
await reconcileSkillResources(api, saved, skill.resources);
|
|
3050
|
+
console.log(pc2.green(" \u2713"), `${skill.name} (${remote ? "updated" : "created"})`);
|
|
2842
3051
|
} catch (error3) {
|
|
2843
3052
|
console.log(pc2.red(" \u2717"), `${skill.name}: ${error3}`);
|
|
2844
3053
|
errors++;
|
|
@@ -2846,7 +3055,57 @@ async function applySkills(api, localSkills, projectId, appName) {
|
|
|
2846
3055
|
}
|
|
2847
3056
|
return errors;
|
|
2848
3057
|
}
|
|
2849
|
-
|
|
3058
|
+
function validateRemoteSkillResources(resources) {
|
|
3059
|
+
if (resources.length > SKILL_RESOURCE_MAX_COUNT) throw new Error(`remote skill has more than ${SKILL_RESOURCE_MAX_COUNT} resources`);
|
|
3060
|
+
const paths = [];
|
|
3061
|
+
let totalBytes = 0;
|
|
3062
|
+
for (const resource of resources) {
|
|
3063
|
+
const pathError = skillResourcePathError(resource.path);
|
|
3064
|
+
if (pathError) throw new Error(`remote resource ${resource.path}: ${pathError}`);
|
|
3065
|
+
const conflict = paths.find((path) => skillResourcePathsCollide(path, resource.path));
|
|
3066
|
+
if (conflict) throw new Error(`remote resource paths conflict: ${conflict} and ${resource.path}`);
|
|
3067
|
+
if (!Number.isSafeInteger(resource.size) || resource.size < 0 || resource.size > SKILL_RESOURCE_MAX_BYTES) {
|
|
3068
|
+
throw new Error(`remote resource ${resource.path} has an invalid size`);
|
|
3069
|
+
}
|
|
3070
|
+
if (!/^[a-f0-9]{64}$/.test(resource.sha256)) throw new Error(`remote resource ${resource.path} has an invalid checksum`);
|
|
3071
|
+
paths.push(resource.path);
|
|
3072
|
+
totalBytes += resource.size;
|
|
3073
|
+
}
|
|
3074
|
+
if (totalBytes > SKILL_RESOURCES_MAX_TOTAL_BYTES) throw new Error("remote skill resources exceed 100 MiB");
|
|
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
|
+
}
|
|
3085
|
+
async function downloadSkillResource(resource, skillDir) {
|
|
3086
|
+
if (!resource.url) throw new Error(`${resource.path}: download URL is unavailable`);
|
|
3087
|
+
let response;
|
|
3088
|
+
try {
|
|
3089
|
+
response = await fetchWithRetry(resource.url);
|
|
3090
|
+
} catch {
|
|
3091
|
+
throw new Error(`${resource.path}: download failed`);
|
|
3092
|
+
}
|
|
3093
|
+
if (!response.ok) throw new Error(`${resource.path}: download failed with HTTP ${response.status}`);
|
|
3094
|
+
writeVerifiedSkillResource(resource, Buffer.from(await response.arrayBuffer()), skillDir, "downloaded");
|
|
3095
|
+
}
|
|
3096
|
+
function replaceSkillDirectory(stagingDir, skillDir) {
|
|
3097
|
+
const backupDir = `${stagingDir}.previous`;
|
|
3098
|
+
const hadExisting = existsSync2(skillDir);
|
|
3099
|
+
if (hadExisting) renameSync(skillDir, backupDir);
|
|
3100
|
+
try {
|
|
3101
|
+
renameSync(stagingDir, skillDir);
|
|
3102
|
+
} catch (error3) {
|
|
3103
|
+
if (hadExisting && !existsSync2(skillDir)) renameSync(backupDir, skillDir);
|
|
3104
|
+
throw error3;
|
|
3105
|
+
}
|
|
3106
|
+
if (hadExisting) rmSync(backupDir, { recursive: true, force: true });
|
|
3107
|
+
}
|
|
3108
|
+
async function pullSkills(api, platformDir, filterName, projectId, appName, checkedLocalSkills) {
|
|
2850
3109
|
if (!projectId) {
|
|
2851
3110
|
console.log(pc2.dim(" Skipped \u2014 project not registered (run `lumera register`)"));
|
|
2852
3111
|
return;
|
|
@@ -2857,24 +3116,56 @@ async function pullSkills(api, platformDir, filterName, projectId, appName) {
|
|
|
2857
3116
|
await api.listAgentSkills({ project_id: scopedProjectID }),
|
|
2858
3117
|
appName
|
|
2859
3118
|
);
|
|
2860
|
-
|
|
3119
|
+
const resolvedFilter = resolveProjectSkillLocalSlug(remoteSkills, filterName);
|
|
3120
|
+
const checkedLocalSkillsBySlug = new Map(checkedLocalSkills?.map((skill) => [skill.slug, skill]) ?? []);
|
|
3121
|
+
for (const [localSlug, listedSkill] of remoteSkills) {
|
|
2861
3122
|
if (!SKILL_SLUG_RE.test(localSlug)) {
|
|
2862
|
-
console.log(pc2.yellow(` \u26A0 Skipping ${
|
|
3123
|
+
console.log(pc2.yellow(` \u26A0 Skipping ${listedSkill.name}: skill slug ${listedSkill.slug} cannot be represented in platform/skills`));
|
|
3124
|
+
continue;
|
|
3125
|
+
}
|
|
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)`);
|
|
2863
3138
|
continue;
|
|
2864
3139
|
}
|
|
2865
|
-
|
|
3140
|
+
const skill = await api.getAgentSkill(listedSkill.id, { resource_urls: "attachment" });
|
|
2866
3141
|
if (!(skill.summary || "").trim() || !(skill.instructions || "").trim()) {
|
|
2867
3142
|
console.log(pc2.yellow(` \u26A0 Skipping ${skill.name}: source-managed skills require both summary and instructions`));
|
|
2868
3143
|
continue;
|
|
2869
3144
|
}
|
|
3145
|
+
const resources = [...skill.resources ?? []].sort((a, b) => a.path.localeCompare(b.path));
|
|
3146
|
+
validateRemoteSkillResources(resources);
|
|
3147
|
+
const localResources = new Map(local?.resources.map((resource) => [resource.path, resource]) ?? []);
|
|
2870
3148
|
const skillDir = join2(skillsDir, localSlug);
|
|
2871
|
-
mkdirSync(
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
3149
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
3150
|
+
const stagingDir = mkdtempSync(join2(platformDir, `.skill-${localSlug}-pull-`));
|
|
3151
|
+
try {
|
|
3152
|
+
writeFileSync(join2(stagingDir, "SKILL.md"), skillMarkdown({
|
|
3153
|
+
name: skill.name,
|
|
3154
|
+
summary: skill.summary || "",
|
|
3155
|
+
instructions: skill.instructions || ""
|
|
3156
|
+
}));
|
|
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
|
+
}
|
|
3165
|
+
replaceSkillDirectory(stagingDir, skillDir);
|
|
3166
|
+
} finally {
|
|
3167
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
3168
|
+
}
|
|
2878
3169
|
console.log(pc2.green(" \u2713"), `${skill.name} \u2192 skills/${localSlug}/`);
|
|
2879
3170
|
}
|
|
2880
3171
|
}
|
|
@@ -2928,14 +3219,6 @@ function loadLocalAgents(platformDir, filterName, appName) {
|
|
|
2928
3219
|
continue;
|
|
2929
3220
|
}
|
|
2930
3221
|
}
|
|
2931
|
-
if (config.allowed_user_tags !== void 0) {
|
|
2932
|
-
const tags = config.allowed_user_tags;
|
|
2933
|
-
if (!Array.isArray(tags) || tags.length > 100 || tags.some((tag) => typeof tag !== "string" || !tag || Buffer.byteLength(tag) > 64 || tag !== tag.trim() || tag !== tag.toLowerCase())) {
|
|
2934
|
-
errors.push(`${entry.name}: allowed_user_tags must be an array of at most 100 canonical tag keys (1\u201364 bytes each)`);
|
|
2935
|
-
continue;
|
|
2936
|
-
}
|
|
2937
|
-
config.allowed_user_tags = [...new Set(tags)].sort();
|
|
2938
|
-
}
|
|
2939
3222
|
let systemPrompt = readFileSync2(promptPath, "utf-8");
|
|
2940
3223
|
let policyScript = existsSync2(policyPath) ? readFileSync2(policyPath, "utf-8") : "";
|
|
2941
3224
|
if (appName) {
|
|
@@ -2956,9 +3239,6 @@ function loadLocalAgents(platformDir, filterName, appName) {
|
|
|
2956
3239
|
}
|
|
2957
3240
|
return agents;
|
|
2958
3241
|
}
|
|
2959
|
-
function allowedUserTagsChanged(local, remote) {
|
|
2960
|
-
return local.allowed_user_tags !== void 0 && JSON.stringify([...new Set(local.allowed_user_tags)].sort()) !== JSON.stringify([...new Set(remote.allowed_user_tags ?? [])].sort());
|
|
2961
|
-
}
|
|
2962
3242
|
async function planAgents(api, localAgents, projectId, localSourceSkillSlugs = /* @__PURE__ */ new Set(), pendingLocalSkillSlugs = /* @__PURE__ */ new Set(), appName) {
|
|
2963
3243
|
const changes = [];
|
|
2964
3244
|
let validationErrors = 0;
|
|
@@ -3011,7 +3291,6 @@ async function planAgents(api, localAgents, projectId, localSourceSkillSlugs = /
|
|
|
3011
3291
|
if ((remote.system_prompt || "").trim() !== systemPrompt.trim()) diffs.push("system_prompt");
|
|
3012
3292
|
if ((remote.model || "") !== (agent.model || "")) diffs.push("model");
|
|
3013
3293
|
if (agent.thinking_level !== void 0 && (remote.thinking_level || "") !== agent.thinking_level) diffs.push("thinking_level");
|
|
3014
|
-
if (allowedUserTagsChanged(agent, remote)) diffs.push("allowed_user_tags");
|
|
3015
3294
|
if ((remote.idle_reap_timeout_seconds ?? 0) !== (agent.idle_reap_timeout_seconds ?? 0)) diffs.push("idle_reap_timeout_seconds");
|
|
3016
3295
|
if ((remote.policy_script || "").trim() !== (policyScript || "").trim()) diffs.push("policy_script");
|
|
3017
3296
|
if ((remote.policy_enabled || false) !== (agent.policy_enabled || false)) diffs.push("policy_enabled");
|
|
@@ -3115,7 +3394,6 @@ async function applyAgents(api, localAgents, projectId, localSourceSkillSlugs =
|
|
|
3115
3394
|
system_prompt: systemPrompt,
|
|
3116
3395
|
model: agent.model || "",
|
|
3117
3396
|
...agent.thinking_level !== void 0 ? { thinking_level: agent.thinking_level } : {},
|
|
3118
|
-
...agent.allowed_user_tags !== void 0 ? { allowed_user_tags: agent.allowed_user_tags } : {},
|
|
3119
3397
|
idle_reap_timeout_seconds: agent.idle_reap_timeout_seconds ?? 0,
|
|
3120
3398
|
skill_ids: skillIds,
|
|
3121
3399
|
policy_script: policyScript || "",
|
|
@@ -3177,7 +3455,6 @@ async function pullAgents(api, platformDir, filterName, projectId, appName) {
|
|
|
3177
3455
|
if (agent.description) config.description = agent.description;
|
|
3178
3456
|
if (agent.model) config.model = agent.model;
|
|
3179
3457
|
if (agent.thinking_level) config.thinking_level = agent.thinking_level;
|
|
3180
|
-
if (agent.allowed_user_tags != null) config.allowed_user_tags = agent.allowed_user_tags;
|
|
3181
3458
|
if (agent.idle_reap_timeout_seconds) config.idle_reap_timeout_seconds = agent.idle_reap_timeout_seconds;
|
|
3182
3459
|
if (skillSlugs.length > 0) config.skills = skillSlugs;
|
|
3183
3460
|
if (agent.policy_enabled) config.policy_enabled = true;
|
|
@@ -3314,16 +3591,21 @@ async function listResources(api, platformDir, filterType, appName, projectId) {
|
|
|
3314
3591
|
for (const skill of localSkills) {
|
|
3315
3592
|
const remote = remoteBySlug.get(skill.slug);
|
|
3316
3593
|
if (!remote) {
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
results.push({ name: skill.name, type: "skills", status: "changed", details: "SKILL.md" });
|
|
3594
|
+
const resourceChanges = skillResourceChangesSummary(compareSkillResources(skill.resources, []));
|
|
3595
|
+
results.push(resourceChanges ? { name: skill.name, type: "skills", status: "local-only", details: `resources (${resourceChanges})` } : { name: skill.name, type: "skills", status: "local-only" });
|
|
3320
3596
|
} else {
|
|
3321
|
-
|
|
3597
|
+
const resourceChanges = compareSkillResources(skill.resources, remote.resources ?? []);
|
|
3598
|
+
const details = [
|
|
3599
|
+
skillMetadataChanges(skill, remote).length > 0 ? "SKILL.md" : "",
|
|
3600
|
+
skillResourceChangesEmpty(resourceChanges) ? "" : `resources (${skillResourceChangesSummary(resourceChanges)})`
|
|
3601
|
+
].filter(Boolean).join(", ");
|
|
3602
|
+
results.push(details ? { name: skill.name, type: "skills", status: "changed", details } : { name: skill.name, type: "skills", status: "synced" });
|
|
3322
3603
|
}
|
|
3323
3604
|
}
|
|
3324
3605
|
for (const remote of remoteSkills) {
|
|
3325
3606
|
if (!localSlugs.has(localProjectSkillSlug(appName, remote.slug))) {
|
|
3326
|
-
|
|
3607
|
+
const resourceChanges = skillResourceChangesSummary(compareSkillResources([], remote.resources ?? []));
|
|
3608
|
+
results.push(resourceChanges ? { name: remote.name, type: "skills", status: "remote-only", details: `resources (${resourceChanges})` } : { name: remote.name, type: "skills", status: "remote-only" });
|
|
3327
3609
|
}
|
|
3328
3610
|
}
|
|
3329
3611
|
}
|
|
@@ -3825,8 +4107,12 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
|
|
|
3825
4107
|
console.log(pc2.bold(` Skill: ${local?.name || remote?.name}`));
|
|
3826
4108
|
console.log();
|
|
3827
4109
|
if (local && remote) {
|
|
3828
|
-
const
|
|
4110
|
+
const resourceChanges = compareSkillResources(local.resources, remote.resources ?? []);
|
|
4111
|
+
const changed = skillMetadataChanges(local, remote).length > 0 || !skillResourceChangesEmpty(resourceChanges);
|
|
3829
4112
|
console.log(` Status: ${changed ? pc2.yellow("changed") : pc2.green("synced")}`);
|
|
4113
|
+
if (!skillResourceChangesEmpty(resourceChanges)) {
|
|
4114
|
+
console.log(` Resources: ${skillResourceChangesSummary(resourceChanges)}`);
|
|
4115
|
+
}
|
|
3830
4116
|
} else if (local) {
|
|
3831
4117
|
console.log(` Status: ${pc2.yellow("local only")}`);
|
|
3832
4118
|
} else {
|
|
@@ -3835,6 +4121,8 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
|
|
|
3835
4121
|
console.log(` Slug: ${local?.slug || (remote ? localProjectSkillSlug(appName, remote.slug) : "")}`);
|
|
3836
4122
|
const summary = local?.summary || remote?.summary;
|
|
3837
4123
|
if (summary) console.log(` Summary: ${summary}`);
|
|
4124
|
+
const resourcePaths = (local?.resources ?? remote?.resources ?? []).map((resource) => resource.path).sort();
|
|
4125
|
+
if (resourcePaths.length > 0) console.log(` Resource files: ${resourcePaths.join(", ")}`);
|
|
3838
4126
|
console.log();
|
|
3839
4127
|
} else if (resourceType === "mailboxes") {
|
|
3840
4128
|
const localMailboxes = loadLocalMailboxes(platformDir, resourceName);
|
|
@@ -4268,6 +4556,7 @@ async function pull(args) {
|
|
|
4268
4556
|
const api = createApiClient(void 0, void 0, appName);
|
|
4269
4557
|
const projectId = getProjectId(projectRoot);
|
|
4270
4558
|
const { type, name } = parseResource(filteredArgs[0]);
|
|
4559
|
+
let checkedLocalSkills;
|
|
4271
4560
|
if (!force) {
|
|
4272
4561
|
const conflicts = [];
|
|
4273
4562
|
let collections;
|
|
@@ -4278,11 +4567,22 @@ async function pull(args) {
|
|
|
4278
4567
|
collections = /* @__PURE__ */ new Map();
|
|
4279
4568
|
}
|
|
4280
4569
|
if (projectId && (!type || type === "skills")) {
|
|
4281
|
-
const
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4570
|
+
const remoteBySlug = remoteSkillsByLocalSlug(
|
|
4571
|
+
await api.listAgentSkills({ project_id: projectId }),
|
|
4572
|
+
appName
|
|
4573
|
+
);
|
|
4574
|
+
checkedLocalSkills = loadLocalSkills(
|
|
4575
|
+
platformDir,
|
|
4576
|
+
resolveProjectSkillLocalSlug(remoteBySlug, name || void 0)
|
|
4577
|
+
);
|
|
4578
|
+
if (checkedLocalSkills.length > 0) {
|
|
4579
|
+
for (const skill of checkedLocalSkills) {
|
|
4580
|
+
const remote = remoteBySlug.get(skill.slug);
|
|
4581
|
+
if (!remote) continue;
|
|
4582
|
+
const resourceChanges = compareSkillResources(skill.resources, remote.resources ?? []);
|
|
4583
|
+
if (skillMetadataChanges(skill, remote).length > 0 || resourceChanges.added.length > 0 || resourceChanges.replaced.length > 0) {
|
|
4584
|
+
conflicts.push(`skills/${skill.slug}`);
|
|
4585
|
+
}
|
|
4286
4586
|
}
|
|
4287
4587
|
}
|
|
4288
4588
|
}
|
|
@@ -4360,7 +4660,7 @@ async function pull(args) {
|
|
|
4360
4660
|
}
|
|
4361
4661
|
if (!type || type === "skills") {
|
|
4362
4662
|
console.log(pc2.bold(" Skills:"));
|
|
4363
|
-
await pullSkills(api, platformDir, name || void 0, projectId, appName);
|
|
4663
|
+
await pullSkills(api, platformDir, name || void 0, projectId, appName, checkedLocalSkills);
|
|
4364
4664
|
console.log();
|
|
4365
4665
|
}
|
|
4366
4666
|
if (!type || type === "agents") {
|
|
@@ -4517,7 +4817,7 @@ ${pc2.dim("Usage:")}
|
|
|
4517
4817
|
|
|
4518
4818
|
${pc2.dim("Resources:")}
|
|
4519
4819
|
agents/<name> Diff agent (system_prompt, policy_script)
|
|
4520
|
-
skills/<slug> Diff project
|
|
4820
|
+
skills/<slug> Diff project SKILL.md and resource paths
|
|
4521
4821
|
automations/<name> Diff automation code
|
|
4522
4822
|
hooks/<name> Diff hook script
|
|
4523
4823
|
|
|
@@ -4597,11 +4897,7 @@ async function diff(args) {
|
|
|
4597
4897
|
const promptChanged = (remote.system_prompt || "").trim() !== local.systemPrompt.trim();
|
|
4598
4898
|
const policyChanged = (remote.policy_script || "").trim() !== (local.policyScript || "").trim();
|
|
4599
4899
|
const thinkingLevelChanged = local.agent.thinking_level !== void 0 && (remote.thinking_level || "") !== local.agent.thinking_level;
|
|
4600
|
-
|
|
4601
|
-
if (userTagsChanged) {
|
|
4602
|
-
console.log(` allowed_user_tags: ${pc2.red(JSON.stringify(remote.allowed_user_tags ?? []))} \u2192 ${pc2.green(JSON.stringify(local.agent.allowed_user_tags))}`);
|
|
4603
|
-
}
|
|
4604
|
-
if (!promptChanged && !policyChanged && !thinkingLevelChanged && !userTagsChanged && remote.name === local.agent.name && (remote.description || "") === (local.agent.description || "") && (remote.model || "") === (local.agent.model || "") && (remote.idle_reap_timeout_seconds ?? 0) === (local.agent.idle_reap_timeout_seconds ?? 0) && (remote.policy_enabled || false) === (local.agent.policy_enabled || false)) {
|
|
4900
|
+
if (!promptChanged && !policyChanged && !thinkingLevelChanged && remote.name === local.agent.name && (remote.description || "") === (local.agent.description || "") && (remote.model || "") === (local.agent.model || "") && (remote.idle_reap_timeout_seconds ?? 0) === (local.agent.idle_reap_timeout_seconds ?? 0) && (remote.policy_enabled || false) === (local.agent.policy_enabled || false)) {
|
|
4605
4901
|
console.log(pc2.green(` \u2713 No changes`));
|
|
4606
4902
|
} else {
|
|
4607
4903
|
if (promptChanged) renderFullDiff("system_prompt.md", remote.system_prompt || "", local.systemPrompt);
|
|
@@ -4697,16 +4993,22 @@ async function diff(args) {
|
|
|
4697
4993
|
console.log(` summary: ${pc2.red(remote.summary || "(empty)")} \u2192 ${pc2.green(local.summary)}`);
|
|
4698
4994
|
}
|
|
4699
4995
|
const remoteMarkdown = skillMarkdown({
|
|
4700
|
-
slug: localProjectSkillSlug(appName, remote.slug),
|
|
4701
4996
|
name: remote.name,
|
|
4702
4997
|
summary: remote.summary || "",
|
|
4703
4998
|
instructions: remote.instructions || ""
|
|
4704
4999
|
});
|
|
4705
5000
|
const localMarkdown = skillMarkdown(local);
|
|
4706
|
-
|
|
5001
|
+
const resourceChanges = compareSkillResources(local.resources, remote.resources ?? []);
|
|
5002
|
+
if (remoteMarkdown === localMarkdown && skillResourceChangesEmpty(resourceChanges)) {
|
|
4707
5003
|
console.log(pc2.green(" \u2713 No changes"));
|
|
4708
5004
|
} else {
|
|
4709
|
-
renderFullDiff("SKILL.md", remoteMarkdown, localMarkdown);
|
|
5005
|
+
if (remoteMarkdown !== localMarkdown) renderFullDiff("SKILL.md", remoteMarkdown, localMarkdown);
|
|
5006
|
+
if (!skillResourceChangesEmpty(resourceChanges)) {
|
|
5007
|
+
console.log(pc2.bold(" Resources:"));
|
|
5008
|
+
for (const path of resourceChanges.added) console.log(pc2.green(` + ${path}`));
|
|
5009
|
+
for (const path of resourceChanges.replaced) console.log(pc2.yellow(` ~ ${path}`));
|
|
5010
|
+
for (const path of resourceChanges.deleted) console.log(pc2.red(` - ${path}`));
|
|
5011
|
+
}
|
|
4710
5012
|
}
|
|
4711
5013
|
} else {
|
|
4712
5014
|
console.log(pc2.red(` Diff not supported for "${type}" \u2014 use agents, skills, automations, or hooks`));
|
|
@@ -4720,11 +5022,11 @@ export {
|
|
|
4720
5022
|
applyAutomations,
|
|
4721
5023
|
applyCollections,
|
|
4722
5024
|
applySkills,
|
|
5025
|
+
compareSkillResources,
|
|
4723
5026
|
convertCollectionToApiFormat,
|
|
4724
5027
|
destroy,
|
|
4725
5028
|
diff,
|
|
4726
5029
|
list,
|
|
4727
|
-
loadLocalAgents,
|
|
4728
5030
|
loadLocalCollections,
|
|
4729
5031
|
loadLocalSkills,
|
|
4730
5032
|
localProjectSkillSlug,
|
|
@@ -4734,10 +5036,10 @@ export {
|
|
|
4734
5036
|
planSkills,
|
|
4735
5037
|
projectSkillRefCandidates,
|
|
4736
5038
|
pull,
|
|
4737
|
-
pullAgents,
|
|
4738
5039
|
pullCollections,
|
|
4739
5040
|
pullSkills,
|
|
4740
5041
|
resolveAgentSkillRefs,
|
|
5042
|
+
resolveProjectSkillLocalSlug,
|
|
4741
5043
|
scopedProjectSkillSlug,
|
|
4742
5044
|
show
|
|
4743
5045
|
};
|