agentwheel 0.3.1 → 0.4.1
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 +55 -6
- package/dist/index.js +345 -127
- 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
|
-
|
|
28
|
-
agentwheel
|
|
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.
|
|
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.
|
|
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
|
-
|
|
55
|
-
agentwheel
|
|
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,51 @@ 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
|
+
`uninstall` removes clean managed files by default and keeps drifted files in place with a warning.
|
|
76
|
+
Use `agentwheel uninstall --force` only when you also want to remove drifted managed files.
|
|
77
|
+
|
|
78
|
+
## Runtime targeting
|
|
79
|
+
|
|
80
|
+
Normal use no longer needs `--target-root`. Run agentwheel inside a runtime folder and it detects
|
|
81
|
+
the target:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
cd ~/.openclaw
|
|
85
|
+
agentwheel sync github:your-org/agent-pack
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
If the current directory is already the runtime directory (`~/.openclaw`), agentwheel uses its
|
|
89
|
+
parent as the root so output lands in `~/.openclaw/skills`, not `~/.openclaw/.openclaw/skills`.
|
|
90
|
+
If the current directory contains a runtime directory (`./.openclaw`), that directory is used as
|
|
91
|
+
the target under the current project.
|
|
92
|
+
|
|
93
|
+
For a control-plane setup, define named agents in config. Global config lives at
|
|
94
|
+
`~/.agentwheel/config.json`; project config lives at `.agentwheel/config.json`; project values win.
|
|
95
|
+
|
|
96
|
+
```jsonc
|
|
97
|
+
{
|
|
98
|
+
"agents": {
|
|
99
|
+
"lab-openclaw": { "adapter": "openclaw", "root": "/Users/me/.openclaw-home" },
|
|
100
|
+
"docs-copilot": { "adapter": "copilot", "root": "/Users/me/projects/docs" }
|
|
101
|
+
},
|
|
102
|
+
"profiles": {
|
|
103
|
+
"daily": [
|
|
104
|
+
{ "agent": "lab-openclaw" },
|
|
105
|
+
{ "agent": "docs-copilot" }
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
agentwheel sync --agent lab-openclaw
|
|
113
|
+
agentwheel sync --all
|
|
114
|
+
agentwheel sync --profile daily
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Target resolution order is exact: `--target-root` wins, then `--agent`, then auto-detect from the
|
|
118
|
+
current directory, then fallback to the current directory.
|
|
119
|
+
|
|
72
120
|
## Core ideas
|
|
73
121
|
|
|
74
122
|
**Three places, one direction:**
|
|
@@ -172,6 +220,7 @@ clear conversion format.
|
|
|
172
220
|
- [x] **v0.1** — install spine: local sources; openclaw/claude/codex adapters; skills/rules/instructions; `plan` · `sync` · `--dry-run` · `uninstall`; manifest + drift + idempotency.
|
|
173
221
|
- [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
222
|
- [x] **v0.3** — skillkit/vercel source drivers; optional registry & federation; programmatic adapters behind `--allow-adapter-code`; rich JSON merge for mcp/hooks/settings; profiles.
|
|
223
|
+
- [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
224
|
|
|
176
225
|
## Design docs
|
|
177
226
|
|
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
|
|
12
|
+
import { join as join19 } from "path";
|
|
13
13
|
import { Command } from "commander";
|
|
14
14
|
|
|
15
15
|
// src/adapters/resolve.ts
|
|
@@ -461,16 +461,52 @@ async function applyInstallPlan(plan, sourceLock, options = {}) {
|
|
|
461
461
|
await writeSourceLock(plan.targetRoot, plan.adapter, sourceLock);
|
|
462
462
|
return manifest;
|
|
463
463
|
}
|
|
464
|
-
async function uninstall(plan,
|
|
464
|
+
async function uninstall(plan, options = {}) {
|
|
465
|
+
const resolvedOptions = typeof options === "boolean" ? { dryRun: options } : options;
|
|
465
466
|
if (plan.hasBlockingChanges) {
|
|
466
|
-
const blockers = plan.operations.filter((operation) => operation.action === "
|
|
467
|
-
throw new Error(`Refusing to uninstall with
|
|
467
|
+
const blockers = plan.operations.filter((operation) => operation.action === "conflict");
|
|
468
|
+
throw new Error(`Refusing to uninstall with blocking changes: ${blockers.map((item) => item.relativeDestPath).join(", ")}`);
|
|
468
469
|
}
|
|
469
|
-
|
|
470
|
+
const removable = plan.operations.filter((operation) => operation.action === "remove" || resolvedOptions.force && operation.action === "keep");
|
|
471
|
+
const kept = resolvedOptions.force ? [] : plan.operations.filter((operation) => operation.action === "keep");
|
|
472
|
+
const removedDrifted = resolvedOptions.force ? plan.operations.filter((operation) => operation.action === "keep").length : 0;
|
|
473
|
+
if (resolvedOptions.dryRun) return { removed: removable.length, kept: kept.length, removedDrifted };
|
|
470
474
|
for (const operation of plan.operations) {
|
|
471
|
-
|
|
475
|
+
if (operation.action === "remove" || resolvedOptions.force && operation.action === "keep") {
|
|
476
|
+
await rm2(operation.destPath, { recursive: true, force: true });
|
|
477
|
+
}
|
|
472
478
|
}
|
|
473
|
-
|
|
479
|
+
if (kept.length > 0) {
|
|
480
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
481
|
+
await writeInstallManifest({
|
|
482
|
+
version: 1,
|
|
483
|
+
adapter: plan.adapter,
|
|
484
|
+
targetRoot: plan.targetRoot,
|
|
485
|
+
generatedAt: now,
|
|
486
|
+
adapterCode: plan.adapterCode,
|
|
487
|
+
entries: kept.map((operation) => {
|
|
488
|
+
if (!operation.manifestHash || !operation.desiredHash) {
|
|
489
|
+
throw new Error(`Invalid kept operation missing manifest/source hash: ${operation.relativeDestPath}`);
|
|
490
|
+
}
|
|
491
|
+
return {
|
|
492
|
+
path: operation.relativeDestPath,
|
|
493
|
+
artifactType: operation.artifactType,
|
|
494
|
+
artifactName: operation.artifactName,
|
|
495
|
+
kind: operation.kind,
|
|
496
|
+
hash: operation.manifestHash,
|
|
497
|
+
sourceHash: operation.desiredHash,
|
|
498
|
+
updatedAt: now,
|
|
499
|
+
channel: operation.channel,
|
|
500
|
+
packageName: operation.packageName,
|
|
501
|
+
semanticCommand: operation.semanticCommand,
|
|
502
|
+
mergeStrategy: operation.mergeStrategy
|
|
503
|
+
};
|
|
504
|
+
}).sort((a, b) => a.path.localeCompare(b.path))
|
|
505
|
+
});
|
|
506
|
+
} else {
|
|
507
|
+
await removeStateFiles(plan.targetRoot, plan.adapter);
|
|
508
|
+
}
|
|
509
|
+
return { removed: removable.length, kept: kept.length, removedDrifted };
|
|
474
510
|
}
|
|
475
511
|
|
|
476
512
|
// src/install/plan.ts
|
|
@@ -658,6 +694,7 @@ function summarizePlan(plan) {
|
|
|
658
694
|
update: 0,
|
|
659
695
|
skip: 0,
|
|
660
696
|
remove: 0,
|
|
697
|
+
keep: 0,
|
|
661
698
|
drift: 0,
|
|
662
699
|
conflict: 0,
|
|
663
700
|
plugin: 0,
|
|
@@ -679,17 +716,20 @@ async function createUninstallPlan(manifest) {
|
|
|
679
716
|
const currentHash = await hashPath(destPath);
|
|
680
717
|
if (currentHash !== entry.hash) {
|
|
681
718
|
operations.push({
|
|
682
|
-
action: "
|
|
719
|
+
action: "keep",
|
|
683
720
|
artifactType: entry.artifactType,
|
|
684
721
|
artifactName: entry.artifactName,
|
|
685
722
|
kind: entry.kind,
|
|
686
723
|
destPath,
|
|
687
724
|
relativeDestPath: entry.path,
|
|
725
|
+
desiredHash: entry.sourceHash,
|
|
688
726
|
currentHash,
|
|
689
727
|
manifestHash: entry.hash,
|
|
690
|
-
reason: "managed destination changed outside agentwheel",
|
|
728
|
+
reason: "managed destination changed outside agentwheel; keeping by default",
|
|
691
729
|
channel: entry.channel,
|
|
692
|
-
packageName: entry.packageName
|
|
730
|
+
packageName: entry.packageName,
|
|
731
|
+
semanticCommand: entry.semanticCommand,
|
|
732
|
+
mergeStrategy: entry.mergeStrategy
|
|
693
733
|
});
|
|
694
734
|
} else {
|
|
695
735
|
operations.push({
|
|
@@ -712,7 +752,8 @@ async function createUninstallPlan(manifest) {
|
|
|
712
752
|
adapter: manifest.adapter,
|
|
713
753
|
targetRoot: manifest.targetRoot,
|
|
714
754
|
operations,
|
|
715
|
-
hasBlockingChanges: operations.some((operation) => operation.action === "
|
|
755
|
+
hasBlockingChanges: operations.some((operation) => operation.action === "conflict"),
|
|
756
|
+
adapterCode: manifest.adapterCode
|
|
716
757
|
};
|
|
717
758
|
}
|
|
718
759
|
|
|
@@ -722,6 +763,7 @@ var labels = {
|
|
|
722
763
|
update: "UPDATE",
|
|
723
764
|
skip: "SKIP",
|
|
724
765
|
remove: "REMOVE",
|
|
766
|
+
keep: "KEEP",
|
|
725
767
|
drift: "DRIFT",
|
|
726
768
|
conflict: "CONFLICT",
|
|
727
769
|
plugin: "PLUGIN",
|
|
@@ -743,7 +785,7 @@ function formatPlan(plan) {
|
|
|
743
785
|
}
|
|
744
786
|
const summary = summarizePlan(plan);
|
|
745
787
|
lines.push(
|
|
746
|
-
`Summary: create ${summary.create}, update ${summary.update}, skip ${summary.skip}, remove ${summary.remove}, drift ${summary.drift}, conflict ${summary.conflict}, plugin ${summary.plugin}`
|
|
788
|
+
`Summary: create ${summary.create}, update ${summary.update}, skip ${summary.skip}, remove ${summary.remove}, keep ${summary.keep}, drift ${summary.drift}, conflict ${summary.conflict}, plugin ${summary.plugin}`
|
|
747
789
|
);
|
|
748
790
|
return lines.join("\n");
|
|
749
791
|
}
|
|
@@ -1584,7 +1626,8 @@ async function stageSource(driver, source, options = {}) {
|
|
|
1584
1626
|
|
|
1585
1627
|
// src/model/workspace.ts
|
|
1586
1628
|
import { readFile as readFile8 } from "fs/promises";
|
|
1587
|
-
import {
|
|
1629
|
+
import { homedir as homedir3 } from "os";
|
|
1630
|
+
import { dirname as dirname6, join as join13, resolve as resolve8 } from "path";
|
|
1588
1631
|
import { z as z5 } from "zod";
|
|
1589
1632
|
var workspacePackageSchema = z5.object({
|
|
1590
1633
|
name: z5.string().min(1),
|
|
@@ -1598,6 +1641,7 @@ var workspacePackageSchema = z5.object({
|
|
|
1598
1641
|
requestedRef: z5.string().min(1).optional()
|
|
1599
1642
|
});
|
|
1600
1643
|
var workspaceProfileRuntimeSchema = z5.object({
|
|
1644
|
+
agent: z5.string().min(1).optional(),
|
|
1601
1645
|
adapter: z5.string().min(1).default("openclaw"),
|
|
1602
1646
|
adapterConfig: z5.string().min(1).optional(),
|
|
1603
1647
|
adapterModule: z5.string().min(1).optional(),
|
|
@@ -1611,18 +1655,23 @@ var workspaceRegistrySchema = z5.object({
|
|
|
1611
1655
|
sources: z5.array(z5.string().min(1)).optional(),
|
|
1612
1656
|
ttlSeconds: z5.number().int().positive().optional()
|
|
1613
1657
|
}).default({});
|
|
1658
|
+
var workspaceAgentSchema = z5.object({
|
|
1659
|
+
adapter: z5.string().min(1),
|
|
1660
|
+
root: z5.string().min(1)
|
|
1661
|
+
});
|
|
1614
1662
|
var workspaceConfigSchema = z5.object({
|
|
1615
1663
|
schemaVersion: z5.literal(1),
|
|
1616
1664
|
packages: z5.array(workspacePackageSchema).default([]),
|
|
1617
1665
|
registry: workspaceRegistrySchema,
|
|
1618
|
-
profiles: z5.record(z5.string(), workspaceProfileSchema).default({})
|
|
1666
|
+
profiles: z5.record(z5.string(), workspaceProfileSchema).default({}),
|
|
1667
|
+
agents: z5.record(z5.string(), workspaceAgentSchema).default({})
|
|
1619
1668
|
});
|
|
1620
1669
|
function workspaceConfigPath(workspaceRoot) {
|
|
1621
1670
|
return join13(workspaceRoot, ".agentwheel", "config.json");
|
|
1622
1671
|
}
|
|
1623
1672
|
async function readWorkspaceConfig(workspaceRoot) {
|
|
1624
1673
|
const path = workspaceConfigPath(workspaceRoot);
|
|
1625
|
-
if (!await pathExists(path)) return
|
|
1674
|
+
if (!await pathExists(path)) return emptyWorkspaceConfig();
|
|
1626
1675
|
return workspaceConfigSchema.parse(JSON.parse(await readFile8(path, "utf8")));
|
|
1627
1676
|
}
|
|
1628
1677
|
async function writeWorkspaceConfig(workspaceRoot, config) {
|
|
@@ -1632,15 +1681,58 @@ function upsertPackage(config, entry) {
|
|
|
1632
1681
|
const packages = config.packages.filter((candidate) => candidate.name !== entry.name);
|
|
1633
1682
|
packages.push(entry);
|
|
1634
1683
|
packages.sort((a, b) => a.name.localeCompare(b.name));
|
|
1635
|
-
return { schemaVersion: 1, packages, registry: config.registry ?? {}, profiles: config.profiles ?? {} };
|
|
1684
|
+
return { schemaVersion: 1, packages, registry: config.registry ?? {}, profiles: config.profiles ?? {}, agents: config.agents ?? {} };
|
|
1685
|
+
}
|
|
1686
|
+
function globalWorkspaceConfigPath(globalRoot = homedir3()) {
|
|
1687
|
+
return join13(globalRoot, ".agentwheel", "config.json");
|
|
1688
|
+
}
|
|
1689
|
+
async function findWorkspaceRoot(start = process.cwd()) {
|
|
1690
|
+
let current = resolve8(start);
|
|
1691
|
+
while (true) {
|
|
1692
|
+
if (await pathExists(workspaceConfigPath(current))) return current;
|
|
1693
|
+
const parent = dirname6(current);
|
|
1694
|
+
if (parent === current) return resolve8(start);
|
|
1695
|
+
current = parent;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
async function readMergedWorkspaceConfig(projectRoot, options = {}) {
|
|
1699
|
+
const global = await readConfigPath(globalWorkspaceConfigPath(options.globalRoot));
|
|
1700
|
+
const project = await readWorkspaceConfig(projectRoot);
|
|
1701
|
+
return mergeWorkspaceConfig(global, project);
|
|
1702
|
+
}
|
|
1703
|
+
function mergeWorkspaceConfig(global, project) {
|
|
1704
|
+
return workspaceConfigSchema.parse({
|
|
1705
|
+
schemaVersion: 1,
|
|
1706
|
+
packages: project.packages.length > 0 ? project.packages : global.packages,
|
|
1707
|
+
registry: {
|
|
1708
|
+
...global.registry,
|
|
1709
|
+
...project.registry,
|
|
1710
|
+
sources: project.registry.sources ?? global.registry.sources,
|
|
1711
|
+
ttlSeconds: project.registry.ttlSeconds ?? global.registry.ttlSeconds
|
|
1712
|
+
},
|
|
1713
|
+
profiles: { ...global.profiles, ...project.profiles },
|
|
1714
|
+
agents: { ...global.agents, ...project.agents }
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
function resolveConfigPath(path, baseRoot) {
|
|
1718
|
+
if (path.startsWith("~/")) return resolve8(homedir3(), path.slice(2));
|
|
1719
|
+
if (path === "~") return homedir3();
|
|
1720
|
+
return path.startsWith("/") ? resolve8(path) : resolve8(baseRoot, path);
|
|
1721
|
+
}
|
|
1722
|
+
function emptyWorkspaceConfig() {
|
|
1723
|
+
return { schemaVersion: 1, packages: [], registry: {}, profiles: {}, agents: {} };
|
|
1724
|
+
}
|
|
1725
|
+
async function readConfigPath(path) {
|
|
1726
|
+
if (!await pathExists(path)) return emptyWorkspaceConfig();
|
|
1727
|
+
return workspaceConfigSchema.parse(JSON.parse(await readFile8(path, "utf8")));
|
|
1636
1728
|
}
|
|
1637
1729
|
|
|
1638
1730
|
// src/lifecycle/customization.ts
|
|
1639
1731
|
import { appendFile, cp as cp4, mkdir as mkdir6, rm as rm5 } from "fs/promises";
|
|
1640
|
-
import { dirname as
|
|
1732
|
+
import { dirname as dirname7, join as join14 } from "path";
|
|
1641
1733
|
async function remember(workspaceRoot, runtime, text) {
|
|
1642
1734
|
const overlayPath = join14(workspaceRoot, ".agentwheel", "overlays", runtime, "instructions.local.md");
|
|
1643
|
-
await mkdir6(
|
|
1735
|
+
await mkdir6(dirname7(overlayPath), { recursive: true });
|
|
1644
1736
|
await appendFile(overlayPath, `${text.trim()}
|
|
1645
1737
|
`, "utf8");
|
|
1646
1738
|
return { overlayPath };
|
|
@@ -1665,7 +1757,7 @@ async function ejectArtifact(workspaceRoot, item) {
|
|
|
1665
1757
|
throw new Error(`Artifact not found: ${item}`);
|
|
1666
1758
|
}
|
|
1667
1759
|
const ejectedPath = join14(workspaceRoot, ".agentwheel", "ejected", ...parsed.packageName.split("/"), parsed.type, parsed.name);
|
|
1668
|
-
await mkdir6(
|
|
1760
|
+
await mkdir6(dirname7(ejectedPath), { recursive: true });
|
|
1669
1761
|
await rm5(ejectedPath, { recursive: true, force: true });
|
|
1670
1762
|
await cp4(artifact.stagedPath ?? artifact.sourcePath, ejectedPath, { recursive: artifact.kind === "dir", dereference: true });
|
|
1671
1763
|
return { ...parsed, ejectedPath };
|
|
@@ -1690,8 +1782,8 @@ import { join as join16 } from "path";
|
|
|
1690
1782
|
|
|
1691
1783
|
// src/registry/client.ts
|
|
1692
1784
|
import { readFile as readFile9, rm as rm6, stat as stat6 } from "fs/promises";
|
|
1693
|
-
import { homedir as
|
|
1694
|
-
import { dirname as
|
|
1785
|
+
import { homedir as homedir4 } from "os";
|
|
1786
|
+
import { dirname as dirname8, join as join15, resolve as resolve9 } from "path";
|
|
1695
1787
|
import { fileURLToPath } from "url";
|
|
1696
1788
|
|
|
1697
1789
|
// src/model/registry.ts
|
|
@@ -1762,7 +1854,7 @@ var RegistryClient = class {
|
|
|
1762
1854
|
return process.env.AGENTWHEEL_REGISTRY.split(",").map((source) => source.trim()).filter(Boolean);
|
|
1763
1855
|
}
|
|
1764
1856
|
if (this.options.workspaceRoot) {
|
|
1765
|
-
const config = await
|
|
1857
|
+
const config = await readMergedWorkspaceConfig(this.options.workspaceRoot);
|
|
1766
1858
|
if (config.registry.sources?.length) return config.registry.sources;
|
|
1767
1859
|
}
|
|
1768
1860
|
return [DEFAULT_REGISTRY_SOURCE];
|
|
@@ -1770,7 +1862,7 @@ var RegistryClient = class {
|
|
|
1770
1862
|
async getTtlMs() {
|
|
1771
1863
|
if (this.options.ttlMs !== void 0) return this.options.ttlMs;
|
|
1772
1864
|
if (this.options.workspaceRoot) {
|
|
1773
|
-
const config = await
|
|
1865
|
+
const config = await readMergedWorkspaceConfig(this.options.workspaceRoot);
|
|
1774
1866
|
if (config.registry.ttlSeconds !== void 0) return config.registry.ttlSeconds * 1e3;
|
|
1775
1867
|
}
|
|
1776
1868
|
return DEFAULT_REGISTRY_TTL_MS;
|
|
@@ -1794,11 +1886,11 @@ var RegistryClient = class {
|
|
|
1794
1886
|
}
|
|
1795
1887
|
const filePath = source.startsWith("file:") ? fileURLToPath(source) : source;
|
|
1796
1888
|
if (await pathExists(filePath)) {
|
|
1797
|
-
const fullPath =
|
|
1889
|
+
const fullPath = resolve9(filePath);
|
|
1798
1890
|
const stats = await stat6(fullPath);
|
|
1799
1891
|
return readFile9(stats.isDirectory() ? join15(fullPath, "index.json") : fullPath, "utf8");
|
|
1800
1892
|
}
|
|
1801
|
-
const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join15(
|
|
1893
|
+
const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join15(dirname8(this.cachePath), "registry-repos") }));
|
|
1802
1894
|
return readFile9(join15(resolved.resolvedPath, "index.json"), "utf8");
|
|
1803
1895
|
}
|
|
1804
1896
|
};
|
|
@@ -1821,7 +1913,7 @@ function mergeIndexes(indexes) {
|
|
|
1821
1913
|
return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
1822
1914
|
}
|
|
1823
1915
|
function defaultRegistryCachePath() {
|
|
1824
|
-
return join15(
|
|
1916
|
+
return join15(homedir4(), ".agentwheel", "registry-cache.json");
|
|
1825
1917
|
}
|
|
1826
1918
|
function sameSources(a, b) {
|
|
1827
1919
|
return a.length === b.length && a.every((source, index) => source === b[index]);
|
|
@@ -1829,7 +1921,7 @@ function sameSources(a, b) {
|
|
|
1829
1921
|
|
|
1830
1922
|
// src/lifecycle/profile.ts
|
|
1831
1923
|
async function syncProfile(options) {
|
|
1832
|
-
const config = await
|
|
1924
|
+
const config = await readMergedWorkspaceConfig(options.workspaceRoot);
|
|
1833
1925
|
const profile = config.profiles[options.profile];
|
|
1834
1926
|
if (!profile) {
|
|
1835
1927
|
throw new Error(`Unknown profile: ${options.profile}`);
|
|
@@ -1841,9 +1933,9 @@ async function syncProfile(options) {
|
|
|
1841
1933
|
const results = [];
|
|
1842
1934
|
for (const pkg of packages) {
|
|
1843
1935
|
for (const runtime of profile.runtimes) {
|
|
1844
|
-
const
|
|
1936
|
+
const target = resolveProfileRuntime(runtime, config, options.workspaceRoot);
|
|
1845
1937
|
const adapter = await resolveAdapter({
|
|
1846
|
-
adapter:
|
|
1938
|
+
adapter: target.adapter,
|
|
1847
1939
|
adapterConfig: runtime.adapterConfig,
|
|
1848
1940
|
adapterModule: runtime.adapterModule,
|
|
1849
1941
|
allowAdapterCode: options.allowAdapterCode,
|
|
@@ -1858,8 +1950,8 @@ async function syncProfile(options) {
|
|
|
1858
1950
|
mode: options.mode ?? pkg.mode
|
|
1859
1951
|
});
|
|
1860
1952
|
try {
|
|
1861
|
-
const plan = await createInstallPlan(bundle, adapter, targetRoot, await readInstallManifest(targetRoot, adapter.name));
|
|
1862
|
-
results.push({ runtime: adapter.name, packageName: pkg.name, plan });
|
|
1953
|
+
const plan = await createInstallPlan(bundle, adapter, target.targetRoot, await readInstallManifest(target.targetRoot, adapter.name));
|
|
1954
|
+
results.push({ runtime: adapter.name, targetRoot: target.targetRoot, packageName: pkg.name, plan });
|
|
1863
1955
|
if (!options.dryRun) {
|
|
1864
1956
|
await applyInstallPlan(plan, bundle.sourceLock, { executePlugins: runtime.executePlugins ?? options.executePlugins });
|
|
1865
1957
|
}
|
|
@@ -1870,6 +1962,17 @@ async function syncProfile(options) {
|
|
|
1870
1962
|
}
|
|
1871
1963
|
return results;
|
|
1872
1964
|
}
|
|
1965
|
+
function resolveProfileRuntime(runtime, config, workspaceRoot) {
|
|
1966
|
+
if (runtime.agent) {
|
|
1967
|
+
const agent = config.agents[runtime.agent];
|
|
1968
|
+
if (!agent) throw new Error(`Unknown agent in profile: ${runtime.agent}`);
|
|
1969
|
+
return { adapter: agent.adapter, targetRoot: resolveConfigPath(agent.root, workspaceRoot) };
|
|
1970
|
+
}
|
|
1971
|
+
return {
|
|
1972
|
+
adapter: runtime.adapter,
|
|
1973
|
+
targetRoot: runtime.targetRoot ? resolveConfigPath(runtime.targetRoot, workspaceRoot) : workspaceRoot
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1873
1976
|
async function packageFromSource(source, options) {
|
|
1874
1977
|
const resolved = await resolvePackageSource(source, options.workspaceRoot);
|
|
1875
1978
|
const driver = options.driver ?? inferSourceDriverName(resolved.source);
|
|
@@ -1885,13 +1988,14 @@ async function packageFromSource(source, options) {
|
|
|
1885
1988
|
// src/lifecycle/source-plan.ts
|
|
1886
1989
|
import { join as join17 } from "path";
|
|
1887
1990
|
async function createSourcePlan(options) {
|
|
1888
|
-
const
|
|
1991
|
+
const workspaceRoot = options.workspaceRoot ?? options.targetRoot;
|
|
1992
|
+
const resolvedInput = await resolvePackageSource(options.source, workspaceRoot);
|
|
1889
1993
|
const resolvedSource = resolvedInput.source;
|
|
1890
1994
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedSource));
|
|
1891
1995
|
const bundle = await stageSource(driver, resolvedSource, {
|
|
1892
|
-
workspaceRoot
|
|
1996
|
+
workspaceRoot,
|
|
1893
1997
|
adapter: options.adapter,
|
|
1894
|
-
cacheRoot: join17(
|
|
1998
|
+
cacheRoot: join17(workspaceRoot, ".agentwheel", "cache"),
|
|
1895
1999
|
mode: options.mode
|
|
1896
2000
|
});
|
|
1897
2001
|
const manifest = await readInstallManifest(options.targetRoot, options.adapter.name);
|
|
@@ -1916,9 +2020,97 @@ function shouldUpdatePackage(pkg, lock) {
|
|
|
1916
2020
|
return { shouldUpdate: false, reason: "pinned source unchanged" };
|
|
1917
2021
|
}
|
|
1918
2022
|
|
|
2023
|
+
// src/runtime/target.ts
|
|
2024
|
+
import { basename as basename7, dirname as dirname9, join as join18, resolve as resolve10 } from "path";
|
|
2025
|
+
var runtimeMarkers = [
|
|
2026
|
+
{ adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
|
|
2027
|
+
{ adapter: "claude", dirs: [".claude"] },
|
|
2028
|
+
{ adapter: "codex", dirs: [".codex"] },
|
|
2029
|
+
{ adapter: "hermes", dirs: [".hermes"] },
|
|
2030
|
+
{ adapter: "copilot", dirs: [".github"] }
|
|
2031
|
+
];
|
|
2032
|
+
async function resolveRuntimeTarget(request = {}) {
|
|
2033
|
+
const cwd = resolve10(request.cwd ?? process.cwd());
|
|
2034
|
+
if (request.targetRoot) {
|
|
2035
|
+
const targetRoot = resolve10(request.targetRoot);
|
|
2036
|
+
return {
|
|
2037
|
+
adapter: request.adapter ?? "openclaw",
|
|
2038
|
+
targetRoot,
|
|
2039
|
+
workspaceRoot: targetRoot,
|
|
2040
|
+
source: "target-root"
|
|
2041
|
+
};
|
|
2042
|
+
}
|
|
2043
|
+
const workspaceRoot = await findWorkspaceRoot(cwd);
|
|
2044
|
+
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
2045
|
+
if (request.agent) {
|
|
2046
|
+
return targetFromAgent(request.agent, config, workspaceRoot);
|
|
2047
|
+
}
|
|
2048
|
+
const detected = await detectRuntimeTarget(cwd, request.adapter);
|
|
2049
|
+
if (detected) {
|
|
2050
|
+
return { ...detected, workspaceRoot: await findWorkspaceRoot(detected.targetRoot), source: "auto-detect" };
|
|
2051
|
+
}
|
|
2052
|
+
return {
|
|
2053
|
+
adapter: request.adapter ?? "openclaw",
|
|
2054
|
+
targetRoot: cwd,
|
|
2055
|
+
workspaceRoot,
|
|
2056
|
+
source: "cwd"
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
async function resolveAllRuntimeTargets(request = {}) {
|
|
2060
|
+
if (request.targetRoot) return [await resolveRuntimeTarget(request)];
|
|
2061
|
+
if (request.agent) return [await resolveRuntimeTarget(request)];
|
|
2062
|
+
const cwd = resolve10(request.cwd ?? process.cwd());
|
|
2063
|
+
const workspaceRoot = await findWorkspaceRoot(cwd);
|
|
2064
|
+
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
2065
|
+
const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot));
|
|
2066
|
+
if (targets.length === 0) {
|
|
2067
|
+
throw new Error("No agents configured. Add agents to .agentwheel/config.json or pass --target-root.");
|
|
2068
|
+
}
|
|
2069
|
+
return targets;
|
|
2070
|
+
}
|
|
2071
|
+
async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
|
|
2072
|
+
const root = resolve10(cwd);
|
|
2073
|
+
const matches = [];
|
|
2074
|
+
for (const marker of runtimeMarkers) {
|
|
2075
|
+
if (adapterFilter && marker.adapter !== adapterFilter) continue;
|
|
2076
|
+
for (const dir of marker.dirs) {
|
|
2077
|
+
if (basename7(root) === dir) {
|
|
2078
|
+
matches.push({ adapter: marker.adapter, targetRoot: dirname9(root) });
|
|
2079
|
+
} else if (await pathExists(join18(root, dir))) {
|
|
2080
|
+
matches.push({ adapter: marker.adapter, targetRoot: root });
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
const unique = dedupeTargets(matches);
|
|
2085
|
+
if (unique.length > 1) {
|
|
2086
|
+
throw new Error(`Multiple runtime directories detected: ${unique.map((item) => `${item.adapter} at ${item.targetRoot}`).join(", ")}. Pass --adapter or --agent.`);
|
|
2087
|
+
}
|
|
2088
|
+
return unique[0];
|
|
2089
|
+
}
|
|
2090
|
+
function targetFromAgent(name, config, workspaceRoot) {
|
|
2091
|
+
const agent = config.agents[name];
|
|
2092
|
+
if (!agent) {
|
|
2093
|
+
throw new Error(`Unknown agent: ${name}`);
|
|
2094
|
+
}
|
|
2095
|
+
return {
|
|
2096
|
+
agentName: name,
|
|
2097
|
+
adapter: agent.adapter,
|
|
2098
|
+
targetRoot: resolveConfigPath(agent.root, workspaceRoot),
|
|
2099
|
+
workspaceRoot,
|
|
2100
|
+
source: "agent"
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
function dedupeTargets(matches) {
|
|
2104
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2105
|
+
for (const match of matches) {
|
|
2106
|
+
byKey.set(`${match.adapter}:${match.targetRoot}`, match);
|
|
2107
|
+
}
|
|
2108
|
+
return [...byKey.values()];
|
|
2109
|
+
}
|
|
2110
|
+
|
|
1919
2111
|
// src/cli/index.ts
|
|
1920
2112
|
var program = new Command();
|
|
1921
|
-
program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version("0.
|
|
2113
|
+
program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version("0.4.1");
|
|
1922
2114
|
program.command("init").argument("[kind]", "workspace or package", "workspace").option("--target-root <path>", "workspace root", process.cwd()).action(async (kind, options) => {
|
|
1923
2115
|
const root = normalizeTargetRoot(options.targetRoot);
|
|
1924
2116
|
if (kind === "package") {
|
|
@@ -1949,7 +2141,7 @@ program.command("add").argument("<source>", "package source").option("--driver <
|
|
|
1949
2141
|
const bundle = await stageSource(driver, resolvedSource, {
|
|
1950
2142
|
workspaceRoot: targetRoot,
|
|
1951
2143
|
adapter,
|
|
1952
|
-
cacheRoot:
|
|
2144
|
+
cacheRoot: join19(targetRoot, ".agentwheel", "cache"),
|
|
1953
2145
|
mode: options.mode
|
|
1954
2146
|
});
|
|
1955
2147
|
const name = options.name ?? resolvedInput.registryEntry?.name ?? bundle.source.packageName ?? source;
|
|
@@ -1972,7 +2164,7 @@ program.command("list").argument("<source>", "package source").option("--driver
|
|
|
1972
2164
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
1973
2165
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
1974
2166
|
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:
|
|
2167
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join19(targetRoot, ".agentwheel", "cache") }))));
|
|
1976
2168
|
const artifacts = await driver.list(resolved);
|
|
1977
2169
|
for (const artifact of artifacts) {
|
|
1978
2170
|
console.log(`${artifact.type} ${artifact.name} ${artifact.relativePath}`);
|
|
@@ -1982,7 +2174,7 @@ program.command("scan").argument("<source>", "package source").option("--driver
|
|
|
1982
2174
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
1983
2175
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
1984
2176
|
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:
|
|
2177
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join19(targetRoot, ".agentwheel", "cache") }))));
|
|
1986
2178
|
const result = await driver.scan(resolved);
|
|
1987
2179
|
if (result.findings.length === 0) {
|
|
1988
2180
|
console.log("Scan ok: no findings");
|
|
@@ -1993,17 +2185,20 @@ program.command("scan").argument("<source>", "package source").option("--driver
|
|
|
1993
2185
|
}
|
|
1994
2186
|
if (!result.ok) process.exitCode = 1;
|
|
1995
2187
|
});
|
|
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",
|
|
1997
|
-
const
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2188
|
+
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) => {
|
|
2189
|
+
const targets = await resolveCliTargets(options);
|
|
2190
|
+
for (const target of targets) {
|
|
2191
|
+
const { plan, bundle } = await buildPlan(source, target, options);
|
|
2192
|
+
console.log(formatPlan(plan));
|
|
2193
|
+
await rm8(bundle.root, { recursive: true, force: true });
|
|
2194
|
+
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
2195
|
+
}
|
|
2001
2196
|
});
|
|
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",
|
|
2197
|
+
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
2198
|
if (options.profile) {
|
|
2004
|
-
const
|
|
2199
|
+
const target = await resolveRuntimeTarget({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent });
|
|
2005
2200
|
const results = await syncProfile({
|
|
2006
|
-
workspaceRoot:
|
|
2201
|
+
workspaceRoot: target.workspaceRoot,
|
|
2007
2202
|
profile: options.profile,
|
|
2008
2203
|
source,
|
|
2009
2204
|
driver: options.driver,
|
|
@@ -2014,66 +2209,37 @@ program.command("sync").argument("[source]", "source directory").option("--drive
|
|
|
2014
2209
|
warn: (message) => console.warn(message)
|
|
2015
2210
|
});
|
|
2016
2211
|
for (const result of results) {
|
|
2017
|
-
console.log(`Profile ${options.profile} / ${result.runtime} / ${result.packageName}:`);
|
|
2212
|
+
console.log(`Profile ${options.profile} / ${result.runtime} / ${result.packageName} at ${result.targetRoot}:`);
|
|
2018
2213
|
console.log(formatPlan(result.plan));
|
|
2019
2214
|
if (result.plan.hasBlockingChanges) process.exitCode = 1;
|
|
2020
2215
|
}
|
|
2021
2216
|
if (!options.dryRun) console.log("Applied.");
|
|
2022
2217
|
return;
|
|
2023
2218
|
}
|
|
2219
|
+
const targets = await resolveCliTargets(options);
|
|
2024
2220
|
if (!source) {
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
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.");
|
|
2221
|
+
for (const target of targets) {
|
|
2222
|
+
await runConfiguredPackages(target, options, { useUpdateDecision: false });
|
|
2223
|
+
}
|
|
2041
2224
|
return;
|
|
2042
2225
|
}
|
|
2043
|
-
for (const
|
|
2044
|
-
const
|
|
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}:`);
|
|
2226
|
+
for (const target of targets) {
|
|
2227
|
+
const { plan, bundle } = await buildPlan(source, target, options);
|
|
2068
2228
|
console.log(formatPlan(plan));
|
|
2069
2229
|
if (!options.dryRun) {
|
|
2070
2230
|
await applyInstallPlan(plan, bundle.sourceLock, { executePlugins: options.executePlugins });
|
|
2071
|
-
console.log(`Applied ${
|
|
2231
|
+
console.log(`Applied ${target.adapter} at ${target.targetRoot}.`);
|
|
2072
2232
|
}
|
|
2073
2233
|
await rm8(bundle.root, { recursive: true, force: true });
|
|
2074
2234
|
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
2075
2235
|
}
|
|
2076
2236
|
});
|
|
2237
|
+
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) => {
|
|
2238
|
+
const targets = await resolveCliTargets(options);
|
|
2239
|
+
for (const target of targets) {
|
|
2240
|
+
await runConfiguredPackages(target, options, { useUpdateDecision: true });
|
|
2241
|
+
}
|
|
2242
|
+
});
|
|
2077
2243
|
program.command("registry").description("manage optional registry indexes").addCommand(
|
|
2078
2244
|
new Command("update").description("refresh the local registry cache").option("--target-root <path>", "workspace root", process.cwd()).action(async (options) => {
|
|
2079
2245
|
const client = new RegistryClient({ workspaceRoot: normalizeTargetRoot(options.targetRoot) });
|
|
@@ -2101,54 +2267,96 @@ program.command("eject").argument("<item>", "package/type/name").option("--targe
|
|
|
2101
2267
|
const result = await ejectArtifact(targetRoot, item);
|
|
2102
2268
|
console.log(`Ejected ${item} to ${result.ejectedPath}.`);
|
|
2103
2269
|
});
|
|
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",
|
|
2105
|
-
const
|
|
2106
|
-
const
|
|
2107
|
-
adapter
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
await uninstall(plan, options.dryRun);
|
|
2122
|
-
if (!options.dryRun && programmaticAdapter) {
|
|
2123
|
-
await programmaticAdapter.programmatic?.uninstall?.({ targetRoot, adapterName: programmaticAdapter.name });
|
|
2270
|
+
program.command("uninstall").option("--adapter <adapter>", "adapter", "openclaw").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show removals without writing", false).option("--force", "remove drifted managed files too", false).action(async (options) => {
|
|
2271
|
+
const targets = await resolveCliTargets(options);
|
|
2272
|
+
for (const target of targets) {
|
|
2273
|
+
const adapter = await resolveAdapterForTarget(target, options);
|
|
2274
|
+
const manifest = await readInstallManifest(target.targetRoot, adapter.name);
|
|
2275
|
+
if (!manifest) {
|
|
2276
|
+
console.log(`No install manifest for ${adapter.name} at ${target.targetRoot}`);
|
|
2277
|
+
continue;
|
|
2278
|
+
}
|
|
2279
|
+
const plan = await createUninstallPlan(manifest);
|
|
2280
|
+
console.log(formatPlan(plan));
|
|
2281
|
+
const result = await uninstall(plan, { dryRun: options.dryRun, force: options.force });
|
|
2282
|
+
if (!options.dryRun) {
|
|
2283
|
+
await adapter.programmatic?.uninstall?.({ targetRoot: target.targetRoot, adapterName: adapter.name });
|
|
2284
|
+
console.log(formatUninstallResult(result));
|
|
2285
|
+
}
|
|
2286
|
+
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
2124
2287
|
}
|
|
2125
|
-
if (!options.dryRun) console.log("Uninstalled.");
|
|
2126
|
-
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
2127
2288
|
});
|
|
2128
|
-
async function buildPlan(source, options) {
|
|
2129
|
-
const
|
|
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
|
-
});
|
|
2289
|
+
async function buildPlan(source, target, options) {
|
|
2290
|
+
const adapter = await resolveAdapterForTarget(target, options);
|
|
2138
2291
|
const result = await createSourcePlan({
|
|
2139
2292
|
source,
|
|
2140
|
-
targetRoot,
|
|
2293
|
+
targetRoot: target.targetRoot,
|
|
2294
|
+
workspaceRoot: target.workspaceRoot,
|
|
2141
2295
|
adapter,
|
|
2142
2296
|
driver: options.driver,
|
|
2143
2297
|
mode: options.mode
|
|
2144
2298
|
});
|
|
2145
2299
|
return { plan: result.plan, bundle: result.bundle };
|
|
2146
2300
|
}
|
|
2301
|
+
async function resolveCliTargets(options) {
|
|
2302
|
+
if (options.all) {
|
|
2303
|
+
return resolveAllRuntimeTargets({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent, all: options.all });
|
|
2304
|
+
}
|
|
2305
|
+
return [await resolveRuntimeTarget({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent })];
|
|
2306
|
+
}
|
|
2307
|
+
async function resolveAdapterForTarget(target, options) {
|
|
2308
|
+
return resolveAdapter({
|
|
2309
|
+
adapter: target.adapter,
|
|
2310
|
+
adapterConfig: options.adapterConfig,
|
|
2311
|
+
adapterModule: options.adapterModule,
|
|
2312
|
+
allowAdapterCode: options.allowAdapterCode,
|
|
2313
|
+
baseDir: target.workspaceRoot,
|
|
2314
|
+
warn: (message) => console.warn(message)
|
|
2315
|
+
});
|
|
2316
|
+
}
|
|
2317
|
+
async function runConfiguredPackages(target, options, behavior) {
|
|
2318
|
+
const config = await readMergedWorkspaceConfig(target.workspaceRoot);
|
|
2319
|
+
if (config.packages.length === 0) {
|
|
2320
|
+
console.log(`No packages configured at ${target.workspaceRoot}.`);
|
|
2321
|
+
return;
|
|
2322
|
+
}
|
|
2323
|
+
for (const pkg of config.packages) {
|
|
2324
|
+
const targetForPackage = options.adapter || target.source !== "cwd" ? target : { ...target, adapter: pkg.adapter };
|
|
2325
|
+
const adapter = await resolveAdapterForTarget(targetForPackage, {
|
|
2326
|
+
adapterConfig: options.adapterConfig ?? pkg.adapterConfig,
|
|
2327
|
+
adapterModule: options.adapterModule ?? pkg.adapterModule,
|
|
2328
|
+
allowAdapterCode: options.allowAdapterCode
|
|
2329
|
+
});
|
|
2330
|
+
if (behavior.useUpdateDecision) {
|
|
2331
|
+
const lock = await readSourceLock(targetForPackage.targetRoot, adapter.name);
|
|
2332
|
+
const decision = shouldUpdatePackage(pkg, lock);
|
|
2333
|
+
if (!decision.shouldUpdate) {
|
|
2334
|
+
console.log(`Skipping ${pkg.name}: ${decision.reason}.`);
|
|
2335
|
+
continue;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
const { plan, bundle } = await buildPlan(pkg.source, targetForPackage, {
|
|
2339
|
+
driver: pkg.driver,
|
|
2340
|
+
adapterConfig: options.adapterConfig ?? pkg.adapterConfig,
|
|
2341
|
+
adapterModule: options.adapterModule ?? pkg.adapterModule,
|
|
2342
|
+
allowAdapterCode: options.allowAdapterCode,
|
|
2343
|
+
mode: pkg.mode
|
|
2344
|
+
});
|
|
2345
|
+
console.log(`${behavior.useUpdateDecision ? "Update" : "Sync"} ${pkg.name} (${adapter.name} at ${targetForPackage.targetRoot}):`);
|
|
2346
|
+
console.log(formatPlan(plan));
|
|
2347
|
+
if (!options.dryRun) {
|
|
2348
|
+
await applyInstallPlan(plan, bundle.sourceLock, { executePlugins: options.executePlugins });
|
|
2349
|
+
console.log(`Applied ${pkg.name}.`);
|
|
2350
|
+
}
|
|
2351
|
+
await rm8(bundle.root, { recursive: true, force: true });
|
|
2352
|
+
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2147
2355
|
async function initPackage(root) {
|
|
2148
|
-
await mkdir7(
|
|
2149
|
-
await mkdir7(
|
|
2150
|
-
await mkdir7(
|
|
2151
|
-
const manifestPath =
|
|
2356
|
+
await mkdir7(join19(root, "instructions"), { recursive: true });
|
|
2357
|
+
await mkdir7(join19(root, "rules"), { recursive: true });
|
|
2358
|
+
await mkdir7(join19(root, "skills"), { recursive: true });
|
|
2359
|
+
const manifestPath = join19(root, "agentwheel.json");
|
|
2152
2360
|
const manifest = {
|
|
2153
2361
|
schemaVersion: 1,
|
|
2154
2362
|
name: "example/agentwheel-package",
|
|
@@ -2161,7 +2369,17 @@ async function initPackage(root) {
|
|
|
2161
2369
|
};
|
|
2162
2370
|
await writeFile4(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
2163
2371
|
`, "utf8");
|
|
2164
|
-
await writeFile4(
|
|
2372
|
+
await writeFile4(join19(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
|
|
2373
|
+
}
|
|
2374
|
+
function formatUninstallResult(result) {
|
|
2375
|
+
const removedLabel = result.removed === 1 ? "managed file" : "managed files";
|
|
2376
|
+
if (result.removedDrifted > 0) {
|
|
2377
|
+
const driftedLabel = result.removedDrifted === 1 ? "drifted file" : "drifted files";
|
|
2378
|
+
return `Removed ${result.removed} ${removedLabel}, including ${result.removedDrifted} ${driftedLabel}.`;
|
|
2379
|
+
}
|
|
2380
|
+
if (result.kept === 0) return `Removed ${result.removed} ${removedLabel}.`;
|
|
2381
|
+
const keptLabel = result.kept === 1 ? "drifted file" : "drifted files";
|
|
2382
|
+
return `Removed ${result.removed} ${removedLabel}; kept ${result.kept} ${keptLabel} (use --force to remove).`;
|
|
2165
2383
|
}
|
|
2166
2384
|
function printRegistryEntries(entries) {
|
|
2167
2385
|
for (const entry of entries) {
|