@ghl-ai/aw 0.1.36-beta.1 → 0.1.36-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/commands/drop.mjs +45 -47
- package/commands/init.mjs +81 -122
- package/commands/nuke.mjs +13 -2
- package/commands/pull.mjs +57 -370
- package/commands/push.mjs +246 -337
- package/commands/status.mjs +50 -80
- package/constants.mjs +6 -0
- package/git.mjs +233 -1
- package/package.json +1 -1
- package/apply.mjs +0 -79
- package/manifest.mjs +0 -64
- package/plan.mjs +0 -147
package/commands/drop.mjs
CHANGED
|
@@ -2,87 +2,85 @@
|
|
|
2
2
|
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import { rmSync, existsSync } from 'node:fs';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
5
6
|
import * as config from '../config.mjs';
|
|
6
7
|
import * as fmt from '../fmt.mjs';
|
|
7
8
|
import { chalk } from '../fmt.mjs';
|
|
8
|
-
import { matchesAny } from '../glob.mjs';
|
|
9
9
|
import { resolveInput } from '../paths.mjs';
|
|
10
|
-
import {
|
|
10
|
+
import { removeFromSparseCheckout, isValidClone } from '../git.mjs';
|
|
11
|
+
import { REGISTRY_DIR, REGISTRY_REPO } from '../constants.mjs';
|
|
12
|
+
import { linkWorkspace } from '../link.mjs';
|
|
11
13
|
|
|
12
14
|
export function dropCommand(args) {
|
|
13
15
|
const input = args._positional?.[0];
|
|
14
16
|
const cwd = process.cwd();
|
|
17
|
+
|
|
18
|
+
const HOME = homedir();
|
|
19
|
+
const AW_HOME = join(HOME, '.aw');
|
|
20
|
+
const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
|
|
15
21
|
const workspaceDir = join(cwd, '.aw_registry');
|
|
16
22
|
|
|
17
23
|
fmt.intro('aw drop');
|
|
18
24
|
|
|
19
25
|
if (!input) {
|
|
20
|
-
fmt.cancel('Missing target. Usage:\n aw drop example-team (stop syncing namespace)\n aw drop example-team/skills/example-skill (stop syncing skill)
|
|
26
|
+
fmt.cancel('Missing target. Usage:\n aw drop example-team (stop syncing namespace)\n aw drop example-team/skills/example-skill (stop syncing skill)');
|
|
27
|
+
return;
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
const
|
|
24
|
-
if (!
|
|
30
|
+
const repoUrl = `https://github.com/${REGISTRY_REPO}.git`;
|
|
31
|
+
if (!isValidClone(AW_HOME, repoUrl)) {
|
|
32
|
+
fmt.cancel('Registry not initialized. Run: aw init');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
25
35
|
|
|
26
|
-
|
|
36
|
+
const cfg = config.load(GLOBAL_AW_DIR);
|
|
37
|
+
if (!cfg) {
|
|
38
|
+
fmt.cancel('No .sync-config.json found. Run: aw init');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Resolve to registry path
|
|
27
43
|
const resolved = resolveInput(input, workspaceDir);
|
|
28
44
|
const regPath = resolved.registryPath;
|
|
29
45
|
|
|
30
46
|
if (!regPath) {
|
|
31
47
|
fmt.cancel(`Could not resolve "${input}" to a registry path`);
|
|
48
|
+
return;
|
|
32
49
|
}
|
|
33
50
|
|
|
34
|
-
// Check if this path (or a parent) is in config
|
|
51
|
+
// Check if this path (or a parent) is in config
|
|
35
52
|
const isConfigPath = cfg.include.some(p => p === regPath || p.startsWith(regPath + '/'));
|
|
36
53
|
|
|
37
54
|
if (isConfigPath) {
|
|
38
|
-
|
|
55
|
+
// Remove from sparse checkout
|
|
56
|
+
try {
|
|
57
|
+
removeFromSparseCheckout(AW_HOME, [`${REGISTRY_DIR}/${regPath}`]);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
fmt.logWarn(`Could not update sparse checkout: ${e.message}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
config.removePattern(GLOBAL_AW_DIR, regPath);
|
|
39
63
|
fmt.logSuccess(`Removed ${chalk.cyan(regPath)} from sync config`);
|
|
40
64
|
}
|
|
41
65
|
|
|
42
|
-
//
|
|
43
|
-
const
|
|
66
|
+
// Count removed files (they disappear from working tree via sparse checkout)
|
|
67
|
+
const registryAbsPath = join(AW_HOME, REGISTRY_DIR, regPath);
|
|
68
|
+
let removed = 0;
|
|
69
|
+
if (!existsSync(registryAbsPath)) {
|
|
70
|
+
removed = 1; // sparse checkout removed it
|
|
71
|
+
}
|
|
44
72
|
|
|
45
|
-
if (removed
|
|
46
|
-
fmt.logInfo(`${chalk.bold(removed)} file${removed > 1 ? 's' : ''} removed from workspace`);
|
|
47
|
-
} else if (!isConfigPath) {
|
|
73
|
+
if (!isConfigPath && removed === 0) {
|
|
48
74
|
fmt.cancel(`Nothing found for ${chalk.cyan(regPath)}.\n\n Use ${chalk.dim('aw status')} to see synced paths.`);
|
|
75
|
+
return;
|
|
49
76
|
}
|
|
50
77
|
|
|
51
|
-
if (!isConfigPath
|
|
52
|
-
fmt.logWarn(`Path
|
|
78
|
+
if (!isConfigPath) {
|
|
79
|
+
fmt.logWarn(`Path was not in sync config — no sparse checkout change made`);
|
|
53
80
|
}
|
|
54
81
|
|
|
55
|
-
|
|
56
|
-
|
|
82
|
+
// Re-link to remove dead symlinks
|
|
83
|
+
linkWorkspace(HOME);
|
|
57
84
|
|
|
58
|
-
|
|
59
|
-
* Find and delete local files whose registry path matches the given path.
|
|
60
|
-
*/
|
|
61
|
-
function deleteMatchingFiles(workspaceDir, path) {
|
|
62
|
-
const manifest = loadManifest(workspaceDir);
|
|
63
|
-
let removed = 0;
|
|
64
|
-
|
|
65
|
-
for (const [manifestKey] of Object.entries(manifest.files)) {
|
|
66
|
-
const registryPath = manifestKeyToRegistryPath(manifestKey);
|
|
67
|
-
|
|
68
|
-
if (matchesAny(registryPath, [path])) {
|
|
69
|
-
const filePath = join(workspaceDir, manifestKey);
|
|
70
|
-
if (existsSync(filePath)) {
|
|
71
|
-
rmSync(filePath, { recursive: true, force: true });
|
|
72
|
-
removed++;
|
|
73
|
-
}
|
|
74
|
-
delete manifest.files[manifestKey];
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
saveManifest(workspaceDir, manifest);
|
|
79
|
-
return removed;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Convert manifest key to registry path.
|
|
84
|
-
* Manifest key now mirrors registry: "platform/agents/architecture-reviewer.md" → "platform/agents/architecture-reviewer"
|
|
85
|
-
*/
|
|
86
|
-
function manifestKeyToRegistryPath(manifestKey) {
|
|
87
|
-
return manifestKey.replace(/\.md$/, '');
|
|
85
|
+
fmt.outro('Done');
|
|
88
86
|
}
|
package/commands/init.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// commands/init.mjs — Clean init: clone registry, link IDEs, global git hooks.
|
|
1
|
+
// commands/init.mjs — Clean init: persistent git clone of registry, link IDEs, global git hooks.
|
|
2
2
|
//
|
|
3
3
|
// No shell profile modifications. No daemons. No background processes.
|
|
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 {
|
|
7
|
+
import { existsSync, writeFileSync, symlinkSync, lstatSync, 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';
|
|
@@ -13,27 +13,33 @@ 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, pullAsync } from './pull.mjs';
|
|
17
|
-
import { sparseCheckoutAsync, includeToSparsePaths, cleanup } from '../git.mjs';
|
|
18
|
-
import { REGISTRY_DIR, REGISTRY_REPO } from '../constants.mjs';
|
|
19
16
|
import { linkWorkspace } from '../link.mjs';
|
|
20
17
|
import { generateCommands, copyInstructions, initAwDocs } from '../integrate.mjs';
|
|
21
18
|
import { setupMcp } from '../mcp.mjs';
|
|
22
19
|
import { autoUpdate, promptUpdate } from '../update.mjs';
|
|
23
20
|
import { installGlobalHooks } from '../hooks.mjs';
|
|
24
21
|
import { installAwEcc } from '../ecc.mjs';
|
|
22
|
+
import {
|
|
23
|
+
initPersistentClone,
|
|
24
|
+
isValidClone,
|
|
25
|
+
fetchAndMerge,
|
|
26
|
+
addToSparseCheckout,
|
|
27
|
+
includeToSparsePaths,
|
|
28
|
+
sparseCheckoutAsync,
|
|
29
|
+
cleanup,
|
|
30
|
+
} from '../git.mjs';
|
|
31
|
+
import { REGISTRY_DIR, REGISTRY_REPO } from '../constants.mjs';
|
|
25
32
|
|
|
26
33
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
34
|
const VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')).version;
|
|
28
35
|
|
|
29
36
|
const HOME = homedir();
|
|
30
37
|
const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
|
|
31
|
-
const
|
|
38
|
+
const AW_HOME = join(HOME, '.aw');
|
|
32
39
|
|
|
33
40
|
// ── IDE tasks for auto-pull ─────────────────────────────────────────────
|
|
34
41
|
|
|
35
42
|
function installIdeTasks() {
|
|
36
|
-
// VS Code / Cursor task — runs aw pull on folder open
|
|
37
43
|
const vscodeTask = {
|
|
38
44
|
version: '2.0.0',
|
|
39
45
|
tasks: [
|
|
@@ -48,18 +54,15 @@ function installIdeTasks() {
|
|
|
48
54
|
],
|
|
49
55
|
};
|
|
50
56
|
|
|
51
|
-
// Install globally for VS Code and Cursor
|
|
52
57
|
for (const ide of ['Code', 'Cursor']) {
|
|
53
58
|
const userDir = join(HOME, 'Library', 'Application Support', ide, 'User');
|
|
54
59
|
if (!existsSync(userDir)) continue;
|
|
55
60
|
|
|
56
61
|
const tasksPath = join(userDir, 'tasks.json');
|
|
57
62
|
if (existsSync(tasksPath)) {
|
|
58
|
-
// Don't override existing tasks — check if aw task already there
|
|
59
63
|
try {
|
|
60
64
|
const existing = JSON.parse(readFileSync(tasksPath, 'utf8'));
|
|
61
65
|
if (existing.tasks?.some(t => t.label === 'aw: sync registry' || t.label === 'aw: pull registry')) continue;
|
|
62
|
-
// Add our task to existing
|
|
63
66
|
existing.tasks = existing.tasks || [];
|
|
64
67
|
existing.tasks.push(vscodeTask.tasks[0]);
|
|
65
68
|
writeFileSync(tasksPath, JSON.stringify(existing, null, 2) + '\n');
|
|
@@ -72,29 +75,6 @@ function installIdeTasks() {
|
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
|
|
75
|
-
function saveManifest(data) {
|
|
76
|
-
writeFileSync(MANIFEST_PATH, JSON.stringify(data, null, 2) + '\n');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
function printPullSummary(pattern, actions) {
|
|
81
|
-
for (const type of ['agents', 'skills', 'commands', 'evals']) {
|
|
82
|
-
const typeActions = actions.filter(a => a.type === type);
|
|
83
|
-
if (typeActions.length === 0) continue;
|
|
84
|
-
|
|
85
|
-
const counts = { ADD: 0, UPDATE: 0, CONFLICT: 0 };
|
|
86
|
-
for (const a of typeActions) counts[a.action] = (counts[a.action] || 0) + 1;
|
|
87
|
-
|
|
88
|
-
const parts = [];
|
|
89
|
-
if (counts.ADD > 0) parts.push(chalk.green(`${counts.ADD} new`));
|
|
90
|
-
if (counts.UPDATE > 0) parts.push(chalk.cyan(`${counts.UPDATE} updated`));
|
|
91
|
-
if (counts.CONFLICT > 0) parts.push(chalk.red(`${counts.CONFLICT} conflict`));
|
|
92
|
-
const detail = parts.length > 0 ? ` (${parts.join(', ')})` : '';
|
|
93
|
-
|
|
94
|
-
fmt.logSuccess(`${chalk.cyan(pattern)}: ${typeActions.length} ${type}${detail}`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
78
|
const ALLOWED_NAMESPACES = ['platform', 'revex', 'mobile', 'commerce', 'leadgen', 'crm', 'marketplace', 'ai'];
|
|
99
79
|
|
|
100
80
|
export async function initCommand(args) {
|
|
@@ -106,12 +86,11 @@ export async function initCommand(args) {
|
|
|
106
86
|
|
|
107
87
|
// ── Validate ──────────────────────────────────────────────────────────
|
|
108
88
|
|
|
109
|
-
// Parse team/sub-team
|
|
110
89
|
let nsParts = namespace ? namespace.split('/') : [];
|
|
111
90
|
let team = nsParts[0] || null;
|
|
112
91
|
let subTeam = nsParts[1] || null;
|
|
113
|
-
let teamNS = subTeam ? `${team}-${subTeam}` : team;
|
|
114
|
-
let folderName = subTeam ? `${team}/${subTeam}` : team;
|
|
92
|
+
let teamNS = subTeam ? `${team}-${subTeam}` : team;
|
|
93
|
+
let folderName = subTeam ? `${team}/${subTeam}` : team;
|
|
115
94
|
|
|
116
95
|
if (team && !ALLOWED_NAMESPACES.includes(team)) {
|
|
117
96
|
const list = ALLOWED_NAMESPACES.map(n => chalk.cyan(n)).join(', ');
|
|
@@ -142,10 +121,16 @@ export async function initCommand(args) {
|
|
|
142
121
|
fmt.cancel(`Invalid sub-team '${subTeam}' — must match: ${SLUG_RE}`);
|
|
143
122
|
}
|
|
144
123
|
|
|
145
|
-
// ──
|
|
124
|
+
// ── Detect installation state ─────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
const repoUrl = `https://github.com/${REGISTRY_REPO}.git`;
|
|
127
|
+
const isGitNative = isValidClone(AW_HOME, repoUrl);
|
|
128
|
+
const isLegacy = !isGitNative && existsSync(GLOBAL_AW_DIR) && !lstatSync(GLOBAL_AW_DIR).isSymbolicLink();
|
|
129
|
+
|
|
130
|
+
// ── Probe remote registry to check if namespace exists (fresh install only) ──
|
|
146
131
|
|
|
147
132
|
let namespaceExistsInRemote = false;
|
|
148
|
-
if (folderName && !silent) {
|
|
133
|
+
if (folderName && !silent && !isGitNative && !isLegacy) {
|
|
149
134
|
try {
|
|
150
135
|
const probePaths = includeToSparsePaths([folderName]);
|
|
151
136
|
const probeDir = await sparseCheckoutAsync(REGISTRY_REPO, probePaths);
|
|
@@ -158,18 +143,16 @@ export async function initCommand(args) {
|
|
|
158
143
|
cleanup(probeDir);
|
|
159
144
|
}
|
|
160
145
|
} catch {
|
|
161
|
-
// Network error — skip probe, proceed without prompt
|
|
162
146
|
namespaceExistsInRemote = true;
|
|
163
147
|
}
|
|
164
148
|
}
|
|
165
149
|
|
|
166
|
-
|
|
167
|
-
if (folderName && !silent && !namespaceExistsInRemote && process.stdin.isTTY) {
|
|
150
|
+
if (folderName && !silent && !isGitNative && !isLegacy && !namespaceExistsInRemote && process.stdin.isTTY) {
|
|
168
151
|
const choice = await fmt.select({
|
|
169
|
-
message: `The namespace '${folderName}' does not exist in the registry yet
|
|
152
|
+
message: `The namespace '${folderName}' does not exist in the registry yet.\nplatform/ includes shared agents, skills & commands that cover most use cases.\nHow would you like to proceed?`,
|
|
170
153
|
options: [
|
|
171
154
|
{ value: 'platform-only', label: 'Continue with platform/ only (recommended for most users)' },
|
|
172
|
-
{ value: 'create-namespace', label: `
|
|
155
|
+
{ value: 'create-namespace', label: `Use '${folderName}' namespace (will be created)` },
|
|
173
156
|
],
|
|
174
157
|
});
|
|
175
158
|
|
|
@@ -183,58 +166,34 @@ export async function initCommand(args) {
|
|
|
183
166
|
}
|
|
184
167
|
}
|
|
185
168
|
|
|
186
|
-
const hasConfig = config.exists(GLOBAL_AW_DIR);
|
|
187
|
-
const hasPlatform = existsSync(join(GLOBAL_AW_DIR, 'platform'));
|
|
188
|
-
const isExisting = hasConfig && hasPlatform;
|
|
189
169
|
const cwd = process.cwd();
|
|
190
170
|
|
|
191
|
-
// ──
|
|
171
|
+
// ── Re-init path: already set up with native git clone ────────────────
|
|
192
172
|
|
|
193
|
-
if (
|
|
173
|
+
if (isGitNative) {
|
|
194
174
|
const cfg = config.load(GLOBAL_AW_DIR);
|
|
195
175
|
|
|
196
|
-
// Add new sub-team if not already tracked
|
|
197
176
|
const isNewSubTeam = folderName && cfg && !cfg.include.includes(folderName);
|
|
198
177
|
if (isNewSubTeam) {
|
|
199
178
|
if (!silent) fmt.logStep(`Adding sub-team ${chalk.cyan(folderName)}...`);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
_positional: ['[template]'],
|
|
203
|
-
_renameNamespace: folderName,
|
|
204
|
-
_teamNS: teamNS,
|
|
205
|
-
_workspaceDir: GLOBAL_AW_DIR,
|
|
206
|
-
_skipIntegrate: true,
|
|
207
|
-
});
|
|
179
|
+
const newSparsePaths = [`.aw_registry/${folderName}`, `content`];
|
|
180
|
+
addToSparseCheckout(AW_HOME, newSparsePaths);
|
|
208
181
|
config.addPattern(GLOBAL_AW_DIR, folderName);
|
|
209
182
|
} else {
|
|
210
183
|
if (!silent) fmt.logStep('Already initialized — syncing...');
|
|
211
184
|
}
|
|
212
185
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
];
|
|
221
|
-
if (freshCfg && freshCfg.include.length > 0) {
|
|
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({
|
|
226
|
-
...args,
|
|
227
|
-
_positional: ['[template]'],
|
|
228
|
-
_workspaceDir: GLOBAL_AW_DIR,
|
|
229
|
-
_skipIntegrate: true,
|
|
230
|
-
_renameNamespace: p,
|
|
231
|
-
_teamNS: derivedTeamNS,
|
|
232
|
-
}));
|
|
233
|
-
}
|
|
186
|
+
const s = fmt.spinner ? fmt.spinner() : { start: () => {}, stop: () => {} };
|
|
187
|
+
if (!silent) s.start('Fetching latest...');
|
|
188
|
+
try {
|
|
189
|
+
fetchAndMerge(AW_HOME);
|
|
190
|
+
if (!silent) s.stop('Registry updated');
|
|
191
|
+
} catch (e) {
|
|
192
|
+
if (!silent) s.stop(chalk.yellow('Fetch failed (continuing with local)'));
|
|
234
193
|
}
|
|
235
|
-
await Promise.all(pullJobs);
|
|
236
194
|
|
|
237
|
-
|
|
195
|
+
const freshCfg = config.load(GLOBAL_AW_DIR);
|
|
196
|
+
|
|
238
197
|
linkWorkspace(HOME);
|
|
239
198
|
await installAwEcc(cwd, { silent });
|
|
240
199
|
generateCommands(HOME);
|
|
@@ -244,7 +203,6 @@ export async function initCommand(args) {
|
|
|
244
203
|
if (cwd !== HOME) await setupMcp(cwd, freshCfg?.namespace || team, { silent });
|
|
245
204
|
installGlobalHooks();
|
|
246
205
|
|
|
247
|
-
// Link current project if needed
|
|
248
206
|
if (cwd !== HOME && !existsSync(join(cwd, '.aw_registry'))) {
|
|
249
207
|
try {
|
|
250
208
|
symlinkSync(GLOBAL_AW_DIR, join(cwd, '.aw_registry'));
|
|
@@ -266,52 +224,66 @@ export async function initCommand(args) {
|
|
|
266
224
|
return;
|
|
267
225
|
}
|
|
268
226
|
|
|
227
|
+
// ── Legacy migration: old ~/.aw_registry/ dir → notify user ──────────
|
|
228
|
+
|
|
229
|
+
if (isLegacy) {
|
|
230
|
+
if (!silent) {
|
|
231
|
+
fmt.logWarn([
|
|
232
|
+
'Legacy installation detected (~/.aw_registry/ is a plain directory).',
|
|
233
|
+
'',
|
|
234
|
+
` Run ${chalk.bold('aw nuke')} first to remove the old install, then ${chalk.bold('aw init')} again.`,
|
|
235
|
+
` This will migrate to the new native git clone at ~/.aw/`,
|
|
236
|
+
].join('\n'));
|
|
237
|
+
}
|
|
238
|
+
// Fall through to full init — create AW_HOME fresh
|
|
239
|
+
}
|
|
240
|
+
|
|
269
241
|
// ── Full init: first time setup ───────────────────────────────────────
|
|
270
242
|
|
|
271
|
-
// Auto-detect user
|
|
272
243
|
if (!user) {
|
|
273
244
|
try {
|
|
274
245
|
user = execSync('git config user.name', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
275
246
|
} catch { /* git not configured */ }
|
|
276
247
|
}
|
|
277
248
|
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
249
|
+
// Determine sparse paths
|
|
250
|
+
const sparsePaths = [`.aw_registry/platform`, `content`, `.aw_registry/AW-PROTOCOL.md`];
|
|
251
|
+
if (folderName) {
|
|
252
|
+
sparsePaths.push(`.aw_registry/${folderName}`);
|
|
253
|
+
}
|
|
282
254
|
|
|
283
255
|
fmt.note([
|
|
284
|
-
`${chalk.dim('source:')} ~/.aw_registry
|
|
256
|
+
`${chalk.dim('source:')} ~/.aw/ → ~/.aw_registry/ (symlink)`,
|
|
285
257
|
folderName ? `${chalk.dim('namespace:')} ${folderName}` : `${chalk.dim('namespace:')} ${chalk.dim('none')}`,
|
|
286
|
-
user ? `${chalk.dim('user:')} ${
|
|
258
|
+
user ? `${chalk.dim('user:')} ${user}` : null,
|
|
287
259
|
`${chalk.dim('version:')} v${VERSION}`,
|
|
288
|
-
].filter(Boolean).join('\n'), 'Config
|
|
260
|
+
].filter(Boolean).join('\n'), 'Config');
|
|
289
261
|
|
|
290
|
-
// Step 2: Pull registry content (parallel)
|
|
291
262
|
const s = fmt.spinner();
|
|
292
|
-
|
|
293
|
-
s.start(`Pulling ${pullTargets}...`);
|
|
294
|
-
|
|
295
|
-
const pullJobs = [
|
|
296
|
-
pullAsync({ ...args, _positional: ['platform'], _workspaceDir: GLOBAL_AW_DIR, _skipIntegrate: true }),
|
|
297
|
-
];
|
|
298
|
-
if (folderName) {
|
|
299
|
-
pullJobs.push(
|
|
300
|
-
pullAsync({ ...args, _positional: ['[template]'], _renameNamespace: folderName, _teamNS: teamNS, _workspaceDir: GLOBAL_AW_DIR, _skipIntegrate: true }),
|
|
301
|
-
);
|
|
302
|
-
}
|
|
263
|
+
s.start(`Cloning registry...`);
|
|
303
264
|
|
|
304
|
-
let pullResults;
|
|
305
265
|
try {
|
|
306
|
-
|
|
307
|
-
s.stop(
|
|
266
|
+
initPersistentClone(repoUrl, AW_HOME, sparsePaths);
|
|
267
|
+
s.stop('Registry cloned');
|
|
308
268
|
} catch (e) {
|
|
309
|
-
s.stop(chalk.red('
|
|
269
|
+
s.stop(chalk.red('Clone failed'));
|
|
310
270
|
fmt.cancel(e.message);
|
|
311
271
|
}
|
|
312
272
|
|
|
313
|
-
|
|
314
|
-
|
|
273
|
+
// Create backward-compat symlink: ~/.aw_registry/ → ~/.aw/.aw_registry/
|
|
274
|
+
if (!existsSync(GLOBAL_AW_DIR)) {
|
|
275
|
+
try {
|
|
276
|
+
symlinkSync(join(AW_HOME, REGISTRY_DIR), GLOBAL_AW_DIR);
|
|
277
|
+
fmt.logStep('Created ~/.aw_registry/ symlink');
|
|
278
|
+
} catch (e) {
|
|
279
|
+
fmt.logWarn(`Could not create symlink ~/.aw_registry/: ${e.message}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Create sync config
|
|
284
|
+
const cfg = config.create(GLOBAL_AW_DIR, { namespace: team, user });
|
|
285
|
+
if (folderName) {
|
|
286
|
+
config.addPattern(GLOBAL_AW_DIR, folderName);
|
|
315
287
|
}
|
|
316
288
|
|
|
317
289
|
// Step 3: Link IDE dirs + setup tasks
|
|
@@ -334,27 +306,14 @@ export async function initCommand(args) {
|
|
|
334
306
|
} catch { /* best effort */ }
|
|
335
307
|
}
|
|
336
308
|
|
|
337
|
-
// Step 5: Write manifest for nuke cleanup
|
|
338
|
-
const manifest = {
|
|
339
|
-
version: 1,
|
|
340
|
-
installedAt: new Date().toISOString(),
|
|
341
|
-
globalDir: GLOBAL_AW_DIR,
|
|
342
|
-
createdFiles: [
|
|
343
|
-
...instructionFiles.map(p => p.startsWith(HOME) ? p.slice(HOME.length + 1) : p),
|
|
344
|
-
...mcpFiles.map(p => p.startsWith(HOME) ? p.slice(HOME.length + 1) : p),
|
|
345
|
-
],
|
|
346
|
-
globalHooksDir: hooksInstalled ? join(HOME, '.aw', 'hooks') : null,
|
|
347
|
-
};
|
|
348
|
-
saveManifest(manifest);
|
|
349
|
-
|
|
350
309
|
// Offer to update if a newer version is available
|
|
351
310
|
await promptUpdate(await args._updateCheck);
|
|
352
311
|
|
|
353
|
-
// Done
|
|
354
312
|
fmt.outro([
|
|
355
313
|
'Install complete',
|
|
356
314
|
'',
|
|
357
|
-
` ${chalk.green('✓')} Source of truth: ~/.
|
|
315
|
+
` ${chalk.green('✓')} Source of truth: ~/.aw/ (git clone)`,
|
|
316
|
+
` ${chalk.green('✓')} Symlink: ~/.aw_registry/ → ~/.aw/.aw_registry/`,
|
|
358
317
|
` ${chalk.green('✓')} IDE integration: ~/.claude/, ~/.cursor/, ~/.codex/`,
|
|
359
318
|
hooksInstalled ? ` ${chalk.green('✓')} Git hooks: auto-sync on pull/clone (core.hooksPath)` : null,
|
|
360
319
|
` ${chalk.green('✓')} IDE task: auto-sync on workspace open`,
|
package/commands/nuke.mjs
CHANGED
|
@@ -290,7 +290,7 @@ export function nukeCommand(args) {
|
|
|
290
290
|
} catch { /* not installed via npm or no permissions */ }
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
// 10. Remove ~/.aw_registry/
|
|
293
|
+
// 10. Remove ~/.aw_registry/ — now a symlink to ~/.aw/.aw_registry/
|
|
294
294
|
try {
|
|
295
295
|
rmSync(GLOBAL_AW_DIR, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
296
296
|
} catch {
|
|
@@ -298,6 +298,17 @@ export function nukeCommand(args) {
|
|
|
298
298
|
}
|
|
299
299
|
fmt.logStep('Removed ~/.aw_registry/');
|
|
300
300
|
|
|
301
|
+
// 11. Remove ~/.aw/ (persistent git clone)
|
|
302
|
+
const AW_HOME = join(HOME, '.aw');
|
|
303
|
+
if (existsSync(AW_HOME)) {
|
|
304
|
+
try {
|
|
305
|
+
rmSync(AW_HOME, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
306
|
+
} catch {
|
|
307
|
+
try { execSync(`rm -rf "${AW_HOME}"`, { stdio: 'pipe' }); } catch { /* best effort */ }
|
|
308
|
+
}
|
|
309
|
+
fmt.logStep('Removed ~/.aw/');
|
|
310
|
+
}
|
|
311
|
+
|
|
301
312
|
fmt.outro([
|
|
302
313
|
'Fully removed',
|
|
303
314
|
'',
|
|
@@ -307,7 +318,7 @@ export function nukeCommand(args) {
|
|
|
307
318
|
` ${chalk.green('✓')} Project symlinks cleaned`,
|
|
308
319
|
` ${chalk.green('✓')} Git hooks removed`,
|
|
309
320
|
` ${chalk.green('✓')} IDE auto-sync tasks removed`,
|
|
310
|
-
` ${chalk.green('✓')} Source of truth deleted`,
|
|
321
|
+
` ${chalk.green('✓')} Source of truth deleted (symlink + git clone)`,
|
|
311
322
|
'',
|
|
312
323
|
` ${chalk.dim('No existing files were touched.')}`,
|
|
313
324
|
` ${chalk.dim('To reinstall:')} ${chalk.bold('aw init')}`,
|