@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.
- package/README.md +9 -0
- package/dist/doctor.js +108 -0
- package/dist/git.js +41 -0
- package/dist/help.js +27 -0
- package/dist/i18n.js +422 -0
- package/dist/index.js +97 -0
- package/dist/initPrompt.js +17 -0
- package/dist/model.js +116 -0
- package/dist/modelSelection.js +34 -0
- package/dist/permissionDisplay.js +46 -0
- package/dist/permissions.js +206 -0
- package/dist/repl.js +346 -0
- package/dist/resume.js +3 -0
- package/dist/setup.js +62 -0
- package/dist/statusline.js +59 -0
- package/dist/style.js +48 -0
- package/dist/syntaxTheme.js +39 -0
- package/dist/tui/RemiApp.js +1756 -0
- package/dist/tui/commands.js +427 -0
- package/dist/tui/index.js +42 -0
- package/dist/tui/renderers/Header.js +28 -0
- package/dist/tui/renderers/MessageList.js +1176 -0
- package/dist/tui/renderers/PromptBox.js +118 -0
- package/dist/tui/renderers/StatusLine.js +124 -0
- package/dist/tui/renderers/WorkingIndicator.js +70 -0
- package/dist/tui/slashCommandHighlight.js +8 -0
- package/dist/tui/theme.js +13 -0
- package/dist/tui/types.js +1 -0
- package/dist/usage.js +66 -0
- package/dist/version.js +5 -0
- package/node_modules/@remi/compact/dist/index.js +389 -0
- package/node_modules/@remi/compact/package.json +8 -0
- package/node_modules/@remi/config/dist/index.js +426 -0
- package/node_modules/@remi/config/package.json +8 -0
- package/node_modules/@remi/core/dist/contextBuilder.js +344 -0
- package/node_modules/@remi/core/dist/directoryOverview.js +359 -0
- package/node_modules/@remi/core/dist/index.js +2843 -0
- package/node_modules/@remi/core/dist/projectInstructions.js +123 -0
- package/node_modules/@remi/core/dist/responseStyles.js +98 -0
- package/node_modules/@remi/core/package.json +8 -0
- package/node_modules/@remi/llm/dist/index.js +804 -0
- package/node_modules/@remi/llm/package.json +8 -0
- package/node_modules/@remi/memory/dist/index.js +312 -0
- package/node_modules/@remi/memory/package.json +8 -0
- package/node_modules/@remi/permissions/dist/index.js +90 -0
- package/node_modules/@remi/permissions/package.json +8 -0
- package/node_modules/@remi/sessions/dist/index.js +370 -0
- package/node_modules/@remi/sessions/package.json +8 -0
- package/node_modules/@remi/skills/dist/index.js +273 -0
- package/node_modules/@remi/skills/package.json +8 -0
- package/node_modules/@remi/terminal-markdown/dist/index.js +1412 -0
- package/node_modules/@remi/terminal-markdown/package.json +8 -0
- package/node_modules/@remi/tools/dist/index.js +3875 -0
- package/node_modules/@remi/tools/package.json +8 -0
- package/package.json +48 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { existsSync, lstatSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, parse, relative, resolve } from 'node:path';
|
|
3
|
+
const projectInstructionsFileNames = ['AGENTS.md', 'AGENT.md', 'CLAUDE.md', 'REMI.md'];
|
|
4
|
+
const maxInstructionFiles = 8;
|
|
5
|
+
const maxInstructionFileBytes = 64 * 1024;
|
|
6
|
+
const maxInstructionFileChars = 32_000;
|
|
7
|
+
const maxInstructionTotalChars = 64_000;
|
|
8
|
+
export function buildProjectInstructionsContext(cwd) {
|
|
9
|
+
const candidates = discoverProjectInstructionFiles(cwd);
|
|
10
|
+
const loadedFiles = [];
|
|
11
|
+
const skippedFiles = [];
|
|
12
|
+
const sections = [];
|
|
13
|
+
let remainingChars = maxInstructionTotalChars;
|
|
14
|
+
for (const candidate of candidates) {
|
|
15
|
+
if (loadedFiles.length >= maxInstructionFiles || remainingChars <= 0) {
|
|
16
|
+
skippedFiles.push(`${candidate} (project instruction budget exhausted)`);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const linkStat = lstatSync(candidate);
|
|
21
|
+
if (linkStat.isSymbolicLink()) {
|
|
22
|
+
skippedFiles.push(`${candidate} (symbolic links are not auto-loaded)`);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const stat = statSync(candidate);
|
|
26
|
+
if (!stat.isFile()) {
|
|
27
|
+
skippedFiles.push(`${candidate} (not a regular file)`);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (stat.size > maxInstructionFileBytes) {
|
|
31
|
+
skippedFiles.push(`${candidate} (${stat.size} bytes exceeds ${maxInstructionFileBytes} byte limit)`);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const raw = readFileSync(candidate, 'utf8');
|
|
35
|
+
const text = clampText(raw.trim(), Math.min(maxInstructionFileChars, remainingChars));
|
|
36
|
+
if (text.length === 0) {
|
|
37
|
+
skippedFiles.push(`${candidate} (empty)`);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
loadedFiles.push(candidate);
|
|
41
|
+
remainingChars -= text.length;
|
|
42
|
+
sections.push([`### ${displayInstructionPath(cwd, candidate)}`, '', text].join('\n'));
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
skippedFiles.push(`${candidate} (${safeInstructionError(error)})`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (sections.length === 0) {
|
|
49
|
+
return {
|
|
50
|
+
source: skippedFiles.length > 0 ? `project instructions unavailable (${skippedFiles.length} skipped)` : 'project instructions not found',
|
|
51
|
+
loadedFiles,
|
|
52
|
+
skippedFiles,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const skippedLine = skippedFiles.length > 0 ? [`Skipped: ${skippedFiles.length} file(s).`, ''] : [];
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
'## Project Instructions',
|
|
59
|
+
'',
|
|
60
|
+
`These files are loaded automatically from the current working directory and its parents: ${projectInstructionsFileNames.join(', ')}. Follow them when they apply to the current task.`,
|
|
61
|
+
'',
|
|
62
|
+
...skippedLine,
|
|
63
|
+
...sections,
|
|
64
|
+
].join('\n'),
|
|
65
|
+
source: `project instructions (${loadedFiles.length} loaded${skippedFiles.length > 0 ? `, ${skippedFiles.length} skipped` : ''})`,
|
|
66
|
+
loadedFiles,
|
|
67
|
+
skippedFiles,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function discoverProjectInstructionFiles(cwd) {
|
|
71
|
+
const dirs = [];
|
|
72
|
+
let current = resolve(cwd);
|
|
73
|
+
const root = parse(current).root;
|
|
74
|
+
while (true) {
|
|
75
|
+
dirs.push(current);
|
|
76
|
+
if (current === root) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
const parent = dirname(current);
|
|
80
|
+
if (parent === current) {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
current = parent;
|
|
84
|
+
}
|
|
85
|
+
const seen = new Set();
|
|
86
|
+
const files = [];
|
|
87
|
+
for (const dir of dirs.reverse()) {
|
|
88
|
+
for (const fileName of projectInstructionsFileNames) {
|
|
89
|
+
const candidate = join(dir, fileName);
|
|
90
|
+
if (!existsSync(candidate)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const key = canonicalInstructionPath(candidate);
|
|
94
|
+
if (seen.has(key)) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
seen.add(key);
|
|
98
|
+
files.push(candidate);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return files;
|
|
102
|
+
}
|
|
103
|
+
function canonicalInstructionPath(path) {
|
|
104
|
+
try {
|
|
105
|
+
return realpathSync(path);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return resolve(path);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function displayInstructionPath(cwd, path) {
|
|
112
|
+
const relativePath = relative(cwd, path);
|
|
113
|
+
return relativePath.length === 0 || relativePath.startsWith('..') ? path : relativePath;
|
|
114
|
+
}
|
|
115
|
+
function clampText(text, maxChars) {
|
|
116
|
+
if (text.length <= maxChars) {
|
|
117
|
+
return text;
|
|
118
|
+
}
|
|
119
|
+
return `${text.slice(0, Math.max(0, maxChars - 52)).trimEnd()}\n[Project instruction content truncated by Remi.]`;
|
|
120
|
+
}
|
|
121
|
+
function safeInstructionError(error) {
|
|
122
|
+
return error instanceof Error ? error.message : String(error);
|
|
123
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
export const defaultResponseStyleId = 'remi';
|
|
3
|
+
export const responseStylePresets = [
|
|
4
|
+
{
|
|
5
|
+
id: 'remi',
|
|
6
|
+
label: 'Remi',
|
|
7
|
+
description: 'Default coding-agent style: direct, practical, and low-noise.',
|
|
8
|
+
instructions: [
|
|
9
|
+
'Do not introduce yourself unless the user asks who you are.',
|
|
10
|
+
'Do not use emoji, cheerleading, or generic small talk.',
|
|
11
|
+
],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: 'brief',
|
|
15
|
+
label: 'Brief',
|
|
16
|
+
description: 'Shortest useful answer; minimal explanation.',
|
|
17
|
+
instructions: [
|
|
18
|
+
'Use the shortest answer that still handles the request.',
|
|
19
|
+
'Prefer one or two concise paragraphs.',
|
|
20
|
+
'Avoid background explanation unless it changes the action.',
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'explain',
|
|
25
|
+
label: 'Explain',
|
|
26
|
+
description: 'Adds reasoning, assumptions, and tradeoffs when useful.',
|
|
27
|
+
instructions: [
|
|
28
|
+
'Explain key reasoning and assumptions clearly.',
|
|
29
|
+
'Mention tradeoffs when there are meaningful implementation choices.',
|
|
30
|
+
'Keep the explanation structured and avoid filler.',
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'mentor',
|
|
35
|
+
label: 'Mentor',
|
|
36
|
+
description: 'Learning-oriented style with steps and why they matter.',
|
|
37
|
+
instructions: [
|
|
38
|
+
'Teach the approach step by step.',
|
|
39
|
+
'Explain why each important step exists.',
|
|
40
|
+
'Keep examples concrete and avoid long lectures.',
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'minimal',
|
|
45
|
+
label: 'Minimal',
|
|
46
|
+
description: 'Ultra-short replies for saving tokens.',
|
|
47
|
+
instructions: [
|
|
48
|
+
'Use the fewest words possible while still answering the request.',
|
|
49
|
+
'For completed work, prefer a terse confirmation in the latest user message language when no detail is needed; for Chinese, use "搞定。".',
|
|
50
|
+
'Do not include background, recap, alternatives, or next steps unless the user asks or a blocker must be surfaced.',
|
|
51
|
+
'For failures, risks, or unverified work, still say so in one short sentence.',
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
export function isResponseStyleId(value) {
|
|
56
|
+
return responseStylePresets.some(style => style.id === value);
|
|
57
|
+
}
|
|
58
|
+
export function resolveResponseStyle(styleId) {
|
|
59
|
+
return responseStylePresets.find(style => style.id === styleId) ?? responseStylePresets[0];
|
|
60
|
+
}
|
|
61
|
+
let cachedBaseSystemPromptTemplate;
|
|
62
|
+
export function buildBaseSystemPrompt(styleId, language) {
|
|
63
|
+
return `${readBaseSystemPromptTemplate()}\n\n## Response Language\n\n${buildResponseLanguageSystemPrompt(language)}\n\n## Response Style\n\n${buildResponseStyleSystemPrompt(styleId)}`;
|
|
64
|
+
}
|
|
65
|
+
export function buildResponseLanguageSystemPrompt(language) {
|
|
66
|
+
if (language === 'zh-Hans') {
|
|
67
|
+
return [
|
|
68
|
+
'- Use Simplified Chinese for all user-facing assistant prose, visible plan items, brief transition text, blockers, and final answers.',
|
|
69
|
+
'- Do not use English filler or mixed-language lead-ins such as "Let me", "First", "I will", or "I need to" in Chinese responses; write those clauses in Chinese instead.',
|
|
70
|
+
'- Keep code identifiers, file paths, commands, API names, compiler output, and required literals unchanged.',
|
|
71
|
+
'- Do not switch to English merely because runtime instructions, tool names, or file comments are in English.',
|
|
72
|
+
].join('\n');
|
|
73
|
+
}
|
|
74
|
+
if (language === 'en') {
|
|
75
|
+
return [
|
|
76
|
+
'- Use English for all user-facing assistant prose, visible plan items, brief transition text, blockers, and final answers.',
|
|
77
|
+
'- Keep code identifiers, file paths, commands, API names, compiler output, and required literals unchanged.',
|
|
78
|
+
].join('\n');
|
|
79
|
+
}
|
|
80
|
+
return [
|
|
81
|
+
'- Match the latest user message language for all user-facing assistant prose, visible plan items, brief transition text, blockers, and final answers.',
|
|
82
|
+
'- If the latest user message is Chinese, use Simplified Chinese.',
|
|
83
|
+
'- For Chinese responses, do not use English filler or mixed-language lead-ins such as "Let me", "First", "I will", or "I need to"; write those clauses in Chinese instead.',
|
|
84
|
+
'- Keep code identifiers, file paths, commands, API names, compiler output, and required literals unchanged.',
|
|
85
|
+
].join('\n');
|
|
86
|
+
}
|
|
87
|
+
export function buildResponseStyleSystemPrompt(styleId) {
|
|
88
|
+
const style = resolveResponseStyle(styleId);
|
|
89
|
+
const defaultStyle = resolveResponseStyle(defaultResponseStyleId);
|
|
90
|
+
const instructions = style.id === defaultStyle.id
|
|
91
|
+
? defaultStyle.instructions
|
|
92
|
+
: [...defaultStyle.instructions, ...style.instructions];
|
|
93
|
+
return instructions.map(instruction => `- ${instruction}`).join('\n');
|
|
94
|
+
}
|
|
95
|
+
function readBaseSystemPromptTemplate() {
|
|
96
|
+
cachedBaseSystemPromptTemplate ??= readFileSync(new URL('../../../prompt/base-system.md', import.meta.url), 'utf8').trim();
|
|
97
|
+
return cachedBaseSystemPromptTemplate;
|
|
98
|
+
}
|