@ghl-ai/aw 0.1.35-beta.1 → 0.1.35-beta.2

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/ide-hooks.mjs +193 -0
  2. package/package.json +3 -2
package/ide-hooks.mjs ADDED
@@ -0,0 +1,193 @@
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.35-beta.1",
3
+ "version": "0.1.35-beta.2",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,7 +24,8 @@
24
24
  "registry.mjs",
25
25
  "apply.mjs",
26
26
  "update.mjs",
27
- "hooks.mjs"
27
+ "hooks.mjs",
28
+ "ide-hooks.mjs"
28
29
  ],
29
30
  "engines": {
30
31
  "node": ">=18.0.0"