@testchimp/cli 0.1.82 → 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 +49 -0
- package/dist/core/dates.d.ts +6 -0
- package/dist/core/dates.js +30 -0
- package/dist/core/schemas.d.ts +13 -0
- package/dist/core/schemas.js +23 -0
- package/dist/core/tools.js +46 -0
- package/dist/core/tools.test.js +64 -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
|
@@ -521,6 +521,55 @@ export function buildCliProgram() {
|
|
|
521
521
|
const out = await runTool("get-meeting-set", { meetingSetId: String(merged.meetingSetId).trim() }, { postMcp });
|
|
522
522
|
console.log(out);
|
|
523
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 });
|
|
571
|
+
console.log(out);
|
|
572
|
+
});
|
|
524
573
|
program
|
|
525
574
|
.command("get-issue-details")
|
|
526
575
|
.description(TOOL_DEFINITIONS.find((t) => t.kebab === "get-issue-details").description)
|
|
@@ -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
|
@@ -296,6 +296,19 @@ export declare const getMeetingTranscriptInput: z.ZodObject<{
|
|
|
296
296
|
export declare const getMeetingSetInput: z.ZodObject<{
|
|
297
297
|
meetingSetId: z.ZodString;
|
|
298
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>;
|
|
299
312
|
export declare const getIssueDetailsInput: z.ZodObject<{
|
|
300
313
|
issueId: z.ZodString;
|
|
301
314
|
}, z.core.$strip>;
|
package/dist/core/schemas.js
CHANGED
|
@@ -177,6 +177,29 @@ export const getMeetingSetInput = z.object({
|
|
|
177
177
|
/** ULID from `/testchimp using meeting-set context <id>` (Meetings page → Start Chat) */
|
|
178
178
|
meetingSetId: z.string().min(1),
|
|
179
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({});
|
|
180
203
|
export const getIssueDetailsInput = z.object({
|
|
181
204
|
/** Accepts #B-123, B-123, #B123, B123, or plain 123 */
|
|
182
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) {
|
|
@@ -415,6 +416,51 @@ export const TOOL_DEFINITIONS = [
|
|
|
415
416
|
});
|
|
416
417
|
},
|
|
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
|
+
},
|
|
418
464
|
{
|
|
419
465
|
kebab: "get-issue-details",
|
|
420
466
|
description: "Fetch a TestChimp issue (bug) by ordinal id. Accepts flexible issueId formats: " +
|
package/dist/core/tools.test.js
CHANGED
|
@@ -99,3 +99,67 @@ describe("get-meeting-set", () => {
|
|
|
99
99
|
});
|
|
100
100
|
});
|
|
101
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
|
+
});
|
|
165
|
+
});
|
package/package.json
CHANGED