agentwheel 0.9.0 → 0.10.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 +31 -7
  2. package/dist/index.js +154 -34
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  <p align="center"><strong>One source. Every agent.</strong></p>
8
8
 
9
9
  <p align="center">
10
- Weave your skills, rules, and instructions across every AI agent you use,
10
+ Weave skills, rules, instructions, commands, subagents, MCP, hooks, settings, and plugins across every AI agent you use,
11
11
  from any source, reconciled into each runtime with your private tweaks intact.
12
12
  </p>
13
13
 
@@ -15,8 +15,9 @@
15
15
 
16
16
  ## Why agentwheel?
17
17
 
18
- Your AI agents are multiplying. Claude here, Codex there, custom runtimes elsewhere, and each one
19
- wants skills, rules, instructions, commands, MCP, hooks, and settings in its own native shape.
18
+ Your AI agents are multiplying. OpenClaw, Claude Code, Codex CLI, Copilot, Hermes and the next
19
+ harness your team adopts. Each one wants its own skills, rules, instructions, commands, subagents,
20
+ MCP servers, hooks, and settings, in its own layout.
20
21
 
21
22
  **agentwheel makes that one declared state.** Add packages to `.agentwheel/config.json`, preview the
22
23
  runtime changes, then install the declared state into each target. Updates move tracking sources
@@ -37,6 +38,20 @@ live in your workspace, and runtimes stay generated output.
37
38
  > `add`, `install`, `update`, and `uninstall`. A hidden `sync` shim remains for one release only
38
39
  > so old bootstrapped skills can self-update; use `install` in all new docs and scripts.
39
40
 
41
+ ## Supported runtimes & resources
42
+
43
+ agentwheel installs OpenPack resources into five built-in runtimes and into custom harnesses:
44
+
45
+ - **OpenClaw** — `.openclaw/`
46
+ - **Claude Code** — `.claude/`
47
+ - **Codex CLI** — `.codex/`
48
+ - **GitHub Copilot** — `.github/`
49
+ - **Hermes** — `.hermes/`
50
+ - **Bring your own** — JSONC config adapters with `--adapter-config`, or programmatic adapters with `--adapter-module`
51
+
52
+ Supported resource types include instructions, rules, skills, commands, subagents, MCP, hooks,
53
+ settings, plugins, and fragments; see the full per-runtime target table below.
54
+
40
55
  ## Core Commands
41
56
 
42
57
  | Command | Meaning |
@@ -133,6 +148,7 @@ For a control-plane setup, define named agents in config. Global config lives at
133
148
  agentwheel install --agent lab-openclaw
134
149
  agentwheel install --all
135
150
  agentwheel install --profile daily
151
+ agentwheel install --all-detected
136
152
  ```
137
153
 
138
154
  SSH targets use the same manifest and drift model as local targets. Planning reads the remote
@@ -146,7 +162,9 @@ agentwheel init --fleet-example
146
162
  ```
147
163
 
148
164
  Target resolution order is exact: `--target-root` wins, then `--agent`, then auto-detect from the
149
- current directory, then fallback to the current directory.
165
+ current directory, then fallback to the current directory. `--all-detected` is an explicit escape hatch
166
+ for applying to every runtime marker found in the current directory or `--target-root`; `--all` remains
167
+ reserved for configured agents.
150
168
 
151
169
  ## Core Ideas
152
170
 
@@ -290,15 +308,21 @@ published:
290
308
  agentwheel install ./my-pack --adapter-config ./myco-internal.jsonc
291
309
  ```
292
310
 
311
+ For adapter behavior that needs code, load a programmatic adapter module:
312
+
313
+ ```bash
314
+ agentwheel install ./my-pack --adapter-module ./myco-adapter.js
315
+ ```
316
+
293
317
  Built-in runtime targets:
294
318
 
295
319
  | Runtime | Main targets |
296
320
  |---|---|
297
- | **OpenClaw** | `.openclaw/AGENTS.md`, `.openclaw/skills`, `.openclaw/rules`, `.openclaw/commands`, MCP/hooks/settings, semantic plugin planning |
321
+ | **OpenClaw** | `.openclaw/AGENTS.md`, `.openclaw/skills`, `.openclaw/rules`, `.openclaw/commands`, `.openclaw/agents`, MCP/hooks/settings, semantic plugin planning |
298
322
  | **Claude Code** | `.claude/CLAUDE.md`, `.claude/skills`, `.claude/commands`, `.claude/agents`, `.claude/rules`, `.claude/.mcp.json`, `.claude/settings.json` |
299
323
  | **Codex CLI** | `.codex/AGENTS.md`, `.codex/skills`, `.codex/commands`, `.codex/agents`, `.codex/rules`, `.codex/config.toml`, `.codex/hooks.json` |
300
- | **Hermes** | `.hermes/AGENTS.md`, `.hermes/skills`, `.hermes/rules`, `.hermes/commands`, MCP/hooks/settings |
301
- | **GitHub Copilot** | `.github/copilot-instructions.md`, `.github/instructions`, `.github/prompts` |
324
+ | **Hermes** | `.hermes/AGENTS.md`, `.hermes/skills`, `.hermes/rules`, `.hermes/commands`, `.hermes/agents`, MCP/hooks/settings |
325
+ | **GitHub Copilot** | `.github/copilot-instructions.md`, `.github/instructions`, `.github/prompts`, `.github/skills`, `.github/agents`, `.vscode/mcp.json` |
302
326
 
303
327
  ## Docs
304
328
 
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
 
10
10
  // src/cli/index.ts
11
11
  import { mkdir as mkdir14, rm as rm9, writeFile as writeFile12 } from "fs/promises";
12
- import { dirname as dirname21, join as join28, resolve as resolve17 } from "path";
12
+ import { dirname as dirname21, join as join28, resolve as resolve18 } from "path";
13
13
  import { fileURLToPath as fileURLToPath3 } from "url";
14
14
  import { Command } from "commander";
15
15
 
@@ -131,9 +131,9 @@ var copilotAdapter = {
131
131
  instructions: { enabled: true, dest: ".github/copilot-instructions.md" },
132
132
  rules: { enabled: true, dest: ".github/instructions" },
133
133
  commands: { enabled: true, dest: ".github/prompts" },
134
- // GitHub Copilot has no native SKILL.md runtime surface; keep skills disabled
135
- // until a conversion target is introduced.
136
- skills: { enabled: false, dest: ".github/skills" }
134
+ skills: { enabled: true, dest: ".github/skills" },
135
+ subagents: { enabled: true, dest: ".github/agents" },
136
+ mcp: { enabled: true, dest: ".vscode/mcp.json", merge: "json-deep" }
137
137
  }
138
138
  };
139
139
 
@@ -162,6 +162,7 @@ var hermesAdapter = {
162
162
  rules: { enabled: true, dest: ".hermes/rules" },
163
163
  skills: { enabled: true, dest: ".hermes/skills" },
164
164
  commands: { enabled: true, dest: ".hermes/commands" },
165
+ subagents: { enabled: true, dest: ".hermes/agents" },
165
166
  mcp: { enabled: true, dest: ".hermes/mcp", merge: "json-deep" },
166
167
  hooks: { enabled: true, dest: ".hermes/hooks", merge: "json-deep" },
167
168
  settings: { enabled: true, dest: ".hermes/settings.json", merge: "json-deep" }
@@ -177,6 +178,7 @@ var openClawAdapter = {
177
178
  rules: { enabled: true, dest: ".openclaw/rules" },
178
179
  skills: { enabled: true, dest: ".openclaw/skills" },
179
180
  commands: { enabled: true, dest: ".openclaw/commands" },
181
+ subagents: { enabled: true, dest: ".openclaw/agents" },
180
182
  mcp: { enabled: true, dest: ".openclaw/mcp", merge: "json-deep" },
181
183
  hooks: { enabled: true, dest: ".openclaw/hooks", merge: "json-deep" },
182
184
  settings: { enabled: true, dest: ".openclaw/settings.json", merge: "json-deep" },
@@ -542,13 +544,13 @@ async function spawnWithInput(command, args, input) {
542
544
  if (result.stderr) throw new Error(result.stderr);
543
545
  }
544
546
  function waitForProcess(child, label) {
545
- return new Promise((resolve18, reject) => {
547
+ return new Promise((resolve19, reject) => {
546
548
  const stderr = [];
547
549
  child.stderr?.on("data", (chunk) => stderr.push(chunk));
548
550
  child.on("error", reject);
549
551
  child.on("close", (code) => {
550
552
  const message = Buffer.concat(stderr).toString("utf8");
551
- if (code === 0) resolve18({ stderr: "" });
553
+ if (code === 0) resolve19({ stderr: "" });
552
554
  else reject(new Error(`${label} exited ${code}${message ? `: ${message}` : ""}`));
553
555
  });
554
556
  });
@@ -654,6 +656,7 @@ import { resolve as resolve3 } from "path";
654
656
  // src/model/manifest.ts
655
657
  import { z as z4 } from "zod";
656
658
  var dependencyRoleSchema = z4.enum(["root", "direct", "transitive", "fragment"]);
659
+ var legacyUnownedWorkspaceOwner = "legacy:unowned";
657
660
  var manifestEntryV1Schema = z4.object({
658
661
  path: z4.string().min(1),
659
662
  artifactType: artifactTypeSchema,
@@ -676,6 +679,7 @@ var manifestEntrySchema = manifestEntryV1Schema.extend({
676
679
  dependencyRole: dependencyRoleSchema.default("root"),
677
680
  owners: z4.array(z4.string().min(1)).min(1),
678
681
  refCount: z4.number().int().positive(),
682
+ workspaceOwner: z4.string().min(1).default(legacyUnownedWorkspaceOwner),
679
683
  graphLockDigest: z4.string().min(1).optional()
680
684
  }).transform((entry) => {
681
685
  const owners = [...new Set(entry.owners)].sort();
@@ -1388,6 +1392,7 @@ function manifestEntryForOperation(operation, values) {
1388
1392
  dependencyRole: operation.dependencyRole ?? "root",
1389
1393
  owners,
1390
1394
  refCount: owners.length,
1395
+ workspaceOwner: operation.workspaceOwner ?? "workspace:unknown",
1391
1396
  kind: operation.kind,
1392
1397
  hash: values.hash,
1393
1398
  sourceHash: values.sourceHash,
@@ -1476,6 +1481,10 @@ async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot,
1476
1481
  return createPlanFromOperations(desired, adapter, targetRoot, manifest, transport, options);
1477
1482
  }
1478
1483
  async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifest, transport, options) {
1484
+ const workspaceOwner = options.workspaceOwner;
1485
+ if (workspaceOwner) {
1486
+ for (const op of desiredOps) op.workspaceOwner = workspaceOwner;
1487
+ }
1479
1488
  for (const op of desiredOps) assertOperationContained(op, targetRoot);
1480
1489
  const migration = await migrateManifestForPlan(manifest, desiredOps, targetRoot, transport);
1481
1490
  const effectiveEntries = migration.entries;
@@ -1518,6 +1527,12 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
1518
1527
  continue;
1519
1528
  }
1520
1529
  const existing = manifestByPath.get(op.relativeDestPath);
1530
+ if (existing && workspaceOwner && !entryOwnedByWorkspace(existing, workspaceOwner)) {
1531
+ if (!await canAdoptLegacyUnownedEntry(existing, op, transport)) {
1532
+ operations.push(keepForeignManifestEntryOperation(existing, targetRoot, workspaceOwner, op));
1533
+ continue;
1534
+ }
1535
+ }
1521
1536
  const exists = await transport.pathExists(op.destPath);
1522
1537
  if (!exists) {
1523
1538
  operations.push({ ...op, action: "create", reason: "destination missing" });
@@ -1559,6 +1574,10 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
1559
1574
  const destPath = join5(targetRoot, entry.path);
1560
1575
  if (!await transport.pathExists(destPath)) continue;
1561
1576
  const currentHash = await transport.hashPath(destPath);
1577
+ if (workspaceOwner && !entryOwnedByWorkspace(entry, workspaceOwner)) {
1578
+ operations.push(keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, void 0, currentHash));
1579
+ continue;
1580
+ }
1562
1581
  if (currentHash !== entry.hash) {
1563
1582
  operations.push({
1564
1583
  action: "drift",
@@ -1714,6 +1733,16 @@ function operationMatchesManifestEntry(op, entry) {
1714
1733
  if ("graphNodeId" in entry && entry.graphNodeId && op.graphNodeId && entry.graphNodeId === op.graphNodeId) return true;
1715
1734
  return op.packageName !== void 0 && entry.packageName === op.packageName;
1716
1735
  }
1736
+ function entryOwnedByWorkspace(entry, workspaceOwner) {
1737
+ return "workspaceOwner" in entry && entry.workspaceOwner === workspaceOwner;
1738
+ }
1739
+ async function canAdoptLegacyUnownedEntry(entry, op, transport) {
1740
+ if (!("workspaceOwner" in entry) || entry.workspaceOwner !== legacyUnownedWorkspaceOwner) return false;
1741
+ if (entry.artifactType !== op.artifactType || entry.kind !== op.kind) return false;
1742
+ if (!op.desiredHash || entry.sourceHash !== op.desiredHash) return false;
1743
+ if (!await transport.pathExists(op.destPath)) return false;
1744
+ return await transport.hashPath(op.destPath) === entry.hash;
1745
+ }
1717
1746
  function collisionOperation(op, group, incumbent) {
1718
1747
  const owners = group.map(describeOperationOwner).sort();
1719
1748
  const incumbentText = incumbent ? `; incumbent ${describeOperationOwner(incumbent)} keeps the plain name` : "";
@@ -1737,6 +1766,7 @@ function adoptLegacyEntry(entry, op) {
1737
1766
  dependencyRole: op.dependencyRole ?? "root",
1738
1767
  owners,
1739
1768
  refCount: owners.length,
1769
+ workspaceOwner: op.workspaceOwner ?? legacyUnownedWorkspaceOwner,
1740
1770
  graphLockDigest: op.graphLockDigest,
1741
1771
  composedFrom: op.composedFrom ?? entry.composedFrom
1742
1772
  };
@@ -1759,6 +1789,7 @@ function operationMetadataFromEntry(entry) {
1759
1789
  graphNodeId: entry.graphNodeId,
1760
1790
  dependencyRole: entry.dependencyRole,
1761
1791
  owners: entry.owners,
1792
+ workspaceOwner: entry.workspaceOwner,
1762
1793
  graphLockDigest: entry.graphLockDigest
1763
1794
  };
1764
1795
  }
@@ -1766,7 +1797,31 @@ function operationMetadataFromEntry(entry) {
1766
1797
  installName: entry.artifactName,
1767
1798
  logicalSelector: `${entry.artifactType}/${entry.artifactName}`,
1768
1799
  dependencyRole: "root",
1769
- owners: [entry.packageName ?? "legacy"]
1800
+ owners: [entry.packageName ?? "legacy"],
1801
+ workspaceOwner: legacyUnownedWorkspaceOwner
1802
+ };
1803
+ }
1804
+ function keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, operation, currentHash) {
1805
+ const owner = "workspaceOwner" in entry ? entry.workspaceOwner : legacyUnownedWorkspaceOwner;
1806
+ return {
1807
+ action: "keep",
1808
+ artifactType: entry.artifactType,
1809
+ artifactName: entry.artifactName,
1810
+ kind: entry.kind,
1811
+ destPath: operation?.destPath ?? join5(targetRoot, entry.path),
1812
+ relativeDestPath: entry.path,
1813
+ desiredHash: entry.sourceHash,
1814
+ currentHash: currentHash ?? operation?.currentHash ?? entry.hash,
1815
+ manifestHash: entry.hash,
1816
+ reason: `foreign artifact owned by ${owner}; kept outside workspace ${workspaceOwner}`,
1817
+ channel: entry.channel,
1818
+ packageName: entry.packageName,
1819
+ semanticCommand: entry.semanticCommand,
1820
+ execute: entry.executed,
1821
+ mergeStrategy: entry.mergeStrategy,
1822
+ composedFrom: entry.composedFrom,
1823
+ preserveInManifest: true,
1824
+ ...operationMetadataFromEntry(entry)
1770
1825
  };
1771
1826
  }
1772
1827
  function normalizeOperationOwners(op) {
@@ -2714,7 +2769,7 @@ async function withFilesystemLock(lockPath, timeoutMs, fn) {
2714
2769
  if (Date.now() - started > timeoutMs) {
2715
2770
  throw new Error(`Timed out waiting for git cache lock at ${lockPath}`);
2716
2771
  }
2717
- await new Promise((resolve18) => setTimeout(resolve18, 50));
2772
+ await new Promise((resolve19) => setTimeout(resolve19, 50));
2718
2773
  }
2719
2774
  }
2720
2775
  try {
@@ -4318,13 +4373,33 @@ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges
4318
4373
  async function processRequirement(requirement, options, fetchCache, nodesByKey, rootResults, edgeMap) {
4319
4374
  try {
4320
4375
  const lockLabel = options.offline ? "Offline" : options.frozenLock ? "Frozen lock" : "Locked install";
4321
- const lockedByReference = lockedNodeForRequirementReference(requirement, options, lockLabel);
4322
- const normalized = lockedByReference ? normalizedSourceFromLockedNode(lockedByReference.node) : await normalizeDependencySource(requirement.source, {
4376
+ let lockedByReference = lockedNodeForRequirementReference(requirement, options, lockLabel);
4377
+ let normalized = lockedByReference ? normalizedSourceFromLockedNode(lockedByReference.node) : await normalizeDependencySource(requirement.source, {
4323
4378
  declaringPackageRoot: requirement.declaringPackageRoot,
4324
4379
  workspaceRoot: options.workspaceRoot,
4325
4380
  ref: requirement.ref,
4326
4381
  registryClient: options.registryClient
4327
4382
  });
4383
+ if (lockedByReference && shouldCheckLockedRootSource(requirement)) {
4384
+ const declared = await normalizeDependencySource(requirement.source, {
4385
+ declaringPackageRoot: requirement.declaringPackageRoot,
4386
+ workspaceRoot: options.workspaceRoot,
4387
+ ref: requirement.ref,
4388
+ registryClient: options.registryClient
4389
+ });
4390
+ if (lockedRootSourceDrifted(declared, lockedByReference.node)) {
4391
+ if (options.frozenLock || options.offline) {
4392
+ throw new Error(
4393
+ `${lockLabel} root '${requirement.rootId}' source differs from declared source:
4394
+ - declared: ${declared.normalizedSource}
4395
+ - locked: ${lockedByReference.node.normalizedSource}
4396
+ Run without ${lockLabel === "Offline" ? "--offline" : "--frozen-lock"} first.`
4397
+ );
4398
+ }
4399
+ lockedByReference = void 0;
4400
+ normalized = declared;
4401
+ }
4402
+ }
4328
4403
  const frozen = lockedByReference ?? lockedNodeForRequirement(normalized.normalizedSource, requirement, options, lockLabel);
4329
4404
  let fetched;
4330
4405
  try {
@@ -4747,6 +4822,18 @@ function lockedNodeForRequirementReference(requirement, options, label) {
4747
4822
  requestedRef: node.driver === "git" ? node.resolvedCommit ?? node.requestedRef : node.requestedRef
4748
4823
  };
4749
4824
  }
4825
+ function shouldCheckLockedRootSource(requirement) {
4826
+ if (!requirement.useLock) return false;
4827
+ if (requirement.depth !== 0 || !requirement.rootId) return false;
4828
+ return isExplicitNonRegistrySource(requirement.source);
4829
+ }
4830
+ function isExplicitNonRegistrySource(source) {
4831
+ const trimmed = source.trim();
4832
+ return trimmed === "~" || trimmed.startsWith("~/") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("/") || trimmed.startsWith("local:") || trimmed.startsWith("github:") || trimmed.startsWith("git:") || trimmed.startsWith("skillkit:") || trimmed.startsWith("vercel:");
4833
+ }
4834
+ function lockedRootSourceDrifted(declared, locked) {
4835
+ return declared.normalizedSource !== locked.normalizedSource || declared.requestedRef !== locked.requestedRef;
4836
+ }
4750
4837
  function normalizedSourceFromLockedNode(node) {
4751
4838
  return {
4752
4839
  source: node.source,
@@ -4765,8 +4852,8 @@ function verifyIntegrity(integrity, sourceHash, label) {
4765
4852
  async function withCachePathLock(path, fn) {
4766
4853
  const previous = cacheLocks.get(path) ?? Promise.resolve();
4767
4854
  let release = () => void 0;
4768
- const current = previous.then(() => new Promise((resolve18) => {
4769
- release = resolve18;
4855
+ const current = previous.then(() => new Promise((resolve19) => {
4856
+ release = resolve19;
4770
4857
  }));
4771
4858
  cacheLocks.set(path, current);
4772
4859
  await previous;
@@ -4974,7 +5061,7 @@ import { rm as rm8 } from "fs/promises";
4974
5061
  // src/lifecycle/source-plan.ts
4975
5062
  import { createHash as createHash6 } from "crypto";
4976
5063
  import { mkdir as mkdir12 } from "fs/promises";
4977
- import { dirname as dirname17, join as join22 } from "path";
5064
+ import { dirname as dirname17, join as join22, resolve as resolve14 } from "path";
4978
5065
 
4979
5066
  // src/resolve/graph-diff.ts
4980
5067
  function diffGraphLocks(previous, next) {
@@ -5512,7 +5599,8 @@ async function createGraphSourcePlan(options) {
5512
5599
  const manifest = await readInstallManifest(options.targetRoot, options.adapter.name, transport);
5513
5600
  const plan = await createCombinedInstallPlan(desiredArtifacts, options.adapter, options.targetRoot, manifest, transport, {
5514
5601
  baseRevision: manifest?.revision ?? null,
5515
- graphLockDigest
5602
+ graphLockDigest,
5603
+ workspaceOwner: workspaceOwnerId(workspaceRoot)
5516
5604
  });
5517
5605
  return {
5518
5606
  plan,
@@ -5570,6 +5658,9 @@ function sanitizePathSegment(value) {
5570
5658
  function digestGraphLock(lock) {
5571
5659
  return createHash6("sha256").update(canonicalGraphLockJson(lock)).digest("hex");
5572
5660
  }
5661
+ function workspaceOwnerId(workspaceRoot) {
5662
+ return `workspace-root:${resolve14(workspaceRoot)}`;
5663
+ }
5573
5664
  function assertFrozenGraph(previousLock, graph, frozen, label) {
5574
5665
  if (!frozen) return;
5575
5666
  if (!previousLock) {
@@ -5743,7 +5834,7 @@ async function packageFromSource(source, options) {
5743
5834
  }
5744
5835
 
5745
5836
  // src/runtime/target.ts
5746
- import { basename as basename12, dirname as dirname18, join as join23, resolve as resolve14 } from "path";
5837
+ import { basename as basename12, dirname as dirname18, join as join23, resolve as resolve15 } from "path";
5747
5838
  var runtimeMarkers = [
5748
5839
  { adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
5749
5840
  { adapter: "claude", dirs: [".claude"] },
@@ -5752,9 +5843,9 @@ var runtimeMarkers = [
5752
5843
  { adapter: "copilot", dirs: [".github"] }
5753
5844
  ];
5754
5845
  async function resolveRuntimeTarget(request = {}) {
5755
- const cwd = resolve14(request.cwd ?? process.cwd());
5846
+ const cwd = resolve15(request.cwd ?? process.cwd());
5756
5847
  if (request.targetRoot) {
5757
- const targetRoot = resolve14(request.targetRoot);
5848
+ const targetRoot = resolve15(request.targetRoot);
5758
5849
  return {
5759
5850
  adapter: request.adapter ?? "openclaw",
5760
5851
  targetRoot,
@@ -5783,7 +5874,7 @@ async function resolveRuntimeTarget(request = {}) {
5783
5874
  async function resolveAllRuntimeTargets(request = {}) {
5784
5875
  if (request.targetRoot) return [await resolveRuntimeTarget(request)];
5785
5876
  if (request.agent) return [await resolveRuntimeTarget(request)];
5786
- const cwd = resolve14(request.cwd ?? process.cwd());
5877
+ const cwd = resolve15(request.cwd ?? process.cwd());
5787
5878
  const workspaceRoot = await findWorkspaceRoot(cwd);
5788
5879
  const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
5789
5880
  const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot));
@@ -5792,8 +5883,29 @@ async function resolveAllRuntimeTargets(request = {}) {
5792
5883
  }
5793
5884
  return targets;
5794
5885
  }
5886
+ async function resolveAllDetectedRuntimeTargets(request = {}) {
5887
+ if (request.agent) return [await resolveRuntimeTarget(request)];
5888
+ const scanRoot = runtimeScanRoot(request);
5889
+ const matches = await detectRuntimeTargets(scanRoot, request.adapter);
5890
+ if (matches.length === 0) {
5891
+ throw new Error(`No runtime directories detected at ${scanRoot}. Pass --target-root or --agent.`);
5892
+ }
5893
+ return Promise.all(matches.map(async (match) => ({
5894
+ ...match,
5895
+ workspaceRoot: await findWorkspaceRoot(match.targetRoot),
5896
+ transport: "local",
5897
+ source: "auto-detect"
5898
+ })));
5899
+ }
5795
5900
  async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
5796
- const root = resolve14(cwd);
5901
+ const unique = await detectRuntimeTargets(cwd, adapterFilter);
5902
+ if (unique.length > 1) {
5903
+ throw new Error(`Multiple runtime directories detected: ${unique.map((item) => `${item.adapter} at ${item.targetRoot}`).join(", ")}. Pass --adapter, --agent, or --all-detected.`);
5904
+ }
5905
+ return unique[0];
5906
+ }
5907
+ async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
5908
+ const root = resolve15(cwd);
5797
5909
  const matches = [];
5798
5910
  for (const marker of runtimeMarkers) {
5799
5911
  if (adapterFilter && marker.adapter !== adapterFilter) continue;
@@ -5805,11 +5917,7 @@ async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
5805
5917
  }
5806
5918
  }
5807
5919
  }
5808
- const unique = dedupeTargets(matches);
5809
- if (unique.length > 1) {
5810
- throw new Error(`Multiple runtime directories detected: ${unique.map((item) => `${item.adapter} at ${item.targetRoot}`).join(", ")}. Pass --adapter or --agent.`);
5811
- }
5812
- return unique[0];
5920
+ return dedupeTargets(matches);
5813
5921
  }
5814
5922
  function targetFromAgent(name, config, workspaceRoot) {
5815
5923
  const agent = config.agents[name];
@@ -5838,6 +5946,11 @@ function dedupeTargets(matches) {
5838
5946
  }
5839
5947
  return [...byKey.values()];
5840
5948
  }
5949
+ function runtimeScanRoot(request) {
5950
+ const root = resolve15(request.targetRoot ?? request.cwd ?? process.cwd());
5951
+ if (request.targetRoot) return root;
5952
+ return runtimeMarkers.some((marker) => marker.dirs.includes(basename12(root))) ? dirname18(root) : root;
5953
+ }
5841
5954
 
5842
5955
  // src/cli/update-check.ts
5843
5956
  import { mkdir as mkdir13, readFile as readFile16, writeFile as writeFile10 } from "fs/promises";
@@ -5919,9 +6032,9 @@ function normalizeVersion(version) {
5919
6032
 
5920
6033
  // src/model/package-validate.ts
5921
6034
  import { stat as stat11 } from "fs/promises";
5922
- import { resolve as resolve15 } from "path";
6035
+ import { resolve as resolve16 } from "path";
5923
6036
  async function validatePackage(root) {
5924
- const packageRoot = resolve15(root);
6037
+ const packageRoot = resolve16(root);
5925
6038
  const findings = [];
5926
6039
  const manifestPath = await findPackageManifestPath(packageRoot);
5927
6040
  if (!manifestPath) {
@@ -5989,7 +6102,7 @@ async function validateManifestComposeInclude(packageRoot, selector, optional, f
5989
6102
  try {
5990
6103
  validateSelector(selector, "compose.include", findings, manifestPath, { fragmentsOnly: true, aliases });
5991
6104
  if (isCrossPackageSelector(selector)) return;
5992
- const full = resolve15(packageRoot, selector);
6105
+ const full = resolve16(packageRoot, selector);
5993
6106
  if (full !== packageRoot && !full.startsWith(`${packageRoot}/`)) {
5994
6107
  findings.push({ level: "error", message: `Compose include escapes package root: ${selector}`, path: manifestPath });
5995
6108
  return;
@@ -6043,10 +6156,10 @@ function isCrossPackageSelector(value) {
6043
6156
 
6044
6157
  // src/model/package-migrate.ts
6045
6158
  import { readFile as readFile17, rename as rename4, writeFile as writeFile11 } from "fs/promises";
6046
- import { join as join26, resolve as resolve16 } from "path";
6159
+ import { join as join26, resolve as resolve17 } from "path";
6047
6160
  import { applyEdits, modify, parse as parse3 } from "jsonc-parser";
6048
6161
  async function migratePackageManifest(root) {
6049
- const packageRoot = resolve16(root);
6162
+ const packageRoot = resolve17(root);
6050
6163
  for (const name of openPackManifestNames) {
6051
6164
  const path = join26(packageRoot, name);
6052
6165
  if (await pathExists(path)) {
@@ -6165,13 +6278,13 @@ program.command("scan").description("scan a package source for validation findin
6165
6278
  }
6166
6279
  if (!result.ok) process.exitCode = 1;
6167
6280
  });
6168
- program.command("plan").description("preview what install would reconcile without writing").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").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).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
6281
+ program.command("plan").description("preview what install would reconcile without writing").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").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("--all-detected", "run for every runtime directory detected in the target root", 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).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
6169
6282
  await runInstallCommand(source, { ...options, dryRun: true }, { apply: false });
6170
6283
  });
6171
- program.command("install").description("install configured packages into runtime targets").argument("[name-or-source]", "configured package name/source or package source to add and install").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").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).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).addHelpText("after", "\nScoped install never removes files owned only by other configured packages; run a full install to reconcile those removals.\n").action(async (source, options) => {
6284
+ program.command("install").description("install configured packages into runtime targets").argument("[name-or-source]", "configured package name/source or package source to add and install").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").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("--all-detected", "run for every runtime directory detected in the target root", 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).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).addHelpText("after", "\nScoped install never removes files owned only by other configured packages; run a full install to reconcile those removals.\n").action(async (source, options) => {
6172
6285
  await runInstallCommand(source, options, { apply: !options.dryRun });
6173
6286
  });
6174
- program.command("sync", { hidden: true }).argument("[name-or-source]", "configured package name/source or package source").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").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).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
6287
+ program.command("sync", { hidden: true }).argument("[name-or-source]", "configured package name/source or package source").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").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("--all-detected", "run for every runtime directory detected in the target root", 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).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
6175
6288
  console.error("warning: 'agentwheel sync' is deprecated and will be removed in 0.10. Use 'agentwheel install'.");
6176
6289
  await runInstallCommand(source, options, { apply: !options.dryRun });
6177
6290
  });
@@ -6345,7 +6458,7 @@ async function runInstallCommand(nameOrSource, options, behavior) {
6345
6458
  let extraPackage;
6346
6459
  if (nameOrSource && !configured) {
6347
6460
  try {
6348
- const entry = await packageEntryFromSource(nameOrSource, target.workspaceRoot, options);
6461
+ const entry = await packageEntryFromSource(nameOrSource, target.workspaceRoot, { ...options, adapter: options.adapter ?? target.adapter });
6349
6462
  scope = entry.name;
6350
6463
  if (behavior.apply) {
6351
6464
  await writeWorkspaceConfig(target.workspaceRoot, upsertPackage(await readWorkspaceConfig(target.workspaceRoot), entry));
@@ -6433,9 +6546,15 @@ function nextInstallNudge() {
6433
6546
  return "Preview: agentwheel plan - Apply: agentwheel install";
6434
6547
  }
6435
6548
  async function resolveCliTargets(options) {
6549
+ if (options.all && options.allDetected) {
6550
+ throw new Error("Choose either --all for configured agents or --all-detected for detected runtime directories.");
6551
+ }
6436
6552
  if (options.all) {
6437
6553
  return resolveAllRuntimeTargets({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent, all: options.all });
6438
6554
  }
6555
+ if (options.allDetected) {
6556
+ return resolveAllDetectedRuntimeTargets({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent, allDetected: options.allDetected });
6557
+ }
6439
6558
  return [await resolveRuntimeTarget({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent })];
6440
6559
  }
6441
6560
  async function resolveAdapterForTarget(target, options) {
@@ -6659,6 +6778,7 @@ function keepManifestEntryOperation(entry, targetRoot, rootId, operation, option
6659
6778
  graphNodeId: fresh?.graphNodeId ?? ("graphNodeId" in entry ? entry.graphNodeId : void 0),
6660
6779
  dependencyRole: fresh?.dependencyRole ?? ("dependencyRole" in entry ? entry.dependencyRole : "root"),
6661
6780
  owners: fresh?.owners ?? owners,
6781
+ workspaceOwner: fresh?.workspaceOwner ?? ("workspaceOwner" in entry ? entry.workspaceOwner : "legacy:unowned"),
6662
6782
  graphLockDigest: fresh ? void 0 : "graphLockDigest" in entry ? entry.graphLockDigest : void 0
6663
6783
  };
6664
6784
  }
@@ -6955,7 +7075,7 @@ function withFleetExample(config) {
6955
7075
  };
6956
7076
  }
6957
7077
  async function findAgentwheelPackageRoot(start) {
6958
- let current = resolve17(start);
7078
+ let current = resolve18(start);
6959
7079
  while (true) {
6960
7080
  if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
6961
7081
  const parent = dirname21(current);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentwheel",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Weave skills, rules, and instructions across every AI agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",