viepilot 1.0.0 → 1.1.0

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/CHANGELOG.md CHANGED
@@ -9,7 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Enhanced
11
11
 
12
- - **M1.15 / Phase 18 (FEAT-004) in progress** — npm publish distribution scaffold: package publish metadata, release scripts, secure GitHub Actions `release-npm` workflow, and maintainer publish/rollback guide. Final registry smoke verify is blocked pending npm publish auth.
12
+ - **M1.17 / Phase 21 (ENH-013) completed** — realigned README metrics (`npm run readme:sync` with `cloc`) and moved `.viepilot` to local-only (`.gitignore` + untracked index).
13
+ - **M1.15 / Phase 18 (FEAT-004) completed** — npm distribution flow is now fully closed: publish pipeline passes and package released to npm as `viepilot@1.0.1`.
14
+ - **M1.15 / Phase 19 (FEAT-005) completed** — installer now supports keyboard selector UX (arrow/space/enter), added `viepilot uninstall` command (`--target`, `--yes`, `--dry-run`), and switched dev installer to copy-first flow to avoid symlink-based skill discovery failures.
15
+ - **M1.16 / Phase 20 (FEAT-006) completed** — added README LOC auto-sync command (`npm run readme:sync`) driven by `cloc` with non-blocking fallback, installer `cloc` dependency checks/guidance, and a donate section in README with PayPal/MOMO links.
13
16
 
14
17
  ### Fixed
15
18
 
@@ -17,6 +20,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
17
20
 
18
21
  ### Documentation
19
22
 
23
+ - `README.md`, `docs/user/quick-start.md` — updated install wizard controls and uninstall command examples.
24
+ - `README.md`, `docs/troubleshooting.md`, `docs/user/quick-start.md` — documented README metric sync flow, `cloc` fallback/install guidance, and maintainer usage.
25
+ - `docs/troubleshooting.md` — added selector TTY fallback guidance and uninstall/reinstall recovery flow for legacy symlink installs.
20
26
  - `README.md` — Project Scale LOC + M1.11 completion banner; Documentation row includes `api/`
21
27
  - `.viepilot/audit-report.md` — PASS after README metric sync (`/vp-audit`)
22
28
  - `docs/api/*` — added framework-appropriate API index (no HTTP surface; points to CLI/file model)
package/README.md CHANGED
@@ -16,13 +16,19 @@ ViePilot là bộ skill framework cho phép AI assistant (Claude, GPT, etc.) ph
16
16
 
17
17
  ViePilot is a skill framework that enables AI assistants to develop projects **autonomously**, with **control points**, and **recovery capability**. Built with professional standards: Semantic Versioning, Conventional Commits, Keep a Changelog.
18
18
 
19
+ ### Support ViePilot
20
+
21
+ Nếu ViePilot giúp ích cho bạn, bạn có thể ủng hộ một ly cafe:
22
+ - PayPal: [https://paypal.me/SATCODING](https://paypal.me/SATCODING)
23
+ - MOMO: [https://me.momo.vn/aMINujUPTbIRtbTli6Fd](https://me.momo.vn/aMINujUPTbIRtbTli6Fd)
24
+
19
25
  ---
20
26
 
21
27
  ## Quy mô dự án / Project Scale
22
28
 
23
29
  | Chỉ số / Metric | Giá trị / Value |
24
30
  |-----------------|-----------------|
25
- | Total LOC | **~24,000+** (`.md`, `.js`, `.cjs`, `.yml`, `.json`, `.sh`; không gồm `node_modules`) |
31
+ | Total LOC | **~22,384+** (`.md`, `.js`, `.cjs`, `.yml`, `.json`, `.sh`; không gồm `node_modules`) |
26
32
  | Skills | **14** |
27
33
  | Workflows | **12** |
28
34
  | Templates | **16** (Project: 11, Phase: 5) |
@@ -31,6 +37,8 @@ ViePilot is a skill framework that enables AI assistants to develop projects **a
31
37
  | ViePilot phases (repo) | **15** hoàn thành (xem `.viepilot/TRACKER.md`) |
32
38
  | Standards | 5 (SemVer, Commits, Changelog, Comments, Contributors) |
33
39
 
40
+ > Metric `Total LOC` có thể được refresh tự động bằng `npm run readme:sync` (dùng `cloc`; nếu thiếu `cloc` script sẽ fallback an toàn).
41
+
34
42
  ### Phân bổ / Breakdown
35
43
 
36
44
  | Thành phần / Component | Số lượng / Count | Mô tả / Description |
@@ -187,17 +195,22 @@ Tổng thể / Overall: ██████████████████
187
195
  ```bash
188
196
  git clone https://github.com/0-CODE/viepilot.git
189
197
  cd viepilot
190
- npx @0/viepilot install
198
+ npx viepilot install
191
199
  ```
192
200
 
193
- Chọn target profile trong wizard:
201
+ Chọn target profile trong wizard (phím mũi tên + space + enter):
194
202
  - Claude Code
195
203
  - Cursor Agent
196
204
  - Cursor IDE
197
205
 
198
206
  Non-interactive:
199
207
  ```bash
200
- npx @0/viepilot install --target cursor-agent --yes
208
+ npx viepilot install --target cursor-agent --yes
209
+ ```
210
+
211
+ Gỡ cài đặt:
212
+ ```bash
213
+ npx viepilot uninstall --target cursor-agent --yes
201
214
  ```
202
215
 
203
216
  ### Cách 2: Thủ công / Manual
package/bin/viepilot.cjs CHANGED
@@ -7,6 +7,7 @@
7
7
  const path = require('path');
8
8
  const { spawnSync } = require('child_process');
9
9
  const readline = require('readline');
10
+ const fs = require('fs');
10
11
 
11
12
  const TARGETS = [
12
13
  { id: 'claude-code', label: 'Claude Code' },
@@ -20,6 +21,7 @@ ViePilot CLI
20
21
 
21
22
  Usage:
22
23
  viepilot install [options]
24
+ viepilot uninstall [options]
23
25
  viepilot --help
24
26
  viepilot --list-targets
25
27
 
@@ -29,6 +31,11 @@ Install options:
29
31
  --dry-run Print actions only, do not execute installers
30
32
  --list-targets Print supported targets and exit
31
33
  --help Show help
34
+
35
+ Uninstall options:
36
+ --target <id|id,id|all> Remove installed assets for selected profile(s)
37
+ --yes Non-interactive mode (skip confirmations)
38
+ --dry-run Print actions only, do not remove files
32
39
  `);
33
40
  }
34
41
 
@@ -68,6 +75,35 @@ function parseInstallArgs(args) {
68
75
  return options;
69
76
  }
70
77
 
78
+ function parseUninstallArgs(args) {
79
+ const options = {
80
+ yes: false,
81
+ dryRun: false,
82
+ targets: null,
83
+ help: false,
84
+ };
85
+
86
+ for (let i = 0; i < args.length; i++) {
87
+ const arg = args[i];
88
+ if (arg === '--yes') options.yes = true;
89
+ else if (arg === '--dry-run') options.dryRun = true;
90
+ else if (arg === '--target') {
91
+ const value = args[i + 1];
92
+ if (!value) {
93
+ throw new Error('Missing value for --target');
94
+ }
95
+ options.targets = value;
96
+ i++;
97
+ } else if (arg === '--help' || arg === '-h') {
98
+ options.help = true;
99
+ } else {
100
+ throw new Error(`Unknown option: ${arg}`);
101
+ }
102
+ }
103
+
104
+ return options;
105
+ }
106
+
71
107
  function normalizeTargets(rawTargets) {
72
108
  if (!rawTargets) return null;
73
109
  if (rawTargets === 'all') return TARGETS.map((t) => t.id);
@@ -80,6 +116,96 @@ function normalizeTargets(rawTargets) {
80
116
  return [...new Set(parsed)];
81
117
  }
82
118
 
119
+ function createSelectorState(items, mode = 'multi') {
120
+ return {
121
+ mode,
122
+ items,
123
+ cursor: 0,
124
+ selected: mode === 'single' ? new Set([0]) : new Set(),
125
+ };
126
+ }
127
+
128
+ function applySelectorKey(state, keyName) {
129
+ const next = {
130
+ ...state,
131
+ selected: new Set([...state.selected]),
132
+ };
133
+ const lastIndex = next.items.length - 1;
134
+
135
+ if (keyName === 'up') {
136
+ next.cursor = next.cursor === 0 ? lastIndex : next.cursor - 1;
137
+ return next;
138
+ }
139
+ if (keyName === 'down') {
140
+ next.cursor = next.cursor === lastIndex ? 0 : next.cursor + 1;
141
+ return next;
142
+ }
143
+ if (keyName === 'space') {
144
+ if (next.mode === 'single') {
145
+ next.selected = new Set([next.cursor]);
146
+ } else if (next.selected.has(next.cursor)) {
147
+ next.selected.delete(next.cursor);
148
+ } else {
149
+ next.selected.add(next.cursor);
150
+ }
151
+ return next;
152
+ }
153
+ return next;
154
+ }
155
+
156
+ function renderSelector(state, title) {
157
+ process.stdout.write('\x1Bc');
158
+ console.log(title);
159
+ console.log('');
160
+ for (let i = 0; i < state.items.length; i++) {
161
+ const item = state.items[i];
162
+ const isCursor = i === state.cursor;
163
+ const isSelected = state.selected.has(i);
164
+ const marker = state.mode === 'single' ? (isSelected ? '(*)' : '( )') : (isSelected ? '[x]' : '[ ]');
165
+ const pointer = isCursor ? '>' : ' ';
166
+ console.log(`${pointer} ${marker} ${item.label} (${item.id})`);
167
+ }
168
+ console.log('');
169
+ console.log('Keys: ↑/↓ move, space select, enter confirm, q cancel');
170
+ }
171
+
172
+ function runKeyboardSelector(items, mode, title) {
173
+ return new Promise((resolve) => {
174
+ let state = createSelectorState(items, mode);
175
+ renderSelector(state, title);
176
+ readline.emitKeypressEvents(process.stdin);
177
+ const shouldRestoreRaw = process.stdin.isTTY;
178
+ if (shouldRestoreRaw) process.stdin.setRawMode(true);
179
+
180
+ const onKeypress = (_, key) => {
181
+ if (!key) return;
182
+ if (key.name === 'q' || (key.ctrl && key.name === 'c')) {
183
+ cleanup();
184
+ resolve([]);
185
+ return;
186
+ }
187
+ if (key.name === 'return') {
188
+ const selected = [...state.selected].sort((a, b) => a - b).map((idx) => state.items[idx].id);
189
+ cleanup();
190
+ resolve(selected);
191
+ return;
192
+ }
193
+ if (['up', 'down', 'space'].includes(key.name)) {
194
+ state = applySelectorKey(state, key.name);
195
+ renderSelector(state, title);
196
+ }
197
+ };
198
+
199
+ const cleanup = () => {
200
+ process.stdin.off('keypress', onKeypress);
201
+ if (shouldRestoreRaw) process.stdin.setRawMode(false);
202
+ console.log('');
203
+ };
204
+
205
+ process.stdin.on('keypress', onKeypress);
206
+ });
207
+ }
208
+
83
209
  function ask(question) {
84
210
  return new Promise((resolve) => {
85
211
  const rl = readline.createInterface({
@@ -94,34 +220,27 @@ function ask(question) {
94
220
  }
95
221
 
96
222
  async function interactiveTargetSelection() {
97
- console.log('\nSelect install targets (comma-separated numbers):');
98
- TARGETS.forEach((t, idx) => {
99
- console.log(` ${idx + 1}. ${t.label} (${t.id})`);
100
- });
101
- console.log(' 4. All targets');
102
- console.log(' 0. Cancel');
103
- const answer = await ask('Choice: ');
104
-
105
- if (answer === '0') return [];
106
- if (answer === '4') return TARGETS.map((t) => t.id);
107
-
108
- const indexes = answer
109
- .split(',')
110
- .map((x) => Number(x.trim()))
111
- .filter((n) => Number.isInteger(n) && n >= 1 && n <= TARGETS.length);
112
-
113
- const selected = [...new Set(indexes.map((n) => TARGETS[n - 1].id))];
114
- return selected;
223
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
224
+ console.log('\nSelect install targets (comma-separated numbers):');
225
+ TARGETS.forEach((t, idx) => {
226
+ console.log(` ${idx + 1}. ${t.label} (${t.id})`);
227
+ });
228
+ console.log(` ${TARGETS.length + 1}. All targets`);
229
+ console.log(' 0. Cancel');
230
+ const answer = await ask('Choice: ');
231
+ if (answer === '0') return [];
232
+ if (answer === String(TARGETS.length + 1)) return TARGETS.map((t) => t.id);
233
+ const indexes = answer
234
+ .split(',')
235
+ .map((x) => Number(x.trim()))
236
+ .filter((n) => Number.isInteger(n) && n >= 1 && n <= TARGETS.length);
237
+ return [...new Set(indexes.map((n) => TARGETS[n - 1].id))];
238
+ }
239
+ return runKeyboardSelector(TARGETS, 'multi', 'Select install targets');
115
240
  }
116
241
 
117
242
  function handlerForTarget(target) {
118
243
  const root = path.join(__dirname, '..');
119
- if (target === 'cursor-agent') {
120
- return {
121
- script: path.join(root, 'dev-install.sh'),
122
- env: { VIEPILOT_AUTO_YES: '1', VIEPILOT_INSTALL_PROFILE: target },
123
- };
124
- }
125
244
  return {
126
245
  script: path.join(root, 'install.sh'),
127
246
  env: { VIEPILOT_AUTO_YES: '1', VIEPILOT_INSTALL_PROFILE: target },
@@ -187,6 +306,92 @@ async function installCommand(rawArgs) {
187
306
  return failed.length === 0 ? 0 : 1;
188
307
  }
189
308
 
309
+ function computeUninstallPaths(targets) {
310
+ const home = process.env.HOME || '';
311
+ const cursorSkills = path.join(home, '.cursor', 'skills');
312
+ const vpRoot = path.join(home, '.cursor', 'viepilot');
313
+ const paths = [];
314
+
315
+ if (targets.some((t) => t === 'cursor-agent' || t === 'cursor-ide')) {
316
+ if (fs.existsSync(cursorSkills)) {
317
+ for (const entry of fs.readdirSync(cursorSkills)) {
318
+ if (entry.startsWith('vp-')) {
319
+ paths.push(path.join(cursorSkills, entry));
320
+ }
321
+ }
322
+ } else {
323
+ paths.push(path.join(cursorSkills, 'vp-*'));
324
+ }
325
+ }
326
+
327
+ paths.push(vpRoot);
328
+ paths.push('/usr/local/bin/vp-tools');
329
+ paths.push('/usr/local/bin/viepilot');
330
+
331
+ return [...new Set(paths)];
332
+ }
333
+
334
+ function removePathSafely(targetPath, dryRun) {
335
+ if (!fs.existsSync(targetPath)) {
336
+ return { path: targetPath, status: 'missing' };
337
+ }
338
+ if (dryRun) {
339
+ return { path: targetPath, status: 'planned' };
340
+ }
341
+ try {
342
+ fs.rmSync(targetPath, { recursive: true, force: true });
343
+ return { path: targetPath, status: 'removed' };
344
+ } catch (error) {
345
+ return { path: targetPath, status: 'failed', reason: error.message };
346
+ }
347
+ }
348
+
349
+ async function uninstallCommand(rawArgs) {
350
+ const options = parseUninstallArgs(rawArgs);
351
+ if (options.help) {
352
+ printHelp();
353
+ return 0;
354
+ }
355
+ let selectedTargets = normalizeTargets(options.targets);
356
+ if (!selectedTargets) {
357
+ if (options.yes) {
358
+ selectedTargets = TARGETS.map((t) => t.id);
359
+ } else if (process.stdin.isTTY && process.stdout.isTTY) {
360
+ selectedTargets = await runKeyboardSelector(TARGETS, 'single', 'Select uninstall profile');
361
+ if (selectedTargets.length === 0) {
362
+ console.log('Uninstall canceled.');
363
+ return 0;
364
+ }
365
+ } else {
366
+ selectedTargets = TARGETS.map((t) => t.id);
367
+ }
368
+ }
369
+
370
+ if (!options.yes && (!process.stdin.isTTY || !process.stdout.isTTY)) {
371
+ throw new Error('Non-interactive uninstall requires --yes');
372
+ }
373
+ if (!options.yes) {
374
+ const answer = await ask(`Uninstall ViePilot for target ${selectedTargets.join(', ')}? (y/N) `);
375
+ if (!/^y(es)?$/i.test(answer)) {
376
+ console.log('Uninstall canceled.');
377
+ return 0;
378
+ }
379
+ }
380
+
381
+ const actions = computeUninstallPaths(selectedTargets).map((p) => removePathSafely(p, options.dryRun));
382
+ const failed = actions.filter((a) => a.status === 'failed');
383
+
384
+ console.log('\nUninstall summary:');
385
+ for (const action of actions) {
386
+ if (action.status === 'removed') console.log(`- ${action.path}: removed`);
387
+ else if (action.status === 'planned') console.log(`- ${action.path}: planned`);
388
+ else if (action.status === 'missing') console.log(`- ${action.path}: not found`);
389
+ else console.log(`- ${action.path}: failed (${action.reason})`);
390
+ }
391
+
392
+ return failed.length === 0 ? 0 : 1;
393
+ }
394
+
190
395
  async function main() {
191
396
  const [, , command, ...rest] = process.argv;
192
397
  if (!command || command === '--help' || command === '-h') {
@@ -197,14 +402,14 @@ async function main() {
197
402
  printTargets();
198
403
  process.exit(0);
199
404
  }
200
- if (command !== 'install') {
405
+ if (command !== 'install' && command !== 'uninstall') {
201
406
  console.error(`Unknown command: ${command}`);
202
407
  printHelp();
203
408
  process.exit(1);
204
409
  }
205
410
 
206
411
  try {
207
- const code = await installCommand(rest);
412
+ const code = command === 'install' ? await installCommand(rest) : await uninstallCommand(rest);
208
413
  process.exit(code);
209
414
  } catch (error) {
210
415
  console.error(`Error: ${error.message}`);
@@ -218,5 +423,9 @@ if (require.main === module) {
218
423
 
219
424
  module.exports = {
220
425
  parseInstallArgs,
426
+ parseUninstallArgs,
221
427
  normalizeTargets,
428
+ createSelectorState,
429
+ applySelectorKey,
430
+ computeUninstallPaths,
222
431
  };
package/dev-install.sh CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/bash
2
2
 
3
3
  # ViePilot Development Installation Script
4
- # Creates symlinks to development version for instant updates
4
+ # Installs development build without symlink dependency by default
5
5
 
6
6
  set -e
7
7
 
@@ -25,15 +25,29 @@ echo " VIEPILOT DEV INSTALLER"
25
25
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
26
26
  echo -e "${NC}"
27
27
 
28
- echo -e "${YELLOW}Development mode installation${NC}"
28
+ echo -e "${YELLOW}Development mode installation (copy-first for reliability)${NC}"
29
29
  echo " Source: $SCRIPT_DIR"
30
30
  echo " Target: $CURSOR_SKILLS_DIR, $VIEPILOT_DIR"
31
31
  echo " Profile: $INSTALL_PROFILE"
32
32
  echo ""
33
33
 
34
+ check_cloc_dependency() {
35
+ if command -v cloc >/dev/null 2>&1; then
36
+ echo -e " ${GREEN}✓${NC} cloc detected"
37
+ return 0
38
+ fi
39
+
40
+ echo -e "${YELLOW} cloc not found.${NC}"
41
+ echo " README LOC auto-sync will fallback safely, but metrics won't refresh automatically."
42
+ echo " Install suggestion:"
43
+ echo " - macOS: brew install cloc"
44
+ echo " - Ubuntu/Debian: sudo apt-get install -y cloc"
45
+ echo " - Windows: choco install cloc"
46
+ }
47
+
34
48
  # Confirm
35
49
  if [ "$AUTO_YES" != "1" ]; then
36
- read -p "This will replace existing installation with symlinks. Continue? (y/n) " -n 1 -r
50
+ read -p "This will replace existing installation with dev files. Continue? (y/n) " -n 1 -r
37
51
  echo
38
52
  if [[ ! $REPLY =~ ^[Yy]$ ]]; then
39
53
  echo "Installation cancelled."
@@ -61,32 +75,41 @@ if [ -d "$VIEPILOT_DIR" ] || [ -L "$VIEPILOT_DIR" ]; then
61
75
  fi
62
76
 
63
77
  echo ""
64
- echo -e "${BLUE}Creating skill symlinks...${NC}"
78
+ echo -e "${BLUE}Installing skills...${NC}"
65
79
 
66
- # Create skill symlinks
80
+ # Install skill copies (avoid symlink discovery issues)
67
81
  mkdir -p "$CURSOR_SKILLS_DIR"
68
82
  for skill in "$SCRIPT_DIR"/skills/vp-*/; do
69
83
  skill_name=$(basename "$skill")
70
- ln -s "$skill" "$CURSOR_SKILLS_DIR/$skill_name"
84
+ cp -R "$skill" "$CURSOR_SKILLS_DIR/$skill_name"
71
85
  echo -e " ${GREEN}✓${NC} $skill_name"
72
86
  done
73
87
 
74
88
  echo ""
75
- echo -e "${BLUE}Creating viepilot symlinks...${NC}"
89
+ echo -e "${BLUE}Installing viepilot files...${NC}"
76
90
 
77
- # Create viepilot directory and symlinks
91
+ # Install file copies (avoid symlink discovery issues)
78
92
  mkdir -p "$VIEPILOT_DIR"
79
- ln -s "$SCRIPT_DIR/workflows" "$VIEPILOT_DIR/workflows"
80
- ln -s "$SCRIPT_DIR/templates" "$VIEPILOT_DIR/templates"
81
- ln -s "$SCRIPT_DIR/bin" "$VIEPILOT_DIR/bin"
82
- ln -s "$SCRIPT_DIR/lib" "$VIEPILOT_DIR/lib"
83
- ln -s "$SCRIPT_DIR/ui-components" "$VIEPILOT_DIR/ui-components"
93
+ mkdir -p "$VIEPILOT_DIR/workflows"
94
+ mkdir -p "$VIEPILOT_DIR/templates"
95
+ mkdir -p "$VIEPILOT_DIR/bin"
96
+ mkdir -p "$VIEPILOT_DIR/lib"
97
+ mkdir -p "$VIEPILOT_DIR/ui-components"
98
+
99
+ cp -R "$SCRIPT_DIR/workflows/"* "$VIEPILOT_DIR/workflows/"
100
+ cp -R "$SCRIPT_DIR/templates/"* "$VIEPILOT_DIR/templates/"
101
+ cp -R "$SCRIPT_DIR/bin/"* "$VIEPILOT_DIR/bin/"
102
+ cp -R "$SCRIPT_DIR/lib/"* "$VIEPILOT_DIR/lib/"
103
+ if [ -d "$SCRIPT_DIR/ui-components" ]; then
104
+ cp -R "$SCRIPT_DIR/ui-components/"* "$VIEPILOT_DIR/ui-components/"
105
+ fi
84
106
 
85
107
  echo -e " ${GREEN}✓${NC} workflows"
86
108
  echo -e " ${GREEN}✓${NC} templates"
87
109
  echo -e " ${GREEN}✓${NC} bin"
88
110
  echo -e " ${GREEN}✓${NC} lib"
89
111
  echo -e " ${GREEN}✓${NC} ui-components"
112
+ check_cloc_dependency
90
113
 
91
114
  # Count installed
92
115
  SKILL_COUNT=$(ls -d "$CURSOR_SKILLS_DIR"/vp-*/ 2>/dev/null | wc -l | tr -d ' ')
@@ -97,13 +120,10 @@ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━
97
120
  echo -e "${GREEN} DEV INSTALLATION COMPLETE ✓${NC}"
98
121
  echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
99
122
  echo ""
100
- echo "Installed (via symlinks):"
123
+ echo "Installed (copy mode):"
101
124
  echo " - Skills: $SKILL_COUNT"
102
125
  echo " - Workflows: $WORKFLOW_COUNT"
103
126
  echo ""
104
- echo -e "${YELLOW}Development mode enabled!${NC}"
105
- echo "Any changes to $SCRIPT_DIR will be reflected immediately."
106
- echo ""
107
- echo "To switch back to stable installation:"
108
- echo " ./install.sh"
127
+ echo -e "${YELLOW}Development mode enabled (reliable copy mode).${NC}"
128
+ echo "Re-run this script after local changes to refresh installed files."
109
129
  echo ""
@@ -7,13 +7,13 @@ ViePilot là một local framework — không có server để deploy. "Deployme
7
7
  ### Method 1: npm distribution (recommended for users)
8
8
 
9
9
  ```bash
10
- npx @0/viepilot install
10
+ npx viepilot install
11
11
  ```
12
12
 
13
13
  Direct target mode:
14
14
 
15
15
  ```bash
16
- npx @0/viepilot install --target cursor-agent --yes
16
+ npx viepilot install --target cursor-agent --yes
17
17
  ```
18
18
 
19
19
  ### Method 2: Git Clone + Install Script (maintainers/dev)
@@ -116,7 +116,7 @@ Secret required:
116
116
  npm run smoke:published
117
117
  # or explicitly:
118
118
  npx viepilot --help
119
- npx @0/viepilot install --help
119
+ npx viepilot install --help
120
120
  ```
121
121
 
122
122
  Optional version pin:
@@ -30,6 +30,33 @@ Use `--yes` for no prompt and `--target` to avoid interactive selection.
30
30
 
31
31
  ---
32
32
 
33
+ ### Interactive selector doesn't work (arrow/space keys)
34
+
35
+ **Cause**: Terminal is non-TTY (CI, redirected stdin/out).
36
+ ViePilot tự fallback về mode nhập số trong môi trường non-TTY.
37
+
38
+ ```bash
39
+ npx viepilot install --target cursor-agent --yes
40
+ ```
41
+
42
+ ---
43
+
44
+ ### README LOC metric is not updating
45
+
46
+ **Cause**: `cloc` is not installed, so `readme:sync` skips update by design.
47
+
48
+ ```bash
49
+ # Install cloc (macOS)
50
+ brew install cloc
51
+
52
+ # Then rerun metric sync
53
+ npm run readme:sync
54
+ ```
55
+
56
+ On Linux/Windows, use your package manager (`apt`, `dnf`, `choco`) to install `cloc`.
57
+
58
+ ---
59
+
33
60
  ### `./install.sh: Permission denied`
34
61
 
35
62
  **Cause**: Script not executable.
@@ -46,12 +73,9 @@ chmod +x install.sh
46
73
  **Cause**: ViePilot bin not in PATH.
47
74
 
48
75
  ```bash
49
- # Check if symlink exists
50
- ls -la ~/.local/bin/vp-tools
51
-
52
- # If missing, re-run dev install
76
+ # Re-run installer to refresh local bin
53
77
  cd /path/to/viepilot
54
- ./dev-install.sh
78
+ npx viepilot install --target cursor-agent --yes
55
79
  ```
56
80
 
57
81
  ---
@@ -72,6 +96,12 @@ cp -r skills/vp-* ~/.cursor/skills/
72
96
 
73
97
  Then restart Cursor.
74
98
 
99
+ If issue happens after old dev symlink installs:
100
+ ```bash
101
+ npx viepilot uninstall --yes
102
+ npx viepilot install --target cursor-agent --yes
103
+ ```
104
+
75
105
  ---
76
106
 
77
107
  ## Project Initialization Issues
@@ -23,17 +23,22 @@ Bạn: Review, approve, hoặc rollback
23
23
  ```bash
24
24
  git clone https://github.com/0-CODE/viepilot
25
25
  cd viepilot
26
- npx @0/viepilot install
26
+ npx viepilot install
27
27
  ```
28
28
 
29
- Chọn profile cài đặt trong wizard:
29
+ Chọn profile cài đặt trong wizard (arrow keys + space + enter):
30
30
  - `claude-code`
31
31
  - `cursor-agent`
32
32
  - `cursor-ide`
33
33
 
34
34
  Non-interactive:
35
35
  ```bash
36
- npx @0/viepilot install --target cursor-agent --yes
36
+ npx viepilot install --target cursor-agent --yes
37
+ ```
38
+
39
+ Uninstall:
40
+ ```bash
41
+ npx viepilot uninstall --target cursor-agent --yes
37
42
  ```
38
43
 
39
44
  Verify:
@@ -43,6 +48,12 @@ vp-tools help
43
48
  # Hiển thị targets + ViePilot CLI Tools
44
49
  ```
45
50
 
51
+ Optional (maintainers):
52
+ ```bash
53
+ npm run readme:sync
54
+ # refresh README Total LOC metrics via cloc
55
+ ```
56
+
46
57
  ---
47
58
 
48
59
  ## Step 2: Create a New Project
package/install.sh CHANGED
@@ -40,6 +40,50 @@ echo " ViePilot: $VIEPILOT_DIR"
40
40
  echo " Profile: $INSTALL_PROFILE"
41
41
  echo ""
42
42
 
43
+ install_cloc_best_effort() {
44
+ if command -v cloc >/dev/null 2>&1; then
45
+ echo " ✓ cloc detected"
46
+ return 0
47
+ fi
48
+
49
+ echo -e "${YELLOW} cloc not found.${NC}"
50
+ echo " README metric auto-sync can still run with fallback, but LOC refresh will be skipped."
51
+ echo " Suggested install:"
52
+ echo " - macOS: brew install cloc"
53
+ echo " - Ubuntu/Debian: sudo apt-get install -y cloc"
54
+ echo " - Windows: choco install cloc"
55
+
56
+ if [ "$AUTO_YES" = "1" ]; then
57
+ return 0
58
+ fi
59
+
60
+ read -p " Attempt automatic cloc install now (best effort)? (y/n) " -n 1 -r
61
+ echo
62
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
63
+ return 0
64
+ fi
65
+
66
+ if command -v brew >/dev/null 2>&1; then
67
+ brew install cloc || true
68
+ elif command -v apt-get >/dev/null 2>&1; then
69
+ sudo apt-get update && sudo apt-get install -y cloc || true
70
+ elif command -v dnf >/dev/null 2>&1; then
71
+ sudo dnf install -y cloc || true
72
+ elif command -v yum >/dev/null 2>&1; then
73
+ sudo yum install -y cloc || true
74
+ elif command -v pacman >/dev/null 2>&1; then
75
+ sudo pacman -Sy --noconfirm cloc || true
76
+ else
77
+ echo -e "${YELLOW} Could not detect supported package manager. Install cloc manually.${NC}"
78
+ fi
79
+
80
+ if command -v cloc >/dev/null 2>&1; then
81
+ echo " ✓ cloc installed"
82
+ else
83
+ echo -e "${YELLOW} cloc is still unavailable; continuing without blocking installation.${NC}"
84
+ fi
85
+ }
86
+
43
87
  # Confirm installation
44
88
  if [ "$AUTO_YES" != "1" ]; then
45
89
  read -p "Continue with installation? (y/n) " -n 1 -r
@@ -102,6 +146,9 @@ chmod +x "$VIEPILOT_DIR/bin/vp-tools.cjs"
102
146
  chmod +x "$VIEPILOT_DIR/bin/viepilot.cjs"
103
147
  echo " ✓ vp-tools.cjs + viepilot.cjs + lib/cli-shared.cjs"
104
148
 
149
+ echo " Checking optional dependency for README metric sync..."
150
+ install_cloc_best_effort
151
+
105
152
  # Create symlink in PATH (optional)
106
153
  echo ""
107
154
  if [ "$AUTO_YES" != "1" ]; then
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viepilot",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "**Autonomous Vibe Coding Framework / Bộ khung phát triển tự động có kiểm soát**",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  "test": "jest",
15
15
  "test:coverage": "jest --coverage",
16
16
  "test:watch": "jest --watch",
17
+ "readme:sync": "node scripts/sync-readme-metrics.cjs",
17
18
  "lint:cli": "node --check bin/vp-tools.cjs && node --check bin/viepilot.cjs",
18
19
  "verify:release": "npm run lint:cli && npm test && npm pack --dry-run",
19
20
  "prepublishOnly": "npm run verify:release",
@@ -63,6 +63,7 @@ If required task details are missing, do not implement until task contract is re
63
63
 
64
64
  **Updates on milestone complete:**
65
65
  - `README.md` — version badge, skills/workflows counts, Skills Reference table, Workflows table, Project Structure, Completion Status
66
+ - `README.md` metrics — run `npm run readme:sync` when script exists; if `cloc` missing, continue with logged guidance (non-blocking)
66
67
 
67
68
  **After:** Project built, or paused for user intervention.
68
69
  </objective>
@@ -323,6 +323,11 @@ fi
323
323
  Update README.md — **generic updates (all projects)**:
324
324
  1. Any version number mentions: update to `$ACTUAL_VERSION`
325
325
  2. Any "last updated" or "as of" date references
326
+ 3. If project contains `README` metrics table and `scripts/sync-readme-metrics.cjs`, run:
327
+ ```bash
328
+ npm run readme:sync || true
329
+ ```
330
+ - If `cloc` is unavailable, script must log guidance and continue (non-blocking).
326
331
 
327
332
  **viepilot framework only** (skip if `skills/` directory does not exist):
328
333
  ```bash