@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 +14 -54
- package/ecc.mjs +1 -1
- package/hooks/codex-home.mjs +14 -45
- package/package.json +1 -1
- package/startup.mjs +0 -116
- package/telemetry.mjs +3 -13
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/.
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
120
|
-
]);
|
|
121
|
-
|
|
122
|
-
function escapeRegex(s) {
|
|
123
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
124
|
-
}
|
|
96
|
+
];
|
|
125
97
|
|
|
126
|
-
|
|
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
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
'
|
|
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.
|
|
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}`;
|
package/hooks/codex-home.mjs
CHANGED
|
@@ -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
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
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
|
|
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
|
|
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
|
-
|
|
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(),
|