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 +3 -0
- package/README.md +8 -3
- package/bin/viepilot.cjs +235 -26
- package/dev-install.sh +24 -19
- package/docs/dev/deployment.md +3 -3
- package/docs/troubleshooting.md +19 -5
- package/docs/user/quick-start.md +8 -3
- package/package.json +1 -1
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
|
|
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
|
|
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
|
-
|
|
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,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
|
|
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}
|
|
64
|
+
echo -e "${BLUE}Installing skills...${NC}"
|
|
65
65
|
|
|
66
|
-
#
|
|
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
|
-
|
|
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}
|
|
75
|
+
echo -e "${BLUE}Installing viepilot files...${NC}"
|
|
76
76
|
|
|
77
|
-
#
|
|
77
|
+
# Install file copies (avoid symlink discovery issues)
|
|
78
78
|
mkdir -p "$VIEPILOT_DIR"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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 (
|
|
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
|
|
105
|
-
echo "
|
|
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 ""
|
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,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
|
-
#
|
|
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
|
-
|
|
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
|
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:
|