@lumerahq/cli 0.19.3 → 0.19.5

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.
@@ -31,7 +31,7 @@ async function fetchSkillsList() {
31
31
  if (!listRes.ok) {
32
32
  throw new Error(`Failed to fetch skills list: ${listRes.status}`);
33
33
  }
34
- return listRes.json();
34
+ return await listRes.json();
35
35
  }
36
36
  async function fetchSkillContent(slug) {
37
37
  const baseUrl = getBaseUrl();
@@ -2,8 +2,9 @@ import {
2
2
  loadEnv
3
3
  } from "./chunk-2CR762KB.js";
4
4
  import {
5
- createApiClient
6
- } from "./chunk-JKXLKK5I.js";
5
+ createApiClient,
6
+ isApiErrorStatus
7
+ } from "./chunk-WY6UMJNI.js";
7
8
  import {
8
9
  findProjectRoot,
9
10
  getAppName
@@ -86,6 +87,10 @@ async function syncResourceShareDeps(projectRoot, api, projectExternalId, opts)
86
87
  try {
87
88
  shares = await listIncomingCollectionShares(api, projectExternalId);
88
89
  } catch (e) {
90
+ if (opts.ignorePermissionDenied && isApiErrorStatus(e, 403)) {
91
+ console.log(pc.yellow(" \u26A0"), "Skipping resource-share sync \u2014 current token cannot list project shares (403).");
92
+ return true;
93
+ }
89
94
  if (!opts.quiet) {
90
95
  console.log(pc.red(" \u2717"), `Failed to list incoming resource shares \u2014 ${e}`);
91
96
  }
@@ -170,7 +175,11 @@ async function syncDeps(projectRoot, options = {}) {
170
175
  const quiet = options.quiet === true;
171
176
  const write = options.write === true;
172
177
  if (flagOn) {
173
- return syncResourceShareDeps(root, api, appName, { write, quiet });
178
+ return syncResourceShareDeps(root, api, appName, {
179
+ write,
180
+ quiet,
181
+ ignorePermissionDenied: options.ignorePermissionDenied === true
182
+ });
174
183
  }
175
184
  if (options.legacyWhenDisabled === false) {
176
185
  return true;
@@ -25,6 +25,19 @@ while (__pkgDir !== "/") {
25
25
  }
26
26
  var pkg = JSON.parse(readFileSync(resolve(__pkgDir, "package.json"), "utf-8"));
27
27
  var CLI_USER_AGENT = `lumera-cli/${pkg.version}`;
28
+ var ApiError = class extends Error {
29
+ status;
30
+ body;
31
+ constructor(status, body) {
32
+ super(`API request failed: ${status} ${body}`);
33
+ this.name = "ApiError";
34
+ this.status = status;
35
+ this.body = body;
36
+ }
37
+ };
38
+ function isApiErrorStatus(error, status) {
39
+ return error instanceof ApiError && error.status === status;
40
+ }
28
41
  var ApiClient = class {
29
42
  baseUrl;
30
43
  token;
@@ -66,12 +79,12 @@ var ApiClient = class {
66
79
  });
67
80
  if (!response.ok) {
68
81
  const text = await response.text();
69
- throw new Error(`API request failed: ${response.status} ${text}`);
82
+ throw new ApiError(response.status, text);
70
83
  }
71
84
  if (response.status === 204) {
72
85
  return {};
73
86
  }
74
- return response.json();
87
+ return await response.json();
75
88
  }
76
89
  // Collections
77
90
  async getMe() {
@@ -104,6 +117,7 @@ var ApiClient = class {
104
117
  const qs = new URLSearchParams();
105
118
  if (params?.external_id) qs.set("external_id", params.external_id);
106
119
  if (params?.include_code) qs.set("include_code", "true");
120
+ if (params?.project_id) qs.set("project_id", params.project_id);
107
121
  qs.set("limit", "100");
108
122
  const all = [];
109
123
  let offset = 0;
@@ -166,6 +180,7 @@ var ApiClient = class {
166
180
  const query = new URLSearchParams();
167
181
  if (params?.collection_id) query.set("collection_id", params.collection_id);
168
182
  if (params?.external_id) query.set("external_id", params.external_id);
183
+ if (params?.project_id) query.set("project_id", params.project_id);
169
184
  const queryString = query.toString();
170
185
  const path = queryString ? `/api/pb/hooks?${queryString}` : "/api/pb/hooks";
171
186
  const result = await this.request(path);
@@ -325,5 +340,6 @@ function createApiClient(token, baseUrl, projectExternalId) {
325
340
  }
326
341
 
327
342
  export {
343
+ isApiErrorStatus,
328
344
  createApiClient
329
345
  };
@@ -2,9 +2,9 @@ import {
2
2
  deps,
3
3
  projectResourceDepsEnabled,
4
4
  syncDeps
5
- } from "./chunk-P5HFNAVN.js";
5
+ } from "./chunk-RUJRTLHA.js";
6
6
  import "./chunk-2CR762KB.js";
7
- import "./chunk-JKXLKK5I.js";
7
+ import "./chunk-WY6UMJNI.js";
8
8
  import "./chunk-ZH3NVYEQ.js";
9
9
  import "./chunk-FJFIWC7G.js";
10
10
  import "./chunk-PNKVD2UK.js";
@@ -4,13 +4,13 @@ import {
4
4
  import {
5
5
  projectResourceDepsEnabled,
6
6
  syncDeps
7
- } from "./chunk-P5HFNAVN.js";
7
+ } from "./chunk-RUJRTLHA.js";
8
8
  import {
9
9
  loadEnv
10
10
  } from "./chunk-2CR762KB.js";
11
11
  import {
12
12
  createApiClient
13
- } from "./chunk-JKXLKK5I.js";
13
+ } from "./chunk-WY6UMJNI.js";
14
14
  import {
15
15
  findProjectRoot,
16
16
  getApiUrl,
@@ -126,7 +126,12 @@ async function dev2(args) {
126
126
  const appTitle = getAppTitle(projectRoot);
127
127
  const apiUrl = getApiUrl();
128
128
  if (await projectResourceDepsEnabled(projectRoot)) {
129
- await syncDeps(projectRoot, { write: true, quiet: true, legacyWhenDisabled: false });
129
+ await syncDeps(projectRoot, {
130
+ write: true,
131
+ quiet: true,
132
+ legacyWhenDisabled: false,
133
+ ignorePermissionDenied: true
134
+ });
130
135
  }
131
136
  if (!flags["skip-setup"]) {
132
137
  const fresh = await isFreshProject(projectRoot);
package/dist/index.js CHANGED
@@ -219,39 +219,39 @@ async function main() {
219
219
  switch (command) {
220
220
  // Resource commands
221
221
  case "plan":
222
- await import("./resources-OP7EECKZ.js").then((m) => m.plan(args.slice(1)));
222
+ await import("./resources-UMGSN6QA.js").then((m) => m.plan(args.slice(1)));
223
223
  break;
224
224
  case "apply":
225
- await import("./resources-OP7EECKZ.js").then((m) => m.apply(args.slice(1)));
225
+ await import("./resources-UMGSN6QA.js").then((m) => m.apply(args.slice(1)));
226
226
  break;
227
227
  case "pull":
228
- await import("./resources-OP7EECKZ.js").then((m) => m.pull(args.slice(1)));
228
+ await import("./resources-UMGSN6QA.js").then((m) => m.pull(args.slice(1)));
229
229
  break;
230
230
  case "destroy":
231
- await import("./resources-OP7EECKZ.js").then((m) => m.destroy(args.slice(1)));
231
+ await import("./resources-UMGSN6QA.js").then((m) => m.destroy(args.slice(1)));
232
232
  break;
233
233
  case "list":
234
- await import("./resources-OP7EECKZ.js").then((m) => m.list(args.slice(1)));
234
+ await import("./resources-UMGSN6QA.js").then((m) => m.list(args.slice(1)));
235
235
  break;
236
236
  case "show":
237
- await import("./resources-OP7EECKZ.js").then((m) => m.show(args.slice(1)));
237
+ await import("./resources-UMGSN6QA.js").then((m) => m.show(args.slice(1)));
238
238
  break;
239
239
  case "diff":
240
- await import("./resources-OP7EECKZ.js").then((m) => m.diff(args.slice(1)));
240
+ await import("./resources-UMGSN6QA.js").then((m) => m.diff(args.slice(1)));
241
241
  break;
242
242
  // Development
243
243
  case "dev":
244
- await import("./dev-R43VQCZD.js").then((m) => m.dev(args.slice(1)));
244
+ await import("./dev-LDLUZ2VO.js").then((m) => m.dev(args.slice(1)));
245
245
  break;
246
246
  case "run":
247
- await import("./run-EJP5WCQU.js").then((m) => m.run(args.slice(1)));
247
+ await import("./run-C23KZI4Z.js").then((m) => m.run(args.slice(1)));
248
248
  break;
249
249
  // Project
250
250
  case "init":
251
- await import("./init-TDIQAOG4.js").then((m) => m.init(args.slice(1)));
251
+ await import("./init-7YWMMTBF.js").then((m) => m.init(args.slice(1)));
252
252
  break;
253
253
  case "register":
254
- await import("./register-JFJADKAJ.js").then((m) => m.register(args.slice(1)));
254
+ await import("./register-KJMSMMD6.js").then((m) => m.register(args.slice(1)));
255
255
  break;
256
256
  case "templates":
257
257
  await import("./templates-LNUOTNLN.js").then((m) => m.templates(subcommand, args.slice(2)));
@@ -264,11 +264,11 @@ async function main() {
264
264
  break;
265
265
  // Skills
266
266
  case "skills":
267
- await import("./skills-TNJHMV4F.js").then((m) => m.skills(subcommand, args.slice(2)));
267
+ await import("./skills-REOKLNEF.js").then((m) => m.skills(subcommand, args.slice(2)));
268
268
  break;
269
269
  // Dependencies
270
270
  case "deps":
271
- await import("./deps-EC3VRNN7.js").then((m) => m.deps(args.slice(1)));
271
+ await import("./deps-73XZXRYP.js").then((m) => m.deps(args.slice(1)));
272
272
  break;
273
273
  // Auth
274
274
  case "login":
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  installAllSkills,
3
3
  syncClaudeMd
4
- } from "./chunk-AUYOTENF.js";
4
+ } from "./chunk-HU7RYLUF.js";
5
5
  import {
6
6
  spinner
7
7
  } from "./chunk-BHYDYR75.js";
8
8
  import {
9
9
  createApiClient
10
- } from "./chunk-JKXLKK5I.js";
10
+ } from "./chunk-WY6UMJNI.js";
11
11
  import {
12
12
  getToken,
13
13
  init_auth,
@@ -287,19 +287,24 @@ async function init(args) {
287
287
  }
288
288
  projectName = response.projectName;
289
289
  }
290
- if (!/^[a-z0-9-]+$/.test(projectName)) {
290
+ if (!projectName) {
291
+ console.log(pc.red(" Error: Project name is required"));
292
+ process.exit(1);
293
+ }
294
+ const finalProjectName = projectName;
295
+ if (!/^[a-z0-9-]+$/.test(finalProjectName)) {
291
296
  console.log(pc.red(" Error: Project name must use lowercase letters, numbers, and hyphens only"));
292
297
  process.exit(1);
293
298
  }
294
299
  if (!directory) {
295
300
  if (nonInteractive) {
296
- directory = projectName;
301
+ directory = finalProjectName;
297
302
  } else {
298
303
  const response = await prompts({
299
304
  type: "text",
300
305
  name: "directory",
301
306
  message: "Where should we create the project?",
302
- initial: projectName
307
+ initial: finalProjectName
303
308
  });
304
309
  if (!response.directory) {
305
310
  console.log(pc.red("Cancelled"));
@@ -308,14 +313,19 @@ async function init(args) {
308
313
  directory = response.directory;
309
314
  }
310
315
  }
311
- const projectTitle = toTitleCase(projectName);
312
- const targetDir = resolve(process.cwd(), directory);
316
+ if (!directory) {
317
+ console.log(pc.red(" Error: Directory is required"));
318
+ process.exit(1);
319
+ }
320
+ const finalDirectory = directory;
321
+ const projectTitle = toTitleCase(finalProjectName);
322
+ const targetDir = resolve(process.cwd(), finalDirectory);
313
323
  if (existsSync(targetDir)) {
314
324
  if (nonInteractive) {
315
325
  if (opts.force) {
316
326
  rmSync(targetDir, { recursive: true });
317
327
  } else {
318
- console.log(pc.red(` Error: Directory ${directory} already exists`));
328
+ console.log(pc.red(` Error: Directory ${finalDirectory} already exists`));
319
329
  console.log(pc.dim(" Use --force (-f) to overwrite"));
320
330
  process.exit(1);
321
331
  }
@@ -323,7 +333,7 @@ async function init(args) {
323
333
  const { overwrite } = await prompts({
324
334
  type: "confirm",
325
335
  name: "overwrite",
326
- message: `Directory ${directory} already exists. Overwrite?`,
336
+ message: `Directory ${finalDirectory} already exists. Overwrite?`,
327
337
  initial: false
328
338
  });
329
339
  if (!overwrite) {
@@ -336,9 +346,9 @@ async function init(args) {
336
346
  mkdirSync(targetDir, { recursive: true });
337
347
  console.log();
338
348
  if (templateName !== "default") {
339
- console.log(pc.dim(` Creating ${projectName} from template ${pc.cyan(templateName)}...`));
349
+ console.log(pc.dim(` Creating ${finalProjectName} from template ${pc.cyan(templateName)}...`));
340
350
  } else {
341
- console.log(pc.dim(` Creating ${projectName}...`));
351
+ console.log(pc.dim(` Creating ${finalProjectName}...`));
342
352
  }
343
353
  console.log();
344
354
  const templatePkgPath = join(templateDir, "package.json");
@@ -346,9 +356,9 @@ async function init(args) {
346
356
  const sourceName = templatePkg.name || "my-lumera-app";
347
357
  const sourceTitle = templatePkg.lumera?.name || toTitleCase(sourceName);
348
358
  const replacements = [
349
- ["{{projectName}}", projectName],
359
+ ["{{projectName}}", finalProjectName],
350
360
  ["{{projectTitle}}", projectTitle],
351
- [sourceName, projectName],
361
+ [sourceName, finalProjectName],
352
362
  [sourceTitle, projectTitle]
353
363
  ];
354
364
  copyDir(templateDir, targetDir, replacements);
@@ -366,7 +376,7 @@ async function init(args) {
366
376
  listFiles(targetDir);
367
377
  if (isGitInstalled()) {
368
378
  const stopGit = spinner("Initializing git repository...");
369
- if (initGitRepo(targetDir, projectName)) {
379
+ if (initGitRepo(targetDir, finalProjectName)) {
370
380
  stopGit(pc.green("\u2713") + pc.dim(" Git repository initialized with initial commit"));
371
381
  } else {
372
382
  stopGit(pc.yellow("\u26A0") + pc.dim(" Failed to initialize git repository"));
@@ -428,7 +438,7 @@ async function init(args) {
428
438
  const api = createApiClient(token);
429
439
  const stopRegister = spinner("Registering project on Lumera...");
430
440
  try {
431
- const project = await api.registerProject(projectName);
441
+ const project = await api.registerProject(finalProjectName);
432
442
  setProjectId(targetDir, project.id);
433
443
  stopRegister(pc.green("\u2713") + pc.dim(` Project registered (${project.id})`));
434
444
  registered = true;
@@ -441,7 +451,7 @@ async function init(args) {
441
451
  console.log();
442
452
  console.log(pc.green(pc.bold(" Done!")), "Next steps:");
443
453
  console.log();
444
- console.log(pc.cyan(` cd ${directory}`));
454
+ console.log(pc.cyan(` cd ${finalDirectory}`));
445
455
  if (!opts.install) {
446
456
  console.log(pc.cyan(" pnpm install"));
447
457
  }
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-BHYDYR75.js";
7
7
  import {
8
8
  createApiClient
9
- } from "./chunk-JKXLKK5I.js";
9
+ } from "./chunk-WY6UMJNI.js";
10
10
  import {
11
11
  findProjectRoot,
12
12
  getAppName,
@@ -4,13 +4,13 @@ import {
4
4
  import {
5
5
  projectResourceDepsEnabled,
6
6
  syncDeps
7
- } from "./chunk-P5HFNAVN.js";
7
+ } from "./chunk-RUJRTLHA.js";
8
8
  import {
9
9
  loadEnv
10
10
  } from "./chunk-2CR762KB.js";
11
11
  import {
12
12
  createApiClient
13
- } from "./chunk-JKXLKK5I.js";
13
+ } from "./chunk-WY6UMJNI.js";
14
14
  import {
15
15
  findProjectRoot,
16
16
  getApiUrl,
@@ -29,8 +29,95 @@ import "./chunk-PNKVD2UK.js";
29
29
  import pc2 from "picocolors";
30
30
  import prompts from "prompts";
31
31
  import { execFileSync, execSync } from "child_process";
32
- import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from "fs";
33
- import { join, resolve } from "path";
32
+ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
33
+ import { join as join2, resolve } from "path";
34
+
35
+ // src/lib/lint/index.ts
36
+ import { existsSync, readFileSync, readdirSync } from "fs";
37
+ import { basename, join } from "path";
38
+
39
+ // src/lib/lint/rules/collection-schema.ts
40
+ function isRecord(value) {
41
+ return typeof value === "object" && value !== null && !Array.isArray(value);
42
+ }
43
+ function error(target, message, snippet) {
44
+ return {
45
+ ruleId: "collection-schema",
46
+ target,
47
+ severity: "error",
48
+ message,
49
+ snippet
50
+ };
51
+ }
52
+ var collectionSchemaRule = {
53
+ id: "collection-schema",
54
+ description: "Validates local collection JSON files before planning or applying resources.",
55
+ appliesTo: ["collection"],
56
+ check(target) {
57
+ let parsed;
58
+ try {
59
+ parsed = JSON.parse(target.source);
60
+ } catch (err) {
61
+ const message = err instanceof Error ? err.message : String(err);
62
+ return [error(target, `Invalid JSON: ${message}`)];
63
+ }
64
+ if (!isRecord(parsed)) {
65
+ return [error(target, "Collection file must contain a JSON object.")];
66
+ }
67
+ const issues = [];
68
+ const id = parsed.id;
69
+ const name = parsed.name;
70
+ const fields = parsed.fields;
71
+ const indexes = parsed.indexes;
72
+ if (typeof id !== "string" || id.trim() === "") {
73
+ issues.push(error(target, 'Missing required string field "id".'));
74
+ }
75
+ if (typeof name !== "string" || name.trim() === "") {
76
+ issues.push(error(target, 'Missing required string field "name".'));
77
+ } else {
78
+ if (/\s/.test(name)) {
79
+ issues.push(error(target, `Collection name "${name}" contains spaces; use underscores instead.`));
80
+ }
81
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
82
+ issues.push(error(target, `Collection name "${name}" contains invalid characters.`));
83
+ }
84
+ }
85
+ if (!Array.isArray(fields)) {
86
+ issues.push(error(target, 'Missing required array field "fields".'));
87
+ } else {
88
+ for (let i = 0; i < fields.length; i++) {
89
+ const field = fields[i];
90
+ if (!isRecord(field)) {
91
+ issues.push(error(target, `Field at index ${i} must be an object.`));
92
+ continue;
93
+ }
94
+ if (typeof field.name !== "string" || field.name.trim() === "") {
95
+ issues.push(error(target, `Field at index ${i} is missing required string field "name".`));
96
+ }
97
+ if (typeof field.type !== "string" || field.type.trim() === "") {
98
+ issues.push(error(target, `Field ${typeof field.name === "string" ? `"${field.name}"` : `at index ${i}`} is missing required string field "type".`));
99
+ }
100
+ }
101
+ }
102
+ if (indexes !== void 0) {
103
+ if (!Array.isArray(indexes)) {
104
+ issues.push(error(target, 'Optional field "indexes" must be an array when present.'));
105
+ } else {
106
+ for (let i = 0; i < indexes.length; i++) {
107
+ const index = indexes[i];
108
+ if (!isRecord(index)) {
109
+ issues.push(error(target, `Index at index ${i} must be an object.`));
110
+ continue;
111
+ }
112
+ if (!Array.isArray(index.fields) || !index.fields.every((field) => typeof field === "string" && field.trim() !== "")) {
113
+ issues.push(error(target, `Index at index ${i} must include a non-empty string array "fields".`));
114
+ }
115
+ }
116
+ }
117
+ }
118
+ return issues;
119
+ }
120
+ };
34
121
 
35
122
  // src/lib/lint/rules/llm-import.ts
36
123
  var FROM_LUMERA_IMPORT_LLM = /^\s*from\s+lumera\s+import\s+\(?\s*(?:[\w\s,]*?\b)?llm\b/;
@@ -75,25 +162,37 @@ var llmImportRule = {
75
162
  };
76
163
 
77
164
  // src/lib/lint/registry.ts
78
- var ALL_RULES = [llmImportRule];
165
+ var ALL_RULES = [collectionSchemaRule, llmImportRule];
79
166
 
80
167
  // src/lib/lint/format.ts
81
168
  import { relative } from "path";
82
169
  import pc from "picocolors";
83
170
  function printLintWarnings(warnings, projectRoot) {
84
171
  if (warnings.length === 0) return;
172
+ const errors = warnings.filter((w) => w.severity === "error");
173
+ const advisory = warnings.filter((w) => w.severity !== "error");
85
174
  console.log();
86
- console.log(pc.bold(" Warnings:"));
175
+ console.log(pc.bold(errors.length > 0 ? " Lint issues:" : " Warnings:"));
87
176
  for (const w of warnings) {
88
177
  const rel = relative(projectRoot, w.target.filePath);
89
178
  const loc = w.line ? `${rel}:${w.line}` : rel;
90
- console.log(` ${pc.yellow("\u26A0")} ${loc} ${pc.dim(`[${w.ruleId}]`)}`);
179
+ const isError = w.severity === "error";
180
+ const icon = isError ? pc.red("\u2717") : pc.yellow("\u26A0");
181
+ console.log(` ${icon} ${loc} ${pc.dim(`[${w.ruleId}]`)}`);
91
182
  console.log(` ${w.message}`);
92
183
  if (w.snippet) console.log(pc.dim(` > ${w.snippet}`));
93
184
  }
94
185
  console.log();
95
- const n = warnings.length;
96
- console.log(pc.dim(` ${n} warning${n === 1 ? "" : "s"} \u2014 advisory, will not block apply.`));
186
+ if (errors.length > 0) {
187
+ const n = errors.length;
188
+ console.log(pc.red(` ${n} error${n === 1 ? "" : "s"} \u2014 fix these before continuing.`));
189
+ if (advisory.length > 0) {
190
+ console.log(pc.dim(` ${advisory.length} warning${advisory.length === 1 ? "" : "s"} \u2014 advisory.`));
191
+ }
192
+ } else {
193
+ const n = advisory.length;
194
+ console.log(pc.dim(` ${n} warning${n === 1 ? "" : "s"} \u2014 advisory, will not block apply.`));
195
+ }
97
196
  console.log();
98
197
  }
99
198
  function serializeLintWarnings(warnings, projectRoot) {
@@ -101,6 +200,7 @@ function serializeLintWarnings(warnings, projectRoot) {
101
200
  ruleId: w.ruleId,
102
201
  target: { kind: w.target.kind, name: w.target.name, filePath: relative(projectRoot, w.target.filePath) },
103
202
  message: w.message,
203
+ severity: w.severity ?? "warning",
104
204
  line: w.line,
105
205
  snippet: w.snippet
106
206
  }));
@@ -131,6 +231,22 @@ function buildAutomationTargets(localAutomations) {
131
231
  source: a.code
132
232
  }));
133
233
  }
234
+ function buildCollectionTargets(platformDir, _filterName) {
235
+ const collectionsDir = join(platformDir, "collections");
236
+ if (!existsSync(collectionsDir)) return [];
237
+ const targets = [];
238
+ for (const entry of readdirSync(collectionsDir, { withFileTypes: true })) {
239
+ if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
240
+ const filePath = join(collectionsDir, entry.name);
241
+ targets.push({
242
+ kind: "collection",
243
+ name: basename(entry.name, ".json"),
244
+ filePath,
245
+ source: readFileSync(filePath, "utf-8")
246
+ });
247
+ }
248
+ return targets;
249
+ }
134
250
 
135
251
  // src/commands/resources.ts
136
252
  init_auth();
@@ -149,6 +265,13 @@ function safePrintLint(warnings, projectRoot) {
149
265
  if (process.env.LUMERA_DEBUG) console.error("[lint] print failed:", err);
150
266
  }
151
267
  }
268
+ function lintCollectionFiles(projectRoot, platformDir, filterName) {
269
+ const issues = runLint({ projectRoot, targets: buildCollectionTargets(platformDir, filterName) });
270
+ const errors = issues.filter((issue) => issue.severity === "error");
271
+ if (errors.length === 0) return;
272
+ printLintWarnings(issues, projectRoot);
273
+ throw new Error(`Found ${errors.length} collection lint error(s)`);
274
+ }
152
275
  function detectPackageManager() {
153
276
  for (const pm of ["bun", "pnpm", "yarn", "npm"]) {
154
277
  try {
@@ -171,6 +294,23 @@ function stripNamespacePrefix(name, appName) {
171
294
  }
172
295
  return name;
173
296
  }
297
+ function buildCollectionIdMap(collections, appName) {
298
+ const result = /* @__PURE__ */ new Map();
299
+ const add = (key, id) => {
300
+ const trimmed = (key || "").trim();
301
+ if (trimmed) result.set(trimmed, id);
302
+ };
303
+ for (const collection of collections) {
304
+ add(collection.id, collection.id);
305
+ add(collection.name, collection.id);
306
+ const stripped = stripNamespacePrefix(collection.name, appName);
307
+ add(stripped, collection.id);
308
+ if (collection.name.includes(NAMESPACE_SEPARATOR)) {
309
+ add(collection.name.split(NAMESPACE_SEPARATOR).slice(1).join(NAMESPACE_SEPARATOR), collection.id);
310
+ }
311
+ }
312
+ return result;
313
+ }
174
314
  function computeLineDiff(oldText, newText) {
175
315
  const oldLines = (oldText || "").trimEnd().split("\n");
176
316
  const newLines = (newText || "").trimEnd().split("\n");
@@ -568,29 +708,29 @@ function parseResource(resourcePath) {
568
708
  return { type, name };
569
709
  }
570
710
  function getPlatformDir() {
571
- if (existsSync(join(process.cwd(), "platform"))) {
572
- return join(process.cwd(), "platform");
711
+ if (existsSync2(join2(process.cwd(), "platform"))) {
712
+ return join2(process.cwd(), "platform");
573
713
  }
574
- if (existsSync(join(process.cwd(), "lumera_platform"))) {
575
- return join(process.cwd(), "lumera_platform");
714
+ if (existsSync2(join2(process.cwd(), "lumera_platform"))) {
715
+ return join2(process.cwd(), "lumera_platform");
576
716
  }
577
- return join(process.cwd(), "platform");
717
+ return join2(process.cwd(), "platform");
578
718
  }
579
719
  function toSafeFilename(name) {
580
720
  return name.replace(/\s+/g, "_").replace(/[^a-zA-Z0-9_-]/g, "").toLowerCase();
581
721
  }
582
722
  function loadLocalCollections(platformDir, filterName) {
583
- const collectionsDir = join(platformDir, "collections");
584
- if (!existsSync(collectionsDir)) {
723
+ const collectionsDir = join2(platformDir, "collections");
724
+ if (!existsSync2(collectionsDir)) {
585
725
  return [];
586
726
  }
587
727
  const collections = [];
588
728
  const errors = [];
589
- for (const file of readdirSync(collectionsDir)) {
729
+ for (const file of readdirSync2(collectionsDir)) {
590
730
  if (!file.endsWith(".json")) continue;
591
- const filePath = join(collectionsDir, file);
731
+ const filePath = join2(collectionsDir, file);
592
732
  try {
593
- const content = readFileSync(filePath, "utf-8");
733
+ const content = readFileSync2(filePath, "utf-8");
594
734
  const collection = JSON.parse(content);
595
735
  if (filterName && collection.name !== filterName && collection.id !== filterName) {
596
736
  continue;
@@ -611,6 +751,27 @@ function loadLocalCollections(platformDir, filterName) {
611
751
  errors.push(`${file}: collection name "${collection.name}" contains invalid characters`);
612
752
  continue;
613
753
  }
754
+ if (!Array.isArray(collection.fields)) {
755
+ errors.push(`${file}: missing fields array`);
756
+ continue;
757
+ }
758
+ for (let i = 0; i < collection.fields.length; i++) {
759
+ const field = collection.fields[i];
760
+ if (!field || typeof field !== "object") {
761
+ errors.push(`${file}: field at index ${i} must be an object`);
762
+ continue;
763
+ }
764
+ if (typeof field.name !== "string" || field.name.trim() === "") {
765
+ errors.push(`${file}: field at index ${i} is missing name`);
766
+ }
767
+ if (typeof field.type !== "string" || field.type.trim() === "") {
768
+ errors.push(`${file}: field ${typeof field.name === "string" ? `"${field.name}"` : `at index ${i}`} is missing type`);
769
+ }
770
+ }
771
+ if (collection.indexes !== void 0 && !Array.isArray(collection.indexes)) {
772
+ errors.push(`${file}: indexes must be an array when present`);
773
+ continue;
774
+ }
614
775
  const existingById = collections.find((c) => c.id === collection.id);
615
776
  if (existingById) {
616
777
  errors.push(`${file}: duplicate collection id "${collection.id}" (also defined in another file)`);
@@ -636,27 +797,27 @@ function loadLocalCollections(platformDir, filterName) {
636
797
  return collections;
637
798
  }
638
799
  function loadLocalAutomations(platformDir, filterName, appName) {
639
- const automationsDir = join(platformDir, "automations");
640
- if (!existsSync(automationsDir)) {
800
+ const automationsDir = join2(platformDir, "automations");
801
+ if (!existsSync2(automationsDir)) {
641
802
  return [];
642
803
  }
643
804
  const automations = [];
644
805
  const errors = [];
645
- for (const entry of readdirSync(automationsDir, { withFileTypes: true })) {
806
+ for (const entry of readdirSync2(automationsDir, { withFileTypes: true })) {
646
807
  if (!entry.isDirectory()) continue;
647
- const automationDir = join(automationsDir, entry.name);
648
- const configPath = join(automationDir, "config.json");
649
- const mainPath = join(automationDir, "main.py");
650
- if (!existsSync(configPath)) {
808
+ const automationDir = join2(automationsDir, entry.name);
809
+ const configPath = join2(automationDir, "config.json");
810
+ const mainPath = join2(automationDir, "main.py");
811
+ if (!existsSync2(configPath)) {
651
812
  errors.push(`${entry.name}: missing config.json`);
652
813
  continue;
653
814
  }
654
- if (!existsSync(mainPath)) {
815
+ if (!existsSync2(mainPath)) {
655
816
  errors.push(`${entry.name}: missing main.py`);
656
817
  continue;
657
818
  }
658
819
  try {
659
- const configContent = readFileSync(configPath, "utf-8");
820
+ const configContent = readFileSync2(configPath, "utf-8");
660
821
  const rawConfig = JSON.parse(configContent);
661
822
  if (filterName && rawConfig.external_id !== filterName && rawConfig.name !== filterName && entry.name !== filterName) {
662
823
  continue;
@@ -683,7 +844,7 @@ function loadLocalAutomations(platformDir, filterName, appName) {
683
844
  errors.push(`${entry.name}: duplicate external_id "${config.external_id}" (also defined in ${existingByExtId.automation.name})`);
684
845
  continue;
685
846
  }
686
- let code = readFileSync(mainPath, "utf-8");
847
+ let code = readFileSync2(mainPath, "utf-8");
687
848
  if (appName) {
688
849
  code = code.replaceAll("{{app}}", appName);
689
850
  }
@@ -702,15 +863,15 @@ function loadLocalAutomations(platformDir, filterName, appName) {
702
863
  return automations;
703
864
  }
704
865
  function loadLocalHooks(platformDir, filterName, appName) {
705
- const hooksDir = join(platformDir, "hooks");
706
- if (!existsSync(hooksDir)) {
866
+ const hooksDir = join2(platformDir, "hooks");
867
+ if (!existsSync2(hooksDir)) {
707
868
  return [];
708
869
  }
709
870
  const hooks = [];
710
- for (const file of readdirSync(hooksDir)) {
871
+ for (const file of readdirSync2(hooksDir)) {
711
872
  if (!file.endsWith(".js") && !file.endsWith(".ts")) continue;
712
- const filePath = join(hooksDir, file);
713
- const content = readFileSync(filePath, "utf-8");
873
+ const filePath = join2(hooksDir, file);
874
+ const content = readFileSync2(filePath, "utf-8");
714
875
  const config = parseHookConfig(content);
715
876
  if (!config) {
716
877
  console.log(pc2.yellow(` \u26A0 Skipping ${file}: could not parse config export`));
@@ -830,6 +991,16 @@ function mapFieldType(type) {
830
991
  };
831
992
  return typeMap[type] || type;
832
993
  }
994
+ function normalizeConstraintValue(fieldType, value) {
995
+ if (value === void 0 || value === null) return void 0;
996
+ if (["text", "email", "url", "editor"].includes(fieldType) && value === 0) {
997
+ return void 0;
998
+ }
999
+ if (fieldType === "date" && value === "") {
1000
+ return void 0;
1001
+ }
1002
+ return value;
1003
+ }
833
1004
  function fieldsDiffer(local, remote) {
834
1005
  if (mapFieldType(local.type) !== remote.type) return true;
835
1006
  if ((local.required || false) !== (remote.required || false)) return true;
@@ -858,11 +1029,11 @@ function fieldsDiffer(local, remote) {
858
1029
  const remoteDecimalPrecision = opts.decimalPrecision !== void 0 ? opts.decimalPrecision : 2;
859
1030
  if (localDecimalPrecision !== remoteDecimalPrecision) return true;
860
1031
  }
861
- const localMin = local.min !== void 0 ? local.min : void 0;
862
- const remoteMin = opts.min !== void 0 ? opts.min : void 0;
1032
+ const localMin = normalizeConstraintValue(local.type, local.min);
1033
+ const remoteMin = normalizeConstraintValue(local.type, opts.min);
863
1034
  if (localMin !== remoteMin) return true;
864
- const localMax = local.max !== void 0 ? local.max : void 0;
865
- const remoteMax = opts.max !== void 0 ? opts.max : void 0;
1035
+ const localMax = normalizeConstraintValue(local.type, local.max);
1036
+ const remoteMax = normalizeConstraintValue(local.type, opts.max);
866
1037
  if (localMax !== remoteMax) return true;
867
1038
  return false;
868
1039
  }
@@ -956,9 +1127,12 @@ async function planCollections(api, localCollections) {
956
1127
  }
957
1128
  return changes;
958
1129
  }
959
- async function planAutomations(api, localAutomations) {
1130
+ async function planAutomations(api, localAutomations, projectId) {
960
1131
  const changes = [];
961
- const remoteAutomations = await api.listAutomations({ include_code: true });
1132
+ const remoteAutomations = await api.listAutomations({
1133
+ include_code: true,
1134
+ ...projectId ? { project_id: projectId } : {}
1135
+ });
962
1136
  const remoteByExternalId = new Map(remoteAutomations.filter((a) => a.external_id).map((a) => [a.external_id, a]));
963
1137
  const remotePresetCache = /* @__PURE__ */ new Map();
964
1138
  const getRemotePresets = (automationId) => {
@@ -1026,12 +1200,12 @@ async function planAutomations(api, localAutomations) {
1026
1200
  return changes;
1027
1201
  }
1028
1202
  function loadLocalMailboxes(platformDir, filterName) {
1029
- const mailboxesDir = join(platformDir, "mailboxes");
1030
- if (!existsSync(mailboxesDir)) {
1203
+ const mailboxesDir = join2(platformDir, "mailboxes");
1204
+ if (!existsSync2(mailboxesDir)) {
1031
1205
  return [];
1032
1206
  }
1033
1207
  const mailboxes = [];
1034
- for (const file of readdirSync(mailboxesDir)) {
1208
+ for (const file of readdirSync2(mailboxesDir)) {
1035
1209
  if (!file.endsWith(".json")) continue;
1036
1210
  const slug = file.replace(/\.json$/, "").trim();
1037
1211
  if (!slug) {
@@ -1041,10 +1215,10 @@ function loadLocalMailboxes(platformDir, filterName) {
1041
1215
  if (filterName && slug !== filterName) {
1042
1216
  continue;
1043
1217
  }
1044
- const filePath = join(mailboxesDir, file);
1218
+ const filePath = join2(mailboxesDir, file);
1045
1219
  let raw;
1046
1220
  try {
1047
- raw = JSON.parse(readFileSync(filePath, "utf-8"));
1221
+ raw = JSON.parse(readFileSync2(filePath, "utf-8"));
1048
1222
  } catch (e) {
1049
1223
  console.log(pc2.yellow(` \u26A0 Skipping ${file}: invalid JSON (${e.message})`));
1050
1224
  continue;
@@ -1123,8 +1297,8 @@ async function pullMailboxes(api, platformDir, filterName) {
1123
1297
  console.log(pc2.dim(" (no mailboxes)"));
1124
1298
  return;
1125
1299
  }
1126
- const outDir = join(platformDir, "mailboxes");
1127
- if (!existsSync(outDir)) {
1300
+ const outDir = join2(platformDir, "mailboxes");
1301
+ if (!existsSync2(outDir)) {
1128
1302
  mkdirSync(outDir, { recursive: true });
1129
1303
  }
1130
1304
  let count = 0;
@@ -1132,7 +1306,7 @@ async function pullMailboxes(api, platformDir, filterName) {
1132
1306
  if (filterName && mb.slug !== filterName) continue;
1133
1307
  const body = {};
1134
1308
  if (mb.description) body.description = mb.description;
1135
- const file = join(outDir, `${toSafeFilename(mb.slug)}.json`);
1309
+ const file = join2(outDir, `${toSafeFilename(mb.slug)}.json`);
1136
1310
  writeFileSync(file, JSON.stringify(body, null, 2) + "\n");
1137
1311
  console.log(pc2.green(" \u2713"), `pulled mailbox ${mb.slug} ${pc2.dim(`\u2192 ${file}`)}`);
1138
1312
  count++;
@@ -1141,9 +1315,9 @@ async function pullMailboxes(api, platformDir, filterName) {
1141
1315
  console.log(pc2.yellow(` \u26A0 mailbox '${filterName}' not found on remote`));
1142
1316
  }
1143
1317
  }
1144
- async function planHooks(api, localHooks, collections) {
1318
+ async function planHooks(api, localHooks, collections, projectId) {
1145
1319
  const changes = [];
1146
- const remoteHooks = await api.listHooks();
1320
+ const remoteHooks = await api.listHooks(projectId ? { project_id: projectId } : void 0);
1147
1321
  const remoteByExternalId = new Map(remoteHooks.filter((h) => h.external_id).map((h) => [h.external_id, h]));
1148
1322
  for (const { hook, script, fileName } of localHooks) {
1149
1323
  const remote = remoteByExternalId.get(hook.external_id);
@@ -1231,7 +1405,7 @@ async function applyCollections(api, localCollections) {
1231
1405
  }
1232
1406
  async function applyAutomations(api, localAutomations, projectId) {
1233
1407
  let errors = 0;
1234
- const remoteAutomations = await api.listAutomations();
1408
+ const remoteAutomations = await api.listAutomations(projectId ? { project_id: projectId } : void 0);
1235
1409
  const remoteByExternalId = new Map(remoteAutomations.filter((a) => a.external_id).map((a) => [a.external_id, a]));
1236
1410
  for (const { automation, code } of localAutomations) {
1237
1411
  const remote = remoteByExternalId.get(automation.external_id);
@@ -1343,7 +1517,7 @@ async function setSchedule(api, automationId, schedule, localPresets) {
1343
1517
  }
1344
1518
  async function applyHooks(api, localHooks, collections, projectId) {
1345
1519
  let errors = 0;
1346
- const remoteHooks = await api.listHooks();
1520
+ const remoteHooks = await api.listHooks(projectId ? { project_id: projectId } : void 0);
1347
1521
  const remoteByExternalId = new Map(remoteHooks.filter((h) => h.external_id).map((h) => [h.external_id, h]));
1348
1522
  for (const { hook, script, fileName } of localHooks) {
1349
1523
  const remote = remoteByExternalId.get(hook.external_id);
@@ -1401,7 +1575,7 @@ async function applyApp(args) {
1401
1575
  await deploy({ token, appName, appTitle, distDir, apiUrl });
1402
1576
  }
1403
1577
  async function pullCollections(api, platformDir, filterName, appName) {
1404
- const collectionsDir = join(platformDir, "collections");
1578
+ const collectionsDir = join2(platformDir, "collections");
1405
1579
  mkdirSync(collectionsDir, { recursive: true });
1406
1580
  const collections = await api.listCollections();
1407
1581
  for (const collection of collections) {
@@ -1445,15 +1619,18 @@ async function pullCollections(api, platformDir, filterName, appName) {
1445
1619
  }).filter((idx) => idx !== null)
1446
1620
  };
1447
1621
  const fileName = toSafeFilename(localName);
1448
- const filePath = join(collectionsDir, `${fileName}.json`);
1622
+ const filePath = join2(collectionsDir, `${fileName}.json`);
1449
1623
  writeFileSync(filePath, JSON.stringify(localFormat, null, 2) + "\n");
1450
1624
  console.log(pc2.green(" \u2713"), `${localName} \u2192 collections/${fileName}.json`);
1451
1625
  }
1452
1626
  }
1453
1627
  async function pullAutomations(api, platformDir, filterName, projectId) {
1454
- const automationsDir = join(platformDir, "automations");
1628
+ const automationsDir = join2(platformDir, "automations");
1455
1629
  mkdirSync(automationsDir, { recursive: true });
1456
- const automations = await api.listAutomations({ include_code: true });
1630
+ const automations = await api.listAutomations({
1631
+ include_code: true,
1632
+ ...projectId ? { project_id: projectId } : {}
1633
+ });
1457
1634
  for (const automation of automations) {
1458
1635
  if (!automation.external_id || automation.managed) continue;
1459
1636
  if (projectId && automation.project_id && automation.project_id !== projectId) continue;
@@ -1462,7 +1639,7 @@ async function pullAutomations(api, platformDir, filterName, projectId) {
1462
1639
  continue;
1463
1640
  }
1464
1641
  const dirName = automation.external_id.replace(/[^a-zA-Z0-9_-]/g, "_");
1465
- const automationDir = join(automationsDir, dirName);
1642
+ const automationDir = join2(automationsDir, dirName);
1466
1643
  mkdirSync(automationDir, { recursive: true });
1467
1644
  const config = {
1468
1645
  external_id: automation.external_id,
@@ -1495,15 +1672,15 @@ async function pullAutomations(api, platformDir, filterName, projectId) {
1495
1672
  }
1496
1673
  } catch {
1497
1674
  }
1498
- writeFileSync(join(automationDir, "config.json"), JSON.stringify(config, null, 2) + "\n");
1499
- writeFileSync(join(automationDir, "main.py"), automation.code || "");
1675
+ writeFileSync(join2(automationDir, "config.json"), JSON.stringify(config, null, 2) + "\n");
1676
+ writeFileSync(join2(automationDir, "main.py"), automation.code || "");
1500
1677
  console.log(pc2.green(" \u2713"), `${automation.name} \u2192 automations/${dirName}/`);
1501
1678
  }
1502
1679
  }
1503
1680
  async function pullHooks(api, platformDir, filterName, appName, projectId) {
1504
- const hooksDir = join(platformDir, "hooks");
1681
+ const hooksDir = join2(platformDir, "hooks");
1505
1682
  mkdirSync(hooksDir, { recursive: true });
1506
- const hooks = await api.listHooks();
1683
+ const hooks = await api.listHooks(projectId ? { project_id: projectId } : void 0);
1507
1684
  for (const hook of hooks) {
1508
1685
  if (!hook.external_id) continue;
1509
1686
  if (projectId && hook.project_id && hook.project_id !== projectId) continue;
@@ -1523,31 +1700,31 @@ export default async function handler({ record, app, http }) {
1523
1700
  ${hook.script.split("\n").map((line) => " " + line).join("\n")}
1524
1701
  }
1525
1702
  `;
1526
- writeFileSync(join(hooksDir, fileName), content);
1703
+ writeFileSync(join2(hooksDir, fileName), content);
1527
1704
  console.log(pc2.green(" \u2713"), `${hook.name} \u2192 hooks/${fileName}`);
1528
1705
  }
1529
1706
  }
1530
1707
  function loadLocalAgents(platformDir, filterName, appName) {
1531
- const agentsDir = join(platformDir, "agents");
1532
- if (!existsSync(agentsDir)) return [];
1708
+ const agentsDir = join2(platformDir, "agents");
1709
+ if (!existsSync2(agentsDir)) return [];
1533
1710
  const agents = [];
1534
1711
  const errors = [];
1535
- for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
1712
+ for (const entry of readdirSync2(agentsDir, { withFileTypes: true })) {
1536
1713
  if (!entry.isDirectory()) continue;
1537
- const agentDir = join(agentsDir, entry.name);
1538
- const configPath = join(agentDir, "config.json");
1539
- const promptPath = join(agentDir, "system_prompt.md");
1540
- const policyPath = join(agentDir, "policy.js");
1541
- if (!existsSync(configPath)) {
1714
+ const agentDir = join2(agentsDir, entry.name);
1715
+ const configPath = join2(agentDir, "config.json");
1716
+ const promptPath = join2(agentDir, "system_prompt.md");
1717
+ const policyPath = join2(agentDir, "policy.js");
1718
+ if (!existsSync2(configPath)) {
1542
1719
  errors.push(`${entry.name}: missing config.json`);
1543
1720
  continue;
1544
1721
  }
1545
- if (!existsSync(promptPath)) {
1722
+ if (!existsSync2(promptPath)) {
1546
1723
  errors.push(`${entry.name}: missing system_prompt.md`);
1547
1724
  continue;
1548
1725
  }
1549
1726
  try {
1550
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
1727
+ const config = JSON.parse(readFileSync2(configPath, "utf-8"));
1551
1728
  if (filterName && config.external_id !== filterName && config.name !== filterName && entry.name !== filterName) {
1552
1729
  continue;
1553
1730
  }
@@ -1570,8 +1747,8 @@ function loadLocalAgents(platformDir, filterName, appName) {
1570
1747
  errors.push(`${entry.name}: missing name in config.json`);
1571
1748
  continue;
1572
1749
  }
1573
- let systemPrompt = readFileSync(promptPath, "utf-8");
1574
- let policyScript = existsSync(policyPath) ? readFileSync(policyPath, "utf-8") : "";
1750
+ let systemPrompt = readFileSync2(promptPath, "utf-8");
1751
+ let policyScript = existsSync2(policyPath) ? readFileSync2(policyPath, "utf-8") : "";
1575
1752
  if (appName) {
1576
1753
  systemPrompt = systemPrompt.replaceAll("{{app}}", appName);
1577
1754
  policyScript = policyScript.replaceAll("{{app}}", appName);
@@ -1701,7 +1878,7 @@ async function applyAgents(api, localAgents, projectId) {
1701
1878
  return errors;
1702
1879
  }
1703
1880
  async function pullAgents(api, platformDir, filterName, projectId) {
1704
- const agentsDir = join(platformDir, "agents");
1881
+ const agentsDir = join2(platformDir, "agents");
1705
1882
  mkdirSync(agentsDir, { recursive: true });
1706
1883
  const agents = await api.listAgents(projectId ? { project_id: projectId } : void 0);
1707
1884
  let skillIdToSlug = /* @__PURE__ */ new Map();
@@ -1716,7 +1893,7 @@ async function pullAgents(api, platformDir, filterName, projectId) {
1716
1893
  continue;
1717
1894
  }
1718
1895
  const dirName = agent.external_id.replace(/[^a-zA-Z0-9_-]/g, "_");
1719
- const agentDir = join(agentsDir, dirName);
1896
+ const agentDir = join2(agentsDir, dirName);
1720
1897
  mkdirSync(agentDir, { recursive: true });
1721
1898
  const skillSlugs = [];
1722
1899
  if (agent.skill_ids) {
@@ -1733,10 +1910,10 @@ async function pullAgents(api, platformDir, filterName, projectId) {
1733
1910
  if (agent.model) config.model = agent.model;
1734
1911
  if (skillSlugs.length > 0) config.skills = skillSlugs;
1735
1912
  if (agent.policy_enabled) config.policy_enabled = true;
1736
- writeFileSync(join(agentDir, "config.json"), JSON.stringify(config, null, 2) + "\n");
1737
- writeFileSync(join(agentDir, "system_prompt.md"), agent.system_prompt || "");
1913
+ writeFileSync(join2(agentDir, "config.json"), JSON.stringify(config, null, 2) + "\n");
1914
+ writeFileSync(join2(agentDir, "system_prompt.md"), agent.system_prompt || "");
1738
1915
  if (agent.policy_script) {
1739
- writeFileSync(join(agentDir, "policy.js"), agent.policy_script);
1916
+ writeFileSync(join2(agentDir, "policy.js"), agent.policy_script);
1740
1917
  }
1741
1918
  console.log(pc2.green(" \u2713"), `${agent.name} \u2192 agents/${dirName}/`);
1742
1919
  }
@@ -1781,7 +1958,10 @@ async function listResources(api, platformDir, filterType, appName, projectId) {
1781
1958
  }
1782
1959
  if (!filterType || filterType === "automations") {
1783
1960
  const localAutomations = loadLocalAutomations(platformDir, void 0, appName);
1784
- const remoteAutomations = await api.listAutomations({ include_code: true });
1961
+ const remoteAutomations = await api.listAutomations({
1962
+ include_code: true,
1963
+ ...projectId ? { project_id: projectId } : {}
1964
+ });
1785
1965
  const remoteByExternalId = new Map(remoteAutomations.filter((a) => a.external_id && !a.managed).map((a) => [a.external_id, a]));
1786
1966
  const localIds = new Set(localAutomations.map((a) => a.automation.external_id));
1787
1967
  for (const { automation, code } of localAutomations) {
@@ -1807,7 +1987,7 @@ async function listResources(api, platformDir, filterType, appName, projectId) {
1807
1987
  }
1808
1988
  if (!filterType || filterType === "hooks") {
1809
1989
  const localHooks = loadLocalHooks(platformDir, void 0, appName);
1810
- const remoteHooks = await api.listHooks();
1990
+ const remoteHooks = await api.listHooks(projectId ? { project_id: projectId } : void 0);
1811
1991
  const remoteByExternalId = new Map(remoteHooks.filter((h) => h.external_id).map((h) => [h.external_id, h]));
1812
1992
  const localIds = new Set(localHooks.map((h) => h.hook.external_id));
1813
1993
  for (const { hook, script } of localHooks) {
@@ -1967,7 +2147,7 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
1967
2147
  if (!resourceType || resourceType === "automations") {
1968
2148
  try {
1969
2149
  const localAutomations = loadLocalAutomations(platformDir, resourceName || void 0, appName);
1970
- const remoteAutomations = await api.listAutomations();
2150
+ const remoteAutomations = await api.listAutomations(projectId ? { project_id: projectId } : void 0);
1971
2151
  const remoteByExternalId = new Map(remoteAutomations.filter((a) => a.external_id).map((a) => [a.external_id, a]));
1972
2152
  for (const { automation } of localAutomations) {
1973
2153
  const remote = remoteByExternalId.get(automation.external_id);
@@ -1980,7 +2160,7 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
1980
2160
  }
1981
2161
  if (!resourceType || resourceType === "hooks") {
1982
2162
  const localHooks = loadLocalHooks(platformDir, resourceName || void 0, appName);
1983
- const remoteHooks = await api.listHooks();
2163
+ const remoteHooks = await api.listHooks(projectId ? { project_id: projectId } : void 0);
1984
2164
  const remoteByExternalId = new Map(remoteHooks.filter((h) => h.external_id).map((h) => [h.external_id, h]));
1985
2165
  for (const { hook } of localHooks) {
1986
2166
  const remote = remoteByExternalId.get(hook.external_id);
@@ -2221,7 +2401,7 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
2221
2401
  console.log();
2222
2402
  } else if (resourceType === "automations") {
2223
2403
  const localAutomations = loadLocalAutomations(platformDir, resourceName, appName);
2224
- const remoteAutomations = await api.listAutomations();
2404
+ const remoteAutomations = await api.listAutomations(projectId ? { project_id: projectId } : void 0);
2225
2405
  const local = localAutomations[0];
2226
2406
  const remote = remoteAutomations.find((a) => a.external_id === resourceName || a.name === resourceName);
2227
2407
  if (!local && !remote) {
@@ -2244,7 +2424,7 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
2244
2424
  console.log();
2245
2425
  } else if (resourceType === "hooks") {
2246
2426
  const localHooks = loadLocalHooks(platformDir, resourceName, appName);
2247
- const remoteHooks = await api.listHooks();
2427
+ const remoteHooks = await api.listHooks(projectId ? { project_id: projectId } : void 0);
2248
2428
  const local = localHooks[0];
2249
2429
  const remote = remoteHooks.find((h) => h.external_id === resourceName);
2250
2430
  if (!local && !remote) {
@@ -2374,12 +2554,15 @@ async function plan(args) {
2374
2554
  console.log(pc2.cyan(pc2.bold(" Plan")));
2375
2555
  console.log(pc2.dim(" Comparing local files to remote state..."));
2376
2556
  console.log();
2377
- await syncDeps(projectRoot);
2557
+ if (!type || type === "collections") {
2558
+ lintCollectionFiles(projectRoot, platformDir, name || void 0);
2559
+ }
2560
+ await syncDeps(projectRoot, { ignorePermissionDenied: true });
2378
2561
  const allChanges = [];
2379
2562
  let collections;
2380
2563
  try {
2381
2564
  const remoteCollections = await api.listCollections();
2382
- collections = new Map(remoteCollections.map((c) => [c.name, c.id]));
2565
+ collections = buildCollectionIdMap(remoteCollections, appName);
2383
2566
  } catch {
2384
2567
  collections = /* @__PURE__ */ new Map();
2385
2568
  }
@@ -2394,14 +2577,14 @@ async function plan(args) {
2394
2577
  if (!type || type === "automations") {
2395
2578
  localAutomations = loadLocalAutomations(platformDir, name || void 0, appName);
2396
2579
  if (localAutomations.length > 0) {
2397
- const changes = await planAutomations(api, localAutomations);
2580
+ const changes = await planAutomations(api, localAutomations, projectId);
2398
2581
  allChanges.push(...changes);
2399
2582
  }
2400
2583
  }
2401
2584
  if (!type || type === "hooks") {
2402
2585
  const localHooks = loadLocalHooks(platformDir, name || void 0, appName);
2403
2586
  if (localHooks.length > 0) {
2404
- const changes = await planHooks(api, localHooks, collections);
2587
+ const changes = await planHooks(api, localHooks, collections, projectId);
2405
2588
  allChanges.push(...changes);
2406
2589
  }
2407
2590
  }
@@ -2509,12 +2692,14 @@ async function apply(args) {
2509
2692
  console.log();
2510
2693
  return;
2511
2694
  }
2512
- await syncDeps(projectRoot);
2695
+ if (!type || type === "collections") {
2696
+ lintCollectionFiles(projectRoot, platformDir, name || void 0);
2697
+ }
2698
+ await syncDeps(projectRoot, { ignorePermissionDenied: true });
2513
2699
  let collections;
2514
2700
  try {
2515
2701
  const remoteCollections = await api.listCollections();
2516
- collections = new Map(remoteCollections.map((c) => [c.name, c.id]));
2517
- for (const c of remoteCollections) collections.set(c.id, c.id);
2702
+ collections = buildCollectionIdMap(remoteCollections, appName);
2518
2703
  } catch {
2519
2704
  collections = /* @__PURE__ */ new Map();
2520
2705
  }
@@ -2525,8 +2710,8 @@ async function apply(args) {
2525
2710
  const localAgents = !type || type === "agents" ? loadLocalAgents(platformDir, name || void 0, appName) : [];
2526
2711
  const localMailboxes = !type || type === "mailboxes" ? loadLocalMailboxes(platformDir, name || void 0) : [];
2527
2712
  if (localCollections.length > 0) allChanges.push(...await planCollections(api, localCollections));
2528
- if (localAutomations.length > 0) allChanges.push(...await planAutomations(api, localAutomations));
2529
- if (localHooks.length > 0) allChanges.push(...await planHooks(api, localHooks, collections));
2713
+ if (localAutomations.length > 0) allChanges.push(...await planAutomations(api, localAutomations, projectId));
2714
+ if (localHooks.length > 0) allChanges.push(...await planHooks(api, localHooks, collections, projectId));
2530
2715
  if (localAgents.length > 0) allChanges.push(...await planAgents(api, localAgents, projectId));
2531
2716
  if (localMailboxes.length > 0) allChanges.push(...await planMailboxes(api, localMailboxes));
2532
2717
  if (name) {
@@ -2540,7 +2725,7 @@ async function apply(args) {
2540
2725
  let willDeployApp = false;
2541
2726
  if (!type) {
2542
2727
  try {
2543
- if (existsSync(join(projectRoot, "dist")) || existsSync(join(projectRoot, "src"))) {
2728
+ if (existsSync2(join2(projectRoot, "dist")) || existsSync2(join2(projectRoot, "src"))) {
2544
2729
  willDeployApp = true;
2545
2730
  }
2546
2731
  } catch {
@@ -2604,8 +2789,7 @@ async function apply(args) {
2604
2789
  }
2605
2790
  try {
2606
2791
  const remoteCollections = await api.listCollections();
2607
- collections = new Map(remoteCollections.map((c) => [c.name, c.id]));
2608
- for (const c of remoteCollections) collections.set(c.id, c.id);
2792
+ collections = buildCollectionIdMap(remoteCollections, appName);
2609
2793
  } catch {
2610
2794
  }
2611
2795
  if (localAutomations.length > 0) {
@@ -2666,7 +2850,7 @@ async function pull(args) {
2666
2850
  let collections;
2667
2851
  try {
2668
2852
  const remoteCollections = await api.listCollections();
2669
- collections = new Map(remoteCollections.map((c) => [c.name, c.id]));
2853
+ collections = buildCollectionIdMap(remoteCollections, appName);
2670
2854
  } catch {
2671
2855
  collections = /* @__PURE__ */ new Map();
2672
2856
  }
@@ -2682,7 +2866,7 @@ async function pull(args) {
2682
2866
  if (!type || type === "automations") {
2683
2867
  const localAutomations = loadLocalAutomations(platformDir, name || void 0, appName);
2684
2868
  if (localAutomations.length > 0) {
2685
- const changes = await planAutomations(api, localAutomations);
2869
+ const changes = await planAutomations(api, localAutomations, projectId);
2686
2870
  for (const c of changes) {
2687
2871
  if (c.type === "update") conflicts.push(`automations/${c.name}`);
2688
2872
  }
@@ -2691,7 +2875,7 @@ async function pull(args) {
2691
2875
  if (!type || type === "hooks") {
2692
2876
  const localHooks = loadLocalHooks(platformDir, name || void 0, appName);
2693
2877
  if (localHooks.length > 0) {
2694
- const changes = await planHooks(api, localHooks, collections);
2878
+ const changes = await planHooks(api, localHooks, collections, projectId);
2695
2879
  for (const c of changes) {
2696
2880
  if (c.type === "update") conflicts.push(`hooks/${c.name}`);
2697
2881
  }
@@ -2720,7 +2904,7 @@ async function pull(args) {
2720
2904
  console.log();
2721
2905
  if (!name && await projectResourceDepsEnabled(projectRoot)) {
2722
2906
  console.log(pc2.bold(" Resource shares:"));
2723
- await syncDeps(projectRoot, { write: true, legacyWhenDisabled: false });
2907
+ await syncDeps(projectRoot, { write: true, legacyWhenDisabled: false, ignorePermissionDenied: true });
2724
2908
  console.log();
2725
2909
  }
2726
2910
  }
@@ -2970,7 +3154,10 @@ async function diff(args) {
2970
3154
  }
2971
3155
  } else if (type === "automations") {
2972
3156
  const localAutomations = loadLocalAutomations(platformDir, name, appName);
2973
- const remoteAutomations = await api.listAutomations({ include_code: true });
3157
+ const remoteAutomations = await api.listAutomations({
3158
+ include_code: true,
3159
+ ...projectId ? { project_id: projectId } : {}
3160
+ });
2974
3161
  const local = localAutomations[0];
2975
3162
  const localExtId = local?.automation.external_id;
2976
3163
  const remote = remoteAutomations.find(
@@ -3000,7 +3187,7 @@ async function diff(args) {
3000
3187
  }
3001
3188
  } else if (type === "hooks") {
3002
3189
  const localHooks = loadLocalHooks(platformDir, name, appName);
3003
- const remoteHooks = await api.listHooks();
3190
+ const remoteHooks = await api.listHooks(projectId ? { project_id: projectId } : void 0);
3004
3191
  const local = localHooks[0];
3005
3192
  const localExtId = local?.hook.external_id;
3006
3193
  const remote = remoteHooks.find(
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-2CR762KB.js";
4
4
  import {
5
5
  createApiClient
6
- } from "./chunk-JKXLKK5I.js";
6
+ } from "./chunk-WY6UMJNI.js";
7
7
  import {
8
8
  findProjectRoot,
9
9
  getApiUrl,
@@ -8,7 +8,7 @@ import {
8
8
  slugToDirName,
9
9
  slugToFilename,
10
10
  syncClaudeMd
11
- } from "./chunk-AUYOTENF.js";
11
+ } from "./chunk-HU7RYLUF.js";
12
12
  import "./chunk-ZH3NVYEQ.js";
13
13
  import "./chunk-FJFIWC7G.js";
14
14
  import "./chunk-PNKVD2UK.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumerahq/cli",
3
- "version": "0.19.3",
3
+ "version": "0.19.5",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {