@ghl-ai/aw 0.1.35-beta.14 → 0.1.35-beta.16

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 +116 -21
  2. package/package.json +1 -1
package/ecc.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { execSync } from "node:child_process";
2
2
  import {
3
- existsSync, mkdirSync, readFileSync, readdirSync,
3
+ cpSync, existsSync, mkdirSync, readFileSync, readdirSync,
4
4
  renameSync, rmSync, writeFileSync,
5
5
  } from "node:fs";
6
6
  import { dirname, join } from "node:path";
@@ -9,13 +9,18 @@ import * as fmt from "./fmt.mjs";
9
9
 
10
10
  const AW_ECC_REPO_SSH = "git@github.com:shreyansh-ghl/aw-ecc.git";
11
11
  const AW_ECC_REPO_HTTPS = "https://github.com/shreyansh-ghl/aw-ecc.git";
12
- const AW_ECC_TAG = "v1.0.0";
12
+ const AW_ECC_TAG = "v1.1.0";
13
13
  const TMP_DIR = "/tmp/aw-ecc";
14
14
 
15
+ const PLUGIN_NAME = "aw";
16
+ const PLUGIN_VERSION = "1.1.0";
17
+ const PLUGIN_KEY = `${PLUGIN_NAME}@${PLUGIN_NAME}`;
18
+
19
+ const FILE_COPY_TARGETS = ["cursor", "codex"];
20
+
15
21
  const TARGET_STATE = {
16
22
  cursor: { state: ".cursor/ecc-install-state.json" },
17
- claude: { state: ".claude/ecc/install-state.json" },
18
- codex: { state: ".codex/ecc-install-state.json" },
23
+ codex: { state: ".codex/ecc-install-state.json" },
19
24
  };
20
25
 
21
26
  function run(cmd, opts = {}) {
@@ -30,6 +35,66 @@ function cloneRepo(tag, dest) {
30
35
  }
31
36
  }
32
37
 
38
+ function getPluginPaths() {
39
+ const HOME = homedir();
40
+ const pluginsDir = join(HOME, ".claude", "plugins");
41
+ const cachePath = join(pluginsDir, "cache", PLUGIN_NAME, PLUGIN_NAME, PLUGIN_VERSION);
42
+ const registryPath = join(pluginsDir, "installed_plugins.json");
43
+ return { pluginsDir, cachePath, registryPath };
44
+ }
45
+
46
+ function registerClaudePlugin(repoDir) {
47
+ const { cachePath, registryPath } = getPluginPaths();
48
+
49
+ if (existsSync(cachePath)) rmSync(cachePath, { recursive: true, force: true });
50
+ mkdirSync(cachePath, { recursive: true });
51
+ cpSync(repoDir, cachePath, { recursive: true });
52
+
53
+ // Remove .git from the cached copy
54
+ const dotGit = join(cachePath, ".git");
55
+ if (existsSync(dotGit)) rmSync(dotGit, { recursive: true, force: true });
56
+
57
+ let registry = { version: 2, plugins: {} };
58
+ if (existsSync(registryPath)) {
59
+ try { registry = JSON.parse(readFileSync(registryPath, "utf8")); } catch { /* fresh */ }
60
+ }
61
+
62
+ let commitSha = "";
63
+ try { commitSha = run("git rev-parse HEAD", { cwd: repoDir }).toString().trim(); } catch { /* ok */ }
64
+
65
+ registry.plugins[PLUGIN_KEY] = [
66
+ {
67
+ scope: "user",
68
+ installPath: cachePath,
69
+ version: PLUGIN_VERSION,
70
+ installedAt: new Date().toISOString(),
71
+ lastUpdated: new Date().toISOString(),
72
+ ...(commitSha && { gitCommitSha: commitSha }),
73
+ },
74
+ ];
75
+
76
+ writeFileSync(registryPath, JSON.stringify(registry, null, 2));
77
+ }
78
+
79
+ function unregisterClaudePlugin() {
80
+ const { cachePath, registryPath } = getPluginPaths();
81
+
82
+ if (existsSync(cachePath)) {
83
+ rmSync(cachePath, { recursive: true, force: true });
84
+ pruneEmptyParents(cachePath, join(homedir(), ".claude"));
85
+ }
86
+
87
+ if (existsSync(registryPath)) {
88
+ try {
89
+ const registry = JSON.parse(readFileSync(registryPath, "utf8"));
90
+ if (registry.plugins?.[PLUGIN_KEY]) {
91
+ delete registry.plugins[PLUGIN_KEY];
92
+ writeFileSync(registryPath, JSON.stringify(registry, null, 2));
93
+ }
94
+ } catch { /* best effort */ }
95
+ }
96
+ }
97
+
33
98
  function namespaceCommands(target) {
34
99
  const HOME = homedir();
35
100
  const cfg = TARGET_STATE[target];
@@ -44,9 +109,6 @@ function namespaceCommands(target) {
44
109
 
45
110
  for (const op of state.operations) {
46
111
  const dest = op.destinationPath;
47
- // Match top-level command files only (e.g. /commands/plan.md)
48
- // Skip files already in subdirs (e.g. /commands/aw/plan.md)
49
- // Skip .opencode paths (different command structure)
50
112
  if (dest.includes(".opencode/")) continue;
51
113
  const match = dest.match(/^(.+\/commands)\/([^/]+\.md)$/);
52
114
  if (!match) continue;
@@ -78,17 +140,26 @@ export async function installAwEcc(
78
140
 
79
141
  try {
80
142
  cloneRepo(AW_ECC_TAG, TMP_DIR);
81
- run("npm install --no-audit --no-fund --ignore-scripts --loglevel=error", { cwd: TMP_DIR });
82
-
83
- for (const target of targets) {
84
- try {
85
- run(
86
- `node ${join(TMP_DIR, "scripts/install-apply.js")} --target ${target} --profile full`,
87
- { cwd },
88
- );
89
- namespaceCommands(target);
90
- } catch {
91
- // Target not supported on this system — skip silently
143
+
144
+ // Claude Code: register as plugin (proper agent dispatch)
145
+ if (targets.includes("claude")) {
146
+ registerClaudePlugin(TMP_DIR);
147
+ }
148
+
149
+ // Cursor + Codex: file-copy approach + namespace commands into /aw:
150
+ const fileCopyTargets = targets.filter((t) => FILE_COPY_TARGETS.includes(t));
151
+ if (fileCopyTargets.length > 0) {
152
+ run("npm install --no-audit --no-fund --ignore-scripts --loglevel=error", {
153
+ cwd: TMP_DIR,
154
+ });
155
+ for (const target of fileCopyTargets) {
156
+ try {
157
+ run(
158
+ `node ${join(TMP_DIR, "scripts/install-apply.js")} --target ${target} --profile full`,
159
+ { cwd },
160
+ );
161
+ namespaceCommands(target);
162
+ } catch { /* target not supported — skip */ }
92
163
  }
93
164
  }
94
165
 
@@ -104,9 +175,14 @@ export function uninstallAwEcc({ silent = false } = {}) {
104
175
  const HOME = homedir();
105
176
  let removed = 0;
106
177
 
178
+ // Claude Code: unregister plugin + remove cache
179
+ unregisterClaudePlugin();
180
+ removed++;
181
+
182
+ // Cursor + Codex: remove file-copied content via install-state
107
183
  for (const cfg of Object.values(TARGET_STATE)) {
108
184
  const statePath = join(HOME, cfg.state);
109
- const ideDir = join(HOME, cfg.state.split("/")[0]); // e.g. ~/.cursor, ~/.claude, ~/.codex
185
+ const ideDir = join(HOME, cfg.state.split("/")[0]);
110
186
  if (!existsSync(statePath)) continue;
111
187
 
112
188
  try {
@@ -123,7 +199,24 @@ export function uninstallAwEcc({ silent = false } = {}) {
123
199
  } catch { /* corrupted state — skip */ }
124
200
  }
125
201
 
126
- if (!silent && removed > 0) fmt.logStep(`Removed ${removed} aw-ecc file${removed > 1 ? "s" : ""}`);
202
+ // Clean leftover claude install-state from older versions
203
+ const claudeState = join(HOME, ".claude", "ecc", "install-state.json");
204
+ if (existsSync(claudeState)) {
205
+ try {
206
+ const data = JSON.parse(readFileSync(claudeState, "utf8"));
207
+ for (const op of data.operations || []) {
208
+ if (op.destinationPath && existsSync(op.destinationPath)) {
209
+ rmSync(op.destinationPath, { recursive: true, force: true });
210
+ removed++;
211
+ }
212
+ }
213
+ rmSync(claudeState, { force: true });
214
+ pruneEmptyParents(claudeState, join(HOME, ".claude"));
215
+ } catch { /* best effort */ }
216
+ }
217
+
218
+ if (!silent && removed > 0)
219
+ fmt.logStep(`Removed ${removed} aw-ecc file${removed > 1 ? "s" : ""}`);
127
220
  return removed;
128
221
  }
129
222
 
@@ -137,6 +230,8 @@ function pruneEmptyParents(filePath, stopAt) {
137
230
  } else {
138
231
  break;
139
232
  }
140
- } catch { break; }
233
+ } catch {
234
+ break;
235
+ }
141
236
  }
142
237
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.35-beta.14",
3
+ "version": "0.1.35-beta.16",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {