@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.
Files changed (2) hide show
  1. package/ecc.mjs +57 -26
  2. 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" intentionally excluded Claude Code is handled via the plugin
20
- // marketplace (installClaudePlugin), which registers commands under the aw:
21
- // namespace. Running install-apply.js --target claude would create a duplicate
22
- // flat copy at ~/.claude/commands/ accessible as /tdd instead of /aw:tdd.
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
- // cursor/codex install relative to cwd always use HOME so they
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)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.36-beta.61",
3
+ "version": "0.1.36-beta.63",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": "bin.js",