claude-cli-advanced-starter-pack 1.8.4 → 1.8.6
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/package.json +1 -1
- package/src/cli/menu.js +172 -2
- package/src/cli/mobile-menu.js +230 -0
- package/src/commands/explore-mcp/claude-md-updater.js +38 -3
- package/src/commands/init.js +89 -0
- package/src/commands/panel.js +84 -0
- package/src/commands/setup-wizard.js +85 -30
- package/src/data/releases.json +30 -0
- package/src/utils/happy-detect.js +66 -0
- package/src/utils/version-check.js +43 -17
- package/templates/commands/create-task-list-for-issue.template.md +72 -0
- package/templates/commands/create-task-list.template.md +73 -0
- package/templates/commands/detect-tech-stack.template.md +137 -0
- package/templates/commands/menu-for-happy-ui.template.md +109 -0
- package/templates/commands/menu-issues-list.template.md +72 -0
- package/templates/hooks/ccasp-update-check.template.js +36 -1
- package/templates/hooks/github-progress-hook.template.cjs +248 -0
- package/templates/hooks/github-progress-hook.template.js +0 -197
package/package.json
CHANGED
package/src/cli/menu.js
CHANGED
|
@@ -20,11 +20,13 @@ import { runCreatePhaseDev, showPhasDevMainMenu } from '../commands/create-phase
|
|
|
20
20
|
import { runExploreMcp, showExploreMcpMenu } from '../commands/explore-mcp.js';
|
|
21
21
|
import { runClaudeAudit, showClaudeAuditMenu } from '../commands/claude-audit.js';
|
|
22
22
|
import { runRoadmap, showRoadmapMenu } from '../commands/roadmap.js';
|
|
23
|
-
import { launchPanel } from '../commands/panel.js';
|
|
23
|
+
import { launchPanel, launchPanelInline } from '../commands/panel.js';
|
|
24
24
|
import { hasTestingConfig } from '../testing/config.js';
|
|
25
25
|
import { showHelp } from '../commands/help.js';
|
|
26
26
|
import { hasValidConfig, getVersion, loadTechStack, saveTechStack } from '../utils.js';
|
|
27
27
|
import { performVersionCheck, formatUpdateBanner } from '../utils/version-check.js';
|
|
28
|
+
import { isHappyMode, shouldUseMobileUI } from '../utils/happy-detect.js';
|
|
29
|
+
import { showMobileMenu, showMobilePanel, showMobileSettings, mobileReturnPrompt } from './mobile-menu.js';
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* Get bypass permissions status from settings.json
|
|
@@ -655,8 +657,15 @@ async function configureHappy(techStack) {
|
|
|
655
657
|
|
|
656
658
|
/**
|
|
657
659
|
* Show main interactive menu
|
|
660
|
+
* Automatically detects Happy CLI and switches to mobile-optimized UI
|
|
658
661
|
*/
|
|
659
662
|
export async function showMainMenu() {
|
|
663
|
+
// Check if we should use mobile UI (Happy CLI detected or happyMode.enabled)
|
|
664
|
+
const techStack = loadTechStack();
|
|
665
|
+
if (shouldUseMobileUI(techStack)) {
|
|
666
|
+
return showMobileMainMenu();
|
|
667
|
+
}
|
|
668
|
+
|
|
660
669
|
console.clear();
|
|
661
670
|
console.log(chalk.cyan(BANNER));
|
|
662
671
|
console.log('');
|
|
@@ -790,7 +799,7 @@ export async function showMainMenu() {
|
|
|
790
799
|
name: 'action',
|
|
791
800
|
message: 'Select an option:',
|
|
792
801
|
choices,
|
|
793
|
-
pageSize:
|
|
802
|
+
pageSize: 20,
|
|
794
803
|
},
|
|
795
804
|
]);
|
|
796
805
|
|
|
@@ -1063,3 +1072,164 @@ export function showWarning(message) {
|
|
|
1063
1072
|
export function showInfo(message) {
|
|
1064
1073
|
console.log(chalk.blue(`ℹ ${message}`));
|
|
1065
1074
|
}
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* Mobile-optimized main menu handler
|
|
1078
|
+
* Routes actions from the mobile menu to their handlers
|
|
1079
|
+
*/
|
|
1080
|
+
async function showMobileMainMenu() {
|
|
1081
|
+
const action = await showMobileMenu();
|
|
1082
|
+
|
|
1083
|
+
switch (action) {
|
|
1084
|
+
case 'create':
|
|
1085
|
+
const configured = hasValidConfig();
|
|
1086
|
+
if (!configured) {
|
|
1087
|
+
console.log(chalk.yellow('Setup required first.'));
|
|
1088
|
+
const { proceed } = await inquirer.prompt([
|
|
1089
|
+
{ type: 'confirm', name: 'proceed', message: 'Run setup?', default: true }
|
|
1090
|
+
]);
|
|
1091
|
+
if (proceed) await runSetup({});
|
|
1092
|
+
} else {
|
|
1093
|
+
await runCreate({});
|
|
1094
|
+
}
|
|
1095
|
+
break;
|
|
1096
|
+
|
|
1097
|
+
case 'decompose':
|
|
1098
|
+
if (!hasValidConfig()) {
|
|
1099
|
+
console.log(chalk.yellow('Setup required first.'));
|
|
1100
|
+
} else {
|
|
1101
|
+
await runDecompose({});
|
|
1102
|
+
}
|
|
1103
|
+
break;
|
|
1104
|
+
|
|
1105
|
+
case 'sync':
|
|
1106
|
+
if (!hasValidConfig()) {
|
|
1107
|
+
console.log(chalk.yellow('Setup required first.'));
|
|
1108
|
+
} else {
|
|
1109
|
+
await runSync({ subcommand: 'status' });
|
|
1110
|
+
}
|
|
1111
|
+
break;
|
|
1112
|
+
|
|
1113
|
+
case 'setup':
|
|
1114
|
+
await runSetup({});
|
|
1115
|
+
break;
|
|
1116
|
+
|
|
1117
|
+
case 'list':
|
|
1118
|
+
if (!hasValidConfig()) {
|
|
1119
|
+
console.log(chalk.yellow('Setup required first.'));
|
|
1120
|
+
} else {
|
|
1121
|
+
await runList({});
|
|
1122
|
+
}
|
|
1123
|
+
break;
|
|
1124
|
+
|
|
1125
|
+
case 'install':
|
|
1126
|
+
await runInstall({});
|
|
1127
|
+
break;
|
|
1128
|
+
|
|
1129
|
+
case 'panel-inline':
|
|
1130
|
+
// Show panel inline instead of launching new window
|
|
1131
|
+
await showMobilePanelLoop();
|
|
1132
|
+
break;
|
|
1133
|
+
|
|
1134
|
+
case 'test-setup':
|
|
1135
|
+
await runTestSetup({});
|
|
1136
|
+
break;
|
|
1137
|
+
|
|
1138
|
+
case 'agent-creator':
|
|
1139
|
+
await runCreateAgent({});
|
|
1140
|
+
break;
|
|
1141
|
+
|
|
1142
|
+
case 'explore-mcp':
|
|
1143
|
+
await showExploreMcpMenu();
|
|
1144
|
+
break;
|
|
1145
|
+
|
|
1146
|
+
case 'project-settings':
|
|
1147
|
+
await showMobileSettingsLoop();
|
|
1148
|
+
break;
|
|
1149
|
+
|
|
1150
|
+
case 'help':
|
|
1151
|
+
showHelp();
|
|
1152
|
+
break;
|
|
1153
|
+
|
|
1154
|
+
case 'exit':
|
|
1155
|
+
console.log(chalk.dim('Goodbye!'));
|
|
1156
|
+
process.exit(0);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// Return to menu unless exiting
|
|
1160
|
+
if (action !== 'exit') {
|
|
1161
|
+
const back = await mobileReturnPrompt();
|
|
1162
|
+
if (back) {
|
|
1163
|
+
await showMobileMainMenu();
|
|
1164
|
+
} else {
|
|
1165
|
+
console.log(chalk.dim('Goodbye!'));
|
|
1166
|
+
process.exit(0);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
/**
|
|
1172
|
+
* Mobile panel loop - inline panel without new window
|
|
1173
|
+
*/
|
|
1174
|
+
async function showMobilePanelLoop() {
|
|
1175
|
+
while (true) {
|
|
1176
|
+
const action = await showMobilePanel();
|
|
1177
|
+
|
|
1178
|
+
if (action === 'back') {
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// Copy command to clipboard and show instructions
|
|
1183
|
+
console.log('');
|
|
1184
|
+
console.log(chalk.cyan(`Command: ${action}`));
|
|
1185
|
+
console.log(chalk.dim('Paste in Claude Code'));
|
|
1186
|
+
console.log('');
|
|
1187
|
+
|
|
1188
|
+
// Try to copy to clipboard
|
|
1189
|
+
try {
|
|
1190
|
+
const { copyToClipboard } = await import('../panel/queue.js');
|
|
1191
|
+
if (copyToClipboard(action)) {
|
|
1192
|
+
console.log(chalk.green('✓ Copied to clipboard'));
|
|
1193
|
+
}
|
|
1194
|
+
} catch {
|
|
1195
|
+
// Clipboard not available
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
await inquirer.prompt([
|
|
1199
|
+
{ type: 'input', name: 'continue', message: 'Enter to continue...' }
|
|
1200
|
+
]);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* Mobile settings loop
|
|
1206
|
+
*/
|
|
1207
|
+
async function showMobileSettingsLoop() {
|
|
1208
|
+
while (true) {
|
|
1209
|
+
const action = await showMobileSettings();
|
|
1210
|
+
|
|
1211
|
+
if (action === 'back') {
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
const techStack = loadTechStack();
|
|
1216
|
+
|
|
1217
|
+
switch (action) {
|
|
1218
|
+
case 'github':
|
|
1219
|
+
await configureGitHub(techStack);
|
|
1220
|
+
break;
|
|
1221
|
+
case 'deployment':
|
|
1222
|
+
await configureDeployment(techStack);
|
|
1223
|
+
break;
|
|
1224
|
+
case 'tunnel':
|
|
1225
|
+
await configureTunnel(techStack);
|
|
1226
|
+
break;
|
|
1227
|
+
case 'token':
|
|
1228
|
+
await configureToken(techStack);
|
|
1229
|
+
break;
|
|
1230
|
+
case 'happy':
|
|
1231
|
+
await configureHappy(techStack);
|
|
1232
|
+
break;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mobile-Optimized Menu System for Happy CLI
|
|
3
|
+
*
|
|
4
|
+
* Renders menus optimized for mobile screens (max 40 chars).
|
|
5
|
+
* Single-column layout, no overflow, minimal decorations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import inquirer from 'inquirer';
|
|
10
|
+
import { getVersion, loadTechStack, hasValidConfig } from '../utils.js';
|
|
11
|
+
import { isHappyMode, getMobileWidth } from '../utils/happy-detect.js';
|
|
12
|
+
|
|
13
|
+
// Mobile-friendly banner (40 chars max)
|
|
14
|
+
const MOBILE_BANNER = `
|
|
15
|
+
${chalk.cyan('╔══════════════════════════════════╗')}
|
|
16
|
+
${chalk.cyan('║')} ${chalk.bold('CCASP')} ${chalk.dim('v' + getVersion().slice(0, 5))}${' '.repeat(17)}${chalk.cyan('║')}
|
|
17
|
+
${chalk.cyan('║')} ${chalk.dim('Mobile Menu')}${' '.repeat(21)}${chalk.cyan('║')}
|
|
18
|
+
${chalk.cyan('╚══════════════════════════════════╝')}`;
|
|
19
|
+
|
|
20
|
+
// Compact panel banner for mobile
|
|
21
|
+
const MOBILE_PANEL_BANNER = `
|
|
22
|
+
${chalk.cyan('╔══════════════════════════════════╗')}
|
|
23
|
+
${chalk.cyan('║')} ${chalk.bold('CCASP Panel')}${' '.repeat(20)}${chalk.cyan('║')}
|
|
24
|
+
${chalk.cyan('╚══════════════════════════════════╝')}`;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Truncate text to fit mobile width
|
|
28
|
+
* @param {string} text - Text to truncate
|
|
29
|
+
* @param {number} maxLen - Maximum length
|
|
30
|
+
* @returns {string} Truncated text
|
|
31
|
+
*/
|
|
32
|
+
function truncate(text, maxLen = 30) {
|
|
33
|
+
if (!text) return '';
|
|
34
|
+
// Strip ANSI codes for length calculation
|
|
35
|
+
const plainText = text.replace(/\x1B\[[0-9;]*m/g, '');
|
|
36
|
+
if (plainText.length <= maxLen) return text;
|
|
37
|
+
return text.slice(0, maxLen - 2) + '..';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Format a menu item for mobile (single line)
|
|
42
|
+
* @param {string} key - Shortcut key
|
|
43
|
+
* @param {string} label - Item label
|
|
44
|
+
* @returns {string} Formatted menu item
|
|
45
|
+
*/
|
|
46
|
+
function formatMobileItem(key, label) {
|
|
47
|
+
const truncLabel = truncate(label, 28);
|
|
48
|
+
return `${chalk.yellow(key + ')')} ${truncLabel}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Show mobile-optimized main menu
|
|
53
|
+
*/
|
|
54
|
+
export async function showMobileMenu() {
|
|
55
|
+
console.clear();
|
|
56
|
+
console.log(MOBILE_BANNER);
|
|
57
|
+
|
|
58
|
+
const configured = hasValidConfig();
|
|
59
|
+
|
|
60
|
+
// Status line
|
|
61
|
+
if (configured) {
|
|
62
|
+
console.log(chalk.green(' ✓ Configured'));
|
|
63
|
+
} else {
|
|
64
|
+
console.log(chalk.yellow(' ⚠ Not configured'));
|
|
65
|
+
}
|
|
66
|
+
console.log('');
|
|
67
|
+
|
|
68
|
+
// Single-column menu items
|
|
69
|
+
const choices = [
|
|
70
|
+
{ name: formatMobileItem('1', 'Create Task'), value: 'create' },
|
|
71
|
+
{ name: formatMobileItem('2', 'Decompose Issue'), value: 'decompose' },
|
|
72
|
+
{ name: formatMobileItem('3', 'Sync Tasks'), value: 'sync' },
|
|
73
|
+
new inquirer.Separator(chalk.dim('─'.repeat(34))),
|
|
74
|
+
{ name: formatMobileItem('4', 'Setup'), value: 'setup' },
|
|
75
|
+
{ name: formatMobileItem('5', 'List Tasks'), value: 'list' },
|
|
76
|
+
{ name: formatMobileItem('6', 'Install Command'), value: 'install' },
|
|
77
|
+
new inquirer.Separator(chalk.dim('─'.repeat(34))),
|
|
78
|
+
{ name: formatMobileItem('P', 'Panel (inline)'), value: 'panel-inline' },
|
|
79
|
+
{ name: formatMobileItem('T', 'Test Setup'), value: 'test-setup' },
|
|
80
|
+
{ name: formatMobileItem('A', 'Agent Creator'), value: 'agent-creator' },
|
|
81
|
+
{ name: formatMobileItem('M', 'MCP Explorer'), value: 'explore-mcp' },
|
|
82
|
+
new inquirer.Separator(chalk.dim('─'.repeat(34))),
|
|
83
|
+
{ name: formatMobileItem('S', 'Settings'), value: 'project-settings' },
|
|
84
|
+
{ name: formatMobileItem('?', 'Help'), value: 'help' },
|
|
85
|
+
{ name: formatMobileItem('Q', 'Exit'), value: 'exit' },
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const { action } = await inquirer.prompt([
|
|
89
|
+
{
|
|
90
|
+
type: 'list',
|
|
91
|
+
name: 'action',
|
|
92
|
+
message: 'Select:',
|
|
93
|
+
choices,
|
|
94
|
+
pageSize: 12,
|
|
95
|
+
},
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
return action;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Show mobile-optimized panel menu (inline, no new window)
|
|
103
|
+
*/
|
|
104
|
+
export async function showMobilePanel() {
|
|
105
|
+
console.clear();
|
|
106
|
+
console.log(MOBILE_PANEL_BANNER);
|
|
107
|
+
console.log('');
|
|
108
|
+
|
|
109
|
+
// Single-column panel items
|
|
110
|
+
const choices = [
|
|
111
|
+
new inquirer.Separator(chalk.cyan(' Agents & Skills')),
|
|
112
|
+
{ name: formatMobileItem('A', 'Create Agent'), value: '/create-agent' },
|
|
113
|
+
{ name: formatMobileItem('H', 'Create Hook'), value: '/create-hook' },
|
|
114
|
+
{ name: formatMobileItem('S', 'Create Skill'), value: '/create-skill' },
|
|
115
|
+
{ name: formatMobileItem('M', 'Explore MCP'), value: '/explore-mcp' },
|
|
116
|
+
new inquirer.Separator(chalk.dim('─'.repeat(34))),
|
|
117
|
+
new inquirer.Separator(chalk.cyan(' Resources')),
|
|
118
|
+
{ name: formatMobileItem('1', 'View Agents'), value: '/view-agents' },
|
|
119
|
+
{ name: formatMobileItem('2', 'View Skills'), value: '/view-skills' },
|
|
120
|
+
{ name: formatMobileItem('3', 'View Hooks'), value: '/view-hooks' },
|
|
121
|
+
{ name: formatMobileItem('4', 'All Commands'), value: '/INDEX' },
|
|
122
|
+
new inquirer.Separator(chalk.dim('─'.repeat(34))),
|
|
123
|
+
new inquirer.Separator(chalk.cyan(' Quick Actions')),
|
|
124
|
+
{ name: formatMobileItem('P', 'Phase Dev Plan'), value: '/phase-dev-plan' },
|
|
125
|
+
{ name: formatMobileItem('G', 'GitHub Task'), value: '/github-task' },
|
|
126
|
+
{ name: formatMobileItem('T', 'Run E2E Tests'), value: '/e2e-test' },
|
|
127
|
+
new inquirer.Separator(chalk.dim('─'.repeat(34))),
|
|
128
|
+
{ name: formatMobileItem('B', 'Back'), value: 'back' },
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
const { action } = await inquirer.prompt([
|
|
132
|
+
{
|
|
133
|
+
type: 'list',
|
|
134
|
+
name: 'action',
|
|
135
|
+
message: 'Select:',
|
|
136
|
+
choices,
|
|
137
|
+
pageSize: 15,
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
return action;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Show mobile-optimized project settings
|
|
146
|
+
*/
|
|
147
|
+
export async function showMobileSettings() {
|
|
148
|
+
console.clear();
|
|
149
|
+
console.log(chalk.cyan('╔══════════════════════════════════╗'));
|
|
150
|
+
console.log(chalk.cyan('║') + chalk.bold(' Settings') + ' '.repeat(23) + chalk.cyan('║'));
|
|
151
|
+
console.log(chalk.cyan('╚══════════════════════════════════╝'));
|
|
152
|
+
console.log('');
|
|
153
|
+
|
|
154
|
+
const choices = [
|
|
155
|
+
{ name: formatMobileItem('1', 'GitHub Board'), value: 'github' },
|
|
156
|
+
{ name: formatMobileItem('2', 'Deployment'), value: 'deployment' },
|
|
157
|
+
{ name: formatMobileItem('3', 'Tunnel'), value: 'tunnel' },
|
|
158
|
+
{ name: formatMobileItem('4', 'Token Budget'), value: 'token' },
|
|
159
|
+
{ name: formatMobileItem('5', 'Happy Mode'), value: 'happy' },
|
|
160
|
+
new inquirer.Separator(chalk.dim('─'.repeat(34))),
|
|
161
|
+
{ name: formatMobileItem('B', 'Back'), value: 'back' },
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
const { action } = await inquirer.prompt([
|
|
165
|
+
{
|
|
166
|
+
type: 'list',
|
|
167
|
+
name: 'action',
|
|
168
|
+
message: 'Select:',
|
|
169
|
+
choices,
|
|
170
|
+
pageSize: 10,
|
|
171
|
+
},
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
return action;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Show a mobile-friendly success message
|
|
179
|
+
* @param {string} message - Success message
|
|
180
|
+
*/
|
|
181
|
+
export function showMobileSuccess(message) {
|
|
182
|
+
console.log('');
|
|
183
|
+
console.log(chalk.green(`✓ ${truncate(message, 32)}`));
|
|
184
|
+
console.log('');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Show a mobile-friendly error message
|
|
189
|
+
* @param {string} message - Error message
|
|
190
|
+
*/
|
|
191
|
+
export function showMobileError(message) {
|
|
192
|
+
console.log('');
|
|
193
|
+
console.log(chalk.red(`✗ ${truncate(message, 32)}`));
|
|
194
|
+
console.log('');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Show a mobile-friendly info box
|
|
199
|
+
* @param {string} title - Box title
|
|
200
|
+
* @param {string[]} lines - Content lines
|
|
201
|
+
*/
|
|
202
|
+
export function showMobileBox(title, lines = []) {
|
|
203
|
+
const width = getMobileWidth() - 4; // Account for borders
|
|
204
|
+
console.log('');
|
|
205
|
+
console.log(chalk.cyan('┌' + '─'.repeat(width) + '┐'));
|
|
206
|
+
console.log(chalk.cyan('│') + ` ${truncate(title, width - 2)}`.padEnd(width) + chalk.cyan('│'));
|
|
207
|
+
console.log(chalk.cyan('├' + '─'.repeat(width) + '┤'));
|
|
208
|
+
for (const line of lines) {
|
|
209
|
+
console.log(chalk.cyan('│') + ` ${truncate(line, width - 2)}`.padEnd(width) + chalk.cyan('│'));
|
|
210
|
+
}
|
|
211
|
+
console.log(chalk.cyan('└' + '─'.repeat(width) + '┘'));
|
|
212
|
+
console.log('');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Prompt to return to menu (mobile version)
|
|
217
|
+
* @returns {Promise<boolean>} True if user wants to return
|
|
218
|
+
*/
|
|
219
|
+
export async function mobileReturnPrompt() {
|
|
220
|
+
console.log('');
|
|
221
|
+
const { back } = await inquirer.prompt([
|
|
222
|
+
{
|
|
223
|
+
type: 'confirm',
|
|
224
|
+
name: 'back',
|
|
225
|
+
message: 'Back to menu?',
|
|
226
|
+
default: true,
|
|
227
|
+
},
|
|
228
|
+
]);
|
|
229
|
+
return back;
|
|
230
|
+
}
|
|
@@ -7,8 +7,33 @@
|
|
|
7
7
|
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import ora from 'ora';
|
|
10
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
11
|
-
import { join } from 'path';
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from 'fs';
|
|
11
|
+
import { join, basename } from 'path';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create a backup of CLAUDE.md before modification
|
|
15
|
+
* @param {string} filePath - Path to CLAUDE.md
|
|
16
|
+
* @param {string} cwd - Working directory
|
|
17
|
+
* @returns {string|null} Backup path or null if failed
|
|
18
|
+
*/
|
|
19
|
+
function backupClaudeMd(filePath, cwd) {
|
|
20
|
+
if (!existsSync(filePath)) return null;
|
|
21
|
+
|
|
22
|
+
const backupDir = join(cwd, '.claude', 'backups');
|
|
23
|
+
if (!existsSync(backupDir)) {
|
|
24
|
+
mkdirSync(backupDir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
28
|
+
const backupPath = join(backupDir, `CLAUDE.md.${timestamp}.bak`);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
copyFileSync(filePath, backupPath);
|
|
32
|
+
return backupPath;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
12
37
|
|
|
13
38
|
/**
|
|
14
39
|
* MCP section marker
|
|
@@ -158,6 +183,12 @@ ${mcpSection}
|
|
|
158
183
|
};
|
|
159
184
|
}
|
|
160
185
|
|
|
186
|
+
// Backup existing CLAUDE.md before modification
|
|
187
|
+
const backupPath = backupClaudeMd(claudeMdPath, cwd);
|
|
188
|
+
if (backupPath) {
|
|
189
|
+
spinner.text = 'Backed up CLAUDE.md...';
|
|
190
|
+
}
|
|
191
|
+
|
|
161
192
|
// Check if MCP section exists
|
|
162
193
|
const hasStartMarker = content.includes(MCP_SECTION_START);
|
|
163
194
|
const hasEndMarker = content.includes(MCP_SECTION_END);
|
|
@@ -193,12 +224,13 @@ ${mcpSection}
|
|
|
193
224
|
}
|
|
194
225
|
|
|
195
226
|
writeFileSync(claudeMdPath, content, 'utf8');
|
|
196
|
-
spinner.succeed(
|
|
227
|
+
spinner.succeed(`Updated CLAUDE.md with MCP documentation${backupPath ? ' (backup created)' : ''}`);
|
|
197
228
|
|
|
198
229
|
return {
|
|
199
230
|
success: true,
|
|
200
231
|
action: hasStartMarker ? 'updated' : 'appended',
|
|
201
232
|
path: claudeMdPath,
|
|
233
|
+
backupPath: backupPath || null,
|
|
202
234
|
};
|
|
203
235
|
} catch (error) {
|
|
204
236
|
spinner.fail(`Failed to update CLAUDE.md: ${error.message}`);
|
|
@@ -225,6 +257,9 @@ export function removeMcpSection(cwd = process.cwd()) {
|
|
|
225
257
|
const hasEndMarker = content.includes(MCP_SECTION_END);
|
|
226
258
|
|
|
227
259
|
if (hasStartMarker && hasEndMarker) {
|
|
260
|
+
// Backup before removal
|
|
261
|
+
backupClaudeMd(claudeMdPath, cwd);
|
|
262
|
+
|
|
228
263
|
const regex = new RegExp(
|
|
229
264
|
`\\n?${MCP_SECTION_START}[\\s\\S]*?${MCP_SECTION_END}\\n?`,
|
|
230
265
|
'g'
|
package/src/commands/init.js
CHANGED
|
@@ -219,6 +219,12 @@ const AVAILABLE_COMMANDS = [
|
|
|
219
219
|
category: 'Claude Code',
|
|
220
220
|
selected: true,
|
|
221
221
|
},
|
|
222
|
+
{
|
|
223
|
+
name: 'detect-tech-stack',
|
|
224
|
+
description: 'Re-run tech stack detection and update configuration',
|
|
225
|
+
category: 'Analysis',
|
|
226
|
+
selected: true,
|
|
227
|
+
},
|
|
222
228
|
{
|
|
223
229
|
name: 'roadmap-sync',
|
|
224
230
|
description: 'Sync roadmaps with GitHub Project Board',
|
|
@@ -2795,3 +2801,86 @@ npx claude-cli-advanced-starter-pack init --force
|
|
|
2795
2801
|
|
|
2796
2802
|
return content;
|
|
2797
2803
|
}
|
|
2804
|
+
|
|
2805
|
+
/**
|
|
2806
|
+
* Verify and fix legacy installations (pre-v1.0.8)
|
|
2807
|
+
* Issue #8: Ensures update-check hook is properly configured
|
|
2808
|
+
*
|
|
2809
|
+
* @param {string} projectDir - Project directory to verify
|
|
2810
|
+
* @returns {Object} Verification result with fixes applied
|
|
2811
|
+
*/
|
|
2812
|
+
export async function verifyLegacyInstallation(projectDir = process.cwd()) {
|
|
2813
|
+
const fixes = [];
|
|
2814
|
+
const issues = [];
|
|
2815
|
+
|
|
2816
|
+
const claudeDir = join(projectDir, '.claude');
|
|
2817
|
+
const hooksDir = join(claudeDir, 'hooks');
|
|
2818
|
+
const settingsPath = join(claudeDir, 'settings.json');
|
|
2819
|
+
const updateCheckHookPath = join(hooksDir, 'ccasp-update-check.js');
|
|
2820
|
+
|
|
2821
|
+
// Check if this is a CCASP installation
|
|
2822
|
+
if (!existsSync(claudeDir)) {
|
|
2823
|
+
return { isLegacy: false, message: 'No .claude folder found' };
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
// Check 1: Does the update-check hook file exist?
|
|
2827
|
+
if (!existsSync(updateCheckHookPath)) {
|
|
2828
|
+
issues.push('Missing ccasp-update-check.js hook file');
|
|
2829
|
+
|
|
2830
|
+
// Fix: Create the hook file
|
|
2831
|
+
if (!existsSync(hooksDir)) {
|
|
2832
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
const templatePath = join(__dirname, '..', '..', 'templates', 'hooks', 'ccasp-update-check.template.js');
|
|
2836
|
+
if (existsSync(templatePath)) {
|
|
2837
|
+
const hookContent = readFileSync(templatePath, 'utf8');
|
|
2838
|
+
writeFileSync(updateCheckHookPath, hookContent, 'utf8');
|
|
2839
|
+
fixes.push('Created ccasp-update-check.js hook file');
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
// Check 2: Is the hook registered in settings.json?
|
|
2844
|
+
if (existsSync(settingsPath)) {
|
|
2845
|
+
try {
|
|
2846
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
2847
|
+
|
|
2848
|
+
// Check if UserPromptSubmit hook exists with update-check
|
|
2849
|
+
const hasUpdateHook = settings.hooks?.UserPromptSubmit?.some(
|
|
2850
|
+
(h) => h.hooks?.some((hook) => hook.command?.includes('ccasp-update-check'))
|
|
2851
|
+
);
|
|
2852
|
+
|
|
2853
|
+
if (!hasUpdateHook) {
|
|
2854
|
+
issues.push('Update-check hook not registered in settings.json');
|
|
2855
|
+
|
|
2856
|
+
// Fix: Add the hook to settings.json
|
|
2857
|
+
if (!settings.hooks) settings.hooks = {};
|
|
2858
|
+
if (!settings.hooks.UserPromptSubmit) {
|
|
2859
|
+
settings.hooks.UserPromptSubmit = [];
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
settings.hooks.UserPromptSubmit.push({
|
|
2863
|
+
matcher: '',
|
|
2864
|
+
hooks: [{
|
|
2865
|
+
type: 'command',
|
|
2866
|
+
command: 'node .claude/hooks/ccasp-update-check.js',
|
|
2867
|
+
}],
|
|
2868
|
+
});
|
|
2869
|
+
|
|
2870
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
2871
|
+
fixes.push('Registered update-check hook in settings.json');
|
|
2872
|
+
}
|
|
2873
|
+
} catch {
|
|
2874
|
+
issues.push('Could not parse settings.json');
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2878
|
+
return {
|
|
2879
|
+
isLegacy: issues.length > 0,
|
|
2880
|
+
issues,
|
|
2881
|
+
fixes,
|
|
2882
|
+
message: fixes.length > 0
|
|
2883
|
+
? `Fixed ${fixes.length} legacy installation issue(s)`
|
|
2884
|
+
: 'Installation is up to date',
|
|
2885
|
+
};
|
|
2886
|
+
}
|