@ghl-ai/aw 0.1.48-beta.0 → 0.1.48-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
@@ -50,6 +50,9 @@ function parseArgs(argv) {
50
50
  if (arg === '--dry-run') {
51
51
  args['--dry-run'] = true;
52
52
  i++;
53
+ } else if (arg === '--aw-docs-only' || arg === '--docs-only') {
54
+ args[arg] = true;
55
+ i++;
53
56
  } else if (arg === '-v' || arg === '--verbose') {
54
57
  args['-v'] = true;
55
58
  i++;
@@ -99,6 +102,7 @@ function printHelp() {
99
102
  sec('Upload'),
100
103
  cmd('aw push', 'Push all modified files (creates one PR)'),
101
104
  cmd('aw push --aw-docs-only', 'Publish generated .aw_docs companions and print share links'),
105
+ cmd('aw push --aw-docs-only --feature <slug>', 'Publish one .aw_docs feature folder and print share links'),
102
106
  cmd('aw push <path>', 'Push file, folder, or namespace to registry'),
103
107
  cmd('aw push-rules [path]', 'Push platform rules to platform-docs'),
104
108
  cmd('aw push --dry-run [path]', 'Preview what would be pushed'),
package/commands/push.mjs CHANGED
@@ -36,6 +36,7 @@ import {
36
36
  AW_DOCS_SEED_BRANCH,
37
37
  AW_DOCS_PUBLISH_DIR,
38
38
  AW_DOCS_PUBLIC_BASE_URL,
39
+ AW_DOCS_TEAMOFONE_ORIGIN,
39
40
  AW_DOCS_TEAMOFONE_BASE_URL,
40
41
  AW_CO_AUTHOR,
41
42
  defaultAwDocsGithubDocsConfig,
@@ -143,13 +144,71 @@ function collectFiles(root, base = root) {
143
144
  return files;
144
145
  }
145
146
 
146
- function collectProjectAwDocs(cwd, home) {
147
+ function normalizeRelPath(value) {
148
+ return String(value || '')
149
+ .trim()
150
+ .replace(/\\/g, '/')
151
+ .replace(/^\.\//, '')
152
+ .replace(/\/+$/, '');
153
+ }
154
+
155
+ function featureScopeFromInput(input) {
156
+ const value = normalizeRelPath(input);
157
+ if (!value) return null;
158
+
159
+ const match = value.match(/^(?:\.aw_docs\/)?features\/([^/]+)$/);
160
+ if (!match) {
161
+ throw new Error('Docs-only publish path must be .aw_docs/features/<feature-slug> or use --feature <feature-slug>.');
162
+ }
163
+ return awDocsFeatureScope(match[1]);
164
+ }
165
+
166
+ function awDocsFeatureScope(featureSlug) {
167
+ const slug = String(featureSlug || '').trim();
168
+ if (!slug || slug === 'true') {
169
+ throw new Error('Missing feature slug. Use: aw push --aw-docs-only --feature <feature-slug>');
170
+ }
171
+ if (!/^[A-Za-z0-9._-]+$/.test(slug)) {
172
+ throw new Error(`Invalid feature slug "${slug}". Feature slugs may contain letters, numbers, dot, underscore, and dash only.`);
173
+ }
174
+ return {
175
+ type: 'feature',
176
+ slug,
177
+ relPrefix: `features/${slug}`,
178
+ };
179
+ }
180
+
181
+ function resolveAwDocsScope(input, featureFlag) {
182
+ const inputScope = featureScopeFromInput(input);
183
+ const flagScope = featureFlag ? awDocsFeatureScope(featureFlag) : null;
184
+ if (inputScope && flagScope && inputScope.relPrefix !== flagScope.relPrefix) {
185
+ throw new Error(`Docs-only publish received conflicting scopes: ${inputScope.relPrefix} and ${flagScope.relPrefix}.`);
186
+ }
187
+ return flagScope || inputScope;
188
+ }
189
+
190
+ function collectProjectAwDocs(cwd, home, scope = null) {
147
191
  const projectRoot = getProjectRoot(cwd, home);
148
192
  const source = join(projectRoot, AW_DOCS_DIR);
149
193
 
150
194
  if (!existsSync(source)) return { projectRoot, files: [] };
151
195
 
152
196
  const files = [];
197
+ if (scope) {
198
+ const sourceRoot = join(source, scope.relPrefix);
199
+ if (!existsSync(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
200
+ throw new Error(`No publishable AW docs found under ${AW_DOCS_DIR}/${scope.relPrefix}.`);
201
+ }
202
+ for (const relFromRoot of collectFiles(sourceRoot)) {
203
+ const relPath = `${scope.relPrefix}/${relFromRoot}`.replace(/\\/g, '/');
204
+ files.push({
205
+ relPath,
206
+ absPath: join(source, relPath),
207
+ });
208
+ }
209
+ return { projectRoot, files };
210
+ }
211
+
153
212
  for (const root of PROJECT_AW_DOCS_AUTO_ROOTS) {
154
213
  const sourceRoot = join(source, root);
155
214
  if (!existsSync(sourceRoot)) continue;
@@ -220,11 +279,11 @@ function resolveAwDocsPublishConfig(projectRoot) {
220
279
  const teamofoneBaseUrl = String(
221
280
  process.env.AW_DOCS_TEAMOFONE_BASE_URL
222
281
  || githubDocs.teamofone_base_url
223
- || AW_DOCS_TEAMOFONE_BASE_URL
224
- || '',
282
+ || AW_DOCS_TEAMOFONE_BASE_URL,
225
283
  ).trim();
226
284
  const publicBaseUrl = process.env.AW_DOCS_PUBLIC_BASE_URL
227
285
  || githubDocs.public_base_url
286
+ || AW_DOCS_PUBLIC_BASE_URL
228
287
  || `https://github.com/${String(repo).replace(/\.git$/, '')}/blob/${branch}`;
229
288
 
230
289
  return {
@@ -234,7 +293,7 @@ function resolveAwDocsPublishConfig(projectRoot) {
234
293
  branch,
235
294
  seedBranch: process.env.AW_DOCS_SEED_BRANCH || githubDocs.seed_branch || AW_DOCS_SEED_BRANCH,
236
295
  dest,
237
- teamofoneBaseUrl,
296
+ teamofoneBaseUrl: normalizeTeamOfOneBaseUrl(teamofoneBaseUrl),
238
297
  publicBaseUrl,
239
298
  };
240
299
  }
@@ -389,6 +448,14 @@ function appendPathToUrl(baseUrl, path) {
389
448
  return `${basePath.replace(/\/$/, '')}/${encodedPath}${query}${hash}`;
390
449
  }
391
450
 
451
+ function normalizeTeamOfOneBaseUrl(baseUrl) {
452
+ const value = String(baseUrl || '').trim();
453
+ if (!value) return '';
454
+ if (/^https?:\/\//i.test(value)) return value;
455
+ if (value.startsWith('/')) return `${AW_DOCS_TEAMOFONE_ORIGIN.replace(/\/$/, '')}${value}`;
456
+ return `https://${value.replace(/^\/+/, '')}`;
457
+ }
458
+
392
459
  function appendQueryParam(url, key, value) {
393
460
  const hashIndex = url.indexOf('#');
394
461
  const withoutHash = hashIndex === -1 ? url : url.slice(0, hashIndex);
@@ -422,8 +489,13 @@ function awDocsRepositoryUrl(publishedPath, publishConfig) {
422
489
  }
423
490
 
424
491
  function printAwDocsLinks(links, limit = 10) {
492
+ fmt.logInfo(chalk.bold('Remote Docs'));
425
493
  for (const link of links.slice(0, limit)) {
426
- fmt.logInfo(chalk.cyan(link.remoteUrl));
494
+ fmt.logInfo(` ${chalk.dim(link.relPath)}`);
495
+ fmt.logInfo(` TeamOfOne: ${chalk.cyan(link.remoteUrl)}`);
496
+ if (link.repositoryUrl && link.repositoryUrl !== link.remoteUrl) {
497
+ fmt.logInfo(` GitHub: ${chalk.cyan(link.repositoryUrl)}`);
498
+ }
427
499
  }
428
500
  if (links.length > limit) {
429
501
  fmt.logInfo(chalk.dim(`...and ${links.length - limit} more`));
@@ -466,7 +538,7 @@ function titleForAwDoc(relPath) {
466
538
  return `${label} ${ext}`;
467
539
  }
468
540
 
469
- function updateAwDocsManifest(docsRepoDir, { repoSlug, sourceRepo, githubUsername, docs, publishConfig }) {
541
+ function updateAwDocsManifest(docsRepoDir, { repoSlug, sourceRepo, githubUsername, docs, publishConfig, scope = null }) {
470
542
  const manifestPath = join(docsRepoDir, 'manifest.json');
471
543
  const manifest = existsSync(manifestPath)
472
544
  ? JSON.parse(readFileSync(manifestPath, 'utf8'))
@@ -480,25 +552,32 @@ function updateAwDocsManifest(docsRepoDir, { repoSlug, sourceRepo, githubUsernam
480
552
  repoEntry.slug = repoSlug;
481
553
  repoEntry.sourceRepo = sourceRepo;
482
554
  repoEntry.users ||= {};
555
+ const existingUser = repoEntry.users[githubUsername] || {};
556
+ const nextDocs = docs.map(doc => ({
557
+ relPath: doc.relPath,
558
+ publishedPath: doc.publishedPath,
559
+ remoteUrl: awDocsRemoteUrl(doc.publishedPath, publishConfig),
560
+ repositoryUrl: awDocsRepositoryUrl(doc.publishedPath, publishConfig),
561
+ sha: createHash('sha256').update(readFileSync(join(docsRepoDir, doc.publishedPath))).digest('hex'),
562
+ syncedAt: now,
563
+ title: titleForAwDoc(doc.relPath),
564
+ }));
565
+ const preservedDocs = scope?.relPrefix
566
+ ? (existingUser.docs || []).filter(doc => !String(doc.relPath || '').startsWith(`${scope.relPrefix}/`))
567
+ : [];
568
+
483
569
  repoEntry.users[githubUsername] = {
570
+ ...existingUser,
484
571
  githubUsername,
485
- docs: docs.map(doc => ({
486
- relPath: doc.relPath,
487
- publishedPath: doc.publishedPath,
488
- remoteUrl: awDocsRemoteUrl(doc.publishedPath, publishConfig),
489
- repositoryUrl: awDocsRepositoryUrl(doc.publishedPath, publishConfig),
490
- sha: createHash('sha256').update(readFileSync(join(docsRepoDir, doc.publishedPath))).digest('hex'),
491
- syncedAt: now,
492
- title: titleForAwDoc(doc.relPath),
493
- })),
572
+ docs: scope?.relPrefix ? [...preservedDocs, ...nextDocs] : nextDocs,
494
573
  };
495
574
  manifest.awDocs.repos[repoSlug] = repoEntry;
496
575
 
497
576
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
498
577
  }
499
578
 
500
- async function publishProjectAwDocs(cwd, home, dryRun) {
501
- const { projectRoot, files } = collectProjectAwDocs(cwd, home);
579
+ async function publishProjectAwDocs(cwd, home, dryRun, scope = null) {
580
+ const { projectRoot, files } = collectProjectAwDocs(cwd, home, scope);
502
581
  if (files.length === 0) return { hasDocs: false, publishedPaths: [] };
503
582
 
504
583
  const publishConfig = resolveAwDocsPublishConfig(projectRoot);
@@ -537,7 +616,10 @@ async function publishProjectAwDocs(cwd, home, dryRun) {
537
616
  s.start(`Publishing ${files.length} AW doc${files.length > 1 ? 's' : ''} to ${publishConfig.repo}...`);
538
617
  try {
539
618
  const docsRepoDir = await ensureAwDocsRepoClone(home, publishConfig);
540
- rmSync(join(docsRepoDir, publishConfig.dest, repoSlug, githubUsername), {
619
+ const deleteTarget = scope?.relPrefix
620
+ ? join(docsRepoDir, publishConfig.dest, repoSlug, githubUsername, scope.relPrefix)
621
+ : join(docsRepoDir, publishConfig.dest, repoSlug, githubUsername);
622
+ rmSync(deleteTarget, {
541
623
  recursive: true,
542
624
  force: true,
543
625
  });
@@ -546,7 +628,7 @@ async function publishProjectAwDocs(cwd, home, dryRun) {
546
628
  mkdirSync(dirname(dest), { recursive: true });
547
629
  copyFileSync(doc.absPath, dest);
548
630
  }
549
- updateAwDocsManifest(docsRepoDir, { repoSlug, sourceRepo, githubUsername, docs, publishConfig });
631
+ updateAwDocsManifest(docsRepoDir, { repoSlug, sourceRepo, githubUsername, docs, publishConfig, scope });
550
632
 
551
633
  const { stdout: status } = await execFile('git', ['status', '--porcelain'], {
552
634
  cwd: docsRepoDir,
@@ -560,7 +642,9 @@ async function publishProjectAwDocs(cwd, home, dryRun) {
560
642
  }
561
643
 
562
644
  await commitAndPushAwDocsRepo(docsRepoDir, {
563
- message: `docs(aw): sync ${repoSlug}/${githubUsername} AW docs`,
645
+ message: scope?.relPrefix
646
+ ? `docs(aw): sync ${repoSlug}/${githubUsername} ${scope.relPrefix}`
647
+ : `docs(aw): sync ${repoSlug}/${githubUsername} AW docs`,
564
648
  branch: publishConfig.branch,
565
649
  });
566
650
  writeAwDocsLinkSummary(projectRoot, links, publishConfig);
@@ -1060,14 +1144,13 @@ export async function pushCommand(args) {
1060
1144
  }
1061
1145
 
1062
1146
  if (docsOnly) {
1063
- if (input) {
1064
- fmt.cancel('Docs-only publish does not accept a path. Run: aw push --aw-docs-only');
1065
- return;
1066
- }
1067
1147
  try {
1068
- const result = await publishProjectAwDocs(cwd, HOME, dryRun);
1148
+ const scope = resolveAwDocsScope(input, args['--feature']);
1149
+ const result = await publishProjectAwDocs(cwd, HOME, dryRun, scope);
1069
1150
  if (!result.hasDocs) {
1070
- fmt.cancel('No publishable AW docs found under .aw_docs/features or .aw_docs/html.');
1151
+ fmt.cancel(scope
1152
+ ? `No publishable AW docs found under ${AW_DOCS_DIR}/${scope.relPrefix}.`
1153
+ : 'No publishable AW docs found under .aw_docs/features or .aw_docs/html.');
1071
1154
  return;
1072
1155
  }
1073
1156
  if (dryRun) fmt.outro(chalk.dim('Remove --dry-run to publish AW docs'));
package/constants.mjs CHANGED
@@ -33,7 +33,8 @@ export const AW_DOCS_BASE_BRANCH = 'master-sync';
33
33
  export const AW_DOCS_SEED_BRANCH = process.env.AW_DOCS_SEED_BRANCH || 'scaffold';
34
34
  export const AW_DOCS_PUBLISH_DIR = 'aw_docs';
35
35
  export const AW_DOCS_PUBLIC_BASE_URL = process.env.AW_DOCS_PUBLIC_BASE_URL || `https://github.com/${AW_DOCS_REPO}/blob/${AW_DOCS_BASE_BRANCH}`;
36
- export const AW_DOCS_TEAMOFONE_BASE_URL = process.env.AW_DOCS_TEAMOFONE_BASE_URL || `https://teamofone.msgsndr.net/too/docs/${AW_DOCS_REPO}`;
36
+ export const AW_DOCS_TEAMOFONE_ORIGIN = process.env.AW_DOCS_TEAMOFONE_ORIGIN || 'https://teamofone.msgsndr.net';
37
+ export const AW_DOCS_TEAMOFONE_BASE_URL = process.env.AW_DOCS_TEAMOFONE_BASE_URL || `${AW_DOCS_TEAMOFONE_ORIGIN}/too/docs/GoHighLevel/ghl-aw-docs`;
37
38
 
38
39
  export function defaultAwDocsGithubDocsConfig() {
39
40
  return {
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.57";
15
+ export const AW_ECC_TAG = "v1.4.55";
16
16
 
17
17
  const MARKETPLACE_NAME = "aw-marketplace";
18
18
  const PLUGIN_KEY = `aw@${MARKETPLACE_NAME}`;
package/integrations.mjs CHANGED
@@ -45,6 +45,8 @@ export const INTEGRATIONS = {
45
45
  type: 'universal-installer',
46
46
  label: 'LeanCTX',
47
47
  description: 'Context OS for AI — compresses file reads + shell output + memory (60-99% fewer input tokens)',
48
+ autoInstall: false, // opt-in only — install via: aw integrations add lean-ctx
49
+ uninstallCmd: 'lean-ctx uninstall', // runs on: aw integrations remove lean-ctx
48
50
  scripts: {
49
51
  win32: null, // no PS1 — uses npm fallback (lean-ctx-bin)
50
52
  posix: 'https://leanctx.com/install.sh',
@@ -773,12 +775,25 @@ export async function removeIntegration(key, { silent = false } = {}) {
773
775
  );
774
776
  }
775
777
  } else if (integration.type === 'universal-installer') {
776
- if (!silent) {
777
- fmt.logWarn(
778
- `Removed ${integration.label} from the aw manifest.\n` +
779
- ` To fully uninstall: follow the uninstall instructions for ${integration.label} in its documentation.`,
780
- 'Manual Cleanup',
781
- );
778
+ if (integration.uninstallCmd) {
779
+ if (!silent) fmt.logStep(`Running ${integration.uninstallCmd}...`);
780
+ try {
781
+ execSync(integration.uninstallCmd, { stdio: silent ? 'ignore' : 'inherit' });
782
+ if (!silent) fmt.logSuccess(`${integration.label} uninstalled`);
783
+ } catch {
784
+ if (!silent) fmt.logWarn(
785
+ `${integration.uninstallCmd} failed or not found. Manual cleanup may be needed.`,
786
+ 'Uninstall Warning',
787
+ );
788
+ }
789
+ } else {
790
+ if (!silent) {
791
+ fmt.logWarn(
792
+ `Removed ${integration.label} from the aw manifest.\n` +
793
+ ` To fully uninstall: follow the uninstall instructions for ${integration.label} in its documentation.`,
794
+ 'Manual Cleanup',
795
+ );
796
+ }
782
797
  }
783
798
  }
784
799
 
@@ -816,6 +831,8 @@ export function suggestForTeam(namespace) {
816
831
 
817
832
  return Object.entries(INTEGRATIONS)
818
833
  .filter(([, integration]) => {
834
+ // Skip opt-in only integrations (autoInstall: false)
835
+ if (integration.autoInstall === false) return false;
819
836
  // Show if: team is in the integration's teams list OR teams list is empty (universal)
820
837
  return (
821
838
  integration.teams.length === 0 || integration.teams.includes(team)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.48-beta.0",
3
+ "version": "0.1.48-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": {