@ghl-ai/aw 0.1.44 → 0.1.46

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.
@@ -17,7 +17,8 @@
17
17
  # @ghl-ai/aw@0.1.x for reproducible CI runs.
18
18
  set -Eeuo pipefail
19
19
 
20
- : "${GITHUB_PAT:?ERROR: Set GITHUB_PAT in your harness secrets UI before running aw c4}"
20
+ # GitHub auth is resolved inside `aw c4` so GITHUB_PAT, GITHUB_TOKEN, and
21
+ # no-token diagnostics stay centralized in the CLI preflight.
21
22
 
22
23
  # Ensure npm is on PATH. Cursor Cloud's install shell is non-interactive — nvm
23
24
  # is not auto-sourced, and Node may not be pre-installed at all. Walk common
@@ -30,6 +31,7 @@ set -Eeuo pipefail
30
31
  # /usr/local/ or /opt/.
31
32
  # - DEBIAN_FRONTEND=noninteractive prevents debconf prompts from hanging
32
33
  # the apt configure step on a non-TTY shell.
34
+ # - Run apt-get directly when already root; use sudo only for non-root shells.
33
35
  # - Drop the >/dev/null on apt + NodeSource setup so progress is visible.
34
36
  # The previous "silent + interactive" combination produced the canonical
35
37
  # "looks frozen" symptom in pilot use after Cursor Cloud stopped shipping
@@ -57,15 +59,28 @@ ensure_npm() {
57
59
  fi
58
60
  done
59
61
 
60
- if command -v sudo >/dev/null 2>&1 && command -v apt-get >/dev/null 2>&1; then
62
+ if command -v apt-get >/dev/null 2>&1; then
61
63
  echo "[aw-c4-bootstrap] npm not found; installing Node 20 via NodeSource (1-2 min over corporate proxy)"
62
64
  export DEBIAN_FRONTEND=noninteractive
63
- if ! timeout 180 bash -c 'curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -'; then
65
+ node_setup_command='curl -fsSL https://deb.nodesource.com/setup_20.x | bash -'
66
+ apt_install_command=(apt-get install -y nodejs)
67
+ current_uid="$(id -u 2>/dev/null || printf '1')"
68
+ if [ "$current_uid" != "0" ]; then
69
+ if command -v sudo >/dev/null 2>&1; then
70
+ node_setup_command='curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -'
71
+ apt_install_command=(sudo -E apt-get install -y nodejs)
72
+ else
73
+ echo "[aw-c4-bootstrap] FATAL: apt-get is available, but sudo is not, and the current user is not root" >&2
74
+ echo "[aw-c4-bootstrap] Run as root, install sudo, or preinstall Node 20+ in the harness snapshot." >&2
75
+ exit 1
76
+ fi
77
+ fi
78
+ if ! timeout 180 bash -c "$node_setup_command"; then
64
79
  echo "[aw-c4-bootstrap] FATAL: NodeSource setup script timed out or failed after 180s" >&2
65
80
  echo "[aw-c4-bootstrap] Preinstall Node 20+ in the agent snapshot, or check proxy reachability." >&2
66
81
  exit 1
67
82
  fi
68
- if ! timeout 180 sudo -E apt-get install -y nodejs; then
83
+ if ! timeout 180 "${apt_install_command[@]}"; then
69
84
  echo "[aw-c4-bootstrap] FATAL: apt-get install nodejs timed out or failed after 180s" >&2
70
85
  exit 1
71
86
  fi
package/cli.mjs CHANGED
@@ -26,6 +26,7 @@ const COMMANDS = {
26
26
  nuke: () => import('./commands/nuke.mjs').then(m => m.nukeCommand),
27
27
  daemon: () => import('./commands/daemon.mjs').then(m => m.daemonCommand),
28
28
  telemetry: () => import('./commands/telemetry.mjs').then(m => m.telemetryCommand),
29
+ integrations: () => import('./commands/integrations.mjs').then(m => m.integrationsCommand),
29
30
  'slack-sim': () => import('./commands/slack-sim.mjs').then(m => m.slackSimCommand),
30
31
  c4: () => import('./commands/c4.mjs').then(m => m.c4Command),
31
32
  'init-repo': () => import('./commands/init-repo.mjs').then(m => m.initRepoCommand),
@@ -82,8 +83,9 @@ function printHelp() {
82
83
  const sec = (title) => `\n ${chalk.bold.underline(title)}`;
83
84
  const help = [
84
85
  sec('Setup'),
85
- cmd('aw init', 'Initialize workspace (platform/ only)'),
86
+ cmd('aw init', 'Initialize workspace (auto-installs suggested integrations)'),
86
87
  cmd('aw init --namespace <team/sub-team>', 'Add a team namespace (optional)'),
88
+ cmd('aw init --no-integrations', 'Skip integration setup (Codex, Caveman, Graphify, etc)'),
87
89
  ` ${chalk.dim('Teams: platform, revex, mobile, commerce, leadgen, crm, marketplace, ai')}`,
88
90
  ` ${chalk.dim('Example: aw init --namespace revex/courses')}`,
89
91
  cmd('aw init-repo', 'Scaffold cloud-bootstrap files (idempotent, --dry-run/--force/--diff)'),
@@ -109,6 +111,10 @@ function printHelp() {
109
111
  cmd('aw routing status', 'Show global AW session-routing mode for Claude/Cursor/Codex'),
110
112
  cmd('aw routing disable', 'Disable automatic AW session routing globally'),
111
113
  cmd('aw routing enable', 'Re-enable automatic AW session routing globally'),
114
+ cmd('aw integrations', 'Manage third-party integrations (Codex, Caveman, etc)'),
115
+ cmd('aw integrations add <key>', 'Install a specific tool (e.g. codex, caveman)'),
116
+ cmd('aw integrations remove <key>', 'Remove a tool'),
117
+ cmd('aw integrations bundle <name>', 'Install a preset bundle'),
112
118
  cmd('aw drop <path>', 'Stop syncing or delete local content'),
113
119
  cmd('aw nuke', 'Remove entire .aw_registry/ & start fresh'),
114
120
  cmd('aw daemon install', 'Auto-pull on a schedule (macOS launchd / Linux cron)'),
package/commands/init.mjs CHANGED
@@ -34,6 +34,7 @@ import { loadConfig as ensureTelemetryConfig } from '../telemetry.mjs';
34
34
  import { installAwEcc, AW_ECC_TAG } from '../ecc.mjs';
35
35
  import { removeWorkspaceHookDefaults } from '../codex.mjs';
36
36
  import { readHookManifest, pruneStaleHooks, writeHookManifest } from '../hook-cleanup.mjs';
37
+ import { promptAndInstall, autoInstallIntegrations } from '../integrations.mjs';
37
38
  import {
38
39
  initPersistentClone,
39
40
  isValidClone,
@@ -212,6 +213,7 @@ export async function initCommand(args) {
212
213
  let namespace = args['--namespace'] || null;
213
214
  let user = args['--user'] || '';
214
215
  const silent = args['--silent'] === true;
216
+ const skipIntegrations = args['--no-integrations'] === true;
215
217
 
216
218
  // In silent mode, suppress ALL fmt output and show a single spinner.
217
219
  // setSilent(true) makes every fmt.* call a no-op — internal functions
@@ -414,6 +416,12 @@ export async function initCommand(args) {
414
416
  // Write hook manifest after all hook installation is complete
415
417
  try { writeHookManifest({ eccVersion: AW_ECC_TAG, awVersion: VERSION }); } catch { /* best effort */ }
416
418
 
419
+ // Auto-install suggested integrations (Codex, Caveman, Graphify, etc) - unless --no-integrations
420
+ let installedIntegrations = [];
421
+ if (!silent && !skipIntegrations && !isNewSubTeam) {
422
+ installedIntegrations = await autoInstallIntegrations(freshCfg?.namespace || team, { silent });
423
+ }
424
+
417
425
  if (silent) {
418
426
  if (silentSpinner) { silentSpinner.stop('Done'); setSilent(false); }
419
427
  autoUpdate(await args._updateCheck);
@@ -427,6 +435,7 @@ export async function initCommand(args) {
427
435
  ? ` ${chalk.green('✓')} Removed ${removedLegacyStartupFiles.length} legacy repo startup file${removedLegacyStartupFiles.length > 1 ? 's' : ''}`
428
436
  : null,
429
437
  cwd !== HOME && isWorktree(join(cwd, '.aw')) ? ` ${chalk.green('✓')} Project linked` : null,
438
+ installedIntegrations.length > 0 ? ` ${chalk.green('✓')} Integrations: ${installedIntegrations.join(', ')}` : null,
430
439
  ].filter(Boolean).join('\n'));
431
440
  }
432
441
  return;
@@ -574,6 +583,12 @@ export async function initCommand(args) {
574
583
  // Ensure telemetry config exists (generates machine_id on first run)
575
584
  ensureTelemetryConfig();
576
585
 
586
+ // Auto-install suggested integrations (Codex, Caveman, Graphify, etc) - unless --no-integrations
587
+ let installedIntegrations = [];
588
+ if (!silent && !skipIntegrations) {
589
+ installedIntegrations = await autoInstallIntegrations(team, { silent });
590
+ }
591
+
577
592
  // Offer to update if a newer version is available
578
593
  if (!silent) await promptUpdate(await args._updateCheck);
579
594
 
@@ -593,10 +608,12 @@ export async function initCommand(args) {
593
608
  hooksInstalled ? ` ${chalk.green('✓')} Git hooks: auto-sync on pull/clone (core.hooksPath)` : null,
594
609
  ` ${chalk.green('✓')} IDE task: auto-sync on workspace open`,
595
610
  cwd !== HOME && isWorktree(join(cwd, '.aw')) ? ` ${chalk.green('✓')} Linked in current project` : null,
611
+ installedIntegrations.length > 0 ? ` ${chalk.green('✓')} Integrations: ${installedIntegrations.join(', ')}` : null,
596
612
  '',
597
613
  ` ${chalk.dim('Existing repos:')} ${chalk.bold('cd <project> && aw link')}`,
598
614
  ` ${chalk.dim('New clones:')} auto-linked via git hook`,
599
615
  ` ${chalk.dim('Update:')} ${chalk.bold('aw init')} ${chalk.dim('(or auto on pull/IDE open)')}`,
616
+ ` ${chalk.dim('Integrations:')} ${chalk.bold('aw integrations')} ${chalk.dim('(manage Codex, Caveman, etc)')}`,
600
617
  ` ${chalk.dim('Uninstall:')} ${chalk.bold('aw nuke')}`,
601
618
  ].filter(Boolean).join('\n'));
602
619
  }
@@ -0,0 +1,254 @@
1
+ // commands/integrations.mjs — CLI command: aw integrations add/remove/list/bundle
2
+
3
+ import * as p from '@clack/prompts';
4
+ import * as fmt from '../fmt.mjs';
5
+ import { chalk } from '../fmt.mjs';
6
+ import {
7
+ INTEGRATIONS,
8
+ BUNDLES,
9
+ installIntegration,
10
+ removeIntegration,
11
+ getInstalledList,
12
+ } from '../integrations.mjs';
13
+
14
+ export async function integrationsCommand(args) {
15
+ const subcommand = args._positional[0];
16
+
17
+ switch (subcommand) {
18
+ case 'add':
19
+ return cmdAdd(args);
20
+ case 'remove':
21
+ return cmdRemove(args);
22
+ case 'bundle':
23
+ return cmdBundle(args);
24
+ case 'list':
25
+ case undefined:
26
+ return cmdList();
27
+ default:
28
+ fmt.cancel(`Unknown subcommand: ${subcommand}`);
29
+ }
30
+ }
31
+
32
+ // ────────────────────────────────────────────────────────────────────────────────
33
+ // aw integrations list
34
+ // ────────────────────────────────────────────────────────────────────────────────
35
+
36
+ async function cmdList() {
37
+ fmt.banner('Integrations', {
38
+ icon: '🔗',
39
+ subtitle: ' Available tools and MCP servers',
40
+ });
41
+
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'
56
+ );
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
+
126
+ fmt.logMessage(`\n${chalk.dim('Commands:')}`);
127
+ fmt.logMessage(` aw integrations add <key> Install a specific tool`);
128
+ fmt.logMessage(` aw integrations remove <key> Remove a tool`);
129
+ fmt.logMessage(` aw integrations bundle <name> Install a preset bundle`);
130
+ }
131
+
132
+ // ────────────────────────────────────────────────────────────────────────────────
133
+ // aw integrations add <key>
134
+ // ────────────────────────────────────────────────────────────────────────────────
135
+
136
+ async function cmdAdd(args) {
137
+ const key = args._positional[1];
138
+
139
+ if (!key) {
140
+ fmt.cancel('Usage: aw integrations add <key>');
141
+ }
142
+
143
+ if (!INTEGRATIONS[key]) {
144
+ // Suggest similar keys
145
+ const available = Object.keys(INTEGRATIONS);
146
+ fmt.cancel(
147
+ [
148
+ `Unknown integration: ${chalk.red(key)}`,
149
+ '',
150
+ `Available: ${available.join(', ')}`,
151
+ ].join('\n')
152
+ );
153
+ }
154
+
155
+ const integration = INTEGRATIONS[key];
156
+ fmt.intro(`Installing ${integration.label}`);
157
+
158
+ const success = await installIntegration(key, { silent: false });
159
+
160
+ if (success) {
161
+ fmt.outro(`✓ ${integration.label} installed successfully`);
162
+ } else {
163
+ fmt.cancel(`Failed to install ${integration.label}`);
164
+ }
165
+ }
166
+
167
+ // ────────────────────────────────────────────────────────────────────────────────
168
+ // aw integrations remove <key>
169
+ // ────────────────────────────────────────────────────────────────────────────────
170
+
171
+ async function cmdRemove(args) {
172
+ const key = args._positional[1];
173
+
174
+ if (!key) {
175
+ fmt.cancel('Usage: aw integrations remove <key>');
176
+ }
177
+
178
+ if (!INTEGRATIONS[key]) {
179
+ const available = Object.keys(INTEGRATIONS);
180
+ fmt.cancel(
181
+ [
182
+ `Unknown integration: ${chalk.red(key)}`,
183
+ '',
184
+ `Available: ${available.join(', ')}`,
185
+ ].join('\n')
186
+ );
187
+ }
188
+
189
+ const integration = INTEGRATIONS[key];
190
+ fmt.intro(`Removing ${integration.label}`);
191
+
192
+ const success = await removeIntegration(key, { silent: false });
193
+
194
+ if (success) {
195
+ fmt.outro(`✓ ${integration.label} removed successfully`);
196
+ } else {
197
+ fmt.cancel(`Failed to remove ${integration.label}`);
198
+ }
199
+ }
200
+
201
+ // ────────────────────────────────────────────────────────────────────────────────
202
+ // aw integrations bundle <bundleName>
203
+ // ────────────────────────────────────────────────────────────────────────────────
204
+
205
+ async function cmdBundle(args) {
206
+ const bundleName = args._positional[1];
207
+
208
+ if (!bundleName) {
209
+ fmt.cancel('Usage: aw integrations bundle <name>');
210
+ }
211
+
212
+ if (!BUNDLES[bundleName]) {
213
+ const available = Object.keys(BUNDLES);
214
+ fmt.cancel(
215
+ [
216
+ `Unknown bundle: ${chalk.red(bundleName)}`,
217
+ '',
218
+ `Available: ${available.join(', ')}`,
219
+ ].join('\n')
220
+ );
221
+ }
222
+
223
+ const bundle = BUNDLES[bundleName];
224
+ fmt.intro(`Installing bundle: ${bundle.label}`);
225
+
226
+ fmt.logMessage(`${bundle.description}\n`);
227
+ fmt.logMessage(`Includes:`);
228
+ for (const key of bundle.includes) {
229
+ const integration = INTEGRATIONS[key];
230
+ fmt.logMessage(` • ${integration.label}`);
231
+ }
232
+
233
+ const confirm = await p.default.confirm({
234
+ message: `Continue installing ${bundle.includes.length} tool(s)?`,
235
+ initialValue: true,
236
+ });
237
+
238
+ if (p.default.isCancel(confirm) || !confirm) {
239
+ fmt.cancel('Cancelled');
240
+ }
241
+
242
+ fmt.logMessage('');
243
+
244
+ // Install all
245
+ let successCount = 0;
246
+ for (const key of bundle.includes) {
247
+ const success = await installIntegration(key, { silent: false });
248
+ if (success) successCount++;
249
+ }
250
+
251
+ fmt.outro(
252
+ `✓ Bundle installation complete (${successCount}/${bundle.includes.length} installed)`
253
+ );
254
+ }