@ghl-ai/aw 0.1.57 → 0.1.58-beta.0

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
@@ -168,10 +168,13 @@ function printHelp() {
168
168
  cmd('aw mcp status', 'Show global AW MCP mode for Claude/Cursor/Codex'),
169
169
  cmd('aw mcp disable', 'Remove AW-managed MCP server across Claude/Cursor/Codex'),
170
170
  cmd('aw mcp enable', 'Restore AW-managed MCP server across Claude/Cursor/Codex'),
171
- cmd('aw integrations', 'Manage third-party integrations (Codex, Caveman, Context Mode, etc)'),
172
- cmd('aw integrations add <key>', 'Install a specific tool (e.g. codex, caveman, context-mode)'),
171
+ cmd('aw integrations', 'Manage third-party integrations (Codex, Caveman, etc)'),
172
+ cmd('aw integrations add <key>', 'Install a specific tool (e.g. codex, caveman)'),
173
173
  cmd('aw integrations remove <key>', 'Remove a tool'),
174
174
  cmd('aw integrations bundle <name>', 'Install a preset bundle'),
175
+ cmd('aw integration add <name>', 'Configure an installed local integration, e.g. context-mode'),
176
+ cmd('aw integration status <name>', 'Show local integration health'),
177
+ cmd('aw integration remove <name>', 'Remove AW-managed local integration entries'),
175
178
  cmd('aw drop <path>', 'Stop syncing or delete local content'),
176
179
  cmd('aw nuke', 'Remove entire .aw_registry/ & start fresh'),
177
180
  cmd('aw daemon install', 'Auto-pull on a schedule (macOS launchd / Linux cron)'),
package/commands/init.mjs CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  import { execSync } from 'node:child_process';
19
19
  import { join, dirname, sep } from 'node:path';
20
20
  import { homedir } from 'node:os';
21
- import { fileURLToPath, pathToFileURL } from 'node:url';
21
+ import { fileURLToPath } from 'node:url';
22
22
  import * as p from '@clack/prompts';
23
23
  import * as config from '../config.mjs';
24
24
  import * as fmt from '../fmt.mjs';
@@ -26,7 +26,7 @@ import { chalk, setSilent } from '../fmt.mjs';
26
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 { isContextModeRequested } from '../integrations/context-mode.mjs';
29
+ import { ensureContextModeIntegration, isContextModeRequested } from '../integrations/context-mode.mjs';
30
30
  import { applyStoredStartupPreferences, ensureAwRuntimeHook, isDefaultRoutingEnabled } from '../startup.mjs';
31
31
  import { installLocalCommitHook } from '../hooks.mjs';
32
32
  import { autoUpdate, promptUpdate } from '../update.mjs';
@@ -35,7 +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 { installIntegration, autoInstallIntegrations } from '../integrations.mjs';
38
+ import { promptAndInstall, autoInstallIntegrations } from '../integrations.mjs';
39
39
  import {
40
40
  initPersistentClone,
41
41
  isValidClone,
@@ -65,41 +65,6 @@ const HOME = (() => { try { return realpathSync(_rawHome); } catch { return _raw
65
65
  const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
66
66
  const AW_HOME = join(HOME, '.aw');
67
67
 
68
- function errorMessage(error) {
69
- return error instanceof Error ? error.message : String(error);
70
- }
71
-
72
- function writeHookManifestBestEffort(manifest, context) {
73
- try {
74
- writeHookManifest(manifest);
75
- } catch (error) {
76
- fmt.logWarn(`Could not persist hook manifest${context ? ` ${context}` : ''}: ${errorMessage(error)}`);
77
- }
78
- }
79
-
80
- export async function syncEccAfterAwUpdate(updateResult, cwd, { silent = false } = {}) {
81
- if (updateResult?.status !== 'upgraded' || !updateResult.packageRoot) {
82
- return { synced: false, reason: updateResult?.reason || updateResult?.status || 'not-upgraded' };
83
- }
84
-
85
- const eccModulePath = join(updateResult.packageRoot, 'ecc.mjs');
86
- if (!existsSync(eccModulePath)) {
87
- return { synced: false, reason: 'missing-ecc-module' };
88
- }
89
-
90
- try {
91
- const moduleUrl = `${pathToFileURL(eccModulePath).href}?aw-post-update=${Date.now()}`;
92
- const ecc = await import(moduleUrl);
93
- await ecc.installAwEcc(cwd, { silent });
94
- return { synced: true, eccVersion: ecc.AW_ECC_TAG || null };
95
- } catch (err) {
96
- if (!silent) {
97
- fmt.logWarn(`AW updated, but post-update ECC sync failed: ${err.message}`);
98
- }
99
- return { synced: false, reason: err.message };
100
- }
101
- }
102
-
103
68
  function syncRulesTargets(targetDir) {
104
69
  const rulesSrc = join(AW_HOME, RULES_SOURCE_DIR);
105
70
  if (!existsSync(rulesSrc)) return false;
@@ -129,24 +94,23 @@ function syncHomeAndProjectInstructions(cwd, namespace) {
129
94
  }
130
95
  }
131
96
 
132
- async function maybeConfigureContextMode(args, { silent = false } = {}) {
97
+ function maybeConfigureContextMode(args, { silent = false } = {}) {
133
98
  if (!isContextModeRequested(args, process.env)) {
134
99
  return null;
135
100
  }
136
101
 
137
- const result = await installIntegration('context-mode', {
138
- home: HOME,
102
+ const result = ensureContextModeIntegration(HOME, {
139
103
  env: process.env,
140
104
  silent,
141
105
  });
142
106
 
143
107
  if (!silent) {
144
- for (const warning of result.warnings || []) {
108
+ for (const warning of result.warnings) {
145
109
  fmt.logWarn(warning);
146
110
  }
147
- if ((result.changedFiles || []).length > 0) {
111
+ if (result.changedFiles.length > 0) {
148
112
  fmt.logStep(`Context Mode configured (${result.changedFiles.length} file${result.changedFiles.length === 1 ? '' : 's'})`);
149
- } else if ((result.warnings || []).length > 0) {
113
+ } else if (result.warnings.length > 0) {
150
114
  fmt.logWarn('Context Mode was requested but not configured');
151
115
  } else {
152
116
  fmt.logStep('Context Mode already configured');
@@ -440,7 +404,7 @@ export async function initCommand(args) {
440
404
  ensureAwRuntimeHook(HOME);
441
405
  syncHomeAndProjectInstructions(cwd, freshCfg?.namespace || team);
442
406
  await setupMcp(HOME, freshCfg?.namespace || team, { silent });
443
- await maybeConfigureContextMode(args, { silent });
407
+ maybeConfigureContextMode(args, { silent });
444
408
  applyStoredStartupPreferences(HOME);
445
409
  const removedLegacyStartupFiles = cwd !== HOME ? removeWorkspaceHookDefaults(cwd) : [];
446
410
  installGlobalHooks();
@@ -480,7 +444,7 @@ export async function initCommand(args) {
480
444
  if (!silent) fmt.logStep(`IDE wired — ${chalk.bold(symlinks)} symlinks · ${chalk.bold(commands)} commands`);
481
445
 
482
446
  // Write hook manifest after all hook installation is complete
483
- writeHookManifestBestEffort({ eccVersion: AW_ECC_TAG, awVersion: VERSION });
447
+ try { writeHookManifest({ eccVersion: AW_ECC_TAG, awVersion: VERSION }); } catch { /* best effort */ }
484
448
 
485
449
  // Auto-install suggested integrations (Codex, Caveman, Graphify, etc) - unless --no-integrations
486
450
  let installedIntegrations = [];
@@ -490,14 +454,7 @@ export async function initCommand(args) {
490
454
 
491
455
  if (silent) {
492
456
  if (silentSpinner) { silentSpinner.stop('Done'); setSilent(false); }
493
- const updateResult = autoUpdate(await args._updateCheck);
494
- const postUpdateEcc = await syncEccAfterAwUpdate(updateResult, cwd, { silent: true });
495
- if (postUpdateEcc.synced) {
496
- writeHookManifestBestEffort(
497
- { eccVersion: postUpdateEcc.eccVersion, awVersion: updateResult.to },
498
- 'after AW update',
499
- );
500
- }
457
+ autoUpdate(await args._updateCheck);
501
458
  } else {
502
459
  fmt.outro([
503
460
  `⟁ ${isNewSubTeam ? `Sub-team ${chalk.cyan(folderName)} added` : 'Up to date'}`,
@@ -608,7 +565,7 @@ export async function initCommand(args) {
608
565
  Promise.resolve(syncHomeAndProjectInstructions(cwd, team)),
609
566
  setupMcp(HOME, team, { silent }),
610
567
  ]);
611
- await maybeConfigureContextMode(args, { silent });
568
+ maybeConfigureContextMode(args, { silent });
612
569
  // applyStoredStartupPreferences reads settings written by ECC — keep after batch B
613
570
  applyStoredStartupPreferences(HOME);
614
571
  const removedLegacyStartupFiles = cwd !== HOME ? removeWorkspaceHookDefaults(cwd) : [];
@@ -673,7 +630,7 @@ export async function initCommand(args) {
673
630
 
674
631
  // Write hook manifest after all hook installation is complete, including
675
632
  // bundled usage hooks, so `aw nuke` can prune AW-managed settings entries.
676
- writeHookManifestBestEffort({ eccVersion: AW_ECC_TAG, awVersion: VERSION });
633
+ try { writeHookManifest({ eccVersion: AW_ECC_TAG, awVersion: VERSION }); } catch { /* best effort */ }
677
634
 
678
635
  // Auto-install suggested integrations (Codex, Caveman, Graphify, etc) - unless --no-integrations
679
636
  let installedIntegrations = [];
@@ -1,52 +1,43 @@
1
- // commands/integration.mjs — Deprecated compatibility wrapper for `aw integration`.
2
-
3
1
  import { homedir } from 'node:os';
4
2
 
5
3
  import * as fmt from '../fmt.mjs';
6
- import { getIntegrationSummary, INTEGRATIONS } from '../integrations.mjs';
7
- import { integrationsCommand } from './integrations.mjs';
4
+ import { listIntegrations, resolveIntegration } from '../integrations/index.mjs';
8
5
 
9
- function requireKnownIntegration(key) {
10
- if (!key) {
11
- fmt.cancel(`Missing integration name. Known integrations: ${Object.keys(INTEGRATIONS).join(', ')}`);
12
- }
13
- if (!INTEGRATIONS[key]) {
14
- fmt.cancel(`Unknown integration: ${key}`);
6
+ function printWarnings(warnings = []) {
7
+ for (const warning of warnings) {
8
+ fmt.logWarn(warning);
15
9
  }
16
10
  }
17
11
 
18
- function printStatus(key, home) {
19
- const status = getIntegrationSummary(key, { home, env: process.env });
20
- if (!status) {
21
- fmt.cancel(`Unknown integration: ${key}`);
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');
22
20
  }
23
21
 
24
- fmt.note(
25
- [
26
- `state: ${status.state}`,
27
- `installed: ${status.installed}`,
28
- `configured: ${status.configured}`,
29
- status.version ? `version: ${status.version}` : null,
30
- status.path ? `path: ${status.path}` : null,
31
- `summary: ${status.summary}`,
32
- ].filter(Boolean).join('\n'),
33
- key,
34
- );
22
+ try {
23
+ return resolveIntegration(name);
24
+ } catch (err) {
25
+ fmt.cancel(err.message);
26
+ }
35
27
  }
36
28
 
37
29
  export async function integrationCommand(args) {
38
- const [subcommand = 'list', key] = args._positional;
30
+ const [subcommand = 'list', name] = args._positional;
39
31
  const home = homedir();
40
32
 
41
33
  if (args['--help']) {
42
34
  fmt.intro('aw integration');
43
- fmt.logWarn('`aw integration` is deprecated. Use `aw integrations` for new automation.');
44
35
  fmt.note(
45
36
  [
46
37
  'aw integration list',
47
- 'aw integration add <key> [--dry-run] [--strict]',
48
- 'aw integration status <key>',
49
- 'aw integration remove <key> [--dry-run]',
38
+ 'aw integration add context-mode [--dry-run]',
39
+ 'aw integration status context-mode',
40
+ 'aw integration remove context-mode [--dry-run]',
50
41
  ].join('\n'),
51
42
  'Usage',
52
43
  );
@@ -54,18 +45,66 @@ export async function integrationCommand(args) {
54
45
  return;
55
46
  }
56
47
 
57
- fmt.logWarn('`aw integration` is deprecated. Use `aw integrations` instead.');
58
-
59
- if (subcommand === 'status') {
60
- requireKnownIntegration(key);
61
- fmt.intro(`aw integration status ${key}`);
62
- printStatus(key, home);
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
+ );
63
55
  fmt.outro('Done');
64
56
  return;
65
57
  }
66
58
 
67
- if (['list', 'add', 'remove', 'bundle'].includes(subcommand)) {
68
- return integrationsCommand(args);
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;
69
108
  }
70
109
 
71
110
  fmt.cancel(`Unknown integration subcommand: ${subcommand}. Use add / remove / status / list`);
@@ -1,53 +1,16 @@
1
1
  // commands/integrations.mjs — CLI command: aw integrations add/remove/list/bundle
2
2
 
3
- import { homedir } from 'node:os';
4
-
5
3
  import * as p from '@clack/prompts';
6
4
  import * as fmt from '../fmt.mjs';
7
5
  import { chalk } from '../fmt.mjs';
8
6
  import {
9
7
  INTEGRATIONS,
10
8
  BUNDLES,
11
- getIntegrationSummaries,
12
9
  installIntegration,
13
- integrationSucceeded,
14
10
  removeIntegration,
11
+ getInstalledList,
15
12
  } from '../integrations.mjs';
16
13
 
17
- function availableKeys() {
18
- return Object.keys(INTEGRATIONS).join(', ');
19
- }
20
-
21
- function printWarnings(warnings = []) {
22
- for (const warning of warnings) fmt.logWarn(warning);
23
- }
24
-
25
- function printChangedFiles(changedFiles = []) {
26
- if (!changedFiles.length) return;
27
- fmt.note(changedFiles.map(file => file.replace(homedir(), '~')).join('\n'), 'Changed files');
28
- }
29
-
30
- function printContextModeAddResult(result) {
31
- printWarnings(result.warnings);
32
- if (result.plannedInstall) {
33
- fmt.note(result.install?.commandLine || 'install context-mode@latest', 'Planned package install');
34
- }
35
- if (result.binary?.present) {
36
- fmt.note(
37
- [
38
- `binary: ${result.binary.path}`,
39
- `source: ${result.binary.source}`,
40
- result.installedPackage ? 'package: installed during this run' : 'package: reused existing binary',
41
- ].filter(Boolean).join('\n'),
42
- 'Context Mode',
43
- );
44
- }
45
- printChangedFiles(result.changedFiles);
46
- if (result.configuredHarnesses?.length) {
47
- fmt.note(result.configuredHarnesses.join('\n'), 'Configured surfaces');
48
- }
49
- }
50
-
51
14
  export async function integrationsCommand(args) {
52
15
  const subcommand = args._positional[0];
53
16
 
@@ -76,16 +39,92 @@ async function cmdList() {
76
39
  subtitle: ' Available tools and MCP servers',
77
40
  });
78
41
 
79
- const rows = getIntegrationSummaries({ home: homedir(), env: process.env });
80
- fmt.note(
81
- rows.map(row => `${row.name.padEnd(14)} ${row.state.padEnd(11)} ${row.summary}`).join('\n'),
82
- 'Available integrations',
42
+ const installed = getInstalledList();
43
+
44
+ // Group by type
45
+ const plugins = Object.entries(INTEGRATIONS).filter(
46
+ ([, i]) => i.type === 'plugin'
47
+ );
48
+ const remoteRcps = Object.entries(INTEGRATIONS).filter(
49
+ ([, i]) => i.type === 'remote-mcp'
50
+ );
51
+ const universalInstallers = Object.entries(INTEGRATIONS).filter(
52
+ ([, i]) => i.type === 'universal-installer'
53
+ );
54
+ const pythonClis = Object.entries(INTEGRATIONS).filter(
55
+ ([, i]) => i.type === 'python-cli'
83
56
  );
84
57
 
58
+ const typeIcon = (type) =>
59
+ type === 'plugin' ? '🔌' : type === 'remote-mcp' ? '🌐' : type === 'universal-installer' ? '🪨' : '⚙️';
60
+
61
+ // Installed section
62
+ if (installed.length > 0) {
63
+ fmt.logMessage(`\n${chalk.bold.underline('Installed')}`);
64
+ for (const key of installed) {
65
+ const integration = INTEGRATIONS[key];
66
+ if (!integration) continue;
67
+ fmt.logSuccess(` ${typeIcon(integration.type)} ${integration.label}`);
68
+ }
69
+ }
70
+
71
+ // Available Plugins
72
+ fmt.logMessage(`\n${chalk.bold.underline('Available Plugins')}`);
73
+ for (const [key, integration] of plugins) {
74
+ if (!installed.includes(key)) {
75
+ fmt.logMessage(
76
+ ` 🔌 ${integration.label.padEnd(25)} — ${integration.description}`
77
+ );
78
+ }
79
+ }
80
+
81
+ // Available Remote MCPs
82
+ fmt.logMessage(`\n${chalk.bold.underline('Available Remote MCPs')}`);
83
+ for (const [key, integration] of remoteRcps) {
84
+ if (!installed.includes(key)) {
85
+ fmt.logMessage(
86
+ ` 🌐 ${integration.label.padEnd(25)} — ${integration.description}`
87
+ );
88
+ }
89
+ }
90
+
91
+ // Universal Installers
92
+ if (universalInstallers.length > 0) {
93
+ fmt.logMessage(`\n${chalk.bold.underline('Universal Tools')}`);
94
+ for (const [key, integration] of universalInstallers) {
95
+ if (!installed.includes(key)) {
96
+ fmt.logMessage(
97
+ ` 🪨 ${integration.label.padEnd(25)} — ${integration.description}`
98
+ );
99
+ }
100
+ }
101
+ }
102
+
103
+ // Python CLIs
104
+ if (pythonClis.length > 0) {
105
+ fmt.logMessage(`\n${chalk.bold.underline('Available Python Tools')}`);
106
+ for (const [key, integration] of pythonClis) {
107
+ if (!installed.includes(key)) {
108
+ fmt.logMessage(
109
+ ` 🐍 ${integration.label.padEnd(25)} — ${integration.description}`
110
+ );
111
+ }
112
+ }
113
+ }
114
+
115
+ // Bundles
116
+ fmt.logMessage(`\n${chalk.bold.underline('Bundles')}`);
117
+ for (const [bundleKey, bundle] of Object.entries(BUNDLES)) {
118
+ fmt.logMessage(
119
+ ` 📦 ${bundle.label.padEnd(25)} — ${bundle.description}`
120
+ );
121
+ fmt.logMessage(
122
+ ` Includes: ${bundle.includes.map((k) => INTEGRATIONS[k].label).join(', ')}`
123
+ );
124
+ }
125
+
85
126
  fmt.logMessage(`\n${chalk.dim('Commands:')}`);
86
127
  fmt.logMessage(` aw integrations add <key> Install a specific tool`);
87
- fmt.logMessage(` aw integrations add <key> --dry-run`);
88
- fmt.logMessage(` aw integrations add context-mode --strict`);
89
128
  fmt.logMessage(` aw integrations remove <key> Remove a tool`);
90
129
  fmt.logMessage(` aw integrations bundle <name> Install a preset bundle`);
91
130
  }
@@ -103,11 +142,12 @@ async function cmdAdd(args) {
103
142
 
104
143
  if (!INTEGRATIONS[key]) {
105
144
  // Suggest similar keys
145
+ const available = Object.keys(INTEGRATIONS);
106
146
  fmt.cancel(
107
147
  [
108
148
  `Unknown integration: ${chalk.red(key)}`,
109
149
  '',
110
- `Available: ${availableKeys()}`,
150
+ `Available: ${available.join(', ')}`,
111
151
  ].join('\n')
112
152
  );
113
153
  }
@@ -115,33 +155,13 @@ async function cmdAdd(args) {
115
155
  const integration = INTEGRATIONS[key];
116
156
  fmt.intro(`Installing ${integration.label}`);
117
157
 
118
- const result = await installIntegration(key, {
119
- home: homedir(),
120
- env: process.env,
121
- dryRun: args['--dry-run'] === true,
122
- silent: false,
123
- });
158
+ const success = await installIntegration(key, { silent: false });
124
159
 
125
- if (integration.type === 'context-mode') {
126
- printContextModeAddResult(result);
127
- if (!result.success) {
128
- const message = result.error === 'partial-config'
129
- ? `${integration.label} was only partially configured`
130
- : `${integration.label} was not configured`;
131
- if (args['--strict'] === true) {
132
- fmt.cancel(message);
133
- }
134
- fmt.outro(message);
135
- return;
136
- }
137
- fmt.outro(args['--dry-run'] === true
138
- ? `${integration.label} install/config dry run complete`
139
- : `${integration.label} configured`);
140
- return;
160
+ if (success) {
161
+ fmt.outro(`✓ ${integration.label} installed successfully`);
162
+ } else {
163
+ fmt.cancel(`Failed to install ${integration.label}`);
141
164
  }
142
-
143
- if (!integrationSucceeded(result)) fmt.cancel(`Failed to install ${integration.label}`);
144
- fmt.outro(`✓ ${integration.label} installed successfully`);
145
165
  }
146
166
 
147
167
  // ────────────────────────────────────────────────────────────────────────────────
@@ -156,11 +176,12 @@ async function cmdRemove(args) {
156
176
  }
157
177
 
158
178
  if (!INTEGRATIONS[key]) {
179
+ const available = Object.keys(INTEGRATIONS);
159
180
  fmt.cancel(
160
181
  [
161
182
  `Unknown integration: ${chalk.red(key)}`,
162
183
  '',
163
- `Available: ${availableKeys()}`,
184
+ `Available: ${available.join(', ')}`,
164
185
  ].join('\n')
165
186
  );
166
187
  }
@@ -168,18 +189,13 @@ async function cmdRemove(args) {
168
189
  const integration = INTEGRATIONS[key];
169
190
  fmt.intro(`Removing ${integration.label}`);
170
191
 
171
- const result = await removeIntegration(key, {
172
- home: homedir(),
173
- dryRun: args['--dry-run'] === true,
174
- silent: false,
175
- });
192
+ const success = await removeIntegration(key, { silent: false });
176
193
 
177
- printWarnings(result.warnings);
178
- printChangedFiles(result.changedFiles);
179
- if (!integrationSucceeded(result)) fmt.cancel(`Failed to remove ${integration.label}`);
180
- fmt.outro(args['--dry-run'] === true
181
- ? `${integration.label} removal dry run complete`
182
- : `✓ ${integration.label} removed successfully`);
194
+ if (success) {
195
+ fmt.outro(`✓ ${integration.label} removed successfully`);
196
+ } else {
197
+ fmt.cancel(`Failed to remove ${integration.label}`);
198
+ }
183
199
  }
184
200
 
185
201
  // ────────────────────────────────────────────────────────────────────────────────
@@ -228,8 +244,8 @@ async function cmdBundle(args) {
228
244
  // Install all
229
245
  let successCount = 0;
230
246
  for (const key of bundle.includes) {
231
- const result = await installIntegration(key, { silent: false });
232
- if (integrationSucceeded(result)) successCount++;
247
+ const success = await installIntegration(key, { silent: false });
248
+ if (success) successCount++;
233
249
  }
234
250
 
235
251
  fmt.outro(
package/ecc.mjs CHANGED
@@ -12,12 +12,7 @@ import { applyStoredStartupPreferences } from "./startup.mjs";
12
12
 
13
13
  const AW_ECC_REPO_SSH = "git@github.com:shreyansh-ghl/aw-ecc.git";
14
14
  const AW_ECC_REPO_HTTPS = "https://github.com/shreyansh-ghl/aw-ecc.git";
15
- export const AW_ECC_TAG = "v1.4.63";
16
- const REQUIRED_ECC_FILES = [
17
- "package.json",
18
- "scripts/install-apply.js",
19
- "scripts/sync-ecc-to-codex.sh",
20
- ];
15
+ export const AW_ECC_TAG = "v1.4.62";
21
16
 
22
17
  const MARKETPLACE_NAME = "aw-marketplace";
23
18
  const PLUGIN_KEY = `aw@${MARKETPLACE_NAME}`;
@@ -90,15 +85,11 @@ async function cloneWithRefAsync(url, ref, dest) {
90
85
  }
91
86
  }
92
87
 
93
- function hasRequiredEccFiles(dest) {
94
- return REQUIRED_ECC_FILES.every((relPath) => existsSync(join(dest, relPath)));
95
- }
96
-
97
88
  async function cloneOrUpdateAsync(tag, dest) {
98
89
  const overrideUrl = process.env.AW_ECC_CLONE_URL;
99
90
  const overrideRef = process.env.AW_ECC_CLONE_REF?.trim();
100
91
 
101
- if (existsSync(join(dest, ".git")) && hasRequiredEccFiles(dest)) {
92
+ if (existsSync(join(dest, ".git"))) {
102
93
  try {
103
94
  if (!overrideUrl && !overrideRef) {
104
95
  await runA(`git -C ${dest} fetch --quiet --depth 1 origin tag ${tag}`);
@@ -111,7 +102,6 @@ async function cloneOrUpdateAsync(tag, dest) {
111
102
  }
112
103
  // Restore working tree — pruneStaleHooks may have deleted tracked files
113
104
  await runA(`git -C ${dest} checkout -- .`);
114
- if (!hasRequiredEccFiles(dest)) throw new Error("aw-ecc checkout is missing required files");
115
105
  return;
116
106
  } catch { /* fall through to fresh clone */ }
117
107
  }
@@ -198,7 +188,7 @@ function cloneOrUpdate(tag, dest) {
198
188
  // AW_ECC_CLONE_REF lets aw init install ECC from a non-default branch or tag.
199
189
  const overrideRef = process.env.AW_ECC_CLONE_REF?.trim();
200
190
 
201
- if (existsSync(join(dest, ".git")) && hasRequiredEccFiles(dest)) {
191
+ if (existsSync(join(dest, ".git"))) {
202
192
  try {
203
193
  if (!overrideUrl && !overrideRef) {
204
194
  run(`git -C ${dest} fetch --quiet --depth 1 origin tag ${tag}`);
@@ -211,7 +201,6 @@ function cloneOrUpdate(tag, dest) {
211
201
  }
212
202
  // Restore working tree — pruneStaleHooks may have deleted tracked files
213
203
  run(`git -C ${dest} checkout -- .`);
214
- if (!hasRequiredEccFiles(dest)) throw new Error("aw-ecc checkout is missing required files");
215
204
  return;
216
205
  } catch { /* fall through to fresh clone */ }
217
206
  }
package/git.mjs CHANGED
@@ -453,7 +453,6 @@ export function detectChanges(awHome, registryDir) {
453
453
  if (!line || line.length < 3) continue;
454
454
  const xy = line.slice(0, 2);
455
455
  const filePath = line.slice(3).trim();
456
- if (filePath === `${registryDir}/.aw-upgrade.log`) continue;
457
456
  const registryPath = filePath.startsWith(registryDir + '/') ? filePath.slice(registryDir.length + 1) : filePath;
458
457
  const entry = { path: filePath, registryPath };
459
458
 
@@ -486,7 +485,6 @@ export function getStagedFiles(awHome, registryDir) {
486
485
  if (!line) continue;
487
486
  const [status, filePath] = line.split('\t');
488
487
  if (!filePath?.startsWith(registryDir + '/')) continue;
489
- if (filePath === `${registryDir}/.aw-upgrade.log`) continue;
490
488
  result.push({
491
489
  path: filePath,
492
490
  registryPath: filePath.slice(registryDir.length + 1),