dual-brain 0.2.7 → 0.2.8
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/CLAUDE.md +29 -143
- package/bin/dual-brain.mjs +80 -44
- package/package.json +10 -2
- package/src/dispatch.mjs +87 -2
- package/src/head.mjs +353 -0
- package/src/health.mjs +156 -0
- package/src/integrity.mjs +245 -0
- package/src/prompt-audit.mjs +231 -0
- package/src/templates.mjs +223 -0
- package/src/tui.mjs +79 -0
package/src/tui.mjs
CHANGED
|
@@ -171,6 +171,85 @@ export function menu(options, opts = {}) {
|
|
|
171
171
|
return rows.join('\n');
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
// ── Modern box rendering with rounded corners ────────────────────────────────
|
|
175
|
+
|
|
176
|
+
const ROUNDED = { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│', ml: '├', mr: '┤' };
|
|
177
|
+
|
|
178
|
+
export function panel(title, content, opts = {}) {
|
|
179
|
+
const { width = 70, titleColor = '\x1b[36m', borderColor = '\x1b[2m', reset = '\x1b[0m' } = opts;
|
|
180
|
+
const lines = [];
|
|
181
|
+
const innerW = width - 2;
|
|
182
|
+
|
|
183
|
+
// Top border with title
|
|
184
|
+
if (title) {
|
|
185
|
+
const titleStr = ` ${title} `;
|
|
186
|
+
const remaining = innerW - titleStr.length - 1;
|
|
187
|
+
lines.push(`${borderColor}${ROUNDED.tl}${ROUNDED.h} ${titleColor}${title}${borderColor} ${ROUNDED.h.repeat(Math.max(0, remaining))}${ROUNDED.tr}${reset}`);
|
|
188
|
+
} else {
|
|
189
|
+
lines.push(`${borderColor}${ROUNDED.tl}${ROUNDED.h.repeat(innerW)}${ROUNDED.tr}${reset}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Content lines
|
|
193
|
+
const contentLines = (typeof content === 'string' ? content.split('\n') : content);
|
|
194
|
+
for (const line of contentLines) {
|
|
195
|
+
const stripped = line.replace(/\x1b\[[0-9;]*m/g, '');
|
|
196
|
+
const pad = Math.max(0, innerW - stripped.length);
|
|
197
|
+
lines.push(`${borderColor}${ROUNDED.v}${reset} ${line}${' '.repeat(pad)}${borderColor}${ROUNDED.v}${reset}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Bottom border
|
|
201
|
+
lines.push(`${borderColor}${ROUNDED.bl}${ROUNDED.h.repeat(innerW)}${ROUNDED.br}${reset}`);
|
|
202
|
+
|
|
203
|
+
return lines.join('\n');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function divider(width = 70) {
|
|
207
|
+
const borderColor = '\x1b[2m';
|
|
208
|
+
const reset = '\x1b[0m';
|
|
209
|
+
return `${borderColor}${ROUNDED.ml}${ROUNDED.h.repeat(width - 2)}${ROUNDED.mr}${reset}`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function statusChip(label, healthy, opts = {}) {
|
|
213
|
+
const green = '\x1b[32m';
|
|
214
|
+
const red = '\x1b[31m';
|
|
215
|
+
const dim = '\x1b[2m';
|
|
216
|
+
const reset = '\x1b[0m';
|
|
217
|
+
const icon = healthy ? `${green}●${reset}` : `${red}●${reset}`;
|
|
218
|
+
return `${icon} ${dim}${label}${reset}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function headerBar(left, right, width = 70) {
|
|
222
|
+
const leftStripped = left.replace(/\x1b\[[0-9;]*m/g, '');
|
|
223
|
+
const rightStripped = right.replace(/\x1b\[[0-9;]*m/g, '');
|
|
224
|
+
const gap = Math.max(1, width - leftStripped.length - rightStripped.length);
|
|
225
|
+
return `${left}${' '.repeat(gap)}${right}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function prompt(text = '> task or /help') {
|
|
229
|
+
const cyan = '\x1b[36m';
|
|
230
|
+
const dim = '\x1b[2m';
|
|
231
|
+
const reset = '\x1b[0m';
|
|
232
|
+
return `${cyan}>${reset} ${dim}${text.replace(/^>\s*/, '')}${reset}`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function signalLine(type, text, meta = '') {
|
|
236
|
+
const green = '\x1b[32m';
|
|
237
|
+
const yellow = '\x1b[33m';
|
|
238
|
+
const dim = '\x1b[2m';
|
|
239
|
+
const reset = '\x1b[0m';
|
|
240
|
+
|
|
241
|
+
let icon;
|
|
242
|
+
switch (type) {
|
|
243
|
+
case 'success': icon = `${green}✓${reset}`; break;
|
|
244
|
+
case 'warning': icon = `${yellow}!${reset}`; break;
|
|
245
|
+
case 'info': icon = `${dim}·${reset}`; break;
|
|
246
|
+
default: icon = `${dim}·${reset}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const metaStr = meta ? `${dim}${meta}${reset}` : '';
|
|
250
|
+
return `${icon} ${text}${metaStr ? ' ' + metaStr : ''}`;
|
|
251
|
+
}
|
|
252
|
+
|
|
174
253
|
// ─── Self-test ────────────────────────────────────────────────────────────────
|
|
175
254
|
|
|
176
255
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|