@lumerahq/cli 0.14.0 → 0.15.1

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.
@@ -1,12 +1,13 @@
1
1
  import {
2
+ getBaseUrl,
2
3
  getCredentials,
3
- getTokenSource
4
- } from "./chunk-NDLYGKS6.js";
5
- import {
6
- getBaseUrl
7
- } from "./chunk-D2BLSEGR.js";
4
+ getTokenSource,
5
+ init_auth
6
+ } from "./chunk-WZMAQXDY.js";
7
+ import "./chunk-PNKVD2UK.js";
8
8
 
9
9
  // src/commands/auth.ts
10
+ init_auth();
10
11
  import pc from "picocolors";
11
12
  import { createServer } from "http";
12
13
  import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
@@ -204,6 +205,7 @@ async function login(args) {
204
205
  const userInfo = await fetchUserInfo(result.token);
205
206
  const credentials = {
206
207
  token: result.token,
208
+ base_url: baseUrl,
207
209
  user: userInfo.email,
208
210
  company: userInfo.company,
209
211
  created: (/* @__PURE__ */ new Date()).toISOString()
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getBaseUrl
3
- } from "./chunk-D2BLSEGR.js";
3
+ } from "./chunk-WZMAQXDY.js";
4
4
 
5
5
  // src/lib/skills.ts
6
6
  import { createHash } from "crypto";
@@ -0,0 +1,26 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __esm = (fn, res) => function __init() {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ };
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ export {
23
+ __esm,
24
+ __export,
25
+ __toCommonJS
26
+ };
@@ -1,8 +1,10 @@
1
1
  import {
2
- getToken
3
- } from "./chunk-NDLYGKS6.js";
2
+ getToken,
3
+ init_auth
4
+ } from "./chunk-WZMAQXDY.js";
4
5
 
5
6
  // src/lib/api.ts
7
+ init_auth();
6
8
  import { readFileSync } from "fs";
7
9
  import { fileURLToPath } from "url";
8
10
  import { dirname, resolve } from "path";
