herm-tui 1.9.0-dev.3 → 1.9.0-dev.31
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 +47 -8
- package/assets/eikons/nous/manifest.json +123 -0
- package/assets/eikons/nous/nous.eikon +0 -0
- package/assets/plugins/eikon/__init__.py +34 -11
- package/assets/plugins/eikon/plugin.yaml +6 -1
- package/assets/plugins/eikon/schemas.py +70 -2
- package/assets/plugins/eikon/tools.py +77 -21
- package/assets/skills/eikon/SKILL.md +44 -4
- package/assets/skills/eikon-create/SKILL.md +6 -1
- package/db.worker.js +1 -1
- package/index.js +595 -555
- package/package.json +1 -1
- package/assets/eikons/ares.eikon +0 -367
- package/assets/eikons/default.eikon +0 -398
- package/assets/eikons/mono.eikon +0 -395
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
1
|
+
"""Eikon lifecycle plugin."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from .schemas import
|
|
6
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
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
|
-
"
|
|
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": "
|
|
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
|
|
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
|
|
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(
|
|
39
|
+
proc = subprocess.run([binary, *parts], capture_output=True, text=True, timeout=timeout, check=False)
|
|
53
40
|
except FileNotFoundError:
|
|
54
|
-
return
|
|
41
|
+
return {"ok": False, "error": f"herm executable not found: {binary}"}
|
|
55
42
|
except subprocess.TimeoutExpired:
|
|
56
|
-
return
|
|
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
|
|
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
|
|
69
|
-
|
|
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 /
|
|
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
|
|
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
|
@@ -55,7 +55,7 @@ var __create=Object.create;var{getPrototypeOf:__getProtoOf,defineProperty:__defP
|
|
|
55
55
|
WHERE s.source IN ('tui', 'cli') AND s.message_count > 0
|
|
56
56
|
AND (s.parent_session_id IS NULL OR ${CONT("s")})
|
|
57
57
|
ORDER BY s.started_at DESC LIMIT 1
|
|
58
|
-
`)?.get();if(!hit)return;
|
|
58
|
+
`)?.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
59
|
WHERE system_prompt IS NOT NULL AND length(system_prompt) > 1000
|
|
60
60
|
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
61
|
COALESCE(SUM(message_count),0) msgs,
|