@bububuger/spanory 0.1.21 → 0.1.22

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 (2) hide show
  1. package/dist/index.js +139 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4681,6 +4681,139 @@ async function applyCodexWatchSetup({ homeRoot, dryRun }) {
4681
4681
  scriptPresent: false
4682
4682
  };
4683
4683
  }
4684
+ function removeClaudeHooks(settings) {
4685
+ if (!settings.hooks || typeof settings.hooks !== "object")
4686
+ return;
4687
+ for (const eventName of Object.keys(settings.hooks)) {
4688
+ const groups = settings.hooks[eventName];
4689
+ if (!Array.isArray(groups))
4690
+ continue;
4691
+ for (const group of groups) {
4692
+ if (!group || !Array.isArray(group.hooks))
4693
+ continue;
4694
+ group.hooks = group.hooks.filter((hook) => !(hook && typeof hook === "object" && isSpanoryHookCommand(hook.command)));
4695
+ }
4696
+ settings.hooks[eventName] = groups.filter((group) => !group || !Array.isArray(group.hooks) || group.hooks.length > 0);
4697
+ if (settings.hooks[eventName].length === 0)
4698
+ delete settings.hooks[eventName];
4699
+ }
4700
+ if (Object.keys(settings.hooks).length === 0)
4701
+ delete settings.hooks;
4702
+ }
4703
+ async function teardownClaudeSetup({ homeRoot, dryRun }) {
4704
+ const settingsPath = path8.join(homeRoot, ".claude", "settings.json");
4705
+ let settings = {};
4706
+ try {
4707
+ settings = JSON.parse(await readFile8(settingsPath, "utf-8"));
4708
+ } catch (error) {
4709
+ if (error?.code !== "ENOENT")
4710
+ throw new Error(`failed to parse Claude settings: ${error.message ?? String(error)}`);
4711
+ return { runtime: "claude-code", ok: true, changed: false, dryRun, settingsPath, backup: null };
4712
+ }
4713
+ const before = JSON.stringify(settings);
4714
+ removeClaudeHooks(settings);
4715
+ const after = JSON.stringify(settings);
4716
+ const changed = before !== after;
4717
+ let backup = null;
4718
+ if (changed && !dryRun) {
4719
+ backup = await backupIfExists(settingsPath);
4720
+ await writeFile4(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
4721
+ }
4722
+ return { runtime: "claude-code", ok: true, changed, dryRun, settingsPath, backup };
4723
+ }
4724
+ async function teardownCodexSetup({ homeRoot, dryRun }) {
4725
+ const codexHome = path8.join(homeRoot, ".codex");
4726
+ const configPath = path8.join(codexHome, "config.toml");
4727
+ const scriptPath = path8.join(codexHome, "bin", "spanory-codex-notify.sh");
4728
+ let currentConfig = "";
4729
+ try {
4730
+ currentConfig = await readFile8(configPath, "utf-8");
4731
+ } catch (error) {
4732
+ if (error?.code !== "ENOENT")
4733
+ throw error;
4734
+ }
4735
+ const nextConfig = stripSpanoryCodexNotifyConfig(currentConfig);
4736
+ const configChanged = currentConfig !== nextConfig;
4737
+ let scriptPresent = false;
4738
+ try {
4739
+ await stat4(scriptPath);
4740
+ scriptPresent = true;
4741
+ } catch {
4742
+ }
4743
+ const changed = configChanged || scriptPresent;
4744
+ let configBackup = null;
4745
+ if (changed && !dryRun) {
4746
+ if (configChanged) {
4747
+ configBackup = await backupIfExists(configPath);
4748
+ await writeFile4(configPath, nextConfig, "utf-8");
4749
+ }
4750
+ if (scriptPresent)
4751
+ await rm(scriptPath, { force: true });
4752
+ }
4753
+ return { runtime: "codex", ok: true, changed, dryRun, configPath, scriptPath, configBackup, scriptRemoved: scriptPresent };
4754
+ }
4755
+ async function teardownOpenclawSetup({ homeRoot, openclawRuntimeHome, dryRun }) {
4756
+ if (!commandExists("openclaw")) {
4757
+ return { runtime: "openclaw", ok: true, skipped: true, detail: "openclaw command not found in PATH" };
4758
+ }
4759
+ const runtimeHome = openclawRuntimeHomeForSetup(homeRoot, openclawRuntimeHome);
4760
+ if (dryRun)
4761
+ return { runtime: "openclaw", ok: true, changed: true, dryRun };
4762
+ const result = runSystemCommand("openclaw", ["plugins", "uninstall", OPENCLAW_SPANORY_PLUGIN_ID], {
4763
+ env: { ...process.env, ...runtimeHome ? { OPENCLAW_STATE_DIR: resolveRuntimeHome3("openclaw", runtimeHome) } : {} }
4764
+ });
4765
+ if (result.code !== 0)
4766
+ throw new Error(result.stderr || result.error || "openclaw plugins uninstall failed");
4767
+ return { runtime: "openclaw", ok: true, changed: true, dryRun };
4768
+ }
4769
+ async function teardownOpencodeSetup({ homeRoot, opencodeRuntimeHome, dryRun }) {
4770
+ const runtimeHome = opencodeRuntimeHomeForSetup(homeRoot, opencodeRuntimeHome);
4771
+ const loaderFile = opencodePluginLoaderPath(runtimeHome);
4772
+ let present = false;
4773
+ try {
4774
+ await stat4(loaderFile);
4775
+ present = true;
4776
+ } catch {
4777
+ }
4778
+ if (present && !dryRun)
4779
+ await rm(loaderFile, { force: true });
4780
+ return { runtime: "opencode", ok: true, changed: present, dryRun, loaderFile };
4781
+ }
4782
+ async function runSetupTeardown(options) {
4783
+ const homeRoot = setupHomeRoot(options.home);
4784
+ const selected = parseSetupRuntimes(options.runtimes);
4785
+ const dryRun = Boolean(options.dryRun);
4786
+ const results = [];
4787
+ if (selected.includes("claude-code")) {
4788
+ try {
4789
+ results.push(await teardownClaudeSetup({ homeRoot, dryRun }));
4790
+ } catch (error) {
4791
+ results.push({ runtime: "claude-code", ok: false, error: String(error?.message ?? error) });
4792
+ }
4793
+ }
4794
+ if (selected.includes("codex")) {
4795
+ try {
4796
+ results.push(await teardownCodexSetup({ homeRoot, dryRun }));
4797
+ } catch (error) {
4798
+ results.push({ runtime: "codex", ok: false, error: String(error?.message ?? error) });
4799
+ }
4800
+ }
4801
+ if (selected.includes("openclaw")) {
4802
+ try {
4803
+ results.push(await teardownOpenclawSetup({ homeRoot, openclawRuntimeHome: options.openclawRuntimeHome, dryRun }));
4804
+ } catch (error) {
4805
+ results.push({ runtime: "openclaw", ok: false, error: String(error?.message ?? error) });
4806
+ }
4807
+ }
4808
+ if (selected.includes("opencode")) {
4809
+ try {
4810
+ results.push(await teardownOpencodeSetup({ homeRoot, opencodeRuntimeHome: options.opencodeRuntimeHome, dryRun }));
4811
+ } catch (error) {
4812
+ results.push({ runtime: "opencode", ok: false, error: String(error?.message ?? error) });
4813
+ }
4814
+ }
4815
+ return { ok: results.every((r) => r.ok), results };
4816
+ }
4684
4817
  function commandExists(command) {
4685
4818
  const result = runSystemCommand("which", [command], { env: process.env });
4686
4819
  return result.code === 0;
@@ -5460,6 +5593,12 @@ setup.command("doctor").description("Run setup diagnostics for selected runtimes
5460
5593
  if (!report2.ok)
5461
5594
  process.exitCode = 2;
5462
5595
  });
5596
+ setup.command("teardown").description("Remove all Spanory integration from local runtimes").option("--runtimes <csv>", `Comma-separated runtimes (default: ${DEFAULT_SETUP_RUNTIMES.join(",")})`, DEFAULT_SETUP_RUNTIMES.join(",")).option("--home <path>", "Home directory root override (default: $HOME)").option("--openclaw-runtime-home <path>", "Override OpenClaw runtime home (default: ~/.openclaw)").option("--opencode-runtime-home <path>", "Override OpenCode runtime home (default: ~/.config/opencode)").option("--dry-run", "Only print planned changes without writing files", false).action(async (options) => {
5597
+ const report2 = await runSetupTeardown(options);
5598
+ console.log(JSON.stringify(report2, null, 2));
5599
+ if (!report2.ok)
5600
+ process.exitCode = 2;
5601
+ });
5463
5602
  program.command("upgrade").description("Upgrade spanory CLI from npm registry").option("--dry-run", "Print upgrade command without executing", false).option("--manager <name>", "Package manager override: npm|tnpm").option("--scope <scope>", "Install scope override: global|local").action(async (options) => {
5464
5603
  const manager = options.manager === "tnpm" ? "tnpm" : detectUpgradePackageManager();
5465
5604
  const scope = options.scope === "local" ? "local" : options.scope === "global" ? "global" : detectUpgradeScope();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bububuger/spanory",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "Spanory CLI for cross-runtime agent observability",
5
5
  "license": "MIT",
6
6
  "type": "module",