@ghl-ai/aw 0.1.42-beta.7 → 0.1.42-beta.9

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
@@ -13,6 +13,7 @@ import {
13
13
  readFileSync,
14
14
  rmSync,
15
15
  realpathSync,
16
+ appendFileSync,
16
17
  } from 'node:fs';
17
18
  import { execSync } from 'node:child_process';
18
19
  import { join, dirname, sep } from 'node:path';
@@ -87,65 +88,24 @@ function syncHomeAndProjectInstructions(cwd, namespace) {
87
88
  }
88
89
  }
89
90
 
90
- // ── Ensure ~/.aw/.git/info/exclude has the whitelist block ─────────────
91
- //
92
- // Strategy: only .aw_registry/, .aw_rules/, content/ are tracked — everything
93
- // else at the top level of ~/.aw/ is local-only (telemetry/, hooks/, logs,
94
- // .DS_Store, etc.). We write to .git/info/exclude (not tracked .gitignore)
95
- // so upstream pulls never conflict.
96
-
97
- const AW_MANAGED_BEGIN = '# BEGIN aw-managed (do not edit; managed by `aw init`)';
98
- const AW_MANAGED_END = '# END aw-managed';
99
-
100
- const AW_MANAGED_BLOCK = [
101
- AW_MANAGED_BEGIN,
102
- '# Whitelist: only these top-level entries are tracked; everything else is local-only.',
103
- '/*',
104
- '!/.aw_registry',
105
- '!/.aw_rules',
106
- '!/content',
107
- '',
108
- '# Nested local state within whitelisted dirs',
109
- '/.aw_registry/.sync-config.json',
110
- AW_MANAGED_END,
111
- '',
112
- ].join('\n');
113
-
114
- // Legacy flat lines appended by earlier versions of ensureAwGitignore — strip on upgrade
115
- // so we don't leave stale rules lingering outside the managed block.
116
- const LEGACY_GITIGNORE_LINES = new Set([
91
+ // ── Ensure ~/.aw/.gitignore has personal/local entries ───────────────────
92
+
93
+ const AW_GITIGNORE_ENTRIES = [
117
94
  '.aw_registry/.sync-config.json',
118
95
  'hooks/',
119
- '# aw: personal/local — do not commit',
120
- ]);
121
-
122
- function escapeRegex(s) {
123
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
124
- }
96
+ ];
125
97
 
