@lumerahq/cli 0.13.2 → 0.15.0
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.
- package/README.md +6 -3
- package/dist/{chunk-7ZGIC6F7.js → chunk-NL6MEHA3.js} +15 -6
- package/dist/{dev-LBWA7G6T.js → dev-5EAZUQ2S.js} +1 -1
- package/dist/index.js +13 -8
- package/dist/{resources-TCYJ5AEO.js → resources-BFT7V6UR.js} +494 -71
- package/dist/{run-3UBV3SVA.js → run-SPC4YXWR.js} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,12 +23,15 @@ lumera dev # Start dev server
|
|
|
23
23
|
lumera apply app # Deploy frontend
|
|
24
24
|
lumera destroy app # Delete app from Lumera
|
|
25
25
|
|
|
26
|
-
lumera plan # Preview
|
|
27
|
-
lumera
|
|
26
|
+
lumera plan # Preview changes (with inline diffs)
|
|
27
|
+
lumera diff <resource> # Full diff between local and remote
|
|
28
|
+
lumera apply # Apply collections, automations, hooks, agents
|
|
28
29
|
lumera pull # Pull remote state to local
|
|
30
|
+
lumera list # List resources with sync status
|
|
31
|
+
lumera show <resource> # Show resource details
|
|
29
32
|
lumera destroy # Delete remote resources
|
|
30
33
|
|
|
31
|
-
lumera run <
|
|
34
|
+
lumera run <target> # Run script, automation, or invoke agent
|
|
32
35
|
```
|
|
33
36
|
|
|
34
37
|
## Scaffolding Projects
|
|
@@ -5,10 +5,19 @@ import {
|
|
|
5
5
|
// src/lib/api.ts
|
|
6
6
|
import { readFileSync } from "fs";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
|
-
import { dirname,
|
|
8
|
+
import { dirname, resolve } from "path";
|
|
9
9
|
var __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
var
|
|
11
|
-
|
|
10
|
+
var __pkgDir = dirname(__filename);
|
|
11
|
+
while (__pkgDir !== "/") {
|
|
12
|
+
try {
|
|
13
|
+
const candidate = resolve(__pkgDir, "package.json");
|
|
14
|
+
const parsed = JSON.parse(readFileSync(candidate, "utf-8"));
|
|
15
|
+
if (parsed.name === "@lumerahq/cli") break;
|
|
16
|
+
} catch {
|
|
17
|
+
}
|
|
18
|
+
__pkgDir = resolve(__pkgDir, "..");
|
|
19
|
+
}
|
|
20
|
+
var pkg = JSON.parse(readFileSync(resolve(__pkgDir, "package.json"), "utf-8"));
|
|
12
21
|
var CLI_USER_AGENT = `lumera-cli/${pkg.version}`;
|
|
13
22
|
var ApiClient = class {
|
|
14
23
|
baseUrl;
|
|
@@ -190,10 +199,10 @@ function createApiClient(token, baseUrl) {
|
|
|
190
199
|
// src/lib/env.ts
|
|
191
200
|
import { config } from "dotenv";
|
|
192
201
|
import { existsSync } from "fs";
|
|
193
|
-
import { resolve } from "path";
|
|
202
|
+
import { resolve as resolve2 } from "path";
|
|
194
203
|
function loadEnv(cwd = process.cwd()) {
|
|
195
|
-
const envPath =
|
|
196
|
-
const envLocalPath =
|
|
204
|
+
const envPath = resolve2(cwd, ".env");
|
|
205
|
+
const envLocalPath = resolve2(cwd, ".env.local");
|
|
197
206
|
if (existsSync(envPath)) {
|
|
198
207
|
config({ path: envPath });
|
|
199
208
|
}
|
package/dist/index.js
CHANGED
|
@@ -81,6 +81,7 @@ var COMMANDS = [
|
|
|
81
81
|
"destroy",
|
|
82
82
|
"list",
|
|
83
83
|
"show",
|
|
84
|
+
"diff",
|
|
84
85
|
"dev",
|
|
85
86
|
"run",
|
|
86
87
|
"init",
|
|
@@ -136,6 +137,7 @@ ${pc.dim("Resource Commands:")}
|
|
|
136
137
|
${pc.cyan("destroy")} [resource] Delete resources
|
|
137
138
|
${pc.cyan("list")} [type] List resources with status
|
|
138
139
|
${pc.cyan("show")} <resource> Show resource details
|
|
140
|
+
${pc.cyan("diff")} <resource> Show full diff between local and remote
|
|
139
141
|
|
|
140
142
|
${pc.dim("Development:")}
|
|
141
143
|
${pc.cyan("dev")} Start dev server
|
|
@@ -210,29 +212,32 @@ async function main() {
|
|
|
210
212
|
switch (command) {
|
|
211
213
|
// Resource commands
|
|
212
214
|
case "plan":
|
|
213
|
-
await import("./resources-
|
|
215
|
+
await import("./resources-BFT7V6UR.js").then((m) => m.plan(args.slice(1)));
|
|
214
216
|
break;
|
|
215
217
|
case "apply":
|
|
216
|
-
await import("./resources-
|
|
218
|
+
await import("./resources-BFT7V6UR.js").then((m) => m.apply(args.slice(1)));
|
|
217
219
|
break;
|
|
218
220
|
case "pull":
|
|
219
|
-
await import("./resources-
|
|
221
|
+
await import("./resources-BFT7V6UR.js").then((m) => m.pull(args.slice(1)));
|
|
220
222
|
break;
|
|
221
223
|
case "destroy":
|
|
222
|
-
await import("./resources-
|
|
224
|
+
await import("./resources-BFT7V6UR.js").then((m) => m.destroy(args.slice(1)));
|
|
223
225
|
break;
|
|
224
226
|
case "list":
|
|
225
|
-
await import("./resources-
|
|
227
|
+
await import("./resources-BFT7V6UR.js").then((m) => m.list(args.slice(1)));
|
|
226
228
|
break;
|
|
227
229
|
case "show":
|
|
228
|
-
await import("./resources-
|
|
230
|
+
await import("./resources-BFT7V6UR.js").then((m) => m.show(args.slice(1)));
|
|
231
|
+
break;
|
|
232
|
+
case "diff":
|
|
233
|
+
await import("./resources-BFT7V6UR.js").then((m) => m.diff(args.slice(1)));
|
|
229
234
|
break;
|
|
230
235
|
// Development
|
|
231
236
|
case "dev":
|
|
232
|
-
await import("./dev-
|
|
237
|
+
await import("./dev-5EAZUQ2S.js").then((m) => m.dev(args.slice(1)));
|
|
233
238
|
break;
|
|
234
239
|
case "run":
|
|
235
|
-
await import("./run-
|
|
240
|
+
await import("./run-SPC4YXWR.js").then((m) => m.run(args.slice(1)));
|
|
236
241
|
break;
|
|
237
242
|
// Project
|
|
238
243
|
case "init":
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
createApiClient,
|
|
6
6
|
loadEnv
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-NL6MEHA3.js";
|
|
8
8
|
import {
|
|
9
9
|
getToken
|
|
10
10
|
} from "./chunk-NDLYGKS6.js";
|
|
@@ -31,6 +31,55 @@ function detectPackageManager() {
|
|
|
31
31
|
}
|
|
32
32
|
return "npm";
|
|
33
33
|
}
|
|
34
|
+
function computeLineDiff(oldText, newText) {
|
|
35
|
+
const oldLines = (oldText || "").trimEnd().split("\n");
|
|
36
|
+
const newLines = (newText || "").trimEnd().split("\n");
|
|
37
|
+
const m = oldLines.length;
|
|
38
|
+
const n = newLines.length;
|
|
39
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
40
|
+
for (let i2 = 1; i2 <= m; i2++) {
|
|
41
|
+
for (let j2 = 1; j2 <= n; j2++) {
|
|
42
|
+
dp[i2][j2] = oldLines[i2 - 1] === newLines[j2 - 1] ? dp[i2 - 1][j2 - 1] + 1 : Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const result = [];
|
|
46
|
+
let i = m, j = n;
|
|
47
|
+
const stack = [];
|
|
48
|
+
while (i > 0 || j > 0) {
|
|
49
|
+
if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
|
|
50
|
+
stack.push({ type: " ", line: oldLines[i - 1] });
|
|
51
|
+
i--;
|
|
52
|
+
j--;
|
|
53
|
+
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
|
|
54
|
+
stack.push({ type: "+", line: newLines[j - 1] });
|
|
55
|
+
j--;
|
|
56
|
+
} else {
|
|
57
|
+
stack.push({ type: "-", line: oldLines[i - 1] });
|
|
58
|
+
i--;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
stack.reverse();
|
|
62
|
+
const contextLines = 2;
|
|
63
|
+
const changeIndices = /* @__PURE__ */ new Set();
|
|
64
|
+
stack.forEach((entry, idx) => {
|
|
65
|
+
if (entry.type !== " ") {
|
|
66
|
+
for (let c = Math.max(0, idx - contextLines); c <= Math.min(stack.length - 1, idx + contextLines); c++) {
|
|
67
|
+
changeIndices.add(c);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
let lastIncluded = -2;
|
|
72
|
+
for (let idx = 0; idx < stack.length; idx++) {
|
|
73
|
+
if (changeIndices.has(idx)) {
|
|
74
|
+
if (lastIncluded < idx - 1) {
|
|
75
|
+
result.push({ type: " ", line: "..." });
|
|
76
|
+
}
|
|
77
|
+
result.push(stack[idx]);
|
|
78
|
+
lastIncluded = idx;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
34
83
|
function showPlanHelp() {
|
|
35
84
|
console.log(`
|
|
36
85
|
${pc.dim("Usage:")}
|
|
@@ -76,12 +125,14 @@ ${pc.dim("Resources:")}
|
|
|
76
125
|
app Deploy the frontend app
|
|
77
126
|
|
|
78
127
|
${pc.dim("Options:")}
|
|
128
|
+
--yes, -y Skip confirmation prompt (for CI/CD)
|
|
79
129
|
--skip-build Skip build step when applying app
|
|
80
130
|
|
|
81
131
|
${pc.dim("Examples:")}
|
|
82
|
-
lumera apply # Apply everything
|
|
132
|
+
lumera apply # Apply everything (shows plan, asks to confirm)
|
|
83
133
|
lumera apply collections # Apply all collections
|
|
84
134
|
lumera apply collections/users # Apply single collection
|
|
135
|
+
lumera apply agents -y # Apply agents without confirmation
|
|
85
136
|
lumera apply app # Deploy frontend
|
|
86
137
|
lumera apply app --skip-build # Deploy without rebuilding
|
|
87
138
|
`);
|
|
@@ -89,10 +140,15 @@ ${pc.dim("Examples:")}
|
|
|
89
140
|
function showPullHelp() {
|
|
90
141
|
console.log(`
|
|
91
142
|
${pc.dim("Usage:")}
|
|
92
|
-
lumera pull [resource]
|
|
143
|
+
lumera pull [resource] [--force]
|
|
93
144
|
|
|
94
145
|
${pc.dim("Description:")}
|
|
95
146
|
Download remote state to local files.
|
|
147
|
+
Refuses to overwrite local files that have uncommitted changes
|
|
148
|
+
(use --force to override, or 'lumera diff' to inspect first).
|
|
149
|
+
|
|
150
|
+
${pc.dim("Options:")}
|
|
151
|
+
--force, -f Overwrite local files even if they have changes
|
|
96
152
|
|
|
97
153
|
${pc.dim("Resources:")}
|
|
98
154
|
(none) Pull all resources
|
|
@@ -105,9 +161,10 @@ ${pc.dim("Resources:")}
|
|
|
105
161
|
agents/<name> Pull single agent
|
|
106
162
|
|
|
107
163
|
${pc.dim("Examples:")}
|
|
108
|
-
lumera pull # Pull all
|
|
109
|
-
lumera pull
|
|
110
|
-
lumera pull
|
|
164
|
+
lumera pull # Pull all (safe \u2014 warns on conflicts)
|
|
165
|
+
lumera pull agents # Pull only agents
|
|
166
|
+
lumera pull --force # Pull all, overwrite local changes
|
|
167
|
+
lumera diff agents/my_agent # Inspect before pulling
|
|
111
168
|
`);
|
|
112
169
|
}
|
|
113
170
|
function showDestroyHelp() {
|
|
@@ -427,6 +484,30 @@ function mapFieldType(type) {
|
|
|
427
484
|
};
|
|
428
485
|
return typeMap[type] || type;
|
|
429
486
|
}
|
|
487
|
+
function fieldsDiffer(local, remote) {
|
|
488
|
+
if (mapFieldType(local.type) !== remote.type) return true;
|
|
489
|
+
if ((local.required || false) !== (remote.required || false)) return true;
|
|
490
|
+
const opts = remote.options || {};
|
|
491
|
+
if (local.type === "select") {
|
|
492
|
+
const localValues = [...local.values || []].sort();
|
|
493
|
+
const remoteValues = [...opts.values || []].sort();
|
|
494
|
+
if (localValues.join(",") !== remoteValues.join(",")) return true;
|
|
495
|
+
const localMultiple = local.multiple || false;
|
|
496
|
+
const remoteMaxSelect = opts.maxSelect || 1;
|
|
497
|
+
if (localMultiple && remoteMaxSelect <= 1) return true;
|
|
498
|
+
if (!localMultiple && remoteMaxSelect > 1) return true;
|
|
499
|
+
}
|
|
500
|
+
if (local.type === "relation") {
|
|
501
|
+
if (local.collection && local.collection !== opts.collectionId) return true;
|
|
502
|
+
const localMultiple = local.multiple || false;
|
|
503
|
+
const remoteMaxSelect = opts.maxSelect || 1;
|
|
504
|
+
if (localMultiple && remoteMaxSelect <= 1) return true;
|
|
505
|
+
if (!localMultiple && remoteMaxSelect > 1) return true;
|
|
506
|
+
}
|
|
507
|
+
if (local.min !== void 0 && local.min !== opts.min) return true;
|
|
508
|
+
if (local.max !== void 0 && local.max !== opts.max) return true;
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
430
511
|
async function planCollections(api, localCollections) {
|
|
431
512
|
const changes = [];
|
|
432
513
|
const remoteCollections = await api.listCollections();
|
|
@@ -454,10 +535,20 @@ async function planCollections(api, localCollections) {
|
|
|
454
535
|
const remoteFieldMap = new Map(remote.schema.map((f) => [f.name, f]));
|
|
455
536
|
const added = [...localFieldNames].filter((n) => !remoteFieldNames.has(n));
|
|
456
537
|
const removed = [...remoteFieldNames].filter((n) => !localFieldNames.has(n));
|
|
457
|
-
|
|
538
|
+
const modified = [];
|
|
539
|
+
for (const name of localFieldNames) {
|
|
540
|
+
if (!remoteFieldNames.has(name)) continue;
|
|
541
|
+
const localField = localFieldMap.get(name);
|
|
542
|
+
const remoteField = remoteFieldMap.get(name);
|
|
543
|
+
if (fieldsDiffer(localField, remoteField)) {
|
|
544
|
+
modified.push(name);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (added.length > 0 || removed.length > 0 || modified.length > 0) {
|
|
458
548
|
const details = [];
|
|
459
549
|
if (added.length > 0) details.push(`+${added.length} field${added.length > 1 ? "s" : ""}`);
|
|
460
550
|
if (removed.length > 0) details.push(`-${removed.length} field${removed.length > 1 ? "s" : ""}`);
|
|
551
|
+
if (modified.length > 0) details.push(`~${modified.length} field${modified.length > 1 ? "s" : ""} (${modified.join(", ")})`);
|
|
461
552
|
const fieldDetails = [];
|
|
462
553
|
for (const name of added) {
|
|
463
554
|
const f = localFieldMap.get(name);
|
|
@@ -502,12 +593,17 @@ async function planAutomations(api, localAutomations) {
|
|
|
502
593
|
if (codeChanged) details.push("code");
|
|
503
594
|
if (nameChanged) details.push("name");
|
|
504
595
|
if (descChanged) details.push("description");
|
|
596
|
+
const textDiffs = [];
|
|
597
|
+
if (codeChanged) {
|
|
598
|
+
textDiffs.push({ field: "main.py", oldText: remote.code || "", newText: code });
|
|
599
|
+
}
|
|
505
600
|
changes.push({
|
|
506
601
|
type: "update",
|
|
507
602
|
resource: "automation",
|
|
508
603
|
id: automation.external_id,
|
|
509
604
|
name: automation.name,
|
|
510
|
-
details: `changed: ${details.join(", ")}
|
|
605
|
+
details: `changed: ${details.join(", ")}`,
|
|
606
|
+
textDiffs
|
|
511
607
|
});
|
|
512
608
|
}
|
|
513
609
|
}
|
|
@@ -539,12 +635,17 @@ async function planHooks(api, localHooks, collections) {
|
|
|
539
635
|
const details = [];
|
|
540
636
|
if (scriptChanged) details.push("script");
|
|
541
637
|
if (eventChanged) details.push("trigger");
|
|
638
|
+
const textDiffs = [];
|
|
639
|
+
if (scriptChanged) {
|
|
640
|
+
textDiffs.push({ field: fileName, oldText: remote.script || "", newText: script });
|
|
641
|
+
}
|
|
542
642
|
changes.push({
|
|
543
643
|
type: "update",
|
|
544
644
|
resource: "hook",
|
|
545
645
|
id: hook.external_id,
|
|
546
646
|
name: `${hook.collection}.${hook.trigger}`,
|
|
547
|
-
details: `changed: ${details.join(", ")}
|
|
647
|
+
details: `changed: ${details.join(", ")}`,
|
|
648
|
+
textDiffs
|
|
548
649
|
});
|
|
549
650
|
}
|
|
550
651
|
}
|
|
@@ -854,6 +955,7 @@ function loadLocalAgents(platformDir, filterName, appName) {
|
|
|
854
955
|
const agentDir = join(agentsDir, entry.name);
|
|
855
956
|
const configPath = join(agentDir, "config.json");
|
|
856
957
|
const promptPath = join(agentDir, "system_prompt.md");
|
|
958
|
+
const policyPath = join(agentDir, "policy.js");
|
|
857
959
|
if (!existsSync(configPath)) {
|
|
858
960
|
errors.push(`${entry.name}: missing config.json`);
|
|
859
961
|
continue;
|
|
@@ -880,7 +982,8 @@ function loadLocalAgents(platformDir, filterName, appName) {
|
|
|
880
982
|
continue;
|
|
881
983
|
}
|
|
882
984
|
const systemPrompt = readFileSync(promptPath, "utf-8");
|
|
883
|
-
|
|
985
|
+
const policyScript = existsSync(policyPath) ? readFileSync(policyPath, "utf-8") : "";
|
|
986
|
+
agents.push({ agent: config, systemPrompt, policyScript });
|
|
884
987
|
} catch (e) {
|
|
885
988
|
errors.push(`${entry.name}: failed to parse config.json - ${e}`);
|
|
886
989
|
}
|
|
@@ -900,7 +1003,18 @@ async function planAgents(api, localAgents) {
|
|
|
900
1003
|
const remoteByExternalId = new Map(
|
|
901
1004
|
remoteAgents.filter((a) => a.external_id && !a.managed).map((a) => [a.external_id, a])
|
|
902
1005
|
);
|
|
903
|
-
|
|
1006
|
+
let skillSlugToId = /* @__PURE__ */ new Map();
|
|
1007
|
+
let skillIdToSlug = /* @__PURE__ */ new Map();
|
|
1008
|
+
const hasSkillRefs = localAgents.some((a) => a.agent.skills && a.agent.skills.length > 0) || remoteAgents.some((a) => a.skill_ids && a.skill_ids.length > 0);
|
|
1009
|
+
if (hasSkillRefs) {
|
|
1010
|
+
try {
|
|
1011
|
+
const skills = await api.listAgentSkills();
|
|
1012
|
+
skillSlugToId = new Map(skills.map((s) => [s.slug, s.id]));
|
|
1013
|
+
skillIdToSlug = new Map(skills.map((s) => [s.id, s.slug]));
|
|
1014
|
+
} catch {
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
for (const { agent, systemPrompt, policyScript } of localAgents) {
|
|
904
1018
|
const remote = remoteByExternalId.get(agent.external_id);
|
|
905
1019
|
if (!remote) {
|
|
906
1020
|
changes.push({ type: "create", resource: "agent", id: agent.external_id, name: agent.name });
|
|
@@ -910,8 +1024,27 @@ async function planAgents(api, localAgents) {
|
|
|
910
1024
|
if ((remote.description || "") !== (agent.description || "")) diffs.push("description");
|
|
911
1025
|
if ((remote.system_prompt || "").trim() !== systemPrompt.trim()) diffs.push("system_prompt");
|
|
912
1026
|
if ((remote.model || "") !== (agent.model || "")) diffs.push("model");
|
|
1027
|
+
if ((remote.policy_script || "").trim() !== (policyScript || "").trim()) diffs.push("policy_script");
|
|
1028
|
+
if ((remote.policy_enabled || false) !== (agent.policy_enabled || false)) diffs.push("policy_enabled");
|
|
1029
|
+
const localSkillIds = (agent.skills || []).map((s) => skillSlugToId.get(s) || s).sort();
|
|
1030
|
+
const remoteSkillIds = [...remote.skill_ids || []].sort();
|
|
1031
|
+
if (localSkillIds.join(",") !== remoteSkillIds.join(",")) {
|
|
1032
|
+
const addedSlugs = localSkillIds.filter((id) => !remoteSkillIds.includes(id)).map((id) => skillIdToSlug.get(id) || id);
|
|
1033
|
+
const removedSlugs = remoteSkillIds.filter((id) => !localSkillIds.includes(id)).map((id) => skillIdToSlug.get(id) || id);
|
|
1034
|
+
const parts = [];
|
|
1035
|
+
if (addedSlugs.length) parts.push(`+${addedSlugs.join(", +")}`);
|
|
1036
|
+
if (removedSlugs.length) parts.push(`-${removedSlugs.join(", -")}`);
|
|
1037
|
+
diffs.push(`skills (${parts.join(", ")})`);
|
|
1038
|
+
}
|
|
913
1039
|
if (diffs.length > 0) {
|
|
914
|
-
|
|
1040
|
+
const textDiffs = [];
|
|
1041
|
+
if (diffs.includes("system_prompt")) {
|
|
1042
|
+
textDiffs.push({ field: "system_prompt.md", oldText: remote.system_prompt || "", newText: systemPrompt });
|
|
1043
|
+
}
|
|
1044
|
+
if (diffs.includes("policy_script")) {
|
|
1045
|
+
textDiffs.push({ field: "policy.js", oldText: remote.policy_script || "", newText: policyScript });
|
|
1046
|
+
}
|
|
1047
|
+
changes.push({ type: "update", resource: "agent", id: agent.external_id, name: agent.name, details: `changed: ${diffs.join(", ")}`, textDiffs });
|
|
915
1048
|
}
|
|
916
1049
|
}
|
|
917
1050
|
}
|
|
@@ -933,7 +1066,7 @@ async function applyAgents(api, localAgents) {
|
|
|
933
1066
|
console.log(pc.yellow(` \u26A0 Could not fetch skills for resolution: ${e}`));
|
|
934
1067
|
}
|
|
935
1068
|
}
|
|
936
|
-
for (const { agent, systemPrompt } of localAgents) {
|
|
1069
|
+
for (const { agent, systemPrompt, policyScript } of localAgents) {
|
|
937
1070
|
const remote = remoteByExternalId.get(agent.external_id);
|
|
938
1071
|
const skillIds = [];
|
|
939
1072
|
if (agent.skills) {
|
|
@@ -952,7 +1085,9 @@ async function applyAgents(api, localAgents) {
|
|
|
952
1085
|
description: agent.description || "",
|
|
953
1086
|
system_prompt: systemPrompt,
|
|
954
1087
|
model: agent.model || "",
|
|
955
|
-
skill_ids: skillIds
|
|
1088
|
+
skill_ids: skillIds,
|
|
1089
|
+
policy_script: policyScript || "",
|
|
1090
|
+
policy_enabled: agent.policy_enabled || false
|
|
956
1091
|
};
|
|
957
1092
|
try {
|
|
958
1093
|
if (remote) {
|
|
@@ -999,9 +1134,14 @@ async function pullAgents(api, platformDir, filterName) {
|
|
|
999
1134
|
name: agent.name
|
|
1000
1135
|
};
|
|
1001
1136
|
if (agent.description) config.description = agent.description;
|
|
1137
|
+
if (agent.model) config.model = agent.model;
|
|
1002
1138
|
if (skillSlugs.length > 0) config.skills = skillSlugs;
|
|
1139
|
+
if (agent.policy_enabled) config.policy_enabled = true;
|
|
1003
1140
|
writeFileSync(join(agentDir, "config.json"), JSON.stringify(config, null, 2) + "\n");
|
|
1004
1141
|
writeFileSync(join(agentDir, "system_prompt.md"), agent.system_prompt || "");
|
|
1142
|
+
if (agent.policy_script) {
|
|
1143
|
+
writeFileSync(join(agentDir, "policy.js"), agent.policy_script);
|
|
1144
|
+
}
|
|
1005
1145
|
console.log(pc.green(" \u2713"), `${agent.name} \u2192 agents/${dirName}/`);
|
|
1006
1146
|
}
|
|
1007
1147
|
}
|
|
@@ -1621,6 +1761,26 @@ async function plan(args) {
|
|
|
1621
1761
|
}
|
|
1622
1762
|
console.log();
|
|
1623
1763
|
}
|
|
1764
|
+
if (change.textDiffs && change.textDiffs.length > 0) {
|
|
1765
|
+
const maxDiffLines = 20;
|
|
1766
|
+
for (const td of change.textDiffs) {
|
|
1767
|
+
const diffLines = computeLineDiff(td.oldText, td.newText);
|
|
1768
|
+
if (diffLines.length > 0) {
|
|
1769
|
+
console.log(pc.dim(` --- ${td.field}`));
|
|
1770
|
+
const shown = diffLines.slice(0, maxDiffLines);
|
|
1771
|
+
for (const dl of shown) {
|
|
1772
|
+
if (dl.type === "+") console.log(pc.green(` + ${dl.line}`));
|
|
1773
|
+
else if (dl.type === "-") console.log(pc.red(` - ${dl.line}`));
|
|
1774
|
+
else console.log(pc.dim(` ${dl.line}`));
|
|
1775
|
+
}
|
|
1776
|
+
if (diffLines.length > maxDiffLines) {
|
|
1777
|
+
const remaining = diffLines.length - maxDiffLines;
|
|
1778
|
+
console.log(pc.dim(` ... ${remaining} more lines \u2014 use ${pc.cyan(`lumera diff ${change.resource}s/${change.name}`)} for full diff`));
|
|
1779
|
+
}
|
|
1780
|
+
console.log();
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1624
1784
|
}
|
|
1625
1785
|
console.log();
|
|
1626
1786
|
console.log(pc.dim(` Run 'lumera apply' to apply these changes.`));
|
|
@@ -1636,11 +1796,12 @@ async function apply(args) {
|
|
|
1636
1796
|
const platformDir = getPlatformDir();
|
|
1637
1797
|
const api = createApiClient();
|
|
1638
1798
|
const appName = getAppName(projectRoot);
|
|
1639
|
-
const { type, name } = parseResource(args[0]);
|
|
1640
|
-
|
|
1641
|
-
console.log(pc.cyan(pc.bold(" Apply")));
|
|
1642
|
-
console.log();
|
|
1799
|
+
const { type, name } = parseResource(args.filter((a) => a !== "--yes" && a !== "-y")[0]);
|
|
1800
|
+
const autoConfirm = args.includes("--yes") || args.includes("-y") || !!process.env.CI;
|
|
1643
1801
|
if (type === "app") {
|
|
1802
|
+
console.log();
|
|
1803
|
+
console.log(pc.cyan(pc.bold(" Apply")));
|
|
1804
|
+
console.log();
|
|
1644
1805
|
console.log(pc.bold(" App:"));
|
|
1645
1806
|
await applyApp(args);
|
|
1646
1807
|
console.log();
|
|
@@ -1649,77 +1810,131 @@ async function apply(args) {
|
|
|
1649
1810
|
return;
|
|
1650
1811
|
}
|
|
1651
1812
|
let collections;
|
|
1652
|
-
let totalErrors = 0;
|
|
1653
|
-
if (!type || type === "collections") {
|
|
1654
|
-
const localCollections = loadLocalCollections(platformDir, name || void 0);
|
|
1655
|
-
if (localCollections.length > 0) {
|
|
1656
|
-
console.log(pc.bold(" Collections:"));
|
|
1657
|
-
totalErrors += await applyCollections(api, localCollections);
|
|
1658
|
-
console.log();
|
|
1659
|
-
} else if (name) {
|
|
1660
|
-
console.log(pc.red(` Collection "${name}" not found locally`));
|
|
1661
|
-
process.exit(1);
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
1813
|
try {
|
|
1665
1814
|
const remoteCollections = await api.listCollections();
|
|
1666
1815
|
collections = new Map(remoteCollections.map((c) => [c.name, c.id]));
|
|
1667
|
-
for (const c of remoteCollections)
|
|
1668
|
-
collections.set(c.id, c.id);
|
|
1669
|
-
}
|
|
1816
|
+
for (const c of remoteCollections) collections.set(c.id, c.id);
|
|
1670
1817
|
} catch {
|
|
1671
1818
|
collections = /* @__PURE__ */ new Map();
|
|
1672
1819
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
const localHooks = loadLocalHooks(platformDir, name || void 0, appName);
|
|
1686
|
-
if (localHooks.length > 0) {
|
|
1687
|
-
console.log(pc.bold(" Hooks:"));
|
|
1688
|
-
totalErrors += await applyHooks(api, localHooks, collections);
|
|
1689
|
-
console.log();
|
|
1690
|
-
} else if (name) {
|
|
1691
|
-
console.log(pc.red(` Hook "${name}" not found locally`));
|
|
1692
|
-
process.exit(1);
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
if (!type || type === "agents") {
|
|
1696
|
-
const localAgents = loadLocalAgents(platformDir, name || void 0, appName);
|
|
1697
|
-
if (localAgents.length > 0) {
|
|
1698
|
-
console.log(pc.bold(" Agents:"));
|
|
1699
|
-
totalErrors += await applyAgents(api, localAgents);
|
|
1820
|
+
const allChanges = [];
|
|
1821
|
+
const localCollections = !type || type === "collections" ? loadLocalCollections(platformDir, name || void 0) : [];
|
|
1822
|
+
const localAutomations = !type || type === "automations" ? loadLocalAutomations(platformDir, name || void 0, appName) : [];
|
|
1823
|
+
const localHooks = !type || type === "hooks" ? loadLocalHooks(platformDir, name || void 0, appName) : [];
|
|
1824
|
+
const localAgents = !type || type === "agents" ? loadLocalAgents(platformDir, name || void 0, appName) : [];
|
|
1825
|
+
if (localCollections.length > 0) allChanges.push(...await planCollections(api, localCollections));
|
|
1826
|
+
if (localAutomations.length > 0) allChanges.push(...await planAutomations(api, localAutomations));
|
|
1827
|
+
if (localHooks.length > 0) allChanges.push(...await planHooks(api, localHooks, collections));
|
|
1828
|
+
if (localAgents.length > 0) allChanges.push(...await planAgents(api, localAgents));
|
|
1829
|
+
if (name) {
|
|
1830
|
+
const hasLocal = localCollections.length > 0 || localAutomations.length > 0 || localHooks.length > 0 || localAgents.length > 0;
|
|
1831
|
+
if (!hasLocal) {
|
|
1700
1832
|
console.log();
|
|
1701
|
-
|
|
1702
|
-
console.log(pc.red(` Agent "${name}" not found locally`));
|
|
1833
|
+
console.log(pc.red(` Resource "${name}" not found locally`));
|
|
1703
1834
|
process.exit(1);
|
|
1704
1835
|
}
|
|
1705
1836
|
}
|
|
1837
|
+
let willDeployApp = false;
|
|
1706
1838
|
if (!type) {
|
|
1707
1839
|
try {
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
console.log(pc.bold(" App:"));
|
|
1711
|
-
await applyApp(args);
|
|
1712
|
-
console.log();
|
|
1840
|
+
if (existsSync(join(projectRoot, "dist")) || existsSync(join(projectRoot, "src"))) {
|
|
1841
|
+
willDeployApp = true;
|
|
1713
1842
|
}
|
|
1714
1843
|
} catch {
|
|
1715
1844
|
}
|
|
1716
1845
|
}
|
|
1846
|
+
if (allChanges.length === 0 && !willDeployApp) {
|
|
1847
|
+
console.log();
|
|
1848
|
+
console.log(pc.green(" \u2713 Nothing to apply \u2014 local and remote are in sync."));
|
|
1849
|
+
console.log();
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
console.log();
|
|
1853
|
+
console.log(pc.cyan(pc.bold(" Apply")));
|
|
1854
|
+
console.log();
|
|
1855
|
+
const creates = allChanges.filter((c) => c.type === "create");
|
|
1856
|
+
const updates = allChanges.filter((c) => c.type === "update");
|
|
1857
|
+
if (allChanges.length > 0) {
|
|
1858
|
+
console.log(pc.bold(" Plan:"));
|
|
1859
|
+
for (const change of allChanges) {
|
|
1860
|
+
const icon = change.type === "create" ? "+" : "~";
|
|
1861
|
+
const color = change.type === "create" ? pc.green : pc.yellow;
|
|
1862
|
+
const details = change.details ? ` (${change.details})` : "";
|
|
1863
|
+
console.log(` ${color(icon)} ${change.resource}: ${change.name}${pc.dim(details)}`);
|
|
1864
|
+
}
|
|
1865
|
+
if (willDeployApp) console.log(` ${pc.blue("\u25CF")} app: frontend build + deploy`);
|
|
1866
|
+
console.log();
|
|
1867
|
+
const parts = [];
|
|
1868
|
+
if (creates.length > 0) parts.push(pc.green(`${creates.length} create`));
|
|
1869
|
+
if (updates.length > 0) parts.push(pc.yellow(`${updates.length} update`));
|
|
1870
|
+
if (willDeployApp) parts.push(pc.blue("1 app deploy"));
|
|
1871
|
+
console.log(` ${parts.join(", ")}`);
|
|
1872
|
+
console.log();
|
|
1873
|
+
} else if (willDeployApp) {
|
|
1874
|
+
console.log(pc.dim(" No infrastructure changes \u2014 deploying app only."));
|
|
1875
|
+
console.log();
|
|
1876
|
+
}
|
|
1877
|
+
if (!autoConfirm && allChanges.length > 0) {
|
|
1878
|
+
const { confirm } = await prompts({
|
|
1879
|
+
type: "confirm",
|
|
1880
|
+
name: "confirm",
|
|
1881
|
+
message: " Proceed with apply?",
|
|
1882
|
+
initial: true
|
|
1883
|
+
});
|
|
1884
|
+
if (!confirm) {
|
|
1885
|
+
console.log(pc.dim(" Cancelled."));
|
|
1886
|
+
console.log();
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
console.log();
|
|
1890
|
+
}
|
|
1891
|
+
let totalErrors = 0;
|
|
1892
|
+
let totalCreated = 0;
|
|
1893
|
+
let totalUpdated = 0;
|
|
1894
|
+
let totalSkipped = 0;
|
|
1895
|
+
if (localCollections.length > 0) {
|
|
1896
|
+
console.log(pc.bold(" Collections:"));
|
|
1897
|
+
totalErrors += await applyCollections(api, localCollections);
|
|
1898
|
+
console.log();
|
|
1899
|
+
}
|
|
1900
|
+
try {
|
|
1901
|
+
const remoteCollections = await api.listCollections();
|
|
1902
|
+
collections = new Map(remoteCollections.map((c) => [c.name, c.id]));
|
|
1903
|
+
for (const c of remoteCollections) collections.set(c.id, c.id);
|
|
1904
|
+
} catch {
|
|
1905
|
+
}
|
|
1906
|
+
if (localAutomations.length > 0) {
|
|
1907
|
+
console.log(pc.bold(" Automations:"));
|
|
1908
|
+
totalErrors += await applyAutomations(api, localAutomations);
|
|
1909
|
+
console.log();
|
|
1910
|
+
}
|
|
1911
|
+
if (localHooks.length > 0) {
|
|
1912
|
+
console.log(pc.bold(" Hooks:"));
|
|
1913
|
+
totalErrors += await applyHooks(api, localHooks, collections);
|
|
1914
|
+
console.log();
|
|
1915
|
+
}
|
|
1916
|
+
if (localAgents.length > 0) {
|
|
1917
|
+
console.log(pc.bold(" Agents:"));
|
|
1918
|
+
totalErrors += await applyAgents(api, localAgents);
|
|
1919
|
+
console.log();
|
|
1920
|
+
}
|
|
1921
|
+
if (willDeployApp) {
|
|
1922
|
+
console.log(pc.bold(" App:"));
|
|
1923
|
+
await applyApp(args);
|
|
1924
|
+
console.log();
|
|
1925
|
+
}
|
|
1926
|
+
totalCreated = creates.length;
|
|
1927
|
+
totalUpdated = updates.length;
|
|
1717
1928
|
if (totalErrors > 0) {
|
|
1718
|
-
console.log(pc.red(`
|
|
1929
|
+
console.log(pc.red(` \u2717 ${totalErrors} error${totalErrors > 1 ? "s" : ""} during apply.`));
|
|
1719
1930
|
console.log();
|
|
1720
1931
|
process.exit(1);
|
|
1721
1932
|
}
|
|
1722
|
-
|
|
1933
|
+
const summary = [];
|
|
1934
|
+
if (totalCreated > 0) summary.push(pc.green(`${totalCreated} created`));
|
|
1935
|
+
if (totalUpdated > 0) summary.push(pc.yellow(`${totalUpdated} updated`));
|
|
1936
|
+
if (willDeployApp) summary.push(pc.blue("app deployed"));
|
|
1937
|
+
console.log(pc.green(" \u2713 Done!") + (summary.length > 0 ? ` ${pc.dim("\u2014")} ${summary.join(", ")}` : ""));
|
|
1723
1938
|
console.log();
|
|
1724
1939
|
}
|
|
1725
1940
|
async function pull(args) {
|
|
@@ -1727,10 +1942,63 @@ async function pull(args) {
|
|
|
1727
1942
|
showPullHelp();
|
|
1728
1943
|
return;
|
|
1729
1944
|
}
|
|
1730
|
-
|
|
1945
|
+
const force = args.includes("--force") || args.includes("-f");
|
|
1946
|
+
const filteredArgs = args.filter((a) => a !== "--force" && a !== "-f");
|
|
1947
|
+
const projectRoot = findProjectRoot();
|
|
1948
|
+
loadEnv(projectRoot);
|
|
1731
1949
|
const platformDir = getPlatformDir();
|
|
1732
1950
|
const api = createApiClient();
|
|
1733
|
-
const
|
|
1951
|
+
const appName = getAppName(projectRoot);
|
|
1952
|
+
const { type, name } = parseResource(filteredArgs[0]);
|
|
1953
|
+
if (!force) {
|
|
1954
|
+
const conflicts = [];
|
|
1955
|
+
let collections;
|
|
1956
|
+
try {
|
|
1957
|
+
const remoteCollections = await api.listCollections();
|
|
1958
|
+
collections = new Map(remoteCollections.map((c) => [c.name, c.id]));
|
|
1959
|
+
} catch {
|
|
1960
|
+
collections = /* @__PURE__ */ new Map();
|
|
1961
|
+
}
|
|
1962
|
+
if (!type || type === "agents") {
|
|
1963
|
+
const localAgents = loadLocalAgents(platformDir, name || void 0, appName);
|
|
1964
|
+
if (localAgents.length > 0) {
|
|
1965
|
+
const changes = await planAgents(api, localAgents);
|
|
1966
|
+
for (const c of changes) {
|
|
1967
|
+
if (c.type === "update") conflicts.push(`agents/${c.name}`);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
if (!type || type === "automations") {
|
|
1972
|
+
const localAutomations = loadLocalAutomations(platformDir, name || void 0, appName);
|
|
1973
|
+
if (localAutomations.length > 0) {
|
|
1974
|
+
const changes = await planAutomations(api, localAutomations);
|
|
1975
|
+
for (const c of changes) {
|
|
1976
|
+
if (c.type === "update") conflicts.push(`automations/${c.name}`);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
if (!type || type === "hooks") {
|
|
1981
|
+
const localHooks = loadLocalHooks(platformDir, name || void 0, appName);
|
|
1982
|
+
if (localHooks.length > 0) {
|
|
1983
|
+
const changes = await planHooks(api, localHooks, collections);
|
|
1984
|
+
for (const c of changes) {
|
|
1985
|
+
if (c.type === "update") conflicts.push(`hooks/${c.name}`);
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
if (conflicts.length > 0) {
|
|
1990
|
+
console.log();
|
|
1991
|
+
console.log(pc.yellow(` \u26A0 ${conflicts.length} local file${conflicts.length > 1 ? "s have" : " has"} changes that would be lost:`));
|
|
1992
|
+
for (const f of conflicts) {
|
|
1993
|
+
console.log(pc.dim(` ${f}`));
|
|
1994
|
+
}
|
|
1995
|
+
console.log();
|
|
1996
|
+
console.log(pc.dim(` Use ${pc.cyan("lumera diff <resource>")} to inspect changes.`));
|
|
1997
|
+
console.log(pc.dim(` Use ${pc.cyan("lumera pull --force")} to overwrite local files.`));
|
|
1998
|
+
console.log();
|
|
1999
|
+
process.exit(1);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
1734
2002
|
console.log();
|
|
1735
2003
|
console.log(pc.cyan(pc.bold(" Pull")));
|
|
1736
2004
|
console.log(pc.dim(` Downloading remote state to ${platformDir}/...`));
|
|
@@ -1887,9 +2155,164 @@ async function show(args) {
|
|
|
1887
2155
|
await showResource(api, platformDir, type, name, appName);
|
|
1888
2156
|
}
|
|
1889
2157
|
}
|
|
2158
|
+
function showDiffHelp() {
|
|
2159
|
+
console.log(`
|
|
2160
|
+
${pc.bold("lumera diff")} - Show full diff between local and remote state
|
|
2161
|
+
|
|
2162
|
+
${pc.dim("Usage:")}
|
|
2163
|
+
lumera diff <resource>
|
|
2164
|
+
|
|
2165
|
+
${pc.dim("Resources:")}
|
|
2166
|
+
agents/<name> Diff agent (system_prompt, policy_script)
|
|
2167
|
+
automations/<name> Diff automation code
|
|
2168
|
+
hooks/<name> Diff hook script
|
|
2169
|
+
|
|
2170
|
+
${pc.dim("Examples:")}
|
|
2171
|
+
lumera diff agents/bank_activity_matcher
|
|
2172
|
+
lumera diff automations/sync
|
|
2173
|
+
lumera diff hooks/encoding_protect_create
|
|
2174
|
+
`);
|
|
2175
|
+
}
|
|
2176
|
+
function renderFullDiff(field, oldText, newText) {
|
|
2177
|
+
const diffLines = computeLineDiff(oldText, newText);
|
|
2178
|
+
if (diffLines.length === 0) {
|
|
2179
|
+
console.log(pc.dim(` ${field}: no changes`));
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
console.log(pc.bold(` --- ${field}`));
|
|
2183
|
+
for (const dl of diffLines) {
|
|
2184
|
+
if (dl.type === "+") console.log(pc.green(` + ${dl.line}`));
|
|
2185
|
+
else if (dl.type === "-") console.log(pc.red(` - ${dl.line}`));
|
|
2186
|
+
else console.log(pc.dim(` ${dl.line}`));
|
|
2187
|
+
}
|
|
2188
|
+
console.log();
|
|
2189
|
+
}
|
|
2190
|
+
async function diff(args) {
|
|
2191
|
+
if (args.includes("--help") || args.includes("-h") || args.length === 0) {
|
|
2192
|
+
showDiffHelp();
|
|
2193
|
+
if (args.length === 0) process.exit(1);
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
const projectRoot = findProjectRoot();
|
|
2197
|
+
loadEnv(projectRoot);
|
|
2198
|
+
const platformDir = getPlatformDir();
|
|
2199
|
+
const api = createApiClient();
|
|
2200
|
+
const appName = getAppName(projectRoot);
|
|
2201
|
+
const { type, name } = parseResource(args[0]);
|
|
2202
|
+
if (!type || !name) {
|
|
2203
|
+
console.log(pc.red(` Invalid resource path: ${args[0]}`));
|
|
2204
|
+
console.log(pc.dim(" Use format: <type>/<name> (e.g., agents/bank_activity_matcher)"));
|
|
2205
|
+
process.exit(1);
|
|
2206
|
+
}
|
|
2207
|
+
console.log();
|
|
2208
|
+
if (type === "agents") {
|
|
2209
|
+
const localAgents = loadLocalAgents(platformDir, name, appName);
|
|
2210
|
+
const remoteAgents = await api.listAgents();
|
|
2211
|
+
const local = localAgents[0];
|
|
2212
|
+
const localExtId = local?.agent.external_id;
|
|
2213
|
+
const remote = remoteAgents.find(
|
|
2214
|
+
(a) => a.external_id === name || a.name === name || localExtId && a.external_id === localExtId
|
|
2215
|
+
);
|
|
2216
|
+
if (!local && !remote) {
|
|
2217
|
+
console.log(pc.red(` Agent "${name}" not found locally or remotely`));
|
|
2218
|
+
process.exit(1);
|
|
2219
|
+
}
|
|
2220
|
+
if (!local) {
|
|
2221
|
+
console.log(pc.cyan(` Agent "${name}" exists only remotely (not in local files)`));
|
|
2222
|
+
process.exit(0);
|
|
2223
|
+
}
|
|
2224
|
+
if (!remote) {
|
|
2225
|
+
console.log(pc.yellow(` Agent "${name}" exists only locally (not yet deployed)`));
|
|
2226
|
+
process.exit(0);
|
|
2227
|
+
}
|
|
2228
|
+
console.log(pc.bold(` Agent: ${local.agent.name}`));
|
|
2229
|
+
console.log();
|
|
2230
|
+
if (remote.name !== local.agent.name)
|
|
2231
|
+
console.log(` name: ${pc.red(remote.name)} \u2192 ${pc.green(local.agent.name)}`);
|
|
2232
|
+
if ((remote.description || "") !== (local.agent.description || ""))
|
|
2233
|
+
console.log(` description: ${pc.red(remote.description || "(empty)")} \u2192 ${pc.green(local.agent.description || "(empty)")}`);
|
|
2234
|
+
if ((remote.model || "") !== (local.agent.model || ""))
|
|
2235
|
+
console.log(` model: ${pc.red(remote.model || "(default)")} \u2192 ${pc.green(local.agent.model || "(default)")}`);
|
|
2236
|
+
if ((remote.policy_enabled || false) !== (local.agent.policy_enabled || false))
|
|
2237
|
+
console.log(` policy_enabled: ${pc.red(String(remote.policy_enabled || false))} \u2192 ${pc.green(String(local.agent.policy_enabled || false))}`);
|
|
2238
|
+
const promptChanged = (remote.system_prompt || "").trim() !== local.systemPrompt.trim();
|
|
2239
|
+
const policyChanged = (remote.policy_script || "").trim() !== (local.policyScript || "").trim();
|
|
2240
|
+
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)) {
|
|
2241
|
+
console.log(pc.green(` \u2713 No changes`));
|
|
2242
|
+
} else {
|
|
2243
|
+
if (promptChanged) renderFullDiff("system_prompt.md", remote.system_prompt || "", local.systemPrompt);
|
|
2244
|
+
if (policyChanged) renderFullDiff("policy.js", remote.policy_script || "", local.policyScript);
|
|
2245
|
+
}
|
|
2246
|
+
} else if (type === "automations") {
|
|
2247
|
+
const localAutomations = loadLocalAutomations(platformDir, name, appName);
|
|
2248
|
+
const remoteAutomations = await api.listAutomations({ include_code: true });
|
|
2249
|
+
const local = localAutomations[0];
|
|
2250
|
+
const localExtId = local?.automation.external_id;
|
|
2251
|
+
const remote = remoteAutomations.find(
|
|
2252
|
+
(a) => a.external_id === name || a.name === name || localExtId && a.external_id === localExtId
|
|
2253
|
+
);
|
|
2254
|
+
if (!local && !remote) {
|
|
2255
|
+
console.log(pc.red(` Automation "${name}" not found locally or remotely`));
|
|
2256
|
+
process.exit(1);
|
|
2257
|
+
}
|
|
2258
|
+
if (!local) {
|
|
2259
|
+
console.log(pc.cyan(` Automation "${name}" exists only remotely`));
|
|
2260
|
+
process.exit(0);
|
|
2261
|
+
}
|
|
2262
|
+
if (!remote) {
|
|
2263
|
+
console.log(pc.yellow(` Automation "${name}" exists only locally (not yet deployed)`));
|
|
2264
|
+
process.exit(0);
|
|
2265
|
+
}
|
|
2266
|
+
console.log(pc.bold(` Automation: ${local.automation.name}`));
|
|
2267
|
+
console.log();
|
|
2268
|
+
if (remote.name !== local.automation.name)
|
|
2269
|
+
console.log(` name: ${pc.red(remote.name)} \u2192 ${pc.green(local.automation.name)}`);
|
|
2270
|
+
const codeChanged = (remote.code || "") !== local.code;
|
|
2271
|
+
if (!codeChanged && remote.name === local.automation.name) {
|
|
2272
|
+
console.log(pc.green(` \u2713 No changes`));
|
|
2273
|
+
} else if (codeChanged) {
|
|
2274
|
+
renderFullDiff("main.py", remote.code || "", local.code);
|
|
2275
|
+
}
|
|
2276
|
+
} else if (type === "hooks") {
|
|
2277
|
+
const localHooks = loadLocalHooks(platformDir, name, appName);
|
|
2278
|
+
const remoteHooks = await api.listHooks();
|
|
2279
|
+
const local = localHooks[0];
|
|
2280
|
+
const localExtId = local?.hook.external_id;
|
|
2281
|
+
const remote = remoteHooks.find(
|
|
2282
|
+
(h) => h.external_id === name || h.name === name || localExtId && h.external_id === localExtId
|
|
2283
|
+
);
|
|
2284
|
+
if (!local && !remote) {
|
|
2285
|
+
console.log(pc.red(` Hook "${name}" not found locally or remotely`));
|
|
2286
|
+
process.exit(1);
|
|
2287
|
+
}
|
|
2288
|
+
if (!local) {
|
|
2289
|
+
console.log(pc.cyan(` Hook "${name}" exists only remotely`));
|
|
2290
|
+
process.exit(0);
|
|
2291
|
+
}
|
|
2292
|
+
if (!remote) {
|
|
2293
|
+
console.log(pc.yellow(` Hook "${name}" exists only locally (not yet deployed)`));
|
|
2294
|
+
process.exit(0);
|
|
2295
|
+
}
|
|
2296
|
+
console.log(pc.bold(` Hook: ${local.hook.external_id}`));
|
|
2297
|
+
console.log();
|
|
2298
|
+
if (remote.event !== local.hook.trigger)
|
|
2299
|
+
console.log(` trigger: ${pc.red(remote.event)} \u2192 ${pc.green(local.hook.trigger)}`);
|
|
2300
|
+
const scriptChanged = (remote.script || "").trim() !== local.script.trim();
|
|
2301
|
+
if (!scriptChanged && remote.event === local.hook.trigger) {
|
|
2302
|
+
console.log(pc.green(` \u2713 No changes`));
|
|
2303
|
+
} else if (scriptChanged) {
|
|
2304
|
+
renderFullDiff(local.fileName, remote.script || "", local.script);
|
|
2305
|
+
}
|
|
2306
|
+
} else {
|
|
2307
|
+
console.log(pc.red(` Diff not supported for "${type}" \u2014 use agents, automations, or hooks`));
|
|
2308
|
+
process.exit(1);
|
|
2309
|
+
}
|
|
2310
|
+
console.log();
|
|
2311
|
+
}
|
|
1890
2312
|
export {
|
|
1891
2313
|
apply,
|
|
1892
2314
|
destroy,
|
|
2315
|
+
diff,
|
|
1893
2316
|
list,
|
|
1894
2317
|
plan,
|
|
1895
2318
|
pull,
|