agentwheel 0.5.0 → 0.7.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 +45 -2
  2. package/dist/index.js +393 -98
  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.7).** 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, full Claude/Codex adapters, rich JSON/TOML 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
 
@@ -233,6 +260,20 @@ agentwheel sync ./my-pack --adapter-config ./myco-internal.jsonc
233
260
  Built-in adapters ship for common runtimes; declarative adapters need no code and stay private.
234
261
  Programmatic adapters, for private runtime logic beyond file placement, require explicit `--allow-adapter-code`.
235
262
 
263
+ Built-in runtime targets:
264
+
265
+ | Runtime | Main targets |
266
+ |---|---|
267
+ | **OpenClaw** | `.openclaw/AGENTS.md`, `.openclaw/skills`, `.openclaw/rules`, `.openclaw/commands`, MCP/hooks/settings, semantic plugin planning |
268
+ | **Claude Code** | `.claude/CLAUDE.md`, `.claude/skills`, `.claude/commands`, `.claude/agents`, `.claude/rules`, `.claude/.mcp.json`, `.claude/settings.json` |
269
+ | **Codex CLI** | `.codex/AGENTS.md`, `.codex/skills`, `.codex/commands`, `.codex/agents`, `.codex/rules`, `.codex/config.toml`, `.codex/hooks.json` |
270
+ | **Hermes** | `.hermes/AGENTS.md`, `.hermes/skills`, `.hermes/rules`, `.hermes/commands`, MCP/hooks/settings |
271
+ | **GitHub Copilot** | `.github/copilot-instructions.md`, `.github/instructions`, `.github/prompts` |
272
+
273
+ Claude MCP and hooks/settings are merged as JSON. Codex MCP entries are merged into
274
+ `[mcp_servers]` in `.codex/config.toml` without deleting unrelated user config; hooks use
275
+ `.codex/hooks.json`.
276
+
236
277
  Copilot support is intentionally file-drop only: instructions, rules, and prompt/command files are
237
278
  placed in GitHub-native locations, while raw `SKILL.md` directories stay disabled until there is a
238
279
  clear conversion format.
