claude-cli-advanced-starter-pack 1.0.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/LICENSE +21 -0
- package/OVERVIEW.md +597 -0
- package/README.md +439 -0
- package/bin/gtask.js +282 -0
- package/bin/postinstall.js +53 -0
- package/package.json +69 -0
- package/src/agents/phase-dev-templates.js +1011 -0
- package/src/agents/templates.js +668 -0
- package/src/analysis/checklist-parser.js +414 -0
- package/src/analysis/codebase.js +481 -0
- package/src/cli/menu.js +958 -0
- package/src/commands/claude-audit.js +1482 -0
- package/src/commands/claude-settings.js +2243 -0
- package/src/commands/create-agent.js +681 -0
- package/src/commands/create-command.js +337 -0
- package/src/commands/create-hook.js +262 -0
- package/src/commands/create-phase-dev/codebase-analyzer.js +813 -0
- package/src/commands/create-phase-dev/documentation-generator.js +352 -0
- package/src/commands/create-phase-dev/post-completion.js +404 -0
- package/src/commands/create-phase-dev/scale-calculator.js +344 -0
- package/src/commands/create-phase-dev/wizard.js +492 -0
- package/src/commands/create-phase-dev.js +481 -0
- package/src/commands/create-skill.js +313 -0
- package/src/commands/create.js +446 -0
- package/src/commands/decompose.js +392 -0
- package/src/commands/detect-tech-stack.js +768 -0
- package/src/commands/explore-mcp/claude-md-updater.js +252 -0
- package/src/commands/explore-mcp/mcp-installer.js +346 -0
- package/src/commands/explore-mcp/mcp-registry.js +438 -0
- package/src/commands/explore-mcp.js +638 -0
- package/src/commands/gtask-init.js +641 -0
- package/src/commands/help.js +128 -0
- package/src/commands/init.js +1890 -0
- package/src/commands/install.js +250 -0
- package/src/commands/list.js +116 -0
- package/src/commands/roadmap.js +750 -0
- package/src/commands/setup-wizard.js +482 -0
- package/src/commands/setup.js +351 -0
- package/src/commands/sync.js +534 -0
- package/src/commands/test-run.js +456 -0
- package/src/commands/test-setup.js +456 -0
- package/src/commands/validate.js +67 -0
- package/src/config/tech-stack.defaults.json +182 -0
- package/src/config/tech-stack.schema.json +502 -0
- package/src/github/client.js +359 -0
- package/src/index.js +84 -0
- package/src/templates/claude-command.js +244 -0
- package/src/templates/issue-body.js +284 -0
- package/src/testing/config.js +411 -0
- package/src/utils/template-engine.js +398 -0
- package/src/utils/validate-templates.js +223 -0
- package/src/utils.js +396 -0
- package/templates/commands/ccasp-setup.template.md +113 -0
- package/templates/commands/context-audit.template.md +97 -0
- package/templates/commands/create-task-list.template.md +382 -0
- package/templates/commands/deploy-full.template.md +261 -0
- package/templates/commands/github-task-start.template.md +99 -0
- package/templates/commands/github-update.template.md +69 -0
- package/templates/commands/happy-start.template.md +117 -0
- package/templates/commands/phase-track.template.md +142 -0
- package/templates/commands/tunnel-start.template.md +127 -0
- package/templates/commands/tunnel-stop.template.md +106 -0
- package/templates/hooks/context-guardian.template.js +173 -0
- package/templates/hooks/deployment-orchestrator.template.js +219 -0
- package/templates/hooks/github-progress-hook.template.js +197 -0
- package/templates/hooks/happy-checkpoint-manager.template.js +222 -0
- package/templates/hooks/phase-dev-enforcer.template.js +183 -0
package/src/cli/menu.js
ADDED
|
@@ -0,0 +1,958 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive ASCII Menu System
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import { runSetup } from '../commands/setup.js';
|
|
8
|
+
import { runCreate } from '../commands/create.js';
|
|
9
|
+
import { runList } from '../commands/list.js';
|
|
10
|
+
import { runInstall } from '../commands/install.js';
|
|
11
|
+
import { runDecompose } from '../commands/decompose.js';
|
|
12
|
+
import { runSync } from '../commands/sync.js';
|
|
13
|
+
import { runTestSetup } from '../commands/test-setup.js';
|
|
14
|
+
import { runTest } from '../commands/test-run.js';
|
|
15
|
+
import { runCreateAgent } from '../commands/create-agent.js';
|
|
16
|
+
import { runClaudeSettings } from '../commands/claude-settings.js';
|
|
17
|
+
import { runCreatePhaseDev, showPhasDevMainMenu } from '../commands/create-phase-dev.js';
|
|
18
|
+
import { runExploreMcp, showExploreMcpMenu } from '../commands/explore-mcp.js';
|
|
19
|
+
import { runClaudeAudit, showClaudeAuditMenu } from '../commands/claude-audit.js';
|
|
20
|
+
import { runRoadmap, showRoadmapMenu } from '../commands/roadmap.js';
|
|
21
|
+
import { hasTestingConfig } from '../testing/config.js';
|
|
22
|
+
import { showHelp } from '../commands/help.js';
|
|
23
|
+
import { hasValidConfig, getVersion, loadTechStack, saveTechStack } from '../utils.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* ASCII Art Banner
|
|
27
|
+
*/
|
|
28
|
+
const BANNER = `
|
|
29
|
+
╔════════════════════════════════════════════════════════════════════════════╗
|
|
30
|
+
║ ║
|
|
31
|
+
║ ╔═╗╦ ╔═╗╦ ╦╔╦╗╔═╗ ╔═╗╔╦╗╦ ╦╔═╗╔╗╔╔═╗╔═╗╔╦╗ ╔═╗╔╦╗╔═╗╦═╗╔╦╗╔═╗╦═╗ ║
|
|
32
|
+
║ ║ ║ ╠═╣║ ║ ║║║╣ ╠═╣ ║║╚╗╔╝╠═╣║║║║ ║╣ ║║ ╚═╗ ║ ╠═╣╠╦╝ ║ ║╣ ╠╦╝ ║
|
|
33
|
+
║ ╚═╝╩═╝╩ ╩╚═╝═╩╝╚═╝ ╩ ╩═╩╝ ╚╝ ╩ ╩╝╚╝╚═╝╚═╝═╩╝ ╚═╝ ╩ ╩ ╩╩╚═ ╩ ╚═╝╩╚═ ║
|
|
34
|
+
║ ║
|
|
35
|
+
║ Advanced Claude Code CLI Toolkit - Agents, MCP, GitHub & More ║
|
|
36
|
+
║ ║
|
|
37
|
+
╚════════════════════════════════════════════════════════════════════════════╝`;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Show Project Settings submenu
|
|
41
|
+
*/
|
|
42
|
+
export async function showProjectSettingsMenu() {
|
|
43
|
+
console.log('');
|
|
44
|
+
console.log(chalk.cyan('╔═══════════════════════════════════════════════════════════════════════════════╗'));
|
|
45
|
+
console.log(chalk.cyan('║') + chalk.bold(' PROJECT CONFIGURATION ') + chalk.cyan('║'));
|
|
46
|
+
console.log(chalk.cyan('╠═══════════════════════════════════════════════════════════════════════════════╣'));
|
|
47
|
+
console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
|
|
48
|
+
console.log(chalk.cyan('║') + ' [1] GitHub Project Board ' + chalk.cyan('║'));
|
|
49
|
+
console.log(chalk.cyan('║') + chalk.dim(' └─ Connect to GitHub Projects v2 for issue tracking ') + chalk.cyan('║'));
|
|
50
|
+
console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
|
|
51
|
+
console.log(chalk.cyan('║') + ' [2] Deployment Platforms ' + chalk.cyan('║'));
|
|
52
|
+
console.log(chalk.cyan('║') + chalk.dim(' └─ Configure Railway, Cloudflare, Vercel, or self-hosted ') + chalk.cyan('║'));
|
|
53
|
+
console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
|
|
54
|
+
console.log(chalk.cyan('║') + ' [3] Tunnel Services ' + chalk.cyan('║'));
|
|
55
|
+
console.log(chalk.cyan('║') + chalk.dim(' └─ Set up ngrok, localtunnel, or cloudflare-tunnel ') + chalk.cyan('║'));
|
|
56
|
+
console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
|
|
57
|
+
console.log(chalk.cyan('║') + ' [4] Token Management ' + chalk.cyan('║'));
|
|
58
|
+
console.log(chalk.cyan('║') + chalk.dim(' └─ Customize daily budget and thresholds ') + chalk.cyan('║'));
|
|
59
|
+
console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
|
|
60
|
+
console.log(chalk.cyan('║') + ' [5] Happy Mode ' + chalk.cyan('║'));
|
|
61
|
+
console.log(chalk.cyan('║') + chalk.dim(' └─ Configure mobile app integration ') + chalk.cyan('║'));
|
|
62
|
+
console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
|
|
63
|
+
console.log(chalk.cyan('║') + ' [B] Back to main menu ' + chalk.cyan('║'));
|
|
64
|
+
console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
|
|
65
|
+
console.log(chalk.cyan('╚═══════════════════════════════════════════════════════════════════════════════╝'));
|
|
66
|
+
console.log('');
|
|
67
|
+
|
|
68
|
+
const { settingsAction } = await inquirer.prompt([
|
|
69
|
+
{
|
|
70
|
+
type: 'list',
|
|
71
|
+
name: 'settingsAction',
|
|
72
|
+
message: 'Select a configuration area:',
|
|
73
|
+
choices: [
|
|
74
|
+
{ name: '1. GitHub Project Board', value: 'github' },
|
|
75
|
+
{ name: '2. Deployment Platforms', value: 'deployment' },
|
|
76
|
+
{ name: '3. Tunnel Services', value: 'tunnel' },
|
|
77
|
+
{ name: '4. Token Management', value: 'token' },
|
|
78
|
+
{ name: '5. Happy Mode', value: 'happy' },
|
|
79
|
+
new inquirer.Separator(),
|
|
80
|
+
{ name: 'Back to main menu', value: 'back' },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
if (settingsAction === 'back') {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Load current tech-stack.json
|
|
90
|
+
const techStack = loadTechStack();
|
|
91
|
+
|
|
92
|
+
switch (settingsAction) {
|
|
93
|
+
case 'github':
|
|
94
|
+
await configureGitHub(techStack);
|
|
95
|
+
break;
|
|
96
|
+
case 'deployment':
|
|
97
|
+
await configureDeployment(techStack);
|
|
98
|
+
break;
|
|
99
|
+
case 'tunnel':
|
|
100
|
+
await configureTunnel(techStack);
|
|
101
|
+
break;
|
|
102
|
+
case 'token':
|
|
103
|
+
await configureToken(techStack);
|
|
104
|
+
break;
|
|
105
|
+
case 'happy':
|
|
106
|
+
await configureHappy(techStack);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Show submenu again
|
|
111
|
+
await showProjectSettingsMenu();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Configure GitHub Project Board
|
|
116
|
+
*/
|
|
117
|
+
async function configureGitHub(techStack) {
|
|
118
|
+
console.log('');
|
|
119
|
+
showHeader('GitHub Project Board Configuration');
|
|
120
|
+
|
|
121
|
+
const current = techStack.versionControl || {};
|
|
122
|
+
const currentBoard = current.projectBoard || {};
|
|
123
|
+
|
|
124
|
+
console.log(chalk.dim(' Current settings:'));
|
|
125
|
+
console.log(chalk.dim(` Provider: ${current.provider || 'not set'}`));
|
|
126
|
+
console.log(chalk.dim(` Owner: ${current.owner || 'not set'}`));
|
|
127
|
+
console.log(chalk.dim(` Repo: ${current.repo || 'not set'}`));
|
|
128
|
+
console.log(chalk.dim(` Project #: ${currentBoard.number || 'not set'}`));
|
|
129
|
+
console.log('');
|
|
130
|
+
|
|
131
|
+
const answers = await inquirer.prompt([
|
|
132
|
+
{
|
|
133
|
+
type: 'input',
|
|
134
|
+
name: 'owner',
|
|
135
|
+
message: 'GitHub owner (username or org):',
|
|
136
|
+
default: current.owner || '',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: 'input',
|
|
140
|
+
name: 'repo',
|
|
141
|
+
message: 'Repository name:',
|
|
142
|
+
default: current.repo || '',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
type: 'number',
|
|
146
|
+
name: 'projectNumber',
|
|
147
|
+
message: 'GitHub Project number (from URL):',
|
|
148
|
+
default: currentBoard.number || null,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
type: 'input',
|
|
152
|
+
name: 'defaultBranch',
|
|
153
|
+
message: 'Default branch:',
|
|
154
|
+
default: current.defaultBranch || 'main',
|
|
155
|
+
},
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
// Update tech-stack
|
|
159
|
+
techStack.versionControl = {
|
|
160
|
+
...current,
|
|
161
|
+
provider: 'github',
|
|
162
|
+
owner: answers.owner,
|
|
163
|
+
repo: answers.repo,
|
|
164
|
+
defaultBranch: answers.defaultBranch,
|
|
165
|
+
projectBoard: {
|
|
166
|
+
type: 'github-projects',
|
|
167
|
+
number: answers.projectNumber,
|
|
168
|
+
url: answers.owner && answers.projectNumber
|
|
169
|
+
? `https://github.com/users/${answers.owner}/projects/${answers.projectNumber}`
|
|
170
|
+
: null,
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Remove from pending configuration
|
|
175
|
+
if (techStack._pendingConfiguration) {
|
|
176
|
+
techStack._pendingConfiguration = techStack._pendingConfiguration.filter((f) => f !== 'githubIntegration');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
saveTechStack(techStack);
|
|
180
|
+
showSuccess('GitHub configuration saved!');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Configure Deployment Platforms
|
|
185
|
+
*/
|
|
186
|
+
async function configureDeployment(techStack) {
|
|
187
|
+
console.log('');
|
|
188
|
+
showHeader('Deployment Platform Configuration');
|
|
189
|
+
|
|
190
|
+
const { target } = await inquirer.prompt([
|
|
191
|
+
{
|
|
192
|
+
type: 'list',
|
|
193
|
+
name: 'target',
|
|
194
|
+
message: 'Which deployment target do you want to configure?',
|
|
195
|
+
choices: [
|
|
196
|
+
{ name: 'Frontend (static sites, SPAs)', value: 'frontend' },
|
|
197
|
+
{ name: 'Backend (APIs, servers)', value: 'backend' },
|
|
198
|
+
{ name: 'Both', value: 'both' },
|
|
199
|
+
{ name: 'Back', value: 'back' },
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
if (target === 'back') return;
|
|
205
|
+
|
|
206
|
+
if (target === 'frontend' || target === 'both') {
|
|
207
|
+
const frontendAnswers = await inquirer.prompt([
|
|
208
|
+
{
|
|
209
|
+
type: 'list',
|
|
210
|
+
name: 'platform',
|
|
211
|
+
message: 'Frontend deployment platform:',
|
|
212
|
+
choices: [
|
|
213
|
+
{ name: 'Cloudflare Pages', value: 'cloudflare' },
|
|
214
|
+
{ name: 'Vercel', value: 'vercel' },
|
|
215
|
+
{ name: 'Netlify', value: 'netlify' },
|
|
216
|
+
{ name: 'GitHub Pages', value: 'github-pages' },
|
|
217
|
+
{ name: 'Railway', value: 'railway' },
|
|
218
|
+
{ name: 'Self-hosted', value: 'self-hosted' },
|
|
219
|
+
{ name: 'None / Skip', value: 'none' },
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
type: 'input',
|
|
224
|
+
name: 'projectName',
|
|
225
|
+
message: 'Project name (for deployment commands):',
|
|
226
|
+
when: (ans) => ans.platform !== 'none',
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
type: 'input',
|
|
230
|
+
name: 'productionUrl',
|
|
231
|
+
message: 'Production URL:',
|
|
232
|
+
when: (ans) => ans.platform !== 'none',
|
|
233
|
+
},
|
|
234
|
+
]);
|
|
235
|
+
|
|
236
|
+
techStack.deployment = techStack.deployment || {};
|
|
237
|
+
techStack.deployment.frontend = {
|
|
238
|
+
platform: frontendAnswers.platform,
|
|
239
|
+
projectName: frontendAnswers.projectName || null,
|
|
240
|
+
productionUrl: frontendAnswers.productionUrl || null,
|
|
241
|
+
deployCommand: getDeployCommand('frontend', frontendAnswers),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (target === 'backend' || target === 'both') {
|
|
246
|
+
const backendAnswers = await inquirer.prompt([
|
|
247
|
+
{
|
|
248
|
+
type: 'list',
|
|
249
|
+
name: 'platform',
|
|
250
|
+
message: 'Backend deployment platform:',
|
|
251
|
+
choices: [
|
|
252
|
+
{ name: 'Railway', value: 'railway' },
|
|
253
|
+
{ name: 'Heroku', value: 'heroku' },
|
|
254
|
+
{ name: 'Render', value: 'render' },
|
|
255
|
+
{ name: 'Fly.io', value: 'fly' },
|
|
256
|
+
{ name: 'Vercel', value: 'vercel' },
|
|
257
|
+
{ name: 'DigitalOcean', value: 'digitalocean' },
|
|
258
|
+
{ name: 'Self-hosted (SSH)', value: 'self-hosted' },
|
|
259
|
+
{ name: 'None / Skip', value: 'none' },
|
|
260
|
+
],
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
type: 'input',
|
|
264
|
+
name: 'projectId',
|
|
265
|
+
message: 'Project ID (from platform dashboard):',
|
|
266
|
+
when: (ans) => ['railway', 'render', 'fly'].includes(ans.platform),
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
type: 'input',
|
|
270
|
+
name: 'serviceId',
|
|
271
|
+
message: 'Service ID:',
|
|
272
|
+
when: (ans) => ans.platform === 'railway',
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
type: 'input',
|
|
276
|
+
name: 'environmentId',
|
|
277
|
+
message: 'Environment ID:',
|
|
278
|
+
when: (ans) => ans.platform === 'railway',
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
type: 'input',
|
|
282
|
+
name: 'productionUrl',
|
|
283
|
+
message: 'Production URL:',
|
|
284
|
+
when: (ans) => ans.platform !== 'none',
|
|
285
|
+
},
|
|
286
|
+
]);
|
|
287
|
+
|
|
288
|
+
// Handle self-hosted config
|
|
289
|
+
let selfHostedConfig = null;
|
|
290
|
+
if (backendAnswers.platform === 'self-hosted') {
|
|
291
|
+
const sshAnswers = await inquirer.prompt([
|
|
292
|
+
{ type: 'input', name: 'sshUser', message: 'SSH username:' },
|
|
293
|
+
{ type: 'input', name: 'sshHost', message: 'SSH host:' },
|
|
294
|
+
{ type: 'number', name: 'sshPort', message: 'SSH port:', default: 22 },
|
|
295
|
+
{ type: 'input', name: 'appPath', message: 'App path on server:' },
|
|
296
|
+
{ type: 'input', name: 'deployScript', message: 'Deploy script/command:', default: './deploy.sh' },
|
|
297
|
+
]);
|
|
298
|
+
selfHostedConfig = sshAnswers;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
techStack.deployment = techStack.deployment || {};
|
|
302
|
+
techStack.deployment.backend = {
|
|
303
|
+
platform: backendAnswers.platform,
|
|
304
|
+
projectId: backendAnswers.projectId || null,
|
|
305
|
+
serviceId: backendAnswers.serviceId || null,
|
|
306
|
+
environmentId: backendAnswers.environmentId || null,
|
|
307
|
+
productionUrl: backendAnswers.productionUrl || null,
|
|
308
|
+
selfHostedConfig,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Remove from pending configuration
|
|
313
|
+
if (techStack._pendingConfiguration) {
|
|
314
|
+
techStack._pendingConfiguration = techStack._pendingConfiguration.filter((f) => f !== 'deploymentAutomation');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
saveTechStack(techStack);
|
|
318
|
+
showSuccess('Deployment configuration saved!');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Get deploy command based on platform
|
|
323
|
+
*/
|
|
324
|
+
function getDeployCommand(type, answers) {
|
|
325
|
+
if (type === 'frontend') {
|
|
326
|
+
switch (answers.platform) {
|
|
327
|
+
case 'cloudflare':
|
|
328
|
+
return `npx wrangler pages deploy dist --project-name=${answers.projectName || '{{PROJECT_NAME}}'}`;
|
|
329
|
+
case 'vercel':
|
|
330
|
+
return 'vercel --prod';
|
|
331
|
+
case 'netlify':
|
|
332
|
+
return 'netlify deploy --prod';
|
|
333
|
+
case 'github-pages':
|
|
334
|
+
return 'npm run deploy'; // Typically uses gh-pages package
|
|
335
|
+
default:
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Configure Tunnel Services
|
|
344
|
+
*/
|
|
345
|
+
async function configureTunnel(techStack) {
|
|
346
|
+
console.log('');
|
|
347
|
+
showHeader('Tunnel Service Configuration');
|
|
348
|
+
|
|
349
|
+
console.log(chalk.dim(' Tunnel services expose your local development server to the internet.'));
|
|
350
|
+
console.log(chalk.dim(' Useful for mobile testing, webhooks, or sharing work-in-progress.\n'));
|
|
351
|
+
|
|
352
|
+
const current = techStack.devEnvironment?.tunnel || {};
|
|
353
|
+
|
|
354
|
+
const answers = await inquirer.prompt([
|
|
355
|
+
{
|
|
356
|
+
type: 'list',
|
|
357
|
+
name: 'service',
|
|
358
|
+
message: 'Which tunnel service would you like to use?',
|
|
359
|
+
choices: [
|
|
360
|
+
{ name: 'None - I don\'t need tunnel access', value: 'none' },
|
|
361
|
+
{ name: 'ngrok - Popular tunneling service (requires ngrok CLI)', value: 'ngrok' },
|
|
362
|
+
{ name: 'localtunnel - Free, no signup required', value: 'localtunnel' },
|
|
363
|
+
{ name: 'cloudflare-tunnel - Enterprise-grade (requires CF account)', value: 'cloudflare-tunnel' },
|
|
364
|
+
{ name: 'serveo - Free SSH-based tunneling', value: 'serveo' },
|
|
365
|
+
{ name: 'Other - I\'ll configure manually', value: 'other' },
|
|
366
|
+
],
|
|
367
|
+
default: current.service || 'none',
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
type: 'input',
|
|
371
|
+
name: 'subdomain',
|
|
372
|
+
message: 'Reserved subdomain (optional):',
|
|
373
|
+
when: (ans) => ['ngrok', 'localtunnel'].includes(ans.service),
|
|
374
|
+
default: current.subdomain || '',
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
type: 'number',
|
|
378
|
+
name: 'adminPort',
|
|
379
|
+
message: 'Admin/API port (for ngrok):',
|
|
380
|
+
when: (ans) => ans.service === 'ngrok',
|
|
381
|
+
default: current.adminPort || 4040,
|
|
382
|
+
},
|
|
383
|
+
]);
|
|
384
|
+
|
|
385
|
+
// Build start command based on service
|
|
386
|
+
let startCommand = null;
|
|
387
|
+
const frontendPort = techStack.frontend?.port || 5173;
|
|
388
|
+
|
|
389
|
+
switch (answers.service) {
|
|
390
|
+
case 'ngrok':
|
|
391
|
+
startCommand = answers.subdomain
|
|
392
|
+
? `ngrok http ${frontendPort} --subdomain=${answers.subdomain}`
|
|
393
|
+
: `ngrok http ${frontendPort}`;
|
|
394
|
+
break;
|
|
395
|
+
case 'localtunnel':
|
|
396
|
+
startCommand = answers.subdomain
|
|
397
|
+
? `lt --port ${frontendPort} --subdomain ${answers.subdomain}`
|
|
398
|
+
: `lt --port ${frontendPort}`;
|
|
399
|
+
break;
|
|
400
|
+
case 'cloudflare-tunnel':
|
|
401
|
+
startCommand = `cloudflared tunnel --url http://localhost:${frontendPort}`;
|
|
402
|
+
break;
|
|
403
|
+
case 'serveo':
|
|
404
|
+
startCommand = `ssh -R 80:localhost:${frontendPort} serveo.net`;
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
techStack.devEnvironment = techStack.devEnvironment || {};
|
|
409
|
+
techStack.devEnvironment.tunnel = {
|
|
410
|
+
service: answers.service,
|
|
411
|
+
subdomain: answers.subdomain || null,
|
|
412
|
+
url: null, // Set when tunnel is running
|
|
413
|
+
startCommand,
|
|
414
|
+
adminPort: answers.adminPort || null,
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// Remove from pending configuration
|
|
418
|
+
if (techStack._pendingConfiguration) {
|
|
419
|
+
techStack._pendingConfiguration = techStack._pendingConfiguration.filter((f) => f !== 'tunnelServices');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
saveTechStack(techStack);
|
|
423
|
+
showSuccess('Tunnel configuration saved!', [
|
|
424
|
+
'',
|
|
425
|
+
answers.service !== 'none' ? `Start command: ${startCommand}` : 'Tunnel disabled',
|
|
426
|
+
]);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Configure Token Management
|
|
431
|
+
*/
|
|
432
|
+
async function configureToken(techStack) {
|
|
433
|
+
console.log('');
|
|
434
|
+
showHeader('Token Budget Management Configuration');
|
|
435
|
+
|
|
436
|
+
const current = techStack.tokenManagement || {};
|
|
437
|
+
const thresholds = current.thresholds || {};
|
|
438
|
+
|
|
439
|
+
const answers = await inquirer.prompt([
|
|
440
|
+
{
|
|
441
|
+
type: 'confirm',
|
|
442
|
+
name: 'enabled',
|
|
443
|
+
message: 'Enable token budget tracking?',
|
|
444
|
+
default: current.enabled || false,
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
type: 'number',
|
|
448
|
+
name: 'dailyBudget',
|
|
449
|
+
message: 'Daily token budget:',
|
|
450
|
+
when: (ans) => ans.enabled,
|
|
451
|
+
default: current.dailyBudget || 200000,
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
type: 'number',
|
|
455
|
+
name: 'compactThreshold',
|
|
456
|
+
message: 'Compact suggestion threshold (0-1):',
|
|
457
|
+
when: (ans) => ans.enabled,
|
|
458
|
+
default: thresholds.compact || 0.75,
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
type: 'number',
|
|
462
|
+
name: 'archiveThreshold',
|
|
463
|
+
message: 'Archive suggestion threshold (0-1):',
|
|
464
|
+
when: (ans) => ans.enabled,
|
|
465
|
+
default: thresholds.archive || 0.85,
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
type: 'number',
|
|
469
|
+
name: 'respawnThreshold',
|
|
470
|
+
message: 'Force respawn threshold (0-1):',
|
|
471
|
+
when: (ans) => ans.enabled,
|
|
472
|
+
default: thresholds.respawn || 0.90,
|
|
473
|
+
},
|
|
474
|
+
]);
|
|
475
|
+
|
|
476
|
+
techStack.tokenManagement = {
|
|
477
|
+
enabled: answers.enabled,
|
|
478
|
+
dailyBudget: answers.dailyBudget || 200000,
|
|
479
|
+
thresholds: answers.enabled ? {
|
|
480
|
+
compact: answers.compactThreshold,
|
|
481
|
+
archive: answers.archiveThreshold,
|
|
482
|
+
respawn: answers.respawnThreshold,
|
|
483
|
+
} : thresholds,
|
|
484
|
+
trackingFile: current.trackingFile || '.claude/hooks/cache/token-usage.json',
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
saveTechStack(techStack);
|
|
488
|
+
showSuccess('Token management configuration saved!');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Configure Happy Mode
|
|
493
|
+
*/
|
|
494
|
+
async function configureHappy(techStack) {
|
|
495
|
+
console.log('');
|
|
496
|
+
showHeader('Happy Mode Configuration');
|
|
497
|
+
|
|
498
|
+
console.log(chalk.dim(' Happy Mode enables integration with the Happy Coder mobile app'));
|
|
499
|
+
console.log(chalk.dim(' for remote session control, checkpoints, and mobile-optimized responses.\n'));
|
|
500
|
+
|
|
501
|
+
const current = techStack.happyMode || {};
|
|
502
|
+
const notifications = current.notifications || {};
|
|
503
|
+
|
|
504
|
+
const answers = await inquirer.prompt([
|
|
505
|
+
{
|
|
506
|
+
type: 'confirm',
|
|
507
|
+
name: 'enabled',
|
|
508
|
+
message: 'Enable Happy Mode integration?',
|
|
509
|
+
default: current.enabled || false,
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
type: 'input',
|
|
513
|
+
name: 'dashboardUrl',
|
|
514
|
+
message: 'Dashboard URL (if self-hosted, leave blank for cloud):',
|
|
515
|
+
when: (ans) => ans.enabled,
|
|
516
|
+
default: current.dashboardUrl || '',
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
type: 'number',
|
|
520
|
+
name: 'checkpointInterval',
|
|
521
|
+
message: 'Checkpoint interval (minutes):',
|
|
522
|
+
when: (ans) => ans.enabled,
|
|
523
|
+
default: current.checkpointInterval || 10,
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
type: 'list',
|
|
527
|
+
name: 'verbosity',
|
|
528
|
+
message: 'Response verbosity for mobile:',
|
|
529
|
+
when: (ans) => ans.enabled,
|
|
530
|
+
choices: [
|
|
531
|
+
{ name: 'Full - Complete responses', value: 'full' },
|
|
532
|
+
{ name: 'Condensed - Summarized responses (recommended)', value: 'condensed' },
|
|
533
|
+
{ name: 'Minimal - Bare essentials only', value: 'minimal' },
|
|
534
|
+
],
|
|
535
|
+
default: current.verbosity || 'condensed',
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
type: 'confirm',
|
|
539
|
+
name: 'notifyTaskComplete',
|
|
540
|
+
message: 'Notify on task completion?',
|
|
541
|
+
when: (ans) => ans.enabled,
|
|
542
|
+
default: notifications.onTaskComplete !== false,
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
type: 'confirm',
|
|
546
|
+
name: 'notifyError',
|
|
547
|
+
message: 'Notify on errors?',
|
|
548
|
+
when: (ans) => ans.enabled,
|
|
549
|
+
default: notifications.onError !== false,
|
|
550
|
+
},
|
|
551
|
+
]);
|
|
552
|
+
|
|
553
|
+
techStack.happyMode = {
|
|
554
|
+
enabled: answers.enabled,
|
|
555
|
+
dashboardUrl: answers.dashboardUrl || null,
|
|
556
|
+
checkpointInterval: answers.checkpointInterval || 10,
|
|
557
|
+
verbosity: answers.verbosity || 'condensed',
|
|
558
|
+
notifications: answers.enabled ? {
|
|
559
|
+
onTaskComplete: answers.notifyTaskComplete,
|
|
560
|
+
onError: answers.notifyError,
|
|
561
|
+
onCheckpoint: false,
|
|
562
|
+
} : notifications,
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
// Remove from pending configuration
|
|
566
|
+
if (techStack._pendingConfiguration) {
|
|
567
|
+
techStack._pendingConfiguration = techStack._pendingConfiguration.filter((f) => f !== 'happyMode');
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
saveTechStack(techStack);
|
|
571
|
+
showSuccess('Happy Mode configuration saved!');
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Show main interactive menu
|
|
576
|
+
*/
|
|
577
|
+
export async function showMainMenu() {
|
|
578
|
+
console.clear();
|
|
579
|
+
console.log(chalk.cyan(BANNER));
|
|
580
|
+
console.log('');
|
|
581
|
+
|
|
582
|
+
const configured = hasValidConfig();
|
|
583
|
+
|
|
584
|
+
// Show config status
|
|
585
|
+
if (configured) {
|
|
586
|
+
console.log(chalk.green(' ✓ Configuration loaded'));
|
|
587
|
+
} else {
|
|
588
|
+
console.log(chalk.yellow(' ⚠ Not configured - run Setup first'));
|
|
589
|
+
}
|
|
590
|
+
console.log(chalk.dim(` v${getVersion()}`));
|
|
591
|
+
console.log('');
|
|
592
|
+
|
|
593
|
+
const choices = [
|
|
594
|
+
{
|
|
595
|
+
name: `${chalk.green('1)')} ${chalk.bold('Create New Task')} Create issue with codebase analysis`,
|
|
596
|
+
value: 'create',
|
|
597
|
+
short: 'Create Task',
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
name: `${chalk.cyan('2)')} ${chalk.bold('Decompose Issue')} Break down issue into tasks`,
|
|
601
|
+
value: 'decompose',
|
|
602
|
+
short: 'Decompose',
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
name: `${chalk.magenta('3)')} ${chalk.bold('Sync Tasks')} Sync progress with GitHub`,
|
|
606
|
+
value: 'sync',
|
|
607
|
+
short: 'Sync',
|
|
608
|
+
},
|
|
609
|
+
new inquirer.Separator(),
|
|
610
|
+
{
|
|
611
|
+
name: `${chalk.blue('4)')} ${chalk.bold('Setup / Configure')} Connect to your GitHub project`,
|
|
612
|
+
value: 'setup',
|
|
613
|
+
short: 'Setup',
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
name: `${chalk.dim('5)')} ${chalk.bold('List Recent Tasks')} View issues you\'ve created`,
|
|
617
|
+
value: 'list',
|
|
618
|
+
short: 'List',
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
name: `${chalk.yellow('6)')} ${chalk.bold('Install Claude Command')} Add to .claude/commands/`,
|
|
622
|
+
value: 'install',
|
|
623
|
+
short: 'Install',
|
|
624
|
+
},
|
|
625
|
+
new inquirer.Separator(),
|
|
626
|
+
{
|
|
627
|
+
name: `${chalk.red('T)')} ${chalk.bold('Testing Setup')} Configure testing mode & credentials`,
|
|
628
|
+
value: 'test-setup',
|
|
629
|
+
short: 'Test Setup',
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
name: `${chalk.red('R)')} ${chalk.bold('Run Tests')} Run tests (Ralph Loop / Manual / Watch)`,
|
|
633
|
+
value: 'test-run',
|
|
634
|
+
short: 'Run Tests',
|
|
635
|
+
},
|
|
636
|
+
new inquirer.Separator(),
|
|
637
|
+
{
|
|
638
|
+
name: `${chalk.magenta('A)')} ${chalk.bold('Agent Creator')} Create agents, hooks, skills, commands`,
|
|
639
|
+
value: 'agent-creator',
|
|
640
|
+
short: 'Agent Creator',
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
name: `${chalk.blue('C)')} ${chalk.bold('Claude Settings')} Permission modes, agent-only launcher`,
|
|
644
|
+
value: 'claude-settings',
|
|
645
|
+
short: 'Claude Settings',
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
name: `${chalk.yellow('P)')} ${chalk.bold('Phase Dev Plan')} Create phased development plan (95%+)`,
|
|
649
|
+
value: 'phase-dev',
|
|
650
|
+
short: 'Phase Dev',
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
name: `${chalk.green('M)')} ${chalk.bold('MCP Explorer')} Discover & install MCP servers`,
|
|
654
|
+
value: 'explore-mcp',
|
|
655
|
+
short: 'MCP Explorer',
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
name: `${chalk.cyan('V)')} ${chalk.bold('Claude Audit')} Verify CLAUDE.md & .claude/ best practices`,
|
|
659
|
+
value: 'claude-audit',
|
|
660
|
+
short: 'Claude Audit',
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
name: `${chalk.magenta('R)')} ${chalk.bold('Roadmap Integration')} Sync roadmaps with GitHub Project Board`,
|
|
664
|
+
value: 'roadmap',
|
|
665
|
+
short: 'Roadmap',
|
|
666
|
+
},
|
|
667
|
+
new inquirer.Separator(),
|
|
668
|
+
{
|
|
669
|
+
name: `${chalk.yellow('S)')} ${chalk.bold('Project Settings')} Configure deployment, GitHub, tunnels`,
|
|
670
|
+
value: 'project-settings',
|
|
671
|
+
short: 'Settings',
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
name: `${chalk.dim('7)')} ${chalk.bold('Help & Examples')} Documentation and examples`,
|
|
675
|
+
value: 'help',
|
|
676
|
+
short: 'Help',
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
name: `${chalk.dim('Q)')} ${chalk.bold('Exit')}`,
|
|
680
|
+
value: 'exit',
|
|
681
|
+
short: 'Exit',
|
|
682
|
+
},
|
|
683
|
+
];
|
|
684
|
+
|
|
685
|
+
const { action } = await inquirer.prompt([
|
|
686
|
+
{
|
|
687
|
+
type: 'list',
|
|
688
|
+
name: 'action',
|
|
689
|
+
message: 'Select an option:',
|
|
690
|
+
choices,
|
|
691
|
+
pageSize: 10,
|
|
692
|
+
},
|
|
693
|
+
]);
|
|
694
|
+
|
|
695
|
+
console.log('');
|
|
696
|
+
|
|
697
|
+
switch (action) {
|
|
698
|
+
case 'create':
|
|
699
|
+
if (!configured) {
|
|
700
|
+
console.log(
|
|
701
|
+
chalk.yellow('You need to configure your GitHub project first.')
|
|
702
|
+
);
|
|
703
|
+
console.log('');
|
|
704
|
+
const { proceed } = await inquirer.prompt([
|
|
705
|
+
{
|
|
706
|
+
type: 'confirm',
|
|
707
|
+
name: 'proceed',
|
|
708
|
+
message: 'Would you like to run setup now?',
|
|
709
|
+
default: true,
|
|
710
|
+
},
|
|
711
|
+
]);
|
|
712
|
+
if (proceed) {
|
|
713
|
+
await runSetup({});
|
|
714
|
+
// After setup, return to menu
|
|
715
|
+
await showMainMenu();
|
|
716
|
+
}
|
|
717
|
+
} else {
|
|
718
|
+
await runCreate({});
|
|
719
|
+
await returnToMenu();
|
|
720
|
+
}
|
|
721
|
+
break;
|
|
722
|
+
|
|
723
|
+
case 'decompose':
|
|
724
|
+
if (!configured) {
|
|
725
|
+
console.log(chalk.yellow('You need to configure your GitHub project first.'));
|
|
726
|
+
await returnToMenu();
|
|
727
|
+
} else {
|
|
728
|
+
await runDecompose({});
|
|
729
|
+
await returnToMenu();
|
|
730
|
+
}
|
|
731
|
+
break;
|
|
732
|
+
|
|
733
|
+
case 'sync':
|
|
734
|
+
if (!configured) {
|
|
735
|
+
console.log(chalk.yellow('You need to configure your GitHub project first.'));
|
|
736
|
+
await returnToMenu();
|
|
737
|
+
} else {
|
|
738
|
+
// Show sync submenu
|
|
739
|
+
const { syncAction } = await inquirer.prompt([
|
|
740
|
+
{
|
|
741
|
+
type: 'list',
|
|
742
|
+
name: 'syncAction',
|
|
743
|
+
message: 'Sync action:',
|
|
744
|
+
choices: [
|
|
745
|
+
{ name: 'Status - Show tracked issues', value: 'status' },
|
|
746
|
+
{ name: 'Pull - Fetch from GitHub', value: 'pull' },
|
|
747
|
+
{ name: 'Push - Update GitHub', value: 'push' },
|
|
748
|
+
{ name: 'Watch - Interactive tracking', value: 'watch' },
|
|
749
|
+
{ name: 'Back to menu', value: 'back' },
|
|
750
|
+
],
|
|
751
|
+
},
|
|
752
|
+
]);
|
|
753
|
+
if (syncAction !== 'back') {
|
|
754
|
+
await runSync({ subcommand: syncAction });
|
|
755
|
+
}
|
|
756
|
+
await returnToMenu();
|
|
757
|
+
}
|
|
758
|
+
break;
|
|
759
|
+
|
|
760
|
+
case 'setup':
|
|
761
|
+
await runSetup({});
|
|
762
|
+
await returnToMenu();
|
|
763
|
+
break;
|
|
764
|
+
|
|
765
|
+
case 'list':
|
|
766
|
+
if (!configured) {
|
|
767
|
+
console.log(
|
|
768
|
+
chalk.yellow(
|
|
769
|
+
'You need to configure your GitHub project first to list tasks.'
|
|
770
|
+
)
|
|
771
|
+
);
|
|
772
|
+
await returnToMenu();
|
|
773
|
+
} else {
|
|
774
|
+
await runList({});
|
|
775
|
+
await returnToMenu();
|
|
776
|
+
}
|
|
777
|
+
break;
|
|
778
|
+
|
|
779
|
+
case 'install':
|
|
780
|
+
await runInstall({});
|
|
781
|
+
await returnToMenu();
|
|
782
|
+
break;
|
|
783
|
+
|
|
784
|
+
case 'help':
|
|
785
|
+
showHelp();
|
|
786
|
+
await returnToMenu();
|
|
787
|
+
break;
|
|
788
|
+
|
|
789
|
+
case 'test-setup':
|
|
790
|
+
await runTestSetup({});
|
|
791
|
+
await returnToMenu();
|
|
792
|
+
break;
|
|
793
|
+
|
|
794
|
+
case 'test-run':
|
|
795
|
+
// Check if testing is configured
|
|
796
|
+
if (!hasTestingConfig()) {
|
|
797
|
+
console.log(chalk.yellow('Testing not configured. Running setup first...'));
|
|
798
|
+
console.log('');
|
|
799
|
+
await runTestSetup({});
|
|
800
|
+
} else {
|
|
801
|
+
// Show test mode submenu
|
|
802
|
+
const { testMode } = await inquirer.prompt([
|
|
803
|
+
{
|
|
804
|
+
type: 'list',
|
|
805
|
+
name: 'testMode',
|
|
806
|
+
message: 'How would you like to run tests?',
|
|
807
|
+
choices: [
|
|
808
|
+
{ name: 'Default - Use configured mode', value: 'default' },
|
|
809
|
+
{ name: 'Ralph Loop - Test-fix cycle until all pass', value: 'ralph' },
|
|
810
|
+
{ name: 'Manual - Run tests once', value: 'manual' },
|
|
811
|
+
{ name: 'Watch - Interactive UI mode', value: 'watch' },
|
|
812
|
+
{ name: 'Back to menu', value: 'back' },
|
|
813
|
+
],
|
|
814
|
+
},
|
|
815
|
+
]);
|
|
816
|
+
if (testMode !== 'back') {
|
|
817
|
+
const options = testMode === 'default' ? {} : { mode: testMode };
|
|
818
|
+
await runTest(options);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
await returnToMenu();
|
|
822
|
+
break;
|
|
823
|
+
|
|
824
|
+
case 'agent-creator':
|
|
825
|
+
await runCreateAgent({});
|
|
826
|
+
await returnToMenu();
|
|
827
|
+
break;
|
|
828
|
+
|
|
829
|
+
case 'claude-settings':
|
|
830
|
+
await runClaudeSettings({});
|
|
831
|
+
await returnToMenu();
|
|
832
|
+
break;
|
|
833
|
+
|
|
834
|
+
case 'phase-dev':
|
|
835
|
+
await showPhasDevMainMenu();
|
|
836
|
+
await returnToMenu();
|
|
837
|
+
break;
|
|
838
|
+
|
|
839
|
+
case 'explore-mcp':
|
|
840
|
+
await showExploreMcpMenu();
|
|
841
|
+
await returnToMenu();
|
|
842
|
+
break;
|
|
843
|
+
|
|
844
|
+
case 'claude-audit':
|
|
845
|
+
await showClaudeAuditMenu();
|
|
846
|
+
await returnToMenu();
|
|
847
|
+
break;
|
|
848
|
+
|
|
849
|
+
case 'roadmap':
|
|
850
|
+
await showRoadmapMenu();
|
|
851
|
+
await returnToMenu();
|
|
852
|
+
break;
|
|
853
|
+
|
|
854
|
+
case 'project-settings':
|
|
855
|
+
await showProjectSettingsMenu();
|
|
856
|
+
await returnToMenu();
|
|
857
|
+
break;
|
|
858
|
+
|
|
859
|
+
case 'exit':
|
|
860
|
+
console.log(chalk.dim('Goodbye!'));
|
|
861
|
+
process.exit(0);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Prompt to return to main menu
|
|
867
|
+
*/
|
|
868
|
+
async function returnToMenu() {
|
|
869
|
+
console.log('');
|
|
870
|
+
const { back } = await inquirer.prompt([
|
|
871
|
+
{
|
|
872
|
+
type: 'confirm',
|
|
873
|
+
name: 'back',
|
|
874
|
+
message: 'Return to main menu?',
|
|
875
|
+
default: true,
|
|
876
|
+
},
|
|
877
|
+
]);
|
|
878
|
+
|
|
879
|
+
if (back) {
|
|
880
|
+
await showMainMenu();
|
|
881
|
+
} else {
|
|
882
|
+
console.log(chalk.dim('Goodbye!'));
|
|
883
|
+
process.exit(0);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Show a styled header
|
|
889
|
+
*/
|
|
890
|
+
export function showHeader(title) {
|
|
891
|
+
const width = 60;
|
|
892
|
+
const padding = Math.max(0, Math.floor((width - title.length - 2) / 2));
|
|
893
|
+
const line = '═'.repeat(width);
|
|
894
|
+
|
|
895
|
+
console.log(chalk.cyan(`╔${line}╗`));
|
|
896
|
+
console.log(
|
|
897
|
+
chalk.cyan('║') +
|
|
898
|
+
' '.repeat(padding) +
|
|
899
|
+
chalk.bold(title) +
|
|
900
|
+
' '.repeat(width - padding - title.length) +
|
|
901
|
+
chalk.cyan('║')
|
|
902
|
+
);
|
|
903
|
+
console.log(chalk.cyan(`╚${line}╝`));
|
|
904
|
+
console.log('');
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Show a success message in a box
|
|
909
|
+
*/
|
|
910
|
+
export function showSuccess(title, details = []) {
|
|
911
|
+
const width = 65;
|
|
912
|
+
const line = '═'.repeat(width);
|
|
913
|
+
|
|
914
|
+
console.log('');
|
|
915
|
+
console.log(chalk.green(`╔${line}╗`));
|
|
916
|
+
console.log(
|
|
917
|
+
chalk.green('║ ') +
|
|
918
|
+
chalk.green.bold('✅ ' + title.padEnd(width - 5)) +
|
|
919
|
+
chalk.green('║')
|
|
920
|
+
);
|
|
921
|
+
|
|
922
|
+
if (details.length > 0) {
|
|
923
|
+
console.log(chalk.green('║' + ' '.repeat(width) + '║'));
|
|
924
|
+
for (const detail of details) {
|
|
925
|
+
const paddedDetail = (' ' + detail).padEnd(width);
|
|
926
|
+
console.log(chalk.green('║') + paddedDetail + chalk.green('║'));
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
console.log(chalk.green(`╚${line}╝`));
|
|
931
|
+
console.log('');
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Show an error message
|
|
936
|
+
*/
|
|
937
|
+
export function showError(title, message) {
|
|
938
|
+
console.log('');
|
|
939
|
+
console.log(chalk.red(`✗ ${chalk.bold(title)}`));
|
|
940
|
+
if (message) {
|
|
941
|
+
console.log(chalk.red(` ${message}`));
|
|
942
|
+
}
|
|
943
|
+
console.log('');
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Show a warning message
|
|
948
|
+
*/
|
|
949
|
+
export function showWarning(message) {
|
|
950
|
+
console.log(chalk.yellow(`⚠ ${message}`));
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Show info message
|
|
955
|
+
*/
|
|
956
|
+
export function showInfo(message) {
|
|
957
|
+
console.log(chalk.blue(`ℹ ${message}`));
|
|
958
|
+
}
|