@wangzhizhi/remi 0.0.1-alpha

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.
Files changed (55) hide show
  1. package/README.md +9 -0
  2. package/dist/doctor.js +108 -0
  3. package/dist/git.js +41 -0
  4. package/dist/help.js +27 -0
  5. package/dist/i18n.js +422 -0
  6. package/dist/index.js +97 -0
  7. package/dist/initPrompt.js +17 -0
  8. package/dist/model.js +116 -0
  9. package/dist/modelSelection.js +34 -0
  10. package/dist/permissionDisplay.js +46 -0
  11. package/dist/permissions.js +206 -0
  12. package/dist/repl.js +346 -0
  13. package/dist/resume.js +3 -0
  14. package/dist/setup.js +62 -0
  15. package/dist/statusline.js +59 -0
  16. package/dist/style.js +48 -0
  17. package/dist/syntaxTheme.js +39 -0
  18. package/dist/tui/RemiApp.js +1756 -0
  19. package/dist/tui/commands.js +427 -0
  20. package/dist/tui/index.js +42 -0
  21. package/dist/tui/renderers/Header.js +28 -0
  22. package/dist/tui/renderers/MessageList.js +1176 -0
  23. package/dist/tui/renderers/PromptBox.js +118 -0
  24. package/dist/tui/renderers/StatusLine.js +124 -0
  25. package/dist/tui/renderers/WorkingIndicator.js +70 -0
  26. package/dist/tui/slashCommandHighlight.js +8 -0
  27. package/dist/tui/theme.js +13 -0
  28. package/dist/tui/types.js +1 -0
  29. package/dist/usage.js +66 -0
  30. package/dist/version.js +5 -0
  31. package/node_modules/@remi/compact/dist/index.js +389 -0
  32. package/node_modules/@remi/compact/package.json +8 -0
  33. package/node_modules/@remi/config/dist/index.js +426 -0
  34. package/node_modules/@remi/config/package.json +8 -0
  35. package/node_modules/@remi/core/dist/contextBuilder.js +344 -0
  36. package/node_modules/@remi/core/dist/directoryOverview.js +359 -0
  37. package/node_modules/@remi/core/dist/index.js +2843 -0
  38. package/node_modules/@remi/core/dist/projectInstructions.js +123 -0
  39. package/node_modules/@remi/core/dist/responseStyles.js +98 -0
  40. package/node_modules/@remi/core/package.json +8 -0
  41. package/node_modules/@remi/llm/dist/index.js +804 -0
  42. package/node_modules/@remi/llm/package.json +8 -0
  43. package/node_modules/@remi/memory/dist/index.js +312 -0
  44. package/node_modules/@remi/memory/package.json +8 -0
  45. package/node_modules/@remi/permissions/dist/index.js +90 -0
  46. package/node_modules/@remi/permissions/package.json +8 -0
  47. package/node_modules/@remi/sessions/dist/index.js +370 -0
  48. package/node_modules/@remi/sessions/package.json +8 -0
  49. package/node_modules/@remi/skills/dist/index.js +273 -0
  50. package/node_modules/@remi/skills/package.json +8 -0
  51. package/node_modules/@remi/terminal-markdown/dist/index.js +1412 -0
  52. package/node_modules/@remi/terminal-markdown/package.json +8 -0
  53. package/node_modules/@remi/tools/dist/index.js +3875 -0
  54. package/node_modules/@remi/tools/package.json +8 -0
  55. package/package.json +48 -0
@@ -0,0 +1,427 @@
1
+ import { loadRemiConfig, readProjectRemiConfig, writeProjectRemiConfig } from '@remi/config';
2
+ import { defaultResponseStyleId, responseStylePresets } from '@remi/core';
3
+ import { createModelRouter } from '@remi/llm';
4
+ import { buildSkillInvocationPrompt, loadSkillContent, loadSkillIndex } from '@remi/skills';
5
+ import { buildInitPrompt } from '../initPrompt.js';
6
+ import { languageLabel, parseLanguageArg, supportedLanguages, t } from '../i18n.js';
7
+ import { runModelCommand } from '../model.js';
8
+ import { formatPermissionProfileChanged, parsePermissionProfileArg, permissionProfileOptions, resolveConfiguredPermissionProfile, } from '../permissions.js';
9
+ import { formatResponseStyleChanged, parseResponseStyleArg, styleDescription } from '../style.js';
10
+ import { formatSyntaxThemeChanged, parseSyntaxThemeArg, syntaxThemeOptions } from '../syntaxTheme.js';
11
+ import { defaultStatusLineItems, getStatusLineCatalog } from '../statusline.js';
12
+ const textMessage = (text, level = 'info') => ({
13
+ kind: 'message',
14
+ message: {
15
+ kind: 'system',
16
+ level,
17
+ text,
18
+ },
19
+ });
20
+ const slashCommandDefinitions = [
21
+ {
22
+ name: '/init',
23
+ aliases: ['init'],
24
+ descriptionKey: 'slash.init.description',
25
+ execute: () => ({ kind: 'prompt', prompt: buildInitPrompt() }),
26
+ },
27
+ {
28
+ name: '/model',
29
+ aliases: ['model'],
30
+ descriptionKey: 'slash.model.description',
31
+ execute: (context, args) => {
32
+ if (args.length > 0) {
33
+ return textMessage(runModelCommand(args, context.cwd).output);
34
+ }
35
+ return {
36
+ kind: 'message',
37
+ message: createModelPanelMessage(context.cwd, context.mainModelAlias, context.language),
38
+ };
39
+ },
40
+ },
41
+ {
42
+ name: '/permissions',
43
+ aliases: ['permissions'],
44
+ descriptionKey: 'slash.permissions.description',
45
+ execute: (context, args) => {
46
+ const [profileArg] = args;
47
+ if (!profileArg) {
48
+ return {
49
+ kind: 'message',
50
+ message: createPermissionProfilePanelMessage(context.cwd, context.permissionProfile, context.language),
51
+ };
52
+ }
53
+ const profile = parsePermissionProfileArg(profileArg);
54
+ if (!profile) {
55
+ return textMessage(t(context.language, 'permissions.unknown', { profile: profileArg }), 'warn');
56
+ }
57
+ return {
58
+ kind: 'permission-profile',
59
+ profile,
60
+ message: {
61
+ kind: 'system',
62
+ level: 'info',
63
+ text: formatPermissionProfileChanged(profile, context.language),
64
+ },
65
+ };
66
+ },
67
+ },
68
+ {
69
+ name: '/style',
70
+ aliases: ['style'],
71
+ descriptionKey: 'slash.style.description',
72
+ execute: (context, args) => {
73
+ const [styleArg] = args;
74
+ if (!styleArg || styleArg === 'list') {
75
+ return {
76
+ kind: 'message',
77
+ message: createStylePanelMessage(context.responseStyle, context.language),
78
+ };
79
+ }
80
+ const styleId = parseResponseStyleArg(styleArg);
81
+ if (!styleId) {
82
+ return textMessage(t(context.language, 'style.unknown', { style: styleArg }), 'warn');
83
+ }
84
+ return {
85
+ kind: 'style',
86
+ styleId,
87
+ message: {
88
+ kind: 'system',
89
+ level: 'info',
90
+ text: formatResponseStyleChanged(styleId, false, context.language),
91
+ },
92
+ };
93
+ },
94
+ },
95
+ {
96
+ name: '/theme',
97
+ aliases: ['theme', 'syntax-theme'],
98
+ descriptionKey: 'slash.theme.description',
99
+ execute: (context, args) => {
100
+ const [themeArg] = args;
101
+ if (!themeArg || themeArg === 'list') {
102
+ return {
103
+ kind: 'message',
104
+ message: createSyntaxThemePanelMessage(context.syntaxTheme, context.language),
105
+ };
106
+ }
107
+ const themeId = parseSyntaxThemeArg(themeArg);
108
+ if (!themeId) {
109
+ return textMessage(t(context.language, 'theme.unknown', { theme: themeArg }), 'warn');
110
+ }
111
+ return {
112
+ kind: 'syntax-theme',
113
+ themeId,
114
+ message: {
115
+ kind: 'system',
116
+ level: 'info',
117
+ text: formatSyntaxThemeChanged(themeId, false, context.language),
118
+ },
119
+ };
120
+ },
121
+ },
122
+ {
123
+ name: '/statusline',
124
+ aliases: ['statusline'],
125
+ descriptionKey: 'slash.statusline.description',
126
+ execute: context => ({
127
+ kind: 'message',
128
+ message: createStatusLinePanelMessage(context.statusLineItems, context.language),
129
+ }),
130
+ },
131
+ {
132
+ name: '/skills',
133
+ aliases: ['skills'],
134
+ descriptionKey: 'slash.skills.description',
135
+ execute: (context, args) => {
136
+ const [action, skillName, ...skillArgs] = args;
137
+ if (!action) {
138
+ return {
139
+ kind: 'message',
140
+ message: createSkillsActionPanelMessage(context.cwd, context.language),
141
+ };
142
+ }
143
+ if (action === 'list') {
144
+ return {
145
+ kind: 'message',
146
+ message: createSkillsListPanelMessage(context.cwd, context.language, 'list'),
147
+ };
148
+ }
149
+ if ((action === 'enable' || action === 'disable') && skillName) {
150
+ const nextDisabled = updateProjectDisabledSkills(context.cwd, skillName, action === 'disable');
151
+ const index = loadSkillIndex({ cwd: context.cwd, disabled: nextDisabled });
152
+ return textMessage(t(context.language, 'skills.saved', {
153
+ enabled: index.skills.filter(skill => skill.enabled).length,
154
+ disabled: index.skills.filter(skill => !skill.enabled).length,
155
+ }));
156
+ }
157
+ const disabled = loadRemiConfig({ cwd: context.cwd }).config.skills?.disabled;
158
+ const skill = loadSkillContent(disabled ? { cwd: context.cwd, disabled, name: action } : { cwd: context.cwd, name: action });
159
+ if (!skill) {
160
+ return textMessage(t(context.language, 'skills.unknown', { name: action }), 'warn');
161
+ }
162
+ return { kind: 'prompt', prompt: buildSkillInvocationPrompt(skill, [skillName, ...skillArgs].filter((arg) => Boolean(arg))) };
163
+ },
164
+ },
165
+ {
166
+ name: '/language',
167
+ aliases: ['language'],
168
+ descriptionKey: 'slash.language.description',
169
+ execute: (context, args) => {
170
+ const [languageArg] = args;
171
+ if (!languageArg) {
172
+ return {
173
+ kind: 'message',
174
+ message: createLanguagePanelMessage(context.language),
175
+ };
176
+ }
177
+ const language = parseLanguageArg(languageArg);
178
+ if (!language) {
179
+ return textMessage(t(context.language, 'language.unknown', { language: languageArg }), 'warn');
180
+ }
181
+ return {
182
+ kind: 'language',
183
+ language,
184
+ message: {
185
+ kind: 'system',
186
+ level: 'info',
187
+ text: t(language, 'language.saved', { label: languageLabel(language) }),
188
+ },
189
+ };
190
+ },
191
+ },
192
+ {
193
+ name: '/compact',
194
+ aliases: ['compact'],
195
+ descriptionKey: 'slash.compact.description',
196
+ execute: context => {
197
+ if (!context.sessionId) {
198
+ return textMessage(t(context.language, 'compact.noActive'), 'warn');
199
+ }
200
+ return { kind: 'compact' };
201
+ },
202
+ },
203
+ {
204
+ name: '/new',
205
+ aliases: ['new'],
206
+ descriptionKey: 'slash.new.description',
207
+ execute: () => ({ kind: 'new-session' }),
208
+ },
209
+ {
210
+ name: '/exit',
211
+ aliases: ['/quit', 'exit', 'quit'],
212
+ descriptionKey: 'slash.exit.description',
213
+ execute: () => ({ kind: 'exit' }),
214
+ },
215
+ ];
216
+ export const slashCommands = createSlashCommands();
217
+ function createSlashCommands(language) {
218
+ return slashCommandDefinitions.map(command => ({
219
+ name: command.name,
220
+ aliases: command.aliases,
221
+ execute: command.execute,
222
+ description: t(language, command.descriptionKey),
223
+ }));
224
+ }
225
+ function createStatusLinePanelMessage(activeItems = defaultStatusLineItems, language) {
226
+ const enabled = new Set(activeItems);
227
+ const items = getStatusLineCatalog(language).map(item => ({
228
+ id: item.id,
229
+ label: item.label,
230
+ description: item.description,
231
+ enabled: enabled.has(item.id),
232
+ }));
233
+ return {
234
+ kind: 'statusline-panel',
235
+ title: t(language, 'statusline.panel.title'),
236
+ subtitle: t(language, 'statusline.panel.subtitle'),
237
+ searchLabel: t(language, 'statusline.panel.search'),
238
+ helpText: t(language, 'statusline.panel.help'),
239
+ query: '',
240
+ selectedIndex: 0,
241
+ items,
242
+ };
243
+ }
244
+ function createModelPanelMessage(cwd, mainModelAlias, language) {
245
+ try {
246
+ const loaded = loadRemiConfig({ cwd });
247
+ const router = createModelRouter(loaded.config);
248
+ const active = router.resolve('main');
249
+ const models = router.models.list().map(model => ({
250
+ alias: model.alias,
251
+ displayName: model.displayName ?? model.alias,
252
+ }));
253
+ const activeAlias = mainModelAlias ?? active.alias;
254
+ const activeModel = models.find(model => model.alias === activeAlias);
255
+ const activeDisplayName = activeModel?.displayName ?? active.displayName ?? active.alias;
256
+ const selectedIndex = Math.max(0, models.findIndex(model => model.alias === activeAlias));
257
+ return {
258
+ kind: 'model-panel',
259
+ title: t(language, 'model.panel.title'),
260
+ subtitle: t(language, 'model.panel.subtitle', {
261
+ profile: active.profile,
262
+ model: activeDisplayName,
263
+ effort: active.effort ?? 'unset',
264
+ }),
265
+ activeAlias,
266
+ currentLabel: t(language, 'model.current'),
267
+ selectedIndex,
268
+ models,
269
+ };
270
+ }
271
+ catch (error) {
272
+ return {
273
+ kind: 'system',
274
+ level: 'error',
275
+ text: error instanceof Error ? error.message : t(language, 'model.error.load'),
276
+ };
277
+ }
278
+ }
279
+ function createStylePanelMessage(activeStyle = defaultResponseStyleId, language) {
280
+ const styles = responseStylePresets.map(style => ({
281
+ id: style.id,
282
+ label: style.label,
283
+ description: styleDescription(style.id, language),
284
+ }));
285
+ const selectedIndex = Math.max(0, styles.findIndex(style => style.id === activeStyle));
286
+ return {
287
+ kind: 'style-panel',
288
+ title: t(language, 'style.panel.title'),
289
+ subtitle: t(language, 'style.panel.subtitle'),
290
+ activeId: activeStyle,
291
+ currentLabel: t(language, 'style.current'),
292
+ selectedIndex,
293
+ styles,
294
+ };
295
+ }
296
+ function createSyntaxThemePanelMessage(activeTheme = 'default', language) {
297
+ const themes = syntaxThemeOptions();
298
+ const selectedIndex = Math.max(0, themes.findIndex(theme => theme.id === activeTheme));
299
+ return {
300
+ kind: 'syntax-theme-panel',
301
+ title: t(language, 'theme.panel.title'),
302
+ subtitle: t(language, 'theme.panel.subtitle'),
303
+ searchLabel: t(language, 'theme.panel.search'),
304
+ helpText: t(language, 'theme.panel.help'),
305
+ query: '',
306
+ activeId: activeTheme,
307
+ currentLabel: t(language, 'theme.current'),
308
+ selectedIndex,
309
+ themes,
310
+ };
311
+ }
312
+ function createLanguagePanelMessage(activeLanguage = 'en') {
313
+ const selectedIndex = Math.max(0, supportedLanguages.findIndex(language => language.code === activeLanguage));
314
+ return {
315
+ kind: 'language-panel',
316
+ title: t(activeLanguage, 'language.panel.title'),
317
+ subtitle: t(activeLanguage, 'language.panel.subtitle'),
318
+ activeCode: activeLanguage,
319
+ currentLabel: t(activeLanguage, 'language.current'),
320
+ selectedIndex,
321
+ languages: supportedLanguages,
322
+ };
323
+ }
324
+ function createPermissionProfilePanelMessage(cwd, activeProfile, language) {
325
+ const profile = activeProfile ?? resolveConfiguredPermissionProfile(cwd);
326
+ const profiles = permissionProfileOptions(language);
327
+ const selectedIndex = Math.max(0, profiles.findIndex(option => option.id === profile));
328
+ return {
329
+ kind: 'permission-profile-panel',
330
+ title: t(language, 'permissions.panel.title'),
331
+ subtitle: t(language, 'permissions.panel.subtitle'),
332
+ activeProfile: profile,
333
+ currentLabel: t(language, 'permissions.current'),
334
+ selectedIndex,
335
+ profiles,
336
+ };
337
+ }
338
+ export function createSkillsActionPanelMessage(cwd, language) {
339
+ const index = loadSkillIndexForCwd(cwd);
340
+ const subtitle = index.skills.length > 0 ? t(language, 'skills.panel.subtitle') : t(language, 'skills.none');
341
+ return {
342
+ kind: 'skills-panel',
343
+ title: t(language, 'skills.panel.title'),
344
+ subtitle,
345
+ helpText: t(language, 'skills.panel.help'),
346
+ mode: 'actions',
347
+ selectedIndex: 0,
348
+ actions: [
349
+ {
350
+ id: 'list',
351
+ label: t(language, 'skills.panel.list'),
352
+ description: t(language, 'skills.panel.listDescription'),
353
+ },
354
+ {
355
+ id: 'toggle',
356
+ label: t(language, 'skills.panel.toggle'),
357
+ description: t(language, 'skills.panel.toggleDescription'),
358
+ },
359
+ ],
360
+ };
361
+ }
362
+ export function createSkillsListPanelMessage(cwd, language, mode = 'list') {
363
+ const index = loadSkillIndexForCwd(cwd);
364
+ const skills = index.skills.map(skill => ({
365
+ name: skill.name,
366
+ displayName: skill.displayName,
367
+ description: skill.description,
368
+ source: skill.source,
369
+ enabled: skill.enabled,
370
+ filePath: skill.filePath,
371
+ }));
372
+ return {
373
+ kind: 'skills-panel',
374
+ title: t(language, 'skills.panel.title'),
375
+ subtitle: mode === 'toggle'
376
+ ? t(language, 'skills.panel.toggleSubtitle')
377
+ : skills.length > 0
378
+ ? t(language, 'skills.panel.listSubtitle', { count: skills.length })
379
+ : t(language, 'skills.none'),
380
+ helpText: t(language, 'skills.panel.help'),
381
+ mode,
382
+ selectedIndex: 0,
383
+ skills,
384
+ };
385
+ }
386
+ function loadSkillIndexForCwd(cwd) {
387
+ const disabled = loadRemiConfig({ cwd }).config.skills?.disabled;
388
+ return loadSkillIndex(disabled ? { cwd, disabled } : { cwd });
389
+ }
390
+ export function saveProjectDisabledSkills(cwd, disabled) {
391
+ const config = readProjectRemiConfig(cwd);
392
+ const uniqueDisabled = Array.from(new Set(disabled.map(skill => skill.trim().toLowerCase()).filter(Boolean))).sort();
393
+ writeProjectRemiConfig({
394
+ ...config,
395
+ skills: {
396
+ ...config.skills,
397
+ disabled: uniqueDisabled,
398
+ },
399
+ }, cwd);
400
+ }
401
+ function updateProjectDisabledSkills(cwd, skillName, disabled) {
402
+ const config = readProjectRemiConfig(cwd);
403
+ const current = new Set((config.skills?.disabled ?? []).map(skill => skill.trim().toLowerCase()).filter(Boolean));
404
+ const normalized = skillName.trim().toLowerCase();
405
+ if (disabled) {
406
+ current.add(normalized);
407
+ }
408
+ else {
409
+ current.delete(normalized);
410
+ }
411
+ const next = Array.from(current).sort();
412
+ saveProjectDisabledSkills(cwd, next);
413
+ return next;
414
+ }
415
+ export function listSlashCommands(language) {
416
+ return createSlashCommands(language);
417
+ }
418
+ export function executeSlashCommand(input, context) {
419
+ const [commandName = '', ...args] = input.trim().split(/\s+/);
420
+ const command = slashCommands.find(candidate => {
421
+ return candidate.name === commandName || candidate.aliases.includes(commandName);
422
+ });
423
+ if (!command) {
424
+ return textMessage(t(context.language, 'command.unknown', { command: commandName }), 'warn');
425
+ }
426
+ return command.execute(context, args);
427
+ }
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { stdin, stdout, stderr } from 'node:process';
3
+ import { render } from 'ink';
4
+ import { resolveActiveSessionId } from '@remi/sessions';
5
+ import { RemiApp } from './RemiApp.js';
6
+ import { formatResumeInstruction } from '../resume.js';
7
+ import { resolveConfiguredLanguage } from '../i18n.js';
8
+ import { addTokenUsage, createEmptyTokenUsage, formatTokenUsage } from '../usage.js';
9
+ export async function startTui(options = {}) {
10
+ const output = options.output ?? stdout;
11
+ const cwd = options.cwd ?? process.cwd();
12
+ let usageTotals = createEmptyTokenUsage();
13
+ let activeSessionId = options.sessionId ?? resolveActiveSessionId(cwd);
14
+ const remiAppProps = {
15
+ cwd,
16
+ initialSessionId: activeSessionId,
17
+ onSessionId: (sessionId) => {
18
+ activeSessionId = sessionId;
19
+ },
20
+ onTokenUsage: (usage) => {
21
+ usageTotals = addTokenUsage(usageTotals, usage);
22
+ },
23
+ };
24
+ const instance = render(_jsx(RemiApp, { ...remiAppProps }), {
25
+ stdin: options.input ?? stdin,
26
+ stdout: output,
27
+ stderr: options.error ?? stderr,
28
+ exitOnCtrlC: false,
29
+ interactive: options.interactive ?? true,
30
+ incrementalRendering: true,
31
+ });
32
+ await instance.waitUntilExit();
33
+ output.write(formatTuiExitFooter(usageTotals, activeSessionId, cwd));
34
+ }
35
+ export function formatTuiExitFooter(usageTotals, sessionId, cwd = process.cwd()) {
36
+ const language = resolveConfiguredLanguage(cwd);
37
+ const lines = ['', formatTokenUsage(usageTotals, language)];
38
+ if (sessionId) {
39
+ lines.push(formatResumeInstruction(sessionId));
40
+ }
41
+ return `${lines.join('\n')}\n`;
42
+ }
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { userInfo } from 'node:os';
3
+ import { Box, Text } from 'ink';
4
+ import { compactPath } from './StatusLine.js';
5
+ import { remiDarkTheme } from '../theme.js';
6
+ export function Header({ status, width = 100 }) {
7
+ const username = currentUsername();
8
+ const cwdWidth = Math.max(24, Math.min(48, width - 22));
9
+ const directory = compactPath(status.cwd, cwdWidth);
10
+ const contentWidth = Math.max(42, Math.min(64, Math.max(displayLength('=^ω^= Remi CLI'), displayLength(`Welcome back, ${username}`), displayLength(`model: ${status.model}`), displayLength(`directory: ${directory}`)) + 4));
11
+ const cardWidth = Math.min(Math.max(46, width - 2), contentWidth + 4);
12
+ return (_jsxs(Box, { borderColor: remiDarkTheme.border, borderStyle: "single", flexDirection: "column", flexShrink: 0, paddingX: 1, width: cardWidth, children: [_jsxs(Text, { children: [_jsx(Text, { color: remiDarkTheme.accent, children: "=^\u03C9^=" }), _jsx(Text, { color: remiDarkTheme.text, bold: true, children: ' Remi CLI' })] }), _jsxs(Text, { children: [_jsx(Text, { color: remiDarkTheme.text, children: "Welcome back, " }), _jsx(Text, { color: remiDarkTheme.accent, children: username })] }), _jsx(Box, { height: 1 }), _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: remiDarkTheme.muted, children: "model: " }), _jsx(Text, { color: remiDarkTheme.text, children: status.model })] }), _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: remiDarkTheme.muted, children: "directory: " }), _jsx(Text, { color: remiDarkTheme.text, children: directory })] })] }));
13
+ }
14
+ function displayLength(value) {
15
+ let length = 0;
16
+ for (const char of value) {
17
+ length += char.charCodeAt(0) > 0xff ? 2 : 1;
18
+ }
19
+ return length;
20
+ }
21
+ function currentUsername() {
22
+ try {
23
+ return userInfo().username || 'developer';
24
+ }
25
+ catch {
26
+ return 'developer';
27
+ }
28
+ }