@@ -244,6 +285,8 @@ clear conversion format.
244
285
  - [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
286
  - [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
287
  - [x] **v0.5** — asset-includes compose shared files into skills at install time; executable bits preserved; hashes include composed assets.
288
+ - [x] **v0.6** — selective installs with `--select`/`--skill`; required artifacts; cached npm update notifier.
289
+ - [x] **v0.7** — full Claude/Codex adapters; Codex TOML MCP merge; subagent enumeration; `--skill` selector fix.
247
290
 
248
291
  ## Design docs
249
292
 
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 mkdir9, rm as rm8, writeFile as writeFile6 } 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
@@ -58,7 +59,7 @@ var targetMappingSchema = z2.object({
58
59
  dest: z2.string().min(1),
59
60
  enabled: z2.boolean().default(true),
60
61
  semantic: z2.enum(["openclaw-plugin"]).optional(),
61
- merge: z2.enum(["json-deep"]).optional()
62
+ merge: z2.enum(["json-deep", "codex-toml-mcp"]).optional()
62
63
  });
63
64
  var adapterSchema = z2.object({
64
65
  name: z2.string().min(1),
@@ -85,8 +86,17 @@ async function loadAdapterConfig(path) {
85
86
  // src/adapters/claude.ts
86
87
  var claudeAdapter = {
87
88
  name: "claude",
88
- displayName: "Claude",
89
- targets: {}
89
+ displayName: "Claude Code",
90
+ targets: {
91
+ instructions: { enabled: true, dest: ".claude/CLAUDE.md" },
92
+ rules: { enabled: true, dest: ".claude/rules" },
93
+ skills: { enabled: true, dest: ".claude/skills" },
94
+ commands: { enabled: true, dest: ".claude/commands" },
95
+ subagents: { enabled: true, dest: ".claude/agents" },
96
+ mcp: { enabled: true, dest: ".claude/.mcp.json", merge: "json-deep" },
97
+ hooks: { enabled: true, dest: ".claude/settings.json", merge: "json-deep" },
98
+ settings: { enabled: true, dest: ".claude/settings.json", merge: "json-deep" }
99
+ }
90
100
  };
91
101
 
92
102
  // src/adapters/copilot.ts
@@ -106,8 +116,17 @@ var copilotAdapter = {
106
116
  // src/adapters/codex.ts
107
117
  var codexAdapter = {
108
118
  name: "codex",
109
- displayName: "Codex",
110
- targets: {}
119
+ displayName: "Codex CLI",
120
+ targets: {
121
+ instructions: { enabled: true, dest: ".codex/AGENTS.md" },
122
+ rules: { enabled: true, dest: ".codex/rules" },
123
+ skills: { enabled: true, dest: ".codex/skills" },
124
+ commands: { enabled: true, dest: ".codex/commands" },
125
+ subagents: { enabled: true, dest: ".codex/agents" },
126
+ mcp: { enabled: true, dest: ".codex/config.toml", merge: "codex-toml-mcp" },
127
+ hooks: { enabled: true, dest: ".codex/hooks.json", merge: "json-deep" },
128
+ settings: { enabled: true, dest: ".codex/settings.json", merge: "json-deep" }
129
+ }
111
130
  };
112
131
 
113
132
  // src/adapters/hermes.ts
@@ -281,7 +300,7 @@ var manifestEntrySchema = z3.object({
281
300
  packageName: z3.string().min(1).optional(),
282
301
  semanticCommand: z3.array(z3.string()).optional(),
283
302
  executed: z3.boolean().optional(),
284
- mergeStrategy: z3.enum(["json-deep"]).optional()
303
+ mergeStrategy: z3.enum(["json-deep", "codex-toml-mcp"]).optional()
285
304
  });
286
305
  var installManifestSchema = z3.object({
287
306
  version: z3.literal(1),
@@ -354,6 +373,94 @@ function normalizeTargetRoot(path) {
354
373
  return resolve3(path);
355
374
  }
356
375
 
376
+ // src/install/toml-merge.ts
377
+ import { mkdir as mkdir2, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
378
+ import { dirname as dirname2 } from "path";
379
+ async function mergeCodexTomlMcp(sourcePath, destPath) {
380
+ const source = JSON.parse(await readFile5(sourcePath, "utf8"));
381
+ const servers = extractMcpServers(source);
382
+ const current = await pathExists(destPath) ? await readFile5(destPath, "utf8") : "";
383
+ const withoutManaged = removeManagedMcpSections(current, Object.keys(servers));
384
+ const merged = appendMcpServers(withoutManaged, servers);
385
+ await mkdir2(dirname2(destPath), { recursive: true });
386
+ await writeFile3(destPath, merged, "utf8");
387
+ }
388
+ function extractMcpServers(source) {
389
+ const raw = isRecord2(source.mcpServers) ? source.mcpServers : source;
390
+ const servers = {};
391
+ for (const [name, value] of Object.entries(raw)) {
392
+ if (!isRecord2(value)) continue;
393
+ servers[name] = value;
394
+ }
395
+ if (Object.keys(servers).length === 0) {
396
+ throw new Error("Codex MCP TOML merge needs a JSON object with mcpServers");
397
+ }
398
+ return servers;
399
+ }
400
+ function removeManagedMcpSections(content, serverNames) {
401
+ if (serverNames.length === 0 || content.trim() === "") return content;
402
+ const names = new Set(serverNames);
403
+ const lines = content.split(/\r?\n/);
404
+ const kept = [];
405
+ let skipping = false;
406
+ for (const line of lines) {
407
+ const section = line.match(/^\s*\[([^\]]+)]\s*$/)?.[1];
408
+ if (section) {
409
+ const match = section.match(/^mcp_servers\.([^.\]]+)(?:\.|$)/);
410
+ skipping = match ? names.has(unquoteTomlKey(match[1] ?? "")) : false;
411
+ }
412
+ if (!skipping) kept.push(line);
413
+ }
414
+ return kept.join("\n").replace(/\n{3,}$/g, "\n\n");
415
+ }
416
+ function appendMcpServers(content, servers) {
417
+ const blocks = Object.entries(servers).sort(([a], [b]) => a.localeCompare(b)).map(([name, server]) => formatMcpServer(name, server));
418
+ const prefix = content.trimEnd();
419
+ return `${prefix ? `${prefix}
420
+
421
+ ` : ""}${blocks.join("\n\n")}
422
+ `;
423
+ }
424
+ function formatMcpServer(name, server) {
425
+ const env = isRecord2(server.env) ? server.env : void 0;
426
+ const lines = [`[mcp_servers.${quoteTomlKey(name)}]`];
427
+ for (const [key, value] of Object.entries(server)) {
428
+ if (key === "env" || value === void 0) continue;
429
+ lines.push(`${quoteTomlKey(key)} = ${formatTomlValue(value)}`);
430
+ }
431
+ if (env && Object.keys(env).length > 0) {
432
+ lines.push("", `[mcp_servers.${quoteTomlKey(name)}.env]`);
433
+ for (const [key, value] of Object.entries(env)) {
434
+ lines.push(`${quoteTomlKey(key)} = ${formatTomlValue(value)}`);
435
+ }
436
+ }
437
+ return lines.join("\n");
438
+ }
439
+ function formatTomlValue(value) {
440
+ if (typeof value === "string") return JSON.stringify(value);
441
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
442
+ if (Array.isArray(value)) return `[${value.map(formatTomlValue).join(", ")}]`;
443
+ if (isRecord2(value)) {
444
+ const entries = Object.entries(value).map(([key, child]) => `${quoteTomlKey(key)} = ${formatTomlValue(child)}`);
445
+ return `{ ${entries.join(", ")} }`;
446
+ }
447
+ return '""';
448
+ }
449
+ function quoteTomlKey(key) {
450
+ return /^[A-Za-z0-9_-]+$/.test(key) ? key : JSON.stringify(key);
451
+ }
452
+ function unquoteTomlKey(key) {
453
+ if (!key.startsWith('"')) return key;
454
+ try {
455
+ return JSON.parse(key);
456
+ } catch {
457
+ return key;
458
+ }
459
+ }
460
+ function isRecord2(value) {
461
+ return typeof value === "object" && value !== null && !Array.isArray(value);
462
+ }
463
+
357
464
  // src/install/apply.ts
358
465
  var execFileAsync = promisify(execFile);
359
466
  async function applyInstallPlan(plan, sourceLock, options = {}) {
@@ -419,6 +526,8 @@ async function applyInstallPlan(plan, sourceLock, options = {}) {
419
526
  }
420
527
  if (operation.mergeStrategy === "json-deep") {
421
528
  await mergeJsonFile(operation.sourcePath, operation.destPath);
529
+ } else if (operation.mergeStrategy === "codex-toml-mcp") {
530
+ await mergeCodexTomlMcp(operation.sourcePath, operation.destPath);
422
531
  } else {
423
532
  await atomicCopy(operation.sourcePath, operation.destPath, operation.kind);
424
533
  }
@@ -444,7 +553,7 @@ async function applyInstallPlan(plan, sourceLock, options = {}) {
444
553
  artifactType: operation.artifactType,
445
554
  artifactName: operation.artifactName,
446
555
  kind: operation.kind,
447
- hash: operation.mergeStrategy === "json-deep" && operation.currentHash ? operation.currentHash : operation.desiredHash,
556
+ hash: operation.mergeStrategy && operation.currentHash ? operation.currentHash : operation.desiredHash,
448
557
  sourceHash: operation.desiredHash,
449
558
  updatedAt: now,
450
559
  channel: operation.channel,
@@ -476,6 +585,7 @@ async function uninstall(plan, options = {}) {
476
585
  }
477
586
  const removable = plan.operations.filter((operation) => operation.action === "remove" || resolvedOptions.force && operation.action === "keep");
478
587
  const kept = resolvedOptions.force ? [] : plan.operations.filter((operation) => operation.action === "keep");
588
+ const skipped = plan.operations.filter((operation) => operation.action === "skip");
479
589
  const removedDrifted = resolvedOptions.force ? plan.operations.filter((operation) => operation.action === "keep").length : 0;
480
590
  if (resolvedOptions.dryRun) return { removed: removable.length, kept: kept.length, removedDrifted };
481
591
  for (const operation of plan.operations) {
@@ -483,7 +593,8 @@ async function uninstall(plan, options = {}) {
483
593
  await rm2(operation.destPath, { recursive: true, force: true });
484
594
  }
485
595
  }
486
- if (kept.length > 0) {
596
+ const preserved = [...kept, ...skipped];
597
+ if (preserved.length > 0) {
487
598
  const now = (/* @__PURE__ */ new Date()).toISOString();
488
599
  await writeInstallManifest({
489
600
  version: 1,
@@ -491,9 +602,9 @@ async function uninstall(plan, options = {}) {
491
602
  targetRoot: plan.targetRoot,
492
603
  generatedAt: now,
493
604
  adapterCode: plan.adapterCode,
494
- entries: kept.map((operation) => {
605
+ entries: preserved.map((operation) => {
495
606
  if (!operation.manifestHash || !operation.desiredHash) {
496
- throw new Error(`Invalid kept operation missing manifest/source hash: ${operation.relativeDestPath}`);
607
+ throw new Error(`Invalid preserved operation missing manifest/source hash: ${operation.relativeDestPath}`);
497
608
  }
498
609
  return {
499
610
  path: operation.relativeDestPath,
@@ -571,7 +682,7 @@ async function createInstallPlan(bundle, adapter, targetRoot, manifest) {
571
682
  }
572
683
  continue;
573
684
  }
574
- if (op.mergeStrategy === "json-deep") {
685
+ if (op.mergeStrategy) {
575
686
  const existing2 = manifestByPath.get(op.relativeDestPath);
576
687
  const exists2 = await pathExists(op.destPath);
577
688
  if (!exists2) {
@@ -679,7 +790,7 @@ function operationForArtifact(artifact, adapter, targetRoot) {
679
790
  semanticCommand: openClawPluginInstallCommand({ path: sourcePath, dryRun: true })
680
791
  };
681
792
  }
682
- const destPath = artifact.type === "instructions" || artifact.type === "settings" ? join3(targetRoot, target.dest) : join3(targetRoot, target.dest, artifact.name);
793
+ const destPath = artifact.type === "instructions" || artifact.type === "settings" || isFileTarget(target.dest) ? join3(targetRoot, target.dest) : join3(targetRoot, target.dest, artifact.name);
683
794
  return {
684
795
  action: "create",
685
796
  artifactType: artifact.type,
@@ -695,6 +806,9 @@ function operationForArtifact(artifact, adapter, targetRoot) {
695
806
  mergeStrategy: target.merge
696
807
  };
697
808
  }
809
+ function isFileTarget(dest) {
810
+ return /\.(json|jsonc|toml|md)$/i.test(dest);
811
+ }
698
812
  function summarizePlan(plan) {
699
813
  const summary = {
700
814
  create: 0,
@@ -746,11 +860,14 @@ async function createUninstallPlan(manifest) {
746
860
  kind: entry.kind,
747
861
  destPath,
748
862
  relativeDestPath: entry.path,
863
+ desiredHash: entry.sourceHash,
749
864
  currentHash,
750
865
  manifestHash: entry.hash,
751
866
  reason: "uninstall managed artifact",
752
867
  channel: entry.channel,
753
- packageName: entry.packageName
868
+ packageName: entry.packageName,
869
+ semanticCommand: entry.semanticCommand,
870
+ mergeStrategy: entry.mergeStrategy
754
871
  });
755
872
  }
756
873
  }
@@ -799,20 +916,21 @@ function formatPlan(plan) {
799
916
 
800
917
  // src/source/git.ts
801
918
  import { execFile as execFile2 } from "child_process";
802
- import { mkdir as mkdir2, rm as rm3 } from "fs/promises";
919
+ import { mkdir as mkdir3, rm as rm3 } from "fs/promises";
803
920
  import { homedir } from "os";
804
921
  import { basename as basename2, join as join7, resolve as resolve5 } from "path";
805
922
  import { promisify as promisify2 } from "util";
806
923
 
807
924
  // src/model/package.ts
808
- import { readFile as readFile5 } from "fs/promises";
925
+ import { readFile as readFile6 } from "fs/promises";
809
926
  import { join as join5 } from "path";
810
927
  import { parse as parse2, printParseErrorCode as printParseErrorCode2 } from "jsonc-parser";
811
928
  import { z as z4 } from "zod";
812
929
  var packageProvideSchema = z4.object({
813
930
  type: artifactTypeSchema,
814
931
  path: z4.string().min(1),
815
- assets: z4.array(packageAssetSchema).optional()
932
+ assets: z4.array(packageAssetSchema).optional(),
933
+ required: z4.boolean().optional()
816
934
  });
817
935
  var packageManifestSchema = z4.object({
818
936
  schemaVersion: z4.literal(1),
@@ -830,7 +948,7 @@ async function findPackageManifestPath(root) {
830
948
  async function readPackageManifest(root) {
831
949
  const path = await findPackageManifestPath(root);
832
950
  if (!path) return void 0;
833
- const content = await readFile5(path, "utf8");
951
+ const content = await readFile6(path, "utf8");
834
952
  const errors = [];
835
953
  const parsed = parse2(content, errors, { allowTrailingComma: true, disallowComments: false });
836
954
  if (errors.length > 0) {
@@ -932,7 +1050,7 @@ var LocalSourceDriver = class {
932
1050
  }
933
1051
  }
934
1052
  }
935
- for (const type of ["commands", "mcp", "hooks", "settings", "plugins"]) {
1053
+ for (const type of ["commands", "subagents", "mcp", "hooks", "settings", "plugins"]) {
936
1054
  const dir = join6(root, type);
937
1055
  if (!await pathExists(dir)) continue;
938
1056
  artifacts.push(...await listGenericArtifacts(type, dir, type, resolved.packageName));
@@ -981,24 +1099,22 @@ async function listFromManifest(root, packageName) {
981
1099
  const stats = await stat2(full);
982
1100
  if (provide.type === "instructions") {
983
1101
  if (stats.isFile()) {
984
- artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName, provide.assets));
1102
+ artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName, provide.assets, provide.required));
985
1103
  }
986
1104
  continue;
987
1105
  }
988
1106
  if (stats.isDirectory()) {
989
1107
  for (const entry of await sortedDirEntries(full)) {
990
1108
  const child = join6(full, entry.name);
991
- if (provide.type === "skills" && entry.isDirectory()) {
992
- artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName, provide.assets));
993
- } 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));
1109
+ if ((provide.type === "skills" || provide.type === "plugins" || provide.type === "subagents") && entry.isDirectory()) {
1110
+ artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName, provide.assets, provide.required));
995
1111
  } else if (entry.isFile()) {
996
1112
  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));
1113
+ artifacts.push(await artifactForFile(provide.type, name, child, join6(provide.path, entry.name), packageName, provide.assets, provide.required));
998
1114
  }
999
1115
  }
1000
1116
  } else if (stats.isFile()) {
1001
- artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName, provide.assets));
1117
+ artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName, provide.assets, provide.required));
1002
1118
  }
1003
1119
  }
1004
1120
  return artifacts;
@@ -1015,7 +1131,7 @@ async function listGenericArtifacts(type, dir, relativeRoot, packageName) {
1015
1131
  }
1016
1132
  return artifacts;
1017
1133
  }
1018
- async function artifactForFile(type, name, sourcePath, relativePath, packageName, assets) {
1134
+ async function artifactForFile(type, name, sourcePath, relativePath, packageName, assets, required) {
1019
1135
  return {
1020
1136
  type,
1021
1137
  name,
@@ -1025,10 +1141,11 @@ async function artifactForFile(type, name, sourcePath, relativePath, packageName
1025
1141
  hash: await hashPath(sourcePath),
1026
1142
  packageName,
1027
1143
  channel: "managed",
1028
- assets
1144
+ assets,
1145
+ required
1029
1146
  };
1030
1147
  }
1031
- async function artifactForDir(type, name, sourcePath, relativePath, packageName, assets) {
1148
+ async function artifactForDir(type, name, sourcePath, relativePath, packageName, assets, required) {
1032
1149
  return {
1033
1150
  type,
1034
1151
  name,
@@ -1038,7 +1155,8 @@ async function artifactForDir(type, name, sourcePath, relativePath, packageName,
1038
1155
  hash: await hashPath(sourcePath),
1039
1156
  packageName,
1040
1157
  channel: "managed",
1041
- assets
1158
+ assets,
1159
+ required
1042
1160
  };
1043
1161
  }
1044
1162
 
@@ -1061,7 +1179,7 @@ var GitSourceDriver = class {
1061
1179
  }
1062
1180
  async fetch(resolved) {
1063
1181
  const parsed = parseGitSource(resolved.source);
1064
- await mkdir2(resolve5(resolved.resolvedPath, ".."), { recursive: true });
1182
+ await mkdir3(resolve5(resolved.resolvedPath, ".."), { recursive: true });
1065
1183
  if (!await pathExists(join7(resolved.resolvedPath, ".git"))) {
1066
1184
  await rm3(resolved.resolvedPath, { recursive: true, force: true });
1067
1185
  await git(["clone", "--no-tags", parsed.url, resolved.resolvedPath]);
@@ -1132,14 +1250,14 @@ async function git(args) {
1132
1250
  }
1133
1251
 
1134
1252
  // src/source/skillkit.ts
1135
- import { cp, mkdir as mkdir3, readFile as readFile6, rm as rm4 } from "fs/promises";
1253
+ import { cp, mkdir as mkdir4, readFile as readFile7, rm as rm4 } from "fs/promises";
1136
1254
  import { homedir as homedir2 } from "os";
1137
- import { basename as basename4, dirname as dirname3, join as join9, resolve as resolve6 } from "path";
1255
+ import { basename as basename4, dirname as dirname4, join as join9, resolve as resolve6 } from "path";
1138
1256
  import * as defaultSkillKit from "@skillkit/core";
1139
1257
 
1140
1258
  // src/source/skill-artifacts.ts
1141
1259
  import { readdir as readdir2, stat as stat3 } from "fs/promises";
1142
- import { basename as basename3, dirname as dirname2, extname as extname2, join as join8 } from "path";
1260
+ import { basename as basename3, dirname as dirname3, extname as extname2, join as join8 } from "path";
1143
1261
  async function artifactsFromSkillPaths(paths, packageName) {
1144
1262
  const artifacts = [];
1145
1263
  const seen = /* @__PURE__ */ new Set();
@@ -1176,7 +1294,7 @@ async function artifactFromSkillPath(item, packageName) {
1176
1294
  };
1177
1295
  }
1178
1296
  if (stats.isFile() && basename3(item.path).toLowerCase() === "skill.md") {
1179
- const dir = dirname2(item.path);
1297
+ const dir = dirname3(item.path);
1180
1298
  const name = sanitizeSkillName(item.name ?? basename3(dir));
1181
1299
  return {
1182
1300
  type: "skills",
@@ -1259,7 +1377,7 @@ var SkillKitSourceDriver = class {
1259
1377
  if (!provider?.clone) {
1260
1378
  throw new Error("SkillKit provider API unavailable or cannot resolve source. Expected @skillkit/core detectProvider().clone().");
1261
1379
  }
1262
- await mkdir3(dirname3(resolved.resolvedPath), { recursive: true });
1380
+ await mkdir4(dirname4(resolved.resolvedPath), { recursive: true });
1263
1381
  const result = await provider.clone(providerSpec, resolved.resolvedPath, {});
1264
1382
  if (!result.success || !result.path) {
1265
1383
  throw new Error(`SkillKit provider failed to fetch ${spec}: ${result.error ?? "unknown error"}`);
@@ -1305,7 +1423,7 @@ var SkillKitSourceDriver = class {
1305
1423
  for (const skill of this.discover(resolved.resolvedPath)) {
1306
1424
  const skillMd = join9(skill.path, "SKILL.md");
1307
1425
  if (await pathExists(skillMd)) {
1308
- this.core.translateSkill(await readFile6(skillMd, "utf8"), "openclaw", { sourceFilename: "SKILL.md" });
1426
+ this.core.translateSkill(await readFile7(skillMd, "utf8"), "openclaw", { sourceFilename: "SKILL.md" });
1309
1427
  }
1310
1428
  }
1311
1429
  return resolved;
@@ -1473,13 +1591,13 @@ function getSourceDriver(name = "local") {
1473
1591
  }
1474
1592
 
1475
1593
  // src/staging/staging.ts
1476
- import { chmod, cp as cp3, mkdir as mkdir5, mkdtemp, readdir as readdir4, stat as stat6 } from "fs/promises";
1477
- import { basename as basename7, dirname as dirname5, join as join12, relative as relative2, resolve as resolve8, sep } from "path";
1594
+ import { chmod, cp as cp3, mkdir as mkdir6, mkdtemp, readdir as readdir4, stat as stat6 } from "fs/promises";
1595
+ import { basename as basename7, dirname as dirname6, join as join12, relative as relative2, resolve as resolve8, sep } from "path";
1478
1596
  import { tmpdir as tmpdir2 } from "os";
1479
1597
 
1480
1598
  // src/staging/customize.ts
1481
- import { cp as cp2, mkdir as mkdir4, readdir as readdir3, readFile as readFile7, writeFile as writeFile3 } from "fs/promises";
1482
- import { dirname as dirname4, join as join11 } from "path";
1599
+ import { cp as cp2, mkdir as mkdir5, readdir as readdir3, readFile as readFile8, writeFile as writeFile4 } from "fs/promises";
1600
+ import { dirname as dirname5, join as join11 } from "path";
1483
1601
  async function applyCustomizations(artifacts, options) {
1484
1602
  let next = [...artifacts];
1485
1603
  next = await applyReplacements(next, options, "override");
@@ -1494,11 +1612,11 @@ async function applyInstructionOverlay(artifacts, options) {
1494
1612
  const index = artifacts.findIndex((artifact2) => artifact2.type === "instructions");
1495
1613
  if (index < 0) return artifacts;
1496
1614
  const artifact = artifacts[index];
1497
- const managed = await readFile7(artifact.stagedPath ?? artifact.sourcePath, "utf8");
1498
- const local = await readFile7(overlayPath, "utf8");
1615
+ const managed = await readFile8(artifact.stagedPath ?? artifact.sourcePath, "utf8");
1616
+ const local = await readFile8(overlayPath, "utf8");
1499
1617
  const composedPath = join11(options.stageRoot, ".agentwheel-composed", "instructions", "AGENTS.md");
1500
- await mkdir4(dirname4(composedPath), { recursive: true });
1501
- await writeFile3(
1618
+ await mkdir5(dirname5(composedPath), { recursive: true });
1619
+ await writeFile4(
1502
1620
  composedPath,
1503
1621
  [
1504
1622
  "<!-- BEGIN agentwheel managed: upstream -->",
@@ -1559,7 +1677,7 @@ async function applyReplacements(artifacts, options, channel) {
1559
1677
  const kind = entry.isDirectory() ? "dir" : "file";
1560
1678
  const existing = byKey.get(`${type}:${entry.name}`);
1561
1679
  const stagedPath = join11(options.stageRoot, ".agentwheel-composed", channel, type, entry.name);
1562
- await mkdir4(dirname4(stagedPath), { recursive: true });
1680
+ await mkdir5(dirname5(stagedPath), { recursive: true });
1563
1681
  await cp2(full, stagedPath, { recursive: kind === "dir", dereference: true });
1564
1682
  byKey.set(`${type}:${entry.name}`, {
1565
1683
  type,
@@ -1583,6 +1701,46 @@ async function sortedDirEntries2(path) {
1583
1701
  return (await readdir3(path, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
1584
1702
  }
1585
1703
 
1704
+ // src/model/selection.ts
1705
+ function artifactSelectorKey(artifact) {
1706
+ return `${artifact.type}/${artifact.name}`;
1707
+ }
1708
+ function normalizeArtifactSelectors(select, legacySkills) {
1709
+ const selected = [
1710
+ ...select ?? [],
1711
+ ...(legacySkills ?? []).map((name) => `skills/${name}`)
1712
+ ].flatMap(splitSelectorList);
1713
+ if (selected.length === 0) return void 0;
1714
+ return [...new Set(selected.map(parseArtifactSelector))];
1715
+ }
1716
+ function filterArtifactsBySelection(artifacts, selectors, legacySkills) {
1717
+ const selected = normalizeArtifactSelectors(selectors, legacySkills);
1718
+ if (!selected?.length) return artifacts;
1719
+ const selectedSet = new Set(selected);
1720
+ const available = new Set(artifacts.map(artifactSelectorKey));
1721
+ const missing = selected.filter((selector) => !available.has(selector));
1722
+ if (missing.length > 0) {
1723
+ throw new Error(`Selected artifact not found in package: ${missing.join(", ")}`);
1724
+ }
1725
+ return artifacts.filter((artifact) => artifact.required || selectedSet.has(artifactSelectorKey(artifact)));
1726
+ }
1727
+ function splitSelectorList(value) {
1728
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
1729
+ }
1730
+ function parseArtifactSelector(value) {
1731
+ const slash = value.indexOf("/");
1732
+ if (slash <= 0 || slash === value.length - 1) {
1733
+ throw new Error(`Invalid artifact selector: ${value}. Expected <type>/<name>.`);
1734
+ }
1735
+ const type = value.slice(0, slash);
1736
+ const name = value.slice(slash + 1);
1737
+ const parsedType = artifactTypeSchema.safeParse(type);
1738
+ if (!parsedType.success) {
1739
+ throw new Error(`Invalid artifact selector type: ${type}`);
1740
+ }
1741
+ return `${parsedType.data}/${name}`;
1742
+ }
1743
+
1586
1744
  // src/staging/staging.ts
1587
1745
  async function stageSource(driver, source, options = {}) {
1588
1746
  const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(source, options))));
@@ -1591,7 +1749,7 @@ async function stageSource(driver, source, options = {}) {
1591
1749
  const stagedArtifacts = [];
1592
1750
  for (const artifact of artifacts) {
1593
1751
  const stagedPath = join12(root, artifact.relativePath);
1594
- await mkdir5(dirname5(stagedPath), { recursive: true });
1752
+ await mkdir6(dirname6(stagedPath), { recursive: true });
1595
1753
  await cp3(artifact.sourcePath, stagedPath, { recursive: artifact.kind === "dir", dereference: true });
1596
1754
  await composeAssets(artifact, resolved.resolvedPath, stagedPath);
1597
1755
  stagedArtifacts.push({
@@ -1601,12 +1759,13 @@ async function stageSource(driver, source, options = {}) {
1601
1759
  channel: artifact.channel ?? "managed"
1602
1760
  });
1603
1761
  }
1604
- const finalArtifacts = options.workspaceRoot && options.adapter ? await applyCustomizations(stagedArtifacts, {
1762
+ const selectedArtifacts = filterArtifactsBySelection(stagedArtifacts, options.select, options.skills);
1763
+ const finalArtifacts = options.workspaceRoot && options.adapter ? await applyCustomizations(selectedArtifacts, {
1605
1764
  workspaceRoot: options.workspaceRoot,
1606
1765
  adapter: options.adapter,
1607
1766
  stageRoot: root,
1608
1767
  packageName: resolved.packageName
1609
- }) : stagedArtifacts;
1768
+ }) : selectedArtifacts;
1610
1769
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
1611
1770
  return {
1612
1771
  root,
@@ -1649,7 +1808,7 @@ async function copyAsset(asset, source, dest) {
1649
1808
  const sourceStats = await stat6(source);
1650
1809
  if (sourceStats.isFile()) {
1651
1810
  if (matchesAny(basename7(source), asset.include)) {
1652
- await mkdir5(dest, { recursive: true });
1811
+ await mkdir6(dest, { recursive: true });
1653
1812
  await copyAssetFile(source, join12(dest, basename7(source)), asset);
1654
1813
  }
1655
1814
  return;
@@ -1658,7 +1817,7 @@ async function copyAsset(asset, source, dest) {
1658
1817
  throw new Error(`Asset include source is not a file or directory: ${source}`);
1659
1818
  }
1660
1819
  if (!asset.include?.length) {
1661
- await mkdir5(dirname5(dest), { recursive: true });
1820
+ await mkdir6(dirname6(dest), { recursive: true });
1662
1821
  await cp3(source, dest, { recursive: true, dereference: true });
1663
1822
  if (asset.mode === "copy") await normalizeCopiedModes(dest);
1664
1823
  return;
@@ -1670,7 +1829,7 @@ async function copyAsset(asset, source, dest) {
1670
1829
  }
1671
1830
  }
1672
1831
  async function copyAssetFile(source, dest, asset) {
1673
- await mkdir5(dirname5(dest), { recursive: true });
1832
+ await mkdir6(dirname6(dest), { recursive: true });
1674
1833
  await cp3(source, dest, { dereference: true });
1675
1834
  if (asset.mode === "copy") await chmod(dest, 420);
1676
1835
  }
@@ -1718,9 +1877,9 @@ function matchesGlob(path, pattern) {
1718
1877
  }
1719
1878
 
1720
1879
  // src/model/workspace.ts
1721
- import { readFile as readFile8 } from "fs/promises";
1880
+ import { readFile as readFile9 } from "fs/promises";
1722
1881
  import { homedir as homedir3 } from "os";
1723
- import { dirname as dirname6, join as join13, resolve as resolve9 } from "path";
1882
+ import { dirname as dirname7, join as join13, resolve as resolve9 } from "path";
1724
1883
  import { z as z5 } from "zod";
1725
1884
  var workspacePackageSchema = z5.object({
1726
1885
  name: z5.string().min(1),
@@ -1731,7 +1890,9 @@ var workspacePackageSchema = z5.object({
1731
1890
  adapterModule: z5.string().min(1).optional(),
1732
1891
  adapterCodeHash: z5.string().min(16).optional(),
1733
1892
  mode: z5.enum(["pinned", "tracking"]).default("pinned"),
1734
- requestedRef: z5.string().min(1).optional()
1893
+ requestedRef: z5.string().min(1).optional(),
1894
+ select: z5.array(z5.string().min(1)).optional(),
1895
+ skills: z5.array(z5.string().min(1)).optional()
1735
1896
  });
1736
1897
  var workspaceProfileRuntimeSchema = z5.object({
1737
1898
  agent: z5.string().min(1).optional(),
@@ -1765,7 +1926,7 @@ function workspaceConfigPath(workspaceRoot) {
1765
1926
  async function readWorkspaceConfig(workspaceRoot) {
1766
1927
  const path = workspaceConfigPath(workspaceRoot);
1767
1928
  if (!await pathExists(path)) return emptyWorkspaceConfig();
1768
- return workspaceConfigSchema.parse(JSON.parse(await readFile8(path, "utf8")));
1929
+ return workspaceConfigSchema.parse(JSON.parse(await readFile9(path, "utf8")));
1769
1930
  }
1770
1931
  async function writeWorkspaceConfig(workspaceRoot, config) {
1771
1932
  await writeJsonAtomic(workspaceConfigPath(workspaceRoot), workspaceConfigSchema.parse(config));
@@ -1783,7 +1944,7 @@ async function findWorkspaceRoot(start = process.cwd()) {
1783
1944
  let current = resolve9(start);
1784
1945
  while (true) {
1785
1946
  if (await pathExists(workspaceConfigPath(current))) return current;
1786
- const parent = dirname6(current);
1947
+ const parent = dirname7(current);
1787
1948
  if (parent === current) return resolve9(start);
1788
1949
  current = parent;
1789
1950
  }
@@ -1817,15 +1978,15 @@ function emptyWorkspaceConfig() {
1817
1978
  }
1818
1979
  async function readConfigPath(path) {
1819
1980
  if (!await pathExists(path)) return emptyWorkspaceConfig();
1820
- return workspaceConfigSchema.parse(JSON.parse(await readFile8(path, "utf8")));
1981
+ return workspaceConfigSchema.parse(JSON.parse(await readFile9(path, "utf8")));
1821
1982
  }
1822
1983
 
1823
1984
  // src/lifecycle/customization.ts
1824
- import { appendFile, cp as cp4, mkdir as mkdir6, rm as rm5 } from "fs/promises";
1825
- import { dirname as dirname7, join as join14 } from "path";
1985
+ import { appendFile, cp as cp4, mkdir as mkdir7, rm as rm5 } from "fs/promises";
1986
+ import { dirname as dirname8, join as join14 } from "path";
1826
1987
  async function remember(workspaceRoot, runtime, text) {
1827
1988
  const overlayPath = join14(workspaceRoot, ".agentwheel", "overlays", runtime, "instructions.local.md");
1828
- await mkdir6(dirname7(overlayPath), { recursive: true });
1989
+ await mkdir7(dirname8(overlayPath), { recursive: true });
1829
1990
  await appendFile(overlayPath, `${text.trim()}
1830
1991
  `, "utf8");
1831
1992
  return { overlayPath };
@@ -1850,7 +2011,7 @@ async function ejectArtifact(workspaceRoot, item) {
1850
2011
  throw new Error(`Artifact not found: ${item}`);
1851
2012
  }
1852
2013
  const ejectedPath = join14(workspaceRoot, ".agentwheel", "ejected", ...parsed.packageName.split("/"), parsed.type, parsed.name);
1853
- await mkdir6(dirname7(ejectedPath), { recursive: true });
2014
+ await mkdir7(dirname8(ejectedPath), { recursive: true });
1854
2015
  await rm5(ejectedPath, { recursive: true, force: true });
1855
2016
  await cp4(artifact.stagedPath ?? artifact.sourcePath, ejectedPath, { recursive: artifact.kind === "dir", dereference: true });
1856
2017
  return { ...parsed, ejectedPath };
@@ -1874,9 +2035,9 @@ import { rm as rm7 } from "fs/promises";
1874
2035
  import { join as join16 } from "path";
1875
2036
 
1876
2037
  // src/registry/client.ts
1877
- import { readFile as readFile9, rm as rm6, stat as stat7 } from "fs/promises";
2038
+ import { readFile as readFile10, rm as rm6, stat as stat7 } from "fs/promises";
1878
2039
  import { homedir as homedir4 } from "os";
1879
- import { dirname as dirname8, join as join15, resolve as resolve10 } from "path";
2040
+ import { dirname as dirname9, join as join15, resolve as resolve10 } from "path";
1880
2041
  import { fileURLToPath } from "url";
1881
2042
 
1882
2043
  // src/model/registry.ts
@@ -1962,7 +2123,7 @@ var RegistryClient = class {
1962
2123
  }
1963
2124
  async readCache() {
1964
2125
  if (!await pathExists(this.cachePath)) return void 0;
1965
- return registryCacheSchema.parse(JSON.parse(await readFile9(this.cachePath, "utf8")));
2126
+ return registryCacheSchema.parse(JSON.parse(await readFile10(this.cachePath, "utf8")));
1966
2127
  }
1967
2128
  isExpired(cache, ttlMs) {
1968
2129
  return this.now().getTime() - new Date(cache.fetchedAt).getTime() > ttlMs;
@@ -1981,10 +2142,10 @@ var RegistryClient = class {
1981
2142
  if (await pathExists(filePath)) {
1982
2143
  const fullPath = resolve10(filePath);
1983
2144
  const stats = await stat7(fullPath);
1984
- return readFile9(stats.isDirectory() ? join15(fullPath, "index.json") : fullPath, "utf8");
2145
+ return readFile10(stats.isDirectory() ? join15(fullPath, "index.json") : fullPath, "utf8");
1985
2146
  }
1986
- const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join15(dirname8(this.cachePath), "registry-repos") }));
1987
- return readFile9(join15(resolved.resolvedPath, "index.json"), "utf8");
2147
+ const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join15(dirname9(this.cachePath), "registry-repos") }));
2148
+ return readFile10(join15(resolved.resolvedPath, "index.json"), "utf8");
1988
2149
  }
1989
2150
  };
1990
2151
  async function resolvePackageSource(source, workspaceRoot) {
@@ -2040,7 +2201,9 @@ async function syncProfile(options) {
2040
2201
  workspaceRoot: options.workspaceRoot,
2041
2202
  adapter,
2042
2203
  cacheRoot: join16(options.workspaceRoot, ".agentwheel", "cache"),
2043
- mode: options.mode ?? pkg.mode
2204
+ mode: options.mode ?? pkg.mode,
2205
+ select: options.select ?? pkg.select,
2206
+ skills: options.select ? void 0 : pkg.skills
2044
2207
  });
2045
2208
  try {
2046
2209
  const plan = await createInstallPlan(bundle, adapter, target.targetRoot, await readInstallManifest(target.targetRoot, adapter.name));
@@ -2074,7 +2237,9 @@ async function packageFromSource(source, options) {
2074
2237
  source: resolved.source,
2075
2238
  driver,
2076
2239
  adapter: "openclaw",
2077
- mode: options.mode ?? "pinned"
2240
+ mode: options.mode ?? "pinned",
2241
+ select: options.select,
2242
+ skills: options.skills
2078
2243
  };
2079
2244
  }
2080
2245
 
@@ -2089,7 +2254,9 @@ async function createSourcePlan(options) {
2089
2254
  workspaceRoot,
2090
2255
  adapter: options.adapter,
2091
2256
  cacheRoot: join17(workspaceRoot, ".agentwheel", "cache"),
2092
- mode: options.mode
2257
+ mode: options.mode,
2258
+ select: options.select,
2259
+ skills: options.skills
2093
2260
  });
2094
2261
  const manifest = await readInstallManifest(options.targetRoot, options.adapter.name);
2095
2262
  const plan = await createInstallPlan(bundle, options.adapter, options.targetRoot, manifest);
@@ -2114,7 +2281,7 @@ function shouldUpdatePackage(pkg, lock) {
2114
2281
  }
2115
2282
 
2116
2283
  // src/runtime/target.ts
2117
- import { basename as basename8, dirname as dirname9, join as join18, resolve as resolve11 } from "path";
2284
+ import { basename as basename8, dirname as dirname10, join as join18, resolve as resolve11 } from "path";
2118
2285
  var runtimeMarkers = [
2119
2286
  { adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
2120
2287
  { adapter: "claude", dirs: [".claude"] },
@@ -2168,7 +2335,7 @@ async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
2168
2335
  if (adapterFilter && marker.adapter !== adapterFilter) continue;
2169
2336
  for (const dir of marker.dirs) {
2170
2337
  if (basename8(root) === dir) {
2171
- matches.push({ adapter: marker.adapter, targetRoot: dirname9(root) });
2338
+ matches.push({ adapter: marker.adapter, targetRoot: dirname10(root) });
2172
2339
  } else if (await pathExists(join18(root, dir))) {
2173
2340
  matches.push({ adapter: marker.adapter, targetRoot: root });
2174
2341
  }
@@ -2201,9 +2368,86 @@ function dedupeTargets(matches) {
2201
2368
  return [...byKey.values()];
2202
2369
  }
2203
2370
 
2371
+ // src/cli/update-check.ts
2372
+ import { mkdir as mkdir8, readFile as readFile11, writeFile as writeFile5 } from "fs/promises";
2373
+ import { homedir as homedir5 } from "os";
2374
+ import { dirname as dirname11, join as join19 } from "path";
2375
+ var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
2376
+ var DEFAULT_TIMEOUT_MS = 300;
2377
+ var REGISTRY_URL = "https://registry.npmjs.org/agentwheel";
2378
+ async function maybeCheckForUpdate(options) {
2379
+ if (isDisabled(options)) return;
2380
+ const now = options.now?.() ?? /* @__PURE__ */ new Date();
2381
+ const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
2382
+ const cachePath = options.cachePath ?? join19(homedir5(), ".agentwheel", "update-check.json");
2383
+ try {
2384
+ const cached = await readCache(cachePath);
2385
+ if (cached && now.getTime() - Date.parse(cached.checkedAt) < ttlMs) {
2386
+ warnIfNewer(cached.latest, options.currentVersion, options.stderr);
2387
+ return;
2388
+ }
2389
+ const latest = await fetchLatestVersion(options.fetchImpl ?? fetch, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
2390
+ if (!latest) return;
2391
+ await writeCache(cachePath, { checkedAt: now.toISOString(), latest });
2392
+ warnIfNewer(latest, options.currentVersion, options.stderr);
2393
+ } catch {
2394
+ }
2395
+ }
2396
+ function isDisabled(options) {
2397
+ const env = options.env ?? process.env;
2398
+ if (env.AGENTWHEEL_NO_UPDATE_CHECK === "1" || env.AGENTWHEEL_NO_UPDATE_CHECK === "true") return true;
2399
+ if (env.CI) return true;
2400
+ if (options.argv?.includes("--no-update-check")) return true;
2401
+ const isTTY = options.isTTY ?? process.stderr.isTTY === true;
2402
+ return !isTTY;
2403
+ }
2404
+ async function fetchLatestVersion(fetchImpl, timeoutMs) {
2405
+ const controller = new AbortController();
2406
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
2407
+ try {
2408
+ const response = await fetchImpl(REGISTRY_URL, { signal: controller.signal });
2409
+ if (!response.ok) return void 0;
2410
+ const body = await response.json();
2411
+ return typeof body["dist-tags"]?.latest === "string" ? body["dist-tags"].latest : void 0;
2412
+ } finally {
2413
+ clearTimeout(timeout);
2414
+ }
2415
+ }
2416
+ async function readCache(path) {
2417
+ try {
2418
+ const parsed = JSON.parse(await readFile11(path, "utf8"));
2419
+ if (typeof parsed.checkedAt !== "string" || typeof parsed.latest !== "string") return void 0;
2420
+ return { checkedAt: parsed.checkedAt, latest: parsed.latest };
2421
+ } catch {
2422
+ return void 0;
2423
+ }
2424
+ }
2425
+ async function writeCache(path, cache) {
2426
+ await mkdir8(dirname11(path), { recursive: true });
2427
+ await writeFile5(path, `${JSON.stringify(cache, null, 2)}
2428
+ `, "utf8");
2429
+ }
2430
+ function warnIfNewer(latest, current, stderr = process.stderr) {
2431
+ if (compareVersions(latest, current) <= 0) return;
2432
+ stderr.write(`agentwheel ${latest} is available (you have ${current}). Update: npm i -g agentwheel
2433
+ `);
2434
+ }
2435
+ function compareVersions(a, b) {
2436
+ const left = normalizeVersion(a);
2437
+ const right = normalizeVersion(b);
2438
+ for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
2439
+ const diff = (left[index] ?? 0) - (right[index] ?? 0);
2440
+ if (diff !== 0) return diff > 0 ? 1 : -1;
2441
+ }
2442
+ return 0;
2443
+ }
2444
+ function normalizeVersion(version) {
2445
+ return version.replace(/^v/, "").split("-", 1)[0].split(".").map((part) => Number.parseInt(part, 10)).map((part) => Number.isFinite(part) ? part : 0);
2446
+ }
2447
+
2204
2448
  // src/cli/index.ts
2205
2449
  var program = new Command();
2206
- program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version("0.5.0");
2450
+ program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version("0.7.0").option("--no-update-check", "disable npm version update check", false);
2207
2451
  program.command("init").argument("[kind]", "workspace or package", "workspace").option("--target-root <path>", "workspace root", process.cwd()).action(async (kind, options) => {
2208
2452
  const root = normalizeTargetRoot(options.targetRoot);
2209
2453
  if (kind === "package") {
@@ -2217,8 +2461,9 @@ program.command("init").argument("[kind]", "workspace or package", "workspace").
2217
2461
  await writeWorkspaceConfig(root, await readWorkspaceConfig(root));
2218
2462
  console.log("Initialized .agentwheel/config.json.");
2219
2463
  });
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) => {
2464
+ 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
2465
  const targetRoot = normalizeTargetRoot(options.targetRoot);
2466
+ const selectedArtifacts = selectedArtifactsFromOptions(options);
2222
2467
  const resolvedInput = await resolvePackageSource(source, targetRoot);
2223
2468
  const resolvedSource = resolvedInput.source;
2224
2469
  const driverName = options.driver ?? inferSourceDriverName(resolvedSource);
@@ -2234,8 +2479,9 @@ program.command("add").argument("<source>", "package source").option("--driver <
2234
2479
  const bundle = await stageSource(driver, resolvedSource, {
2235
2480
  workspaceRoot: targetRoot,
2236
2481
  adapter,
2237
- cacheRoot: join19(targetRoot, ".agentwheel", "cache"),
2238
- mode: options.mode
2482
+ cacheRoot: join20(targetRoot, ".agentwheel", "cache"),
2483
+ mode: options.mode,
2484
+ select: selectedArtifacts
2239
2485
  });
2240
2486
  const name = options.name ?? resolvedInput.registryEntry?.name ?? bundle.source.packageName ?? source;
2241
2487
  const entry = {
@@ -2247,18 +2493,20 @@ program.command("add").argument("<source>", "package source").option("--driver <
2247
2493
  adapterModule: options.adapterModule,
2248
2494
  adapterCodeHash: adapter.programmatic?.hash,
2249
2495
  mode: options.mode,
2250
- requestedRef: bundle.source.requestedRef
2496
+ requestedRef: bundle.source.requestedRef,
2497
+ select: selectedArtifacts
2251
2498
  };
2252
2499
  await writeWorkspaceConfig(targetRoot, upsertPackage(await readWorkspaceConfig(targetRoot), entry));
2253
2500
  await rm8(bundle.root, { recursive: true, force: true });
2254
2501
  console.log(`Added ${name}.`);
2255
2502
  });
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) => {
2503
+ 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
2504
  const targetRoot = normalizeTargetRoot(options.targetRoot);
2505
+ const selectedArtifacts = selectedArtifactsFromOptions(options);
2258
2506
  const resolvedInput = await resolvePackageSource(source, targetRoot);
2259
2507
  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);
2508
+ const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join20(targetRoot, ".agentwheel", "cache") }))));
2509
+ const artifacts = filterArtifactsBySelection(await driver.list(resolved), selectedArtifacts);
2262
2510
  for (const artifact of artifacts) {
2263
2511
  console.log(`${artifact.type} ${artifact.name} ${artifact.relativePath}`);
2264
2512
  }
@@ -2267,7 +2515,7 @@ program.command("scan").argument("<source>", "package source").option("--driver
2267
2515
  const targetRoot = normalizeTargetRoot(options.targetRoot);
2268
2516
  const resolvedInput = await resolvePackageSource(source, targetRoot);
2269
2517
  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") }))));
2518
+ const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join20(targetRoot, ".agentwheel", "cache") }))));
2271
2519
  const result = await driver.scan(resolved);
2272
2520
  if (result.findings.length === 0) {
2273
2521
  console.log("Scan ok: no findings");
@@ -2278,7 +2526,7 @@ program.command("scan").argument("<source>", "package source").option("--driver
2278
2526
  }
2279
2527
  if (!result.ok) process.exitCode = 1;
2280
2528
  });
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) => {
2529
+ 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
2530
  const targets = await resolveCliTargets(options);
2283
2531
  for (const target of targets) {
2284
2532
  const { plan, bundle } = await buildPlan(source, target, options);
@@ -2287,7 +2535,7 @@ program.command("plan").argument("<source>", "source directory").option("--drive
2287
2535
  if (plan.hasBlockingChanges) process.exitCode = 1;
2288
2536
  }
2289
2537
  });
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) => {
2538
+ 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
2539
  if (options.profile) {
2292
2540
  const target = await resolveRuntimeTarget({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent });
2293
2541
  const results = await syncProfile({
@@ -2296,6 +2544,7 @@ program.command("sync").argument("[source]", "source directory").option("--drive
2296
2544
  source,
2297
2545
  driver: options.driver,
2298
2546
  mode: options.mode,
2547
+ select: selectedArtifactsFromOptions(options),
2299
2548
  dryRun: options.dryRun,
2300
2549
  executePlugins: options.executePlugins,
2301
2550
  allowAdapterCode: options.allowAdapterCode,
@@ -2327,7 +2576,7 @@ program.command("sync").argument("[source]", "source directory").option("--drive
2327
2576
  if (plan.hasBlockingChanges) process.exitCode = 1;
2328
2577
  }
2329
2578
  });
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) => {
2579
+ 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
2580
  const targets = await resolveCliTargets(options);
2332
2581
  for (const target of targets) {
2333
2582
  await runConfiguredPackages(target, options, { useUpdateDecision: true });
@@ -2360,7 +2609,7 @@ program.command("eject").argument("<item>", "package/type/name").option("--targe
2360
2609
  const result = await ejectArtifact(targetRoot, item);
2361
2610
  console.log(`Ejected ${item} to ${result.ejectedPath}.`);
2362
2611
  });
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) => {
2612
+ 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
2613
  const targets = await resolveCliTargets(options);
2365
2614
  for (const target of targets) {
2366
2615
  const adapter = await resolveAdapterForTarget(target, options);
@@ -2369,7 +2618,7 @@ program.command("uninstall").option("--adapter <adapter>", "adapter", "openclaw"
2369
2618
  console.log(`No install manifest for ${adapter.name} at ${target.targetRoot}`);
2370
2619
  continue;
2371
2620
  }
2372
- const plan = await createUninstallPlan(manifest);
2621
+ const plan = filterUninstallPlanBySelection(await createUninstallPlan(manifest), selectedArtifactsFromOptions(options));
2373
2622
  console.log(formatPlan(plan));
2374
2623
  const result = await uninstall(plan, { dryRun: options.dryRun, force: options.force });
2375
2624
  if (!options.dryRun) {
@@ -2387,7 +2636,8 @@ async function buildPlan(source, target, options) {
2387
2636
  workspaceRoot: target.workspaceRoot,
2388
2637
  adapter,
2389
2638
  driver: options.driver,
2390
- mode: options.mode
2639
+ mode: options.mode,
2640
+ select: selectedArtifactsFromOptions(options)
2391
2641
  });
2392
2642
  return { plan: result.plan, bundle: result.bundle };
2393
2643
  }
@@ -2413,6 +2663,7 @@ async function runConfiguredPackages(target, options, behavior) {
2413
2663
  console.log(`No packages configured at ${target.workspaceRoot}.`);
2414
2664
  return;
2415
2665
  }
2666
+ const selectedArtifacts = selectedArtifactsFromOptions(options);
2416
2667
  for (const pkg of config.packages) {
2417
2668
  const targetForPackage = options.adapter || target.source !== "cwd" ? target : { ...target, adapter: pkg.adapter };
2418
2669
  const adapter = await resolveAdapterForTarget(targetForPackage, {
@@ -2433,7 +2684,9 @@ async function runConfiguredPackages(target, options, behavior) {
2433
2684
  adapterConfig: options.adapterConfig ?? pkg.adapterConfig,
2434
2685
  adapterModule: options.adapterModule ?? pkg.adapterModule,
2435
2686
  allowAdapterCode: options.allowAdapterCode,
2436
- mode: pkg.mode
2687
+ mode: pkg.mode,
2688
+ select: selectedArtifacts ?? pkg.select,
2689
+ skills: selectedArtifacts ? void 0 : pkg.skills
2437
2690
  });
2438
2691
  console.log(`${behavior.useUpdateDecision ? "Update" : "Sync"} ${pkg.name} (${adapter.name} at ${targetForPackage.targetRoot}):`);
2439
2692
  console.log(formatPlan(plan));
@@ -2445,11 +2698,44 @@ async function runConfiguredPackages(target, options, behavior) {
2445
2698
  if (plan.hasBlockingChanges) process.exitCode = 1;
2446
2699
  }
2447
2700
  }
2701
+ function collectSelectOption(value, previous) {
2702
+ return [...previous, ...splitSelectorList(value)];
2703
+ }
2704
+ function collectSkillOption(value, previous) {
2705
+ return [...previous, ...splitSelectorList(value)];
2706
+ }
2707
+ function selectedArtifactsFromOptions(options) {
2708
+ return normalizeArtifactSelectors(options.select, options.skills ?? options.skill);
2709
+ }
2710
+ function filterUninstallPlanBySelection(plan, selected) {
2711
+ if (!selected?.length) return plan;
2712
+ const requested = normalizeArtifactSelectors(selected) ?? [];
2713
+ const available = new Set(plan.operations.map((operation) => `${operation.artifactType}/${operation.artifactName}`));
2714
+ const missing = requested.filter((selector) => !available.has(selector));
2715
+ if (missing.length > 0) {
2716
+ throw new Error(`Selected artifact not found in install manifest: ${missing.join(", ")}`);
2717
+ }
2718
+ const selectedSet = new Set(requested);
2719
+ const operations = plan.operations.map((operation) => {
2720
+ if (selectedSet.has(`${operation.artifactType}/${operation.artifactName}`)) return operation;
2721
+ return {
2722
+ ...operation,
2723
+ action: "skip",
2724
+ desiredHash: operation.desiredHash ?? operation.manifestHash,
2725
+ reason: "not selected for uninstall"
2726
+ };
2727
+ });
2728
+ return {
2729
+ ...plan,
2730
+ operations,
2731
+ hasBlockingChanges: operations.some((operation) => operation.action === "conflict")
2732
+ };
2733
+ }
2448
2734
  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");
2735
+ await mkdir9(join20(root, "instructions"), { recursive: true });
2736
+ await mkdir9(join20(root, "rules"), { recursive: true });
2737
+ await mkdir9(join20(root, "skills"), { recursive: true });
2738
+ const manifestPath = join20(root, "agentwheel.json");
2453
2739
  const manifest = {
2454
2740
  schemaVersion: 1,
2455
2741
  name: "example/agentwheel-package",
@@ -2460,9 +2746,9 @@ async function initPackage(root) {
2460
2746
  { type: "skills", path: "skills" }
2461
2747
  ]
2462
2748
  };
2463
- await writeFile4(manifestPath, `${JSON.stringify(manifest, null, 2)}
2749
+ await writeFile6(manifestPath, `${JSON.stringify(manifest, null, 2)}
2464
2750
  `, "utf8");
2465
- await writeFile4(join19(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
2751
+ await writeFile6(join20(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
2466
2752
  }
2467
2753
  function formatUninstallResult(result) {
2468
2754
  const removedLabel = result.removed === 1 ? "managed file" : "managed files";
@@ -2480,7 +2766,16 @@ function printRegistryEntries(entries) {
2480
2766
  console.log(`${entry.name} ${entry.type} ${entry.source} ${entry.description}${tags}`);
2481
2767
  }
2482
2768
  }
2483
- program.parseAsync().catch((error) => {
2769
+ async function main() {
2770
+ await maybeCheckForUpdate({
2771
+ currentVersion: "0.7.0",
2772
+ argv: process.argv,
2773
+ env: process.env,
2774
+ isTTY: process.stderr.isTTY === true
2775
+ });
2776
+ await program.parseAsync();
2777
+ }
2778
+ main().catch((error) => {
2484
2779
  console.error(error instanceof Error ? error.message : String(error));
2485
2780
  process.exitCode = 1;
2486
2781
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentwheel",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Weave skills, rules, and instructions across every AI agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",