@ghl-ai/aw 0.1.57 → 0.1.58-beta.0
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 +5 -2
- package/commands/init.mjs +13 -56
- package/commands/integration.mjs +77 -38
- package/commands/integrations.mjs +100 -84
- package/ecc.mjs +3 -14
- package/git.mjs +0 -2
- package/integrations/context-mode.mjs +89 -312
- package/integrations/index.mjs +31 -0
- package/integrations.mjs +40 -153
- package/link.mjs +69 -3
- package/package.json +3 -4
- package/update.mjs +14 -29
- package/package-manager.mjs +0 -72
package/integrations.mjs
CHANGED
|
@@ -19,18 +19,10 @@ import { join, basename } from 'node:path';
|
|
|
19
19
|
import { homedir } from 'node:os';
|
|
20
20
|
import * as fmt from './fmt.mjs';
|
|
21
21
|
import { chalk } from './fmt.mjs';
|
|
22
|
-
import {
|
|
23
|
-
ensureContextModeIntegration,
|
|
24
|
-
getContextModeIntegrationSummary,
|
|
25
|
-
removeContextModeIntegration,
|
|
26
|
-
} from './integrations/context-mode.mjs';
|
|
27
22
|
|
|
28
23
|
const execAsync = promisify(exec);
|
|
29
24
|
const HOME = homedir();
|
|
30
|
-
|
|
31
|
-
function manifestPath(home = HOME) {
|
|
32
|
-
return join(home, '.aw_registry', '.integration-manifest.json');
|
|
33
|
-
}
|
|
25
|
+
const MANIFEST_PATH = join(HOME, '.aw_registry', '.integration-manifest.json');
|
|
34
26
|
|
|
35
27
|
function getErrorMessage(error) {
|
|
36
28
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -144,16 +136,6 @@ export const INTEGRATIONS = {
|
|
|
144
136
|
requiresAuth: false,
|
|
145
137
|
authNote: 'run /graphify . inside your IDE to build the graph whenever you need it',
|
|
146
138
|
},
|
|
147
|
-
|
|
148
|
-
'context-mode': {
|
|
149
|
-
type: 'context-mode',
|
|
150
|
-
label: 'Context Mode',
|
|
151
|
-
description: 'Local context-mode MCP server and hook-based context preservation',
|
|
152
|
-
autoInstall: false,
|
|
153
|
-
teams: [],
|
|
154
|
-
requiresAuth: false,
|
|
155
|
-
authNote: 'Run aw doctor to verify context-mode health.',
|
|
156
|
-
},
|
|
157
139
|
};
|
|
158
140
|
|
|
159
141
|
// ────────────────────────────────────────────────────────────────────────────────
|
|
@@ -166,27 +148,25 @@ export const BUNDLES = {};
|
|
|
166
148
|
// MANIFEST MANAGEMENT
|
|
167
149
|
// ────────────────────────────────────────────────────────────────────────────────
|
|
168
150
|
|
|
169
|
-
export function readManifest(
|
|
170
|
-
|
|
171
|
-
if (!existsSync(filePath)) {
|
|
151
|
+
export function readManifest() {
|
|
152
|
+
if (!existsSync(MANIFEST_PATH)) {
|
|
172
153
|
return { version: 1, installed: {} };
|
|
173
154
|
}
|
|
174
155
|
|
|
175
156
|
try {
|
|
176
|
-
return JSON.parse(readFileSync(
|
|
157
|
+
return JSON.parse(readFileSync(MANIFEST_PATH, 'utf8'));
|
|
177
158
|
} catch {
|
|
178
159
|
return { version: 1, installed: {} };
|
|
179
160
|
}
|
|
180
161
|
}
|
|
181
162
|
|
|
182
|
-
export function writeManifest(manifest
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
writeFileSync(filePath, JSON.stringify(manifest, null, 2) + '\n');
|
|
163
|
+
export function writeManifest(manifest) {
|
|
164
|
+
mkdirSync(join(HOME, '.aw_registry'), { recursive: true });
|
|
165
|
+
writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2) + '\n');
|
|
186
166
|
}
|
|
187
167
|
|
|
188
|
-
export function isInstalled(key
|
|
189
|
-
const manifest = readManifest(
|
|
168
|
+
export function isInstalled(key) {
|
|
169
|
+
const manifest = readManifest();
|
|
190
170
|
const entry = manifest.installed[key];
|
|
191
171
|
if (!entry) return false;
|
|
192
172
|
// Backward compat: pre-existing entries have no `status` field — treat as installed.
|
|
@@ -195,35 +175,25 @@ export function isInstalled(key, options = {}) {
|
|
|
195
175
|
return entry.status !== 'skipped';
|
|
196
176
|
}
|
|
197
177
|
|
|
198
|
-
function recordInstalled(key, type
|
|
199
|
-
|
|
200
|
-
const manifest = readManifest(options.home);
|
|
178
|
+
function recordInstalled(key, type) {
|
|
179
|
+
const manifest = readManifest();
|
|
201
180
|
manifest.installed[key] = {
|
|
202
181
|
type,
|
|
203
182
|
status: 'installed',
|
|
204
183
|
installedAt: new Date().toISOString(),
|
|
205
|
-
...(options.extra || {}),
|
|
206
184
|
};
|
|
207
|
-
writeManifest(manifest
|
|
185
|
+
writeManifest(manifest);
|
|
208
186
|
}
|
|
209
187
|
|
|
210
|
-
function recordSkipped(key, type, reason
|
|
211
|
-
|
|
212
|
-
const manifest = readManifest(options.home);
|
|
188
|
+
function recordSkipped(key, type, reason) {
|
|
189
|
+
const manifest = readManifest();
|
|
213
190
|
manifest.installed[key] = {
|
|
214
191
|
type,
|
|
215
192
|
status: 'skipped',
|
|
216
193
|
reason,
|
|
217
194
|
installedAt: new Date().toISOString(),
|
|
218
195
|
};
|
|
219
|
-
writeManifest(manifest
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function removeInstalled(key, options = {}) {
|
|
223
|
-
if (options.dryRun) return;
|
|
224
|
-
const manifest = readManifest(options.home);
|
|
225
|
-
delete manifest.installed[key];
|
|
226
|
-
writeManifest(manifest, options.home);
|
|
196
|
+
writeManifest(manifest);
|
|
227
197
|
}
|
|
228
198
|
|
|
229
199
|
// ────────────────────────────────────────────────────────────────────────────────
|
|
@@ -449,7 +419,7 @@ async function resolveCliInvocation(cliCommand, pythonCmd, installer) {
|
|
|
449
419
|
}
|
|
450
420
|
}
|
|
451
421
|
|
|
452
|
-
async function runPythonCli(integration, key, { silent = false
|
|
422
|
+
async function runPythonCli(integration, key, { silent = false } = {}) {
|
|
453
423
|
const spinner = silent ? null : fmt.spinner();
|
|
454
424
|
|
|
455
425
|
// 1. Detect Python ≥ minPython.
|
|
@@ -457,7 +427,7 @@ async function runPythonCli(integration, key, { silent = false, home = HOME } =
|
|
|
457
427
|
const py = await detectPython(integration.minPython);
|
|
458
428
|
if (!py) {
|
|
459
429
|
if (!silent) spinner.stop(chalk.yellow('Python not found'));
|
|
460
|
-
recordSkipped(key, 'python-cli', 'python-not-found'
|
|
430
|
+
recordSkipped(key, 'python-cli', 'python-not-found');
|
|
461
431
|
if (!silent) {
|
|
462
432
|
fmt.logWarn(
|
|
463
433
|
`${integration.label} skipped — Python ${integration.minPython.major}.${integration.minPython.minor}+ required.`,
|
|
@@ -589,7 +559,7 @@ async function runClaudePlugin(installCmd, { silent = false, marketplaceSource =
|
|
|
589
559
|
// UNIVERSAL INSTALLER (OS-detected master script — covers all IDEs in one shot)
|
|
590
560
|
// ────────────────────────────────────────────────────────────────────────────────
|
|
591
561
|
|
|
592
|
-
async function runUniversalInstaller(integration, key, { silent = false
|
|
562
|
+
async function runUniversalInstaller(integration, key, { silent = false } = {}) {
|
|
593
563
|
const spinner = silent ? null : fmt.spinner();
|
|
594
564
|
|
|
595
565
|
if (!silent) spinner.start(`Installing ${integration.label} for all detected IDEs...`);
|
|
@@ -636,7 +606,7 @@ async function runUniversalInstaller(integration, key, { silent = false, home =
|
|
|
636
606
|
'Skipped',
|
|
637
607
|
);
|
|
638
608
|
}
|
|
639
|
-
recordSkipped(key, 'universal-installer', 'wsl-not-found'
|
|
609
|
+
recordSkipped(key, 'universal-installer', 'wsl-not-found');
|
|
640
610
|
return false;
|
|
641
611
|
}
|
|
642
612
|
}
|
|
@@ -679,60 +649,13 @@ async function runUniversalInstaller(integration, key, { silent = false, home =
|
|
|
679
649
|
// INTEGRATION INSTALLER
|
|
680
650
|
// ────────────────────────────────────────────────────────────────────────────────
|
|
681
651
|
|
|
682
|
-
function
|
|
683
|
-
return { success: true, key, integration, ...extra };
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
function failureResult(key, integration, error, extra = {}) {
|
|
687
|
-
return { success: false, key, integration, error, ...extra };
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
export function integrationSucceeded(result) {
|
|
691
|
-
return result === true || result?.success === true;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
export async function installIntegration(key, options = {}) {
|
|
695
|
-
const { silent = false } = options;
|
|
696
|
-
const home = options.home || HOME;
|
|
697
|
-
const dryRun = options.dryRun === true;
|
|
652
|
+
export async function installIntegration(key, { silent = false } = {}) {
|
|
698
653
|
const integration = INTEGRATIONS[key];
|
|
699
654
|
if (!integration) {
|
|
700
655
|
throw new Error(`Unknown integration: ${key}`);
|
|
701
656
|
}
|
|
702
657
|
|
|
703
|
-
if (
|
|
704
|
-
const result = ensureContextModeIntegration(home, {
|
|
705
|
-
...options,
|
|
706
|
-
home,
|
|
707
|
-
env: options.env || process.env,
|
|
708
|
-
dryRun,
|
|
709
|
-
});
|
|
710
|
-
if (dryRun) {
|
|
711
|
-
return successResult(key, integration, result);
|
|
712
|
-
}
|
|
713
|
-
if (result.binary.present && result.complete) {
|
|
714
|
-
recordInstalled(key, integration.type, {
|
|
715
|
-
home,
|
|
716
|
-
extra: {
|
|
717
|
-
binaryPath: result.binary.path,
|
|
718
|
-
version: result.binary.version,
|
|
719
|
-
},
|
|
720
|
-
});
|
|
721
|
-
return successResult(key, integration, result);
|
|
722
|
-
}
|
|
723
|
-
return failureResult(
|
|
724
|
-
key,
|
|
725
|
-
integration,
|
|
726
|
-
result.binary.present ? 'partial-config' : (result.warnings?.[0] || 'binary-missing'),
|
|
727
|
-
result,
|
|
728
|
-
);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
if (dryRun) {
|
|
732
|
-
return successResult(key, integration, { plannedInstall: true });
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
if (isInstalled(key, { home })) {
|
|
658
|
+
if (isInstalled(key)) {
|
|
736
659
|
if (!silent) fmt.logWarn(`${integration.label} is already installed`);
|
|
737
660
|
return false;
|
|
738
661
|
}
|
|
@@ -741,7 +664,7 @@ export async function installIntegration(key, options = {}) {
|
|
|
741
664
|
if (integration.type === 'plugin') {
|
|
742
665
|
// PLUGIN: Run claude plugin install
|
|
743
666
|
await runClaudePlugin(integration.installCmd, { silent, marketplaceSource: integration.marketplaceSource ?? null });
|
|
744
|
-
recordInstalled(key, 'plugin'
|
|
667
|
+
recordInstalled(key, 'plugin');
|
|
745
668
|
|
|
746
669
|
if (!silent) {
|
|
747
670
|
fmt.logSuccess(`${integration.label} plugin installed`);
|
|
@@ -776,7 +699,7 @@ export async function installIntegration(key, options = {}) {
|
|
|
776
699
|
integration.mcpUrl
|
|
777
700
|
);
|
|
778
701
|
|
|
779
|
-
recordInstalled(key, 'remote-mcp'
|
|
702
|
+
recordInstalled(key, 'remote-mcp');
|
|
780
703
|
|
|
781
704
|
if (!silent) {
|
|
782
705
|
fmt.logSuccess(`${integration.label} added to MCP servers`);
|
|
@@ -786,10 +709,10 @@ export async function installIntegration(key, options = {}) {
|
|
|
786
709
|
}
|
|
787
710
|
} else if (integration.type === 'python-cli') {
|
|
788
711
|
// PYTHON CLI: pip-install + run post-install + per-project hooks
|
|
789
|
-
const ok = await runPythonCli(integration, key, { silent
|
|
712
|
+
const ok = await runPythonCli(integration, key, { silent });
|
|
790
713
|
if (!ok) return false; // skipped (e.g. Python missing) — manifest already recorded
|
|
791
714
|
|
|
792
|
-
recordInstalled(key, 'python-cli'
|
|
715
|
+
recordInstalled(key, 'python-cli');
|
|
793
716
|
|
|
794
717
|
if (!silent) {
|
|
795
718
|
fmt.logSuccess(`${integration.label} installed`);
|
|
@@ -799,9 +722,9 @@ export async function installIntegration(key, options = {}) {
|
|
|
799
722
|
}
|
|
800
723
|
} else if (integration.type === 'universal-installer') {
|
|
801
724
|
// UNIVERSAL INSTALLER: OS-detected master script, covers all IDEs in one shot
|
|
802
|
-
const ok = await runUniversalInstaller(integration, key, { silent
|
|
725
|
+
const ok = await runUniversalInstaller(integration, key, { silent });
|
|
803
726
|
if (!ok) return false; // skipped (e.g. WSL not found) — manifest already recorded
|
|
804
|
-
recordInstalled(key, 'universal-installer'
|
|
727
|
+
recordInstalled(key, 'universal-installer');
|
|
805
728
|
|
|
806
729
|
if (!silent) {
|
|
807
730
|
fmt.logSuccess(`${integration.label} installed across all detected IDEs`);
|
|
@@ -825,22 +748,13 @@ export async function installIntegration(key, options = {}) {
|
|
|
825
748
|
// INTEGRATION REMOVER
|
|
826
749
|
// ────────────────────────────────────────────────────────────────────────────────
|
|
827
750
|
|
|
828
|
-
export async function removeIntegration(key,
|
|
829
|
-
const { silent = false } = options;
|
|
830
|
-
const home = options.home || HOME;
|
|
831
|
-
const dryRun = options.dryRun === true;
|
|
751
|
+
export async function removeIntegration(key, { silent = false } = {}) {
|
|
832
752
|
const integration = INTEGRATIONS[key];
|
|
833
753
|
if (!integration) {
|
|
834
754
|
throw new Error(`Unknown integration: ${key}`);
|
|
835
755
|
}
|
|
836
756
|
|
|
837
|
-
if (
|
|
838
|
-
const result = removeContextModeIntegration(home, { dryRun });
|
|
839
|
-
removeInstalled(key, { home, dryRun });
|
|
840
|
-
return successResult(key, integration, result);
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
if (!isInstalled(key, { home })) {
|
|
757
|
+
if (!isInstalled(key)) {
|
|
844
758
|
if (!silent) fmt.logWarn(`${integration.label} is not installed`);
|
|
845
759
|
return false;
|
|
846
760
|
}
|
|
@@ -934,7 +848,9 @@ export async function removeIntegration(key, options = {}) {
|
|
|
934
848
|
}
|
|
935
849
|
|
|
936
850
|
// Remove from manifest
|
|
937
|
-
|
|
851
|
+
const manifest = readManifest();
|
|
852
|
+
delete manifest.installed[key];
|
|
853
|
+
writeManifest(manifest);
|
|
938
854
|
|
|
939
855
|
return true;
|
|
940
856
|
} catch (e) {
|
|
@@ -949,8 +865,8 @@ export async function removeIntegration(key, options = {}) {
|
|
|
949
865
|
// HELPERS
|
|
950
866
|
// ────────────────────────────────────────────────────────────────────────────────
|
|
951
867
|
|
|
952
|
-
export function getInstalledList(
|
|
953
|
-
const manifest = readManifest(
|
|
868
|
+
export function getInstalledList() {
|
|
869
|
+
const manifest = readManifest();
|
|
954
870
|
// Only return entries actually installed — skipped entries (e.g. Python missing)
|
|
955
871
|
// shouldn't show up as "installed" to the rest of the system.
|
|
956
872
|
return Object.entries(manifest.installed)
|
|
@@ -958,34 +874,6 @@ export function getInstalledList(options = {}) {
|
|
|
958
874
|
.map(([key]) => key);
|
|
959
875
|
}
|
|
960
876
|
|
|
961
|
-
export function getIntegrationSummary(key, options = {}) {
|
|
962
|
-
const integration = INTEGRATIONS[key];
|
|
963
|
-
if (!integration) return null;
|
|
964
|
-
if (integration.type === 'context-mode') {
|
|
965
|
-
return {
|
|
966
|
-
...getContextModeIntegrationSummary(options.home || HOME, options),
|
|
967
|
-
label: integration.label,
|
|
968
|
-
description: integration.description,
|
|
969
|
-
type: integration.type,
|
|
970
|
-
};
|
|
971
|
-
}
|
|
972
|
-
const installed = isInstalled(key, options);
|
|
973
|
-
return {
|
|
974
|
-
name: key,
|
|
975
|
-
label: integration.label,
|
|
976
|
-
description: integration.description,
|
|
977
|
-
type: integration.type,
|
|
978
|
-
installed,
|
|
979
|
-
configured: installed,
|
|
980
|
-
state: installed ? 'installed' : 'available',
|
|
981
|
-
summary: installed ? `${integration.label} installed` : `${integration.label} available`,
|
|
982
|
-
};
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
export function getIntegrationSummaries(options = {}) {
|
|
986
|
-
return Object.keys(INTEGRATIONS).map(key => getIntegrationSummary(key, options));
|
|
987
|
-
}
|
|
988
|
-
|
|
989
877
|
export function suggestForTeam(namespace) {
|
|
990
878
|
if (!namespace) return [];
|
|
991
879
|
|
|
@@ -1007,8 +895,7 @@ export function suggestForTeam(namespace) {
|
|
|
1007
895
|
// AUTO-INSTALL (called from init.mjs - installs suggested integrations)
|
|
1008
896
|
// ────────────────────────────────────────────────────────────────────────────────
|
|
1009
897
|
|
|
1010
|
-
export async function autoInstallIntegrations(team,
|
|
1011
|
-
const { silent = false, installer = installIntegration } = options;
|
|
898
|
+
export async function autoInstallIntegrations(team, { silent = false, installer = installIntegration } = {}) {
|
|
1012
899
|
// Get suggested integrations for this team
|
|
1013
900
|
const suggested = suggestForTeam(team);
|
|
1014
901
|
if (!suggested || suggested.length === 0) {
|
|
@@ -1016,7 +903,7 @@ export async function autoInstallIntegrations(team, options = {}) {
|
|
|
1016
903
|
}
|
|
1017
904
|
|
|
1018
905
|
// Only install if they're not already installed
|
|
1019
|
-
const toInstall = suggested.filter((key) => !isInstalled(key
|
|
906
|
+
const toInstall = suggested.filter((key) => !isInstalled(key));
|
|
1020
907
|
if (toInstall.length === 0) {
|
|
1021
908
|
return [];
|
|
1022
909
|
}
|
|
@@ -1027,8 +914,8 @@ export async function autoInstallIntegrations(team, options = {}) {
|
|
|
1027
914
|
|
|
1028
915
|
const installed = [];
|
|
1029
916
|
for (const key of toInstall) {
|
|
1030
|
-
const
|
|
1031
|
-
if (
|
|
917
|
+
const success = await installer(key, { silent });
|
|
918
|
+
if (success) {
|
|
1032
919
|
installed.push(INTEGRATIONS[key].label);
|
|
1033
920
|
}
|
|
1034
921
|
}
|
|
@@ -1124,8 +1011,8 @@ export async function promptAndInstall(team, { silent = false } = {}) {
|
|
|
1124
1011
|
// Install all
|
|
1125
1012
|
const installed = [];
|
|
1126
1013
|
for (const key of toInstall) {
|
|
1127
|
-
const
|
|
1128
|
-
if (
|
|
1014
|
+
const success = await installIntegration(key, { silent: false });
|
|
1015
|
+
if (success) {
|
|
1129
1016
|
installed.push(INTEGRATIONS[key].label);
|
|
1130
1017
|
}
|
|
1131
1018
|
}
|
package/link.mjs
CHANGED
|
@@ -11,11 +11,29 @@ function forceSymlink(target, linkPath) {
|
|
|
11
11
|
symlinkSync(target, linkPath);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
function linkNestedSkillRoot(target, linkPath, { silent }) {
|
|
15
|
+
try {
|
|
16
|
+
forceSymlink(target, linkPath);
|
|
17
|
+
return true;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (!silent) {
|
|
20
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
21
|
+
throw new Error(`Could not link nested skill ${linkPath} -> ${target}: ${message}`);
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
14
27
|
const IDE_DIRS = ['.claude', '.cursor', '.codex'];
|
|
15
28
|
// Per-file symlink types
|
|
16
29
|
const FILE_TYPES = ['agents'];
|
|
17
30
|
const ALL_KNOWN_TYPES = new Set([...FILE_TYPES, 'skills', 'commands', 'evals', 'references', 'docs']);
|
|
18
31
|
|
|
32
|
+
function realHomeDir() {
|
|
33
|
+
const HOME = homedir();
|
|
34
|
+
try { return realpathSync(HOME); } catch { return HOME; }
|
|
35
|
+
}
|
|
36
|
+
|
|
19
37
|
/**
|
|
20
38
|
* List namespace directories inside .aw_registry/ (skip dotfiles).
|
|
21
39
|
*/
|
|
@@ -36,6 +54,32 @@ function listDirs(dir) {
|
|
|
36
54
|
.map(d => d.name);
|
|
37
55
|
}
|
|
38
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Recursively find directories below a skills/ folder that are real skill roots.
|
|
59
|
+
* A real skill root is a directory containing SKILL.md. Once found, do not
|
|
60
|
+
* descend further so bundled references/scripts inside that skill are preserved.
|
|
61
|
+
*/
|
|
62
|
+
function findSkillRootDirs(skillsDir) {
|
|
63
|
+
const results = [];
|
|
64
|
+
function walk(dir, segments) {
|
|
65
|
+
if (!existsSync(dir)) return;
|
|
66
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
67
|
+
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
68
|
+
|
|
69
|
+
const childDir = join(dir, entry.name);
|
|
70
|
+
const childSegments = [...segments, entry.name];
|
|
71
|
+
if (existsSync(join(childDir, 'SKILL.md'))) {
|
|
72
|
+
results.push({ skillDirPath: childDir, segments: childSegments });
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
walk(childDir, childSegments);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
walk(skillsDir, []);
|
|
80
|
+
return results;
|
|
81
|
+
}
|
|
82
|
+
|
|
39
83
|
/**
|
|
40
84
|
* Recursively find directories named `typeName` within `nsDir`,
|
|
41
85
|
* skipping other known type directories to avoid false matches.
|
|
@@ -77,7 +121,7 @@ function cleanIdeSymlinks(cwd) {
|
|
|
77
121
|
cleanSymlinksRecursive(ideDir);
|
|
78
122
|
}
|
|
79
123
|
// Also clean .agents/skills/ (global only — Codex reads from ~/.agents/skills/)
|
|
80
|
-
const HOME =
|
|
124
|
+
const HOME = realHomeDir();
|
|
81
125
|
if (cwd === HOME) {
|
|
82
126
|
const agentsSkillsDir = join(cwd, '.agents', 'skills');
|
|
83
127
|
if (existsSync(agentsSkillsDir)) cleanSymlinksRecursive(agentsSkillsDir);
|
|
@@ -140,7 +184,8 @@ export function linkWorkspace(cwd, awDirOverride = null, { silent = false } = {}
|
|
|
140
184
|
// where $HOME may be /var/... but process.cwd() resolves to /private/var/...
|
|
141
185
|
try { cwd = realpathSync(cwd); } catch { /* use as-is */ }
|
|
142
186
|
|
|
143
|
-
const
|
|
187
|
+
const HOME = realHomeDir();
|
|
188
|
+
const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
|
|
144
189
|
let awDir = awDirOverride || getLocalRegistryDir(cwd, GLOBAL_AW_DIR);
|
|
145
190
|
try { awDir = realpathSync(awDir); } catch { /* use as-is if it doesn't exist */ }
|
|
146
191
|
if (!existsSync(awDir)) return 0;
|
|
@@ -185,6 +230,19 @@ export function linkWorkspace(cwd, awDirOverride = null, { silent = false } = {}
|
|
|
185
230
|
try { forceSymlink(relTarget, linkPath); created++; } catch { /* best effort */ }
|
|
186
231
|
}
|
|
187
232
|
}
|
|
233
|
+
|
|
234
|
+
for (const { skillDirPath, segments: skillSegments } of findSkillRootDirs(skillsDir)) {
|
|
235
|
+
if (skillSegments.length <= 1) continue;
|
|
236
|
+
const flat = [ns, ...segments, ...skillSegments].join('-');
|
|
237
|
+
|
|
238
|
+
for (const ide of IDE_DIRS) {
|
|
239
|
+
const linkDir = join(cwd, ide, 'skills');
|
|
240
|
+
mkdirSync(linkDir, { recursive: true });
|
|
241
|
+
const linkPath = join(linkDir, flat);
|
|
242
|
+
const relTarget = relative(linkDir, skillDirPath);
|
|
243
|
+
if (linkNestedSkillRoot(relTarget, linkPath, { silent })) created++;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
188
246
|
}
|
|
189
247
|
}
|
|
190
248
|
|
|
@@ -246,7 +304,7 @@ export function linkWorkspace(cwd, awDirOverride = null, { silent = false } = {}
|
|
|
246
304
|
}
|
|
247
305
|
|
|
248
306
|
// Codex per-skill symlinks: ~/.agents/skills/<name> (global only)
|
|
249
|
-
if (cwd ===
|
|
307
|
+
if (cwd === HOME) {
|
|
250
308
|
const agentsSkillsDir = join(cwd, '.agents/skills');
|
|
251
309
|
for (const ns of namespaces) {
|
|
252
310
|
for (const { typeDirPath: skillsDir, segments } of findNestedTypeDirs(join(awDir, ns), 'skills')) {
|
|
@@ -258,6 +316,14 @@ export function linkWorkspace(cwd, awDirOverride = null, { silent = false } = {}
|
|
|
258
316
|
const relTarget = relative(agentsSkillsDir, targetPath);
|
|
259
317
|
try { forceSymlink(relTarget, linkPath); created++; } catch { /* best effort */ }
|
|
260
318
|
}
|
|
319
|
+
|
|
320
|
+
for (const { skillDirPath, segments: skillSegments } of findSkillRootDirs(skillsDir)) {
|
|
321
|
+
if (skillSegments.length <= 1) continue;
|
|
322
|
+
const flat = [ns, ...segments, ...skillSegments].join('-');
|
|
323
|
+
const linkPath = join(agentsSkillsDir, flat);
|
|
324
|
+
const relTarget = relative(agentsSkillsDir, skillDirPath);
|
|
325
|
+
if (linkNestedSkillRoot(relTarget, linkPath, { silent })) created++;
|
|
326
|
+
}
|
|
261
327
|
}
|
|
262
328
|
}
|
|
263
329
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghl-ai/aw",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.58-beta.0",
|
|
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,6 @@
|
|
|
22
22
|
"link.mjs",
|
|
23
23
|
"manifest.mjs",
|
|
24
24
|
"mcp.mjs",
|
|
25
|
-
"package-manager.mjs",
|
|
26
25
|
"paths.mjs",
|
|
27
26
|
"plan.mjs",
|
|
28
27
|
"registry.mjs",
|
|
@@ -55,9 +54,9 @@
|
|
|
55
54
|
"license": "MIT",
|
|
56
55
|
"scripts": {
|
|
57
56
|
"test": "yarn test:vitest && yarn test:node",
|
|
58
|
-
"test:vitest": "vitest run --reporter=verbose tests/commands tests/mcp.test.mjs tests/telemetry.test.mjs tests/c4 tests/integrations-graphify.test.mjs
|
|
57
|
+
"test:vitest": "vitest run --reporter=verbose tests/commands tests/mcp.test.mjs tests/telemetry.test.mjs tests/c4 tests/integrations-graphify.test.mjs",
|
|
59
58
|
"test:node": "node tests/run-node-tests.mjs",
|
|
60
|
-
"test:watch": "vitest --reporter=verbose tests/commands tests/mcp.test.mjs tests/telemetry.test.mjs tests/c4 tests/integrations-graphify.test.mjs
|
|
59
|
+
"test:watch": "vitest --reporter=verbose tests/commands tests/mcp.test.mjs tests/telemetry.test.mjs tests/c4 tests/integrations-graphify.test.mjs",
|
|
61
60
|
"preuninstall": "node bin.js nuke 2>/dev/null || true"
|
|
62
61
|
},
|
|
63
62
|
"publishConfig": {
|
package/update.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// .volta/ → volta install pnpm/ → pnpm add -g
|
|
10
10
|
// else → npm i -g
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { readFileSync, mkdirSync, writeFileSync, statSync, rmSync, appendFileSync } from 'node:fs';
|
|
13
13
|
import { exec as execCb, execSync } from 'node:child_process';
|
|
14
14
|
import { promisify } from 'node:util';
|
|
15
15
|
import { join, dirname } from 'node:path';
|
|
@@ -39,16 +39,15 @@ const LOG_MAX_ENTRIES = 50;
|
|
|
39
39
|
|
|
40
40
|
const SELF_PATH = fileURLToPath(import.meta.url);
|
|
41
41
|
|
|
42
|
-
function detectPM(
|
|
43
|
-
|
|
44
|
-
if (
|
|
45
|
-
if (normalizedPath.includes('/pnpm/')) return 'pnpm';
|
|
42
|
+
function detectPM() {
|
|
43
|
+
if (SELF_PATH.includes('/.volta/')) return 'volta';
|
|
44
|
+
if (SELF_PATH.includes('/pnpm/')) return 'pnpm';
|
|
46
45
|
return 'npm';
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
const PM = detectPM();
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
function getInstallCmd(version = 'latest') {
|
|
52
51
|
const spec = `${PKG_NAME}@${version}`;
|
|
53
52
|
if (PM === 'volta') return `volta install ${spec}`;
|
|
54
53
|
if (PM === 'pnpm') return `pnpm add -g ${spec}`;
|
|
@@ -92,36 +91,23 @@ function compareVersions(a, b) {
|
|
|
92
91
|
// After install, re-resolve where the package actually landed and read
|
|
93
92
|
// its package.json. Falls back to getLocalVersion() (our own bundle).
|
|
94
93
|
|
|
95
|
-
|
|
96
|
-
const packageManager = options.packageManager || PM;
|
|
97
|
-
const runner = options.execSync || execSync;
|
|
94
|
+
function getInstalledVersion() {
|
|
98
95
|
try {
|
|
99
|
-
if (
|
|
96
|
+
if (PM === 'volta') {
|
|
100
97
|
// Volta resolves via its own image; ask it directly
|
|
101
|
-
const resolved =
|
|
98
|
+
const resolved = execSync('volta which aw', {
|
|
102
99
|
encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 10_000,
|
|
103
100
|
}).trim();
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
return existsSync(join(packageRoot, 'package.json')) ? packageRoot : imageRoot;
|
|
101
|
+
const pkgRoot = join(resolved, '..', '..');
|
|
102
|
+
return JSON.parse(readFileSync(join(pkgRoot, 'package.json'), 'utf8')).version;
|
|
107
103
|
}
|
|
108
104
|
|
|
109
105
|
// npm / pnpm: read from the global prefix
|
|
110
|
-
const prefix =
|
|
106
|
+
const prefix = execSync('npm prefix -g', {
|
|
111
107
|
encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 10_000,
|
|
112
108
|
}).trim();
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
return join(prefix, 'lib', 'node_modules', PKG_NAME);
|
|
117
|
-
} catch {
|
|
118
|
-
return dirname(SELF_PATH);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function getInstalledVersion() {
|
|
123
|
-
try {
|
|
124
|
-
return JSON.parse(readFileSync(join(getInstalledPackageRoot(), 'package.json'), 'utf8')).version;
|
|
109
|
+
const pkgPath = join(prefix, 'lib', 'node_modules', PKG_NAME, 'package.json');
|
|
110
|
+
return JSON.parse(readFileSync(pkgPath, 'utf8')).version;
|
|
125
111
|
} catch {
|
|
126
112
|
return getLocalVersion();
|
|
127
113
|
}
|
|
@@ -214,8 +200,7 @@ export function autoUpdate(result) {
|
|
|
214
200
|
execSync(cmd, { stdio: 'pipe', timeout: 60_000 });
|
|
215
201
|
const installed = getInstalledVersion();
|
|
216
202
|
const ok = compareVersions(installed, result.current) > 0;
|
|
217
|
-
const
|
|
218
|
-
const entry = { ts: new Date().toISOString(), pm: PM, cmd, from: result.current, to: installed, packageRoot, ok };
|
|
203
|
+
const entry = { ts: new Date().toISOString(), pm: PM, cmd, from: result.current, to: installed, ok };
|
|
219
204
|
appendLog(entry);
|
|
220
205
|
return { status: ok ? 'upgraded' : 'failed', ...entry };
|
|
221
206
|
} catch (e) {
|