@ghl-ai/aw 0.1.20 → 0.1.22
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 +19 -1
- package/constants.mjs +1 -1
- package/git.mjs +1 -0
- package/link.mjs +16 -0
- package/mcp.mjs +70 -116
- package/package.json +1 -1
package/commands/init.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Uses git's built-in template hooks for automatic linking on clone/checkout.
|
|
5
5
|
// Uses IDE tasks for auto-pull on workspace open.
|
|
6
6
|
|
|
7
|
-
import { mkdirSync, existsSync, writeFileSync, symlinkSync, chmodSync } from 'node:fs';
|
|
7
|
+
import { mkdirSync, existsSync, writeFileSync, symlinkSync, chmodSync, copyFileSync } from 'node:fs';
|
|
8
8
|
import { execSync } from 'node:child_process';
|
|
9
9
|
import { join, dirname } from 'node:path';
|
|
10
10
|
import { homedir } from 'node:os';
|
|
@@ -111,6 +111,18 @@ function saveManifest(data) {
|
|
|
111
111
|
writeFileSync(MANIFEST_PATH, JSON.stringify(data, null, 2) + '\n');
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// Place AW-PROTOCOL.md at .aw_registry root so any namespace can reference it
|
|
115
|
+
// as .aw_registry/AW-PROTOCOL.md regardless of team name.
|
|
116
|
+
// Source of truth in repo: registry/AW-PROTOCOL.md (mirrors this root placement).
|
|
117
|
+
// After ghl is pulled, ghl/AW-PROTOCOL.md is the local copy we hoist from.
|
|
118
|
+
function hoistProtocol(registryDir) {
|
|
119
|
+
const src = join(registryDir, 'ghl', 'AW-PROTOCOL.md');
|
|
120
|
+
const dest = join(registryDir, 'AW-PROTOCOL.md');
|
|
121
|
+
if (existsSync(src)) {
|
|
122
|
+
copyFileSync(src, dest);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
114
126
|
export async function initCommand(args) {
|
|
115
127
|
const namespace = args['--namespace'] || null;
|
|
116
128
|
let user = args['--user'] || '';
|
|
@@ -139,6 +151,9 @@ export async function initCommand(args) {
|
|
|
139
151
|
// Pull latest
|
|
140
152
|
pullCommand({ ...args, _positional: [], _workspaceDir: GLOBAL_AW_DIR, '--silent': silent });
|
|
141
153
|
|
|
154
|
+
// Hoist AW-PROTOCOL.md to registry root
|
|
155
|
+
hoistProtocol(GLOBAL_AW_DIR);
|
|
156
|
+
|
|
142
157
|
// Re-link IDE dirs (idempotent)
|
|
143
158
|
linkWorkspace(HOME);
|
|
144
159
|
generateCommands(HOME);
|
|
@@ -190,6 +205,9 @@ export async function initCommand(args) {
|
|
|
190
205
|
// Step 2: Pull registry content
|
|
191
206
|
pullCommand({ ...args, _positional: ['ghl'], _workspaceDir: GLOBAL_AW_DIR });
|
|
192
207
|
|
|
208
|
+
// Hoist AW-PROTOCOL.md to registry root so all namespaces can access it
|
|
209
|
+
hoistProtocol(GLOBAL_AW_DIR);
|
|
210
|
+
|
|
193
211
|
if (namespace) {
|
|
194
212
|
pullCommand({ ...args, _positional: ['[template]'], _renameNamespace: namespace, _workspaceDir: GLOBAL_AW_DIR });
|
|
195
213
|
}
|
package/constants.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// constants.mjs — Single source of truth for registry settings.
|
|
2
2
|
|
|
3
3
|
/** Base branch for PRs and sync checkout */
|
|
4
|
-
export const REGISTRY_BASE_BRANCH = '
|
|
4
|
+
export const REGISTRY_BASE_BRANCH = 'chore/imp-command';
|
|
5
5
|
|
|
6
6
|
/** Default registry repository */
|
|
7
7
|
export const REGISTRY_REPO = 'GoHighLevel/ghl-agentic-workspace';
|
package/git.mjs
CHANGED
package/link.mjs
CHANGED
|
@@ -201,6 +201,22 @@ export function linkWorkspace(cwd) {
|
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
// AW-PROTOCOL.md: symlink into each IDE dir root so commands can always find it
|
|
205
|
+
const protocolSrc = join(awDir, 'AW-PROTOCOL.md');
|
|
206
|
+
if (existsSync(protocolSrc)) {
|
|
207
|
+
for (const ide of IDE_DIRS) {
|
|
208
|
+
const linkPath = join(cwd, ide, 'AW-PROTOCOL.md');
|
|
209
|
+
const relTarget = relative(join(cwd, ide), protocolSrc);
|
|
210
|
+
try {
|
|
211
|
+
if (existsSync(linkPath) || lstatSync(linkPath).isSymbolicLink()) unlinkSync(linkPath);
|
|
212
|
+
} catch { /* not there yet */ }
|
|
213
|
+
try {
|
|
214
|
+
execSync(`ln -sfn "${relTarget}" "${linkPath}"`, { stdio: 'pipe' });
|
|
215
|
+
created++;
|
|
216
|
+
} catch { /* best effort */ }
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
204
220
|
if (created > 0) {
|
|
205
221
|
fmt.logSuccess(`Linked ${created} IDE symlink${created > 1 ? 's' : ''}`);
|
|
206
222
|
}
|
package/mcp.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// mcp.mjs — MCP config generation for
|
|
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 ||
|
|
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
|
|
32
|
-
*
|
|
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
|
|
34
|
+
const updatedFiles = [];
|
|
37
35
|
|
|
38
|
-
const
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
134
|
-
|
|
61
|
+
// Deduplicate (claude settings may be added twice if both servers written)
|
|
62
|
+
const unique = [...new Set(updatedFiles)];
|
|
135
63
|
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
67
|
+
fmt.logInfo('MCP servers already configured — no changes needed');
|
|
146
68
|
}
|
|
147
|
-
|
|
69
|
+
|
|
70
|
+
if (!paths.gitJenkinsPath) {
|
|
71
|
+
fmt.logWarn('git-jenkins MCP not found — skipped');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return unique;
|
|
148
75
|
}
|
|
149
76
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
}
|