@ghl-ai/aw 0.1.29 → 0.1.30-beta.2

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;
@@ -243,6 +243,7 @@ export async function initCommand(args) {
243
243
  copyInstructions(HOME, null, freshCfg?.namespace || team) || [];
244
244
  initAwDocs(HOME);
245
245
  setupMcp(HOME, freshCfg?.namespace || team) || [];
246
+ if (cwd !== HOME) setupMcp(cwd, freshCfg?.namespace || team);
246
247
 
247
248
  // Link current project if needed
248
249
  if (cwd !== HOME && !existsSync(join(cwd, '.aw_registry'))) {
@@ -321,6 +322,7 @@ export async function initCommand(args) {
321
322
  const instructionFiles = copyInstructions(HOME, null, team) || [];
322
323
  initAwDocs(HOME);
323
324
  const mcpFiles = setupMcp(HOME, team) || [];
325
+ if (cwd !== HOME) setupMcp(cwd, team);
324
326
  const gitTemplateInstalled = installGitTemplate();
325
327
  installIdeTasks();
326
328
 
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,92 @@ 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 token found anywhere
88
+ fmt.logWarn('⚠ No GitHub token found — MCP authentication will not work!');
89
+ fmt.logWarn('');
90
+ fmt.logWarn(' Fix (recommended): brew install gh && gh auth login');
91
+ fmt.logWarn(' Fix (manual): export GITHUB_TOKEN=ghp_xxx');
92
+ fmt.logWarn('');
93
+ fmt.logWarn(' Then re-run: aw init');
94
+ return null;
95
+ }
96
+
97
+ /**
98
+ * Resolve GitHub username from a PAT. Used for tracking who initialized MCP.
99
+ */
100
+ function resolveGitHubUser(token) {
101
+ try {
102
+ const res = execSync(`curl -sf -H "Authorization: Bearer ${token}" -H "User-Agent: ghl-ai-aw" https://api.github.com/user`, {
103
+ encoding: 'utf8',
104
+ stdio: ['pipe', 'pipe', 'pipe'],
105
+ timeout: 5000,
106
+ });
107
+ const data = JSON.parse(res);
108
+ return data.login || null;
109
+ } catch {
110
+ return null;
111
+ }
112
+ }
113
+
27
114
  /**
28
115
  * Setup MCP configs globally for Claude Code and Cursor.
29
116
  * Merges ghl-ai server into existing configs without overwriting other servers.
@@ -34,54 +121,63 @@ export function setupMcp(cwd, namespace) {
34
121
  const updatedFiles = [];
35
122
 
36
123
  const mcpUrl = paths.ghlMcpUrl;
124
+ const ghToken = resolveGitHubToken();
125
+
126
+ // Track who initialized MCP
127
+ if (ghToken) {
128
+ const ghUser = resolveGitHubUser(ghToken);
129
+ if (ghUser) {
130
+ fmt.logSuccess(`Authenticated as GitHub user: ${fmt.chalk.cyan(ghUser)}`);
131
+ }
132
+ }
133
+
134
+ // Server config with resolved token for local IDE configs (not committed, safe)
135
+ const ghlAiServerLocal = {
136
+ type: 'http',
137
+ url: mcpUrl,
138
+ headers: { Authorization: `Bearer ${ghToken || '${GITHUB_TOKEN}'}` },
139
+ };
37
140
 
38
- const ghlAiServer = { type: 'http', url: mcpUrl };
39
141
  const gitJenkinsServer = paths.gitJenkinsPath
40
142
  ? { command: 'node', args: [paths.gitJenkinsPath] }
41
143
  : null;
42
144
 
43
- // ── Claude Code: ~/.claude/settings.json (global) ──
145
+ // ── Claude Code: ~/.claude/settings.json (global, local) ──
44
146
  const claudeSettingsPath = join(HOME, '.claude', 'settings.json');
45
- if (mergeJsonMcpServer(claudeSettingsPath, 'ghl-ai', ghlAiServer)) {
147
+ if (mergeJsonMcpServer(claudeSettingsPath, 'ghl-ai', ghlAiServerLocal)) {
46
148
  updatedFiles.push(claudeSettingsPath);
47
149
  }
48
150
  if (gitJenkinsServer && mergeJsonMcpServer(claudeSettingsPath, 'git-jenkins', gitJenkinsServer)) {
49
151
  updatedFiles.push(claudeSettingsPath);
50
152
  }
51
153
 
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.
154
+ // ── Claude Code: .mcp.json (project root — committed, uses env var) ──
56
155
  const projectMcpPath = join(cwd, '.mcp.json');
57
- if (mergeJsonMcpServer(projectMcpPath, 'ghl-ai', ghlAiServer)) {
156
+ if (mergeJsonMcpServer(projectMcpPath, 'ghl-ai', ghlAiServerLocal)) {
58
157
  updatedFiles.push(projectMcpPath);
59
158
  }
60
159
 
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).
160
+ // ── Claude Code: .claude/mcp.json (workspace-level, local) ──
65
161
  const claudeWorkspaceMcpPath = join(cwd, '.claude', 'mcp.json');
66
- if (mergeJsonMcpServer(claudeWorkspaceMcpPath, 'ghl-ai', ghlAiServer)) {
162
+ if (mergeJsonMcpServer(claudeWorkspaceMcpPath, 'ghl-ai', ghlAiServerLocal)) {
67
163
  updatedFiles.push(claudeWorkspaceMcpPath);
68
164
  }
69
165
  if (gitJenkinsServer && mergeJsonMcpServer(claudeWorkspaceMcpPath, 'git-jenkins', gitJenkinsServer)) {
70
166
  updatedFiles.push(claudeWorkspaceMcpPath);
71
167
  }
72
168
 
73
- // ── Cursor: project .cursor/mcp.json (workspace-level) ──
169
+ // ── Cursor: project .cursor/mcp.json (workspace-level, local) ──
74
170
  const cursorProjectMcpPath = join(cwd, '.cursor', 'mcp.json');
75
- if (mergeJsonMcpServer(cursorProjectMcpPath, 'ghl-ai', ghlAiServer)) {
171
+ if (mergeJsonMcpServer(cursorProjectMcpPath, 'ghl-ai', ghlAiServerLocal)) {
76
172
  updatedFiles.push(cursorProjectMcpPath);
77
173
  }
78
174
  if (gitJenkinsServer && mergeJsonMcpServer(cursorProjectMcpPath, 'git-jenkins', gitJenkinsServer)) {
79
175
  updatedFiles.push(cursorProjectMcpPath);
80
176
  }
81
177
 
82
- // ── Cursor: ~/.cursor/mcp.json (global) ──
178
+ // ── Cursor: ~/.cursor/mcp.json (global, local) ──
83
179
  const cursorMcpPath = join(HOME, '.cursor', 'mcp.json');
84
- if (mergeJsonMcpServer(cursorMcpPath, 'ghl-ai', ghlAiServer)) {
180
+ if (mergeJsonMcpServer(cursorMcpPath, 'ghl-ai', ghlAiServerLocal)) {
85
181
  updatedFiles.push(cursorMcpPath);
86
182
  }
87
183
  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.2",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {