agentwheel 0.4.0 → 0.5.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/README.md +28 -2
- package/dist/index.js +188 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,9 +33,9 @@ No lock-in. No central gatekeeper. Your packages live in plain git repos, your c
|
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
36
|
-
> **Status: early (v0.
|
|
36
|
+
> **Status: early (v0.5).** The lifecycle core is real and tested — local/git/skillkit/vercel
|
|
37
37
|
> sources, optional registry discovery, plan/sync/update/drift/uninstall, overlays, eject/remember,
|
|
38
|
-
> profiles, runtime auto-detection, fleet targeting, rich JSON merge, and pluggable adapters.
|
|
38
|
+
> profiles, runtime auto-detection, fleet targeting, asset-includes, rich JSON merge, and pluggable adapters.
|
|
39
39
|
> Expect sharp edges.
|
|
40
40
|
|
|
41
41
|
## What it does
|
|
@@ -72,6 +72,9 @@ pnpm link --global
|
|
|
72
72
|
|
|
73
73
|
`plan`, `sync --dry-run`, and `update --dry-run` show exactly what would change before anything is written. They're the commands to trust.
|
|
74
74
|
|
|
75
|
+
`uninstall` removes clean managed files by default and keeps drifted files in place with a warning.
|
|
76
|
+
Use `agentwheel uninstall --force` only when you also want to remove drifted managed files.
|
|
77
|
+
|
|
75
78
|
## Runtime targeting
|
|
76
79
|
|
|
77
80
|
Normal use no longer needs `--target-root`. Run agentwheel inside a runtime folder and it detects
|
|
@@ -144,6 +147,28 @@ A package is a git repo (or folder) with a JSON manifest and a canonical layout:
|
|
|
144
147
|
}
|
|
145
148
|
```
|
|
146
149
|
|
|
150
|
+
Packages can compose shared files into each directory artifact at staging time. This keeps one
|
|
151
|
+
canonical copy in the package repo while installing self-contained skills:
|
|
152
|
+
|
|
153
|
+
```jsonc
|
|
154
|
+
{
|
|
155
|
+
"type": "skills",
|
|
156
|
+
"path": "skills",
|
|
157
|
+
"assets": [
|
|
158
|
+
{
|
|
159
|
+
"from": "packages/tmux-bridge/bin",
|
|
160
|
+
"into": "bin",
|
|
161
|
+
"include": ["*.sh"],
|
|
162
|
+
"mode": "preserve"
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`mode: "preserve"` keeps executable bits on copied scripts. The composed files are included in
|
|
169
|
+
the skill directory hash, so idempotency and drift detection work as if the assets had always
|
|
170
|
+
belonged to the skill.
|
|
171
|
+
|
|
147
172
|
Publish by pushing to any git host. A registry exists only for short names and discovery — it's
|
|
148
173
|
optional, and `agentwheel add <url|path>` always works without it.
|
|
149
174
|
|
|
@@ -218,6 +243,7 @@ clear conversion format.
|
|
|
218
243
|
- [x] **v0.2** — git source driver; `update` (pinned & tracking); overlays/additive/override/eject; `init`; hermes + copilot adapters; commands/mcp/hooks artifacts; OpenClaw semantic plugin planning.
|
|
219
244
|
- [x] **v0.3** — skillkit/vercel source drivers; optional registry & federation; programmatic adapters behind `--allow-adapter-code`; rich JSON merge for mcp/hooks/settings; profiles.
|
|
220
245
|
- [x] **v0.4** — runtime auto-detection; no `--target-root` needed for normal use; fleet config with named agents; global + project config merge; `--agent` and `--all`.
|
|
246
|
+
- [x] **v0.5** — asset-includes compose shared files into skills at install time; executable bits preserved; hashes include composed assets.
|
|
221
247
|
|
|
222
248
|
## Design docs
|
|
223
249
|
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,12 @@ var artifactTypeSchema = z.enum([
|
|
|
34
34
|
"plugins"
|
|
35
35
|
]);
|
|
36
36
|
var fileKindSchema = z.enum(["file", "dir"]);
|
|
37
|
+
var packageAssetSchema = z.object({
|
|
38
|
+
from: z.string().min(1),
|
|
39
|
+
into: z.string().min(1),
|
|
40
|
+
include: z.array(z.string().min(1)).optional(),
|
|
41
|
+
mode: z.enum(["preserve", "copy"]).default("preserve")
|
|
42
|
+
});
|
|
37
43
|
var artifactSchema = z.object({
|
|
38
44
|
type: artifactTypeSchema,
|
|
39
45
|
name: z.string().min(1),
|
|
@@ -43,7 +49,8 @@ var artifactSchema = z.object({
|
|
|
43
49
|
kind: fileKindSchema,
|
|
44
50
|
hash: z.string().min(16),
|
|
45
51
|
packageName: z.string().min(1).optional(),
|
|
46
|
-
channel: z.enum(["managed", "overlay", "addition", "override", "ejected"]).default("managed")
|
|
52
|
+
channel: z.enum(["managed", "overlay", "addition", "override", "ejected"]).default("managed"),
|
|
53
|
+
assets: z.array(packageAssetSchema).optional()
|
|
47
54
|
});
|
|
48
55
|
|
|
49
56
|
// src/model/adapter.ts
|
|
@@ -461,16 +468,52 @@ async function applyInstallPlan(plan, sourceLock, options = {}) {
|
|
|
461
468
|
await writeSourceLock(plan.targetRoot, plan.adapter, sourceLock);
|
|
462
469
|
return manifest;
|
|
463
470
|
}
|
|
464
|
-
async function uninstall(plan,
|
|
471
|
+
async function uninstall(plan, options = {}) {
|
|
472
|
+
const resolvedOptions = typeof options === "boolean" ? { dryRun: options } : options;
|
|
465
473
|
if (plan.hasBlockingChanges) {
|
|
466
|
-
const blockers = plan.operations.filter((operation) => operation.action === "
|
|
467
|
-
throw new Error(`Refusing to uninstall with
|
|
474
|
+
const blockers = plan.operations.filter((operation) => operation.action === "conflict");
|
|
475
|
+
throw new Error(`Refusing to uninstall with blocking changes: ${blockers.map((item) => item.relativeDestPath).join(", ")}`);
|
|
468
476
|
}
|
|
469
|
-
|
|
477
|
+
const removable = plan.operations.filter((operation) => operation.action === "remove" || resolvedOptions.force && operation.action === "keep");
|
|
478
|
+
const kept = resolvedOptions.force ? [] : plan.operations.filter((operation) => operation.action === "keep");
|
|
479
|
+
const removedDrifted = resolvedOptions.force ? plan.operations.filter((operation) => operation.action === "keep").length : 0;
|
|
480
|
+
if (resolvedOptions.dryRun) return { removed: removable.length, kept: kept.length, removedDrifted };
|
|
470
481
|
for (const operation of plan.operations) {
|
|
471
|
-
|
|
482
|
+
if (operation.action === "remove" || resolvedOptions.force && operation.action === "keep") {
|
|
483
|
+
await rm2(operation.destPath, { recursive: true, force: true });
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (kept.length > 0) {
|
|
487
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
488
|
+
await writeInstallManifest({
|
|
489
|
+
version: 1,
|
|
490
|
+
adapter: plan.adapter,
|
|
491
|
+
targetRoot: plan.targetRoot,
|
|
492
|
+
generatedAt: now,
|
|
493
|
+
adapterCode: plan.adapterCode,
|
|
494
|
+
entries: kept.map((operation) => {
|
|
495
|
+
if (!operation.manifestHash || !operation.desiredHash) {
|
|
496
|
+
throw new Error(`Invalid kept operation missing manifest/source hash: ${operation.relativeDestPath}`);
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
path: operation.relativeDestPath,
|
|
500
|
+
artifactType: operation.artifactType,
|
|
501
|
+
artifactName: operation.artifactName,
|
|
502
|
+
kind: operation.kind,
|
|
503
|
+
hash: operation.manifestHash,
|
|
504
|
+
sourceHash: operation.desiredHash,
|
|
505
|
+
updatedAt: now,
|
|
506
|
+
channel: operation.channel,
|
|
507
|
+
packageName: operation.packageName,
|
|
508
|
+
semanticCommand: operation.semanticCommand,
|
|
509
|
+
mergeStrategy: operation.mergeStrategy
|
|
510
|
+
};
|
|
511
|
+
}).sort((a, b) => a.path.localeCompare(b.path))
|
|
512
|
+
});
|
|
513
|
+
} else {
|
|
514
|
+
await removeStateFiles(plan.targetRoot, plan.adapter);
|
|
472
515
|
}
|
|
473
|
-
|
|
516
|
+
return { removed: removable.length, kept: kept.length, removedDrifted };
|
|
474
517
|
}
|
|
475
518
|
|
|
476
519
|
// src/install/plan.ts
|
|
@@ -658,6 +701,7 @@ function summarizePlan(plan) {
|
|
|
658
701
|
update: 0,
|
|
659
702
|
skip: 0,
|
|
660
703
|
remove: 0,
|
|
704
|
+
keep: 0,
|
|
661
705
|
drift: 0,
|
|
662
706
|
conflict: 0,
|
|
663
707
|
plugin: 0,
|
|
@@ -679,17 +723,20 @@ async function createUninstallPlan(manifest) {
|
|
|
679
723
|
const currentHash = await hashPath(destPath);
|
|
680
724
|
if (currentHash !== entry.hash) {
|
|
681
725
|
operations.push({
|
|
682
|
-
action: "
|
|
726
|
+
action: "keep",
|
|
683
727
|
artifactType: entry.artifactType,
|
|
684
728
|
artifactName: entry.artifactName,
|
|
685
729
|
kind: entry.kind,
|
|
686
730
|
destPath,
|
|
687
731
|
relativeDestPath: entry.path,
|
|
732
|
+
desiredHash: entry.sourceHash,
|
|
688
733
|
currentHash,
|
|
689
734
|
manifestHash: entry.hash,
|
|
690
|
-
reason: "managed destination changed outside agentwheel",
|
|
735
|
+
reason: "managed destination changed outside agentwheel; keeping by default",
|
|
691
736
|
channel: entry.channel,
|
|
692
|
-
packageName: entry.packageName
|
|
737
|
+
packageName: entry.packageName,
|
|
738
|
+
semanticCommand: entry.semanticCommand,
|
|
739
|
+
mergeStrategy: entry.mergeStrategy
|
|
693
740
|
});
|
|
694
741
|
} else {
|
|
695
742
|
operations.push({
|
|
@@ -712,7 +759,8 @@ async function createUninstallPlan(manifest) {
|
|
|
712
759
|
adapter: manifest.adapter,
|
|
713
760
|
targetRoot: manifest.targetRoot,
|
|
714
761
|
operations,
|
|
715
|
-
hasBlockingChanges: operations.some((operation) => operation.action === "
|
|
762
|
+
hasBlockingChanges: operations.some((operation) => operation.action === "conflict"),
|
|
763
|
+
adapterCode: manifest.adapterCode
|
|
716
764
|
};
|
|
717
765
|
}
|
|
718
766
|
|
|
@@ -722,6 +770,7 @@ var labels = {
|
|
|
722
770
|
update: "UPDATE",
|
|
723
771
|
skip: "SKIP",
|
|
724
772
|
remove: "REMOVE",
|
|
773
|
+
keep: "KEEP",
|
|
725
774
|
drift: "DRIFT",
|
|
726
775
|
conflict: "CONFLICT",
|
|
727
776
|
plugin: "PLUGIN",
|
|
@@ -743,7 +792,7 @@ function formatPlan(plan) {
|
|
|
743
792
|
}
|
|
744
793
|
const summary = summarizePlan(plan);
|
|
745
794
|
lines.push(
|
|
746
|
-
`Summary: create ${summary.create}, update ${summary.update}, skip ${summary.skip}, remove ${summary.remove}, drift ${summary.drift}, conflict ${summary.conflict}, plugin ${summary.plugin}`
|
|
795
|
+
`Summary: create ${summary.create}, update ${summary.update}, skip ${summary.skip}, remove ${summary.remove}, keep ${summary.keep}, drift ${summary.drift}, conflict ${summary.conflict}, plugin ${summary.plugin}`
|
|
747
796
|
);
|
|
748
797
|
return lines.join("\n");
|
|
749
798
|
}
|
|
@@ -762,7 +811,8 @@ import { parse as parse2, printParseErrorCode as printParseErrorCode2 } from "js
|
|
|
762
811
|
import { z as z4 } from "zod";
|
|
763
812
|
var packageProvideSchema = z4.object({
|
|
764
813
|
type: artifactTypeSchema,
|
|
765
|
-
path: z4.string().min(1)
|
|
814
|
+
path: z4.string().min(1),
|
|
815
|
+
assets: z4.array(packageAssetSchema).optional()
|
|
766
816
|
});
|
|
767
817
|
var packageManifestSchema = z4.object({
|
|
768
818
|
schemaVersion: z4.literal(1),
|
|
@@ -931,7 +981,7 @@ async function listFromManifest(root, packageName) {
|
|
|
931
981
|
const stats = await stat2(full);
|
|
932
982
|
if (provide.type === "instructions") {
|
|
933
983
|
if (stats.isFile()) {
|
|
934
|
-
artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName));
|
|
984
|
+
artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName, provide.assets));
|
|
935
985
|
}
|
|
936
986
|
continue;
|
|
937
987
|
}
|
|
@@ -939,16 +989,16 @@ async function listFromManifest(root, packageName) {
|
|
|
939
989
|
for (const entry of await sortedDirEntries(full)) {
|
|
940
990
|
const child = join6(full, entry.name);
|
|
941
991
|
if (provide.type === "skills" && entry.isDirectory()) {
|
|
942
|
-
artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName));
|
|
992
|
+
artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName, provide.assets));
|
|
943
993
|
} else if (provide.type === "plugins" && entry.isDirectory()) {
|
|
944
|
-
artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName));
|
|
994
|
+
artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName, provide.assets));
|
|
945
995
|
} else if (entry.isFile()) {
|
|
946
996
|
const name = provide.type === "rules" && entry.name.endsWith(".md") ? entry.name : entry.name;
|
|
947
|
-
artifacts.push(await artifactForFile(provide.type, name, child, join6(provide.path, entry.name), packageName));
|
|
997
|
+
artifacts.push(await artifactForFile(provide.type, name, child, join6(provide.path, entry.name), packageName, provide.assets));
|
|
948
998
|
}
|
|
949
999
|
}
|
|
950
1000
|
} else if (stats.isFile()) {
|
|
951
|
-
artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName));
|
|
1001
|
+
artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName, provide.assets));
|
|
952
1002
|
}
|
|
953
1003
|
}
|
|
954
1004
|
return artifacts;
|
|
@@ -965,7 +1015,7 @@ async function listGenericArtifacts(type, dir, relativeRoot, packageName) {
|
|
|
965
1015
|
}
|
|
966
1016
|
return artifacts;
|
|
967
1017
|
}
|
|
968
|
-
async function artifactForFile(type, name, sourcePath, relativePath, packageName) {
|
|
1018
|
+
async function artifactForFile(type, name, sourcePath, relativePath, packageName, assets) {
|
|
969
1019
|
return {
|
|
970
1020
|
type,
|
|
971
1021
|
name,
|
|
@@ -974,10 +1024,11 @@ async function artifactForFile(type, name, sourcePath, relativePath, packageName
|
|
|
974
1024
|
kind: "file",
|
|
975
1025
|
hash: await hashPath(sourcePath),
|
|
976
1026
|
packageName,
|
|
977
|
-
channel: "managed"
|
|
1027
|
+
channel: "managed",
|
|
1028
|
+
assets
|
|
978
1029
|
};
|
|
979
1030
|
}
|
|
980
|
-
async function artifactForDir(type, name, sourcePath, relativePath, packageName) {
|
|
1031
|
+
async function artifactForDir(type, name, sourcePath, relativePath, packageName, assets) {
|
|
981
1032
|
return {
|
|
982
1033
|
type,
|
|
983
1034
|
name,
|
|
@@ -986,7 +1037,8 @@ async function artifactForDir(type, name, sourcePath, relativePath, packageName)
|
|
|
986
1037
|
kind: "dir",
|
|
987
1038
|
hash: await hashPath(sourcePath),
|
|
988
1039
|
packageName,
|
|
989
|
-
channel: "managed"
|
|
1040
|
+
channel: "managed",
|
|
1041
|
+
assets
|
|
990
1042
|
};
|
|
991
1043
|
}
|
|
992
1044
|
|
|
@@ -1421,8 +1473,8 @@ function getSourceDriver(name = "local") {
|
|
|
1421
1473
|
}
|
|
1422
1474
|
|
|
1423
1475
|
// src/staging/staging.ts
|
|
1424
|
-
import { cp as cp3, mkdir as mkdir5, mkdtemp } from "fs/promises";
|
|
1425
|
-
import { dirname as dirname5, join as join12 } from "path";
|
|
1476
|
+
import { chmod, cp as cp3, mkdir as mkdir5, mkdtemp, readdir as readdir4, stat as stat6 } from "fs/promises";
|
|
1477
|
+
import { basename as basename7, dirname as dirname5, join as join12, relative as relative2, resolve as resolve8, sep } from "path";
|
|
1426
1478
|
import { tmpdir as tmpdir2 } from "os";
|
|
1427
1479
|
|
|
1428
1480
|
// src/staging/customize.ts
|
|
@@ -1541,6 +1593,7 @@ async function stageSource(driver, source, options = {}) {
|
|
|
1541
1593
|
const stagedPath = join12(root, artifact.relativePath);
|
|
1542
1594
|
await mkdir5(dirname5(stagedPath), { recursive: true });
|
|
1543
1595
|
await cp3(artifact.sourcePath, stagedPath, { recursive: artifact.kind === "dir", dereference: true });
|
|
1596
|
+
await composeAssets(artifact, resolved.resolvedPath, stagedPath);
|
|
1544
1597
|
stagedArtifacts.push({
|
|
1545
1598
|
...artifact,
|
|
1546
1599
|
stagedPath,
|
|
@@ -1581,11 +1634,93 @@ async function stageSource(driver, source, options = {}) {
|
|
|
1581
1634
|
}
|
|
1582
1635
|
};
|
|
1583
1636
|
}
|
|
1637
|
+
async function composeAssets(artifact, packageRoot, stagedPath) {
|
|
1638
|
+
if (!artifact.assets?.length) return;
|
|
1639
|
+
if (artifact.kind !== "dir") {
|
|
1640
|
+
throw new Error(`Asset includes require a directory artifact: ${artifact.type}/${artifact.name}`);
|
|
1641
|
+
}
|
|
1642
|
+
for (const asset of artifact.assets) {
|
|
1643
|
+
const source = resolvePackagePath(packageRoot, asset.from);
|
|
1644
|
+
const dest = join12(stagedPath, asset.into);
|
|
1645
|
+
await copyAsset(asset, source, dest);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
async function copyAsset(asset, source, dest) {
|
|
1649
|
+
const sourceStats = await stat6(source);
|
|
1650
|
+
if (sourceStats.isFile()) {
|
|
1651
|
+
if (matchesAny(basename7(source), asset.include)) {
|
|
1652
|
+
await mkdir5(dest, { recursive: true });
|
|
1653
|
+
await copyAssetFile(source, join12(dest, basename7(source)), asset);
|
|
1654
|
+
}
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
if (!sourceStats.isDirectory()) {
|
|
1658
|
+
throw new Error(`Asset include source is not a file or directory: ${source}`);
|
|
1659
|
+
}
|
|
1660
|
+
if (!asset.include?.length) {
|
|
1661
|
+
await mkdir5(dirname5(dest), { recursive: true });
|
|
1662
|
+
await cp3(source, dest, { recursive: true, dereference: true });
|
|
1663
|
+
if (asset.mode === "copy") await normalizeCopiedModes(dest);
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
for (const file of await listFiles(source)) {
|
|
1667
|
+
const rel = relative2(source, file).replaceAll("\\", "/");
|
|
1668
|
+
if (!matchesAny(rel, asset.include) && !matchesAny(basename7(file), asset.include)) continue;
|
|
1669
|
+
await copyAssetFile(file, join12(dest, rel), asset);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
async function copyAssetFile(source, dest, asset) {
|
|
1673
|
+
await mkdir5(dirname5(dest), { recursive: true });
|
|
1674
|
+
await cp3(source, dest, { dereference: true });
|
|
1675
|
+
if (asset.mode === "copy") await chmod(dest, 420);
|
|
1676
|
+
}
|
|
1677
|
+
function resolvePackagePath(packageRoot, path) {
|
|
1678
|
+
const resolved = resolve8(packageRoot, path);
|
|
1679
|
+
const root = resolve8(packageRoot);
|
|
1680
|
+
if (resolved !== root && !resolved.startsWith(`${root}${sep}`)) {
|
|
1681
|
+
throw new Error(`Asset include escapes package root: ${path}`);
|
|
1682
|
+
}
|
|
1683
|
+
return resolved;
|
|
1684
|
+
}
|
|
1685
|
+
async function listFiles(root) {
|
|
1686
|
+
const out = [];
|
|
1687
|
+
async function walk2(dir) {
|
|
1688
|
+
for (const entry of (await readdir4(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1689
|
+
const full = join12(dir, entry.name);
|
|
1690
|
+
if (entry.isDirectory()) {
|
|
1691
|
+
await walk2(full);
|
|
1692
|
+
} else if (entry.isFile()) {
|
|
1693
|
+
out.push(full);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
await walk2(root);
|
|
1698
|
+
return out;
|
|
1699
|
+
}
|
|
1700
|
+
async function normalizeCopiedModes(path) {
|
|
1701
|
+
const stats = await stat6(path);
|
|
1702
|
+
if (stats.isFile()) {
|
|
1703
|
+
await chmod(path, 420);
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
if (!stats.isDirectory()) return;
|
|
1707
|
+
for (const entry of await readdir4(path, { withFileTypes: true })) {
|
|
1708
|
+
await normalizeCopiedModes(join12(path, entry.name));
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
function matchesAny(path, patterns) {
|
|
1712
|
+
if (!patterns?.length) return true;
|
|
1713
|
+
return patterns.some((pattern) => matchesGlob(path, pattern));
|
|
1714
|
+
}
|
|
1715
|
+
function matchesGlob(path, pattern) {
|
|
1716
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1717
|
+
return new RegExp(`^${escaped}$`).test(path);
|
|
1718
|
+
}
|
|
1584
1719
|
|
|
1585
1720
|
// src/model/workspace.ts
|
|
1586
1721
|
import { readFile as readFile8 } from "fs/promises";
|
|
1587
1722
|
import { homedir as homedir3 } from "os";
|
|
1588
|
-
import { dirname as dirname6, join as join13, resolve as
|
|
1723
|
+
import { dirname as dirname6, join as join13, resolve as resolve9 } from "path";
|
|
1589
1724
|
import { z as z5 } from "zod";
|
|
1590
1725
|
var workspacePackageSchema = z5.object({
|
|
1591
1726
|
name: z5.string().min(1),
|
|
@@ -1645,11 +1780,11 @@ function globalWorkspaceConfigPath(globalRoot = homedir3()) {
|
|
|
1645
1780
|
return join13(globalRoot, ".agentwheel", "config.json");
|
|
1646
1781
|
}
|
|
1647
1782
|
async function findWorkspaceRoot(start = process.cwd()) {
|
|
1648
|
-
let current =
|
|
1783
|
+
let current = resolve9(start);
|
|
1649
1784
|
while (true) {
|
|
1650
1785
|
if (await pathExists(workspaceConfigPath(current))) return current;
|
|
1651
1786
|
const parent = dirname6(current);
|
|
1652
|
-
if (parent === current) return
|
|
1787
|
+
if (parent === current) return resolve9(start);
|
|
1653
1788
|
current = parent;
|
|
1654
1789
|
}
|
|
1655
1790
|
}
|
|
@@ -1673,9 +1808,9 @@ function mergeWorkspaceConfig(global, project) {
|
|
|
1673
1808
|
});
|
|
1674
1809
|
}
|
|
1675
1810
|
function resolveConfigPath(path, baseRoot) {
|
|
1676
|
-
if (path.startsWith("~/")) return
|
|
1811
|
+
if (path.startsWith("~/")) return resolve9(homedir3(), path.slice(2));
|
|
1677
1812
|
if (path === "~") return homedir3();
|
|
1678
|
-
return path.startsWith("/") ?
|
|
1813
|
+
return path.startsWith("/") ? resolve9(path) : resolve9(baseRoot, path);
|
|
1679
1814
|
}
|
|
1680
1815
|
function emptyWorkspaceConfig() {
|
|
1681
1816
|
return { schemaVersion: 1, packages: [], registry: {}, profiles: {}, agents: {} };
|
|
@@ -1739,9 +1874,9 @@ import { rm as rm7 } from "fs/promises";
|
|
|
1739
1874
|
import { join as join16 } from "path";
|
|
1740
1875
|
|
|
1741
1876
|
// src/registry/client.ts
|
|
1742
|
-
import { readFile as readFile9, rm as rm6, stat as
|
|
1877
|
+
import { readFile as readFile9, rm as rm6, stat as stat7 } from "fs/promises";
|
|
1743
1878
|
import { homedir as homedir4 } from "os";
|
|
1744
|
-
import { dirname as dirname8, join as join15, resolve as
|
|
1879
|
+
import { dirname as dirname8, join as join15, resolve as resolve10 } from "path";
|
|
1745
1880
|
import { fileURLToPath } from "url";
|
|
1746
1881
|
|
|
1747
1882
|
// src/model/registry.ts
|
|
@@ -1844,8 +1979,8 @@ var RegistryClient = class {
|
|
|
1844
1979
|
}
|
|
1845
1980
|
const filePath = source.startsWith("file:") ? fileURLToPath(source) : source;
|
|
1846
1981
|
if (await pathExists(filePath)) {
|
|
1847
|
-
const fullPath =
|
|
1848
|
-
const stats = await
|
|
1982
|
+
const fullPath = resolve10(filePath);
|
|
1983
|
+
const stats = await stat7(fullPath);
|
|
1849
1984
|
return readFile9(stats.isDirectory() ? join15(fullPath, "index.json") : fullPath, "utf8");
|
|
1850
1985
|
}
|
|
1851
1986
|
const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join15(dirname8(this.cachePath), "registry-repos") }));
|
|
@@ -1979,7 +2114,7 @@ function shouldUpdatePackage(pkg, lock) {
|
|
|
1979
2114
|
}
|
|
1980
2115
|
|
|
1981
2116
|
// src/runtime/target.ts
|
|
1982
|
-
import { basename as
|
|
2117
|
+
import { basename as basename8, dirname as dirname9, join as join18, resolve as resolve11 } from "path";
|
|
1983
2118
|
var runtimeMarkers = [
|
|
1984
2119
|
{ adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
|
|
1985
2120
|
{ adapter: "claude", dirs: [".claude"] },
|
|
@@ -1988,9 +2123,9 @@ var runtimeMarkers = [
|
|
|
1988
2123
|
{ adapter: "copilot", dirs: [".github"] }
|
|
1989
2124
|
];
|
|
1990
2125
|
async function resolveRuntimeTarget(request = {}) {
|
|
1991
|
-
const cwd =
|
|
2126
|
+
const cwd = resolve11(request.cwd ?? process.cwd());
|
|
1992
2127
|
if (request.targetRoot) {
|
|
1993
|
-
const targetRoot =
|
|
2128
|
+
const targetRoot = resolve11(request.targetRoot);
|
|
1994
2129
|
return {
|
|
1995
2130
|
adapter: request.adapter ?? "openclaw",
|
|
1996
2131
|
targetRoot,
|
|
@@ -2017,7 +2152,7 @@ async function resolveRuntimeTarget(request = {}) {
|
|
|
2017
2152
|
async function resolveAllRuntimeTargets(request = {}) {
|
|
2018
2153
|
if (request.targetRoot) return [await resolveRuntimeTarget(request)];
|
|
2019
2154
|
if (request.agent) return [await resolveRuntimeTarget(request)];
|
|
2020
|
-
const cwd =
|
|
2155
|
+
const cwd = resolve11(request.cwd ?? process.cwd());
|
|
2021
2156
|
const workspaceRoot = await findWorkspaceRoot(cwd);
|
|
2022
2157
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
2023
2158
|
const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot));
|
|
@@ -2027,12 +2162,12 @@ async function resolveAllRuntimeTargets(request = {}) {
|
|
|
2027
2162
|
return targets;
|
|
2028
2163
|
}
|
|
2029
2164
|
async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
|
|
2030
|
-
const root =
|
|
2165
|
+
const root = resolve11(cwd);
|
|
2031
2166
|
const matches = [];
|
|
2032
2167
|
for (const marker of runtimeMarkers) {
|
|
2033
2168
|
if (adapterFilter && marker.adapter !== adapterFilter) continue;
|
|
2034
2169
|
for (const dir of marker.dirs) {
|
|
2035
|
-
if (
|
|
2170
|
+
if (basename8(root) === dir) {
|
|
2036
2171
|
matches.push({ adapter: marker.adapter, targetRoot: dirname9(root) });
|
|
2037
2172
|
} else if (await pathExists(join18(root, dir))) {
|
|
2038
2173
|
matches.push({ adapter: marker.adapter, targetRoot: root });
|
|
@@ -2068,7 +2203,7 @@ function dedupeTargets(matches) {
|
|
|
2068
2203
|
|
|
2069
2204
|
// src/cli/index.ts
|
|
2070
2205
|
var program = new Command();
|
|
2071
|
-
program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version("0.
|
|
2206
|
+
program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version("0.5.0");
|
|
2072
2207
|
program.command("init").argument("[kind]", "workspace or package", "workspace").option("--target-root <path>", "workspace root", process.cwd()).action(async (kind, options) => {
|
|
2073
2208
|
const root = normalizeTargetRoot(options.targetRoot);
|
|
2074
2209
|
if (kind === "package") {
|
|
@@ -2225,7 +2360,7 @@ program.command("eject").argument("<item>", "package/type/name").option("--targe
|
|
|
2225
2360
|
const result = await ejectArtifact(targetRoot, item);
|
|
2226
2361
|
console.log(`Ejected ${item} to ${result.ejectedPath}.`);
|
|
2227
2362
|
});
|
|
2228
|
-
program.command("uninstall").option("--adapter <adapter>", "adapter", "openclaw").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show removals without writing", false).action(async (options) => {
|
|
2363
|
+
program.command("uninstall").option("--adapter <adapter>", "adapter", "openclaw").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show removals without writing", false).option("--force", "remove drifted managed files too", false).action(async (options) => {
|
|
2229
2364
|
const targets = await resolveCliTargets(options);
|
|
2230
2365
|
for (const target of targets) {
|
|
2231
2366
|
const adapter = await resolveAdapterForTarget(target, options);
|
|
@@ -2236,10 +2371,10 @@ program.command("uninstall").option("--adapter <adapter>", "adapter", "openclaw"
|
|
|
2236
2371
|
}
|
|
2237
2372
|
const plan = await createUninstallPlan(manifest);
|
|
2238
2373
|
console.log(formatPlan(plan));
|
|
2239
|
-
await uninstall(plan, options.dryRun);
|
|
2374
|
+
const result = await uninstall(plan, { dryRun: options.dryRun, force: options.force });
|
|
2240
2375
|
if (!options.dryRun) {
|
|
2241
2376
|
await adapter.programmatic?.uninstall?.({ targetRoot: target.targetRoot, adapterName: adapter.name });
|
|
2242
|
-
console.log(
|
|
2377
|
+
console.log(formatUninstallResult(result));
|
|
2243
2378
|
}
|
|
2244
2379
|
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
2245
2380
|
}
|
|
@@ -2329,6 +2464,16 @@ async function initPackage(root) {
|
|
|
2329
2464
|
`, "utf8");
|
|
2330
2465
|
await writeFile4(join19(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
|
|
2331
2466
|
}
|
|
2467
|
+
function formatUninstallResult(result) {
|
|
2468
|
+
const removedLabel = result.removed === 1 ? "managed file" : "managed files";
|
|
2469
|
+
if (result.removedDrifted > 0) {
|
|
2470
|
+
const driftedLabel = result.removedDrifted === 1 ? "drifted file" : "drifted files";
|
|
2471
|
+
return `Removed ${result.removed} ${removedLabel}, including ${result.removedDrifted} ${driftedLabel}.`;
|
|
2472
|
+
}
|
|
2473
|
+
if (result.kept === 0) return `Removed ${result.removed} ${removedLabel}.`;
|
|
2474
|
+
const keptLabel = result.kept === 1 ? "drifted file" : "drifted files";
|
|
2475
|
+
return `Removed ${result.removed} ${removedLabel}; kept ${result.kept} ${keptLabel} (use --force to remove).`;
|
|
2476
|
+
}
|
|
2332
2477
|
function printRegistryEntries(entries) {
|
|
2333
2478
|
for (const entry of entries) {
|
|
2334
2479
|
const tags = entry.tags?.length ? ` [${entry.tags.join(",")}]` : "";
|