From 282e42845d8dbe565d1ece7361e22fb2bb6faf98 Mon Sep 17 00:00:00 2001 From: Dennis Chen Date: Wed, 21 Oct 2026 19:10:49 +0000 Subject: [PATCH 1/2] Document CLI command structure conventions Describe how REST paths map to ` -` commands, how inputs become flags, and where help text comes from. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2e10265..59dbab6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,15 @@ -# CLI conventions +# Command structure -- Group related commands by user-facing domain, not API resource boundaries (e.g. `people notification-settings`, `vendors assessment-types`). +We map from the REST api path to a command structure for CLI with the following rules: +- A command is ` -`. The group is the path's first segment (`/vendors/...` → `vendors`). +- The verb is a trailing action in the path (`/vendors/{vendorId}/set-status` → `set-status`), or comes from the method when there is no trailing action: GET → `list` or `get`, POST → `create` or `upload` (files), PATCH → `update`, PUT → `set`, DELETE → `delete`. A POST or DELETE that attaches or detaches an existing resource is `add` or `remove` (`groups add-person`), and a GET that returns a file is `download` (`trust-centers download-resource-media`). A multi-word action puts the noun after its first word: `trust-centers send-viewer-invite-reminder`. +- The noun joins the REST api path resources after the group: `vendors list-findings`. It is singular, except the last word is plural for `list` and for bulk operations on a collection (`trust-centers set-videos`). `create` and `upload` make one item, so stay singular. Only segments that are plural nouns change number; others stay as written (`knowledge-base list-answer-library`). Omit the noun when the command acts on the group itself (`vendors get`). +- Every API input is a flag; there are no positional arguments. Path parameters, query parameters, and multipart fields each get a kebab-case flag, required when the spec requires it and repeatable for arrays. The group's own ID is `--id` (`trust-centers` uses `--slug-id `); other path parameters keep their names (`--finding-id`). JSON bodies take `--json ` or `--file `, and file downloads take `--output `. +- Pass values through to the API as given; the API validates them. Only booleans and `--page-size` are parsed, and file paths read. + +# Help text + +- Start from the spec: option descriptions are the parameter's spec description, and command descriptions follow the verb (“List vendors”, “Get a vendor by ID”). Override them only where the default reads poorly. - Make inputs discoverable in `--help`: list enum choices, identify ID resources, and use specific placeholders (``, ``, ``), not ``. - Use the same placeholder for common inputs: `` for IDs, `` for date-times, `` for date-only values, `` for true/false, `` for search terms, `` for inline JSON, `` for local files, and `` for other free text. - Mark repeatable flags `(repeatable)`. Describe timestamps as “ISO 8601 timestamp”; avoid repeated format examples. From 866696d44aa643905d8fcce3b2b36e38d74050b0 Mon Sep 17 00:00:00 2001 From: Dennis Chen Date: Wed, 21 Oct 2026 19:10:49 +0000 Subject: [PATCH 2/2] Restructure commands to follow the documented conventions - Flatten trust-centers, customer-trust, and knowledge-base subgroups into verb-noun commands (`trust-centers list-faqs`) - Name deep paths by joining their resources (`integrations list-resource-kind-resources`) - Name linking operations add/remove and file downloads download (`groups add-person`, `trust-centers download-resource-media`) - Make personnel-notification-settings, vendor-assessment-types, and vendor-risk-attributes top-level groups named after their paths - Add flags for spec inputs the commands didn't expose yet - Register all API groups from src/commands/index.ts Request paths are unchanged. Co-Authored-By: Claude Opus 5.5 --- README.md | 6 +- skills/suggest-mitigating-controls/SKILL.md | 2 +- src/cli.ts | 50 +- src/commands/business-units.ts | 10 +- src/commands/contracts.ts | 29 +- src/commands/controls.ts | 197 +- src/commands/customer-trust-accounts.ts | 113 -- .../customer-trust-deletion-requests.ts | 32 - src/commands/customer-trust-questionnaires.ts | 438 ----- src/commands/customer-trust-tag-categories.ts | 111 -- src/commands/customer-trust.ts | 667 ++++++- src/commands/discovered-vendors.ts | 21 +- src/commands/documents.ts | 162 +- src/commands/event-logs.ts | 30 +- src/commands/frameworks.ts | 24 +- src/commands/groups.ts | 199 +- src/commands/index.ts | 59 + src/commands/integrations.ts | 140 +- src/commands/issues.ts | 150 +- src/commands/knowledge-base-answer-library.ts | 149 -- src/commands/knowledge-base-resources.ts | 271 --- src/commands/knowledge-base.ts | 406 +++- src/commands/monitored-computers.ts | 16 +- src/commands/people.ts | 176 +- .../personnel-notification-settings.ts | 11 +- src/commands/policies.ts | 5 +- src/commands/program-scopes.ts | 36 +- src/commands/risk-scenarios.ts | 171 +- src/commands/tests.ts | 100 +- src/commands/trust-center-access-requests.ts | 117 -- src/commands/trust-center-activity.ts | 63 - src/commands/trust-center-chatbot.ts | 64 - .../trust-center-compliance-frameworks.ts | 118 -- .../trust-center-control-categories.ts | 197 -- src/commands/trust-center-controls.ts | 137 -- src/commands/trust-center-data-collected.ts | 55 - src/commands/trust-center-faq-categories.ts | 99 - src/commands/trust-center-faqs.ts | 114 -- ...trust-center-historical-access-requests.ts | 40 - .../trust-center-resource-categories.ts | 120 -- src/commands/trust-center-resources.ts | 155 -- src/commands/trust-center-subprocessors.ts | 114 -- .../trust-center-subscriber-groups.ts | 124 -- src/commands/trust-center-subscribers.ts | 133 -- src/commands/trust-center-updates.ts | 168 -- src/commands/trust-center-videos.ts | 37 - src/commands/trust-center-viewers.ts | 149 -- src/commands/trust-centers.ts | 1702 ++++++++++++++++- src/commands/users.ts | 5 +- src/commands/vendor-assessment-types.ts | 35 +- src/commands/vendor-risk-attributes.ts | 17 +- src/commands/vendors.ts | 433 ++--- src/commands/vulnerabilities.ts | 90 +- src/commands/vulnerability-remediations.ts | 54 +- src/commands/vulnerable-assets.ts | 24 +- 55 files changed, 3794 insertions(+), 4351 deletions(-) delete mode 100644 src/commands/customer-trust-accounts.ts delete mode 100644 src/commands/customer-trust-deletion-requests.ts delete mode 100644 src/commands/customer-trust-questionnaires.ts delete mode 100644 src/commands/customer-trust-tag-categories.ts create mode 100644 src/commands/index.ts delete mode 100644 src/commands/knowledge-base-answer-library.ts delete mode 100644 src/commands/knowledge-base-resources.ts delete mode 100644 src/commands/trust-center-access-requests.ts delete mode 100644 src/commands/trust-center-activity.ts delete mode 100644 src/commands/trust-center-chatbot.ts delete mode 100644 src/commands/trust-center-compliance-frameworks.ts delete mode 100644 src/commands/trust-center-control-categories.ts delete mode 100644 src/commands/trust-center-controls.ts delete mode 100644 src/commands/trust-center-data-collected.ts delete mode 100644 src/commands/trust-center-faq-categories.ts delete mode 100644 src/commands/trust-center-faqs.ts delete mode 100644 src/commands/trust-center-historical-access-requests.ts delete mode 100644 src/commands/trust-center-resource-categories.ts delete mode 100644 src/commands/trust-center-resources.ts delete mode 100644 src/commands/trust-center-subprocessors.ts delete mode 100644 src/commands/trust-center-subscriber-groups.ts delete mode 100644 src/commands/trust-center-subscribers.ts delete mode 100644 src/commands/trust-center-updates.ts delete mode 100644 src/commands/trust-center-videos.ts delete mode 100644 src/commands/trust-center-viewers.ts diff --git a/README.md b/README.md index deaeac6..d18d9c1 100644 --- a/README.md +++ b/README.md @@ -93,10 +93,10 @@ vanta tests list --status-filter NEEDS_ATTENTION | People | `vanta people` | | Business units | `vanta business-units` | | Issues | `vanta issues` | -| Personnel notification settings | `vanta people notification-settings` | +| Personnel notification settings | `vanta personnel-notification-settings` | | Program scopes | `vanta program-scopes` | -| Vendor assessment types | `vanta vendors assessment-types` | -| Vendor risk attributes | `vanta vendors risk-attributes` | +| Vendor assessment types | `vanta vendor-assessment-types` | +| Vendor risk attributes | `vanta vendor-risk-attributes` | | Groups | `vanta groups` | | Frameworks | `vanta frameworks` | | Users | `vanta users` | diff --git a/skills/suggest-mitigating-controls/SKILL.md b/skills/suggest-mitigating-controls/SKILL.md index 77922fd..29c9152 100644 --- a/skills/suggest-mitigating-controls/SKILL.md +++ b/skills/suggest-mitigating-controls/SKILL.md @@ -37,7 +37,7 @@ Requires the `vanta` CLI on `$PATH` and a completed `vanta login`. See the `vant **Field naming trap.** On a risk scenario, `description` is what the UI labels **Title** and `detailedDescription` is what the UI labels **Description**. Read both; the detailed field carries the context this analysis needs. -**Which controls are in scope.** `vanta controls list` returns the controls the organization has adopted — that is the population. `vanta controls list-library` returns Vanta's catalog of controls *not yet adopted*; those are out of scope, because this skill suggests coverage from what the customer already has. The API exposes no retired or draft state on a control, so do not claim to have filtered for one. +**Which controls are in scope.** `vanta controls list` returns the controls the organization has adopted — that is the population. `vanta controls list-controls-library` returns Vanta's catalog of controls *not yet adopted*; those are out of scope, because this skill suggests coverage from what the customer already has. The API exposes no retired or draft state on a control, so do not claim to have filtered for one. **Already-linked controls.** The linked-controls listing identifies each control by its shorthand identifier where it has one, falling back to the Vanta control ID — join on that same field. Controls already linked to the risk are still evaluated; they are marked so the reader can tell a confirmation from a new suggestion. diff --git a/src/cli.ts b/src/cli.ts index db49de8..fe68728 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,31 +1,8 @@ import { Command } from "commander"; import type { GlobalFlags } from "./api-client.js"; -import { registerBusinessUnitsCommand } from "./commands/business-units.js"; -import { registerContractsCommand } from "./commands/contracts.js"; -import { registerControlsCommand } from "./commands/controls.js"; -import { registerCustomerTrustCommand } from "./commands/customer-trust.js"; -import { registerDiscoveredVendorsCommand } from "./commands/discovered-vendors.js"; -import { registerDocumentsCommand } from "./commands/documents.js"; -import { registerEventLogsCommand } from "./commands/event-logs.js"; -import { registerFrameworksCommand } from "./commands/frameworks.js"; -import { registerGroupsCommand } from "./commands/groups.js"; -import { registerIntegrationsCommand } from "./commands/integrations.js"; -import { registerIssuesCommand } from "./commands/issues.js"; -import { registerKnowledgeBaseCommand } from "./commands/knowledge-base.js"; +import { registerApiCommands } from "./commands/index.js"; import { registerLoginCommand } from "./commands/login.js"; -import { registerMonitoredComputersCommand } from "./commands/monitored-computers.js"; -import { registerPeopleCommand } from "./commands/people.js"; -import { registerPoliciesCommand } from "./commands/policies.js"; -import { registerProgramScopesCommand } from "./commands/program-scopes.js"; -import { registerRiskScenariosCommand } from "./commands/risk-scenarios.js"; -import { registerTestsCommand } from "./commands/tests.js"; -import { registerTrustCentersCommand } from "./commands/trust-centers.js"; -import { registerUsersCommand } from "./commands/users.js"; -import { registerVendorsCommand } from "./commands/vendors.js"; import { registerVersionCommand } from "./commands/version.js"; -import { registerVulnerabilitiesCommand } from "./commands/vulnerabilities.js"; -import { registerVulnerabilityRemediationsCommand } from "./commands/vulnerability-remediations.js"; -import { registerVulnerableAssetsCommand } from "./commands/vulnerable-assets.js"; import { finishBackgroundUpdateCheck, startBackgroundUpdateCheck, @@ -96,30 +73,7 @@ function buildProgram(): Command { registerLoginCommand(program, getFlags); registerVersionCommand(program); - registerControlsCommand(program, getFlags); - registerPoliciesCommand(program, getFlags); - registerDocumentsCommand(program, getFlags); - registerTestsCommand(program, getFlags); - registerPeopleCommand(program, getFlags); - registerGroupsCommand(program, getFlags); - registerFrameworksCommand(program, getFlags); - registerUsersCommand(program, getFlags); - registerVulnerabilitiesCommand(program, getFlags); - registerVulnerableAssetsCommand(program, getFlags); - registerVulnerabilityRemediationsCommand(program, getFlags); - registerContractsCommand(program, getFlags); - registerRiskScenariosCommand(program, getFlags); - registerMonitoredComputersCommand(program, getFlags); - registerVendorsCommand(program, getFlags); - registerDiscoveredVendorsCommand(program, getFlags); - registerIntegrationsCommand(program, getFlags); - registerEventLogsCommand(program, getFlags); - registerBusinessUnitsCommand(program, getFlags); - registerIssuesCommand(program, getFlags); - registerProgramScopesCommand(program, getFlags); - registerCustomerTrustCommand(program, getFlags); - registerKnowledgeBaseCommand(program, getFlags); - registerTrustCentersCommand(program, getFlags); + registerApiCommands(program, getFlags); return program; } diff --git a/src/commands/business-units.ts b/src/commands/business-units.ts index 2b3d5b2..9c24b25 100644 --- a/src/commands/business-units.ts +++ b/src/commands/business-units.ts @@ -1,6 +1,5 @@ import type { Command } from "commander"; import { listBusinessUnits, getBusinessUnit } from "../generated/sdk.gen.js"; -import type { ListBusinessUnitsData } from "../generated/types.gen.js"; import { addPaginationOptions, paginationQuery, @@ -18,14 +17,9 @@ export function registerBusinessUnitsCommand( addPaginationOptions( businessUnits.command("list").description("List business units"), - ).action(async (opts: NonNullable) => { + ).action(async (opts) => { await runSdk(getFlags, (api) => - listBusinessUnits({ - client: api.client, - query: { - ...paginationQuery(opts), - }, - }), + listBusinessUnits({ client: api.client, query: paginationQuery(opts) }), ); }); diff --git a/src/commands/contracts.ts b/src/commands/contracts.ts index 7102b58..1056db6 100644 --- a/src/commands/contracts.ts +++ b/src/commands/contracts.ts @@ -1,8 +1,8 @@ import type { Command } from "commander"; import { - deleteContract, - getContract, listContracts, + getContract, + deleteContract, uploadContract, } from "../generated/sdk.gen.js"; import { @@ -52,33 +52,38 @@ export function registerContractsCommand( contracts .command("upload") .description("Upload a contract") - .requiredOption("--file ", "Path to contract file to upload") + .requiredOption( + "--file ", + "Path to file to upload: the contract file to upload. Only PDF files are accepted", + ) .option( "--executed-date ", - "ISO 8601 date when the contract was executed", + "ISO 8601 date indicating when the contract was executed", ) .option( "--account-id ", - "Customer trust account ID to associate with the contract", + "ID of the customer trust account to associate with this contract", + ) + .option( + "--external-id ", + "Stable source-system identifier used to reject duplicate contract uploads; repeats return 409. Use the upstream document or record ID when available, such as the Microsoft Graph DriveItem ID for SharePoint or the Ironclad record ID for Ironclad. If there is no source system, generate and store a durable idempotency key. Maximum 256 characters", ) .action( async (opts: { file: string; executedDate?: string; accountId?: string; + externalId?: string; }) => { - const file = await readBinaryFile(opts.file.trim()); + const file = await readBinaryFile(opts.file); await runSdk(getFlags, (api) => uploadContract({ client: api.client, body: { file, - ...(opts.executedDate?.trim() - ? { executedDate: opts.executedDate.trim() } - : {}), - ...(opts.accountId?.trim() - ? { accountId: opts.accountId.trim() } - : {}), + executedDate: opts.executedDate, + accountId: opts.accountId, + externalId: opts.externalId, }, }), ); diff --git a/src/commands/controls.ts b/src/commands/controls.ts index d071452..7ec683e 100644 --- a/src/commands/controls.ts +++ b/src/commands/controls.ts @@ -1,29 +1,28 @@ import type { Command } from "commander"; import { - listDeactivatedControls, listControls, getControl, createCustomControl, - addControlFromLibrary, updateControlMetadata, deleteControl, - setOwnerForControl, + addControlFromLibrary, listLibraryControls, - listDocumentsForControl, - listTestsForControl, + listDeactivatedControls, addDocumentToControl, - deleteDocumentForcontrol, addTestToControl, + listDocumentsForControl, + deleteDocumentForcontrol, + setOwnerForControl, + listTestsForControl, deleteTestForControl, } from "../generated/sdk.gen.js"; import type { - ListDeactivatedControlsData, - AddControlDocumentMappingInput, - AddControlFromLibraryInput, - AddControlTestMappingInput, - CreateControlInput, - EditControlMetadataInput, - SetOwnerForControlInput, + CreateCustomControlData, + UpdateControlMetadataData, + AddControlFromLibraryData, + AddDocumentToControlData, + AddTestToControlData, + SetOwnerForControlData, } from "../generated/types.gen.js"; import { addJsonFileOptions, @@ -57,15 +56,12 @@ export function registerControlsCommand( pageCursor?: string; frameworkMatchesAny: string[]; }) => { - const frameworkMatchesAny = opts.frameworkMatchesAny.filter(Boolean); await runSdk(getFlags, (api) => listControls({ client: api.client, query: { ...paginationQuery(opts), - ...(frameworkMatchesAny.length > 0 - ? { frameworkMatchesAny } - : {}), + frameworkMatchesAny: opts.frameworkMatchesAny, }, }), ); @@ -88,36 +84,22 @@ export function registerControlsCommand( const body = (await readJSONPayload( opts.json, opts.file, - )) as CreateControlInput; + )) as CreateCustomControlData["body"]; await runSdk(getFlags, (api) => createCustomControl({ client: api.client, body }), ); }); - addJsonFileOptions( - controls - .command("add-from-library") - .description("Add a control from the Vanta library"), - ).action(async (opts: { json?: string; file?: string }) => { - const body = (await readJSONPayload( - opts.json, - opts.file, - )) as AddControlFromLibraryInput; - await runSdk(getFlags, (api) => - addControlFromLibrary({ client: api.client, body }), - ); - }); - addJsonFileOptions( controls .command("update") - .description("Update a control's metadata") + .description("Update a control") .requiredOption("--id ", "Control ID"), ).action(async (opts: { id: string; json?: string; file?: string }) => { const body = (await readJSONPayload( opts.json, opts.file, - )) as EditControlMetadataInput; + )) as UpdateControlMetadataData["body"]; await runSdk(getFlags, (api) => updateControlMetadata({ client: api.client, @@ -139,16 +121,53 @@ export function registerControlsCommand( addJsonFileOptions( controls - .command("set-owner") - .description("Set or clear a control owner") + .command("add-from-library") + .description("Add control from Vanta library"), + ).action(async (opts: { json?: string; file?: string }) => { + const body = (await readJSONPayload( + opts.json, + opts.file, + )) as AddControlFromLibraryData["body"]; + await runSdk(getFlags, (api) => + addControlFromLibrary({ client: api.client, body }), + ); + }); + + addPaginationOptions( + controls + .command("list-controls-library") + .description("List controls from the library"), + ).action(async (opts) => { + await runSdk(getFlags, (api) => + listLibraryControls({ client: api.client, query: paginationQuery(opts) }), + ); + }); + + addPaginationOptions( + controls + .command("list-deactivated-controls") + .description("List deactivated controls"), + ).action(async (opts) => { + await runSdk(getFlags, (api) => + listDeactivatedControls({ + client: api.client, + query: paginationQuery(opts), + }), + ); + }); + + addJsonFileOptions( + controls + .command("add-document-to-control") + .description("Add a document to a control") .requiredOption("--id ", "Control ID"), ).action(async (opts: { id: string; json?: string; file?: string }) => { const body = (await readJSONPayload( opts.json, opts.file, - )) as SetOwnerForControlInput; + )) as AddDocumentToControlData["body"]; await runSdk(getFlags, (api) => - setOwnerForControl({ + addDocumentToControl({ client: api.client, path: { controlId: opts.id }, body, @@ -156,13 +175,21 @@ export function registerControlsCommand( ); }); - addPaginationOptions( - controls.command("list-library").description("List controls from the library"), - ).action(async (opts) => { + addJsonFileOptions( + controls + .command("add-test-to-control") + .description("Add a test to a control") + .requiredOption("--id ", "Control ID"), + ).action(async (opts: { id: string; json?: string; file?: string }) => { + const body = (await readJSONPayload( + opts.json, + opts.file, + )) as AddTestToControlData["body"]; await runSdk(getFlags, (api) => - listLibraryControls({ + addTestToControl({ client: api.client, - query: paginationQuery(opts), + path: { controlId: opts.id }, + body, }), ); }); @@ -173,7 +200,11 @@ export function registerControlsCommand( .description("List documents for a control") .requiredOption("--id ", "Control ID"), ).action( - async (opts: { id: string; pageSize?: number; pageCursor?: string }) => { + async (opts: { + id: string; + pageSize?: number; + pageCursor?: string; + }) => { await runSdk(getFlags, (api) => listDocumentsForControl({ client: api.client, @@ -184,42 +215,6 @@ export function registerControlsCommand( }, ); - addPaginationOptions( - controls - .command("list-tests") - .description("List tests for a control") - .requiredOption("--id ", "Control ID"), - ).action( - async (opts: { id: string; pageSize?: number; pageCursor?: string }) => { - await runSdk(getFlags, (api) => - listTestsForControl({ - client: api.client, - path: { controlId: opts.id }, - query: paginationQuery(opts), - }), - ); - }, - ); - - addJsonFileOptions( - controls - .command("add-document") - .description("Add a document to a control") - .requiredOption("--id ", "Control ID"), - ).action(async (opts: { id: string; json?: string; file?: string }) => { - const body = (await readJSONPayload( - opts.json, - opts.file, - )) as AddControlDocumentMappingInput; - await runSdk(getFlags, (api) => - addDocumentToControl({ - client: api.client, - path: { controlId: opts.id }, - body, - }), - ); - }); - controls .command("remove-document") .description("Remove a document from a control") @@ -236,16 +231,16 @@ export function registerControlsCommand( addJsonFileOptions( controls - .command("add-test") - .description("Add a test to a control") + .command("set-owner") + .description("Set or clear a control owner") .requiredOption("--id ", "Control ID"), ).action(async (opts: { id: string; json?: string; file?: string }) => { const body = (await readJSONPayload( opts.json, opts.file, - )) as AddControlTestMappingInput; + )) as SetOwnerForControlData["body"]; await runSdk(getFlags, (api) => - addTestToControl({ + setOwnerForControl({ client: api.client, path: { controlId: opts.id }, body, @@ -253,6 +248,27 @@ export function registerControlsCommand( ); }); + addPaginationOptions( + controls + .command("list-tests") + .description("List tests for a control") + .requiredOption("--id ", "Control ID"), + ).action( + async (opts: { + id: string; + pageSize?: number; + pageCursor?: string; + }) => { + await runSdk(getFlags, (api) => + listTestsForControl({ + client: api.client, + path: { controlId: opts.id }, + query: paginationQuery(opts), + }), + ); + }, + ); + controls .command("remove-test") .description("Remove a test from a control") @@ -266,17 +282,4 @@ export function registerControlsCommand( }), ); }); - - addPaginationOptions( - controls.command("list-deactivated").description("List deactivated controls"), - ).action(async (opts: NonNullable) => { - await runSdk(getFlags, (api) => - listDeactivatedControls({ - client: api.client, - query: { - ...paginationQuery(opts), - }, - }), - ); - }); } diff --git a/src/commands/customer-trust-accounts.ts b/src/commands/customer-trust-accounts.ts deleted file mode 100644 index 5d28222..0000000 --- a/src/commands/customer-trust-accounts.ts +++ /dev/null @@ -1,113 +0,0 @@ -import type { Command } from "commander"; -import { - listCustomerTrustAccounts, - createCustomerTrustAccount, - getCustomerTrustAccount, - deleteCustomerTrustAccount, - updateCustomerTrustAccount, -} from "../generated/sdk.gen.js"; -import type { - ListCustomerTrustAccountsData, - CreateCustomerTrustAccountData, - UpdateCustomerTrustAccountData, -} from "../generated/types.gen.js"; -import { - addJsonFileOptions, - addPaginationOptions, - paginationQuery, - parseOptionalBoolString, - readJSONPayload, - runSdk, - type GetFlags, -} from "./helpers.js"; - -export function registerCustomerTrustAccountsCommand( - customerTrust: Command, - getFlags: GetFlags, -): void { - const accounts = customerTrust - .command("accounts") - .description("Manage Customer Trust accounts"); - - addPaginationOptions( - accounts - .command("list") - .description("List Customer Trust accounts") - .option("--search-string ", "Search accounts by text") - .option( - "--is-auto-approval-enabled ", - "Filter by whether access requests are auto-approved (true/false)", - (value) => parseOptionalBoolString(value, "is-auto-approval-enabled"), - ) - .option("--custom-fields-filter ", "Filter by custom field values"), - ).action(async (opts: NonNullable) => { - await runSdk(getFlags, (api) => - listCustomerTrustAccounts({ - client: api.client, - query: { - ...paginationQuery(opts), - searchString: opts.searchString, - isAutoApprovalEnabled: opts.isAutoApprovalEnabled, - customFieldsFilter: opts.customFieldsFilter, - }, - }), - ); - }); - - accounts - .command("get") - .description("Get a Customer Trust account by ID") - .requiredOption("--id ", "Customer Trust account ID") - .action(async (opts: { id: string }) => { - await runSdk(getFlags, (api) => - getCustomerTrustAccount({ - client: api.client, - path: { accountId: opts.id }, - }), - ); - }); - - addJsonFileOptions( - accounts.command("create").description("Create a Customer Trust account"), - ).action(async (opts: { json?: string; file?: string }) => { - const body = (await readJSONPayload( - opts.json, - opts.file, - )) as CreateCustomerTrustAccountData["body"]; - await runSdk(getFlags, (api) => - createCustomerTrustAccount({ client: api.client, body }), - ); - }); - - addJsonFileOptions( - accounts - .command("update") - .description("Update a Customer Trust account") - .requiredOption("--id ", "Customer Trust account ID"), - ).action(async (opts: { id: string; json?: string; file?: string }) => { - const body = (await readJSONPayload( - opts.json, - opts.file, - )) as UpdateCustomerTrustAccountData["body"]; - await runSdk(getFlags, (api) => - updateCustomerTrustAccount({ - client: api.client, - path: { accountId: opts.id }, - body, - }), - ); - }); - - accounts - .command("delete") - .description("Delete a Customer Trust account") - .requiredOption("--id ", "Customer Trust account ID") - .action(async (opts: { id: string }) => { - await runSdk(getFlags, (api) => - deleteCustomerTrustAccount({ - client: api.client, - path: { accountId: opts.id }, - }), - ); - }); -} diff --git a/src/commands/customer-trust-deletion-requests.ts b/src/commands/customer-trust-deletion-requests.ts deleted file mode 100644 index fa12400..0000000 --- a/src/commands/customer-trust-deletion-requests.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Command } from "commander"; -import { createDeletionRequest } from "../generated/sdk.gen.js"; -import type { CreateDeletionRequestData } from "../generated/types.gen.js"; -import { - addJsonFileOptions, - readJSONPayload, - runSdk, - type GetFlags, -} from "./helpers.js"; - -export function registerCustomerTrustDeletionRequestsCommand( - customerTrust: Command, - getFlags: GetFlags, -): void { - const deletionRequests = customerTrust - .command("deletion-requests") - .description("Manage Customer Trust data deletion requests"); - - addJsonFileOptions( - deletionRequests - .command("create") - .description("Create a data deletion request"), - ).action(async (opts: { json?: string; file?: string }) => { - const body = (await readJSONPayload( - opts.json, - opts.file, - )) as CreateDeletionRequestData["body"]; - await runSdk(getFlags, (api) => - createDeletionRequest({ client: api.client, body }), - ); - }); -} diff --git a/src/commands/customer-trust-questionnaires.ts b/src/commands/customer-trust-questionnaires.ts deleted file mode 100644 index 1c76d46..0000000 --- a/src/commands/customer-trust-questionnaires.ts +++ /dev/null @@ -1,438 +0,0 @@ -import type { Command } from "commander"; -import { - listQuestionnaires, - listAssignableUsers, - createQuestionnaireExport, - getQuestionnaireExport, - createFileQuestionnaire, - createWebsiteQuestionnaire, - getQuestionnaire, - deleteQuestionnaire, - updateQuestionnaire, - approveQuestionnaire, - completeQuestionnaire, - listQuestionnaireResponses, - getQuestionnaireResponse, - updateQuestionnaireResponseContent, - updateQuestionnaireResponseOwner, -} from "../generated/sdk.gen.js"; -import type { - ListQuestionnairesData, - ListAssignableUsersData, - CreateQuestionnaireExportData, - CreateWebsiteQuestionnaireData, - UpdateQuestionnaireData, - ApproveQuestionnaireData, - CompleteQuestionnaireData, - UpdateQuestionnaireResponseContentData, - UpdateQuestionnaireResponseOwnerData, -} from "../generated/types.gen.js"; -import { - addJsonFileOptions, - addPaginationOptions, - collectString, - paginationQuery, - readBinaryFile, - readJSONPayload, - runSdk, - type GetFlags, -} from "./helpers.js"; - -export function registerCustomerTrustQuestionnairesCommand( - customerTrust: Command, - getFlags: GetFlags, -): void { - const questionnaires = customerTrust - .command("questionnaires") - .description("Manage Customer Trust questionnaires"); - - addPaginationOptions( - questionnaires - .command("list") - .description("List questionnaires") - .option( - "--q ", - "Filter questionnaires by display name (case-insensitive, partial match).", - ) - .option( - "--status-matches-any ", - "Filter questionnaires matching any of the provided statuses: APPROVED, IN_PROGRESS, IN_REVIEW, READY_FOR_REVIEW, WAITING_ON_ANSWERS, ON_HOLD, NO_LONGER_NEEDED, COMPLETE, ERROR, EXTRACTING_QUESTIONS, QUEUED_FOR_SECTION_EXTRACTION, EXTRACTING_SECTIONS, MAPPING_SECTIONS, QUEUED_FOR_ANSWERING, GENERATING_ANSWERS, QUEUED_FOR_EXTRACTION, PROCESSING, QUEUED_FOR_PROCESSING, WAITING_ON_COLUMN_SELECTION, WAITING_ON_COLUMN_APPROVAL, QUEUED_FOR_COLUMN_DETECTION, DETECTING_COLUMNS (repeatable)", - collectString, - [] as string[], - ) - .option( - "--type-matches-any ", - "Filter questionnaires matching any of the provided types: SPREADSHEET, WEBSITE, DOCUMENT (repeatable)", - collectString, - [] as string[], - ) - .option( - "--created-after ", - "Filter to questionnaires created after this date, as an ISO 8601 timestamp.", - ) - .option( - "--created-before ", - "Filter to questionnaires created before this date, as an ISO 8601 timestamp.", - ) - .option( - "--owner-id-matches-any ", - "Filter to questionnaires owned by any of the provided user IDs. (repeatable)", - collectString, - [] as string[], - ) - .option( - "--approver-id-matches-any ", - "Filter to questionnaires with an approver matching any of the provided user IDs. (repeatable)", - collectString, - [] as string[], - ), - ).action(async (opts: NonNullable) => { - await runSdk(getFlags, (api) => - listQuestionnaires({ - client: api.client, - query: { - ...paginationQuery(opts), - q: opts.q, - statusMatchesAny: opts.statusMatchesAny, - typeMatchesAny: opts.typeMatchesAny, - createdAfter: opts.createdAfter, - createdBefore: opts.createdBefore, - ownerIdMatchesAny: opts.ownerIdMatchesAny, - approverIdMatchesAny: opts.approverIdMatchesAny, - }, - }), - ); - }); - - questionnaires - .command("get") - .description("Get a questionnaire by ID") - .requiredOption("--id ", "Questionnaire ID") - .action(async (opts: { id: string }) => { - await runSdk(getFlags, (api) => - getQuestionnaire({ - client: api.client, - path: { questionnaireId: opts.id }, - }), - ); - }); - - questionnaires - .command("create-file") - .description("Create a questionnaire from a file") - .requiredOption("--file ", "Path to file to upload") - .requiredOption( - "--display-name ", - "Display name for the questionnaire.", - ) - .option( - "--owner-assignment ", - "Owner to assign, as a JSON string: {\"type\": \"User\" | \"Team\", \"id\": \"\"}.", - ) - .option( - "--approver-assignment ", - "Approver to assign, as a JSON string: {\"type\": \"User\" | \"Team\", \"id\": \"\"}.", - ) - .option("--description ", "Description of the questionnaire.") - .option( - "--company-url ", - "URL of the company associated with this questionnaire.", - ) - .option( - "--due-date ", - "Due date for questionnaire completion, as an ISO 8601 timestamp.", - ) - .option( - "--customer-trust-account-id ", - "ID of the customer trust account to associate with this questionnaire.", - ) - .option( - "--include-untagged-entities-for-category-ids ", - "Comma-separated category IDs for which to include untagged entities.", - ) - .option( - "--metadata ", - "Custom key-value pairs, as a JSON string array: [{\"key\": \"\", \"value\": \"\"}]. Keys and values may contain alphanumeric characters, hyphens, underscores, and periods.", - ) - .option( - "--tag-and-category-ids ", - "Tags to assign, as a JSON string array: [{\"categoryId\": \"\", \"tagId\": \"\"}]. Replaces all existing tags.", - ) - .action( - async (opts: { - file: string; - displayName: string; - ownerAssignment?: string; - approverAssignment?: string; - description?: string; - companyUrl?: string; - dueDate?: string; - customerTrustAccountId?: string; - includeUntaggedEntitiesForCategoryIds?: string; - metadata?: string; - tagAndCategoryIds?: string; - }) => { - const file = await readBinaryFile(opts.file); - await runSdk(getFlags, (api) => - createFileQuestionnaire({ - client: api.client, - body: { - file, - displayName: opts.displayName, - ownerAssignment: opts.ownerAssignment, - approverAssignment: opts.approverAssignment, - description: opts.description, - companyUrl: opts.companyUrl, - dueDate: opts.dueDate, - customerTrustAccountId: opts.customerTrustAccountId, - includeUntaggedEntitiesForCategoryIds: - opts.includeUntaggedEntitiesForCategoryIds, - metadata: opts.metadata, - tagAndCategoryIds: opts.tagAndCategoryIds, - }, - }), - ); - }, - ); - - addJsonFileOptions( - questionnaires - .command("create-website") - .description("Create a questionnaire from a website portal"), - ).action(async (opts: { json?: string; file?: string }) => { - const body = (await readJSONPayload( - opts.json, - opts.file, - )) as CreateWebsiteQuestionnaireData["body"]; - await runSdk(getFlags, (api) => - createWebsiteQuestionnaire({ client: api.client, body }), - ); - }); - - addJsonFileOptions( - questionnaires - .command("update") - .description("Update a questionnaire") - .requiredOption("--id ", "Questionnaire ID"), - ).action(async (opts: { id: string; json?: string; file?: string }) => { - const body = (await readJSONPayload( - opts.json, - opts.file, - )) as UpdateQuestionnaireData["body"]; - await runSdk(getFlags, (api) => - updateQuestionnaire({ - client: api.client, - path: { questionnaireId: opts.id }, - body, - }), - ); - }); - - questionnaires - .command("delete") - .description("Delete a questionnaire") - .requiredOption("--id ", "Questionnaire ID") - .action(async (opts: { id: string }) => { - await runSdk(getFlags, (api) => - deleteQuestionnaire({ - client: api.client, - path: { questionnaireId: opts.id }, - }), - ); - }); - - addJsonFileOptions( - questionnaires - .command("approve") - .description("Approve a questionnaire") - .requiredOption("--id ", "Questionnaire ID"), - ).action(async (opts: { id: string; json?: string; file?: string }) => { - const body = (await readJSONPayload( - opts.json, - opts.file, - )) as ApproveQuestionnaireData["body"]; - await runSdk(getFlags, (api) => - approveQuestionnaire({ - client: api.client, - path: { questionnaireId: opts.id }, - body, - }), - ); - }); - - addJsonFileOptions( - questionnaires - .command("complete") - .description("Complete a questionnaire") - .requiredOption("--id ", "Questionnaire ID"), - ).action(async (opts: { id: string; json?: string; file?: string }) => { - const body = (await readJSONPayload( - opts.json, - opts.file, - )) as CompleteQuestionnaireData["body"]; - await runSdk(getFlags, (api) => - completeQuestionnaire({ - client: api.client, - path: { questionnaireId: opts.id }, - body, - }), - ); - }); - - const assignableUsers = questionnaires - .command("assignable-users") - .description("Manage users assignable to questionnaires"); - - assignableUsers - .command("list") - .description("List users who can be assigned to questionnaires") - .option("--role ", "Filter by role: owner, approver") - .option("--q ", "Filter users by name or email") - .action(async (opts: NonNullable) => { - await runSdk(getFlags, (api) => - listAssignableUsers({ - client: api.client, - query: { - role: opts.role, - q: opts.q, - }, - }), - ); - }); - - const exports = questionnaires - .command("exports") - .description("Manage questionnaire exports"); - - addJsonFileOptions( - exports.command("create").description("Create a questionnaire export"), - ).action(async (opts: { json?: string; file?: string }) => { - const body = (await readJSONPayload( - opts.json, - opts.file, - )) as CreateQuestionnaireExportData["body"]; - await runSdk(getFlags, (api) => - createQuestionnaireExport({ client: api.client, body }), - ); - }); - - exports - .command("get") - .description("Get a questionnaire export's status by ID") - .requiredOption( - "--id ", - "Export job ID returned by `customer-trust questionnaires exports create`", - ) - .action(async (opts: { id: string }) => { - await runSdk(getFlags, (api) => - getQuestionnaireExport({ client: api.client, path: { id: opts.id } }), - ); - }); - - const responses = questionnaires - .command("responses") - .description("Manage questionnaire responses"); - - addPaginationOptions( - responses - .command("list") - .description("List responses for a questionnaire") - .requiredOption("--questionnaire-id ", "Questionnaire ID") - .option( - "--q ", - "Filter responses by question text (case-insensitive, partial match).", - ), - ).action( - async (opts: { - questionnaireId: string; - pageSize?: number; - pageCursor?: string; - q?: string; - }) => { - await runSdk(getFlags, (api) => - listQuestionnaireResponses({ - client: api.client, - path: { questionnaireId: opts.questionnaireId }, - query: { - ...paginationQuery(opts), - q: opts.q, - }, - }), - ); - }, - ); - - responses - .command("get") - .description("Get a questionnaire response by ID") - .requiredOption("--questionnaire-id ", "Questionnaire ID") - .requiredOption("--id ", "Questionnaire response ID") - .action(async (opts: { questionnaireId: string; id: string }) => { - await runSdk(getFlags, (api) => - getQuestionnaireResponse({ - client: api.client, - path: { questionnaireId: opts.questionnaireId, responseId: opts.id }, - }), - ); - }); - - addJsonFileOptions( - responses - .command("update-content") - .description("Update a questionnaire response's content") - .requiredOption("--questionnaire-id ", "Questionnaire ID") - .requiredOption("--id ", "Questionnaire response ID"), - ).action( - async (opts: { - questionnaireId: string; - id: string; - json?: string; - file?: string; - }) => { - const body = (await readJSONPayload( - opts.json, - opts.file, - )) as UpdateQuestionnaireResponseContentData["body"]; - await runSdk(getFlags, (api) => - updateQuestionnaireResponseContent({ - client: api.client, - path: { - questionnaireId: opts.questionnaireId, - responseId: opts.id, - }, - body, - }), - ); - }, - ); - - addJsonFileOptions( - responses - .command("update-owner") - .description("Update a questionnaire response's owner") - .requiredOption("--questionnaire-id ", "Questionnaire ID") - .requiredOption("--id ", "Questionnaire response ID"), - ).action( - async (opts: { - questionnaireId: string; - id: string; - json?: string; - file?: string; - }) => { - const body = (await readJSONPayload( - opts.json, - opts.file, - )) as UpdateQuestionnaireResponseOwnerData["body"]; - await runSdk(getFlags, (api) => - updateQuestionnaireResponseOwner({ - client: api.client, - path: { - questionnaireId: opts.questionnaireId, - responseId: opts.id, - }, - body, - }), - ); - }, - ); -} diff --git a/src/commands/customer-trust-tag-categories.ts b/src/commands/customer-trust-tag-categories.ts deleted file mode 100644 index 59dd936..0000000 --- a/src/commands/customer-trust-tag-categories.ts +++ /dev/null @@ -1,111 +0,0 @@ -import type { Command } from "commander"; -import { - listTagCategories, - getTagsForCategory, - addTagCategoryProductContext, - removeTagCategoryProductContext, -} from "../generated/sdk.gen.js"; -import type { - ListTagCategoriesData, - AddTagCategoryProductContextData, - RemoveTagCategoryProductContextData, -} from "../generated/types.gen.js"; -import { - addJsonFileOptions, - collectString, - readJSONPayload, - runSdk, - type GetFlags, -} from "./helpers.js"; - -export function registerCustomerTrustTagCategoriesCommand( - customerTrust: Command, - getFlags: GetFlags, -): void { - const tagCategories = customerTrust - .command("tag-categories") - .description("Manage Customer Trust tag categories"); - - tagCategories - .command("list") - .description("List tag categories") - .option( - "--product-context-ids-matches-any ", - "Filter by product context: EXTERNAL_TRUST_CENTER, DOCUMENT_SHARING, CONTROL_SHARING, QUESTIONNAIRE (repeatable)", - collectString, - [] as string[], - ) - .action(async (opts: NonNullable) => { - await runSdk(getFlags, (api) => - listTagCategories({ - client: api.client, - query: { - productContextIdsMatchesAny: opts.productContextIdsMatchesAny, - }, - }), - ); - }); - - tagCategories - .command("get") - .description("Get the tags in a tag category by ID") - .requiredOption("--id ", "Tag category ID") - .action(async (opts: { id: string }) => { - await runSdk(getFlags, (api) => - getTagsForCategory({ - client: api.client, - path: { tagCategoryId: opts.id }, - }), - ); - }); - - const productContexts = tagCategories - .command("product-contexts") - .description("Manage the product contexts a tag category is enabled for"); - - addJsonFileOptions( - productContexts - .command("add") - .description("Enable a tag category for a product context") - .requiredOption("--tag-category-id ", "Tag category ID"), - ).action( - async (opts: { tagCategoryId: string; json?: string; file?: string }) => { - const body = (await readJSONPayload( - opts.json, - opts.file, - )) as AddTagCategoryProductContextData["body"]; - await runSdk(getFlags, (api) => - addTagCategoryProductContext({ - client: api.client, - path: { tagCategoryId: opts.tagCategoryId }, - body, - }), - ); - }, - ); - - productContexts - .command("delete") - .description("Disable a tag category for a product context") - .requiredOption("--tag-category-id ", "Tag category ID") - .requiredOption( - "--product-context-id ", - "Product context: EXTERNAL_TRUST_CENTER, DOCUMENT_SHARING, CONTROL_SHARING", - ) - .action( - async (opts: { - tagCategoryId: string; - productContextId: RemoveTagCategoryProductContextData["path"]["productContextId"]; - }) => { - await runSdk(getFlags, (api) => - removeTagCategoryProductContext({ - client: api.client, - path: { - tagCategoryId: opts.tagCategoryId, - productContextId: opts.productContextId, - }, - }), - ); - }, - ); -} diff --git a/src/commands/customer-trust.ts b/src/commands/customer-trust.ts index 5da1932..407a8b6 100644 --- a/src/commands/customer-trust.ts +++ b/src/commands/customer-trust.ts @@ -1,9 +1,59 @@ import type { Command } from "commander"; -import { registerCustomerTrustAccountsCommand } from "./customer-trust-accounts.js"; -import { registerCustomerTrustDeletionRequestsCommand } from "./customer-trust-deletion-requests.js"; -import { registerCustomerTrustQuestionnairesCommand } from "./customer-trust-questionnaires.js"; -import { registerCustomerTrustTagCategoriesCommand } from "./customer-trust-tag-categories.js"; -import type { GetFlags } from "./helpers.js"; +import { + listCustomerTrustAccounts, + createCustomerTrustAccount, + getCustomerTrustAccount, + updateCustomerTrustAccount, + deleteCustomerTrustAccount, + createDeletionRequest, + listQuestionnaires, + listAssignableUsers, + createQuestionnaireExport, + getQuestionnaireExport, + createFileQuestionnaire, + createWebsiteQuestionnaire, + getQuestionnaire, + updateQuestionnaire, + deleteQuestionnaire, + approveQuestionnaire, + completeQuestionnaire, + listQuestionnaireResponses, + getQuestionnaireResponse, + updateQuestionnaireResponseContent, + updateQuestionnaireResponseOwner, + listTagCategories, + getTagsForCategory, + addTagCategoryProductContext, + removeTagCategoryProductContext, +} from "../generated/sdk.gen.js"; +import type { + CreateCustomerTrustAccountData, + UpdateCustomerTrustAccountData, + CreateDeletionRequestData, + ListQuestionnairesData, + ListAssignableUsersData, + CreateQuestionnaireExportData, + CreateWebsiteQuestionnaireData, + UpdateQuestionnaireData, + ApproveQuestionnaireData, + CompleteQuestionnaireData, + UpdateQuestionnaireResponseContentData, + UpdateQuestionnaireResponseOwnerData, + ListTagCategoriesData, + AddTagCategoryProductContextData, + RemoveTagCategoryProductContextData, +} from "../generated/types.gen.js"; +import { + addJsonFileOptions, + addPaginationOptions, + collectString, + paginationQuery, + parseOptionalBoolString, + readBinaryFile, + readJSONPayload, + runSdk, + type GetFlags, +} from "./helpers.js"; export function registerCustomerTrustCommand( program: Command, @@ -13,11 +63,610 @@ export function registerCustomerTrustCommand( .command("customer-trust") .description("Manage Customer Trust accounts, questionnaires, and tags"); - registerCustomerTrustAccountsCommand(customerTrust, getFlags); + addPaginationOptions( + customerTrust + .command("list-accounts") + .description("List Customer Trust accounts") + .option("--search-string ", "Search Customer Trust accounts") + .option( + "--is-auto-approval-enabled ", + "Filter by whether access requests are auto-approved (true/false)", + (value) => parseOptionalBoolString(value, "is-auto-approval-enabled"), + ) + .option("--custom-fields-filter ", "Filter by custom field values"), + ).action( + async (opts: { + pageSize?: number; + pageCursor?: string; + searchString?: string; + isAutoApprovalEnabled?: boolean; + customFieldsFilter?: string; + }) => { + await runSdk(getFlags, (api) => + listCustomerTrustAccounts({ + client: api.client, + query: { + ...paginationQuery(opts), + searchString: opts.searchString, + isAutoApprovalEnabled: opts.isAutoApprovalEnabled, + customFieldsFilter: opts.customFieldsFilter, + }, + }), + ); + }, + ); - registerCustomerTrustDeletionRequestsCommand(customerTrust, getFlags); + addJsonFileOptions( + customerTrust + .command("create-account") + .description("Create a Customer Trust account"), + ).action(async (opts: { json?: string; file?: string }) => { + const body = (await readJSONPayload( + opts.json, + opts.file, + )) as CreateCustomerTrustAccountData["body"]; + await runSdk(getFlags, (api) => + createCustomerTrustAccount({ client: api.client, body }), + ); + }); - registerCustomerTrustQuestionnairesCommand(customerTrust, getFlags); + customerTrust + .command("get-account") + .description("Get a Customer Trust account by ID") + .requiredOption("--account-id ", "Customer Trust account ID") + .action(async (opts: { accountId: string }) => { + await runSdk(getFlags, (api) => + getCustomerTrustAccount({ + client: api.client, + path: { accountId: opts.accountId }, + }), + ); + }); - registerCustomerTrustTagCategoriesCommand(customerTrust, getFlags); + addJsonFileOptions( + customerTrust + .command("update-account") + .description("Update a Customer Trust account") + .requiredOption("--account-id ", "Customer Trust account ID"), + ).action(async (opts: { accountId: string; json?: string; file?: string }) => { + const body = (await readJSONPayload( + opts.json, + opts.file, + )) as UpdateCustomerTrustAccountData["body"]; + await runSdk(getFlags, (api) => + updateCustomerTrustAccount({ + client: api.client, + path: { accountId: opts.accountId }, + body, + }), + ); + }); + + customerTrust + .command("delete-account") + .description("Delete a Customer Trust account") + .requiredOption("--account-id ", "Customer Trust account ID") + .action(async (opts: { accountId: string }) => { + await runSdk(getFlags, (api) => + deleteCustomerTrustAccount({ + client: api.client, + path: { accountId: opts.accountId }, + }), + ); + }); + + addJsonFileOptions( + customerTrust + .command("create-deletion-request") + .description("Create a deletion request"), + ).action(async (opts: { json?: string; file?: string }) => { + const body = (await readJSONPayload( + opts.json, + opts.file, + )) as CreateDeletionRequestData["body"]; + await runSdk(getFlags, (api) => + createDeletionRequest({ client: api.client, body }), + ); + }); + + addPaginationOptions( + customerTrust + .command("list-questionnaires") + .description("List questionnaires") + .option( + "--q ", + "Filter questionnaires by display name (case-insensitive, partial match)", + ) + .option( + "--status-matches-any ", + "Filter questionnaires matching any of the provided statuses: APPROVED, IN_PROGRESS, IN_REVIEW, READY_FOR_REVIEW, WAITING_ON_ANSWERS, ON_HOLD, NO_LONGER_NEEDED, COMPLETE, ERROR, EXTRACTING_QUESTIONS, QUEUED_FOR_SECTION_EXTRACTION, EXTRACTING_SECTIONS, MAPPING_SECTIONS, QUEUED_FOR_ANSWERING, GENERATING_ANSWERS, QUEUED_FOR_EXTRACTION, PROCESSING, QUEUED_FOR_PROCESSING, WAITING_ON_COLUMN_SELECTION, WAITING_ON_COLUMN_APPROVAL, QUEUED_FOR_COLUMN_DETECTION, DETECTING_COLUMNS (repeatable)", + collectString, + [] as string[], + ) + .option( + "--type-matches-any ", + "Filter questionnaires matching any of the provided types: SPREADSHEET, WEBSITE, DOCUMENT (repeatable)", + collectString, + [] as string[], + ) + .option( + "--created-after ", + "Filter to questionnaires created after this date (ISO 8601 timestamp)", + ) + .option( + "--created-before ", + "Filter to questionnaires created before this date (ISO 8601 timestamp)", + ) + .option( + "--owner-id-matches-any ", + "Filter to questionnaires owned by any of the provided user IDs (repeatable)", + collectString, + [] as string[], + ) + .option( + "--approver-id-matches-any ", + "Filter to questionnaires with an approver matching any of the provided user IDs (repeatable)", + collectString, + [] as string[], + ), + ).action( + async (opts: { + pageSize?: number; + pageCursor?: string; + q?: string; + statusMatchesAny: NonNullable["statusMatchesAny"]>; + typeMatchesAny: NonNullable["typeMatchesAny"]>; + createdAfter?: string; + createdBefore?: string; + ownerIdMatchesAny: string[]; + approverIdMatchesAny: string[]; + }) => { + await runSdk(getFlags, (api) => + listQuestionnaires({ + client: api.client, + query: { + ...paginationQuery(opts), + q: opts.q, + statusMatchesAny: opts.statusMatchesAny, + typeMatchesAny: opts.typeMatchesAny, + createdAfter: opts.createdAfter, + createdBefore: opts.createdBefore, + ownerIdMatchesAny: opts.ownerIdMatchesAny, + approverIdMatchesAny: opts.approverIdMatchesAny, + }, + }), + ); + }, + ); + + customerTrust + .command("list-questionnaire-assignable-users") + .description("List users who can be assigned to questionnaires") + .option("--role ", "Filter by role: \"owner\" or \"approver\"") + .option( + "--q ", + "Optional search string to filter users by name or email", + ) + .action( + async (opts: { + role?: NonNullable["role"]; + q?: string; + }) => { + await runSdk(getFlags, (api) => + listAssignableUsers({ + client: api.client, + query: { role: opts.role, q: opts.q }, + }), + ); + }, + ); + + addJsonFileOptions( + customerTrust + .command("create-questionnaire-export") + .description("Create a questionnaire export"), + ).action(async (opts: { json?: string; file?: string }) => { + const body = (await readJSONPayload( + opts.json, + opts.file, + )) as CreateQuestionnaireExportData["body"]; + await runSdk(getFlags, (api) => + createQuestionnaireExport({ client: api.client, body }), + ); + }); + + customerTrust + .command("get-questionnaire-export") + .description("Get a questionnaire export's status by ID") + .requiredOption( + "--id ", + "Export job ID returned by `customer-trust create-questionnaire-export`", + ) + .action(async (opts: { id: string }) => { + await runSdk(getFlags, (api) => + getQuestionnaireExport({ client: api.client, path: { id: opts.id } }), + ); + }); + + customerTrust + .command("upload-questionnaire-file") + .description("Upload a file to create a questionnaire") + .requiredOption("--file ", "Path to file to upload") + .requiredOption( + "--display-name ", + "Display name for the questionnaire", + ) + .option( + "--owner-assignment ", + "Owner to assign, as a JSON string: {\"type\": \"User\" | \"Team\", \"id\": \"\"}", + ) + .option( + "--approver-assignment ", + "Approver to assign, as a JSON string: {\"type\": \"User\" | \"Team\", \"id\": \"\"}", + ) + .option("--description ", "Description of the questionnaire") + .option( + "--company-url ", + "URL of the company associated with this questionnaire", + ) + .option( + "--due-date ", + "Due date for questionnaire completion (ISO 8601 timestamp)", + ) + .option( + "--customer-trust-account-id ", + "ID of the customer trust account to associate with this questionnaire", + ) + .option( + "--include-untagged-entities-for-category-ids ", + "Comma-separated category IDs for which to include untagged entities", + ) + .option( + "--metadata ", + "Custom key-value pairs, as a JSON string array: [{\"key\": \"\", \"value\": \"\"}]. Keys and values may contain alphanumeric characters, hyphens, underscores, and periods", + ) + .option( + "--tag-and-category-ids ", + "Tags to assign, as a JSON string array: [{\"categoryId\": \"\", \"tagId\": \"\"}]. Replaces all existing tags", + ) + .action( + async (opts: { + file: string; + displayName: string; + ownerAssignment?: string; + approverAssignment?: string; + description?: string; + companyUrl?: string; + dueDate?: string; + customerTrustAccountId?: string; + includeUntaggedEntitiesForCategoryIds?: string; + metadata?: string; + tagAndCategoryIds?: string; + }) => { + const file = await readBinaryFile(opts.file); + await runSdk(getFlags, (api) => + createFileQuestionnaire({ + client: api.client, + body: { + file, + displayName: opts.displayName, + ownerAssignment: opts.ownerAssignment, + approverAssignment: opts.approverAssignment, + description: opts.description, + companyUrl: opts.companyUrl, + dueDate: opts.dueDate, + customerTrustAccountId: opts.customerTrustAccountId, + includeUntaggedEntitiesForCategoryIds: opts.includeUntaggedEntitiesForCategoryIds, + metadata: opts.metadata, + tagAndCategoryIds: opts.tagAndCategoryIds, + }, + }), + ); + }, + ); + + addJsonFileOptions( + customerTrust + .command("create-questionnaire-website") + .description("Create a website questionnaire"), + ).action(async (opts: { json?: string; file?: string }) => { + const body = (await readJSONPayload( + opts.json, + opts.file, + )) as CreateWebsiteQuestionnaireData["body"]; + await runSdk(getFlags, (api) => + createWebsiteQuestionnaire({ client: api.client, body }), + ); + }); + + customerTrust + .command("get-questionnaire") + .description("Get a questionnaire by ID") + .requiredOption("--questionnaire-id ", "Questionnaire ID") + .action(async (opts: { questionnaireId: string }) => { + await runSdk(getFlags, (api) => + getQuestionnaire({ + client: api.client, + path: { questionnaireId: opts.questionnaireId }, + }), + ); + }); + + addJsonFileOptions( + customerTrust + .command("update-questionnaire") + .description("Update a questionnaire") + .requiredOption("--questionnaire-id ", "Questionnaire ID"), + ).action( + async (opts: { + questionnaireId: string; + json?: string; + file?: string; + }) => { + const body = (await readJSONPayload( + opts.json, + opts.file, + )) as UpdateQuestionnaireData["body"]; + await runSdk(getFlags, (api) => + updateQuestionnaire({ + client: api.client, + path: { questionnaireId: opts.questionnaireId }, + body, + }), + ); + }, + ); + + customerTrust + .command("delete-questionnaire") + .description("Delete a questionnaire") + .requiredOption("--questionnaire-id ", "Questionnaire ID") + .action(async (opts: { questionnaireId: string }) => { + await runSdk(getFlags, (api) => + deleteQuestionnaire({ + client: api.client, + path: { questionnaireId: opts.questionnaireId }, + }), + ); + }); + + addJsonFileOptions( + customerTrust + .command("approve-questionnaire") + .description("Approve a questionnaire") + .requiredOption("--questionnaire-id ", "Questionnaire ID"), + ).action( + async (opts: { + questionnaireId: string; + json?: string; + file?: string; + }) => { + const body = (await readJSONPayload( + opts.json, + opts.file, + )) as ApproveQuestionnaireData["body"]; + await runSdk(getFlags, (api) => + approveQuestionnaire({ + client: api.client, + path: { questionnaireId: opts.questionnaireId }, + body, + }), + ); + }, + ); + + addJsonFileOptions( + customerTrust + .command("complete-questionnaire") + .description("Complete a questionnaire") + .requiredOption("--questionnaire-id ", "Questionnaire ID"), + ).action( + async (opts: { + questionnaireId: string; + json?: string; + file?: string; + }) => { + const body = (await readJSONPayload( + opts.json, + opts.file, + )) as CompleteQuestionnaireData["body"]; + await runSdk(getFlags, (api) => + completeQuestionnaire({ + client: api.client, + path: { questionnaireId: opts.questionnaireId }, + body, + }), + ); + }, + ); + + addPaginationOptions( + customerTrust + .command("list-questionnaire-responses") + .description("List responses for a questionnaire") + .requiredOption("--questionnaire-id ", "Questionnaire ID") + .option( + "--q ", + "Filter responses by question text (case-insensitive, partial match)", + ), + ).action( + async (opts: { + questionnaireId: string; + pageSize?: number; + pageCursor?: string; + q?: string; + }) => { + await runSdk(getFlags, (api) => + listQuestionnaireResponses({ + client: api.client, + path: { questionnaireId: opts.questionnaireId }, + query: { ...paginationQuery(opts), q: opts.q }, + }), + ); + }, + ); + + customerTrust + .command("get-questionnaire-response") + .description("Get a questionnaire response by ID") + .requiredOption("--questionnaire-id ", "Questionnaire ID") + .requiredOption("--response-id ", "Response ID") + .action(async (opts: { questionnaireId: string; responseId: string }) => { + await runSdk(getFlags, (api) => + getQuestionnaireResponse({ + client: api.client, + path: { + questionnaireId: opts.questionnaireId, + responseId: opts.responseId, + }, + }), + ); + }); + + addJsonFileOptions( + customerTrust + .command("update-questionnaire-response") + .description("Update a questionnaire response") + .requiredOption("--questionnaire-id ", "Questionnaire ID") + .requiredOption("--response-id ", "Response ID"), + ).action( + async (opts: { + questionnaireId: string; + responseId: string; + json?: string; + file?: string; + }) => { + const body = (await readJSONPayload( + opts.json, + opts.file, + )) as UpdateQuestionnaireResponseContentData["body"]; + await runSdk(getFlags, (api) => + updateQuestionnaireResponseContent({ + client: api.client, + path: { + questionnaireId: opts.questionnaireId, + responseId: opts.responseId, + }, + body, + }), + ); + }, + ); + + addJsonFileOptions( + customerTrust + .command("update-questionnaire-response-owner") + .description("Update a questionnaire response owner") + .requiredOption("--questionnaire-id ", "Questionnaire ID") + .requiredOption("--response-id ", "Response ID"), + ).action( + async (opts: { + questionnaireId: string; + responseId: string; + json?: string; + file?: string; + }) => { + const body = (await readJSONPayload( + opts.json, + opts.file, + )) as UpdateQuestionnaireResponseOwnerData["body"]; + await runSdk(getFlags, (api) => + updateQuestionnaireResponseOwner({ + client: api.client, + path: { + questionnaireId: opts.questionnaireId, + responseId: opts.responseId, + }, + body, + }), + ); + }, + ); + + customerTrust + .command("list-tag-categories") + .description("List tag categories") + .option( + "--product-context-ids-matches-any ", + "Filter by product context IDs: EXTERNAL_TRUST_CENTER, DOCUMENT_SHARING, CONTROL_SHARING, QUESTIONNAIRE (repeatable)", + collectString, + [] as string[], + ) + .action( + async (opts: { + productContextIdsMatchesAny: NonNullable["productContextIdsMatchesAny"]>; + }) => { + await runSdk(getFlags, (api) => + listTagCategories({ + client: api.client, + query: { + productContextIdsMatchesAny: opts.productContextIdsMatchesAny, + }, + }), + ); + }, + ); + + customerTrust + .command("get-tag-category") + .description("Get the tags in a tag category by ID") + .requiredOption("--tag-category-id ", "Tag category ID") + .action(async (opts: { tagCategoryId: string }) => { + await runSdk(getFlags, (api) => + getTagsForCategory({ + client: api.client, + path: { tagCategoryId: opts.tagCategoryId }, + }), + ); + }); + + addJsonFileOptions( + customerTrust + .command("add-tag-category-product-context") + .description("Enable a tag category for a product context") + .requiredOption("--tag-category-id ", "Tag category ID"), + ).action( + async (opts: { + tagCategoryId: string; + json?: string; + file?: string; + }) => { + const body = (await readJSONPayload( + opts.json, + opts.file, + )) as AddTagCategoryProductContextData["body"]; + await runSdk(getFlags, (api) => + addTagCategoryProductContext({ + client: api.client, + path: { tagCategoryId: opts.tagCategoryId }, + body, + }), + ); + }, + ); + + customerTrust + .command("remove-tag-category-product-context") + .description("Disable a tag category for a product context") + .requiredOption("--tag-category-id ", "Tag category ID") + .requiredOption( + "--product-context-id ", + "Product context: EXTERNAL_TRUST_CENTER, DOCUMENT_SHARING, CONTROL_SHARING", + ) + .action( + async (opts: { + tagCategoryId: string; + productContextId: RemoveTagCategoryProductContextData["path"]["productContextId"]; + }) => { + await runSdk(getFlags, (api) => + removeTagCategoryProductContext({ + client: api.client, + path: { + tagCategoryId: opts.tagCategoryId, + productContextId: opts.productContextId, + }, + }), + ); + }, + ); } diff --git a/src/commands/discovered-vendors.ts b/src/commands/discovered-vendors.ts index 8119895..6150e45 100644 --- a/src/commands/discovered-vendors.ts +++ b/src/commands/discovered-vendors.ts @@ -1,10 +1,10 @@ import type { Command } from "commander"; import { - addDiscoveredVendorToManaged, - listDiscoveredVendorAccounts, listDiscoveredVendors, + listDiscoveredVendorAccounts, + addDiscoveredVendorToManaged, } from "../generated/sdk.gen.js"; -import type { DiscoveredVendorScope } from "../generated/types.gen.js"; +import type { ListDiscoveredVendorsData } from "../generated/types.gen.js"; import { addPaginationOptions, paginationQuery, @@ -32,17 +32,12 @@ export function registerDiscoveredVendorsCommand( async (opts: { pageSize?: number; pageCursor?: string; - scope?: string; + scope?: NonNullable["scope"]; }) => { await runSdk(getFlags, (api) => listDiscoveredVendors({ client: api.client, - query: { - ...paginationQuery(opts), - ...(opts.scope?.trim() - ? { scope: opts.scope.trim() as DiscoveredVendorScope } - : {}), - }, + query: { ...paginationQuery(opts), scope: opts.scope }, }), ); }, @@ -54,7 +49,11 @@ export function registerDiscoveredVendorsCommand( .description("List accounts for a discovered vendor") .requiredOption("--id ", "Discovered vendor ID"), ).action( - async (opts: { id: string; pageSize?: number; pageCursor?: string }) => { + async (opts: { + id: string; + pageSize?: number; + pageCursor?: string; + }) => { await runSdk(getFlags, (api) => listDiscoveredVendorAccounts({ client: api.client, diff --git a/src/commands/documents.ts b/src/commands/documents.ts index 0863903..067aeb7 100644 --- a/src/commands/documents.ts +++ b/src/commands/documents.ts @@ -4,22 +4,22 @@ import { getDocument, createDocument, deleteDocument, - setOwnerForDocument, listControlsForDocument, listLinksForDocument, createLinkForDocument, deleteLinkForDocument, + setOwnerForDocument, + submitDocumentCollection, listFilesForDocument, uploadFileForDocument, deleteFileForDocument, getUploadedfileMedia, - submitDocumentCollection, } from "../generated/sdk.gen.js"; import type { - CreateDocumentInput, - CreateLinkForDocumentInput, - DocumentStatus, - SetOwnerForDocumentInput, + ListDocumentsData, + CreateDocumentData, + CreateLinkForDocumentData, + SetOwnerForDocumentData, } from "../generated/types.gen.js"; import { printResponse } from "../output.js"; import { @@ -64,21 +64,15 @@ export function registerDocumentsCommand( pageSize?: number; pageCursor?: string; frameworkMatchesAny: string[]; - statusMatchesAny: string[]; + statusMatchesAny: NonNullable["statusMatchesAny"]>; }) => { - const frameworkMatchesAny = opts.frameworkMatchesAny.filter(Boolean); - const statusMatchesAny = opts.statusMatchesAny.filter( - Boolean, - ) as DocumentStatus[]; await runSdk(getFlags, (api) => listDocuments({ client: api.client, query: { ...paginationQuery(opts), - ...(frameworkMatchesAny.length > 0 - ? { frameworkMatchesAny } - : {}), - ...(statusMatchesAny.length > 0 ? { statusMatchesAny } : {}), + frameworkMatchesAny: opts.frameworkMatchesAny, + statusMatchesAny: opts.statusMatchesAny, }, }), ); @@ -101,7 +95,7 @@ export function registerDocumentsCommand( const body = (await readJSONPayload( opts.json, opts.file, - )) as CreateDocumentInput; + )) as CreateDocumentData["body"]; await runSdk(getFlags, (api) => createDocument({ client: api.client, body }), ); @@ -117,32 +111,17 @@ export function registerDocumentsCommand( ); }); - addJsonFileOptions( - documents - .command("set-owner") - .description("Set or clear a document owner") - .requiredOption("--id ", "Document ID"), - ).action(async (opts: { id: string; json?: string; file?: string }) => { - const body = (await readJSONPayload( - opts.json, - opts.file, - )) as SetOwnerForDocumentInput; - await runSdk(getFlags, (api) => - setOwnerForDocument({ - client: api.client, - path: { documentId: opts.id }, - body, - }), - ); - }); - addPaginationOptions( documents .command("list-controls") .description("List controls for a document") .requiredOption("--id ", "Document ID"), ).action( - async (opts: { id: string; pageSize?: number; pageCursor?: string }) => { + async (opts: { + id: string; + pageSize?: number; + pageCursor?: string; + }) => { await runSdk(getFlags, (api) => listControlsForDocument({ client: api.client, @@ -159,7 +138,11 @@ export function registerDocumentsCommand( .description("List links for a document") .requiredOption("--id ", "Document ID"), ).action( - async (opts: { id: string; pageSize?: number; pageCursor?: string }) => { + async (opts: { + id: string; + pageSize?: number; + pageCursor?: string; + }) => { await runSdk(getFlags, (api) => listLinksForDocument({ client: api.client, @@ -173,13 +156,13 @@ export function registerDocumentsCommand( addJsonFileOptions( documents .command("create-link") - .description("Create a link for a document") + .description("Create a document link") .requiredOption("--id ", "Document ID"), ).action(async (opts: { id: string; json?: string; file?: string }) => { const body = (await readJSONPayload( opts.json, opts.file, - )) as CreateLinkForDocumentInput; + )) as CreateLinkForDocumentData["body"]; await runSdk(getFlags, (api) => createLinkForDocument({ client: api.client, @@ -191,7 +174,7 @@ export function registerDocumentsCommand( documents .command("delete-link") - .description("Delete a link for a document") + .description("Delete a document link") .requiredOption("--id ", "Document ID") .requiredOption("--link-id ", "Link ID") .action(async (opts: { id: string; linkId: string }) => { @@ -203,13 +186,49 @@ export function registerDocumentsCommand( ); }); + addJsonFileOptions( + documents + .command("set-owner") + .description("Set or clear a document owner") + .requiredOption("--id ", "Document ID"), + ).action(async (opts: { id: string; json?: string; file?: string }) => { + const body = (await readJSONPayload( + opts.json, + opts.file, + )) as SetOwnerForDocumentData["body"]; + await runSdk(getFlags, (api) => + setOwnerForDocument({ + client: api.client, + path: { documentId: opts.id }, + body, + }), + ); + }); + + documents + .command("submit") + .description("Submit document collection") + .requiredOption("--id ", "Document ID") + .action(async (opts: { id: string }) => { + await runSdk(getFlags, (api) => + submitDocumentCollection({ + client: api.client, + path: { documentId: opts.id }, + }), + ); + }); + addPaginationOptions( documents - .command("list-uploads") - .description("List uploaded files for a document") + .command("list-files") + .description("List uploads for a document") .requiredOption("--id ", "Document ID"), ).action( - async (opts: { id: string; pageSize?: number; pageCursor?: string }) => { + async (opts: { + id: string; + pageSize?: number; + pageCursor?: string; + }) => { await runSdk(getFlags, (api) => listFilesForDocument({ client: api.client, @@ -225,8 +244,11 @@ export function registerDocumentsCommand( .description("Upload a file for a document") .requiredOption("--id ", "Document ID") .requiredOption("--file ", "Path to file to upload") - .option("--effective-at-date ", "Effective date for the uploaded file") - .option("--description ", "Description for the uploaded file") + .option( + "--effective-at-date ", + "Date indicating when the document is effective from", + ) + .option("--description ", "Description of the uploaded document") .action( async (opts: { id: string; @@ -234,23 +256,15 @@ export function registerDocumentsCommand( effectiveAtDate?: string; description?: string; }) => { - const filePath = opts.file.trim(); - if (!filePath) { - throw new Error("--file is required"); - } - const file = await readBinaryFile(filePath); + const file = await readBinaryFile(opts.file); await runSdk(getFlags, (api) => uploadFileForDocument({ client: api.client, path: { documentId: opts.id }, body: { file, - ...(opts.effectiveAtDate?.trim() - ? { effectiveAtDate: opts.effectiveAtDate.trim() } - : {}), - ...(opts.description?.trim() - ? { description: opts.description.trim() } - : {}), + effectiveAtDate: opts.effectiveAtDate, + description: opts.description, }, }), ); @@ -259,24 +273,21 @@ export function registerDocumentsCommand( documents .command("delete-file") - .description("Delete an uploaded file for a document") + .description("Delete a document file") .requiredOption("--id ", "Document ID") .requiredOption("--uploaded-file-id ", "Uploaded file ID") .action(async (opts: { id: string; uploadedFileId: string }) => { await runSdk(getFlags, (api) => deleteFileForDocument({ client: api.client, - path: { - documentId: opts.id, - uploadedFileId: opts.uploadedFileId, - }, + path: { documentId: opts.id, uploadedFileId: opts.uploadedFileId }, }), ); }); documents - .command("download-file") - .description("Download uploaded file media for a document") + .command("download-file-media") + .description("Download the media for a document upload") .requiredOption("--id ", "Document ID") .requiredOption("--uploaded-file-id ", "Uploaded file ID") .option( @@ -292,32 +303,13 @@ export function registerDocumentsCommand( await withClient(getFlags, async (api, flags) => { const result = await getUploadedfileMedia({ client: api.client, - path: { - documentId: opts.id, - uploadedFileId: opts.uploadedFileId, - }, + path: { documentId: opts.id, uploadedFileId: opts.uploadedFileId }, }); if (result.error) throw result.error; - const data = result.data; const dest = opts.output?.trim(); - await writeDownloadedMedia(data, dest); - if (dest) { - printResponse({ savedTo: dest }, flags); - } + await writeDownloadedMedia(result.data, dest); + if (dest) printResponse({ savedTo: dest }, flags); }); }, ); - - documents - .command("submit") - .description("Submit document collection") - .requiredOption("--id ", "Document ID") - .action(async (opts: { id: string }) => { - await runSdk(getFlags, (api) => - submitDocumentCollection({ - client: api.client, - path: { documentId: opts.id }, - }), - ); - }); } diff --git a/src/commands/event-logs.ts b/src/commands/event-logs.ts index 6c155ae..e79f7b3 100644 --- a/src/commands/event-logs.ts +++ b/src/commands/event-logs.ts @@ -16,23 +16,25 @@ export function registerEventLogsCommand( .description("Manage event logs"); addPaginationOptions( - eventLogs.command("list").description("List event logs"), - ) - .option( - "--start-date ", - "Filter to event logs created at or after this timestamp", - ) - .action(async (opts: { pageSize?: number; pageCursor?: string; startDate?: string }) => { + eventLogs + .command("list") + .description("List event logs") + .option( + "--start-date ", + "Filter to event logs created at or after this ISO 8601 timestamp", + ), + ).action( + async (opts: { + pageSize?: number; + pageCursor?: string; + startDate?: string; + }) => { await runSdk(getFlags, (api) => listEventLogs({ client: api.client, - query: { - ...paginationQuery(opts), - ...(opts.startDate?.trim() - ? { startDate: opts.startDate.trim() } - : {}), - }, + query: { ...paginationQuery(opts), startDate: opts.startDate }, }), ); - }); + }, + ); } diff --git a/src/commands/frameworks.ts b/src/commands/frameworks.ts index 16a60bf..201f1c8 100644 --- a/src/commands/frameworks.ts +++ b/src/commands/frameworks.ts @@ -42,13 +42,19 @@ export function registerFrameworksCommand( .command("list-controls") .description("List controls for a framework") .requiredOption("--id ", "Framework ID"), - ).action(async (opts: { id: string; pageSize?: number; pageCursor?: string }) => { - await runSdk(getFlags, (api) => - listControlsForFramework({ - client: api.client, - path: { frameworkId: opts.id }, - query: paginationQuery(opts), - }), - ); - }); + ).action( + async (opts: { + id: string; + pageSize?: number; + pageCursor?: string; + }) => { + await runSdk(getFlags, (api) => + listControlsForFramework({ + client: api.client, + path: { frameworkId: opts.id }, + query: paginationQuery(opts), + }), + ); + }, + ); } diff --git a/src/commands/groups.ts b/src/commands/groups.ts index 462e85f..0696570 100644 --- a/src/commands/groups.ts +++ b/src/commands/groups.ts @@ -1,30 +1,29 @@ import type { Command } from "commander"; import { + listPersonGroups, + getGroup, createGroup, updateGroup, importIdpGroups, listImportableIdpGroups, addPeopleToGroup, - addPersonToGroup, - getGroup, getGroupMembers, - listPersonGroups, - removePeopleFromGroup, + addPersonToGroup, removePersonFromGroup, + removePeopleFromGroup, } from "../generated/sdk.gen.js"; import type { CreateGroupData, UpdateGroupData, ImportIdpGroupsData, - ListImportableIdpGroupsData, AddPeopleToGroupData, AddPersonToGroupData, RemovePeopleFromGroupData, } from "../generated/types.gen.js"; import { - collectString, addJsonFileOptions, addPaginationOptions, + collectString, paginationQuery, readJSONPayload, runSdk, @@ -41,10 +40,7 @@ export function registerGroupsCommand( groups.command("list").description("List groups"), ).action(async (opts) => { await runSdk(getFlags, (api) => - listPersonGroups({ - client: api.client, - query: paginationQuery(opts), - }), + listPersonGroups({ client: api.client, query: paginationQuery(opts) }), ); }); @@ -58,159 +54,146 @@ export function registerGroupsCommand( ); }); - groups - .command("list-people") - .description("List people in a group") - .requiredOption("--id ", "Group ID") - .action(async (opts: { id: string }) => { - await runSdk(getFlags, (api) => - getGroupMembers({ client: api.client, path: { groupId: opts.id } }), - ); - }); - addJsonFileOptions( - groups - .command("add-person") - .description("Add a person to a group") - .requiredOption("--id ", "Group ID"), - ).action(async (opts: { id: string; json?: string; file?: string }) => { + groups.command("create").description("Create a group"), + ).action(async (opts: { json?: string; file?: string }) => { const body = (await readJSONPayload( opts.json, opts.file, - )) as AddPersonToGroupData["body"]; + )) as CreateGroupData["body"]; await runSdk(getFlags, (api) => - addPersonToGroup({ - client: api.client, - path: { groupId: opts.id }, - body, - }), + createGroup({ client: api.client, body }), ); }); - groups - .command("remove-person") - .description("Remove a person from a group") - .requiredOption("--id ", "Group ID") - .requiredOption("--person-id ", "Person ID") - .action(async (opts: { id: string; personId: string }) => { - await runSdk(getFlags, (api) => - removePersonFromGroup({ - client: api.client, - path: { groupId: opts.id, personId: opts.personId }, - }), - ); - }); - addJsonFileOptions( groups - .command("add-people") - .description("Add people to a group") + .command("update") + .description("Update a group") .requiredOption("--id ", "Group ID"), ).action(async (opts: { id: string; json?: string; file?: string }) => { const body = (await readJSONPayload( opts.json, opts.file, - )) as AddPeopleToGroupData["body"]; + )) as UpdateGroupData["body"]; await runSdk(getFlags, (api) => - addPeopleToGroup({ - client: api.client, - path: { groupId: opts.id }, - body, - }), + updateGroup({ client: api.client, path: { groupId: opts.id }, body }), ); }); addJsonFileOptions( - groups - .command("remove-people") - .description("Remove people from a group") - .requiredOption("--id ", "Group ID"), - ).action(async (opts: { id: string; json?: string; file?: string }) => { + groups.command("import-from-idp").description("Import IdP groups"), + ).action(async (opts: { json?: string; file?: string }) => { const body = (await readJSONPayload( opts.json, opts.file, - )) as RemovePeopleFromGroupData["body"]; + )) as ImportIdpGroupsData["body"]; await runSdk(getFlags, (api) => - removePeopleFromGroup({ - client: api.client, - path: { groupId: opts.id }, - body, - }), + importIdpGroups({ client: api.client, body }), ); }); + addPaginationOptions( + groups + .command("list-importable-idp-groups") + .description("List importable identity provider groups") + .option("--search ", "Search importable identity provider groups") + .option( + "--integration-id ", + "Filter by integration ID (repeatable)", + collectString, + [] as string[], + ), + ).action( + async (opts: { + pageSize?: number; + pageCursor?: string; + search?: string; + integrationId: string[]; + }) => { + await runSdk(getFlags, (api) => + listImportableIdpGroups({ + client: api.client, + query: { + ...paginationQuery(opts), + search: opts.search, + integrationId: opts.integrationId, + }, + }), + ); + }, + ); + addJsonFileOptions( - groups.command("create").description("Create a group"), - ).action(async (opts: { json?: string; file?: string }) => { + groups + .command("add-people") + .description("Add people to a group") + .requiredOption("--id ", "Group ID"), + ).action(async (opts: { id: string; json?: string; file?: string }) => { const body = (await readJSONPayload( opts.json, opts.file, - )) as CreateGroupData["body"]; + )) as AddPeopleToGroupData["body"]; await runSdk(getFlags, (api) => - createGroup({ - client: api.client, - body, - }), + addPeopleToGroup({ client: api.client, path: { groupId: opts.id }, body }), ); }); + groups + .command("list-people") + .description("List people for a group") + .requiredOption("--id ", "Group ID") + .action(async (opts: { id: string }) => { + await runSdk(getFlags, (api) => + getGroupMembers({ client: api.client, path: { groupId: opts.id } }), + ); + }); + addJsonFileOptions( groups - .command("update") - .description("Update a group") + .command("add-person") + .description("Add a person to a group") .requiredOption("--id ", "Group ID"), ).action(async (opts: { id: string; json?: string; file?: string }) => { const body = (await readJSONPayload( opts.json, opts.file, - )) as UpdateGroupData["body"]; + )) as AddPersonToGroupData["body"]; await runSdk(getFlags, (api) => - updateGroup({ - client: api.client, - path: { groupId: opts.id }, - body, - }), + addPersonToGroup({ client: api.client, path: { groupId: opts.id }, body }), ); }); + groups + .command("remove-person") + .description("Remove a person from a group") + .requiredOption("--id ", "Group ID") + .requiredOption("--person-id ", "Person ID") + .action(async (opts: { id: string; personId: string }) => { + await runSdk(getFlags, (api) => + removePersonFromGroup({ + client: api.client, + path: { groupId: opts.id, personId: opts.personId }, + }), + ); + }); + addJsonFileOptions( groups - .command("import-from-idp") - .description("Import groups from an identity provider"), - ).action(async (opts: { json?: string; file?: string }) => { + .command("remove-people") + .description("Remove people from a group") + .requiredOption("--id ", "Group ID"), + ).action(async (opts: { id: string; json?: string; file?: string }) => { const body = (await readJSONPayload( opts.json, opts.file, - )) as ImportIdpGroupsData["body"]; + )) as RemovePeopleFromGroupData["body"]; await runSdk(getFlags, (api) => - importIdpGroups({ + removePeopleFromGroup({ client: api.client, + path: { groupId: opts.id }, body, }), ); }); - - addPaginationOptions( - groups - .command("list-importable-idp-groups") - .description("List importable identity provider groups") - .option("--search ", "Search importable identity provider groups") - .option( - "--integration-id ", - "Filter by identity provider integration ID (repeatable)", - collectString, - [] as string[], - ), - ).action(async (opts: NonNullable) => { - await runSdk(getFlags, (api) => - listImportableIdpGroups({ - client: api.client, - query: { - ...paginationQuery(opts), - search: opts.search, - integrationId: opts.integrationId, - }, - }), - ); - }); } diff --git a/src/commands/index.ts b/src/commands/index.ts new file mode 100644 index 0000000..1ecaa7f --- /dev/null +++ b/src/commands/index.ts @@ -0,0 +1,59 @@ +import type { Command } from "commander"; +import { registerControlsCommand } from "./controls.js"; +import { registerPoliciesCommand } from "./policies.js"; +import { registerDocumentsCommand } from "./documents.js"; +import { registerTestsCommand } from "./tests.js"; +import { registerPeopleCommand } from "./people.js"; +import { registerPersonnelNotificationSettingsCommand } from "./personnel-notification-settings.js"; +import { registerGroupsCommand } from "./groups.js"; +import { registerFrameworksCommand } from "./frameworks.js"; +import { registerUsersCommand } from "./users.js"; +import { registerVulnerabilitiesCommand } from "./vulnerabilities.js"; +import { registerVulnerableAssetsCommand } from "./vulnerable-assets.js"; +import { registerVulnerabilityRemediationsCommand } from "./vulnerability-remediations.js"; +import { registerContractsCommand } from "./contracts.js"; +import { registerRiskScenariosCommand } from "./risk-scenarios.js"; +import { registerMonitoredComputersCommand } from "./monitored-computers.js"; +import { registerVendorsCommand } from "./vendors.js"; +import { registerVendorAssessmentTypesCommand } from "./vendor-assessment-types.js"; +import { registerVendorRiskAttributesCommand } from "./vendor-risk-attributes.js"; +import { registerDiscoveredVendorsCommand } from "./discovered-vendors.js"; +import { registerIntegrationsCommand } from "./integrations.js"; +import { registerEventLogsCommand } from "./event-logs.js"; +import { registerBusinessUnitsCommand } from "./business-units.js"; +import { registerIssuesCommand } from "./issues.js"; +import { registerProgramScopesCommand } from "./program-scopes.js"; +import { registerCustomerTrustCommand } from "./customer-trust.js"; +import { registerKnowledgeBaseCommand } from "./knowledge-base.js"; +import { registerTrustCentersCommand } from "./trust-centers.js"; +import type { GetFlags } from "./helpers.js"; + +export function registerApiCommands(program: Command, getFlags: GetFlags): void { + registerControlsCommand(program, getFlags); + registerPoliciesCommand(program, getFlags); + registerDocumentsCommand(program, getFlags); + registerTestsCommand(program, getFlags); + registerPeopleCommand(program, getFlags); + registerPersonnelNotificationSettingsCommand(program, getFlags); + registerGroupsCommand(program, getFlags); + registerFrameworksCommand(program, getFlags); + registerUsersCommand(program, getFlags); + registerVulnerabilitiesCommand(program, getFlags); + registerVulnerableAssetsCommand(program, getFlags); + registerVulnerabilityRemediationsCommand(program, getFlags); + registerContractsCommand(program, getFlags); + registerRiskScenariosCommand(program, getFlags); + registerMonitoredComputersCommand(program, getFlags); + registerVendorsCommand(program, getFlags); + registerVendorAssessmentTypesCommand(program, getFlags); + registerVendorRiskAttributesCommand(program, getFlags); + registerDiscoveredVendorsCommand(program, getFlags); + registerIntegrationsCommand(program, getFlags); + registerEventLogsCommand(program, getFlags); + registerBusinessUnitsCommand(program, getFlags); + registerIssuesCommand(program, getFlags); + registerProgramScopesCommand(program, getFlags); + registerCustomerTrustCommand(program, getFlags); + registerKnowledgeBaseCommand(program, getFlags); + registerTrustCentersCommand(program, getFlags); +} diff --git a/src/commands/integrations.ts b/src/commands/integrations.ts index 2367f17..a89c123 100644 --- a/src/commands/integrations.ts +++ b/src/commands/integrations.ts @@ -1,17 +1,17 @@ import type { Command } from "commander"; import { - getConnectedIntegration, - getResource, - getResourceKindDetails, listConnectedIntegrations, + getConnectedIntegration, listResourceKindSummaries, + getResourceKindDetails, listResources, - updateResource, updateResources, + getResource, + updateResource, } from "../generated/sdk.gen.js"; import type { - UpdateResourceData, UpdateResourcesData, + UpdateResourceData, } from "../generated/types.gen.js"; import { addJsonFileOptions, @@ -29,11 +29,11 @@ export function registerIntegrationsCommand( ): void { const integrations = program .command("integrations") - .description("Manage connected integrations"); + .description("Manage integrations"); addPaginationOptions( - integrations.command("list").description("List connected integrations"), - ).action(async (opts: { pageSize?: number; pageCursor?: string }) => { + integrations.command("list").description("List integrations"), + ).action(async (opts) => { await runSdk(getFlags, (api) => listConnectedIntegrations({ client: api.client, @@ -44,7 +44,7 @@ export function registerIntegrationsCommand( integrations .command("get") - .description("Get a connected integration by ID") + .description("Get an integration by ID") .requiredOption("--id ", "Integration ID") .action(async (opts: { id: string }) => { await runSdk(getFlags, (api) => @@ -57,7 +57,7 @@ export function registerIntegrationsCommand( integrations .command("list-resource-kinds") - .description("List resource kind summaries for an integration") + .description("List resource kinds for an integration") .requiredOption("--id ", "Integration ID") .action(async (opts: { id: string }) => { await runSdk(getFlags, (api) => @@ -70,12 +70,12 @@ export function registerIntegrationsCommand( integrations .command("get-resource-kind") - .description("Get details for an integration resource kind") + .description("Get an integration resource kind by ID") .requiredOption("--id ", "Integration ID") .requiredOption("--resource-kind ", "Integration resource kind") .option( "--connection-id ", - "Filter by integration connection ID", + "Unique identifier of the integration connection", ) .action( async (opts: { @@ -86,15 +86,8 @@ export function registerIntegrationsCommand( await runSdk(getFlags, (api) => getResourceKindDetails({ client: api.client, - path: { - integrationId: opts.id, - resourceKind: opts.resourceKind, - }, - query: { - ...(opts.connectionId?.trim() - ? { connectionId: opts.connectionId.trim() } - : {}), - }, + path: { integrationId: opts.id, resourceKind: opts.resourceKind }, + query: { connectionId: opts.connectionId }, }), ); }, @@ -102,25 +95,28 @@ export function registerIntegrationsCommand( addPaginationOptions( integrations - .command("list-resources") + .command("list-resource-kind-resources") .description("List resources for an integration resource kind") .requiredOption("--id ", "Integration ID") .requiredOption("--resource-kind ", "Integration resource kind") .option( "--connection-id ", - "Filter by integration connection ID", + "Unique identifier of the integration connection", ) .option( "--has-description ", - "Filter resources with descriptions (true/false)", + "Filter resources that have a description. If omitted, this will return resources both with and without a description (true/false)", + (value) => parseOptionalBoolString(value, "has-description"), ) .option( "--has-owner ", - "Filter resources with owners (true/false)", + "Filter resources that have an owner. If omitted, this will return resources both with and without an owner (true/false)", + (value) => parseOptionalBoolString(value, "has-owner"), ) .option( "--is-in-scope ", - "Filter resources by scope status (true/false)", + "Filter resources that are in scope. If omitted, this will return resources both in and out of scope (true/false)", + (value) => parseOptionalBoolString(value, "is-in-scope"), ), ).action( async (opts: { @@ -129,40 +125,56 @@ export function registerIntegrationsCommand( pageSize?: number; pageCursor?: string; connectionId?: string; - hasDescription?: string; - hasOwner?: string; - isInScope?: string; + hasDescription?: boolean; + hasOwner?: boolean; + isInScope?: boolean; }) => { - const hasDescription = parseOptionalBoolString( - opts.hasDescription, - "has-description", - ); - const hasOwner = parseOptionalBoolString(opts.hasOwner, "has-owner"); - const isInScope = parseOptionalBoolString(opts.isInScope, "is-in-scope"); await runSdk(getFlags, (api) => listResources({ client: api.client, - path: { - integrationId: opts.id, - resourceKind: opts.resourceKind, - }, + path: { integrationId: opts.id, resourceKind: opts.resourceKind }, query: { ...paginationQuery(opts), - ...(opts.connectionId?.trim() - ? { connectionId: opts.connectionId.trim() } - : {}), - ...(hasDescription !== undefined ? { hasDescription } : {}), - ...(hasOwner !== undefined ? { hasOwner } : {}), - ...(isInScope !== undefined ? { isInScope } : {}), + connectionId: opts.connectionId, + hasDescription: opts.hasDescription, + hasOwner: opts.hasOwner, + isInScope: opts.isInScope, }, }), ); }, ); + addJsonFileOptions( + integrations + .command("update-resource-kind-resources") + .description("Update resources for an integration resource kind") + .requiredOption("--id ", "Integration ID") + .requiredOption("--resource-kind ", "Integration resource kind"), + ).action( + async (opts: { + id: string; + resourceKind: string; + json?: string; + file?: string; + }) => { + const body = (await readJSONPayload( + opts.json, + opts.file, + )) as UpdateResourcesData["body"]; + await runSdk(getFlags, (api) => + updateResources({ + client: api.client, + path: { integrationId: opts.id, resourceKind: opts.resourceKind }, + body, + }), + ); + }, + ); + integrations - .command("get-resource") - .description("Get an integration resource by ID") + .command("get-resource-kind-resource") + .description("Get an integration resource kind resource by ID") .requiredOption("--id ", "Integration ID") .requiredOption("--resource-kind ", "Integration resource kind") .requiredOption("--resource-id ", "Resource ID") @@ -187,8 +199,8 @@ export function registerIntegrationsCommand( addJsonFileOptions( integrations - .command("update-resource") - .description("Update metadata for a single integration resource") + .command("update-resource-kind-resource") + .description("Update an integration resource kind resource") .requiredOption("--id ", "Integration ID") .requiredOption("--resource-kind ", "Integration resource kind") .requiredOption("--resource-id ", "Resource ID"), @@ -217,34 +229,4 @@ export function registerIntegrationsCommand( ); }, ); - - addJsonFileOptions( - integrations - .command("update-resources") - .description("Update metadata for multiple integration resources") - .requiredOption("--id ", "Integration ID") - .requiredOption("--resource-kind ", "Integration resource kind"), - ).action( - async (opts: { - id: string; - resourceKind: string; - json?: string; - file?: string; - }) => { - const body = (await readJSONPayload( - opts.json, - opts.file, - )) as UpdateResourcesData["body"]; - await runSdk(getFlags, (api) => - updateResources({ - client: api.client, - path: { - integrationId: opts.id, - resourceKind: opts.resourceKind, - }, - body, - }), - ); - }, - ); } diff --git a/src/commands/issues.ts b/src/commands/issues.ts index f25e217..a9fc796 100644 --- a/src/commands/issues.ts +++ b/src/commands/issues.ts @@ -1,5 +1,5 @@ import type { Command } from "commander"; -import { list as listIssues, getIssue } from "../generated/sdk.gen.js"; +import { list, getIssue } from "../generated/sdk.gen.js"; import type { ListData } from "../generated/types.gen.js"; import { addPaginationOptions, @@ -14,9 +14,7 @@ export function registerIssuesCommand( program: Command, getFlags: GetFlags, ): void { - const issues = program - .command("issues") - .description("Manage issues"); + const issues = program.command("issues").description("Manage issues"); addPaginationOptions( issues @@ -24,63 +22,63 @@ export function registerIssuesCommand( .description("List issues") .option( "--search ", - "Search issue titles and descriptions", + "Full-text search across issue title and description", ) .option( "--readable-issue-id-matches-any ", - "Filter by readable issue ID (repeatable)", + "Filter to issues matching any of the provided readable issue IDs (repeatable)", collectString, [] as string[], ) .option( "--status-matches-any ", - "Filter by status: NOT_STARTED, IN_PROGRESS, CLOSED (repeatable)", + "Filter to issues matching any of the provided statuses: NOT_STARTED, IN_PROGRESS, CLOSED (repeatable)", collectString, [] as string[], ) .option( "--severity-matches-any ", - "Filter by severity: CRITICAL, HIGH, MEDIUM, LOW, NO_SEVERITY (repeatable)", + "Filter to issues matching any of the provided severities: CRITICAL, HIGH, MEDIUM, LOW, NO_SEVERITY (repeatable)", collectString, [] as string[], ) .option( "--source-matches-any ", - "Filter by source: AUDIT, AUDIT_EXTERNAL, INCIDENT, EXTERNAL_PARTY, SELF_ASSESSMENT, OTHER (repeatable)", + "Filter to issues matching any of the provided sources: AUDIT, AUDIT_EXTERNAL, INCIDENT, EXTERNAL_PARTY, SELF_ASSESSMENT, OTHER (repeatable)", collectString, [] as string[], ) .option( "--type-matches-any ", - "Filter by issue type: AREA_OF_CONCERN, MAJOR_NONCONFORMITY, MINOR_NONCONFORMITY, OPP_FOR_IMPROVEMENT, EXCEPTION, PROCESS_FOR_IMPROVEMENT (repeatable)", + "Filter to issues matching any of the provided types: AREA_OF_CONCERN, MAJOR_NONCONFORMITY, MINOR_NONCONFORMITY, OPP_FOR_IMPROVEMENT, EXCEPTION, PROCESS_FOR_IMPROVEMENT (repeatable)", collectString, [] as string[], ) .option( "--owner-id-matches-any ", - "Filter by issue owner user ID (repeatable)", + "Filter to issues owned by any of the provided owner IDs (repeatable)", collectString, [] as string[], ) .option( "--template-matches-any