blackops-onboard 0.3.0 → 0.4.0

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 (3) hide show
  1. package/README.md +14 -1
  2. package/bin/onboard.mjs +112 -48
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -8,12 +8,25 @@ Agent-first onboarding for [BlackOps Center](https://blackopscenter.com).
8
8
  npx blackops-onboard
9
9
  ```
10
10
 
11
- Then open Claude Code and say:
11
+ Then open your MCP-capable agent (Claude Code, Claude Desktop, Cursor, Cline, …) and say:
12
12
 
13
13
  > Help me onboard to BlackOps Center and publish my first piece of content.
14
14
 
15
15
  That's it. The agent walks you through account creation, brand voice, social OAuth (X / Threads / LinkedIn), publishing your first four pieces of content, and getting a magic link to your dashboard.
16
16
 
17
+ ## Supported MCP clients
18
+
19
+ The CLI auto-detects and writes the BlackOps MCP server entry to any of these configs it finds:
20
+
21
+ | Client | Config path |
22
+ |---|---|
23
+ | Claude Code | `~/.claude/settings.json` |
24
+ | Claude Desktop | macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` · Windows: `%APPDATA%\Claude\claude_desktop_config.json` · Linux: `~/.config/Claude/claude_desktop_config.json` |
25
+ | Cursor | `~/.cursor/mcp.json` |
26
+ | Cline (VS Code) | `~/.cline/mcp_settings.json` |
27
+
28
+ For any other MCP client, the CLI also prints a universal JSON snippet you can paste into that client's `mcpServers` config.
29
+
17
30
  ## Re-running
18
31
 
19
32
  Re-running detects an existing config at `~/.blackops/config.json`. You can keep the existing token (and re-paste the saved prompt) or overwrite to start fresh.
package/bin/onboard.mjs CHANGED
@@ -4,8 +4,9 @@
4
4
  // What this does:
5
5
  // 1. POSTs to https://blackopscenter.com/api/install to mint an onboarding token.
6
6
  // 2. Writes ~/.blackops/config.json with token + MCP URL.
7
- // 3. Adds a `blackops` MCP server entry to Claude Code's settings.json.
8
- // 4. Prints the onboarding prompt for the user to paste into Claude Code.
7
+ // 3. Detects installed MCP clients (Claude Code, Claude Desktop, Cursor, Cline)
8
+ // and adds a `blackops` MCP server entry to each one's config.
9
+ // 4. Always prints a universal copy-paste snippet as fallback for unknown clients.
9
10
  //
10
11
  // Re-runs are idempotent: an existing ~/.blackops/config.json will prompt the
11
12
  // user before overwriting.
@@ -15,7 +16,7 @@ import path from 'node:path'
15
16
  import os from 'node:os'
16
17
  import readline from 'node:readline/promises'
17
18
 
18
- const VERSION = '0.3.0'
19
+ const VERSION = '0.4.0'
19
20
  const INSTALL_URL = process.env.BLACKOPS_INSTALL_URL || 'https://blackopscenter.com/api/install'
20
21
  const CONFIG_DIR = path.join(os.homedir(), '.blackops')
21
22
  const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json')
@@ -41,18 +42,6 @@ async function confirm(question) {
41
42
  }
42
43
  }
43
44
 
