hive-am 1.11.0 → 1.12.1

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 (187) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +7 -3
  3. package/package.json +1 -1
  4. package/server/dist/http/routes/fs.js +3 -2
  5. package/server/dist/http/routes/resources.js +32 -5
  6. package/server/dist/objects/docker-info.js +118 -11
  7. package/server/dist/objects/docker.js +492 -16
  8. package/server/dist/objects/files.js +152 -0
  9. package/server/dist/objects/index.js +61 -24
  10. package/server/dist/objects/model.js +72 -4
  11. package/web/.next/BUILD_ID +1 -1
  12. package/web/.next/app-build-manifest.json +141 -143
  13. package/web/.next/app-path-routes-manifest.json +3 -3
  14. package/web/.next/build-manifest.json +13 -13
  15. package/web/.next/prerender-manifest.json +50 -50
  16. package/web/.next/react-loadable-manifest.json +4 -4
  17. package/web/.next/required-server-files.json +1 -1
  18. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  19. package/web/.next/server/app/_not-found.html +1 -1
  20. package/web/.next/server/app/_not-found.rsc +4 -4
  21. package/web/.next/server/app/agents/[id]/page.js +3 -3
  22. package/web/.next/server/app/agents/[id]/page.js.nft.json +1 -1
  23. package/web/.next/server/app/agents/[id]/page_client-reference-manifest.js +1 -1
  24. package/web/.next/server/app/agents/page.js +2 -2
  25. package/web/.next/server/app/agents/page.js.nft.json +1 -1
  26. package/web/.next/server/app/agents/page_client-reference-manifest.js +1 -1
  27. package/web/.next/server/app/agents.html +1 -1
  28. package/web/.next/server/app/agents.rsc +6 -6
  29. package/web/.next/server/app/apple-icon.png/route.js +1 -1
  30. package/web/.next/server/app/apple-icon.png.body +0 -0
  31. package/web/.next/server/app/chat/page_client-reference-manifest.js +1 -1
  32. package/web/.next/server/app/chat.html +1 -1
  33. package/web/.next/server/app/chat.rsc +5 -5
  34. package/web/.next/server/app/colony/page.js +2 -2
  35. package/web/.next/server/app/colony/page.js.nft.json +1 -1
  36. package/web/.next/server/app/colony/page_client-reference-manifest.js +1 -1
  37. package/web/.next/server/app/colony.html +1 -1
  38. package/web/.next/server/app/colony.rsc +5 -5
  39. package/web/.next/server/app/connections/page_client-reference-manifest.js +1 -1
  40. package/web/.next/server/app/connections.html +1 -1
  41. package/web/.next/server/app/connections.rsc +5 -5
  42. package/web/.next/server/app/favicon.ico/route.js +1 -1
  43. package/web/.next/server/app/favicon.ico.body +0 -0
  44. package/web/.next/server/app/icon.png/route.js +1 -1
  45. package/web/.next/server/app/icon.png.body +0 -0
  46. package/web/.next/server/app/index.html +1 -1
  47. package/web/.next/server/app/index.rsc +5 -5
  48. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  49. package/web/.next/server/app/relations/page_client-reference-manifest.js +1 -1
  50. package/web/.next/server/app/relations.html +1 -1
  51. package/web/.next/server/app/relations.rsc +5 -5
  52. package/web/.next/server/app/resources/[id]/page.js +1 -1
  53. package/web/.next/server/app/resources/[id]/page.js.nft.json +1 -1
  54. package/web/.next/server/app/resources/[id]/page_client-reference-manifest.js +1 -1
  55. package/web/.next/server/app/resources/page.js +2 -2
  56. package/web/.next/server/app/resources/page.js.nft.json +1 -1
  57. package/web/.next/server/app/resources/page_client-reference-manifest.js +1 -1
  58. package/web/.next/server/app/resources.html +1 -1
  59. package/web/.next/server/app/resources.rsc +5 -5
  60. package/web/.next/server/app/schedules/page_client-reference-manifest.js +1 -1
  61. package/web/.next/server/app/schedules.html +1 -1
  62. package/web/.next/server/app/schedules.rsc +5 -5
  63. package/web/.next/server/app/sessions/page.js +2 -2
  64. package/web/.next/server/app/sessions/page.js.nft.json +1 -1
  65. package/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  66. package/web/.next/server/app/sessions.html +1 -1
  67. package/web/.next/server/app/sessions.rsc +5 -5
  68. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  69. package/web/.next/server/app/skills.html +1 -1
  70. package/web/.next/server/app/skills.rsc +5 -5
  71. package/web/.next/server/app/types/page_client-reference-manifest.js +1 -1
  72. package/web/.next/server/app/types.html +1 -1
  73. package/web/.next/server/app/types.rsc +5 -5
  74. package/web/.next/server/app-paths-manifest.json +3 -3
  75. package/web/.next/server/chunks/12.js +1 -0
  76. package/web/.next/server/chunks/295.js +1 -0
  77. package/web/.next/server/chunks/374.js +6 -0
  78. package/web/.next/server/chunks/687.js +1 -0
  79. package/web/.next/server/chunks/770.js +2 -0
  80. package/web/.next/server/chunks/{944.js → 867.js} +1 -1
  81. package/web/.next/server/chunks/953.js +1 -1
  82. package/web/.next/server/middleware-build-manifest.js +1 -1
  83. package/web/.next/server/middleware-react-loadable-manifest.js +1 -1
  84. package/web/.next/server/pages/404.html +1 -1
  85. package/web/.next/server/pages/500.html +1 -1
  86. package/web/.next/server/server-reference-manifest.json +1 -1
  87. package/web/.next/static/{HzL5-xIKbr0VsaWuLWHNl → OhCKoDEs2q4LInf0mWyHz}/_buildManifest.js +1 -1
  88. package/web/.next/static/chunks/0937d497-114a2a3de515565e.js +1 -0
  89. package/web/.next/static/chunks/199-c000d298c94aac98.js +1 -0
  90. package/web/.next/static/chunks/214-35b88db02113ae02.js +1 -0
  91. package/web/.next/static/chunks/218-91cee960142e2880.js +1 -0
  92. package/web/.next/static/chunks/2b9ff8ee-969e65cc354b2150.js +1 -0
  93. package/web/.next/static/chunks/{969-07e498da015b149a.js → 350-bf7c44f226170b15.js} +1 -1
  94. package/web/.next/static/chunks/517-870d39e307db4dbb.js +1 -0
  95. package/web/.next/static/chunks/523-665ef4ba8ec58171.js +1 -0
  96. package/web/.next/static/chunks/545-f3d12b73a5a530a3.js +1 -0
  97. package/web/.next/static/chunks/642-dc874c8caf203ee1.js +1 -0
  98. package/web/.next/static/chunks/654-92a63b3c4b412666.js +1 -0
  99. package/web/.next/static/chunks/{2657.8d96393b971ae134.js → 657.b4c39e676c3e20dc.js} +1 -1
  100. package/web/.next/static/chunks/701.6abcaf5e60463eef.js +6 -0
  101. package/web/.next/static/chunks/706-fa2be0f10ffebe6a.js +1 -0
  102. package/web/.next/static/chunks/740-72f941744e530c78.js +1 -0
  103. package/web/.next/static/chunks/757-03361e7a97a605a2.js +1 -0
  104. package/web/.next/static/chunks/790-91732fc93acda224.js +1 -0
  105. package/web/.next/static/chunks/837-33c7480524f48577.js +1 -0
  106. package/web/.next/static/chunks/870-18f6bab953828102.js +1 -0
  107. package/web/.next/static/chunks/{545.bfbd92b7632c09de.js → 926.03a65d3574bc521c.js} +1 -1
  108. package/web/.next/static/chunks/97.594543c2196de524.js +1 -0
  109. package/web/.next/static/chunks/982-147e6a55046c90ba.js +1 -0
  110. package/web/.next/static/chunks/app/_not-found/page-948f39e30bfc6144.js +1 -0
  111. package/web/.next/static/chunks/app/agents/[id]/page-b9ed0dd106af45f7.js +1 -0
  112. package/web/.next/static/chunks/app/agents/layout-89bb7e860326cf4b.js +1 -0
  113. package/web/.next/static/chunks/app/agents/page-0f809513514f3c4d.js +1 -0
  114. package/web/.next/static/chunks/app/chat/page-5ec04b8997e2c0f7.js +1 -0
  115. package/web/.next/static/chunks/app/colony/page-2d41fa9508904912.js +1 -0
  116. package/web/.next/static/chunks/app/connections/page-301e45029c8a7b52.js +1 -0
  117. package/web/.next/static/chunks/app/layout-78eeccfd41b604e1.js +1 -0
  118. package/web/.next/static/chunks/app/page-5549d1e344c5f5bb.js +1 -0
  119. package/web/.next/static/chunks/app/relations/page-18e76ccb008fc7f5.js +1 -0
  120. package/web/.next/static/chunks/app/resources/[id]/page-caf4c61561524382.js +1 -0
  121. package/web/.next/static/chunks/app/resources/page-d27d342404176148.js +1 -0
  122. package/web/.next/static/chunks/app/schedules/page-448f7f201195bc00.js +1 -0
  123. package/web/.next/static/chunks/app/sessions/page-8b8ee7e01a3a8def.js +1 -0
  124. package/web/.next/static/chunks/app/skills/page-638e3f7d7bbf657c.js +1 -0
  125. package/web/.next/static/chunks/app/types/page-ee51477946f8029f.js +1 -0
  126. package/web/.next/static/chunks/{fcafe1ac.47e831cc63938bfb.js → fcafe1ac.366e4950d3b0d4b7.js} +1 -1
  127. package/web/.next/static/chunks/framework-bbac72adbbe2d328.js +1 -0
  128. package/web/.next/static/chunks/main-app-61597cc1ea26ecc5.js +1 -0
  129. package/web/.next/static/chunks/main-c15f3fee674e0303.js +1 -0
  130. package/web/.next/static/chunks/pages/{_app-8aeadc281b5934a7.js → _app-d1670a2c6ce392fc.js} +1 -1
  131. package/web/.next/static/chunks/pages/_error-95aac014d202fa7a.js +1 -0
  132. package/web/.next/static/chunks/webpack-0ff2be4f351faaaf.js +1 -0
  133. package/web/.next/static/css/{f9099a8765eed6f0.css → e6062c3ccbd917a3.css} +1 -1
  134. package/web/.next/trace +31 -31
  135. package/web/public/logo-completo.png +0 -0
  136. package/web/public/logo.png +0 -0
  137. package/server/dist/objects/templates.js +0 -67
  138. package/web/.next/server/chunks/345.js +0 -2
  139. package/web/.next/server/chunks/368.js +0 -1
  140. package/web/.next/server/chunks/403.js +0 -1
  141. package/web/.next/server/chunks/505.js +0 -6
  142. package/web/.next/server/chunks/561.js +0 -1
  143. package/web/.next/server/chunks/616.js +0 -1
  144. package/web/.next/server/chunks/802.js +0 -1
  145. package/web/.next/static/chunks/0937d497-daa99a23a4431317.js +0 -1
  146. package/web/.next/static/chunks/1517-9b82b61ee9921bb1.js +0 -1
  147. package/web/.next/static/chunks/1606-6d25d725156f5127.js +0 -1
  148. package/web/.next/static/chunks/1771-4688ad65e21bb512.js +0 -1
  149. package/web/.next/static/chunks/2547-8b6bdae3c622a158.js +0 -1
  150. package/web/.next/static/chunks/2654-4aeef09c0d6bf56a.js +0 -1
  151. package/web/.next/static/chunks/2b9ff8ee-0851c7e556e3a39b.js +0 -1
  152. package/web/.next/static/chunks/3214-c9d0c933db5b0f44.js +0 -1
  153. package/web/.next/static/chunks/3757-45ea801718ec19e4.js +0 -1
  154. package/web/.next/static/chunks/3790-3d3f11a264e8ede8.js +0 -1
  155. package/web/.next/static/chunks/4030-bc2e0aa4da0d9ad8.js +0 -1
  156. package/web/.next/static/chunks/4162-c2361cb017547c37.js +0 -1
  157. package/web/.next/static/chunks/4491-886c18aee1e9c737.js +0 -1
  158. package/web/.next/static/chunks/4740-0581e5cb9334010b.js +0 -1
  159. package/web/.next/static/chunks/4837-8dc05d75f258f9e1.js +0 -1
  160. package/web/.next/static/chunks/5587-cdccc5cf4056b739.js +0 -1
  161. package/web/.next/static/chunks/5642-09dd4161ddddaf94.js +0 -1
  162. package/web/.next/static/chunks/7097.60e7ac5abf76b2cb.js +0 -1
  163. package/web/.next/static/chunks/7706-bd0d2bda0e0c8d1e.js +0 -1
  164. package/web/.next/static/chunks/8523-82e1ee6239699d8a.js +0 -1
  165. package/web/.next/static/chunks/8701.0db2914c123a180e.js +0 -6
  166. package/web/.next/static/chunks/app/_not-found/page-d7975fab88bfe2c3.js +0 -1
  167. package/web/.next/static/chunks/app/agents/[id]/page-eeb93069b69e7f7e.js +0 -1
  168. package/web/.next/static/chunks/app/agents/layout-db700c86124f32de.js +0 -1
  169. package/web/.next/static/chunks/app/agents/page-bf40d654c5fe5c23.js +0 -1
  170. package/web/.next/static/chunks/app/chat/page-157685bdaad86c26.js +0 -1
  171. package/web/.next/static/chunks/app/colony/page-600b663a716d08c4.js +0 -1
  172. package/web/.next/static/chunks/app/connections/page-cbfcb1547d45c833.js +0 -1
  173. package/web/.next/static/chunks/app/layout-b327408bcfb13a70.js +0 -1
  174. package/web/.next/static/chunks/app/page-78c38105a034b0b3.js +0 -1
  175. package/web/.next/static/chunks/app/relations/page-622a4c5723c7c5cc.js +0 -1
  176. package/web/.next/static/chunks/app/resources/[id]/page-b9af9eecf693b695.js +0 -1
  177. package/web/.next/static/chunks/app/resources/page-a2e17f0dc205a2a4.js +0 -1
  178. package/web/.next/static/chunks/app/schedules/page-d534462088c64b59.js +0 -1
  179. package/web/.next/static/chunks/app/sessions/page-6fef5b73bfbf262e.js +0 -1
  180. package/web/.next/static/chunks/app/skills/page-ab25a3a6d348a049.js +0 -1
  181. package/web/.next/static/chunks/app/types/page-7e08ca1bcbee605f.js +0 -1
  182. package/web/.next/static/chunks/framework-ab1d276eb4175e96.js +0 -1
  183. package/web/.next/static/chunks/main-18f19eeb8b4d3fe4.js +0 -1
  184. package/web/.next/static/chunks/main-app-27cc7ffdb888b879.js +0 -1
  185. package/web/.next/static/chunks/pages/_error-6bffadfc60f180a1.js +0 -1
  186. package/web/.next/static/chunks/webpack-ba68c1813987a630.js +0 -1
  187. /package/web/.next/static/{HzL5-xIKbr0VsaWuLWHNl → OhCKoDEs2q4LInf0mWyHz}/_ssgManifest.js +0 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,45 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.12.1] - 2026-10-24
10
+
11
+ ### Added
12
+
13
+ - **Diagrams of how hive-am works inside.** `docs/diagrams/`, in English and Spanish, has ten pages that each follow a real path through the code: a chat message, a turn, the effective prompt, a delegation, wake-ups and schedules, processes and ports, a source map of `server/src`, the data model, the life cycle of an agent and the providers. Each has a Mermaid diagram, a table that maps every step to a file, a function and a line, the test scripts that cover it and its edge cases. The README and the reference index link to them.
14
+
15
+ ### Changed
16
+
17
+ - **New hive-am logos.** The README uses the full logo (isotype plus wordmark), and the app logo, favicon and icons use the new isotype. The editable sources and exports are in `docs/brand/`.
18
+
19
+ ### Fixed
20
+
21
+ - **Cards no longer touch in a resource's Settings.** In the two-column Settings form, a full-width card (such as **Volumes**) sat 2 px under the last card of a column instead of the usual 14 px, so the two looked glued together. The gap is now the same everywhere.
22
+
23
+ ## [1.12.0] - 2026-10-23
24
+
25
+ ### Added
26
+
27
+ - **Inspect a Docker resource in every mode.** Inspect is now also in **More** for an existing container (it was only there for a container hive-am creates), and it shows the container's **Labels**, grouped as hive-am's, compose's and the rest. It has two panels: **Configured in hive-am**, always there (the settings of a container hive-am creates, the name of an existing one, or what the compose file says about a service), and **What Docker says**, the live detail. A stopped container is inspected as before, without the shell button.
28
+ - **Raw `docker inspect`.** Inspect has a **Raw JSON** tab with the whole `docker inspect`, highlighted like the code explorer (same themes and whitespace setting, a Web Worker for big ones), with line numbers, a search box that jumps from match to match and **Copy JSON**, which copies exactly what is shown. The server hides the value of variables whose name looks like a secret, and the password of a URL with credentials in any other variable (`postgres://user:••••••••@host/db`), until you press **Reveal**; labels and the command are not hidden. Each compose service has it from its own Inspect. New routes: `GET /api/objects/:id/inspect/raw` and `GET /api/docker/inspect?kind=container|image|volume|network&ref=`, open like the other reads of resources (the app is also opened through other names on the network); the hiding above is done by the server, but it is for the screen, not a security boundary: any page can ask for `reveal=1`, and `GET /api/objects/:id/inspect` already carries the variables in the clear.
29
+ - **Pick networks and volumes from Docker.** In **Create a container**, the **Network** is a picker with the networks of this machine and the **Volumes** are rows: a volume of this machine (picked or typed; a new name is created by Docker on the first start, and the row says so, unless Docker is not answering) or a folder, the path in the container and **Read only**. Images, networks and volumes say how many containers use them. A volume row needs both sides: one with only a source or only a path says so, and the server refuses `data:` or `:/data`, which used to fail only in `docker run`. The rows keep the same saved format, so resources saved before show as they were. New routes `GET /api/docker/volumes` and `GET /api/docker/networks`; `GET /api/docker/containers` gives each container's `mounts` and `networks`, and `GET /api/docker/images` each image's `inUse`. Everything here only reads Docker.
30
+ - **Create and delete networks and volumes from the Docker form.** A network that is not on the machine says the container will not start without it and offers **Create network**; a new volume name offers **Create it now**. Each is an immediate action with a notice, labelled `hive-am.created=1` and marked *created by hive-am*; asking again for one that exists changes nothing. A network or a volume hive-am created that no container uses (running or stopped) can be deleted from the suggestions after a confirmation; the server checks the label and the use again against Docker and refuses another's, Docker's own networks, anonymous volumes and anything in use. New routes, local app only: `POST /api/docker/networks`, `POST /api/docker/volumes`, `DELETE /api/docker/networks/:name`, `DELETE /api/docker/volumes/:name`.
31
+ - **Build an image from a Dockerfile.** Under the image of **Create a container**, **Build from a Dockerfile** takes the file, the build folder (its own by default) and the image name (suggested from the folder), runs `docker build` with the label `hive-am.created=1` and picks the image when it ends; only a regular file named like a Dockerfile (`Dockerfile`, `Containerfile`, `Dockerfile.<suffix>`, `<name>.Dockerfile`, its real name too) is built, because a failure shows the last lines of the output. It waits up to 15 minutes, runs the Dockerfile's steps on this machine and may download base images. The folder picker can now list Dockerfiles (`files=dockerfile,containerfile`). New route, local app only: `POST /api/docker/images/build`.
32
+ - **Edit or copy the compose file and a Dockerfile (server).** New routes, local app only (reading included), to read, save and copy the compose file of a compose resource (always the path saved in it, never one from the page) and a Dockerfile (a regular file named like one, its real name too, up to 512 KB). A save is atomic (a temporary file in the same folder, then a rename: the original survives a failed write) and carries the etag read with the file, so a file changed on disk since is a 409 instead of being overwritten. A copy goes next to the original and never replaces a file without `overwrite`; a compose copy can become the resource's file. Saving the compose file does not touch Docker: it answers whether `docker compose config` reads it (an invalid file is saved and marked) and whether services are running.
33
+ - **Edit or copy the compose file and a Dockerfile.** A compose resource has a **File** tab with its compose file in the code editor: **Save** (after a confirmation that names the file), **Save as copy** next to it (the resource can use the copy, on by default) and **Discard**. If the file changed on disk since it was opened you choose to reload it or overwrite it; if services are running, a notice offers **Create again**; a file Docker cannot read is saved and its error shown. In **Build from a Dockerfile**, **Edit** and **Save as copy** open the Dockerfile in the same editor, and a copy becomes the file to build; saving builds nothing. Windows line endings (`\r\n`) and the file's mode are kept when saving. Going to another resource from the list does not ask about unsaved edits. An existing container offers no file editing.
34
+ - **Additional networks and more options of `docker run`.** A container hive-am creates can be connected to **additional networks** besides its main one; they are connected and disconnected in place (`docker network connect / disconnect`, running or stopped, same container), never by creating it again. **More options of docker run** adds a fixed, checked list: labels, user, working folder, entrypoint and hostname; any other option in the settings (such as `privileged`) is refused instead of ignored. The settings of a container saved before give the same `hive-am.cfg` hash as before, so updating does not create any container again.
35
+ - **Create a managed copy of an existing container.** hive-am still never changes a container it did not create; **Create a managed copy** (in **More** and in its Settings) reads its `docker inspect` and opens the form of a new container hive-am creates, filled in with its image, ports, named volumes and folders, its own variables, networks, limits and options, and lists what could not be copied (anonymous volumes, random ports). The original is only read. New route, local app only: `GET /api/objects/:id/docker/managed-copy`.
36
+ - **Relations map of a Docker resource.** A new **Relations** tab (every Docker mode) maps the resource's neighbourhood only: its containers (for compose, those of its project), their image, the volumes and the folders of this machine they mount (shown as different kinds of node), their networks and the other containers that share one of those, at most 12 per shared node with *and N more*, while Docker's own networks only count their users. Choosing a node lights what it is joined to and shows its detail below, where **Inspect** (Summary and Raw JSON for hive-am's containers, the raw `docker inspect` for the rest) opens in the same panel; a container of a hive-am resource offers **Edit settings**. Read only, no search and no whole-machine view. `GET /api/docker/containers` now gives each container's `object`, `project` and `service`.
37
+
38
+ ### Changed
39
+
40
+ - **Save and apply, with a confirmation before a container is created again.** In the Settings of a container hive-am creates, *Save and restart* becomes **Save and apply**, for a running or a stopped container. It first asks what would change: additional networks are applied in place without asking; anything else needs the container to be created again, and a confirmation lists each change and its kind, what is lost (files written inside, logs, restart count, id and IP), the anonymous volumes left behind unmounted and the commands that will run, with secrets hidden. Nothing in Docker changes until it is confirmed, and the server refuses an unconfirmed recreate. **Save** alone still never touches Docker. Containers created from now on carry the label `hive-am.networks=1`, and hive-am keeps their networks as configured: a network connected by hand is disconnected on the next start. New routes, local app only: `POST /api/objects/:id/docker/plan` and `POST /api/objects/:id/docker/apply`.
41
+ - **Inspect explains instead of failing.** A container hive-am has not created yet, an existing container Docker cannot find, a compose service with no container and Docker not installed or not running no longer show a bare error: Inspect shows what is configured and says why Docker has nothing. When the settings changed since hive-am created the container, it says the container will be created again on the next start.
42
+ - **`GET /api/objects/:id/inspect` changed shape.** It answers `{ exists, configured, ... }`: with `exists: true` the detail of `docker inspect` and `cfgStale`, with `exists: false` a `reason` (`not-created`, `not-found`, `no-service`, `no-docker`).
43
+
44
+ ### Removed
45
+
46
+ - **App templates for Docker resources.** Creating a Docker resource no longer offers **From a template** (the ready-made Redis, PostgreSQL, MySQL and other services), so hive-am shows no predefined catalog of services or images and depends on no registry for one; the form opens in **Create a container**, and the other modes are unchanged. `GET /api/object-templates` and `POST /api/objects/from-template` are gone. A compose resource made from a template keeps working as before: its file stays in the data folder (`stacks/<name>/`) and is shown, started and edited (**File** tab) like any other compose file; nothing is moved or deleted.
47
+
9
48
  ## [1.11.0] - 2026-10-23
10
49
 
11
50
  ### Added
@@ -302,7 +341,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
302
341
  - Complete documentation in English and Spanish.
303
342
  - npm package with the `hive-am` command.
304
343
 
305
- [Unreleased]: https://github.com/hughcbscrt/hive-am/compare/v1.11.0...HEAD
344
+ [Unreleased]: https://github.com/hughcbscrt/hive-am/compare/v1.12.1...HEAD
345
+ [1.12.1]: https://github.com/hughcbscrt/hive-am/compare/v1.12.0...v1.12.1
346
+ [1.12.0]: https://github.com/hughcbscrt/hive-am/compare/v1.11.0...v1.12.0
306
347
  [1.11.0]: https://github.com/hughcbscrt/hive-am/compare/v1.10.1...v1.11.0
307
348
  [1.10.1]: https://github.com/hughcbscrt/hive-am/compare/v1.10.0...v1.10.1
308
349
  [1.10.0]: https://github.com/hughcbscrt/hive-am/compare/v1.9.1...v1.10.0
package/README.md CHANGED
@@ -1,9 +1,7 @@
1
1
  <p align="center">
2
- <img src="web/public/logo.png" alt="hive-am" width="180" />
2
+ <img src="web/public/logo-completo.png" alt="hive-am" width="100%" />
3
3
  </p>
4
4
 
5
- <h1 align="center">hive-am</h1>
6
-
7
5
  <p align="center">
8
6
  <strong>A colony of coding agents on your machine.</strong><br />
9
7
  Orchestrators and subagents over <b>Claude Code</b>, <b>OpenCode</b>, <b>Kiro</b> and <b>Cursor</b>, with the real sessions of each CLI, a browser UI and Telegram.
@@ -146,6 +144,8 @@ A step by step guide with screenshots, from installing hive-am to running resour
146
144
 
147
145
  Full documentation in English and Spanish: **[English](docs/reference/en/README.md)** · **[Español](docs/reference/es/README.md)**.
148
146
 
147
+ Diagrams of how it works inside, with the file, function and line behind each step: **[English](docs/diagrams/en/README.md)** · **[Español](docs/diagrams/es/README.md)**.
148
+
149
149
  | Topic | Where |
150
150
  |---|---|
151
151
  | Overview and architecture | [docs/01](docs/reference/en/01-overview.md) |
@@ -166,3 +166,7 @@ Full documentation in English and Spanish: **[English](docs/reference/en/README.
166
166
  - A turn in flight during a crash is lost; the session is intact and the agent is idle on restart.
167
167
  - Instructions are advice to the model; what the server enforces is who an agent can delegate to, who a connection answers, which files can be sent, and the permission level.
168
168
  - There is no automated test suite. Features are checked with simulations against real agents (`server/scripts/sim-*.ts`).
169
+
170
+ <p align="center">
171
+ <img src="web/public/logo.png" alt="hive-am" width="84" />
172
+ </p>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hive-am",
3
- "version": "1.11.0",
3
+ "version": "1.12.1",
4
4
  "description": "A colony of coding agents: orchestrators and subagents over Claude Code, OpenCode, Kiro and Cursor, with real session resume, a browser UI and Telegram.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -8,8 +8,9 @@ route('GET', '/api/fs/dirs', ({ url }) => {
8
8
  const p = resolve(url.searchParams.get('path') || homedir());
9
9
  if (!existsSync(p) || !statSync(p).isDirectory())
10
10
  throw bad('Not a folder');
11
- // `files=yml,yaml` also lists the files with those extensions (to pick a file, e.g. a compose file).
12
- const exts = (url.searchParams.get('files') ?? '').split(',').map((x) => x.trim().toLowerCase().replace(/^\./, '')).filter((x) => /^[a-z0-9]{1,8}$/.test(x));
11
+ // `files=yml,yaml` also lists the files with those extensions (to pick a file, e.g. a compose file). A name with no dot is its own
12
+ // extension, so `files=dockerfile,containerfile` lists `Dockerfile`, `Containerfile` and `web.Dockerfile`.
13
+ const exts = (url.searchParams.get('files') ?? '').split(',').map((x) => x.trim().toLowerCase().replace(/^\./, '')).filter((x) => /^[a-z0-9]{1,16}$/.test(x));
13
14
  let dirs = [], files = [];
14
15
  try {
15
16
  const all = readdirSync(p, { withFileTypes: true });
@@ -1,9 +1,11 @@
1
1
  import { notifyAgentsChanged } from '../../runtime.js';
2
2
  import { DRIVER_PROPS } from '../../objects/driver-props.js';
3
- import { composeServices, listContainers, listImages } from '../../objects/docker-info.js';
3
+ import { composeServices, inspectMachine, listContainers, listImages, listNetworks, listVolumes } from '../../objects/docker-info.js';
4
+ import { buildImage, createMade, removeMade } from '../../objects/docker.js';
5
+ import { copySource, readSource, writeSource } from '../../objects/files.js';
4
6
  import { agentObjectAction, agentObjectLogs, agentObjects } from '../../objects/agent.js';
5
7
  import { agentDb } from '../../objects/agent-db.js';
6
- import { dbColumnsOf, dbHistory, dbHistoryClear, dbRun, dbSavedAdd, dbSavedList, dbSavedRemove, dbSavedUpdate, dbSchemaOf, dbTestConfig, createObject, statsOf, httpDescribe, httpSession, httpSessionClear, httpSessionSet, httpInspect, httpExport, httpFileRead, httpFileWrite, httpFileCreate, httpRun, httpHistory, httpDocs, httpHistoryClear, httpScan, listObjects, setObjectManager, setAgentObjects, objectAction, objectServiceAction, listTemplates, createFromTemplate, objectInspect, objectLogs, removeObject, updateObject } from '../../objects/index.js';
8
+ import { dbColumnsOf, dbHistory, dbHistoryClear, dbRun, dbSavedAdd, dbSavedList, dbSavedRemove, dbSavedUpdate, dbSchemaOf, dbTestConfig, createObject, statsOf, httpDescribe, httpSession, httpSessionClear, httpSessionSet, httpInspect, httpExport, httpFileRead, httpFileWrite, httpFileCreate, httpRun, httpHistory, httpDocs, httpHistoryClear, httpScan, listObjects, setObjectManager, setAgentObjects, objectAction, objectServiceAction, composeFileCopy, composeFileRead, composeFileWrite, objectApply, objectInspect, objectManagedCopy, objectPlan, objectInspectRaw, objectLogs, removeObject, updateObject } from '../../objects/index.js';
7
9
  import { body } from '../body.js';
8
10
  import { notFound } from '../errors.js';
9
11
  import { requireLocal } from '../guards.js';
@@ -28,18 +30,43 @@ write('POST', '/api/objects', (b) => createObject(b));
28
30
  write('PATCH', '/api/objects/:id', (b, { params }) => updateObject(params[0], b));
29
31
  route('DELETE', '/api/objects/:id', async ({ req, params }) => { local(req); if (!(await removeObject(params[0])))
30
32
  throw notFound('Object not found'); return { ok: true }; });
31
- route('GET', '/api/object-templates', () => listTemplates());
32
33
  write('PUT', '/api/objects/:id/manager', (b, { params }) => { const r = setObjectManager(params[0], b.agent_id); if (r.skillAdded)
33
34
  notifyAgentsChanged(); return r; });
34
35
  write('PUT', '/api/agents/:id/resources', (b, { params }) => { const r = setAgentObjects(params[0], b.object_ids); if (r.skillAdded)
35
36
  notifyAgentsChanged(); return r; });
36
- write('POST', '/api/objects/from-template', (b) => createFromTemplate(b));
37
+ // Reads, open like the other reads of resources (the app is also reached by other names on the network). Secret-looking variables and the
38
+ // passwords of URLs come hidden in the raw one unless `reveal=1`.
37
39
  route('GET', '/api/objects/:id/inspect', ({ params, url }) => objectInspect(params[0], p(url, 'service')));
40
+ route('GET', '/api/objects/:id/inspect/raw', ({ params, url }) => objectInspectRaw(params[0], p(url, 'service'), url.searchParams.get('reveal') === '1'));
41
+ // The whole `docker inspect` of anything on this machine (container, image, volume, network).
42
+ route('GET', '/api/docker/inspect', ({ url }) => inspectMachine(url.searchParams.get('kind') ?? '', url.searchParams.get('ref') ?? '', url.searchParams.get('reveal') === '1'));
38
43
  write('POST', '/api/objects/:id/service', (b, { params }) => objectServiceAction(params[0], b.service, b.action));
39
44
  write('POST', '/api/objects/:id/action', (b, { params }) => objectAction(params[0], b.action));
40
- // What Docker knows, to suggest in the forms (containers, images, the services of a compose file).
45
+ // Changing a container hive-am created (local app only): what applying the settings in the form would do (Docker only read), then applying
46
+ // the saved ones (recreating needs `confirm: true`). An existing container is never changed: it is read to fill the form of a managed copy.
47
+ write('POST', '/api/objects/:id/docker/plan', (b, { params }) => objectPlan(params[0], b.config));
48
+ write('POST', '/api/objects/:id/docker/apply', (b, { params }) => objectApply(params[0], b));
49
+ guarded('/api/objects/:id/docker/managed-copy', ({ params }) => objectManagedCopy(params[0]));
50
+ // What Docker knows, to suggest in the forms (containers, images, volumes, networks, the services of a compose file).
41
51
  route('GET', '/api/docker/containers', () => listContainers());
42
52
  route('GET', '/api/docker/images', () => listImages());
53
+ route('GET', '/api/docker/volumes', () => listVolumes());
54
+ route('GET', '/api/docker/networks', () => listNetworks());
55
+ // What the Docker form creates when asked (local app only): a network, a volume, an image from a Dockerfile. A network or a volume is
56
+ // removed only if hive-am created it and no container uses it (checked in objects/docker.ts, against Docker).
57
+ write('POST', '/api/docker/networks', (b) => createMade('network', b.name));
58
+ write('POST', '/api/docker/volumes', (b) => createMade('volume', b.name));
59
+ guarded('/api/docker/networks/:name', ({ params }) => removeMade('network', params[0]), 'DELETE');
60
+ guarded('/api/docker/volumes/:name', ({ params }) => removeMade('volume', params[0]), 'DELETE');
61
+ write('POST', '/api/docker/images/build', (b) => buildImage(b));
62
+ // Editing the compose file of a resource (its path is the saved one) and a Dockerfile (path from the page, checked by name in objects/files.ts),
63
+ // in place or as a copy next to it. They are the person's own files, which may hold secrets: local app only, reading included. A stale etag is 409.
64
+ guarded('/api/objects/:id/docker/file', ({ params }) => composeFileRead(params[0]));
65
+ write('PUT', '/api/objects/:id/docker/file', (b, { params }) => composeFileWrite(params[0], b.content, b.etag));
66
+ write('POST', '/api/objects/:id/docker/file/copy', (b, { params }) => composeFileCopy(params[0], b));
67
+ guarded('/api/docker/dockerfile', ({ url }) => readSource('dockerfile', url.searchParams.get('path') ?? ''));
68
+ write('PUT', '/api/docker/dockerfile', (b) => writeSource('dockerfile', b.path, b.content, b.etag));
69
+ write('POST', '/api/docker/dockerfile/copy', (b) => copySource('dockerfile', b.path, b.name, b.content, b.overwrite === true));
43
70
  route('GET', '/api/docker/compose-services', ({ url }) => composeServices(url.searchParams.get('file') ?? ''));
44
71
  route('GET', '/api/objects/:id/stats', ({ params }) => statsOf(params[0]));
45
72
  route('GET', '/api/objects/:id/http', ({ params }) => httpScan(params[0]));
@@ -1,10 +1,10 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { basename, dirname } from 'node:path';
3
3
  import { execCapture } from './exec.js';
4
- import { ObjectError } from './model.js';
4
+ import { CONTAINER, IMAGE, ObjectError } from './model.js';
5
5
  /**
6
- * What Docker knows, to suggest in the forms: the containers on this machine, the images, and the services of a compose file. Read-only;
7
- * when Docker is not available the lists are empty and say why.
6
+ * What Docker knows, to suggest in the forms: the containers on this machine, the images, volumes and networks (each with how many
7
+ * containers use it), and the services of a compose file. Read-only; when Docker is not available the lists are empty and say why.
8
8
  */
9
9
  const run = async (args, o = {}) => {
10
10
  const r = await execCapture('docker', args, { cwd: o.cwd, timeout: o.timeout ?? 15_000, env: { ...process.env, DOCKER_CLI_HINTS: 'false' } });
@@ -16,20 +16,81 @@ const lines = (out) => out.trim().split('\n').flatMap((l) => { try {
16
16
  catch {
17
17
  return [];
18
18
  } });
19
- export async function listContainers() {
20
- const r = await run(['ps', '-a', '--format', '{{json .}}']);
19
+ /** Docker is installed but its daemon does not answer (stopped, or no permission on its socket). */
20
+ export const NO_DAEMON = /Cannot connect to the Docker daemon|Is the docker daemon running|error during connect|permission denied while trying to connect/i;
21
+ /** Variable names whose value is hidden unless asked for (the same pattern the interface uses for the variables it shows). */
22
+ export const SECRET_NAME = /pass|secret|token|api[_-]?key|auth|credential|private/i;
23
+ export const MASK = '••••••••';
24
+ /** The password of a URL with credentials (`scheme://user:password@host`), wherever it sits in a value. */
25
+ const URL_PASSWORD = /([a-z][a-z0-9+.-]*:\/\/[^\s/:@]*:)[^\s/@]+@/gi;
26
+ /** A `NAME=value` variable with its secret hidden: the whole value when the name looks like a secret, else the password of any URL in it. */
27
+ export function maskVar(item) {
28
+ const i = item.indexOf('=');
29
+ if (i < 0)
30
+ return item;
31
+ if (SECRET_NAME.test(item.slice(0, i)))
32
+ return `${item.slice(0, i + 1)}${MASK}`;
33
+ return item.slice(0, i + 1) + item.slice(i + 1).replace(URL_PASSWORD, `$1${MASK}@`);
34
+ }
35
+ const failed = (r) => r.err.trim().split('\n').pop()?.slice(0, 200);
36
+ const list = (v) => String(v ?? '').split(',').map((x) => x.trim()).filter(Boolean);
37
+ const labelled = (labels, re) => re.test(String(labels ?? ''));
38
+ /** One label of `docker ps` (`k=v,k=v`); a value with a comma is cut there, which these labels (ids and names) never have. */
39
+ const label = (labels, key) => new RegExp(`(?:^|,)${key.replace(/\./g, '\\.')}=([^,]*)`).exec(String(labels ?? ''))?.[1] || undefined;
40
+ const BUILTIN = ['bridge', 'host', 'none'];
41
+ let reading = null;
42
+ /**
43
+ * The containers of this machine. The form asks for containers, images, volumes and networks at once, and the last three count who uses
44
+ * them: while one `docker ps` is running, every other caller gets its answer instead of starting another one.
45
+ */
46
+ export function listContainers() {
47
+ reading ??= readContainers().finally(() => { reading = null; });
48
+ return reading;
49
+ }
50
+ async function readContainers() {
51
+ // --no-trunc: without it Docker cuts the mounts to a few characters ("/home/you/Doc…").
52
+ const r = await run(['ps', '-a', '--no-trunc', '--format', '{{json .}}']);
21
53
  if (r.code !== 0)
22
- return { available: false, error: r.err.trim().split('\n').pop()?.slice(0, 200), containers: [] };
23
- const containers = lines(r.out).map((j) => ({ id: String(j.ID), name: String(j.Names), image: String(j.Image), state: String(j.State), status: String(j.Status), ports: String(j.Ports ?? ''), managed: /(^|,)hive-am\.object=/.test(String(j.Labels ?? '')) }));
24
- return { available: true, containers: containers.sort((a, b) => a.name.localeCompare(b.name)) };
54
+ return { available: false, error: failed(r), containers: [] };
55
+ const all = lines(r.out).map((j) => ({
56
+ id: String(j.ID).slice(0, 12), name: String(j.Names), image: String(j.Image), state: String(j.State), status: String(j.Status), ports: String(j.Ports ?? ''),
57
+ managed: labelled(j.Labels, /(^|,)hive-am\.object=/), mounts: list(j.Mounts), networks: list(j.Networks),
58
+ object: label(j.Labels, 'hive-am.object'), project: label(j.Labels, 'com.docker.compose.project'), service: label(j.Labels, 'com.docker.compose.service'),
59
+ }));
60
+ return { available: true, containers: all.sort((a, b) => a.name.localeCompare(b.name)) };
25
61
  }
62
+ /** `nginx` and `nginx:latest` are the same image for counting who uses it. */
63
+ const sameImage = (ref, image) => ref === image || ref === `${image}:latest`;
26
64
  export async function listImages() {
27
- const r = await run(['images', '--format', '{{json .}}']);
65
+ const [r, c] = await Promise.all([run(['images', '--format', '{{json .}}']), listContainers()]);
28
66
  if (r.code !== 0)
29
- return { available: false, error: r.err.trim().split('\n').pop()?.slice(0, 200), images: [] };
30
- const images = lines(r.out).filter((j) => j.Repository && j.Repository !== '<none>').map((j) => ({ ref: j.Tag && j.Tag !== '<none>' ? `${j.Repository}:${j.Tag}` : String(j.Repository), id: String(j.ID), size: String(j.Size ?? ''), created: String(j.CreatedSince ?? '') }));
67
+ return { available: false, error: failed(r), images: [] };
68
+ const images = lines(r.out).filter((j) => j.Repository && j.Repository !== '<none>').map((j) => {
69
+ const ref = j.Tag && j.Tag !== '<none>' ? `${j.Repository}:${j.Tag}` : String(j.Repository);
70
+ return { ref, id: String(j.ID), size: String(j.Size ?? ''), created: String(j.CreatedSince ?? ''), inUse: c.containers.filter((x) => sameImage(ref, x.image)).length };
71
+ });
31
72
  return { available: true, images: [...new Map(images.map((i) => [i.ref, i])).values()].sort((a, b) => a.ref.localeCompare(b.ref)) };
32
73
  }
74
+ export async function listVolumes() {
75
+ const [r, c] = await Promise.all([run(['volume', 'ls', '--format', '{{json .}}']), listContainers()]);
76
+ if (r.code !== 0)
77
+ return { available: false, error: failed(r), volumes: [] };
78
+ const volumes = lines(r.out).map((j) => {
79
+ const name = String(j.Name);
80
+ return { name, driver: String(j.Driver ?? ''), anonymous: /^[0-9a-f]{64}$/.test(name) || labelled(j.Labels, /(^|,)com\.docker\.volume\.anonymous=/), managed: labelled(j.Labels, /(^|,)hive-am\.created=1(,|$)/), inUse: c.containers.filter((x) => x.mounts.includes(name)).length };
81
+ });
82
+ return { available: true, volumes: volumes.sort((a, b) => Number(a.anonymous) - Number(b.anonymous) || a.name.localeCompare(b.name)) };
83
+ }
84
+ export async function listNetworks() {
85
+ const [r, c] = await Promise.all([run(['network', 'ls', '--format', '{{json .}}']), listContainers()]);
86
+ if (r.code !== 0)
87
+ return { available: false, error: failed(r), networks: [] };
88
+ const networks = lines(r.out).map((j) => {
89
+ const name = String(j.Name);
90
+ return { name, id: String(j.ID), driver: String(j.Driver ?? ''), scope: String(j.Scope ?? ''), builtin: BUILTIN.includes(name), managed: labelled(j.Labels, /(^|,)hive-am\.created=1(,|$)/), inUse: c.containers.filter((x) => x.networks.includes(name)).length };
91
+ });
92
+ return { available: true, networks: networks.sort((a, b) => Number(b.builtin) - Number(a.builtin) || a.name.localeCompare(b.name)) };
93
+ }
33
94
  /** Without Docker's help: the keys under a top-level `services:` (enough for ordinary compose files). */
34
95
  function servicesByHand(text) {
35
96
  const out = [];
@@ -73,3 +134,49 @@ export async function composeServices(file) {
73
134
  catch { /* unreadable */ }
74
135
  return { services: servicesByHand(text).sort(), project, source: 'file', error: r.err.trim().split('\n').pop()?.slice(0, 200) };
75
136
  }
137
+ const KINDS = ['container', 'image', 'volume', 'network'];
138
+ /** The `NAME=value` items of `Config.Env` (and the older `ContainerConfig.Env` of an image) with their secrets hidden (`maskVar`). */
139
+ function maskEnv(items) {
140
+ let n = 0;
141
+ for (const it of items)
142
+ for (const cfg of [it?.Config, it?.ContainerConfig]) {
143
+ if (!Array.isArray(cfg?.Env))
144
+ continue;
145
+ cfg.Env = cfg.Env.map((e) => {
146
+ const s = String(e), m = maskVar(s);
147
+ if (m === s)
148
+ return e;
149
+ n++;
150
+ return m;
151
+ });
152
+ }
153
+ return n;
154
+ }
155
+ /**
156
+ * The whole `docker inspect` of a container, an image, a volume or a network, as indented JSON. The values of the variables whose name
157
+ * looks like a secret, and the passwords of URLs in the others, are hidden unless `reveal` (other fields, like labels or the command, can
158
+ * carry secrets too and are not touched).
159
+ * `available: false` when Docker is not installed or does not answer.
160
+ */
161
+ export async function inspectMachine(kind, ref, reveal = false) {
162
+ if (!KINDS.includes(kind))
163
+ throw new ObjectError('Say what to inspect: a container, an image, a volume or a network');
164
+ if (!(kind === 'image' ? IMAGE : CONTAINER).test(ref))
165
+ throw new ObjectError(`That is not a valid ${kind} name`);
166
+ const r = await run([kind, 'inspect', ref], { timeout: 20_000 });
167
+ if (r.code !== 0) {
168
+ const error = r.err.trim().split('\n').pop()?.slice(0, 200) || `docker exited with ${r.code}`;
169
+ if (/not installed/.test(r.err) || NO_DAEMON.test(r.err))
170
+ return { available: false, error, text: '', masked: 0 };
171
+ throw new ObjectError(`Docker has no ${kind} ${ref}`);
172
+ }
173
+ let items;
174
+ try {
175
+ items = JSON.parse(r.out);
176
+ }
177
+ catch {
178
+ throw new ObjectError(`docker ${kind} inspect did not return JSON`);
179
+ }
180
+ const masked = reveal ? 0 : maskEnv(items);
181
+ return { available: true, text: JSON.stringify(items, null, 2), masked };
182
+ }