@ghl-ai/aw 0.1.51 → 0.1.53
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/c4/templates/scripts/aw-c4-bootstrap.sh +5 -4
- package/cli.mjs +17 -9
- package/commands/c4.mjs +17 -9
- package/commands/doctor.mjs +2 -2
- package/commands/init.mjs +23 -5
- package/commands/integrations.mjs +268 -0
- package/commands/mcp.mjs +90 -0
- package/commands/nuke.mjs +1 -1
- package/commands/pull.mjs +3 -2
- package/commands/startup.mjs +22 -3
- package/ecc.mjs +1 -1
- package/integrate.mjs +94 -21
- package/integrations/context-mode.mjs +237 -57
- package/integrations.mjs +971 -0
- package/mcp.mjs +132 -16
- package/package.json +4 -3
- package/render-rules.mjs +25 -1
- package/startup.mjs +52 -8
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
# - .codex/scripts/codex-web-bootstrap.sh (passes --harness codex-web)
|
|
13
13
|
#
|
|
14
14
|
# Override knobs (env):
|
|
15
|
-
# AW_PACKAGE npm spec to install. Defaults to @ghl-ai/aw@latest.
|
|
16
|
-
#
|
|
17
|
-
#
|
|
15
|
+
# AW_PACKAGE npm spec to install. Defaults to @ghl-ai/aw@latest.
|
|
16
|
+
# Override to pin a specific @ghl-ai/aw@0.1.x for reproducible
|
|
17
|
+
# CI runs.
|
|
18
18
|
set -Eeuo pipefail
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
# GitHub auth is resolved inside `aw c4` so GITHUB_PAT, GITHUB_TOKEN, and
|
|
21
|
+
# no-token diagnostics stay centralized in the CLI preflight.
|
|
21
22
|
|
|
22
23
|
# Ensure npm is on PATH. Cursor Cloud's install shell is non-interactive — nvm
|
|
23
24
|
# is not auto-sourced, and Node may not be pre-installed at all. Walk common
|
package/cli.mjs
CHANGED
|
@@ -21,13 +21,14 @@ const COMMANDS = {
|
|
|
21
21
|
doctor: () => import('./commands/doctor.mjs').then(m => m.doctorCommand),
|
|
22
22
|
routing: () => import('./commands/startup.mjs').then(m => m.routingCommand),
|
|
23
23
|
startup: () => import('./commands/startup.mjs').then(m => m.startupCommand),
|
|
24
|
+
mcp: () => import('./commands/mcp.mjs').then(m => m.mcpCommand),
|
|
24
25
|
search: () => import('./commands/search.mjs').then(m => m.searchCommand),
|
|
25
26
|
link: () => import('./commands/link-project.mjs').then(m => m.linkProjectCommand),
|
|
26
27
|
nuke: () => import('./commands/nuke.mjs').then(m => m.nukeCommand),
|
|
27
28
|
daemon: () => import('./commands/daemon.mjs').then(m => m.daemonCommand),
|
|
28
|
-
integration: () => import('./commands/integration.mjs').then(m => m.integrationCommand),
|
|
29
|
-
integrations: () => import('./commands/integration.mjs').then(m => m.integrationCommand),
|
|
30
29
|
telemetry: () => import('./commands/telemetry.mjs').then(m => m.telemetryCommand),
|
|
30
|
+
integration: () => import('./commands/integration.mjs').then(m => m.integrationCommand),
|
|
31
|
+
integrations: () => import('./commands/integrations.mjs').then(m => m.integrationsCommand),
|
|
31
32
|
'slack-sim': () => import('./commands/slack-sim.mjs').then(m => m.slackSimCommand),
|
|
32
33
|
c4: () => import('./commands/c4.mjs').then(m => m.c4Command),
|
|
33
34
|
'init-repo': () => import('./commands/init-repo.mjs').then(m => m.initRepoCommand),
|
|
@@ -87,8 +88,9 @@ function printHelp() {
|
|
|
87
88
|
const sec = (title) => `\n ${chalk.bold.underline(title)}`;
|
|
88
89
|
const help = [
|
|
89
90
|
sec('Setup'),
|
|
90
|
-
cmd('aw init', 'Initialize workspace (
|
|
91
|
+
cmd('aw init', 'Initialize workspace (auto-installs suggested integrations)'),
|
|
91
92
|
cmd('aw init --namespace <team/sub-team>', 'Add a team namespace (optional)'),
|
|
93
|
+
cmd('aw init --no-integrations', 'Skip integration setup (Codex, Caveman, Graphify, etc)'),
|
|
92
94
|
` ${chalk.dim('Teams: platform, revex, mobile, commerce, leadgen, crm, marketplace, ai')}`,
|
|
93
95
|
` ${chalk.dim('Example: aw init --namespace revex/courses')}`,
|
|
94
96
|
cmd('aw init-repo', 'Scaffold cloud-bootstrap files (idempotent, --dry-run/--force/--diff)'),
|
|
@@ -114,12 +116,18 @@ function printHelp() {
|
|
|
114
116
|
cmd('aw doctor', 'Run a health check for routing, MCP, plugin, and AW ECC surfaces'),
|
|
115
117
|
cmd('aw link', 'Link current project as a git worktree (wires IDE symlinks)'),
|
|
116
118
|
cmd('aw routing status', 'Show global AW session-routing mode for Claude/Cursor/Codex'),
|
|
117
|
-
cmd('aw routing disable', 'Disable
|
|
118
|
-
cmd('aw routing enable', 'Re-enable
|
|
119
|
-
cmd('aw
|
|
120
|
-
cmd('aw
|
|
121
|
-
cmd('aw
|
|
122
|
-
cmd('aw
|
|
119
|
+
cmd('aw routing disable', 'Disable default AW router/rules injection globally'),
|
|
120
|
+
cmd('aw routing enable', 'Re-enable default AW router/rules injection globally'),
|
|
121
|
+
cmd('aw mcp status', 'Show global AW MCP mode for Claude/Cursor/Codex'),
|
|
122
|
+
cmd('aw mcp disable', 'Remove AW-managed MCP server across Claude/Cursor/Codex'),
|
|
123
|
+
cmd('aw mcp enable', 'Restore AW-managed MCP server across Claude/Cursor/Codex'),
|
|
124
|
+
cmd('aw integrations', 'Manage third-party integrations (Codex, Caveman, etc)'),
|
|
125
|
+
cmd('aw integrations add <key>', 'Install a specific tool (e.g. codex, caveman)'),
|
|
126
|
+
cmd('aw integrations remove <key>', 'Remove a tool'),
|
|
127
|
+
cmd('aw integrations bundle <name>', 'Install a preset bundle'),
|
|
128
|
+
cmd('aw integration add <name>', 'Configure an installed local integration, e.g. context-mode'),
|
|
129
|
+
cmd('aw integration status <name>', 'Show local integration health'),
|
|
130
|
+
cmd('aw integration remove <name>', 'Remove AW-managed local integration entries'),
|
|
123
131
|
cmd('aw drop <path>', 'Stop syncing or delete local content'),
|
|
124
132
|
cmd('aw nuke', 'Remove entire .aw_registry/ & start fresh'),
|
|
125
133
|
cmd('aw daemon install', 'Auto-pull on a schedule (macOS launchd / Linux cron)'),
|
package/commands/c4.mjs
CHANGED
|
@@ -24,6 +24,7 @@ import { spawnSync as nodeSpawnSync } from 'node:child_process';
|
|
|
24
24
|
import { existsSync as fsExistsSync } from 'node:fs';
|
|
25
25
|
import { join } from 'node:path';
|
|
26
26
|
import * as c4Default from '../c4/index.mjs';
|
|
27
|
+
import { isMcpEnabled } from '../mcp.mjs';
|
|
27
28
|
|
|
28
29
|
/* ─────────────────────────────────────────────────────────────────────────
|
|
29
30
|
* Constants — referenced by self-tests.
|
|
@@ -349,10 +350,10 @@ export async function c4Command(rawArgs, overrides = {}) {
|
|
|
349
350
|
return exit(0);
|
|
350
351
|
}
|
|
351
352
|
|
|
352
|
-
// Step 6 — npm install -g @ghl-ai/aw.
|
|
353
|
-
const npmRes = spawnSync('npm', ['install', '-g', '@ghl-ai/aw'], { stdio: 'pipe' });
|
|
353
|
+
// Step 6 — npm install -g @ghl-ai/aw@latest.
|
|
354
|
+
const npmRes = spawnSync('npm', ['install', '-g', '@ghl-ai/aw@latest'], { stdio: 'pipe' });
|
|
354
355
|
if (npmRes && npmRes.status !== 0) {
|
|
355
|
-
writer.stderr('[aw-c4] npm install -g @ghl-ai/aw failed (non-fatal); using existing aw if present\n');
|
|
356
|
+
writer.stderr('[aw-c4] npm install -g @ghl-ai/aw@latest failed (non-fatal); using existing aw if present\n');
|
|
356
357
|
}
|
|
357
358
|
|
|
358
359
|
// Step 7 — aw init --silent.
|
|
@@ -380,7 +381,12 @@ export async function c4Command(rawArgs, overrides = {}) {
|
|
|
380
381
|
}
|
|
381
382
|
|
|
382
383
|
// Step 11 — MCP register.
|
|
383
|
-
|
|
384
|
+
const mcpEnabled = isMcpEnabled(home, env);
|
|
385
|
+
if (mcpEnabled) {
|
|
386
|
+
safe('registerGhlAiMcp', () => c4.registerGhlAiMcp(harness, home, token), writer);
|
|
387
|
+
} else {
|
|
388
|
+
writer.stdout('[aw-c4] MCP disabled; skipping registerGhlAiMcp\n');
|
|
389
|
+
}
|
|
384
390
|
|
|
385
391
|
// Step 12 — slash command surface.
|
|
386
392
|
safe('ensureCommandSurface', () => c4.ensureCommandSurface({ harness, home, eccHome }), writer);
|
|
@@ -403,11 +409,13 @@ export async function c4Command(rawArgs, overrides = {}) {
|
|
|
403
409
|
safe('ensureRepoLocalIgnore', () => c4.ensureRepoLocalIgnore({ cwd, harness }), writer);
|
|
404
410
|
|
|
405
411
|
// Step 15 — MCP smoke probe (best-effort).
|
|
406
|
-
const mcpProbe =
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
412
|
+
const mcpProbe = mcpEnabled
|
|
413
|
+
? await safeAsync(
|
|
414
|
+
'probeMcpServer',
|
|
415
|
+
() => c4.probeMcpServer({ url: c4.MCP_URL_DEFAULT, token }),
|
|
416
|
+
writer,
|
|
417
|
+
)
|
|
418
|
+
: null;
|
|
411
419
|
|
|
412
420
|
// Step 16 — self-tests.
|
|
413
421
|
const self = runSelfTests({ harness, c4, home, awHome, eccHome });
|
package/commands/doctor.mjs
CHANGED
|
@@ -210,7 +210,7 @@ function directoryContainsGeneratedRuleFiles(dirPath, extension) {
|
|
|
210
210
|
try {
|
|
211
211
|
return readdirSync(dirPath, { withFileTypes: true })
|
|
212
212
|
.filter(entry => entry.isFile() && entry.name.endsWith(extension))
|
|
213
|
-
.some(entry => readText(join(dirPath, entry.name)).
|
|
213
|
+
.some(entry => readText(join(dirPath, entry.name)).includes(GENERATED_RULE_HEADER));
|
|
214
214
|
} catch {
|
|
215
215
|
return false;
|
|
216
216
|
}
|
|
@@ -501,7 +501,7 @@ function listGeneratedRuleFiles(dirPath, extension) {
|
|
|
501
501
|
return readdirSync(dirPath, { withFileTypes: true })
|
|
502
502
|
.filter(entry => entry.isFile() && entry.name.endsWith(extension))
|
|
503
503
|
.map(entry => join(dirPath, entry.name))
|
|
504
|
-
.filter(filePath => readText(filePath).
|
|
504
|
+
.filter(filePath => readText(filePath).includes(GENERATED_RULE_HEADER));
|
|
505
505
|
} catch {
|
|
506
506
|
return [];
|
|
507
507
|
}
|
package/commands/init.mjs
CHANGED
|
@@ -35,6 +35,7 @@ import { loadConfig as ensureTelemetryConfig } from '../telemetry.mjs';
|
|
|
35
35
|
import { installAwEcc, AW_ECC_TAG } from '../ecc.mjs';
|
|
36
36
|
import { removeWorkspaceHookDefaults } from '../codex.mjs';
|
|
37
37
|
import { readHookManifest, pruneStaleHooks, writeHookManifest } from '../hook-cleanup.mjs';
|
|
38
|
+
import { promptAndInstall, autoInstallIntegrations } from '../integrations.mjs';
|
|
38
39
|
import {
|
|
39
40
|
initPersistentClone,
|
|
40
41
|
isValidClone,
|
|
@@ -48,7 +49,7 @@ import {
|
|
|
48
49
|
syncWorktreeSparseCheckout,
|
|
49
50
|
findNearestWorktree,
|
|
50
51
|
} from '../git.mjs';
|
|
51
|
-
import { REGISTRY_DIR, REGISTRY_REPO, REGISTRY_URL, RULES_SOURCE_DIR, RULES_RUNTIME_DIR } from '../constants.mjs';
|
|
52
|
+
import { REGISTRY_DIR, REGISTRY_REPO, REGISTRY_URL, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR, RULES_RUNTIME_DIR } from '../constants.mjs';
|
|
52
53
|
import { syncFileTree } from '../file-tree.mjs';
|
|
53
54
|
|
|
54
55
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -136,8 +137,8 @@ function scaffoldNamespace(awHome, folderName) {
|
|
|
136
137
|
|
|
137
138
|
// ── Ensure ~/.aw/.git/info/exclude has the whitelist block ─────────────
|
|
138
139
|
//
|
|
139
|
-
// Strategy: only .aw_registry/, .aw_rules/, content/ are
|
|
140
|
-
// else at the top level of ~/.aw/ is local-only (telemetry/, hooks/, logs,
|
|
140
|
+
// Strategy: only .aw_registry/, .aw_rules/, content/, and .aw_docs/ are
|
|
141
|
+
// tracked — everything else at the top level of ~/.aw/ is local-only (telemetry/, hooks/, logs,
|
|
141
142
|
// .DS_Store, etc.). We write to .git/info/exclude (not tracked .gitignore)
|
|
142
143
|
// so upstream pulls never conflict.
|
|
143
144
|
|
|
@@ -151,6 +152,7 @@ const AW_MANAGED_BLOCK = [
|
|
|
151
152
|
'!/.aw_registry',
|
|
152
153
|
'!/.aw_rules',
|
|
153
154
|
'!/content',
|
|
155
|
+
'!/.aw_docs',
|
|
154
156
|
'',
|
|
155
157
|
'# Nested local state within whitelisted dirs',
|
|
156
158
|
'/.aw_registry/.sync-config.json',
|
|
@@ -239,6 +241,7 @@ export async function initCommand(args) {
|
|
|
239
241
|
let namespace = args['--namespace'] || null;
|
|
240
242
|
let user = args['--user'] || '';
|
|
241
243
|
const silent = args['--silent'] === true;
|
|
244
|
+
const skipIntegrations = args['--no-integrations'] === true;
|
|
242
245
|
|
|
243
246
|
// In silent mode, suppress ALL fmt output and show a single spinner.
|
|
244
247
|
// setSilent(true) makes every fmt.* call a no-op — internal functions
|
|
@@ -350,7 +353,7 @@ export async function initCommand(args) {
|
|
|
350
353
|
const isNewSubTeam = folderName && cfg && !cfg.include.includes(folderName);
|
|
351
354
|
if (isNewSubTeam) {
|
|
352
355
|
if (!silent) fmt.logStep(`Adding sub-team ${chalk.cyan(folderName)}...`);
|
|
353
|
-
const newSparsePaths = [`.aw_registry/${folderName}`,
|
|
356
|
+
const newSparsePaths = [`.aw_registry/${folderName}`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR];
|
|
354
357
|
addToSparseCheckout(AW_HOME, newSparsePaths);
|
|
355
358
|
config.addPattern(GLOBAL_AW_DIR, folderName);
|
|
356
359
|
scaffoldNamespace(AW_HOME, folderName);
|
|
@@ -442,6 +445,12 @@ export async function initCommand(args) {
|
|
|
442
445
|
// Write hook manifest after all hook installation is complete
|
|
443
446
|
try { writeHookManifest({ eccVersion: AW_ECC_TAG, awVersion: VERSION }); } catch { /* best effort */ }
|
|
444
447
|
|
|
448
|
+
// Auto-install suggested integrations (Codex, Caveman, Graphify, etc) - unless --no-integrations
|
|
449
|
+
let installedIntegrations = [];
|
|
450
|
+
if (!silent && !skipIntegrations && !isNewSubTeam) {
|
|
451
|
+
installedIntegrations = await autoInstallIntegrations(freshCfg?.namespace || team, { silent });
|
|
452
|
+
}
|
|
453
|
+
|
|
445
454
|
if (silent) {
|
|
446
455
|
if (silentSpinner) { silentSpinner.stop('Done'); setSilent(false); }
|
|
447
456
|
autoUpdate(await args._updateCheck);
|
|
@@ -455,6 +464,7 @@ export async function initCommand(args) {
|
|
|
455
464
|
? ` ${chalk.green('✓')} Removed ${removedLegacyStartupFiles.length} legacy repo startup file${removedLegacyStartupFiles.length > 1 ? 's' : ''}`
|
|
456
465
|
: null,
|
|
457
466
|
cwd !== HOME && isWorktree(join(cwd, '.aw')) ? ` ${chalk.green('✓')} Project linked` : null,
|
|
467
|
+
installedIntegrations.length > 0 ? ` ${chalk.green('✓')} Integrations: ${installedIntegrations.join(', ')}` : null,
|
|
458
468
|
].filter(Boolean).join('\n'));
|
|
459
469
|
}
|
|
460
470
|
return;
|
|
@@ -483,7 +493,7 @@ export async function initCommand(args) {
|
|
|
483
493
|
}
|
|
484
494
|
|
|
485
495
|
// Determine sparse paths
|
|
486
|
-
const sparsePaths = [`.aw_registry/platform`,
|
|
496
|
+
const sparsePaths = [`.aw_registry/platform`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR, `.aw_registry/AW-PROTOCOL.md`, `CODEOWNERS`];
|
|
487
497
|
if (folderName) {
|
|
488
498
|
sparsePaths.push(`.aw_registry/${folderName}`);
|
|
489
499
|
}
|
|
@@ -603,6 +613,12 @@ export async function initCommand(args) {
|
|
|
603
613
|
// Ensure telemetry config exists (generates machine_id on first run)
|
|
604
614
|
ensureTelemetryConfig();
|
|
605
615
|
|
|
616
|
+
// Auto-install suggested integrations (Codex, Caveman, Graphify, etc) - unless --no-integrations
|
|
617
|
+
let installedIntegrations = [];
|
|
618
|
+
if (!silent && !skipIntegrations) {
|
|
619
|
+
installedIntegrations = await autoInstallIntegrations(team, { silent });
|
|
620
|
+
}
|
|
621
|
+
|
|
606
622
|
// Offer to update if a newer version is available
|
|
607
623
|
if (!silent) await promptUpdate(await args._updateCheck);
|
|
608
624
|
|
|
@@ -622,10 +638,12 @@ export async function initCommand(args) {
|
|
|
622
638
|
hooksInstalled ? ` ${chalk.green('✓')} Git hooks: auto-sync on pull/clone (core.hooksPath)` : null,
|
|
623
639
|
` ${chalk.green('✓')} IDE task: auto-sync on workspace open`,
|
|
624
640
|
cwd !== HOME && isWorktree(join(cwd, '.aw')) ? ` ${chalk.green('✓')} Linked in current project` : null,
|
|
641
|
+
installedIntegrations.length > 0 ? ` ${chalk.green('✓')} Integrations: ${installedIntegrations.join(', ')}` : null,
|
|
625
642
|
'',
|
|
626
643
|
` ${chalk.dim('Existing repos:')} ${chalk.bold('cd <project> && aw link')}`,
|
|
627
644
|
` ${chalk.dim('New clones:')} auto-linked via git hook`,
|
|
628
645
|
` ${chalk.dim('Update:')} ${chalk.bold('aw init')} ${chalk.dim('(or auto on pull/IDE open)')}`,
|
|
646
|
+
` ${chalk.dim('Integrations:')} ${chalk.bold('aw integrations')} ${chalk.dim('(manage Codex, Caveman, etc)')}`,
|
|
629
647
|
` ${chalk.dim('Uninstall:')} ${chalk.bold('aw nuke')}`,
|
|
630
648
|
].filter(Boolean).join('\n'));
|
|
631
649
|
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// commands/integrations.mjs — CLI command: aw integrations add/remove/list/bundle
|
|
2
|
+
|
|
3
|
+
import * as p from '@clack/prompts';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import * as fmt from '../fmt.mjs';
|
|
6
|
+
import { chalk } from '../fmt.mjs';
|
|
7
|
+
import { listIntegrations as listLocalIntegrations } from '../integrations/index.mjs';
|
|
8
|
+
import {
|
|
9
|
+
INTEGRATIONS,
|
|
10
|
+
BUNDLES,
|
|
11
|
+
installIntegration,
|
|
12
|
+
removeIntegration,
|
|
13
|
+
getInstalledList,
|
|
14
|
+
} from '../integrations.mjs';
|
|
15
|
+
|
|
16
|
+
export async function integrationsCommand(args) {
|
|
17
|
+
const subcommand = args._positional[0];
|
|
18
|
+
|
|
19
|
+
switch (subcommand) {
|
|
20
|
+
case 'add':
|
|
21
|
+
return cmdAdd(args);
|
|
22
|
+
case 'remove':
|
|
23
|
+
return cmdRemove(args);
|
|
24
|
+
case 'bundle':
|
|
25
|
+
return cmdBundle(args);
|
|
26
|
+
case 'list':
|
|
27
|
+
case undefined:
|
|
28
|
+
return cmdList();
|
|
29
|
+
default:
|
|
30
|
+
fmt.cancel(`Unknown subcommand: ${subcommand}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ────────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
// aw integrations list
|
|
36
|
+
// ────────────────────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
async function cmdList() {
|
|
39
|
+
fmt.banner('Integrations', {
|
|
40
|
+
icon: '🔗',
|
|
41
|
+
subtitle: ' Available tools and MCP servers',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const installed = getInstalledList();
|
|
45
|
+
const localIntegrations = listLocalIntegrations(homedir(), { env: process.env });
|
|
46
|
+
|
|
47
|
+
// Group by type
|
|
48
|
+
const plugins = Object.entries(INTEGRATIONS).filter(
|
|
49
|
+
([, i]) => i.type === 'plugin'
|
|
50
|
+
);
|
|
51
|
+
const remoteRcps = Object.entries(INTEGRATIONS).filter(
|
|
52
|
+
([, i]) => i.type === 'remote-mcp'
|
|
53
|
+
);
|
|
54
|
+
const universalInstallers = Object.entries(INTEGRATIONS).filter(
|
|
55
|
+
([, i]) => i.type === 'universal-installer'
|
|
56
|
+
);
|
|
57
|
+
const pythonClis = Object.entries(INTEGRATIONS).filter(
|
|
58
|
+
([, i]) => i.type === 'python-cli'
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const typeIcon = (type) =>
|
|
62
|
+
type === 'plugin' ? '🔌' : type === 'remote-mcp' ? '🌐' : type === 'universal-installer' ? '🪨' : '⚙️';
|
|
63
|
+
|
|
64
|
+
// Installed section
|
|
65
|
+
if (installed.length > 0) {
|
|
66
|
+
fmt.logMessage(`\n${chalk.bold.underline('Installed')}`);
|
|
67
|
+
for (const key of installed) {
|
|
68
|
+
const integration = INTEGRATIONS[key];
|
|
69
|
+
if (!integration) continue;
|
|
70
|
+
fmt.logSuccess(` ${typeIcon(integration.type)} ${integration.label}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Local Integrations
|
|
75
|
+
if (localIntegrations.length > 0) {
|
|
76
|
+
fmt.logMessage(`\n${chalk.bold.underline('Local Integrations')}`);
|
|
77
|
+
for (const integration of localIntegrations) {
|
|
78
|
+
fmt.logMessage(
|
|
79
|
+
` ⚙️ ${integration.name.padEnd(25)} — ${integration.summary}`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Available Plugins
|
|
85
|
+
fmt.logMessage(`\n${chalk.bold.underline('Available Plugins')}`);
|
|
86
|
+
for (const [key, integration] of plugins) {
|
|
87
|
+
if (!installed.includes(key)) {
|
|
88
|
+
fmt.logMessage(
|
|
89
|
+
` 🔌 ${integration.label.padEnd(25)} — ${integration.description}`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Available Remote MCPs
|
|
95
|
+
fmt.logMessage(`\n${chalk.bold.underline('Available Remote MCPs')}`);
|
|
96
|
+
for (const [key, integration] of remoteRcps) {
|
|
97
|
+
if (!installed.includes(key)) {
|
|
98
|
+
fmt.logMessage(
|
|
99
|
+
` 🌐 ${integration.label.padEnd(25)} — ${integration.description}`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Universal Installers
|
|
105
|
+
if (universalInstallers.length > 0) {
|
|
106
|
+
fmt.logMessage(`\n${chalk.bold.underline('Universal Tools')}`);
|
|
107
|
+
for (const [key, integration] of universalInstallers) {
|
|
108
|
+
if (!installed.includes(key)) {
|
|
109
|
+
fmt.logMessage(
|
|
110
|
+
` 🪨 ${integration.label.padEnd(25)} — ${integration.description}`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Python CLIs
|
|
117
|
+
if (pythonClis.length > 0) {
|
|
118
|
+
fmt.logMessage(`\n${chalk.bold.underline('Available Python Tools')}`);
|
|
119
|
+
for (const [key, integration] of pythonClis) {
|
|
120
|
+
if (!installed.includes(key)) {
|
|
121
|
+
fmt.logMessage(
|
|
122
|
+
` 🐍 ${integration.label.padEnd(25)} — ${integration.description}`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Bundles
|
|
129
|
+
fmt.logMessage(`\n${chalk.bold.underline('Bundles')}`);
|
|
130
|
+
for (const [bundleKey, bundle] of Object.entries(BUNDLES)) {
|
|
131
|
+
fmt.logMessage(
|
|
132
|
+
` 📦 ${bundle.label.padEnd(25)} — ${bundle.description}`
|
|
133
|
+
);
|
|
134
|
+
fmt.logMessage(
|
|
135
|
+
` Includes: ${bundle.includes.map((k) => INTEGRATIONS[k].label).join(', ')}`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
fmt.logMessage(`\n${chalk.dim('Commands:')}`);
|
|
140
|
+
fmt.logMessage(` aw integrations add <key> Install a specific tool`);
|
|
141
|
+
fmt.logMessage(` aw integrations remove <key> Remove a tool`);
|
|
142
|
+
fmt.logMessage(` aw integrations bundle <name> Install a preset bundle`);
|
|
143
|
+
fmt.logMessage(` aw integration add <name> Configure a local integration`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ────────────────────────────────────────────────────────────────────────────────
|
|
147
|
+
// aw integrations add <key>
|
|
148
|
+
// ────────────────────────────────────────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
async function cmdAdd(args) {
|
|
151
|
+
const key = args._positional[1];
|
|
152
|
+
|
|
153
|
+
if (!key) {
|
|
154
|
+
fmt.cancel('Usage: aw integrations add <key>');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!INTEGRATIONS[key]) {
|
|
158
|
+
// Suggest similar keys
|
|
159
|
+
const available = Object.keys(INTEGRATIONS);
|
|
160
|
+
fmt.cancel(
|
|
161
|
+
[
|
|
162
|
+
`Unknown integration: ${chalk.red(key)}`,
|
|
163
|
+
'',
|
|
164
|
+
`Available: ${available.join(', ')}`,
|
|
165
|
+
].join('\n')
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const integration = INTEGRATIONS[key];
|
|
170
|
+
fmt.intro(`Installing ${integration.label}`);
|
|
171
|
+
|
|
172
|
+
const success = await installIntegration(key, { silent: false });
|
|
173
|
+
|
|
174
|
+
if (success) {
|
|
175
|
+
fmt.outro(`✓ ${integration.label} installed successfully`);
|
|
176
|
+
} else {
|
|
177
|
+
fmt.cancel(`Failed to install ${integration.label}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ────────────────────────────────────────────────────────────────────────────────
|
|
182
|
+
// aw integrations remove <key>
|
|
183
|
+
// ────────────────────────────────────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
async function cmdRemove(args) {
|
|
186
|
+
const key = args._positional[1];
|
|
187
|
+
|
|
188
|
+
if (!key) {
|
|
189
|
+
fmt.cancel('Usage: aw integrations remove <key>');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!INTEGRATIONS[key]) {
|
|
193
|
+
const available = Object.keys(INTEGRATIONS);
|
|
194
|
+
fmt.cancel(
|
|
195
|
+
[
|
|
196
|
+
`Unknown integration: ${chalk.red(key)}`,
|
|
197
|
+
'',
|
|
198
|
+
`Available: ${available.join(', ')}`,
|
|
199
|
+
].join('\n')
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const integration = INTEGRATIONS[key];
|
|
204
|
+
fmt.intro(`Removing ${integration.label}`);
|
|
205
|
+
|
|
206
|
+
const success = await removeIntegration(key, { silent: false });
|
|
207
|
+
|
|
208
|
+
if (success) {
|
|
209
|
+
fmt.outro(`✓ ${integration.label} removed successfully`);
|
|
210
|
+
} else {
|
|
211
|
+
fmt.cancel(`Failed to remove ${integration.label}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ────────────────────────────────────────────────────────────────────────────────
|
|
216
|
+
// aw integrations bundle <bundleName>
|
|
217
|
+
// ────────────────────────────────────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
async function cmdBundle(args) {
|
|
220
|
+
const bundleName = args._positional[1];
|
|
221
|
+
|
|
222
|
+
if (!bundleName) {
|
|
223
|
+
fmt.cancel('Usage: aw integrations bundle <name>');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!BUNDLES[bundleName]) {
|
|
227
|
+
const available = Object.keys(BUNDLES);
|
|
228
|
+
fmt.cancel(
|
|
229
|
+
[
|
|
230
|
+
`Unknown bundle: ${chalk.red(bundleName)}`,
|
|
231
|
+
'',
|
|
232
|
+
`Available: ${available.join(', ')}`,
|
|
233
|
+
].join('\n')
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const bundle = BUNDLES[bundleName];
|
|
238
|
+
fmt.intro(`Installing bundle: ${bundle.label}`);
|
|
239
|
+
|
|
240
|
+
fmt.logMessage(`${bundle.description}\n`);
|
|
241
|
+
fmt.logMessage(`Includes:`);
|
|
242
|
+
for (const key of bundle.includes) {
|
|
243
|
+
const integration = INTEGRATIONS[key];
|
|
244
|
+
fmt.logMessage(` • ${integration.label}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const confirm = await p.default.confirm({
|
|
248
|
+
message: `Continue installing ${bundle.includes.length} tool(s)?`,
|
|
249
|
+
initialValue: true,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
if (p.default.isCancel(confirm) || !confirm) {
|
|
253
|
+
fmt.cancel('Cancelled');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
fmt.logMessage('');
|
|
257
|
+
|
|
258
|
+
// Install all
|
|
259
|
+
let successCount = 0;
|
|
260
|
+
for (const key of bundle.includes) {
|
|
261
|
+
const success = await installIntegration(key, { silent: false });
|
|
262
|
+
if (success) successCount++;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
fmt.outro(
|
|
266
|
+
`✓ Bundle installation complete (${successCount}/${bundle.includes.length} installed)`
|
|
267
|
+
);
|
|
268
|
+
}
|
package/commands/mcp.mjs
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
|
|
3
|
+
import * as fmt from '../fmt.mjs';
|
|
4
|
+
import { chalk } from '../fmt.mjs';
|
|
5
|
+
import {
|
|
6
|
+
getMcpStatus,
|
|
7
|
+
removeMcpConfig,
|
|
8
|
+
saveMcpPreferences,
|
|
9
|
+
setupMcp,
|
|
10
|
+
} from '../mcp.mjs';
|
|
11
|
+
|
|
12
|
+
const HOME = homedir();
|
|
13
|
+
|
|
14
|
+
function isTruthyEnv(value) {
|
|
15
|
+
return ['1', 'true', 'yes', 'on'].includes(String(value || '').trim().toLowerCase());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function mcpCommand(args) {
|
|
19
|
+
const action = String(args._positional?.[0] || 'status').toLowerCase();
|
|
20
|
+
|
|
21
|
+
if (!['status', 'enable', 'disable'].includes(action)) {
|
|
22
|
+
fmt.cancel(`Unknown MCP action: ${action}. Use: aw mcp status|enable|disable`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
fmt.intro(`aw mcp ${action}`);
|
|
27
|
+
|
|
28
|
+
if (action === 'status') {
|
|
29
|
+
return renderStatus();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (action === 'disable') {
|
|
33
|
+
saveMcpPreferences('disabled', HOME);
|
|
34
|
+
const removed = removeMcpConfig();
|
|
35
|
+
const status = getMcpStatus(HOME);
|
|
36
|
+
fmt.outro([
|
|
37
|
+
'⟁ MCP disabled',
|
|
38
|
+
'',
|
|
39
|
+
` ${chalk.green('✓')} Preference saved: ${chalk.dim(status.preferencesPath.replace(`${HOME}/`, '~/'))}`,
|
|
40
|
+
` ${chalk.green('✓')} Removed AW-managed MCP server from ${removed} config file${removed === 1 ? '' : 's'}`,
|
|
41
|
+
].join('\n'));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
saveMcpPreferences('enabled', HOME);
|
|
46
|
+
const silent = !!args['--silent'] || isTruthyEnv(process.env.CI);
|
|
47
|
+
const updatedFiles = await setupMcp(HOME, null, { silent });
|
|
48
|
+
const status = getMcpStatus(HOME);
|
|
49
|
+
if (status.effectiveMode === 'disabled') {
|
|
50
|
+
fmt.outro([
|
|
51
|
+
'⟁ MCP preference enabled',
|
|
52
|
+
'',
|
|
53
|
+
` ${chalk.green('✓')} Preference saved: ${chalk.dim(status.preferencesPath.replace(`${HOME}/`, '~/'))}`,
|
|
54
|
+
` ${chalk.yellow('!')} ${status.envDisableMcpName}=1 override is still active`,
|
|
55
|
+
].join('\n'));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
fmt.outro([
|
|
59
|
+
'⟁ MCP enabled',
|
|
60
|
+
'',
|
|
61
|
+
` ${chalk.green('✓')} Preference saved: ${chalk.dim(status.preferencesPath.replace(`${HOME}/`, '~/'))}`,
|
|
62
|
+
updatedFiles.length > 0
|
|
63
|
+
? ` ${chalk.green('✓')} Updated ${updatedFiles.length} MCP config file${updatedFiles.length === 1 ? '' : 's'}`
|
|
64
|
+
: ` ${chalk.dim('Note:')} MCP config files already up to date`,
|
|
65
|
+
].join('\n'));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function renderStatus() {
|
|
69
|
+
const status = getMcpStatus(HOME);
|
|
70
|
+
const modeLabel = status.envDisableMcp
|
|
71
|
+
? `${status.effectiveMode} (${status.envDisableMcpName}=1 override; saved preference: ${status.mode})`
|
|
72
|
+
: status.mode;
|
|
73
|
+
|
|
74
|
+
fmt.note([
|
|
75
|
+
`${chalk.dim('mode:')} ${modeLabel}`,
|
|
76
|
+
`${chalk.dim('prefs:')} ${status.preferencesPath.replace(`${HOME}/`, '~/')}`,
|
|
77
|
+
`${chalk.dim('claude MCP:')} ${formatHealth(status.claude)}`,
|
|
78
|
+
`${chalk.dim('cursor MCP:')} ${formatHealth(status.cursor)}`,
|
|
79
|
+
`${chalk.dim('codex MCP:')} ${formatHealth(status.codex)}`,
|
|
80
|
+
`${chalk.dim('aw-ecc Codex MCP source:')} ${formatHealth(status.eccCodex)}`,
|
|
81
|
+
].join('\n'), 'MCP');
|
|
82
|
+
|
|
83
|
+
fmt.outro('⟁ MCP status complete');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function formatHealth(health) {
|
|
87
|
+
if (!health.present) return 'disabled';
|
|
88
|
+
if (health.url && health.authorization) return 'enabled';
|
|
89
|
+
return `incomplete (url=${health.url}, authorization=${health.authorization})`;
|
|
90
|
+
}
|
package/commands/nuke.mjs
CHANGED
|
@@ -366,7 +366,7 @@ export async function nukeCommand(args) {
|
|
|
366
366
|
'',
|
|
367
367
|
` ${chalk.green('✓')} Generated files cleaned`,
|
|
368
368
|
` ${chalk.green('✓')} IDE symlinks cleaned`,
|
|
369
|
-
` ${chalk.green('✓')} MCP config removed (ghl-ai and context-mode)`,
|
|
369
|
+
` ${chalk.green('✓')} MCP config removed (ghl-ai and AW-managed context-mode entries)`,
|
|
370
370
|
` ${chalk.green('✓')} aw-ecc engine removed`,
|
|
371
371
|
` ${chalk.green('✓')} Project worktrees removed`,
|
|
372
372
|
` ${chalk.green('✓')} Project symlinks cleaned`,
|
package/commands/pull.mjs
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
REGISTRY_DIR,
|
|
28
28
|
REGISTRY_URL,
|
|
29
29
|
DOCS_SOURCE_DIR,
|
|
30
|
+
AW_DOCS_DIR,
|
|
30
31
|
RULES_SOURCE_DIR,
|
|
31
32
|
RULES_RUNTIME_DIR,
|
|
32
33
|
} from '../constants.mjs';
|
|
@@ -92,7 +93,7 @@ export async function pullCommand(args) {
|
|
|
92
93
|
// Ensure platform pulls also fetch docs and rules on older installs that
|
|
93
94
|
// pre-date the new sparse-checkout paths.
|
|
94
95
|
if (input === 'platform') {
|
|
95
|
-
addToSparseCheckout(AW_HOME, [`.aw_registry/platform`, DOCS_SOURCE_DIR, RULES_SOURCE_DIR]);
|
|
96
|
+
addToSparseCheckout(AW_HOME, [`.aw_registry/platform`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR]);
|
|
96
97
|
if (!cfg.include.includes('platform')) {
|
|
97
98
|
config.addPattern(GLOBAL_AW_DIR, 'platform');
|
|
98
99
|
}
|
|
@@ -110,7 +111,7 @@ export async function pullCommand(args) {
|
|
|
110
111
|
const label = input.split('/').pop();
|
|
111
112
|
if (!cfg.include.includes(input)) {
|
|
112
113
|
log.logStep(`Adding ${chalk.cyan(label)} to sparse checkout...`);
|
|
113
|
-
addToSparseCheckout(AW_HOME, [sparsePath, DOCS_SOURCE_DIR]);
|
|
114
|
+
addToSparseCheckout(AW_HOME, [sparsePath, DOCS_SOURCE_DIR, AW_DOCS_DIR]);
|
|
114
115
|
config.addPattern(GLOBAL_AW_DIR, input);
|
|
115
116
|
addedInput = input;
|
|
116
117
|
addedSparsePath = sparsePath;
|