@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
package/dist/repl.js ADDED
@@ -0,0 +1,346 @@
1
+ import { createInterface } from 'node:readline/promises';
2
+ import { stdin as input, stdout as output } from 'node:process';
3
+ import { homedir } from 'node:os';
4
+ import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
5
+ import { runChatTurn } from '@remi/core';
6
+ import { createNewSession, readSessionPermissionRules, resolveActiveSessionId, writeSessionPermissionRules } from '@remi/sessions';
7
+ import { buildInitPrompt } from './initPrompt.js';
8
+ import { languageLabel, parseLanguageArg, resolveConfiguredLanguage, saveProjectLanguage, t } from './i18n.js';
9
+ import { runModelCommand } from './model.js';
10
+ import { addSessionPermissionRules, formatPermissionProfileChanged, formatPermissionProfileList, formatPermissionRulesScope, parsePermissionProfileArg, resolveConfiguredPermissionProfile, saveProjectPermissionRule, saveProjectPermissionProfile, } from './permissions.js';
11
+ import { formatPermissionFileAction } from './permissionDisplay.js';
12
+ import { formatResumeInstruction } from './resume.js';
13
+ import { formatResponseStyleChanged, formatResponseStyleList, parseResponseStyleArg, resolveConfiguredResponseStyle, saveProjectResponseStyle, } from './style.js';
14
+ import { formatSyntaxThemeChanged, formatSyntaxThemeList, parseSyntaxThemeArg, resolveConfiguredSyntaxTheme, saveProjectSyntaxTheme, } from './syntaxTheme.js';
15
+ import { addTokenUsage, createEmptyTokenUsage, formatTokenUsage } from './usage.js';
16
+ import { version } from './version.js';
17
+ export async function startRepl(options = {}) {
18
+ const out = options.output ?? output;
19
+ const cwd = options.cwd ?? process.cwd();
20
+ let sessionId = options.sessionId ?? resolveActiveSessionId(cwd);
21
+ const rl = createInterface({
22
+ input: options.input ?? input,
23
+ output: out,
24
+ prompt: '❯ ',
25
+ historySize: 200,
26
+ });
27
+ let isClosed = false;
28
+ let usageTotals = createEmptyTokenUsage();
29
+ let responseStyle = resolveConfiguredResponseStyle(cwd);
30
+ let syntaxTheme = resolveConfiguredSyntaxTheme(cwd);
31
+ let language = resolveConfiguredLanguage(cwd);
32
+ let permissionProfile = resolveConfiguredPermissionProfile(cwd);
33
+ let sessionPermissionRules = readSessionPermissionRules(cwd, sessionId);
34
+ rl.on('close', () => {
35
+ isClosed = true;
36
+ });
37
+ const write = (text = '') => {
38
+ out.write(`${text}\n`);
39
+ };
40
+ const prompt = () => {
41
+ if (!isClosed) {
42
+ rl.prompt();
43
+ }
44
+ };
45
+ write(`Remi ${version}`);
46
+ write('Commands: /init, /model, /permissions, /style, /theme, /language, /new, /exit');
47
+ prompt();
48
+ for await (const rawLine of rl) {
49
+ const line = rawLine.trim();
50
+ if (line.length === 0) {
51
+ prompt();
52
+ continue;
53
+ }
54
+ if (line === '/exit' || line === '/quit' || line === 'exit' || line === 'quit') {
55
+ break;
56
+ }
57
+ if (line === '/model' || line.startsWith('/model ')) {
58
+ const args = line.slice('/model'.length).trim().split(/\s+/).filter(Boolean);
59
+ write(runModelCommand(args, cwd).output);
60
+ prompt();
61
+ continue;
62
+ }
63
+ if (line === '/permissions' || line.startsWith('/permissions ')) {
64
+ const args = line.slice('/permissions'.length).trim().split(/\s+/).filter(Boolean);
65
+ const profileArg = args[0];
66
+ if (!profileArg) {
67
+ write(formatPermissionProfileList(permissionProfile, language));
68
+ prompt();
69
+ continue;
70
+ }
71
+ const nextProfile = parsePermissionProfileArg(profileArg);
72
+ if (!nextProfile) {
73
+ write(t(language, 'permissions.unknown', { profile: profileArg }));
74
+ prompt();
75
+ continue;
76
+ }
77
+ permissionProfile = nextProfile;
78
+ saveProjectPermissionProfile(cwd, permissionProfile);
79
+ write(formatPermissionProfileChanged(permissionProfile, language));
80
+ prompt();
81
+ continue;
82
+ }
83
+ if (line === '/new') {
84
+ write(formatTokenUsage(usageTotals, language));
85
+ sessionId = createNewSession(cwd);
86
+ sessionPermissionRules = readSessionPermissionRules(cwd, sessionId);
87
+ write(`${t(language, 'session.new')} ${sessionId}`);
88
+ prompt();
89
+ continue;
90
+ }
91
+ if (line === '/language' || line.startsWith('/language ')) {
92
+ const args = line.slice('/language'.length).trim().split(/\s+/).filter(Boolean);
93
+ const nextLanguage = parseLanguageArg(args[0]);
94
+ if (!nextLanguage) {
95
+ write(t(language, 'language.unknown', { language: args[0] ?? '' }));
96
+ prompt();
97
+ continue;
98
+ }
99
+ language = nextLanguage;
100
+ saveProjectLanguage(cwd, language);
101
+ write(t(language, 'language.saved', { label: languageLabel(language) }));
102
+ prompt();
103
+ continue;
104
+ }
105
+ if (line === '/style' || line.startsWith('/style ')) {
106
+ const args = line.slice('/style'.length).trim().split(/\s+/).filter(Boolean);
107
+ const styleArg = args[0];
108
+ if (!styleArg || styleArg === 'list') {
109
+ write(formatResponseStyleList(responseStyle, language));
110
+ prompt();
111
+ continue;
112
+ }
113
+ const styleId = parseResponseStyleArg(styleArg);
114
+ if (!styleId) {
115
+ write(t(language, 'style.unknown', { style: styleArg }));
116
+ prompt();
117
+ continue;
118
+ }
119
+ responseStyle = styleId;
120
+ saveProjectResponseStyle(cwd, responseStyle);
121
+ write(formatResponseStyleChanged(responseStyle, true, language));
122
+ prompt();
123
+ continue;
124
+ }
125
+ if (line === '/theme' || line.startsWith('/theme ')) {
126
+ const args = line.slice('/theme'.length).trim().split(/\s+/).filter(Boolean);
127
+ const themeArg = args[0];
128
+ if (!themeArg || themeArg === 'list') {
129
+ write(formatSyntaxThemeList(syntaxTheme));
130
+ prompt();
131
+ continue;
132
+ }
133
+ const themeId = parseSyntaxThemeArg(themeArg);
134
+ if (!themeId) {
135
+ write(t(language, 'theme.unknown', { theme: themeArg }));
136
+ prompt();
137
+ continue;
138
+ }
139
+ syntaxTheme = themeId;
140
+ saveProjectSyntaxTheme(cwd, syntaxTheme);
141
+ write(formatSyntaxThemeChanged(syntaxTheme, true, language));
142
+ prompt();
143
+ continue;
144
+ }
145
+ const chatInput = line === '/init' ? buildInitPrompt() : line;
146
+ if (line.startsWith('/') && line !== '/init') {
147
+ write(t(language, 'command.unknown', { command: line }));
148
+ prompt();
149
+ continue;
150
+ }
151
+ const requestToolPermission = async (request, decision) => {
152
+ const fileAction = formatPermissionFileAction(request, language);
153
+ write(t(language, fileAction ? 'permission.request.title.action' : 'permission.request.title'));
154
+ write(t(language, 'permission.request.reason', { reason: decision.reason }));
155
+ if (fileAction) {
156
+ write(fileAction.action);
157
+ write(fileAction.detail);
158
+ }
159
+ else {
160
+ write(`$ ${request.command?.commandLine ?? `${request.toolName} ${request.inputSummary}`}`);
161
+ }
162
+ if (request.command?.capability) {
163
+ write(formatCommandCapabilityForDisplay(request.command.capability));
164
+ }
165
+ const explicitSuggestedRules = request.suggestedRules?.length
166
+ ? request.suggestedRules
167
+ : request.command?.suggestedRules?.length
168
+ ? request.command.suggestedRules
169
+ : request.command?.suggestedRule
170
+ ? [request.command.suggestedRule]
171
+ : request.command?.suggestedPrefix
172
+ ? [{ kind: 'shell-prefix', prefix: request.command.suggestedPrefix }]
173
+ : [];
174
+ const fallbackSuggestedRules = explicitSuggestedRules.length > 0 ? [] : fallbackFilesystemPermissionRules(request);
175
+ const suggestedRules = explicitSuggestedRules.length > 0 ? explicitSuggestedRules : fallbackSuggestedRules;
176
+ const ruleLabel = suggestedRules.length > 0 ? formatPermissionRulesScope(suggestedRules, language) : '';
177
+ const canOfferRule = ((request.canPersist ?? request.command?.canPersist ?? false) || fallbackSuggestedRules.length > 0) && suggestedRules.length > 0;
178
+ const filesystemRulesOnly = suggestedRules.length > 0 && suggestedRules.every(rule => rule.kind === 'filesystem-write-root');
179
+ const shellRulesOnly = suggestedRules.length > 0 && suggestedRules.every(rule => rule.kind === 'shell-prefix');
180
+ const persistHint = canOfferRule && filesystemRulesOnly
181
+ ? `, s=session ${ruleLabel}`
182
+ : canOfferRule && shellRulesOnly
183
+ ? `, p=don't-ask-again ${ruleLabel}`
184
+ : '';
185
+ const answer = (await rl.question(`Allow? y=yes${persistHint}, n=no: `)).trim().toLowerCase();
186
+ if (answer === 's' && canOfferRule && filesystemRulesOnly) {
187
+ sessionPermissionRules = addSessionPermissionRules(sessionPermissionRules, cwd, suggestedRules);
188
+ writeSessionPermissionRules(cwd, sessionId, sessionPermissionRules);
189
+ write(t(language, 'permission.savedRules', { rule: ruleLabel }));
190
+ return { status: 'allow', reason: 'User approved and saved a permission rule.', requirements: decision.requirements };
191
+ }
192
+ if (answer === 'p' && canOfferRule && shellRulesOnly) {
193
+ for (const rule of suggestedRules) {
194
+ saveProjectPermissionRule(cwd, rule);
195
+ }
196
+ write(t(language, 'permission.savedPersistentRules', { rule: ruleLabel }));
197
+ return { status: 'allow', reason: 'User approved and saved a persistent permission rule.', requirements: decision.requirements };
198
+ }
199
+ if (answer === 'y' || answer === 'yes') {
200
+ return { status: 'allow', reason: 'User approved this tool call once.', requirements: decision.requirements };
201
+ }
202
+ return { status: 'deny', reason: 'User denied the permission request.', requirements: decision.requirements };
203
+ };
204
+ let sawUsageEvent = false;
205
+ for await (const event of runChatTurn(chatInput, {
206
+ cwd,
207
+ sessionId,
208
+ responseStyle,
209
+ permissionProfile,
210
+ requestToolPermission,
211
+ permissionRules: () => sessionPermissionRules,
212
+ })) {
213
+ if (event.type === 'start') {
214
+ sessionId = event.sessionId;
215
+ write(`model ${event.model.displayName ?? event.model.alias} (${event.model.model})`);
216
+ }
217
+ else if (event.type === 'compact') {
218
+ write(t(language, 'compact.done', { count: event.sourceEventCount, tokens: event.estimatedTokens }));
219
+ }
220
+ else if (event.type === 'delta') {
221
+ out.write(event.text);
222
+ }
223
+ else if (event.type === 'tool_call') {
224
+ write(`tool ${event.toolName} ${event.summary}`);
225
+ }
226
+ else if (event.type === 'tool_result') {
227
+ if (event.recoverable) {
228
+ continue;
229
+ }
230
+ write(`${event.ok ? 'result' : 'error'} ${event.toolName} ${event.summary}`);
231
+ }
232
+ else if (event.type === 'plan_update') {
233
+ write('Plan');
234
+ for (const item of event.items) {
235
+ const marker = item.status === 'completed' ? '✓' : item.status === 'in_progress' ? '▸' : '□';
236
+ write(` ${marker} ${item.content}`);
237
+ }
238
+ }
239
+ else if (event.type === 'usage') {
240
+ sawUsageEvent = true;
241
+ usageTotals = addTokenUsage(usageTotals, event.usage);
242
+ }
243
+ else if (event.type === 'done') {
244
+ if (event.usage && !sawUsageEvent) {
245
+ usageTotals = addTokenUsage(usageTotals, event.usage);
246
+ }
247
+ write('');
248
+ }
249
+ else if (event.type === 'error') {
250
+ write(`error ${event.message}`);
251
+ }
252
+ }
253
+ prompt();
254
+ }
255
+ if (!isClosed) {
256
+ rl.close();
257
+ }
258
+ write(formatTokenUsage(usageTotals, language));
259
+ write(formatResumeInstruction(sessionId));
260
+ }
261
+ function formatCommandCapabilityForDisplay(capability) {
262
+ if (!capability) {
263
+ return '';
264
+ }
265
+ const parts = [`capability: ${capability.id}`, `risk: ${capability.risk}`];
266
+ if (capability.platform) {
267
+ parts.push(`platform: ${capability.platform}`);
268
+ }
269
+ if (capability.target) {
270
+ parts.push(`target: ${capability.target}`);
271
+ }
272
+ if (capability.packageManager) {
273
+ parts.push(`manager: ${capability.packageManager}`);
274
+ }
275
+ if (capability.requiresAdmin) {
276
+ parts.push('admin required');
277
+ }
278
+ return parts.join(' · ');
279
+ }
280
+ function fallbackFilesystemPermissionRules(request) {
281
+ const operations = permissionFilesystemOperationsForTool(request.toolName);
282
+ const targetPath = request.targetPath ?? parsePermissionRequestPath(request.inputSummary);
283
+ if (operations.length === 0 || !targetPath) {
284
+ return [];
285
+ }
286
+ const root = fallbackFilesystemPermissionRoot(targetPath, request.cwd, request.toolName);
287
+ return root ? [{ kind: 'filesystem-write-root', root, operations }] : [];
288
+ }
289
+ function permissionFilesystemOperationForTool(toolName) {
290
+ if (toolName === 'create_directory') {
291
+ return 'create';
292
+ }
293
+ if (toolName === 'write_file') {
294
+ return 'write';
295
+ }
296
+ if (toolName === 'edit_file') {
297
+ return 'edit';
298
+ }
299
+ if (toolName === 'delete_file' || toolName === 'delete_directory') {
300
+ return 'delete';
301
+ }
302
+ return undefined;
303
+ }
304
+ function permissionFilesystemOperationsForTool(toolName) {
305
+ const operation = permissionFilesystemOperationForTool(toolName);
306
+ if (!operation) {
307
+ return [];
308
+ }
309
+ if (operation === 'write' || operation === 'edit') {
310
+ return ['write', 'edit'];
311
+ }
312
+ return [operation];
313
+ }
314
+ function fallbackFilesystemPermissionRoot(targetPath, cwd, toolName) {
315
+ const absolutePath = isAbsolute(targetPath) ? resolve(targetPath) : resolve(cwd, targetPath);
316
+ const resolvedCwd = resolve(cwd);
317
+ if (isPathInsideOrEqual(absolutePath, resolvedCwd)) {
318
+ return resolvedCwd;
319
+ }
320
+ const desktop = resolve(homedir(), 'Desktop');
321
+ if (isPathInsideOrEqual(absolutePath, desktop)) {
322
+ const rel = relative(desktop, absolutePath);
323
+ const [project] = rel.split(sep).filter(Boolean);
324
+ if (project) {
325
+ return resolve(desktop, project);
326
+ }
327
+ }
328
+ return toolName === 'delete_directory' ? absolutePath : dirname(absolutePath);
329
+ }
330
+ function isPathInsideOrEqual(target, root) {
331
+ const rel = relative(root, target);
332
+ return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
333
+ }
334
+ function parsePermissionRequestPath(inputSummary) {
335
+ try {
336
+ const parsed = JSON.parse(inputSummary);
337
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
338
+ return undefined;
339
+ }
340
+ const path = parsed['path'];
341
+ return typeof path === 'string' && path.trim().length > 0 ? path : undefined;
342
+ }
343
+ catch {
344
+ return undefined;
345
+ }
346
+ }
package/dist/resume.js ADDED
@@ -0,0 +1,3 @@
1
+ export function formatResumeInstruction(sessionId, _env = process.env) {
2
+ return `To continue this session, run remi resume ${sessionId}`;
3
+ }
package/dist/setup.js ADDED
@@ -0,0 +1,62 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { createDeepSeekPresetConfig, userConfigPath, writeUserRemiConfig } from '@remi/config';
4
+ export function runSetupCommand(args = [], options = {}) {
5
+ const force = args.includes('--force');
6
+ const help = args.includes('--help') || args.includes('-h');
7
+ const unknown = args.find(arg => arg !== '--force' && arg !== '--help' && arg !== '-h');
8
+ if (help) {
9
+ return { code: 0, output: formatSetupHelp() };
10
+ }
11
+ if (unknown) {
12
+ return { code: 1, output: `Unknown setup option: ${unknown}\n\n${formatSetupHelp()}` };
13
+ }
14
+ const homeDir = options.homeDir ?? homedir();
15
+ const platform = options.platform ?? process.platform;
16
+ const path = userConfigPath(homeDir);
17
+ const existed = existsSync(path);
18
+ if (!existed || force) {
19
+ writeUserRemiConfig(createDeepSeekPresetConfig(), homeDir);
20
+ }
21
+ return {
22
+ code: 0,
23
+ output: formatSetupResult({ path, wrote: !existed || force, platform }),
24
+ };
25
+ }
26
+ export function formatSetupHelp() {
27
+ return [
28
+ 'Usage:',
29
+ ' remi setup Create ~/.remi/config.json if it does not exist',
30
+ ' remi setup --force Rewrite ~/.remi/config.json with the default preset',
31
+ '',
32
+ 'The generated config references DEEPSEEK_API_KEY by environment variable and never stores a raw key.',
33
+ ].join('\n');
34
+ }
35
+ function formatSetupResult({ path, wrote, platform }) {
36
+ return [
37
+ 'Remi setup',
38
+ '',
39
+ `${wrote ? 'created' : 'exists '} config ${path}`,
40
+ '',
41
+ 'Next steps:',
42
+ ...platformSetupHints(platform),
43
+ ].join('\n');
44
+ }
45
+ export function platformSetupHints(platform) {
46
+ if (platform === 'win32') {
47
+ return [
48
+ ' 1. Set your API key in PowerShell:',
49
+ ' [Environment]::SetEnvironmentVariable("DEEPSEEK_API_KEY", "<your-key>", "User")',
50
+ ' 2. Restart the terminal so the new user environment is loaded.',
51
+ ' 3. If remi is not found after npm install -g, add the npm global bin directory to PATH.',
52
+ ' Usually this is %APPDATA%\\npm; confirm with: npm prefix -g',
53
+ ];
54
+ }
55
+ return [
56
+ ' 1. Add your API key to your shell profile:',
57
+ ' export DEEPSEEK_API_KEY="<your-key>"',
58
+ ' 2. Restart the terminal or source the profile.',
59
+ ' 3. If remi is not found after npm install -g, add the npm global bin directory to PATH.',
60
+ ' Usually this is $(npm prefix -g)/bin; confirm with: npm prefix -g',
61
+ ];
62
+ }
@@ -0,0 +1,59 @@
1
+ import { loadRemiConfig, readProjectRemiConfig, statusLineItemIds, writeProjectRemiConfig } from '@remi/config';
2
+ import { t } from './i18n.js';
3
+ export const defaultStatusLineItems = ['model', 'context-remaining', 'cwd'];
4
+ export const statusLineCatalog = [
5
+ { id: 'model', label: 'model', description: 'Current main model' },
6
+ { id: 'cwd', label: 'cwd', description: 'Current working directory' },
7
+ { id: 'git-branch', label: 'git-branch', description: 'Current Git branch (hidden when unavailable)' },
8
+ { id: 'branch-changes', label: 'branch-changes', description: 'Working tree line changes, such as +8 -1' },
9
+ { id: 'context-remaining', label: 'context-remaining', description: 'Percentage of context window remaining' },
10
+ { id: 'used-tokens', label: 'used-tokens', description: 'Total tokens used in the current session' },
11
+ { id: 'total-input-tokens', label: 'total-input-tokens', description: 'Total input tokens used in the current session' },
12
+ { id: 'total-output-tokens', label: 'total-output-tokens', description: 'Total output tokens used in the current session' },
13
+ { id: 'run-state', label: 'run-state', description: 'Compact session run-state text' },
14
+ { id: 'permissions', label: 'permissions', description: 'Current permission profile' },
15
+ ];
16
+ const statusLineDescriptionKeys = {
17
+ model: 'statusline.catalog.model',
18
+ cwd: 'statusline.catalog.cwd',
19
+ 'git-branch': 'statusline.catalog.git-branch',
20
+ 'branch-changes': 'statusline.catalog.branch-changes',
21
+ 'context-remaining': 'statusline.catalog.context-remaining',
22
+ 'used-tokens': 'statusline.catalog.used-tokens',
23
+ 'total-input-tokens': 'statusline.catalog.total-input-tokens',
24
+ 'total-output-tokens': 'statusline.catalog.total-output-tokens',
25
+ 'run-state': 'statusline.catalog.run-state',
26
+ permissions: 'statusline.catalog.permissions',
27
+ };
28
+ export function getStatusLineCatalog(language) {
29
+ return statusLineCatalog.map(item => ({
30
+ ...item,
31
+ description: t(language, statusLineDescriptionKeys[item.id]),
32
+ }));
33
+ }
34
+ export function normalizeStatusLineItems(items) {
35
+ if (!Array.isArray(items)) {
36
+ return defaultStatusLineItems;
37
+ }
38
+ const allowed = new Set(statusLineItemIds);
39
+ const normalized = items.filter((item) => typeof item === 'string' && allowed.has(item));
40
+ return normalized.length > 0 ? Array.from(new Set(normalized)) : defaultStatusLineItems;
41
+ }
42
+ export function resolveConfiguredStatusLineItems(cwd) {
43
+ try {
44
+ return normalizeStatusLineItems(loadRemiConfig({ cwd }).config.statusLine?.items);
45
+ }
46
+ catch {
47
+ return defaultStatusLineItems;
48
+ }
49
+ }
50
+ export function saveProjectStatusLineItems(cwd, items) {
51
+ const projectConfig = readProjectRemiConfig(cwd);
52
+ writeProjectRemiConfig({
53
+ ...projectConfig,
54
+ statusLine: {
55
+ ...projectConfig.statusLine,
56
+ items: normalizeStatusLineItems(items),
57
+ },
58
+ }, cwd);
59
+ }
package/dist/style.js ADDED
@@ -0,0 +1,48 @@
1
+ import { defaultResponseStyleId, isResponseStyleId, responseStylePresets, } from '@remi/core';
2
+ import { loadRemiConfig, readProjectRemiConfig, writeProjectRemiConfig } from '@remi/config';
3
+ import { t } from './i18n.js';
4
+ export function parseResponseStyleArg(value) {
5
+ if (!value) {
6
+ return undefined;
7
+ }
8
+ const normalized = value.toLowerCase();
9
+ if (normalized === '极简') {
10
+ return 'minimal';
11
+ }
12
+ if (isResponseStyleId(normalized)) {
13
+ return normalized;
14
+ }
15
+ return responseStylePresets.find(style => style.label.toLowerCase() === normalized)?.id;
16
+ }
17
+ export function formatResponseStyleList(activeStyle = defaultResponseStyleId, language) {
18
+ return [
19
+ 'Response styles:',
20
+ ...responseStylePresets.map(style => {
21
+ const marker = style.id === activeStyle ? '*' : ' ';
22
+ return ` ${marker} ${style.id.padEnd(8)} ${style.label.padEnd(8)} ${styleDescription(style.id, language)}`;
23
+ }),
24
+ ].join('\n');
25
+ }
26
+ export function resolveConfiguredResponseStyle(cwd) {
27
+ return parseResponseStyleArg(loadRemiConfig({ cwd }).config.responseStyle) ?? defaultResponseStyleId;
28
+ }
29
+ export function saveProjectResponseStyle(cwd, styleId) {
30
+ const config = readProjectRemiConfig(cwd);
31
+ writeProjectRemiConfig({ ...config, responseStyle: styleId }, cwd);
32
+ }
33
+ export function formatResponseStyleChanged(styleId, saved = false, language) {
34
+ const style = responseStylePresets.find(candidate => candidate.id === styleId);
35
+ const label = style?.label ?? styleId;
36
+ return saved ? t(language, 'style.saved', { label }) : t(language, 'style.selected', { label });
37
+ }
38
+ export function styleDescription(styleId, language) {
39
+ if (styleId === 'brief')
40
+ return t(language, 'style.description.brief');
41
+ if (styleId === 'explain')
42
+ return t(language, 'style.description.explain');
43
+ if (styleId === 'mentor')
44
+ return t(language, 'style.description.mentor');
45
+ if (styleId === 'minimal')
46
+ return t(language, 'style.description.minimal');
47
+ return t(language, 'style.description.remi');
48
+ }
@@ -0,0 +1,39 @@
1
+ import { loadRemiConfig, readProjectRemiConfig, writeProjectRemiConfig } from '@remi/config';
2
+ import { listTerminalSyntaxThemes, resolveTerminalSyntaxThemeId, terminalSyntaxThemeLabel, } from '@remi/terminal-markdown';
3
+ import { t } from './i18n.js';
4
+ export function syntaxThemeOptions() {
5
+ return listTerminalSyntaxThemes();
6
+ }
7
+ export function parseSyntaxThemeArg(value) {
8
+ if (!value) {
9
+ return undefined;
10
+ }
11
+ const normalized = value.trim().toLowerCase();
12
+ const option = listTerminalSyntaxThemes().find(theme => {
13
+ return theme.id === normalized || theme.label.toLowerCase() === normalized;
14
+ });
15
+ return option?.id;
16
+ }
17
+ export function resolveConfiguredSyntaxTheme(cwd) {
18
+ try {
19
+ return resolveTerminalSyntaxThemeId(loadRemiConfig({ cwd }).config.syntaxTheme);
20
+ }
21
+ catch {
22
+ return 'default';
23
+ }
24
+ }
25
+ export function saveProjectSyntaxTheme(cwd, themeId) {
26
+ const config = readProjectRemiConfig(cwd);
27
+ writeProjectRemiConfig({ ...config, syntaxTheme: themeId }, cwd);
28
+ }
29
+ export function formatSyntaxThemeChanged(themeId, saved = false, language) {
30
+ return t(language, saved ? 'theme.saved' : 'theme.selected', { label: terminalSyntaxThemeLabel(themeId) });
31
+ }
32
+ export function formatSyntaxThemeList(activeTheme = 'default') {
33
+ return syntaxThemeOptions()
34
+ .map(theme => {
35
+ const marker = theme.id === activeTheme ? '*' : ' ';
36
+ return `${marker} ${theme.id.padEnd(24)} ${theme.label}`;
37
+ })
38
+ .join('\n');
39
+ }