ei-tui 1.2.0 → 1.3.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.
@@ -5,6 +5,7 @@
5
5
  import type { RoomParticipantIdentity, RoomHistoryMessage, RoomJudgeCandidate } from "./types.js";
6
6
  import type { PersonaTrait, PersonaTopic } from "../../core/types.js";
7
7
  import { RoomMode } from "../../core/types.js";
8
+ import { partitionTraits, bucketTraits } from "../trait-utils.js";
8
9
 
9
10
  const DESCRIPTION_MAX_CHARS = 500;
10
11
 
@@ -70,14 +71,35 @@ export function buildRoomGuidelinesSection(personaName: string, mode?: RoomMode)
70
71
  return baseGuidelines;
71
72
  }
72
73
 
74
+ const ROOM_TRAIT_BUCKETS = [
75
+ { min: 90, max: 100, header: "### Core Expression\nEvident in every response." },
76
+ { min: 66, max: 89, header: "### Strong Tendencies\nFrequent, but not every sentence." },
77
+ { min: 36, max: 65, header: "### Noticeable in Casual Messages\nSurfaces naturally, not constantly." },
78
+ { min: 1, max: 35, header: "### Subtle Undercurrents\nBackground texture only." },
79
+ ] as const;
80
+
73
81
  export function buildRoomTraitsSection(traits: PersonaTrait[]): string {
74
82
  if (traits.length === 0) return "";
75
- const sorted = [...traits].sort((a, b) => (b.strength ?? 0.5) - (a.strength ?? 0.5)).slice(0, 12);
76
- const lines = sorted.map(t => {
77
- const pct = t.strength !== undefined ? ` (${Math.round(t.strength * 100)}%)` : "";
78
- return `- **${t.name}**${pct}: ${truncate(t.description)}`;
79
- });
80
- return `## Your Personality\n\n${lines.join("\n")}`;
83
+
84
+ const capped = [...traits].sort((a, b) => (b.strength ?? 0.5) - (a.strength ?? 0.5)).slice(0, 12);
85
+ const { guardrails, active } = partitionTraits(capped);
86
+
87
+ const sections: string[] = [];
88
+
89
+ if (guardrails.length > 0) {
90
+ const lines = guardrails.map(t => `**${t.name}**`).join("\n");
91
+ sections.push(`### Must NEVER Do — User Explicitly Asked You To Stop\n${lines}`);
92
+ }
93
+
94
+ for (const { bucket, traits: inBucket } of bucketTraits(active, ROOM_TRAIT_BUCKETS)) {
95
+ if (inBucket.length === 0) continue;
96
+ const lines = inBucket.map(t => `**${t.name}**: ${truncate(t.description)}`).join("\n");
97
+ sections.push(`${bucket.header}\n${lines}`);
98
+ }
99
+
100
+ if (sections.length === 0) return "";
101
+
102
+ return `## Your Personality\n\n${sections.join("\n\n")}`;
81
103
  }
82
104
 
83
105
  export function buildRoomTopicsSection(topics: PersonaTopic[]): string {
@@ -0,0 +1,33 @@
1
+ import type { PersonaTrait } from "../core/types.js";
2
+
3
+ export interface TraitBucket {
4
+ min: number;
5
+ max: number;
6
+ header: string;
7
+ }
8
+
9
+ export interface PartitionedTraits {
10
+ guardrails: PersonaTrait[];
11
+ active: PersonaTrait[];
12
+ }
13
+
14
+ // Special-case: .filter() is intentional here. This file is the designated home
15
+ // for trait partitioning logic that prompt builders need for rendering. Keeping it
16
+ // here (rather than inline in each builder) is what lets the structural check
17
+ // enforce "no .filter() in prompt builders" elsewhere.
18
+ export function partitionTraits(traits: PersonaTrait[]): PartitionedTraits {
19
+ return {
20
+ guardrails: traits.filter(t => (t.strength ?? 0.5) === 0),
21
+ active: traits.filter(t => (t.strength ?? 0.5) > 0),
22
+ };
23
+ }
24
+
25
+ export function bucketTraits(active: PersonaTrait[], buckets: readonly TraitBucket[]): Array<{ bucket: TraitBucket; traits: PersonaTrait[] }> {
26
+ return buckets.map(bucket => ({
27
+ bucket,
28
+ traits: active.filter(t => {
29
+ const pct = Math.round((t.strength ?? 0.5) * 100);
30
+ return pct >= bucket.min && pct <= bucket.max;
31
+ }),
32
+ }));
33
+ }
package/tui/README.md CHANGED
@@ -132,6 +132,8 @@ Rooms have three modes, set at creation time:
132
132
  | `/me <type>` | | Edit one type: `facts`, `topics`, or `people` |
133
133
  | `/import <path>` | | Import a document (txt, md, pdf, etc.) into Ei — extracted knowledge is attributed to the "Emmett" persona |
134
134
  | `/unsource <source_tag>` | | Remove all knowledge extracted from a previously imported document |
135
+ | `/generate <subject>` | | Synthesize everything Ei knows about a subject into a markdown document — lands in `$EI_DATA_PATH/docs/` automatically |
136
+ | `/generate` | | (no args) Manage your generated documents — re-run, re-export, or delete |
135
137
  | `/dedupe <person\|topic> <term> [term2 ...]` | | Fuzzy-search and merge duplicate people or topics in `$EDITOR`. Unquoted words are individual OR terms; quoted strings match as exact phrases: `/dedupe person Flare "Jeremy Scherer"` finds records matching `Flare` OR `Jeremy Scherer` |
136
138
  | `/settings` | `/set` | Edit your global settings in `$EDITOR` |
137
139
  | `/setsync <user> <pass>` | `/ss` | Set sync credentials (triggers restart) |
@@ -109,6 +109,17 @@ EXTENDED COMMANDS
109
109
  document. Use the source tag shown when the import completed.
110
110
  /unsource my-journal-2024
111
111
 
112
+ /generate <subject>
113
+ Synthesize everything Ei knows about a subject into a clean markdown
114
+ document. Uses your rewrite_model (Opus-class recommended). The file
115
+ lands in $EI_DATA_PATH/docs/ automatically.
116
+ /generate everything about the Uniform project
117
+ /generate comprehensive runbook for the IDP deployment issues
118
+
119
+ /generate
120
+ (no args) — Open your generated documents. Re-run, re-export, or
121
+ delete from here.
122
+
112
123
  /tools
113
124
  Manage tool providers — enable or disable tools per persona.
114
125