agentwheel 0.3.1 → 0.4.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 +52 -6
  2. package/dist/index.js +282 -116
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -24,17 +24,19 @@ So you copy-paste. You forget which agent has the latest version. You tweak a ru
24
24
  ```bash
25
25
  npm i -g agentwheel
26
26
  agentwheel add github:your-org/agent-pack
27
- agentwheel update --dry-run # show me what would change
28
- agentwheel update # install into configured agents
27
+ cd ~/.openclaw
28
+ agentwheel sync --dry-run # show me what would change
29
+ agentwheel sync # install into the detected runtime
29
30
  ```
30
31
 
31
32
  No lock-in. No central gatekeeper. Your packages live in plain git repos, your customizations live in your own repo, and anything reachable by a URL just works.
32
33
 
33
34
  ---
34
35
 
35
- > **Status: early (v0.3).** The lifecycle core is real and tested — local/git/skillkit/vercel
36
+ > **Status: early (v0.4).** The lifecycle core is real and tested — local/git/skillkit/vercel
36
37
  > sources, optional registry discovery, plan/sync/update/drift/uninstall, overlays, eject/remember,
37
- > profiles, rich JSON merge, and pluggable adapters. Expect sharp edges.
38
+ > profiles, runtime auto-detection, fleet targeting, rich JSON merge, and pluggable adapters.
39
+ > Expect sharp edges.
38
40
 
39
41
  ## What it does
40
42
 
@@ -51,8 +53,9 @@ npm i -g agentwheel
51
53
 
52
54
  agentwheel init
53
55
  agentwheel add github:your-org/agent-pack --adapter openclaw --mode tracking
54
- agentwheel update --dry-run
55
- agentwheel update
56
+ cd ~/.openclaw
57
+ agentwheel sync --dry-run
58
+ agentwheel sync
56
59
  ```
57
60
 
58
61
  Prefer pnpm? `pnpm add -g agentwheel` works too.
@@ -69,6 +72,48 @@ pnpm link --global
69
72
 
70
73
  `plan`, `sync --dry-run`, and `update --dry-run` show exactly what would change before anything is written. They're the commands to trust.
71
74
 
75
+ ## Runtime targeting
76
+
77
+ Normal use no longer needs `--target-root`. Run agentwheel inside a runtime folder and it detects
78
+ the target:
79
+
80
+ ```bash
81
+ cd ~/.openclaw
82
+ agentwheel sync github:your-org/agent-pack
83
+ ```
84
+
85
+ If the current directory is already the runtime directory (`~/.openclaw`), agentwheel uses its
86
+ parent as the root so output lands in `~/.openclaw/skills`, not `~/.openclaw/.openclaw/skills`.
87
+ If the current directory contains a runtime directory (`./.openclaw`), that directory is used as
88
+ the target under the current project.
89
+
90
+ For a control-plane setup, define named agents in config. Global config lives at
91
+ `~/.agentwheel/config.json`; project config lives at `.agentwheel/config.json`; project values win.
92
+
93
+ ```jsonc
94
+ {
95
+ "agents": {
96
+ "lab-openclaw": { "adapter": "openclaw", "root": "/Users/me/.openclaw-home" },
97
+ "docs-copilot": { "adapter": "copilot", "root": "/Users/me/projects/docs" }
98
+ },
99
+ "profiles": {
100
+ "daily": [
101
+ { "agent": "lab-openclaw" },
102
+ { "agent": "docs-copilot" }
103
+ ]
104
+ }
105
+ }
106
+ ```
107
+
108
+ ```bash
109
+ agentwheel sync --agent lab-openclaw
110
+ agentwheel sync --all
111
+ agentwheel sync --profile daily
112
+ ```
113
+
114
+ Target resolution order is exact: `--target-root` wins, then `--agent`, then auto-detect from the
115
+ current directory, then fallback to the current directory.
116
+
72
117
  ## Core ideas
73
118
 
74
119
  **Three places, one direction:**
@@ -172,6 +217,7 @@ clear conversion format.
172
217
  - [x] **v0.1** — install spine: local sources; openclaw/claude/codex adapters; skills/rules/instructions; `plan` · `sync` · `--dry-run` · `uninstall`; manifest + drift + idempotency.
173
218
  - [x] **v0.2** — git source driver; `update` (pinned & tracking); overlays/additive/override/eject; `init`; hermes + copilot adapters; commands/mcp/hooks artifacts; OpenClaw semantic plugin planning.
174
219
  - [x] **v0.3** — skillkit/vercel source drivers; optional registry & federation; programmatic adapters behind `--allow-adapter-code`; rich JSON merge for mcp/hooks/settings; profiles.
220
+ - [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`.
175
221
 
176
222
  ## Design docs
177
223
 
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
 
10
10
  // src/cli/index.ts
11
11
  import { mkdir as mkdir7, rm as rm8, writeFile as writeFile4 } from "fs/promises";
12
- import { join as join18 } from "path";
12
+ import { join as join19 } from "path";
13
13
  import { Command } from "commander";
14
14
 
15
15
  // src/adapters/resolve.ts
@@ -1584,7 +1584,8 @@ async function stageSource(driver, source, options = {}) {
1584
1584
 
1585
1585
  // src/model/workspace.ts
1586
1586
  import { readFile as readFile8 } from "fs/promises";
1587
- import { join as join13 } from "path";
1587
+ import { homedir as homedir3 } from "os";
1588
+ import { dirname as dirname6, join as join13, resolve as resolve8 } from "path";
1588
1589
  import { z as z5 } from "zod";
1589
1590
  var workspacePackageSchema = z5.object({
1590
1591
  name: z5.string().min(1),
@@ -1598,6 +1599,7 @@ var workspacePackageSchema = z5.object({
1598
1599
  requestedRef: z5.string().min(1).optional()
1599
1600
  });
1600
1601
  var workspaceProfileRuntimeSchema = z5.object({
1602
+ agent: z5.string().min(1).optional(),
1601
1603
  adapter: z5.string().min(1).default("openclaw"),
1602
1604
  adapterConfig: z5.string().min(1).optional(),
1603
1605
  adapterModule: z5.string().min(1).optional(),
@@ -1611,18 +1613,23 @@ var workspaceRegistrySchema = z5.object({
1611
1613
  sources: z5.array(z5.string().min(1)).optional(),
1612
1614
  ttlSeconds: z5.number().int().positive().optional()
1613
1615
  }).default({});
1616
+ var workspaceAgentSchema = z5.object({
1617
+ adapter: z5.string().min(1),
1618
+ root: z5.string().min(1)
1619
+ });
1614
1620
  var workspaceConfigSchema = z5.object({
1615
1621
  schemaVersion: z5.literal(1),
1616
1622
  packages: z5.array(workspacePackageSchema).default([]),
1617
1623
  registry: workspaceRegistrySchema,
1618
- profiles: z5.record(z5.string(), workspaceProfileSchema).default({})
1624
+ profiles: z5.record(z5.string(), workspaceProfileSchema).default({}),
1625
+ agents: z5.record(z5.string(), workspaceAgentSchema).default({})
1619
1626
  });
1620
1627
  function workspaceConfigPath(workspaceRoot) {
1621
1628
  return join13(workspaceRoot, ".agentwheel", "config.json");
1622
1629
  }
1623
1630
  async function readWorkspaceConfig(workspaceRoot) {
1624
1631
  const path = workspaceConfigPath(workspaceRoot);
1625
- if (!await pathExists(path)) return { schemaVersion: 1, packages: [], registry: {}, profiles: {} };
1632
+ if (!await pathExists(path)) return emptyWorkspaceConfig();
1626
1633
  return workspaceConfigSchema.parse(JSON.parse(await readFile8(path, "utf8")));
1627
1634
  }
1628
1635
  async function writeWorkspaceConfig(workspaceRoot, config) {
@@ -1632,15 +1639,58 @@ function upsertPackage(config, entry) {
1632
1639
  const packages = config.packages.filter((candidate) => candidate.name !== entry.name);
1633
1640
  packages.push(entry);
1634
1641
  packages.sort((a, b) => a.name.localeCompare(b.name));
1635
- return { schemaVersion: 1, packages, registry: config.registry ?? {}, profiles: config.profiles ?? {} };
1642
+ return { schemaVersion: 1, packages, registry: config.registry ?? {}, profiles: config.profiles ?? {}, agents: config.agents ?? {} };
1643
+ }
1644
+ function globalWorkspaceConfigPath(globalRoot = homedir3()) {
1645
+ return join13(globalRoot, ".agentwheel", "config.json");
1646
+ }
1647
+ async function findWorkspaceRoot(start = process.cwd()) {
1648
+ let current = resolve8(start);
1649
+ while (true) {
1650
+ if (await pathExists(workspaceConfigPath(current))) return current;
1651
+ const parent = dirname6(current);
1652
+ if (parent === current) return resolve8(start);
1653
+ current = parent;
1654
+ }
1655
+ }
1656
+ async function readMergedWorkspaceConfig(projectRoot, options = {}) {
1657
+ const global = await readConfigPath(globalWorkspaceConfigPath(options.globalRoot));
1658
+ const project = await readWorkspaceConfig(projectRoot);
1659
+ return mergeWorkspaceConfig(global, project);
1660
+ }
1661
+ function mergeWorkspaceConfig(global, project) {
1662
+ return workspaceConfigSchema.parse({
1663
+ schemaVersion: 1,
1664
+ packages: project.packages.length > 0 ? project.packages : global.packages,
1665
+ registry: {
1666
+ ...global.registry,
1667
+ ...project.registry,
1668
+ sources: project.registry.sources ?? global.registry.sources,
1669
+ ttlSeconds: project.registry.ttlSeconds ?? global.registry.ttlSeconds
1670
+ },
1671
+ profiles: { ...global.profiles, ...project.profiles },
1672
+ agents: { ...global.agents, ...project.agents }
1673
+ });
1674
+ }
1675
+ function resolveConfigPath(path, baseRoot) {
1676
+ if (path.startsWith("~/")) return resolve8(homedir3(), path.slice(2));
1677
+ if (path === "~") return homedir3();
1678
+ return path.startsWith("/") ? resolve8(path) : resolve8(baseRoot, path);
1679
+ }
1680
+ function emptyWorkspaceConfig() {
1681
+ return { schemaVersion: 1, packages: [], registry: {}, profiles: {}, agents: {} };
1682
+ }
1683
+ async function readConfigPath(path) {
1684
+ if (!await pathExists(path)) return emptyWorkspaceConfig();
1685
+ return workspaceConfigSchema.parse(JSON.parse(await readFile8(path, "utf8")));
1636
1686
  }
1637
1687
 
1638
1688
  // src/lifecycle/customization.ts
1639
1689
  import { appendFile, cp as cp4, mkdir as mkdir6, rm as rm5 } from "fs/promises";
1640
- import { dirname as dirname6, join as join14 } from "path";
1690
+ import { dirname as dirname7, join as join14 } from "path";
1641
1691
  async function remember(workspaceRoot, runtime, text) {
1642
1692
  const overlayPath = join14(workspaceRoot, ".agentwheel", "overlays", runtime, "instructions.local.md");
1643
- await mkdir6(dirname6(overlayPath), { recursive: true });
1693
+ await mkdir6(dirname7(overlayPath), { recursive: true });
1644
1694
  await appendFile(overlayPath, `${text.trim()}
1645
1695
  `, "utf8");
1646
1696
  return { overlayPath };
@@ -1665,7 +1715,7 @@ async function ejectArtifact(workspaceRoot, item) {
1665
1715
  throw new Error(`Artifact not found: ${item}`);
1666
1716
  }
1667
1717
  const ejectedPath = join14(workspaceRoot, ".agentwheel", "ejected", ...parsed.packageName.split("/"), parsed.type, parsed.name);
1668
- await mkdir6(dirname6(ejectedPath), { recursive: true });
1718
+ await mkdir6(dirname7(ejectedPath), { recursive: true });
1669
1719
  await rm5(ejectedPath, { recursive: true, force: true });
1670
1720
  await cp4(artifact.stagedPath ?? artifact.sourcePath, ejectedPath, { recursive: artifact.kind === "dir", dereference: true });
1671
1721
  return { ...parsed, ejectedPath };
@@ -1690,8 +1740,8 @@ import { join as join16 } from "path";
1690
1740
 
1691
1741
  // src/registry/client.ts
1692
1742
  import { readFile as readFile9, rm as rm6, stat as stat6 } from "fs/promises";
1693
- import { homedir as homedir3 } from "os";
1694
- import { dirname as dirname7, join as join15, resolve as resolve8 } from "path";
1743
+ import { homedir as homedir4 } from "os";
1744
+ import { dirname as dirname8, join as join15, resolve as resolve9 } from "path";
1695
1745
  import { fileURLToPath } from "url";
1696
1746
 
1697
1747
  // src/model/registry.ts
@@ -1762,7 +1812,7 @@ var RegistryClient = class {
1762
1812
  return process.env.AGENTWHEEL_REGISTRY.split(",").map((source) => source.trim()).filter(Boolean);
1763
1813
  }
1764
1814
  if (this.options.workspaceRoot) {
1765
- const config = await readWorkspaceConfig(this.options.workspaceRoot);
1815
+ const config = await readMergedWorkspaceConfig(this.options.workspaceRoot);
1766
1816
  if (config.registry.sources?.length) return config.registry.sources;
1767
1817
  }
1768
1818
  return [DEFAULT_REGISTRY_SOURCE];
@@ -1770,7 +1820,7 @@ var RegistryClient = class {
1770
1820
  async getTtlMs() {
1771
1821
  if (this.options.ttlMs !== void 0) return this.options.ttlMs;
1772
1822
  if (this.options.workspaceRoot) {
1773
- const config = await readWorkspaceConfig(this.options.workspaceRoot);
1823
+ const config = await readMergedWorkspaceConfig(this.options.workspaceRoot);
1774
1824
  if (config.registry.ttlSeconds !== void 0) return config.registry.ttlSeconds * 1e3;
1775
1825
  }
1776
1826
  return DEFAULT_REGISTRY_TTL_MS;
@@ -1794,11 +1844,11 @@ var RegistryClient = class {
1794
1844
  }
1795
1845
  const filePath = source.startsWith("file:") ? fileURLToPath(source) : source;
1796
1846
  if (await pathExists(filePath)) {
1797
- const fullPath = resolve8(filePath);
1847
+ const fullPath = resolve9(filePath);
1798
1848
  const stats = await stat6(fullPath);
1799
1849
  return readFile9(stats.isDirectory() ? join15(fullPath, "index.json") : fullPath, "utf8");
1800
1850
  }
1801
- const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join15(dirname7(this.cachePath), "registry-repos") }));
1851
+ const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join15(dirname8(this.cachePath), "registry-repos") }));
1802
1852
  return readFile9(join15(resolved.resolvedPath, "index.json"), "utf8");
1803
1853
  }
1804
1854
  };
@@ -1821,7 +1871,7 @@ function mergeIndexes(indexes) {
1821
1871
  return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
1822
1872
  }
1823
1873
  function defaultRegistryCachePath() {
1824
- return join15(homedir3(), ".agentwheel", "registry-cache.json");
1874
+ return join15(homedir4(), ".agentwheel", "registry-cache.json");
1825
1875
  }
1826
1876
  function sameSources(a, b) {
1827
1877
  return a.length === b.length && a.every((source, index) => source === b[index]);
@@ -1829,7 +1879,7 @@ function sameSources(a, b) {
1829
1879
 
1830
1880
  // src/lifecycle/profile.ts
1831
1881
  async function syncProfile(options) {
1832
- const config = await readWorkspaceConfig(options.workspaceRoot);
1882
+ const config = await readMergedWorkspaceConfig(options.workspaceRoot);
1833
1883
  const profile = config.profiles[options.profile];
1834
1884
  if (!profile) {
1835
1885
  throw new Error(`Unknown profile: ${options.profile}`);
@@ -1841,9 +1891,9 @@ async function syncProfile(options) {
1841
1891
  const results = [];
1842
1892
  for (const pkg of packages) {
1843
1893
  for (const runtime of profile.runtimes) {
1844
- const targetRoot = runtime.targetRoot ?? options.workspaceRoot;
1894
+ const target = resolveProfileRuntime(runtime, config, options.workspaceRoot);
1845
1895
  const adapter = await resolveAdapter({
1846
- adapter: runtime.adapter,
1896
+ adapter: target.adapter,
1847
1897
  adapterConfig: runtime.adapterConfig,
1848
1898
  adapterModule: runtime.adapterModule,
1849
1899
  allowAdapterCode: options.allowAdapterCode,
@@ -1858,8 +1908,8 @@ async function syncProfile(options) {
1858
1908
  mode: options.mode ?? pkg.mode
1859
1909
  });
1860
1910
  try {
1861
- const plan = await createInstallPlan(bundle, adapter, targetRoot, await readInstallManifest(targetRoot, adapter.name));
1862
- results.push({ runtime: adapter.name, packageName: pkg.name, plan });
1911
+ const plan = await createInstallPlan(bundle, adapter, target.targetRoot, await readInstallManifest(target.targetRoot, adapter.name));
1912
+ results.push({ runtime: adapter.name, targetRoot: target.targetRoot, packageName: pkg.name, plan });
1863
1913
  if (!options.dryRun) {
1864
1914
  await applyInstallPlan(plan, bundle.sourceLock, { executePlugins: runtime.executePlugins ?? options.executePlugins });
1865
1915
  }
@@ -1870,6 +1920,17 @@ async function syncProfile(options) {
1870
1920
  }
1871
1921
  return results;
1872
1922
  }
1923
+ function resolveProfileRuntime(runtime, config, workspaceRoot) {
1924
+ if (runtime.agent) {
1925
+ const agent = config.agents[runtime.agent];
1926
+ if (!agent) throw new Error(`Unknown agent in profile: ${runtime.agent}`);
1927
+ return { adapter: agent.adapter, targetRoot: resolveConfigPath(agent.root, workspaceRoot) };
1928
+ }
1929
+ return {
1930
+ adapter: runtime.adapter,
1931
+ targetRoot: runtime.targetRoot ? resolveConfigPath(runtime.targetRoot, workspaceRoot) : workspaceRoot
1932
+ };
1933
+ }
1873
1934
  async function packageFromSource(source, options) {
1874
1935
  const resolved = await resolvePackageSource(source, options.workspaceRoot);
1875
1936
  const driver = options.driver ?? inferSourceDriverName(resolved.source);
@@ -1885,13 +1946,14 @@ async function packageFromSource(source, options) {
1885
1946
  // src/lifecycle/source-plan.ts
1886
1947
  import { join as join17 } from "path";
1887
1948
  async function createSourcePlan(options) {
1888
- const resolvedInput = await resolvePackageSource(options.source, options.targetRoot);
1949
+ const workspaceRoot = options.workspaceRoot ?? options.targetRoot;
1950
+ const resolvedInput = await resolvePackageSource(options.source, workspaceRoot);
1889
1951
  const resolvedSource = resolvedInput.source;
1890
1952
  const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedSource));
1891
1953
  const bundle = await stageSource(driver, resolvedSource, {
1892
- workspaceRoot: options.targetRoot,
1954
+ workspaceRoot,
1893
1955
  adapter: options.adapter,
1894
- cacheRoot: join17(options.targetRoot, ".agentwheel", "cache"),
1956
+ cacheRoot: join17(workspaceRoot, ".agentwheel", "cache"),
1895
1957
  mode: options.mode
1896
1958
  });
1897
1959
  const manifest = await readInstallManifest(options.targetRoot, options.adapter.name);
@@ -1916,9 +1978,97 @@ function shouldUpdatePackage(pkg, lock) {
1916
1978
  return { shouldUpdate: false, reason: "pinned source unchanged" };
1917
1979
  }
1918
1980
 
1981
+ // src/runtime/target.ts
1982
+ import { basename as basename7, dirname as dirname9, join as join18, resolve as resolve10 } from "path";
1983
+ var runtimeMarkers = [
1984
+ { adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
1985
+ { adapter: "claude", dirs: [".claude"] },
1986
+ { adapter: "codex", dirs: [".codex"] },
1987
+ { adapter: "hermes", dirs: [".hermes"] },
1988
+ { adapter: "copilot", dirs: [".github"] }
1989
+ ];
1990
+ async function resolveRuntimeTarget(request = {}) {
1991
+ const cwd = resolve10(request.cwd ?? process.cwd());
1992
+ if (request.targetRoot) {
1993
+ const targetRoot = resolve10(request.targetRoot);
1994
+ return {
1995
+ adapter: request.adapter ?? "openclaw",
1996
+ targetRoot,
1997
+ workspaceRoot: targetRoot,
1998
+ source: "target-root"
1999
+ };
2000
+ }
2001
+ const workspaceRoot = await findWorkspaceRoot(cwd);
2002
+ const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
2003
+ if (request.agent) {
2004
+ return targetFromAgent(request.agent, config, workspaceRoot);
2005
+ }
2006
+ const detected = await detectRuntimeTarget(cwd, request.adapter);
2007
+ if (detected) {
2008
+ return { ...detected, workspaceRoot: await findWorkspaceRoot(detected.targetRoot), source: "auto-detect" };
2009
+ }
2010
+ return {
2011
+ adapter: request.adapter ?? "openclaw",
2012
+ targetRoot: cwd,
2013
+ workspaceRoot,
2014
+ source: "cwd"
2015
+ };
2016
+ }
2017
+ async function resolveAllRuntimeTargets(request = {}) {
2018
+ if (request.targetRoot) return [await resolveRuntimeTarget(request)];
2019
+ if (request.agent) return [await resolveRuntimeTarget(request)];
2020
+ const cwd = resolve10(request.cwd ?? process.cwd());
2021
+ const workspaceRoot = await findWorkspaceRoot(cwd);
2022
+ const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
2023
+ const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot));
2024
+ if (targets.length === 0) {
2025
+ throw new Error("No agents configured. Add agents to .agentwheel/config.json or pass --target-root.");
2026
+ }
2027
+ return targets;
2028
+ }
2029
+ async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
2030
+ const root = resolve10(cwd);
2031
+ const matches = [];
2032
+ for (const marker of runtimeMarkers) {
2033
+ if (adapterFilter && marker.adapter !== adapterFilter) continue;
2034
+ for (const dir of marker.dirs) {
2035
+ if (basename7(root) === dir) {
2036
+ matches.push({ adapter: marker.adapter, targetRoot: dirname9(root) });
2037
+ } else if (await pathExists(join18(root, dir))) {
2038
+ matches.push({ adapter: marker.adapter, targetRoot: root });
2039
+ }
2040
+ }
2041
+ }
2042
+ const unique = dedupeTargets(matches);
2043
+ if (unique.length > 1) {
2044
+ throw new Error(`Multiple runtime directories detected: ${unique.map((item) => `${item.adapter} at ${item.targetRoot}`).join(", ")}. Pass --adapter or --agent.`);
2045
+ }
2046
+ return unique[0];
2047
+ }
2048
+ function targetFromAgent(name, config, workspaceRoot) {
2049
+ const agent = config.agents[name];
2050
+ if (!agent) {
2051
+ throw new Error(`Unknown agent: ${name}`);
2052
+ }
2053
+ return {
2054
+ agentName: name,
2055
+ adapter: agent.adapter,
2056
+ targetRoot: resolveConfigPath(agent.root, workspaceRoot),
2057
+ workspaceRoot,
2058
+ source: "agent"
2059
+ };
2060
+ }
2061
+ function dedupeTargets(matches) {
2062
+ const byKey = /* @__PURE__ */ new Map();
2063
+ for (const match of matches) {
2064
+ byKey.set(`${match.adapter}:${match.targetRoot}`, match);
2065
+ }
2066
+ return [...byKey.values()];
2067
+ }
2068
+
1919
2069
  // src/cli/index.ts
1920
2070
  var program = new Command();
1921
- program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version("0.3.1");
2071
+ program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version("0.4.0");
1922
2072
  program.command("init").argument("[kind]", "workspace or package", "workspace").option("--target-root <path>", "workspace root", process.cwd()).action(async (kind, options) => {
1923
2073
  const root = normalizeTargetRoot(options.targetRoot);
1924
2074
  if (kind === "package") {
@@ -1949,7 +2099,7 @@ program.command("add").argument("<source>", "package source").option("--driver <
1949
2099
  const bundle = await stageSource(driver, resolvedSource, {
1950
2100
  workspaceRoot: targetRoot,
1951
2101
  adapter,
1952
- cacheRoot: join18(targetRoot, ".agentwheel", "cache"),
2102
+ cacheRoot: join19(targetRoot, ".agentwheel", "cache"),
1953
2103
  mode: options.mode
1954
2104
  });
1955
2105
  const name = options.name ?? resolvedInput.registryEntry?.name ?? bundle.source.packageName ?? source;
@@ -1972,7 +2122,7 @@ program.command("list").argument("<source>", "package source").option("--driver
1972
2122
  const targetRoot = normalizeTargetRoot(options.targetRoot);
1973
2123
  const resolvedInput = await resolvePackageSource(source, targetRoot);
1974
2124
  const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
1975
- const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join18(targetRoot, ".agentwheel", "cache") }))));
2125
+ const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join19(targetRoot, ".agentwheel", "cache") }))));
1976
2126
  const artifacts = await driver.list(resolved);
1977
2127
  for (const artifact of artifacts) {
1978
2128
  console.log(`${artifact.type} ${artifact.name} ${artifact.relativePath}`);
@@ -1982,7 +2132,7 @@ program.command("scan").argument("<source>", "package source").option("--driver
1982
2132
  const targetRoot = normalizeTargetRoot(options.targetRoot);
1983
2133
  const resolvedInput = await resolvePackageSource(source, targetRoot);
1984
2134
  const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
1985
- const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join18(targetRoot, ".agentwheel", "cache") }))));
2135
+ const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join19(targetRoot, ".agentwheel", "cache") }))));
1986
2136
  const result = await driver.scan(resolved);
1987
2137
  if (result.findings.length === 0) {
1988
2138
  console.log("Scan ok: no findings");
@@ -1993,17 +2143,20 @@ program.command("scan").argument("<source>", "package source").option("--driver
1993
2143
  }
1994
2144
  if (!result.ok) process.exitCode = 1;
1995
2145
  });
1996
- 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", process.cwd()).option("--mode <mode>", "pinned or tracking").option("--dry-run", "accepted for symmetry; plan never writes", false).action(async (source, options) => {
1997
- const { plan, bundle } = await buildPlan(source, options);
1998
- console.log(formatPlan(plan));
1999
- await rm8(bundle.root, { recursive: true, force: true });
2000
- if (plan.hasBlockingChanges) process.exitCode = 1;
2146
+ 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) => {
2147
+ const targets = await resolveCliTargets(options);
2148
+ for (const target of targets) {
2149
+ const { plan, bundle } = await buildPlan(source, target, options);
2150
+ console.log(formatPlan(plan));
2151
+ await rm8(bundle.root, { recursive: true, force: true });
2152
+ if (plan.hasBlockingChanges) process.exitCode = 1;
2153
+ }
2001
2154
  });
2002
- 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", process.cwd()).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) => {
2155
+ 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) => {
2003
2156
  if (options.profile) {
2004
- const targetRoot = normalizeTargetRoot(options.targetRoot);
2157
+ const target = await resolveRuntimeTarget({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent });
2005
2158
  const results = await syncProfile({
2006
- workspaceRoot: targetRoot,
2159
+ workspaceRoot: target.workspaceRoot,
2007
2160
  profile: options.profile,
2008
2161
  source,
2009
2162
  driver: options.driver,
@@ -2014,66 +2167,37 @@ program.command("sync").argument("[source]", "source directory").option("--drive
2014
2167
  warn: (message) => console.warn(message)
2015
2168
  });
2016
2169
  for (const result of results) {
2017
- console.log(`Profile ${options.profile} / ${result.runtime} / ${result.packageName}:`);
2170
+ console.log(`Profile ${options.profile} / ${result.runtime} / ${result.packageName} at ${result.targetRoot}:`);
2018
2171
  console.log(formatPlan(result.plan));
2019
2172
  if (result.plan.hasBlockingChanges) process.exitCode = 1;
2020
2173
  }
2021
2174
  if (!options.dryRun) console.log("Applied.");
2022
2175
  return;
2023
2176
  }
2177
+ const targets = await resolveCliTargets(options);
2024
2178
  if (!source) {
2025
- throw new Error("sync requires a source unless --profile is used");
2026
- }
2027
- const { plan, bundle } = await buildPlan(source, options);
2028
- console.log(formatPlan(plan));
2029
- if (!options.dryRun) {
2030
- await applyInstallPlan(plan, bundle.sourceLock, { executePlugins: options.executePlugins });
2031
- console.log("Applied.");
2032
- }
2033
- await rm8(bundle.root, { recursive: true, force: true });
2034
- if (plan.hasBlockingChanges) process.exitCode = 1;
2035
- });
2036
- program.command("update").option("--target-root <path>", "workspace root", process.cwd()).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) => {
2037
- const targetRoot = normalizeTargetRoot(options.targetRoot);
2038
- const config = await readWorkspaceConfig(targetRoot);
2039
- if (config.packages.length === 0) {
2040
- console.log("No packages configured.");
2179
+ for (const target of targets) {
2180
+ await runConfiguredPackages(target, options, { useUpdateDecision: false });
2181
+ }
2041
2182
  return;
2042
2183
  }
2043
- for (const pkg of config.packages) {
2044
- const adapter = await resolveAdapter({
2045
- adapter: pkg.adapter,
2046
- adapterConfig: pkg.adapterConfig,
2047
- adapterModule: pkg.adapterModule,
2048
- allowAdapterCode: options.allowAdapterCode,
2049
- baseDir: targetRoot,
2050
- warn: (message) => console.warn(message)
2051
- });
2052
- const lock = await readSourceLock(targetRoot, adapter.name);
2053
- const decision = shouldUpdatePackage(pkg, lock);
2054
- if (!decision.shouldUpdate) {
2055
- console.log(`Skipping ${pkg.name}: ${decision.reason}.`);
2056
- continue;
2057
- }
2058
- const { plan, bundle } = await buildPlan(pkg.source, {
2059
- driver: pkg.driver,
2060
- adapter: pkg.adapter,
2061
- adapterConfig: pkg.adapterConfig,
2062
- adapterModule: pkg.adapterModule,
2063
- allowAdapterCode: options.allowAdapterCode,
2064
- targetRoot,
2065
- mode: pkg.mode
2066
- });
2067
- console.log(`Update ${pkg.name}:`);
2184
+ for (const target of targets) {
2185
+ const { plan, bundle } = await buildPlan(source, target, options);
2068
2186
  console.log(formatPlan(plan));
2069
2187
  if (!options.dryRun) {
2070
2188
  await applyInstallPlan(plan, bundle.sourceLock, { executePlugins: options.executePlugins });
2071
- console.log(`Applied ${pkg.name}.`);
2189
+ console.log(`Applied ${target.adapter} at ${target.targetRoot}.`);
2072
2190
  }
2073
2191
  await rm8(bundle.root, { recursive: true, force: true });
2074
2192
  if (plan.hasBlockingChanges) process.exitCode = 1;
2075
2193
  }
2076
2194
  });
2195
+ 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) => {
2196
+ const targets = await resolveCliTargets(options);
2197
+ for (const target of targets) {
2198
+ await runConfiguredPackages(target, options, { useUpdateDecision: true });
2199
+ }
2200
+ });
2077
2201
  program.command("registry").description("manage optional registry indexes").addCommand(
2078
2202
  new Command("update").description("refresh the local registry cache").option("--target-root <path>", "workspace root", process.cwd()).action(async (options) => {
2079
2203
  const client = new RegistryClient({ workspaceRoot: normalizeTargetRoot(options.targetRoot) });
@@ -2101,54 +2225,96 @@ program.command("eject").argument("<item>", "package/type/name").option("--targe
2101
2225
  const result = await ejectArtifact(targetRoot, item);
2102
2226
  console.log(`Ejected ${item} to ${result.ejectedPath}.`);
2103
2227
  });
2104
- 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", process.cwd()).option("--dry-run", "show removals without writing", false).action(async (options) => {
2105
- const targetRoot = normalizeTargetRoot(options.targetRoot);
2106
- const programmaticAdapter = options.adapterModule ? await resolveAdapter({
2107
- adapter: options.adapter,
2108
- adapterModule: options.adapterModule,
2109
- allowAdapterCode: options.allowAdapterCode,
2110
- baseDir: targetRoot,
2111
- warn: (message) => console.warn(message)
2112
- }) : void 0;
2113
- const adapterName = programmaticAdapter?.name ?? options.adapter;
2114
- const manifest = await readInstallManifest(targetRoot, adapterName);
2115
- if (!manifest) {
2116
- console.log(`No install manifest for ${adapterName} at ${targetRoot}`);
2117
- return;
2118
- }
2119
- const plan = await createUninstallPlan(manifest);
2120
- console.log(formatPlan(plan));
2121
- await uninstall(plan, options.dryRun);
2122
- if (!options.dryRun && programmaticAdapter) {
2123
- await programmaticAdapter.programmatic?.uninstall?.({ targetRoot, adapterName: programmaticAdapter.name });
2228
+ 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).action(async (options) => {
2229
+ const targets = await resolveCliTargets(options);
2230
+ for (const target of targets) {
2231
+ const adapter = await resolveAdapterForTarget(target, options);
2232
+ const manifest = await readInstallManifest(target.targetRoot, adapter.name);
2233
+ if (!manifest) {
2234
+ console.log(`No install manifest for ${adapter.name} at ${target.targetRoot}`);
2235
+ continue;
2236
+ }
2237
+ const plan = await createUninstallPlan(manifest);
2238
+ console.log(formatPlan(plan));
2239
+ await uninstall(plan, options.dryRun);
2240
+ if (!options.dryRun) {
2241
+ await adapter.programmatic?.uninstall?.({ targetRoot: target.targetRoot, adapterName: adapter.name });
2242
+ console.log(`Uninstalled ${adapter.name} at ${target.targetRoot}.`);
2243
+ }
2244
+ if (plan.hasBlockingChanges) process.exitCode = 1;
2124
2245
  }
2125
- if (!options.dryRun) console.log("Uninstalled.");
2126
- if (plan.hasBlockingChanges) process.exitCode = 1;
2127
2246
  });
2128
- async function buildPlan(source, options) {
2129
- const targetRoot = normalizeTargetRoot(options.targetRoot);
2130
- const adapter = await resolveAdapter({
2131
- adapter: options.adapter,
2132
- adapterConfig: options.adapterConfig,
2133
- adapterModule: options.adapterModule,
2134
- allowAdapterCode: options.allowAdapterCode,
2135
- baseDir: targetRoot,
2136
- warn: (message) => console.warn(message)
2137
- });
2247
+ async function buildPlan(source, target, options) {
2248
+ const adapter = await resolveAdapterForTarget(target, options);
2138
2249
  const result = await createSourcePlan({
2139
2250
  source,
2140
- targetRoot,
2251
+ targetRoot: target.targetRoot,
2252
+ workspaceRoot: target.workspaceRoot,
2141
2253
  adapter,
2142
2254
  driver: options.driver,
2143
2255
  mode: options.mode
2144
2256
  });
2145
2257
  return { plan: result.plan, bundle: result.bundle };
2146
2258
  }
2259
+ async function resolveCliTargets(options) {
2260
+ if (options.all) {
2261
+ return resolveAllRuntimeTargets({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent, all: options.all });
2262
+ }
2263
+ return [await resolveRuntimeTarget({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent })];
2264
+ }
2265
+ async function resolveAdapterForTarget(target, options) {
2266
+ return resolveAdapter({
2267
+ adapter: target.adapter,
2268
+ adapterConfig: options.adapterConfig,
2269
+ adapterModule: options.adapterModule,
2270
+ allowAdapterCode: options.allowAdapterCode,
2271
+ baseDir: target.workspaceRoot,
2272
+ warn: (message) => console.warn(message)
2273
+ });
2274
+ }
2275
+ async function runConfiguredPackages(target, options, behavior) {
2276
+ const config = await readMergedWorkspaceConfig(target.workspaceRoot);
2277
+ if (config.packages.length === 0) {
2278
+ console.log(`No packages configured at ${target.workspaceRoot}.`);
2279
+ return;
2280
+ }
2281
+ for (const pkg of config.packages) {
2282
+ const targetForPackage = options.adapter || target.source !== "cwd" ? target : { ...target, adapter: pkg.adapter };
2283
+ const adapter = await resolveAdapterForTarget(targetForPackage, {
2284
+ adapterConfig: options.adapterConfig ?? pkg.adapterConfig,
2285
+ adapterModule: options.adapterModule ?? pkg.adapterModule,
2286
+ allowAdapterCode: options.allowAdapterCode
2287
+ });
2288
+ if (behavior.useUpdateDecision) {
2289
+ const lock = await readSourceLock(targetForPackage.targetRoot, adapter.name);
2290
+ const decision = shouldUpdatePackage(pkg, lock);
2291
+ if (!decision.shouldUpdate) {
2292
+ console.log(`Skipping ${pkg.name}: ${decision.reason}.`);
2293
+ continue;
2294
+ }
2295
+ }
2296
+ const { plan, bundle } = await buildPlan(pkg.source, targetForPackage, {
2297
+ driver: pkg.driver,
2298
+ adapterConfig: options.adapterConfig ?? pkg.adapterConfig,
2299
+ adapterModule: options.adapterModule ?? pkg.adapterModule,
2300
+ allowAdapterCode: options.allowAdapterCode,
2301
+ mode: pkg.mode
2302
+ });
2303
+ console.log(`${behavior.useUpdateDecision ? "Update" : "Sync"} ${pkg.name} (${adapter.name} at ${targetForPackage.targetRoot}):`);
2304
+ console.log(formatPlan(plan));
2305
+ if (!options.dryRun) {
2306
+ await applyInstallPlan(plan, bundle.sourceLock, { executePlugins: options.executePlugins });
2307
+ console.log(`Applied ${pkg.name}.`);
2308
+ }
2309
+ await rm8(bundle.root, { recursive: true, force: true });
2310
+ if (plan.hasBlockingChanges) process.exitCode = 1;
2311
+ }
2312
+ }
2147
2313
  async function initPackage(root) {
2148
- await mkdir7(join18(root, "instructions"), { recursive: true });
2149
- await mkdir7(join18(root, "rules"), { recursive: true });
2150
- await mkdir7(join18(root, "skills"), { recursive: true });
2151
- const manifestPath = join18(root, "agentwheel.json");
2314
+ await mkdir7(join19(root, "instructions"), { recursive: true });
2315
+ await mkdir7(join19(root, "rules"), { recursive: true });
2316
+ await mkdir7(join19(root, "skills"), { recursive: true });
2317
+ const manifestPath = join19(root, "agentwheel.json");
2152
2318
  const manifest = {
2153
2319
  schemaVersion: 1,
2154
2320
  name: "example/agentwheel-package",
@@ -2161,7 +2327,7 @@ async function initPackage(root) {
2161
2327
  };
2162
2328
  await writeFile4(manifestPath, `${JSON.stringify(manifest, null, 2)}
2163
2329
  `, "utf8");
2164
- await writeFile4(join18(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
2330
+ await writeFile4(join19(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
2165
2331
  }
2166
2332
  function printRegistryEntries(entries) {
2167
2333
  for (const entry of entries) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentwheel",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Weave skills, rules, and instructions across every AI agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",