@ghl-ai/aw 0.1.35-beta.23 → 0.1.35-beta.25

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
@@ -72,7 +72,8 @@ function printHelp() {
72
72
  const sec = (title) => `\n ${chalk.bold.underline(title)}`;
73
73
  const help = [
74
74
  sec('Setup'),
75
- cmd('aw init --namespace <team/sub-team>', 'Initialize workspace (required)'),
75
+ cmd('aw init', 'Initialize workspace (platform/ only)'),
76
+ cmd('aw init --namespace <team/sub-team>', 'Add a team namespace (optional)'),
76
77
  ` ${chalk.dim('Teams: platform, revex, mobile, commerce, leadgen, crm, marketplace, ai')}`,
77
78
  ` ${chalk.dim('Example: aw init --namespace revex/courses')}`,
78
79
 
package/commands/init.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
  // Uses core.hooksPath (git-lfs pattern) for system-wide hook interception.
5
5
  // Uses IDE tasks for auto-pull on workspace open.
6
6
 
7
- import { mkdirSync, existsSync, writeFileSync, symlinkSync } from 'node:fs';
7
+ import { mkdirSync, existsSync, writeFileSync, symlinkSync, readdirSync } from 'node:fs';
8
8
  import { execSync } from 'node:child_process';
9
9
  import { join, dirname } from 'node:path';
10
10
  import { homedir } from 'node:os';
@@ -14,6 +14,8 @@ import * as config from '../config.mjs';
14
14
  import * as fmt from '../fmt.mjs';
15
15
  import { chalk } from '../fmt.mjs';
16
16
  import { pullCommand, pullAsync } from './pull.mjs';
17
+ import { sparseCheckoutAsync, includeToSparsePaths, cleanup } from '../git.mjs';
18
+ import { REGISTRY_DIR, REGISTRY_REPO } from '../constants.mjs';
17
19
  import { linkWorkspace } from '../link.mjs';
18
20
  import { generateCommands, copyInstructions, initAwDocs } from '../integrate.mjs';
19
21
  import { setupMcp } from '../mcp.mjs';
@@ -96,7 +98,7 @@ function printPullSummary(pattern, actions) {
96
98
  const ALLOWED_NAMESPACES = ['platform', 'revex', 'mobile', 'commerce', 'leadgen', 'crm', 'marketplace', 'ai'];
97
99
 
98
100
  export async function initCommand(args) {
99
- const namespace = args['--namespace'] || null;
101
+ let namespace = args['--namespace'] || null;
100
102
  let user = args['--user'] || '';
101
103
  const silent = args['--silent'] === true;
102
104
 
@@ -104,24 +106,12 @@ export async function initCommand(args) {
104
106
 
105
107
  // ── Validate ──────────────────────────────────────────────────────────
106
108
 
107
- if (!namespace && !silent) {
108
- const list = ALLOWED_NAMESPACES.map(n => chalk.cyan(n)).join(', ');
109
- fmt.cancel([
110
- `Missing required ${chalk.bold('--namespace')} flag`,
111
- '',
112
- ` ${chalk.dim('Usage:')} aw init --namespace <team/sub-team>`,
113
- ` ${chalk.dim('Teams:')} ${list}`,
114
- '',
115
- ` ${chalk.dim('Example:')} ${chalk.bold('aw init --namespace commerce/payments')}`,
116
- ].join('\n'));
117
- }
118
-
119
109
  // Parse team/sub-team
120
- const nsParts = namespace ? namespace.split('/') : [];
121
- const team = nsParts[0] || null;
122
- const subTeam = nsParts[1] || null;
123
- const teamNS = subTeam ? `${team}-${subTeam}` : team; // for $TEAM_NS replacement
124
- const folderName = subTeam ? `${team}/${subTeam}` : team; // for .aw_registry/ path
110
+ let nsParts = namespace ? namespace.split('/') : [];
111
+ let team = nsParts[0] || null;
112
+ let subTeam = nsParts[1] || null;
113
+ let teamNS = subTeam ? `${team}-${subTeam}` : team; // for $TEAM_NS replacement
114
+ let folderName = subTeam ? `${team}/${subTeam}` : team; // for .aw_registry/ path
125
115
 
126
116
  if (team && !ALLOWED_NAMESPACES.includes(team)) {
127
117
  const list = ALLOWED_NAMESPACES.map(n => chalk.cyan(n)).join(', ');
@@ -152,6 +142,47 @@ export async function initCommand(args) {
152
142
  fmt.cancel(`Invalid sub-team '${subTeam}' — must match: ${SLUG_RE}`);
153
143
  }
154
144
 
145
+ // ── Probe remote registry to check if namespace exists ────────────────
146
+
147
+ let namespaceExistsInRemote = false;
148
+ if (folderName && !silent) {
149
+ try {
150
+ const probePaths = includeToSparsePaths([folderName]);
151
+ const probeDir = await sparseCheckoutAsync(REGISTRY_REPO, probePaths);
152
+ try {
153
+ const fullNsPath = join(probeDir, REGISTRY_DIR, ...folderName.split('/'));
154
+ namespaceExistsInRemote = existsSync(fullNsPath) &&
155
+ readdirSync(fullNsPath, { withFileTypes: true })
156
+ .some(d => d.isDirectory() && !d.name.startsWith('.'));
157
+ } finally {
158
+ cleanup(probeDir);
159
+ }
160
+ } catch {
161
+ // Network error — skip probe, proceed without prompt
162
+ namespaceExistsInRemote = true;
163
+ }
164
+ }
165
+
166
+ // If namespace does NOT exist in remote, ask user to confirm
167
+ if (folderName && !silent && !namespaceExistsInRemote && process.stdin.isTTY) {
168
+ const choice = await fmt.select({
169
+ message: `The namespace '${folderName}' does not exist in the registry yet and will be created from [template].\nplatform/ includes shared agents, skills & commands that cover most use cases.\nHow would you like to proceed?`,
170
+ options: [
171
+ { value: 'platform-only', label: 'Continue with platform/ only (recommended for most users)' },
172
+ { value: 'create-namespace', label: `Create '${folderName}' namespace from template` },
173
+ ],
174
+ });
175
+
176
+ if (fmt.isCancel(choice)) {
177
+ fmt.cancel('Operation cancelled.');
178
+ process.exit(0);
179
+ }
180
+
181
+ if (choice === 'platform-only') {
182
+ namespace = null; team = null; subTeam = null; teamNS = null; folderName = null;
183
+ }
184
+ }
185
+
155
186
  const hasConfig = config.exists(GLOBAL_AW_DIR);
156
187
  const hasPlatform = existsSync(join(GLOBAL_AW_DIR, 'platform'));
157
188
  const isExisting = hasConfig && hasPlatform;
@@ -182,22 +213,26 @@ export async function initCommand(args) {
182
213
  // Pull latest (parallel)
183
214
  // cfg.include has the renamed namespace (e.g. 'revex/courses'), but the repo
184
215
  // only has '.aw_registry/[template]/' — remap non-platform entries back.
216
+ // Platform is never stored in cfg.include but must always be pulled.
185
217
  const freshCfg = config.load(GLOBAL_AW_DIR);
218
+ const pullJobs = [
219
+ pullAsync({ ...args, _positional: ['platform'], _workspaceDir: GLOBAL_AW_DIR, _skipIntegrate: true }),
220
+ ];
186
221
  if (freshCfg && freshCfg.include.length > 0) {
187
- const pullJobs = freshCfg.include.map(p => {
188
- const isTeamNs = p !== 'platform';
189
- const derivedTeamNS = isTeamNs ? p.replace(/\//g, '-') : undefined;
190
- return pullAsync({
222
+ for (const p of freshCfg.include) {
223
+ if (p === 'platform') continue; // already added above
224
+ const derivedTeamNS = p.replace(/\//g, '-');
225
+ pullJobs.push(pullAsync({
191
226
  ...args,
192
- _positional: [isTeamNs ? '[template]' : p],
227
+ _positional: ['[template]'],
193
228
  _workspaceDir: GLOBAL_AW_DIR,
194
229
  _skipIntegrate: true,
195
- _renameNamespace: isTeamNs ? p : undefined,
230
+ _renameNamespace: p,
196
231
  _teamNS: derivedTeamNS,
197
- });
198
- });
199
- await Promise.all(pullJobs);
232
+ }));
233
+ }
200
234
  }
235
+ await Promise.all(pullJobs);
201
236
 
202
237
  // Re-link IDE dirs + hooks (idempotent)
203
238
  linkWorkspace(HOME);
package/config.mjs CHANGED
@@ -48,7 +48,7 @@ export function create(workspaceDir, { namespace, user }) {
48
48
 
49
49
  export function addPattern(workspaceDir, pattern) {
50
50
  const config = load(workspaceDir);
51
- if (!config) throw new Error('No .sync-config.json found. Run: aw --init --namespace <name>');
51
+ if (!config) throw new Error('No .sync-config.json found. Run: aw init');
52
52
  // If a parent path already covers this, skip
53
53
  if (config.include.some(p => pattern === p || pattern.startsWith(p + '/'))) {
54
54
  return config;
@@ -62,7 +62,7 @@ export function addPattern(workspaceDir, pattern) {
62
62
 
63
63
  export function removePattern(workspaceDir, pattern) {
64
64
  const config = load(workspaceDir);
65
- if (!config) throw new Error('No .sync-config.json found. Run: aw --init --namespace <name>');
65
+ if (!config) throw new Error('No .sync-config.json found. Run: aw init');
66
66
  // Remove exact match + all children
67
67
  config.include = config.include.filter(p => p !== pattern && !p.startsWith(pattern + '/'));
68
68
  save(workspaceDir, config);
package/ecc.mjs CHANGED
@@ -9,7 +9,7 @@ import * as fmt from "./fmt.mjs";
9
9
 
10
10
  const AW_ECC_REPO_SSH = "git@github.com:shreyansh-ghl/aw-ecc.git";
11
11
  const AW_ECC_REPO_HTTPS = "https://github.com/shreyansh-ghl/aw-ecc.git";
12
- const AW_ECC_TAG = "v1.2.1";
12
+ const AW_ECC_TAG = "v1.2.2";
13
13
 
14
14
  const MARKETPLACE_NAME = "aw-marketplace";
15
15
  const PLUGIN_KEY = `aw@${MARKETPLACE_NAME}`;
package/fmt.mjs CHANGED
@@ -36,6 +36,8 @@ export function banner(text, opts = {}) {
36
36
  export const intro = (msg) => p.intro(chalk.bgCyan.black(` ${msg} `));
37
37
  export const outro = (msg) => p.outro(chalk.green(msg));
38
38
  export const spinner = () => p.spinner();
39
+ export const select = p.select;
40
+ export const isCancel = p.isCancel;
39
41
 
40
42
  export function cancel(msg) {
41
43
  p.cancel(msg);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.35-beta.23",
3
+ "version": "0.1.35-beta.25",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {