@vsem/ai 0.1.0 → 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.
Files changed (3) hide show
  1. package/README.md +32 -1
  2. package/bin/cli.js +111 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -14,15 +14,46 @@ 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
 
26
32
  Rerun `npx @vsem/ai@latest` (without `--force`) inside an already-bootstrapped
27
33
  workspace to pick up new roles or rule changes published in later versions — your
28
34
  `project.md`, `plan.md`, and tasks are left untouched.
35
+
36
+ ## Releasing (CI/CD)
37
+
38
+ - `.github/workflows/ci.yml` — runs on every push/PR to `main`: validates
39
+ `package.json`, dry-run `npm pack`, and a smoke test of `bin/cli.js` against a
40
+ scratch directory.
41
+ - `.github/workflows/release.yml` — on a pushed tag matching `v*.*.*`, calls the
42
+ reusable workflow in [vsem-org/.github](https://github.com/vsem-org/.github)
43
+ to run `npm publish` via **npm Trusted Publishing (OIDC)** — no long-lived
44
+ `NPM_TOKEN` secret involved. Configured once on npmjs.com (package Settings ->
45
+ Trusted Publisher -> GitHub Actions):
46
+ - Organization or user: `vsem-org`
47
+ - Repository: `-core--ai`
48
+ - Workflow filename: `release.yml` (the *calling* workflow, not the reusable
49
+ one in `vsem-org/.github` — npm validates the caller)
50
+ - Allowed actions: direct `npm publish` enabled
51
+
52
+ To ship a new version:
53
+
54
+ ```bash
55
+ npm version patch # or minor / major - bumps package.json, commits, tags
56
+ git push --follow-tags
57
+ ```
58
+
59
+ CI publishes automatically once the tag lands.
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);
@@ -167,11 +257,18 @@ function main() {
167
257
 
168
258
  if (!noChanges) {
169
259
  const msg = isRerun ? 'chore: sync vsem-framework system files' : 'chore: bootstrap vsem-framework workspace';
170
- tryGit(['commit', '-q', '-m', msg], target);
171
- console.log(`[ok] commit created: ${msg}`);
260
+ const commitResult = tryGit(['commit', '-q', '-m', msg], target);
261
+ if (commitResult.ok) {
262
+ console.log(green(`[ok] commit created: ${msg}`));
263
+ } else {
264
+ console.log(yellow('[warn] git commit failed (is git config user.name/user.email set?) - changes are staged but not committed.'));
265
+ }
172
266
  } else {
173
- console.log('[skip] nothing to commit (already up to date)');
267
+ console.log(dim('[skip] nothing to commit (already up to date)'));
174
268
  }
175
269
  }
176
270
 
177
- main();
271
+ main().catch((err) => {
272
+ console.error(err);
273
+ process.exit(1);
274
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vsem/ai",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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"