gitshift 1.0.8 → 2.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/.github/workflows/publish.yml +1 -1
- package/README.md +5 -1
- package/package.json +2 -1
- package/src/commands/scan.js +89 -0
- package/src/server.js +8 -0
- package/src/services/scan.js +36 -0
- package/src/services/ssh.js +19 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# GitShift CLI
|
|
2
2
|
|
|
3
|
-
GitShift CLI helps you create, manage, and switch between GitHub identity profiles from the terminal. It stores profiles locally, updates your global Git config,
|
|
3
|
+
GitShift CLI helps you create, manage, and switch between GitHub identity profiles from the terminal. It stores profiles locally, updates your global Git config, can generate SSH keys for each profile when needed, and can import existing SSH keys from your `~/.ssh` folder.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -37,6 +37,7 @@ gitshift --help
|
|
|
37
37
|
- `gitshift current` - Display the active profile.
|
|
38
38
|
- `gitshift use <profile>` - Switch to a saved profile and update global Git user name and email.
|
|
39
39
|
- `gitshift remove <profile>` - Delete a saved profile.
|
|
40
|
+
- `gitshift scan` - Scan your `~/.ssh` folder and import existing SSH keys into new profiles.
|
|
40
41
|
- `gitshift doctor` - Check whether Git, SSH, and GitHub CLI are installed.
|
|
41
42
|
|
|
42
43
|
## Example Workflow
|
|
@@ -44,6 +45,7 @@ gitshift --help
|
|
|
44
45
|
```bash
|
|
45
46
|
gitshift add
|
|
46
47
|
gitshift list
|
|
48
|
+
gitshift scan
|
|
47
49
|
gitshift use personal
|
|
48
50
|
gitshift current
|
|
49
51
|
gitshift doctor
|
|
@@ -51,6 +53,8 @@ gitshift doctor
|
|
|
51
53
|
|
|
52
54
|
When you create a profile and choose SSH generation, GitShift creates a key under your home directory in `.ssh` using the pattern `gitshift-<profile-name>`.
|
|
53
55
|
|
|
56
|
+
If you already have SSH keys on your machine, `gitshift scan` will list the available keys, let you pick one, and save it as a new imported profile.
|
|
57
|
+
|
|
54
58
|
## How It Works
|
|
55
59
|
|
|
56
60
|
Profiles are saved locally on your machine using the app's configuration store. Switching profiles updates your global Git identity with:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitshift",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "GitHub Account Switcher CLI",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"bin": {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"commander": "^15.0.0",
|
|
29
29
|
"conf": "^15.1.0",
|
|
30
30
|
"execa": "^9.6.1",
|
|
31
|
+
"fast-glob": "^3.3.3",
|
|
31
32
|
"fs-extra": "^11.3.5",
|
|
32
33
|
"ora": "^9.4.0"
|
|
33
34
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
input,
|
|
5
|
+
select,
|
|
6
|
+
} from "@inquirer/prompts";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
findSSHKeys,
|
|
10
|
+
} from "../services/scan.js";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
getProfiles,
|
|
14
|
+
saveProfile,
|
|
15
|
+
} from "../services/profile.js";
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
error,
|
|
19
|
+
info,
|
|
20
|
+
success,
|
|
21
|
+
} from "../utils/logger.js";
|
|
22
|
+
|
|
23
|
+
export async function scanCommand() {
|
|
24
|
+
try {
|
|
25
|
+
const keys = await findSSHKeys();
|
|
26
|
+
|
|
27
|
+
if (!keys.length) {
|
|
28
|
+
info("No SSH keys found");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const existing = getProfiles();
|
|
33
|
+
|
|
34
|
+
const importedPaths = existing.map((profile) => profile.sshKey);
|
|
35
|
+
|
|
36
|
+
const available = keys.filter((key) => !importedPaths.includes(key));
|
|
37
|
+
|
|
38
|
+
if (!available.length) {
|
|
39
|
+
info("All keys already imported");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const selected = await select({
|
|
44
|
+
message: "Select SSH Key",
|
|
45
|
+
choices: available.map((key) => ({
|
|
46
|
+
name: path.basename(key),
|
|
47
|
+
value: key,
|
|
48
|
+
})),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const profileName = await input({
|
|
52
|
+
message: "Profile Name",
|
|
53
|
+
default: path.basename(selected),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const username = await input({
|
|
57
|
+
message: "GitHub Username",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const email = await input({
|
|
61
|
+
message: "Email",
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
saveProfile({
|
|
66
|
+
name: profileName,
|
|
67
|
+
username,
|
|
68
|
+
email,
|
|
69
|
+
sshKey: selected,
|
|
70
|
+
source:
|
|
71
|
+
"imported",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
success(`Existing SSH Key Imported "${profileName}"`);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
error(err.message);
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (err && err.name === "ExitPromptError") {
|
|
80
|
+
// User canceled the prompt (Ctrl+C / SIGINT). Exit gracefully.
|
|
81
|
+
error("Existing SSH Key Import canceled by user.");
|
|
82
|
+
process.exitCode = 0;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
error(String(err));
|
|
87
|
+
process.exitCode = 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/server.js
CHANGED
|
@@ -9,6 +9,7 @@ import { currentCommand } from "./commands/current.js";
|
|
|
9
9
|
import { doctorCommand } from "./commands/doctor.js";
|
|
10
10
|
import { listCommand } from "./commands/list.js";
|
|
11
11
|
import { removeCommand } from "./commands/remove.js";
|
|
12
|
+
import { scanCommand } from "./commands/scan.js";
|
|
12
13
|
import { useCommand } from "./commands/use.js";
|
|
13
14
|
|
|
14
15
|
const require = createRequire(import.meta.url);
|
|
@@ -116,6 +117,13 @@ async function main() {
|
|
|
116
117
|
)
|
|
117
118
|
.action(removeCommand);
|
|
118
119
|
|
|
120
|
+
program
|
|
121
|
+
.command("scan")
|
|
122
|
+
.description(
|
|
123
|
+
"Import existing SSH keys"
|
|
124
|
+
)
|
|
125
|
+
.action(scanCommand);
|
|
126
|
+
|
|
119
127
|
program
|
|
120
128
|
.command("doctor")
|
|
121
129
|
.description(
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import fg from "fast-glob";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
|
|
6
|
+
export async function findSSHKeys() {
|
|
7
|
+
const sshDir = path.join(os.homedir(), ".ssh");
|
|
8
|
+
|
|
9
|
+
const exists =
|
|
10
|
+
await fs.pathExists(sshDir);
|
|
11
|
+
|
|
12
|
+
if (!exists) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const files = await fg("*", {
|
|
17
|
+
cwd: sshDir,
|
|
18
|
+
absolute: true,
|
|
19
|
+
onlyFiles: true,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return files.filter((file) => {
|
|
23
|
+
const name = path.basename(file);
|
|
24
|
+
|
|
25
|
+
if (
|
|
26
|
+
name.endsWith(".pub") ||
|
|
27
|
+
name === "config" ||
|
|
28
|
+
name === "known_hosts" ||
|
|
29
|
+
name === "authorized_keys"
|
|
30
|
+
) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return true;
|
|
35
|
+
});
|
|
36
|
+
}
|
package/src/services/ssh.js
CHANGED
|
@@ -51,4 +51,23 @@ export async function generateSSHKey(
|
|
|
51
51
|
]);
|
|
52
52
|
|
|
53
53
|
return keyPath;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function getPublicKey(
|
|
57
|
+
privateKeyPath
|
|
58
|
+
) {
|
|
59
|
+
const publicKey =
|
|
60
|
+
`${privateKeyPath}.pub`;
|
|
61
|
+
|
|
62
|
+
const exists =
|
|
63
|
+
await fs.pathExists(publicKey);
|
|
64
|
+
|
|
65
|
+
if (!exists) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return fs.readFile(
|
|
70
|
+
publicKey,
|
|
71
|
+
"utf8"
|
|
72
|
+
);
|
|
54
73
|
}
|