hsh19900502 1.0.21 → 1.0.22

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/CLAUDE.md CHANGED
@@ -47,7 +47,8 @@ src/
47
47
  ├── commands/
48
48
  │ ├── git.ts # Git workflow commands (gcm, push, merge, mr, branchout)
49
49
  │ ├── mono.ts # Monorepo management (init, cd)
50
- └── ide.ts # IDE integration (cursor, surf)
50
+ ├── ide.ts # IDE integration (cursor, surf)
51
+ │ └── mcp.ts # MCP server synchronization
51
52
  ├── types/
52
53
  │ └── index.ts # TypeScript type definitions
53
54
  └── util.ts # Utility functions (package.json reading)
@@ -91,6 +92,14 @@ The CLI follows a modular command structure using Commander.js:
91
92
  - `cursor`: Open project in Cursor editor with config-based project selection
92
93
  - `surf`: Open project in Windsurf editor with config-based project selection
93
94
 
95
+ ### MCP Server Management
96
+
97
+ - `mcp sync`: Synchronize MCP server configurations from `~/.mcp/servers.json` to all projects in `~/.claude.json`
98
+ - This addresses the limitation that Claude Code cannot install MCP servers globally
99
+ - Automatically updates all project configurations with centralized MCP server definitions
100
+ - Idempotent operation - safe to run multiple times
101
+ - Useful for maintaining consistent MCP configurations across multiple projects
102
+
94
103
  ## Configuration Requirements
95
104
 
96
105
  ### IDE Configuration
@@ -116,6 +125,34 @@ For monorepo commands, projects should have:
116
125
  - `.hsh` marker file in the root (created by `mono init`)
117
126
  - Directory structure: `<repo-name>/client/` and `<repo-name>/server/`
118
127
 
128
+ ### MCP Server Configuration
129
+
130
+ MCP (Model Context Protocol) servers can be centrally managed:
131
+
132
+ - Create `~/.mcp/servers.json` with your global MCP server configurations
133
+ - Run `hsh mcp sync` to propagate configurations to all projects in `~/.claude.json`
134
+ - Each project's `mcpServers` field will be updated with the latest configuration
135
+
136
+ Example `~/.mcp/servers.json`:
137
+ ```json
138
+ {
139
+ "chrome-devtools": {
140
+ "type": "stdio",
141
+ "command": "npx",
142
+ "args": ["chrome-devtools-mcp@latest"],
143
+ "env": {}
144
+ },
145
+ "GitLab communication server": {
146
+ "command": "npx",
147
+ "args": ["-y", "@zereight/mcp-gitlab"],
148
+ "env": {
149
+ "GITLAB_PERSONAL_ACCESS_TOKEN": "your-token",
150
+ "GITLAB_API_URL": "https://gitlab.example.com/api/v4"
151
+ }
152
+ }
153
+ }
154
+ ```
155
+
119
156
  ## External Dependencies
120
157
 
121
158
  ### Required CLI Tools
package/README.md CHANGED
@@ -0,0 +1,158 @@
1
+ # hsh - CLI Workflow Automation Tool
2
+
3
+ A TypeScript-based CLI tool that provides Git workflow automation, IDE project management, and MCP server configuration synchronization.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ yarn install
9
+ yarn build:install
10
+ ```
11
+
12
+ ## Features
13
+
14
+ ### Git Workflow Automation
15
+ - `hsh gcm <message> [--push]` - Add all, commit with message, optionally push
16
+ - `hsh push` - Interactive branch selection for pushing to remote
17
+ - `hsh merge <branch>` - Safe merge with branch switching and pulling
18
+ - `hsh mr create` - Create merge requests with JIRA integration
19
+ - `hsh branchout <branch>` - Create new branch from master
20
+
21
+ ### Monorepo Management
22
+ - `hsh mono init` - Initialize workspace with `.hsh` marker file
23
+ - `hsh mono cd <level> [--repo <name>]` - Navigate to repo directories (root/client/server)
24
+
25
+ ### IDE Integration
26
+ - `hsh cursor` - Open project in Cursor editor with config-based project selection
27
+ - `hsh claude` - Open project in Claude Code editor with config-based project selection
28
+
29
+ ### MCP Server Management
30
+
31
+ **NEW!** Solve the problem of having to configure MCP servers individually for each project.
32
+
33
+ #### The Problem
34
+ Claude Code doesn't support global MCP server installation. You have to configure MCP servers in each project's `.claude.json` file individually, which is tedious and error-prone.
35
+
36
+ #### The Solution
37
+ `hsh mcp sync` - Synchronize MCP server configurations from a central location to all your projects
38
+
39
+ #### How It Works
40
+
41
+ 1. Create a central MCP configuration file at `~/.mcp/servers.json`:
42
+
43
+ ```json
44
+ {
45
+ "chrome-devtools": {
46
+ "type": "stdio",
47
+ "command": "npx",
48
+ "args": ["chrome-devtools-mcp@latest"],
49
+ "env": {}
50
+ },
51
+ "GitLab communication server": {
52
+ "command": "npx",
53
+ "args": ["-y", "@zereight/mcp-gitlab"],
54
+ "env": {
55
+ "GITLAB_PERSONAL_ACCESS_TOKEN": "your-token",
56
+ "GITLAB_API_URL": "https://gitlab.example.com/api/v4"
57
+ }
58
+ },
59
+ "jira": {
60
+ "command": "npx",
61
+ "args": ["-y", "@aashari/mcp-server-atlassian-jira"],
62
+ "env": {
63
+ "ATLASSIAN_SITE_NAME": "your-site",
64
+ "ATLASSIAN_USER_EMAIL": "your-email",
65
+ "ATLASSIAN_API_TOKEN": "your-token"
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ 2. Run the sync command:
72
+
73
+ ```bash
74
+ hsh mcp sync
75
+ ```
76
+
77
+ 3. The command will:
78
+ - Read all MCP server configurations from `~/.mcp/servers.json`
79
+ - Update the `mcpServers` field for all projects in `~/.claude.json`
80
+ - Show you which servers were synced and how many projects were updated
81
+
82
+ #### Features
83
+ - ✅ Idempotent - Safe to run multiple times
84
+ - ✅ Atomic updates - Updates all projects at once
85
+ - ✅ Clear feedback - Shows which servers and projects were updated
86
+ - ✅ Validation - Checks for file existence and valid JSON
87
+
88
+ #### Example Output
89
+
90
+ ```bash
91
+ $ hsh mcp sync
92
+ ✔ Successfully synced MCP servers to 21 projects
93
+
94
+ MCP Servers synced:
95
+ • chrome-devtools
96
+ • Playwright
97
+ • figma-mcp
98
+ • GitLab communication server
99
+ • jira
100
+ • confluence
101
+
102
+ Projects updated:
103
+ • /Users/you/project1
104
+ • /Users/you/project2
105
+ • /Users/you/project3
106
+ ... and 18 more
107
+ ```
108
+
109
+ ## Configuration
110
+
111
+ ### IDE Configuration
112
+ Create `~/hsh.config.json` for IDE project management:
113
+
114
+ ```json
115
+ {
116
+ "work": {
117
+ "project1": "/path/to/work/project1",
118
+ "project2": "/path/to/work/project2"
119
+ },
120
+ "personal": {
121
+ "project3": "/path/to/personal/project3"
122
+ }
123
+ }
124
+ ```
125
+
126
+ ### Monorepo Setup
127
+ For monorepo commands, projects should have:
128
+ - `.hsh` marker file in the root (created by `hsh mono init`)
129
+ - Directory structure: `<repo-name>/client/` and `<repo-name>/server/`
130
+
131
+ ## Development
132
+
133
+ ```bash
134
+ # Install dependencies
135
+ yarn install
136
+
137
+ # Build the project
138
+ yarn build
139
+
140
+ # Build and install globally
141
+ yarn build:install
142
+
143
+ # Development mode
144
+ yarn dev
145
+ ```
146
+
147
+ ## Requirements
148
+
149
+ - Node.js with ES module support
150
+ - Git
151
+ - GitLab CLI (`glab`) for merge request features
152
+ - Cursor editor (for `cursor` command)
153
+ - Claude Code editor (for `claude` command)
154
+
155
+ ## License
156
+
157
+ MIT
158
+
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Sync MCP servers from ~/.mcp/servers.json to all projects in ~/.claude.json
3
+ */
4
+ export declare function syncMcpServers(): Promise<void>;
@@ -0,0 +1,79 @@
1
+ import { readFileSync, writeFileSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+ /**
7
+ * Sync MCP servers from ~/.mcp/servers.json to all projects in ~/.claude.json
8
+ */
9
+ export async function syncMcpServers() {
10
+ const spinner = ora('Syncing MCP servers...').start();
11
+ try {
12
+ // Read MCP servers configuration
13
+ const mcpConfigPath = join(homedir(), '.mcp', 'servers.json');
14
+ const claudeConfigPath = join(homedir(), '.claude.json');
15
+ let mcpServers;
16
+ try {
17
+ const mcpConfigContent = readFileSync(mcpConfigPath, 'utf-8');
18
+ mcpServers = JSON.parse(mcpConfigContent);
19
+ spinner.text = `Found ${Object.keys(mcpServers).length} MCP servers`;
20
+ }
21
+ catch (error) {
22
+ spinner.fail(chalk.red(`Failed to read MCP servers from ${mcpConfigPath}`));
23
+ console.error(chalk.yellow(`Please ensure ${mcpConfigPath} exists and is valid JSON`));
24
+ return;
25
+ }
26
+ // Read Claude configuration
27
+ let claudeConfig;
28
+ try {
29
+ const claudeConfigContent = readFileSync(claudeConfigPath, 'utf-8');
30
+ claudeConfig = JSON.parse(claudeConfigContent);
31
+ }
32
+ catch (error) {
33
+ spinner.fail(chalk.red(`Failed to read Claude config from ${claudeConfigPath}`));
34
+ return;
35
+ }
36
+ // Check if there are any projects
37
+ if (!claudeConfig.projects || Object.keys(claudeConfig.projects).length === 0) {
38
+ spinner.warn(chalk.yellow('No projects found in ~/.claude.json'));
39
+ return;
40
+ }
41
+ // Update each project with MCP servers
42
+ const projectPaths = Object.keys(claudeConfig.projects);
43
+ spinner.text = `Updating ${projectPaths.length} projects...`;
44
+ let updatedCount = 0;
45
+ for (const projectPath of projectPaths) {
46
+ const project = claudeConfig.projects[projectPath];
47
+ // Check if mcpServers need to be updated
48
+ const currentServers = JSON.stringify(project.mcpServers || {});
49
+ const newServers = JSON.stringify(mcpServers);
50
+ if (currentServers !== newServers) {
51
+ project.mcpServers = mcpServers;
52
+ updatedCount++;
53
+ }
54
+ }
55
+ // Write back to ~/.claude.json
56
+ if (updatedCount > 0) {
57
+ writeFileSync(claudeConfigPath, JSON.stringify(claudeConfig, null, 2), 'utf-8');
58
+ spinner.succeed(chalk.green(`✓ Successfully synced MCP servers to ${updatedCount} project${updatedCount > 1 ? 's' : ''}`));
59
+ console.log(chalk.cyan('\nMCP Servers synced:'));
60
+ Object.keys(mcpServers).forEach(serverName => {
61
+ console.log(chalk.gray(` • ${serverName}`));
62
+ });
63
+ console.log(chalk.cyan('\nProjects updated:'));
64
+ projectPaths.slice(0, 5).forEach(path => {
65
+ console.log(chalk.gray(` • ${path}`));
66
+ });
67
+ if (projectPaths.length > 5) {
68
+ console.log(chalk.gray(` ... and ${projectPaths.length - 5} more`));
69
+ }
70
+ }
71
+ else {
72
+ spinner.info(chalk.blue('All projects already have the latest MCP servers configuration'));
73
+ }
74
+ }
75
+ catch (error) {
76
+ spinner.fail(chalk.red('Failed to sync MCP servers'));
77
+ console.error(chalk.red(error.message));
78
+ }
79
+ }
package/dist/hsh.js CHANGED
@@ -5,6 +5,7 @@ import { initMonoRepo, monoCd } from './commands/mono.js';
5
5
  import { getPackageJson } from './util.js';
6
6
  import { openIDE } from './commands/ide.js';
7
7
  import { cloudLogin, cloudScp } from './commands/cloud.js';
8
+ import { syncMcpServers } from './commands/mcp.js';
8
9
  const packageJson = getPackageJson();
9
10
  const program = new Command();
10
11
  program.usage('<command> [options]');
@@ -90,4 +91,14 @@ cloudCommand
90
91
  .action(async (localPath, remotePath, options) => {
91
92
  await cloudScp(localPath, remotePath, options);
92
93
  });
94
+ // MCP management commands
95
+ const mcpCommand = program
96
+ .command('mcp')
97
+ .description('MCP (Model Context Protocol) server management');
98
+ mcpCommand
99
+ .command('sync')
100
+ .description('Sync MCP servers from ~/.mcp/servers.json to all projects in ~/.claude.json')
101
+ .action(async () => {
102
+ await syncMcpServers();
103
+ });
93
104
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hsh19900502",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "main": "index.js",
@@ -0,0 +1,118 @@
1
+ import { $ } from 'zx';
2
+ import { readFileSync, writeFileSync } from 'fs';
3
+ import { homedir } from 'os';
4
+ import { join } from 'path';
5
+ import chalk from 'chalk';
6
+ import ora from 'ora';
7
+
8
+ interface McpServerConfig {
9
+ [serverName: string]: {
10
+ type?: string;
11
+ command: string;
12
+ args: string[];
13
+ env?: Record<string, string>;
14
+ };
15
+ }
16
+
17
+ interface ClaudeConfig {
18
+ projects: {
19
+ [projectPath: string]: {
20
+ mcpServers?: McpServerConfig;
21
+ [key: string]: any;
22
+ };
23
+ };
24
+ [key: string]: any;
25
+ }
26
+
27
+ /**
28
+ * Sync MCP servers from ~/.mcp/servers.json to all projects in ~/.claude.json
29
+ */
30
+ export async function syncMcpServers() {
31
+ const spinner = ora('Syncing MCP servers...').start();
32
+
33
+ try {
34
+ // Read MCP servers configuration
35
+ const mcpConfigPath = join(homedir(), '.mcp', 'servers.json');
36
+ const claudeConfigPath = join(homedir(), '.claude.json');
37
+
38
+ let mcpServers: McpServerConfig;
39
+ try {
40
+ const mcpConfigContent = readFileSync(mcpConfigPath, 'utf-8');
41
+ mcpServers = JSON.parse(mcpConfigContent);
42
+ spinner.text = `Found ${Object.keys(mcpServers).length} MCP servers`;
43
+ } catch (error) {
44
+ spinner.fail(chalk.red(`Failed to read MCP servers from ${mcpConfigPath}`));
45
+ console.error(chalk.yellow(`Please ensure ${mcpConfigPath} exists and is valid JSON`));
46
+ return;
47
+ }
48
+
49
+ // Read Claude configuration
50
+ let claudeConfig: ClaudeConfig;
51
+ try {
52
+ const claudeConfigContent = readFileSync(claudeConfigPath, 'utf-8');
53
+ claudeConfig = JSON.parse(claudeConfigContent);
54
+ } catch (error) {
55
+ spinner.fail(chalk.red(`Failed to read Claude config from ${claudeConfigPath}`));
56
+ return;
57
+ }
58
+
59
+ // Check if there are any projects
60
+ if (!claudeConfig.projects || Object.keys(claudeConfig.projects).length === 0) {
61
+ spinner.warn(chalk.yellow('No projects found in ~/.claude.json'));
62
+ return;
63
+ }
64
+
65
+ // Update each project with MCP servers
66
+ const projectPaths = Object.keys(claudeConfig.projects);
67
+ spinner.text = `Updating ${projectPaths.length} projects...`;
68
+
69
+ let updatedCount = 0;
70
+ for (const projectPath of projectPaths) {
71
+ const project = claudeConfig.projects[projectPath];
72
+
73
+ // Check if mcpServers need to be updated
74
+ const currentServers = JSON.stringify(project.mcpServers || {});
75
+ const newServers = JSON.stringify(mcpServers);
76
+
77
+ if (currentServers !== newServers) {
78
+ project.mcpServers = mcpServers;
79
+ updatedCount++;
80
+ }
81
+ }
82
+
83
+ // Write back to ~/.claude.json
84
+ if (updatedCount > 0) {
85
+ writeFileSync(
86
+ claudeConfigPath,
87
+ JSON.stringify(claudeConfig, null, 2),
88
+ 'utf-8'
89
+ );
90
+
91
+ spinner.succeed(
92
+ chalk.green(
93
+ `✓ Successfully synced MCP servers to ${updatedCount} project${updatedCount > 1 ? 's' : ''}`
94
+ )
95
+ );
96
+
97
+ console.log(chalk.cyan('\nMCP Servers synced:'));
98
+ Object.keys(mcpServers).forEach(serverName => {
99
+ console.log(chalk.gray(` • ${serverName}`));
100
+ });
101
+
102
+ console.log(chalk.cyan('\nProjects updated:'));
103
+ projectPaths.slice(0, 5).forEach(path => {
104
+ console.log(chalk.gray(` • ${path}`));
105
+ });
106
+ if (projectPaths.length > 5) {
107
+ console.log(chalk.gray(` ... and ${projectPaths.length - 5} more`));
108
+ }
109
+ } else {
110
+ spinner.info(chalk.blue('All projects already have the latest MCP servers configuration'));
111
+ }
112
+
113
+ } catch (error: any) {
114
+ spinner.fail(chalk.red('Failed to sync MCP servers'));
115
+ console.error(chalk.red(error.message));
116
+ }
117
+ }
118
+
package/src/hsh.ts CHANGED
@@ -6,6 +6,7 @@ import { RepoDirLevel } from './types/index.js';
6
6
  import { getPackageJson } from './util.js';
7
7
  import { openIDE } from './commands/ide.js';
8
8
  import { cloudLogin, cloudScp } from './commands/cloud.js';
9
+ import { syncMcpServers } from './commands/mcp.js';
9
10
 
10
11
  const packageJson = getPackageJson();
11
12
 
@@ -108,4 +109,16 @@ cloudCommand
108
109
  await cloudScp(localPath, remotePath, options);
109
110
  });
110
111
 
112
+ // MCP management commands
113
+ const mcpCommand = program
114
+ .command('mcp')
115
+ .description('MCP (Model Context Protocol) server management');
116
+
117
+ mcpCommand
118
+ .command('sync')
119
+ .description('Sync MCP servers from ~/.mcp/servers.json to all projects in ~/.claude.json')
120
+ .action(async () => {
121
+ await syncMcpServers();
122
+ });
123
+
111
124
  program.parse();