githate 1.0.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 +105 -0
- package/bin/hater.js +67 -0
- package/lib/commands/check.js +98 -0
- package/lib/commands/follow.js +27 -0
- package/lib/commands/followers.js +43 -0
- package/lib/commands/following.js +43 -0
- package/lib/commands/login.js +39 -0
- package/lib/commands/unfollow.js +27 -0
- package/lib/ui/display.js +30 -0
- package/lib/utils/auth.js +27 -0
- package/lib/utils/store.js +32 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# GitHate CLI 🕵️♂️
|
|
2
|
+
|
|
3
|
+
**Track who unfollowed you on GitHub directly from your terminal.**
|
|
4
|
+
|
|
5
|
+
`githate` is a modern, fast, and beautiful CLI tool that helps you keep track of your GitHub followers. It detects who unfollowed you since the last check and lets you manage your following list with ease.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- 🕵️ **Track Unfollowers**: Instantly see who stopped following you.
|
|
12
|
+
- 📈 **Track New Followers**: See who started following you.
|
|
13
|
+
- 👥 **Manage Relationships**: List followers, following, and follow/unfollow users.
|
|
14
|
+
- 🔐 **Secure**: Your Personal Access Token is stored locally on your machine.
|
|
15
|
+
- 💅 **Beautiful UI**: Built with `@clack/prompts` and `picocolors` for a great experience.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
You can install `githate` globally using npm:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g githate
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
_Note: You may need to use `sudo` on macOS/Linux if you have permission issues._
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
1. **Generate a GitHub Personal Access Token (PAT)**:
|
|
30
|
+
- Go to [GitHub Settings > Developer settings > Personal access tokens > Tokens (classic)](https://github.com/settings/tokens).
|
|
31
|
+
- Click **Generate new token (classic)**.
|
|
32
|
+
- Give it a note (e.g., "GitHate CLI").
|
|
33
|
+
- Select the **`read:user`** and **`user:follow`** scopes.
|
|
34
|
+
- Click **Generate token** and copy it.
|
|
35
|
+
|
|
36
|
+
2. **Login**:
|
|
37
|
+
Run the login command and paste your token when prompted:
|
|
38
|
+
```bash
|
|
39
|
+
githate login
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
### Check for Haters (Unfollowers)
|
|
45
|
+
|
|
46
|
+
This is the main feature. Run this command to compare your current followers with the last saved state.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
githate check
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
_On the first run, it will just save your current followers._
|
|
53
|
+
|
|
54
|
+
### List Followers
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
githate followers
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### List Following
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
githate following
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Follow a User
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
githate follow <username>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Unfollow a User
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
githate unfollow <username>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Automation (Daily Check)
|
|
79
|
+
|
|
80
|
+
You can set up a cron job or task scheduler to run `githate check` daily.
|
|
81
|
+
|
|
82
|
+
### macOS / Linux (Cron)
|
|
83
|
+
|
|
84
|
+
1. Open your crontab:
|
|
85
|
+
```bash
|
|
86
|
+
crontab -e
|
|
87
|
+
```
|
|
88
|
+
2. Add the following line to run everyday at 9 AM:
|
|
89
|
+
```bash
|
|
90
|
+
0 9 * * * /usr/local/bin/githate check >> /tmp/githate.log 2>&1
|
|
91
|
+
```
|
|
92
|
+
_(Make sure to use the correct path to `githate`. You can find it with `which githate`)_
|
|
93
|
+
|
|
94
|
+
### Windows (Task Scheduler)
|
|
95
|
+
|
|
96
|
+
1. Open **Task Scheduler**.
|
|
97
|
+
2. Create a Basic Task.
|
|
98
|
+
3. Set the trigger to **Daily**.
|
|
99
|
+
4. Set the action to **Start a program**.
|
|
100
|
+
5. Program/script: `githate` (or full path to `githate.cmd`).
|
|
101
|
+
6. Add arguments: `check`.
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
ISC
|
package/bin/hater.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { join, dirname } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const pkg = JSON.parse(
|
|
9
|
+
readFileSync(join(__dirname, "../package.json"), "utf-8"),
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
const program = new Command();
|
|
13
|
+
|
|
14
|
+
program
|
|
15
|
+
.name("githate")
|
|
16
|
+
.description("Track who unfollowed you on GitHub")
|
|
17
|
+
.version(pkg.version);
|
|
18
|
+
|
|
19
|
+
program
|
|
20
|
+
.command("login")
|
|
21
|
+
.description("Login to GitHub with a Personal Access Token")
|
|
22
|
+
.action(async () => {
|
|
23
|
+
const { login } = await import("../lib/commands/login.js");
|
|
24
|
+
await login();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command("check")
|
|
29
|
+
.description("Check for new unfollowers")
|
|
30
|
+
.action(async () => {
|
|
31
|
+
const { check } = await import("../lib/commands/check.js");
|
|
32
|
+
await check();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
program
|
|
36
|
+
.command("followers")
|
|
37
|
+
.description("List your followers")
|
|
38
|
+
.action(async () => {
|
|
39
|
+
const { followers } = await import("../lib/commands/followers.js");
|
|
40
|
+
await followers();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
program
|
|
44
|
+
.command("following")
|
|
45
|
+
.description("List who you are following")
|
|
46
|
+
.action(async () => {
|
|
47
|
+
const { following } = await import("../lib/commands/following.js");
|
|
48
|
+
await following();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
program
|
|
52
|
+
.command("follow <username>")
|
|
53
|
+
.description("Follow a user")
|
|
54
|
+
.action(async (username) => {
|
|
55
|
+
const { follow } = await import("../lib/commands/follow.js");
|
|
56
|
+
await follow(username);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
program
|
|
60
|
+
.command("unfollow <username>")
|
|
61
|
+
.description("Unfollow a user")
|
|
62
|
+
.action(async (username) => {
|
|
63
|
+
const { unfollow } = await import("../lib/commands/unfollow.js");
|
|
64
|
+
await unfollow(username);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { getOctokit } from "../utils/auth.js";
|
|
2
|
+
import {
|
|
3
|
+
getStoredFollowers,
|
|
4
|
+
setStoredFollowers,
|
|
5
|
+
setLastCheck,
|
|
6
|
+
getLastCheck,
|
|
7
|
+
} from "../utils/store.js";
|
|
8
|
+
import {
|
|
9
|
+
displayIntro,
|
|
10
|
+
displaySuccess,
|
|
11
|
+
displayError,
|
|
12
|
+
createSpinner,
|
|
13
|
+
displayInfo,
|
|
14
|
+
displayWarning,
|
|
15
|
+
displayOutro,
|
|
16
|
+
} from "../ui/display.js";
|
|
17
|
+
import color from "picocolors";
|
|
18
|
+
|
|
19
|
+
export const check = async () => {
|
|
20
|
+
displayIntro();
|
|
21
|
+
const s = createSpinner();
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const octokit = getOctokit();
|
|
25
|
+
s.start("Fetching current followers...");
|
|
26
|
+
|
|
27
|
+
const currentFollowers = await octokit.paginate(
|
|
28
|
+
octokit.rest.users.listFollowersForAuthenticatedUser,
|
|
29
|
+
{
|
|
30
|
+
per_page: 100,
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
const currentFollowerLogins = currentFollowers.map((f) => f.login);
|
|
34
|
+
|
|
35
|
+
s.stop("Followers fetched");
|
|
36
|
+
|
|
37
|
+
const storedFollowers = getStoredFollowers();
|
|
38
|
+
const lastCheck = getLastCheck();
|
|
39
|
+
|
|
40
|
+
if (storedFollowers.length === 0) {
|
|
41
|
+
displayInfo(
|
|
42
|
+
"First run detected! Storing current followers for future checks.",
|
|
43
|
+
);
|
|
44
|
+
setStoredFollowers(currentFollowerLogins);
|
|
45
|
+
setLastCheck(new Date().toISOString());
|
|
46
|
+
displayOutro(`Tracking ${currentFollowerLogins.length} followers.`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Find new followers
|
|
51
|
+
const newFollowers = currentFollowerLogins.filter(
|
|
52
|
+
(login) => !storedFollowers.includes(login),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Find unfollowers (The Haters)
|
|
56
|
+
const unfollowers = storedFollowers.filter(
|
|
57
|
+
(login) => !currentFollowerLogins.includes(login),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
console.log("");
|
|
61
|
+
if (lastCheck) {
|
|
62
|
+
displayInfo(`Last check: ${new Date(lastCheck).toLocaleString()}`);
|
|
63
|
+
}
|
|
64
|
+
console.log("");
|
|
65
|
+
|
|
66
|
+
if (newFollowers.length > 0) {
|
|
67
|
+
displaySuccess(`New Followers (+${newFollowers.length}):`);
|
|
68
|
+
newFollowers.forEach((login) =>
|
|
69
|
+
console.log(` ${color.green("+")} ${login}`),
|
|
70
|
+
);
|
|
71
|
+
console.log("");
|
|
72
|
+
} else {
|
|
73
|
+
console.log(color.dim("No new followers."));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (unfollowers.length > 0) {
|
|
77
|
+
displayWarning(`Unfollowers (-${unfollowers.length}):`); // Using warning for "haters"
|
|
78
|
+
unfollowers.forEach((login) =>
|
|
79
|
+
console.log(` ${color.red("-")} ${login}`),
|
|
80
|
+
);
|
|
81
|
+
console.log("");
|
|
82
|
+
displayInfo(
|
|
83
|
+
'Consider using "hater unfollow <username>" if you want to respond.',
|
|
84
|
+
);
|
|
85
|
+
} else {
|
|
86
|
+
console.log(color.dim("No new unfollowers. Everyone still loves you!"));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Update store
|
|
90
|
+
setStoredFollowers(currentFollowerLogins);
|
|
91
|
+
setLastCheck(new Date().toISOString());
|
|
92
|
+
|
|
93
|
+
displayOutro("Check complete & database updated.");
|
|
94
|
+
} catch (error) {
|
|
95
|
+
s.stop("Check failed");
|
|
96
|
+
displayError(error.message);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getOctokit } from "../utils/auth.js";
|
|
2
|
+
import {
|
|
3
|
+
displayIntro,
|
|
4
|
+
displaySuccess,
|
|
5
|
+
displayError,
|
|
6
|
+
createSpinner,
|
|
7
|
+
} from "../ui/display.js";
|
|
8
|
+
|
|
9
|
+
export const follow = async (username) => {
|
|
10
|
+
displayIntro();
|
|
11
|
+
const s = createSpinner();
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const octokit = getOctokit();
|
|
15
|
+
s.start(`Following ${username}...`);
|
|
16
|
+
|
|
17
|
+
await octokit.rest.users.follow({
|
|
18
|
+
username,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
s.stop(`Followed ${username}`);
|
|
22
|
+
displaySuccess(`Successfully followed ${username}`);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
s.stop(`Failed to follow ${username}`);
|
|
25
|
+
displayError(error.message);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { getOctokit } from "../utils/auth.js";
|
|
2
|
+
import {
|
|
3
|
+
displayIntro,
|
|
4
|
+
displayOutro,
|
|
5
|
+
createSpinner,
|
|
6
|
+
displayError,
|
|
7
|
+
} from "../ui/display.js";
|
|
8
|
+
import color from "picocolors";
|
|
9
|
+
|
|
10
|
+
export const followers = async () => {
|
|
11
|
+
displayIntro();
|
|
12
|
+
const s = createSpinner();
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const octokit = getOctokit();
|
|
16
|
+
s.start("Fetching followers...");
|
|
17
|
+
|
|
18
|
+
const followersList = await octokit.paginate(
|
|
19
|
+
octokit.rest.users.listFollowersForAuthenticatedUser,
|
|
20
|
+
{
|
|
21
|
+
per_page: 100,
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
s.stop(`Found ${followersList.length} followers`);
|
|
26
|
+
|
|
27
|
+
if (followersList.length === 0) {
|
|
28
|
+
displayOutro("You have no followers yet.");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log(""); // New line
|
|
33
|
+
followersList.forEach((follower) => {
|
|
34
|
+
console.log(`${color.green("•")} ${follower.login}`);
|
|
35
|
+
});
|
|
36
|
+
console.log("");
|
|
37
|
+
|
|
38
|
+
displayOutro("End of followers list");
|
|
39
|
+
} catch (error) {
|
|
40
|
+
s.stop("Failed to fetch followers");
|
|
41
|
+
displayError(error.message);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { getOctokit } from "../utils/auth.js";
|
|
2
|
+
import {
|
|
3
|
+
displayIntro,
|
|
4
|
+
displayOutro,
|
|
5
|
+
createSpinner,
|
|
6
|
+
displayError,
|
|
7
|
+
} from "../ui/display.js";
|
|
8
|
+
import color from "picocolors";
|
|
9
|
+
|
|
10
|
+
export const following = async () => {
|
|
11
|
+
displayIntro();
|
|
12
|
+
const s = createSpinner();
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const octokit = getOctokit();
|
|
16
|
+
s.start("Fetching following list...");
|
|
17
|
+
|
|
18
|
+
const followingList = await octokit.paginate(
|
|
19
|
+
octokit.rest.users.listFollowedByAuthenticated,
|
|
20
|
+
{
|
|
21
|
+
per_page: 100,
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
s.stop(`You are following ${followingList.length} users`);
|
|
26
|
+
|
|
27
|
+
if (followingList.length === 0) {
|
|
28
|
+
displayOutro("You are not following anyone.");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log(""); // New line
|
|
33
|
+
followingList.forEach((user) => {
|
|
34
|
+
console.log(`${color.blue("•")} ${user.login}`);
|
|
35
|
+
});
|
|
36
|
+
console.log("");
|
|
37
|
+
|
|
38
|
+
displayOutro("End of following list");
|
|
39
|
+
} catch (error) {
|
|
40
|
+
s.stop("Failed to fetch following list");
|
|
41
|
+
displayError(error.message);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { password, isCancel, cancel } from "@clack/prompts";
|
|
2
|
+
import { setStoredToken, verifyToken } from "../utils/auth.js";
|
|
3
|
+
import {
|
|
4
|
+
displayIntro,
|
|
5
|
+
displaySuccess,
|
|
6
|
+
displayError,
|
|
7
|
+
displayOutro,
|
|
8
|
+
createSpinner,
|
|
9
|
+
} from "../ui/display.js";
|
|
10
|
+
|
|
11
|
+
export const login = async () => {
|
|
12
|
+
displayIntro();
|
|
13
|
+
|
|
14
|
+
const token = await password({
|
|
15
|
+
message: "Enter your GitHub Personal Access Token",
|
|
16
|
+
mask: "*",
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (isCancel(token)) {
|
|
20
|
+
cancel("Login cancelled");
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const s = createSpinner();
|
|
25
|
+
s.start("Verifying token...");
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const user = await verifyToken(token);
|
|
29
|
+
setStoredToken(token);
|
|
30
|
+
s.stop("Token verified!");
|
|
31
|
+
displaySuccess(`Logged in as ${user.login}`);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
s.stop("Verification failed");
|
|
34
|
+
displayError(error.message);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
displayOutro("You are ready to track the haters!");
|
|
39
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getOctokit } from "../utils/auth.js";
|
|
2
|
+
import {
|
|
3
|
+
displayIntro,
|
|
4
|
+
displaySuccess,
|
|
5
|
+
displayError,
|
|
6
|
+
createSpinner,
|
|
7
|
+
} from "../ui/display.js";
|
|
8
|
+
|
|
9
|
+
export const unfollow = async (username) => {
|
|
10
|
+
displayIntro();
|
|
11
|
+
const s = createSpinner();
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const octokit = getOctokit();
|
|
15
|
+
s.start(`Unfollowing ${username}...`);
|
|
16
|
+
|
|
17
|
+
await octokit.rest.users.unfollow({
|
|
18
|
+
username,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
s.stop(`Unfollowed ${username}`);
|
|
22
|
+
displaySuccess(`Successfully unfollowed ${username}`);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
s.stop(`Failed to unfollow ${username}`);
|
|
25
|
+
displayError(error.message);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { intro, outro, spinner, note, log } from "@clack/prompts";
|
|
2
|
+
import color from "picocolors";
|
|
3
|
+
|
|
4
|
+
export const displayIntro = () => {
|
|
5
|
+
intro(color.bgRed(color.white(" GITHATE CLI ")));
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const displayOutro = (message) => {
|
|
9
|
+
outro(message);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const displayError = (message) => {
|
|
13
|
+
log.error(color.red(message));
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const displaySuccess = (message) => {
|
|
17
|
+
log.success(color.green(message));
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const displayInfo = (message) => {
|
|
21
|
+
log.info(color.cyan(message));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const displayWarning = (message) => {
|
|
25
|
+
log.warn(color.yellow(message));
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const createSpinner = () => {
|
|
29
|
+
return spinner();
|
|
30
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Octokit } from "@octokit/rest";
|
|
2
|
+
import { getStoredToken } from "./store.js";
|
|
3
|
+
import { displayError } from "../ui/display.js";
|
|
4
|
+
|
|
5
|
+
let octokitInstance = null;
|
|
6
|
+
|
|
7
|
+
export const getOctokit = () => {
|
|
8
|
+
if (octokitInstance) return octokitInstance;
|
|
9
|
+
|
|
10
|
+
const token = getStoredToken();
|
|
11
|
+
if (!token) {
|
|
12
|
+
throw new Error('Not logged in. Run "hater login" first.');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
octokitInstance = new Octokit({ auth: token });
|
|
16
|
+
return octokitInstance;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const verifyToken = async (token) => {
|
|
20
|
+
try {
|
|
21
|
+
const octokit = new Octokit({ auth: token });
|
|
22
|
+
const { data } = await octokit.rest.users.getAuthenticated();
|
|
23
|
+
return data;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
throw new Error("Invalid token or network error.");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Conf from "conf";
|
|
2
|
+
|
|
3
|
+
const schema = {
|
|
4
|
+
token: {
|
|
5
|
+
type: "string",
|
|
6
|
+
},
|
|
7
|
+
followers: {
|
|
8
|
+
type: "array",
|
|
9
|
+
default: [],
|
|
10
|
+
},
|
|
11
|
+
lastCheck: {
|
|
12
|
+
type: "string",
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const config = new Conf({
|
|
17
|
+
projectName: "githate-cli",
|
|
18
|
+
schema,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const getStoredToken = () => config.get("token");
|
|
22
|
+
export const setStoredToken = (token) => config.set("token", token);
|
|
23
|
+
export const deleteStoredToken = () => config.delete("token");
|
|
24
|
+
|
|
25
|
+
export const getStoredFollowers = () => config.get("followers");
|
|
26
|
+
export const setStoredFollowers = (followers) =>
|
|
27
|
+
config.set("followers", followers);
|
|
28
|
+
|
|
29
|
+
export const setLastCheck = (date) => config.set("lastCheck", date);
|
|
30
|
+
export const getLastCheck = () => config.get("lastCheck");
|
|
31
|
+
|
|
32
|
+
export const clearStore = () => config.clear();
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "githate",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A CLI tool to track who unfollowed you on GitHub.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"githate": "bin/hater.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"github",
|
|
15
|
+
"cli",
|
|
16
|
+
"unfollowers",
|
|
17
|
+
"tracker"
|
|
18
|
+
],
|
|
19
|
+
"author": "",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@clack/prompts": "^1.0.0",
|
|
23
|
+
"@octokit/rest": "^20.0.0",
|
|
24
|
+
"commander": "^14.0.3",
|
|
25
|
+
"conf": "^15.1.0",
|
|
26
|
+
"picocolors": "^1.1.1"
|
|
27
|
+
}
|
|
28
|
+
}
|