@ghl-ai/aw 0.1.41-beta.0 → 0.1.41-beta.2

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/cli.mjs CHANGED
@@ -26,6 +26,7 @@ const COMMANDS = {
26
26
  nuke: () => import('./commands/nuke.mjs').then(m => m.nukeCommand),
27
27
  daemon: () => import('./commands/daemon.mjs').then(m => m.daemonCommand),
28
28
  telemetry: () => import('./commands/telemetry.mjs').then(m => m.telemetryCommand),
29
+ 'slack-sim': () => import('./commands/slack-sim.mjs').then(m => m.slackSimCommand),
29
30
  };
30
31
 
31
32
  function parseArgs(argv) {
package/commands/init.mjs CHANGED
@@ -371,7 +371,7 @@ export async function initCommand(args) {
371
371
  }
372
372
 
373
373
  // Determine sparse paths
374
- const sparsePaths = [`.aw_registry/platform`, `content`, RULES_SOURCE_DIR, `.aw_registry/AW-PROTOCOL.md`, `CODEOWNERS`, `.github/CODEOWNERS`];
374
+ const sparsePaths = [`.aw_registry/platform`, `content`, RULES_SOURCE_DIR, `.aw_registry/AW-PROTOCOL.md`, `CODEOWNERS`];
375
375
  if (folderName) {
376
376
  sparsePaths.push(`.aw_registry/${folderName}`);
377
377
  }
package/commands/push.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  // commands/push.mjs — Push local agents/skills to registry via PR using persistent git clone
2
2
 
3
3
  import { existsSync, statSync, readFileSync, appendFileSync } from 'node:fs';
4
- import { join, dirname, relative } from 'node:path';
4
+ import { join, dirname } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { exec as execCb, execFile as execFileCb } from 'node:child_process';
7
7
  import { promisify } from 'node:util';
@@ -361,11 +361,7 @@ async function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = f
361
361
 
362
362
  // CODEOWNERS for new namespaces (runs inside spinner so no silent gap)
363
363
  const topNamespaces = [...new Set(files.map(f => f.namespace.split('/')[0]))];
364
- // GitHub looks for CODEOWNERS in .github/ first (takes precedence over root).
365
- // platform-docs has .github/CODEOWNERS as the authoritative file.
366
- const codeownersPath = existsSync(join(awHome, '.github', 'CODEOWNERS'))
367
- ? join(awHome, '.github', 'CODEOWNERS')
368
- : join(awHome, 'CODEOWNERS');
364
+ const codeownersPath = join(awHome, 'CODEOWNERS');
369
365
  const newNamespaces = [];
370
366
  const ghUser = await getGitHubUser();
371
367
  for (const ns of topNamespaces) {
@@ -378,8 +374,7 @@ async function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = f
378
374
 
379
375
  const pathsToStage = files.map(f => f.registryTarget);
380
376
  if (newNamespaces.length > 0 && existsSync(codeownersPath)) {
381
- // Stage the correct relative path (could be .github/CODEOWNERS or root CODEOWNERS)
382
- pathsToStage.push(relative(awHome, codeownersPath));
377
+ pathsToStage.push('CODEOWNERS');
383
378
  }
384
379
  // Also stage any extra paths (content/, CODEOWNERS manual edits) passed from the caller
385
380
  for (const p of extraPaths) {
@@ -478,17 +473,17 @@ export async function pushCommand(args) {
478
473
  if (!input) {
479
474
  const rulesChanged = hasRulesChanges(cwd);
480
475
 
481
- // Extra paths outside .aw_registry/ that aw also manages: content/, CODEOWNERS, .github/CODEOWNERS.
476
+ // Extra paths outside .aw_registry/ that aw also manages: content/ and CODEOWNERS.
482
477
  // Detect staged variants for staged-mode and unstaged variants for auto-mode.
483
478
  const getExtraStagedPaths = async () => {
484
479
  try {
485
- const { stdout } = await exec(`git -C "${awHome}" diff --cached --name-only -- content/ CODEOWNERS .github/CODEOWNERS`);
480
+ const { stdout } = await exec(`git -C "${awHome}" diff --cached --name-only -- content/ CODEOWNERS`);
486
481
  return stdout.trim().split('\n').filter(Boolean);
487
482
  } catch { return []; }
488
483
  };
489
484
  const getExtraChangedPaths = async () => {
490
485
  try {
491
- const { stdout } = await exec(`git -C "${awHome}" status --porcelain -- content/ CODEOWNERS .github/CODEOWNERS`);
486
+ const { stdout } = await exec(`git -C "${awHome}" status --porcelain -- content/ CODEOWNERS`);
492
487
  // git status --porcelain prefix is XY (2 chars) + optional space + path.
493
488
  // Staged-only files: `M path` (2-char prefix); unstaged files: ` M path` (3-char prefix).
494
489
  // slice(2).trimStart() handles both cases correctly.
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.37";
13
+ export const AW_ECC_TAG = "v1.4.39-beta.1";
14
14
 
15
15
  const MARKETPLACE_NAME = "aw-marketplace";
16
16
  const PLUGIN_KEY = `aw@${MARKETPLACE_NAME}`;
package/git.mjs CHANGED
@@ -9,6 +9,49 @@ import { REGISTRY_BASE_BRANCH, REGISTRY_DIR, DOCS_SOURCE_DIR, RULES_SOURCE_DIR }
9
9
 
10
10
  const exec = promisify(execCb);
11
11
 
12
+ /**
13
+ * Env vars applied to every git command that touches the network.
14
+ * GIT_TERMINAL_PROMPT=0 prevents git from hanging when it would otherwise
15
+ * prompt for credentials (e.g. HTTPS URL with SSH-only auth configured).
16
+ * Instead, git exits immediately with a non-zero code that we can catch.
17
+ */
18
+ const GIT_NET_ENV = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
19
+
20
+ /**
21
+ * Convert an HTTPS GitHub URL to its SSH equivalent.
22
+ * e.g. https://github.com/Org/Repo.git → git@github.com:Org/Repo.git
23
+ */
24
+ export function toSshUrl(httpsUrl) {
25
+ const m = httpsUrl.match(/^https:\/\/github\.com\/(.+)$/);
26
+ return m ? `git@github.com:${m[1]}` : httpsUrl;
27
+ }
28
+
29
+ /**
30
+ * Detect whether the user's git is configured to prefer SSH for github.com.
31
+ * Checks: 1) git insteadOf config 2) gh CLI auth status
32
+ * Returns true if SSH is preferred.
33
+ */
34
+ export function prefersSsh() {
35
+ // Check git url."git@github.com:".insteadOf
36
+ try {
37
+ const out = execSync(
38
+ 'git config --global --get-regexp "url\\.git@github\\.com.*\\.insteadOf"',
39
+ { stdio: 'pipe', encoding: 'utf8', env: GIT_NET_ENV },
40
+ ).trim();
41
+ if (out.includes('https://github.com')) return true;
42
+ } catch { /* not configured */ }
43
+
44
+ // Check gh auth — if protocol is ssh, prefer SSH
45
+ try {
46
+ const out = execSync('gh auth status 2>&1', {
47
+ stdio: 'pipe', encoding: 'utf8', env: GIT_NET_ENV, timeout: 5000,
48
+ });
49
+ if (/git protocol:\s*ssh/i.test(out)) return true;
50
+ } catch { /* gh not installed or not authed */ }
51
+
52
+ return false;
53
+ }
54
+
12
55
  // ── Backward-compat: temp-dir sparse checkout (used by search.mjs) ────────────
13
56
 
14
57
  /**
@@ -18,12 +61,23 @@ const exec = promisify(execCb);
18
61
  export function sparseCheckout(repo, paths) {
19
62
  const tempDir = mkdtempSync(join(tmpdir(), 'aw-'));
20
63
 
21
- const repoUrl = repo.startsWith('http') ? repo : `https://github.com/${repo}.git`;
22
- try {
23
- execSync(`git clone --filter=blob:none --no-checkout "${repoUrl}" "${tempDir}"`, {
24
- stdio: 'pipe',
25
- });
26
- } catch (e) {
64
+ const httpsUrl = repo.startsWith('http') ? repo : `https://github.com/${repo}.git`;
65
+ const urls = prefersSsh() ? [toSshUrl(httpsUrl), httpsUrl] : [httpsUrl, toSshUrl(httpsUrl)];
66
+
67
+ let cloned = false;
68
+ for (const url of urls) {
69
+ try {
70
+ execSync(`git clone --filter=blob:none --no-checkout "${url}" "${tempDir}"`, {
71
+ stdio: 'pipe', env: GIT_NET_ENV,
72
+ });
73
+ cloned = true;
74
+ break;
75
+ } catch {
76
+ // Clean up partial clone so next attempt can use the same tempDir
77
+ try { rmSync(join(tempDir, '.git'), { recursive: true, force: true }); } catch {}
78
+ }
79
+ }
80
+ if (!cloned) {
27
81
  throw new Error(`Failed to clone ${repo}. Check your git credentials and repo access.`);
28
82
  }
29
83
 
@@ -47,10 +101,20 @@ export function sparseCheckout(repo, paths) {
47
101
  export async function sparseCheckoutAsync(repo, paths) {
48
102
  const tempDir = mkdtempSync(join(tmpdir(), 'aw-'));
49
103
 
50
- const repoUrl = repo.startsWith('http') ? repo : `https://github.com/${repo}.git`;
51
- try {
52
- await exec(`git clone --filter=blob:none --no-checkout "${repoUrl}" "${tempDir}"`);
53
- } catch (e) {
104
+ const httpsUrl = repo.startsWith('http') ? repo : `https://github.com/${repo}.git`;
105
+ const urls = prefersSsh() ? [toSshUrl(httpsUrl), httpsUrl] : [httpsUrl, toSshUrl(httpsUrl)];
106
+
107
+ let cloned = false;
108
+ for (const url of urls) {
109
+ try {
110
+ await exec(`git clone --filter=blob:none --no-checkout "${url}" "${tempDir}"`, { env: GIT_NET_ENV });
111
+ cloned = true;
112
+ break;
113
+ } catch {
114
+ try { rmSync(join(tempDir, '.git'), { recursive: true, force: true }); } catch {}
115
+ }
116
+ }
117
+ if (!cloned) {
54
118
  throw new Error(`Failed to clone ${repo}. Check your git credentials and repo access.`);
55
119
  }
56
120
 
@@ -106,7 +170,9 @@ export function isValidClone(awHome, repoUrl) {
106
170
  if (!existsSync(join(awHome, '.git'))) return false;
107
171
  try {
108
172
  const remote = execSync('git remote get-url origin', { cwd: awHome, stdio: 'pipe', encoding: 'utf8' }).trim();
109
- return remote === repoUrl || remote === repoUrl.replace(/\.git$/, '') + '.git' || remote.replace(/\.git$/, '') === repoUrl.replace(/\.git$/, '');
173
+ // Normalize both sides to bare repo path for comparison (handles HTTPS SSH)
174
+ const normalize = (u) => u.replace(/\.git$/, '').replace(/^git@github\.com:/, 'https://github.com/');
175
+ return normalize(remote) === normalize(repoUrl);
110
176
  } catch {
111
177
  return false;
112
178
  }
@@ -123,10 +189,26 @@ export async function initPersistentClone(repoUrl, awHome, sparsePaths) {
123
189
  try { execSync(`rm -rf "${awHome}"`, { stdio: 'pipe' }); } catch {}
124
190
  }
125
191
 
126
- try {
127
- await exec(`git clone --filter=blob:none --no-checkout "${repoUrl}" "${awHome}"`);
128
- } catch (e) {
129
- throw new Error(`Failed to clone ${repoUrl}: ${e.message}`);
192
+ const urls = prefersSsh()
193
+ ? [toSshUrl(repoUrl), repoUrl]
194
+ : [repoUrl, toSshUrl(repoUrl)];
195
+
196
+ let cloned = false;
197
+ for (const url of urls) {
198
+ // Clean up any partial clone from a previous attempt
199
+ if (existsSync(awHome) && !isValidClone(awHome, url)) {
200
+ try { execSync(`rm -rf "${awHome}"`, { stdio: 'pipe' }); } catch {}
201
+ }
202
+ try {
203
+ await exec(`git clone --filter=blob:none --no-checkout "${url}" "${awHome}"`, { env: GIT_NET_ENV });
204
+ cloned = true;
205
+ break;
206
+ } catch {
207
+ // try next URL
208
+ }
209
+ }
210
+ if (!cloned) {
211
+ throw new Error(`Failed to clone ${repoUrl}. Check your git credentials and repo access (HTTPS and SSH both failed).`);
130
212
  }
131
213
 
132
214
  try {
@@ -286,7 +368,7 @@ export async function fetchAndMerge(awHome, { silent = true } = {}) {
286
368
 
287
369
  // ── 2. Fetch ──────────────────────────────────────────────────────────────
288
370
  try {
289
- await exec(`git -C "${awHome}" fetch origin ${REGISTRY_BASE_BRANCH}`);
371
+ await exec(`git -C "${awHome}" fetch origin ${REGISTRY_BASE_BRANCH}`, { env: GIT_NET_ENV });
290
372
  } catch (e) {
291
373
  throw new Error(`Failed to fetch from origin: ${e.message}`);
292
374
  }
@@ -315,7 +397,7 @@ export async function fetchAndMerge(awHome, { silent = true } = {}) {
315
397
  // someone else pushed to the remote tracking branch since our last fetch.
316
398
  if (isPushBranch) {
317
399
  try {
318
- await exec(`git -C "${awHome}" push --force-with-lease origin "${currentBranch}"`);
400
+ await exec(`git -C "${awHome}" push --force-with-lease origin "${currentBranch}"`, { env: GIT_NET_ENV });
319
401
  } catch { /* non-blocking — divergence will be resolved on next aw push */ }
320
402
  }
321
403
  } catch {
@@ -442,7 +524,7 @@ export function updatePushBranch(awHome, pushBranchName) {
442
524
  }
443
525
 
444
526
  try {
445
- execSync(`git -C "${awHome}" push origin "${pushBranchName}" --force`, { stdio: 'pipe' });
527
+ execSync(`git -C "${awHome}" push origin "${pushBranchName}" --force`, { stdio: 'pipe', env: GIT_NET_ENV });
446
528
  } catch (e) {
447
529
  throw new Error(`Failed to push branch: ${e.message}`);
448
530
  }
@@ -485,7 +567,7 @@ export async function createPushBranch(awHome, branchName, files, commitMsg, pre
485
567
  }
486
568
 
487
569
  try {
488
- await exec(`git -C "${awHome}" push -u origin "${branchName}"`);
570
+ await exec(`git -C "${awHome}" push -u origin "${branchName}"`, { env: GIT_NET_ENV });
489
571
  } catch (e) {
490
572
  throw new Error(`Failed to push branch: ${e.message}`);
491
573
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.41-beta.0",
3
+ "version": "0.1.41-beta.2",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {