agentwheel 0.21.2 → 0.21.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -98,6 +98,7 @@ Fragments are Agentwheel composition inputs, not runtime file-drop targets.
98
98
  |---|---|
99
99
  | `agentwheel search <query>` | Search configured registries and the public enriched/Vercel catalogue; supports stable JSON output for agent reranking. |
100
100
  | `agentwheel add <source>` | Validate and save a package entry in `.agentwheel/config.json`; does not touch runtimes. |
101
+ | `agentwheel pin <alias> <commit>` | Change an existing pinned alias's exact commit, preserving its configuration; does not resolve sources or touch runtimes. |
101
102
  | `agentwheel plan [name-or-source]` | Preview what `install` would reconcile without writing; supports `--profile <name>` and `--json`. |
102
103
  | `agentwheel install` | Reconcile configured packages into the current target or selected fleet. Uses the graph lock as input by default. |
103
104
  | `agentwheel install <name-or-source>` | Ensure semantics: configured name/source scopes the install; a new source is added and installed. |
@@ -583,6 +584,56 @@ agentwheel plan
583
584
  agentwheel install
584
585
  ```
585
586
 
587
+ Use `add --runtimes codex` to restrict a saved package to matching runtime adapters. The option
588
+ accepts comma-separated names and can be repeated. When replacing an existing package alias,
589
+ pass its runtime restrictions and selection again; `add` replaces the saved entry.
590
+
591
+ To change only an existing pinned alias's revision, use its exact configured name and a full
592
+ 40-hex commit SHA:
593
+
594
+ ```bash
595
+ agentwheel pin shared-pack 0123456789abcdef0123456789abcdef01234567 --fleet delivery --reason "Pin the reviewed source revision"
596
+ ```
597
+
598
+ `pin` preserves the source URL, driver, adapter, runtime restrictions, installation type (including
599
+ its absence), package order and other fields. It accepts the normal workspace selectors
600
+ `--fleet`, `--local`, `--user`, or `--target-root`, and uses the same mutation policy, durable
601
+ receipts and revision provider as other source mutations. Missing or duplicate aliases, tracking
602
+ packages and abbreviated or symbolic revisions are refused. No sources are fetched or rendered;
603
+ use the normal plan/dry-run before installing the new revision.
604
+
605
+ Omitting `--select` preserves the complete existing selection. Supplying repeatable or
606
+ comma-separated `--select type/name` replaces the complete selection, including any legacy
607
+ `skills` or imported `selection` field. An explicit empty selection is refused.
608
+
609
+ For one source mutation with several aliases, use `pin --pins-file reviewed-pins.json --fleet
610
+ delivery --reason "Pin the reviewed source set"`. The JSON file is a nonempty array of objects
611
+ containing only `alias`, `commit` and optional `select`:
612
+
613
+ ```json
614
+ [
615
+ { "alias": "shared-pack", "commit": "0123456789abcdef0123456789abcdef01234567" },
616
+ { "alias": "companion-pack", "commit": "abcdef0123456789abcdef0123456789abcdef01", "select": ["skills/companion"] }
617
+ ]
618
+ ```
619
+
620
+ To replace the complete package declaration list without resolving sources, use
621
+ `pin --packages-file reviewed-packages.json --fleet delivery --dry-run`. The file must be a JSON
622
+ array of workspace package entries with unique names; an empty array explicitly removes all
623
+ package declarations. The preview prints the complete proposed config as JSON and writes nothing.
624
+ Review it, then run the same command without `--dry-run` and with `--reason` to apply through the
625
+ normal mutation policy, durable receipt and revision provider. This changes only desired source
626
+ config; it does not install or remove runtime artifacts. Run normal runtime plans before installing.
627
+ All non-package fields are preserved, as are the supplied package order, omitted defaults, metadata
628
+ and extension fields. Package entries must pass the workspace schema. `--packages-file` cannot be
629
+ combined with positional pins, `--pins-file` or `--select`. Existing pin forms also accept
630
+ `--dry-run` for a non-mutating config preview.
631
+
632
+ The batch cannot be combined with positional pins or `--select`. Every entry is validated before
633
+ the config write; duplicate aliases and extra fields are refused. An omitted `select` preserves
634
+ that package's selection; an explicit list replaces only that package's complete selection. The
635
+ batch shares one durable receipt and revision-provider transaction.
636
+
586
637
  Package authors can mark artifacts as required. Required artifacts are always installed and cannot
587
638
  be deselected:
588
639
 
package/dist/index.js CHANGED
@@ -2805,6 +2805,62 @@ var init_semver = __esm({
2805
2805
  }
2806
2806
  });
2807
2807
 
2808
+ // src/model/selection.ts
2809
+ function artifactSelectorKey(artifact) {
2810
+ return `${artifact.type}/${artifact.name}`;
2811
+ }
2812
+ function artifactSelectorAliases(artifact) {
2813
+ const primary = artifactSelectorKey(artifact);
2814
+ if (artifact.type !== "subagents") return [primary];
2815
+ const baseName = subagentBaseName(artifact.name);
2816
+ if (baseName === artifact.name) return [primary];
2817
+ return [primary, `subagents/${baseName}`];
2818
+ }
2819
+ function normalizeArtifactSelectors(select, legacySkills) {
2820
+ const selected = [
2821
+ ...select ?? [],
2822
+ ...(legacySkills ?? []).map((name) => `skills/${name}`)
2823
+ ].flatMap(splitSelectorList);
2824
+ if (selected.length === 0) return void 0;
2825
+ return [...new Set(selected.map(parseArtifactSelector))];
2826
+ }
2827
+ function filterArtifactsBySelection(artifacts, selectors, legacySkills, options = {}) {
2828
+ const selected = normalizeArtifactSelectors(selectors, legacySkills);
2829
+ if (!selected?.length) return artifacts;
2830
+ const selectedSet = new Set(selected);
2831
+ const available = new Set((options.validationArtifacts ?? artifacts).flatMap(artifactSelectorAliases));
2832
+ const missing = selected.filter((selector) => !available.has(selector));
2833
+ if (missing.length > 0) {
2834
+ throw new Error(`Selected artifact not found in package: ${missing.join(", ")}`);
2835
+ }
2836
+ return artifacts.filter((artifact) => artifact.required || artifactSelectorAliases(artifact).some((selector) => selectedSet.has(selector)));
2837
+ }
2838
+ function splitSelectorList(value) {
2839
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
2840
+ }
2841
+ function parseArtifactSelector(value) {
2842
+ const slash = value.indexOf("/");
2843
+ if (slash <= 0 || slash === value.length - 1) {
2844
+ throw new Error(`Invalid artifact selector: ${value}. Expected <type>/<name>.`);
2845
+ }
2846
+ const type = value.slice(0, slash);
2847
+ const name = value.slice(slash + 1);
2848
+ const parsedType = artifactTypeSchema.safeParse(type);
2849
+ if (!parsedType.success) {
2850
+ throw new Error(`Invalid artifact selector type: ${type}`);
2851
+ }
2852
+ return `${parsedType.data}/${name}`;
2853
+ }
2854
+ function subagentBaseName(name) {
2855
+ return name.replace(/\.agent\.md$/i, "").replace(/\.toml$/i, "").replace(/\.md$/i, "");
2856
+ }
2857
+ var init_selection = __esm({
2858
+ "src/model/selection.ts"() {
2859
+ "use strict";
2860
+ init_artifact();
2861
+ }
2862
+ });
2863
+
2808
2864
  // src/model/workspace.ts
2809
2865
  import { readFile as readFile10 } from "fs/promises";
2810
2866
  import { homedir as homedir4 } from "os";
@@ -2829,6 +2885,62 @@ async function writeWorkspaceConfig(workspaceRoot, config) {
2829
2885
  declareMutationPath(path);
2830
2886
  await writeJsonAtomic(path, workspaceConfigSchema.parse(config));
2831
2887
  }
2888
+ async function pinConfiguredPackages(workspaceRoot, input, options = {}) {
2889
+ if (options.replacePackages) {
2890
+ const packages = z10.array(workspacePackageSchema).parse(input);
2891
+ if (new Set(packages.map((pkg) => pkg.name)).size !== packages.length) {
2892
+ throw new Error("Package declarations require unique aliases.");
2893
+ }
2894
+ const path2 = workspaceConfigPath(workspaceRoot);
2895
+ const config2 = JSON.parse(await readFile10(path2, "utf8"));
2896
+ workspaceConfigSchema.parse(config2);
2897
+ config2.packages = input;
2898
+ workspaceConfigSchema.parse(config2);
2899
+ if (!options.dryRun) {
2900
+ declareMutationPath(path2);
2901
+ await writeJsonAtomic(path2, config2);
2902
+ }
2903
+ return config2;
2904
+ }
2905
+ const pins = z10.array(z10.object({
2906
+ alias: z10.string().min(1),
2907
+ commit: z10.string().regex(/^[a-f0-9]{40}$/i, "Pin requires an exact 40-hex commit SHA."),
2908
+ select: z10.array(z10.string().min(1)).min(1).optional()
2909
+ }).strict()).min(1).parse(input).map((pin) => {
2910
+ const select = pin.select === void 0 ? void 0 : normalizeArtifactSelectors(pin.select);
2911
+ if (pin.select !== void 0 && !select?.length) throw new Error("Explicit pin selection must not be empty.");
2912
+ return { ...pin, select };
2913
+ });
2914
+ if (new Set(pins.map((pin) => pin.alias)).size !== pins.length) {
2915
+ throw new Error("Pin requires unique configured aliases.");
2916
+ }
2917
+ const path = workspaceConfigPath(workspaceRoot);
2918
+ const config = JSON.parse(await readFile10(path, "utf8"));
2919
+ workspaceConfigSchema.parse(config);
2920
+ const updates = pins.map(({ alias, commit, select }) => {
2921
+ const matches = (config.packages ?? []).filter((pkg2) => pkg2.name === alias);
2922
+ if (matches.length !== 1) {
2923
+ throw new Error(matches.length === 0 ? `Configured package alias not found: ${alias}` : `Ambiguous configured package alias: ${alias}`);
2924
+ }
2925
+ const pkg = matches[0];
2926
+ if (pkg.mode === "tracking") throw new Error(`Configured package ${alias} is tracking; pin requires pinned mode.`);
2927
+ return { pkg, requestedRef: commit, select };
2928
+ });
2929
+ for (const { pkg, requestedRef, select } of updates) {
2930
+ pkg.requestedRef = requestedRef;
2931
+ if (select !== void 0) {
2932
+ pkg.select = select;
2933
+ delete pkg.skills;
2934
+ delete pkg.selection;
2935
+ }
2936
+ }
2937
+ workspaceConfigSchema.parse(config);
2938
+ if (!options.dryRun) {
2939
+ declareMutationPath(path);
2940
+ await writeJsonAtomic(path, config);
2941
+ }
2942
+ return config;
2943
+ }
2832
2944
  function upsertPackage(config, entry) {
2833
2945
  const parsed = workspaceConfigSchema.parse(config);
2834
2946
  const packages = parsed.packages.filter((candidate) => candidate.name !== entry.name);
@@ -2920,6 +3032,7 @@ var init_workspace = __esm({
2920
3032
  init_mutation();
2921
3033
  init_declarations();
2922
3034
  init_adapters();
3035
+ init_selection();
2923
3036
  artifactSelectorListSchema = z10.array(z10.string().min(1));
2924
3037
  CURRENT_WORKSPACE_SCHEMA_VERSION = 4;
2925
3038
  workspaceSelectionImportSchema = z10.object({
@@ -6874,7 +6987,7 @@ var init_identify = __esm({
6874
6987
  // src/cli/index.ts
6875
6988
  import { createHash as createHash28 } from "crypto";
6876
6989
  import { existsSync } from "fs";
6877
- import { mkdir as mkdir27, mkdtemp as mkdtemp6, realpath as realpath4, rm as rm17, writeFile as writeFile24 } from "fs/promises";
6990
+ import { mkdir as mkdir27, mkdtemp as mkdtemp6, readFile as readFile45, realpath as realpath4, rm as rm17, writeFile as writeFile24 } from "fs/promises";
6878
6991
  import { homedir as homedir18, tmpdir as tmpdir7 } from "os";
6879
6992
  import { basename as basename24, dirname as dirname37, isAbsolute as isAbsolute14, join as join61, relative as relative19, resolve as resolve44 } from "path";
6880
6993
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -7505,59 +7618,7 @@ init_transport();
7505
7618
 
7506
7619
  // src/validation/adapter-targets.ts
7507
7620
  init_adapter();
7508
-
7509
- // src/model/selection.ts
7510
- init_artifact();
7511
- function artifactSelectorKey(artifact) {
7512
- return `${artifact.type}/${artifact.name}`;
7513
- }
7514
- function artifactSelectorAliases(artifact) {
7515
- const primary = artifactSelectorKey(artifact);
7516
- if (artifact.type !== "subagents") return [primary];
7517
- const baseName = subagentBaseName(artifact.name);
7518
- if (baseName === artifact.name) return [primary];
7519
- return [primary, `subagents/${baseName}`];
7520
- }
7521
- function normalizeArtifactSelectors(select, legacySkills) {
7522
- const selected = [
7523
- ...select ?? [],
7524
- ...(legacySkills ?? []).map((name) => `skills/${name}`)
7525
- ].flatMap(splitSelectorList);
7526
- if (selected.length === 0) return void 0;
7527
- return [...new Set(selected.map(parseArtifactSelector))];
7528
- }
7529
- function filterArtifactsBySelection(artifacts, selectors, legacySkills, options = {}) {
7530
- const selected = normalizeArtifactSelectors(selectors, legacySkills);
7531
- if (!selected?.length) return artifacts;
7532
- const selectedSet = new Set(selected);
7533
- const available = new Set((options.validationArtifacts ?? artifacts).flatMap(artifactSelectorAliases));
7534
- const missing = selected.filter((selector) => !available.has(selector));
7535
- if (missing.length > 0) {
7536
- throw new Error(`Selected artifact not found in package: ${missing.join(", ")}`);
7537
- }
7538
- return artifacts.filter((artifact) => artifact.required || artifactSelectorAliases(artifact).some((selector) => selectedSet.has(selector)));
7539
- }
7540
- function splitSelectorList(value) {
7541
- return value.split(",").map((item) => item.trim()).filter(Boolean);
7542
- }
7543
- function parseArtifactSelector(value) {
7544
- const slash = value.indexOf("/");
7545
- if (slash <= 0 || slash === value.length - 1) {
7546
- throw new Error(`Invalid artifact selector: ${value}. Expected <type>/<name>.`);
7547
- }
7548
- const type = value.slice(0, slash);
7549
- const name = value.slice(slash + 1);
7550
- const parsedType = artifactTypeSchema.safeParse(type);
7551
- if (!parsedType.success) {
7552
- throw new Error(`Invalid artifact selector type: ${type}`);
7553
- }
7554
- return `${parsedType.data}/${name}`;
7555
- }
7556
- function subagentBaseName(name) {
7557
- return name.replace(/\.agent\.md$/i, "").replace(/\.toml$/i, "").replace(/\.md$/i, "");
7558
- }
7559
-
7560
- // src/validation/adapter-targets.ts
7621
+ init_selection();
7561
7622
  function filterArtifactsByAdapterTargets(artifacts, adapter, installationType, options = {}) {
7562
7623
  const skipped = [];
7563
7624
  const installable = artifacts.filter((artifact) => {
@@ -7600,10 +7661,11 @@ function skipWarning(selector, adapter, installationType, artifactType, support)
7600
7661
 
7601
7662
  // src/validation/artifacts.ts
7602
7663
  init_adapter();
7664
+ init_selection();
7665
+ init_fs();
7603
7666
  import { readFile as readFile24 } from "fs/promises";
7604
7667
  import { basename as basename7, join as join23 } from "path";
7605
7668
  import { parseDocument as parseDocument2 } from "yaml";
7606
- init_fs();
7607
7669
  var behavioralRuleFormats = ["markdown-rule", "claude-markdown-rule", "copilot-instruction-rule"];
7608
7670
  var pluginFormats = ["claude-plugin", "codex-plugin", "hermes-plugin", "copilot-plugin", "openclaw-plugin", "openclaw-clawhub-plugin"];
7609
7671
  async function filterArtifactsByInstallFormat(artifacts, adapter, installationType, options = {}) {
@@ -12027,6 +12089,7 @@ function claudeAgentName(artifact) {
12027
12089
  }
12028
12090
 
12029
12091
  // src/staging/staging.ts
12092
+ init_selection();
12030
12093
  async function stageSource(driver, source, options = {}) {
12031
12094
  const raw = await stageSourceRaw(driver, source, options);
12032
12095
  return removeOnFailure(raw.root, () => renderStagedBundle(raw, options));
@@ -12232,13 +12295,15 @@ import { readdir as readdir12, readFile as readFile34, stat as stat14 } from "fs
12232
12295
  import { basename as basename20, extname as extname4, join as join41 } from "path";
12233
12296
  init_artifact();
12234
12297
  init_fs();
12298
+ init_selection();
12235
12299
 
12236
12300
  // src/model/workspace-composition.ts
12237
12301
  init_workspace();
12302
+ init_selection();
12303
+ init_fs();
12238
12304
  import { createHash as createHash16 } from "crypto";
12239
12305
  import { readFile as readFile32 } from "fs/promises";
12240
12306
  import { z as z12 } from "zod";
12241
- init_fs();
12242
12307
  var selectionSourceConfigSchema = z12.object({
12243
12308
  schemaVersion: z12.literal(2),
12244
12309
  exports: workspaceExportsSchema
@@ -12397,6 +12462,7 @@ var registryCacheSchema = z13.object({
12397
12462
  });
12398
12463
 
12399
12464
  // src/registry/client.ts
12465
+ init_selection();
12400
12466
  init_workspace();
12401
12467
  init_fs();
12402
12468
  var DEFAULT_REGISTRY_SOURCE = "github:NestDevLab/agentwheel-registry";
@@ -13924,6 +13990,7 @@ function formatSelectionImport(root) {
13924
13990
  }
13925
13991
 
13926
13992
  // src/resolve/render.ts
13993
+ init_selection();
13927
13994
  import { createHash as createHash18 } from "crypto";
13928
13995
  import { readFile as readFile35, mkdtemp as mkdtemp5 } from "fs/promises";
13929
13996
  import { tmpdir as tmpdir6 } from "os";
@@ -14528,6 +14595,7 @@ function defaultTrustStorePath() {
14528
14595
 
14529
14596
  // src/lifecycle/source-plan.ts
14530
14597
  init_workspace();
14598
+ init_selection();
14531
14599
 
14532
14600
  // src/model/workspace-owner.ts
14533
14601
  import { Buffer as Buffer2 } from "buffer";
@@ -15998,6 +16066,7 @@ function runtimeScanRoot(request) {
15998
16066
  // src/lifecycle/profile.ts
15999
16067
  init_identify();
16000
16068
  init_transport();
16069
+ init_selection();
16001
16070
  init_declarations();
16002
16071
  async function syncProfile(options) {
16003
16072
  const config = await readMergedWorkspaceConfig(options.workspaceRoot);
@@ -16155,6 +16224,7 @@ function combinedSuggestionAliases(packageAliases, optionAliases) {
16155
16224
  }
16156
16225
 
16157
16226
  // src/registry/publish.ts
16227
+ init_selection();
16158
16228
  var DEFAULT_REGISTRY_SUBMISSION_URL = "https://github.com/NestDevLab/agentwheel-registry/issues/new";
16159
16229
  var registryEntryTypes = ["package", "skill", "plugin", "mcp", "adapter"];
16160
16230
  var explicitSourcePrefixes = ["github:", "git:", "skillkit:", "vercel:", "mcp-registry:", "clawhub:"];
@@ -16318,6 +16388,9 @@ function shellQuoteArg(value) {
16318
16388
  return `'${value.replaceAll("'", `'"'"'`)}'`;
16319
16389
  }
16320
16390
 
16391
+ // src/cli/index.ts
16392
+ init_selection();
16393
+
16321
16394
  // src/cli/update-check.ts
16322
16395
  import { mkdir as mkdir26, readFile as readFile38, writeFile as writeFile22 } from "fs/promises";
16323
16396
  import { homedir as homedir14 } from "os";
@@ -16818,6 +16891,7 @@ init_manifest2();
16818
16891
  init_apply();
16819
16892
  init_paths();
16820
16893
  init_transaction();
16894
+ init_selection();
16821
16895
  import { createHash as createHash20 } from "crypto";
16822
16896
  import { resolve as resolve37 } from "path";
16823
16897
  init_declarations();
@@ -19479,6 +19553,7 @@ function ensureTrailingSlash(value) {
19479
19553
  }
19480
19554
 
19481
19555
  // src/trial/skill.ts
19556
+ init_selection();
19482
19557
  import { createHash as createHash26 } from "crypto";
19483
19558
  import { readFile as readFile43, stat as stat16 } from "fs/promises";
19484
19559
  import { join as join59 } from "path";
@@ -21129,6 +21204,7 @@ var commandGovernance = {
21129
21204
  "fleet normalize": "governed-apply-or-recover",
21130
21205
  "cache prune": "governed-apply",
21131
21206
  add: "governed-always",
21207
+ pin: "governed-unless-dry-run",
21132
21208
  list: "incidental-cache",
21133
21209
  search: "incidental-cache",
21134
21210
  try: "incidental-cache",
@@ -21185,6 +21261,7 @@ function requiresCleanMutationPreflight(path) {
21185
21261
  "init",
21186
21262
  "fleet register",
21187
21263
  "add",
21264
+ "pin",
21188
21265
  "package migrate",
21189
21266
  "remember",
21190
21267
  "journal abort"
@@ -21264,13 +21341,28 @@ cacheCommand.command("prune").description("remove old Git source snapshots while
21264
21341
  for (const path of result.removedPaths) console.log(`${verb} ${path}`);
21265
21342
  console.log(`${options.apply ? "Pruned" : "Preview"}: ${result.removedPaths.length} snapshots; retained ${result.retainedPaths.length}.`);
21266
21343
  });
21267
- program.command("add").description("add a package to .agentwheel/config.json without touching runtimes").argument("<source>", "package source").option("--driver <driver>", "source driver (local, git, skillkit, vercel-skills, mcp-registry, or clawhub)").option("--adapter <adapter>", "built-in adapter", "openclaw").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").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("-t, --target-root <path>", "workspace root").option("--mode <mode>", "pinned or tracking", "pinned").option("--version <range>", "root package version policy (exact, ~, ^, or *)").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, []).option("--with-suggestions", "include suggested companion artifacts for selected roots on future installs", false).option("--suggestion <alias>", "include one suggested companion alias on future installs (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "allow this package to replace a colliding artifact (repeatable)", collectOverrideOption, []).action(async (source, options) => {
21344
+ program.command("add").description("add a package to .agentwheel/config.json without touching runtimes").argument("<source>", "package source").option("--driver <driver>", "source driver (local, git, skillkit, vercel-skills, mcp-registry, or clawhub)").option("--adapter <adapter>", "built-in adapter", "openclaw").option("--runtimes <adapters>", "restrict package runtimes (repeatable or comma-separated)", collectSelectOption, []).option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").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("-t, --target-root <path>", "workspace root").option("--mode <mode>", "pinned or tracking", "pinned").option("--version <range>", "root package version policy (exact, ~, ^, or *)").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, []).option("--with-suggestions", "include suggested companion artifacts for selected roots on future installs", false).option("--suggestion <alias>", "include one suggested companion alias on future installs (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "allow this package to replace a colliding artifact (repeatable)", collectOverrideOption, []).action(async (source, options) => {
21268
21345
  const normalizedOptions = normalizeRuntimeScopeOptions(options);
21269
21346
  const targetRoot = await workspaceRootFromOptions(normalizedOptions);
21270
21347
  const entry = await packageEntryFromSource(source, targetRoot, normalizedOptions);
21271
21348
  await writeWorkspaceConfig(targetRoot, upsertPackage(await readWorkspaceConfig(targetRoot), entry));
21272
21349
  console.log(`Added ${entry.name}. Preview: agentwheel plan - Apply: agentwheel install`);
21273
21350
  });
21351
+ program.command("pin").description("pin configured revisions or replace package declarations without touching runtimes").argument("[alias]", "exact configured package alias").argument("[commit]", "exact 40-hex commit SHA").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").option("-t, --target-root <path>", "explicit workspace root").option("--pins-file <path>", "JSON array of {alias, commit, select?} pins in one mutation").option("--packages-file <path>", "JSON array replacing the complete configured packages list").option("--dry-run", "print the complete proposed config as JSON without writing", false).option("--select <type/name>", "replace the complete selection (repeatable or comma-separated)", collectSelectOption).action(async (alias, commit, options) => {
21352
+ const workspaceRoot = await workspaceRootFromOptions(normalizeRuntimeScopeOptions(options));
21353
+ const positional = alias !== void 0 || commit !== void 0 || options.select !== void 0;
21354
+ if (options.packagesFile ? positional || options.pinsFile : options.pinsFile ? positional : !alias || !commit) {
21355
+ throw new Error("Choose pin <alias> <commit> [--select ...], --pins-file <path>, or --packages-file <path>, exclusively.");
21356
+ }
21357
+ const inputFile = options.packagesFile ?? options.pinsFile;
21358
+ const input = inputFile ? JSON.parse(await readFile45(resolve44(inputFile), "utf8")) : [{ alias, commit, ...options.select === void 0 ? {} : { select: options.select } }];
21359
+ const config = await pinConfiguredPackages(workspaceRoot, input, {
21360
+ dryRun: options.dryRun,
21361
+ replacePackages: options.packagesFile !== void 0
21362
+ });
21363
+ if (options.dryRun) console.log(JSON.stringify(config, null, 2));
21364
+ else console.log("Pinned configured packages. Preview: agentwheel plan - Apply: agentwheel install");
21365
+ });
21274
21366
  program.command("list").description("list artifacts exposed by a package source").argument("<source>", "package source").option("--driver <driver>", "source driver").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").option("-t, --target-root <path>", "explicit workspace root").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) => {
21275
21367
  const targetRoot = await workspaceContextRootFromOptions(normalizeRuntimeScopeOptions(options));
21276
21368
  const resolvedInput = await resolvePackageSource(source, targetRoot);
@@ -22365,6 +22457,7 @@ async function packageEntryFromSource(source, targetRoot, options) {
22365
22457
  source: resolvedSource,
22366
22458
  driver: driverName,
22367
22459
  adapter: adapter.name,
22460
+ runtimes: options.runtimes?.length ? options.runtimes : void 0,
22368
22461
  installationType,
22369
22462
  adapterConfig: options.adapterConfig,
22370
22463
  adapterModule: options.adapterModule,
@@ -24401,7 +24494,7 @@ function shellQuoteArg2(value) {
24401
24494
  if (/^[A-Za-z0-9_./:@=-]+$/.test(value)) return value;
24402
24495
  return `'${value.replaceAll("'", `'"'"'`)}'`;
24403
24496
  }
24404
- function collectSelectOption(value, previous) {
24497
+ function collectSelectOption(value, previous = []) {
24405
24498
  return [...previous, ...splitSelectorList(value)];
24406
24499
  }
24407
24500
  function parsePositiveInteger(value) {
@@ -24808,7 +24901,7 @@ async function governedCommandContext(command, options) {
24808
24901
  const contextOnly = path === "cache prune";
24809
24902
  const workspaceRoot = contextOnly ? await workspaceContextRootFromOptions(normalized) : await workspaceRootFromOptions(normalized);
24810
24903
  const anticipatedPaths = [];
24811
- if (path === "add") anticipatedPaths.push(workspaceConfigPath(workspaceRoot));
24904
+ if (path === "add" || path === "pin") anticipatedPaths.push(workspaceConfigPath(workspaceRoot));
24812
24905
  if (path === "remember" && options.runtime) {
24813
24906
  anticipatedPaths.push(join61(workspaceRoot, ".agentwheel", "overlays", options.runtime, "instructions.local.md"));
24814
24907
  }
package/openpack.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "name": "NestDevLab/agentwheel",
4
- "version": "0.21.2",
4
+ "version": "0.21.4",
5
5
  "provides": [
6
6
  { "type": "skills", "path": "skills" }
7
7
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentwheel",
3
- "version": "0.21.2",
3
+ "version": "0.21.4",
4
4
  "description": "Weave skills, rules, and instructions across every AI agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -5,7 +5,7 @@ allowed-tools: [Bash]
5
5
  license: MIT
6
6
  metadata:
7
7
  author: NestDevLab
8
- version: "0.21.2"
8
+ version: "0.21.4"
9
9
  ---
10
10
 
11
11
  # agentwheel
@@ -5,7 +5,7 @@ allowed-tools: [Bash]
5
5
  license: MIT
6
6
  metadata:
7
7
  author: NestDevLab
8
- version: "0.21.2"
8
+ version: "0.21.4"
9
9
  ---
10
10
 
11
11
  # Agentwheel Discovery