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 +7 -1
- package/README.md +17 -4
- package/bin/viepilot.cjs +235 -26
- package/dev-install.sh +39 -19
- package/docs/dev/deployment.md +3 -3
- package/docs/troubleshooting.md +35 -5
- package/docs/user/quick-start.md +14 -3
- package/install.sh +47 -0
- package/package.json +2 -1
- package/skills/vp-auto/SKILL.md +1 -0
- package/workflows/autonomous.md +5 -0
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.
|
|
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 | **~
|
|
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
|
|
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
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
.
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
#
|
|
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
|
|
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}
|
|
78
|
+
echo -e "${BLUE}Installing skills...${NC}"
|
|
65
79
|
|
|
66
|
-
#
|
|
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
|
-
|
|
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}
|
|
89
|
+
echo -e "${BLUE}Installing viepilot files...${NC}"
|
|
76
90
|
|
|
77
|
-
#
|
|
91
|
+
# Install file copies (avoid symlink discovery issues)
|
|
78
92
|
mkdir -p "$VIEPILOT_DIR"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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 (
|
|
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
|
|
105
|
-
echo "
|
|
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 ""
|
package/docs/dev/deployment.md
CHANGED
|
@@ -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
|
|
10
|
+
npx viepilot install
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
Direct target mode:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npx
|
|
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
|
|
119
|
+
npx viepilot install --help
|
|
120
120
|
```
|
|
121
121
|
|
|
122
122
|
Optional version pin:
|
package/docs/troubleshooting.md
CHANGED
|
@@ -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
|
-
#
|
|
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
|
-
|
|
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
|
package/docs/user/quick-start.md
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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",
|
package/skills/vp-auto/SKILL.md
CHANGED
|
@@ -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>
|
package/workflows/autonomous.md
CHANGED
|
@@ -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
|