@paradigma-inc/flywheel 0.1.0 → 0.1.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.
- package/README.md +4 -10
- package/package.json +12 -1
- package/src/agents.mjs +113 -61
- package/src/cli.mjs +390 -329
- package/src/mcp-writer.mjs +105 -76
- package/src/setup-auth.mjs +7 -2
- package/tests/mcp-writer.test.mjs +0 -117
package/README.md
CHANGED
|
@@ -27,13 +27,15 @@ npx @paradigma-inc/flywheel setup --base-url https://flywheel-staging.paradigma.
|
|
|
27
27
|
|
|
28
28
|
- Staging remains protected by the existing staging password gate in the WebUI.
|
|
29
29
|
The browser auth step must pass that gate before key creation can complete.
|
|
30
|
-
- Setup writes one Flywheel server entry per host+scope.
|
|
31
|
-
|
|
30
|
+
- Setup writes one Flywheel server entry per host+scope. If a host is already
|
|
31
|
+
configured, setup leaves that entry in place (Context7-style).
|
|
32
32
|
|
|
33
33
|
## Commands
|
|
34
34
|
|
|
35
35
|
- `setup`: interview + browser auth bridge + idempotent host config writes.
|
|
36
36
|
- `uninstall`: remove Flywheel MCP entries from selected host configs.
|
|
37
|
+
- Add `--yes` to skip interactive prompts in the Flywheel wizard.
|
|
38
|
+
- `npx --yes` only auto-confirms `npx`; it does not imply `flywheel --yes`.
|
|
37
39
|
|
|
38
40
|
## Release channels
|
|
39
41
|
|
|
@@ -43,14 +45,6 @@ npx @paradigma-inc/flywheel setup --base-url https://flywheel-staging.paradigma.
|
|
|
43
45
|
npx @paradigma-inc/flywheel setup
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
- Internal staging builds are published to GitHub Packages (`staging` tag) and
|
|
47
|
-
require npm auth for `npm.pkg.github.com`.
|
|
48
|
-
- Example internal install:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
npx --registry=https://npm.pkg.github.com @paradigma-inc/flywheel@staging setup
|
|
52
|
-
```
|
|
53
|
-
|
|
54
48
|
## Supported hosts
|
|
55
49
|
|
|
56
50
|
- Codex (`~/.codex/config.toml` or `.codex/config.toml`)
|
package/package.json
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paradigma-inc/flywheel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "One-command setup for Flywheel MCP on Codex, Claude Code, and OpenCode",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"bin/",
|
|
8
|
+
"src/",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
6
11
|
"bin": {
|
|
7
12
|
"flywheel": "bin/flywheel.js"
|
|
8
13
|
},
|
|
9
14
|
"scripts": {
|
|
10
15
|
"test": "node --test tests/*.test.mjs"
|
|
11
16
|
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@inquirer/prompts": "^8.2.0",
|
|
19
|
+
"commander": "^14.0.1",
|
|
20
|
+
"ora": "^9.0.0",
|
|
21
|
+
"picocolors": "^1.1.1"
|
|
22
|
+
},
|
|
12
23
|
"engines": {
|
|
13
24
|
"node": ">=20"
|
|
14
25
|
},
|
package/src/agents.mjs
CHANGED
|
@@ -1,97 +1,149 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
1
2
|
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
|
|
4
5
|
export const SERVER_NAME = "flywheel";
|
|
5
|
-
export const
|
|
6
|
-
|
|
7
|
-
export const HOST_LABELS = {
|
|
8
|
-
codex: "Codex",
|
|
6
|
+
export const ALL_HOST_NAMES = ["claude", "opencode", "codex"];
|
|
7
|
+
export const SETUP_HOST_NAMES = {
|
|
9
8
|
claude: "Claude Code",
|
|
10
9
|
opencode: "OpenCode",
|
|
10
|
+
codex: "Codex",
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
const HOME = os.homedir();
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
* Patterns mirrored from Context7's `setup/agents.ts`.
|
|
17
|
-
*/
|
|
18
|
-
export const AGENT_CONFIG = {
|
|
19
|
-
codex: {
|
|
20
|
-
configType: "toml",
|
|
21
|
-
projectPath: path.join(".codex", "config.toml"),
|
|
22
|
-
globalPath: path.join(HOME, ".codex", "config.toml"),
|
|
23
|
-
buildEntry: ({ serverUrl, apiKey }) => ({
|
|
24
|
-
type: "http",
|
|
25
|
-
url: serverUrl,
|
|
26
|
-
headers: {
|
|
27
|
-
Authorization: `Bearer ${apiKey}`,
|
|
28
|
-
},
|
|
29
|
-
}),
|
|
30
|
-
},
|
|
15
|
+
const hosts = {
|
|
31
16
|
claude: {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
17
|
+
name: "claude",
|
|
18
|
+
displayName: "Claude Code",
|
|
19
|
+
mcp: {
|
|
20
|
+
configType: "json",
|
|
21
|
+
projectPaths: [".mcp.json"],
|
|
22
|
+
globalPaths: [path.join(HOME, ".claude.json")],
|
|
23
|
+
configKey: "mcpServers",
|
|
24
|
+
buildEntry: ({ serverUrl, apiKey }) => ({
|
|
25
|
+
type: "http",
|
|
26
|
+
url: serverUrl,
|
|
27
|
+
headers: {
|
|
28
|
+
Authorization: `Bearer ${apiKey}`,
|
|
29
|
+
},
|
|
30
|
+
}),
|
|
31
|
+
},
|
|
32
|
+
detect: {
|
|
33
|
+
projectPaths: [".mcp.json", ".claude"],
|
|
34
|
+
globalPaths: [
|
|
35
|
+
path.join(HOME, ".claude.json"),
|
|
36
|
+
path.join(HOME, ".claude"),
|
|
37
|
+
],
|
|
38
|
+
},
|
|
43
39
|
},
|
|
44
40
|
opencode: {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
41
|
+
name: "opencode",
|
|
42
|
+
displayName: "OpenCode",
|
|
43
|
+
mcp: {
|
|
44
|
+
configType: "json",
|
|
45
|
+
projectPaths: ["opencode.json", ".opencode.json"],
|
|
46
|
+
globalPaths: [
|
|
47
|
+
path.join(HOME, ".config", "opencode", "opencode.json"),
|
|
48
|
+
path.join(HOME, ".config", "opencode", ".opencode.json"),
|
|
49
|
+
],
|
|
50
|
+
configKey: "mcp",
|
|
51
|
+
buildEntry: ({ serverUrl, apiKey }) => ({
|
|
52
|
+
type: "remote",
|
|
53
|
+
url: serverUrl,
|
|
54
|
+
enabled: true,
|
|
55
|
+
headers: {
|
|
56
|
+
Authorization: `Bearer ${apiKey}`,
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
detect: {
|
|
61
|
+
projectPaths: ["opencode.json", ".opencode.json"],
|
|
62
|
+
globalPaths: [path.join(HOME, ".config", "opencode")],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
codex: {
|
|
66
|
+
name: "codex",
|
|
67
|
+
displayName: "Codex",
|
|
68
|
+
mcp: {
|
|
69
|
+
configType: "toml",
|
|
70
|
+
projectPaths: [path.join(".codex", "config.toml")],
|
|
71
|
+
globalPaths: [path.join(HOME, ".codex", "config.toml")],
|
|
72
|
+
configKey: "mcp_servers",
|
|
73
|
+
buildEntry: ({ serverUrl, apiKey }) => ({
|
|
74
|
+
type: "http",
|
|
75
|
+
url: serverUrl,
|
|
76
|
+
headers: {
|
|
77
|
+
Authorization: `Bearer ${apiKey}`,
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
},
|
|
81
|
+
detect: {
|
|
82
|
+
projectPaths: [".codex", path.join(".codex", "config.toml")],
|
|
83
|
+
globalPaths: [path.join(HOME, ".codex")],
|
|
84
|
+
},
|
|
57
85
|
},
|
|
58
86
|
};
|
|
59
87
|
|
|
60
|
-
export function
|
|
61
|
-
|
|
62
|
-
|
|
88
|
+
export function getHost(name) {
|
|
89
|
+
const host = hosts[name];
|
|
90
|
+
if (!host) {
|
|
91
|
+
throw new Error(`Unsupported host: ${name}`);
|
|
92
|
+
}
|
|
93
|
+
return host;
|
|
63
94
|
}
|
|
64
95
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
96
|
+
async function pathExists(filePath) {
|
|
97
|
+
try {
|
|
98
|
+
await access(filePath);
|
|
99
|
+
return true;
|
|
100
|
+
} catch {
|
|
101
|
+
return false;
|
|
69
102
|
}
|
|
70
|
-
|
|
71
|
-
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function detectHosts(scope) {
|
|
106
|
+
const detected = [];
|
|
107
|
+
for (const hostName of ALL_HOST_NAMES) {
|
|
108
|
+
const host = getHost(hostName);
|
|
109
|
+
const candidates =
|
|
110
|
+
scope === "global" ? host.detect.globalPaths : host.detect.projectPaths;
|
|
111
|
+
for (const candidate of candidates) {
|
|
112
|
+
const fullPath =
|
|
113
|
+
scope === "global" ? candidate : path.join(process.cwd(), candidate);
|
|
114
|
+
// eslint-disable-next-line no-await-in-loop
|
|
115
|
+
if (await pathExists(fullPath)) {
|
|
116
|
+
detected.push(hostName);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
72
120
|
}
|
|
73
|
-
return
|
|
121
|
+
return detected;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function normalizeScope(scope) {
|
|
125
|
+
if (scope === "global" || scope === "project") return scope;
|
|
126
|
+
return "global";
|
|
74
127
|
}
|
|
75
128
|
|
|
76
129
|
export function normalizeHosts(rawHosts) {
|
|
77
130
|
if (!Array.isArray(rawHosts) || rawHosts.length === 0) {
|
|
78
|
-
return [...
|
|
131
|
+
return [...ALL_HOST_NAMES];
|
|
79
132
|
}
|
|
80
133
|
const normalized = [];
|
|
81
134
|
for (const host of rawHosts) {
|
|
82
|
-
const candidate = String(host || "")
|
|
135
|
+
const candidate = String(host || "")
|
|
136
|
+
.trim()
|
|
137
|
+
.toLowerCase();
|
|
83
138
|
if (!candidate) continue;
|
|
84
|
-
if (!
|
|
139
|
+
if (!ALL_HOST_NAMES.includes(candidate)) {
|
|
85
140
|
throw new Error(
|
|
86
|
-
`Unsupported host '${candidate}'. Supported: ${
|
|
141
|
+
`Unsupported host '${candidate}'. Supported: ${ALL_HOST_NAMES.join(", ")}`,
|
|
87
142
|
);
|
|
88
143
|
}
|
|
89
144
|
if (!normalized.includes(candidate)) {
|
|
90
145
|
normalized.push(candidate);
|
|
91
146
|
}
|
|
92
147
|
}
|
|
93
|
-
|
|
94
|
-
return [...HOSTS];
|
|
95
|
-
}
|
|
96
|
-
return normalized;
|
|
148
|
+
return normalized.length > 0 ? normalized : [...ALL_HOST_NAMES];
|
|
97
149
|
}
|