ocuclaw 0.1.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.
@@ -0,0 +1,249 @@
1
+ const MESSAGE_EMOJI_ALLOWLIST = [
2
+ "πŸ˜‚",
3
+ "❀️",
4
+ "🀣",
5
+ "πŸ‘",
6
+ "😭",
7
+ "πŸ™",
8
+ "😘",
9
+ "πŸ₯°",
10
+ "😍",
11
+ "😊",
12
+ "πŸŽ‰",
13
+ "😁",
14
+ "πŸ’•",
15
+ "πŸ₯Ί",
16
+ "πŸ˜…",
17
+ "πŸ”₯",
18
+ "☺️",
19
+ "🀦",
20
+ "🀷",
21
+ "πŸ™„",
22
+ "πŸ˜†",
23
+ "πŸ€—",
24
+ "πŸ˜‰",
25
+ "πŸŽ‚",
26
+ "πŸ€”",
27
+ "πŸ‘",
28
+ "πŸ™‚",
29
+ "😳",
30
+ "πŸ₯³",
31
+ "😎",
32
+ "πŸ‘Œ",
33
+ "πŸ˜”",
34
+ "πŸ’ͺ",
35
+ "✨",
36
+ "πŸ’–",
37
+ "πŸ’ž",
38
+ "πŸ‘€",
39
+ "πŸ˜‹",
40
+ "😏",
41
+ "😒",
42
+ "πŸ‘‰",
43
+ "πŸ’—",
44
+ "😩",
45
+ "πŸ’―",
46
+ "🌹",
47
+ "🎈",
48
+ "😚",
49
+ "😐",
50
+ "πŸ˜’",
51
+ "πŸ˜€",
52
+ ];
53
+
54
+ const MESSAGE_EMOJI_ALLOWLIST_SET = new Set(MESSAGE_EMOJI_ALLOWLIST);
55
+ const EMOJI_CLUSTER_SEGMENTER = new Intl.Segmenter(undefined, {
56
+ granularity: "grapheme",
57
+ });
58
+ const RGI_EMOJI_SEQUENCE_RE = createOptionalRegex("^\\p{RGI_Emoji}$", "v");
59
+ const EMOJI_CLUSTER_FALLBACK_RE =
60
+ /(?:\p{Extended_Pictographic}|[\u{1F1E6}-\u{1F1FF}\u{1F3FB}-\u{1F3FF}\u20E3\u{E0020}-\u{E007F}])/u;
61
+ const LEFTOVER_EMOJI_CONTROL_RE =
62
+ /[\uFE0E\uFE0F\u20E3\u{1F3FB}-\u{1F3FF}\u{E0020}-\u{E007F}]/gu;
63
+
64
+ function createOptionalRegex(pattern, flags) {
65
+ try {
66
+ return new RegExp(pattern, flags);
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+
72
+ function isEmojiCluster(segment) {
73
+ if (!segment) return false;
74
+ if (RGI_EMOJI_SEQUENCE_RE && RGI_EMOJI_SEQUENCE_RE.test(segment)) {
75
+ return true;
76
+ }
77
+ return EMOJI_CLUSTER_FALLBACK_RE.test(segment);
78
+ }
79
+
80
+ function isHorizontalWhitespace(segment) {
81
+ return /^[ \t]+$/.test(segment);
82
+ }
83
+
84
+ function isNewlineSegment(segment) {
85
+ return /^(?:\r\n|\r|\n)$/.test(segment);
86
+ }
87
+
88
+ function shouldDropGapBeforePunctuation(
89
+ nextSegment,
90
+ beforeRemoved,
91
+ afterRemoved,
92
+ removedEmojiInGap,
93
+ ) {
94
+ return (
95
+ removedEmojiInGap &&
96
+ beforeRemoved.length > 0 &&
97
+ afterRemoved.length === 0 &&
98
+ /^[.,]$/.test(nextSegment)
99
+ );
100
+ }
101
+
102
+ function emitGapForText(
103
+ mode,
104
+ beforeRemoved,
105
+ afterRemoved,
106
+ removedEmojiInGap,
107
+ nextSegment,
108
+ ) {
109
+ const totalWhitespace = beforeRemoved + afterRemoved;
110
+ if (!totalWhitespace) return "";
111
+ if (
112
+ shouldDropGapBeforePunctuation(
113
+ nextSegment,
114
+ beforeRemoved,
115
+ afterRemoved,
116
+ removedEmojiInGap,
117
+ )
118
+ ) {
119
+ return "";
120
+ }
121
+ if (mode === "display") {
122
+ return " ";
123
+ }
124
+ if (removedEmojiInGap) {
125
+ return " ";
126
+ }
127
+ return totalWhitespace;
128
+ }
129
+
130
+ function emitGapForLineEnd(mode, beforeRemoved, afterRemoved, removedEmojiInGap) {
131
+ if (mode === "display") {
132
+ return "";
133
+ }
134
+ if (removedEmojiInGap) {
135
+ return afterRemoved.length >= 2 ? afterRemoved : "";
136
+ }
137
+ return beforeRemoved + afterRemoved;
138
+ }
139
+
140
+ function emitGapForTextEnd(mode, beforeRemoved, afterRemoved, removedEmojiInGap) {
141
+ if (mode === "display") {
142
+ return "";
143
+ }
144
+ if (removedEmojiInGap) {
145
+ return "";
146
+ }
147
+ return beforeRemoved + afterRemoved;
148
+ }
149
+
150
+ function filterEmojiText(text, mode) {
151
+ if (!text) return "";
152
+
153
+ const selectedMode = mode === "display" ? "display" : "raw";
154
+ const out = [];
155
+ let pendingWhitespaceBeforeRemoved = "";
156
+ let pendingWhitespaceAfterRemoved = "";
157
+ let removedEmojiInGap = false;
158
+
159
+ function resetGap() {
160
+ pendingWhitespaceBeforeRemoved = "";
161
+ pendingWhitespaceAfterRemoved = "";
162
+ removedEmojiInGap = false;
163
+ }
164
+
165
+ function flushGapForText(nextSegment) {
166
+ const gap = emitGapForText(
167
+ selectedMode,
168
+ pendingWhitespaceBeforeRemoved,
169
+ pendingWhitespaceAfterRemoved,
170
+ removedEmojiInGap,
171
+ nextSegment,
172
+ );
173
+ if (gap) out.push(gap);
174
+ resetGap();
175
+ }
176
+
177
+ function flushGapForLineEnd() {
178
+ const gap = emitGapForLineEnd(
179
+ selectedMode,
180
+ pendingWhitespaceBeforeRemoved,
181
+ pendingWhitespaceAfterRemoved,
182
+ removedEmojiInGap,
183
+ );
184
+ if (gap) out.push(gap);
185
+ resetGap();
186
+ }
187
+
188
+ function flushGapForTextEnd() {
189
+ const gap = emitGapForTextEnd(
190
+ selectedMode,
191
+ pendingWhitespaceBeforeRemoved,
192
+ pendingWhitespaceAfterRemoved,
193
+ removedEmojiInGap,
194
+ );
195
+ if (gap) out.push(gap);
196
+ resetGap();
197
+ }
198
+
199
+ for (const part of EMOJI_CLUSTER_SEGMENTER.segment(text)) {
200
+ const segment = part.segment;
201
+ if (MESSAGE_EMOJI_ALLOWLIST_SET.has(segment)) {
202
+ flushGapForText(segment);
203
+ out.push(segment);
204
+ continue;
205
+ }
206
+ if (isEmojiCluster(segment)) {
207
+ removedEmojiInGap = true;
208
+ continue;
209
+ }
210
+
211
+ const cleaned = segment.replace(LEFTOVER_EMOJI_CONTROL_RE, "");
212
+ if (!cleaned) {
213
+ continue;
214
+ }
215
+ if (isHorizontalWhitespace(cleaned)) {
216
+ if (removedEmojiInGap) {
217
+ pendingWhitespaceAfterRemoved += cleaned;
218
+ } else {
219
+ pendingWhitespaceBeforeRemoved += cleaned;
220
+ }
221
+ continue;
222
+ }
223
+ if (isNewlineSegment(cleaned)) {
224
+ flushGapForLineEnd();
225
+ out.push(cleaned);
226
+ continue;
227
+ }
228
+
229
+ flushGapForText(cleaned);
230
+ out.push(cleaned);
231
+ }
232
+
233
+ flushGapForTextEnd();
234
+
235
+ if (selectedMode === "raw") {
236
+ return out.join("").replace(/[ \t]+$/g, "");
237
+ }
238
+ return out.join("");
239
+ }
240
+
241
+ export function filterDisplayEmojiText(text) {
242
+ return filterEmojiText(text, "display");
243
+ }
244
+
245
+ export function filterRawEmojiText(text) {
246
+ return filterEmojiText(text, "raw");
247
+ }
248
+
249
+ export { MESSAGE_EMOJI_ALLOWLIST };
@@ -0,0 +1,17 @@
1
+ export const SMALL_SCREEN_READABILITY_BASE_SYSTEM_PROMPT =
2
+ "For small-screen readability, prefer compact paragraphs and complete sentences. Keep formatting simple; avoid tables, code fences, and long unbroken strings unless needed.";
3
+
4
+ function normalizeUserPrompt(value) {
5
+ if (typeof value !== "string") {
6
+ return "";
7
+ }
8
+ return value.trim();
9
+ }
10
+
11
+ export function composeReadabilitySystemPrompt(userPrompt) {
12
+ const normalizedUserPrompt = normalizeUserPrompt(userPrompt);
13
+ if (!normalizedUserPrompt) {
14
+ return SMALL_SCREEN_READABILITY_BASE_SYSTEM_PROMPT;
15
+ }
16
+ return `${SMALL_SCREEN_READABILITY_BASE_SYSTEM_PROMPT}\n\n${normalizedUserPrompt}`;
17
+ }