@ghl-ai/aw 0.1.12 → 0.1.14

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.
Files changed (2) hide show
  1. package/commands/nuke.mjs +132 -64
  2. package/package.json +1 -1
package/commands/nuke.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // commands/nuke.mjs — Safe cleanup: reads manifest, removes only what AW created
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
- return JSON.parse(readFileSync(MANIFEST_PATH, 'utf8'));
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
- if (existsSync(p)) { rmSync(p); removed++; }
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
- // Scan ~/Documents, ~/Projects, ~/Desktop, ~/dev and their children
74
- for (const parent of ['Documents', 'Projects', 'Desktop', 'dev', 'repos', 'src', 'code', 'work']) {
75
- const p = join(HOME, parent);
76
- if (!existsSync(p)) continue;
77
- try {
78
- for (const sub of readdirSync(p)) {
79
- dirsToCheck.add(join(p, sub));
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
- } catch {}
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
- for (const dir of dirsToCheck) {
85
- const link = join(dir, '.aw_registry');
86
- try {
87
- if (lstatSync(link).isSymbolicLink()) { unlinkSync(link); removed++; }
88
- } catch { /* doesn't exist or not a symlink */ }
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,66 @@ 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
- const gitTemplateDir = manifest?.gitTemplate || join(HOME, '.git-templates', 'aw');
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-pull tasks
143
- for (const ide of ['Code', 'Cursor']) {
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
- // 7. Remove ~/.aw_docs/
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
- // 8. Remove ~/.aw_registry/ itself
214
+ // 7. Remove any manual `aw` symlinks (e.g. ~/.local/bin/aw)
215
+ const manualBins = [
216
+ join(HOME, '.local', 'bin', 'aw'),
217
+ join(HOME, 'bin', 'aw'),
218
+ ];
219
+ for (const bin of manualBins) {
220
+ try {
221
+ if (lstatSync(bin).isSymbolicLink()) {
222
+ unlinkSync(bin);
223
+ fmt.logStep(`Removed manual symlink ${bin.replace(HOME, '~')}`);
224
+ }
225
+ } catch { /* doesn't exist */ }
226
+ }
227
+
228
+ // 8. Uninstall npm global package
229
+ try {
230
+ execSync('npm uninstall -g @ghl-ai/aw', { stdio: 'pipe', timeout: 15000 });
231
+ fmt.logStep('Uninstalled @ghl-ai/aw globally');
232
+ } catch { /* not installed via npm or no permissions */ }
233
+
234
+ // 9. Remove ~/.aw_registry/ itself (source of truth — last!)
169
235
  rmSync(GLOBAL_AW_DIR, { recursive: true, force: true });
170
236
  fmt.logStep('Removed ~/.aw_registry/');
171
237
 
172
238
  fmt.outro([
173
239
  'Fully removed',
174
240
  '',
241
+ ` ${chalk.green('✓')} Generated files cleaned`,
175
242
  ` ${chalk.green('✓')} IDE symlinks cleaned`,
176
243
  ` ${chalk.green('✓')} Project symlinks cleaned`,
177
244
  ` ${chalk.green('✓')} Git template hook removed`,
178
- ` ${chalk.green('✓')} IDE auto-pull tasks removed`,
245
+ ` ${chalk.green('✓')} IDE auto-sync tasks removed`,
179
246
  ` ${chalk.green('✓')} Source of truth deleted`,
180
247
  '',
181
248
  ` ${chalk.dim('No existing files were touched.')}`,
182
- ` ${chalk.dim('Run')} ${chalk.bold('aw init')} ${chalk.dim('to reinstall.')}`,
249
+ ` ${chalk.dim('To reinstall:')} ${chalk.bold('aw init')}`,
250
+ ` ${chalk.dim('To uninstall CLI:')} ${chalk.bold('npm uninstall -g @ghl-ai/aw')}`,
183
251
  ].join('\n'));
184
252
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {