@ghl-ai/aw 0.1.34 → 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.
package/commands/init.mjs CHANGED
@@ -19,6 +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 { installIdeHooks } from '../ide-hooks.mjs';
22
23
 
23
24
  const __dirname = dirname(fileURLToPath(import.meta.url));
24
25
  const VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')).version;
@@ -206,6 +207,7 @@ export async function initCommand(args) {
206
207
  setupMcp(HOME, freshCfg?.namespace || team) || [];
207
208
  if (cwd !== HOME) setupMcp(cwd, freshCfg?.namespace || team);
208
209
  installGlobalHooks();
210
+ const ideHookFiles = installIdeHooks(HOME);
209
211
 
210
212
  // Link current project if needed
211
213
  if (cwd !== HOME && !existsSync(join(cwd, '.aw_registry'))) {
@@ -286,6 +288,7 @@ export async function initCommand(args) {
286
288
  const mcpFiles = setupMcp(HOME, team) || [];
287
289
  if (cwd !== HOME) setupMcp(cwd, team);
288
290
  const hooksInstalled = installGlobalHooks();
291
+ const ideHookFilesInit = installIdeHooks(HOME);
289
292
  installIdeTasks();
290
293
 
291
294
  // Step 4: Symlink in current directory if it's a git repo
@@ -304,6 +307,7 @@ export async function initCommand(args) {
304
307
  createdFiles: [
305
308
  ...instructionFiles.map(p => p.startsWith(HOME) ? p.slice(HOME.length + 1) : p),
306
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),
307
311
  ],
308
312
  globalHooksDir: hooksInstalled ? join(HOME, '.aw', 'hooks') : null,
309
313
  };
package/commands/nuke.mjs CHANGED
@@ -9,6 +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 { removeIdeHooks } from '../ide-hooks.mjs';
12
13
 
13
14
  const HOME = homedir();
14
15
  const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
@@ -249,7 +250,10 @@ export function nukeCommand(args) {
249
250
  // 4. Remove git hooks (core.hooksPath + legacy template)
250
251
  removeGitHooks(manifest);
251
252
 
252
- // 5. Remove IDE auto-init tasks
253
+ // 5. Remove IDE session hooks (superpowers bootstrap)
254
+ removeIdeHooks(HOME);
255
+
256
+ // 6. Remove IDE auto-init tasks
253
257
  removeIdeTasks();
254
258
 
255
259
  // 5b. Remove upgrade lock/log (inside .aw_registry, must happen before dir removal)
package/commands/push.mjs CHANGED
@@ -73,12 +73,17 @@ function collectBatchFiles(folderAbsPath, workspaceDir) {
73
73
  }
74
74
 
75
75
  /**
76
- * Collect all modified files from manifest (for no-args push).
76
+ * Collect all modified + untracked files for no-args push.
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).
77
79
  * Returns array of { absPath, registryTarget, type, namespace, slug, isDir }.
78
80
  */
79
81
  function collectModifiedFiles(workspaceDir) {
80
82
  const manifest = loadManifest(workspaceDir);
83
+ const manifestKeys = new Set(Object.keys(manifest.files || {}));
81
84
  const files = [];
85
+
86
+ // 1. Manifest-tracked: modified or never-pushed
82
87
  for (const [key, entry] of Object.entries(manifest.files || {})) {
83
88
  const filePath = join(workspaceDir, key);
84
89
  if (!existsSync(filePath)) continue;
@@ -99,6 +104,30 @@ function collectModifiedFiles(workspaceDir) {
99
104
  }
100
105
  }
101
106
  }
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
+
102
131
  return files;
103
132
  }
104
133
 
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 = 'main';
4
+ export const REGISTRY_BASE_BRANCH = 'sync/platform-superpowers-l8ynb';
5
5
 
6
6
  /** Default registry repository */
7
7
  export const REGISTRY_REPO = 'GoHighLevel/platform-docs';
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.34",
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"