viepilot 1.0.0 → 1.0.1

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
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10
10
  ### Enhanced
11
11
 
12
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.
13
+ - **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.
13
14
 
14
15
  ### Fixed
15
16
 
@@ -17,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
17
18
 
18
19
  ### Documentation
19
20
 
21
+ - `README.md`, `docs/user/quick-start.md` — updated install wizard controls and uninstall command examples.
22
+ - `docs/troubleshooting.md` — added selector TTY fallback guidance and uninstall/reinstall recovery flow for legacy symlink installs.
20
23
  - `README.md` — Project Scale LOC + M1.11 completion banner; Documentation row includes `api/`
21
24
  - `.viepilot/audit-report.md` — PASS after README metric sync (`/vp-audit`)
22
25
  - `docs/api/*` — added framework-appropriate API index (no HTTP surface; points to CLI/file model)
package/README.md CHANGED
@@ -187,17 +187,22 @@ Tổng thể / Overall: ██████████████████
187
187
  ```bash
188
188
  git clone https://github.com/0-CODE/viepilot.git
189
189
  cd viepilot
190
- npx @0/viepilot install
190
+ npx viepilot install
191
191
  ```
192
192
 
193
- Chọn target profile trong wizard:
193
+ Chọn target profile trong wizard (phím mũi tên + space + enter):
194
194
  - Claude Code
195
195
  - Cursor Agent
196
196
  - Cursor IDE
197
197
 
198
198
  Non-interactive:
199
199
  ```bash
200
- npx @0/viepilot install --target cursor-agent --yes
200
+ npx viepilot install --target cursor-agent --yes
201
+ ```
202
+
203
+ Gỡ cài đặt:
204
+ ```bash
205
+ npx viepilot uninstall --target cursor-agent --yes
201
206
  ```
202
207
 
203
208
  ### 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,7 +25,7 @@ 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"
@@ -33,7 +33,7 @@ echo ""
33
33
 
34
34
  # Confirm
35
35
  if [ "$AUTO_YES" != "1" ]; then
36
- read -p "This will replace existing installation with symlinks. Continue? (y/n) " -n 1 -r
36
+ read -p "This will replace existing installation with dev files. Continue? (y/n) " -n 1 -r
37
37
  echo
38
38
  if [[ ! $REPLY =~ ^[Yy]$ ]]; then
39
39
  echo "Installation cancelled."
@@ -61,26 +61,34 @@ if [ -d "$VIEPILOT_DIR" ] || [ -L "$VIEPILOT_DIR" ]; then
61
61
  fi
62
62
 
63
63
  echo ""
64
- echo -e "${BLUE}Creating skill symlinks...${NC}"
64
+ echo -e "${BLUE}Installing skills...${NC}"
65
65
 
66
- # Create skill symlinks
66
+ # Install skill copies (avoid symlink discovery issues)
67
67
  mkdir -p "$CURSOR_SKILLS_DIR"
68
68
  for skill in "$SCRIPT_DIR"/skills/vp-*/; do
69
69
  skill_name=$(basename "$skill")
70
- ln -s "$skill" "$CURSOR_SKILLS_DIR/$skill_name"
70
+ cp -R "$skill" "$CURSOR_SKILLS_DIR/$skill_name"
71
71
  echo -e " ${GREEN}✓${NC} $skill_name"
72
72
  done
73
73
 
74
74
  echo ""
75
- echo -e "${BLUE}Creating viepilot symlinks...${NC}"
75
+ echo -e "${BLUE}Installing viepilot files...${NC}"
76
76
 
77
- # Create viepilot directory and symlinks
77
+ # Install file copies (avoid symlink discovery issues)
78
78
  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"
79
+ mkdir -p "$VIEPILOT_DIR/workflows"
80
+ mkdir -p "$VIEPILOT_DIR/templates"
81
+ mkdir -p "$VIEPILOT_DIR/bin"
82
+ mkdir -p "$VIEPILOT_DIR/lib"
83
+ mkdir -p "$VIEPILOT_DIR/ui-components"
84
+
85
+ cp -R "$SCRIPT_DIR/workflows/"* "$VIEPILOT_DIR/workflows/"
86
+ cp -R "$SCRIPT_DIR/templates/"* "$VIEPILOT_DIR/templates/"
87
+ cp -R "$SCRIPT_DIR/bin/"* "$VIEPILOT_DIR/bin/"
88
+ cp -R "$SCRIPT_DIR/lib/"* "$VIEPILOT_DIR/lib/"
89
+ if [ -d "$SCRIPT_DIR/ui-components" ]; then
90
+ cp -R "$SCRIPT_DIR/ui-components/"* "$VIEPILOT_DIR/ui-components/"
91
+ fi
84
92
 
85
93
  echo -e " ${GREEN}✓${NC} workflows"
86
94
  echo -e " ${GREEN}✓${NC} templates"
@@ -97,13 +105,10 @@ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━
97
105
  echo -e "${GREEN} DEV INSTALLATION COMPLETE ✓${NC}"
98
106
  echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
99
107
  echo ""
100
- echo "Installed (via symlinks):"
108
+ echo "Installed (copy mode):"
101
109
  echo " - Skills: $SKILL_COUNT"
102
110
  echo " - Workflows: $WORKFLOW_COUNT"
103
111
  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"
112
+ echo -e "${YELLOW}Development mode enabled (reliable copy mode).${NC}"
113
+ echo "Re-run this script after local changes to refresh installed files."
109
114
  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,17 @@ 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
+
33
44
  ### `./install.sh: Permission denied`
34
45
 
35
46
  **Cause**: Script not executable.
@@ -46,12 +57,9 @@ chmod +x install.sh
46
57
  **Cause**: ViePilot bin not in PATH.
47
58
 
48
59
  ```bash
49
- # Check if symlink exists
50
- ls -la ~/.local/bin/vp-tools
51
-
52
- # If missing, re-run dev install
60
+ # Re-run installer to refresh local bin
53
61
  cd /path/to/viepilot
54
- ./dev-install.sh
62
+ npx viepilot install --target cursor-agent --yes
55
63
  ```
56
64
 
57
65
  ---
@@ -72,6 +80,12 @@ cp -r skills/vp-* ~/.cursor/skills/
72
80
 
73
81
  Then restart Cursor.
74
82
 
83
+ If issue happens after old dev symlink installs:
84
+ ```bash
85
+ npx viepilot uninstall --yes
86
+ npx viepilot install --target cursor-agent --yes
87
+ ```
88
+
75
89
  ---
76
90
 
77
91
  ## 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:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viepilot",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
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": {