agentwheel 0.5.0 → 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 +30 -2
- package/dist/index.js +233 -46
- 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, asset-includes,
|
|
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,28 @@ 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
|
+
|
|
150
177
|
Packages can compose shared files into each directory artifact at staging time. This keeps one
|
|
151
178
|
canonical copy in the package repo while installing self-contained skills:
|
|
152
179
|
|
|
@@ -244,6 +271,7 @@ clear conversion format.
|
|
|
244
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.
|
|
245
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`.
|
|
246
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.
|
|
247
275
|
|
|
248
276
|
## Design docs
|
|
249
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
|
|
@@ -50,7 +50,8 @@ var artifactSchema = z.object({
|
|
|
50
50
|
hash: z.string().min(16),
|
|
51
51
|
packageName: z.string().min(1).optional(),
|
|
52
52
|
channel: z.enum(["managed", "overlay", "addition", "override", "ejected"]).default("managed"),
|
|
53
|
-
assets: z.array(packageAssetSchema).optional()
|
|
53
|
+
assets: z.array(packageAssetSchema).optional(),
|
|
54
|
+
required: z.boolean().optional()
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
// src/model/adapter.ts
|
|
@@ -476,6 +477,7 @@ async function uninstall(plan, options = {}) {
|
|
|
476
477
|
}
|
|
477
478
|
const removable = plan.operations.filter((operation) => operation.action === "remove" || resolvedOptions.force && operation.action === "keep");
|
|
478
479
|
const kept = resolvedOptions.force ? [] : plan.operations.filter((operation) => operation.action === "keep");
|
|
480
|
+
const skipped = plan.operations.filter((operation) => operation.action === "skip");
|
|
479
481
|
const removedDrifted = resolvedOptions.force ? plan.operations.filter((operation) => operation.action === "keep").length : 0;
|
|
480
482
|
if (resolvedOptions.dryRun) return { removed: removable.length, kept: kept.length, removedDrifted };
|
|
481
483
|
for (const operation of plan.operations) {
|
|
@@ -483,7 +485,8 @@ async function uninstall(plan, options = {}) {
|
|
|
483
485
|
await rm2(operation.destPath, { recursive: true, force: true });
|
|
484
486
|
}
|
|
485
487
|
}
|
|
486
|
-
|
|
488
|
+
const preserved = [...kept, ...skipped];
|
|
489
|
+
if (preserved.length > 0) {
|
|
487
490
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
488
491
|
await writeInstallManifest({
|
|
489
492
|
version: 1,
|
|
@@ -491,9 +494,9 @@ async function uninstall(plan, options = {}) {
|
|
|
491
494
|
targetRoot: plan.targetRoot,
|
|
492
495
|
generatedAt: now,
|
|
493
496
|
adapterCode: plan.adapterCode,
|
|
494
|
-
entries:
|
|
497
|
+
entries: preserved.map((operation) => {
|
|
495
498
|
if (!operation.manifestHash || !operation.desiredHash) {
|
|
496
|
-
throw new Error(`Invalid
|
|
499
|
+
throw new Error(`Invalid preserved operation missing manifest/source hash: ${operation.relativeDestPath}`);
|
|
497
500
|
}
|
|
498
501
|
return {
|
|
499
502
|
path: operation.relativeDestPath,
|
|
@@ -746,11 +749,14 @@ async function createUninstallPlan(manifest) {
|
|
|
746
749
|
kind: entry.kind,
|
|
747
750
|
destPath,
|
|
748
751
|
relativeDestPath: entry.path,
|
|
752
|
+
desiredHash: entry.sourceHash,
|
|
749
753
|
currentHash,
|
|
750
754
|
manifestHash: entry.hash,
|
|
751
755
|
reason: "uninstall managed artifact",
|
|
752
756
|
channel: entry.channel,
|
|
753
|
-
packageName: entry.packageName
|
|
757
|
+
packageName: entry.packageName,
|
|
758
|
+
semanticCommand: entry.semanticCommand,
|
|
759
|
+
mergeStrategy: entry.mergeStrategy
|
|
754
760
|
});
|
|
755
761
|
}
|
|
756
762
|
}
|
|
@@ -812,7 +818,8 @@ import { z as z4 } from "zod";
|
|
|
812
818
|
var packageProvideSchema = z4.object({
|
|
813
819
|
type: artifactTypeSchema,
|
|
814
820
|
path: z4.string().min(1),
|
|
815
|
-
assets: z4.array(packageAssetSchema).optional()
|
|
821
|
+
assets: z4.array(packageAssetSchema).optional(),
|
|
822
|
+
required: z4.boolean().optional()
|
|
816
823
|
});
|
|
817
824
|
var packageManifestSchema = z4.object({
|
|
818
825
|
schemaVersion: z4.literal(1),
|
|
@@ -981,7 +988,7 @@ async function listFromManifest(root, packageName) {
|
|
|
981
988
|
const stats = await stat2(full);
|
|
982
989
|
if (provide.type === "instructions") {
|
|
983
990
|
if (stats.isFile()) {
|
|
984
|
-
artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName, provide.assets));
|
|
991
|
+
artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName, provide.assets, provide.required));
|
|
985
992
|
}
|
|
986
993
|
continue;
|
|
987
994
|
}
|
|
@@ -989,16 +996,16 @@ async function listFromManifest(root, packageName) {
|
|
|
989
996
|
for (const entry of await sortedDirEntries(full)) {
|
|
990
997
|
const child = join6(full, entry.name);
|
|
991
998
|
if (provide.type === "skills" && entry.isDirectory()) {
|
|
992
|
-
artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName, provide.assets));
|
|
999
|
+
artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName, provide.assets, provide.required));
|
|
993
1000
|
} else if (provide.type === "plugins" && entry.isDirectory()) {
|
|
994
|
-
artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName, provide.assets));
|
|
1001
|
+
artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName, provide.assets, provide.required));
|
|
995
1002
|
} else if (entry.isFile()) {
|
|
996
1003
|
const name = provide.type === "rules" && entry.name.endsWith(".md") ? entry.name : entry.name;
|
|
997
|
-
artifacts.push(await artifactForFile(provide.type, name, child, join6(provide.path, entry.name), packageName, provide.assets));
|
|
1004
|
+
artifacts.push(await artifactForFile(provide.type, name, child, join6(provide.path, entry.name), packageName, provide.assets, provide.required));
|
|
998
1005
|
}
|
|
999
1006
|
}
|
|
1000
1007
|
} else if (stats.isFile()) {
|
|
1001
|
-
artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName, provide.assets));
|
|
1008
|
+
artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName, provide.assets, provide.required));
|
|
1002
1009
|
}
|
|
1003
1010
|
}
|
|
1004
1011
|
return artifacts;
|
|
@@ -1015,7 +1022,7 @@ async function listGenericArtifacts(type, dir, relativeRoot, packageName) {
|
|
|
1015
1022
|
}
|
|
1016
1023
|
return artifacts;
|
|
1017
1024
|
}
|
|
1018
|
-
async function artifactForFile(type, name, sourcePath, relativePath, packageName, assets) {
|
|
1025
|
+
async function artifactForFile(type, name, sourcePath, relativePath, packageName, assets, required) {
|
|
1019
1026
|
return {
|
|
1020
1027
|
type,
|
|
1021
1028
|
name,
|
|
@@ -1025,10 +1032,11 @@ async function artifactForFile(type, name, sourcePath, relativePath, packageName
|
|
|
1025
1032
|
hash: await hashPath(sourcePath),
|
|
1026
1033
|
packageName,
|
|
1027
1034
|
channel: "managed",
|
|
1028
|
-
assets
|
|
1035
|
+
assets,
|
|
1036
|
+
required
|
|
1029
1037
|
};
|
|
1030
1038
|
}
|
|
1031
|
-
async function artifactForDir(type, name, sourcePath, relativePath, packageName, assets) {
|
|
1039
|
+
async function artifactForDir(type, name, sourcePath, relativePath, packageName, assets, required) {
|
|
1032
1040
|
return {
|
|
1033
1041
|
type,
|
|
1034
1042
|
name,
|
|
@@ -1038,7 +1046,8 @@ async function artifactForDir(type, name, sourcePath, relativePath, packageName,
|
|
|
1038
1046
|
hash: await hashPath(sourcePath),
|
|
1039
1047
|
packageName,
|
|
1040
1048
|
channel: "managed",
|
|
1041
|
-
assets
|
|
1049
|
+
assets,
|
|
1050
|
+
required
|
|
1042
1051
|
};
|
|
1043
1052
|
}
|
|
1044
1053
|
|
|
@@ -1583,6 +1592,46 @@ async function sortedDirEntries2(path) {
|
|
|
1583
1592
|
return (await readdir3(path, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
|
|
1584
1593
|
}
|
|
1585
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
|
+
|
|
1586
1635
|
// src/staging/staging.ts
|
|
1587
1636
|
async function stageSource(driver, source, options = {}) {
|
|
1588
1637
|
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(source, options))));
|
|
@@ -1601,12 +1650,13 @@ async function stageSource(driver, source, options = {}) {
|
|
|
1601
1650
|
channel: artifact.channel ?? "managed"
|
|
1602
1651
|
});
|
|
1603
1652
|
}
|
|
1604
|
-
const
|
|
1653
|
+
const selectedArtifacts = filterArtifactsBySelection(stagedArtifacts, options.select, options.skills);
|
|
1654
|
+
const finalArtifacts = options.workspaceRoot && options.adapter ? await applyCustomizations(selectedArtifacts, {
|
|
1605
1655
|
workspaceRoot: options.workspaceRoot,
|
|
1606
1656
|
adapter: options.adapter,
|
|
1607
1657
|
stageRoot: root,
|
|
1608
1658
|
packageName: resolved.packageName
|
|
1609
|
-
}) :
|
|
1659
|
+
}) : selectedArtifacts;
|
|
1610
1660
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1611
1661
|
return {
|
|
1612
1662
|
root,
|
|
@@ -1731,7 +1781,9 @@ var workspacePackageSchema = z5.object({
|
|
|
1731
1781
|
adapterModule: z5.string().min(1).optional(),
|
|
1732
1782
|
adapterCodeHash: z5.string().min(16).optional(),
|
|
1733
1783
|
mode: z5.enum(["pinned", "tracking"]).default("pinned"),
|
|
1734
|
-
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()
|
|
1735
1787
|
});
|
|
1736
1788
|
var workspaceProfileRuntimeSchema = z5.object({
|
|
1737
1789
|
agent: z5.string().min(1).optional(),
|
|
@@ -2040,7 +2092,9 @@ async function syncProfile(options) {
|
|
|
2040
2092
|
workspaceRoot: options.workspaceRoot,
|
|
2041
2093
|
adapter,
|
|
2042
2094
|
cacheRoot: join16(options.workspaceRoot, ".agentwheel", "cache"),
|
|
2043
|
-
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
|
|
2044
2098
|
});
|
|
2045
2099
|
try {
|
|
2046
2100
|
const plan = await createInstallPlan(bundle, adapter, target.targetRoot, await readInstallManifest(target.targetRoot, adapter.name));
|
|
@@ -2074,7 +2128,9 @@ async function packageFromSource(source, options) {
|
|
|
2074
2128
|
source: resolved.source,
|
|
2075
2129
|
driver,
|
|
2076
2130
|
adapter: "openclaw",
|
|
2077
|
-
mode: options.mode ?? "pinned"
|
|
2131
|
+
mode: options.mode ?? "pinned",
|
|
2132
|
+
select: options.select,
|
|
2133
|
+
skills: options.skills
|
|
2078
2134
|
};
|
|
2079
2135
|
}
|
|
2080
2136
|
|
|
@@ -2089,7 +2145,9 @@ async function createSourcePlan(options) {
|
|
|
2089
2145
|
workspaceRoot,
|
|
2090
2146
|
adapter: options.adapter,
|
|
2091
2147
|
cacheRoot: join17(workspaceRoot, ".agentwheel", "cache"),
|
|
2092
|
-
mode: options.mode
|
|
2148
|
+
mode: options.mode,
|
|
2149
|
+
select: options.select,
|
|
2150
|
+
skills: options.skills
|
|
2093
2151
|
});
|
|
2094
2152
|
const manifest = await readInstallManifest(options.targetRoot, options.adapter.name);
|
|
2095
2153
|
const plan = await createInstallPlan(bundle, options.adapter, options.targetRoot, manifest);
|
|
@@ -2201,9 +2259,86 @@ function dedupeTargets(matches) {
|
|
|
2201
2259
|
return [...byKey.values()];
|
|
2202
2260
|
}
|
|
2203
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
|
+
|
|
2204
2339
|
// src/cli/index.ts
|
|
2205
2340
|
var program = new Command();
|
|
2206
|
-
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);
|
|
2207
2342
|
program.command("init").argument("[kind]", "workspace or package", "workspace").option("--target-root <path>", "workspace root", process.cwd()).action(async (kind, options) => {
|
|
2208
2343
|
const root = normalizeTargetRoot(options.targetRoot);
|
|
2209
2344
|
if (kind === "package") {
|
|
@@ -2217,8 +2352,9 @@ program.command("init").argument("[kind]", "workspace or package", "workspace").
|
|
|
2217
2352
|
await writeWorkspaceConfig(root, await readWorkspaceConfig(root));
|
|
2218
2353
|
console.log("Initialized .agentwheel/config.json.");
|
|
2219
2354
|
});
|
|
2220
|
-
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) => {
|
|
2221
2356
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
2357
|
+
const selectedArtifacts = selectedArtifactsFromOptions(options);
|
|
2222
2358
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
2223
2359
|
const resolvedSource = resolvedInput.source;
|
|
2224
2360
|
const driverName = options.driver ?? inferSourceDriverName(resolvedSource);
|
|
@@ -2234,8 +2370,9 @@ program.command("add").argument("<source>", "package source").option("--driver <
|
|
|
2234
2370
|
const bundle = await stageSource(driver, resolvedSource, {
|
|
2235
2371
|
workspaceRoot: targetRoot,
|
|
2236
2372
|
adapter,
|
|
2237
|
-
cacheRoot:
|
|
2238
|
-
mode: options.mode
|
|
2373
|
+
cacheRoot: join20(targetRoot, ".agentwheel", "cache"),
|
|
2374
|
+
mode: options.mode,
|
|
2375
|
+
select: selectedArtifacts
|
|
2239
2376
|
});
|
|
2240
2377
|
const name = options.name ?? resolvedInput.registryEntry?.name ?? bundle.source.packageName ?? source;
|
|
2241
2378
|
const entry = {
|
|
@@ -2247,18 +2384,20 @@ program.command("add").argument("<source>", "package source").option("--driver <
|
|
|
2247
2384
|
adapterModule: options.adapterModule,
|
|
2248
2385
|
adapterCodeHash: adapter.programmatic?.hash,
|
|
2249
2386
|
mode: options.mode,
|
|
2250
|
-
requestedRef: bundle.source.requestedRef
|
|
2387
|
+
requestedRef: bundle.source.requestedRef,
|
|
2388
|
+
select: selectedArtifacts
|
|
2251
2389
|
};
|
|
2252
2390
|
await writeWorkspaceConfig(targetRoot, upsertPackage(await readWorkspaceConfig(targetRoot), entry));
|
|
2253
2391
|
await rm8(bundle.root, { recursive: true, force: true });
|
|
2254
2392
|
console.log(`Added ${name}.`);
|
|
2255
2393
|
});
|
|
2256
|
-
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) => {
|
|
2257
2395
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
2396
|
+
const selectedArtifacts = selectedArtifactsFromOptions(options);
|
|
2258
2397
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
2259
2398
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
2260
|
-
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot:
|
|
2261
|
-
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);
|
|
2262
2401
|
for (const artifact of artifacts) {
|
|
2263
2402
|
console.log(`${artifact.type} ${artifact.name} ${artifact.relativePath}`);
|
|
2264
2403
|
}
|
|
@@ -2267,7 +2406,7 @@ program.command("scan").argument("<source>", "package source").option("--driver
|
|
|
2267
2406
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
2268
2407
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
2269
2408
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
2270
|
-
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") }))));
|
|
2271
2410
|
const result = await driver.scan(resolved);
|
|
2272
2411
|
if (result.findings.length === 0) {
|
|
2273
2412
|
console.log("Scan ok: no findings");
|
|
@@ -2278,7 +2417,7 @@ program.command("scan").argument("<source>", "package source").option("--driver
|
|
|
2278
2417
|
}
|
|
2279
2418
|
if (!result.ok) process.exitCode = 1;
|
|
2280
2419
|
});
|
|
2281
|
-
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) => {
|
|
2282
2421
|
const targets = await resolveCliTargets(options);
|
|
2283
2422
|
for (const target of targets) {
|
|
2284
2423
|
const { plan, bundle } = await buildPlan(source, target, options);
|
|
@@ -2287,7 +2426,7 @@ program.command("plan").argument("<source>", "source directory").option("--drive
|
|
|
2287
2426
|
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
2288
2427
|
}
|
|
2289
2428
|
});
|
|
2290
|
-
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) => {
|
|
2291
2430
|
if (options.profile) {
|
|
2292
2431
|
const target = await resolveRuntimeTarget({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent });
|
|
2293
2432
|
const results = await syncProfile({
|
|
@@ -2296,6 +2435,7 @@ program.command("sync").argument("[source]", "source directory").option("--drive
|
|
|
2296
2435
|
source,
|
|
2297
2436
|
driver: options.driver,
|
|
2298
2437
|
mode: options.mode,
|
|
2438
|
+
select: selectedArtifactsFromOptions(options),
|
|
2299
2439
|
dryRun: options.dryRun,
|
|
2300
2440
|
executePlugins: options.executePlugins,
|
|
2301
2441
|
allowAdapterCode: options.allowAdapterCode,
|
|
@@ -2327,7 +2467,7 @@ program.command("sync").argument("[source]", "source directory").option("--drive
|
|
|
2327
2467
|
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
2328
2468
|
}
|
|
2329
2469
|
});
|
|
2330
|
-
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) => {
|
|
2331
2471
|
const targets = await resolveCliTargets(options);
|
|
2332
2472
|
for (const target of targets) {
|
|
2333
2473
|
await runConfiguredPackages(target, options, { useUpdateDecision: true });
|
|
@@ -2360,7 +2500,7 @@ program.command("eject").argument("<item>", "package/type/name").option("--targe
|
|
|
2360
2500
|
const result = await ejectArtifact(targetRoot, item);
|
|
2361
2501
|
console.log(`Ejected ${item} to ${result.ejectedPath}.`);
|
|
2362
2502
|
});
|
|
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) => {
|
|
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) => {
|
|
2364
2504
|
const targets = await resolveCliTargets(options);
|
|
2365
2505
|
for (const target of targets) {
|
|
2366
2506
|
const adapter = await resolveAdapterForTarget(target, options);
|
|
@@ -2369,7 +2509,7 @@ program.command("uninstall").option("--adapter <adapter>", "adapter", "openclaw"
|
|
|
2369
2509
|
console.log(`No install manifest for ${adapter.name} at ${target.targetRoot}`);
|
|
2370
2510
|
continue;
|
|
2371
2511
|
}
|
|
2372
|
-
const plan = await createUninstallPlan(manifest);
|
|
2512
|
+
const plan = filterUninstallPlanBySelection(await createUninstallPlan(manifest), selectedArtifactsFromOptions(options));
|
|
2373
2513
|
console.log(formatPlan(plan));
|
|
2374
2514
|
const result = await uninstall(plan, { dryRun: options.dryRun, force: options.force });
|
|
2375
2515
|
if (!options.dryRun) {
|
|
@@ -2387,7 +2527,9 @@ async function buildPlan(source, target, options) {
|
|
|
2387
2527
|
workspaceRoot: target.workspaceRoot,
|
|
2388
2528
|
adapter,
|
|
2389
2529
|
driver: options.driver,
|
|
2390
|
-
mode: options.mode
|
|
2530
|
+
mode: options.mode,
|
|
2531
|
+
select: options.select ?? selectedArtifactsFromOptions(options),
|
|
2532
|
+
skills: options.skills
|
|
2391
2533
|
});
|
|
2392
2534
|
return { plan: result.plan, bundle: result.bundle };
|
|
2393
2535
|
}
|
|
@@ -2413,6 +2555,7 @@ async function runConfiguredPackages(target, options, behavior) {
|
|
|
2413
2555
|
console.log(`No packages configured at ${target.workspaceRoot}.`);
|
|
2414
2556
|
return;
|
|
2415
2557
|
}
|
|
2558
|
+
const selectedArtifacts = selectedArtifactsFromOptions(options);
|
|
2416
2559
|
for (const pkg of config.packages) {
|
|
2417
2560
|
const targetForPackage = options.adapter || target.source !== "cwd" ? target : { ...target, adapter: pkg.adapter };
|
|
2418
2561
|
const adapter = await resolveAdapterForTarget(targetForPackage, {
|
|
@@ -2433,7 +2576,9 @@ async function runConfiguredPackages(target, options, behavior) {
|
|
|
2433
2576
|
adapterConfig: options.adapterConfig ?? pkg.adapterConfig,
|
|
2434
2577
|
adapterModule: options.adapterModule ?? pkg.adapterModule,
|
|
2435
2578
|
allowAdapterCode: options.allowAdapterCode,
|
|
2436
|
-
mode: pkg.mode
|
|
2579
|
+
mode: pkg.mode,
|
|
2580
|
+
select: selectedArtifacts ?? pkg.select,
|
|
2581
|
+
skills: selectedArtifacts ? void 0 : pkg.skills
|
|
2437
2582
|
});
|
|
2438
2583
|
console.log(`${behavior.useUpdateDecision ? "Update" : "Sync"} ${pkg.name} (${adapter.name} at ${targetForPackage.targetRoot}):`);
|
|
2439
2584
|
console.log(formatPlan(plan));
|
|
@@ -2445,11 +2590,44 @@ async function runConfiguredPackages(target, options, behavior) {
|
|
|
2445
2590
|
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
2446
2591
|
}
|
|
2447
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
|
+
}
|
|
2448
2626
|
async function initPackage(root) {
|
|
2449
|
-
await
|
|
2450
|
-
await
|
|
2451
|
-
await
|
|
2452
|
-
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");
|
|
2453
2631
|
const manifest = {
|
|
2454
2632
|
schemaVersion: 1,
|
|
2455
2633
|
name: "example/agentwheel-package",
|
|
@@ -2460,9 +2638,9 @@ async function initPackage(root) {
|
|
|
2460
2638
|
{ type: "skills", path: "skills" }
|
|
2461
2639
|
]
|
|
2462
2640
|
};
|
|
2463
|
-
await
|
|
2641
|
+
await writeFile5(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
2464
2642
|
`, "utf8");
|
|
2465
|
-
await
|
|
2643
|
+
await writeFile5(join20(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
|
|
2466
2644
|
}
|
|
2467
2645
|
function formatUninstallResult(result) {
|
|
2468
2646
|
const removedLabel = result.removed === 1 ? "managed file" : "managed files";
|
|
@@ -2480,7 +2658,16 @@ function printRegistryEntries(entries) {
|
|
|
2480
2658
|
console.log(`${entry.name} ${entry.type} ${entry.source} ${entry.description}${tags}`);
|
|
2481
2659
|
}
|
|
2482
2660
|
}
|
|
2483
|
-
|
|
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) => {
|
|
2484
2671
|
console.error(error instanceof Error ? error.message : String(error));
|
|
2485
2672
|
process.exitCode = 1;
|
|
2486
2673
|
});
|