writethevision 7.0.3 → 7.0.5
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.js +126 -38
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "writethevision",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.5",
|
|
4
4
|
"description": "Write The Vision (WTV): vision-driven development with the Habakkuk workflow. 10 agents + 21 skills for Claude Code, Codex CLI, and OpenCode.",
|
|
5
5
|
"author": "Christopher Hogg",
|
|
6
6
|
"license": "MIT",
|
package/src/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
writeFileSync,
|
|
15
15
|
} from 'fs';
|
|
16
16
|
import { homedir, platform } from 'os';
|
|
17
|
-
import { spawn } from 'child_process';
|
|
17
|
+
import { spawn, spawnSync } from 'child_process';
|
|
18
18
|
|
|
19
19
|
async function openInEditor(filePath) {
|
|
20
20
|
const editor = process.env.EDITOR || 'vi';
|
|
@@ -402,13 +402,71 @@ function getAgentLocations() {
|
|
|
402
402
|
return locations;
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
-
|
|
406
|
-
|
|
405
|
+
const BLOCK_FONT_3X5 = {
|
|
406
|
+
A: ['███', '█ █', '███', '█ █', '█ █'],
|
|
407
|
+
B: ['██ ', '█ █', '██ ', '█ █', '██ '],
|
|
408
|
+
C: ['███', '█ ', '█ ', '█ ', '███'],
|
|
409
|
+
D: ['██ ', '█ █', '█ █', '█ █', '██ '],
|
|
410
|
+
E: ['███', '█ ', '██ ', '█ ', '███'],
|
|
411
|
+
F: ['███', '█ ', '██ ', '█ ', '█ '],
|
|
412
|
+
G: ['███', '█ ', '█ █', '█ █', '███'],
|
|
413
|
+
H: ['█ █', '█ █', '███', '█ █', '█ █'],
|
|
414
|
+
I: ['███', ' █ ', ' █ ', ' █ ', '███'],
|
|
415
|
+
J: [' ██', ' █', ' █', '█ █', '██ '],
|
|
416
|
+
K: ['█ █', '█ █', '██ ', '█ █', '█ █'],
|
|
417
|
+
L: ['█ ', '█ ', '█ ', '█ ', '███'],
|
|
418
|
+
M: ['█ █', '███', '███', '█ █', '█ █'],
|
|
419
|
+
N: ['█ █', '███', '███', '███', '█ █'],
|
|
420
|
+
O: ['███', '█ █', '█ █', '█ █', '███'],
|
|
421
|
+
P: ['███', '█ █', '███', '█ ', '█ '],
|
|
422
|
+
Q: ['███', '█ █', '█ █', '███', ' █'],
|
|
423
|
+
R: ['███', '█ █', '███', '█ █', '█ █'],
|
|
424
|
+
S: ['███', '█ ', '███', ' █', '███'],
|
|
425
|
+
T: ['███', ' █ ', ' █ ', ' █ ', ' █ '],
|
|
426
|
+
U: ['█ █', '█ █', '█ █', '█ █', '███'],
|
|
427
|
+
V: ['█ █', '█ █', '█ █', '█ █', ' █ '],
|
|
428
|
+
W: ['█ █', '█ █', '███', '███', '█ █'],
|
|
429
|
+
X: ['█ █', '█ █', ' █ ', '█ █', '█ █'],
|
|
430
|
+
Y: ['█ █', '█ █', ' █ ', ' █ ', ' █ '],
|
|
431
|
+
Z: ['███', ' █', ' █ ', '█ ', '███'],
|
|
432
|
+
' ': [' ', ' ', ' ', ' ', ' '],
|
|
433
|
+
'-': [' ', ' ', '███', ' ', ' '],
|
|
434
|
+
'?': ['███', ' █', ' ██', ' ', ' █ '],
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
function renderBlockText(text, { maxWidth = null, color = null, bold = false } = {}) {
|
|
438
|
+
const normalized = String(text || '').toUpperCase();
|
|
439
|
+
const glyphWidth = 3;
|
|
440
|
+
const letterGap = 1;
|
|
441
|
+
const maxCharsPerLine = maxWidth
|
|
442
|
+
? Math.max(1, Math.floor((maxWidth + letterGap) / (glyphWidth + letterGap)))
|
|
443
|
+
: normalized.length;
|
|
444
|
+
|
|
445
|
+
const chunks = [];
|
|
446
|
+
for (let i = 0; i < normalized.length; i += maxCharsPerLine) {
|
|
447
|
+
chunks.push(normalized.slice(i, i + maxCharsPerLine));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const renderedLines = [];
|
|
451
|
+
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
|
|
452
|
+
const chunk = chunks[chunkIndex];
|
|
453
|
+
for (let row = 0; row < 5; row++) {
|
|
454
|
+
const rowText = chunk
|
|
455
|
+
.split('')
|
|
456
|
+
.map(ch => (BLOCK_FONT_3X5[ch] ? BLOCK_FONT_3X5[ch][row] : BLOCK_FONT_3X5['?']?.[row] || '???'))
|
|
457
|
+
.join(' '.repeat(letterGap));
|
|
458
|
+
|
|
459
|
+
const prefix = color ? (bold ? color + c.bold : color) : '';
|
|
460
|
+
const suffix = color ? c.reset : '';
|
|
461
|
+
renderedLines.push(prefix + rowText + suffix);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (chunkIndex !== chunks.length - 1) {
|
|
465
|
+
renderedLines.push('');
|
|
466
|
+
}
|
|
467
|
+
}
|
|
407
468
|
|
|
408
|
-
return
|
|
409
|
-
.split('\n')
|
|
410
|
-
.map(line => line.replace(/[^\s]/g, '█'))
|
|
411
|
-
.join('\n');
|
|
469
|
+
return renderedLines.join('\n');
|
|
412
470
|
}
|
|
413
471
|
|
|
414
472
|
function parseAgentFile(filePath) {
|
|
@@ -453,7 +511,7 @@ function parseAgentFile(filePath) {
|
|
|
453
511
|
let asciiArt = '';
|
|
454
512
|
const asciiMatch = content.match(/```text\n([\s\S]+?)\n```/);
|
|
455
513
|
if (asciiMatch) {
|
|
456
|
-
asciiArt =
|
|
514
|
+
asciiArt = asciiMatch[1];
|
|
457
515
|
}
|
|
458
516
|
|
|
459
517
|
return {
|
|
@@ -1191,15 +1249,16 @@ function habakkukStones() {
|
|
|
1191
1249
|
console.log('');
|
|
1192
1250
|
}
|
|
1193
1251
|
|
|
1194
|
-
function checkForUpdates() {
|
|
1252
|
+
async function checkForUpdates() {
|
|
1195
1253
|
const shouldCheck = process.env.WTV_NO_UPDATE_CHECK !== '1' && process.env.CODEHOGG_NO_UPDATE_CHECK !== '1';
|
|
1196
1254
|
if (!shouldCheck) return;
|
|
1197
1255
|
if (!process.stdout.isTTY) return;
|
|
1198
1256
|
|
|
1199
1257
|
try {
|
|
1258
|
+
const packageName = getPackageName();
|
|
1200
1259
|
const notifier = updateNotifier({
|
|
1201
1260
|
pkg: {
|
|
1202
|
-
name:
|
|
1261
|
+
name: packageName,
|
|
1203
1262
|
version: getVersion(),
|
|
1204
1263
|
},
|
|
1205
1264
|
updateCheckInterval: 1000 * 60 * 60 * 24 * 7,
|
|
@@ -1209,31 +1268,66 @@ function checkForUpdates() {
|
|
|
1209
1268
|
const update = notifier.update;
|
|
1210
1269
|
if (!update) return;
|
|
1211
1270
|
|
|
1271
|
+
const npmGlobalCmd = `npm install -g ${packageName}@latest`;
|
|
1272
|
+
const npxCmd = `npx -y ${packageName}@latest`;
|
|
1273
|
+
|
|
1212
1274
|
notifier.notify({
|
|
1213
1275
|
message: `Update available ${c.dim}${update.current}${c.reset} → ${c.green}${update.latest}${c.reset}
|
|
1214
|
-
Run ${c.cyan}npx writethevision update${c.reset} to get the latest version`,
|
|
1215
|
-
defer: false,
|
|
1216
|
-
boxenOpts: {
|
|
1217
|
-
padding: 1,
|
|
1218
|
-
margin: 1,
|
|
1219
|
-
align: 'center',
|
|
1220
|
-
borderColor: 'yellow',
|
|
1221
|
-
borderStyle: 'round',
|
|
1222
|
-
},
|
|
1223
|
-
});
|
|
1224
1276
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1277
|
+
Update global install:
|
|
1278
|
+
${c.cyan}${npmGlobalCmd}${c.reset}
|
|
1279
|
+
|
|
1280
|
+
Or run latest once:
|
|
1281
|
+
${c.cyan}${npxCmd}${c.reset}
|
|
1282
|
+
`,
|
|
1227
1283
|
defer: false,
|
|
1228
1284
|
boxenOpts: {
|
|
1229
1285
|
padding: 1,
|
|
1230
1286
|
margin: 1,
|
|
1231
|
-
align: '
|
|
1287
|
+
align: 'left',
|
|
1232
1288
|
borderColor: 'yellow',
|
|
1233
1289
|
borderStyle: 'round',
|
|
1234
1290
|
},
|
|
1235
1291
|
});
|
|
1236
|
-
|
|
1292
|
+
|
|
1293
|
+
const shouldPrompt = process.stdin.isTTY && process.env.WTV_NO_AUTO_UPDATE !== '1' && !process.env.CI;
|
|
1294
|
+
if (!shouldPrompt) return;
|
|
1295
|
+
|
|
1296
|
+
const isDevCheckout = existsSync(join(PACKAGE_ROOT, '.git'));
|
|
1297
|
+
if (isDevCheckout) return;
|
|
1298
|
+
|
|
1299
|
+
const wantsUpdate = await confirm(`Update ${packageName} to v${update.latest} now?`, false);
|
|
1300
|
+
if (!wantsUpdate) return;
|
|
1301
|
+
|
|
1302
|
+
const argv1 = process.argv[1] || '';
|
|
1303
|
+
const userAgent = process.env.npm_config_user_agent || '';
|
|
1304
|
+
const runningViaNpx = argv1.includes('_npx') || userAgent.includes('npx/');
|
|
1305
|
+
|
|
1306
|
+
if (runningViaNpx) {
|
|
1307
|
+
console.log(`
|
|
1308
|
+
${c.yellow}${sym.warn}${c.reset} You're running via npx cache.`);
|
|
1309
|
+
console.log(` Next run: ${c.cyan}${npxCmd}${c.reset}
|
|
1310
|
+
`);
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
1315
|
+
const result = spawnSync(npmBin, ['install', '-g', `${packageName}@latest`], { stdio: 'inherit' });
|
|
1316
|
+
|
|
1317
|
+
if (result.status !== 0) {
|
|
1318
|
+
console.log(`
|
|
1319
|
+
${c.red}${sym.cross}${c.reset} Update failed.`);
|
|
1320
|
+
console.log(` Try manually: ${c.cyan}${npmGlobalCmd}${c.reset}
|
|
1321
|
+
`);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
console.log(`
|
|
1326
|
+
${c.green}${sym.check}${c.reset} Updated ${packageName} to ${update.latest}.`);
|
|
1327
|
+
console.log(` Re-run ${c.cyan}wtv${c.reset} to use the new version.
|
|
1328
|
+
`);
|
|
1329
|
+
process.exit(0);
|
|
1330
|
+
} catch {
|
|
1237
1331
|
}
|
|
1238
1332
|
}
|
|
1239
1333
|
|
|
@@ -1685,11 +1779,7 @@ async function meetTheTeam() {
|
|
|
1685
1779
|
console.log(` ${c.bold}${c.yellow}PAUL — THE MASTERBUILDER${c.reset}`);
|
|
1686
1780
|
console.log(` ${c.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}`);
|
|
1687
1781
|
const paulAgent = parseAgentFile(join(TEMPLATES_DIR, 'agents', 'paul.md'));
|
|
1688
|
-
|
|
1689
|
-
console.log(paulAgent.asciiArt);
|
|
1690
|
-
} else {
|
|
1691
|
-
console.log(AVATARS.paul);
|
|
1692
|
-
}
|
|
1782
|
+
console.log(AVATARS.paul || (paulAgent && paulAgent.asciiArt ? paulAgent.asciiArt : ''));
|
|
1693
1783
|
await sleep(shortPause);
|
|
1694
1784
|
console.log(`
|
|
1695
1785
|
${c.cyan}"According to the grace of God which is given unto me,${c.reset}
|
|
@@ -1719,11 +1809,7 @@ async function meetTheTeam() {
|
|
|
1719
1809
|
console.log(` ${artisan.color}${c.bold}${artisan.name}${c.reset}`);
|
|
1720
1810
|
const templatePath = join(TEMPLATES_DIR, 'agents', artisan.file);
|
|
1721
1811
|
const agent = parseAgentFile(templatePath);
|
|
1722
|
-
|
|
1723
|
-
console.log(agent.asciiArt);
|
|
1724
|
-
} else {
|
|
1725
|
-
console.log(AVATARS[artisan.id]);
|
|
1726
|
-
}
|
|
1812
|
+
console.log(AVATARS[artisan.id] || (agent && agent.asciiArt ? agent.asciiArt : ''));
|
|
1727
1813
|
console.log(` ${c.dim}${artisan.domain}${c.reset}`);
|
|
1728
1814
|
if (artisan.verse) {
|
|
1729
1815
|
console.log(` ${c.dim}${artisan.verse.ref}${c.reset} ${c.dim}"${artisan.verse.text}"${c.reset}`);
|
|
@@ -3522,13 +3608,15 @@ async function agentsInteractive() {
|
|
|
3522
3608
|
if (agent.wantsGlobal) statusText += c.blue + 'Global ' + c.reset;
|
|
3523
3609
|
if (!agent.wantsLocal && !agent.wantsGlobal) statusText = c.dim + 'None' + c.reset;
|
|
3524
3610
|
|
|
3611
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
3612
|
+
const detailWidth = Math.max(20, terminalWidth - detailStartX - 1);
|
|
3613
|
+
const nameBanner = renderBlockText(agent.name, { maxWidth: detailWidth, color: c.magenta, bold: true });
|
|
3614
|
+
|
|
3525
3615
|
const details = [
|
|
3526
|
-
|
|
3616
|
+
nameBanner,
|
|
3527
3617
|
`Status: ${statusText}`,
|
|
3528
3618
|
'',
|
|
3529
3619
|
`${c.dim}${agent.description}${c.reset}`,
|
|
3530
|
-
'',
|
|
3531
|
-
agent.asciiArt ? agent.asciiArt : `${c.dim}[ No Portrait ]${c.reset}`
|
|
3532
3620
|
];
|
|
3533
3621
|
|
|
3534
3622
|
// Draw details
|
|
@@ -4220,7 +4308,7 @@ export async function run(args) {
|
|
|
4220
4308
|
const scope = opts.global ? 'global' : 'project';
|
|
4221
4309
|
|
|
4222
4310
|
if (opts.command !== 'version') {
|
|
4223
|
-
checkForUpdates();
|
|
4311
|
+
await checkForUpdates();
|
|
4224
4312
|
}
|
|
4225
4313
|
|
|
4226
4314
|
switch (opts.command) {
|