@nimbus-dev/connectors 0.2.1 → 0.2.3
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 +19 -7
- package/connectors/airflow/src/search-filter.ts +2 -15
- package/connectors/airflow/src/server.ts +2 -87
- package/connectors/airflow/src/tools.ts +74 -0
- package/connectors/apple/src/adapters.ts +326 -0
- package/connectors/apple/src/apple-mail-core.ts +4 -8
- package/connectors/apple/src/calendar-tools.ts +28 -8
- package/connectors/apple/src/server.ts +23 -512
- package/connectors/apple/src/tools.ts +18 -44
- package/connectors/argocd/src/server.ts +25 -38
- package/connectors/athena/src/tools.ts +26 -43
- package/connectors/aws/src/server.ts +2 -183
- package/connectors/aws/src/tools.ts +199 -0
- package/connectors/azure/src/server.ts +2 -131
- package/connectors/azure/src/tools.ts +134 -0
- package/connectors/bigeye/src/search-filter.ts +4 -15
- package/connectors/bigeye/src/server.ts +38 -38
- package/connectors/bigquery/src/tools.ts +1 -26
- package/connectors/bitbucket/src/server.ts +2 -244
- package/connectors/bitbucket/src/tools.ts +241 -0
- package/connectors/bitrise/src/server.ts +2 -79
- package/connectors/bitrise/src/tools.ts +76 -0
- package/connectors/canva/src/search-filter.ts +2 -11
- package/connectors/canva/src/server.ts +2 -73
- package/connectors/canva/src/tools.ts +66 -0
- package/connectors/circleci/src/server.ts +2 -176
- package/connectors/circleci/src/tools.ts +193 -0
- package/connectors/cloud-logging/src/tools.ts +19 -35
- package/connectors/cloudwatch/src/tools.ts +20 -38
- package/connectors/codemagic/src/server.ts +2 -70
- package/connectors/codemagic/src/tools.ts +71 -0
- package/connectors/confluence/src/server.ts +2 -301
- package/connectors/confluence/src/tools.ts +320 -0
- package/connectors/dagster/src/server.ts +2 -214
- package/connectors/dagster/src/tools.ts +187 -0
- package/connectors/databricks/src/search-filter.ts +7 -7
- package/connectors/databricks/src/server.ts +2 -77
- package/connectors/databricks/src/tools.ts +66 -0
- package/connectors/datadog/src/server.ts +2 -48
- package/connectors/datadog/src/tools.ts +48 -0
- package/connectors/dataprofile/src/profile.ts +16 -46
- package/connectors/dbt/src/server.ts +2 -92
- package/connectors/dbt/src/tools.ts +80 -0
- package/connectors/dependencytrack/src/server.ts +2 -76
- package/connectors/dependencytrack/src/tools.ts +65 -0
- package/connectors/discord/src/server.ts +2 -85
- package/connectors/discord/src/tools.ts +82 -0
- package/connectors/elasticsearch/src/tools.ts +7 -37
- package/connectors/fastmail/src/jmap-client.ts +202 -0
- package/connectors/fastmail/src/jmap-core.ts +8 -21
- package/connectors/fastmail/src/server.ts +2 -187
- package/connectors/fastmail/src/tools.ts +4 -23
- package/connectors/figma/src/search-filter.ts +2 -11
- package/connectors/figma/src/server.ts +2 -113
- package/connectors/figma/src/tools.ts +99 -0
- package/connectors/firebase/src/server.ts +2 -90
- package/connectors/firebase/src/token.ts +2 -5
- package/connectors/firebase/src/tools.ts +106 -0
- package/connectors/flagsmith/src/server.ts +2 -98
- package/connectors/flagsmith/src/tools.ts +95 -0
- package/connectors/flux/src/server.ts +52 -53
- package/connectors/gcp/src/server.ts +2 -132
- package/connectors/gcp/src/tools.ts +123 -0
- package/connectors/github/src/server.ts +2 -328
- package/connectors/github/src/tools.ts +311 -0
- package/connectors/github-actions/src/server.ts +2 -205
- package/connectors/github-actions/src/tools.ts +209 -0
- package/connectors/gitlab/src/server.ts +2 -384
- package/connectors/gitlab/src/tools.ts +392 -0
- package/connectors/gmail/src/server.ts +2 -270
- package/connectors/gmail/src/tools.ts +209 -0
- package/connectors/google-drive/src/server.ts +2 -524
- package/connectors/google-drive/src/tools.ts +529 -0
- package/connectors/google-meet/src/server.ts +2 -82
- package/connectors/google-meet/src/tools.ts +87 -0
- package/connectors/google-photos/src/server.ts +2 -132
- package/connectors/google-photos/src/tools.ts +138 -0
- package/connectors/grafana/src/server.ts +2 -53
- package/connectors/grafana/src/tools.ts +53 -0
- package/connectors/great-expectations/src/gx-parse.ts +14 -45
- package/connectors/greenhouse/src/server.ts +2 -63
- package/connectors/greenhouse/src/tools.ts +68 -0
- package/connectors/hubspot/src/server.ts +2 -77
- package/connectors/hubspot/src/tools.ts +70 -0
- package/connectors/iac/src/server.ts +2 -137
- package/connectors/iac/src/tools.ts +153 -0
- package/connectors/imap/src/imap-core.ts +29 -82
- package/connectors/imap/src/server.ts +33 -220
- package/connectors/intercom/src/server.ts +2 -61
- package/connectors/intercom/src/tools.ts +58 -0
- package/connectors/jenkins/src/jenkins-api.ts +2 -6
- package/connectors/jenkins/src/server.ts +2 -215
- package/connectors/jenkins/src/tools.ts +240 -0
- package/connectors/jira/src/server.ts +2 -301
- package/connectors/jira/src/tools.ts +325 -0
- package/connectors/kubernetes/src/server.ts +2 -155
- package/connectors/kubernetes/src/tools.ts +160 -0
- package/connectors/launchdarkly/src/server.ts +2 -75
- package/connectors/launchdarkly/src/tools.ts +72 -0
- package/connectors/lever/src/search-filter.ts +4 -12
- package/connectors/lever/src/server.ts +2 -60
- package/connectors/lever/src/tools.ts +61 -0
- package/connectors/linear/src/server.ts +2 -459
- package/connectors/linear/src/tools.ts +482 -0
- package/connectors/localdb/src/sql-scan.ts +13 -41
- package/connectors/localdb/src/tools.ts +1 -1
- package/connectors/looker/src/search-filter.ts +2 -15
- package/connectors/looker/src/server.ts +35 -70
- package/connectors/mendeley/src/server.ts +2 -55
- package/connectors/mendeley/src/tools.ts +54 -0
- package/connectors/mercury/src/server.ts +2 -57
- package/connectors/mercury/src/tools.ts +50 -0
- package/connectors/metabase/src/search-filter.ts +4 -15
- package/connectors/metabase/src/server.ts +2 -79
- package/connectors/metabase/src/tools.ts +64 -0
- package/connectors/miro/src/server.ts +2 -72
- package/connectors/miro/src/tools.ts +65 -0
- package/connectors/mlflow/src/search-filter.ts +3 -7
- package/connectors/mlflow/src/server.ts +20 -40
- package/connectors/monte-carlo/src/server.ts +50 -57
- package/connectors/netlify/src/search-filter.ts +6 -14
- package/connectors/netlify/src/server.ts +2 -60
- package/connectors/netlify/src/tools.ts +53 -0
- package/connectors/newrelic/src/server.ts +2 -40
- package/connectors/newrelic/src/tools.ts +37 -0
- package/connectors/notion/src/server.ts +2 -335
- package/connectors/notion/src/tools.ts +344 -0
- package/connectors/obsidian/src/server.ts +2 -425
- package/connectors/obsidian/src/tools.ts +466 -0
- package/connectors/onedrive/src/server.ts +2 -249
- package/connectors/onedrive/src/tools.ts +253 -0
- package/connectors/outlook/src/server.ts +2 -351
- package/connectors/outlook/src/tools.ts +357 -0
- package/connectors/pagerduty/src/server.ts +2 -132
- package/connectors/pagerduty/src/tools.ts +126 -0
- package/connectors/pipedrive/src/server.ts +2 -58
- package/connectors/pipedrive/src/tools.ts +63 -0
- package/connectors/powerbi/src/search-filter.ts +2 -15
- package/connectors/powerbi/src/server.ts +43 -68
- package/connectors/prefect/src/search-filter.ts +3 -16
- package/connectors/prefect/src/server.ts +2 -90
- package/connectors/prefect/src/tools.ts +83 -0
- package/connectors/protonmail/src/mail-core.ts +16 -75
- package/connectors/protonmail/src/server.ts +34 -214
- package/connectors/raindrop/src/server.ts +2 -103
- package/connectors/raindrop/src/tools.ts +103 -0
- package/connectors/ramp/src/server.ts +2 -105
- package/connectors/ramp/src/tools.ts +98 -0
- package/connectors/readwise/src/server.ts +2 -88
- package/connectors/readwise/src/tools.ts +81 -0
- package/connectors/sagemaker/src/tools.ts +21 -40
- package/connectors/salesforce/src/search-filter.ts +4 -11
- package/connectors/salesforce/src/server.ts +2 -90
- package/connectors/salesforce/src/tools.ts +75 -0
- package/connectors/semgrep/src/search-filter.ts +3 -10
- package/connectors/semgrep/src/server.ts +2 -104
- package/connectors/semgrep/src/tools.ts +92 -0
- package/connectors/sentry/src/server.ts +2 -58
- package/connectors/sentry/src/tools.ts +53 -0
- package/connectors/slack/src/server.ts +2 -352
- package/connectors/slack/src/tools.ts +376 -0
- package/connectors/snowflake/src/search-filter.ts +4 -19
- package/connectors/snowflake/src/server.ts +6 -25
- package/connectors/snyk/src/server.ts +2 -140
- package/connectors/snyk/src/tools.ts +136 -0
- package/connectors/sonarqube/src/server.ts +2 -111
- package/connectors/sonarqube/src/tools.ts +98 -0
- package/connectors/stackoverflow/src/server.ts +2 -77
- package/connectors/stackoverflow/src/tools.ts +66 -0
- package/connectors/storybook/src/storybook-parse.ts +3 -6
- package/connectors/stripe/src/server.ts +2 -61
- package/connectors/stripe/src/tools.ts +54 -0
- package/connectors/superset/src/search-filter.ts +4 -15
- package/connectors/superset/src/server.ts +2 -115
- package/connectors/superset/src/tools.ts +98 -0
- package/connectors/tableau/src/search-filter.ts +2 -15
- package/connectors/tableau/src/server.ts +13 -65
- package/connectors/teams/src/server.ts +2 -249
- package/connectors/teams/src/tools.ts +283 -0
- package/connectors/testflight/src/server.ts +2 -70
- package/connectors/testflight/src/tools.ts +79 -0
- package/connectors/vercel/src/search-filter.ts +2 -10
- package/connectors/vercel/src/server.ts +2 -75
- package/connectors/vercel/src/tools.ts +73 -0
- package/connectors/vertex-ai/src/tools.ts +23 -33
- package/connectors/wiz/src/search-filter.ts +6 -30
- package/connectors/wiz/src/server.ts +2 -188
- package/connectors/wiz/src/tools.ts +168 -0
- package/connectors/zendesk/src/server.ts +2 -73
- package/connectors/zendesk/src/tools.ts +60 -0
- package/connectors/zoom/src/search-filter.ts +1 -7
- package/connectors/zoom/src/server.ts +2 -94
- package/connectors/zoom/src/tools.ts +93 -0
- package/connectors/zotero/src/server.ts +2 -78
- package/connectors/zotero/src/tools.ts +67 -0
- package/package.json +14 -12
- package/shared/access-token-cache.ts +123 -0
- package/shared/atlassian-json-fetch.ts +6 -16
- package/shared/audit-chain.ts +601 -34
- package/shared/cli-json-kit.ts +191 -0
- package/shared/collection-tool-kit.ts +143 -0
- package/shared/connector-mode.ts +3 -2
- package/shared/consent-kit.ts +81 -26
- package/shared/cursor-list-tool.ts +29 -0
- package/shared/env-json-api.ts +209 -0
- package/shared/fetch-json-text.ts +27 -0
- package/shared/github-rest.ts +62 -0
- package/shared/graphql-json.ts +60 -0
- package/shared/imap-mail-core.ts +91 -0
- package/shared/imap-tool-kit.ts +57 -23
- package/shared/imapflow-adapter.ts +417 -0
- package/shared/nimbus-spawn.ts +84 -27
- package/shared/rest-tool-kit.ts +77 -7
- package/shared/run-cli-json.ts +38 -16
- package/shared/safe-cli-arg.ts +139 -17
- package/shared/search-filter.ts +58 -18
- package/shared/walk-files.ts +118 -0
- package/shared/windows-batch-args.ts +273 -0
- package/shared/write-budget.ts +100 -0
- package/standalone/src/launcher.ts +19 -7
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
cliArg,
|
|
4
|
+
createCliJsonRunner,
|
|
5
|
+
gcloudProjectArgs,
|
|
6
|
+
isRecord,
|
|
7
|
+
strField,
|
|
8
|
+
} from "../../../shared/cli-json-kit.ts";
|
|
2
9
|
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
3
10
|
import { nimbusSpawn } from "../../../shared/nimbus-spawn.ts";
|
|
4
11
|
import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
5
12
|
import { isSafeCliArg } from "../../../shared/safe-cli-arg.ts";
|
|
6
13
|
|
|
7
14
|
/**
|
|
8
|
-
*
|
|
9
|
-
* or a model id)
|
|
10
|
-
*
|
|
11
|
-
* can assert the guard rejects a `-`-prefixed value.
|
|
15
|
+
* The argv-injection guard every value passed to the `gcloud ai` CLI (a Vertex AI region like
|
|
16
|
+
* `us-central1`, or a model id) goes through. Re-exported so this connector's own test can assert
|
|
17
|
+
* the guard rejects a `-`-prefixed value.
|
|
12
18
|
*/
|
|
13
|
-
export
|
|
14
|
-
.string()
|
|
15
|
-
.min(1)
|
|
16
|
-
.refine(isSafeCliArg, { message: 'must not start with "-" or contain control characters' });
|
|
19
|
+
export { cliArg };
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* GCP Vertex AI (Tier-3, no-row-data) MCP tool surface. ALL tools index Vertex
|
|
@@ -32,25 +35,10 @@ export const VERTEX_AI_TOOL_NAMES = [
|
|
|
32
35
|
|
|
33
36
|
const DEFAULT_REGION = "us-central1";
|
|
34
37
|
|
|
35
|
-
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
36
|
-
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function strField(r: Record<string, unknown>, key: string): string {
|
|
40
|
-
const v = r[key];
|
|
41
|
-
return typeof v === "string" ? v : "";
|
|
42
|
-
}
|
|
43
|
-
|
|
44
38
|
function asArray(parsed: unknown): unknown[] {
|
|
45
39
|
return Array.isArray(parsed) ? parsed : [];
|
|
46
40
|
}
|
|
47
41
|
|
|
48
|
-
/** The configured GCP project id, if `GOOGLE_CLOUD_PROJECT` is set at spawn. */
|
|
49
|
-
function projectArgs(): string[] {
|
|
50
|
-
const project = process.env["GOOGLE_CLOUD_PROJECT"]?.trim();
|
|
51
|
-
return project !== undefined && project !== "" ? ["--project", project] : [];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
42
|
/**
|
|
55
43
|
* Resolve the Vertex AI region: an explicit tool argument wins, else the
|
|
56
44
|
* `VERTEX_AI_REGION` env injected at spawn, else `us-central1`. The result is a
|
|
@@ -69,21 +57,23 @@ function resolveRegion(explicit?: string): string {
|
|
|
69
57
|
* spawn time. The `<region>` is `isSafeCliArg`-guarded before use (argv flag
|
|
70
58
|
* smuggling). Throws on non-zero exit.
|
|
71
59
|
*/
|
|
60
|
+
const runGcloudAi = createCliJsonRunner(
|
|
61
|
+
{
|
|
62
|
+
// The region has already been guarded by the caller below; everything else
|
|
63
|
+
// is a literal or a schema-validated argument.
|
|
64
|
+
argv: (args) => ["gcloud", "ai", ...args],
|
|
65
|
+
label: "gcloud ai",
|
|
66
|
+
emptyResult: [],
|
|
67
|
+
},
|
|
68
|
+
nimbusSpawn,
|
|
69
|
+
);
|
|
70
|
+
|
|
72
71
|
async function gcloudAi(args: string[], region: string): Promise<unknown> {
|
|
73
72
|
const regionPreview = region.slice(0, 64);
|
|
74
73
|
if (!isSafeCliArg(region)) {
|
|
75
74
|
throw new Error(`Invalid region: ${regionPreview}`);
|
|
76
75
|
}
|
|
77
|
-
|
|
78
|
-
["gcloud", "ai", ...args, "--region", region, ...projectArgs(), "--format", "json"],
|
|
79
|
-
{},
|
|
80
|
-
);
|
|
81
|
-
const out = stdout.trim();
|
|
82
|
-
const err = stderr.trim();
|
|
83
|
-
if (code !== 0) {
|
|
84
|
-
throw new Error(`gcloud ai ${args[0] ?? ""} failed: ${err.slice(0, 400)}`);
|
|
85
|
-
}
|
|
86
|
-
return out === "" ? [] : (JSON.parse(out) as unknown);
|
|
76
|
+
return runGcloudAi([...args, "--region", region, ...gcloudProjectArgs(), "--format", "json"]);
|
|
87
77
|
}
|
|
88
78
|
|
|
89
79
|
function modelMatches(entry: unknown, q: string): boolean {
|
|
@@ -1,49 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
2
|
asObjectish,
|
|
3
3
|
makeQueryFilter,
|
|
4
|
+
nestedString,
|
|
5
|
+
objectNamesText,
|
|
4
6
|
type SearchMatchOptions,
|
|
5
7
|
stringField,
|
|
6
8
|
} from "../../../shared/search-filter.ts";
|
|
7
9
|
|
|
8
10
|
export type WizSearchMatchOptions = SearchMatchOptions;
|
|
9
11
|
|
|
10
|
-
function nestedString(row: Record<string, unknown>, parent: string, key: string): string {
|
|
11
|
-
const p = row[parent];
|
|
12
|
-
if (p === null || typeof p !== "object") {
|
|
13
|
-
return "";
|
|
14
|
-
}
|
|
15
|
-
return stringField(p as Record<string, unknown>, key);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function projectNames(row: Record<string, unknown>): string {
|
|
19
|
-
const projects = row["projects"];
|
|
20
|
-
if (!Array.isArray(projects)) {
|
|
21
|
-
return "";
|
|
22
|
-
}
|
|
23
|
-
const names: string[] = [];
|
|
24
|
-
for (const p of projects) {
|
|
25
|
-
if (p === null || typeof p !== "object") {
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
const name = stringField(p as Record<string, unknown>, "name");
|
|
29
|
-
if (name !== "") {
|
|
30
|
-
names.push(name);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return names.join(" ");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
12
|
function fieldsOf(item: unknown): readonly string[] | null {
|
|
37
13
|
const row = asObjectish(item);
|
|
38
14
|
if (row === undefined) {
|
|
39
15
|
return null;
|
|
40
16
|
}
|
|
41
17
|
return [
|
|
42
|
-
nestedString(row, "sourceRule", "name"),
|
|
18
|
+
nestedString(row, ["sourceRule", "name"]),
|
|
43
19
|
stringField(row, "description"),
|
|
44
|
-
nestedString(row, "entity", "name"),
|
|
45
|
-
nestedString(row, "entity", "type"),
|
|
46
|
-
|
|
20
|
+
nestedString(row, ["entity", "name"]),
|
|
21
|
+
nestedString(row, ["entity", "type"]),
|
|
22
|
+
objectNamesText(row, "projects"),
|
|
47
23
|
];
|
|
48
24
|
}
|
|
49
25
|
|
|
@@ -1,192 +1,6 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
4
|
-
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
5
1
|
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
const DEFAULT_API = "https://api.app.wiz.io/graphql";
|
|
9
|
-
const DEFAULT_AUTH = "https://auth.app.wiz.io/oauth/token";
|
|
10
|
-
const SEVERITIES = ["INFORMATIONAL", "LOW", "MEDIUM", "HIGH", "CRITICAL"] as const;
|
|
11
|
-
const STATUSES = ["OPEN", "IN_PROGRESS", "RESOLVED", "REJECTED"] as const;
|
|
12
|
-
|
|
13
|
-
function apiUrl(): string {
|
|
14
|
-
const v = process.env["WIZ_API_URL"]?.trim();
|
|
15
|
-
return v === undefined || v === "" ? DEFAULT_API : v;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function authUrl(): string {
|
|
19
|
-
const v = process.env["WIZ_AUTH_URL"]?.trim();
|
|
20
|
-
return v === undefined || v === "" ? DEFAULT_AUTH : v;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function fetchAccessToken(): Promise<string> {
|
|
24
|
-
const clientId = process.env["WIZ_CLIENT_ID"]?.trim();
|
|
25
|
-
const clientSecret = process.env["WIZ_CLIENT_SECRET"]?.trim();
|
|
26
|
-
if (clientId === undefined || clientId === "") {
|
|
27
|
-
throw new Error("WIZ_CLIENT_ID is not set");
|
|
28
|
-
}
|
|
29
|
-
if (clientSecret === undefined || clientSecret === "") {
|
|
30
|
-
throw new Error("WIZ_CLIENT_SECRET is not set");
|
|
31
|
-
}
|
|
32
|
-
const body = new URLSearchParams({
|
|
33
|
-
grant_type: "client_credentials",
|
|
34
|
-
client_id: clientId,
|
|
35
|
-
client_secret: clientSecret,
|
|
36
|
-
audience: "wiz-api",
|
|
37
|
-
});
|
|
38
|
-
const res = await fetch(authUrl(), {
|
|
39
|
-
method: "POST",
|
|
40
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
41
|
-
body: body.toString(),
|
|
42
|
-
});
|
|
43
|
-
const text = await res.text();
|
|
44
|
-
if (!res.ok) {
|
|
45
|
-
throw new Error(`Wiz auth ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
46
|
-
}
|
|
47
|
-
const parsed = JSON.parse(text) as { access_token?: string };
|
|
48
|
-
if (typeof parsed.access_token !== "string" || parsed.access_token === "") {
|
|
49
|
-
throw new Error("Wiz auth: missing access_token in response");
|
|
50
|
-
}
|
|
51
|
-
return parsed.access_token;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
let cachedToken: string | undefined;
|
|
55
|
-
async function getToken(): Promise<string> {
|
|
56
|
-
cachedToken ??= await fetchAccessToken();
|
|
57
|
-
return cachedToken;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function wizGraphql<T>(query: string, variables: Record<string, unknown>): Promise<T> {
|
|
61
|
-
const token = await getToken();
|
|
62
|
-
const res = await fetch(apiUrl(), {
|
|
63
|
-
method: "POST",
|
|
64
|
-
headers: {
|
|
65
|
-
Authorization: `Bearer ${token}`,
|
|
66
|
-
"Content-Type": "application/json",
|
|
67
|
-
Accept: "application/json",
|
|
68
|
-
},
|
|
69
|
-
body: JSON.stringify({ query, variables }),
|
|
70
|
-
});
|
|
71
|
-
const text = await res.text();
|
|
72
|
-
if (!res.ok) {
|
|
73
|
-
throw new Error(`Wiz ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
74
|
-
}
|
|
75
|
-
const parsed = JSON.parse(text) as { data?: T; errors?: unknown };
|
|
76
|
-
if (parsed.errors !== undefined) {
|
|
77
|
-
throw new Error(`Wiz GraphQL error: ${JSON.stringify(parsed.errors).slice(0, 400)}`);
|
|
78
|
-
}
|
|
79
|
-
if (parsed.data === undefined) {
|
|
80
|
-
throw new Error("Wiz GraphQL: response missing `data` field");
|
|
81
|
-
}
|
|
82
|
-
return parsed.data;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const ISSUES_QUERY = `
|
|
86
|
-
query Issues($first: Int!, $after: String, $filterBy: IssueFilters) {
|
|
87
|
-
issues(first: $first, after: $after, filterBy: $filterBy) {
|
|
88
|
-
nodes {
|
|
89
|
-
id
|
|
90
|
-
sourceRule { id name }
|
|
91
|
-
severity
|
|
92
|
-
status
|
|
93
|
-
type
|
|
94
|
-
createdAt
|
|
95
|
-
updatedAt
|
|
96
|
-
resolvedAt
|
|
97
|
-
description
|
|
98
|
-
remediation
|
|
99
|
-
entity { id name type }
|
|
100
|
-
projects { id name slug }
|
|
101
|
-
}
|
|
102
|
-
pageInfo { hasNextPage endCursor }
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
`;
|
|
106
|
-
|
|
107
|
-
const SINGLE_ISSUE_QUERY = `
|
|
108
|
-
query Issue($id: ID!) {
|
|
109
|
-
issue(id: $id) {
|
|
110
|
-
id
|
|
111
|
-
sourceRule { id name }
|
|
112
|
-
severity
|
|
113
|
-
status
|
|
114
|
-
type
|
|
115
|
-
createdAt
|
|
116
|
-
updatedAt
|
|
117
|
-
resolvedAt
|
|
118
|
-
description
|
|
119
|
-
remediation
|
|
120
|
-
entity { id name type }
|
|
121
|
-
projects { id name slug }
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
`;
|
|
125
|
-
|
|
126
|
-
interface IssuesPage {
|
|
127
|
-
readonly issues: {
|
|
128
|
-
readonly nodes: readonly unknown[];
|
|
129
|
-
readonly pageInfo: { readonly hasNextPage: boolean; readonly endCursor: string | null };
|
|
130
|
-
};
|
|
131
|
-
}
|
|
2
|
+
import { registerWizTools } from "./tools.ts";
|
|
132
3
|
|
|
133
4
|
await runReadOnlyMcpConnector("nimbus-wiz", (reg) => {
|
|
134
|
-
reg
|
|
135
|
-
"wiz_list",
|
|
136
|
-
"List Wiz issues. Filters map to the `IssueFilters` GraphQL input — severity, status, and entity type are all optional. Defaults to open issues across all severities, capped at 100.",
|
|
137
|
-
z.object({
|
|
138
|
-
severity: z.array(z.enum(SEVERITIES)).min(1).optional(),
|
|
139
|
-
status: z.array(z.enum(STATUSES)).min(1).optional(),
|
|
140
|
-
entityType: z.string().min(1).optional(),
|
|
141
|
-
limit: z.number().int().min(1).max(500).optional(),
|
|
142
|
-
}),
|
|
143
|
-
async (p) => {
|
|
144
|
-
const filterBy: Record<string, unknown> = {};
|
|
145
|
-
filterBy["status"] = p.status ?? ["OPEN"];
|
|
146
|
-
if (p.severity !== undefined && p.severity.length > 0) {
|
|
147
|
-
filterBy["severity"] = p.severity;
|
|
148
|
-
}
|
|
149
|
-
if (p.entityType !== undefined) {
|
|
150
|
-
filterBy["entityType"] = p.entityType;
|
|
151
|
-
}
|
|
152
|
-
const data = await wizGraphql<IssuesPage>(ISSUES_QUERY, {
|
|
153
|
-
first: p.limit ?? 100,
|
|
154
|
-
after: null,
|
|
155
|
-
filterBy,
|
|
156
|
-
});
|
|
157
|
-
return jsonResult(data.issues);
|
|
158
|
-
},
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
reg(
|
|
162
|
-
"wiz_get",
|
|
163
|
-
"Fetch one Wiz issue by its id. Throws when no match is found.",
|
|
164
|
-
z.object({
|
|
165
|
-
issueId: z.string().min(1),
|
|
166
|
-
}),
|
|
167
|
-
async (p) => {
|
|
168
|
-
const data = await wizGraphql<{ issue: unknown }>(SINGLE_ISSUE_QUERY, {
|
|
169
|
-
id: p.issueId,
|
|
170
|
-
});
|
|
171
|
-
if (data.issue === null) {
|
|
172
|
-
throw new Error(`Wiz: issue ${p.issueId} not found`);
|
|
173
|
-
}
|
|
174
|
-
return jsonResult(data.issue);
|
|
175
|
-
},
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
reg(
|
|
179
|
-
"wiz_search",
|
|
180
|
-
"Substring search across open issues. Matches the query against `sourceRule.name`, `description`, `entity.name`, `entity.type`, and project names (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
181
|
-
searchToolInputSchema(200),
|
|
182
|
-
async (p) => {
|
|
183
|
-
const data = await wizGraphql<IssuesPage>(ISSUES_QUERY, {
|
|
184
|
-
first: 500,
|
|
185
|
-
after: null,
|
|
186
|
-
filterBy: { status: ["OPEN"] },
|
|
187
|
-
});
|
|
188
|
-
const matches = filterWizIssues(data.issues.nodes, { query: p.query, limit: p.limit });
|
|
189
|
-
return jsonResult({ matches });
|
|
190
|
-
},
|
|
191
|
-
);
|
|
5
|
+
registerWizTools(reg);
|
|
192
6
|
});
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createAccessTokenCache } from "../../../shared/access-token-cache.ts";
|
|
3
|
+
import { optionalEnv, requiredEnv } from "../../../shared/env-json-api.ts";
|
|
4
|
+
import { postGraphql } from "../../../shared/graphql-json.ts";
|
|
5
|
+
import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
6
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
7
|
+
import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
8
|
+
import { filterWizIssues } from "./search-filter.ts";
|
|
9
|
+
|
|
10
|
+
const DEFAULT_API = "https://api.app.wiz.io/graphql";
|
|
11
|
+
const DEFAULT_AUTH = "https://auth.app.wiz.io/oauth/token";
|
|
12
|
+
const SEVERITIES = ["INFORMATIONAL", "LOW", "MEDIUM", "HIGH", "CRITICAL"] as const;
|
|
13
|
+
const STATUSES = ["OPEN", "IN_PROGRESS", "RESOLVED", "REJECTED"] as const;
|
|
14
|
+
|
|
15
|
+
function apiUrl(): string {
|
|
16
|
+
return optionalEnv("WIZ_API_URL", DEFAULT_API);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function authUrl(): string {
|
|
20
|
+
return optionalEnv("WIZ_AUTH_URL", DEFAULT_AUTH);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** OAuth2 client-credentials against the Wiz auth endpoint. */
|
|
24
|
+
const getToken = createAccessTokenCache({
|
|
25
|
+
label: "Wiz auth",
|
|
26
|
+
exchange: async () => {
|
|
27
|
+
const body = new URLSearchParams({
|
|
28
|
+
grant_type: "client_credentials",
|
|
29
|
+
client_id: requiredEnv("WIZ_CLIENT_ID"),
|
|
30
|
+
client_secret: requiredEnv("WIZ_CLIENT_SECRET"),
|
|
31
|
+
audience: "wiz-api",
|
|
32
|
+
});
|
|
33
|
+
const res = await fetch(authUrl(), {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
36
|
+
body: body.toString(),
|
|
37
|
+
});
|
|
38
|
+
return { ok: res.ok, status: res.status, text: await res.text() };
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/** Forget the exchanged token. Tests only: the cache otherwise outlives the test that filled it. */
|
|
43
|
+
export function __resetWizTokenForTests(): void {
|
|
44
|
+
getToken.clear();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function wizGraphql<T>(query: string, variables: Record<string, unknown>): Promise<T> {
|
|
48
|
+
const token = await getToken();
|
|
49
|
+
return postGraphql<T>({
|
|
50
|
+
url: apiUrl(),
|
|
51
|
+
label: "Wiz",
|
|
52
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
53
|
+
query,
|
|
54
|
+
variables,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const ISSUES_QUERY = `
|
|
59
|
+
query Issues($first: Int!, $after: String, $filterBy: IssueFilters) {
|
|
60
|
+
issues(first: $first, after: $after, filterBy: $filterBy) {
|
|
61
|
+
nodes {
|
|
62
|
+
id
|
|
63
|
+
sourceRule { id name }
|
|
64
|
+
severity
|
|
65
|
+
status
|
|
66
|
+
type
|
|
67
|
+
createdAt
|
|
68
|
+
updatedAt
|
|
69
|
+
resolvedAt
|
|
70
|
+
description
|
|
71
|
+
remediation
|
|
72
|
+
entity { id name type }
|
|
73
|
+
projects { id name slug }
|
|
74
|
+
}
|
|
75
|
+
pageInfo { hasNextPage endCursor }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
const SINGLE_ISSUE_QUERY = `
|
|
81
|
+
query Issue($id: ID!) {
|
|
82
|
+
issue(id: $id) {
|
|
83
|
+
id
|
|
84
|
+
sourceRule { id name }
|
|
85
|
+
severity
|
|
86
|
+
status
|
|
87
|
+
type
|
|
88
|
+
createdAt
|
|
89
|
+
updatedAt
|
|
90
|
+
resolvedAt
|
|
91
|
+
description
|
|
92
|
+
remediation
|
|
93
|
+
entity { id name type }
|
|
94
|
+
projects { id name slug }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
interface IssuesPage {
|
|
100
|
+
readonly issues: {
|
|
101
|
+
readonly nodes: readonly unknown[];
|
|
102
|
+
readonly pageInfo: { readonly hasNextPage: boolean; readonly endCursor: string | null };
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
107
|
+
export const WIZ_TOOL_NAMES = ["wiz_get", "wiz_list", "wiz_search"] as const;
|
|
108
|
+
|
|
109
|
+
export function registerWizTools(reg: ZodToolRegistrar): void {
|
|
110
|
+
reg(
|
|
111
|
+
"wiz_list",
|
|
112
|
+
"List Wiz issues. Filters map to the `IssueFilters` GraphQL input — severity, status, and entity type are all optional. Defaults to open issues across all severities, capped at 100.",
|
|
113
|
+
z.object({
|
|
114
|
+
severity: z.array(z.enum(SEVERITIES)).min(1).optional(),
|
|
115
|
+
status: z.array(z.enum(STATUSES)).min(1).optional(),
|
|
116
|
+
entityType: z.string().min(1).optional(),
|
|
117
|
+
limit: z.number().int().min(1).max(500).optional(),
|
|
118
|
+
}),
|
|
119
|
+
async (p) => {
|
|
120
|
+
const filterBy: Record<string, unknown> = {};
|
|
121
|
+
filterBy["status"] = p.status ?? ["OPEN"];
|
|
122
|
+
if (p.severity !== undefined && p.severity.length > 0) {
|
|
123
|
+
filterBy["severity"] = p.severity;
|
|
124
|
+
}
|
|
125
|
+
if (p.entityType !== undefined) {
|
|
126
|
+
filterBy["entityType"] = p.entityType;
|
|
127
|
+
}
|
|
128
|
+
const data = await wizGraphql<IssuesPage>(ISSUES_QUERY, {
|
|
129
|
+
first: p.limit ?? 100,
|
|
130
|
+
after: null,
|
|
131
|
+
filterBy,
|
|
132
|
+
});
|
|
133
|
+
return jsonResult(data.issues);
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
reg(
|
|
138
|
+
"wiz_get",
|
|
139
|
+
"Fetch one Wiz issue by its id. Throws when no match is found.",
|
|
140
|
+
z.object({
|
|
141
|
+
issueId: z.string().min(1),
|
|
142
|
+
}),
|
|
143
|
+
async (p) => {
|
|
144
|
+
const data = await wizGraphql<{ issue: unknown }>(SINGLE_ISSUE_QUERY, {
|
|
145
|
+
id: p.issueId,
|
|
146
|
+
});
|
|
147
|
+
if (data.issue === null) {
|
|
148
|
+
throw new Error(`Wiz: issue ${p.issueId} not found`);
|
|
149
|
+
}
|
|
150
|
+
return jsonResult(data.issue);
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
reg(
|
|
155
|
+
"wiz_search",
|
|
156
|
+
"Substring search across open issues. Matches the query against `sourceRule.name`, `description`, `entity.name`, `entity.type`, and project names (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
157
|
+
searchToolInputSchema(200),
|
|
158
|
+
async (p) => {
|
|
159
|
+
const data = await wizGraphql<IssuesPage>(ISSUES_QUERY, {
|
|
160
|
+
first: 500,
|
|
161
|
+
after: null,
|
|
162
|
+
filterBy: { status: ["OPEN"] },
|
|
163
|
+
});
|
|
164
|
+
const matches = filterWizIssues(data.issues.nodes, { query: p.query, limit: p.limit });
|
|
165
|
+
return jsonResult({ matches });
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
}
|
|
@@ -1,77 +1,6 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { matchesResult, searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
3
|
-
import {
|
|
4
|
-
encodeBasicAuthHeader,
|
|
5
|
-
mcpJsonResult as jsonResult,
|
|
6
|
-
} from "../../../shared/mcp-tool-kit.ts";
|
|
7
1
|
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
function trimTrailingSlash(s: string): string {
|
|
11
|
-
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function baseUrl(): string {
|
|
15
|
-
const v = process.env["ZENDESK_URL"]?.trim();
|
|
16
|
-
if (v === undefined || v === "") {
|
|
17
|
-
throw new Error("ZENDESK_URL is not set");
|
|
18
|
-
}
|
|
19
|
-
return trimTrailingSlash(v);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function authHeader(): Record<string, string> {
|
|
23
|
-
const email = process.env["ZENDESK_EMAIL"]?.trim();
|
|
24
|
-
if (email === undefined || email === "") {
|
|
25
|
-
throw new Error("ZENDESK_EMAIL is not set");
|
|
26
|
-
}
|
|
27
|
-
const token = process.env["ZENDESK_API_TOKEN"]?.trim();
|
|
28
|
-
if (token === undefined || token === "") {
|
|
29
|
-
throw new Error("ZENDESK_API_TOKEN is not set");
|
|
30
|
-
}
|
|
31
|
-
return {
|
|
32
|
-
Authorization: encodeBasicAuthHeader(`${email}/token`, token),
|
|
33
|
-
Accept: "application/json",
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function zendeskGet(path: string): Promise<unknown> {
|
|
38
|
-
const res = await fetch(`${baseUrl()}${path}`, { headers: authHeader() });
|
|
39
|
-
const text = await res.text();
|
|
40
|
-
if (!res.ok) {
|
|
41
|
-
throw new Error(`Zendesk ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
42
|
-
}
|
|
43
|
-
return JSON.parse(text) as unknown;
|
|
44
|
-
}
|
|
2
|
+
import { registerZendeskTools } from "./tools.ts";
|
|
45
3
|
|
|
46
4
|
await runReadOnlyMcpConnector("nimbus-zendesk", (reg) => {
|
|
47
|
-
reg
|
|
48
|
-
"zendesk_list",
|
|
49
|
-
"List the user's Zendesk tickets (`GET /api/v2/tickets.json?page[size]=100`). Returns the cursor-pagination envelope `{ tickets: [...], meta: { has_more, after_cursor }, links: { next } }` — `tickets` holds the ticket objects.",
|
|
50
|
-
z.object({}),
|
|
51
|
-
async () => {
|
|
52
|
-
return jsonResult(await zendeskGet(`/api/v2/tickets.json?page[size]=100`));
|
|
53
|
-
},
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
reg(
|
|
57
|
-
"zendesk_get",
|
|
58
|
-
"Fetch one Zendesk ticket by its numeric id (`GET /api/v2/tickets/{id}.json`). Returns the `{ ticket: {...} }` envelope. Throws when no match is found.",
|
|
59
|
-
z.object({
|
|
60
|
-
id: z.string().min(1),
|
|
61
|
-
}),
|
|
62
|
-
async (p) => {
|
|
63
|
-
return jsonResult(await zendeskGet(`/api/v2/tickets/${encodeURIComponent(p.id)}.json`));
|
|
64
|
-
},
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
reg(
|
|
68
|
-
"zendesk_search",
|
|
69
|
-
"Substring search across the user's Zendesk tickets (first page only). Matches the query against the ticket subject, description, status, priority, type, and tags (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
70
|
-
searchToolInputSchema(100),
|
|
71
|
-
async (p) => {
|
|
72
|
-
const root = await zendeskGet(`/api/v2/tickets.json?page[size]=100`);
|
|
73
|
-
const tickets = (root as { tickets?: unknown[] } | null)?.tickets;
|
|
74
|
-
return matchesResult(tickets, filterZendeskTickets, p);
|
|
75
|
-
},
|
|
76
|
-
);
|
|
5
|
+
registerZendeskTools(reg);
|
|
77
6
|
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createJsonGetter, requiredBaseUrl, requiredEnv } from "../../../shared/env-json-api.ts";
|
|
3
|
+
import { matchesResult, searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
4
|
+
import {
|
|
5
|
+
encodeBasicAuthHeader,
|
|
6
|
+
mcpJsonResult as jsonResult,
|
|
7
|
+
} from "../../../shared/mcp-tool-kit.ts";
|
|
8
|
+
import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
9
|
+
import { filterZendeskTickets } from "./search-filter.ts";
|
|
10
|
+
|
|
11
|
+
function authHeader(): Record<string, string> {
|
|
12
|
+
const email = requiredEnv("ZENDESK_EMAIL");
|
|
13
|
+
const token = requiredEnv("ZENDESK_API_TOKEN");
|
|
14
|
+
return {
|
|
15
|
+
Authorization: encodeBasicAuthHeader(`${email}/token`, token),
|
|
16
|
+
Accept: "application/json",
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const zendeskGet = createJsonGetter({
|
|
21
|
+
base: () => requiredBaseUrl("ZENDESK_URL"),
|
|
22
|
+
label: "Zendesk",
|
|
23
|
+
headers: authHeader,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
27
|
+
export const ZENDESK_TOOL_NAMES = ["zendesk_get", "zendesk_list", "zendesk_search"] as const;
|
|
28
|
+
|
|
29
|
+
export function registerZendeskTools(reg: ZodToolRegistrar): void {
|
|
30
|
+
reg(
|
|
31
|
+
"zendesk_list",
|
|
32
|
+
"List the user's Zendesk tickets (`GET /api/v2/tickets.json?page[size]=100`). Returns the cursor-pagination envelope `{ tickets: [...], meta: { has_more, after_cursor }, links: { next } }` — `tickets` holds the ticket objects.",
|
|
33
|
+
z.object({}),
|
|
34
|
+
async () => {
|
|
35
|
+
return jsonResult(await zendeskGet(`/api/v2/tickets.json?page[size]=100`));
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
reg(
|
|
40
|
+
"zendesk_get",
|
|
41
|
+
"Fetch one Zendesk ticket by its numeric id (`GET /api/v2/tickets/{id}.json`). Returns the `{ ticket: {...} }` envelope. Throws when no match is found.",
|
|
42
|
+
z.object({
|
|
43
|
+
id: z.string().min(1),
|
|
44
|
+
}),
|
|
45
|
+
async (p) => {
|
|
46
|
+
return jsonResult(await zendeskGet(`/api/v2/tickets/${encodeURIComponent(p.id)}.json`));
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
reg(
|
|
51
|
+
"zendesk_search",
|
|
52
|
+
"Substring search across the user's Zendesk tickets (first page only). Matches the query against the ticket subject, description, status, priority, type, and tags (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
53
|
+
searchToolInputSchema(100),
|
|
54
|
+
async (p) => {
|
|
55
|
+
const root = await zendeskGet(`/api/v2/tickets.json?page[size]=100`);
|
|
56
|
+
const tickets = (root as { tickets?: unknown[] } | null)?.tickets;
|
|
57
|
+
return matchesResult(tickets, filterZendeskTickets, p);
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
import { stringField } from "../../../shared/search-filter.ts";
|
|
1
|
+
import { asRecord, stringField } from "../../../shared/search-filter.ts";
|
|
2
2
|
|
|
3
3
|
export interface ZoomSearchOptions {
|
|
4
4
|
readonly query: string;
|
|
5
5
|
readonly limit?: number;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
function asRecord(v: unknown): Record<string, unknown> | undefined {
|
|
9
|
-
return v !== null && typeof v === "object" && !Array.isArray(v)
|
|
10
|
-
? (v as Record<string, unknown>)
|
|
11
|
-
: undefined;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
8
|
export function filterZoomMeetings(
|
|
15
9
|
meetings: readonly unknown[],
|
|
16
10
|
options: ZoomSearchOptions,
|