@lumerahq/cli 0.13.2 → 0.14.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-QKEUX3C3.js} +325 -14
- 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-QKEUX3C3.js").then((m) => m.plan(args.slice(1)));
|
|
214
216
|
break;
|
|
215
217
|
case "apply":
|
|
216
|
-
await import("./resources-
|
|
218
|
+
await import("./resources-QKEUX3C3.js").then((m) => m.apply(args.slice(1)));
|
|
217
219
|
break;
|
|
218
220
|
case "pull":
|
|
219
|
-
await import("./resources-
|
|
221
|
+
await import("./resources-QKEUX3C3.js").then((m) => m.pull(args.slice(1)));
|
|
220
222
|
break;
|
|
221
223
|
case "destroy":
|
|
222
|
-
await import("./resources-
|
|
224
|
+
await import("./resources-QKEUX3C3.js").then((m) => m.destroy(args.slice(1)));
|
|
223
225
|
break;
|
|
224
226
|
case "list":
|
|
225
|
-
await import("./resources-
|
|
227
|
+
await import("./resources-QKEUX3C3.js").then((m) => m.list(args.slice(1)));
|
|
226
228
|
break;
|
|
227
229
|
case "show":
|
|
228
|
-
await import("./resources-
|
|
230
|
+
await import("./resources-QKEUX3C3.js").then((m) => m.show(args.slice(1)));
|
|
231
|
+
break;
|
|
232
|
+
case "diff":
|
|
233
|
+
await import("./resources-QKEUX3C3.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:")}
|
|
@@ -89,10 +138,15 @@ ${pc.dim("Examples:")}
|
|
|
89
138
|
function showPullHelp() {
|
|
90
139
|
console.log(`
|
|
91
140
|
${pc.dim("Usage:")}
|
|
92
|
-
lumera pull [resource]
|
|
141
|
+
lumera pull [resource] [--force]
|
|
93
142
|
|
|
94
143
|
${pc.dim("Description:")}
|
|
95
144
|
Download remote state to local files.
|
|
145
|
+
Refuses to overwrite local files that have uncommitted changes
|
|
146
|
+
(use --force to override, or 'lumera diff' to inspect first).
|
|
147
|
+
|
|
148
|
+
${pc.dim("Options:")}
|
|
149
|
+
--force, -f Overwrite local files even if they have changes
|
|
96
150
|
|
|
97
151
|
${pc.dim("Resources:")}
|
|
98
152
|
(none) Pull all resources
|
|
@@ -105,9 +159,10 @@ ${pc.dim("Resources:")}
|
|
|
105
159
|
agents/<name> Pull single agent
|
|
106
160
|
|
|
107
161
|
${pc.dim("Examples:")}
|
|
108
|
-
lumera pull # Pull all
|
|
109
|
-
lumera pull
|
|
110
|
-
lumera pull
|
|
162
|
+
lumera pull # Pull all (safe \u2014 warns on conflicts)
|
|
163
|
+
lumera pull agents # Pull only agents
|
|
164
|
+
lumera pull --force # Pull all, overwrite local changes
|
|
165
|
+
lumera diff agents/my_agent # Inspect before pulling
|
|
111
166
|
`);
|
|
112
167
|
}
|
|
113
168
|
function showDestroyHelp() {
|
|
@@ -502,12 +557,17 @@ async function planAutomations(api, localAutomations) {
|
|
|
502
557
|
if (codeChanged) details.push("code");
|
|
503
558
|
if (nameChanged) details.push("name");
|
|
504
559
|
if (descChanged) details.push("description");
|
|
560
|
+
const textDiffs = [];
|
|
561
|
+
if (codeChanged) {
|
|
562
|
+
textDiffs.push({ field: "main.py", oldText: remote.code || "", newText: code });
|
|
563
|
+
}
|
|
505
564
|
changes.push({
|
|
506
565
|
type: "update",
|
|
507
566
|
resource: "automation",
|
|
508
567
|
id: automation.external_id,
|
|
509
568
|
name: automation.name,
|
|
510
|
-
details: `changed: ${details.join(", ")}
|
|
569
|
+
details: `changed: ${details.join(", ")}`,
|
|
570
|
+
textDiffs
|
|
511
571
|
});
|
|
512
572
|
}
|
|
513
573
|
}
|
|
@@ -539,12 +599,17 @@ async function planHooks(api, localHooks, collections) {
|
|
|
539
599
|
const details = [];
|
|
540
600
|
if (scriptChanged) details.push("script");
|
|
541
601
|
if (eventChanged) details.push("trigger");
|
|
602
|
+
const textDiffs = [];
|
|
603
|
+
if (scriptChanged) {
|
|
604
|
+
textDiffs.push({ field: fileName, oldText: remote.script || "", newText: script });
|
|
605
|
+
}
|
|
542
606
|
changes.push({
|
|
543
607
|
type: "update",
|
|
544
608
|
resource: "hook",
|
|
545
609
|
id: hook.external_id,
|
|
546
610
|
name: `${hook.collection}.${hook.trigger}`,
|
|
547
|
-
details: `changed: ${details.join(", ")}
|
|
611
|
+
details: `changed: ${details.join(", ")}`,
|
|
612
|
+
textDiffs
|
|
548
613
|
});
|
|
549
614
|
}
|
|
550
615
|
}
|
|
@@ -854,6 +919,7 @@ function loadLocalAgents(platformDir, filterName, appName) {
|
|
|
854
919
|
const agentDir = join(agentsDir, entry.name);
|
|
855
920
|
const configPath = join(agentDir, "config.json");
|
|
856
921
|
const promptPath = join(agentDir, "system_prompt.md");
|
|
922
|
+
const policyPath = join(agentDir, "policy.js");
|
|
857
923
|
if (!existsSync(configPath)) {
|
|
858
924
|
errors.push(`${entry.name}: missing config.json`);
|
|
859
925
|
continue;
|
|
@@ -880,7 +946,8 @@ function loadLocalAgents(platformDir, filterName, appName) {
|
|
|
880
946
|
continue;
|
|
881
947
|
}
|
|
882
948
|
const systemPrompt = readFileSync(promptPath, "utf-8");
|
|
883
|
-
|
|
949
|
+
const policyScript = existsSync(policyPath) ? readFileSync(policyPath, "utf-8") : "";
|
|
950
|
+
agents.push({ agent: config, systemPrompt, policyScript });
|
|
884
951
|
} catch (e) {
|
|
885
952
|
errors.push(`${entry.name}: failed to parse config.json - ${e}`);
|
|
886
953
|
}
|
|
@@ -900,7 +967,7 @@ async function planAgents(api, localAgents) {
|
|
|
900
967
|
const remoteByExternalId = new Map(
|
|
901
968
|
remoteAgents.filter((a) => a.external_id && !a.managed).map((a) => [a.external_id, a])
|
|
902
969
|
);
|
|
903
|
-
for (const { agent, systemPrompt } of localAgents) {
|
|
970
|
+
for (const { agent, systemPrompt, policyScript } of localAgents) {
|
|
904
971
|
const remote = remoteByExternalId.get(agent.external_id);
|
|
905
972
|
if (!remote) {
|
|
906
973
|
changes.push({ type: "create", resource: "agent", id: agent.external_id, name: agent.name });
|
|
@@ -910,8 +977,17 @@ async function planAgents(api, localAgents) {
|
|
|
910
977
|
if ((remote.description || "") !== (agent.description || "")) diffs.push("description");
|
|
911
978
|
if ((remote.system_prompt || "").trim() !== systemPrompt.trim()) diffs.push("system_prompt");
|
|
912
979
|
if ((remote.model || "") !== (agent.model || "")) diffs.push("model");
|
|
980
|
+
if ((remote.policy_script || "").trim() !== (policyScript || "").trim()) diffs.push("policy_script");
|
|
981
|
+
if ((remote.policy_enabled || false) !== (agent.policy_enabled || false)) diffs.push("policy_enabled");
|
|
913
982
|
if (diffs.length > 0) {
|
|
914
|
-
|
|
983
|
+
const textDiffs = [];
|
|
984
|
+
if (diffs.includes("system_prompt")) {
|
|
985
|
+
textDiffs.push({ field: "system_prompt.md", oldText: remote.system_prompt || "", newText: systemPrompt });
|
|
986
|
+
}
|
|
987
|
+
if (diffs.includes("policy_script")) {
|
|
988
|
+
textDiffs.push({ field: "policy.js", oldText: remote.policy_script || "", newText: policyScript });
|
|
989
|
+
}
|
|
990
|
+
changes.push({ type: "update", resource: "agent", id: agent.external_id, name: agent.name, details: `changed: ${diffs.join(", ")}`, textDiffs });
|
|
915
991
|
}
|
|
916
992
|
}
|
|
917
993
|
}
|
|
@@ -933,7 +1009,7 @@ async function applyAgents(api, localAgents) {
|
|
|
933
1009
|
console.log(pc.yellow(` \u26A0 Could not fetch skills for resolution: ${e}`));
|
|
934
1010
|
}
|
|
935
1011
|
}
|
|
936
|
-
for (const { agent, systemPrompt } of localAgents) {
|
|
1012
|
+
for (const { agent, systemPrompt, policyScript } of localAgents) {
|
|
937
1013
|
const remote = remoteByExternalId.get(agent.external_id);
|
|
938
1014
|
const skillIds = [];
|
|
939
1015
|
if (agent.skills) {
|
|
@@ -952,7 +1028,9 @@ async function applyAgents(api, localAgents) {
|
|
|
952
1028
|
description: agent.description || "",
|
|
953
1029
|
system_prompt: systemPrompt,
|
|
954
1030
|
model: agent.model || "",
|
|
955
|
-
skill_ids: skillIds
|
|
1031
|
+
skill_ids: skillIds,
|
|
1032
|
+
policy_script: policyScript || "",
|
|
1033
|
+
policy_enabled: agent.policy_enabled || false
|
|
956
1034
|
};
|
|
957
1035
|
try {
|
|
958
1036
|
if (remote) {
|
|
@@ -999,9 +1077,14 @@ async function pullAgents(api, platformDir, filterName) {
|
|
|
999
1077
|
name: agent.name
|
|
1000
1078
|
};
|
|
1001
1079
|
if (agent.description) config.description = agent.description;
|
|
1080
|
+
if (agent.model) config.model = agent.model;
|
|
1002
1081
|
if (skillSlugs.length > 0) config.skills = skillSlugs;
|
|
1082
|
+
if (agent.policy_enabled) config.policy_enabled = true;
|
|
1003
1083
|
writeFileSync(join(agentDir, "config.json"), JSON.stringify(config, null, 2) + "\n");
|
|
1004
1084
|
writeFileSync(join(agentDir, "system_prompt.md"), agent.system_prompt || "");
|
|
1085
|
+
if (agent.policy_script) {
|
|
1086
|
+
writeFileSync(join(agentDir, "policy.js"), agent.policy_script);
|
|
1087
|
+
}
|
|
1005
1088
|
console.log(pc.green(" \u2713"), `${agent.name} \u2192 agents/${dirName}/`);
|
|
1006
1089
|
}
|
|
1007
1090
|
}
|
|
@@ -1621,6 +1704,26 @@ async function plan(args) {
|
|
|
1621
1704
|
}
|
|
1622
1705
|
console.log();
|
|
1623
1706
|
}
|
|
1707
|
+
if (change.textDiffs && change.textDiffs.length > 0) {
|
|
1708
|
+
const maxDiffLines = 20;
|
|
1709
|
+
for (const td of change.textDiffs) {
|
|
1710
|
+
const diffLines = computeLineDiff(td.oldText, td.newText);
|
|
1711
|
+
if (diffLines.length > 0) {
|
|
1712
|
+
console.log(pc.dim(` --- ${td.field}`));
|
|
1713
|
+
const shown = diffLines.slice(0, maxDiffLines);
|
|
1714
|
+
for (const dl of shown) {
|
|
1715
|
+
if (dl.type === "+") console.log(pc.green(` + ${dl.line}`));
|
|
1716
|
+
else if (dl.type === "-") console.log(pc.red(` - ${dl.line}`));
|
|
1717
|
+
else console.log(pc.dim(` ${dl.line}`));
|
|
1718
|
+
}
|
|
1719
|
+
if (diffLines.length > maxDiffLines) {
|
|
1720
|
+
const remaining = diffLines.length - maxDiffLines;
|
|
1721
|
+
console.log(pc.dim(` ... ${remaining} more lines \u2014 use ${pc.cyan(`lumera diff ${change.resource}s/${change.name}`)} for full diff`));
|
|
1722
|
+
}
|
|
1723
|
+
console.log();
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1624
1727
|
}
|
|
1625
1728
|
console.log();
|
|
1626
1729
|
console.log(pc.dim(` Run 'lumera apply' to apply these changes.`));
|
|
@@ -1727,10 +1830,63 @@ async function pull(args) {
|
|
|
1727
1830
|
showPullHelp();
|
|
1728
1831
|
return;
|
|
1729
1832
|
}
|
|
1730
|
-
|
|
1833
|
+
const force = args.includes("--force") || args.includes("-f");
|
|
1834
|
+
const filteredArgs = args.filter((a) => a !== "--force" && a !== "-f");
|
|
1835
|
+
const projectRoot = findProjectRoot();
|
|
1836
|
+
loadEnv(projectRoot);
|
|
1731
1837
|
const platformDir = getPlatformDir();
|
|
1732
1838
|
const api = createApiClient();
|
|
1733
|
-
const
|
|
1839
|
+
const appName = getAppName(projectRoot);
|
|
1840
|
+
const { type, name } = parseResource(filteredArgs[0]);
|
|
1841
|
+
if (!force) {
|
|
1842
|
+
const conflicts = [];
|
|
1843
|
+
let collections;
|
|
1844
|
+
try {
|
|
1845
|
+
const remoteCollections = await api.listCollections();
|
|
1846
|
+
collections = new Map(remoteCollections.map((c) => [c.name, c.id]));
|
|
1847
|
+
} catch {
|
|
1848
|
+
collections = /* @__PURE__ */ new Map();
|
|
1849
|
+
}
|
|
1850
|
+
if (!type || type === "agents") {
|
|
1851
|
+
const localAgents = loadLocalAgents(platformDir, name || void 0, appName);
|
|
1852
|
+
if (localAgents.length > 0) {
|
|
1853
|
+
const changes = await planAgents(api, localAgents);
|
|
1854
|
+
for (const c of changes) {
|
|
1855
|
+
if (c.type === "update") conflicts.push(`agents/${c.name}`);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
if (!type || type === "automations") {
|
|
1860
|
+
const localAutomations = loadLocalAutomations(platformDir, name || void 0, appName);
|
|
1861
|
+
if (localAutomations.length > 0) {
|
|
1862
|
+
const changes = await planAutomations(api, localAutomations);
|
|
1863
|
+
for (const c of changes) {
|
|
1864
|
+
if (c.type === "update") conflicts.push(`automations/${c.name}`);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
if (!type || type === "hooks") {
|
|
1869
|
+
const localHooks = loadLocalHooks(platformDir, name || void 0, appName);
|
|
1870
|
+
if (localHooks.length > 0) {
|
|
1871
|
+
const changes = await planHooks(api, localHooks, collections);
|
|
1872
|
+
for (const c of changes) {
|
|
1873
|
+
if (c.type === "update") conflicts.push(`hooks/${c.name}`);
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
if (conflicts.length > 0) {
|
|
1878
|
+
console.log();
|
|
1879
|
+
console.log(pc.yellow(` \u26A0 ${conflicts.length} local file${conflicts.length > 1 ? "s have" : " has"} changes that would be lost:`));
|
|
1880
|
+
for (const f of conflicts) {
|
|
1881
|
+
console.log(pc.dim(` ${f}`));
|
|
1882
|
+
}
|
|
1883
|
+
console.log();
|
|
1884
|
+
console.log(pc.dim(` Use ${pc.cyan("lumera diff <resource>")} to inspect changes.`));
|
|
1885
|
+
console.log(pc.dim(` Use ${pc.cyan("lumera pull --force")} to overwrite local files.`));
|
|
1886
|
+
console.log();
|
|
1887
|
+
process.exit(1);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1734
1890
|
console.log();
|
|
1735
1891
|
console.log(pc.cyan(pc.bold(" Pull")));
|
|
1736
1892
|
console.log(pc.dim(` Downloading remote state to ${platformDir}/...`));
|
|
@@ -1887,9 +2043,164 @@ async function show(args) {
|
|
|
1887
2043
|
await showResource(api, platformDir, type, name, appName);
|
|
1888
2044
|
}
|
|
1889
2045
|
}
|
|
2046
|
+
function showDiffHelp() {
|
|
2047
|
+
console.log(`
|
|
2048
|
+
${pc.bold("lumera diff")} - Show full diff between local and remote state
|
|
2049
|
+
|
|
2050
|
+
${pc.dim("Usage:")}
|
|
2051
|
+
lumera diff <resource>
|
|
2052
|
+
|
|
2053
|
+
${pc.dim("Resources:")}
|
|
2054
|
+
agents/<name> Diff agent (system_prompt, policy_script)
|
|
2055
|
+
automations/<name> Diff automation code
|
|
2056
|
+
hooks/<name> Diff hook script
|
|
2057
|
+
|
|
2058
|
+
${pc.dim("Examples:")}
|
|
2059
|
+
lumera diff agents/bank_activity_matcher
|
|
2060
|
+
lumera diff automations/sync
|
|
2061
|
+
lumera diff hooks/encoding_protect_create
|
|
2062
|
+
`);
|
|
2063
|
+
}
|
|
2064
|
+
function renderFullDiff(field, oldText, newText) {
|
|
2065
|
+
const diffLines = computeLineDiff(oldText, newText);
|
|
2066
|
+
if (diffLines.length === 0) {
|
|
2067
|
+
console.log(pc.dim(` ${field}: no changes`));
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
console.log(pc.bold(` --- ${field}`));
|
|
2071
|
+
for (const dl of diffLines) {
|
|
2072
|
+
if (dl.type === "+") console.log(pc.green(` + ${dl.line}`));
|
|
2073
|
+
else if (dl.type === "-") console.log(pc.red(` - ${dl.line}`));
|
|
2074
|
+
else console.log(pc.dim(` ${dl.line}`));
|
|
2075
|
+
}
|
|
2076
|
+
console.log();
|
|
2077
|
+
}
|
|
2078
|
+
async function diff(args) {
|
|
2079
|
+
if (args.includes("--help") || args.includes("-h") || args.length === 0) {
|
|
2080
|
+
showDiffHelp();
|
|
2081
|
+
if (args.length === 0) process.exit(1);
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
2084
|
+
const projectRoot = findProjectRoot();
|
|
2085
|
+
loadEnv(projectRoot);
|
|
2086
|
+
const platformDir = getPlatformDir();
|
|
2087
|
+
const api = createApiClient();
|
|
2088
|
+
const appName = getAppName(projectRoot);
|
|
2089
|
+
const { type, name } = parseResource(args[0]);
|
|
2090
|
+
if (!type || !name) {
|
|
2091
|
+
console.log(pc.red(` Invalid resource path: ${args[0]}`));
|
|
2092
|
+
console.log(pc.dim(" Use format: <type>/<name> (e.g., agents/bank_activity_matcher)"));
|
|
2093
|
+
process.exit(1);
|
|
2094
|
+
}
|
|
2095
|
+
console.log();
|
|
2096
|
+
if (type === "agents") {
|
|
2097
|
+
const localAgents = loadLocalAgents(platformDir, name, appName);
|
|
2098
|
+
const remoteAgents = await api.listAgents();
|
|
2099
|
+
const local = localAgents[0];
|
|
2100
|
+
const localExtId = local?.agent.external_id;
|
|
2101
|
+
const remote = remoteAgents.find(
|
|
2102
|
+
(a) => a.external_id === name || a.name === name || localExtId && a.external_id === localExtId
|
|
2103
|
+
);
|
|
2104
|
+
if (!local && !remote) {
|
|
2105
|
+
console.log(pc.red(` Agent "${name}" not found locally or remotely`));
|
|
2106
|
+
process.exit(1);
|
|
2107
|
+
}
|
|
2108
|
+
if (!local) {
|
|
2109
|
+
console.log(pc.cyan(` Agent "${name}" exists only remotely (not in local files)`));
|
|
2110
|
+
process.exit(0);
|
|
2111
|
+
}
|
|
2112
|
+
if (!remote) {
|
|
2113
|
+
console.log(pc.yellow(` Agent "${name}" exists only locally (not yet deployed)`));
|
|
2114
|
+
process.exit(0);
|
|
2115
|
+
}
|
|
2116
|
+
console.log(pc.bold(` Agent: ${local.agent.name}`));
|
|
2117
|
+
console.log();
|
|
2118
|
+
if (remote.name !== local.agent.name)
|
|
2119
|
+
console.log(` name: ${pc.red(remote.name)} \u2192 ${pc.green(local.agent.name)}`);
|
|
2120
|
+
if ((remote.description || "") !== (local.agent.description || ""))
|
|
2121
|
+
console.log(` description: ${pc.red(remote.description || "(empty)")} \u2192 ${pc.green(local.agent.description || "(empty)")}`);
|
|
2122
|
+
if ((remote.model || "") !== (local.agent.model || ""))
|
|
2123
|
+
console.log(` model: ${pc.red(remote.model || "(default)")} \u2192 ${pc.green(local.agent.model || "(default)")}`);
|
|
2124
|
+
if ((remote.policy_enabled || false) !== (local.agent.policy_enabled || false))
|
|
2125
|
+
console.log(` policy_enabled: ${pc.red(String(remote.policy_enabled || false))} \u2192 ${pc.green(String(local.agent.policy_enabled || false))}`);
|
|
2126
|
+
const promptChanged = (remote.system_prompt || "").trim() !== local.systemPrompt.trim();
|
|
2127
|
+
const policyChanged = (remote.policy_script || "").trim() !== (local.policyScript || "").trim();
|
|
2128
|
+
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)) {
|
|
2129
|
+
console.log(pc.green(` \u2713 No changes`));
|
|
2130
|
+
} else {
|
|
2131
|
+
if (promptChanged) renderFullDiff("system_prompt.md", remote.system_prompt || "", local.systemPrompt);
|
|
2132
|
+
if (policyChanged) renderFullDiff("policy.js", remote.policy_script || "", local.policyScript);
|
|
2133
|
+
}
|
|
2134
|
+
} else if (type === "automations") {
|
|
2135
|
+
const localAutomations = loadLocalAutomations(platformDir, name, appName);
|
|
2136
|
+
const remoteAutomations = await api.listAutomations({ include_code: true });
|
|
2137
|
+
const local = localAutomations[0];
|
|
2138
|
+
const localExtId = local?.automation.external_id;
|
|
2139
|
+
const remote = remoteAutomations.find(
|
|
2140
|
+
(a) => a.external_id === name || a.name === name || localExtId && a.external_id === localExtId
|
|
2141
|
+
);
|
|
2142
|
+
if (!local && !remote) {
|
|
2143
|
+
console.log(pc.red(` Automation "${name}" not found locally or remotely`));
|
|
2144
|
+
process.exit(1);
|
|
2145
|
+
}
|
|
2146
|
+
if (!local) {
|
|
2147
|
+
console.log(pc.cyan(` Automation "${name}" exists only remotely`));
|
|
2148
|
+
process.exit(0);
|
|
2149
|
+
}
|
|
2150
|
+
if (!remote) {
|
|
2151
|
+
console.log(pc.yellow(` Automation "${name}" exists only locally (not yet deployed)`));
|
|
2152
|
+
process.exit(0);
|
|
2153
|
+
}
|
|
2154
|
+
console.log(pc.bold(` Automation: ${local.automation.name}`));
|
|
2155
|
+
console.log();
|
|
2156
|
+
if (remote.name !== local.automation.name)
|
|
2157
|
+
console.log(` name: ${pc.red(remote.name)} \u2192 ${pc.green(local.automation.name)}`);
|
|
2158
|
+
const codeChanged = (remote.code || "") !== local.code;
|
|
2159
|
+
if (!codeChanged && remote.name === local.automation.name) {
|
|
2160
|
+
console.log(pc.green(` \u2713 No changes`));
|
|
2161
|
+
} else if (codeChanged) {
|
|
2162
|
+
renderFullDiff("main.py", remote.code || "", local.code);
|
|
2163
|
+
}
|
|
2164
|
+
} else if (type === "hooks") {
|
|
2165
|
+
const localHooks = loadLocalHooks(platformDir, name, appName);
|
|
2166
|
+
const remoteHooks = await api.listHooks();
|
|
2167
|
+
const local = localHooks[0];
|
|
2168
|
+
const localExtId = local?.hook.external_id;
|
|
2169
|
+
const remote = remoteHooks.find(
|
|
2170
|
+
(h) => h.external_id === name || h.name === name || localExtId && h.external_id === localExtId
|
|
2171
|
+
);
|
|
2172
|
+
if (!local && !remote) {
|
|
2173
|
+
console.log(pc.red(` Hook "${name}" not found locally or remotely`));
|
|
2174
|
+
process.exit(1);
|
|
2175
|
+
}
|
|
2176
|
+
if (!local) {
|
|
2177
|
+
console.log(pc.cyan(` Hook "${name}" exists only remotely`));
|
|
2178
|
+
process.exit(0);
|
|
2179
|
+
}
|
|
2180
|
+
if (!remote) {
|
|
2181
|
+
console.log(pc.yellow(` Hook "${name}" exists only locally (not yet deployed)`));
|
|
2182
|
+
process.exit(0);
|
|
2183
|
+
}
|
|
2184
|
+
console.log(pc.bold(` Hook: ${local.hook.external_id}`));
|
|
2185
|
+
console.log();
|
|
2186
|
+
if (remote.event !== local.hook.trigger)
|
|
2187
|
+
console.log(` trigger: ${pc.red(remote.event)} \u2192 ${pc.green(local.hook.trigger)}`);
|
|
2188
|
+
const scriptChanged = (remote.script || "").trim() !== local.script.trim();
|
|
2189
|
+
if (!scriptChanged && remote.event === local.hook.trigger) {
|
|
2190
|
+
console.log(pc.green(` \u2713 No changes`));
|
|
2191
|
+
} else if (scriptChanged) {
|
|
2192
|
+
renderFullDiff(local.fileName, remote.script || "", local.script);
|
|
2193
|
+
}
|
|
2194
|
+
} else {
|
|
2195
|
+
console.log(pc.red(` Diff not supported for "${type}" \u2014 use agents, automations, or hooks`));
|
|
2196
|
+
process.exit(1);
|
|
2197
|
+
}
|
|
2198
|
+
console.log();
|
|
2199
|
+
}
|
|
1890
2200
|
export {
|
|
1891
2201
|
apply,
|
|
1892
2202
|
destroy,
|
|
2203
|
+
diff,
|
|
1893
2204
|
list,
|
|
1894
2205
|
plan,
|
|
1895
2206
|
pull,
|