vibecodingmachine-cli 2026.1.29-713 ā 2026.2.20-423
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/bin/vibecodingmachine.js +124 -0
- package/package.json +3 -2
- package/src/commands/agents-check.js +69 -0
- package/src/commands/auto-direct.js +930 -145
- package/src/commands/auto.js +26 -4
- package/src/commands/ide.js +2 -1
- package/src/commands/requirements.js +23 -27
- package/src/utils/auto-mode.js +4 -1
- package/src/utils/cline-js-handler.js +218 -0
- package/src/utils/config.js +22 -0
- package/src/utils/display-formatters-complete.js +229 -0
- package/src/utils/display-formatters-extracted.js +219 -0
- package/src/utils/display-formatters.js +157 -0
- package/src/utils/feedback-handler.js +143 -0
- package/src/utils/ide-detection-complete.js +126 -0
- package/src/utils/ide-detection-extracted.js +116 -0
- package/src/utils/ide-detection.js +124 -0
- package/src/utils/interactive-backup.js +5664 -0
- package/src/utils/interactive-broken.js +280 -0
- package/src/utils/interactive.js +31 -5534
- package/src/utils/provider-checker.js +410 -0
- package/src/utils/provider-manager.js +251 -0
- package/src/utils/provider-registry.js +18 -9
- package/src/utils/requirement-actions.js +884 -0
- package/src/utils/requirements-navigator.js +585 -0
- package/src/utils/rui-trui-adapter.js +311 -0
- package/src/utils/simple-trui.js +204 -0
- package/src/utils/status-helpers-extracted.js +125 -0
- package/src/utils/status-helpers.js +107 -0
- package/src/utils/trui-debug.js +261 -0
- package/src/utils/trui-feedback.js +133 -0
- package/src/utils/trui-nav-agents.js +119 -0
- package/src/utils/trui-nav-requirements.js +268 -0
- package/src/utils/trui-nav-settings.js +157 -0
- package/src/utils/trui-nav-specifications.js +139 -0
- package/src/utils/trui-navigation.js +303 -0
- package/src/utils/trui-provider-manager.js +182 -0
- package/src/utils/trui-quick-menu.js +365 -0
- package/src/utils/trui-req-actions.js +372 -0
- package/src/utils/trui-req-tree.js +534 -0
- package/src/utils/trui-specifications.js +359 -0
- package/src/utils/trui-text-editor.js +350 -0
- package/src/utils/trui-windsurf.js +336 -0
- package/src/utils/welcome-screen-extracted.js +135 -0
- package/src/utils/welcome-screen.js +134 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TRUI Requirements Section
|
|
3
|
+
* Exposes loadRequirementsData / buildRequirementChoices / handleRequirementSelection
|
|
4
|
+
* for inline accordion display in the main navigation menu.
|
|
5
|
+
* Also exports addRequirementFlow for the [+ Add Requirement] shortcut.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const inquirer = require('inquirer');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const fs = require('fs-extra');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Load requirements data via the RUI command resolver
|
|
14
|
+
* @param {Object} navigation - TRUINavigation instance
|
|
15
|
+
* @returns {Promise<{requirements: Array}|null>}
|
|
16
|
+
*/
|
|
17
|
+
async function loadRequirementsData(navigation) {
|
|
18
|
+
try {
|
|
19
|
+
const result = navigation.resolver.resolve('list requirements');
|
|
20
|
+
if (!result.success) return null;
|
|
21
|
+
|
|
22
|
+
const commandResult = await result.command.execute();
|
|
23
|
+
if (!commandResult.success || !commandResult.data) return null;
|
|
24
|
+
|
|
25
|
+
return commandResult.data;
|
|
26
|
+
} catch (err) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build inline accordion choice objects for the requirements section.
|
|
33
|
+
* Requirements are grouped by status with Separator headers.
|
|
34
|
+
* @param {{requirements: Array}} data
|
|
35
|
+
* @returns {Array} inquirer choice objects (includes Separators)
|
|
36
|
+
*/
|
|
37
|
+
function buildRequirementChoices(data) {
|
|
38
|
+
const requirements = (data && data.requirements) || [];
|
|
39
|
+
|
|
40
|
+
if (!requirements.length) {
|
|
41
|
+
return [{ name: chalk.gray(' (no requirements found)'), value: 'noop' }];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const sections = {
|
|
45
|
+
current: requirements.filter(r => r.status === 'current'),
|
|
46
|
+
todo: requirements.filter(r => r.status === 'todo'),
|
|
47
|
+
verify: requirements.filter(r => r.status === 'verify'),
|
|
48
|
+
verified: requirements.filter(r => r.status === 'verified'),
|
|
49
|
+
'need-information': requirements.filter(r => r.status === 'need-information'),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const choices = [];
|
|
53
|
+
|
|
54
|
+
const addSection = (label, items, icon) => {
|
|
55
|
+
if (!items.length) return;
|
|
56
|
+
choices.push(new inquirer.Separator(chalk.bold(` ${label}`)));
|
|
57
|
+
items.forEach(req => {
|
|
58
|
+
const idx = requirements.indexOf(req);
|
|
59
|
+
const isDisabled = req.title.startsWith('DISABLED:');
|
|
60
|
+
const cleanTitle = isDisabled ? req.title.replace(/^DISABLED:\s*/, '') : req.title;
|
|
61
|
+
const disabledIcon = isDisabled ? chalk.red('š«') : '';
|
|
62
|
+
choices.push({
|
|
63
|
+
name: ` ${disabledIcon}${icon} ${chalk.white(cleanTitle)}`,
|
|
64
|
+
value: `req:${idx}`,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
addSection('š CURRENT', sections.current, chalk.cyan('ā'));
|
|
70
|
+
addSection('ā³ TODO', sections.todo, chalk.yellow('ā'));
|
|
71
|
+
addSection('š TO VERIFY', sections.verify, chalk.blue('ā'));
|
|
72
|
+
addSection('ā
VERIFIED', sections.verified, chalk.green('ā'));
|
|
73
|
+
addSection('ā NEED INFO', sections['need-information'], chalk.red('?'));
|
|
74
|
+
|
|
75
|
+
return choices;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Handle selection of a requirement item ā shows status-change sub-prompt
|
|
80
|
+
* @param {number} index - Index into data.requirements
|
|
81
|
+
* @param {{requirements: Array}} data
|
|
82
|
+
* @param {Object} navigation - TRUINavigation instance
|
|
83
|
+
* @returns {Promise<boolean>} true if requirements were changed
|
|
84
|
+
*/
|
|
85
|
+
async function handleRequirementSelection(index, data, navigation) {
|
|
86
|
+
if (!data || !data.requirements || !data.requirements[index]) return false;
|
|
87
|
+
|
|
88
|
+
const req = data.requirements[index];
|
|
89
|
+
|
|
90
|
+
const {
|
|
91
|
+
promoteToVerified,
|
|
92
|
+
demoteFromVerifiedToTodo,
|
|
93
|
+
promoteTodoToVerify,
|
|
94
|
+
demoteVerifyToTodo,
|
|
95
|
+
getOrCreateRequirementsFilePath,
|
|
96
|
+
} = require('vibecodingmachine-core');
|
|
97
|
+
|
|
98
|
+
const statusLabel = {
|
|
99
|
+
current: chalk.cyan('CURRENT'),
|
|
100
|
+
todo: chalk.yellow('TODO'),
|
|
101
|
+
verify: chalk.blue('TO VERIFY'),
|
|
102
|
+
verified: chalk.green('VERIFIED'),
|
|
103
|
+
'need-information': chalk.red('NEED INFO'),
|
|
104
|
+
}[req.status] || chalk.gray(req.status || 'unknown');
|
|
105
|
+
|
|
106
|
+
const isDisabled = req.title.startsWith('DISABLED:');
|
|
107
|
+
const cleanTitle = isDisabled ? req.title.replace(/^DISABLED:\s*/, '') : req.title;
|
|
108
|
+
|
|
109
|
+
const choices = [
|
|
110
|
+
{ name: 'Move to TODO', value: 'moveToTodo' },
|
|
111
|
+
{ name: 'Move to TO VERIFY', value: 'moveToVerify' },
|
|
112
|
+
{ name: 'Move to VERIFIED', value: 'moveToVerified' },
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
// Add enable/disable option
|
|
116
|
+
if (isDisabled) {
|
|
117
|
+
choices.push({ name: 'Enable Requirement', value: 'enable' });
|
|
118
|
+
} else {
|
|
119
|
+
choices.push({ name: 'Disable Requirement', value: 'disable' });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
choices.push({ name: chalk.gray('Cancel'), value: 'cancel' });
|
|
123
|
+
|
|
124
|
+
const { reqAction } = await inquirer.prompt([
|
|
125
|
+
{
|
|
126
|
+
type: 'list',
|
|
127
|
+
name: 'reqAction',
|
|
128
|
+
message: `${cleanTitle} [${statusLabel}]`,
|
|
129
|
+
choices,
|
|
130
|
+
},
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
if (reqAction === 'cancel') return false;
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const reqPath = await getOrCreateRequirementsFilePath();
|
|
137
|
+
|
|
138
|
+
if (reqAction === 'moveToTodo') {
|
|
139
|
+
if (req.status === 'verify') {
|
|
140
|
+
await demoteVerifyToTodo(reqPath, req.title);
|
|
141
|
+
} else if (req.status === 'verified') {
|
|
142
|
+
await demoteFromVerifiedToTodo(reqPath, req.title);
|
|
143
|
+
} else {
|
|
144
|
+
console.log(chalk.yellow('Cannot move to TODO from current status'));
|
|
145
|
+
await navigation.promptContinue();
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
} else if (reqAction === 'moveToVerify') {
|
|
149
|
+
await promoteTodoToVerify(reqPath, req.title);
|
|
150
|
+
} else if (reqAction === 'moveToVerified') {
|
|
151
|
+
await promoteToVerified(reqPath, req.title);
|
|
152
|
+
} else if (reqAction === 'enable' || reqAction === 'disable') {
|
|
153
|
+
// Toggle disabled status
|
|
154
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
155
|
+
const lines = content.split('\n');
|
|
156
|
+
|
|
157
|
+
// Find the requirement line
|
|
158
|
+
for (let i = 0; i < lines.length; i++) {
|
|
159
|
+
if (lines[i].includes(req.title)) {
|
|
160
|
+
if (reqAction === 'enable') {
|
|
161
|
+
// Remove DISABLED: prefix
|
|
162
|
+
lines[i] = lines[i].replace(/^###\s*DISABLED:\s*/, '### ');
|
|
163
|
+
} else {
|
|
164
|
+
// Add DISABLED: prefix
|
|
165
|
+
lines[i] = lines[i].replace(/^###\s*/, '### DISABLED: ');
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await fs.writeFile(reqPath, lines.join('\n'), 'utf8');
|
|
172
|
+
const action = reqAction === 'enable' ? 'enabled' : 'disabled';
|
|
173
|
+
console.log(chalk.green(`\nā Requirement "${cleanTitle}" ${action}`));
|
|
174
|
+
console.log(chalk.gray(` Equivalent: app requirements ${cleanTitle} ${reqAction}`));
|
|
175
|
+
await navigation.promptContinue();
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log(chalk.green(`\nā Moved "${req.title}" successfully`));
|
|
180
|
+
console.log(chalk.gray(` Equivalent: app requirements ${req.title} ${reqAction}`));
|
|
181
|
+
await navigation.promptContinue();
|
|
182
|
+
return true;
|
|
183
|
+
} catch (err) {
|
|
184
|
+
console.log(chalk.red(`Error: ${err.message}`));
|
|
185
|
+
await navigation.promptContinue();
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Interactive flow to add a new requirement
|
|
192
|
+
* Appends a new requirement to the "Requirements not yet completed" section
|
|
193
|
+
* @param {Object} navigation - TRUINavigation instance
|
|
194
|
+
*/
|
|
195
|
+
async function addRequirementFlow(navigation) {
|
|
196
|
+
console.log(chalk.bold.cyan('\nš Add New Requirement\n'));
|
|
197
|
+
console.log(chalk.gray('Equivalent: app create requirement <name>\n'));
|
|
198
|
+
|
|
199
|
+
const { title } = await inquirer.prompt([
|
|
200
|
+
{
|
|
201
|
+
type: 'input',
|
|
202
|
+
name: 'title',
|
|
203
|
+
message: chalk.cyan('Requirement title:'),
|
|
204
|
+
validate: input => input.trim().length > 0 || 'Title is required',
|
|
205
|
+
},
|
|
206
|
+
]);
|
|
207
|
+
|
|
208
|
+
const { description } = await inquirer.prompt([
|
|
209
|
+
{
|
|
210
|
+
type: 'input',
|
|
211
|
+
name: 'description',
|
|
212
|
+
message: chalk.cyan('Description (optional):'),
|
|
213
|
+
},
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const { getOrCreateRequirementsFilePath } = require('vibecodingmachine-core');
|
|
218
|
+
const repoPath = process.cwd();
|
|
219
|
+
const reqPath = await getOrCreateRequirementsFilePath(repoPath);
|
|
220
|
+
|
|
221
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
222
|
+
const lines = content.split('\n');
|
|
223
|
+
|
|
224
|
+
let insertIndex = -1;
|
|
225
|
+
for (let i = 0; i < lines.length; i++) {
|
|
226
|
+
if (lines[i].trim().match(/^##\s+.*(?:Requirements not yet completed|TODO)/i)) {
|
|
227
|
+
insertIndex = i + 1;
|
|
228
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
229
|
+
const inner = lines[j].trim();
|
|
230
|
+
if (inner.startsWith('##') && !inner.startsWith('###')) {
|
|
231
|
+
insertIndex = j;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
if (inner.startsWith('###') || inner.startsWith('- ')) {
|
|
235
|
+
insertIndex = j;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const requirementLine = description.trim()
|
|
244
|
+
? `### ${title.trim()}\n\n${description.trim()}\n`
|
|
245
|
+
: `### ${title.trim()}\n`;
|
|
246
|
+
|
|
247
|
+
if (insertIndex >= 0) {
|
|
248
|
+
lines.splice(insertIndex, 0, requirementLine);
|
|
249
|
+
} else {
|
|
250
|
+
lines.push('', '## Requirements not yet completed', '', requirementLine);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
await fs.writeFile(reqPath, lines.join('\n'), 'utf8');
|
|
254
|
+
console.log(chalk.green(`\nā Requirement "${title.trim()}" added`));
|
|
255
|
+
console.log(chalk.gray(` Equivalent: app create requirement "${title.trim()}"`));
|
|
256
|
+
} catch (err) {
|
|
257
|
+
console.log(chalk.red(`Error adding requirement: ${err.message}`));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
await navigation.promptContinue();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
module.exports = {
|
|
264
|
+
loadRequirementsData,
|
|
265
|
+
buildRequirementChoices,
|
|
266
|
+
handleRequirementSelection,
|
|
267
|
+
addRequirementFlow,
|
|
268
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TRUI Settings Section
|
|
3
|
+
* Exposes loadSettingsData / buildSettingChoices / handleSettingSelection
|
|
4
|
+
* for inline accordion display in the main navigation menu.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const inquirer = require('inquirer');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Load settings data via the RUI command resolver
|
|
12
|
+
* @param {Object} navigation - TRUINavigation instance
|
|
13
|
+
* @returns {Promise<{settings: Array}|null>}
|
|
14
|
+
*/
|
|
15
|
+
async function loadSettingsData(navigation) {
|
|
16
|
+
try {
|
|
17
|
+
const result = navigation.resolver.resolve('list settings');
|
|
18
|
+
if (!result.success) return null;
|
|
19
|
+
|
|
20
|
+
const commandResult = await result.command.execute();
|
|
21
|
+
if (!commandResult.success || !commandResult.data) return null;
|
|
22
|
+
|
|
23
|
+
return commandResult.data;
|
|
24
|
+
} catch (err) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build inline accordion choice objects for the settings section
|
|
31
|
+
* @param {{settings: Array}} data
|
|
32
|
+
* @returns {Array} inquirer choice objects
|
|
33
|
+
*/
|
|
34
|
+
function buildSettingChoices(data) {
|
|
35
|
+
const settings = (data && data.settings) || [];
|
|
36
|
+
|
|
37
|
+
if (!settings.length) {
|
|
38
|
+
return [{ name: chalk.gray(' (no settings found)'), value: 'noop' }];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return settings.map(setting => ({
|
|
42
|
+
name: ` ${chalk.white(setting.key)}: ${chalk.gray(String(setting.value))}`,
|
|
43
|
+
value: `setting:${setting.key}`,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Handle selection of a setting item ā shows key/value detail
|
|
49
|
+
* @param {string} key - The setting key
|
|
50
|
+
* @param {{settings: Array}} data
|
|
51
|
+
* @param {Object} navigation - TRUINavigation instance
|
|
52
|
+
*/
|
|
53
|
+
async function handleSettingSelection(key, data, navigation) {
|
|
54
|
+
const settings = (data && data.settings) || [];
|
|
55
|
+
const setting = settings.find(s => s.key === key);
|
|
56
|
+
|
|
57
|
+
if (!setting) {
|
|
58
|
+
console.log(chalk.red(`Setting "${key}" not found`));
|
|
59
|
+
await navigation.promptContinue();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log(chalk.bold.cyan(`\nāļø ${setting.key}\n`));
|
|
64
|
+
console.log(chalk.white(` Value: ${setting.value}`));
|
|
65
|
+
if (setting.description) {
|
|
66
|
+
console.log(chalk.gray(` Description: ${setting.description}`));
|
|
67
|
+
}
|
|
68
|
+
console.log(chalk.gray(`\n Equivalent: app get settings ${setting.key}`));
|
|
69
|
+
|
|
70
|
+
await navigation.promptContinue();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Show the settings menu ā a showQuickMenu loop with current config displayed.
|
|
75
|
+
*/
|
|
76
|
+
async function showSettings() {
|
|
77
|
+
const { showQuickMenu } = require('./trui-quick-menu');
|
|
78
|
+
const { getAutoConfig, getRepoPath, getStages, setAutoConfig } = require('./config');
|
|
79
|
+
const { getProviderPreferences } = require('./provider-registry');
|
|
80
|
+
const inquirer = require('inquirer');
|
|
81
|
+
|
|
82
|
+
const printHeader = () => {
|
|
83
|
+
console.clear();
|
|
84
|
+
process.stdout.write(chalk.bold.cyan('āļø Settings\n\n'));
|
|
85
|
+
process.stdout.write(chalk.gray('[āā navigate Enter select ā back]\n\n'));
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const buildItems = async () => {
|
|
89
|
+
const repoPath = await getRepoPath();
|
|
90
|
+
const autoConfig = await getAutoConfig();
|
|
91
|
+
const prefs = await getProviderPreferences();
|
|
92
|
+
// Find first enabled provider in order for display
|
|
93
|
+
let activeIde = autoConfig.ide || autoConfig.agent || 'cline';
|
|
94
|
+
|
|
95
|
+
const items = [];
|
|
96
|
+
items.push({ type: 'setting', name: `Repo: ${chalk.white(repoPath || '(not set)')}`, value: 'setting:repo' });
|
|
97
|
+
items.push({ type: 'setting', name: `IDE/Agent: ${chalk.white(activeIde)}`, value: 'setting:ide' });
|
|
98
|
+
items.push({ type: 'setting', name: `Max Chats: ${chalk.white(autoConfig.maxChats || '(unlimited)')}`, value: 'setting:maxChats' });
|
|
99
|
+
items.push({ type: 'setting', name: `Never Stop: ${chalk.white(autoConfig.neverStop ? 'true' : 'false')}`, value: 'setting:neverStop' });
|
|
100
|
+
items.push({ type: 'setting', name: `Skip Disabled: ${chalk.white(autoConfig.skipDisabled ? 'true' : 'false')}`, value: 'setting:skipDisabled' });
|
|
101
|
+
return items;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
printHeader();
|
|
105
|
+
|
|
106
|
+
while (true) {
|
|
107
|
+
const items = await buildItems();
|
|
108
|
+
const result = await showQuickMenu(items, 0);
|
|
109
|
+
const value = result.value;
|
|
110
|
+
|
|
111
|
+
if (value === '__cancel__' || value === 'exit') break;
|
|
112
|
+
|
|
113
|
+
if (value === 'setting:ide') {
|
|
114
|
+
const prefs = await getProviderPreferences();
|
|
115
|
+
const autoConfig = await getAutoConfig();
|
|
116
|
+
const enabledAgents = prefs.order.filter(id => prefs.enabled[id] !== false);
|
|
117
|
+
if (enabledAgents.length === 0) {
|
|
118
|
+
console.log(chalk.yellow('\nNo enabled agents. Enable agents in Agents menu first.\n'));
|
|
119
|
+
await inquirer.prompt([{ type: 'input', name: 'c', message: chalk.gray('Press Enter...') }]);
|
|
120
|
+
} else {
|
|
121
|
+
const { ide } = await inquirer.prompt([{
|
|
122
|
+
type: 'list',
|
|
123
|
+
name: 'ide',
|
|
124
|
+
message: 'Select active IDE/Agent:',
|
|
125
|
+
choices: enabledAgents,
|
|
126
|
+
default: autoConfig.ide || autoConfig.agent,
|
|
127
|
+
}]);
|
|
128
|
+
await setAutoConfig({ ide });
|
|
129
|
+
}
|
|
130
|
+
} else if (value === 'setting:maxChats') {
|
|
131
|
+
const autoConfig = await getAutoConfig();
|
|
132
|
+
const { maxChats } = await inquirer.prompt([{
|
|
133
|
+
type: 'input',
|
|
134
|
+
name: 'maxChats',
|
|
135
|
+
message: 'Max chats per run (blank = unlimited):',
|
|
136
|
+
default: autoConfig.maxChats ? String(autoConfig.maxChats) : '',
|
|
137
|
+
}]);
|
|
138
|
+
const parsed = parseInt(maxChats, 10);
|
|
139
|
+
await setAutoConfig({ maxChats: isNaN(parsed) ? undefined : parsed });
|
|
140
|
+
} else if (value === 'setting:neverStop') {
|
|
141
|
+
const autoConfig = await getAutoConfig();
|
|
142
|
+
await setAutoConfig({ neverStop: !autoConfig.neverStop });
|
|
143
|
+
} else if (value === 'setting:skipDisabled') {
|
|
144
|
+
const autoConfig = await getAutoConfig();
|
|
145
|
+
await setAutoConfig({ skipDisabled: !autoConfig.skipDisabled });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
printHeader();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
loadSettingsData,
|
|
154
|
+
buildSettingChoices,
|
|
155
|
+
handleSettingSelection,
|
|
156
|
+
showSettings,
|
|
157
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TRUI Specifications Section
|
|
3
|
+
* Exposes loadSpecificationsData / buildSpecificationChoices / handleSpecificationSelection
|
|
4
|
+
* for inline accordion display in the main navigation menu.
|
|
5
|
+
* Also exports addSpecificationFlow for the [+ Add Specification] shortcut.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const inquirer = require('inquirer');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Load all specifications from the repo
|
|
13
|
+
* @returns {Promise<Array|null>}
|
|
14
|
+
*/
|
|
15
|
+
async function loadSpecificationsData() {
|
|
16
|
+
try {
|
|
17
|
+
const { getAllSpecifications } = require('vibecodingmachine-core');
|
|
18
|
+
const repoPath = process.cwd();
|
|
19
|
+
return await getAllSpecifications(repoPath);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Build inline accordion choice objects for the specifications section
|
|
27
|
+
* @param {Array} specs - Array of spec objects
|
|
28
|
+
* @returns {Array} inquirer choice objects
|
|
29
|
+
*/
|
|
30
|
+
function buildSpecificationChoices(specs) {
|
|
31
|
+
if (!specs || !specs.length) {
|
|
32
|
+
return [{ name: chalk.gray(' (no specifications found)'), value: 'noop' }];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return specs.map(spec => {
|
|
36
|
+
const todoTag = !spec.hasTasks ? chalk.yellow(' [TODO]') : chalk.green(' [done]');
|
|
37
|
+
const label = spec.title || spec.directory;
|
|
38
|
+
return {
|
|
39
|
+
name: ` ${chalk.white(spec.directory)} ${chalk.gray('ā')} ${chalk.gray(label)}${todoTag}`,
|
|
40
|
+
value: `spec:${spec.directory}`,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Handle selection of a specification item ā shows detail
|
|
47
|
+
* @param {string} directory - Spec directory name
|
|
48
|
+
* @param {Array} specs
|
|
49
|
+
* @param {Object} navigation - TRUINavigation instance
|
|
50
|
+
*/
|
|
51
|
+
async function handleSpecificationSelection(directory, specs, navigation) {
|
|
52
|
+
const spec = (specs || []).find(s => s.directory === directory);
|
|
53
|
+
|
|
54
|
+
if (!spec) {
|
|
55
|
+
console.log(chalk.red(`Specification "${directory}" not found`));
|
|
56
|
+
await navigation.promptContinue();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(chalk.bold.cyan(`\nš ${spec.title || spec.directory}\n`));
|
|
61
|
+
console.log(chalk.white(` Directory: ${spec.directory}`));
|
|
62
|
+
console.log(chalk.white(` Path: ${spec.path}`));
|
|
63
|
+
console.log(chalk.white(` Has Plan: ${spec.hasPlan ? chalk.green('Yes') : chalk.gray('No')}`));
|
|
64
|
+
console.log(chalk.white(` Has Tasks: ${spec.hasTasks ? chalk.green('Yes') : chalk.yellow('No (TODO)')}`));
|
|
65
|
+
|
|
66
|
+
await navigation.promptContinue();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Interactive flow to add a new specification
|
|
71
|
+
* @param {Object} navigation - TRUINavigation instance
|
|
72
|
+
*/
|
|
73
|
+
async function addSpecificationFlow(navigation) {
|
|
74
|
+
console.log(chalk.bold.cyan('\nš New Specification\n'));
|
|
75
|
+
|
|
76
|
+
const { shortName } = await inquirer.prompt([
|
|
77
|
+
{
|
|
78
|
+
type: 'input',
|
|
79
|
+
name: 'shortName',
|
|
80
|
+
message: chalk.cyan('Short name (e.g. "print-to-pdf"):'),
|
|
81
|
+
validate: input => {
|
|
82
|
+
if (!input.trim()) return 'Short name is required';
|
|
83
|
+
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(input.trim())) {
|
|
84
|
+
return 'Must be lowercase with hyphens: /^[a-z0-9]+(-[a-z0-9]+)*$/';
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
console.log(chalk.gray('Enter specification prompt. Enter a blank line to finish.'));
|
|
92
|
+
const promptLines = [];
|
|
93
|
+
while (true) {
|
|
94
|
+
const { line } = await inquirer.prompt([
|
|
95
|
+
{
|
|
96
|
+
type: 'input',
|
|
97
|
+
name: 'line',
|
|
98
|
+
message: chalk.cyan(promptLines.length === 0 ? 'Prompt > ' : ' > '),
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
if (line.trim() === '' && promptLines.length > 0) break;
|
|
102
|
+
if (line.trim() !== '') promptLines.push(line);
|
|
103
|
+
}
|
|
104
|
+
const specPrompt = promptLines.join('\n');
|
|
105
|
+
|
|
106
|
+
console.log(chalk.gray('\nPlan prompt (optional). Enter blank line to skip.'));
|
|
107
|
+
const planLines = [];
|
|
108
|
+
while (true) {
|
|
109
|
+
const { line } = await inquirer.prompt([
|
|
110
|
+
{
|
|
111
|
+
type: 'input',
|
|
112
|
+
name: 'line',
|
|
113
|
+
message: chalk.cyan(planLines.length === 0 ? 'Plan > ' : ' > '),
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
if (line.trim() === '') break;
|
|
117
|
+
planLines.push(line);
|
|
118
|
+
}
|
|
119
|
+
const planPrompt = planLines.join('\n');
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const { createSpecification } = require('vibecodingmachine-core');
|
|
123
|
+
const repoPath = process.cwd();
|
|
124
|
+
const result = await createSpecification(repoPath, shortName.trim(), specPrompt, planPrompt);
|
|
125
|
+
console.log(chalk.green(`\nā
Specification created: ${result.directory}`));
|
|
126
|
+
console.log(chalk.gray(` Path: ${result.path}`));
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.log(chalk.red(`ā Error creating specification: ${err.message}`));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await navigation.promptContinue();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = {
|
|
135
|
+
loadSpecificationsData,
|
|
136
|
+
buildSpecificationChoices,
|
|
137
|
+
handleSpecificationSelection,
|
|
138
|
+
addSpecificationFlow,
|
|
139
|
+
};
|