@ghl-ai/aw 0.1.36-beta.61 → 0.1.36-beta.63
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/ecc.mjs +57 -26
- package/package.json +1 -1
package/ecc.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import {
|
|
3
3
|
existsSync, readFileSync, readdirSync,
|
|
4
|
-
mkdirSync, rmSync, writeFileSync,
|
|
4
|
+
mkdirSync, rmSync, writeFileSync, renameSync,
|
|
5
5
|
} from "node:fs";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { homedir } from "node:os";
|
|
@@ -16,13 +16,14 @@ const PLUGIN_KEY = `aw@${MARKETPLACE_NAME}`;
|
|
|
16
16
|
|
|
17
17
|
function eccDir() { return join(homedir(), ".aw-ecc"); }
|
|
18
18
|
|
|
19
|
-
// "claude"
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
const FILE_COPY_TARGETS = ["cursor", "codex"];
|
|
19
|
+
// "claude" runs install-apply.js --without baseline:commands so hooks, rules,
|
|
20
|
+
// and agents land in ~/.claude/ — but commands are skipped because the plugin
|
|
21
|
+
// marketplace (installClaudePlugin) already registers them under /aw:tdd.
|
|
22
|
+
// File-copying commands would create a duplicate flat /tdd alongside /aw:tdd.
|
|
23
|
+
const FILE_COPY_TARGETS = ["claude", "cursor", "codex"];
|
|
24
24
|
|
|
25
25
|
const TARGET_STATE = {
|
|
26
|
+
claude: { state: ".claude/ecc/install-state.json" },
|
|
26
27
|
cursor: { state: ".cursor/ecc-install-state.json" },
|
|
27
28
|
codex: { state: ".codex/ecc-install-state.json" },
|
|
28
29
|
};
|
|
@@ -71,6 +72,45 @@ function uninstallClaudePlugin() {
|
|
|
71
72
|
try { run(`claude plugin marketplace remove ${MARKETPLACE_NAME}`); } catch { /* not registered */ }
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Move ecc command files from ~/.cursor/commands/*.md → ~/.cursor/commands/aw/*.md
|
|
77
|
+
* so Cursor exposes them as /aw:tdd, /aw:plan — consistent with Claude Code's plugin namespace.
|
|
78
|
+
* Also updates the ecc-install-state.json paths so nuke can clean them up correctly.
|
|
79
|
+
*/
|
|
80
|
+
function namespaceCursorCommands(home) {
|
|
81
|
+
const commandsDir = join(home, ".cursor", "commands");
|
|
82
|
+
const awDir = join(commandsDir, "aw");
|
|
83
|
+
if (!existsSync(commandsDir)) return;
|
|
84
|
+
|
|
85
|
+
// Move any flat .md files (not already in a subdirectory) into aw/
|
|
86
|
+
const moved = [];
|
|
87
|
+
for (const file of readdirSync(commandsDir)) {
|
|
88
|
+
if (!file.endsWith(".md") || file.startsWith(".")) continue;
|
|
89
|
+
const src = join(commandsDir, file);
|
|
90
|
+
mkdirSync(awDir, { recursive: true });
|
|
91
|
+
const dest = join(awDir, file);
|
|
92
|
+
try {
|
|
93
|
+
renameSync(src, dest);
|
|
94
|
+
moved.push({ from: src, to: dest });
|
|
95
|
+
} catch { /* best effort */ }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (moved.length === 0) return;
|
|
99
|
+
|
|
100
|
+
// Update install-state so nuke removes files from the new aw/ location
|
|
101
|
+
const statePath = join(home, ".cursor", "ecc-install-state.json");
|
|
102
|
+
if (!existsSync(statePath)) return;
|
|
103
|
+
try {
|
|
104
|
+
const state = JSON.parse(readFileSync(statePath, "utf8"));
|
|
105
|
+
const pathMap = new Map(moved.map(({ from, to }) => [from, to]));
|
|
106
|
+
state.operations = (state.operations || []).map((op) => {
|
|
107
|
+
const newPath = op.destinationPath && pathMap.get(op.destinationPath);
|
|
108
|
+
return newPath ? { ...op, destinationPath: newPath } : op;
|
|
109
|
+
});
|
|
110
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
111
|
+
} catch { /* best effort */ }
|
|
112
|
+
}
|
|
113
|
+
|
|
74
114
|
export async function installAwEcc(
|
|
75
115
|
cwd,
|
|
76
116
|
{ targets = ["cursor", "claude", "codex"], silent = false } = {},
|
|
@@ -92,7 +132,7 @@ export async function installAwEcc(
|
|
|
92
132
|
}
|
|
93
133
|
}
|
|
94
134
|
|
|
95
|
-
// Cursor + Codex: file-copy via install-apply.js
|
|
135
|
+
// Claude + Cursor + Codex: file-copy via install-apply.js
|
|
96
136
|
const fileCopyTargets = targets.filter((t) => FILE_COPY_TARGETS.includes(t));
|
|
97
137
|
if (fileCopyTargets.length > 0) {
|
|
98
138
|
run("npm install --no-audit --no-fund --ignore-scripts --loglevel=error", {
|
|
@@ -100,13 +140,20 @@ export async function installAwEcc(
|
|
|
100
140
|
});
|
|
101
141
|
for (const target of fileCopyTargets) {
|
|
102
142
|
try {
|
|
103
|
-
//
|
|
104
|
-
// land in ~/.cursor/ and ~/.codex/ globally, not in the project dir.
|
|
143
|
+
// Always use HOME as cwd so files land in ~/.<target>/ globally.
|
|
105
144
|
const runCwd = homedir();
|
|
145
|
+
// For claude: skip commands (plugin handles them as /aw:tdd) but
|
|
146
|
+
// install hooks, rules, agents into ~/.claude/.
|
|
147
|
+
const withoutFlag = target === "claude" ? " --without baseline:commands" : "";
|
|
106
148
|
run(
|
|
107
|
-
`node ${join(repoDir, "scripts/install-apply.js")} --target ${target} --profile full`,
|
|
149
|
+
`node ${join(repoDir, "scripts/install-apply.js")} --target ${target} --profile full${withoutFlag}`,
|
|
108
150
|
{ cwd: runCwd },
|
|
109
151
|
);
|
|
152
|
+
// Move cursor commands into aw/ subfolder for namespace consistency
|
|
153
|
+
// so they're accessible as /aw:tdd, /aw:plan — same as Claude Code plugin.
|
|
154
|
+
if (target === "cursor") {
|
|
155
|
+
namespaceCursorCommands(runCwd);
|
|
156
|
+
}
|
|
110
157
|
} catch { /* target not supported — skip */ }
|
|
111
158
|
}
|
|
112
159
|
}
|
|
@@ -146,22 +193,6 @@ export function uninstallAwEcc({ silent = false } = {}) {
|
|
|
146
193
|
} catch { /* corrupted state — skip */ }
|
|
147
194
|
}
|
|
148
195
|
|
|
149
|
-
// Clean leftover claude install-state from older file-copy versions
|
|
150
|
-
const claudeState = join(HOME, ".claude", "ecc", "install-state.json");
|
|
151
|
-
if (existsSync(claudeState)) {
|
|
152
|
-
try {
|
|
153
|
-
const data = JSON.parse(readFileSync(claudeState, "utf8"));
|
|
154
|
-
for (const op of data.operations || []) {
|
|
155
|
-
if (op.destinationPath && existsSync(op.destinationPath)) {
|
|
156
|
-
rmSync(op.destinationPath, { recursive: true, force: true });
|
|
157
|
-
removed++;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
rmSync(claudeState, { force: true });
|
|
161
|
-
pruneEmptyParents(claudeState, join(HOME, ".claude"));
|
|
162
|
-
} catch { /* best effort */ }
|
|
163
|
-
}
|
|
164
|
-
|
|
165
196
|
// Clean leftover manual plugin cache from older versions
|
|
166
197
|
const oldCache = join(HOME, ".claude", "plugins", "cache", "aw");
|
|
167
198
|
if (existsSync(oldCache)) {
|