Skip to content

Commit 7f56aef

Browse files
authored
Merge pull request #2 from lpgrd/develop
Release v1.1.0
2 parents d34827c + 49cdb5f commit 7f56aef

18 files changed

Lines changed: 500 additions & 152 deletions

File tree

.github/workflows/release.yml

Lines changed: 0 additions & 119 deletions
This file was deleted.

.npmignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ tsup.config.prod.ts
99
.prettierrc
1010

1111
# Testing
12-
tests/
12+
test/
1313
coverage/
1414
*.test.ts
1515
*.spec.ts

CHANGELOG.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4040

4141
### Removed
4242
- Code coverage dependencies and scripts (`vitest coverage`, `c8`, `jest`)
43-
- Duplicate npm scripts (consolidated lint/format variants)
43+
- Duplicate npm scripts (consolidated lint/format variants)
44+
45+
## [1.1.0] - 2025-07-06
46+
47+
### Added
48+
- **Email Masking**: All email addresses are now masked by default in command outputs (e.g., `u***r@e****e.com`)
49+
- **SSH Key Fingerprints**: `auth` command displays SSH key fingerprint (SHA256) instead of the full public key
50+
- **Windows File Permissions**: Implemented ACL-based permissions for SSH config and key files on Windows using `icacls`
51+
52+
### Changed
53+
- **Profile Auto-detection**: Now requires user confirmation before applying detected profiles
54+
- **Account Selection**: Removed email addresses from selection dropdowns in `clone` and `init` commands
55+
- **SSH Key Info**: Removed comment field from SSH key information display
56+
57+
### Fixed
58+
- SSH key generation failing due to missing comment parameter in ssh-keygen command
59+
- Windows SSH config files not receiving restrictive permissions
60+
61+
### Security
62+
- Enhanced privacy protection by masking email addresses throughout the application
63+
- Reduced risk of accidental SSH key exposure by showing only fingerprints
64+
- Improved Windows security with proper file permissions for SSH-related files

README.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -407,17 +407,28 @@ ssh -T git@github.com-personal
407407

408408
### Windows: SSH Agent Not Running
409409

410-
If you see "Could not add key to ssh-agent" on Windows:
410+
If you see "Could not add key to ssh-agent" on Windows, the SSH agent service is likely not running.
411411

