@coder11125/omg 0.3.0 → 0.3.3

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/dist/index.js CHANGED
@@ -39,14 +39,62 @@ function validateConfigKey(key) {
39
39
  }
40
40
  }
41
41
  }
42
+ const ERROR_MAPPINGS = [
43
+ { pattern: /not a git repository/i, command: 'omg init' },
44
+ { pattern: /no upstream branch/i, command: 'omg push -u origin <branch>' },
45
+ { pattern: /merge conflict/i, command: 'omg merge --abort', context: 'or resolve conflicts manually' },
46
+ { pattern: /rebase conflict/i, command: 'omg rebase --abort', context: 'or use --continue after resolving' },
47
+ { pattern: /not fully merged/i, command: 'omg branch -D <branch>', context: 'force delete unmerged branch' },
48
+ { pattern: /no remote/i, command: 'omg remote <url>' },
49
+ { pattern: /uncommitted changes/i, command: 'omg -c "message"', context: 'stage and commit first' },
50
+ { pattern: /local changes/i, command: 'omg stash', context: 'stash changes first' },
51
+ { pattern: /no stash entries/i, command: 'omg stash', context: 'create a stash first' },
52
+ { pattern: /already exists/i, command: 'omg doctor', context: 'check for issues' },
53
+ { pattern: /detached HEAD/i, command: 'omg oops restore-branch' },
54
+ { pattern: /conflict/i, command: 'omg doctor', context: 'resolve conflicts' },
55
+ { pattern: /permission denied/i, command: 'omg doctor' },
56
+ { pattern: /could not resolve host/i, command: 'omg doctor', context: 'check network/remote' },
57
+ ];
58
+ let verboseMode = false;
59
+ function setVerbose(verbose) {
60
+ verboseMode = verbose;
61
+ }
62
+ function handleNerdError(err, context) {
63
+ const rawMessage = formatError(err);
64
+ // Find matching error pattern
65
+ for (const mapping of ERROR_MAPPINGS) {
66
+ if (mapping.pattern.test(rawMessage)) {
67
+ const cmd = context ? mapping.command.replace('<branch>', context) : mapping.command;
68
+ let output = `${chalk.magenta('(OMG)')} 🤓 Nerd Error hidden: Run ${chalk.cyan(cmd)} to solve it`;
69
+ if (mapping.context) {
70
+ output += chalk.dim(` (${mapping.context})`);
71
+ }
72
+ console.error(output);
73
+ if (verboseMode) {
74
+ console.error(chalk.dim('\nDetails:'));
75
+ console.error(chalk.dim(rawMessage));
76
+ }
77
+ return;
78
+ }
79
+ }
80
+ // Fallback for unmapped errors
81
+ console.error(`${chalk.magenta('(OMG)')} 🤓 Nerd Error hidden: Run ${chalk.cyan('omg doctor')} to diagnose`);
82
+ if (verboseMode) {
83
+ console.error(chalk.dim('\nDetails:'));
84
+ console.error(chalk.dim(rawMessage));
85
+ }
86
+ }
42
87
  const program = new Command();
43
88
  program
44
89
  .name('omg')
45
90
  .description('Oh My Git - a friendly CLI wrapper for common git tasks')
46
91
  .version(PACKAGE_VERSION, '-V, --version', 'output the current version')
47
- .option('-v, --visit <branch>', 'checkout the specified branch')
92
+ .option('--verbose', 'show detailed error output (show nerd errors)')
93
+ .option('--visit <branch>', 'checkout the specified branch')
48
94
  .option('-c, --commit <message>', 'stage all changes and commit with a message')
49
95
  .action(async (opts) => {
96
+ // Set verbose mode globally
97
+ setVerbose(opts.verbose ?? false);
50
98
  let didSomething = false;
51
99
  if (opts.visit) {
52
100
  didSomething = true;
@@ -60,6 +108,13 @@ program
60
108
  program.help();
61
109
  }
62
110
  });
111
+ // Hook to set verbose mode before any command runs
112
+ program.hook('preAction', (thisCommand) => {
113
+ const opts = thisCommand.opts();
114
+ if (opts.verbose !== undefined) {
115
+ setVerbose(opts.verbose);
116
+ }
117
+ });
63
118
  // ---------------------------------------------------------------------------
64
119
  // branch subcommand
65
120
  // ---------------------------------------------------------------------------
@@ -369,7 +424,7 @@ async function checkoutBranch(branch) {
369
424
  }
370
425
  catch (err) {
371
426
  spinner.fail(chalk.red(`Failed to checkout '${branch}'`));
372
- console.error(chalk.red(formatError(err)));
427
+ handleNerdError(err);
373
428
  process.exitCode = 1;
374
429
  }
375
430
  }
@@ -384,7 +439,7 @@ async function stageAndCommit(message) {
384
439
  }
385
440
  catch (err) {
386
441
  spinner.fail(chalk.red('Commit failed'));
387
- console.error(chalk.red(formatError(err)));
442
+ handleNerdError(err);
388
443
  process.exitCode = 1;
389
444
  }
390
445
  }
@@ -415,7 +470,7 @@ async function listBranches() {
415
470
  }
416
471
  catch (err) {
417
472
  spinner.fail(chalk.red('Could not list branches'));
418
- console.error(chalk.red(formatError(err)));
473
+ handleNerdError(err);
419
474
  process.exitCode = 1;
420
475
  }
421
476
  }
@@ -446,7 +501,7 @@ async function createBranch(name, switchAfter) {
446
501
  }
447
502
  catch (err) {
448
503
  spinner.fail(chalk.red(`Failed to create branch '${name}'`));
449
- console.error(chalk.red(formatError(err)));
504
+ handleNerdError(err);
450
505
  process.exitCode = 1;
451
506
  }
452
507
  }
@@ -474,7 +529,7 @@ async function deleteBranch(name) {
474
529
  }
475
530
  else {
476
531
  spinner.fail(chalk.red(`Failed to delete '${name}'`));
477
- console.error(chalk.red(msg));
532
+ handleNerdError(err, name);
478
533
  }
479
534
  process.exitCode = 1;
480
535
  }
@@ -501,7 +556,7 @@ async function listRemotes() {
501
556
  }
502
557
  catch (err) {
503
558
  spinner.fail(chalk.red('Could not list remotes'));
504
- console.error(chalk.red(formatError(err)));
559
+ handleNerdError(err);
505
560
  process.exitCode = 1;
506
561
  }
507
562
  }
@@ -522,7 +577,7 @@ async function addRemote(url, name) {
522
577
  }
523
578
  catch (err) {
524
579
  spinner.fail(chalk.red(`Failed to add remote '${name}'`));
525
- console.error(chalk.red(formatError(err)));
580
+ handleNerdError(err);
526
581
  process.exitCode = 1;
527
582
  }
528
583
  }
@@ -576,7 +631,7 @@ async function showStatus() {
576
631
  }
577
632
  catch (err) {
578
633
  spinner.fail(chalk.red('Could not fetch status'));
579
- console.error(chalk.red(formatError(err)));
634
+ handleNerdError(err);
580
635
  process.exitCode = 1;
581
636
  }
582
637
  }
@@ -609,9 +664,10 @@ async function pushCommits(remote, force = false, setUpstream) {
609
664
  const msg = formatError(err);
610
665
  if (msg.includes('no upstream branch')) {
611
666
  console.error(chalk.red('No upstream configured. Use -u flag to set upstream.'));
667
+ handleNerdError(err);
612
668
  }
613
669
  else {
614
- console.error(chalk.red(msg));
670
+ handleNerdError(err);
615
671
  }
616
672
  process.exitCode = 1;
617
673
  }
@@ -639,16 +695,7 @@ async function pullChanges(remote, rebase = false) {
639
695
  }
640
696
  catch (err) {
641
697
  spinner.fail(chalk.red('Pull failed'));
642
- const msg = formatError(err);
643
- if (msg.includes('merge conflicts')) {
644
- console.error(chalk.red('Merge conflicts detected. Resolve conflicts and commit.'));
645
- }
646
- else if (msg.includes('local changes')) {
647
- console.error(chalk.red('You have uncommitted changes. Stash or commit them first.'));
648
- }
649
- else {
650
- console.error(chalk.red(msg));
651
- }
698
+ handleNerdError(err);
652
699
  process.exitCode = 1;
653
700
  }
654
701
  }
@@ -674,14 +721,11 @@ async function mergeBranch(branch, squash = false) {
674
721
  catch (err) {
675
722
  spinner.fail(chalk.red('Merge failed'));
676
723
  const msg = formatError(err);
677
- if (msg.includes('conflicts')) {
678
- console.error(chalk.red('Merge conflicts detected. Resolve conflicts and commit, or use --abort to cancel.'));
679
- }
680
- else if (msg.includes('already up to date')) {
724
+ if (msg.includes('already up to date')) {
681
725
  console.log(chalk.yellow('Already up to date.'));
682
726
  }
683
727
  else {
684
- console.error(chalk.red(msg));
728
+ handleNerdError(err);
685
729
  }
686
730
  process.exitCode = 1;
687
731
  }
@@ -694,13 +738,7 @@ async function abortMerge() {
694
738
  }
695
739
  catch (err) {
696
740
  spinner.fail(chalk.red('Failed to abort merge'));
697
- const msg = formatError(err);
698
- if (msg.includes('no merge')) {
699
- console.error(chalk.red('No merge in progress'));
700
- }
701
- else {
702
- console.error(chalk.red(msg));
703
- }
741
+ handleNerdError(err);
704
742
  process.exitCode = 1;
705
743
  }
706
744
  }
@@ -716,13 +754,7 @@ async function rebaseBranch(branch) {
716
754
  }
717
755
  catch (err) {
718
756
  spinner.fail(chalk.red('Rebase failed'));
719
- const msg = formatError(err);
720
- if (msg.includes('conflicts')) {
721
- console.error(chalk.red('Rebase conflicts detected. Resolve conflicts, then use --continue or --abort.'));
722
- }
723
- else {
724
- console.error(chalk.red(msg));
725
- }
757
+ handleNerdError(err);
726
758
  process.exitCode = 1;
727
759
  }
728
760
  }
@@ -734,16 +766,7 @@ async function continueRebase() {
734
766
  }
735
767
  catch (err) {
736
768
  spinner.fail(chalk.red('Failed to continue rebase'));
737
- const msg = formatError(err);
738
- if (msg.includes('no rebase')) {
739
- console.error(chalk.red('No rebase in progress'));
740
- }
741
- else if (msg.includes('conflicts')) {
742
- console.error(chalk.red('Unresolved conflicts remain. Resolve them first.'));
743
- }
744
- else {
745
- console.error(chalk.red(msg));
746
- }
769
+ handleNerdError(err);
747
770
  process.exitCode = 1;
748
771
  }
749
772
  }
@@ -755,13 +778,7 @@ async function abortRebase() {
755
778
  }
756
779
  catch (err) {
757
780
  spinner.fail(chalk.red('Failed to abort rebase'));
758
- const msg = formatError(err);
759
- if (msg.includes('no rebase')) {
760
- console.error(chalk.red('No rebase in progress'));
761
- }
762
- else {
763
- console.error(chalk.red(msg));
764
- }
781
+ handleNerdError(err);
765
782
  process.exitCode = 1;
766
783
  }
767
784
  }
@@ -795,7 +812,7 @@ async function showLog(count, oneline) {
795
812
  }
796
813
  catch (err) {
797
814
  spinner.fail(chalk.red('Failed to fetch log'));
798
- console.error(chalk.red(formatError(err)));
815
+ handleNerdError(err);
799
816
  process.exitCode = 1;
800
817
  }
801
818
  }
@@ -848,7 +865,7 @@ async function showDiff(file, staged) {
848
865
  }
849
866
  catch (err) {
850
867
  spinner.fail(chalk.red('Failed to fetch diff'));
851
- console.error(chalk.red(formatError(err)));
868
+ handleNerdError(err);
852
869
  process.exitCode = 1;
853
870
  }
854
871
  }
@@ -874,16 +891,7 @@ async function cloneRepo(url, directory) {
874
891
  }
875
892
  catch (err) {
876
893
  spinner.fail(chalk.red('Clone failed'));
877
- const msg = formatError(err);
878
- if (msg.includes('already exists')) {
879
- console.error(chalk.red(`Directory '${targetDir}' already exists.`));
880
- }
881
- else if (msg.includes('not found') || msg.includes('does not exist')) {
882
- console.error(chalk.red('Repository not found. Check the URL.'));
883
- }
884
- else {
885
- console.error(chalk.red(msg));
886
- }
894
+ handleNerdError(err);
887
895
  process.exitCode = 1;
888
896
  }
889
897
  }
@@ -927,7 +935,7 @@ async function stashSave() {
927
935
  }
928
936
  catch (err) {
929
937
  spinner.fail(chalk.red('Failed to stash changes'));
930
- console.error(chalk.red(formatError(err)));
938
+ handleNerdError(err);
931
939
  process.exitCode = 1;
932
940
  }
933
941
  }
@@ -939,16 +947,7 @@ async function stashPop() {
939
947
  }
940
948
  catch (err) {
941
949
  spinner.fail(chalk.red('Failed to pop stash'));
942
- const msg = formatError(err);
943
- if (msg.includes('No stash entries')) {
944
- console.error(chalk.red('No stash entries found'));
945
- }
946
- else if (msg.includes('conflicts')) {
947
- console.error(chalk.red('Conflicts when applying stash. Resolve conflicts manually.'));
948
- }
949
- else {
950
- console.error(chalk.red(msg));
951
- }
950
+ handleNerdError(err);
952
951
  process.exitCode = 1;
953
952
  }
954
953
  }
@@ -970,7 +969,7 @@ async function stashList() {
970
969
  }
971
970
  catch (err) {
972
971
  spinner.fail(chalk.red('Failed to list stashes'));
973
- console.error(chalk.red(formatError(err)));
972
+ handleNerdError(err);
974
973
  process.exitCode = 1;
975
974
  }
976
975
  }
@@ -989,13 +988,7 @@ async function stashDrop(index) {
989
988
  }
990
989
  catch (err) {
991
990
  spinner.fail(chalk.red(`Failed to drop ${stashRef}`));
992
- const msg = formatError(err);
993
- if (msg.includes('Invalid reflog')) {
994
- console.error(chalk.red(`Invalid stash index: ${index ?? 0}`));
995
- }
996
- else {
997
- console.error(chalk.red(msg));
998
- }
991
+ handleNerdError(err, index ?? '0');
999
992
  process.exitCode = 1;
1000
993
  }
1001
994
  }
@@ -1014,16 +1007,7 @@ async function stashApply(index) {
1014
1007
  }
1015
1008
  catch (err) {
1016
1009
  spinner.fail(chalk.red(`Failed to apply ${stashRef}`));
1017
- const msg = formatError(err);
1018
- if (msg.includes('Invalid reflog')) {
1019
- console.error(chalk.red(`Invalid stash index: ${index ?? 0}`));
1020
- }
1021
- else if (msg.includes('conflicts')) {
1022
- console.error(chalk.red('Conflicts when applying stash. Resolve conflicts manually.'));
1023
- }
1024
- else {
1025
- console.error(chalk.red(msg));
1026
- }
1010
+ handleNerdError(err, index ?? '0');
1027
1011
  process.exitCode = 1;
1028
1012
  }
1029
1013
  }
@@ -1065,17 +1049,7 @@ async function performUpdate(version) {
1065
1049
  }
1066
1050
  catch (err) {
1067
1051
  spinner.fail(chalk.red('Update failed'));
1068
- const msg = formatError(err);
1069
- if (msg.includes('EACCES') || msg.includes('permission')) {
1070
- console.error(chalk.red('Permission denied. Try running with sudo:'));
1071
- console.error(chalk.yellow(' sudo omg update'));
1072
- }
1073
- else if (msg.includes('npm')) {
1074
- console.error(chalk.red('npm command failed. Is npm installed?'));
1075
- }
1076
- else {
1077
- console.error(chalk.red(msg));
1078
- }
1052
+ handleNerdError(err);
1079
1053
  process.exitCode = 1;
1080
1054
  }
1081
1055
  }
@@ -1121,7 +1095,7 @@ async function initRepo(directory, message) {
1121
1095
  }
1122
1096
  catch (err) {
1123
1097
  spinner.fail(chalk.red('Failed to initialize repository'));
1124
- console.error(chalk.red(formatError(err)));
1098
+ handleNerdError(err);
1125
1099
  process.exitCode = 1;
1126
1100
  }
1127
1101
  }
@@ -1143,13 +1117,7 @@ async function createTag(name, message) {
1143
1117
  }
1144
1118
  catch (err) {
1145
1119
  spinner.fail(chalk.red(`Failed to create tag '${name}'`));
1146
- const msg = formatError(err);
1147
- if (msg.includes('already exists')) {
1148
- console.error(chalk.red(`Tag '${name}' already exists`));
1149
- }
1150
- else {
1151
- console.error(chalk.red(msg));
1152
- }
1120
+ handleNerdError(err, name);
1153
1121
  process.exitCode = 1;
1154
1122
  }
1155
1123
  }
@@ -1170,7 +1138,7 @@ async function listTags() {
1170
1138
  }
1171
1139
  catch (err) {
1172
1140
  spinner.fail(chalk.red('Failed to list tags'));
1173
- console.error(chalk.red(formatError(err)));
1141
+ handleNerdError(err);
1174
1142
  process.exitCode = 1;
1175
1143
  }
1176
1144
  }
@@ -1193,16 +1161,7 @@ async function fetchChanges(remote) {
1193
1161
  }
1194
1162
  catch (err) {
1195
1163
  spinner.fail(chalk.red('Fetch failed'));
1196
- const msg = formatError(err);
1197
- if (msg.includes('not a git repository')) {
1198
- console.error(chalk.red('Not a git repository'));
1199
- }
1200
- else if (msg.includes('no remote')) {
1201
- console.error(chalk.red('No remote configured. Add one with: omg remote <url>'));
1202
- }
1203
- else {
1204
- console.error(chalk.red(msg));
1205
- }
1164
+ handleNerdError(err);
1206
1165
  process.exitCode = 1;
1207
1166
  }
1208
1167
  }
@@ -1232,7 +1191,7 @@ async function resetChanges(mode) {
1232
1191
  }
1233
1192
  catch (err) {
1234
1193
  spinner.fail(chalk.red('Reset failed'));
1235
- console.error(chalk.red(formatError(err)));
1194
+ handleNerdError(err);
1236
1195
  process.exitCode = 1;
1237
1196
  }
1238
1197
  }
@@ -1248,19 +1207,7 @@ async function revertCommit(commit) {
1248
1207
  }
1249
1208
  catch (err) {
1250
1209
  spinner.fail(chalk.red(`Failed to revert '${commit}'`));
1251
- const msg = formatError(err);
1252
- if (msg.includes('conflicts')) {
1253
- console.error(chalk.red('Merge conflicts detected. Resolve conflicts, then use: omg revert --continue'));
1254
- }
1255
- else if (msg.includes('is a merge')) {
1256
- console.error(chalk.red('Cannot revert merge commit automatically. Use git revert -m 1 <commit>'));
1257
- }
1258
- else if (msg.includes('already reverted')) {
1259
- console.log(chalk.yellow('This commit may have already been reverted.'));
1260
- }
1261
- else {
1262
- console.error(chalk.red(msg));
1263
- }
1210
+ handleNerdError(err, commit);
1264
1211
  process.exitCode = 1;
1265
1212
  }
1266
1213
  }
@@ -1272,16 +1219,7 @@ async function continueRevert() {
1272
1219
  }
1273
1220
  catch (err) {
1274
1221
  spinner.fail(chalk.red('Failed to continue revert'));
1275
- const msg = formatError(err);
1276
- if (msg.includes('no revert')) {
1277
- console.error(chalk.red('No revert in progress'));
1278
- }
1279
- else if (msg.includes('conflicts')) {
1280
- console.error(chalk.red('Unresolved conflicts remain. Resolve them first.'));
1281
- }
1282
- else {
1283
- console.error(chalk.red(msg));
1284
- }
1222
+ handleNerdError(err);
1285
1223
  process.exitCode = 1;
1286
1224
  }
1287
1225
  }
@@ -1297,19 +1235,7 @@ async function cherryPickCommit(commit) {
1297
1235
  }
1298
1236
  catch (err) {
1299
1237
  spinner.fail(chalk.red(`Failed to cherry-pick '${commit}'`));
1300
- const msg = formatError(err);
1301
- if (msg.includes('conflicts')) {
1302
- console.error(chalk.red('Merge conflicts detected. Resolve conflicts, then use: omg cherry-pick --continue'));
1303
- }
1304
- else if (msg.includes('already exists')) {
1305
- console.error(chalk.yellow('This commit is already in the current branch.'));
1306
- }
1307
- else if (msg.includes('empty')) {
1308
- console.error(chalk.yellow('This cherry-pick would result in an empty commit.'));
1309
- }
1310
- else {
1311
- console.error(chalk.red(msg));
1312
- }
1238
+ handleNerdError(err, commit);
1313
1239
  process.exitCode = 1;
1314
1240
  }
1315
1241
  }
@@ -1321,16 +1247,7 @@ async function continueCherryPick() {
1321
1247
  }
1322
1248
  catch (err) {
1323
1249
  spinner.fail(chalk.red('Failed to continue cherry-pick'));
1324
- const msg = formatError(err);
1325
- if (msg.includes('no cherry-pick')) {
1326
- console.error(chalk.red('No cherry-pick in progress'));
1327
- }
1328
- else if (msg.includes('conflicts')) {
1329
- console.error(chalk.red('Unresolved conflicts remain. Resolve them first.'));
1330
- }
1331
- else {
1332
- console.error(chalk.red(msg));
1333
- }
1250
+ handleNerdError(err);
1334
1251
  process.exitCode = 1;
1335
1252
  }
1336
1253
  }
@@ -1408,7 +1325,7 @@ async function shipChanges(message, useRebase, dryRun) {
1408
1325
  }
1409
1326
  catch (err) {
1410
1327
  spinner.fail(chalk.red('Failed to analyze repository'));
1411
- console.error(chalk.red(formatError(err)));
1328
+ handleNerdError(err);
1412
1329
  process.exitCode = 1;
1413
1330
  return;
1414
1331
  }
@@ -1428,7 +1345,7 @@ async function shipChanges(message, useRebase, dryRun) {
1428
1345
  }
1429
1346
  catch (err) {
1430
1347
  stepSpinner.fail(chalk.red('Commit failed'));
1431
- console.error(chalk.red(formatError(err)));
1348
+ handleNerdError(err);
1432
1349
  process.exitCode = 1;
1433
1350
  return;
1434
1351
  }
@@ -1448,7 +1365,7 @@ async function shipChanges(message, useRebase, dryRun) {
1448
1365
  }
1449
1366
  catch (err) {
1450
1367
  stepSpinner.fail(chalk.red('Failed to stage'));
1451
- console.error(chalk.red(formatError(err)));
1368
+ handleNerdError(err);
1452
1369
  process.exitCode = 1;
1453
1370
  return;
1454
1371
  }
@@ -1470,13 +1387,7 @@ async function shipChanges(message, useRebase, dryRun) {
1470
1387
  }
1471
1388
  catch (err) {
1472
1389
  fetchSpinner.fail(chalk.red('Fetch failed'));
1473
- const msg = formatError(err);
1474
- if (msg.includes('no remote')) {
1475
- console.error(chalk.red('No remote configured. Add one with: omg remote <url>'));
1476
- }
1477
- else {
1478
- console.error(chalk.red(msg));
1479
- }
1390
+ handleNerdError(err);
1480
1391
  process.exitCode = 1;
1481
1392
  return;
1482
1393
  }
@@ -1504,7 +1415,7 @@ async function shipChanges(message, useRebase, dryRun) {
1504
1415
  }
1505
1416
  catch (err) {
1506
1417
  rebaseSpinner.fail(chalk.red('Rebase failed'));
1507
- console.error(chalk.red('Conflicts detected. Resolve them, then run: omg rebase --continue'));
1418
+ handleNerdError(err);
1508
1419
  process.exitCode = 1;
1509
1420
  return;
1510
1421
  }
@@ -1522,7 +1433,7 @@ async function shipChanges(message, useRebase, dryRun) {
1522
1433
  }
1523
1434
  catch (err) {
1524
1435
  mergeSpinner.fail(chalk.red('Merge failed'));
1525
- console.error(chalk.red('Conflicts detected. Resolve them and commit.'));
1436
+ handleNerdError(err);
1526
1437
  process.exitCode = 1;
1527
1438
  return;
1528
1439
  }
@@ -1545,13 +1456,7 @@ async function shipChanges(message, useRebase, dryRun) {
1545
1456
  }
1546
1457
  catch (err) {
1547
1458
  pushSpinner.fail(chalk.red('Push failed'));
1548
- const msg = formatError(err);
1549
- if (msg.includes('rejected')) {
1550
- console.error(chalk.red('Push was rejected. Run `omg ship` again to sync and retry.'));
1551
- }
1552
- else {
1553
- console.error(chalk.red(msg));
1554
- }
1459
+ handleNerdError(err);
1555
1460
  process.exitCode = 1;
1556
1461
  return;
1557
1462
  }
@@ -1610,13 +1515,7 @@ async function oopsUncommit(keepChanges) {
1610
1515
  }
1611
1516
  catch (err) {
1612
1517
  spinner.fail(chalk.red('Failed to undo commit'));
1613
- const msg = formatError(err);
1614
- if (msg.includes('unknown revision')) {
1615
- console.error(chalk.red('No commits to undo'));
1616
- }
1617
- else {
1618
- console.error(chalk.red(msg));
1619
- }
1518
+ handleNerdError(err);
1620
1519
  process.exitCode = 1;
1621
1520
  }
1622
1521
  }
@@ -1629,7 +1528,7 @@ async function oopsUnstage() {
1629
1528
  }
1630
1529
  catch (err) {
1631
1530
  spinner.fail(chalk.red('Failed to unstage'));
1632
- console.error(chalk.red(formatError(err)));
1531
+ handleNerdError(err);
1633
1532
  process.exitCode = 1;
1634
1533
  }
1635
1534
  }
@@ -1641,7 +1540,7 @@ async function oopsUnadd(file) {
1641
1540
  }
1642
1541
  catch (err) {
1643
1542
  spinner.fail(chalk.red(`Failed to unstage '${file}'`));
1644
- console.error(chalk.red(formatError(err)));
1543
+ handleNerdError(err);
1645
1544
  process.exitCode = 1;
1646
1545
  }
1647
1546
  }
@@ -1679,7 +1578,7 @@ async function oopsRestoreBranch() {
1679
1578
  }
1680
1579
  catch (err) {
1681
1580
  spinner.fail(chalk.red('Failed to check reflog'));
1682
- console.error(chalk.red(formatError(err)));
1581
+ handleNerdError(err);
1683
1582
  process.exitCode = 1;
1684
1583
  }
1685
1584
  }
@@ -1742,7 +1641,7 @@ async function syncWorkspace(baseBranch) {
1742
1641
  }
1743
1642
  catch (err) {
1744
1643
  checkSpinner.fail(chalk.red('Failed to check repository'));
1745
- console.error(chalk.red(formatError(err)));
1644
+ handleNerdError(err);
1746
1645
  process.exitCode = 1;
1747
1646
  return;
1748
1647
  }
@@ -1821,7 +1720,7 @@ async function syncWorkspace(baseBranch) {
1821
1720
  }
1822
1721
  catch (err) {
1823
1722
  returnSpinner.fail(chalk.red('Failed to return to original branch'));
1824
- console.error(chalk.red(formatError(err)));
1723
+ handleNerdError(err);
1825
1724
  console.error(chalk.yellow(`You are now on ${baseBranch}. Manual recovery needed.`));
1826
1725
  process.exitCode = 1;
1827
1726
  return;
@@ -1835,7 +1734,7 @@ async function syncWorkspace(baseBranch) {
1835
1734
  }
1836
1735
  catch (err) {
1837
1736
  popSpinner.fail(chalk.red('Failed to restore stash'));
1838
- console.error(chalk.red(formatError(err)));
1737
+ handleNerdError(err);
1839
1738
  console.error(chalk.yellow('Your changes are in the stash. Run: omg stash pop'));
1840
1739
  process.exitCode = 1;
1841
1740
  return;
@@ -2020,7 +1919,7 @@ async function runDoctor(autoFix) {
2020
1919
  }
2021
1920
  catch (err) {
2022
1921
  spinner.fail(chalk.red('Failed to run health checks'));
2023
- console.error(chalk.red(formatError(err)));
1922
+ handleNerdError(err);
2024
1923
  process.exitCode = 1;
2025
1924
  return;
2026
1925
  }
@@ -2105,7 +2004,7 @@ async function getConfig(key) {
2105
2004
  }
2106
2005
  catch (err) {
2107
2006
  console.error(chalk.red(`Failed to get config '${key}'`));
2108
- console.error(chalk.red(formatError(err)));
2007
+ handleNerdError(err);
2109
2008
  process.exitCode = 1;
2110
2009
  }
2111
2010
  }
@@ -2118,10 +2017,163 @@ async function setConfig(key, value) {
2118
2017
  }
2119
2018
  catch (err) {
2120
2019
  spinner.fail(chalk.red(`Failed to set config '${key}'`));
2121
- console.error(chalk.red(formatError(err)));
2020
+ handleNerdError(err);
2021
+ process.exitCode = 1;
2022
+ }
2023
+ }
2024
+ // ---------------------------------------------------------------------------
2025
+ // blame subcommand
2026
+ // ---------------------------------------------------------------------------
2027
+ program
2028
+ .command('blame <file>')
2029
+ .description('show line-by-line authorship of a file\n' +
2030
+ ' <file> file to blame\n' +
2031
+ ' -L <line> show specific line\n' +
2032
+ ' -s, --stats show author statistics')
2033
+ .option('-L, --line <number>', 'show blame for specific line only')
2034
+ .option('-s, --stats', 'show author statistics instead of line-by-line')
2035
+ .action(async (file, options) => {
2036
+ await showBlame(file, options.line ? parseInt(options.line, 10) : undefined, options.stats ?? false);
2037
+ });
2038
+ // ---------------------------------------------------------------------------
2039
+ // blame helpers
2040
+ // ---------------------------------------------------------------------------
2041
+ async function showBlame(file, lineNum, showStats = false) {
2042
+ validateNotFlag(file, 'file path');
2043
+ const spinner = ora(`Analyzing ${chalk.cyan(file)}`).start();
2044
+ try {
2045
+ const blameData = await git.raw(['blame', file]);
2046
+ spinner.stop();
2047
+ if (showStats) {
2048
+ await showBlameStats(blameData, file);
2049
+ }
2050
+ else if (lineNum !== undefined) {
2051
+ showBlameLine(blameData, file, lineNum);
2052
+ }
2053
+ else {
2054
+ showBlameFull(blameData, file);
2055
+ }
2056
+ }
2057
+ catch (err) {
2058
+ spinner.fail(chalk.red(`Failed to blame '${file}'`));
2059
+ handleNerdError(err);
2122
2060
  process.exitCode = 1;
2123
2061
  }
2124
2062
  }
2063
+ function showBlameFull(blameData, file) {
2064
+ const lines = blameData.split('\n').filter(line => line.trim());
2065
+ if (lines.length === 0) {
2066
+ console.log(chalk.yellow(`No blame data available for ${chalk.cyan(file)}`));
2067
+ return;
2068
+ }
2069
+ console.log(chalk.bold(`\n--- Blame: ${chalk.cyan(file)} ---\n`));
2070
+ // Parse blame output
2071
+ // Git blame format: hash (author date time timezone lineNum) content
2072
+ const entries = [];
2073
+ for (const line of lines) {
2074
+ // Match pattern: hash (author date time timezone lineNum) content
2075
+ const match = line.match(/^([0-9a-f]+)\s+\(([^)]+)\)\s*(.*)$/);
2076
+ if (match) {
2077
+ const hash = match[1];
2078
+ const metaPart = match[2];
2079
+ const content = match[3];
2080
+ // Parse meta part: author date time timezone lineNum
2081
+ const metaParts = metaPart.split(/\s+/);
2082
+ if (metaParts.length >= 4) {
2083
+ const author = metaParts[0];
2084
+ const date = metaParts.slice(1, metaParts.length - 1).join(' ');
2085
+ const lineNum = parseInt(metaParts[metaParts.length - 1], 10);
2086
+ entries.push({
2087
+ hash,
2088
+ author,
2089
+ date,
2090
+ lineNum,
2091
+ content,
2092
+ });
2093
+ }
2094
+ }
2095
+ }
2096
+ // Display with formatting
2097
+ for (const entry of entries) {
2098
+ const shortHash = entry.hash.slice(0, 7);
2099
+ const lineNum = chalk.dim(`${entry.lineNum.toString().padStart(4)}:`);
2100
+ const hashStr = chalk.green(shortHash);
2101
+ const authorStr = chalk.cyan(entry.author.padEnd(15));
2102
+ const dateStr = chalk.dim(entry.date);
2103
+ console.log(`${lineNum} ${hashStr} ${authorStr} ${dateStr} ${entry.content}`);
2104
+ }
2105
+ console.log('');
2106
+ }
2107
+ function showBlameLine(blameData, file, lineNum) {
2108
+ const lines = blameData.split('\n').filter(line => line.trim());
2109
+ if (lines.length === 0 || lineNum < 1 || lineNum > lines.length) {
2110
+ console.log(chalk.yellow(`Line ${lineNum} not found in ${chalk.cyan(file)}`));
2111
+ return;
2112
+ }
2113
+ const targetLine = lines[lineNum - 1];
2114
+ // Match pattern: hash (author date time timezone lineNum) content
2115
+ const match = targetLine.match(/^([0-9a-f]+)\s+\(([^)]+)\)\s*(.*)$/);
2116
+ if (match) {
2117
+ const hash = match[1];
2118
+ const metaPart = match[2];
2119
+ const content = match[3];
2120
+ // Parse meta part: author date time timezone lineNum
2121
+ const metaParts = metaPart.split(/\s+/);
2122
+ if (metaParts.length >= 4) {
2123
+ const author = metaParts[0];
2124
+ const date = metaParts.slice(1, metaParts.length - 1).join(' ');
2125
+ console.log(chalk.bold(`\n--- Blame: ${chalk.cyan(file)}:${chalk.yellow(lineNum.toString())} ---\n`));
2126
+ console.log(`${chalk.green('Commit:')} ${chalk.cyan(hash.slice(0, 7))} ${chalk.dim(`(${hash})`)}`);
2127
+ console.log(`${chalk.green('Author:')} ${chalk.cyan(author)}`);
2128
+ console.log(`${chalk.green('Date:')} ${chalk.dim(date)}`);
2129
+ console.log(`${chalk.green('Line:')} ${content}`);
2130
+ console.log('');
2131
+ }
2132
+ }
2133
+ }
2134
+ async function showBlameStats(blameData, file) {
2135
+ const lines = blameData.split('\n').filter(line => line.trim());
2136
+ if (lines.length === 0) {
2137
+ console.log(chalk.yellow(`No blame data available for ${chalk.cyan(file)}`));
2138
+ return;
2139
+ }
2140
+ // Parse and count by author
2141
+ const authorStats = new Map();
2142
+ for (const line of lines) {
2143
+ // Match pattern: hash (author date time timezone lineNum) content
2144
+ const match = line.match(/^([0-9a-f]+)\s+\(([^)]+)\)\s*(.*)$/);
2145
+ if (match) {
2146
+ const metaPart = match[2];
2147
+ const metaParts = metaPart.split(/\s+/);
2148
+ if (metaParts.length >= 4) {
2149
+ const author = metaParts[0];
2150
+ authorStats.set(author, (authorStats.get(author) || 0) + 1);
2151
+ }
2152
+ }
2153
+ }
2154
+ const totalLines = Array.from(authorStats.values()).reduce((a, b) => a + b, 0);
2155
+ const sortedStats = Array.from(authorStats.entries())
2156
+ .sort((a, b) => b[1] - a[1])
2157
+ .map(([author, count]) => ({
2158
+ author,
2159
+ count,
2160
+ percentage: ((count / totalLines) * 100).toFixed(1),
2161
+ }));
2162
+ console.log(chalk.bold(`\n--- Author Statistics: ${chalk.cyan(file)} ---\n`));
2163
+ console.log(`${chalk.dim('Total lines:')} ${totalLines}\n`);
2164
+ const maxCount = sortedStats[0]?.count || 1;
2165
+ const maxAuthorLength = Math.max(...sortedStats.map(s => s.author.length));
2166
+ for (const stat of sortedStats) {
2167
+ const barLength = Math.floor((stat.count / maxCount) * 30);
2168
+ const bar = '█'.repeat(barLength);
2169
+ const authorPadded = stat.author.padEnd(maxAuthorLength);
2170
+ console.log(`${chalk.cyan(authorPadded)} ` +
2171
+ `${chalk.yellow(stat.count.toString().padStart(4))} lines ` +
2172
+ `${chalk.dim(`(${stat.percentage}%)`)} ` +
2173
+ `${chalk.green(bar)}`);
2174
+ }
2175
+ console.log('');
2176
+ }
2125
2177
  // ---------------------------------------------------------------------------
2126
2178
  // utilities
2127
2179
  // ---------------------------------------------------------------------------
@@ -2131,7 +2183,7 @@ function formatError(err) {
2131
2183
  return String(err);
2132
2184
  }
2133
2185
  program.parseAsync(process.argv).catch((err) => {
2134
- console.error(chalk.red(formatError(err)));
2186
+ handleNerdError(err);
2135
2187
  process.exit(1);
2136
2188
  });
2137
2189
  //# sourceMappingURL=index.js.map