@ghl-ai/aw 0.1.50-beta.1 → 0.1.51

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.
@@ -12,13 +12,12 @@
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
- # Override to pin a specific @ghl-ai/aw@0.1.x for reproducible
17
- # CI runs.
15
+ # AW_PACKAGE npm spec to install. Defaults to @ghl-ai/aw@latest. Override
16
+ # with @ghl-ai/aw@beta to opt into pre-release builds, or pin to
17
+ # @ghl-ai/aw@0.1.x for reproducible CI runs.
18
18
  set -Eeuo pipefail
19
19
 
20
- # GitHub auth is resolved inside `aw c4` so GITHUB_PAT, GITHUB_TOKEN, and
21
- # no-token diagnostics stay centralized in the CLI preflight.
20
+ : "${GITHUB_PAT:?ERROR: Set GITHUB_PAT in your harness secrets UI before running aw c4}"
22
21
 
23
22
  # Ensure npm is on PATH. Cursor Cloud's install shell is non-interactive — nvm
24
23
  # is not auto-sourced, and Node may not be pre-installed at all. Walk common
package/cli.mjs CHANGED
@@ -21,13 +21,13 @@ 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),
25
24
  search: () => import('./commands/search.mjs').then(m => m.searchCommand),
26
25
  link: () => import('./commands/link-project.mjs').then(m => m.linkProjectCommand),
27
26
  nuke: () => import('./commands/nuke.mjs').then(m => m.nukeCommand),
28
27
  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),
29
30
  telemetry: () => import('./commands/telemetry.mjs').then(m => m.telemetryCommand),
30
- integrations: () => import('./commands/integrations.mjs').then(m => m.integrationsCommand),
31
31
  'slack-sim': () => import('./commands/slack-sim.mjs').then(m => m.slackSimCommand),
32
32
  c4: () => import('./commands/c4.mjs').then(m => m.c4Command),
33
33
  'init-repo': () => import('./commands/init-repo.mjs').then(m => m.initRepoCommand),
