cito-mcp 0.2.6 → 0.2.7
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 +23 -10
- package/dist/index.js +12 -0
- package/dist/install.js +340 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,33 +38,39 @@ Composites multi-fetch server-side, return a **stable JSON envelope**, and isola
|
|
|
38
38
|
|
|
39
39
|
## Install
|
|
40
40
|
|
|
41
|
-
### One
|
|
41
|
+
### One command (recommended)
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
npx cito-mcp
|
|
44
|
+
npx cito-mcp install --key cito_your_key_here
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
Detects the MCP clients installed on your machine — Claude Code, Claude Desktop, Cursor, Windsurf, Codex CLI — and writes the config for each. Then restart your editor.
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
It is safe to re-run: existing config is merged, not replaced, your other MCP servers are left alone, a `.cito-bak` backup is written before the first change, and running it again just rotates the key rather than adding a second entry.
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
|
|
52
|
+
npx cito-mcp install --dry-run # show the plan, write nothing
|
|
53
|
+
npx cito-mcp install --key … --client cursor # one client only
|
|
54
|
+
npx cito-mcp install --help
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
Get a key at [citoapi.com/dashboard](https://citoapi.com/dashboard).
|
|
58
|
+
|
|
59
|
+
### Manual — Claude Code
|
|
56
60
|
|
|
57
61
|
```bash
|
|
58
|
-
claude mcp add cito -e CITO_API_KEY=cito_your_key_here --
|
|
62
|
+
claude mcp add cito -e CITO_API_KEY=cito_your_key_here "--" npx -y cito-mcp
|
|
59
63
|
```
|
|
60
64
|
|
|
65
|
+
**Quote the `--`.** PowerShell 5.1 strips a bare `--` before the CLI sees it; because `-e` takes a variable number of values it then swallows `npx -y cito-mcp` as env vars and fails with `unknown option '-y'`. The quoted form works in PowerShell, cmd, bash and zsh alike. `npx cito-mcp install` avoids the problem entirely.
|
|
66
|
+
|
|
61
67
|
From a local clone (development):
|
|
62
68
|
|
|
63
69
|
```bash
|
|
64
70
|
cd mcp
|
|
65
71
|
npm install
|
|
66
72
|
npm run build
|
|
67
|
-
claude mcp add cito -e CITO_API_KEY=cito_your_key_here -- node "%CD%\dist\index.js"
|
|
73
|
+
claude mcp add cito -e CITO_API_KEY=cito_your_key_here "--" node "%CD%\dist\index.js"
|
|
68
74
|
```
|
|
69
75
|
|
|
70
76
|
### Cursor
|
|
@@ -460,13 +466,20 @@ Package: **`cito-mcp@0.2.4`**
|
|
|
460
466
|
- `live_matches`
|
|
461
467
|
- `resolve_entity` (e.g. T1 / s1mple)
|
|
462
468
|
- `match_summary` with a real `matchId` from live/schedule
|
|
463
|
-
4. Confirm `package.json` version and README match the shipped tool list
|
|
469
|
+
4. Confirm `package.json` version and README match the shipped tool list.
|
|
464
470
|
5. `npm publish` from `mcp/` (or your release pipeline) with appropriate npm auth / access.
|
|
471
|
+
`prepublishOnly` runs build + tests + smoke, so a broken build cannot ship.
|
|
465
472
|
|
|
466
473
|
### Install line for docs & marketing
|
|
467
474
|
|
|
468
475
|
```bash
|
|
469
|
-
|
|
476
|
+
npx cito-mcp install --key cito_…
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Manual fallback (note the quoted `--`, required for PowerShell):
|
|
480
|
+
|
|
481
|
+
```bash
|
|
482
|
+
claude mcp add cito -e CITO_API_KEY=cito_… "--" npx -y cito-mcp
|
|
470
483
|
```
|
|
471
484
|
|
|
472
485
|
```json
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,18 @@ import { SERVER_INSTRUCTIONS } from './instructions.js';
|
|
|
19
19
|
import { allTools, getTool } from './tools/index.js';
|
|
20
20
|
import { runTool } from './tools/types.js';
|
|
21
21
|
import { PACKAGE_VERSION } from './version.js';
|
|
22
|
+
/**
|
|
23
|
+
* Subcommands run and exit before any transport exists.
|
|
24
|
+
*
|
|
25
|
+
* `install` writes editor config and prints to stdout, which would corrupt the
|
|
26
|
+
* JSON-RPC channel if it ran alongside the server — hence the early return.
|
|
27
|
+
* Handled here rather than in a second binary so the documented entry point
|
|
28
|
+
* stays `npx cito-mcp`.
|
|
29
|
+
*/
|
|
30
|
+
if (process.argv[2] === 'install') {
|
|
31
|
+
const { runInstall } = await import('./install.js');
|
|
32
|
+
process.exit(await runInstall(process.argv.slice(3)));
|
|
33
|
+
}
|
|
22
34
|
const API_KEY = process.env.CITO_API_KEY;
|
|
23
35
|
/**
|
|
24
36
|
* Tools that need no API key. These must keep working with the server
|
package/dist/install.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `npx cito-mcp install` — write the server config for whichever MCP clients
|
|
3
|
+
* are actually installed.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists: the documented one-liner
|
|
6
|
+
*
|
|
7
|
+
* claude mcp add cito -e CITO_API_KEY=… -- npx -y cito-mcp
|
|
8
|
+
*
|
|
9
|
+
* is broken in PowerShell 5.1, which strips the bare `--`. Because `-e` is a
|
|
10
|
+
* variadic option, it then swallows `npx -y cito-mcp` as extra env values and
|
|
11
|
+
* the CLI dies on "unknown option '-y'". Quoting the separator fixes it, but
|
|
12
|
+
* expecting every Windows user to know that is not onboarding — it is a trap.
|
|
13
|
+
* A subcommand has no separator to mangle and works identically everywhere.
|
|
14
|
+
*
|
|
15
|
+
* Rules this follows, because it edits files the user did not open:
|
|
16
|
+
* - only touch clients that are already installed
|
|
17
|
+
* - back up before the first write of a run
|
|
18
|
+
* - merge into existing config; never rewrite a file wholesale
|
|
19
|
+
* - idempotent: re-running replaces our entry and nothing else
|
|
20
|
+
* - --dry-run prints the plan and writes nothing
|
|
21
|
+
* - never print the full API key
|
|
22
|
+
*/
|
|
23
|
+
import { execFileSync } from 'node:child_process';
|
|
24
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs';
|
|
25
|
+
import { homedir, platform } from 'node:os';
|
|
26
|
+
import { dirname, join } from 'node:path';
|
|
27
|
+
import { PACKAGE_VERSION } from './version.js';
|
|
28
|
+
const SERVER_NAME = 'cito';
|
|
29
|
+
function envHome(name, fallback) {
|
|
30
|
+
const v = process.env[name];
|
|
31
|
+
return v && v.trim() ? v : fallback;
|
|
32
|
+
}
|
|
33
|
+
function clientsFor() {
|
|
34
|
+
const home = homedir();
|
|
35
|
+
const os = platform();
|
|
36
|
+
const appData = envHome('APPDATA', join(home, 'AppData', 'Roaming'));
|
|
37
|
+
const desktopConfig = os === 'win32'
|
|
38
|
+
? join(appData, 'Claude', 'claude_desktop_config.json')
|
|
39
|
+
: os === 'darwin'
|
|
40
|
+
? join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')
|
|
41
|
+
: join(home, '.config', 'Claude', 'claude_desktop_config.json');
|
|
42
|
+
return [
|
|
43
|
+
{
|
|
44
|
+
id: 'claude-code',
|
|
45
|
+
label: 'Claude Code',
|
|
46
|
+
configPath: join(home, '.claude.json'),
|
|
47
|
+
detectPath: join(home, '.claude.json'),
|
|
48
|
+
format: 'json-mcpServers',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'claude-desktop',
|
|
52
|
+
label: 'Claude Desktop',
|
|
53
|
+
configPath: desktopConfig,
|
|
54
|
+
detectPath: dirname(desktopConfig),
|
|
55
|
+
format: 'json-mcpServers',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'cursor',
|
|
59
|
+
label: 'Cursor',
|
|
60
|
+
configPath: join(home, '.cursor', 'mcp.json'),
|
|
61
|
+
detectPath: join(home, '.cursor'),
|
|
62
|
+
format: 'json-mcpServers',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'windsurf',
|
|
66
|
+
label: 'Windsurf',
|
|
67
|
+
configPath: join(home, '.codeium', 'windsurf', 'mcp_config.json'),
|
|
68
|
+
detectPath: join(home, '.codeium', 'windsurf'),
|
|
69
|
+
format: 'json-mcpServers',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'codex',
|
|
73
|
+
label: 'Codex CLI',
|
|
74
|
+
configPath: join(home, '.codex', 'config.toml'),
|
|
75
|
+
detectPath: join(home, '.codex'),
|
|
76
|
+
format: 'toml-mcp_servers',
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
export function maskKey(key) {
|
|
81
|
+
if (key.length <= 12)
|
|
82
|
+
return '****';
|
|
83
|
+
return `${key.slice(0, 9)}…${key.slice(-4)}`;
|
|
84
|
+
}
|
|
85
|
+
/** The stdio entry every JSON-based client understands. */
|
|
86
|
+
function serverEntry(key, base) {
|
|
87
|
+
return {
|
|
88
|
+
type: 'stdio',
|
|
89
|
+
command: 'npx',
|
|
90
|
+
args: ['-y', 'cito-mcp'],
|
|
91
|
+
env: { CITO_API_KEY: key, ...(base ? { CITO_API_BASE: base } : {}) },
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function readJson(path) {
|
|
95
|
+
if (!existsSync(path))
|
|
96
|
+
return null;
|
|
97
|
+
try {
|
|
98
|
+
const raw = readFileSync(path, 'utf8').trim();
|
|
99
|
+
if (!raw)
|
|
100
|
+
return {};
|
|
101
|
+
const parsed = JSON.parse(raw);
|
|
102
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Escape a TOML basic string. Only the characters TOML actually requires —
|
|
110
|
+
* an API key with a quote or backslash would otherwise produce a file the
|
|
111
|
+
* client cannot parse.
|
|
112
|
+
*/
|
|
113
|
+
function tomlString(v) {
|
|
114
|
+
return `"${v.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
115
|
+
}
|
|
116
|
+
function tomlBlock(key, base) {
|
|
117
|
+
const envLines = [`CITO_API_KEY = ${tomlString(key)}`]
|
|
118
|
+
.concat(base ? [`CITO_API_BASE = ${tomlString(base)}`] : [])
|
|
119
|
+
.join('\n');
|
|
120
|
+
return [
|
|
121
|
+
`[mcp_servers.${SERVER_NAME}]`,
|
|
122
|
+
`command = "npx"`,
|
|
123
|
+
`args = ["-y", "cito-mcp"]`,
|
|
124
|
+
``,
|
|
125
|
+
`[mcp_servers.${SERVER_NAME}.env]`,
|
|
126
|
+
envLines,
|
|
127
|
+
``,
|
|
128
|
+
].join('\n');
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Replace an existing [mcp_servers.cito] block (and its .env subtable) or
|
|
132
|
+
* append a new one. Deliberately conservative: it only recognises the exact
|
|
133
|
+
* headers it writes, so a hand-edited file is appended to rather than mangled.
|
|
134
|
+
*/
|
|
135
|
+
export function upsertTomlBlock(existing, key, base) {
|
|
136
|
+
const block = tomlBlock(key, base);
|
|
137
|
+
const header = new RegExp(`^\\[mcp_servers\\.${SERVER_NAME}(\\.[A-Za-z_]+)?\\]\\s*$`);
|
|
138
|
+
const anyHeader = /^\[[^\]]+\]\s*$/;
|
|
139
|
+
const lines = existing.split(/\r?\n/);
|
|
140
|
+
const kept = [];
|
|
141
|
+
let skipping = false;
|
|
142
|
+
for (const line of lines) {
|
|
143
|
+
if (header.test(line)) {
|
|
144
|
+
skipping = true;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (skipping && anyHeader.test(line))
|
|
148
|
+
skipping = false;
|
|
149
|
+
if (!skipping)
|
|
150
|
+
kept.push(line);
|
|
151
|
+
}
|
|
152
|
+
const body = kept.join('\n').replace(/\n{3,}$/, '\n\n').replace(/\s*$/, '');
|
|
153
|
+
return (body ? `${body}\n\n` : '') + block;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Claude Code owns the schema of ~/.claude.json and stores far more than MCP
|
|
157
|
+
* config in it, so let its own CLI do the write when it is on PATH. Direct
|
|
158
|
+
* editing is the fallback, not the default.
|
|
159
|
+
*/
|
|
160
|
+
function tryClaudeCli(key, base, dryRun) {
|
|
161
|
+
const json = JSON.stringify(serverEntry(key, base));
|
|
162
|
+
// On Windows `claude` is a .cmd shim, which execFileSync cannot spawn
|
|
163
|
+
// directly — it needs a shell. Quoting matters there because the JSON
|
|
164
|
+
// payload contains spaces and double quotes.
|
|
165
|
+
const isWin = platform() === 'win32';
|
|
166
|
+
const run = (args) => {
|
|
167
|
+
if (!isWin) {
|
|
168
|
+
execFileSync('claude', args, { stdio: 'ignore' });
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
execFileSync('cmd', ['/c', 'claude', ...args], { stdio: 'ignore' });
|
|
172
|
+
};
|
|
173
|
+
try {
|
|
174
|
+
if (dryRun) {
|
|
175
|
+
run(['--version']);
|
|
176
|
+
return 'would use `claude mcp add-json` (CLI detected)';
|
|
177
|
+
}
|
|
178
|
+
run(['mcp', 'add-json', SERVER_NAME, json, '-s', 'user']);
|
|
179
|
+
return 'via `claude mcp add-json` (user scope)';
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function writeJsonClient(c, key, base, dryRun) {
|
|
186
|
+
const current = readJson(c.configPath);
|
|
187
|
+
if (current === null && existsSync(c.configPath)) {
|
|
188
|
+
return {
|
|
189
|
+
client: c,
|
|
190
|
+
status: 'failed',
|
|
191
|
+
note: `${c.configPath} is not valid JSON — left untouched`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const config = current ?? {};
|
|
195
|
+
const servers = (config.mcpServers ?? {});
|
|
196
|
+
const existed = Object.prototype.hasOwnProperty.call(servers, SERVER_NAME);
|
|
197
|
+
const next = { ...config, mcpServers: { ...servers, [SERVER_NAME]: serverEntry(key, base) } };
|
|
198
|
+
if (dryRun) {
|
|
199
|
+
return {
|
|
200
|
+
client: c,
|
|
201
|
+
status: 'planned',
|
|
202
|
+
note: `${existed ? 'replace' : 'add'} "${SERVER_NAME}" in ${c.configPath}`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
mkdirSync(dirname(c.configPath), { recursive: true });
|
|
206
|
+
if (existsSync(c.configPath))
|
|
207
|
+
copyFileSync(c.configPath, `${c.configPath}.cito-bak`);
|
|
208
|
+
writeFileSync(c.configPath, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
|
|
209
|
+
return {
|
|
210
|
+
client: c,
|
|
211
|
+
status: 'written',
|
|
212
|
+
note: `${existed ? 'updated' : 'added'} in ${c.configPath}`,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function writeTomlClient(c, key, base, dryRun) {
|
|
216
|
+
const existing = existsSync(c.configPath) ? readFileSync(c.configPath, 'utf8') : '';
|
|
217
|
+
const existed = new RegExp(`\\[mcp_servers\\.${SERVER_NAME}\\]`).test(existing);
|
|
218
|
+
if (dryRun) {
|
|
219
|
+
return {
|
|
220
|
+
client: c,
|
|
221
|
+
status: 'planned',
|
|
222
|
+
note: `${existed ? 'replace' : 'add'} [mcp_servers.${SERVER_NAME}] in ${c.configPath}`,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
mkdirSync(dirname(c.configPath), { recursive: true });
|
|
226
|
+
if (existsSync(c.configPath))
|
|
227
|
+
copyFileSync(c.configPath, `${c.configPath}.cito-bak`);
|
|
228
|
+
writeFileSync(c.configPath, upsertTomlBlock(existing, key, base), 'utf8');
|
|
229
|
+
return {
|
|
230
|
+
client: c,
|
|
231
|
+
status: 'written',
|
|
232
|
+
note: `${existed ? 'updated' : 'added'} in ${c.configPath}`,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function parseArgs(argv) {
|
|
236
|
+
const out = { key: undefined, base: undefined, only: [], dryRun: false, help: false };
|
|
237
|
+
for (let i = 0; i < argv.length; i++) {
|
|
238
|
+
const a = argv[i];
|
|
239
|
+
const eat = () => argv[++i];
|
|
240
|
+
if (a === '--help' || a === '-h')
|
|
241
|
+
out.help = true;
|
|
242
|
+
else if (a === '--dry-run' || a === '-n')
|
|
243
|
+
out.dryRun = true;
|
|
244
|
+
else if (a === '--key')
|
|
245
|
+
out.key = eat();
|
|
246
|
+
else if (a?.startsWith('--key='))
|
|
247
|
+
out.key = a.slice(6);
|
|
248
|
+
else if (a === '--base')
|
|
249
|
+
out.base = eat();
|
|
250
|
+
else if (a?.startsWith('--base='))
|
|
251
|
+
out.base = a.slice(7);
|
|
252
|
+
else if (a === '--client')
|
|
253
|
+
out.only.push(...(eat() ?? '').split(',').filter(Boolean));
|
|
254
|
+
else if (a?.startsWith('--client='))
|
|
255
|
+
out.only.push(...a.slice(9).split(',').filter(Boolean));
|
|
256
|
+
}
|
|
257
|
+
return out;
|
|
258
|
+
}
|
|
259
|
+
const HELP = `cito-mcp install — configure the Cito MCP server for your editors
|
|
260
|
+
|
|
261
|
+
Usage:
|
|
262
|
+
npx cito-mcp install --key cito_xxx
|
|
263
|
+
npx cito-mcp install --key cito_xxx --client cursor,claude-code
|
|
264
|
+
npx cito-mcp install --dry-run
|
|
265
|
+
|
|
266
|
+
Options:
|
|
267
|
+
--key <key> Cito API key. Falls back to $CITO_API_KEY.
|
|
268
|
+
--client <ids> Comma-separated: claude-code, claude-desktop, cursor, windsurf, codex.
|
|
269
|
+
Default: every client detected on this machine.
|
|
270
|
+
--base <url> Override API base (staging / self-hosted).
|
|
271
|
+
--dry-run, -n Show what would change; write nothing.
|
|
272
|
+
--help, -h This message.
|
|
273
|
+
|
|
274
|
+
Get a key at https://citoapi.com/dashboard`;
|
|
275
|
+
export async function runInstall(argv) {
|
|
276
|
+
const args = parseArgs(argv);
|
|
277
|
+
if (args.help) {
|
|
278
|
+
console.log(HELP);
|
|
279
|
+
return 0;
|
|
280
|
+
}
|
|
281
|
+
const key = args.key ?? process.env.CITO_API_KEY;
|
|
282
|
+
if (!key && !args.dryRun) {
|
|
283
|
+
console.error('No API key. Pass --key cito_xxx or set CITO_API_KEY.\n');
|
|
284
|
+
console.error('Get one at https://citoapi.com/dashboard');
|
|
285
|
+
return 1;
|
|
286
|
+
}
|
|
287
|
+
const all = clientsFor();
|
|
288
|
+
const unknown = args.only.filter((id) => !all.some((c) => c.id === id));
|
|
289
|
+
if (unknown.length) {
|
|
290
|
+
console.error(`Unknown client(s): ${unknown.join(', ')}`);
|
|
291
|
+
console.error(`Valid: ${all.map((c) => c.id).join(', ')}`);
|
|
292
|
+
return 1;
|
|
293
|
+
}
|
|
294
|
+
// An explicit --client is a instruction, not a guess: honour it even if we
|
|
295
|
+
// cannot detect the client (fresh install, portable install, unusual path).
|
|
296
|
+
const selected = args.only.length
|
|
297
|
+
? all.filter((c) => args.only.includes(c.id))
|
|
298
|
+
: all.filter((c) => existsSync(c.detectPath));
|
|
299
|
+
if (selected.length === 0) {
|
|
300
|
+
console.error('No supported MCP clients detected.');
|
|
301
|
+
console.error(`Looked for: ${all.map((c) => c.label).join(', ')}`);
|
|
302
|
+
console.error('Use --client <id> to configure one anyway.');
|
|
303
|
+
return 1;
|
|
304
|
+
}
|
|
305
|
+
console.log(`cito-mcp ${PACKAGE_VERSION} installer`);
|
|
306
|
+
console.log(key ? `key: ${maskKey(key)}` : 'key: (none — dry run)');
|
|
307
|
+
console.log(args.dryRun ? 'mode: dry run, nothing will be written\n' : '');
|
|
308
|
+
const effectiveKey = key ?? 'CITO_API_KEY';
|
|
309
|
+
const results = [];
|
|
310
|
+
for (const c of selected) {
|
|
311
|
+
if (c.id === 'claude-code') {
|
|
312
|
+
const viaCli = tryClaudeCli(effectiveKey, args.base, args.dryRun);
|
|
313
|
+
if (viaCli) {
|
|
314
|
+
results.push({ client: c, status: args.dryRun ? 'planned' : 'written', note: viaCli });
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
results.push(c.format === 'toml-mcp_servers'
|
|
319
|
+
? writeTomlClient(c, effectiveKey, args.base, args.dryRun)
|
|
320
|
+
: writeJsonClient(c, effectiveKey, args.base, args.dryRun));
|
|
321
|
+
}
|
|
322
|
+
for (const r of results) {
|
|
323
|
+
const mark = r.status === 'failed' ? '✗' : r.status === 'skipped' ? '–' : '✓';
|
|
324
|
+
console.log(` ${mark} ${r.client.label.padEnd(16)} ${r.note}`);
|
|
325
|
+
}
|
|
326
|
+
const failed = results.filter((r) => r.status === 'failed');
|
|
327
|
+
console.log('');
|
|
328
|
+
if (args.dryRun) {
|
|
329
|
+
console.log('Dry run complete. Re-run without --dry-run to apply.');
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
console.log('Restart your editor to load the server, then ask it:');
|
|
333
|
+
console.log(' "what UFC events are coming up?"');
|
|
334
|
+
}
|
|
335
|
+
if (failed.length) {
|
|
336
|
+
console.log(`\n${failed.length} client(s) could not be configured (see above).`);
|
|
337
|
+
return 1;
|
|
338
|
+
}
|
|
339
|
+
return 0;
|
|
340
|
+
}
|
package/package.json
CHANGED