herm-tui 1.9.0-dev.9 → 1.9.0
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 +55 -10
- 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 +92 -19
- package/assets/skills/eikon-create/SKILL.md +106 -69
- package/db.worker.js +8 -5
- package/index.js +182 -147
- package/package.json +1 -1
- package/assets/eikons/ares/ares.eikon +0 -367
- package/assets/eikons/ares/manifest.json +0 -28
- package/assets/eikons/default/default.eikon +0 -398
- package/assets/eikons/default/manifest.json +0 -28
- package/assets/eikons/mono/manifest.json +0 -28
- package/assets/eikons/mono/mono.eikon +0 -395
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eikon-create
|
|
3
|
-
description:
|
|
4
|
-
tags: [eikon, avatar, image-gen, herm]
|
|
3
|
+
description: Use when generating or adopting source images/videos for a Herm eikon so Eikon Studio can tune, bake, and optionally share it.
|
|
4
|
+
tags: [eikon, avatar, image-gen, video-gen, herm]
|
|
5
5
|
related_skills: [eikon]
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -9,47 +9,52 @@ related_skills: [eikon]
|
|
|
9
9
|
|
|
10
10
|
You are producing **source files**, not the packed `.eikon`. Studio owns
|
|
11
11
|
crop, tone (contrast/invert/flip), rasterizer choice, and baking. Your
|
|
12
|
-
deliverable is one or more images/videos under
|
|
12
|
+
deliverable is one or more images/videos under the active Hermes profile:
|
|
13
13
|
|
|
14
14
|
~/.hermes/eikons/<name>/source/
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
Studio resolves `base.*` for the default source and `<state>.*` for
|
|
17
|
+
per-state overrides (`idle listening thinking speaking working error`).
|
|
18
|
+
Direct `source/` discovery supports `png jpg jpeg webp gif bmp mp4 webm mov
|
|
19
|
+
mkv`; Studio's Local file picker currently accepts `png jpg jpeg webp gif mp4
|
|
20
|
+
webm mov`, so copy `bmp`/`mkv` into `source/` manually if needed.
|
|
21
|
+
|
|
22
|
+
Studio bakes with **Ctrl+S** without activation; **Ctrl+U** bakes and uses the
|
|
23
|
+
eikon as the active avatar.
|
|
19
24
|
|
|
20
25
|
## What survives rasterization
|
|
21
26
|
|
|
22
|
-
Output is 48×24, one theme color, braille/block glyphs. The one-line
|
|
23
|
-
|
|
27
|
+
Output is 48×24, one theme color, braille/block glyphs. The one-line brief
|
|
28
|
+
(say once, don't repeat):
|
|
24
29
|
|
|
25
|
-
> 48×24, one color. Best results: light subject on black background,
|
|
26
|
-
>
|
|
30
|
+
> 48×24, one color. Best results: light subject on black background, high
|
|
31
|
+
> contrast, strong silhouette.
|
|
27
32
|
|
|
28
|
-
Outline carries everything — facial detail, fine texture, and tonal
|
|
29
|
-
|
|
33
|
+
Outline carries everything — facial detail, fine texture, and tonal gradients
|
|
34
|
+
mostly vanish.
|
|
30
35
|
|
|
31
36
|
## What Studio fixes for you (don't regenerate for these)
|
|
32
37
|
|
|
33
38
|
Studio adjusts these live on any source without a new generation:
|
|
34
39
|
|
|
35
|
-
- **Aspect / framing** — Studio crops to a square window; zoom + pan
|
|
36
|
-
|
|
37
|
-
- **Contrast** — slider, mean-centered. Flat or over-bright sources
|
|
38
|
-
|
|
39
|
-
- **Invert** — light↔dark swap. A dark-subject-on-light source works
|
|
40
|
-
|
|
40
|
+
- **Aspect / framing** — Studio crops to a square window; zoom + pan pick
|
|
41
|
+
which square. A 16:9 or portrait source is fine.
|
|
42
|
+
- **Contrast** — slider, mean-centered. Flat or over-bright sources are
|
|
43
|
+
usually salvageable.
|
|
44
|
+
- **Invert** — light↔dark swap. A dark-subject-on-light source works with
|
|
45
|
+
invert off.
|
|
41
46
|
- **Flip** — horizontal/vertical mirror.
|
|
42
47
|
|
|
43
|
-
So: regenerate for **subject, pose, silhouette, background clutter**.
|
|
44
|
-
|
|
48
|
+
So: regenerate for **subject, pose, silhouette, background clutter**. Don't
|
|
49
|
+
regenerate for **crop, exposure, polarity, orientation**.
|
|
45
50
|
|
|
46
51
|
## Flow
|
|
47
52
|
|
|
48
53
|
### 0. Name
|
|
49
54
|
|
|
50
|
-
If the user passed an argument (`/eikon-create <name>`), slug it
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
If the user passed an argument (`/eikon-create <name>`), slug it (lowercase,
|
|
56
|
+
`[^a-z0-9-]` → `-`, collapse runs, trim). Otherwise ask once. Make the folder
|
|
57
|
+
immediately so Studio's picker lists it:
|
|
53
58
|
|
|
54
59
|
```bash
|
|
55
60
|
n="<slug>"; mkdir -p "${HERMES_HOME:-$HOME/.hermes}/eikons/$n/source"
|
|
@@ -57,52 +62,67 @@ n="<slug>"; mkdir -p "${HERMES_HOME:-$HOME/.hermes}/eikons/$n/source"
|
|
|
57
62
|
|
|
58
63
|
### 1. Subject
|
|
59
64
|
|
|
60
|
-
Ask what the eikon is. One line is enough. If they drop an image
|
|
61
|
-
|
|
65
|
+
Ask what the eikon is. One line is enough. If they drop an image instead of
|
|
66
|
+
describing one, skip to §3 adopt-only.
|
|
62
67
|
|
|
63
68
|
### 2. Generate base
|
|
64
69
|
|
|
65
|
-
Call `image_generate` with the subject on line 1 and
|
|
66
|
-
on line 2
|
|
70
|
+
Call `image_generate` with the subject on line 1 and Studio's fixed style hint
|
|
71
|
+
on line 2:
|
|
67
72
|
|
|
68
|
-
```
|
|
73
|
+
```text
|
|
69
74
|
<subject>, close-up portrait emphasizing the face/head, looking slightly left, stark black and white, bold silhouette, simple uncluttered shape
|
|
70
75
|
high contrast, light subject on dark, black background
|
|
71
76
|
```
|
|
72
77
|
|
|
73
|
-
Keep this general: replace `face/head` with the subject's most readable
|
|
74
|
-
|
|
75
|
-
takes
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
Keep this general: replace `face/head` with the subject's most readable feature
|
|
79
|
+
if it is not a character or creature. Prefer `aspect_ratio="square"` if the
|
|
80
|
+
tool takes it; if it doesn't, don't worry — Studio crops.
|
|
81
|
+
|
|
82
|
+
After every candidate, adopt it into `source/base.<ext>` before handoff or
|
|
83
|
+
iteration. Show the result inline with `` and a 48-wide terminal
|
|
84
|
+
preview:
|
|
78
85
|
|
|
79
86
|
```bash
|
|
80
87
|
chafa --size=48x24 --symbols=braille --colors=none --format=symbols --stretch "<path>" 2>/dev/null || true
|
|
81
88
|
```
|
|
82
89
|
|
|
83
|
-
Ask: **keep, regenerate, or adjust?** On adjust, fold their note into
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
Ask: **keep, regenerate, or adjust?** On adjust, fold their note into the
|
|
91
|
+
subject line and leave the style hint alone. If two rounds fail on background
|
|
92
|
+
clutter, silently append `, isolated on pure black, no floor, no environment`
|
|
93
|
+
and try again.
|
|
94
|
+
|
|
95
|
+
Overwrite the same role file (`base.<ext>` for the default source,
|
|
96
|
+
`<state>.<ext>` for overrides) so candidates do not drift across filenames. If
|
|
97
|
+
Studio is already open after an external write, tell the user to press reload
|
|
98
|
+
(`r`) or reopen the eikon; dirty drafts must be saved or reverted before reload.
|
|
99
|
+
A candidate left only in temp/cache or in an unselected filename is invisible.
|
|
89
100
|
|
|
90
101
|
### 3. Adopt
|
|
91
102
|
|
|
103
|
+
Use this for generated files, downloaded URLs, or a user-supplied local path.
|
|
104
|
+
Set `role=<state>` for per-state overrides; default is `base`.
|
|
105
|
+
|
|
92
106
|
```bash
|
|
107
|
+
role="${role:-base}"
|
|
93
108
|
src="<path-or-downloaded-tmp>"
|
|
94
|
-
|
|
109
|
+
base="${src##*/}"
|
|
110
|
+
ext="${base##*.}"
|
|
111
|
+
[ "$ext" = "$base" ] && ext="png"
|
|
112
|
+
ext=".${ext,,}"
|
|
113
|
+
case "$ext" in .png|.jpg|.jpeg|.webp|.gif|.bmp|.mp4|.webm|.mov|.mkv) ;; *) ext=".png" ;; esac
|
|
114
|
+
dst="${HERMES_HOME:-$HOME/.hermes}/eikons/$n/source/$role$ext"
|
|
95
115
|
cp "$src" "$dst" && ls -l "$dst"
|
|
96
116
|
```
|
|
97
117
|
|
|
98
|
-
URL return →
|
|
99
|
-
extension → `.png`.
|
|
118
|
+
URL return → download to `/tmp/<n>-<role>.<ext>` first, preserving the media
|
|
119
|
+
extension when known. Ambiguous image URL → `.png`; ambiguous video URL → `.mp4`.
|
|
100
120
|
|
|
101
121
|
### 4. Per-state sources (optional)
|
|
102
122
|
|
|
103
|
-
Base covers all six states by default. If the user wants distinct
|
|
104
|
-
|
|
105
|
-
|
|
123
|
+
Base covers all six states by default. If the user wants distinct ones, repeat
|
|
124
|
+
§2–3 per state, saving as `<state>.<ext>`. Nudge the subject line with pose
|
|
125
|
+
intent:
|
|
106
126
|
|
|
107
127
|
| state | pose nudge |
|
|
108
128
|
|---|---|
|
|
@@ -114,24 +134,39 @@ subject line with pose intent:
|
|
|
114
134
|
|
|
115
135
|
### 5. Video (optional, only if asked)
|
|
116
136
|
|
|
117
|
-
Use `video_generate` if available.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
Use `video_generate` if available. Studio's Generate video row appears only
|
|
138
|
+
when the installed Hermes Agent video backend passes its requirement check; this
|
|
139
|
+
skill may also use any available agent `video_generate` tool. If video generation
|
|
140
|
+
is unavailable, adopt a local file or URL result instead.
|
|
141
|
+
|
|
142
|
+
Before prompting motion for a character/creature, establish a one-line persona:
|
|
143
|
+
temperament, bearing, and what an "idle" moment means for this subject. It keeps
|
|
144
|
+
motion specific instead of generic breathing/bobbing.
|
|
145
|
+
|
|
146
|
+
Motion discipline: animate only what the user asked for. Do not add ambient
|
|
147
|
+
breathing, swaying, shimmer, cloth ripple, or body motion unless requested;
|
|
148
|
+
small artifacts become obvious at 48×24. Prefer one deliberate motion plus one
|
|
149
|
+
blink over many tiny movements. Put unwanted motion in the negative prompt or
|
|
150
|
+
prompt body when the provider supports it.
|
|
151
|
+
|
|
152
|
+
What matters for an eikon video source:
|
|
153
|
+
|
|
154
|
+
- **Duration** — 1–4 s; Studio defaults to 2 s. Longer usually inflates the
|
|
155
|
+
bake without improving readability.
|
|
156
|
+
- **Aspect** — prefer 1:1 if accepted; otherwise use an accepted aspect and let
|
|
157
|
+
Studio crop.
|
|
158
|
+
- **Resolution** — low is fine; the target is 48×24. Don't ask for HD.
|
|
159
|
+
- **Start image** — if the tool accepts it, pass `base.*` / the effective idle
|
|
160
|
+
source as `image_url` so frame 0 anchors on the still pose. Studio's current
|
|
161
|
+
bridge does not provide an end-frame handoff; skip unsupported knobs.
|
|
162
|
+
- **Loop** — useful but not required. If the provider has loop/seamless flags,
|
|
163
|
+
set them; otherwise do not chase a perfect loop by prompting.
|
|
164
|
+
- **Model tier** — if a lite/fast tier warps the subject, switch to a better
|
|
165
|
+
tier if the tool exposes `model=`. Don't burn iterations fighting artifacts a
|
|
166
|
+
tier bump fixes.
|
|
167
|
+
|
|
168
|
+
Providers vary. **Pass what the tool accepts, skip what it doesn't, and don't
|
|
169
|
+
apologize for the gaps.** None of them block a usable eikon.
|
|
135
170
|
|
|
136
171
|
Adopt as `<state>.mp4` (or `base.mp4` for an animated idle).
|
|
137
172
|
|
|
@@ -139,17 +174,19 @@ Adopt as `<state>.mp4` (or `base.mp4` for an animated idle).
|
|
|
139
174
|
|
|
140
175
|
Once source files are in place:
|
|
141
176
|
|
|
142
|
-
> Open
|
|
143
|
-
>
|
|
177
|
+
> Open **Eikon → Studio** → `eikon` row → **<name>**. Tune zoom / contrast /
|
|
178
|
+
> invert / symbols there, then **Ctrl+S** to bake or **Ctrl+U** to bake and use.
|
|
179
|
+
> After a saved bake, press `u` in Studio for the Submit eikon dialog or `s` in
|
|
180
|
+
> **Library** for Share to catalog; Herm previews the bundle and GitHub PR target
|
|
181
|
+
> before submission.
|
|
144
182
|
|
|
145
183
|
Stop there. Studio writes `<name>.eikon` and `studio.json`.
|
|
146
184
|
|
|
147
185
|
## Don'ts
|
|
148
186
|
|
|
149
187
|
- Don't write `.eikon` or `studio.json`.
|
|
150
|
-
- Don't pick contrast, invert, zoom, or rasterizer values — name the
|
|
151
|
-
|
|
188
|
+
- Don't pick contrast, invert, zoom, or rasterizer values — name the knob, the
|
|
189
|
+
user turns it in Studio.
|
|
152
190
|
- Don't regenerate for things Studio can fix (see table above).
|
|
153
|
-
- Don't enumerate provider capabilities to the user; just use what's
|
|
154
|
-
there.
|
|
191
|
+
- Don't enumerate provider capabilities to the user; just use what's there.
|
|
155
192
|
- Don't repeat the 48×24 brief.
|
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,
|
|
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 (
|
|
29
|
-
|
|
30
|
-
|
|
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;
|
|
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,
|