@vsem/ai 0.1.2 → 0.1.3
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 +14 -6
- package/bin/cli.js +70 -17
- package/package.json +1 -1
- package/templates/system/CLAUDE.md.tmpl +3 -0
package/README.md
CHANGED
|
@@ -14,18 +14,26 @@ npx @vsem/ai@latest
|
|
|
14
14
|
Runs against the current directory by default. Optional arguments:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
npx @vsem/ai@latest [target-dir] [--branch <name>] [--force] [--yes]
|
|
17
|
+
npx @vsem/ai@latest [target-dir] [--branch <name>] [--lang <language>] [--force] [--yes]
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
- `target-dir` — install into this directory instead of the current one (created if missing).
|
|
21
21
|
- `--branch <name>` — personal branch name (default: your `git config --global user.name`, or `dev`).
|
|
22
|
+
- `--lang <language>` — language the assistant should use to talk to you and to reason, written into `CLAUDE.md` (default: Russian).
|
|
22
23
|
- `--force` — also overwrite project-owned files (`.ai/project.md`, `.ai/plan.md`, task/decision/incident indexes), resetting them to placeholders. Without this flag, reruns only update framework-owned files (`.ai/rules/`, `.ai/roles/`, `CLAUDE.md`) and never touch your own project state.
|
|
23
|
-
- `--yes` / `-y` — skip the interactive confirmation prompt.
|
|
24
|
+
- `--yes` / `-y` — skip the interactive wizard and confirmation prompt, accepting all defaults.
|
|
24
25
|
|
|
25
|
-
In an interactive terminal, the CLI shows a banner and
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
In an interactive terminal, the CLI shows a banner and asks for whatever
|
|
27
|
+
wasn't already given on the command line (target folder, language, branch),
|
|
28
|
+
then shows an install confirmation "button" before touching disk — press
|
|
29
|
+
Enter to proceed or `n` to cancel. Any value passed as a flag skips its
|
|
30
|
+
question. In non-interactive contexts (CI, pipes) it detects the lack of a
|
|
31
|
+
TTY and proceeds automatically with defaults, same as passing `--yes`.
|
|
32
|
+
|
|
33
|
+
The generated workspace's local git repo also gets `core.autocrlf` disabled,
|
|
34
|
+
so `git add`/`commit` don't print `LF will be replaced by CRLF` warnings for
|
|
35
|
+
this project's text files on Windows. This only affects that one repo, not
|
|
36
|
+
your global git config.
|
|
29
37
|
|
|
30
38
|
## Updating an existing workspace
|
|
31
39
|
|
package/bin/cli.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// user's own project state. Pass --force to overwrite scaffold files too.
|
|
18
18
|
//
|
|
19
19
|
// Usage:
|
|
20
|
-
// npx @vsem/ai@latest [target-dir] [--branch <name>] [--force] [--yes]
|
|
20
|
+
// npx @vsem/ai@latest [target-dir] [--branch <name>] [--lang <language>] [--force] [--yes]
|
|
21
21
|
|
|
22
22
|
const fs = require('fs');
|
|
23
23
|
const path = require('path');
|
|
@@ -26,6 +26,7 @@ const { execFileSync } = require('child_process');
|
|
|
26
26
|
|
|
27
27
|
const PACKAGE_JSON = require('../package.json');
|
|
28
28
|
const FRAMEWORK_VERSION = '0.1.0';
|
|
29
|
+
const DEFAULT_LANGUAGE = 'Русский';
|
|
29
30
|
const PACKAGE_DIR = path.join(__dirname, '..');
|
|
30
31
|
const TEMPLATES_DIR = path.join(PACKAGE_DIR, 'templates');
|
|
31
32
|
const SYSTEM_DIR = path.join(TEMPLATES_DIR, 'system');
|
|
@@ -81,12 +82,24 @@ function renderButton(label) {
|
|
|
81
82
|
return `${top}\n${mid}\n${bottom}`;
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
// Asks a question with a default value shown in the prompt; Enter accepts it.
|
|
86
|
+
function ask(rl, question, defaultValue) {
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
rl.question(question, (answer) => {
|
|
89
|
+
const trimmed = answer.trim();
|
|
90
|
+
resolve(trimmed || defaultValue);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
84
95
|
// Shows an install "button" and waits for confirmation. Auto-confirms when
|
|
85
96
|
// stdin isn't a TTY (CI, pipes) or when --yes was passed, so scripted/non-
|
|
86
|
-
// interactive runs never hang.
|
|
87
|
-
|
|
97
|
+
// interactive runs never hang. `rl` must be provided whenever this runs
|
|
98
|
+
// interactively (i.e. whenever opts.yes is false and stdin is a TTY).
|
|
99
|
+
function confirmInstall(info, opts, rl) {
|
|
88
100
|
console.log(` ${dim('Папка:')} ${info.target}`);
|
|
89
101
|
console.log(` ${dim('Ветка:')} ${info.branch}`);
|
|
102
|
+
console.log(` ${dim('Язык:')} ${info.language}`);
|
|
90
103
|
console.log(` ${dim('Режим:')} ${info.isRerun ? 'обновление существующего пространства' : 'новая установка'}`);
|
|
91
104
|
console.log('');
|
|
92
105
|
|
|
@@ -100,10 +113,8 @@ function confirmInstall(info, opts) {
|
|
|
100
113
|
console.log(renderButton('Установить') + '\n');
|
|
101
114
|
console.log(dim('Enter — установить, n — отменить'));
|
|
102
115
|
|
|
103
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
104
116
|
return new Promise((resolve) => {
|
|
105
117
|
rl.question('> ', (answer) => {
|
|
106
|
-
rl.close();
|
|
107
118
|
console.log('');
|
|
108
119
|
resolve(!/^n(o)?$/i.test(answer.trim()));
|
|
109
120
|
});
|
|
@@ -111,7 +122,7 @@ function confirmInstall(info, opts) {
|
|
|
111
122
|
}
|
|
112
123
|
|
|
113
124
|
function parseArgs(argv) {
|
|
114
|
-
const opts = { target: null, branch: null, force: false, yes: false };
|
|
125
|
+
const opts = { target: null, branch: null, lang: null, force: false, yes: false };
|
|
115
126
|
for (let i = 0; i < argv.length; i++) {
|
|
116
127
|
const a = argv[i];
|
|
117
128
|
if (a === '--force') {
|
|
@@ -122,6 +133,10 @@ function parseArgs(argv) {
|
|
|
122
133
|
opts.branch = argv[++i] || null;
|
|
123
134
|
} else if (a.startsWith('--branch=')) {
|
|
124
135
|
opts.branch = a.slice('--branch='.length);
|
|
136
|
+
} else if (a === '--lang') {
|
|
137
|
+
opts.lang = argv[++i] || null;
|
|
138
|
+
} else if (a.startsWith('--lang=')) {
|
|
139
|
+
opts.lang = a.slice('--lang='.length);
|
|
125
140
|
} else if (!a.startsWith('-') && opts.target === null) {
|
|
126
141
|
opts.target = a;
|
|
127
142
|
}
|
|
@@ -183,6 +198,18 @@ function tryGit(args, cwd) {
|
|
|
183
198
|
}
|
|
184
199
|
}
|
|
185
200
|
|
|
201
|
+
// Personal-branch name suggested from the user's global git identity, falling
|
|
202
|
+
// back to 'dev'. `cwd` must already exist (global config doesn't care where
|
|
203
|
+
// it's read from, but a missing directory would make the git spawn itself fail).
|
|
204
|
+
function suggestBranch(cwd) {
|
|
205
|
+
const cfg = tryGit(['config', '--global', 'user.name'], cwd);
|
|
206
|
+
if (cfg.ok && cfg.out.trim()) {
|
|
207
|
+
const slug = cfg.out.trim().toLowerCase().replace(/\s+/g, '-').replace(/^-+|-+$/g, '');
|
|
208
|
+
if (slug) return slug;
|
|
209
|
+
}
|
|
210
|
+
return 'dev';
|
|
211
|
+
}
|
|
212
|
+
|
|
186
213
|
async function main() {
|
|
187
214
|
if (!fs.existsSync(SYSTEM_DIR)) {
|
|
188
215
|
console.error(`Templates not found at ${SYSTEM_DIR}. This script must ship together with templates/.`);
|
|
@@ -190,30 +217,51 @@ async function main() {
|
|
|
190
217
|
}
|
|
191
218
|
|
|
192
219
|
const args = parseArgs(process.argv.slice(2));
|
|
193
|
-
|
|
220
|
+
printBanner();
|
|
221
|
+
|
|
222
|
+
// Only prompt when nothing overrides it and we can actually read an answer.
|
|
223
|
+
// Any explicit flag (--branch, --lang, positional target dir) skips its
|
|
224
|
+
// corresponding question. --yes or a non-TTY stdin (CI, pipes) skips the
|
|
225
|
+
// whole wizard so scripted runs never hang.
|
|
226
|
+
const interactive = !args.yes && process.stdin.isTTY;
|
|
227
|
+
const rl = interactive ? readline.createInterface({ input: process.stdin, output: process.stdout }) : null;
|
|
228
|
+
|
|
229
|
+
let targetInput = args.target;
|
|
230
|
+
if (interactive && !targetInput) {
|
|
231
|
+
targetInput = await ask(rl, `${dim("Папка")} (путь; '.' — текущая, название/путь — новая) [.]: `, '.');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const target = path.resolve(targetInput || '.');
|
|
235
|
+
fs.mkdirSync(target, { recursive: true });
|
|
194
236
|
const aiDir = path.join(target, '.ai');
|
|
195
237
|
const workspaceName = path.basename(target);
|
|
196
238
|
const isRerun = fs.existsSync(aiDir);
|
|
197
239
|
|
|
240
|
+
let language = args.lang;
|
|
198
241
|
let branch = args.branch;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (
|
|
202
|
-
|
|
242
|
+
|
|
243
|
+
if (interactive) {
|
|
244
|
+
if (!language) {
|
|
245
|
+
language = await ask(rl, `${dim('Язык общения и рассуждений ассистента')} [${DEFAULT_LANGUAGE}]: `, DEFAULT_LANGUAGE);
|
|
246
|
+
}
|
|
247
|
+
if (!branch) {
|
|
248
|
+
const suggested = suggestBranch(target);
|
|
249
|
+
branch = await ask(rl, `${dim('Ветка')} [${suggested}]: `, suggested);
|
|
203
250
|
}
|
|
204
|
-
|
|
251
|
+
} else {
|
|
252
|
+
if (!language) language = DEFAULT_LANGUAGE;
|
|
253
|
+
if (!branch) branch = suggestBranch(target);
|
|
205
254
|
}
|
|
206
255
|
|
|
207
|
-
|
|
208
|
-
|
|
256
|
+
const proceed = await confirmInstall({ target, branch, language, isRerun }, args, rl);
|
|
257
|
+
if (rl) rl.close();
|
|
258
|
+
|
|
209
259
|
if (!proceed) {
|
|
210
260
|
console.log(yellow('[cancelled] Ничего не изменено.'));
|
|
211
261
|
return;
|
|
212
262
|
}
|
|
213
263
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const vars = { WORKSPACE_NAME: workspaceName, FRAMEWORK_VERSION: FRAMEWORK_VERSION };
|
|
264
|
+
const vars = { WORKSPACE_NAME: workspaceName, FRAMEWORK_VERSION: FRAMEWORK_VERSION, LANGUAGE: language };
|
|
217
265
|
|
|
218
266
|
copyTemplateTree(SYSTEM_DIR, target, vars, true);
|
|
219
267
|
copyTemplateTree(SCAFFOLD_DIR, target, vars, args.force);
|
|
@@ -239,6 +287,11 @@ async function main() {
|
|
|
239
287
|
console.log(green('[ok] git init'));
|
|
240
288
|
}
|
|
241
289
|
|
|
290
|
+
// Local-only override: stops git from silently converting LF<->CRLF (and
|
|
291
|
+
// printing a warning per file) on Windows checkouts, regardless of the
|
|
292
|
+
// user's global core.autocrlf. This workspace's text files are always LF.
|
|
293
|
+
tryGit(['config', 'core.autocrlf', 'false'], target);
|
|
294
|
+
|
|
242
295
|
const currentBranchResult = tryGit(['branch', '--show-current'], target);
|
|
243
296
|
const currentBranch = currentBranchResult.ok ? currentBranchResult.out.trim() : '';
|
|
244
297
|
|
package/package.json
CHANGED
|
@@ -4,5 +4,8 @@ This project is managed by vsem-framework.
|
|
|
4
4
|
Work according to the rules and structure in `.ai/`.
|
|
5
5
|
Start with `.ai/rules/core.md` and `.ai/project.md`.
|
|
6
6
|
|
|
7
|
+
Communicate with the user, and think, in **{{LANGUAGE}}**, unless they ask you
|
|
8
|
+
to switch.
|
|
9
|
+
|
|
7
10
|
**Hard rule:** never modify the top-level structure of `.ai/` (create/delete/rename
|
|
8
11
|
top-level folders inside it) without explicit human confirmation.
|