@lumerahq/cli 0.19.3-dev.0 → 0.19.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.
Files changed (44) hide show
  1. package/README.md +5 -3
  2. package/dist/chunk-5T22627H.js +289 -0
  3. package/dist/chunk-H357NP7T.js +77 -0
  4. package/dist/{chunk-AUYOTENF.js → chunk-HU7RYLUF.js} +1 -1
  5. package/dist/{chunk-53NOF33P.js → chunk-SU26C4GL.js} +9 -49
  6. package/dist/{chunk-GKI2HQJC.js → chunk-UH5X43VV.js} +30 -3
  7. package/dist/{deps-ULTIIDYK.js → deps-GCOH3TXL.js} +4 -2
  8. package/dist/{dev-5JMHMS4U.js → dev-JLSTLIMZ.js} +14 -2
  9. package/dist/index.js +14 -14
  10. package/dist/{init-37XOMJLU.js → init-CBZAIERZ.js} +54 -24
  11. package/dist/{register-JJUMS4FI.js → register-N2LF3CCK.js} +1 -1
  12. package/dist/{resources-4IUZYKGX.js → resources-RHF2MDF7.js} +237 -159
  13. package/dist/{run-5ZOSPBGO.js → run-XJLB4AFD.js} +1 -1
  14. package/dist/{skills-TNJHMV4F.js → skills-REOKLNEF.js} +1 -1
  15. package/dist/{templates-M3RDNDDY.js → templates-LNUOTNLN.js} +2 -3
  16. package/package.json +1 -1
  17. package/templates/default/.agents/skills/.gitkeep +0 -0
  18. package/templates/default/AGENTS.md +151 -0
  19. package/templates/default/README.md +18 -0
  20. package/templates/default/_gitignore +14 -0
  21. package/templates/default/architecture.md +29 -0
  22. package/templates/default/biome.json +38 -0
  23. package/templates/default/components.json +21 -0
  24. package/templates/default/icon.svg +29 -0
  25. package/templates/default/index.html +16 -0
  26. package/templates/default/package.json +53 -0
  27. package/templates/default/platform/automations/.gitkeep +0 -0
  28. package/templates/default/platform/collections/.gitkeep +0 -0
  29. package/templates/default/platform/hooks/.gitkeep +0 -0
  30. package/templates/default/pyproject.toml +14 -0
  31. package/templates/default/scripts/.gitkeep +0 -0
  32. package/templates/default/src/components/layout.tsx +11 -0
  33. package/templates/default/src/lib/auth.ts +9 -0
  34. package/templates/default/src/lib/queries.ts +17 -0
  35. package/templates/default/src/lib/utils.ts +6 -0
  36. package/templates/default/src/main.tsx +130 -0
  37. package/templates/default/src/routes/__root.tsx +10 -0
  38. package/templates/default/src/routes/index.tsx +87 -0
  39. package/templates/default/src/styles.css +128 -0
  40. package/templates/default/template.json +7 -0
  41. package/templates/default/tsconfig.json +22 -0
  42. package/templates/default/vite.config.ts +28 -0
  43. package/dist/chunk-OQW5E7UT.js +0 -159
  44. package/dist/chunk-XDTWVFPE.js +0 -120
@@ -1,15 +1,16 @@
1
- import {
2
- syncDeps
3
- } from "./chunk-XDTWVFPE.js";
4
1
  import {
5
2
  deploy
6
- } from "./chunk-53NOF33P.js";
3
+ } from "./chunk-SU26C4GL.js";
4
+ import {
5
+ projectResourceDepsEnabled,
6
+ syncDeps
7
+ } from "./chunk-5T22627H.js";
7
8
  import {
8
9
  loadEnv
9
10
  } from "./chunk-2CR762KB.js";
10
11
  import {
11
12
  createApiClient
12
- } from "./chunk-GKI2HQJC.js";
13
+ } from "./chunk-UH5X43VV.js";
13
14
  import {
14
15
  findProjectRoot,
15
16
  getApiUrl,
@@ -28,8 +29,95 @@ import "./chunk-PNKVD2UK.js";
28
29
  import pc2 from "picocolors";
29
30
  import prompts from "prompts";
30
31
  import { execFileSync, execSync } from "child_process";
31
- import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from "fs";
32
- 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
+ };
33
121
 