@@ -87,13 +87,10 @@ function printHelp() {
87
87
  const sec = (title) => `\n ${chalk.bold.underline(title)}`;
88
88
  const help = [
89
89
  sec('Setup'),
90
- cmd('aw init', 'Initialize workspace (auto-installs suggested integrations)'),
90
+ cmd('aw init', 'Initialize workspace (platform/ only)'),
91
91
  cmd('aw init --namespace <team/sub-team>', 'Add a team namespace (optional)'),
92
- cmd('aw init skills <path...>', 'Initialize/link only specific skill folders'),
93
- cmd('aw init --no-integrations', 'Skip integration setup (Codex, Caveman, Graphify, etc)'),
94
92
  ` ${chalk.dim('Teams: platform, revex, mobile, commerce, leadgen, crm, marketplace, ai')}`,
95
93
  ` ${chalk.dim('Example: aw init --namespace revex/courses')}`,
96
- ` ${chalk.dim('Example: aw init skills platform/core/skills/pr-review')}`,
97
94
  cmd('aw init-repo', 'Scaffold cloud-bootstrap files (idempotent, --dry-run/--force/--diff)'),
98
95
 
99
96
  sec('Download'),
@@ -117,15 +114,12 @@ function printHelp() {
117
114
  cmd('aw doctor', 'Run a health check for routing, MCP, plugin, and AW ECC surfaces'),
118
115
  cmd('aw link', 'Link current project as a git worktree (wires IDE symlinks)'),
119
116
  cmd('aw routing status', 'Show global AW session-routing mode for Claude/Cursor/Codex'),
120
- cmd('aw routing disable', 'Disable default AW router/rules injection globally'),
121
- cmd('aw routing enable', 'Re-enable default AW router/rules injection globally'),
122
- cmd('aw mcp status', 'Show global AW MCP mode for Claude/Cursor/Codex'),
123
- cmd('aw mcp disable', 'Remove AW-managed MCP server across Claude/Cursor/Codex'),
124
- cmd('aw mcp enable', 'Restore AW-managed MCP server across Claude/Cursor/Codex'),
125
- cmd('aw integrations', 'Manage third-party integrations (Codex, Caveman, etc)'),
126
- cmd('aw integrations add <key>', 'Install a specific tool (e.g. codex, caveman)'),
127
- cmd('aw integrations remove <key>', 'Remove a tool'),
128
- cmd('aw integrations bundle <name>', 'Install a preset bundle'),
117
+ cmd('aw routing disable', 'Disable automatic AW session routing globally'),
118
+ cmd('aw routing enable', 'Re-enable automatic AW session routing globally'),
119
+ cmd('aw integration list', 'List addable third-party integrations'),
120
+ cmd('aw integration add <name>', 'Add a specific integration, e.g. context-mode'),
121
+ cmd('aw integration status <name>', 'Show integration health and configured surfaces'),
122
+ cmd('aw integration remove <name>', 'Remove a specific integration'),
129
123
  cmd('aw drop <path>', 'Stop syncing or delete local content'),
130
124
  cmd('aw nuke', 'Remove entire .aw_registry/ & start fresh'),
131
125
  cmd('aw daemon install', 'Auto-pull on a schedule (macOS launchd / Linux cron)'),
@@ -154,7 +148,6 @@ function printHelp() {
154
148
  cmd('aw pull <team>/agents', 'All agents from a team'),
155
149
  cmd('aw pull <team>/agents/<name>', 'One specific agent'),
156
150
  cmd('aw pull <team>/skills/<name>', 'One specific skill folder'),
157
- cmd('aw init skills <team>/<path>/skills/<name>', 'Install and link only selected skill symlinks'),
158
151
  '',
159
152
  ` ${chalk.dim('# Push your local changes to registry')}`,
160
153
  cmd('aw push', 'Push all modified files (one PR)'),
package/commands/c4.mjs CHANGED
@@ -24,7 +24,6 @@ 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';
28
27
 
29
28
  /* ─────────────────────────────────────────────────────────────────────────
30
29
  * Constants — referenced by self-tests.
@@ -350,10 +349,10 @@ export async function c4Command(rawArgs, overrides = {}) {
350
349
  return exit(0);
351
350
  }
352
351
 
353
- // Step 6 — npm install -g @ghl-ai/aw@latest.
354
- const npmRes = spawnSync('npm', ['install', '-g', '@ghl-ai/aw@latest'], { stdio: 'pipe' });
352
+ // Step 6 — npm install -g @ghl-ai/aw.
353
+ const npmRes = spawnSync('npm', ['install', '-g', '@ghl-ai/aw'], { stdio: 'pipe' });
355
354
  if (npmRes && npmRes.status !== 0) {
356
- writer.stderr('[aw-c4] npm install -g @ghl-ai/aw@latest failed (non-fatal); using existing aw if present\n');
355
+ writer.stderr('[aw-c4] npm install -g @ghl-ai/aw failed (non-fatal); using existing aw if present\n');
357
356
  }
358
357
 
359
358
  // Step 7 — aw init --silent.
@@ -381,12 +380,7 @@ export async function c4Command(rawArgs, overrides = {}) {
381
380
  }
382
381
 
383
382
  // Step 11 — MCP register.
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
- }
383
+ safe('registerGhlAiMcp', () => c4.registerGhlAiMcp(harness, home, token), writer);
390
384
 
391
385
  // Step 12 — slash command surface.
392
386
  safe('ensureCommandSurface', () => c4.ensureCommandSurface({ harness, home, eccHome }), writer);
@@ -409,13 +403,11 @@ export async function c4Command(rawArgs, overrides = {}) {
409
403
  safe('ensureRepoLocalIgnore', () => c4.ensureRepoLocalIgnore({ cwd, harness }), writer);
410
404
 
411
405
  // Step 15 — MCP smoke probe (best-effort).
412
- const mcpProbe = mcpEnabled
413
- ? await safeAsync(
414
- 'probeMcpServer',
415
- () => c4.probeMcpServer({ url: c4.MCP_URL_DEFAULT, token }),
416
- writer,
417
- )
418
- : null;
406
+ const mcpProbe = await safeAsync(
407
+ 'probeMcpServer',
408
+ () => c4.probeMcpServer({ url: c4.MCP_URL_DEFAULT, token }),
409
+ writer,
410
+ );
419
411
 
420
412
  // Step 16 — self-tests.
421
413
  const self = runSelfTests({ harness, c4, home, awHome, eccHome });
@@ -7,6 +7,7 @@ import * as fmt from '../fmt.mjs';
7
7
  import { chalk } from '../fmt.mjs';
8
8
  import { AW_ECC_TAG } from '../ecc.mjs';
9
9
  import { getSupportedHarnessPhaseEntries } from '../hook-manifest.mjs';
10
+ import { getContextModeDoctorStatus } from '../integrations/context-mode.mjs';
10
11
  import { getStartupStatus, hasLegacyRepoStartupDefaults } from '../startup.mjs';
11
12
 
12
13
  const STATUS_PRIORITY = { pass: 0, warn: 1, fail: 2 };
@@ -209,7 +210,7 @@ function directoryContainsGeneratedRuleFiles(dirPath, extension) {
209
210
  try {
210
211
  return readdirSync(dirPath, { withFileTypes: true })
211
212
  .filter(entry => entry.isFile() && entry.name.endsWith(extension))
212
- .some(entry => readText(join(dirPath, entry.name)).includes(GENERATED_RULE_HEADER));
213
+ .some(entry => readText(join(dirPath, entry.name)).startsWith(GENERATED_RULE_HEADER));
213
214
  } catch {
214
215
  return false;
215
216
  }
@@ -500,7 +501,7 @@ function listGeneratedRuleFiles(dirPath, extension) {
500
501
  return readdirSync(dirPath, { withFileTypes: true })
501
502
  .filter(entry => entry.isFile() && entry.name.endsWith(extension))
502
503
  .map(entry => join(dirPath, entry.name))
503
- .filter(filePath => readText(filePath).includes(GENERATED_RULE_HEADER));
504
+ .filter(filePath => readText(filePath).startsWith(GENERATED_RULE_HEADER));
504
505
  } catch {
505
506
  return [];
506
507
  }
@@ -1026,6 +1027,8 @@ function buildDoctorChecks(homeDir, cwd) {
1026
1027
  ),
1027
1028
  );
1028
1029
 
1030
+ checks.push(...getContextModeDoctorStatus(homeDir, { env: process.env }));
1031
+
1029
1032
  const eccDir = join(homeDir, '.aw-ecc');
1030
1033
  const eccSyncScript = join(eccDir, 'scripts', 'sync-ecc-to-codex.sh');
1031
1034
  if (!existsSync(eccDir)) {
package/commands/init.mjs CHANGED
@@ -23,9 +23,10 @@ import * as p from '@clack/prompts';
23
23
  import * as config from '../config.mjs';
24
24
  import * as fmt from '../fmt.mjs';
25
25
  import { chalk, setSilent } from '../fmt.mjs';
26
- import { linkWorkspace, linkSkills, normalizeSkillTarget } from '../link.mjs';
26
+ import { linkWorkspace } from '../link.mjs';
27
27
  import { generateCommands, copyInstructions, initAwDocs, syncHomeHarnessInstructions } from '../integrate.mjs';
28
28
  import { setupMcp } from '../mcp.mjs';
29
+ import { ensureContextModeIntegration, isContextModeRequested } from '../integrations/context-mode.mjs';
29
30
  import { applyStoredStartupPreferences, ensureAwRuntimeHook } from '../startup.mjs';
30
31
  import { installLocalCommitHook } from '../hooks.mjs';
31
32
  import { autoUpdate, promptUpdate } from '../update.mjs';
@@ -34,13 +35,11 @@ import { loadConfig as ensureTelemetryConfig } from '../telemetry.mjs';
34
35
  import { installAwEcc, AW_ECC_TAG } from '../ecc.mjs';
35
36
  import { removeWorkspaceHookDefaults } from '../codex.mjs';
36
37
  import { readHookManifest, pruneStaleHooks, writeHookManifest } from '../hook-cleanup.mjs';
37
- import { promptAndInstall, autoInstallIntegrations } from '../integrations.mjs';
38
38
  import {
39
39
  initPersistentClone,
40
40
  isValidClone,
41
41
  fetchAndMerge,
42
42
  addToSparseCheckout,
43
- setSparseCheckout,
44
43
  addProjectWorktree,
45
44
  isWorktree,
46
45
  includeToSparsePaths,
@@ -49,7 +48,7 @@ import {
49
48
  syncWorktreeSparseCheckout,
50
49
  findNearestWorktree,
51
50
  } from '../git.mjs';
52
- import { REGISTRY_DIR, REGISTRY_REPO, REGISTRY_URL, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR, RULES_RUNTIME_DIR } from '../constants.mjs';
51
+ import { REGISTRY_DIR, REGISTRY_REPO, REGISTRY_URL, RULES_SOURCE_DIR, RULES_RUNTIME_DIR } from '../constants.mjs';
53
52
  import { syncFileTree } from '../file-tree.mjs';
54
53
 
55
54
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -93,6 +92,32 @@ function syncHomeAndProjectInstructions(cwd, namespace) {
93
92
  }
94
93
  }
95
94
 
95
+ function maybeConfigureContextMode(args, { silent = false } = {}) {
96
+ if (!isContextModeRequested(args, process.env)) {
97
+ return null;
98
+ }
99
+
100
+ const result = ensureContextModeIntegration(HOME, {
101
+ env: process.env,
102
+ silent,
103
+ });
104
+
105
+ if (!silent) {
106
+ for (const warning of result.warnings) {
107
+ fmt.logWarn(warning);
108
+ }
109
+ if (result.changedFiles.length > 0) {
110
+ fmt.logStep(`Context Mode configured (${result.changedFiles.length} file${result.changedFiles.length === 1 ? '' : 's'})`);
111
+ } else if (result.warnings.length > 0) {
112
+ fmt.logWarn('Context Mode was requested but not configured');
113
+ } else {
114
+ fmt.logStep('Context Mode already configured');
115
+ }
116
+ }
117
+
118
+ return result;
119
+ }
120
+
96
121
  /**
97
122
  * Create scaffold directories for a new team namespace so the developer
98
123
  * has the standard folder structure ready for agents, skills, commands, and evals.
@@ -109,60 +134,10 @@ function scaffoldNamespace(awHome, folderName) {
109
134
  }
110
135
  }
111
136
 
112
- function getSkillInitSpecs(args) {
113
- const positional = args._positional || [];
114
- const mode = positional[0];
115
- const rawTargets = [];
116
-
117
- if (mode === 'skill' || mode === 'skills') {
118
- rawTargets.push(...positional.slice(1));
119
- }
120
-
121
- if (args['--skill']) {
122
- rawTargets.push(args['--skill']);
123
- }
124
-
125
- if ((mode === 'skill' || mode === 'skills' || args['--skill']) && rawTargets.length === 0) {
126
- fmt.cancel(`Missing skill path.\n\n ${chalk.dim('Example:')} ${chalk.bold('aw init skills platform/core/skills/pr-review')}`);
127
- }
128
-
129
- if (rawTargets.length === 0) return [];
130
-
131
- try {
132
- return [...new Map(rawTargets.map(input => {
133
- const spec = normalizeSkillTarget(input);
134
- return [spec.registryPath, spec];
135
- })).values()];
136
- } catch (e) {
137
- fmt.cancel(e.message);
138
- }
139
- }
140
-
141
- function skillInitSparsePaths(skillSpecs) {
142
- const paths = new Set([
143
- DOCS_SOURCE_DIR,
144
- AW_DOCS_DIR,
145
- RULES_SOURCE_DIR,
146
- `${REGISTRY_DIR}/AW-PROTOCOL.md`,
147
- `CODEOWNERS`,
148
- ]);
149
-
150
- for (const spec of skillSpecs) {
151
- paths.add(`${REGISTRY_DIR}/${spec.registryPath}`);
152
-
153
- for (let i = 0; i <= spec.segments.length; i++) {
154
- const scope = [spec.namespace, ...spec.segments.slice(0, i)].join('/');
155
- paths.add(`${REGISTRY_DIR}/${scope}/references`);
156
- }
157
- }
158
-
159
- return [...paths];
160
- }
161
-
162
137
  // ── Ensure ~/.aw/.git/info/exclude has the whitelist block ─────────────
163
138
  //
164
- // Strategy: only .aw_registry/, .aw_rules/, content/, and .aw_docs/ are
165
- // tracked — everything else at the top level of ~/.aw/ is local-only (telemetry/, hooks/, logs,
139
+ // Strategy: only .aw_registry/, .aw_rules/, content/ are tracked — everything
140
+ // else at the top level of ~/.aw/ is local-only (telemetry/, hooks/, logs,
166
141
  // .DS_Store, etc.). We write to .git/info/exclude (not tracked .gitignore)
167
142
  // so upstream pulls never conflict.
168
143
 
@@ -176,7 +151,6 @@ const AW_MANAGED_BLOCK = [
176
151
  '!/.aw_registry',
177
152
  '!/.aw_rules',
178
153
  '!/content',
179
- '!/.aw_docs',
180
154
  '',
181
155
  '# Nested local state within whitelisted dirs',
182
156
  '/.aw_registry/.sync-config.json',
@@ -265,15 +239,6 @@ export async function initCommand(args) {
265
239
  let namespace = args['--namespace'] || null;
266
240
  let user = args['--user'] || '';
267
241
  const silent = args['--silent'] === true;
268
- const skipIntegrations = args['--no-integrations'] === true;
269
- const skillSpecs = getSkillInitSpecs(args);
270
- const skillTargets = skillSpecs.map(spec => spec.registryPath);
271
- const isSkillInit = skillTargets.length > 0;
272
- const skillNamespace = skillSpecs[0]?.namespace || null;
273
-
274
- if (isSkillInit && namespace) {
275
- fmt.cancel('Use either `aw init skills <path...>` or `aw init --namespace <team/sub-team>`, not both.');
276
- }
277
242
 
278
243
  // In silent mode, suppress ALL fmt output and show a single spinner.
279
244
  // setSilent(true) makes every fmt.* call a no-op — internal functions
@@ -382,21 +347,10 @@ export async function initCommand(args) {
382
347
  if (isGitNative) {
383
348
  const cfg = config.load(GLOBAL_AW_DIR);
384
349
 
385
- const isNewSubTeam = !isSkillInit && folderName && cfg && !cfg.include.includes(folderName);
386
-
387
- if (isSkillInit) {
388
- if (!silent) fmt.logStep('Syncing selected skills...');
389
- setSparseCheckout(AW_HOME, skillInitSparsePaths(skillSpecs));
390
- if (cfg) {
391
- config.save(GLOBAL_AW_DIR, {
392
- ...cfg,
393
- namespace: skillNamespace || cfg.namespace || 'platform',
394
- include: skillTargets,
395
- });
396
- }
397
- } else if (isNewSubTeam) {
350
+ const isNewSubTeam = folderName && cfg && !cfg.include.includes(folderName);
351
+ if (isNewSubTeam) {
398
352
  if (!silent) fmt.logStep(`Adding sub-team ${chalk.cyan(folderName)}...`);
399
- const newSparsePaths = [`.aw_registry/${folderName}`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR];
353
+ const newSparsePaths = [`.aw_registry/${folderName}`, 'content', RULES_SOURCE_DIR];
400
354
  addToSparseCheckout(AW_HOME, newSparsePaths);
401
355
  config.addPattern(GLOBAL_AW_DIR, folderName);
402
356
  scaffoldNamespace(AW_HOME, folderName);
@@ -422,7 +376,6 @@ export async function initCommand(args) {
422
376
 
423
377
  ensureAwGitignore(AW_HOME);
424
378
  const freshCfg = config.load(GLOBAL_AW_DIR);
425
- const activeNamespace = skillNamespace || freshCfg?.namespace || team;
426
379
  syncRulesTargets(HOME);
427
380
  if (cwd !== HOME) {
428
381
  syncRulesTargets(cwd);
@@ -445,8 +398,9 @@ export async function initCommand(args) {
445
398
  await installAwEcc(cwd, { silent });
446
399
 
447
400
  ensureAwRuntimeHook(HOME);
448
- syncHomeAndProjectInstructions(cwd, activeNamespace);
449
- await setupMcp(HOME, activeNamespace, { silent });
401
+ syncHomeAndProjectInstructions(cwd, freshCfg?.namespace || team);
402
+ await setupMcp(HOME, freshCfg?.namespace || team, { silent });
403
+ maybeConfigureContextMode(args, { silent });
450
404
  applyStoredStartupPreferences(HOME);
451
405
  const removedLegacyStartupFiles = cwd !== HOME ? removeWorkspaceHookDefaults(cwd) : [];
452
406
  installGlobalHooks();
@@ -480,22 +434,14 @@ export async function initCommand(args) {
480
434
  if (!silent) fmt.logStep('Wiring IDE symlinks...');
481
435
  const projectRegistryDir = cwd !== HOME ? join(cwd, '.aw', REGISTRY_DIR) : null;
482
436
  const awDirForLinks = (projectRegistryDir && existsSync(projectRegistryDir)) ? projectRegistryDir : null;
483
- const symlinks = isSkillInit
484
- ? linkSkills(HOME, skillTargets, awDirForLinks, { silent: true, exclusive: true })
485
- : linkWorkspace(HOME, awDirForLinks, { silent: true });
486
- const commands = isSkillInit ? 0 : generateCommands(HOME, { silent: true });
437
+ const symlinks = linkWorkspace(HOME, awDirForLinks, { silent: true });
438
+ const commands = generateCommands(HOME, { silent: true });
487
439
  if (cwd !== HOME) installLocalCommitHook(cwd);
488
- if (!silent) fmt.logStep(`IDE wired — ${chalk.bold(symlinks)} ${isSkillInit ? 'targeted symlinks' : 'symlinks'} · ${chalk.bold(commands)} commands`);
440
+ if (!silent) fmt.logStep(`IDE wired — ${chalk.bold(symlinks)} symlinks · ${chalk.bold(commands)} commands`);
489
441
 
490
442
  // Write hook manifest after all hook installation is complete
491
443
  try { writeHookManifest({ eccVersion: AW_ECC_TAG, awVersion: VERSION }); } catch { /* best effort */ }
492
444
 
493
- // Auto-install suggested integrations (Codex, Caveman, Graphify, etc) - unless --no-integrations
494
- let installedIntegrations = [];
495
- if (!silent && !skipIntegrations && !isNewSubTeam) {
496
- installedIntegrations = await autoInstallIntegrations(activeNamespace, { silent });
497
- }
498
-
499
445
  if (silent) {
500
446
  if (silentSpinner) { silentSpinner.stop('Done'); setSilent(false); }
501
447
  autoUpdate(await args._updateCheck);
@@ -504,12 +450,11 @@ export async function initCommand(args) {
504
450
  `⟁ ${isNewSubTeam ? `Sub-team ${chalk.cyan(folderName)} added` : 'Up to date'}`,
505
451
  '',
506
452
  ` ${chalk.green('✓')} Registry synced`,
507
- ` ${chalk.green('✓')} IDE refreshed — ${chalk.bold(symlinks)} ${isSkillInit ? 'targeted symlinks' : 'symlinks'} · ${chalk.bold(commands)} commands`,
453
+ ` ${chalk.green('✓')} IDE refreshed — ${chalk.bold(symlinks)} symlinks · ${chalk.bold(commands)} commands`,
508
454
  removedLegacyStartupFiles.length > 0
509
455
  ? ` ${chalk.green('✓')} Removed ${removedLegacyStartupFiles.length} legacy repo startup file${removedLegacyStartupFiles.length > 1 ? 's' : ''}`
510
456
  : null,
511
457
  cwd !== HOME && isWorktree(join(cwd, '.aw')) ? ` ${chalk.green('✓')} Project linked` : null,
512
- installedIntegrations.length > 0 ? ` ${chalk.green('✓')} Integrations: ${installedIntegrations.join(', ')}` : null,
513
458
  ].filter(Boolean).join('\n'));
514
459
  }
515
460
  return;
@@ -538,17 +483,13 @@ export async function initCommand(args) {
538
483
  }
539
484
 
540
485
  // Determine sparse paths
541
- const sparsePaths = isSkillInit
542
- ? skillInitSparsePaths(skillSpecs)
543
- : [`.aw_registry/platform`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR, `.aw_registry/AW-PROTOCOL.md`, `CODEOWNERS`];
544
- if (!isSkillInit && folderName) {
486
+ const sparsePaths = [`.aw_registry/platform`, `content`, RULES_SOURCE_DIR, `.aw_registry/AW-PROTOCOL.md`, `CODEOWNERS`];
487
+ if (folderName) {
545
488
  sparsePaths.push(`.aw_registry/${folderName}`);
546
489
  }
547
490
 
548
491
  fmt.note([
549
- isSkillInit
550
- ? `${chalk.dim('skills:')} ${skillTargets.join(', ')}`
551
- : (folderName ? `${chalk.dim('namespace:')} ${folderName}` : `${chalk.dim('namespace:')} ${chalk.dim('none')}`),
492
+ folderName ? `${chalk.dim('namespace:')} ${folderName}` : `${chalk.dim('namespace:')} ${chalk.dim('none')}`,
552
493
  user ? `${chalk.dim('user:')} ${user}` : null,
553
494
  `${chalk.dim('version:')} v${VERSION}`,
554
495
  ].filter(Boolean).join('\n'), 'Config');
@@ -588,11 +529,8 @@ export async function initCommand(args) {
588
529
  }
589
530
 
590
531
  // Create sync config — default to 'platform' when no namespace specified
591
- const activeNamespace = skillNamespace || team || 'platform';
592
- const cfg = config.create(GLOBAL_AW_DIR, { namespace: activeNamespace, user });
593
- if (isSkillInit) {
594
- for (const target of skillTargets) config.addPattern(GLOBAL_AW_DIR, target);
595
- } else if (folderName) {
532
+ const cfg = config.create(GLOBAL_AW_DIR, { namespace: team || 'platform', user });
533
+ if (folderName) {
596
534
  config.addPattern(GLOBAL_AW_DIR, folderName);
597
535
  scaffoldNamespace(AW_HOME, folderName);
598
536
  }
@@ -613,9 +551,10 @@ export async function initCommand(args) {
613
551
 
614
552
  // Parallel batch B: post-ECC setup (instructions and MCP are independent)
615
553
  const [, mcpFiles] = await Promise.all([
616
- Promise.resolve(syncHomeAndProjectInstructions(cwd, activeNamespace)),
617
- setupMcp(HOME, activeNamespace, { silent }),
554
+ Promise.resolve(syncHomeAndProjectInstructions(cwd, team)),
555
+ setupMcp(HOME, team, { silent }),
618
556
  ]);
557
+ maybeConfigureContextMode(args, { silent });
619
558
  // applyStoredStartupPreferences reads settings written by ECC — keep after batch B
620
559
  applyStoredStartupPreferences(HOME);
621
560
  const removedLegacyStartupFiles = cwd !== HOME ? removeWorkspaceHookDefaults(cwd) : [];
@@ -652,13 +591,11 @@ export async function initCommand(args) {
652
591
  const awDirForLinks = (projectRegistryDir && existsSync(projectRegistryDir)) ? projectRegistryDir : null;
653
592
  // Parallel batch C: symlinks + commands are independent
654
593
  if (cwd !== HOME) installLocalCommitHook(cwd);
655
- const [symlinks, commands] = isSkillInit
656
- ? [linkSkills(HOME, skillTargets, awDirForLinks, { silent: true, exclusive: true }), 0]
657
- : [
658
- linkWorkspace(HOME, awDirForLinks, { silent: true }),
659
- generateCommands(HOME, { silent: true }),
660
- ];
661
- if (!silent) fmt.logStep(`IDE wired — ${chalk.bold(symlinks)} ${isSkillInit ? 'targeted symlinks' : 'symlinks'} · ${chalk.bold(commands)} commands`);
594
+ const [symlinks, commands] = [
595
+ linkWorkspace(HOME, awDirForLinks, { silent: true }),
596
+ generateCommands(HOME, { silent: true }),
597
+ ];
598
+ if (!silent) fmt.logStep(`IDE wired — ${chalk.bold(symlinks)} symlinks · ${chalk.bold(commands)} commands`);
662
599
 
663
600
  // Write hook manifest after all hook installation is complete
664
601
  try { writeHookManifest({ eccVersion: AW_ECC_TAG, awVersion: VERSION }); } catch { /* best effort */ }
@@ -666,12 +603,6 @@ export async function initCommand(args) {
666
603
  // Ensure telemetry config exists (generates machine_id on first run)
667
604
  ensureTelemetryConfig();
668
605
 
669
- // Auto-install suggested integrations (Codex, Caveman, Graphify, etc) - unless --no-integrations
670
- let installedIntegrations = [];
671
- if (!silent && !skipIntegrations) {
672
- installedIntegrations = await autoInstallIntegrations(activeNamespace, { silent });
673
- }
674
-
675
606
  // Offer to update if a newer version is available
676
607
  if (!silent) await promptUpdate(await args._updateCheck);
677
608
 
@@ -691,12 +622,10 @@ export async function initCommand(args) {
691
622
  hooksInstalled ? ` ${chalk.green('✓')} Git hooks: auto-sync on pull/clone (core.hooksPath)` : null,
692
623
  ` ${chalk.green('✓')} IDE task: auto-sync on workspace open`,
693
624
  cwd !== HOME && isWorktree(join(cwd, '.aw')) ? ` ${chalk.green('✓')} Linked in current project` : null,
694
- installedIntegrations.length > 0 ? ` ${chalk.green('✓')} Integrations: ${installedIntegrations.join(', ')}` : null,
695
625
  '',
696
626
  ` ${chalk.dim('Existing repos:')} ${chalk.bold('cd <project> && aw link')}`,
697
627
  ` ${chalk.dim('New clones:')} auto-linked via git hook`,
698
628
  ` ${chalk.dim('Update:')} ${chalk.bold('aw init')} ${chalk.dim('(or auto on pull/IDE open)')}`,
699
- ` ${chalk.dim('Integrations:')} ${chalk.bold('aw integrations')} ${chalk.dim('(manage Codex, Caveman, etc)')}`,
700
629
  ` ${chalk.dim('Uninstall:')} ${chalk.bold('aw nuke')}`,
701
630
  ].filter(Boolean).join('\n'));
702
631
  }
@@ -0,0 +1,111 @@
1
+ import { homedir } from 'node:os';
2
+
3
+ import * as fmt from '../fmt.mjs';
4
+ import { listIntegrations, resolveIntegration } from '../integrations/index.mjs';
5
+
6
+ function printWarnings(warnings = []) {
7
+ for (const warning of warnings) {
8
+ fmt.logWarn(warning);
9
+ }
10
+ }
11
+
12
+ function printChangedFiles(changedFiles = []) {
13
+ if (changedFiles.length === 0) return;
14
+ fmt.note(changedFiles.map(file => file.replace(homedir(), '~')).join('\n'), 'Changed files');
15
+ }
16
+
17
+ function requireIntegration(name) {
18
+ if (!name) {
19
+ fmt.cancel('Missing integration name. Known integrations: context-mode');
20
+ }
21
+
22
+ try {
23
+ return resolveIntegration(name);
24
+ } catch (err) {
25
+ fmt.cancel(err.message);
26
+ }
27
+ }
28
+
29
+ export async function integrationCommand(args) {
30
+ const [subcommand = 'list', name] = args._positional;
31
+ const home = homedir();
32
+
33
+ if (args['--help']) {
34
+ fmt.intro('aw integration');
35
+ fmt.note(
36
+ [
37
+ 'aw integration list',
38
+ 'aw integration add context-mode [--dry-run]',
39
+ 'aw integration status context-mode',
40
+ 'aw integration remove context-mode [--dry-run]',
41
+ ].join('\n'),
42
+ 'Usage',
43
+ );
44
+ fmt.outro('Done');
45
+ return;
46
+ }
47
+
48
+ if (subcommand === 'list') {
49
+ fmt.intro('aw integration list');
50
+ const rows = listIntegrations(home, { env: process.env });
51
+ fmt.note(
52
+ rows.map(row => `${row.name} ${row.state} ${row.summary}`).join('\n'),
53
+ 'Known integrations',
54
+ );
55
+ fmt.outro('Done');
56
+ return;
57
+ }
58
+
59
+ const integration = requireIntegration(name);
60
+
61
+ if (subcommand === 'add') {
62
+ fmt.intro(`aw integration add ${integration.name}`);
63
+ const result = integration.add(home, {
64
+ env: process.env,
65
+ dryRun: args['--dry-run'] === true,
66
+ });
67
+ printWarnings(result.warnings);
68
+ printChangedFiles(result.changedFiles);
69
+ if (result.changedFiles.length > 0) {
70
+ fmt.outro(`${integration.name} configured`);
71
+ } else if (result.warnings.length > 0) {
72
+ fmt.outro(`${integration.name} not configured; binary is missing or config was unsafe to edit`);
73
+ } else {
74
+ fmt.outro(`${integration.name} already configured`);
75
+ }
76
+ return;
77
+ }
78
+
79
+ if (subcommand === 'remove') {
80
+ fmt.intro(`aw integration remove ${integration.name}`);
81
+ const result = integration.remove(home, {
82
+ dryRun: args['--dry-run'] === true,
83
+ });
84
+ printWarnings(result.warnings);
85
+ printChangedFiles(result.changedFiles);
86
+ fmt.outro(result.changedFiles.length > 0
87
+ ? `${integration.name} removed`
88
+ : `${integration.name} not configured`);
89
+ return;
90
+ }
91
+
92
+ if (subcommand === 'status') {
93
+ fmt.intro(`aw integration status ${integration.name}`);
94
+ const status = integration.status(home, { env: process.env });
95
+ fmt.note(
96
+ [
97
+ `state: ${status.state}`,
98
+ `installed: ${status.installed}`,
99
+ `configured: ${status.configured}`,
100
+ status.version ? `version: ${status.version}` : null,
101
+ status.path ? `path: ${status.path}` : null,
102
+ `summary: ${status.summary}`,
103
+ ].filter(Boolean).join('\n'),
104
+ integration.name,
105
+ );
106
+ fmt.outro('Done');
107
+ return;
108
+ }
109
+
110
+ fmt.cancel(`Unknown integration subcommand: ${subcommand}. Use add / remove / status / list`);
111
+ }
package/commands/nuke.mjs CHANGED
@@ -14,6 +14,7 @@ import { chalk } from '../fmt.mjs';
14
14
  import { removeGlobalHooks } from '../hooks.mjs';
15
15
  import { uninstallAwEcc } from '../ecc.mjs';
16
16
  import { removeMcpConfig } from '../mcp.mjs';
17
+ import { removeContextModeIntegration } from '../integrations/context-mode.mjs';
17
18
  import { listProjectWorktrees } from '../git.mjs';
18
19
  import { removeWorkspaceHookDefaults } from '../codex.mjs';
19
20
  import { readHookManifest, pruneStaleHooks, removeHookManifest } from '../hook-cleanup.mjs';
@@ -245,6 +246,7 @@ export async function nukeCommand(args) {
245
246
  const ideCount = removeIdeSymlinks();
246
247
  uninstallAwEcc();
247
248
  removeMcpConfig();
249
+ removeContextModeIntegration(HOME);
248
250
  // Prune stale hook entries using manifest (catches entries not tracked by install-state)
249
251
  if (hookManifest) pruneStaleHooks(hookManifest);
250
252
  removeHookManifest();
@@ -364,7 +366,7 @@ export async function nukeCommand(args) {
364
366
  '',
365
367
  ` ${chalk.green('✓')} Generated files cleaned`,
366
368
  ` ${chalk.green('✓')} IDE symlinks cleaned`,
367
- ` ${chalk.green('✓')} MCP config removed (ghl-ai from Claude, Cursor, and Codex configs)`,
369
+ ` ${chalk.green('✓')} MCP config removed (ghl-ai and context-mode)`,
368
370
  ` ${chalk.green('✓')} aw-ecc engine removed`,
369
371
  ` ${chalk.green('✓')} Project worktrees removed`,
370
372
  ` ${chalk.green('✓')} Project symlinks cleaned`,
package/commands/pull.mjs CHANGED
@@ -27,7 +27,6 @@ import {
27
27
  REGISTRY_DIR,
28
28
  REGISTRY_URL,
29
29
  DOCS_SOURCE_DIR,
30
- AW_DOCS_DIR,
31
30
  RULES_SOURCE_DIR,
32
31
  RULES_RUNTIME_DIR,
33
32
  } from '../constants.mjs';
@@ -93,7 +92,7 @@ export async function pullCommand(args) {
93
92
  // Ensure platform pulls also fetch docs and rules on older installs that
94
93
  // pre-date the new sparse-checkout paths.
95
94
  if (input === 'platform') {
96
- addToSparseCheckout(AW_HOME, [`.aw_registry/platform`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR]);
95
+ addToSparseCheckout(AW_HOME, [`.aw_registry/platform`, DOCS_SOURCE_DIR, RULES_SOURCE_DIR]);
97
96
  if (!cfg.include.includes('platform')) {
98
97
  config.addPattern(GLOBAL_AW_DIR, 'platform');
99
98
  }
@@ -111,7 +110,7 @@ export async function pullCommand(args) {
111
110
  const label = input.split('/').pop();
112
111
  if (!cfg.include.includes(input)) {
113
112
  log.logStep(`Adding ${chalk.cyan(label)} to sparse checkout...`);
114
- addToSparseCheckout(AW_HOME, [sparsePath, DOCS_SOURCE_DIR, AW_DOCS_DIR]);
113
+ addToSparseCheckout(AW_HOME, [sparsePath, DOCS_SOURCE_DIR]);
115
114
  config.addPattern(GLOBAL_AW_DIR, input);
116
115
  addedInput = input;
117
116
  addedSparsePath = sparsePath;