@staff0rd/assist 0.13.3 → 0.14.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/README.md CHANGED
@@ -3,6 +3,13 @@
3
3
  A CLI tool for enforcing determinism in LLM development workflow automation.
4
4
 
5
5
  ## Installation
6
+ You can install `assist` globally using npm:
7
+
8
+ ```bash
9
+ npm install -g @staff0rd/assist
10
+ ```
11
+
12
+ ## Local Development
6
13
 
7
14
  ```bash
8
15
  # Clone the repository
@@ -27,6 +34,9 @@ After installation, the `assist` command will be available globally.
27
34
  - `assist new` - Initialize a new Vite React TypeScript project
28
35
  - `assist sync` - Copy command files to `~/.claude/commands`
29
36
  - `assist commit <message>` - Create a git commit with validation
37
+ - `assist prs` - List pull requests for the current repository
38
+ - `assist run <name>` - Run a configured command from assist.yml
39
+ - `assist run add` - Add a new run configuration to assist.yml
30
40
  - `assist update` - Update claude-code to the latest version
31
41
  - `assist verify` - Run all verify:* scripts from package.json in parallel
32
42
  - `assist verify init` - Add verify scripts to a project
@@ -0,0 +1,39 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import chalk from "chalk";
3
+
4
+ const TRAILING_SLASH_SCRIPT = ` <script>
5
+ if (!window.location.pathname.endsWith('/')) {
6
+ window.location.href = \`\${window.location.pathname}/\${window.location.search}\${window.location.hash}\`;
7
+ }
8
+ </script>`;
9
+
10
+ export function redirect(): void {
11
+ const indexPath = "index.html";
12
+
13
+ if (!existsSync(indexPath)) {
14
+ console.log(chalk.yellow("No index.html found"));
15
+ return;
16
+ }
17
+
18
+ const content = readFileSync(indexPath, "utf-8");
19
+
20
+ if (content.includes("window.location.pathname.endsWith('/')")) {
21
+ console.log(chalk.dim("Trailing slash script already present"));
22
+ return;
23
+ }
24
+
25
+ const headCloseIndex = content.indexOf("</head>");
26
+ if (headCloseIndex === -1) {
27
+ console.log(chalk.red("Could not find </head> tag in index.html"));
28
+ return;
29
+ }
30
+
31
+ const newContent =
32
+ content.slice(0, headCloseIndex) +
33
+ TRAILING_SLASH_SCRIPT +
34
+ "\n " +
35
+ content.slice(headCloseIndex);
36
+
37
+ writeFileSync(indexPath, newContent);
38
+ console.log(chalk.green("Added trailing slash redirect to index.html"));
39
+ }
package/dist/index.js CHANGED
@@ -246,17 +246,46 @@ async function init() {
246
246
  );
247
247
  }
248
248
 
249
+ // src/commands/deploy/redirect.ts
250
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
251
+ import chalk3 from "chalk";
252
+ var TRAILING_SLASH_SCRIPT = ` <script>
253
+ if (!window.location.pathname.endsWith('/')) {
254
+ window.location.href = \`\${window.location.pathname}/\${window.location.search}\${window.location.hash}\`;
255
+ }
256
+ </script>`;
257
+ function redirect() {
258
+ const indexPath = "index.html";
259
+ if (!existsSync3(indexPath)) {
260
+ console.log(chalk3.yellow("No index.html found"));
261
+ return;
262
+ }
263
+ const content = readFileSync3(indexPath, "utf-8");
264
+ if (content.includes("window.location.pathname.endsWith('/')")) {
265
+ console.log(chalk3.dim("Trailing slash script already present"));
266
+ return;
267
+ }
268
+ const headCloseIndex = content.indexOf("</head>");
269
+ if (headCloseIndex === -1) {
270
+ console.log(chalk3.red("Could not find </head> tag in index.html"));
271
+ return;
272
+ }
273
+ const newContent = content.slice(0, headCloseIndex) + TRAILING_SLASH_SCRIPT + "\n " + content.slice(headCloseIndex);
274
+ writeFileSync3(indexPath, newContent);
275
+ console.log(chalk3.green("Added trailing slash redirect to index.html"));
276
+ }
277
+
249
278
  // src/commands/devlog/list.ts
250
279
  import { execSync as execSync4 } from "child_process";
251
280
  import { basename as basename2 } from "path";
252
- import chalk4 from "chalk";
281
+ import chalk5 from "chalk";
253
282
 
254
283
  // src/commands/devlog/shared.ts
255
284
  import { execSync as execSync3 } from "child_process";
256
- import chalk3 from "chalk";
285
+ import chalk4 from "chalk";
257
286
 
258
287
  // src/commands/devlog/loadDevlogEntries.ts
259
- import { readdirSync, readFileSync as readFileSync3 } from "fs";
288
+ import { readdirSync, readFileSync as readFileSync4 } from "fs";
260
289
  import { homedir } from "os";
261
290
  import { join as join3 } from "path";
262
291
  var DEVLOG_DIR = join3(homedir(), "git/blog/src/content/devlog");
@@ -265,7 +294,7 @@ function loadDevlogEntries(repoName) {
265
294
  try {
266
295
  const files = readdirSync(DEVLOG_DIR).filter((f) => f.endsWith(".md"));
267
296
  for (const file of files) {
268
- const content = readFileSync3(join3(DEVLOG_DIR, file), "utf-8");
297
+ const content = readFileSync4(join3(DEVLOG_DIR, file), "utf-8");
269
298
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
270
299
  if (frontmatterMatch) {
271
300
  const frontmatter = frontmatterMatch[1];
@@ -314,13 +343,13 @@ function shouldIgnoreCommit(files, ignorePaths) {
314
343
  }
315
344
  function printCommitsWithFiles(commits, ignore2, verbose) {
316
345
  for (const commit2 of commits) {
317
- console.log(` ${chalk3.yellow(commit2.hash)} ${commit2.message}`);
346
+ console.log(` ${chalk4.yellow(commit2.hash)} ${commit2.message}`);
318
347
  if (verbose) {
319
348
  const visibleFiles = commit2.files.filter(
320
349
  (file) => !ignore2.some((p) => file.startsWith(p))
321
350
  );
322
351
  for (const file of visibleFiles) {
323
- console.log(` ${chalk3.dim(file)}`);
352
+ console.log(` ${chalk4.dim(file)}`);
324
353
  }
325
354
  }
326
355
  }
@@ -373,12 +402,12 @@ function list(options) {
373
402
  isFirst = false;
374
403
  const entries = devlogEntries.get(date);
375
404
  if (skipDays.has(date)) {
376
- console.log(`${chalk4.bold.blue(date)} ${chalk4.dim("skipped")}`);
405
+ console.log(`${chalk5.bold.blue(date)} ${chalk5.dim("skipped")}`);
377
406
  } else if (entries && entries.length > 0) {
378
- const entryInfo = entries.map((e) => `${chalk4.green(e.version)} ${e.title}`).join(" | ");
379
- console.log(`${chalk4.bold.blue(date)} ${entryInfo}`);
407
+ const entryInfo = entries.map((e) => `${chalk5.green(e.version)} ${e.title}`).join(" | ");
408
+ console.log(`${chalk5.bold.blue(date)} ${entryInfo}`);
380
409
  } else {
381
- console.log(`${chalk4.bold.blue(date)} ${chalk4.red("\u26A0 devlog missing")}`);
410
+ console.log(`${chalk5.bold.blue(date)} ${chalk5.red("\u26A0 devlog missing")}`);
382
411
  }
383
412
  printCommitsWithFiles(dateCommits, ignore2, options.verbose ?? false);
384
413
  }
@@ -386,7 +415,7 @@ function list(options) {
386
415
 
387
416
  // src/commands/devlog/next.ts
388
417
  import { execSync as execSync5 } from "child_process";
389
- import chalk5 from "chalk";
418
+ import chalk6 from "chalk";
390
419
 
391
420
  // src/commands/devlog/getLastVersionInfo.ts
392
421
  import semver from "semver";
@@ -456,36 +485,36 @@ function next(options) {
456
485
  const targetDate = dates[0];
457
486
  if (!targetDate) {
458
487
  if (lastInfo) {
459
- console.log(chalk5.dim("No commits after last versioned entry"));
488
+ console.log(chalk6.dim("No commits after last versioned entry"));
460
489
  } else {
461
- console.log(chalk5.dim("No commits found"));
490
+ console.log(chalk6.dim("No commits found"));
462
491
  }
463
492
  return;
464
493
  }
465
494
  const commits = commitsByDate.get(targetDate) ?? [];
466
- console.log(`${chalk5.bold("name:")} ${repoName}`);
495
+ console.log(`${chalk6.bold("name:")} ${repoName}`);
467
496
  if (patchVersion && minorVersion) {
468
497
  console.log(
469
- `${chalk5.bold("version:")} ${patchVersion} (patch) or ${minorVersion} (minor)`
498
+ `${chalk6.bold("version:")} ${patchVersion} (patch) or ${minorVersion} (minor)`
470
499
  );
471
500
  } else {
472
- console.log(`${chalk5.bold("version:")} v0.1 (initial)`);
501
+ console.log(`${chalk6.bold("version:")} v0.1 (initial)`);
473
502
  }
474
- console.log(`${chalk5.bold.blue(targetDate)}`);
503
+ console.log(`${chalk6.bold.blue(targetDate)}`);
475
504
  printCommitsWithFiles(commits, ignore2, options.verbose ?? false);
476
505
  }
477
506
 
478
507
  // src/commands/devlog/skip.ts
479
- import chalk6 from "chalk";
508
+ import chalk7 from "chalk";
480
509
  function skip(date) {
481
510
  if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
482
- console.log(chalk6.red("Invalid date format. Use YYYY-MM-DD"));
511
+ console.log(chalk7.red("Invalid date format. Use YYYY-MM-DD"));
483
512
  process.exit(1);
484
513
  }
485
514
  const config = loadConfig();
486
515
  const skipDays = config.devlog?.skip?.days ?? [];
487
516
  if (skipDays.includes(date)) {
488
- console.log(chalk6.yellow(`${date} is already in skip list`));
517
+ console.log(chalk7.yellow(`${date} is already in skip list`));
489
518
  return;
490
519
  }
491
520
  skipDays.push(date);
@@ -498,19 +527,19 @@ function skip(date) {
498
527
  }
499
528
  };
500
529
  saveConfig(config);
501
- console.log(chalk6.green(`Added ${date} to skip list`));
530
+ console.log(chalk7.green(`Added ${date} to skip list`));
502
531
  }
503
532
 
504
533
  // src/commands/devlog/version.ts
505
- import { readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
534
+ import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
506
535
  import { join as join4 } from "path";
507
- import chalk7 from "chalk";
536
+ import chalk8 from "chalk";
508
537
  import semver2 from "semver";
509
538
  function getLatestVersion(repoName) {
510
539
  try {
511
540
  const files = readdirSync2(DEVLOG_DIR).filter((f) => f.endsWith(".md")).sort().reverse();
512
541
  for (const file of files) {
513
- const content = readFileSync4(join4(DEVLOG_DIR, file), "utf-8");
542
+ const content = readFileSync5(join4(DEVLOG_DIR, file), "utf-8");
514
543
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
515
544
  if (frontmatterMatch) {
516
545
  const frontmatter = frontmatterMatch[1];
@@ -541,16 +570,16 @@ function version() {
541
570
  const name = getRepoName();
542
571
  const lastVersion = getLatestVersion(name);
543
572
  const nextVersion = lastVersion ? bumpPatchVersion(lastVersion) : null;
544
- console.log(`${chalk7.bold("name:")} ${name}`);
545
- console.log(`${chalk7.bold("last:")} ${lastVersion ?? chalk7.dim("none")}`);
546
- console.log(`${chalk7.bold("next:")} ${nextVersion ?? chalk7.dim("none")}`);
573
+ console.log(`${chalk8.bold("name:")} ${name}`);
574
+ console.log(`${chalk8.bold("last:")} ${lastVersion ?? chalk8.dim("none")}`);
575
+ console.log(`${chalk8.bold("next:")} ${nextVersion ?? chalk8.dim("none")}`);
547
576
  }
548
577
 
549
578
  // src/commands/enable-ralph/index.ts
550
579
  import * as fs from "fs";
551
580
  import * as path from "path";
552
581
  import { fileURLToPath as fileURLToPath2 } from "url";
553
- import chalk8 from "chalk";
582
+ import chalk9 from "chalk";
554
583
  var __dirname3 = path.dirname(fileURLToPath2(import.meta.url));
555
584
  function deepMerge(target, source) {
556
585
  const result = { ...target };
@@ -588,10 +617,10 @@ async function enableRalph() {
588
617
  const merged = deepMerge(targetData, sourceData);
589
618
  const mergedContent = JSON.stringify(merged, null, " ") + "\n";
590
619
  if (mergedContent === targetContent) {
591
- console.log(chalk8.green("settings.local.json already has ralph enabled"));
620
+ console.log(chalk9.green("settings.local.json already has ralph enabled"));
592
621
  return;
593
622
  }
594
- console.log(chalk8.yellow("\nChanges to settings.local.json:"));
623
+ console.log(chalk9.yellow("\nChanges to settings.local.json:"));
595
624
  console.log();
596
625
  printDiff(targetContent, mergedContent);
597
626
  const confirm = await promptConfirm("Apply these changes?");
@@ -604,10 +633,10 @@ async function enableRalph() {
604
633
  }
605
634
 
606
635
  // src/commands/verify/init.ts
607
- import chalk19 from "chalk";
636
+ import chalk20 from "chalk";
608
637
 
609
638
  // src/shared/promptMultiselect.ts
610
- import chalk9 from "chalk";
639
+ import chalk10 from "chalk";
611
640
  import enquirer3 from "enquirer";
612
641
  async function promptMultiselect(message, options) {
613
642
  const { selected } = await enquirer3.prompt({
@@ -616,7 +645,7 @@ async function promptMultiselect(message, options) {
616
645
  message,
617
646
  choices: options.map((opt) => ({
618
647
  name: opt.value,
619
- message: `${opt.name} - ${chalk9.dim(opt.description)}`
648
+ message: `${opt.name} - ${chalk10.dim(opt.description)}`
620
649
  })),
621
650
  // @ts-expect-error - enquirer types don't include symbols but it's supported
622
651
  symbols: {
@@ -632,7 +661,7 @@ async function promptMultiselect(message, options) {
632
661
  // src/shared/readPackageJson.ts
633
662
  import * as fs2 from "fs";
634
663
  import * as path2 from "path";
635
- import chalk10 from "chalk";
664
+ import chalk11 from "chalk";
636
665
  function findPackageJson() {
637
666
  const packageJsonPath = path2.join(process.cwd(), "package.json");
638
667
  if (fs2.existsSync(packageJsonPath)) {
@@ -646,7 +675,7 @@ function readPackageJson(filePath) {
646
675
  function requirePackageJson() {
647
676
  const packageJsonPath = findPackageJson();
648
677
  if (!packageJsonPath) {
649
- console.error(chalk10.red("No package.json found in current directory"));
678
+ console.error(chalk11.red("No package.json found in current directory"));
650
679
  process.exit(1);
651
680
  }
652
681
  const pkg = readPackageJson(packageJsonPath);
@@ -684,13 +713,13 @@ var EXPECTED_SCRIPTS = {
684
713
  };
685
714
 
686
715
  // src/commands/verify/setup/setupBuild.ts
687
- import chalk12 from "chalk";
716
+ import chalk13 from "chalk";
688
717
 
689
718
  // src/commands/verify/installPackage.ts
690
719
  import { execSync as execSync6 } from "child_process";
691
720
  import * as fs3 from "fs";
692
721
  import * as path3 from "path";
693
- import chalk11 from "chalk";
722
+ import chalk12 from "chalk";
694
723
  function writePackageJson(filePath, pkg) {
695
724
  fs3.writeFileSync(filePath, `${JSON.stringify(pkg, null, 2)}
696
725
  `);
@@ -705,12 +734,12 @@ function addScript(pkg, name, command) {
705
734
  };
706
735
  }
707
736
  function installPackage(name, cwd) {
708
- console.log(chalk11.dim(`Installing ${name}...`));
737
+ console.log(chalk12.dim(`Installing ${name}...`));
709
738
  try {
710
739
  execSync6(`npm install -D ${name}`, { stdio: "inherit", cwd });
711
740
  return true;
712
741
  } catch {
713
- console.error(chalk11.red(`Failed to install ${name}`));
742
+ console.error(chalk12.red(`Failed to install ${name}`));
714
743
  return false;
715
744
  }
716
745
  }
@@ -731,10 +760,10 @@ function addToKnipIgnoreBinaries(cwd, binary) {
731
760
  `${JSON.stringify(knipConfig, null, " ")}
732
761
  `
733
762
  );
734
- console.log(chalk11.dim(`Added '${binary}' to knip.json ignoreBinaries`));
763
+ console.log(chalk12.dim(`Added '${binary}' to knip.json ignoreBinaries`));
735
764
  }
736
765
  } catch {
737
- console.log(chalk11.yellow("Warning: Could not update knip.json"));
766
+ console.log(chalk12.yellow("Warning: Could not update knip.json"));
738
767
  }
739
768
  }
740
769
  function setupVerifyScript(packageJsonPath, scriptName, command) {
@@ -746,7 +775,7 @@ function setupVerifyScript(packageJsonPath, scriptName, command) {
746
775
 
747
776
  // src/commands/verify/setup/setupBuild.ts
748
777
  async function setupBuild(packageJsonPath, hasVite, hasTypescript) {
749
- console.log(chalk12.blue("\nSetting up build verification..."));
778
+ console.log(chalk13.blue("\nSetting up build verification..."));
750
779
  let command;
751
780
  if (hasVite && hasTypescript) {
752
781
  command = "tsc -b && vite build --logLevel error";
@@ -755,16 +784,16 @@ async function setupBuild(packageJsonPath, hasVite, hasTypescript) {
755
784
  } else {
756
785
  command = "tsc --noEmit";
757
786
  }
758
- console.log(chalk12.dim(`Using: ${command}`));
787
+ console.log(chalk13.dim(`Using: ${command}`));
759
788
  const pkg = readPackageJson(packageJsonPath);
760
789
  writePackageJson(packageJsonPath, addScript(pkg, "verify:build", command));
761
790
  }
762
791
 
763
792
  // src/commands/verify/setup/setupDuplicateCode.ts
764
793
  import * as path4 from "path";
765
- import chalk13 from "chalk";
794
+ import chalk14 from "chalk";
766
795
  async function setupDuplicateCode(packageJsonPath) {
767
- console.log(chalk13.blue("\nSetting up jscpd..."));
796
+ console.log(chalk14.blue("\nSetting up jscpd..."));
768
797
  const cwd = path4.dirname(packageJsonPath);
769
798
  const pkg = readPackageJson(packageJsonPath);
770
799
  const hasJscpd = !!pkg.dependencies?.jscpd || !!pkg.devDependencies?.jscpd;
@@ -780,9 +809,9 @@ async function setupDuplicateCode(packageJsonPath) {
780
809
 
781
810
  // src/commands/verify/setup/setupHardcodedColors.ts
782
811
  import * as path5 from "path";
783
- import chalk14 from "chalk";
812
+ import chalk15 from "chalk";
784
813
  async function setupHardcodedColors(packageJsonPath, hasOpenColor) {
785
- console.log(chalk14.blue("\nSetting up hardcoded colors check..."));
814
+ console.log(chalk15.blue("\nSetting up hardcoded colors check..."));
786
815
  const cwd = path5.dirname(packageJsonPath);
787
816
  if (!hasOpenColor) {
788
817
  installPackage("open-color", cwd);
@@ -797,9 +826,9 @@ async function setupHardcodedColors(packageJsonPath, hasOpenColor) {
797
826
 
798
827
  // src/commands/verify/setup/setupKnip.ts
799
828
  import * as path6 from "path";
800
- import chalk15 from "chalk";
829
+ import chalk16 from "chalk";
801
830
  async function setupKnip(packageJsonPath) {
802
- console.log(chalk15.blue("\nSetting up knip..."));
831
+ console.log(chalk16.blue("\nSetting up knip..."));
803
832
  const cwd = path6.dirname(packageJsonPath);
804
833
  const pkg = readPackageJson(packageJsonPath);
805
834
  if (!pkg.devDependencies?.knip && !installPackage("knip", cwd)) {
@@ -814,18 +843,18 @@ async function setupKnip(packageJsonPath) {
814
843
 
815
844
  // src/commands/verify/setup/setupLint.ts
816
845
  import * as path7 from "path";
817
- import chalk17 from "chalk";
846
+ import chalk18 from "chalk";
818
847
 
819
848
  // src/commands/lint/init.ts
820
849
  import { execSync as execSync8 } from "child_process";
821
- import { existsSync as existsSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
850
+ import { existsSync as existsSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
822
851
  import { dirname as dirname7, join as join8 } from "path";
823
852
  import { fileURLToPath as fileURLToPath3 } from "url";
824
- import chalk16 from "chalk";
853
+ import chalk17 from "chalk";
825
854
 
826
855
  // src/shared/removeEslint.ts
827
856
  import { execSync as execSync7 } from "child_process";
828
- import { existsSync as existsSync6, readFileSync as readFileSync8, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
857
+ import { existsSync as existsSync7, readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync6 } from "fs";
829
858
  function removeEslint(options = {}) {
830
859
  const removedFromPackageJson = removeEslintFromPackageJson(options);
831
860
  const removedConfigFiles = removeEslintConfigFiles();
@@ -838,10 +867,10 @@ function removeEslint(options = {}) {
838
867
  }
839
868
  function removeEslintFromPackageJson(options) {
840
869
  const packageJsonPath = "package.json";
841
- if (!existsSync6(packageJsonPath)) {
870
+ if (!existsSync7(packageJsonPath)) {
842
871
  return false;
843
872
  }
844
- const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
873
+ const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
845
874
  let modified = false;
846
875
  if (packageJson.dependencies) {
847
876
  for (const key of Object.keys(packageJson.dependencies)) {
@@ -869,7 +898,7 @@ function removeEslintFromPackageJson(options) {
869
898
  }
870
899
  }
871
900
  if (modified) {
872
- writeFileSync5(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
901
+ writeFileSync6(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
873
902
  `);
874
903
  console.log("Removed eslint references from package.json");
875
904
  }
@@ -890,7 +919,7 @@ function removeEslintConfigFiles() {
890
919
  ];
891
920
  let removed = false;
892
921
  for (const configFile of eslintConfigFiles) {
893
- if (existsSync6(configFile)) {
922
+ if (existsSync7(configFile)) {
894
923
  unlinkSync(configFile);
895
924
  console.log(`Removed ${configFile}`);
896
925
  removed = true;
@@ -904,17 +933,17 @@ var __dirname4 = dirname7(fileURLToPath3(import.meta.url));
904
933
  async function init2() {
905
934
  removeEslint();
906
935
  const biomeConfigPath = "biome.json";
907
- if (!existsSync7(biomeConfigPath)) {
936
+ if (!existsSync8(biomeConfigPath)) {
908
937
  console.log("Initializing Biome...");
909
938
  execSync8("npx @biomejs/biome init", { stdio: "inherit" });
910
939
  }
911
- if (!existsSync7(biomeConfigPath)) {
940
+ if (!existsSync8(biomeConfigPath)) {
912
941
  console.log("No biome.json found, skipping linter config");
913
942
  return;
914
943
  }
915
944
  const linterConfigPath = join8(__dirname4, "commands/lint/biome.linter.json");
916
- const linterConfig = JSON.parse(readFileSync9(linterConfigPath, "utf-8"));
917
- const biomeConfig = JSON.parse(readFileSync9(biomeConfigPath, "utf-8"));
945
+ const linterConfig = JSON.parse(readFileSync10(linterConfigPath, "utf-8"));
946
+ const biomeConfig = JSON.parse(readFileSync10(biomeConfigPath, "utf-8"));
918
947
  const oldContent = `${JSON.stringify(biomeConfig, null, 2)}
919
948
  `;
920
949
  biomeConfig.linter = linterConfig.linter;
@@ -927,21 +956,21 @@ async function init2() {
927
956
  console.log("biome.json already has the correct linter config");
928
957
  return;
929
958
  }
930
- console.log(chalk16.yellow("\n\u26A0\uFE0F biome.json will be updated:"));
959
+ console.log(chalk17.yellow("\n\u26A0\uFE0F biome.json will be updated:"));
931
960
  console.log();
932
961
  printDiff(oldContent, newContent);
933
- const confirm = await promptConfirm(chalk16.red("Update biome.json?"));
962
+ const confirm = await promptConfirm(chalk17.red("Update biome.json?"));
934
963
  if (!confirm) {
935
964
  console.log("Skipped biome.json update");
936
965
  return;
937
966
  }
938
- writeFileSync6(biomeConfigPath, newContent);
967
+ writeFileSync7(biomeConfigPath, newContent);
939
968
  console.log("Updated biome.json with linter config");
940
969
  }
941
970
 
942
971
  // src/commands/verify/setup/setupLint.ts
943
972
  async function setupLint(packageJsonPath) {
944
- console.log(chalk17.blue("\nSetting up biome..."));
973
+ console.log(chalk18.blue("\nSetting up biome..."));
945
974
  const cwd = path7.dirname(packageJsonPath);
946
975
  const pkg = readPackageJson(packageJsonPath);
947
976
  if (!pkg.devDependencies?.["@biomejs/biome"]) {
@@ -959,9 +988,9 @@ async function setupLint(packageJsonPath) {
959
988
 
960
989
  // src/commands/verify/setup/setupTest.ts
961
990
  import * as path8 from "path";
962
- import chalk18 from "chalk";
991
+ import chalk19 from "chalk";
963
992
  async function setupTest(packageJsonPath) {
964
- console.log(chalk18.blue("\nSetting up vitest..."));
993
+ console.log(chalk19.blue("\nSetting up vitest..."));
965
994
  const cwd = path8.dirname(packageJsonPath);
966
995
  const pkg = readPackageJson(packageJsonPath);
967
996
  if (!pkg.devDependencies?.vitest && !installPackage("vitest", cwd)) {
@@ -1104,16 +1133,16 @@ async function init3() {
1104
1133
  const setup = detectExistingSetup(pkg);
1105
1134
  const availableOptions = getAvailableOptions(setup);
1106
1135
  if (availableOptions.length === 0) {
1107
- console.log(chalk19.green("All verify scripts are already configured!"));
1136
+ console.log(chalk20.green("All verify scripts are already configured!"));
1108
1137
  return;
1109
1138
  }
1110
- console.log(chalk19.bold("Available verify scripts to add:\n"));
1139
+ console.log(chalk20.bold("Available verify scripts to add:\n"));
1111
1140
  const selected = await promptMultiselect(
1112
1141
  "Select verify scripts to add:",
1113
1142
  availableOptions
1114
1143
  );
1115
1144
  if (selected.length === 0) {
1116
- console.log(chalk19.yellow("No scripts selected"));
1145
+ console.log(chalk20.yellow("No scripts selected"));
1117
1146
  return;
1118
1147
  }
1119
1148
  for (const choice of selected) {
@@ -1138,28 +1167,28 @@ async function init3() {
1138
1167
  break;
1139
1168
  }
1140
1169
  }
1141
- console.log(chalk19.green(`
1170
+ console.log(chalk20.green(`
1142
1171
  Added ${selected.length} verify script(s):`));
1143
1172
  for (const choice of selected) {
1144
- console.log(chalk19.green(` - verify:${choice}`));
1173
+ console.log(chalk20.green(` - verify:${choice}`));
1145
1174
  }
1146
- console.log(chalk19.dim("\nRun 'assist verify' to run all verify scripts"));
1175
+ console.log(chalk20.dim("\nRun 'assist verify' to run all verify scripts"));
1147
1176
  }
1148
1177
 
1149
1178
  // src/commands/vscode/init.ts
1150
1179
  import * as fs5 from "fs";
1151
1180
  import * as path10 from "path";
1152
- import chalk21 from "chalk";
1181
+ import chalk22 from "chalk";
1153
1182
 
1154
1183
  // src/commands/vscode/createLaunchJson.ts
1155
1184
  import * as fs4 from "fs";
1156
1185
  import * as path9 from "path";
1157
- import chalk20 from "chalk";
1186
+ import chalk21 from "chalk";
1158
1187
  function ensureVscodeFolder() {
1159
1188
  const vscodeDir = path9.join(process.cwd(), ".vscode");
1160
1189
  if (!fs4.existsSync(vscodeDir)) {
1161
1190
  fs4.mkdirSync(vscodeDir);
1162
- console.log(chalk20.dim("Created .vscode folder"));
1191
+ console.log(chalk21.dim("Created .vscode folder"));
1163
1192
  }
1164
1193
  }
1165
1194
  function removeVscodeFromGitignore() {
@@ -1174,7 +1203,7 @@ function removeVscodeFromGitignore() {
1174
1203
  );
1175
1204
  if (filteredLines.length !== lines.length) {
1176
1205
  fs4.writeFileSync(gitignorePath, filteredLines.join("\n"));
1177
- console.log(chalk20.dim("Removed .vscode references from .gitignore"));
1206
+ console.log(chalk21.dim("Removed .vscode references from .gitignore"));
1178
1207
  }
1179
1208
  }
1180
1209
  function createLaunchJson() {
@@ -1192,7 +1221,7 @@ function createLaunchJson() {
1192
1221
  const launchPath = path9.join(process.cwd(), ".vscode", "launch.json");
1193
1222
  fs4.writeFileSync(launchPath, `${JSON.stringify(launchConfig, null, " ")}
1194
1223
  `);
1195
- console.log(chalk20.green("Created .vscode/launch.json"));
1224
+ console.log(chalk21.green("Created .vscode/launch.json"));
1196
1225
  }
1197
1226
  function createSettingsJson() {
1198
1227
  const settings = {
@@ -1211,7 +1240,7 @@ function createSettingsJson() {
1211
1240
  const settingsPath = path9.join(process.cwd(), ".vscode", "settings.json");
1212
1241
  fs4.writeFileSync(settingsPath, `${JSON.stringify(settings, null, " ")}
1213
1242
  `);
1214
- console.log(chalk20.green("Created .vscode/settings.json"));
1243
+ console.log(chalk21.green("Created .vscode/settings.json"));
1215
1244
  }
1216
1245
  function createExtensionsJson() {
1217
1246
  const extensions = {
@@ -1223,7 +1252,7 @@ function createExtensionsJson() {
1223
1252
  `${JSON.stringify(extensions, null, " ")}
1224
1253
  `
1225
1254
  );
1226
- console.log(chalk20.green("Created .vscode/extensions.json"));
1255
+ console.log(chalk21.green("Created .vscode/extensions.json"));
1227
1256
  }
1228
1257
 
1229
1258
  // src/commands/vscode/init.ts
@@ -1255,16 +1284,16 @@ async function init4() {
1255
1284
  });
1256
1285
  }
1257
1286
  if (availableOptions.length === 0) {
1258
- console.log(chalk21.green("VS Code configuration already exists!"));
1287
+ console.log(chalk22.green("VS Code configuration already exists!"));
1259
1288
  return;
1260
1289
  }
1261
- console.log(chalk21.bold("Available VS Code configurations to add:\n"));
1290
+ console.log(chalk22.bold("Available VS Code configurations to add:\n"));
1262
1291
  const selected = await promptMultiselect(
1263
1292
  "Select configurations to add:",
1264
1293
  availableOptions
1265
1294
  );
1266
1295
  if (selected.length === 0) {
1267
- console.log(chalk21.yellow("No configurations selected"));
1296
+ console.log(chalk22.yellow("No configurations selected"));
1268
1297
  return;
1269
1298
  }
1270
1299
  removeVscodeFromGitignore();
@@ -1281,7 +1310,7 @@ async function init4() {
1281
1310
  }
1282
1311
  }
1283
1312
  console.log(
1284
- chalk21.green(`
1313
+ chalk22.green(`
1285
1314
  Added ${selected.length} VS Code configuration(s)`)
1286
1315
  );
1287
1316
  }
@@ -1295,7 +1324,7 @@ async function init5() {
1295
1324
  // src/commands/lint/runFileNameCheck.ts
1296
1325
  import fs7 from "fs";
1297
1326
  import path12 from "path";
1298
- import chalk22 from "chalk";
1327
+ import chalk23 from "chalk";
1299
1328
 
1300
1329
  // src/shared/findSourceFiles.ts
1301
1330
  import fs6 from "fs";
@@ -1347,16 +1376,16 @@ function checkFileNames() {
1347
1376
  function runFileNameCheck() {
1348
1377
  const violations = checkFileNames();
1349
1378
  if (violations.length > 0) {
1350
- console.error(chalk22.red("\nFile name check failed:\n"));
1379
+ console.error(chalk23.red("\nFile name check failed:\n"));
1351
1380
  console.error(
1352
- chalk22.red(
1381
+ chalk23.red(
1353
1382
  " Files without classes or React components should not start with a capital letter.\n"
1354
1383
  )
1355
1384
  );
1356
1385
  for (const violation of violations) {
1357
- console.error(chalk22.red(` ${violation.filePath}`));
1386
+ console.error(chalk23.red(` ${violation.filePath}`));
1358
1387
  console.error(
1359
- chalk22.gray(
1388
+ chalk23.gray(
1360
1389
  ` Rename to: ${violation.fileName.charAt(0).toLowerCase()}${violation.fileName.slice(1)}
1361
1390
  `
1362
1391
  )
@@ -1374,7 +1403,7 @@ function runFileNameCheck() {
1374
1403
 
1375
1404
  // src/commands/lint/runStaticImportCheck.ts
1376
1405
  import fs8 from "fs";
1377
- import chalk23 from "chalk";
1406
+ import chalk24 from "chalk";
1378
1407
  function checkForDynamicImports(filePath) {
1379
1408
  const content = fs8.readFileSync(filePath, "utf-8");
1380
1409
  const lines = content.split("\n");
@@ -1404,15 +1433,15 @@ function checkStaticImports() {
1404
1433
  function runStaticImportCheck() {
1405
1434
  const violations = checkStaticImports();
1406
1435
  if (violations.length > 0) {
1407
- console.error(chalk23.red("\nStatic import check failed:\n"));
1436
+ console.error(chalk24.red("\nStatic import check failed:\n"));
1408
1437
  console.error(
1409
- chalk23.red(
1438
+ chalk24.red(
1410
1439
  " Dynamic imports (require() and import()) are not allowed. Use static imports instead.\n"
1411
1440
  )
1412
1441
  );
1413
1442
  for (const violation of violations) {
1414
- console.error(chalk23.red(` ${violation.filePath}:${violation.line}`));
1415
- console.error(chalk23.gray(` ${violation.content}
1443
+ console.error(chalk24.red(` ${violation.filePath}:${violation.line}`));
1444
+ console.error(chalk24.gray(` ${violation.content}
1416
1445
  `));
1417
1446
  }
1418
1447
  return false;
@@ -1434,7 +1463,7 @@ function lint() {
1434
1463
 
1435
1464
  // src/commands/new/newProject.ts
1436
1465
  import { execSync as execSync9 } from "child_process";
1437
- import { existsSync as existsSync10, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
1466
+ import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
1438
1467
  async function newProject() {
1439
1468
  console.log("Initializing Vite with react-ts template...");
1440
1469
  execSync9("npm create vite@latest . -- --template react-ts", {
@@ -1447,11 +1476,11 @@ async function newProject() {
1447
1476
  }
1448
1477
  function addViteBaseConfig() {
1449
1478
  const viteConfigPath = "vite.config.ts";
1450
- if (!existsSync10(viteConfigPath)) {
1479
+ if (!existsSync11(viteConfigPath)) {
1451
1480
  console.log("No vite.config.ts found, skipping base config");
1452
1481
  return;
1453
1482
  }
1454
- const content = readFileSync11(viteConfigPath, "utf-8");
1483
+ const content = readFileSync12(viteConfigPath, "utf-8");
1455
1484
  if (content.includes("base:")) {
1456
1485
  console.log("vite.config.ts already has base config");
1457
1486
  return;
@@ -1461,14 +1490,14 @@ function addViteBaseConfig() {
1461
1490
  'defineConfig({\n base: "./",'
1462
1491
  );
1463
1492
  if (updated !== content) {
1464
- writeFileSync8(viteConfigPath, updated);
1493
+ writeFileSync9(viteConfigPath, updated);
1465
1494
  console.log('Added base: "./" to vite.config.ts');
1466
1495
  }
1467
1496
  }
1468
1497
 
1469
1498
  // src/commands/prs.ts
1470
1499
  import { execSync as execSync10 } from "child_process";
1471
- import chalk24 from "chalk";
1500
+ import chalk25 from "chalk";
1472
1501
  import enquirer4 from "enquirer";
1473
1502
  var PAGE_SIZE = 10;
1474
1503
  async function prs(options) {
@@ -1503,12 +1532,12 @@ async function displayPaginated(pullRequests) {
1503
1532
  let currentPage = 0;
1504
1533
  const getStatus = (pr) => {
1505
1534
  if (pr.state === "MERGED" && pr.mergedAt) {
1506
- return { label: chalk24.magenta("merged"), date: pr.mergedAt };
1535
+ return { label: chalk25.magenta("merged"), date: pr.mergedAt };
1507
1536
  }
1508
1537
  if (pr.state === "CLOSED" && pr.closedAt) {
1509
- return { label: chalk24.red("closed"), date: pr.closedAt };
1538
+ return { label: chalk25.red("closed"), date: pr.closedAt };
1510
1539
  }
1511
- return { label: chalk24.green("opened"), date: pr.createdAt };
1540
+ return { label: chalk25.green("opened"), date: pr.createdAt };
1512
1541
  };
1513
1542
  const displayPage = (page) => {
1514
1543
  const start = page * PAGE_SIZE;
@@ -1524,9 +1553,9 @@ Page ${page + 1} of ${totalPages} (${pullRequests.length} total)
1524
1553
  const formattedDate = new Date(status.date).toISOString().split("T")[0];
1525
1554
  const fileCount = pr.changedFiles.toLocaleString();
1526
1555
  console.log(
1527
- `${chalk24.cyan(`#${pr.number}`)} ${pr.title} ${chalk24.dim(`(${pr.author.login},`)} ${status.label} ${chalk24.dim(`${formattedDate})`)}`
1556
+ `${chalk25.cyan(`#${pr.number}`)} ${pr.title} ${chalk25.dim(`(${pr.author.login},`)} ${status.label} ${chalk25.dim(`${formattedDate})`)}`
1528
1557
  );
1529
- console.log(chalk24.dim(` ${fileCount} files | ${pr.url}`));
1558
+ console.log(chalk25.dim(` ${fileCount} files | ${pr.url}`));
1530
1559
  console.log();
1531
1560
  }
1532
1561
  };
@@ -1604,7 +1633,7 @@ function getIgnoredFiles() {
1604
1633
  }
1605
1634
 
1606
1635
  // src/commands/refactor/logViolations.ts
1607
- import chalk25 from "chalk";
1636
+ import chalk26 from "chalk";
1608
1637
  var DEFAULT_MAX_LINES = 100;
1609
1638
  function logViolations(violations, maxLines = DEFAULT_MAX_LINES) {
1610
1639
  if (violations.length === 0) {
@@ -1613,43 +1642,43 @@ function logViolations(violations, maxLines = DEFAULT_MAX_LINES) {
1613
1642
  }
1614
1643
  return;
1615
1644
  }
1616
- console.error(chalk25.red(`
1645
+ console.error(chalk26.red(`
1617
1646
  Refactor check failed:
1618
1647
  `));
1619
- console.error(chalk25.red(` The following files exceed ${maxLines} lines:
1648
+ console.error(chalk26.red(` The following files exceed ${maxLines} lines:
1620
1649
  `));
1621
1650
  for (const violation of violations) {
1622
- console.error(chalk25.red(` ${violation.file} (${violation.lines} lines)`));
1651
+ console.error(chalk26.red(` ${violation.file} (${violation.lines} lines)`));
1623
1652
  }
1624
1653
  console.error(
1625
- chalk25.yellow(
1654
+ chalk26.yellow(
1626
1655
  `
1627
1656
  Each file needs to be sensibly refactored, or if there is no sensible
1628
1657
  way to refactor it, ignore it with:
1629
1658
  `
1630
1659
  )
1631
1660
  );
1632
- console.error(chalk25.gray(` assist refactor ignore <file>
1661
+ console.error(chalk26.gray(` assist refactor ignore <file>
1633
1662
  `));
1634
1663
  if (process.env.CLAUDECODE) {
1635
- console.error(chalk25.cyan(`
1664
+ console.error(chalk26.cyan(`
1636
1665
  ## Extracting Code to New Files
1637
1666
  `));
1638
1667
  console.error(
1639
- chalk25.cyan(
1668
+ chalk26.cyan(
1640
1669
  ` When extracting logic from one file to another, consider where the extracted code belongs:
1641
1670
  `
1642
1671
  )
1643
1672
  );
1644
1673
  console.error(
1645
- chalk25.cyan(
1674
+ chalk26.cyan(
1646
1675
  ` 1. Keep related logic together: If the extracted code is tightly coupled to the
1647
1676
  original file's domain, create a new folder containing both the original and extracted files.
1648
1677
  `
1649
1678
  )
1650
1679
  );
1651
1680
  console.error(
1652
- chalk25.cyan(
1681
+ chalk26.cyan(
1653
1682
  ` 2. Share common utilities: If the extracted code can be reused across multiple
1654
1683
  domains, move it to a common/shared folder.
1655
1684
  `
@@ -1766,11 +1795,11 @@ async function check(pattern2, options) {
1766
1795
 
1767
1796
  // src/commands/refactor/ignore.ts
1768
1797
  import fs11 from "fs";
1769
- import chalk26 from "chalk";
1798
+ import chalk27 from "chalk";
1770
1799
  var REFACTOR_YML_PATH2 = "refactor.yml";
1771
1800
  function ignore(file) {
1772
1801
  if (!fs11.existsSync(file)) {
1773
- console.error(chalk26.red(`Error: File does not exist: ${file}`));
1802
+ console.error(chalk27.red(`Error: File does not exist: ${file}`));
1774
1803
  process.exit(1);
1775
1804
  }
1776
1805
  const content = fs11.readFileSync(file, "utf-8");
@@ -1786,7 +1815,7 @@ function ignore(file) {
1786
1815
  fs11.writeFileSync(REFACTOR_YML_PATH2, entry);
1787
1816
  }
1788
1817
  console.log(
1789
- chalk26.green(
1818
+ chalk27.green(
1790
1819
  `Added ${file} to refactor ignore list (max ${maxLines} lines)`
1791
1820
  )
1792
1821
  );
@@ -1894,7 +1923,7 @@ import { fileURLToPath as fileURLToPath4 } from "url";
1894
1923
  // src/commands/sync/syncSettings.ts
1895
1924
  import * as fs12 from "fs";
1896
1925
  import * as path14 from "path";
1897
- import chalk27 from "chalk";
1926
+ import chalk28 from "chalk";
1898
1927
  async function syncSettings(claudeDir, targetBase) {
1899
1928
  const source = path14.join(claudeDir, "settings.json");
1900
1929
  const target = path14.join(targetBase, "settings.json");
@@ -1905,12 +1934,12 @@ async function syncSettings(claudeDir, targetBase) {
1905
1934
  const normalizedTarget = JSON.stringify(JSON.parse(targetContent), null, 2);
1906
1935
  if (normalizedSource !== normalizedTarget) {
1907
1936
  console.log(
1908
- chalk27.yellow("\n\u26A0\uFE0F Warning: settings.json differs from existing file")
1937
+ chalk28.yellow("\n\u26A0\uFE0F Warning: settings.json differs from existing file")
1909
1938
  );
1910
1939
  console.log();
1911
1940
  printDiff(targetContent, sourceContent);
1912
1941
  const confirm = await promptConfirm(
1913
- chalk27.red("Overwrite existing settings.json?"),
1942
+ chalk28.red("Overwrite existing settings.json?"),
1914
1943
  false
1915
1944
  );
1916
1945
  if (!confirm) {
@@ -2092,6 +2121,7 @@ var vscodeCommand = program.command("vscode").description("VS Code configuration
2092
2121
  vscodeCommand.command("init").description("Add VS Code configuration files").action(init4);
2093
2122
  var deployCommand = program.command("deploy").description("Netlify deployment utilities");
2094
2123
  deployCommand.command("init").description("Initialize Netlify project and configure deployment").action(init);
2124
+ deployCommand.command("redirect").description("Add trailing slash redirect script to index.html").action(redirect);
2095
2125
  program.command("enable-ralph").description("Enable ralph-wiggum plugin for spacetraders").action(enableRalph);
2096
2126
  program.command("status-line").description("Format Claude Code status line from JSON stdin").action(statusLine);
2097
2127
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.13.3",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {