agentwheel 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9,8 +9,8 @@ import {
9
9
 
10
10
  // src/cli/index.ts
11
11
  import { mkdir as mkdir14, rm as rm9, writeFile as writeFile12 } from "fs/promises";
12
- import { dirname as dirname20, join as join27, resolve as resolve17 } from "path";
13
- import { fileURLToPath as fileURLToPath2 } from "url";
12
+ import { dirname as dirname21, join as join28, resolve as resolve17 } from "path";
13
+ import { fileURLToPath as fileURLToPath3 } from "url";
14
14
  import { Command } from "commander";
15
15
 
16
16
  // src/adapters/resolve.ts
@@ -1050,9 +1050,6 @@ function normalizeOwners(owners) {
1050
1050
 
1051
1051
  // src/install/apply.ts
1052
1052
  var execFileAsync2 = promisify2(execFile2);
1053
- async function applyInstallPlan(plan, sourceLock, options = {}) {
1054
- return applyPlanTransactionally(plan, { ...options, sourceLock });
1055
- }
1056
1053
  async function applyCombinedInstallPlan(plan, options = {}) {
1057
1054
  return applyPlanTransactionally(plan, options);
1058
1055
  }
@@ -1165,16 +1162,20 @@ async function applyPlanTransactionally(plan, options = {}) {
1165
1162
  async function uninstall(plan, options = {}) {
1166
1163
  const resolvedOptions = typeof options === "boolean" ? { dryRun: options } : options;
1167
1164
  const transport = resolvedOptions.transport ?? localTransport;
1165
+ if (resolvedOptions.keepFiles && resolvedOptions.force) {
1166
+ throw new Error("--keep-files cannot be combined with --force.");
1167
+ }
1168
1168
  if (plan.hasBlockingChanges) {
1169
1169
  const blockers = plan.operations.filter((operation) => operation.action === "conflict");
1170
1170
  throw new Error(`Refusing to uninstall with blocking changes: ${blockers.map((item) => item.relativeDestPath).join(", ")}`);
1171
1171
  }
1172
- const removable = plan.operations.filter((operation) => operation.action === "remove" || resolvedOptions.force && operation.action === "keep").map((operation) => operation.action === "keep" ? { ...operation, action: "remove", reason: `${operation.reason}; force removing drifted managed file` } : operation);
1173
- const kept = resolvedOptions.force ? [] : plan.operations.filter((operation) => operation.action === "keep");
1172
+ 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);
1173
+ const kept = plan.operations.filter((operation) => operation.action === "keep" && (!resolvedOptions.force || !isForceRemovableKeep(operation)));
1174
1174
  const skipped = plan.operations.filter((operation) => operation.action === "skip");
1175
- const removedDrifted = resolvedOptions.force ? plan.operations.filter((operation) => operation.action === "keep").length : 0;
1176
- if (resolvedOptions.dryRun) return { removed: removable.length, kept: kept.length, removedDrifted };
1177
- const preserved = [...kept, ...skipped];
1175
+ const removedDrifted = resolvedOptions.force ? plan.operations.filter((operation) => operation.action === "keep" && isForceRemovableKeep(operation)).length : 0;
1176
+ if (resolvedOptions.dryRun) return { removed: resolvedOptions.keepFiles ? 0 : removable.length, kept: kept.length, removedDrifted };
1177
+ const preservedKept = resolvedOptions.keepFiles ? kept.filter((operation) => shouldPreserveKeptOperationWhenKeepingFiles(operation)) : kept;
1178
+ const preserved = [...preservedKept, ...skipped];
1178
1179
  for (const operation of [...removable, ...preserved]) assertOperationContained(operation, plan.targetRoot);
1179
1180
  const now = (/* @__PURE__ */ new Date()).toISOString();
1180
1181
  const finalManifest = withManifestRevision({
@@ -1209,7 +1210,7 @@ async function uninstall(plan, options = {}) {
1209
1210
  graphLockDigest: plan.graphLockDigest,
1210
1211
  createdAt: now,
1211
1212
  updatedAt: now,
1212
- operations: removable,
1213
+ operations: resolvedOptions.keepFiles ? [] : removable,
1213
1214
  completed: [],
1214
1215
  manifest: finalManifest,
1215
1216
  graphLockPath: resolvedOptions.graphLock?.path,
@@ -1219,7 +1220,7 @@ async function uninstall(plan, options = {}) {
1219
1220
  workspaceConfig: resolvedOptions.workspaceConfig?.data
1220
1221
  };
1221
1222
  await writeApplyJournal(journal, transport);
1222
- for (const [index, operation] of removable.entries()) {
1223
+ for (const [index, operation] of (resolvedOptions.keepFiles ? [] : removable).entries()) {
1223
1224
  const backup = await recordBackup(operation, index, plan.targetRoot, plan.adapter, transport);
1224
1225
  journal.completed.push(backup);
1225
1226
  await writeApplyJournal(journal, transport);
@@ -1231,7 +1232,13 @@ async function uninstall(plan, options = {}) {
1231
1232
  } finally {
1232
1233
  await lock.release();
1233
1234
  }
1234
- return { removed: removable.length, kept: kept.length, removedDrifted };
1235
+ return { removed: resolvedOptions.keepFiles ? 0 : removable.length, kept: kept.length, removedDrifted };
1236
+ }
1237
+ function shouldPreserveKeptOperationWhenKeepingFiles(operation) {
1238
+ return operation.preserveInManifest === true;
1239
+ }
1240
+ function isForceRemovableKeep(operation) {
1241
+ return operation.action === "keep" && operation.preserveInManifest !== true;
1235
1242
  }
1236
1243
  async function commitJournalState(journal, transport, entries, now) {
1237
1244
  const manifest = withManifestRevision({
@@ -1332,6 +1339,17 @@ async function applyOperation(operation, context) {
1332
1339
  graphLockDigest: context.graphLockDigest
1333
1340
  });
1334
1341
  }
1342
+ if (operation.action === "keep") {
1343
+ if (!operation.manifestHash || !operation.desiredHash) {
1344
+ throw new Error(`Invalid keep operation missing manifest/source hash: ${operation.relativeDestPath}`);
1345
+ }
1346
+ return manifestEntryForOperation(operation, {
1347
+ now,
1348
+ hash: operation.manifestHash,
1349
+ sourceHash: operation.desiredHash,
1350
+ graphLockDigest: operation.graphLockDigest ?? context.graphLockDigest
1351
+ });
1352
+ }
1335
1353
  if (operation.action === "remove") {
1336
1354
  await transport.rm(operation.destPath);
1337
1355
  return void 0;
@@ -1377,7 +1395,7 @@ function manifestEntryForOperation(operation, values) {
1377
1395
  channel: operation.channel,
1378
1396
  packageName: operation.packageName,
1379
1397
  semanticCommand: operation.semanticCommand,
1380
- executed: values.executed,
1398
+ executed: values.executed ?? operation.execute,
1381
1399
  mergeStrategy: operation.mergeStrategy,
1382
1400
  composedFrom: operation.composedFrom,
1383
1401
  graphLockDigest: operation.graphLockDigest ?? values.graphLockDigest
@@ -1441,22 +1459,6 @@ function openClawPluginInstallCommand(request) {
1441
1459
  }
1442
1460
 
1443
1461
  // src/install/plan.ts
1444
- async function createInstallPlan(bundle, adapter, targetRoot, manifest, transport = localTransport) {
1445
- const desired = [];
1446
- for (const artifact of bundle.artifacts) {
1447
- const op = operationForArtifact(artifact, adapter, targetRoot, {
1448
- logicalSelector: `${artifact.type}/${artifact.name}`,
1449
- dependencyRole: "root",
1450
- owners: [artifact.packageName ?? bundle.source.packageName ?? bundle.source.source],
1451
- composedFrom: artifact.composedFrom
1452
- });
1453
- if (op) {
1454
- desired.push(op);
1455
- }
1456
- }
1457
- await addProgrammaticOperations(desired, adapter, targetRoot);
1458
- return createPlanFromOperations(desired, adapter, targetRoot, manifest, transport, {});
1459
- }
1460
1462
  async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot, manifest, transport = localTransport, options = {}) {
1461
1463
  for (const artifact of desiredArtifacts) {
1462
1464
  if (artifact.meta.dependencyRole !== "root" && isGuardedMergeTarget(artifact.type)) {
@@ -1938,7 +1940,8 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
1938
1940
  mergeStrategy: entry.mergeStrategy,
1939
1941
  composedFrom: entry.composedFrom,
1940
1942
  ...operationMetadataFromEntry2(entry, remainingOwners),
1941
- graphLockDigest: options.graphLockDigest
1943
+ graphLockDigest: options.graphLockDigest,
1944
+ preserveInManifest: true
1942
1945
  });
1943
1946
  continue;
1944
1947
  }
@@ -2038,7 +2041,7 @@ function formatPlan(plan) {
2038
2041
  const dropped = plan.migrationReport.dropped.length > 0 ? `; dropped unmanaged ${plan.migrationReport.dropped.join(", ")}` : "";
2039
2042
  lines.push(`MIGRATE adopted ${plan.migrationReport.adopted} legacy entries${dropped}`);
2040
2043
  }
2041
- for (const operation of plan.operations) {
2044
+ for (const operation of sortedPlanOperations(plan.operations)) {
2042
2045
  const source = operation.sourcePath ? `${operation.sourcePath} -> ` : "";
2043
2046
  const command = operation.semanticCommand ? ` :: ${operation.semanticCommand.join(" ")}` : "";
2044
2047
  const blocked = operation.blockedReason ? `; ${operation.blockedReason}` : "";
@@ -2050,6 +2053,14 @@ function formatPlan(plan) {
2050
2053
  );
2051
2054
  return lines.join("\n");
2052
2055
  }
2056
+ function sortedPlanOperations(operations) {
2057
+ return [...operations].sort((a, b) => {
2058
+ const destructiveA = a.action === "remove" || a.action === "drift" || a.action === "conflict";
2059
+ const destructiveB = b.action === "remove" || b.action === "drift" || b.action === "conflict";
2060
+ if (destructiveA !== destructiveB) return destructiveA ? -1 : 1;
2061
+ return a.relativeDestPath.localeCompare(b.relativeDestPath);
2062
+ });
2063
+ }
2053
2064
  function formatGraphPlan(result) {
2054
2065
  const lines = [
2055
2066
  ...formatDependencyTree(result.graph),
@@ -2252,7 +2263,17 @@ var packageManifestV2Schema = z5.object({
2252
2263
  runtimes: runtimeListSchema.optional(),
2253
2264
  requires: z5.record(z5.string().min(1), packageDependencySchema).optional(),
2254
2265
  compose: z5.array(packageComposeEntrySchema).optional(),
2255
- provides: z5.array(packageProvideSchema).min(1)
2266
+ provides: z5.array(packageProvideSchema).default([])
2267
+ }).superRefine((manifest, ctx) => {
2268
+ const hasProvides = manifest.provides.length > 0;
2269
+ const hasRequires = Object.keys(manifest.requires ?? {}).length > 0;
2270
+ if (!hasProvides && !hasRequires) {
2271
+ ctx.addIssue({
2272
+ code: z5.ZodIssueCode.custom,
2273
+ path: ["provides"],
2274
+ message: "OpenPack v2 manifest must declare at least one provides entry or one requires dependency"
2275
+ });
2276
+ }
2256
2277
  });
2257
2278
  var packageManifestSchema = z5.union([packageManifestV1Schema, packageManifestV2Schema]);
2258
2279
  var openPackManifestNames = ["openpack.json", "openpack.jsonc"];
@@ -4239,6 +4260,7 @@ async function resolveDependencyGraph(roots, options) {
4239
4260
  requiredBy: `workspace:${rootId}`,
4240
4261
  rootId,
4241
4262
  aliases: root.aliases,
4263
+ useLock: root.useLock ?? options.lockedResolution,
4242
4264
  depth: 0,
4243
4265
  optional: false,
4244
4266
  chain: [`workspace:${rootId}`]
@@ -4295,19 +4317,21 @@ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges
4295
4317
  }
4296
4318
  async function processRequirement(requirement, options, fetchCache, nodesByKey, rootResults, edgeMap) {
4297
4319
  try {
4298
- const normalized = await normalizeDependencySource(requirement.source, {
4320
+ const lockLabel = options.offline ? "Offline" : options.frozenLock ? "Frozen lock" : "Locked install";
4321
+ const lockedByReference = lockedNodeForRequirementReference(requirement, options, lockLabel);
4322
+ const normalized = lockedByReference ? normalizedSourceFromLockedNode(lockedByReference.node) : await normalizeDependencySource(requirement.source, {
4299
4323
  declaringPackageRoot: requirement.declaringPackageRoot,
4300
4324
  workspaceRoot: options.workspaceRoot,
4301
4325
  ref: requirement.ref,
4302
4326
  registryClient: options.registryClient
4303
4327
  });
4304
- const lockLabel = options.offline ? "Offline" : "Frozen lock";
4305
- const frozen = options.frozenLock ? lockedNodeForSource(normalized.normalizedSource, options.previousLock, lockLabel) : void 0;
4328
+ const frozen = lockedByReference ?? lockedNodeForRequirement(normalized.normalizedSource, requirement, options, lockLabel);
4306
4329
  let fetched;
4307
4330
  try {
4308
4331
  fetched = await fetchPackage(normalized, requirement.mode, options, fetchCache, frozen?.requestedRef);
4309
4332
  } catch (error) {
4310
- if (!options.frozenLock) throw error;
4333
+ const usingLockedNode = frozen?.node !== void 0;
4334
+ if (!options.frozenLock && !options.offline && !usingLockedNode) throw error;
4311
4335
  const message = error instanceof Error ? error.message : String(error);
4312
4336
  const label = frozen?.node ? `${frozen.node.id} (${normalized.normalizedSource})` : normalized.normalizedSource;
4313
4337
  throw new Error(`${lockLabel} cache missing or stale for locked graph node:
@@ -4407,7 +4431,7 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
4407
4431
  if (!dependency.select?.length && !(state.fullPackageSelected && dependency.select === void 0)) continue;
4408
4432
  state.processedPackageAliases.add(alias);
4409
4433
  if (!dependencyTargetsRuntime(dependency.runtimes, options.runtime, state.node.id, alias, options.warn)) continue;
4410
- requirements.push(dependencyRequirement(state, fetched, alias, dependency, dependency.select, chain));
4434
+ requirements.push(dependencyRequirement(state, fetched, alias, dependency, dependency.select, chain, options.lockedResolution === true));
4411
4435
  }
4412
4436
  }
4413
4437
  const artifactsBySelector = new Map(fetched.artifacts.map((artifact) => [artifactSelectorKey(artifact), artifact]));
@@ -4437,6 +4461,7 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
4437
4461
  dependency,
4438
4462
  sortedUnique3([...dependency.select ?? [], parsed.selector]),
4439
4463
  chain,
4464
+ options.lockedResolution === true,
4440
4465
  parsed.optional || dependency.optional === true,
4441
4466
  `required by ${parentSelector}`
4442
4467
  ));
@@ -4466,6 +4491,7 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
4466
4491
  dependency,
4467
4492
  sortedUnique3([...dependency.select ?? [], include.selector]),
4468
4493
  chain,
4494
+ options.lockedResolution === true,
4469
4495
  include.optional || dependency.optional === true
4470
4496
  ));
4471
4497
  }
@@ -4475,12 +4501,13 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
4475
4501
  refreshNode(state);
4476
4502
  return requirements;
4477
4503
  }
4478
- function dependencyRequirement(state, fetched, alias, dependency, select, chain, optional = dependency.optional ?? false, selectionReason) {
4504
+ function dependencyRequirement(state, fetched, alias, dependency, select, chain, lockByDefault, optional = dependency.optional ?? false, selectionReason) {
4479
4505
  return {
4480
4506
  source: dependency.source,
4481
4507
  select,
4482
4508
  mode: dependency.mode ?? "pinned",
4483
4509
  ref: dependency.ref,
4510
+ useLock: lockByDefault || dependency.mode !== "tracking",
4484
4511
  declaringPackageRoot: fetched.resolved.resolvedPath,
4485
4512
  requiredBy: state.node.id,
4486
4513
  alias,
@@ -4626,7 +4653,8 @@ function dependencyTargetsRuntime(runtimes, runtime, nodeId, alias, warn) {
4626
4653
  return false;
4627
4654
  }
4628
4655
  async function fetchPackage(normalized, mode, options, fetchCache, refOverride) {
4629
- const key = `${normalized.driver}\0${normalized.normalizedSource}\0${mode}\0${refOverride ?? ""}\0${options.frozenLock ? "frozen" : ""}`;
4656
+ const hardLockedCheckout = options.frozenLock === true || options.offline === true;
4657
+ const key = `${normalized.driver}\0${normalized.normalizedSource}\0${mode}\0${refOverride ?? ""}\0${hardLockedCheckout ? "hard-locked" : "mutable"}`;
4630
4658
  const existing = fetchCache.get(key);
4631
4659
  if (existing) return existing;
4632
4660
  const promise = (async () => {
@@ -4635,7 +4663,7 @@ async function fetchPackage(normalized, mode, options, fetchCache, refOverride)
4635
4663
  cacheRoot: options.cacheRoot ?? join18(options.workspaceRoot, ".agentwheel", "cache"),
4636
4664
  mode,
4637
4665
  ref: refOverride ?? normalized.requestedRef,
4638
- frozenLock: options.frozenLock
4666
+ frozenLock: hardLockedCheckout
4639
4667
  });
4640
4668
  const fetched = await withCachePathLock(resolved.resolvedPath, () => driver.fetch(resolved));
4641
4669
  const translated = await driver.translate(fetched);
@@ -4664,15 +4692,20 @@ async function fetchPackage(normalized, mode, options, fetchCache, refOverride)
4664
4692
  fetchCache.set(key, promise);
4665
4693
  return promise;
4666
4694
  }
4667
- function lockedNodeForSource(normalizedSource, lock, label) {
4668
- if (!lock) {
4669
- throw new Error(`${label} requires an existing graph lock before resolving ${normalizedSource}.`);
4695
+ function lockedNodeForRequirement(normalizedSource, requirement, options, label) {
4696
+ const hard = options.frozenLock === true || options.offline === true;
4697
+ if (!hard && !requirement.useLock) return void 0;
4698
+ if (!options.previousLock) {
4699
+ if (hard) throw new Error(`${label} requires an existing graph lock before resolving ${normalizedSource}.`);
4700
+ return void 0;
4670
4701
  }
4671
- const matches = lock.canonical.nodes.filter((node2) => node2.normalizedSource === normalizedSource);
4702
+ const matches = options.previousLock.canonical.nodes.filter((node2) => node2.normalizedSource === normalizedSource);
4672
4703
  if (matches.length === 0) {
4673
- throw new Error(`${label} cannot resolve new source: ${normalizedSource}. Run without ${label === "Offline" ? "--offline" : "--frozen-lock"} first.`);
4704
+ if (hard) throw new Error(`${label} cannot resolve new source: ${normalizedSource}. Run without ${label === "Offline" ? "--offline" : "--frozen-lock"} first.`);
4705
+ return void 0;
4674
4706
  }
4675
4707
  if (matches.length > 1) {
4708
+ if (!hard) return void 0;
4676
4709
  throw new Error(`${label} has multiple nodes for ${normalizedSource}; cannot choose a cached source deterministically.`);
4677
4710
  }
4678
4711
  const node = matches[0];
@@ -4681,6 +4714,47 @@ function lockedNodeForSource(normalizedSource, lock, label) {
4681
4714
  requestedRef: node.driver === "git" ? node.resolvedCommit ?? node.requestedRef : node.requestedRef
4682
4715
  };
4683
4716
  }
4717
+ function lockedNodeForRequirementReference(requirement, options, label) {
4718
+ const hard = options.frozenLock === true || options.offline === true;
4719
+ if (!hard && !requirement.useLock) return void 0;
4720
+ if (!options.previousLock) {
4721
+ if (hard) throw new Error(`${label} requires an existing graph lock before resolving ${requirement.source}.`);
4722
+ return void 0;
4723
+ }
4724
+ let nodeId;
4725
+ let description;
4726
+ if (requirement.depth === 0 && requirement.rootId) {
4727
+ const root = options.previousLock.canonical.roots.find((candidate) => candidate.rootId === requirement.rootId);
4728
+ nodeId = root?.graphNodeId;
4729
+ description = `root ${requirement.rootId}`;
4730
+ } else if (requirement.parentId && requirement.alias) {
4731
+ const edge = options.previousLock.canonical.edges.find((candidate) => candidate.from === requirement.parentId && candidate.alias === requirement.alias);
4732
+ nodeId = edge?.to;
4733
+ description = `dependency ${requirement.parentId}:${requirement.alias}`;
4734
+ }
4735
+ if (!nodeId) {
4736
+ if (hard && description) {
4737
+ throw new Error(`${label} cannot resolve new locked ${description}. Run without ${label === "Offline" ? "--offline" : "--frozen-lock"} first.`);
4738
+ }
4739
+ return void 0;
4740
+ }
4741
+ const node = options.previousLock.canonical.nodes.find((candidate) => candidate.id === nodeId);
4742
+ if (!node) {
4743
+ throw new Error(`${label} graph lock is missing node ${nodeId} for ${description ?? requirement.source}.`);
4744
+ }
4745
+ return {
4746
+ node,
4747
+ requestedRef: node.driver === "git" ? node.resolvedCommit ?? node.requestedRef : node.requestedRef
4748
+ };
4749
+ }
4750
+ function normalizedSourceFromLockedNode(node) {
4751
+ return {
4752
+ source: node.source,
4753
+ normalizedSource: node.normalizedSource,
4754
+ driver: node.driver,
4755
+ requestedRef: node.requestedRef
4756
+ };
4757
+ }
4684
4758
  function verifyIntegrity(integrity, sourceHash, label) {
4685
4759
  if (!integrity) return;
4686
4760
  const expected = integrity.replace(/^sha256[-:]/i, "");
@@ -5381,26 +5455,6 @@ function defaultTrustStorePath() {
5381
5455
  }
5382
5456
 
5383
5457
  // src/lifecycle/source-plan.ts
5384
- async function createSourcePlan(options) {
5385
- const workspaceRoot = options.workspaceRoot ?? options.targetRoot;
5386
- const lockMode = options.frozenLock === true || options.offline === true;
5387
- const resolvedInput = await resolvePackageSource(options.source, workspaceRoot, { offline: lockMode, warn: options.warn });
5388
- const resolvedSource = resolvedInput.source;
5389
- const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedSource));
5390
- const bundle = await stageSource(driver, resolvedSource, {
5391
- workspaceRoot,
5392
- adapter: options.adapter,
5393
- cacheRoot: join22(workspaceRoot, ".agentwheel", "cache"),
5394
- mode: options.mode,
5395
- frozenLock: lockMode,
5396
- select: options.select,
5397
- skills: options.skills
5398
- });
5399
- const transport = options.transport ?? localTransport;
5400
- const manifest = await readInstallManifest(options.targetRoot, options.adapter.name, transport);
5401
- const plan = await createInstallPlan(bundle, options.adapter, options.targetRoot, manifest, transport);
5402
- return { plan, bundle, resolvedSource, registryEntryName: resolvedInput.registryEntry?.name };
5403
- }
5404
5458
  async function createGraphSourcePlan(options) {
5405
5459
  if (options.roots.length === 0) {
5406
5460
  throw new Error("At least one source is required for a graph plan.");
@@ -5412,14 +5466,14 @@ async function createGraphSourcePlan(options) {
5412
5466
  warnings.push(message);
5413
5467
  options.warn?.(message);
5414
5468
  };
5415
- const recoveredPendingApply = await recoverPendingApplyIfSafe(options.targetRoot, options.adapter.name, transport);
5469
+ const recoveredPendingApply = options.readOnly === true ? false : await recoverPendingApplyIfSafe(options.targetRoot, options.adapter.name, transport);
5416
5470
  const workspaceConfig = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: options.globalRoot });
5417
5471
  const trustPolicy = {
5418
5472
  ...normalizeTrustPolicy(workspaceConfig.trust),
5419
5473
  acceptedSources: await readTrustedSources(workspaceRoot, options.trustStorePath)
5420
5474
  };
5421
5475
  const lockMode = options.frozenLock === true || options.offline === true;
5422
- const lockLabel = options.offline === true ? "Offline" : "Frozen lock";
5476
+ const lockLabel = options.offline === true ? "Offline" : options.frozenLock === true ? "Frozen lock" : options.lockedResolution === true ? "Locked install" : "Fresh resolve";
5423
5477
  const targetFingerprint = computeTargetFingerprint(options.targetFingerprintParts ?? {
5424
5478
  adapter: options.adapter.name,
5425
5479
  targetRoot: options.targetRoot,
@@ -5434,6 +5488,7 @@ async function createGraphSourcePlan(options) {
5434
5488
  cacheRoot: join22(workspaceRoot, ".agentwheel", "cache"),
5435
5489
  registryClient,
5436
5490
  noDeps: options.noDeps,
5491
+ lockedResolution: options.lockedResolution,
5437
5492
  frozenLock: lockMode,
5438
5493
  offline: options.offline,
5439
5494
  previousLock,
@@ -5444,7 +5499,7 @@ async function createGraphSourcePlan(options) {
5444
5499
  assertTrustArtifactPolicy(graph, trustPolicy);
5445
5500
  const trustEvaluation = evaluateTransitiveTrust(graph, previousLock, trustPolicy, options.trustPatterns ?? [], options.yes === true);
5446
5501
  await assertTrusted(trustEvaluation.promptSources, options);
5447
- const persistedTrustSources = await rememberTrustedSources(workspaceRoot, trustEvaluation.persistSources, options.trustStorePath);
5502
+ const persistedTrustSources = options.readOnly === true ? [] : await rememberTrustedSources(workspaceRoot, trustEvaluation.persistSources, options.trustStorePath);
5448
5503
  for (const source of persistedTrustSources) warn(`remembered trusted transitive source: ${source}`);
5449
5504
  const bundle = await renderGraphForTarget(graph, {
5450
5505
  workspaceRoot,
@@ -5542,6 +5597,10 @@ ${mismatches.map((item) => `- ${item}`).join("\n")}`);
5542
5597
  }
5543
5598
  async function assertTrusted(sources, options) {
5544
5599
  if (sources.length === 0) return;
5600
+ if (options.readOnly === true) {
5601
+ throw new Error(`New transitive sources require trust. Re-run with --yes or --trust <pattern>:
5602
+ ${sources.map((source) => `- ${source}`).join("\n")}`);
5603
+ }
5545
5604
  if (options.promptTrust) {
5546
5605
  if (await options.promptTrust(sources)) return;
5547
5606
  throw new Error(`Untrusted transitive sources:
@@ -5609,10 +5668,12 @@ async function syncProfile(options) {
5609
5668
  transport: target.transport.kind
5610
5669
  },
5611
5670
  noDeps: options.noDeps,
5671
+ lockedResolution: options.lockedResolution,
5612
5672
  frozenLock: options.frozenLock,
5613
5673
  offline: options.offline,
5614
5674
  yes: options.yes,
5615
5675
  trustPatterns: options.trustPatterns ?? [],
5676
+ readOnly: options.readOnly,
5616
5677
  isTTY: options.isTTY,
5617
5678
  warn: options.warn
5618
5679
  });
@@ -6023,10 +6084,38 @@ function updateSchemaVersion(content) {
6023
6084
  return applyEdits(content, edits);
6024
6085
  }
6025
6086
 
6087
+ // src/cli/version.ts
6088
+ import { readFileSync } from "fs";
6089
+ import { dirname as dirname20, join as join27 } from "path";
6090
+ import { fileURLToPath as fileURLToPath2 } from "url";
6091
+ var FALLBACK_VERSION = "0.0.0";
6092
+ function resolveCliVersion() {
6093
+ let dir = dirname20(fileURLToPath2(import.meta.url));
6094
+ while (true) {
6095
+ try {
6096
+ const pkg = JSON.parse(readFileSync(join27(dir, "package.json"), "utf8"));
6097
+ if (pkg.name === "agentwheel" && typeof pkg.version === "string") {
6098
+ return pkg.version;
6099
+ }
6100
+ } catch {
6101
+ }
6102
+ const parent = dirname20(dir);
6103
+ if (parent === dir) return FALLBACK_VERSION;
6104
+ dir = parent;
6105
+ }
6106
+ }
6107
+
6026
6108
  // src/cli/index.ts
6109
+ var CLI_VERSION = resolveCliVersion();
6027
6110
  var program = new Command();
6028
- program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version("0.7.0").option("--no-update-check", "disable npm version update check", false);
6029
- program.command("init").argument("[kind]", "workspace or package", "workspace").option("--target-root <path>", "workspace root", process.cwd()).option("--fleet-example", "scaffold example agents and profiles in workspace config", false).action(async (kind, options) => {
6111
+ program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version(CLI_VERSION).showSuggestionAfterError(false).option("--no-update-check", "disable npm version update check", false).addHelpText("after", `
6112
+
6113
+ Core flow:
6114
+ $ agentwheel add github:org/agent-pack --adapter codex
6115
+ $ agentwheel plan
6116
+ $ agentwheel install
6117
+ `);
6118
+ program.command("init").description("initialize an agentwheel workspace or package").argument("[kind]", "workspace or package", "workspace").option("--target-root <path>", "workspace root", process.cwd()).option("--fleet-example", "scaffold example agents and profiles in workspace config", false).action(async (kind, options) => {
6030
6119
  const root = normalizeTargetRoot(options.targetRoot);
6031
6120
  if (kind === "package") {
6032
6121
  await initPackage(root);
@@ -6040,63 +6129,32 @@ program.command("init").argument("[kind]", "workspace or package", "workspace").
6040
6129
  const bootstrapPackage = config.bootstrapSkills === false ? void 0 : await defaultBootstrapPackage(root);
6041
6130
  const withBootstrap = bootstrapPackage ? upsertPackage(config, bootstrapPackage) : config;
6042
6131
  await writeWorkspaceConfig(root, options.fleetExample ? withFleetExample(withBootstrap) : withBootstrap);
6043
- console.log("Initialized .agentwheel/config.json.");
6132
+ console.log(`Initialized ${workspaceConfigPath(root)}.`);
6133
+ if (bootstrapPackage) console.log("Auto-added the agentwheel bootstrap skill for openclaw.");
6134
+ console.log(nextInstallNudge());
6044
6135
  });
6045
- program.command("add").argument("<source>", "package source").option("--driver <driver>", "source driver (local, git, skillkit, or vercel-skills)").option("--adapter <adapter>", "built-in adapter", "openclaw").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "workspace root", process.cwd()).option("--mode <mode>", "pinned or tracking", "pinned").option("--name <name>", "package alias").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).action(async (source, options) => {
6136
+ 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("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "workspace root", process.cwd()).option("--mode <mode>", "pinned or tracking", "pinned").option("--name <name>", "package alias").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).action(async (source, options) => {
6046
6137
  const targetRoot = normalizeTargetRoot(options.targetRoot);
6047
- const selectedArtifacts = selectedArtifactsFromOptions(options);
6048
- const resolvedInput = await resolvePackageSource(source, targetRoot);
6049
- const resolvedSource = resolvedInput.source;
6050
- const driverName = options.driver ?? inferSourceDriverName(resolvedSource);
6051
- const driver = getSourceDriver(driverName);
6052
- const adapter = await resolveAdapter({
6053
- adapter: options.adapter,
6054
- adapterConfig: options.adapterConfig,
6055
- adapterModule: options.adapterModule,
6056
- allowAdapterCode: options.allowAdapterCode,
6057
- baseDir: targetRoot,
6058
- warn: (message) => console.warn(message)
6059
- });
6060
- const bundle = await stageSource(driver, resolvedSource, {
6061
- workspaceRoot: targetRoot,
6062
- adapter,
6063
- cacheRoot: join27(targetRoot, ".agentwheel", "cache"),
6064
- mode: options.mode,
6065
- select: selectedArtifacts
6066
- });
6067
- const name = options.name ?? resolvedInput.registryEntry?.name ?? bundle.source.packageName ?? source;
6068
- const entry = {
6069
- name,
6070
- source: resolvedSource,
6071
- driver: driverName,
6072
- adapter: adapter.name,
6073
- adapterConfig: options.adapterConfig,
6074
- adapterModule: options.adapterModule,
6075
- adapterCodeHash: adapter.programmatic?.hash,
6076
- mode: options.mode,
6077
- requestedRef: bundle.source.requestedRef,
6078
- select: selectedArtifacts
6079
- };
6138
+ const entry = await packageEntryFromSource(source, targetRoot, options);
6080
6139
  await writeWorkspaceConfig(targetRoot, upsertPackage(await readWorkspaceConfig(targetRoot), entry));
6081
- await rm9(bundle.root, { recursive: true, force: true });
6082
- console.log(`Added ${name}.`);
6140
+ console.log(`Added ${entry.name}. Preview: agentwheel plan - Apply: agentwheel install`);
6083
6141
  });
6084
- program.command("list").argument("<source>", "package source").option("--driver <driver>", "source driver").option("--target-root <path>", "workspace root", process.cwd()).option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).action(async (source, options) => {
6142
+ program.command("list").description("list artifacts exposed by a package source").argument("<source>", "package source").option("--driver <driver>", "source driver").option("--target-root <path>", "workspace root", process.cwd()).option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).action(async (source, options) => {
6085
6143
  const targetRoot = normalizeTargetRoot(options.targetRoot);
6086
6144
  const selectedArtifacts = selectedArtifactsFromOptions(options);
6087
6145
  const resolvedInput = await resolvePackageSource(source, targetRoot);
6088
6146
  const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
6089
- const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join27(targetRoot, ".agentwheel", "cache") }))));
6147
+ const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join28(targetRoot, ".agentwheel", "cache") }))));
6090
6148
  const artifacts = filterArtifactsBySelection(await driver.list(resolved), selectedArtifacts);
6091
6149
  for (const artifact of artifacts) {
6092
6150
  console.log(`${artifact.type} ${artifact.name} ${artifact.relativePath}`);
6093
6151
  }
6094
6152
  });
6095
- program.command("scan").argument("<source>", "package source").option("--driver <driver>", "source driver").option("--target-root <path>", "workspace root", process.cwd()).action(async (source, options) => {
6153
+ program.command("scan").description("scan a package source for validation findings").argument("<source>", "package source").option("--driver <driver>", "source driver").option("--target-root <path>", "workspace root", process.cwd()).action(async (source, options) => {
6096
6154
  const targetRoot = normalizeTargetRoot(options.targetRoot);
6097
6155
  const resolvedInput = await resolvePackageSource(source, targetRoot);
6098
6156
  const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
6099
- const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join27(targetRoot, ".agentwheel", "cache") }))));
6157
+ const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join28(targetRoot, ".agentwheel", "cache") }))));
6100
6158
  const result = await driver.scan(resolved);
6101
6159
  if (result.findings.length === 0) {
6102
6160
  console.log("Scan ok: no findings");
@@ -6107,99 +6165,28 @@ program.command("scan").argument("<source>", "package source").option("--driver
6107
6165
  }
6108
6166
  if (!result.ok) process.exitCode = 1;
6109
6167
  });
6110
- program.command("plan").argument("[source]", "source directory").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning", false).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) => {
6111
- const targets = await resolveCliTargets(options);
6112
- for (const target of targets) {
6113
- if (source && options.noDeps && options.onlySource) {
6114
- const { plan, bundle } = await buildPlan(source, target, options);
6115
- console.log(formatPlan(plan));
6116
- await rm9(bundle.root, { recursive: true, force: true });
6117
- if (plan.hasBlockingChanges) process.exitCode = 1;
6118
- continue;
6119
- }
6120
- for (const result of await buildGraphPlansForTarget(target, source, options, { useUpdateDecision: false })) {
6121
- console.log(formatGraphPlan(result));
6122
- await rm9(result.bundle.root, { recursive: true, force: true });
6123
- if (result.plan.hasBlockingChanges) process.exitCode = 1;
6124
- }
6125
- }
6168
+ 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").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).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) => {
6169
+ await runInstallCommand(source, { ...options, dryRun: true }, { apply: false });
6126
6170
  });
6127
- program.command("sync").argument("[source]", "source directory").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning", false).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) => {
6128
- if (options.profile) {
6129
- const target = await resolveRuntimeTarget({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent });
6130
- const results = await syncProfile({
6131
- workspaceRoot: target.workspaceRoot,
6132
- profile: options.profile,
6133
- source,
6134
- driver: options.driver,
6135
- mode: options.mode,
6136
- select: selectedArtifactsFromOptions(options),
6137
- dryRun: options.dryRun,
6138
- executePlugins: options.executePlugins,
6139
- allowAdapterCode: options.allowAdapterCode,
6140
- noDeps: options.noDeps,
6141
- frozenLock: options.frozenLock,
6142
- offline: options.offline,
6143
- yes: options.yes,
6144
- trustPatterns: options.trust ?? [],
6145
- isTTY: process.stdin.isTTY === true,
6146
- warn: (message) => console.warn(message)
6147
- });
6148
- for (const result of results) {
6149
- console.log(`Profile ${options.profile} / ${result.runtime} / ${result.packageName} at ${result.targetRoot} (${result.transport}):`);
6150
- console.log(formatPlan(result.plan));
6151
- if (result.plan.hasBlockingChanges) process.exitCode = 1;
6152
- }
6153
- if (!options.dryRun) console.log("Applied.");
6154
- return;
6155
- }
6156
- const targets = await resolveCliTargets(options);
6157
- if (!source) {
6158
- for (const target of targets) {
6159
- await runConfiguredGraphPackages(target, options, { useUpdateDecision: false });
6160
- }
6161
- return;
6162
- }
6163
- for (const target of targets) {
6164
- if (options.noDeps && options.onlySource) {
6165
- const { plan, bundle } = await buildPlan(source, target, options);
6166
- console.log(formatPlan(plan));
6167
- if (!options.dryRun) {
6168
- await applyInstallPlan(plan, bundle.sourceLock, { executePlugins: options.executePlugins, transport: transportForTarget(target) });
6169
- console.log(`Applied ${target.adapter} at ${target.targetRoot}.`);
6170
- }
6171
- await rm9(bundle.root, { recursive: true, force: true });
6172
- if (plan.hasBlockingChanges) process.exitCode = 1;
6173
- continue;
6174
- }
6175
- for (const result of await buildGraphPlansForTarget(target, source, options, { useUpdateDecision: false })) {
6176
- console.log(formatGraphPlan(result));
6177
- if (!options.dryRun) {
6178
- await applyCombinedInstallPlan(result.plan, {
6179
- executePlugins: options.executePlugins,
6180
- transport: transportForTarget(target),
6181
- graphLockDigest: result.graphLockDigest,
6182
- graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
6183
- });
6184
- console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
6185
- }
6186
- await rm9(result.bundle.root, { recursive: true, force: true });
6187
- if (result.plan.hasBlockingChanges) process.exitCode = 1;
6188
- }
6189
- }
6171
+ 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").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).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) => {
6172
+ await runInstallCommand(source, options, { apply: !options.dryRun });
6190
6173
  });
6191
- program.command("update").option("--adapter <adapter>", "built-in adapter").option("--target-root <path>", "workspace root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show plans without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--allow-adapter-code", "allow loading local adapter code from configured packages", false).option("--select <type/name>", "temporarily select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "temporarily select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--no-deps", "resolve only root sources and ignore requires with a warning", 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 (options) => {
6174
+ 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").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).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) => {
6175
+ console.error("warning: 'agentwheel sync' is deprecated and will be removed in 0.10. Use 'agentwheel install'.");
6176
+ await runInstallCommand(source, options, { apply: !options.dryRun });
6177
+ });
6178
+ 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").option("--target-root <path>", "workspace root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show plans without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--allow-adapter-code", "allow loading local adapter code from configured packages", false).option("--select <type/name>", "temporarily select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "temporarily select a skill by name (repeatable or comma-separated)", collectSkillOption, []).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) => {
6192
6179
  const targets = await resolveCliTargets(options);
6193
6180
  for (const target of targets) {
6194
- await runConfiguredGraphPackages(target, options, { useUpdateDecision: true });
6181
+ await runConfiguredGraphPackages(target, { ...options, scope: name }, { mode: "update" });
6195
6182
  }
6196
6183
  });
6197
6184
  program.command("deps").description("inspect the OpenPack dependency graph").addCommand(
6198
- new Command("tree").argument("[source]", "optional source directory to resolve").option("--adapter <adapter>", "built-in adapter").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--no-deps", "resolve only root sources and ignore requires with a warning", 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) => {
6185
+ new Command("tree").description("print the OpenPack dependency graph").argument("[source]", "optional package source to resolve").option("--adapter <adapter>", "built-in adapter").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--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) => {
6199
6186
  const targets = await resolveCliTargets(options);
6200
6187
  for (const target of targets) {
6201
6188
  if (source) {
6202
- for (const result of await buildGraphPlansForTarget(target, source, options, { useUpdateDecision: false })) {
6189
+ for (const result of await buildGraphPlansForTarget(target, source, options, { mode: "install" })) {
6203
6190
  console.log(formatDependencyTree(result.graph).join("\n"));
6204
6191
  for (const decision of result.bundle.graphLock.canonical.namespacing) {
6205
6192
  console.log(`NAMESPACE ${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`);
@@ -6213,7 +6200,7 @@ program.command("deps").description("inspect the OpenPack dependency graph").add
6213
6200
  }
6214
6201
  })
6215
6202
  ).addCommand(
6216
- new Command("why").argument("<selector>", "installed path, type/installName, or graphNodeId:type/name").option("--adapter <adapter>", "built-in adapter").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).action(async (selector, options) => {
6203
+ new Command("why").description("explain why an artifact is installed").argument("<selector>", "installed path, type/installName, or graphNodeId:type/name").option("--adapter <adapter>", "built-in adapter").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).action(async (selector, options) => {
6217
6204
  const targets = await resolveCliTargets(options);
6218
6205
  for (const target of targets) {
6219
6206
  const { lock, adapter } = await readTargetGraphLock(target, options);
@@ -6240,7 +6227,7 @@ program.command("registry").description("manage optional registry indexes").addC
6240
6227
  })
6241
6228
  );
6242
6229
  program.command("trust").description("manage persisted source trust decisions").addCommand(
6243
- new Command("forget").argument("<pattern>", "trusted source glob to revoke").option("--target-root <path>", "workspace root", process.cwd()).action(async (pattern, options) => {
6230
+ new Command("forget").description("forget a persisted trusted source pattern").argument("<pattern>", "trusted source glob to revoke").option("--target-root <path>", "workspace root", process.cwd()).action(async (pattern, options) => {
6244
6231
  const removed = await forgetTrustedSources(normalizeTargetRoot(options.targetRoot), pattern);
6245
6232
  if (removed.length === 0) {
6246
6233
  console.log(`No persisted trust matched ${pattern}.`);
@@ -6267,23 +6254,31 @@ program.command("package").description("validate and migrate OpenPack packages")
6267
6254
  console.log(result.message);
6268
6255
  })
6269
6256
  );
6270
- program.command("remember").requiredOption("--runtime <runtime>", "runtime/adapter name").option("--target-root <path>", "workspace root", process.cwd()).argument("<text>", "text to append to the local instructions overlay").action(async (text, options) => {
6257
+ program.command("remember").description("append text to the local instructions overlay").requiredOption("--runtime <runtime>", "runtime/adapter name").option("--target-root <path>", "workspace root", process.cwd()).argument("<text>", "text to append to the local instructions overlay").action(async (text, options) => {
6271
6258
  const targetRoot = normalizeTargetRoot(options.targetRoot);
6272
6259
  const result = await remember(targetRoot, options.runtime, text);
6273
- console.log(`Remembered in ${result.overlayPath}. Run: agentwheel sync <source> --adapter ${options.runtime}`);
6260
+ console.log(`Remembered in ${result.overlayPath}.`);
6261
+ console.log(nextInstallNudge());
6274
6262
  });
6275
- program.command("eject").argument("<item>", "package/type/name").option("--target-root <path>", "workspace root", process.cwd()).action(async (item, options) => {
6263
+ program.command("eject").description("copy a managed artifact into local ownership").argument("<item>", "package/type/name").option("--target-root <path>", "workspace root", process.cwd()).action(async (item, options) => {
6276
6264
  const targetRoot = normalizeTargetRoot(options.targetRoot);
6277
6265
  const result = await ejectArtifact(targetRoot, item);
6278
6266
  console.log(`Ejected ${item} to ${result.ejectedPath}.`);
6267
+ console.log(nextInstallNudge());
6279
6268
  });
6280
- program.command("uninstall").argument("[package]", "configured package name or source to remove from the ownership graph").option("--adapter <adapter>", "adapter").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show removals without writing", false).option("--force", "remove drifted managed files too", false).option("--select <type/name>", "uninstall only selected artifact type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "uninstall only selected skill name (repeatable or comma-separated)", collectSkillOption, []).option("--frozen-lock", "resolve remaining packages strictly from the existing graph lock and cached sources", false).option("--offline", "resolve remaining packages strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources while resolving remaining packages", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (packageName, options) => {
6269
+ program.command("uninstall").description("remove configured packages or managed runtime files").argument("[package]", "configured package name or source to remove from the ownership graph").option("--adapter <adapter>", "adapter").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show removals without writing", false).option("--force", "remove drifted managed files too", false).option("--keep-files", "remove from config and manifest but leave runtime files unmanaged", false).option("--select <type/name>", "uninstall only selected artifact type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "uninstall only selected skill name (repeatable or comma-separated)", collectSkillOption, []).option("--frozen-lock", "resolve remaining packages strictly from the existing graph lock and cached sources", false).option("--offline", "resolve remaining packages strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources while resolving remaining packages", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (packageName, options) => {
6270
+ if (options.keepFiles && options.force) {
6271
+ throw new Error("--keep-files cannot be combined with --force.");
6272
+ }
6281
6273
  const targets = await resolveCliTargets(options);
6282
6274
  for (const target of targets) {
6283
6275
  if (packageName) {
6284
6276
  await uninstallConfiguredPackage(target, packageName, options);
6285
6277
  continue;
6286
6278
  }
6279
+ if (options.keepFiles) {
6280
+ throw new Error("--keep-files requires a configured package name or source.");
6281
+ }
6287
6282
  const adapter = await resolveAdapterForTarget(target, options);
6288
6283
  const transport = transportForTarget(target);
6289
6284
  const manifest = await readInstallManifest(target.targetRoot, adapter.name, transport);
@@ -6304,23 +6299,138 @@ program.command("uninstall").argument("[package]", "configured package name or s
6304
6299
  if (plan.hasBlockingChanges) process.exitCode = 1;
6305
6300
  }
6306
6301
  });
6307
- async function buildPlan(source, target, options) {
6308
- const adapter = await resolveAdapterForTarget(target, options);
6309
- const transport = transportForTarget(target);
6310
- const result = await createSourcePlan({
6311
- source,
6312
- targetRoot: target.targetRoot,
6313
- workspaceRoot: target.workspaceRoot,
6302
+ program.command("status").description("show configured packages and runtime install state").option("--adapter <adapter>", "built-in adapter").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).action(async (options) => {
6303
+ const targets = await resolveCliTargets(options);
6304
+ for (const target of targets) {
6305
+ await printStatus(target, options);
6306
+ }
6307
+ });
6308
+ async function runInstallCommand(nameOrSource, options, behavior) {
6309
+ if (options.profile) {
6310
+ const target = await resolveRuntimeTarget({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent });
6311
+ const results = await syncProfile({
6312
+ workspaceRoot: target.workspaceRoot,
6313
+ profile: options.profile,
6314
+ source: nameOrSource,
6315
+ driver: options.driver,
6316
+ mode: options.mode,
6317
+ select: selectedArtifactsFromOptions(options),
6318
+ dryRun: !behavior.apply,
6319
+ executePlugins: options.executePlugins,
6320
+ allowAdapterCode: options.allowAdapterCode,
6321
+ noDeps: noDepsFromOptions(options),
6322
+ lockedResolution: true,
6323
+ frozenLock: options.frozenLock,
6324
+ offline: options.offline,
6325
+ yes: options.yes,
6326
+ trustPatterns: options.trust ?? [],
6327
+ readOnly: !behavior.apply,
6328
+ isTTY: process.stdin.isTTY === true,
6329
+ warn: (message) => console.warn(message)
6330
+ });
6331
+ for (const result of results) {
6332
+ console.log(`Profile ${options.profile} / ${result.runtime} / ${result.packageName} at ${result.targetRoot} (${result.transport}):`);
6333
+ console.log(formatPlan(result.plan));
6334
+ if (result.plan.hasBlockingChanges) process.exitCode = 1;
6335
+ }
6336
+ if (behavior.apply) console.log("Applied.");
6337
+ return;
6338
+ }
6339
+ const targets = await resolveCliTargets(options);
6340
+ for (const target of targets) {
6341
+ const config = await readMergedWorkspaceConfig(target.workspaceRoot);
6342
+ const configured = nameOrSource ? findConfiguredPackage(config.packages, nameOrSource) : void 0;
6343
+ let source;
6344
+ let scope = configured?.name;
6345
+ let extraPackage;
6346
+ if (nameOrSource && !configured) {
6347
+ try {
6348
+ const entry = await packageEntryFromSource(nameOrSource, target.workspaceRoot, options);
6349
+ scope = entry.name;
6350
+ if (behavior.apply) {
6351
+ await writeWorkspaceConfig(target.workspaceRoot, upsertPackage(await readWorkspaceConfig(target.workspaceRoot), entry));
6352
+ } else {
6353
+ source = nameOrSource;
6354
+ extraPackage = entry;
6355
+ }
6356
+ } catch (error) {
6357
+ throw teachingInstallError(nameOrSource, error);
6358
+ }
6359
+ }
6360
+ for (const result of await buildGraphPlansForTarget(target, source, { ...options, scope, extraPackage }, { mode: "install" })) {
6361
+ console.log(formatGraphPlan(result));
6362
+ if (behavior.apply) {
6363
+ await applyCombinedInstallPlan(result.plan, {
6364
+ executePlugins: options.executePlugins,
6365
+ transport: transportForTarget(target),
6366
+ graphLockDigest: result.graphLockDigest,
6367
+ graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
6368
+ });
6369
+ console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
6370
+ }
6371
+ await rm9(result.bundle.root, { recursive: true, force: true });
6372
+ if (result.plan.hasBlockingChanges) process.exitCode = 1;
6373
+ }
6374
+ }
6375
+ }
6376
+ async function packageEntryFromSource(source, targetRoot, options) {
6377
+ const selectedArtifacts = selectedArtifactsFromOptions(options);
6378
+ const lockMode = options.frozenLock === true || options.offline === true;
6379
+ const resolvedInput = await resolvePackageSource(source, targetRoot, { offline: lockMode });
6380
+ const resolvedSource = resolvedInput.source;
6381
+ const driverName = options.driver ?? inferSourceDriverName(resolvedSource);
6382
+ const driver = getSourceDriver(driverName);
6383
+ const adapter = await resolveAdapter({
6384
+ adapter: options.adapter ?? "openclaw",
6385
+ adapterConfig: options.adapterConfig,
6386
+ adapterModule: options.adapterModule,
6387
+ allowAdapterCode: options.allowAdapterCode,
6388
+ baseDir: targetRoot,
6389
+ warn: (message) => console.warn(message)
6390
+ });
6391
+ const bundle = await stageSource(driver, resolvedSource, {
6392
+ workspaceRoot: targetRoot,
6314
6393
  adapter,
6315
- driver: options.driver,
6394
+ cacheRoot: join28(targetRoot, ".agentwheel", "cache"),
6316
6395
  mode: options.mode,
6317
- select: selectedArtifactsFromOptions(options),
6318
- frozenLock: options.frozenLock,
6319
- offline: options.offline,
6320
- warn: (message) => console.warn(message),
6321
- transport
6396
+ frozenLock: lockMode,
6397
+ select: selectedArtifacts
6322
6398
  });
6323
- return { plan: result.plan, bundle: result.bundle };
6399
+ try {
6400
+ return {
6401
+ name: options.name ?? resolvedInput.registryEntry?.name ?? bundle.source.packageName ?? source,
6402
+ source: resolvedSource,
6403
+ driver: driverName,
6404
+ adapter: adapter.name,
6405
+ adapterConfig: options.adapterConfig,
6406
+ adapterModule: options.adapterModule,
6407
+ adapterCodeHash: adapter.programmatic?.hash,
6408
+ mode: options.mode ?? "pinned",
6409
+ requestedRef: bundle.source.requestedRef,
6410
+ select: selectedArtifacts
6411
+ };
6412
+ } finally {
6413
+ await rm9(bundle.root, { recursive: true, force: true });
6414
+ }
6415
+ }
6416
+ function findConfiguredPackage(packages, value) {
6417
+ return packages.find((pkg) => pkg.name === value || pkg.source === value);
6418
+ }
6419
+ function noDepsFromOptions(options) {
6420
+ return options.noDeps === true || options.deps === false;
6421
+ }
6422
+ function teachingInstallError(input, cause) {
6423
+ const message = cause instanceof Error ? cause.message : String(cause);
6424
+ return new Error(
6425
+ `'${input}' is not a configured package and could not be resolved as a source.
6426
+ To add and install a new package: agentwheel install <source> (e.g. github:org/pack)
6427
+ To see what's configured: agentwheel status
6428
+
6429
+ Resolver error: ${message}`
6430
+ );
6431
+ }
6432
+ function nextInstallNudge() {
6433
+ return "Preview: agentwheel plan - Apply: agentwheel install";
6324
6434
  }
6325
6435
  async function resolveCliTargets(options) {
6326
6436
  if (options.all) {
@@ -6341,7 +6451,7 @@ async function resolveAdapterForTarget(target, options) {
6341
6451
  async function runConfiguredGraphPackages(target, options, behavior) {
6342
6452
  const results = await buildGraphPlansForTarget(target, void 0, options, behavior);
6343
6453
  for (const result of results) {
6344
- console.log(`${behavior.useUpdateDecision ? "Update" : "Sync"} ${result.plan.adapter} at ${result.plan.targetRoot}:`);
6454
+ console.log(`${behavior.mode === "update" ? "Update" : "Install"} ${result.plan.adapter} at ${result.plan.targetRoot}:`);
6345
6455
  console.log(formatGraphPlan(result));
6346
6456
  if (!options.dryRun) {
6347
6457
  await applyCombinedInstallPlan(result.plan, {
@@ -6356,10 +6466,13 @@ async function runConfiguredGraphPackages(target, options, behavior) {
6356
6466
  if (result.plan.hasBlockingChanges) process.exitCode = 1;
6357
6467
  }
6358
6468
  }
6359
- async function buildGraphPlansForTarget(target, source, options, _behavior) {
6469
+ async function buildGraphPlansForTarget(target, source, options, behavior) {
6360
6470
  const config = await readMergedWorkspaceConfig(target.workspaceRoot);
6361
6471
  const groups = /* @__PURE__ */ new Map();
6362
6472
  const selectedArtifacts = selectedArtifactsFromOptions(options);
6473
+ const scopedPackage = options.scope ? findConfiguredPackage(config.packages, options.scope) : void 0;
6474
+ const scopedRootId = scopedPackage?.name ?? (source ? options.scope : void 0);
6475
+ if (options.scope && !scopedPackage && !source) throw new Error(`Configured package not found: ${options.scope}`);
6363
6476
  if (!source || !options.onlySource) {
6364
6477
  for (const pkg of config.packages) {
6365
6478
  const group = graphGroupForPackage(groups, target, pkg, options);
@@ -6381,14 +6494,11 @@ async function buildGraphPlansForTarget(target, source, options, _behavior) {
6381
6494
  allowAdapterCode: options.allowAdapterCode
6382
6495
  },
6383
6496
  packages: [],
6384
- extraRoots: []
6497
+ extraRoots: [],
6498
+ extraPackages: []
6385
6499
  };
6386
- group.extraRoots.push({
6387
- rootId: `source:${source}`,
6388
- source,
6389
- mode: options.mode ?? "pinned",
6390
- select: selectedArtifacts
6391
- });
6500
+ const entry = options.extraPackage ?? await packageEntryFromSource(source, target.workspaceRoot, options);
6501
+ group.extraPackages.push(entry);
6392
6502
  groups.set(key, group);
6393
6503
  }
6394
6504
  if (groups.size === 0) {
@@ -6398,36 +6508,160 @@ async function buildGraphPlansForTarget(target, source, options, _behavior) {
6398
6508
  const results = [];
6399
6509
  for (const group of groups.values()) {
6400
6510
  const adapter = await resolveAdapterForTarget(group.target, group.adapterOptions);
6511
+ const transport = transportForTarget(group.target);
6512
+ const allPackages = [...group.packages, ...group.extraPackages];
6513
+ const groupHasScope = !scopedRootId || allPackages.some((pkg) => pkg.name === scopedRootId || pkg.source === options.scope);
6514
+ if (behavior.mode === "install" && scopedRootId && !groupHasScope) continue;
6515
+ const updateScope = behavior.mode === "update" ? scopedPackage ? /* @__PURE__ */ new Set([scopedPackage.name]) : void 0 : void 0;
6401
6516
  const roots = [
6402
- ...group.packages.map((pkg) => ({
6403
- rootId: pkg.name,
6404
- source: pkg.source,
6405
- mode: pkg.mode,
6406
- ref: pkg.requestedRef,
6407
- select: selectedArtifacts ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
6408
- aliases: pkg.aliases
6409
- })),
6517
+ ...allPackages.map((pkg) => {
6518
+ const updateThisPackage = behavior.mode === "update" && pkg.mode === "tracking" && (!updateScope || updateScope.has(pkg.name) || updateScope.has(pkg.source));
6519
+ return {
6520
+ rootId: pkg.name,
6521
+ source: pkg.source,
6522
+ mode: pkg.mode,
6523
+ ref: pkg.requestedRef,
6524
+ select: selectedArtifacts ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
6525
+ aliases: pkg.aliases,
6526
+ useLock: behavior.mode === "install" ? true : !updateThisPackage
6527
+ };
6528
+ }),
6410
6529
  ...group.extraRoots
6411
6530
  ];
6531
+ if (behavior.mode === "update") {
6532
+ const changed = roots.filter((root) => root.useLock === false);
6533
+ if (changed.length === 0) {
6534
+ const label = options.scope ? ` ${options.scope}` : "";
6535
+ console.log(`No tracking packages to update${label}.`);
6536
+ continue;
6537
+ }
6538
+ }
6412
6539
  if (roots.length === 0) continue;
6413
- results.push(await createGraphSourcePlan({
6540
+ const result = await createGraphSourcePlan({
6414
6541
  roots,
6415
6542
  targetRoot: group.target.targetRoot,
6416
6543
  workspaceRoot: group.target.workspaceRoot,
6417
6544
  adapter,
6418
- transport: transportForTarget(group.target),
6545
+ transport,
6419
6546
  targetKey: group.target.agentName ?? group.target.source,
6420
6547
  targetFingerprintParts: targetFingerprintParts(group.target, adapter, group.adapterOptions),
6421
- noDeps: options.noDeps,
6548
+ noDeps: noDepsFromOptions(options),
6549
+ lockedResolution: behavior.mode === "install",
6422
6550
  frozenLock: options.frozenLock,
6423
6551
  offline: options.offline,
6424
6552
  yes: options.yes,
6425
6553
  trustPatterns: options.trust ?? [],
6554
+ readOnly: options.dryRun === true,
6426
6555
  isTTY: process.stdin.isTTY === true
6427
- }));
6556
+ });
6557
+ if (behavior.mode === "install" && scopedRootId) {
6558
+ const manifest = await readInstallManifest(group.target.targetRoot, adapter.name, transport);
6559
+ results.push(scopeInstallPlanToRoot(result, scopedRootId, manifest));
6560
+ } else {
6561
+ results.push(result);
6562
+ }
6428
6563
  }
6429
6564
  return results;
6430
6565
  }
6566
+ function scopeInstallPlanToRoot(result, rootId, manifest) {
6567
+ const scopedOwners = scopedGraphOwnerKeys(result, rootId);
6568
+ const manifestByPath = new Map((manifest?.entries ?? []).map((entry) => [entry.path, entry]));
6569
+ const preservedPaths = /* @__PURE__ */ new Set();
6570
+ const plannedPaths = /* @__PURE__ */ new Set();
6571
+ const operations = [];
6572
+ for (const operation of result.plan.operations) {
6573
+ plannedPaths.add(operation.relativeDestPath);
6574
+ if (operationBelongsToScopedRoot(operation, scopedOwners)) {
6575
+ operations.push(operation);
6576
+ continue;
6577
+ }
6578
+ const entry = manifestByPath.get(operation.relativeDestPath);
6579
+ const transformed = transformOutOfScopeOperation(operation, entry, result.plan.targetRoot, rootId);
6580
+ for (const scopedOperation of transformed) {
6581
+ if (preservedPaths.has(scopedOperation.relativeDestPath)) continue;
6582
+ preservedPaths.add(scopedOperation.relativeDestPath);
6583
+ operations.push(scopedOperation);
6584
+ }
6585
+ }
6586
+ for (const entry of manifest?.entries ?? []) {
6587
+ if (plannedPaths.has(entry.path) || preservedPaths.has(entry.path) || entryBelongsToScopedRoot(entry, scopedOwners)) continue;
6588
+ preservedPaths.add(entry.path);
6589
+ operations.push(keepManifestEntryOperation(entry, result.plan.targetRoot, rootId));
6590
+ }
6591
+ return {
6592
+ ...result,
6593
+ plan: {
6594
+ ...result.plan,
6595
+ operations,
6596
+ hasBlockingChanges: operations.some((operation) => operation.action === "drift" || operation.action === "conflict")
6597
+ }
6598
+ };
6599
+ }
6600
+ function transformOutOfScopeOperation(operation, entry, targetRoot, rootId) {
6601
+ if (operation.action === "skip") return [operation];
6602
+ if (operation.action === "update" || operation.action === "drift") {
6603
+ return entry ? [keepManifestEntryOperation(entry, targetRoot, rootId, operation, { freshMetadata: true })] : [];
6604
+ }
6605
+ if (operation.action === "remove") {
6606
+ return entry ? [keepManifestEntryOperation(entry, targetRoot, rootId)] : [];
6607
+ }
6608
+ if (operation.action === "plugin" || operation.action === "program") {
6609
+ return entry ? [keepManifestEntryOperation(entry, targetRoot, rootId)] : [];
6610
+ }
6611
+ return [];
6612
+ }
6613
+ function scopedGraphOwnerKeys(result, rootId) {
6614
+ const root = result.graph.roots.find((candidate) => candidate.rootId === rootId);
6615
+ if (!root) throw new Error(`Resolved graph root not found for scoped install: ${rootId}`);
6616
+ const keys = /* @__PURE__ */ new Set([`workspace:${rootId}`]);
6617
+ const queue = [root.graphNodeId];
6618
+ while (queue.length > 0) {
6619
+ const nodeId = queue.shift();
6620
+ if (keys.has(nodeId)) continue;
6621
+ keys.add(nodeId);
6622
+ for (const edge of result.graph.edges) {
6623
+ if (edge.from === nodeId) queue.push(edge.to);
6624
+ }
6625
+ }
6626
+ return keys;
6627
+ }
6628
+ function operationBelongsToScopedRoot(operation, scopedOwners) {
6629
+ if (operation.graphNodeId && scopedOwners.has(operation.graphNodeId)) return true;
6630
+ return operation.owners?.some((owner) => scopedOwners.has(owner)) === true;
6631
+ }
6632
+ function entryBelongsToScopedRoot(entry, scopedOwners) {
6633
+ if ("graphNodeId" in entry && entry.graphNodeId && scopedOwners.has(entry.graphNodeId)) return true;
6634
+ const owners = "owners" in entry ? entry.owners : [entry.packageName ?? "legacy"];
6635
+ return owners.some((owner) => scopedOwners.has(owner));
6636
+ }
6637
+ function keepManifestEntryOperation(entry, targetRoot, rootId, operation, options = {}) {
6638
+ const owners = "owners" in entry ? entry.owners : [entry.packageName ?? "legacy"];
6639
+ const fresh = options.freshMetadata ? operation : void 0;
6640
+ return {
6641
+ action: "keep",
6642
+ artifactType: entry.artifactType,
6643
+ artifactName: entry.artifactName,
6644
+ kind: entry.kind,
6645
+ destPath: operation?.destPath ?? join28(targetRoot, entry.path),
6646
+ relativeDestPath: entry.path,
6647
+ desiredHash: entry.sourceHash,
6648
+ currentHash: operation?.currentHash ?? entry.hash,
6649
+ manifestHash: entry.hash,
6650
+ reason: `preserved outside scoped install ${rootId}`,
6651
+ channel: entry.channel,
6652
+ packageName: entry.packageName,
6653
+ semanticCommand: entry.semanticCommand,
6654
+ execute: entry.executed,
6655
+ mergeStrategy: entry.mergeStrategy,
6656
+ composedFrom: entry.composedFrom,
6657
+ installName: fresh?.installName ?? ("installName" in entry ? entry.installName : entry.artifactName),
6658
+ logicalSelector: fresh?.logicalSelector ?? ("logicalSelector" in entry ? entry.logicalSelector : `${entry.artifactType}/${entry.artifactName}`),
6659
+ graphNodeId: fresh?.graphNodeId ?? ("graphNodeId" in entry ? entry.graphNodeId : void 0),
6660
+ dependencyRole: fresh?.dependencyRole ?? ("dependencyRole" in entry ? entry.dependencyRole : "root"),
6661
+ owners: fresh?.owners ?? owners,
6662
+ graphLockDigest: fresh ? void 0 : "graphLockDigest" in entry ? entry.graphLockDigest : void 0
6663
+ };
6664
+ }
6431
6665
  async function uninstallConfiguredPackage(target, packageName, options) {
6432
6666
  const config = await readMergedWorkspaceConfig(target.workspaceRoot);
6433
6667
  const removed = config.packages.filter((pkg) => pkg.name === packageName || pkg.source === packageName);
@@ -6476,10 +6710,12 @@ async function uninstallConfiguredPackage(target, packageName, options) {
6476
6710
  transport,
6477
6711
  targetKey: remainingGroup.target.agentName ?? remainingGroup.target.source,
6478
6712
  targetFingerprintParts: targetFingerprintParts(remainingGroup.target, remainingAdapter, remainingGroup.adapterOptions),
6713
+ lockedResolution: true,
6479
6714
  frozenLock: options.frozenLock,
6480
6715
  offline: options.offline,
6481
6716
  yes: options.yes,
6482
6717
  trustPatterns: options.trust ?? [],
6718
+ readOnly: options.dryRun === true,
6483
6719
  isTTY: process.stdin.isTTY === true
6484
6720
  });
6485
6721
  remainingGraphPlan = result2;
@@ -6500,6 +6736,7 @@ async function uninstallConfiguredPackage(target, packageName, options) {
6500
6736
  const result = await uninstall(plan, {
6501
6737
  dryRun: options.dryRun,
6502
6738
  force: options.force,
6739
+ keepFiles: options.keepFiles,
6503
6740
  transport,
6504
6741
  ...graphLockFinalState,
6505
6742
  workspaceConfig: {
@@ -6528,7 +6765,8 @@ function graphGroupForPackage(groups, target, pkg, options) {
6528
6765
  target: packageTarget,
6529
6766
  adapterOptions,
6530
6767
  packages: [],
6531
- extraRoots: []
6768
+ extraRoots: [],
6769
+ extraPackages: []
6532
6770
  };
6533
6771
  groups.set(key, created);
6534
6772
  return created;
@@ -6571,6 +6809,53 @@ async function readTargetGraphLock(target, options) {
6571
6809
  }
6572
6810
  return { adapter, path, lock: await readGraphLock(path) };
6573
6811
  }
6812
+ async function printStatus(target, options) {
6813
+ const config = await readMergedWorkspaceConfig(target.workspaceRoot);
6814
+ const adapter = await resolveAdapterForTarget(target, options);
6815
+ const transport = transportForTarget(target);
6816
+ console.log(`Status for ${adapter.name} at ${target.targetRoot}`);
6817
+ if (config.packages.length === 0) {
6818
+ console.log(`Configured packages: none at ${target.workspaceRoot}`);
6819
+ return;
6820
+ }
6821
+ console.log("Configured packages:");
6822
+ for (const pkg of config.packages) {
6823
+ console.log(`- ${pkg.name} (${pkg.mode}) ${pkg.source}`);
6824
+ }
6825
+ const manifest = await readInstallManifest(target.targetRoot, adapter.name, transport);
6826
+ console.log(manifest ? `Install manifest: ${manifest.entries.length} entries, revision ${manifest.revision}` : "Install manifest: missing");
6827
+ try {
6828
+ const { path, lock } = await readTargetGraphLock(target, options);
6829
+ console.log(`Graph lock: ${path}`);
6830
+ console.log(`Locked graph: ${lock.canonical.roots.length} roots, ${lock.canonical.nodes.length} nodes, ${lock.canonical.artifacts.length} artifacts`);
6831
+ } catch {
6832
+ console.log("Graph lock: missing");
6833
+ }
6834
+ await printPendingInstallWork(target, options);
6835
+ }
6836
+ async function printPendingInstallWork(target, options) {
6837
+ let results = [];
6838
+ try {
6839
+ results = await buildGraphPlansForTarget(target, void 0, { ...options, dryRun: true }, { mode: "install" });
6840
+ const operations = results.flatMap((result) => result.plan.operations);
6841
+ const pending = operations.filter((operation) => operation.action !== "skip");
6842
+ if (pending.length === 0) {
6843
+ console.log("Pending install work: none");
6844
+ return;
6845
+ }
6846
+ const counts = [...pending.reduce((map, operation) => {
6847
+ map.set(operation.action, (map.get(operation.action) ?? 0) + 1);
6848
+ return map;
6849
+ }, /* @__PURE__ */ new Map())].map(([action, count]) => `${action}=${count}`).join(", ");
6850
+ const blocking = pending.filter((operation) => operation.action === "conflict" || operation.action === "drift").length;
6851
+ console.log(`Pending install work: ${pending.length} operations (${counts}${blocking ? `; blocking=${blocking}` : ""})`);
6852
+ } catch (error) {
6853
+ const message = error instanceof Error ? error.message : String(error);
6854
+ console.log(`Pending install work: unavailable (${message})`);
6855
+ } finally {
6856
+ await Promise.all(results.map((result) => rm9(result.bundle.root, { recursive: true, force: true })));
6857
+ }
6858
+ }
6574
6859
  function collectSelectOption(value, previous) {
6575
6860
  return [...previous, ...splitSelectorList(value)];
6576
6861
  }
@@ -6608,10 +6893,10 @@ function filterUninstallPlanBySelection(plan, selected) {
6608
6893
  };
6609
6894
  }
6610
6895
  async function initPackage(root) {
6611
- await mkdir14(join27(root, "instructions"), { recursive: true });
6612
- await mkdir14(join27(root, "rules"), { recursive: true });
6613
- await mkdir14(join27(root, "skills"), { recursive: true });
6614
- const manifestPath = join27(root, "openpack.json");
6896
+ await mkdir14(join28(root, "instructions"), { recursive: true });
6897
+ await mkdir14(join28(root, "rules"), { recursive: true });
6898
+ await mkdir14(join28(root, "skills"), { recursive: true });
6899
+ const manifestPath = join28(root, "openpack.json");
6615
6900
  const manifest = {
6616
6901
  schemaVersion: 2,
6617
6902
  name: "example/agentwheel-package",
@@ -6624,10 +6909,10 @@ async function initPackage(root) {
6624
6909
  };
6625
6910
  await writeFile12(manifestPath, `${JSON.stringify(manifest, null, 2)}
6626
6911
  `, "utf8");
6627
- await writeFile12(join27(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
6912
+ await writeFile12(join28(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
6628
6913
  }
6629
6914
  async function defaultBootstrapPackage(_root) {
6630
- const packageRoot = await findAgentwheelPackageRoot(dirname20(fileURLToPath2(import.meta.url)));
6915
+ const packageRoot = await findAgentwheelPackageRoot(dirname21(fileURLToPath3(import.meta.url)));
6631
6916
  if (!packageRoot) return void 0;
6632
6917
  return {
6633
6918
  name: "agentwheel",
@@ -6673,7 +6958,7 @@ async function findAgentwheelPackageRoot(start) {
6673
6958
  let current = resolve17(start);
6674
6959
  while (true) {
6675
6960
  if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
6676
- const parent = dirname20(current);
6961
+ const parent = dirname21(current);
6677
6962
  if (parent === current) return void 0;
6678
6963
  current = parent;
6679
6964
  }
@@ -6696,7 +6981,7 @@ function printRegistryEntries(entries) {
6696
6981
  }
6697
6982
  async function main() {
6698
6983
  await maybeCheckForUpdate({
6699
- currentVersion: "0.7.0",
6984
+ currentVersion: CLI_VERSION,
6700
6985
  argv: process.argv,
6701
6986
  env: process.env,
6702
6987
  isTTY: process.stderr.isTTY === true