44
- function findClaudeCodeSettingsPath() {
45
- // Claude Code stores user-level settings in ~/.claude/settings.json on macOS/Linux.
46
- // (The same file holds permissions, hooks, and MCP servers.)
47
- const candidates = [
48
- path.join(os.homedir(), '.claude', 'settings.json'),
49
- ]
50
- for (const p of candidates) {
51
- if (fs.existsSync(p)) return p
52
- }
53
- return candidates[0] // first candidate; we'll create it
54
- }
55
-
56
45
  function readJsonSafe(filePath) {
57
46
  try {
58
47
  const raw = fs.readFileSync(filePath, 'utf8')
@@ -69,6 +58,68 @@ function writeJsonAtomic(filePath, value) {
69
58
  fs.renameSync(tmp, filePath)
70
59
  }
71
60
 
61
+ // ── MCP client detection ────────────────────────────────────────────────
62
+ //
63
+ // Each entry is one MCP client we know how to install into. `path` is where
64
+ // the client stores its MCP config; `installed` is a heuristic that checks
65
+ // whether the client appears to be set up. `alwaysInstall` is true for
66
+ // Claude Code (the canonical target — we create the file if missing).
67
+ //
68
+ // All known clients use the same `mcpServers.<name>.{type,url,headers}`
69
+ // JSON shape, so a single writer covers them.
70
+
71
+ function getClientConfigs(homedir, platform) {
72
+ const APPDATA = process.env.APPDATA || ''
73
+ return [
74
+ {
75
+ id: 'claude-code',
76
+ label: 'Claude Code',
77
+ path: path.join(homedir, '.claude', 'settings.json'),
78
+ alwaysInstall: true,
79
+ },
80
+ {
81
+ id: 'claude-desktop',
82
+ label: 'Claude Desktop',
83
+ path:
84
+ platform === 'darwin'
85
+ ? path.join(homedir, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')
86
+ : platform === 'win32'
87
+ ? path.join(APPDATA, 'Claude', 'claude_desktop_config.json')
88
+ : path.join(homedir, '.config', 'Claude', 'claude_desktop_config.json'),
89
+ },
90
+ {
91
+ id: 'cursor',
92
+ label: 'Cursor',
93
+ path: path.join(homedir, '.cursor', 'mcp.json'),
94
+ },
95
+ {
96
+ id: 'cline',
97
+ label: 'Cline (VS Code)',
98
+ path: path.join(homedir, '.cline', 'mcp_settings.json'),
99
+ },
100
+ ]
101
+ }
102
+
103
+ function clientIsInstalled(cfg) {
104
+ if (cfg.alwaysInstall) return true
105
+ // File exists OR its parent dir exists (covers cases where the user
106
+ // has the app installed but hasn't created an MCP config file yet).
107
+ if (fs.existsSync(cfg.path)) return true
108
+ if (fs.existsSync(path.dirname(cfg.path))) return true
109
+ return false
110
+ }
111
+
112
+ function injectMcpServer(cfg, { mcpServerName, mcpUrl, token }) {
113
+ const existing = readJsonSafe(cfg.path) ?? {}
114
+ existing.mcpServers = existing.mcpServers ?? {}
115
+ existing.mcpServers[mcpServerName] = {
116
+ type: 'http',
117
+ url: mcpUrl,
118
+ headers: { Authorization: `Bearer ${token}` },
119
+ }
120
+ writeJsonAtomic(cfg.path, existing)
121
+ }
122
+
72
123
  async function callInstallEndpoint() {
73
124
  const response = await fetch(INSTALL_URL, {
74
125
  method: 'POST',
@@ -86,24 +137,6 @@ async function callInstallEndpoint() {
86
137
  return data
87
138
  }
88
139
 
89
- function injectClaudeCodeMcp({ mcpUrl, mcpServerName, token }) {
90
- const settingsPath = findClaudeCodeSettingsPath()
91
- const settings = readJsonSafe(settingsPath) ?? {}
92
- settings.mcpServers = settings.mcpServers ?? {}
93
-
94
- // Use the SSE/HTTP transport — Claude Code supports `type: "http"` MCP servers.
95
- settings.mcpServers[mcpServerName] = {
96
- type: 'http',
97
- url: mcpUrl,
98
- headers: {
99
- Authorization: `Bearer ${token}`,
100
- },
101
- }
102
-
103
- writeJsonAtomic(settingsPath, settings)
104
- return settingsPath
105
- }
106
-
107
140
  async function main() {
108
141
  log(`${BOLD}${CYAN}BlackOps Center — agent-first onboarding${RESET}`)
109
142
  log(`${DIM}v${VERSION}${RESET}`)
@@ -146,26 +179,57 @@ async function main() {
146
179
  writeJsonAtomic(CONFIG_PATH, config)
147
180
  log(`${GREEN}✓${RESET} Saved config to ${CONFIG_PATH}`)
148
181
 
149
- // 3. Wire up Claude Code MCP.
150
- let settingsPath
151
- try {
152
- settingsPath = injectClaudeCodeMcp({
153
- mcpUrl: config.mcp_url,
154
- mcpServerName: config.mcp_server_name,
155
- token: config.token,
156
- })
157
- log(`${GREEN}✓${RESET} Added MCP server "${config.mcp_server_name}" to ${settingsPath}`)
158
- } catch (e) {
159
- err(`Could not write Claude Code settings: ${e.message}`)
160
- log('You can configure manually by adding this MCP server to your Claude Code settings:')
161
- log(JSON.stringify({ [config.mcp_server_name]: { type: 'http', url: config.mcp_url, headers: { Authorization: `Bearer ${config.token}` } } }, null, 2))
182
+ // 3. Detect installed MCP clients and write to each one we recognize.
183
+ const clientConfigs = getClientConfigs(os.homedir(), process.platform)
184
+ const installedTo = []
185
+ const failed = []
186
+ for (const c of clientConfigs) {
187
+ if (!clientIsInstalled(c)) continue
188
+ try {
189
+ injectMcpServer(c, {
190
+ mcpServerName: config.mcp_server_name,
191
+ mcpUrl: config.mcp_url,
192
+ token: config.token,
193
+ })
194
+ installedTo.push(c)
195
+ } catch (e) {
196
+ failed.push({ client: c, error: e.message })
197
+ }
198
+ }
199
+
200
+ if (installedTo.length === 0) {
201
+ log(`${YELLOW}No supported MCP client configs detected.${RESET}`)
202
+ } else {
203
+ for (const c of installedTo) {
204
+ log(`${GREEN}✓${RESET} Added MCP server "${config.mcp_server_name}" to ${c.label} (${c.path})`)
205
+ }
206
+ }
207
+ for (const f of failed) {
208
+ err(`Could not write ${f.client.label} config: ${f.error}`)
162
209
  }
163
210
 
164
- // 4. Tell the user the one thing to say next. Single line, copy-pasteable.
211
+ // 4. Always print the universal snippet as fallback for any other MCP client.
212
+ log()
213
+ log(`${DIM}Using a different MCP client? Add this to its config under \`mcpServers\`:${RESET}`)
214
+ log()
215
+ const snippet = JSON.stringify(
216
+ {
217
+ [config.mcp_server_name]: {
218
+ type: 'http',
219
+ url: config.mcp_url,
220
+ headers: { Authorization: `Bearer ${config.token}` },
221
+ },
222
+ },
223
+ null,
224
+ 2
225
+ )
226
+ log(`${DIM}${snippet}${RESET}`)
227
+
228
+ // 5. Tell the user the one thing to say next. Single line, copy-pasteable.
165
229
  log()
166
230
  log(`${GREEN}✓${RESET} Installed.`)
167
231
  log()
168
- log(`Open ${BOLD}Claude Code${RESET} and say:`)
232
+ log(`Open your MCP-capable agent (Claude Code, Cursor, etc.) and say:`)
169
233
  log()
170
234
  log(` ${CYAN}${config.prompt}${RESET}`)
171
235
  log()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blackops-onboard",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Agent-first onboarding for BlackOps Center. Run via `npx blackops-onboard` to provision your account through Claude Code.",
5
5
  "type": "module",
6
6
  "publishConfig": {