@ghl-ai/aw 0.1.73-beta.4 → 0.1.73

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
@@ -25,6 +25,7 @@ import * as fmt from '../fmt.mjs';
25
25
  import { chalk, setSilent } from '../fmt.mjs';
26
26
  import { linkWorkspace } from '../link.mjs';
27
27
  import { generateCommands, copyInstructions, initAwDocs, syncHomeHarnessInstructions } from '../integrate.mjs';
28
+ import { renderRules } from '../render-rules.mjs';
28
29
  import { setupMcp } from '../mcp.mjs';
29
30
  import { isContextModeRequested } from '../integrations/context-mode.mjs';
30
31
  import { applyStoredStartupPreferences, ensureAwRuntimeHook, isDefaultRoutingEnabled } from '../startup.mjs';
@@ -77,19 +78,6 @@ function writeHookManifestBestEffort(manifest, context) {
77
78
  }
78
79
  }
79
80
 
80
- function installAwUsageHooksBestEffort({ silent = false } = {}) {
81
- ensureTelemetryConfig();
82
-
83
- try {
84
- if (!isDefaultRoutingEnabled(HOME, process.env)) return null;
85
- const result = installAwUsageHooks();
86
- return formatAwUsageHooksInstallReport(result);
87
- } catch (e) {
88
- if (!silent) fmt.note(`aw-usage hooks install: ${e.message}`, 'Telemetry');
89
- return null;
90
- }
91
- }
92
-
93
81
  function formatIntegrationStatusSummary(statuses, installedNow = []) {
94
82
  if (!statuses || statuses.length === 0) return null;
95
83
 
@@ -163,6 +151,11 @@ function syncHomeAndProjectInstructions(cwd, namespace) {
163
151
  initAwDocs(HOME);
164
152
  if (cwd !== HOME) {
165
153
  syncInstructionsAndAwDocs(cwd, namespace);
154
+ } else {
155
+ // Running from $HOME (fresh-laptop flow): render global IDE rules directly.
156
+ // The project branch above is otherwise the only renderRules call site, so
157
+ // init from $HOME used to leave ~/.claude/rules and ~/.cursor/rules empty.
158
+ renderRules(HOME, { homeDir: HOME });
166
159
  }
167
160
  }
168
161
 
@@ -519,8 +512,6 @@ export async function initCommand(args) {
519
512
  if (cwd !== HOME) installLocalCommitHook(cwd);
520
513
  if (!silent) fmt.logStep(`IDE wired — ${chalk.bold(symlinks)} symlinks · ${chalk.bold(commands)} commands`);
521
514
 
522
- const awUsageHooksReport = installAwUsageHooksBestEffort({ silent });
523
-
524
515
  // Write hook manifest after all hook installation is complete
525
516
  writeHookManifestBestEffort({ eccVersion: AW_ECC_TAG, awVersion: VERSION });
526
517
 
@@ -548,7 +539,6 @@ export async function initCommand(args) {
548
539
  '',
549
540
  ` ${chalk.green('✓')} Registry synced`,
550
541
  ` ${chalk.green('✓')} IDE refreshed — ${chalk.bold(symlinks)} symlinks · ${chalk.bold(commands)} commands`,
551
- awUsageHooksReport ? ` ${chalk.green('✓')} ${awUsageHooksReport}` : null,
552
542
  removedLegacyStartupFiles.length > 0
553
543
  ? ` ${chalk.green('✓')} Removed ${removedLegacyStartupFiles.length} legacy repo startup file${removedLegacyStartupFiles.length > 1 ? 's' : ''}`
554
544
  : null,
@@ -696,12 +686,25 @@ export async function initCommand(args) {
696
686
  ];
697
687
  if (!silent) fmt.logStep(`IDE wired — ${chalk.bold(symlinks)} symlinks · ${chalk.bold(commands)} commands`);
698
688
 
689
+ // Ensure telemetry config exists (generates machine_id on first run)
690
+ ensureTelemetryConfig();
691
+
699
692
  // Install bundled aw-usage producer hooks into ~/.claude.
700
693
  // Copies the scripts + lib files and non-destructively merges 5 hook phases
701
694
  // (SessionStart, UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop)
702
695
  // into ~/.claude/settings.json. Idempotent. After this, every Claude Code
703
696
  // session emits usage_events to the live telemetry API automatically.
704
- const awUsageHooksReport = installAwUsageHooksBestEffort({ silent });
697
+ let awUsageHooksReport = null;
698
+ try {
699
+ if (isDefaultRoutingEnabled(HOME, process.env)) {
700
+ const result = installAwUsageHooks();
701
+ awUsageHooksReport = formatAwUsageHooksInstallReport(result);
702
+ }
703
+ } catch (e) {
704
+ // Non-fatal — telemetry is observational. Surface the error in silent mode
705
+ // logs but don't block the install.
706
+ if (!silent) fmt.note(`aw-usage hooks install: ${e.message}`, 'Telemetry');
707
+ }
705
708
 
706
709
  // Write hook manifest after all hook installation is complete, including
707
710
  // bundled usage hooks, so `aw nuke` can prune AW-managed settings entries.
package/constants.mjs CHANGED
@@ -105,7 +105,7 @@ export const RULES_SOURCE_DIR = '.aw_rules';
105
105
  /** Runtime location exposed to harnesses and generated instructions */
106
106
  export const RULES_RUNTIME_DIR = '.aw/.aw_rules';
107
107
  /** Telemetry endpoint — override with AW_TELEMETRY_URL env var */
108
- export const TELEMETRY_URL = process.env.AW_TELEMETRY_URL || 'https://staging.services.leadconnectorhq.com/agentic-workspace/api/telemetry/events';
108
+ export const TELEMETRY_URL = process.env.AW_TELEMETRY_URL || 'https://services.leadconnectorhq.com/agentic-workspace/api/telemetry/events';
109
109
 
110
110
  /** AW bot identity for Co-Authored-By trailers */
111
111
  export const AW_BOT_NAME = 'AW';
package/ecc.mjs CHANGED
@@ -12,7 +12,7 @@ import { applyStoredStartupPreferences } from "./startup.mjs";
12
12
 
13
13
  const AW_ECC_REPO_SSH = "git@github.com:shreyansh-ghl/aw-ecc.git";
14
14
  const AW_ECC_REPO_HTTPS = "https://github.com/shreyansh-ghl/aw-ecc.git";
15
- export const AW_ECC_TAG = "v1.4.67";
15
+ export const AW_ECC_TAG = "v1.4.66";
16
16
  const REQUIRED_ECC_FILES = [
17
17
  "package.json",
18
18
  "scripts/install-apply.js",
@@ -11,23 +11,14 @@
11
11
 
12
12
  'use strict';
13
13
 
14
- const {
15
- buildEvent,
16
- sendAsync,
17
- isDisabled,
18
- findRecentSdlcSessionForProject,
19
- } = require('../lib/aw-usage-telemetry');
14
+ const { buildEvent, sendAsync, isDisabled } = require('../lib/aw-usage-telemetry');
20
15
 
21
16
  function buildCommitCreatedEvent({ commitHash = 'unknown', branch = 'unknown', cwd = process.cwd() } = {}) {
22
- const linkedSession = findRecentSdlcSessionForProject(cwd);
23
- const event = buildEvent({
24
- cwd,
25
- ...(linkedSession?.session_id ? { session_id: linkedSession.session_id } : {}),
26
- }, 'commit_created', {
17
+ // Git hooks have no harness session context, but cwd lets buildEvent derive
18
+ // project_hash so the dashboard can correlate commits back to /aw:* sessions.
19
+ const event = buildEvent({ cwd }, 'commit_created', {
27
20
  commit_hash: commitHash,
28
- commit_sha: commitHash,
29
21
  branch,
30
- ...(linkedSession?.session_id ? { linked_session_source: 'project_recent_sdlc_session' } : {}),
31
22
  });
32
23
 
33
24
  // Override harness to 'git' since this fires from a git hook, not a harness.
@@ -112,44 +112,6 @@ function getCommand(input) {
112
112
  );
113
113
  }
114
114
 
115
- function commandSegments(command) {
116
- return String(command || '')
117
- .split(/\n|&&|\|\||;/)
118
- .map(segment => segment.trim())
119
- .filter(Boolean)
120
- .map(segment => segment.replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S+)\s+)*/, ''))
121
- .filter(segment => !segment.startsWith('#'));
122
- }
123
-
124
- function detectDeployCommand(command) {
125
- const patterns = [
126
- { regex: /^(?:npx\s+)?aw\s+deploy\b/i, tool: 'aw', provider: 'aw' },
127
- { regex: /^(?:npm|yarn|pnpm|bun)\s+(?:run\s+)?deploy(?::[a-z0-9_-]+)?\b/i, tool: 'package-script', provider: 'package-script' },
128
- { regex: /^gcloud\s+run\s+deploy\b/i, tool: 'gcloud', provider: 'cloud-run' },
129
- { regex: /^firebase\s+deploy\b/i, tool: 'firebase', provider: 'firebase' },
130
- { regex: /^vercel\s+(?:deploy\b|--prod\b)/i, tool: 'vercel', provider: 'vercel' },
131
- { regex: /^wrangler\s+(?:deploy|pages\s+deploy)\b/i, tool: 'wrangler', provider: 'cloudflare' },
132
- { regex: /^helm\s+upgrade\b/i, tool: 'helm', provider: 'kubernetes' },
133
- { regex: /^kubectl\s+(?:rollout\s+restart|set\s+image)\b/i, tool: 'kubectl', provider: 'kubernetes' },
134
- { regex: /^gh\s+workflow\s+run\b.*\b(?:deploy|release|staging|production|prod)\b/i, tool: 'gh', provider: 'github-actions' },
135
- { regex: /^aws\s+(?:ecs\s+update-service|lambda\s+update-function-code)\b/i, tool: 'aws', provider: 'aws' },
136
- { regex: /^(?:sam|serverless|sls)\s+deploy\b/i, tool: 'serverless', provider: 'serverless' },
137
- ];
138
-
139
- for (const segment of commandSegments(command)) {
140
- for (const pattern of patterns) {
141
- if (pattern.regex.test(segment)) {
142
- return {
143
- deploy_tool: pattern.tool,
144
- deploy_provider: pattern.provider,
145
- };
146
- }
147
- }
148
- }
149
-
150
- return null;
151
- }
152
-
153
115
  function normalizeToolResult(input) {
154
116
  const rawToolResponse = parseMaybeJsonObject(input?.tool_response);
155
117
  const rawToolOutput = parseMaybeJsonObject(input?.tool_output);
@@ -270,32 +232,6 @@ function collectPostToolUseEvents(input, options = {}) {
270
232
 
271
233
  if (toolName === 'Shell' || toolName === 'Bash') {
272
234
  const cmd = getCommand(input);
273
- const deploy = detectDeployCommand(cmd);
274
- if (deploy) {
275
- const deployPayload = {
276
- ...deploy,
277
- tool_name: toolName,
278
- sdlc_correlated_command: slashCmd ? slashCmd.command_name : null,
279
- sdlc_correlated_namespace: slashCmd ? slashCmd.command_namespace : null,
280
- sdlc_correlated_is_sdlc_stage: slashCmd ? Boolean(slashCmd.is_sdlc_stage) : false,
281
- };
282
- const failed = isExplicitFailureExitCode(toolResult.exitCode)
283
- || inferShellFailureFromMessage(toolName, toolResult.rawErrorMessage);
284
-
285
- events.push({
286
- eventType: 'deploy_triggered',
287
- payload: deployPayload,
288
- });
289
- events.push({
290
- eventType: failed ? 'deploy_failed' : 'deploy_success',
291
- payload: {
292
- ...deployPayload,
293
- status: failed ? 'failed' : 'success',
294
- ...(isExplicitFailureExitCode(toolResult.exitCode) ? { exit_code: toolResult.exitCode } : {}),
295
- },
296
- });
297
- }
298
-
299
235
  // Codex skill detection: Bash commands that read SKILL.md files.
300
236
  if (!promptSkillOverride) {
301
237
  const skillCmdMatch = cmd.match(/\/skills\/([^/]+)\/SKILL\.md/i);
@@ -426,7 +362,6 @@ if (require.main === module) {
426
362
 
427
363
  module.exports = {
428
364
  collectPostToolUseEvents,
429
- detectDeployCommand,
430
365
  detectSdlcArtifact,
431
366
  detectTestFramework,
432
367
  inferShellFailureFromMessage,
@@ -109,9 +109,7 @@ function processPromptSubmitInput(input, deps = {}) {
109
109
 
110
110
  if (slash) {
111
111
  persistSkill(getSessionId(input), input?.turn_id || null, slash);
112
- persistSlashCmd(getSessionId(input), slash, {
113
- cwd: input?.cwd || (input?.workspace_roots && input.workspace_roots[0]) || null,
114
- });
112
+ persistSlashCmd(getSessionId(input), slash);
115
113
  events.push({
116
114
  eventType: 'skill_invoked',
117
115
  payload: {
@@ -12,7 +12,7 @@
12
12
  const https = require('https');
13
13
  const http = require('http');
14
14
 
15
- const DEFAULT_URL = 'https://staging.services.leadconnectorhq.com/agentic-workspace/api/telemetry/usage-events';
15
+ const DEFAULT_URL = 'https://services.leadconnectorhq.com/agentic-workspace/api/telemetry/usage-events';
16
16
  const TIMEOUT_MS = 10_000;
17
17
 
18
18
  function post(url, body) {
@@ -21,7 +21,6 @@ const SENDER_SCRIPT = path.join(__dirname, '..', 'hooks', 'aw-usage-telemetry-se
21
21
  const AW_HOME = path.join(os.homedir(), '.aw');
22
22
  const CONFIG_PATH = path.join(AW_HOME, 'telemetry', 'config.json');
23
23
  const SESSION_DIR = path.join(AW_HOME, 'telemetry', 'sessions');
24
- const SESSION_PROJECT_DIR = path.join(SESSION_DIR, 'by-project');
25
24
  const DEDUPE_DIR = path.join(os.tmpdir(), 'aw-usage-telemetry-dedupe');
26
25
 
27
26
  // ── Git config cache (once per process) ──────────────────────────────
@@ -158,16 +157,6 @@ function computeProjectHash(cwd) {
158
157
  return crypto.createHash('sha256').update(cwd).digest('hex').slice(0, 16);
159
158
  }
160
159
 
161
- function normalizeProjectHash(value) {
162
- return typeof value === 'string' && /^[a-f0-9]{16}$/i.test(value) ? value.toLowerCase() : null;
163
- }
164
-
165
- function resolveProjectHash(context) {
166
- if (!context || typeof context !== 'object') return null;
167
- return normalizeProjectHash(context.project_hash || context.projectHash)
168
- || computeProjectHash(context.cwd);
169
- }
170
-
171
160
  // ── Session file cleanup ─────────────────────────────────────────────
172
161
  // Prune session files older than SESSION_MAX_AGE_MS to prevent unbounded growth.
173
162
  // Called once per session start — best-effort, never blocks.
@@ -252,40 +241,21 @@ function readSessionSkill(sessionId, turnId) {
252
241
  // to the originating /aw:* invocation. Separate from `last_skill` because
253
242
  // the source is the prompt, not the tool, and the lifetime is the whole
254
243
  // session (not just one turn).
255
- function writeProjectSessionIndex(projectHash, sessionId, slashCommand) {
256
- if (!projectHash || !sessionId || !slashCommand?.is_sdlc_stage) return;
257
- try {
258
- fs.mkdirSync(SESSION_PROJECT_DIR, { recursive: true });
259
- fs.writeFileSync(path.join(SESSION_PROJECT_DIR, `${projectHash}.json`), JSON.stringify({
260
- project_hash: projectHash,
261
- session_id: sessionId,
262
- command_namespace: slashCommand.command_namespace || null,
263
- command_name: slashCommand.command_name,
264
- is_sdlc_stage: Boolean(slashCommand.is_sdlc_stage),
265
- updated_at: new Date().toISOString(),
266
- }));
267
- } catch { /* ignore */ }
268
- }
269
-
270
- function persistSessionSlashCommand(sessionId, slashCommand, context = {}) {
244
+ function persistSessionSlashCommand(sessionId, slashCommand) {
271
245
  if (!sessionId || !slashCommand?.command_name) return;
272
246
  try {
273
247
  fs.mkdirSync(SESSION_DIR, { recursive: true });
274
248
  const state = readSessionState(sessionId);
275
- const projectHash = resolveProjectHash(context) || state.project_hash || null;
276
249
  fs.writeFileSync(path.join(SESSION_DIR, sessionId + '.json'), JSON.stringify({
277
250
  ...state,
278
- ...(projectHash ? { project_hash: projectHash } : {}),
279
251
  last_slash_command: {
280
252
  command_namespace: slashCommand.command_namespace || null,
281
253
  command_name: slashCommand.command_name,
282
254
  command_args: slashCommand.command_args || '',
283
255
  is_sdlc_stage: Boolean(slashCommand.is_sdlc_stage),
284
- ...(projectHash ? { project_hash: projectHash } : {}),
285
256
  updated_at: new Date().toISOString(),
286
257
  },
287
258
  }));
288
- writeProjectSessionIndex(projectHash, sessionId, slashCommand);
289
259
  } catch { /* ignore */ }
290
260
  }
291
261
 
@@ -295,59 +265,6 @@ function readSessionLastSlashCommand(sessionId) {
295
265
  return cmd;
296
266
  }
297
267
 
298
- function parseUpdatedAt(value) {
299
- const millis = Date.parse(value || '');
300
- return Number.isFinite(millis) ? millis : 0;
301
- }
302
-
303
- function isRecentSdlcSessionCandidate(candidate, projectHash, maxAgeMs) {
304
- if (!candidate?.session_id) return false;
305
- if (!candidate?.is_sdlc_stage) return false;
306
- if (candidate.project_hash !== projectHash) return false;
307
- const updatedAt = parseUpdatedAt(candidate.updated_at);
308
- if (!updatedAt) return false;
309
- return Date.now() - updatedAt <= maxAgeMs;
310
- }
311
-
312
- function readProjectSessionIndex(projectHash, maxAgeMs) {
313
- try {
314
- const candidate = JSON.parse(fs.readFileSync(path.join(SESSION_PROJECT_DIR, `${projectHash}.json`), 'utf8'));
315
- return isRecentSdlcSessionCandidate(candidate, projectHash, maxAgeMs) ? candidate : null;
316
- } catch {
317
- return null;
318
- }
319
- }
320
-
321
- function findRecentSdlcSessionForProject(cwd, maxAgeMs = SESSION_MAX_AGE_MS) {
322
- const projectHash = computeProjectHash(cwd);
323
- if (!projectHash) return null;
324
-
325
- const indexed = readProjectSessionIndex(projectHash, maxAgeMs);
326
- if (indexed) return indexed;
327
-
328
- let best = null;
329
- try {
330
- const entries = fs.readdirSync(SESSION_DIR);
331
- for (const entry of entries) {
332
- if (!entry.endsWith('.json')) continue;
333
- const sessionId = entry.slice(0, -'.json'.length);
334
- const state = readSessionState(sessionId);
335
- const cmd = state.last_slash_command;
336
- const candidate = {
337
- session_id: sessionId,
338
- project_hash: cmd?.project_hash || state.project_hash || null,
339
- is_sdlc_stage: Boolean(cmd?.is_sdlc_stage),
340
- updated_at: cmd?.updated_at || state.updated_at || null,
341
- };
342
- if (!isRecentSdlcSessionCandidate(candidate, projectHash, maxAgeMs)) continue;
343
- if (!best || parseUpdatedAt(candidate.updated_at) > parseUpdatedAt(best.updated_at)) {
344
- best = candidate;
345
- }
346
- }
347
- } catch { /* ignore */ }
348
- return best;
349
- }
350
-
351
268
  // ── Short-TTL dedupe guards ──────────────────────────────────────────
352
269
 
353
270
  function normalizeDedupePart(value) {
@@ -586,7 +503,6 @@ module.exports = {
586
503
  readSessionSkill,
587
504
  persistSessionSlashCommand,
588
505
  readSessionLastSlashCommand,
589
- findRecentSdlcSessionForProject,
590
506
  readLastAssistantFromTranscript,
591
507
  resolvePromptText,
592
508
  getAwCliVersionDetails,
package/hooks.mjs CHANGED
@@ -102,7 +102,7 @@ if git log -1 --format='%b' HEAD 2>/dev/null | grep -qF "Co-Authored-By: AW"; th
102
102
  TELEMETRY_HOOK="$HOME/.aw-ecc/scripts/hooks/aw-usage-commit-created.js"
103
103
  fi
104
104
  if command -v node >/dev/null 2>&1 && [ -f "$TELEMETRY_HOOK" ]; then
105
- COMMIT_HASH="$(git rev-parse HEAD 2>/dev/null || echo unknown)"
105
+ COMMIT_HASH="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)"
106
106
  BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)"
107
107
  node "$TELEMETRY_HOOK" "$COMMIT_HASH" "$BRANCH" "$(pwd)" >/dev/null 2>&1
108
108
  fi
@@ -286,7 +286,7 @@ if git log -1 --format='%b' HEAD 2>/dev/null | grep -qF "Co-Authored-By: AW"; th
286
286
  TELEMETRY_HOOK="$HOME/.aw-ecc/scripts/hooks/aw-usage-commit-created.js"
287
287
  fi
288
288
  if command -v node >/dev/null 2>&1 && [ -f "$TELEMETRY_HOOK" ]; then
289
- COMMIT_HASH="$(git rev-parse HEAD 2>/dev/null || echo unknown)"
289
+ COMMIT_HASH="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)"
290
290
  BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)"
291
291
  node "$TELEMETRY_HOOK" "$COMMIT_HASH" "$BRANCH" "$(pwd)" >/dev/null 2>&1
292
292
  fi
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.73-beta.4",
4
- "description": "Agentic Workspace CLI pull, push & manage agents, skills and commands from the registry",
3
+ "version": "0.1.73",
4
+ "description": "Agentic Workspace CLI \u2014 pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "aw": "bin.js"
@@ -74,4 +74,4 @@
74
74
  "devDependencies": {
75
75
  "vitest": "^4.1.2"
76
76
  }
77
- }
77
+ }