@@ -0,0 +1,163 @@
1
+ import {
2
+ __esm,
3
+ __export,
4
+ __toCommonJS
5
+ } from "./chunk-PNKVD2UK.js";
6
+
7
+ // src/lib/auth.ts
8
+ var auth_exports = {};
9
+ __export(auth_exports, {
10
+ getCredentials: () => getCredentials,
11
+ getToken: () => getToken,
12
+ getTokenSource: () => getTokenSource
13
+ });
14
+ import { existsSync, readFileSync } from "fs";
15
+ import { homedir } from "os";
16
+ import { join } from "path";
17
+ function getLocalCredsPath(cwd = process.cwd()) {
18
+ return join(cwd, ".lumera", "credentials.json");
19
+ }
20
+ function getToken(cwd = process.cwd()) {
21
+ if (process.env.LUMERA_TOKEN) {
22
+ return process.env.LUMERA_TOKEN;
23
+ }
24
+ const localCredsPath = getLocalCredsPath(cwd);
25
+ if (existsSync(localCredsPath)) {
26
+ try {
27
+ const creds = JSON.parse(readFileSync(localCredsPath, "utf-8"));
28
+ if (creds.token) return creds.token;
29
+ } catch {
30
+ }
31
+ }
32
+ if (existsSync(GLOBAL_CREDS_PATH)) {
33
+ try {
34
+ const creds = JSON.parse(readFileSync(GLOBAL_CREDS_PATH, "utf-8"));
35
+ if (creds.token) return creds.token;
36
+ } catch {
37
+ }
38
+ }
39
+ throw new Error(
40
+ "No Lumera token found.\n Run `lumera login` to authenticate, or\n Set LUMERA_TOKEN environment variable"
41
+ );
42
+ }
43
+ function getTokenSource(cwd = process.cwd()) {
44
+ if (process.env.LUMERA_TOKEN) {
45
+ return "environment (LUMERA_TOKEN)";
46
+ }
47
+ const localCredsPath = getLocalCredsPath(cwd);
48
+ if (existsSync(localCredsPath)) {
49
+ try {
50
+ const creds = JSON.parse(readFileSync(localCredsPath, "utf-8"));
51
+ if (creds.token) return `project (${localCredsPath})`;
52
+ } catch {
53
+ }
54
+ }
55
+ if (existsSync(GLOBAL_CREDS_PATH)) {
56
+ try {
57
+ const creds = JSON.parse(readFileSync(GLOBAL_CREDS_PATH, "utf-8"));
58
+ if (creds.token) return `global (${GLOBAL_CREDS_PATH})`;
59
+ } catch {
60
+ }
61
+ }
62
+ return null;
63
+ }
64
+ function getCredentials(cwd = process.cwd()) {
65
+ const localCredsPath = getLocalCredsPath(cwd);
66
+ if (existsSync(localCredsPath)) {
67
+ try {
68
+ const creds = JSON.parse(readFileSync(localCredsPath, "utf-8"));
69
+ if (creds.token) return creds;
70
+ } catch {
71
+ }
72
+ }
73
+ if (existsSync(GLOBAL_CREDS_PATH)) {
74
+ try {
75
+ const creds = JSON.parse(readFileSync(GLOBAL_CREDS_PATH, "utf-8"));
76
+ if (creds.token) return creds;
77
+ } catch {
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+ var GLOBAL_CONFIG_DIR, GLOBAL_CREDS_PATH;
83
+ var init_auth = __esm({
84
+ "src/lib/auth.ts"() {
85
+ "use strict";
86
+ GLOBAL_CONFIG_DIR = join(homedir(), ".config", "lumera");
87
+ GLOBAL_CREDS_PATH = join(GLOBAL_CONFIG_DIR, "credentials.json");
88
+ }
89
+ });
90
+
91
+ // src/lib/config.ts
92
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
93
+ import { resolve, join as join2 } from "path";
94
+ function findProjectRoot(startDir = process.cwd()) {
95
+ let dir = startDir;
96
+ while (dir !== "/") {
97
+ if (existsSync2(join2(dir, "package.json"))) {
98
+ return dir;
99
+ }
100
+ dir = resolve(dir, "..");
101
+ }
102
+ throw new Error("Could not find project root (no package.json found)");
103
+ }
104
+ function readPackageJson(projectRoot) {
105
+ const pkgPath = join2(projectRoot, "package.json");
106
+ if (!existsSync2(pkgPath)) {
107
+ throw new Error(`package.json not found at ${pkgPath}`);
108
+ }
109
+ return JSON.parse(readFileSync2(pkgPath, "utf-8"));
110
+ }
111
+ function getAppName(projectRoot) {
112
+ const pkg = readPackageJson(projectRoot);
113
+ return pkg.name;
114
+ }
115
+ function getAppTitle(projectRoot) {
116
+ const pkg = readPackageJson(projectRoot);
117
+ return pkg.lumera?.name || pkg.name;
118
+ }
119
+ function detectProjectVersion(projectRoot) {
120
+ const pkg = readPackageJson(projectRoot);
121
+ if (pkg.lumera?.version !== void 0) {
122
+ return pkg.lumera.version;
123
+ }
124
+ if (existsSync2(join2(projectRoot, "lumera_platform"))) {
125
+ return 0;
126
+ }
127
+ if (existsSync2(join2(projectRoot, "platform"))) {
128
+ return 1;
129
+ }
130
+ return 1;
131
+ }
132
+ function getBaseUrl() {
133
+ const envBase = process.env.LUMERA_BASE_URL || process.env.LUMERA_API_URL;
134
+ if (envBase) {
135
+ return envBase.replace(/\/api\/?$/, "").replace(/\/$/, "");
136
+ }
137
+ try {
138
+ const { getCredentials: getCredentials2 } = (init_auth(), __toCommonJS(auth_exports));
139
+ const creds = getCredentials2();
140
+ if (creds?.base_url) {
141
+ return creds.base_url.replace(/\/api\/?$/, "").replace(/\/$/, "");
142
+ }
143
+ } catch {
144
+ }
145
+ return "https://app.lumerahq.com";
146
+ }
147
+ function getApiUrl() {
148
+ return `${getBaseUrl()}/api`;
149
+ }
150
+
151
+ export {
152
+ getToken,
153
+ getTokenSource,
154
+ getCredentials,
155
+ init_auth,
156
+ findProjectRoot,
157
+ readPackageJson,
158
+ getAppName,
159
+ getAppTitle,
160
+ detectProjectVersion,
161
+ getBaseUrl,
162
+ getApiUrl
163
+ };
@@ -4,22 +4,23 @@ import {
4
4
  import {
5
5
  createApiClient,
6
6
  loadEnv
7
- } from "./chunk-NL6MEHA3.js";
8
- import {
9
- getToken
10
- } from "./chunk-NDLYGKS6.js";
7
+ } from "./chunk-RWPUF6L7.js";
11
8
  import {
12
9
  findProjectRoot,
13
10
  getApiUrl,
14
11
  getAppName,
15
- getAppTitle
16
- } from "./chunk-D2BLSEGR.js";
12
+ getAppTitle,
13
+ getToken,
14
+ init_auth
15
+ } from "./chunk-WZMAQXDY.js";
16
+ import "./chunk-PNKVD2UK.js";
17
17
 
18
18
  // src/commands/dev.ts
19
19
  import pc from "picocolors";
20
20
  import { execFileSync, execSync } from "child_process";
21
21
  import { existsSync } from "fs";
22
22
  import { join } from "path";
23
+ init_auth();
23
24
  function parseFlags(args) {
24
25
  const result = {};
25
26
  for (let i = 0; i < args.length; i++) {
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import "./chunk-PNKVD2UK.js";
2
3
 
3
4
  // src/index.ts
4
5
  import { readFileSync as readFileSync2 } from "fs";
@@ -212,59 +213,59 @@ async function main() {
212
213
  switch (command) {
213
214
  // Resource commands
214
215
  case "plan":
215
- await import("./resources-QKEUX3C3.js").then((m) => m.plan(args.slice(1)));
216
+ await import("./resources-26TZX2XU.js").then((m) => m.plan(args.slice(1)));
216
217
  break;
217
218
  case "apply":
218
- await import("./resources-QKEUX3C3.js").then((m) => m.apply(args.slice(1)));
219
+ await import("./resources-26TZX2XU.js").then((m) => m.apply(args.slice(1)));
219
220
  break;
220
221
  case "pull":
221
- await import("./resources-QKEUX3C3.js").then((m) => m.pull(args.slice(1)));
222
+ await import("./resources-26TZX2XU.js").then((m) => m.pull(args.slice(1)));
222
223
  break;
223
224
  case "destroy":
224
- await import("./resources-QKEUX3C3.js").then((m) => m.destroy(args.slice(1)));
225
+ await import("./resources-26TZX2XU.js").then((m) => m.destroy(args.slice(1)));
225
226
  break;
226
227
  case "list":
227
- await import("./resources-QKEUX3C3.js").then((m) => m.list(args.slice(1)));
228
+ await import("./resources-26TZX2XU.js").then((m) => m.list(args.slice(1)));
228
229
  break;
229
230
  case "show":
230
- await import("./resources-QKEUX3C3.js").then((m) => m.show(args.slice(1)));
231
+ await import("./resources-26TZX2XU.js").then((m) => m.show(args.slice(1)));
231
232
  break;
232
233
  case "diff":
233
- await import("./resources-QKEUX3C3.js").then((m) => m.diff(args.slice(1)));
234
+ await import("./resources-26TZX2XU.js").then((m) => m.diff(args.slice(1)));
234
235
  break;
235
236
  // Development
236
237
  case "dev":
237
- await import("./dev-5EAZUQ2S.js").then((m) => m.dev(args.slice(1)));
238
+ await import("./dev-NN766QJW.js").then((m) => m.dev(args.slice(1)));
238
239
  break;
239
240
  case "run":
240
- await import("./run-SPC4YXWR.js").then((m) => m.run(args.slice(1)));
241
+ await import("./run-WD5CKV3S.js").then((m) => m.run(args.slice(1)));
241
242
  break;
242
243
  // Project
243
244
  case "init":
244
- await import("./init-4JSHTLX2.js").then((m) => m.init(args.slice(1)));
245
+ await import("./init-ZI3FZSRE.js").then((m) => m.init(args.slice(1)));
245
246
  break;
246
247
  case "templates":
247
- await import("./templates-6KMZWOYH.js").then((m) => m.templates(subcommand, args.slice(2)));
248
+ await import("./templates-ESFQ4QO4.js").then((m) => m.templates(subcommand, args.slice(2)));
248
249
  break;
249
250
  case "status":
250
- await import("./status-E4IHEUKO.js").then((m) => m.status(args.slice(1)));
251
+ await import("./status-ZFEEIK4O.js").then((m) => m.status(args.slice(1)));
251
252
  break;
252
253
  case "migrate":
253
- await import("./migrate-2DZ6RQ5K.js").then((m) => m.migrate(args.slice(1)));
254
+ await import("./migrate-MW5Q2HS6.js").then((m) => m.migrate(args.slice(1)));
254
255
  break;
255
256
  // Skills
256
257
  case "skills":
257
- await import("./skills-LKXXSJJS.js").then((m) => m.skills(subcommand, args.slice(2)));
258
+ await import("./skills-6TSSUJYR.js").then((m) => m.skills(subcommand, args.slice(2)));
258
259
  break;
259
260
  // Auth
260
261
  case "login":
261
- await import("./auth-7RGL7GXU.js").then((m) => m.login(args.slice(1)));
262
+ await import("./auth-YQXLYQZZ.js").then((m) => m.login(args.slice(1)));
262
263
  break;
263
264
  case "logout":
264
- await import("./auth-7RGL7GXU.js").then((m) => m.logout(args.slice(1)));
265
+ await import("./auth-YQXLYQZZ.js").then((m) => m.logout(args.slice(1)));
265
266
  break;
266
267
  case "whoami":
267
- await import("./auth-7RGL7GXU.js").then((m) => m.whoami());
268
+ await import("./auth-YQXLYQZZ.js").then((m) => m.whoami());
268
269
  break;
269
270
  // Convenience aliases
270
271
  case "help":
@@ -1,12 +1,13 @@
1
1
  import {
2
2
  installAllSkills,
3
3
  syncClaudeMd
4
- } from "./chunk-UP3GV4HN.js";
4
+ } from "./chunk-FZPONF23.js";
5
5
  import {
6
6
  listAllTemplates,
7
7
  resolveTemplate
8
8
  } from "./chunk-CHRKCAIZ.js";
9
- import "./chunk-D2BLSEGR.js";
9
+ import "./chunk-WZMAQXDY.js";
10
+ import "./chunk-PNKVD2UK.js";
10
11
 
11
12
  // src/commands/init.ts
12
13
  import pc2 from "picocolors";
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  detectProjectVersion,
3
3
  findProjectRoot
4
- } from "./chunk-D2BLSEGR.js";
4
+ } from "./chunk-WZMAQXDY.js";
5
+ import "./chunk-PNKVD2UK.js";
5
6
 
6
7
  // src/commands/migrate.ts
7
8
  import pc from "picocolors";
@@ -4,16 +4,16 @@ import {
4
4
  import {
5
5
  createApiClient,
6
6
  loadEnv
7
- } from "./chunk-NL6MEHA3.js";
8
- import {
9
- getToken
10
- } from "./chunk-NDLYGKS6.js";
7
+ } from "./chunk-RWPUF6L7.js";
11
8
  import {
12
9
  findProjectRoot,
13
10
  getApiUrl,
14
11
  getAppName,
15
- getAppTitle
16
- } from "./chunk-D2BLSEGR.js";
12
+ getAppTitle,
13
+ getToken,
14
+ init_auth
15
+ } from "./chunk-WZMAQXDY.js";
16
+ import "./chunk-PNKVD2UK.js";
17
17
 
18
18
  // src/commands/resources.ts
19
19
  import pc from "picocolors";
@@ -21,6 +21,7 @@ import prompts from "prompts";
21
21
  import { execFileSync, execSync } from "child_process";
22
22
  import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from "fs";
23
23
  import { join, resolve } from "path";
24
+ init_auth();
24
25
  function detectPackageManager() {
25
26
  for (const pm of ["bun", "pnpm", "yarn", "npm"]) {
26
27
  try {
@@ -125,12 +126,14 @@ ${pc.dim("Resources:")}
125
126
  app Deploy the frontend app
126
127
 
127
128
  ${pc.dim("Options:")}
129
+ --yes, -y Skip confirmation prompt (for CI/CD)
128
130
  --skip-build Skip build step when applying app
129
131
 
130
132
  ${pc.dim("Examples:")}
131
- lumera apply # Apply everything
133
+ lumera apply # Apply everything (shows plan, asks to confirm)
132
134
  lumera apply collections # Apply all collections
133
135
  lumera apply collections/users # Apply single collection
136
+ lumera apply agents -y # Apply agents without confirmation
134
137
  lumera apply app # Deploy frontend
135
138
  lumera apply app --skip-build # Deploy without rebuilding
136
139
  `);
@@ -482,6 +485,30 @@ function mapFieldType(type) {
482
485
  };
483
486
  return typeMap[type] || type;
484
487
  }
488
+ function fieldsDiffer(local, remote) {
489
+ if (mapFieldType(local.type) !== remote.type) return true;
490
+ if ((local.required || false) !== (remote.required || false)) return true;
491
+ const opts = remote.options || {};
492
+ if (local.type === "select") {
493
+ const localValues = [...local.values || []].sort();
494
+ const remoteValues = [...opts.values || []].sort();
495
+ if (localValues.join(",") !== remoteValues.join(",")) return true;
496
+ const localMultiple = local.multiple || false;
497
+ const remoteMaxSelect = opts.maxSelect || 1;
498
+ if (localMultiple && remoteMaxSelect <= 1) return true;
499
+ if (!localMultiple && remoteMaxSelect > 1) return true;
500
+ }
501
+ if (local.type === "relation") {
502
+ if (local.collection && local.collection !== opts.collectionId) return true;
503
+ const localMultiple = local.multiple || false;
504
+ const remoteMaxSelect = opts.maxSelect || 1;
505
+ if (localMultiple && remoteMaxSelect <= 1) return true;
506
+ if (!localMultiple && remoteMaxSelect > 1) return true;
507
+ }
508
+ if (local.min !== void 0 && local.min !== opts.min) return true;
509
+ if (local.max !== void 0 && local.max !== opts.max) return true;
510
+ return false;
511
+ }
485
512
  async function planCollections(api, localCollections) {
486
513
  const changes = [];
487
514
  const remoteCollections = await api.listCollections();
@@ -509,10 +536,20 @@ async function planCollections(api, localCollections) {
509
536
  const remoteFieldMap = new Map(remote.schema.map((f) => [f.name, f]));
510
537
  const added = [...localFieldNames].filter((n) => !remoteFieldNames.has(n));
511
538
  const removed = [...remoteFieldNames].filter((n) => !localFieldNames.has(n));
512
- if (added.length > 0 || removed.length > 0) {
539
+ const modified = [];
540
+ for (const name of localFieldNames) {
541
+ if (!remoteFieldNames.has(name)) continue;
542
+ const localField = localFieldMap.get(name);
543
+ const remoteField = remoteFieldMap.get(name);
544
+ if (fieldsDiffer(localField, remoteField)) {
545
+ modified.push(name);
546
+ }
547
+ }
548
+ if (added.length > 0 || removed.length > 0 || modified.length > 0) {
513
549
  const details = [];
514
550
  if (added.length > 0) details.push(`+${added.length} field${added.length > 1 ? "s" : ""}`);
515
551
  if (removed.length > 0) details.push(`-${removed.length} field${removed.length > 1 ? "s" : ""}`);
552
+ if (modified.length > 0) details.push(`~${modified.length} field${modified.length > 1 ? "s" : ""} (${modified.join(", ")})`);
516
553
  const fieldDetails = [];
517
554
  for (const name of added) {
518
555
  const f = localFieldMap.get(name);
@@ -967,6 +1004,17 @@ async function planAgents(api, localAgents) {
967
1004
  const remoteByExternalId = new Map(
968
1005
  remoteAgents.filter((a) => a.external_id && !a.managed).map((a) => [a.external_id, a])
969
1006
  );
1007
+ let skillSlugToId = /* @__PURE__ */ new Map();
1008
+ let skillIdToSlug = /* @__PURE__ */ new Map();
1009
+ const hasSkillRefs = localAgents.some((a) => a.agent.skills && a.agent.skills.length > 0) || remoteAgents.some((a) => a.skill_ids && a.skill_ids.length > 0);
1010
+ if (hasSkillRefs) {
1011
+ try {
1012
+ const skills = await api.listAgentSkills();
1013
+ skillSlugToId = new Map(skills.map((s) => [s.slug, s.id]));
1014
+ skillIdToSlug = new Map(skills.map((s) => [s.id, s.slug]));
1015
+ } catch {
1016
+ }
1017
+ }
970
1018
  for (const { agent, systemPrompt, policyScript } of localAgents) {
971
1019
  const remote = remoteByExternalId.get(agent.external_id);
972
1020
  if (!remote) {
@@ -979,6 +1027,16 @@ async function planAgents(api, localAgents) {
979
1027
  if ((remote.model || "") !== (agent.model || "")) diffs.push("model");
980
1028
  if ((remote.policy_script || "").trim() !== (policyScript || "").trim()) diffs.push("policy_script");
981
1029
  if ((remote.policy_enabled || false) !== (agent.policy_enabled || false)) diffs.push("policy_enabled");
1030
+ const localSkillIds = (agent.skills || []).map((s) => skillSlugToId.get(s) || s).sort();
1031
+ const remoteSkillIds = [...remote.skill_ids || []].sort();
1032
+ if (localSkillIds.join(",") !== remoteSkillIds.join(",")) {
1033
+ const addedSlugs = localSkillIds.filter((id) => !remoteSkillIds.includes(id)).map((id) => skillIdToSlug.get(id) || id);
1034
+ const removedSlugs = remoteSkillIds.filter((id) => !localSkillIds.includes(id)).map((id) => skillIdToSlug.get(id) || id);
1035
+ const parts = [];
1036
+ if (addedSlugs.length) parts.push(`+${addedSlugs.join(", +")}`);
1037
+ if (removedSlugs.length) parts.push(`-${removedSlugs.join(", -")}`);
1038
+ diffs.push(`skills (${parts.join(", ")})`);
1039
+ }
982
1040
  if (diffs.length > 0) {
983
1041
  const textDiffs = [];
984
1042
  if (diffs.includes("system_prompt")) {
@@ -1739,11 +1797,12 @@ async function apply(args) {
1739
1797
  const platformDir = getPlatformDir();
1740
1798
  const api = createApiClient();
1741
1799
  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();
1800
+ const { type, name } = parseResource(args.filter((a) => a !== "--yes" && a !== "-y")[0]);
1801
+ const autoConfirm = args.includes("--yes") || args.includes("-y") || !!process.env.CI;
1746
1802
  if (type === "app") {
1803
+ console.log();
1804
+ console.log(pc.cyan(pc.bold(" Apply")));
1805
+ console.log();
1747
1806
  console.log(pc.bold(" App:"));
1748
1807
  await applyApp(args);
1749
1808
  console.log();
@@ -1752,77 +1811,131 @@ async function apply(args) {
1752
1811
  return;
1753
1812
  }
1754
1813
  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
1814
  try {
1768
1815
  const remoteCollections = await api.listCollections();
1769
1816
  collections = new Map(remoteCollections.map((c) => [c.name, c.id]));
1770
- for (const c of remoteCollections) {
1771
- collections.set(c.id, c.id);
1772
- }
1817
+ for (const c of remoteCollections) collections.set(c.id, c.id);
1773
1818
  } catch {
1774
1819
  collections = /* @__PURE__ */ new Map();
1775
1820
  }
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);
1821
+ const allChanges = [];
1822
+ const localCollections = !type || type === "collections" ? loadLocalCollections(platformDir, name || void 0) : [];
1823
+ const localAutomations = !type || type === "automations" ? loadLocalAutomations(platformDir, name || void 0, appName) : [];
1824
+ const localHooks = !type || type === "hooks" ? loadLocalHooks(platformDir, name || void 0, appName) : [];
1825
+ const localAgents = !type || type === "agents" ? loadLocalAgents(platformDir, name || void 0, appName) : [];
1826
+ if (localCollections.length > 0) allChanges.push(...await planCollections(api, localCollections));
1827
+ if (localAutomations.length > 0) allChanges.push(...await planAutomations(api, localAutomations));
1828
+ if (localHooks.length > 0) allChanges.push(...await planHooks(api, localHooks, collections));
1829
+ if (localAgents.length > 0) allChanges.push(...await planAgents(api, localAgents));
1830
+ if (name) {
1831
+ const hasLocal = localCollections.length > 0 || localAutomations.length > 0 || localHooks.length > 0 || localAgents.length > 0;
1832
+ if (!hasLocal) {
1803
1833
  console.log();
1804
- } else if (name) {
1805
- console.log(pc.red(` Agent "${name}" not found locally`));
1834
+ console.log(pc.red(` Resource "${name}" not found locally`));
1806
1835
  process.exit(1);
1807
1836
  }
1808
1837
  }
1838
+ let willDeployApp = false;
1809
1839
  if (!type) {
1810
1840
  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();
1841
+ if (existsSync(join(projectRoot, "dist")) || existsSync(join(projectRoot, "src"))) {
1842
+ willDeployApp = true;
1816
1843
  }
1817
1844
  } catch {
1818
1845
  }
1819
1846
  }
1847
+ if (allChanges.length === 0 && !willDeployApp) {
1848
+ console.log();
1849
+ console.log(pc.green(" \u2713 Nothing to apply \u2014 local and remote are in sync."));
1850
+ console.log();
1851
+ return;
1852
+ }
1853
+ console.log();
1854
+ console.log(pc.cyan(pc.bold(" Apply")));
1855
+ console.log();
1856
+ const creates = allChanges.filter((c) => c.type === "create");
1857
+ const updates = allChanges.filter((c) => c.type === "update");
1858
+ if (allChanges.length > 0) {
1859
+ console.log(pc.bold(" Plan:"));
1860
+ for (const change of allChanges) {
1861
+ const icon = change.type === "create" ? "+" : "~";
1862
+ const color = change.type === "create" ? pc.green : pc.yellow;
1863
+ const details = change.details ? ` (${change.details})` : "";
1864
+ console.log(` ${color(icon)} ${change.resource}: ${change.name}${pc.dim(details)}`);
1865
+ }
1866
+ if (willDeployApp) console.log(` ${pc.blue("\u25CF")} app: frontend build + deploy`);
1867
+ console.log();
1868
+ const parts = [];
1869
+ if (creates.length > 0) parts.push(pc.green(`${creates.length} create`));
1870
+ if (updates.length > 0) parts.push(pc.yellow(`${updates.length} update`));
1871
+ if (willDeployApp) parts.push(pc.blue("1 app deploy"));
1872
+ console.log(` ${parts.join(", ")}`);
1873
+ console.log();
1874
+ } else if (willDeployApp) {
1875
+ console.log(pc.dim(" No infrastructure changes \u2014 deploying app only."));
1876
+ console.log();
1877
+ }
1878
+ if (!autoConfirm && allChanges.length > 0) {
1879
+ const { confirm } = await prompts({
1880
+ type: "confirm",
1881
+ name: "confirm",
1882
+ message: " Proceed with apply?",
1883
+ initial: true
1884
+ });
1885
+ if (!confirm) {
1886
+ console.log(pc.dim(" Cancelled."));
1887
+ console.log();
1888
+ return;
1889
+ }
1890
+ console.log();
1891
+ }
1892
+ let totalErrors = 0;
1893
+ let totalCreated = 0;
1894
+ let totalUpdated = 0;
1895
+ let totalSkipped = 0;
1896
+ if (localCollections.length > 0) {
1897
+ console.log(pc.bold(" Collections:"));
1898
+ totalErrors += await applyCollections(api, localCollections);
1899
+ console.log();
1900
+ }
1901
+ try {
1902
+ const remoteCollections = await api.listCollections();
1903
+ collections = new Map(remoteCollections.map((c) => [c.name, c.id]));
1904
+ for (const c of remoteCollections) collections.set(c.id, c.id);
1905
+ } catch {
1906
+ }
1907
+ if (localAutomations.length > 0) {
1908
+ console.log(pc.bold(" Automations:"));
1909
+ totalErrors += await applyAutomations(api, localAutomations);
1910
+ console.log();
1911
+ }
1912
+ if (localHooks.length > 0) {
1913
+ console.log(pc.bold(" Hooks:"));
1914
+ totalErrors += await applyHooks(api, localHooks, collections);
1915
+ console.log();
1916
+ }
1917
+ if (localAgents.length > 0) {
1918
+ console.log(pc.bold(" Agents:"));
1919
+ totalErrors += await applyAgents(api, localAgents);
1920
+ console.log();
1921
+ }
1922
+ if (willDeployApp) {
1923
+ console.log(pc.bold(" App:"));
1924
+ await applyApp(args);
1925
+ console.log();
1926
+ }
1927
+ totalCreated = creates.length;
1928
+ totalUpdated = updates.length;
1820
1929
  if (totalErrors > 0) {
1821
- console.log(pc.red(` Failed with ${totalErrors} error${totalErrors > 1 ? "s" : ""}.`));
1930
+ console.log(pc.red(` \u2717 ${totalErrors} error${totalErrors > 1 ? "s" : ""} during apply.`));
1822
1931
  console.log();
1823
1932
  process.exit(1);
1824
1933
  }
1825
- console.log(pc.green(" Done!"));
1934
+ const summary = [];
1935
+ if (totalCreated > 0) summary.push(pc.green(`${totalCreated} created`));
1936
+ if (totalUpdated > 0) summary.push(pc.yellow(`${totalUpdated} updated`));
1937
+ if (willDeployApp) summary.push(pc.blue("app deployed"));
1938
+ console.log(pc.green(" \u2713 Done!") + (summary.length > 0 ? ` ${pc.dim("\u2014")} ${summary.join(", ")}` : ""));
1826
1939
  console.log();
1827
1940
  }
1828
1941
  async function pull(args) {
@@ -1,20 +1,21 @@
1
1
  import {
2
2
  createApiClient,
3
3
  loadEnv
4
- } from "./chunk-NL6MEHA3.js";
5
- import {
6
- getToken
7
- } from "./chunk-NDLYGKS6.js";
4
+ } from "./chunk-RWPUF6L7.js";
8
5
  import {
9
6
  findProjectRoot,
10
- getApiUrl
11
- } from "./chunk-D2BLSEGR.js";
7
+ getApiUrl,
8
+ getToken,
9
+ init_auth
10
+ } from "./chunk-WZMAQXDY.js";
11
+ import "./chunk-PNKVD2UK.js";
12
12
 
13
13
  // src/commands/run.ts
14
14
  import pc from "picocolors";
15
15
  import { spawn } from "child_process";
16
16
  import { existsSync, readdirSync, readFileSync } from "fs";
17
17
  import { resolve, extname, join } from "path";
18
+ init_auth();
18
19
  function showHelp() {
19
20
  console.log(`
20
21
  ${pc.dim("Usage:")}
@@ -6,8 +6,9 @@ import {
6
6
  installAllSkills,
7
7
  slugToFilename,
8
8
  syncClaudeMd
9
- } from "./chunk-UP3GV4HN.js";
10
- import "./chunk-D2BLSEGR.js";
9
+ } from "./chunk-FZPONF23.js";
10
+ import "./chunk-WZMAQXDY.js";
11
+ import "./chunk-PNKVD2UK.js";
11
12
 
12
13
  // src/commands/skills.ts
13
14
  import pc from "picocolors";
@@ -1,20 +1,21 @@
1
- import {
2
- getToken,
3
- getTokenSource
4
- } from "./chunk-NDLYGKS6.js";
5
1
  import {
6
2
  detectProjectVersion,
7
3
  findProjectRoot,
8
4
  getAppName,
9
5
  getAppTitle,
10
6
  getBaseUrl,
7
+ getToken,
8
+ getTokenSource,
9
+ init_auth,
11
10
  readPackageJson
12
- } from "./chunk-D2BLSEGR.js";
11
+ } from "./chunk-WZMAQXDY.js";
12
+ import "./chunk-PNKVD2UK.js";
13
13
 
14
14
  // src/commands/status.ts
15
15
  import pc from "picocolors";
16
16
  import { existsSync, readdirSync } from "fs";
17
17
  import { join } from "path";
18
+ init_auth();
18
19
  async function validateToken(token) {
19
20
  const baseUrl = getBaseUrl();
20
21
  try {
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  listAllTemplates
3
3
  } from "./chunk-CHRKCAIZ.js";
4
+ import "./chunk-PNKVD2UK.js";
4
5
 
5
6
  // src/commands/templates.ts
6
7
  import pc from "picocolors";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumerahq/cli",
3
- "version": "0.14.0",
3
+ "version": "0.15.1",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1,59 +0,0 @@
1
- // src/lib/config.ts
2
- import { existsSync, readFileSync } from "fs";
3
- import { resolve, join } from "path";
4
- function findProjectRoot(startDir = process.cwd()) {
5
- let dir = startDir;
6
- while (dir !== "/") {
7
- if (existsSync(join(dir, "package.json"))) {
8
- return dir;
9
- }
10
- dir = resolve(dir, "..");
11
- }
12
- throw new Error("Could not find project root (no package.json found)");
13
- }
14
- function readPackageJson(projectRoot) {
15
- const pkgPath = join(projectRoot, "package.json");
16
- if (!existsSync(pkgPath)) {
17
- throw new Error(`package.json not found at ${pkgPath}`);
18
- }
19
- return JSON.parse(readFileSync(pkgPath, "utf-8"));
20
- }
21
- function getAppName(projectRoot) {
22
- const pkg = readPackageJson(projectRoot);
23
- return pkg.name;
24
- }
25
- function getAppTitle(projectRoot) {
26
- const pkg = readPackageJson(projectRoot);
27
- return pkg.lumera?.name || pkg.name;
28
- }
29
- function detectProjectVersion(projectRoot) {
30
- const pkg = readPackageJson(projectRoot);
31
- if (pkg.lumera?.version !== void 0) {
32
- return pkg.lumera.version;
33
- }
34
- if (existsSync(join(projectRoot, "lumera_platform"))) {
35
- return 0;
36
- }
37
- if (existsSync(join(projectRoot, "platform"))) {
38
- return 1;
39
- }
40
- return 1;
41
- }
42
- function getBaseUrl() {
43
- let base = process.env.LUMERA_BASE_URL || process.env.LUMERA_API_URL || "https://app.lumerahq.com";
44
- base = base.replace(/\/api\/?$/, "").replace(/\/$/, "");
45
- return base;
46
- }
47
- function getApiUrl() {
48
- return `${getBaseUrl()}/api`;
49
- }
50
-
51
- export {
52
- findProjectRoot,
53
- readPackageJson,
54
- getAppName,
55
- getAppTitle,
56
- detectProjectVersion,
57
- getBaseUrl,
58
- getApiUrl
59
- };
@@ -1,77 +0,0 @@
1
- // src/lib/auth.ts
2
- import { existsSync, readFileSync } from "fs";
3
- import { homedir } from "os";
4
- import { join } from "path";
5
- var GLOBAL_CONFIG_DIR = join(homedir(), ".config", "lumera");
6
- var GLOBAL_CREDS_PATH = join(GLOBAL_CONFIG_DIR, "credentials.json");
7
- function getLocalCredsPath(cwd = process.cwd()) {
8
- return join(cwd, ".lumera", "credentials.json");
9
- }
10
- function getToken(cwd = process.cwd()) {
11
- if (process.env.LUMERA_TOKEN) {
12
- return process.env.LUMERA_TOKEN;
13
- }
14
- const localCredsPath = getLocalCredsPath(cwd);
15
- if (existsSync(localCredsPath)) {
16
- try {
17
- const creds = JSON.parse(readFileSync(localCredsPath, "utf-8"));
18
- if (creds.token) return creds.token;
19
- } catch {
20
- }
21
- }
22
- if (existsSync(GLOBAL_CREDS_PATH)) {
23
- try {
24
- const creds = JSON.parse(readFileSync(GLOBAL_CREDS_PATH, "utf-8"));
25
- if (creds.token) return creds.token;
26
- } catch {
27
- }
28
- }
29
- throw new Error(
30
- "No Lumera token found.\n Run `lumera login` to authenticate, or\n Set LUMERA_TOKEN environment variable"
31
- );
32
- }
33
- function getTokenSource(cwd = process.cwd()) {
34
- if (process.env.LUMERA_TOKEN) {
35
- return "environment (LUMERA_TOKEN)";
36
- }
37
- const localCredsPath = getLocalCredsPath(cwd);
38
- if (existsSync(localCredsPath)) {
39
- try {
40
- const creds = JSON.parse(readFileSync(localCredsPath, "utf-8"));
41
- if (creds.token) return `project (${localCredsPath})`;
42
- } catch {
43
- }
44
- }
45
- if (existsSync(GLOBAL_CREDS_PATH)) {
46
- try {
47
- const creds = JSON.parse(readFileSync(GLOBAL_CREDS_PATH, "utf-8"));
48
- if (creds.token) return `global (${GLOBAL_CREDS_PATH})`;
49
- } catch {
50
- }
51
- }
52
- return null;
53
- }
54
- function getCredentials(cwd = process.cwd()) {
55
- const localCredsPath = getLocalCredsPath(cwd);
56
- if (existsSync(localCredsPath)) {
57
- try {
58
- const creds = JSON.parse(readFileSync(localCredsPath, "utf-8"));
59
- if (creds.token) return creds;
60
- } catch {
61
- }
62
- }
63
- if (existsSync(GLOBAL_CREDS_PATH)) {
64
- try {
65
- const creds = JSON.parse(readFileSync(GLOBAL_CREDS_PATH, "utf-8"));
66
- if (creds.token) return creds;
67
- } catch {
68
- }
69
- }
70
- return null;
71
- }
72
-
73
- export {
74
- getToken,
75
- getTokenSource,
76
- getCredentials
77
- };