@paradigma-inc/flywheel 0.1.52 → 0.1.60
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/README.md +121 -10
- package/package.json +1 -1
- package/skills/flywheel/references/flywheel-cli-tool-map.md +8 -0
- package/skills/flywheel/references/flywheel-mcp-tool-map.md +8 -0
- package/src/agents.mjs +114 -48
- package/src/cli.mjs +267 -35
- package/src/mcp-writer.mjs +190 -17
- package/src/runtime/required-runtime-commands.json +17 -4
- package/src/runtime/vendor/flywheel-cli-dist/auth/login.d.ts +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/auth/login.js +3 -6
- package/src/runtime/vendor/flywheel-cli-dist/auth/login.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/auth/profiles.d.ts +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/auth/profiles.js +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/auth/profiles.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/_admin-auth.d.ts +25 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/_admin-auth.js +35 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/_admin-auth.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/_stream-file-out.d.ts +39 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/_stream-file-out.js +72 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/_stream-file-out.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-compute-policy-get.d.ts +2 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-compute-policy-get.js +10 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-compute-policy-get.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-compute-policy-set.d.ts +2 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/{compute-policy-set.js → admin-compute-policy-set.js} +8 -3
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-compute-policy-set.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-graphs-export-stream.d.ts +2 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-graphs-export-stream.js +114 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-graphs-export-stream.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-graphs-export.d.ts +2 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-graphs-export.js +11 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-graphs-export.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-graphs-list.d.ts +4 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-graphs-list.js +38 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-graphs-list.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-nodes-audit-list.d.ts +2 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-nodes-audit-list.js +14 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-nodes-audit-list.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/enums.d.ts +6 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/enums.js +3 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/enums.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/env.d.ts +0 -4
- package/src/runtime/vendor/flywheel-cli-dist/commands/env.js +7 -12
- package/src/runtime/vendor/flywheel-cli-dist/commands/env.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/export-history.js +40 -30
- package/src/runtime/vendor/flywheel-cli-dist/commands/export-history.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/help.d.ts +2 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/help.js +60 -10
- package/src/runtime/vendor/flywheel-cli-dist/commands/help.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-create.d.ts +2 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-create.js +25 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-create.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-delete.d.ts +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-delete.js +6 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-delete.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-list.d.ts +2 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-list.js +10 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-list.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-runs-list.d.ts +2 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-runs-list.js +15 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-runs-list.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-secrets-create.d.ts +2 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-secrets-create.js +8 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-secrets-create.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-secrets-delete.d.ts +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-secrets-delete.js +6 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-secrets-delete.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-secrets-list.d.ts +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-secrets-list.js +6 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-secrets-list.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-secrets-update.d.ts +2 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-secrets-update.js +8 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-secrets-update.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-set-enabled.d.ts +2 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-set-enabled.js +8 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-set-enabled.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-update.d.ts +2 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-update.js +29 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/hooks-update.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/index.js +32 -6
- package/src/runtime/vendor/flywheel-cli-dist/commands/index.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/profile-set.js +4 -2
- package/src/runtime/vendor/flywheel-cli-dist/commands/profile-set.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/admin.d.ts +8 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/admin.js +118 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/admin.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/auth.js +2 -2
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/auth.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/env.js +7 -5
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/env.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/hooks.d.ts +2 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/hooks.js +244 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/hooks.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/lifecycle.js +1 -16
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/lifecycle.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/profile.js +2 -2
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/profile.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/resources.js +1 -31
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/resources.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry.d.ts +2 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry.js +4 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/main.js +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/main.js.map +1 -1
- package/src/runtime/vendor/manifest.json +61 -10
- package/src/setup/io/patchers/json.mjs +2 -0
- package/src/setup/io/patchers/toml.mjs +7 -1
- package/src/setup/io/patchers/yaml.mjs +2 -0
- package/src/setup/modes/cli.mjs +41 -16
- package/src/setup/modes/mcp-command.mjs +98 -19
- package/src/setup/modes/mcp.mjs +26 -1
- package/src/setup/shared/prior-mode-detect.mjs +133 -14
- package/src/skill-installer.mjs +179 -1
- package/src/unified-cli.mjs +38 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/compute-policy-get.d.ts +0 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/compute-policy-get.js +0 -6
- package/src/runtime/vendor/flywheel-cli-dist/commands/compute-policy-get.js.map +0 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/compute-policy-set.d.ts +0 -2
- package/src/runtime/vendor/flywheel-cli-dist/commands/compute-policy-set.js.map +0 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/nodes-audit-list.d.ts +0 -2
- package/src/runtime/vendor/flywheel-cli-dist/commands/nodes-audit-list.js +0 -7
- package/src/runtime/vendor/flywheel-cli-dist/commands/nodes-audit-list.js.map +0 -1
package/src/cli.mjs
CHANGED
|
@@ -2,18 +2,21 @@ import { Command } from "commander";
|
|
|
2
2
|
import { checkbox, select } from "@inquirer/prompts";
|
|
3
3
|
import ora from "ora";
|
|
4
4
|
import pc from "picocolors";
|
|
5
|
-
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { access, readFile, rm } from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
ALL_HOST_NAMES,
|
|
10
10
|
SERVER_NAME,
|
|
11
11
|
SETUP_HOST_NAMES,
|
|
12
|
+
SKILL_ONLY_HOST_NAMES,
|
|
12
13
|
detectHosts,
|
|
13
14
|
getHost,
|
|
15
|
+
isSkillOnlyHost,
|
|
14
16
|
normalizeHosts,
|
|
15
17
|
} from "./agents.mjs";
|
|
16
18
|
import {
|
|
19
|
+
readEntryServerUrl,
|
|
17
20
|
readNamedConfigEntry,
|
|
18
21
|
readJsonConfig,
|
|
19
22
|
readTomlServerEntry,
|
|
@@ -28,6 +31,8 @@ import {
|
|
|
28
31
|
listBundledSkills,
|
|
29
32
|
partitionInstallSkillHosts,
|
|
30
33
|
removeInstalledSkillsForHosts,
|
|
34
|
+
resolveInstalledSkillDir,
|
|
35
|
+
resolveFlywheelInstalledMarkerPath,
|
|
31
36
|
SUPPORTED_INSTALL_SKILL_HOSTS,
|
|
32
37
|
resolvePackageRoot,
|
|
33
38
|
} from "./skill-installer.mjs";
|
|
@@ -42,6 +47,15 @@ import {
|
|
|
42
47
|
import { findFlywheelMcpEntryNames } from "./setup/shared/prior-mode-detect.mjs";
|
|
43
48
|
import { renderSetupSummary } from "./setup/shared/summary.mjs";
|
|
44
49
|
|
|
50
|
+
async function pathExists(targetPath) {
|
|
51
|
+
try {
|
|
52
|
+
await access(targetPath);
|
|
53
|
+
return true;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
45
59
|
export { buildGuidedSkillInstallPrompt };
|
|
46
60
|
|
|
47
61
|
const INSTALL_SKILL_BASE_URL = "https://flywheel.paradigma.inc";
|
|
@@ -95,9 +109,12 @@ function normalizeAuthMode(value) {
|
|
|
95
109
|
function selectedHostsFromOptions(options) {
|
|
96
110
|
const hosts = [];
|
|
97
111
|
if (options.claude) hosts.push("claude");
|
|
112
|
+
if (options.cursor) hosts.push("cursor");
|
|
113
|
+
if (options.universal) hosts.push("universal");
|
|
114
|
+
if (options.antigravity) hosts.push("antigravity");
|
|
98
115
|
if (options.opencode) hosts.push("opencode");
|
|
99
116
|
if (options.codex) hosts.push("codex");
|
|
100
|
-
if (options.
|
|
117
|
+
if (options.gemini) hosts.push("gemini");
|
|
101
118
|
if (options.piMono) hosts.push("pi-mono");
|
|
102
119
|
if (options.hermesAgent) hosts.push("hermes-agent");
|
|
103
120
|
if (options.openclaw) hosts.push("openclaw");
|
|
@@ -109,6 +126,9 @@ const UNINSTALL_OPTION_KEY_BY_HOST = Object.freeze({
|
|
|
109
126
|
codex: "codex",
|
|
110
127
|
opencode: "opencode",
|
|
111
128
|
cursor: "cursor",
|
|
129
|
+
gemini: "gemini",
|
|
130
|
+
universal: "universal",
|
|
131
|
+
antigravity: "antigravity",
|
|
112
132
|
"pi-mono": "piMono",
|
|
113
133
|
"hermes-agent": "hermesAgent",
|
|
114
134
|
openclaw: "openclaw",
|
|
@@ -167,6 +187,9 @@ async function isAlreadyConfigured(hostName, scope, serverName) {
|
|
|
167
187
|
|
|
168
188
|
async function readExistingServerState(hostName, scope, serverName) {
|
|
169
189
|
const host = getHost(hostName);
|
|
190
|
+
if (isSkillOnlyHost(host)) {
|
|
191
|
+
return { exists: false, url: null, mcpPath: null };
|
|
192
|
+
}
|
|
170
193
|
const mcpPath = await resolveMcpPath(mcpCandidatesForScope(host, scope));
|
|
171
194
|
|
|
172
195
|
if (host.mcp.configType === "toml") {
|
|
@@ -181,7 +204,7 @@ async function readExistingServerState(hostName, scope, serverName) {
|
|
|
181
204
|
const entry = readNamedConfigEntry(existing, host.mcp.configKey, serverName);
|
|
182
205
|
return {
|
|
183
206
|
exists: entry !== null,
|
|
184
|
-
url:
|
|
207
|
+
url: readEntryServerUrl(entry),
|
|
185
208
|
mcpPath,
|
|
186
209
|
};
|
|
187
210
|
}
|
|
@@ -289,7 +312,19 @@ async function promptSetupMode() {
|
|
|
289
312
|
return await select(buildSetupModePromptConfig());
|
|
290
313
|
}
|
|
291
314
|
|
|
292
|
-
|
|
315
|
+
/**
|
|
316
|
+
* Validate the flag combination for --mode cli. Rejections raise
|
|
317
|
+
* `SetupModeUsageError` so the top-level handler reports them as usage
|
|
318
|
+
* errors (exit code 2) and not as operational failures.
|
|
319
|
+
*
|
|
320
|
+
* `--oauth` controls the MCP config written in --mode mcp (it omits the
|
|
321
|
+
* Authorization header so the IDE can discover MCP 2.0 OAuth instead).
|
|
322
|
+
* --mode cli writes no MCP config at all, so there is nothing for --oauth
|
|
323
|
+
* to modify. Silently accepting it would look successful but still fall
|
|
324
|
+
* back to API-key auth in the runtime config — a misconfiguration. Reject
|
|
325
|
+
* it up front with guidance toward the correct invocation.
|
|
326
|
+
*/
|
|
327
|
+
function validateCliModeFlags(options) {
|
|
293
328
|
if (options.installSkill && options.skipSkill) {
|
|
294
329
|
throw new SetupModeUsageError(
|
|
295
330
|
"--install-skill and --skip-skill cannot be used together.",
|
|
@@ -302,10 +337,15 @@ function validateCliModeSkillFlags(options) {
|
|
|
302
337
|
"--skip-skill is not supported with --mode cli; use --mode mcp --skip-skill for MCP-only setup.",
|
|
303
338
|
);
|
|
304
339
|
}
|
|
340
|
+
if (options.oauth === true) {
|
|
341
|
+
throw new SetupModeUsageError(
|
|
342
|
+
"--oauth is not supported with --mode cli. --oauth only affects the MCP config written by --mode mcp; re-run with --mode mcp --oauth, or drop --oauth for a CLI-mode setup.",
|
|
343
|
+
);
|
|
344
|
+
}
|
|
305
345
|
}
|
|
306
346
|
|
|
307
347
|
async function runCliModeCommand(options) {
|
|
308
|
-
|
|
348
|
+
validateCliModeFlags(options);
|
|
309
349
|
const scope = options.project ? "project" : "global";
|
|
310
350
|
const baseUrl = normalizeBaseUrl(options.baseUrl || DEFAULT_BASE_URL);
|
|
311
351
|
const serverUrl = `${baseUrl}/mcp-server`;
|
|
@@ -484,6 +524,9 @@ function isLikelyFlywheelMcpUrl(url) {
|
|
|
484
524
|
|
|
485
525
|
async function listHostMcpEntries(hostName, scope) {
|
|
486
526
|
const host = getHost(hostName);
|
|
527
|
+
if (isSkillOnlyHost(host)) {
|
|
528
|
+
return [];
|
|
529
|
+
}
|
|
487
530
|
const filePath = await resolveMcpPath(mcpCandidatesForScope(host, scope));
|
|
488
531
|
|
|
489
532
|
if (host.mcp.configType === "toml") {
|
|
@@ -517,7 +560,7 @@ async function listHostMcpEntries(hostName, scope) {
|
|
|
517
560
|
.filter(([, entry]) => entry && typeof entry === "object" && !Array.isArray(entry))
|
|
518
561
|
.map(([serverName, entry]) => ({
|
|
519
562
|
serverName,
|
|
520
|
-
url:
|
|
563
|
+
url: readEntryServerUrl(entry),
|
|
521
564
|
}));
|
|
522
565
|
}
|
|
523
566
|
|
|
@@ -533,14 +576,68 @@ async function resolveHostsWithRemainingFlywheelMcpEntries({ scope }) {
|
|
|
533
576
|
return remainingHosts;
|
|
534
577
|
}
|
|
535
578
|
|
|
579
|
+
/**
|
|
580
|
+
* Determine which non-uninstalled hosts still claim bundled skill artifacts
|
|
581
|
+
* via their install marker, and therefore must keep their (possibly shared)
|
|
582
|
+
* skill directories intact.
|
|
583
|
+
*
|
|
584
|
+
* The install marker (`.flywheel-installed-by-<host>`) is written by
|
|
585
|
+
* `installBundledSkillForHosts` for any host whose install path produces no
|
|
586
|
+
* MCP entry:
|
|
587
|
+
*
|
|
588
|
+
* - skill-only hosts (universal, antigravity): marker always written.
|
|
589
|
+
* - MCP-capable hosts (codex, opencode, cursor, claude, pi-mono, openclaw,
|
|
590
|
+
* gemini): marker written ONLY when the host was installed via
|
|
591
|
+
* `--mode cli`, because CLI-mode installs write no MCP entry.
|
|
592
|
+
*
|
|
593
|
+
* A marker at a skill-root is therefore the unambiguous "this host still
|
|
594
|
+
* owns these skills" signal for both categories. Narrowing the retain pass
|
|
595
|
+
* to skill-only hosts would let `uninstall --universal` silently wipe the
|
|
596
|
+
* shared `.agents/skills/` dir out from under a still-installed CLI-mode
|
|
597
|
+
* codex. Using dir existence alone would over-retain for hosts that never
|
|
598
|
+
* installed anything. The per-host marker resolves both.
|
|
599
|
+
*
|
|
600
|
+
* `excludedHostNames` is the list of hosts currently being uninstalled;
|
|
601
|
+
* their markers will be removed and their artifacts are expected to go, so
|
|
602
|
+
* they are excluded from retention here.
|
|
603
|
+
*/
|
|
604
|
+
async function resolveSkillOnlyHostsRetainingArtifacts({
|
|
605
|
+
scope,
|
|
606
|
+
excludedHostNames = [],
|
|
607
|
+
}) {
|
|
608
|
+
const excluded = new Set(excludedHostNames);
|
|
609
|
+
const retained = [];
|
|
610
|
+
for (const hostName of SUPPORTED_INSTALL_SKILL_HOSTS) {
|
|
611
|
+
if (excluded.has(hostName)) continue;
|
|
612
|
+
let markerPath;
|
|
613
|
+
try {
|
|
614
|
+
markerPath = resolveFlywheelInstalledMarkerPath(
|
|
615
|
+
process.cwd(),
|
|
616
|
+
hostName,
|
|
617
|
+
scope,
|
|
618
|
+
);
|
|
619
|
+
} catch {
|
|
620
|
+
// Host isn't registered with the flywheel skill installer; no marker
|
|
621
|
+
// to consult, nothing to retain.
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
// eslint-disable-next-line no-await-in-loop
|
|
625
|
+
const markerExists = await pathExists(markerPath);
|
|
626
|
+
if (markerExists) {
|
|
627
|
+
retained.push(hostName);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return retained;
|
|
631
|
+
}
|
|
632
|
+
|
|
536
633
|
async function findFlywheelServerNamesOnHost(hostName, scope) {
|
|
537
634
|
// Mirror setup's prior-mode detector: walk every MCP entry on the host and
|
|
538
635
|
// keep those whose URL is Flywheel-identifying (hostname contains "flywheel"
|
|
539
636
|
// or path === /mcp-server with a matching target origin). Uninstall must
|
|
540
|
-
// catch renamed
|
|
541
|
-
// setup already detects; otherwise a user
|
|
542
|
-
// name is stuck between setup ("already
|
|
543
|
-
// configured").
|
|
637
|
+
// catch renamed or drifted entries (e.g. a non-default TOML section name
|
|
638
|
+
// under `[mcp_servers.*]`) that setup already detects; otherwise a user
|
|
639
|
+
// whose host has a non-default entry name is stuck between setup ("already
|
|
640
|
+
// installed") and uninstall ("not configured").
|
|
544
641
|
return await findFlywheelMcpEntryNames({ hostName, scope }).catch(() => []);
|
|
545
642
|
}
|
|
546
643
|
|
|
@@ -574,10 +671,92 @@ async function isConfiguredForUninstallScope(
|
|
|
574
671
|
{ explicitServerName },
|
|
575
672
|
);
|
|
576
673
|
if (targets.length > 0) return true;
|
|
674
|
+
// Skill-only hosts and CLI-mode MCP-capable hosts never write an MCP
|
|
675
|
+
// entry, so the sweep above finds nothing for them. Fall back to the
|
|
676
|
+
// install marker: if this host wrote its own ownership marker in the
|
|
677
|
+
// selected scope, it IS configured for the purposes of the uninstall
|
|
678
|
+
// prompt.
|
|
679
|
+
if (SUPPORTED_INSTALL_SKILL_HOSTS.includes(hostName)) {
|
|
680
|
+
try {
|
|
681
|
+
const markerPath = resolveFlywheelInstalledMarkerPath(
|
|
682
|
+
process.cwd(),
|
|
683
|
+
hostName,
|
|
684
|
+
singleScope,
|
|
685
|
+
);
|
|
686
|
+
// eslint-disable-next-line no-await-in-loop
|
|
687
|
+
if (await pathExists(markerPath)) return true;
|
|
688
|
+
} catch {
|
|
689
|
+
// Host isn't registered with the skill installer; nothing to check.
|
|
690
|
+
}
|
|
691
|
+
}
|
|
577
692
|
}
|
|
578
693
|
return false;
|
|
579
694
|
}
|
|
580
695
|
|
|
696
|
+
/**
|
|
697
|
+
* Public wrapper exposing the uninstall-eligibility check used by the
|
|
698
|
+
* interactive prompt. Returns true when the host has either a Flywheel MCP
|
|
699
|
+
* entry OR a Flywheel install marker in the requested scope. Exported so
|
|
700
|
+
* tests can verify that skill-only hosts surface as configurable in the
|
|
701
|
+
* prompt even though they write no MCP entry.
|
|
702
|
+
*/
|
|
703
|
+
export async function isHostConfiguredForUninstall({
|
|
704
|
+
hostName,
|
|
705
|
+
scope = "project",
|
|
706
|
+
serverName = SERVER_NAME,
|
|
707
|
+
} = {}) {
|
|
708
|
+
// Mirror `runUninstallCommand`'s derivation: only treat the caller's
|
|
709
|
+
// `serverName` as "explicit" when it differs from the package default.
|
|
710
|
+
// An explicit name short-circuits the Flywheel-identifying sweep in
|
|
711
|
+
// `resolveUninstallServerNamesForHost`, which the default-named case
|
|
712
|
+
// needs to run so drifted entries (and the marker fallback for
|
|
713
|
+
// skill-only / CLI-mode hosts) are reachable.
|
|
714
|
+
const explicitServerName =
|
|
715
|
+
typeof serverName === "string" &&
|
|
716
|
+
serverName.length > 0 &&
|
|
717
|
+
serverName !== SERVER_NAME
|
|
718
|
+
? serverName
|
|
719
|
+
: null;
|
|
720
|
+
return isConfiguredForUninstallScope(hostName, scope, { explicitServerName });
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Remove every Flywheel install marker for the requested hosts across the
|
|
725
|
+
* requested scopes. Exported and implemented as a standalone helper so it
|
|
726
|
+
* can run independently of bundled-skill discovery: a package with a
|
|
727
|
+
* missing or trimmed `skills/` directory must still be able to uninstall
|
|
728
|
+
* cleanly, otherwise stale markers leak into later prompt-eligibility,
|
|
729
|
+
* retention, and prior-mode-detection logic. `rm ..., { force: true }`
|
|
730
|
+
* makes the operation idempotent — hosts that never wrote a marker (e.g.
|
|
731
|
+
* MCP-capable hosts installed only via `--mode mcp`) are silent no-ops.
|
|
732
|
+
*
|
|
733
|
+
* Hosts not registered with the Flywheel skill installer are skipped
|
|
734
|
+
* rather than throwing, so callers can pass the full uninstall host list
|
|
735
|
+
* without pre-filtering.
|
|
736
|
+
*/
|
|
737
|
+
export async function cleanupHostInstallMarkers({
|
|
738
|
+
cwd = process.cwd(),
|
|
739
|
+
scopes = ["project"],
|
|
740
|
+
hostNames = [],
|
|
741
|
+
} = {}) {
|
|
742
|
+
for (const singleScope of scopes) {
|
|
743
|
+
for (const hostName of hostNames) {
|
|
744
|
+
let markerPath;
|
|
745
|
+
try {
|
|
746
|
+
markerPath = resolveFlywheelInstalledMarkerPath(
|
|
747
|
+
cwd,
|
|
748
|
+
hostName,
|
|
749
|
+
singleScope,
|
|
750
|
+
);
|
|
751
|
+
} catch {
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
// eslint-disable-next-line no-await-in-loop
|
|
755
|
+
await rm(markerPath, { force: true });
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
581
760
|
async function promptUninstallScope(defaultScope) {
|
|
582
761
|
try {
|
|
583
762
|
return await select({
|
|
@@ -674,6 +853,14 @@ async function resolveUninstallTargets(options, { explicitServerName }) {
|
|
|
674
853
|
|
|
675
854
|
async function removeHostConfig(hostName, scope, serverName) {
|
|
676
855
|
const host = getHost(hostName);
|
|
856
|
+
if (isSkillOnlyHost(host)) {
|
|
857
|
+
return {
|
|
858
|
+
host: host.displayName,
|
|
859
|
+
scope,
|
|
860
|
+
filePath: null,
|
|
861
|
+
status: "not present",
|
|
862
|
+
};
|
|
863
|
+
}
|
|
677
864
|
const filePath = await resolveMcpPath(mcpCandidatesForScope(host, scope));
|
|
678
865
|
|
|
679
866
|
if (host.mcp.configType === "toml") {
|
|
@@ -733,7 +920,8 @@ async function runUninstallCommand(options) {
|
|
|
733
920
|
// Resolve the concrete server name(s) to remove at the host/scope level.
|
|
734
921
|
// When --name was not given, this iterates every Flywheel-identifying
|
|
735
922
|
// entry (matching setup's prior-mode detector) so renamed or drifted
|
|
736
|
-
// entries (
|
|
923
|
+
// entries (non-default TOML section names under `[mcp_servers.*]`) are
|
|
924
|
+
// actually cleared.
|
|
737
925
|
// eslint-disable-next-line no-await-in-loop
|
|
738
926
|
const targetNames = await resolveUninstallServerNamesForHost(
|
|
739
927
|
host,
|
|
@@ -743,10 +931,16 @@ async function runUninstallCommand(options) {
|
|
|
743
931
|
if (targetNames.length === 0) {
|
|
744
932
|
// eslint-disable-next-line no-await-in-loop
|
|
745
933
|
const hostForDisplay = getHost(host);
|
|
746
|
-
//
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
934
|
+
// Skill-only hosts (universal, antigravity) have no MCP surface
|
|
935
|
+
// at all — `host.mcp` is null, so dereferencing `projectPaths` via
|
|
936
|
+
// `mcpCandidatesForScope` throws. Report the absence without
|
|
937
|
+
// consulting a path that doesn't exist for them.
|
|
938
|
+
const filePath = isSkillOnlyHost(hostForDisplay)
|
|
939
|
+
? null
|
|
940
|
+
: // eslint-disable-next-line no-await-in-loop
|
|
941
|
+
await resolveMcpPath(
|
|
942
|
+
mcpCandidatesForScope(hostForDisplay, singleScope),
|
|
943
|
+
);
|
|
750
944
|
mcpResults.push({
|
|
751
945
|
host: hostForDisplay.displayName,
|
|
752
946
|
scope: singleScope,
|
|
@@ -774,12 +968,26 @@ async function runUninstallCommand(options) {
|
|
|
774
968
|
if (bundledSkillNames.length > 0) {
|
|
775
969
|
const skillSpinner = ora("Removing Flywheel bundled skills...").start();
|
|
776
970
|
for (const singleScope of scopes) {
|
|
777
|
-
//
|
|
778
|
-
//
|
|
971
|
+
// Retain any skill directory still claimed by a non-uninstalled host.
|
|
972
|
+
// Two signals combine: (a) MCP-backed hosts whose Flywheel entries
|
|
973
|
+
// survived the MCP-removal pass above, and (b) any host with bundled
|
|
974
|
+
// skill artifacts still on disk (including skill-only hosts like
|
|
975
|
+
// universal/antigravity, which never advertise themselves via MCP
|
|
976
|
+
// entries). Without (b), uninstalling one MCP host would silently
|
|
977
|
+
// wipe a shared `.agents/skills/<skill>/` that another host still
|
|
978
|
+
// relies on.
|
|
779
979
|
// eslint-disable-next-line no-await-in-loop
|
|
780
|
-
const
|
|
980
|
+
const mcpRetained = await resolveHostsWithRemainingFlywheelMcpEntries({
|
|
781
981
|
scope: singleScope,
|
|
782
982
|
});
|
|
983
|
+
// eslint-disable-next-line no-await-in-loop
|
|
984
|
+
const skillOnlyRetained = await resolveSkillOnlyHostsRetainingArtifacts({
|
|
985
|
+
scope: singleScope,
|
|
986
|
+
excludedHostNames: supportedHosts,
|
|
987
|
+
});
|
|
988
|
+
const configuredRetainHostNames = [
|
|
989
|
+
...new Set([...mcpRetained, ...skillOnlyRetained]),
|
|
990
|
+
];
|
|
783
991
|
skillSpinner.text = `Removing bundled skills from ${supportedHosts.length} host(s) (${singleScope})...`;
|
|
784
992
|
// eslint-disable-next-line no-await-in-loop
|
|
785
993
|
bundledSkillResults.push(
|
|
@@ -794,6 +1002,19 @@ async function runUninstallCommand(options) {
|
|
|
794
1002
|
}
|
|
795
1003
|
skillSpinner.succeed("Bundled skill uninstall complete");
|
|
796
1004
|
}
|
|
1005
|
+
|
|
1006
|
+
// Install-marker cleanup runs independently of bundled-skill discovery.
|
|
1007
|
+
// A broken or trimmed package (missing `skills/`) can leave
|
|
1008
|
+
// `bundledSkillNames` empty — the skill-removal pass above legitimately
|
|
1009
|
+
// no-ops in that case, but the ownership markers must still be cleared,
|
|
1010
|
+
// otherwise a later `uninstall`/`setup` would treat the uninstalled
|
|
1011
|
+
// host as still owning shared skill dirs (wrong retention) or as a
|
|
1012
|
+
// prior CLI install (false cross-mode conflict).
|
|
1013
|
+
await cleanupHostInstallMarkers({
|
|
1014
|
+
cwd: process.cwd(),
|
|
1015
|
+
scopes,
|
|
1016
|
+
hostNames: supportedHosts,
|
|
1017
|
+
});
|
|
797
1018
|
}
|
|
798
1019
|
|
|
799
1020
|
log.blank();
|
|
@@ -882,15 +1103,25 @@ Examples:
|
|
|
882
1103
|
${pc.green("npx --yes @paradigma-inc/flywheel setup --mode mcp --auth-mode device --codex --project")}
|
|
883
1104
|
${pc.green("npx --yes @paradigma-inc/flywheel setup --mode cli --codex --project")}
|
|
884
1105
|
${pc.green("npx --yes @paradigma-inc/flywheel setup --mode mcp --yes --codex --claude --cursor")}
|
|
885
|
-
${pc.green(
|
|
886
|
-
"npx --yes @paradigma-inc/flywheel uninstall --scope all --hosts codex,claude,opencode,cursor,pi-mono,hermes-agent,openclaw",
|
|
887
|
-
)}
|
|
1106
|
+
${pc.green("npx --yes @paradigma-inc/flywheel uninstall --yes --scope all")}
|
|
888
1107
|
`,
|
|
889
1108
|
);
|
|
890
1109
|
|
|
891
1110
|
program
|
|
892
1111
|
.command("setup")
|
|
893
1112
|
.description("Set up Flywheel for your AI coding host")
|
|
1113
|
+
.optionsGroup("Hosts:")
|
|
1114
|
+
.option("--claude", "Set up for Claude Code")
|
|
1115
|
+
.option("--cursor", "Set up for Cursor")
|
|
1116
|
+
.option("--universal", "Set up for Universal (.agents/skills)")
|
|
1117
|
+
.option("--antigravity", "Set up for Antigravity (.agent/skills)")
|
|
1118
|
+
.option("--opencode", "Set up for OpenCode")
|
|
1119
|
+
.option("--codex", "Set up for Codex")
|
|
1120
|
+
.option("--gemini", "Set up for Gemini CLI")
|
|
1121
|
+
.option("--pi-mono", "Set up for Pi (pi-mono)")
|
|
1122
|
+
.option("--hermes-agent", "Set up for Hermes Agent")
|
|
1123
|
+
.option("--openclaw", "Set up for OpenClaw")
|
|
1124
|
+
.optionsGroup("Mode:")
|
|
894
1125
|
.option(
|
|
895
1126
|
"--mode <mode>",
|
|
896
1127
|
"Install mode: mcp | cli (required in non-interactive; prompted in TTY, default: mcp)",
|
|
@@ -899,33 +1130,31 @@ Examples:
|
|
|
899
1130
|
)
|
|
900
1131
|
.option("--mcp", "Alias for --mode mcp")
|
|
901
1132
|
.option("--cli", "Alias for --mode cli")
|
|
902
|
-
.
|
|
903
|
-
.option("--opencode", "Set up for OpenCode")
|
|
904
|
-
.option("--codex", "Set up for Codex")
|
|
905
|
-
.option("--cursor", "Set up for Cursor")
|
|
906
|
-
.option("--pi-mono", "Set up for Pi (pi-mono)")
|
|
907
|
-
.option("--hermes-agent", "Set up for Hermes Agent")
|
|
908
|
-
.option("--openclaw", "Set up for OpenClaw")
|
|
1133
|
+
.optionsGroup("Scope:")
|
|
909
1134
|
.option(
|
|
910
1135
|
"-p, --project",
|
|
911
1136
|
"Configure for current project instead of globally",
|
|
912
1137
|
)
|
|
913
|
-
.option("-y, --yes", "Skip
|
|
914
|
-
.
|
|
1138
|
+
.option("-y, --yes", "Skip confirmation prompts")
|
|
1139
|
+
.optionsGroup("Authentication:")
|
|
915
1140
|
.option("--api-key <key>", "Use API key authentication")
|
|
1141
|
+
.option("--oauth", "Use OAuth endpoint (IDE handles auth flow)")
|
|
1142
|
+
.optionsGroup("Skills:")
|
|
1143
|
+
.option("--install-skill", "Install or refresh the bundled Flywheel skills")
|
|
1144
|
+
.option("--skip-skill", "Skip bundled skill installation (MCP-only setup)")
|
|
1145
|
+
.optionsGroup("Advanced:")
|
|
916
1146
|
.option(
|
|
917
1147
|
"--auth-mode <mode>",
|
|
918
|
-
"
|
|
1148
|
+
"API-key mint flow: auto | loopback | device (default: auto)",
|
|
919
1149
|
normalizeAuthMode,
|
|
920
1150
|
"auto",
|
|
921
1151
|
)
|
|
922
|
-
.option("--install-skill", "Install or refresh the bundled Flywheel skills")
|
|
923
|
-
.option("--skip-skill", "Skip bundled skill installation (MCP-only setup)")
|
|
924
1152
|
.option(
|
|
925
1153
|
"--base-url <url>",
|
|
926
1154
|
`Public Flywheel origin used for setup and MCP config (default: ${DEFAULT_BASE_URL})`,
|
|
927
1155
|
)
|
|
928
1156
|
.option("--name <name>", `MCP server name (default: ${SERVER_NAME})`)
|
|
1157
|
+
.option("--force", "Reconcile prior install mode automatically via uninstall")
|
|
929
1158
|
.action(async (options) => {
|
|
930
1159
|
await runSetupCommandViaRouter(options);
|
|
931
1160
|
});
|
|
@@ -934,15 +1163,18 @@ Examples:
|
|
|
934
1163
|
.command("uninstall")
|
|
935
1164
|
.description("Remove Flywheel MCP entries and bundled skills from selected hosts")
|
|
936
1165
|
.option("--claude", "Uninstall for Claude Code")
|
|
1166
|
+
.option("--cursor", "Uninstall for Cursor")
|
|
1167
|
+
.option("--universal", "Uninstall bundled skills from .agents/skills")
|
|
1168
|
+
.option("--antigravity", "Uninstall bundled skills from .agent/skills")
|
|
937
1169
|
.option("--opencode", "Uninstall for OpenCode")
|
|
938
1170
|
.option("--codex", "Uninstall for Codex")
|
|
939
|
-
.option("--
|
|
1171
|
+
.option("--gemini", "Uninstall for Gemini CLI")
|
|
940
1172
|
.option("--pi-mono", "Uninstall for Pi (pi-mono)")
|
|
941
1173
|
.option("--hermes-agent", "Uninstall for Hermes Agent")
|
|
942
1174
|
.option("--openclaw", "Uninstall for OpenClaw")
|
|
943
1175
|
.option(
|
|
944
1176
|
"--hosts <list>",
|
|
945
|
-
"Comma-separated:
|
|
1177
|
+
"Comma-separated: claude,cursor,universal,antigravity,opencode,codex,gemini,pi-mono,hermes-agent,openclaw",
|
|
946
1178
|
)
|
|
947
1179
|
.option("--scope <scope>", "all | global | project")
|
|
948
1180
|
.option(
|