@ghl-ai/aw 0.1.19 → 0.1.21

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.
Files changed (3) hide show
  1. package/commands/push.mjs +25 -10
  2. package/mcp.mjs +70 -116
  3. package/package.json +1 -1
package/commands/push.mjs CHANGED
@@ -113,7 +113,8 @@ export function pushCommand(args) {
113
113
  const tempDir = mkdtempSync(join(tmpdir(), 'aw-upload-'));
114
114
 
115
115
  try {
116
- execSync(`gh repo clone ${repo} ${tempDir} -- --filter=blob:none --no-checkout`, { stdio: 'pipe' });
116
+ const repoUrl = repo.startsWith('http') ? repo : `https://github.com/${repo}.git`;
117
+ execSync(`git clone --filter=blob:none --no-checkout "${repoUrl}" "${tempDir}"`, { stdio: 'pipe' });
117
118
  execSync(`git checkout ${REGISTRY_BASE_BRANCH}`, { cwd: tempDir, stdio: 'pipe' });
118
119
  s.stop('Repository cloned');
119
120
 
@@ -178,20 +179,29 @@ export function pushCommand(args) {
178
179
 
179
180
  const prTitle = `Add ${slug} (${parentDir}) to ${namespacePath}`;
180
181
  const prBody = bodyParts.join('\n');
181
- const prUrl = execFileSync('gh', [
182
- 'pr', 'create',
183
- '--base', REGISTRY_BASE_BRANCH,
184
- '--title', prTitle,
185
- '--body', prBody,
186
- ], { cwd: tempDir, encoding: 'utf8' }).trim();
187
182
 
188
- s3.stop('PR created');
183
+ // Try gh for PR creation, fall back to manual URL
184
+ let prUrl;
185
+ try {
186
+ prUrl = execFileSync('gh', [
187
+ 'pr', 'create',
188
+ '--base', REGISTRY_BASE_BRANCH,
189
+ '--title', prTitle,
190
+ '--body', prBody,
191
+ ], { cwd: tempDir, encoding: 'utf8' }).trim();
192
+ } catch {
193
+ // gh not available — construct PR URL manually
194
+ const repoBase = repo.replace(/\.git$/, '');
195
+ prUrl = `https://github.com/${repoBase}/compare/${REGISTRY_BASE_BRANCH}...${branch}?expand=1`;
196
+ }
197
+
198
+ s3.stop('Branch pushed');
189
199
 
190
200
  if (newNamespace) {
191
201
  fmt.logInfo(`New namespace ${chalk.cyan(topNamespace)} — CODEOWNERS entry added`);
192
202
  }
193
203
  fmt.logSuccess(`PR: ${chalk.cyan(prUrl)}`);
194
- fmt.outro('Upload complete');
204
+ fmt.outro('Upload complete — open the link above to create the PR');
195
205
  } catch (e) {
196
206
  fmt.cancel(`Upload failed: ${e.message}`);
197
207
  } finally {
@@ -200,10 +210,15 @@ export function pushCommand(args) {
200
210
  }
201
211
 
202
212
  function getGitHubUser() {
213
+ // Try gh first, fall back to git config
203
214
  try {
204
215
  return execSync('gh api user --jq .login', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
205
216
  } catch {
206
- return null;
217
+ try {
218
+ return execSync('git config user.name', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
219
+ } catch {
220
+ return null;
221
+ }
207
222
  }
208
223
  }
209
224
 
package/mcp.mjs CHANGED
@@ -1,4 +1,5 @@
1
- // mcp.mjs — MCP config generation for all 3 IDEs (init only, never overwrites)
1
+ // mcp.mjs — MCP config generation for Claude Code + Cursor (global ~/ configs)
2
+ // Uses native Streamable HTTP — no bridge process needed.
2
3
 
3
4
  import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'node:fs';
4
5
  import { join, resolve } from 'node:path';
@@ -6,149 +7,102 @@ import { homedir } from 'node:os';
6
7
  import * as fmt from './fmt.mjs';
7
8
 
8
9
  const HOME = homedir();
10
+ const DEFAULT_MCP_URL = 'https://staging.services.leadconnectorhq.com/agentic-workspace/mcp';
9
11
 
10
12
  /**
11
13
  * Auto-detect MCP server paths.
12
14
  */
13
15
  function detectPaths() {
14
- const ghlAiBridge = join(HOME, '.claude', 'ghl-ai-bridge.mjs');
15
16
  const gitJenkinsCandidates = [
16
17
  join(HOME, 'Documents', 'GitHub', 'git-jenkins-mcp', 'dist', 'index.js'),
17
18
  resolve('..', 'git-jenkins-mcp', 'dist', 'index.js'),
18
19
  ];
19
20
 
20
21
  const gitJenkinsPath = gitJenkinsCandidates.find(p => existsSync(p)) || null;
21
- const ghlMcpUrl = process.env.GHL_MCP_URL || 'https://ghl-ai-staging.highlevel-staging.com';
22
+ const ghlMcpUrl = process.env.GHL_MCP_URL || DEFAULT_MCP_URL;
22
23
 
23
- return {
24
- ghlAiBridge: existsSync(ghlAiBridge) ? ghlAiBridge : null,
25
- gitJenkinsPath,
26
- ghlMcpUrl,
27
- };
24
+ return { gitJenkinsPath, ghlMcpUrl };
28
25
  }
29
26
 
30
27
  /**
31
- * Setup MCP configs for all IDEs. Only writes if file doesn't exist.
32
- * Returns list of file paths that were actually created.
28
+ * Setup MCP configs globally for Claude Code and Cursor.
29
+ * Merges ghl-ai server into existing configs without overwriting other servers.
30
+ * Returns list of file paths that were created or updated.
33
31
  */
34
32
  export function setupMcp(cwd, namespace) {
35
33
  const paths = detectPaths();
36
- const createdFiles = [];
34
+ const updatedFiles = [];
37
35
 
38
- const attempt = (filePath, contentFn, append = false) => {
39
- if (writeIfMissing(filePath, contentFn, append)) {
40
- createdFiles.push(filePath);
41
- }
42
- };
43
-
44
- // .claude/settings.local.json
45
- attempt(join(cwd, '.claude', 'settings.local.json'), () => {
46
- const servers = {};
47
- if (paths.ghlAiBridge) {
48
- servers['ghl-ai'] = {
49
- command: 'node',
50
- args: [paths.ghlAiBridge],
51
- env: { GHL_MCP_URL: paths.ghlMcpUrl, TEAM_NAME: namespace || '' },
52
- };
53
- }
54
- if (paths.gitJenkinsPath) {
55
- servers['git-jenkins'] = { command: 'node', args: [paths.gitJenkinsPath] };
56
- }
57
- if (Object.keys(servers).length === 0) return null;
58
- return JSON.stringify({ mcpServers: servers }, null, 2) + '\n';
59
- });
60
-
61
- // .cursor/mcp.json
62
- attempt(join(cwd, '.cursor', 'mcp.json'), () => {
63
- const servers = {};
64
- if (paths.ghlAiBridge) {
65
- servers['ghl-ai'] = {
66
- command: 'node',
67
- args: [paths.ghlAiBridge],
68
- env: { GHL_MCP_URL: paths.ghlMcpUrl, TEAM_NAME: namespace || '' },
69
- };
70
- }
71
- if (paths.gitJenkinsPath) {
72
- servers['git-jenkins'] = { command: 'node', args: [paths.gitJenkinsPath] };
73
- }
74
- if (Object.keys(servers).length === 0) return null;
75
- return JSON.stringify({ mcpServers: servers }, null, 2) + '\n';
76
- });
77
-
78
- // .codex/config.toml — append MCP servers
79
- const codexConfig = join(cwd, '.codex', 'config.toml');
80
- if (!hasMcpSection(codexConfig)) {
81
- attempt(codexConfig, () => {
82
- const lines = [];
83
- if (paths.ghlAiBridge) {
84
- lines.push(
85
- '[mcp_servers.ghl-ai]',
86
- 'command = "node"',
87
- `args = ["${paths.ghlAiBridge}"]`,
88
- '',
89
- '[mcp_servers.ghl-ai.env]',
90
- `GHL_MCP_URL = "${paths.ghlMcpUrl}"`,
91
- `TEAM_NAME = "${namespace || ''}"`,
92
- '',
93
- );
94
- }
95
- if (paths.gitJenkinsPath) {
96
- lines.push(
97
- '[mcp_servers.git-jenkins]',
98
- 'command = "node"',
99
- `args = ["${paths.gitJenkinsPath}"]`,
100
- '',
101
- );
102
- }
103
- if (lines.length === 0) return null;
104
- return lines.join('\n');
105
- }, true /* append mode */);
106
- }
36
+ const mcpUrl = paths.ghlMcpUrl;
107
37
 
108
- // mcp.json at project root (Claude Code auto-discovery)
109
- attempt(join(cwd, 'mcp.json'), () => {
110
- const servers = {};
111
- if (paths.ghlAiBridge) {
112
- servers['ghl-ai'] = {
113
- command: 'node',
114
- args: [paths.ghlAiBridge],
115
- env: { GHL_MCP_URL: paths.ghlMcpUrl, TEAM_NAME: namespace || '' },
116
- };
117
- }
118
- if (Object.keys(servers).length === 0) return null;
119
- return JSON.stringify({ mcpServers: servers }, null, 2) + '\n';
120
- });
38
+ const ghlAiServer = { type: 'url', url: mcpUrl };
39
+ const gitJenkinsServer = paths.gitJenkinsPath
40
+ ? { command: 'node', args: [paths.gitJenkinsPath] }
41
+ : null;
121
42
 
122
- if (createdFiles.length > 0) {
123
- fmt.logSuccess(`Created ${createdFiles.length} MCP config${createdFiles.length > 1 ? 's' : ''}`);
43
+ // ── Claude Code: ~/.claude/settings.json ──
44
+ const claudeSettingsPath = join(HOME, '.claude', 'settings.json');
45
+ if (mergeJsonMcpServer(claudeSettingsPath, 'ghl-ai', ghlAiServer)) {
46
+ updatedFiles.push(claudeSettingsPath);
47
+ }
48
+ if (gitJenkinsServer && mergeJsonMcpServer(claudeSettingsPath, 'git-jenkins', gitJenkinsServer)) {
49
+ updatedFiles.push(claudeSettingsPath);
124
50
  }
125
51
 
126
- const warnings = [];
127
- if (!paths.ghlAiBridge) warnings.push('ghl-ai bridge not found at ~/.claude/ghl-ai-bridge.mjs');
128
- if (!paths.gitJenkinsPath) warnings.push('git-jenkins MCP not found');
129
- for (const w of warnings) {
130
- fmt.logWarn(w);
52
+ // ── Cursor: ~/.cursor/mcp.json ──
53
+ const cursorMcpPath = join(HOME, '.cursor', 'mcp.json');
54
+ if (mergeJsonMcpServer(cursorMcpPath, 'ghl-ai', ghlAiServer)) {
55
+ updatedFiles.push(cursorMcpPath);
56
+ }
57
+ if (gitJenkinsServer && mergeJsonMcpServer(cursorMcpPath, 'git-jenkins', gitJenkinsServer)) {
58
+ updatedFiles.push(cursorMcpPath);
131
59
  }
132
60
 
133
- return createdFiles;
134
- }
61
+ // Deduplicate (claude settings may be added twice if both servers written)
62
+ const unique = [...new Set(updatedFiles)];
135
63
 
136
- function writeIfMissing(filePath, contentFn, append = false) {
137
- if (!append && existsSync(filePath)) return false;
138
- const content = contentFn();
139
- if (!content) return false;
140
- mkdirSync(join(filePath, '..'), { recursive: true });
141
- if (append && existsSync(filePath)) {
142
- const existing = readFileSync(filePath, 'utf8');
143
- writeFileSync(filePath, existing + '\n' + content);
64
+ if (unique.length > 0) {
65
+ fmt.logSuccess(`MCP configured in ${unique.map(f => fmt.chalk.cyan(f)).join(', ')}`);
144
66
  } else {
145
- writeFileSync(filePath, content);
67
+ fmt.logInfo('MCP servers already configured — no changes needed');
146
68
  }
147
- return true;
69
+
70
+ if (!paths.gitJenkinsPath) {
71
+ fmt.logWarn('git-jenkins MCP not found — skipped');
72
+ }
73
+
74
+ return unique;
148
75
  }
149
76
 
150
- function hasMcpSection(filePath) {
151
- if (!existsSync(filePath)) return false;
152
- const content = readFileSync(filePath, 'utf8');
153
- return content.includes('[mcp_servers');
77
+ /**
78
+ * Merge an MCP server entry into a JSON config file.
79
+ * Creates the file if it doesn't exist.
80
+ * Returns true if the file was modified.
81
+ */
82
+ function mergeJsonMcpServer(filePath, serverName, serverConfig) {
83
+ let config = {};
84
+
85
+ if (existsSync(filePath)) {
86
+ try {
87
+ config = JSON.parse(readFileSync(filePath, 'utf8'));
88
+ } catch {
89
+ config = {};
90
+ }
91
+ }
92
+
93
+ if (!config.mcpServers) {
94
+ config.mcpServers = {};
95
+ }
96
+
97
+ // Check if already configured with same URL
98
+ const existing = config.mcpServers[serverName];
99
+ if (existing && JSON.stringify(existing) === JSON.stringify(serverConfig)) {
100
+ return false;
101
+ }
102
+
103
+ config.mcpServers[serverName] = serverConfig;
104
+
105
+ mkdirSync(join(filePath, '..'), { recursive: true });
106
+ writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n');
107
+ return true;
154
108
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {