@ghl-ai/aw 0.1.50-beta.0 → 0.1.50

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),
@@ -50,9 +50,6 @@ function parseArgs(argv) {
50
50
  if (arg === '--dry-run') {
51
51
  args['--dry-run'] = true;
52
52
  i++;
53
- } else if (arg === '--aw-docs-only' || arg === '--docs-only') {
54
- args[arg] = true;
55
- i++;
56
53
  } else if (arg === '-v' || arg === '--verbose') {
57
54
  args['-v'] = true;
58
55
  i++;
@@ -87,13 +84,10 @@ function printHelp() {
87
84
  const sec = (title) => `\n ${chalk.bold.underline(title)}`;
88
85
  const help = [
89
86
  sec('Setup'),
90
- cmd('aw init', 'Initialize workspace (auto-installs suggested integrations)'),
87
+ cmd('aw init', 'Initialize workspace (platform/ only)'),
91
88
  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
89
  ` ${chalk.dim('Teams: platform, revex, mobile, commerce, leadgen, crm, marketplace, ai')}`,
95
90
  ` ${chalk.dim('Example: aw init --namespace revex/courses')}`,
96
- ` ${chalk.dim('Example: aw init skills platform/core/skills/pr-review')}`,
97
91
  cmd('aw init-repo', 'Scaffold cloud-bootstrap files (idempotent, --dry-run/--force/--diff)'),
98
92
 
99
93
  sec('Download'),
@@ -103,8 +97,6 @@ function printHelp() {
103
97
 
104
98
  sec('Upload'),
105
99
  cmd('aw push', 'Push all modified files (creates one PR)'),
106
- cmd('aw push --aw-docs-only', 'Publish generated .aw_docs companions and print share links'),
107
- cmd('aw push --aw-docs-only --feature <slug>', 'Publish one .aw_docs feature folder and print share links'),
108
100
  cmd('aw push <path>', 'Push file, folder, or namespace to registry'),
109
101
  cmd('aw push-rules [path]', 'Push platform rules to platform-docs'),
110
102
  cmd('aw push --dry-run [path]', 'Preview what would be pushed'),
@@ -117,15 +109,12 @@ function printHelp() {
117
109
  cmd('aw doctor', 'Run a health check for routing, MCP, plugin, and AW ECC surfaces'),
118
110
  cmd('aw link', 'Link current project as a git worktree (wires IDE symlinks)'),
119
111
  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'),
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 integration list', 'List addable third-party integrations'),
115
+ cmd('aw integration add <name>', 'Add a specific integration, e.g. context-mode'),
116
+ cmd('aw integration status <name>', 'Show integration health and configured surfaces'),
117
+ cmd('aw integration remove <name>', 'Remove a specific integration'),
129
118
  cmd('aw drop <path>', 'Stop syncing or delete local content'),
130
119
  cmd('aw nuke', 'Remove entire .aw_registry/ & start fresh'),
131
120
  cmd('aw daemon install', 'Auto-pull on a schedule (macOS launchd / Linux cron)'),
@@ -154,7 +143,6 @@ function printHelp() {
154
143
  cmd('aw pull <team>/agents', 'All agents from a team'),
155
144
  cmd('aw pull <team>/agents/<name>', 'One specific agent'),
156
145
  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
146
  '',
159
147
  ` ${chalk.dim('# Push your local changes to registry')}`,
160
148
  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,7 +35,6 @@ 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,
@@ -48,7 +48,7 @@ import {
48
48
  syncWorktreeSparseCheckout,
49
49
  findNearestWorktree,
50
50
  } from '../git.mjs';
51
- 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';
52
52
  import { syncFileTree } from '../file-tree.mjs';
53
53
 
54
54
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -92,6 +92,32 @@ function syncHomeAndProjectInstructions(cwd, namespace) {
92
92
  }
93
93
  }
94
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
+
95
121
  /**
96
122
  * Create scaffold directories for a new team namespace so the developer
97
123
  * has the standard folder structure ready for agents, skills, commands, and evals.
@@ -108,39 +134,10 @@ function scaffoldNamespace(awHome, folderName) {
108
134
  }
109
135
  }
110
136
 
111
- function getSkillInitSpecs(args) {
112
- const positional = args._positional || [];
113
- const mode = positional[0];
114
- const rawTargets = [];
115
-
116
- if (mode === 'skill' || mode === 'skills') {
117
- rawTargets.push(...positional.slice(1));
118
- }
119
-
120
- if (args['--skill']) {
121
- rawTargets.push(args['--skill']);
122
- }
123
-
124
- if ((mode === 'skill' || mode === 'skills' || args['--skill']) && rawTargets.length === 0) {
125
- fmt.cancel(`Missing skill path.\n\n ${chalk.dim('Example:')} ${chalk.bold('aw init skills platform/core/skills/pr-review')}`);
126
- }
127
-
128
- if (rawTargets.length === 0) return [];
129
-
130
- try {
131
- return [...new Map(rawTargets.map(input => {
132
- const spec = normalizeSkillTarget(input);
133
- return [spec.registryPath, spec];
134
- })).values()];
135
- } catch (e) {
136
- fmt.cancel(e.message);
137
- }
138
- }
139
-
140
137
  // ── Ensure ~/.aw/.git/info/exclude has the whitelist block ─────────────
141
138
  //
142
- // Strategy: only .aw_registry/, .aw_rules/, content/, and .aw_docs/ are
143
- // 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,
144
141
  // .DS_Store, etc.). We write to .git/info/exclude (not tracked .gitignore)
145
142
  // so upstream pulls never conflict.
146
143
 
@@ -154,7 +151,6 @@ const AW_MANAGED_BLOCK = [
154
151
  '!/.aw_registry',
155
152
  '!/.aw_rules',
156
153
  '!/content',
157
- '!/.aw_docs',
158
154
  '',
159
155
  '# Nested local state within whitelisted dirs',
160
156
  '/.aw_registry/.sync-config.json',
@@ -243,15 +239,6 @@ export async function initCommand(args) {
243
239
  let namespace = args['--namespace'] || null;
244
240
  let user = args['--user'] || '';
245
241
  const silent = args['--silent'] === true;
246
- const skipIntegrations = args['--no-integrations'] === true;
247
- const skillSpecs = getSkillInitSpecs(args);
248
- const skillTargets = skillSpecs.map(spec => spec.registryPath);
249
- const isSkillInit = skillTargets.length > 0;
250
- const skillNamespace = skillSpecs[0]?.namespace || null;
251
-
252
- if (isSkillInit && namespace) {
253
- fmt.cancel('Use either `aw init skills <path...>` or `aw init --namespace <team/sub-team>`, not both.');
254
- }
255
242
 
256
243
  // In silent mode, suppress ALL fmt output and show a single spinner.
257
244
  // setSilent(true) makes every fmt.* call a no-op — internal functions
@@ -360,28 +347,10 @@ export async function initCommand(args) {
360
347
  if (isGitNative) {
361
348
  const cfg = config.load(GLOBAL_AW_DIR);
362
349
 
363
- const newSkillTargets = isSkillInit && cfg
364
- ? skillTargets.filter(target => !cfg.include.some(pattern => target === pattern || target.startsWith(pattern + '/')))
365
- : [];
366
- const isNewSubTeam = !isSkillInit && folderName && cfg && !cfg.include.includes(folderName);
367
-
368
- if (isSkillInit) {
369
- if (newSkillTargets.length > 0) {
370
- if (!silent) fmt.logStep(`Adding ${chalk.cyan(newSkillTargets.length)} skill${newSkillTargets.length > 1 ? 's' : ''}...`);
371
- addToSparseCheckout(AW_HOME, [
372
- ...newSkillTargets.map(target => `.aw_registry/${target}`),
373
- DOCS_SOURCE_DIR,
374
- AW_DOCS_DIR,
375
- RULES_SOURCE_DIR,
376
- `.aw_registry/AW-PROTOCOL.md`,
377
- ]);
378
- for (const target of newSkillTargets) config.addPattern(GLOBAL_AW_DIR, target);
379
- } else if (!silent) {
380
- fmt.logStep('Already initialized — syncing selected skills...');
381
- }
382
- } else if (isNewSubTeam) {
350
+ const isNewSubTeam = folderName && cfg && !cfg.include.includes(folderName);
351
+ if (isNewSubTeam) {
383
352
  if (!silent) fmt.logStep(`Adding sub-team ${chalk.cyan(folderName)}...`);
384
- const newSparsePaths = [`.aw_registry/${folderName}`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR];
353
+ const newSparsePaths = [`.aw_registry/${folderName}`, 'content', RULES_SOURCE_DIR];
385
354
  addToSparseCheckout(AW_HOME, newSparsePaths);
386
355
  config.addPattern(GLOBAL_AW_DIR, folderName);
387
356
  scaffoldNamespace(AW_HOME, folderName);
@@ -407,7 +376,6 @@ export async function initCommand(args) {
407
376
 
408
377
  ensureAwGitignore(AW_HOME);
409
378
  const freshCfg = config.load(GLOBAL_AW_DIR);
410
- const activeNamespace = skillNamespace || freshCfg?.namespace || team;
411
379
  syncRulesTargets(HOME);
412
380
  if (cwd !== HOME) {
413
381
  syncRulesTargets(cwd);
@@ -430,8 +398,9 @@ export async function initCommand(args) {
430
398
  await installAwEcc(cwd, { silent });
431
399
 
432
400
  ensureAwRuntimeHook(HOME);
433
- syncHomeAndProjectInstructions(cwd, activeNamespace);
434
- await setupMcp(HOME, activeNamespace, { silent });
401
+ syncHomeAndProjectInstructions(cwd, freshCfg?.namespace || team);
402
+ await setupMcp(HOME, freshCfg?.namespace || team, { silent });
403
+ maybeConfigureContextMode(args, { silent });
435
404
  applyStoredStartupPreferences(HOME);
436
405
  const removedLegacyStartupFiles = cwd !== HOME ? removeWorkspaceHookDefaults(cwd) : [];
437
406
  installGlobalHooks();
@@ -465,22 +434,14 @@ export async function initCommand(args) {
465
434
  if (!silent) fmt.logStep('Wiring IDE symlinks...');
466
435
  const projectRegistryDir = cwd !== HOME ? join(cwd, '.aw', REGISTRY_DIR) : null;
467
436
  const awDirForLinks = (projectRegistryDir && existsSync(projectRegistryDir)) ? projectRegistryDir : null;
468
- const symlinks = isSkillInit
469
- ? linkSkills(HOME, skillTargets, awDirForLinks, { silent: true, exclusive: true })
470
- : linkWorkspace(HOME, awDirForLinks, { silent: true });
471
- const commands = isSkillInit ? 0 : generateCommands(HOME, { silent: true });
437
+ const symlinks = linkWorkspace(HOME, awDirForLinks, { silent: true });
438
+ const commands = generateCommands(HOME, { silent: true });
472
439
  if (cwd !== HOME) installLocalCommitHook(cwd);
473
- if (!silent) fmt.logStep(`IDE wired — ${chalk.bold(symlinks)} ${isSkillInit ? 'skill symlinks' : 'symlinks'} · ${chalk.bold(commands)} commands`);
440
+ if (!silent) fmt.logStep(`IDE wired — ${chalk.bold(symlinks)} symlinks · ${chalk.bold(commands)} commands`);
474
441
 
475
442
  // Write hook manifest after all hook installation is complete
476
443
  try { writeHookManifest({ eccVersion: AW_ECC_TAG, awVersion: VERSION }); } catch { /* best effort */ }
477
444
 
478
- // Auto-install suggested integrations (Codex, Caveman, Graphify, etc) - unless --no-integrations
479
- let installedIntegrations = [];
480
- if (!silent && !skipIntegrations && !isNewSubTeam) {
481
- installedIntegrations = await autoInstallIntegrations(activeNamespace, { silent });
482
- }
483
-
484
445
  if (silent) {
485
446
  if (silentSpinner) { silentSpinner.stop('Done'); setSilent(false); }
486
447
  autoUpdate(await args._updateCheck);
@@ -489,12 +450,11 @@ export async function initCommand(args) {
489
450
  `⟁ ${isNewSubTeam ? `Sub-team ${chalk.cyan(folderName)} added` : 'Up to date'}`,
490
451
  '',
491
452
  ` ${chalk.green('✓')} Registry synced`,
492
- ` ${chalk.green('✓')} IDE refreshed — ${chalk.bold(symlinks)} ${isSkillInit ? 'skill symlinks' : 'symlinks'} · ${chalk.bold(commands)} commands`,
453
+ ` ${chalk.green('✓')} IDE refreshed — ${chalk.bold(symlinks)} symlinks · ${chalk.bold(commands)} commands`,
493
454
  removedLegacyStartupFiles.length > 0
494
455
  ? ` ${chalk.green('✓')} Removed ${removedLegacyStartupFiles.length} legacy repo startup file${removedLegacyStartupFiles.length > 1 ? 's' : ''}`
495
456
  : null,
496
457
  cwd !== HOME && isWorktree(join(cwd, '.aw')) ? ` ${chalk.green('✓')} Project linked` : null,
497
- installedIntegrations.length > 0 ? ` ${chalk.green('✓')} Integrations: ${installedIntegrations.join(', ')}` : null,
498
458
  ].filter(Boolean).join('\n'));
499
459
  }
500
460
  return;
@@ -523,24 +483,13 @@ export async function initCommand(args) {
523
483
  }
524
484
 
525
485
  // Determine sparse paths
526
- const sparsePaths = isSkillInit
527
- ? [
528
- ...skillTargets.map(target => `.aw_registry/${target}`),
529
- DOCS_SOURCE_DIR,
530
- AW_DOCS_DIR,
531
- RULES_SOURCE_DIR,
532
- `.aw_registry/AW-PROTOCOL.md`,
533
- `CODEOWNERS`,
534
- ]
535
- : [`.aw_registry/platform`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR, `.aw_registry/AW-PROTOCOL.md`, `CODEOWNERS`];
536
- if (!isSkillInit && folderName) {
486
+ const sparsePaths = [`.aw_registry/platform`, `content`, RULES_SOURCE_DIR, `.aw_registry/AW-PROTOCOL.md`, `CODEOWNERS`];
487
+ if (folderName) {
537
488
  sparsePaths.push(`.aw_registry/${folderName}`);
538
489
  }
539
490
 
540
491
  fmt.note([
541
- isSkillInit
542
- ? `${chalk.dim('skills:')} ${skillTargets.join(', ')}`
543
- : (folderName ? `${chalk.dim('namespace:')} ${folderName}` : `${chalk.dim('namespace:')} ${chalk.dim('none')}`),
492
+ folderName ? `${chalk.dim('namespace:')} ${folderName}` : `${chalk.dim('namespace:')} ${chalk.dim('none')}`,
544
493
  user ? `${chalk.dim('user:')} ${user}` : null,
545
494
  `${chalk.dim('version:')} v${VERSION}`,
546
495
  ].filter(Boolean).join('\n'), 'Config');
@@ -580,11 +529,8 @@ export async function initCommand(args) {
580
529
  }
581
530
 
582
531
  // Create sync config — default to 'platform' when no namespace specified
583
- const activeNamespace = skillNamespace || team || 'platform';
584
- const cfg = config.create(GLOBAL_AW_DIR, { namespace: activeNamespace, user });
585
- if (isSkillInit) {
586
- for (const target of skillTargets) config.addPattern(GLOBAL_AW_DIR, target);
587
- } else if (folderName) {
532
+ const cfg = config.create(GLOBAL_AW_DIR, { namespace: team || 'platform', user });
533
+ if (folderName) {
588
534
  config.addPattern(GLOBAL_AW_DIR, folderName);
589
535
  scaffoldNamespace(AW_HOME, folderName);
590
536
  }
@@ -605,9 +551,10 @@ export async function initCommand(args) {
605
551
 
606
552
  // Parallel batch B: post-ECC setup (instructions and MCP are independent)
607
553
  const [, mcpFiles] = await Promise.all([
608
- Promise.resolve(syncHomeAndProjectInstructions(cwd, activeNamespace)),
609
- setupMcp(HOME, activeNamespace, { silent }),
554
+ Promise.resolve(syncHomeAndProjectInstructions(cwd, team)),
555
+ setupMcp(HOME, team, { silent }),
610
556
  ]);
557
+ maybeConfigureContextMode(args, { silent });
611
558
  // applyStoredStartupPreferences reads settings written by ECC — keep after batch B
612
559
  applyStoredStartupPreferences(HOME);
613
560
  const removedLegacyStartupFiles = cwd !== HOME ? removeWorkspaceHookDefaults(cwd) : [];
@@ -644,13 +591,11 @@ export async function initCommand(args) {
644
591
  const awDirForLinks = (projectRegistryDir && existsSync(projectRegistryDir)) ? projectRegistryDir : null;
645
592
  // Parallel batch C: symlinks + commands are independent
646
593
  if (cwd !== HOME) installLocalCommitHook(cwd);
647
- const [symlinks, commands] = isSkillInit
648
- ? [linkSkills(HOME, skillTargets, awDirForLinks, { silent: true, exclusive: true }), 0]
649
- : [
650
- linkWorkspace(HOME, awDirForLinks, { silent: true }),
651
- generateCommands(HOME, { silent: true }),
652
- ];
653
- if (!silent) fmt.logStep(`IDE wired — ${chalk.bold(symlinks)} ${isSkillInit ? 'skill 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`);
654
599
 
655
600
  // Write hook manifest after all hook installation is complete
656
601
  try { writeHookManifest({ eccVersion: AW_ECC_TAG, awVersion: VERSION }); } catch { /* best effort */ }
@@ -658,12 +603,6 @@ export async function initCommand(args) {
658
603
  // Ensure telemetry config exists (generates machine_id on first run)
659
604
  ensureTelemetryConfig();
660
605
 
661
- // Auto-install suggested integrations (Codex, Caveman, Graphify, etc) - unless --no-integrations
662
- let installedIntegrations = [];
663
- if (!silent && !skipIntegrations) {
664
- installedIntegrations = await autoInstallIntegrations(activeNamespace, { silent });
665
- }
666
-
667
606
  // Offer to update if a newer version is available
668
607
  if (!silent) await promptUpdate(await args._updateCheck);
669
608
 
@@ -683,12 +622,10 @@ export async function initCommand(args) {
683
622
  hooksInstalled ? ` ${chalk.green('✓')} Git hooks: auto-sync on pull/clone (core.hooksPath)` : null,
684
623
  ` ${chalk.green('✓')} IDE task: auto-sync on workspace open`,
685
624
  cwd !== HOME && isWorktree(join(cwd, '.aw')) ? ` ${chalk.green('✓')} Linked in current project` : null,
686
- installedIntegrations.length > 0 ? ` ${chalk.green('✓')} Integrations: ${installedIntegrations.join(', ')}` : null,
687
625
  '',
688
626
  ` ${chalk.dim('Existing repos:')} ${chalk.bold('cd <project> && aw link')}`,
689
627
  ` ${chalk.dim('New clones:')} auto-linked via git hook`,
690
628
  ` ${chalk.dim('Update:')} ${chalk.bold('aw init')} ${chalk.dim('(or auto on pull/IDE open)')}`,
691
- ` ${chalk.dim('Integrations:')} ${chalk.bold('aw integrations')} ${chalk.dim('(manage Codex, Caveman, etc)')}`,
692
629
  ` ${chalk.dim('Uninstall:')} ${chalk.bold('aw nuke')}`,
693
630
  ].filter(Boolean).join('\n'));
694
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`,