create-workframe 0.1.3 → 0.1.4
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 +1 -1
- package/package.json +1 -1
- package/scripts/bundle-workframe-ui.mjs +13 -7
- package/workframe-api/package.json +1 -1
- package/workframe-api/server.py +34 -2
- package/workframe-api/updates.py +107 -18
- package/workframe-supervisor/server.py +75 -2
- package/workframe-ui/public/assets/{arc-CRhulRPr.js → arc-BskqxLmE.js} +1 -1
- package/workframe-ui/public/assets/architecture-7EHR7CIX-DOxsUI33.js +1 -0
- package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-B1Gq5onc.js → architectureDiagram-3BPJPVTR-B3eGdqvq.js} +1 -1
- package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-DtXEetq-.js → blockDiagram-GPEHLZMM-CzFZG7uX.js} +1 -1
- package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-Bw7NEN3_.js → c4Diagram-AAUBKEIU-BS-gGyCH.js} +1 -1
- package/workframe-ui/public/assets/channel-Baa20pIs.js +1 -0
- package/workframe-ui/public/assets/{chunk-2J33WTMH-91lObBre.js → chunk-2J33WTMH-Cjvqf7c9.js} +1 -1
- package/workframe-ui/public/assets/{chunk-3OPIFGDE-D5IopWdW.js → chunk-3OPIFGDE--eUuL8AK.js} +1 -1
- package/workframe-ui/public/assets/{chunk-4BX2VUAB-DRHbh4Yd.js → chunk-4BX2VUAB-s9lHUFKL.js} +1 -1
- package/workframe-ui/public/assets/{chunk-55IACEB6-DcfKoh86.js → chunk-55IACEB6-D1McA5nz.js} +1 -1
- package/workframe-ui/public/assets/{chunk-5ZQYHXKU-BMtcWXOU.js → chunk-5ZQYHXKU-DIstLfU2.js} +1 -1
- package/workframe-ui/public/assets/{chunk-727SXJPM-BW7tTfej.js → chunk-727SXJPM-Cd0GD2CO.js} +1 -1
- package/workframe-ui/public/assets/{chunk-AQP2D5EJ-CGoSQAgm.js → chunk-AQP2D5EJ-gc5Ra6G4.js} +1 -1
- package/workframe-ui/public/assets/{chunk-BSJP7CBP-RW1Jng9K.js → chunk-BSJP7CBP-Cg5QfihJ.js} +1 -1
- package/workframe-ui/public/assets/{chunk-CSCIHK7Q-D8PwFfuF.js → chunk-CSCIHK7Q-D_gobbhY.js} +1 -1
- package/workframe-ui/public/assets/{chunk-FMBD7UC4-rUp_DSZ2.js → chunk-FMBD7UC4-B6eLY9_d.js} +1 -1
- package/workframe-ui/public/assets/{chunk-KSCS5N6A-C3eMeGne.js → chunk-KSCS5N6A-BGroAeW2.js} +1 -1
- package/workframe-ui/public/assets/{chunk-L5ZTLDWV-DaE5LwXn.js → chunk-L5ZTLDWV-34xv7WgT.js} +1 -1
- package/workframe-ui/public/assets/{chunk-LZXEDZCA-Cmn67nL3.js → chunk-LZXEDZCA-XJBctFZw.js} +2 -2
- package/workframe-ui/public/assets/{chunk-ND2GUHAM-Bg-FPAN5.js → chunk-ND2GUHAM-nTOZpln8.js} +1 -1
- package/workframe-ui/public/assets/{chunk-NZK2D7GU-orlL5R9g.js → chunk-NZK2D7GU-BBrzcaPH.js} +1 -1
- package/workframe-ui/public/assets/{chunk-O5CBEL6O-BaPyh9SK.js → chunk-O5CBEL6O-Cntncx6L.js} +1 -1
- package/workframe-ui/public/assets/chunk-QZHKN3VN-B7tPxs4H.js +1 -0
- package/workframe-ui/public/assets/chunk-WU5MYG2G-Dvh2By1o.js +1 -0
- package/workframe-ui/public/assets/{chunk-XPW4576I-CXYFE8tT.js → chunk-XPW4576I-DSbtd0cg.js} +1 -1
- package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BljHyDzF.js +1 -0
- package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BljHyDzF.js +1 -0
- package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-DDzKMTbp.js → cose-bilkent-S5V4N54A-lmYstEMQ.js} +1 -1
- package/workframe-ui/public/assets/{dagre-BM42HDAG-DCa6EvS3.js → dagre-BM42HDAG-Bdkbdul2.js} +1 -1
- package/workframe-ui/public/assets/{diagram-2AECGRRQ-C-WIQoE_.js → diagram-2AECGRRQ-Clb9kSXe.js} +1 -1
- package/workframe-ui/public/assets/{diagram-5GNKFQAL-UDk1nRH6.js → diagram-5GNKFQAL-BApg0yec.js} +1 -1
- package/workframe-ui/public/assets/{diagram-KO2AKTUF-DjLxUTPx.js → diagram-KO2AKTUF-peZjDRV6.js} +1 -1
- package/workframe-ui/public/assets/{diagram-LMA3HP47-DNA9GSwJ.js → diagram-LMA3HP47-DZm_1pjc.js} +1 -1
- package/workframe-ui/public/assets/{diagram-OG6HWLK6-DO2nQu2f.js → diagram-OG6HWLK6-DP_N6fYL.js} +1 -1
- package/workframe-ui/public/assets/{dist-DyW8oFIW.js → dist-CqP43hXK.js} +1 -1
- package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-DFtJLZx1.js → erDiagram-TEJ5UH35-Blfs6kLv.js} +1 -1
- package/workframe-ui/public/assets/eventmodeling-FCH6USID-C4xwO6dx.js +1 -0
- package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-B6K6EnPA.js → flowDiagram-I6XJVG4X-CcmMOkwH.js} +1 -1
- package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-CbuWdupl.js → ganttDiagram-6RSMTGT7-ByoO3dF7.js} +1 -1
- package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-BW_jwUPU.js → gitGraph-WXDBUCRP-DCyTpmPs.js} +1 -1
- package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-B4MYuP9j.js → gitGraphDiagram-PVQCEYII-CvLYB55b.js} +1 -1
- package/workframe-ui/public/assets/{index-BYacg3Nj.js → index-BIfRGM8p.js} +4 -4
- package/workframe-ui/public/assets/{info-J43DQDTF-Xa61PMDn.js → info-J43DQDTF-CfSRIfOI.js} +1 -1
- package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-BNbQgdUG.js → infoDiagram-5YYISTIA-DwS44DS6.js} +1 -1
- package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-BDfiCzHo.js → ishikawaDiagram-YF4QCWOH-MRlyodyU.js} +1 -1
- package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-Dn52B8tT.js → journeyDiagram-JHISSGLW-sq7awk87.js} +1 -1
- package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-rhwkS_g6.js → kanban-definition-UN3LZRKU-CH6HbcWu.js} +1 -1
- package/workframe-ui/public/assets/{line-BQJgrj-x.js → line-DRBjFmUW.js} +1 -1
- package/workframe-ui/public/assets/{linear-Bq2ZvWSJ.js → linear-BR0LunjE.js} +1 -1
- package/workframe-ui/public/assets/{mermaid-parser.core-CwFK_i8p.js → mermaid-parser.core-Cl1GjHkT.js} +2 -2
- package/workframe-ui/public/assets/{mermaid.core-C9CAm83c.js → mermaid.core-DsiKK1kE.js} +3 -3
- package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-BXwEccNZ.js → mindmap-definition-RKZ34NQL-DBvi3OpW.js} +1 -1
- package/workframe-ui/public/assets/{packet-YPE3B663-D754iSKT.js → packet-YPE3B663-CPDgtUHg.js} +1 -1
- package/workframe-ui/public/assets/{pie-LRSECV5Y-BQ2V4UWk.js → pie-LRSECV5Y-BXJIPPDm.js} +1 -1
- package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-CNHecl4C.js → pieDiagram-4H26LBE5-CZwLEK-o.js} +1 -1
- package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-Bu6ciL2O.js → quadrantDiagram-W4KKPZXB-B65bEUIQ.js} +1 -1
- package/workframe-ui/public/assets/{radar-GUYGQ44K-Dr8b9Qak.js → radar-GUYGQ44K-vXHkZzRK.js} +1 -1
- package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-Beua6Xju.js → requirementDiagram-4Y6WPE33-BGWdvDfG.js} +1 -1
- package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-CSDFJ0P_.js → sankeyDiagram-5OEKKPKP-BT2oC8dV.js} +1 -1
- package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-KKziodNI.js → sequenceDiagram-3UESZ5HK-45yj0Wtx.js} +1 -1
- package/workframe-ui/public/assets/{src-PKTIkQR0.js → src-D-P8U2PB.js} +1 -1
- package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-Bu8FNMcj.js → stateDiagram-AJRCARHV-SBrBJBZG.js} +1 -1
- package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-B3im3So2.js +1 -0
- package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DMa-RPqy.js → timeline-definition-PNZ67QCA-DegmxXXj.js} +1 -1
- package/workframe-ui/public/assets/{treeView-BLDUP644-CzQeUY7D.js → treeView-BLDUP644-BVy7CsCj.js} +1 -1
- package/workframe-ui/public/assets/{treemap-LRROVOQU-BT4RYAhY.js → treemap-LRROVOQU-BD5Kb74J.js} +1 -1
- package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-nKaUH6GG.js → vennDiagram-CIIHVFJN-hwAkNbl3.js} +1 -1
- package/workframe-ui/public/assets/{wardley-L42UT6IY-eSpvH7au.js → wardley-L42UT6IY-CBMPT8Y1.js} +1 -1
- package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-B5u4s4a5.js → wardleyDiagram-YWT4CUSO-DXqihVlE.js} +1 -1
- package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DcXo5hDi.js → xychartDiagram-2RQKCTM6-Cbb4LBzj.js} +1 -1
- package/workframe-ui/public/index.html +1 -1
- package/workframe-ui/public/assets/architecture-7EHR7CIX-CwxC-UJz.js +0 -1
- package/workframe-ui/public/assets/channel-BaZSQvmW.js +0 -1
- package/workframe-ui/public/assets/chunk-QZHKN3VN-DXcsnu-x.js +0 -1
- package/workframe-ui/public/assets/chunk-WU5MYG2G-B2O0DZxu.js +0 -1
- package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-C_TyUBYX.js +0 -1
- package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-C_TyUBYX.js +0 -1
- package/workframe-ui/public/assets/eventmodeling-FCH6USID-BTmiKS74.js +0 -1
- package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-E9A5RCa9.js +0 -1
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -31,18 +31,24 @@ function copyTree(src, dst) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
const skipBuild = process.argv.includes('--skip-build');
|
|
35
|
+
|
|
34
36
|
if (!fs.existsSync(UI_SRC)) {
|
|
35
37
|
console.error(`apps/web source not found: ${UI_SRC}`);
|
|
36
38
|
process.exit(1);
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
if (!skipBuild) {
|
|
42
|
+
console.log('Building @workframe/web...');
|
|
43
|
+
const build = spawnSync(npmCmd(), ['run', 'build'], {
|
|
44
|
+
cwd: UI_SRC,
|
|
45
|
+
stdio: 'inherit',
|
|
46
|
+
shell: process.platform === 'win32',
|
|
47
|
+
});
|
|
48
|
+
if (build.status !== 0) process.exit(build.status ?? 1);
|
|
49
|
+
} else {
|
|
50
|
+
console.log('Skipping @workframe/web build (--skip-build)');
|
|
51
|
+
}
|
|
46
52
|
|
|
47
53
|
const dist = path.join(UI_SRC, 'dist');
|
|
48
54
|
if (!fs.existsSync(path.join(dist, 'index.html'))) {
|
package/workframe-api/server.py
CHANGED
|
@@ -551,12 +551,26 @@ def _admin_write_allowed(handler: BaseHTTPRequestHandler) -> bool:
|
|
|
551
551
|
|
|
552
552
|
|
|
553
553
|
def _apply_stack_update(target: str) -> dict[str, Any]:
|
|
554
|
+
workframe_version = ""
|
|
555
|
+
if target in {"workframe", "all"}:
|
|
556
|
+
try:
|
|
557
|
+
workframe_version = str(
|
|
558
|
+
stack_updates.updates_available().get("workframe", {}).get("latest") or ""
|
|
559
|
+
).strip()
|
|
560
|
+
except Exception: # noqa: BLE001
|
|
561
|
+
workframe_version = ""
|
|
554
562
|
try:
|
|
555
563
|
return stack_updates.apply_update(target)
|
|
556
564
|
except ValueError as exc:
|
|
557
|
-
|
|
565
|
+
err = str(exc)
|
|
566
|
+
if not _supervisor_ready():
|
|
567
|
+
raise
|
|
568
|
+
if err not in ("docker_unavailable", "admin_updates_disabled"):
|
|
558
569
|
raise
|
|
559
|
-
|
|
570
|
+
body: dict[str, Any] = {"target": target}
|
|
571
|
+
if workframe_version:
|
|
572
|
+
body["workframe_version"] = workframe_version
|
|
573
|
+
status, data = _supervisor_request("POST", "/v1/stack.apply", body, timeout=900.0)
|
|
560
574
|
if status >= 300 or not isinstance(data, dict) or not data.get("ok"):
|
|
561
575
|
raise ValueError(str((data or {}).get("error") or "supervisor_apply_failed"))
|
|
562
576
|
return data
|
|
@@ -2641,6 +2655,24 @@ def _spawn_hermes_device_oauth(user_id: str, hermes_auth_id: str, log_container:
|
|
|
2641
2655
|
"""Start long-running `hermes auth add` detached in gateway (survives exec return)."""
|
|
2642
2656
|
home = _hermes_user_home_container(user_id)
|
|
2643
2657
|
_user_hermes_home(user_id).mkdir(parents=True, exist_ok=True)
|
|
2658
|
+
if SECURE_MODE and _supervisor_ready():
|
|
2659
|
+
status, data = _supervisor_request(
|
|
2660
|
+
"POST",
|
|
2661
|
+
"/v1/hermes.device_oauth_start",
|
|
2662
|
+
{"home": home, "hermes_auth_id": hermes_auth_id, "log_path": log_container},
|
|
2663
|
+
timeout=30.0,
|
|
2664
|
+
)
|
|
2665
|
+
if status >= 300:
|
|
2666
|
+
err = data.get("error") if isinstance(data, dict) else str(data)
|
|
2667
|
+
raise ValueError(err or "device_oauth_start_failed")
|
|
2668
|
+
if not isinstance(data, dict):
|
|
2669
|
+
raise ValueError("device_oauth_start_invalid")
|
|
2670
|
+
exit_code = data.get("exit_code")
|
|
2671
|
+
try:
|
|
2672
|
+
code = int(exit_code if exit_code is not None else 1)
|
|
2673
|
+
except (TypeError, ValueError):
|
|
2674
|
+
code = 1
|
|
2675
|
+
return code, str(data.get("output") or "")
|
|
2644
2676
|
auth_cmd = " ".join(shlex.quote(part) for part in ["auth", "add", hermes_auth_id])
|
|
2645
2677
|
shell = (
|
|
2646
2678
|
f"mkdir -p {shlex.quote(home)}; "
|
package/workframe-api/updates.py
CHANGED
|
@@ -190,6 +190,38 @@ def _host_compose_ready() -> bool:
|
|
|
190
190
|
return compose.joinpath("docker-compose.yml").is_file()
|
|
191
191
|
|
|
192
192
|
|
|
193
|
+
def _supervisor_configured() -> bool:
|
|
194
|
+
return bool(os.environ.get("WORKFRAME_SUPERVISOR_URL", "").strip()) and bool(
|
|
195
|
+
os.environ.get("WORKFRAME_SUPERVISOR_TOKEN", "").strip()
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _supervisor_gateway_image_digest() -> tuple[str, str]:
|
|
200
|
+
"""Read gateway image digest via workframe-supervisor (SECURE_MODE API has no docker.sock)."""
|
|
201
|
+
base = str(os.environ.get("WORKFRAME_SUPERVISOR_URL", "")).rstrip("/")
|
|
202
|
+
token = str(os.environ.get("WORKFRAME_SUPERVISOR_TOKEN", "")).strip()
|
|
203
|
+
if not base or not token:
|
|
204
|
+
return "", ""
|
|
205
|
+
req = urllib.request.Request(
|
|
206
|
+
f"{base}/v1/gateway.image",
|
|
207
|
+
headers={"Authorization": f"Bearer {token}", "Accept": "application/json", "User-Agent": "workframe-api"},
|
|
208
|
+
)
|
|
209
|
+
try:
|
|
210
|
+
with urllib.request.urlopen(req, timeout=15.0) as resp:
|
|
211
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
212
|
+
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError, OSError):
|
|
213
|
+
return "", ""
|
|
214
|
+
if not isinstance(data, dict) or not data.get("ok"):
|
|
215
|
+
return "", ""
|
|
216
|
+
return str(data.get("digest") or "").strip(), str(data.get("ref") or "").strip()
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _admin_stack_updates_enabled() -> bool:
|
|
220
|
+
if os.environ.get("WORKFRAME_ENABLE_ADMIN_UPDATES") == "1":
|
|
221
|
+
return True
|
|
222
|
+
return _supervisor_configured()
|
|
223
|
+
|
|
224
|
+
|
|
193
225
|
def _docker_apply_ready() -> tuple[bool, str | None]:
|
|
194
226
|
if not Path(DOCKER_SOCK).exists():
|
|
195
227
|
return False, "Docker socket is not available to the API container."
|
|
@@ -202,6 +234,29 @@ def _docker_apply_ready() -> tuple[bool, str | None]:
|
|
|
202
234
|
return True, None
|
|
203
235
|
|
|
204
236
|
|
|
237
|
+
def _update_apply_channel() -> tuple[str, bool, str | None]:
|
|
238
|
+
"""Returns (channel, ready, reason). channel: api_docker | supervisor | none."""
|
|
239
|
+
api_docker = Path(DOCKER_SOCK).exists()
|
|
240
|
+
if api_docker:
|
|
241
|
+
ok, reason = _docker_apply_ready()
|
|
242
|
+
if ok:
|
|
243
|
+
return "api_docker", True, None
|
|
244
|
+
if not _supervisor_configured():
|
|
245
|
+
return "none", False, reason
|
|
246
|
+
if _supervisor_configured():
|
|
247
|
+
if _script_path("apply-update-workframe.sh") is None and _script_path("apply-update-hermes.sh") is None:
|
|
248
|
+
return "supervisor", False, "Stack update scripts are missing from this install."
|
|
249
|
+
return "supervisor", True, None
|
|
250
|
+
if api_docker:
|
|
251
|
+
_, reason = _docker_apply_ready()
|
|
252
|
+
return "none", False, reason
|
|
253
|
+
return (
|
|
254
|
+
"none",
|
|
255
|
+
False,
|
|
256
|
+
"In-place updates need workframe-supervisor or Docker on the stack host.",
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
205
260
|
def _product_state(*, update_available: bool, can_update: bool) -> str:
|
|
206
261
|
if update_available and can_update:
|
|
207
262
|
return "available"
|
|
@@ -238,7 +293,9 @@ def _releases_manifest() -> dict[str, Any]:
|
|
|
238
293
|
def updates_available(*, desktop_version: str = "", hermes_agent_version: str = "") -> dict[str, Any]:
|
|
239
294
|
compose_dir = _compose_dir()
|
|
240
295
|
project_root = _project_root()
|
|
241
|
-
|
|
296
|
+
api_docker = Path(DOCKER_SOCK).exists()
|
|
297
|
+
supervisor_ok = _supervisor_configured()
|
|
298
|
+
apply_channel, apply_ready, apply_reason = _update_apply_channel()
|
|
242
299
|
installed = _read_installed_workframe_version(project_root)
|
|
243
300
|
|
|
244
301
|
npm_latest = ""
|
|
@@ -254,15 +311,20 @@ def updates_available(*, desktop_version: str = "", hermes_agent_version: str =
|
|
|
254
311
|
installed_pkg = installed.get("package") or installed.get("api") or ""
|
|
255
312
|
workframe_update = bool(workframe_latest and _version_lt(installed_pkg, workframe_latest))
|
|
256
313
|
|
|
257
|
-
hermes_digest, hermes_ref =
|
|
314
|
+
hermes_digest, hermes_ref = ("", "")
|
|
315
|
+
if api_docker:
|
|
316
|
+
hermes_digest, hermes_ref = _container_image_digest(GATEWAY_CONTAINER)
|
|
317
|
+
elif supervisor_ok:
|
|
318
|
+
hermes_digest, hermes_ref = _supervisor_gateway_image_digest()
|
|
258
319
|
hermes_tag = hermes_ref.rsplit(":", 1)[-1] if hermes_ref and ":" in hermes_ref else HERMES_TAG
|
|
259
320
|
hermes_latest_digest = ""
|
|
260
321
|
try:
|
|
261
322
|
hermes_latest_digest = _docker_hub_digest(HERMES_IMAGE, HERMES_TAG)
|
|
262
323
|
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError, KeyError):
|
|
263
324
|
pass
|
|
325
|
+
hermes_image_known = bool(api_docker or supervisor_ok)
|
|
264
326
|
hermes_update = bool(
|
|
265
|
-
|
|
327
|
+
hermes_image_known
|
|
266
328
|
and hermes_latest_digest
|
|
267
329
|
and hermes_digest
|
|
268
330
|
and hermes_digest != hermes_latest_digest,
|
|
@@ -275,15 +337,14 @@ def updates_available(*, desktop_version: str = "", hermes_agent_version: str =
|
|
|
275
337
|
if len(digest_short) > 28:
|
|
276
338
|
digest_short = digest_short[:28] + "…"
|
|
277
339
|
|
|
278
|
-
docker_apply_ok, docker_apply_reason = _docker_apply_ready()
|
|
279
340
|
hermes_script_ok = _script_path("apply-update-hermes.sh") is not None
|
|
280
341
|
workframe_script_ok = _script_path("apply-update-workframe.sh") is not None
|
|
281
|
-
hermes_can_update = bool(
|
|
282
|
-
workframe_can_update = bool(
|
|
283
|
-
hermes_reason =
|
|
342
|
+
hermes_can_update = bool(apply_ready and hermes_script_ok)
|
|
343
|
+
workframe_can_update = bool(apply_ready and workframe_script_ok)
|
|
344
|
+
hermes_reason = apply_reason
|
|
284
345
|
if not hermes_reason and hermes_update and not hermes_script_ok:
|
|
285
346
|
hermes_reason = "Hermes update script is missing from this install."
|
|
286
|
-
workframe_reason =
|
|
347
|
+
workframe_reason = apply_reason
|
|
287
348
|
if not workframe_reason and workframe_update and not workframe_script_ok:
|
|
288
349
|
workframe_reason = "Workframe update script is missing from this install."
|
|
289
350
|
if not workframe_reason and workframe_update and not workframe_latest:
|
|
@@ -294,7 +355,11 @@ def updates_available(*, desktop_version: str = "", hermes_agent_version: str =
|
|
|
294
355
|
|
|
295
356
|
return {
|
|
296
357
|
"ok": True,
|
|
297
|
-
"docker_available":
|
|
358
|
+
"docker_available": apply_ready,
|
|
359
|
+
"docker_sock_on_api": api_docker,
|
|
360
|
+
"supervisor_configured": supervisor_ok,
|
|
361
|
+
"update_apply_channel": apply_channel if apply_ready else None,
|
|
362
|
+
"update_apply_ready": apply_ready,
|
|
298
363
|
"compose_dir": str(compose_dir),
|
|
299
364
|
"project_root": str(project_root),
|
|
300
365
|
"workframe": {
|
|
@@ -323,7 +388,7 @@ def updates_available(*, desktop_version: str = "", hermes_agent_version: str =
|
|
|
323
388
|
"reason": hermes_reason,
|
|
324
389
|
"update_mode": "docker-compose-pull",
|
|
325
390
|
"install_kind": "docker",
|
|
326
|
-
"can_restart_gateway": bool(
|
|
391
|
+
"can_restart_gateway": bool(apply_ready and _script_path("restart-gateway-hermes.sh") is not None),
|
|
327
392
|
},
|
|
328
393
|
"desktop": {
|
|
329
394
|
"current": desktop_installed,
|
|
@@ -339,12 +404,33 @@ def updates_available(*, desktop_version: str = "", hermes_agent_version: str =
|
|
|
339
404
|
}
|
|
340
405
|
|
|
341
406
|
|
|
407
|
+
def _apply_env_for_target(target: str) -> dict[str, str]:
|
|
408
|
+
env = os.environ.copy()
|
|
409
|
+
env.setdefault("WORKFRAME_COMPOSE_DIR", str(_compose_dir()))
|
|
410
|
+
env.setdefault("WORKFRAME_PROJECT_ROOT", str(_project_root()))
|
|
411
|
+
if target in {"workframe", "all"}:
|
|
412
|
+
latest = ""
|
|
413
|
+
try:
|
|
414
|
+
latest = str(updates_available().get("workframe", {}).get("latest") or "").strip()
|
|
415
|
+
except Exception: # noqa: BLE001
|
|
416
|
+
latest = ""
|
|
417
|
+
if latest:
|
|
418
|
+
env["WORKFRAME_UPDATE_ALLOW_NPM"] = "1"
|
|
419
|
+
env["WORKFRAME_UPDATE_VERSION"] = latest
|
|
420
|
+
return env
|
|
421
|
+
|
|
422
|
+
|
|
342
423
|
def apply_update(target: str) -> dict[str, Any]:
|
|
343
|
-
if
|
|
424
|
+
if not _admin_stack_updates_enabled():
|
|
344
425
|
raise ValueError("admin_updates_disabled")
|
|
345
426
|
target = str(target or "all").strip().lower()
|
|
346
427
|
if target not in {"hermes", "workframe", "all"}:
|
|
347
428
|
raise ValueError("invalid_update_target")
|
|
429
|
+
_, apply_ready, apply_reason = _update_apply_channel()
|
|
430
|
+
if not apply_ready:
|
|
431
|
+
if not Path(DOCKER_SOCK).exists():
|
|
432
|
+
raise ValueError("docker_unavailable")
|
|
433
|
+
raise ValueError(str(apply_reason or "docker_apply_unavailable"))
|
|
348
434
|
if not Path(DOCKER_SOCK).exists():
|
|
349
435
|
raise ValueError("docker_unavailable")
|
|
350
436
|
|
|
@@ -360,9 +446,7 @@ def apply_update(target: str) -> dict[str, Any]:
|
|
|
360
446
|
raise ValueError("update_script_missing:workframe")
|
|
361
447
|
scripts.append(str(script))
|
|
362
448
|
|
|
363
|
-
env =
|
|
364
|
-
env.setdefault("WORKFRAME_COMPOSE_DIR", str(_compose_dir()))
|
|
365
|
-
env.setdefault("WORKFRAME_PROJECT_ROOT", str(_project_root()))
|
|
449
|
+
env = _apply_env_for_target(target)
|
|
366
450
|
|
|
367
451
|
logs: list[str] = []
|
|
368
452
|
for script in scripts:
|
|
@@ -382,13 +466,15 @@ def apply_update(target: str) -> dict[str, Any]:
|
|
|
382
466
|
|
|
383
467
|
|
|
384
468
|
def restart_gateway() -> dict[str, Any]:
|
|
385
|
-
if
|
|
469
|
+
if not _admin_stack_updates_enabled():
|
|
386
470
|
raise ValueError("admin_updates_disabled")
|
|
471
|
+
_, apply_ready, apply_reason = _update_apply_channel()
|
|
472
|
+
if not apply_ready:
|
|
473
|
+
if not Path(DOCKER_SOCK).exists():
|
|
474
|
+
raise ValueError("docker_unavailable")
|
|
475
|
+
raise ValueError(str(apply_reason or "docker_apply_unavailable"))
|
|
387
476
|
if not Path(DOCKER_SOCK).exists():
|
|
388
477
|
raise ValueError("docker_unavailable")
|
|
389
|
-
docker_apply_ok, docker_apply_reason = _docker_apply_ready()
|
|
390
|
-
if not docker_apply_ok:
|
|
391
|
-
raise ValueError(str(docker_apply_reason or "docker_apply_unavailable"))
|
|
392
478
|
script = _script_path("restart-gateway-hermes.sh")
|
|
393
479
|
if not script:
|
|
394
480
|
raise ValueError("restart_script_missing:gateway")
|
|
@@ -414,4 +500,7 @@ if __name__ == "__main__":
|
|
|
414
500
|
assert _version_lt("0.1.0", "0.1.1")
|
|
415
501
|
assert not _version_lt("0.1.0", "0.1.0")
|
|
416
502
|
assert parse_hermes_version_output("Hermes Agent v0.17.0 (2026.6.19)") == "0.17.0"
|
|
503
|
+
ch, ready, _ = _update_apply_channel()
|
|
504
|
+
assert ch in {"api_docker", "supervisor", "none"}
|
|
505
|
+
assert ready is False or ch != "none"
|
|
417
506
|
print("updates module ok")
|
|
@@ -60,7 +60,7 @@ def _exec_targets_runtime_profile_secrets(cmd: list[str], acting_profile: str =
|
|
|
60
60
|
return exec_blocked_for_profile(cmd, acting_profile)
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
def _stack_apply(target: str) -> dict[str, Any]:
|
|
63
|
+
def _stack_apply(target: str, *, workframe_version: str = "") -> dict[str, Any]:
|
|
64
64
|
target = str(target or "all").strip().lower()
|
|
65
65
|
if target == "gateway-restart":
|
|
66
66
|
script = SCRIPTS_DIR / "restart-gateway-hermes.sh"
|
|
@@ -90,6 +90,11 @@ def _stack_apply(target: str) -> dict[str, Any]:
|
|
|
90
90
|
if not p.is_file():
|
|
91
91
|
raise ValueError("update_script_missing:workframe")
|
|
92
92
|
scripts.append(p)
|
|
93
|
+
env = os.environ.copy()
|
|
94
|
+
version = str(workframe_version or "").strip()
|
|
95
|
+
if version and target in {"workframe", "all"}:
|
|
96
|
+
env["WORKFRAME_UPDATE_ALLOW_NPM"] = "1"
|
|
97
|
+
env["WORKFRAME_UPDATE_VERSION"] = version
|
|
93
98
|
logs: list[str] = []
|
|
94
99
|
for script in scripts:
|
|
95
100
|
proc = subprocess.run(
|
|
@@ -97,6 +102,7 @@ def _stack_apply(target: str) -> dict[str, Any]:
|
|
|
97
102
|
capture_output=True,
|
|
98
103
|
text=True,
|
|
99
104
|
timeout=900,
|
|
105
|
+
env=env,
|
|
100
106
|
cwd=str(COMPOSE_DIR) if COMPOSE_DIR.is_dir() else None,
|
|
101
107
|
)
|
|
102
108
|
logs.append(f"=== {script} (exit {proc.returncode}) ===\n{proc.stdout}\n{proc.stderr}")
|
|
@@ -105,6 +111,47 @@ def _stack_apply(target: str) -> dict[str, Any]:
|
|
|
105
111
|
return {"ok": True, "target": target, "log": "\n".join(logs)[-12000:]}
|
|
106
112
|
|
|
107
113
|
|
|
114
|
+
_DEVICE_OAUTH_AUTH_IDS = frozenset({"openai-codex", "nous"})
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _gateway_image_digest() -> tuple[str, str]:
|
|
118
|
+
status, data = _docker_request("GET", f"/containers/{urllib.parse.quote(GATEWAY_CONTAINER_NAME, safe='')}/json")
|
|
119
|
+
if status != 200 or not isinstance(data, dict):
|
|
120
|
+
return "", ""
|
|
121
|
+
image_id = str(data.get("Image") or "")
|
|
122
|
+
ist, idata = _docker_request("GET", f"/images/{image_id}/json")
|
|
123
|
+
digest = ""
|
|
124
|
+
ref = os.environ.get("WORKFRAME_HERMES_IMAGE", "nousresearch/hermes-agent")
|
|
125
|
+
if ist == 200 and isinstance(idata, dict):
|
|
126
|
+
digests = idata.get("RepoDigests") or []
|
|
127
|
+
if digests:
|
|
128
|
+
digest = str(digests[0]).split("@")[-1]
|
|
129
|
+
tags = idata.get("RepoTags") or []
|
|
130
|
+
if tags:
|
|
131
|
+
ref = str(tags[0])
|
|
132
|
+
return digest, ref
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _hermes_device_oauth_start(home: str, hermes_auth_id: str, log_path: str) -> tuple[int, str]:
|
|
136
|
+
home = str(home or "").strip()
|
|
137
|
+
hermes_auth_id = str(hermes_auth_id or "").strip()
|
|
138
|
+
log_path = str(log_path or "").strip()
|
|
139
|
+
if not home.startswith("/opt/data/profiles/") or not log_path.startswith("/opt/data/profiles/"):
|
|
140
|
+
raise ValueError("invalid profile home")
|
|
141
|
+
if hermes_auth_id not in _DEVICE_OAUTH_AUTH_IDS:
|
|
142
|
+
raise ValueError("invalid_device_oauth_provider")
|
|
143
|
+
auth_cmd = " ".join(shlex.quote(part) for part in ["auth", "add", hermes_auth_id])
|
|
144
|
+
shell = (
|
|
145
|
+
f"mkdir -p {shlex.quote(home)}; "
|
|
146
|
+
f"chown -R hermes:hermes {shlex.quote(home)}; "
|
|
147
|
+
f"su -s /bin/sh hermes -c "
|
|
148
|
+
f"'export HERMES_HOME={shlex.quote(home)} HOME={shlex.quote(home)}; "
|
|
149
|
+
f"cd {shlex.quote(home)}; "
|
|
150
|
+
f"/opt/hermes/bin/hermes {auth_cmd} >> {shlex.quote(log_path)} 2>&1'"
|
|
151
|
+
)
|
|
152
|
+
return _docker_exec_detached(GATEWAY_CONTAINER_NAME, ["sh", "-lc", shell])
|
|
153
|
+
|
|
154
|
+
|
|
108
155
|
def _host_setup_public_https(host: str, port: int) -> dict[str, Any]:
|
|
109
156
|
host = str(host or "").strip().lower()
|
|
110
157
|
if not re.fullmatch(r"[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?", host, re.IGNORECASE):
|
|
@@ -666,6 +713,17 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
666
713
|
200,
|
|
667
714
|
{"ok": True, "profiles": profiles, "generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())},
|
|
668
715
|
)
|
|
716
|
+
if path == "/v1/gateway.image":
|
|
717
|
+
digest, ref = _gateway_image_digest()
|
|
718
|
+
return self._json(
|
|
719
|
+
200,
|
|
720
|
+
{
|
|
721
|
+
"ok": bool(digest or ref),
|
|
722
|
+
"digest": digest,
|
|
723
|
+
"ref": ref,
|
|
724
|
+
"container": GATEWAY_CONTAINER_NAME,
|
|
725
|
+
},
|
|
726
|
+
)
|
|
669
727
|
return self._json(404, {"ok": False, "error": "not found"})
|
|
670
728
|
|
|
671
729
|
def do_POST(self) -> None:
|
|
@@ -721,8 +779,23 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
721
779
|
return self._json(500, {"ok": False, "error": str(exc)})
|
|
722
780
|
if path == "/v1/stack.apply":
|
|
723
781
|
target = str(body.get("target") or "all").strip().lower()
|
|
782
|
+
workframe_version = str(body.get("workframe_version") or "").strip()
|
|
783
|
+
try:
|
|
784
|
+
return self._json(200, _stack_apply(target, workframe_version=workframe_version))
|
|
785
|
+
except ValueError as exc:
|
|
786
|
+
return self._json(400, {"ok": False, "error": str(exc)})
|
|
787
|
+
except Exception as exc: # noqa: BLE001
|
|
788
|
+
return self._json(500, {"ok": False, "error": str(exc)})
|
|
789
|
+
if path == "/v1/hermes.device_oauth_start":
|
|
790
|
+
home = str(body.get("home") or "").strip()
|
|
791
|
+
hermes_auth_id = str(body.get("hermes_auth_id") or "").strip()
|
|
792
|
+
log_path = str(body.get("log_path") or "").strip()
|
|
724
793
|
try:
|
|
725
|
-
|
|
794
|
+
code, out = _hermes_device_oauth_start(home, hermes_auth_id, log_path)
|
|
795
|
+
return self._json(
|
|
796
|
+
200,
|
|
797
|
+
{"ok": code == 0, "exit_code": code, "output": out, "detached": True},
|
|
798
|
+
)
|
|
726
799
|
except ValueError as exc:
|
|
727
800
|
return self._json(400, {"ok": False, "error": str(exc)})
|
|
728
801
|
except Exception as exc: # noqa: BLE001
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e,t}from"./path-BWPyau1x.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-
|
|
1
|
+
import{n as e,t}from"./path-BWPyau1x.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-CqP43hXK.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i)if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./chunk-NNHCCRGN-DlpIbxXb.js";import{x as e}from"./mermaid-parser.core-Cl1GjHkT.js";export{e as createArchitectureServices};
|