chain-insights 0.46.6 → 0.47.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.
- package/bin/install.cjs +137 -30
- package/docs/mcp-proxy.md +12 -0
- package/package.json +1 -1
package/bin/install.cjs
CHANGED
|
@@ -33,7 +33,7 @@ if (!hasClaude && !hasCodex && !hasHermes && !hasClaudeDesktop && !hasLocal) {
|
|
|
33
33
|
` ${cyan}--claude-desktop${reset} Register MCP in Claude Desktop (draws the money-flow views)`
|
|
34
34
|
)
|
|
35
35
|
console.log(
|
|
36
|
-
` ${cyan}--codex${reset} Install Codex skills
|
|
36
|
+
` ${cyan}--codex${reset} Install Codex skills to ~/.codex/skills/ and the Chain Insights Codex plugin (draws views)`
|
|
37
37
|
)
|
|
38
38
|
console.log(
|
|
39
39
|
` ${cyan}--hermes${reset} Install Hermes skills globally to ~/.hermes/skills/chain-insights/ and register MCP`
|
|
@@ -170,36 +170,128 @@ function tomlQuoted(value) {
|
|
|
170
170
|
return JSON.stringify(value)
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
173
|
+
// Codex draws an MCP app view only for a server that a Codex plugin provides:
|
|
174
|
+
// the desktop app needs both the view's ui:// address and a plugin id on the
|
|
175
|
+
// tool call. A plain [mcp_servers] entry has no plugin id, so its graph_query
|
|
176
|
+
// answers stay text. setup codex therefore installs a local one-plugin
|
|
177
|
+
// marketplace and removes the plain entry earlier versions wrote.
|
|
178
|
+
const CODEX_MARKETPLACE = 'chain-insights'
|
|
179
|
+
const CODEX_PLUGIN = 'chain-insights'
|
|
180
|
+
const CODEX_PLUGIN_REF = `${CODEX_PLUGIN}@${CODEX_MARKETPLACE}`
|
|
181
|
+
const LEGACY_CODEX_MCP_HEADING = '[mcp_servers.chain-insights]'
|
|
182
|
+
|
|
183
|
+
function packageVersion() {
|
|
184
|
+
return JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')).version
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function writeJson(file, value) {
|
|
188
|
+
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
189
|
+
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`, 'utf8')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function writeCodexMarketplace(root, proxyPath, version) {
|
|
193
|
+
writeJson(path.join(root, '.agents', 'plugins', 'marketplace.json'), {
|
|
194
|
+
name: CODEX_MARKETPLACE,
|
|
195
|
+
interface: { displayName: 'Chain Insights' },
|
|
196
|
+
plugins: [
|
|
197
|
+
{
|
|
198
|
+
name: CODEX_PLUGIN,
|
|
199
|
+
source: { source: 'local', path: `./plugins/${CODEX_PLUGIN}` },
|
|
200
|
+
policy: { installation: 'AVAILABLE', authentication: 'ON_INSTALL' },
|
|
201
|
+
category: 'Developer Tools',
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
})
|
|
205
|
+
const pluginDir = path.join(root, 'plugins', CODEX_PLUGIN)
|
|
206
|
+
writeJson(path.join(pluginDir, '.codex-plugin', 'plugin.json'), {
|
|
207
|
+
name: CODEX_PLUGIN,
|
|
208
|
+
version,
|
|
209
|
+
description: 'Chain Insights graph queries, with money-flow, chart and table views.',
|
|
210
|
+
author: { name: 'Chain Insights' },
|
|
211
|
+
license: 'MIT',
|
|
212
|
+
mcpServers: './.mcp.json',
|
|
213
|
+
interface: { displayName: 'Chain Insights', shortDescription: 'Money flows and graph queries' },
|
|
214
|
+
})
|
|
215
|
+
// The installing Node's absolute path, as setup claude-desktop does: a
|
|
216
|
+
// desktop app does not inherit the shell's PATH.
|
|
217
|
+
writeJson(path.join(pluginDir, '.mcp.json'), {
|
|
218
|
+
mcpServers: { 'chain-insights': { command: process.execPath, args: [proxyPath] } },
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// The [heading] section of a TOML file, from its heading to the next heading.
|
|
223
|
+
function tomlSectionRange(content, heading) {
|
|
224
|
+
const lines = content.split('\n')
|
|
225
|
+
const start = lines.findIndex((line) => line.trim() === heading)
|
|
226
|
+
if (start < 0) return null
|
|
227
|
+
let end = start + 1
|
|
228
|
+
while (end < lines.length && !/^\s*\[/.test(lines[end])) end++
|
|
229
|
+
return { lines, start, end }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function removeTomlSection(content, heading) {
|
|
233
|
+
const range = tomlSectionRange(content, heading)
|
|
234
|
+
if (!range) return content
|
|
235
|
+
const { lines, start, end } = range
|
|
236
|
+
return [...lines.slice(0, start), ...lines.slice(end)].join('\n').replace(/\n{3,}/g, '\n\n')
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function setTomlSection(content, heading, body) {
|
|
240
|
+
const block = [heading, ...body, '']
|
|
241
|
+
const range = tomlSectionRange(content, heading)
|
|
242
|
+
if (range) {
|
|
243
|
+
const { lines, start, end } = range
|
|
244
|
+
return [...lines.slice(0, start), ...block, ...lines.slice(end)].join('\n')
|
|
194
245
|
}
|
|
246
|
+
const separator = content.endsWith('\n') || content.length === 0 ? '' : '\n'
|
|
247
|
+
return `${content}${separator}\n${block.join('\n')}`
|
|
248
|
+
}
|
|
195
249
|
|
|
250
|
+
function installCodexPlugin(configFile, marketplaceRoot) {
|
|
251
|
+
fs.mkdirSync(path.dirname(configFile), { recursive: true })
|
|
252
|
+
let content = fs.existsSync(configFile) ? fs.readFileSync(configFile, 'utf8') : ''
|
|
253
|
+
content = removeTomlSection(content, LEGACY_CODEX_MCP_HEADING)
|
|
254
|
+
content = setTomlSection(content, `[marketplaces.${CODEX_MARKETPLACE}]`, [
|
|
255
|
+
'source_type = "local"',
|
|
256
|
+
`source = ${tomlQuoted(marketplaceRoot)}`,
|
|
257
|
+
])
|
|
258
|
+
content = setTomlSection(content, `[plugins.${tomlQuoted(CODEX_PLUGIN_REF)}]`, ['enabled = true'])
|
|
196
259
|
fs.writeFileSync(configFile, content, 'utf8')
|
|
197
260
|
}
|
|
198
261
|
|
|
262
|
+
// Codex copies a plugin into its own cache on `codex plugin add`. Without the
|
|
263
|
+
// codex command the config entries above are enough for the next start; with
|
|
264
|
+
// it, the cache is refreshed now, so an upgrade takes the new version.
|
|
265
|
+
function cacheCodexPlugin() {
|
|
266
|
+
try {
|
|
267
|
+
execFileSync('codex', ['plugin', 'remove', CODEX_PLUGIN_REF], {
|
|
268
|
+
stdio: 'ignore',
|
|
269
|
+
timeout: 30000,
|
|
270
|
+
})
|
|
271
|
+
} catch {
|
|
272
|
+
// Not installed yet, or no codex command: the add below decides.
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
execFileSync('codex', ['plugin', 'add', CODEX_PLUGIN_REF], { stdio: 'ignore', timeout: 30000 })
|
|
276
|
+
return true
|
|
277
|
+
} catch {
|
|
278
|
+
return false
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
199
282
|
if (hasCodex) {
|
|
200
283
|
const codexConfig = path.join(homeDir, '.codex', 'config.toml')
|
|
201
|
-
|
|
202
|
-
|
|
284
|
+
const marketplaceRoot = path.join(dataDir, 'codex-marketplace')
|
|
285
|
+
writeCodexMarketplace(marketplaceRoot, proxyBinPath, packageVersion())
|
|
286
|
+
installCodexPlugin(codexConfig, marketplaceRoot)
|
|
287
|
+
console.log(` ${cyan}Codex plugin:${reset} ${CODEX_PLUGIN_REF} registered in ${codexConfig}`)
|
|
288
|
+
if (cacheCodexPlugin()) {
|
|
289
|
+
console.log(` ${cyan}Codex plugin:${reset} installed; restart the Codex app to load it`)
|
|
290
|
+
} else {
|
|
291
|
+
console.log(
|
|
292
|
+
` ${dim}Codex plugin:${reset} run once: codex plugin add ${CODEX_PLUGIN_REF}, then restart the Codex app`
|
|
293
|
+
)
|
|
294
|
+
}
|
|
203
295
|
}
|
|
204
296
|
|
|
205
297
|
function yamlQuoted(value) {
|
|
@@ -280,7 +372,13 @@ if (hasHermes) {
|
|
|
280
372
|
// absolute path. Skills reach Claude Desktop through the plugin, not this file.
|
|
281
373
|
function claudeDesktopConfigPath() {
|
|
282
374
|
if (process.platform === 'darwin') {
|
|
283
|
-
return path.join(
|
|
375
|
+
return path.join(
|
|
376
|
+
homeDir,
|
|
377
|
+
'Library',
|
|
378
|
+
'Application Support',
|
|
379
|
+
'Claude',
|
|
380
|
+
'claude_desktop_config.json'
|
|
381
|
+
)
|
|
284
382
|
}
|
|
285
383
|
if (process.platform === 'win32') {
|
|
286
384
|
const appData = process.env['APPDATA'] || path.join(homeDir, 'AppData', 'Roaming')
|
|
@@ -302,7 +400,9 @@ function installClaudeDesktopMcp(configFile, proxyPath) {
|
|
|
302
400
|
throw new Error(`${configFile} is not valid JSON; fix it, then run the setup again`)
|
|
303
401
|
}
|
|
304
402
|
if (config === null || typeof config !== 'object' || Array.isArray(config)) {
|
|
305
|
-
throw new Error(
|
|
403
|
+
throw new Error(
|
|
404
|
+
`${configFile} does not hold a JSON object; fix it, then run the setup again`
|
|
405
|
+
)
|
|
306
406
|
}
|
|
307
407
|
}
|
|
308
408
|
fs.copyFileSync(configFile, `${configFile}.bak`)
|
|
@@ -322,10 +422,13 @@ function claudeDesktopRunning() {
|
|
|
322
422
|
const { spawnSync } = require('child_process')
|
|
323
423
|
try {
|
|
324
424
|
if (process.platform === 'win32') {
|
|
325
|
-
const out = spawnSync('tasklist', ['/FI', 'IMAGENAME eq Claude.exe', '/NH'], {
|
|
425
|
+
const out = spawnSync('tasklist', ['/FI', 'IMAGENAME eq Claude.exe', '/NH'], {
|
|
426
|
+
encoding: 'utf8',
|
|
427
|
+
})
|
|
326
428
|
return /claude\.exe/i.test(out.stdout || '')
|
|
327
429
|
}
|
|
328
|
-
const pgrepArgs =
|
|
430
|
+
const pgrepArgs =
|
|
431
|
+
process.platform === 'darwin' ? ['-x', 'Claude'] : ['-f', '(^|/)claude-desktop( |$)']
|
|
329
432
|
return spawnSync('pgrep', pgrepArgs, { encoding: 'utf8' }).status === 0
|
|
330
433
|
} catch {
|
|
331
434
|
return false
|
|
@@ -341,8 +444,12 @@ function quitClaudeDesktopHint() {
|
|
|
341
444
|
if (hasClaudeDesktop) {
|
|
342
445
|
const desktopConfig = claudeDesktopConfigPath()
|
|
343
446
|
if (claudeDesktopRunning()) {
|
|
344
|
-
console.log(
|
|
345
|
-
|
|
447
|
+
console.log(
|
|
448
|
+
` ${bold}Claude Desktop is running.${reset} It can overwrite the new entry when it saves its own settings.`
|
|
449
|
+
)
|
|
450
|
+
console.log(
|
|
451
|
+
` Quit it fully (${quitClaudeDesktopHint()}), run ${cyan}cia setup claude-desktop${reset} again, then start Claude Desktop.`
|
|
452
|
+
)
|
|
346
453
|
}
|
|
347
454
|
try {
|
|
348
455
|
installClaudeDesktopMcp(desktopConfig, proxyBinPath)
|
package/docs/mcp-proxy.md
CHANGED
|
@@ -279,6 +279,18 @@ Desktop's `claude_desktop_config.json`, keeps every other entry, saves a
|
|
|
279
279
|
`.bak` copy, and refuses a file that is not valid JSON. Restart Claude
|
|
280
280
|
Desktop afterwards.
|
|
281
281
|
|
|
282
|
+
`cia setup codex` installs the Chain Insights Codex plugin: a local
|
|
283
|
+
one-plugin marketplace under `~/.chain-insights/codex-marketplace`, registered
|
|
284
|
+
in `~/.codex/config.toml` as `chain-insights@chain-insights`, with the skills
|
|
285
|
+
copied to `~/.codex/skills`. The Codex app draws the graph, chart and table
|
|
286
|
+
views only for a server that a plugin provides, so the plain
|
|
287
|
+
`[mcp_servers.chain-insights]` entry that earlier versions wrote is removed.
|
|
288
|
+
Every other entry in `config.toml` is kept. When the `codex` command is on
|
|
289
|
+
`PATH`, setup also runs `codex plugin add chain-insights@chain-insights`;
|
|
290
|
+
otherwise run it once yourself. Restart the Codex app afterwards. To remove
|
|
291
|
+
the plugin: `codex plugin remove chain-insights@chain-insights`, then
|
|
292
|
+
`codex plugin marketplace remove chain-insights`.
|
|
293
|
+
|
|
282
294
|
Current MCP prompts exposed by the local proxy:
|
|
283
295
|
|
|
284
296
|
- `meta-network-capabilities`
|
package/package.json
CHANGED