@lumerahq/cli 0.19.1 → 0.19.2
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.
|
@@ -25,12 +25,129 @@ import {
|
|
|
25
25
|
import "./chunk-PNKVD2UK.js";
|
|
26
26
|
|
|
27
27
|
// src/commands/resources.ts
|
|
28
|
-
import
|
|
28
|
+
import pc2 from "picocolors";
|
|
29
29
|
import prompts from "prompts";
|
|
30
30
|
import { execFileSync, execSync } from "child_process";
|
|
31
31
|
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
32
32
|
import { join, resolve } from "path";
|
|
33
|
+
|
|
34
|
+
// src/lib/lint/rules/llm-import.ts
|
|
35
|
+
var FROM_LUMERA_IMPORT_LLM = /^\s*from\s+lumera\s+import\s+\(?\s*(?:[\w\s,]*?\b)?llm\b/;
|
|
36
|
+
var LUMERA_LLM_SUBMODULE = /^\s*(?:from\s+lumera\.llm\b|import\s+lumera\.llm\b)/;
|
|
37
|
+
var llmImportRule = {
|
|
38
|
+
id: "llm-import",
|
|
39
|
+
description: "Flags imports of 'lumera.llm' \u2014 prefer custom agents for LLM/agentic capabilities.",
|
|
40
|
+
appliesTo: ["automation"],
|
|
41
|
+
check(target) {
|
|
42
|
+
const warnings = [];
|
|
43
|
+
const lines = target.source.split(/\r?\n/);
|
|
44
|
+
let inTriple = false;
|
|
45
|
+
for (let i = 0; i < lines.length; i++) {
|
|
46
|
+
const raw = lines[i];
|
|
47
|
+
if (inTriple) {
|
|
48
|
+
if (raw.includes(inTriple)) inTriple = false;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const tripleMatch = raw.match(/("""|''')/);
|
|
52
|
+
if (tripleMatch) {
|
|
53
|
+
const q = tripleMatch[1];
|
|
54
|
+
const rest = raw.slice(raw.indexOf(q) + 3);
|
|
55
|
+
if (!rest.includes(q)) {
|
|
56
|
+
inTriple = q;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const trimmed = raw.trimStart();
|
|
61
|
+
if (trimmed.startsWith("#")) continue;
|
|
62
|
+
if (FROM_LUMERA_IMPORT_LLM.test(raw) || LUMERA_LLM_SUBMODULE.test(raw)) {
|
|
63
|
+
warnings.push({
|
|
64
|
+
ruleId: "llm-import",
|
|
65
|
+
target,
|
|
66
|
+
line: i + 1,
|
|
67
|
+
snippet: raw.trim(),
|
|
68
|
+
message: "Imports 'lumera.llm'. Prefer a custom agent for LLM/agentic capabilities in new automations."
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return warnings;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// src/lib/lint/registry.ts
|
|
77
|
+
var ALL_RULES = [llmImportRule];
|
|
78
|
+
|
|
79
|
+
// src/lib/lint/format.ts
|
|
80
|
+
import { relative } from "path";
|
|
81
|
+
import pc from "picocolors";
|
|
82
|
+
function printLintWarnings(warnings, projectRoot) {
|
|
83
|
+
if (warnings.length === 0) return;
|
|
84
|
+
console.log();
|
|
85
|
+
console.log(pc.bold(" Warnings:"));
|
|
86
|
+
for (const w of warnings) {
|
|
87
|
+
const rel = relative(projectRoot, w.target.filePath);
|
|
88
|
+
const loc = w.line ? `${rel}:${w.line}` : rel;
|
|
89
|
+
console.log(` ${pc.yellow("\u26A0")} ${loc} ${pc.dim(`[${w.ruleId}]`)}`);
|
|
90
|
+
console.log(` ${w.message}`);
|
|
91
|
+
if (w.snippet) console.log(pc.dim(` > ${w.snippet}`));
|
|
92
|
+
}
|
|
93
|
+
console.log();
|
|
94
|
+
const n = warnings.length;
|
|
95
|
+
console.log(pc.dim(` ${n} warning${n === 1 ? "" : "s"} \u2014 advisory, will not block apply.`));
|
|
96
|
+
console.log();
|
|
97
|
+
}
|
|
98
|
+
function serializeLintWarnings(warnings, projectRoot) {
|
|
99
|
+
return warnings.map((w) => ({
|
|
100
|
+
ruleId: w.ruleId,
|
|
101
|
+
target: { kind: w.target.kind, name: w.target.name, filePath: relative(projectRoot, w.target.filePath) },
|
|
102
|
+
message: w.message,
|
|
103
|
+
line: w.line,
|
|
104
|
+
snippet: w.snippet
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/lib/lint/index.ts
|
|
109
|
+
function runLint(ctx) {
|
|
110
|
+
const out = [];
|
|
111
|
+
for (const rule of ALL_RULES) {
|
|
112
|
+
for (const t of ctx.targets) {
|
|
113
|
+
if (!rule.appliesTo.includes(t.kind)) continue;
|
|
114
|
+
try {
|
|
115
|
+
out.push(...rule.check(t, ctx));
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (process.env.LUMERA_DEBUG) {
|
|
118
|
+
console.error(`[lint] rule "${rule.id}" threw on ${t.filePath}:`, err);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
function buildAutomationTargets(localAutomations) {
|
|
126
|
+
return localAutomations.map((a) => ({
|
|
127
|
+
kind: "automation",
|
|
128
|
+
name: a.automation.external_id,
|
|
129
|
+
filePath: a.filePath,
|
|
130
|
+
source: a.code
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/commands/resources.ts
|
|
33
135
|
init_auth();
|
|
136
|
+
function safeLint(projectRoot, localAutomations) {
|
|
137
|
+
try {
|
|
138
|
+
return runLint({ projectRoot, targets: buildAutomationTargets(localAutomations) });
|
|
139
|
+
} catch (err) {
|
|
140
|
+
if (process.env.LUMERA_DEBUG) console.error("[lint] pass failed:", err);
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function safePrintLint(warnings, projectRoot) {
|
|
145
|
+
try {
|
|
146
|
+
printLintWarnings(warnings, projectRoot);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
if (process.env.LUMERA_DEBUG) console.error("[lint] print failed:", err);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
34
151
|
function detectPackageManager() {
|
|
35
152
|
for (const pm of ["bun", "pnpm", "yarn", "npm"]) {
|
|
36
153
|
try {
|
|
@@ -267,13 +384,13 @@ function normalizeLocalAutomation(config) {
|
|
|
267
384
|
}
|
|
268
385
|
function showPlanHelp() {
|
|
269
386
|
console.log(`
|
|
270
|
-
${
|
|
387
|
+
${pc2.dim("Usage:")}
|
|
271
388
|
lumera plan [resource]
|
|
272
389
|
|
|
273
|
-
${
|
|
390
|
+
${pc2.dim("Description:")}
|
|
274
391
|
Preview changes between local files and remote state.
|
|
275
392
|
|
|
276
|
-
${
|
|
393
|
+
${pc2.dim("Resources:")}
|
|
277
394
|
(none) Plan all resources
|
|
278
395
|
collections Plan only collections
|
|
279
396
|
collections/<name> Plan single collection
|
|
@@ -286,7 +403,7 @@ ${pc.dim("Resources:")}
|
|
|
286
403
|
mailboxes/<slug> Plan single mailbox
|
|
287
404
|
app Plan app deployment
|
|
288
405
|
|
|
289
|
-
${
|
|
406
|
+
${pc2.dim("Examples:")}
|
|
290
407
|
lumera plan # Plan all resources
|
|
291
408
|
lumera plan collections # Plan only collections
|
|
292
409
|
lumera plan automations/sync # Plan single automation
|
|
@@ -295,13 +412,13 @@ ${pc.dim("Examples:")}
|
|
|
295
412
|
}
|
|
296
413
|
function showApplyHelp() {
|
|
297
414
|
console.log(`
|
|
298
|
-
${
|
|
415
|
+
${pc2.dim("Usage:")}
|
|
299
416
|
lumera apply [resource]
|
|
300
417
|
|
|
301
|
-
${
|
|
418
|
+
${pc2.dim("Description:")}
|
|
302
419
|
Create or update resources from local files.
|
|
303
420
|
|
|
304
|
-
${
|
|
421
|
+
${pc2.dim("Resources:")}
|
|
305
422
|
(none) Apply all resources
|
|
306
423
|
collections Apply only collections
|
|
307
424
|
collections/<name> Apply single collection
|
|
@@ -314,11 +431,11 @@ ${pc.dim("Resources:")}
|
|
|
314
431
|
mailboxes/<slug> Apply single mailbox
|
|
315
432
|
app Deploy the frontend app
|
|
316
433
|
|
|
317
|
-
${
|
|
434
|
+
${pc2.dim("Options:")}
|
|
318
435
|
--yes, -y Skip confirmation prompt (for CI/CD)
|
|
319
436
|
--skip-build Skip build step when applying app
|
|
320
437
|
|
|
321
|
-
${
|
|
438
|
+
${pc2.dim("Examples:")}
|
|
322
439
|
lumera apply # Apply everything (shows plan, asks to confirm)
|
|
323
440
|
lumera apply collections # Apply all collections
|
|
324
441
|
lumera apply collections/users # Apply single collection
|
|
@@ -329,18 +446,18 @@ ${pc.dim("Examples:")}
|
|
|
329
446
|
}
|
|
330
447
|
function showPullHelp() {
|
|
331
448
|
console.log(`
|
|
332
|
-
${
|
|
449
|
+
${pc2.dim("Usage:")}
|
|
333
450
|
lumera pull [resource] [--force]
|
|
334
451
|
|
|
335
|
-
${
|
|
452
|
+
${pc2.dim("Description:")}
|
|
336
453
|
Download remote state to local files.
|
|
337
454
|
Refuses to overwrite local files that have uncommitted changes
|
|
338
455
|
(use --force to override, or 'lumera diff' to inspect first).
|
|
339
456
|
|
|
340
|
-
${
|
|
457
|
+
${pc2.dim("Options:")}
|
|
341
458
|
--force, -f Overwrite local files even if they have changes
|
|
342
459
|
|
|
343
|
-
${
|
|
460
|
+
${pc2.dim("Resources:")}
|
|
344
461
|
(none) Pull all resources
|
|
345
462
|
collections Pull only collections
|
|
346
463
|
collections/<name> Pull single collection
|
|
@@ -352,7 +469,7 @@ ${pc.dim("Resources:")}
|
|
|
352
469
|
mailboxes Pull only mailboxes
|
|
353
470
|
mailboxes/<slug> Pull single mailbox
|
|
354
471
|
|
|
355
|
-
${
|
|
472
|
+
${pc2.dim("Examples:")}
|
|
356
473
|
lumera pull # Pull all (safe \u2014 warns on conflicts)
|
|
357
474
|
lumera pull agents # Pull only agents
|
|
358
475
|
lumera pull --force # Pull all, overwrite local changes
|
|
@@ -361,13 +478,13 @@ ${pc.dim("Examples:")}
|
|
|
361
478
|
}
|
|
362
479
|
function showDestroyHelp() {
|
|
363
480
|
console.log(`
|
|
364
|
-
${
|
|
481
|
+
${pc2.dim("Usage:")}
|
|
365
482
|
lumera destroy [resource]
|
|
366
483
|
|
|
367
|
-
${
|
|
484
|
+
${pc2.dim("Description:")}
|
|
368
485
|
Delete resources from remote.
|
|
369
486
|
|
|
370
|
-
${
|
|
487
|
+
${pc2.dim("Resources:")}
|
|
371
488
|
(none) Destroy all resources
|
|
372
489
|
collections Destroy only collections
|
|
373
490
|
collections/<name> Destroy single collection
|
|
@@ -378,11 +495,11 @@ ${pc.dim("Resources:")}
|
|
|
378
495
|
agents/<name> Destroy single agent
|
|
379
496
|
app Delete app registration
|
|
380
497
|
|
|
381
|
-
${
|
|
498
|
+
${pc2.dim("Options:")}
|
|
382
499
|
--confirm Skip confirmation prompt
|
|
383
500
|
--force-cycles Remove relation fields to break circular references before deleting
|
|
384
501
|
|
|
385
|
-
${
|
|
502
|
+
${pc2.dim("Examples:")}
|
|
386
503
|
lumera destroy # Destroy everything
|
|
387
504
|
lumera destroy collections/users # Destroy single collection
|
|
388
505
|
lumera destroy app # Delete app registration
|
|
@@ -390,13 +507,13 @@ ${pc.dim("Examples:")}
|
|
|
390
507
|
}
|
|
391
508
|
function showListHelp() {
|
|
392
509
|
console.log(`
|
|
393
|
-
${
|
|
510
|
+
${pc2.dim("Usage:")}
|
|
394
511
|
lumera list [type] [--all]
|
|
395
512
|
|
|
396
|
-
${
|
|
513
|
+
${pc2.dim("Description:")}
|
|
397
514
|
List resources with status. By default, remote-only resources are hidden.
|
|
398
515
|
|
|
399
|
-
${
|
|
516
|
+
${pc2.dim("Types:")}
|
|
400
517
|
(none) List all resources
|
|
401
518
|
collections List only collections
|
|
402
519
|
automations List only automations
|
|
@@ -404,10 +521,10 @@ ${pc.dim("Types:")}
|
|
|
404
521
|
agents List only agents
|
|
405
522
|
mailboxes List only mailboxes
|
|
406
523
|
|
|
407
|
-
${
|
|
524
|
+
${pc2.dim("Options:")}
|
|
408
525
|
--all Include remote-only resources
|
|
409
526
|
|
|
410
|
-
${
|
|
527
|
+
${pc2.dim("Examples:")}
|
|
411
528
|
lumera list # List local resources
|
|
412
529
|
lumera list --all # Include remote-only resources
|
|
413
530
|
lumera list collections # List only collections
|
|
@@ -415,13 +532,13 @@ ${pc.dim("Examples:")}
|
|
|
415
532
|
}
|
|
416
533
|
function showShowHelp() {
|
|
417
534
|
console.log(`
|
|
418
|
-
${
|
|
535
|
+
${pc2.dim("Usage:")}
|
|
419
536
|
lumera show <resource>
|
|
420
537
|
|
|
421
|
-
${
|
|
538
|
+
${pc2.dim("Description:")}
|
|
422
539
|
Show details of a single resource.
|
|
423
540
|
|
|
424
|
-
${
|
|
541
|
+
${pc2.dim("Resources:")}
|
|
425
542
|
collections/<name> Show collection details
|
|
426
543
|
automations/<name> Show automation details
|
|
427
544
|
hooks/<name> Show hook details
|
|
@@ -429,7 +546,7 @@ ${pc.dim("Resources:")}
|
|
|
429
546
|
mailboxes/<slug> Show mailbox details
|
|
430
547
|
app Show app details
|
|
431
548
|
|
|
432
|
-
${
|
|
549
|
+
${pc2.dim("Examples:")}
|
|
433
550
|
lumera show collections/users # Show collection details
|
|
434
551
|
lumera show automations/sync # Show automation details
|
|
435
552
|
lumera show app # Show app details
|
|
@@ -444,7 +561,7 @@ function parseResource(resourcePath) {
|
|
|
444
561
|
const name = parts.slice(1).join("/") || null;
|
|
445
562
|
const validTypes = ["collections", "automations", "hooks", "agents", "mailboxes", "app"];
|
|
446
563
|
if (!validTypes.includes(type)) {
|
|
447
|
-
console.log(
|
|
564
|
+
console.log(pc2.red(` Unknown resource type "${type}". Valid types: ${validTypes.join(", ")}`));
|
|
448
565
|
process.exit(1);
|
|
449
566
|
}
|
|
450
567
|
return { type, name };
|
|
@@ -509,9 +626,9 @@ function loadLocalCollections(platformDir, filterName) {
|
|
|
509
626
|
}
|
|
510
627
|
}
|
|
511
628
|
if (errors.length > 0) {
|
|
512
|
-
console.log(
|
|
629
|
+
console.log(pc2.red(" Collection errors:"));
|
|
513
630
|
for (const err of errors) {
|
|
514
|
-
console.log(
|
|
631
|
+
console.log(pc2.red(` \u2717 ${err}`));
|
|
515
632
|
}
|
|
516
633
|
throw new Error(`Found ${errors.length} collection error(s)`);
|
|
517
634
|
}
|
|
@@ -569,15 +686,15 @@ function loadLocalAutomations(platformDir, filterName, appName) {
|
|
|
569
686
|
if (appName) {
|
|
570
687
|
code = code.replaceAll("{{app}}", appName);
|
|
571
688
|
}
|
|
572
|
-
automations.push({ automation: config, code });
|
|
689
|
+
automations.push({ automation: config, code, filePath: mainPath });
|
|
573
690
|
} catch (e) {
|
|
574
691
|
errors.push(`${entry.name}: failed to parse config.json - ${e}`);
|
|
575
692
|
}
|
|
576
693
|
}
|
|
577
694
|
if (errors.length > 0) {
|
|
578
|
-
console.log(
|
|
695
|
+
console.log(pc2.red(" Automation errors:"));
|
|
579
696
|
for (const err of errors) {
|
|
580
|
-
console.log(
|
|
697
|
+
console.log(pc2.red(` \u2717 ${err}`));
|
|
581
698
|
}
|
|
582
699
|
throw new Error(`Found ${errors.length} automation error(s)`);
|
|
583
700
|
}
|
|
@@ -595,14 +712,14 @@ function loadLocalHooks(platformDir, filterName, appName) {
|
|
|
595
712
|
const content = readFileSync(filePath, "utf-8");
|
|
596
713
|
const config = parseHookConfig(content);
|
|
597
714
|
if (!config) {
|
|
598
|
-
console.log(
|
|
715
|
+
console.log(pc2.yellow(` \u26A0 Skipping ${file}: could not parse config export`));
|
|
599
716
|
continue;
|
|
600
717
|
}
|
|
601
718
|
if (!config.external_id) {
|
|
602
719
|
if (appName) {
|
|
603
720
|
config.external_id = `${appName}:${file.replace(/\.(js|ts)$/, "")}`;
|
|
604
721
|
} else {
|
|
605
|
-
console.log(
|
|
722
|
+
console.log(pc2.yellow(` \u26A0 Skipping ${file}: missing external_id in config`));
|
|
606
723
|
continue;
|
|
607
724
|
}
|
|
608
725
|
}
|
|
@@ -917,7 +1034,7 @@ function loadLocalMailboxes(platformDir, filterName) {
|
|
|
917
1034
|
if (!file.endsWith(".json")) continue;
|
|
918
1035
|
const slug = file.replace(/\.json$/, "").trim();
|
|
919
1036
|
if (!slug) {
|
|
920
|
-
console.log(
|
|
1037
|
+
console.log(pc2.yellow(` \u26A0 Skipping ${file}: empty filename`));
|
|
921
1038
|
continue;
|
|
922
1039
|
}
|
|
923
1040
|
if (filterName && slug !== filterName) {
|
|
@@ -928,11 +1045,11 @@ function loadLocalMailboxes(platformDir, filterName) {
|
|
|
928
1045
|
try {
|
|
929
1046
|
raw = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
930
1047
|
} catch (e) {
|
|
931
|
-
console.log(
|
|
1048
|
+
console.log(pc2.yellow(` \u26A0 Skipping ${file}: invalid JSON (${e.message})`));
|
|
932
1049
|
continue;
|
|
933
1050
|
}
|
|
934
1051
|
if (!isPlainObject(raw)) {
|
|
935
|
-
console.log(
|
|
1052
|
+
console.log(pc2.yellow(` \u26A0 Skipping ${file}: top-level value must be an object`));
|
|
936
1053
|
continue;
|
|
937
1054
|
}
|
|
938
1055
|
const descriptionRaw = raw["description"];
|
|
@@ -983,17 +1100,17 @@ async function applyMailboxes(api, localMailboxes) {
|
|
|
983
1100
|
const localDesc = (mb.description ?? "").trim();
|
|
984
1101
|
const remoteDesc = (existing.description ?? "").trim();
|
|
985
1102
|
if (localDesc !== remoteDesc) {
|
|
986
|
-
console.log(
|
|
1103
|
+
console.log(pc2.yellow(" \u26A0"), `mailbox ${mb.slug}: description drift not reconcilable by CLI (no PATCH endpoint yet) \u2014 update in the UI or DB`);
|
|
987
1104
|
} else {
|
|
988
|
-
console.log(
|
|
1105
|
+
console.log(pc2.green(" \u2713"), pc2.dim(`mailbox ${mb.slug} already exists (${existing.email})`));
|
|
989
1106
|
}
|
|
990
1107
|
continue;
|
|
991
1108
|
}
|
|
992
1109
|
try {
|
|
993
1110
|
const created = await api.createMailbox({ slug: mb.slug, description: mb.description });
|
|
994
|
-
console.log(
|
|
1111
|
+
console.log(pc2.green(" \u2713"), `created mailbox ${mb.slug} ${pc2.dim(`\u2192 ${created.email}`)}`);
|
|
995
1112
|
} catch (e) {
|
|
996
|
-
console.log(
|
|
1113
|
+
console.log(pc2.red(" \u2717"), `mailbox ${mb.slug}: ${e.message}`);
|
|
997
1114
|
errors++;
|
|
998
1115
|
}
|
|
999
1116
|
}
|
|
@@ -1002,7 +1119,7 @@ async function applyMailboxes(api, localMailboxes) {
|
|
|
1002
1119
|
async function pullMailboxes(api, platformDir, filterName) {
|
|
1003
1120
|
const mailboxes = await api.listMailboxes();
|
|
1004
1121
|
if (mailboxes.length === 0) {
|
|
1005
|
-
console.log(
|
|
1122
|
+
console.log(pc2.dim(" (no mailboxes)"));
|
|
1006
1123
|
return;
|
|
1007
1124
|
}
|
|
1008
1125
|
const outDir = join(platformDir, "mailboxes");
|
|
@@ -1016,11 +1133,11 @@ async function pullMailboxes(api, platformDir, filterName) {
|
|
|
1016
1133
|
if (mb.description) body.description = mb.description;
|
|
1017
1134
|
const file = join(outDir, `${toSafeFilename(mb.slug)}.json`);
|
|
1018
1135
|
writeFileSync(file, JSON.stringify(body, null, 2) + "\n");
|
|
1019
|
-
console.log(
|
|
1136
|
+
console.log(pc2.green(" \u2713"), `pulled mailbox ${mb.slug} ${pc2.dim(`\u2192 ${file}`)}`);
|
|
1020
1137
|
count++;
|
|
1021
1138
|
}
|
|
1022
1139
|
if (count === 0 && filterName) {
|
|
1023
|
-
console.log(
|
|
1140
|
+
console.log(pc2.yellow(` \u26A0 mailbox '${filterName}' not found on remote`));
|
|
1024
1141
|
}
|
|
1025
1142
|
}
|
|
1026
1143
|
async function planHooks(api, localHooks, collections) {
|
|
@@ -1031,7 +1148,7 @@ async function planHooks(api, localHooks, collections) {
|
|
|
1031
1148
|
const remote = remoteByExternalId.get(hook.external_id);
|
|
1032
1149
|
const collectionId = collections.get(hook.collection);
|
|
1033
1150
|
if (!collectionId) {
|
|
1034
|
-
console.log(
|
|
1151
|
+
console.log(pc2.yellow(` \u26A0 Skipping ${fileName}: collection '${hook.collection}' not found`));
|
|
1035
1152
|
continue;
|
|
1036
1153
|
}
|
|
1037
1154
|
if (!remote) {
|
|
@@ -1079,9 +1196,9 @@ async function applyCollections(api, localCollections) {
|
|
|
1079
1196
|
for (const local of localCollections) {
|
|
1080
1197
|
try {
|
|
1081
1198
|
await api.ensureCollection(local.name, { id: local.id });
|
|
1082
|
-
console.log(
|
|
1199
|
+
console.log(pc2.green(" \u2713"), `${local.name}`);
|
|
1083
1200
|
} catch (e) {
|
|
1084
|
-
console.log(
|
|
1201
|
+
console.log(pc2.red(" \u2717"), `${local.name}: ${e}`);
|
|
1085
1202
|
pass1Failed.add(local.name);
|
|
1086
1203
|
errors++;
|
|
1087
1204
|
}
|
|
@@ -1091,9 +1208,9 @@ async function applyCollections(api, localCollections) {
|
|
|
1091
1208
|
const apiFormat = convertCollectionToApiFormat(local);
|
|
1092
1209
|
try {
|
|
1093
1210
|
await api.ensureCollection(local.name, apiFormat);
|
|
1094
|
-
console.log(
|
|
1211
|
+
console.log(pc2.green(" \u2713"), `${local.name} (schema)`);
|
|
1095
1212
|
} catch (e) {
|
|
1096
|
-
console.log(
|
|
1213
|
+
console.log(pc2.red(" \u2717"), `${local.name} (schema): ${e}`);
|
|
1097
1214
|
errors++;
|
|
1098
1215
|
}
|
|
1099
1216
|
}
|
|
@@ -1102,9 +1219,9 @@ async function applyCollections(api, localCollections) {
|
|
|
1102
1219
|
const apiFormat = convertCollectionToApiFormat(local);
|
|
1103
1220
|
try {
|
|
1104
1221
|
await api.ensureCollection(local.name, apiFormat);
|
|
1105
|
-
console.log(
|
|
1222
|
+
console.log(pc2.green(" \u2713"), `${local.name}`);
|
|
1106
1223
|
} catch (e) {
|
|
1107
|
-
console.log(
|
|
1224
|
+
console.log(pc2.red(" \u2717"), `${local.name}: ${e}`);
|
|
1108
1225
|
errors++;
|
|
1109
1226
|
}
|
|
1110
1227
|
}
|
|
@@ -1134,11 +1251,11 @@ async function applyAutomations(api, localAutomations, projectId) {
|
|
|
1134
1251
|
if (remote) {
|
|
1135
1252
|
await api.updateAutomation(remote.id, payload);
|
|
1136
1253
|
automationId = remote.id;
|
|
1137
|
-
console.log(
|
|
1254
|
+
console.log(pc2.green(" \u2713"), `${automation.name} (updated)`);
|
|
1138
1255
|
} else {
|
|
1139
1256
|
const created = await api.createAutomation(payload);
|
|
1140
1257
|
automationId = created.id;
|
|
1141
|
-
console.log(
|
|
1258
|
+
console.log(pc2.green(" \u2713"), `${automation.name} (created)`);
|
|
1142
1259
|
}
|
|
1143
1260
|
const localPresets = automation.inputs?.presets;
|
|
1144
1261
|
if (localPresets) {
|
|
@@ -1148,7 +1265,7 @@ async function applyAutomations(api, localAutomations, projectId) {
|
|
|
1148
1265
|
await setSchedule(api, automationId, automation.schedule, localPresets || {});
|
|
1149
1266
|
} else if (remote?.schedule) {
|
|
1150
1267
|
await api.updateAutomation(automationId, { schedule: "", schedule_tz: "" });
|
|
1151
|
-
console.log(
|
|
1268
|
+
console.log(pc2.dim(` Cleared schedule`));
|
|
1152
1269
|
}
|
|
1153
1270
|
if (localPresets) {
|
|
1154
1271
|
const current = await api.getAutomation(automationId).catch(() => null);
|
|
@@ -1162,7 +1279,7 @@ async function applyAutomations(api, localAutomations, projectId) {
|
|
|
1162
1279
|
}
|
|
1163
1280
|
}
|
|
1164
1281
|
} catch (e) {
|
|
1165
|
-
console.log(
|
|
1282
|
+
console.log(pc2.red(" \u2717"), `${automation.name}: ${e}`);
|
|
1166
1283
|
errors++;
|
|
1167
1284
|
}
|
|
1168
1285
|
}
|
|
@@ -1177,13 +1294,13 @@ async function syncPresets(api, automationId, localPresets) {
|
|
|
1177
1294
|
try {
|
|
1178
1295
|
if (existing) {
|
|
1179
1296
|
await api.updatePreset(existing.id, { name: presetName, inputs: preset.inputs });
|
|
1180
|
-
console.log(
|
|
1297
|
+
console.log(pc2.dim(` Updated preset: ${presetName}`));
|
|
1181
1298
|
} else {
|
|
1182
1299
|
await api.createPreset(automationId, { name: presetName, inputs: preset.inputs });
|
|
1183
|
-
console.log(
|
|
1300
|
+
console.log(pc2.dim(` Created preset: ${presetName}`));
|
|
1184
1301
|
}
|
|
1185
1302
|
} catch (e) {
|
|
1186
|
-
console.log(
|
|
1303
|
+
console.log(pc2.yellow(` \u26A0 Failed to sync preset ${presetName}: ${e}`));
|
|
1187
1304
|
}
|
|
1188
1305
|
}
|
|
1189
1306
|
}
|
|
@@ -1198,9 +1315,9 @@ async function deleteStalePresets(api, automationId, localPresets, preservePrese
|
|
|
1198
1315
|
}
|
|
1199
1316
|
try {
|
|
1200
1317
|
await api.deletePreset(remote.id);
|
|
1201
|
-
console.log(
|
|
1318
|
+
console.log(pc2.dim(` Deleted preset: ${remote.name}`));
|
|
1202
1319
|
} catch (e) {
|
|
1203
|
-
console.log(
|
|
1320
|
+
console.log(pc2.yellow(` \u26A0 Failed to delete preset ${remote.name}: ${e}`));
|
|
1204
1321
|
}
|
|
1205
1322
|
}
|
|
1206
1323
|
}
|
|
@@ -1209,7 +1326,7 @@ async function setSchedule(api, automationId, schedule, localPresets) {
|
|
|
1209
1326
|
const remotePresets = await api.listPresets(automationId);
|
|
1210
1327
|
const preset = remotePresets.find((p) => p.name === presetName);
|
|
1211
1328
|
if (!preset) {
|
|
1212
|
-
console.log(
|
|
1329
|
+
console.log(pc2.yellow(` \u26A0 Schedule preset '${schedule.preset}' not found, skipping schedule`));
|
|
1213
1330
|
return;
|
|
1214
1331
|
}
|
|
1215
1332
|
try {
|
|
@@ -1218,9 +1335,9 @@ async function setSchedule(api, automationId, schedule, localPresets) {
|
|
|
1218
1335
|
schedule_tz: schedule.timezone || "UTC",
|
|
1219
1336
|
schedule_preset_id: preset.id
|
|
1220
1337
|
});
|
|
1221
|
-
console.log(
|
|
1338
|
+
console.log(pc2.dim(` Set schedule: ${schedule.cron}`));
|
|
1222
1339
|
} catch (e) {
|
|
1223
|
-
console.log(
|
|
1340
|
+
console.log(pc2.yellow(` \u26A0 Failed to set schedule: ${e}`));
|
|
1224
1341
|
}
|
|
1225
1342
|
}
|
|
1226
1343
|
async function applyHooks(api, localHooks, collections, projectId) {
|
|
@@ -1231,7 +1348,7 @@ async function applyHooks(api, localHooks, collections, projectId) {
|
|
|
1231
1348
|
const remote = remoteByExternalId.get(hook.external_id);
|
|
1232
1349
|
const collectionId = collections.get(hook.collection);
|
|
1233
1350
|
if (!collectionId) {
|
|
1234
|
-
console.log(
|
|
1351
|
+
console.log(pc2.red(` \u2717 ${fileName}: collection '${hook.collection}' not found. Apply the collection first or use 'lumera apply' to apply all resources.`));
|
|
1235
1352
|
errors++;
|
|
1236
1353
|
continue;
|
|
1237
1354
|
}
|
|
@@ -1250,13 +1367,13 @@ async function applyHooks(api, localHooks, collections, projectId) {
|
|
|
1250
1367
|
try {
|
|
1251
1368
|
if (remote) {
|
|
1252
1369
|
await api.updateHook(remote.id, payload);
|
|
1253
|
-
console.log(
|
|
1370
|
+
console.log(pc2.green(" \u2713"), `${payload.name} (updated)`);
|
|
1254
1371
|
} else {
|
|
1255
1372
|
await api.createHook(payload);
|
|
1256
|
-
console.log(
|
|
1373
|
+
console.log(pc2.green(" \u2713"), `${payload.name} (created)`);
|
|
1257
1374
|
}
|
|
1258
1375
|
} catch (e) {
|
|
1259
|
-
console.log(
|
|
1376
|
+
console.log(pc2.red(" \u2717"), `${payload.name}: ${e}`);
|
|
1260
1377
|
errors++;
|
|
1261
1378
|
}
|
|
1262
1379
|
}
|
|
@@ -1271,7 +1388,7 @@ async function applyApp(args) {
|
|
|
1271
1388
|
const appTitle = getAppTitle(projectRoot);
|
|
1272
1389
|
const apiUrl = getApiUrl();
|
|
1273
1390
|
if (!skipBuild) {
|
|
1274
|
-
console.log(
|
|
1391
|
+
console.log(pc2.dim(" Building..."));
|
|
1275
1392
|
try {
|
|
1276
1393
|
const pm = detectPackageManager();
|
|
1277
1394
|
execSync(`${pm} run build`, { cwd: projectRoot, stdio: "inherit" });
|
|
@@ -1329,7 +1446,7 @@ async function pullCollections(api, platformDir, filterName, appName) {
|
|
|
1329
1446
|
const fileName = toSafeFilename(localName);
|
|
1330
1447
|
const filePath = join(collectionsDir, `${fileName}.json`);
|
|
1331
1448
|
writeFileSync(filePath, JSON.stringify(localFormat, null, 2) + "\n");
|
|
1332
|
-
console.log(
|
|
1449
|
+
console.log(pc2.green(" \u2713"), `${localName} \u2192 collections/${fileName}.json`);
|
|
1333
1450
|
}
|
|
1334
1451
|
}
|
|
1335
1452
|
async function pullAutomations(api, platformDir, filterName, projectId) {
|
|
@@ -1379,7 +1496,7 @@ async function pullAutomations(api, platformDir, filterName, projectId) {
|
|
|
1379
1496
|
}
|
|
1380
1497
|
writeFileSync(join(automationDir, "config.json"), JSON.stringify(config, null, 2) + "\n");
|
|
1381
1498
|
writeFileSync(join(automationDir, "main.py"), automation.code || "");
|
|
1382
|
-
console.log(
|
|
1499
|
+
console.log(pc2.green(" \u2713"), `${automation.name} \u2192 automations/${dirName}/`);
|
|
1383
1500
|
}
|
|
1384
1501
|
}
|
|
1385
1502
|
async function pullHooks(api, platformDir, filterName, appName, projectId) {
|
|
@@ -1406,7 +1523,7 @@ ${hook.script.split("\n").map((line) => " " + line).join("\n")}
|
|
|
1406
1523
|
}
|
|
1407
1524
|
`;
|
|
1408
1525
|
writeFileSync(join(hooksDir, fileName), content);
|
|
1409
|
-
console.log(
|
|
1526
|
+
console.log(pc2.green(" \u2713"), `${hook.name} \u2192 hooks/${fileName}`);
|
|
1410
1527
|
}
|
|
1411
1528
|
}
|
|
1412
1529
|
function loadLocalAgents(platformDir, filterName, appName) {
|
|
@@ -1435,7 +1552,7 @@ function loadLocalAgents(platformDir, filterName, appName) {
|
|
|
1435
1552
|
}
|
|
1436
1553
|
if (config.mcp_servers !== void 0) {
|
|
1437
1554
|
console.log(
|
|
1438
|
-
|
|
1555
|
+
pc2.yellow(
|
|
1439
1556
|
` \u26A0 ${entry.name}: "mcp_servers" is deprecated and ignored \u2014 MCP servers are now scoped per project in Platform \u2192 Integrations.`
|
|
1440
1557
|
)
|
|
1441
1558
|
);
|
|
@@ -1464,9 +1581,9 @@ function loadLocalAgents(platformDir, filterName, appName) {
|
|
|
1464
1581
|
}
|
|
1465
1582
|
}
|
|
1466
1583
|
if (errors.length > 0) {
|
|
1467
|
-
console.log(
|
|
1584
|
+
console.log(pc2.red(" Agent errors:"));
|
|
1468
1585
|
for (const err of errors) {
|
|
1469
|
-
console.log(
|
|
1586
|
+
console.log(pc2.red(` \u2717 ${err}`));
|
|
1470
1587
|
}
|
|
1471
1588
|
throw new Error(`Found ${errors.length} agent error(s)`);
|
|
1472
1589
|
}
|
|
@@ -1539,7 +1656,7 @@ async function applyAgents(api, localAgents, projectId) {
|
|
|
1539
1656
|
const skills = await api.listAgentSkills();
|
|
1540
1657
|
skillMap = new Map(skills.map((s) => [s.slug, s.id]));
|
|
1541
1658
|
} catch (e) {
|
|
1542
|
-
console.log(
|
|
1659
|
+
console.log(pc2.yellow(` \u26A0 Could not fetch skills for resolution: ${e}`));
|
|
1543
1660
|
}
|
|
1544
1661
|
}
|
|
1545
1662
|
for (const { agent, systemPrompt, policyScript } of localAgents) {
|
|
@@ -1551,7 +1668,7 @@ async function applyAgents(api, localAgents, projectId) {
|
|
|
1551
1668
|
if (id) {
|
|
1552
1669
|
skillIds.push(id);
|
|
1553
1670
|
} else {
|
|
1554
|
-
console.log(
|
|
1671
|
+
console.log(pc2.yellow(` \u26A0 Skill "${slug}" not found, skipping`));
|
|
1555
1672
|
}
|
|
1556
1673
|
}
|
|
1557
1674
|
}
|
|
@@ -1569,14 +1686,14 @@ async function applyAgents(api, localAgents, projectId) {
|
|
|
1569
1686
|
try {
|
|
1570
1687
|
if (remote) {
|
|
1571
1688
|
await api.updateAgent(remote.id, payload);
|
|
1572
|
-
console.log(
|
|
1689
|
+
console.log(pc2.green(" \u2713"), `${agent.name} (updated)`);
|
|
1573
1690
|
} else {
|
|
1574
1691
|
if (projectId) payload.project_id = projectId;
|
|
1575
1692
|
await api.createAgent(payload);
|
|
1576
|
-
console.log(
|
|
1693
|
+
console.log(pc2.green(" \u2713"), `${agent.name} (created)`);
|
|
1577
1694
|
}
|
|
1578
1695
|
} catch (e) {
|
|
1579
|
-
console.log(
|
|
1696
|
+
console.log(pc2.red(" \u2717"), `${agent.name}: ${e}`);
|
|
1580
1697
|
errors++;
|
|
1581
1698
|
}
|
|
1582
1699
|
}
|
|
@@ -1620,7 +1737,7 @@ async function pullAgents(api, platformDir, filterName, projectId) {
|
|
|
1620
1737
|
if (agent.policy_script) {
|
|
1621
1738
|
writeFileSync(join(agentDir, "policy.js"), agent.policy_script);
|
|
1622
1739
|
}
|
|
1623
|
-
console.log(
|
|
1740
|
+
console.log(pc2.green(" \u2713"), `${agent.name} \u2192 agents/${dirName}/`);
|
|
1624
1741
|
}
|
|
1625
1742
|
}
|
|
1626
1743
|
async function listResources(api, platformDir, filterType, appName, projectId) {
|
|
@@ -1886,13 +2003,13 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
|
|
|
1886
2003
|
}
|
|
1887
2004
|
}
|
|
1888
2005
|
if (toDelete.length === 0) {
|
|
1889
|
-
console.log(
|
|
2006
|
+
console.log(pc2.green(" \u2713 No resources found to delete"));
|
|
1890
2007
|
return;
|
|
1891
2008
|
}
|
|
1892
|
-
console.log(
|
|
2009
|
+
console.log(pc2.bold(" Resources to delete:"));
|
|
1893
2010
|
console.log();
|
|
1894
2011
|
for (const item of toDelete) {
|
|
1895
|
-
console.log(
|
|
2012
|
+
console.log(pc2.red(` - ${item.type}: ${item.name}`));
|
|
1896
2013
|
}
|
|
1897
2014
|
console.log();
|
|
1898
2015
|
if (!skipConfirm) {
|
|
@@ -1903,7 +2020,7 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
|
|
|
1903
2020
|
initial: false
|
|
1904
2021
|
});
|
|
1905
2022
|
if (!confirmed) {
|
|
1906
|
-
console.log(
|
|
2023
|
+
console.log(pc2.dim(" Cancelled"));
|
|
1907
2024
|
return;
|
|
1908
2025
|
}
|
|
1909
2026
|
}
|
|
@@ -1915,45 +2032,45 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
|
|
|
1915
2032
|
for (const resource of agents) {
|
|
1916
2033
|
try {
|
|
1917
2034
|
await api.deleteAgent(resource.remoteId);
|
|
1918
|
-
console.log(
|
|
2035
|
+
console.log(pc2.green(" \u2713"), `Deleted agent: ${resource.name}`);
|
|
1919
2036
|
} catch (e) {
|
|
1920
|
-
console.log(
|
|
2037
|
+
console.log(pc2.red(" \u2717"), `Failed to delete agent ${resource.name}: ${e}`);
|
|
1921
2038
|
errors++;
|
|
1922
2039
|
}
|
|
1923
2040
|
}
|
|
1924
2041
|
for (const resource of hooks) {
|
|
1925
2042
|
try {
|
|
1926
2043
|
await api.deleteHook(resource.remoteId);
|
|
1927
|
-
console.log(
|
|
2044
|
+
console.log(pc2.green(" \u2713"), `Deleted hook: ${resource.name}`);
|
|
1928
2045
|
} catch (e) {
|
|
1929
|
-
console.log(
|
|
2046
|
+
console.log(pc2.red(" \u2717"), `Failed to delete hook ${resource.name}: ${e}`);
|
|
1930
2047
|
errors++;
|
|
1931
2048
|
}
|
|
1932
2049
|
}
|
|
1933
2050
|
for (const resource of automations) {
|
|
1934
2051
|
try {
|
|
1935
2052
|
await api.deleteAutomation(resource.remoteId);
|
|
1936
|
-
console.log(
|
|
2053
|
+
console.log(pc2.green(" \u2713"), `Deleted automation: ${resource.name}`);
|
|
1937
2054
|
} catch (e) {
|
|
1938
|
-
console.log(
|
|
2055
|
+
console.log(pc2.red(" \u2717"), `Failed to delete automation ${resource.name}: ${e}`);
|
|
1939
2056
|
errors++;
|
|
1940
2057
|
}
|
|
1941
2058
|
}
|
|
1942
2059
|
const deletePlan = planCollectionDelete(collections, platformDir);
|
|
1943
2060
|
if (deletePlan.cycleNames.length > 0 && !forceCycles) {
|
|
1944
2061
|
console.log();
|
|
1945
|
-
console.log(
|
|
2062
|
+
console.log(pc2.yellow(" Circular references detected:"));
|
|
1946
2063
|
for (const edge of deletePlan.cycleEdges) {
|
|
1947
|
-
console.log(
|
|
2064
|
+
console.log(pc2.yellow(` ${edge.from}.${edge.field} \u2192 ${edge.to}`));
|
|
1948
2065
|
}
|
|
1949
2066
|
console.log();
|
|
1950
|
-
console.log(
|
|
1951
|
-
console.log(
|
|
2067
|
+
console.log(pc2.dim(" To destroy these, relation fields forming the cycle must be removed first."));
|
|
2068
|
+
console.log(pc2.dim(" Use --force-cycles to proceed."));
|
|
1952
2069
|
console.log();
|
|
1953
2070
|
process.exit(1);
|
|
1954
2071
|
}
|
|
1955
2072
|
if (deletePlan.cycleNames.length > 0 && forceCycles) {
|
|
1956
|
-
console.log(
|
|
2073
|
+
console.log(pc2.dim(" Breaking circular references..."));
|
|
1957
2074
|
for (const edge of deletePlan.cycleEdges) {
|
|
1958
2075
|
const resource = collections.find((c) => c.name === edge.from);
|
|
1959
2076
|
if (!resource?.remoteId) continue;
|
|
@@ -1963,10 +2080,10 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
|
|
|
1963
2080
|
if (remote) {
|
|
1964
2081
|
const updatedSchema = remote.schema.filter((f) => f.name !== edge.field);
|
|
1965
2082
|
await api.ensureCollection(remote.name, { name: remote.name, schema: updatedSchema });
|
|
1966
|
-
console.log(
|
|
2083
|
+
console.log(pc2.green(" \u2713"), `Removed ${edge.from}.${edge.field}`);
|
|
1967
2084
|
}
|
|
1968
2085
|
} catch (e) {
|
|
1969
|
-
console.log(
|
|
2086
|
+
console.log(pc2.red(" \u2717"), `Failed to remove ${edge.from}.${edge.field}: ${e}`);
|
|
1970
2087
|
errors++;
|
|
1971
2088
|
}
|
|
1972
2089
|
}
|
|
@@ -1974,9 +2091,9 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
|
|
|
1974
2091
|
for (const resource of deletePlan.sorted) {
|
|
1975
2092
|
try {
|
|
1976
2093
|
await api.deleteCollection(resource.remoteId);
|
|
1977
|
-
console.log(
|
|
2094
|
+
console.log(pc2.green(" \u2713"), `Deleted collection: ${resource.name}`);
|
|
1978
2095
|
} catch (e) {
|
|
1979
|
-
console.log(
|
|
2096
|
+
console.log(pc2.red(" \u2717"), `Failed to delete collection ${resource.name}: ${e}`);
|
|
1980
2097
|
errors++;
|
|
1981
2098
|
}
|
|
1982
2099
|
}
|
|
@@ -2006,10 +2123,10 @@ async function destroyApp(skipConfirm) {
|
|
|
2006
2123
|
const data = await searchRes.json();
|
|
2007
2124
|
const appRecord = data.items?.[0];
|
|
2008
2125
|
if (!appRecord) {
|
|
2009
|
-
console.log(
|
|
2126
|
+
console.log(pc2.yellow(` App "${appName}" not found in Lumera.`));
|
|
2010
2127
|
return;
|
|
2011
2128
|
}
|
|
2012
|
-
console.log(
|
|
2129
|
+
console.log(pc2.dim(` App to delete: ${appRecord.name} (${appRecord.external_id})`));
|
|
2013
2130
|
console.log();
|
|
2014
2131
|
if (!skipConfirm) {
|
|
2015
2132
|
const { confirmed } = await prompts({
|
|
@@ -2019,7 +2136,7 @@ async function destroyApp(skipConfirm) {
|
|
|
2019
2136
|
initial: false
|
|
2020
2137
|
});
|
|
2021
2138
|
if (!confirmed) {
|
|
2022
|
-
console.log(
|
|
2139
|
+
console.log(pc2.dim(" Cancelled"));
|
|
2023
2140
|
return;
|
|
2024
2141
|
}
|
|
2025
2142
|
}
|
|
@@ -2036,7 +2153,7 @@ async function destroyApp(skipConfirm) {
|
|
|
2036
2153
|
if (!deleteRes.ok) {
|
|
2037
2154
|
throw new Error(`Failed to delete app: ${await deleteRes.text()}`);
|
|
2038
2155
|
}
|
|
2039
|
-
console.log(
|
|
2156
|
+
console.log(pc2.green(" \u2713"), `App "${appRecord.name}" deleted from Lumera.`);
|
|
2040
2157
|
}
|
|
2041
2158
|
async function showResource(api, platformDir, resourceType, resourceName, appName, projectId) {
|
|
2042
2159
|
if (resourceType === "collections") {
|
|
@@ -2045,11 +2162,11 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
|
|
|
2045
2162
|
const local = localCollections[0];
|
|
2046
2163
|
const remote = remoteCollections.find((c) => c.name === resourceName || c.id === resourceName);
|
|
2047
2164
|
if (!local && !remote) {
|
|
2048
|
-
console.log(
|
|
2165
|
+
console.log(pc2.red(` Collection "${resourceName}" not found`));
|
|
2049
2166
|
process.exit(1);
|
|
2050
2167
|
}
|
|
2051
2168
|
console.log();
|
|
2052
|
-
console.log(
|
|
2169
|
+
console.log(pc2.bold(` Collection: ${resourceName}`));
|
|
2053
2170
|
console.log();
|
|
2054
2171
|
let collectionStatus;
|
|
2055
2172
|
let addedFields = [];
|
|
@@ -2066,38 +2183,38 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
|
|
|
2066
2183
|
collectionStatus = "remote-only";
|
|
2067
2184
|
}
|
|
2068
2185
|
const statusDisplay = {
|
|
2069
|
-
"synced":
|
|
2070
|
-
"changed":
|
|
2071
|
-
"local-only":
|
|
2072
|
-
"remote-only":
|
|
2186
|
+
"synced": pc2.green("synced"),
|
|
2187
|
+
"changed": pc2.yellow("changed"),
|
|
2188
|
+
"local-only": pc2.yellow("local only"),
|
|
2189
|
+
"remote-only": pc2.cyan("remote only")
|
|
2073
2190
|
};
|
|
2074
2191
|
console.log(` Status: ${statusDisplay[collectionStatus]}`);
|
|
2075
2192
|
console.log();
|
|
2076
2193
|
const addedSet = new Set(addedFields);
|
|
2077
2194
|
const removedSet = new Set(removedFields);
|
|
2078
|
-
console.log(
|
|
2195
|
+
console.log(pc2.bold(" Fields:"));
|
|
2079
2196
|
if (local) {
|
|
2080
2197
|
for (const field of local.fields) {
|
|
2081
|
-
const req = field.required ?
|
|
2198
|
+
const req = field.required ? pc2.red("*") : "";
|
|
2082
2199
|
if (addedSet.has(field.name)) {
|
|
2083
|
-
console.log(` ${
|
|
2200
|
+
console.log(` ${pc2.green("+")} ${field.name}${req} ${pc2.dim(`(${field.type})`)}`);
|
|
2084
2201
|
} else {
|
|
2085
|
-
console.log(` ${field.name}${req} ${
|
|
2202
|
+
console.log(` ${field.name}${req} ${pc2.dim(`(${field.type})`)}`);
|
|
2086
2203
|
}
|
|
2087
2204
|
}
|
|
2088
2205
|
}
|
|
2089
2206
|
if (remote) {
|
|
2090
2207
|
for (const field of remote.schema) {
|
|
2091
2208
|
if (removedSet.has(field.name)) {
|
|
2092
|
-
const req = field.required ?
|
|
2093
|
-
console.log(` ${
|
|
2209
|
+
const req = field.required ? pc2.red("*") : "";
|
|
2210
|
+
console.log(` ${pc2.red("-")} ${field.name}${req} ${pc2.dim(`(${field.type})`)}`);
|
|
2094
2211
|
}
|
|
2095
2212
|
}
|
|
2096
2213
|
}
|
|
2097
2214
|
if (!local && remote) {
|
|
2098
2215
|
for (const field of remote.schema) {
|
|
2099
|
-
const req = field.required ?
|
|
2100
|
-
console.log(` ${field.name}${req} ${
|
|
2216
|
+
const req = field.required ? pc2.red("*") : "";
|
|
2217
|
+
console.log(` ${field.name}${req} ${pc2.dim(`(${field.type})`)}`);
|
|
2101
2218
|
}
|
|
2102
2219
|
}
|
|
2103
2220
|
console.log();
|
|
@@ -2107,18 +2224,18 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
|
|
|
2107
2224
|
const local = localAutomations[0];
|
|
2108
2225
|
const remote = remoteAutomations.find((a) => a.external_id === resourceName || a.name === resourceName);
|
|
2109
2226
|
if (!local && !remote) {
|
|
2110
|
-
console.log(
|
|
2227
|
+
console.log(pc2.red(` Automation "${resourceName}" not found`));
|
|
2111
2228
|
process.exit(1);
|
|
2112
2229
|
}
|
|
2113
2230
|
console.log();
|
|
2114
|
-
console.log(
|
|
2231
|
+
console.log(pc2.bold(` Automation: ${local?.automation.name || remote?.name}`));
|
|
2115
2232
|
console.log();
|
|
2116
2233
|
if (local && remote) {
|
|
2117
|
-
console.log(` Status: ${
|
|
2234
|
+
console.log(` Status: ${pc2.green("synced")}`);
|
|
2118
2235
|
} else if (local) {
|
|
2119
|
-
console.log(` Status: ${
|
|
2236
|
+
console.log(` Status: ${pc2.yellow("local only")}`);
|
|
2120
2237
|
} else {
|
|
2121
|
-
console.log(` Status: ${
|
|
2238
|
+
console.log(` Status: ${pc2.cyan("remote only")}`);
|
|
2122
2239
|
}
|
|
2123
2240
|
if (local?.automation.description || remote?.description) {
|
|
2124
2241
|
console.log(` Description: ${local?.automation.description || remote?.description}`);
|
|
@@ -2130,11 +2247,11 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
|
|
|
2130
2247
|
const local = localHooks[0];
|
|
2131
2248
|
const remote = remoteHooks.find((h) => h.external_id === resourceName);
|
|
2132
2249
|
if (!local && !remote) {
|
|
2133
|
-
console.log(
|
|
2250
|
+
console.log(pc2.red(` Hook "${resourceName}" not found`));
|
|
2134
2251
|
process.exit(1);
|
|
2135
2252
|
}
|
|
2136
2253
|
console.log();
|
|
2137
|
-
console.log(
|
|
2254
|
+
console.log(pc2.bold(` Hook: ${local?.hook.external_id || remote?.external_id}`));
|
|
2138
2255
|
console.log();
|
|
2139
2256
|
console.log(` Collection: ${local?.hook.collection || remote?.collection_name}`);
|
|
2140
2257
|
console.log(` Trigger: ${local?.hook.trigger || remote?.event}`);
|
|
@@ -2146,25 +2263,25 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
|
|
|
2146
2263
|
const local = localAgents[0];
|
|
2147
2264
|
const remote = remoteAgents.find((a) => a.external_id === resourceName || a.name === resourceName);
|
|
2148
2265
|
if (!local && !remote) {
|
|
2149
|
-
console.log(
|
|
2266
|
+
console.log(pc2.red(` Agent "${resourceName}" not found`));
|
|
2150
2267
|
process.exit(1);
|
|
2151
2268
|
}
|
|
2152
2269
|
console.log();
|
|
2153
|
-
console.log(
|
|
2270
|
+
console.log(pc2.bold(` Agent: ${local?.agent.name || remote?.name}`));
|
|
2154
2271
|
console.log();
|
|
2155
2272
|
if (local && remote) {
|
|
2156
2273
|
const promptChanged = (remote.system_prompt || "").trim() !== local.systemPrompt.trim();
|
|
2157
2274
|
const nameChanged = remote.name !== local.agent.name;
|
|
2158
2275
|
const descChanged = (remote.description || "") !== (local.agent.description || "");
|
|
2159
2276
|
if (promptChanged || nameChanged || descChanged) {
|
|
2160
|
-
console.log(` Status: ${
|
|
2277
|
+
console.log(` Status: ${pc2.yellow("changed")}`);
|
|
2161
2278
|
} else {
|
|
2162
|
-
console.log(` Status: ${
|
|
2279
|
+
console.log(` Status: ${pc2.green("synced")}`);
|
|
2163
2280
|
}
|
|
2164
2281
|
} else if (local) {
|
|
2165
|
-
console.log(` Status: ${
|
|
2282
|
+
console.log(` Status: ${pc2.yellow("local only")}`);
|
|
2166
2283
|
} else {
|
|
2167
|
-
console.log(` Status: ${
|
|
2284
|
+
console.log(` Status: ${pc2.cyan("remote only")}`);
|
|
2168
2285
|
}
|
|
2169
2286
|
if (local?.agent.external_id || remote?.external_id) {
|
|
2170
2287
|
console.log(` External ID: ${local?.agent.external_id || remote?.external_id}`);
|
|
@@ -2183,19 +2300,19 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
|
|
|
2183
2300
|
const local = localMailboxes[0];
|
|
2184
2301
|
const remote = remoteMailboxes.find((m) => m.slug === resourceName);
|
|
2185
2302
|
if (!local && !remote) {
|
|
2186
|
-
console.log(
|
|
2303
|
+
console.log(pc2.red(` Mailbox "${resourceName}" not found`));
|
|
2187
2304
|
process.exit(1);
|
|
2188
2305
|
}
|
|
2189
2306
|
console.log();
|
|
2190
|
-
console.log(
|
|
2307
|
+
console.log(pc2.bold(` Mailbox: ${local?.slug || remote?.slug}`));
|
|
2191
2308
|
console.log();
|
|
2192
2309
|
if (local && remote) {
|
|
2193
2310
|
const descChanged = (local.description ?? "").trim() !== (remote.description ?? "").trim();
|
|
2194
|
-
console.log(` Status: ${descChanged ?
|
|
2311
|
+
console.log(` Status: ${descChanged ? pc2.yellow("changed") : pc2.green("synced")}`);
|
|
2195
2312
|
} else if (local) {
|
|
2196
|
-
console.log(` Status: ${
|
|
2313
|
+
console.log(` Status: ${pc2.yellow("local only")}`);
|
|
2197
2314
|
} else {
|
|
2198
|
-
console.log(` Status: ${
|
|
2315
|
+
console.log(` Status: ${pc2.cyan("remote only")}`);
|
|
2199
2316
|
}
|
|
2200
2317
|
if (remote?.email) console.log(` Email: ${remote.email}`);
|
|
2201
2318
|
const desc = local?.description || remote?.description;
|
|
@@ -2210,7 +2327,7 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
|
|
|
2210
2327
|
const appTitle = getAppTitle(projectRoot);
|
|
2211
2328
|
let apiUrl = getApiUrl().replace(/\/+$/, "").replace(/\/api$/, "");
|
|
2212
2329
|
console.log();
|
|
2213
|
-
console.log(
|
|
2330
|
+
console.log(pc2.bold(` App: ${appTitle || appName2}`));
|
|
2214
2331
|
console.log();
|
|
2215
2332
|
console.log(` External ID: ${appName2}`);
|
|
2216
2333
|
const filterParam = encodeURIComponent(JSON.stringify({ external_id: appName2 }));
|
|
@@ -2227,12 +2344,12 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
|
|
|
2227
2344
|
const data = await searchRes.json();
|
|
2228
2345
|
const appRecord = data.items?.[0];
|
|
2229
2346
|
if (appRecord) {
|
|
2230
|
-
console.log(` Status: ${
|
|
2347
|
+
console.log(` Status: ${pc2.green("deployed")}`);
|
|
2231
2348
|
if (appRecord.hosting_type) console.log(` Hosting: ${appRecord.hosting_type}`);
|
|
2232
2349
|
if (appRecord.current_version) console.log(` Version: ${appRecord.current_version}`);
|
|
2233
2350
|
if (appRecord.deployed_at) console.log(` Deployed: ${appRecord.deployed_at}`);
|
|
2234
2351
|
} else {
|
|
2235
|
-
console.log(` Status: ${
|
|
2352
|
+
console.log(` Status: ${pc2.yellow("not deployed")}`);
|
|
2236
2353
|
}
|
|
2237
2354
|
}
|
|
2238
2355
|
console.log();
|
|
@@ -2253,8 +2370,8 @@ async function plan(args) {
|
|
|
2253
2370
|
const positionalArgs = args.filter((a) => !a.startsWith("-"));
|
|
2254
2371
|
const { type, name } = parseResource(positionalArgs.filter((a) => a !== "--json")[0]);
|
|
2255
2372
|
console.log();
|
|
2256
|
-
console.log(
|
|
2257
|
-
console.log(
|
|
2373
|
+
console.log(pc2.cyan(pc2.bold(" Plan")));
|
|
2374
|
+
console.log(pc2.dim(" Comparing local files to remote state..."));
|
|
2258
2375
|
console.log();
|
|
2259
2376
|
await syncDeps(projectRoot);
|
|
2260
2377
|
const allChanges = [];
|
|
@@ -2272,8 +2389,9 @@ async function plan(args) {
|
|
|
2272
2389
|
allChanges.push(...changes);
|
|
2273
2390
|
}
|
|
2274
2391
|
}
|
|
2392
|
+
let localAutomations = [];
|
|
2275
2393
|
if (!type || type === "automations") {
|
|
2276
|
-
|
|
2394
|
+
localAutomations = loadLocalAutomations(platformDir, name || void 0, appName);
|
|
2277
2395
|
if (localAutomations.length > 0) {
|
|
2278
2396
|
const changes = await planAutomations(api, localAutomations);
|
|
2279
2397
|
allChanges.push(...changes);
|
|
@@ -2300,13 +2418,15 @@ async function plan(args) {
|
|
|
2300
2418
|
allChanges.push(...changes);
|
|
2301
2419
|
}
|
|
2302
2420
|
}
|
|
2421
|
+
const lintWarnings = safeLint(projectRoot, localAutomations);
|
|
2303
2422
|
if (allChanges.length === 0) {
|
|
2304
2423
|
if (jsonOutput) {
|
|
2305
|
-
console.log(JSON.stringify({ changes: [], warnings: [] }));
|
|
2424
|
+
console.log(JSON.stringify({ changes: [], warnings: [], lintWarnings: serializeLintWarnings(lintWarnings, projectRoot) }));
|
|
2306
2425
|
return;
|
|
2307
2426
|
}
|
|
2308
|
-
console.log(
|
|
2427
|
+
console.log(pc2.green(" \u2713 No changes detected"));
|
|
2309
2428
|
console.log();
|
|
2429
|
+
safePrintLint(lintWarnings, projectRoot);
|
|
2310
2430
|
return;
|
|
2311
2431
|
}
|
|
2312
2432
|
if (jsonOutput) {
|
|
@@ -2319,21 +2439,21 @@ async function plan(args) {
|
|
|
2319
2439
|
}
|
|
2320
2440
|
}
|
|
2321
2441
|
}
|
|
2322
|
-
console.log(JSON.stringify({ changes: allChanges, warnings }));
|
|
2442
|
+
console.log(JSON.stringify({ changes: allChanges, warnings, lintWarnings: serializeLintWarnings(lintWarnings, projectRoot) }));
|
|
2323
2443
|
return;
|
|
2324
2444
|
}
|
|
2325
|
-
console.log(
|
|
2445
|
+
console.log(pc2.bold(" Changes:"));
|
|
2326
2446
|
console.log();
|
|
2327
2447
|
for (const change of allChanges) {
|
|
2328
2448
|
const icon = change.type === "create" ? "+" : change.type === "update" ? "~" : "-";
|
|
2329
|
-
const color = change.type === "create" ?
|
|
2449
|
+
const color = change.type === "create" ? pc2.green : change.type === "update" ? pc2.yellow : pc2.red;
|
|
2330
2450
|
const details = change.details ? ` (${change.details})` : "";
|
|
2331
|
-
console.log(` ${color(icon)} ${change.resource}: ${change.name}${
|
|
2451
|
+
console.log(` ${color(icon)} ${change.resource}: ${change.name}${pc2.dim(details)}`);
|
|
2332
2452
|
if (change.fieldDetails && change.fieldDetails.length > 0) {
|
|
2333
2453
|
for (const field of change.fieldDetails) {
|
|
2334
|
-
const fColor = field.action === "+" ?
|
|
2454
|
+
const fColor = field.action === "+" ? pc2.green : pc2.red;
|
|
2335
2455
|
const req = field.required ? "*" : "";
|
|
2336
|
-
console.log(` ${fColor(field.action)} ${field.name}${req} ${
|
|
2456
|
+
console.log(` ${fColor(field.action)} ${field.name}${req} ${pc2.dim(`(${field.type})`)}`);
|
|
2337
2457
|
}
|
|
2338
2458
|
console.log();
|
|
2339
2459
|
}
|
|
@@ -2342,16 +2462,16 @@ async function plan(args) {
|
|
|
2342
2462
|
for (const td of change.textDiffs) {
|
|
2343
2463
|
const diffLines = computeLineDiff(td.oldText, td.newText);
|
|
2344
2464
|
if (diffLines.length > 0) {
|
|
2345
|
-
console.log(
|
|
2465
|
+
console.log(pc2.dim(` --- ${td.field}`));
|
|
2346
2466
|
const shown = diffLines.slice(0, maxDiffLines);
|
|
2347
2467
|
for (const dl of shown) {
|
|
2348
|
-
if (dl.type === "+") console.log(
|
|
2349
|
-
else if (dl.type === "-") console.log(
|
|
2350
|
-
else console.log(
|
|
2468
|
+
if (dl.type === "+") console.log(pc2.green(` + ${dl.line}`));
|
|
2469
|
+
else if (dl.type === "-") console.log(pc2.red(` - ${dl.line}`));
|
|
2470
|
+
else console.log(pc2.dim(` ${dl.line}`));
|
|
2351
2471
|
}
|
|
2352
2472
|
if (diffLines.length > maxDiffLines) {
|
|
2353
2473
|
const remaining = diffLines.length - maxDiffLines;
|
|
2354
|
-
console.log(
|
|
2474
|
+
console.log(pc2.dim(` ... ${remaining} more lines \u2014 use ${pc2.cyan(`lumera diff ${change.resource}s/${change.name}`)} for full diff`));
|
|
2355
2475
|
}
|
|
2356
2476
|
console.log();
|
|
2357
2477
|
}
|
|
@@ -2359,7 +2479,8 @@ async function plan(args) {
|
|
|
2359
2479
|
}
|
|
2360
2480
|
}
|
|
2361
2481
|
console.log();
|
|
2362
|
-
|
|
2482
|
+
safePrintLint(lintWarnings, projectRoot);
|
|
2483
|
+
console.log(pc2.dim(` Run 'lumera apply' to apply these changes.`));
|
|
2363
2484
|
console.log();
|
|
2364
2485
|
}
|
|
2365
2486
|
async function apply(args) {
|
|
@@ -2378,12 +2499,12 @@ async function apply(args) {
|
|
|
2378
2499
|
const autoConfirm = args.includes("--yes") || args.includes("-y") || !!process.env.CI;
|
|
2379
2500
|
if (type === "app") {
|
|
2380
2501
|
console.log();
|
|
2381
|
-
console.log(
|
|
2502
|
+
console.log(pc2.cyan(pc2.bold(" Apply")));
|
|
2382
2503
|
console.log();
|
|
2383
|
-
console.log(
|
|
2504
|
+
console.log(pc2.bold(" App:"));
|
|
2384
2505
|
await applyApp(args);
|
|
2385
2506
|
console.log();
|
|
2386
|
-
console.log(
|
|
2507
|
+
console.log(pc2.green(" Done!"));
|
|
2387
2508
|
console.log();
|
|
2388
2509
|
return;
|
|
2389
2510
|
}
|
|
@@ -2411,7 +2532,7 @@ async function apply(args) {
|
|
|
2411
2532
|
const hasLocal = localCollections.length > 0 || localAutomations.length > 0 || localHooks.length > 0 || localAgents.length > 0 || localMailboxes.length > 0;
|
|
2412
2533
|
if (!hasLocal) {
|
|
2413
2534
|
console.log();
|
|
2414
|
-
console.log(
|
|
2535
|
+
console.log(pc2.red(` Resource "${name}" not found locally`));
|
|
2415
2536
|
process.exit(1);
|
|
2416
2537
|
}
|
|
2417
2538
|
}
|
|
@@ -2426,35 +2547,37 @@ async function apply(args) {
|
|
|
2426
2547
|
}
|
|
2427
2548
|
if (allChanges.length === 0 && !willDeployApp) {
|
|
2428
2549
|
console.log();
|
|
2429
|
-
console.log(
|
|
2550
|
+
console.log(pc2.green(" \u2713 Nothing to apply \u2014 local and remote are in sync."));
|
|
2430
2551
|
console.log();
|
|
2552
|
+
safePrintLint(safeLint(projectRoot, localAutomations), projectRoot);
|
|
2431
2553
|
return;
|
|
2432
2554
|
}
|
|
2433
2555
|
console.log();
|
|
2434
|
-
console.log(
|
|
2556
|
+
console.log(pc2.cyan(pc2.bold(" Apply")));
|
|
2435
2557
|
console.log();
|
|
2436
2558
|
const creates = allChanges.filter((c) => c.type === "create");
|
|
2437
2559
|
const updates = allChanges.filter((c) => c.type === "update");
|
|
2438
2560
|
if (allChanges.length > 0) {
|
|
2439
|
-
console.log(
|
|
2561
|
+
console.log(pc2.bold(" Plan:"));
|
|
2440
2562
|
for (const change of allChanges) {
|
|
2441
2563
|
const icon = change.type === "create" ? "+" : "~";
|
|
2442
|
-
const color = change.type === "create" ?
|
|
2564
|
+
const color = change.type === "create" ? pc2.green : pc2.yellow;
|
|
2443
2565
|
const details = change.details ? ` (${change.details})` : "";
|
|
2444
|
-
console.log(` ${color(icon)} ${change.resource}: ${change.name}${
|
|
2566
|
+
console.log(` ${color(icon)} ${change.resource}: ${change.name}${pc2.dim(details)}`);
|
|
2445
2567
|
}
|
|
2446
|
-
if (willDeployApp) console.log(` ${
|
|
2568
|
+
if (willDeployApp) console.log(` ${pc2.blue("\u25CF")} app: frontend build + deploy`);
|
|
2447
2569
|
console.log();
|
|
2448
2570
|
const parts = [];
|
|
2449
|
-
if (creates.length > 0) parts.push(
|
|
2450
|
-
if (updates.length > 0) parts.push(
|
|
2451
|
-
if (willDeployApp) parts.push(
|
|
2571
|
+
if (creates.length > 0) parts.push(pc2.green(`${creates.length} create`));
|
|
2572
|
+
if (updates.length > 0) parts.push(pc2.yellow(`${updates.length} update`));
|
|
2573
|
+
if (willDeployApp) parts.push(pc2.blue("1 app deploy"));
|
|
2452
2574
|
console.log(` ${parts.join(", ")}`);
|
|
2453
2575
|
console.log();
|
|
2454
2576
|
} else if (willDeployApp) {
|
|
2455
|
-
console.log(
|
|
2577
|
+
console.log(pc2.dim(" No infrastructure changes \u2014 deploying app only."));
|
|
2456
2578
|
console.log();
|
|
2457
2579
|
}
|
|
2580
|
+
safePrintLint(safeLint(projectRoot, localAutomations), projectRoot);
|
|
2458
2581
|
if (!autoConfirm && allChanges.length > 0) {
|
|
2459
2582
|
const { confirm } = await prompts({
|
|
2460
2583
|
type: "confirm",
|
|
@@ -2463,7 +2586,7 @@ async function apply(args) {
|
|
|
2463
2586
|
initial: true
|
|
2464
2587
|
});
|
|
2465
2588
|
if (!confirm) {
|
|
2466
|
-
console.log(
|
|
2589
|
+
console.log(pc2.dim(" Cancelled."));
|
|
2467
2590
|
console.log();
|
|
2468
2591
|
return;
|
|
2469
2592
|
}
|
|
@@ -2474,7 +2597,7 @@ async function apply(args) {
|
|
|
2474
2597
|
let totalUpdated = 0;
|
|
2475
2598
|
let totalSkipped = 0;
|
|
2476
2599
|
if (localCollections.length > 0) {
|
|
2477
|
-
console.log(
|
|
2600
|
+
console.log(pc2.bold(" Collections:"));
|
|
2478
2601
|
totalErrors += await applyCollections(api, localCollections);
|
|
2479
2602
|
console.log();
|
|
2480
2603
|
}
|
|
@@ -2485,42 +2608,42 @@ async function apply(args) {
|
|
|
2485
2608
|
} catch {
|
|
2486
2609
|
}
|
|
2487
2610
|
if (localAutomations.length > 0) {
|
|
2488
|
-
console.log(
|
|
2611
|
+
console.log(pc2.bold(" Automations:"));
|
|
2489
2612
|
totalErrors += await applyAutomations(api, localAutomations, projectId);
|
|
2490
2613
|
console.log();
|
|
2491
2614
|
}
|
|
2492
2615
|
if (localHooks.length > 0) {
|
|
2493
|
-
console.log(
|
|
2616
|
+
console.log(pc2.bold(" Hooks:"));
|
|
2494
2617
|
totalErrors += await applyHooks(api, localHooks, collections, projectId);
|
|
2495
2618
|
console.log();
|
|
2496
2619
|
}
|
|
2497
2620
|
if (localAgents.length > 0) {
|
|
2498
|
-
console.log(
|
|
2621
|
+
console.log(pc2.bold(" Agents:"));
|
|
2499
2622
|
totalErrors += await applyAgents(api, localAgents, projectId);
|
|
2500
2623
|
console.log();
|
|
2501
2624
|
}
|
|
2502
2625
|
if (localMailboxes.length > 0) {
|
|
2503
|
-
console.log(
|
|
2626
|
+
console.log(pc2.bold(" Mailboxes:"));
|
|
2504
2627
|
totalErrors += await applyMailboxes(api, localMailboxes);
|
|
2505
2628
|
console.log();
|
|
2506
2629
|
}
|
|
2507
2630
|
if (willDeployApp) {
|
|
2508
|
-
console.log(
|
|
2631
|
+
console.log(pc2.bold(" App:"));
|
|
2509
2632
|
await applyApp(args);
|
|
2510
2633
|
console.log();
|
|
2511
2634
|
}
|
|
2512
2635
|
totalCreated = creates.length;
|
|
2513
2636
|
totalUpdated = updates.length;
|
|
2514
2637
|
if (totalErrors > 0) {
|
|
2515
|
-
console.log(
|
|
2638
|
+
console.log(pc2.red(` \u2717 ${totalErrors} error${totalErrors > 1 ? "s" : ""} during apply.`));
|
|
2516
2639
|
console.log();
|
|
2517
2640
|
process.exit(1);
|
|
2518
2641
|
}
|
|
2519
2642
|
const summary = [];
|
|
2520
|
-
if (totalCreated > 0) summary.push(
|
|
2521
|
-
if (totalUpdated > 0) summary.push(
|
|
2522
|
-
if (willDeployApp) summary.push(
|
|
2523
|
-
console.log(
|
|
2643
|
+
if (totalCreated > 0) summary.push(pc2.green(`${totalCreated} created`));
|
|
2644
|
+
if (totalUpdated > 0) summary.push(pc2.yellow(`${totalUpdated} updated`));
|
|
2645
|
+
if (willDeployApp) summary.push(pc2.blue("app deployed"));
|
|
2646
|
+
console.log(pc2.green(" \u2713 Done!") + (summary.length > 0 ? ` ${pc2.dim("\u2014")} ${summary.join(", ")}` : ""));
|
|
2524
2647
|
console.log();
|
|
2525
2648
|
}
|
|
2526
2649
|
async function pull(args) {
|
|
@@ -2575,47 +2698,47 @@ async function pull(args) {
|
|
|
2575
2698
|
}
|
|
2576
2699
|
if (conflicts.length > 0) {
|
|
2577
2700
|
console.log();
|
|
2578
|
-
console.log(
|
|
2701
|
+
console.log(pc2.yellow(` \u26A0 ${conflicts.length} local file${conflicts.length > 1 ? "s have" : " has"} changes that would be lost:`));
|
|
2579
2702
|
for (const f of conflicts) {
|
|
2580
|
-
console.log(
|
|
2703
|
+
console.log(pc2.dim(` ${f}`));
|
|
2581
2704
|
}
|
|
2582
2705
|
console.log();
|
|
2583
|
-
console.log(
|
|
2584
|
-
console.log(
|
|
2706
|
+
console.log(pc2.dim(` Use ${pc2.cyan("lumera diff <resource>")} to inspect changes.`));
|
|
2707
|
+
console.log(pc2.dim(` Use ${pc2.cyan("lumera pull --force")} to overwrite local files.`));
|
|
2585
2708
|
console.log();
|
|
2586
2709
|
process.exit(1);
|
|
2587
2710
|
}
|
|
2588
2711
|
}
|
|
2589
2712
|
console.log();
|
|
2590
|
-
console.log(
|
|
2591
|
-
console.log(
|
|
2713
|
+
console.log(pc2.cyan(pc2.bold(" Pull")));
|
|
2714
|
+
console.log(pc2.dim(` Downloading remote state to ${platformDir}/...`));
|
|
2592
2715
|
console.log();
|
|
2593
2716
|
if (!type || type === "collections") {
|
|
2594
|
-
console.log(
|
|
2717
|
+
console.log(pc2.bold(" Collections:"));
|
|
2595
2718
|
await pullCollections(api, platformDir, name || void 0, appName);
|
|
2596
2719
|
console.log();
|
|
2597
2720
|
}
|
|
2598
2721
|
if (!type || type === "automations") {
|
|
2599
|
-
console.log(
|
|
2722
|
+
console.log(pc2.bold(" Automations:"));
|
|
2600
2723
|
await pullAutomations(api, platformDir, name || void 0, projectId);
|
|
2601
2724
|
console.log();
|
|
2602
2725
|
}
|
|
2603
2726
|
if (!type || type === "hooks") {
|
|
2604
|
-
console.log(
|
|
2727
|
+
console.log(pc2.bold(" Hooks:"));
|
|
2605
2728
|
await pullHooks(api, platformDir, name || void 0, appName, projectId);
|
|
2606
2729
|
console.log();
|
|
2607
2730
|
}
|
|
2608
2731
|
if (!type || type === "agents") {
|
|
2609
|
-
console.log(
|
|
2732
|
+
console.log(pc2.bold(" Agents:"));
|
|
2610
2733
|
await pullAgents(api, platformDir, name || void 0, projectId);
|
|
2611
2734
|
console.log();
|
|
2612
2735
|
}
|
|
2613
2736
|
if (!type || type === "mailboxes") {
|
|
2614
|
-
console.log(
|
|
2737
|
+
console.log(pc2.bold(" Mailboxes:"));
|
|
2615
2738
|
await pullMailboxes(api, platformDir, name || void 0);
|
|
2616
2739
|
console.log();
|
|
2617
2740
|
}
|
|
2618
|
-
console.log(
|
|
2741
|
+
console.log(pc2.green(" Done!"));
|
|
2619
2742
|
console.log();
|
|
2620
2743
|
}
|
|
2621
2744
|
async function destroy(args) {
|
|
@@ -2633,7 +2756,7 @@ async function destroy(args) {
|
|
|
2633
2756
|
const skipConfirm = args.includes("--confirm");
|
|
2634
2757
|
const forceCycles = args.includes("--force-cycles");
|
|
2635
2758
|
console.log();
|
|
2636
|
-
console.log(
|
|
2759
|
+
console.log(pc2.red(pc2.bold(" Destroy")));
|
|
2637
2760
|
console.log();
|
|
2638
2761
|
if (type === "app") {
|
|
2639
2762
|
await destroyApp(skipConfirm);
|
|
@@ -2657,19 +2780,19 @@ async function list(args) {
|
|
|
2657
2780
|
const positionalArgs = args.filter((a) => !a.startsWith("--"));
|
|
2658
2781
|
const filterType = positionalArgs[0];
|
|
2659
2782
|
console.log();
|
|
2660
|
-
console.log(
|
|
2783
|
+
console.log(pc2.cyan(pc2.bold(" Resources")));
|
|
2661
2784
|
console.log();
|
|
2662
2785
|
const allResources = await listResources(api, platformDir, filterType, appName, projectId);
|
|
2663
2786
|
const remoteOnlyCount = allResources.filter((r) => r.status === "remote-only").length;
|
|
2664
2787
|
const resources = showAll ? allResources : allResources.filter((r) => r.status !== "remote-only");
|
|
2665
2788
|
if (resources.length === 0 && remoteOnlyCount === 0) {
|
|
2666
|
-
console.log(
|
|
2789
|
+
console.log(pc2.dim(" No resources found"));
|
|
2667
2790
|
console.log();
|
|
2668
2791
|
return;
|
|
2669
2792
|
}
|
|
2670
2793
|
if (resources.length === 0 && remoteOnlyCount > 0) {
|
|
2671
|
-
console.log(
|
|
2672
|
-
console.log(
|
|
2794
|
+
console.log(pc2.dim(" No local resources found"));
|
|
2795
|
+
console.log(pc2.dim(` ${remoteOnlyCount} remote-only resource(s) hidden. Use --all to show.`));
|
|
2673
2796
|
console.log();
|
|
2674
2797
|
return;
|
|
2675
2798
|
}
|
|
@@ -2679,29 +2802,29 @@ async function list(args) {
|
|
|
2679
2802
|
byType.get(r.type).push(r);
|
|
2680
2803
|
}
|
|
2681
2804
|
for (const [type, items] of byType) {
|
|
2682
|
-
console.log(
|
|
2805
|
+
console.log(pc2.bold(` ${type.charAt(0).toUpperCase() + type.slice(1)}:`));
|
|
2683
2806
|
for (const item of items) {
|
|
2684
2807
|
let icon;
|
|
2685
2808
|
let color;
|
|
2686
2809
|
switch (item.status) {
|
|
2687
2810
|
case "synced":
|
|
2688
2811
|
icon = "\u2713";
|
|
2689
|
-
color =
|
|
2812
|
+
color = pc2.green;
|
|
2690
2813
|
break;
|
|
2691
2814
|
case "changed":
|
|
2692
2815
|
icon = "~";
|
|
2693
|
-
color =
|
|
2816
|
+
color = pc2.yellow;
|
|
2694
2817
|
break;
|
|
2695
2818
|
case "local-only":
|
|
2696
2819
|
icon = "+";
|
|
2697
|
-
color =
|
|
2820
|
+
color = pc2.cyan;
|
|
2698
2821
|
break;
|
|
2699
2822
|
case "remote-only":
|
|
2700
2823
|
icon = "?";
|
|
2701
|
-
color =
|
|
2824
|
+
color = pc2.dim;
|
|
2702
2825
|
break;
|
|
2703
2826
|
}
|
|
2704
|
-
const details = item.details ?
|
|
2827
|
+
const details = item.details ? pc2.dim(` (${item.details})`) : "";
|
|
2705
2828
|
console.log(` ${color(icon)} ${item.name}${details}`);
|
|
2706
2829
|
}
|
|
2707
2830
|
console.log();
|
|
@@ -2711,14 +2834,14 @@ async function list(args) {
|
|
|
2711
2834
|
const localOnly = resources.filter((r) => r.status === "local-only").length;
|
|
2712
2835
|
const displayedRemoteOnly = resources.filter((r) => r.status === "remote-only").length;
|
|
2713
2836
|
const summary = [];
|
|
2714
|
-
if (synced > 0) summary.push(
|
|
2715
|
-
if (changed > 0) summary.push(
|
|
2716
|
-
if (localOnly > 0) summary.push(
|
|
2717
|
-
if (displayedRemoteOnly > 0) summary.push(
|
|
2837
|
+
if (synced > 0) summary.push(pc2.green(`${synced} synced`));
|
|
2838
|
+
if (changed > 0) summary.push(pc2.yellow(`${changed} changed`));
|
|
2839
|
+
if (localOnly > 0) summary.push(pc2.cyan(`${localOnly} local-only`));
|
|
2840
|
+
if (displayedRemoteOnly > 0) summary.push(pc2.dim(`${displayedRemoteOnly} remote-only`));
|
|
2718
2841
|
console.log(` ${summary.join(" | ")}`);
|
|
2719
|
-
console.log(
|
|
2842
|
+
console.log(pc2.dim(` ${pc2.green("\u2713")} synced ${pc2.yellow("~")} changed ${pc2.cyan("+")} local-only ? remote-only`));
|
|
2720
2843
|
if (!showAll && remoteOnlyCount > 0) {
|
|
2721
|
-
console.log(
|
|
2844
|
+
console.log(pc2.dim(` ${remoteOnlyCount} remote-only resource(s) hidden. Use --all to show.`));
|
|
2722
2845
|
}
|
|
2723
2846
|
console.log();
|
|
2724
2847
|
}
|
|
@@ -2736,15 +2859,15 @@ async function show(args) {
|
|
|
2736
2859
|
const projectId = getProjectId(projectRoot);
|
|
2737
2860
|
const { type, name } = parseResource(args[0]);
|
|
2738
2861
|
if (!type) {
|
|
2739
|
-
console.log(
|
|
2740
|
-
console.log(
|
|
2862
|
+
console.log(pc2.red(` Invalid resource path: ${args[0]}`));
|
|
2863
|
+
console.log(pc2.dim(" Use format: <type>/<name> (e.g., collections/users)"));
|
|
2741
2864
|
process.exit(1);
|
|
2742
2865
|
}
|
|
2743
2866
|
if (type === "app") {
|
|
2744
2867
|
await showResource(api, platformDir, "app", "", appName, projectId);
|
|
2745
2868
|
} else if (!name) {
|
|
2746
|
-
console.log(
|
|
2747
|
-
console.log(
|
|
2869
|
+
console.log(pc2.red(` Resource name required`));
|
|
2870
|
+
console.log(pc2.dim(" Use format: <type>/<name> (e.g., collections/users)"));
|
|
2748
2871
|
process.exit(1);
|
|
2749
2872
|
} else {
|
|
2750
2873
|
await showResource(api, platformDir, type, name, appName, projectId);
|
|
@@ -2752,17 +2875,17 @@ async function show(args) {
|
|
|
2752
2875
|
}
|
|
2753
2876
|
function showDiffHelp() {
|
|
2754
2877
|
console.log(`
|
|
2755
|
-
${
|
|
2878
|
+
${pc2.bold("lumera diff")} - Show full diff between local and remote state
|
|
2756
2879
|
|
|
2757
|
-
${
|
|
2880
|
+
${pc2.dim("Usage:")}
|
|
2758
2881
|
lumera diff <resource>
|
|
2759
2882
|
|
|
2760
|
-
${
|
|
2883
|
+
${pc2.dim("Resources:")}
|
|
2761
2884
|
agents/<name> Diff agent (system_prompt, policy_script)
|
|
2762
2885
|
automations/<name> Diff automation code
|
|
2763
2886
|
hooks/<name> Diff hook script
|
|
2764
2887
|
|
|
2765
|
-
${
|
|
2888
|
+
${pc2.dim("Examples:")}
|
|
2766
2889
|
lumera diff agents/bank_activity_matcher
|
|
2767
2890
|
lumera diff automations/sync
|
|
2768
2891
|
lumera diff hooks/encoding_protect_create
|
|
@@ -2771,14 +2894,14 @@ ${pc.dim("Examples:")}
|
|
|
2771
2894
|
function renderFullDiff(field, oldText, newText) {
|
|
2772
2895
|
const diffLines = computeLineDiff(oldText, newText);
|
|
2773
2896
|
if (diffLines.length === 0) {
|
|
2774
|
-
console.log(
|
|
2897
|
+
console.log(pc2.dim(` ${field}: no changes`));
|
|
2775
2898
|
return;
|
|
2776
2899
|
}
|
|
2777
|
-
console.log(
|
|
2900
|
+
console.log(pc2.bold(` --- ${field}`));
|
|
2778
2901
|
for (const dl of diffLines) {
|
|
2779
|
-
if (dl.type === "+") console.log(
|
|
2780
|
-
else if (dl.type === "-") console.log(
|
|
2781
|
-
else console.log(
|
|
2902
|
+
if (dl.type === "+") console.log(pc2.green(` + ${dl.line}`));
|
|
2903
|
+
else if (dl.type === "-") console.log(pc2.red(` - ${dl.line}`));
|
|
2904
|
+
else console.log(pc2.dim(` ${dl.line}`));
|
|
2782
2905
|
}
|
|
2783
2906
|
console.log();
|
|
2784
2907
|
}
|
|
@@ -2796,8 +2919,8 @@ async function diff(args) {
|
|
|
2796
2919
|
const projectId = getProjectId(projectRoot);
|
|
2797
2920
|
const { type, name } = parseResource(args[0]);
|
|
2798
2921
|
if (!type || !name) {
|
|
2799
|
-
console.log(
|
|
2800
|
-
console.log(
|
|
2922
|
+
console.log(pc2.red(` Invalid resource path: ${args[0]}`));
|
|
2923
|
+
console.log(pc2.dim(" Use format: <type>/<name> (e.g., agents/bank_activity_matcher)"));
|
|
2801
2924
|
process.exit(1);
|
|
2802
2925
|
}
|
|
2803
2926
|
console.log();
|
|
@@ -2810,31 +2933,31 @@ async function diff(args) {
|
|
|
2810
2933
|
(a) => a.external_id === name || a.name === name || localExtId && a.external_id === localExtId
|
|
2811
2934
|
);
|
|
2812
2935
|
if (!local && !remote) {
|
|
2813
|
-
console.log(
|
|
2936
|
+
console.log(pc2.red(` Agent "${name}" not found locally or remotely`));
|
|
2814
2937
|
process.exit(1);
|
|
2815
2938
|
}
|
|
2816
2939
|
if (!local) {
|
|
2817
|
-
console.log(
|
|
2940
|
+
console.log(pc2.cyan(` Agent "${name}" exists only remotely (not in local files)`));
|
|
2818
2941
|
process.exit(0);
|
|
2819
2942
|
}
|
|
2820
2943
|
if (!remote) {
|
|
2821
|
-
console.log(
|
|
2944
|
+
console.log(pc2.yellow(` Agent "${name}" exists only locally (not yet deployed)`));
|
|
2822
2945
|
process.exit(0);
|
|
2823
2946
|
}
|
|
2824
|
-
console.log(
|
|
2947
|
+
console.log(pc2.bold(` Agent: ${local.agent.name}`));
|
|
2825
2948
|
console.log();
|
|
2826
2949
|
if (remote.name !== local.agent.name)
|
|
2827
|
-
console.log(` name: ${
|
|
2950
|
+
console.log(` name: ${pc2.red(remote.name)} \u2192 ${pc2.green(local.agent.name)}`);
|
|
2828
2951
|
if ((remote.description || "") !== (local.agent.description || ""))
|
|
2829
|
-
console.log(` description: ${
|
|
2952
|
+
console.log(` description: ${pc2.red(remote.description || "(empty)")} \u2192 ${pc2.green(local.agent.description || "(empty)")}`);
|
|
2830
2953
|
if ((remote.model || "") !== (local.agent.model || ""))
|
|
2831
|
-
console.log(` model: ${
|
|
2954
|
+
console.log(` model: ${pc2.red(remote.model || "(default)")} \u2192 ${pc2.green(local.agent.model || "(default)")}`);
|
|
2832
2955
|
if ((remote.policy_enabled || false) !== (local.agent.policy_enabled || false))
|
|
2833
|
-
console.log(` policy_enabled: ${
|
|
2956
|
+
console.log(` policy_enabled: ${pc2.red(String(remote.policy_enabled || false))} \u2192 ${pc2.green(String(local.agent.policy_enabled || false))}`);
|
|
2834
2957
|
const promptChanged = (remote.system_prompt || "").trim() !== local.systemPrompt.trim();
|
|
2835
2958
|
const policyChanged = (remote.policy_script || "").trim() !== (local.policyScript || "").trim();
|
|
2836
2959
|
if (!promptChanged && !policyChanged && remote.name === local.agent.name && (remote.description || "") === (local.agent.description || "") && (remote.model || "") === (local.agent.model || "") && (remote.policy_enabled || false) === (local.agent.policy_enabled || false)) {
|
|
2837
|
-
console.log(
|
|
2960
|
+
console.log(pc2.green(` \u2713 No changes`));
|
|
2838
2961
|
} else {
|
|
2839
2962
|
if (promptChanged) renderFullDiff("system_prompt.md", remote.system_prompt || "", local.systemPrompt);
|
|
2840
2963
|
if (policyChanged) renderFullDiff("policy.js", remote.policy_script || "", local.policyScript);
|
|
@@ -2848,24 +2971,24 @@ async function diff(args) {
|
|
|
2848
2971
|
(a) => a.external_id === name || a.name === name || localExtId && a.external_id === localExtId
|
|
2849
2972
|
);
|
|
2850
2973
|
if (!local && !remote) {
|
|
2851
|
-
console.log(
|
|
2974
|
+
console.log(pc2.red(` Automation "${name}" not found locally or remotely`));
|
|
2852
2975
|
process.exit(1);
|
|
2853
2976
|
}
|
|
2854
2977
|
if (!local) {
|
|
2855
|
-
console.log(
|
|
2978
|
+
console.log(pc2.cyan(` Automation "${name}" exists only remotely`));
|
|
2856
2979
|
process.exit(0);
|
|
2857
2980
|
}
|
|
2858
2981
|
if (!remote) {
|
|
2859
|
-
console.log(
|
|
2982
|
+
console.log(pc2.yellow(` Automation "${name}" exists only locally (not yet deployed)`));
|
|
2860
2983
|
process.exit(0);
|
|
2861
2984
|
}
|
|
2862
|
-
console.log(
|
|
2985
|
+
console.log(pc2.bold(` Automation: ${local.automation.name}`));
|
|
2863
2986
|
console.log();
|
|
2864
2987
|
if (remote.name !== local.automation.name)
|
|
2865
|
-
console.log(` name: ${
|
|
2988
|
+
console.log(` name: ${pc2.red(remote.name)} \u2192 ${pc2.green(local.automation.name)}`);
|
|
2866
2989
|
const codeChanged = (remote.code || "") !== local.code;
|
|
2867
2990
|
if (!codeChanged && remote.name === local.automation.name) {
|
|
2868
|
-
console.log(
|
|
2991
|
+
console.log(pc2.green(` \u2713 No changes`));
|
|
2869
2992
|
} else if (codeChanged) {
|
|
2870
2993
|
renderFullDiff("main.py", remote.code || "", local.code);
|
|
2871
2994
|
}
|
|
@@ -2878,29 +3001,29 @@ async function diff(args) {
|
|
|
2878
3001
|
(h) => h.external_id === name || h.name === name || localExtId && h.external_id === localExtId
|
|
2879
3002
|
);
|
|
2880
3003
|
if (!local && !remote) {
|
|
2881
|
-
console.log(
|
|
3004
|
+
console.log(pc2.red(` Hook "${name}" not found locally or remotely`));
|
|
2882
3005
|
process.exit(1);
|
|
2883
3006
|
}
|
|
2884
3007
|
if (!local) {
|
|
2885
|
-
console.log(
|
|
3008
|
+
console.log(pc2.cyan(` Hook "${name}" exists only remotely`));
|
|
2886
3009
|
process.exit(0);
|
|
2887
3010
|
}
|
|
2888
3011
|
if (!remote) {
|
|
2889
|
-
console.log(
|
|
3012
|
+
console.log(pc2.yellow(` Hook "${name}" exists only locally (not yet deployed)`));
|
|
2890
3013
|
process.exit(0);
|
|
2891
3014
|
}
|
|
2892
|
-
console.log(
|
|
3015
|
+
console.log(pc2.bold(` Hook: ${local.hook.external_id}`));
|
|
2893
3016
|
console.log();
|
|
2894
3017
|
if (remote.event !== local.hook.trigger)
|
|
2895
|
-
console.log(` trigger: ${
|
|
3018
|
+
console.log(` trigger: ${pc2.red(remote.event)} \u2192 ${pc2.green(local.hook.trigger)}`);
|
|
2896
3019
|
const scriptChanged = (remote.script || "").trim() !== local.script.trim();
|
|
2897
3020
|
if (!scriptChanged && remote.event === local.hook.trigger) {
|
|
2898
|
-
console.log(
|
|
3021
|
+
console.log(pc2.green(` \u2713 No changes`));
|
|
2899
3022
|
} else if (scriptChanged) {
|
|
2900
3023
|
renderFullDiff(local.fileName, remote.script || "", local.script);
|
|
2901
3024
|
}
|
|
2902
3025
|
} else {
|
|
2903
|
-
console.log(
|
|
3026
|
+
console.log(pc2.red(` Diff not supported for "${type}" \u2014 use agents, automations, or hooks`));
|
|
2904
3027
|
process.exit(1);
|
|
2905
3028
|
}
|
|
2906
3029
|
console.log();
|