@ghl-ai/aw 0.1.35-beta.9 → 0.1.35
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 +4 -7
- package/mcp.mjs +143 -39
- package/package.json +2 -3
- package/ecc.mjs +0 -48
package/commands/init.mjs
CHANGED
|
@@ -19,7 +19,6 @@ import { generateCommands, copyInstructions, initAwDocs } from '../integrate.mjs
|
|
|
19
19
|
import { setupMcp } from '../mcp.mjs';
|
|
20
20
|
import { autoUpdate, promptUpdate } from '../update.mjs';
|
|
21
21
|
import { installGlobalHooks } from '../hooks.mjs';
|
|
22
|
-
import { installAwEcc } from '../ecc.mjs';
|
|
23
22
|
|
|
24
23
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
25
24
|
const VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')).version;
|
|
@@ -201,12 +200,11 @@ export async function initCommand(args) {
|
|
|
201
200
|
|
|
202
201
|
// Re-link IDE dirs + hooks (idempotent)
|
|
203
202
|
linkWorkspace(HOME);
|
|
204
|
-
await installAwEcc(cwd, { targets: ["cursor"], silent });
|
|
205
203
|
generateCommands(HOME);
|
|
206
204
|
copyInstructions(HOME, null, freshCfg?.namespace || team) || [];
|
|
207
205
|
initAwDocs(HOME);
|
|
208
|
-
setupMcp(HOME, freshCfg?.namespace || team
|
|
209
|
-
if (cwd !== HOME) setupMcp(cwd, freshCfg?.namespace || team);
|
|
206
|
+
await setupMcp(HOME, freshCfg?.namespace || team, { silent });
|
|
207
|
+
if (cwd !== HOME) await setupMcp(cwd, freshCfg?.namespace || team, { silent });
|
|
210
208
|
installGlobalHooks();
|
|
211
209
|
|
|
212
210
|
// Link current project if needed
|
|
@@ -282,12 +280,11 @@ export async function initCommand(args) {
|
|
|
282
280
|
// Step 3: Link IDE dirs + setup tasks
|
|
283
281
|
fmt.logStep('Linking IDE symlinks...');
|
|
284
282
|
linkWorkspace(HOME);
|
|
285
|
-
await installAwEcc(cwd, { targets: ["cursor"], silent });
|
|
286
283
|
generateCommands(HOME);
|
|
287
284
|
const instructionFiles = copyInstructions(HOME, null, team) || [];
|
|
288
285
|
initAwDocs(HOME);
|
|
289
|
-
const mcpFiles = setupMcp(HOME, team) || [];
|
|
290
|
-
if (cwd !== HOME) setupMcp(cwd, team);
|
|
286
|
+
const mcpFiles = await setupMcp(HOME, team) || [];
|
|
287
|
+
if (cwd !== HOME) await setupMcp(cwd, team);
|
|
291
288
|
const hooksInstalled = installGlobalHooks();
|
|
292
289
|
installIdeTasks();
|
|
293
290
|
|
package/mcp.mjs
CHANGED
|
@@ -3,26 +3,21 @@
|
|
|
3
3
|
|
|
4
4
|
import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'node:fs';
|
|
5
5
|
import { execSync } from 'node:child_process';
|
|
6
|
-
import {
|
|
6
|
+
import { createInterface } from 'node:readline';
|
|
7
|
+
import { join } from 'node:path';
|
|
7
8
|
import { homedir } from 'node:os';
|
|
8
9
|
import * as fmt from './fmt.mjs';
|
|
9
10
|
|
|
10
11
|
const HOME = homedir();
|
|
11
|
-
const DEFAULT_MCP_URL = 'https://
|
|
12
|
+
const DEFAULT_MCP_URL = 'https://services.leadconnectorhq.com/agentic-workspace/mcp';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Auto-detect MCP server paths.
|
|
15
16
|
*/
|
|
16
17
|
function detectPaths() {
|
|
17
|
-
const gitJenkinsCandidates = [
|
|
18
|
-
join(HOME, 'Documents', 'GitHub', 'git-jenkins-mcp', 'dist', 'index.js'),
|
|
19
|
-
resolve('..', 'git-jenkins-mcp', 'dist', 'index.js'),
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
const gitJenkinsPath = gitJenkinsCandidates.find(p => existsSync(p)) || null;
|
|
23
18
|
const ghlMcpUrl = process.env.GHL_MCP_URL || DEFAULT_MCP_URL;
|
|
24
19
|
|
|
25
|
-
return {
|
|
20
|
+
return { ghlMcpUrl };
|
|
26
21
|
}
|
|
27
22
|
|
|
28
23
|
/**
|
|
@@ -33,10 +28,10 @@ function detectPaths() {
|
|
|
33
28
|
* 2. `gh auth token` (GitHub CLI — most devs have this)
|
|
34
29
|
* 3. null (fall back to ${GITHUB_TOKEN} interpolation in config)
|
|
35
30
|
*/
|
|
36
|
-
function resolveGitHubToken() {
|
|
31
|
+
function resolveGitHubToken(silent = false) {
|
|
37
32
|
// 1. Environment variable
|
|
38
33
|
if (process.env.GITHUB_TOKEN) {
|
|
39
|
-
fmt.logStep('Using GITHUB_TOKEN from environment');
|
|
34
|
+
if (!silent) fmt.logStep('Using GITHUB_TOKEN from environment');
|
|
40
35
|
return process.env.GITHUB_TOKEN;
|
|
41
36
|
}
|
|
42
37
|
|
|
@@ -56,12 +51,14 @@ function resolveGitHubToken() {
|
|
|
56
51
|
timeout: 5000,
|
|
57
52
|
}).trim();
|
|
58
53
|
if (token && (token.startsWith('ghp_') || token.startsWith('gho_') || token.startsWith('github_pat_'))) {
|
|
59
|
-
fmt.logStep('Using GitHub token from gh CLI');
|
|
54
|
+
if (!silent) fmt.logStep('Using GitHub token from gh CLI');
|
|
60
55
|
return token;
|
|
61
56
|
}
|
|
62
57
|
} catch { /* not authenticated yet */ }
|
|
63
58
|
|
|
64
|
-
// 2b. Not authenticated —
|
|
59
|
+
// 2b. Not authenticated — skip browser login in silent mode (no terminal)
|
|
60
|
+
if (silent) return null;
|
|
61
|
+
|
|
65
62
|
fmt.logStep('GitHub CLI found but not authenticated — launching login...');
|
|
66
63
|
try {
|
|
67
64
|
execSync('gh auth login --web --git-protocol https', {
|
|
@@ -111,45 +108,165 @@ function resolveGitHubUser(token) {
|
|
|
111
108
|
}
|
|
112
109
|
}
|
|
113
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Look for an existing ClickUp token already saved in IDE config files.
|
|
113
|
+
* Returns the first token found, or null.
|
|
114
|
+
*/
|
|
115
|
+
function findExistingClickUpToken(cwd) {
|
|
116
|
+
const candidates = [
|
|
117
|
+
join(HOME, '.claude', 'settings.json'),
|
|
118
|
+
join(HOME, '.claude', 'mcp.json'),
|
|
119
|
+
join(HOME, '.mcp.json'),
|
|
120
|
+
join(HOME, '.cursor', 'mcp.json'),
|
|
121
|
+
join(cwd, '.claude', 'mcp.json'),
|
|
122
|
+
join(cwd, '.mcp.json'),
|
|
123
|
+
join(cwd, '.cursor', 'mcp.json'),
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
for (const filePath of candidates) {
|
|
127
|
+
try {
|
|
128
|
+
if (!existsSync(filePath)) continue;
|
|
129
|
+
const config = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
130
|
+
const token = config?.mcpServers?.['ghl-ai']?.headers?.['X-ClickUp-Token'];
|
|
131
|
+
if (token && typeof token === 'string' && token.startsWith('pk_')) {
|
|
132
|
+
return { token, source: filePath };
|
|
133
|
+
}
|
|
134
|
+
} catch { /* corrupt file — skip */ }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Resolve ClickUp personal API token.
|
|
142
|
+
*
|
|
143
|
+
* Resolution chain:
|
|
144
|
+
* 1. $CLICKUP_API_TOKEN env var (already set)
|
|
145
|
+
* 2. Existing token in IDE config files (already configured)
|
|
146
|
+
* 3. Open browser + prompt user (one-time)
|
|
147
|
+
* 4. null (skip ClickUp — other MCP tools still work)
|
|
148
|
+
*/
|
|
149
|
+
async function resolveClickUpToken(silent = false, cwd = process.cwd()) {
|
|
150
|
+
// 1. Environment variable
|
|
151
|
+
if (process.env.CLICKUP_API_TOKEN) {
|
|
152
|
+
fmt.logStep('Using CLICKUP_API_TOKEN from environment');
|
|
153
|
+
return process.env.CLICKUP_API_TOKEN;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 2. Already saved in an IDE config file — reuse it
|
|
157
|
+
const existing = findExistingClickUpToken(cwd);
|
|
158
|
+
if (existing) {
|
|
159
|
+
fmt.logStep(`Using existing ClickUp token from ${fmt.chalk.cyan(existing.source)}`);
|
|
160
|
+
return existing.token;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// In silent mode (git hooks, auto-pull) there is no terminal — skip prompt
|
|
164
|
+
if (silent) return null;
|
|
165
|
+
|
|
166
|
+
// 3. Interactive prompt — open browser and ask user to paste
|
|
167
|
+
fmt.logStep('ClickUp API token needed for per-user task attribution');
|
|
168
|
+
fmt.logStep('Go to https://app.clickup.com/settings/apps to copy your API token');
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
172
|
+
execSync(`${openCmd} "https://app.clickup.com/settings/apps"`, { stdio: 'ignore', timeout: 5000 });
|
|
173
|
+
fmt.logStep('Opened ClickUp settings in your browser');
|
|
174
|
+
} catch {
|
|
175
|
+
fmt.logWarn('Could not open browser — navigate to the URL above manually');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Drain any leftover stdin bytes from prior interactive flows (e.g. gh auth login)
|
|
179
|
+
if (process.stdin.readable) {
|
|
180
|
+
process.stdin.resume();
|
|
181
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
182
|
+
process.stdin.pause();
|
|
183
|
+
while (process.stdin.read() !== null) { /* drain */ }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
187
|
+
const rawInput = await new Promise((resolve) => {
|
|
188
|
+
rl.question(' Paste your ClickUp API Token (pk_...): ', (answer) => {
|
|
189
|
+
rl.close();
|
|
190
|
+
resolve(answer);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Extract the pk_ token from the paste — handles accidental double-paste,
|
|
195
|
+
// surrounding whitespace, or trailing garbage from terminal paste buffers.
|
|
196
|
+
const match = typeof rawInput === 'string' ? rawInput.match(/\b(pk_\w+)\b/) : null;
|
|
197
|
+
const token = match ? match[1] : null;
|
|
198
|
+
|
|
199
|
+
if (!token) {
|
|
200
|
+
fmt.logWarn('No valid ClickUp token provided — skipping (ClickUp tools will use shared token)');
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Validate token against ClickUp API
|
|
205
|
+
try {
|
|
206
|
+
const res = execSync(`curl -s -H "Authorization: ${token}" "https://api.clickup.com/api/v2/user"`, {
|
|
207
|
+
encoding: 'utf8',
|
|
208
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
209
|
+
timeout: 10000,
|
|
210
|
+
maxBuffer: 1024 * 256,
|
|
211
|
+
});
|
|
212
|
+
const data = JSON.parse(res);
|
|
213
|
+
if (data.err) {
|
|
214
|
+
fmt.logWarn(`ClickUp token validation failed: ${data.err} — using it anyway`);
|
|
215
|
+
} else {
|
|
216
|
+
const username = data.user?.username || data.user?.email;
|
|
217
|
+
if (username) {
|
|
218
|
+
fmt.logSuccess(`ClickUp token valid — user: ${fmt.chalk.cyan(username)}`);
|
|
219
|
+
} else {
|
|
220
|
+
fmt.logWarn('ClickUp token accepted but no user found — using it anyway');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
} catch (e) {
|
|
224
|
+
fmt.logWarn(`ClickUp token validation failed: ${e.message?.split('\n')[0]} — using it anyway`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return token;
|
|
228
|
+
}
|
|
229
|
+
|
|
114
230
|
/**
|
|
115
231
|
* Setup MCP configs globally for Claude Code and Cursor.
|
|
116
232
|
* Merges ghl-ai server into existing configs without overwriting other servers.
|
|
117
233
|
* Returns list of file paths that were created or updated.
|
|
118
234
|
*/
|
|
119
|
-
export function setupMcp(cwd, namespace) {
|
|
235
|
+
export async function setupMcp(cwd, namespace, { silent = false } = {}) {
|
|
120
236
|
const paths = detectPaths();
|
|
121
237
|
const updatedFiles = [];
|
|
122
238
|
|
|
123
239
|
const mcpUrl = paths.ghlMcpUrl;
|
|
124
|
-
const ghToken = resolveGitHubToken();
|
|
240
|
+
const ghToken = resolveGitHubToken(silent);
|
|
125
241
|
|
|
126
242
|
// Track who initialized MCP
|
|
127
|
-
if (ghToken) {
|
|
243
|
+
if (ghToken && !silent) {
|
|
128
244
|
const ghUser = resolveGitHubUser(ghToken);
|
|
129
245
|
if (ghUser) {
|
|
130
246
|
fmt.logSuccess(`Authenticated as GitHub user: ${fmt.chalk.cyan(ghUser)}`);
|
|
131
247
|
}
|
|
132
248
|
}
|
|
133
249
|
|
|
134
|
-
//
|
|
250
|
+
// Resolve ClickUp token — skipped in silent mode (no terminal)
|
|
251
|
+
const clickupToken = await resolveClickUpToken(silent, cwd);
|
|
252
|
+
|
|
253
|
+
// Server config with resolved tokens for local IDE configs (not committed, safe)
|
|
254
|
+
const headers = { Authorization: `Bearer ${ghToken || '${GITHUB_TOKEN}'}` };
|
|
255
|
+
if (clickupToken) {
|
|
256
|
+
headers['X-ClickUp-Token'] = clickupToken;
|
|
257
|
+
}
|
|
258
|
+
|
|
135
259
|
const ghlAiServerLocal = {
|
|
136
260
|
type: 'http',
|
|
137
261
|
url: mcpUrl,
|
|
138
|
-
headers
|
|
262
|
+
headers,
|
|
139
263
|
};
|
|
140
264
|
|
|
141
|
-
const gitJenkinsServer = paths.gitJenkinsPath
|
|
142
|
-
? { command: 'node', args: [paths.gitJenkinsPath] }
|
|
143
|
-
: null;
|
|
144
|
-
|
|
145
265
|
// ── Claude Code: ~/.claude/settings.json (global, local) ──
|
|
146
266
|
const claudeSettingsPath = join(HOME, '.claude', 'settings.json');
|
|
147
267
|
if (mergeJsonMcpServer(claudeSettingsPath, 'ghl-ai', ghlAiServerLocal)) {
|
|
148
268
|
updatedFiles.push(claudeSettingsPath);
|
|
149
269
|
}
|
|
150
|
-
if (gitJenkinsServer && mergeJsonMcpServer(claudeSettingsPath, 'git-jenkins', gitJenkinsServer)) {
|
|
151
|
-
updatedFiles.push(claudeSettingsPath);
|
|
152
|
-
}
|
|
153
270
|
|
|
154
271
|
// ── Claude Code: .mcp.json (project root — committed, uses env var) ──
|
|
155
272
|
const projectMcpPath = join(cwd, '.mcp.json');
|
|
@@ -162,29 +279,20 @@ export function setupMcp(cwd, namespace) {
|
|
|
162
279
|
if (mergeJsonMcpServer(claudeWorkspaceMcpPath, 'ghl-ai', ghlAiServerLocal)) {
|
|
163
280
|
updatedFiles.push(claudeWorkspaceMcpPath);
|
|
164
281
|
}
|
|
165
|
-
if (gitJenkinsServer && mergeJsonMcpServer(claudeWorkspaceMcpPath, 'git-jenkins', gitJenkinsServer)) {
|
|
166
|
-
updatedFiles.push(claudeWorkspaceMcpPath);
|
|
167
|
-
}
|
|
168
282
|
|
|
169
283
|
// ── Cursor: project .cursor/mcp.json (workspace-level, local) ──
|
|
170
284
|
const cursorProjectMcpPath = join(cwd, '.cursor', 'mcp.json');
|
|
171
285
|
if (mergeJsonMcpServer(cursorProjectMcpPath, 'ghl-ai', ghlAiServerLocal)) {
|
|
172
286
|
updatedFiles.push(cursorProjectMcpPath);
|
|
173
287
|
}
|
|
174
|
-
if (gitJenkinsServer && mergeJsonMcpServer(cursorProjectMcpPath, 'git-jenkins', gitJenkinsServer)) {
|
|
175
|
-
updatedFiles.push(cursorProjectMcpPath);
|
|
176
|
-
}
|
|
177
288
|
|
|
178
289
|
// ── Cursor: ~/.cursor/mcp.json (global, local) ──
|
|
179
290
|
const cursorMcpPath = join(HOME, '.cursor', 'mcp.json');
|
|
180
291
|
if (mergeJsonMcpServer(cursorMcpPath, 'ghl-ai', ghlAiServerLocal)) {
|
|
181
292
|
updatedFiles.push(cursorMcpPath);
|
|
182
293
|
}
|
|
183
|
-
if (gitJenkinsServer && mergeJsonMcpServer(cursorMcpPath, 'git-jenkins', gitJenkinsServer)) {
|
|
184
|
-
updatedFiles.push(cursorMcpPath);
|
|
185
|
-
}
|
|
186
294
|
|
|
187
|
-
// Deduplicate
|
|
295
|
+
// Deduplicate
|
|
188
296
|
const unique = [...new Set(updatedFiles)];
|
|
189
297
|
|
|
190
298
|
if (unique.length > 0) {
|
|
@@ -193,10 +301,6 @@ export function setupMcp(cwd, namespace) {
|
|
|
193
301
|
fmt.logInfo('MCP servers already configured — no changes needed');
|
|
194
302
|
}
|
|
195
303
|
|
|
196
|
-
if (!paths.gitJenkinsPath) {
|
|
197
|
-
fmt.logWarn('git-jenkins MCP not found — skipped');
|
|
198
|
-
}
|
|
199
|
-
|
|
200
304
|
return unique;
|
|
201
305
|
}
|
|
202
306
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghl-ai/aw",
|
|
3
|
-
"version": "0.1.35
|
|
3
|
+
"version": "0.1.35",
|
|
4
4
|
"description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -24,8 +24,7 @@
|
|
|
24
24
|
"registry.mjs",
|
|
25
25
|
"apply.mjs",
|
|
26
26
|
"update.mjs",
|
|
27
|
-
"hooks.mjs"
|
|
28
|
-
"ecc.mjs"
|
|
27
|
+
"hooks.mjs"
|
|
29
28
|
],
|
|
30
29
|
"engines": {
|
|
31
30
|
"node": ">=18.0.0"
|
package/ecc.mjs
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
2
|
-
import { existsSync, rmSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import * as fmt from "./fmt.mjs";
|
|
5
|
-
|
|
6
|
-
const AW_ECC_REPO_SSH = "git@github.com:shreyansh-ghl/aw-ecc.git";
|
|
7
|
-
const AW_ECC_REPO_HTTPS = "https://github.com/shreyansh-ghl/aw-ecc.git";
|
|
8
|
-
const AW_ECC_TAG = "v1.0.0";
|
|
9
|
-
const TMP_DIR = "/tmp/aw-ecc";
|
|
10
|
-
|
|
11
|
-
function cloneRepo(tag, dest, stdio) {
|
|
12
|
-
try {
|
|
13
|
-
execSync(`git clone --depth 1 --branch ${tag} ${AW_ECC_REPO_SSH} ${dest}`, {
|
|
14
|
-
stdio,
|
|
15
|
-
});
|
|
16
|
-
} catch {
|
|
17
|
-
execSync(
|
|
18
|
-
`git clone --depth 1 --branch ${tag} ${AW_ECC_REPO_HTTPS} ${dest}`,
|
|
19
|
-
{ stdio },
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function installAwEcc(
|
|
25
|
-
cwd,
|
|
26
|
-
{ targets = ["cursor"], silent = false } = {},
|
|
27
|
-
) {
|
|
28
|
-
if (!silent) fmt.logStep("Installing aw-ecc engine...");
|
|
29
|
-
|
|
30
|
-
if (existsSync(TMP_DIR)) rmSync(TMP_DIR, { recursive: true, force: true });
|
|
31
|
-
|
|
32
|
-
cloneRepo(AW_ECC_TAG, TMP_DIR, silent ? "pipe" : "inherit");
|
|
33
|
-
|
|
34
|
-
execSync("npm install --no-audit --no-fund", {
|
|
35
|
-
cwd: TMP_DIR,
|
|
36
|
-
stdio: silent ? "pipe" : "inherit",
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
for (const target of targets) {
|
|
40
|
-
execSync(
|
|
41
|
-
`node ${join(TMP_DIR, "scripts/install-apply.js")} --target ${target} --profile full`,
|
|
42
|
-
{ cwd, stdio: silent ? "pipe" : "inherit" },
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
rmSync(TMP_DIR, { recursive: true, force: true });
|
|
47
|
-
if (!silent) fmt.logSuccess("aw-ecc engine installed");
|
|
48
|
-
}
|