linkedin-toolkit-mcp 2.0.2 → 2.1.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/dist/bridge.d.ts +1 -1
- package/dist/bridge.js +1 -1
- package/dist/cli.d.ts +44 -0
- package/dist/cli.js +300 -7
- package/dist/cli.js.map +1 -1
- package/dist/contract.d.ts +38 -9
- package/dist/contract.js +53 -8
- package/dist/contract.js.map +1 -1
- package/dist/endpoints.d.ts +49 -0
- package/dist/endpoints.js +160 -0
- package/dist/endpoints.js.map +1 -0
- package/dist/fake-data.d.ts +8 -0
- package/dist/fake-data.js +37 -2
- package/dist/fake-data.js.map +1 -1
- package/dist/prompts.js +1 -1
- package/dist/prompts.js.map +1 -1
- package/dist/server.d.ts +11 -0
- package/dist/server.js +18 -1
- package/dist/server.js.map +1 -1
- package/dist/setup.d.ts +248 -0
- package/dist/setup.js +584 -0
- package/dist/setup.js.map +1 -0
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +1 -1
- package/openapi.json +290 -8
- package/package.json +1 -1
- package/tools.json +5 -4
package/dist/bridge.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { EventEmitter } from 'node:events';
|
|
15
15
|
import type { ErrorCode, EventName, RequestOrigin } from './contract.js';
|
|
16
|
-
export declare const SERVER_VERSION = "2.
|
|
16
|
+
export declare const SERVER_VERSION = "2.1.0";
|
|
17
17
|
export declare const OFFLINE_HOW_TO_FIX: string;
|
|
18
18
|
export declare class BridgeError extends Error {
|
|
19
19
|
readonly code: ErrorCode;
|
package/dist/bridge.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { EventEmitter } from 'node:events';
|
|
15
15
|
import { randomUUID } from 'node:crypto';
|
|
16
16
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
17
|
-
export const SERVER_VERSION = '2.
|
|
17
|
+
export const SERVER_VERSION = '2.1.0';
|
|
18
18
|
export const OFFLINE_HOW_TO_FIX = 'Install the extension from GitHub Releases, open the popup → Settings → Local bridge, ' +
|
|
19
19
|
'paste the pairing token printed by `lit serve`, and enable the bridge.';
|
|
20
20
|
export class BridgeError extends Error {
|
package/dist/cli.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { Command } from 'commander';
|
|
11
11
|
import { type ServerConfig } from './config.js';
|
|
12
|
+
import { type FsLike } from './setup.js';
|
|
12
13
|
import { FakeExtensionClient } from './fake-extension.js';
|
|
13
14
|
import { HttpServer } from './http.js';
|
|
14
15
|
import { Toolkit } from './toolkit.js';
|
|
@@ -79,5 +80,48 @@ export declare function serve(options: {
|
|
|
79
80
|
bridgePort?: number;
|
|
80
81
|
fake?: boolean;
|
|
81
82
|
}, io: Io): Promise<ServeHandles>;
|
|
83
|
+
export type SetupOptions = {
|
|
84
|
+
dir?: string;
|
|
85
|
+
open?: boolean;
|
|
86
|
+
dryRun?: boolean;
|
|
87
|
+
client?: string;
|
|
88
|
+
version?: string;
|
|
89
|
+
wait?: number;
|
|
90
|
+
};
|
|
91
|
+
/** A server `lit setup` is holding open while it waits for the extension. */
|
|
92
|
+
export type SetupServer = {
|
|
93
|
+
url: string;
|
|
94
|
+
startedHere: boolean;
|
|
95
|
+
connected: () => Promise<boolean>;
|
|
96
|
+
stop: () => Promise<void>;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Everything `lit setup` touches that a test must not: the network, the
|
|
100
|
+
* filesystem, the clock, Chrome, and a listening socket.
|
|
101
|
+
*/
|
|
102
|
+
export type SetupDeps = {
|
|
103
|
+
fetchImpl?: typeof fetch;
|
|
104
|
+
fs?: FsLike;
|
|
105
|
+
now?: () => number;
|
|
106
|
+
platform?: string;
|
|
107
|
+
env?: NodeJS.ProcessEnv;
|
|
108
|
+
home?: string;
|
|
109
|
+
cwd?: string;
|
|
110
|
+
/** Ask the OS to launch Chrome. Returns false if it could not even try. */
|
|
111
|
+
launch?: (command: string, args: string[]) => boolean;
|
|
112
|
+
exists?: (path: string) => boolean;
|
|
113
|
+
openServer?: () => Promise<SetupServer>;
|
|
114
|
+
sleep?: (ms: number) => Promise<void>;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* `lit setup` — the one command.
|
|
118
|
+
*
|
|
119
|
+
* Downloads the extension for this package's version, checks it really is an
|
|
120
|
+
* extension, unpacks it somewhere permanent, prints the three Chrome steps
|
|
121
|
+
* nobody can automate, waits for the extension to pair, and writes the MCP
|
|
122
|
+
* config for whichever client was asked for. Each step reports what it did,
|
|
123
|
+
* including when it did nothing.
|
|
124
|
+
*/
|
|
125
|
+
export declare function runSetup(options: SetupOptions, io: Io, deps?: SetupDeps): Promise<void>;
|
|
82
126
|
export declare function buildProgram(io?: Io): Command;
|
|
83
127
|
export declare function run(argv: string[], io?: Io): Promise<number>;
|
package/dist/cli.js
CHANGED
|
@@ -8,11 +8,14 @@
|
|
|
8
8
|
* same code path.
|
|
9
9
|
*/
|
|
10
10
|
import { Command, CommanderError } from 'commander';
|
|
11
|
-
import {
|
|
11
|
+
import { spawn } from 'node:child_process';
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
12
13
|
import { join, resolve } from 'node:path';
|
|
13
14
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
15
|
import { clearRuntime, generateToken, loadConfig, pairingInstructions, readRuntime, saveConfig, withOverrides, writeRuntime, } from './config.js';
|
|
15
16
|
import { ORIGIN_HEADER } from './contract.js';
|
|
17
|
+
import { doctorReport } from './endpoints.js';
|
|
18
|
+
import { CLIENTS, chromeLaunch, chromeSteps, clientTarget, defaultExtensionDir, installExtension, packageVersion, pairingTimeoutMessage, realFs, SERVER_KEY, serverEntry, SetupError, writeClientConfig, } from './setup.js';
|
|
16
19
|
import { TABLES } from './db.js';
|
|
17
20
|
import { createDemoHandlers, FAKE_BANNER } from './fake-data.js';
|
|
18
21
|
import { FakeExtensionClient } from './fake-extension.js';
|
|
@@ -334,6 +337,219 @@ export async function serve(options, io) {
|
|
|
334
337
|
},
|
|
335
338
|
};
|
|
336
339
|
}
|
|
340
|
+
const DEFAULT_WAIT_SECONDS = 120;
|
|
341
|
+
/** Detect a running server, or start one and hold it open for the pairing. */
|
|
342
|
+
async function openServerForPairing() {
|
|
343
|
+
const { config } = loadConfig();
|
|
344
|
+
const api = ServerClient.fromConfig(config);
|
|
345
|
+
try {
|
|
346
|
+
const health = await api.health();
|
|
347
|
+
if (health.ok) {
|
|
348
|
+
return {
|
|
349
|
+
url: api.baseUrl,
|
|
350
|
+
startedHere: false,
|
|
351
|
+
connected: async () => (await api.health()).extensionConnected,
|
|
352
|
+
stop: async () => undefined,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
/* not running: start one below */
|
|
358
|
+
}
|
|
359
|
+
const quiet = { out: () => undefined, err: () => undefined };
|
|
360
|
+
const handles = await serve({ http: true }, quiet);
|
|
361
|
+
return {
|
|
362
|
+
url: handles.http?.url ?? `http://127.0.0.1:${config.httpPort}`,
|
|
363
|
+
startedHere: true,
|
|
364
|
+
connected: async () => handles.toolkit.isConnected(),
|
|
365
|
+
stop: () => handles.stop(),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* `lit setup` — the one command.
|
|
370
|
+
*
|
|
371
|
+
* Downloads the extension for this package's version, checks it really is an
|
|
372
|
+
* extension, unpacks it somewhere permanent, prints the three Chrome steps
|
|
373
|
+
* nobody can automate, waits for the extension to pair, and writes the MCP
|
|
374
|
+
* config for whichever client was asked for. Each step reports what it did,
|
|
375
|
+
* including when it did nothing.
|
|
376
|
+
*/
|
|
377
|
+
export async function runSetup(options, io, deps = {}) {
|
|
378
|
+
const fs = deps.fs ?? realFs;
|
|
379
|
+
const platform = deps.platform ?? process.platform;
|
|
380
|
+
const env = deps.env ?? process.env;
|
|
381
|
+
const dryRun = Boolean(options.dryRun);
|
|
382
|
+
const waitSeconds = options.wait === undefined ? DEFAULT_WAIT_SECONDS : Math.max(0, options.wait);
|
|
383
|
+
const version = options.version ?? packageVersion() ?? SERVER_VERSION;
|
|
384
|
+
const dir = resolve(options.dir ?? defaultExtensionDir(env.LINKEDIN_TOOLKIT_HOME));
|
|
385
|
+
const clientId = (options.client ?? 'print');
|
|
386
|
+
if (!CLIENTS.includes(clientId)) {
|
|
387
|
+
throw new CliError(`"${options.client}" is not a client. Choose one of: ${CLIENTS.join(', ')}.\n` +
|
|
388
|
+
'Every other client is a paste: see docs/clients.md.');
|
|
389
|
+
}
|
|
390
|
+
io.out('LinkedIn Toolkit setup');
|
|
391
|
+
io.out(dryRun ? '(dry run — nothing will be written)\n' : '');
|
|
392
|
+
/* -- 1. the extension ------------------------------------------- */
|
|
393
|
+
io.out('[1/4] Extension');
|
|
394
|
+
if (dryRun) {
|
|
395
|
+
io.out(` Would install version ${version} into ${dir}`);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
let installed;
|
|
399
|
+
try {
|
|
400
|
+
installed = await installExtension({
|
|
401
|
+
version,
|
|
402
|
+
dir,
|
|
403
|
+
fetchImpl: deps.fetchImpl,
|
|
404
|
+
fs,
|
|
405
|
+
now: deps.now,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
catch (err) {
|
|
409
|
+
if (err instanceof SetupError) {
|
|
410
|
+
throw new CliError([err.message, err.howToFix].filter(Boolean).join('\n '));
|
|
411
|
+
}
|
|
412
|
+
throw err;
|
|
413
|
+
}
|
|
414
|
+
if (installed.source === 'latest' && installed.version !== version.replace(/^v/, '')) {
|
|
415
|
+
io.out(` No release v${version.replace(/^v/, '')}; installed the latest instead (v${installed.version}).`);
|
|
416
|
+
}
|
|
417
|
+
io.out(` Downloaded ${installed.url}`);
|
|
418
|
+
io.out(` Unpacked ${installed.files} files (extension v${installed.manifestVersion}) to:\n` +
|
|
419
|
+
` ${installed.dir}`);
|
|
420
|
+
if (installed.replaced)
|
|
421
|
+
io.out(' The previous copy was replaced.');
|
|
422
|
+
}
|
|
423
|
+
/* -- 2. the three clicks ---------------------------------------- */
|
|
424
|
+
io.out('');
|
|
425
|
+
io.out('[2/4] Three steps only you can do (Chrome does not allow any installer to do them)');
|
|
426
|
+
for (const step of chromeSteps(dir))
|
|
427
|
+
io.out(step);
|
|
428
|
+
if (options.open === false) {
|
|
429
|
+
io.out(' (--no-open: not touching Chrome.)');
|
|
430
|
+
}
|
|
431
|
+
else if (dryRun) {
|
|
432
|
+
io.out(' (dry run: not touching Chrome.)');
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
const plan = chromeLaunch(platform, env, deps.exists ?? ((path) => existsSync(path)));
|
|
436
|
+
if (plan.kind === 'none') {
|
|
437
|
+
io.out(` Not opening Chrome for you: ${plan.reason}. Type the URL above.`);
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
const launched = (deps.launch ?? defaultLaunch)(plan.command, plan.args);
|
|
441
|
+
io.out(launched
|
|
442
|
+
? ' Asked Chrome to open that page. Chrome ignores chrome:// URLs from the command\n' +
|
|
443
|
+
' line in many builds, so if no tab appeared, type it in the address bar.'
|
|
444
|
+
: ' Could not start Chrome from here. Open it yourself and type the URL above.');
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/* -- 3. pairing -------------------------------------------------- */
|
|
448
|
+
io.out('');
|
|
449
|
+
io.out('[3/4] Pairing');
|
|
450
|
+
const { config } = loadConfig();
|
|
451
|
+
io.out(` Pairing token: ${config.token}`);
|
|
452
|
+
io.out(' In the popup: Settings -> Local bridge -> paste the token -> enable.');
|
|
453
|
+
if (dryRun) {
|
|
454
|
+
io.out(' (dry run: not starting a server and not waiting.)');
|
|
455
|
+
}
|
|
456
|
+
else if (waitSeconds === 0) {
|
|
457
|
+
io.out(' (--wait 0: not waiting. Run `lit serve --http`, then `lit status`.)');
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
const server = await (deps.openServer ?? openServerForPairing)();
|
|
461
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((done) => setTimeout(done, ms)));
|
|
462
|
+
try {
|
|
463
|
+
io.out(server.startedHere
|
|
464
|
+
? ` Started a server on ${server.url} and holding it open while you pair.`
|
|
465
|
+
: ` A server is already running on ${server.url}.`);
|
|
466
|
+
io.out(` Waiting up to ${waitSeconds}s for the extension to connect...`);
|
|
467
|
+
let paired = await server.connected();
|
|
468
|
+
const deadline = (deps.now ?? Date.now)() + waitSeconds * 1000;
|
|
469
|
+
while (!paired && (deps.now ?? Date.now)() < deadline) {
|
|
470
|
+
await sleep(1000);
|
|
471
|
+
paired = await server.connected();
|
|
472
|
+
}
|
|
473
|
+
if (paired) {
|
|
474
|
+
io.out(' Paired. The extension is talking to this machine.');
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
for (const line of pairingTimeoutMessage(waitSeconds).split('\n'))
|
|
478
|
+
io.out(` ${line}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
finally {
|
|
482
|
+
await server.stop();
|
|
483
|
+
if (server.startedHere) {
|
|
484
|
+
io.out(' (That setup server has stopped. Pairing is remembered by the extension;');
|
|
485
|
+
io.out(' your agent starts its own server, or run `lit serve --http` yourself.)');
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
/* -- 4. the agent's config --------------------------------------- */
|
|
490
|
+
io.out('');
|
|
491
|
+
io.out('[4/4] Agent config');
|
|
492
|
+
const target = clientTarget(clientId, {
|
|
493
|
+
platform,
|
|
494
|
+
env,
|
|
495
|
+
home: deps.home,
|
|
496
|
+
cwd: deps.cwd,
|
|
497
|
+
});
|
|
498
|
+
const entry = serverEntry(clientId);
|
|
499
|
+
const snippet = `${JSON.stringify({ [target.key]: { [SERVER_KEY]: entry } }, null, 2)}`;
|
|
500
|
+
if (target.mode === 'print') {
|
|
501
|
+
io.out(` ${target.label}: no file to write.`);
|
|
502
|
+
io.out(` ${target.note}`);
|
|
503
|
+
io.out('');
|
|
504
|
+
for (const line of snippet.split('\n'))
|
|
505
|
+
io.out(` ${line}`);
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
let outcome;
|
|
509
|
+
try {
|
|
510
|
+
outcome = writeClientConfig({ target, entry, dryRun, fs, now: deps.now });
|
|
511
|
+
}
|
|
512
|
+
catch (err) {
|
|
513
|
+
if (err instanceof SetupError) {
|
|
514
|
+
throw new CliError([err.message, err.howToFix].filter(Boolean).join('\n '));
|
|
515
|
+
}
|
|
516
|
+
throw err;
|
|
517
|
+
}
|
|
518
|
+
if (outcome.action === 'dry-run') {
|
|
519
|
+
io.out(` Would ${outcome.created ? 'create' : 'update'} ${outcome.path}:`);
|
|
520
|
+
io.out('');
|
|
521
|
+
for (const line of snippet.split('\n'))
|
|
522
|
+
io.out(` ${line}`);
|
|
523
|
+
}
|
|
524
|
+
else if (outcome.action === 'unchanged') {
|
|
525
|
+
io.out(` ${outcome.path} already has "${SERVER_KEY}". Nothing to change.`);
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
io.out(` ${outcome.created ? 'Created' : 'Updated'} ${outcome.path}`);
|
|
529
|
+
if (outcome.backup)
|
|
530
|
+
io.out(` Backed the old one up to ${outcome.backup}`);
|
|
531
|
+
}
|
|
532
|
+
if (outcome.kept.length > 0) {
|
|
533
|
+
io.out(` Your other MCP servers are untouched: ${outcome.kept.join(', ')}`);
|
|
534
|
+
}
|
|
535
|
+
io.out(` ${target.note}`);
|
|
536
|
+
}
|
|
537
|
+
io.out('');
|
|
538
|
+
io.out('Done. `lit status` says whether the extension is connected;');
|
|
539
|
+
io.out('docs/clients.md has the config block for every other client.');
|
|
540
|
+
}
|
|
541
|
+
/** Launch Chrome, detached, ignoring its output. False if it would not start. */
|
|
542
|
+
function defaultLaunch(command, args) {
|
|
543
|
+
try {
|
|
544
|
+
const child = spawn(command, args, { detached: true, stdio: 'ignore' });
|
|
545
|
+
child.on('error', () => undefined);
|
|
546
|
+
child.unref();
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
337
553
|
/* ------------------------------------------------------------------ *
|
|
338
554
|
* Commands
|
|
339
555
|
* ------------------------------------------------------------------ */
|
|
@@ -361,10 +577,33 @@ export function buildProgram(io = defaultIo) {
|
|
|
361
577
|
.description('LinkedIn Toolkit: drive your own logged-in Chrome through the toolkit extension.\n' +
|
|
362
578
|
'Start the server with `lit serve --http`, then every other command talks to it.')
|
|
363
579
|
.version(SERVER_VERSION)
|
|
580
|
+
// Without this, commander lets the program's own `-V, --version` swallow
|
|
581
|
+
// `lit setup --version 2.1.0` and print its version instead of installing
|
|
582
|
+
// that one. Positional parsing keeps each command's flags to itself.
|
|
583
|
+
.enablePositionalOptions()
|
|
364
584
|
.configureOutput({
|
|
365
585
|
writeOut: (text) => io.out(text.replace(/\n$/, '')),
|
|
366
586
|
writeErr: (text) => io.err(text.replace(/\n$/, '')),
|
|
367
587
|
});
|
|
588
|
+
program
|
|
589
|
+
.command('setup')
|
|
590
|
+
.description('Install the extension, pair it, and write your agent\'s MCP config. Start here.')
|
|
591
|
+
.option('--dir <path>', 'where to unpack the extension (default ~/.linkedin-toolkit/extension)')
|
|
592
|
+
.option('--no-open', 'do not ask Chrome to open chrome://extensions')
|
|
593
|
+
.option('--dry-run', 'say what would happen, and write nothing')
|
|
594
|
+
.option('--client <client>', `write the MCP config for: ${CLIENTS.join(' | ')}`, 'print')
|
|
595
|
+
.option('--version <version>', 'install a specific release instead of this package\'s version')
|
|
596
|
+
.option('--wait <seconds>', `how long to wait for the extension to pair (default ${DEFAULT_WAIT_SECONDS}, 0 to skip)`, (value) => Number(value))
|
|
597
|
+
.action(async (options) => {
|
|
598
|
+
await runSetup({
|
|
599
|
+
dir: options.dir,
|
|
600
|
+
open: options.open,
|
|
601
|
+
dryRun: options.dryRun,
|
|
602
|
+
client: options.client,
|
|
603
|
+
version: options.version,
|
|
604
|
+
wait: options.wait,
|
|
605
|
+
}, io);
|
|
606
|
+
});
|
|
368
607
|
program
|
|
369
608
|
.command('serve')
|
|
370
609
|
.description('Run the bridge and the MCP server. Add --http for the HTTP API.')
|
|
@@ -505,7 +744,7 @@ export function buildProgram(io = defaultIo) {
|
|
|
505
744
|
.command('invite')
|
|
506
745
|
.argument('<url>', 'profile URL or publicId')
|
|
507
746
|
.description('Send a connection invite (queued for approval in Copilot mode).')
|
|
508
|
-
.option('--note <note>', 'a note,
|
|
747
|
+
.option('--note <note>', 'a note, at most 200 characters (LinkedIn\'s limit)')
|
|
509
748
|
.option('--dry-run', 'show what would be sent without sending it')
|
|
510
749
|
.action(async (url, options) => {
|
|
511
750
|
const data = await client().action('outreach.invite', {
|
|
@@ -552,11 +791,13 @@ export function buildProgram(io = defaultIo) {
|
|
|
552
791
|
const columns = ['threadId', 'who', 'unread', ...(options.sentiment ? ['sentiment'] : []), 'snippet'];
|
|
553
792
|
print(io, options.json, data, () => table(threads, columns));
|
|
554
793
|
});
|
|
794
|
+
const QUEUE_STATUSES = ['pending', 'approved', 'rejected', 'sent', 'failed'];
|
|
555
795
|
program
|
|
556
796
|
.command('queue')
|
|
557
797
|
.argument('[action]', 'approve | reject | list', 'list')
|
|
558
798
|
.argument('[ids...]', 'queue item ids')
|
|
559
799
|
.description('Show the approval queue, or approve or reject items.')
|
|
800
|
+
.option('--status <status>', `filter the list: ${QUEUE_STATUSES.join(' | ')} (default pending)`)
|
|
560
801
|
.option('--json', 'print raw JSON')
|
|
561
802
|
.action(async (action, ids, options) => {
|
|
562
803
|
const api = client();
|
|
@@ -564,20 +805,34 @@ export function buildProgram(io = defaultIo) {
|
|
|
564
805
|
if (ids.length === 0)
|
|
565
806
|
throw new CliError(`Give at least one id: lit queue ${action} <id>`);
|
|
566
807
|
const data = await api.action(`queue.${action}`, { ids });
|
|
567
|
-
|
|
808
|
+
if (action === 'reject') {
|
|
809
|
+
io.out(`Rejected ${data.rejected}.`);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
// Approving no longer waits for the sends: the extension paces them and
|
|
813
|
+
// marks each item as it goes, so say where to look rather than implying
|
|
814
|
+
// they have already gone out.
|
|
815
|
+
io.out(`Approved ${data.approved}. Sending in the background — check with: lit queue list --status sent`);
|
|
568
816
|
return;
|
|
569
817
|
}
|
|
570
818
|
if (action !== 'list')
|
|
571
819
|
throw new CliError(`Unknown queue action "${action}".`);
|
|
572
|
-
const
|
|
820
|
+
const status = options.status ?? 'pending';
|
|
821
|
+
if (!QUEUE_STATUSES.includes(status)) {
|
|
822
|
+
throw new CliError(`Unknown queue status "${status}". Use one of: ${QUEUE_STATUSES.join(', ')}.`);
|
|
823
|
+
}
|
|
824
|
+
const data = await api.action('queue.list', { status });
|
|
573
825
|
const items = (data.items ?? []).map((item) => ({
|
|
574
826
|
id: item.id,
|
|
575
827
|
action: item.action,
|
|
576
828
|
who: item.profile?.fullName ?? item.params?.publicId ?? '',
|
|
577
829
|
origin: item.origin,
|
|
578
|
-
|
|
830
|
+
status: item.status,
|
|
831
|
+
// A failed item's whole value is why it failed, so that is what the
|
|
832
|
+
// preview column shows instead of the note nobody received.
|
|
833
|
+
preview: String(item.result?.error?.message ?? item.params?.note ?? item.params?.body ?? '').slice(0, 60),
|
|
579
834
|
}));
|
|
580
|
-
print(io, options.json, data, () => table(items, ['id', 'action', 'who', 'origin', 'preview']));
|
|
835
|
+
print(io, options.json, data, () => table(items, ['id', 'action', 'who', 'origin', 'status', 'preview']));
|
|
581
836
|
});
|
|
582
837
|
const campaign = program.command('campaign').description('Create and control campaigns.');
|
|
583
838
|
campaign
|
|
@@ -729,6 +984,33 @@ export function buildProgram(io = defaultIo) {
|
|
|
729
984
|
throw new CliError('', 2);
|
|
730
985
|
}
|
|
731
986
|
});
|
|
987
|
+
endpointsCommand
|
|
988
|
+
.command('doctor')
|
|
989
|
+
.description('Run the check and explain any failure: which query id hash went stale, which file ' +
|
|
990
|
+
'holds it, and how to re-capture it. Exits 2 if any endpoint failed.')
|
|
991
|
+
.option('--post <url>', 'a post URL, so the reaction endpoint can be checked too')
|
|
992
|
+
.option('--json', 'also print the raw check output — this is what the issue template asks for')
|
|
993
|
+
.action(async (options) => {
|
|
994
|
+
const status = await client().action('status.get', {
|
|
995
|
+
verify: true,
|
|
996
|
+
...(options.post ? { postUrl: options.post } : {}),
|
|
997
|
+
});
|
|
998
|
+
const report = doctorReport({
|
|
999
|
+
endpoints: status.endpoints ?? {},
|
|
1000
|
+
errors: status.endpointErrors ?? {},
|
|
1001
|
+
clientVersionCaptured: status.clientVersionCaptured,
|
|
1002
|
+
endpointsCapturedAt: status.endpointsCapturedAt,
|
|
1003
|
+
loggedIn: status.loggedIn,
|
|
1004
|
+
extensionVersion: status.extensionVersion,
|
|
1005
|
+
});
|
|
1006
|
+
io.out(report.text);
|
|
1007
|
+
if (options.json) {
|
|
1008
|
+
io.out('');
|
|
1009
|
+
io.out(JSON.stringify(status, null, 2));
|
|
1010
|
+
}
|
|
1011
|
+
if (report.failed.length > 0)
|
|
1012
|
+
throw new CliError('', 2);
|
|
1013
|
+
});
|
|
732
1014
|
const configCommand = program
|
|
733
1015
|
.command('config')
|
|
734
1016
|
.description('Read and change this server\'s local settings (~/.linkedin-toolkit/config.json).');
|
|
@@ -865,9 +1147,20 @@ export function buildProgram(io = defaultIo) {
|
|
|
865
1147
|
/* ------------------------------------------------------------------ *
|
|
866
1148
|
* Entry point
|
|
867
1149
|
* ------------------------------------------------------------------ */
|
|
1150
|
+
/**
|
|
1151
|
+
* `exitOverride` is inherited by a subcommand only if it was set before that
|
|
1152
|
+
* subcommand was created, and `buildProgram` creates them all up front. Without
|
|
1153
|
+
* this walk, `lit setup --help` reaches commander's `process.exit` and kills
|
|
1154
|
+
* whatever embedded this CLI — including the test runner.
|
|
1155
|
+
*/
|
|
1156
|
+
function overrideExits(command) {
|
|
1157
|
+
command.exitOverride();
|
|
1158
|
+
for (const child of command.commands)
|
|
1159
|
+
overrideExits(child);
|
|
1160
|
+
}
|
|
868
1161
|
export async function run(argv, io = defaultIo) {
|
|
869
1162
|
const program = buildProgram(io);
|
|
870
|
-
program
|
|
1163
|
+
overrideExits(program);
|
|
871
1164
|
try {
|
|
872
1165
|
await program.parseAsync(argv, { from: 'user' });
|
|
873
1166
|
return 0;
|