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.
Files changed (3) hide show
  1. package/README.md +30 -2
  2. package/dist/index.js +233 -46
  3. 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.5).** The lifecycle core is real and tested — local/git/skillkit/vercel
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, rich JSON merge, and pluggable adapters.
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 mkdir7, rm as rm8, writeFile as writeFile4 } from "fs/promises";
12
- import { join as join19 } from "path";
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
- if (kept.length > 0) {
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: kept.map((operation) => {
497
+ entries: preserved.map((operation) => {
495
498
  if (!operation.manifestHash || !operation.desiredHash) {
496
- throw new Error(`Invalid kept operation missing manifest/source hash: ${operation.relativeDestPath}`);
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 finalArtifacts = options.workspaceRoot && options.adapter ? await applyCustomizations(stagedArtifacts, {
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
- }) : stagedArtifacts;
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.5.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: join19(targetRoot, ".agentwheel", "cache"),
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: join19(targetRoot, ".agentwheel", "cache") }))));
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: join19(targetRoot, ".agentwheel", "cache") }))));
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 mkdir7(join19(root, "instructions"), { recursive: true });
2450
- await mkdir7(join19(root, "rules"), { recursive: true });
2451
- await mkdir7(join19(root, "skills"), { recursive: true });
2452
- const manifestPath = join19(root, "agentwheel.json");
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 writeFile4(manifestPath, `${JSON.stringify(manifest, null, 2)}
2641
+ await writeFile5(manifestPath, `${JSON.stringify(manifest, null, 2)}
2464
2642
  `, "utf8");
2465
- await writeFile4(join19(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
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
- program.parseAsync().catch((error) => {
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentwheel",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Weave skills, rules, and instructions across every AI agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",