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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. 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",
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
- function brickifyAsciiArt(asciiArt) {
406
- if (!asciiArt) return asciiArt;
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 asciiArt
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 = brickifyAsciiArt(asciiMatch[1]);
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: getPackageName(),
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
- notifier.notify({
1226
- message: `Update available ${c.dim}${notifier.update.current}${c.reset} → ${c.green}${notifier.update.latest}${c.reset}\nRun ${c.cyan}npx writethevision update${c.reset} to get the latest version`,
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: 'center',
1287
+ align: 'left',
1232
1288
  borderColor: 'yellow',
1233
1289
  borderStyle: 'round',
1234
1290
  },
1235
1291
  });
1236
- } catch (err) {
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
- if (paulAgent && paulAgent.asciiArt) {
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
- if (agent && agent.asciiArt) {
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
- `${c.magenta}${c.bold}${agent.name.toUpperCase()}${c.reset}`,
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) {