@lumerahq/cli 0.18.1 → 0.18.3

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,5 +1,6 @@
1
1
  // src/lib/deploy.ts
2
2
  import { execSync, spawn } from "child_process";
3
+ import { createConnection } from "net";
3
4
  import { existsSync, readFileSync } from "fs";
4
5
  import { resolve, dirname } from "path";
5
6
  import archiver from "archiver";
@@ -106,35 +107,22 @@ async function deploy(options) {
106
107
  Deployed! ${result.url}`));
107
108
  return { url: result.url, version };
108
109
  }
109
- async function registerDevApp(apiBase, token, appName, appTitle, appUrl) {
110
- const filterParam = encodeURIComponent(JSON.stringify({ external_id: appName }));
111
- const searchRes = await fetch(
112
- `${apiBase}/pb/collections/lm_custom_apps/records?filter=${filterParam}`,
113
- { headers: { Authorization: `Bearer ${token}` } }
114
- );
115
- if (!searchRes.ok) {
116
- throw new Error(`Failed to check app record: ${await searchRes.text()}`);
117
- }
118
- const existing = (await searchRes.json()).items?.[0];
119
- const appData = {
120
- dev_iframe_url: appUrl
121
- };
122
- if (!existing) {
123
- appData.external_id = appName;
124
- appData.name = appTitle;
125
- }
126
- const endpoint = existing ? `${apiBase}/pb/collections/lm_custom_apps/records/${existing.id}` : `${apiBase}/pb/collections/lm_custom_apps/records`;
127
- const res = await fetch(endpoint, {
128
- method: existing ? "PATCH" : "POST",
129
- headers: {
130
- Authorization: `Bearer ${token}`,
131
- "Content-Type": "application/json"
132
- },
133
- body: JSON.stringify(appData)
110
+ function isPortInUse(port, host = "127.0.0.1") {
111
+ return new Promise((resolve2) => {
112
+ const socket = createConnection({ port, host });
113
+ socket.once("connect", () => {
114
+ socket.destroy();
115
+ resolve2(true);
116
+ });
117
+ socket.once("error", () => {
118
+ socket.destroy();
119
+ resolve2(false);
120
+ });
121
+ socket.setTimeout(1e3, () => {
122
+ socket.destroy();
123
+ resolve2(false);
124
+ });
134
125
  });
135
- if (!res.ok) {
136
- throw new Error(`Failed to register app: ${await res.text()}`);
137
- }
138
126
  }
139
127
  function detectRunner(projectRoot) {
140
128
  if (existsSync(resolve(projectRoot, "bun.lockb")) || existsSync(resolve(projectRoot, "bun.lock"))) {
@@ -161,6 +149,10 @@ async function dev(options) {
161
149
  apiUrl
162
150
  } = options;
163
151
  const projectRoot = process.cwd();
152
+ if (await isPortInUse(port)) {
153
+ console.log(pc.yellow(` Dev server already running on port ${port}, skipping start.`));
154
+ return;
155
+ }
164
156
  console.log();
165
157
  console.log(pc.cyan(pc.bold(` ${appTitle} - Dev Server`)));
166
158
  console.log();
@@ -169,16 +161,6 @@ async function dev(options) {
169
161
  if (host) console.log(pc.dim(` Host: ${host}`));
170
162
  console.log(pc.dim(` URL: ${appUrl}`));
171
163
  console.log();
172
- console.log(pc.dim(" Registering app with Lumera..."));
173
- try {
174
- await registerDevApp(apiUrl, token, appName, appTitle, appUrl);
175
- console.log(pc.green(" \u2713 App registered"));
176
- } catch (err) {
177
- console.log(pc.yellow(" \u26A0 Failed to register app (continuing anyway)"));
178
- if (err instanceof Error) {
179
- console.log(pc.dim(` ${err.message}`));
180
- }
181
- }
182
164
  console.log();
183
165
  console.log(pc.green(" Starting dev server..."));
184
166
  console.log();
@@ -24,8 +24,10 @@ var CLI_USER_AGENT = `lumera-cli/${pkg.version}`;
24
24
  var ApiClient = class {
25
25
  baseUrl;
26
26
  token;
27
- constructor(token, baseUrl) {
27
+ projectExternalId;
28
+ constructor(token, baseUrl, projectExternalId) {
28
29
  this.token = token || getToken();
30
+ this.projectExternalId = projectExternalId;
29
31
  let base = baseUrl || process.env.LUMERA_BASE_URL || "https://app.lumerahq.com";
30
32
  base = base.replace(/\/$/, "");
31
33
  if (base.endsWith("/api")) {
@@ -39,6 +41,7 @@ var ApiClient = class {
39
41
  "Content-Type": "application/json",
40
42
  Authorization: `Bearer ${this.token}`,
41
43
  "User-Agent": CLI_USER_AGENT,
44
+ ...this.projectExternalId ? { "X-Lumera-Project": this.projectExternalId } : {},
42
45
  ...options.headers
43
46
  };
44
47
  const response = await fetch(url, {
@@ -251,9 +254,13 @@ var ApiClient = class {
251
254
  updated: rec.updated
252
255
  };
253
256
  }
257
+ // Project manifest — list public collections for a project
258
+ async getProjectManifest(externalId) {
259
+ return this.request(`/api/pb/projects/${encodeURIComponent(externalId)}/manifest`);
260
+ }
254
261
  };
255
- function createApiClient(token, baseUrl) {
256
- return new ApiClient(token, baseUrl);
262
+ function createApiClient(token, baseUrl, projectExternalId) {
263
+ return new ApiClient(token, baseUrl, projectExternalId);
257
264
  }
258
265
 
259
266
  export {
@@ -0,0 +1,120 @@
1
+ import {
2
+ loadEnv
3
+ } from "./chunk-2CR762KB.js";
4
+ import {
5
+ createApiClient
6
+ } from "./chunk-G7427W43.js";
7
+ import {
8
+ findProjectRoot,
9
+ getAppName
10
+ } from "./chunk-ZH3NVYEQ.js";
11
+
12
+ // src/commands/deps.ts
13
+ import pc from "picocolors";
14
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
15
+ import { join } from "path";
16
+ var DEPS_FILE = "platform/project_deps.json";
17
+ function loadDeps(projectRoot) {
18
+ const depsPath = join(projectRoot, DEPS_FILE);
19
+ if (!existsSync(depsPath)) return null;
20
+ try {
21
+ return JSON.parse(readFileSync(depsPath, "utf-8"));
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+ async function syncDeps(projectRoot) {
27
+ const root = projectRoot ?? findProjectRoot();
28
+ loadEnv(root);
29
+ const deps2 = loadDeps(root);
30
+ if (!deps2 || !deps2.dependencies || Object.keys(deps2.dependencies).length === 0) {
31
+ return true;
32
+ }
33
+ const appName = getAppName(root);
34
+ const api = createApiClient(void 0, void 0, appName);
35
+ let allOk = true;
36
+ for (const [depProject, config] of Object.entries(deps2.dependencies)) {
37
+ try {
38
+ const manifest = await api.getProjectManifest(depProject);
39
+ const remoteNames = new Set(manifest.collections.map((c) => c.name));
40
+ for (const col of config.collections) {
41
+ if (remoteNames.has(col)) {
42
+ console.log(pc.green(" \u2713"), `${depProject}/${col}`);
43
+ } else {
44
+ console.log(pc.red(" \u2717"), `${depProject}/${col} \u2014 not found in project manifest`);
45
+ allOk = false;
46
+ }
47
+ }
48
+ } catch (e) {
49
+ console.log(pc.red(" \u2717"), `${depProject}: failed to fetch manifest \u2014 ${e}`);
50
+ allOk = false;
51
+ }
52
+ }
53
+ return allOk;
54
+ }
55
+ async function deps(args) {
56
+ const sub = args[0];
57
+ const projectRoot = findProjectRoot();
58
+ loadEnv(projectRoot);
59
+ if (sub === "sync") {
60
+ console.log();
61
+ console.log(pc.cyan(pc.bold(" Deps Sync")));
62
+ console.log(pc.dim(" Verifying cross-project dependencies..."));
63
+ console.log();
64
+ const ok = await syncDeps(projectRoot);
65
+ if (!ok) {
66
+ console.log();
67
+ console.log(pc.red(" Some dependencies could not be resolved."));
68
+ process.exit(1);
69
+ }
70
+ console.log();
71
+ console.log(pc.green(" All dependencies verified."));
72
+ return;
73
+ }
74
+ if (sub === "list") {
75
+ const depsData = loadDeps(projectRoot);
76
+ if (!depsData || Object.keys(depsData.dependencies).length === 0) {
77
+ console.log(pc.dim(" No dependencies declared in platform/project_deps.json"));
78
+ return;
79
+ }
80
+ console.log();
81
+ console.log(pc.cyan(pc.bold(" Project Dependencies")));
82
+ console.log();
83
+ for (const [depProject, config] of Object.entries(depsData.dependencies)) {
84
+ console.log(` ${pc.bold(depProject)}`);
85
+ for (const col of config.collections) {
86
+ console.log(` \u2022 ${col}`);
87
+ }
88
+ }
89
+ console.log();
90
+ return;
91
+ }
92
+ if (sub === "init") {
93
+ const depsPath = join(projectRoot, DEPS_FILE);
94
+ if (existsSync(depsPath)) {
95
+ console.log(pc.yellow(" platform/project_deps.json already exists."));
96
+ return;
97
+ }
98
+ const platformDir = join(projectRoot, "platform");
99
+ mkdirSync(platformDir, { recursive: true });
100
+ const initial = { dependencies: {} };
101
+ writeFileSync(depsPath, JSON.stringify(initial, null, 2) + "\n");
102
+ console.log(pc.green(" \u2713"), "Created platform/project_deps.json");
103
+ return;
104
+ }
105
+ console.log(`
106
+ ${pc.bold("lumera deps")} \u2014 manage cross-project dependencies
107
+
108
+ ${pc.bold("Commands:")}
109
+ sync Fetch manifests and verify declared dependencies exist
110
+ list Show declared dependencies
111
+ init Create an empty platform/project_deps.json
112
+
113
+ ${pc.bold("Dependency file:")} platform/project_deps.json
114
+ `);
115
+ }
116
+
117
+ export {
118
+ syncDeps,
119
+ deps
120
+ };
@@ -0,0 +1,12 @@
1
+ import {
2
+ deps,
3
+ syncDeps
4
+ } from "./chunk-YIQRLXEN.js";
5
+ import "./chunk-2CR762KB.js";
6
+ import "./chunk-G7427W43.js";
7
+ import "./chunk-ZH3NVYEQ.js";
8
+ import "./chunk-PNKVD2UK.js";
9
+ export {
10
+ deps,
11
+ syncDeps
12
+ };
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  dev
3
- } from "./chunk-XTRDJLIA.js";
3
+ } from "./chunk-2I2VXPJK.js";
4
4
  import {
5
5
  loadEnv
6
6
  } from "./chunk-2CR762KB.js";
7
7
  import {
8
8
  createApiClient
9
- } from "./chunk-ILO2IR2G.js";
9
+ } from "./chunk-G7427W43.js";
10
10
  import {
11
11
  findProjectRoot,
12
12
  getApiUrl,
package/dist/index.js CHANGED
@@ -91,6 +91,7 @@ var COMMANDS = [
91
91
  "status",
92
92
  "migrate",
93
93
  "skills",
94
+ "deps",
94
95
  "login",
95
96
  "logout",
96
97
  "whoami"
@@ -215,39 +216,39 @@ async function main() {
215
216
  switch (command) {
216
217
  // Resource commands
217
218
  case "plan":
218
- await import("./resources-E4XMWX7N.js").then((m) => m.plan(args.slice(1)));
219
+ await import("./resources-IJ2VFUG5.js").then((m) => m.plan(args.slice(1)));
219
220
  break;
220
221
  case "apply":
221
- await import("./resources-E4XMWX7N.js").then((m) => m.apply(args.slice(1)));
222
+ await import("./resources-IJ2VFUG5.js").then((m) => m.apply(args.slice(1)));
222
223
  break;
223
224
  case "pull":
224
- await import("./resources-E4XMWX7N.js").then((m) => m.pull(args.slice(1)));
225
+ await import("./resources-IJ2VFUG5.js").then((m) => m.pull(args.slice(1)));
225
226
  break;
226
227
  case "destroy":
227
- await import("./resources-E4XMWX7N.js").then((m) => m.destroy(args.slice(1)));
228
+ await import("./resources-IJ2VFUG5.js").then((m) => m.destroy(args.slice(1)));
228
229
  break;
229
230
  case "list":
230
- await import("./resources-E4XMWX7N.js").then((m) => m.list(args.slice(1)));
231
+ await import("./resources-IJ2VFUG5.js").then((m) => m.list(args.slice(1)));
231
232
  break;
232
233
  case "show":
233
- await import("./resources-E4XMWX7N.js").then((m) => m.show(args.slice(1)));
234
+ await import("./resources-IJ2VFUG5.js").then((m) => m.show(args.slice(1)));
234
235
  break;
235
236
  case "diff":
236
- await import("./resources-E4XMWX7N.js").then((m) => m.diff(args.slice(1)));
237
+ await import("./resources-IJ2VFUG5.js").then((m) => m.diff(args.slice(1)));
237
238
  break;
238
239
  // Development
239
240
  case "dev":
240
- await import("./dev-RNV6CJQI.js").then((m) => m.dev(args.slice(1)));
241
+ await import("./dev-62JXNDN3.js").then((m) => m.dev(args.slice(1)));
241
242
  break;
242
243
  case "run":
243
- await import("./run-2VKKOCHR.js").then((m) => m.run(args.slice(1)));
244
+ await import("./run-6ULEFGHJ.js").then((m) => m.run(args.slice(1)));
244
245
  break;
245
246
  // Project
246
247
  case "init":
247
- await import("./init-YUZQ54HZ.js").then((m) => m.init(args.slice(1)));
248
+ await import("./init-ZDOPG3KN.js").then((m) => m.init(args.slice(1)));
248
249
  break;
249
250
  case "register":
250
- await import("./register-MLXJNMNR.js").then((m) => m.register(args.slice(1)));
251
+ await import("./register-COESODY2.js").then((m) => m.register(args.slice(1)));
251
252
  break;
252
253
  case "templates":
253
254
  await import("./templates-ESFQ4QO4.js").then((m) => m.templates(subcommand, args.slice(2)));
@@ -262,6 +263,10 @@ async function main() {
262
263
  case "skills":
263
264
  await import("./skills-VY42VAXX.js").then((m) => m.skills(subcommand, args.slice(2)));
264
265
  break;
266
+ // Dependencies
267
+ case "deps":
268
+ await import("./deps-K54NOIZY.js").then((m) => m.deps(args.slice(1)));
269
+ break;
265
270
  // Auth
266
271
  case "login":
267
272
  await import("./auth-KFXSNCJB.js").then((m) => m.login(args.slice(1)));
@@ -7,16 +7,16 @@ import {
7
7
  } from "./chunk-BHYDYR75.js";
8
8
  import {
9
9
  createApiClient
10
- } from "./chunk-ILO2IR2G.js";
11
- import {
12
- listAllTemplates,
13
- resolveTemplate
14
- } from "./chunk-CHRKCAIZ.js";
10
+ } from "./chunk-G7427W43.js";
15
11
  import {
16
12
  getToken,
17
13
  init_auth,
18
14
  setProjectId
19
15
  } from "./chunk-ZH3NVYEQ.js";
16
+ import {
17
+ listAllTemplates,
18
+ resolveTemplate
19
+ } from "./chunk-CHRKCAIZ.js";
20
20
  import "./chunk-PNKVD2UK.js";
21
21
 
22
22
  // src/commands/init.ts
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-BHYDYR75.js";
7
7
  import {
8
8
  createApiClient
9
- } from "./chunk-ILO2IR2G.js";
9
+ } from "./chunk-G7427W43.js";
10
10
  import {
11
11
  findProjectRoot,
12
12
  getAppName,
@@ -1,12 +1,15 @@
1
+ import {
2
+ syncDeps
3
+ } from "./chunk-YIQRLXEN.js";
1
4
  import {
2
5
  deploy
3
- } from "./chunk-XTRDJLIA.js";
6
+ } from "./chunk-2I2VXPJK.js";
4
7
  import {
5
8
  loadEnv
6
9
  } from "./chunk-2CR762KB.js";
7
10
  import {
8
11
  createApiClient
9
- } from "./chunk-ILO2IR2G.js";
12
+ } from "./chunk-G7427W43.js";
10
13
  import {
11
14
  findProjectRoot,
12
15
  getApiUrl,
@@ -35,6 +38,18 @@ function detectPackageManager() {
35
38
  }
36
39
  return "npm";
37
40
  }
41
+ var NAMESPACE_SEPARATOR = "__";
42
+ function sanitizeSlugForCollectionName(slug) {
43
+ return slug.replace(/-/g, "_");
44
+ }
45
+ function stripNamespacePrefix(name, appName) {
46
+ if (!appName) return name;
47
+ const prefix = sanitizeSlugForCollectionName(appName) + NAMESPACE_SEPARATOR;
48
+ if (name.startsWith(prefix)) {
49
+ return name.slice(prefix.length);
50
+ }
51
+ return name;
52
+ }
38
53
  function computeLineDiff(oldText, newText) {
39
54
  const oldLines = (oldText || "").trimEnd().split("\n");
40
55
  const newLines = (newText || "").trimEnd().split("\n");
@@ -950,7 +965,7 @@ async function applyCollections(api, localCollections) {
950
965
  }
951
966
  return errors;
952
967
  }
953
- async function applyAutomations(api, localAutomations) {
968
+ async function applyAutomations(api, localAutomations, projectId) {
954
969
  let errors = 0;
955
970
  const remoteAutomations = await api.listAutomations();
956
971
  const remoteByExternalId = new Map(remoteAutomations.filter((a) => a.external_id).map((a) => [a.external_id, a]));
@@ -962,6 +977,9 @@ async function applyAutomations(api, localAutomations) {
962
977
  description: automation.description,
963
978
  code
964
979
  };
980
+ if (projectId) {
981
+ payload.project_id = projectId;
982
+ }
965
983
  if (automation.inputs?.schema) {
966
984
  payload.input_schema = automation.inputs.schema;
967
985
  }
@@ -976,15 +994,27 @@ async function applyAutomations(api, localAutomations) {
976
994
  automationId = created.id;
977
995
  console.log(pc.green(" \u2713"), `${automation.name} (created)`);
978
996
  }
979
- if (automation.inputs?.presets) {
980
- await syncPresets(api, automationId, automation.inputs.presets);
997
+ const localPresets = automation.inputs?.presets;
998
+ if (localPresets) {
999
+ await syncPresets(api, automationId, localPresets);
981
1000
  }
982
1001
  if (automation.schedule) {
983
- await setSchedule(api, automationId, automation.schedule, automation.inputs?.presets || {});
1002
+ await setSchedule(api, automationId, automation.schedule, localPresets || {});
984
1003
  } else if (remote?.schedule) {
985
1004
  await api.updateAutomation(automationId, { schedule: "", schedule_tz: "" });
986
1005
  console.log(pc.dim(` Cleared schedule`));
987
1006
  }
1007
+ if (localPresets) {
1008
+ const current = await api.getAutomation(automationId).catch(() => null);
1009
+ if (current) {
1010
+ await deleteStalePresets(
1011
+ api,
1012
+ automationId,
1013
+ localPresets,
1014
+ current.schedule_preset_id ? /* @__PURE__ */ new Set([current.schedule_preset_id]) : /* @__PURE__ */ new Set()
1015
+ );
1016
+ }
1017
+ }
988
1018
  } catch (e) {
989
1019
  console.log(pc.red(" \u2717"), `${automation.name}: ${e}`);
990
1020
  errors++;
@@ -995,9 +1025,6 @@ async function applyAutomations(api, localAutomations) {
995
1025
  async function syncPresets(api, automationId, localPresets) {
996
1026
  const remotePresets = await api.listPresets(automationId);
997
1027
  const remoteByName = new Map(remotePresets.map((p) => [p.name, p]));
998
- const localPresetNames = new Set(
999
- Object.entries(localPresets).map(([key, preset]) => preset.label || key)
1000
- );
1001
1028
  for (const [presetKey, preset] of Object.entries(localPresets)) {
1002
1029
  const presetName = preset.label || presetKey;
1003
1030
  const existing = remoteByName.get(presetName);
@@ -1013,14 +1040,21 @@ async function syncPresets(api, automationId, localPresets) {
1013
1040
  console.log(pc.yellow(` \u26A0 Failed to sync preset ${presetName}: ${e}`));
1014
1041
  }
1015
1042
  }
1043
+ }
1044
+ async function deleteStalePresets(api, automationId, localPresets, preservePresetIds = /* @__PURE__ */ new Set()) {
1045
+ const remotePresets = await api.listPresets(automationId);
1046
+ const localPresetNames = new Set(
1047
+ Object.entries(localPresets).map(([key, preset]) => preset.label || key)
1048
+ );
1016
1049
  for (const remote of remotePresets) {
1017
- if (!localPresetNames.has(remote.name)) {
1018
- try {
1019
- await api.deletePreset(remote.id);
1020
- console.log(pc.dim(` Deleted preset: ${remote.name}`));
1021
- } catch (e) {
1022
- console.log(pc.yellow(` \u26A0 Failed to delete preset ${remote.name}: ${e}`));
1023
- }
1050
+ if (localPresetNames.has(remote.name) || preservePresetIds.has(remote.id)) {
1051
+ continue;
1052
+ }
1053
+ try {
1054
+ await api.deletePreset(remote.id);
1055
+ console.log(pc.dim(` Deleted preset: ${remote.name}`));
1056
+ } catch (e) {
1057
+ console.log(pc.yellow(` \u26A0 Failed to delete preset ${remote.name}: ${e}`));
1024
1058
  }
1025
1059
  }
1026
1060
  }
@@ -1043,7 +1077,7 @@ async function setSchedule(api, automationId, schedule, localPresets) {
1043
1077
  console.log(pc.yellow(` \u26A0 Failed to set schedule: ${e}`));
1044
1078
  }
1045
1079
  }
1046
- async function applyHooks(api, localHooks, collections) {
1080
+ async function applyHooks(api, localHooks, collections, projectId) {
1047
1081
  let errors = 0;
1048
1082
  const remoteHooks = await api.listHooks();
1049
1083
  const remoteByExternalId = new Map(remoteHooks.filter((h) => h.external_id).map((h) => [h.external_id, h]));
@@ -1064,6 +1098,9 @@ async function applyHooks(api, localHooks, collections) {
1064
1098
  enabled: hook.enabled !== false,
1065
1099
  metadata: hook.metadata
1066
1100
  };
1101
+ if (projectId) {
1102
+ payload.project_id = projectId;
1103
+ }
1067
1104
  try {
1068
1105
  if (remote) {
1069
1106
  await api.updateHook(remote.id, payload);
@@ -1099,18 +1136,23 @@ async function applyApp(args) {
1099
1136
  const distDir = resolve(projectRoot, "dist");
1100
1137
  await deploy({ token, appName, appTitle, distDir, apiUrl });
1101
1138
  }
1102
- async function pullCollections(api, platformDir, filterName) {
1139
+ async function pullCollections(api, platformDir, filterName, appName) {
1103
1140
  const collectionsDir = join(platformDir, "collections");
1104
1141
  mkdirSync(collectionsDir, { recursive: true });
1105
1142
  const collections = await api.listCollections();
1106
1143
  for (const collection of collections) {
1107
1144
  if (collection.system || collection.managed) continue;
1108
- if (filterName && collection.name !== filterName && collection.id !== filterName) {
1145
+ if (appName && collection.name.includes(NAMESPACE_SEPARATOR)) {
1146
+ const prefix = collection.name.split(NAMESPACE_SEPARATOR)[0];
1147
+ if (prefix !== sanitizeSlugForCollectionName(appName)) continue;
1148
+ }
1149
+ const localName = stripNamespacePrefix(collection.name, appName);
1150
+ if (filterName && localName !== filterName && collection.name !== filterName && collection.id !== filterName) {
1109
1151
  continue;
1110
1152
  }
1111
1153
  const localFormat = {
1112
1154
  id: collection.id,
1113
- name: collection.name,
1155
+ name: localName,
1114
1156
  fields: collection.schema.map((field) => {
1115
1157
  const localField = {
1116
1158
  name: field.name,
@@ -1132,18 +1174,20 @@ async function pullCollections(api, platformDir, filterName) {
1132
1174
  return null;
1133
1175
  }).filter((idx) => idx !== null)
1134
1176
  };
1135
- const fileName = toSafeFilename(collection.name);
1177
+ const fileName = toSafeFilename(localName);
1136
1178
  const filePath = join(collectionsDir, `${fileName}.json`);
1137
1179
  writeFileSync(filePath, JSON.stringify(localFormat, null, 2) + "\n");
1138
- console.log(pc.green(" \u2713"), `${collection.name} \u2192 collections/${fileName}.json`);
1180
+ console.log(pc.green(" \u2713"), `${localName} \u2192 collections/${fileName}.json`);
1139
1181
  }
1140
1182
  }
1141
- async function pullAutomations(api, platformDir, filterName) {
1183
+ async function pullAutomations(api, platformDir, filterName, projectId) {
1142
1184
  const automationsDir = join(platformDir, "automations");
1143
1185
  mkdirSync(automationsDir, { recursive: true });
1144
1186
  const automations = await api.listAutomations({ include_code: true });
1145
1187
  for (const automation of automations) {
1146
1188
  if (!automation.external_id || automation.managed) continue;
1189
+ if (projectId && automation.project_id && automation.project_id !== projectId) continue;
1190
+ if (!projectId && automation.project_id) continue;
1147
1191
  if (filterName && automation.external_id !== filterName && automation.name !== filterName) {
1148
1192
  continue;
1149
1193
  }
@@ -1186,19 +1230,21 @@ async function pullAutomations(api, platformDir, filterName) {
1186
1230
  console.log(pc.green(" \u2713"), `${automation.name} \u2192 automations/${dirName}/`);
1187
1231
  }
1188
1232
  }
1189
- async function pullHooks(api, platformDir, filterName) {
1233
+ async function pullHooks(api, platformDir, filterName, appName, projectId) {
1190
1234
  const hooksDir = join(platformDir, "hooks");
1191
1235
  mkdirSync(hooksDir, { recursive: true });
1192
1236
  const hooks = await api.listHooks();
1193
1237
  for (const hook of hooks) {
1194
1238
  if (!hook.external_id) continue;
1239
+ if (projectId && hook.project_id && hook.project_id !== projectId) continue;
1240
+ if (!projectId && hook.project_id) continue;
1195
1241
  if (filterName && hook.external_id !== filterName) {
1196
1242
  continue;
1197
1243
  }
1198
1244
  const fileName = `${hook.external_id.replace(/[^a-zA-Z0-9_-]/g, "_")}.js`;
1199
1245
  const content = `export const config = {
1200
1246
  external_id: '${hook.external_id}',
1201
- collection: '${hook.collection_name}',
1247
+ collection: '${stripNamespacePrefix(hook.collection_name, appName)}',
1202
1248
  trigger: '${hook.event}',
1203
1249
  enabled: ${hook.enabled},
1204
1250
  };
@@ -1417,7 +1463,7 @@ async function listResources(api, platformDir, filterType, appName, projectId) {
1417
1463
  if (!filterType || filterType === "collections") {
1418
1464
  const localCollections = loadLocalCollections(platformDir);
1419
1465
  const remoteCollections = await api.listCollections();
1420
- const remoteByName = new Map(remoteCollections.filter((c) => !c.system && !c.managed).map((c) => [c.name, c]));
1466
+ const remoteByName = new Map(remoteCollections.filter((c) => !c.system && !c.managed).map((c) => [stripNamespacePrefix(c.name, appName), c]));
1421
1467
  const localNames = new Set(localCollections.map((c) => c.name));
1422
1468
  for (const local of localCollections) {
1423
1469
  const remote = remoteByName.get(local.name);
@@ -1440,8 +1486,13 @@ async function listResources(api, platformDir, filterType, appName, projectId) {
1440
1486
  }
1441
1487
  for (const remote of remoteCollections) {
1442
1488
  if (remote.system || remote.managed) continue;
1443
- if (!localNames.has(remote.name)) {
1444
- results.push({ name: remote.name, type: "collections", status: "remote-only" });
1489
+ if (appName && remote.name.includes(NAMESPACE_SEPARATOR)) {
1490
+ const prefix = remote.name.split(NAMESPACE_SEPARATOR)[0];
1491
+ if (prefix !== sanitizeSlugForCollectionName(appName)) continue;
1492
+ }
1493
+ const stripped = stripNamespacePrefix(remote.name, appName);
1494
+ if (!localNames.has(stripped)) {
1495
+ results.push({ name: stripped, type: "collections", status: "remote-only" });
1445
1496
  }
1446
1497
  }
1447
1498
  }
@@ -1965,8 +2016,8 @@ async function plan(args) {
1965
2016
  const projectRoot = findProjectRoot();
1966
2017
  loadEnv(projectRoot);
1967
2018
  const platformDir = getPlatformDir();
1968
- const api = createApiClient();
1969
2019
  const appName = getAppName(projectRoot);
2020
+ const api = createApiClient(void 0, void 0, appName);
1970
2021
  const projectId = getProjectId(projectRoot);
1971
2022
  const positionalArgs = args.filter((a) => !a.startsWith("-"));
1972
2023
  const { type, name } = parseResource(positionalArgs[0]);
@@ -1974,6 +2025,7 @@ async function plan(args) {
1974
2025
  console.log(pc.cyan(pc.bold(" Plan")));
1975
2026
  console.log(pc.dim(" Comparing local files to remote state..."));
1976
2027
  console.log();
2028
+ await syncDeps(projectRoot);
1977
2029
  const allChanges = [];
1978
2030
  let collections;
1979
2031
  try {
@@ -2063,8 +2115,8 @@ async function apply(args) {
2063
2115
  const projectRoot = findProjectRoot();
2064
2116
  loadEnv(projectRoot);
2065
2117
  const platformDir = getPlatformDir();
2066
- const api = createApiClient();
2067
2118
  const appName = getAppName(projectRoot);
2119
+ const api = createApiClient(void 0, void 0, appName);
2068
2120
  const projectId = getProjectId(projectRoot);
2069
2121
  const positionalArgs = args.filter((a) => !a.startsWith("-"));
2070
2122
  const { type, name } = parseResource(positionalArgs[0]);
@@ -2080,6 +2132,7 @@ async function apply(args) {
2080
2132
  console.log();
2081
2133
  return;
2082
2134
  }
2135
+ await syncDeps(projectRoot);
2083
2136
  let collections;
2084
2137
  try {
2085
2138
  const remoteCollections = await api.listCollections();
@@ -2176,12 +2229,12 @@ async function apply(args) {
2176
2229
  }
2177
2230
  if (localAutomations.length > 0) {
2178
2231
  console.log(pc.bold(" Automations:"));
2179
- totalErrors += await applyAutomations(api, localAutomations);
2232
+ totalErrors += await applyAutomations(api, localAutomations, projectId);
2180
2233
  console.log();
2181
2234
  }
2182
2235
  if (localHooks.length > 0) {
2183
2236
  console.log(pc.bold(" Hooks:"));
2184
- totalErrors += await applyHooks(api, localHooks, collections);
2237
+ totalErrors += await applyHooks(api, localHooks, collections, projectId);
2185
2238
  console.log();
2186
2239
  }
2187
2240
  if (localAgents.length > 0) {
@@ -2218,8 +2271,8 @@ async function pull(args) {
2218
2271
  const projectRoot = findProjectRoot();
2219
2272
  loadEnv(projectRoot);
2220
2273
  const platformDir = getPlatformDir();
2221
- const api = createApiClient();
2222
2274
  const appName = getAppName(projectRoot);
2275
+ const api = createApiClient(void 0, void 0, appName);
2223
2276
  const projectId = getProjectId(projectRoot);
2224
2277
  const { type, name } = parseResource(filteredArgs[0]);
2225
2278
  if (!force) {
@@ -2277,17 +2330,17 @@ async function pull(args) {
2277
2330
  console.log();
2278
2331
  if (!type || type === "collections") {
2279
2332
  console.log(pc.bold(" Collections:"));
2280
- await pullCollections(api, platformDir, name || void 0);
2333
+ await pullCollections(api, platformDir, name || void 0, appName);
2281
2334
  console.log();
2282
2335
  }
2283
2336
  if (!type || type === "automations") {
2284
2337
  console.log(pc.bold(" Automations:"));
2285
- await pullAutomations(api, platformDir, name || void 0);
2338
+ await pullAutomations(api, platformDir, name || void 0, projectId);
2286
2339
  console.log();
2287
2340
  }
2288
2341
  if (!type || type === "hooks") {
2289
2342
  console.log(pc.bold(" Hooks:"));
2290
- await pullHooks(api, platformDir, name || void 0);
2343
+ await pullHooks(api, platformDir, name || void 0, appName, projectId);
2291
2344
  console.log();
2292
2345
  }
2293
2346
  if (!type || type === "agents") {
@@ -2306,8 +2359,8 @@ async function destroy(args) {
2306
2359
  const projectRoot = findProjectRoot();
2307
2360
  loadEnv(projectRoot);
2308
2361
  const platformDir = getPlatformDir();
2309
- const api = createApiClient();
2310
2362
  const appName = getAppName(projectRoot);
2363
+ const api = createApiClient(void 0, void 0, appName);
2311
2364
  const projectId = getProjectId(projectRoot);
2312
2365
  const { type, name } = parseResource(args[0]);
2313
2366
  const skipConfirm = args.includes("--confirm");
@@ -2330,8 +2383,8 @@ async function list(args) {
2330
2383
  const projectRoot = findProjectRoot();
2331
2384
  loadEnv(projectRoot);
2332
2385
  const platformDir = getPlatformDir();
2333
- const api = createApiClient();
2334
2386
  const appName = getAppName(projectRoot);
2387
+ const api = createApiClient(void 0, void 0, appName);
2335
2388
  const projectId = getProjectId(projectRoot);
2336
2389
  const showAll = args.includes("--all");
2337
2390
  const positionalArgs = args.filter((a) => !a.startsWith("--"));
@@ -2411,8 +2464,8 @@ async function show(args) {
2411
2464
  const projectRoot = findProjectRoot();
2412
2465
  loadEnv(projectRoot);
2413
2466
  const platformDir = getPlatformDir();
2414
- const api = createApiClient();
2415
2467
  const appName = getAppName(projectRoot);
2468
+ const api = createApiClient(void 0, void 0, appName);
2416
2469
  const projectId = getProjectId(projectRoot);
2417
2470
  const { type, name } = parseResource(args[0]);
2418
2471
  if (!type) {
@@ -2471,8 +2524,8 @@ async function diff(args) {
2471
2524
  const projectRoot = findProjectRoot();
2472
2525
  loadEnv(projectRoot);
2473
2526
  const platformDir = getPlatformDir();
2474
- const api = createApiClient();
2475
2527
  const appName = getAppName(projectRoot);
2528
+ const api = createApiClient(void 0, void 0, appName);
2476
2529
  const projectId = getProjectId(projectRoot);
2477
2530
  const { type, name } = parseResource(args[0]);
2478
2531
  if (!type || !name) {
@@ -3,10 +3,11 @@ import {
3
3
  } from "./chunk-2CR762KB.js";
4
4
  import {
5
5
  createApiClient
6
- } from "./chunk-ILO2IR2G.js";
6
+ } from "./chunk-G7427W43.js";
7
7
  import {
8
8
  findProjectRoot,
9
9
  getApiUrl,
10
+ getAppName,
10
11
  getToken,
11
12
  init_auth
12
13
  } from "./chunk-ZH3NVYEQ.js";
@@ -115,7 +116,8 @@ async function runScript(scriptPath, projectRoot) {
115
116
  }
116
117
  }
117
118
  async function triggerAutomation(automationName, projectRoot, flags) {
118
- const api = createApiClient();
119
+ const appName = getAppName(projectRoot);
120
+ const api = createApiClient(void 0, void 0, appName);
119
121
  const token = getToken(projectRoot);
120
122
  let apiUrl = getApiUrl().replace(/\/+$/, "").replace(/\/api$/, "");
121
123
  console.log();
@@ -235,13 +237,14 @@ async function runAutomationLocally(automationName, projectRoot, flags) {
235
237
  process.on("SIGINT", () => uv.kill("SIGINT"));
236
238
  process.on("SIGTERM", () => uv.kill("SIGTERM"));
237
239
  }
238
- async function invokeAgent(agentName, message, flags) {
240
+ async function invokeAgent(agentName, message, flags, projectRoot) {
239
241
  if (!message) {
240
242
  console.error(pc.red(" Message is required."));
241
243
  console.error(pc.dim(' Usage: lumera run agents/<name> "Your message here"'));
242
244
  process.exit(1);
243
245
  }
244
- const api = createApiClient();
246
+ const appName = projectRoot ? getAppName(projectRoot) : void 0;
247
+ const api = createApiClient(void 0, void 0, appName);
245
248
  console.log();
246
249
  console.log(pc.cyan(pc.bold(" Invoke Agent")));
247
250
  console.log();
@@ -313,7 +316,7 @@ async function run(args) {
313
316
  if (target.startsWith("agents/")) {
314
317
  const agentName = target.replace("agents/", "");
315
318
  const message = positionalArgs.join(" ") || void 0;
316
- await invokeAgent(agentName, message, flags);
319
+ await invokeAgent(agentName, message, flags, projectRoot);
317
320
  return;
318
321
  }
319
322
  await runScript(target, projectRoot);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumerahq/cli",
3
- "version": "0.18.1",
3
+ "version": "0.18.3",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {