herm-tui 1.9.0-dev.4 → 1.9.0-dev.41

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 CHANGED
@@ -93,14 +93,53 @@ See [`.env.example`](./.env.example) for rarely-needed overrides.
93
93
 
94
94
  ### Share and install eikons
95
95
 
96
- - Press `M` from Gallery to enter the native Eikon Marketplace.
97
- - Search shared catalog entries, preview the selected eikon, install without
98
- activating, then use it when ready.
99
- - Use `eikon.liftaris.dev` as a discovery mirror only; it previews catalog
100
- entries and points back to Herm for native install/use.
101
- - Submit local non-bundled eikons for review with `u`; Herm shows the exact
102
- preflight bundle before submission and blocks published marketplace installs
103
- from duplicate review submission.
96
+ Eikons are 48×24 terminal avatars. The shipped lifecycle is deterministic:
97
+ discover, inspect, install, use, update, and remove.
98
+
99
+ In Herm:
100
+
101
+ - Open Eikon Marketplace, or run `/marketplace`, to browse shared catalog
102
+ entries.
103
+ - Preview rows before installing. Trust is shown as `Verified`, `Unverified`,
104
+ or `Mismatch` beside source and compatibility state.
105
+ - Install adds the eikon to your local library without activating it.
106
+ - Use selects an installed eikon as the active avatar.
107
+ - Update or remove an active eikon only after confirming that the active
108
+ avatar's backing package will change or be cleared.
109
+
110
+ From the shell:
111
+
112
+ ```bash
113
+ herm eikon search [query]
114
+ herm eikon inspect <name|github.com/user/repo/eikon-name|dir>
115
+ herm eikon install <name|github.com/user/repo/eikon-name|dir>
116
+ herm eikon use <name>
117
+ herm eikon info <name>
118
+ herm eikon update <name> --active-ok
119
+ herm eikon remove <name> --active-ok
120
+ ```
121
+
122
+ `install` never activates. `use` is the activation action. JSON output is
123
+ available for automation with `--json`.
124
+
125
+ Default Marketplace installs fetch built package artifacts referenced by the
126
+ catalog, not creator repositories. Direct GitHub installs are for sharing
127
+ outside the default catalog and support both single-package repos and
128
+ multi-eikon catalog repos addressed as `github.com/user/repo/eikon-name`.
129
+ Private GitHub repos use normal git authentication.
130
+
131
+ Creators can share Eikons through normal GitHub repositories. For official
132
+ registry listing, use Eikon → Studio/Gallery → submit after baking. Herm previews
133
+ the exact public bundle, asks for title/author/description/glyph, and either
134
+ creates a GitHub PR through local `gh` auth or gives a browser/manual PR
135
+ fallback with copyable bundle and PR text. Direct-install repos can still be
136
+ prepared with upstream `eikon pack`, `eikon index`, and `eikon manifest`.
137
+ `eikon publish` remains the lower-level GitHub PR contribution helper for the
138
+ configured/default catalog repo; it is not a hosted marketplace account, upload,
139
+ dashboard, or moderation flow.
140
+
141
+ Use `eikon.liftaris.dev` as a discovery gallery only; it previews catalog
142
+ entries and gives copyable Herm install instructions.
104
143
 
105
144
  Herm owns native Marketplace behavior. The eikon repo owns the registry,
106
145
  browser mirror, shared catalog/player exports, install resolver, and publish
@@ -0,0 +1,123 @@
1
+ {
2
+ "kind": "eikon.package",
3
+ "schemaVersion": "1.0",
4
+ "id": "liftaris/nous",
5
+ "name": "nous",
6
+ "version": "1.0.0",
7
+ "display": {
8
+ "title": "nous",
9
+ "author": "kaio",
10
+ "glyph": "⬡"
11
+ },
12
+ "compatibility": {
13
+ "eikon": ">=1 <2"
14
+ },
15
+ "legacy": {
16
+ "sourceFormat": "pre-launch .eikon draft",
17
+ "migration": "converted",
18
+ "notes": [
19
+ "moved legacy metadata: author, created, source_url"
20
+ ]
21
+ },
22
+ "source": {
23
+ "base": "blobs/sha256/8c4dc411ca159253cc90fa31f5263799054592e6eaeb4e5c99881c8cac29e67b",
24
+ "states": {
25
+ "idle": {
26
+ "file": "blobs/sha256/481c010826d7cacb9c419455bd0f73d020f66492a1236b37e4e53353b7965902"
27
+ },
28
+ "listening": {
29
+ "file": "blobs/sha256/94f0f4f710c708f516128125142d5dce701f83558e9fad444c1bb376ee3c1fea"
30
+ },
31
+ "thinking": {
32
+ "file": "blobs/sha256/5ae0eb2e1889c72bed69144998acc6f80b9d11b67beffccd972842a9d6ed0f24"
33
+ },
34
+ "speaking": {
35
+ "file": "blobs/sha256/5c15fd3db3fba93b378eb5b23ab9ecf283e8fb5a08828f1393e91b323b65eab2"
36
+ },
37
+ "working": {
38
+ "file": "blobs/sha256/f63a864e84c4b4f672d6b02a4bcf610b2881fcf64166273b29a642fb76e00f09"
39
+ },
40
+ "error": {
41
+ "file": "blobs/sha256/5d1a1fa59ec9091d9c03d99728b15287ef58d397ea2c787a65c55519482683c8"
42
+ }
43
+ }
44
+ },
45
+ "entrypoints": {
46
+ "default": "nous.eikon"
47
+ },
48
+ "files": [
49
+ {
50
+ "path": "nous.eikon",
51
+ "role": "runtime",
52
+ "mediaType": "application/vnd.eikon.stream+jsonl",
53
+ "size": 139822,
54
+ "digest": "sha256:6a74e9ae20ecafe6cf268230272582ef618b31927cd13e11ce59ed8931413883",
55
+ "encoding": "gzip",
56
+ "decodedSize": 1401439,
57
+ "decodedDigest": "sha256:b2685743b05ddf201920b69db58640d4fab644e637fa7d313c6dfa215fa41fb7"
58
+ },
59
+ {
60
+ "path": "blobs/sha256/8c4dc411ca159253cc90fa31f5263799054592e6eaeb4e5c99881c8cac29e67b",
61
+ "role": "source.base",
62
+ "mediaType": "image/png",
63
+ "size": 98644,
64
+ "digest": "sha256:8c4dc411ca159253cc90fa31f5263799054592e6eaeb4e5c99881c8cac29e67b"
65
+ },
66
+ {
67
+ "path": "blobs/sha256/481c010826d7cacb9c419455bd0f73d020f66492a1236b37e4e53353b7965902",
68
+ "role": "source.clip",
69
+ "mediaType": "video/mp4",
70
+ "size": 105789,
71
+ "digest": "sha256:481c010826d7cacb9c419455bd0f73d020f66492a1236b37e4e53353b7965902",
72
+ "signal": "state.idle"
73
+ },
74
+ {
75
+ "path": "blobs/sha256/94f0f4f710c708f516128125142d5dce701f83558e9fad444c1bb376ee3c1fea",
76
+ "role": "source.clip",
77
+ "mediaType": "video/mp4",
78
+ "size": 275110,
79
+ "digest": "sha256:94f0f4f710c708f516128125142d5dce701f83558e9fad444c1bb376ee3c1fea",
80
+ "signal": "state.listening"
81
+ },
82
+ {
83
+ "path": "blobs/sha256/5ae0eb2e1889c72bed69144998acc6f80b9d11b67beffccd972842a9d6ed0f24",
84
+ "role": "source.clip",
85
+ "mediaType": "video/mp4",
86
+ "size": 174520,
87
+ "digest": "sha256:5ae0eb2e1889c72bed69144998acc6f80b9d11b67beffccd972842a9d6ed0f24",
88
+ "signal": "state.thinking"
89
+ },
90
+ {
91
+ "path": "blobs/sha256/5c15fd3db3fba93b378eb5b23ab9ecf283e8fb5a08828f1393e91b323b65eab2",
92
+ "role": "source.clip",
93
+ "mediaType": "video/mp4",
94
+ "size": 96790,
95
+ "digest": "sha256:5c15fd3db3fba93b378eb5b23ab9ecf283e8fb5a08828f1393e91b323b65eab2",
96
+ "signal": "state.speaking"
97
+ },
98
+ {
99
+ "path": "blobs/sha256/f63a864e84c4b4f672d6b02a4bcf610b2881fcf64166273b29a642fb76e00f09",
100
+ "role": "source.clip",
101
+ "mediaType": "video/mp4",
102
+ "size": 160072,
103
+ "digest": "sha256:f63a864e84c4b4f672d6b02a4bcf610b2881fcf64166273b29a642fb76e00f09",
104
+ "signal": "state.working"
105
+ },
106
+ {
107
+ "path": "blobs/sha256/5d1a1fa59ec9091d9c03d99728b15287ef58d397ea2c787a65c55519482683c8",
108
+ "role": "source.clip",
109
+ "mediaType": "video/mp4",
110
+ "size": 168804,
111
+ "digest": "sha256:5d1a1fa59ec9091d9c03d99728b15287ef58d397ea2c787a65c55519482683c8",
112
+ "signal": "state.error"
113
+ }
114
+ ],
115
+ "origin": {
116
+ "kind": "default-catalog",
117
+ "source": "https://eikon.liftaris.dev/packages/liftaris/nous/1.0.0.json",
118
+ "sourceKey": "registry:eikon.liftaris.dev:liftaris/nous@1.0.0",
119
+ "identityKey": "registry:eikon.liftaris.dev:liftaris/nous@1.0.0",
120
+ "packageUrl": "https://eikon.liftaris.dev/packages/liftaris/nous/1.0.0.json",
121
+ "trust": "verified"
122
+ }
123
+ }
Binary file
@@ -1,17 +1,40 @@
1
- """Eikon install plugin."""
1
+ """Eikon lifecycle plugin."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from .schemas import EIKON_INSTALL_SCHEMA
6
- from .tools import _handle_eikon_install, check_herm_available
5
+ from .schemas import (
6
+ EIKON_INSTALL_SCHEMA,
7
+ EIKON_LIST_SCHEMA,
8
+ EIKON_REMOVE_SCHEMA,
9
+ EIKON_SEARCH_SCHEMA,
10
+ EIKON_UPDATE_SCHEMA,
11
+ EIKON_USE_SCHEMA,
12
+ )
13
+ from .tools import (
14
+ _handle_eikon_install,
15
+ _handle_eikon_list,
16
+ _handle_eikon_remove,
17
+ _handle_eikon_search,
18
+ _handle_eikon_update,
19
+ _handle_eikon_use,
20
+ check_herm_available,
21
+ )
7
22
 
8
23
 
9
24
  def register(ctx) -> None:
10
- ctx.register_tool(
11
- name="eikon_install",
12
- toolset="eikon",
13
- schema=EIKON_INSTALL_SCHEMA,
14
- handler=_handle_eikon_install,
15
- check_fn=check_herm_available,
16
- emoji="",
17
- )
25
+ for name, schema, handler in [
26
+ ("eikon_install", EIKON_INSTALL_SCHEMA, _handle_eikon_install),
27
+ ("eikon_search", EIKON_SEARCH_SCHEMA, _handle_eikon_search),
28
+ ("eikon_list", EIKON_LIST_SCHEMA, _handle_eikon_list),
29
+ ("eikon_use", EIKON_USE_SCHEMA, _handle_eikon_use),
30
+ ("eikon_update", EIKON_UPDATE_SCHEMA, _handle_eikon_update),
31
+ ("eikon_remove", EIKON_REMOVE_SCHEMA, _handle_eikon_remove),
32
+ ]:
33
+ ctx.register_tool(
34
+ name=name,
35
+ toolset="eikon",
36
+ schema=schema,
37
+ handler=handler,
38
+ check_fn=check_herm_available,
39
+ emoji="⬡",
40
+ )
@@ -1,7 +1,12 @@
1
1
  name: eikon
2
2
  version: 1.0.0
3
- description: "Herm eikon installer tool. Lets Hermes chat install terminal avatars through the existing `herm eikon install` CLI path."
3
+ description: "Herm eikon lifecycle tools. Lets Hermes chat search, install, activate, update, and remove terminal avatars through the existing `herm eikon` CLI path."
4
4
  author: Liftaris
5
5
  kind: standalone
6
6
  provides_tools:
7
7
  - eikon_install
8
+ - eikon_search
9
+ - eikon_list
10
+ - eikon_use
11
+ - eikon_update
12
+ - eikon_remove
@@ -7,7 +7,7 @@ EIKON_INSTALL_SCHEMA = {
7
7
  "description": (
8
8
  "Install a Herm eikon/avatar from the public catalog, a manifest URL, "
9
9
  "a git repository, or a local directory. Uses `herm eikon install`; "
10
- "no separate eikon executable is required."
10
+ "set_active activates the installed avatar with `herm eikon use`."
11
11
  ),
12
12
  "parameters": {
13
13
  "type": "object",
@@ -32,10 +32,78 @@ EIKON_INSTALL_SCHEMA = {
32
32
  },
33
33
  "set_active": {
34
34
  "type": "boolean",
35
- "description": "Set the installed eikon as the active Herm avatar. Default true.",
35
+ "description": "Activate the installed eikon after install. Default true.",
36
36
  "default": True,
37
37
  },
38
+ "active_ok": {
39
+ "type": "boolean",
40
+ "description": "Allow replacing the active eikon's backing package when installing over it.",
41
+ "default": False,
42
+ },
38
43
  },
39
44
  "required": ["source"],
40
45
  },
41
46
  }
47
+
48
+ EIKON_SEARCH_SCHEMA = {
49
+ "name": "eikon_search",
50
+ "description": "Search the Herm eikon marketplace/catalog via `herm eikon search --json`.",
51
+ "parameters": {
52
+ "type": "object",
53
+ "properties": {
54
+ "query": {"type": "string", "description": "Optional search query."},
55
+ },
56
+ },
57
+ }
58
+
59
+ EIKON_LIST_SCHEMA = {
60
+ "name": "eikon_list",
61
+ "description": "List installed Herm eikons via `herm eikon list --json`.",
62
+ "parameters": {"type": "object", "properties": {}},
63
+ }
64
+
65
+ EIKON_USE_SCHEMA = {
66
+ "name": "eikon_use",
67
+ "description": "Set an installed or bundled Herm eikon as active via `herm eikon use --json`.",
68
+ "parameters": {
69
+ "type": "object",
70
+ "properties": {
71
+ "name": {"type": "string", "description": "Installed or bundled eikon name."},
72
+ },
73
+ "required": ["name"],
74
+ },
75
+ }
76
+
77
+ EIKON_UPDATE_SCHEMA = {
78
+ "name": "eikon_update",
79
+ "description": "Update an installed Herm eikon from its recorded source via `herm eikon update --json`.",
80
+ "parameters": {
81
+ "type": "object",
82
+ "properties": {
83
+ "name": {"type": "string", "description": "Installed eikon name."},
84
+ "active_ok": {
85
+ "type": "boolean",
86
+ "description": "Allow updating the active avatar's backing package.",
87
+ "default": False,
88
+ },
89
+ },
90
+ "required": ["name"],
91
+ },
92
+ }
93
+
94
+ EIKON_REMOVE_SCHEMA = {
95
+ "name": "eikon_remove",
96
+ "description": "Remove an installed Herm eikon via `herm eikon remove --json`.",
97
+ "parameters": {
98
+ "type": "object",
99
+ "properties": {
100
+ "name": {"type": "string", "description": "Installed eikon name."},
101
+ "active_ok": {
102
+ "type": "boolean",
103
+ "description": "Allow clearing the active avatar if this eikon is active.",
104
+ "default": False,
105
+ },
106
+ },
107
+ "required": ["name"],
108
+ },
109
+ }
@@ -30,40 +30,96 @@ def check_herm_available() -> bool:
30
30
  return shutil.which(binary) is not None
31
31
 
32
32
 
33
- def _handle_eikon_install(args: dict[str, Any], **_: Any) -> str:
34
- source = str(args.get("source") or "").strip()
35
- if not source:
36
- return _json({"ok": False, "error": "source is required"})
37
-
33
+ def _run(parts: list[str], label: str, timeout: int = 120) -> dict[str, Any]:
38
34
  binary = _herm_bin()
39
35
  if not binary:
40
- return _json({"ok": False, "error": "herm executable not found on PATH"})
41
-
42
- cmd = [binary, "eikon", "install", source, "--json"]
43
- name = str(args.get("name") or "").strip()
44
- if name:
45
- cmd.extend(["--name", name])
46
- if args.get("media") is False or args.get("no_source") is True:
47
- cmd.append("--no-source")
48
- if args.get("set_active") is False:
49
- cmd.append("--no-use")
36
+ return {"ok": False, "error": "herm executable not found on PATH"}
50
37
 
51
38
  try:
52
- proc = subprocess.run(cmd, capture_output=True, text=True, timeout=120, check=False)
39
+ proc = subprocess.run([binary, *parts], capture_output=True, text=True, timeout=timeout, check=False)
53
40
  except FileNotFoundError:
54
- return _json({"ok": False, "error": f"herm executable not found: {binary}"})
41
+ return {"ok": False, "error": f"herm executable not found: {binary}"}
55
42
  except subprocess.TimeoutExpired:
56
- return _json({"ok": False, "error": "herm eikon install timed out"})
43
+ return {"ok": False, "error": f"herm eikon {label} timed out"}
57
44
 
58
45
  stdout = (proc.stdout or "").strip()
59
46
  stderr = (proc.stderr or "").strip()
60
47
  if proc.returncode != 0:
61
48
  detail = stderr or stdout or f"exit {proc.returncode}"
62
- return _json({"ok": False, "error": f"herm eikon install failed: {detail}"})
49
+ return {"ok": False, "error": f"herm eikon {label} failed: {detail}"}
63
50
 
64
51
  line = next((ln for ln in reversed(stdout.splitlines()) if ln.strip()), "")
65
52
  try:
66
53
  payload = json.loads(line)
67
54
  except json.JSONDecodeError:
68
- return _json({"ok": False, "error": "herm eikon install returned non-JSON output", "output": stdout})
69
- return _json(payload)
55
+ return {"ok": False, "error": f"herm eikon {label} returned non-JSON output", "output": stdout}
56
+ if isinstance(payload, dict):
57
+ return payload
58
+ return {"ok": True, "result": payload}
59
+
60
+
61
+ def _name(args: dict[str, Any]) -> str:
62
+ return str(args.get("name") or "").strip()
63
+
64
+
65
+ def _handle_eikon_install(args: dict[str, Any], **_: Any) -> str:
66
+ source = str(args.get("source") or "").strip()
67
+ if not source:
68
+ return _json({"ok": False, "error": "source is required"})
69
+
70
+ cmd = ["eikon", "install", source, "--json"]
71
+ name = _name(args)
72
+ if name:
73
+ cmd.extend(["--name", name])
74
+ if args.get("media") is False or args.get("no_source") is True:
75
+ cmd.append("--no-source")
76
+ if args.get("active_ok") is True:
77
+ cmd.append("--active-ok")
78
+
79
+ payload = _run(cmd, "install")
80
+ if not payload.get("ok") or args.get("set_active") is False:
81
+ return _json(payload)
82
+
83
+ active = _run(["eikon", "use", str(payload.get("name") or source), "--json"], "use")
84
+ if not active.get("ok"):
85
+ return _json({"ok": False, "installed": payload, "error": f"installed but activation failed: {active.get('error', 'unknown error')}"})
86
+ return _json({**payload, "active": active.get("active")})
87
+
88
+
89
+ def _handle_eikon_search(args: dict[str, Any], **_: Any) -> str:
90
+ query = str(args.get("query") or "").strip()
91
+ cmd = ["eikon", "search"]
92
+ if query:
93
+ cmd.append(query)
94
+ return _json(_run([*cmd, "--json"], "search"))
95
+
96
+
97
+ def _handle_eikon_list(args: dict[str, Any], **_: Any) -> str:
98
+ return _json(_run(["eikon", "list", "--json"], "list"))
99
+
100
+
101
+ def _handle_eikon_use(args: dict[str, Any], **_: Any) -> str:
102
+ name = _name(args)
103
+ if not name:
104
+ return _json({"ok": False, "error": "name is required"})
105
+ return _json(_run(["eikon", "use", name, "--json"], "use"))
106
+
107
+
108
+ def _handle_eikon_update(args: dict[str, Any], **_: Any) -> str:
109
+ name = _name(args)
110
+ if not name:
111
+ return _json({"ok": False, "error": "name is required"})
112
+ cmd = ["eikon", "update", name, "--json"]
113
+ if args.get("active_ok") is True:
114
+ cmd.append("--active-ok")
115
+ return _json(_run(cmd, "update"))
116
+
117
+
118
+ def _handle_eikon_remove(args: dict[str, Any], **_: Any) -> str:
119
+ name = _name(args)
120
+ if not name:
121
+ return _json({"ok": False, "error": "name is required"})
122
+ cmd = ["eikon", "remove", name, "--json"]
123
+ if args.get("active_ok") is True:
124
+ cmd.append("--active-ok")
125
+ return _json(_run(cmd, "remove"))
@@ -9,7 +9,7 @@ related_skills: [eikon-create]
9
9
  An eikon is a 48×24 monochrome text avatar. It lives on disk as:
10
10
 
11
11
  ~/.hermes/eikons/<name>/
12
- <name>.eikon packed NDJSON — written by Studio on Ctrl+S
12
+ <name>.eikon packed NDJSON — written by Studio on Ctrl+S / Ctrl+U
13
13
  studio.json Studio's workspace state
14
14
  source/ base.<ext>, <state>.<ext>
15
15
 
@@ -17,15 +17,17 @@ You do **not** write `.eikon` or `studio.json`. Studio does.
17
17
 
18
18
  ## Where the user does the work
19
19
 
20
- Herm's built-in **Eikon** tab (Studio / Gallery). Tell the user: "open
21
- the Eikon tab" or "Ctrl+K → Eikon". In Studio:
20
+ Herm's built-in **Eikon** tab (Gallery / Studio / Marketplace). Tell the user:
21
+ "open the Eikon tab" or "Ctrl+K → Eikon". In Studio:
22
22
 
23
23
  - `eikon` row → pick / New…
24
24
  - `source` row → Local file… / Generate image… / Generate video…
25
25
  - `input` section → contrast / invert / flip (pixel-domain, shared)
26
26
  - `<rasterizer>` section → symbols / fill / dither (glyph-domain)
27
27
  - Preview pane → wheel pans, Ctrl+wheel zooms, Shift+wheel pans X
28
- - **Ctrl+S** bakes all six states and sets it active
28
+ - **Ctrl+S** bakes all six states without changing the active avatar
29
+ - **Ctrl+U** bakes all six states and uses it as the active avatar
30
+ - `u` from Studio or `s` from Gallery opens the official-registry submit flow
29
31
 
30
32
  ## What makes a good source
31
33
 
@@ -40,9 +42,47 @@ everything.
40
42
  | "make me an eikon of X" | Load `eikon-create` and follow it. |
41
43
  | drops an image path | `cp` it to `~/.hermes/eikons/<name>/source/base.<ext>` → "Eikon tab, pick <name>". |
42
44
  | "edit my <name> eikon" | "Eikon tab → `eikon` row → <name>." |
45
+ | "install/shared marketplace eikon" | "Eikon tab → Marketplace", or `/marketplace`. Install, then Use when ready. |
46
+ | "install from GitHub" | "Eikon tab → New… → inspect/install", or `herm eikon install github.com/user/repo/eikon-name`. |
43
47
  | "too dark / washed out" | "invert toggle, then contrast slider — under `input`." |
44
48
  | "off-center / too small" | "Ctrl+wheel to zoom, wheel/drag to pan on the preview." |
45
49
  | "make it move" | `eikon-create` §5 (video), or Studio's `source` → Generate video…. |
50
+ | "publish/share my eikon" | "Eikon tab → Studio/Gallery → submit. Review the bundle, metadata, and GitHub PR target before continuing." |
51
+
52
+ ## Install and manage shared eikons
53
+
54
+ For catalog eikons, use **Eikon → Marketplace**. Rows show source,
55
+ compatibility, and trust (`Verified`, `Unverified`, or `Mismatch`).
56
+ Marketplace installs fetch built package artifacts and do not clone creator
57
+ repos.
58
+
59
+ For direct sharing, use `github.com/user/repo/eikon-name` for a multi-eikon
60
+ GitHub catalog repo, `github.com/user/repo` for a single-package repo, or a
61
+ local package directory. Private GitHub repos use normal git authentication.
62
+
63
+ The local lifecycle is explicit:
64
+
65
+ ```bash
66
+ herm eikon search [query]
67
+ herm eikon inspect <name|github.com/user/repo/eikon-name|dir>
68
+ herm eikon install <name|github.com/user/repo/eikon-name|dir>
69
+ herm eikon use <name>
70
+ herm eikon info <name>
71
+ herm eikon update <name> --active-ok
72
+ herm eikon remove <name> --active-ok
73
+ ```
74
+
75
+ `install` never activates. `use` activates. Updating or removing the active
76
+ eikon requires explicit acknowledgement because it changes or clears the active
77
+ avatar.
78
+
79
+ Creators share through normal GitHub repositories. For official registry
80
+ listing, use Gallery/Studio submit after baking: Herm previews the public
81
+ bundle, asks for title/author/description/glyph, and creates or guides a
82
+ GitHub PR to the shared registry. Use upstream `eikon pack`, `eikon index`,
83
+ and `eikon manifest` for direct-install repos. `eikon publish` is the lower-
84
+ level GitHub PR contribution helper for the configured/default catalog repo,
85
+ not a hosted marketplace account or upload flow.
46
86
 
47
87
  ## Quick poster
48
88
 
@@ -16,6 +16,8 @@ deliverable is one or more images/videos under
16
16
  named so Studio resolves them: `base.*` for the default pose, `<state>.*`
17
17
  for per-state overrides (`idle listening thinking speaking working
18
18
  error`). Extensions: `png jpg jpeg webp gif bmp mp4 webm mov mkv`.
19
+ Studio bakes with **Ctrl+S** without activation; **Ctrl+U** bakes and uses the
20
+ eikon as the active avatar.
19
21
 
20
22
  ## What survives rasterization
21
23
 
@@ -140,7 +142,10 @@ Adopt as `<state>.mp4` (or `base.mp4` for an animated idle).
140
142
  Once source files are in place:
141
143
 
142
144
  > Open the **Eikon** tab → `eikon` row → **<name>**. Tune zoom /
143
- > contrast / invert / symbols there, then **Ctrl+S** to bake.
145
+ > contrast / invert / symbols there, then **Ctrl+S** to bake or **Ctrl+U**
146
+ > to bake and use. For official registry sharing, press `u` in Studio or
147
+ > `s` in Gallery after baking; Herm previews the public bundle and GitHub PR
148
+ > target before anything is submitted.
144
149
 
145
150
  Stop there. Studio writes `<name>.eikon` and `studio.json`.
146
151
 
package/db.worker.js CHANGED
@@ -22,12 +22,15 @@ var __create=Object.create;var{getPrototypeOf:__getProtoOf,defineProperty:__defP
22
22
  ORDER BY c.started_at DESC LIMIT 1`)?.get(sid);return{...pred&&{continuesFrom:pred},...succ&&{compressedTo:succ}}}finally{end()}}function tip(sid){let step=q(`SELECT c.id FROM sessions c
23
23
  JOIN sessions p ON p.id = c.parent_session_id
24
24
  WHERE p.id = ? AND ${CONT("c")}
25
- ORDER BY c.started_at DESC LIMIT 1`),cur=sid;for(let i=0;i<100;i++){let next=step?.get(cur);if(!next)return cur;cur=next.id}return cur}function peek(sid,n=60){let end=mark("io:sessions.peek");try{let ext=[hasMsgCol("platform_message_id")?"platform_message_id":"NULL AS platform_message_id",hasMsgCol("observed")?"observed":"NULL AS observed"];return(q(`SELECT role, SUBSTR(content,1,400) AS content, tool_name,
25
+ ORDER BY c.started_at DESC LIMIT 1`),cur=sid;for(let i=0;i<100;i++){let next=step?.get(cur);if(!next)return cur;cur=next.id}return cur}function peek(sid,_n=4){let end=mark("io:sessions.peek");try{let ext=[hasMsgCol("platform_message_id")?"platform_message_id":"NULL AS platform_message_id",hasMsgCol("observed")?"observed":"NULL AS observed"];return(q(`SELECT role, SUBSTR(content,1,400) AS content, tool_name,
26
26
  SUBSTR(tool_calls,1,400) AS tool_calls, timestamp AS at,
27
27
  ${ext.join(", ")}
28
- FROM (SELECT * FROM messages WHERE session_id = ?
29
- ORDER BY id DESC LIMIT ?)
30
- ORDER BY id ASC`)?.all(sid,n)??[]).map((r)=>({role:r.role,content:r.content,tool_name:r.tool_name,tool_calls:r.tool_calls,...r.platform_message_id!==null&&{platform_message_id:r.platform_message_id},...r.observed!==null&&{observed:r.observed},at:r.at}))}finally{end()}}function goalState(sid){let row=q("SELECT value FROM state_meta WHERE key = ?")?.get(`goal:${sid}`);if(!row)return null;try{let j=JSON.parse(row.value),checklist=(Array.isArray(j.checklist)?j.checklist:[]).map(parseItem).filter((x)=>x!==null),subgoals=(Array.isArray(j.subgoals)?j.subgoals:[]).map((s)=>typeof s==="string"?s.trim():"").filter((s)=>s.length>0);return{goal:String(j.goal??""),status:j.status??"active",turn_count:typeof j.turn_count==="number"?j.turn_count:void 0,max_turns:j.max_turns??null,checklist:checklist.length>0?checklist:void 0,subgoals:subgoals.length>0?subgoals:void 0,decomposed:j.decomposed===!0?!0:void 0}}catch{return null}}function search(query,limit=30){let m=fts(query);if(!m)return[];let end=mark("io:sessions.search");try{let raw=q(`SELECT m.session_id, m.role,
28
+ FROM (
29
+ SELECT * FROM (SELECT * FROM messages WHERE session_id = ? ORDER BY id ASC LIMIT 2)
30
+ UNION
31
+ SELECT * FROM (SELECT * FROM messages WHERE session_id = ? ORDER BY id DESC LIMIT 2)
32
+ )
33
+ ORDER BY id ASC`)?.all(sid,sid)??[]).map((r)=>({role:r.role,content:r.content,tool_name:r.tool_name,tool_calls:r.tool_calls,...r.platform_message_id!==null&&{platform_message_id:r.platform_message_id},...r.observed!==null&&{observed:r.observed},at:r.at}))}finally{end()}}function goalState(sid){let row=q("SELECT value FROM state_meta WHERE key = ?")?.get(`goal:${sid}`);if(!row)return null;try{let j=JSON.parse(row.value),checklist=(Array.isArray(j.checklist)?j.checklist:[]).map(parseItem).filter((x)=>x!==null),subgoals=(Array.isArray(j.subgoals)?j.subgoals:[]).map((s)=>typeof s==="string"?s.trim():"").filter((s)=>s.length>0);return{goal:String(j.goal??""),status:j.status??"active",turn_count:typeof j.turn_count==="number"?j.turn_count:void 0,max_turns:j.max_turns??null,checklist:checklist.length>0?checklist:void 0,subgoals:subgoals.length>0?subgoals:void 0,decomposed:j.decomposed===!0?!0:void 0}}catch{return null}}function search(query,limit=30){let m=fts(query);if(!m)return[];let end=mark("io:sessions.search");try{let raw=q(`SELECT m.session_id, m.role,
31
34
  snippet(messages_fts, 0, '>>>', '<<<', '...', 40) AS snippet,
32
35
  s.source, s.model, s.started_at,
33
36
  COALESCE(s.title, SUBSTR(m.content, 1, 120)) AS title
@@ -55,7 +58,7 @@ var __create=Object.create;var{getPrototypeOf:__getProtoOf,defineProperty:__defP
55
58
  WHERE s.source IN ('tui', 'cli') AND s.message_count > 0
56
59
  AND (s.parent_session_id IS NULL OR ${CONT("s")})
57
60
  ORDER BY s.started_at DESC LIMIT 1
58
- `)?.get();if(!hit)return;let row=byId(chainTip(hit.id));return row&&row.message_count>0?row:void 0},chainTip=(sid)=>tip(walkUp(sid)),systemPrompt=()=>q(`SELECT id, system_prompt AS text FROM sessions
61
+ `)?.get();if(!hit)return;return byId(chainTip(hit.id))??void 0},chainTip=(sid)=>tip(walkUp(sid)),systemPrompt=()=>q(`SELECT id, system_prompt AS text FROM sessions
59
62
  WHERE system_prompt IS NOT NULL AND length(system_prompt) > 1000
60
63
  ORDER BY started_at DESC LIMIT 1`)?.get()??null,VALID_ITEM,parseItem=(raw)=>{if(!raw||typeof raw!=="object")return null;let o=raw,text=typeof o.text==="string"?o.text:"";if(!text.trim())return null;let s=typeof o.status==="string"?o.status:"pending",status=VALID_ITEM.has(s)?s:"pending",by=typeof o.added_by==="string"?o.added_by:void 0;return{text,status,addedBy:by==="judge"||by==="user"?by:void 0}},fts=(s)=>s.trim().split(/\s+/).filter(Boolean).map((w)=>/^\w+$/.test(w)?`${w}*`:`"${w.replace(/"/g,'""')}"`).join(" ");var init_sessions_db=__esm(()=>{init_perf();init_sessions_db();HERMES=process.env.HERMES_HOME||`${process.env.HOME||homedir()}/.hermes`,SRC={file:`${HERMES}/state.db`,relative:"state.db",label:"state.db"},conn={path:SRC.file,ro:null},stmts=new Map,msgCols=new Map;VALID_ITEM=new Set(["pending","completed","impossible"])});function analytics(days,opts){let since=Math.floor(Date.now()/1000)-days*86400,db=stateDb();if(!db)return ZERO;let q2=db.query.bind(db),tot=q2(`SELECT COUNT(*) n,
61
64
  COALESCE(SUM(message_count),0) msgs,