412-
```bash
413-
# Start ssh-agent service (run as Administrator)
412+
**To fix this permanently:**
413+
414+
```powershell
415+
# 1. Open PowerShell as Administrator
416+
# 2. Enable and start the SSH agent service
414417
Get-Service ssh-agent | Set-Service -StartupType Automatic
415418
Start-Service ssh-agent
416419
417-
# Or manually add your key
420+
# 3. Verify it's running
421+
Get-Service ssh-agent
422+
```
423+
424+
**Alternative: Manually add your key**
425+
```bash
426+
# If the above doesn't work, manually add your key
418427
ssh-add C:\Users\YourName\.ssh\gitm_personal_github
419428
```
420429

430+
**Note:** This is a one-time setup. Once enabled, the SSH agent will start automatically with Windows.
431+
421432
### Port 22 Blocked
422433

423434
If your network blocks port 22:

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@loopgrid/gitm",
3-
"version": "1.0.2",
3+
"version": "1.1.0",
44
"description": "Seamlessly manage multiple git accounts on the same device",
55
"main": "dist/cli.js",
66
"bin": {

src/commands/add.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,9 @@ export async function addAccount(profile: string, options?: { provider?: string
109109

110110
if (answers.generateSSH) {
111111
try {
112-
await generateSSHKey(sshKeyPath, answers.email, profile);
112+
await generateSSHKey(sshKeyPath, answers.email, {
113+
comment: `${answers.email} (gitm: ${profile})`,
114+
});
113115
logSuccess('SSH key generated successfully');
114116
} catch (error) {
115117
logError(`Failed to generate SSH key: ${(error as Error).message}`);

src/commands/auth.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getAccount } from '@/lib/config';
2-
import { readPublicKey, updateSSHConfig } from '@/utils/ssh';
2+
import { readPublicKey, updateSSHConfig, getSSHKeyInfo } from '@/utils/ssh';
33
import { logError, logSuccess, logInfo, log, LogLevel } from '@/utils/cli';
44
import { getAuthState, updateAuthState, clearAuthState, isAuthCompleted } from '@/utils/auth-state';
55
import { safeExec } from '@/utils/shell';
@@ -46,9 +46,11 @@ export async function authenticateAccount(profile: string): Promise<void> {
4646

4747
try {
4848
const publicKey = await readPublicKey(account.sshKeyPath);
49+
const keyInfo = getSSHKeyInfo(publicKey);
4950

50-
console.log(chalk.bold('\nSSH Public Key for ' + profile + ':\n'));
51-
console.log(chalk.gray(publicKey));
51+
console.log(chalk.bold('\nSSH Key Details for ' + profile + ':\n'));
52+
console.log(chalk.cyan('Type: ') + keyInfo.type);
53+
console.log(chalk.cyan('Fingerprint: ') + keyInfo.fingerprint);
5254

5355
const providerUrls: Record<string, string> = {
5456
github: 'https://github.com/settings/keys',

src/commands/clone.ts

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { detectAccountForRepo } from '@/utils/git';
55
import { getSSHRemoteUrl, updateSSHConfig, addSSHKeyToAgent } from '@/utils/ssh';
66
import { getAccount, getAccounts } from '@/lib/config';
77
import { CloneOptions } from '@/types';
8-
import { logError, logSuccess, logWarning, logInfo } from '@/utils/cli';
8+
import { logError, logSuccess, logWarning, logInfo, maskEmail } from '@/utils/cli';
99

1010
/**
1111
* Clone a repository with the appropriate account
@@ -35,17 +35,46 @@ export async function cloneRepo(
3535
let selectedAccount;
3636

3737
if (detection.profile) {
38-
selectedProfile = detection.profile;
39-
selectedAccount = detection.account;
40-
logSuccess(`Auto-detected account: ${selectedProfile}`);
38+
logSuccess(`Auto-detected account: ${detection.profile}`);
39+
40+
const { confirmDetection } = await inquirer.prompt<{ confirmDetection: boolean }>([
41+
{
42+
type: 'confirm',
43+
name: 'confirmDetection',
44+
message: `Use auto-detected account '${detection.profile}'?`,
45+
default: true,
46+
},
47+
]);
48+
49+
if (confirmDetection) {
50+
selectedProfile = detection.profile;
51+
selectedAccount = detection.account;
52+
} else {
53+
// Show all available accounts for selection
54+
const allAccounts = Object.entries(accounts);
55+
const { profile } = await inquirer.prompt<{ profile: string }>([
56+
{
57+
type: 'list',
58+
name: 'profile',
59+
message: 'Select account to use for this repository:',
60+
choices: allAccounts.map(([profileName, account]) => ({
61+
name: `${profileName} (${account.name})`,
62+
value: profileName,
63+
})),
64+
},
65+
]);
66+
67+
selectedProfile = profile;
68+
selectedAccount = getAccount(profile);
69+
}
4170
} else if (detection.candidates && detection.candidates.length > 0) {
4271
const { profile } = await inquirer.prompt<{ profile: string }>([
4372
{
4473
type: 'list',
4574
name: 'profile',
4675
message: 'Select account to use for this repository:',
4776
choices: detection.candidates.map(([profileName, account]) => ({
48-
name: `${profileName} (${account.name} - ${account.email})`,
77+
name: `${profileName} (${account.name})`,
4978
value: profileName,
5079
})),
5180
},
@@ -103,7 +132,7 @@ export async function cloneRepo(
103132

104133
logSuccess(`Git config set for account '${selectedProfile}'`);
105134
logInfo(`Repository: ${path.resolve(targetDir)}`);
106-
logInfo(`Account: ${selectedAccount.name} <${selectedAccount.email}>`);
135+
logInfo(`Account: ${selectedAccount.name} <${maskEmail(selectedAccount.email)}>`);
107136

108137
if (!useSSH && url.startsWith('https://')) {
109138
logInfo("Note: Cloned with HTTPS. Use 'gitm use <profile>' to switch to SSH");

src/commands/init.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import inquirer from 'inquirer';
22
import { getCurrentRepo, detectAccountForRepo, applyAccountToRepo } from '@/utils/git';
33
import { logError, logSuccess, logInfo, logWarning } from '@/utils/cli';
4+
import { getAccounts } from '@/lib/config';
45

56
/**
67
* Initialize repository with auto-detected or selected account
@@ -14,7 +15,6 @@ export async function initRepo(options: { ssh?: boolean } = { ssh: true }): Prom
1415

1516
if (!detection) {
1617
// Let's check if accounts actually exist
17-
const { getAccounts } = await import('@/lib/config');
1818
const accounts = getAccounts();
1919
const accountCount = Object.keys(accounts).length;
2020

@@ -32,15 +32,44 @@ export async function initRepo(options: { ssh?: boolean } = { ssh: true }): Prom
3232

3333
if (detection.profile) {
3434
logSuccess(`Auto-detected account: ${detection.profile}`);
35-
selectedProfile = detection.profile;
35+
36+
const { confirmDetection } = await inquirer.prompt<{ confirmDetection: boolean }>([
37+
{
38+
type: 'confirm',
39+
name: 'confirmDetection',
40+
message: `Use auto-detected account '${detection.profile}'?`,
41+
default: true,
42+
},
43+
]);
44+
45+
if (confirmDetection) {
46+
selectedProfile = detection.profile;
47+
} else {
48+
// Show all available accounts for selection
49+
const accounts = getAccounts();
50+
const allAccounts = Object.entries(accounts);
51+
const { profile } = await inquirer.prompt<{ profile: string }>([
52+
{
53+
type: 'list',
54+
name: 'profile',
55+
message: 'Select account to use for this repository:',
56+
choices: allAccounts.map(([profileName, account]) => ({
57+
name: `${profileName} (${account.name})`,
58+
value: profileName,
59+
})),
60+
},
61+
]);
62+
63+
selectedProfile = profile;
64+
}
3665
} else if (detection.candidates && detection.candidates.length > 0) {
3766
const { profile } = await inquirer.prompt<{ profile: string }>([
3867
{
3968
type: 'list',
4069
name: 'profile',
4170
message: 'Select account to use for this repository:',
4271
choices: detection.candidates.map(([profileName, account]) => ({
43-
name: `${profileName} (${account.name} - ${account.email})`,
72+
name: `${profileName} (${account.name})`,
4473
value: profileName,
4574
})),
4675
},

src/commands/list.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getAccounts } from '@/lib/config';
2-
import { log, logWarning, logInfo, sectionHeader, formatKeyValue } from '@/utils/cli';
2+
import { log, logWarning, logInfo, sectionHeader, formatKeyValue, maskEmail } from '@/utils/cli';
33
import chalk from 'chalk';
44

55
/**
@@ -21,7 +21,7 @@ export function listAccounts(): void {
2121
const account = accounts[profile];
2222
log(chalk.cyan(` ${profile}`));
2323
log(formatKeyValue('Name', account.name, 4));
24-
log(formatKeyValue('Email', account.email, 4));
24+
log(formatKeyValue('Email', maskEmail(account.email), 4));
2525
log(formatKeyValue('Provider', account.provider, 4));
2626
log(formatKeyValue('Username', account.username, 4));
2727
console.log();

0 commit comments

Comments
 (0)