126
- export function ensureAwGitignore(awHome) {
98
+ function ensureAwGitignore(awHome) {
99
+ // Use .git/info/exclude so the tracked .gitignore stays clean
127
100
  const excludePath = join(awHome, '.git', 'info', 'exclude');
128
101
  let existing = '';
129
- try { existing = readFileSync(excludePath, 'utf8'); } catch (err) { void err; /* doesn't exist yet */ }
130
-
131
- // Strip any prior aw-managed block so re-rendering is idempotent.
132
- const blockRegex = new RegExp(
133
- `${escapeRegex(AW_MANAGED_BEGIN)}[\\s\\S]*?${escapeRegex(AW_MANAGED_END)}\\n?`,
134
- 'g'
135
- );
136
- const withoutManaged = existing.replace(blockRegex, '');
137
-
138
- // Strip legacy flat lines (pre-whitelist implementation).
139
- const cleaned = withoutManaged
140
- .split('\n')
141
- .filter(line => !LEGACY_GITIGNORE_LINES.has(line.trim()))
142
- .join('\n');
143
-
144
- const prefix = cleaned === '' || cleaned.endsWith('\n') ? cleaned : cleaned + '\n';
145
- const next = prefix + AW_MANAGED_BLOCK;
146
-
147
- if (next === existing) return; // already up to date
148
- try { writeFileSync(excludePath, next); } catch (err) { void err; /* best effort */ }
102
+ try { existing = readFileSync(excludePath, 'utf8'); } catch { /* doesn't exist yet */ }
103
+ const missing = AW_GITIGNORE_ENTRIES.filter(e => !existing.includes(e));
104
+ if (missing.length === 0) return;
105
+ const block = (existing.endsWith('\n') || existing === '' ? '' : '\n')
106
+ + '# aw: personal/local — do not commit\n'
107
+ + missing.join('\n') + '\n';
108
+ try { appendFileSync(excludePath, block); } catch { /* best effort */ }
149
109
  }
150
110
 
151
111
  // ── IDE tasks for auto-pull ─────────────────────────────────────────────
package/ecc.mjs CHANGED
@@ -10,7 +10,7 @@ import { applyStoredStartupPreferences } from "./startup.mjs";
10
10
 
11
11
  const AW_ECC_REPO_SSH = "git@github.com:shreyansh-ghl/aw-ecc.git";
12
12
  const AW_ECC_REPO_HTTPS = "https://github.com/shreyansh-ghl/aw-ecc.git";
13
- export const AW_ECC_TAG = "v1.4.38-beta.6";
13
+ export const AW_ECC_TAG = "v1.4.38-beta.8";
14
14
 
15
15
  const MARKETPLACE_NAME = "aw-marketplace";
16
16
  const PLUGIN_KEY = `aw@${MARKETPLACE_NAME}`;
@@ -46,27 +46,10 @@ const CODEX_HOME_PHASE_BLUEPRINTS = {
46
46
  scriptName: 'aw-user-prompt-submit.sh',
47
47
  scriptMarker: '# aw-managed: codex-global-user-prompt-submit',
48
48
  buildScriptContent() {
49
- return `#!/usr/bin/env bash
50
- ${this.scriptMarker}
51
- set -euo pipefail
52
-
53
- # Capture stdin so we can feed it to both the existing delegate and telemetry.
54
- STDIN=$(cat)
55
-
56
- # Fire telemetry in background (non-blocking).
57
- TELEMETRY_HOOK="$HOME/.aw-ecc/scripts/hooks/aw-usage-prompt-submit.js"
58
- if [[ -f "$TELEMETRY_HOOK" ]]; then
59
- echo "$STDIN" | node "$TELEMETRY_HOOK" >/dev/null 2>&1 &
60
- fi
61
-
62
- # Delegate to existing rules-context hook.
63
- TARGET="$HOME/.aw-ecc/scripts/hooks/session-start-rules-context.sh"
64
- if [[ -f "$TARGET" ]]; then
65
- echo "$STDIN" | exec bash "$TARGET"
66
- fi
67
-
68
- exit 0
69
- `;
49
+ return buildDelegatingPhaseScript({
50
+ marker: this.scriptMarker,
51
+ targetPath: '$HOME/.aw-ecc/scripts/hooks/session-start-rules-context.sh',
52
+ });
70
53
  },
71
54
  buildEntry(command) {
72
55
  return {
@@ -105,18 +88,11 @@ exit 0
105
88
  scriptName: 'aw-post-tool-use.sh',
106
89
  scriptMarker: '# aw-managed: codex-global-post-tool-use',
107
90
  buildScriptContent() {
108
- return `#!/usr/bin/env bash
109
- ${this.scriptMarker}
110
- set -euo pipefail
111
-
112
- # AW PostToolUse phase for Codex — usage telemetry.
113
- # Codex tool_name is always "Bash" — only PR and aw-push detection works.
114
- TELEMETRY_HOOK="$HOME/.aw-ecc/scripts/hooks/aw-usage-post-tool-use.js"
115
- if [[ -f "$TELEMETRY_HOOK" ]]; then
116
- exec node "$TELEMETRY_HOOK"
117
- fi
118
- echo '{}'
119
- `;
91
+ return buildReservedPhaseScript({
92
+ marker: this.scriptMarker,
93
+ phase: 'PostToolUse',
94
+ harnessLabel: 'Codex home routing',
95
+ });
120
96
  },
121
97
  buildEntry(command) {
122
98
  return {
@@ -134,18 +110,11 @@ echo '{}'
134
110
  scriptName: 'aw-stop.sh',
135
111
  scriptMarker: '# aw-managed: codex-global-stop',
136
112
  buildScriptContent() {
137
- return `#!/usr/bin/env bash
138
- ${this.scriptMarker}
139
- set -euo pipefail
140
-
141
- # AW Stop phase for Codex — usage telemetry.
142
- # Codex Stop has last_assistant_message, stop_hook_active — no token usage.
143
- TELEMETRY_HOOK="$HOME/.aw-ecc/scripts/hooks/aw-usage-stop.js"
144
- if [[ -f "$TELEMETRY_HOOK" ]]; then
145
- exec node "$TELEMETRY_HOOK"
146
- fi
147
- echo '{}'
148
- `;
113
+ return buildReservedPhaseScript({
114
+ marker: this.scriptMarker,
115
+ phase: 'Stop',
116
+ harnessLabel: 'Codex home routing',
117
+ });
149
118
  },
150
119
  buildEntry(command) {
151
120
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.42-beta.7",
3
+ "version": "0.1.42-beta.9",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {
package/startup.mjs CHANGED
@@ -14,34 +14,6 @@ const ENABLED_MODE = 'enabled';
14
14
  const DISABLED_MODE = 'disabled';
15
15
 
16
16
  const CLAUDE_DISABLE_DESCRIPTION = 'AW-managed override: disable automatic AW session routing';
17
- const CLAUDE_TELEMETRY_DESCRIPTION = 'AW usage telemetry';
18
- const CLAUDE_TELEMETRY_HOOKS = [
19
- {
20
- phase: 'SessionStart',
21
- matcher: undefined,
22
- command: 'node "$HOME/.aw-ecc/scripts/hooks/aw-usage-session-start.js"',
23
- },
24
- {
25
- phase: 'PostToolUse',
26
- matcher: 'Skill|Agent|Shell|Bash',
27
- command: 'node "$HOME/.aw-ecc/scripts/hooks/aw-usage-post-tool-use.js"',
28
- },
29
- {
30
- phase: 'Stop',
31
- matcher: undefined,
32
- command: 'node "$HOME/.aw-ecc/scripts/hooks/aw-usage-stop.js"',
33
- },
34
- {
35
- phase: 'PostToolUseFailure',
36
- matcher: undefined,
37
- command: 'node "$HOME/.aw-ecc/scripts/hooks/aw-usage-post-tool-use-failure.js"',
38
- },
39
- {
40
- phase: 'UserPromptSubmit',
41
- matcher: undefined,
42
- command: 'node "$HOME/.aw-ecc/scripts/hooks/aw-usage-prompt-submit.js"',
43
- },
44
- ];
45
17
  const CURSOR_SESSION_START_COMMAND = 'node .cursor/hooks/session-start.js';
46
18
  const CURSOR_SESSION_START_DESCRIPTION = 'Load previous context and detect environment';
47
19
  const REPO_CURSOR_SESSION_START_COMMAND = 'bash "$(git rev-parse --show-toplevel)/hooks/aw-session-start"';
@@ -172,92 +144,6 @@ function hasCommandHook(entry, command) {
172
144
  );
173
145
  }
174
146
 
175
- function isManagedClaudeTelemetryEntry(entry) {
176
- if (entry?.description === CLAUDE_TELEMETRY_DESCRIPTION) return true;
177
- // Also match legacy entries (no description) by command pattern
178
- const cmds = Array.isArray(entry?.hooks) ? entry.hooks.map(h => h?.command || '') : [];
179
- return cmds.some(c => c.includes('aw-usage-'));
180
- }
181
-
182
- function buildClaudeTelemetryEntry(hookDef) {
183
- const entry = {
184
- hooks: [
185
- {
186
- type: 'command',
187
- command: hookDef.command,
188
- },
189
- ],
190
- description: CLAUDE_TELEMETRY_DESCRIPTION,
191
- };
192
- if (hookDef.matcher) {
193
- entry.matcher = hookDef.matcher;
194
- }
195
- return entry;
196
- }
197
-
198
- function enableClaudeTelemetryHooks(homeDir = homedir()) {
199
- const settingsPath = join(homeDir, '.claude', 'settings.json');
200
- const config = readJson(settingsPath, {});
201
- ensureHooksObject(config);
202
-
203
- let changed = false;
204
-
205
- for (const hookDef of CLAUDE_TELEMETRY_HOOKS) {
206
- const current = Array.isArray(config.hooks[hookDef.phase]) ? config.hooks[hookDef.phase] : [];
207
- const managed = current.filter(isManagedClaudeTelemetryEntry);
208
-
209
- // If exactly one properly described entry exists, nothing to do
210
- if (managed.length === 1 && managed[0].description === CLAUDE_TELEMETRY_DESCRIPTION) continue;
211
-
212
- // Remove all legacy/duplicate telemetry entries, then add the canonical one
213
- const cleaned = current.filter(e => !isManagedClaudeTelemetryEntry(e));
214
- config.hooks[hookDef.phase] = [...cleaned, buildClaudeTelemetryEntry(hookDef)];
215
- changed = true;
216
- }
217
-
218
- if (!changed) return [];
219
-
220
- writeJson(settingsPath, config);
221
- return [settingsPath];
222
- }
223
-
224
- function disableClaudeTelemetryHooks(homeDir = homedir()) {
225
- const settingsPath = join(homeDir, '.claude', 'settings.json');
226
- if (!existsSync(settingsPath)) return [];
227
-
228
- const config = readJson(settingsPath, {});
229
- if (!isObject(config.hooks)) return [];
230
-
231
- let changed = false;
232
-
233
- for (const hookDef of CLAUDE_TELEMETRY_HOOKS) {
234
- const current = Array.isArray(config.hooks[hookDef.phase]) ? config.hooks[hookDef.phase] : null;
235
- if (!current) continue;
236
-
237
- const filtered = current.filter(entry => !isManagedClaudeTelemetryEntry(entry));
238
- if (filtered.length !== current.length) {
239
- changed = true;
240
- if (filtered.length > 0) {
241
- config.hooks[hookDef.phase] = filtered;
242
- } else {
243
- delete config.hooks[hookDef.phase];
244
- }
245
- }
246
- }
247
-
248
- if (!changed) return [];
249
-
250
- if (isEmptyObject(config.hooks)) {
251
- delete config.hooks;
252
- }
253
-
254
- if (!pruneEmptySettingsFile(settingsPath, config)) {
255
- writeJson(settingsPath, config);
256
- }
257
-
258
- return [settingsPath];
259
- }
260
-
261
147
  function isManagedCodexSessionStartEntry(entry) {
262
148
  return isManagedCodexHomeEntry(entry, CODEX_SESSION_START_DEFINITION);
263
149
  }
@@ -604,12 +490,10 @@ export function applyGlobalStartupMode(mode, homeDir = homedir()) {
604
490
 
605
491
  if (normalizedMode === DISABLED_MODE) {
606
492
  updatedFiles.push(...disableClaudeStartup(homeDir));
607
- updatedFiles.push(...disableClaudeTelemetryHooks(homeDir));
608
493
  updatedFiles.push(...disableCodexStartup(homeDir));
609
494
  updatedFiles.push(...disableCursorStartup(homeDir));
610
495
  } else {
611
496
  updatedFiles.push(...enableClaudeStartup(homeDir));
612
- updatedFiles.push(...enableClaudeTelemetryHooks(homeDir));
613
497
  updatedFiles.push(...enableCodexStartup(homeDir));
614
498
  updatedFiles.push(...enableCursorStartup(homeDir));
615
499
  }
package/telemetry.mjs CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { createHash, randomUUID } from 'node:crypto';
9
9
  import { hostname, userInfo, platform, arch, release } from 'node:os';
10
- import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, lstatSync, unlinkSync } from 'node:fs';
10
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'node:fs';
11
11
  import { join, dirname } from 'node:path';
12
12
  import { fileURLToPath } from 'node:url';
13
13
  import { execSync } from 'node:child_process';
@@ -16,9 +16,7 @@ import { TELEMETRY_URL, AW_HOME } from './constants.mjs';
16
16
  const __dirname = dirname(fileURLToPath(import.meta.url));
17
17
  const VERSION = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8')).version;
18
18
 
19
- const CONFIG_DIR = join(AW_HOME, 'telemetry');
20
- const CONFIG_PATH = join(CONFIG_DIR, 'config.json');
21
- const LEGACY_CONFIG_PATH = join(AW_HOME, '.telemetry');
19
+ const CONFIG_PATH = join(AW_HOME, '.telemetry');
22
20
 
23
21
  // ── Config ──────────────────────────────────────────────────────────
24
22
 
@@ -32,15 +30,7 @@ export function loadConfig() {
32
30
  if (existsSync(CONFIG_PATH)) {
33
31
  return JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));
34
32
  }
35
- // One-time migration from legacy single-file shape (~/.aw/.telemetry).
36
- // Only triggered when new path is missing, so re-running is a no-op.
37
- if (existsSync(LEGACY_CONFIG_PATH) && lstatSync(LEGACY_CONFIG_PATH).isFile()) {
38
- const legacy = JSON.parse(readFileSync(LEGACY_CONFIG_PATH, 'utf8'));
39
- saveConfig(legacy);
40
- try { unlinkSync(LEGACY_CONFIG_PATH); } catch (err) { void err; /* best-effort cleanup */ }
41
- return legacy;
42
- }
43
- } catch (err) { void err; /* corrupt file — fall through to fresh config */ }
33
+ } catch { /* corrupt file recreate */ }
44
34
 
45
35
  const config = {
46
36
  machine_id: generateMachineId(),