@ghl-ai/aw 0.1.26-beta.2 → 0.1.26-beta.4
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 +6 -0
- package/commands/init.mjs +7 -1
- package/commands/pull.mjs +30 -18
- package/constants.mjs +3 -0
- package/git.mjs +7 -1
- package/package.json +3 -2
- package/update.mjs +126 -0
package/cli.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { join, dirname } from 'node:path';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import * as fmt from './fmt.mjs';
|
|
7
7
|
import { chalk } from './fmt.mjs';
|
|
8
|
+
import { checkForUpdate, notifyUpdate } from './update.mjs';
|
|
8
9
|
|
|
9
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
const VERSION = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8')).version;
|
|
@@ -127,6 +128,9 @@ function printHelp() {
|
|
|
127
128
|
export async function run(argv) {
|
|
128
129
|
const { command, args } = parseArgs(argv);
|
|
129
130
|
|
|
131
|
+
// Fire background npm version check (runs in parallel with the command)
|
|
132
|
+
const updateCheck = checkForUpdate();
|
|
133
|
+
|
|
130
134
|
if (args['--version'] || (!command && args['-v'])) {
|
|
131
135
|
console.log(`aw v${VERSION}`);
|
|
132
136
|
process.exit(0);
|
|
@@ -138,8 +142,10 @@ export async function run(argv) {
|
|
|
138
142
|
}
|
|
139
143
|
|
|
140
144
|
if (command && COMMANDS[command]) {
|
|
145
|
+
args._updateCheck = updateCheck;
|
|
141
146
|
const handler = await COMMANDS[command]();
|
|
142
147
|
await handler(args);
|
|
148
|
+
notifyUpdate(await updateCheck);
|
|
143
149
|
return;
|
|
144
150
|
}
|
|
145
151
|
|
package/commands/init.mjs
CHANGED
|
@@ -17,6 +17,7 @@ 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';
|
|
20
|
+
import { autoUpdate, promptUpdate } from '../update.mjs';
|
|
20
21
|
|
|
21
22
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
23
|
const VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')).version;
|
|
@@ -251,7 +252,9 @@ export async function initCommand(args) {
|
|
|
251
252
|
} catch { /* best effort */ }
|
|
252
253
|
}
|
|
253
254
|
|
|
254
|
-
if (
|
|
255
|
+
if (silent) {
|
|
256
|
+
autoUpdate(await args._updateCheck);
|
|
257
|
+
} else {
|
|
255
258
|
fmt.outro([
|
|
256
259
|
isNewSubTeam ? `Sub-team ${chalk.cyan(folderName)} added` : 'Sync complete',
|
|
257
260
|
'',
|
|
@@ -342,6 +345,9 @@ export async function initCommand(args) {
|
|
|
342
345
|
};
|
|
343
346
|
saveManifest(manifest);
|
|
344
347
|
|
|
348
|
+
// Offer to update if a newer version is available
|
|
349
|
+
await promptUpdate(await args._updateCheck);
|
|
350
|
+
|
|
345
351
|
// Done
|
|
346
352
|
fmt.outro([
|
|
347
353
|
'Install complete',
|
package/commands/pull.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// commands/pull.mjs — Pull content from registry
|
|
2
2
|
|
|
3
|
-
import { mkdirSync, existsSync, readdirSync, copyFileSync
|
|
3
|
+
import { mkdirSync, existsSync, readdirSync, copyFileSync } from 'node:fs';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { execSync } from 'node:child_process';
|
|
@@ -8,7 +8,7 @@ import * as config from '../config.mjs';
|
|
|
8
8
|
import * as fmt from '../fmt.mjs';
|
|
9
9
|
import { chalk } from '../fmt.mjs';
|
|
10
10
|
import { sparseCheckout, sparseCheckoutAsync, cleanup, includeToSparsePaths } from '../git.mjs';
|
|
11
|
-
import { REGISTRY_DIR } from '../constants.mjs';
|
|
11
|
+
import { REGISTRY_DIR, DOCS_SOURCE_DIR } from '../constants.mjs';
|
|
12
12
|
import { walkRegistryTree } from '../registry.mjs';
|
|
13
13
|
import { matchesAny } from '../glob.mjs';
|
|
14
14
|
import { computePlan } from '../plan.mjs';
|
|
@@ -191,14 +191,11 @@ export async function pullCommand(args) {
|
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
mkdirSync(dest, { recursive: true });
|
|
200
|
-
cpSync(docsDir, dest, { recursive: true });
|
|
201
|
-
}
|
|
194
|
+
// Sync docs from repo content/ into platform/docs/ (markdown only, skip images)
|
|
195
|
+
const contentSrc = join(tempDir, DOCS_SOURCE_DIR);
|
|
196
|
+
if (existsSync(contentSrc)) {
|
|
197
|
+
const docsDest = join(workspaceDir, 'platform', 'docs');
|
|
198
|
+
copyMarkdownTree(contentSrc, docsDest);
|
|
202
199
|
}
|
|
203
200
|
|
|
204
201
|
// MCP registration (second-class — skip if not available)
|
|
@@ -293,14 +290,11 @@ export async function pullAsync(args) {
|
|
|
293
290
|
if (existsSync(src)) copyFileSync(src, join(workspaceDir, fname));
|
|
294
291
|
}
|
|
295
292
|
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
mkdirSync(dest, { recursive: true });
|
|
302
|
-
cpSync(docsDir, dest, { recursive: true });
|
|
303
|
-
}
|
|
293
|
+
// Sync docs from repo content/ into platform/docs/ (markdown only, skip images)
|
|
294
|
+
const contentSrc = join(tempDir, DOCS_SOURCE_DIR);
|
|
295
|
+
if (existsSync(contentSrc)) {
|
|
296
|
+
const docsDest = join(workspaceDir, 'platform', 'docs');
|
|
297
|
+
copyMarkdownTree(contentSrc, docsDest);
|
|
304
298
|
}
|
|
305
299
|
|
|
306
300
|
return { pattern, actions, conflictCount };
|
|
@@ -315,6 +309,24 @@ function listDirs(dir) {
|
|
|
315
309
|
.map(d => d.name);
|
|
316
310
|
}
|
|
317
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Recursively copy only .md files from src to dest, preserving directory structure.
|
|
314
|
+
* Skips images and other non-markdown content.
|
|
315
|
+
*/
|
|
316
|
+
function copyMarkdownTree(src, dest) {
|
|
317
|
+
mkdirSync(dest, { recursive: true });
|
|
318
|
+
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
319
|
+
if (entry.name.startsWith('.')) continue;
|
|
320
|
+
const srcPath = join(src, entry.name);
|
|
321
|
+
const destPath = join(dest, entry.name);
|
|
322
|
+
if (entry.isDirectory()) {
|
|
323
|
+
copyMarkdownTree(srcPath, destPath);
|
|
324
|
+
} else if (entry.name.endsWith('.md')) {
|
|
325
|
+
copyFileSync(srcPath, destPath);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
318
330
|
function registerMcp(namespace) {
|
|
319
331
|
const mcpUrl = process.env.GHL_MCP_URL;
|
|
320
332
|
if (!mcpUrl) return;
|
package/constants.mjs
CHANGED
|
@@ -8,3 +8,6 @@ export const REGISTRY_REPO = 'GoHighLevel/platform-docs';
|
|
|
8
8
|
|
|
9
9
|
/** Directory inside the registry repo that holds platform/ and [template]/ */
|
|
10
10
|
export const REGISTRY_DIR = '.aw_registry';
|
|
11
|
+
|
|
12
|
+
/** Directory in platform-docs repo containing documentation (pulled into platform/docs/) */
|
|
13
|
+
export const DOCS_SOURCE_DIR = 'content';
|
package/git.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { mkdtempSync, existsSync } from 'node:fs';
|
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
|
-
import { REGISTRY_BASE_BRANCH, REGISTRY_DIR } from './constants.mjs';
|
|
8
|
+
import { REGISTRY_BASE_BRANCH, REGISTRY_DIR, DOCS_SOURCE_DIR } from './constants.mjs';
|
|
9
9
|
|
|
10
10
|
const exec = promisify(execCb);
|
|
11
11
|
|
|
@@ -77,6 +77,9 @@ export function cleanup(tempDir) {
|
|
|
77
77
|
/**
|
|
78
78
|
* Compute sparse checkout paths from include paths.
|
|
79
79
|
* e.g., ["platform", "dev/agents/debugger"] -> [".aw_registry/platform", ".aw_registry/dev/agents/debugger"]
|
|
80
|
+
*
|
|
81
|
+
* When "platform" is in the paths, also includes the repo's docs source
|
|
82
|
+
* directory (content/) so docs are pulled on-the-fly into platform/docs/.
|
|
80
83
|
*/
|
|
81
84
|
export function includeToSparsePaths(paths) {
|
|
82
85
|
const result = new Set();
|
|
@@ -84,5 +87,8 @@ export function includeToSparsePaths(paths) {
|
|
|
84
87
|
result.add(`${REGISTRY_DIR}/${p}`);
|
|
85
88
|
}
|
|
86
89
|
result.add(`${REGISTRY_DIR}/AW-PROTOCOL.md`);
|
|
90
|
+
if (paths.includes('platform')) {
|
|
91
|
+
result.add(DOCS_SOURCE_DIR);
|
|
92
|
+
}
|
|
87
93
|
return [...result];
|
|
88
94
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghl-ai/aw",
|
|
3
|
-
"version": "0.1.26-beta.
|
|
3
|
+
"version": "0.1.26-beta.4",
|
|
4
4
|
"description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"paths.mjs",
|
|
23
23
|
"plan.mjs",
|
|
24
24
|
"registry.mjs",
|
|
25
|
-
"apply.mjs"
|
|
25
|
+
"apply.mjs",
|
|
26
|
+
"update.mjs"
|
|
26
27
|
],
|
|
27
28
|
"engines": {
|
|
28
29
|
"node": ">=18.0.0"
|
package/update.mjs
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// update.mjs — Version check + auto-update for @ghl-ai/aw CLI.
|
|
2
|
+
//
|
|
3
|
+
// checkForUpdate() → fires npm view in background, returns { current, latest, hasUpdate }
|
|
4
|
+
// notifyUpdate(r) → prints banner if r says update available
|
|
5
|
+
// autoUpdate(r) → spawns npm install in background (--silent mode)
|
|
6
|
+
// promptUpdate(r) → asks user interactively (normal init)
|
|
7
|
+
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
import { exec as execCb, execSync, spawn } from 'node:child_process';
|
|
10
|
+
import { promisify } from 'node:util';
|
|
11
|
+
import * as fmt from './fmt.mjs';
|
|
12
|
+
import { chalk } from './fmt.mjs';
|
|
13
|
+
|
|
14
|
+
const exec = promisify(execCb);
|
|
15
|
+
|
|
16
|
+
const PKG_NAME = '@ghl-ai/aw';
|
|
17
|
+
|
|
18
|
+
function getLocalVersion() {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')).version;
|
|
21
|
+
} catch {
|
|
22
|
+
return '0.0.0';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isPrerelease(version) {
|
|
27
|
+
return /-(alpha|beta|rc|dev|canary)/.test(version);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function compareVersions(a, b) {
|
|
31
|
+
const pa = a.replace(/^v/, '').split(/[-.]/).map(p => (isNaN(p) ? p : Number(p)));
|
|
32
|
+
const pb = b.replace(/^v/, '').split(/[-.]/).map(p => (isNaN(p) ? p : Number(p)));
|
|
33
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
34
|
+
const va = pa[i] ?? 0;
|
|
35
|
+
const vb = pb[i] ?? 0;
|
|
36
|
+
if (typeof va === 'string' || typeof vb === 'string') {
|
|
37
|
+
const sa = String(va), sb = String(vb);
|
|
38
|
+
if (sa < sb) return -1;
|
|
39
|
+
if (sa > sb) return 1;
|
|
40
|
+
} else {
|
|
41
|
+
if (va < vb) return -1;
|
|
42
|
+
if (va > vb) return 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Fetch the latest version from npm (non-blocking, 5s timeout).
|
|
50
|
+
* Call at the start of any command, await at the end.
|
|
51
|
+
*/
|
|
52
|
+
export async function checkForUpdate() {
|
|
53
|
+
try {
|
|
54
|
+
const { stdout } = await exec(`npm view ${PKG_NAME} version`, { timeout: 5000 });
|
|
55
|
+
const latest = stdout.trim();
|
|
56
|
+
const current = getLocalVersion();
|
|
57
|
+
return { current, latest, hasUpdate: compareVersions(latest, current) > 0 };
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Print a banner nudge if a newer version is available.
|
|
65
|
+
* Shows for all users (including beta).
|
|
66
|
+
*/
|
|
67
|
+
export function notifyUpdate(result) {
|
|
68
|
+
if (!result?.hasUpdate) return;
|
|
69
|
+
const msg = [
|
|
70
|
+
'',
|
|
71
|
+
` ${chalk.bgHex('#FF6B35').bold(' UPDATE ')} New version available: ${chalk.dim(result.current)} → ${chalk.green(result.latest)}`,
|
|
72
|
+
` ${chalk.dim('Run')} ${chalk.bold(`npm i -g ${PKG_NAME}`)} ${chalk.dim('to update')}`,
|
|
73
|
+
'',
|
|
74
|
+
].join('\n');
|
|
75
|
+
console.log(msg);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Auto-update in background (fire-and-forget, detached process).
|
|
80
|
+
* Used by `aw init --silent`. Skipped for prerelease versions.
|
|
81
|
+
*/
|
|
82
|
+
export function autoUpdate(result) {
|
|
83
|
+
if (!result?.hasUpdate) return;
|
|
84
|
+
if (isPrerelease(result.current)) return;
|
|
85
|
+
try {
|
|
86
|
+
const child = spawn('npm', ['i', '-g', `${PKG_NAME}@latest`], {
|
|
87
|
+
detached: true,
|
|
88
|
+
stdio: 'ignore',
|
|
89
|
+
});
|
|
90
|
+
child.unref();
|
|
91
|
+
} catch { /* best effort */ }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Interactive prompt to update during normal init.
|
|
96
|
+
* Skipped for prerelease versions.
|
|
97
|
+
*/
|
|
98
|
+
export async function promptUpdate(result) {
|
|
99
|
+
if (!result?.hasUpdate) return false;
|
|
100
|
+
if (isPrerelease(result.current)) return false;
|
|
101
|
+
|
|
102
|
+
fmt.logWarn(`Update available: ${chalk.dim(result.current)} → ${chalk.green(result.latest)}`);
|
|
103
|
+
|
|
104
|
+
const { default: clack } = await import('@clack/prompts');
|
|
105
|
+
const shouldUpdate = await clack.confirm({
|
|
106
|
+
message: `Update ${PKG_NAME} to ${result.latest}?`,
|
|
107
|
+
initialValue: true,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (clack.isCancel(shouldUpdate) || !shouldUpdate) {
|
|
111
|
+
fmt.logStep(chalk.dim('Skipped update'));
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const s = fmt.spinner();
|
|
116
|
+
s.start(`Updating to ${result.latest}...`);
|
|
117
|
+
try {
|
|
118
|
+
execSync(`npm i -g ${PKG_NAME}@latest`, { stdio: 'pipe', timeout: 30000 });
|
|
119
|
+
s.stop(`Updated to ${chalk.green(result.latest)}`);
|
|
120
|
+
return true;
|
|
121
|
+
} catch {
|
|
122
|
+
s.stop(chalk.red('Update failed'));
|
|
123
|
+
fmt.logWarn(`Manual update: ${chalk.bold(`npm i -g ${PKG_NAME}@latest`)}`);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|