@ghl-ai/aw 0.1.11 → 0.1.13
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/commands/nuke.mjs +112 -64
- package/commands/pull.mjs +25 -26
- package/package.json +1 -1
package/commands/nuke.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// commands/nuke.mjs — Safe cleanup:
|
|
1
|
+
// commands/nuke.mjs — Safe cleanup: removes only what AW created
|
|
2
2
|
//
|
|
3
3
|
// Safety guarantee: NEVER deletes files that AW didn't create.
|
|
4
4
|
|
|
@@ -13,31 +13,28 @@ const HOME = homedir();
|
|
|
13
13
|
const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
|
|
14
14
|
const MANIFEST_PATH = join(GLOBAL_AW_DIR, '.aw-manifest.json');
|
|
15
15
|
|
|
16
|
-
// IDE dirs where AW creates symlinks
|
|
17
16
|
const IDE_DIRS = ['.claude', '.cursor', '.codex'];
|
|
18
17
|
const CONTENT_TYPES = ['agents', 'skills', 'commands', 'blueprints', 'evals'];
|
|
19
18
|
|
|
20
19
|
function loadManifest() {
|
|
21
20
|
if (!existsSync(MANIFEST_PATH)) return null;
|
|
22
|
-
try {
|
|
23
|
-
|
|
24
|
-
} catch { return null; }
|
|
21
|
+
try { return JSON.parse(readFileSync(MANIFEST_PATH, 'utf8')); }
|
|
22
|
+
catch { return null; }
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
// Remove AW-created files listed in manifest
|
|
25
|
+
// 1. Remove AW-created files listed in manifest
|
|
28
26
|
function removeCreatedFiles(manifest) {
|
|
29
27
|
if (!manifest?.createdFiles?.length) return;
|
|
30
28
|
let removed = 0;
|
|
31
29
|
for (const rel of manifest.createdFiles) {
|
|
32
30
|
const p = join(HOME, rel);
|
|
33
|
-
try {
|
|
34
|
-
|
|
35
|
-
} catch { /* best effort */ }
|
|
31
|
+
try { if (existsSync(p)) { rmSync(p); removed++; } }
|
|
32
|
+
catch { /* best effort */ }
|
|
36
33
|
}
|
|
37
34
|
if (removed > 0) fmt.logStep(`Removed ${removed} generated file${removed > 1 ? 's' : ''}`);
|
|
38
35
|
}
|
|
39
36
|
|
|
40
|
-
// Remove symlinks from IDE dirs that point into .aw_registry
|
|
37
|
+
// 2. Remove symlinks from IDE dirs that point into .aw_registry
|
|
41
38
|
function removeIdeSymlinks() {
|
|
42
39
|
let removed = 0;
|
|
43
40
|
|
|
@@ -60,42 +57,118 @@ function removeIdeSymlinks() {
|
|
|
60
57
|
// Remove dir if now empty
|
|
61
58
|
try { if (readdirSync(dir).length === 0) rmSync(dir); } catch {}
|
|
62
59
|
}
|
|
60
|
+
|
|
61
|
+
// Also clean CLAUDE.md / AGENTS.md if they're AW-generated
|
|
62
|
+
for (const file of ['CLAUDE.md', 'AGENTS.md']) {
|
|
63
|
+
const p = join(HOME, ide, file);
|
|
64
|
+
try {
|
|
65
|
+
if (lstatSync(p).isSymbolicLink()) {
|
|
66
|
+
const target = readlinkSync(p);
|
|
67
|
+
if (target.includes('.aw_registry') || target.includes('aw_registry')) {
|
|
68
|
+
unlinkSync(p); removed++;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch { /* not a symlink or doesn't exist */ }
|
|
72
|
+
}
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
if (removed > 0) fmt.logStep(`Removed ${removed} IDE symlink${removed > 1 ? 's' : ''}`);
|
|
66
76
|
}
|
|
67
77
|
|
|
68
|
-
// Find and remove .aw_registry symlinks from project directories
|
|
78
|
+
// 3. Find and remove ALL .aw_registry symlinks from project directories
|
|
69
79
|
function removeProjectSymlinks() {
|
|
70
80
|
let removed = 0;
|
|
71
|
-
const dirsToCheck = new Set([process.cwd()]);
|
|
72
81
|
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
// Use find command for thorough search — much more reliable than manual scanning
|
|
83
|
+
try {
|
|
84
|
+
const result = execSync(
|
|
85
|
+
`find "${HOME}" -maxdepth 4 -name ".aw_registry" -type l 2>/dev/null`,
|
|
86
|
+
{ encoding: 'utf8', timeout: 10000 }
|
|
87
|
+
).trim();
|
|
88
|
+
|
|
89
|
+
if (result) {
|
|
90
|
+
for (const linkPath of result.split('\n').filter(Boolean)) {
|
|
91
|
+
try {
|
|
92
|
+
unlinkSync(linkPath);
|
|
93
|
+
removed++;
|
|
94
|
+
} catch { /* best effort */ }
|
|
80
95
|
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
// Fallback: manual scan of common dirs
|
|
99
|
+
const dirsToCheck = new Set([process.cwd()]);
|
|
100
|
+
for (const parent of ['Documents', 'Projects', 'Desktop', 'dev', 'repos', 'src', 'code', 'work']) {
|
|
101
|
+
const p = join(HOME, parent);
|
|
102
|
+
if (!existsSync(p)) continue;
|
|
103
|
+
try {
|
|
104
|
+
for (const sub of readdirSync(p)) {
|
|
105
|
+
const sp = join(p, sub);
|
|
106
|
+
dirsToCheck.add(sp);
|
|
107
|
+
// Also check one level deeper
|
|
108
|
+
try {
|
|
109
|
+
for (const sub2 of readdirSync(sp)) {
|
|
110
|
+
dirsToCheck.add(join(sp, sub2));
|
|
111
|
+
}
|
|
112
|
+
} catch {}
|
|
113
|
+
}
|
|
114
|
+
} catch {}
|
|
115
|
+
}
|
|
83
116
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
117
|
+
for (const dir of dirsToCheck) {
|
|
118
|
+
const link = join(dir, '.aw_registry');
|
|
119
|
+
try {
|
|
120
|
+
if (lstatSync(link).isSymbolicLink()) { unlinkSync(link); removed++; }
|
|
121
|
+
} catch {}
|
|
122
|
+
}
|
|
89
123
|
}
|
|
90
124
|
|
|
91
125
|
if (removed > 0) fmt.logStep(`Removed ${removed} project .aw_registry symlink${removed > 1 ? 's' : ''}`);
|
|
92
126
|
}
|
|
93
127
|
|
|
128
|
+
// 4. Remove git template hook
|
|
129
|
+
function removeGitTemplate(manifest) {
|
|
130
|
+
const gitTemplateDir = manifest?.gitTemplate || join(HOME, '.git-templates', 'aw');
|
|
131
|
+
if (!existsSync(gitTemplateDir)) return;
|
|
132
|
+
|
|
133
|
+
rmSync(gitTemplateDir, { recursive: true, force: true });
|
|
134
|
+
try {
|
|
135
|
+
const current = execSync('git config --global init.templateDir', {
|
|
136
|
+
encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
|
|
137
|
+
}).trim();
|
|
138
|
+
if (current === gitTemplateDir) {
|
|
139
|
+
execSync('git config --global --unset init.templateDir', { stdio: 'pipe' });
|
|
140
|
+
}
|
|
141
|
+
} catch { /* not set */ }
|
|
142
|
+
fmt.logStep('Removed git template hook');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 5. Remove IDE auto-init tasks
|
|
146
|
+
function removeIdeTasks() {
|
|
147
|
+
for (const ide of ['Code', 'Cursor']) {
|
|
148
|
+
const tasksPath = join(HOME, 'Library', 'Application Support', ide, 'User', 'tasks.json');
|
|
149
|
+
if (!existsSync(tasksPath)) continue;
|
|
150
|
+
try {
|
|
151
|
+
const data = JSON.parse(readFileSync(tasksPath, 'utf8'));
|
|
152
|
+
const before = data.tasks?.length || 0;
|
|
153
|
+
data.tasks = (data.tasks || []).filter(t =>
|
|
154
|
+
t.label !== 'aw: sync registry' && t.label !== 'aw: pull registry'
|
|
155
|
+
);
|
|
156
|
+
if (data.tasks.length < before) {
|
|
157
|
+
if (data.tasks.length === 0) {
|
|
158
|
+
unlinkSync(tasksPath);
|
|
159
|
+
} else {
|
|
160
|
+
writeFileSync(tasksPath, JSON.stringify(data, null, 2) + '\n');
|
|
161
|
+
}
|
|
162
|
+
fmt.logStep(`Removed auto-sync task from ${ide}`);
|
|
163
|
+
}
|
|
164
|
+
} catch { /* best effort */ }
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
94
168
|
export function nukeCommand(args) {
|
|
95
169
|
fmt.intro('aw nuke');
|
|
96
170
|
|
|
97
171
|
if (!existsSync(GLOBAL_AW_DIR)) {
|
|
98
|
-
// Check cwd for local symlink
|
|
99
172
|
const local = join(process.cwd(), '.aw_registry');
|
|
100
173
|
if (existsSync(local) && lstatSync(local).isSymbolicLink()) {
|
|
101
174
|
unlinkSync(local);
|
|
@@ -114,71 +187,46 @@ export function nukeCommand(args) {
|
|
|
114
187
|
manifest ? `${chalk.dim('installed:')} ${manifest.installedAt}` : null,
|
|
115
188
|
].filter(Boolean).join('\n'), 'Cleaning up');
|
|
116
189
|
|
|
190
|
+
// Order matters: remove symlinks BEFORE removing the source
|
|
191
|
+
|
|
117
192
|
// 1. Remove AW-created files (instruction files, MCP configs)
|
|
118
193
|
removeCreatedFiles(manifest);
|
|
119
194
|
|
|
120
195
|
// 2. Remove IDE symlinks (only those pointing to .aw_registry)
|
|
121
196
|
removeIdeSymlinks();
|
|
122
197
|
|
|
123
|
-
// 3. Remove .aw_registry symlinks from project directories
|
|
198
|
+
// 3. Remove .aw_registry symlinks from ALL project directories
|
|
124
199
|
removeProjectSymlinks();
|
|
125
200
|
|
|
126
201
|
// 4. Remove git template hook
|
|
127
|
-
|
|
128
|
-
if (existsSync(gitTemplateDir)) {
|
|
129
|
-
rmSync(gitTemplateDir, { recursive: true, force: true });
|
|
130
|
-
// Unset git config if it pointed to our template
|
|
131
|
-
try {
|
|
132
|
-
const current = execSync('git config --global init.templateDir', {
|
|
133
|
-
encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
|
|
134
|
-
}).trim();
|
|
135
|
-
if (current === gitTemplateDir) {
|
|
136
|
-
execSync('git config --global --unset init.templateDir', { stdio: 'pipe' });
|
|
137
|
-
}
|
|
138
|
-
} catch { /* not set */ }
|
|
139
|
-
fmt.logStep('Removed git template hook');
|
|
140
|
-
}
|
|
202
|
+
removeGitTemplate(manifest);
|
|
141
203
|
|
|
142
|
-
// 5. Remove IDE auto-
|
|
143
|
-
|
|
144
|
-
const tasksPath = join(HOME, 'Library', 'Application Support', ide, 'User', 'tasks.json');
|
|
145
|
-
if (!existsSync(tasksPath)) continue;
|
|
146
|
-
try {
|
|
147
|
-
const data = JSON.parse(readFileSync(tasksPath, 'utf8'));
|
|
148
|
-
const before = data.tasks?.length || 0;
|
|
149
|
-
data.tasks = (data.tasks || []).filter(t => t.label !== 'aw: sync registry' && t.label !== 'aw: pull registry');
|
|
150
|
-
if (data.tasks.length < before) {
|
|
151
|
-
if (data.tasks.length === 0) {
|
|
152
|
-
unlinkSync(tasksPath);
|
|
153
|
-
} else {
|
|
154
|
-
writeFileSync(tasksPath, JSON.stringify(data, null, 2) + '\n');
|
|
155
|
-
}
|
|
156
|
-
fmt.logStep(`Removed auto-pull task from ${ide}`);
|
|
157
|
-
}
|
|
158
|
-
} catch { /* best effort */ }
|
|
159
|
-
}
|
|
204
|
+
// 5. Remove IDE auto-init tasks
|
|
205
|
+
removeIdeTasks();
|
|
160
206
|
|
|
161
|
-
//
|
|
207
|
+
// 6. Remove ~/.aw_docs/
|
|
162
208
|
const awDocs = join(HOME, '.aw_docs');
|
|
163
209
|
if (existsSync(awDocs)) {
|
|
164
210
|
rmSync(awDocs, { recursive: true, force: true });
|
|
165
211
|
fmt.logStep('Removed ~/.aw_docs/');
|
|
166
212
|
}
|
|
167
213
|
|
|
168
|
-
//
|
|
214
|
+
// 7. Remove ~/.aw_registry/ itself (source of truth — last!)
|
|
169
215
|
rmSync(GLOBAL_AW_DIR, { recursive: true, force: true });
|
|
170
216
|
fmt.logStep('Removed ~/.aw_registry/');
|
|
171
217
|
|
|
172
218
|
fmt.outro([
|
|
173
219
|
'Fully removed',
|
|
174
220
|
'',
|
|
221
|
+
` ${chalk.green('✓')} Generated files cleaned`,
|
|
175
222
|
` ${chalk.green('✓')} IDE symlinks cleaned`,
|
|
176
223
|
` ${chalk.green('✓')} Project symlinks cleaned`,
|
|
177
224
|
` ${chalk.green('✓')} Git template hook removed`,
|
|
178
|
-
` ${chalk.green('✓')} IDE auto-
|
|
225
|
+
` ${chalk.green('✓')} IDE auto-sync tasks removed`,
|
|
179
226
|
` ${chalk.green('✓')} Source of truth deleted`,
|
|
180
227
|
'',
|
|
181
228
|
` ${chalk.dim('No existing files were touched.')}`,
|
|
182
|
-
` ${chalk.dim('
|
|
229
|
+
` ${chalk.dim('To reinstall:')} ${chalk.bold('aw init')}`,
|
|
230
|
+
` ${chalk.dim('To uninstall CLI:')} ${chalk.bold('npm uninstall -g @ghl-ai/aw')}`,
|
|
183
231
|
].join('\n'));
|
|
184
232
|
}
|
package/commands/pull.mjs
CHANGED
|
@@ -28,28 +28,27 @@ export function pullCommand(args) {
|
|
|
28
28
|
const silent = args['--silent'] === true || args._silent === true;
|
|
29
29
|
const renameNamespace = args._renameNamespace || null;
|
|
30
30
|
|
|
31
|
-
// Silent mode: suppress all output and exit cleanly on errors
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
31
|
+
// Silent mode: wrap fmt to suppress all output and exit cleanly on errors
|
|
32
|
+
const log = {
|
|
33
|
+
cancel: silent ? () => { process.exit(0); } : fmt.cancel,
|
|
34
|
+
logInfo: silent ? () => {} : fmt.logInfo,
|
|
35
|
+
logSuccess: silent ? () => {} : fmt.logSuccess,
|
|
36
|
+
logStep: silent ? () => {} : fmt.logStep,
|
|
37
|
+
logWarn: silent ? () => {} : fmt.logWarn,
|
|
38
|
+
logMessage: silent ? () => {} : fmt.logMessage,
|
|
39
|
+
note: silent ? () => {} : fmt.note,
|
|
40
|
+
outro: silent ? () => {} : fmt.outro,
|
|
41
|
+
spinner: silent ? () => ({ start: () => {}, stop: () => {} }) : fmt.spinner,
|
|
42
|
+
};
|
|
44
43
|
|
|
45
44
|
// No args = re-pull everything in sync config
|
|
46
45
|
if (!input) {
|
|
47
46
|
const cfg = config.load(workspaceDir);
|
|
48
|
-
if (!cfg)
|
|
47
|
+
if (!cfg) log.cancel('No .sync-config.json found. Run: aw init');
|
|
49
48
|
if (cfg.include.length === 0) {
|
|
50
|
-
|
|
49
|
+
log.cancel('Nothing to pull. Add paths first:\n\n aw pull <path>');
|
|
51
50
|
}
|
|
52
|
-
|
|
51
|
+
log.logInfo(`Pulling ${chalk.cyan(cfg.include.length)} synced path${cfg.include.length > 1 ? 's' : ''}...`);
|
|
53
52
|
for (const p of cfg.include) {
|
|
54
53
|
pullCommand({ ...args, _positional: [p], _skipIntegrate: true });
|
|
55
54
|
}
|
|
@@ -65,7 +64,7 @@ export function pullCommand(args) {
|
|
|
65
64
|
let pattern = resolved.registryPath;
|
|
66
65
|
|
|
67
66
|
if (!pattern) {
|
|
68
|
-
|
|
67
|
+
log.cancel(`Could not resolve "${input}" to a registry path`);
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
// Ensure workspace exists
|
|
@@ -76,11 +75,11 @@ export function pullCommand(args) {
|
|
|
76
75
|
// Load config
|
|
77
76
|
const cfg = config.load(workspaceDir);
|
|
78
77
|
if (!cfg) {
|
|
79
|
-
|
|
78
|
+
log.cancel('No .sync-config.json found. Run: aw init');
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
// Fetch from registry
|
|
83
|
-
const s =
|
|
82
|
+
const s = log.spinner();
|
|
84
83
|
s.start('Fetching from registry...');
|
|
85
84
|
|
|
86
85
|
const sparsePaths = includeToSparsePaths([pattern]);
|
|
@@ -89,7 +88,7 @@ export function pullCommand(args) {
|
|
|
89
88
|
tempDir = sparseCheckout(cfg.repo, sparsePaths);
|
|
90
89
|
} catch (e) {
|
|
91
90
|
s.stop(chalk.red('Fetch failed'));
|
|
92
|
-
|
|
91
|
+
log.cancel(e.message);
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
try {
|
|
@@ -104,8 +103,8 @@ export function pullCommand(args) {
|
|
|
104
103
|
|
|
105
104
|
if (registryDirs.length === 0) {
|
|
106
105
|
s.stop(chalk.red('Not found'));
|
|
107
|
-
if (
|
|
108
|
-
|
|
106
|
+
if (silent) return;
|
|
107
|
+
log.cancel(`Nothing found in registry for ${chalk.cyan(pattern)}`);
|
|
109
108
|
}
|
|
110
109
|
|
|
111
110
|
// Rename namespace if requested (e.g., [template] → dev)
|
|
@@ -132,8 +131,8 @@ export function pullCommand(args) {
|
|
|
132
131
|
if (!hasMatch) {
|
|
133
132
|
s.stop(chalk.red('Not found'));
|
|
134
133
|
cleanup(tempDir);
|
|
135
|
-
if (
|
|
136
|
-
|
|
134
|
+
if (silent) return;
|
|
135
|
+
log.cancel(`Nothing found in registry for ${chalk.cyan(pattern)}\n\n Check the pattern exists in the registry repo.`);
|
|
137
136
|
}
|
|
138
137
|
|
|
139
138
|
const fetched = registryDirs.map(d => chalk.cyan(d.name)).join(', ');
|
|
@@ -142,7 +141,7 @@ export function pullCommand(args) {
|
|
|
142
141
|
// Add to config if not already there
|
|
143
142
|
if (!cfg.include.includes(pattern)) {
|
|
144
143
|
config.addPattern(workspaceDir, pattern);
|
|
145
|
-
|
|
144
|
+
log.logSuccess(`Added ${chalk.cyan(pattern)} to config`);
|
|
146
145
|
}
|
|
147
146
|
|
|
148
147
|
// Compute plan
|
|
@@ -156,7 +155,7 @@ export function pullCommand(args) {
|
|
|
156
155
|
}
|
|
157
156
|
|
|
158
157
|
// Apply
|
|
159
|
-
const s2 =
|
|
158
|
+
const s2 = log.spinner();
|
|
160
159
|
s2.start('Applying changes...');
|
|
161
160
|
const conflictCount = applyActions(actions);
|
|
162
161
|
updateManifest(workspaceDir, actions, cfg.namespace);
|