@ghl-ai/aw 0.1.35-beta.24 → 0.1.35-beta.26
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/init.mjs +0 -3
- package/commands/nuke.mjs +8 -13
- package/integrate.mjs +6 -8
- package/mcp.mjs +69 -3
- package/package.json +2 -3
- package/ecc.mjs +0 -180
package/commands/init.mjs
CHANGED
|
@@ -19,7 +19,6 @@ import { generateCommands, copyInstructions, initAwDocs } from '../integrate.mjs
|
|
|
19
19
|
import { setupMcp } from '../mcp.mjs';
|
|
20
20
|
import { autoUpdate, promptUpdate } from '../update.mjs';
|
|
21
21
|
import { installGlobalHooks } from '../hooks.mjs';
|
|
22
|
-
import { installAwEcc } from '../ecc.mjs';
|
|
23
22
|
|
|
24
23
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
25
24
|
const VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')).version;
|
|
@@ -201,7 +200,6 @@ export async function initCommand(args) {
|
|
|
201
200
|
|
|
202
201
|
// Re-link IDE dirs + hooks (idempotent)
|
|
203
202
|
linkWorkspace(HOME);
|
|
204
|
-
await installAwEcc(cwd, { silent });
|
|
205
203
|
generateCommands(HOME);
|
|
206
204
|
copyInstructions(HOME, null, freshCfg?.namespace || team) || [];
|
|
207
205
|
initAwDocs(HOME);
|
|
@@ -282,7 +280,6 @@ export async function initCommand(args) {
|
|
|
282
280
|
// Step 3: Link IDE dirs + setup tasks
|
|
283
281
|
fmt.logStep('Linking IDE symlinks...');
|
|
284
282
|
linkWorkspace(HOME);
|
|
285
|
-
await installAwEcc(cwd, { silent });
|
|
286
283
|
generateCommands(HOME);
|
|
287
284
|
const instructionFiles = copyInstructions(HOME, null, team) || [];
|
|
288
285
|
initAwDocs(HOME);
|
package/commands/nuke.mjs
CHANGED
|
@@ -9,7 +9,6 @@ import { execSync } from 'node:child_process';
|
|
|
9
9
|
import * as fmt from '../fmt.mjs';
|
|
10
10
|
import { chalk } from '../fmt.mjs';
|
|
11
11
|
import { removeGlobalHooks } from '../hooks.mjs';
|
|
12
|
-
import { uninstallAwEcc } from '../ecc.mjs';
|
|
13
12
|
|
|
14
13
|
const HOME = homedir();
|
|
15
14
|
const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
|
|
@@ -244,31 +243,28 @@ export function nukeCommand(args) {
|
|
|
244
243
|
// 2. Remove IDE symlinks (only those pointing to .aw_registry)
|
|
245
244
|
removeIdeSymlinks();
|
|
246
245
|
|
|
247
|
-
// 3. Remove
|
|
248
|
-
uninstallAwEcc();
|
|
249
|
-
|
|
250
|
-
// 4. Remove .aw_registry symlinks from ALL project directories
|
|
246
|
+
// 3. Remove .aw_registry symlinks from ALL project directories
|
|
251
247
|
removeProjectSymlinks();
|
|
252
248
|
|
|
253
|
-
//
|
|
249
|
+
// 4. Remove git hooks (core.hooksPath + legacy template)
|
|
254
250
|
removeGitHooks(manifest);
|
|
255
251
|
|
|
256
|
-
//
|
|
252
|
+
// 5. Remove IDE auto-init tasks
|
|
257
253
|
removeIdeTasks();
|
|
258
254
|
|
|
259
|
-
// Remove upgrade lock/log (inside .aw_registry, must happen before dir removal)
|
|
255
|
+
// 5b. Remove upgrade lock/log (inside .aw_registry, must happen before dir removal)
|
|
260
256
|
for (const p of [join(GLOBAL_AW_DIR, '.aw-upgrade.lock'), join(GLOBAL_AW_DIR, '.aw-upgrade.log')]) {
|
|
261
257
|
try { if (existsSync(p)) rmSync(p, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
262
258
|
}
|
|
263
259
|
|
|
264
|
-
//
|
|
260
|
+
// 6. Remove ~/.aw_docs/
|
|
265
261
|
const awDocs = join(HOME, '.aw_docs');
|
|
266
262
|
if (existsSync(awDocs)) {
|
|
267
263
|
rmSync(awDocs, { recursive: true, force: true });
|
|
268
264
|
fmt.logStep('Removed ~/.aw_docs/');
|
|
269
265
|
}
|
|
270
266
|
|
|
271
|
-
//
|
|
267
|
+
// 7. Remove any manual `aw` symlinks (e.g. ~/.local/bin/aw)
|
|
272
268
|
const manualBins = [
|
|
273
269
|
join(HOME, '.local', 'bin', 'aw'),
|
|
274
270
|
join(HOME, 'bin', 'aw'),
|
|
@@ -282,7 +278,7 @@ export function nukeCommand(args) {
|
|
|
282
278
|
} catch { /* doesn't exist */ }
|
|
283
279
|
}
|
|
284
280
|
|
|
285
|
-
//
|
|
281
|
+
// 8. Uninstall npm global package (skip if already in npm uninstall lifecycle)
|
|
286
282
|
if (!process.env.npm_lifecycle_event) {
|
|
287
283
|
try {
|
|
288
284
|
execSync('npm uninstall -g @ghl-ai/aw', { stdio: 'pipe', timeout: 15000 });
|
|
@@ -290,7 +286,7 @@ export function nukeCommand(args) {
|
|
|
290
286
|
} catch { /* not installed via npm or no permissions */ }
|
|
291
287
|
}
|
|
292
288
|
|
|
293
|
-
//
|
|
289
|
+
// 9. Remove ~/.aw_registry/ itself (source of truth — last!)
|
|
294
290
|
rmSync(GLOBAL_AW_DIR, { recursive: true, force: true });
|
|
295
291
|
fmt.logStep('Removed ~/.aw_registry/');
|
|
296
292
|
|
|
@@ -299,7 +295,6 @@ export function nukeCommand(args) {
|
|
|
299
295
|
'',
|
|
300
296
|
` ${chalk.green('✓')} Generated files cleaned`,
|
|
301
297
|
` ${chalk.green('✓')} IDE symlinks cleaned`,
|
|
302
|
-
` ${chalk.green('✓')} aw-ecc engine removed`,
|
|
303
298
|
` ${chalk.green('✓')} Project symlinks cleaned`,
|
|
304
299
|
` ${chalk.green('✓')} Git hooks removed`,
|
|
305
300
|
` ${chalk.green('✓')} IDE auto-sync tasks removed`,
|
package/integrate.mjs
CHANGED
|
@@ -56,13 +56,11 @@ function findFiles(dir, typeName) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* Copy AGENTS.md to project root.
|
|
60
|
-
* CLAUDE.md is intentionally NOT generated — its routing rule hijacks plugin
|
|
61
|
-
* commands like /aw:plan, preventing proper agent dispatch.
|
|
59
|
+
* Copy CLAUDE.md and AGENTS.md to project root.
|
|
62
60
|
*/
|
|
63
61
|
export function copyInstructions(cwd, tempDir, namespace) {
|
|
64
62
|
const createdFiles = [];
|
|
65
|
-
for (const file of ['AGENTS.md']) {
|
|
63
|
+
for (const file of ['CLAUDE.md', 'AGENTS.md']) {
|
|
66
64
|
const dest = join(cwd, file);
|
|
67
65
|
if (existsSync(dest)) continue;
|
|
68
66
|
|
|
@@ -80,7 +78,9 @@ export function copyInstructions(cwd, tempDir, namespace) {
|
|
|
80
78
|
}
|
|
81
79
|
}
|
|
82
80
|
|
|
83
|
-
const content =
|
|
81
|
+
const content = file === 'CLAUDE.md'
|
|
82
|
+
? generateClaudeMd(cwd, namespace)
|
|
83
|
+
: generateAgentsMd(cwd, namespace);
|
|
84
84
|
if (content) {
|
|
85
85
|
writeFileSync(dest, content);
|
|
86
86
|
fmt.logSuccess(`Created ${file}`);
|
|
@@ -100,9 +100,7 @@ Team: ${team} | Local-first orchestration via \`.aw_docs/\` | MCPs: \`memory/*\`
|
|
|
100
100
|
|
|
101
101
|
> **Every non-trivial task MUST call \`Skill(skill: "platform-ai-task-router")\` BEFORE any response.**
|
|
102
102
|
>
|
|
103
|
-
> **
|
|
104
|
-
> - **Plugin commands**: any \`/aw:*\` slash command — these have their own agent dispatch via the plugin system. Execute the plugin command as-is, do NOT re-route through the task router.
|
|
105
|
-
> - **Trivial tasks**: typo fixes, single-line edits, git ops, file exploration, factual code questions.
|
|
103
|
+
> **Trivial** (do directly): typo fixes, single-line edits, git ops, file exploration, factual code questions.
|
|
106
104
|
>
|
|
107
105
|
> Everything else — including tasks phrased as questions, suggestions, or discussions — routes first.
|
|
108
106
|
> **No conversational responses first. No planning first. Route first.**
|
package/mcp.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'node:fs';
|
|
5
5
|
import { execSync } from 'node:child_process';
|
|
6
|
+
import { createInterface } from 'node:readline';
|
|
6
7
|
import { join, resolve } from 'node:path';
|
|
7
8
|
import { homedir } from 'node:os';
|
|
8
9
|
import * as fmt from './fmt.mjs';
|
|
@@ -111,12 +112,69 @@ function resolveGitHubUser(token) {
|
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Resolve ClickUp personal API token. Opens browser to ClickUp settings page
|
|
117
|
+
* and prompts the user to paste their token.
|
|
118
|
+
*
|
|
119
|
+
* Resolution chain:
|
|
120
|
+
* 1. $CLICKUP_API_TOKEN env var (already set)
|
|
121
|
+
* 2. Open browser + prompt user (one-time)
|
|
122
|
+
* 3. null (skip ClickUp — other MCP tools still work)
|
|
123
|
+
*/
|
|
124
|
+
async function resolveClickUpToken() {
|
|
125
|
+
// 1. Environment variable
|
|
126
|
+
if (process.env.CLICKUP_API_TOKEN) {
|
|
127
|
+
fmt.logStep('Using CLICKUP_API_TOKEN from environment');
|
|
128
|
+
return process.env.CLICKUP_API_TOKEN;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 2. Open browser and prompt
|
|
132
|
+
fmt.logStep('ClickUp API token needed for per-user task attribution');
|
|
133
|
+
fmt.logStep('Opening ClickUp settings page — copy your API token from there...');
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
137
|
+
execSync(`${openCmd} "https://app.clickup.com/settings/apps"`, { stdio: 'ignore', timeout: 5000 });
|
|
138
|
+
} catch { /* browser open failed — user can navigate manually */ }
|
|
139
|
+
|
|
140
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
141
|
+
const token = await new Promise((resolve) => {
|
|
142
|
+
rl.question(' Paste your ClickUp API Token (pk_...): ', (answer) => {
|
|
143
|
+
rl.close();
|
|
144
|
+
resolve(answer.trim());
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (!token || !token.startsWith('pk_')) {
|
|
149
|
+
fmt.logWarn('No valid ClickUp token provided — skipping (ClickUp tools will use shared token)');
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Validate token
|
|
154
|
+
try {
|
|
155
|
+
const res = execSync(`curl -sf -H "Authorization: ${token}" "https://api.clickup.com/api/v2/team"`, {
|
|
156
|
+
encoding: 'utf8',
|
|
157
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
158
|
+
timeout: 10000,
|
|
159
|
+
});
|
|
160
|
+
const data = JSON.parse(res);
|
|
161
|
+
const teamName = data.teams?.[0]?.name;
|
|
162
|
+
if (teamName) {
|
|
163
|
+
fmt.logSuccess(`ClickUp token valid — workspace: ${fmt.chalk.cyan(teamName)}`);
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
fmt.logWarn('ClickUp token validation failed — using it anyway');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return token;
|
|
170
|
+
}
|
|
171
|
+
|
|
114
172
|
/**
|
|
115
173
|
* Setup MCP configs globally for Claude Code and Cursor.
|
|
116
174
|
* Merges ghl-ai server into existing configs without overwriting other servers.
|
|
117
175
|
* Returns list of file paths that were created or updated.
|
|
118
176
|
*/
|
|
119
|
-
export function setupMcp(cwd, namespace) {
|
|
177
|
+
export async function setupMcp(cwd, namespace) {
|
|
120
178
|
const paths = detectPaths();
|
|
121
179
|
const updatedFiles = [];
|
|
122
180
|
|
|
@@ -131,11 +189,19 @@ export function setupMcp(cwd, namespace) {
|
|
|
131
189
|
}
|
|
132
190
|
}
|
|
133
191
|
|
|
134
|
-
//
|
|
192
|
+
// Resolve ClickUp token for per-user attribution
|
|
193
|
+
const clickupToken = await resolveClickUpToken();
|
|
194
|
+
|
|
195
|
+
// Server config with resolved tokens for local IDE configs (not committed, safe)
|
|
196
|
+
const headers = { Authorization: `Bearer ${ghToken || '${GITHUB_TOKEN}'}` };
|
|
197
|
+
if (clickupToken) {
|
|
198
|
+
headers['X-ClickUp-Token'] = clickupToken;
|
|
199
|
+
}
|
|
200
|
+
|
|
135
201
|
const ghlAiServerLocal = {
|
|
136
202
|
type: 'http',
|
|
137
203
|
url: mcpUrl,
|
|
138
|
-
headers
|
|
204
|
+
headers,
|
|
139
205
|
};
|
|
140
206
|
|
|
141
207
|
const gitJenkinsServer = paths.gitJenkinsPath
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghl-ai/aw",
|
|
3
|
-
"version": "0.1.35-beta.
|
|
3
|
+
"version": "0.1.35-beta.26",
|
|
4
4
|
"description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -24,8 +24,7 @@
|
|
|
24
24
|
"registry.mjs",
|
|
25
25
|
"apply.mjs",
|
|
26
26
|
"update.mjs",
|
|
27
|
-
"hooks.mjs"
|
|
28
|
-
"ecc.mjs"
|
|
27
|
+
"hooks.mjs"
|
|
29
28
|
],
|
|
30
29
|
"engines": {
|
|
31
30
|
"node": ">=18.0.0"
|
package/ecc.mjs
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
2
|
-
import {
|
|
3
|
-
existsSync, readFileSync, readdirSync,
|
|
4
|
-
mkdirSync, rmSync, writeFileSync,
|
|
5
|
-
} from "node:fs";
|
|
6
|
-
import { dirname, join } from "node:path";
|
|
7
|
-
import { homedir } from "node:os";
|
|
8
|
-
import * as fmt from "./fmt.mjs";
|
|
9
|
-
|
|
10
|
-
const AW_ECC_REPO_SSH = "git@github.com:shreyansh-ghl/aw-ecc.git";
|
|
11
|
-
const AW_ECC_REPO_HTTPS = "https://github.com/shreyansh-ghl/aw-ecc.git";
|
|
12
|
-
const AW_ECC_TAG = "v1.2.2";
|
|
13
|
-
|
|
14
|
-
const MARKETPLACE_NAME = "aw-marketplace";
|
|
15
|
-
const PLUGIN_KEY = `aw@${MARKETPLACE_NAME}`;
|
|
16
|
-
|
|
17
|
-
function eccDir() { return join(homedir(), ".aw-ecc"); }
|
|
18
|
-
|
|
19
|
-
const FILE_COPY_TARGETS = ["cursor", "codex"];
|
|
20
|
-
|
|
21
|
-
const TARGET_STATE = {
|
|
22
|
-
cursor: { state: ".cursor/ecc-install-state.json" },
|
|
23
|
-
codex: { state: ".codex/ecc-install-state.json" },
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
function run(cmd, opts = {}) {
|
|
27
|
-
return execSync(cmd, { stdio: "pipe", ...opts });
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function cloneOrUpdate(tag, dest) {
|
|
31
|
-
if (existsSync(join(dest, ".git"))) {
|
|
32
|
-
try {
|
|
33
|
-
run(`git -C ${dest} fetch --quiet --depth 1 origin tag ${tag}`);
|
|
34
|
-
run(`git -C ${dest} checkout --quiet ${tag}`);
|
|
35
|
-
return;
|
|
36
|
-
} catch { /* fall through to fresh clone */ }
|
|
37
|
-
}
|
|
38
|
-
if (existsSync(dest)) rmSync(dest, { recursive: true, force: true });
|
|
39
|
-
try {
|
|
40
|
-
run(`git clone --quiet --depth 1 --branch ${tag} ${AW_ECC_REPO_SSH} ${dest}`);
|
|
41
|
-
} catch {
|
|
42
|
-
run(`git clone --quiet --depth 1 --branch ${tag} ${AW_ECC_REPO_HTTPS} ${dest}`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function installClaudePlugin(repoDir) {
|
|
47
|
-
try {
|
|
48
|
-
run(`claude plugin marketplace add ${repoDir} --scope user`);
|
|
49
|
-
} catch {
|
|
50
|
-
try { run(`claude plugin marketplace update ${MARKETPLACE_NAME}`); } catch { /* ok */ }
|
|
51
|
-
}
|
|
52
|
-
run(`claude plugin install ${PLUGIN_KEY} --scope user`);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function uninstallClaudePlugin() {
|
|
56
|
-
try { run(`claude plugin uninstall ${PLUGIN_KEY} --scope user`); } catch { /* not installed */ }
|
|
57
|
-
try { run(`claude plugin marketplace remove ${MARKETPLACE_NAME}`); } catch { /* not registered */ }
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export async function installAwEcc(
|
|
61
|
-
cwd,
|
|
62
|
-
{ targets = ["cursor", "claude", "codex"], silent = false } = {},
|
|
63
|
-
) {
|
|
64
|
-
if (!silent) fmt.logStep("Installing aw-ecc engine...");
|
|
65
|
-
|
|
66
|
-
const repoDir = eccDir();
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
cloneOrUpdate(AW_ECC_TAG, repoDir);
|
|
70
|
-
|
|
71
|
-
// Claude Code: plugin install via marketplace CLI (proper agent dispatch)
|
|
72
|
-
if (targets.includes("claude")) {
|
|
73
|
-
try {
|
|
74
|
-
installClaudePlugin(repoDir);
|
|
75
|
-
} catch (err) {
|
|
76
|
-
if (!silent) fmt.logWarn(`Claude plugin install skipped: ${err.message}`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Cursor + Codex: file-copy via install-apply.js
|
|
81
|
-
const fileCopyTargets = targets.filter((t) => FILE_COPY_TARGETS.includes(t));
|
|
82
|
-
if (fileCopyTargets.length > 0) {
|
|
83
|
-
run("npm install --no-audit --no-fund --ignore-scripts --loglevel=error", {
|
|
84
|
-
cwd: repoDir,
|
|
85
|
-
});
|
|
86
|
-
for (const target of fileCopyTargets) {
|
|
87
|
-
try {
|
|
88
|
-
run(
|
|
89
|
-
`node ${join(repoDir, "scripts/install-apply.js")} --target ${target} --profile full`,
|
|
90
|
-
{ cwd },
|
|
91
|
-
);
|
|
92
|
-
} catch { /* target not supported — skip */ }
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (!silent) fmt.logSuccess("aw-ecc engine installed");
|
|
97
|
-
} catch (err) {
|
|
98
|
-
if (!silent) fmt.logWarn(`aw-ecc install failed: ${err.message}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function uninstallAwEcc({ silent = false } = {}) {
|
|
103
|
-
const HOME = homedir();
|
|
104
|
-
let removed = 0;
|
|
105
|
-
|
|
106
|
-
// Claude Code: uninstall plugin + remove marketplace via CLI
|
|
107
|
-
try {
|
|
108
|
-
uninstallClaudePlugin();
|
|
109
|
-
removed++;
|
|
110
|
-
} catch { /* best effort */ }
|
|
111
|
-
|
|
112
|
-
// Cursor + Codex: remove file-copied content via install-state
|
|
113
|
-
for (const cfg of Object.values(TARGET_STATE)) {
|
|
114
|
-
const statePath = join(HOME, cfg.state);
|
|
115
|
-
if (!existsSync(statePath)) continue;
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
const data = JSON.parse(readFileSync(statePath, "utf8"));
|
|
119
|
-
for (const op of data.operations || []) {
|
|
120
|
-
if (op.destinationPath && existsSync(op.destinationPath)) {
|
|
121
|
-
rmSync(op.destinationPath, { recursive: true, force: true });
|
|
122
|
-
removed++;
|
|
123
|
-
pruneEmptyParents(op.destinationPath, join(HOME, cfg.state.split("/")[0]));
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
rmSync(statePath, { force: true });
|
|
127
|
-
pruneEmptyParents(statePath, join(HOME, cfg.state.split("/")[0]));
|
|
128
|
-
} catch { /* corrupted state — skip */ }
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Clean leftover claude install-state from older file-copy versions
|
|
132
|
-
const claudeState = join(HOME, ".claude", "ecc", "install-state.json");
|
|
133
|
-
if (existsSync(claudeState)) {
|
|
134
|
-
try {
|
|
135
|
-
const data = JSON.parse(readFileSync(claudeState, "utf8"));
|
|
136
|
-
for (const op of data.operations || []) {
|
|
137
|
-
if (op.destinationPath && existsSync(op.destinationPath)) {
|
|
138
|
-
rmSync(op.destinationPath, { recursive: true, force: true });
|
|
139
|
-
removed++;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
rmSync(claudeState, { force: true });
|
|
143
|
-
pruneEmptyParents(claudeState, join(HOME, ".claude"));
|
|
144
|
-
} catch { /* best effort */ }
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Clean leftover manual plugin cache from older versions
|
|
148
|
-
const oldCache = join(HOME, ".claude", "plugins", "cache", "aw");
|
|
149
|
-
if (existsSync(oldCache)) {
|
|
150
|
-
rmSync(oldCache, { recursive: true, force: true });
|
|
151
|
-
removed++;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Remove permanent aw-ecc repo clone
|
|
155
|
-
const repoDir = eccDir();
|
|
156
|
-
if (existsSync(repoDir)) {
|
|
157
|
-
rmSync(repoDir, { recursive: true, force: true });
|
|
158
|
-
removed++;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (!silent && removed > 0)
|
|
162
|
-
fmt.logStep(`Removed ${removed} aw-ecc file${removed > 1 ? "s" : ""}`);
|
|
163
|
-
return removed;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function pruneEmptyParents(filePath, stopAt) {
|
|
167
|
-
let dir = dirname(filePath);
|
|
168
|
-
while (dir !== stopAt && dir.startsWith(stopAt)) {
|
|
169
|
-
try {
|
|
170
|
-
if (readdirSync(dir).length === 0) {
|
|
171
|
-
rmSync(dir);
|
|
172
|
-
dir = dirname(dir);
|
|
173
|
-
} else {
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
} catch {
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|