@ghl-ai/aw 0.1.25-beta.1 → 0.1.25-beta.10
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 +3 -2
- package/commands/init.mjs +112 -30
- package/commands/pull.mjs +106 -8
- package/commands/push.mjs +2 -2
- package/git.mjs +30 -2
- package/link.mjs +23 -17
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -71,8 +71,9 @@ function printHelp() {
|
|
|
71
71
|
const sec = (title) => `\n ${chalk.bold.underline(title)}`;
|
|
72
72
|
const help = [
|
|
73
73
|
sec('Setup'),
|
|
74
|
-
cmd('aw init --namespace <team>', 'Initialize workspace (required)'),
|
|
74
|
+
cmd('aw init --namespace <team/sub-team>', 'Initialize workspace (required)'),
|
|
75
75
|
` ${chalk.dim('Teams: revex, mobile, commerce, leadgen, crm, marketplace, ai')}`,
|
|
76
|
+
` ${chalk.dim('Example: aw init --namespace revex/courses')}`,
|
|
76
77
|
|
|
77
78
|
sec('Download'),
|
|
78
79
|
cmd('aw pull', 'Re-pull all synced paths (like git pull)'),
|
|
@@ -99,7 +100,7 @@ function printHelp() {
|
|
|
99
100
|
sec('Examples'),
|
|
100
101
|
'',
|
|
101
102
|
` ${chalk.dim('# Pull content from registry using path')}`,
|
|
102
|
-
cmd('aw pull
|
|
103
|
+
cmd('aw pull platform', 'All shared platform agents & skills'),
|
|
103
104
|
cmd('aw pull <team>', 'Everything from a team namespace'),
|
|
104
105
|
cmd('aw pull <team>/agents', 'All agents from a team'),
|
|
105
106
|
cmd('aw pull <team>/agents/<name>', 'One specific agent'),
|
package/commands/init.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import { readFileSync } from 'node:fs';
|
|
|
13
13
|
import * as config from '../config.mjs';
|
|
14
14
|
import * as fmt from '../fmt.mjs';
|
|
15
15
|
import { chalk } from '../fmt.mjs';
|
|
16
|
-
import { pullCommand } from './pull.mjs';
|
|
16
|
+
import { pullCommand, pullAsync } from './pull.mjs';
|
|
17
17
|
import { linkWorkspace } from '../link.mjs';
|
|
18
18
|
import { generateCommands, copyInstructions, initAwDocs } from '../integrate.mjs';
|
|
19
19
|
import { setupMcp } from '../mcp.mjs';
|
|
@@ -112,6 +112,24 @@ function saveManifest(data) {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
|
|
115
|
+
function printPullSummary(pattern, actions) {
|
|
116
|
+
for (const type of ['agents', 'skills', 'commands', 'evals']) {
|
|
117
|
+
const typeActions = actions.filter(a => a.type === type);
|
|
118
|
+
if (typeActions.length === 0) continue;
|
|
119
|
+
|
|
120
|
+
const counts = { ADD: 0, UPDATE: 0, CONFLICT: 0 };
|
|
121
|
+
for (const a of typeActions) counts[a.action] = (counts[a.action] || 0) + 1;
|
|
122
|
+
|
|
123
|
+
const parts = [];
|
|
124
|
+
if (counts.ADD > 0) parts.push(chalk.green(`${counts.ADD} new`));
|
|
125
|
+
if (counts.UPDATE > 0) parts.push(chalk.cyan(`${counts.UPDATE} updated`));
|
|
126
|
+
if (counts.CONFLICT > 0) parts.push(chalk.red(`${counts.CONFLICT} conflict`));
|
|
127
|
+
const detail = parts.length > 0 ? ` (${parts.join(', ')})` : '';
|
|
128
|
+
|
|
129
|
+
fmt.logSuccess(`${chalk.cyan(pattern)}: ${typeActions.length} ${type}${detail}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
115
133
|
const ALLOWED_NAMESPACES = ['revex', 'mobile', 'commerce', 'leadgen', 'crm', 'marketplace', 'ai'];
|
|
116
134
|
|
|
117
135
|
export async function initCommand(args) {
|
|
@@ -128,45 +146,92 @@ export async function initCommand(args) {
|
|
|
128
146
|
fmt.cancel([
|
|
129
147
|
`Missing required ${chalk.bold('--namespace')} flag`,
|
|
130
148
|
'',
|
|
131
|
-
` ${chalk.dim('Usage:')} aw init --namespace <team>`,
|
|
149
|
+
` ${chalk.dim('Usage:')} aw init --namespace <team/sub-team>`,
|
|
132
150
|
` ${chalk.dim('Teams:')} ${list}`,
|
|
133
151
|
'',
|
|
134
|
-
` ${chalk.dim('Example:')} ${chalk.bold('aw init --namespace commerce')}`,
|
|
152
|
+
` ${chalk.dim('Example:')} ${chalk.bold('aw init --namespace commerce/payments')}`,
|
|
135
153
|
].join('\n'));
|
|
136
154
|
}
|
|
137
155
|
|
|
138
|
-
|
|
156
|
+
// Parse team/sub-team
|
|
157
|
+
const nsParts = namespace ? namespace.split('/') : [];
|
|
158
|
+
const team = nsParts[0] || null;
|
|
159
|
+
const subTeam = nsParts[1] || null;
|
|
160
|
+
const teamNS = subTeam ? `${team}-${subTeam}` : team; // for $TEAM_NS replacement
|
|
161
|
+
const folderName = subTeam ? `${team}/${subTeam}` : team; // for .aw_registry/ path
|
|
162
|
+
|
|
163
|
+
if (team && !ALLOWED_NAMESPACES.includes(team)) {
|
|
139
164
|
const list = ALLOWED_NAMESPACES.map(n => chalk.cyan(n)).join(', ');
|
|
140
165
|
fmt.cancel([
|
|
141
|
-
`Unknown
|
|
166
|
+
`Unknown team ${chalk.red(team)}`,
|
|
142
167
|
'',
|
|
143
168
|
` ${chalk.dim('Allowed:')} ${list}`,
|
|
144
169
|
'',
|
|
145
|
-
` ${chalk.dim('Example:')} ${chalk.bold('aw init --namespace commerce')}`,
|
|
170
|
+
` ${chalk.dim('Example:')} ${chalk.bold('aw init --namespace commerce/payments')}`,
|
|
146
171
|
].join('\n'));
|
|
147
172
|
}
|
|
148
173
|
|
|
149
|
-
|
|
150
|
-
|
|
174
|
+
const SLUG_RE = /^[a-z][a-z0-9-]{1,38}[a-z0-9]$/;
|
|
175
|
+
if (team && !SLUG_RE.test(team)) {
|
|
176
|
+
fmt.cancel(`Invalid team '${team}' — must match: ${SLUG_RE}`);
|
|
177
|
+
}
|
|
178
|
+
if (subTeam && !SLUG_RE.test(subTeam)) {
|
|
179
|
+
fmt.cancel(`Invalid sub-team '${subTeam}' — must match: ${SLUG_RE}`);
|
|
151
180
|
}
|
|
152
181
|
|
|
153
|
-
const
|
|
182
|
+
const hasConfig = config.exists(GLOBAL_AW_DIR);
|
|
183
|
+
const hasPlatform = existsSync(join(GLOBAL_AW_DIR, 'platform'));
|
|
184
|
+
const isExisting = hasConfig && hasPlatform;
|
|
154
185
|
const cwd = process.cwd();
|
|
155
186
|
|
|
156
187
|
// ── Fast path: already initialized → just pull + link ─────────────────
|
|
157
188
|
|
|
158
189
|
if (isExisting) {
|
|
159
|
-
|
|
190
|
+
const cfg = config.load(GLOBAL_AW_DIR);
|
|
191
|
+
|
|
192
|
+
// Add new sub-team if not already tracked
|
|
193
|
+
const isNewSubTeam = folderName && cfg && !cfg.include.includes(folderName);
|
|
194
|
+
if (isNewSubTeam) {
|
|
195
|
+
if (!silent) fmt.logStep(`Adding sub-team ${chalk.cyan(folderName)}...`);
|
|
196
|
+
await pullAsync({
|
|
197
|
+
...args,
|
|
198
|
+
_positional: ['[template]'],
|
|
199
|
+
_renameNamespace: folderName,
|
|
200
|
+
_teamNS: teamNS,
|
|
201
|
+
_workspaceDir: GLOBAL_AW_DIR,
|
|
202
|
+
_skipIntegrate: true,
|
|
203
|
+
});
|
|
204
|
+
config.addPattern(GLOBAL_AW_DIR, folderName);
|
|
205
|
+
} else {
|
|
206
|
+
if (!silent) fmt.logStep('Already initialized — syncing...');
|
|
207
|
+
}
|
|
160
208
|
|
|
161
|
-
// Pull latest
|
|
162
|
-
|
|
209
|
+
// Pull latest (parallel)
|
|
210
|
+
// cfg.include has the renamed namespace (e.g. 'revex/courses'), but the repo
|
|
211
|
+
// only has 'registry/[template]/' — remap non-platform entries back.
|
|
212
|
+
const freshCfg = config.load(GLOBAL_AW_DIR);
|
|
213
|
+
if (freshCfg && freshCfg.include.length > 0) {
|
|
214
|
+
const pullJobs = freshCfg.include.map(p => {
|
|
215
|
+
const isTeamNs = p !== 'platform';
|
|
216
|
+
const derivedTeamNS = isTeamNs ? p.replace(/\//g, '-') : undefined;
|
|
217
|
+
return pullAsync({
|
|
218
|
+
...args,
|
|
219
|
+
_positional: [isTeamNs ? '[template]' : p],
|
|
220
|
+
_workspaceDir: GLOBAL_AW_DIR,
|
|
221
|
+
_skipIntegrate: true,
|
|
222
|
+
_renameNamespace: isTeamNs ? p : undefined,
|
|
223
|
+
_teamNS: derivedTeamNS,
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
await Promise.all(pullJobs);
|
|
227
|
+
}
|
|
163
228
|
|
|
164
229
|
// Re-link IDE dirs (idempotent)
|
|
165
230
|
linkWorkspace(HOME);
|
|
166
231
|
generateCommands(HOME);
|
|
167
|
-
copyInstructions(HOME, null, namespace) || [];
|
|
232
|
+
copyInstructions(HOME, null, freshCfg?.namespace || team) || [];
|
|
168
233
|
initAwDocs(HOME);
|
|
169
|
-
setupMcp(HOME, namespace) || [];
|
|
234
|
+
setupMcp(HOME, freshCfg?.namespace || team) || [];
|
|
170
235
|
|
|
171
236
|
// Link current project if needed
|
|
172
237
|
if (cwd !== HOME && !existsSync(join(cwd, '.aw_registry'))) {
|
|
@@ -178,7 +243,7 @@ export async function initCommand(args) {
|
|
|
178
243
|
|
|
179
244
|
if (!silent) {
|
|
180
245
|
fmt.outro([
|
|
181
|
-
'Sync complete',
|
|
246
|
+
isNewSubTeam ? `Sub-team ${chalk.cyan(folderName)} added` : 'Sync complete',
|
|
182
247
|
'',
|
|
183
248
|
` ${chalk.green('✓')} Registry updated`,
|
|
184
249
|
` ${chalk.green('✓')} IDE integration refreshed`,
|
|
@@ -200,36 +265,53 @@ export async function initCommand(args) {
|
|
|
200
265
|
// Step 1: Create global source of truth
|
|
201
266
|
mkdirSync(GLOBAL_AW_DIR, { recursive: true });
|
|
202
267
|
|
|
203
|
-
const cfg = config.create(GLOBAL_AW_DIR, { namespace, user });
|
|
268
|
+
const cfg = config.create(GLOBAL_AW_DIR, { namespace: team, user });
|
|
204
269
|
|
|
205
270
|
fmt.note([
|
|
206
271
|
`${chalk.dim('source:')} ~/.aw_registry/`,
|
|
207
|
-
|
|
272
|
+
folderName ? `${chalk.dim('namespace:')} ${folderName}` : `${chalk.dim('namespace:')} ${chalk.dim('none')}`,
|
|
208
273
|
user ? `${chalk.dim('user:')} ${cfg.user}` : null,
|
|
209
274
|
`${chalk.dim('version:')} v${VERSION}`,
|
|
210
275
|
].filter(Boolean).join('\n'), 'Config created');
|
|
211
276
|
|
|
212
|
-
// Step 2: Pull registry content
|
|
213
|
-
|
|
277
|
+
// Step 2: Pull registry content (parallel)
|
|
278
|
+
const s = fmt.spinner();
|
|
279
|
+
const pullTargets = folderName ? `platform + ${folderName}` : 'platform';
|
|
280
|
+
s.start(`Pulling ${pullTargets}...`);
|
|
281
|
+
|
|
282
|
+
const pullJobs = [
|
|
283
|
+
pullAsync({ ...args, _positional: ['platform'], _workspaceDir: GLOBAL_AW_DIR, _skipIntegrate: true }),
|
|
284
|
+
];
|
|
285
|
+
if (folderName) {
|
|
286
|
+
pullJobs.push(
|
|
287
|
+
pullAsync({ ...args, _positional: ['[template]'], _renameNamespace: folderName, _teamNS: teamNS, _workspaceDir: GLOBAL_AW_DIR, _skipIntegrate: true }),
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
let pullResults;
|
|
292
|
+
try {
|
|
293
|
+
pullResults = await Promise.all(pullJobs);
|
|
294
|
+
s.stop(`Pulled ${pullTargets}`);
|
|
295
|
+
} catch (e) {
|
|
296
|
+
s.stop(chalk.red('Pull failed'));
|
|
297
|
+
fmt.cancel(e.message);
|
|
298
|
+
}
|
|
214
299
|
|
|
215
|
-
|
|
216
|
-
|
|
300
|
+
for (const { pattern, actions } of pullResults) {
|
|
301
|
+
printPullSummary(pattern, actions);
|
|
217
302
|
}
|
|
218
303
|
|
|
219
|
-
// Step 3: Link
|
|
304
|
+
// Step 3: Link IDE dirs + setup tasks
|
|
305
|
+
fmt.logStep('Linking IDE symlinks...');
|
|
220
306
|
linkWorkspace(HOME);
|
|
221
307
|
generateCommands(HOME);
|
|
222
|
-
const instructionFiles = copyInstructions(HOME, null,
|
|
308
|
+
const instructionFiles = copyInstructions(HOME, null, team) || [];
|
|
223
309
|
initAwDocs(HOME);
|
|
224
|
-
const mcpFiles = setupMcp(HOME,
|
|
225
|
-
|
|
226
|
-
// Step 4: Git template hook (omnipresence)
|
|
310
|
+
const mcpFiles = setupMcp(HOME, team) || [];
|
|
227
311
|
const gitTemplateInstalled = installGitTemplate();
|
|
228
|
-
|
|
229
|
-
// Step 5: IDE auto-init tasks
|
|
230
312
|
installIdeTasks();
|
|
231
313
|
|
|
232
|
-
// Step
|
|
314
|
+
// Step 4: Symlink in current directory if it's a git repo
|
|
233
315
|
if (cwd !== HOME && !existsSync(join(cwd, '.aw_registry'))) {
|
|
234
316
|
try {
|
|
235
317
|
symlinkSync(GLOBAL_AW_DIR, join(cwd, '.aw_registry'));
|
|
@@ -237,7 +319,7 @@ export async function initCommand(args) {
|
|
|
237
319
|
} catch { /* best effort */ }
|
|
238
320
|
}
|
|
239
321
|
|
|
240
|
-
// Step
|
|
322
|
+
// Step 5: Write manifest for nuke cleanup
|
|
241
323
|
const manifest = {
|
|
242
324
|
version: 1,
|
|
243
325
|
installedAt: new Date().toISOString(),
|
package/commands/pull.mjs
CHANGED
|
@@ -7,7 +7,7 @@ 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
|
-
import { sparseCheckout, cleanup, includeToSparsePaths } from '../git.mjs';
|
|
10
|
+
import { sparseCheckout, sparseCheckoutAsync, cleanup, includeToSparsePaths } from '../git.mjs';
|
|
11
11
|
import { walkRegistryTree } from '../registry.mjs';
|
|
12
12
|
import { matchesAny } from '../glob.mjs';
|
|
13
13
|
import { computePlan } from '../plan.mjs';
|
|
@@ -17,7 +17,15 @@ import { resolveInput } from '../paths.mjs';
|
|
|
17
17
|
import { linkWorkspace } from '../link.mjs';
|
|
18
18
|
import { generateCommands, copyInstructions } from '../integrate.mjs';
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
// Platform-level types to skip (CLI meta-docs, not useful as agent content)
|
|
21
|
+
const SKIP_PLATFORM_TYPES = new Set(['commands']);
|
|
22
|
+
|
|
23
|
+
function filterActions(actions, pattern) {
|
|
24
|
+
if (pattern !== 'platform') return actions;
|
|
25
|
+
return actions.filter(a => !SKIP_PLATFORM_TYPES.has(a.type));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function pullCommand(args) {
|
|
21
29
|
const input = args._positional?.[0] || '';
|
|
22
30
|
const cwd = process.cwd();
|
|
23
31
|
const GLOBAL_AW_DIR = join(homedir(), '.aw_registry');
|
|
@@ -27,6 +35,7 @@ export function pullCommand(args) {
|
|
|
27
35
|
const verbose = args['-v'] === true || args['--verbose'] === true;
|
|
28
36
|
const silent = args['--silent'] === true || args._silent === true;
|
|
29
37
|
const renameNamespace = args._renameNamespace || null;
|
|
38
|
+
const teamNSOverride = args._teamNS || null;
|
|
30
39
|
|
|
31
40
|
// Silent mode: wrap fmt to suppress all output and exit cleanly on errors
|
|
32
41
|
const log = {
|
|
@@ -41,7 +50,7 @@ export function pullCommand(args) {
|
|
|
41
50
|
spinner: silent ? () => ({ start: () => {}, stop: () => {} }) : fmt.spinner,
|
|
42
51
|
};
|
|
43
52
|
|
|
44
|
-
// No args = re-pull everything in sync config
|
|
53
|
+
// No args = re-pull everything in sync config (parallel)
|
|
45
54
|
if (!input) {
|
|
46
55
|
const cfg = config.load(workspaceDir);
|
|
47
56
|
if (!cfg) log.cancel('No .sync-config.json found. Run: aw init');
|
|
@@ -49,9 +58,19 @@ export function pullCommand(args) {
|
|
|
49
58
|
log.cancel('Nothing to pull. Add paths first:\n\n aw pull <path>');
|
|
50
59
|
}
|
|
51
60
|
log.logInfo(`Pulling ${chalk.cyan(cfg.include.length)} synced path${cfg.include.length > 1 ? 's' : ''}...`);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
const pullJobs = cfg.include.map(p => {
|
|
62
|
+
const isTeamNs = p !== 'platform';
|
|
63
|
+
const derivedTeamNS = isTeamNs ? p.replace(/\//g, '-') : undefined;
|
|
64
|
+
return pullAsync({
|
|
65
|
+
...args,
|
|
66
|
+
_positional: [isTeamNs ? '[template]' : p],
|
|
67
|
+
_workspaceDir: workspaceDir,
|
|
68
|
+
_skipIntegrate: true,
|
|
69
|
+
_renameNamespace: isTeamNs ? p : undefined,
|
|
70
|
+
_teamNS: derivedTeamNS,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
await Promise.all(pullJobs);
|
|
55
74
|
// Post-pull IDE integration (once after all paths pulled)
|
|
56
75
|
linkWorkspace(cwd);
|
|
57
76
|
generateCommands(cwd);
|
|
@@ -145,9 +164,10 @@ export function pullCommand(args) {
|
|
|
145
164
|
}
|
|
146
165
|
|
|
147
166
|
// Compute plan
|
|
148
|
-
const { actions } = computePlan(registryDirs, workspaceDir, [pattern], {
|
|
167
|
+
const { actions: rawActions } = computePlan(registryDirs, workspaceDir, [pattern], {
|
|
149
168
|
skipOrphans: true,
|
|
150
169
|
});
|
|
170
|
+
const actions = filterActions(rawActions, pattern);
|
|
151
171
|
|
|
152
172
|
if (dryRun) {
|
|
153
173
|
printDryRun(actions, verbose);
|
|
@@ -157,7 +177,7 @@ export function pullCommand(args) {
|
|
|
157
177
|
// Apply
|
|
158
178
|
const s2 = log.spinner();
|
|
159
179
|
s2.start('Applying changes...');
|
|
160
|
-
const conflictCount = applyActions(actions, { teamNS: renameNamespace || undefined });
|
|
180
|
+
const conflictCount = applyActions(actions, { teamNS: teamNSOverride || renameNamespace || undefined });
|
|
161
181
|
updateManifest(workspaceDir, actions, cfg.namespace);
|
|
162
182
|
s2.stop('Changes applied');
|
|
163
183
|
|
|
@@ -191,6 +211,84 @@ export function pullCommand(args) {
|
|
|
191
211
|
}
|
|
192
212
|
}
|
|
193
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Async pull for parallel use by init. Runs git fetch asynchronously,
|
|
216
|
+
* then applies changes synchronously. Returns a summary object instead
|
|
217
|
+
* of printing directly — the caller controls output.
|
|
218
|
+
*/
|
|
219
|
+
export async function pullAsync(args) {
|
|
220
|
+
const input = args._positional?.[0] || '';
|
|
221
|
+
const workspaceDir = args._workspaceDir;
|
|
222
|
+
const renameNamespace = args._renameNamespace || null;
|
|
223
|
+
const teamNSOverride = args._teamNS || null;
|
|
224
|
+
|
|
225
|
+
const resolved = resolveInput(input, workspaceDir);
|
|
226
|
+
let pattern = resolved.registryPath;
|
|
227
|
+
if (!pattern) throw new Error(`Could not resolve "${input}" to a registry path`);
|
|
228
|
+
|
|
229
|
+
if (!existsSync(workspaceDir)) mkdirSync(workspaceDir, { recursive: true });
|
|
230
|
+
|
|
231
|
+
const cfg = config.load(workspaceDir);
|
|
232
|
+
if (!cfg) throw new Error('No .sync-config.json found');
|
|
233
|
+
|
|
234
|
+
const sparsePaths = includeToSparsePaths([pattern]);
|
|
235
|
+
const tempDir = await sparseCheckoutAsync(cfg.repo, sparsePaths);
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const registryDirs = [];
|
|
239
|
+
const regBase = join(tempDir, 'registry');
|
|
240
|
+
|
|
241
|
+
if (existsSync(regBase)) {
|
|
242
|
+
for (const name of listDirs(regBase)) {
|
|
243
|
+
registryDirs.push({ name, path: join(regBase, name) });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (registryDirs.length === 0) {
|
|
248
|
+
throw new Error(`Nothing found in registry for ${pattern}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (renameNamespace) {
|
|
252
|
+
for (const rd of registryDirs) {
|
|
253
|
+
if (rd.name === pattern) rd.name = renameNamespace;
|
|
254
|
+
}
|
|
255
|
+
pattern = renameNamespace;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let hasMatch = false;
|
|
259
|
+
for (const { name, path } of registryDirs) {
|
|
260
|
+
const entries = walkRegistryTree(path, name);
|
|
261
|
+
if (entries.some(e => matchesAny(e.registryPath, [pattern]))) {
|
|
262
|
+
hasMatch = true;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!hasMatch) {
|
|
268
|
+
throw new Error(`Nothing found in registry for ${pattern}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!cfg.include.includes(pattern)) {
|
|
272
|
+
config.addPattern(workspaceDir, pattern);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const { actions: rawActions } = computePlan(registryDirs, workspaceDir, [pattern], { skipOrphans: true });
|
|
276
|
+
const actions = filterActions(rawActions, pattern);
|
|
277
|
+
const conflictCount = applyActions(actions, { teamNS: teamNSOverride || renameNamespace || undefined });
|
|
278
|
+
updateManifest(workspaceDir, actions, cfg.namespace);
|
|
279
|
+
|
|
280
|
+
const ROOT_REGISTRY_FILES = ['AW-PROTOCOL.md'];
|
|
281
|
+
for (const fname of ROOT_REGISTRY_FILES) {
|
|
282
|
+
const src = join(regBase, fname);
|
|
283
|
+
if (existsSync(src)) copyFileSync(src, join(workspaceDir, fname));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return { pattern, actions, conflictCount };
|
|
287
|
+
} finally {
|
|
288
|
+
cleanup(tempDir);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
194
292
|
function listDirs(dir) {
|
|
195
293
|
return readdirSync(dir, { withFileTypes: true })
|
|
196
294
|
.filter(d => d.isDirectory() && !d.name.startsWith('.'))
|
package/commands/push.mjs
CHANGED
|
@@ -92,8 +92,8 @@ export function pushCommand(args) {
|
|
|
92
92
|
const namespacePath = namespaceParts.join('/');
|
|
93
93
|
const topNamespace = namespaceParts[0];
|
|
94
94
|
|
|
95
|
-
if (topNamespace === '
|
|
96
|
-
fmt.cancel("Cannot push to '
|
|
95
|
+
if (topNamespace === 'platform') {
|
|
96
|
+
fmt.cancel("Cannot push to 'platform' namespace — it is the shared platform layer");
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
const isDir = statSync(absPath).isDirectory();
|
package/git.mjs
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
// git.mjs — Git sparse checkout helpers. Zero dependencies.
|
|
2
2
|
|
|
3
|
-
import { execSync } from 'node:child_process';
|
|
3
|
+
import { execSync, exec as execCb } from 'node:child_process';
|
|
4
4
|
import { mkdtempSync, existsSync } from 'node:fs';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
7
8
|
import { REGISTRY_BASE_BRANCH } from './constants.mjs';
|
|
8
9
|
|
|
10
|
+
const exec = promisify(execCb);
|
|
11
|
+
|
|
9
12
|
/**
|
|
10
|
-
* Sparse-checkout registry paths from GitHub.
|
|
13
|
+
* Sparse-checkout registry paths from GitHub (sync).
|
|
11
14
|
* Returns the temp directory path containing the checkout.
|
|
12
15
|
*/
|
|
13
16
|
export function sparseCheckout(repo, paths) {
|
|
@@ -35,6 +38,31 @@ export function sparseCheckout(repo, paths) {
|
|
|
35
38
|
return tempDir;
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Sparse-checkout registry paths from GitHub (async).
|
|
43
|
+
* Same as sparseCheckout but non-blocking — can run in parallel.
|
|
44
|
+
*/
|
|
45
|
+
export async function sparseCheckoutAsync(repo, paths) {
|
|
46
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'aw-'));
|
|
47
|
+
|
|
48
|
+
const repoUrl = repo.startsWith('http') ? repo : `https://github.com/${repo}.git`;
|
|
49
|
+
try {
|
|
50
|
+
await exec(`git clone --filter=blob:none --no-checkout "${repoUrl}" "${tempDir}"`);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
throw new Error(`Failed to clone ${repo}. Check your git credentials and repo access.`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
await exec('git sparse-checkout init --cone', { cwd: tempDir });
|
|
57
|
+
await exec(`git sparse-checkout set --skip-checks ${paths.map(p => `"${p}"`).join(' ')}`, { cwd: tempDir });
|
|
58
|
+
await exec(`git checkout ${REGISTRY_BASE_BRANCH}`, { cwd: tempDir });
|
|
59
|
+
} catch (e) {
|
|
60
|
+
throw new Error(`Failed sparse checkout: ${e.message}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return tempDir;
|
|
64
|
+
}
|
|
65
|
+
|
|
38
66
|
/**
|
|
39
67
|
* Clean up temp directory.
|
|
40
68
|
*/
|
package/link.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { existsSync, lstatSync, mkdirSync, readdirSync, unlinkSync } from 'node:fs';
|
|
4
4
|
import { join, relative } from 'node:path';
|
|
5
5
|
import { execSync } from 'node:child_process';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
6
7
|
import * as fmt from './fmt.mjs';
|
|
7
8
|
|
|
8
9
|
const IDE_DIRS = ['.claude', '.cursor', '.codex'];
|
|
@@ -66,9 +67,12 @@ function cleanIdeSymlinks(cwd) {
|
|
|
66
67
|
if (!existsSync(ideDir)) continue;
|
|
67
68
|
cleanSymlinksRecursive(ideDir);
|
|
68
69
|
}
|
|
69
|
-
// Also clean .agents/skills/
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
70
|
+
// Also clean .agents/skills/ (global only — Codex reads from ~/.agents/skills/)
|
|
71
|
+
const HOME = homedir();
|
|
72
|
+
if (cwd === HOME) {
|
|
73
|
+
const agentsSkillsDir = join(cwd, '.agents', 'skills');
|
|
74
|
+
if (existsSync(agentsSkillsDir)) cleanSymlinksRecursive(agentsSkillsDir);
|
|
75
|
+
}
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
/**
|
|
@@ -184,20 +188,22 @@ export function linkWorkspace(cwd) {
|
|
|
184
188
|
}
|
|
185
189
|
}
|
|
186
190
|
|
|
187
|
-
// Codex per-skill symlinks:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
for (const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
191
|
+
// Codex per-skill symlinks: ~/.agents/skills/<name> (global only)
|
|
192
|
+
if (cwd === homedir()) {
|
|
193
|
+
const agentsSkillsDir = join(cwd, '.agents/skills');
|
|
194
|
+
for (const ns of namespaces) {
|
|
195
|
+
for (const { typeDirPath: skillsDir, segments } of findNestedTypeDirs(join(awDir, ns), 'skills')) {
|
|
196
|
+
mkdirSync(agentsSkillsDir, { recursive: true });
|
|
197
|
+
for (const skill of listDirs(skillsDir)) {
|
|
198
|
+
const flat = [ns, ...segments, skill].join('-');
|
|
199
|
+
const linkPath = join(agentsSkillsDir, flat);
|
|
200
|
+
const targetPath = join(skillsDir, skill);
|
|
201
|
+
const relTarget = relative(agentsSkillsDir, targetPath);
|
|
202
|
+
try {
|
|
203
|
+
execSync(`ln -sfn "${relTarget}" "${linkPath}"`, { stdio: 'pipe' });
|
|
204
|
+
created++;
|
|
205
|
+
} catch { /* best effort */ }
|
|
206
|
+
}
|
|
201
207
|
}
|
|
202
208
|
}
|
|
203
209
|
}
|