@ghl-ai/aw 0.1.36-beta.3 → 0.1.36-beta.4

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/drop.mjs CHANGED
@@ -7,7 +7,7 @@ import * as config from '../config.mjs';
7
7
  import * as fmt from '../fmt.mjs';
8
8
  import { chalk } from '../fmt.mjs';
9
9
  import { resolveInput } from '../paths.mjs';
10
- import { removeFromSparseCheckout, isValidClone } from '../git.mjs';
10
+ import { removeFromSparseCheckout, isValidClone, getLocalRegistryDir } from '../git.mjs';
11
11
  import { REGISTRY_DIR, REGISTRY_REPO } from '../constants.mjs';
12
12
  import { linkWorkspace } from '../link.mjs';
13
13
 
@@ -18,7 +18,7 @@ export function dropCommand(args) {
18
18
  const HOME = homedir();
19
19
  const AW_HOME = join(HOME, '.aw');
20
20
  const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
21
- const workspaceDir = join(cwd, '.aw_registry');
21
+ const workspaceDir = getLocalRegistryDir(cwd, GLOBAL_AW_DIR);
22
22
 
23
23
  fmt.intro('aw drop');
24
24
 
package/commands/init.mjs CHANGED
@@ -205,7 +205,7 @@ export async function initCommand(args) {
205
205
  if (cwd !== HOME) await setupMcp(cwd, freshCfg?.namespace || team, { silent });
206
206
  installGlobalHooks();
207
207
 
208
- if (cwd !== HOME && !existsSync(join(cwd, '.aw_registry')) && !isWorktree(join(cwd, '.aw'))) {
208
+ if (cwd !== HOME && !isWorktree(join(cwd, '.aw'))) {
209
209
  try {
210
210
  addProjectWorktree(AW_HOME, cwd);
211
211
  if (!silent) fmt.logStep('Linked current project as git worktree');
@@ -220,7 +220,7 @@ export async function initCommand(args) {
220
220
  '',
221
221
  ` ${chalk.green('✓')} Registry updated`,
222
222
  ` ${chalk.green('✓')} IDE integration refreshed`,
223
- cwd !== HOME && existsSync(join(cwd, '.aw_registry')) ? ` ${chalk.green('✓')} Current project linked` : null,
223
+ cwd !== HOME && isWorktree(join(cwd, '.aw')) ? ` ${chalk.green('✓')} Current project linked` : null,
224
224
  ].filter(Boolean).join('\n'));
225
225
  }
226
226
  return;
@@ -301,7 +301,7 @@ export async function initCommand(args) {
301
301
  installIdeTasks();
302
302
 
303
303
  // Step 4: Link current project as a git worktree (gives IDE git panel)
304
- if (cwd !== HOME && !existsSync(join(cwd, '.aw_registry')) && !isWorktree(join(cwd, '.aw'))) {
304
+ if (cwd !== HOME && !isWorktree(join(cwd, '.aw'))) {
305
305
  try {
306
306
  addProjectWorktree(AW_HOME, cwd);
307
307
  fmt.logStep('Linked current project as git worktree');
@@ -319,7 +319,7 @@ export async function initCommand(args) {
319
319
  ` ${chalk.green('✓')} IDE integration: ~/.claude/, ~/.cursor/, ~/.codex/`,
320
320
  hooksInstalled ? ` ${chalk.green('✓')} Git hooks: auto-sync on pull/clone (core.hooksPath)` : null,
321
321
  ` ${chalk.green('✓')} IDE task: auto-sync on workspace open`,
322
- cwd !== HOME && existsSync(join(cwd, '.aw_registry')) ? ` ${chalk.green('✓')} Linked in current project` : null,
322
+ cwd !== HOME && isWorktree(join(cwd, '.aw')) ? ` ${chalk.green('✓')} Linked in current project` : null,
323
323
  '',
324
324
  ` ${chalk.dim('Existing repos:')} ${chalk.bold('cd <project> && aw link')}`,
325
325
  ` ${chalk.dim('New clones:')} auto-linked via git hook`,
@@ -1,12 +1,11 @@
1
1
  // commands/link-project.mjs — Link current project to registry via git worktree
2
2
 
3
- import { existsSync } from 'node:fs';
4
3
  import { join } from 'node:path';
5
4
  import { homedir } from 'node:os';
6
5
  import * as fmt from '../fmt.mjs';
7
6
  import { chalk } from '../fmt.mjs';
8
7
  import { addProjectWorktree, isWorktree, isValidClone } from '../git.mjs';
9
- import { REGISTRY_REPO } from '../constants.mjs';
8
+ import { REGISTRY_DIR, REGISTRY_REPO } from '../constants.mjs';
10
9
 
11
10
  const HOME = homedir();
12
11
  const AW_HOME = join(HOME, '.aw');
@@ -28,25 +27,19 @@ export function linkProjectCommand(args) {
28
27
  }
29
28
 
30
29
  const worktreeDir = join(cwd, '.aw');
31
- const registryLink = join(cwd, '.aw_registry');
32
30
 
33
31
  if (isWorktree(worktreeDir)) {
34
32
  fmt.logSuccess(`Already linked — ${chalk.dim('.aw/')} is a git worktree`);
35
33
  return;
36
34
  }
37
35
 
38
- if (existsSync(registryLink)) {
39
- fmt.logSuccess('.aw_registry already exists in this project');
40
- return;
41
- }
42
-
43
36
  try {
44
37
  addProjectWorktree(AW_HOME, cwd);
45
38
  fmt.logSuccess([
46
39
  `Linked project as git worktree`,
47
40
  '',
48
- ` ${chalk.green('✓')} ${chalk.dim('.aw/')} git worktree (IDE git panel enabled)`,
49
- ` ${chalk.green('✓')} ${chalk.dim('.aw_registry/')} symlink → .aw/.aw_registry/`,
41
+ ` ${chalk.green('✓')} ${chalk.dim('.aw/')} git worktree (IDE git panel enabled)`,
42
+ ` ${chalk.green('✓')} ${chalk.dim(`.aw/${REGISTRY_DIR}/`)} registry content`,
50
43
  ].join('\n'));
51
44
  } catch (e) {
52
45
  fmt.cancel(`Failed to link project: ${e.message}`);
package/commands/push.mjs CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  getCurrentBranch,
17
17
  isValidClone,
18
18
  isWorktree,
19
+ getLocalRegistryDir,
19
20
  } from '../git.mjs';
20
21
 
21
22
  const PUSHABLE_TYPES = ['agents', 'skills', 'commands', 'evals'];
@@ -355,7 +356,7 @@ export function pushCommand(args) {
355
356
  const hasWorktree = isWorktree(localAw);
356
357
  const awHome = hasWorktree ? localAw : globalAw;
357
358
  const registrySubDir = join(awHome, REGISTRY_DIR);
358
- const workspaceDir = join(cwd, '.aw_registry');
359
+ const workspaceDir = getLocalRegistryDir(cwd, join(HOME, '.aw_registry'));
359
360
 
360
361
  // Save current worktree branch so we can restore it after push
361
362
  const worktreeBranch = hasWorktree ? getCurrentBranch(awHome) : null;
@@ -2,12 +2,13 @@
2
2
 
3
3
  import { readFileSync, existsSync, readdirSync, statSync, mkdtempSync } from 'node:fs';
4
4
  import { join } from 'node:path';
5
- import { tmpdir } from 'node:os';
5
+ import { tmpdir, homedir } from 'node:os';
6
6
  import { execSync } from 'node:child_process';
7
7
  import * as config from '../config.mjs';
8
8
  import * as fmt from '../fmt.mjs';
9
9
  import { chalk } from '../fmt.mjs';
10
10
  import { REGISTRY_BASE_BRANCH, REGISTRY_REPO, REGISTRY_DIR } from '../constants.mjs';
11
+ import { getLocalRegistryDir } from '../git.mjs';
11
12
 
12
13
  export function searchCommand(args) {
13
14
  const query = (args._positional || []).join(' ').toLowerCase();
@@ -19,7 +20,7 @@ export function searchCommand(args) {
19
20
  fmt.intro('aw search');
20
21
 
21
22
  const cwd = process.cwd();
22
- const workspaceDir = join(cwd, '.aw_registry');
23
+ const workspaceDir = getLocalRegistryDir(cwd, join(homedir(), '.aw_registry'));
23
24
 
24
25
  // ── Local search ──
25
26
  const localResults = searchLocal(workspaceDir, query);
package/git.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  // git.mjs — Git helpers: sparse checkout (temp) + persistent clone operations.
2
2
 
3
3
  import { execSync, exec as execCb } from 'node:child_process';
4
- import { mkdtempSync, existsSync, lstatSync, symlinkSync, rmSync } from 'node:fs';
4
+ import { mkdtempSync, existsSync, lstatSync, rmSync } from 'node:fs';
5
5
  import { join, basename } from 'node:path';
6
6
  import { tmpdir } from 'node:os';
7
7
  import { promisify } from 'node:util';
@@ -28,7 +28,7 @@ export function sparseCheckout(repo, paths) {
28
28
  }
29
29
 
30
30
  try {
31
- execSync('git sparse-checkout init --cone', { cwd: tempDir, stdio: 'pipe' });
31
+ execSync('git sparse-checkout init --no-cone', { cwd: tempDir, stdio: 'pipe' });
32
32
  execSync(`git sparse-checkout set --skip-checks ${paths.map(p => `"${p}"`).join(' ')}`, {
33
33
  cwd: tempDir, stdio: 'pipe',
34
34
  });
@@ -55,7 +55,7 @@ export async function sparseCheckoutAsync(repo, paths) {
55
55
  }
56
56
 
57
57
  try {
58
- await exec('git sparse-checkout init --cone', { cwd: tempDir });
58
+ await exec('git sparse-checkout init --no-cone', { cwd: tempDir });
59
59
  await exec(`git sparse-checkout set --skip-checks ${paths.map(p => `"${p}"`).join(' ')}`, { cwd: tempDir });
60
60
  await exec(`git checkout ${REGISTRY_BASE_BRANCH}`, { cwd: tempDir });
61
61
  } catch (e) {
@@ -123,7 +123,7 @@ export function initPersistentClone(repoUrl, awHome, sparsePaths) {
123
123
  }
124
124
 
125
125
  try {
126
- execSync('git sparse-checkout init --cone', { cwd: awHome, stdio: 'pipe' });
126
+ execSync('git sparse-checkout init --no-cone', { cwd: awHome, stdio: 'pipe' });
127
127
  execSync(`git sparse-checkout set ${sparsePaths.map(p => `"${p}"`).join(' ')}`, { cwd: awHome, stdio: 'pipe' });
128
128
  execSync(`git checkout ${REGISTRY_BASE_BRANCH}`, { cwd: awHome, stdio: 'pipe' });
129
129
  } catch (e) {
@@ -327,6 +327,20 @@ export function checkoutMain(awHome) {
327
327
 
328
328
  // ── Project worktree operations ────────────────────────────────────────────────
329
329
 
330
+ /**
331
+ * Resolve the registry directory for a given working directory.
332
+ *
333
+ * - If cwd/.aw/ is a linked worktree → returns cwd/.aw/.aw_registry/
334
+ * - Otherwise → returns fallback (usually ~/.aw_registry/)
335
+ *
336
+ * Works correctly when cwd is HOME: ~/.aw/ is the main clone (not a linked
337
+ * worktree), so isWorktree returns false and the global fallback is used.
338
+ */
339
+ export function getLocalRegistryDir(cwd, fallback) {
340
+ if (isWorktree(join(cwd, '.aw'))) return join(cwd, '.aw', REGISTRY_DIR);
341
+ return fallback;
342
+ }
343
+
330
344
  /**
331
345
  * Check if dir is a git linked worktree (has .git as a FILE, not a directory).
332
346
  */
@@ -348,15 +362,9 @@ export function isWorktree(dir) {
348
362
  */
349
363
  export function addProjectWorktree(awHome, projectDir) {
350
364
  const worktreeDir = join(projectDir, '.aw');
351
- const registryLink = join(projectDir, '.aw_registry');
352
365
 
353
- // Already set up — just ensure symlink exists
354
- if (isWorktree(worktreeDir)) {
355
- if (!existsSync(registryLink)) {
356
- symlinkSync(join('.aw', REGISTRY_DIR), registryLink);
357
- }
358
- return worktreeDir;
359
- }
366
+ // Already set up
367
+ if (isWorktree(worktreeDir)) return worktreeDir;
360
368
 
361
369
  const slug = basename(projectDir)
362
370
  .toLowerCase()
@@ -388,7 +396,7 @@ export function addProjectWorktree(awHome, projectDir) {
388
396
 
389
397
  // Set up sparse checkout in the worktree, mirroring the main clone's paths
390
398
  try {
391
- execSync(`git -C "${worktreeDir}" sparse-checkout init --cone`, { stdio: 'pipe' });
399
+ execSync(`git -C "${worktreeDir}" sparse-checkout init --no-cone`, { stdio: 'pipe' });
392
400
 
393
401
  let sparsePaths = [];
394
402
  try {
@@ -406,11 +414,6 @@ export function addProjectWorktree(awHome, projectDir) {
406
414
  throw new Error(`Failed to configure worktree sparse checkout: ${e.message}`);
407
415
  }
408
416
 
409
- // Relative symlink: project/.aw_registry → .aw/.aw_registry
410
- if (!existsSync(registryLink)) {
411
- symlinkSync(join('.aw', REGISTRY_DIR), registryLink);
412
- }
413
-
414
417
  return worktreeDir;
415
418
  }
416
419
 
@@ -420,10 +423,6 @@ export function addProjectWorktree(awHome, projectDir) {
420
423
  */
421
424
  export function removeProjectWorktree(awHome, projectDir) {
422
425
  const worktreeDir = join(projectDir, '.aw');
423
- const registryLink = join(projectDir, '.aw_registry');
424
-
425
- // Remove .aw_registry symlink
426
- try { rmSync(registryLink, { force: true }); } catch { /* best effort */ }
427
426
 
428
427
  // Remove via git first
429
428
  try {
package/hooks.mjs CHANGED
@@ -55,9 +55,8 @@ if command -v aw >/dev/null 2>&1; then
55
55
  fi`);
56
56
 
57
57
  const POST_CHECKOUT = makeDispatcher('post-checkout', `\
58
- AW_REGISTRY="$HOME/.aw_registry"
59
- if [ -d "$AW_REGISTRY" ] && [ ! -e ".aw_registry" ] && [ -d ".git" ]; then
60
- ln -s "$AW_REGISTRY" ".aw_registry" 2>/dev/null
58
+ if [ -d "$HOME/.aw" ] && [ ! -d ".aw" ] && [ -d ".git" ] && command -v aw >/dev/null 2>&1; then
59
+ aw link >/dev/null 2>&1 &
61
60
  fi
62
61
  if command -v aw >/dev/null 2>&1; then
63
62
  aw init --silent >/dev/null 2>&1 &
package/integrate.mjs CHANGED
@@ -2,15 +2,17 @@
2
2
 
3
3
  import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, rmSync } from 'node:fs';
4
4
  import { join } from 'node:path';
5
+ import { homedir } from 'node:os';
5
6
  import * as fmt from './fmt.mjs';
6
7
  import * as config from './config.mjs';
8
+ import { getLocalRegistryDir } from './git.mjs';
7
9
 
8
10
  /**
9
11
  * Count hand-written commands already present in the registry.
10
12
  * No CLI stub generation — all commands come from the registry itself.
11
13
  */
12
14
  export function generateCommands(cwd) {
13
- const awDir = join(cwd, '.aw_registry');
15
+ const awDir = getLocalRegistryDir(cwd, join(homedir(), '.aw_registry'));
14
16
 
15
17
  // Clean old .generated-commands if it exists (migration)
16
18
  const oldGenDir = join(awDir, '.generated-commands');
package/link.mjs CHANGED
@@ -4,6 +4,7 @@ import { existsSync, lstatSync, mkdirSync, readdirSync, unlinkSync, symlinkSync,
4
4
  import { join, relative } from 'node:path';
5
5
  import { homedir } from 'node:os';
6
6
  import * as fmt from './fmt.mjs';
7
+ import { getLocalRegistryDir } from './git.mjs';
7
8
 
8
9
  function forceSymlink(target, linkPath) {
9
10
  try { unlinkSync(linkPath); } catch { /* not there yet */ }
@@ -126,7 +127,8 @@ function flatName(ns, name) {
126
127
  * file content is resolved by LLMs at runtime using AW-PROTOCOL.md context.
127
128
  */
128
129
  export function linkWorkspace(cwd) {
129
- const awDir = join(cwd, '.aw_registry');
130
+ const GLOBAL_AW_DIR = join(homedir(), '.aw_registry');
131
+ const awDir = getLocalRegistryDir(cwd, GLOBAL_AW_DIR);
130
132
  if (!existsSync(awDir)) return 0;
131
133
 
132
134
  let created = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.36-beta.3",
3
+ "version": "0.1.36-beta.4",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {