@meshxdata/fops 0.1.55 → 0.1.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +191 -4
  2. package/package.json +1 -2
  3. package/src/commands/index.js +2 -0
  4. package/src/commands/k3s-cmd.js +124 -0
  5. package/src/commands/lifecycle.js +7 -0
  6. package/src/plugins/builtins/docker-compose.js +17 -35
  7. package/src/plugins/bundled/fops-plugin-azure/lib/azure-openai.js +0 -3
  8. package/src/plugins/bundled/fops-plugin-azure/lib/commands/vm-cmds.js +5 -2
  9. package/src/plugins/bundled/fops-plugin-cloud/api.js +14 -0
  10. package/src/project.js +12 -7
  11. package/src/plugins/bundled/fops-plugin-cloud/ui/postcss.config.cjs +0 -5
  12. package/src/plugins/bundled/fops-plugin-cloud/ui/src/App.jsx +0 -32
  13. package/src/plugins/bundled/fops-plugin-cloud/ui/src/api/client.js +0 -114
  14. package/src/plugins/bundled/fops-plugin-cloud/ui/src/api/queries.js +0 -111
  15. package/src/plugins/bundled/fops-plugin-cloud/ui/src/components/LogPanel.jsx +0 -162
  16. package/src/plugins/bundled/fops-plugin-cloud/ui/src/components/ThemeToggle.jsx +0 -46
  17. package/src/plugins/bundled/fops-plugin-cloud/ui/src/css/additional-styles/utility-patterns.css +0 -147
  18. package/src/plugins/bundled/fops-plugin-cloud/ui/src/css/style.css +0 -138
  19. package/src/plugins/bundled/fops-plugin-cloud/ui/src/favicon.svg +0 -15
  20. package/src/plugins/bundled/fops-plugin-cloud/ui/src/lib/utils.ts +0 -19
  21. package/src/plugins/bundled/fops-plugin-cloud/ui/src/main.jsx +0 -25
  22. package/src/plugins/bundled/fops-plugin-cloud/ui/src/pages/Audit.jsx +0 -164
  23. package/src/plugins/bundled/fops-plugin-cloud/ui/src/pages/Costs.jsx +0 -305
  24. package/src/plugins/bundled/fops-plugin-cloud/ui/src/pages/CreateResource.jsx +0 -285
  25. package/src/plugins/bundled/fops-plugin-cloud/ui/src/pages/Fleet.jsx +0 -307
  26. package/src/plugins/bundled/fops-plugin-cloud/ui/src/pages/Resources.jsx +0 -229
  27. package/src/plugins/bundled/fops-plugin-cloud/ui/src/partials/Header.jsx +0 -132
  28. package/src/plugins/bundled/fops-plugin-cloud/ui/src/partials/Sidebar.jsx +0 -174
  29. package/src/plugins/bundled/fops-plugin-cloud/ui/src/partials/SidebarLinkGroup.jsx +0 -21
  30. package/src/plugins/bundled/fops-plugin-cloud/ui/src/utils/AuthContext.jsx +0 -170
  31. package/src/plugins/bundled/fops-plugin-cloud/ui/src/utils/Info.jsx +0 -49
  32. package/src/plugins/bundled/fops-plugin-cloud/ui/src/utils/ThemeContext.jsx +0 -37
  33. package/src/plugins/bundled/fops-plugin-cloud/ui/src/utils/Transition.jsx +0 -116
  34. package/src/plugins/bundled/fops-plugin-cloud/ui/src/utils/Utils.js +0 -63
package/CHANGELOG.md CHANGED
@@ -1,5 +1,194 @@
1
- ## [0.1.55] - 2026-03-26
1
+ # Changelog
2
+
3
+ All notable changes to @meshxdata/fops (Foundation Operator CLI) are documented here.
2
4
 
5
+ ## [0.1.58] - 2026-03-26
6
+
7
+ - bump storage (a1a5761)
8
+ - restore all missing services (pgpool, exporters, grafana, etc), add loki to k3s profile, always activate loki profile in fops up (4e2744a)
9
+ - fix: grafana alert-rules provisioning, ENVIRONMENT_NAME from --url, k3s secret sync, vm-sizes endpoint, project root resolution (9839052)
10
+ - feat(azure): add 'fops azure reconcile <name>' command for VM drift fix (79ba6e2)
11
+ - fix(otel,loki): remove duplicate spanmetrics dimensions, use .env for loki S3 creds (e3d1def)
12
+ - fix(loki): pass S3 credentials from .env so loki works without vault-init (c57906d)
13
+ - fix(azure): improve VM provisioning reliability (2ddd669)
14
+ - cluster discovery (009257d)
15
+ - feat(storage): add loki container to provisioning (898c544)
16
+ - feat(azure): add ping command to check backend health (8336825)
17
+ - operator cli bump 0.1.52 (f052cb5)
18
+ - fix(doctor): set KUBECONFIG for k3s kubectl commands (db9359b)
19
+ - fix(azure): move --landscape to test run command, not separate subcommand (4b9b089)
20
+ - feat(azure): add test integration command with landscape support (b2990a0)
21
+ - fix(fleet): skip VMs without public IPs in fleet exec (39acbaa)
22
+ - feat(azure): detect and fix External Secrets identity issues (f907d11)
23
+ - operator cli bump 0.1.51 (db55bdc)
24
+ - feat: add postgres-exporter and Azure tray menu improvements (2a337ac)
25
+ - operator cli plugin fix (4dae908)
26
+ - operator cli plugin fix (25620cc)
27
+ - operator cli test fixes (1d1c18f)
28
+ - feat(test): add setup-users command for QA test user creation (b929507)
29
+ - feat(aks): show HA standby clusters with visual grouping (8fb640c)
30
+ - refactor(provision): extract VM provisioning to dedicated module (af321a7)
31
+ - refactor(provision): extract post-start health checks to dedicated module (6ed5f2d)
32
+ - fix: ping timeout 15s, fix prometheus sed escaping (d11ac14)
33
+ - refactor(vm): extract terraform HCL generation to dedicated module (896a64b)
34
+ - refactor(keyvault): extract key operations to dedicated module (716bbe4)
35
+ - refactor(azure): extract swarm functions to azure-fleet-swarm.js (4690e34)
36
+ - refactor(azure): extract SSH/remote functions to azure-ops-ssh.js (e62b8f0)
37
+ - refactor(azure): split azure-ops.js into smaller modules (4515425)
38
+ - feat(aks): add --ha flag for full cross-region HA setup (ece68c5)
39
+ - feat(fops): inject ENVIRONMENT_NAME on VM provisioning (6ef2a27)
40
+ - fix(postgres): disable SSL mode to fix connection issues (c789ae9)
41
+ - feat(trino): add caching configuration for docker-compose (3668224)
42
+ - fix(fops-azure): run pytest directly instead of missing scripts (29f8410)
43
+ - add -d detach option for local frontend dev, remove hive cpu limits (3306667)
44
+ - release 0.1.49 (dcca32b)
45
+ - release 0.1.48 (9b195e5)
46
+ - stash on updates (2916c01)
47
+ - stash on updates (b5c14df)
48
+ - stash on updates (d0453d1)
49
+ - frontend dev fixes (0ca7b00)
50
+ - fix: update azure test commands (77c81da)
51
+ - default locust to CLI mode, add --web for UI (ca35bff)
52
+ - add locust command for load testing AKS clusters (1278722)
53
+ - update spot node pool default autoscaling to 1-20 (617c182)
54
+ - module for aks (3dd1a61)
55
+ - add hive to PG_SERVICE_DBS for fops pg-setup (afccb16)
56
+ - feat(azure): enhance aks doctor with ExternalSecrets and PGSSLMODE checks (8b14861)
57
+ - add foundation-postgres ExternalName service to reconciler (ea88e11)
58
+ - new flux templates (0e2e372)
59
+ - feat(azure): add storage-engine secrets to Key Vault (a4f488e)
60
+ - feat(azure-aks): add AUTH0_DOMAIN to template rendering variables (216c37e)
61
+ - feat(azure): add storage account creation per cluster (aa1b138)
62
+ - bump watcher (ab24473)
63
+ - fix: concurrent compute calls (#66) (03e2edf)
64
+ - bump backend version (5058ff5)
65
+ - bump fops to 0.1.44 (8c0ef5d)
66
+ - Mlflow and azure plugin fix (176881f)
67
+ - fix lifecycle (a2cb9e7)
68
+ - callback url for localhost (821fb94)
69
+ - disable 4 scaffolding plugin by default. (bfb2b76)
70
+ - jaccard improvements (b7494a0)
71
+ - refactor azure plugin (68dfef4)
72
+ - refactor azure plugin (b24a008)
73
+ - fix trino catalog missing (4928a55)
74
+ - v36 bump and changelog generation on openai (37a0440)
75
+ - v36 bump and changelog generation on openai (a3b02d9)
76
+ - bump (a990058)
77
+ - status bar fix and new plugin for ttyd (27dde1e)
78
+ - file demo and tray (1a3e704)
79
+ - electron app (59ad0bb)
80
+ - compose and fops file plugin (1cf0e81)
81
+ - bump (346ffc1)
82
+ - localhost replaced by 127.0.0.1 (82b9f30)
83
+ - .29 (587b0e1)
84
+ - improve up down and bootstrap script (b79ebaf)
85
+ - checksum (22c8086)
86
+ - checksum (96b434f)
87
+ - checksum (15ed3c0)
88
+ - checksum (8a6543a)
89
+ - bump embed trino linksg (8440504)
90
+ - bump data (765ffd9)
91
+ - bump (cb8b232)
92
+ - broken tests (c532229)
93
+ - release 0.1.18, preflight checks (d902249)
94
+ - fix compute display bug (d10f5d9)
95
+ - cleanup packer files (6330f18)
96
+ - plan mode (cb36a8a)
97
+ - bump to 0.1.16 - agent ui (41ac1a2)
98
+ - bump to 0.1.15 - agent ui (4ebe2e1)
99
+ - bump to 0.1.14 (6c3a7fa)
100
+ - bump to 0.1.13 (8db570f)
101
+ - release 0.1.12 (c1c79e5)
102
+ - bump (11aa3b0)
103
+ - git keep and bump tui (be1678e)
104
+ - skills, index, rrf, compacted context (100k > 10k) (7b2fffd)
105
+ - cloudflare and token consumption, graphs indexing (0ad9eec)
106
+ - bump storage default (22c83ba)
107
+ - storage fix (68a22a0)
108
+ - skills update (7f56500)
109
+ - v9 bump (3864446)
110
+ - bump (c95eedc)
111
+ - rrf (dbf8c95)
112
+ - feat: warning when running predictions (95e8c52)
113
+ - feat: support for local predictions (45cf26b)
114
+ - feat: wip support for predictions + mlflow (3457052)
115
+ - add Reciprocal Rank Fusion (RRF) to knowledge and skill retrieval (61549bc)
116
+ - validate CSV headers in compute_run readiness check (a8c7a43)
117
+ - fix corrupted Iceberg metadata: probe tables + force cleanup on re-apply (50578af)
118
+ - enforce: never use foundation_apply to fix broken products (2e049bf)
119
+ - update SKILL.md with complete tool reference for knowledge retrieval (30b1924)
120
+ - add storage read, input DP table probe, and compute_run improvements (34e6c4c)
121
+ - skills update (1220385)
122
+ - skills update (bb66958)
123
+ - some tui improvement andd tools apply overwrite (e90c35c)
124
+ - skills update (e9227a1)
125
+ - skills update (669c4b3)
126
+ - fix plugin pre-flight checks (f741743)
127
+ - increase agent context (6479aaa)
128
+ - skills and init sql fixes (5fce35e)
129
+ - checksum (3518b56)
130
+ - penging job limit (a139861)
131
+ - checksum (575d28c)
132
+ - bump (92049ba)
133
+ - fix bug per tab status (0a33657)
134
+ - fix bug per tab status (50457c6)
135
+ - checksumming (0ad842e)
136
+ - shot af mardkwon overlapping (51f63b9)
137
+ - add spark dockerfile for multiarch builds (95abbd1)
138
+ - fix plugin initialization (16b9782)
139
+ - split index.js (50902a2)
140
+ - cloudflare cidr (cc4e021)
141
+ - cloduflare restrictions (2f6ba2d)
142
+ - sequential start (86b496e)
143
+ - sequential start (4930fe1)
144
+ - sequential start (353f014)
145
+ - qa tests (2dc6a1a)
146
+ - bump sha for .85 (dc2edfe)
147
+ - preserve env on sudo (7831227)
148
+ - bump sha for .84 (6c052f9)
149
+ - non interactive for azure vms (0aa8a2f)
150
+ - keep .env if present (d072450)
151
+ - bump (7a8e732)
152
+ - ensure opa is on compose if not set (f4a5228)
153
+ - checksum bump (a2ccc20)
154
+ - netrc defensive checks (a0b0ccc)
155
+ - netrc defensive checks (ae37403)
156
+ - checksum (ec45d11)
157
+ - update sync and fix up (7f9af72)
158
+ - expand test for azure and add new per app tag support (388a168)
159
+ - checksum on update (44005fc)
160
+ - cleanup for later (15e5313)
161
+ - cleanup for later (11c9597)
162
+ - switch branch feature (822fecc)
163
+ - add pull (d1c19ab)
164
+ - Bump hono from 4.11.9 to 4.12.0 in /operator-cli (ad25144)
165
+ - tests (f180a9a)
166
+ - cleanup (39c49a3)
167
+ - registry (7b7126a)
168
+ - reconcile kafka (832d0db)
169
+ - gh login bug (025886c)
170
+ - cleanup (bb96cab)
171
+ - strip envs from process (2421180)
172
+ - force use of gh creds not tokens in envs var (fff7787)
173
+ - resolve import between npm installs and npm link (79522e1)
174
+ - fix gh scope and azure states (afd846c)
175
+ - refactoring (da50352)
176
+ - split fops repo (d447638)
177
+ - aks (b791f8f)
178
+ - refactor azure (67d3bad)
179
+ - wildcard (391f023)
180
+ - azure plugin (c074074)
181
+ - zap (d7e6e7f)
182
+ - fix knock (cf89c05)
183
+ - azure (4adec98)
184
+ - Bump tar from 7.5.7 to 7.5.9 in /operator-cli (e41e98e)
185
+ - azure stack index.js split (de12272)
186
+ - Bump ajv from 8.17.1 to 8.18.0 in /operator-cli (76da21f)
187
+
188
+ ## [0.1.57] - 2026-03-26
189
+
190
+ - restore all missing services (pgpool, exporters, grafana, etc), add loki to k3s profile, always activate loki profile in fops up (4e2744a)
191
+ - fix: grafana alert-rules provisioning, ENVIRONMENT_NAME from --url, k3s secret sync, vm-sizes endpoint, project root resolution (9839052)
3
192
  - feat(azure): add 'fops azure reconcile <name>' command for VM drift fix (79ba6e2)
4
193
  - fix(otel,loki): remove duplicate spanmetrics dimensions, use .env for loki S3 creds (e3d1def)
5
194
  - fix(loki): pass S3 credentials from .env so loki works without vault-init (c57906d)
@@ -178,14 +367,12 @@
178
367
  - azure stack index.js split (de12272)
179
368
  - Bump ajv from 8.17.1 to 8.18.0 in /operator-cli (76da21f)
180
369
  - packer (9665fbc)
181
- - remove stack api (db0fd4d)
182
- - packer cleanup (fe1bf14)
183
370
 
184
371
  # Changelog
185
372
 
186
373
  All notable changes to @meshxdata/fops (Foundation Operator CLI) are documented here.
187
374
 
188
- ## [0.1.54] - 2026-03-26
375
+ ## [0.1.56] - 2026-03-26
189
376
 
190
377
  - feat(azure): add 'fops azure reconcile <name>' command for VM drift fix (79ba6e2)
191
378
  - fix(otel,loki): remove duplicate spanmetrics dimensions, use .env for loki S3 creds (e3d1def)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meshxdata/fops",
3
- "version": "0.1.55",
3
+ "version": "0.1.58",
4
4
  "description": "CLI to install and manage data mesh platforms",
5
5
  "keywords": [
6
6
  "fops",
@@ -17,7 +17,6 @@
17
17
  "fops.mjs",
18
18
  "src/",
19
19
  "!src/**/*.test.js",
20
- "!src/**/node_modules",
21
20
  "!scripts/",
22
21
  "README.md",
23
22
  "CHANGELOG.md"
@@ -7,6 +7,7 @@ import { registerPluginCommands } from "./plugin-cmd.js";
7
7
  import { registerIntegrationCommands } from "./integration-cmd.js";
8
8
  import { registerCompletionCommand } from "./completion.js";
9
9
  import { registerEditCommands } from "./edit-cmd.js";
10
+ import { registerK3sCommands } from "./k3s-cmd.js";
10
11
  import { configureColorHelp } from "./help.js";
11
12
 
12
13
  export function registerCommands(program, registry) {
@@ -43,5 +44,6 @@ export function registerCommands(program, registry) {
43
44
  registerPluginCommands(program, registry);
44
45
  registerIntegrationCommands(program, registry);
45
46
  registerEditCommands(program);
47
+ registerK3sCommands(program);
46
48
  registerCompletionCommand(program);
47
49
  }
@@ -0,0 +1,124 @@
1
+ import path from "node:path";
2
+ import chalk from "chalk";
3
+ import { requireRoot } from "../project.js";
4
+
5
+ const DIM = chalk.dim;
6
+ const OK = chalk.green;
7
+ const ERR = chalk.red;
8
+ const WARN = chalk.yellow;
9
+
10
+ const K3S_KUBECTL = ["exec", "-e", "KUBECONFIG=/etc/rancher/k3s/k3s.yaml", "k3s-server", "kubectl"];
11
+ const K3S_KUBECTL_I = ["exec", "-i", "-e", "KUBECONFIG=/etc/rancher/k3s/k3s.yaml", "k3s-server", "kubectl"];
12
+
13
+ async function kubectlApply(execa, args) {
14
+ const create = await execa("docker", [
15
+ ...K3S_KUBECTL, "create", ...args, "--dry-run=client", "-o", "yaml",
16
+ ], { timeout: 15000, reject: false });
17
+ if (create.exitCode !== 0) throw new Error(`kubectl create failed: ${create.stderr}`);
18
+ const apply = await execa("docker", [
19
+ ...K3S_KUBECTL_I, "apply", "-f", "-",
20
+ ], { input: create.stdout, timeout: 15000, reject: false });
21
+ if (apply.exitCode !== 0) throw new Error(`kubectl apply failed: ${apply.stderr}`);
22
+ }
23
+
24
+ async function syncSecrets(root) {
25
+ const { execa } = await import("execa");
26
+ const { loadEnvFromFile } = await import("../utils/load-env.js");
27
+
28
+ // Check k3s is running
29
+ const { exitCode, stdout } = await execa("docker", [
30
+ ...K3S_KUBECTL, "get", "nodes",
31
+ ], { timeout: 10000, reject: false });
32
+ if (exitCode !== 0 || !/Ready/.test(stdout)) {
33
+ console.error(ERR(" k3s cluster is not reachable. Is it running?"));
34
+ console.error(DIM(" Start it with: fops up --k3s"));
35
+ return;
36
+ }
37
+
38
+ // Load credentials from .env (same resolution as setup-kubernetes.sh)
39
+ const env = loadEnvFromFile(path.join(root, ".env"));
40
+ const s3Id = env.BOOTSTRAP_STORAGE_ACCESS_KEY || env.AUTH_IDENTITY || "minio";
41
+ const s3Pw = env.BOOTSTRAP_STORAGE_SECRET_KEY || env.AUTH_CREDENTIAL || "minio123";
42
+
43
+ console.log(DIM(` Storage credentials: ${s3Id} / ${"*".repeat(Math.min(s3Pw.length, 8))}`));
44
+
45
+ // 1. storage-secret in foundation namespace
46
+ console.log(DIM(" Creating storage-secret (foundation)..."));
47
+ await kubectlApply(execa, [
48
+ "secret", "generic", "storage-secret",
49
+ `--from-literal=ACCESS_KEY=${s3Id}`,
50
+ `--from-literal=SECRET_KEY=${s3Pw}`,
51
+ "--namespace=foundation",
52
+ ]);
53
+ console.log(OK(" ✓ storage-secret"));
54
+
55
+ // 2. foundation-storage-engine-auth in foundation namespace
56
+ console.log(DIM(" Creating foundation-storage-engine-auth (foundation)..."));
57
+ await kubectlApply(execa, [
58
+ "secret", "generic", "foundation-storage-engine-auth",
59
+ `--from-literal=AUTH_IDENTITY=${s3Id}`,
60
+ `--from-literal=AUTH_CREDENTIAL=${s3Pw}`,
61
+ "--namespace=foundation",
62
+ ]);
63
+ console.log(OK(" ✓ foundation-storage-engine-auth"));
64
+
65
+ // 3. Default storage system secret for Spark jobs
66
+ const sparkSecretName = "00000000-0000-0000-0000-000000000001-secret";
67
+ console.log(DIM(` Creating ${sparkSecretName} (spark-jobs)...`));
68
+ await kubectlApply(execa, [
69
+ "secret", "generic", sparkSecretName,
70
+ `--from-literal=AWS_ACCESS_KEY_ID=${s3Id}`,
71
+ `--from-literal=AWS_SECRET_ACCESS_KEY=${s3Pw}`,
72
+ `--from-literal=AWS_SECRET_ACCESS_KEY_ID=${s3Pw}`,
73
+ "--from-literal=AWS_ENDPOINT_LOCATION=http://foundation-storage-engine:8080",
74
+ "--from-literal=AWS_REGION=me-central-1",
75
+ "--from-literal=MLFLOW_S3_ENDPOINT_URL=http://foundation-storage-engine:8080",
76
+ "--namespace=spark-jobs",
77
+ ]);
78
+
79
+ // Re-apply label
80
+ await execa("docker", [
81
+ ...K3S_KUBECTL, "label", "secret", sparkSecretName,
82
+ "foundation.io/data-system=storage",
83
+ "--namespace=spark-jobs", "--overwrite",
84
+ ], { timeout: 10000, reject: false });
85
+ console.log(OK(` ✓ ${sparkSecretName} (with label)`));
86
+
87
+ console.log(OK("\n ✓ All storage secrets synced to k3s"));
88
+ }
89
+
90
+ export function registerK3sCommands(program) {
91
+ const k3s = program
92
+ .command("k3s")
93
+ .description("Manage local k3s Kubernetes cluster");
94
+
95
+ k3s
96
+ .command("sync-secrets")
97
+ .description("Sync storage secrets from .env into k3s (fixes S3 AccessDenied)")
98
+ .action(async () => {
99
+ const root = requireRoot(program);
100
+ try {
101
+ await syncSecrets(root);
102
+ } catch (err) {
103
+ console.error(ERR(` ✗ ${err.message}`));
104
+ process.exitCode = 1;
105
+ }
106
+ });
107
+
108
+ k3s
109
+ .command("exec [cmd...]")
110
+ .description("Run a command inside k3s (default: interactive shell)")
111
+ .option("-n, --namespace <ns>", "Kubernetes namespace", "spark-jobs")
112
+ .action(async (cmd, opts) => {
113
+ const { execaNode } = await import("execa");
114
+ const { execSync } = await import("node:child_process");
115
+ const args = cmd.length
116
+ ? [...K3S_KUBECTL, "-n", opts.namespace, ...cmd]
117
+ : ["exec", "-it", "-e", "KUBECONFIG=/etc/rancher/k3s/k3s.yaml", "k3s-server", "/bin/sh"];
118
+ try {
119
+ execSync(`docker ${args.map(a => a.includes(" ") ? `"${a}"` : a).join(" ")}`, { stdio: "inherit" });
120
+ } catch (err) {
121
+ if (err.status) process.exitCode = err.status;
122
+ }
123
+ });
124
+ }
@@ -562,6 +562,12 @@ async function runUp(program, registry, opts) {
562
562
  fs.writeFileSync(envPath, envContent);
563
563
  console.log(chalk.dim(` FOUNDATION_PUBLIC_URL=${publicUrl} written to .env`));
564
564
  }
565
+ // Derive and set ENVIRONMENT_NAME from URL (e.g. https://staging.meshx.app → Staging)
566
+ const envName = publicUrl.replace(/https?:\/\//, "").split(".")[0] || "Local";
567
+ const environmentName = envName.charAt(0).toUpperCase() + envName.slice(1);
568
+ envContent = envContent.replace(/^ENVIRONMENT_NAME=.*\n?/m, "");
569
+ envContent = envContent.trimEnd() + `\nENVIRONMENT_NAME=${environmentName}\n`;
570
+ fs.writeFileSync(envPath, envContent);
565
571
  } else {
566
572
  // Local: only inject localhost fallback if not already set
567
573
  if (!/^FOUNDATION_PUBLIC_URL=/m.test(envContent)) {
@@ -577,6 +583,7 @@ async function runUp(program, registry, opts) {
577
583
  const envProfiles = (process.env.COMPOSE_PROFILES || "").split(",").map(s => s.trim()).filter(Boolean);
578
584
  const activeProfiles = new Set(envProfiles);
579
585
  if (opts.k3s) activeProfiles.add("k3s");
586
+ activeProfiles.add("loki");
580
587
  if (publicUrl) {
581
588
  if (opts.traefik) activeProfiles.add("traefik");
582
589
  try {
@@ -486,60 +486,42 @@ You manage Docker Compose stacks: inspect containers, read logs, restart service
486
486
  ## Role
487
487
  You investigate alerts, diagnose service failures, and suggest fixes. You have direct access to Docker containers, logs, and system metrics. You are called by the Glue bot when monitoring alerts fire.
488
488
 
489
- ## CRITICAL: Always Use Tools — Never Guess
490
- You MUST use your tools to investigate. NEVER give generic checklists or ask the user to check things manually.
491
- - Don't say "check the logs for X" — run compose_logs and find X yourself.
492
- - Don't say "verify auth config" — run compose_inspect or compose_exec to read the actual config.
493
- - Don't say "correlate with metrics" — run compose_stats and report the numbers.
494
- - If you need to grep logs, use compose_exec with grep/jq inside the container.
495
- - Every finding in your response must be backed by tool output, not speculation.
496
-
497
489
  ## Tools Available
498
490
  - **compose_ps**: List all containers and their status (start here)
499
- - **compose_logs**: Read container logs (check for errors, crashes, OOM). Use the tail parameter to get recent logs, and grep for specific patterns.
491
+ - **compose_logs**: Read container logs (check for errors, crashes, OOM)
500
492
  - **compose_inspect**: Get container details (health checks, env vars, mounts, restarts)
501
493
  - **compose_stats**: CPU/memory/network usage per container
502
- - **compose_exec**: Run commands inside containers (e.g. grep logs, check disk, curl endpoints, read config files, test connectivity)
494
+ - **compose_exec**: Run commands inside containers (e.g. check disk, network, processes)
503
495
  - **compose_images**: List images and versions
504
- - **compose_restart**: Restart specific services (only after diagnosing the issue)
496
+ - **compose_restart**: Restart specific services
505
497
  - **embeddings_search**: Search docs, configs, and past knowledge for context
506
498
 
507
499
  ## Investigation Approach
508
500
  1. **Triage**: Run compose_ps to see overall stack health. Identify unhealthy/restarting containers.
509
- 2. **Deep dive**: For each affected container, use MULTIPLE tools:
510
- - compose_logs with tail=200 to find errors, then grep for specific patterns (4xx, 5xx, OOM, connection refused, timeout)
511
- - compose_exec to grep logs for specific status codes: e.g. grep -c "HTTP/1.1 4" or check config files
512
- - compose_inspect for health check failures, restart count, resource limits, env vars
513
- - compose_stats for CPU/memory report actual numbers (e.g. "backend: 450MB/512MB, 85% memory")
514
- 3. **Correlate**: If you see errors, trace them to the root service. Check dependencies (postgres, kafka, storage-engine).
515
- 4. **Root cause**: State the specific cause with evidence from tool output.
516
- 5. **Fix**: Take action if safe (restart a crashed container) or give a specific command to run.
501
+ 2. **Diagnose**: For each affected container:
502
+ - compose_logs to find errors, exceptions, OOM kills, crash traces
503
+ - compose_inspect for health check failures, restart count, resource limits
504
+ - compose_stats for CPU/memory spikes
505
+ 3. **Context**: Use embeddings_search to find relevant docs or known issues.
506
+ 4. **Root cause**: Correlate findings is it a code bug, resource exhaustion, dependency failure, config issue?
507
+ 5. **Fix**: Suggest specific actions (restart, config change, scale, rollback).
517
508
 
518
509
  ## Output Format
519
- Structure your response with blank lines between each section:
520
-
521
- **Status:** One-line summary (e.g. "Processor container restarting due to OOM")
522
-
523
- **Findings:** Specific evidence from tools (include actual log lines, numbers, status codes)
524
-
525
- **Root Cause:** Most likely cause, backed by evidence
526
-
527
- **Actions:** Specific steps to fix (commands, not vague suggestions)
528
-
529
- **Prevention:** How to avoid this in the future
510
+ Structure your response as:
511
+ - **Status**: One-line summary (e.g. "Processor container restarting due to OOM")
512
+ - **Findings**: What you discovered from each tool
513
+ - **Root Cause**: Most likely cause
514
+ - **Actions**: Specific steps to fix
515
+ - **Prevention**: How to avoid this in the future
530
516
 
531
517
  ## Rules
532
518
  - Always check compose_ps first.
533
- - USE TOOLS AGGRESSIVELY. Run 5-10 tool calls per investigation, not 1-2.
534
519
  - Check logs BEFORE suggesting restarts.
535
- - When investigating HTTP errors: grep the actual logs for status codes and show the top error endpoints.
536
- - When investigating performance: show actual CPU/memory numbers from compose_stats.
537
520
  - Look for patterns: repeated restarts, OOM kills, connection refused, timeout errors.
538
521
  - If a dependency is down (postgres, kafka), flag it — fixing the dependency fixes the dependent.
539
522
  - Be concise — this output goes into a Glue chat thread.
540
523
  - Never suggest 'docker compose down' — prefer targeted restarts.
541
- - After restarting, verify with compose_ps.
542
- - IMPORTANT: Always put a blank line between sections in your response so they render as separate paragraphs.`,
524
+ - After restarting, verify with compose_ps.`,
543
525
  });
544
526
 
545
527
  // ── Doctor check: Trivy ───────────────────────────────────────────
@@ -55,9 +55,6 @@ async function ensureAzAuth(execa, { subscription } = {}) {
55
55
  /** Resolve Foundation project root (directory with docker-compose). Exported for use by azure agent sync. */
56
56
  export function findProjectRoot() {
57
57
  const cwd = process.cwd();
58
- if (process.env.FOUNDATION_ROOT && fs.existsSync(process.env.FOUNDATION_ROOT)) {
59
- return path.resolve(process.env.FOUNDATION_ROOT);
60
- }
61
58
  try {
62
59
  const cfgPath = path.join(process.env.HOME || process.env.USERPROFILE || "", ".fops.json");
63
60
  if (fs.existsSync(cfgPath)) {
@@ -263,7 +263,7 @@ export function registerVmCommands(azure, api, registry) {
263
263
  if (opts.dai) opts.k3s = true;
264
264
  const {
265
265
  lazyExeca, ensureAzCli, ensureAzAuth, resolveGithubToken, verifyGithubToken,
266
- reconcileVm, DEFAULTS,
266
+ reconcileVm, DEFAULTS, buildDefaultUrl,
267
267
  } = await import("../azure.js");
268
268
  const { resolveCfToken } = await import("../cloudflare.js");
269
269
  const { readVmState, writeVmState } = await import("../azure-state.js");
@@ -279,7 +279,10 @@ export function registerVmCommands(azure, api, registry) {
279
279
  process.exit(1);
280
280
  }
281
281
  const rg = tracked.resourceGroup;
282
- const desiredUrl = opts.url || tracked.publicUrl;
282
+ const storedUrl = tracked.publicUrl;
283
+ // If the stored URL is IP-based, prefer the default domain-based URL
284
+ const isIpUrl = storedUrl && /^https?:\/\/\d+\.\d+\.\d+\.\d+/.test(storedUrl);
285
+ const desiredUrl = opts.url || (isIpUrl ? buildDefaultUrl(name) : storedUrl);
283
286
  const cfToken = resolveCfToken(opts.cfToken);
284
287
  const { publicIp, publicUrl, rg: actualRg } = await reconcileVm(execa, {
285
288
  vmName: name, rg, sub, subId, location: tracked.location || DEFAULTS.location,
@@ -333,6 +333,20 @@ export function createCloudApi(registry) {
333
333
  return c.json(providers);
334
334
  });
335
335
 
336
+ // ── VM Sizes ────────────────────────────────────────────
337
+
338
+ app.get("/vm-sizes", (c) => {
339
+ return c.json([
340
+ "Standard_D4s_v5",
341
+ "Standard_D8s_v5",
342
+ "Standard_D16s_v5",
343
+ "Standard_D32s_v5",
344
+ "Standard_D48s_v5",
345
+ "Standard_D64s_v5",
346
+ "Standard_D96s_v5",
347
+ ]);
348
+ });
349
+
336
350
  // ── Resources ───────────────────────────────────────────
337
351
 
338
352
  app.get("/resources", async (c) => {
package/src/project.js CHANGED
@@ -32,8 +32,8 @@ function saveProjectRoot(root) {
32
32
  const configPath = path.join(os.homedir(), ".fops.json");
33
33
  let config = {};
34
34
  try { config = JSON.parse(fs.readFileSync(configPath, "utf8")); } catch {}
35
- if (config.projectRoot === root) return; // already saved
36
- config.projectRoot = root;
35
+ if (config.foundationRoot === root || config.projectRoot === root) return; // already saved
36
+ config.foundationRoot = root;
37
37
  try {
38
38
  fs.mkdirSync(path.dirname(configPath), { recursive: true });
39
39
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
@@ -41,14 +41,19 @@ function saveProjectRoot(root) {
41
41
  }
42
42
 
43
43
  export function rootDir(cwd = process.cwd()) {
44
+
45
+ // Check FOUNDATION_ROOT env var first (explicit override)
44
46
  const envRoot = process.env.FOUNDATION_ROOT;
45
- if (envRoot && fs.existsSync(envRoot)) return path.resolve(envRoot);
47
+ if (envRoot && isFoundationRoot(envRoot)) {
48
+ return path.resolve(envRoot);
49
+ }
46
50
 
47
- // Check ~/.fops.json for saved project root
51
+ // Check ~/.fops.json for saved project root (projectRoot or foundationRoot)
48
52
  try {
49
53
  const fopsConfig = JSON.parse(fs.readFileSync(path.join(os.homedir(), ".fops.json"), "utf8"));
50
- if (fopsConfig.projectRoot && isFoundationRoot(fopsConfig.projectRoot)) {
51
- return path.resolve(fopsConfig.projectRoot);
54
+ const configRoot = fopsConfig.foundationRoot || fopsConfig.projectRoot;
55
+ if (configRoot && isFoundationRoot(configRoot)) {
56
+ return path.resolve(configRoot);
52
57
  }
53
58
  } catch {}
54
59
 
@@ -104,7 +109,7 @@ export function requireRoot(program) {
104
109
  console.error(
105
110
  chalk.red("Not a Foundation project (no docker-compose + Makefile).")
106
111
  );
107
- console.error(chalk.dim(" Run `fops init` to set up, or set FOUNDATION_ROOT."));
112
+ console.error(chalk.dim(" Run `fops init` to set up, or run from the foundation-compose directory."));
108
113
  program.error("", { exitCode: 1 });
109
114
  }
110
115
  return r;
@@ -1,5 +0,0 @@
1
- module.exports = {
2
- plugins: {
3
- "@tailwindcss/postcss": {},
4
- },
5
- };
@@ -1,32 +0,0 @@
1
- import React, { useEffect } from "react";
2
- import { Routes, Route, useLocation } from "react-router-dom";
3
-
4
- import "./css/style.css";
5
-
6
- import Resources from "./pages/Resources";
7
- import CreateResource from "./pages/CreateResource";
8
- import Fleet from "./pages/Fleet";
9
- import Costs from "./pages/Costs";
10
- import Audit from "./pages/Audit";
11
-
12
- function App() {
13
- const location = useLocation();
14
-
15
- useEffect(() => {
16
- document.querySelector("html").style.scrollBehavior = "auto";
17
- window.scroll({ top: 0 });
18
- document.querySelector("html").style.scrollBehavior = "";
19
- }, [location.pathname]);
20
-
21
- return (
22
- <Routes>
23
- <Route exact path="/" element={<Resources />} />
24
- <Route path="/resources/new" element={<CreateResource />} />
25
- <Route path="/fleet" element={<Fleet />} />
26
- <Route path="/costs" element={<Costs />} />
27
- <Route path="/audit" element={<Audit />} />
28
- </Routes>
29
- );
30
- }
31
-
32
- export default App;