@ghl-ai/aw 0.1.29 → 0.1.30-beta.1
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/commands/init.mjs +1 -1
- package/mcp.mjs +117 -17
- package/package.json +1 -1
package/commands/init.mjs
CHANGED
|
@@ -131,7 +131,7 @@ function printPullSummary(pattern, actions) {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
const ALLOWED_NAMESPACES = ['revex', 'mobile', 'commerce', 'leadgen', 'crm', 'marketplace', 'ai'
|
|
134
|
+
const ALLOWED_NAMESPACES = ['revex', 'mobile', 'commerce', 'leadgen', 'crm', 'marketplace', 'ai'];
|
|
135
135
|
|
|
136
136
|
export async function initCommand(args) {
|
|
137
137
|
const namespace = args['--namespace'] || null;
|
package/mcp.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Uses native Streamable HTTP — no bridge process needed.
|
|
3
3
|
|
|
4
4
|
import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'node:fs';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
5
6
|
import { join, resolve } from 'node:path';
|
|
6
7
|
import { homedir } from 'node:os';
|
|
7
8
|
import * as fmt from './fmt.mjs';
|
|
@@ -24,6 +25,89 @@ function detectPaths() {
|
|
|
24
25
|
return { gitJenkinsPath, ghlMcpUrl };
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Resolve GitHub token for MCP auth. Zero manual steps.
|
|
30
|
+
*
|
|
31
|
+
* Resolution chain:
|
|
32
|
+
* 1. $GITHUB_TOKEN env var (already set)
|
|
33
|
+
* 2. `gh auth token` (GitHub CLI — most devs have this)
|
|
34
|
+
* 3. null (fall back to ${GITHUB_TOKEN} interpolation in config)
|
|
35
|
+
*/
|
|
36
|
+
function resolveGitHubToken() {
|
|
37
|
+
// 1. Environment variable
|
|
38
|
+
if (process.env.GITHUB_TOKEN) {
|
|
39
|
+
fmt.logStep('Using GITHUB_TOKEN from environment');
|
|
40
|
+
return process.env.GITHUB_TOKEN;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 2. GitHub CLI — check if installed
|
|
44
|
+
let ghInstalled = false;
|
|
45
|
+
try {
|
|
46
|
+
execSync('which gh', { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
47
|
+
ghInstalled = true;
|
|
48
|
+
} catch { /* gh not installed */ }
|
|
49
|
+
|
|
50
|
+
if (ghInstalled) {
|
|
51
|
+
// 2a. Try existing token
|
|
52
|
+
try {
|
|
53
|
+
const token = execSync('gh auth token', {
|
|
54
|
+
encoding: 'utf8',
|
|
55
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
56
|
+
timeout: 5000,
|
|
57
|
+
}).trim();
|
|
58
|
+
if (token && (token.startsWith('ghp_') || token.startsWith('gho_') || token.startsWith('github_pat_'))) {
|
|
59
|
+
fmt.logStep('Using GitHub token from gh CLI');
|
|
60
|
+
return token;
|
|
61
|
+
}
|
|
62
|
+
} catch { /* not authenticated yet */ }
|
|
63
|
+
|
|
64
|
+
// 2b. Not authenticated — run gh auth login (opens browser)
|
|
65
|
+
fmt.logStep('GitHub CLI found but not authenticated — launching login...');
|
|
66
|
+
try {
|
|
67
|
+
execSync('gh auth login --web --git-protocol https', {
|
|
68
|
+
stdio: 'inherit', // show browser prompt to user
|
|
69
|
+
timeout: 120000, // 2 min for user to complete browser auth
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Now grab the token
|
|
73
|
+
const token = execSync('gh auth token', {
|
|
74
|
+
encoding: 'utf8',
|
|
75
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
76
|
+
timeout: 5000,
|
|
77
|
+
}).trim();
|
|
78
|
+
if (token) {
|
|
79
|
+
fmt.logSuccess('GitHub login successful');
|
|
80
|
+
return token;
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
fmt.logWarn('GitHub login failed or timed out');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 3. No gh CLI — prompt manual setup
|
|
88
|
+
fmt.logWarn('No GitHub token found — MCP auth will use ${GITHUB_TOKEN} env var interpolation');
|
|
89
|
+
fmt.logWarn(' Install gh CLI: brew install gh && gh auth login');
|
|
90
|
+
fmt.logWarn(' Or export: export GITHUB_TOKEN=ghp_xxx');
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Resolve GitHub username from a PAT. Used for tracking who initialized MCP.
|
|
96
|
+
*/
|
|
97
|
+
function resolveGitHubUser(token) {
|
|
98
|
+
try {
|
|
99
|
+
const res = execSync(`curl -sf -H "Authorization: Bearer ${token}" -H "User-Agent: ghl-ai-aw" https://api.github.com/user`, {
|
|
100
|
+
encoding: 'utf8',
|
|
101
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
102
|
+
timeout: 5000,
|
|
103
|
+
});
|
|
104
|
+
const data = JSON.parse(res);
|
|
105
|
+
return data.login || null;
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
27
111
|
/**
|
|
28
112
|
* Setup MCP configs globally for Claude Code and Cursor.
|
|
29
113
|
* Merges ghl-ai server into existing configs without overwriting other servers.
|
|
@@ -34,54 +118,70 @@ export function setupMcp(cwd, namespace) {
|
|
|
34
118
|
const updatedFiles = [];
|
|
35
119
|
|
|
36
120
|
const mcpUrl = paths.ghlMcpUrl;
|
|
121
|
+
const ghToken = resolveGitHubToken();
|
|
122
|
+
|
|
123
|
+
// Track who initialized MCP
|
|
124
|
+
if (ghToken) {
|
|
125
|
+
const ghUser = resolveGitHubUser(ghToken);
|
|
126
|
+
if (ghUser) {
|
|
127
|
+
fmt.logSuccess(`Authenticated as GitHub user: ${fmt.chalk.cyan(ghUser)}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Server config with resolved token for local IDE configs (not committed, safe)
|
|
132
|
+
const ghlAiServerLocal = {
|
|
133
|
+
type: 'http',
|
|
134
|
+
url: mcpUrl,
|
|
135
|
+
headers: { Authorization: `Bearer ${ghToken || '${GITHUB_TOKEN}'}` },
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Server config with env var interpolation for committed project files
|
|
139
|
+
const ghlAiServerProject = {
|
|
140
|
+
type: 'http',
|
|
141
|
+
url: mcpUrl,
|
|
142
|
+
headers: { Authorization: 'Bearer ${GITHUB_TOKEN}' },
|
|
143
|
+
};
|
|
37
144
|
|
|
38
|
-
const ghlAiServer = { type: 'http', url: mcpUrl };
|
|
39
145
|
const gitJenkinsServer = paths.gitJenkinsPath
|
|
40
146
|
? { command: 'node', args: [paths.gitJenkinsPath] }
|
|
41
147
|
: null;
|
|
42
148
|
|
|
43
|
-
// ── Claude Code: ~/.claude/settings.json (global) ──
|
|
149
|
+
// ── Claude Code: ~/.claude/settings.json (global, local) ──
|
|
44
150
|
const claudeSettingsPath = join(HOME, '.claude', 'settings.json');
|
|
45
|
-
if (mergeJsonMcpServer(claudeSettingsPath, 'ghl-ai',
|
|
151
|
+
if (mergeJsonMcpServer(claudeSettingsPath, 'ghl-ai', ghlAiServerLocal)) {
|
|
46
152
|
updatedFiles.push(claudeSettingsPath);
|
|
47
153
|
}
|
|
48
154
|
if (gitJenkinsServer && mergeJsonMcpServer(claudeSettingsPath, 'git-jenkins', gitJenkinsServer)) {
|
|
49
155
|
updatedFiles.push(claudeSettingsPath);
|
|
50
156
|
}
|
|
51
157
|
|
|
52
|
-
// ── Claude Code: .mcp.json (project root —
|
|
53
|
-
// Claude Code resolves project .mcp.json before global settings.json,
|
|
54
|
-
// so we must write here to ensure the HTTP URL takes precedence over
|
|
55
|
-
// any stale command-based configs in parent directories.
|
|
158
|
+
// ── Claude Code: .mcp.json (project root — committed, uses env var) ──
|
|
56
159
|
const projectMcpPath = join(cwd, '.mcp.json');
|
|
57
|
-
if (mergeJsonMcpServer(projectMcpPath, 'ghl-ai',
|
|
160
|
+
if (mergeJsonMcpServer(projectMcpPath, 'ghl-ai', ghlAiServerProject)) {
|
|
58
161
|
updatedFiles.push(projectMcpPath);
|
|
59
162
|
}
|
|
60
163
|
|
|
61
|
-
// ── Claude Code: .claude/mcp.json (workspace-level) ──
|
|
62
|
-
// Claude Code also reads .claude/mcp.json for workspace-scoped MCP config.
|
|
63
|
-
// Write here so the MCP server is available regardless of which resolution
|
|
64
|
-
// path Claude Code uses (root .mcp.json or .claude/mcp.json).
|
|
164
|
+
// ── Claude Code: .claude/mcp.json (workspace-level, local) ──
|
|
65
165
|
const claudeWorkspaceMcpPath = join(cwd, '.claude', 'mcp.json');
|
|
66
|
-
if (mergeJsonMcpServer(claudeWorkspaceMcpPath, 'ghl-ai',
|
|
166
|
+
if (mergeJsonMcpServer(claudeWorkspaceMcpPath, 'ghl-ai', ghlAiServerLocal)) {
|
|
67
167
|
updatedFiles.push(claudeWorkspaceMcpPath);
|
|
68
168
|
}
|
|
69
169
|
if (gitJenkinsServer && mergeJsonMcpServer(claudeWorkspaceMcpPath, 'git-jenkins', gitJenkinsServer)) {
|
|
70
170
|
updatedFiles.push(claudeWorkspaceMcpPath);
|
|
71
171
|
}
|
|
72
172
|
|
|
73
|
-
// ── Cursor: project .cursor/mcp.json (workspace-level) ──
|
|
173
|
+
// ── Cursor: project .cursor/mcp.json (workspace-level, local) ──
|
|
74
174
|
const cursorProjectMcpPath = join(cwd, '.cursor', 'mcp.json');
|
|
75
|
-
if (mergeJsonMcpServer(cursorProjectMcpPath, 'ghl-ai',
|
|
175
|
+
if (mergeJsonMcpServer(cursorProjectMcpPath, 'ghl-ai', ghlAiServerLocal)) {
|
|
76
176
|
updatedFiles.push(cursorProjectMcpPath);
|
|
77
177
|
}
|
|
78
178
|
if (gitJenkinsServer && mergeJsonMcpServer(cursorProjectMcpPath, 'git-jenkins', gitJenkinsServer)) {
|
|
79
179
|
updatedFiles.push(cursorProjectMcpPath);
|
|
80
180
|
}
|
|
81
181
|
|
|
82
|
-
// ── Cursor: ~/.cursor/mcp.json (global) ──
|
|
182
|
+
// ── Cursor: ~/.cursor/mcp.json (global, local) ──
|
|
83
183
|
const cursorMcpPath = join(HOME, '.cursor', 'mcp.json');
|
|
84
|
-
if (mergeJsonMcpServer(cursorMcpPath, 'ghl-ai',
|
|
184
|
+
if (mergeJsonMcpServer(cursorMcpPath, 'ghl-ai', ghlAiServerLocal)) {
|
|
85
185
|
updatedFiles.push(cursorMcpPath);
|
|
86
186
|
}
|
|
87
187
|
if (gitJenkinsServer && mergeJsonMcpServer(cursorMcpPath, 'git-jenkins', gitJenkinsServer)) {
|