@ghl-ai/aw 0.1.37-beta.67 → 0.1.37-beta.69
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/bin.js +2 -2
- package/cli.mjs +25 -11
- package/commands/daemon.mjs +4 -4
- package/commands/init.mjs +15 -35
- package/commands/link-project.mjs +4 -11
- package/commands/nuke.mjs +23 -28
- package/commands/protocol.mjs +107 -0
- package/commands/pull.mjs +5 -84
- package/commands/push-rules.mjs +2 -2
- package/commands/push.mjs +15 -8
- package/commands/search.mjs +1 -1
- package/commands/slack-sim.mjs +128 -0
- package/commands/telemetry.mjs +395 -0
- package/config.mjs +1 -1
- package/constants.mjs +50 -0
- package/ecc.mjs +92 -89
- package/fmt.mjs +14 -0
- package/hooks/activity-tracker.js +61 -0
- package/hooks/capabilities/telemetry.mjs +880 -0
- package/hooks/manifest.mjs +25 -0
- package/hooks/shared/dispatch.mjs +83 -0
- package/hooks/shared/pre-compact.mjs +5 -0
- package/hooks/shared/session-end.mjs +5 -0
- package/hooks/shared/session-start.mjs +5 -0
- package/hooks/shared/stop.mjs +5 -0
- package/hooks/telemetry-stop.js +411 -0
- package/hooks.mjs +107 -6
- package/integrate.mjs +243 -172
- package/link.mjs +1 -36
- package/package.json +10 -10
- package/paths.mjs +1 -1
- package/registry.mjs +1 -1
- package/render-rules.mjs +8 -48
- package/slack-sim/fake-slack.mjs +200 -0
- package/slack-sim/http.mjs +170 -0
- package/slack-sim/in-process.mjs +263 -0
- package/slack-sim/render.mjs +42 -0
- package/slack-sim/scenario.mjs +64 -0
- package/slack-sim/scenarios/checkpoint-approve.json +21 -0
- package/slack-sim/scenarios/image-thread.json +27 -0
- package/slack-sim/scenarios/implementation-basic.json +18 -0
- package/slack-sim/scenarios/poll-webhook-race.json +18 -0
- package/slack-sim/scenarios/review-pr.json +14 -0
- package/telemetry.mjs +233 -0
- package/update.mjs +6 -1
- package/codex.mjs +0 -828
- package/commands/doctor.mjs +0 -927
- package/commands/startup.mjs +0 -87
- package/startup.mjs +0 -531
package/bin.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { run } from './cli.mjs';
|
|
3
|
+
run(process.argv.slice(2));
|
package/cli.mjs
CHANGED
|
@@ -4,8 +4,9 @@ import { readFileSync } from 'node:fs';
|
|
|
4
4
|
import { join, dirname } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import * as fmt from './fmt.mjs';
|
|
7
|
-
import { chalk } from './fmt.mjs';
|
|
7
|
+
import { chalk, CancelError } from './fmt.mjs';
|
|
8
8
|
import { checkForUpdate, notifyUpdate } from './update.mjs';
|
|
9
|
+
import { startSpan } from './telemetry.mjs';
|
|
9
10
|
|
|
10
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
const VERSION = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8')).version;
|
|
@@ -17,13 +18,11 @@ const COMMANDS = {
|
|
|
17
18
|
'push-rules': () => import('./commands/push-rules.mjs').then(m => m.pushRulesCommand),
|
|
18
19
|
drop: () => import('./commands/drop.mjs').then(m => m.dropCommand),
|
|
19
20
|
status: () => import('./commands/status.mjs').then(m => m.statusCommand),
|
|
20
|
-
doctor: () => import('./commands/doctor.mjs').then(m => m.doctorCommand),
|
|
21
|
-
routing: () => import('./commands/startup.mjs').then(m => m.routingCommand),
|
|
22
|
-
startup: () => import('./commands/startup.mjs').then(m => m.startupCommand),
|
|
23
21
|
search: () => import('./commands/search.mjs').then(m => m.searchCommand),
|
|
24
22
|
link: () => import('./commands/link-project.mjs').then(m => m.linkProjectCommand),
|
|
25
23
|
nuke: () => import('./commands/nuke.mjs').then(m => m.nukeCommand),
|
|
26
24
|
daemon: () => import('./commands/daemon.mjs').then(m => m.daemonCommand),
|
|
25
|
+
telemetry: () => import('./commands/telemetry.mjs').then(m => m.telemetryCommand),
|
|
27
26
|
};
|
|
28
27
|
|
|
29
28
|
function parseArgs(argv) {
|
|
@@ -98,17 +97,20 @@ function printHelp() {
|
|
|
98
97
|
|
|
99
98
|
sec('Manage'),
|
|
100
99
|
cmd('aw status', 'Show synced paths, modified files & conflicts'),
|
|
101
|
-
cmd('aw doctor', 'Run a health check for routing, MCP, plugin, and AW ECC surfaces'),
|
|
102
100
|
cmd('aw link', 'Link current project as a git worktree (wires IDE symlinks)'),
|
|
103
|
-
cmd('aw routing status', 'Show global AW session-routing mode for Claude/Cursor/Codex'),
|
|
104
|
-
cmd('aw routing disable', 'Disable automatic AW session routing globally'),
|
|
105
|
-
cmd('aw routing enable', 'Re-enable automatic AW session routing globally'),
|
|
106
101
|
cmd('aw drop <path>', 'Stop syncing or delete local content'),
|
|
107
102
|
cmd('aw nuke', 'Remove entire .aw_registry/ & start fresh'),
|
|
108
103
|
cmd('aw daemon install', 'Auto-pull on a schedule (macOS launchd / Linux cron)'),
|
|
109
104
|
cmd('aw daemon install --interval 30m', 'Set custom interval (e.g. 30m, 2h, 3600)'),
|
|
110
105
|
cmd('aw daemon uninstall', 'Stop the background daemon'),
|
|
111
106
|
cmd('aw daemon status', 'Check if daemon is running'),
|
|
107
|
+
cmd('aw slack-sim run <scenario>', 'Replay Slack-like scenarios against real runtime'),
|
|
108
|
+
cmd('aw slack-sim list-scenarios', 'List built-in Slack simulator scenarios'),
|
|
109
|
+
|
|
110
|
+
sec('Settings'),
|
|
111
|
+
cmd('aw telemetry status', 'Show telemetry status'),
|
|
112
|
+
cmd('aw telemetry disable', 'Opt out of anonymous analytics'),
|
|
113
|
+
cmd('aw telemetry enable', 'Re-enable analytics'),
|
|
112
114
|
|
|
113
115
|
sec('Examples'),
|
|
114
116
|
'',
|
|
@@ -158,9 +160,21 @@ export async function run(argv) {
|
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
if (command && COMMANDS[command]) {
|
|
163
|
+
const span = await startSpan(command, args);
|
|
164
|
+
span.notice();
|
|
161
165
|
args._updateCheck = updateCheck;
|
|
162
|
-
|
|
163
|
-
|
|
166
|
+
try {
|
|
167
|
+
const handler = await COMMANDS[command]();
|
|
168
|
+
await handler(args);
|
|
169
|
+
await span.end({ status: 'completed' });
|
|
170
|
+
} catch (err) {
|
|
171
|
+
if (err instanceof CancelError) {
|
|
172
|
+
await span.end({ status: 'cancelled', error_type: 'CancelError' });
|
|
173
|
+
process.exit(err.exitCode ?? 1);
|
|
174
|
+
}
|
|
175
|
+
await span.end({ status: 'failed', error_type: err.constructor.name });
|
|
176
|
+
throw err;
|
|
177
|
+
}
|
|
164
178
|
notifyUpdate(await updateCheck);
|
|
165
179
|
return;
|
|
166
180
|
}
|
|
@@ -170,5 +184,5 @@ export async function run(argv) {
|
|
|
170
184
|
process.exit(0);
|
|
171
185
|
}
|
|
172
186
|
|
|
173
|
-
fmt.
|
|
187
|
+
fmt.cancelAndExit(`Unknown command: ${command}`);
|
|
174
188
|
}
|
package/commands/daemon.mjs
CHANGED
|
@@ -39,9 +39,9 @@ async function installLaunchd(intervalSeconds) {
|
|
|
39
39
|
|
|
40
40
|
<key>ProgramArguments</key>
|
|
41
41
|
<array>
|
|
42
|
-
<string
|
|
43
|
-
<string
|
|
44
|
-
<string
|
|
42
|
+
<string>/bin/sh</string>
|
|
43
|
+
<string>-c</string>
|
|
44
|
+
<string>'${awBin}' pull --silent && '${awBin}' telemetry flush 2>/dev/null</string>
|
|
45
45
|
</array>
|
|
46
46
|
|
|
47
47
|
<key>StartInterval</key>
|
|
@@ -113,7 +113,7 @@ async function installCron(intervalSeconds) {
|
|
|
113
113
|
if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
|
|
114
114
|
|
|
115
115
|
const cronExpr = toCronExpression(intervalSeconds);
|
|
116
|
-
const cronLine = `${cronExpr} ${awBin} pull --silent >> ${logDir}/pull.log 2>&1 # aw-daemon`;
|
|
116
|
+
const cronLine = `${cronExpr} /bin/sh -c '${awBin} pull --silent && ${awBin} telemetry flush 2>/dev/null' >> ${logDir}/pull.log 2>&1 # aw-daemon`;
|
|
117
117
|
|
|
118
118
|
let current = '';
|
|
119
119
|
try { current = execSync('crontab -l 2>/dev/null', { encoding: 'utf8' }); } catch { /* empty */ }
|
package/commands/init.mjs
CHANGED
|
@@ -23,13 +23,12 @@ import * as config from '../config.mjs';
|
|
|
23
23
|
import * as fmt from '../fmt.mjs';
|
|
24
24
|
import { chalk } from '../fmt.mjs';
|
|
25
25
|
import { linkWorkspace } from '../link.mjs';
|
|
26
|
-
import { generateCommands, copyInstructions, initAwDocs,
|
|
26
|
+
import { generateCommands, copyInstructions, initAwDocs, installIdeHooks } from '../integrate.mjs';
|
|
27
27
|
import { setupMcp } from '../mcp.mjs';
|
|
28
|
-
import {
|
|
28
|
+
import { installLocalCommitHook } from '../hooks.mjs';
|
|
29
29
|
import { autoUpdate, promptUpdate } from '../update.mjs';
|
|
30
30
|
import { installGlobalHooks } from '../hooks.mjs';
|
|
31
31
|
import { installAwEcc } from '../ecc.mjs';
|
|
32
|
-
import { removeWorkspaceHookDefaults } from '../codex.mjs';
|
|
33
32
|
import {
|
|
34
33
|
initPersistentClone,
|
|
35
34
|
isValidClone,
|
|
@@ -58,19 +57,6 @@ const HOME = (() => { try { return realpathSync(_rawHome); } catch { return _raw
|
|
|
58
57
|
const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
|
|
59
58
|
const AW_HOME = join(HOME, '.aw');
|
|
60
59
|
|
|
61
|
-
function syncInstructionsAndAwDocs(targetDir, namespace) {
|
|
62
|
-
copyInstructions(targetDir, null, namespace);
|
|
63
|
-
initAwDocs(targetDir);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function syncHomeAndProjectInstructions(cwd, namespace) {
|
|
67
|
-
syncHomeHarnessInstructions(HOME);
|
|
68
|
-
initAwDocs(HOME);
|
|
69
|
-
if (cwd !== HOME) {
|
|
70
|
-
syncInstructionsAndAwDocs(cwd, namespace);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
60
|
// ── Ensure ~/.aw/.gitignore has personal/local entries ───────────────────
|
|
75
61
|
|
|
76
62
|
const AW_GITIGNORE_ENTRIES = [
|
|
@@ -100,7 +86,7 @@ function installIdeTasks() {
|
|
|
100
86
|
{
|
|
101
87
|
label: 'aw: sync registry',
|
|
102
88
|
type: 'shell',
|
|
103
|
-
command: 'aw init --silent',
|
|
89
|
+
command: 'AW_TRIGGER=ide:task aw init --silent',
|
|
104
90
|
presentation: { reveal: 'silent', panel: 'shared', close: true },
|
|
105
91
|
runOptions: { runOn: 'folderOpen' },
|
|
106
92
|
problemMatcher: [],
|
|
@@ -221,7 +207,7 @@ export async function initCommand(args) {
|
|
|
221
207
|
}
|
|
222
208
|
|
|
223
209
|
if (choice === 'platform-only') {
|
|
224
|
-
namespace =
|
|
210
|
+
namespace = 'platform'; team = 'platform'; subTeam = null; folderName = null;
|
|
225
211
|
}
|
|
226
212
|
}
|
|
227
213
|
|
|
@@ -274,11 +260,11 @@ export async function initCommand(args) {
|
|
|
274
260
|
}
|
|
275
261
|
|
|
276
262
|
await installAwEcc(cwd, { silent });
|
|
277
|
-
|
|
278
|
-
|
|
263
|
+
copyInstructions(HOME, null, freshCfg?.namespace || team) || [];
|
|
264
|
+
initAwDocs(HOME);
|
|
279
265
|
await setupMcp(HOME, freshCfg?.namespace || team, { silent });
|
|
280
|
-
const removedLegacyStartupFiles = cwd !== HOME ? removeWorkspaceHookDefaults(cwd) : [];
|
|
281
266
|
installGlobalHooks();
|
|
267
|
+
installIdeHooks();
|
|
282
268
|
|
|
283
269
|
// Remove old local .git/hooks/post-checkout that pre-dates core.hooksPath (creates stale .aw_registry symlink)
|
|
284
270
|
if (cwd !== HOME) {
|
|
@@ -309,6 +295,7 @@ export async function initCommand(args) {
|
|
|
309
295
|
const awDirForLinks = (projectRegistryDir && existsSync(projectRegistryDir)) ? projectRegistryDir : null;
|
|
310
296
|
const symlinks = linkWorkspace(HOME, awDirForLinks, { silent: true });
|
|
311
297
|
const commands = generateCommands(HOME, { silent: true });
|
|
298
|
+
if (cwd !== HOME) installLocalCommitHook(cwd);
|
|
312
299
|
|
|
313
300
|
if (silent) {
|
|
314
301
|
autoUpdate(await args._updateCheck);
|
|
@@ -318,9 +305,6 @@ export async function initCommand(args) {
|
|
|
318
305
|
'',
|
|
319
306
|
` ${chalk.green('✓')} Registry synced`,
|
|
320
307
|
` ${chalk.green('✓')} IDE refreshed — ${chalk.bold(symlinks)} symlinks · ${chalk.bold(commands)} commands`,
|
|
321
|
-
removedLegacyStartupFiles.length > 0
|
|
322
|
-
? ` ${chalk.green('✓')} Removed ${removedLegacyStartupFiles.length} legacy repo startup file${removedLegacyStartupFiles.length > 1 ? 's' : ''}`
|
|
323
|
-
: null,
|
|
324
308
|
cwd !== HOME && isWorktree(join(cwd, '.aw')) ? ` ${chalk.green('✓')} Project linked` : null,
|
|
325
309
|
].filter(Boolean).join('\n'));
|
|
326
310
|
}
|
|
@@ -345,7 +329,7 @@ export async function initCommand(args) {
|
|
|
345
329
|
|
|
346
330
|
if (!user) {
|
|
347
331
|
try {
|
|
348
|
-
user = execSync('git config user.name', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
332
|
+
user = execSync('git config --global user.name', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
349
333
|
} catch { /* git not configured */ }
|
|
350
334
|
}
|
|
351
335
|
|
|
@@ -396,8 +380,8 @@ export async function initCommand(args) {
|
|
|
396
380
|
}
|
|
397
381
|
}
|
|
398
382
|
|
|
399
|
-
// Create sync config
|
|
400
|
-
const cfg = config.create(GLOBAL_AW_DIR, { namespace: team, user });
|
|
383
|
+
// Create sync config — default to 'platform' when no namespace specified
|
|
384
|
+
const cfg = config.create(GLOBAL_AW_DIR, { namespace: team || 'platform', user });
|
|
401
385
|
if (folderName) {
|
|
402
386
|
config.addPattern(GLOBAL_AW_DIR, folderName);
|
|
403
387
|
}
|
|
@@ -407,12 +391,11 @@ export async function initCommand(args) {
|
|
|
407
391
|
|
|
408
392
|
// Step 3: Setup tasks, MCP, hooks
|
|
409
393
|
await installAwEcc(cwd, { silent });
|
|
410
|
-
|
|
411
|
-
|
|
394
|
+
const instructionFiles = copyInstructions(HOME, null, team) || [];
|
|
395
|
+
initAwDocs(HOME);
|
|
412
396
|
const mcpFiles = await setupMcp(HOME, team, { silent }) || [];
|
|
413
|
-
applyStoredStartupPreferences(HOME);
|
|
414
|
-
const removedLegacyStartupFiles = cwd !== HOME ? removeWorkspaceHookDefaults(cwd) : [];
|
|
415
397
|
const hooksInstalled = installGlobalHooks();
|
|
398
|
+
installIdeHooks();
|
|
416
399
|
installIdeTasks();
|
|
417
400
|
|
|
418
401
|
// Remove old local .git/hooks/post-checkout that pre-dates core.hooksPath
|
|
@@ -444,6 +427,7 @@ export async function initCommand(args) {
|
|
|
444
427
|
const projectRegistryDir = cwd !== HOME ? join(cwd, '.aw', REGISTRY_DIR) : null;
|
|
445
428
|
const awDirForLinks = (projectRegistryDir && existsSync(projectRegistryDir)) ? projectRegistryDir : null;
|
|
446
429
|
const symlinks = linkWorkspace(HOME, awDirForLinks, { silent: true });
|
|
430
|
+
if (cwd !== HOME) installLocalCommitHook(cwd);
|
|
447
431
|
ideSpinner.message('Generating commands...');
|
|
448
432
|
const commands = generateCommands(HOME, { silent: true });
|
|
449
433
|
ideSpinner.stop(`IDE wired — ${chalk.bold(symlinks)} symlinks · ${chalk.bold(commands)} commands`);
|
|
@@ -457,10 +441,6 @@ export async function initCommand(args) {
|
|
|
457
441
|
` ${chalk.green('✓')} Source of truth: ~/.aw/ (git clone)`,
|
|
458
442
|
` ${chalk.green('✓')} Symlink: ~/.aw_registry/ → ~/.aw/.aw_registry/`,
|
|
459
443
|
` ${chalk.green('✓')} IDE integration: ~/.claude/, ~/.cursor/, ~/.codex/`,
|
|
460
|
-
cwd !== HOME ? ` ${chalk.green('✓')} Global startup managed from ~/.claude/, ~/.cursor/, ~/.codex/` : null,
|
|
461
|
-
removedLegacyStartupFiles.length > 0
|
|
462
|
-
? ` ${chalk.green('✓')} Removed ${removedLegacyStartupFiles.length} legacy repo startup file${removedLegacyStartupFiles.length > 1 ? 's' : ''}`
|
|
463
|
-
: null,
|
|
464
444
|
hooksInstalled ? ` ${chalk.green('✓')} Git hooks: auto-sync on pull/clone (core.hooksPath)` : null,
|
|
465
445
|
` ${chalk.green('✓')} IDE task: auto-sync on workspace open`,
|
|
466
446
|
cwd !== HOME && isWorktree(join(cwd, '.aw')) ? ` ${chalk.green('✓')} Linked in current project` : null,
|
|
@@ -9,8 +9,7 @@ import { addProjectWorktree, isWorktree, isValidClone } from '../git.mjs';
|
|
|
9
9
|
import { REGISTRY_DIR, REGISTRY_URL } from '../constants.mjs';
|
|
10
10
|
import { linkWorkspace } from '../link.mjs';
|
|
11
11
|
import { generateCommands } from '../integrate.mjs';
|
|
12
|
-
import {
|
|
13
|
-
import { applyStoredStartupPreferences } from '../startup.mjs';
|
|
12
|
+
import { installLocalCommitHook } from '../hooks.mjs';
|
|
14
13
|
|
|
15
14
|
const HOME = homedir();
|
|
16
15
|
const AW_HOME = join(HOME, '.aw');
|
|
@@ -42,9 +41,8 @@ export function linkProjectCommand(args) {
|
|
|
42
41
|
const awDirForLinks = existsSync(projectRegistryDir) ? projectRegistryDir : null;
|
|
43
42
|
const symlinks = linkWorkspace(HOME, awDirForLinks, { silent: true });
|
|
44
43
|
const commands = generateCommands(HOME, { silent: true });
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
fmt.logSuccess(`Already linked — refreshed ${chalk.bold(symlinks)} IDE symlinks · ${chalk.bold(commands)} commands${removedLegacyStartupFiles.length > 0 ? ` · removed ${removedLegacyStartupFiles.length} legacy repo startup file${removedLegacyStartupFiles.length > 1 ? 's' : ''}` : ''}`);
|
|
44
|
+
installLocalCommitHook(cwd);
|
|
45
|
+
fmt.logSuccess(`Already linked — refreshed ${chalk.bold(symlinks)} IDE symlinks · ${chalk.bold(commands)} commands`);
|
|
48
46
|
return;
|
|
49
47
|
}
|
|
50
48
|
|
|
@@ -54,18 +52,13 @@ export function linkProjectCommand(args) {
|
|
|
54
52
|
const awDirForLinks = existsSync(projectRegistryDir) ? projectRegistryDir : null;
|
|
55
53
|
const symlinks = linkWorkspace(HOME, awDirForLinks, { silent: true });
|
|
56
54
|
const commands = generateCommands(HOME, { silent: true });
|
|
57
|
-
|
|
58
|
-
const removedLegacyStartupFiles = removeWorkspaceHookDefaults(cwd);
|
|
55
|
+
installLocalCommitHook(cwd);
|
|
59
56
|
fmt.logSuccess([
|
|
60
57
|
`Project linked — ${chalk.bold(symlinks)} IDE symlinks · ${chalk.bold(commands)} commands`,
|
|
61
58
|
'',
|
|
62
59
|
` ${chalk.green('✓')} ${chalk.dim('.aw/')} git worktree (IDE git panel enabled)`,
|
|
63
60
|
` ${chalk.green('✓')} ${chalk.dim(`.aw/${REGISTRY_DIR}/`)} registry content`,
|
|
64
61
|
` ${chalk.green('✓')} ${chalk.dim('.claude/.cursor/.codex/')} IDE symlinks wired`,
|
|
65
|
-
` ${chalk.green('✓')} ${chalk.dim('startup mode')} global-first via ~/.claude/, ~/.cursor/, ~/.codex/`,
|
|
66
|
-
removedLegacyStartupFiles.length > 0
|
|
67
|
-
? ` ${chalk.green('✓')} ${chalk.dim('legacy repo hooks')} removed ${removedLegacyStartupFiles.length} AW-managed file${removedLegacyStartupFiles.length > 1 ? 's' : ''}`
|
|
68
|
-
: null,
|
|
69
62
|
].join('\n'));
|
|
70
63
|
} catch (e) {
|
|
71
64
|
fmt.cancel(`Failed to link project: ${e.message}`);
|
package/commands/nuke.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Safety guarantee: NEVER deletes files that AW didn't create.
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { join } from 'node:path';
|
|
6
6
|
import { existsSync, rmSync, lstatSync, unlinkSync, readdirSync, readFileSync, readlinkSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { homedir } from 'node:os';
|
|
8
8
|
import { execSync, exec as execCb } from 'node:child_process';
|
|
@@ -15,14 +15,13 @@ import { removeGlobalHooks } from '../hooks.mjs';
|
|
|
15
15
|
import { uninstallAwEcc } from '../ecc.mjs';
|
|
16
16
|
import { removeMcpConfig } from '../mcp.mjs';
|
|
17
17
|
import { listProjectWorktrees } from '../git.mjs';
|
|
18
|
-
import { removeWorkspaceHookDefaults } from '../codex.mjs';
|
|
19
18
|
|
|
20
19
|
const HOME = homedir();
|
|
21
20
|
const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
|
|
22
21
|
const MANIFEST_PATH = join(GLOBAL_AW_DIR, '.aw-manifest.json');
|
|
23
22
|
|
|
24
23
|
const IDE_DIRS = ['.claude', '.cursor', '.codex', '.agents'];
|
|
25
|
-
const CONTENT_TYPES = ['agents', 'skills', 'commands', 'evals'
|
|
24
|
+
const CONTENT_TYPES = ['agents', 'skills', 'commands', 'evals'];
|
|
26
25
|
|
|
27
26
|
function loadManifest() {
|
|
28
27
|
if (!existsSync(MANIFEST_PATH)) return null;
|
|
@@ -140,27 +139,28 @@ async function removeProjectSymlinks() {
|
|
|
140
139
|
{ encoding: 'utf8', timeout: 30000 }
|
|
141
140
|
);
|
|
142
141
|
for (const linkPath of registryLinks.trim().split('\n').filter(Boolean)) {
|
|
143
|
-
try {
|
|
144
|
-
removeWorkspaceHookDefaults(dirname(linkPath));
|
|
145
|
-
unlinkSync(linkPath);
|
|
146
|
-
removed++;
|
|
147
|
-
} catch { /* best effort */ }
|
|
142
|
+
try { unlinkSync(linkPath); removed++; } catch { /* best effort */ }
|
|
148
143
|
}
|
|
149
144
|
|
|
150
145
|
// Also remove legacy local .git/hooks/post-checkout installed by old aw versions
|
|
146
|
+
// and prepare-commit-msg hooks installed by installLocalCommitHook
|
|
151
147
|
let hooksRemoved = 0;
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
{
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
148
|
+
const hookNames = ['post-checkout', 'prepare-commit-msg'];
|
|
149
|
+
for (const hookName of hookNames) {
|
|
150
|
+
const { stdout: hookFiles } = await exec(
|
|
151
|
+
`find "${HOME}" -maxdepth 5 -path "*/.git/hooks/${hookName}" -type f 2>/dev/null || true`,
|
|
152
|
+
{ encoding: 'utf8', timeout: 30000 }
|
|
153
|
+
);
|
|
154
|
+
for (const hookPath of hookFiles.trim().split('\n').filter(Boolean)) {
|
|
155
|
+
try {
|
|
156
|
+
const content = readFileSync(hookPath, 'utf8');
|
|
157
|
+
// Only remove hooks that AW installed — identified by our marker comment
|
|
158
|
+
if (content.includes('aw:') || content.includes('aw: auto-link registry') || content.includes('ln -s "$AW_REGISTRY"')) {
|
|
159
|
+
unlinkSync(hookPath);
|
|
160
|
+
hooksRemoved++;
|
|
161
|
+
}
|
|
162
|
+
} catch { /* best effort */ }
|
|
163
|
+
}
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
return { removed, hooksRemoved };
|
|
@@ -209,20 +209,15 @@ function removeIdeTasks() {
|
|
|
209
209
|
|
|
210
210
|
export async function nukeCommand(args) {
|
|
211
211
|
// Catch unhandled errors and surface them instead of letting clack show generic "Something went wrong"
|
|
212
|
-
process.on('uncaughtException', (e) => { fmt.
|
|
213
|
-
process.on('unhandledRejection', (e) => { fmt.
|
|
212
|
+
process.on('uncaughtException', (e) => { fmt.cancelAndExit(`Unexpected error: ${e.message}`); });
|
|
213
|
+
process.on('unhandledRejection', (e) => { fmt.cancelAndExit(`Unexpected error: ${e?.message ?? e}`); });
|
|
214
214
|
|
|
215
215
|
fmt.intro('aw nuke');
|
|
216
216
|
|
|
217
217
|
// Remove stale local .aw_registry symlink if present (skip if cwd IS home — that's the global one)
|
|
218
218
|
if (process.cwd() !== HOME) {
|
|
219
219
|
const local = join(process.cwd(), '.aw_registry');
|
|
220
|
-
try {
|
|
221
|
-
if (lstatSync(local).isSymbolicLink()) {
|
|
222
|
-
removeWorkspaceHookDefaults(process.cwd());
|
|
223
|
-
unlinkSync(local);
|
|
224
|
-
}
|
|
225
|
-
} catch { /* fine */ }
|
|
220
|
+
try { if (lstatSync(local).isSymbolicLink()) unlinkSync(local); } catch { /* fine */ }
|
|
226
221
|
}
|
|
227
222
|
|
|
228
223
|
const manifest = loadManifest();
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// commands/protocol.mjs — Write AW-PROTOCOL into machine-global AI tool files.
|
|
2
|
+
// Uses block-replacement markers so running multiple times is safe.
|
|
3
|
+
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
5
|
+
import { join, dirname } from 'node:path';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { AW_PROTOCOL_START, AW_PROTOCOL_END } from '../constants.mjs';
|
|
8
|
+
import * as fmt from '../fmt.mjs';
|
|
9
|
+
|
|
10
|
+
const HOME = homedir();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Read AW-PROTOCOL.md from the global registry.
|
|
14
|
+
* Returns null if not found.
|
|
15
|
+
*/
|
|
16
|
+
function readProtocol() {
|
|
17
|
+
const path = join(HOME, '.aw_registry', 'AW-PROTOCOL.md');
|
|
18
|
+
if (!existsSync(path)) return null;
|
|
19
|
+
return readFileSync(path, 'utf8');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Inject/replace the AW-PROTOCOL block in a target file.
|
|
24
|
+
* If file doesn't exist, creates it with just the block.
|
|
25
|
+
* If file exists, replaces content between markers or appends block.
|
|
26
|
+
* Returns true if file was written/updated.
|
|
27
|
+
*/
|
|
28
|
+
function injectBlock(filePath, content, toolName) {
|
|
29
|
+
const block = `${AW_PROTOCOL_START}\n${content}\n${AW_PROTOCOL_END}`;
|
|
30
|
+
|
|
31
|
+
// Ensure parent directory exists
|
|
32
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
33
|
+
|
|
34
|
+
if (!existsSync(filePath)) {
|
|
35
|
+
writeFileSync(filePath, block + '\n', 'utf8');
|
|
36
|
+
fmt.logStep(`Created ${toolName} global rules: ${filePath}`);
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const existing = readFileSync(filePath, 'utf8');
|
|
41
|
+
const startIdx = existing.indexOf(AW_PROTOCOL_START);
|
|
42
|
+
const endIdx = existing.indexOf(AW_PROTOCOL_END);
|
|
43
|
+
|
|
44
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
45
|
+
// Replace existing block
|
|
46
|
+
const before = existing.slice(0, startIdx);
|
|
47
|
+
const after = existing.slice(endIdx + AW_PROTOCOL_END.length);
|
|
48
|
+
const updated = before + block + after;
|
|
49
|
+
if (updated === existing) return false; // no change
|
|
50
|
+
writeFileSync(filePath, updated, 'utf8');
|
|
51
|
+
fmt.logStep(`Updated ${toolName} global rules: ${filePath}`);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Append block to existing file
|
|
56
|
+
const updated = existing.trimEnd() + '\n\n' + block + '\n';
|
|
57
|
+
writeFileSync(filePath, updated, 'utf8');
|
|
58
|
+
fmt.logStep(`Appended to ${toolName} global rules: ${filePath}`);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Write AW-PROTOCOL into all supported AI tool global files.
|
|
64
|
+
* Called by aw init. Silent if AW-PROTOCOL.md not found.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} [workspaceDir] - Optional workspace root. When provided,
|
|
67
|
+
* also writes to <workspaceDir>/.cursor/rules/aw-protocol.mdc so Cursor
|
|
68
|
+
* auto-loads it via its workspace rules mechanism (alwaysApply: true).
|
|
69
|
+
*/
|
|
70
|
+
export function writeGlobalProtocolFiles(workspaceDir) {
|
|
71
|
+
const protocol = readProtocol();
|
|
72
|
+
if (!protocol) {
|
|
73
|
+
fmt.logWarn('AW-PROTOCOL.md not found in ~/.aw_registry — skipping global rules install');
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const written = [];
|
|
78
|
+
const cursorContent = `---\nalwaysApply: true\n---\n\n${protocol}`;
|
|
79
|
+
|
|
80
|
+
// Claude Code + Claude CLI (machine-global)
|
|
81
|
+
const claudeMd = join(HOME, '.claude', 'CLAUDE.md');
|
|
82
|
+
if (injectBlock(claudeMd, protocol, 'Claude Code/CLI')) written.push(claudeMd);
|
|
83
|
+
|
|
84
|
+
// Cursor — workspace-level rules (auto-loaded by Cursor from .cursor/rules/)
|
|
85
|
+
// Machine-global ~/.cursor/rules/ is NOT auto-loaded by Cursor; workspace-level is.
|
|
86
|
+
if (workspaceDir) {
|
|
87
|
+
const wsCursorMdc = join(workspaceDir, '.cursor', 'rules', 'aw-protocol.mdc');
|
|
88
|
+
if (injectBlock(wsCursorMdc, cursorContent, 'Cursor workspace')) written.push(wsCursorMdc);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Cursor — also write to ~/.cursor/rules/ as fallback for manual reference
|
|
92
|
+
const cursorMdc = join(HOME, '.cursor', 'rules', 'aw-protocol.mdc');
|
|
93
|
+
if (injectBlock(cursorMdc, cursorContent, 'Cursor (home)')) written.push(cursorMdc);
|
|
94
|
+
|
|
95
|
+
// Codex CLI (machine-global)
|
|
96
|
+
const codexAgents = join(HOME, '.codex', 'AGENTS.md');
|
|
97
|
+
if (injectBlock(codexAgents, protocol, 'Codex')) written.push(codexAgents);
|
|
98
|
+
|
|
99
|
+
// Windsurf (known reliability issues — warn user)
|
|
100
|
+
const windsurfRules = join(HOME, '.codeium', 'windsurf', 'memories', 'global_rules.md');
|
|
101
|
+
if (injectBlock(windsurfRules, protocol, 'Windsurf')) {
|
|
102
|
+
written.push(windsurfRules);
|
|
103
|
+
fmt.logWarn('Windsurf global_rules.md has known reliability issues. Verify in Cascade > Customizations > Rules.');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return written;
|
|
107
|
+
}
|
package/commands/pull.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
existsSync,
|
|
5
5
|
lstatSync,
|
|
6
6
|
} from 'node:fs';
|
|
7
|
-
import {
|
|
7
|
+
import { join, extname } from 'node:path';
|
|
8
8
|
import { homedir } from 'node:os';
|
|
9
9
|
import { exec as execCb, execSync } from 'node:child_process';
|
|
10
10
|
import { promisify } from 'node:util';
|
|
@@ -30,9 +30,7 @@ import {
|
|
|
30
30
|
} from '../constants.mjs';
|
|
31
31
|
import { collectAllPaths, syncFileTree } from '../file-tree.mjs';
|
|
32
32
|
import { linkWorkspace } from '../link.mjs';
|
|
33
|
-
import { generateCommands, copyInstructions
|
|
34
|
-
import { removeWorkspaceHookDefaults } from '../codex.mjs';
|
|
35
|
-
import { applyStoredStartupPreferences, ensureAwRuntimeHook } from '../startup.mjs';
|
|
33
|
+
import { generateCommands, copyInstructions } from '../integrate.mjs';
|
|
36
34
|
|
|
37
35
|
const HOME = homedir();
|
|
38
36
|
const AW_HOME = join(HOME, '.aw');
|
|
@@ -44,7 +42,7 @@ export async function pullCommand(args) {
|
|
|
44
42
|
const silent = args['--silent'] === true || args._silent === true;
|
|
45
43
|
|
|
46
44
|
const log = {
|
|
47
|
-
cancel: silent ? () => {
|
|
45
|
+
cancel: silent ? (msg) => { throw new fmt.CancelError(msg || 'silent cancel', { exitCode: 0 }); } : fmt.cancel,
|
|
48
46
|
logInfo: silent ? () => {} : fmt.logInfo,
|
|
49
47
|
logSuccess: silent ? () => {} : fmt.logSuccess,
|
|
50
48
|
logStep: silent ? () => {} : fmt.logStep,
|
|
@@ -247,7 +245,6 @@ export async function pullCommand(args) {
|
|
|
247
245
|
if (!args._skipIntegrate) {
|
|
248
246
|
const projectRegistryDir = cwd !== HOME ? join(cwd, '.aw', REGISTRY_DIR) : null;
|
|
249
247
|
const awDirForLinks = (projectRegistryDir && existsSync(projectRegistryDir)) ? projectRegistryDir : null;
|
|
250
|
-
const startupCleanupDir = localAw ? dirname(localAw) : (cwd !== HOME ? cwd : null);
|
|
251
248
|
|
|
252
249
|
if (!silent) {
|
|
253
250
|
const ideSpinner = log.spinner();
|
|
@@ -255,18 +252,12 @@ export async function pullCommand(args) {
|
|
|
255
252
|
const symlinks = linkWorkspace(HOME, awDirForLinks, { silent: true });
|
|
256
253
|
ideSpinner.message('Generating commands...');
|
|
257
254
|
const commands = generateCommands(HOME, { silent: true });
|
|
258
|
-
|
|
259
|
-
ensureAwRuntimeHook(HOME);
|
|
260
|
-
applyStoredStartupPreferences(HOME);
|
|
261
|
-
if (startupCleanupDir) removeWorkspaceHookDefaults(startupCleanupDir);
|
|
255
|
+
copyInstructions(HOME, null, cfg.namespace);
|
|
262
256
|
ideSpinner.stop(`IDE wired — ${chalk.bold(symlinks)} symlink${symlinks !== 1 ? 's' : ''}, ${chalk.bold(commands)} command${commands !== 1 ? 's' : ''}`);
|
|
263
257
|
} else {
|
|
264
258
|
linkWorkspace(HOME, awDirForLinks, { silent: true });
|
|
265
259
|
generateCommands(HOME, { silent: true });
|
|
266
|
-
|
|
267
|
-
ensureAwRuntimeHook(HOME);
|
|
268
|
-
applyStoredStartupPreferences(HOME);
|
|
269
|
-
if (startupCleanupDir) removeWorkspaceHookDefaults(startupCleanupDir);
|
|
260
|
+
copyInstructions(HOME, null, cfg.namespace);
|
|
270
261
|
}
|
|
271
262
|
}
|
|
272
263
|
|
|
@@ -299,73 +290,3 @@ function registerMcp(namespace) {
|
|
|
299
290
|
fmt.logWarn('MCP registration failed (pull still successful)');
|
|
300
291
|
}
|
|
301
292
|
}
|
|
302
|
-
function printDryRun(actions, verbose) {
|
|
303
|
-
const counts = { ADD: 0, UPDATE: 0, CONFLICT: 0, ORPHAN: 0, UNCHANGED: 0 };
|
|
304
|
-
const lines = [];
|
|
305
|
-
|
|
306
|
-
for (const type of ['agents', 'skills', 'commands', 'evals', 'references']) {
|
|
307
|
-
const items = actions.filter(a => a.type === type);
|
|
308
|
-
if (items.length === 0) continue;
|
|
309
|
-
|
|
310
|
-
lines.push(chalk.bold(`${type}/`));
|
|
311
|
-
for (const act of items.sort((a, b) => a.targetFilename.localeCompare(b.targetFilename))) {
|
|
312
|
-
counts[act.action] = (counts[act.action] || 0) + 1;
|
|
313
|
-
if (!verbose && act.action === 'UNCHANGED') continue;
|
|
314
|
-
const ns = act.namespacePath ? chalk.dim(` [${act.namespacePath}]`) : '';
|
|
315
|
-
lines.push(` ${fmt.actionLabel(act.action)} ${act.targetFilename}${ns}`);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (lines.length > 0) {
|
|
320
|
-
fmt.note(lines.join('\n'), 'Dry Run');
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
fmt.logInfo(`Summary: ${fmt.countSummary(counts)}`);
|
|
324
|
-
fmt.logWarn('No files modified (--dry-run)');
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function printSummary(actions, verbose, conflictCount) {
|
|
328
|
-
const conflicts = actions.filter(a => a.action === 'CONFLICT');
|
|
329
|
-
|
|
330
|
-
for (const type of ['agents', 'skills', 'commands', 'evals', 'references']) {
|
|
331
|
-
const typeActions = actions.filter(a => a.type === type);
|
|
332
|
-
if (typeActions.length === 0) continue;
|
|
333
|
-
|
|
334
|
-
const counts = { ADD: 0, UPDATE: 0, CONFLICT: 0, ORPHAN: 0, UNCHANGED: 0 };
|
|
335
|
-
for (const a of typeActions) counts[a.action]++;
|
|
336
|
-
|
|
337
|
-
const parts = [];
|
|
338
|
-
if (counts.ADD > 0) parts.push(chalk.green(`${counts.ADD} new`));
|
|
339
|
-
if (counts.UPDATE > 0) parts.push(chalk.cyan(`${counts.UPDATE} updated`));
|
|
340
|
-
if (counts.CONFLICT > 0) parts.push(chalk.red(`${counts.CONFLICT} conflict`));
|
|
341
|
-
if (counts.ORPHAN > 0) parts.push(chalk.yellow(`${counts.ORPHAN} removed`));
|
|
342
|
-
const detail = parts.length > 0 ? ` (${parts.join(', ')})` : '';
|
|
343
|
-
|
|
344
|
-
fmt.logSuccess(`${typeActions.length} ${type} pulled${detail}`);
|
|
345
|
-
|
|
346
|
-
if (verbose) {
|
|
347
|
-
for (const a of typeActions.filter(a => a.action !== 'UNCHANGED')) {
|
|
348
|
-
const ns = a.namespacePath ? chalk.dim(` [${a.namespacePath}]`) : '';
|
|
349
|
-
fmt.logMessage(` ${fmt.actionLabel(a.action)} ${a.targetFilename}${ns}`);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
if (conflicts.length > 0) {
|
|
355
|
-
const conflictLines = conflicts.map(c => {
|
|
356
|
-
return `${chalk.red('both modified:')} ${c.type}/${c.targetFilename}`;
|
|
357
|
-
}).join('\n');
|
|
358
|
-
|
|
359
|
-
fmt.note(
|
|
360
|
-
conflictLines + '\n\n' +
|
|
361
|
-
chalk.dim('Fix conflicts, then re-run pull to verify.\n') +
|
|
362
|
-
chalk.dim('grep -r "<<<<<<< " .aw_registry/'),
|
|
363
|
-
chalk.red('Merge Conflicts')
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
fmt.outro(chalk.red('Pull completed with conflicts — resolve and re-run'));
|
|
367
|
-
process.exit(1);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
fmt.outro('Pull complete');
|
|
371
|
-
}
|
package/commands/push-rules.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { tmpdir } from 'node:os';
|
|
|
5
5
|
|
|
6
6
|
import * as fmt from '../fmt.mjs';
|
|
7
7
|
import { chalk } from '../fmt.mjs';
|
|
8
|
-
import { REGISTRY_BASE_BRANCH, REGISTRY_REPO, RULES_SOURCE_DIR } from '../constants.mjs';
|
|
8
|
+
import { REGISTRY_BASE_BRANCH, REGISTRY_REPO, RULES_SOURCE_DIR, AW_CO_AUTHOR } from '../constants.mjs';
|
|
9
9
|
import { syncFileTree } from '../file-tree.mjs';
|
|
10
10
|
|
|
11
11
|
function normalizeSlashes(path) {
|
|
@@ -147,7 +147,7 @@ function pushRulesTree(sourceRoot, { repo, dryRun, cwd }) {
|
|
|
147
147
|
const prTitle = buildRulesPrTitle(sourceRoot, cwd);
|
|
148
148
|
const prBody = buildRulesPrBody(sourceRoot, sourceType, cwd);
|
|
149
149
|
|
|
150
|
-
execSync(
|
|
150
|
+
execSync(`git commit -m "registry: sync platform rules\n\n${AW_CO_AUTHOR}"`, { cwd: tempDir, stdio: 'pipe' });
|
|
151
151
|
s2.stop('Rules sync prepared');
|
|
152
152
|
|
|
153
153
|
const s3 = fmt.spinner();
|