@lumerahq/cli 0.18.2 → 0.18.4

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-6SJR45CY.js").then((m) => m.plan(args.slice(1)));
219
+ await import("./resources-CMY4IRUS.js").then((m) => m.plan(args.slice(1)));
219
220
  break;
220
221
  case "apply":
221
- await import("./resources-6SJR45CY.js").then((m) => m.apply(args.slice(1)));
222
+ await import("./resources-CMY4IRUS.js").then((m) => m.apply(args.slice(1)));
222
223
  break;
223
224
  case "pull":
224
- await import("./resources-6SJR45CY.js").then((m) => m.pull(args.slice(1)));
225
+ await import("./resources-CMY4IRUS.js").then((m) => m.pull(args.slice(1)));
225
226
  break;
226
227
  case "destroy":
227
- await import("./resources-6SJR45CY.js").then((m) => m.destroy(args.slice(1)));
228
+ await import("./resources-CMY4IRUS.js").then((m) => m.destroy(args.slice(1)));
228
229
  break;
229
230
  case "list":
230
- await import("./resources-6SJR45CY.js").then((m) => m.list(args.slice(1)));
231
+ await import("./resources-CMY4IRUS.js").then((m) => m.list(args.slice(1)));
231
232
  break;
232
233
  case "show":
233
- await import("./resources-6SJR45CY.js").then((m) => m.show(args.slice(1)));
234
+ await import("./resources-CMY4IRUS.js").then((m) => m.show(args.slice(1)));
234
235
  break;
235
236
  case "diff":
236
- await import("./resources-6SJR45CY.js").then((m) => m.diff(args.slice(1)));
237
+ await import("./resources-CMY4IRUS.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");
@@ -538,7 +553,10 @@ function loadLocalAutomations(platformDir, filterName, appName) {
538
553
  errors.push(`${entry.name}: duplicate external_id "${config.external_id}" (also defined in ${existingByExtId.automation.name})`);
539
554
  continue;
540
555
  }
541
- const code = readFileSync(mainPath, "utf-8");
556
+ let code = readFileSync(mainPath, "utf-8");
557
+ if (appName) {
558
+ code = code.replaceAll("{{app}}", appName);
559
+ }
542
560
  automations.push({ automation: config, code });
543
561
  } catch (e) {
544
562
  errors.push(`${entry.name}: failed to parse config.json - ${e}`);
@@ -950,7 +968,7 @@ async function applyCollections(api, localCollections) {
950
968
  }
951
969
  return errors;
952
970
  }
953
- async function applyAutomations(api, localAutomations) {
971
+ async function applyAutomations(api, localAutomations, projectId) {
954
972
  let errors = 0;
955
973
  const remoteAutomations = await api.listAutomations();
956
974
  const remoteByExternalId = new Map(remoteAutomations.filter((a) => a.external_id).map((a) => [a.external_id, a]));
@@ -962,6 +980,9 @@ async function applyAutomations(api, localAutomations) {
962
980
  description: automation.description,
963
981
  code
964
982
  };
983
+ if (projectId) {
984
+ payload.project_id = projectId;
985
+ }
965
986
  if (automation.inputs?.schema) {
966
987
  payload.input_schema = automation.inputs.schema;
967
988
  }
@@ -1059,7 +1080,7 @@ async function setSchedule(api, automationId, schedule, localPresets) {
1059
1080
  console.log(pc.yellow(` \u26A0 Failed to set schedule: ${e}`));
1060
1081
  }
1061
1082
  }
1062
- async function applyHooks(api, localHooks, collections) {
1083
+ async function applyHooks(api, localHooks, collections, projectId) {
1063
1084
  let errors = 0;
1064
1085
  const remoteHooks = await api.listHooks();
1065
1086
  const remoteByExternalId = new Map(remoteHooks.filter((h) => h.external_id).map((h) => [h.external_id, h]));
@@ -1080,6 +1101,9 @@ async function applyHooks(api, localHooks, collections) {
1080
1101
  enabled: hook.enabled !== false,
1081
1102
  metadata: hook.metadata
1082
1103
  };
1104
+ if (projectId) {
1105
+ payload.project_id = projectId;
1106
+ }
1083
1107
  try {
1084
1108
  if (remote) {
1085
1109
  await api.updateHook(remote.id, payload);
@@ -1115,18 +1139,23 @@ async function applyApp(args) {
1115
1139
  const distDir = resolve(projectRoot, "dist");
1116
1140
  await deploy({ token, appName, appTitle, distDir, apiUrl });
1117
1141
  }
1118
- async function pullCollections(api, platformDir, filterName) {
1142
+ async function pullCollections(api, platformDir, filterName, appName) {
1119
1143
  const collectionsDir = join(platformDir, "collections");
1120
1144
  mkdirSync(collectionsDir, { recursive: true });
1121
1145
  const collections = await api.listCollections();
1122
1146
  for (const collection of collections) {
1123
1147
  if (collection.system || collection.managed) continue;
1124
- if (filterName && collection.name !== filterName && collection.id !== filterName) {
1148
+ if (appName && collection.name.includes(NAMESPACE_SEPARATOR)) {
1149
+ const prefix = collection.name.split(NAMESPACE_SEPARATOR)[0];
1150
+ if (prefix !== sanitizeSlugForCollectionName(appName)) continue;
1151
+ }
1152
+ const localName = stripNamespacePrefix(collection.name, appName);
1153
+ if (filterName && localName !== filterName && collection.name !== filterName && collection.id !== filterName) {
1125
1154
  continue;
1126
1155
  }
1127
1156
  const localFormat = {
1128
1157
  id: collection.id,
1129
- name: collection.name,
1158
+ name: localName,
1130
1159
  fields: collection.schema.map((field) => {
1131
1160
  const localField = {
1132
1161
  name: field.name,
@@ -1148,18 +1177,20 @@ async function pullCollections(api, platformDir, filterName) {
1148
1177
  return null;
1149
1178
  }).filter((idx) => idx !== null)
1150
1179
  };
1151
- const fileName = toSafeFilename(collection.name);
1180
+ const fileName = toSafeFilename(localName);
1152
1181
  const filePath = join(collectionsDir, `${fileName}.json`);
1153
1182
  writeFileSync(filePath, JSON.stringify(localFormat, null, 2) + "\n");
1154
- console.log(pc.green(" \u2713"), `${collection.name} \u2192 collections/${fileName}.json`);
1183
+ console.log(pc.green(" \u2713"), `${localName} \u2192 collections/${fileName}.json`);
1155
1184
  }
1156
1185
  }
1157
- async function pullAutomations(api, platformDir, filterName) {
1186
+ async function pullAutomations(api, platformDir, filterName, projectId) {
1158
1187
  const automationsDir = join(platformDir, "automations");
1159
1188
  mkdirSync(automationsDir, { recursive: true });
1160
1189
  const automations = await api.listAutomations({ include_code: true });
1161
1190
  for (const automation of automations) {
1162
1191
  if (!automation.external_id || automation.managed) continue;
1192
+ if (projectId && automation.project_id && automation.project_id !== projectId) continue;
1193
+ if (!projectId && automation.project_id) continue;
1163
1194
  if (filterName && automation.external_id !== filterName && automation.name !== filterName) {
1164
1195
  continue;
1165
1196
  }
@@ -1202,19 +1233,21 @@ async function pullAutomations(api, platformDir, filterName) {
1202
1233
  console.log(pc.green(" \u2713"), `${automation.name} \u2192 automations/${dirName}/`);
1203
1234
  }
1204
1235
  }
1205
- async function pullHooks(api, platformDir, filterName) {
1236
+ async function pullHooks(api, platformDir, filterName, appName, projectId) {
1206
1237
  const hooksDir = join(platformDir, "hooks");
1207
1238
  mkdirSync(hooksDir, { recursive: true });
1208
1239
  const hooks = await api.listHooks();
1209
1240
  for (const hook of hooks) {
1210
1241
  if (!hook.external_id) continue;
1242
+ if (projectId && hook.project_id && hook.project_id !== projectId) continue;
1243
+ if (!projectId && hook.project_id) continue;
1211
1244
  if (filterName && hook.external_id !== filterName) {
1212
1245
  continue;
1213
1246
  }
1214
1247
  const fileName = `${hook.external_id.replace(/[^a-zA-Z0-9_-]/g, "_")}.js`;
1215
1248
  const content = `export const config = {
1216
1249
  external_id: '${hook.external_id}',
1217
- collection: '${hook.collection_name}',
1250
+ collection: '${stripNamespacePrefix(hook.collection_name, appName)}',
1218
1251
  trigger: '${hook.event}',
1219
1252
  enabled: ${hook.enabled},
1220
1253
  };
@@ -1263,8 +1296,12 @@ function loadLocalAgents(platformDir, filterName, appName) {
1263
1296
  errors.push(`${entry.name}: missing name in config.json`);
1264
1297
  continue;
1265
1298
  }
1266
- const systemPrompt = readFileSync(promptPath, "utf-8");
1267
- const policyScript = existsSync(policyPath) ? readFileSync(policyPath, "utf-8") : "";
1299
+ let systemPrompt = readFileSync(promptPath, "utf-8");
1300
+ let policyScript = existsSync(policyPath) ? readFileSync(policyPath, "utf-8") : "";
1301
+ if (appName) {
1302
+ systemPrompt = systemPrompt.replaceAll("{{app}}", appName);
1303
+ policyScript = policyScript.replaceAll("{{app}}", appName);
1304
+ }
1268
1305
  agents.push({ agent: config, systemPrompt, policyScript });
1269
1306
  } catch (e) {
1270
1307
  errors.push(`${entry.name}: failed to parse config.json - ${e}`);
@@ -1308,6 +1345,7 @@ async function planAgents(api, localAgents, projectId) {
1308
1345
  if ((remote.model || "") !== (agent.model || "")) diffs.push("model");
1309
1346
  if ((remote.policy_script || "").trim() !== (policyScript || "").trim()) diffs.push("policy_script");
1310
1347
  if ((remote.policy_enabled || false) !== (agent.policy_enabled || false)) diffs.push("policy_enabled");
1348
+ if ((remote.policy_description || "") !== (agent.policy_description || "")) diffs.push("policy_description");
1311
1349
  const localSkillIds = (agent.skills || []).map((s) => skillSlugToId.get(s) || s).sort();
1312
1350
  const remoteSkillIds = [...remote.skill_ids || []].sort();
1313
1351
  if (localSkillIds.join(",") !== remoteSkillIds.join(",")) {
@@ -1369,7 +1407,8 @@ async function applyAgents(api, localAgents, projectId) {
1369
1407
  model: agent.model || "",
1370
1408
  skill_ids: skillIds,
1371
1409
  policy_script: policyScript || "",
1372
- policy_enabled: agent.policy_enabled || false
1410
+ policy_enabled: agent.policy_enabled || false,
1411
+ policy_description: agent.policy_description || ""
1373
1412
  };
1374
1413
  try {
1375
1414
  if (remote) {
@@ -1433,7 +1472,7 @@ async function listResources(api, platformDir, filterType, appName, projectId) {
1433
1472
  if (!filterType || filterType === "collections") {
1434
1473
  const localCollections = loadLocalCollections(platformDir);
1435
1474
  const remoteCollections = await api.listCollections();
1436
- const remoteByName = new Map(remoteCollections.filter((c) => !c.system && !c.managed).map((c) => [c.name, c]));
1475
+ const remoteByName = new Map(remoteCollections.filter((c) => !c.system && !c.managed).map((c) => [stripNamespacePrefix(c.name, appName), c]));
1437
1476
  const localNames = new Set(localCollections.map((c) => c.name));
1438
1477
  for (const local of localCollections) {
1439
1478
  const remote = remoteByName.get(local.name);
@@ -1456,8 +1495,13 @@ async function listResources(api, platformDir, filterType, appName, projectId) {
1456
1495
  }
1457
1496
  for (const remote of remoteCollections) {
1458
1497
  if (remote.system || remote.managed) continue;
1459
- if (!localNames.has(remote.name)) {
1460
- results.push({ name: remote.name, type: "collections", status: "remote-only" });
1498
+ if (appName && remote.name.includes(NAMESPACE_SEPARATOR)) {
1499
+ const prefix = remote.name.split(NAMESPACE_SEPARATOR)[0];
1500
+ if (prefix !== sanitizeSlugForCollectionName(appName)) continue;
1501
+ }
1502
+ const stripped = stripNamespacePrefix(remote.name, appName);
1503
+ if (!localNames.has(stripped)) {
1504
+ results.push({ name: stripped, type: "collections", status: "remote-only" });
1461
1505
  }
1462
1506
  }
1463
1507
  }
@@ -1981,8 +2025,8 @@ async function plan(args) {
1981
2025
  const projectRoot = findProjectRoot();
1982
2026
  loadEnv(projectRoot);
1983
2027
  const platformDir = getPlatformDir();
1984
- const api = createApiClient();
1985
2028
  const appName = getAppName(projectRoot);
2029
+ const api = createApiClient(void 0, void 0, appName);
1986
2030
  const projectId = getProjectId(projectRoot);
1987
2031
  const positionalArgs = args.filter((a) => !a.startsWith("-"));
1988
2032
  const { type, name } = parseResource(positionalArgs[0]);
@@ -1990,6 +2034,7 @@ async function plan(args) {
1990
2034
  console.log(pc.cyan(pc.bold(" Plan")));
1991
2035
  console.log(pc.dim(" Comparing local files to remote state..."));
1992
2036
  console.log();
2037
+ await syncDeps(projectRoot);
1993
2038
  const allChanges = [];
1994
2039
  let collections;
1995
2040
  try {
@@ -2079,8 +2124,8 @@ async function apply(args) {
2079
2124
  const projectRoot = findProjectRoot();
2080
2125
  loadEnv(projectRoot);
2081
2126
  const platformDir = getPlatformDir();
2082
- const api = createApiClient();
2083
2127
  const appName = getAppName(projectRoot);
2128
+ const api = createApiClient(void 0, void 0, appName);
2084
2129
  const projectId = getProjectId(projectRoot);
2085
2130
  const positionalArgs = args.filter((a) => !a.startsWith("-"));
2086
2131
  const { type, name } = parseResource(positionalArgs[0]);
@@ -2096,6 +2141,7 @@ async function apply(args) {
2096
2141
  console.log();
2097
2142
  return;
2098
2143
  }
2144
+ await syncDeps(projectRoot);
2099
2145
  let collections;
2100
2146
  try {
2101
2147
  const remoteCollections = await api.listCollections();
@@ -2192,12 +2238,12 @@ async function apply(args) {
2192
2238
  }
2193
2239
  if (localAutomations.length > 0) {
2194
2240
  console.log(pc.bold(" Automations:"));
2195
- totalErrors += await applyAutomations(api, localAutomations);
2241
+ totalErrors += await applyAutomations(api, localAutomations, projectId);
2196
2242
  console.log();
2197
2243
  }
2198
2244
  if (localHooks.length > 0) {
2199
2245
  console.log(pc.bold(" Hooks:"));
2200
- totalErrors += await applyHooks(api, localHooks, collections);
2246
+ totalErrors += await applyHooks(api, localHooks, collections, projectId);
2201
2247
  console.log();
2202
2248
  }
2203
2249
  if (localAgents.length > 0) {
@@ -2234,8 +2280,8 @@ async function pull(args) {
2234
2280
  const projectRoot = findProjectRoot();
2235
2281
  loadEnv(projectRoot);
2236
2282
  const platformDir = getPlatformDir();
2237
- const api = createApiClient();
2238
2283
  const appName = getAppName(projectRoot);
2284
+ const api = createApiClient(void 0, void 0, appName);
2239
2285
  const projectId = getProjectId(projectRoot);
2240
2286
  const { type, name } = parseResource(filteredArgs[0]);
2241
2287
  if (!force) {
@@ -2293,17 +2339,17 @@ async function pull(args) {
2293
2339
  console.log();
2294
2340
  if (!type || type === "collections") {
2295
2341
  console.log(pc.bold(" Collections:"));
2296
- await pullCollections(api, platformDir, name || void 0);
2342
+ await pullCollections(api, platformDir, name || void 0, appName);
2297
2343
  console.log();
2298
2344
  }
2299
2345
  if (!type || type === "automations") {
2300
2346
  console.log(pc.bold(" Automations:"));
2301
- await pullAutomations(api, platformDir, name || void 0);
2347
+ await pullAutomations(api, platformDir, name || void 0, projectId);
2302
2348
  console.log();
2303
2349
  }
2304
2350
  if (!type || type === "hooks") {
2305
2351
  console.log(pc.bold(" Hooks:"));
2306
- await pullHooks(api, platformDir, name || void 0);
2352
+ await pullHooks(api, platformDir, name || void 0, appName, projectId);
2307
2353
  console.log();
2308
2354
  }
2309
2355
  if (!type || type === "agents") {
@@ -2322,8 +2368,8 @@ async function destroy(args) {
2322
2368
  const projectRoot = findProjectRoot();
2323
2369
  loadEnv(projectRoot);
2324
2370
  const platformDir = getPlatformDir();
2325
- const api = createApiClient();
2326
2371
  const appName = getAppName(projectRoot);
2372
+ const api = createApiClient(void 0, void 0, appName);
2327
2373
  const projectId = getProjectId(projectRoot);
2328
2374
  const { type, name } = parseResource(args[0]);
2329
2375
  const skipConfirm = args.includes("--confirm");
@@ -2346,8 +2392,8 @@ async function list(args) {
2346
2392
  const projectRoot = findProjectRoot();
2347
2393
  loadEnv(projectRoot);
2348
2394
  const platformDir = getPlatformDir();
2349
- const api = createApiClient();
2350
2395
  const appName = getAppName(projectRoot);
2396
+ const api = createApiClient(void 0, void 0, appName);
2351
2397
  const projectId = getProjectId(projectRoot);
2352
2398
  const showAll = args.includes("--all");
2353
2399
  const positionalArgs = args.filter((a) => !a.startsWith("--"));
@@ -2427,8 +2473,8 @@ async function show(args) {
2427
2473
  const projectRoot = findProjectRoot();
2428
2474
  loadEnv(projectRoot);
2429
2475
  const platformDir = getPlatformDir();
2430
- const api = createApiClient();
2431
2476
  const appName = getAppName(projectRoot);
2477
+ const api = createApiClient(void 0, void 0, appName);
2432
2478
  const projectId = getProjectId(projectRoot);
2433
2479
  const { type, name } = parseResource(args[0]);
2434
2480
  if (!type) {
@@ -2487,8 +2533,8 @@ async function diff(args) {
2487
2533
  const projectRoot = findProjectRoot();
2488
2534
  loadEnv(projectRoot);
2489
2535
  const platformDir = getPlatformDir();
2490
- const api = createApiClient();
2491
2536
  const appName = getAppName(projectRoot);
2537
+ const api = createApiClient(void 0, void 0, appName);
2492
2538
  const projectId = getProjectId(projectRoot);
2493
2539
  const { type, name } = parseResource(args[0]);
2494
2540
  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.2",
3
+ "version": "0.18.4",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {