agentwheel 0.4.1 → 0.6.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 +53 -2
- package/dist/index.js +343 -63
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,9 +33,10 @@ 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.6).** 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,
|
|
38
|
+
> profiles, runtime auto-detection, fleet targeting, asset-includes, selective installs,
|
|
39
|
+
> update notifications, rich JSON merge, and pluggable adapters.
|
|
39
40
|
> Expect sharp edges.
|
|
40
41
|
|
|
41
42
|
## What it does
|
|
@@ -75,6 +76,10 @@ pnpm link --global
|
|
|
75
76
|
`uninstall` removes clean managed files by default and keeps drifted files in place with a warning.
|
|
76
77
|
Use `agentwheel uninstall --force` only when you also want to remove drifted managed files.
|
|
77
78
|
|
|
79
|
+
agentwheel checks npm for newer versions at most once every 24 hours and prints a non-blocking
|
|
80
|
+
stderr warning when an update is available. Disable it with `--no-update-check` or
|
|
81
|
+
`AGENTWHEEL_NO_UPDATE_CHECK=1`.
|
|
82
|
+
|
|
78
83
|
## Runtime targeting
|
|
79
84
|
|
|
80
85
|
Normal use no longer needs `--target-root`. Run agentwheel inside a runtime folder and it detects
|
|
@@ -147,6 +152,50 @@ A package is a git repo (or folder) with a JSON manifest and a canonical layout:
|
|
|
147
152
|
}
|
|
148
153
|
```
|
|
149
154
|
|
|
155
|
+
Install only part of a package with `--select <type>/<name>`. `--skill <name>` is a shortcut for
|
|
156
|
+
`--select skills/<name>`, and selections saved during `add` are reused by later `sync` and `update`
|
|
157
|
+
runs.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
agentwheel add github:NestDevLab/agent-mesh --skill codex-tmux --adapter openclaw
|
|
161
|
+
agentwheel sync --dry-run
|
|
162
|
+
|
|
163
|
+
agentwheel sync github:your-org/agent-pack --select rules/safe-actions.md --select commands/build.md
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Package authors can mark dependencies as required. Required artifacts are always installed and
|
|
167
|
+
cannot be deselected:
|
|
168
|
+
|
|
169
|
+
```jsonc
|
|
170
|
+
{
|
|
171
|
+
"type": "rules",
|
|
172
|
+
"path": "rules/core-safety.md",
|
|
173
|
+
"required": true
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Packages can compose shared files into each directory artifact at staging time. This keeps one
|
|
178
|
+
canonical copy in the package repo while installing self-contained skills:
|
|
179
|
+
|
|
180
|
+
```jsonc
|
|
181
|
+
{
|
|
182
|
+
"type": "skills",
|
|
183
|
+
"path": "skills",
|
|
184
|
+
"assets": [
|
|
185
|
+
{
|
|
186
|
+
"from": "packages/tmux-bridge/bin",
|
|
187
|
+
"into": "bin",
|
|
188
|
+
"include": ["*.sh"],
|
|
189
|
+
"mode": "preserve"
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`mode: "preserve"` keeps executable bits on copied scripts. The composed files are included in
|
|
196
|
+
the skill directory hash, so idempotency and drift detection work as if the assets had always
|
|
197
|
+
belonged to the skill.
|
|
198
|
+
|
|
150
199
|
Publish by pushing to any git host. A registry exists only for short names and discovery — it's
|
|
151
200
|
optional, and `agentwheel add <url|path>` always works without it.
|
|
152
201
|
|
|
@@ -221,6 +270,8 @@ clear conversion format.
|
|
|
221
270
|
- [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.
|
|
222
271
|
- [x] **v0.3** — skillkit/vercel source drivers; optional registry & federation; programmatic adapters behind `--allow-adapter-code`; rich JSON merge for mcp/hooks/settings; profiles.
|
|
223
272
|
- [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`.
|
|
273
|
+
- [x] **v0.5** — asset-includes compose shared files into skills at install time; executable bits preserved; hashes include composed assets.
|
|
274
|
+
- [x] **v0.6** — selective installs with `--select`/`--skill`; required artifacts; cached npm update notifier.
|
|
224
275
|
|
|
225
276
|
## Design docs
|
|
226
277
|
|
package/dist/index.js
CHANGED
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
} from "./chunk-N2LZY7LO.js";
|
|
9
9
|
|
|
10
10
|
// src/cli/index.ts
|
|
11
|
-
import { mkdir as
|
|
12
|
-
import { join as
|
|
11
|
+
import { mkdir as mkdir8, rm as rm8, writeFile as writeFile5 } from "fs/promises";
|
|
12
|
+
import { join as join20 } from "path";
|
|
13
13
|
import { Command } from "commander";
|
|
14
14
|
|
|
15
15
|
// src/adapters/resolve.ts
|
|
@@ -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,9 @@ 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(),
|
|
54
|
+
required: z.boolean().optional()
|
|
47
55
|
});
|
|
48
56
|
|
|
49
57
|
// src/model/adapter.ts
|
|
@@ -469,6 +477,7 @@ async function uninstall(plan, options = {}) {
|
|
|
469
477
|
}
|
|
470
478
|
const removable = plan.operations.filter((operation) => operation.action === "remove" || resolvedOptions.force && operation.action === "keep");
|
|
471
479
|
const kept = resolvedOptions.force ? [] : plan.operations.filter((operation) => operation.action === "keep");
|
|
480
|
+
const skipped = plan.operations.filter((operation) => operation.action === "skip");
|
|
472
481
|
const removedDrifted = resolvedOptions.force ? plan.operations.filter((operation) => operation.action === "keep").length : 0;
|
|
473
482
|
if (resolvedOptions.dryRun) return { removed: removable.length, kept: kept.length, removedDrifted };
|
|
474
483
|
for (const operation of plan.operations) {
|
|
@@ -476,7 +485,8 @@ async function uninstall(plan, options = {}) {
|
|
|
476
485
|
await rm2(operation.destPath, { recursive: true, force: true });
|
|
477
486
|
}
|
|
478
487
|
}
|
|
479
|
-
|
|
488
|
+
const preserved = [...kept, ...skipped];
|
|
489
|
+
if (preserved.length > 0) {
|
|
480
490
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
481
491
|
await writeInstallManifest({
|
|
482
492
|
version: 1,
|
|
@@ -484,9 +494,9 @@ async function uninstall(plan, options = {}) {
|
|
|
484
494
|
targetRoot: plan.targetRoot,
|
|
485
495
|
generatedAt: now,
|
|
486
496
|
adapterCode: plan.adapterCode,
|
|
487
|
-
entries:
|
|
497
|
+
entries: preserved.map((operation) => {
|
|
488
498
|
if (!operation.manifestHash || !operation.desiredHash) {
|
|
489
|
-
throw new Error(`Invalid
|
|
499
|
+
throw new Error(`Invalid preserved operation missing manifest/source hash: ${operation.relativeDestPath}`);
|
|
490
500
|
}
|
|
491
501
|
return {
|
|
492
502
|
path: operation.relativeDestPath,
|
|
@@ -739,11 +749,14 @@ async function createUninstallPlan(manifest) {
|
|
|
739
749
|
kind: entry.kind,
|
|
740
750
|
destPath,
|
|
741
751
|
relativeDestPath: entry.path,
|
|
752
|
+
desiredHash: entry.sourceHash,
|
|
742
753
|
currentHash,
|
|
743
754
|
manifestHash: entry.hash,
|
|
744
755
|
reason: "uninstall managed artifact",
|
|
745
756
|
channel: entry.channel,
|
|
746
|
-
packageName: entry.packageName
|
|
757
|
+
packageName: entry.packageName,
|
|
758
|
+
semanticCommand: entry.semanticCommand,
|
|
759
|
+
mergeStrategy: entry.mergeStrategy
|
|
747
760
|
});
|
|
748
761
|
}
|
|
749
762
|
}
|
|
@@ -804,7 +817,9 @@ import { parse as parse2, printParseErrorCode as printParseErrorCode2 } from "js
|
|
|
804
817
|
import { z as z4 } from "zod";
|
|
805
818
|
var packageProvideSchema = z4.object({
|
|
806
819
|
type: artifactTypeSchema,
|
|
807
|
-
path: z4.string().min(1)
|
|
820
|
+
path: z4.string().min(1),
|
|
821
|
+
assets: z4.array(packageAssetSchema).optional(),
|
|
822
|
+
required: z4.boolean().optional()
|
|
808
823
|
});
|
|
809
824
|
var packageManifestSchema = z4.object({
|
|
810
825
|
schemaVersion: z4.literal(1),
|
|
@@ -973,7 +988,7 @@ async function listFromManifest(root, packageName) {
|
|
|
973
988
|
const stats = await stat2(full);
|
|
974
989
|
if (provide.type === "instructions") {
|
|
975
990
|
if (stats.isFile()) {
|
|
976
|
-
artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName));
|
|
991
|
+
artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName, provide.assets, provide.required));
|
|
977
992
|
}
|
|
978
993
|
continue;
|
|
979
994
|
}
|
|
@@ -981,16 +996,16 @@ async function listFromManifest(root, packageName) {
|
|
|
981
996
|
for (const entry of await sortedDirEntries(full)) {
|
|
982
997
|
const child = join6(full, entry.name);
|
|
983
998
|
if (provide.type === "skills" && entry.isDirectory()) {
|
|
984
|
-
artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName));
|
|
999
|
+
artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName, provide.assets, provide.required));
|
|
985
1000
|
} else if (provide.type === "plugins" && entry.isDirectory()) {
|
|
986
|
-
artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName));
|
|
1001
|
+
artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName, provide.assets, provide.required));
|
|
987
1002
|
} else if (entry.isFile()) {
|
|
988
1003
|
const name = provide.type === "rules" && entry.name.endsWith(".md") ? entry.name : entry.name;
|
|
989
|
-
artifacts.push(await artifactForFile(provide.type, name, child, join6(provide.path, entry.name), packageName));
|
|
1004
|
+
artifacts.push(await artifactForFile(provide.type, name, child, join6(provide.path, entry.name), packageName, provide.assets, provide.required));
|
|
990
1005
|
}
|
|
991
1006
|
}
|
|
992
1007
|
} else if (stats.isFile()) {
|
|
993
|
-
artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName));
|
|
1008
|
+
artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName, provide.assets, provide.required));
|
|
994
1009
|
}
|
|
995
1010
|
}
|
|
996
1011
|
return artifacts;
|
|
@@ -1007,7 +1022,7 @@ async function listGenericArtifacts(type, dir, relativeRoot, packageName) {
|
|
|
1007
1022
|
}
|
|
1008
1023
|
return artifacts;
|
|
1009
1024
|
}
|
|
1010
|
-
async function artifactForFile(type, name, sourcePath, relativePath, packageName) {
|
|
1025
|
+
async function artifactForFile(type, name, sourcePath, relativePath, packageName, assets, required) {
|
|
1011
1026
|
return {
|
|
1012
1027
|
type,
|
|
1013
1028
|
name,
|
|
@@ -1016,10 +1031,12 @@ async function artifactForFile(type, name, sourcePath, relativePath, packageName
|
|
|
1016
1031
|
kind: "file",
|
|
1017
1032
|
hash: await hashPath(sourcePath),
|
|
1018
1033
|
packageName,
|
|
1019
|
-
channel: "managed"
|
|
1034
|
+
channel: "managed",
|
|
1035
|
+
assets,
|
|
1036
|
+
required
|
|
1020
1037
|
};
|
|
1021
1038
|
}
|
|
1022
|
-
async function artifactForDir(type, name, sourcePath, relativePath, packageName) {
|
|
1039
|
+
async function artifactForDir(type, name, sourcePath, relativePath, packageName, assets, required) {
|
|
1023
1040
|
return {
|
|
1024
1041
|
type,
|
|
1025
1042
|
name,
|
|
@@ -1028,7 +1045,9 @@ async function artifactForDir(type, name, sourcePath, relativePath, packageName)
|
|
|
1028
1045
|
kind: "dir",
|
|
1029
1046
|
hash: await hashPath(sourcePath),
|
|
1030
1047
|
packageName,
|
|
1031
|
-
channel: "managed"
|
|
1048
|
+
channel: "managed",
|
|
1049
|
+
assets,
|
|
1050
|
+
required
|
|
1032
1051
|
};
|
|
1033
1052
|
}
|
|
1034
1053
|
|
|
@@ -1463,8 +1482,8 @@ function getSourceDriver(name = "local") {
|
|
|
1463
1482
|
}
|
|
1464
1483
|
|
|
1465
1484
|
// src/staging/staging.ts
|
|
1466
|
-
import { cp as cp3, mkdir as mkdir5, mkdtemp } from "fs/promises";
|
|
1467
|
-
import { dirname as dirname5, join as join12 } from "path";
|
|
1485
|
+
import { chmod, cp as cp3, mkdir as mkdir5, mkdtemp, readdir as readdir4, stat as stat6 } from "fs/promises";
|
|
1486
|
+
import { basename as basename7, dirname as dirname5, join as join12, relative as relative2, resolve as resolve8, sep } from "path";
|
|
1468
1487
|
import { tmpdir as tmpdir2 } from "os";
|
|
1469
1488
|
|
|
1470
1489
|
// src/staging/customize.ts
|
|
@@ -1573,6 +1592,46 @@ async function sortedDirEntries2(path) {
|
|
|
1573
1592
|
return (await readdir3(path, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
|
|
1574
1593
|
}
|
|
1575
1594
|
|
|
1595
|
+
// src/model/selection.ts
|
|
1596
|
+
function artifactSelectorKey(artifact) {
|
|
1597
|
+
return `${artifact.type}/${artifact.name}`;
|
|
1598
|
+
}
|
|
1599
|
+
function normalizeArtifactSelectors(select, legacySkills) {
|
|
1600
|
+
const selected = [
|
|
1601
|
+
...select ?? [],
|
|
1602
|
+
...(legacySkills ?? []).map((name) => `skills/${name}`)
|
|
1603
|
+
].flatMap(splitSelectorList);
|
|
1604
|
+
if (selected.length === 0) return void 0;
|
|
1605
|
+
return [...new Set(selected.map(parseArtifactSelector))];
|
|
1606
|
+
}
|
|
1607
|
+
function filterArtifactsBySelection(artifacts, selectors, legacySkills) {
|
|
1608
|
+
const selected = normalizeArtifactSelectors(selectors, legacySkills);
|
|
1609
|
+
if (!selected?.length) return artifacts;
|
|
1610
|
+
const selectedSet = new Set(selected);
|
|
1611
|
+
const available = new Set(artifacts.map(artifactSelectorKey));
|
|
1612
|
+
const missing = selected.filter((selector) => !available.has(selector));
|
|
1613
|
+
if (missing.length > 0) {
|
|
1614
|
+
throw new Error(`Selected artifact not found in package: ${missing.join(", ")}`);
|
|
1615
|
+
}
|
|
1616
|
+
return artifacts.filter((artifact) => artifact.required || selectedSet.has(artifactSelectorKey(artifact)));
|
|
1617
|
+
}
|
|
1618
|
+
function splitSelectorList(value) {
|
|
1619
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
1620
|
+
}
|
|
1621
|
+
function parseArtifactSelector(value) {
|
|
1622
|
+
const slash = value.indexOf("/");
|
|
1623
|
+
if (slash <= 0 || slash === value.length - 1) {
|
|
1624
|
+
throw new Error(`Invalid artifact selector: ${value}. Expected <type>/<name>.`);
|
|
1625
|
+
}
|
|
1626
|
+
const type = value.slice(0, slash);
|
|
1627
|
+
const name = value.slice(slash + 1);
|
|
1628
|
+
const parsedType = artifactTypeSchema.safeParse(type);
|
|
1629
|
+
if (!parsedType.success) {
|
|
1630
|
+
throw new Error(`Invalid artifact selector type: ${type}`);
|
|
1631
|
+
}
|
|
1632
|
+
return `${parsedType.data}/${name}`;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1576
1635
|
// src/staging/staging.ts
|
|
1577
1636
|
async function stageSource(driver, source, options = {}) {
|
|
1578
1637
|
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(source, options))));
|
|
@@ -1583,6 +1642,7 @@ async function stageSource(driver, source, options = {}) {
|
|
|
1583
1642
|
const stagedPath = join12(root, artifact.relativePath);
|
|
1584
1643
|
await mkdir5(dirname5(stagedPath), { recursive: true });
|
|
1585
1644
|
await cp3(artifact.sourcePath, stagedPath, { recursive: artifact.kind === "dir", dereference: true });
|
|
1645
|
+
await composeAssets(artifact, resolved.resolvedPath, stagedPath);
|
|
1586
1646
|
stagedArtifacts.push({
|
|
1587
1647
|
...artifact,
|
|
1588
1648
|
stagedPath,
|
|
@@ -1590,12 +1650,13 @@ async function stageSource(driver, source, options = {}) {
|
|
|
1590
1650
|
channel: artifact.channel ?? "managed"
|
|
1591
1651
|
});
|
|
1592
1652
|
}
|
|
1593
|
-
const
|
|
1653
|
+
const selectedArtifacts = filterArtifactsBySelection(stagedArtifacts, options.select, options.skills);
|
|
1654
|
+
const finalArtifacts = options.workspaceRoot && options.adapter ? await applyCustomizations(selectedArtifacts, {
|
|
1594
1655
|
workspaceRoot: options.workspaceRoot,
|
|
1595
1656
|
adapter: options.adapter,
|
|
1596
1657
|
stageRoot: root,
|
|
1597
1658
|
packageName: resolved.packageName
|
|
1598
|
-
}) :
|
|
1659
|
+
}) : selectedArtifacts;
|
|
1599
1660
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1600
1661
|
return {
|
|
1601
1662
|
root,
|
|
@@ -1623,11 +1684,93 @@ async function stageSource(driver, source, options = {}) {
|
|
|
1623
1684
|
}
|
|
1624
1685
|
};
|
|
1625
1686
|
}
|
|
1687
|
+
async function composeAssets(artifact, packageRoot, stagedPath) {
|
|
1688
|
+
if (!artifact.assets?.length) return;
|
|
1689
|
+
if (artifact.kind !== "dir") {
|
|
1690
|
+
throw new Error(`Asset includes require a directory artifact: ${artifact.type}/${artifact.name}`);
|
|
1691
|
+
}
|
|
1692
|
+
for (const asset of artifact.assets) {
|
|
1693
|
+
const source = resolvePackagePath(packageRoot, asset.from);
|
|
1694
|
+
const dest = join12(stagedPath, asset.into);
|
|
1695
|
+
await copyAsset(asset, source, dest);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
async function copyAsset(asset, source, dest) {
|
|
1699
|
+
const sourceStats = await stat6(source);
|
|
1700
|
+
if (sourceStats.isFile()) {
|
|
1701
|
+
if (matchesAny(basename7(source), asset.include)) {
|
|
1702
|
+
await mkdir5(dest, { recursive: true });
|
|
1703
|
+
await copyAssetFile(source, join12(dest, basename7(source)), asset);
|
|
1704
|
+
}
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
if (!sourceStats.isDirectory()) {
|
|
1708
|
+
throw new Error(`Asset include source is not a file or directory: ${source}`);
|
|
1709
|
+
}
|
|
1710
|
+
if (!asset.include?.length) {
|
|
1711
|
+
await mkdir5(dirname5(dest), { recursive: true });
|
|
1712
|
+
await cp3(source, dest, { recursive: true, dereference: true });
|
|
1713
|
+
if (asset.mode === "copy") await normalizeCopiedModes(dest);
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
for (const file of await listFiles(source)) {
|
|
1717
|
+
const rel = relative2(source, file).replaceAll("\\", "/");
|
|
1718
|
+
if (!matchesAny(rel, asset.include) && !matchesAny(basename7(file), asset.include)) continue;
|
|
1719
|
+
await copyAssetFile(file, join12(dest, rel), asset);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
async function copyAssetFile(source, dest, asset) {
|
|
1723
|
+
await mkdir5(dirname5(dest), { recursive: true });
|
|
1724
|
+
await cp3(source, dest, { dereference: true });
|
|
1725
|
+
if (asset.mode === "copy") await chmod(dest, 420);
|
|
1726
|
+
}
|
|
1727
|
+
function resolvePackagePath(packageRoot, path) {
|
|
1728
|
+
const resolved = resolve8(packageRoot, path);
|
|
1729
|
+
const root = resolve8(packageRoot);
|
|
1730
|
+
if (resolved !== root && !resolved.startsWith(`${root}${sep}`)) {
|
|
1731
|
+
throw new Error(`Asset include escapes package root: ${path}`);
|
|
1732
|
+
}
|
|
1733
|
+
return resolved;
|
|
1734
|
+
}
|
|
1735
|
+
async function listFiles(root) {
|
|
1736
|
+
const out = [];
|
|
1737
|
+
async function walk2(dir) {
|
|
1738
|
+
for (const entry of (await readdir4(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1739
|
+
const full = join12(dir, entry.name);
|
|
1740
|
+
if (entry.isDirectory()) {
|
|
1741
|
+
await walk2(full);
|
|
1742
|
+
} else if (entry.isFile()) {
|
|
1743
|
+
out.push(full);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
await walk2(root);
|
|
1748
|
+
return out;
|
|
1749
|
+
}
|
|
1750
|
+
async function normalizeCopiedModes(path) {
|
|
1751
|
+
const stats = await stat6(path);
|
|
1752
|
+
if (stats.isFile()) {
|
|
1753
|
+
await chmod(path, 420);
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
if (!stats.isDirectory()) return;
|
|
1757
|
+
for (const entry of await readdir4(path, { withFileTypes: true })) {
|
|
1758
|
+
await normalizeCopiedModes(join12(path, entry.name));
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
function matchesAny(path, patterns) {
|
|
1762
|
+
if (!patterns?.length) return true;
|
|
1763
|
+
return patterns.some((pattern) => matchesGlob(path, pattern));
|
|
1764
|
+
}
|
|
1765
|
+
function matchesGlob(path, pattern) {
|
|
1766
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1767
|
+
return new RegExp(`^${escaped}$`).test(path);
|
|
1768
|
+
}
|
|
1626
1769
|
|
|
1627
1770
|
// src/model/workspace.ts
|
|
1628
1771
|
import { readFile as readFile8 } from "fs/promises";
|
|
1629
1772
|
import { homedir as homedir3 } from "os";
|
|
1630
|
-
import { dirname as dirname6, join as join13, resolve as
|
|
1773
|
+
import { dirname as dirname6, join as join13, resolve as resolve9 } from "path";
|
|
1631
1774
|
import { z as z5 } from "zod";
|
|
1632
1775
|
var workspacePackageSchema = z5.object({
|
|
1633
1776
|
name: z5.string().min(1),
|
|
@@ -1638,7 +1781,9 @@ var workspacePackageSchema = z5.object({
|
|
|
1638
1781
|
adapterModule: z5.string().min(1).optional(),
|
|
1639
1782
|
adapterCodeHash: z5.string().min(16).optional(),
|
|
1640
1783
|
mode: z5.enum(["pinned", "tracking"]).default("pinned"),
|
|
1641
|
-
requestedRef: z5.string().min(1).optional()
|
|
1784
|
+
requestedRef: z5.string().min(1).optional(),
|
|
1785
|
+
select: z5.array(z5.string().min(1)).optional(),
|
|
1786
|
+
skills: z5.array(z5.string().min(1)).optional()
|
|
1642
1787
|
});
|
|
1643
1788
|
var workspaceProfileRuntimeSchema = z5.object({
|
|
1644
1789
|
agent: z5.string().min(1).optional(),
|
|
@@ -1687,11 +1832,11 @@ function globalWorkspaceConfigPath(globalRoot = homedir3()) {
|
|
|
1687
1832
|
return join13(globalRoot, ".agentwheel", "config.json");
|
|
1688
1833
|
}
|
|
1689
1834
|
async function findWorkspaceRoot(start = process.cwd()) {
|
|
1690
|
-
let current =
|
|
1835
|
+
let current = resolve9(start);
|
|
1691
1836
|
while (true) {
|
|
1692
1837
|
if (await pathExists(workspaceConfigPath(current))) return current;
|
|
1693
1838
|
const parent = dirname6(current);
|
|
1694
|
-
if (parent === current) return
|
|
1839
|
+
if (parent === current) return resolve9(start);
|
|
1695
1840
|
current = parent;
|
|
1696
1841
|
}
|
|
1697
1842
|
}
|
|
@@ -1715,9 +1860,9 @@ function mergeWorkspaceConfig(global, project) {
|
|
|
1715
1860
|
});
|
|
1716
1861
|
}
|
|
1717
1862
|
function resolveConfigPath(path, baseRoot) {
|
|
1718
|
-
if (path.startsWith("~/")) return
|
|
1863
|
+
if (path.startsWith("~/")) return resolve9(homedir3(), path.slice(2));
|
|
1719
1864
|
if (path === "~") return homedir3();
|
|
1720
|
-
return path.startsWith("/") ?
|
|
1865
|
+
return path.startsWith("/") ? resolve9(path) : resolve9(baseRoot, path);
|
|
1721
1866
|
}
|
|
1722
1867
|
function emptyWorkspaceConfig() {
|
|
1723
1868
|
return { schemaVersion: 1, packages: [], registry: {}, profiles: {}, agents: {} };
|
|
@@ -1781,9 +1926,9 @@ import { rm as rm7 } from "fs/promises";
|
|
|
1781
1926
|
import { join as join16 } from "path";
|
|
1782
1927
|
|
|
1783
1928
|
// src/registry/client.ts
|
|
1784
|
-
import { readFile as readFile9, rm as rm6, stat as
|
|
1929
|
+
import { readFile as readFile9, rm as rm6, stat as stat7 } from "fs/promises";
|
|
1785
1930
|
import { homedir as homedir4 } from "os";
|
|
1786
|
-
import { dirname as dirname8, join as join15, resolve as
|
|
1931
|
+
import { dirname as dirname8, join as join15, resolve as resolve10 } from "path";
|
|
1787
1932
|
import { fileURLToPath } from "url";
|
|
1788
1933
|
|
|
1789
1934
|
// src/model/registry.ts
|
|
@@ -1886,8 +2031,8 @@ var RegistryClient = class {
|
|
|
1886
2031
|
}
|
|
1887
2032
|
const filePath = source.startsWith("file:") ? fileURLToPath(source) : source;
|
|
1888
2033
|
if (await pathExists(filePath)) {
|
|
1889
|
-
const fullPath =
|
|
1890
|
-
const stats = await
|
|
2034
|
+
const fullPath = resolve10(filePath);
|
|
2035
|
+
const stats = await stat7(fullPath);
|
|
1891
2036
|
return readFile9(stats.isDirectory() ? join15(fullPath, "index.json") : fullPath, "utf8");
|
|
1892
2037
|
}
|
|
1893
2038
|
const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join15(dirname8(this.cachePath), "registry-repos") }));
|
|
@@ -1947,7 +2092,9 @@ async function syncProfile(options) {
|
|
|
1947
2092
|
workspaceRoot: options.workspaceRoot,
|
|
1948
2093
|
adapter,
|
|
1949
2094
|
cacheRoot: join16(options.workspaceRoot, ".agentwheel", "cache"),
|
|
1950
|
-
mode: options.mode ?? pkg.mode
|
|
2095
|
+
mode: options.mode ?? pkg.mode,
|
|
2096
|
+
select: options.select ?? pkg.select,
|
|
2097
|
+
skills: options.select ? void 0 : pkg.skills
|
|
1951
2098
|
});
|
|
1952
2099
|
try {
|
|
1953
2100
|
const plan = await createInstallPlan(bundle, adapter, target.targetRoot, await readInstallManifest(target.targetRoot, adapter.name));
|
|
@@ -1981,7 +2128,9 @@ async function packageFromSource(source, options) {
|
|
|
1981
2128
|
source: resolved.source,
|
|
1982
2129
|
driver,
|
|
1983
2130
|
adapter: "openclaw",
|
|
1984
|
-
mode: options.mode ?? "pinned"
|
|
2131
|
+
mode: options.mode ?? "pinned",
|
|
2132
|
+
select: options.select,
|
|
2133
|
+
skills: options.skills
|
|
1985
2134
|
};
|
|
1986
2135
|
}
|
|
1987
2136
|
|
|
@@ -1996,7 +2145,9 @@ async function createSourcePlan(options) {
|
|
|
1996
2145
|
workspaceRoot,
|
|
1997
2146
|
adapter: options.adapter,
|
|
1998
2147
|
cacheRoot: join17(workspaceRoot, ".agentwheel", "cache"),
|
|
1999
|
-
mode: options.mode
|
|
2148
|
+
mode: options.mode,
|
|
2149
|
+
select: options.select,
|
|
2150
|
+
skills: options.skills
|
|
2000
2151
|
});
|
|
2001
2152
|
const manifest = await readInstallManifest(options.targetRoot, options.adapter.name);
|
|
2002
2153
|
const plan = await createInstallPlan(bundle, options.adapter, options.targetRoot, manifest);
|
|
@@ -2021,7 +2172,7 @@ function shouldUpdatePackage(pkg, lock) {
|
|
|
2021
2172
|
}
|
|
2022
2173
|
|
|
2023
2174
|
// src/runtime/target.ts
|
|
2024
|
-
import { basename as
|
|
2175
|
+
import { basename as basename8, dirname as dirname9, join as join18, resolve as resolve11 } from "path";
|
|
2025
2176
|
var runtimeMarkers = [
|
|
2026
2177
|
{ adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
|
|
2027
2178
|
{ adapter: "claude", dirs: [".claude"] },
|
|
@@ -2030,9 +2181,9 @@ var runtimeMarkers = [
|
|
|
2030
2181
|
{ adapter: "copilot", dirs: [".github"] }
|
|
2031
2182
|
];
|
|
2032
2183
|
async function resolveRuntimeTarget(request = {}) {
|
|
2033
|
-
const cwd =
|
|
2184
|
+
const cwd = resolve11(request.cwd ?? process.cwd());
|
|
2034
2185
|
if (request.targetRoot) {
|
|
2035
|
-
const targetRoot =
|
|
2186
|
+
const targetRoot = resolve11(request.targetRoot);
|
|
2036
2187
|
return {
|
|
2037
2188
|
adapter: request.adapter ?? "openclaw",
|
|
2038
2189
|
targetRoot,
|
|
@@ -2059,7 +2210,7 @@ async function resolveRuntimeTarget(request = {}) {
|
|
|
2059
2210
|
async function resolveAllRuntimeTargets(request = {}) {
|
|
2060
2211
|
if (request.targetRoot) return [await resolveRuntimeTarget(request)];
|
|
2061
2212
|
if (request.agent) return [await resolveRuntimeTarget(request)];
|
|
2062
|
-
const cwd =
|
|
2213
|
+
const cwd = resolve11(request.cwd ?? process.cwd());
|
|
2063
2214
|
const workspaceRoot = await findWorkspaceRoot(cwd);
|
|
2064
2215
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
2065
2216
|
const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot));
|
|
@@ -2069,12 +2220,12 @@ async function resolveAllRuntimeTargets(request = {}) {
|
|
|
2069
2220
|
return targets;
|
|
2070
2221
|
}
|
|
2071
2222
|
async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
|
|
2072
|
-
const root =
|
|
2223
|
+
const root = resolve11(cwd);
|
|
2073
2224
|
const matches = [];
|
|
2074
2225
|
for (const marker of runtimeMarkers) {
|
|
2075
2226
|
if (adapterFilter && marker.adapter !== adapterFilter) continue;
|
|
2076
2227
|
for (const dir of marker.dirs) {
|
|
2077
|
-
if (
|
|
2228
|
+
if (basename8(root) === dir) {
|
|
2078
2229
|
matches.push({ adapter: marker.adapter, targetRoot: dirname9(root) });
|
|
2079
2230
|
} else if (await pathExists(join18(root, dir))) {
|
|
2080
2231
|
matches.push({ adapter: marker.adapter, targetRoot: root });
|
|
@@ -2108,9 +2259,86 @@ function dedupeTargets(matches) {
|
|
|
2108
2259
|
return [...byKey.values()];
|
|
2109
2260
|
}
|
|
2110
2261
|
|
|
2262
|
+
// src/cli/update-check.ts
|
|
2263
|
+
import { mkdir as mkdir7, readFile as readFile10, writeFile as writeFile4 } from "fs/promises";
|
|
2264
|
+
import { homedir as homedir5 } from "os";
|
|
2265
|
+
import { dirname as dirname10, join as join19 } from "path";
|
|
2266
|
+
var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2267
|
+
var DEFAULT_TIMEOUT_MS = 300;
|
|
2268
|
+
var REGISTRY_URL = "https://registry.npmjs.org/agentwheel";
|
|
2269
|
+
async function maybeCheckForUpdate(options) {
|
|
2270
|
+
if (isDisabled(options)) return;
|
|
2271
|
+
const now = options.now?.() ?? /* @__PURE__ */ new Date();
|
|
2272
|
+
const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
2273
|
+
const cachePath = options.cachePath ?? join19(homedir5(), ".agentwheel", "update-check.json");
|
|
2274
|
+
try {
|
|
2275
|
+
const cached = await readCache(cachePath);
|
|
2276
|
+
if (cached && now.getTime() - Date.parse(cached.checkedAt) < ttlMs) {
|
|
2277
|
+
warnIfNewer(cached.latest, options.currentVersion, options.stderr);
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
const latest = await fetchLatestVersion(options.fetchImpl ?? fetch, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
2281
|
+
if (!latest) return;
|
|
2282
|
+
await writeCache(cachePath, { checkedAt: now.toISOString(), latest });
|
|
2283
|
+
warnIfNewer(latest, options.currentVersion, options.stderr);
|
|
2284
|
+
} catch {
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
function isDisabled(options) {
|
|
2288
|
+
const env = options.env ?? process.env;
|
|
2289
|
+
if (env.AGENTWHEEL_NO_UPDATE_CHECK === "1" || env.AGENTWHEEL_NO_UPDATE_CHECK === "true") return true;
|
|
2290
|
+
if (env.CI) return true;
|
|
2291
|
+
if (options.argv?.includes("--no-update-check")) return true;
|
|
2292
|
+
const isTTY = options.isTTY ?? process.stderr.isTTY === true;
|
|
2293
|
+
return !isTTY;
|
|
2294
|
+
}
|
|
2295
|
+
async function fetchLatestVersion(fetchImpl, timeoutMs) {
|
|
2296
|
+
const controller = new AbortController();
|
|
2297
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
2298
|
+
try {
|
|
2299
|
+
const response = await fetchImpl(REGISTRY_URL, { signal: controller.signal });
|
|
2300
|
+
if (!response.ok) return void 0;
|
|
2301
|
+
const body = await response.json();
|
|
2302
|
+
return typeof body["dist-tags"]?.latest === "string" ? body["dist-tags"].latest : void 0;
|
|
2303
|
+
} finally {
|
|
2304
|
+
clearTimeout(timeout);
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
async function readCache(path) {
|
|
2308
|
+
try {
|
|
2309
|
+
const parsed = JSON.parse(await readFile10(path, "utf8"));
|
|
2310
|
+
if (typeof parsed.checkedAt !== "string" || typeof parsed.latest !== "string") return void 0;
|
|
2311
|
+
return { checkedAt: parsed.checkedAt, latest: parsed.latest };
|
|
2312
|
+
} catch {
|
|
2313
|
+
return void 0;
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
async function writeCache(path, cache) {
|
|
2317
|
+
await mkdir7(dirname10(path), { recursive: true });
|
|
2318
|
+
await writeFile4(path, `${JSON.stringify(cache, null, 2)}
|
|
2319
|
+
`, "utf8");
|
|
2320
|
+
}
|
|
2321
|
+
function warnIfNewer(latest, current, stderr = process.stderr) {
|
|
2322
|
+
if (compareVersions(latest, current) <= 0) return;
|
|
2323
|
+
stderr.write(`agentwheel ${latest} is available (you have ${current}). Update: npm i -g agentwheel
|
|
2324
|
+
`);
|
|
2325
|
+
}
|
|
2326
|
+
function compareVersions(a, b) {
|
|
2327
|
+
const left = normalizeVersion(a);
|
|
2328
|
+
const right = normalizeVersion(b);
|
|
2329
|
+
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
2330
|
+
const diff = (left[index] ?? 0) - (right[index] ?? 0);
|
|
2331
|
+
if (diff !== 0) return diff > 0 ? 1 : -1;
|
|
2332
|
+
}
|
|
2333
|
+
return 0;
|
|
2334
|
+
}
|
|
2335
|
+
function normalizeVersion(version) {
|
|
2336
|
+
return version.replace(/^v/, "").split("-", 1)[0].split(".").map((part) => Number.parseInt(part, 10)).map((part) => Number.isFinite(part) ? part : 0);
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2111
2339
|
// src/cli/index.ts
|
|
2112
2340
|
var program = new Command();
|
|
2113
|
-
program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version("0.
|
|
2341
|
+
program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version("0.6.0").option("--no-update-check", "disable npm version update check", false);
|
|
2114
2342
|
program.command("init").argument("[kind]", "workspace or package", "workspace").option("--target-root <path>", "workspace root", process.cwd()).action(async (kind, options) => {
|
|
2115
2343
|
const root = normalizeTargetRoot(options.targetRoot);
|
|
2116
2344
|
if (kind === "package") {
|
|
@@ -2124,8 +2352,9 @@ program.command("init").argument("[kind]", "workspace or package", "workspace").
|
|
|
2124
2352
|
await writeWorkspaceConfig(root, await readWorkspaceConfig(root));
|
|
2125
2353
|
console.log("Initialized .agentwheel/config.json.");
|
|
2126
2354
|
});
|
|
2127
|
-
program.command("add").argument("<source>", "package source").option("--driver <driver>", "source driver (local, git, skillkit, or vercel-skills)").option("--adapter <adapter>", "built-in adapter", "openclaw").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "workspace root", process.cwd()).option("--mode <mode>", "pinned or tracking", "pinned").option("--name <name>", "package alias").action(async (source, options) => {
|
|
2355
|
+
program.command("add").argument("<source>", "package source").option("--driver <driver>", "source driver (local, git, skillkit, or vercel-skills)").option("--adapter <adapter>", "built-in adapter", "openclaw").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "workspace root", process.cwd()).option("--mode <mode>", "pinned or tracking", "pinned").option("--name <name>", "package alias").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).action(async (source, options) => {
|
|
2128
2356
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
2357
|
+
const selectedArtifacts = selectedArtifactsFromOptions(options);
|
|
2129
2358
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
2130
2359
|
const resolvedSource = resolvedInput.source;
|
|
2131
2360
|
const driverName = options.driver ?? inferSourceDriverName(resolvedSource);
|
|
@@ -2141,8 +2370,9 @@ program.command("add").argument("<source>", "package source").option("--driver <
|
|
|
2141
2370
|
const bundle = await stageSource(driver, resolvedSource, {
|
|
2142
2371
|
workspaceRoot: targetRoot,
|
|
2143
2372
|
adapter,
|
|
2144
|
-
cacheRoot:
|
|
2145
|
-
mode: options.mode
|
|
2373
|
+
cacheRoot: join20(targetRoot, ".agentwheel", "cache"),
|
|
2374
|
+
mode: options.mode,
|
|
2375
|
+
select: selectedArtifacts
|
|
2146
2376
|
});
|
|
2147
2377
|
const name = options.name ?? resolvedInput.registryEntry?.name ?? bundle.source.packageName ?? source;
|
|
2148
2378
|
const entry = {
|
|
@@ -2154,18 +2384,20 @@ program.command("add").argument("<source>", "package source").option("--driver <
|
|
|
2154
2384
|
adapterModule: options.adapterModule,
|
|
2155
2385
|
adapterCodeHash: adapter.programmatic?.hash,
|
|
2156
2386
|
mode: options.mode,
|
|
2157
|
-
requestedRef: bundle.source.requestedRef
|
|
2387
|
+
requestedRef: bundle.source.requestedRef,
|
|
2388
|
+
select: selectedArtifacts
|
|
2158
2389
|
};
|
|
2159
2390
|
await writeWorkspaceConfig(targetRoot, upsertPackage(await readWorkspaceConfig(targetRoot), entry));
|
|
2160
2391
|
await rm8(bundle.root, { recursive: true, force: true });
|
|
2161
2392
|
console.log(`Added ${name}.`);
|
|
2162
2393
|
});
|
|
2163
|
-
program.command("list").argument("<source>", "package source").option("--driver <driver>", "source driver").option("--target-root <path>", "workspace root", process.cwd()).action(async (source, options) => {
|
|
2394
|
+
program.command("list").argument("<source>", "package source").option("--driver <driver>", "source driver").option("--target-root <path>", "workspace root", process.cwd()).option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).action(async (source, options) => {
|
|
2164
2395
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
2396
|
+
const selectedArtifacts = selectedArtifactsFromOptions(options);
|
|
2165
2397
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
2166
2398
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
2167
|
-
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot:
|
|
2168
|
-
const artifacts = await driver.list(resolved);
|
|
2399
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join20(targetRoot, ".agentwheel", "cache") }))));
|
|
2400
|
+
const artifacts = filterArtifactsBySelection(await driver.list(resolved), selectedArtifacts);
|
|
2169
2401
|
for (const artifact of artifacts) {
|
|
2170
2402
|
console.log(`${artifact.type} ${artifact.name} ${artifact.relativePath}`);
|
|
2171
2403
|
}
|
|
@@ -2174,7 +2406,7 @@ program.command("scan").argument("<source>", "package source").option("--driver
|
|
|
2174
2406
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
2175
2407
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
2176
2408
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
2177
|
-
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot:
|
|
2409
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join20(targetRoot, ".agentwheel", "cache") }))));
|
|
2178
2410
|
const result = await driver.scan(resolved);
|
|
2179
2411
|
if (result.findings.length === 0) {
|
|
2180
2412
|
console.log("Scan ok: no findings");
|
|
@@ -2185,7 +2417,7 @@ program.command("scan").argument("<source>", "package source").option("--driver
|
|
|
2185
2417
|
}
|
|
2186
2418
|
if (!result.ok) process.exitCode = 1;
|
|
2187
2419
|
});
|
|
2188
|
-
program.command("plan").argument("<source>", "source directory").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter", "openclaw").option("--adapter-config <path>", "adapter JSON/JSONC file").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("--mode <mode>", "pinned or tracking").option("--dry-run", "accepted for symmetry; plan never writes", false).action(async (source, options) => {
|
|
2420
|
+
program.command("plan").argument("<source>", "source directory").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter", "openclaw").option("--adapter-config <path>", "adapter JSON/JSONC file").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("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).action(async (source, options) => {
|
|
2189
2421
|
const targets = await resolveCliTargets(options);
|
|
2190
2422
|
for (const target of targets) {
|
|
2191
2423
|
const { plan, bundle } = await buildPlan(source, target, options);
|
|
@@ -2194,7 +2426,7 @@ program.command("plan").argument("<source>", "source directory").option("--drive
|
|
|
2194
2426
|
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
2195
2427
|
}
|
|
2196
2428
|
});
|
|
2197
|
-
program.command("sync").argument("[source]", "source directory").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter", "openclaw").option("--adapter-config <path>", "adapter JSON/JSONC file").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("--mode <mode>", "pinned or tracking").option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).action(async (source, options) => {
|
|
2429
|
+
program.command("sync").argument("[source]", "source directory").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter", "openclaw").option("--adapter-config <path>", "adapter JSON/JSONC file").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("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).action(async (source, options) => {
|
|
2198
2430
|
if (options.profile) {
|
|
2199
2431
|
const target = await resolveRuntimeTarget({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent });
|
|
2200
2432
|
const results = await syncProfile({
|
|
@@ -2203,6 +2435,7 @@ program.command("sync").argument("[source]", "source directory").option("--drive
|
|
|
2203
2435
|
source,
|
|
2204
2436
|
driver: options.driver,
|
|
2205
2437
|
mode: options.mode,
|
|
2438
|
+
select: selectedArtifactsFromOptions(options),
|
|
2206
2439
|
dryRun: options.dryRun,
|
|
2207
2440
|
executePlugins: options.executePlugins,
|
|
2208
2441
|
allowAdapterCode: options.allowAdapterCode,
|
|
@@ -2234,7 +2467,7 @@ program.command("sync").argument("[source]", "source directory").option("--drive
|
|
|
2234
2467
|
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
2235
2468
|
}
|
|
2236
2469
|
});
|
|
2237
|
-
program.command("update").option("--adapter <adapter>", "built-in adapter").option("--target-root <path>", "workspace root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show plans without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--allow-adapter-code", "allow loading local adapter code from configured packages", false).action(async (options) => {
|
|
2470
|
+
program.command("update").option("--adapter <adapter>", "built-in adapter").option("--target-root <path>", "workspace root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show plans without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--allow-adapter-code", "allow loading local adapter code from configured packages", false).option("--select <type/name>", "temporarily select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "temporarily select a skill by name (repeatable or comma-separated)", collectSkillOption, []).action(async (options) => {
|
|
2238
2471
|
const targets = await resolveCliTargets(options);
|
|
2239
2472
|
for (const target of targets) {
|
|
2240
2473
|
await runConfiguredPackages(target, options, { useUpdateDecision: true });
|
|
@@ -2267,7 +2500,7 @@ program.command("eject").argument("<item>", "package/type/name").option("--targe
|
|
|
2267
2500
|
const result = await ejectArtifact(targetRoot, item);
|
|
2268
2501
|
console.log(`Ejected ${item} to ${result.ejectedPath}.`);
|
|
2269
2502
|
});
|
|
2270
|
-
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) => {
|
|
2503
|
+
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).option("--select <type/name>", "uninstall only selected artifact type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "uninstall only selected skill name (repeatable or comma-separated)", collectSkillOption, []).action(async (options) => {
|
|
2271
2504
|
const targets = await resolveCliTargets(options);
|
|
2272
2505
|
for (const target of targets) {
|
|
2273
2506
|
const adapter = await resolveAdapterForTarget(target, options);
|
|
@@ -2276,7 +2509,7 @@ program.command("uninstall").option("--adapter <adapter>", "adapter", "openclaw"
|
|
|
2276
2509
|
console.log(`No install manifest for ${adapter.name} at ${target.targetRoot}`);
|
|
2277
2510
|
continue;
|
|
2278
2511
|
}
|
|
2279
|
-
const plan = await createUninstallPlan(manifest);
|
|
2512
|
+
const plan = filterUninstallPlanBySelection(await createUninstallPlan(manifest), selectedArtifactsFromOptions(options));
|
|
2280
2513
|
console.log(formatPlan(plan));
|
|
2281
2514
|
const result = await uninstall(plan, { dryRun: options.dryRun, force: options.force });
|
|
2282
2515
|
if (!options.dryRun) {
|
|
@@ -2294,7 +2527,9 @@ async function buildPlan(source, target, options) {
|
|
|
2294
2527
|
workspaceRoot: target.workspaceRoot,
|
|
2295
2528
|
adapter,
|
|
2296
2529
|
driver: options.driver,
|
|
2297
|
-
mode: options.mode
|
|
2530
|
+
mode: options.mode,
|
|
2531
|
+
select: options.select ?? selectedArtifactsFromOptions(options),
|
|
2532
|
+
skills: options.skills
|
|
2298
2533
|
});
|
|
2299
2534
|
return { plan: result.plan, bundle: result.bundle };
|
|
2300
2535
|
}
|
|
@@ -2320,6 +2555,7 @@ async function runConfiguredPackages(target, options, behavior) {
|
|
|
2320
2555
|
console.log(`No packages configured at ${target.workspaceRoot}.`);
|
|
2321
2556
|
return;
|
|
2322
2557
|
}
|
|
2558
|
+
const selectedArtifacts = selectedArtifactsFromOptions(options);
|
|
2323
2559
|
for (const pkg of config.packages) {
|
|
2324
2560
|
const targetForPackage = options.adapter || target.source !== "cwd" ? target : { ...target, adapter: pkg.adapter };
|
|
2325
2561
|
const adapter = await resolveAdapterForTarget(targetForPackage, {
|
|
@@ -2340,7 +2576,9 @@ async function runConfiguredPackages(target, options, behavior) {
|
|
|
2340
2576
|
adapterConfig: options.adapterConfig ?? pkg.adapterConfig,
|
|
2341
2577
|
adapterModule: options.adapterModule ?? pkg.adapterModule,
|
|
2342
2578
|
allowAdapterCode: options.allowAdapterCode,
|
|
2343
|
-
mode: pkg.mode
|
|
2579
|
+
mode: pkg.mode,
|
|
2580
|
+
select: selectedArtifacts ?? pkg.select,
|
|
2581
|
+
skills: selectedArtifacts ? void 0 : pkg.skills
|
|
2344
2582
|
});
|
|
2345
2583
|
console.log(`${behavior.useUpdateDecision ? "Update" : "Sync"} ${pkg.name} (${adapter.name} at ${targetForPackage.targetRoot}):`);
|
|
2346
2584
|
console.log(formatPlan(plan));
|
|
@@ -2352,11 +2590,44 @@ async function runConfiguredPackages(target, options, behavior) {
|
|
|
2352
2590
|
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
2353
2591
|
}
|
|
2354
2592
|
}
|
|
2593
|
+
function collectSelectOption(value, previous) {
|
|
2594
|
+
return [...previous, ...splitSelectorList(value)];
|
|
2595
|
+
}
|
|
2596
|
+
function collectSkillOption(value, previous) {
|
|
2597
|
+
return [...previous, ...splitSelectorList(value)];
|
|
2598
|
+
}
|
|
2599
|
+
function selectedArtifactsFromOptions(options) {
|
|
2600
|
+
return normalizeArtifactSelectors(options.select, options.skills ?? options.skill);
|
|
2601
|
+
}
|
|
2602
|
+
function filterUninstallPlanBySelection(plan, selected) {
|
|
2603
|
+
if (!selected?.length) return plan;
|
|
2604
|
+
const requested = normalizeArtifactSelectors(selected) ?? [];
|
|
2605
|
+
const available = new Set(plan.operations.map((operation) => `${operation.artifactType}/${operation.artifactName}`));
|
|
2606
|
+
const missing = requested.filter((selector) => !available.has(selector));
|
|
2607
|
+
if (missing.length > 0) {
|
|
2608
|
+
throw new Error(`Selected artifact not found in install manifest: ${missing.join(", ")}`);
|
|
2609
|
+
}
|
|
2610
|
+
const selectedSet = new Set(requested);
|
|
2611
|
+
const operations = plan.operations.map((operation) => {
|
|
2612
|
+
if (selectedSet.has(`${operation.artifactType}/${operation.artifactName}`)) return operation;
|
|
2613
|
+
return {
|
|
2614
|
+
...operation,
|
|
2615
|
+
action: "skip",
|
|
2616
|
+
desiredHash: operation.desiredHash ?? operation.manifestHash,
|
|
2617
|
+
reason: "not selected for uninstall"
|
|
2618
|
+
};
|
|
2619
|
+
});
|
|
2620
|
+
return {
|
|
2621
|
+
...plan,
|
|
2622
|
+
operations,
|
|
2623
|
+
hasBlockingChanges: operations.some((operation) => operation.action === "conflict")
|
|
2624
|
+
};
|
|
2625
|
+
}
|
|
2355
2626
|
async function initPackage(root) {
|
|
2356
|
-
await
|
|
2357
|
-
await
|
|
2358
|
-
await
|
|
2359
|
-
const manifestPath =
|
|
2627
|
+
await mkdir8(join20(root, "instructions"), { recursive: true });
|
|
2628
|
+
await mkdir8(join20(root, "rules"), { recursive: true });
|
|
2629
|
+
await mkdir8(join20(root, "skills"), { recursive: true });
|
|
2630
|
+
const manifestPath = join20(root, "agentwheel.json");
|
|
2360
2631
|
const manifest = {
|
|
2361
2632
|
schemaVersion: 1,
|
|
2362
2633
|
name: "example/agentwheel-package",
|
|
@@ -2367,9 +2638,9 @@ async function initPackage(root) {
|
|
|
2367
2638
|
{ type: "skills", path: "skills" }
|
|
2368
2639
|
]
|
|
2369
2640
|
};
|
|
2370
|
-
await
|
|
2641
|
+
await writeFile5(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
2371
2642
|
`, "utf8");
|
|
2372
|
-
await
|
|
2643
|
+
await writeFile5(join20(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
|
|
2373
2644
|
}
|
|
2374
2645
|
function formatUninstallResult(result) {
|
|
2375
2646
|
const removedLabel = result.removed === 1 ? "managed file" : "managed files";
|
|
@@ -2387,7 +2658,16 @@ function printRegistryEntries(entries) {
|
|
|
2387
2658
|
console.log(`${entry.name} ${entry.type} ${entry.source} ${entry.description}${tags}`);
|
|
2388
2659
|
}
|
|
2389
2660
|
}
|
|
2390
|
-
|
|
2661
|
+
async function main() {
|
|
2662
|
+
await maybeCheckForUpdate({
|
|
2663
|
+
currentVersion: "0.6.0",
|
|
2664
|
+
argv: process.argv,
|
|
2665
|
+
env: process.env,
|
|
2666
|
+
isTTY: process.stderr.isTTY === true
|
|
2667
|
+
});
|
|
2668
|
+
await program.parseAsync();
|
|
2669
|
+
}
|
|
2670
|
+
main().catch((error) => {
|
|
2391
2671
|
console.error(error instanceof Error ? error.message : String(error));
|
|
2392
2672
|
process.exitCode = 1;
|
|
2393
2673
|
});
|