@airscript/ghitgud 2.0.0 → 2.1.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
@@ -54,6 +54,15 @@ ghitgud config get repo
54
54
  ## Commands
55
55
 
56
56
  ```
57
+ ghitgud gh <args> Pass through to the gh CLI
58
+ ghitgud notifications list List notifications
59
+ ghitgud notifications list -a Include read notifications
60
+ ghitgud notifications list -p Only participating
61
+ ghitgud notifications list -r owner/repo Filter by repository
62
+ ghitgud notifications read <id> Mark a notification as read
63
+ ghitgud notifications done <id> Mark a notification as done
64
+ ghitgud activity Assigned issues, review requests, mentions
65
+ ghitgud mentions Recent @mentions of you
57
66
  ghitgud ping Check if the CLI is working
58
67
  ghitgud labels list List all labels for a repository
59
68
  ghitgud labels pull Pull labels from a repository to local config
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0
1
+ 2.1.0
package/dist/index.js CHANGED
@@ -39,6 +39,7 @@ let commander = require("commander");
39
39
  let figlet = require("figlet");
40
40
  figlet = __toESM(figlet);
41
41
  let consola = require("consola");
42
+ let child_process = require("child_process");
42
43
  let path = require("path");
43
44
  path = __toESM(path);
44
45
  let fs = require("fs");
@@ -57,6 +58,29 @@ var ascii = figlet.default.textSync("Ghitgud", {
57
58
  //#region src/core/logger.ts
58
59
  var logger = (0, consola.createConsola)({ defaults: { tag: "ghitgud" } });
59
60
  //#endregion
61
+ //#region src/commands/gh.ts
62
+ var register$6 = (program) => {
63
+ program.command("gh").description("Pass through to the gh CLI. Usage: ghitgud gh <args>").allowUnknownOption().action((_opts, command) => {
64
+ const args = command.args;
65
+ const child = (0, child_process.spawn)("gh", args, {
66
+ stdio: "inherit",
67
+ shell: false
68
+ });
69
+ child.on("error", (error) => {
70
+ if (error.code === "ENOENT") {
71
+ logger.error("gh CLI is not installed. Install it from https://cli.github.com.");
72
+ process$1.default.exit(1);
73
+ }
74
+ logger.error(String(error));
75
+ process$1.default.exit(1);
76
+ });
77
+ child.on("exit", (code) => {
78
+ process$1.default.exitCode = code ?? 0;
79
+ });
80
+ });
81
+ };
82
+ var gh_default = { register: register$6 };
83
+ //#endregion
60
84
  //#region src/core/constants.ts
61
85
  var GHITGUD_FOLDER = path.default.join(os.default.homedir(), ".config", "ghitgud");
62
86
  var CREDENTIALS_FILE = "credentials.json";
@@ -76,6 +100,7 @@ var ERROR_NO_REPO = "Repository not configured. Set it with: ghitgud config set
76
100
  var ERROR_NO_TOKEN = "Token not configured. Set it with: ghitgud config set token <your-token>.";
77
101
  var ERROR_UNSUPPORTED_KEY = "Trying to set unsupported key.";
78
102
  var ERROR_NO_METADATA = "No metadata file found.";
103
+ var INFO_NO_NOTIFICATIONS = "No notifications found.";
79
104
  var PING_RESPONSE = "pong";
80
105
  var SUPPORTED_CONFIG_KEYS = ["token", "repo"];
81
106
  //#endregion
@@ -562,6 +587,10 @@ var client = {
562
587
  method: "PATCH",
563
588
  body
564
589
  }),
590
+ put: (endpoint, body) => request(endpoint, {
591
+ method: "PUT",
592
+ body
593
+ }),
565
594
  getRepo: () => config.getRepo(),
566
595
  isOk: (status) => isSuccessful(status),
567
596
  isNotFound: (status) => status === 404,
@@ -624,7 +653,7 @@ var ping = () => {
624
653
  message: PING_RESPONSE
625
654
  };
626
655
  };
627
- var list = async () => {
656
+ var list$1 = async () => {
628
657
  logger.info("Fetching labels from repository.");
629
658
  const labels$1 = (await (await labels.fetch()).json()).map((label) => normalizeLabel(label));
630
659
  formatLabels(labels$1);
@@ -697,7 +726,7 @@ var prune = async () => {
697
726
  };
698
727
  var labels_default$1 = {
699
728
  ping,
700
- list,
729
+ list: list$1,
701
730
  pull,
702
731
  pullTemplate,
703
732
  push,
@@ -706,13 +735,13 @@ var labels_default$1 = {
706
735
  };
707
736
  //#endregion
708
737
  //#region src/commands/ping.ts
709
- var register$2 = (program) => {
738
+ var register$5 = (program) => {
710
739
  program.command("ping").description("Check if the CLI is working.").action(() => void labels_default$1.ping());
711
740
  };
712
- var ping_default = { register: register$2 };
741
+ var ping_default = { register: register$5 };
713
742
  //#endregion
714
743
  //#region src/commands/labels.ts
715
- var register$1 = (program) => {
744
+ var register$4 = (program) => {
716
745
  const labels = program.command("labels").description("Manage labels for a repository.");
717
746
  labels.command("list").description("List all labels for a repository.").action(() => void labels_default$1.list());
718
747
  labels.command("pull").description("Pull all related labels for a repository.").option("-t, --template <name>", "Pull from a built-in template instead of the remote repository").action(async (options) => {
@@ -725,7 +754,7 @@ var register$1 = (program) => {
725
754
  });
726
755
  labels.command("prune").description("Prune all related labels for a repository.").action(() => void labels_default$1.prune());
727
756
  };
728
- var labels_default = { register: register$1 };
757
+ var labels_default = { register: register$4 };
729
758
  //#endregion
730
759
  //#region src/services/config.ts
731
760
  var validateKey = (key) => {
@@ -755,7 +784,7 @@ var config_default$1 = {
755
784
  };
756
785
  //#endregion
757
786
  //#region src/commands/config.ts
758
- var register = (program) => {
787
+ var register$3 = (program) => {
759
788
  const config = program.command("config").description("Set CLI configurations.");
760
789
  config.command("set").description("Set configuration.").arguments("<key> <value>").action((key, value) => {
761
790
  config_default$1.set(key, value);
@@ -764,10 +793,198 @@ var register = (program) => {
764
793
  config_default$1.get(key);
765
794
  });
766
795
  };
767
- var config_default = { register };
796
+ var config_default = { register: register$3 };
797
+ //#endregion
798
+ //#region src/api/notifications.ts
799
+ var BASE_PATH = "/notifications";
800
+ var notifications = {
801
+ fetch: (params) => {
802
+ const query = new URLSearchParams();
803
+ if (params?.all) query.set("all", "true");
804
+ if (params?.participating) query.set("participating", "true");
805
+ if (params?.perPage) query.set("per_page", String(params.perPage));
806
+ const qs = query.toString();
807
+ const endpoint = qs ? `${BASE_PATH}?${qs}` : BASE_PATH;
808
+ return client.get(endpoint);
809
+ },
810
+ markRead: (id) => {
811
+ return client.patch(`/notifications/threads/${id}`, {});
812
+ },
813
+ markDone: (id) => {
814
+ return client.put(`/notifications/threads/${id}/subscription`, { ignored: true });
815
+ },
816
+ assignedIssues: () => {
817
+ return client.get("/issues?filter=assigned&state=open");
818
+ },
819
+ reviewRequests: () => {
820
+ return client.get("/search/issues?q=is:pr+is:open+review-requested:@me");
821
+ },
822
+ mentions: (username) => {
823
+ const since = (/* @__PURE__ */ new Date(Date.now() - 10080 * 60 * 1e3)).toISOString().split("T")[0];
824
+ return client.get(`/search/issues?q=mentions:${username}+updated:>${since}`);
825
+ }
826
+ };
827
+ //#endregion
828
+ //#region src/types/notifications.ts
829
+ var normalizeThread = (item) => {
830
+ const data = item;
831
+ const repo = data.repository ?? {};
832
+ const subject = data.subject ?? {};
833
+ return {
834
+ id: String(data.id),
835
+ repository: String(repo.full_name ?? ""),
836
+ subjectTitle: String(subject.title ?? ""),
837
+ subjectType: String(subject.type ?? ""),
838
+ reason: String(data.reason ?? ""),
839
+ unread: Boolean(data.unread),
840
+ updatedAt: String(data.updated_at ?? "")
841
+ };
842
+ };
843
+ var normalizeIssue = (item) => {
844
+ const data = item;
845
+ const repo = data.repository ?? {};
846
+ return {
847
+ id: String(data.id),
848
+ repository: String(repo.full_name ?? ""),
849
+ subjectTitle: String(data.title ?? ""),
850
+ subjectType: String(data.pull_request ? "PullRequest" : "Issue"),
851
+ reason: "assigned",
852
+ unread: false,
853
+ updatedAt: String(data.updated_at ?? "")
854
+ };
855
+ };
856
+ var normalizeSearchItem = (item) => {
857
+ const data = item;
858
+ return {
859
+ id: String(data.id),
860
+ repository: String(data.repository_url ?? "").replace("https://api.github.com/repos/", ""),
861
+ subjectTitle: String(data.title ?? ""),
862
+ subjectType: String(data.pull_request ? "PullRequest" : "Issue"),
863
+ reason: "mention",
864
+ unread: false,
865
+ updatedAt: String(data.updated_at ?? "")
866
+ };
867
+ };
868
+ //#endregion
869
+ //#region src/services/notifications.ts
870
+ var formatTable = (notifications) => {
871
+ if (notifications.length === 0) {
872
+ logger.info(INFO_NO_NOTIFICATIONS);
873
+ return;
874
+ }
875
+ console.log();
876
+ console.table(notifications.map((n) => ({
877
+ repository: n.repository,
878
+ subject: n.subjectTitle,
879
+ type: n.subjectType,
880
+ reason: n.reason
881
+ })));
882
+ };
883
+ var list = async (options = {}) => {
884
+ logger.info("Fetching notifications.");
885
+ let notifications$1 = (await (await notifications.fetch({
886
+ all: options.all,
887
+ participating: options.participating,
888
+ perPage: options.limit
889
+ })).json()).map(normalizeThread);
890
+ if (options.repo) notifications$1 = notifications$1.filter((n) => n.repository === options.repo);
891
+ formatTable(notifications$1);
892
+ return {
893
+ success: true,
894
+ metadata: notifications$1
895
+ };
896
+ };
897
+ var markRead = async (id) => {
898
+ logger.info(`Marking notification ${id} as read.`);
899
+ await notifications.markRead(id);
900
+ logger.success("Notification marked as read.");
901
+ return { success: true };
902
+ };
903
+ var markDone = async (id) => {
904
+ logger.info(`Marking notification ${id} as done.`);
905
+ await notifications.markDone(id);
906
+ logger.success("Notification marked as done.");
907
+ return { success: true };
908
+ };
909
+ var activity = async () => {
910
+ logger.info("Fetching activity.");
911
+ const [issuesRes, reviewsRes, mentionsRes] = await Promise.all([
912
+ notifications.assignedIssues(),
913
+ notifications.reviewRequests(),
914
+ notifications.mentions("@me")
915
+ ]);
916
+ const assignedIssues = await issuesRes.json();
917
+ const reviewData = await reviewsRes.json();
918
+ const mentionData = await mentionsRes.json();
919
+ const result = {
920
+ assignedIssues: assignedIssues.map(normalizeIssue),
921
+ reviewRequests: (reviewData.items ?? []).map(normalizeSearchItem),
922
+ recentMentions: (mentionData.items ?? []).map(normalizeSearchItem)
923
+ };
924
+ console.log();
925
+ console.log("Assigned Issues:", result.assignedIssues.length);
926
+ console.log("Review Requests:", result.reviewRequests.length);
927
+ console.log("Recent Mentions:", result.recentMentions.length);
928
+ return {
929
+ success: true,
930
+ metadata: result
931
+ };
932
+ };
933
+ var mentions = async () => {
934
+ logger.info("Fetching mentions.");
935
+ const notifications$2 = ((await (await notifications.mentions("@me")).json()).items ?? []).map(normalizeSearchItem);
936
+ formatTable(notifications$2);
937
+ return {
938
+ success: true,
939
+ metadata: notifications$2
940
+ };
941
+ };
942
+ var notifications_default$1 = {
943
+ list,
944
+ markRead,
945
+ markDone,
946
+ activity,
947
+ mentions
948
+ };
949
+ //#endregion
950
+ //#region src/commands/mentions.ts
951
+ var register$2 = (program) => {
952
+ program.command("mentions").description("Find recent @mentions of you.").action(() => void notifications_default$1.mentions());
953
+ };
954
+ var mentions_default = { register: register$2 };
955
+ //#endregion
956
+ //#region src/commands/activity.ts
957
+ var register$1 = (program) => {
958
+ program.command("activity").description("Show assigned issues, review requests, and mentions.").action(() => void notifications_default$1.activity());
959
+ };
960
+ var activity_default = { register: register$1 };
961
+ //#endregion
962
+ //#region src/commands/notifications.ts
963
+ var register = (program) => {
964
+ const notifications = program.command("notifications").description("Manage GitHub notifications.");
965
+ notifications.command("list").description("List notifications.").option("-a, --all", "Include read notifications").option("-p, --participating", "Only participating notifications").option("-r, --repo <owner/repo>", "Filter by repository").option("-l, --limit <n>", "Max results").action((options) => {
966
+ notifications_default$1.list({
967
+ all: options.all,
968
+ participating: options.participating,
969
+ repo: options.repo,
970
+ limit: options.limit ? parseInt(options.limit, 10) : void 0
971
+ });
972
+ });
973
+ notifications.command("read <id>").description("Mark a notification as read.").action((id) => {
974
+ notifications_default$1.markRead(id);
975
+ });
976
+ notifications.command("done <id>").description("Mark a notification as done.").action((id) => {
977
+ notifications_default$1.markDone(id);
978
+ });
979
+ };
980
+ var notifications_default = { register };
768
981
  //#endregion
769
982
  //#region src/cli/index.ts
770
- commander.program.name("ghitgud").description("A simple CLI to give superpowers to GitHub.").version("2.0.0");
983
+ commander.program.name("ghitgud").description("A simple CLI to give superpowers to GitHub.").version("2.1.0");
984
+ gh_default.register(commander.program);
985
+ notifications_default.register(commander.program);
986
+ activity_default.register(commander.program);
987
+ mentions_default.register(commander.program);
771
988
  ping_default.register(commander.program);
772
989
  labels_default.register(commander.program);
773
990
  config_default.register(commander.program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@airscript/ghitgud",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "A simple CLI to give superpowers to GitHub.",
5
5
  "main": "dist/index.js",
6
6
  "files": [
@@ -30,7 +30,8 @@
30
30
  "lint": "eslint src/ tests/",
31
31
  "format": "prettier --write .",
32
32
  "format:check": "prettier --check .",
33
- "clean": "rm -rf dist coverage"
33
+ "clean": "rm -rf dist coverage",
34
+ "prepare": "husky && pnpm build"
34
35
  },
35
36
  "repository": {
36
37
  "type": "git",
@@ -49,6 +50,7 @@
49
50
  "@vitest/coverage-v8": "^3.2.4",
50
51
  "eslint": "10.3.0",
51
52
  "eslint-config-prettier": "10.1.8",
53
+ "husky": "9.1.7",
52
54
  "prettier": "3.8.3",
53
55
  "typescript": "^5.8.3",
54
56
  "typescript-eslint": "8.59.2",