agentwheel 0.14.5 → 0.14.7

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/dist/index.js CHANGED
@@ -6,12 +6,13 @@ import {
6
6
  isIgnoredGeneratedEntry,
7
7
  pathExists,
8
8
  writeJsonAtomic
9
- } from "./chunk-B3FMBTWC.js";
9
+ } from "./chunk-QJTTISLY.js";
10
10
 
11
11
  // src/cli/index.ts
12
- import { mkdir as mkdir17, rm as rm10, writeFile as writeFile16 } from "fs/promises";
12
+ import { existsSync } from "fs";
13
+ import { mkdir as mkdir21, rm as rm11, writeFile as writeFile20 } from "fs/promises";
13
14
  import { homedir as homedir9 } from "os";
14
- import { dirname as dirname25, join as join38, resolve as resolve18 } from "path";
15
+ import { dirname as dirname30, join as join41, resolve as resolve20 } from "path";
15
16
  import { fileURLToPath as fileURLToPath3 } from "url";
16
17
  import { Command } from "commander";
17
18
 
@@ -60,6 +61,18 @@ var packageItemRequireSchema = z.union([
60
61
  z.string().min(1),
61
62
  packageItemRequireObjectSchema
62
63
  ]);
64
+ var packageItemSuggestObjectSchema = z.object({
65
+ alias: z.string().min(1),
66
+ select: z.array(z.string().min(1)).optional(),
67
+ optional: z.boolean().optional(),
68
+ runtimes: z.array(z.string().min(1)).optional(),
69
+ reason: z.string().min(1).optional(),
70
+ when: z.string().min(1).optional()
71
+ }).passthrough();
72
+ var packageItemSuggestSchema = z.union([
73
+ z.string().min(1),
74
+ packageItemSuggestObjectSchema
75
+ ]);
63
76
  var composedFromEntrySchema = z.object({
64
77
  selector: z.string().min(1),
65
78
  hash: z.string().min(16)
@@ -78,6 +91,7 @@ var artifactSchema = z.object({
78
91
  assets: z.array(packageAssetSchema).optional(),
79
92
  required: z.boolean().optional(),
80
93
  requires: z.array(packageItemRequireSchema).optional(),
94
+ suggests: z.array(packageItemSuggestSchema).optional(),
81
95
  compose: z.array(packageComposeEntrySchema).optional(),
82
96
  runtimes: z.array(z.string().min(1)).optional(),
83
97
  composedFrom: z.array(composedFromEntrySchema).optional()
@@ -97,12 +111,13 @@ var targetMappingSchema = z2.object({
97
111
  "codex-plugin",
98
112
  "hermes-plugin",
99
113
  "copilot-plugin",
114
+ "openclaw-subagent",
100
115
  "codex-subagent",
101
116
  "copilot-instruction",
102
117
  "copilot-prompt",
103
118
  "copilot-agent"
104
119
  ]).optional(),
105
- merge: z2.enum(["json-deep", "yaml-deep", "codex-toml-mcp"]).optional(),
120
+ merge: z2.enum(["json-deep", "openclaw-json-deep", "yaml-deep", "codex-toml-mcp"]).optional(),
106
121
  mode: z2.enum(["managed-block"]).optional()
107
122
  });
108
123
  var targetRegistrySchema = z2.record(installationTypeSchema, targetMappingSchema);
@@ -362,14 +377,17 @@ var openClawAdapter = {
362
377
  local: { enabled: true, dest: "skills" },
363
378
  user: { enabled: true, root: "home", dest: ".openclaw/skills" }
364
379
  },
380
+ subagents: {
381
+ user: { enabled: true, root: "home", dest: ".openclaw/workspace-subagents", semantic: "openclaw-subagent" }
382
+ },
365
383
  mcp: {
366
- user: { enabled: true, root: "home", dest: ".openclaw/openclaw.json", merge: "json-deep" }
384
+ user: { enabled: true, root: "home", dest: ".openclaw/openclaw.json", merge: "openclaw-json-deep" }
367
385
  },
368
386
  settings: {
369
- user: { enabled: true, root: "home", dest: ".openclaw/openclaw.json", merge: "json-deep" }
387
+ user: { enabled: true, root: "home", dest: ".openclaw/openclaw.json", merge: "openclaw-json-deep" }
370
388
  },
371
389
  plugins: {
372
- local: { enabled: true, dest: ".openclaw/plugins", formats: ["openclaw-plugin"], semantic: "openclaw-plugin" }
390
+ local: { enabled: true, dest: ".openclaw/plugins", formats: ["openclaw-plugin", "openclaw-clawhub-plugin"], semantic: "openclaw-plugin" }
373
391
  }
374
392
  }
375
393
  };
@@ -453,9 +471,9 @@ async function resolveAdapter(options) {
453
471
 
454
472
  // src/install/apply.ts
455
473
  import { execFile as execFile3 } from "child_process";
456
- import { mkdtemp as mkdtemp2, rm as rm4, writeFile as writeFile8 } from "fs/promises";
474
+ import { mkdtemp as mkdtemp2, rm as rm4, writeFile as writeFile9 } from "fs/promises";
457
475
  import { tmpdir as tmpdir3 } from "os";
458
- import { basename as basename3, join as join5 } from "path";
476
+ import { basename as basename3, dirname as dirname10, join as join5 } from "path";
459
477
  import { promisify as promisify3 } from "util";
460
478
 
461
479
  // src/model/graph-lock.ts
@@ -759,13 +777,13 @@ async function spawnWithInput(command, args, input) {
759
777
  if (result.stderr) throw new Error(result.stderr);
760
778
  }
761
779
  function waitForProcess(child, label) {
762
- return new Promise((resolve19, reject) => {
780
+ return new Promise((resolve21, reject) => {
763
781
  const stderr = [];
764
782
  child.stderr?.on("data", (chunk) => stderr.push(chunk));
765
783
  child.on("error", reject);
766
784
  child.on("close", (code) => {
767
785
  const message = Buffer.concat(stderr).toString("utf8");
768
- if (code === 0) resolve19({ stderr: "" });
786
+ if (code === 0) resolve21({ stderr: "" });
769
787
  else reject(new Error(`${label} exited ${code}${message ? `: ${message}` : ""}`));
770
788
  });
771
789
  });
@@ -869,6 +887,98 @@ function dedupeArray(values) {
869
887
  return out;
870
888
  }
871
889
 
890
+ // src/install/openclaw-json-merge.ts
891
+ import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
892
+ import { dirname as dirname5 } from "path";
893
+ async function mergeOpenClawJsonFile(sourcePath, destPath) {
894
+ const source = expandEnvPlaceholders(
895
+ normalizeOpenClawConfig(JSON.parse(await readFile6(sourcePath, "utf8"))),
896
+ sourcePath
897
+ );
898
+ const current = await pathExists(destPath) ? JSON.parse(await readFile6(destPath, "utf8")) : {};
899
+ const merged = mergeOpenClawJson(current, source);
900
+ await mkdir4(dirname5(destPath), { recursive: true });
901
+ await writeFile5(destPath, `${JSON.stringify(merged, null, 2)}
902
+ `, "utf8");
903
+ }
904
+ function mergeOpenClawJson(base, incoming, path = []) {
905
+ if (isMcpServerCodexAgentsPath(path) && Array.isArray(incoming)) {
906
+ return incoming;
907
+ }
908
+ if (path.join(".") === "agents.list" && Array.isArray(base) && Array.isArray(incoming)) {
909
+ return mergeOpenClawAgentsById(base, incoming);
910
+ }
911
+ if (Array.isArray(base) && Array.isArray(incoming)) {
912
+ return deepMerge(base, incoming);
913
+ }
914
+ if (isRecord(base) && isRecord(incoming)) {
915
+ const out = { ...base };
916
+ for (const [key, value] of Object.entries(incoming)) {
917
+ out[key] = key in out ? mergeOpenClawJson(out[key], value, [...path, key]) : value;
918
+ }
919
+ return out;
920
+ }
921
+ return incoming;
922
+ }
923
+ function isMcpServerCodexAgentsPath(path) {
924
+ return path.length === 5 && path[0] === "mcp" && path[1] === "servers" && path[3] === "codex" && path[4] === "agents";
925
+ }
926
+ function expandEnvPlaceholders(value, sourcePath) {
927
+ if (typeof value === "string") {
928
+ return value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_match, name) => {
929
+ const replacement = process.env[name];
930
+ if (replacement === void 0) {
931
+ throw new Error(`Missing environment variable ${name} while rendering OpenClaw JSON merge artifact ${sourcePath}`);
932
+ }
933
+ return replacement;
934
+ });
935
+ }
936
+ if (Array.isArray(value)) return value.map((item) => expandEnvPlaceholders(item, sourcePath));
937
+ if (!isRecord(value)) return value;
938
+ return Object.fromEntries(
939
+ Object.entries(value).map(([key, child]) => [key, expandEnvPlaceholders(child, sourcePath)])
940
+ );
941
+ }
942
+ function normalizeOpenClawConfig(value) {
943
+ if (!isRecord(value)) return value;
944
+ const rootMcpServers = isRecord(value.mcpServers) ? value.mcpServers : void 0;
945
+ if (!rootMcpServers) return value;
946
+ const out = { ...value };
947
+ const normalizedServers = {};
948
+ for (const [name, server] of Object.entries(rootMcpServers)) {
949
+ if (isRecord(server)) normalizedServers[name] = normalizeOpenClawMcpServer(server);
950
+ }
951
+ const mcp = isRecord(out.mcp) ? out.mcp : {};
952
+ out.mcp = deepMerge(mcp, { servers: normalizedServers });
953
+ delete out.mcpServers;
954
+ return out;
955
+ }
956
+ function normalizeOpenClawMcpServer(server) {
957
+ const out = { ...server };
958
+ const type = typeof out.type === "string" ? out.type : void 0;
959
+ if (type && typeof out.transport !== "string") out.transport = type;
960
+ delete out.type;
961
+ return out;
962
+ }
963
+ function mergeOpenClawAgentsById(base, incoming) {
964
+ const out = [...base];
965
+ const indexById = /* @__PURE__ */ new Map();
966
+ for (const [index, value] of out.entries()) {
967
+ const id = isRecord(value) && typeof value.id === "string" ? value.id : void 0;
968
+ if (id) indexById.set(id, index);
969
+ }
970
+ for (const value of incoming) {
971
+ const id = isRecord(value) && typeof value.id === "string" ? value.id : void 0;
972
+ if (!id || !indexById.has(id)) {
973
+ out.push(value);
974
+ if (id) indexById.set(id, out.length - 1);
975
+ continue;
976
+ }
977
+ out[indexById.get(id)] = value;
978
+ }
979
+ return out;
980
+ }
981
+
872
982
  // src/install/manifest.ts
873
983
  import { createHash as createHash2 } from "crypto";
874
984
  import { resolve as resolve3 } from "path";
@@ -900,7 +1010,7 @@ var manifestEntryV1Schema = z4.object({
900
1010
  semanticCommand: z4.array(z4.string()).optional(),
901
1011
  semanticPlugin: semanticPluginSpecSchema.optional(),
902
1012
  executed: z4.boolean().optional(),
903
- mergeStrategy: z4.enum(["json-deep", "yaml-deep", "codex-toml-mcp"]).optional(),
1013
+ mergeStrategy: z4.enum(["json-deep", "openclaw-json-deep", "yaml-deep", "codex-toml-mcp"]).optional(),
904
1014
  mode: z4.enum(["managed-block"]).optional(),
905
1015
  composedFrom: z4.array(composedFromEntrySchema).optional()
906
1016
  });
@@ -1092,8 +1202,8 @@ function assertOperationContained(operation, targetRoot) {
1092
1202
  }
1093
1203
 
1094
1204
  // src/install/transaction.ts
1095
- import { cp, mkdir as mkdir4, rm as rm2, stat as stat2 } from "fs/promises";
1096
- import { dirname as dirname5, join as join3 } from "path";
1205
+ import { cp, mkdir as mkdir5, rm as rm2, stat as stat2 } from "fs/promises";
1206
+ import { dirname as dirname6, join as join3 } from "path";
1097
1207
  function applyLockPath(targetRoot, adapter, scope = {}) {
1098
1208
  return join3(metadataDir(targetRoot), `${stateKeyFor(adapter, scope)}.apply-lock`);
1099
1209
  }
@@ -1158,7 +1268,7 @@ async function recordBackup(operation, index, targetRoot, adapter, transport = l
1158
1268
  }
1159
1269
  const backupPath = join3(applyBackupDir(targetRoot, adapter, scope), String(index));
1160
1270
  await rm2(backupPath, { recursive: true, force: true });
1161
- await mkdir4(dirname5(backupPath), { recursive: true });
1271
+ await mkdir5(dirname6(backupPath), { recursive: true });
1162
1272
  await cp(operation.destPath, backupPath, { recursive: operation.kind === "dir", dereference: true });
1163
1273
  return {
1164
1274
  index,
@@ -1212,16 +1322,16 @@ async function localPathExists(path) {
1212
1322
  }
1213
1323
 
1214
1324
  // src/install/toml-merge.ts
1215
- import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
1216
- import { dirname as dirname6 } from "path";
1325
+ import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
1326
+ import { dirname as dirname7 } from "path";
1217
1327
  async function mergeCodexTomlMcp(sourcePath, destPath) {
1218
- const source = JSON.parse(await readFile6(sourcePath, "utf8"));
1328
+ const source = JSON.parse(await readFile7(sourcePath, "utf8"));
1219
1329
  const servers = extractMcpServers(source);
1220
- const current = await pathExists(destPath) ? await readFile6(destPath, "utf8") : "";
1330
+ const current = await pathExists(destPath) ? await readFile7(destPath, "utf8") : "";
1221
1331
  const withoutManaged = removeManagedMcpSections(current, Object.keys(servers));
1222
1332
  const merged = appendMcpServers(withoutManaged, servers);
1223
- await mkdir5(dirname6(destPath), { recursive: true });
1224
- await writeFile5(destPath, merged, "utf8");
1333
+ await mkdir6(dirname7(destPath), { recursive: true });
1334
+ await writeFile6(destPath, merged, "utf8");
1225
1335
  }
1226
1336
  function extractMcpServers(source) {
1227
1337
  const raw = isRecord2(source.mcpServers) ? source.mcpServers : source;
@@ -1300,15 +1410,15 @@ function isRecord2(value) {
1300
1410
  }
1301
1411
 
1302
1412
  // src/install/yaml-merge.ts
1303
- import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
1304
- import { dirname as dirname7 } from "path";
1413
+ import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile7 } from "fs/promises";
1414
+ import { dirname as dirname8 } from "path";
1305
1415
  import { parse as parse2, stringify } from "yaml";
1306
1416
  async function mergeYamlFile(sourcePath, destPath) {
1307
- const source = parseYamlValue(await readFile7(sourcePath, "utf8"));
1308
- const current = await pathExists(destPath) ? parseYamlValue(await readFile7(destPath, "utf8")) : {};
1417
+ const source = parseYamlValue(await readFile8(sourcePath, "utf8"));
1418
+ const current = await pathExists(destPath) ? parseYamlValue(await readFile8(destPath, "utf8")) : {};
1309
1419
  const merged = deepMerge2(current, source);
1310
- await mkdir6(dirname7(destPath), { recursive: true });
1311
- await writeFile6(destPath, stringify(merged), "utf8");
1420
+ await mkdir7(dirname8(destPath), { recursive: true });
1421
+ await writeFile7(destPath, stringify(merged), "utf8");
1312
1422
  }
1313
1423
  function parseYamlValue(content) {
1314
1424
  return normalizeYamlValue(parse2(content));
@@ -1369,13 +1479,13 @@ function normalizeOwners(owners) {
1369
1479
 
1370
1480
  // src/install/instructions-block.ts
1371
1481
  import { createHash as createHash3 } from "crypto";
1372
- import { mkdtemp, readFile as readFile8, realpath, rm as rm3, writeFile as writeFile7 } from "fs/promises";
1482
+ import { mkdtemp, readFile as readFile9, realpath, rm as rm3, writeFile as writeFile8 } from "fs/promises";
1373
1483
  import { tmpdir as tmpdir2 } from "os";
1374
- import { basename as basename2, dirname as dirname8, join as join4, relative as relative2 } from "path";
1484
+ import { basename as basename2, dirname as dirname9, join as join4, relative as relative2 } from "path";
1375
1485
  var managedInstructionBlockMode = "managed-block";
1376
1486
  var managedInstructionBanner = "<!-- agentwheel-managed: edit fragments, not this block -->";
1377
1487
  async function desiredManagedInstructionBlockHash(sourcePath) {
1378
- const source = await readFile8(sourcePath, "utf8");
1488
+ const source = await readFile9(sourcePath, "utf8");
1379
1489
  return hashText(managedBlockBody(source));
1380
1490
  }
1381
1491
  async function readManagedInstructionBlockState(destPath, selector, transport) {
@@ -1394,18 +1504,18 @@ async function readManagedInstructionBlockState(destPath, selector, transport) {
1394
1504
  drifted: hash !== block.markerHash
1395
1505
  };
1396
1506
  }
1397
- async function writeManagedInstructionBlock(sourcePath, destPath, selector, transport, expectedHash) {
1398
- const source = await readFile8(sourcePath, "utf8");
1507
+ async function writeManagedInstructionBlock(sourcePath, destPath, selector, transport, options = {}) {
1508
+ const source = await readFile9(sourcePath, "utf8");
1399
1509
  const desired = renderManagedInstructionBlock(selector, source);
1400
1510
  const existing = await readOptionalText(destPath, transport);
1401
- const merged = upsertManagedInstructionBlock(existing ?? "", selector, desired.block, expectedHash);
1511
+ const merged = upsertManagedInstructionBlock(existing ?? "", selector, desired.block, options);
1402
1512
  await writeTextWithTransport(destPath, merged, transport);
1403
1513
  return desired.hash;
1404
1514
  }
1405
- async function removeManagedInstructionBlock(destPath, selector, transport, expectedHash) {
1515
+ async function removeManagedInstructionBlock(destPath, selector, transport, options = {}) {
1406
1516
  if (!await transport.pathExists(destPath)) return;
1407
1517
  const existing = await transport.readFile(destPath);
1408
- const updated = removeManagedBlockFromContent(existing, selector, expectedHash);
1518
+ const updated = removeManagedBlockFromContent(existing, selector, options);
1409
1519
  await writeTextWithTransport(destPath, updated, transport);
1410
1520
  }
1411
1521
  async function managedInstructionBlockLanded(destPath, selector, expectedHash, transport) {
@@ -1438,24 +1548,30 @@ ${body}<!-- END openpack:include ${selector} -->
1438
1548
  hash
1439
1549
  };
1440
1550
  }
1441
- function upsertManagedInstructionBlock(content, selector, block, expectedHash) {
1551
+ function upsertManagedInstructionBlock(content, selector, block, options) {
1552
+ const { expectedHash, allowDrift = false } = options;
1442
1553
  const existing = findManagedInstructionBlock(content, selector);
1443
1554
  if (!existing) {
1444
1555
  if (expectedHash) throw new Error(`Managed instruction block missing for ${selector}`);
1445
1556
  return appendManagedInstructionBlock(content, block);
1446
1557
  }
1447
- assertCleanBlock(existing, selector);
1448
- if (expectedHash && hashText(existing.body) !== expectedHash) {
1449
- throw new Error(`Managed instruction block drift detected for ${selector}`);
1558
+ if (!allowDrift) {
1559
+ assertCleanBlock(existing, selector);
1560
+ if (expectedHash && hashText(existing.body) !== expectedHash) {
1561
+ throw new Error(`Managed instruction block drift detected for ${selector}`);
1562
+ }
1450
1563
  }
1451
1564
  return `${content.slice(0, existing.start)}${block}${content.slice(existing.end)}`;
1452
1565
  }
1453
- function removeManagedBlockFromContent(content, selector, expectedHash) {
1566
+ function removeManagedBlockFromContent(content, selector, options) {
1567
+ const { expectedHash, allowDrift = false } = options;
1454
1568
  const existing = findManagedInstructionBlock(content, selector);
1455
1569
  if (!existing) return content;
1456
- assertCleanBlock(existing, selector);
1457
- if (expectedHash && hashText(existing.body) !== expectedHash) {
1458
- throw new Error(`Managed instruction block drift detected for ${selector}`);
1570
+ if (!allowDrift) {
1571
+ assertCleanBlock(existing, selector);
1572
+ if (expectedHash && hashText(existing.body) !== expectedHash) {
1573
+ throw new Error(`Managed instruction block drift detected for ${selector}`);
1574
+ }
1459
1575
  }
1460
1576
  return `${content.slice(0, existing.start)}${content.slice(existing.end)}`;
1461
1577
  }
@@ -1501,7 +1617,7 @@ function assertCleanBlock(block, selector) {
1501
1617
  }
1502
1618
  }
1503
1619
  function referencesAgentsMd(content, claudePath, agentsPath) {
1504
- const claudeDir = dirname8(claudePath);
1620
+ const claudeDir = dirname9(claudePath);
1505
1621
  for (const rawLine of content.split(/\r?\n/)) {
1506
1622
  const line = rawLine.trim();
1507
1623
  const atImport = /^@import\s+(.+)$/i.exec(line);
@@ -1511,7 +1627,7 @@ function referencesAgentsMd(content, claudePath, agentsPath) {
1511
1627
  const cleaned = referenced.trim().replace(/^["']|["']$/g, "");
1512
1628
  if (!/AGENTS\.md$/i.test(cleaned)) continue;
1513
1629
  const candidate = cleaned.startsWith("/") ? cleaned : join4(claudeDir, cleaned);
1514
- if (relative2(dirname8(agentsPath), candidate).replaceAll("\\", "/") === "AGENTS.md") return true;
1630
+ if (relative2(dirname9(agentsPath), candidate).replaceAll("\\", "/") === "AGENTS.md") return true;
1515
1631
  if (candidate === agentsPath) return true;
1516
1632
  }
1517
1633
  return false;
@@ -1533,7 +1649,7 @@ async function writeTextWithTransport(path, content, transport) {
1533
1649
  const tempRoot = await mkdtemp(join4(tmpdir2(), "agentwheel-instructions-"));
1534
1650
  const localPath = join4(tempRoot, basename2(path) || "instructions.md");
1535
1651
  try {
1536
- await writeFile7(localPath, content, "utf8");
1652
+ await writeFile8(localPath, content, "utf8");
1537
1653
  await transport.atomicCopy(localPath, path, "file");
1538
1654
  } finally {
1539
1655
  await rm3(tempRoot, { recursive: true, force: true });
@@ -1677,7 +1793,7 @@ async function uninstall(plan, options = {}) {
1677
1793
  const blockers = plan.operations.filter((operation) => operation.action === "conflict");
1678
1794
  throw new Error(`Refusing to uninstall with blocking changes: ${blockers.map((item) => item.relativeDestPath).join(", ")}`);
1679
1795
  }
1680
- const removable = plan.operations.filter((operation) => operation.action === "remove" || resolvedOptions.force && isForceRemovableKeep(operation)).map((operation) => operation.action === "keep" ? { ...operation, action: "remove", reason: `${operation.reason}; force removing drifted managed file` } : operation);
1796
+ const removable = plan.operations.filter((operation) => operation.action === "remove" || resolvedOptions.force && isForceRemovableKeep(operation)).map((operation) => operation.action === "keep" ? { ...operation, action: "remove", overrideDrift: true, reason: `${operation.reason}; force removing drifted managed file` } : operation);
1681
1797
  const kept = plan.operations.filter((operation) => operation.action === "keep" && (!resolvedOptions.force || !isForceRemovableKeep(operation)));
1682
1798
  const skipped = plan.operations.filter((operation) => operation.action === "skip");
1683
1799
  const removedDrifted = resolvedOptions.force ? plan.operations.filter((operation) => operation.action === "keep" && isForceRemovableKeep(operation)).length : 0;
@@ -1822,7 +1938,7 @@ async function applyOperation(operation, context) {
1822
1938
  }
1823
1939
  if (operation.mode === managedInstructionBlockMode) {
1824
1940
  const selector = managedInstructionSelector(operation.logicalSelector, operation.artifactType, operation.artifactName);
1825
- const hash2 = await writeManagedInstructionBlock(operation.sourcePath, operation.destPath, selector, transport, operation.manifestHash);
1941
+ const hash2 = await writeManagedInstructionBlock(operation.sourcePath, operation.destPath, selector, transport, managedBlockMutationOptions(operation));
1826
1942
  if (hash2 !== operation.desiredHash) {
1827
1943
  throw new Error(`Managed block hash verification failed for ${operation.relativeDestPath}: expected ${operation.desiredHash}, got ${hash2}`);
1828
1944
  }
@@ -1835,6 +1951,8 @@ async function applyOperation(operation, context) {
1835
1951
  }
1836
1952
  if (operation.mergeStrategy === "json-deep") {
1837
1953
  await mergeWithTransport(operation.sourcePath, operation.destPath, transport, mergeJsonFile);
1954
+ } else if (operation.mergeStrategy === "openclaw-json-deep") {
1955
+ await mergeOpenClawJsonWithTransport(operation.sourcePath, operation.destPath, transport);
1838
1956
  } else if (operation.mergeStrategy === "yaml-deep") {
1839
1957
  await mergeWithTransport(operation.sourcePath, operation.destPath, transport, mergeYamlFile);
1840
1958
  } else if (operation.mergeStrategy === "codex-toml-mcp") {
@@ -1884,7 +2002,7 @@ async function applyOperation(operation, context) {
1884
2002
  }
1885
2003
  if (operation.mode === managedInstructionBlockMode) {
1886
2004
  const selector = managedInstructionSelector(operation.logicalSelector, operation.artifactType, operation.artifactName);
1887
- await removeManagedInstructionBlock(operation.destPath, selector, transport, operation.manifestHash);
2005
+ await removeManagedInstructionBlock(operation.destPath, selector, transport, managedBlockMutationOptions(operation));
1888
2006
  } else {
1889
2007
  await transport.rm(operation.destPath);
1890
2008
  }
@@ -2057,6 +2175,12 @@ function semanticInstallCommands(operation) {
2057
2175
  if (operation.semanticPlugin) return operation.semanticPlugin.installCommands;
2058
2176
  return operation.semanticCommand ? [operation.semanticCommand] : [];
2059
2177
  }
2178
+ function managedBlockMutationOptions(operation) {
2179
+ return {
2180
+ expectedHash: operation.overrideDrift ? void 0 : operation.manifestHash,
2181
+ allowDrift: operation.overrideDrift === true
2182
+ };
2183
+ }
2060
2184
  async function entryForCompletedOperation(operation, transport, now, graphLockDigest) {
2061
2185
  if (operation.action === "remove") return void 0;
2062
2186
  if (operation.action === "create" || operation.action === "update") {
@@ -2170,7 +2294,7 @@ async function mergeWithTransport(sourcePath, destPath, transport, merge) {
2170
2294
  const localDest = join5(tempRoot, basename3(destPath) || "merged");
2171
2295
  try {
2172
2296
  if (await transport.pathExists(destPath)) {
2173
- await writeFile8(localDest, await transport.readFile(destPath), "utf8");
2297
+ await writeFile9(localDest, await transport.readFile(destPath), "utf8");
2174
2298
  }
2175
2299
  await merge(sourcePath, localDest);
2176
2300
  await transport.atomicCopy(localDest, destPath, "file");
@@ -2178,13 +2302,58 @@ async function mergeWithTransport(sourcePath, destPath, transport, merge) {
2178
2302
  await rm4(tempRoot, { recursive: true, force: true });
2179
2303
  }
2180
2304
  }
2305
+ async function mergeOpenClawJsonWithTransport(sourcePath, destPath, transport) {
2306
+ const tempRoot = await mkdtemp2(join5(tmpdir3(), "agentwheel-openclaw-merge-"));
2307
+ const localDest = join5(tempRoot, basename3(destPath) || "openclaw.json");
2308
+ const validationPath = transport.kind === "local" ? localDest : `${destPath}.validate-agentwheel-${process.pid}-${Date.now()}`;
2309
+ try {
2310
+ if (await transport.pathExists(destPath)) {
2311
+ await writeFile9(localDest, await transport.readFile(destPath), "utf8");
2312
+ }
2313
+ await mergeOpenClawJsonFile(sourcePath, localDest);
2314
+ if (transport.kind !== "local") {
2315
+ await transport.atomicCopy(localDest, validationPath, "file");
2316
+ }
2317
+ await validateOpenClawConfig(validationPath, destPath, transport);
2318
+ await transport.atomicCopy(localDest, destPath, "file");
2319
+ } finally {
2320
+ if (transport.kind !== "local") await transport.rm(validationPath);
2321
+ await rm4(tempRoot, { recursive: true, force: true });
2322
+ }
2323
+ }
2324
+ async function validateOpenClawConfig(configPath, destPath, transport) {
2325
+ if (!transport.execFile) {
2326
+ throw new Error(`Cannot validate OpenClaw config over ${transport.description}: transport does not support command execution.`);
2327
+ }
2328
+ const openClawHome = dirname10(destPath);
2329
+ const bundledBin = join5(openClawHome, "npm", "node_modules", ".bin", "openclaw");
2330
+ const script = String.raw`
2331
+ set -euo pipefail
2332
+ cfg=$1
2333
+ bundled_bin=$2
2334
+ if [ -x "$bundled_bin" ]; then
2335
+ bin="$bundled_bin"
2336
+ elif command -v openclaw >/dev/null 2>&1; then
2337
+ bin="openclaw"
2338
+ else
2339
+ echo "OpenClaw binary not found; cannot validate $cfg" >&2
2340
+ exit 127
2341
+ fi
2342
+ out=$(OPENCLAW_CONFIG_PATH="$cfg" "$bin" config validate --json 2>&1) || {
2343
+ printf '%s\n' "$out" >&2
2344
+ exit 1
2345
+ }
2346
+ printf '%s' "$out" | node -e 'let s=""; process.stdin.on("data", c => s += c); process.stdin.on("end", () => { const data = JSON.parse(s); if (!data.valid) { console.error(JSON.stringify(data, null, 2)); process.exit(1); } });'
2347
+ `;
2348
+ await transport.execFile("bash", ["-lc", script, "agentwheel-openclaw-validate", configPath, bundledBin]);
2349
+ }
2181
2350
 
2182
2351
  // src/install/plan.ts
2183
- import { basename as basename7, join as join15, relative as relative3 } from "path";
2352
+ import { basename as basename8, join as join16, relative as relative3 } from "path";
2184
2353
 
2185
2354
  // src/staging/codex-subagents.ts
2186
- import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
2187
- import { basename as basename4, dirname as dirname9, join as join6 } from "path";
2355
+ import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile10 } from "fs/promises";
2356
+ import { basename as basename4, dirname as dirname11, join as join6 } from "path";
2188
2357
  var requiredCodexAgentFields = ["name", "description", "developer_instructions"];
2189
2358
  async function renderCodexSubagents(artifacts, stageRoot, adapter) {
2190
2359
  if (adapter?.name !== "codex") return artifacts;
@@ -2210,7 +2379,7 @@ async function renderCodexSubagent(artifact, stageRoot) {
2210
2379
  const renderedPath = join6(stageRoot, ".agentwheel-rendered", "codex-subagents", `${agentName}.toml`);
2211
2380
  const lowerSourcePath = sourcePath.toLowerCase();
2212
2381
  if (artifact.kind === "file" && (artifact.name.toLowerCase().endsWith(".toml") || lowerSourcePath.endsWith(".toml"))) {
2213
- const content = await readFile9(sourcePath, "utf8");
2382
+ const content = await readFile10(sourcePath, "utf8");
2214
2383
  validateCodexAgentToml(content, sourcePath);
2215
2384
  return {
2216
2385
  ...artifact,
@@ -2227,10 +2396,10 @@ async function renderCodexSubagent(artifact, stageRoot) {
2227
2396
  if (artifact.kind === "file" && !artifact.name.toLowerCase().endsWith(".md") && !lowerSourcePath.endsWith(".md")) {
2228
2397
  throw new Error(`Codex subagent ${artifact.relativePath} must be a .toml file, .md file, or directory containing AGENTS.md.`);
2229
2398
  }
2230
- const markdown = await readFile9(markdownPath, "utf8");
2399
+ const markdown = await readFile10(markdownPath, "utf8");
2231
2400
  const toml = markdownToCodexAgentToml(agentName, markdown);
2232
- await mkdir7(dirname9(renderedPath), { recursive: true });
2233
- await writeFile9(renderedPath, toml, "utf8");
2401
+ await mkdir8(dirname11(renderedPath), { recursive: true });
2402
+ await writeFile10(renderedPath, toml, "utf8");
2234
2403
  return {
2235
2404
  ...artifact,
2236
2405
  name: agentName,
@@ -2298,8 +2467,8 @@ function escapeRegExp(value) {
2298
2467
  }
2299
2468
 
2300
2469
  // src/staging/copilot-artifacts.ts
2301
- import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile10 } from "fs/promises";
2302
- import { basename as basename5, dirname as dirname10, join as join7 } from "path";
2470
+ import { mkdir as mkdir9, readFile as readFile11, writeFile as writeFile11 } from "fs/promises";
2471
+ import { basename as basename5, dirname as dirname12, join as join7 } from "path";
2303
2472
  async function renderCopilotArtifacts(artifacts, stageRoot, adapter) {
2304
2473
  if (adapter?.name !== "copilot") return artifacts;
2305
2474
  const names = /* @__PURE__ */ new Set();
@@ -2329,9 +2498,9 @@ async function renderCopilotSubagent(artifact, stageRoot) {
2329
2498
  if (artifact.kind === "file" && !artifact.name.toLowerCase().endsWith(".md")) {
2330
2499
  throw new Error(`Copilot subagent ${artifact.relativePath} must be a .md file or directory containing AGENTS.md.`);
2331
2500
  }
2332
- const markdown = await readFile10(markdownPath, "utf8");
2333
- await mkdir8(dirname10(renderedPath), { recursive: true });
2334
- await writeFile10(renderedPath, ensureCopilotAgentDescription(agentName, markdown), "utf8");
2501
+ const markdown = await readFile11(markdownPath, "utf8");
2502
+ await mkdir9(dirname12(renderedPath), { recursive: true });
2503
+ await writeFile11(renderedPath, ensureCopilotAgentDescription(agentName, markdown), "utf8");
2335
2504
  return {
2336
2505
  ...artifact,
2337
2506
  name: `${agentName}.agent.md`,
@@ -2383,15 +2552,84 @@ function yamlString(value) {
2383
2552
  return JSON.stringify(value);
2384
2553
  }
2385
2554
 
2555
+ // src/staging/openclaw-subagents.ts
2556
+ import { mkdir as mkdir10, readFile as readFile12, writeFile as writeFile12 } from "fs/promises";
2557
+ import { basename as basename6, dirname as dirname13, join as join8 } from "path";
2558
+ async function renderOpenClawSubagents(artifacts, stageRoot, adapter) {
2559
+ if (adapter?.name !== "openclaw") return artifacts;
2560
+ const names = /* @__PURE__ */ new Set();
2561
+ const rendered = [];
2562
+ for (const artifact of artifacts) {
2563
+ if (artifact.type !== "subagents") {
2564
+ rendered.push(artifact);
2565
+ continue;
2566
+ }
2567
+ const next = await renderOpenClawSubagent(artifact, stageRoot);
2568
+ if (names.has(next.name)) {
2569
+ throw new Error(`OpenClaw subagents produce duplicate agent id '${next.name}'.`);
2570
+ }
2571
+ names.add(next.name);
2572
+ rendered.push(next);
2573
+ }
2574
+ return rendered;
2575
+ }
2576
+ async function renderOpenClawSubagent(artifact, stageRoot) {
2577
+ const sourcePath = artifact.stagedPath ?? artifact.sourcePath;
2578
+ const agentId = openClawAgentId(artifact);
2579
+ const markdownPath = artifact.kind === "dir" ? join8(sourcePath, "AGENTS.md") : sourcePath;
2580
+ if (artifact.kind === "dir" && !await pathExists(markdownPath)) {
2581
+ throw new Error(`OpenClaw subagent directory ${artifact.relativePath} must contain AGENTS.md.`);
2582
+ }
2583
+ if (artifact.kind === "file" && !artifact.name.toLowerCase().endsWith(".md") && !sourcePath.toLowerCase().endsWith(".md")) {
2584
+ throw new Error(`OpenClaw subagent ${artifact.relativePath} must be a .md file or directory containing AGENTS.md.`);
2585
+ }
2586
+ const parsed = splitFrontmatter3(await readFile12(markdownPath, "utf8"));
2587
+ const body = parsed.body.trim().length > 0 ? parsed.body.trim() : `# ${titleFromAgentId(agentId)}
2588
+
2589
+ ${parsed.description ?? `OpenClaw subagent ${agentId}.`}`;
2590
+ const renderedPath = join8(stageRoot, ".agentwheel-rendered", "openclaw-subagents", agentId, "AGENTS.md");
2591
+ await mkdir10(dirname13(renderedPath), { recursive: true });
2592
+ await writeFile12(renderedPath, `${body}
2593
+ `, "utf8");
2594
+ const renderedDir = dirname13(renderedPath);
2595
+ return {
2596
+ ...artifact,
2597
+ name: agentId,
2598
+ sourcePath: renderedDir,
2599
+ stagedPath: renderedDir,
2600
+ relativePath: join8("subagents", agentId),
2601
+ kind: "dir",
2602
+ hash: await hashPath(renderedDir)
2603
+ };
2604
+ }
2605
+ function openClawAgentId(artifact) {
2606
+ const raw = artifact.kind === "dir" ? artifact.name : basename6(artifact.name);
2607
+ return raw.replace(/\.agent\.md$/i, "").replace(/\.md$/i, "");
2608
+ }
2609
+ function splitFrontmatter3(markdown) {
2610
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(markdown);
2611
+ if (!match) return { body: markdown };
2612
+ const frontmatter = match[1] ?? "";
2613
+ const body = markdown.slice(match[0].length);
2614
+ const description = frontmatter.split(/\r?\n/).map((line) => /^description:\s*(?:"([^"]*)"|'([^']*)'|(.+))\s*$/.exec(line.trim())).find((item) => item !== null);
2615
+ return {
2616
+ body,
2617
+ description: description ? (description[1] ?? description[2] ?? description[3] ?? "").trim() : void 0
2618
+ };
2619
+ }
2620
+ function titleFromAgentId(agentId) {
2621
+ return agentId.split(/[-_]/g).filter(Boolean).map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
2622
+ }
2623
+
2386
2624
  // src/targets/plugins/claude.ts
2387
- import { join as join9 } from "path";
2625
+ import { join as join10 } from "path";
2388
2626
 
2389
2627
  // src/targets/plugins/common.ts
2390
- import { readFile as readFile11 } from "fs/promises";
2391
- import { join as join8 } from "path";
2628
+ import { readFile as readFile13 } from "fs/promises";
2629
+ import { join as join9 } from "path";
2392
2630
  import { parseDocument } from "yaml";
2393
2631
  function pluginStateRoot(request) {
2394
- return join8(
2632
+ return join9(
2395
2633
  request.targetRoot,
2396
2634
  ".agentwheel",
2397
2635
  "plugins",
@@ -2409,16 +2647,16 @@ function safeNameSegment(value) {
2409
2647
  return normalized.length > 0 ? normalized : "unnamed";
2410
2648
  }
2411
2649
  async function jsonPluginName(root, relativeManifestPath, fallback) {
2412
- const manifestPath = join8(root, relativeManifestPath);
2650
+ const manifestPath = join9(root, relativeManifestPath);
2413
2651
  if (!await pathExists(manifestPath)) return fallback;
2414
- const parsed = JSON.parse(await readFile11(manifestPath, "utf8"));
2652
+ const parsed = JSON.parse(await readFile13(manifestPath, "utf8"));
2415
2653
  return typeof parsed.name === "string" && parsed.name.trim().length > 0 ? parsed.name.trim() : fallback;
2416
2654
  }
2417
2655
  async function yamlPluginName(root, relativeManifestPaths, fallback) {
2418
2656
  for (const relativeManifestPath of relativeManifestPaths) {
2419
- const manifestPath = join8(root, relativeManifestPath);
2657
+ const manifestPath = join9(root, relativeManifestPath);
2420
2658
  if (!await pathExists(manifestPath)) continue;
2421
- const document = parseDocument(await readFile11(manifestPath, "utf8"));
2659
+ const document = parseDocument(await readFile13(manifestPath, "utf8"));
2422
2660
  const parsed = document.toJSON();
2423
2661
  if (!isRecord4(parsed)) continue;
2424
2662
  for (const key of ["name", "module", "package"]) {
@@ -2447,7 +2685,7 @@ async function claudePluginSpec(request) {
2447
2685
  packageName: request.artifact.packageName,
2448
2686
  installName: request.installName
2449
2687
  });
2450
- const marketplaceRoot = join9(stateRoot, "marketplace");
2688
+ const marketplaceRoot = join10(stateRoot, "marketplace");
2451
2689
  const scope = claudeScope(request.installationType);
2452
2690
  const selector = `${pluginName}@${marketplaceName}`;
2453
2691
  return {
@@ -2472,7 +2710,7 @@ function claudeScope(installationType) {
2472
2710
  }
2473
2711
 
2474
2712
  // src/targets/plugins/codex.ts
2475
- import { join as join10 } from "path";
2713
+ import { join as join11 } from "path";
2476
2714
  async function codexPluginSpec(request) {
2477
2715
  const pluginName = await jsonPluginName(request.sourcePath, ".codex-plugin/plugin.json", request.installName);
2478
2716
  const marketplaceName = agentwheelMarketplaceName(request.artifact.packageName, pluginName);
@@ -2483,7 +2721,7 @@ async function codexPluginSpec(request) {
2483
2721
  packageName: request.artifact.packageName,
2484
2722
  installName: request.installName
2485
2723
  });
2486
- const marketplaceRoot = join10(stateRoot, "marketplace");
2724
+ const marketplaceRoot = join11(stateRoot, "marketplace");
2487
2725
  const selector = `${pluginName}@${marketplaceName}`;
2488
2726
  return {
2489
2727
  runtime: "codex",
@@ -2502,7 +2740,7 @@ async function codexPluginSpec(request) {
2502
2740
  }
2503
2741
 
2504
2742
  // src/targets/plugins/copilot.ts
2505
- import { join as join11 } from "path";
2743
+ import { join as join12 } from "path";
2506
2744
  async function copilotPluginSpec(request) {
2507
2745
  if (request.installationType !== "user") {
2508
2746
  throw new Error("Copilot plugins are persistent user-level installs only; pass --installation-type user.");
@@ -2515,7 +2753,7 @@ async function copilotPluginSpec(request) {
2515
2753
  packageName: request.artifact.packageName,
2516
2754
  installName: request.installName
2517
2755
  });
2518
- const pluginRoot = join11(stateRoot, "plugin");
2756
+ const pluginRoot = join12(stateRoot, "plugin");
2519
2757
  return {
2520
2758
  runtime: "copilot",
2521
2759
  pluginName,
@@ -2526,7 +2764,7 @@ async function copilotPluginSpec(request) {
2526
2764
  }
2527
2765
 
2528
2766
  // src/targets/plugins/hermes.ts
2529
- import { join as join12 } from "path";
2767
+ import { join as join13 } from "path";
2530
2768
  async function hermesPluginSpec(request) {
2531
2769
  if (request.installationType !== "user") {
2532
2770
  throw new Error("Hermes plugins are user-level installs only; pass --installation-type user.");
@@ -2539,7 +2777,7 @@ async function hermesPluginSpec(request) {
2539
2777
  packageName: request.artifact.packageName,
2540
2778
  installName: request.installName
2541
2779
  });
2542
- const repoRoot = join12(stateRoot, "repo");
2780
+ const repoRoot = join13(stateRoot, "repo");
2543
2781
  return {
2544
2782
  runtime: "hermes",
2545
2783
  pluginName,
@@ -2550,8 +2788,8 @@ async function hermesPluginSpec(request) {
2550
2788
  }
2551
2789
 
2552
2790
  // src/targets/plugins/openclaw.ts
2553
- import { readFile as readFile12 } from "fs/promises";
2554
- import { join as join13 } from "path";
2791
+ import { readFile as readFile14 } from "fs/promises";
2792
+ import { join as join14 } from "path";
2555
2793
  function openClawPluginInstallCommand(request) {
2556
2794
  return ["openclaw", "plugins", "install", "--force", request.path];
2557
2795
  }
@@ -2559,6 +2797,15 @@ function openClawPluginUninstallCommand(pluginName) {
2559
2797
  return ["openclaw", "plugins", "uninstall", pluginName, "--force"];
2560
2798
  }
2561
2799
  async function openClawPluginSpec(request) {
2800
+ if (request.format === "openclaw-clawhub-plugin") {
2801
+ const metadata = await openClawClawHubPluginMetadata(request.path, request.fallbackPluginName);
2802
+ return {
2803
+ runtime: "openclaw",
2804
+ pluginName: metadata.pluginName,
2805
+ installCommands: [openClawPluginInstallCommand({ path: metadata.installSpec, dryRun: true })],
2806
+ uninstallCommands: [openClawPluginUninstallCommand(metadata.pluginName)]
2807
+ };
2808
+ }
2562
2809
  const pluginName = await openClawPluginName(request.path, request.fallbackPluginName);
2563
2810
  return {
2564
2811
  runtime: "openclaw",
@@ -2569,19 +2816,39 @@ async function openClawPluginSpec(request) {
2569
2816
  }
2570
2817
  async function openClawPluginName(root, fallback) {
2571
2818
  for (const manifestName of ["plugin.json", "openclaw.plugin.json"]) {
2572
- const manifestPath = join13(root, manifestName);
2819
+ const manifestPath = join14(root, manifestName);
2573
2820
  if (!await pathExists(manifestPath)) continue;
2574
- const parsed = JSON.parse(await readFile12(manifestPath, "utf8"));
2821
+ const parsed = JSON.parse(await readFile14(manifestPath, "utf8"));
2575
2822
  if (typeof parsed.name === "string" && parsed.name.trim().length > 0) return parsed.name.trim();
2576
2823
  }
2577
2824
  return fallback;
2578
2825
  }
2826
+ async function openClawClawHubPluginMetadata(root, fallback) {
2827
+ const metadataPath = join14(root, "clawhub.json");
2828
+ if (!await pathExists(metadataPath)) {
2829
+ throw new Error("OpenClaw ClawHub plugins must contain clawhub.json");
2830
+ }
2831
+ const parsed = JSON.parse(await readFile14(metadataPath, "utf8"));
2832
+ const installSpec = stringField(parsed.installSpec);
2833
+ if (!installSpec?.startsWith("clawhub:")) {
2834
+ throw new Error("OpenClaw ClawHub plugin metadata must declare installSpec starting with clawhub:");
2835
+ }
2836
+ const pluginName = stringField(parsed.runtimeId) ?? installNameFor(stringField(parsed.name) ?? fallback);
2837
+ return { installSpec, pluginName };
2838
+ }
2839
+ function stringField(value) {
2840
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
2841
+ }
2842
+ function installNameFor(value) {
2843
+ return value.split("/").at(-1).replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "clawhub-plugin";
2844
+ }
2579
2845
 
2580
2846
  // src/targets/plugins/index.ts
2581
2847
  async function semanticPluginSpecForArtifact(request) {
2582
2848
  if (request.semantic === "openclaw-plugin") {
2583
2849
  return openClawPluginSpec({
2584
2850
  path: request.sourcePath,
2851
+ format: request.artifact.format,
2585
2852
  fallbackPluginName: request.installName
2586
2853
  });
2587
2854
  }
@@ -2601,8 +2868,8 @@ async function semanticPluginSpecForArtifact(request) {
2601
2868
  }
2602
2869
 
2603
2870
  // src/validation/artifacts.ts
2604
- import { readFile as readFile13 } from "fs/promises";
2605
- import { basename as basename6, join as join14 } from "path";
2871
+ import { readFile as readFile15 } from "fs/promises";
2872
+ import { basename as basename7, join as join15 } from "path";
2606
2873
  import { parseDocument as parseDocument2 } from "yaml";
2607
2874
 
2608
2875
  // src/model/selection.ts
@@ -2657,7 +2924,7 @@ function subagentBaseName(name) {
2657
2924
 
2658
2925
  // src/validation/artifacts.ts
2659
2926
  var behavioralRuleFormats = ["markdown-rule", "claude-markdown-rule", "copilot-instruction-rule"];
2660
- var pluginFormats = ["claude-plugin", "codex-plugin", "hermes-plugin", "copilot-plugin", "openclaw-plugin"];
2927
+ var pluginFormats = ["claude-plugin", "codex-plugin", "hermes-plugin", "copilot-plugin", "openclaw-plugin", "openclaw-clawhub-plugin"];
2661
2928
  async function filterArtifactsByInstallFormat(artifacts, adapter, installationType, options = {}) {
2662
2929
  const selectedSet = new Set(normalizeArtifactSelectors(options.selected ?? []) ?? []);
2663
2930
  const kept = [];
@@ -2766,6 +3033,7 @@ async function inferArtifactFormat(artifact, target) {
2766
3033
  return void 0;
2767
3034
  }
2768
3035
  if (artifact.type === "plugins" && target.semantic === "openclaw-plugin") {
3036
+ if (artifact.kind === "dir" && await pathExists(join15(artifactPath(artifact), "clawhub.json"))) return "openclaw-clawhub-plugin";
2769
3037
  if (artifact.kind === "dir" && (await openClawPluginManifestPaths(artifact)).length > 0) return "openclaw-plugin";
2770
3038
  }
2771
3039
  return void 0;
@@ -2781,9 +3049,9 @@ function expectedFormats(artifact, target) {
2781
3049
  return behavioralRuleFormats;
2782
3050
  }
2783
3051
  if (artifact.type === "plugins") {
2784
- if (isPluginFormat(target.semantic)) return [target.semantic];
2785
3052
  const declared = target.formats?.filter((format) => pluginFormats.includes(format));
2786
3053
  if (target.formats?.length) return declared?.length ? declared : pluginFormats;
3054
+ if (isPluginFormat(target.semantic)) return [target.semantic];
2787
3055
  }
2788
3056
  return target.formats;
2789
3057
  }
@@ -2792,7 +3060,10 @@ async function validateKnownFormat(artifact, format, target) {
2792
3060
  if (format === "markdown-rule" || format === "claude-markdown-rule" || format === "copilot-instruction-rule") {
2793
3061
  return validateMarkdownRule(artifact, format);
2794
3062
  }
2795
- if (format === "openclaw-plugin" || target.semantic === "openclaw-plugin") {
3063
+ if (format === "openclaw-clawhub-plugin") {
3064
+ return validateOpenClawClawHubPlugin(artifact);
3065
+ }
3066
+ if (format === "openclaw-plugin") {
2796
3067
  return validateOpenClawPlugin(artifact);
2797
3068
  }
2798
3069
  if (format === "claude-plugin" || target.semantic === "claude-plugin") {
@@ -2814,7 +3085,7 @@ async function validateGenericStructure(artifact, target) {
2814
3085
  const issues = [];
2815
3086
  if (artifact.type === "skills") {
2816
3087
  if (artifact.kind === "dir") {
2817
- const skillMd = join14(artifactPath(artifact), "SKILL.md");
3088
+ const skillMd = join15(artifactPath(artifact), "SKILL.md");
2818
3089
  if (!await pathExists(skillMd)) {
2819
3090
  issues.push({ artifact, message: "skill directory must contain SKILL.md" });
2820
3091
  } else {
@@ -2826,10 +3097,17 @@ async function validateGenericStructure(artifact, target) {
2826
3097
  issues.push(...await validateSkillFrontmatter(artifact, artifactPath(artifact)));
2827
3098
  }
2828
3099
  }
2829
- if (target.merge === "json-deep") {
3100
+ if (target.merge === "json-deep" || target.merge === "openclaw-json-deep") {
2830
3101
  const parsed = await parseJsonObjectArtifact(artifact);
2831
3102
  if (!parsed.ok) issues.push({ artifact, message: parsed.message });
2832
3103
  }
3104
+ if (artifact.type === "subagents" && target.semantic === "openclaw-subagent") {
3105
+ if (artifact.kind !== "dir") {
3106
+ issues.push({ artifact, message: "OpenClaw subagent artifacts must render to a workspace directory containing AGENTS.md" });
3107
+ } else if (!await pathExists(join15(artifactPath(artifact), "AGENTS.md"))) {
3108
+ issues.push({ artifact, message: "OpenClaw subagent workspace directory must contain AGENTS.md" });
3109
+ }
3110
+ }
2833
3111
  if (target.merge === "yaml-deep") {
2834
3112
  const parsed = await parseYamlObjectArtifact(artifact);
2835
3113
  if (!parsed.ok) issues.push({ artifact, message: parsed.message });
@@ -2847,7 +3125,7 @@ async function validateGenericStructure(artifact, target) {
2847
3125
  async function validateSkillFrontmatter(artifact, skillMdPath) {
2848
3126
  let content;
2849
3127
  try {
2850
- content = await readFile13(skillMdPath, "utf8");
3128
+ content = await readFile15(skillMdPath, "utf8");
2851
3129
  } catch (error) {
2852
3130
  return [{ artifact, message: `could not read SKILL.md: ${errorMessage(error)}` }];
2853
3131
  }
@@ -2900,7 +3178,7 @@ function validatePluginArtifact(artifact, format) {
2900
3178
  async function validateJsonPluginDescriptor(artifact, relativeManifestPath, label) {
2901
3179
  const generic = validatePluginArtifact(artifact, `${label.toLowerCase()}-plugin`);
2902
3180
  if (generic.length > 0) return generic;
2903
- const manifestPath = join14(artifactPath(artifact), relativeManifestPath);
3181
+ const manifestPath = join15(artifactPath(artifact), relativeManifestPath);
2904
3182
  if (!await pathExists(manifestPath)) {
2905
3183
  return [{ artifact, message: `${label} plugins must contain ${relativeManifestPath}` }];
2906
3184
  }
@@ -2911,26 +3189,26 @@ async function validateHermesPlugin(artifact) {
2911
3189
  const generic = validatePluginArtifact(artifact, "hermes-plugin");
2912
3190
  if (generic.length > 0) return generic;
2913
3191
  const root = artifactPath(artifact);
2914
- const manifestPaths = [join14(root, "plugin.yaml"), join14(root, "plugin.yml")];
3192
+ const manifestPaths = [join15(root, "plugin.yaml"), join15(root, "plugin.yml")];
2915
3193
  const manifestPath = await firstExistingPath(manifestPaths);
2916
3194
  if (!manifestPath) {
2917
3195
  return [{ artifact, message: "Hermes plugins must contain plugin.yaml or plugin.yml" }];
2918
3196
  }
2919
3197
  try {
2920
- const document = parseDocument2(await readFile13(manifestPath, "utf8"));
3198
+ const document = parseDocument2(await readFile15(manifestPath, "utf8"));
2921
3199
  if (document.errors.length > 0) {
2922
- return [{ artifact, message: `Hermes ${basename6(manifestPath)} must be valid YAML: ${document.errors[0]?.message ?? "parse error"}` }];
3200
+ return [{ artifact, message: `Hermes ${basename7(manifestPath)} must be valid YAML: ${document.errors[0]?.message ?? "parse error"}` }];
2923
3201
  }
2924
3202
  const parsed = document.toJSON();
2925
3203
  if (!isUnknownRecord(parsed)) {
2926
- return [{ artifact, message: `Hermes ${basename6(manifestPath)} must contain a YAML object` }];
3204
+ return [{ artifact, message: `Hermes ${basename7(manifestPath)} must contain a YAML object` }];
2927
3205
  }
2928
3206
  if (!hasStringField(parsed, "name") && !hasStringField(parsed, "module") && !hasStringField(parsed, "package") && !hasNestedPackageName(parsed)) {
2929
- return [{ artifact, message: `Hermes ${basename6(manifestPath)} must declare a non-empty name, module, or package name` }];
3207
+ return [{ artifact, message: `Hermes ${basename7(manifestPath)} must declare a non-empty name, module, or package name` }];
2930
3208
  }
2931
3209
  return [];
2932
3210
  } catch (error) {
2933
- return [{ artifact, message: `Hermes ${basename6(manifestPath)} must be valid YAML: ${errorMessage(error)}` }];
3211
+ return [{ artifact, message: `Hermes ${basename7(manifestPath)} must be valid YAML: ${errorMessage(error)}` }];
2934
3212
  }
2935
3213
  }
2936
3214
  async function firstExistingPath(paths) {
@@ -2941,16 +3219,16 @@ async function firstExistingPath(paths) {
2941
3219
  }
2942
3220
  async function parseJsonPluginManifest(manifestPath, label) {
2943
3221
  try {
2944
- const parsed = JSON.parse(await readFile13(manifestPath, "utf8"));
3222
+ const parsed = JSON.parse(await readFile15(manifestPath, "utf8"));
2945
3223
  if (!isRecord5(parsed)) {
2946
- return { ok: false, message: `${label} ${basename6(manifestPath)} must be a JSON object` };
3224
+ return { ok: false, message: `${label} ${basename7(manifestPath)} must be a JSON object` };
2947
3225
  }
2948
3226
  if (typeof parsed.name !== "string" || parsed.name.trim().length === 0) {
2949
- return { ok: false, message: `${label} ${basename6(manifestPath)} must declare a non-empty name` };
3227
+ return { ok: false, message: `${label} ${basename7(manifestPath)} must declare a non-empty name` };
2950
3228
  }
2951
3229
  return { ok: true, name: parsed.name.trim() };
2952
3230
  } catch (error) {
2953
- return { ok: false, message: `${label} ${basename6(manifestPath)} must be valid JSON: ${errorMessage(error)}` };
3231
+ return { ok: false, message: `${label} ${basename7(manifestPath)} must be valid JSON: ${errorMessage(error)}` };
2954
3232
  }
2955
3233
  }
2956
3234
  async function validateOpenClawPlugin(artifact) {
@@ -2976,16 +3254,43 @@ async function validateOpenClawPlugin(artifact) {
2976
3254
  }
2977
3255
  return issues;
2978
3256
  }
3257
+ async function validateOpenClawClawHubPlugin(artifact) {
3258
+ if (artifact.type !== "plugins") {
3259
+ return [{ artifact, message: "openclaw-clawhub-plugin format is only valid for plugins artifacts" }];
3260
+ }
3261
+ if (artifact.kind !== "dir") {
3262
+ return [{ artifact, message: "OpenClaw ClawHub plugins must be directory artifacts" }];
3263
+ }
3264
+ const metadataPath = join15(artifactPath(artifact), "clawhub.json");
3265
+ if (!await pathExists(metadataPath)) {
3266
+ return [{ artifact, message: "OpenClaw ClawHub plugins must contain clawhub.json" }];
3267
+ }
3268
+ try {
3269
+ const parsed = JSON.parse(await readFile15(metadataPath, "utf8"));
3270
+ if (!isRecord5(parsed)) {
3271
+ return [{ artifact, message: "OpenClaw ClawHub clawhub.json must be a JSON object" }];
3272
+ }
3273
+ if (typeof parsed.installSpec !== "string" || !parsed.installSpec.trim().startsWith("clawhub:")) {
3274
+ return [{ artifact, message: "OpenClaw ClawHub clawhub.json must declare installSpec starting with clawhub:" }];
3275
+ }
3276
+ if (typeof parsed.name !== "string" || parsed.name.trim().length === 0) {
3277
+ return [{ artifact, message: "OpenClaw ClawHub clawhub.json must declare a non-empty name" }];
3278
+ }
3279
+ return [];
3280
+ } catch (error) {
3281
+ return [{ artifact, message: `OpenClaw ClawHub clawhub.json must be valid JSON: ${errorMessage(error)}` }];
3282
+ }
3283
+ }
2979
3284
  async function openClawPluginManifestPaths(artifact) {
2980
3285
  const root = artifactPath(artifact);
2981
- const candidates = [join14(root, "plugin.json"), join14(root, "openclaw.plugin.json")];
3286
+ const candidates = [join15(root, "plugin.json"), join15(root, "openclaw.plugin.json")];
2982
3287
  const existing = await Promise.all(candidates.map(async (candidate) => await pathExists(candidate) ? candidate : void 0));
2983
3288
  return existing.filter((candidate) => candidate !== void 0);
2984
3289
  }
2985
3290
  async function parseOpenClawPluginManifest(manifestPath) {
2986
- const manifestName = basename6(manifestPath);
3291
+ const manifestName = basename7(manifestPath);
2987
3292
  try {
2988
- const parsed = JSON.parse(await readFile13(manifestPath, "utf8"));
3293
+ const parsed = JSON.parse(await readFile15(manifestPath, "utf8"));
2989
3294
  if (!isRecord5(parsed)) {
2990
3295
  return { ok: false, path: manifestPath, message: `OpenClaw ${manifestName} must be a JSON object` };
2991
3296
  }
@@ -3002,7 +3307,7 @@ async function parseJsonObjectArtifact(artifact) {
3002
3307
  return { ok: false, message: "merge artifacts must be JSON files" };
3003
3308
  }
3004
3309
  try {
3005
- const parsed = JSON.parse(await readFile13(artifactPath(artifact), "utf8"));
3310
+ const parsed = JSON.parse(await readFile15(artifactPath(artifact), "utf8"));
3006
3311
  if (!isRecord5(parsed)) return { ok: false, message: "merge artifacts must contain a JSON object" };
3007
3312
  return { ok: true, value: parsed };
3008
3313
  } catch (error) {
@@ -3014,7 +3319,7 @@ async function parseYamlObjectArtifact(artifact) {
3014
3319
  return { ok: false, message: "merge artifacts must be YAML files" };
3015
3320
  }
3016
3321
  try {
3017
- const document = parseDocument2(await readFile13(artifactPath(artifact), "utf8"));
3322
+ const document = parseDocument2(await readFile15(artifactPath(artifact), "utf8"));
3018
3323
  if (document.errors.length > 0) {
3019
3324
  return { ok: false, message: `merge artifact must be valid YAML: ${document.errors[0]?.message ?? "parse error"}` };
3020
3325
  }
@@ -3041,7 +3346,7 @@ function artifactLabel(artifact) {
3041
3346
  return `${owner}${artifact.type}/${artifact.name}`;
3042
3347
  }
3043
3348
  function hasExtension(artifact, extension) {
3044
- return basename6(artifact.name).toLowerCase().endsWith(extension) || basename6(artifactPath(artifact)).toLowerCase().endsWith(extension);
3349
+ return basename7(artifact.name).toLowerCase().endsWith(extension) || basename7(artifactPath(artifact)).toLowerCase().endsWith(extension);
3045
3350
  }
3046
3351
  function isPluginFormat(value) {
3047
3352
  return value !== void 0 && pluginFormats.includes(value);
@@ -3139,7 +3444,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
3139
3444
  }
3140
3445
  const currentHash2 = await transport.hashPath(op.destPath);
3141
3446
  if (existing2 && existing2.sourceHash === op.desiredHash) {
3142
- operations.push({ ...op, action: "skip", currentHash: currentHash2, manifestHash: existing2.hash, reason: "merged source already up to date" });
3447
+ operations.push(options.forceDrift ? { ...op, action: "update", currentHash: currentHash2, manifestHash: existing2.hash, reason: "force refreshing managed merge destination" } : { ...op, action: "skip", currentHash: currentHash2, manifestHash: existing2.hash, reason: "merged source already up to date" });
3143
3448
  } else {
3144
3449
  operations.push({ ...op, action: "update", currentHash: currentHash2, manifestHash: existing2?.hash, reason: existing2 ? "merge source changed" : "merge into existing destination" });
3145
3450
  }
@@ -3186,6 +3491,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
3186
3491
  action: "update",
3187
3492
  currentHash,
3188
3493
  manifestHash: existing.hash,
3494
+ overrideDrift: true,
3189
3495
  reason: reasonWithComposedDiff("force replacing drifted managed destination", op.composedFrom, existing.composedFrom),
3190
3496
  composedFromDiff
3191
3497
  });
@@ -3218,7 +3524,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
3218
3524
  for (const entry of effectiveEntries) {
3219
3525
  if (desired.has(entry.path)) continue;
3220
3526
  const semanticPlugin = entry.semanticPlugin;
3221
- const destPath = semanticPlugin ? targetRoot : join15(targetRoot, entry.path);
3527
+ const destPath = semanticPlugin ? targetRoot : join16(targetRoot, entry.path);
3222
3528
  if (!semanticPlugin && !await transport.pathExists(destPath)) continue;
3223
3529
  const currentHash = semanticPlugin ? entry.hash : await currentEntryHash(entry, destPath, transport);
3224
3530
  if (workspaceOwner && !entryOwnedByWorkspace(entry, workspaceOwner)) {
@@ -3245,7 +3551,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
3245
3551
  mode: entry.mode,
3246
3552
  composedFrom: entry.composedFrom,
3247
3553
  ...operationMetadataFromEntry(entry),
3248
- ...options.forceDrift ? { action: "remove", reason: "force removing drifted stale managed destination" } : {}
3554
+ ...options.forceDrift ? { action: "remove", reason: "force removing drifted stale managed destination", overrideDrift: true } : {}
3249
3555
  });
3250
3556
  } else {
3251
3557
  operations.push({
@@ -3367,15 +3673,15 @@ async function prepareManagedBlockOperations(desiredOps, adapter, targetRoot, tr
3367
3673
  return prepared;
3368
3674
  }
3369
3675
  async function shouldSkipClaudeBridge(adapter, op, targetRoot, transport) {
3370
- if (adapter.name !== "claude" || op.artifactType !== "instructions" || basename7(op.destPath).toLowerCase() !== "claude.md") {
3676
+ if (adapter.name !== "claude" || op.artifactType !== "instructions" || basename8(op.destPath).toLowerCase() !== "claude.md") {
3371
3677
  return false;
3372
3678
  }
3373
- const agentsPath = join15(targetRoot, "AGENTS.md");
3679
+ const agentsPath = join16(targetRoot, "AGENTS.md");
3374
3680
  return claudeInstructionBridgesAgents(op.destPath, agentsPath, transport);
3375
3681
  }
3376
3682
  async function warnOnCopilotDoubleRead(adapter, op, targetRoot, transport, options) {
3377
- if (adapter.name !== "claude" || op.artifactType !== "instructions" || basename7(op.destPath).toLowerCase() !== "claude.md") return;
3378
- const agentsPath = join15(targetRoot, "AGENTS.md");
3683
+ if (adapter.name !== "claude" || op.artifactType !== "instructions" || basename8(op.destPath).toLowerCase() !== "claude.md") return;
3684
+ const agentsPath = join16(targetRoot, "AGENTS.md");
3379
3685
  if (!await transport.pathExists(agentsPath)) return;
3380
3686
  if (await claudeInstructionBridgesAgents(op.destPath, agentsPath, transport)) return;
3381
3687
  options.warn?.("CLAUDE.md and AGENTS.md are separate instruction files; if Copilot is active it may read the managed instructions twice.");
@@ -3422,6 +3728,7 @@ async function planManagedBlockOperation(op, manifestByPath, transport, options,
3422
3728
  action: "update",
3423
3729
  currentHash: state.hash,
3424
3730
  manifestHash: existing.hash,
3731
+ overrideDrift: true,
3425
3732
  reason: reasonWithComposedDiff("force replacing drifted managed instruction block", op.composedFrom, existing.composedFrom),
3426
3733
  composedFromDiff
3427
3734
  }];
@@ -3452,13 +3759,13 @@ async function currentEntryHash(entry, destPath, transport) {
3452
3759
  if (entry.mode !== managedInstructionBlockMode) return transport.hashPath(destPath);
3453
3760
  const selector = managedInstructionSelector("logicalSelector" in entry ? entry.logicalSelector : void 0, entry.artifactType, entry.artifactName);
3454
3761
  const state = await readManagedInstructionBlockState(destPath, selector, transport);
3455
- return state.drifted ? state.markerHash : state.hash;
3762
+ return state.hash;
3456
3763
  }
3457
3764
  async function canStrictlyAdoptLegacyEntry(entry, op, targetRoot, transport) {
3458
3765
  if (entry.artifactType !== op.artifactType || entry.artifactName !== op.artifactName) return false;
3459
3766
  if (!op.desiredHash || entry.sourceHash !== op.desiredHash) return false;
3460
3767
  if (!packageIdentityMatches(entry, op)) return false;
3461
- const destPath = join15(targetRoot, entry.path);
3768
+ const destPath = join16(targetRoot, entry.path);
3462
3769
  if (!await transport.pathExists(destPath)) return false;
3463
3770
  return await transport.hashPath(destPath) === entry.hash;
3464
3771
  }
@@ -3589,7 +3896,7 @@ function keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, op
3589
3896
  artifactType: entry.artifactType,
3590
3897
  artifactName: entry.artifactName,
3591
3898
  kind: entry.kind,
3592
- destPath: operation?.destPath ?? join15(targetRoot, entry.path),
3899
+ destPath: operation?.destPath ?? join16(targetRoot, entry.path),
3593
3900
  relativeDestPath: entry.path,
3594
3901
  desiredHash: entry.sourceHash,
3595
3902
  currentHash: currentHash ?? operation?.currentHash ?? entry.hash,
@@ -3657,7 +3964,7 @@ async function operationForArtifact(artifact, adapter, targetRoot, installationT
3657
3964
  }
3658
3965
  }
3659
3966
  if (artifact.type === "subagents" && target.semantic === "codex-subagent") {
3660
- const destPath2 = join15(targetRoot, target.dest, `${installName.replace(/\.toml$/i, "")}.toml`);
3967
+ const destPath2 = join16(targetRoot, target.dest, `${installName.replace(/\.toml$/i, "")}.toml`);
3661
3968
  return {
3662
3969
  action: "create",
3663
3970
  artifactType: artifact.type,
@@ -3675,7 +3982,7 @@ async function operationForArtifact(artifact, adapter, targetRoot, installationT
3675
3982
  installName: installName.replace(/\.toml$/i, "")
3676
3983
  };
3677
3984
  }
3678
- const destPath = artifact.type === "instructions" || artifact.type === "settings" || isFileTarget(target.dest) ? join15(targetRoot, target.dest) : join15(targetRoot, target.dest, installName);
3985
+ const destPath = artifact.type === "instructions" || artifact.type === "settings" || isFileTarget(target.dest) ? join16(targetRoot, target.dest) : join16(targetRoot, target.dest, installName);
3679
3986
  return {
3680
3987
  action: "create",
3681
3988
  artifactType: artifact.type,
@@ -3754,12 +4061,12 @@ function isPendingInstallOperation(operation) {
3754
4061
  }
3755
4062
 
3756
4063
  // src/install/uninstall.ts
3757
- import { join as join16 } from "path";
4064
+ import { join as join17 } from "path";
3758
4065
  async function createUninstallPlan(manifest, transport = localTransport) {
3759
4066
  const operations = [];
3760
4067
  for (const entry of manifest.entries) {
3761
4068
  const semanticPlugin = "semanticPlugin" in entry ? entry.semanticPlugin : void 0;
3762
- const destPath = semanticPlugin ? manifest.targetRoot : join16(manifest.targetRoot, entry.path);
4069
+ const destPath = semanticPlugin ? manifest.targetRoot : join17(manifest.targetRoot, entry.path);
3763
4070
  if (!semanticPlugin && !await transport.pathExists(destPath)) continue;
3764
4071
  const currentHash = semanticPlugin ? entry.hash : await currentEntryHash2(entry, destPath, transport);
3765
4072
  if (currentHash !== entry.hash) {
@@ -3830,7 +4137,7 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
3830
4137
  const operations = [];
3831
4138
  for (const entry of manifest.entries) {
3832
4139
  const semanticPlugin = "semanticPlugin" in entry ? entry.semanticPlugin : void 0;
3833
- const destPath = semanticPlugin ? manifest.targetRoot : join16(manifest.targetRoot, entry.path);
4140
+ const destPath = semanticPlugin ? manifest.targetRoot : join17(manifest.targetRoot, entry.path);
3834
4141
  if (!semanticPlugin && !await transport.pathExists(destPath)) continue;
3835
4142
  const currentHash = semanticPlugin ? entry.hash : await currentEntryHash2(entry, destPath, transport);
3836
4143
  const remainingOwners = ownersByPath.get(entry.path) ?? [];
@@ -3924,7 +4231,7 @@ async function currentEntryHash2(entry, destPath, transport) {
3924
4231
  if (entry.mode !== managedInstructionBlockMode) return transport.hashPath(destPath);
3925
4232
  const selector = managedInstructionSelector("logicalSelector" in entry ? entry.logicalSelector : void 0, entry.artifactType, entry.artifactName);
3926
4233
  const state = await readManagedInstructionBlockState(destPath, selector, transport);
3927
- return state.drifted ? state.markerHash : state.hash;
4234
+ return state.hash;
3928
4235
  }
3929
4236
  function operationMetadataFromEntry2(entry, ownersOverride) {
3930
4237
  if ("owners" in entry) {
@@ -4148,16 +4455,18 @@ function ownerChains(lock, nodeId) {
4148
4455
  return incoming.flatMap((edge) => ownerChains(lock, edge.from).map((chain) => [...chain, `${edge.alias}:${nodeId}`]));
4149
4456
  }
4150
4457
 
4151
- // src/source/git.ts
4152
- import { execFile as execFile4 } from "child_process";
4153
- import { cp as cp2, mkdir as mkdir9, rename as rename3, rm as rm5, writeFile as writeFile11 } from "fs/promises";
4154
- import { homedir as homedir2 } from "os";
4155
- import { basename as basename9, dirname as dirname11, join as join19, resolve as resolve6 } from "path";
4156
- import { promisify as promisify4 } from "util";
4458
+ // src/source/clawhub.ts
4459
+ import { mkdir as mkdir11, rm as rm5, writeFile as writeFile13 } from "fs/promises";
4460
+ import { basename as basename10, dirname as dirname14, join as join20, resolve as resolve6 } from "path";
4461
+
4462
+ // src/source/local.ts
4463
+ import { createHash as createHash4 } from "crypto";
4464
+ import { readdir, stat as stat3 } from "fs/promises";
4465
+ import { basename as basename9, join as join19, relative as relative4, resolve as resolve5 } from "path";
4157
4466
 
4158
4467
  // src/model/package.ts
4159
- import { readFile as readFile14 } from "fs/promises";
4160
- import { join as join17 } from "path";
4468
+ import { readFile as readFile16 } from "fs/promises";
4469
+ import { join as join18 } from "path";
4161
4470
  import { parse as parse3, printParseErrorCode as printParseErrorCode2 } from "jsonc-parser";
4162
4471
  import { z as z5 } from "zod";
4163
4472
  var legacyArtifactTypeSchema = z5.enum([
@@ -4181,6 +4490,7 @@ var packageProvideBaseSchema = z5.object({
4181
4490
  var packageItemSchema = z5.object({
4182
4491
  format: artifactFormatSchema.optional(),
4183
4492
  requires: z5.array(packageItemRequireSchema).optional(),
4493
+ suggests: z5.array(packageItemSuggestSchema).optional(),
4184
4494
  compose: z5.array(packageComposeEntrySchema).optional(),
4185
4495
  runtimes: runtimeListSchema.optional()
4186
4496
  });
@@ -4194,6 +4504,10 @@ var packageDependencySchema = z5.object({
4194
4504
  integrity: z5.string().min(1).optional(),
4195
4505
  runtimes: runtimeListSchema.optional()
4196
4506
  });
4507
+ var packageSuggestionSchema = packageDependencySchema.extend({
4508
+ reason: z5.string().min(1).optional(),
4509
+ when: z5.string().min(1).optional()
4510
+ });
4197
4511
  var packageProvideV1Schema = packageProvideBaseSchema.extend({
4198
4512
  type: legacyArtifactTypeSchema
4199
4513
  });
@@ -4214,6 +4528,7 @@ var packageManifestV2Schema = z5.object({
4214
4528
  version: z5.string().min(1),
4215
4529
  runtimes: runtimeListSchema.optional(),
4216
4530
  requires: z5.record(z5.string().min(1), packageDependencySchema).optional(),
4531
+ suggests: z5.record(z5.string().min(1), packageSuggestionSchema).optional(),
4217
4532
  compose: z5.array(packageComposeEntrySchema).optional(),
4218
4533
  provides: z5.array(packageProvideSchema).default([])
4219
4534
  }).superRefine((manifest, ctx) => {
@@ -4234,7 +4549,7 @@ var packageManifestNames = [...openPackManifestNames, ...legacyPackageManifestNa
4234
4549
  var warnedLegacyManifestPaths = /* @__PURE__ */ new Set();
4235
4550
  async function findPackageManifestPath(root, options = {}) {
4236
4551
  for (const name of packageManifestNames) {
4237
- const candidate = join17(root, name);
4552
+ const candidate = join18(root, name);
4238
4553
  if (!await pathExists(candidate)) continue;
4239
4554
  if (isLegacyPackageManifestName(name) && options.warnLegacy !== false && !warnedLegacyManifestPaths.has(candidate)) {
4240
4555
  warnedLegacyManifestPaths.add(candidate);
@@ -4247,7 +4562,7 @@ async function findPackageManifestPath(root, options = {}) {
4247
4562
  async function readPackageManifest(root) {
4248
4563
  const path = await findPackageManifestPath(root);
4249
4564
  if (!path) return void 0;
4250
- const content = await readFile14(path, "utf8");
4565
+ const content = await readFile16(path, "utf8");
4251
4566
  const errors = [];
4252
4567
  const parsed = parse3(content, errors, { allowTrailingComma: true, disallowComments: false });
4253
4568
  if (errors.length > 0) {
@@ -4301,9 +4616,6 @@ function isRecord6(value) {
4301
4616
  }
4302
4617
 
4303
4618
  // src/source/local.ts
4304
- import { createHash as createHash4 } from "crypto";
4305
- import { readdir, stat as stat3 } from "fs/promises";
4306
- import { basename as basename8, join as join18, relative as relative4, resolve as resolve5 } from "path";
4307
4619
  var LocalSourceDriver = class {
4308
4620
  name = "local";
4309
4621
  async resolve(source) {
@@ -4333,29 +4645,29 @@ var LocalSourceDriver = class {
4333
4645
  }
4334
4646
  const artifacts = [];
4335
4647
  const root = resolved.resolvedPath;
4336
- const instructions = await firstExisting([join18(root, "instructions.md"), join18(root, "AGENTS.md")]);
4648
+ const instructions = await firstExisting([join19(root, "instructions.md"), join19(root, "AGENTS.md")]);
4337
4649
  if (instructions) {
4338
4650
  artifacts.push({
4339
4651
  type: "instructions",
4340
- name: basename8(instructions),
4652
+ name: basename9(instructions),
4341
4653
  sourcePath: instructions,
4342
- relativePath: basename8(instructions),
4654
+ relativePath: basename9(instructions),
4343
4655
  kind: "file",
4344
4656
  hash: await hashPath(instructions),
4345
4657
  packageName: resolved.packageName,
4346
4658
  channel: "managed"
4347
4659
  });
4348
4660
  }
4349
- const rulesDir = join18(root, "rules");
4661
+ const rulesDir = join19(root, "rules");
4350
4662
  if (await pathExists(rulesDir)) {
4351
4663
  for (const entry of await sortedDirEntries(rulesDir)) {
4352
- const full = join18(rulesDir, entry.name);
4664
+ const full = join19(rulesDir, entry.name);
4353
4665
  if (entry.isFile()) {
4354
4666
  artifacts.push({
4355
4667
  type: "rules",
4356
4668
  name: entry.name,
4357
4669
  sourcePath: full,
4358
- relativePath: join18("rules", entry.name),
4670
+ relativePath: join19("rules", entry.name),
4359
4671
  kind: "file",
4360
4672
  hash: await hashPath(full),
4361
4673
  packageName: resolved.packageName,
@@ -4364,16 +4676,16 @@ var LocalSourceDriver = class {
4364
4676
  }
4365
4677
  }
4366
4678
  }
4367
- const fragmentsDir = join18(root, "fragments");
4679
+ const fragmentsDir = join19(root, "fragments");
4368
4680
  if (await pathExists(fragmentsDir)) {
4369
4681
  for (const entry of await sortedDirEntries(fragmentsDir)) {
4370
- const full = join18(fragmentsDir, entry.name);
4682
+ const full = join19(fragmentsDir, entry.name);
4371
4683
  if (entry.isFile()) {
4372
4684
  artifacts.push({
4373
4685
  type: "fragments",
4374
4686
  name: entry.name,
4375
4687
  sourcePath: full,
4376
- relativePath: join18("fragments", entry.name),
4688
+ relativePath: join19("fragments", entry.name),
4377
4689
  kind: "file",
4378
4690
  hash: await hashPath(full),
4379
4691
  packageName: resolved.packageName,
@@ -4382,16 +4694,16 @@ var LocalSourceDriver = class {
4382
4694
  }
4383
4695
  }
4384
4696
  }
4385
- const skillsDir = join18(root, "skills");
4697
+ const skillsDir = join19(root, "skills");
4386
4698
  if (await pathExists(skillsDir)) {
4387
4699
  for (const entry of await sortedDirEntries(skillsDir)) {
4388
- const full = join18(skillsDir, entry.name);
4700
+ const full = join19(skillsDir, entry.name);
4389
4701
  if (entry.isDirectory()) {
4390
4702
  artifacts.push({
4391
4703
  type: "skills",
4392
4704
  name: entry.name,
4393
4705
  sourcePath: full,
4394
- relativePath: join18("skills", entry.name),
4706
+ relativePath: join19("skills", entry.name),
4395
4707
  kind: "dir",
4396
4708
  hash: await hashPath(full),
4397
4709
  packageName: resolved.packageName,
@@ -4402,7 +4714,7 @@ var LocalSourceDriver = class {
4402
4714
  type: "skills",
4403
4715
  name: entry.name.replace(/\.md$/, ""),
4404
4716
  sourcePath: full,
4405
- relativePath: join18("skills", entry.name),
4717
+ relativePath: join19("skills", entry.name),
4406
4718
  kind: "file",
4407
4719
  hash: await hashPath(full),
4408
4720
  packageName: resolved.packageName,
@@ -4412,7 +4724,7 @@ var LocalSourceDriver = class {
4412
4724
  }
4413
4725
  }
4414
4726
  for (const type of ["commands", "subagents", "mcp", "hooks", "settings", "plugins"]) {
4415
- const dir = join18(root, type);
4727
+ const dir = join19(root, type);
4416
4728
  if (!await pathExists(dir)) continue;
4417
4729
  artifacts.push(...await listGenericArtifacts(type, dir, type, resolved.packageName));
4418
4730
  }
@@ -4428,7 +4740,7 @@ var LocalSourceDriver = class {
4428
4740
  findings.push({ level: "warning", message: "No instructions.md or AGENTS.md found", path: resolved.resolvedPath });
4429
4741
  }
4430
4742
  for (const artifact of artifacts.filter((item) => item.type === "skills" && item.kind === "dir")) {
4431
- if (!await pathExists(join18(artifact.sourcePath, "SKILL.md"))) {
4743
+ if (!await pathExists(join19(artifact.sourcePath, "SKILL.md"))) {
4432
4744
  findings.push({ level: "warning", message: `Skill directory has no SKILL.md: ${artifact.name}`, path: artifact.sourcePath });
4433
4745
  }
4434
4746
  }
@@ -4452,7 +4764,7 @@ async function hashLocalSource(root, manifest) {
4452
4764
  }
4453
4765
  const provides = [...manifest.provides].sort((a, b) => `${a.type}\0${a.path}`.localeCompare(`${b.type}\0${b.path}`));
4454
4766
  for (const provide of provides) {
4455
- const full = join18(root, provide.path);
4767
+ const full = join19(root, provide.path);
4456
4768
  if (!await pathExists(full)) continue;
4457
4769
  hash.update("provide\0");
4458
4770
  hash.update(provide.type).update("\0");
@@ -4475,27 +4787,27 @@ async function listFromManifest(root, packageName) {
4475
4787
  if (!manifest) return [];
4476
4788
  const artifacts = [];
4477
4789
  for (const provide of manifest.provides) {
4478
- const full = join18(root, provide.path);
4790
+ const full = join19(root, provide.path);
4479
4791
  if (!await pathExists(full)) continue;
4480
4792
  const stats = await stat3(full);
4481
4793
  if (provide.type === "instructions") {
4482
4794
  if (stats.isFile()) {
4483
- artifacts.push(await artifactForFile(provide.type, basename8(full), full, provide.path, packageName, provide, manifest, basename8(full)));
4795
+ artifacts.push(await artifactForFile(provide.type, basename9(full), full, provide.path, packageName, provide, manifest, basename9(full)));
4484
4796
  }
4485
4797
  continue;
4486
4798
  }
4487
4799
  if (stats.isDirectory()) {
4488
4800
  for (const entry of await sortedDirEntries(full)) {
4489
- const child = join18(full, entry.name);
4801
+ const child = join19(full, entry.name);
4490
4802
  if ((provide.type === "skills" || provide.type === "plugins" || provide.type === "subagents") && entry.isDirectory()) {
4491
- artifacts.push(await artifactForDir(provide.type, entry.name, child, join18(provide.path, entry.name), packageName, provide, manifest, entry.name));
4803
+ artifacts.push(await artifactForDir(provide.type, entry.name, child, join19(provide.path, entry.name), packageName, provide, manifest, entry.name));
4492
4804
  } else if (entry.isFile()) {
4493
4805
  const name = provide.type === "rules" && entry.name.endsWith(".md") ? entry.name : entry.name;
4494
- artifacts.push(await artifactForFile(provide.type, name, child, join18(provide.path, entry.name), packageName, provide, manifest, name));
4806
+ artifacts.push(await artifactForFile(provide.type, name, child, join19(provide.path, entry.name), packageName, provide, manifest, name));
4495
4807
  }
4496
4808
  }
4497
4809
  } else if (stats.isFile()) {
4498
- artifacts.push(await artifactForFile(provide.type, basename8(full), full, provide.path, packageName, provide, manifest, basename8(full)));
4810
+ artifacts.push(await artifactForFile(provide.type, basename9(full), full, provide.path, packageName, provide, manifest, basename9(full)));
4499
4811
  }
4500
4812
  }
4501
4813
  return artifacts;
@@ -4503,11 +4815,11 @@ async function listFromManifest(root, packageName) {
4503
4815
  async function listGenericArtifacts(type, dir, relativeRoot, packageName) {
4504
4816
  const artifacts = [];
4505
4817
  for (const entry of await sortedDirEntries(dir)) {
4506
- const full = join18(dir, entry.name);
4818
+ const full = join19(dir, entry.name);
4507
4819
  if (entry.isDirectory()) {
4508
- artifacts.push(await artifactForDir(type, entry.name, full, join18(relativeRoot, entry.name), packageName));
4820
+ artifacts.push(await artifactForDir(type, entry.name, full, join19(relativeRoot, entry.name), packageName));
4509
4821
  } else if (entry.isFile()) {
4510
- artifacts.push(await artifactForFile(type, entry.name, full, join18(relativeRoot, entry.name), packageName));
4822
+ artifacts.push(await artifactForFile(type, entry.name, full, join19(relativeRoot, entry.name), packageName));
4511
4823
  }
4512
4824
  }
4513
4825
  return artifacts;
@@ -4527,6 +4839,7 @@ async function artifactForFile(type, name, sourcePath, relativePath, packageName
4527
4839
  assets: provide?.assets,
4528
4840
  required: provide?.required,
4529
4841
  requires: item.requires,
4842
+ suggests: item.suggests,
4530
4843
  compose: item.compose,
4531
4844
  runtimes: item.runtimes ?? provideRuntimes(provide) ?? manifestRuntimes(manifest)
4532
4845
  };
@@ -4546,6 +4859,7 @@ async function artifactForDir(type, name, sourcePath, relativePath, packageName,
4546
4859
  assets: provide?.assets,
4547
4860
  required: provide?.required,
4548
4861
  requires: item.requires,
4862
+ suggests: item.suggests,
4549
4863
  compose: item.compose,
4550
4864
  runtimes: item.runtimes ?? provideRuntimes(provide) ?? manifestRuntimes(manifest)
4551
4865
  };
@@ -4554,7 +4868,7 @@ function itemMetadata(provide, itemName) {
4554
4868
  if (!provide || !("items" in provide) || !provide.items || !itemName) return {};
4555
4869
  const item = provide.items[itemName];
4556
4870
  if (!item) return {};
4557
- return { format: item.format, requires: item.requires, compose: item.compose, runtimes: item.runtimes };
4871
+ return { format: item.format, requires: item.requires, suggests: item.suggests, compose: item.compose, runtimes: item.runtimes };
4558
4872
  }
4559
4873
  function provideRuntimes(provide) {
4560
4874
  return provide && "runtimes" in provide ? provide.runtimes : void 0;
@@ -4563,7 +4877,154 @@ function manifestRuntimes(manifest) {
4563
4877
  return manifest && manifest.schemaVersion === 2 ? manifest.runtimes : void 0;
4564
4878
  }
4565
4879
 
4880
+ // src/source/clawhub.ts
4881
+ var clawHubBaseUrl = "https://clawhub.ai/api/v1";
4882
+ var sourcePrefix = "clawhub:";
4883
+ var transientStatuses = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
4884
+ var ClawHubSourceDriver = class {
4885
+ constructor(fetchImpl = fetch) {
4886
+ this.fetchImpl = fetchImpl;
4887
+ }
4888
+ fetchImpl;
4889
+ name = "clawhub";
4890
+ local = new LocalSourceDriver();
4891
+ async resolve(source, options = {}) {
4892
+ const packageName = parseClawHubSource(source);
4893
+ return {
4894
+ driver: this.name,
4895
+ source,
4896
+ resolvedPath: cachePathFor(packageName, options.cacheRoot),
4897
+ packageName: `clawhub/${packageName}`,
4898
+ mode: options.mode ?? "tracking",
4899
+ requestedRef: options.ref ?? "latest",
4900
+ frozenLock: options.frozenLock,
4901
+ cacheLockTimeoutMs: options.cacheLockTimeoutMs
4902
+ };
4903
+ }
4904
+ async fetch(resolved) {
4905
+ if (resolved.frozenLock) {
4906
+ if (!await pathExists(resolved.resolvedPath)) {
4907
+ throw new Error(`Frozen lock requires cached ClawHub source at ${resolved.resolvedPath}`);
4908
+ }
4909
+ return {
4910
+ ...resolved,
4911
+ sourceHash: await hashPath(resolved.resolvedPath)
4912
+ };
4913
+ }
4914
+ const requestedName = parseClawHubSource(resolved.source);
4915
+ const response = await fetchClawHubPackage(this.fetchImpl, requestedName);
4916
+ if (!response.ok) {
4917
+ throw new Error(`ClawHub lookup failed for ${requestedName}: HTTP ${response.status}`);
4918
+ }
4919
+ const payload = await response.json();
4920
+ const packageInfo = payload.package;
4921
+ if (!packageInfo?.name) {
4922
+ throw new Error(`ClawHub response missing package metadata for ${requestedName}`);
4923
+ }
4924
+ if (!isInstallableOpenClawPackage(packageInfo.family)) {
4925
+ throw new Error(`ClawHub package is not an OpenClaw plugin or hook package: ${packageInfo.name}`);
4926
+ }
4927
+ await writeGeneratedPackage(resolved.resolvedPath, packageInfo);
4928
+ return {
4929
+ ...resolved,
4930
+ packageName: `clawhub/${packageInfo.name}`,
4931
+ packageVersion: packageInfo.latestVersion ?? "latest",
4932
+ sourceHash: await hashPath(resolved.resolvedPath)
4933
+ };
4934
+ }
4935
+ async list(resolved) {
4936
+ return this.local.list({ ...resolved, driver: "local" });
4937
+ }
4938
+ async scan(resolved) {
4939
+ if (!await pathExists(join20(resolved.resolvedPath, "plugins"))) {
4940
+ return { ok: false, findings: [{ level: "error", message: "ClawHub source has no generated plugin artifact" }] };
4941
+ }
4942
+ return { ok: true, findings: [] };
4943
+ }
4944
+ async translate(resolved) {
4945
+ return resolved;
4946
+ }
4947
+ async export(resolved) {
4948
+ return resolved;
4949
+ }
4950
+ };
4951
+ async function fetchClawHubPackage(fetchImpl, packageName) {
4952
+ const url = `${clawHubBaseUrl}/packages/${encodeURIComponent(packageName)}`;
4953
+ for (let attempt = 0; attempt < 3; attempt++) {
4954
+ const response = await fetchImpl(url, { headers: { Accept: "application/json" } });
4955
+ if (!transientStatuses.has(response.status) || attempt === 2) return response;
4956
+ await delay((attempt + 1) * 250);
4957
+ }
4958
+ throw new Error(`ClawHub lookup failed for ${packageName}`);
4959
+ }
4960
+ async function delay(ms) {
4961
+ await new Promise((resolve21) => setTimeout(resolve21, ms));
4962
+ }
4963
+ function parseClawHubSource(source) {
4964
+ if (!source.startsWith(sourcePrefix)) {
4965
+ throw new Error(`Invalid ClawHub source: ${source}`);
4966
+ }
4967
+ const packageName = source.slice(sourcePrefix.length).trim();
4968
+ if (!packageName) throw new Error(`Invalid ClawHub source: ${source}`);
4969
+ return packageName;
4970
+ }
4971
+ function isInstallableOpenClawPackage(family) {
4972
+ if (!family) return true;
4973
+ return family.endsWith("-plugin") || family === "hook-pack";
4974
+ }
4975
+ async function writeGeneratedPackage(root, packageInfo) {
4976
+ const name = packageInfo.name?.trim();
4977
+ if (!name) throw new Error("ClawHub package metadata must include a name");
4978
+ const pluginId = installNameFor2(packageInfo.runtimeId ?? name);
4979
+ const plugin = {
4980
+ name,
4981
+ installSpec: `${sourcePrefix}${name}`,
4982
+ pluginId,
4983
+ displayName: packageInfo.displayName,
4984
+ runtimeId: packageInfo.runtimeId,
4985
+ latestVersion: packageInfo.latestVersion,
4986
+ family: packageInfo.family,
4987
+ summary: packageInfo.summary ?? packageInfo.description,
4988
+ artifact: packageInfo.artifact,
4989
+ verification: packageInfo.verification
4990
+ };
4991
+ const pluginPath = join20(root, "plugins", pluginId, "clawhub.json");
4992
+ await rm5(root, { recursive: true, force: true });
4993
+ await mkdir11(dirname14(pluginPath), { recursive: true });
4994
+ await writeFile13(join20(root, "openpack.json"), `${JSON.stringify({
4995
+ schemaVersion: 2,
4996
+ name: `clawhub/${name}`,
4997
+ version: packageInfo.latestVersion ?? "latest",
4998
+ runtimes: ["openclaw"],
4999
+ provides: [
5000
+ {
5001
+ type: "plugins",
5002
+ path: "plugins",
5003
+ format: "openclaw-clawhub-plugin",
5004
+ runtimes: ["openclaw"],
5005
+ required: true
5006
+ }
5007
+ ]
5008
+ }, null, 2)}
5009
+ `, "utf8");
5010
+ await writeFile13(pluginPath, `${JSON.stringify(plugin, null, 2)}
5011
+ `, "utf8");
5012
+ }
5013
+ function installNameFor2(value) {
5014
+ return basename10(value).replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "clawhub-plugin";
5015
+ }
5016
+ function cachePathFor(packageName, cacheRoot) {
5017
+ const root = cacheRoot ? resolve6(cacheRoot) : join20(process.env.HOME ?? ".", ".agentwheel", "cache");
5018
+ const slug2 = `clawhub-${packageName}`.replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "");
5019
+ return join20(root, slug2 || "clawhub-package");
5020
+ }
5021
+
4566
5022
  // src/source/git.ts
5023
+ import { execFile as execFile4 } from "child_process";
5024
+ import { cp as cp2, mkdir as mkdir12, rename as rename3, rm as rm6, writeFile as writeFile14 } from "fs/promises";
5025
+ import { homedir as homedir2 } from "os";
5026
+ import { basename as basename11, dirname as dirname15, join as join21, resolve as resolve7 } from "path";
5027
+ import { promisify as promisify4 } from "util";
4567
5028
  var execFileAsync4 = promisify4(execFile4);
4568
5029
  var GitSourceDriver = class {
4569
5030
  name = "git";
@@ -4575,7 +5036,7 @@ var GitSourceDriver = class {
4575
5036
  return {
4576
5037
  driver: this.name,
4577
5038
  source,
4578
- resolvedPath: cachePathFor(parsed.url, options.cacheRoot),
5039
+ resolvedPath: cachePathFor2(parsed.url, options.cacheRoot),
4579
5040
  mode,
4580
5041
  requestedRef,
4581
5042
  frozenLock: options.frozenLock,
@@ -4585,12 +5046,12 @@ var GitSourceDriver = class {
4585
5046
  async fetch(resolved) {
4586
5047
  return withFilesystemLock(`${resolved.resolvedPath}.lock`, resolved.cacheLockTimeoutMs ?? 3e4, async () => {
4587
5048
  const parsed = parseGitSource(resolved.source);
4588
- await mkdir9(resolve6(resolved.resolvedPath, ".."), { recursive: true });
4589
- if (!await pathExists(join19(resolved.resolvedPath, ".git"))) {
5049
+ await mkdir12(resolve7(resolved.resolvedPath, ".."), { recursive: true });
5050
+ if (!await pathExists(join21(resolved.resolvedPath, ".git"))) {
4590
5051
  if (resolved.frozenLock) {
4591
5052
  throw new Error(`Frozen lock requires cached git checkout at ${resolved.resolvedPath}`);
4592
5053
  }
4593
- await rm5(resolved.resolvedPath, { recursive: true, force: true });
5054
+ await rm6(resolved.resolvedPath, { recursive: true, force: true });
4594
5055
  await git(["clone", "--no-tags", parsed.url, resolved.resolvedPath]);
4595
5056
  } else if (!resolved.frozenLock) {
4596
5057
  await git(["-C", resolved.resolvedPath, "fetch", "--prune", "origin"]);
@@ -4652,65 +5113,201 @@ function parseGitSource(source) {
4652
5113
  }
4653
5114
  throw new Error(`Invalid git source: ${source}`);
4654
5115
  }
4655
- function cachePathFor(url, cacheRoot) {
4656
- const root = cacheRoot ? resolve6(cacheRoot) : join19(homedir2(), ".agentwheel", "cache");
5116
+ function cachePathFor2(url, cacheRoot) {
5117
+ const root = cacheRoot ? resolve7(cacheRoot) : join21(homedir2(), ".agentwheel", "cache");
4657
5118
  const slug2 = url.replace(/^[a-z]+:\/\//i, "").replace(/\.git$/i, "").replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "");
4658
- return join19(root, slug2 || basename9(url));
5119
+ return join21(root, slug2 || basename11(url));
4659
5120
  }
4660
5121
  async function git(args) {
4661
5122
  return execFileAsync4("git", args, { maxBuffer: 1024 * 1024 * 10 });
4662
5123
  }
4663
5124
  async function snapshotCheckout(checkoutPath, commit) {
4664
- const snapshotPath = join19(dirname11(checkoutPath), `${basename9(checkoutPath)}-${commit.slice(0, 12)}`);
5125
+ const snapshotPath = join21(dirname15(checkoutPath), `${basename11(checkoutPath)}-${commit.slice(0, 12)}`);
4665
5126
  if (await pathExists(snapshotPath)) return snapshotPath;
4666
- const tempPath = join19(dirname11(checkoutPath), `${basename9(snapshotPath)}.tmp-${process.pid}-${Date.now()}`);
4667
- await rm5(tempPath, { recursive: true, force: true });
5127
+ const tempPath = join21(dirname15(checkoutPath), `${basename11(snapshotPath)}.tmp-${process.pid}-${Date.now()}`);
5128
+ await rm6(tempPath, { recursive: true, force: true });
4668
5129
  await cp2(checkoutPath, tempPath, { recursive: true, dereference: true });
4669
- await rm5(join19(tempPath, ".git"), { recursive: true, force: true });
5130
+ await rm6(join21(tempPath, ".git"), { recursive: true, force: true });
4670
5131
  try {
4671
5132
  await rename3(tempPath, snapshotPath);
4672
5133
  } catch (error) {
4673
5134
  if (!isAlreadyExists2(error)) throw error;
4674
- await rm5(tempPath, { recursive: true, force: true });
5135
+ await rm6(tempPath, { recursive: true, force: true });
4675
5136
  return snapshotPath;
4676
5137
  }
4677
5138
  return snapshotPath;
4678
5139
  }
4679
5140
  async function withFilesystemLock(lockPath, timeoutMs, fn) {
4680
- await mkdir9(dirname11(lockPath), { recursive: true });
5141
+ await mkdir12(dirname15(lockPath), { recursive: true });
4681
5142
  const started = Date.now();
4682
5143
  while (true) {
4683
5144
  try {
4684
- await mkdir9(lockPath);
4685
- await writeFile11(join19(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf8");
5145
+ await mkdir12(lockPath);
5146
+ await writeFile14(join21(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf8");
4686
5147
  break;
4687
5148
  } catch (error) {
4688
5149
  if (!isAlreadyExists2(error)) throw error;
4689
5150
  if (Date.now() - started > timeoutMs) {
4690
5151
  throw new Error(`Timed out waiting for git cache lock at ${lockPath}`);
4691
5152
  }
4692
- await new Promise((resolve19) => setTimeout(resolve19, 50));
5153
+ await new Promise((resolve21) => setTimeout(resolve21, 50));
4693
5154
  }
4694
5155
  }
4695
5156
  try {
4696
5157
  return await fn();
4697
5158
  } finally {
4698
- await rm5(lockPath, { recursive: true, force: true });
5159
+ await rm6(lockPath, { recursive: true, force: true });
4699
5160
  }
4700
5161
  }
4701
5162
  function isAlreadyExists2(error) {
4702
5163
  return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
4703
5164
  }
4704
5165
 
5166
+ // src/source/mcp-registry.ts
5167
+ import { mkdir as mkdir13, writeFile as writeFile15 } from "fs/promises";
5168
+ import { basename as basename12, dirname as dirname16, join as join22, resolve as resolve8 } from "path";
5169
+ var registryBaseUrl = "https://registry.modelcontextprotocol.io/v0.1";
5170
+ var sourcePrefix2 = "mcp-registry:";
5171
+ var McpRegistrySourceDriver = class {
5172
+ constructor(fetchImpl = fetch) {
5173
+ this.fetchImpl = fetchImpl;
5174
+ }
5175
+ fetchImpl;
5176
+ name = "mcp-registry";
5177
+ local = new LocalSourceDriver();
5178
+ async resolve(source, options = {}) {
5179
+ const serverName = parseMcpRegistrySource(source);
5180
+ return {
5181
+ driver: this.name,
5182
+ source,
5183
+ resolvedPath: cachePathFor3(serverName, options.cacheRoot),
5184
+ packageName: `mcp-registry/${serverName}`,
5185
+ mode: options.mode ?? "tracking",
5186
+ requestedRef: options.ref ?? "latest",
5187
+ frozenLock: options.frozenLock
5188
+ };
5189
+ }
5190
+ async fetch(resolved) {
5191
+ if (resolved.frozenLock) {
5192
+ if (!await pathExists(resolved.resolvedPath)) {
5193
+ throw new Error(`Frozen lock requires cached MCP registry source at ${resolved.resolvedPath}`);
5194
+ }
5195
+ return {
5196
+ ...resolved,
5197
+ sourceHash: await hashPath(resolved.resolvedPath)
5198
+ };
5199
+ }
5200
+ const serverName = parseMcpRegistrySource(resolved.source);
5201
+ const response = await this.fetchImpl(`${registryBaseUrl}/servers/${encodeURIComponent(serverName)}/versions/latest`, {
5202
+ headers: { Accept: "application/json" }
5203
+ });
5204
+ if (!response.ok) {
5205
+ throw new Error(`MCP registry lookup failed for ${serverName}: HTTP ${response.status}`);
5206
+ }
5207
+ const payload = await response.json();
5208
+ const server = payload.server;
5209
+ if (!server?.name) {
5210
+ throw new Error(`MCP registry response missing server metadata for ${serverName}`);
5211
+ }
5212
+ const remote = supportedRemote(server.remotes ?? []);
5213
+ if (!remote) {
5214
+ throw new Error(`MCP registry server is discovery-only for Agentwheel: ${serverName}`);
5215
+ }
5216
+ await writeGeneratedPackage2(resolved.resolvedPath, {
5217
+ serverName: server.name,
5218
+ title: server.title,
5219
+ description: server.description,
5220
+ version: server.version,
5221
+ url: remote.url
5222
+ });
5223
+ return {
5224
+ ...resolved,
5225
+ packageName: `mcp-registry/${server.name}`,
5226
+ packageVersion: server.version,
5227
+ sourceHash: await hashPath(resolved.resolvedPath)
5228
+ };
5229
+ }
5230
+ async list(resolved) {
5231
+ return this.local.list({ ...resolved, driver: "local" });
5232
+ }
5233
+ async scan(resolved) {
5234
+ if (!await pathExists(join22(resolved.resolvedPath, "mcp"))) {
5235
+ return { ok: false, findings: [{ level: "error", message: "MCP registry source has no generated mcp artifact" }] };
5236
+ }
5237
+ return { ok: true, findings: [] };
5238
+ }
5239
+ async translate(resolved) {
5240
+ return resolved;
5241
+ }
5242
+ async export(resolved) {
5243
+ return resolved;
5244
+ }
5245
+ };
5246
+ function parseMcpRegistrySource(source) {
5247
+ if (!source.startsWith(sourcePrefix2)) {
5248
+ throw new Error(`Invalid MCP registry source: ${source}`);
5249
+ }
5250
+ const serverName = source.slice(sourcePrefix2.length).trim();
5251
+ if (!serverName) throw new Error(`Invalid MCP registry source: ${source}`);
5252
+ return serverName;
5253
+ }
5254
+ function supportedRemote(remotes) {
5255
+ for (const remote of remotes ?? []) {
5256
+ if (remote?.type !== "streamable-http") continue;
5257
+ if (!isSafeHttpUrl(remote.url)) continue;
5258
+ if ((remote.headers ?? []).some((header) => header.isRequired && header.isSecret)) continue;
5259
+ return { url: remote.url };
5260
+ }
5261
+ return void 0;
5262
+ }
5263
+ function isSafeHttpUrl(value) {
5264
+ if (typeof value !== "string") return false;
5265
+ try {
5266
+ const url = new URL(value);
5267
+ return url.protocol === "https:" || url.protocol === "http:";
5268
+ } catch {
5269
+ return false;
5270
+ }
5271
+ }
5272
+ async function writeGeneratedPackage2(root, server) {
5273
+ const serverId = installNameFor3(server.serverName);
5274
+ const mcpPath = join22(root, "mcp", `${serverId}.json`);
5275
+ await mkdir13(dirname16(mcpPath), { recursive: true });
5276
+ await writeFile15(join22(root, "openpack.json"), `${JSON.stringify({
5277
+ schemaVersion: 2,
5278
+ name: `mcp-registry/${server.serverName}`,
5279
+ version: server.version ?? "latest",
5280
+ provides: [{ type: "mcp", path: "mcp" }]
5281
+ }, null, 2)}
5282
+ `, "utf8");
5283
+ await writeFile15(mcpPath, `${JSON.stringify({
5284
+ mcpServers: {
5285
+ [serverId]: {
5286
+ type: "streamable-http",
5287
+ url: server.url
5288
+ }
5289
+ }
5290
+ }, null, 2)}
5291
+ `, "utf8");
5292
+ }
5293
+ function installNameFor3(serverName) {
5294
+ return basename12(serverName).replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "mcp-server";
5295
+ }
5296
+ function cachePathFor3(serverName, cacheRoot) {
5297
+ const root = cacheRoot ? resolve8(cacheRoot) : join22(process.env.HOME ?? ".", ".agentwheel", "cache");
5298
+ const slug2 = `mcp-registry-${serverName}`.replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "");
5299
+ return join22(root, slug2 || "mcp-registry-server");
5300
+ }
5301
+
4705
5302
  // src/source/skillkit.ts
4706
- import { cp as cp3, mkdir as mkdir10, readFile as readFile15, rm as rm6 } from "fs/promises";
5303
+ import { cp as cp3, mkdir as mkdir14, readFile as readFile17, rm as rm7 } from "fs/promises";
4707
5304
  import { homedir as homedir3 } from "os";
4708
- import { basename as basename11, dirname as dirname13, join as join21, resolve as resolve7 } from "path";
5305
+ import { basename as basename14, dirname as dirname18, join as join24, resolve as resolve9 } from "path";
4709
5306
  import * as defaultSkillKit from "@skillkit/core";
4710
5307
 
4711
5308
  // src/source/skill-artifacts.ts
4712
5309
  import { readdir as readdir2, stat as stat4 } from "fs/promises";
4713
- import { basename as basename10, dirname as dirname12, extname as extname2, join as join20 } from "path";
5310
+ import { basename as basename13, dirname as dirname17, extname as extname2, join as join23 } from "path";
4714
5311
  async function artifactsFromSkillPaths(paths, packageName) {
4715
5312
  const artifacts = [];
4716
5313
  const seen = /* @__PURE__ */ new Set();
@@ -4732,28 +5329,28 @@ async function discoverSkillPaths(root) {
4732
5329
  async function artifactFromSkillPath(item, packageName) {
4733
5330
  const stats = await stat4(item.path);
4734
5331
  if (stats.isDirectory()) {
4735
- const skillMd = join20(item.path, "SKILL.md");
5332
+ const skillMd = join23(item.path, "SKILL.md");
4736
5333
  if (!await pathExists(skillMd)) return void 0;
4737
- const name = sanitizeSkillName(item.name ?? basename10(item.path));
5334
+ const name = sanitizeSkillName(item.name ?? basename13(item.path));
4738
5335
  return {
4739
5336
  type: "skills",
4740
5337
  name,
4741
5338
  sourcePath: item.path,
4742
- relativePath: join20("skills", name),
5339
+ relativePath: join23("skills", name),
4743
5340
  kind: "dir",
4744
5341
  hash: await hashPath(item.path),
4745
5342
  packageName,
4746
5343
  channel: "managed"
4747
5344
  };
4748
5345
  }
4749
- if (stats.isFile() && basename10(item.path).toLowerCase() === "skill.md") {
4750
- const dir = dirname12(item.path);
4751
- const name = sanitizeSkillName(item.name ?? basename10(dir));
5346
+ if (stats.isFile() && basename13(item.path).toLowerCase() === "skill.md") {
5347
+ const dir = dirname17(item.path);
5348
+ const name = sanitizeSkillName(item.name ?? basename13(dir));
4752
5349
  return {
4753
5350
  type: "skills",
4754
5351
  name,
4755
5352
  sourcePath: dir,
4756
- relativePath: join20("skills", name),
5353
+ relativePath: join23("skills", name),
4757
5354
  kind: "dir",
4758
5355
  hash: await hashPath(dir),
4759
5356
  packageName,
@@ -4761,12 +5358,12 @@ async function artifactFromSkillPath(item, packageName) {
4761
5358
  };
4762
5359
  }
4763
5360
  if (stats.isFile() && extname2(item.path).toLowerCase() === ".md") {
4764
- const name = sanitizeSkillName(item.name ?? basename10(item.path, ".md"));
5361
+ const name = sanitizeSkillName(item.name ?? basename13(item.path, ".md"));
4765
5362
  return {
4766
5363
  type: "skills",
4767
5364
  name,
4768
5365
  sourcePath: item.path,
4769
- relativePath: join20("skills", `${name}.md`),
5366
+ relativePath: join23("skills", `${name}.md`),
4770
5367
  kind: "file",
4771
5368
  hash: await hashPath(item.path),
4772
5369
  packageName,
@@ -4784,7 +5381,7 @@ async function walk(dir, paths) {
4784
5381
  }
4785
5382
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
4786
5383
  if (!entry.isDirectory() || entry.name === ".git" || entry.name === "node_modules") continue;
4787
- await walk(join20(dir, entry.name), paths);
5384
+ await walk(join23(dir, entry.name), paths);
4788
5385
  }
4789
5386
  }
4790
5387
  function sanitizeSkillName(name) {
@@ -4801,12 +5398,12 @@ var SkillKitSourceDriver = class {
4801
5398
  async resolve(source, options = {}) {
4802
5399
  const spec = parseSkillKitSource(source);
4803
5400
  if (await pathExists(spec)) {
4804
- const resolvedPath = resolve7(spec);
5401
+ const resolvedPath = resolve9(spec);
4805
5402
  return {
4806
5403
  driver: this.name,
4807
5404
  source,
4808
5405
  resolvedPath,
4809
- packageName: `skillkit/${basename11(resolvedPath)}`,
5406
+ packageName: `skillkit/${basename14(resolvedPath)}`,
4810
5407
  mode: options.mode ?? "pinned",
4811
5408
  sourceHash: await hashPath(resolvedPath)
4812
5409
  };
@@ -4814,7 +5411,7 @@ var SkillKitSourceDriver = class {
4814
5411
  return {
4815
5412
  driver: this.name,
4816
5413
  source,
4817
- resolvedPath: cachePathFor2(spec, options.cacheRoot),
5414
+ resolvedPath: cachePathFor4(spec, options.cacheRoot),
4818
5415
  packageName: `skillkit/${packageSlug(spec)}`,
4819
5416
  mode: options.mode ?? "tracking",
4820
5417
  requestedRef: options.ref,
@@ -4840,17 +5437,17 @@ var SkillKitSourceDriver = class {
4840
5437
  if (!provider?.clone) {
4841
5438
  throw new Error("SkillKit provider API unavailable or cannot resolve source. Expected @skillkit/core detectProvider().clone().");
4842
5439
  }
4843
- await mkdir10(dirname13(resolved.resolvedPath), { recursive: true });
5440
+ await mkdir14(dirname18(resolved.resolvedPath), { recursive: true });
4844
5441
  const result = await provider.clone(providerSpec, resolved.resolvedPath, {});
4845
5442
  if (!result.success || !result.path) {
4846
5443
  throw new Error(`SkillKit provider failed to fetch ${spec}: ${result.error ?? "unknown error"}`);
4847
5444
  }
4848
- if (resolve7(result.path) !== resolve7(resolved.resolvedPath)) {
4849
- await rm6(resolved.resolvedPath, { recursive: true, force: true });
5445
+ if (resolve9(result.path) !== resolve9(resolved.resolvedPath)) {
5446
+ await rm7(resolved.resolvedPath, { recursive: true, force: true });
4850
5447
  await cp3(result.path, resolved.resolvedPath, { recursive: true, dereference: true });
4851
5448
  }
4852
5449
  if (result.tempRoot) {
4853
- await rm6(result.tempRoot, { recursive: true, force: true });
5450
+ await rm7(result.tempRoot, { recursive: true, force: true });
4854
5451
  }
4855
5452
  return {
4856
5453
  ...resolved,
@@ -4884,9 +5481,9 @@ var SkillKitSourceDriver = class {
4884
5481
  throw new Error("SkillKit translateSkill API unavailable");
4885
5482
  }
4886
5483
  for (const skill of this.discover(resolved.resolvedPath)) {
4887
- const skillMd = join21(skill.path, "SKILL.md");
5484
+ const skillMd = join24(skill.path, "SKILL.md");
4888
5485
  if (await pathExists(skillMd)) {
4889
- this.core.translateSkill(await readFile15(skillMd, "utf8"), "openclaw", { sourceFilename: "SKILL.md" });
5486
+ this.core.translateSkill(await readFile17(skillMd, "utf8"), "openclaw", { sourceFilename: "SKILL.md" });
4890
5487
  }
4891
5488
  }
4892
5489
  return resolved;
@@ -4914,9 +5511,9 @@ function normalizeProviderSource(spec) {
4914
5511
  if (spec.startsWith("git:https://github.com/")) return spec.slice("git:".length);
4915
5512
  return spec;
4916
5513
  }
4917
- function cachePathFor2(spec, cacheRoot) {
4918
- const root = cacheRoot ? resolve7(cacheRoot) : join21(homedir3(), ".agentwheel", "cache");
4919
- return join21(root, "skillkit", packageSlug(spec));
5514
+ function cachePathFor4(spec, cacheRoot) {
5515
+ const root = cacheRoot ? resolve9(cacheRoot) : join24(homedir3(), ".agentwheel", "cache");
5516
+ return join24(root, "skillkit", packageSlug(spec));
4920
5517
  }
4921
5518
  function packageSlug(spec) {
4922
5519
  return spec.replace(/^[a-z]+:\/\//i, "").replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "source";
@@ -4929,14 +5526,14 @@ function mapSeverity(severity) {
4929
5526
 
4930
5527
  // src/source/vercel-skills.ts
4931
5528
  import { stat as stat5 } from "fs/promises";
4932
- import { basename as basename12, join as join22, resolve as resolve8 } from "path";
5529
+ import { basename as basename15, join as join25, relative as relative5, resolve as resolve10 } from "path";
4933
5530
  var VercelSkillsSourceDriver = class {
4934
5531
  name = "vercel-skills";
4935
5532
  git = new GitSourceDriver();
4936
5533
  async resolve(source, options = {}) {
4937
5534
  const parsed = parseVercelSource(source);
4938
5535
  if (parsed.kind === "local") {
4939
- const resolvedPath = resolve8(parsed.path);
5536
+ const resolvedPath = resolve10(parsed.path);
4940
5537
  if (!await pathExists(resolvedPath) || !(await stat5(resolvedPath)).isDirectory()) {
4941
5538
  throw new Error(`Vercel skills local source not found: ${resolvedPath}`);
4942
5539
  }
@@ -4944,7 +5541,7 @@ var VercelSkillsSourceDriver = class {
4944
5541
  driver: this.name,
4945
5542
  source,
4946
5543
  resolvedPath,
4947
- packageName: `vercel/${basename12(resolvedPath)}`,
5544
+ packageName: `vercel/${basename15(resolvedPath)}`,
4948
5545
  mode: options.mode ?? "pinned",
4949
5546
  sourceHash: await hashPath(resolvedPath)
4950
5547
  };
@@ -4965,10 +5562,7 @@ var VercelSkillsSourceDriver = class {
4965
5562
  driver: "git",
4966
5563
  source: parsed.gitSource
4967
5564
  });
4968
- const resolvedPath = parsed.subpath ? join22(fetched.resolvedPath, parsed.subpath) : fetched.resolvedPath;
4969
- if (!await pathExists(resolvedPath)) {
4970
- throw new Error(`Vercel skills subpath not found: ${parsed.subpath}`);
4971
- }
5565
+ const resolvedPath = await resolveVercelSkillSubpath(fetched.resolvedPath, parsed.subpath);
4972
5566
  return {
4973
5567
  ...resolved,
4974
5568
  resolvedPath,
@@ -4993,6 +5587,15 @@ var VercelSkillsSourceDriver = class {
4993
5587
  return resolved;
4994
5588
  }
4995
5589
  };
5590
+ async function resolveVercelSkillSubpath(root, subpath) {
5591
+ if (!subpath) return root;
5592
+ const candidates = [join25(root, subpath), join25(root, "skills", subpath)];
5593
+ for (const candidate of candidates) {
5594
+ if (await pathExists(candidate)) return candidate;
5595
+ }
5596
+ const tried = candidates.map((candidate) => relative5(root, candidate)).join(", ");
5597
+ throw new Error(`Vercel skills subpath not found: ${subpath} (tried ${tried})`);
5598
+ }
4996
5599
  function parseVercelSource(source) {
4997
5600
  if (!source.startsWith("vercel:")) {
4998
5601
  throw new Error(`Invalid Vercel skills source: ${source}`);
@@ -5043,7 +5646,9 @@ var drivers = [
5043
5646
  new LocalSourceDriver(),
5044
5647
  new GitSourceDriver(),
5045
5648
  new SkillKitSourceDriver(),
5046
- new VercelSkillsSourceDriver()
5649
+ new VercelSkillsSourceDriver(),
5650
+ new McpRegistrySourceDriver(),
5651
+ new ClawHubSourceDriver()
5047
5652
  ];
5048
5653
  function getSourceDriver(name = "local") {
5049
5654
  const driver = drivers.find((candidate) => candidate.name === name);
@@ -5054,14 +5659,14 @@ function getSourceDriver(name = "local") {
5054
5659
  }
5055
5660
 
5056
5661
  // src/staging/staging.ts
5057
- import { chmod, cp as cp5, mkdir as mkdir12, mkdtemp as mkdtemp3, readdir as readdir5, stat as stat8 } from "fs/promises";
5058
- import { basename as basename15, dirname as dirname16, join as join25, relative as relative6, resolve as resolve10, sep as sep2 } from "path";
5662
+ import { chmod, cp as cp5, mkdir as mkdir16, mkdtemp as mkdtemp3, readdir as readdir5, stat as stat8 } from "fs/promises";
5663
+ import { basename as basename18, dirname as dirname21, join as join28, relative as relative7, resolve as resolve12, sep as sep2 } from "path";
5059
5664
  import { tmpdir as tmpdir4 } from "os";
5060
5665
 
5061
5666
  // src/compose/markdown.ts
5062
5667
  import { createHash as createHash5 } from "crypto";
5063
- import { readdir as readdir3, readFile as readFile16, stat as stat6, writeFile as writeFile12 } from "fs/promises";
5064
- import { basename as basename13, dirname as dirname14, extname as extname3, join as join23, relative as relative5, resolve as resolve9, sep } from "path";
5668
+ import { readdir as readdir3, readFile as readFile18, stat as stat6, writeFile as writeFile16 } from "fs/promises";
5669
+ import { basename as basename16, dirname as dirname19, extname as extname3, join as join26, relative as relative6, resolve as resolve11, sep } from "path";
5065
5670
  var includePattern = /<!--\s*openpack:include(\?)?\s+([^>]+?)\s*-->/g;
5066
5671
  var escapedIncludePattern = /<!--\s*openpack\\:include(\?)?\s+([^>]+?)\s*-->/g;
5067
5672
  var generatedPattern = /<!--\s*(?:BEGIN|END)\s+openpack:include\b/;
@@ -5076,7 +5681,7 @@ async function expandMarkdownIncludes(artifacts, packageRoot, options = {}) {
5076
5681
  const composedFrom = [];
5077
5682
  for (const file of files) {
5078
5683
  const result = await expandFile(file, packageRoot, composeEntriesForFile(artifact, file), artifactPaths, options);
5079
- if (result.changed) await writeFile12(file, result.content, "utf8");
5684
+ if (result.changed) await writeFile16(file, result.content, "utf8");
5080
5685
  composedFrom.push(...result.composedFrom);
5081
5686
  }
5082
5687
  const stagedPath = artifact.stagedPath ?? artifact.sourcePath;
@@ -5098,7 +5703,7 @@ async function validateMarkdownIncludes(artifacts, packageRoot, options = {}) {
5098
5703
  }
5099
5704
  }
5100
5705
  async function expandFile(file, packageRoot, appendEntries, artifactPaths, options) {
5101
- const raw = await readFile16(file, "utf8");
5706
+ const raw = await readFile18(file, "utf8");
5102
5707
  const owner = ownerSelector(packageRoot, file, options.nodeId);
5103
5708
  const expanded = await expandContent(raw, packageRoot, [owner], artifactPaths, options);
5104
5709
  let content = expanded.content;
@@ -5191,7 +5796,7 @@ async function expandInclude(selector, packageRoot, artifactPaths, options) {
5191
5796
  if (!stats.isFile()) {
5192
5797
  throw new Error(`OpenPack include is not a file: ${displaySelector}`);
5193
5798
  }
5194
- const raw = sourceContent ?? await readFile16(sourcePath, "utf8");
5799
+ const raw = sourceContent ?? await readFile18(sourcePath, "utf8");
5195
5800
  const { optional: _optional, markers: _markers, chain: _chain, ...childOptions } = options;
5196
5801
  const expanded = await expandContent(raw, includePackageRoot, [...options.chain, displaySelector], includeArtifactPaths, {
5197
5802
  ...childOptions,
@@ -5249,8 +5854,8 @@ function extractOpenPackIncludeSelectors(content) {
5249
5854
  return selectors;
5250
5855
  }
5251
5856
  function resolvePackageSelector(packageRoot, selector) {
5252
- const root = resolve9(packageRoot);
5253
- const resolved = resolve9(root, selector);
5857
+ const root = resolve11(packageRoot);
5858
+ const resolved = resolve11(root, selector);
5254
5859
  if (resolved !== root && !resolved.startsWith(`${root}${sep}`)) {
5255
5860
  throw new Error(`OpenPack include escapes package root: ${selector}`);
5256
5861
  }
@@ -5267,7 +5872,7 @@ async function listMarkdownFiles(root) {
5267
5872
  const out = [];
5268
5873
  async function walk2(dir) {
5269
5874
  for (const entry of (await readdir3(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
5270
- const full = join23(dir, entry.name);
5875
+ const full = join26(dir, entry.name);
5271
5876
  if (entry.isDirectory()) {
5272
5877
  await walk2(full);
5273
5878
  } else if (entry.isFile() && extname3(entry.name).toLowerCase() === ".md") {
@@ -5280,8 +5885,8 @@ async function listMarkdownFiles(root) {
5280
5885
  }
5281
5886
  function composeEntriesForFile(artifact, file) {
5282
5887
  if (!artifact.compose?.length) return [];
5283
- if (artifact.kind === "file") return [resolve9(artifact.stagedPath ?? artifact.sourcePath), resolve9(file)].every(Boolean) && resolve9(artifact.stagedPath ?? artifact.sourcePath) === resolve9(file) ? artifact.compose : [];
5284
- return basename13(file) === "SKILL.md" && dirname14(file) === resolve9(artifact.stagedPath ?? artifact.sourcePath) ? artifact.compose : [];
5888
+ if (artifact.kind === "file") return [resolve11(artifact.stagedPath ?? artifact.sourcePath), resolve11(file)].every(Boolean) && resolve11(artifact.stagedPath ?? artifact.sourcePath) === resolve11(file) ? artifact.compose : [];
5889
+ return basename16(file) === "SKILL.md" && dirname19(file) === resolve11(artifact.stagedPath ?? artifact.sourcePath) ? artifact.compose : [];
5285
5890
  }
5286
5891
  function orderedForExpansion(artifacts) {
5287
5892
  return [...artifacts].sort((a, b) => Number(a.type === "fragments") - Number(b.type === "fragments"));
@@ -5298,7 +5903,7 @@ function applyReplacements(content, replacements) {
5298
5903
  return out + content.slice(cursor);
5299
5904
  }
5300
5905
  function relativeSelector(root, file) {
5301
- return relative5(root, file).replaceAll("\\", "/");
5906
+ return relative6(root, file).replaceAll("\\", "/");
5302
5907
  }
5303
5908
  function ownerSelector(root, file, nodeId) {
5304
5909
  const selector = relativeSelector(root, file);
@@ -5327,8 +5932,8 @@ function artifactPathMap(artifacts) {
5327
5932
  }
5328
5933
 
5329
5934
  // src/staging/customize.ts
5330
- import { cp as cp4, mkdir as mkdir11, readdir as readdir4, readFile as readFile17, writeFile as writeFile13 } from "fs/promises";
5331
- import { dirname as dirname15, join as join24 } from "path";
5935
+ import { cp as cp4, mkdir as mkdir15, readdir as readdir4, readFile as readFile19, writeFile as writeFile17 } from "fs/promises";
5936
+ import { dirname as dirname20, join as join27 } from "path";
5332
5937
  async function applyCustomizations(artifacts, options) {
5333
5938
  let next = [...artifacts];
5334
5939
  next = await applyReplacements2(next, options, "override", installableArtifactTypes());
@@ -5344,16 +5949,16 @@ async function applyFragmentCustomizations(artifacts, options) {
5344
5949
  return next.sort((a, b) => `${a.type}:${a.name}:${a.channel}`.localeCompare(`${b.type}:${b.name}:${b.channel}`));
5345
5950
  }
5346
5951
  async function applyInstructionOverlay(artifacts, options) {
5347
- const overlayPath = join24(options.workspaceRoot, ".agentwheel", "overlays", options.adapter.name, "instructions.local.md");
5952
+ const overlayPath = join27(options.workspaceRoot, ".agentwheel", "overlays", options.adapter.name, "instructions.local.md");
5348
5953
  if (!await pathExists(overlayPath)) return artifacts;
5349
5954
  const index = artifacts.findIndex((artifact2) => artifact2.type === "instructions");
5350
5955
  if (index < 0) return artifacts;
5351
5956
  const artifact = artifacts[index];
5352
- const managed = await readFile17(artifact.stagedPath ?? artifact.sourcePath, "utf8");
5353
- const local = await readFile17(overlayPath, "utf8");
5354
- const composedPath = join24(options.stageRoot, ".agentwheel-composed", "instructions", "AGENTS.md");
5355
- await mkdir11(dirname15(composedPath), { recursive: true });
5356
- await writeFile13(
5957
+ const managed = await readFile19(artifact.stagedPath ?? artifact.sourcePath, "utf8");
5958
+ const local = await readFile19(overlayPath, "utf8");
5959
+ const composedPath = join27(options.stageRoot, ".agentwheel-composed", "instructions", "AGENTS.md");
5960
+ await mkdir15(dirname20(composedPath), { recursive: true });
5961
+ await writeFile17(
5357
5962
  composedPath,
5358
5963
  [
5359
5964
  "<!-- BEGIN agentwheel managed: upstream -->",
@@ -5379,19 +5984,19 @@ async function applyInstructionOverlay(artifacts, options) {
5379
5984
  return [...artifacts.slice(0, index), updated, ...artifacts.slice(index + 1)];
5380
5985
  }
5381
5986
  async function applyAdditions(artifacts, options) {
5382
- const additionsRoot = join24(options.workspaceRoot, ".agentwheel", "additions");
5383
- const rulesRoot = join24(additionsRoot, "rules");
5987
+ const additionsRoot = join27(options.workspaceRoot, ".agentwheel", "additions");
5988
+ const rulesRoot = join27(additionsRoot, "rules");
5384
5989
  if (!await pathExists(rulesRoot)) return artifacts;
5385
5990
  const additions = [];
5386
5991
  for (const entry of await sortedDirEntries2(rulesRoot)) {
5387
- const full = join24(rulesRoot, entry.name);
5992
+ const full = join27(rulesRoot, entry.name);
5388
5993
  if (!entry.isFile()) continue;
5389
5994
  additions.push({
5390
5995
  type: "rules",
5391
5996
  name: entry.name,
5392
5997
  sourcePath: full,
5393
5998
  stagedPath: full,
5394
- relativePath: join24("additions", "rules", entry.name),
5999
+ relativePath: join27("additions", "rules", entry.name),
5395
6000
  kind: "file",
5396
6001
  hash: await hashPath(full),
5397
6002
  packageName: options.packageName,
@@ -5415,17 +6020,17 @@ async function applyReplacements2(artifacts, options, channel, artifactTypes) {
5415
6020
  );
5416
6021
  }
5417
6022
  for (const type of artifactTypes) {
5418
- const typeRoot = join24(root, type);
6023
+ const typeRoot = join27(root, type);
5419
6024
  if (!await pathExists(typeRoot)) continue;
5420
6025
  for (const entry of await sortedDirEntries2(typeRoot)) {
5421
6026
  const artifactMapKey = `${type}:${entry.name}`;
5422
6027
  if (seen.has(artifactMapKey)) continue;
5423
6028
  seen.add(artifactMapKey);
5424
- const full = join24(typeRoot, entry.name);
6029
+ const full = join27(typeRoot, entry.name);
5425
6030
  const artifactKind = entry.isDirectory() ? "dir" : "file";
5426
6031
  const existing = byKey.get(artifactMapKey);
5427
- const stagedPath = join24(options.stageRoot, ".agentwheel-composed", channel, type, entry.name);
5428
- await mkdir11(dirname15(stagedPath), { recursive: true });
6032
+ const stagedPath = join27(options.stageRoot, ".agentwheel-composed", channel, type, entry.name);
6033
+ await mkdir15(dirname20(stagedPath), { recursive: true });
5429
6034
  await cp4(full, stagedPath, { recursive: artifactKind === "dir", dereference: true });
5430
6035
  byKey.set(artifactMapKey, {
5431
6036
  ...existing,
@@ -5433,7 +6038,7 @@ async function applyReplacements2(artifacts, options, channel, artifactTypes) {
5433
6038
  name: entry.name,
5434
6039
  sourcePath: full,
5435
6040
  stagedPath,
5436
- relativePath: existing?.relativePath ?? join24(type, entry.name),
6041
+ relativePath: existing?.relativePath ?? join27(type, entry.name),
5437
6042
  kind: artifactKind,
5438
6043
  hash: await hashPath(stagedPath),
5439
6044
  packageName,
@@ -5448,13 +6053,13 @@ function replacementRoots(options, channel) {
5448
6053
  const stateDir = channel === "override" ? "overrides" : "ejected";
5449
6054
  const roots = [];
5450
6055
  if (options.graphNodeId) {
5451
- roots.push({ root: join24(options.workspaceRoot, ".agentwheel", stateDir, ...options.graphNodeId.split("/")), kind: "node" });
6056
+ roots.push({ root: join27(options.workspaceRoot, ".agentwheel", stateDir, ...options.graphNodeId.split("/")), kind: "node" });
5452
6057
  }
5453
6058
  if (options.packageName && options.packageVersion) {
5454
- roots.push({ root: join24(options.workspaceRoot, ".agentwheel", stateDir, ...`${options.packageName}@${options.packageVersion}`.split("/")), kind: "version" });
6059
+ roots.push({ root: join27(options.workspaceRoot, ".agentwheel", stateDir, ...`${options.packageName}@${options.packageVersion}`.split("/")), kind: "version" });
5455
6060
  }
5456
6061
  if (options.packageName) {
5457
- roots.push({ root: join24(options.workspaceRoot, ".agentwheel", stateDir, ...options.packageName.split("/")), kind: "package" });
6062
+ roots.push({ root: join27(options.workspaceRoot, ".agentwheel", stateDir, ...options.packageName.split("/")), kind: "package" });
5458
6063
  }
5459
6064
  return roots;
5460
6065
  }
@@ -5481,15 +6086,15 @@ async function stageResolvedSourceRaw(driver, resolved) {
5481
6086
  return stageResolvedArtifactsRaw(resolved, artifacts);
5482
6087
  }
5483
6088
  async function stageResolvedArtifactsRaw(resolved, artifacts) {
5484
- const root = await mkdtemp3(join25(tmpdir4(), "agentwheel-stage-"));
6089
+ const root = await mkdtemp3(join28(tmpdir4(), "agentwheel-stage-"));
5485
6090
  const stagedArtifacts = [];
5486
6091
  for (const artifact of artifacts) {
5487
- const stagedPath = join25(root, artifact.relativePath);
5488
- await mkdir12(dirname16(stagedPath), { recursive: true });
6092
+ const stagedPath = join28(root, artifact.relativePath);
6093
+ await mkdir16(dirname21(stagedPath), { recursive: true });
5489
6094
  await cp5(artifact.sourcePath, stagedPath, {
5490
6095
  recursive: artifact.kind === "dir",
5491
6096
  dereference: true,
5492
- filter: (path) => !isIgnoredGeneratedEntry(basename15(path))
6097
+ filter: (path) => !isIgnoredGeneratedEntry(basename18(path))
5493
6098
  });
5494
6099
  await composeAssets(artifact, resolved.resolvedPath, stagedPath);
5495
6100
  stagedArtifacts.push({
@@ -5518,7 +6123,8 @@ async function renderStagedBundle(bundle, options = {}) {
5518
6123
  const runtimeSelectedSet = new Set(normalizeArtifactSelectors(options.select, options.skills) ?? []);
5519
6124
  const runtimeArtifacts = options.adapter ? filterArtifactsByRuntime(selectedArtifacts, options.adapter.name, runtimeSelectedSet) : selectedArtifacts;
5520
6125
  const codexRenderedArtifacts = await renderCodexSubagents(runtimeArtifacts, root, options.adapter);
5521
- const renderedArtifacts = await renderCopilotArtifacts(codexRenderedArtifacts, root, options.adapter);
6126
+ const openClawRenderedArtifacts = await renderOpenClawSubagents(codexRenderedArtifacts, root, options.adapter);
6127
+ const renderedArtifacts = await renderCopilotArtifacts(openClawRenderedArtifacts, root, options.adapter);
5522
6128
  const finalArtifacts = options.workspaceRoot && options.adapter ? await applyCustomizations(renderedArtifacts, {
5523
6129
  workspaceRoot: options.workspaceRoot,
5524
6130
  adapter: options.adapter,
@@ -5570,16 +6176,16 @@ async function composeAssets(artifact, packageRoot, stagedPath) {
5570
6176
  }
5571
6177
  for (const asset of artifact.assets) {
5572
6178
  const source = resolvePackagePath(packageRoot, asset.from);
5573
- const dest = join25(stagedPath, asset.into);
6179
+ const dest = join28(stagedPath, asset.into);
5574
6180
  await copyAsset(asset, source, dest);
5575
6181
  }
5576
6182
  }
5577
6183
  async function copyAsset(asset, source, dest) {
5578
6184
  const sourceStats = await stat8(source);
5579
6185
  if (sourceStats.isFile()) {
5580
- if (matchesAny(basename15(source), asset.include)) {
5581
- await mkdir12(dest, { recursive: true });
5582
- await copyAssetFile(source, join25(dest, basename15(source)), asset);
6186
+ if (matchesAny(basename18(source), asset.include)) {
6187
+ await mkdir16(dest, { recursive: true });
6188
+ await copyAssetFile(source, join28(dest, basename18(source)), asset);
5583
6189
  }
5584
6190
  return;
5585
6191
  }
@@ -5587,25 +6193,25 @@ async function copyAsset(asset, source, dest) {
5587
6193
  throw new Error(`Asset include source is not a file or directory: ${source}`);
5588
6194
  }
5589
6195
  if (!asset.include?.length) {
5590
- await mkdir12(dirname16(dest), { recursive: true });
6196
+ await mkdir16(dirname21(dest), { recursive: true });
5591
6197
  await cp5(source, dest, { recursive: true, dereference: true });
5592
6198
  if (asset.mode === "copy") await normalizeCopiedModes(dest);
5593
6199
  return;
5594
6200
  }
5595
6201
  for (const file of await listFiles(source)) {
5596
- const rel = relative6(source, file).replaceAll("\\", "/");
5597
- if (!matchesAny(rel, asset.include) && !matchesAny(basename15(file), asset.include)) continue;
5598
- await copyAssetFile(file, join25(dest, rel), asset);
6202
+ const rel = relative7(source, file).replaceAll("\\", "/");
6203
+ if (!matchesAny(rel, asset.include) && !matchesAny(basename18(file), asset.include)) continue;
6204
+ await copyAssetFile(file, join28(dest, rel), asset);
5599
6205
  }
5600
6206
  }
5601
6207
  async function copyAssetFile(source, dest, asset) {
5602
- await mkdir12(dirname16(dest), { recursive: true });
6208
+ await mkdir16(dirname21(dest), { recursive: true });
5603
6209
  await cp5(source, dest, { dereference: true });
5604
6210
  if (asset.mode === "copy") await chmod(dest, 420);
5605
6211
  }
5606
6212
  function resolvePackagePath(packageRoot, path) {
5607
- const resolved = resolve10(packageRoot, path);
5608
- const root = resolve10(packageRoot);
6213
+ const resolved = resolve12(packageRoot, path);
6214
+ const root = resolve12(packageRoot);
5609
6215
  if (resolved !== root && !resolved.startsWith(`${root}${sep2}`)) {
5610
6216
  throw new Error(`Asset include escapes package root: ${path}`);
5611
6217
  }
@@ -5615,7 +6221,7 @@ async function listFiles(root) {
5615
6221
  const out = [];
5616
6222
  async function walk2(dir) {
5617
6223
  for (const entry of (await readdir5(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
5618
- const full = join25(dir, entry.name);
6224
+ const full = join28(dir, entry.name);
5619
6225
  if (entry.isDirectory()) {
5620
6226
  await walk2(full);
5621
6227
  } else if (entry.isFile()) {
@@ -5634,7 +6240,7 @@ async function normalizeCopiedModes(path) {
5634
6240
  }
5635
6241
  if (!stats.isDirectory()) return;
5636
6242
  for (const entry of await readdir5(path, { withFileTypes: true })) {
5637
- await normalizeCopiedModes(join25(path, entry.name));
6243
+ await normalizeCopiedModes(join28(path, entry.name));
5638
6244
  }
5639
6245
  }
5640
6246
  function matchesAny(path, patterns) {
@@ -5647,14 +6253,14 @@ function matchesGlob(path, pattern) {
5647
6253
  }
5648
6254
 
5649
6255
  // src/model/workspace.ts
5650
- import { readFile as readFile18 } from "fs/promises";
6256
+ import { readFile as readFile20 } from "fs/promises";
5651
6257
  import { homedir as homedir4 } from "os";
5652
- import { dirname as dirname17, join as join26, resolve as resolve11 } from "path";
6258
+ import { dirname as dirname22, join as join29, resolve as resolve13 } from "path";
5653
6259
  import { z as z6 } from "zod";
5654
6260
  var workspacePackageSchema = z6.object({
5655
6261
  name: z6.string().min(1),
5656
6262
  source: z6.string().min(1),
5657
- driver: z6.enum(["local", "git", "skillkit", "vercel-skills"]).default("local"),
6263
+ driver: z6.enum(["local", "git", "skillkit", "vercel-skills", "mcp-registry", "clawhub"]).default("local"),
5658
6264
  adapter: z6.string().min(1).default("openclaw"),
5659
6265
  adapterConfig: z6.string().min(1).optional(),
5660
6266
  adapterModule: z6.string().min(1).optional(),
@@ -5664,6 +6270,8 @@ var workspacePackageSchema = z6.object({
5664
6270
  requestedRef: z6.string().min(1).optional(),
5665
6271
  select: z6.array(z6.string().min(1)).optional(),
5666
6272
  skills: z6.array(z6.string().min(1)).optional(),
6273
+ withSuggestions: z6.boolean().optional(),
6274
+ suggestions: z6.array(z6.string().min(1)).optional(),
5667
6275
  aliases: z6.record(z6.string(), z6.string().min(1)).optional(),
5668
6276
  overrides: z6.array(z6.string().min(1)).optional()
5669
6277
  });
@@ -5718,12 +6326,12 @@ var workspaceConfigSchema = z6.object({
5718
6326
  agents: z6.record(z6.string(), workspaceAgentSchema).default({})
5719
6327
  });
5720
6328
  function workspaceConfigPath(workspaceRoot) {
5721
- return join26(workspaceRoot, ".agentwheel", "config.json");
6329
+ return join29(workspaceRoot, ".agentwheel", "config.json");
5722
6330
  }
5723
6331
  async function readWorkspaceConfig(workspaceRoot) {
5724
6332
  const path = workspaceConfigPath(workspaceRoot);
5725
6333
  if (!await pathExists(path)) return emptyWorkspaceConfig();
5726
- return workspaceConfigSchema.parse(JSON.parse(await readFile18(path, "utf8")));
6334
+ return workspaceConfigSchema.parse(JSON.parse(await readFile20(path, "utf8")));
5727
6335
  }
5728
6336
  async function writeWorkspaceConfig(workspaceRoot, config) {
5729
6337
  await writeJsonAtomic(workspaceConfigPath(workspaceRoot), workspaceConfigSchema.parse(config));
@@ -5736,14 +6344,14 @@ function upsertPackage(config, entry) {
5736
6344
  return { schemaVersion: 1, packages, bootstrapSkills: parsed.bootstrapSkills, registry: parsed.registry ?? {}, trust: parsed.trust ?? {}, profiles: parsed.profiles ?? {}, agents: parsed.agents ?? {} };
5737
6345
  }
5738
6346
  function globalWorkspaceConfigPath(globalRoot = homedir4()) {
5739
- return join26(globalRoot, ".agentwheel", "config.json");
6347
+ return join29(globalRoot, ".agentwheel", "config.json");
5740
6348
  }
5741
6349
  async function findWorkspaceRoot(start = process.cwd()) {
5742
- let current = resolve11(start);
6350
+ let current = resolve13(start);
5743
6351
  while (true) {
5744
6352
  if (await pathExists(workspaceConfigPath(current))) return current;
5745
- const parent = dirname17(current);
5746
- if (parent === current) return resolve11(start);
6353
+ const parent = dirname22(current);
6354
+ if (parent === current) return resolve13(start);
5747
6355
  current = parent;
5748
6356
  }
5749
6357
  }
@@ -5769,16 +6377,16 @@ function mergeWorkspaceConfig(global, project) {
5769
6377
  });
5770
6378
  }
5771
6379
  function resolveConfigPath(path, baseRoot) {
5772
- if (path.startsWith("~/")) return resolve11(homedir4(), path.slice(2));
6380
+ if (path.startsWith("~/")) return resolve13(homedir4(), path.slice(2));
5773
6381
  if (path === "~") return homedir4();
5774
- return path.startsWith("/") ? resolve11(path) : resolve11(baseRoot, path);
6382
+ return path.startsWith("/") ? resolve13(path) : resolve13(baseRoot, path);
5775
6383
  }
5776
6384
  function emptyWorkspaceConfig() {
5777
6385
  return { schemaVersion: 1, packages: [], registry: {}, trust: {}, profiles: {}, agents: {} };
5778
6386
  }
5779
6387
  async function readConfigPath(path) {
5780
6388
  if (!await pathExists(path)) return emptyWorkspaceConfig();
5781
- return workspaceConfigSchema.parse(JSON.parse(await readFile18(path, "utf8")));
6389
+ return workspaceConfigSchema.parse(JSON.parse(await readFile20(path, "utf8")));
5782
6390
  }
5783
6391
  function mergeWorkspaceTrust(global, project) {
5784
6392
  return {
@@ -5793,23 +6401,23 @@ function sortedUnique2(values) {
5793
6401
  }
5794
6402
 
5795
6403
  // src/lifecycle/customization.ts
5796
- import { appendFile, cp as cp6, mkdir as mkdir13, rm as rm8 } from "fs/promises";
5797
- import { dirname as dirname19, join as join29 } from "path";
6404
+ import { appendFile, cp as cp6, mkdir as mkdir17, rm as rm9 } from "fs/promises";
6405
+ import { dirname as dirname24, join as join32 } from "path";
5798
6406
 
5799
6407
  // src/resolve/graph.ts
5800
6408
  import { createHash as createHash6 } from "crypto";
5801
- import { mkdtemp as mkdtemp4, readdir as readdir6, readFile as readFile20, stat as stat10 } from "fs/promises";
6409
+ import { mkdtemp as mkdtemp4, readdir as readdir6, readFile as readFile22, stat as stat10 } from "fs/promises";
5802
6410
  import { tmpdir as tmpdir5 } from "os";
5803
- import { basename as basename16, extname as extname4, join as join28 } from "path";
6411
+ import { basename as basename19, extname as extname4, join as join31 } from "path";
5804
6412
 
5805
6413
  // src/resolve/identity.ts
5806
6414
  import { homedir as homedir6 } from "os";
5807
- import { resolve as resolve13 } from "path";
6415
+ import { resolve as resolve15 } from "path";
5808
6416
 
5809
6417
  // src/registry/client.ts
5810
- import { readFile as readFile19, rm as rm7, stat as stat9 } from "fs/promises";
6418
+ import { readFile as readFile21, rm as rm8, stat as stat9 } from "fs/promises";
5811
6419
  import { homedir as homedir5 } from "os";
5812
- import { dirname as dirname18, join as join27, resolve as resolve12 } from "path";
6420
+ import { dirname as dirname23, join as join30, resolve as resolve14 } from "path";
5813
6421
  import { fileURLToPath } from "url";
5814
6422
 
5815
6423
  // src/model/registry.ts
@@ -5820,6 +6428,12 @@ var registryEntrySchema = z7.object({
5820
6428
  type: z7.enum(["package", "skill", "plugin", "mcp", "adapter"]).default("package"),
5821
6429
  description: z7.string().default(""),
5822
6430
  tags: z7.array(z7.string().min(1)).default([]),
6431
+ select: z7.array(z7.string().min(1)).optional(),
6432
+ skills: z7.array(z7.string().min(1)).optional(),
6433
+ homepageUrl: z7.string().min(1).optional(),
6434
+ homepageLinkLabel: z7.string().min(1).optional(),
6435
+ sourceUrl: z7.string().min(1).optional(),
6436
+ sourceLinkLabel: z7.string().min(1).optional(),
5823
6437
  openpack: z7.object({
5824
6438
  schemaVersion: z7.number().int().positive().optional(),
5825
6439
  specVersion: z7.string().min(1).optional()
@@ -5884,7 +6498,7 @@ var RegistryClient = class {
5884
6498
  );
5885
6499
  }
5886
6500
  async clearCache() {
5887
- await rm7(this.cachePath, { force: true });
6501
+ await rm8(this.cachePath, { force: true });
5888
6502
  }
5889
6503
  async getSources() {
5890
6504
  if (this.options.sources?.length) return this.options.sources;
@@ -5907,7 +6521,7 @@ var RegistryClient = class {
5907
6521
  }
5908
6522
  async readCache() {
5909
6523
  if (!await pathExists(this.cachePath)) return void 0;
5910
- return registryCacheSchema.parse(JSON.parse(await readFile19(this.cachePath, "utf8")));
6524
+ return registryCacheSchema.parse(JSON.parse(await readFile21(this.cachePath, "utf8")));
5911
6525
  }
5912
6526
  isExpired(cache, ttlMs) {
5913
6527
  return this.now().getTime() - new Date(cache.fetchedAt).getTime() > ttlMs;
@@ -5924,12 +6538,12 @@ var RegistryClient = class {
5924
6538
  }
5925
6539
  const filePath = source.startsWith("file:") ? fileURLToPath(source) : source;
5926
6540
  if (await pathExists(filePath)) {
5927
- const fullPath = resolve12(filePath);
6541
+ const fullPath = resolve14(filePath);
5928
6542
  const stats = await stat9(fullPath);
5929
- return readFile19(stats.isDirectory() ? join27(fullPath, "index.json") : fullPath, "utf8");
6543
+ return readFile21(stats.isDirectory() ? join30(fullPath, "index.json") : fullPath, "utf8");
5930
6544
  }
5931
- const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join27(dirname18(this.cachePath), "registry-repos") }));
5932
- return readFile19(join27(resolved.resolvedPath, "index.json"), "utf8");
6545
+ const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join30(dirname23(this.cachePath), "registry-repos") }));
6546
+ return readFile21(join30(resolved.resolvedPath, "index.json"), "utf8");
5933
6547
  }
5934
6548
  warnCompatibility(entries) {
5935
6549
  for (const entry of entries) {
@@ -5943,17 +6557,20 @@ var RegistryClient = class {
5943
6557
  }
5944
6558
  };
5945
6559
  async function resolvePackageSource(source, workspaceRoot, options = {}) {
5946
- const { isExplicitSource } = await import("./identify-TXIDGMNL.js");
6560
+ const { isExplicitSource } = await import("./identify-T4RE5RBD.js");
5947
6561
  if (await isExplicitSource(source)) return { source };
5948
6562
  const entry = await new RegistryClient({ workspaceRoot, offline: options.offline, warn: options.warn }).resolve(source);
5949
6563
  if (!entry) {
5950
6564
  if (options.offline) {
5951
6565
  throw new Error(`Offline cannot refresh registry indexes (entry not found in cache: ${source}). Run without --offline first.`);
5952
6566
  }
5953
- throw new Error(`Registry entry not found: ${source}. Use an explicit path/git/skillkit/vercel source to bypass the registry.`);
6567
+ throw new Error(`Registry entry not found: ${source}. Use an explicit path/git/skillkit/vercel/mcp-registry/clawhub source to bypass the registry.`);
5954
6568
  }
5955
6569
  return { source: entry.source, registryEntry: entry };
5956
6570
  }
6571
+ function selectorsFromRegistryEntry(entry) {
6572
+ return normalizeArtifactSelectors(entry?.select, entry?.skills);
6573
+ }
5957
6574
  function mergeIndexes(indexes) {
5958
6575
  const merged = /* @__PURE__ */ new Map();
5959
6576
  for (const index of indexes) {
@@ -5964,7 +6581,7 @@ function mergeIndexes(indexes) {
5964
6581
  return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
5965
6582
  }
5966
6583
  function defaultRegistryCachePath() {
5967
- return join27(homedir5(), ".agentwheel", "registry-cache.json");
6584
+ return join30(homedir5(), ".agentwheel", "registry-cache.json");
5968
6585
  }
5969
6586
  function sameSources(a, b) {
5970
6587
  return a.length === b.length && a.every((source, index) => source === b[index]);
@@ -6021,7 +6638,23 @@ async function normalizeDependencySource(source, options) {
6021
6638
  driver: "vercel-skills"
6022
6639
  };
6023
6640
  }
6024
- throw new Error(`Unsupported dependency source: ${source}. Use registry:<name>, ./, ../, local:, github:, git:, skillkit:, or vercel:.`);
6641
+ if (trimmed.startsWith("mcp-registry:")) {
6642
+ const spec = normalizeLiteralProviderSpec(trimmed, "mcp-registry:");
6643
+ return {
6644
+ source: spec,
6645
+ normalizedSource: spec,
6646
+ driver: "mcp-registry"
6647
+ };
6648
+ }
6649
+ if (trimmed.startsWith("clawhub:")) {
6650
+ const spec = normalizeLiteralProviderSpec(trimmed, "clawhub:");
6651
+ return {
6652
+ source: spec,
6653
+ normalizedSource: spec,
6654
+ driver: "clawhub"
6655
+ };
6656
+ }
6657
+ throw new Error(`Unsupported dependency source: ${source}. Use registry:<name>, ./, ../, local:, github:, git:, skillkit:, vercel:, mcp-registry:, or clawhub:.`);
6025
6658
  }
6026
6659
  function isBareRegistryName(source) {
6027
6660
  return !source.includes(":") && !isLocalSource(source);
@@ -6034,16 +6667,16 @@ function localSourcePath(source) {
6034
6667
  }
6035
6668
  function resolveLocalPath(path, declaringPackageRoot) {
6036
6669
  if (path === "~") return homedir6();
6037
- if (path.startsWith("~/")) return resolve13(homedir6(), path.slice(2));
6038
- if (path.startsWith("/")) return resolve13(path);
6039
- return resolve13(declaringPackageRoot, path);
6670
+ if (path.startsWith("~/")) return resolve15(homedir6(), path.slice(2));
6671
+ if (path.startsWith("/")) return resolve15(path);
6672
+ return resolve15(declaringPackageRoot, path);
6040
6673
  }
6041
6674
  async function normalizeRegistrySource(name, options) {
6042
6675
  const client = options.registryClient ?? new RegistryClient({ workspaceRoot: options.workspaceRoot });
6043
6676
  const entry = await client.resolve(name);
6044
6677
  if (!entry) {
6045
6678
  throw new Error(
6046
- `Registry entry not found: ${name}. Bare dependency names are registry-only inside package manifests; use ./, ../, local:, github:, git:, skillkit:, or vercel: for explicit sources.`
6679
+ `Registry entry not found: ${name}. Bare dependency names are registry-only inside package manifests; use ./, ../, local:, github:, git:, skillkit:, vercel:, mcp-registry:, or clawhub: for explicit sources.`
6047
6680
  );
6048
6681
  }
6049
6682
  const resolved = await normalizeDependencySource(entry.source, {
@@ -6102,6 +6735,11 @@ function normalizeProviderSpec(spec, declaringPackageRoot) {
6102
6735
  }
6103
6736
  return spec.trim();
6104
6737
  }
6738
+ function normalizeLiteralProviderSpec(source, prefix) {
6739
+ const spec = source.slice(prefix.length).trim();
6740
+ if (!spec) throw new Error(`Provider dependency source must include a spec: ${prefix}<name>`);
6741
+ return `${prefix}${spec}`;
6742
+ }
6105
6743
 
6106
6744
  // src/resolve/semver.ts
6107
6745
  var semverPattern = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
@@ -6193,7 +6831,7 @@ function compareSemver(a, b) {
6193
6831
  var cacheLocks = /* @__PURE__ */ new Map();
6194
6832
  async function resolveDependencyGraph(roots, options) {
6195
6833
  if (roots.length === 0) throw new Error("At least one graph root is required.");
6196
- const graphRoot = await mkdtemp4(join28(tmpdir5(), "agentwheel-graph-"));
6834
+ const graphRoot = await mkdtemp4(join31(tmpdir5(), "agentwheel-graph-"));
6197
6835
  const fetchCache = /* @__PURE__ */ new Map();
6198
6836
  const nodesByKey = /* @__PURE__ */ new Map();
6199
6837
  const rootResults = [];
@@ -6213,7 +6851,9 @@ async function resolveDependencyGraph(roots, options) {
6213
6851
  useLock: root.useLock ?? options.lockedResolution,
6214
6852
  depth: 0,
6215
6853
  optional: false,
6216
- chain: [`workspace:${rootId}`]
6854
+ chain: [`workspace:${rootId}`],
6855
+ includeSuggestions: root.includeSuggestions ?? options.includeSuggestions,
6856
+ suggestionAliases: sortedUnique3([...root.suggestionAliases ?? [], ...options.suggestionAliases ?? []])
6217
6857
  };
6218
6858
  });
6219
6859
  let iterations = 0;
@@ -6341,13 +6981,18 @@ Run without ${lockLabel === "Offline" ? "--offline" : "--frozen-lock"} first.`
6341
6981
  selectionReasons: /* @__PURE__ */ new Map(),
6342
6982
  processedNeeds: /* @__PURE__ */ new Set(),
6343
6983
  processedPackageAliases: /* @__PURE__ */ new Set(),
6984
+ processedSuggestions: /* @__PURE__ */ new Set(),
6344
6985
  depth: requirement.depth,
6345
- fullPackageSelected: false
6986
+ fullPackageSelected: false,
6987
+ includeSuggestions: false,
6988
+ suggestionAliases: /* @__PURE__ */ new Set()
6346
6989
  };
6347
6990
  nodesByKey.set(nodeKey, state);
6348
6991
  }
6349
6992
  state.depth = Math.min(state.depth, requirement.depth);
6350
6993
  state.fullPackageSelected = state.fullPackageSelected || requirement.select === void 0;
6994
+ state.includeSuggestions = state.includeSuggestions || requirement.includeSuggestions === true;
6995
+ for (const alias of requirement.suggestionAliases ?? []) state.suggestionAliases.add(alias);
6351
6996
  state.requiredBy.add(requirement.requiredBy);
6352
6997
  for (const selector of selected) addSelectedSelector(state, selector, requirement.selectionReason);
6353
6998
  refreshNode(state);
@@ -6394,10 +7039,13 @@ Dependency chain: ${requirement.chain.join(" -> ")}`);
6394
7039
  async function collectDependencyNeeds(state, fetched, options, chain) {
6395
7040
  if (fetched.manifest?.schemaVersion !== 2) return [];
6396
7041
  const dependencies = fetched.manifest.requires ?? {};
7042
+ const suggestions = fetched.manifest.suggests ?? {};
6397
7043
  const dependencyEntries = Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b));
7044
+ const suggestionEntries = Object.entries(suggestions).sort(([a], [b]) => a.localeCompare(b));
7045
+ const suggestionOptions = suggestionOptionsForState(state, options);
6398
7046
  const requirements = [];
6399
7047
  if (options.noDeps) {
6400
- warnNoDepsOnce(state, dependencyEntries.map(([alias]) => alias), options.warn);
7048
+ warnNoDepsOnce(state, [...dependencyEntries.map(([alias]) => alias), ...suggestionEntries.map(([alias]) => alias)], options.warn);
6401
7049
  } else {
6402
7050
  for (const [alias, dependency] of dependencyEntries) {
6403
7051
  if (state.processedPackageAliases.has(alias)) continue;
@@ -6406,6 +7054,14 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
6406
7054
  if (!dependencyTargetsRuntime(dependency.runtimes, options.runtime, state.node.id, alias, options.warn)) continue;
6407
7055
  requirements.push(dependencyRequirement(state, fetched, alias, dependency, dependency.select, chain, options.lockedResolution === true));
6408
7056
  }
7057
+ for (const [alias, suggestion] of suggestionEntries) {
7058
+ if (state.processedSuggestions.has(alias)) continue;
7059
+ if (!shouldIncludeSuggestionAlias(alias, suggestionOptions, state.fullPackageSelected)) continue;
7060
+ if (!suggestion.select?.length && !(state.fullPackageSelected && suggestion.select === void 0) && !explicitSuggestionAlias(alias, suggestionOptions)) continue;
7061
+ state.processedSuggestions.add(alias);
7062
+ if (!dependencyTargetsRuntime(suggestion.runtimes, options.runtime, state.node.id, alias, options.warn)) continue;
7063
+ requirements.push(suggestionRequirement(state, fetched, alias, suggestion, suggestion.select, chain, suggestionOptions));
7064
+ }
6409
7065
  }
6410
7066
  const artifactsBySelector = new Map(fetched.artifacts.map((artifact) => [artifactSelectorKey(artifact), artifact]));
6411
7067
  const artifactsByRelativePath = new Map(fetched.artifacts.map((artifact) => [artifact.relativePath.replaceAll("\\", "/"), artifact]));
@@ -6449,6 +7105,28 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
6449
7105
  }
6450
7106
  addSelectedSelector(state, parsed.selector, `required by ${parentSelector}`);
6451
7107
  }
7108
+ for (const suggestion of artifact.suggests ?? []) {
7109
+ const parsed = parseArtifactSuggestion(suggestion);
7110
+ if (!shouldIncludeSuggestionAlias(parsed.alias, suggestionOptions, true)) continue;
7111
+ if (!requirementTargetsRuntime(parsed.runtimes, options.runtime, `${state.node.id}:${parentSelector} -> ${parsed.raw}`, options.warn)) continue;
7112
+ if (options.noDeps) {
7113
+ warnNoDepsOnce(state, [parsed.alias], options.warn);
7114
+ continue;
7115
+ }
7116
+ const packageSuggestion = suggestionForAlias(suggestions, state.node.id, parsed.alias);
7117
+ if (!dependencyTargetsRuntime(packageSuggestion.runtimes, options.runtime, state.node.id, parsed.alias, options.warn)) continue;
7118
+ requirements.push(suggestionRequirement(
7119
+ state,
7120
+ fetched,
7121
+ parsed.alias,
7122
+ packageSuggestion,
7123
+ combinedSelectors(packageSuggestion.select, parsed.select),
7124
+ chain,
7125
+ suggestionOptions,
7126
+ parsed.optional || shouldTreatSuggestionAsOptional(parsed.alias, packageSuggestion, suggestionOptions),
7127
+ `suggested by ${parentSelector}`
7128
+ ));
7129
+ }
6452
7130
  for (const include of await collectIncludeNeeds(artifact, artifactsByRelativePath)) {
6453
7131
  if (!include.alias) continue;
6454
7132
  if (options.noDeps) {
@@ -6490,9 +7168,24 @@ function dependencyRequirement(state, fetched, alias, dependency, select, chain,
6490
7168
  chain: [...chain, `${state.node.id}:${alias}`],
6491
7169
  version: dependency.version,
6492
7170
  integrity: dependency.integrity,
6493
- selectionReason
7171
+ selectionReason,
7172
+ includeSuggestions: state.includeSuggestions,
7173
+ suggestionAliases: sortedUnique3([...state.suggestionAliases])
6494
7174
  };
6495
7175
  }
7176
+ function suggestionRequirement(state, fetched, alias, suggestion, select, chain, options, optional = shouldTreatSuggestionAsOptional(alias, suggestion, options), selectionReason) {
7177
+ return dependencyRequirement(
7178
+ state,
7179
+ fetched,
7180
+ alias,
7181
+ suggestion,
7182
+ select,
7183
+ chain,
7184
+ options.lockedResolution === true,
7185
+ optional,
7186
+ selectionReason
7187
+ );
7188
+ }
6496
7189
  function dependencyForAlias(dependencies, nodeId, alias) {
6497
7190
  const dependency = dependencies[alias];
6498
7191
  if (!dependency) {
@@ -6500,6 +7193,13 @@ function dependencyForAlias(dependencies, nodeId, alias) {
6500
7193
  }
6501
7194
  return dependency;
6502
7195
  }
7196
+ function suggestionForAlias(suggestions, nodeId, alias) {
7197
+ const suggestion = suggestions[alias];
7198
+ if (!suggestion) {
7199
+ throw new Error(`Suggestion alias not found in ${nodeId}: ${alias}`);
7200
+ }
7201
+ return suggestion;
7202
+ }
6503
7203
  function warnNoDepsOnce(state, aliases, warn) {
6504
7204
  const unique = sortedUnique3(aliases.filter(Boolean));
6505
7205
  if (unique.length === 0 || state.processedPackageAliases.has("__noDepsWarned")) return;
@@ -6517,6 +7217,45 @@ function parseArtifactRequirement(requirement) {
6517
7217
  runtimes: typeof requirement === "string" ? void 0 : requirement.runtimes
6518
7218
  };
6519
7219
  }
7220
+ function parseArtifactSuggestion(suggestion) {
7221
+ if (typeof suggestion === "string") {
7222
+ return {
7223
+ raw: suggestion,
7224
+ alias: suggestion,
7225
+ optional: false
7226
+ };
7227
+ }
7228
+ return {
7229
+ raw: suggestion.alias,
7230
+ alias: suggestion.alias,
7231
+ select: suggestion.select,
7232
+ optional: suggestion.optional === true,
7233
+ runtimes: suggestion.runtimes
7234
+ };
7235
+ }
7236
+ function shouldIncludeSuggestionAlias(alias, options, includeWhenAllSuggestions) {
7237
+ const aliases = new Set(options.suggestionAliases ?? []);
7238
+ if (aliases.has(alias)) return true;
7239
+ return options.includeSuggestions === true && includeWhenAllSuggestions;
7240
+ }
7241
+ function suggestionOptionsForState(state, options) {
7242
+ return {
7243
+ ...options,
7244
+ includeSuggestions: state.includeSuggestions || options.includeSuggestions === true,
7245
+ suggestionAliases: sortedUnique3([...options.suggestionAliases ?? [], ...state.suggestionAliases])
7246
+ };
7247
+ }
7248
+ function explicitSuggestionAlias(alias, options) {
7249
+ return (options.suggestionAliases ?? []).includes(alias);
7250
+ }
7251
+ function shouldTreatSuggestionAsOptional(alias, suggestion, options) {
7252
+ if (suggestion.optional === true) return true;
7253
+ return options.includeSuggestions === true && !(options.suggestionAliases ?? []).includes(alias);
7254
+ }
7255
+ function combinedSelectors(base, extra) {
7256
+ const values = [...base ?? [], ...extra ?? []];
7257
+ return values.length > 0 ? sortedUnique3(values) : void 0;
7258
+ }
6520
7259
  function parseDependencySelector(value) {
6521
7260
  const cleaned = value.trim();
6522
7261
  const slash = cleaned.indexOf("/");
@@ -6555,7 +7294,7 @@ async function collectIncludeNeeds(artifact, artifactsByRelativePath) {
6555
7294
  const file = stack.shift();
6556
7295
  if (scanned.has(file)) continue;
6557
7296
  scanned.add(file);
6558
- const content = await readFile20(file, "utf8");
7297
+ const content = await readFile22(file, "utf8");
6559
7298
  for (const include of extractOpenPackIncludeSelectors(content)) {
6560
7299
  await collectIncludeSelector(include.raw, include.optional, artifactsByRelativePath, scanned, stack, needs);
6561
7300
  }
@@ -6598,7 +7337,7 @@ async function listMarkdownFiles2(root) {
6598
7337
  const out = [];
6599
7338
  async function walk2(dir) {
6600
7339
  for (const entry of (await readdir6(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
6601
- const full = join28(dir, entry.name);
7340
+ const full = join31(dir, entry.name);
6602
7341
  if (entry.isDirectory()) {
6603
7342
  await walk2(full);
6604
7343
  } else if (entry.isFile() && extname4(entry.name).toLowerCase() === ".md") {
@@ -6633,7 +7372,7 @@ async function fetchPackage(normalized, mode, options, fetchCache, refOverride)
6633
7372
  const promise = (async () => {
6634
7373
  const driver = getSourceDriver(normalized.driver);
6635
7374
  const resolved = await driver.resolve(normalized.source, {
6636
- cacheRoot: options.cacheRoot ?? join28(options.workspaceRoot, ".agentwheel", "cache"),
7375
+ cacheRoot: options.cacheRoot ?? join31(options.workspaceRoot, ".agentwheel", "cache"),
6637
7376
  mode,
6638
7377
  ref: refOverride ?? normalized.requestedRef,
6639
7378
  frozenLock: hardLockedCheckout
@@ -6643,7 +7382,7 @@ async function fetchPackage(normalized, mode, options, fetchCache, refOverride)
6643
7382
  const exported = await driver.export(translated);
6644
7383
  const manifest = await readPackageManifest(exported.resolvedPath);
6645
7384
  const artifacts = await driver.list(exported);
6646
- const name = manifest?.name ?? exported.packageName ?? basename16(exported.resolvedPath);
7385
+ const name = manifest?.name ?? exported.packageName ?? basename19(exported.resolvedPath);
6647
7386
  const version = manifest?.version ?? exported.packageVersion ?? "0.0.0";
6648
7387
  const sourceHash = exported.sourceHash ?? await hashPath(exported.resolvedPath);
6649
7388
  return {
@@ -6727,7 +7466,7 @@ function shouldCheckLockedRootSource(requirement) {
6727
7466
  }
6728
7467
  function isExplicitNonRegistrySource(source) {
6729
7468
  const trimmed = source.trim();
6730
- return trimmed === "~" || trimmed.startsWith("~/") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("/") || trimmed.startsWith("local:") || trimmed.startsWith("github:") || trimmed.startsWith("git:") || trimmed.startsWith("skillkit:") || trimmed.startsWith("vercel:");
7469
+ return trimmed === "~" || trimmed.startsWith("~/") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("/") || trimmed.startsWith("local:") || trimmed.startsWith("github:") || trimmed.startsWith("git:") || trimmed.startsWith("skillkit:") || trimmed.startsWith("vercel:") || trimmed.startsWith("mcp-registry:") || trimmed.startsWith("clawhub:");
6731
7470
  }
6732
7471
  function lockedRootSourceDrifted(declared, locked) {
6733
7472
  return declared.normalizedSource !== locked.normalizedSource || declared.requestedRef !== locked.requestedRef;
@@ -6750,8 +7489,8 @@ function verifyIntegrity(integrity, sourceHash, label) {
6750
7489
  async function withCachePathLock(path, fn) {
6751
7490
  const previous = cacheLocks.get(path) ?? Promise.resolve();
6752
7491
  let release = () => void 0;
6753
- const current = previous.then(() => new Promise((resolve19) => {
6754
- release = resolve19;
7492
+ const current = previous.then(() => new Promise((resolve21) => {
7493
+ release = resolve21;
6755
7494
  }));
6756
7495
  cacheLocks.set(path, current);
6757
7496
  await previous;
@@ -6840,8 +7579,8 @@ async function mapLimit(items, limit, fn) {
6840
7579
 
6841
7580
  // src/lifecycle/customization.ts
6842
7581
  async function remember(workspaceRoot, runtime, text) {
6843
- const overlayPath = join29(workspaceRoot, ".agentwheel", "overlays", runtime, "instructions.local.md");
6844
- await mkdir13(dirname19(overlayPath), { recursive: true });
7582
+ const overlayPath = join32(workspaceRoot, ".agentwheel", "overlays", runtime, "instructions.local.md");
7583
+ await mkdir17(dirname24(overlayPath), { recursive: true });
6845
7584
  await appendFile(overlayPath, `${text.trim()}
6846
7585
  `, "utf8");
6847
7586
  return { overlayPath };
@@ -6864,9 +7603,9 @@ async function ejectArtifact(workspaceRoot, item) {
6864
7603
  throw new Error(`Artifact not found: ${item}`);
6865
7604
  }
6866
7605
  const ejectedIdentity = parsed.packageIdentity === parsed.packageName ? parsed.packageIdentity : candidate.nodeId === parsed.packageIdentity ? candidate.nodeId : `${candidate.packageName}@${candidate.packageVersion}`;
6867
- const ejectedPath = join29(workspaceRoot, ".agentwheel", "ejected", ...ejectedIdentity.split("/"), parsed.type, parsed.name);
6868
- await mkdir13(dirname19(ejectedPath), { recursive: true });
6869
- await rm8(ejectedPath, { recursive: true, force: true });
7606
+ const ejectedPath = join32(workspaceRoot, ".agentwheel", "ejected", ...ejectedIdentity.split("/"), parsed.type, parsed.name);
7607
+ await mkdir17(dirname24(ejectedPath), { recursive: true });
7608
+ await rm9(ejectedPath, { recursive: true, force: true });
6870
7609
  await cp6(artifact.stagedPath ?? artifact.sourcePath, ejectedPath, { recursive: artifact.kind === "dir", dereference: true });
6871
7610
  return {
6872
7611
  ...parsed,
@@ -6877,7 +7616,7 @@ async function ejectArtifact(workspaceRoot, item) {
6877
7616
  ejectedPath
6878
7617
  };
6879
7618
  } finally {
6880
- await Promise.all(candidates.map((candidate) => rm8(candidate.bundle.root, { recursive: true, force: true })));
7619
+ await Promise.all(candidates.map((candidate) => rm9(candidate.bundle.root, { recursive: true, force: true })));
6881
7620
  }
6882
7621
  }
6883
7622
  function parseEjectItem(item) {
@@ -6907,7 +7646,7 @@ async function stageEjectCandidate(workspaceRoot, pkg) {
6907
7646
  const adapter = pkg.adapterConfig ? await loadAdapterConfig(pkg.adapterConfig) : getAdapter(pkg.adapter);
6908
7647
  const bundle = await stageSource(driver, normalized.source, {
6909
7648
  adapter,
6910
- cacheRoot: join29(workspaceRoot, ".agentwheel", "cache"),
7649
+ cacheRoot: join32(workspaceRoot, ".agentwheel", "cache"),
6911
7650
  mode: pkg.mode,
6912
7651
  ref: normalized.requestedRef ?? pkg.requestedRef
6913
7652
  });
@@ -6954,12 +7693,12 @@ function ejectCommands(candidates, parsed) {
6954
7693
  }
6955
7694
 
6956
7695
  // src/lifecycle/profile.ts
6957
- import { rm as rm9 } from "fs/promises";
7696
+ import { rm as rm10 } from "fs/promises";
6958
7697
 
6959
7698
  // src/lifecycle/source-plan.ts
6960
7699
  import { createHash as createHash8 } from "crypto";
6961
- import { mkdir as mkdir15 } from "fs/promises";
6962
- import { dirname as dirname21, join as join32, resolve as resolve14 } from "path";
7700
+ import { mkdir as mkdir19 } from "fs/promises";
7701
+ import { dirname as dirname26, join as join35, resolve as resolve16 } from "path";
6963
7702
 
6964
7703
  // src/resolve/graph-diff.ts
6965
7704
  function diffGraphLocks(previous, next) {
@@ -7081,11 +7820,11 @@ function short(hash) {
7081
7820
 
7082
7821
  // src/resolve/render.ts
7083
7822
  import { createHash as createHash7 } from "crypto";
7084
- import { readFile as readFile21, mkdtemp as mkdtemp5 } from "fs/promises";
7823
+ import { readFile as readFile23, mkdtemp as mkdtemp5 } from "fs/promises";
7085
7824
  import { tmpdir as tmpdir6 } from "os";
7086
- import { join as join30 } from "path";
7825
+ import { join as join33 } from "path";
7087
7826
  async function renderGraphForTarget(graph, targetContext = {}) {
7088
- const root = await mkdtemp5(join30(tmpdir6(), "agentwheel-render-"));
7827
+ const root = await mkdtemp5(join33(tmpdir6(), "agentwheel-render-"));
7089
7828
  const artifacts = [];
7090
7829
  const stagedNodes = /* @__PURE__ */ new Map();
7091
7830
  const includeEdges = /* @__PURE__ */ new Map();
@@ -7158,7 +7897,8 @@ async function renderGraphForTarget(graph, targetContext = {}) {
7158
7897
  const runtimeSelectedSet = new Set(normalizeArtifactSelectors(rawNode.node.selected) ?? []);
7159
7898
  const runtimeArtifacts = targetContext.adapter ? filterArtifactsByRuntime2(selectedArtifacts, targetContext.adapter.name, runtimeSelectedSet) : selectedArtifacts;
7160
7899
  const codexRenderedArtifacts = await renderCodexSubagents(runtimeArtifacts, staged.root, targetContext.adapter);
7161
- const runtimeRenderedArtifacts = await renderCopilotArtifacts(codexRenderedArtifacts, staged.root, targetContext.adapter);
7900
+ const openClawRenderedArtifacts = await renderOpenClawSubagents(codexRenderedArtifacts, staged.root, targetContext.adapter);
7901
+ const runtimeRenderedArtifacts = await renderCopilotArtifacts(openClawRenderedArtifacts, staged.root, targetContext.adapter);
7162
7902
  const renderedArtifacts = targetContext.workspaceRoot && targetContext.adapter ? await applyCustomizations(runtimeRenderedArtifacts, {
7163
7903
  workspaceRoot: targetContext.workspaceRoot,
7164
7904
  adapter: targetContext.adapter,
@@ -7205,7 +7945,7 @@ async function artifactContentMap(artifacts) {
7205
7945
  const out = /* @__PURE__ */ new Map();
7206
7946
  for (const artifact of artifacts) {
7207
7947
  if (artifact.kind !== "file") continue;
7208
- out.set(artifact.relativePath.replaceAll("\\", "/"), await readFile21(artifact.stagedPath ?? artifact.sourcePath, "utf8"));
7948
+ out.set(artifact.relativePath.replaceAll("\\", "/"), await readFile23(artifact.stagedPath ?? artifact.sourcePath, "utf8"));
7209
7949
  }
7210
7950
  return out;
7211
7951
  }
@@ -7468,9 +8208,9 @@ function lockArtifactFor(artifact) {
7468
8208
  }
7469
8209
 
7470
8210
  // src/lifecycle/trust.ts
7471
- import { mkdir as mkdir14, readFile as readFile22 } from "fs/promises";
8211
+ import { mkdir as mkdir18, readFile as readFile24 } from "fs/promises";
7472
8212
  import { homedir as homedir7 } from "os";
7473
- import { dirname as dirname20, join as join31 } from "path";
8213
+ import { dirname as dirname25, join as join34 } from "path";
7474
8214
  import { z as z8 } from "zod";
7475
8215
  var trustStoreSchema = z8.object({
7476
8216
  version: z8.literal(1),
@@ -7544,14 +8284,14 @@ function sortedUnique4(values) {
7544
8284
  }
7545
8285
  async function readTrustStore(path) {
7546
8286
  if (!await pathExists(path)) return { version: 1, acceptedSources: [] };
7547
- return trustStoreSchema.parse(JSON.parse(await readFile22(path, "utf8")));
8287
+ return trustStoreSchema.parse(JSON.parse(await readFile24(path, "utf8")));
7548
8288
  }
7549
8289
  async function writeTrustStore(path, store) {
7550
- await mkdir14(dirname20(path), { recursive: true });
8290
+ await mkdir18(dirname25(path), { recursive: true });
7551
8291
  await writeJsonAtomic(path, trustStoreSchema.parse(store));
7552
8292
  }
7553
8293
  function defaultTrustStorePath() {
7554
- return process.env.AGENTWHEEL_TRUST_STORE ?? join31(homedir7(), ".agentwheel", "trust.json");
8294
+ return process.env.AGENTWHEEL_TRUST_STORE ?? join34(homedir7(), ".agentwheel", "trust.json");
7555
8295
  }
7556
8296
 
7557
8297
  // src/lifecycle/source-plan.ts
@@ -7589,9 +8329,11 @@ async function createGraphSourcePlan(options) {
7589
8329
  const registryClient = new RegistryClient({ workspaceRoot, offline: lockMode, offlineLabel: lockLabel, warn });
7590
8330
  const graph = await resolveDependencyGraph(options.roots, {
7591
8331
  workspaceRoot,
7592
- cacheRoot: join32(workspaceRoot, ".agentwheel", "cache"),
8332
+ cacheRoot: join35(workspaceRoot, ".agentwheel", "cache"),
7593
8333
  registryClient,
7594
8334
  noDeps: options.noDeps,
8335
+ includeSuggestions: options.includeSuggestions,
8336
+ suggestionAliases: options.suggestionAliases,
7595
8337
  lockedResolution: options.lockedResolution,
7596
8338
  frozenLock: lockMode,
7597
8339
  offline: options.offline,
@@ -7687,7 +8429,7 @@ async function readExistingGraphLock(path) {
7687
8429
  return readGraphLock(path);
7688
8430
  }
7689
8431
  function pathForGraphLock(workspaceRoot, targetKey, adapter, targetFingerprint) {
7690
- return join32(workspaceRoot, ".agentwheel", "locks", sanitizePathSegment(targetKey), sanitizePathSegment(adapter), `${targetFingerprint}.graph-lock.json`);
8432
+ return join35(workspaceRoot, ".agentwheel", "locks", sanitizePathSegment(targetKey), sanitizePathSegment(adapter), `${targetFingerprint}.graph-lock.json`);
7691
8433
  }
7692
8434
  function sanitizePathSegment(value) {
7693
8435
  return value.replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "default";
@@ -7696,7 +8438,7 @@ function digestGraphLock(lock) {
7696
8438
  return createHash8("sha256").update(canonicalGraphLockJson(lock)).digest("hex");
7697
8439
  }
7698
8440
  function workspaceOwnerId(workspaceRoot) {
7699
- return `workspace-root:${resolve14(workspaceRoot)}`;
8441
+ return `workspace-root:${resolve16(workspaceRoot)}`;
7700
8442
  }
7701
8443
  function assertFrozenGraph(previousLock, graph, frozen, label) {
7702
8444
  if (!frozen) return;
@@ -7751,7 +8493,7 @@ ${sources.map((source) => `- ${source}`).join("\n")}`);
7751
8493
  }
7752
8494
 
7753
8495
  // src/runtime/target.ts
7754
- import { basename as basename17, dirname as dirname22, join as join33, resolve as resolve15 } from "path";
8496
+ import { basename as basename20, dirname as dirname27, join as join36, resolve as resolve17 } from "path";
7755
8497
  var runtimeMarkers = [
7756
8498
  { adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
7757
8499
  { adapter: "claude", dirs: [".claude"] },
@@ -7760,9 +8502,9 @@ var runtimeMarkers = [
7760
8502
  { adapter: "copilot", dirs: [".github"] }
7761
8503
  ];
7762
8504
  async function resolveRuntimeTarget(request = {}) {
7763
- const cwd = resolve15(request.cwd ?? process.cwd());
8505
+ const cwd = resolve17(request.cwd ?? process.cwd());
7764
8506
  if (request.targetRoot) {
7765
- const targetRoot = resolve15(request.targetRoot);
8507
+ const targetRoot = resolve17(request.targetRoot);
7766
8508
  return {
7767
8509
  adapter: request.adapter ?? "openclaw",
7768
8510
  installationType: request.installationType,
@@ -7793,7 +8535,7 @@ async function resolveRuntimeTarget(request = {}) {
7793
8535
  async function resolveAllRuntimeTargets(request = {}) {
7794
8536
  if (request.targetRoot) return [await resolveRuntimeTarget(request)];
7795
8537
  if (request.agent) return [await resolveRuntimeTarget(request)];
7796
- const cwd = resolve15(request.cwd ?? process.cwd());
8538
+ const cwd = resolve17(request.cwd ?? process.cwd());
7797
8539
  const workspaceRoot = await findWorkspaceRoot(cwd);
7798
8540
  const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
7799
8541
  const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot, request.installationType));
@@ -7803,8 +8545,8 @@ async function resolveAllRuntimeTargets(request = {}) {
7803
8545
  return targets;
7804
8546
  }
7805
8547
  async function resolveProfileRuntimeTargets(request) {
7806
- const cwd = resolve15(request.cwd ?? process.cwd());
7807
- const workspaceRoot = request.targetRoot ? resolve15(request.targetRoot) : await findWorkspaceRoot(cwd);
8548
+ const cwd = resolve17(request.cwd ?? process.cwd());
8549
+ const workspaceRoot = request.targetRoot ? resolve17(request.targetRoot) : await findWorkspaceRoot(cwd);
7808
8550
  const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
7809
8551
  const profile = config.profiles[request.profile];
7810
8552
  if (!profile) {
@@ -7857,14 +8599,14 @@ async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
7857
8599
  return unique[0];
7858
8600
  }
7859
8601
  async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
7860
- const root = resolve15(cwd);
8602
+ const root = resolve17(cwd);
7861
8603
  const matches = [];
7862
8604
  for (const marker of runtimeMarkers) {
7863
8605
  if (adapterFilter && marker.adapter !== adapterFilter) continue;
7864
8606
  for (const dir of marker.dirs) {
7865
- if (basename17(root) === dir) {
7866
- matches.push({ adapter: marker.adapter, targetRoot: dirname22(root) });
7867
- } else if (await pathExists(join33(root, dir))) {
8607
+ if (basename20(root) === dir) {
8608
+ matches.push({ adapter: marker.adapter, targetRoot: dirname27(root) });
8609
+ } else if (await pathExists(join36(root, dir))) {
7868
8610
  matches.push({ adapter: marker.adapter, targetRoot: root });
7869
8611
  }
7870
8612
  }
@@ -7901,9 +8643,9 @@ function dedupeTargets(matches) {
7901
8643
  return [...byKey.values()];
7902
8644
  }
7903
8645
  function runtimeScanRoot(request) {
7904
- const root = resolve15(request.targetRoot ?? request.cwd ?? process.cwd());
8646
+ const root = resolve17(request.targetRoot ?? request.cwd ?? process.cwd());
7905
8647
  if (request.targetRoot) return root;
7906
- return runtimeMarkers.some((marker) => marker.dirs.includes(basename17(root))) ? dirname22(root) : root;
8648
+ return runtimeMarkers.some((marker) => marker.dirs.includes(basename20(root))) ? dirname27(root) : root;
7907
8649
  }
7908
8650
 
7909
8651
  // src/lifecycle/profile.ts
@@ -7940,7 +8682,9 @@ async function syncProfile(options) {
7940
8682
  ref: pkg.requestedRef,
7941
8683
  select: selected ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
7942
8684
  aliases: pkg.aliases,
7943
- overrides: pkg.overrides
8685
+ overrides: pkg.overrides,
8686
+ includeSuggestions: options.includeSuggestions === true || pkg.withSuggestions === true,
8687
+ suggestionAliases: combinedSuggestionAliases(pkg.suggestions, options.suggestionAliases)
7944
8688
  })),
7945
8689
  targetRoot: target.targetRoot,
7946
8690
  workspaceRoot: options.workspaceRoot,
@@ -7960,6 +8704,8 @@ async function syncProfile(options) {
7960
8704
  },
7961
8705
  installationType,
7962
8706
  noDeps: options.noDeps,
8707
+ includeSuggestions: options.includeSuggestions,
8708
+ suggestionAliases: options.suggestionAliases,
7963
8709
  lockedResolution: options.lockedResolution,
7964
8710
  frozenLock: options.frozenLock,
7965
8711
  offline: options.offline,
@@ -7989,7 +8735,7 @@ async function syncProfile(options) {
7989
8735
  });
7990
8736
  }
7991
8737
  } finally {
7992
- await rm9(graphPlan.bundle.root, { recursive: true, force: true });
8738
+ await rm10(graphPlan.bundle.root, { recursive: true, force: true });
7993
8739
  }
7994
8740
  }
7995
8741
  return results;
@@ -8000,6 +8746,7 @@ async function packageFromSource(source, options) {
8000
8746
  warn: options.warn
8001
8747
  });
8002
8748
  const driver = options.driver ?? inferSourceDriverName(resolved.source);
8749
+ const selectedArtifacts = normalizeArtifactSelectors(options.select, options.skills) ?? selectorsFromRegistryEntry(resolved.registryEntry);
8003
8750
  return {
8004
8751
  name: resolved.registryEntry?.name ?? source,
8005
8752
  source: resolved.source,
@@ -8007,15 +8754,184 @@ async function packageFromSource(source, options) {
8007
8754
  adapter: "openclaw",
8008
8755
  installationType: options.installationType,
8009
8756
  mode: options.mode ?? "pinned",
8010
- select: options.select,
8011
- skills: options.skills
8757
+ select: selectedArtifacts,
8758
+ withSuggestions: options.includeSuggestions === true ? true : void 0,
8759
+ suggestions: options.suggestionAliases
8012
8760
  };
8013
8761
  }
8762
+ function combinedSuggestionAliases(packageAliases, optionAliases) {
8763
+ const aliases = [...packageAliases ?? [], ...optionAliases ?? []].map((item) => item.trim()).filter(Boolean);
8764
+ return aliases.length > 0 ? [...new Set(aliases)].sort((a, b) => a.localeCompare(b)) : void 0;
8765
+ }
8766
+
8767
+ // src/registry/publish.ts
8768
+ var DEFAULT_REGISTRY_SUBMISSION_URL = "https://github.com/NestDevLab/agentwheel-registry/issues/new";
8769
+ var registryEntryTypes = ["package", "skill", "plugin", "mcp", "adapter"];
8770
+ var explicitSourcePrefixes = ["github:", "git:", "skillkit:", "vercel:", "mcp-registry:", "clawhub:"];
8771
+ function createRegistryPublishDraft(sourceInput, options = {}) {
8772
+ const source = normalizeCatalogueSource(sourceInput);
8773
+ const entry = {
8774
+ name: normalizeRegistryName(options.name ?? inferRegistryName(source)),
8775
+ source,
8776
+ type: options.type ? parseRegistryEntryType(options.type) : inferRegistryEntryType(source),
8777
+ description: options.description?.trim() ?? "",
8778
+ tags: normalizeTags(options.tags ?? [])
8779
+ };
8780
+ const selectors = normalizeArtifactSelectors(options.select, options.skills);
8781
+ if (selectors?.length) entry.select = selectors;
8782
+ if (options.skills?.length) entry.skills = normalizeSkillNames(options.skills);
8783
+ const installCommand = installCommandForEntry(entry);
8784
+ return {
8785
+ entry,
8786
+ installCommand,
8787
+ issueUrl: registrySubmissionUrl(entry, installCommand, options.submissionUrl ?? DEFAULT_REGISTRY_SUBMISSION_URL)
8788
+ };
8789
+ }
8790
+ function normalizeCatalogueSource(sourceInput) {
8791
+ const source = sourceInput.trim();
8792
+ if (!source) throw new Error("Catalogue source is required.");
8793
+ const unprefixedGitUrl = source.startsWith("git+http://") || source.startsWith("git+https://") ? source.slice("git+".length) : source;
8794
+ const githubSource = normalizeGitHubUrl(unprefixedGitUrl);
8795
+ if (githubSource) return githubSource;
8796
+ if (isHttpUrl(unprefixedGitUrl)) return `git:${unprefixedGitUrl}`;
8797
+ if (explicitSourcePrefixes.some((prefix) => source.startsWith(prefix))) return source;
8798
+ if (source.startsWith(".") || source.startsWith("/") || source.startsWith("local:")) {
8799
+ throw new Error("Catalogue submissions must use a public source, not a local path.");
8800
+ }
8801
+ const shorthand = normalizeOwnerRepoShorthand(source);
8802
+ if (shorthand) return shorthand;
8803
+ throw new Error(`Unsupported catalogue source: ${sourceInput}. Use a GitHub URL, github:owner/repo, git:https://..., skillkit:, vercel:, mcp-registry:, or clawhub:.`);
8804
+ }
8805
+ function inferRegistryEntryType(source) {
8806
+ if (source.startsWith("mcp-registry:")) return "mcp";
8807
+ if (source.startsWith("clawhub:")) return "plugin";
8808
+ if (source.startsWith("skillkit:") || source.startsWith("vercel:")) return "skill";
8809
+ return "package";
8810
+ }
8811
+ function parseRegistryEntryType(value) {
8812
+ const normalized = value.trim().toLowerCase();
8813
+ if (registryEntryTypes.includes(normalized)) return normalized;
8814
+ throw new Error(`Unsupported registry entry type: ${value}. Use ${registryEntryTypes.join(", ")}.`);
8815
+ }
8816
+ function installCommandForEntry(entry) {
8817
+ const base = ["agentwheel", "install", entry.source, ...selectorArgsForEntry(entry)];
8818
+ if (entry.type === "mcp") return [...base, "--adapter", "claude", "--local", "--dry-run"].map(shellQuoteArg).join(" ");
8819
+ if (entry.source.startsWith("clawhub:") || entry.type === "plugin") {
8820
+ return [...base, "--adapter", "openclaw", "--local", "--dry-run"].map(shellQuoteArg).join(" ");
8821
+ }
8822
+ return [...base, "--adapter", "codex", "--local", "--dry-run"].map(shellQuoteArg).join(" ");
8823
+ }
8824
+ function registrySubmissionUrl(entry, installCommand, baseUrl) {
8825
+ const url = new URL(baseUrl);
8826
+ url.searchParams.set("title", `Catalogue submission: ${entry.name}`);
8827
+ url.searchParams.set("body", registrySubmissionBody(entry, installCommand));
8828
+ return url.toString();
8829
+ }
8830
+ function registrySubmissionBody(entry, installCommand) {
8831
+ const descriptionNote = entry.description ? [] : ["", "Note: add a concise description before submitting."];
8832
+ return [
8833
+ "## Agentwheel catalogue submission",
8834
+ "",
8835
+ "Please review this generated registry entry:",
8836
+ "",
8837
+ "```json",
8838
+ JSON.stringify(entry, null, 2),
8839
+ "```",
8840
+ "",
8841
+ "## Verification",
8842
+ "",
8843
+ `Source: \`${entry.source}\``,
8844
+ `Suggested check: \`${installCommand}\``,
8845
+ "",
8846
+ "## Checklist",
8847
+ "",
8848
+ "- [ ] The source is public and installable.",
8849
+ "- [ ] The description is concise and factual.",
8850
+ "- [ ] Tags help discovery.",
8851
+ ...descriptionNote
8852
+ ].join("\n");
8853
+ }
8854
+ function normalizeGitHubUrl(value) {
8855
+ let url;
8856
+ try {
8857
+ url = new URL(value);
8858
+ } catch {
8859
+ return void 0;
8860
+ }
8861
+ if (url.hostname.toLowerCase() !== "github.com") return void 0;
8862
+ const segments = url.pathname.split("/").filter(Boolean).map((segment) => decodeURIComponent(segment));
8863
+ const [owner, repoSegment] = segments;
8864
+ if (!owner || !repoSegment) return void 0;
8865
+ const repo = repoSegment.replace(/\.git$/i, "");
8866
+ let ref = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
8867
+ if (segments[2] === "tree" && segments.length > 3) {
8868
+ ref = segments.slice(3).join("/");
8869
+ }
8870
+ return `github:${owner}/${repo}${ref ? `#${ref}` : ""}`;
8871
+ }
8872
+ function normalizeOwnerRepoShorthand(value) {
8873
+ const match = /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)(#[^\s]+)?$/.exec(value);
8874
+ if (!match) return void 0;
8875
+ return `github:${match[1]}/${match[2]}${match[3] ?? ""}`;
8876
+ }
8877
+ function inferRegistryName(source) {
8878
+ const withoutRef = source.split("#", 1)[0];
8879
+ if (withoutRef.startsWith("github:")) return lastPathSegment(withoutRef.slice("github:".length));
8880
+ if (withoutRef.startsWith("git:")) return nameFromGitUrl(withoutRef.slice("git:".length));
8881
+ if (withoutRef.startsWith("skillkit:")) return lastPathSegment(withoutRef.slice("skillkit:".length));
8882
+ if (withoutRef.startsWith("vercel:")) return lastPathSegment(withoutRef.slice("vercel:".length));
8883
+ if (withoutRef.startsWith("mcp-registry:")) return lastPathSegment(withoutRef.slice("mcp-registry:".length));
8884
+ if (withoutRef.startsWith("clawhub:")) return lastPathSegment(withoutRef.slice("clawhub:".length));
8885
+ return withoutRef;
8886
+ }
8887
+ function nameFromGitUrl(value) {
8888
+ try {
8889
+ return lastPathSegment(new URL(value).pathname);
8890
+ } catch {
8891
+ return lastPathSegment(value);
8892
+ }
8893
+ }
8894
+ function lastPathSegment(value) {
8895
+ const trimmed = value.replace(/\.git$/i, "").replace(/\/+$/g, "");
8896
+ const segments = trimmed.split("/").filter(Boolean);
8897
+ return segments.at(-1) ?? trimmed;
8898
+ }
8899
+ function normalizeRegistryName(value) {
8900
+ const name = slugify(value);
8901
+ if (!name) throw new Error("Registry entry name could not be inferred. Pass --name <short-name>.");
8902
+ return name;
8903
+ }
8904
+ function normalizeTags(tags) {
8905
+ const normalized = tags.flatMap((tag) => tag.split(",")).map((tag) => slugify(tag)).filter(Boolean);
8906
+ return [...new Set(normalized)];
8907
+ }
8908
+ function normalizeSkillNames(skills) {
8909
+ return [...new Set(skills.flatMap((skill) => skill.split(",")).map((skill) => skill.trim()).filter(Boolean))];
8910
+ }
8911
+ function selectorArgsForEntry(entry) {
8912
+ if (entry.skills?.length) return entry.skills.flatMap((skill) => ["--skill", skill]);
8913
+ return (entry.select ?? []).flatMap((selector) => ["--select", selector]);
8914
+ }
8915
+ function slugify(value) {
8916
+ return value.trim().toLowerCase().replace(/\.git$/i, "").replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
8917
+ }
8918
+ function isHttpUrl(value) {
8919
+ try {
8920
+ const url = new URL(value);
8921
+ return url.protocol === "http:" || url.protocol === "https:";
8922
+ } catch {
8923
+ return false;
8924
+ }
8925
+ }
8926
+ function shellQuoteArg(value) {
8927
+ if (/^[A-Za-z0-9_./:@=-]+$/.test(value)) return value;
8928
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
8929
+ }
8014
8930
 
8015
8931
  // src/cli/update-check.ts
8016
- import { mkdir as mkdir16, readFile as readFile23, writeFile as writeFile14 } from "fs/promises";
8932
+ import { mkdir as mkdir20, readFile as readFile25, writeFile as writeFile18 } from "fs/promises";
8017
8933
  import { homedir as homedir8 } from "os";
8018
- import { dirname as dirname23, join as join34 } from "path";
8934
+ import { dirname as dirname28, join as join37 } from "path";
8019
8935
  var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
8020
8936
  var DEFAULT_TIMEOUT_MS = 300;
8021
8937
  var REGISTRY_URL = "https://registry.npmjs.org/agentwheel";
@@ -8023,7 +8939,7 @@ async function maybeCheckForUpdate(options) {
8023
8939
  if (isDisabled(options)) return;
8024
8940
  const now = options.now?.() ?? /* @__PURE__ */ new Date();
8025
8941
  const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
8026
- const cachePath = options.cachePath ?? join34(homedir8(), ".agentwheel", "update-check.json");
8942
+ const cachePath = options.cachePath ?? join37(homedir8(), ".agentwheel", "update-check.json");
8027
8943
  try {
8028
8944
  const cached = await readCache(cachePath);
8029
8945
  if (cached && now.getTime() - Date.parse(cached.checkedAt) < ttlMs) {
@@ -8060,7 +8976,7 @@ async function fetchLatestVersion(fetchImpl, timeoutMs) {
8060
8976
  }
8061
8977
  async function readCache(path) {
8062
8978
  try {
8063
- const parsed = JSON.parse(await readFile23(path, "utf8"));
8979
+ const parsed = JSON.parse(await readFile25(path, "utf8"));
8064
8980
  if (typeof parsed.checkedAt !== "string" || typeof parsed.latest !== "string") return void 0;
8065
8981
  return { checkedAt: parsed.checkedAt, latest: parsed.latest };
8066
8982
  } catch {
@@ -8068,8 +8984,8 @@ async function readCache(path) {
8068
8984
  }
8069
8985
  }
8070
8986
  async function writeCache(path, cache) {
8071
- await mkdir16(dirname23(path), { recursive: true });
8072
- await writeFile14(path, `${JSON.stringify(cache, null, 2)}
8987
+ await mkdir20(dirname28(path), { recursive: true });
8988
+ await writeFile18(path, `${JSON.stringify(cache, null, 2)}
8073
8989
  `, "utf8");
8074
8990
  }
8075
8991
  function warnIfNewer(latest, current, stderr = process.stderr) {
@@ -8092,9 +9008,9 @@ function normalizeVersion(version) {
8092
9008
 
8093
9009
  // src/model/package-validate.ts
8094
9010
  import { stat as stat11 } from "fs/promises";
8095
- import { resolve as resolve16 } from "path";
9011
+ import { resolve as resolve18 } from "path";
8096
9012
  async function validatePackage(root) {
8097
- const packageRoot = resolve16(root);
9013
+ const packageRoot = resolve18(root);
8098
9014
  const findings = [];
8099
9015
  const manifestPath = await findPackageManifestPath(packageRoot);
8100
9016
  if (!manifestPath) {
@@ -8141,6 +9057,14 @@ function validateDeclaredSelectors(manifest, findings, manifestPath) {
8141
9057
  validateSelector(selector, `requires.${alias}.select`, findings, manifestPath, { localOnly: true });
8142
9058
  }
8143
9059
  }
9060
+ for (const [alias, suggestion] of Object.entries(manifest.suggests ?? {})) {
9061
+ if (!alias.trim()) {
9062
+ findings.push({ level: "error", message: "Suggestion alias must be non-empty", path: manifestPath });
9063
+ }
9064
+ for (const selector of suggestion.select ?? []) {
9065
+ validateSelector(selector, `suggests.${alias}.select`, findings, manifestPath, { localOnly: true });
9066
+ }
9067
+ }
8144
9068
  }
8145
9069
  for (const [provideIndex, provide] of manifest.provides.entries()) {
8146
9070
  if (!("items" in provide) || !provide.items) continue;
@@ -8149,6 +9073,15 @@ function validateDeclaredSelectors(manifest, findings, manifestPath) {
8149
9073
  const selector = typeof requirement === "string" ? requirement : requirement.selector;
8150
9074
  validateSelector(selector, `provides[${provideIndex}].items.${itemName}.requires`, findings, manifestPath, { aliases: manifest.schemaVersion === 2 ? Object.keys(manifest.requires ?? {}) : [] });
8151
9075
  }
9076
+ for (const suggestion of item.suggests ?? []) {
9077
+ const alias = typeof suggestion === "string" ? suggestion : suggestion.alias;
9078
+ if (manifest.schemaVersion === 2 && !Object.keys(manifest.suggests ?? {}).includes(alias)) {
9079
+ findings.push({ level: "error", message: `provides[${provideIndex}].items.${itemName}.suggests: suggestion alias not declared: ${alias}`, path: manifestPath });
9080
+ }
9081
+ for (const selector of typeof suggestion === "string" ? [] : suggestion.select ?? []) {
9082
+ validateSelector(selector, `provides[${provideIndex}].items.${itemName}.suggests.${alias}.select`, findings, manifestPath, { localOnly: true });
9083
+ }
9084
+ }
8152
9085
  for (const entry of item.compose ?? []) {
8153
9086
  validateSelector(entry.include, `provides[${provideIndex}].items.${itemName}.compose.include`, findings, manifestPath, {
8154
9087
  aliases: manifest.schemaVersion === 2 ? Object.keys(manifest.requires ?? {}) : [],
@@ -8162,7 +9095,7 @@ async function validateManifestComposeInclude(packageRoot, selector, optional, f
8162
9095
  try {
8163
9096
  validateSelector(selector, "compose.include", findings, manifestPath, { fragmentsOnly: true, aliases });
8164
9097
  if (isCrossPackageSelector(selector)) return;
8165
- const full = resolve16(packageRoot, selector);
9098
+ const full = resolve18(packageRoot, selector);
8166
9099
  if (full !== packageRoot && !full.startsWith(`${packageRoot}/`)) {
8167
9100
  findings.push({ level: "error", message: `Compose include escapes package root: ${selector}`, path: manifestPath });
8168
9101
  return;
@@ -8215,13 +9148,13 @@ function isCrossPackageSelector(value) {
8215
9148
  }
8216
9149
 
8217
9150
  // src/model/package-migrate.ts
8218
- import { readFile as readFile24, rename as rename4, writeFile as writeFile15 } from "fs/promises";
8219
- import { join as join36, resolve as resolve17 } from "path";
9151
+ import { readFile as readFile26, rename as rename4, writeFile as writeFile19 } from "fs/promises";
9152
+ import { join as join39, resolve as resolve19 } from "path";
8220
9153
  import { applyEdits, modify, parse as parse4 } from "jsonc-parser";
8221
9154
  async function migratePackageManifest(root) {
8222
- const packageRoot = resolve17(root);
9155
+ const packageRoot = resolve19(root);
8223
9156
  for (const name of openPackManifestNames) {
8224
- const path = join36(packageRoot, name);
9157
+ const path = join39(packageRoot, name);
8225
9158
  if (await pathExists(path)) {
8226
9159
  return { changed: false, to: path, message: `Package already uses ${name}.` };
8227
9160
  }
@@ -8230,18 +9163,18 @@ async function migratePackageManifest(root) {
8230
9163
  if (!legacyName) {
8231
9164
  throw new Error(`No legacy package manifest found at ${packageRoot}`);
8232
9165
  }
8233
- const from = join36(packageRoot, legacyName);
9166
+ const from = join39(packageRoot, legacyName);
8234
9167
  const toName = legacyName.endsWith(".jsonc") ? "openpack.jsonc" : "openpack.json";
8235
- const to = join36(packageRoot, toName);
8236
- const content = await readFile24(from, "utf8");
9168
+ const to = join39(packageRoot, toName);
9169
+ const content = await readFile26(from, "utf8");
8237
9170
  const updated = updateSchemaVersion(content);
8238
9171
  await rename4(from, to);
8239
- await writeFile15(to, updated, "utf8");
9172
+ await writeFile19(to, updated, "utf8");
8240
9173
  return { changed: true, from, to, message: `Migrated ${legacyName} to ${toName}.` };
8241
9174
  }
8242
9175
  async function firstExistingLegacyManifest(root) {
8243
9176
  for (const name of legacyPackageManifestNames) {
8244
- if (await pathExists(join36(root, name))) return name;
9177
+ if (await pathExists(join39(root, name))) return name;
8245
9178
  }
8246
9179
  return void 0;
8247
9180
  }
@@ -8259,20 +9192,20 @@ function updateSchemaVersion(content) {
8259
9192
 
8260
9193
  // src/cli/version.ts
8261
9194
  import { readFileSync } from "fs";
8262
- import { dirname as dirname24, join as join37 } from "path";
9195
+ import { dirname as dirname29, join as join40 } from "path";
8263
9196
  import { fileURLToPath as fileURLToPath2 } from "url";
8264
9197
  var FALLBACK_VERSION = "0.0.0";
8265
9198
  function resolveCliVersion() {
8266
- let dir = dirname24(fileURLToPath2(import.meta.url));
9199
+ let dir = dirname29(fileURLToPath2(import.meta.url));
8267
9200
  while (true) {
8268
9201
  try {
8269
- const pkg = JSON.parse(readFileSync(join37(dir, "package.json"), "utf8"));
9202
+ const pkg = JSON.parse(readFileSync(join40(dir, "package.json"), "utf8"));
8270
9203
  if (pkg.name === "agentwheel" && typeof pkg.version === "string") {
8271
9204
  return pkg.version;
8272
9205
  }
8273
9206
  } catch {
8274
9207
  }
8275
- const parent = dirname24(dir);
9208
+ const parent = dirname29(dir);
8276
9209
  if (parent === dir) return FALLBACK_VERSION;
8277
9210
  dir = parent;
8278
9211
  }
@@ -8308,7 +9241,7 @@ program.command("init").description("initialize an agentwheel workspace or packa
8308
9241
  if (bootstrapPackage) console.log("Auto-added the agentwheel bootstrap skill for openclaw.");
8309
9242
  console.log(nextInstallNudge());
8310
9243
  });
8311
- 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, or vercel-skills)").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("--override <source-or-package::type/name>", "allow this package to replace a colliding artifact (repeatable)", collectOverrideOption, []).action(async (source, options) => {
9244
+ 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) => {
8312
9245
  const normalizedOptions = normalizeRuntimeScopeOptions(options);
8313
9246
  const targetRoot = normalizeTargetRoot(normalizedOptions.targetRoot ?? process.cwd());
8314
9247
  const entry = await packageEntryFromSource(source, targetRoot, normalizedOptions);
@@ -8317,10 +9250,10 @@ program.command("add").description("add a package to .agentwheel/config.json wit
8317
9250
  });
8318
9251
  program.command("list").description("list artifacts exposed by a package source").argument("<source>", "package source").option("--driver <driver>", "source driver").option("-t, --target-root <path>", "workspace root", process.cwd()).option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).action(async (source, options) => {
8319
9252
  const targetRoot = normalizeTargetRoot(options.targetRoot);
8320
- const selectedArtifacts = selectedArtifactsFromOptions(options);
8321
9253
  const resolvedInput = await resolvePackageSource(source, targetRoot);
9254
+ const selectedArtifacts = selectedArtifactsFromOptionsOrRegistry(options, resolvedInput.registryEntry);
8322
9255
  const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
8323
- const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join38(targetRoot, ".agentwheel", "cache") }))));
9256
+ const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join41(targetRoot, ".agentwheel", "cache") }))));
8324
9257
  const artifacts = filterArtifactsBySelection(await driver.list(resolved), selectedArtifacts);
8325
9258
  for (const artifact of artifacts) {
8326
9259
  console.log(`${artifact.type} ${artifact.name} ${artifact.relativePath}`);
@@ -8330,7 +9263,7 @@ program.command("scan").description("scan a package source for validation findin
8330
9263
  const targetRoot = normalizeTargetRoot(options.targetRoot);
8331
9264
  const resolvedInput = await resolvePackageSource(source, targetRoot);
8332
9265
  const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
8333
- const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join38(targetRoot, ".agentwheel", "cache") }))));
9266
+ const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join41(targetRoot, ".agentwheel", "cache") }))));
8334
9267
  const result = await driver.scan(resolved);
8335
9268
  if (result.findings.length === 0) {
8336
9269
  console.log("Scan ok: no findings");
@@ -8341,17 +9274,17 @@ program.command("scan").description("scan a package source for validation findin
8341
9274
  }
8342
9275
  if (!result.ok) process.exitCode = 1;
8343
9276
  });
8344
- 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("--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("--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("--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) => {
9277
+ 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("--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("--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) => {
8345
9278
  await runInstallCommand(source, { ...options, dryRun: true }, { apply: false });
8346
9279
  });
8347
- 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("--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("--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) => {
9280
+ 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("--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("--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) => {
8348
9281
  await runInstallCommand(source, options, { apply: !options.dryRun });
8349
9282
  });
8350
- 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("--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("--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) => {
9283
+ 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("--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) => {
8351
9284
  console.error("warning: 'agentwheel sync' is deprecated and will be removed in 0.10. Use 'agentwheel install'.");
8352
9285
  await runInstallCommand(source, options, { apply: !options.dryRun });
8353
9286
  });
8354
- 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("--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("--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) => {
9287
+ 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("--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("--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) => {
8355
9288
  const normalizedOptions = normalizeRuntimeScopeOptions(options);
8356
9289
  const targets = await resolveCliTargets(normalizedOptions, { preferAllProfile: true });
8357
9290
  for (const target of targets) {
@@ -8359,7 +9292,7 @@ program.command("update").description("re-resolve tracking packages, then apply
8359
9292
  }
8360
9293
  });
8361
9294
  program.command("deps").description("inspect the OpenPack dependency graph").addCommand(
8362
- new Command("tree").description("print the OpenPack dependency graph").argument("[source]", "optional package source to resolve").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("--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("--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 (source, options) => {
9295
+ new Command("tree").description("print the OpenPack dependency graph").argument("[source]", "optional package source to resolve").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("--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("--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 (source, options) => {
8363
9296
  const normalizedOptions = normalizeRuntimeScopeOptions(options, { defaultUser: shouldDefaultUserInstall(source, options) });
8364
9297
  const targets = await resolveCliTargets(normalizedOptions);
8365
9298
  for (const target of targets) {
@@ -8372,7 +9305,7 @@ program.command("deps").description("inspect the OpenPack dependency graph").add
8372
9305
  for (const decision of result.bundle.graphLock.canonical.overrides) {
8373
9306
  console.log(`OVERRIDE ${decision.graphNodeId}:${decision.type}/${decision.name} replaces ${decision.overriddenGraphNodeId}:${decision.type}/${decision.name} via ${decision.rootId} (${decision.selector})`);
8374
9307
  }
8375
- await rm10(result.bundle.root, { recursive: true, force: true });
9308
+ await rm11(result.bundle.root, { recursive: true, force: true });
8376
9309
  }
8377
9310
  continue;
8378
9311
  }
@@ -8409,6 +9342,29 @@ program.command("registry").description("manage optional registry indexes").addC
8409
9342
  const client = new RegistryClient({ workspaceRoot: normalizeTargetRoot(options.targetRoot), warn: (message) => console.warn(message) });
8410
9343
  printRegistryEntries(await client.search(query));
8411
9344
  })
9345
+ ).addCommand(
9346
+ 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) => {
9347
+ const draft = createRegistryPublishDraft(source, {
9348
+ name: options.name,
9349
+ type: options.type,
9350
+ description: options.description,
9351
+ tags: options.tag,
9352
+ select: options.select,
9353
+ skills: options.skill
9354
+ });
9355
+ if (options.json) {
9356
+ console.log(JSON.stringify(draft.entry, null, 2));
9357
+ return;
9358
+ }
9359
+ console.log("Draft registry entry:");
9360
+ console.log(JSON.stringify(draft.entry, null, 2));
9361
+ console.log("");
9362
+ console.log(`Verify: ${draft.installCommand}`);
9363
+ if (!draft.entry.description) console.log('Tip: add --description "..." or fill the description before submitting.');
9364
+ console.log("");
9365
+ console.log("Submit:");
9366
+ console.log(draft.issueUrl);
9367
+ })
8412
9368
  );
8413
9369
  program.command("trust").description("manage persisted source trust decisions").addCommand(
8414
9370
  new Command("forget").description("forget a persisted trusted source pattern").argument("<pattern>", "trusted source glob to revoke").option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (pattern, options) => {
@@ -8493,7 +9449,7 @@ program.command("status").description("show configured packages and runtime inst
8493
9449
  await printStatus(target, normalizedOptions);
8494
9450
  }
8495
9451
  });
8496
- program.command("doctor").description("check agentwheel runtime setup and companion skill guidance").option("--adapter <adapter>", "built-in adapter").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>", "runtime/project root").option("--agent <name>", "named agent from merged config").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).action(async (options) => {
9452
+ program.command("doctor").description("check agentwheel runtime setup and companion skill guidance").option("--adapter <adapter>", "built-in adapter").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>", "runtime/project root").option("--agent <name>", "named agent from merged config").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("--skill <name>", "check a specific skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--source <source>", "source to use in suggested install commands").option("--json", "print machine-readable doctor report", false).action(async (options) => {
8497
9453
  const normalizedOptions = normalizeRuntimeScopeOptions(options);
8498
9454
  const target = await resolveRuntimeTarget({
8499
9455
  targetRoot: normalizedOptions.targetRoot,
@@ -8527,6 +9483,8 @@ async function runInstallCommand(nameOrSource, options, behavior) {
8527
9483
  forceConflict: normalizedOptions.forceConflict,
8528
9484
  replaceConflict: normalizedOptions.replaceConflict,
8529
9485
  noDeps: noDepsFromOptions(normalizedOptions),
9486
+ includeSuggestions: normalizedOptions.withSuggestions,
9487
+ suggestionAliases: suggestionAliasesFromOptions(normalizedOptions),
8530
9488
  lockedResolution: true,
8531
9489
  frozenLock: normalizedOptions.frozenLock,
8532
9490
  offline: normalizedOptions.offline,
@@ -8576,7 +9534,7 @@ async function runInstallCommand(nameOrSource, options, behavior) {
8576
9534
  });
8577
9535
  console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
8578
9536
  }
8579
- await rm10(result.bundle.root, { recursive: true, force: true });
9537
+ await rm11(result.bundle.root, { recursive: true, force: true });
8580
9538
  if (result.plan.hasBlockingChanges) process.exitCode = 1;
8581
9539
  }
8582
9540
  if (behavior.apply && extraPackage && !targetOptions.onlySource) {
@@ -8585,10 +9543,10 @@ async function runInstallCommand(nameOrSource, options, behavior) {
8585
9543
  }
8586
9544
  }
8587
9545
  async function packageEntryFromSource(source, targetRoot, options) {
8588
- const selectedArtifacts = selectedArtifactsFromOptions(options);
8589
9546
  const lockMode = options.frozenLock === true || options.offline === true;
8590
9547
  const resolvedInput = await resolvePackageSource(source, targetRoot, { offline: lockMode });
8591
9548
  const resolvedSource = resolvedInput.source;
9549
+ const selectedArtifacts = selectedArtifactsFromOptionsOrRegistry(options, resolvedInput.registryEntry);
8592
9550
  const driverName = options.driver ?? inferSourceDriverName(resolvedSource);
8593
9551
  const driver = getSourceDriver(driverName);
8594
9552
  const adapter = await resolveAdapter({
@@ -8602,7 +9560,7 @@ async function packageEntryFromSource(source, targetRoot, options) {
8602
9560
  const bundle = await stageSource(driver, resolvedSource, {
8603
9561
  workspaceRoot: targetRoot,
8604
9562
  adapter,
8605
- cacheRoot: join38(targetRoot, ".agentwheel", "cache"),
9563
+ cacheRoot: join41(targetRoot, ".agentwheel", "cache"),
8606
9564
  mode: options.mode,
8607
9565
  frozenLock: lockMode,
8608
9566
  select: selectedArtifacts
@@ -8621,10 +9579,12 @@ async function packageEntryFromSource(source, targetRoot, options) {
8621
9579
  mode: options.mode ?? "pinned",
8622
9580
  requestedRef: bundle.source.requestedRef,
8623
9581
  select: selectedArtifacts,
9582
+ withSuggestions: options.withSuggestions === true ? true : void 0,
9583
+ suggestions: suggestionAliasesFromOptions(options),
8624
9584
  overrides: overrideArtifactsFromOptions(options)
8625
9585
  };
8626
9586
  } finally {
8627
- await rm10(bundle.root, { recursive: true, force: true });
9587
+ await rm11(bundle.root, { recursive: true, force: true });
8628
9588
  }
8629
9589
  }
8630
9590
  function findConfiguredPackage(packages, value) {
@@ -8765,7 +9725,7 @@ async function runConfiguredGraphPackages(target, options, behavior) {
8765
9725
  });
8766
9726
  console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
8767
9727
  }
8768
- await rm10(result.bundle.root, { recursive: true, force: true });
9728
+ await rm11(result.bundle.root, { recursive: true, force: true });
8769
9729
  if (result.plan.hasBlockingChanges) process.exitCode = 1;
8770
9730
  }
8771
9731
  }
@@ -8830,6 +9790,8 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
8830
9790
  select: selectedArtifacts && packageIsScoped ? selectedArtifacts : normalizeArtifactSelectors(pkg.select, pkg.skills),
8831
9791
  aliases: pkg.aliases,
8832
9792
  overrides: pkg.overrides,
9793
+ includeSuggestions: targetOptions.withSuggestions === true || pkg.withSuggestions === true,
9794
+ suggestionAliases: packageSuggestionAliases(pkg, targetOptions),
8833
9795
  useLock: behavior.mode === "install" ? true : !updateThisPackage
8834
9796
  };
8835
9797
  }),
@@ -8854,6 +9816,8 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
8854
9816
  targetFingerprintParts: targetFingerprintParts(group.target, adapter, group.adapterOptions, group.installationType),
8855
9817
  installationType: group.installationType,
8856
9818
  noDeps: noDepsFromOptions(targetOptions),
9819
+ includeSuggestions: targetOptions.withSuggestions,
9820
+ suggestionAliases: suggestionAliasesFromOptions(targetOptions),
8857
9821
  lockedResolution: behavior.mode === "install",
8858
9822
  frozenLock: targetOptions.frozenLock,
8859
9823
  offline: targetOptions.offline,
@@ -8954,7 +9918,7 @@ function keepManifestEntryOperation(entry, targetRoot, rootId, operation, option
8954
9918
  artifactType: entry.artifactType,
8955
9919
  artifactName: entry.artifactName,
8956
9920
  kind: entry.kind,
8957
- destPath: operation?.destPath ?? join38(targetRoot, entry.path),
9921
+ destPath: operation?.destPath ?? join41(targetRoot, entry.path),
8958
9922
  relativeDestPath: entry.path,
8959
9923
  desiredHash: entry.sourceHash,
8960
9924
  currentHash: operation?.currentHash ?? entry.hash,
@@ -9019,7 +9983,9 @@ async function uninstallConfiguredPackage(target, packageName, options) {
9019
9983
  ref: pkg2.requestedRef,
9020
9984
  select: normalizeArtifactSelectors(pkg2.select, pkg2.skills),
9021
9985
  aliases: pkg2.aliases,
9022
- overrides: pkg2.overrides
9986
+ overrides: pkg2.overrides,
9987
+ includeSuggestions: options.withSuggestions === true || pkg2.withSuggestions === true,
9988
+ suggestionAliases: packageSuggestionAliases(pkg2, options)
9023
9989
  })),
9024
9990
  targetRoot: remainingGroup.target.targetRoot,
9025
9991
  workspaceRoot: remainingGroup.target.workspaceRoot,
@@ -9028,6 +9994,8 @@ async function uninstallConfiguredPackage(target, packageName, options) {
9028
9994
  targetKey: targetKeyForTarget(remainingGroup.target, remainingAdapter.name),
9029
9995
  targetFingerprintParts: targetFingerprintParts(remainingGroup.target, remainingAdapter, remainingGroup.adapterOptions, remainingGroup.installationType),
9030
9996
  installationType: remainingGroup.installationType,
9997
+ includeSuggestions: options.withSuggestions,
9998
+ suggestionAliases: suggestionAliasesFromOptions(options),
9031
9999
  lockedResolution: true,
9032
10000
  frozenLock: options.frozenLock,
9033
10001
  offline: options.offline,
@@ -9065,7 +10033,7 @@ async function uninstallConfiguredPackage(target, packageName, options) {
9065
10033
  if (!options.dryRun) {
9066
10034
  console.log(formatUninstallResult(result));
9067
10035
  }
9068
- if (renderedRoot) await rm10(renderedRoot, { recursive: true, force: true });
10036
+ if (renderedRoot) await rm11(renderedRoot, { recursive: true, force: true });
9069
10037
  if (plan.hasBlockingChanges) process.exitCode = 1;
9070
10038
  }
9071
10039
  }
@@ -9193,7 +10161,7 @@ async function printPendingInstallWork(target, options) {
9193
10161
  const message = error instanceof Error ? error.message : String(error);
9194
10162
  console.log(`Pending install work: unavailable (${message})`);
9195
10163
  } finally {
9196
- await Promise.all(results.map((result) => rm10(result.bundle.root, { recursive: true, force: true })));
10164
+ await Promise.all(results.map((result) => rm11(result.bundle.root, { recursive: true, force: true })));
9197
10165
  }
9198
10166
  }
9199
10167
  async function printDoctor(target, options) {
@@ -9204,33 +10172,113 @@ async function printDoctor(target, options) {
9204
10172
  if (!targetMapping?.enabled) {
9205
10173
  throw new Error(`Adapter ${adapter.name} does not support skills for installation type '${installationType}'.`);
9206
10174
  }
9207
- const installRoot = installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, target.transport === "ssh");
9208
- const companionSkillPath = join38(installRoot, targetMapping.dest, COMPANION_SKILL_NAME);
9209
- const installed = await pathExists(companionSkillPath);
9210
- console.log(`Doctor for ${adapter.name}/${installationType} at ${installRoot}`);
9211
- if (installed) {
9212
- console.log(`Agentwheel companion skill: installed at ${companionSkillPath}`);
10175
+ const transport = transportForTarget(target);
10176
+ const state = installStateForTarget(target, adapter, adapterOptions, installationType);
10177
+ const manifest = await readInstallManifest(state.installRoot, adapter.name, transport, state);
10178
+ const requestedSkills = doctorSkillRequests(target, options);
10179
+ const skills = [];
10180
+ for (const request of requestedSkills) {
10181
+ const skillPath = join41(state.installRoot, targetMapping.dest, request.name);
10182
+ const exists = await pathExists(skillPath);
10183
+ const manifestEntry = manifest?.entries.find((entry) => {
10184
+ if (entry.artifactType !== "skills") return false;
10185
+ const legacyInstallName = "installName" in entry && typeof entry.installName === "string" ? entry.installName : void 0;
10186
+ return entry.artifactName === request.name || legacyInstallName === request.name || entry.path === join41(targetMapping.dest, request.name);
10187
+ });
10188
+ const status = manifestEntry ? "managed" : exists ? "present-unmanaged" : "missing";
10189
+ skills.push({
10190
+ name: request.name,
10191
+ source: request.source,
10192
+ label: request.label,
10193
+ status,
10194
+ path: skillPath,
10195
+ managed: Boolean(manifestEntry),
10196
+ present: exists,
10197
+ suggestedCommands: status === "missing" ? {
10198
+ dryRun: skillInstallCommand(adapter.name, installationType, options, request, { dryRun: true }),
10199
+ apply: skillInstallCommand(adapter.name, installationType, options, request)
10200
+ } : void 0
10201
+ });
10202
+ }
10203
+ const report = {
10204
+ adapter: adapter.name,
10205
+ installationType,
10206
+ targetRoot: target.targetRoot,
10207
+ installRoot: state.installRoot,
10208
+ manifest: manifest ? { entries: manifest.entries.length, revision: manifest.revision } : null,
10209
+ skills
10210
+ };
10211
+ if (options.json) {
10212
+ console.log(JSON.stringify(report, null, 2));
9213
10213
  return;
9214
10214
  }
9215
- console.log(`Agentwheel companion skill: missing at ${companionSkillPath}`);
9216
- console.log("Suggested commands:");
9217
- console.log(` ${companionSkillInstallCommand(adapter.name, installationType, options, { dryRun: true })}`);
9218
- console.log(` ${companionSkillInstallCommand(adapter.name, installationType, options)}`);
10215
+ console.log(`Doctor for ${adapter.name}/${installationType} at ${state.installRoot}`);
10216
+ for (const skill of skills) {
10217
+ const statusLabel = skill.status === "managed" ? "installed" : skill.status === "present-unmanaged" ? "installed (unmanaged)" : "missing";
10218
+ console.log(`${skill.label}: ${statusLabel} at ${skill.path}`);
10219
+ }
10220
+ const missing = skills.filter((skill) => skill.status === "missing");
10221
+ if (missing.length > 0) {
10222
+ console.log("Suggested commands:");
10223
+ for (const skill of missing) {
10224
+ console.log(` ${skill.suggestedCommands?.dryRun}`);
10225
+ console.log(` ${skill.suggestedCommands?.apply}`);
10226
+ }
10227
+ }
10228
+ }
10229
+ function doctorSkillRequests(target, options) {
10230
+ const explicitSkills = normalizeDoctorSkillNames(options.skill ?? []);
10231
+ if (explicitSkills.length > 0) {
10232
+ return explicitSkills.map((name) => ({
10233
+ name,
10234
+ source: options.source ?? defaultSourceForSkill(name),
10235
+ label: doctorSkillLabel(name)
10236
+ }));
10237
+ }
10238
+ const requests = [{
10239
+ name: COMPANION_SKILL_NAME,
10240
+ source: COMPANION_SKILL_SOURCE,
10241
+ label: "Agentwheel companion skill"
10242
+ }];
10243
+ if (isSyncwheelWorkspace(target.targetRoot)) {
10244
+ requests.push({
10245
+ name: "syncwheel",
10246
+ source: "github:NestDevLab/syncwheel",
10247
+ label: "Syncwheel skill"
10248
+ });
10249
+ }
10250
+ return requests;
9219
10251
  }
9220
- function companionSkillInstallCommand(adapter, installationType, options, behavior = {}) {
10252
+ function normalizeDoctorSkillNames(skills) {
10253
+ return [...new Set(skills.flatMap(splitSelectorList).map((item) => item.trim()).filter(Boolean))];
10254
+ }
10255
+ function defaultSourceForSkill(name) {
10256
+ if (name === COMPANION_SKILL_NAME) return COMPANION_SKILL_SOURCE;
10257
+ if (name === "syncwheel") return "github:NestDevLab/syncwheel";
10258
+ return `github:NestDevLab/${name}`;
10259
+ }
10260
+ function doctorSkillLabel(name) {
10261
+ if (name === COMPANION_SKILL_NAME) return "Agentwheel companion skill";
10262
+ if (name === "syncwheel") return "Syncwheel skill";
10263
+ return `${name} skill`;
10264
+ }
10265
+ function isSyncwheelWorkspace(targetRoot) {
10266
+ return existsSync(join41(targetRoot, ".syncwheel", "manifest.json"));
10267
+ }
10268
+ function skillInstallCommand(adapter, installationType, options, skill, behavior = {}) {
9221
10269
  const args = [
9222
10270
  "agentwheel",
9223
10271
  "install",
9224
- COMPANION_SKILL_SOURCE,
10272
+ skill.source,
9225
10273
  "--adapter",
9226
10274
  adapter,
9227
10275
  ...installationTypeCommandArgs(installationType),
9228
10276
  ...targetRootCommandArgs(options),
9229
10277
  "--skill",
9230
- COMPANION_SKILL_NAME
10278
+ skill.name
9231
10279
  ];
9232
10280
  if (behavior.dryRun) args.push("--dry-run");
9233
- return args.map(shellQuoteArg).join(" ");
10281
+ return args.map(shellQuoteArg2).join(" ");
9234
10282
  }
9235
10283
  function installationTypeCommandArgs(installationType) {
9236
10284
  if (installationType === "user") return ["--user"];
@@ -9240,7 +10288,7 @@ function installationTypeCommandArgs(installationType) {
9240
10288
  function targetRootCommandArgs(options) {
9241
10289
  return options.targetRoot && options.installationType !== "user" ? ["--target-root", options.targetRoot] : [];
9242
10290
  }
9243
- function shellQuoteArg(value) {
10291
+ function shellQuoteArg2(value) {
9244
10292
  if (/^[A-Za-z0-9_./:@=-]+$/.test(value)) return value;
9245
10293
  return `'${value.replaceAll("'", `'"'"'`)}'`;
9246
10294
  }
@@ -9250,12 +10298,18 @@ function collectSelectOption(value, previous) {
9250
10298
  function collectSkillOption(value, previous) {
9251
10299
  return [...previous, ...splitSelectorList(value)];
9252
10300
  }
10301
+ function collectSuggestionOption(value, previous) {
10302
+ return [...previous, ...splitSelectorList(value)];
10303
+ }
9253
10304
  function collectTrustOption(value, previous) {
9254
10305
  return [...previous, value];
9255
10306
  }
9256
10307
  function collectOverrideOption(value, previous) {
9257
10308
  return [...previous, ...splitSelectorList(value)];
9258
10309
  }
10310
+ function collectTagOption(value, previous) {
10311
+ return [...previous, ...splitSelectorList(value)];
10312
+ }
9259
10313
  function normalizeRuntimeScopeOptions(options, behavior = {}) {
9260
10314
  if (options.user && options.local) {
9261
10315
  throw new Error("Choose either --user or --local.");
@@ -9292,11 +10346,11 @@ function looksLikeSourceSpecifier(value) {
9292
10346
  }
9293
10347
  function normalizeCliPath(value) {
9294
10348
  if (value === "~") return homedir9();
9295
- if (value.startsWith("~/")) return resolve18(homedir9(), value.slice(2));
9296
- return resolve18(value);
10349
+ if (value.startsWith("~/")) return resolve20(homedir9(), value.slice(2));
10350
+ return resolve20(value);
9297
10351
  }
9298
10352
  function isHomePath(path) {
9299
- return resolve18(path) === resolve18(homedir9());
10353
+ return resolve20(path) === resolve20(homedir9());
9300
10354
  }
9301
10355
  function adapterListFromOption(adapter) {
9302
10356
  if (!adapter) return [];
@@ -9310,6 +10364,18 @@ function adapterListFromOption(adapter) {
9310
10364
  function selectedArtifactsFromOptions(options) {
9311
10365
  return normalizeArtifactSelectors(options.select, options.skills ?? options.skill);
9312
10366
  }
10367
+ function selectedArtifactsFromOptionsOrRegistry(options, registryEntry) {
10368
+ return selectedArtifactsFromOptions(options) ?? selectorsFromRegistryEntry(registryEntry);
10369
+ }
10370
+ function suggestionAliasesFromOptions(options) {
10371
+ const values = options.suggestions ?? options.suggestion;
10372
+ if (!values?.length) return void 0;
10373
+ return [...new Set(values.flatMap(splitSelectorList).map((item) => item.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
10374
+ }
10375
+ function packageSuggestionAliases(pkg, options) {
10376
+ const aliases = [...pkg.suggestions ?? [], ...suggestionAliasesFromOptions(options) ?? []].map((item) => item.trim()).filter(Boolean);
10377
+ return aliases.length > 0 ? [...new Set(aliases)].sort((a, b) => a.localeCompare(b)) : void 0;
10378
+ }
9313
10379
  function overrideArtifactsFromOptions(options) {
9314
10380
  const values = options.overrides ?? options.override;
9315
10381
  return values && values.length > 0 ? values : void 0;
@@ -9339,10 +10405,10 @@ function filterUninstallPlanBySelection(plan, selected) {
9339
10405
  };
9340
10406
  }
9341
10407
  async function initPackage(root) {
9342
- await mkdir17(join38(root, "instructions"), { recursive: true });
9343
- await mkdir17(join38(root, "rules"), { recursive: true });
9344
- await mkdir17(join38(root, "skills"), { recursive: true });
9345
- const manifestPath = join38(root, "openpack.json");
10408
+ await mkdir21(join41(root, "instructions"), { recursive: true });
10409
+ await mkdir21(join41(root, "rules"), { recursive: true });
10410
+ await mkdir21(join41(root, "skills"), { recursive: true });
10411
+ const manifestPath = join41(root, "openpack.json");
9346
10412
  const manifest = {
9347
10413
  schemaVersion: 2,
9348
10414
  name: "example/agentwheel-package",
@@ -9353,12 +10419,12 @@ async function initPackage(root) {
9353
10419
  { type: "skills", path: "skills" }
9354
10420
  ]
9355
10421
  };
9356
- await writeFile16(manifestPath, `${JSON.stringify(manifest, null, 2)}
10422
+ await writeFile20(manifestPath, `${JSON.stringify(manifest, null, 2)}
9357
10423
  `, "utf8");
9358
- await writeFile16(join38(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
10424
+ await writeFile20(join41(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
9359
10425
  }
9360
10426
  async function defaultBootstrapPackage(_root) {
9361
- const packageRoot = await findAgentwheelPackageRoot(dirname25(fileURLToPath3(import.meta.url)));
10427
+ const packageRoot = await findAgentwheelPackageRoot(dirname30(fileURLToPath3(import.meta.url)));
9362
10428
  if (!packageRoot) return void 0;
9363
10429
  return {
9364
10430
  name: "agentwheel",
@@ -9402,10 +10468,10 @@ function withFleetExample(config) {
9402
10468
  };
9403
10469
  }
9404
10470
  async function findAgentwheelPackageRoot(start) {
9405
- let current = resolve18(start);
10471
+ let current = resolve20(start);
9406
10472
  while (true) {
9407
10473
  if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
9408
- const parent = dirname25(current);
10474
+ const parent = dirname30(current);
9409
10475
  if (parent === current) return void 0;
9410
10476
  current = parent;
9411
10477
  }