@paradigma-inc/flywheel 0.1.51 → 0.1.53

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paradigma-inc/flywheel",
3
- "version": "0.1.51",
3
+ "version": "0.1.53",
4
4
  "description": "One-command setup for Flywheel MCP hosts",
5
5
  "type": "module",
6
6
  "files": [
package/src/cli.mjs CHANGED
@@ -39,6 +39,7 @@ import {
39
39
  buildGuidedSkillInstallPrompt,
40
40
  runMcpModeCommand,
41
41
  } from "./setup/modes/mcp-command.mjs";
42
+ import { findFlywheelMcpEntryNames } from "./setup/shared/prior-mode-detect.mjs";
42
43
  import { renderSetupSummary } from "./setup/shared/summary.mjs";
43
44
 
44
45
  export { buildGuidedSkillInstallPrompt };
@@ -532,16 +533,47 @@ async function resolveHostsWithRemainingFlywheelMcpEntries({ scope }) {
532
533
  return remainingHosts;
533
534
  }
534
535
 
535
- async function isConfiguredForUninstallScope(hostName, scope, serverName) {
536
+ async function findFlywheelServerNamesOnHost(hostName, scope) {
537
+ // Mirror setup's prior-mode detector: walk every MCP entry on the host and
538
+ // keep those whose URL is Flywheel-identifying (hostname contains "flywheel"
539
+ // or path === /mcp-server with a matching target origin). Uninstall must
540
+ // catch renamed/drifted entries (e.g. `[mcp_servers.flywheel-staging]`) that
541
+ // setup already detects; otherwise a user whose host has a non-default entry
542
+ // name is stuck between setup ("already installed") and uninstall ("not
543
+ // configured").
544
+ return await findFlywheelMcpEntryNames({ hostName, scope }).catch(() => []);
545
+ }
546
+
547
+ async function resolveUninstallServerNamesForHost(
548
+ hostName,
549
+ scope,
550
+ { explicitServerName },
551
+ ) {
552
+ if (explicitServerName) {
553
+ const configured = await isAlreadyConfigured(
554
+ hostName,
555
+ scope,
556
+ explicitServerName,
557
+ ).catch(() => false);
558
+ return configured ? [explicitServerName] : [];
559
+ }
560
+ return await findFlywheelServerNamesOnHost(hostName, scope);
561
+ }
562
+
563
+ async function isConfiguredForUninstallScope(
564
+ hostName,
565
+ scope,
566
+ { explicitServerName },
567
+ ) {
536
568
  const scopes = scopesFromUninstallScope(scope);
537
569
  for (const singleScope of scopes) {
538
570
  // eslint-disable-next-line no-await-in-loop
539
- const configured = await isAlreadyConfigured(
571
+ const targets = await resolveUninstallServerNamesForHost(
540
572
  hostName,
541
573
  singleScope,
542
- serverName,
543
- ).catch(() => false);
544
- if (configured) return true;
574
+ { explicitServerName },
575
+ );
576
+ if (targets.length > 0) return true;
545
577
  }
546
578
  return false;
547
579
  }
@@ -563,14 +595,12 @@ async function promptUninstallScope(defaultScope) {
563
595
  }
564
596
  }
565
597
 
566
- async function promptUninstallHosts(scope, serverName) {
598
+ async function promptUninstallHosts(scope, { explicitServerName }) {
567
599
  const choices = await Promise.all(
568
600
  ALL_HOST_NAMES.map(async (hostName) => {
569
- const configured = await isConfiguredForUninstallScope(
570
- hostName,
571
- scope,
572
- serverName,
573
- );
601
+ const configured = await isConfiguredForUninstallScope(hostName, scope, {
602
+ explicitServerName,
603
+ });
574
604
  return {
575
605
  name: SETUP_HOST_NAMES[hostName],
576
606
  value: hostName,
@@ -599,7 +629,7 @@ async function promptUninstallHosts(scope, serverName) {
599
629
  }
600
630
  }
601
631
 
602
- async function resolveUninstallTargets(options, serverName) {
632
+ async function resolveUninstallTargets(options, { explicitServerName }) {
603
633
  const explicitHostsFromFlags = selectedHostsFromOptions(options);
604
634
  const explicitHostsFromList =
605
635
  explicitHostsFromFlags.length === 0 ? parseHostsList(options.hosts) : [];
@@ -628,7 +658,9 @@ async function resolveUninstallTargets(options, serverName) {
628
658
  }
629
659
 
630
660
  if (!hasExplicitHosts) {
631
- const selectedHosts = await promptUninstallHosts(scope, serverName);
661
+ const selectedHosts = await promptUninstallHosts(scope, {
662
+ explicitServerName,
663
+ });
632
664
  if (!selectedHosts) {
633
665
  log.warn("Uninstall cancelled");
634
666
  return null;
@@ -682,8 +714,13 @@ async function removeHostConfig(hostName, scope, serverName) {
682
714
  }
683
715
 
684
716
  async function runUninstallCommand(options) {
685
- const serverName = normalizeServerName(options.name || SERVER_NAME);
686
- const resolved = await resolveUninstallTargets(options, serverName);
717
+ const explicitServerName =
718
+ typeof options.name === "string" && options.name.trim().length > 0
719
+ ? normalizeServerName(options.name)
720
+ : null;
721
+ const resolved = await resolveUninstallTargets(options, {
722
+ explicitServerName,
723
+ });
687
724
  if (!resolved) return;
688
725
  const { scope, hosts } = resolved;
689
726
  const scopes = scopesFromUninstallScope(scope);
@@ -693,8 +730,35 @@ async function runUninstallCommand(options) {
693
730
  for (const singleScope of scopes) {
694
731
  for (const host of hosts) {
695
732
  mcpSpinner.text = `Removing from ${getHost(host).displayName} (${singleScope})...`;
733
+ // Resolve the concrete server name(s) to remove at the host/scope level.
734
+ // When --name was not given, this iterates every Flywheel-identifying
735
+ // entry (matching setup's prior-mode detector) so renamed or drifted
736
+ // entries (e.g. `[mcp_servers.flywheel-staging]`) are actually cleared.
696
737
  // eslint-disable-next-line no-await-in-loop
697
- mcpResults.push(await removeHostConfig(host, singleScope, serverName));
738
+ const targetNames = await resolveUninstallServerNamesForHost(
739
+ host,
740
+ singleScope,
741
+ { explicitServerName },
742
+ );
743
+ if (targetNames.length === 0) {
744
+ // eslint-disable-next-line no-await-in-loop
745
+ const hostForDisplay = getHost(host);
746
+ // eslint-disable-next-line no-await-in-loop
747
+ const filePath = await resolveMcpPath(
748
+ mcpCandidatesForScope(hostForDisplay, singleScope),
749
+ );
750
+ mcpResults.push({
751
+ host: hostForDisplay.displayName,
752
+ scope: singleScope,
753
+ filePath,
754
+ status: "not present",
755
+ });
756
+ continue;
757
+ }
758
+ for (const targetName of targetNames) {
759
+ // eslint-disable-next-line no-await-in-loop
760
+ mcpResults.push(await removeHostConfig(host, singleScope, targetName));
761
+ }
698
762
  }
699
763
  }
700
764
  mcpSpinner.succeed("MCP uninstall complete");
@@ -1,5 +1,5 @@
1
1
  {
2
- "sourceCommit": "ffedf6bc3851ff31746837968cf586a6fee876b4",
2
+ "sourceCommit": "ad9659b3de7545ce1411e819a0643cfe4c38888f",
3
3
  "files": [
4
4
  "auth/active-key-id.d.ts",
5
5
  "auth/active-key-id.js",
@@ -172,15 +172,15 @@ async function findFlywheelTomlEntryNames(filePath, targetOrigin) {
172
172
  return unique(matches);
173
173
  }
174
174
 
175
- async function findFlywheelMcpEntryNames({
175
+ export async function findFlywheelMcpEntryNames({
176
176
  hostName,
177
177
  scope,
178
- cwd,
179
- targetOrigin,
180
- getHost,
181
- resolveMcpPath,
182
- readJsonConfig,
183
- readYamlConfig,
178
+ cwd = process.cwd(),
179
+ targetOrigin = null,
180
+ getHost = defaultGetHost,
181
+ resolveMcpPath = defaultResolveMcpPath,
182
+ readJsonConfig = defaultReadJsonConfig,
183
+ readYamlConfig = defaultReadYamlConfig,
184
184
  }) {
185
185
  try {
186
186
  const host = getHost(hostName);
@@ -1,7 +1,14 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
1
5
  import { runCli as runSetupCli } from "./cli.mjs";
2
6
  import { runRuntimeCli } from "./runtime/delegate.mjs";
3
7
  import { RUNTIME_ALIASES } from "./runtime/vendor/flywheel-cli-dist/commands/aliases.js";
4
8
 
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const PACKAGE_JSON_PATH = path.resolve(__dirname, "..", "package.json");
11
+
5
12
  const ALIAS_RESERVED_COMMANDS = new Set([
6
13
  "setup",
7
14
  "uninstall",
@@ -9,6 +16,7 @@ const ALIAS_RESERVED_COMMANDS = new Set([
9
16
  "--help",
10
17
  "-h",
11
18
  ]);
19
+ const VERSION_REQUEST_TOKENS = new Set(["--version", "-v", "version"]);
12
20
 
13
21
  const HELP_EXTRAS = [
14
22
  {
@@ -49,6 +57,30 @@ function stripFormatTokens(argv) {
49
57
  return out;
50
58
  }
51
59
 
60
+ let cachedPackageVersion = null;
61
+
62
+ async function loadPackageVersion() {
63
+ if (cachedPackageVersion !== null) {
64
+ return cachedPackageVersion;
65
+ }
66
+ try {
67
+ const raw = await readFile(PACKAGE_JSON_PATH, "utf8");
68
+ const parsed = JSON.parse(raw);
69
+ if (typeof parsed.version === "string" && parsed.version.trim()) {
70
+ cachedPackageVersion = parsed.version.trim();
71
+ return cachedPackageVersion;
72
+ }
73
+ } catch {
74
+ // Fall through to deterministic fallback.
75
+ }
76
+ cachedPackageVersion = "0.0.0";
77
+ return cachedPackageVersion;
78
+ }
79
+
80
+ function isVersionRequest(argv) {
81
+ return argv.length === 1 && VERSION_REQUEST_TOKENS.has(argv[0]);
82
+ }
83
+
52
84
  /**
53
85
  * Detect a top-level help invocation.
54
86
  *
@@ -104,6 +136,12 @@ export async function runUnifiedCli(argv = process.argv.slice(2)) {
104
136
  throw new Error("runUnifiedCli expects an argv array.");
105
137
  }
106
138
 
139
+ if (isVersionRequest(argv)) {
140
+ process.stdout.write(`${await loadPackageVersion()}\n`);
141
+ process.exitCode = 0;
142
+ return;
143
+ }
144
+
107
145
  if (isCombinedHelpRequest(argv)) {
108
146
  // Preserve any --format token verbatim so the runtime's format validator
109
147
  // runs and rejects unsupported values (e.g. `--format=badvalue`) with the