@ghl-ai/aw 0.1.35-beta.2 → 0.1.35-beta.20
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 +3 -4
- package/commands/nuke.mjs +12 -11
- package/commands/push.mjs +1 -30
- package/constants.mjs +1 -1
- package/ecc.mjs +180 -0
- package/integrate.mjs +2 -2
- package/link.mjs +1 -1
- package/package.json +2 -2
- package/ide-hooks.mjs +0 -193
package/commands/init.mjs
CHANGED
|
@@ -19,7 +19,7 @@ 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 {
|
|
22
|
+
import { installAwEcc } from '../ecc.mjs';
|
|
23
23
|
|
|
24
24
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
25
25
|
const VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')).version;
|
|
@@ -201,13 +201,13 @@ export async function initCommand(args) {
|
|
|
201
201
|
|
|
202
202
|
// Re-link IDE dirs + hooks (idempotent)
|
|
203
203
|
linkWorkspace(HOME);
|
|
204
|
+
await installAwEcc(cwd, { silent });
|
|
204
205
|
generateCommands(HOME);
|
|
205
206
|
copyInstructions(HOME, null, freshCfg?.namespace || team) || [];
|
|
206
207
|
initAwDocs(HOME);
|
|
207
208
|
setupMcp(HOME, freshCfg?.namespace || team) || [];
|
|
208
209
|
if (cwd !== HOME) setupMcp(cwd, freshCfg?.namespace || team);
|
|
209
210
|
installGlobalHooks();
|
|
210
|
-
const ideHookFiles = installIdeHooks(HOME);
|
|
211
211
|
|
|
212
212
|
// Link current project if needed
|
|
213
213
|
if (cwd !== HOME && !existsSync(join(cwd, '.aw_registry'))) {
|
|
@@ -282,13 +282,13 @@ export async function initCommand(args) {
|
|
|
282
282
|
// Step 3: Link IDE dirs + setup tasks
|
|
283
283
|
fmt.logStep('Linking IDE symlinks...');
|
|
284
284
|
linkWorkspace(HOME);
|
|
285
|
+
await installAwEcc(cwd, { silent });
|
|
285
286
|
generateCommands(HOME);
|
|
286
287
|
const instructionFiles = copyInstructions(HOME, null, team) || [];
|
|
287
288
|
initAwDocs(HOME);
|
|
288
289
|
const mcpFiles = setupMcp(HOME, team) || [];
|
|
289
290
|
if (cwd !== HOME) setupMcp(cwd, team);
|
|
290
291
|
const hooksInstalled = installGlobalHooks();
|
|
291
|
-
const ideHookFilesInit = installIdeHooks(HOME);
|
|
292
292
|
installIdeTasks();
|
|
293
293
|
|
|
294
294
|
// Step 4: Symlink in current directory if it's a git repo
|
|
@@ -307,7 +307,6 @@ export async function initCommand(args) {
|
|
|
307
307
|
createdFiles: [
|
|
308
308
|
...instructionFiles.map(p => p.startsWith(HOME) ? p.slice(HOME.length + 1) : p),
|
|
309
309
|
...mcpFiles.map(p => p.startsWith(HOME) ? p.slice(HOME.length + 1) : p),
|
|
310
|
-
...(ideHookFilesInit || []).map(p => p.startsWith(HOME) ? p.slice(HOME.length + 1) : p),
|
|
311
310
|
],
|
|
312
311
|
globalHooksDir: hooksInstalled ? join(HOME, '.aw', 'hooks') : null,
|
|
313
312
|
};
|
package/commands/nuke.mjs
CHANGED
|
@@ -9,7 +9,7 @@ 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 {
|
|
12
|
+
import { uninstallAwEcc } from '../ecc.mjs';
|
|
13
13
|
|
|
14
14
|
const HOME = homedir();
|
|
15
15
|
const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
|
|
@@ -244,31 +244,31 @@ export function nukeCommand(args) {
|
|
|
244
244
|
// 2. Remove IDE symlinks (only those pointing to .aw_registry)
|
|
245
245
|
removeIdeSymlinks();
|
|
246
246
|
|
|
247
|
-
// 3. Remove
|
|
247
|
+
// 3. Remove aw-ecc installed files (agents, commands, rules, skills, hooks)
|
|
248
|
+
uninstallAwEcc();
|
|
249
|
+
|
|
250
|
+
// 4. Remove .aw_registry symlinks from ALL project directories
|
|
248
251
|
removeProjectSymlinks();
|
|
249
252
|
|
|
250
|
-
//
|
|
253
|
+
// 5. Remove git hooks (core.hooksPath + legacy template)
|
|
251
254
|
removeGitHooks(manifest);
|
|
252
255
|
|
|
253
|
-
// 5. Remove IDE session hooks (superpowers bootstrap)
|
|
254
|
-
removeIdeHooks(HOME);
|
|
255
|
-
|
|
256
256
|
// 6. Remove IDE auto-init tasks
|
|
257
257
|
removeIdeTasks();
|
|
258
258
|
|
|
259
|
-
//
|
|
259
|
+
// Remove upgrade lock/log (inside .aw_registry, must happen before dir removal)
|
|
260
260
|
for (const p of [join(GLOBAL_AW_DIR, '.aw-upgrade.lock'), join(GLOBAL_AW_DIR, '.aw-upgrade.log')]) {
|
|
261
261
|
try { if (existsSync(p)) rmSync(p, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
//
|
|
264
|
+
// 7. Remove ~/.aw_docs/
|
|
265
265
|
const awDocs = join(HOME, '.aw_docs');
|
|
266
266
|
if (existsSync(awDocs)) {
|
|
267
267
|
rmSync(awDocs, { recursive: true, force: true });
|
|
268
268
|
fmt.logStep('Removed ~/.aw_docs/');
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
//
|
|
271
|
+
// 8. Remove any manual `aw` symlinks (e.g. ~/.local/bin/aw)
|
|
272
272
|
const manualBins = [
|
|
273
273
|
join(HOME, '.local', 'bin', 'aw'),
|
|
274
274
|
join(HOME, 'bin', 'aw'),
|
|
@@ -282,7 +282,7 @@ export function nukeCommand(args) {
|
|
|
282
282
|
} catch { /* doesn't exist */ }
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
//
|
|
285
|
+
// 9. Uninstall npm global package (skip if already in npm uninstall lifecycle)
|
|
286
286
|
if (!process.env.npm_lifecycle_event) {
|
|
287
287
|
try {
|
|
288
288
|
execSync('npm uninstall -g @ghl-ai/aw', { stdio: 'pipe', timeout: 15000 });
|
|
@@ -290,7 +290,7 @@ export function nukeCommand(args) {
|
|
|
290
290
|
} catch { /* not installed via npm or no permissions */ }
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
//
|
|
293
|
+
// 10. Remove ~/.aw_registry/ itself (source of truth — last!)
|
|
294
294
|
rmSync(GLOBAL_AW_DIR, { recursive: true, force: true });
|
|
295
295
|
fmt.logStep('Removed ~/.aw_registry/');
|
|
296
296
|
|
|
@@ -299,6 +299,7 @@ export function nukeCommand(args) {
|
|
|
299
299
|
'',
|
|
300
300
|
` ${chalk.green('✓')} Generated files cleaned`,
|
|
301
301
|
` ${chalk.green('✓')} IDE symlinks cleaned`,
|
|
302
|
+
` ${chalk.green('✓')} aw-ecc engine removed`,
|
|
302
303
|
` ${chalk.green('✓')} Project symlinks cleaned`,
|
|
303
304
|
` ${chalk.green('✓')} Git hooks removed`,
|
|
304
305
|
` ${chalk.green('✓')} IDE auto-sync tasks removed`,
|
package/commands/push.mjs
CHANGED
|
@@ -73,17 +73,12 @@ function collectBatchFiles(folderAbsPath, workspaceDir) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
* Collect all modified
|
|
77
|
-
* 1. Manifest-tracked files that are modified or template-derived (never pushed).
|
|
78
|
-
* 2. Filesystem files not in the manifest at all (newly added by user).
|
|
76
|
+
* Collect all modified files from manifest (for no-args push).
|
|
79
77
|
* Returns array of { absPath, registryTarget, type, namespace, slug, isDir }.
|
|
80
78
|
*/
|
|
81
79
|
function collectModifiedFiles(workspaceDir) {
|
|
82
80
|
const manifest = loadManifest(workspaceDir);
|
|
83
|
-
const manifestKeys = new Set(Object.keys(manifest.files || {}));
|
|
84
81
|
const files = [];
|
|
85
|
-
|
|
86
|
-
// 1. Manifest-tracked: modified or never-pushed
|
|
87
82
|
for (const [key, entry] of Object.entries(manifest.files || {})) {
|
|
88
83
|
const filePath = join(workspaceDir, key);
|
|
89
84
|
if (!existsSync(filePath)) continue;
|
|
@@ -104,30 +99,6 @@ function collectModifiedFiles(workspaceDir) {
|
|
|
104
99
|
}
|
|
105
100
|
}
|
|
106
101
|
}
|
|
107
|
-
|
|
108
|
-
// 2. Untracked: files on disk but not in manifest (e.g. manually added)
|
|
109
|
-
for (const name of readdirSync(workspaceDir, { withFileTypes: true })) {
|
|
110
|
-
if (!name.isDirectory() || name.name.startsWith('.')) continue;
|
|
111
|
-
const nsDir = join(workspaceDir, name.name);
|
|
112
|
-
const entries = walkRegistryTree(nsDir, name.name);
|
|
113
|
-
for (const entry of entries) {
|
|
114
|
-
// Build the manifest key for this file
|
|
115
|
-
const manifestKey = (entry.type === 'skills' || entry.type === 'evals')
|
|
116
|
-
? `${entry.namespacePath}/${entry.type}/${entry.slug}/${entry.skillRelPath || entry.filename}`
|
|
117
|
-
: `${entry.namespacePath}/${entry.type}/${entry.filename}`;
|
|
118
|
-
if (manifestKeys.has(manifestKey)) continue; // Already handled above
|
|
119
|
-
const registryTarget = `${REGISTRY_DIR}/${manifestKey}`;
|
|
120
|
-
files.push({
|
|
121
|
-
absPath: entry.sourcePath,
|
|
122
|
-
registryTarget,
|
|
123
|
-
type: entry.type,
|
|
124
|
-
namespace: entry.namespacePath,
|
|
125
|
-
slug: entry.slug,
|
|
126
|
-
isDir: false,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
102
|
return files;
|
|
132
103
|
}
|
|
133
104
|
|
package/constants.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// constants.mjs — Single source of truth for registry settings.
|
|
2
2
|
|
|
3
3
|
/** Base branch for PRs and sync checkout */
|
|
4
|
-
export const REGISTRY_BASE_BRANCH = '
|
|
4
|
+
export const REGISTRY_BASE_BRANCH = 'main';
|
|
5
5
|
|
|
6
6
|
/** Default registry repository */
|
|
7
7
|
export const REGISTRY_REPO = 'GoHighLevel/platform-docs';
|
package/ecc.mjs
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
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.0";
|
|
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
|
+
}
|
package/integrate.mjs
CHANGED
|
@@ -136,7 +136,7 @@ Team: ${team} | Local-first orchestration via \`.aw_docs/\` | MCPs: \`memory/*\`
|
|
|
136
136
|
└── tasks/BOARD.md # Task board
|
|
137
137
|
\`\`\`
|
|
138
138
|
|
|
139
|
-
Symlinked to \`.claude/{agents,skills,commands/
|
|
139
|
+
Symlinked to \`.claude/{agents,skills,commands/ghl,evals}\`.
|
|
140
140
|
|
|
141
141
|
## Local-First Operations
|
|
142
142
|
|
|
@@ -172,7 +172,7 @@ stitch/* → External design generation
|
|
|
172
172
|
All symlinks are prefixed with their namespace (\`platform-\` or \`${team}-\`):
|
|
173
173
|
- \`.aw_registry/platform/review/agents/security-reviewer.md\` → \`.claude/agents/platform-review-security-reviewer.md\`
|
|
174
174
|
- \`.aw_registry/${team}/frontend/agents/frontend-developer.md\` → \`.claude/agents/${team}-frontend-developer.md\`
|
|
175
|
-
- \`.aw_registry/${team}/commands/ship.md\` → \`.claude/commands/
|
|
175
|
+
- \`.aw_registry/${team}/commands/ship.md\` → \`.claude/commands/ghl/${team}-ship.md\`
|
|
176
176
|
|
|
177
177
|
## Dependency Rule
|
|
178
178
|
|
package/link.mjs
CHANGED
|
@@ -217,7 +217,7 @@ export function linkWorkspace(cwd) {
|
|
|
217
217
|
const cmdFileName = [ns, ...segments, file].join('-');
|
|
218
218
|
|
|
219
219
|
for (const ide of IDE_DIRS) {
|
|
220
|
-
const linkDir = join(cwd, ide, 'commands', '
|
|
220
|
+
const linkDir = join(cwd, ide, 'commands', 'ghl');
|
|
221
221
|
mkdirSync(linkDir, { recursive: true });
|
|
222
222
|
const linkPath = join(linkDir, cmdFileName);
|
|
223
223
|
const targetPath = join(commandsDir, file);
|
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.20",
|
|
4
4
|
"description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"apply.mjs",
|
|
26
26
|
"update.mjs",
|
|
27
27
|
"hooks.mjs",
|
|
28
|
-
"
|
|
28
|
+
"ecc.mjs"
|
|
29
29
|
],
|
|
30
30
|
"engines": {
|
|
31
31
|
"node": ">=18.0.0"
|
package/ide-hooks.mjs
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
// ide-hooks.mjs — Generate IDE session-start hooks for superpowers bootstrap.
|
|
2
|
-
//
|
|
3
|
-
// installIdeHooks(cwd) → writes/merges hook configs into ~/.claude/ and ~/.cursor/
|
|
4
|
-
// removeIdeHooks(cwd) → removes aw-generated hook entries from IDE hook configs
|
|
5
|
-
|
|
6
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
7
|
-
import { join } from 'node:path';
|
|
8
|
-
import { homedir } from 'node:os';
|
|
9
|
-
import * as fmt from './fmt.mjs';
|
|
10
|
-
|
|
11
|
-
const HOME = homedir();
|
|
12
|
-
const AW_REGISTRY = join(HOME, '.aw_registry');
|
|
13
|
-
const HOOKS_DIR = join(AW_REGISTRY, 'platform', 'superpowers', 'hooks');
|
|
14
|
-
const SESSION_START = join(HOOKS_DIR, 'session-start');
|
|
15
|
-
const RUN_HOOK_CMD = join(HOOKS_DIR, 'run-hook.cmd');
|
|
16
|
-
|
|
17
|
-
const AW_MARKER = 'aw-superpowers-session';
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Install IDE session-start hooks that bootstrap using-superpowers.
|
|
21
|
-
* Only installs if the superpowers hooks exist in the registry.
|
|
22
|
-
* Merges with existing hook configs — never clobbers user hooks.
|
|
23
|
-
* @returns {string[]} paths of created/modified hook config files
|
|
24
|
-
*/
|
|
25
|
-
export function installIdeHooks(cwd) {
|
|
26
|
-
if (!existsSync(SESSION_START)) return [];
|
|
27
|
-
|
|
28
|
-
const created = [];
|
|
29
|
-
|
|
30
|
-
const claudeResult = installClaudeCodeHooks(cwd);
|
|
31
|
-
if (claudeResult) created.push(claudeResult);
|
|
32
|
-
|
|
33
|
-
const cursorResult = installCursorHooks(cwd);
|
|
34
|
-
if (cursorResult) created.push(cursorResult);
|
|
35
|
-
|
|
36
|
-
if (created.length > 0) {
|
|
37
|
-
fmt.logStep('IDE session hooks installed (superpowers bootstrap)');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return created;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Remove aw-generated hook entries from IDE hook configs.
|
|
45
|
-
*/
|
|
46
|
-
export function removeIdeHooks(cwd) {
|
|
47
|
-
removeClaudeCodeHooks(cwd);
|
|
48
|
-
removeCursorHooks(cwd);
|
|
49
|
-
fmt.logStep('IDE session hooks removed');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// ── Claude Code ─────────────────────────────────────────────────────────
|
|
53
|
-
|
|
54
|
-
function installClaudeCodeHooks(cwd) {
|
|
55
|
-
const hooksPath = join(HOME, '.claude', 'hooks.json');
|
|
56
|
-
|
|
57
|
-
const hookEntry = {
|
|
58
|
-
matcher: AW_MARKER,
|
|
59
|
-
hooks: [{
|
|
60
|
-
type: 'command',
|
|
61
|
-
command: `"${RUN_HOOK_CMD}" session-start`,
|
|
62
|
-
async: false,
|
|
63
|
-
}],
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
let config;
|
|
67
|
-
if (existsSync(hooksPath)) {
|
|
68
|
-
try {
|
|
69
|
-
config = JSON.parse(readFileSync(hooksPath, 'utf8'));
|
|
70
|
-
} catch {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (!config.hooks) config.hooks = {};
|
|
75
|
-
if (!Array.isArray(config.hooks.SessionStart)) config.hooks.SessionStart = [];
|
|
76
|
-
|
|
77
|
-
const existing = config.hooks.SessionStart.findIndex(
|
|
78
|
-
e => e.matcher === AW_MARKER
|
|
79
|
-
);
|
|
80
|
-
if (existing !== -1) {
|
|
81
|
-
config.hooks.SessionStart[existing] = hookEntry;
|
|
82
|
-
} else {
|
|
83
|
-
config.hooks.SessionStart.push(hookEntry);
|
|
84
|
-
}
|
|
85
|
-
} else {
|
|
86
|
-
mkdirSync(join(HOME, '.claude'), { recursive: true });
|
|
87
|
-
config = {
|
|
88
|
-
hooks: {
|
|
89
|
-
SessionStart: [hookEntry],
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
writeFileSync(hooksPath, JSON.stringify(config, null, 2) + '\n');
|
|
95
|
-
return hooksPath;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function removeClaudeCodeHooks(cwd) {
|
|
99
|
-
const hooksPath = join(HOME, '.claude', 'hooks.json');
|
|
100
|
-
if (!existsSync(hooksPath)) return;
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
const config = JSON.parse(readFileSync(hooksPath, 'utf8'));
|
|
104
|
-
if (!config.hooks?.SessionStart) return;
|
|
105
|
-
|
|
106
|
-
config.hooks.SessionStart = config.hooks.SessionStart.filter(
|
|
107
|
-
e => e.matcher !== AW_MARKER
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
if (config.hooks.SessionStart.length === 0) {
|
|
111
|
-
delete config.hooks.SessionStart;
|
|
112
|
-
}
|
|
113
|
-
if (Object.keys(config.hooks).length === 0) {
|
|
114
|
-
delete config.hooks;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (Object.keys(config).length === 0) {
|
|
118
|
-
writeFileSync(hooksPath, '{}\n');
|
|
119
|
-
} else {
|
|
120
|
-
writeFileSync(hooksPath, JSON.stringify(config, null, 2) + '\n');
|
|
121
|
-
}
|
|
122
|
-
} catch { /* best effort */ }
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// ── Cursor ──────────────────────────────────────────────────────────────
|
|
126
|
-
|
|
127
|
-
function installCursorHooks(cwd) {
|
|
128
|
-
const hooksPath = join(HOME, '.cursor', 'hooks.json');
|
|
129
|
-
|
|
130
|
-
const hookEntry = {
|
|
131
|
-
command: SESSION_START,
|
|
132
|
-
_aw: AW_MARKER,
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
let config;
|
|
136
|
-
if (existsSync(hooksPath)) {
|
|
137
|
-
try {
|
|
138
|
-
config = JSON.parse(readFileSync(hooksPath, 'utf8'));
|
|
139
|
-
} catch {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (!config.hooks) config.hooks = {};
|
|
144
|
-
if (!Array.isArray(config.hooks.sessionStart)) config.hooks.sessionStart = [];
|
|
145
|
-
|
|
146
|
-
const existing = config.hooks.sessionStart.findIndex(
|
|
147
|
-
e => e._aw === AW_MARKER
|
|
148
|
-
);
|
|
149
|
-
if (existing !== -1) {
|
|
150
|
-
config.hooks.sessionStart[existing] = hookEntry;
|
|
151
|
-
} else {
|
|
152
|
-
config.hooks.sessionStart.push(hookEntry);
|
|
153
|
-
}
|
|
154
|
-
} else {
|
|
155
|
-
mkdirSync(join(HOME, '.cursor'), { recursive: true });
|
|
156
|
-
config = {
|
|
157
|
-
version: 1,
|
|
158
|
-
hooks: {
|
|
159
|
-
sessionStart: [hookEntry],
|
|
160
|
-
},
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
writeFileSync(hooksPath, JSON.stringify(config, null, 2) + '\n');
|
|
165
|
-
return hooksPath;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function removeCursorHooks(cwd) {
|
|
169
|
-
const hooksPath = join(HOME, '.cursor', 'hooks.json');
|
|
170
|
-
if (!existsSync(hooksPath)) return;
|
|
171
|
-
|
|
172
|
-
try {
|
|
173
|
-
const config = JSON.parse(readFileSync(hooksPath, 'utf8'));
|
|
174
|
-
if (!config.hooks?.sessionStart) return;
|
|
175
|
-
|
|
176
|
-
config.hooks.sessionStart = config.hooks.sessionStart.filter(
|
|
177
|
-
e => e._aw !== AW_MARKER
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
if (config.hooks.sessionStart.length === 0) {
|
|
181
|
-
delete config.hooks.sessionStart;
|
|
182
|
-
}
|
|
183
|
-
if (config.hooks && Object.keys(config.hooks).length === 0) {
|
|
184
|
-
delete config.hooks;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (Object.keys(config).length <= 1 && config.version) {
|
|
188
|
-
writeFileSync(hooksPath, JSON.stringify({ version: config.version }, null, 2) + '\n');
|
|
189
|
-
} else {
|
|
190
|
-
writeFileSync(hooksPath, JSON.stringify(config, null, 2) + '\n');
|
|
191
|
-
}
|
|
192
|
-
} catch { /* best effort */ }
|
|
193
|
-
}
|