@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 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 an install confirmation
26
- prompt before touching disk; press Enter to proceed or `n` to cancel. In
27
- non-interactive contexts (CI, pipes) it detects the lack of a TTY and
28
- proceeds automatically, same as passing `--yes`.
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
- function confirmInstall(info, opts) {
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
- const target = path.resolve(args.target || process.cwd());
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
- if (!branch) {
200
- const cfg = tryGit(['config', '--global', 'user.name'], target);
201
- if (cfg.ok && cfg.out.trim()) {
202
- branch = cfg.out.trim().toLowerCase().replace(/\s+/g, '-').replace(/^-+|-+$/g, '');
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
- if (!branch) branch = 'dev';
251
+ } else {
252
+ if (!language) language = DEFAULT_LANGUAGE;
253
+ if (!branch) branch = suggestBranch(target);
205
254
  }
206
255
 
207
- printBanner();
208
- const proceed = await confirmInstall({ target, branch, isRerun }, args);
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
- fs.mkdirSync(target, { recursive: true });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vsem/ai",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Install or sync a vsem-framework workspace (.ai/ structure, roles, rules) via npx.",
5
5
  "bin": {
6
6
  "vsem-ai": "bin/cli.js"
@@ -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.