@bububuger/spanory 0.1.20 → 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 +321 -20
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4542,6 +4542,14 @@ ${notifyLine}
4542
4542
  function escapeTomlBasicString(value) {
4543
4543
  return String(value).replaceAll("\\", "\\\\").replaceAll('"', '\\"');
4544
4544
  }
4545
+ function isSpanoryCodexNotifyConfigured(configText) {
4546
+ return /notify\s*=\s*\[[^\]]*spanory-codex-notify\.sh[^\]]*\]/m.test(String(configText ?? ""));
4547
+ }
4548
+ function stripSpanoryCodexNotifyConfig(configText) {
4549
+ const next = String(configText ?? "").replace(/^notify\s*=\s*\[[^\]]*spanory-codex-notify\.sh[^\]]*\]\s*$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
4550
+ return next ? `${next}
4551
+ ` : "";
4552
+ }
4545
4553
  function codexNotifyScriptContent({ spanoryBin, codexHome, exportDir, logFile }) {
4546
4554
  return `#!/usr/bin/env bash
4547
4555
  set -euo pipefail
@@ -4629,6 +4637,183 @@ async function applyCodexSetup({ homeRoot, spanoryBin, dryRun }) {
4629
4637
  configBackup
4630
4638
  };
4631
4639
  }
4640
+ async function applyCodexWatchSetup({ homeRoot, dryRun }) {
4641
+ const codexHome = path8.join(homeRoot, ".codex");
4642
+ const scriptPath = path8.join(codexHome, "bin", "spanory-codex-notify.sh");
4643
+ const configPath = path8.join(codexHome, "config.toml");
4644
+ let currentConfig = "";
4645
+ try {
4646
+ currentConfig = await readFile8(configPath, "utf-8");
4647
+ } catch (error) {
4648
+ if (error?.code !== "ENOENT")
4649
+ throw error;
4650
+ }
4651
+ const nextConfig = stripSpanoryCodexNotifyConfig(currentConfig);
4652
+ const configChanged = currentConfig !== nextConfig;
4653
+ let scriptPresent = false;
4654
+ try {
4655
+ await stat4(scriptPath);
4656
+ scriptPresent = true;
4657
+ } catch {
4658
+ }
4659
+ const changed = configChanged || scriptPresent;
4660
+ let configBackup = null;
4661
+ if (changed && !dryRun) {
4662
+ if (configChanged) {
4663
+ await mkdir4(path8.dirname(configPath), { recursive: true });
4664
+ configBackup = await backupIfExists(configPath);
4665
+ await writeFile4(configPath, nextConfig, "utf-8");
4666
+ }
4667
+ if (scriptPresent) {
4668
+ await rm(scriptPath, { force: true });
4669
+ }
4670
+ }
4671
+ return {
4672
+ runtime: "codex",
4673
+ ok: true,
4674
+ changed,
4675
+ dryRun,
4676
+ mode: "watch",
4677
+ configPath,
4678
+ scriptPath,
4679
+ configBackup,
4680
+ notifyConfigured: isSpanoryCodexNotifyConfigured(nextConfig),
4681
+ scriptPresent: false
4682
+ };
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
+ }
4632
4817
  function commandExists(command) {
4633
4818
  const result = runSystemCommand("which", [command], { env: process.env });
4634
4819
  return result.code === 0;
@@ -4671,7 +4856,102 @@ function openclawRuntimeHomeForSetup(homeRoot, explicitRuntimeHome) {
4671
4856
  function opencodeRuntimeHomeForSetup(homeRoot, explicitRuntimeHome) {
4672
4857
  return explicitRuntimeHome || path8.join(homeRoot, ".config", "opencode");
4673
4858
  }
4674
- function installOpenclawPlugin(runtimeHome) {
4859
+ function resolveOpenclawConfigPath(runtimeHome) {
4860
+ return path8.join(runtimeHome, "openclaw.json");
4861
+ }
4862
+ function canonicalPath(input) {
4863
+ const resolved = path8.resolve(String(input ?? ""));
4864
+ try {
4865
+ return realpathSync(resolved);
4866
+ } catch {
4867
+ return resolved;
4868
+ }
4869
+ }
4870
+ function readOpenclawPluginNameFromDir(dirPath) {
4871
+ const pkgPath = path8.join(dirPath, "package.json");
4872
+ if (!existsSync(pkgPath))
4873
+ return null;
4874
+ try {
4875
+ const parsed = JSON.parse(readFileSync(pkgPath, "utf-8"));
4876
+ const name = String(parsed?.name ?? "").trim();
4877
+ return name || null;
4878
+ } catch {
4879
+ return null;
4880
+ }
4881
+ }
4882
+ function isSpanoryOpenclawPluginPath(candidatePath) {
4883
+ const text = String(candidatePath ?? "").trim();
4884
+ if (!text)
4885
+ return false;
4886
+ const normalized = text.replaceAll("\\", "/").toLowerCase();
4887
+ if (normalized.includes("spanory") && normalized.includes("openclaw-plugin")) {
4888
+ return true;
4889
+ }
4890
+ const canonical = canonicalPath(text);
4891
+ const pluginName = readOpenclawPluginNameFromDir(canonical);
4892
+ return pluginName === "@bububuger/spanory-openclaw-plugin" || pluginName === "@alipay/spanory-openclaw-plugin";
4893
+ }
4894
+ async function normalizeOpenclawPluginLoadPaths(runtimeHome, pluginDir, dryRun) {
4895
+ const configPath = resolveOpenclawConfigPath(runtimeHome);
4896
+ let configRaw = "";
4897
+ try {
4898
+ configRaw = await readFile8(configPath, "utf-8");
4899
+ } catch (error) {
4900
+ if (error?.code === "ENOENT")
4901
+ return { changed: false, configPath, backup: null };
4902
+ throw error;
4903
+ }
4904
+ let parsed;
4905
+ try {
4906
+ parsed = JSON.parse(configRaw);
4907
+ } catch (error) {
4908
+ throw new Error(`failed to parse openclaw config ${configPath}: ${error?.message ?? String(error)}`);
4909
+ }
4910
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
4911
+ parsed = {};
4912
+ }
4913
+ if (!parsed.plugins || typeof parsed.plugins !== "object" || Array.isArray(parsed.plugins)) {
4914
+ parsed.plugins = {};
4915
+ }
4916
+ if (!parsed.plugins.load || typeof parsed.plugins.load !== "object" || Array.isArray(parsed.plugins.load)) {
4917
+ parsed.plugins.load = {};
4918
+ }
4919
+ const target = path8.resolve(pluginDir);
4920
+ const targetCanonical = canonicalPath(target);
4921
+ const currentPaths = Array.isArray(parsed.plugins.load.paths) ? parsed.plugins.load.paths : [];
4922
+ const nonSpanoryPaths = [];
4923
+ const seen = /* @__PURE__ */ new Set();
4924
+ for (const item of currentPaths) {
4925
+ if (typeof item !== "string")
4926
+ continue;
4927
+ const text = item.trim();
4928
+ if (!text)
4929
+ continue;
4930
+ if (isSpanoryOpenclawPluginPath(text))
4931
+ continue;
4932
+ const key = canonicalPath(text);
4933
+ if (seen.has(key))
4934
+ continue;
4935
+ seen.add(key);
4936
+ nonSpanoryPaths.push(text);
4937
+ }
4938
+ if (!seen.has(targetCanonical)) {
4939
+ nonSpanoryPaths.push(target);
4940
+ }
4941
+ parsed.plugins.load.paths = nonSpanoryPaths;
4942
+ const nextRaw = `${JSON.stringify(parsed, null, 2)}
4943
+ `;
4944
+ if (nextRaw === configRaw) {
4945
+ return { changed: false, configPath, backup: null };
4946
+ }
4947
+ let backup = null;
4948
+ if (!dryRun) {
4949
+ backup = await backupIfExists(configPath);
4950
+ await writeFile4(configPath, nextRaw, "utf-8");
4951
+ }
4952
+ return { changed: true, configPath, backup };
4953
+ }
4954
+ async function installOpenclawPlugin(runtimeHome, dryRun) {
4675
4955
  const pluginDir = path8.resolve(resolveOpenclawPluginDir());
4676
4956
  const installResult = runSystemCommand("openclaw", ["plugins", "install", "-l", pluginDir], {
4677
4957
  env: {
@@ -4691,9 +4971,12 @@ function installOpenclawPlugin(runtimeHome) {
4691
4971
  if (enableResult.code !== 0) {
4692
4972
  throw new Error(enableResult.stderr || enableResult.error || "openclaw plugins enable failed");
4693
4973
  }
4974
+ const pathNormalizeResult = await normalizeOpenclawPluginLoadPaths(runtimeHome, pluginDir, dryRun);
4694
4975
  return {
4976
+ pluginDir,
4695
4977
  installStdout: installResult.stdout.trim(),
4696
- enableStdout: enableResult.stdout.trim()
4978
+ enableStdout: enableResult.stdout.trim(),
4979
+ pathNormalizeResult
4697
4980
  };
4698
4981
  }
4699
4982
  async function installOpencodePlugin(runtimeHome, pluginDirOverride) {
@@ -4749,7 +5032,7 @@ async function runSetupDetect(options) {
4749
5032
  let codexNotifyConfigured = false;
4750
5033
  try {
4751
5034
  const config = await readFile8(codexConfigPath, "utf-8");
4752
- codexNotifyConfigured = /notify\s*=\s*\[[^\]]*spanory-codex-notify\.sh[^\]]*\]/m.test(config);
5035
+ codexNotifyConfigured = isSpanoryCodexNotifyConfigured(config);
4753
5036
  } catch {
4754
5037
  }
4755
5038
  let codexScriptPresent = true;
@@ -4761,10 +5044,11 @@ async function runSetupDetect(options) {
4761
5044
  report2.runtimes.push({
4762
5045
  runtime: "codex",
4763
5046
  available: commandExists("codex"),
4764
- configured: codexNotifyConfigured && codexScriptPresent,
5047
+ configured: !codexNotifyConfigured,
4765
5048
  details: {
4766
5049
  configPath: codexConfigPath,
4767
5050
  scriptPath: codexScriptPath,
5051
+ mode: "watch",
4768
5052
  notifyConfigured: codexNotifyConfigured,
4769
5053
  scriptPresent: codexScriptPresent
4770
5054
  }
@@ -4822,7 +5106,7 @@ async function runSetupDoctor(options) {
4822
5106
  let notifyConfigured = false;
4823
5107
  try {
4824
5108
  const config = await readFile8(configPath, "utf-8");
4825
- notifyConfigured = /notify\s*=\s*\[[^\]]*spanory-codex-notify\.sh[^\]]*\]/m.test(config);
5109
+ notifyConfigured = isSpanoryCodexNotifyConfigured(config);
4826
5110
  } catch {
4827
5111
  }
4828
5112
  let scriptPresent = false;
@@ -4832,15 +5116,15 @@ async function runSetupDoctor(options) {
4832
5116
  } catch {
4833
5117
  }
4834
5118
  checks.push({
4835
- id: "codex_notify_configured",
5119
+ id: "codex_watch_mode",
4836
5120
  runtime: "codex",
4837
- ok: notifyConfigured,
5121
+ ok: !notifyConfigured,
4838
5122
  detail: configPath
4839
5123
  });
4840
5124
  checks.push({
4841
- id: "codex_notify_script",
5125
+ id: "codex_notify_script_absent",
4842
5126
  runtime: "codex",
4843
- ok: scriptPresent,
5127
+ ok: !scriptPresent,
4844
5128
  detail: scriptPath
4845
5129
  });
4846
5130
  }
@@ -4883,7 +5167,7 @@ async function runSetupApply(options) {
4883
5167
  const selected = parseSetupRuntimes(options.runtimes);
4884
5168
  const spanoryBin = options.spanoryBin ?? "spanory";
4885
5169
  const dryRun = Boolean(options.dryRun);
4886
- const codexMode = options.codexMode ?? "notify";
5170
+ const codexMode = options.codexMode ?? process.env.SPANORY_CODEX_MODE ?? "watch";
4887
5171
  const results = [];
4888
5172
  if (selected.includes("claude-code")) {
4889
5173
  try {
@@ -4898,14 +5182,7 @@ async function runSetupApply(options) {
4898
5182
  }
4899
5183
  }
4900
5184
  if (selected.includes("codex")) {
4901
- if (codexMode !== "notify") {
4902
- results.push({
4903
- runtime: "codex",
4904
- ok: true,
4905
- skipped: true,
4906
- detail: `codex mode "${codexMode}" skips notify setup`
4907
- });
4908
- } else {
5185
+ if (codexMode === "notify") {
4909
5186
  try {
4910
5187
  const result = await applyCodexSetup({ homeRoot, spanoryBin, dryRun });
4911
5188
  results.push(result);
@@ -4916,6 +5193,24 @@ async function runSetupApply(options) {
4916
5193
  error: String(error?.message ?? error)
4917
5194
  });
4918
5195
  }
5196
+ } else if (codexMode === "watch") {
5197
+ try {
5198
+ const result = await applyCodexWatchSetup({ homeRoot, dryRun });
5199
+ results.push(result);
5200
+ } catch (error) {
5201
+ results.push({
5202
+ runtime: "codex",
5203
+ ok: false,
5204
+ error: String(error?.message ?? error)
5205
+ });
5206
+ }
5207
+ } else {
5208
+ results.push({
5209
+ runtime: "codex",
5210
+ ok: true,
5211
+ skipped: true,
5212
+ detail: `codex mode "${codexMode}" has no setup action`
5213
+ });
4919
5214
  }
4920
5215
  }
4921
5216
  if (selected.includes("openclaw")) {
@@ -4930,7 +5225,7 @@ async function runSetupApply(options) {
4930
5225
  const runtimeHome = openclawRuntimeHomeForSetup(homeRoot, options.openclawRuntimeHome);
4931
5226
  try {
4932
5227
  if (!dryRun)
4933
- installOpenclawPlugin(runtimeHome);
5228
+ await installOpenclawPlugin(runtimeHome, dryRun);
4934
5229
  const doctor = await runOpenclawPluginDoctor(runtimeHome);
4935
5230
  results.push({
4936
5231
  runtime: "openclaw",
@@ -5286,7 +5581,7 @@ setup.command("detect").description("Detect local runtime availability and setup
5286
5581
  const report2 = await runSetupDetect(options);
5287
5582
  console.log(JSON.stringify(report2, null, 2));
5288
5583
  });
5289
- setup.command("apply").description("Apply idempotent local setup for selected 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("--spanory-bin <path>", "Spanory binary/command to write into runtime configs", "spanory").option("--codex-mode <mode>", "Codex setup mode: notify | proxy (default: notify)", "notify").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) => {
5584
+ setup.command("apply").description("Apply idempotent local setup for selected 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("--spanory-bin <path>", "Spanory binary/command to write into runtime configs", "spanory").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) => {
5290
5585
  const report2 = await runSetupApply(options);
5291
5586
  console.log(JSON.stringify(report2, null, 2));
5292
5587
  if (!report2.ok)
@@ -5298,6 +5593,12 @@ setup.command("doctor").description("Run setup diagnostics for selected runtimes
5298
5593
  if (!report2.ok)
5299
5594
  process.exitCode = 2;
5300
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
+ });
5301
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) => {
5302
5603
  const manager = options.manager === "tnpm" ? "tnpm" : detectUpgradePackageManager();
5303
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.20",
3
+ "version": "0.1.22",
4
4
  "description": "Spanory CLI for cross-runtime agent observability",
5
5
  "license": "MIT",
6
6
  "type": "module",