@testchimp/cli 0.1.81 → 0.1.83
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 +4 -0
- package/dist/cli/program.js +74 -1
- package/dist/core/dates.d.ts +6 -0
- package/dist/core/dates.js +30 -0
- package/dist/core/schemas.d.ts +17 -0
- package/dist/core/schemas.js +30 -1
- package/dist/core/tools.js +64 -1
- package/dist/core/tools.test.js +89 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,11 +48,15 @@ testchimp promote-perf-baseline --run-id 01ABC --env-class CI
|
|
|
48
48
|
testchimp compare-perf-to-baseline --run-id 01ABC --max-p95-regression-percent 10
|
|
49
49
|
testchimp list-related-perf-tests --scenario-titles "Checkout,Refund"
|
|
50
50
|
testchimp list-api-operation-interactions --operation-id 01XYZ --interaction-type REAL --limit 100
|
|
51
|
+
testchimp list-meeting-filter-options
|
|
52
|
+
testchimp list-meetings --from 2026-09-01 --to 2026-09-30 --domain customer.com --label Sales --search "pricing"
|
|
53
|
+
testchimp get-meeting-transcript --meeting-id <meeting-id> --summary-only
|
|
51
54
|
```
|
|
52
55
|
|
|
53
56
|
- **stdout:** API response JSON.
|
|
54
57
|
- **stderr:** progress for `provision-ephemeral-environment-and-wait` (“still waiting…” polls).
|
|
55
58
|
- **performance gate:** `compare-perf-to-baseline` still prints its JSON response but exits nonzero when `regressed` is `true` (top-level or under `comparison`).
|
|
59
|
+
- **Meetings:** `list-meetings` / `list-meeting-filter-options` cover team-wide Meeting Bots meetings only (same filters as the Meetings page). `--from` / `--to` take `YYYY-MM-DD` (inclusive local days), ISO datetimes, or epoch millis; `--label`, `--participant`, `--domain` are repeatable or comma-separated.
|
|
56
60
|
- **Flags:** default for each subcommand; **`--json-input '<json>'`** or **`--json-input @file.json`** merges over flags (JSON wins on conflicts). Use JSON for nested bodies (e.g. TrueCoverage scopes).
|
|
57
61
|
|
|
58
62
|
## Migration from `testchimp-mcp-client`
|
package/dist/cli/program.js
CHANGED
|
@@ -485,16 +485,89 @@ export function buildCliProgram() {
|
|
|
485
485
|
.description(TOOL_DEFINITIONS.find((t) => t.kebab === "get-meeting-transcript").description)
|
|
486
486
|
.addOption(jsonInputOption())
|
|
487
487
|
.option("--meeting-id <id>", "meeting id (calendar event id, or URL hash for ad-hoc; same as Studio folder under ~/.testchimp/data/meetings/)")
|
|
488
|
+
.option("--summary-only", "return only the post-meeting summary (omit the transcript body)")
|
|
488
489
|
.action(async (opts) => {
|
|
489
490
|
const body = {};
|
|
490
491
|
if (opts.meetingId) {
|
|
491
492
|
body.meetingId = String(opts.meetingId).trim();
|
|
492
493
|
}
|
|
494
|
+
if (opts.summaryOnly) {
|
|
495
|
+
body.summaryOnly = true;
|
|
496
|
+
}
|
|
493
497
|
const merged = mergeBodies(body, opts.jsonInput);
|
|
494
498
|
if (!merged.meetingId || String(merged.meetingId).trim() === "") {
|
|
495
499
|
throw new Error("meetingId is required (--meeting-id)");
|
|
496
500
|
}
|
|
497
|
-
const out = await runTool("get-meeting-transcript", {
|
|
501
|
+
const out = await runTool("get-meeting-transcript", {
|
|
502
|
+
meetingId: String(merged.meetingId).trim(),
|
|
503
|
+
...(merged.summaryOnly ? { summaryOnly: true } : {}),
|
|
504
|
+
}, { postMcp });
|
|
505
|
+
console.log(out);
|
|
506
|
+
});
|
|
507
|
+
program
|
|
508
|
+
.command("get-meeting-set")
|
|
509
|
+
.description(TOOL_DEFINITIONS.find((t) => t.kebab === "get-meeting-set").description)
|
|
510
|
+
.addOption(jsonInputOption())
|
|
511
|
+
.option("--meeting-set-id <id>", "meeting-set ULID (from `/testchimp using meeting-set context <id>`)")
|
|
512
|
+
.action(async (opts) => {
|
|
513
|
+
const body = {};
|
|
514
|
+
if (opts.meetingSetId) {
|
|
515
|
+
body.meetingSetId = String(opts.meetingSetId).trim();
|
|
516
|
+
}
|
|
517
|
+
const merged = mergeBodies(body, opts.jsonInput);
|
|
518
|
+
if (!merged.meetingSetId || String(merged.meetingSetId).trim() === "") {
|
|
519
|
+
throw new Error("meetingSetId is required (--meeting-set-id)");
|
|
520
|
+
}
|
|
521
|
+
const out = await runTool("get-meeting-set", { meetingSetId: String(merged.meetingSetId).trim() }, { postMcp });
|
|
522
|
+
console.log(out);
|
|
523
|
+
});
|
|
524
|
+
const collectRepeatable = (value, previous = []) => [
|
|
525
|
+
...previous,
|
|
526
|
+
...value.split(",").map((s) => s.trim()).filter(Boolean),
|
|
527
|
+
];
|
|
528
|
+
program
|
|
529
|
+
.command("list-meetings")
|
|
530
|
+
.description(TOOL_DEFINITIONS.find((t) => t.kebab === "list-meetings").description)
|
|
531
|
+
.addOption(jsonInputOption())
|
|
532
|
+
.option("--from <date>", "inclusive start: YYYY-MM-DD (local start of day), ISO datetime, or epoch millis")
|
|
533
|
+
.option("--to <date>", "inclusive end: YYYY-MM-DD (local end of day), ISO datetime, or epoch millis")
|
|
534
|
+
.option("--label <label>", "label filter (repeatable or comma-separated; OR, case-insensitive)", collectRepeatable)
|
|
535
|
+
.option("--participant <emailOrUserId>", "participant filter by email or user id (repeatable or comma-separated; OR)", collectRepeatable)
|
|
536
|
+
.option("--domain <domain>", "participant email domain filter (repeatable or comma-separated; OR)", collectRepeatable)
|
|
537
|
+
.option("--search <text>", "full-text search over title + transcript")
|
|
538
|
+
.option("--page-size <n>", "page size (default 50, max 200; max 25 when searching)")
|
|
539
|
+
.option("--page-token <token>", "nextPageToken from the previous page")
|
|
540
|
+
.action(async (opts) => {
|
|
541
|
+
const body = {};
|
|
542
|
+
if (opts.from)
|
|
543
|
+
body.from = String(opts.from).trim();
|
|
544
|
+
if (opts.to)
|
|
545
|
+
body.to = String(opts.to).trim();
|
|
546
|
+
if (opts.label?.length)
|
|
547
|
+
body.labels = opts.label;
|
|
548
|
+
if (opts.participant?.length)
|
|
549
|
+
body.participantKeys = opts.participant;
|
|
550
|
+
if (opts.domain?.length)
|
|
551
|
+
body.participantDomains = opts.domain;
|
|
552
|
+
if (opts.search)
|
|
553
|
+
body.searchText = String(opts.search);
|
|
554
|
+
if (opts.pageSize) {
|
|
555
|
+
const n = Number(opts.pageSize);
|
|
556
|
+
if (!Number.isInteger(n) || n <= 0)
|
|
557
|
+
throw new Error("--page-size must be a positive integer");
|
|
558
|
+
body.pageSize = n;
|
|
559
|
+
}
|
|
560
|
+
if (opts.pageToken)
|
|
561
|
+
body.pageToken = String(opts.pageToken).trim();
|
|
562
|
+
const merged = mergeBodies(body, opts.jsonInput);
|
|
563
|
+
const out = await runTool("list-meetings", merged, { postMcp });
|
|
564
|
+
console.log(out);
|
|
565
|
+
});
|
|
566
|
+
program
|
|
567
|
+
.command("list-meeting-filter-options")
|
|
568
|
+
.description(TOOL_DEFINITIONS.find((t) => t.kebab === "list-meeting-filter-options").description)
|
|
569
|
+
.action(async () => {
|
|
570
|
+
const out = await runTool("list-meeting-filter-options", {}, { postMcp });
|
|
498
571
|
console.log(out);
|
|
499
572
|
});
|
|
500
573
|
program
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a date bound to epoch millis: epoch millis, ISO date (`2026-09-01`), or ISO datetime.
|
|
3
|
+
* Date-only values use local time: start of day, or end of day when `endOfDay` is set
|
|
4
|
+
* (so `--to 2026-09-30` includes meetings on the 30th).
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseDateBoundToMillis(raw: string | number, endOfDay?: boolean): number;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
2
|
+
const EPOCH_MILLIS = /^\d{10,}$/;
|
|
3
|
+
/**
|
|
4
|
+
* Parse a date bound to epoch millis: epoch millis, ISO date (`2026-09-01`), or ISO datetime.
|
|
5
|
+
* Date-only values use local time: start of day, or end of day when `endOfDay` is set
|
|
6
|
+
* (so `--to 2026-09-30` includes meetings on the 30th).
|
|
7
|
+
*/
|
|
8
|
+
export function parseDateBoundToMillis(raw, endOfDay = false) {
|
|
9
|
+
if (typeof raw === "number") {
|
|
10
|
+
if (!Number.isFinite(raw))
|
|
11
|
+
throw new Error(`Invalid date: ${raw}`);
|
|
12
|
+
return Math.trunc(raw);
|
|
13
|
+
}
|
|
14
|
+
const value = String(raw).trim();
|
|
15
|
+
if (EPOCH_MILLIS.test(value))
|
|
16
|
+
return Number(value);
|
|
17
|
+
if (DATE_ONLY.test(value)) {
|
|
18
|
+
const [y, m, d] = value.split("-").map(Number);
|
|
19
|
+
const date = endOfDay ? new Date(y, m - 1, d, 23, 59, 59, 999) : new Date(y, m - 1, d, 0, 0, 0, 0);
|
|
20
|
+
if (date.getFullYear() !== y || date.getMonth() !== m - 1 || date.getDate() !== d) {
|
|
21
|
+
throw new Error(`Invalid date: ${value}`);
|
|
22
|
+
}
|
|
23
|
+
return date.getTime();
|
|
24
|
+
}
|
|
25
|
+
const millis = Date.parse(value);
|
|
26
|
+
if (Number.isNaN(millis)) {
|
|
27
|
+
throw new Error(`Invalid date: ${value} (use YYYY-MM-DD, an ISO datetime, or epoch millis)`);
|
|
28
|
+
}
|
|
29
|
+
return millis;
|
|
30
|
+
}
|
package/dist/core/schemas.d.ts
CHANGED
|
@@ -291,7 +291,24 @@ export declare const getManualSessionDetailsInput: z.ZodObject<{
|
|
|
291
291
|
}, z.core.$strip>;
|
|
292
292
|
export declare const getMeetingTranscriptInput: z.ZodObject<{
|
|
293
293
|
meetingId: z.ZodString;
|
|
294
|
+
summaryOnly: z.ZodOptional<z.ZodBoolean>;
|
|
294
295
|
}, z.core.$strip>;
|
|
296
|
+
export declare const getMeetingSetInput: z.ZodObject<{
|
|
297
|
+
meetingSetId: z.ZodString;
|
|
298
|
+
}, z.core.$strip>;
|
|
299
|
+
export declare const listMeetingsInput: z.ZodObject<{
|
|
300
|
+
from: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
301
|
+
to: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
302
|
+
startDateMillis: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
303
|
+
endDateMillis: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
304
|
+
labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
305
|
+
participantKeys: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
306
|
+
participantDomains: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
307
|
+
searchText: z.ZodOptional<z.ZodString>;
|
|
308
|
+
pageSize: z.ZodOptional<z.ZodNumber>;
|
|
309
|
+
pageToken: z.ZodOptional<z.ZodString>;
|
|
310
|
+
}, z.core.$strip>;
|
|
311
|
+
export declare const listMeetingFilterOptionsInput: z.ZodObject<{}, z.core.$strip>;
|
|
295
312
|
export declare const getIssueDetailsInput: z.ZodObject<{
|
|
296
313
|
issueId: z.ZodString;
|
|
297
314
|
}, z.core.$strip>;
|
package/dist/core/schemas.js
CHANGED
|
@@ -170,7 +170,36 @@ export const getManualSessionDetailsInput = z.object({
|
|
|
170
170
|
export const getMeetingTranscriptInput = z.object({
|
|
171
171
|
/** Calendar event id, or URL hash for ad-hoc meetings */
|
|
172
172
|
meetingId: z.string().min(1),
|
|
173
|
-
|
|
173
|
+
/** Return only the post-meeting summary (transcript body omitted). */
|
|
174
|
+
summaryOnly: z.boolean().optional(),
|
|
175
|
+
});
|
|
176
|
+
export const getMeetingSetInput = z.object({
|
|
177
|
+
/** ULID from `/testchimp using meeting-set context <id>` (Meetings page → Start Chat) */
|
|
178
|
+
meetingSetId: z.string().min(1),
|
|
179
|
+
});
|
|
180
|
+
export const listMeetingsInput = z.object({
|
|
181
|
+
/** Inclusive start bound: YYYY-MM-DD (local start of day), ISO datetime, or epoch millis. */
|
|
182
|
+
from: z.union([z.string().min(1), z.number()]).optional(),
|
|
183
|
+
/** Inclusive end bound: YYYY-MM-DD (local end of day), ISO datetime, or epoch millis. */
|
|
184
|
+
to: z.union([z.string().min(1), z.number()]).optional(),
|
|
185
|
+
/** Raw inclusive start (epoch millis); `from` wins when both are set. */
|
|
186
|
+
startDateMillis: z.union([z.string().min(1), z.number()]).optional(),
|
|
187
|
+
/** Raw inclusive end (epoch millis); `to` wins when both are set. */
|
|
188
|
+
endDateMillis: z.union([z.string().min(1), z.number()]).optional(),
|
|
189
|
+
/** OR filter, case-insensitive (exact values from list-meeting-filter-options). */
|
|
190
|
+
labels: z.array(z.string().min(1)).optional(),
|
|
191
|
+
/** OR filter: participant user ids or emails (keys from list-meeting-filter-options). */
|
|
192
|
+
participantKeys: z.array(z.string().min(1)).optional(),
|
|
193
|
+
/** OR filter: participant email domains (e.g. customer.com). */
|
|
194
|
+
participantDomains: z.array(z.string().min(1)).optional(),
|
|
195
|
+
/** Full-text search over title + transcript (web-search syntax: quotes, OR, -exclude). */
|
|
196
|
+
searchText: z.string().optional(),
|
|
197
|
+
/** Default 50, max 200 (max 25 when searchText is set). */
|
|
198
|
+
pageSize: z.number().int().positive().optional(),
|
|
199
|
+
/** nextPageToken from the previous page. */
|
|
200
|
+
pageToken: z.string().min(1).optional(),
|
|
201
|
+
});
|
|
202
|
+
export const listMeetingFilterOptionsInput = z.object({});
|
|
174
203
|
export const getIssueDetailsInput = z.object({
|
|
175
204
|
/** Accepts #B-123, B-123, #B123, B123, or plain 123 */
|
|
176
205
|
issueId: z.string().min(1),
|
package/dist/core/tools.js
CHANGED
|
@@ -2,6 +2,7 @@ import { normalizeScope } from "./normalize.js";
|
|
|
2
2
|
import { runProvisionEphemeralEnvironmentAndWait } from "./ephemeralWait.js";
|
|
3
3
|
import * as S from "./schemas.js";
|
|
4
4
|
import { resolveGitHeadSha } from "./gitSha.js";
|
|
5
|
+
import { parseDateBoundToMillis } from "./dates.js";
|
|
5
6
|
import { buildAgentTraceabilityPayload, resolveToolchainVersions } from "./agentTraceability.js";
|
|
6
7
|
function platformToProtoEnum(platform) {
|
|
7
8
|
switch (platform) {
|
|
@@ -388,16 +389,78 @@ export const TOOL_DEFINITIONS = [
|
|
|
388
389
|
{
|
|
389
390
|
kebab: "get-meeting-transcript",
|
|
390
391
|
description: "Fetch a cloud-synced Meeting Bots transcript by meeting id (calendar event id, or URL hash " +
|
|
391
|
-
"for ad-hoc meetings).
|
|
392
|
+
"for ad-hoc meetings). Returns title, start time, post-meeting summary, and transcript. " +
|
|
393
|
+
"Use summaryOnly to fetch just the summary (much smaller) and pull the full transcript only " +
|
|
394
|
+
"when the summary is not enough. Prefer local ~/.testchimp/data/meetings/<meeting-id>/transcript.md " +
|
|
392
395
|
"when present on Studio / the recording machine.",
|
|
393
396
|
inputSchema: S.getMeetingTranscriptInput,
|
|
394
397
|
execute: async (args, { postMcp }) => {
|
|
395
398
|
const a = args;
|
|
396
399
|
return postMcp("/api/mcp/get_meeting_transcript", {
|
|
397
400
|
meetingId: a.meetingId,
|
|
401
|
+
...(a.summaryOnly ? { summaryOnly: true } : {}),
|
|
398
402
|
});
|
|
399
403
|
},
|
|
400
404
|
},
|
|
405
|
+
{
|
|
406
|
+
kebab: "get-meeting-set",
|
|
407
|
+
description: "Fetch a meeting-set context by id (ULID from `/testchimp using meeting-set context <id>`, " +
|
|
408
|
+
"created on the Meetings page via Start Chat). Returns the filters / search text applied and " +
|
|
409
|
+
"the meetings in scope (id, title, start). Fetch each meeting with get-meeting-transcript " +
|
|
410
|
+
"(summaryOnly first). Sets expire after 7 days.",
|
|
411
|
+
inputSchema: S.getMeetingSetInput,
|
|
412
|
+
execute: async (args, { postMcp }) => {
|
|
413
|
+
const a = args;
|
|
414
|
+
return postMcp("/api/mcp/get_meeting_set", {
|
|
415
|
+
meetingSetId: a.meetingSetId,
|
|
416
|
+
});
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
kebab: "list-meetings",
|
|
421
|
+
description: "List / search cloud-synced Meeting Bots meetings, newest first, like the Meetings page " +
|
|
422
|
+
"filters + search. Team-wide meetings only (visibility: all team members). Filters: from / to " +
|
|
423
|
+
"(YYYY-MM-DD, ISO datetime, or epoch millis; inclusive), labels, participantKeys (user id or " +
|
|
424
|
+
"email), participantDomains, searchText (full-text over title + transcript; hits include a " +
|
|
425
|
+
"searchSnippet with matches wrapped in ⟦ ⟧). Returns meetingId, title, startMillis, labels, " +
|
|
426
|
+
"participants, summaryStatus; page with nextPageToken. Use list-meeting-filter-options for exact " +
|
|
427
|
+
"label / domain / participant values, then get-meeting-transcript (summaryOnly first) per hit.",
|
|
428
|
+
inputSchema: S.listMeetingsInput,
|
|
429
|
+
execute: async (args, { postMcp }) => {
|
|
430
|
+
const a = args;
|
|
431
|
+
const body = {};
|
|
432
|
+
const start = a.from ?? a.startDateMillis;
|
|
433
|
+
const end = a.to ?? a.endDateMillis;
|
|
434
|
+
if (start != null)
|
|
435
|
+
body.startDateMillis = String(parseDateBoundToMillis(start));
|
|
436
|
+
if (end != null)
|
|
437
|
+
body.endDateMillis = String(parseDateBoundToMillis(end, true));
|
|
438
|
+
if (a.labels?.length)
|
|
439
|
+
body.labels = a.labels.map((v) => v.trim());
|
|
440
|
+
if (a.participantKeys?.length)
|
|
441
|
+
body.participantKeys = a.participantKeys.map((v) => v.trim());
|
|
442
|
+
if (a.participantDomains?.length) {
|
|
443
|
+
body.participantDomains = a.participantDomains.map((v) => v.trim().replace(/^@/, ""));
|
|
444
|
+
}
|
|
445
|
+
if (a.searchText?.trim())
|
|
446
|
+
body.searchText = a.searchText.trim();
|
|
447
|
+
if (a.pageSize != null)
|
|
448
|
+
body.pageSize = a.pageSize;
|
|
449
|
+
if (a.pageToken)
|
|
450
|
+
body.pageToken = a.pageToken;
|
|
451
|
+
return postMcp("/api/mcp/list_meetings", body);
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
kebab: "list-meeting-filter-options",
|
|
456
|
+
description: "List the labels, participants (key = user id or email, with display name), and participant " +
|
|
457
|
+
"email domains that appear on team-wide Meeting Bots meetings. Use the exact values as " +
|
|
458
|
+
"list-meetings filters (labels, participantKeys, participantDomains).",
|
|
459
|
+
inputSchema: S.listMeetingFilterOptionsInput,
|
|
460
|
+
execute: async (_args, { postMcp }) => {
|
|
461
|
+
return postMcp("/api/mcp/list_meeting_filter_options", {});
|
|
462
|
+
},
|
|
463
|
+
},
|
|
401
464
|
{
|
|
402
465
|
kebab: "get-issue-details",
|
|
403
466
|
description: "Fetch a TestChimp issue (bug) by ordinal id. Accepts flexible issueId formats: " +
|
package/dist/core/tools.test.js
CHANGED
|
@@ -73,4 +73,93 @@ describe("get-meeting-transcript", () => {
|
|
|
73
73
|
});
|
|
74
74
|
assert.deepEqual(JSON.parse(result), JSON.parse(response));
|
|
75
75
|
});
|
|
76
|
+
it("passes summaryOnly when requested", async () => {
|
|
77
|
+
let body;
|
|
78
|
+
await runTool("get-meeting-transcript", { meetingId: "evt-123", summaryOnly: true }, {
|
|
79
|
+
postMcp: async (_path, b) => {
|
|
80
|
+
body = b;
|
|
81
|
+
return "{}";
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
assert.deepEqual(body, { meetingId: "evt-123", summaryOnly: true });
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe("get-meeting-set", () => {
|
|
88
|
+
it("posts meetingSetId to get_meeting_set", async () => {
|
|
89
|
+
let request;
|
|
90
|
+
await runTool("get-meeting-set", { meetingSetId: "01J9Z3X5V4ABCDEF0123456789" }, {
|
|
91
|
+
postMcp: async (path, body) => {
|
|
92
|
+
request = { path, body };
|
|
93
|
+
return "{}";
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
assert.deepEqual(request, {
|
|
97
|
+
path: "/api/mcp/get_meeting_set",
|
|
98
|
+
body: { meetingSetId: "01J9Z3X5V4ABCDEF0123456789" },
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe("list-meetings", () => {
|
|
103
|
+
async function capture(args) {
|
|
104
|
+
let request;
|
|
105
|
+
await runTool("list-meetings", args, {
|
|
106
|
+
postMcp: async (path, body) => {
|
|
107
|
+
request = { path, body };
|
|
108
|
+
return "{}";
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
return request;
|
|
112
|
+
}
|
|
113
|
+
it("maps filters and converts date-only bounds to inclusive local-day millis", async () => {
|
|
114
|
+
const request = await capture({
|
|
115
|
+
from: "2026-09-01",
|
|
116
|
+
to: "2026-09-30",
|
|
117
|
+
labels: ["Sales "],
|
|
118
|
+
participantKeys: ["buyer@customer.com"],
|
|
119
|
+
participantDomains: ["@customer.com"],
|
|
120
|
+
searchText: " pricing ",
|
|
121
|
+
pageSize: 10,
|
|
122
|
+
pageToken: "tok",
|
|
123
|
+
});
|
|
124
|
+
assert.deepEqual(request, {
|
|
125
|
+
path: "/api/mcp/list_meetings",
|
|
126
|
+
body: {
|
|
127
|
+
startDateMillis: String(new Date(2026, 8, 1, 0, 0, 0, 0).getTime()),
|
|
128
|
+
endDateMillis: String(new Date(2026, 8, 30, 23, 59, 59, 999).getTime()),
|
|
129
|
+
labels: ["Sales"],
|
|
130
|
+
participantKeys: ["buyer@customer.com"],
|
|
131
|
+
participantDomains: ["customer.com"],
|
|
132
|
+
searchText: "pricing",
|
|
133
|
+
pageSize: 10,
|
|
134
|
+
pageToken: "tok",
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
it("accepts ISO datetimes and raw epoch millis, and omits empty filters", async () => {
|
|
139
|
+
const request = await capture({ from: "2026-09-01T10:00:00Z", endDateMillis: "1790000000000" });
|
|
140
|
+
assert.deepEqual(request, {
|
|
141
|
+
path: "/api/mcp/list_meetings",
|
|
142
|
+
body: {
|
|
143
|
+
startDateMillis: String(Date.parse("2026-09-01T10:00:00Z")),
|
|
144
|
+
endDateMillis: "1790000000000",
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
assert.deepEqual(await capture({}), { path: "/api/mcp/list_meetings", body: {} });
|
|
148
|
+
});
|
|
149
|
+
it("rejects unparseable dates", async () => {
|
|
150
|
+
await assert.rejects(() => capture({ from: "last tuesday" }), /Invalid date/);
|
|
151
|
+
await assert.rejects(() => capture({ to: "2026-02-30" }), /Invalid date/);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
describe("list-meeting-filter-options", () => {
|
|
155
|
+
it("posts an empty body to list_meeting_filter_options", async () => {
|
|
156
|
+
let request;
|
|
157
|
+
await runTool("list-meeting-filter-options", {}, {
|
|
158
|
+
postMcp: async (path, body) => {
|
|
159
|
+
request = { path, body };
|
|
160
|
+
return "{}";
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
assert.deepEqual(request, { path: "/api/mcp/list_meeting_filter_options", body: {} });
|
|
164
|
+
});
|
|
76
165
|
});
|
package/package.json
CHANGED