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