agentwheel 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +3 -0
  2. package/dist/index.js +67 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -72,6 +72,9 @@ pnpm link --global
72
72
 
73
73
  `plan`, `sync --dry-run`, and `update --dry-run` show exactly what would change before anything is written. They're the commands to trust.
74
74
 
75
+ `uninstall` removes clean managed files by default and keeps drifted files in place with a warning.
76
+ Use `agentwheel uninstall --force` only when you also want to remove drifted managed files.
77
+
75
78
  ## Runtime targeting
76
79
 
77
80
  Normal use no longer needs `--target-root`. Run agentwheel inside a runtime folder and it detects
package/dist/index.js CHANGED
@@ -461,16 +461,52 @@ async function applyInstallPlan(plan, sourceLock, options = {}) {
461
461
  await writeSourceLock(plan.targetRoot, plan.adapter, sourceLock);
462
462
  return manifest;
463
463
  }
464
- async function uninstall(plan, dryRun) {
464
+ async function uninstall(plan, options = {}) {
465
+ const resolvedOptions = typeof options === "boolean" ? { dryRun: options } : options;
465
466
  if (plan.hasBlockingChanges) {
466
- const blockers = plan.operations.filter((operation) => operation.action === "drift" || operation.action === "conflict");
467
- throw new Error(`Refusing to uninstall with drift: ${blockers.map((item) => item.relativeDestPath).join(", ")}`);
467
+ const blockers = plan.operations.filter((operation) => operation.action === "conflict");
468
+ throw new Error(`Refusing to uninstall with blocking changes: ${blockers.map((item) => item.relativeDestPath).join(", ")}`);
468
469
  }
469
- if (dryRun) return;
470
+ const removable = plan.operations.filter((operation) => operation.action === "remove" || resolvedOptions.force && operation.action === "keep");
471
+ const kept = resolvedOptions.force ? [] : plan.operations.filter((operation) => operation.action === "keep");
472
+ const removedDrifted = resolvedOptions.force ? plan.operations.filter((operation) => operation.action === "keep").length : 0;
473
+ if (resolvedOptions.dryRun) return { removed: removable.length, kept: kept.length, removedDrifted };
470
474
  for (const operation of plan.operations) {
471
- await rm2(operation.destPath, { recursive: true, force: true });
475
+ if (operation.action === "remove" || resolvedOptions.force && operation.action === "keep") {
476
+ await rm2(operation.destPath, { recursive: true, force: true });
477
+ }
478
+ }
479
+ if (kept.length > 0) {
480
+ const now = (/* @__PURE__ */ new Date()).toISOString();
481
+ await writeInstallManifest({
482
+ version: 1,
483
+ adapter: plan.adapter,
484
+ targetRoot: plan.targetRoot,
485
+ generatedAt: now,
486
+ adapterCode: plan.adapterCode,
487
+ entries: kept.map((operation) => {
488
+ if (!operation.manifestHash || !operation.desiredHash) {
489
+ throw new Error(`Invalid kept operation missing manifest/source hash: ${operation.relativeDestPath}`);
490
+ }
491
+ return {
492
+ path: operation.relativeDestPath,
493
+ artifactType: operation.artifactType,
494
+ artifactName: operation.artifactName,
495
+ kind: operation.kind,
496
+ hash: operation.manifestHash,
497
+ sourceHash: operation.desiredHash,
498
+ updatedAt: now,
499
+ channel: operation.channel,
500
+ packageName: operation.packageName,
501
+ semanticCommand: operation.semanticCommand,
502
+ mergeStrategy: operation.mergeStrategy
503
+ };
504
+ }).sort((a, b) => a.path.localeCompare(b.path))
505
+ });
506
+ } else {
507
+ await removeStateFiles(plan.targetRoot, plan.adapter);
472
508
  }
473
- await removeStateFiles(plan.targetRoot, plan.adapter);
509
+ return { removed: removable.length, kept: kept.length, removedDrifted };
474
510
  }
475
511
 
476
512
  // src/install/plan.ts
@@ -658,6 +694,7 @@ function summarizePlan(plan) {
658
694
  update: 0,
659
695
  skip: 0,
660
696
  remove: 0,
697
+ keep: 0,
661
698
  drift: 0,
662
699
  conflict: 0,
663
700
  plugin: 0,
@@ -679,17 +716,20 @@ async function createUninstallPlan(manifest) {
679
716
  const currentHash = await hashPath(destPath);
680
717
  if (currentHash !== entry.hash) {
681
718
  operations.push({
682
- action: "drift",
719
+ action: "keep",
683
720
  artifactType: entry.artifactType,
684
721
  artifactName: entry.artifactName,
685
722
  kind: entry.kind,
686
723
  destPath,
687
724
  relativeDestPath: entry.path,
725
+ desiredHash: entry.sourceHash,
688
726
  currentHash,
689
727
  manifestHash: entry.hash,
690
- reason: "managed destination changed outside agentwheel",
728
+ reason: "managed destination changed outside agentwheel; keeping by default",
691
729
  channel: entry.channel,
692
- packageName: entry.packageName
730
+ packageName: entry.packageName,
731
+ semanticCommand: entry.semanticCommand,
732
+ mergeStrategy: entry.mergeStrategy
693
733
  });
694
734
  } else {
695
735
  operations.push({
@@ -712,7 +752,8 @@ async function createUninstallPlan(manifest) {
712
752
  adapter: manifest.adapter,
713
753
  targetRoot: manifest.targetRoot,
714
754
  operations,
715
- hasBlockingChanges: operations.some((operation) => operation.action === "drift" || operation.action === "conflict")
755
+ hasBlockingChanges: operations.some((operation) => operation.action === "conflict"),
756
+ adapterCode: manifest.adapterCode
716
757
  };
717
758
  }
718
759
 
@@ -722,6 +763,7 @@ var labels = {
722
763
  update: "UPDATE",
723
764
  skip: "SKIP",
724
765
  remove: "REMOVE",
766
+ keep: "KEEP",
725
767
  drift: "DRIFT",
726
768
  conflict: "CONFLICT",
727
769
  plugin: "PLUGIN",
@@ -743,7 +785,7 @@ function formatPlan(plan) {
743
785
  }
744
786
  const summary = summarizePlan(plan);
745
787
  lines.push(
746
- `Summary: create ${summary.create}, update ${summary.update}, skip ${summary.skip}, remove ${summary.remove}, drift ${summary.drift}, conflict ${summary.conflict}, plugin ${summary.plugin}`
788
+ `Summary: create ${summary.create}, update ${summary.update}, skip ${summary.skip}, remove ${summary.remove}, keep ${summary.keep}, drift ${summary.drift}, conflict ${summary.conflict}, plugin ${summary.plugin}`
747
789
  );
748
790
  return lines.join("\n");
749
791
  }
@@ -2068,7 +2110,7 @@ function dedupeTargets(matches) {
2068
2110
 
2069
2111
  // src/cli/index.ts
2070
2112
  var program = new Command();
2071
- program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version("0.4.0");
2113
+ program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version("0.4.1");
2072
2114
  program.command("init").argument("[kind]", "workspace or package", "workspace").option("--target-root <path>", "workspace root", process.cwd()).action(async (kind, options) => {
2073
2115
  const root = normalizeTargetRoot(options.targetRoot);
2074
2116
  if (kind === "package") {
@@ -2225,7 +2267,7 @@ program.command("eject").argument("<item>", "package/type/name").option("--targe
2225
2267
  const result = await ejectArtifact(targetRoot, item);
2226
2268
  console.log(`Ejected ${item} to ${result.ejectedPath}.`);
2227
2269
  });
2228
- program.command("uninstall").option("--adapter <adapter>", "adapter", "openclaw").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show removals without writing", false).action(async (options) => {
2270
+ program.command("uninstall").option("--adapter <adapter>", "adapter", "openclaw").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show removals without writing", false).option("--force", "remove drifted managed files too", false).action(async (options) => {
2229
2271
  const targets = await resolveCliTargets(options);
2230
2272
  for (const target of targets) {
2231
2273
  const adapter = await resolveAdapterForTarget(target, options);
@@ -2236,10 +2278,10 @@ program.command("uninstall").option("--adapter <adapter>", "adapter", "openclaw"
2236
2278
  }
2237
2279
  const plan = await createUninstallPlan(manifest);
2238
2280
  console.log(formatPlan(plan));
2239
- await uninstall(plan, options.dryRun);
2281
+ const result = await uninstall(plan, { dryRun: options.dryRun, force: options.force });
2240
2282
  if (!options.dryRun) {
2241
2283
  await adapter.programmatic?.uninstall?.({ targetRoot: target.targetRoot, adapterName: adapter.name });
2242
- console.log(`Uninstalled ${adapter.name} at ${target.targetRoot}.`);
2284
+ console.log(formatUninstallResult(result));
2243
2285
  }
2244
2286
  if (plan.hasBlockingChanges) process.exitCode = 1;
2245
2287
  }
@@ -2329,6 +2371,16 @@ async function initPackage(root) {
2329
2371
  `, "utf8");
2330
2372
  await writeFile4(join19(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
2331
2373
  }
2374
+ function formatUninstallResult(result) {
2375
+ const removedLabel = result.removed === 1 ? "managed file" : "managed files";
2376
+ if (result.removedDrifted > 0) {
2377
+ const driftedLabel = result.removedDrifted === 1 ? "drifted file" : "drifted files";
2378
+ return `Removed ${result.removed} ${removedLabel}, including ${result.removedDrifted} ${driftedLabel}.`;
2379
+ }
2380
+ if (result.kept === 0) return `Removed ${result.removed} ${removedLabel}.`;
2381
+ const keptLabel = result.kept === 1 ? "drifted file" : "drifted files";
2382
+ return `Removed ${result.removed} ${removedLabel}; kept ${result.kept} ${keptLabel} (use --force to remove).`;
2383
+ }
2332
2384
  function printRegistryEntries(entries) {
2333
2385
  for (const entry of entries) {
2334
2386
  const tags = entry.tags?.length ? ` [${entry.tags.join(",")}]` : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentwheel",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Weave skills, rules, and instructions across every AI agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",