@ghl-ai/aw 0.1.40 → 0.1.41-beta.1

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,7 +13,6 @@ import {
13
13
  readFileSync,
14
14
  rmSync,
15
15
  realpathSync,
16
- appendFileSync,
17
16
  } from 'node:fs';
18
17
  import { execSync } from 'node:child_process';
19
18
  import { join, dirname, sep } from 'node:path';
@@ -88,24 +87,65 @@ function syncHomeAndProjectInstructions(cwd, namespace) {
88
87
  }
89
88
  }
90
89
 
91
- // ── Ensure ~/.aw/.gitignore has personal/local entries ───────────────────
92
-
93
- const AW_GITIGNORE_ENTRIES = [
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([
94
117
  '.aw_registry/.sync-config.json',
95
118
  'hooks/',
96
- ];
119
+ '# aw: personal/local — do not commit',
120
+ ]);
121
+
122
+ function escapeRegex(s) {
123
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
124
+ }
97
125
 
98
- function ensureAwGitignore(awHome) {
99
- // Use .git/info/exclude so the tracked .gitignore stays clean
126
+ export function ensureAwGitignore(awHome) {
100
127
  const excludePath = join(awHome, '.git', 'info', 'exclude');
101
128
  let existing = '';
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 */ }
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 */ }
109
149
  }
110
150
 
111
151
  // ── IDE tasks for auto-pull ─────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.40",
3
+ "version": "0.1.41-beta.1",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {
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 } from 'node:fs';
10
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, lstatSync, unlinkSync } 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,7 +16,9 @@ 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_PATH = join(AW_HOME, '.telemetry');
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');
20
22
 
21
23
  // ── Config ──────────────────────────────────────────────────────────
22
24
 
@@ -30,7 +32,15 @@ export function loadConfig() {
30
32
  if (existsSync(CONFIG_PATH)) {
31
33
  return JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));
32
34
  }
33
- } catch { /* corrupt file recreate */ }
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 */ }
34
44
 
35
45
  const config = {
36
46
  machine_id: generateMachineId(),