34
122
  // src/lib/lint/rules/llm-import.ts
35
123
  var FROM_LUMERA_IMPORT_LLM = /^\s*from\s+lumera\s+import\s+\(?\s*(?:[\w\s,]*?\b)?llm\b/;
@@ -74,25 +162,37 @@ var llmImportRule = {
74
162
  };
75
163
 
76
164
  // src/lib/lint/registry.ts
77
- var ALL_RULES = [llmImportRule];
165
+ var ALL_RULES = [collectionSchemaRule, llmImportRule];
78
166
 
79
167
  // src/lib/lint/format.ts
80
168
  import { relative } from "path";
81
169
  import pc from "picocolors";
82
170
  function printLintWarnings(warnings, projectRoot) {
83
171
  if (warnings.length === 0) return;
172
+ const errors = warnings.filter((w) => w.severity === "error");
173
+ const advisory = warnings.filter((w) => w.severity !== "error");
84
174
  console.log();
85
- console.log(pc.bold(" Warnings:"));
175
+ console.log(pc.bold(errors.length > 0 ? " Lint issues:" : " Warnings:"));
86
176
  for (const w of warnings) {
87
177
  const rel = relative(projectRoot, w.target.filePath);
88
178
  const loc = w.line ? `${rel}:${w.line}` : rel;
89
- 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}]`)}`);
90
182
  console.log(` ${w.message}`);
91
183
  if (w.snippet) console.log(pc.dim(` > ${w.snippet}`));
92
184
  }
93
185
  console.log();
94
- const n = warnings.length;
95
- 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
+ }
96
196
  console.log();
97
197
  }
98
198
  function serializeLintWarnings(warnings, projectRoot) {
@@ -100,6 +200,7 @@ function serializeLintWarnings(warnings, projectRoot) {
100
200
  ruleId: w.ruleId,
101
201
  target: { kind: w.target.kind, name: w.target.name, filePath: relative(projectRoot, w.target.filePath) },
102
202
  message: w.message,
203
+ severity: w.severity ?? "warning",
103
204
  line: w.line,
104
205
  snippet: w.snippet
105
206
  }));
@@ -130,6 +231,22 @@ function buildAutomationTargets(localAutomations) {
130
231
  source: a.code
131
232
  }));
132
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
+ }
133
250
 
134
251
  // src/commands/resources.ts
135
252
  init_auth();
@@ -148,82 +265,20 @@ function safePrintLint(warnings, projectRoot) {
148
265
  if (process.env.LUMERA_DEBUG) console.error("[lint] print failed:", err);
149
266
  }
150
267
  }
151
- var PACKAGE_MANAGERS = ["bun", "pnpm", "yarn", "npm"];
152
- var PACKAGE_MANAGER_VALUE_FLAGS = /* @__PURE__ */ new Set(["--package-manager"]);
153
- function isPackageManager(value) {
154
- return PACKAGE_MANAGERS.includes(value);
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)`);
155
274
  }
156
- function commandAvailable(command) {
157
- try {
158
- execFileSync(command, ["--version"], { stdio: "ignore" });
159
- return true;
160
- } catch {
161
- return false;
162
- }
163
- }
164
- function parsePackageManagerSpecifier(value) {
165
- const raw = (value || "").trim();
166
- if (!raw) return void 0;
167
- const name = raw.split("@")[0];
168
- return isPackageManager(name) ? name : void 0;
169
- }
170
- function packageManagerFromPackageJson(projectRoot) {
171
- try {
172
- const pkg = JSON.parse(readFileSync(join(projectRoot, "package.json"), "utf-8"));
173
- return parsePackageManagerSpecifier(pkg.packageManager);
174
- } catch {
175
- return void 0;
176
- }
177
- }
178
- function getFlagValue(args, name) {
179
- const long = `--${name}`;
180
- for (let i = 0; i < args.length; i++) {
181
- const arg = args[i];
182
- if (arg === long) {
183
- const next = args[i + 1];
184
- return next && !next.startsWith("-") ? next : void 0;
185
- }
186
- if (arg.startsWith(`${long}=`)) {
187
- return arg.slice(long.length + 1);
188
- }
189
- }
190
- return void 0;
191
- }
192
- function getPositionalArgs(args) {
193
- const out = [];
194
- for (let i = 0; i < args.length; i++) {
195
- const arg = args[i];
196
- if (PACKAGE_MANAGER_VALUE_FLAGS.has(arg)) {
197
- i++;
198
- continue;
275
+ function detectPackageManager() {
276
+ for (const pm of ["bun", "pnpm", "yarn", "npm"]) {
277
+ try {
278
+ execFileSync(pm, ["--version"], { stdio: "ignore" });
279
+ return pm;
280
+ } catch {
199
281
  }
200
- if (arg.startsWith("-")) continue;
201
- out.push(arg);
202
- }
203
- return out;
204
- }
205
- function detectPackageManager(projectRoot, override) {
206
- const requested = parsePackageManagerSpecifier(override || process.env.LUMERA_PACKAGE_MANAGER);
207
- if (requested) {
208
- if (!commandAvailable(requested)) {
209
- throw new Error(`Package manager '${requested}' was requested but is not available on PATH`);
210
- }
211
- return requested;
212
- }
213
- const declared = packageManagerFromPackageJson(projectRoot);
214
- if (declared && commandAvailable(declared)) return declared;
215
- const lockfileManagers = [
216
- { pm: "pnpm", file: "pnpm-lock.yaml" },
217
- { pm: "bun", file: "bun.lockb" },
218
- { pm: "bun", file: "bun.lock" },
219
- { pm: "yarn", file: "yarn.lock" },
220
- { pm: "npm", file: "package-lock.json" }
221
- ];
222
- for (const { pm, file } of lockfileManagers) {
223
- if (existsSync(join(projectRoot, file)) && commandAvailable(pm)) return pm;
224
- }
225
- for (const pm of ["pnpm", "bun", "yarn", "npm"]) {
226
- if (commandAvailable(pm)) return pm;
227
282
  }
228
283
  return "npm";
229
284
  }
@@ -503,9 +558,6 @@ ${pc2.dim("Resources:")}
503
558
  ${pc2.dim("Options:")}
504
559
  --yes, -y Skip confirmation prompt (for CI/CD)
505
560
  --skip-build Skip build step when applying app
506
- --no-app Apply resources only; do not build/deploy the app
507
- --resources-only Alias for --no-app
508
- --package-manager Package manager for app builds (pnpm, bun, yarn, npm)
509
561
 
510
562
  ${pc2.dim("Examples:")}
511
563
  lumera apply # Apply everything (shows plan, asks to confirm)
@@ -514,8 +566,6 @@ ${pc2.dim("Examples:")}
514
566
  lumera apply agents -y # Apply agents without confirmation
515
567
  lumera apply app # Deploy frontend
516
568
  lumera apply app --skip-build # Deploy without rebuilding
517
- lumera apply --no-app -y # Apply resources without app build/deploy
518
- lumera apply app --package-manager pnpm
519
569
  `);
520
570
  }
521
571
  function showPullHelp() {
@@ -641,29 +691,29 @@ function parseResource(resourcePath) {
641
691
  return { type, name };
642
692
  }
643
693
  function getPlatformDir() {
644
- if (existsSync(join(process.cwd(), "platform"))) {
645
- return join(process.cwd(), "platform");
694
+ if (existsSync2(join2(process.cwd(), "platform"))) {
695
+ return join2(process.cwd(), "platform");
646
696
  }
647
- if (existsSync(join(process.cwd(), "lumera_platform"))) {
648
- return join(process.cwd(), "lumera_platform");
697
+ if (existsSync2(join2(process.cwd(), "lumera_platform"))) {
698
+ return join2(process.cwd(), "lumera_platform");
649
699
  }
650
- return join(process.cwd(), "platform");
700
+ return join2(process.cwd(), "platform");
651
701
  }
652
702
  function toSafeFilename(name) {
653
703
  return name.replace(/\s+/g, "_").replace(/[^a-zA-Z0-9_-]/g, "").toLowerCase();
654
704
  }
655
705
  function loadLocalCollections(platformDir, filterName) {
656
- const collectionsDir = join(platformDir, "collections");
657
- if (!existsSync(collectionsDir)) {
706
+ const collectionsDir = join2(platformDir, "collections");
707
+ if (!existsSync2(collectionsDir)) {
658
708
  return [];
659
709
  }
660
710
  const collections = [];
661
711
  const errors = [];
662
- for (const file of readdirSync(collectionsDir)) {
712
+ for (const file of readdirSync2(collectionsDir)) {
663
713
  if (!file.endsWith(".json")) continue;
664
- const filePath = join(collectionsDir, file);
714
+ const filePath = join2(collectionsDir, file);
665
715
  try {
666
- const content = readFileSync(filePath, "utf-8");
716
+ const content = readFileSync2(filePath, "utf-8");
667
717
  const collection = JSON.parse(content);
668
718
  if (filterName && collection.name !== filterName && collection.id !== filterName) {
669
719
  continue;
@@ -684,6 +734,27 @@ function loadLocalCollections(platformDir, filterName) {
684
734
  errors.push(`${file}: collection name "${collection.name}" contains invalid characters`);
685
735
  continue;
686
736
  }
737
+ if (!Array.isArray(collection.fields)) {
738
+ errors.push(`${file}: missing fields array`);
739
+ continue;
740
+ }
741
+ for (let i = 0; i < collection.fields.length; i++) {
742
+ const field = collection.fields[i];
743
+ if (!field || typeof field !== "object") {
744
+ errors.push(`${file}: field at index ${i} must be an object`);
745
+ continue;
746
+ }
747
+ if (typeof field.name !== "string" || field.name.trim() === "") {
748
+ errors.push(`${file}: field at index ${i} is missing name`);
749
+ }
750
+ if (typeof field.type !== "string" || field.type.trim() === "") {
751
+ errors.push(`${file}: field ${typeof field.name === "string" ? `"${field.name}"` : `at index ${i}`} is missing type`);
752
+ }
753
+ }
754
+ if (collection.indexes !== void 0 && !Array.isArray(collection.indexes)) {
755
+ errors.push(`${file}: indexes must be an array when present`);
756
+ continue;
757
+ }
687
758
  const existingById = collections.find((c) => c.id === collection.id);
688
759
  if (existingById) {
689
760
  errors.push(`${file}: duplicate collection id "${collection.id}" (also defined in another file)`);
@@ -709,27 +780,27 @@ function loadLocalCollections(platformDir, filterName) {
709
780
  return collections;
710
781
  }
711
782
  function loadLocalAutomations(platformDir, filterName, appName) {
712
- const automationsDir = join(platformDir, "automations");
713
- if (!existsSync(automationsDir)) {
783
+ const automationsDir = join2(platformDir, "automations");
784
+ if (!existsSync2(automationsDir)) {
714
785
  return [];
715
786
  }
716
787
  const automations = [];
717
788
  const errors = [];
718
- for (const entry of readdirSync(automationsDir, { withFileTypes: true })) {
789
+ for (const entry of readdirSync2(automationsDir, { withFileTypes: true })) {
719
790
  if (!entry.isDirectory()) continue;
720
- const automationDir = join(automationsDir, entry.name);
721
- const configPath = join(automationDir, "config.json");
722
- const mainPath = join(automationDir, "main.py");
723
- if (!existsSync(configPath)) {
791
+ const automationDir = join2(automationsDir, entry.name);
792
+ const configPath = join2(automationDir, "config.json");
793
+ const mainPath = join2(automationDir, "main.py");
794
+ if (!existsSync2(configPath)) {
724
795
  errors.push(`${entry.name}: missing config.json`);
725
796
  continue;
726
797
  }
727
- if (!existsSync(mainPath)) {
798
+ if (!existsSync2(mainPath)) {
728
799
  errors.push(`${entry.name}: missing main.py`);
729
800
  continue;
730
801
  }
731
802
  try {
732
- const configContent = readFileSync(configPath, "utf-8");
803
+ const configContent = readFileSync2(configPath, "utf-8");
733
804
  const rawConfig = JSON.parse(configContent);
734
805
  if (filterName && rawConfig.external_id !== filterName && rawConfig.name !== filterName && entry.name !== filterName) {
735
806
  continue;
@@ -756,7 +827,7 @@ function loadLocalAutomations(platformDir, filterName, appName) {
756
827
  errors.push(`${entry.name}: duplicate external_id "${config.external_id}" (also defined in ${existingByExtId.automation.name})`);
757
828
  continue;
758
829
  }
759
- let code = readFileSync(mainPath, "utf-8");
830
+ let code = readFileSync2(mainPath, "utf-8");
760
831
  if (appName) {
761
832
  code = code.replaceAll("{{app}}", appName);
762
833
  }
@@ -775,15 +846,15 @@ function loadLocalAutomations(platformDir, filterName, appName) {
775
846
  return automations;
776
847
  }
777
848
  function loadLocalHooks(platformDir, filterName, appName) {
778
- const hooksDir = join(platformDir, "hooks");
779
- if (!existsSync(hooksDir)) {
849
+ const hooksDir = join2(platformDir, "hooks");
850
+ if (!existsSync2(hooksDir)) {
780
851
  return [];
781
852
  }
782
853
  const hooks = [];
783
- for (const file of readdirSync(hooksDir)) {
854
+ for (const file of readdirSync2(hooksDir)) {
784
855
  if (!file.endsWith(".js") && !file.endsWith(".ts")) continue;
785
- const filePath = join(hooksDir, file);
786
- const content = readFileSync(filePath, "utf-8");
856
+ const filePath = join2(hooksDir, file);
857
+ const content = readFileSync2(filePath, "utf-8");
787
858
  const config = parseHookConfig(content);
788
859
  if (!config) {
789
860
  console.log(pc2.yellow(` \u26A0 Skipping ${file}: could not parse config export`));
@@ -1099,12 +1170,12 @@ async function planAutomations(api, localAutomations) {
1099
1170
  return changes;
1100
1171
  }
1101
1172
  function loadLocalMailboxes(platformDir, filterName) {
1102
- const mailboxesDir = join(platformDir, "mailboxes");
1103
- if (!existsSync(mailboxesDir)) {
1173
+ const mailboxesDir = join2(platformDir, "mailboxes");
1174
+ if (!existsSync2(mailboxesDir)) {
1104
1175
  return [];
1105
1176
  }
1106
1177
  const mailboxes = [];
1107
- for (const file of readdirSync(mailboxesDir)) {
1178
+ for (const file of readdirSync2(mailboxesDir)) {
1108
1179
  if (!file.endsWith(".json")) continue;
1109
1180
  const slug = file.replace(/\.json$/, "").trim();
1110
1181
  if (!slug) {
@@ -1114,10 +1185,10 @@ function loadLocalMailboxes(platformDir, filterName) {
1114
1185
  if (filterName && slug !== filterName) {
1115
1186
  continue;
1116
1187
  }
1117
- const filePath = join(mailboxesDir, file);
1188
+ const filePath = join2(mailboxesDir, file);
1118
1189
  let raw;
1119
1190
  try {
1120
- raw = JSON.parse(readFileSync(filePath, "utf-8"));
1191
+ raw = JSON.parse(readFileSync2(filePath, "utf-8"));
1121
1192
  } catch (e) {
1122
1193
  console.log(pc2.yellow(` \u26A0 Skipping ${file}: invalid JSON (${e.message})`));
1123
1194
  continue;
@@ -1196,8 +1267,8 @@ async function pullMailboxes(api, platformDir, filterName) {
1196
1267
  console.log(pc2.dim(" (no mailboxes)"));
1197
1268
  return;
1198
1269
  }
1199
- const outDir = join(platformDir, "mailboxes");
1200
- if (!existsSync(outDir)) {
1270
+ const outDir = join2(platformDir, "mailboxes");
1271
+ if (!existsSync2(outDir)) {
1201
1272
  mkdirSync(outDir, { recursive: true });
1202
1273
  }
1203
1274
  let count = 0;
@@ -1205,7 +1276,7 @@ async function pullMailboxes(api, platformDir, filterName) {
1205
1276
  if (filterName && mb.slug !== filterName) continue;
1206
1277
  const body = {};
1207
1278
  if (mb.description) body.description = mb.description;
1208
- const file = join(outDir, `${toSafeFilename(mb.slug)}.json`);
1279
+ const file = join2(outDir, `${toSafeFilename(mb.slug)}.json`);
1209
1280
  writeFileSync(file, JSON.stringify(body, null, 2) + "\n");
1210
1281
  console.log(pc2.green(" \u2713"), `pulled mailbox ${mb.slug} ${pc2.dim(`\u2192 ${file}`)}`);
1211
1282
  count++;
@@ -1464,7 +1535,7 @@ async function applyApp(args) {
1464
1535
  if (!skipBuild) {
1465
1536
  console.log(pc2.dim(" Building..."));
1466
1537
  try {
1467
- const pm = detectPackageManager(projectRoot, getFlagValue(args, "package-manager"));
1538
+ const pm = detectPackageManager();
1468
1539
  execSync(`${pm} run build`, { cwd: projectRoot, stdio: "inherit" });
1469
1540
  } catch {
1470
1541
  throw new Error("Build failed");
@@ -1474,7 +1545,7 @@ async function applyApp(args) {
1474
1545
  await deploy({ token, appName, appTitle, distDir, apiUrl });
1475
1546
  }
1476
1547
  async function pullCollections(api, platformDir, filterName, appName) {
1477
- const collectionsDir = join(platformDir, "collections");
1548
+ const collectionsDir = join2(platformDir, "collections");
1478
1549
  mkdirSync(collectionsDir, { recursive: true });
1479
1550
  const collections = await api.listCollections();
1480
1551
  for (const collection of collections) {
@@ -1518,13 +1589,13 @@ async function pullCollections(api, platformDir, filterName, appName) {
1518
1589
  }).filter((idx) => idx !== null)
1519
1590
  };
1520
1591
  const fileName = toSafeFilename(localName);
1521
- const filePath = join(collectionsDir, `${fileName}.json`);
1592
+ const filePath = join2(collectionsDir, `${fileName}.json`);
1522
1593
  writeFileSync(filePath, JSON.stringify(localFormat, null, 2) + "\n");
1523
1594
  console.log(pc2.green(" \u2713"), `${localName} \u2192 collections/${fileName}.json`);
1524
1595
  }
1525
1596
  }
1526
1597
  async function pullAutomations(api, platformDir, filterName, projectId) {
1527
- const automationsDir = join(platformDir, "automations");
1598
+ const automationsDir = join2(platformDir, "automations");
1528
1599
  mkdirSync(automationsDir, { recursive: true });
1529
1600
  const automations = await api.listAutomations({ include_code: true });
1530
1601
  for (const automation of automations) {
@@ -1535,7 +1606,7 @@ async function pullAutomations(api, platformDir, filterName, projectId) {
1535
1606
  continue;
1536
1607
  }
1537
1608
  const dirName = automation.external_id.replace(/[^a-zA-Z0-9_-]/g, "_");
1538
- const automationDir = join(automationsDir, dirName);
1609
+ const automationDir = join2(automationsDir, dirName);
1539
1610
  mkdirSync(automationDir, { recursive: true });
1540
1611
  const config = {
1541
1612
  external_id: automation.external_id,
@@ -1568,13 +1639,13 @@ async function pullAutomations(api, platformDir, filterName, projectId) {
1568
1639
  }
1569
1640
  } catch {
1570
1641
  }
1571
- writeFileSync(join(automationDir, "config.json"), JSON.stringify(config, null, 2) + "\n");
1572
- writeFileSync(join(automationDir, "main.py"), automation.code || "");
1642
+ writeFileSync(join2(automationDir, "config.json"), JSON.stringify(config, null, 2) + "\n");
1643
+ writeFileSync(join2(automationDir, "main.py"), automation.code || "");
1573
1644
  console.log(pc2.green(" \u2713"), `${automation.name} \u2192 automations/${dirName}/`);
1574
1645
  }
1575
1646
  }
1576
1647
  async function pullHooks(api, platformDir, filterName, appName, projectId) {
1577
- const hooksDir = join(platformDir, "hooks");
1648
+ const hooksDir = join2(platformDir, "hooks");
1578
1649
  mkdirSync(hooksDir, { recursive: true });
1579
1650
  const hooks = await api.listHooks();
1580
1651
  for (const hook of hooks) {
@@ -1596,31 +1667,31 @@ export default async function handler({ record, app, http }) {
1596
1667
  ${hook.script.split("\n").map((line) => " " + line).join("\n")}
1597
1668
  }
1598
1669
  `;
1599
- writeFileSync(join(hooksDir, fileName), content);
1670
+ writeFileSync(join2(hooksDir, fileName), content);
1600
1671
  console.log(pc2.green(" \u2713"), `${hook.name} \u2192 hooks/${fileName}`);
1601
1672
  }
1602
1673
  }
1603
1674
  function loadLocalAgents(platformDir, filterName, appName) {
1604
- const agentsDir = join(platformDir, "agents");
1605
- if (!existsSync(agentsDir)) return [];
1675
+ const agentsDir = join2(platformDir, "agents");
1676
+ if (!existsSync2(agentsDir)) return [];
1606
1677
  const agents = [];
1607
1678
  const errors = [];
1608
- for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
1679
+ for (const entry of readdirSync2(agentsDir, { withFileTypes: true })) {
1609
1680
  if (!entry.isDirectory()) continue;
1610
- const agentDir = join(agentsDir, entry.name);
1611
- const configPath = join(agentDir, "config.json");
1612
- const promptPath = join(agentDir, "system_prompt.md");
1613
- const policyPath = join(agentDir, "policy.js");
1614
- if (!existsSync(configPath)) {
1681
+ const agentDir = join2(agentsDir, entry.name);
1682
+ const configPath = join2(agentDir, "config.json");
1683
+ const promptPath = join2(agentDir, "system_prompt.md");
1684
+ const policyPath = join2(agentDir, "policy.js");
1685
+ if (!existsSync2(configPath)) {
1615
1686
  errors.push(`${entry.name}: missing config.json`);
1616
1687
  continue;
1617
1688
  }
1618
- if (!existsSync(promptPath)) {
1689
+ if (!existsSync2(promptPath)) {
1619
1690
  errors.push(`${entry.name}: missing system_prompt.md`);
1620
1691
  continue;
1621
1692
  }
1622
1693
  try {
1623
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
1694
+ const config = JSON.parse(readFileSync2(configPath, "utf-8"));
1624
1695
  if (filterName && config.external_id !== filterName && config.name !== filterName && entry.name !== filterName) {
1625
1696
  continue;
1626
1697
  }
@@ -1643,8 +1714,8 @@ function loadLocalAgents(platformDir, filterName, appName) {
1643
1714
  errors.push(`${entry.name}: missing name in config.json`);
1644
1715
  continue;
1645
1716
  }
1646
- let systemPrompt = readFileSync(promptPath, "utf-8");
1647
- let policyScript = existsSync(policyPath) ? readFileSync(policyPath, "utf-8") : "";
1717
+ let systemPrompt = readFileSync2(promptPath, "utf-8");
1718
+ let policyScript = existsSync2(policyPath) ? readFileSync2(policyPath, "utf-8") : "";
1648
1719
  if (appName) {
1649
1720
  systemPrompt = systemPrompt.replaceAll("{{app}}", appName);
1650
1721
  policyScript = policyScript.replaceAll("{{app}}", appName);
@@ -1774,7 +1845,7 @@ async function applyAgents(api, localAgents, projectId) {
1774
1845
  return errors;
1775
1846
  }
1776
1847
  async function pullAgents(api, platformDir, filterName, projectId) {
1777
- const agentsDir = join(platformDir, "agents");
1848
+ const agentsDir = join2(platformDir, "agents");
1778
1849
  mkdirSync(agentsDir, { recursive: true });
1779
1850
  const agents = await api.listAgents(projectId ? { project_id: projectId } : void 0);
1780
1851
  let skillIdToSlug = /* @__PURE__ */ new Map();
@@ -1789,7 +1860,7 @@ async function pullAgents(api, platformDir, filterName, projectId) {
1789
1860
  continue;
1790
1861
  }
1791
1862
  const dirName = agent.external_id.replace(/[^a-zA-Z0-9_-]/g, "_");
1792
- const agentDir = join(agentsDir, dirName);
1863
+ const agentDir = join2(agentsDir, dirName);
1793
1864
  mkdirSync(agentDir, { recursive: true });
1794
1865
  const skillSlugs = [];
1795
1866
  if (agent.skill_ids) {
@@ -1806,10 +1877,10 @@ async function pullAgents(api, platformDir, filterName, projectId) {
1806
1877
  if (agent.model) config.model = agent.model;
1807
1878
  if (skillSlugs.length > 0) config.skills = skillSlugs;
1808
1879
  if (agent.policy_enabled) config.policy_enabled = true;
1809
- writeFileSync(join(agentDir, "config.json"), JSON.stringify(config, null, 2) + "\n");
1810
- writeFileSync(join(agentDir, "system_prompt.md"), agent.system_prompt || "");
1880
+ writeFileSync(join2(agentDir, "config.json"), JSON.stringify(config, null, 2) + "\n");
1881
+ writeFileSync(join2(agentDir, "system_prompt.md"), agent.system_prompt || "");
1811
1882
  if (agent.policy_script) {
1812
- writeFileSync(join(agentDir, "policy.js"), agent.policy_script);
1883
+ writeFileSync(join2(agentDir, "policy.js"), agent.policy_script);
1813
1884
  }
1814
1885
  console.log(pc2.green(" \u2713"), `${agent.name} \u2192 agents/${dirName}/`);
1815
1886
  }
@@ -2447,7 +2518,10 @@ async function plan(args) {
2447
2518
  console.log(pc2.cyan(pc2.bold(" Plan")));
2448
2519
  console.log(pc2.dim(" Comparing local files to remote state..."));
2449
2520
  console.log();
2450
- await syncDeps(projectRoot);
2521
+ if (!type || type === "collections") {
2522
+ lintCollectionFiles(projectRoot, platformDir, name || void 0);
2523
+ }
2524
+ await syncDeps(projectRoot, { ignorePermissionDenied: true });
2451
2525
  const allChanges = [];
2452
2526
  let collections;
2453
2527
  try {
@@ -2568,13 +2642,9 @@ async function apply(args) {
2568
2642
  const appName = getAppName(projectRoot);
2569
2643
  const api = createApiClient(void 0, void 0, appName);
2570
2644
  const projectId = getProjectId(projectRoot);
2571
- const positionalArgs = getPositionalArgs(args);
2645
+ const positionalArgs = args.filter((a) => !a.startsWith("-"));
2572
2646
  const { type, name } = parseResource(positionalArgs[0]);
2573
2647
  const autoConfirm = args.includes("--yes") || args.includes("-y") || !!process.env.CI;
2574
- const skipApp = args.includes("--no-app") || args.includes("--resources-only");
2575
- if (type === "app" && skipApp) {
2576
- throw new Error("Cannot combine app resource with --no-app / --resources-only");
2577
- }
2578
2648
  if (type === "app") {
2579
2649
  console.log();
2580
2650
  console.log(pc2.cyan(pc2.bold(" Apply")));
@@ -2586,7 +2656,10 @@ async function apply(args) {
2586
2656
  console.log();
2587
2657
  return;
2588
2658
  }
2589
- await syncDeps(projectRoot);
2659
+ if (!type || type === "collections") {
2660
+ lintCollectionFiles(projectRoot, platformDir, name || void 0);
2661
+ }
2662
+ await syncDeps(projectRoot, { ignorePermissionDenied: true });
2590
2663
  let collections;
2591
2664
  try {
2592
2665
  const remoteCollections = await api.listCollections();
@@ -2615,9 +2688,9 @@ async function apply(args) {
2615
2688
  }
2616
2689
  }
2617
2690
  let willDeployApp = false;
2618
- if (!type && !skipApp) {
2691
+ if (!type) {
2619
2692
  try {
2620
- if (existsSync(join(projectRoot, "dist")) || existsSync(join(projectRoot, "src"))) {
2693
+ if (existsSync2(join2(projectRoot, "dist")) || existsSync2(join2(projectRoot, "src"))) {
2621
2694
  willDeployApp = true;
2622
2695
  }
2623
2696
  } catch {
@@ -2795,6 +2868,11 @@ async function pull(args) {
2795
2868
  console.log(pc2.bold(" Collections:"));
2796
2869
  await pullCollections(api, platformDir, name || void 0, appName);
2797
2870
  console.log();
2871
+ if (!name && await projectResourceDepsEnabled(projectRoot)) {
2872
+ console.log(pc2.bold(" Resource shares:"));
2873
+ await syncDeps(projectRoot, { write: true, legacyWhenDisabled: false, ignorePermissionDenied: true });
2874
+ console.log();
2875
+ }
2798
2876
  }
2799
2877
  if (!type || type === "automations") {
2800
2878
  console.log(pc2.bold(" Automations:"));