@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 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. Re-running setup for a
31
- different base URL in the same host+scope updates that same entry.
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.0",
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 HOSTS = ["codex", "claude", "opencode"];
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
- configType: "json",
33
- projectPath: ".mcp.json",
34
- globalPath: path.join(HOME, ".claude.json"),
35
- configKey: "mcpServers",
36
- buildEntry: ({ serverUrl, apiKey }) => ({
37
- type: "http",
38
- url: serverUrl,
39
- headers: {
40
- Authorization: `Bearer ${apiKey}`,
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
- configType: "json",
46
- projectPath: "opencode.json",
47
- globalPath: path.join(HOME, ".config", "opencode", "opencode.json"),
48
- configKey: "mcp",
49
- buildEntry: ({ serverUrl, apiKey }) => ({
50
- type: "remote",
51
- url: serverUrl,
52
- enabled: true,
53
- headers: {
54
- Authorization: `Bearer ${apiKey}`,
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 normalizeScope(scope) {
61
- if (scope === "global" || scope === "project") return scope;
62
- return "global";
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
- export function resolveHostConfigPath(host, scope) {
66
- const hostConfig = AGENT_CONFIG[host];
67
- if (!hostConfig) {
68
- throw new Error(`Unsupported host: ${host}`);
96
+ async function pathExists(filePath) {
97
+ try {
98
+ await access(filePath);
99
+ return true;
100
+ } catch {
101
+ return false;
69
102
  }
70
- if (scope === "project") {
71
- return path.join(process.cwd(), hostConfig.projectPath);
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 hostConfig.globalPath;
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 [...HOSTS];
131
+ return [...ALL_HOST_NAMES];
79
132
  }
80
133
  const normalized = [];
81
134
  for (const host of rawHosts) {
82
- const candidate = String(host || "").trim().toLowerCase();
135
+ const candidate = String(host || "")
136
+ .trim()
137
+ .toLowerCase();
83
138
  if (!candidate) continue;
84
- if (!HOSTS.includes(candidate)) {
139
+ if (!ALL_HOST_NAMES.includes(candidate)) {
85
140
  throw new Error(
86
- `Unsupported host '${candidate}'. Supported: ${HOSTS.join(", ")}`,
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
- if (normalized.length === 0) {
94
- return [...HOSTS];
95
- }
96
- return normalized;
148
+ return normalized.length > 0 ? normalized : [...ALL_HOST_NAMES];
97
149
  }