@ghl-ai/aw 0.1.48-beta.1 → 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
@@ -144,13 +144,71 @@ function collectFiles(root, base = root) {
144
144
  return files;
145
145
  }
146
146
 
147
- 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) {
148
191
  const projectRoot = getProjectRoot(cwd, home);
149
192
  const source = join(projectRoot, AW_DOCS_DIR);
150
193
 
151
194
  if (!existsSync(source)) return { projectRoot, files: [] };
152
195
 
153
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
+
154
212
  for (const root of PROJECT_AW_DOCS_AUTO_ROOTS) {
155
213
  const sourceRoot = join(source, root);
156
214
  if (!existsSync(sourceRoot)) continue;
@@ -480,7 +538,7 @@ function titleForAwDoc(relPath) {
480
538
  return `${label} ${ext}`;
481
539
  }
482
540
 
483
- function updateAwDocsManifest(docsRepoDir, { repoSlug, sourceRepo, githubUsername, docs, publishConfig }) {
541
+ function updateAwDocsManifest(docsRepoDir, { repoSlug, sourceRepo, githubUsername, docs, publishConfig, scope = null }) {
484
542
  const manifestPath = join(docsRepoDir, 'manifest.json');
485
543
  const manifest = existsSync(manifestPath)
486
544
  ? JSON.parse(readFileSync(manifestPath, 'utf8'))
@@ -494,25 +552,32 @@ function updateAwDocsManifest(docsRepoDir, { repoSlug, sourceRepo, githubUsernam
494
552
  repoEntry.slug = repoSlug;
495
553
  repoEntry.sourceRepo = sourceRepo;
496
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
+
497
569
  repoEntry.users[githubUsername] = {
570
+ ...existingUser,
498
571
  githubUsername,
499
- docs: docs.map(doc => ({
500
- relPath: doc.relPath,
501
- publishedPath: doc.publishedPath,
502
- remoteUrl: awDocsRemoteUrl(doc.publishedPath, publishConfig),
503
- repositoryUrl: awDocsRepositoryUrl(doc.publishedPath, publishConfig),
504
- sha: createHash('sha256').update(readFileSync(join(docsRepoDir, doc.publishedPath))).digest('hex'),
505
- syncedAt: now,
506
- title: titleForAwDoc(doc.relPath),
507
- })),
572
+ docs: scope?.relPrefix ? [...preservedDocs, ...nextDocs] : nextDocs,
508
573
  };
509
574
  manifest.awDocs.repos[repoSlug] = repoEntry;
510
575
 
511
576
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
512
577
  }
513
578
 
514
- async function publishProjectAwDocs(cwd, home, dryRun) {
515
- const { projectRoot, files } = collectProjectAwDocs(cwd, home);
579
+ async function publishProjectAwDocs(cwd, home, dryRun, scope = null) {
580
+ const { projectRoot, files } = collectProjectAwDocs(cwd, home, scope);
516
581
  if (files.length === 0) return { hasDocs: false, publishedPaths: [] };
517
582
 
518
583
  const publishConfig = resolveAwDocsPublishConfig(projectRoot);
@@ -551,7 +616,10 @@ async function publishProjectAwDocs(cwd, home, dryRun) {
551
616
  s.start(`Publishing ${files.length} AW doc${files.length > 1 ? 's' : ''} to ${publishConfig.repo}...`);
552
617
  try {
553
618
  const docsRepoDir = await ensureAwDocsRepoClone(home, publishConfig);
554
- 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, {
555
623
  recursive: true,
556
624
  force: true,
557
625
  });
@@ -560,7 +628,7 @@ async function publishProjectAwDocs(cwd, home, dryRun) {
560
628
  mkdirSync(dirname(dest), { recursive: true });
561
629
  copyFileSync(doc.absPath, dest);
562
630
  }
563
- updateAwDocsManifest(docsRepoDir, { repoSlug, sourceRepo, githubUsername, docs, publishConfig });
631
+ updateAwDocsManifest(docsRepoDir, { repoSlug, sourceRepo, githubUsername, docs, publishConfig, scope });
564
632
 
565
633
  const { stdout: status } = await execFile('git', ['status', '--porcelain'], {
566
634
  cwd: docsRepoDir,
@@ -574,7 +642,9 @@ async function publishProjectAwDocs(cwd, home, dryRun) {
574
642
  }
575
643
 
576
644
  await commitAndPushAwDocsRepo(docsRepoDir, {
577
- 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`,
578
648
  branch: publishConfig.branch,
579
649
  });
580
650
  writeAwDocsLinkSummary(projectRoot, links, publishConfig);
@@ -1074,14 +1144,13 @@ export async function pushCommand(args) {
1074
1144
  }
1075
1145
 
1076
1146
  if (docsOnly) {
1077
- if (input) {
1078
- fmt.cancel('Docs-only publish does not accept a path. Run: aw push --aw-docs-only');
1079
- return;
1080
- }
1081
1147
  try {
1082
- const result = await publishProjectAwDocs(cwd, HOME, dryRun);
1148
+ const scope = resolveAwDocsScope(input, args['--feature']);
1149
+ const result = await publishProjectAwDocs(cwd, HOME, dryRun, scope);
1083
1150
  if (!result.hasDocs) {
1084
- 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.');
1085
1154
  return;
1086
1155
  }
1087
1156
  if (dryRun) fmt.outro(chalk.dim('Remove --dry-run to publish AW docs'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.48-beta.1",
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": {