@ghl-ai/aw 0.1.47-beta.1 → 0.1.47-beta.11

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/cli.mjs CHANGED
@@ -21,6 +21,7 @@ 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),
@@ -97,6 +98,7 @@ function printHelp() {
97
98
 
98
99
  sec('Upload'),
99
100
  cmd('aw push', 'Push all modified files (creates one PR)'),
101
+ cmd('aw push --aw-docs-only', 'Publish generated .aw_docs companions and print share links'),
100
102
  cmd('aw push <path>', 'Push file, folder, or namespace to registry'),
101
103
  cmd('aw push-rules [path]', 'Push platform rules to platform-docs'),
102
104
  cmd('aw push --dry-run [path]', 'Preview what would be pushed'),
@@ -109,8 +111,11 @@ function printHelp() {
109
111
  cmd('aw doctor', 'Run a health check for routing, MCP, plugin, and AW ECC surfaces'),
110
112
  cmd('aw link', 'Link current project as a git worktree (wires IDE symlinks)'),
111
113
  cmd('aw routing status', 'Show global AW session-routing mode for Claude/Cursor/Codex'),
112
- cmd('aw routing disable', 'Disable automatic AW session routing globally'),
113
- cmd('aw routing enable', 'Re-enable automatic AW session routing globally'),
114
+ cmd('aw routing disable', 'Disable default AW router/rules injection globally'),
115
+ cmd('aw routing enable', 'Re-enable default AW router/rules injection globally'),
116
+ cmd('aw mcp status', 'Show global AW MCP mode for Claude/Cursor/Codex'),
117
+ cmd('aw mcp disable', 'Remove AW-managed MCP server across Claude/Cursor/Codex'),
118
+ cmd('aw mcp enable', 'Restore AW-managed MCP server across Claude/Cursor/Codex'),
114
119
  cmd('aw integrations', 'Manage third-party integrations (Codex, Caveman, etc)'),
115
120
  cmd('aw integrations add <key>', 'Install a specific tool (e.g. codex, caveman)'),
116
121
  cmd('aw integrations remove <key>', 'Remove a tool'),
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.
@@ -380,7 +381,12 @@ export async function c4Command(rawArgs, overrides = {}) {
380
381
  }
381
382
 
382
383
  // Step 11 — MCP register.
383
- safe('registerGhlAiMcp', () => c4.registerGhlAiMcp(harness, home, token), writer);
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 = await safeAsync(
407
- 'probeMcpServer',
408
- () => c4.probeMcpServer({ url: c4.MCP_URL_DEFAULT, token }),
409
- writer,
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 });
@@ -209,7 +209,7 @@ function directoryContainsGeneratedRuleFiles(dirPath, extension) {
209
209
  try {
210
210
  return readdirSync(dirPath, { withFileTypes: true })
211
211
  .filter(entry => entry.isFile() && entry.name.endsWith(extension))
212
- .some(entry => readText(join(dirPath, entry.name)).startsWith(GENERATED_RULE_HEADER));
212
+ .some(entry => readText(join(dirPath, entry.name)).includes(GENERATED_RULE_HEADER));
213
213
  } catch {
214
214
  return false;
215
215
  }
@@ -500,7 +500,7 @@ function listGeneratedRuleFiles(dirPath, extension) {
500
500
  return readdirSync(dirPath, { withFileTypes: true })
501
501
  .filter(entry => entry.isFile() && entry.name.endsWith(extension))
502
502
  .map(entry => join(dirPath, entry.name))
503
- .filter(filePath => readText(filePath).startsWith(GENERATED_RULE_HEADER));
503
+ .filter(filePath => readText(filePath).includes(GENERATED_RULE_HEADER));
504
504
  } catch {
505
505
  return [];
506
506
  }
package/commands/init.mjs CHANGED
@@ -48,7 +48,7 @@ import {
48
48
  syncWorktreeSparseCheckout,
49
49
  findNearestWorktree,
50
50
  } from '../git.mjs';
51
- import { REGISTRY_DIR, REGISTRY_REPO, REGISTRY_URL, RULES_SOURCE_DIR, RULES_RUNTIME_DIR } from '../constants.mjs';
51
+ import { REGISTRY_DIR, REGISTRY_REPO, REGISTRY_URL, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR, RULES_RUNTIME_DIR } from '../constants.mjs';
52
52
  import { syncFileTree } from '../file-tree.mjs';
53
53
 
54
54
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -110,8 +110,8 @@ function scaffoldNamespace(awHome, folderName) {
110
110
 
111
111
  // ── Ensure ~/.aw/.git/info/exclude has the whitelist block ─────────────
112
112
  //
113
- // Strategy: only .aw_registry/, .aw_rules/, content/ are tracked — everything
114
- // else at the top level of ~/.aw/ is local-only (telemetry/, hooks/, logs,
113
+ // Strategy: only .aw_registry/, .aw_rules/, content/, and .aw_docs/ are
114
+ // tracked — everything else at the top level of ~/.aw/ is local-only (telemetry/, hooks/, logs,
115
115
  // .DS_Store, etc.). We write to .git/info/exclude (not tracked .gitignore)
116
116
  // so upstream pulls never conflict.
117
117
 
@@ -125,6 +125,7 @@ const AW_MANAGED_BLOCK = [
125
125
  '!/.aw_registry',
126
126
  '!/.aw_rules',
127
127
  '!/content',
128
+ '!/.aw_docs',
128
129
  '',
129
130
  '# Nested local state within whitelisted dirs',
130
131
  '/.aw_registry/.sync-config.json',
@@ -325,7 +326,7 @@ export async function initCommand(args) {
325
326
  const isNewSubTeam = folderName && cfg && !cfg.include.includes(folderName);
326
327
  if (isNewSubTeam) {
327
328
  if (!silent) fmt.logStep(`Adding sub-team ${chalk.cyan(folderName)}...`);
328
- const newSparsePaths = [`.aw_registry/${folderName}`, 'content', RULES_SOURCE_DIR];
329
+ const newSparsePaths = [`.aw_registry/${folderName}`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR];
329
330
  addToSparseCheckout(AW_HOME, newSparsePaths);
330
331
  config.addPattern(GLOBAL_AW_DIR, folderName);
331
332
  scaffoldNamespace(AW_HOME, folderName);
@@ -464,7 +465,7 @@ export async function initCommand(args) {
464
465
  }
465
466
 
466
467
  // Determine sparse paths
467
- const sparsePaths = [`.aw_registry/platform`, `content`, RULES_SOURCE_DIR, `.aw_registry/AW-PROTOCOL.md`, `CODEOWNERS`];
468
+ const sparsePaths = [`.aw_registry/platform`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR, `.aw_registry/AW-PROTOCOL.md`, `CODEOWNERS`];
468
469
  if (folderName) {
469
470
  sparsePaths.push(`.aw_registry/${folderName}`);
470
471
  }
@@ -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
@@ -364,7 +364,7 @@ export async function nukeCommand(args) {
364
364
  '',
365
365
  ` ${chalk.green('✓')} Generated files cleaned`,
366
366
  ` ${chalk.green('✓')} IDE symlinks cleaned`,
367
- ` ${chalk.green('✓')} MCP config removed (ghl-ai from ~/.claude.json + ~/.cursor/mcp.json)`,
367
+ ` ${chalk.green('✓')} MCP config removed (ghl-ai from Claude, Cursor, and Codex configs)`,
368
368
  ` ${chalk.green('✓')} aw-ecc engine removed`,
369
369
  ` ${chalk.green('✓')} Project worktrees removed`,
370
370
  ` ${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;