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.
@@ -8,7 +8,7 @@ on:
8
8
  - package.json
9
9
  - package-lock.json
10
10
  - src/**
11
- - .github/workflows/publish-root.yml
11
+ - .github/workflows/publish.yml
12
12
 
13
13
  jobs:
14
14
  publish:
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, and can generate SSH keys for each profile when needed.
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": "1.0.8",
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
+ }
@@ -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
  }