@lumerahq/cli 0.30.0-dev.0 → 0.30.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-YEOQ7YN6.js").then((m) => m.plan(args.slice(1)));
243
+ await import("./resources-5YWEX6DG.js").then((m) => m.plan(args.slice(1)));
244
244
  break;
245
245
  case "apply":
246
- await import("./resources-YEOQ7YN6.js").then((m) => m.apply(args.slice(1)));
246
+ await import("./resources-5YWEX6DG.js").then((m) => m.apply(args.slice(1)));
247
247
  break;
248
248
  case "pull":
249
- await import("./resources-YEOQ7YN6.js").then((m) => m.pull(args.slice(1)));
249
+ await import("./resources-5YWEX6DG.js").then((m) => m.pull(args.slice(1)));
250
250
  break;
251
251
  case "destroy":
252
- await import("./resources-YEOQ7YN6.js").then((m) => m.destroy(args.slice(1)));
252
+ await import("./resources-5YWEX6DG.js").then((m) => m.destroy(args.slice(1)));
253
253
  break;
254
254
  case "list":
255
- await import("./resources-YEOQ7YN6.js").then((m) => m.list(args.slice(1)));
255
+ await import("./resources-5YWEX6DG.js").then((m) => m.list(args.slice(1)));
256
256
  break;
257
257
  case "show":
258
- await import("./resources-YEOQ7YN6.js").then((m) => m.show(args.slice(1)));
258
+ await import("./resources-5YWEX6DG.js").then((m) => m.show(args.slice(1)));
259
259
  break;
260
260
  case "diff":
261
- await import("./resources-YEOQ7YN6.js").then((m) => m.diff(args.slice(1)));
261
+ await import("./resources-5YWEX6DG.js").then((m) => m.diff(args.slice(1)));
262
262
  break;
263
263
  // Development
264
264
  case "dev":
@@ -2578,7 +2578,7 @@ function remoteSkillsByLocalSlug(remoteSkills, appName) {
2578
2578
  function projectSkillRefCandidates(refs, appName) {
2579
2579
  return [...new Set(refs.flatMap((ref) => appName && SKILL_SLUG_RE.test(ref) ? [scopedProjectSkillSlug(appName, ref), ref] : [ref]))];
2580
2580
  }
2581
- async function resolveAgentSkillRefs(api, refs, projectId, appName, pendingLocalSkillSlugs = /* @__PURE__ */ new Set()) {
2581
+ async function resolveAgentSkillRefs(api, refs, projectId, appName, localSourceSkillSlugs = /* @__PURE__ */ new Set()) {
2582
2582
  const refToId = /* @__PURE__ */ new Map();
2583
2583
  const skillIdToSlug = /* @__PURE__ */ new Map();
2584
2584
  const blockedRefs = /* @__PURE__ */ new Set();
@@ -2605,7 +2605,7 @@ async function resolveAgentSkillRefs(api, refs, projectId, appName, pendingLocal
2605
2605
  let selected = byID.get(ref);
2606
2606
  if (!selected && SKILL_SLUG_RE.test(ref)) {
2607
2607
  selected = projectByLocalSlug.get(ref);
2608
- if (!selected && pendingLocalSkillSlugs.has(ref)) {
2608
+ if (!selected && localSourceSkillSlugs.has(ref)) {
2609
2609
  continue;
2610
2610
  }
2611
2611
  }
@@ -2795,8 +2795,9 @@ function loadLocalAgents(platformDir, filterName, appName) {
2795
2795
  }
2796
2796
  return agents;
2797
2797
  }
2798
- async function planAgents(api, localAgents, projectId, localSkillSlugs = /* @__PURE__ */ new Set(), appName) {
2798
+ async function planAgents(api, localAgents, projectId, localSourceSkillSlugs = /* @__PURE__ */ new Set(), pendingLocalSkillSlugs = /* @__PURE__ */ new Set(), appName) {
2799
2799
  const changes = [];
2800
+ let validationErrors = 0;
2800
2801
  const remoteAgents = await api.listAgents(projectId ? { project_id: projectId } : void 0);
2801
2802
  const remoteByExternalId = new Map(
2802
2803
  remoteAgents.filter((a) => a.external_id && !a.managed).map((a) => [a.external_id, a])
@@ -2814,7 +2815,7 @@ async function planAgents(api, localAgents, projectId, localSkillSlugs = /* @__P
2814
2815
  outsideProjectRefs: /* @__PURE__ */ new Set()
2815
2816
  };
2816
2817
  try {
2817
- resolution = await resolveAgentSkillRefs(api, planRefs, projectId, appName, localSkillSlugs);
2818
+ resolution = await resolveAgentSkillRefs(api, planRefs, projectId, appName, localSourceSkillSlugs);
2818
2819
  } catch (e) {
2819
2820
  console.log(pc2.yellow(` \u26A0 Could not resolve skills for plan: ${e}`));
2820
2821
  }
@@ -2824,10 +2825,17 @@ async function planAgents(api, localAgents, projectId, localSkillSlugs = /* @__P
2824
2825
  for (const ref of agent.skills ?? []) {
2825
2826
  if (outsideProjectRefs.has(ref)) {
2826
2827
  console.log(pc2.red(` \u2717 Skill "${ref}" belongs to another project \u2014 apply will fail for this agent`));
2828
+ validationErrors++;
2827
2829
  } else if (blockedRefs.has(ref)) {
2828
2830
  console.log(pc2.red(` \u2717 Skill "${ref}" is a Lumera platform skill, only available to Lumera-built agents \u2014 apply will fail for this agent`));
2829
- } else if (!refToId.has(ref) && !localSkillSlugs.has(ref)) {
2830
- console.log(pc2.red(` \u2717 Skill "${ref}" not found \u2014 apply will fail for this agent`));
2831
+ validationErrors++;
2832
+ } else if (!refToId.has(ref) && !pendingLocalSkillSlugs.has(ref)) {
2833
+ if (localSourceSkillSlugs.has(ref)) {
2834
+ console.log(pc2.red(` \u2717 Skill "${ref}" exists in project source but is not deployed \u2014 run 'lumera apply skills' first or use 'lumera apply'`));
2835
+ } else {
2836
+ console.log(pc2.red(` \u2717 Skill "${ref}" not found \u2014 apply will fail for this agent`));
2837
+ }
2838
+ validationErrors++;
2831
2839
  }
2832
2840
  }
2833
2841
  if (!remote) {
@@ -2844,14 +2852,14 @@ async function planAgents(api, localAgents, projectId, localSkillSlugs = /* @__P
2844
2852
  if ((remote.policy_enabled || false) !== (agent.policy_enabled || false)) diffs.push("policy_enabled");
2845
2853
  if ((remote.policy_description || "") !== (agent.policy_description || "")) diffs.push("policy_description");
2846
2854
  const localSkillIds = (agent.skills || []).map((s) => refToId.get(s)).filter((id) => !!id).sort();
2847
- const pendingLocalSkillSlugs = (agent.skills || []).filter((ref) => localSkillSlugs.has(ref) && !refToId.has(ref));
2855
+ const pendingSkillSlugs = (agent.skills || []).filter((ref) => pendingLocalSkillSlugs.has(ref) && !refToId.has(ref));
2848
2856
  const remoteSkillIds = [...remote.skill_ids || []].sort();
2849
- if (localSkillIds.join(",") !== remoteSkillIds.join(",") || pendingLocalSkillSlugs.length > 0) {
2857
+ if (localSkillIds.join(",") !== remoteSkillIds.join(",") || pendingSkillSlugs.length > 0) {
2850
2858
  const addedSlugs = localSkillIds.filter((id) => !remoteSkillIds.includes(id)).map((id) => skillIdToSlug.get(id) || id);
2851
2859
  const removedSlugs = remoteSkillIds.filter((id) => !localSkillIds.includes(id)).map((id) => skillIdToSlug.get(id) || id);
2852
2860
  const parts = [];
2853
2861
  if (addedSlugs.length) parts.push(`+${addedSlugs.join(", +")}`);
2854
- if (pendingLocalSkillSlugs.length) parts.push(`+${pendingLocalSkillSlugs.join(", +")}`);
2862
+ if (pendingSkillSlugs.length) parts.push(`+${pendingSkillSlugs.join(", +")}`);
2855
2863
  if (removedSlugs.length) parts.push(`-${removedSlugs.join(", -")}`);
2856
2864
  diffs.push(`skills (${parts.join(", ")})`);
2857
2865
  }
@@ -2867,14 +2875,14 @@ async function planAgents(api, localAgents, projectId, localSkillSlugs = /* @__P
2867
2875
  }
2868
2876
  }
2869
2877
  }
2870
- return changes;
2878
+ return { changes, validationErrors };
2871
2879
  }
2872
2880
  var TAG_AVAILABLE_TO_CUSTOM_AGENTS = "available_to_custom_agents";
2873
2881
  function isAvailableToCustomAgents(skill) {
2874
2882
  if (!skill.managed) return true;
2875
2883
  return (skill.tags ?? []).includes(TAG_AVAILABLE_TO_CUSTOM_AGENTS);
2876
2884
  }
2877
- async function applyAgents(api, localAgents, projectId, localSkillSlugs = /* @__PURE__ */ new Set(), appName) {
2885
+ async function applyAgents(api, localAgents, projectId, localSourceSkillSlugs = /* @__PURE__ */ new Set(), appName) {
2878
2886
  let errors = 0;
2879
2887
  const remoteAgents = await api.listAgents(projectId ? { project_id: projectId } : void 0);
2880
2888
  const remoteByExternalId = new Map(
@@ -2888,7 +2896,7 @@ async function applyAgents(api, localAgents, projectId, localSkillSlugs = /* @__
2888
2896
  outsideProjectRefs: /* @__PURE__ */ new Set()
2889
2897
  };
2890
2898
  try {
2891
- resolution = await resolveAgentSkillRefs(api, refs, projectId, appName, localSkillSlugs);
2899
+ resolution = await resolveAgentSkillRefs(api, refs, projectId, appName, localSourceSkillSlugs);
2892
2900
  } catch (e) {
2893
2901
  console.log(pc2.yellow(` \u26A0 Could not resolve skills: ${e}`));
2894
2902
  }
@@ -2898,6 +2906,7 @@ async function applyAgents(api, localAgents, projectId, localSkillSlugs = /* @__
2898
2906
  const skillIds = [];
2899
2907
  const outsideProjectForAgent = [];
2900
2908
  const blockedForAgent = [];
2909
+ const undeployedLocalForAgent = [];
2901
2910
  const notFoundForAgent = [];
2902
2911
  if (agent.skills) {
2903
2912
  for (const ref of agent.skills) {
@@ -2908,12 +2917,14 @@ async function applyAgents(api, localAgents, projectId, localSkillSlugs = /* @__
2908
2917
  outsideProjectForAgent.push(ref);
2909
2918
  } else if (blockedRefs.has(ref)) {
2910
2919
  blockedForAgent.push(ref);
2920
+ } else if (localSourceSkillSlugs.has(ref)) {
2921
+ undeployedLocalForAgent.push(ref);
2911
2922
  } else {
2912
2923
  notFoundForAgent.push(ref);
2913
2924
  }
2914
2925
  }
2915
2926
  }
2916
- if (outsideProjectForAgent.length > 0 || blockedForAgent.length > 0 || notFoundForAgent.length > 0) {
2927
+ if (outsideProjectForAgent.length > 0 || blockedForAgent.length > 0 || undeployedLocalForAgent.length > 0 || notFoundForAgent.length > 0) {
2917
2928
  console.log(pc2.red(" \u2717"), `${agent.name}:`);
2918
2929
  if (outsideProjectForAgent.length > 0) {
2919
2930
  console.log(pc2.red(` skills outside the agent project: ${outsideProjectForAgent.join(", ")}`));
@@ -2921,10 +2932,14 @@ async function applyAgents(api, localAgents, projectId, localSkillSlugs = /* @__
2921
2932
  if (blockedForAgent.length > 0) {
2922
2933
  console.log(pc2.red(` skills not available for custom agents: ${blockedForAgent.join(", ")}`));
2923
2934
  }
2935
+ if (undeployedLocalForAgent.length > 0) {
2936
+ console.log(pc2.red(` locally authored skills not deployed: ${undeployedLocalForAgent.join(", ")}`));
2937
+ console.log(pc2.dim(` run 'lumera apply skills' first, or use 'lumera apply' to deploy skills and agents together`));
2938
+ }
2924
2939
  if (notFoundForAgent.length > 0) {
2925
2940
  console.log(pc2.red(` skills not found: ${notFoundForAgent.join(", ")}`));
2941
+ console.log(pc2.dim(` pick skills from GET /api/skills (tags includes "available_to_custom_agents")`));
2926
2942
  }
2927
- console.log(pc2.dim(` pick skills from GET /api/skills (tags includes "available_to_custom_agents")`));
2928
2943
  errors++;
2929
2944
  continue;
2930
2945
  }
@@ -3720,6 +3735,7 @@ async function plan(args) {
3720
3735
  }
3721
3736
  await syncDeps(projectRoot, { ignorePermissionDenied: true });
3722
3737
  const allChanges = [];
3738
+ let planValidationErrors = 0;
3723
3739
  let collections;
3724
3740
  try {
3725
3741
  const remoteCollections = await api.listCollections();
@@ -3750,15 +3766,18 @@ async function plan(args) {
3750
3766
  }
3751
3767
  }
3752
3768
  const localSkills = !type || type === "skills" ? loadLocalSkills(platformDir, name || void 0) : [];
3753
- const localSkillSlugs = new Set(!type || type === "skills" || type === "agents" ? (localSkills.length > 0 || type !== "agents" ? localSkills : loadLocalSkills(platformDir)).map((skill) => skill.slug) : []);
3769
+ const sourceSkillsForAgentResolution = type === "agents" ? loadLocalSkills(platformDir) : localSkills;
3770
+ const localSourceSkillSlugs = new Set(sourceSkillsForAgentResolution.map((skill) => skill.slug));
3771
+ const pendingLocalSkillSlugs = new Set(localSkills.map((skill) => skill.slug));
3754
3772
  if (localSkills.length > 0) {
3755
3773
  allChanges.push(...await planSkills(api, localSkills, projectId, appName));
3756
3774
  }
3757
3775
  if (!type || type === "agents") {
3758
3776
  const localAgents = loadLocalAgents(platformDir, name || void 0, appName);
3759
3777
  if (localAgents.length > 0) {
3760
- const changes = await planAgents(api, localAgents, projectId, localSkillSlugs, appName);
3761
- allChanges.push(...changes);
3778
+ const result = await planAgents(api, localAgents, projectId, localSourceSkillSlugs, pendingLocalSkillSlugs, appName);
3779
+ allChanges.push(...result.changes);
3780
+ planValidationErrors += result.validationErrors;
3762
3781
  }
3763
3782
  }
3764
3783
  if (!type || type === "mailboxes") {
@@ -3771,7 +3790,14 @@ async function plan(args) {
3771
3790
  const lintWarnings = await safeLint(projectRoot, localAutomations);
3772
3791
  if (allChanges.length === 0) {
3773
3792
  if (jsonOutput) {
3774
- console.log(JSON.stringify({ changes: [], warnings: [], lintWarnings: serializeLintWarnings(lintWarnings, projectRoot) }));
3793
+ console.log(JSON.stringify({ changes: [], warnings: [], validationErrors: planValidationErrors, lintWarnings: serializeLintWarnings(lintWarnings, projectRoot) }));
3794
+ if (planValidationErrors > 0) process.exitCode = 1;
3795
+ return;
3796
+ }
3797
+ if (planValidationErrors > 0) {
3798
+ console.log(pc2.red(` \u2717 Plan has ${planValidationErrors} agent skill validation error${planValidationErrors === 1 ? "" : "s"}.`));
3799
+ console.log();
3800
+ process.exitCode = 1;
3775
3801
  return;
3776
3802
  }
3777
3803
  console.log(pc2.green(" \u2713 No changes detected"));
@@ -3789,7 +3815,8 @@ async function plan(args) {
3789
3815
  }
3790
3816
  }
3791
3817
  }
3792
- console.log(JSON.stringify({ changes: allChanges, warnings, lintWarnings: serializeLintWarnings(lintWarnings, projectRoot) }));
3818
+ console.log(JSON.stringify({ changes: allChanges, warnings, validationErrors: planValidationErrors, lintWarnings: serializeLintWarnings(lintWarnings, projectRoot) }));
3819
+ if (planValidationErrors > 0) process.exitCode = 1;
3793
3820
  return;
3794
3821
  }
3795
3822
  console.log(pc2.bold(" Changes:"));
@@ -3830,7 +3857,12 @@ async function plan(args) {
3830
3857
  }
3831
3858
  console.log();
3832
3859
  safePrintLint(lintWarnings, projectRoot);
3833
- console.log(pc2.dim(` Run 'lumera apply' to apply these changes.`));
3860
+ if (planValidationErrors > 0) {
3861
+ console.log(pc2.red(` \u2717 Fix ${planValidationErrors} agent skill validation error${planValidationErrors === 1 ? "" : "s"} before applying.`));
3862
+ process.exitCode = 1;
3863
+ } else {
3864
+ console.log(pc2.dim(` Run 'lumera apply' to apply these changes.`));
3865
+ }
3834
3866
  console.log();
3835
3867
  }
3836
3868
  async function apply(args) {
@@ -3877,18 +3909,25 @@ async function apply(args) {
3877
3909
  collections = /* @__PURE__ */ new Map();
3878
3910
  }
3879
3911
  const allChanges = [];
3912
+ let planValidationErrors = 0;
3880
3913
  const localCollections = !type || type === "collections" ? loadLocalCollections(platformDir, name || void 0) : [];
3881
3914
  const localAutomations = !type || type === "automations" ? loadLocalAutomations(platformDir, name || void 0, appName) : [];
3882
3915
  const localHooks = !type || type === "hooks" ? loadLocalHooks(platformDir, name || void 0, appName) : [];
3883
3916
  const localSkills = !type || type === "skills" ? loadLocalSkills(platformDir, name || void 0) : [];
3884
- const localSkillSlugs = new Set(!type || type === "skills" || type === "agents" ? (localSkills.length > 0 || type !== "agents" ? localSkills : loadLocalSkills(platformDir)).map((skill) => skill.slug) : []);
3917
+ const sourceSkillsForAgentResolution = type === "agents" ? loadLocalSkills(platformDir) : localSkills;
3918
+ const localSourceSkillSlugs = new Set(sourceSkillsForAgentResolution.map((skill) => skill.slug));
3919
+ const pendingLocalSkillSlugs = new Set(localSkills.map((skill) => skill.slug));
3885
3920
  const localAgents = !type || type === "agents" ? loadLocalAgents(platformDir, name || void 0, appName) : [];
3886
3921
  const localMailboxes = !type || type === "mailboxes" ? loadLocalMailboxes(platformDir, name || void 0) : [];
3887
3922
  if (localCollections.length > 0) allChanges.push(...await planCollections(api, localCollections, appName, projectId));
3888
3923
  if (localAutomations.length > 0) allChanges.push(...await planAutomations(api, localAutomations, projectId));
3889
3924
  if (localHooks.length > 0) allChanges.push(...await planHooks(api, localHooks, collections, projectId));
3890
3925
  if (localSkills.length > 0) allChanges.push(...await planSkills(api, localSkills, projectId, appName));
3891
- if (localAgents.length > 0) allChanges.push(...await planAgents(api, localAgents, projectId, localSkillSlugs, appName));
3926
+ if (localAgents.length > 0) {
3927
+ const result = await planAgents(api, localAgents, projectId, localSourceSkillSlugs, pendingLocalSkillSlugs, appName);
3928
+ allChanges.push(...result.changes);
3929
+ planValidationErrors += result.validationErrors;
3930
+ }
3892
3931
  if (localMailboxes.length > 0) allChanges.push(...await planMailboxes(api, localMailboxes));
3893
3932
  if (name) {
3894
3933
  const hasLocal = localCollections.length > 0 || localAutomations.length > 0 || localHooks.length > 0 || localSkills.length > 0 || localAgents.length > 0 || localMailboxes.length > 0;
@@ -3898,6 +3937,12 @@ async function apply(args) {
3898
3937
  process.exit(1);
3899
3938
  }
3900
3939
  }
3940
+ if (planValidationErrors > 0) {
3941
+ console.log();
3942
+ console.log(pc2.red(` \u2717 Apply aborted: fix ${planValidationErrors} agent skill validation error${planValidationErrors === 1 ? "" : "s"} first.`));
3943
+ console.log();
3944
+ process.exit(1);
3945
+ }
3901
3946
  let willDeployApp = false;
3902
3947
  if (!type && !skipApp) {
3903
3948
  try {
@@ -3985,7 +4030,7 @@ async function apply(args) {
3985
4030
  }
3986
4031
  if (localAgents.length > 0) {
3987
4032
  console.log(pc2.bold(" Agents:"));
3988
- totalErrors += await applyAgents(api, localAgents, projectId, localSkillSlugs, appName);
4033
+ totalErrors += await applyAgents(api, localAgents, projectId, localSourceSkillSlugs, appName);
3989
4034
  console.log();
3990
4035
  }
3991
4036
  if (localMailboxes.length > 0) {
@@ -4050,11 +4095,13 @@ async function pull(args) {
4050
4095
  if (!type || type === "agents") {
4051
4096
  const localAgents = loadLocalAgents(platformDir, name || void 0, appName);
4052
4097
  if (localAgents.length > 0) {
4053
- const changes = await planAgents(
4098
+ const localSkillSlugs = new Set(loadLocalSkills(platformDir).map((skill) => skill.slug));
4099
+ const { changes } = await planAgents(
4054
4100
  api,
4055
4101
  localAgents,
4056
4102
  projectId,
4057
- new Set(loadLocalSkills(platformDir).map((skill) => skill.slug)),
4103
+ localSkillSlugs,
4104
+ localSkillSlugs,
4058
4105
  appName
4059
4106
  );
4060
4107
  for (const c of changes) {
@@ -4482,6 +4529,7 @@ export {
4482
4529
  loadLocalSkills,
4483
4530
  localProjectSkillSlug,
4484
4531
  plan,
4532
+ planAgents,
4485
4533
  planCollections,
4486
4534
  planSkills,
4487
4535
  projectSkillRefCandidates,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumerahq/cli",
3
- "version": "0.30.0-dev.0",
3
+ "version": "0.30.0",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {