@vsem/ai 0.1.1 → 0.1.2
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 +7 -1
- package/bin/cli.js +107 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,12 +14,18 @@ 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]
|
|
17
|
+
npx @vsem/ai@latest [target-dir] [--branch <name>] [--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
22
|
- `--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
|
+
|
|
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`.
|
|
23
29
|
|
|
24
30
|
## Updating an existing workspace
|
|
25
31
|
|
package/bin/cli.js
CHANGED
|
@@ -17,24 +17,107 @@
|
|
|
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]
|
|
20
|
+
// npx @vsem/ai@latest [target-dir] [--branch <name>] [--force] [--yes]
|
|
21
21
|
|
|
22
22
|
const fs = require('fs');
|
|
23
23
|
const path = require('path');
|
|
24
|
+
const readline = require('readline');
|
|
24
25
|
const { execFileSync } = require('child_process');
|
|
25
26
|
|
|
27
|
+
const PACKAGE_JSON = require('../package.json');
|
|
26
28
|
const FRAMEWORK_VERSION = '0.1.0';
|
|
27
29
|
const PACKAGE_DIR = path.join(__dirname, '..');
|
|
28
30
|
const TEMPLATES_DIR = path.join(PACKAGE_DIR, 'templates');
|
|
29
31
|
const SYSTEM_DIR = path.join(TEMPLATES_DIR, 'system');
|
|
30
32
|
const SCAFFOLD_DIR = path.join(TEMPLATES_DIR, 'scaffold');
|
|
31
33
|
|
|
34
|
+
// --- terminal styling -------------------------------------------------
|
|
35
|
+
|
|
36
|
+
const supportsColor = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
37
|
+
|
|
38
|
+
function style(codes, text) {
|
|
39
|
+
if (!supportsColor) return text;
|
|
40
|
+
return `\x1b[${codes}m${text}\x1b[0m`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const cyan = (s) => style('36', s);
|
|
44
|
+
const cyanBold = (s) => style('1;36', s);
|
|
45
|
+
const dim = (s) => style('2', s);
|
|
46
|
+
const green = (s) => style('32', s);
|
|
47
|
+
const yellow = (s) => style('33', s);
|
|
48
|
+
const button = (s) => style('1;30;46', s); // bold black text on cyan background
|
|
49
|
+
|
|
50
|
+
// 5-row pixel font, just enough to spell VSEM.
|
|
51
|
+
const GLYPHS = {
|
|
52
|
+
V: ['█ █', '█ █', '█ █', ' █ █ ', ' █ '],
|
|
53
|
+
S: [' ████', '█ ', ' ███ ', ' █', '████ '],
|
|
54
|
+
E: ['█████', '█ ', '████ ', '█ ', '█████'],
|
|
55
|
+
M: ['█ █', '██ ██', '█ █ █', '█ █', '█ █'],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function renderLogo(word) {
|
|
59
|
+
const rows = [];
|
|
60
|
+
for (let row = 0; row < 5; row++) {
|
|
61
|
+
rows.push(word.split('').map((ch) => GLYPHS[ch][row]).join(' '));
|
|
62
|
+
}
|
|
63
|
+
return rows.map(cyanBold).join('\n');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function printBanner() {
|
|
67
|
+
console.log('');
|
|
68
|
+
console.log(renderLogo('VSEM'));
|
|
69
|
+
console.log('');
|
|
70
|
+
console.log(cyan('vsem-framework') + dim(` · CLI v${PACKAGE_JSON.version} · шаблон v${FRAMEWORK_VERSION}`));
|
|
71
|
+
console.log(dim('Разворачивает рабочее пространство для AI-агента: .ai/ (роли, правила,'));
|
|
72
|
+
console.log(dim('задачи, решения, инциденты), CLAUDE.md и личную git-ветку — в этой папке.'));
|
|
73
|
+
console.log('');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function renderButton(label) {
|
|
77
|
+
const padded = ` ${label} `;
|
|
78
|
+
const top = '┌' + '─'.repeat(padded.length) + '┐';
|
|
79
|
+
const mid = '│' + button(padded) + '│';
|
|
80
|
+
const bottom = '└' + '─'.repeat(padded.length) + '┘';
|
|
81
|
+
return `${top}\n${mid}\n${bottom}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Shows an install "button" and waits for confirmation. Auto-confirms when
|
|
85
|
+
// 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) {
|
|
88
|
+
console.log(` ${dim('Папка:')} ${info.target}`);
|
|
89
|
+
console.log(` ${dim('Ветка:')} ${info.branch}`);
|
|
90
|
+
console.log(` ${dim('Режим:')} ${info.isRerun ? 'обновление существующего пространства' : 'новая установка'}`);
|
|
91
|
+
console.log('');
|
|
92
|
+
|
|
93
|
+
if (opts.yes || !process.stdin.isTTY) {
|
|
94
|
+
console.log(renderButton('Установить') + '\n');
|
|
95
|
+
console.log(dim(opts.yes ? 'Подтверждение пропущено (--yes).' : 'Неинтерактивный режим — продолжаю автоматически.'));
|
|
96
|
+
console.log('');
|
|
97
|
+
return Promise.resolve(true);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log(renderButton('Установить') + '\n');
|
|
101
|
+
console.log(dim('Enter — установить, n — отменить'));
|
|
102
|
+
|
|
103
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
rl.question('> ', (answer) => {
|
|
106
|
+
rl.close();
|
|
107
|
+
console.log('');
|
|
108
|
+
resolve(!/^n(o)?$/i.test(answer.trim()));
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
32
113
|
function parseArgs(argv) {
|
|
33
|
-
const opts = { target: null, branch: null, force: false };
|
|
114
|
+
const opts = { target: null, branch: null, force: false, yes: false };
|
|
34
115
|
for (let i = 0; i < argv.length; i++) {
|
|
35
116
|
const a = argv[i];
|
|
36
117
|
if (a === '--force') {
|
|
37
118
|
opts.force = true;
|
|
119
|
+
} else if (a === '--yes' || a === '-y') {
|
|
120
|
+
opts.yes = true;
|
|
38
121
|
} else if (a === '--branch') {
|
|
39
122
|
opts.branch = argv[++i] || null;
|
|
40
123
|
} else if (a.startsWith('--branch=')) {
|
|
@@ -100,7 +183,7 @@ function tryGit(args, cwd) {
|
|
|
100
183
|
}
|
|
101
184
|
}
|
|
102
185
|
|
|
103
|
-
function main() {
|
|
186
|
+
async function main() {
|
|
104
187
|
if (!fs.existsSync(SYSTEM_DIR)) {
|
|
105
188
|
console.error(`Templates not found at ${SYSTEM_DIR}. This script must ship together with templates/.`);
|
|
106
189
|
process.exit(1);
|
|
@@ -108,8 +191,6 @@ function main() {
|
|
|
108
191
|
|
|
109
192
|
const args = parseArgs(process.argv.slice(2));
|
|
110
193
|
const target = path.resolve(args.target || process.cwd());
|
|
111
|
-
|
|
112
|
-
fs.mkdirSync(target, { recursive: true });
|
|
113
194
|
const aiDir = path.join(target, '.ai');
|
|
114
195
|
const workspaceName = path.basename(target);
|
|
115
196
|
const isRerun = fs.existsSync(aiDir);
|
|
@@ -123,6 +204,15 @@ function main() {
|
|
|
123
204
|
if (!branch) branch = 'dev';
|
|
124
205
|
}
|
|
125
206
|
|
|
207
|
+
printBanner();
|
|
208
|
+
const proceed = await confirmInstall({ target, branch, isRerun }, args);
|
|
209
|
+
if (!proceed) {
|
|
210
|
+
console.log(yellow('[cancelled] Ничего не изменено.'));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
fs.mkdirSync(target, { recursive: true });
|
|
215
|
+
|
|
126
216
|
const vars = { WORKSPACE_NAME: workspaceName, FRAMEWORK_VERSION: FRAMEWORK_VERSION };
|
|
127
217
|
|
|
128
218
|
copyTemplateTree(SYSTEM_DIR, target, vars, true);
|
|
@@ -133,20 +223,20 @@ function main() {
|
|
|
133
223
|
}
|
|
134
224
|
|
|
135
225
|
if (isRerun) {
|
|
136
|
-
console.log(`[ok] Workspace synced at ${target} (system files updated; scaffold files kept unless --force)`);
|
|
226
|
+
console.log(green(`[ok] Workspace synced at ${target} (system files updated; scaffold files kept unless --force)`));
|
|
137
227
|
} else {
|
|
138
|
-
console.log(`[ok] Workspace structure created at ${target}`);
|
|
228
|
+
console.log(green(`[ok] Workspace structure created at ${target}`));
|
|
139
229
|
}
|
|
140
230
|
|
|
141
231
|
const gitCheck = tryGit(['--version'], target);
|
|
142
232
|
if (!gitCheck.ok) {
|
|
143
|
-
console.log('[warn] git not found in PATH - skipping git init.');
|
|
233
|
+
console.log(yellow('[warn] git not found in PATH - skipping git init.'));
|
|
144
234
|
return;
|
|
145
235
|
}
|
|
146
236
|
|
|
147
237
|
if (!fs.existsSync(path.join(target, '.git'))) {
|
|
148
238
|
tryGit(['init', '-q'], target);
|
|
149
|
-
console.log('[ok] git init');
|
|
239
|
+
console.log(green('[ok] git init'));
|
|
150
240
|
}
|
|
151
241
|
|
|
152
242
|
const currentBranchResult = tryGit(['branch', '--show-current'], target);
|
|
@@ -159,7 +249,7 @@ function main() {
|
|
|
159
249
|
} else {
|
|
160
250
|
tryGit(['checkout', '-q', '-b', branch], target);
|
|
161
251
|
}
|
|
162
|
-
console.log(`[ok] branch '${branch}' is active`);
|
|
252
|
+
console.log(green(`[ok] branch '${branch}' is active`));
|
|
163
253
|
}
|
|
164
254
|
|
|
165
255
|
tryGit(['add', '-A'], target);
|
|
@@ -169,13 +259,16 @@ function main() {
|
|
|
169
259
|
const msg = isRerun ? 'chore: sync vsem-framework system files' : 'chore: bootstrap vsem-framework workspace';
|
|
170
260
|
const commitResult = tryGit(['commit', '-q', '-m', msg], target);
|
|
171
261
|
if (commitResult.ok) {
|
|
172
|
-
console.log(`[ok] commit created: ${msg}`);
|
|
262
|
+
console.log(green(`[ok] commit created: ${msg}`));
|
|
173
263
|
} else {
|
|
174
|
-
console.log('[warn] git commit failed (is git config user.name/user.email set?) - changes are staged but not committed.');
|
|
264
|
+
console.log(yellow('[warn] git commit failed (is git config user.name/user.email set?) - changes are staged but not committed.'));
|
|
175
265
|
}
|
|
176
266
|
} else {
|
|
177
|
-
console.log('[skip] nothing to commit (already up to date)');
|
|
267
|
+
console.log(dim('[skip] nothing to commit (already up to date)'));
|
|
178
268
|
}
|
|
179
269
|
}
|
|
180
270
|
|
|
181
|
-
main()
|
|
271
|
+
main().catch((err) => {
|
|
272
|
+
console.error(err);
|
|
273
|
+
process.exit(1);
|
|
274
|
+
});
|