Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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 `<group> <verb>-<noun>`. 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 <slug>`); other path parameters keep their names (`--finding-id`). JSON bodies take `--json <json>` or `--file <path>`, and file downloads take `--output <path>`.
- 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 (`<status>`, `<id>`, `<timestamp>`), not `<value>`.
- Use the same placeholder for common inputs: `<id>` for IDs, `<timestamp>` for date-times, `<date>` for date-only values, `<bool>` for true/false, `<query>` for search terms, `<json>` for inline JSON, `<path>` for local files, and `<text>` for other free text.
- Mark repeatable flags `(repeatable)`. Describe timestamps as “ISO 8601 timestamp”; avoid repeated format examples.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
Comment thread
dennis-chen-vanta marked this conversation as resolved.
| Groups | `vanta groups` |
| Frameworks | `vanta frameworks` |
| Users | `vanta users` |
Expand Down
2 changes: 1 addition & 1 deletion skills/suggest-mitigating-controls/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
50 changes: 2 additions & 48 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down
10 changes: 2 additions & 8 deletions src/commands/business-units.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -18,14 +17,9 @@ export function registerBusinessUnitsCommand(

addPaginationOptions(
businessUnits.command("list").description("List business units"),
).action(async (opts: NonNullable<ListBusinessUnitsData["query"]>) => {
).action(async (opts) => {
await runSdk(getFlags, (api) =>
listBusinessUnits({
client: api.client,
query: {
...paginationQuery(opts),
},
}),
listBusinessUnits({ client: api.client, query: paginationQuery(opts) }),
);
});

Expand Down
29 changes: 17 additions & 12 deletions src/commands/contracts.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Command } from "commander";
import {
deleteContract,
getContract,
listContracts,
getContract,
deleteContract,
uploadContract,
} from "../generated/sdk.gen.js";
import {
Expand Down Expand Up @@ -52,33 +52,38 @@ export function registerContractsCommand(
contracts
.command("upload")
.description("Upload a contract")
.requiredOption("--file <path>", "Path to contract file to upload")
.requiredOption(
"--file <path>",
"Path to file to upload: the contract file to upload. Only PDF files are accepted",
)
.option(
"--executed-date <date>",
"ISO 8601 date when the contract was executed",
"ISO 8601 date indicating when the contract was executed",
)
.option(
"--account-id <id>",
"Customer trust account ID to associate with the contract",
"ID of the customer trust account to associate with this contract",
)
.option(
"--external-id <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,
},
}),
);
Expand Down
Loading
Loading