agentwheel 0.14.13 → 0.16.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 +46 -0
- package/dist/index.js +2158 -301
- package/openpack.json +1 -1
- package/package.json +1 -1
- package/skills/agentwheel/SKILL.md +31 -10
package/dist/index.js
CHANGED
|
@@ -9,11 +9,11 @@ import {
|
|
|
9
9
|
} from "./chunk-PKAPR55N.js";
|
|
10
10
|
|
|
11
11
|
// src/cli/index.ts
|
|
12
|
-
import { createHash as
|
|
12
|
+
import { createHash as createHash12 } from "crypto";
|
|
13
13
|
import { existsSync } from "fs";
|
|
14
|
-
import { mkdir as
|
|
15
|
-
import { homedir as
|
|
16
|
-
import { dirname as
|
|
14
|
+
import { mkdir as mkdir23, rm as rm12, writeFile as writeFile22 } from "fs/promises";
|
|
15
|
+
import { homedir as homedir10 } from "os";
|
|
16
|
+
import { dirname as dirname32, join as join45, resolve as resolve22 } from "path";
|
|
17
17
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
18
18
|
import { Command } from "commander";
|
|
19
19
|
|
|
@@ -810,13 +810,13 @@ async function spawnWithInput(command, args, input) {
|
|
|
810
810
|
if (result.stderr) throw new Error(result.stderr);
|
|
811
811
|
}
|
|
812
812
|
function waitForProcess(child, label) {
|
|
813
|
-
return new Promise((
|
|
813
|
+
return new Promise((resolve23, reject) => {
|
|
814
814
|
const stderr = [];
|
|
815
815
|
child.stderr?.on("data", (chunk) => stderr.push(chunk));
|
|
816
816
|
child.on("error", reject);
|
|
817
817
|
child.on("close", (code) => {
|
|
818
818
|
const message = Buffer.concat(stderr).toString("utf8");
|
|
819
|
-
if (code === 0)
|
|
819
|
+
if (code === 0) resolve23({ stderr: "" });
|
|
820
820
|
else reject(new Error(`${label} exited ${code}${message ? `: ${message}` : ""}`));
|
|
821
821
|
});
|
|
822
822
|
});
|
|
@@ -2592,6 +2592,7 @@ import { basename as basename8, join as join16, relative as relative3 } from "pa
|
|
|
2592
2592
|
// src/staging/codex-subagents.ts
|
|
2593
2593
|
import { mkdir as mkdir9, readFile as readFile11, writeFile as writeFile11 } from "fs/promises";
|
|
2594
2594
|
import { basename as basename4, dirname as dirname12, join as join6 } from "path";
|
|
2595
|
+
import { parse as parseYaml } from "yaml";
|
|
2595
2596
|
var requiredCodexAgentFields = ["name", "description", "developer_instructions"];
|
|
2596
2597
|
async function renderCodexSubagents(artifacts, stageRoot, adapter) {
|
|
2597
2598
|
if (adapter?.name !== "codex") return artifacts;
|
|
@@ -2664,24 +2665,36 @@ function markdownToCodexAgentToml(agentName, markdown) {
|
|
|
2664
2665
|
const parsed = splitFrontmatter(markdown);
|
|
2665
2666
|
const description = parsed.description ?? firstMeaningfulMarkdownLine(parsed.body) ?? `Custom Codex subagent ${agentName}.`;
|
|
2666
2667
|
const developerInstructions = parsed.body.trim().length > 0 ? parsed.body.trimEnd() : description;
|
|
2667
|
-
|
|
2668
|
+
const lines = [
|
|
2668
2669
|
`name = ${tomlString(agentName)}`,
|
|
2669
|
-
`description = ${tomlString(description)}
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2670
|
+
`description = ${tomlString(description)}`
|
|
2671
|
+
];
|
|
2672
|
+
if (parsed.model) lines.push(`model = ${tomlString(parsed.model)}`);
|
|
2673
|
+
if (parsed.modelReasoningEffort) lines.push(`model_reasoning_effort = ${tomlString(parsed.modelReasoningEffort)}`);
|
|
2674
|
+
lines.push(`developer_instructions = ${tomlMultilineString(developerInstructions)}`, "");
|
|
2675
|
+
return lines.join("\n");
|
|
2673
2676
|
}
|
|
2674
2677
|
function splitFrontmatter(markdown) {
|
|
2675
2678
|
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(markdown);
|
|
2676
2679
|
if (!match) return { body: markdown };
|
|
2677
2680
|
const frontmatter = match[1] ?? "";
|
|
2678
2681
|
const body = markdown.slice(match[0].length);
|
|
2679
|
-
const
|
|
2682
|
+
const metadata = parseYaml(frontmatter);
|
|
2680
2683
|
return {
|
|
2681
2684
|
body,
|
|
2682
|
-
description:
|
|
2685
|
+
description: optionalFrontmatterString(metadata, "description"),
|
|
2686
|
+
model: optionalFrontmatterString(metadata, "model"),
|
|
2687
|
+
modelReasoningEffort: optionalFrontmatterString(metadata, "model_reasoning_effort")
|
|
2683
2688
|
};
|
|
2684
2689
|
}
|
|
2690
|
+
function optionalFrontmatterString(metadata, key) {
|
|
2691
|
+
const value = metadata?.[key];
|
|
2692
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
2693
|
+
if (typeof value !== "string") {
|
|
2694
|
+
throw new Error(`Codex subagent frontmatter field '${key}' must be a string.`);
|
|
2695
|
+
}
|
|
2696
|
+
return value.trim();
|
|
2697
|
+
}
|
|
2685
2698
|
function firstMeaningfulMarkdownLine(markdown) {
|
|
2686
2699
|
for (const rawLine of markdown.split(/\r?\n/)) {
|
|
2687
2700
|
const line = rawLine.trim();
|
|
@@ -5250,14 +5263,14 @@ async function writeMermaidAsset(response) {
|
|
|
5250
5263
|
"content-type": "application/javascript; charset=utf-8",
|
|
5251
5264
|
"cache-control": "public, max-age=3600"
|
|
5252
5265
|
});
|
|
5253
|
-
await new Promise((
|
|
5266
|
+
await new Promise((resolve23) => {
|
|
5254
5267
|
const stream = createReadStream(assetPath);
|
|
5255
5268
|
stream.on("error", () => {
|
|
5256
5269
|
if (!response.headersSent) writeText(response, 404, "Mermaid asset unavailable.\n");
|
|
5257
5270
|
else response.destroy();
|
|
5258
|
-
|
|
5271
|
+
resolve23();
|
|
5259
5272
|
});
|
|
5260
|
-
stream.on("end",
|
|
5273
|
+
stream.on("end", resolve23);
|
|
5261
5274
|
stream.pipe(response);
|
|
5262
5275
|
});
|
|
5263
5276
|
}
|
|
@@ -5284,14 +5297,14 @@ function escapeHtml2(value) {
|
|
|
5284
5297
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
5285
5298
|
}
|
|
5286
5299
|
function listen(server, port, bind) {
|
|
5287
|
-
return new Promise((
|
|
5300
|
+
return new Promise((resolve23, reject) => {
|
|
5288
5301
|
const onError = (error) => {
|
|
5289
5302
|
server.off("listening", onListening);
|
|
5290
5303
|
reject(error);
|
|
5291
5304
|
};
|
|
5292
5305
|
const onListening = () => {
|
|
5293
5306
|
server.off("error", onError);
|
|
5294
|
-
|
|
5307
|
+
resolve23();
|
|
5295
5308
|
};
|
|
5296
5309
|
server.once("error", onError);
|
|
5297
5310
|
server.once("listening", onListening);
|
|
@@ -5299,7 +5312,7 @@ function listen(server, port, bind) {
|
|
|
5299
5312
|
});
|
|
5300
5313
|
}
|
|
5301
5314
|
function waitForShutdown(server, beforeClose) {
|
|
5302
|
-
return new Promise((
|
|
5315
|
+
return new Promise((resolve23) => {
|
|
5303
5316
|
let closing = false;
|
|
5304
5317
|
const shutdown = () => {
|
|
5305
5318
|
if (closing) return;
|
|
@@ -5307,7 +5320,7 @@ function waitForShutdown(server, beforeClose) {
|
|
|
5307
5320
|
process.off("SIGINT", shutdown);
|
|
5308
5321
|
process.off("SIGTERM", shutdown);
|
|
5309
5322
|
beforeClose();
|
|
5310
|
-
server.close(() =>
|
|
5323
|
+
server.close(() => resolve23());
|
|
5311
5324
|
};
|
|
5312
5325
|
process.once("SIGINT", shutdown);
|
|
5313
5326
|
process.once("SIGTERM", shutdown);
|
|
@@ -5317,7 +5330,7 @@ function waitForShutdown(server, beforeClose) {
|
|
|
5317
5330
|
process.off("SIGINT", shutdown);
|
|
5318
5331
|
process.off("SIGTERM", shutdown);
|
|
5319
5332
|
beforeClose();
|
|
5320
|
-
|
|
5333
|
+
resolve23();
|
|
5321
5334
|
});
|
|
5322
5335
|
});
|
|
5323
5336
|
}
|
|
@@ -5837,7 +5850,7 @@ async function fetchClawHubPackage(fetchImpl, packageName) {
|
|
|
5837
5850
|
throw new Error(`ClawHub lookup failed for ${packageName}`);
|
|
5838
5851
|
}
|
|
5839
5852
|
async function delay(ms) {
|
|
5840
|
-
await new Promise((
|
|
5853
|
+
await new Promise((resolve23) => setTimeout(resolve23, ms));
|
|
5841
5854
|
}
|
|
5842
5855
|
function parseClawHubSource(source) {
|
|
5843
5856
|
if (!source.startsWith(sourcePrefix)) {
|
|
@@ -5931,9 +5944,9 @@ var GitSourceDriver = class {
|
|
|
5931
5944
|
throw new Error(`Frozen lock requires cached git checkout at ${resolved.resolvedPath}`);
|
|
5932
5945
|
}
|
|
5933
5946
|
await rm6(resolved.resolvedPath, { recursive: true, force: true });
|
|
5934
|
-
await git(["clone",
|
|
5947
|
+
await git(["clone", parsed.url, resolved.resolvedPath]);
|
|
5935
5948
|
} else if (!resolved.frozenLock) {
|
|
5936
|
-
await git(["-C", resolved.resolvedPath, "fetch", "--prune", "origin"]);
|
|
5949
|
+
await git(["-C", resolved.resolvedPath, "fetch", "--tags", "--prune", "origin"]);
|
|
5937
5950
|
}
|
|
5938
5951
|
const ref = resolved.requestedRef ?? parsed.ref ?? "HEAD";
|
|
5939
5952
|
if (ref === "HEAD") {
|
|
@@ -6029,7 +6042,7 @@ async function withFilesystemLock(lockPath, timeoutMs, fn) {
|
|
|
6029
6042
|
if (Date.now() - started > timeoutMs) {
|
|
6030
6043
|
throw new Error(`Timed out waiting for git cache lock at ${lockPath}`);
|
|
6031
6044
|
}
|
|
6032
|
-
await new Promise((
|
|
6045
|
+
await new Promise((resolve23) => setTimeout(resolve23, 50));
|
|
6033
6046
|
}
|
|
6034
6047
|
}
|
|
6035
6048
|
try {
|
|
@@ -6538,8 +6551,8 @@ function getSourceDriver(name = "local") {
|
|
|
6538
6551
|
}
|
|
6539
6552
|
|
|
6540
6553
|
// src/staging/staging.ts
|
|
6541
|
-
import { chmod, cp as cp5, mkdir as
|
|
6542
|
-
import { basename as
|
|
6554
|
+
import { chmod, cp as cp5, mkdir as mkdir18, mkdtemp as mkdtemp3, readdir as readdir5, stat as stat8 } from "fs/promises";
|
|
6555
|
+
import { basename as basename19, dirname as dirname23, join as join29, relative as relative7, resolve as resolve12, sep as sep2 } from "path";
|
|
6543
6556
|
import { tmpdir as tmpdir4 } from "os";
|
|
6544
6557
|
|
|
6545
6558
|
// src/compose/markdown.ts
|
|
@@ -6953,6 +6966,57 @@ async function sortedDirEntries2(path) {
|
|
|
6953
6966
|
return (await readdir4(path, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
|
|
6954
6967
|
}
|
|
6955
6968
|
|
|
6969
|
+
// src/staging/claude-subagents.ts
|
|
6970
|
+
import { mkdir as mkdir17, readFile as readFile21, writeFile as writeFile19 } from "fs/promises";
|
|
6971
|
+
import { basename as basename18, dirname as dirname22, join as join28 } from "path";
|
|
6972
|
+
async function renderClaudeSubagents(artifacts, stageRoot, adapter) {
|
|
6973
|
+
if (adapter?.name !== "claude") return artifacts;
|
|
6974
|
+
const names = /* @__PURE__ */ new Set();
|
|
6975
|
+
const rendered = [];
|
|
6976
|
+
for (const artifact of artifacts) {
|
|
6977
|
+
if (artifact.type !== "subagents") {
|
|
6978
|
+
rendered.push(artifact);
|
|
6979
|
+
continue;
|
|
6980
|
+
}
|
|
6981
|
+
const next = await renderClaudeSubagent(artifact, stageRoot);
|
|
6982
|
+
if (names.has(next.name)) {
|
|
6983
|
+
throw new Error(`Claude subagents produce duplicate agent name '${next.name}'.`);
|
|
6984
|
+
}
|
|
6985
|
+
names.add(next.name);
|
|
6986
|
+
rendered.push(next);
|
|
6987
|
+
}
|
|
6988
|
+
return rendered;
|
|
6989
|
+
}
|
|
6990
|
+
async function renderClaudeSubagent(artifact, stageRoot) {
|
|
6991
|
+
const sourcePath = artifact.stagedPath ?? artifact.sourcePath;
|
|
6992
|
+
const agentName = claudeAgentName(artifact);
|
|
6993
|
+
const markdownPath = artifact.kind === "dir" ? join28(sourcePath, "AGENTS.md") : sourcePath;
|
|
6994
|
+
if (artifact.kind === "dir" && !await pathExists(markdownPath)) {
|
|
6995
|
+
throw new Error(`Claude subagent directory ${artifact.relativePath} must contain AGENTS.md.`);
|
|
6996
|
+
}
|
|
6997
|
+
if (artifact.kind === "file" && !artifact.name.toLowerCase().endsWith(".md") && !sourcePath.toLowerCase().endsWith(".md")) {
|
|
6998
|
+
throw new Error(`Claude subagent ${artifact.relativePath} must be a .md file or directory containing AGENTS.md.`);
|
|
6999
|
+
}
|
|
7000
|
+
const content = await readFile21(markdownPath, "utf8");
|
|
7001
|
+
const renderedPath = join28(stageRoot, ".agentwheel-rendered", "claude-subagents", `${agentName}.md`);
|
|
7002
|
+
await mkdir17(dirname22(renderedPath), { recursive: true });
|
|
7003
|
+
await writeFile19(renderedPath, content.endsWith("\n") ? content : `${content}
|
|
7004
|
+
`, "utf8");
|
|
7005
|
+
return {
|
|
7006
|
+
...artifact,
|
|
7007
|
+
name: `${agentName}.md`,
|
|
7008
|
+
sourcePath: renderedPath,
|
|
7009
|
+
stagedPath: renderedPath,
|
|
7010
|
+
relativePath: join28("subagents", `${agentName}.md`),
|
|
7011
|
+
kind: "file",
|
|
7012
|
+
hash: await hashPath(renderedPath)
|
|
7013
|
+
};
|
|
7014
|
+
}
|
|
7015
|
+
function claudeAgentName(artifact) {
|
|
7016
|
+
const raw = artifact.kind === "dir" ? artifact.name : basename18(artifact.name);
|
|
7017
|
+
return raw.replace(/\.agent\.md$/i, "").replace(/\.md$/i, "");
|
|
7018
|
+
}
|
|
7019
|
+
|
|
6956
7020
|
// src/staging/staging.ts
|
|
6957
7021
|
async function stageSource(driver, source, options = {}) {
|
|
6958
7022
|
return renderStagedBundle(await stageSourceRaw(driver, source, options), options);
|
|
@@ -6966,15 +7030,15 @@ async function stageResolvedSourceRaw(driver, resolved) {
|
|
|
6966
7030
|
return stageResolvedArtifactsRaw(resolved, artifacts);
|
|
6967
7031
|
}
|
|
6968
7032
|
async function stageResolvedArtifactsRaw(resolved, artifacts) {
|
|
6969
|
-
const root = await mkdtemp3(
|
|
7033
|
+
const root = await mkdtemp3(join29(tmpdir4(), "agentwheel-stage-"));
|
|
6970
7034
|
const stagedArtifacts = [];
|
|
6971
7035
|
for (const artifact of artifacts) {
|
|
6972
|
-
const stagedPath =
|
|
6973
|
-
await
|
|
7036
|
+
const stagedPath = join29(root, artifact.relativePath);
|
|
7037
|
+
await mkdir18(dirname23(stagedPath), { recursive: true });
|
|
6974
7038
|
await cp5(artifact.sourcePath, stagedPath, {
|
|
6975
7039
|
recursive: artifact.kind === "dir",
|
|
6976
7040
|
dereference: true,
|
|
6977
|
-
filter: (path) => !isIgnoredGeneratedEntry(
|
|
7041
|
+
filter: (path) => !isIgnoredGeneratedEntry(basename19(path))
|
|
6978
7042
|
});
|
|
6979
7043
|
await composeAssets(artifact, resolved.resolvedPath, stagedPath);
|
|
6980
7044
|
stagedArtifacts.push({
|
|
@@ -7002,7 +7066,8 @@ async function renderStagedBundle(bundle, options = {}) {
|
|
|
7002
7066
|
const selectedArtifacts = filterArtifactsBySelection(expandedArtifacts, options.select, options.skills);
|
|
7003
7067
|
const runtimeSelectedSet = new Set(normalizeArtifactSelectors(options.select, options.skills) ?? []);
|
|
7004
7068
|
const runtimeArtifacts = options.adapter ? filterArtifactsByRuntime(selectedArtifacts, options.adapter.name, runtimeSelectedSet) : selectedArtifacts;
|
|
7005
|
-
const
|
|
7069
|
+
const claudeRenderedArtifacts = await renderClaudeSubagents(runtimeArtifacts, root, options.adapter);
|
|
7070
|
+
const codexRenderedArtifacts = await renderCodexSubagents(claudeRenderedArtifacts, root, options.adapter);
|
|
7006
7071
|
const openClawRenderedArtifacts = await renderOpenClawSubagents(codexRenderedArtifacts, root, options.adapter);
|
|
7007
7072
|
const renderedArtifacts = await renderCopilotArtifacts(openClawRenderedArtifacts, root, options.adapter);
|
|
7008
7073
|
const finalArtifacts = options.workspaceRoot && options.adapter ? await applyCustomizations(renderedArtifacts, {
|
|
@@ -7056,16 +7121,16 @@ async function composeAssets(artifact, packageRoot, stagedPath) {
|
|
|
7056
7121
|
}
|
|
7057
7122
|
for (const asset of artifact.assets) {
|
|
7058
7123
|
const source = resolvePackagePath(packageRoot, asset.from);
|
|
7059
|
-
const dest =
|
|
7124
|
+
const dest = join29(stagedPath, asset.into);
|
|
7060
7125
|
await copyAsset(asset, source, dest);
|
|
7061
7126
|
}
|
|
7062
7127
|
}
|
|
7063
7128
|
async function copyAsset(asset, source, dest) {
|
|
7064
7129
|
const sourceStats = await stat8(source);
|
|
7065
7130
|
if (sourceStats.isFile()) {
|
|
7066
|
-
if (matchesAny(
|
|
7067
|
-
await
|
|
7068
|
-
await copyAssetFile(source,
|
|
7131
|
+
if (matchesAny(basename19(source), asset.include)) {
|
|
7132
|
+
await mkdir18(dest, { recursive: true });
|
|
7133
|
+
await copyAssetFile(source, join29(dest, basename19(source)), asset);
|
|
7069
7134
|
}
|
|
7070
7135
|
return;
|
|
7071
7136
|
}
|
|
@@ -7073,19 +7138,19 @@ async function copyAsset(asset, source, dest) {
|
|
|
7073
7138
|
throw new Error(`Asset include source is not a file or directory: ${source}`);
|
|
7074
7139
|
}
|
|
7075
7140
|
if (!asset.include?.length) {
|
|
7076
|
-
await
|
|
7141
|
+
await mkdir18(dirname23(dest), { recursive: true });
|
|
7077
7142
|
await cp5(source, dest, { recursive: true, dereference: true });
|
|
7078
7143
|
if (asset.mode === "copy") await normalizeCopiedModes(dest);
|
|
7079
7144
|
return;
|
|
7080
7145
|
}
|
|
7081
7146
|
for (const file of await listFiles(source)) {
|
|
7082
7147
|
const rel = relative7(source, file).replaceAll("\\", "/");
|
|
7083
|
-
if (!matchesAny(rel, asset.include) && !matchesAny(
|
|
7084
|
-
await copyAssetFile(file,
|
|
7148
|
+
if (!matchesAny(rel, asset.include) && !matchesAny(basename19(file), asset.include)) continue;
|
|
7149
|
+
await copyAssetFile(file, join29(dest, rel), asset);
|
|
7085
7150
|
}
|
|
7086
7151
|
}
|
|
7087
7152
|
async function copyAssetFile(source, dest, asset) {
|
|
7088
|
-
await
|
|
7153
|
+
await mkdir18(dirname23(dest), { recursive: true });
|
|
7089
7154
|
await cp5(source, dest, { dereference: true });
|
|
7090
7155
|
if (asset.mode === "copy") await chmod(dest, 420);
|
|
7091
7156
|
}
|
|
@@ -7101,7 +7166,7 @@ async function listFiles(root) {
|
|
|
7101
7166
|
const out = [];
|
|
7102
7167
|
async function walk2(dir) {
|
|
7103
7168
|
for (const entry of (await readdir5(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
7104
|
-
const full =
|
|
7169
|
+
const full = join29(dir, entry.name);
|
|
7105
7170
|
if (entry.isDirectory()) {
|
|
7106
7171
|
await walk2(full);
|
|
7107
7172
|
} else if (entry.isFile()) {
|
|
@@ -7120,7 +7185,7 @@ async function normalizeCopiedModes(path) {
|
|
|
7120
7185
|
}
|
|
7121
7186
|
if (!stats.isDirectory()) return;
|
|
7122
7187
|
for (const entry of await readdir5(path, { withFileTypes: true })) {
|
|
7123
|
-
await normalizeCopiedModes(
|
|
7188
|
+
await normalizeCopiedModes(join29(path, entry.name));
|
|
7124
7189
|
}
|
|
7125
7190
|
}
|
|
7126
7191
|
function matchesAny(path, patterns) {
|
|
@@ -7133,10 +7198,108 @@ function matchesGlob(path, pattern) {
|
|
|
7133
7198
|
}
|
|
7134
7199
|
|
|
7135
7200
|
// src/model/workspace.ts
|
|
7136
|
-
import { readFile as
|
|
7201
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
7137
7202
|
import { homedir as homedir4 } from "os";
|
|
7138
|
-
import { dirname as
|
|
7203
|
+
import { dirname as dirname24, join as join30, resolve as resolve13 } from "path";
|
|
7139
7204
|
import { z as z6 } from "zod";
|
|
7205
|
+
|
|
7206
|
+
// src/resolve/semver.ts
|
|
7207
|
+
var semverPattern = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
7208
|
+
function parseSemver(value) {
|
|
7209
|
+
const match = semverPattern.exec(value.trim());
|
|
7210
|
+
if (!match) return void 0;
|
|
7211
|
+
return {
|
|
7212
|
+
major: Number(match[1]),
|
|
7213
|
+
minor: Number(match[2]),
|
|
7214
|
+
patch: Number(match[3]),
|
|
7215
|
+
prerelease: match[4]
|
|
7216
|
+
};
|
|
7217
|
+
}
|
|
7218
|
+
function satisfiesVersionRange(version, range) {
|
|
7219
|
+
if (!range || range.trim() === "" || range.trim() === "*") return true;
|
|
7220
|
+
const parsedVersion = parseSemver(version);
|
|
7221
|
+
const trimmed = range.trim();
|
|
7222
|
+
if (!parsedVersion) {
|
|
7223
|
+
return trimmed === "*" || trimmed === version;
|
|
7224
|
+
}
|
|
7225
|
+
const comparators = parseRange(trimmed);
|
|
7226
|
+
if (!comparators) return trimmed === version;
|
|
7227
|
+
return comparators.every((comparator) => compareWith(parsedVersion, comparator));
|
|
7228
|
+
}
|
|
7229
|
+
function semverMajorOrVersion(version) {
|
|
7230
|
+
const parsed = parseSemver(version);
|
|
7231
|
+
return parsed ? String(parsed.major) : version;
|
|
7232
|
+
}
|
|
7233
|
+
function compareSemverStrings(a, b) {
|
|
7234
|
+
const parsedA = parseSemver(a);
|
|
7235
|
+
const parsedB = parseSemver(b);
|
|
7236
|
+
if (!parsedA || !parsedB) return a.localeCompare(b);
|
|
7237
|
+
return compareSemver(parsedA, parsedB);
|
|
7238
|
+
}
|
|
7239
|
+
function isSupportedVersionRange(range) {
|
|
7240
|
+
const trimmed = range.trim();
|
|
7241
|
+
return trimmed === "*" || parseRange(trimmed) !== void 0;
|
|
7242
|
+
}
|
|
7243
|
+
function parseRange(range) {
|
|
7244
|
+
if (range === "*") return [];
|
|
7245
|
+
if (range.startsWith("^")) {
|
|
7246
|
+
const base = parseSemver(range.slice(1));
|
|
7247
|
+
if (!base) return void 0;
|
|
7248
|
+
return [
|
|
7249
|
+
{ op: ">=", version: base },
|
|
7250
|
+
{ op: "<", version: caretUpperBound(base) }
|
|
7251
|
+
];
|
|
7252
|
+
}
|
|
7253
|
+
if (range.startsWith("~")) {
|
|
7254
|
+
const base = parseSemver(range.slice(1));
|
|
7255
|
+
if (!base) return void 0;
|
|
7256
|
+
return [
|
|
7257
|
+
{ op: ">=", version: base },
|
|
7258
|
+
{ op: "<", version: { major: base.major, minor: base.minor + 1, patch: 0 } }
|
|
7259
|
+
];
|
|
7260
|
+
}
|
|
7261
|
+
const parts = range.split(/\s+/).filter(Boolean);
|
|
7262
|
+
const comparators = [];
|
|
7263
|
+
for (const part of parts) {
|
|
7264
|
+
const match = /^(>=|<=|>|<|=)?(.+)$/.exec(part);
|
|
7265
|
+
if (!match) return void 0;
|
|
7266
|
+
const version = parseSemver(match[2] ?? "");
|
|
7267
|
+
if (!version) return void 0;
|
|
7268
|
+
comparators.push({ op: match[1] ?? "=", version });
|
|
7269
|
+
}
|
|
7270
|
+
return comparators;
|
|
7271
|
+
}
|
|
7272
|
+
function caretUpperBound(version) {
|
|
7273
|
+
if (version.major > 0) return { major: version.major + 1, minor: 0, patch: 0 };
|
|
7274
|
+
if (version.minor > 0) return { major: 0, minor: version.minor + 1, patch: 0 };
|
|
7275
|
+
return { major: 0, minor: 0, patch: version.patch + 1 };
|
|
7276
|
+
}
|
|
7277
|
+
function compareWith(version, comparator) {
|
|
7278
|
+
const order = compareSemver(version, comparator.version);
|
|
7279
|
+
switch (comparator.op) {
|
|
7280
|
+
case "=":
|
|
7281
|
+
return order === 0;
|
|
7282
|
+
case ">":
|
|
7283
|
+
return order > 0;
|
|
7284
|
+
case ">=":
|
|
7285
|
+
return order >= 0;
|
|
7286
|
+
case "<":
|
|
7287
|
+
return order < 0;
|
|
7288
|
+
case "<=":
|
|
7289
|
+
return order <= 0;
|
|
7290
|
+
}
|
|
7291
|
+
}
|
|
7292
|
+
function compareSemver(a, b) {
|
|
7293
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
7294
|
+
if (a[key] !== b[key]) return a[key] > b[key] ? 1 : -1;
|
|
7295
|
+
}
|
|
7296
|
+
if (a.prerelease === b.prerelease) return 0;
|
|
7297
|
+
if (!a.prerelease) return 1;
|
|
7298
|
+
if (!b.prerelease) return -1;
|
|
7299
|
+
return a.prerelease.localeCompare(b.prerelease);
|
|
7300
|
+
}
|
|
7301
|
+
|
|
7302
|
+
// src/model/workspace.ts
|
|
7140
7303
|
var artifactSelectorListSchema = z6.array(z6.string().min(1));
|
|
7141
7304
|
var workspaceSelectionImportSchema = z6.object({
|
|
7142
7305
|
export: z6.string().min(1),
|
|
@@ -7177,6 +7340,9 @@ var workspacePackageBaseSchema = z6.object({
|
|
|
7177
7340
|
adapterCodeHash: z6.string().min(16).optional(),
|
|
7178
7341
|
installationType: installationTypeSchema.optional(),
|
|
7179
7342
|
mode: z6.enum(["pinned", "tracking"]).default("pinned"),
|
|
7343
|
+
version: z6.string().min(1).refine(isSupportedVersionRange, {
|
|
7344
|
+
message: "Version policy must be an exact semver, ~range, ^range, comparator range, or *"
|
|
7345
|
+
}).optional(),
|
|
7180
7346
|
requestedRef: z6.string().min(1).optional(),
|
|
7181
7347
|
select: z6.array(z6.string().min(1)).optional(),
|
|
7182
7348
|
skills: z6.array(z6.string().min(1)).optional(),
|
|
@@ -7212,9 +7378,45 @@ var workspaceProfileRuntimeSchema = z6.object({
|
|
|
7212
7378
|
reloadRuntimes: z6.boolean().optional(),
|
|
7213
7379
|
reloadCommands: commandListSchema
|
|
7214
7380
|
});
|
|
7215
|
-
var
|
|
7216
|
-
|
|
7381
|
+
var workspaceProfileMemberSchema = z6.object({
|
|
7382
|
+
id: z6.string().min(1).regex(/^[a-z0-9][a-z0-9._-]*$/i),
|
|
7383
|
+
workspace: z6.string().min(1),
|
|
7384
|
+
profile: z6.string().min(1),
|
|
7385
|
+
transport: z6.enum(["local", "ssh"]).default("local"),
|
|
7386
|
+
host: z6.string().min(1).optional(),
|
|
7387
|
+
user: z6.string().min(1).optional(),
|
|
7388
|
+
port: z6.number().int().positive().optional(),
|
|
7389
|
+
identityFile: z6.string().min(1).optional(),
|
|
7390
|
+
refreshTtlSeconds: z6.number().int().positive().optional()
|
|
7391
|
+
}).strict().superRefine((member, ctx) => {
|
|
7392
|
+
if (member.transport === "ssh" && !member.host) {
|
|
7393
|
+
ctx.addIssue({ code: "custom", path: ["host"], message: "SSH profile members require host" });
|
|
7394
|
+
}
|
|
7395
|
+
if (member.transport === "ssh" && !member.workspace.startsWith("/")) {
|
|
7396
|
+
ctx.addIssue({ code: "custom", path: ["workspace"], message: "SSH profile member workspaces must be absolute" });
|
|
7397
|
+
}
|
|
7398
|
+
});
|
|
7399
|
+
var workspaceLeafProfileSchema = z6.object({
|
|
7400
|
+
runtimes: z6.array(workspaceProfileRuntimeSchema).min(1),
|
|
7401
|
+
members: z6.never().optional()
|
|
7402
|
+
}).strict();
|
|
7403
|
+
var workspaceCompositeProfileSchema = z6.object({
|
|
7404
|
+
members: z6.array(workspaceProfileMemberSchema).min(1),
|
|
7405
|
+
runtimes: z6.never().optional(),
|
|
7406
|
+
refreshTtlSeconds: z6.number().int().positive().default(86400)
|
|
7407
|
+
}).strict().superRefine((profile, ctx) => {
|
|
7408
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7409
|
+
for (const [index, member] of profile.members.entries()) {
|
|
7410
|
+
if (seen.has(member.id)) {
|
|
7411
|
+
ctx.addIssue({ code: "custom", path: ["members", index, "id"], message: "Composite profile member ids must be unique" });
|
|
7412
|
+
}
|
|
7413
|
+
seen.add(member.id);
|
|
7414
|
+
}
|
|
7217
7415
|
});
|
|
7416
|
+
var workspaceProfileSchema = z6.union([
|
|
7417
|
+
workspaceLeafProfileSchema,
|
|
7418
|
+
workspaceCompositeProfileSchema
|
|
7419
|
+
]);
|
|
7218
7420
|
var workspaceRegistrySchema = z6.object({
|
|
7219
7421
|
sources: z6.array(z6.string().min(1)).optional(),
|
|
7220
7422
|
ttlSeconds: z6.number().int().positive().optional()
|
|
@@ -7269,12 +7471,12 @@ var workspaceConfigSchema = z6.discriminatedUnion("schemaVersion", [
|
|
|
7269
7471
|
workspaceConfigV2Schema
|
|
7270
7472
|
]);
|
|
7271
7473
|
function workspaceConfigPath(workspaceRoot) {
|
|
7272
|
-
return
|
|
7474
|
+
return join30(workspaceRoot, ".agentwheel", "config.json");
|
|
7273
7475
|
}
|
|
7274
7476
|
async function readWorkspaceConfig(workspaceRoot) {
|
|
7275
7477
|
const path = workspaceConfigPath(workspaceRoot);
|
|
7276
7478
|
if (!await pathExists(path)) return emptyWorkspaceConfig();
|
|
7277
|
-
return workspaceConfigSchema.parse(JSON.parse(await
|
|
7479
|
+
return workspaceConfigSchema.parse(JSON.parse(await readFile22(path, "utf8")));
|
|
7278
7480
|
}
|
|
7279
7481
|
async function writeWorkspaceConfig(workspaceRoot, config) {
|
|
7280
7482
|
await writeJsonAtomic(workspaceConfigPath(workspaceRoot), workspaceConfigSchema.parse(config));
|
|
@@ -7287,13 +7489,13 @@ function upsertPackage(config, entry) {
|
|
|
7287
7489
|
return workspaceConfigSchema.parse({ ...parsed, packages });
|
|
7288
7490
|
}
|
|
7289
7491
|
function globalWorkspaceConfigPath(globalRoot = homedir4()) {
|
|
7290
|
-
return
|
|
7492
|
+
return join30(globalRoot, ".agentwheel", "config.json");
|
|
7291
7493
|
}
|
|
7292
7494
|
async function findWorkspaceRoot(start = process.cwd()) {
|
|
7293
7495
|
let current = resolve13(start);
|
|
7294
7496
|
while (true) {
|
|
7295
7497
|
if (await pathExists(workspaceConfigPath(current))) return current;
|
|
7296
|
-
const parent =
|
|
7498
|
+
const parent = dirname24(current);
|
|
7297
7499
|
if (parent === current) return resolve13(start);
|
|
7298
7500
|
current = parent;
|
|
7299
7501
|
}
|
|
@@ -7330,9 +7532,12 @@ function resolveConfigPath(path, baseRoot) {
|
|
|
7330
7532
|
function emptyWorkspaceConfig() {
|
|
7331
7533
|
return { schemaVersion: 1, packages: [], registry: {}, trust: {}, profiles: {}, agents: {} };
|
|
7332
7534
|
}
|
|
7535
|
+
function isCompositeWorkspaceProfile(profile) {
|
|
7536
|
+
return "members" in profile && Array.isArray(profile.members);
|
|
7537
|
+
}
|
|
7333
7538
|
async function readConfigPath(path) {
|
|
7334
7539
|
if (!await pathExists(path)) return emptyWorkspaceConfig();
|
|
7335
|
-
return workspaceConfigSchema.parse(JSON.parse(await
|
|
7540
|
+
return workspaceConfigSchema.parse(JSON.parse(await readFile22(path, "utf8")));
|
|
7336
7541
|
}
|
|
7337
7542
|
function mergeWorkspaceTrust(global, project) {
|
|
7338
7543
|
return {
|
|
@@ -7347,18 +7552,18 @@ function sortedUnique2(values) {
|
|
|
7347
7552
|
}
|
|
7348
7553
|
|
|
7349
7554
|
// src/lifecycle/customization.ts
|
|
7350
|
-
import { appendFile, cp as cp6, mkdir as
|
|
7351
|
-
import { dirname as
|
|
7555
|
+
import { appendFile, cp as cp6, mkdir as mkdir19, rm as rm9 } from "fs/promises";
|
|
7556
|
+
import { dirname as dirname26, join as join33 } from "path";
|
|
7352
7557
|
|
|
7353
7558
|
// src/resolve/graph.ts
|
|
7354
7559
|
import { createHash as createHash8 } from "crypto";
|
|
7355
|
-
import { mkdtemp as mkdtemp4, readdir as readdir6, readFile as
|
|
7560
|
+
import { mkdtemp as mkdtemp4, readdir as readdir6, readFile as readFile25, stat as stat10 } from "fs/promises";
|
|
7356
7561
|
import { tmpdir as tmpdir5 } from "os";
|
|
7357
|
-
import { basename as
|
|
7562
|
+
import { basename as basename20, extname as extname4, join as join32 } from "path";
|
|
7358
7563
|
|
|
7359
7564
|
// src/model/workspace-composition.ts
|
|
7360
7565
|
import { createHash as createHash7 } from "crypto";
|
|
7361
|
-
import { readFile as
|
|
7566
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
7362
7567
|
import { z as z7 } from "zod";
|
|
7363
7568
|
var selectionSourceConfigSchema = z7.object({
|
|
7364
7569
|
schemaVersion: z7.literal(2),
|
|
@@ -7377,7 +7582,7 @@ async function resolveSelectionImport(sourceRoot, sourceDriver, selection) {
|
|
|
7377
7582
|
}
|
|
7378
7583
|
let raw;
|
|
7379
7584
|
try {
|
|
7380
|
-
raw = JSON.parse(await
|
|
7585
|
+
raw = JSON.parse(await readFile23(path, "utf8"));
|
|
7381
7586
|
} catch (error) {
|
|
7382
7587
|
const message = error instanceof Error ? error.message : String(error);
|
|
7383
7588
|
throw new Error(`Selection import '${parsedSelection.export}' cannot parse ${path}: ${message}`);
|
|
@@ -7479,9 +7684,9 @@ import { homedir as homedir6 } from "os";
|
|
|
7479
7684
|
import { resolve as resolve15 } from "path";
|
|
7480
7685
|
|
|
7481
7686
|
// src/registry/client.ts
|
|
7482
|
-
import { readFile as
|
|
7687
|
+
import { readFile as readFile24, rm as rm8, stat as stat9 } from "fs/promises";
|
|
7483
7688
|
import { homedir as homedir5 } from "os";
|
|
7484
|
-
import { dirname as
|
|
7689
|
+
import { dirname as dirname25, join as join31, resolve as resolve14 } from "path";
|
|
7485
7690
|
import { fileURLToPath } from "url";
|
|
7486
7691
|
|
|
7487
7692
|
// src/model/registry.ts
|
|
@@ -7554,13 +7759,6 @@ var RegistryClient = class {
|
|
|
7554
7759
|
const index = await this.getIndex(options);
|
|
7555
7760
|
return index.entries.find((entry) => entry.name === name);
|
|
7556
7761
|
}
|
|
7557
|
-
async search(query, options = {}) {
|
|
7558
|
-
const q = query.toLowerCase();
|
|
7559
|
-
const index = await this.getIndex(options);
|
|
7560
|
-
return index.entries.filter(
|
|
7561
|
-
(entry) => entry.name.toLowerCase().includes(q) || entry.description.toLowerCase().includes(q) || entry.tags.some((tag) => tag.toLowerCase().includes(q))
|
|
7562
|
-
);
|
|
7563
|
-
}
|
|
7564
7762
|
async clearCache() {
|
|
7565
7763
|
await rm8(this.cachePath, { force: true });
|
|
7566
7764
|
}
|
|
@@ -7585,7 +7783,7 @@ var RegistryClient = class {
|
|
|
7585
7783
|
}
|
|
7586
7784
|
async readCache() {
|
|
7587
7785
|
if (!await pathExists(this.cachePath)) return void 0;
|
|
7588
|
-
return registryCacheSchema.parse(JSON.parse(await
|
|
7786
|
+
return registryCacheSchema.parse(JSON.parse(await readFile24(this.cachePath, "utf8")));
|
|
7589
7787
|
}
|
|
7590
7788
|
isExpired(cache, ttlMs) {
|
|
7591
7789
|
return this.now().getTime() - new Date(cache.fetchedAt).getTime() > ttlMs;
|
|
@@ -7604,10 +7802,10 @@ var RegistryClient = class {
|
|
|
7604
7802
|
if (await pathExists(filePath)) {
|
|
7605
7803
|
const fullPath = resolve14(filePath);
|
|
7606
7804
|
const stats = await stat9(fullPath);
|
|
7607
|
-
return
|
|
7805
|
+
return readFile24(stats.isDirectory() ? join31(fullPath, "index.json") : fullPath, "utf8");
|
|
7608
7806
|
}
|
|
7609
|
-
const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot:
|
|
7610
|
-
return
|
|
7807
|
+
const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join31(dirname25(this.cachePath), "registry-repos") }));
|
|
7808
|
+
return readFile24(join31(resolved.resolvedPath, "index.json"), "utf8");
|
|
7611
7809
|
}
|
|
7612
7810
|
warnCompatibility(entries) {
|
|
7613
7811
|
for (const entry of entries) {
|
|
@@ -7645,7 +7843,7 @@ function mergeIndexes(indexes) {
|
|
|
7645
7843
|
return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
7646
7844
|
}
|
|
7647
7845
|
function defaultRegistryCachePath() {
|
|
7648
|
-
return
|
|
7846
|
+
return join31(homedir5(), ".agentwheel", "registry-cache.json");
|
|
7649
7847
|
}
|
|
7650
7848
|
function sameSources(a, b) {
|
|
7651
7849
|
return a.length === b.length && a.every((source, index) => source === b[index]);
|
|
@@ -7805,97 +8003,11 @@ function normalizeLiteralProviderSpec(source, prefix) {
|
|
|
7805
8003
|
return `${prefix}${spec}`;
|
|
7806
8004
|
}
|
|
7807
8005
|
|
|
7808
|
-
// src/resolve/semver.ts
|
|
7809
|
-
var semverPattern = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
7810
|
-
function parseSemver(value) {
|
|
7811
|
-
const match = semverPattern.exec(value.trim());
|
|
7812
|
-
if (!match) return void 0;
|
|
7813
|
-
return {
|
|
7814
|
-
major: Number(match[1]),
|
|
7815
|
-
minor: Number(match[2]),
|
|
7816
|
-
patch: Number(match[3]),
|
|
7817
|
-
prerelease: match[4]
|
|
7818
|
-
};
|
|
7819
|
-
}
|
|
7820
|
-
function satisfiesVersionRange(version, range) {
|
|
7821
|
-
if (!range || range.trim() === "" || range.trim() === "*") return true;
|
|
7822
|
-
const parsedVersion = parseSemver(version);
|
|
7823
|
-
const trimmed = range.trim();
|
|
7824
|
-
if (!parsedVersion) {
|
|
7825
|
-
return trimmed === "*" || trimmed === version;
|
|
7826
|
-
}
|
|
7827
|
-
const comparators = parseRange(trimmed);
|
|
7828
|
-
if (!comparators) return trimmed === version;
|
|
7829
|
-
return comparators.every((comparator) => compareWith(parsedVersion, comparator));
|
|
7830
|
-
}
|
|
7831
|
-
function semverMajorOrVersion(version) {
|
|
7832
|
-
const parsed = parseSemver(version);
|
|
7833
|
-
return parsed ? String(parsed.major) : version;
|
|
7834
|
-
}
|
|
7835
|
-
function parseRange(range) {
|
|
7836
|
-
if (range === "*") return [];
|
|
7837
|
-
if (range.startsWith("^")) {
|
|
7838
|
-
const base = parseSemver(range.slice(1));
|
|
7839
|
-
if (!base) return void 0;
|
|
7840
|
-
return [
|
|
7841
|
-
{ op: ">=", version: base },
|
|
7842
|
-
{ op: "<", version: caretUpperBound(base) }
|
|
7843
|
-
];
|
|
7844
|
-
}
|
|
7845
|
-
if (range.startsWith("~")) {
|
|
7846
|
-
const base = parseSemver(range.slice(1));
|
|
7847
|
-
if (!base) return void 0;
|
|
7848
|
-
return [
|
|
7849
|
-
{ op: ">=", version: base },
|
|
7850
|
-
{ op: "<", version: { major: base.major, minor: base.minor + 1, patch: 0 } }
|
|
7851
|
-
];
|
|
7852
|
-
}
|
|
7853
|
-
const parts = range.split(/\s+/).filter(Boolean);
|
|
7854
|
-
const comparators = [];
|
|
7855
|
-
for (const part of parts) {
|
|
7856
|
-
const match = /^(>=|<=|>|<|=)?(.+)$/.exec(part);
|
|
7857
|
-
if (!match) return void 0;
|
|
7858
|
-
const version = parseSemver(match[2] ?? "");
|
|
7859
|
-
if (!version) return void 0;
|
|
7860
|
-
comparators.push({ op: match[1] ?? "=", version });
|
|
7861
|
-
}
|
|
7862
|
-
return comparators;
|
|
7863
|
-
}
|
|
7864
|
-
function caretUpperBound(version) {
|
|
7865
|
-
if (version.major > 0) return { major: version.major + 1, minor: 0, patch: 0 };
|
|
7866
|
-
if (version.minor > 0) return { major: 0, minor: version.minor + 1, patch: 0 };
|
|
7867
|
-
return { major: 0, minor: 0, patch: version.patch + 1 };
|
|
7868
|
-
}
|
|
7869
|
-
function compareWith(version, comparator) {
|
|
7870
|
-
const order = compareSemver(version, comparator.version);
|
|
7871
|
-
switch (comparator.op) {
|
|
7872
|
-
case "=":
|
|
7873
|
-
return order === 0;
|
|
7874
|
-
case ">":
|
|
7875
|
-
return order > 0;
|
|
7876
|
-
case ">=":
|
|
7877
|
-
return order >= 0;
|
|
7878
|
-
case "<":
|
|
7879
|
-
return order < 0;
|
|
7880
|
-
case "<=":
|
|
7881
|
-
return order <= 0;
|
|
7882
|
-
}
|
|
7883
|
-
}
|
|
7884
|
-
function compareSemver(a, b) {
|
|
7885
|
-
for (const key of ["major", "minor", "patch"]) {
|
|
7886
|
-
if (a[key] !== b[key]) return a[key] > b[key] ? 1 : -1;
|
|
7887
|
-
}
|
|
7888
|
-
if (a.prerelease === b.prerelease) return 0;
|
|
7889
|
-
if (!a.prerelease) return 1;
|
|
7890
|
-
if (!b.prerelease) return -1;
|
|
7891
|
-
return a.prerelease.localeCompare(b.prerelease);
|
|
7892
|
-
}
|
|
7893
|
-
|
|
7894
8006
|
// src/resolve/graph.ts
|
|
7895
8007
|
var cacheLocks = /* @__PURE__ */ new Map();
|
|
7896
8008
|
async function resolveDependencyGraph(roots, options) {
|
|
7897
8009
|
if (roots.length === 0) throw new Error("At least one graph root is required.");
|
|
7898
|
-
const graphRoot = await mkdtemp4(
|
|
8010
|
+
const graphRoot = await mkdtemp4(join32(tmpdir5(), "agentwheel-graph-"));
|
|
7899
8011
|
const fetchCache = /* @__PURE__ */ new Map();
|
|
7900
8012
|
const nodesByKey = /* @__PURE__ */ new Map();
|
|
7901
8013
|
const rootResults = [];
|
|
@@ -7908,6 +8020,7 @@ async function resolveDependencyGraph(roots, options) {
|
|
|
7908
8020
|
select: root.select,
|
|
7909
8021
|
selection: root.selection,
|
|
7910
8022
|
mode: root.mode ?? "pinned",
|
|
8023
|
+
version: root.version,
|
|
7911
8024
|
ref: root.ref,
|
|
7912
8025
|
declaringPackageRoot: options.workspaceRoot,
|
|
7913
8026
|
requiredBy: `workspace:${rootId}`,
|
|
@@ -8414,7 +8527,7 @@ async function collectIncludeNeeds(artifact, artifactsByRelativePath) {
|
|
|
8414
8527
|
const file = stack.shift();
|
|
8415
8528
|
if (scanned.has(file)) continue;
|
|
8416
8529
|
scanned.add(file);
|
|
8417
|
-
const content = await
|
|
8530
|
+
const content = await readFile25(file, "utf8");
|
|
8418
8531
|
for (const include of extractOpenPackIncludeSelectors(content)) {
|
|
8419
8532
|
await collectIncludeSelector(include.raw, include.optional, artifactsByRelativePath, scanned, stack, needs);
|
|
8420
8533
|
}
|
|
@@ -8457,7 +8570,7 @@ async function listMarkdownFiles2(root) {
|
|
|
8457
8570
|
const out = [];
|
|
8458
8571
|
async function walk2(dir) {
|
|
8459
8572
|
for (const entry of (await readdir6(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
8460
|
-
const full =
|
|
8573
|
+
const full = join32(dir, entry.name);
|
|
8461
8574
|
if (entry.isDirectory()) {
|
|
8462
8575
|
await walk2(full);
|
|
8463
8576
|
} else if (entry.isFile() && extname4(entry.name).toLowerCase() === ".md") {
|
|
@@ -8492,7 +8605,7 @@ async function fetchPackage(normalized, mode, options, fetchCache, refOverride)
|
|
|
8492
8605
|
const promise = (async () => {
|
|
8493
8606
|
const driver = getSourceDriver(normalized.driver);
|
|
8494
8607
|
const resolved = await driver.resolve(normalized.source, {
|
|
8495
|
-
cacheRoot: options.cacheRoot ??
|
|
8608
|
+
cacheRoot: options.cacheRoot ?? join32(options.workspaceRoot, ".agentwheel", "cache"),
|
|
8496
8609
|
mode,
|
|
8497
8610
|
ref: refOverride ?? normalized.requestedRef,
|
|
8498
8611
|
frozenLock: hardLockedCheckout
|
|
@@ -8502,7 +8615,7 @@ async function fetchPackage(normalized, mode, options, fetchCache, refOverride)
|
|
|
8502
8615
|
const exported = await driver.export(translated);
|
|
8503
8616
|
const manifest = await readPackageManifest(exported.resolvedPath);
|
|
8504
8617
|
const artifacts = await driver.list(exported);
|
|
8505
|
-
const name = manifest?.name ?? exported.packageName ??
|
|
8618
|
+
const name = manifest?.name ?? exported.packageName ?? basename20(exported.resolvedPath);
|
|
8506
8619
|
const version = manifest?.version ?? exported.packageVersion ?? "0.0.0";
|
|
8507
8620
|
const sourceHash = exported.sourceHash ?? await hashPath(exported.resolvedPath);
|
|
8508
8621
|
return {
|
|
@@ -8609,8 +8722,8 @@ function verifyIntegrity(integrity, sourceHash, label) {
|
|
|
8609
8722
|
async function withCachePathLock(path, fn) {
|
|
8610
8723
|
const previous = cacheLocks.get(path) ?? Promise.resolve();
|
|
8611
8724
|
let release = () => void 0;
|
|
8612
|
-
const current = previous.then(() => new Promise((
|
|
8613
|
-
release =
|
|
8725
|
+
const current = previous.then(() => new Promise((resolve23) => {
|
|
8726
|
+
release = resolve23;
|
|
8614
8727
|
}));
|
|
8615
8728
|
cacheLocks.set(path, current);
|
|
8616
8729
|
await previous;
|
|
@@ -8699,8 +8812,8 @@ async function mapLimit(items, limit, fn) {
|
|
|
8699
8812
|
|
|
8700
8813
|
// src/lifecycle/customization.ts
|
|
8701
8814
|
async function remember(workspaceRoot, runtime, text) {
|
|
8702
|
-
const overlayPath =
|
|
8703
|
-
await
|
|
8815
|
+
const overlayPath = join33(workspaceRoot, ".agentwheel", "overlays", runtime, "instructions.local.md");
|
|
8816
|
+
await mkdir19(dirname26(overlayPath), { recursive: true });
|
|
8704
8817
|
await appendFile(overlayPath, `${text.trim()}
|
|
8705
8818
|
`, "utf8");
|
|
8706
8819
|
return { overlayPath };
|
|
@@ -8723,8 +8836,8 @@ async function ejectArtifact(workspaceRoot, item) {
|
|
|
8723
8836
|
throw new Error(`Artifact not found: ${item}`);
|
|
8724
8837
|
}
|
|
8725
8838
|
const ejectedIdentity = parsed.packageIdentity === parsed.packageName ? parsed.packageIdentity : candidate.nodeId === parsed.packageIdentity ? candidate.nodeId : `${candidate.packageName}@${candidate.packageVersion}`;
|
|
8726
|
-
const ejectedPath =
|
|
8727
|
-
await
|
|
8839
|
+
const ejectedPath = join33(workspaceRoot, ".agentwheel", "ejected", ...ejectedIdentity.split("/"), parsed.type, parsed.name);
|
|
8840
|
+
await mkdir19(dirname26(ejectedPath), { recursive: true });
|
|
8728
8841
|
await rm9(ejectedPath, { recursive: true, force: true });
|
|
8729
8842
|
await cp6(artifact.stagedPath ?? artifact.sourcePath, ejectedPath, { recursive: artifact.kind === "dir", dereference: true });
|
|
8730
8843
|
return {
|
|
@@ -8766,7 +8879,7 @@ async function stageEjectCandidate(workspaceRoot, pkg) {
|
|
|
8766
8879
|
const adapter = pkg.adapterConfig ? await loadAdapterConfig(pkg.adapterConfig) : getAdapter(pkg.adapter);
|
|
8767
8880
|
const bundle = await stageSource(driver, normalized.source, {
|
|
8768
8881
|
adapter,
|
|
8769
|
-
cacheRoot:
|
|
8882
|
+
cacheRoot: join33(workspaceRoot, ".agentwheel", "cache"),
|
|
8770
8883
|
mode: pkg.mode,
|
|
8771
8884
|
ref: normalized.requestedRef ?? pkg.requestedRef
|
|
8772
8885
|
});
|
|
@@ -8817,8 +8930,8 @@ import { rm as rm10 } from "fs/promises";
|
|
|
8817
8930
|
|
|
8818
8931
|
// src/lifecycle/source-plan.ts
|
|
8819
8932
|
import { createHash as createHash10 } from "crypto";
|
|
8820
|
-
import { mkdir as
|
|
8821
|
-
import { dirname as
|
|
8933
|
+
import { mkdir as mkdir21 } from "fs/promises";
|
|
8934
|
+
import { dirname as dirname28, join as join36 } from "path";
|
|
8822
8935
|
|
|
8823
8936
|
// src/resolve/graph-diff.ts
|
|
8824
8937
|
function diffGraphLocks(previous, next) {
|
|
@@ -8980,11 +9093,11 @@ function formatSelectionImport(root) {
|
|
|
8980
9093
|
|
|
8981
9094
|
// src/resolve/render.ts
|
|
8982
9095
|
import { createHash as createHash9 } from "crypto";
|
|
8983
|
-
import { readFile as
|
|
9096
|
+
import { readFile as readFile26, mkdtemp as mkdtemp5 } from "fs/promises";
|
|
8984
9097
|
import { tmpdir as tmpdir6 } from "os";
|
|
8985
|
-
import { join as
|
|
9098
|
+
import { join as join34 } from "path";
|
|
8986
9099
|
async function renderGraphForTarget(graph, targetContext = {}) {
|
|
8987
|
-
const root = await mkdtemp5(
|
|
9100
|
+
const root = await mkdtemp5(join34(tmpdir6(), "agentwheel-render-"));
|
|
8988
9101
|
const artifacts = [];
|
|
8989
9102
|
const stagedNodes = /* @__PURE__ */ new Map();
|
|
8990
9103
|
const includeEdges = /* @__PURE__ */ new Map();
|
|
@@ -9056,7 +9169,8 @@ async function renderGraphForTarget(graph, targetContext = {}) {
|
|
|
9056
9169
|
const selectedArtifacts = filterArtifactsBySelection(expandedArtifacts, rawNode.node.selected);
|
|
9057
9170
|
const runtimeSelectedSet = new Set(normalizeArtifactSelectors(rawNode.node.selected) ?? []);
|
|
9058
9171
|
const runtimeArtifacts = targetContext.adapter ? filterArtifactsByRuntime2(selectedArtifacts, targetContext.adapter.name, runtimeSelectedSet) : selectedArtifacts;
|
|
9059
|
-
const
|
|
9172
|
+
const claudeRenderedArtifacts = await renderClaudeSubagents(runtimeArtifacts, staged.root, targetContext.adapter);
|
|
9173
|
+
const codexRenderedArtifacts = await renderCodexSubagents(claudeRenderedArtifacts, staged.root, targetContext.adapter);
|
|
9060
9174
|
const openClawRenderedArtifacts = await renderOpenClawSubagents(codexRenderedArtifacts, staged.root, targetContext.adapter);
|
|
9061
9175
|
const runtimeRenderedArtifacts = await renderCopilotArtifacts(openClawRenderedArtifacts, staged.root, targetContext.adapter);
|
|
9062
9176
|
const renderedArtifacts = targetContext.workspaceRoot && targetContext.adapter ? await applyCustomizations(runtimeRenderedArtifacts, {
|
|
@@ -9105,7 +9219,7 @@ async function artifactContentMap(artifacts) {
|
|
|
9105
9219
|
const out = /* @__PURE__ */ new Map();
|
|
9106
9220
|
for (const artifact of artifacts) {
|
|
9107
9221
|
if (artifact.kind !== "file") continue;
|
|
9108
|
-
out.set(artifact.relativePath.replaceAll("\\", "/"), await
|
|
9222
|
+
out.set(artifact.relativePath.replaceAll("\\", "/"), await readFile26(artifact.stagedPath ?? artifact.sourcePath, "utf8"));
|
|
9109
9223
|
}
|
|
9110
9224
|
return out;
|
|
9111
9225
|
}
|
|
@@ -9368,9 +9482,9 @@ function lockArtifactFor(artifact) {
|
|
|
9368
9482
|
}
|
|
9369
9483
|
|
|
9370
9484
|
// src/lifecycle/trust.ts
|
|
9371
|
-
import { mkdir as
|
|
9485
|
+
import { mkdir as mkdir20, readFile as readFile27 } from "fs/promises";
|
|
9372
9486
|
import { homedir as homedir7 } from "os";
|
|
9373
|
-
import { dirname as
|
|
9487
|
+
import { dirname as dirname27, join as join35 } from "path";
|
|
9374
9488
|
import { z as z9 } from "zod";
|
|
9375
9489
|
var trustStoreSchema = z9.object({
|
|
9376
9490
|
version: z9.literal(1),
|
|
@@ -9444,14 +9558,14 @@ function sortedUnique5(values) {
|
|
|
9444
9558
|
}
|
|
9445
9559
|
async function readTrustStore(path) {
|
|
9446
9560
|
if (!await pathExists(path)) return { version: 1, acceptedSources: [] };
|
|
9447
|
-
return trustStoreSchema.parse(JSON.parse(await
|
|
9561
|
+
return trustStoreSchema.parse(JSON.parse(await readFile27(path, "utf8")));
|
|
9448
9562
|
}
|
|
9449
9563
|
async function writeTrustStore(path, store) {
|
|
9450
|
-
await
|
|
9564
|
+
await mkdir20(dirname27(path), { recursive: true });
|
|
9451
9565
|
await writeJsonAtomic(path, trustStoreSchema.parse(store));
|
|
9452
9566
|
}
|
|
9453
9567
|
function defaultTrustStorePath() {
|
|
9454
|
-
return process.env.AGENTWHEEL_TRUST_STORE ??
|
|
9568
|
+
return process.env.AGENTWHEEL_TRUST_STORE ?? join35(homedir7(), ".agentwheel", "trust.json");
|
|
9455
9569
|
}
|
|
9456
9570
|
|
|
9457
9571
|
// src/lifecycle/ownership.ts
|
|
@@ -9623,7 +9737,7 @@ async function createGraphSourcePlan(options) {
|
|
|
9623
9737
|
const registryClient = new RegistryClient({ workspaceRoot, offline: lockMode, offlineLabel: lockLabel, warn });
|
|
9624
9738
|
const graph = await resolveDependencyGraph(options.roots, {
|
|
9625
9739
|
workspaceRoot,
|
|
9626
|
-
cacheRoot:
|
|
9740
|
+
cacheRoot: join36(workspaceRoot, ".agentwheel", "cache"),
|
|
9627
9741
|
registryClient,
|
|
9628
9742
|
noDeps: options.noDeps,
|
|
9629
9743
|
includeSuggestions: options.includeSuggestions,
|
|
@@ -9729,7 +9843,7 @@ async function readExistingGraphLock(path) {
|
|
|
9729
9843
|
return readGraphLock(path);
|
|
9730
9844
|
}
|
|
9731
9845
|
function pathForGraphLock(workspaceRoot, targetKey2, adapter, targetFingerprint) {
|
|
9732
|
-
return
|
|
9846
|
+
return join36(workspaceRoot, ".agentwheel", "locks", sanitizePathSegment(targetKey2), sanitizePathSegment(adapter), `${targetFingerprint}.graph-lock.json`);
|
|
9733
9847
|
}
|
|
9734
9848
|
function sanitizePathSegment(value) {
|
|
9735
9849
|
return value.replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "default";
|
|
@@ -9861,7 +9975,7 @@ function targetLabel(target) {
|
|
|
9861
9975
|
}
|
|
9862
9976
|
|
|
9863
9977
|
// src/runtime/target.ts
|
|
9864
|
-
import { basename as
|
|
9978
|
+
import { basename as basename21, dirname as dirname29, join as join37, resolve as resolve17 } from "path";
|
|
9865
9979
|
var runtimeMarkers = [
|
|
9866
9980
|
{ adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
|
|
9867
9981
|
{ adapter: "claude", dirs: [".claude"] },
|
|
@@ -9920,6 +10034,9 @@ async function resolveProfileRuntimeTargets(request) {
|
|
|
9920
10034
|
if (!profile) {
|
|
9921
10035
|
throw new Error(`Unknown profile: ${request.profile}`);
|
|
9922
10036
|
}
|
|
10037
|
+
if (isCompositeWorkspaceProfile(profile)) {
|
|
10038
|
+
throw new Error(`Profile '${request.profile}' is composite and has no direct runtime targets.`);
|
|
10039
|
+
}
|
|
9923
10040
|
return profile.runtimes.map((runtime) => resolveProfileRuntimeTarget(runtime, config, workspaceRoot, request.installationType));
|
|
9924
10041
|
}
|
|
9925
10042
|
function resolveProfileRuntimeTarget(runtime, config, workspaceRoot, installationType) {
|
|
@@ -9978,9 +10095,9 @@ async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
|
|
|
9978
10095
|
for (const marker of runtimeMarkers) {
|
|
9979
10096
|
if (adapterFilter && marker.adapter !== adapterFilter) continue;
|
|
9980
10097
|
for (const dir of marker.dirs) {
|
|
9981
|
-
if (
|
|
9982
|
-
matches.push({ adapter: marker.adapter, targetRoot:
|
|
9983
|
-
} else if (await pathExists(
|
|
10098
|
+
if (basename21(root) === dir) {
|
|
10099
|
+
matches.push({ adapter: marker.adapter, targetRoot: dirname29(root) });
|
|
10100
|
+
} else if (await pathExists(join37(root, dir))) {
|
|
9984
10101
|
matches.push({ adapter: marker.adapter, targetRoot: root });
|
|
9985
10102
|
}
|
|
9986
10103
|
}
|
|
@@ -10022,7 +10139,7 @@ function dedupeTargets(matches) {
|
|
|
10022
10139
|
function runtimeScanRoot(request) {
|
|
10023
10140
|
const root = resolve17(request.targetRoot ?? request.cwd ?? process.cwd());
|
|
10024
10141
|
if (request.targetRoot) return root;
|
|
10025
|
-
return runtimeMarkers.some((marker) => marker.dirs.includes(
|
|
10142
|
+
return runtimeMarkers.some((marker) => marker.dirs.includes(basename21(root))) ? dirname29(root) : root;
|
|
10026
10143
|
}
|
|
10027
10144
|
|
|
10028
10145
|
// src/lifecycle/profile.ts
|
|
@@ -10032,6 +10149,9 @@ async function syncProfile(options) {
|
|
|
10032
10149
|
if (!profile) {
|
|
10033
10150
|
throw new Error(`Unknown profile: ${options.profile}`);
|
|
10034
10151
|
}
|
|
10152
|
+
if (isCompositeWorkspaceProfile(profile)) {
|
|
10153
|
+
throw new Error(`Composite profile '${options.profile}' must be executed through member delegation.`);
|
|
10154
|
+
}
|
|
10035
10155
|
const packages = options.source ? [await packageFromSource(options.source, options)] : config.packages;
|
|
10036
10156
|
if (packages.length === 0) {
|
|
10037
10157
|
throw new Error("Profile sync needs a source argument or configured packages.");
|
|
@@ -10059,6 +10179,7 @@ async function syncProfile(options) {
|
|
|
10059
10179
|
rootId: pkg.name,
|
|
10060
10180
|
source: pkg.source,
|
|
10061
10181
|
mode: options.mode ?? pkg.mode,
|
|
10182
|
+
version: pkg.version,
|
|
10062
10183
|
ref: pkg.requestedRef,
|
|
10063
10184
|
select: pkg.selection ? void 0 : selected ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
|
|
10064
10185
|
selection: pkg.selection,
|
|
@@ -10320,9 +10441,9 @@ function shellQuoteArg(value) {
|
|
|
10320
10441
|
}
|
|
10321
10442
|
|
|
10322
10443
|
// src/cli/update-check.ts
|
|
10323
|
-
import { mkdir as
|
|
10444
|
+
import { mkdir as mkdir22, readFile as readFile28, writeFile as writeFile20 } from "fs/promises";
|
|
10324
10445
|
import { homedir as homedir8 } from "os";
|
|
10325
|
-
import { dirname as
|
|
10446
|
+
import { dirname as dirname30, join as join38 } from "path";
|
|
10326
10447
|
var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
10327
10448
|
var DEFAULT_TIMEOUT_MS = 300;
|
|
10328
10449
|
var REGISTRY_URL = "https://registry.npmjs.org/agentwheel";
|
|
@@ -10330,7 +10451,7 @@ async function maybeCheckForUpdate(options) {
|
|
|
10330
10451
|
if (isDisabled(options)) return;
|
|
10331
10452
|
const now = options.now?.() ?? /* @__PURE__ */ new Date();
|
|
10332
10453
|
const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
10333
|
-
const cachePath = options.cachePath ??
|
|
10454
|
+
const cachePath = options.cachePath ?? join38(homedir8(), ".agentwheel", "update-check.json");
|
|
10334
10455
|
try {
|
|
10335
10456
|
const cached = await readCache(cachePath);
|
|
10336
10457
|
if (cached && now.getTime() - Date.parse(cached.checkedAt) < ttlMs) {
|
|
@@ -10367,7 +10488,7 @@ async function fetchLatestVersion(fetchImpl, timeoutMs) {
|
|
|
10367
10488
|
}
|
|
10368
10489
|
async function readCache(path) {
|
|
10369
10490
|
try {
|
|
10370
|
-
const parsed = JSON.parse(await
|
|
10491
|
+
const parsed = JSON.parse(await readFile28(path, "utf8"));
|
|
10371
10492
|
if (typeof parsed.checkedAt !== "string" || typeof parsed.latest !== "string") return void 0;
|
|
10372
10493
|
return { checkedAt: parsed.checkedAt, latest: parsed.latest };
|
|
10373
10494
|
} catch {
|
|
@@ -10375,8 +10496,8 @@ async function readCache(path) {
|
|
|
10375
10496
|
}
|
|
10376
10497
|
}
|
|
10377
10498
|
async function writeCache(path, cache) {
|
|
10378
|
-
await
|
|
10379
|
-
await
|
|
10499
|
+
await mkdir22(dirname30(path), { recursive: true });
|
|
10500
|
+
await writeFile20(path, `${JSON.stringify(cache, null, 2)}
|
|
10380
10501
|
`, "utf8");
|
|
10381
10502
|
}
|
|
10382
10503
|
function warnIfNewer(latest, current, stderr = process.stderr) {
|
|
@@ -10539,13 +10660,13 @@ function isCrossPackageSelector(value) {
|
|
|
10539
10660
|
}
|
|
10540
10661
|
|
|
10541
10662
|
// src/model/package-migrate.ts
|
|
10542
|
-
import { readFile as
|
|
10543
|
-
import { join as
|
|
10663
|
+
import { readFile as readFile29, rename as rename4, writeFile as writeFile21 } from "fs/promises";
|
|
10664
|
+
import { join as join40, resolve as resolve19 } from "path";
|
|
10544
10665
|
import { applyEdits, modify, parse as parse5 } from "jsonc-parser";
|
|
10545
10666
|
async function migratePackageManifest(root) {
|
|
10546
10667
|
const packageRoot = resolve19(root);
|
|
10547
10668
|
for (const name of openPackManifestNames) {
|
|
10548
|
-
const path =
|
|
10669
|
+
const path = join40(packageRoot, name);
|
|
10549
10670
|
if (await pathExists(path)) {
|
|
10550
10671
|
return { changed: false, to: path, message: `Package already uses ${name}.` };
|
|
10551
10672
|
}
|
|
@@ -10554,18 +10675,18 @@ async function migratePackageManifest(root) {
|
|
|
10554
10675
|
if (!legacyName) {
|
|
10555
10676
|
throw new Error(`No legacy package manifest found at ${packageRoot}`);
|
|
10556
10677
|
}
|
|
10557
|
-
const from =
|
|
10678
|
+
const from = join40(packageRoot, legacyName);
|
|
10558
10679
|
const toName = legacyName.endsWith(".jsonc") ? "openpack.jsonc" : "openpack.json";
|
|
10559
|
-
const to =
|
|
10560
|
-
const content = await
|
|
10680
|
+
const to = join40(packageRoot, toName);
|
|
10681
|
+
const content = await readFile29(from, "utf8");
|
|
10561
10682
|
const updated = updateSchemaVersion(content);
|
|
10562
10683
|
await rename4(from, to);
|
|
10563
|
-
await
|
|
10684
|
+
await writeFile21(to, updated, "utf8");
|
|
10564
10685
|
return { changed: true, from, to, message: `Migrated ${legacyName} to ${toName}.` };
|
|
10565
10686
|
}
|
|
10566
10687
|
async function firstExistingLegacyManifest(root) {
|
|
10567
10688
|
for (const name of legacyPackageManifestNames) {
|
|
10568
|
-
if (await pathExists(
|
|
10689
|
+
if (await pathExists(join40(root, name))) return name;
|
|
10569
10690
|
}
|
|
10570
10691
|
return void 0;
|
|
10571
10692
|
}
|
|
@@ -10583,38 +10704,1248 @@ function updateSchemaVersion(content) {
|
|
|
10583
10704
|
|
|
10584
10705
|
// src/cli/version.ts
|
|
10585
10706
|
import { readFileSync } from "fs";
|
|
10586
|
-
import { dirname as
|
|
10707
|
+
import { dirname as dirname31, join as join41 } from "path";
|
|
10587
10708
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10588
10709
|
var FALLBACK_VERSION = "0.0.0";
|
|
10589
10710
|
function resolveCliVersion() {
|
|
10590
|
-
let dir =
|
|
10711
|
+
let dir = dirname31(fileURLToPath2(import.meta.url));
|
|
10591
10712
|
while (true) {
|
|
10592
10713
|
try {
|
|
10593
|
-
const pkg = JSON.parse(readFileSync(
|
|
10714
|
+
const pkg = JSON.parse(readFileSync(join41(dir, "package.json"), "utf8"));
|
|
10594
10715
|
if (pkg.name === "agentwheel" && typeof pkg.version === "string") {
|
|
10595
10716
|
return pkg.version;
|
|
10596
10717
|
}
|
|
10597
10718
|
} catch {
|
|
10598
10719
|
}
|
|
10599
|
-
const parent =
|
|
10720
|
+
const parent = dirname31(dir);
|
|
10600
10721
|
if (parent === dir) return FALLBACK_VERSION;
|
|
10601
10722
|
dir = parent;
|
|
10602
10723
|
}
|
|
10603
10724
|
}
|
|
10604
10725
|
|
|
10605
|
-
// src/
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
10726
|
+
// src/version/policy.ts
|
|
10727
|
+
import { execFile as execFile5 } from "child_process";
|
|
10728
|
+
import { readFile as readFile30 } from "fs/promises";
|
|
10729
|
+
import { join as join42, resolve as resolve20 } from "path";
|
|
10730
|
+
import { promisify as promisify5 } from "util";
|
|
10731
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
10732
|
+
import { z as z10 } from "zod";
|
|
10733
|
+
var execFileAsync5 = promisify5(execFile5);
|
|
10734
|
+
var DEFAULT_VERSION_REFRESH_TTL_SECONDS = 86400;
|
|
10735
|
+
var cachedVersionSchema = z10.object({
|
|
10736
|
+
version: z10.string().min(1),
|
|
10737
|
+
ref: z10.string().min(1)
|
|
10738
|
+
});
|
|
10739
|
+
var versionCacheEntrySchema = z10.object({
|
|
10740
|
+
checkedAt: z10.string().datetime(),
|
|
10741
|
+
versions: z10.array(cachedVersionSchema)
|
|
10742
|
+
});
|
|
10743
|
+
var versionCacheSchema = z10.object({
|
|
10744
|
+
schemaVersion: z10.literal(1),
|
|
10745
|
+
sources: z10.record(z10.string(), versionCacheEntrySchema)
|
|
10746
|
+
});
|
|
10747
|
+
async function discoverPackageVersions(pkg, workspaceRoot, options = {}) {
|
|
10748
|
+
const now = (options.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
10749
|
+
const ttlSeconds = options.ttlSeconds ?? DEFAULT_VERSION_REFRESH_TTL_SECONDS;
|
|
10750
|
+
const cachePath = versionCachePath(workspaceRoot);
|
|
10751
|
+
const cache = await readVersionCache(cachePath);
|
|
10752
|
+
const cached = cache.sources[pkg.source];
|
|
10753
|
+
const cachedAgeMs = cached ? now.getTime() - new Date(cached.checkedAt).getTime() : Number.POSITIVE_INFINITY;
|
|
10754
|
+
const cachedFresh = cachedAgeMs <= ttlSeconds * 1e3;
|
|
10755
|
+
if (options.offline || cached && cachedFresh && !options.forceRefresh) {
|
|
10756
|
+
return availabilityFromVersions(pkg, cached?.versions ?? [], {
|
|
10757
|
+
checkedAt: cached?.checkedAt ?? null,
|
|
10758
|
+
stale: !cachedFresh,
|
|
10759
|
+
refreshed: false,
|
|
10760
|
+
error: !cached && options.offline ? "No cached version index is available offline." : void 0
|
|
10761
|
+
});
|
|
10762
|
+
}
|
|
10763
|
+
try {
|
|
10764
|
+
const versions = await discoverVersionsFromSource(pkg, workspaceRoot);
|
|
10765
|
+
const checkedAt = now.toISOString();
|
|
10766
|
+
await writeJsonAtomic(cachePath, {
|
|
10767
|
+
schemaVersion: 1,
|
|
10768
|
+
sources: {
|
|
10769
|
+
...cache.sources,
|
|
10770
|
+
[pkg.source]: { checkedAt, versions }
|
|
10771
|
+
}
|
|
10772
|
+
});
|
|
10773
|
+
return availabilityFromVersions(pkg, versions, {
|
|
10774
|
+
checkedAt,
|
|
10775
|
+
stale: false,
|
|
10776
|
+
refreshed: true
|
|
10777
|
+
});
|
|
10778
|
+
} catch (error) {
|
|
10779
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10780
|
+
return availabilityFromVersions(pkg, cached?.versions ?? [], {
|
|
10781
|
+
checkedAt: cached?.checkedAt ?? null,
|
|
10782
|
+
stale: true,
|
|
10783
|
+
refreshed: false,
|
|
10784
|
+
error: message
|
|
10785
|
+
});
|
|
10786
|
+
}
|
|
10787
|
+
}
|
|
10788
|
+
async function effectiveTrackingRef(pkg, workspaceRoot, options = {}) {
|
|
10789
|
+
if (pkg.mode !== "tracking" || !pkg.version) return { ref: pkg.requestedRef };
|
|
10790
|
+
const availability = await discoverPackageVersions(pkg, workspaceRoot, options);
|
|
10791
|
+
const driverName = pkg.driver === "local" ? inferSourceDriverName(pkg.source) : pkg.driver;
|
|
10792
|
+
return {
|
|
10793
|
+
ref: driverName === "local" ? pkg.requestedRef : availability.latestAllowedRef ?? pkg.requestedRef,
|
|
10794
|
+
availability
|
|
10795
|
+
};
|
|
10796
|
+
}
|
|
10797
|
+
function availabilityFromVersions(pkg, versions, state) {
|
|
10798
|
+
const sorted = [...versions].sort((a, b) => compareSemverStrings(b.version, a.version));
|
|
10799
|
+
const policy = pkg.version ?? "*";
|
|
10800
|
+
const latestOverall = sorted[0] ?? null;
|
|
10801
|
+
const latestAllowed = sorted.find((candidate) => satisfiesVersionRange(candidate.version, policy)) ?? null;
|
|
10802
|
+
return {
|
|
10803
|
+
source: pkg.source,
|
|
10804
|
+
policy,
|
|
10805
|
+
checkedAt: state.checkedAt,
|
|
10806
|
+
stale: state.stale,
|
|
10807
|
+
refreshed: state.refreshed,
|
|
10808
|
+
latestAllowed: latestAllowed?.version ?? null,
|
|
10809
|
+
latestAllowedRef: latestAllowed?.ref ?? null,
|
|
10810
|
+
latestOverall: latestOverall?.version ?? null,
|
|
10811
|
+
latestOverallRef: latestOverall?.ref ?? null,
|
|
10812
|
+
versions: sorted,
|
|
10813
|
+
...state.error ? { error: state.error } : {}
|
|
10814
|
+
};
|
|
10815
|
+
}
|
|
10816
|
+
async function discoverVersionsFromSource(pkg, workspaceRoot) {
|
|
10817
|
+
const driverName = pkg.driver === "local" ? inferSourceDriverName(pkg.source) : pkg.driver;
|
|
10818
|
+
if (driverName === "git") {
|
|
10819
|
+
const tagged = await discoverGitTags(pkg.source, pkg.version);
|
|
10820
|
+
if (tagged.length > 0) return tagged;
|
|
10821
|
+
}
|
|
10822
|
+
if (driverName === "local") {
|
|
10823
|
+
const root = resolve20(workspaceRoot, pkg.source);
|
|
10824
|
+
const manifest2 = await readPackageManifest(root);
|
|
10825
|
+
const current = manifest2 ? [{ version: manifest2.version, ref: pkg.requestedRef ?? root }] : [];
|
|
10826
|
+
try {
|
|
10827
|
+
const { stdout } = await execFileAsync5("git", ["-C", root, "remote", "get-url", "origin"]);
|
|
10828
|
+
return uniqueVersions([
|
|
10829
|
+
...await discoverGitTagsFromUrl(stdout.trim(), pkg.version, root),
|
|
10830
|
+
...current
|
|
10831
|
+
]);
|
|
10832
|
+
} catch {
|
|
10833
|
+
return current;
|
|
10834
|
+
}
|
|
10835
|
+
}
|
|
10836
|
+
const driver = getSourceDriver(driverName);
|
|
10837
|
+
const resolved = await driver.resolve(pkg.source, {
|
|
10838
|
+
cacheRoot: join42(workspaceRoot, ".agentwheel", "cache"),
|
|
10839
|
+
mode: "tracking",
|
|
10840
|
+
ref: pkg.requestedRef
|
|
10841
|
+
});
|
|
10842
|
+
const fetched = await driver.fetch(resolved);
|
|
10843
|
+
const manifest = await readPackageManifest(fetched.resolvedPath);
|
|
10844
|
+
const version = manifest?.version ?? fetched.packageVersion;
|
|
10845
|
+
return version ? [{ version, ref: fetched.requestedRef ?? pkg.requestedRef ?? "HEAD" }] : [];
|
|
10846
|
+
}
|
|
10847
|
+
async function discoverGitTags(source, policy) {
|
|
10848
|
+
const url = gitUrlFromSource(source);
|
|
10849
|
+
const localRoot = url.startsWith("/") ? url : void 0;
|
|
10850
|
+
return discoverGitTagsFromUrl(url, policy, localRoot);
|
|
10851
|
+
}
|
|
10852
|
+
async function discoverGitTagsFromUrl(url, policy, localRoot) {
|
|
10853
|
+
const { stdout } = await execFileAsync5("git", ["ls-remote", "--tags", "--refs", url], {
|
|
10854
|
+
maxBuffer: 10 * 1024 * 1024
|
|
10855
|
+
});
|
|
10856
|
+
const byVersion = /* @__PURE__ */ new Map();
|
|
10857
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
10858
|
+
const match = /^[0-9a-f]+\s+refs\/tags\/(.+)$/.exec(line.trim());
|
|
10859
|
+
if (!match) continue;
|
|
10860
|
+
const tag = match[1];
|
|
10861
|
+
if (!parseSemver(tag)) continue;
|
|
10862
|
+
const version = tag.replace(/^v/, "");
|
|
10863
|
+
const incumbent = byVersion.get(version);
|
|
10864
|
+
if (!incumbent || tag.startsWith("v")) byVersion.set(version, { version, ref: tag });
|
|
10865
|
+
}
|
|
10866
|
+
const candidates = [...byVersion.values()].sort((a, b) => compareSemverStrings(b.version, a.version));
|
|
10867
|
+
const valid = [];
|
|
10868
|
+
let foundOverall = false;
|
|
10869
|
+
let foundAllowed = false;
|
|
10870
|
+
for (const candidate of candidates) {
|
|
10871
|
+
const manifestVersion = await manifestVersionAtRef(url, candidate.ref, localRoot);
|
|
10872
|
+
if (manifestVersion !== candidate.version) continue;
|
|
10873
|
+
valid.push(candidate);
|
|
10874
|
+
foundOverall = true;
|
|
10875
|
+
if (satisfiesVersionRange(candidate.version, policy)) foundAllowed = true;
|
|
10876
|
+
if (foundOverall && foundAllowed) break;
|
|
10877
|
+
}
|
|
10878
|
+
return valid;
|
|
10879
|
+
}
|
|
10880
|
+
function uniqueVersions(versions) {
|
|
10881
|
+
const byVersion = /* @__PURE__ */ new Map();
|
|
10882
|
+
for (const version of versions) {
|
|
10883
|
+
if (!byVersion.has(version.version)) byVersion.set(version.version, version);
|
|
10884
|
+
}
|
|
10885
|
+
return [...byVersion.values()].sort((a, b) => compareSemverStrings(b.version, a.version));
|
|
10886
|
+
}
|
|
10887
|
+
async function manifestVersionAtRef(url, ref, localRoot) {
|
|
10888
|
+
if (localRoot) {
|
|
10889
|
+
for (const name of ["openpack.json", "openpack.jsonc"]) {
|
|
10890
|
+
try {
|
|
10891
|
+
const { stdout } = await execFileAsync5("git", ["-C", localRoot, "show", `${ref}:${name}`], {
|
|
10892
|
+
maxBuffer: 1024 * 1024
|
|
10893
|
+
});
|
|
10894
|
+
const parsed = parseJsonc(stdout);
|
|
10895
|
+
if (typeof parsed?.version === "string") return parsed.version.replace(/^v/, "");
|
|
10896
|
+
} catch {
|
|
10897
|
+
}
|
|
10898
|
+
}
|
|
10899
|
+
return null;
|
|
10900
|
+
}
|
|
10901
|
+
const repository = githubRepositoryFromUrl(url);
|
|
10902
|
+
if (!repository) return null;
|
|
10903
|
+
for (const name of ["openpack.json", "openpack.jsonc"]) {
|
|
10904
|
+
const response = await fetch(
|
|
10905
|
+
`https://raw.githubusercontent.com/${repository}/${encodeURIComponent(ref)}/${name}`,
|
|
10906
|
+
{ headers: { "user-agent": "agentwheel-version-discovery" } }
|
|
10907
|
+
);
|
|
10908
|
+
if (!response.ok) continue;
|
|
10909
|
+
const parsed = parseJsonc(await response.text());
|
|
10910
|
+
if (typeof parsed?.version === "string") return parsed.version.replace(/^v/, "");
|
|
10911
|
+
}
|
|
10912
|
+
return null;
|
|
10913
|
+
}
|
|
10914
|
+
function githubRepositoryFromUrl(url) {
|
|
10915
|
+
const match = /^(?:https:\/\/github\.com\/|git@github\.com:)([^/]+\/[^/#]+?)(?:\.git)?$/.exec(url);
|
|
10916
|
+
return match?.[1] ?? null;
|
|
10917
|
+
}
|
|
10918
|
+
function gitUrlFromSource(source) {
|
|
10919
|
+
if (source.startsWith("github:")) {
|
|
10920
|
+
const repo = source.slice("github:".length).split("#", 1)[0];
|
|
10921
|
+
if (!repo.includes("/")) throw new Error(`Invalid GitHub source: ${source}`);
|
|
10922
|
+
return `https://github.com/${repo}.git`;
|
|
10923
|
+
}
|
|
10924
|
+
if (source.startsWith("git:")) {
|
|
10925
|
+
const rest = source.slice("git:".length);
|
|
10926
|
+
const hashIndex = rest.lastIndexOf("#");
|
|
10927
|
+
return hashIndex >= 0 ? rest.slice(0, hashIndex) : rest;
|
|
10928
|
+
}
|
|
10929
|
+
throw new Error(`Version discovery does not support Git source: ${source}`);
|
|
10930
|
+
}
|
|
10931
|
+
function versionCachePath(workspaceRoot) {
|
|
10932
|
+
return join42(workspaceRoot, ".agentwheel", "cache", "version-index.json");
|
|
10933
|
+
}
|
|
10934
|
+
async function readVersionCache(path) {
|
|
10935
|
+
if (!await pathExists(path)) return { schemaVersion: 1, sources: {} };
|
|
10936
|
+
try {
|
|
10937
|
+
return versionCacheSchema.parse(JSON.parse(await readFile30(path, "utf8")));
|
|
10938
|
+
} catch {
|
|
10939
|
+
return { schemaVersion: 1, sources: {} };
|
|
10940
|
+
}
|
|
10941
|
+
}
|
|
10942
|
+
|
|
10943
|
+
// src/profile/members.ts
|
|
10944
|
+
import { execFile as execFile6 } from "child_process";
|
|
10945
|
+
import { readFile as readFile31 } from "fs/promises";
|
|
10946
|
+
import { join as join43, resolve as resolve21 } from "path";
|
|
10947
|
+
import { promisify as promisify6 } from "util";
|
|
10948
|
+
import { z as z12 } from "zod";
|
|
10949
|
+
|
|
10950
|
+
// src/status/report.ts
|
|
10951
|
+
import { z as z11 } from "zod";
|
|
10952
|
+
var statusHealthSchema = z11.enum([
|
|
10953
|
+
"PASS",
|
|
10954
|
+
"WARN",
|
|
10955
|
+
"FAIL",
|
|
10956
|
+
"STALE",
|
|
10957
|
+
"DEGRADED",
|
|
10958
|
+
"INCOMPATIBLE",
|
|
10959
|
+
"BUSY"
|
|
10960
|
+
]);
|
|
10961
|
+
var statusPackageSchema = z11.object({
|
|
10962
|
+
name: z11.string().min(1),
|
|
10963
|
+
source: z11.string().min(1),
|
|
10964
|
+
mode: z11.enum(["pinned", "tracking"]),
|
|
10965
|
+
policy: z11.string().min(1),
|
|
10966
|
+
installed: z11.string().nullable(),
|
|
10967
|
+
locked: z11.string().nullable(),
|
|
10968
|
+
latestAllowed: z11.string().nullable(),
|
|
10969
|
+
latestOverall: z11.string().nullable(),
|
|
10970
|
+
availability: z11.enum(["FRESH", "STALE", "UNKNOWN"]),
|
|
10971
|
+
checkedAt: z11.string().nullable(),
|
|
10972
|
+
error: z11.string().optional(),
|
|
10973
|
+
updateAvailableAllowed: z11.boolean(),
|
|
10974
|
+
updateAvailableOverall: z11.boolean()
|
|
10975
|
+
});
|
|
10976
|
+
var statusArtifactSchema = z11.object({
|
|
10977
|
+
selector: z11.string().min(1),
|
|
10978
|
+
type: z11.string().min(1),
|
|
10979
|
+
name: z11.string().min(1),
|
|
10980
|
+
installName: z11.string().min(1),
|
|
10981
|
+
packageName: z11.string().nullable(),
|
|
10982
|
+
packageVersion: z11.string().nullable(),
|
|
10983
|
+
hash: z11.string().min(16),
|
|
10984
|
+
installed: z11.boolean()
|
|
10985
|
+
});
|
|
10986
|
+
var statusTargetSchema = z11.object({
|
|
10987
|
+
adapter: z11.string().min(1),
|
|
10988
|
+
installationType: z11.string().min(1),
|
|
10989
|
+
targetRoot: z11.string().min(1),
|
|
10990
|
+
health: statusHealthSchema,
|
|
10991
|
+
manifestRevision: z11.string().nullable(),
|
|
10992
|
+
manifestEntryCount: z11.number().int().nonnegative(),
|
|
10993
|
+
graphLockPath: z11.string().nullable(),
|
|
10994
|
+
packageCount: z11.number().int().nonnegative(),
|
|
10995
|
+
artifactCount: z11.number().int().nonnegative(),
|
|
10996
|
+
pendingCount: z11.number().int().nonnegative(),
|
|
10997
|
+
driftCount: z11.number().int().nonnegative(),
|
|
10998
|
+
conflictCount: z11.number().int().nonnegative(),
|
|
10999
|
+
error: z11.string().optional(),
|
|
11000
|
+
packages: z11.array(statusPackageSchema),
|
|
11001
|
+
artifacts: z11.array(statusArtifactSchema)
|
|
11002
|
+
});
|
|
11003
|
+
var statusReportSchema = z11.lazy(() => z11.object({
|
|
11004
|
+
schemaVersion: z11.literal(1),
|
|
11005
|
+
command: z11.literal("status"),
|
|
11006
|
+
agentwheelVersion: z11.string().min(1),
|
|
11007
|
+
generatedAt: z11.string().datetime(),
|
|
11008
|
+
workspace: z11.string().min(1),
|
|
11009
|
+
profile: z11.string().nullable(),
|
|
11010
|
+
health: statusHealthSchema,
|
|
11011
|
+
repository: z11.object({
|
|
11012
|
+
available: z11.boolean(),
|
|
11013
|
+
branch: z11.string().nullable(),
|
|
11014
|
+
head: z11.string().nullable(),
|
|
11015
|
+
upstream: z11.string().nullable(),
|
|
11016
|
+
ahead: z11.number().int().nonnegative(),
|
|
11017
|
+
behind: z11.number().int().nonnegative(),
|
|
11018
|
+
dirtyCount: z11.number().int().nonnegative(),
|
|
11019
|
+
error: z11.string().optional()
|
|
11020
|
+
}),
|
|
11021
|
+
targets: z11.array(statusTargetSchema),
|
|
11022
|
+
members: z11.array(z11.object({
|
|
11023
|
+
id: z11.string().min(1),
|
|
11024
|
+
transport: z11.enum(["local", "ssh"]),
|
|
11025
|
+
workspace: z11.string().min(1),
|
|
11026
|
+
profile: z11.string().min(1),
|
|
11027
|
+
health: statusHealthSchema,
|
|
11028
|
+
agentwheelVersion: z11.string().nullable(),
|
|
11029
|
+
checkedAt: z11.string().nullable(),
|
|
11030
|
+
stale: z11.boolean(),
|
|
11031
|
+
error: z11.string().optional(),
|
|
11032
|
+
report: statusReportSchema.optional()
|
|
11033
|
+
}))
|
|
11034
|
+
}));
|
|
11035
|
+
var HEALTH_RANK = {
|
|
11036
|
+
PASS: 0,
|
|
11037
|
+
WARN: 1,
|
|
11038
|
+
STALE: 2,
|
|
11039
|
+
DEGRADED: 3,
|
|
11040
|
+
BUSY: 4,
|
|
11041
|
+
INCOMPATIBLE: 5,
|
|
11042
|
+
FAIL: 6
|
|
11043
|
+
};
|
|
11044
|
+
function worstStatusHealth(values) {
|
|
11045
|
+
return values.reduce(
|
|
11046
|
+
(worst, value) => HEALTH_RANK[value] > HEALTH_RANK[worst] ? value : worst,
|
|
11047
|
+
"PASS"
|
|
11048
|
+
);
|
|
11049
|
+
}
|
|
11050
|
+
function blocksCompositeApply(health) {
|
|
11051
|
+
return !["PASS", "WARN"].includes(health);
|
|
11052
|
+
}
|
|
11053
|
+
|
|
11054
|
+
// src/profile/members.ts
|
|
11055
|
+
var execFileAsync6 = promisify6(execFile6);
|
|
11056
|
+
var memberCacheSchema = z12.object({
|
|
11057
|
+
schemaVersion: z12.literal(1),
|
|
11058
|
+
checkedAt: z12.string().datetime(),
|
|
11059
|
+
report: statusReportSchema
|
|
11060
|
+
});
|
|
11061
|
+
async function collectCompositeMembers(options) {
|
|
11062
|
+
const chain = [...options.chain ?? [], compositeKey(options.workspaceRoot, options.profileName)];
|
|
11063
|
+
const results = [];
|
|
11064
|
+
for (const member of options.members) {
|
|
11065
|
+
results.push(await collectMember(member, options, chain));
|
|
11066
|
+
}
|
|
11067
|
+
return results;
|
|
11068
|
+
}
|
|
11069
|
+
async function collectMember(member, options, chain) {
|
|
11070
|
+
const cachePath = memberCachePath(options.workspaceRoot, options.profileName, member.id);
|
|
11071
|
+
const cached = await readMemberCache(cachePath);
|
|
11072
|
+
const ttlSeconds = member.refreshTtlSeconds ?? options.profileTtlSeconds;
|
|
11073
|
+
const ageMs = cached ? Date.now() - new Date(cached.checkedAt).getTime() : Number.POSITIVE_INFINITY;
|
|
11074
|
+
const fresh = ageMs <= ttlSeconds * 1e3;
|
|
11075
|
+
if (options.offline || cached && fresh && !options.refresh) {
|
|
11076
|
+
if (!cached) {
|
|
11077
|
+
return memberFailure(member, "STALE", "No cached member status is available offline.");
|
|
11078
|
+
}
|
|
11079
|
+
return memberFromReport(member, cached.report, {
|
|
11080
|
+
checkedAt: cached.checkedAt,
|
|
11081
|
+
stale: options.offline || !fresh,
|
|
11082
|
+
health: options.offline || !fresh ? worstStatusHealth([cached.report.health, "STALE"]) : cached.report.health
|
|
11083
|
+
});
|
|
11084
|
+
}
|
|
11085
|
+
try {
|
|
11086
|
+
const report = await invokeMemberStatus(member, options.workspaceRoot, chain, {
|
|
11087
|
+
refresh: options.refresh || !fresh,
|
|
11088
|
+
offline: false
|
|
11089
|
+
}, options.cliEntry);
|
|
11090
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11091
|
+
await writeJsonAtomic(cachePath, { schemaVersion: 1, checkedAt, report });
|
|
11092
|
+
const versionHealth = report.agentwheelVersion === options.cliVersion ? "PASS" : "WARN";
|
|
11093
|
+
return memberFromReport(member, report, {
|
|
11094
|
+
checkedAt,
|
|
11095
|
+
stale: false,
|
|
11096
|
+
health: worstStatusHealth([report.health, versionHealth])
|
|
11097
|
+
});
|
|
11098
|
+
} catch (error) {
|
|
11099
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11100
|
+
const incompatible = /Incompatible member status protocol|Unknown option.*--json|Unknown command.*status/i.test(message);
|
|
11101
|
+
if (cached) {
|
|
11102
|
+
return memberFromReport(member, cached.report, {
|
|
11103
|
+
checkedAt: cached.checkedAt,
|
|
11104
|
+
stale: true,
|
|
11105
|
+
health: incompatible ? "INCOMPATIBLE" : "DEGRADED",
|
|
11106
|
+
error: message
|
|
11107
|
+
});
|
|
11108
|
+
}
|
|
11109
|
+
return memberFailure(member, incompatible ? "INCOMPATIBLE" : "FAIL", message);
|
|
11110
|
+
}
|
|
11111
|
+
}
|
|
11112
|
+
async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEntry = process.argv[1]) {
|
|
11113
|
+
const args = ["--no-update-check", "status", "--profile", member.profile, "--json"];
|
|
11114
|
+
if (options.refresh) args.push("--refresh");
|
|
11115
|
+
if (options.offline) args.push("--offline");
|
|
11116
|
+
const env = { ...process.env, AGENTWHEEL_COMPOSITE_CHAIN: JSON.stringify(chain) };
|
|
11117
|
+
let stdout = "";
|
|
11118
|
+
let stderr = "";
|
|
11119
|
+
try {
|
|
11120
|
+
if (member.transport === "local") {
|
|
11121
|
+
const workspace = resolve21(parentWorkspace, member.workspace);
|
|
11122
|
+
const result = await execFileAsync6(process.execPath, [cliEntry, ...args], {
|
|
11123
|
+
cwd: workspace,
|
|
11124
|
+
env,
|
|
11125
|
+
maxBuffer: 20 * 1024 * 1024
|
|
11126
|
+
});
|
|
11127
|
+
stdout = result.stdout;
|
|
11128
|
+
stderr = result.stderr;
|
|
11129
|
+
} else {
|
|
11130
|
+
const sshArgs = sshArguments(member);
|
|
11131
|
+
const remoteArgs = [
|
|
11132
|
+
`cd ${shellQuote(member.workspace)}`,
|
|
11133
|
+
"&&",
|
|
11134
|
+
`AGENTWHEEL_COMPOSITE_CHAIN=${shellQuote(JSON.stringify(chain))}`,
|
|
11135
|
+
"agentwheel",
|
|
11136
|
+
...args.map(shellQuote)
|
|
11137
|
+
];
|
|
11138
|
+
const result = await execFileAsync6("ssh", [...sshArgs, remoteArgs.join(" ")], {
|
|
11139
|
+
env,
|
|
11140
|
+
maxBuffer: 20 * 1024 * 1024
|
|
11141
|
+
});
|
|
11142
|
+
stdout = result.stdout;
|
|
11143
|
+
stderr = result.stderr;
|
|
11144
|
+
}
|
|
11145
|
+
} catch (error) {
|
|
11146
|
+
if (typeof error === "object" && error !== null) {
|
|
11147
|
+
stdout = "stdout" in error ? String(error.stdout ?? "") : "";
|
|
11148
|
+
stderr = "stderr" in error ? String(error.stderr ?? "") : "";
|
|
11149
|
+
}
|
|
11150
|
+
if (!stdout.trim()) throw error;
|
|
11151
|
+
}
|
|
11152
|
+
try {
|
|
11153
|
+
return statusReportSchema.parse(JSON.parse(stdout));
|
|
11154
|
+
} catch (error) {
|
|
11155
|
+
const detail = stderr.trim() || (error instanceof Error ? error.message : String(error));
|
|
11156
|
+
throw new Error(`Incompatible member status protocol for ${member.id}: ${detail}`);
|
|
11157
|
+
}
|
|
11158
|
+
}
|
|
11159
|
+
async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
|
|
11160
|
+
const env = { ...process.env, AGENTWHEEL_COMPOSITE_CHAIN: JSON.stringify(chain) };
|
|
11161
|
+
try {
|
|
11162
|
+
if (member.transport === "local") {
|
|
11163
|
+
const result2 = await execFileAsync6(
|
|
11164
|
+
process.execPath,
|
|
11165
|
+
[process.argv[1], "--no-update-check", ...args],
|
|
11166
|
+
{
|
|
11167
|
+
cwd: resolve21(parentWorkspace, member.workspace),
|
|
11168
|
+
env,
|
|
11169
|
+
maxBuffer: 20 * 1024 * 1024
|
|
11170
|
+
}
|
|
11171
|
+
);
|
|
11172
|
+
return { stdout: result2.stdout, stderr: result2.stderr };
|
|
11173
|
+
}
|
|
11174
|
+
const remoteArgs = [
|
|
11175
|
+
`cd ${shellQuote(member.workspace)}`,
|
|
11176
|
+
"&&",
|
|
11177
|
+
`AGENTWHEEL_COMPOSITE_CHAIN=${shellQuote(JSON.stringify(chain))}`,
|
|
11178
|
+
"agentwheel",
|
|
11179
|
+
"--no-update-check",
|
|
11180
|
+
...args.map(shellQuote)
|
|
11181
|
+
];
|
|
11182
|
+
const result = await execFileAsync6("ssh", [...sshArguments(member), remoteArgs.join(" ")], {
|
|
11183
|
+
env,
|
|
11184
|
+
maxBuffer: 20 * 1024 * 1024
|
|
11185
|
+
});
|
|
11186
|
+
return { stdout: result.stdout, stderr: result.stderr };
|
|
11187
|
+
} catch (error) {
|
|
11188
|
+
const detail = commandErrorDetail(error);
|
|
11189
|
+
if (/lock|busy|timed out waiting/i.test(detail)) {
|
|
11190
|
+
throw new Error(`BUSY ${member.id}: ${detail}`);
|
|
11191
|
+
}
|
|
11192
|
+
throw new Error(`Member ${member.id} command failed: ${detail}`);
|
|
11193
|
+
}
|
|
11194
|
+
}
|
|
11195
|
+
function sshArguments(member) {
|
|
11196
|
+
const destination = member.user ? `${member.user}@${member.host}` : member.host;
|
|
11197
|
+
return [
|
|
11198
|
+
...member.port ? ["-p", String(member.port)] : [],
|
|
11199
|
+
...member.identityFile ? ["-i", member.identityFile] : [],
|
|
11200
|
+
"--",
|
|
11201
|
+
destination
|
|
11202
|
+
];
|
|
11203
|
+
}
|
|
11204
|
+
function shellQuote(value) {
|
|
11205
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
11206
|
+
}
|
|
11207
|
+
function commandErrorDetail(error) {
|
|
11208
|
+
if (typeof error === "object" && error !== null) {
|
|
11209
|
+
const stderr = "stderr" in error ? String(error.stderr).trim() : "";
|
|
11210
|
+
if (stderr) return stderr;
|
|
11211
|
+
}
|
|
11212
|
+
return error instanceof Error ? error.message : String(error);
|
|
11213
|
+
}
|
|
11214
|
+
function memberFromReport(member, report, state) {
|
|
11215
|
+
return {
|
|
11216
|
+
id: member.id,
|
|
11217
|
+
transport: member.transport,
|
|
11218
|
+
workspace: member.workspace,
|
|
11219
|
+
profile: member.profile,
|
|
11220
|
+
health: state.health,
|
|
11221
|
+
agentwheelVersion: report.agentwheelVersion,
|
|
11222
|
+
checkedAt: state.checkedAt,
|
|
11223
|
+
stale: state.stale,
|
|
11224
|
+
...state.error ? { error: state.error } : {},
|
|
11225
|
+
report
|
|
11226
|
+
};
|
|
11227
|
+
}
|
|
11228
|
+
function memberFailure(member, health, error) {
|
|
11229
|
+
return {
|
|
11230
|
+
id: member.id,
|
|
11231
|
+
transport: member.transport,
|
|
11232
|
+
workspace: member.workspace,
|
|
11233
|
+
profile: member.profile,
|
|
11234
|
+
health,
|
|
11235
|
+
agentwheelVersion: null,
|
|
11236
|
+
checkedAt: null,
|
|
11237
|
+
stale: true,
|
|
11238
|
+
error
|
|
11239
|
+
};
|
|
11240
|
+
}
|
|
11241
|
+
function memberCachePath(workspaceRoot, profileName, memberId) {
|
|
11242
|
+
return join43(workspaceRoot, ".agentwheel", "cache", "member-status", profileName, `${memberId}.json`);
|
|
11243
|
+
}
|
|
11244
|
+
async function readMemberCache(path) {
|
|
11245
|
+
if (!await pathExists(path)) return void 0;
|
|
11246
|
+
try {
|
|
11247
|
+
return memberCacheSchema.parse(JSON.parse(await readFile31(path, "utf8")));
|
|
11248
|
+
} catch {
|
|
11249
|
+
return void 0;
|
|
11250
|
+
}
|
|
11251
|
+
}
|
|
11252
|
+
function parseCompositeChain() {
|
|
11253
|
+
const value = process.env.AGENTWHEEL_COMPOSITE_CHAIN;
|
|
11254
|
+
if (!value) return [];
|
|
11255
|
+
try {
|
|
11256
|
+
return z12.array(z12.string()).parse(JSON.parse(value));
|
|
11257
|
+
} catch {
|
|
11258
|
+
throw new Error("Invalid AGENTWHEEL_COMPOSITE_CHAIN protocol value.");
|
|
11259
|
+
}
|
|
11260
|
+
}
|
|
11261
|
+
function assertNoCompositeCycle(workspaceRoot, profileName, chain) {
|
|
11262
|
+
const key = compositeKey(workspaceRoot, profileName);
|
|
11263
|
+
if (chain.includes(key)) {
|
|
11264
|
+
throw new Error(`Composite profile cycle detected: ${[...chain, key].join(" -> ")}`);
|
|
11265
|
+
}
|
|
11266
|
+
}
|
|
11267
|
+
function compositeKey(workspaceRoot, profileName) {
|
|
11268
|
+
return `${resolve21(workspaceRoot)}#${profileName}`;
|
|
11269
|
+
}
|
|
11270
|
+
|
|
11271
|
+
// src/status/repository.ts
|
|
11272
|
+
import { execFile as execFile7 } from "child_process";
|
|
11273
|
+
import { promisify as promisify7 } from "util";
|
|
11274
|
+
var execFileAsync7 = promisify7(execFile7);
|
|
11275
|
+
async function collectRepositoryStatus(workspaceRoot) {
|
|
11276
|
+
try {
|
|
11277
|
+
const { stdout } = await execFileAsync7(
|
|
11278
|
+
"git",
|
|
11279
|
+
["-C", workspaceRoot, "status", "--porcelain=v2", "--branch"],
|
|
11280
|
+
{ maxBuffer: 10 * 1024 * 1024 }
|
|
11281
|
+
);
|
|
11282
|
+
const lines = stdout.split(/\r?\n/).filter(Boolean);
|
|
11283
|
+
const branch = valueAfter(lines, "# branch.head ");
|
|
11284
|
+
const head = valueAfter(lines, "# branch.oid ");
|
|
11285
|
+
const upstream = valueAfter(lines, "# branch.upstream ");
|
|
11286
|
+
const ab = valueAfter(lines, "# branch.ab ");
|
|
11287
|
+
const match = ab ? /^\+(\d+)\s+-(\d+)$/.exec(ab) : void 0;
|
|
11288
|
+
return {
|
|
11289
|
+
available: true,
|
|
11290
|
+
branch: branch === "(detached)" ? null : branch,
|
|
11291
|
+
head: head === "(initial)" ? null : head,
|
|
11292
|
+
upstream,
|
|
11293
|
+
ahead: match ? Number(match[1]) : 0,
|
|
11294
|
+
behind: match ? Number(match[2]) : 0,
|
|
11295
|
+
dirtyCount: lines.filter((line) => !line.startsWith("# ")).length
|
|
11296
|
+
};
|
|
11297
|
+
} catch (error) {
|
|
11298
|
+
return {
|
|
11299
|
+
available: false,
|
|
11300
|
+
branch: null,
|
|
11301
|
+
head: null,
|
|
11302
|
+
upstream: null,
|
|
11303
|
+
ahead: 0,
|
|
11304
|
+
behind: 0,
|
|
11305
|
+
dirtyCount: 0,
|
|
11306
|
+
error: error instanceof Error ? error.message : String(error)
|
|
11307
|
+
};
|
|
11308
|
+
}
|
|
11309
|
+
}
|
|
11310
|
+
function valueAfter(lines, prefix) {
|
|
11311
|
+
return lines.find((line) => line.startsWith(prefix))?.slice(prefix.length).trim() ?? null;
|
|
11312
|
+
}
|
|
11313
|
+
|
|
11314
|
+
// src/catalogue/client.ts
|
|
11315
|
+
import { createHash as createHash11 } from "crypto";
|
|
11316
|
+
import { readFile as readFile32, rm as rm11 } from "fs/promises";
|
|
11317
|
+
import { homedir as homedir9 } from "os";
|
|
11318
|
+
import { join as join44 } from "path";
|
|
11319
|
+
|
|
11320
|
+
// src/model/catalogue.ts
|
|
11321
|
+
import { z as z13 } from "zod";
|
|
11322
|
+
var searchScopeSchema = z13.enum(["all", "registry", "enriched", "vercel"]);
|
|
11323
|
+
var searchTypeSchema = z13.enum(["package", "skill", "plugin", "mcp", "adapter"]);
|
|
11324
|
+
var searchEcosystemSchema = z13.enum([
|
|
11325
|
+
"official",
|
|
11326
|
+
"openpack",
|
|
11327
|
+
"mcp-registry",
|
|
11328
|
+
"clawhub",
|
|
11329
|
+
"skillkit",
|
|
11330
|
+
"vercel"
|
|
11331
|
+
]);
|
|
11332
|
+
var catalogueProvenanceSchema = z13.enum(["registry", "enriched", "vercel"]);
|
|
11333
|
+
var installabilitySchema = z13.enum(["registry", "source", "informational"]);
|
|
11334
|
+
var nullableString = z13.string().nullable();
|
|
11335
|
+
var nullableStringArray = z13.array(z13.string()).nullable();
|
|
11336
|
+
var enrichedCatalogueEntrySchema = z13.object({
|
|
11337
|
+
id: z13.string().min(1),
|
|
11338
|
+
name: z13.string().min(1),
|
|
11339
|
+
ecosystem: searchEcosystemSchema.nullable(),
|
|
11340
|
+
type: searchTypeSchema.nullable(),
|
|
11341
|
+
description: nullableString,
|
|
11342
|
+
tags: nullableStringArray,
|
|
11343
|
+
source: nullableString,
|
|
11344
|
+
installCommand: nullableString,
|
|
11345
|
+
repoUrl: nullableString,
|
|
11346
|
+
homepageUrl: nullableString.optional(),
|
|
11347
|
+
homepageLinkLabel: nullableString.optional(),
|
|
11348
|
+
stars: z13.number().finite().nullable().optional(),
|
|
11349
|
+
lastPush: nullableString.optional(),
|
|
11350
|
+
archived: z13.boolean().nullable(),
|
|
11351
|
+
provides: nullableStringArray,
|
|
11352
|
+
version: nullableString,
|
|
11353
|
+
featured: z13.boolean().nullable().optional()
|
|
11354
|
+
});
|
|
11355
|
+
var enrichedCatalogueSchema = z13.object({
|
|
11356
|
+
schemaVersion: z13.literal(1),
|
|
11357
|
+
generatedAt: z13.string().datetime(),
|
|
11358
|
+
entries: z13.array(enrichedCatalogueEntrySchema)
|
|
11359
|
+
}).superRefine((value, context) => {
|
|
11360
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11361
|
+
value.entries.forEach((entry, index) => {
|
|
11362
|
+
if (seen.has(entry.id)) {
|
|
11363
|
+
context.addIssue({
|
|
11364
|
+
code: "custom",
|
|
11365
|
+
path: ["entries", index, "id"],
|
|
11366
|
+
message: `duplicate catalogue id: ${entry.id}`
|
|
11367
|
+
});
|
|
11368
|
+
}
|
|
11369
|
+
seen.add(entry.id);
|
|
11370
|
+
});
|
|
11371
|
+
});
|
|
11372
|
+
var vercelCatalogueEntrySchema = z13.object({
|
|
11373
|
+
o: z13.string().min(1),
|
|
11374
|
+
r: z13.string().min(1),
|
|
11375
|
+
s: z13.string().min(1),
|
|
11376
|
+
d: z13.string().nullable().optional()
|
|
11377
|
+
});
|
|
11378
|
+
var vercelCatalogueSchema = z13.object({
|
|
11379
|
+
schemaVersion: z13.literal(1),
|
|
11380
|
+
generatedAt: z13.string().datetime(),
|
|
11381
|
+
count: z13.number().int().nonnegative(),
|
|
11382
|
+
entries: z13.array(vercelCatalogueEntrySchema)
|
|
11383
|
+
}).superRefine((value, context) => {
|
|
11384
|
+
if (value.count !== value.entries.length) {
|
|
11385
|
+
context.addIssue({
|
|
11386
|
+
code: "custom",
|
|
11387
|
+
path: ["count"],
|
|
11388
|
+
message: `count must equal entries length (${value.entries.length})`
|
|
11389
|
+
});
|
|
11390
|
+
}
|
|
11391
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11392
|
+
value.entries.forEach((entry, index) => {
|
|
11393
|
+
const id = `${entry.o}/${entry.r}/${entry.s}`;
|
|
11394
|
+
if (seen.has(id)) {
|
|
11395
|
+
context.addIssue({
|
|
11396
|
+
code: "custom",
|
|
11397
|
+
path: ["entries", index],
|
|
11398
|
+
message: `duplicate Vercel catalogue id: ${id}`
|
|
11399
|
+
});
|
|
11400
|
+
}
|
|
11401
|
+
seen.add(id);
|
|
11402
|
+
});
|
|
11403
|
+
});
|
|
11404
|
+
var catalogueCacheSchema = z13.object({
|
|
11405
|
+
version: z13.literal(1),
|
|
11406
|
+
fetchedAt: z13.string().datetime(),
|
|
11407
|
+
sources: z13.tuple([z13.string().url(), z13.string().url()]),
|
|
11408
|
+
enriched: enrichedCatalogueSchema,
|
|
11409
|
+
vercel: vercelCatalogueSchema
|
|
11410
|
+
});
|
|
11411
|
+
var catalogueCacheEnvelopeSchema = z13.object({
|
|
11412
|
+
version: z13.literal(1),
|
|
11413
|
+
fetchedAt: z13.string().datetime(),
|
|
11414
|
+
sources: z13.tuple([z13.string().url(), z13.string().url()]),
|
|
11415
|
+
contentHash: z13.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
11416
|
+
enriched: z13.unknown(),
|
|
11417
|
+
vercel: z13.unknown()
|
|
11418
|
+
});
|
|
11419
|
+
var searchResultSchema = z13.object({
|
|
11420
|
+
id: z13.string().min(1),
|
|
11421
|
+
name: z13.string().min(1),
|
|
11422
|
+
description: z13.string(),
|
|
11423
|
+
type: searchTypeSchema,
|
|
11424
|
+
ecosystem: searchEcosystemSchema.optional(),
|
|
11425
|
+
tags: z13.array(z13.string()),
|
|
11426
|
+
provides: z13.array(z13.string()),
|
|
11427
|
+
source: z13.string().min(1).optional(),
|
|
11428
|
+
repoUrl: z13.string().min(1).optional(),
|
|
11429
|
+
installCommand: z13.string().min(1).optional(),
|
|
11430
|
+
installability: installabilitySchema,
|
|
11431
|
+
provenances: z13.array(catalogueProvenanceSchema).min(1),
|
|
11432
|
+
score: z13.number().int().nonnegative(),
|
|
11433
|
+
matchedFields: z13.array(z13.string())
|
|
11434
|
+
});
|
|
11435
|
+
var searchResponseSchema = z13.object({
|
|
11436
|
+
schemaVersion: z13.literal(1),
|
|
11437
|
+
query: z13.string(),
|
|
11438
|
+
scope: searchScopeSchema,
|
|
11439
|
+
fromCache: z13.boolean(),
|
|
11440
|
+
results: z13.array(searchResultSchema)
|
|
11441
|
+
});
|
|
11442
|
+
|
|
11443
|
+
// src/catalogue/client.ts
|
|
11444
|
+
var DEFAULT_ENRICHED_CATALOGUE_URL = "https://raw.githubusercontent.com/NestDevLab/agentwheel-registry/main/catalogue-data.json";
|
|
11445
|
+
var DEFAULT_VERCEL_CATALOGUE_URL = "https://raw.githubusercontent.com/NestDevLab/agentwheel-registry/main/catalogue-vercel-index.json";
|
|
11446
|
+
var DEFAULT_CATALOGUE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
11447
|
+
var MAX_CATALOGUE_PAYLOAD_BYTES = 32 * 1024 * 1024;
|
|
11448
|
+
var CatalogueClient = class {
|
|
11449
|
+
constructor(options = {}) {
|
|
11450
|
+
this.options = options;
|
|
11451
|
+
this.cachePath = options.cachePath ?? defaultCatalogueCachePath();
|
|
11452
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
11453
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
11454
|
+
this.sources = [
|
|
11455
|
+
options.enrichedUrl ?? DEFAULT_ENRICHED_CATALOGUE_URL,
|
|
11456
|
+
options.vercelUrl ?? DEFAULT_VERCEL_CATALOGUE_URL
|
|
11457
|
+
];
|
|
11458
|
+
}
|
|
11459
|
+
options;
|
|
11460
|
+
cachePath;
|
|
11461
|
+
now;
|
|
11462
|
+
fetchImpl;
|
|
11463
|
+
sources;
|
|
11464
|
+
async getIndex(options = {}) {
|
|
11465
|
+
const cached = await this.readCache();
|
|
11466
|
+
const usableCache = cached && sameSources2(cached.sources, this.sources) ? cached : void 0;
|
|
11467
|
+
const expired = usableCache ? this.isExpired(usableCache) : false;
|
|
11468
|
+
if (this.options.offline) {
|
|
11469
|
+
if (!usableCache) {
|
|
11470
|
+
throw new Error("Offline catalogue cache is missing. Run without --offline first.");
|
|
11471
|
+
}
|
|
11472
|
+
const stale = expired;
|
|
11473
|
+
this.options.warn?.(
|
|
11474
|
+
stale ? "Offline: using stale catalogue cache because refresh is disabled." : "Offline: using cached catalogue data."
|
|
11475
|
+
);
|
|
11476
|
+
return this.fromCache(usableCache, stale);
|
|
11477
|
+
}
|
|
11478
|
+
if (!options.refresh && usableCache && !expired) {
|
|
11479
|
+
return this.fromCache(usableCache, false);
|
|
11480
|
+
}
|
|
11481
|
+
try {
|
|
11482
|
+
const [enriched, vercel] = await Promise.all([
|
|
11483
|
+
this.fetchJson(this.sources[0], enrichedCatalogueSchema),
|
|
11484
|
+
this.fetchJson(this.sources[1], vercelCatalogueSchema)
|
|
11485
|
+
]);
|
|
11486
|
+
const fetchedAt = this.now().toISOString();
|
|
11487
|
+
const cache = {
|
|
11488
|
+
version: 1,
|
|
11489
|
+
fetchedAt,
|
|
11490
|
+
sources: this.sources,
|
|
11491
|
+
enriched,
|
|
11492
|
+
vercel
|
|
11493
|
+
};
|
|
11494
|
+
const cacheFile = {
|
|
11495
|
+
...cache,
|
|
11496
|
+
contentHash: catalogueContentHash(enriched, vercel)
|
|
11497
|
+
};
|
|
11498
|
+
await writeJsonAtomic(this.cachePath, cacheFile);
|
|
11499
|
+
return { enriched, vercel, sources: this.sources, fetchedAt, fromCache: false, stale: false };
|
|
11500
|
+
} catch (error) {
|
|
11501
|
+
if (!usableCache) throw error;
|
|
11502
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
11503
|
+
this.options.warn?.(`Catalogue refresh failed; using stale catalogue cache: ${reason}`);
|
|
11504
|
+
return this.fromCache(usableCache, true);
|
|
11505
|
+
}
|
|
11506
|
+
}
|
|
11507
|
+
async clearCache() {
|
|
11508
|
+
await rm11(this.cachePath, { force: true });
|
|
11509
|
+
}
|
|
11510
|
+
async readCache() {
|
|
11511
|
+
if (!await pathExists(this.cachePath)) return void 0;
|
|
11512
|
+
try {
|
|
11513
|
+
const value = JSON.parse(await readFile32(this.cachePath, "utf8"));
|
|
11514
|
+
const envelope = catalogueCacheEnvelopeSchema.parse(value);
|
|
11515
|
+
if (envelope.contentHash) {
|
|
11516
|
+
const contentHash = catalogueContentHash(envelope.enriched, envelope.vercel);
|
|
11517
|
+
if (contentHash !== envelope.contentHash) {
|
|
11518
|
+
throw new Error("catalogue cache integrity check failed");
|
|
11519
|
+
}
|
|
11520
|
+
return envelope;
|
|
11521
|
+
}
|
|
11522
|
+
return catalogueCacheSchema.parse(value);
|
|
11523
|
+
} catch (error) {
|
|
11524
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
11525
|
+
this.options.warn?.(`Ignoring invalid catalogue cache: ${reason}`);
|
|
11526
|
+
return void 0;
|
|
11527
|
+
}
|
|
11528
|
+
}
|
|
11529
|
+
isExpired(cache) {
|
|
11530
|
+
const ttlMs = this.options.ttlMs ?? DEFAULT_CATALOGUE_TTL_MS;
|
|
11531
|
+
return this.now().getTime() - new Date(cache.fetchedAt).getTime() > ttlMs;
|
|
11532
|
+
}
|
|
11533
|
+
fromCache(cache, stale) {
|
|
11534
|
+
return {
|
|
11535
|
+
enriched: cache.enriched,
|
|
11536
|
+
vercel: cache.vercel,
|
|
11537
|
+
sources: cache.sources,
|
|
11538
|
+
fetchedAt: cache.fetchedAt,
|
|
11539
|
+
fromCache: true,
|
|
11540
|
+
stale
|
|
11541
|
+
};
|
|
11542
|
+
}
|
|
11543
|
+
async fetchJson(source, schema) {
|
|
11544
|
+
const response = await this.fetchImpl(source);
|
|
11545
|
+
if (!response.ok) {
|
|
11546
|
+
throw new Error(`Catalogue source failed (${response.status}): ${source}`);
|
|
11547
|
+
}
|
|
11548
|
+
const declaredLength = response.headers.get("content-length");
|
|
11549
|
+
if (declaredLength !== null) {
|
|
11550
|
+
const bytes = Number(declaredLength);
|
|
11551
|
+
if (Number.isFinite(bytes) && bytes > MAX_CATALOGUE_PAYLOAD_BYTES) {
|
|
11552
|
+
throw new Error(`Catalogue payload exceeds 32 MiB limit: ${source}`);
|
|
11553
|
+
}
|
|
11554
|
+
}
|
|
11555
|
+
const payload = await response.arrayBuffer();
|
|
11556
|
+
if (payload.byteLength > MAX_CATALOGUE_PAYLOAD_BYTES) {
|
|
11557
|
+
throw new Error(`Catalogue payload exceeds 32 MiB limit: ${source}`);
|
|
11558
|
+
}
|
|
11559
|
+
let value;
|
|
11560
|
+
try {
|
|
11561
|
+
value = JSON.parse(new TextDecoder().decode(payload));
|
|
11562
|
+
} catch {
|
|
11563
|
+
throw new Error(`Catalogue source returned invalid JSON: ${source}`);
|
|
11564
|
+
}
|
|
11565
|
+
return schema.parse(value);
|
|
11566
|
+
}
|
|
11567
|
+
};
|
|
11568
|
+
function defaultCatalogueCachePath() {
|
|
11569
|
+
return join44(homedir9(), ".agentwheel", "catalogue-cache.json");
|
|
11570
|
+
}
|
|
11571
|
+
function sameSources2(a, b) {
|
|
11572
|
+
return a.length === b.length && a.every((source, index) => source === b[index]);
|
|
11573
|
+
}
|
|
11574
|
+
function catalogueContentHash(enriched, vercel) {
|
|
11575
|
+
return createHash11("sha256").update(JSON.stringify({ enriched, vercel })).digest("hex");
|
|
11576
|
+
}
|
|
11577
|
+
|
|
11578
|
+
// src/search/index.ts
|
|
11579
|
+
var SCORE = {
|
|
11580
|
+
exactName: 1e4,
|
|
11581
|
+
namePrefix: 5e3,
|
|
11582
|
+
namePhrase: 3e3,
|
|
11583
|
+
tagProvidesPhrase: 2e3,
|
|
11584
|
+
descriptionPhrase: 1e3,
|
|
11585
|
+
typeEcosystemPhrase: 800,
|
|
11586
|
+
repositoryPhrase: 400,
|
|
11587
|
+
nameToken: 300,
|
|
11588
|
+
nameTokenPrefix: 200,
|
|
11589
|
+
tagProvidesToken: 180,
|
|
11590
|
+
descriptionToken: 80,
|
|
11591
|
+
typeEcosystemToken: 60,
|
|
11592
|
+
repositoryToken: 40,
|
|
11593
|
+
allTerms: 500
|
|
11594
|
+
};
|
|
11595
|
+
var PROVENANCE_ORDER = ["registry", "enriched", "vercel"];
|
|
11596
|
+
var MATCHED_FIELD_ORDER = ["name", "tags", "provides", "description", "type", "ecosystem", "repository"];
|
|
11597
|
+
function buildSearchEntries(input) {
|
|
11598
|
+
const enriched = catalogueEntries(input.enriched);
|
|
11599
|
+
const vercel = catalogueEntries(input.vercel);
|
|
11600
|
+
assertUniqueIdentities(enriched.map((entry) => entry.id), "enriched catalogue");
|
|
11601
|
+
assertUniqueIdentities(vercel.map((entry) => `${entry.o}/${entry.r}/${entry.s}`), "Vercel catalogue");
|
|
11602
|
+
const records = [
|
|
11603
|
+
...(input.registry ?? []).map(normalizeRegistryEntry),
|
|
11604
|
+
...enriched.map(normalizeEnrichedEntry),
|
|
11605
|
+
...vercel.map(normalizeVercelEntry)
|
|
11606
|
+
];
|
|
11607
|
+
const byId = /* @__PURE__ */ new Map();
|
|
11608
|
+
for (const record of records) {
|
|
11609
|
+
const existing = byId.get(record.id);
|
|
11610
|
+
byId.set(record.id, existing ? mergeEntry(existing, record) : record);
|
|
11611
|
+
}
|
|
11612
|
+
for (const [registryId, registryEntry] of [...byId]) {
|
|
11613
|
+
if (registryEntry.provenances.length !== 1 || registryEntry.provenances[0] !== "registry" || registryEntry.hasRegistrySelectors || !registryEntry.source) {
|
|
11614
|
+
continue;
|
|
11615
|
+
}
|
|
11616
|
+
const canonicalSource = canonicalizeSource(registryEntry.source);
|
|
11617
|
+
const candidates = [...byId.entries()].filter(
|
|
11618
|
+
([candidateId2, candidate2]) => candidateId2 !== registryId && !candidate2.provenances.includes("registry") && candidate2.name === registryEntry.name && candidate2.source !== void 0 && canonicalizeSource(candidate2.source) === canonicalSource
|
|
11619
|
+
);
|
|
11620
|
+
if (candidates.length !== 1) continue;
|
|
11621
|
+
const [candidateId, candidate] = candidates[0];
|
|
11622
|
+
byId.set(candidateId, mergeEntry(registryEntry, candidate, candidateId));
|
|
11623
|
+
byId.delete(registryId);
|
|
11624
|
+
}
|
|
11625
|
+
return [...byId.values()].sort(compareStableEntries);
|
|
11626
|
+
}
|
|
11627
|
+
function searchEntries(entries, query, options = {}) {
|
|
11628
|
+
const normalizedQuery = normalizeSearchText(query);
|
|
11629
|
+
const queryTokens = tokenizeSearchText(query);
|
|
11630
|
+
if (!normalizedQuery || queryTokens.length === 0) return [];
|
|
11631
|
+
const results = entries.filter((entry) => options.includeArchived || !entry.archived).filter((entry) => options.type === void 0 || entry.type === options.type).filter((entry) => options.ecosystem === void 0 || entry.ecosystem === options.ecosystem).map((entry) => ({ entry, result: scoreEntry(entry, normalizedQuery, queryTokens) })).filter(({ result }) => result.score > 0).sort(
|
|
11632
|
+
(a, b) => b.result.score - a.result.score || Number(b.entry.featured) - Number(a.entry.featured) || (b.entry.stars ?? Number.NEGATIVE_INFINITY) - (a.entry.stars ?? Number.NEGATIVE_INFINITY) || compareText(b.entry.lastPush ?? "", a.entry.lastPush ?? "") || compareText(normalizeSearchText(a.result.name), normalizeSearchText(b.result.name)) || compareText(a.result.id, b.result.id)
|
|
11633
|
+
).map(({ result }) => result);
|
|
11634
|
+
const limit = options.limit === void 0 ? 20 : Math.min(100, Math.max(0, Math.trunc(options.limit)));
|
|
11635
|
+
return results.slice(0, limit);
|
|
11636
|
+
}
|
|
11637
|
+
function normalizeSearchText(value) {
|
|
11638
|
+
return value.normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim().replace(/\s+/g, " ");
|
|
11639
|
+
}
|
|
11640
|
+
function tokenizeSearchText(value) {
|
|
11641
|
+
const normalized = normalizeSearchText(value);
|
|
11642
|
+
return normalized ? [...new Set(normalized.split(" "))] : [];
|
|
11643
|
+
}
|
|
11644
|
+
function normalizeRegistryEntry(entry) {
|
|
11645
|
+
return {
|
|
11646
|
+
id: `registry:${entry.name}`,
|
|
11647
|
+
name: entry.name,
|
|
11648
|
+
description: entry.description,
|
|
11649
|
+
type: entry.type,
|
|
11650
|
+
ecosystem: inferEcosystem(entry.source),
|
|
11651
|
+
tags: sortedUniqueStrings(entry.tags),
|
|
11652
|
+
provides: [],
|
|
11653
|
+
source: entry.source,
|
|
11654
|
+
installCommand: `npx agentwheel install ${shellQuote2(entry.name)}`,
|
|
11655
|
+
installability: "registry",
|
|
11656
|
+
provenances: ["registry"],
|
|
11657
|
+
archived: false,
|
|
11658
|
+
featured: false,
|
|
11659
|
+
alternateDescriptions: [],
|
|
11660
|
+
descriptionRank: 2,
|
|
11661
|
+
hasRegistrySelectors: Boolean(entry.select?.length || entry.skills?.length)
|
|
11662
|
+
};
|
|
11663
|
+
}
|
|
11664
|
+
function normalizeEnrichedEntry(entry) {
|
|
11665
|
+
const source = nonEmpty(entry.source);
|
|
11666
|
+
const installCommand = enrichedInstallCommand(entry, source);
|
|
11667
|
+
return {
|
|
11668
|
+
id: entry.id,
|
|
11669
|
+
name: entry.name,
|
|
11670
|
+
description: entry.description ?? "",
|
|
11671
|
+
type: entry.type ?? inferType(entry.ecosystem),
|
|
11672
|
+
ecosystem: entry.ecosystem ?? void 0,
|
|
11673
|
+
tags: sortedUniqueStrings(entry.tags ?? []),
|
|
11674
|
+
provides: sortedUniqueStrings(entry.provides ?? []),
|
|
11675
|
+
source,
|
|
11676
|
+
repoUrl: nonEmpty(entry.repoUrl),
|
|
11677
|
+
installCommand,
|
|
11678
|
+
installability: source || installCommand ? "source" : "informational",
|
|
11679
|
+
provenances: ["enriched"],
|
|
11680
|
+
archived: entry.archived ?? false,
|
|
11681
|
+
featured: entry.featured ?? false,
|
|
11682
|
+
stars: entry.stars ?? void 0,
|
|
11683
|
+
lastPush: nonEmpty(entry.lastPush),
|
|
11684
|
+
alternateDescriptions: [],
|
|
11685
|
+
descriptionRank: 3,
|
|
11686
|
+
hasRegistrySelectors: false
|
|
11687
|
+
};
|
|
11688
|
+
}
|
|
11689
|
+
function normalizeVercelEntry(entry) {
|
|
11690
|
+
const path = `${entry.o}/${entry.r}/${entry.s}`;
|
|
11691
|
+
const source = `vercel:skills.sh/${path}`;
|
|
11692
|
+
return {
|
|
11693
|
+
id: `vercel:${path}`,
|
|
11694
|
+
name: entry.s,
|
|
11695
|
+
description: entry.d ?? "",
|
|
11696
|
+
type: "skill",
|
|
11697
|
+
ecosystem: "vercel",
|
|
11698
|
+
tags: [],
|
|
11699
|
+
provides: ["skills"],
|
|
11700
|
+
source,
|
|
11701
|
+
repoUrl: `https://github.com/${entry.o}/${entry.r}`,
|
|
11702
|
+
installCommand: `npx agentwheel install ${shellQuote2(source)}`,
|
|
11703
|
+
installability: "source",
|
|
11704
|
+
provenances: ["vercel"],
|
|
11705
|
+
archived: false,
|
|
11706
|
+
featured: false,
|
|
11707
|
+
alternateDescriptions: [],
|
|
11708
|
+
descriptionRank: 1,
|
|
11709
|
+
hasRegistrySelectors: false
|
|
11710
|
+
};
|
|
11711
|
+
}
|
|
11712
|
+
function mergeEntry(first, second, id = first.id) {
|
|
11713
|
+
const primary = second.description && second.descriptionRank > first.descriptionRank ? second : first;
|
|
11714
|
+
const secondary = primary === first ? second : first;
|
|
11715
|
+
const descriptions = uniqueStrings([
|
|
11716
|
+
primary.description,
|
|
11717
|
+
...primary.alternateDescriptions,
|
|
11718
|
+
secondary.description,
|
|
11719
|
+
...secondary.alternateDescriptions
|
|
11720
|
+
]).filter(Boolean);
|
|
11721
|
+
const description = descriptions[0] ?? "";
|
|
11722
|
+
return {
|
|
11723
|
+
id,
|
|
11724
|
+
name: first.name || second.name,
|
|
11725
|
+
description,
|
|
11726
|
+
type: first.type ?? second.type,
|
|
11727
|
+
ecosystem: first.ecosystem ?? second.ecosystem,
|
|
11728
|
+
tags: sortedUniqueStrings([...first.tags, ...second.tags]),
|
|
11729
|
+
provides: sortedUniqueStrings([...first.provides, ...second.provides]),
|
|
11730
|
+
source: first.source ?? second.source,
|
|
11731
|
+
repoUrl: first.repoUrl ?? second.repoUrl,
|
|
11732
|
+
installCommand: first.installCommand ?? second.installCommand,
|
|
11733
|
+
installability: betterInstallability(first.installability, second.installability),
|
|
11734
|
+
provenances: PROVENANCE_ORDER.filter(
|
|
11735
|
+
(provenance) => first.provenances.includes(provenance) || second.provenances.includes(provenance)
|
|
11736
|
+
),
|
|
11737
|
+
archived: first.archived || second.archived,
|
|
11738
|
+
featured: first.featured || second.featured,
|
|
11739
|
+
stars: maxDefined(first.stars, second.stars),
|
|
11740
|
+
lastPush: maxText(first.lastPush, second.lastPush),
|
|
11741
|
+
alternateDescriptions: descriptions.slice(1),
|
|
11742
|
+
descriptionRank: Math.max(first.descriptionRank, second.descriptionRank),
|
|
11743
|
+
hasRegistrySelectors: first.hasRegistrySelectors || second.hasRegistrySelectors
|
|
11744
|
+
};
|
|
11745
|
+
}
|
|
11746
|
+
function scoreEntry(entry, query, queryTokens) {
|
|
11747
|
+
let score = 0;
|
|
11748
|
+
const matched = /* @__PURE__ */ new Set();
|
|
11749
|
+
const name = normalizeSearchText(entry.name);
|
|
11750
|
+
const descriptions = [entry.description, ...entry.alternateDescriptions].map(normalizeSearchText);
|
|
11751
|
+
const tags = entry.tags.map(normalizeSearchText);
|
|
11752
|
+
const provides = entry.provides.map(normalizeSearchText);
|
|
11753
|
+
const type = normalizeSearchText(entry.type);
|
|
11754
|
+
const ecosystem = normalizeSearchText(entry.ecosystem ?? "");
|
|
11755
|
+
const repositories = [entry.source ?? "", entry.repoUrl ?? ""].map(normalizeSearchText);
|
|
11756
|
+
if (name === query) {
|
|
11757
|
+
score += SCORE.exactName;
|
|
11758
|
+
matched.add("name");
|
|
11759
|
+
} else if (name.startsWith(query)) {
|
|
11760
|
+
score += SCORE.namePrefix;
|
|
11761
|
+
matched.add("name");
|
|
11762
|
+
} else if (name.includes(query)) {
|
|
11763
|
+
score += SCORE.namePhrase;
|
|
11764
|
+
matched.add("name");
|
|
11765
|
+
}
|
|
11766
|
+
const tagsPhraseMatch = matchesPhrase(tags, query);
|
|
11767
|
+
const providesPhraseMatch = matchesPhrase(provides, query);
|
|
11768
|
+
if (tagsPhraseMatch || providesPhraseMatch) {
|
|
11769
|
+
score += SCORE.tagProvidesPhrase;
|
|
11770
|
+
if (tagsPhraseMatch) matched.add("tags");
|
|
11771
|
+
if (providesPhraseMatch) matched.add("provides");
|
|
11772
|
+
}
|
|
11773
|
+
if (matchesPhrase(descriptions, query)) {
|
|
11774
|
+
score += SCORE.descriptionPhrase;
|
|
11775
|
+
matched.add("description");
|
|
11776
|
+
}
|
|
11777
|
+
const typePhraseMatch = type.includes(query);
|
|
11778
|
+
const ecosystemPhraseMatch = ecosystem.includes(query);
|
|
11779
|
+
if (typePhraseMatch || ecosystemPhraseMatch) {
|
|
11780
|
+
score += SCORE.typeEcosystemPhrase;
|
|
11781
|
+
if (typePhraseMatch) matched.add("type");
|
|
11782
|
+
if (ecosystemPhraseMatch) matched.add("ecosystem");
|
|
11783
|
+
}
|
|
11784
|
+
if (matchesPhrase(repositories, query)) {
|
|
11785
|
+
score += SCORE.repositoryPhrase;
|
|
11786
|
+
matched.add("repository");
|
|
11787
|
+
}
|
|
11788
|
+
const nameTokens = name.split(" ");
|
|
11789
|
+
const tagTokenText = tags.join(" ");
|
|
11790
|
+
const provideTokenText = provides.join(" ");
|
|
11791
|
+
const descriptionTokenText = descriptions.join(" ");
|
|
11792
|
+
const repositoryTokenText = repositories.join(" ");
|
|
11793
|
+
let allTermsCovered = true;
|
|
11794
|
+
for (const token of queryTokens) {
|
|
11795
|
+
const nameTokenMatch = includesToken(name, token);
|
|
11796
|
+
if (nameTokenMatch) {
|
|
11797
|
+
score += SCORE.nameToken;
|
|
11798
|
+
matched.add("name");
|
|
11799
|
+
} else if (nameTokens.some((candidate) => candidate.startsWith(token))) {
|
|
11800
|
+
score += SCORE.nameTokenPrefix;
|
|
11801
|
+
matched.add("name");
|
|
11802
|
+
}
|
|
11803
|
+
const tagTokenMatch = includesToken(tagTokenText, token);
|
|
11804
|
+
const provideTokenMatch = includesToken(provideTokenText, token);
|
|
11805
|
+
if (tagTokenMatch || provideTokenMatch) {
|
|
11806
|
+
score += SCORE.tagProvidesToken;
|
|
11807
|
+
if (tagTokenMatch) matched.add("tags");
|
|
11808
|
+
if (provideTokenMatch) matched.add("provides");
|
|
11809
|
+
}
|
|
11810
|
+
const descriptionTokenMatch = includesToken(descriptionTokenText, token);
|
|
11811
|
+
if (descriptionTokenMatch) {
|
|
11812
|
+
score += SCORE.descriptionToken;
|
|
11813
|
+
matched.add("description");
|
|
11814
|
+
}
|
|
11815
|
+
const typeTokenMatch = includesToken(type, token);
|
|
11816
|
+
const ecosystemTokenMatch = includesToken(ecosystem, token);
|
|
11817
|
+
if (typeTokenMatch || ecosystemTokenMatch) {
|
|
11818
|
+
score += SCORE.typeEcosystemToken;
|
|
11819
|
+
if (typeTokenMatch) matched.add("type");
|
|
11820
|
+
if (ecosystemTokenMatch) matched.add("ecosystem");
|
|
11821
|
+
}
|
|
11822
|
+
const repositoryTokenMatch = includesToken(repositoryTokenText, token);
|
|
11823
|
+
if (repositoryTokenMatch) {
|
|
11824
|
+
score += SCORE.repositoryToken;
|
|
11825
|
+
matched.add("repository");
|
|
11826
|
+
}
|
|
11827
|
+
if (!nameTokenMatch && !tagTokenMatch && !provideTokenMatch && !descriptionTokenMatch && !typeTokenMatch && !ecosystemTokenMatch && !repositoryTokenMatch) {
|
|
11828
|
+
allTermsCovered = false;
|
|
11829
|
+
}
|
|
11830
|
+
}
|
|
11831
|
+
if (allTermsCovered) {
|
|
11832
|
+
score += SCORE.allTerms;
|
|
11833
|
+
}
|
|
11834
|
+
return {
|
|
11835
|
+
id: entry.id,
|
|
11836
|
+
name: entry.name,
|
|
11837
|
+
description: entry.description,
|
|
11838
|
+
type: entry.type,
|
|
11839
|
+
...entry.ecosystem ? { ecosystem: entry.ecosystem } : {},
|
|
11840
|
+
tags: entry.tags,
|
|
11841
|
+
provides: entry.provides,
|
|
11842
|
+
...entry.source ? { source: entry.source } : {},
|
|
11843
|
+
...entry.repoUrl ? { repoUrl: entry.repoUrl } : {},
|
|
11844
|
+
...entry.installCommand ? { installCommand: entry.installCommand } : {},
|
|
11845
|
+
installability: entry.installability,
|
|
11846
|
+
provenances: entry.provenances,
|
|
11847
|
+
score,
|
|
11848
|
+
matchedFields: MATCHED_FIELD_ORDER.filter((field) => matched.has(field))
|
|
11849
|
+
};
|
|
11850
|
+
}
|
|
11851
|
+
function catalogueEntries(catalogue) {
|
|
11852
|
+
if (!catalogue) return [];
|
|
11853
|
+
return Array.isArray(catalogue) ? catalogue : catalogue.entries;
|
|
11854
|
+
}
|
|
11855
|
+
function canonicalizeSource(source) {
|
|
11856
|
+
const value = source.normalize("NFKC").trim();
|
|
11857
|
+
const github = value.match(
|
|
11858
|
+
/^(?:github:|git:(?:git\+)?https?:\/\/github\.com\/|(?:git\+)?https?:\/\/github\.com\/)([^/#]+)\/([^#]+?)(?:#(.*))?$/i
|
|
11859
|
+
);
|
|
11860
|
+
if (github) {
|
|
11861
|
+
const owner = github[1].toLowerCase();
|
|
11862
|
+
const repository = github[2].replace(/\.git$/i, "").replace(/\/+$/, "").toLowerCase();
|
|
11863
|
+
const ref = github[3];
|
|
11864
|
+
return `github:${owner}/${repository}${ref === void 0 ? "" : `#${ref}`}`;
|
|
11865
|
+
}
|
|
11866
|
+
return value.replace(/\/+$/, "");
|
|
11867
|
+
}
|
|
11868
|
+
function inferEcosystem(source) {
|
|
11869
|
+
const canonical = canonicalizeSource(source);
|
|
11870
|
+
if (canonical.startsWith("vercel:")) return "vercel";
|
|
11871
|
+
if (canonical.startsWith("mcp-registry:")) return "mcp-registry";
|
|
11872
|
+
if (canonical.startsWith("clawhub:")) return "clawhub";
|
|
11873
|
+
if (canonical.startsWith("skillkit:")) return "skillkit";
|
|
11874
|
+
return void 0;
|
|
11875
|
+
}
|
|
11876
|
+
function inferType(ecosystem) {
|
|
11877
|
+
if (ecosystem === "vercel" || ecosystem === "skillkit") return "skill";
|
|
11878
|
+
if (ecosystem === "mcp-registry") return "mcp";
|
|
11879
|
+
if (ecosystem === "clawhub") return "plugin";
|
|
11880
|
+
return "package";
|
|
11881
|
+
}
|
|
11882
|
+
function betterInstallability(a, b) {
|
|
11883
|
+
const rank = { registry: 3, source: 2, informational: 1 };
|
|
11884
|
+
return rank[a] >= rank[b] ? a : b;
|
|
11885
|
+
}
|
|
11886
|
+
function nonEmpty(value) {
|
|
11887
|
+
return value?.trim() ? value : void 0;
|
|
11888
|
+
}
|
|
11889
|
+
function uniqueStrings(values) {
|
|
11890
|
+
return [...new Set(values)];
|
|
11891
|
+
}
|
|
11892
|
+
function sortedUniqueStrings(values) {
|
|
11893
|
+
return uniqueStrings(values).sort(compareText);
|
|
11894
|
+
}
|
|
11895
|
+
function enrichedInstallCommand(entry, source) {
|
|
11896
|
+
const catalogueCommand = nonEmpty(entry.installCommand);
|
|
11897
|
+
if (!source) return catalogueCommand;
|
|
11898
|
+
if (entry.ecosystem === "mcp-registry" || entry.ecosystem === "clawhub") {
|
|
11899
|
+
return catalogueCommand ?? `npx agentwheel install ${shellQuote2(source)}`;
|
|
11900
|
+
}
|
|
11901
|
+
return `npx agentwheel install ${shellQuote2(source)}`;
|
|
11902
|
+
}
|
|
11903
|
+
function shellQuote2(value) {
|
|
11904
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
11905
|
+
}
|
|
11906
|
+
function matchesPhrase(fields, query) {
|
|
11907
|
+
return fields.some((field) => field.includes(query));
|
|
11908
|
+
}
|
|
11909
|
+
function compareStableEntries(a, b) {
|
|
11910
|
+
return compareText(normalizeSearchText(a.name), normalizeSearchText(b.name)) || compareText(a.id, b.id);
|
|
11911
|
+
}
|
|
11912
|
+
function maxDefined(a, b) {
|
|
11913
|
+
if (a === void 0) return b;
|
|
11914
|
+
if (b === void 0) return a;
|
|
11915
|
+
return Math.max(a, b);
|
|
11916
|
+
}
|
|
11917
|
+
function maxText(a, b) {
|
|
11918
|
+
if (a === void 0) return b;
|
|
11919
|
+
if (b === void 0) return a;
|
|
11920
|
+
return a >= b ? a : b;
|
|
11921
|
+
}
|
|
11922
|
+
function assertUniqueIdentities(ids, label) {
|
|
11923
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11924
|
+
for (const id of ids) {
|
|
11925
|
+
if (seen.has(id)) throw new Error(`Duplicate ${label} id: ${id}`);
|
|
11926
|
+
seen.add(id);
|
|
11927
|
+
}
|
|
11928
|
+
}
|
|
11929
|
+
function compareText(a, b) {
|
|
11930
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
11931
|
+
}
|
|
11932
|
+
function includesToken(normalizedText, token) {
|
|
11933
|
+
return normalizedText === token || normalizedText.startsWith(`${token} `) || normalizedText.endsWith(` ${token}`) || normalizedText.includes(` ${token} `);
|
|
11934
|
+
}
|
|
11935
|
+
|
|
11936
|
+
// src/cli/index.ts
|
|
11937
|
+
var CLI_VERSION = resolveCliVersion();
|
|
11938
|
+
var COMPANION_SKILL_SOURCE = "github:NestDevLab/agentwheel";
|
|
11939
|
+
var COMPANION_SKILL_NAME = "agentwheel";
|
|
11940
|
+
var planOutputFormats = ["human", "json", "mermaid", "html"];
|
|
11941
|
+
var program = new Command();
|
|
11942
|
+
program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version(CLI_VERSION).showSuggestionAfterError(false).option("--no-update-check", "disable npm version update check", false).addHelpText("after", `
|
|
11943
|
+
|
|
11944
|
+
Core flow:
|
|
11945
|
+
$ agentwheel add github:org/agent-pack --adapter codex
|
|
11946
|
+
$ agentwheel plan
|
|
11947
|
+
$ agentwheel install
|
|
11948
|
+
`);
|
|
10618
11949
|
program.command("init").description("initialize an agentwheel workspace or package").argument("[kind]", "workspace or package", "workspace").option("-t, --target-root <path>", "workspace root", process.cwd()).option("--fleet-example", "scaffold example agents and profiles in workspace config", false).action(async (kind, options) => {
|
|
10619
11950
|
const root = normalizeTargetRoot(options.targetRoot);
|
|
10620
11951
|
if (kind === "package") {
|
|
@@ -10633,7 +11964,7 @@ program.command("init").description("initialize an agentwheel workspace or packa
|
|
|
10633
11964
|
if (bootstrapPackage) console.log("Auto-added the agentwheel bootstrap skill for openclaw.");
|
|
10634
11965
|
console.log(nextInstallNudge());
|
|
10635
11966
|
});
|
|
10636
|
-
program.command("add").description("add a package to .agentwheel/config.json without touching runtimes").argument("<source>", "package source").option("--driver <driver>", "source driver (local, git, skillkit, vercel-skills, mcp-registry, or clawhub)").option("--adapter <adapter>", "built-in adapter", "openclaw").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "workspace root").option("--mode <mode>", "pinned or tracking", "pinned").option("--name <name>", "package alias").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots on future installs", false).option("--suggestion <alias>", "include one suggested companion alias on future installs (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "allow this package to replace a colliding artifact (repeatable)", collectOverrideOption, []).action(async (source, options) => {
|
|
11967
|
+
program.command("add").description("add a package to .agentwheel/config.json without touching runtimes").argument("<source>", "package source").option("--driver <driver>", "source driver (local, git, skillkit, vercel-skills, mcp-registry, or clawhub)").option("--adapter <adapter>", "built-in adapter", "openclaw").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "workspace root").option("--mode <mode>", "pinned or tracking", "pinned").option("--version <range>", "root package version policy (exact, ~, ^, or *)").option("--name <name>", "package alias").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots on future installs", false).option("--suggestion <alias>", "include one suggested companion alias on future installs (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "allow this package to replace a colliding artifact (repeatable)", collectOverrideOption, []).action(async (source, options) => {
|
|
10637
11968
|
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
10638
11969
|
const targetRoot = normalizeTargetRoot(normalizedOptions.targetRoot ?? process.cwd());
|
|
10639
11970
|
const entry = await packageEntryFromSource(source, targetRoot, normalizedOptions);
|
|
@@ -10645,17 +11976,59 @@ program.command("list").description("list artifacts exposed by a package source"
|
|
|
10645
11976
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
10646
11977
|
const selectedArtifacts = selectedArtifactsFromOptionsOrRegistry(options, resolvedInput.registryEntry);
|
|
10647
11978
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
10648
|
-
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot:
|
|
11979
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join45(targetRoot, ".agentwheel", "cache") }))));
|
|
10649
11980
|
const artifacts = filterArtifactsBySelection(await driver.list(resolved), selectedArtifacts);
|
|
10650
11981
|
for (const artifact of artifacts) {
|
|
10651
11982
|
console.log(`${artifact.type} ${artifact.name} ${artifact.relativePath}`);
|
|
10652
11983
|
}
|
|
10653
11984
|
});
|
|
11985
|
+
program.command("search").description("search registry and public catalogue artifacts").argument("<query>", "search query").option("--json", "print the versioned search response as JSON", false).option("--scope <scope>", "search scope: all, registry, enriched, or vercel", "all").option("--type <type>", "artifact type: package, skill, plugin, mcp, or adapter").option("--ecosystem <ecosystem>", "ecosystem: official, openpack, mcp-registry, clawhub, skillkit, or vercel").option("--limit <n>", "maximum number of results (1-100)", "20").option("--include-archived", "include archived catalogue entries", false).option("--refresh", "refresh registry and catalogue caches", false).option("--offline", "use compatible local caches without network access", false).option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (query, options) => {
|
|
11986
|
+
const trimmedQuery = query.trim();
|
|
11987
|
+
if (!trimmedQuery) {
|
|
11988
|
+
throw new Error("Search query must not be empty.");
|
|
11989
|
+
}
|
|
11990
|
+
const scope = parseSearchScope(options.scope);
|
|
11991
|
+
const type = options.type === void 0 ? void 0 : parseSearchType(options.type);
|
|
11992
|
+
const ecosystem = options.ecosystem === void 0 ? void 0 : parseSearchEcosystem(options.ecosystem);
|
|
11993
|
+
const limit = parseSearchLimit(options.limit);
|
|
11994
|
+
if (options.refresh && options.offline) {
|
|
11995
|
+
throw new Error("--refresh cannot be used with --offline.");
|
|
11996
|
+
}
|
|
11997
|
+
const warning = (message) => console.error(message);
|
|
11998
|
+
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
11999
|
+
const registryRequest = scope === "all" || scope === "registry" ? new RegistryClient({ workspaceRoot: targetRoot, offline: options.offline, warn: warning }).getIndex({ refresh: options.refresh }) : void 0;
|
|
12000
|
+
const catalogueRequest = scope === "all" || scope === "enriched" || scope === "vercel" ? new CatalogueClient({ offline: options.offline, warn: warning }).getIndex({ refresh: options.refresh }) : void 0;
|
|
12001
|
+
const [registryIndex, catalogueIndex] = await Promise.all([registryRequest, catalogueRequest]);
|
|
12002
|
+
const entries = buildSearchEntries({
|
|
12003
|
+
registry: registryIndex?.entries,
|
|
12004
|
+
enriched: scope === "all" || scope === "enriched" ? catalogueIndex?.enriched : void 0,
|
|
12005
|
+
vercel: scope === "all" || scope === "vercel" ? catalogueIndex?.vercel : void 0
|
|
12006
|
+
});
|
|
12007
|
+
const results = searchEntries(entries, trimmedQuery, {
|
|
12008
|
+
type,
|
|
12009
|
+
ecosystem,
|
|
12010
|
+
limit,
|
|
12011
|
+
includeArchived: options.includeArchived
|
|
12012
|
+
});
|
|
12013
|
+
const loadedIndexes = [registryIndex, catalogueIndex].filter((index) => index !== void 0);
|
|
12014
|
+
const response = {
|
|
12015
|
+
schemaVersion: 1,
|
|
12016
|
+
query: trimmedQuery,
|
|
12017
|
+
scope,
|
|
12018
|
+
fromCache: loadedIndexes.every((index) => index.fromCache),
|
|
12019
|
+
results
|
|
12020
|
+
};
|
|
12021
|
+
if (options.json) {
|
|
12022
|
+
console.log(JSON.stringify(response, null, 2));
|
|
12023
|
+
return;
|
|
12024
|
+
}
|
|
12025
|
+
printSearchResults(trimmedQuery, results);
|
|
12026
|
+
});
|
|
10654
12027
|
program.command("scan").description("scan a package source for validation findings").argument("<source>", "package source").option("--driver <driver>", "source driver").option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (source, options) => {
|
|
10655
12028
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
10656
12029
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
10657
12030
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
10658
|
-
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot:
|
|
12031
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join45(targetRoot, ".agentwheel", "cache") }))));
|
|
10659
12032
|
const result = await driver.scan(resolved);
|
|
10660
12033
|
if (result.findings.length === 0) {
|
|
10661
12034
|
console.log("Scan ok: no findings");
|
|
@@ -10666,13 +12039,13 @@ program.command("scan").description("scan a package source for validation findin
|
|
|
10666
12039
|
}
|
|
10667
12040
|
if (!result.ok) process.exitCode = 1;
|
|
10668
12041
|
});
|
|
10669
|
-
program.command("plan").description("preview what install would reconcile without writing").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--profile <name>", "workspace runtime profile").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("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
|
|
12042
|
+
program.command("plan").description("preview what install would reconcile without writing").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--profile <name>", "workspace runtime profile").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("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
|
|
10670
12043
|
await runInstallCommand(source, { ...options, dryRun: true }, { apply: false });
|
|
10671
12044
|
});
|
|
10672
|
-
program.command("install").description("install configured packages into runtime targets").argument("[name-or-source]", "configured package name/source or package source to add and install").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).addHelpText("after", "\nScoped install never removes files owned only by other configured packages; run a full install to reconcile those removals.\n").action(async (source, options) => {
|
|
12045
|
+
program.command("install").description("install configured packages into runtime targets").argument("[name-or-source]", "configured package name/source or package source to add and install").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).addHelpText("after", "\nScoped install never removes files owned only by other configured packages; run a full install to reconcile those removals.\n").action(async (source, options) => {
|
|
10673
12046
|
await runInstallCommand(source, options, { apply: !options.dryRun });
|
|
10674
12047
|
});
|
|
10675
|
-
program.command("serve").description("serve a read-only live dashboard for the resolved install plan").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--profile <name>", "workspace runtime profile").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("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).option("--bind <addr>", "interface to bind", "127.0.0.1").option("--port <n>", "TCP port (0 selects an ephemeral port)", "8765").option("--interval <seconds>", "background re-render cadence in seconds", "60").option("--once", "render once and skip the background re-render loop", false).action(async (source, options) => {
|
|
12048
|
+
program.command("serve").description("serve a read-only live dashboard for the resolved install plan").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--profile <name>", "workspace runtime profile").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("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).option("--bind <addr>", "interface to bind", "127.0.0.1").option("--port <n>", "TCP port (0 selects an ephemeral port)", "8765").option("--interval <seconds>", "background re-render cadence in seconds", "60").option("--once", "render once and skip the background re-render loop", false).action(async (source, options) => {
|
|
10676
12049
|
await servePlanDashboard({
|
|
10677
12050
|
bind: options.bind,
|
|
10678
12051
|
port: parseServePort(options.port),
|
|
@@ -10681,11 +12054,11 @@ program.command("serve").description("serve a read-only live dashboard for the r
|
|
|
10681
12054
|
buildReport: () => buildPlanReport(source, options)
|
|
10682
12055
|
});
|
|
10683
12056
|
});
|
|
10684
|
-
program.command("sync", { hidden: true }).argument("[name-or-source]", "configured package name/source or package source").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
|
|
12057
|
+
program.command("sync", { hidden: true }).argument("[name-or-source]", "configured package name/source or package source").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
|
|
10685
12058
|
console.error("warning: 'agentwheel sync' is deprecated and will be removed in 0.10. Use 'agentwheel install'.");
|
|
10686
12059
|
await runInstallCommand(source, options, { apply: !options.dryRun });
|
|
10687
12060
|
});
|
|
10688
|
-
program.command("update").description("re-resolve tracking packages, then apply the result").argument("[name]", "configured package name or source to update").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("-t, --target-root <path>", "workspace root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plans without writing", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", 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, []).option("--dependency <name-or-source>", "update one tracking dependency while keeping unrelated graph nodes locked (repeatable)", collectDependencyOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (name, options) => {
|
|
12061
|
+
program.command("update").description("re-resolve tracking packages, then apply the result").argument("[name]", "configured package name or source to update").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("-t, --target-root <path>", "workspace root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plans without writing", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", 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, []).option("--dependency <name-or-source>", "update one tracking dependency while keeping unrelated graph nodes locked (repeatable)", collectDependencyOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (name, options) => {
|
|
10689
12062
|
if (name && options.dependency.length > 0) throw new Error("A package argument cannot be combined with --dependency.");
|
|
10690
12063
|
if (options.dependency.length > 0 && (options.select.length > 0 || options.skill.length > 0)) {
|
|
10691
12064
|
throw new Error("--dependency cannot be combined with --select or --skill; package selections remain unchanged.");
|
|
@@ -10694,6 +12067,11 @@ program.command("update").description("re-resolve tracking packages, then apply
|
|
|
10694
12067
|
throw new Error("--dependency cannot be combined with --frozen-lock or --offline.");
|
|
10695
12068
|
}
|
|
10696
12069
|
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
12070
|
+
const composite = await resolveSelectedCompositeProfile(normalizedOptions);
|
|
12071
|
+
if (composite) {
|
|
12072
|
+
await runCompositeUpdate(composite.workspaceRoot, composite.name, composite.profile, name, normalizedOptions);
|
|
12073
|
+
return;
|
|
12074
|
+
}
|
|
10697
12075
|
const targets = await resolveCliTargets(normalizedOptions, { preferAllProfile: true });
|
|
10698
12076
|
for (const target of targets) {
|
|
10699
12077
|
await runConfiguredGraphPackages(target, { ...normalizedOptions, scope: name }, { mode: "update" });
|
|
@@ -10713,7 +12091,7 @@ program.command("deps").description("inspect the OpenPack dependency graph").add
|
|
|
10713
12091
|
for (const decision of result.bundle.graphLock.canonical.overrides) {
|
|
10714
12092
|
console.log(`OVERRIDE ${decision.graphNodeId}:${decision.type}/${decision.name} replaces ${decision.overriddenGraphNodeId}:${decision.type}/${decision.name} via ${decision.rootId} (${decision.selector})`);
|
|
10715
12093
|
}
|
|
10716
|
-
await
|
|
12094
|
+
await rm12(result.bundle.root, { recursive: true, force: true });
|
|
10717
12095
|
}
|
|
10718
12096
|
continue;
|
|
10719
12097
|
}
|
|
@@ -10745,11 +12123,6 @@ program.command("registry").description("manage optional registry indexes").addC
|
|
|
10745
12123
|
const client = new RegistryClient({ workspaceRoot: normalizeTargetRoot(options.targetRoot), warn: (message) => console.warn(message) });
|
|
10746
12124
|
printRegistryEntries((await client.getIndex()).entries);
|
|
10747
12125
|
})
|
|
10748
|
-
).addCommand(
|
|
10749
|
-
new Command("search").description("search registry entries").argument("<query>", "search query").option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (query, options) => {
|
|
10750
|
-
const client = new RegistryClient({ workspaceRoot: normalizeTargetRoot(options.targetRoot), warn: (message) => console.warn(message) });
|
|
10751
|
-
printRegistryEntries(await client.search(query));
|
|
10752
|
-
})
|
|
10753
12126
|
).addCommand(
|
|
10754
12127
|
new Command("publish").description("draft a catalogue submission for a public source").argument("<source>", "public resource source or GitHub URL").option("--name <name>", "registry short name").option("--type <type>", "entry type (package, skill, plugin, mcp, or adapter)").option("--description <text>", "short catalogue description").option("--tag <tag>", "search tag (repeatable or comma-separated)", collectTagOption, []).option("--select <type/name>", "selected artifact inside a larger package (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "selected skill inside a larger package (repeatable or comma-separated)", collectSkillOption, []).option("--json", "print only the registry entry JSON", false).action(async (source, options) => {
|
|
10755
12128
|
const draft = createRegistryPublishDraft(source, {
|
|
@@ -10889,9 +12262,25 @@ program.command("uninstall").description("remove configured packages or managed
|
|
|
10889
12262
|
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
10890
12263
|
}
|
|
10891
12264
|
});
|
|
10892
|
-
program.command("status").description("show configured packages and runtime install state").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--profile <name>", "workspace runtime profile").action(async (options) => {
|
|
12265
|
+
program.command("status").description("show configured packages and runtime install state").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--profile <name>", "workspace runtime profile").option("--json", "print the versioned status protocol as JSON", false).option("--offline", "use cached version and member status even when stale", false).option("--refresh", "refresh package and member status regardless of TTL", false).action(async (options) => {
|
|
10893
12266
|
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
12267
|
+
const composite = await resolveSelectedCompositeProfile(normalizedOptions);
|
|
12268
|
+
if (composite) {
|
|
12269
|
+
const report = await collectCompositeStatus(composite.workspaceRoot, composite.name, composite.profile, normalizedOptions);
|
|
12270
|
+
if (options.json) console.log(JSON.stringify(report, null, 2));
|
|
12271
|
+
else printStatusReport(report);
|
|
12272
|
+
if (!["PASS", "WARN"].includes(report.health)) process.exitCode = 1;
|
|
12273
|
+
return;
|
|
12274
|
+
}
|
|
10894
12275
|
const targets = await resolveCliTargets(normalizedOptions, { preferAllProfile: true });
|
|
12276
|
+
if (options.json) {
|
|
12277
|
+
const targetReports = [];
|
|
12278
|
+
for (const target of targets) targetReports.push(await collectTargetStatus(target, normalizedOptions));
|
|
12279
|
+
const report = await statusReport(targets[0]?.workspaceRoot ?? process.cwd(), options.profile ?? null, targetReports);
|
|
12280
|
+
console.log(JSON.stringify(report, null, 2));
|
|
12281
|
+
if (!["PASS", "WARN"].includes(report.health)) process.exitCode = 1;
|
|
12282
|
+
return;
|
|
12283
|
+
}
|
|
10895
12284
|
for (const target of targets) {
|
|
10896
12285
|
await printStatus(target, normalizedOptions);
|
|
10897
12286
|
}
|
|
@@ -10907,7 +12296,7 @@ journalCommand.command("list").description("show pending apply journals for reso
|
|
|
10907
12296
|
if (!journal) continue;
|
|
10908
12297
|
pending += 1;
|
|
10909
12298
|
console.log(`PENDING ${state.adapter.name}/${state.installationType} at ${state.installRoot}`);
|
|
10910
|
-
console.log(` journal: ${
|
|
12299
|
+
console.log(` journal: ${join45(state.installRoot, ".agentwheel", `${state.state.stateKey}.apply-journal.json`)}`);
|
|
10911
12300
|
console.log(` stateKey: ${state.state.stateKey}`);
|
|
10912
12301
|
console.log(` createdAt: ${journal.createdAt}`);
|
|
10913
12302
|
console.log(` updatedAt: ${journal.updatedAt}`);
|
|
@@ -10941,6 +12330,21 @@ program.command("doctor").description("check agentwheel runtime setup and compan
|
|
|
10941
12330
|
async function runInstallCommand(nameOrSource, options, behavior) {
|
|
10942
12331
|
const normalizedOptions = normalizeRuntimeScopeOptions(options, { defaultUser: shouldDefaultUserInstall(nameOrSource, options) });
|
|
10943
12332
|
const outputFormat = effectivePlanOutputFormat(normalizedOptions);
|
|
12333
|
+
const composite = await resolveSelectedCompositeProfile(normalizedOptions);
|
|
12334
|
+
if (composite) {
|
|
12335
|
+
if (outputFormat !== "human") {
|
|
12336
|
+
throw new Error("Composite profile plan/install currently requires human output; use status --json for the versioned member protocol.");
|
|
12337
|
+
}
|
|
12338
|
+
await runCompositeInstall(
|
|
12339
|
+
composite.workspaceRoot,
|
|
12340
|
+
composite.name,
|
|
12341
|
+
composite.profile,
|
|
12342
|
+
nameOrSource,
|
|
12343
|
+
normalizedOptions,
|
|
12344
|
+
behavior
|
|
12345
|
+
);
|
|
12346
|
+
return;
|
|
12347
|
+
}
|
|
10944
12348
|
if (outputFormat !== "human") {
|
|
10945
12349
|
const report = await buildPlanReport(nameOrSource, normalizedOptions);
|
|
10946
12350
|
if (report.targets.some((target) => target.hasBlockingChanges)) process.exitCode = 1;
|
|
@@ -11032,7 +12436,7 @@ async function runInstallCommand(nameOrSource, options, behavior) {
|
|
|
11032
12436
|
console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
|
|
11033
12437
|
if (reloaded) console.log(`Reloaded runtime via ${formatReloadCommands(target.reloadCommands)}.`);
|
|
11034
12438
|
}
|
|
11035
|
-
await
|
|
12439
|
+
await rm12(result.bundle.root, { recursive: true, force: true });
|
|
11036
12440
|
if (result.plan.hasBlockingChanges) process.exitCode = 1;
|
|
11037
12441
|
}
|
|
11038
12442
|
if (behavior.apply && extraPackage && !targetOptions.onlySource) {
|
|
@@ -11127,7 +12531,7 @@ async function buildPlanReport(nameOrSource, options) {
|
|
|
11127
12531
|
for (const result of results) {
|
|
11128
12532
|
reportTargets.push(installPlanReportTarget(result.plan, result.graphLockDigest));
|
|
11129
12533
|
reportWarnings.push(...result.warnings);
|
|
11130
|
-
await
|
|
12534
|
+
await rm12(result.bundle.root, { recursive: true, force: true });
|
|
11131
12535
|
}
|
|
11132
12536
|
}
|
|
11133
12537
|
return planReport(reportTargets, reportWarnings);
|
|
@@ -11160,11 +12564,32 @@ async function packageEntryFromSource(source, targetRoot, options) {
|
|
|
11160
12564
|
baseDir: targetRoot,
|
|
11161
12565
|
warn: options.warn ?? ((message) => console.warn(message))
|
|
11162
12566
|
});
|
|
12567
|
+
const provisionalName = options.name ?? resolvedInput.registryEntry?.name ?? source;
|
|
12568
|
+
const initialVersion = options.mode === "tracking" && options.version ? await effectiveTrackingRef({
|
|
12569
|
+
name: provisionalName,
|
|
12570
|
+
source: resolvedSource,
|
|
12571
|
+
driver: driverName,
|
|
12572
|
+
adapter: adapter.name,
|
|
12573
|
+
installationType: options.installationType,
|
|
12574
|
+
mode: "tracking",
|
|
12575
|
+
version: options.version
|
|
12576
|
+
}, targetRoot, { offline: options.offline }) : void 0;
|
|
12577
|
+
if (initialVersion?.availability?.error || initialVersion?.availability?.stale) {
|
|
12578
|
+
throw new Error(
|
|
12579
|
+
`Cannot select an initial version for ${provisionalName}: ${initialVersion.availability?.error ?? "version metadata is stale"}`
|
|
12580
|
+
);
|
|
12581
|
+
}
|
|
12582
|
+
if (initialVersion?.availability && !initialVersion.availability.latestAllowedRef) {
|
|
12583
|
+
throw new Error(
|
|
12584
|
+
`No available version of ${provisionalName} satisfies ${options.version}; latest overall is ${initialVersion.availability.latestOverall ?? "unknown"}.`
|
|
12585
|
+
);
|
|
12586
|
+
}
|
|
11163
12587
|
const bundle = await stageSource(driver, resolvedSource, {
|
|
11164
12588
|
workspaceRoot: targetRoot,
|
|
11165
12589
|
adapter,
|
|
11166
|
-
cacheRoot:
|
|
12590
|
+
cacheRoot: join45(targetRoot, ".agentwheel", "cache"),
|
|
11167
12591
|
mode: options.mode,
|
|
12592
|
+
ref: initialVersion?.ref,
|
|
11168
12593
|
frozenLock: lockMode,
|
|
11169
12594
|
select: selectedArtifacts
|
|
11170
12595
|
});
|
|
@@ -11180,6 +12605,7 @@ async function packageEntryFromSource(source, targetRoot, options) {
|
|
|
11180
12605
|
adapterModule: options.adapterModule,
|
|
11181
12606
|
adapterCodeHash: adapter.programmatic?.hash,
|
|
11182
12607
|
mode: options.mode ?? "pinned",
|
|
12608
|
+
version: options.version,
|
|
11183
12609
|
requestedRef: bundle.source.requestedRef,
|
|
11184
12610
|
select: selectedArtifacts,
|
|
11185
12611
|
withSuggestions: options.withSuggestions === true ? true : void 0,
|
|
@@ -11187,7 +12613,7 @@ async function packageEntryFromSource(source, targetRoot, options) {
|
|
|
11187
12613
|
overrides: overrideArtifactsFromOptions(options)
|
|
11188
12614
|
};
|
|
11189
12615
|
} finally {
|
|
11190
|
-
await
|
|
12616
|
+
await rm12(bundle.root, { recursive: true, force: true });
|
|
11191
12617
|
}
|
|
11192
12618
|
}
|
|
11193
12619
|
function findConfiguredPackage(packages, value) {
|
|
@@ -11338,7 +12764,7 @@ async function runConfiguredGraphPackages(target, options, behavior) {
|
|
|
11338
12764
|
console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
|
|
11339
12765
|
if (reloaded) console.log(`Reloaded runtime via ${formatReloadCommands(target.reloadCommands)}.`);
|
|
11340
12766
|
}
|
|
11341
|
-
await
|
|
12767
|
+
await rm12(result.bundle.root, { recursive: true, force: true });
|
|
11342
12768
|
if (result.plan.hasBlockingChanges) process.exitCode = 1;
|
|
11343
12769
|
}
|
|
11344
12770
|
}
|
|
@@ -11396,28 +12822,61 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
11396
12822
|
const allPackages = [...group.packages, ...group.extraPackages];
|
|
11397
12823
|
const groupHasScope = !scopedRootId || allPackages.some((pkg) => pkg.name === scopedRootId || pkg.source === targetOptions.scope);
|
|
11398
12824
|
if (behavior.mode === "install" && scopedRootId && !groupHasScope) continue;
|
|
12825
|
+
const groupGraphLockPath = graphLockPathForTarget(
|
|
12826
|
+
group.target.workspaceRoot,
|
|
12827
|
+
targetKeyForTarget(group.target, adapter.name),
|
|
12828
|
+
adapter.name,
|
|
12829
|
+
targetFingerprintParts(group.target, adapter, group.adapterOptions, group.installationType)
|
|
12830
|
+
);
|
|
12831
|
+
const previousGroupLock = await (await pathExists(groupGraphLockPath) ? readGraphLock(groupGraphLockPath) : void 0);
|
|
11399
12832
|
const dependencyUpdateRootNames = /* @__PURE__ */ new Set();
|
|
11400
12833
|
if (scopedDependencyUpdate) {
|
|
11401
|
-
const graphLockPath = graphLockPathForTarget(
|
|
11402
|
-
group.target.workspaceRoot,
|
|
11403
|
-
targetKeyForTarget(group.target, adapter.name),
|
|
11404
|
-
adapter.name,
|
|
11405
|
-
targetFingerprintParts(group.target, adapter, group.adapterOptions, group.installationType)
|
|
11406
|
-
);
|
|
11407
|
-
const previousLock = await (await pathExists(graphLockPath) ? readGraphLock(graphLockPath) : void 0);
|
|
11408
12834
|
for (const pkg of group.packages) {
|
|
11409
|
-
const root =
|
|
12835
|
+
const root = previousGroupLock?.canonical.roots.find((candidate) => candidate.rootId === pkg.name);
|
|
11410
12836
|
if (root?.mode !== "tracking") continue;
|
|
11411
|
-
const node =
|
|
12837
|
+
const node = previousGroupLock?.canonical.nodes.find((candidate) => candidate.id === root.graphNodeId);
|
|
11412
12838
|
if (node && dependencyUpdateSelectors.some((selector) => dependencyUpdateSelectorMatchesRoot(root, node, selector))) {
|
|
11413
12839
|
dependencyUpdateRootNames.add(pkg.name);
|
|
11414
12840
|
}
|
|
11415
12841
|
}
|
|
11416
12842
|
}
|
|
11417
12843
|
const updateScope = behavior.mode === "update" ? scopedPackage ? /* @__PURE__ */ new Set([scopedPackage.name]) : void 0 : void 0;
|
|
12844
|
+
const versionSelections = /* @__PURE__ */ new Map();
|
|
12845
|
+
const lockedVersionRefs = /* @__PURE__ */ new Map();
|
|
12846
|
+
const versionPolicyUpdateNames = /* @__PURE__ */ new Set();
|
|
12847
|
+
for (const pkg of allPackages) {
|
|
12848
|
+
if (pkg.mode !== "tracking" || !pkg.version) continue;
|
|
12849
|
+
const previousRoot = previousGroupLock?.canonical.roots.find((candidate) => candidate.rootId === pkg.name);
|
|
12850
|
+
const previousNode = previousRoot ? previousGroupLock?.canonical.nodes.find((candidate) => candidate.id === previousRoot.graphNodeId) : void 0;
|
|
12851
|
+
const policyRequiresResolution = !previousNode || !satisfiesVersionRange(previousNode.version, pkg.version);
|
|
12852
|
+
if (!policyRequiresResolution && previousNode?.requestedRef) {
|
|
12853
|
+
lockedVersionRefs.set(pkg.name, previousNode.requestedRef);
|
|
12854
|
+
}
|
|
12855
|
+
const packageUpdateSelected = !updateScope || updateScope.has(pkg.name);
|
|
12856
|
+
if (behavior.mode === "update" && packageUpdateSelected || behavior.mode === "install" && policyRequiresResolution) {
|
|
12857
|
+
const selection = await effectiveTrackingRef(pkg, group.target.workspaceRoot, {
|
|
12858
|
+
offline: targetOptions.offline,
|
|
12859
|
+
forceRefresh: targetOptions.refresh
|
|
12860
|
+
});
|
|
12861
|
+
if (selection.availability?.error || selection.availability?.stale) {
|
|
12862
|
+
throw new Error(
|
|
12863
|
+
`Cannot resolve ${pkg.name} with stale version metadata: ${selection.availability?.error ?? "version index TTL expired"}`
|
|
12864
|
+
);
|
|
12865
|
+
}
|
|
12866
|
+
versionSelections.set(pkg.name, selection);
|
|
12867
|
+
if (policyRequiresResolution) versionPolicyUpdateNames.add(pkg.name);
|
|
12868
|
+
if (!selection.availability?.latestAllowed) {
|
|
12869
|
+
targetOptions.warn?.(
|
|
12870
|
+
`No available version of ${pkg.name} satisfies ${pkg.version}; latest overall is ${selection.availability?.latestOverall ?? "unknown"}.`
|
|
12871
|
+
);
|
|
12872
|
+
}
|
|
12873
|
+
}
|
|
12874
|
+
}
|
|
11418
12875
|
const roots = [
|
|
11419
12876
|
...allPackages.map((pkg) => {
|
|
11420
|
-
const
|
|
12877
|
+
const versionSelection = versionSelections.get(pkg.name);
|
|
12878
|
+
const versionAllowsUpdate = !pkg.version || Boolean(versionSelection?.availability?.latestAllowed);
|
|
12879
|
+
const updateThisPackage = behavior.mode === "update" && !scopedDependencyUpdate && pkg.mode === "tracking" && versionAllowsUpdate && (!updateScope || updateScope.has(pkg.name) || updateScope.has(pkg.source));
|
|
11421
12880
|
const packageIsScoped = scopedRootId ? pkg.name === scopedRootId || pkg.source === targetOptions.scope : true;
|
|
11422
12881
|
if (pkg.selection && selectedArtifacts && packageIsScoped) {
|
|
11423
12882
|
throw new Error(`--select/--skill cannot override imported selection for configured package '${pkg.name}'.`);
|
|
@@ -11426,14 +12885,15 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
11426
12885
|
rootId: pkg.name,
|
|
11427
12886
|
source: pkg.source,
|
|
11428
12887
|
mode: pkg.mode,
|
|
11429
|
-
|
|
12888
|
+
version: pkg.version,
|
|
12889
|
+
ref: versionSelection?.ref ?? lockedVersionRefs.get(pkg.name) ?? pkg.requestedRef,
|
|
11430
12890
|
select: pkg.selection ? void 0 : selectedArtifacts && packageIsScoped ? selectedArtifacts : normalizeArtifactSelectors(pkg.select, pkg.skills),
|
|
11431
12891
|
selection: pkg.selection,
|
|
11432
12892
|
aliases: pkg.aliases,
|
|
11433
12893
|
overrides: pkg.overrides,
|
|
11434
12894
|
includeSuggestions: targetOptions.withSuggestions === true || pkg.withSuggestions === true,
|
|
11435
12895
|
suggestionAliases: packageSuggestionAliases(pkg, targetOptions),
|
|
11436
|
-
useLock: behavior.mode === "install" ?
|
|
12896
|
+
useLock: behavior.mode === "install" ? !versionPolicyUpdateNames.has(pkg.name) : scopedDependencyUpdate ? !dependencyUpdateRootNames.has(pkg.name) : !updateThisPackage
|
|
11437
12897
|
};
|
|
11438
12898
|
}),
|
|
11439
12899
|
...group.extraRoots
|
|
@@ -11474,7 +12934,7 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
11474
12934
|
if ((behavior.mode === "install" || behavior.mode === "update") && scopedRootId) {
|
|
11475
12935
|
const state = installStateForTarget(group.target, adapter, group.adapterOptions, group.installationType);
|
|
11476
12936
|
const manifest = await readInstallManifest(state.installRoot, adapter.name, transport, state);
|
|
11477
|
-
results.push(scopeInstallPlanToRoot(result, scopedRootId, manifest));
|
|
12937
|
+
results.push(behavior.mode === "update" && previousGroupLock ? scopeUpdatePlanToRoot(result, scopedRootId, previousGroupLock, manifest) : scopeInstallPlanToRoot(result, scopedRootId, manifest));
|
|
11478
12938
|
} else if (scopedDependencyUpdate) {
|
|
11479
12939
|
const state = installStateForTarget(group.target, adapter, group.adapterOptions, group.installationType);
|
|
11480
12940
|
const manifest = await readInstallManifest(state.installRoot, adapter.name, transport, state);
|
|
@@ -11521,7 +12981,7 @@ function scopeUpdatePlanToDependencies(result, selectors, previousLock, manifest
|
|
|
11521
12981
|
selectedPreviousNodeIds,
|
|
11522
12982
|
selectedRootIds
|
|
11523
12983
|
);
|
|
11524
|
-
const graphLockDigest =
|
|
12984
|
+
const graphLockDigest = createHash12("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
11525
12985
|
return {
|
|
11526
12986
|
...result,
|
|
11527
12987
|
bundle: { ...result.bundle, graphLock },
|
|
@@ -11678,6 +13138,44 @@ function scopeInstallPlanToRoot(result, rootId, manifest) {
|
|
|
11678
13138
|
}
|
|
11679
13139
|
};
|
|
11680
13140
|
}
|
|
13141
|
+
function scopeUpdatePlanToRoot(result, rootId, previousLock, manifest) {
|
|
13142
|
+
const scoped = scopeInstallPlanToRoot(result, rootId, manifest);
|
|
13143
|
+
const selectedCurrentNodeIds = graphRootClosure(scoped.bundle.graphLock, rootId);
|
|
13144
|
+
const selectedPreviousNodeIds = graphRootClosure(previousLock, rootId);
|
|
13145
|
+
const graphLock = preserveUnrelatedGraphPackages(
|
|
13146
|
+
scoped.bundle.graphLock,
|
|
13147
|
+
previousLock,
|
|
13148
|
+
selectedCurrentNodeIds,
|
|
13149
|
+
selectedPreviousNodeIds,
|
|
13150
|
+
/* @__PURE__ */ new Set([rootId])
|
|
13151
|
+
);
|
|
13152
|
+
const graphLockDigest = createHash12("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
13153
|
+
return {
|
|
13154
|
+
...scoped,
|
|
13155
|
+
bundle: { ...scoped.bundle, graphLock },
|
|
13156
|
+
graphLockDigest,
|
|
13157
|
+
graphDiff: diffGraphLocks(previousLock, graphLock),
|
|
13158
|
+
plan: {
|
|
13159
|
+
...scoped.plan,
|
|
13160
|
+
graphLockDigest
|
|
13161
|
+
}
|
|
13162
|
+
};
|
|
13163
|
+
}
|
|
13164
|
+
function graphRootClosure(lock, rootId) {
|
|
13165
|
+
const root = lock.canonical.roots.find((candidate) => candidate.rootId === rootId);
|
|
13166
|
+
if (!root) return /* @__PURE__ */ new Set();
|
|
13167
|
+
const selected = /* @__PURE__ */ new Set();
|
|
13168
|
+
const queue = [root.graphNodeId];
|
|
13169
|
+
while (queue.length > 0) {
|
|
13170
|
+
const nodeId = queue.shift();
|
|
13171
|
+
if (selected.has(nodeId)) continue;
|
|
13172
|
+
selected.add(nodeId);
|
|
13173
|
+
for (const edge of lock.canonical.edges) {
|
|
13174
|
+
if (edge.from === nodeId) queue.push(edge.to);
|
|
13175
|
+
}
|
|
13176
|
+
}
|
|
13177
|
+
return selected;
|
|
13178
|
+
}
|
|
11681
13179
|
function transformOutOfScopeOperation(operation, entry, targetRoot, scopeDescription) {
|
|
11682
13180
|
if (operation.action === "skip") return [operation];
|
|
11683
13181
|
if (operation.action === "update" || operation.action === "drift") {
|
|
@@ -11723,7 +13221,7 @@ function keepManifestEntryOperation(entry, targetRoot, scopeDescription, operati
|
|
|
11723
13221
|
artifactType: entry.artifactType,
|
|
11724
13222
|
artifactName: entry.artifactName,
|
|
11725
13223
|
kind: entry.kind,
|
|
11726
|
-
destPath: operation?.destPath ??
|
|
13224
|
+
destPath: operation?.destPath ?? join45(targetRoot, entry.path),
|
|
11727
13225
|
relativeDestPath: entry.path,
|
|
11728
13226
|
desiredHash: entry.sourceHash,
|
|
11729
13227
|
currentHash: operation?.currentHash ?? entry.hash,
|
|
@@ -11785,6 +13283,7 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
11785
13283
|
rootId: pkg2.name,
|
|
11786
13284
|
source: pkg2.source,
|
|
11787
13285
|
mode: pkg2.mode,
|
|
13286
|
+
version: pkg2.version,
|
|
11788
13287
|
ref: pkg2.requestedRef,
|
|
11789
13288
|
select: pkg2.selection ? void 0 : normalizeArtifactSelectors(pkg2.select, pkg2.skills),
|
|
11790
13289
|
selection: pkg2.selection,
|
|
@@ -11839,7 +13338,7 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
11839
13338
|
if (!options.dryRun) {
|
|
11840
13339
|
console.log(formatUninstallResult(result));
|
|
11841
13340
|
}
|
|
11842
|
-
if (renderedRoot) await
|
|
13341
|
+
if (renderedRoot) await rm12(renderedRoot, { recursive: true, force: true });
|
|
11843
13342
|
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
11844
13343
|
}
|
|
11845
13344
|
}
|
|
@@ -11921,31 +13420,341 @@ async function readTargetGraphLock(target, options) {
|
|
|
11921
13420
|
return { adapter, path, lock: await readGraphLock(path) };
|
|
11922
13421
|
}
|
|
11923
13422
|
async function printStatus(target, options) {
|
|
13423
|
+
const report = await collectTargetStatus(target, options);
|
|
13424
|
+
printTargetStatus(report);
|
|
13425
|
+
}
|
|
13426
|
+
async function collectTargetStatus(target, options) {
|
|
11924
13427
|
const config = await readMergedWorkspaceConfig(target.workspaceRoot);
|
|
11925
13428
|
const adapterOptions = adapterOptionsForTarget(target, options);
|
|
11926
|
-
const adapter = await resolveAdapterForTarget(target, adapterOptions);
|
|
13429
|
+
const adapter = await resolveAdapterForTarget(target, { ...adapterOptions, warn: () => void 0 });
|
|
11927
13430
|
const transport = transportForTarget(target);
|
|
11928
13431
|
const installationType = options.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
|
|
11929
13432
|
const state = installStateForTarget(target, adapter, adapterOptions, installationType);
|
|
11930
|
-
console.log(`Status for ${adapter.name}/${installationType} at ${state.installRoot}`);
|
|
11931
|
-
if (config.packages.length === 0) {
|
|
11932
|
-
console.log(`Configured packages: none at ${target.workspaceRoot}`);
|
|
11933
|
-
return;
|
|
11934
|
-
}
|
|
11935
|
-
console.log("Configured packages:");
|
|
11936
|
-
for (const pkg of config.packages) {
|
|
11937
|
-
console.log(`- ${pkg.name} (${pkg.mode}) ${pkg.source}`);
|
|
11938
|
-
}
|
|
11939
13433
|
const manifest = await readInstallManifest(state.installRoot, adapter.name, transport, state);
|
|
11940
|
-
|
|
13434
|
+
let graphLockPath = null;
|
|
13435
|
+
let graphLock;
|
|
11941
13436
|
try {
|
|
11942
|
-
const
|
|
11943
|
-
|
|
11944
|
-
|
|
13437
|
+
const result = await readTargetGraphLock(target, adapterOptions);
|
|
13438
|
+
graphLockPath = result.path;
|
|
13439
|
+
graphLock = result.lock;
|
|
11945
13440
|
} catch {
|
|
11946
|
-
|
|
13441
|
+
graphLock = void 0;
|
|
13442
|
+
}
|
|
13443
|
+
const packages = [];
|
|
13444
|
+
for (const pkg of config.packages) {
|
|
13445
|
+
const root = graphLock?.canonical.roots.find((candidate) => candidate.rootId === pkg.name);
|
|
13446
|
+
const node = root ? graphLock?.canonical.nodes.find((candidate) => candidate.id === root.graphNodeId) : void 0;
|
|
13447
|
+
const availability = await discoverPackageVersions(pkg, target.workspaceRoot, {
|
|
13448
|
+
forceRefresh: options.refresh,
|
|
13449
|
+
offline: options.offline
|
|
13450
|
+
});
|
|
13451
|
+
const installed = node && manifest?.entries.some((entry) => "graphNodeId" in entry && entry.graphNodeId === node.id) ? node.version : null;
|
|
13452
|
+
const locked = node?.version ?? null;
|
|
13453
|
+
const baseline = installed ?? locked;
|
|
13454
|
+
packages.push({
|
|
13455
|
+
name: pkg.name,
|
|
13456
|
+
source: pkg.source,
|
|
13457
|
+
mode: pkg.mode,
|
|
13458
|
+
policy: pkg.version ?? "*",
|
|
13459
|
+
installed,
|
|
13460
|
+
locked,
|
|
13461
|
+
latestAllowed: availability.latestAllowed,
|
|
13462
|
+
latestOverall: availability.latestOverall,
|
|
13463
|
+
availability: availability.stale ? "STALE" : availability.checkedAt ? "FRESH" : "UNKNOWN",
|
|
13464
|
+
checkedAt: availability.checkedAt,
|
|
13465
|
+
...availability.error ? { error: availability.error } : {},
|
|
13466
|
+
updateAvailableAllowed: Boolean(
|
|
13467
|
+
baseline && availability.latestAllowed && compareSemverStrings(availability.latestAllowed, baseline) > 0
|
|
13468
|
+
),
|
|
13469
|
+
updateAvailableOverall: Boolean(
|
|
13470
|
+
baseline && availability.latestOverall && compareSemverStrings(availability.latestOverall, baseline) > 0
|
|
13471
|
+
)
|
|
13472
|
+
});
|
|
11947
13473
|
}
|
|
11948
|
-
await
|
|
13474
|
+
const pending = await collectPendingInstallWork(target, options);
|
|
13475
|
+
const artifacts = (graphLock?.canonical.artifacts ?? []).map((artifact) => {
|
|
13476
|
+
const node = graphLock?.canonical.nodes.find((candidate) => candidate.id === artifact.graphNodeId);
|
|
13477
|
+
const installed = manifest?.entries.some((entry) => {
|
|
13478
|
+
if (!("graphNodeId" in entry) || entry.graphNodeId !== artifact.graphNodeId) return false;
|
|
13479
|
+
if ("logicalSelector" in entry && entry.logicalSelector) return entry.logicalSelector === artifact.logicalSelector;
|
|
13480
|
+
return entry.artifactType === artifact.type && entry.artifactName === artifact.name;
|
|
13481
|
+
}) ?? false;
|
|
13482
|
+
return {
|
|
13483
|
+
selector: artifact.logicalSelector,
|
|
13484
|
+
type: artifact.type,
|
|
13485
|
+
name: artifact.name,
|
|
13486
|
+
installName: artifact.installName,
|
|
13487
|
+
packageName: node?.name ?? null,
|
|
13488
|
+
packageVersion: node?.version ?? null,
|
|
13489
|
+
hash: artifact.hash,
|
|
13490
|
+
installed
|
|
13491
|
+
};
|
|
13492
|
+
});
|
|
13493
|
+
const health = [];
|
|
13494
|
+
if (!manifest || !graphLock) health.push("FAIL");
|
|
13495
|
+
if (pending.error) health.push("DEGRADED");
|
|
13496
|
+
if (pending.driftCount > 0 || pending.conflictCount > 0) health.push("FAIL");
|
|
13497
|
+
else if (pending.pendingCount > 0) health.push("WARN");
|
|
13498
|
+
if (packages.some((pkg) => pkg.availability === "STALE" || pkg.error)) health.push("DEGRADED");
|
|
13499
|
+
else if (packages.some((pkg) => pkg.updateAvailableAllowed || pkg.updateAvailableOverall)) health.push("WARN");
|
|
13500
|
+
return {
|
|
13501
|
+
adapter: adapter.name,
|
|
13502
|
+
installationType,
|
|
13503
|
+
targetRoot: state.installRoot,
|
|
13504
|
+
health: worstStatusHealth(health),
|
|
13505
|
+
manifestRevision: manifest?.revision ?? null,
|
|
13506
|
+
manifestEntryCount: manifest?.entries.length ?? 0,
|
|
13507
|
+
graphLockPath,
|
|
13508
|
+
packageCount: packages.length,
|
|
13509
|
+
artifactCount: graphLock?.canonical.artifacts.length ?? 0,
|
|
13510
|
+
pendingCount: pending.pendingCount,
|
|
13511
|
+
driftCount: pending.driftCount,
|
|
13512
|
+
conflictCount: pending.conflictCount,
|
|
13513
|
+
...pending.error ? { error: pending.error } : {},
|
|
13514
|
+
packages,
|
|
13515
|
+
artifacts
|
|
13516
|
+
};
|
|
13517
|
+
}
|
|
13518
|
+
async function resolveSelectedCompositeProfile(options) {
|
|
13519
|
+
const workspaceRoot = await findWorkspaceRoot(options.targetRoot ?? process.cwd());
|
|
13520
|
+
const config = await readMergedWorkspaceConfig(workspaceRoot);
|
|
13521
|
+
const name = options.profile ?? (options.all && config.profiles.all ? "all" : void 0);
|
|
13522
|
+
if (!name) return void 0;
|
|
13523
|
+
const profile = config.profiles[name];
|
|
13524
|
+
if (!profile) throw new Error(`Unknown profile: ${name}`);
|
|
13525
|
+
if (!isCompositeWorkspaceProfile(profile)) return void 0;
|
|
13526
|
+
const chain = parseCompositeChain();
|
|
13527
|
+
assertNoCompositeCycle(workspaceRoot, name, chain);
|
|
13528
|
+
return { workspaceRoot, name, profile };
|
|
13529
|
+
}
|
|
13530
|
+
async function collectCompositeStatus(workspaceRoot, profileName, profile, options) {
|
|
13531
|
+
const chain = parseCompositeChain();
|
|
13532
|
+
const members = await collectCompositeMembers({
|
|
13533
|
+
cliVersion: CLI_VERSION,
|
|
13534
|
+
workspaceRoot,
|
|
13535
|
+
profileName,
|
|
13536
|
+
profileTtlSeconds: profile.refreshTtlSeconds,
|
|
13537
|
+
members: profile.members,
|
|
13538
|
+
refresh: options.refresh,
|
|
13539
|
+
offline: options.offline,
|
|
13540
|
+
chain
|
|
13541
|
+
});
|
|
13542
|
+
const repository = await collectRepositoryStatus(workspaceRoot);
|
|
13543
|
+
const repositoryHealth = repository.available && repository.ahead === 0 && repository.behind === 0 && repository.dirtyCount === 0 ? "PASS" : "WARN";
|
|
13544
|
+
return {
|
|
13545
|
+
schemaVersion: 1,
|
|
13546
|
+
command: "status",
|
|
13547
|
+
agentwheelVersion: CLI_VERSION,
|
|
13548
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13549
|
+
workspace: workspaceRoot,
|
|
13550
|
+
profile: profileName,
|
|
13551
|
+
health: worstStatusHealth([...members.map((member) => member.health), repositoryHealth]),
|
|
13552
|
+
repository,
|
|
13553
|
+
targets: [],
|
|
13554
|
+
members
|
|
13555
|
+
};
|
|
13556
|
+
}
|
|
13557
|
+
async function runCompositeUpdate(workspaceRoot, profileName, profile, packageName, options) {
|
|
13558
|
+
const preflight = await collectCompositeStatus(workspaceRoot, profileName, profile, options);
|
|
13559
|
+
printStatusReport(preflight);
|
|
13560
|
+
const blockers = preflight.members.filter((member) => blocksCompositeApply(member.health));
|
|
13561
|
+
if (blockers.length > 0) {
|
|
13562
|
+
throw new Error(
|
|
13563
|
+
`Composite update blocked before member execution: ` + blockers.map((member) => `${member.id}=${member.health}`).join(", ")
|
|
13564
|
+
);
|
|
13565
|
+
}
|
|
13566
|
+
const incomingChain = parseCompositeChain();
|
|
13567
|
+
const memberChain = [...incomingChain, compositeKey(workspaceRoot, profileName)];
|
|
13568
|
+
for (const member of profile.members) {
|
|
13569
|
+
if (!options.dryRun) {
|
|
13570
|
+
const before = preflight.members.find((candidate) => candidate.id === member.id);
|
|
13571
|
+
const revalidated = await collectCompositeMembers({
|
|
13572
|
+
cliVersion: CLI_VERSION,
|
|
13573
|
+
workspaceRoot,
|
|
13574
|
+
profileName,
|
|
13575
|
+
profileTtlSeconds: profile.refreshTtlSeconds,
|
|
13576
|
+
members: [member],
|
|
13577
|
+
refresh: true,
|
|
13578
|
+
chain: incomingChain
|
|
13579
|
+
});
|
|
13580
|
+
const current = revalidated[0];
|
|
13581
|
+
if (blocksCompositeApply(current.health)) {
|
|
13582
|
+
throw new Error(`Composite update stopped before ${member.id}: revalidation is ${current.health}.`);
|
|
13583
|
+
}
|
|
13584
|
+
if (statusRevisionSignature(before?.report) !== statusRevisionSignature(current.report)) {
|
|
13585
|
+
throw new Error(`Composite update stopped before ${member.id}: member revision changed after preflight.`);
|
|
13586
|
+
}
|
|
13587
|
+
}
|
|
13588
|
+
const args = compositeUpdateArguments(member.profile, packageName, options);
|
|
13589
|
+
console.log(`${options.dryRun ? "Plan" : "Update"} member ${member.id}:`);
|
|
13590
|
+
const result = await runMemberAgentwheel(member, workspaceRoot, args, memberChain);
|
|
13591
|
+
if (result.stdout.trim()) console.log(result.stdout.trimEnd());
|
|
13592
|
+
if (result.stderr.trim()) console.error(result.stderr.trimEnd());
|
|
13593
|
+
}
|
|
13594
|
+
}
|
|
13595
|
+
async function runCompositeInstall(workspaceRoot, profileName, profile, nameOrSource, options, behavior) {
|
|
13596
|
+
const preflight = await collectCompositeStatus(workspaceRoot, profileName, profile, options);
|
|
13597
|
+
printStatusReport(preflight);
|
|
13598
|
+
const blockers = preflight.members.filter((member) => blocksCompositeApply(member.health));
|
|
13599
|
+
if (blockers.length > 0) {
|
|
13600
|
+
throw new Error(
|
|
13601
|
+
`Composite ${behavior.apply ? "install" : "plan"} blocked before member execution: ` + blockers.map((member) => `${member.id}=${member.health}`).join(", ")
|
|
13602
|
+
);
|
|
13603
|
+
}
|
|
13604
|
+
const incomingChain = parseCompositeChain();
|
|
13605
|
+
const memberChain = [...incomingChain, compositeKey(workspaceRoot, profileName)];
|
|
13606
|
+
for (const member of profile.members) {
|
|
13607
|
+
if (behavior.apply) {
|
|
13608
|
+
const before = preflight.members.find((candidate) => candidate.id === member.id);
|
|
13609
|
+
const revalidated = await collectCompositeMembers({
|
|
13610
|
+
cliVersion: CLI_VERSION,
|
|
13611
|
+
workspaceRoot,
|
|
13612
|
+
profileName,
|
|
13613
|
+
profileTtlSeconds: profile.refreshTtlSeconds,
|
|
13614
|
+
members: [member],
|
|
13615
|
+
refresh: true,
|
|
13616
|
+
chain: incomingChain
|
|
13617
|
+
});
|
|
13618
|
+
const current = revalidated[0];
|
|
13619
|
+
if (blocksCompositeApply(current.health)) {
|
|
13620
|
+
throw new Error(`Composite install stopped before ${member.id}: revalidation is ${current.health}.`);
|
|
13621
|
+
}
|
|
13622
|
+
if (statusRevisionSignature(before?.report) !== statusRevisionSignature(current.report)) {
|
|
13623
|
+
throw new Error(`Composite install stopped before ${member.id}: member revision changed after preflight.`);
|
|
13624
|
+
}
|
|
13625
|
+
}
|
|
13626
|
+
const args = compositeInstallArguments(member.profile, nameOrSource, options, behavior.apply);
|
|
13627
|
+
console.log(`${behavior.apply ? "Install" : "Plan"} member ${member.id}:`);
|
|
13628
|
+
const result = await runMemberAgentwheel(member, workspaceRoot, args, memberChain);
|
|
13629
|
+
if (result.stdout.trim()) console.log(result.stdout.trimEnd());
|
|
13630
|
+
if (result.stderr.trim()) console.error(result.stderr.trimEnd());
|
|
13631
|
+
}
|
|
13632
|
+
}
|
|
13633
|
+
function compositeInstallArguments(profile, nameOrSource, options, apply) {
|
|
13634
|
+
const args = [apply ? "install" : "plan"];
|
|
13635
|
+
if (nameOrSource) args.push(nameOrSource);
|
|
13636
|
+
args.push("--profile", profile);
|
|
13637
|
+
if (options.refresh) args.push("--refresh");
|
|
13638
|
+
if (options.forceDrift) args.push("--force-drift");
|
|
13639
|
+
if (options.forceConflict) args.push("--force-conflict");
|
|
13640
|
+
if (options.replaceConflict) args.push("--replace-conflict");
|
|
13641
|
+
if (options.executePlugins) args.push("--execute-plugins");
|
|
13642
|
+
if (shouldReloadRuntimes(options)) args.push("--reload-runtimes");
|
|
13643
|
+
if (options.noDeps) args.push("--no-deps");
|
|
13644
|
+
if (options.frozenLock) args.push("--frozen-lock");
|
|
13645
|
+
if (options.onlySource) args.push("--only-source");
|
|
13646
|
+
for (const selection of options.select ?? []) args.push("--select", selection);
|
|
13647
|
+
for (const skill of options.skill ?? []) args.push("--skill", skill);
|
|
13648
|
+
for (const trust of options.trust ?? []) args.push("--trust", trust);
|
|
13649
|
+
if (options.yes) args.push("--yes");
|
|
13650
|
+
return args;
|
|
13651
|
+
}
|
|
13652
|
+
function compositeUpdateArguments(profile, packageName, options) {
|
|
13653
|
+
const args = ["update"];
|
|
13654
|
+
if (packageName) args.push(packageName);
|
|
13655
|
+
args.push("--profile", profile);
|
|
13656
|
+
if (options.dryRun) args.push("--dry-run");
|
|
13657
|
+
if (options.refresh) args.push("--refresh");
|
|
13658
|
+
if (options.forceDrift) args.push("--force-drift");
|
|
13659
|
+
if (options.forceConflict) args.push("--force-conflict");
|
|
13660
|
+
if (options.replaceConflict) args.push("--replace-conflict");
|
|
13661
|
+
if (options.executePlugins) args.push("--execute-plugins");
|
|
13662
|
+
if (shouldReloadRuntimes(options)) args.push("--reload-runtimes");
|
|
13663
|
+
if (options.noDeps) args.push("--no-deps");
|
|
13664
|
+
if (options.frozenLock) args.push("--frozen-lock");
|
|
13665
|
+
for (const dependency of options.dependency ?? []) args.push("--dependency", dependency);
|
|
13666
|
+
for (const trust of options.trust ?? []) args.push("--trust", trust);
|
|
13667
|
+
if (options.yes) args.push("--yes");
|
|
13668
|
+
return args;
|
|
13669
|
+
}
|
|
13670
|
+
function statusRevisionSignature(report) {
|
|
13671
|
+
if (!report) return "missing";
|
|
13672
|
+
return JSON.stringify({
|
|
13673
|
+
profile: report.profile,
|
|
13674
|
+
repository: {
|
|
13675
|
+
head: report.repository.head,
|
|
13676
|
+
ahead: report.repository.ahead,
|
|
13677
|
+
behind: report.repository.behind,
|
|
13678
|
+
dirtyCount: report.repository.dirtyCount
|
|
13679
|
+
},
|
|
13680
|
+
targets: report.targets.map((target) => ({
|
|
13681
|
+
adapter: target.adapter,
|
|
13682
|
+
installationType: target.installationType,
|
|
13683
|
+
targetRoot: target.targetRoot,
|
|
13684
|
+
manifestRevision: target.manifestRevision,
|
|
13685
|
+
graphLockPath: target.graphLockPath,
|
|
13686
|
+
packages: target.packages.map((pkg) => ({
|
|
13687
|
+
name: pkg.name,
|
|
13688
|
+
installed: pkg.installed,
|
|
13689
|
+
locked: pkg.locked,
|
|
13690
|
+
latestAllowed: pkg.latestAllowed,
|
|
13691
|
+
latestOverall: pkg.latestOverall
|
|
13692
|
+
}))
|
|
13693
|
+
})),
|
|
13694
|
+
members: report.members.map((member) => ({
|
|
13695
|
+
id: member.id,
|
|
13696
|
+
report: statusRevisionSignature(member.report)
|
|
13697
|
+
}))
|
|
13698
|
+
});
|
|
13699
|
+
}
|
|
13700
|
+
async function statusReport(workspace, profile, targets) {
|
|
13701
|
+
const repository = await collectRepositoryStatus(workspace);
|
|
13702
|
+
const repositoryHealth = repository.available && repository.ahead === 0 && repository.behind === 0 && repository.dirtyCount === 0 ? "PASS" : "WARN";
|
|
13703
|
+
return {
|
|
13704
|
+
schemaVersion: 1,
|
|
13705
|
+
command: "status",
|
|
13706
|
+
agentwheelVersion: CLI_VERSION,
|
|
13707
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13708
|
+
workspace,
|
|
13709
|
+
profile,
|
|
13710
|
+
health: worstStatusHealth([...targets.map((target) => target.health), repositoryHealth]),
|
|
13711
|
+
repository,
|
|
13712
|
+
targets,
|
|
13713
|
+
members: []
|
|
13714
|
+
};
|
|
13715
|
+
}
|
|
13716
|
+
function printStatusReport(report) {
|
|
13717
|
+
console.log(`Status ${report.health} for profile ${report.profile ?? "(direct)"} at ${report.workspace}`);
|
|
13718
|
+
console.log(
|
|
13719
|
+
`Repository: ${report.repository.available ? report.repository.branch ?? "detached" : "unavailable"}; ahead=${report.repository.ahead}; behind=${report.repository.behind}; dirty=${report.repository.dirtyCount}`
|
|
13720
|
+
);
|
|
13721
|
+
if (report.members.length > 0) {
|
|
13722
|
+
console.log("MEMBER TRANSPORT PROFILE VERSION HEALTH CACHE");
|
|
13723
|
+
for (const member of report.members) {
|
|
13724
|
+
console.log([
|
|
13725
|
+
member.id,
|
|
13726
|
+
member.transport,
|
|
13727
|
+
member.profile,
|
|
13728
|
+
member.agentwheelVersion ?? "unknown",
|
|
13729
|
+
member.health,
|
|
13730
|
+
member.stale ? "stale" : "fresh"
|
|
13731
|
+
].join(" "));
|
|
13732
|
+
if (member.error) console.log(` ${member.error}`);
|
|
13733
|
+
}
|
|
13734
|
+
}
|
|
13735
|
+
for (const target of report.targets) printTargetStatus(target);
|
|
13736
|
+
}
|
|
13737
|
+
function printTargetStatus(target) {
|
|
13738
|
+
console.log(`Status for ${target.adapter}/${target.installationType} at ${target.targetRoot} (health: ${target.health})`);
|
|
13739
|
+
console.log(target.manifestRevision ? `Install manifest: ${target.manifestEntryCount} entries, revision ${target.manifestRevision}` : "Install manifest: missing");
|
|
13740
|
+
console.log(target.graphLockPath ? `Graph lock: ${target.graphLockPath} (${target.artifactCount} artifacts)` : "Graph lock: missing");
|
|
13741
|
+
console.log("PACKAGE MODE POLICY INSTALLED LOCKED LATEST ALLOWED LATEST OVERALL STATUS");
|
|
13742
|
+
for (const pkg of target.packages) {
|
|
13743
|
+
console.log([
|
|
13744
|
+
pkg.name,
|
|
13745
|
+
pkg.mode,
|
|
13746
|
+
pkg.policy,
|
|
13747
|
+
pkg.installed ?? "-",
|
|
13748
|
+
pkg.locked ?? "-",
|
|
13749
|
+
pkg.latestAllowed ?? "-",
|
|
13750
|
+
pkg.latestOverall ?? "-",
|
|
13751
|
+
pkg.availability
|
|
13752
|
+
].join(" "));
|
|
13753
|
+
}
|
|
13754
|
+
console.log(`Artifacts: ${target.artifactCount} locked, ${target.artifacts.filter((artifact) => artifact.installed).length} installed`);
|
|
13755
|
+
if (target.error) console.log(`Pending install work: unavailable (${target.error})`);
|
|
13756
|
+
else if (target.pendingCount === 0) console.log("Pending install work: none");
|
|
13757
|
+
else console.log(`Pending install work: ${target.pendingCount} (drift=${target.driftCount}, conflict=${target.conflictCount})`);
|
|
11949
13758
|
}
|
|
11950
13759
|
async function journalStateForTarget(target, options) {
|
|
11951
13760
|
const adapterOptions = adapterOptionsForTarget(target, options);
|
|
@@ -11955,27 +13764,32 @@ async function journalStateForTarget(target, options) {
|
|
|
11955
13764
|
const state = installStateForTarget(target, adapter, adapterOptions, installationType);
|
|
11956
13765
|
return { adapter, transport, installationType, installRoot: state.installRoot, state };
|
|
11957
13766
|
}
|
|
11958
|
-
async function
|
|
13767
|
+
async function collectPendingInstallWork(target, options) {
|
|
11959
13768
|
let results = [];
|
|
11960
13769
|
try {
|
|
11961
|
-
results = await buildGraphPlansForTarget(
|
|
13770
|
+
results = await buildGraphPlansForTarget(
|
|
13771
|
+
target,
|
|
13772
|
+
void 0,
|
|
13773
|
+
{ ...options, dryRun: true, warn: options.warn ?? (() => void 0) },
|
|
13774
|
+
{ mode: "install" }
|
|
13775
|
+
);
|
|
11962
13776
|
const operations = results.flatMap((result) => result.plan.operations);
|
|
11963
13777
|
const pending = operations.filter(isPendingInstallOperation);
|
|
11964
|
-
|
|
11965
|
-
console.log("Pending install work: none");
|
|
11966
|
-
return;
|
|
11967
|
-
}
|
|
11968
|
-
const counts = [...pending.reduce((map, operation) => {
|
|
13778
|
+
const counts = Object.fromEntries([...pending.reduce((map, operation) => {
|
|
11969
13779
|
map.set(operation.action, (map.get(operation.action) ?? 0) + 1);
|
|
11970
13780
|
return map;
|
|
11971
|
-
}, /* @__PURE__ */ new Map())]
|
|
11972
|
-
|
|
11973
|
-
|
|
13781
|
+
}, /* @__PURE__ */ new Map())]);
|
|
13782
|
+
return {
|
|
13783
|
+
pendingCount: pending.length,
|
|
13784
|
+
driftCount: counts.drift ?? 0,
|
|
13785
|
+
conflictCount: counts.conflict ?? 0,
|
|
13786
|
+
counts
|
|
13787
|
+
};
|
|
11974
13788
|
} catch (error) {
|
|
11975
13789
|
const message = error instanceof Error ? error.message : String(error);
|
|
11976
|
-
|
|
13790
|
+
return { pendingCount: 0, driftCount: 0, conflictCount: 0, counts: {}, error: message };
|
|
11977
13791
|
} finally {
|
|
11978
|
-
await Promise.all(results.map((result) =>
|
|
13792
|
+
await Promise.all(results.map((result) => rm12(result.bundle.root, { recursive: true, force: true })));
|
|
11979
13793
|
}
|
|
11980
13794
|
}
|
|
11981
13795
|
async function printDoctor(target, options) {
|
|
@@ -11992,12 +13806,12 @@ async function printDoctor(target, options) {
|
|
|
11992
13806
|
const requestedSkills = doctorSkillRequests(target, options);
|
|
11993
13807
|
const skills = [];
|
|
11994
13808
|
for (const request of requestedSkills) {
|
|
11995
|
-
const skillPath =
|
|
13809
|
+
const skillPath = join45(state.installRoot, targetMapping.dest, request.name);
|
|
11996
13810
|
const exists = await pathExists(skillPath);
|
|
11997
13811
|
const manifestEntry = manifest?.entries.find((entry) => {
|
|
11998
13812
|
if (entry.artifactType !== "skills") return false;
|
|
11999
13813
|
const legacyInstallName = "installName" in entry && typeof entry.installName === "string" ? entry.installName : void 0;
|
|
12000
|
-
return entry.artifactName === request.name || legacyInstallName === request.name || entry.path ===
|
|
13814
|
+
return entry.artifactName === request.name || legacyInstallName === request.name || entry.path === join45(targetMapping.dest, request.name);
|
|
12001
13815
|
});
|
|
12002
13816
|
const status = manifestEntry ? "managed" : exists ? "present-unmanaged" : "missing";
|
|
12003
13817
|
skills.push({
|
|
@@ -12077,7 +13891,7 @@ function doctorSkillLabel(name) {
|
|
|
12077
13891
|
return `${name} skill`;
|
|
12078
13892
|
}
|
|
12079
13893
|
function isSyncwheelWorkspace(targetRoot) {
|
|
12080
|
-
return existsSync(
|
|
13894
|
+
return existsSync(join45(targetRoot, ".syncwheel", "manifest.json"));
|
|
12081
13895
|
}
|
|
12082
13896
|
function skillInstallCommand(adapter, installationType, options, skill, behavior = {}) {
|
|
12083
13897
|
const args = [
|
|
@@ -12145,7 +13959,7 @@ function normalizeRuntimeScopeOptions(options, behavior = {}) {
|
|
|
12145
13959
|
}
|
|
12146
13960
|
const canDefaultTargetRoot = !options.agent && !options.all && !options.allDetected && !options.profile;
|
|
12147
13961
|
if (!targetRoot && canDefaultTargetRoot && (options.user || installationType === "user" || behavior.defaultUser)) {
|
|
12148
|
-
targetRoot =
|
|
13962
|
+
targetRoot = homedir10();
|
|
12149
13963
|
}
|
|
12150
13964
|
if (!installationType && behavior.defaultUser) {
|
|
12151
13965
|
installationType = "user";
|
|
@@ -12165,12 +13979,12 @@ function looksLikeSourceSpecifier(value) {
|
|
|
12165
13979
|
return value.includes(":") || value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value === "~" || value.startsWith("~/");
|
|
12166
13980
|
}
|
|
12167
13981
|
function normalizeCliPath(value) {
|
|
12168
|
-
if (value === "~") return
|
|
12169
|
-
if (value.startsWith("~/")) return
|
|
12170
|
-
return
|
|
13982
|
+
if (value === "~") return homedir10();
|
|
13983
|
+
if (value.startsWith("~/")) return resolve22(homedir10(), value.slice(2));
|
|
13984
|
+
return resolve22(value);
|
|
12171
13985
|
}
|
|
12172
13986
|
function isHomePath(path) {
|
|
12173
|
-
return
|
|
13987
|
+
return resolve22(path) === resolve22(homedir10());
|
|
12174
13988
|
}
|
|
12175
13989
|
function adapterListFromOption(adapter) {
|
|
12176
13990
|
if (!adapter) return [];
|
|
@@ -12225,10 +14039,10 @@ function filterUninstallPlanBySelection(plan, selected) {
|
|
|
12225
14039
|
};
|
|
12226
14040
|
}
|
|
12227
14041
|
async function initPackage(root) {
|
|
12228
|
-
await
|
|
12229
|
-
await
|
|
12230
|
-
await
|
|
12231
|
-
const manifestPath =
|
|
14042
|
+
await mkdir23(join45(root, "instructions"), { recursive: true });
|
|
14043
|
+
await mkdir23(join45(root, "rules"), { recursive: true });
|
|
14044
|
+
await mkdir23(join45(root, "skills"), { recursive: true });
|
|
14045
|
+
const manifestPath = join45(root, "openpack.json");
|
|
12232
14046
|
const manifest = {
|
|
12233
14047
|
schemaVersion: 2,
|
|
12234
14048
|
name: "example/agentwheel-package",
|
|
@@ -12239,12 +14053,12 @@ async function initPackage(root) {
|
|
|
12239
14053
|
{ type: "skills", path: "skills" }
|
|
12240
14054
|
]
|
|
12241
14055
|
};
|
|
12242
|
-
await
|
|
14056
|
+
await writeFile22(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
12243
14057
|
`, "utf8");
|
|
12244
|
-
await
|
|
14058
|
+
await writeFile22(join45(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
|
|
12245
14059
|
}
|
|
12246
14060
|
async function defaultBootstrapPackage(_root) {
|
|
12247
|
-
const packageRoot = await findAgentwheelPackageRoot(
|
|
14061
|
+
const packageRoot = await findAgentwheelPackageRoot(dirname32(fileURLToPath3(import.meta.url)));
|
|
12248
14062
|
if (!packageRoot) return void 0;
|
|
12249
14063
|
return {
|
|
12250
14064
|
name: "agentwheel",
|
|
@@ -12288,10 +14102,10 @@ function withFleetExample(config) {
|
|
|
12288
14102
|
};
|
|
12289
14103
|
}
|
|
12290
14104
|
async function findAgentwheelPackageRoot(start) {
|
|
12291
|
-
let current =
|
|
14105
|
+
let current = resolve22(start);
|
|
12292
14106
|
while (true) {
|
|
12293
14107
|
if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
|
|
12294
|
-
const parent =
|
|
14108
|
+
const parent = dirname32(current);
|
|
12295
14109
|
if (parent === current) return void 0;
|
|
12296
14110
|
current = parent;
|
|
12297
14111
|
}
|
|
@@ -12312,6 +14126,49 @@ function printRegistryEntries(entries) {
|
|
|
12312
14126
|
console.log(`${entry.name} ${entry.type} ${entry.source} ${entry.description}${tags}`);
|
|
12313
14127
|
}
|
|
12314
14128
|
}
|
|
14129
|
+
function parseSearchScope(value) {
|
|
14130
|
+
const parsed = searchScopeSchema.safeParse(value);
|
|
14131
|
+
if (parsed.success) return parsed.data;
|
|
14132
|
+
throw new Error(`Invalid search scope: ${value}. Expected one of: ${searchScopeSchema.options.join(", ")}.`);
|
|
14133
|
+
}
|
|
14134
|
+
function parseSearchType(value) {
|
|
14135
|
+
const parsed = searchTypeSchema.safeParse(value);
|
|
14136
|
+
if (parsed.success) return parsed.data;
|
|
14137
|
+
throw new Error(`Invalid artifact type: ${value}. Expected one of: ${searchTypeSchema.options.join(", ")}.`);
|
|
14138
|
+
}
|
|
14139
|
+
function parseSearchEcosystem(value) {
|
|
14140
|
+
const parsed = searchEcosystemSchema.safeParse(value);
|
|
14141
|
+
if (parsed.success) return parsed.data;
|
|
14142
|
+
throw new Error(`Invalid ecosystem: ${value}. Expected one of: ${searchEcosystemSchema.options.join(", ")}.`);
|
|
14143
|
+
}
|
|
14144
|
+
function parseSearchLimit(value) {
|
|
14145
|
+
const limit = Number(value);
|
|
14146
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
|
|
14147
|
+
throw new Error(`Invalid search limit: ${value}. Expected an integer from 1 to 100.`);
|
|
14148
|
+
}
|
|
14149
|
+
return limit;
|
|
14150
|
+
}
|
|
14151
|
+
function printSearchResults(query, results) {
|
|
14152
|
+
if (results.length === 0) {
|
|
14153
|
+
console.log(`No artifacts found for ${JSON.stringify(query)}.`);
|
|
14154
|
+
return;
|
|
14155
|
+
}
|
|
14156
|
+
for (const [index, result] of results.entries()) {
|
|
14157
|
+
const ecosystem = result.ecosystem ?? "unknown";
|
|
14158
|
+
const provenances = result.provenances.join("+");
|
|
14159
|
+
console.log(
|
|
14160
|
+
`${index + 1}. ${result.name} [type=${result.type}; ecosystem=${ecosystem}; installability=${result.installability}; provenance=${provenances}]`
|
|
14161
|
+
);
|
|
14162
|
+
console.log(` ${result.description || "(no description)"}`);
|
|
14163
|
+
if (result.installCommand) {
|
|
14164
|
+
console.log(` Install: ${result.installCommand}`);
|
|
14165
|
+
} else if (result.source) {
|
|
14166
|
+
console.log(` Source: ${result.source}`);
|
|
14167
|
+
} else {
|
|
14168
|
+
console.log(" Install: unavailable");
|
|
14169
|
+
}
|
|
14170
|
+
}
|
|
14171
|
+
}
|
|
12315
14172
|
async function main() {
|
|
12316
14173
|
await maybeCheckForUpdate({
|
|
12317
14174
|
currentVersion: CLI_VERSION,
|