@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 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', 'platform'];
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', ghlAiServer)) {
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 — highest priority) ──
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', ghlAiServer)) {
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', ghlAiServer)) {
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', ghlAiServer)) {
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', ghlAiServer)) {
184
+ if (mergeJsonMcpServer(cursorMcpPath, 'ghl-ai', ghlAiServerLocal)) {
85
185
  updatedFiles.push(cursorMcpPath);
86
186
  }
87
187
  if (gitJenkinsServer && mergeJsonMcpServer(cursorMcpPath, 'git-jenkins', gitJenkinsServer)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.29",
3
+ "version": "0.1.30-beta.1",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {