@meshxdata/fops 0.1.37 → 0.1.39

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 (51) hide show
  1. package/CHANGELOG.md +374 -0
  2. package/package.json +1 -1
  3. package/src/agent/llm.js +2 -0
  4. package/src/auth/azure.js +92 -0
  5. package/src/auth/cloudflare.js +125 -0
  6. package/src/auth/index.js +2 -0
  7. package/src/commands/index.js +8 -4
  8. package/src/commands/lifecycle.js +31 -10
  9. package/src/doctor.js +27 -5
  10. package/src/plugins/bundled/fops-plugin-1password/index.js +13 -1
  11. package/src/plugins/bundled/fops-plugin-azure/index.js +4 -2
  12. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks.js +130 -2
  13. package/src/plugins/bundled/fops-plugin-azure/lib/azure-auth.js +66 -6
  14. package/src/plugins/bundled/fops-plugin-azure/lib/azure-helpers.js +64 -2
  15. package/src/plugins/bundled/fops-plugin-azure/lib/azure-ops.js +36 -28
  16. package/src/plugins/bundled/fops-plugin-azure/lib/azure-provision.js +66 -26
  17. package/src/plugins/bundled/fops-plugin-azure/lib/azure-shared-cache.js +1 -1
  18. package/src/plugins/bundled/fops-plugin-azure/lib/azure-sync.js +4 -4
  19. package/src/plugins/bundled/fops-plugin-azure/lib/commands/infra-cmds.js +4 -0
  20. package/src/plugins/bundled/fops-plugin-azure/lib/commands/test-cmds.js +22 -0
  21. package/src/plugins/bundled/fops-plugin-azure/lib/commands/vm-cmds.js +4 -3
  22. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/apps/dai-backend.yaml +13 -0
  23. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/apps/dai-frontend.yaml +13 -0
  24. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/apps/foundation-backend.yaml +13 -0
  25. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/apps/foundation-frontend.yaml +13 -0
  26. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/apps/foundation-hive.yaml +13 -0
  27. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/apps/foundation-kafka.yaml +13 -0
  28. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/apps/foundation-meltano.yaml +13 -0
  29. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/apps/foundation-mlflow.yaml +13 -0
  30. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/apps/foundation-opa.yaml +13 -0
  31. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/apps/foundation-processor.yaml +13 -0
  32. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/apps/foundation-scheduler.yaml +13 -0
  33. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/apps/foundation-storage-engine.yaml +13 -0
  34. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/apps/foundation-trino.yaml +13 -0
  35. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/apps/foundation-watcher.yaml +13 -0
  36. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/config/repository.yaml +66 -0
  37. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/kustomization.yaml +30 -0
  38. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/operator/acr-webhook-controller.yaml +63 -0
  39. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/operator/externalsecrets.yaml +15 -0
  40. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/operator/istio.yaml +42 -0
  41. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/operator/kafka.yaml +15 -0
  42. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/operator/kube-reflector.yaml +33 -0
  43. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/operator/kubecost.yaml +12 -0
  44. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/operator/nats-server.yaml +15 -0
  45. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/operator/prometheus-agent.yaml +34 -0
  46. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/operator/reloader.yaml +12 -0
  47. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/operator/spark.yaml +112 -0
  48. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/operator/tailscale.yaml +67 -0
  49. package/src/plugins/bundled/fops-plugin-azure/templates/cluster/operator/vertical-pod-autoscaler.yaml +15 -0
  50. package/src/plugins/bundled/fops-plugin-foundation/index.js +44 -7
  51. package/src/plugins/loader.js +23 -6
@@ -0,0 +1,63 @@
1
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
2
+ kind: Kustomization
3
+ metadata:
4
+ name: acr-webhook-controller
5
+ namespace: flux-system
6
+ spec:
7
+ interval: 1m0s
8
+ sourceRef:
9
+ kind: GitRepository
10
+ name: flux-system
11
+ path: ./infrastructure/acr-webhook-controller
12
+ prune: false
13
+ patches:
14
+ - target:
15
+ kind: Secret
16
+ name: acr-pull-secret
17
+ namespace: acr-cache-system
18
+ patch: |-
19
+ - op: replace
20
+ path: /stringData/.dockerconfigjson
21
+ value: |
22
+ {
23
+ "auths": {
24
+ "meshxregistry.azurecr.io": {
25
+ "username": "{{ACR_USERNAME}}",
26
+ "password": "{{ACR_PASSWORD}}"
27
+ }
28
+ }
29
+ }
30
+ - target:
31
+ kind: Secret
32
+ name: meshxregistry-helm-secret
33
+ namespace: acr-cache-system
34
+ patch: |-
35
+ - op: replace
36
+ path: /stringData/username
37
+ value: "{{ACR_USERNAME}}"
38
+ - op: replace
39
+ path: /stringData/password
40
+ value: "{{ACR_PASSWORD}}"
41
+ - target:
42
+ kind: Deployment
43
+ name: acr-cache-webhook
44
+ namespace: acr-cache-system
45
+ patch: |
46
+ apiVersion: apps/v1
47
+ kind: Deployment
48
+ metadata:
49
+ name: acr-cache-webhook
50
+ spec:
51
+ replicas: 3
52
+ template:
53
+ spec:
54
+ affinity:
55
+ podAntiAffinity:
56
+ requiredDuringSchedulingIgnoredDuringExecution:
57
+ - labelSelector:
58
+ matchExpressions:
59
+ - key: app
60
+ operator: In
61
+ values:
62
+ - acr-cache-webhook
63
+ topologyKey: kubernetes.io/hostname
@@ -0,0 +1,15 @@
1
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
2
+ kind: Kustomization
3
+ metadata:
4
+ name: external-secrets
5
+ namespace: flux-system
6
+ spec:
7
+ interval: 1h
8
+ sourceRef:
9
+ kind: GitRepository
10
+ name: flux-system
11
+ path: ./infrastructure/external-secrets/manifests
12
+ prune: false
13
+ dependsOn:
14
+ - name: acr-webhook-controller
15
+ namespace: flux-system
@@ -0,0 +1,42 @@
1
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
2
+ kind: Kustomization
3
+ metadata:
4
+ name: istio-operator
5
+ namespace: flux-system
6
+ spec:
7
+ interval: 1h
8
+ sourceRef:
9
+ kind: GitRepository
10
+ name: flux-system
11
+ path: ./infrastructure/istio-operator
12
+ prune: false
13
+ ---
14
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
15
+ kind: Kustomization
16
+ metadata:
17
+ name: istio-controlplane
18
+ namespace: flux-system
19
+ spec:
20
+ dependsOn:
21
+ - name: istio-operator
22
+ interval: 10s
23
+ sourceRef:
24
+ kind: GitRepository
25
+ name: flux-system
26
+ path: ./infrastructure/istio-tenant/demo/azure/uaenorth
27
+ prune: false
28
+ ---
29
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
30
+ kind: Kustomization
31
+ metadata:
32
+ name: jaeger
33
+ namespace: flux-system
34
+ spec:
35
+ dependsOn:
36
+ - name: istio-operator
37
+ interval: 10m0s
38
+ sourceRef:
39
+ kind: GitRepository
40
+ name: flux-system
41
+ path: ./infrastructure/jaeger
42
+ prune: false
@@ -0,0 +1,15 @@
1
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
2
+ kind: Kustomization
3
+ metadata:
4
+ name: kafka-operator
5
+ namespace: flux-system
6
+ spec:
7
+ interval: 1m0s
8
+ sourceRef:
9
+ kind: GitRepository
10
+ name: flux-system
11
+ path: ./infrastructure/kafka-operator/velora
12
+ prune: false
13
+ dependsOn:
14
+ - name: acr-webhook-controller
15
+ namespace: flux-system
@@ -0,0 +1,33 @@
1
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
2
+ kind: Kustomization
3
+ metadata:
4
+ name: kube-reflector
5
+ namespace: flux-system
6
+ spec:
7
+ interval: 1m0s
8
+ sourceRef:
9
+ kind: GitRepository
10
+ name: flux-system
11
+ path: ./infrastructure/reflector
12
+ prune: false
13
+ targetNamespace: reflector
14
+ dependsOn:
15
+ - name: acr-webhook-controller
16
+ namespace: flux-system
17
+ patches:
18
+ - target:
19
+ kind: Secret
20
+ name: acr-pull-secret
21
+ namespace: reflector
22
+ patch: |-
23
+ - op: replace
24
+ path: /stringData/.dockerconfigjson
25
+ value: |
26
+ {
27
+ "auths": {
28
+ "meshxregistry.azurecr.io": {
29
+ "username": "{{ACR_USERNAME}}",
30
+ "password": "{{ACR_PASSWORD}}"
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,12 @@
1
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
2
+ kind: Kustomization
3
+ metadata:
4
+ name: kubecost
5
+ namespace: flux-system
6
+ spec:
7
+ interval: 1m0s
8
+ sourceRef:
9
+ kind: GitRepository
10
+ name: flux-system
11
+ path: ./infrastructure/kubecost/{{CLUSTER_NAME}}
12
+ prune: false
@@ -0,0 +1,15 @@
1
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
2
+ kind: Kustomization
3
+ metadata:
4
+ name: nats-server
5
+ namespace: flux-system
6
+ spec:
7
+ interval: 1m0s
8
+ sourceRef:
9
+ kind: GitRepository
10
+ name: flux-system
11
+ path: ./infrastructure/nats-server/velora
12
+ prune: false
13
+ dependsOn:
14
+ - name: acr-webhook-controller
15
+ namespace: flux-system
@@ -0,0 +1,34 @@
1
+ ---
2
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
3
+ kind: Kustomization
4
+ metadata:
5
+ name: prometheus-agent
6
+ namespace: flux-system
7
+ spec:
8
+ interval: 1m
9
+ sourceRef:
10
+ kind: GitRepository
11
+ name: flux-system
12
+ path: ./infrastructure/prometheus-agent
13
+ prune: false
14
+ patches:
15
+ - target:
16
+ kind: ConfigMap
17
+ name: prometheus-agent-server
18
+ patch: |
19
+ apiVersion: v1
20
+ kind: ConfigMap
21
+ metadata:
22
+ name: prometheus-agent-server
23
+ namespace: monitoring
24
+ data:
25
+ prometheus.yml: |
26
+ global:
27
+ scrape_interval: 15s
28
+ evaluation_interval: 30s
29
+ scrape_configs:
30
+ - job_name: "{{CLUSTER_NAME}}-nats-exporter"
31
+ static_configs:
32
+ - targets: ["nats.nats.svc.cluster.local:7777"]
33
+ remote_write:
34
+ - url: "http://central-live-prometheus.privatelink.uaenorth.azmk8s.io/api/v1/write"
@@ -0,0 +1,12 @@
1
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
2
+ kind: Kustomization
3
+ metadata:
4
+ name: reloader
5
+ namespace: flux-system
6
+ spec:
7
+ interval: 1m0s
8
+ sourceRef:
9
+ kind: GitRepository
10
+ name: flux-system
11
+ path: ./infrastructure/reloader
12
+ prune: false
@@ -0,0 +1,112 @@
1
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
2
+ kind: Kustomization
3
+ metadata:
4
+ name: spark
5
+ namespace: flux-system
6
+ spec:
7
+ interval: 1m0s
8
+ sourceRef:
9
+ kind: GitRepository
10
+ name: flux-system
11
+ path: ./infrastructure/spark-operator/launchpad
12
+ prune: false
13
+ dependsOn:
14
+ - name: velora
15
+ namespace: flux-system
16
+ - name: acr-webhook-controller
17
+ namespace: flux-system
18
+ patches:
19
+ - target:
20
+ kind: HelmRelease
21
+ name: spark-operator
22
+ namespace: flux-system
23
+ patch: |-
24
+ apiVersion: helm.toolkit.fluxcd.io/v2
25
+ kind: HelmRelease
26
+ metadata:
27
+ name: spark-operator
28
+ namespace: flux-system
29
+ spec:
30
+ values:
31
+ image:
32
+ pullSecrets:
33
+ - name: ecr-credentials
34
+ spark:
35
+ jobNamespaces:
36
+ - velora
37
+ ---
38
+ apiVersion: rbac.authorization.k8s.io/v1
39
+ kind: ClusterRole
40
+ metadata:
41
+ name: job-creator
42
+ namespace: velora
43
+ rules:
44
+ - apiGroups: [""]
45
+ resources: ["configmaps"]
46
+ verbs: ["create", "delete", "get"]
47
+ - apiGroups: [""]
48
+ resources: ["pods"]
49
+ verbs: ["get"]
50
+ - apiGroups: [""]
51
+ resources: ["pods/log"]
52
+ verbs: ["get"]
53
+ - apiGroups: [""]
54
+ resources: ["pods/status"]
55
+ verbs: ["get", "list", "watch"]
56
+ - apiGroups: ["sparkoperator.k8s.io"]
57
+ resources: ["sparkapplications"]
58
+ verbs: ["create", "delete", "get"]
59
+ - apiGroups: ["sparkoperator.k8s.io"]
60
+ resources: ["scheduledsparkapplications"]
61
+ verbs: ["create", "delete", "get", "patch"]
62
+ - apiGroups: [""]
63
+ resources: ["secrets"]
64
+ verbs: ["create", "delete", "get", "patch", "update"]
65
+ ---
66
+ apiVersion: rbac.authorization.k8s.io/v1
67
+ kind: RoleBinding
68
+ metadata:
69
+ name: create-jobs
70
+ namespace: velora
71
+ subjects:
72
+ - kind: Group
73
+ name: system:serviceaccounts:foundation
74
+ apiGroup: rbac.authorization.k8s.io
75
+ roleRef:
76
+ kind: ClusterRole
77
+ name: job-creator
78
+ apiGroup: rbac.authorization.k8s.io
79
+ ---
80
+ apiVersion: rbac.authorization.k8s.io/v1
81
+ kind: RoleBinding
82
+ metadata:
83
+ name: spark
84
+ namespace: velora
85
+ subjects:
86
+ - kind: Group
87
+ name: system:serviceaccounts:foundation
88
+ roleRef:
89
+ kind: ClusterRole
90
+ name: job-creator
91
+ apiGroup: rbac.authorization.k8s.io
92
+ ---
93
+ apiVersion: rbac.authorization.k8s.io/v1
94
+ kind: RoleBinding
95
+ metadata:
96
+ name: allow-velora-sas-in-spark-jobs
97
+ namespace: spark-jobs
98
+ subjects:
99
+ - kind: Group
100
+ name: system:serviceaccounts:velora
101
+ apiGroup: rbac.authorization.k8s.io
102
+ roleRef:
103
+ kind: ClusterRole
104
+ name: job-creator
105
+ apiGroup: rbac.authorization.k8s.io
106
+ ---
107
+ apiVersion: v1
108
+ kind: Namespace
109
+ metadata:
110
+ name: spark-jobs
111
+ labels:
112
+ name: spark-jobs
@@ -0,0 +1,67 @@
1
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
2
+ kind: Kustomization
3
+ metadata:
4
+ name: tailscale-operator
5
+ namespace: flux-system
6
+ spec:
7
+ interval: 1m0s
8
+ sourceRef:
9
+ kind: GitRepository
10
+ name: flux-system
11
+ path: ./infrastructure/tailscale/operator
12
+ prune: false
13
+ dependsOn:
14
+ - name: acr-webhook-controller
15
+ namespace: flux-system
16
+ patches:
17
+ - target:
18
+ kind: HelmRelease
19
+ name: tailscale-operator
20
+ namespace: flux-system
21
+ patch: |-
22
+ apiVersion: helm.toolkit.fluxcd.io/v2
23
+ kind: HelmRelease
24
+ metadata:
25
+ name: tailscale-operator
26
+ namespace: flux-system
27
+ spec:
28
+ values:
29
+ oauth:
30
+ clientId: "{{TAILSCALE_CLIENT_ID}}"
31
+ clientSecret: "{{TAILSCALE_CLIENT_SECRET}}"
32
+ operatorConfig:
33
+ hostname: "{{CLUSTER_NAME}}"
34
+ ---
35
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
36
+ kind: Kustomization
37
+ metadata:
38
+ name: tailscale-connector
39
+ namespace: flux-system
40
+ spec:
41
+ interval: 1m0s
42
+ sourceRef:
43
+ kind: GitRepository
44
+ name: flux-system
45
+ path: ./infrastructure/tailscale/connector
46
+ prune: false
47
+ dependsOn:
48
+ - name: tailscale-operator
49
+ patches:
50
+ - target:
51
+ kind: Connector
52
+ name: default-connector
53
+ namespace: tailscale
54
+ patch: |-
55
+ - op: replace
56
+ path: /metadata/name
57
+ value: "{{CLUSTER_NAME}}"
58
+ - op: replace
59
+ path: /spec/hostname
60
+ value: "{{CLUSTER_NAME}}"
61
+ - op: replace
62
+ path: /spec/exitNode
63
+ value: true
64
+ - op: replace
65
+ path: /spec/subnetRouter/advertiseRoutes
66
+ value:
67
+ - 10.50.0.0/16
@@ -0,0 +1,15 @@
1
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
2
+ kind: Kustomization
3
+ metadata:
4
+ name: vertical-pod-autoscaler
5
+ namespace: flux-system
6
+ spec:
7
+ interval: 1m0s
8
+ sourceRef:
9
+ kind: GitRepository
10
+ name: flux-system
11
+ path: ./infrastructure/vertical-pod-autoscaler/overlays/{{OVERLAY}}
12
+ prune: false
13
+ dependsOn:
14
+ - name: acr-webhook-controller
15
+ namespace: flux-system
@@ -986,23 +986,44 @@ app.run()
986
986
  .option("--url <url>", "Override the backend API URL")
987
987
  .action(async (opts) => {
988
988
  const { spawn } = await import("node:child_process");
989
- const { writeFileSync, existsSync, realpathSync, readFileSync } = await import("node:fs");
989
+ const { writeFileSync, existsSync, realpathSync, readFileSync, unlinkSync, mkdirSync } = await import("node:fs");
990
990
  const { tmpdir, homedir } = await import("node:os");
991
991
  const { join, dirname } = await import("node:path");
992
992
  const { findComposeRoot } = await import("./lib/tools-write.js");
993
993
 
994
+ // ── Singleton: kill any existing tray process ──────────────────────────
995
+ const pidDir = join(homedir(), ".fops");
996
+ const pidFile = join(pidDir, "tray.pid");
997
+ if (existsSync(pidFile)) {
998
+ try {
999
+ const oldPid = parseInt(readFileSync(pidFile, "utf8").trim(), 10);
1000
+ if (oldPid) process.kill(oldPid, "SIGTERM");
1001
+ } catch {
1002
+ // process already gone — ignore
1003
+ }
1004
+ try { unlinkSync(pidFile); } catch {}
1005
+ }
1006
+
994
1007
  const composeRoot = program._fopsRoot || findComposeRoot() || "";
995
1008
 
996
1009
  let apiUrl = opts.url || process.env.FOPS_API_URL || "http://127.0.0.1:9001";
997
1010
 
998
- // Current fops version
1011
+ // Current fops version — resolve from the running binary's location
999
1012
  let fopsVersion = "0.0.0";
1000
1013
  try {
1001
- const pluginsNodeModules2 = join(homedir(), ".fops", "plugins", "node_modules");
1002
- const fopsRoot2 = dirname(realpathSync(pluginsNodeModules2));
1014
+ // process.argv[1] is the path to fops.mjs; package.json sits next to it
1015
+ const fopsRoot2 = dirname(realpathSync(process.argv[1]));
1003
1016
  const pkgJson = JSON.parse(readFileSync(join(fopsRoot2, "package.json"), "utf8"));
1004
1017
  fopsVersion = pkgJson.version || "0.0.0";
1005
- } catch { /* fallback */ }
1018
+ } catch {
1019
+ // Fallback: try the plugins node_modules path
1020
+ try {
1021
+ const pluginsNodeModules2 = join(homedir(), ".fops", "plugins", "node_modules");
1022
+ const fopsRoot2 = dirname(realpathSync(pluginsNodeModules2));
1023
+ const pkgJson = JSON.parse(readFileSync(join(fopsRoot2, "package.json"), "utf8"));
1024
+ fopsVersion = pkgJson.version || "0.0.0";
1025
+ } catch { /* stay at 0.0.0 */ }
1026
+ }
1006
1027
 
1007
1028
  // Resolve icon
1008
1029
  let iconPath = "";
@@ -1227,6 +1248,8 @@ $tray.Visible = $false
1227
1248
  env: trayEnv,
1228
1249
  windowsHide: true,
1229
1250
  });
1251
+ if (!existsSync(pidDir)) mkdirSync(pidDir, { recursive: true });
1252
+ writeFileSync(pidFile, String(winChild.pid));
1230
1253
  winChild.unref();
1231
1254
  return;
1232
1255
  }
@@ -1449,6 +1472,8 @@ menu.addItem(NSMenuItem.separator())
1449
1472
  let updateItem = NSMenuItem(title: "", action: #selector(AppDelegate.runUpdate), keyEquivalent: "")
1450
1473
  updateItem.isHidden = true
1451
1474
  menu.addItem(updateItem)
1475
+ let checkUpdateItem = NSMenuItem(title: "Check for updates", action: #selector(AppDelegate.checkForUpdateManual), keyEquivalent: "")
1476
+ menu.addItem(checkUpdateItem)
1452
1477
 
1453
1478
  menu.addItem(NSMenuItem.separator())
1454
1479
  menu.addItem(NSMenuItem(title: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
@@ -1467,9 +1492,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
1467
1492
  refresh()
1468
1493
  let pollTimer = Timer(timeInterval: 8, repeats: true) { _ in self.refresh() }
1469
1494
  RunLoop.main.add(pollTimer, forMode: .common)
1470
- // Check for updates on launch, then every hour
1495
+ // Check for updates on launch, then every 15 minutes
1471
1496
  checkForUpdate()
1472
- let updateTimer = Timer(timeInterval: 3600, repeats: true) { _ in self.checkForUpdate() }
1497
+ let updateTimer = Timer(timeInterval: 900, repeats: true) { _ in self.checkForUpdate() }
1473
1498
  RunLoop.main.add(updateTimer, forMode: .common)
1474
1499
  }
1475
1500
 
@@ -1526,6 +1551,16 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
1526
1551
  RunLoop.main.add(pulse, forMode: .common)
1527
1552
  }
1528
1553
 
1554
+ @objc func checkForUpdateManual() {
1555
+ checkUpdateItem.title = "Checking…"
1556
+ checkUpdateItem.isEnabled = false
1557
+ checkForUpdate()
1558
+ DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
1559
+ checkUpdateItem.title = "Check for updates"
1560
+ checkUpdateItem.isEnabled = true
1561
+ }
1562
+ }
1563
+
1529
1564
  @objc func runUpdate() {
1530
1565
  updateItem.title = "⬆ Updating…"
1531
1566
  updateItem.isEnabled = false
@@ -1975,6 +2010,8 @@ app.run()
1975
2010
  detached: true,
1976
2011
  env: trayEnv,
1977
2012
  });
2013
+ if (!existsSync(pidDir)) mkdirSync(pidDir, { recursive: true });
2014
+ writeFileSync(pidFile, String(child.pid));
1978
2015
  child.unref();
1979
2016
  });
1980
2017
  });
@@ -156,21 +156,38 @@ function parseFrontmatter(content) {
156
156
  // Alias for backward compatibility
157
157
  const parseSkillFrontmatter = parseFrontmatter;
158
158
 
159
+ /**
160
+ * Plugins that are disabled by default and must be explicitly enabled via
161
+ * `fops plugin enable <id>` or by setting `enabled: true` in ~/.fops.json.
162
+ */
163
+ const DEFAULT_DISABLED = new Set([
164
+ "fops-plugin-teleport",
165
+ "coda",
166
+ "fops-plugin-github-roles",
167
+ "fops-plugin-ecr",
168
+ "fops-plugin-vm-users",
169
+ "fops-plugin-aws",
170
+ "fops-plugin-dai-ttyd",
171
+ ]);
172
+
159
173
  /**
160
174
  * Check if a plugin is enabled in ~/.fops.json.
161
- * Default: enabled unless explicitly set to false.
175
+ * Plugins in DEFAULT_DISABLED are off unless explicitly enabled.
176
+ * All other plugins are on unless explicitly disabled.
162
177
  */
163
178
  function isPluginEnabled(pluginId) {
164
179
  try {
165
180
  const fopsConfig = path.join(os.homedir(), ".fops.json");
166
- if (!fs.existsSync(fopsConfig)) return true;
167
- const raw = JSON.parse(fs.readFileSync(fopsConfig, "utf8"));
168
- const entry = raw?.plugins?.entries?.[pluginId];
169
- if (entry && entry.enabled === false) return false;
181
+ if (fs.existsSync(fopsConfig)) {
182
+ const raw = JSON.parse(fs.readFileSync(fopsConfig, "utf8"));
183
+ const entry = raw?.plugins?.entries?.[pluginId];
184
+ if (entry?.enabled === false) return false;
185
+ if (entry?.enabled === true) return true;
186
+ }
170
187
  } catch {
171
188
  // ignore parse errors
172
189
  }
173
- return true;
190
+ return !DEFAULT_DISABLED.has(pluginId);
174
191
  }
175
192
 
176
193
  /**