@lumerahq/cli 0.14.0 → 0.15.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
@@ -212,25 +212,25 @@ async function main() {
212
212
  switch (command) {
213
213
  // Resource commands
214
214
  case "plan":
215
- await import("./resources-QKEUX3C3.js").then((m) => m.plan(args.slice(1)));
215
+ await import("./resources-BFT7V6UR.js").then((m) => m.plan(args.slice(1)));
216
216
  break;
217
217
  case "apply":
218
- await import("./resources-QKEUX3C3.js").then((m) => m.apply(args.slice(1)));
218
+ await import("./resources-BFT7V6UR.js").then((m) => m.apply(args.slice(1)));
219
219
  break;
220
220
  case "pull":
221
- await import("./resources-QKEUX3C3.js").then((m) => m.pull(args.slice(1)));
221
+ await import("./resources-BFT7V6UR.js").then((m) => m.pull(args.slice(1)));
222
222
  break;
223
223
  case "destroy":
224
- await import("./resources-QKEUX3C3.js").then((m) => m.destroy(args.slice(1)));
224
+ await import("./resources-BFT7V6UR.js").then((m) => m.destroy(args.slice(1)));
225
225
  break;
226
226
  case "list":
227
- await import("./resources-QKEUX3C3.js").then((m) => m.list(args.slice(1)));
227
+ await import("./resources-BFT7V6UR.js").then((m) => m.list(args.slice(1)));
228
228
  break;
229
229
  case "show":
230
- await import("./resources-QKEUX3C3.js").then((m) => m.show(args.slice(1)));
230
+ await import("./resources-BFT7V6UR.js").then((m) => m.show(args.slice(1)));
231
231
  break;
232
232
  case "diff":
233
- await import("./resources-QKEUX3C3.js").then((m) => m.diff(args.slice(1)));
233
+ await import("./resources-BFT7V6UR.js").then((m) => m.diff(args.slice(1)));
234
234
  break;
235
235
  // Development
236
236
  case "dev":
@@ -125,12 +125,14 @@ ${pc.dim("Resources:")}
125
125
  app Deploy the frontend app
126
126
 
127
127
  ${pc.dim("Options:")}
128
+ --yes, -y Skip confirmation prompt (for CI/CD)
128
129
  --skip-build Skip build step when applying app
129
130
 
130
131
  ${pc.dim("Examples:")}
131
- lumera apply # Apply everything
132
+ lumera apply # Apply everything (shows plan, asks to confirm)
132
133
  lumera apply collections # Apply all collections
133
134
  lumera apply collections/users # Apply single collection
135
+ lumera apply agents -y # Apply agents without confirmation
134
136
  lumera apply app # Deploy frontend
135
137
  lumera apply app --skip-build # Deploy without rebuilding
136
138
  `);
@@ -482,6 +484,30 @@ function mapFieldType(type) {
482
484
  };
483
485
  return typeMap[type] || type;
484
486
  }
487
+ function fieldsDiffer(local, remote) {
488
+ if (mapFieldType(local.type) !== remote.type) return true;
489
+ if ((local.required || false) !== (remote.required || false)) return true;
490
+ const opts = remote.options || {};
491
+ if (local.type === "select") {
492
+ const localValues = [...local.values || []].sort();
493
+ const remoteValues = [...opts.values || []].sort();
494
+ if (localValues.join(",") !== remoteValues.join(",")) return true;
495
+ const localMultiple = local.multiple || false;
496
+ const remoteMaxSelect = opts.maxSelect || 1;
497
+ if (localMultiple && remoteMaxSelect <= 1) return true;
498
+ if (!localMultiple && remoteMaxSelect > 1) return true;
499
+ }
500
+ if (local.type === "relation") {
501
+ if (local.collection && local.collection !== opts.collectionId) return true;
502
+ const localMultiple = local.multiple || false;
503
+ const remoteMaxSelect = opts.maxSelect || 1;
504
+ if (localMultiple && remoteMaxSelect <= 1) return true;
505
+ if (!localMultiple && remoteMaxSelect > 1) return true;
506
+ }
507
+ if (local.min !== void 0 && local.min !== opts.min) return true;
508
+ if (local.max !== void 0 && local.max !== opts.max) return true;
509
+ return false;
510
+ }
485
511
  async function planCollections(api, localCollections) {
486
512
  const changes = [];
487
513
  const remoteCollections = await api.listCollections();
@@ -509,10 +535,20 @@ async function planCollections(api, localCollections) {
509
535
  const remoteFieldMap = new Map(remote.schema.map((f) => [f.name, f]));
510
536
  const added = [...localFieldNames].filter((n) => !remoteFieldNames.has(n));
511
537
  const removed = [...remoteFieldNames].filter((n) => !localFieldNames.has(n));
512
- if (added.length > 0 || removed.length > 0) {
538
+ const modified = [];
539
+ for (const name of localFieldNames) {
540
+ if (!remoteFieldNames.has(name)) continue;
541
+ const localField = localFieldMap.get(name);
542
+ const remoteField = remoteFieldMap.get(name);
543
+ if (fieldsDiffer(localField, remoteField)) {
544
+ modified.push(name);
545
+ }
546
+ }
547
+ if (added.length > 0 || removed.length > 0 || modified.length > 0) {
513
548
  const details = [];
514
549
  if (added.length > 0) details.push(`+${added.length} field${added.length > 1 ? "s" : ""}`);
515
550
  if (removed.length > 0) details.push(`-${removed.length} field${removed.length > 1 ? "s" : ""}`);
551
+ if (modified.length > 0) details.push(`~${modified.length} field${modified.length > 1 ? "s" : ""} (${modified.join(", ")})`);
516
552
  const fieldDetails = [];
517
553
  for (const name of added) {
518
554
  const f = localFieldMap.get(name);
@@ -967,6 +1003,17 @@ async function planAgents(api, localAgents) {
967
1003
  const remoteByExternalId = new Map(
968
1004
  remoteAgents.filter((a) => a.external_id && !a.managed).map((a) => [a.external_id, a])
969
1005
  );
1006
+ let skillSlugToId = /* @__PURE__ */ new Map();
1007
+ let skillIdToSlug = /* @__PURE__ */ new Map();
1008
+ const hasSkillRefs = localAgents.some((a) => a.agent.skills && a.agent.skills.length > 0) || remoteAgents.some((a) => a.skill_ids && a.skill_ids.length > 0);
1009
+ if (hasSkillRefs) {
1010
+ try {
1011
+ const skills = await api.listAgentSkills();
1012
+ skillSlugToId = new Map(skills.map((s) => [s.slug, s.id]));
1013
+ skillIdToSlug = new Map(skills.map((s) => [s.id, s.slug]));
1014
+ } catch {
1015
+ }
1016
+ }
970
1017
  for (const { agent, systemPrompt, policyScript } of localAgents) {
971
1018
  const remote = remoteByExternalId.get(agent.external_id);
972
1019
  if (!remote) {
@@ -979,6 +1026,16 @@ async function planAgents(api, localAgents) {
979
1026
  if ((remote.model || "") !== (agent.model || "")) diffs.push("model");
980
1027
  if ((remote.policy_script || "").trim() !== (policyScript || "").trim()) diffs.push("policy_script");
981
1028
  if ((remote.policy_enabled || false) !== (agent.policy_enabled || false)) diffs.push("policy_enabled");
1029
+ const localSkillIds = (agent.skills || []).map((s) => skillSlugToId.get(s) || s).sort();
1030
+ const remoteSkillIds = [...remote.skill_ids || []].sort();
1031
+ if (localSkillIds.join(",") !== remoteSkillIds.join(",")) {
1032
+ const addedSlugs = localSkillIds.filter((id) => !remoteSkillIds.includes(id)).map((id) => skillIdToSlug.get(id) || id);
1033
+ const removedSlugs = remoteSkillIds.filter((id) => !localSkillIds.includes(id)).map((id) => skillIdToSlug.get(id) || id);
1034
+ const parts = [];
1035
+ if (addedSlugs.length) parts.push(`+${addedSlugs.join(", +")}`);
1036
+ if (removedSlugs.length) parts.push(`-${removedSlugs.join(", -")}`);
1037
+ diffs.push(`skills (${parts.join(", ")})`);
1038
+ }
982
1039
  if (diffs.length > 0) {
983
1040
  const textDiffs = [];
984
1041
  if (diffs.includes("system_prompt")) {
@@ -1739,11 +1796,12 @@ async function apply(args) {
1739
1796
  const platformDir = getPlatformDir();
1740
1797
  const api = createApiClient();
1741
1798
  const appName = getAppName(projectRoot);
1742
- const { type, name } = parseResource(args[0]);
1743
- console.log();
1744
- console.log(pc.cyan(pc.bold(" Apply")));
1745
- console.log();
1799
+ const { type, name } = parseResource(args.filter((a) => a !== "--yes" && a !== "-y")[0]);
1800
+ const autoConfirm = args.includes("--yes") || args.includes("-y") || !!process.env.CI;
1746
1801
  if (type === "app") {
1802
+ console.log();
1803
+ console.log(pc.cyan(pc.bold(" Apply")));
1804
+ console.log();
1747
1805
  console.log(pc.bold(" App:"));
1748
1806
  await applyApp(args);
1749
1807
  console.log();
@@ -1752,77 +1810,131 @@ async function apply(args) {
1752
1810
  return;
1753
1811
  }
1754
1812
  let collections;
1755
- let totalErrors = 0;
1756
- if (!type || type === "collections") {
1757
- const localCollections = loadLocalCollections(platformDir, name || void 0);
1758
- if (localCollections.length > 0) {
1759
- console.log(pc.bold(" Collections:"));
1760
- totalErrors += await applyCollections(api, localCollections);
1761
- console.log();
1762
- } else if (name) {
1763
- console.log(pc.red(` Collection "${name}" not found locally`));
1764
- process.exit(1);
1765
- }
1766
- }
1767
1813
  try {
1768
1814
  const remoteCollections = await api.listCollections();
1769
1815
  collections = new Map(remoteCollections.map((c) => [c.name, c.id]));
1770
- for (const c of remoteCollections) {
1771
- collections.set(c.id, c.id);
1772
- }
1816
+ for (const c of remoteCollections) collections.set(c.id, c.id);
1773
1817
  } catch {
1774
1818
  collections = /* @__PURE__ */ new Map();
1775
1819
  }
1776
- if (!type || type === "automations") {
1777
- const localAutomations = loadLocalAutomations(platformDir, name || void 0, appName);
1778
- if (localAutomations.length > 0) {
1779
- console.log(pc.bold(" Automations:"));
1780
- totalErrors += await applyAutomations(api, localAutomations);
1781
- console.log();
1782
- } else if (name) {
1783
- console.log(pc.red(` Automation "${name}" not found locally`));
1784
- process.exit(1);
1785
- }
1786
- }
1787
- if (!type || type === "hooks") {
1788
- const localHooks = loadLocalHooks(platformDir, name || void 0, appName);
1789
- if (localHooks.length > 0) {
1790
- console.log(pc.bold(" Hooks:"));
1791
- totalErrors += await applyHooks(api, localHooks, collections);
1792
- console.log();
1793
- } else if (name) {
1794
- console.log(pc.red(` Hook "${name}" not found locally`));
1795
- process.exit(1);
1796
- }
1797
- }
1798
- if (!type || type === "agents") {
1799
- const localAgents = loadLocalAgents(platformDir, name || void 0, appName);
1800
- if (localAgents.length > 0) {
1801
- console.log(pc.bold(" Agents:"));
1802
- totalErrors += await applyAgents(api, localAgents);
1820
+ const allChanges = [];
1821
+ const localCollections = !type || type === "collections" ? loadLocalCollections(platformDir, name || void 0) : [];
1822
+ const localAutomations = !type || type === "automations" ? loadLocalAutomations(platformDir, name || void 0, appName) : [];
1823
+ const localHooks = !type || type === "hooks" ? loadLocalHooks(platformDir, name || void 0, appName) : [];
1824
+ const localAgents = !type || type === "agents" ? loadLocalAgents(platformDir, name || void 0, appName) : [];
1825
+ if (localCollections.length > 0) allChanges.push(...await planCollections(api, localCollections));
1826
+ if (localAutomations.length > 0) allChanges.push(...await planAutomations(api, localAutomations));
1827
+ if (localHooks.length > 0) allChanges.push(...await planHooks(api, localHooks, collections));
1828
+ if (localAgents.length > 0) allChanges.push(...await planAgents(api, localAgents));
1829
+ if (name) {
1830
+ const hasLocal = localCollections.length > 0 || localAutomations.length > 0 || localHooks.length > 0 || localAgents.length > 0;
1831
+ if (!hasLocal) {
1803
1832
  console.log();
1804
- } else if (name) {
1805
- console.log(pc.red(` Agent "${name}" not found locally`));
1833
+ console.log(pc.red(` Resource "${name}" not found locally`));
1806
1834
  process.exit(1);
1807
1835
  }
1808
1836
  }
1837
+ let willDeployApp = false;
1809
1838
  if (!type) {
1810
1839
  try {
1811
- const projectRoot2 = findProjectRoot();
1812
- if (existsSync(join(projectRoot2, "dist")) || existsSync(join(projectRoot2, "src"))) {
1813
- console.log(pc.bold(" App:"));
1814
- await applyApp(args);
1815
- console.log();
1840
+ if (existsSync(join(projectRoot, "dist")) || existsSync(join(projectRoot, "src"))) {
1841
+ willDeployApp = true;
1816
1842
  }
1817
1843
  } catch {
1818
1844
  }
1819
1845
  }
1846
+ if (allChanges.length === 0 && !willDeployApp) {
1847
+ console.log();
1848
+ console.log(pc.green(" \u2713 Nothing to apply \u2014 local and remote are in sync."));
1849
+ console.log();
1850
+ return;
1851
+ }
1852
+ console.log();
1853
+ console.log(pc.cyan(pc.bold(" Apply")));
1854
+ console.log();
1855
+ const creates = allChanges.filter((c) => c.type === "create");
1856
+ const updates = allChanges.filter((c) => c.type === "update");
1857
+ if (allChanges.length > 0) {
1858
+ console.log(pc.bold(" Plan:"));
1859
+ for (const change of allChanges) {
1860
+ const icon = change.type === "create" ? "+" : "~";
1861
+ const color = change.type === "create" ? pc.green : pc.yellow;
1862
+ const details = change.details ? ` (${change.details})` : "";
1863
+ console.log(` ${color(icon)} ${change.resource}: ${change.name}${pc.dim(details)}`);
1864
+ }
1865
+ if (willDeployApp) console.log(` ${pc.blue("\u25CF")} app: frontend build + deploy`);
1866
+ console.log();
1867
+ const parts = [];
1868
+ if (creates.length > 0) parts.push(pc.green(`${creates.length} create`));
1869
+ if (updates.length > 0) parts.push(pc.yellow(`${updates.length} update`));
1870
+ if (willDeployApp) parts.push(pc.blue("1 app deploy"));
1871
+ console.log(` ${parts.join(", ")}`);
1872
+ console.log();
1873
+ } else if (willDeployApp) {
1874
+ console.log(pc.dim(" No infrastructure changes \u2014 deploying app only."));
1875
+ console.log();
1876
+ }
1877
+ if (!autoConfirm && allChanges.length > 0) {
1878
+ const { confirm } = await prompts({
1879
+ type: "confirm",
1880
+ name: "confirm",
1881
+ message: " Proceed with apply?",
1882
+ initial: true
1883
+ });
1884
+ if (!confirm) {
1885
+ console.log(pc.dim(" Cancelled."));
1886
+ console.log();
1887
+ return;
1888
+ }
1889
+ console.log();
1890
+ }
1891
+ let totalErrors = 0;
1892
+ let totalCreated = 0;
1893
+ let totalUpdated = 0;
1894
+ let totalSkipped = 0;
1895
+ if (localCollections.length > 0) {
1896
+ console.log(pc.bold(" Collections:"));
1897
+ totalErrors += await applyCollections(api, localCollections);
1898
+ console.log();
1899
+ }
1900
+ try {
1901
+ const remoteCollections = await api.listCollections();
1902
+ collections = new Map(remoteCollections.map((c) => [c.name, c.id]));
1903
+ for (const c of remoteCollections) collections.set(c.id, c.id);
1904
+ } catch {
1905
+ }
1906
+ if (localAutomations.length > 0) {
1907
+ console.log(pc.bold(" Automations:"));
1908
+ totalErrors += await applyAutomations(api, localAutomations);
1909
+ console.log();
1910
+ }
1911
+ if (localHooks.length > 0) {
1912
+ console.log(pc.bold(" Hooks:"));
1913
+ totalErrors += await applyHooks(api, localHooks, collections);
1914
+ console.log();
1915
+ }
1916
+ if (localAgents.length > 0) {
1917
+ console.log(pc.bold(" Agents:"));
1918
+ totalErrors += await applyAgents(api, localAgents);
1919
+ console.log();
1920
+ }
1921
+ if (willDeployApp) {
1922
+ console.log(pc.bold(" App:"));
1923
+ await applyApp(args);
1924
+ console.log();
1925
+ }
1926
+ totalCreated = creates.length;
1927
+ totalUpdated = updates.length;
1820
1928
  if (totalErrors > 0) {
1821
- console.log(pc.red(` Failed with ${totalErrors} error${totalErrors > 1 ? "s" : ""}.`));
1929
+ console.log(pc.red(` \u2717 ${totalErrors} error${totalErrors > 1 ? "s" : ""} during apply.`));
1822
1930
  console.log();
1823
1931
  process.exit(1);
1824
1932
  }
1825
- console.log(pc.green(" Done!"));
1933
+ const summary = [];
1934
+ if (totalCreated > 0) summary.push(pc.green(`${totalCreated} created`));
1935
+ if (totalUpdated > 0) summary.push(pc.yellow(`${totalUpdated} updated`));
1936
+ if (willDeployApp) summary.push(pc.blue("app deployed"));
1937
+ console.log(pc.green(" \u2713 Done!") + (summary.length > 0 ? ` ${pc.dim("\u2014")} ${summary.join(", ")}` : ""));
1826
1938
  console.log();
1827
1939
  }
1828
1940
  async function pull(args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumerahq/cli",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {