@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/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # Remi
2
+
3
+ Personal alpha build of the Remi AI coding agent CLI.
4
+
5
+ ```bash
6
+ npm install -g @wangzhizhi/remi@alpha
7
+ remi setup
8
+ remi doctor
9
+ ```
package/dist/doctor.js ADDED
@@ -0,0 +1,108 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { loadRemiConfig } from '@remi/config';
5
+ import { createModelRouter } from '@remi/llm';
6
+ function commandVersion(command, args = ['--version']) {
7
+ return execFileSync(command, args, {
8
+ encoding: 'utf8',
9
+ stdio: ['ignore', 'pipe', 'ignore'],
10
+ }).trim();
11
+ }
12
+ export function runDoctor(cwd = process.cwd()) {
13
+ const checks = [];
14
+ const nodeVersion = process.version;
15
+ checks.push({
16
+ name: 'node',
17
+ ok: nodeVersion.startsWith('v24.'),
18
+ detail: nodeVersion,
19
+ });
20
+ try {
21
+ const pnpmVersion = commandVersion('pnpm', ['-v']);
22
+ checks.push({
23
+ name: 'pnpm',
24
+ ok: pnpmVersion.length > 0,
25
+ detail: pnpmVersion,
26
+ });
27
+ }
28
+ catch (error) {
29
+ checks.push({
30
+ name: 'pnpm',
31
+ ok: false,
32
+ detail: error instanceof Error ? error.message : String(error),
33
+ });
34
+ }
35
+ checks.push({
36
+ name: 'cwd',
37
+ ok: cwd.length > 0,
38
+ detail: cwd,
39
+ });
40
+ try {
41
+ const agentDir = join(cwd, '.agent');
42
+ mkdirSync(agentDir, { recursive: true });
43
+ const probePath = join(agentDir, '.doctor-write-test');
44
+ writeFileSync(probePath, 'ok');
45
+ rmSync(probePath, { force: true });
46
+ checks.push({
47
+ name: 'agentDir',
48
+ ok: true,
49
+ detail: agentDir,
50
+ });
51
+ }
52
+ catch (error) {
53
+ checks.push({
54
+ name: 'agentDir',
55
+ ok: false,
56
+ detail: error instanceof Error ? error.message : String(error),
57
+ });
58
+ }
59
+ const keyNames = ['REMI_API_KEY', 'OPENAI_API_KEY', 'ANTHROPIC_AUTH_TOKEN', 'DEEPSEEK_API_KEY'];
60
+ const configuredKeys = keyNames.filter(name => Boolean(process.env[name]));
61
+ checks.push({
62
+ name: 'apiKey',
63
+ ok: configuredKeys.length > 0,
64
+ detail: configuredKeys.length > 0 ? configuredKeys.join(', ') : 'not configured',
65
+ });
66
+ try {
67
+ const loaded = loadRemiConfig({ cwd });
68
+ const router = createModelRouter(loaded.config);
69
+ const providers = router.providers.list();
70
+ const missingProviders = providers.filter(provider => provider.apiKeyStatus === 'missing');
71
+ checks.push({
72
+ name: 'profile',
73
+ ok: Boolean(loaded.config.activeProfile),
74
+ detail: loaded.config.activeProfile ?? 'not configured',
75
+ });
76
+ checks.push({
77
+ name: 'providers',
78
+ ok: providers.length > 0,
79
+ detail: providers.length > 0 ? providers.map(provider => provider.alias).join(', ') : 'not configured',
80
+ });
81
+ checks.push({
82
+ name: 'modelKeys',
83
+ ok: missingProviders.length === 0,
84
+ detail: missingProviders.length === 0 ? 'configured' : `missing: ${missingProviders.map(provider => provider.alias).join(', ')}`,
85
+ });
86
+ }
87
+ catch (error) {
88
+ checks.push({
89
+ name: 'models',
90
+ ok: false,
91
+ detail: error instanceof Error ? error.message : String(error),
92
+ });
93
+ }
94
+ return {
95
+ ok: checks.filter(check => check.name !== 'apiKey' && check.name !== 'modelKeys').every(check => check.ok),
96
+ cwd,
97
+ checks,
98
+ };
99
+ }
100
+ export function formatDoctor(report) {
101
+ const lines = ['Remi doctor', ''];
102
+ for (const check of report.checks) {
103
+ const status = check.ok ? 'ok' : 'warn';
104
+ lines.push(`${status.padEnd(5)} ${check.name.padEnd(10)} ${check.detail}`);
105
+ }
106
+ lines.push('', `overall ${report.ok ? 'ok' : 'needs attention'}`);
107
+ return lines.join('\n');
108
+ }
package/dist/git.js ADDED
@@ -0,0 +1,41 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ export function loadGitStatus(cwd) {
3
+ const isRepo = runGit(cwd, ['rev-parse', '--is-inside-work-tree']);
4
+ if (isRepo !== 'true') {
5
+ return undefined;
6
+ }
7
+ const branch = runGit(cwd, ['branch', '--show-current']) || runGit(cwd, ['rev-parse', '--short', 'HEAD']);
8
+ const unstaged = runGit(cwd, ['diff', '--numstat']) ?? '';
9
+ const staged = runGit(cwd, ['diff', '--cached', '--numstat']) ?? '';
10
+ const changes = parseGitNumstat(`${unstaged}\n${staged}`);
11
+ return {
12
+ ...(branch ? { branch } : {}),
13
+ ...changes,
14
+ };
15
+ }
16
+ export function parseGitNumstat(output) {
17
+ return output.split('\n').reduce((totals, line) => {
18
+ const [added, deleted] = line.trim().split(/\s+/, 3);
19
+ const addedCount = Number(added);
20
+ const deletedCount = Number(deleted);
21
+ if (Number.isFinite(addedCount)) {
22
+ totals.added += addedCount;
23
+ }
24
+ if (Number.isFinite(deletedCount)) {
25
+ totals.deleted += deletedCount;
26
+ }
27
+ return totals;
28
+ }, { added: 0, deleted: 0 });
29
+ }
30
+ function runGit(cwd, args) {
31
+ try {
32
+ return execFileSync('git', ['-C', cwd, ...args], {
33
+ encoding: 'utf8',
34
+ stdio: ['ignore', 'pipe', 'ignore'],
35
+ timeout: 500,
36
+ }).trim();
37
+ }
38
+ catch {
39
+ return undefined;
40
+ }
41
+ }
package/dist/help.js ADDED
@@ -0,0 +1,27 @@
1
+ export function formatHelp() {
2
+ return [
3
+ 'Remi - AI coding agent',
4
+ '',
5
+ 'Usage:',
6
+ ' remi Start interactive TUI',
7
+ ' remi repl Start interactive TUI',
8
+ ' remi resume <id> Continue a saved session',
9
+ ' remi --plain Start readline fallback',
10
+ ' remi --tui Force TUI mode',
11
+ ' remi doctor Check local environment',
12
+ ' remi setup Create user config in ~/.remi/config.json',
13
+ ' remi model list Show configured model aliases',
14
+ ' remi model doctor Check model configuration',
15
+ ' remi --help Show help',
16
+ ' remi --version Show version',
17
+ '',
18
+ 'TUI commands:',
19
+ ' /init Create AGENTS.md repository instructions',
20
+ ' /model Show configured model aliases',
21
+ ' /new Start a new session',
22
+ ' /permissions Choose permissions and approval behavior',
23
+ ' /statusline Choose status line fields',
24
+ ' /style Choose response style',
25
+ ' /exit Exit',
26
+ ].join('\n');
27
+ }
package/dist/i18n.js ADDED
@@ -0,0 +1,422 @@
1
+ import { loadRemiConfig, readProjectRemiConfig, writeProjectRemiConfig } from '@remi/config';
2
+ export const defaultLanguage = 'en';
3
+ export const supportedLanguages = [
4
+ { code: 'zh-Hans', label: '简体中文', description: '使用简体中文显示 Remi 系统提示语。' },
5
+ { code: 'en', label: 'English', description: 'Use English for Remi system messages.' },
6
+ ];
7
+ const dictionaries = {
8
+ en: {
9
+ 'slash.init.description': 'Create an AGENTS.md file with repository instructions',
10
+ 'slash.model.description': 'Choose what model and reasoning effort to use',
11
+ 'slash.permissions.description': 'Choose model permissions and approval behavior',
12
+ 'slash.style.description': 'Choose how Remi should answer',
13
+ 'slash.theme.description': 'Choose syntax theme for code and diffs',
14
+ 'slash.statusline.description': 'Choose status line fields',
15
+ 'slash.skills.description': 'Manage and load local Remi skills',
16
+ 'slash.language.description': 'Choose interface language',
17
+ 'slash.compact.description': 'Summarize this session into compact context',
18
+ 'slash.new.description': 'Start a new session',
19
+ 'slash.exit.description': 'Exit Remi',
20
+ 'command.unknown': 'Unknown command: {{command}}',
21
+ 'version.message': 'Remi is running version {{version}}.',
22
+ 'style.panel.title': 'Select Response Style',
23
+ 'style.panel.subtitle': 'Session-only style for model replies. It does not change model selection.',
24
+ 'style.current': 'current',
25
+ 'style.selected': 'Response style selected: {{label}}.',
26
+ 'style.saved': 'Response style saved: {{label}}.',
27
+ 'style.unknown': 'Unknown style: {{style}}. Run /style to choose.',
28
+ 'style.description.remi': 'Pragmatic coding-agent tone with concise, direct answers.',
29
+ 'style.description.brief': 'Very short answers that save tokens.',
30
+ 'style.description.explain': 'More explanation and context for technical decisions.',
31
+ 'style.description.mentor': 'Teaching-oriented answers with reasoning and tradeoffs.',
32
+ 'style.description.minimal': 'Minimal replies such as "OK." and "Done."',
33
+ 'theme.panel.title': 'Select Syntax Theme',
34
+ 'theme.panel.subtitle': 'Move up/down to live preview code themes.',
35
+ 'theme.panel.search': 'Type to filter themes',
36
+ 'theme.panel.help': 'Press enter to confirm or esc to go back',
37
+ 'theme.current': 'current',
38
+ 'theme.selected': 'Syntax theme selected: {{label}}.',
39
+ 'theme.saved': 'Syntax theme saved: {{label}}.',
40
+ 'theme.closed': 'Syntax theme settings closed without saving.',
41
+ 'theme.unknown': 'Unknown syntax theme: {{theme}}. Run /theme to choose.',
42
+ 'language.panel.title': 'Select Language',
43
+ 'language.panel.subtitle': 'Choose the language for Remi system messages.',
44
+ 'language.current': 'current',
45
+ 'language.saved': 'Language saved: {{label}}.',
46
+ 'language.unknown': 'Unknown language: {{language}}. Run /language to choose.',
47
+ 'model.panel.title': 'Select Model and Effort',
48
+ 'model.panel.subtitle': 'Current profile {{profile}} / main uses {{model}} / effort {{effort}}',
49
+ 'model.current': 'current',
50
+ 'model.selected': 'Model selected: {{label}}.',
51
+ 'model.error.load': 'Failed to load model configuration',
52
+ 'permissions.panel.title': 'Update Model Permissions',
53
+ 'permissions.panel.subtitle': 'Choose how Remi handles local files, shell execution, and approval requests.',
54
+ 'permissions.current': 'current',
55
+ 'permissions.saved': 'Permission profile saved: {{label}}.',
56
+ 'permissions.unknown': 'Unknown permission profile: {{profile}}. Run /permissions to choose.',
57
+ 'permissions.profile.default.label': 'Default',
58
+ 'permissions.profile.default.description': 'Remi can read and edit files in the current workspace, and requests approval for shell execution, internet, or other files.',
59
+ 'permissions.profile.auto-review.label': 'Auto-review',
60
+ 'permissions.profile.auto-review.description': 'Same workspace permissions as Default; eligible approval requests are reviewed automatically before asking you.',
61
+ 'permissions.profile.full-access.label': 'Full Access',
62
+ 'permissions.profile.full-access.description': 'Remi can edit files and execute local commands without asking. Use with caution.',
63
+ 'permission.request.title': 'Would you like to run the following command?',
64
+ 'permission.request.title.action': 'Would you like to allow this action?',
65
+ 'permission.request.reason': 'Reason: {{reason}}',
66
+ 'permission.request.action.writeFileIn': 'Write files in {{directory}}',
67
+ 'permission.request.action.editFileIn': 'Edit files in {{directory}}',
68
+ 'permission.request.action.modifyFilesIn': 'Modify files in {{directory}}',
69
+ 'permission.request.action.target': 'Target: {{path}}',
70
+ 'permission.request.path.currentDirectory': 'the current directory',
71
+ 'permission.request.persist': "Yes, and don't ask again for commands that start with `{{prefix}}`",
72
+ 'permission.request.persistRule': "Yes, and don't ask again for {{rule}}",
73
+ 'permission.request.sessionRule': 'Yes, during this session for {{rule}}',
74
+ 'permission.request.once': 'Yes, proceed',
75
+ 'permission.request.deny': 'No, tell Remi what to do differently',
76
+ 'permission.request.help': 'Press enter to confirm or esc to cancel',
77
+ 'permission.rule.prefix': 'commands that start with `{{prefix}}`',
78
+ 'permission.rule.scoped': 'commands that start with `{{prefix}}` in `{{cwd}}`',
79
+ 'permission.rule.multiple': 'commands that start with {{prefixes}}',
80
+ 'permission.rule.multipleScoped': 'commands that start with {{prefixes}} in `{{cwd}}`',
81
+ 'permission.rule.filesystem': 'file changes under `{{root}}`',
82
+ 'permission.rule.filesystemOperations': '{{operations}} files under `{{root}}`',
83
+ 'permission.savedRule': 'Allowed this session for commands that start with `{{prefix}}`.',
84
+ 'permission.savedRules': 'Allowed this session for {{rule}}.',
85
+ 'permission.savedPersistentRules': 'Always allowed {{rule}}.',
86
+ 'statusline.panel.title': 'Configure Status Line',
87
+ 'statusline.panel.subtitle': 'Select which items to display in the status line.',
88
+ 'statusline.panel.search': 'Type to search',
89
+ 'statusline.panel.help': 'Press space to toggle; ↑/↓ to move; enter to save and close; esc to close',
90
+ 'statusline.saved': 'Status line saved: {{items}}.',
91
+ 'statusline.closed': 'Status line settings closed without saving.',
92
+ 'statusline.context': 'Context',
93
+ 'statusline.used': 'used',
94
+ 'statusline.input': 'input',
95
+ 'statusline.output': 'output',
96
+ 'statusline.catalog.model': 'Current main model',
97
+ 'statusline.catalog.cwd': 'Current working directory',
98
+ 'statusline.catalog.git-branch': 'Current Git branch (hidden when unavailable)',
99
+ 'statusline.catalog.branch-changes': 'Working tree line changes, such as +8 -1',
100
+ 'statusline.catalog.context-remaining': 'Percentage of context window remaining',
101
+ 'statusline.catalog.used-tokens': 'Total tokens used in the current session',
102
+ 'statusline.catalog.total-input-tokens': 'Total input tokens used in the current session',
103
+ 'statusline.catalog.total-output-tokens': 'Total output tokens used in the current session',
104
+ 'statusline.catalog.run-state': 'Compact session run-state text',
105
+ 'statusline.catalog.permissions': 'Current permission profile',
106
+ 'skills.panel.title': 'Skills',
107
+ 'skills.panel.subtitle': 'Choose an action',
108
+ 'skills.panel.help': 'Press enter to confirm or esc to go back',
109
+ 'skills.panel.list': 'List skills',
110
+ 'skills.panel.listDescription': 'Tip: press $ to open this list directly.',
111
+ 'skills.panel.toggle': 'Enable/Disable Skills',
112
+ 'skills.panel.toggleDescription': 'Enable or disable skills.',
113
+ 'skills.panel.listSubtitle': '{{count}} skills found',
114
+ 'skills.panel.toggleSubtitle': 'Space toggles skills; enter saves',
115
+ 'skills.none': 'No skills found. Create skills in skills/<name>/SKILL.md or ~/.remi/skills/<name>/SKILL.md.',
116
+ 'skills.loaded': 'Loaded skill: {{name}}.',
117
+ 'skills.unknown': 'Unknown skill: {{name}}. Run /skills list to see available skills.',
118
+ 'skills.saved': 'Skills updated: {{enabled}} enabled, {{disabled}} disabled.',
119
+ 'skills.closed': 'Skills panel closed.',
120
+ 'skill.using': 'Using Skill',
121
+ 'tool.action.list': 'List',
122
+ 'tool.action.read': 'Read',
123
+ 'tool.action.search': 'Search',
124
+ 'tool.action.find': 'Find',
125
+ 'tool.action.create': 'Create',
126
+ 'tool.action.exists': 'Exists',
127
+ 'tool.action.write': 'Write',
128
+ 'tool.action.edit': 'Edit',
129
+ 'tool.action.delete': 'Delete',
130
+ 'tool.action.run': 'Run',
131
+ 'tool.action.planUpdate': 'Plan update',
132
+ 'tool.action.done': 'Done',
133
+ 'tool.action.completed': 'Completed',
134
+ 'tool.running.listing': 'Listing',
135
+ 'tool.running.reading': 'Reading',
136
+ 'tool.running.searching': 'Searching',
137
+ 'tool.running.finding': 'Finding',
138
+ 'tool.running.creating': 'Creating',
139
+ 'tool.running.writing': 'Writing',
140
+ 'tool.running.editing': 'Editing',
141
+ 'tool.running.deleting': 'Deleting',
142
+ 'tool.running.running': 'Running',
143
+ 'tool.running.updatingPlan': 'Updating plan',
144
+ 'tool.count.directory.one': 'directory',
145
+ 'tool.count.directory.other': 'directories',
146
+ 'tool.count.file.one': 'file',
147
+ 'tool.count.file.other': 'files',
148
+ 'tool.count.query.one': 'query',
149
+ 'tool.count.query.other': 'queries',
150
+ 'tool.count.pattern.one': 'pattern',
151
+ 'tool.count.pattern.other': 'patterns',
152
+ 'tool.count.command.one': 'command',
153
+ 'tool.count.command.other': 'commands',
154
+ 'tool.count.tool.one': 'tool',
155
+ 'tool.count.tool.other': 'tools',
156
+ 'tool.result.paths': 'paths',
157
+ 'tool.result.matches': 'matches',
158
+ 'tool.state.running': 'Running',
159
+ 'tool.state.done': 'Done',
160
+ 'tool.state.failed': 'Failed',
161
+ 'tool.state.denied': 'Denied',
162
+ 'tool.state.pending': 'Pending',
163
+ 'run.ready': 'Ready',
164
+ 'run.working': 'Working',
165
+ 'run.thinking': 'Thinking',
166
+ 'prompt.placeholder': 'Write a task, or press / for commands',
167
+ 'prompt.queue.placeholder': 'Type guidance to queue',
168
+ 'prompt.working': 'Working...',
169
+ 'queue.title': 'Messages to be submitted after next tool call',
170
+ 'queue.hint': 'press esc to interrupt and send immediately',
171
+ 'messages.empty': 'Ready. Type a task, or press / for commands.',
172
+ 'working.label': 'Working',
173
+ 'working.interrupt': 'esc to interrupt',
174
+ 'thinking.label': 'Thinking...',
175
+ 'session.new': 'New session',
176
+ 'compact.noActive': 'No active session to compact.',
177
+ 'compact.progress': 'Compacting',
178
+ 'compact.done': 'Compacted {{count}} events (~{{tokens}} tokens).',
179
+ 'compact.summary': 'Compacted {{count}} events (~{{tokens}} tokens).\n{{summary}}',
180
+ 'header.welcomeBack': 'Welcome back,',
181
+ 'header.model': 'Model',
182
+ 'header.profile': 'profile',
183
+ 'header.announcements': 'Announcements',
184
+ 'header.useModel': 'Use /model to switch models for this session.',
185
+ 'header.resume': 'Resume later with the command printed on exit.',
186
+ 'header.didYouKnow': 'Did you know?',
187
+ 'header.newTip': '/new starts a clean session in this project.',
188
+ 'header.statuslineTip': '/statusline lets you choose bottom bar fields.',
189
+ 'usage.prefix': 'Token usage:',
190
+ 'usage.cached': 'cached',
191
+ 'usage.reasoning': 'reasoning',
192
+ 'task.worked': 'Worked for {{duration}}',
193
+ 'interrupt.message': 'Conversation interrupted - tell Remi what to do differently.',
194
+ },
195
+ 'zh-Hans': {
196
+ 'slash.init.description': '创建包含仓库协作规则的 AGENTS.md 文件',
197
+ 'slash.model.description': '选择本次会话使用的模型和推理强度',
198
+ 'slash.permissions.description': '选择模型权限和审批行为',
199
+ 'slash.style.description': '选择 Remi 的回复风格',
200
+ 'slash.theme.description': '选择代码和 diff 的语法主题',
201
+ 'slash.statusline.description': '选择底部状态栏字段',
202
+ 'slash.skills.description': '管理和加载本地 Remi skills',
203
+ 'slash.language.description': '选择界面语言',
204
+ 'slash.compact.description': '把当前会话压缩为上下文摘要',
205
+ 'slash.new.description': '开启一个新会话',
206
+ 'slash.exit.description': '退出 Remi',
207
+ 'command.unknown': '未知命令:{{command}}',
208
+ 'version.message': 'Remi 当前版本是 {{version}}。',
209
+ 'style.panel.title': '选择回复风格',
210
+ 'style.panel.subtitle': '只影响模型回复风格,不改变模型选择。',
211
+ 'style.current': '当前',
212
+ 'style.selected': '已选择回复风格:{{label}}。',
213
+ 'style.saved': '已保存回复风格:{{label}}。',
214
+ 'style.unknown': '未知回复风格:{{style}}。运行 /style 选择。',
215
+ 'style.description.remi': '务实的 Coding Agent 语气,直接、简洁。',
216
+ 'style.description.brief': '非常短的回复,用更少 token。',
217
+ 'style.description.explain': '提供更多技术背景和决策解释。',
218
+ 'style.description.mentor': '偏教学,说明推理过程和取舍。',
219
+ 'style.description.minimal': '极简回复,例如“好。”、“搞定。”。',
220
+ 'theme.panel.title': '选择语法主题',
221
+ 'theme.panel.subtitle': '上下移动以实时预览代码主题。',
222
+ 'theme.panel.search': '输入以筛选主题',
223
+ 'theme.panel.help': '按回车确认,按 esc 返回',
224
+ 'theme.current': '当前',
225
+ 'theme.selected': '已选择语法主题:{{label}}。',
226
+ 'theme.saved': '已保存语法主题:{{label}}。',
227
+ 'theme.closed': '已关闭语法主题设置,未保存更改。',
228
+ 'theme.unknown': '未知语法主题:{{theme}}。运行 /theme 选择。',
229
+ 'language.panel.title': '选择语言',
230
+ 'language.panel.subtitle': '选择 Remi 系统提示语使用的语言。',
231
+ 'language.current': '当前',
232
+ 'language.saved': '已保存语言:{{label}}。',
233
+ 'language.unknown': '未知语言:{{language}}。运行 /language 选择。',
234
+ 'model.panel.title': '选择模型和推理强度',
235
+ 'model.panel.subtitle': '当前 profile {{profile}} / main 使用 {{model}} / effort {{effort}}',
236
+ 'model.current': '当前',
237
+ 'model.selected': '已选择模型:{{label}}。',
238
+ 'model.error.load': '加载模型配置失败',
239
+ 'permissions.panel.title': '更新模型权限',
240
+ 'permissions.panel.subtitle': '选择 Remi 如何处理本地文件、shell 执行和审批请求。',
241
+ 'permissions.current': '当前',
242
+ 'permissions.saved': '已保存权限档位:{{label}}。',
243
+ 'permissions.unknown': '未知权限档位:{{profile}}。运行 /permissions 选择。',
244
+ 'permissions.profile.default.label': 'Default',
245
+ 'permissions.profile.default.description': 'Remi 可以读写当前工作区文件;执行 shell、联网或访问其他文件时需要审批。',
246
+ 'permissions.profile.auto-review.label': 'Auto-review',
247
+ 'permissions.profile.auto-review.description': '工作区权限与 Default 相同;符合条件的审批请求先由自动审查判断。',
248
+ 'permissions.profile.full-access.label': 'Full Access',
249
+ 'permissions.profile.full-access.description': 'Remi 可以不再询问地编辑文件和执行本地命令,请谨慎使用。',
250
+ 'permission.request.title': '是否允许执行以下命令?',
251
+ 'permission.request.title.action': '是否允许这个操作?',
252
+ 'permission.request.reason': '原因:{{reason}}',
253
+ 'permission.request.action.writeFileIn': '在 {{directory}} 中写入文件',
254
+ 'permission.request.action.editFileIn': '在 {{directory}} 中修改文件',
255
+ 'permission.request.action.modifyFilesIn': '在 {{directory}} 中修改文件',
256
+ 'permission.request.action.target': '目标:{{path}}',
257
+ 'permission.request.path.currentDirectory': '当前目录',
258
+ 'permission.request.persist': '是,并且不再询问以 `{{prefix}}` 开头的命令',
259
+ 'permission.request.persistRule': '是,并且不再询问{{rule}}',
260
+ 'permission.request.sessionRule': '是,本 session 允许{{rule}}',
261
+ 'permission.request.once': '是,继续',
262
+ 'permission.request.deny': '否,告诉 Remi 换个方式',
263
+ 'permission.request.help': '按回车确认,按 esc 取消',
264
+ 'permission.rule.prefix': '以 `{{prefix}}` 开头的命令',
265
+ 'permission.rule.scoped': '`{{cwd}}` 中以 `{{prefix}}` 开头的命令',
266
+ 'permission.rule.multiple': '以 {{prefixes}} 开头的命令',
267
+ 'permission.rule.multipleScoped': '`{{cwd}}` 中以 {{prefixes}} 开头的命令',
268
+ 'permission.rule.filesystem': '`{{root}}` 目录中的文件变更',
269
+ 'permission.rule.filesystemOperations': '`{{root}}` 目录中的{{operations}}文件',
270
+ 'permission.savedRule': '本 session 已允许以 `{{prefix}}` 开头的命令。',
271
+ 'permission.savedRules': '本 session 已允许{{rule}}。',
272
+ 'permission.savedPersistentRules': '已始终允许{{rule}}。',
273
+ 'statusline.panel.title': '配置状态栏',
274
+ 'statusline.panel.subtitle': '选择底部状态栏展示哪些字段。',
275
+ 'statusline.panel.search': '输入以搜索',
276
+ 'statusline.panel.help': '空格切换;↑/↓ 移动;回车保存并关闭;esc 关闭',
277
+ 'statusline.saved': '已保存状态栏字段:{{items}}。',
278
+ 'statusline.closed': '已关闭状态栏设置,未保存更改。',
279
+ 'statusline.context': 'Context',
280
+ 'statusline.used': 'used',
281
+ 'statusline.input': 'input',
282
+ 'statusline.output': 'output',
283
+ 'statusline.catalog.model': '当前 main 模型',
284
+ 'statusline.catalog.cwd': '当前工作目录',
285
+ 'statusline.catalog.git-branch': '当前 Git 分支(不可用时隐藏)',
286
+ 'statusline.catalog.branch-changes': '工作区行级改动,例如 +8 -1',
287
+ 'statusline.catalog.context-remaining': '上下文窗口剩余百分比',
288
+ 'statusline.catalog.used-tokens': '当前会话已使用 token 总数',
289
+ 'statusline.catalog.total-input-tokens': '当前会话 input token 总数',
290
+ 'statusline.catalog.total-output-tokens': '当前会话 output token 总数',
291
+ 'statusline.catalog.run-state': '紧凑的会话运行状态文本',
292
+ 'statusline.catalog.permissions': '当前权限档位',
293
+ 'skills.panel.title': 'Skills',
294
+ 'skills.panel.subtitle': '选择操作',
295
+ 'skills.panel.help': '按回车确认,按 esc 返回',
296
+ 'skills.panel.list': '列出 skills',
297
+ 'skills.panel.listDescription': '提示:按 $ 可直接打开这个列表。',
298
+ 'skills.panel.toggle': '启用/禁用 Skills',
299
+ 'skills.panel.toggleDescription': '启用或禁用 skills。',
300
+ 'skills.panel.listSubtitle': '发现 {{count}} 个 skills',
301
+ 'skills.panel.toggleSubtitle': '空格切换;回车保存',
302
+ 'skills.none': '未发现 skills。可创建 skills/<name>/SKILL.md 或 ~/.remi/skills/<name>/SKILL.md。',
303
+ 'skills.loaded': '已加载 skill:{{name}}。',
304
+ 'skills.unknown': '未知 skill:{{name}}。运行 /skills list 查看可用 skills。',
305
+ 'skills.saved': 'Skills 已更新:{{enabled}} 个启用,{{disabled}} 个禁用。',
306
+ 'skills.closed': '已关闭 Skills 面板。',
307
+ 'skill.using': '使用 Skill',
308
+ 'tool.action.list': '列出',
309
+ 'tool.action.read': '读取',
310
+ 'tool.action.search': '搜索',
311
+ 'tool.action.find': '查找',
312
+ 'tool.action.create': '创建',
313
+ 'tool.action.exists': '已存在',
314
+ 'tool.action.write': '写入',
315
+ 'tool.action.edit': '修改',
316
+ 'tool.action.delete': '删除',
317
+ 'tool.action.run': '执行',
318
+ 'tool.action.planUpdate': '更新计划',
319
+ 'tool.action.done': '完成',
320
+ 'tool.action.completed': '完成',
321
+ 'tool.running.listing': '正在列出',
322
+ 'tool.running.reading': '正在读取',
323
+ 'tool.running.searching': '正在搜索',
324
+ 'tool.running.finding': '正在查找',
325
+ 'tool.running.creating': '正在创建',
326
+ 'tool.running.writing': '正在写入',
327
+ 'tool.running.editing': '正在修改',
328
+ 'tool.running.deleting': '正在删除',
329
+ 'tool.running.running': '正在执行',
330
+ 'tool.running.updatingPlan': '正在更新计划',
331
+ 'tool.count.directory.one': '个目录',
332
+ 'tool.count.directory.other': '个目录',
333
+ 'tool.count.file.one': '个文件',
334
+ 'tool.count.file.other': '个文件',
335
+ 'tool.count.query.one': '个查询',
336
+ 'tool.count.query.other': '个查询',
337
+ 'tool.count.pattern.one': '个模式',
338
+ 'tool.count.pattern.other': '个模式',
339
+ 'tool.count.command.one': '条命令',
340
+ 'tool.count.command.other': '条命令',
341
+ 'tool.count.tool.one': '个工具',
342
+ 'tool.count.tool.other': '个工具',
343
+ 'tool.result.paths': '个路径',
344
+ 'tool.result.matches': '处匹配',
345
+ 'tool.state.running': '运行中',
346
+ 'tool.state.done': '完成',
347
+ 'tool.state.failed': '失败',
348
+ 'tool.state.denied': '已拒绝',
349
+ 'tool.state.pending': '等待中',
350
+ 'run.ready': '就绪',
351
+ 'run.working': '处理中',
352
+ 'run.thinking': '思考中',
353
+ 'prompt.placeholder': '输入任务,或按 / 查看命令',
354
+ 'prompt.queue.placeholder': '输入引导,回车排队',
355
+ 'prompt.working': '处理中...',
356
+ 'queue.title': '排队消息将在下一次工具调用后提交',
357
+ 'queue.hint': '按 esc 可中断当前请求并立即发送',
358
+ 'messages.empty': '就绪。输入任务,或按 / 查看命令。',
359
+ 'working.label': '处理中',
360
+ 'working.interrupt': 'esc 中断',
361
+ 'thinking.label': '思考中...',
362
+ 'session.new': '新会话',
363
+ 'compact.noActive': '没有可压缩的活动会话。',
364
+ 'compact.progress': '正在压缩',
365
+ 'compact.done': '已压缩 {{count}} 条事件(约 {{tokens}} tokens)。',
366
+ 'compact.summary': '已压缩 {{count}} 条事件(约 {{tokens}} tokens)。\n{{summary}}',
367
+ 'header.welcomeBack': '欢迎回来,',
368
+ 'header.model': '模型',
369
+ 'header.profile': 'profile',
370
+ 'header.announcements': '公告',
371
+ 'header.useModel': '使用 /model 切换本次会话模型。',
372
+ 'header.resume': '退出时会打印可恢复会话的命令。',
373
+ 'header.didYouKnow': '提示',
374
+ 'header.newTip': '/new 会在当前项目开启干净新会话。',
375
+ 'header.statuslineTip': '/statusline 可以选择底部状态栏字段。',
376
+ 'usage.prefix': 'Token 用量:',
377
+ 'usage.cached': 'cached',
378
+ 'usage.reasoning': 'reasoning',
379
+ 'task.worked': '耗时 {{duration}}',
380
+ 'interrupt.message': '对话已中断。请告诉 Remi 接下来怎么做。',
381
+ },
382
+ };
383
+ export function isLanguageCode(value) {
384
+ return typeof value === 'string' && supportedLanguages.some(language => language.code === value);
385
+ }
386
+ export function parseLanguageArg(value) {
387
+ if (!value) {
388
+ return undefined;
389
+ }
390
+ const normalized = value.trim().toLowerCase();
391
+ if (['zh', 'zh-cn', 'zh-hans', 'cn', '中文', '简体中文', 'simplified-chinese', 'simplified'].includes(normalized)) {
392
+ return 'zh-Hans';
393
+ }
394
+ if (['en', 'english'].includes(normalized)) {
395
+ return 'en';
396
+ }
397
+ return supportedLanguages.find(language => language.label.toLowerCase() === normalized)?.code;
398
+ }
399
+ export function resolveConfiguredLanguage(cwd) {
400
+ try {
401
+ const language = loadRemiConfig({ cwd }).config.language;
402
+ return isLanguageCode(language) ? language : defaultLanguage;
403
+ }
404
+ catch {
405
+ return defaultLanguage;
406
+ }
407
+ }
408
+ export function saveProjectLanguage(cwd, language) {
409
+ const config = readProjectRemiConfig(cwd);
410
+ writeProjectRemiConfig({ ...config, language }, cwd);
411
+ }
412
+ export function languageLabel(language) {
413
+ return supportedLanguages.find(candidate => candidate.code === language)?.label ?? language;
414
+ }
415
+ export function t(language, key, variables = {}) {
416
+ const dictionary = dictionaries[language ?? defaultLanguage] ?? dictionaries[defaultLanguage];
417
+ const template = dictionary[key] ?? dictionaries[defaultLanguage][key];
418
+ return template.replace(/\{\{(\w+)\}\}/g, (match, name) => {
419
+ const value = variables[name];
420
+ return value === undefined ? match : String(value);
421
+ });
422
+ }