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.
Files changed (86) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/scripts/bundle-workframe-ui.mjs +13 -7
  4. package/workframe-api/package.json +1 -1
  5. package/workframe-api/server.py +34 -2
  6. package/workframe-api/updates.py +107 -18
  7. package/workframe-supervisor/server.py +75 -2
  8. package/workframe-ui/public/assets/{arc-CRhulRPr.js → arc-BskqxLmE.js} +1 -1
  9. package/workframe-ui/public/assets/architecture-7EHR7CIX-DOxsUI33.js +1 -0
  10. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-B1Gq5onc.js → architectureDiagram-3BPJPVTR-B3eGdqvq.js} +1 -1
  11. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-DtXEetq-.js → blockDiagram-GPEHLZMM-CzFZG7uX.js} +1 -1
  12. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-Bw7NEN3_.js → c4Diagram-AAUBKEIU-BS-gGyCH.js} +1 -1
  13. package/workframe-ui/public/assets/channel-Baa20pIs.js +1 -0
  14. package/workframe-ui/public/assets/{chunk-2J33WTMH-91lObBre.js → chunk-2J33WTMH-Cjvqf7c9.js} +1 -1
  15. package/workframe-ui/public/assets/{chunk-3OPIFGDE-D5IopWdW.js → chunk-3OPIFGDE--eUuL8AK.js} +1 -1
  16. package/workframe-ui/public/assets/{chunk-4BX2VUAB-DRHbh4Yd.js → chunk-4BX2VUAB-s9lHUFKL.js} +1 -1
  17. package/workframe-ui/public/assets/{chunk-55IACEB6-DcfKoh86.js → chunk-55IACEB6-D1McA5nz.js} +1 -1
  18. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-BMtcWXOU.js → chunk-5ZQYHXKU-DIstLfU2.js} +1 -1
  19. package/workframe-ui/public/assets/{chunk-727SXJPM-BW7tTfej.js → chunk-727SXJPM-Cd0GD2CO.js} +1 -1
  20. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-CGoSQAgm.js → chunk-AQP2D5EJ-gc5Ra6G4.js} +1 -1
  21. package/workframe-ui/public/assets/{chunk-BSJP7CBP-RW1Jng9K.js → chunk-BSJP7CBP-Cg5QfihJ.js} +1 -1
  22. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-D8PwFfuF.js → chunk-CSCIHK7Q-D_gobbhY.js} +1 -1
  23. package/workframe-ui/public/assets/{chunk-FMBD7UC4-rUp_DSZ2.js → chunk-FMBD7UC4-B6eLY9_d.js} +1 -1
  24. package/workframe-ui/public/assets/{chunk-KSCS5N6A-C3eMeGne.js → chunk-KSCS5N6A-BGroAeW2.js} +1 -1
  25. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-DaE5LwXn.js → chunk-L5ZTLDWV-34xv7WgT.js} +1 -1
  26. package/workframe-ui/public/assets/{chunk-LZXEDZCA-Cmn67nL3.js → chunk-LZXEDZCA-XJBctFZw.js} +2 -2
  27. package/workframe-ui/public/assets/{chunk-ND2GUHAM-Bg-FPAN5.js → chunk-ND2GUHAM-nTOZpln8.js} +1 -1
  28. package/workframe-ui/public/assets/{chunk-NZK2D7GU-orlL5R9g.js → chunk-NZK2D7GU-BBrzcaPH.js} +1 -1
  29. package/workframe-ui/public/assets/{chunk-O5CBEL6O-BaPyh9SK.js → chunk-O5CBEL6O-Cntncx6L.js} +1 -1
  30. package/workframe-ui/public/assets/chunk-QZHKN3VN-B7tPxs4H.js +1 -0
  31. package/workframe-ui/public/assets/chunk-WU5MYG2G-Dvh2By1o.js +1 -0
  32. package/workframe-ui/public/assets/{chunk-XPW4576I-CXYFE8tT.js → chunk-XPW4576I-DSbtd0cg.js} +1 -1
  33. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BljHyDzF.js +1 -0
  34. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BljHyDzF.js +1 -0
  35. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-DDzKMTbp.js → cose-bilkent-S5V4N54A-lmYstEMQ.js} +1 -1
  36. package/workframe-ui/public/assets/{dagre-BM42HDAG-DCa6EvS3.js → dagre-BM42HDAG-Bdkbdul2.js} +1 -1
  37. package/workframe-ui/public/assets/{diagram-2AECGRRQ-C-WIQoE_.js → diagram-2AECGRRQ-Clb9kSXe.js} +1 -1
  38. package/workframe-ui/public/assets/{diagram-5GNKFQAL-UDk1nRH6.js → diagram-5GNKFQAL-BApg0yec.js} +1 -1
  39. package/workframe-ui/public/assets/{diagram-KO2AKTUF-DjLxUTPx.js → diagram-KO2AKTUF-peZjDRV6.js} +1 -1
  40. package/workframe-ui/public/assets/{diagram-LMA3HP47-DNA9GSwJ.js → diagram-LMA3HP47-DZm_1pjc.js} +1 -1
  41. package/workframe-ui/public/assets/{diagram-OG6HWLK6-DO2nQu2f.js → diagram-OG6HWLK6-DP_N6fYL.js} +1 -1
  42. package/workframe-ui/public/assets/{dist-DyW8oFIW.js → dist-CqP43hXK.js} +1 -1
  43. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-DFtJLZx1.js → erDiagram-TEJ5UH35-Blfs6kLv.js} +1 -1
  44. package/workframe-ui/public/assets/eventmodeling-FCH6USID-C4xwO6dx.js +1 -0
  45. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-B6K6EnPA.js → flowDiagram-I6XJVG4X-CcmMOkwH.js} +1 -1
  46. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-CbuWdupl.js → ganttDiagram-6RSMTGT7-ByoO3dF7.js} +1 -1
  47. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-BW_jwUPU.js → gitGraph-WXDBUCRP-DCyTpmPs.js} +1 -1
  48. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-B4MYuP9j.js → gitGraphDiagram-PVQCEYII-CvLYB55b.js} +1 -1
  49. package/workframe-ui/public/assets/{index-BYacg3Nj.js → index-BIfRGM8p.js} +4 -4
  50. package/workframe-ui/public/assets/{info-J43DQDTF-Xa61PMDn.js → info-J43DQDTF-CfSRIfOI.js} +1 -1
  51. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-BNbQgdUG.js → infoDiagram-5YYISTIA-DwS44DS6.js} +1 -1
  52. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-BDfiCzHo.js → ishikawaDiagram-YF4QCWOH-MRlyodyU.js} +1 -1
  53. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-Dn52B8tT.js → journeyDiagram-JHISSGLW-sq7awk87.js} +1 -1
  54. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-rhwkS_g6.js → kanban-definition-UN3LZRKU-CH6HbcWu.js} +1 -1
  55. package/workframe-ui/public/assets/{line-BQJgrj-x.js → line-DRBjFmUW.js} +1 -1
  56. package/workframe-ui/public/assets/{linear-Bq2ZvWSJ.js → linear-BR0LunjE.js} +1 -1
  57. package/workframe-ui/public/assets/{mermaid-parser.core-CwFK_i8p.js → mermaid-parser.core-Cl1GjHkT.js} +2 -2
  58. package/workframe-ui/public/assets/{mermaid.core-C9CAm83c.js → mermaid.core-DsiKK1kE.js} +3 -3
  59. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-BXwEccNZ.js → mindmap-definition-RKZ34NQL-DBvi3OpW.js} +1 -1
  60. package/workframe-ui/public/assets/{packet-YPE3B663-D754iSKT.js → packet-YPE3B663-CPDgtUHg.js} +1 -1
  61. package/workframe-ui/public/assets/{pie-LRSECV5Y-BQ2V4UWk.js → pie-LRSECV5Y-BXJIPPDm.js} +1 -1
  62. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-CNHecl4C.js → pieDiagram-4H26LBE5-CZwLEK-o.js} +1 -1
  63. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-Bu6ciL2O.js → quadrantDiagram-W4KKPZXB-B65bEUIQ.js} +1 -1
  64. package/workframe-ui/public/assets/{radar-GUYGQ44K-Dr8b9Qak.js → radar-GUYGQ44K-vXHkZzRK.js} +1 -1
  65. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-Beua6Xju.js → requirementDiagram-4Y6WPE33-BGWdvDfG.js} +1 -1
  66. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-CSDFJ0P_.js → sankeyDiagram-5OEKKPKP-BT2oC8dV.js} +1 -1
  67. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-KKziodNI.js → sequenceDiagram-3UESZ5HK-45yj0Wtx.js} +1 -1
  68. package/workframe-ui/public/assets/{src-PKTIkQR0.js → src-D-P8U2PB.js} +1 -1
  69. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-Bu8FNMcj.js → stateDiagram-AJRCARHV-SBrBJBZG.js} +1 -1
  70. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-B3im3So2.js +1 -0
  71. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DMa-RPqy.js → timeline-definition-PNZ67QCA-DegmxXXj.js} +1 -1
  72. package/workframe-ui/public/assets/{treeView-BLDUP644-CzQeUY7D.js → treeView-BLDUP644-BVy7CsCj.js} +1 -1
  73. package/workframe-ui/public/assets/{treemap-LRROVOQU-BT4RYAhY.js → treemap-LRROVOQU-BD5Kb74J.js} +1 -1
  74. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-nKaUH6GG.js → vennDiagram-CIIHVFJN-hwAkNbl3.js} +1 -1
  75. package/workframe-ui/public/assets/{wardley-L42UT6IY-eSpvH7au.js → wardley-L42UT6IY-CBMPT8Y1.js} +1 -1
  76. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-B5u4s4a5.js → wardleyDiagram-YWT4CUSO-DXqihVlE.js} +1 -1
  77. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DcXo5hDi.js → xychartDiagram-2RQKCTM6-Cbb4LBzj.js} +1 -1
  78. package/workframe-ui/public/index.html +1 -1
  79. package/workframe-ui/public/assets/architecture-7EHR7CIX-CwxC-UJz.js +0 -1
  80. package/workframe-ui/public/assets/channel-BaZSQvmW.js +0 -1
  81. package/workframe-ui/public/assets/chunk-QZHKN3VN-DXcsnu-x.js +0 -1
  82. package/workframe-ui/public/assets/chunk-WU5MYG2G-B2O0DZxu.js +0 -1
  83. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-C_TyUBYX.js +0 -1
  84. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-C_TyUBYX.js +0 -1
  85. package/workframe-ui/public/assets/eventmodeling-FCH6USID-BTmiKS74.js +0 -1
  86. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-E9A5RCa9.js +0 -1
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Published on npm as **create-workframe**.
4
4
 
5
5
  ```bash
6
- npx create-workframe@0.1.3 MyProject
6
+ npx create-workframe@0.1.4 MyProject
7
7
  ```
8
8
 
9
9
  Scaffolds an isolated Workframe + Hermes project on Windows, macOS, and Linux.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-workframe",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Scaffold a Workframe + Hermes workspace with guided onboarding",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- console.log('Building @workframe/web...');
40
- const build = spawnSync(npmCmd(), ['run', 'build'], {
41
- cwd: UI_SRC,
42
- stdio: 'inherit',
43
- shell: process.platform === 'win32',
44
- });
45
- if (build.status !== 0) process.exit(build.status ?? 1);
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'))) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workframe/workframe-api",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -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
- if str(exc) != "docker_unavailable" or not _supervisor_ready():
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
- status, data = _supervisor_request("POST", "/v1/stack.apply", {"target": target}, timeout=900.0)
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)}; "
@@ -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
- docker_ok = Path(DOCKER_SOCK).exists()
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 = _container_image_digest(GATEWAY_CONTAINER)
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
- docker_ok
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(docker_apply_ok and hermes_script_ok)
282
- workframe_can_update = bool(docker_apply_ok and workframe_script_ok)
283
- hermes_reason = docker_apply_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 = docker_apply_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": docker_ok,
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(docker_apply_ok and _script_path("restart-gateway-hermes.sh") is not None),
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 os.environ.get("WORKFRAME_ENABLE_ADMIN_UPDATES") != "1":
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 = os.environ.copy()
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 os.environ.get("WORKFRAME_ENABLE_ADMIN_UPDATES") != "1":
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
- return self._json(200, _stack_apply(target))
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-DyW8oFIW.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};
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};