Expose commitment CRUD on MCP, CLI, n8n

Sync GraphQL commitment group and item operations
to the remaining API surfaces so automation can
manage compliance portal commitments end to end.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-07-20 19:25:29 +02:00
parent e27a830d76
commit 2db37660d3
24 changed files with 3588 additions and 0 deletions

318
e2e/mcp/commitment_test.go Normal file
View File

@@ -0,0 +1,318 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
type commitmentGroup struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Rank int `json:"rank"`
}
type commitment struct {
ID string `json:"id"`
GroupID string `json:"group_id"`
Icon string `json:"icon"`
Eyebrow string `json:"eyebrow"`
Title string `json:"title"`
Description string `json:"description"`
Rank int `json:"rank"`
}
func mcpTrustCenterID(t *testing.T, mc *testutil.MCPClient, orgID string) string {
t.Helper()
var getResult struct {
TrustCenter struct {
ID string `json:"id"`
} `json:"trust_center"`
}
mc.CallToolInto("getTrustCenter", map[string]any{
"organization_id": orgID,
}, &getResult)
require.NotEmpty(t, getResult.TrustCenter.ID)
return getResult.TrustCenter.ID
}
func TestMCP_AddCommitmentGroup(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
tcID := mcpTrustCenterID(t, mc, owner.GetOrganizationID().String())
var result struct {
CommitmentGroup commitmentGroup `json:"commitment_group"`
}
mc.CallToolInto("addCommitmentGroup", map[string]any{
"trust_center_id": tcID,
"title": "Security Practices",
"description": "How we protect customer data",
}, &result)
assert.NotEmpty(t, result.CommitmentGroup.ID)
assert.Equal(t, "Security Practices", result.CommitmentGroup.Title)
assert.Equal(t, "How we protect customer data", result.CommitmentGroup.Description)
}
func TestMCP_UpdateCommitmentGroup(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
tcID := mcpTrustCenterID(t, mc, owner.GetOrganizationID().String())
var addResult struct {
CommitmentGroup commitmentGroup `json:"commitment_group"`
}
mc.CallToolInto("addCommitmentGroup", map[string]any{
"trust_center_id": tcID,
"title": "Original Group",
"description": "Original description",
}, &addResult)
require.NotEmpty(t, addResult.CommitmentGroup.ID)
var updateResult struct {
CommitmentGroup commitmentGroup `json:"commitment_group"`
}
mc.CallToolInto("updateCommitmentGroup", map[string]any{
"id": addResult.CommitmentGroup.ID,
"title": "Updated Group",
}, &updateResult)
assert.Equal(t, addResult.CommitmentGroup.ID, updateResult.CommitmentGroup.ID)
assert.Equal(t, "Updated Group", updateResult.CommitmentGroup.Title)
}
func TestMCP_DeleteCommitmentGroup(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
tcID := mcpTrustCenterID(t, mc, owner.GetOrganizationID().String())
var addResult struct {
CommitmentGroup commitmentGroup `json:"commitment_group"`
}
mc.CallToolInto("addCommitmentGroup", map[string]any{
"trust_center_id": tcID,
"title": "Group to delete",
"description": "Temporary",
}, &addResult)
require.NotEmpty(t, addResult.CommitmentGroup.ID)
var deleteResult struct {
DeletedCommitmentGroupID string `json:"deleted_commitment_group_id"`
}
mc.CallToolInto("deleteCommitmentGroup", map[string]any{
"id": addResult.CommitmentGroup.ID,
}, &deleteResult)
assert.Equal(t, addResult.CommitmentGroup.ID, deleteResult.DeletedCommitmentGroupID)
}
func TestMCP_ListCommitmentGroups(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
tcID := mcpTrustCenterID(t, mc, owner.GetOrganizationID().String())
for range 2 {
var result struct {
CommitmentGroup commitmentGroup `json:"commitment_group"`
}
mc.CallToolInto("addCommitmentGroup", map[string]any{
"trust_center_id": tcID,
"title": factory.SafeName("Group"),
"description": factory.SafeName("Desc"),
}, &result)
require.NotEmpty(t, result.CommitmentGroup.ID)
}
var listResult struct {
CommitmentGroups []commitmentGroup `json:"commitment_groups"`
}
mc.CallToolInto("listCommitmentGroups", map[string]any{
"trust_center_id": tcID,
}, &listResult)
assert.GreaterOrEqual(t, len(listResult.CommitmentGroups), 2)
}
func TestMCP_AddCommitment(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
tcID := mcpTrustCenterID(t, mc, owner.GetOrganizationID().String())
var groupResult struct {
CommitmentGroup commitmentGroup `json:"commitment_group"`
}
mc.CallToolInto("addCommitmentGroup", map[string]any{
"trust_center_id": tcID,
"title": "Encryption",
"description": "Encryption commitments",
}, &groupResult)
require.NotEmpty(t, groupResult.CommitmentGroup.ID)
var result struct {
Commitment commitment `json:"commitment"`
}
mc.CallToolInto("addCommitment", map[string]any{
"group_id": groupResult.CommitmentGroup.ID,
"icon": "LOCK_KEY",
"eyebrow": "Data at rest",
"title": "AES-256 encryption",
"description": "All customer data is encrypted at rest",
}, &result)
assert.NotEmpty(t, result.Commitment.ID)
assert.Equal(t, groupResult.CommitmentGroup.ID, result.Commitment.GroupID)
assert.Equal(t, "LOCK_KEY", result.Commitment.Icon)
assert.Equal(t, "AES-256 encryption", result.Commitment.Title)
}
func TestMCP_UpdateCommitment(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
tcID := mcpTrustCenterID(t, mc, owner.GetOrganizationID().String())
var groupResult struct {
CommitmentGroup commitmentGroup `json:"commitment_group"`
}
mc.CallToolInto("addCommitmentGroup", map[string]any{
"trust_center_id": tcID,
"title": "Access control",
"description": "Access commitments",
}, &groupResult)
require.NotEmpty(t, groupResult.CommitmentGroup.ID)
var addResult struct {
Commitment commitment `json:"commitment"`
}
mc.CallToolInto("addCommitment", map[string]any{
"group_id": groupResult.CommitmentGroup.ID,
"icon": "KEY",
"eyebrow": "SSO",
"title": "Original title",
"description": "Original description",
}, &addResult)
require.NotEmpty(t, addResult.Commitment.ID)
var updateResult struct {
Commitment commitment `json:"commitment"`
}
mc.CallToolInto("updateCommitment", map[string]any{
"id": addResult.Commitment.ID,
"title": "Updated title",
"icon": "FINGERPRINT",
}, &updateResult)
assert.Equal(t, addResult.Commitment.ID, updateResult.Commitment.ID)
assert.Equal(t, "Updated title", updateResult.Commitment.Title)
assert.Equal(t, "FINGERPRINT", updateResult.Commitment.Icon)
}
func TestMCP_DeleteCommitment(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
tcID := mcpTrustCenterID(t, mc, owner.GetOrganizationID().String())
var groupResult struct {
CommitmentGroup commitmentGroup `json:"commitment_group"`
}
mc.CallToolInto("addCommitmentGroup", map[string]any{
"trust_center_id": tcID,
"title": "Temporary group",
"description": "For delete test",
}, &groupResult)
require.NotEmpty(t, groupResult.CommitmentGroup.ID)
var addResult struct {
Commitment commitment `json:"commitment"`
}
mc.CallToolInto("addCommitment", map[string]any{
"group_id": groupResult.CommitmentGroup.ID,
"icon": "SHIELD_CHECK",
"eyebrow": "Compliance",
"title": "Commitment to delete",
"description": "Temporary",
}, &addResult)
require.NotEmpty(t, addResult.Commitment.ID)
var deleteResult struct {
DeletedCommitmentID string `json:"deleted_commitment_id"`
}
mc.CallToolInto("deleteCommitment", map[string]any{
"id": addResult.Commitment.ID,
}, &deleteResult)
assert.Equal(t, addResult.Commitment.ID, deleteResult.DeletedCommitmentID)
}
func TestMCP_ListCommitments(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
tcID := mcpTrustCenterID(t, mc, owner.GetOrganizationID().String())
var groupResult struct {
CommitmentGroup commitmentGroup `json:"commitment_group"`
}
mc.CallToolInto("addCommitmentGroup", map[string]any{
"trust_center_id": tcID,
"title": "List group",
"description": "For list test",
}, &groupResult)
require.NotEmpty(t, groupResult.CommitmentGroup.ID)
for range 2 {
var result struct {
Commitment commitment `json:"commitment"`
}
mc.CallToolInto("addCommitment", map[string]any{
"group_id": groupResult.CommitmentGroup.ID,
"icon": "LOCK",
"eyebrow": factory.SafeName("Eye"),
"title": factory.SafeName("Title"),
"description": factory.SafeName("Desc"),
}, &result)
require.NotEmpty(t, result.Commitment.ID)
}
var listResult struct {
Commitments []commitment `json:"commitments"`
}
mc.CallToolInto("listCommitments", map[string]any{
"group_id": groupResult.CommitmentGroup.ID,
}, &listResult)
assert.GreaterOrEqual(t, len(listResult.Commitments), 2)
}

View File

@@ -0,0 +1,161 @@
// Copyright (c) 2025-2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
const commitmentIconOptions = [
{ name: 'Lock Key', value: 'LOCK_KEY' },
{ name: 'Eye Slash', value: 'EYE_SLASH' },
{ name: 'Fingerprint', value: 'FINGERPRINT' },
{ name: 'Shield Warning', value: 'SHIELD_WARNING' },
{ name: 'Shield Check', value: 'SHIELD_CHECK' },
{ name: 'Siren', value: 'SIREN' },
{ name: 'Key', value: 'KEY' },
{ name: 'Lock', value: 'LOCK' },
{ name: 'Cloud', value: 'CLOUD' },
{ name: 'Database', value: 'DATABASE' },
{ name: 'Globe', value: 'GLOBE' },
{ name: 'Eye', value: 'EYE' },
{ name: 'Users', value: 'USERS' },
{ name: 'Certificate', value: 'CERTIFICATE' },
{ name: 'Gavel', value: 'GAVEL' },
{ name: 'Heartbeat', value: 'HEARTBEAT' },
{ name: 'Bell', value: 'BELL' },
{ name: 'Bug', value: 'BUG' },
{ name: 'Code', value: 'CODE' },
{ name: 'Server', value: 'SERVER' },
];
export const description: INodeProperties[] = [
{
displayName: 'Commitment Group ID',
name: 'groupId',
type: 'string',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['createCommitment'],
},
},
default: '',
description: 'The ID of the commitment group',
required: true,
},
{
displayName: 'Icon',
name: 'icon',
type: 'options',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['createCommitment'],
},
},
options: commitmentIconOptions,
default: 'LOCK_KEY',
description: 'The icon of the commitment',
required: true,
},
{
displayName: 'Eyebrow',
name: 'eyebrow',
type: 'string',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['createCommitment'],
},
},
default: '',
description: 'The eyebrow text of the commitment',
required: true,
},
{
displayName: 'Title',
name: 'title',
type: 'string',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['createCommitment'],
},
},
default: '',
description: 'The title of the commitment',
required: true,
},
{
displayName: 'Description',
name: 'description',
type: 'string',
typeOptions: {
rows: 4,
},
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['createCommitment'],
},
},
default: '',
description: 'The description of the commitment',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const groupId = this.getNodeParameter('groupId', itemIndex) as string;
const icon = this.getNodeParameter('icon', itemIndex) as string;
const eyebrow = this.getNodeParameter('eyebrow', itemIndex) as string;
const title = this.getNodeParameter('title', itemIndex) as string;
const description = this.getNodeParameter('description', itemIndex) as string;
const query = `
mutation CreateCompliancePortalCommitment($input: CreateCompliancePortalCommitmentInput!) {
createCompliancePortalCommitment(input: $input) {
compliancePortalCommitmentEdge {
node {
id
icon
eyebrow
title
description
rank
createdAt
updatedAt
}
}
}
}
`;
const responseData = await proboApiRequest.call(this, query, {
input: { groupId, icon, eyebrow, title, description },
});
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,105 @@
// Copyright (c) 2025-2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Trust Center ID',
name: 'trustCenterId',
type: 'string',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['createCommitmentGroup'],
},
},
default: '',
description: 'The ID of the trust center',
required: true,
},
{
displayName: 'Title',
name: 'title',
type: 'string',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['createCommitmentGroup'],
},
},
default: '',
description: 'The title of the commitment group',
required: true,
},
{
displayName: 'Description',
name: 'description',
type: 'string',
typeOptions: {
rows: 4,
},
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['createCommitmentGroup'],
},
},
default: '',
description: 'The description of the commitment group',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const trustCenterId = this.getNodeParameter('trustCenterId', itemIndex) as string;
const title = this.getNodeParameter('title', itemIndex) as string;
const description = this.getNodeParameter('description', itemIndex) as string;
const query = `
mutation CreateCompliancePortalCommitmentGroup($input: CreateCompliancePortalCommitmentGroupInput!) {
createCompliancePortalCommitmentGroup(input: $input) {
compliancePortalCommitmentGroupEdge {
node {
id
title
description
rank
createdAt
updatedAt
}
}
}
}
`;
const responseData = await proboApiRequest.call(this, query, {
input: { trustCenterId, title, description },
});
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,61 @@
// Copyright (c) 2025-2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Commitment ID',
name: 'commitmentId',
type: 'string',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['deleteCommitment'],
},
},
default: '',
description: 'The ID of the commitment to delete',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const commitmentId = this.getNodeParameter('commitmentId', itemIndex) as string;
const query = `
mutation DeleteCompliancePortalCommitment($input: DeleteCompliancePortalCommitmentInput!) {
deleteCompliancePortalCommitment(input: $input) {
deletedCompliancePortalCommitmentId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { id: commitmentId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,61 @@
// Copyright (c) 2025-2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Commitment Group ID',
name: 'commitmentGroupId',
type: 'string',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['deleteCommitmentGroup'],
},
},
default: '',
description: 'The ID of the commitment group to delete',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const commitmentGroupId = this.getNodeParameter('commitmentGroupId', itemIndex) as string;
const query = `
mutation DeleteCompliancePortalCommitmentGroup($input: DeleteCompliancePortalCommitmentGroupInput!) {
deleteCompliancePortalCommitmentGroup(input: $input) {
deletedCompliancePortalCommitmentGroupId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { id: commitmentGroupId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,124 @@
// Copyright (c) 2025-2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
import { proboApiRequestAllItems } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['getAllCommitmentGroups'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['getAllCommitmentGroups'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['getAllCommitmentGroups'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
},
default: 50,
description: 'Max number of results to return',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean;
const limit = this.getNodeParameter('limit', itemIndex, 50) as number;
const query = `
query GetCompliancePortalCommitmentGroups($organizationId: ID!, $first: Int, $after: CursorKey) {
node(id: $organizationId) {
... on Organization {
trustCenter {
commitmentGroups(first: $first, after: $after) {
edges {
node {
id
title
description
rank
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
}
`;
const commitmentGroups = await proboApiRequestAllItems.call(
this,
query,
{ organizationId },
(response) => {
const data = response?.data as IDataObject | undefined;
const node = data?.node as IDataObject | undefined;
const trustCenter = node?.trustCenter as IDataObject | undefined;
return trustCenter?.commitmentGroups as IDataObject | undefined;
},
returnAll,
limit,
);
return {
json: { commitmentGroups },
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,123 @@
// Copyright (c) 2025-2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
import { proboApiRequestAllItems } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Commitment Group ID',
name: 'groupId',
type: 'string',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['getAllCommitments'],
},
},
default: '',
description: 'The ID of the commitment group',
required: true,
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['getAllCommitments'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['getAllCommitments'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
},
default: 50,
description: 'Max number of results to return',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const groupId = this.getNodeParameter('groupId', itemIndex) as string;
const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean;
const limit = this.getNodeParameter('limit', itemIndex, 50) as number;
const query = `
query GetCompliancePortalCommitments($groupId: ID!, $first: Int, $after: CursorKey) {
node(id: $groupId) {
... on CompliancePortalCommitmentGroup {
commitments(first: $first, after: $after) {
edges {
node {
id
icon
eyebrow
title
description
rank
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`;
const commitments = await proboApiRequestAllItems.call(
this,
query,
{ groupId },
(response) => {
const data = response?.data as IDataObject | undefined;
const node = data?.node as IDataObject | undefined;
return node?.commitments as IDataObject | undefined;
},
returnAll,
limit,
);
return {
json: { commitments },
pairedItem: { item: itemIndex },
};
}

View File

@@ -28,6 +28,14 @@ import * as getAllFilesOp from './getAllFiles.operation';
import * as deleteFileOp from './deleteFile.operation';
import * as createExternalUrlOp from './createExternalUrl.operation';
import * as deleteExternalUrlOp from './deleteExternalUrl.operation';
import * as getAllCommitmentGroupsOp from './getAllCommitmentGroups.operation';
import * as createCommitmentGroupOp from './createCommitmentGroup.operation';
import * as updateCommitmentGroupOp from './updateCommitmentGroup.operation';
import * as deleteCommitmentGroupOp from './deleteCommitmentGroup.operation';
import * as getAllCommitmentsOp from './getAllCommitments.operation';
import * as createCommitmentOp from './createCommitment.operation';
import * as updateCommitmentOp from './updateCommitment.operation';
import * as deleteCommitmentOp from './deleteCommitment.operation';
export const description: INodeProperties[] = [
{
@@ -41,6 +49,18 @@ export const description: INodeProperties[] = [
},
},
options: [
{
name: 'Create Commitment',
value: 'createCommitment',
description: 'Create a new compliance portal commitment',
action: 'Create a compliance portal commitment',
},
{
name: 'Create Commitment Group',
value: 'createCommitmentGroup',
description: 'Create a new compliance portal commitment group',
action: 'Create a compliance portal commitment group',
},
{
name: 'Create External URL',
value: 'createExternalUrl',
@@ -53,6 +73,18 @@ export const description: INodeProperties[] = [
description: 'Create a new trust center reference',
action: 'Create a trust center reference',
},
{
name: 'Delete Commitment',
value: 'deleteCommitment',
description: 'Delete a compliance portal commitment',
action: 'Delete a compliance portal commitment',
},
{
name: 'Delete Commitment Group',
value: 'deleteCommitmentGroup',
description: 'Delete a compliance portal commitment group',
action: 'Delete a compliance portal commitment group',
},
{
name: 'Delete External URL',
value: 'deleteExternalUrl',
@@ -77,6 +109,18 @@ export const description: INodeProperties[] = [
description: 'Get trust center settings',
action: 'Get trust center settings',
},
{
name: 'Get Many Commitment Groups',
value: 'getAllCommitmentGroups',
description: 'Get many compliance portal commitment groups',
action: 'Get many compliance portal commitment groups',
},
{
name: 'Get Many Commitments',
value: 'getAllCommitments',
description: 'Get many compliance portal commitments',
action: 'Get many compliance portal commitments',
},
{
name: 'Get Many Files',
value: 'getAllFiles',
@@ -95,6 +139,18 @@ export const description: INodeProperties[] = [
description: 'Update trust center settings',
action: 'Update trust center settings',
},
{
name: 'Update Commitment',
value: 'updateCommitment',
description: 'Update a compliance portal commitment',
action: 'Update a compliance portal commitment',
},
{
name: 'Update Commitment Group',
value: 'updateCommitmentGroup',
description: 'Update a compliance portal commitment group',
action: 'Update a compliance portal commitment group',
},
],
default: 'get',
},
@@ -107,6 +163,14 @@ export const description: INodeProperties[] = [
...deleteFileOp.description,
...createExternalUrlOp.description,
...deleteExternalUrlOp.description,
...getAllCommitmentGroupsOp.description,
...createCommitmentGroupOp.description,
...updateCommitmentGroupOp.description,
...deleteCommitmentGroupOp.description,
...getAllCommitmentsOp.description,
...createCommitmentOp.description,
...updateCommitmentOp.description,
...deleteCommitmentOp.description,
];
export {
@@ -119,4 +183,12 @@ export {
deleteFileOp as deleteFile,
createExternalUrlOp as createExternalUrl,
deleteExternalUrlOp as deleteExternalUrl,
getAllCommitmentGroupsOp as getAllCommitmentGroups,
createCommitmentGroupOp as createCommitmentGroup,
updateCommitmentGroupOp as updateCommitmentGroup,
deleteCommitmentGroupOp as deleteCommitmentGroup,
getAllCommitmentsOp as getAllCommitments,
createCommitmentOp as createCommitment,
updateCommitmentOp as updateCommitment,
deleteCommitmentOp as deleteCommitment,
};

View File

@@ -0,0 +1,175 @@
// Copyright (c) 2025-2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
const commitmentIconOptions = [
{ name: '(Unchanged)', value: '' },
{ name: 'Lock Key', value: 'LOCK_KEY' },
{ name: 'Eye Slash', value: 'EYE_SLASH' },
{ name: 'Fingerprint', value: 'FINGERPRINT' },
{ name: 'Shield Warning', value: 'SHIELD_WARNING' },
{ name: 'Shield Check', value: 'SHIELD_CHECK' },
{ name: 'Siren', value: 'SIREN' },
{ name: 'Key', value: 'KEY' },
{ name: 'Lock', value: 'LOCK' },
{ name: 'Cloud', value: 'CLOUD' },
{ name: 'Database', value: 'DATABASE' },
{ name: 'Globe', value: 'GLOBE' },
{ name: 'Eye', value: 'EYE' },
{ name: 'Users', value: 'USERS' },
{ name: 'Certificate', value: 'CERTIFICATE' },
{ name: 'Gavel', value: 'GAVEL' },
{ name: 'Heartbeat', value: 'HEARTBEAT' },
{ name: 'Bell', value: 'BELL' },
{ name: 'Bug', value: 'BUG' },
{ name: 'Code', value: 'CODE' },
{ name: 'Server', value: 'SERVER' },
];
export const description: INodeProperties[] = [
{
displayName: 'Commitment ID',
name: 'commitmentId',
type: 'string',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['updateCommitment'],
},
},
default: '',
description: 'The ID of the commitment to update',
required: true,
},
{
displayName: 'Icon',
name: 'icon',
type: 'options',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['updateCommitment'],
},
},
options: commitmentIconOptions,
default: '',
description: 'The icon of the commitment',
},
{
displayName: 'Eyebrow',
name: 'eyebrow',
type: 'string',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['updateCommitment'],
},
},
default: '',
description: 'The eyebrow text of the commitment',
},
{
displayName: 'Title',
name: 'title',
type: 'string',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['updateCommitment'],
},
},
default: '',
description: 'The title of the commitment',
},
{
displayName: 'Description',
name: 'description',
type: 'string',
typeOptions: {
rows: 4,
},
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['updateCommitment'],
},
},
default: '',
description: 'The description of the commitment',
},
{
displayName: 'Rank',
name: 'rank',
type: 'string',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['updateCommitment'],
},
},
default: '',
description: 'The rank of the commitment for ordering',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const commitmentId = this.getNodeParameter('commitmentId', itemIndex) as string;
const icon = this.getNodeParameter('icon', itemIndex, '') as string;
const eyebrow = this.getNodeParameter('eyebrow', itemIndex, '') as string;
const title = this.getNodeParameter('title', itemIndex, '') as string;
const description = this.getNodeParameter('description', itemIndex, '') as string;
const rank = this.getNodeParameter('rank', itemIndex, '') as string;
const query = `
mutation UpdateCompliancePortalCommitment($input: UpdateCompliancePortalCommitmentInput!) {
updateCompliancePortalCommitment(input: $input) {
compliancePortalCommitment {
id
icon
eyebrow
title
description
rank
createdAt
updatedAt
}
}
}
`;
const input: Record<string, unknown> = { id: commitmentId };
if (icon) input.icon = icon;
if (eyebrow) input.eyebrow = eyebrow;
if (title) input.title = title;
if (description) input.description = description;
if (rank) input.rank = Number(rank);
const responseData = await proboApiRequest.call(this, query, { input });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,118 @@
// Copyright (c) 2025-2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Commitment Group ID',
name: 'commitmentGroupId',
type: 'string',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['updateCommitmentGroup'],
},
},
default: '',
description: 'The ID of the commitment group to update',
required: true,
},
{
displayName: 'Title',
name: 'title',
type: 'string',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['updateCommitmentGroup'],
},
},
default: '',
description: 'The title of the commitment group',
},
{
displayName: 'Description',
name: 'description',
type: 'string',
typeOptions: {
rows: 4,
},
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['updateCommitmentGroup'],
},
},
default: '',
description: 'The description of the commitment group',
},
{
displayName: 'Rank',
name: 'rank',
type: 'string',
displayOptions: {
show: {
resource: ['trustCenter'],
operation: ['updateCommitmentGroup'],
},
},
default: '',
description: 'The rank of the commitment group for ordering',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const commitmentGroupId = this.getNodeParameter('commitmentGroupId', itemIndex) as string;
const title = this.getNodeParameter('title', itemIndex, '') as string;
const description = this.getNodeParameter('description', itemIndex, '') as string;
const rank = this.getNodeParameter('rank', itemIndex, '') as string;
const query = `
mutation UpdateCompliancePortalCommitmentGroup($input: UpdateCompliancePortalCommitmentGroupInput!) {
updateCompliancePortalCommitmentGroup(input: $input) {
compliancePortalCommitmentGroup {
id
title
description
rank
createdAt
updatedAt
}
}
}
`;
const input: Record<string, unknown> = { id: commitmentGroupId };
if (title) input.title = title;
if (description) input.description = description;
if (rank) input.rank = Number(rank);
const responseData = await proboApiRequest.call(this, query, { input });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,45 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package commitment
import (
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cmd/cmdutil"
"go.probo.inc/probo/pkg/cmd/trust-center/commitment/create"
"go.probo.inc/probo/pkg/cmd/trust-center/commitment/delete"
"go.probo.inc/probo/pkg/cmd/trust-center/commitment/list"
"go.probo.inc/probo/pkg/cmd/trust-center/commitment/update"
)
func NewCmdCommitment(f *cmdutil.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "commitment <command>",
Short: "Manage compliance portal commitments",
Aliases: []string{"cmt"},
}
cmd.AddCommand(list.NewCmdList(f))
cmd.AddCommand(create.NewCmdCreate(f))
cmd.AddCommand(update.NewCmdUpdate(f))
cmd.AddCommand(delete.NewCmdDelete(f))
return cmd
}

View File

@@ -0,0 +1,246 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package create
import (
"encoding/json"
"fmt"
"github.com/charmbracelet/huh"
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil"
)
var validIcons = []string{
"LOCK_KEY",
"EYE_SLASH",
"FINGERPRINT",
"SHIELD_WARNING",
"SHIELD_CHECK",
"SIREN",
"KEY",
"LOCK",
"CLOUD",
"DATABASE",
"GLOBE",
"EYE",
"USERS",
"CERTIFICATE",
"GAVEL",
"HEARTBEAT",
"BELL",
"BUG",
"CODE",
"SERVER",
}
const createMutation = `
mutation($input: CreateCompliancePortalCommitmentInput!) {
createCompliancePortalCommitment(input: $input) {
compliancePortalCommitmentEdge {
node {
id
icon
eyebrow
title
description
rank
}
}
}
}
`
type createResponse struct {
CreateCompliancePortalCommitment struct {
CompliancePortalCommitmentEdge struct {
Node struct {
ID string `json:"id"`
Icon string `json:"icon"`
Eyebrow string `json:"eyebrow"`
Title string `json:"title"`
Description string `json:"description"`
Rank int `json:"rank"`
} `json:"node"`
} `json:"compliancePortalCommitmentEdge"`
} `json:"createCompliancePortalCommitment"`
}
func NewCmdCreate(f *cmdutil.Factory) *cobra.Command {
var (
flagGroup string
flagIcon string
flagEyebrow string
flagTitle string
flagDescription string
)
cmd := &cobra.Command{
Use: "create",
Short: "Create a compliance portal commitment",
Example: ` # Create a commitment interactively
prb trust-center commitment create --group <group-id>
# Create a commitment non-interactively
prb trust-center cmt create --group <group-id> --icon SHIELD_CHECK --eyebrow "Security" --title "Encryption" --description "Data encrypted at rest"`,
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := f.Config()
if err != nil {
return err
}
host, hc, err := cfg.DefaultHost()
if err != nil {
return err
}
client := api.NewClient(
host,
hc.Token,
"/api/console/v1/graphql",
cfg.HTTPTimeoutDuration(),
cmdutil.TokenRefreshOption(cfg, host, hc),
)
if f.IOStreams.IsInteractive() {
if flagGroup == "" {
err := huh.NewInput().
Title("Commitment group ID").
Value(&flagGroup).
Run()
if err != nil {
return err
}
}
if flagIcon == "" {
iconOptions := make([]huh.Option[string], 0, len(validIcons))
for _, icon := range validIcons {
iconOptions = append(iconOptions, huh.NewOption(icon, icon))
}
err := huh.NewSelect[string]().
Title("Icon").
Options(iconOptions...).
Value(&flagIcon).
Run()
if err != nil {
return err
}
}
if flagEyebrow == "" {
err := huh.NewInput().
Title("Eyebrow").
Value(&flagEyebrow).
Run()
if err != nil {
return err
}
}
if flagTitle == "" {
err := huh.NewInput().
Title("Title").
Value(&flagTitle).
Run()
if err != nil {
return err
}
}
if flagDescription == "" {
err := huh.NewText().
Title("Description").
Value(&flagDescription).
Run()
if err != nil {
return err
}
}
}
if flagGroup == "" {
return fmt.Errorf("group is required; pass --group or run interactively")
}
if flagIcon == "" {
return fmt.Errorf("icon is required; pass --icon or run interactively")
}
if err := cmdutil.ValidateEnum("icon", flagIcon, validIcons); err != nil {
return err
}
if flagEyebrow == "" {
return fmt.Errorf("eyebrow is required; pass --eyebrow or run interactively")
}
if flagTitle == "" {
return fmt.Errorf("title is required; pass --title or run interactively")
}
if flagDescription == "" {
return fmt.Errorf("description is required; pass --description or run interactively")
}
data, err := client.Do(
createMutation,
map[string]any{
"input": map[string]any{
"groupId": flagGroup,
"icon": flagIcon,
"eyebrow": flagEyebrow,
"title": flagTitle,
"description": flagDescription,
},
},
)
if err != nil {
return err
}
var resp createResponse
if err := json.Unmarshal(data, &resp); err != nil {
return fmt.Errorf("cannot parse response: %w", err)
}
c := resp.CreateCompliancePortalCommitment.CompliancePortalCommitmentEdge.Node
_, _ = fmt.Fprintf(
f.IOStreams.Out,
"Created commitment %s (%s)\n",
c.ID,
c.Title,
)
return nil
},
}
cmd.Flags().StringVar(&flagGroup, "group", "", "Commitment group ID (required)")
cmd.Flags().StringVar(&flagIcon, "icon", "", "Commitment icon (required)")
cmd.Flags().StringVar(&flagEyebrow, "eyebrow", "", "Commitment eyebrow (required)")
cmd.Flags().StringVar(&flagTitle, "title", "", "Commitment title (required)")
cmd.Flags().StringVar(&flagDescription, "description", "", "Commitment description (required)")
return cmd
}

View File

@@ -0,0 +1,111 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package delete
import (
"fmt"
"github.com/charmbracelet/huh"
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil"
)
const deleteMutation = `
mutation($input: DeleteCompliancePortalCommitmentInput!) {
deleteCompliancePortalCommitment(input: $input) {
deletedCompliancePortalCommitmentId
}
}
`
func NewCmdDelete(f *cmdutil.Factory) *cobra.Command {
var flagYes bool
cmd := &cobra.Command{
Use: "delete <id>",
Short: "Delete a compliance portal commitment",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if !flagYes {
if !f.IOStreams.IsInteractive() {
return fmt.Errorf("cannot delete commitment: confirmation required, use --yes to confirm")
}
var confirmed bool
err := huh.NewConfirm().
Title(fmt.Sprintf("Delete commitment %s?", args[0])).
Value(&confirmed).
Run()
if err != nil {
return err
}
if !confirmed {
return nil
}
}
cfg, err := f.Config()
if err != nil {
return err
}
host, hc, err := cfg.DefaultHost()
if err != nil {
return err
}
client := api.NewClient(
host,
hc.Token,
"/api/console/v1/graphql",
cfg.HTTPTimeoutDuration(),
cmdutil.TokenRefreshOption(cfg, host, hc),
)
_, err = client.Do(
deleteMutation,
map[string]any{
"input": map[string]any{
"id": args[0],
},
},
)
if err != nil {
return err
}
_, _ = fmt.Fprintf(
f.IOStreams.Out,
"Deleted commitment %s\n",
args[0],
)
return nil
},
}
cmd.Flags().BoolVarP(&flagYes, "yes", "y", false, "Skip confirmation prompt")
return cmd
}

View File

@@ -0,0 +1,206 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package list
import (
"encoding/json"
"fmt"
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil"
)
const listQuery = `
query($id: ID!, $first: Int, $after: CursorKey, $orderBy: CompliancePortalCommitmentOrder) {
node(id: $id) {
__typename
... on CompliancePortalCommitmentGroup {
commitments(first: $first, after: $after, orderBy: $orderBy) {
totalCount
edges {
node {
id
icon
eyebrow
title
description
rank
createdAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`
type commitment struct {
ID string `json:"id"`
Icon string `json:"icon"`
Eyebrow string `json:"eyebrow"`
Title string `json:"title"`
Description string `json:"description"`
Rank int `json:"rank"`
CreatedAt string `json:"createdAt"`
}
func NewCmdList(f *cmdutil.Factory) *cobra.Command {
var (
flagGroup string
flagLimit int
flagOrderBy string
flagOrderDir string
flagOutput *string
)
cmd := &cobra.Command{
Use: "list",
Short: "List compliance portal commitments",
Aliases: []string{"ls"},
Example: ` # List commitments in a group
prb trust-center commitment list --group <group-id>
# List commitments sorted by rank
prb trust-center cmt ls --group <group-id> --order-by RANK`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if err := cmdutil.ValidateOutputFlag(flagOutput); err != nil {
return err
}
if flagGroup == "" {
return fmt.Errorf("group is required; pass --group")
}
cfg, err := f.Config()
if err != nil {
return err
}
host, hc, err := cfg.DefaultHost()
if err != nil {
return err
}
client := api.NewClient(
host,
hc.Token,
"/api/console/v1/graphql",
cfg.HTTPTimeoutDuration(),
cmdutil.TokenRefreshOption(cfg, host, hc),
)
variables := map[string]any{
"id": flagGroup,
}
if flagOrderBy != "" {
if err := cmdutil.ValidateEnum("order-by", flagOrderBy, []string{"RANK", "CREATED_AT", "UPDATED_AT"}); err != nil {
return err
}
variables["orderBy"] = map[string]any{
"field": flagOrderBy,
"direction": flagOrderDir,
}
}
commitments, totalCount, err := api.Paginate(
client,
listQuery,
variables,
flagLimit,
func(data json.RawMessage) (*api.Connection[commitment], error) {
var resp struct {
Node *struct {
Typename string `json:"__typename"`
Commitments api.Connection[commitment] `json:"commitments"`
} `json:"node"`
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, err
}
if resp.Node == nil {
return nil, fmt.Errorf("commitment group %s not found", flagGroup)
}
if resp.Node.Typename != "CompliancePortalCommitmentGroup" {
return nil, fmt.Errorf("expected CompliancePortalCommitmentGroup node, got %s", resp.Node.Typename)
}
return &resp.Node.Commitments, nil
},
)
if err != nil {
return err
}
if *flagOutput == cmdutil.OutputJSON {
return cmdutil.PrintJSON(f.IOStreams.Out, commitments)
}
if len(commitments) == 0 {
_, _ = fmt.Fprintln(f.IOStreams.Out, "No commitments found.")
return nil
}
rows := make([][]string, 0, len(commitments))
for _, c := range commitments {
rows = append(rows, []string{
c.ID,
c.Icon,
c.Eyebrow,
c.Title,
fmt.Sprintf("%d", c.Rank),
})
}
t := cmdutil.NewTable("ID", "ICON", "EYEBROW", "TITLE", "RANK").Rows(rows...)
_, _ = fmt.Fprintln(f.IOStreams.Out, t)
if totalCount > len(commitments) {
_, _ = fmt.Fprintf(
f.IOStreams.ErrOut,
"\nShowing %d of %d commitments\n",
len(commitments),
totalCount,
)
}
return nil
},
}
cmd.Flags().StringVar(&flagGroup, "group", "", "Commitment group ID (required)")
cmd.Flags().IntVarP(&flagLimit, "limit", "L", 30, "Maximum number of commitments to list")
cmd.Flags().StringVar(&flagOrderBy, "order-by", "", "Order by field (RANK, CREATED_AT, UPDATED_AT)")
cmd.Flags().StringVar(&flagOrderDir, "order-direction", "DESC", "Sort direction (ASC, DESC)")
flagOutput = cmdutil.AddOutputFlag(cmd)
return cmd
}

View File

@@ -0,0 +1,184 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package update
import (
"encoding/json"
"fmt"
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil"
)
var validIcons = []string{
"LOCK_KEY",
"EYE_SLASH",
"FINGERPRINT",
"SHIELD_WARNING",
"SHIELD_CHECK",
"SIREN",
"KEY",
"LOCK",
"CLOUD",
"DATABASE",
"GLOBE",
"EYE",
"USERS",
"CERTIFICATE",
"GAVEL",
"HEARTBEAT",
"BELL",
"BUG",
"CODE",
"SERVER",
}
const updateMutation = `
mutation($input: UpdateCompliancePortalCommitmentInput!) {
updateCompliancePortalCommitment(input: $input) {
compliancePortalCommitment {
id
icon
eyebrow
title
description
rank
}
}
}
`
type updateResponse struct {
UpdateCompliancePortalCommitment struct {
CompliancePortalCommitment struct {
ID string `json:"id"`
Icon string `json:"icon"`
Eyebrow string `json:"eyebrow"`
Title string `json:"title"`
Description string `json:"description"`
Rank int `json:"rank"`
} `json:"compliancePortalCommitment"`
} `json:"updateCompliancePortalCommitment"`
}
func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command {
var (
flagIcon string
flagEyebrow string
flagTitle string
flagDescription string
flagRank int
)
cmd := &cobra.Command{
Use: "update <id>",
Short: "Update a compliance portal commitment",
Example: ` # Update a commitment title
prb trust-center commitment update <id> --title "Encryption at rest"
# Update icon and rank
prb trust-center cmt update <id> --icon SHIELD_CHECK --rank 1`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := f.Config()
if err != nil {
return err
}
host, hc, err := cfg.DefaultHost()
if err != nil {
return err
}
client := api.NewClient(
host,
hc.Token,
"/api/console/v1/graphql",
cfg.HTTPTimeoutDuration(),
cmdutil.TokenRefreshOption(cfg, host, hc),
)
input := map[string]any{
"id": args[0],
}
if cmd.Flags().Changed("icon") {
if err := cmdutil.ValidateEnum("icon", flagIcon, validIcons); err != nil {
return err
}
input["icon"] = flagIcon
}
if cmd.Flags().Changed("eyebrow") {
input["eyebrow"] = flagEyebrow
}
if cmd.Flags().Changed("title") {
input["title"] = flagTitle
}
if cmd.Flags().Changed("description") {
input["description"] = flagDescription
}
if cmd.Flags().Changed("rank") {
input["rank"] = flagRank
}
if len(input) == 1 {
return fmt.Errorf("at least one field must be specified for update")
}
data, err := client.Do(
updateMutation,
map[string]any{"input": input},
)
if err != nil {
return err
}
var resp updateResponse
if err := json.Unmarshal(data, &resp); err != nil {
return fmt.Errorf("cannot parse response: %w", err)
}
c := resp.UpdateCompliancePortalCommitment.CompliancePortalCommitment
_, _ = fmt.Fprintf(
f.IOStreams.Out,
"Updated commitment %s (%s)\n",
c.ID,
c.Title,
)
return nil
},
}
cmd.Flags().StringVar(&flagIcon, "icon", "", "Commitment icon")
cmd.Flags().StringVar(&flagEyebrow, "eyebrow", "", "Commitment eyebrow")
cmd.Flags().StringVar(&flagTitle, "title", "", "Commitment title")
cmd.Flags().StringVar(&flagDescription, "description", "", "Commitment description")
cmd.Flags().IntVar(&flagRank, "rank", 0, "Display rank")
return cmd
}

View File

@@ -0,0 +1,45 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package commitmentgroup
import (
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cmd/cmdutil"
"go.probo.inc/probo/pkg/cmd/trust-center/commitmentgroup/create"
"go.probo.inc/probo/pkg/cmd/trust-center/commitmentgroup/delete"
"go.probo.inc/probo/pkg/cmd/trust-center/commitmentgroup/list"
"go.probo.inc/probo/pkg/cmd/trust-center/commitmentgroup/update"
)
func NewCmdCommitmentGroup(f *cmdutil.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "commitment-group <command>",
Short: "Manage compliance portal commitment groups",
Aliases: []string{"cg"},
}
cmd.AddCommand(list.NewCmdList(f))
cmd.AddCommand(create.NewCmdCreate(f))
cmd.AddCommand(update.NewCmdUpdate(f))
cmd.AddCommand(delete.NewCmdDelete(f))
return cmd
}

View File

@@ -0,0 +1,216 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package create
import (
"encoding/json"
"fmt"
"github.com/charmbracelet/huh"
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil"
)
const trustCenterQuery = `
query($id: ID!) {
node(id: $id) {
__typename
... on Organization {
trustCenter {
id
}
}
}
}
`
const createMutation = `
mutation($input: CreateCompliancePortalCommitmentGroupInput!) {
createCompliancePortalCommitmentGroup(input: $input) {
compliancePortalCommitmentGroupEdge {
node {
id
title
description
rank
}
}
}
}
`
type trustCenterQueryResponse struct {
Node *struct {
Typename string `json:"__typename"`
TrustCenter *struct {
ID string `json:"id"`
} `json:"trustCenter"`
} `json:"node"`
}
type createResponse struct {
CreateCompliancePortalCommitmentGroup struct {
CompliancePortalCommitmentGroupEdge struct {
Node struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Rank int `json:"rank"`
} `json:"node"`
} `json:"compliancePortalCommitmentGroupEdge"`
} `json:"createCompliancePortalCommitmentGroup"`
}
func NewCmdCreate(f *cmdutil.Factory) *cobra.Command {
var (
flagOrg string
flagTitle string
flagDescription string
)
cmd := &cobra.Command{
Use: "create",
Short: "Create a compliance portal commitment group",
Example: ` # Create a commitment group interactively
prb trust-center commitment-group create
# Create a commitment group non-interactively
prb trust-center cg create --title "Security" --description "Our security commitments"`,
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := f.Config()
if err != nil {
return err
}
host, hc, err := cfg.DefaultHost()
if err != nil {
return err
}
client := api.NewClient(
host,
hc.Token,
"/api/console/v1/graphql",
cfg.HTTPTimeoutDuration(),
cmdutil.TokenRefreshOption(cfg, host, hc),
)
if flagOrg == "" {
flagOrg = hc.Organization
}
if flagOrg == "" {
return fmt.Errorf("organization is required; pass --org or set a default with 'prb auth login'")
}
data, err := client.Do(
trustCenterQuery,
map[string]any{"id": flagOrg},
)
if err != nil {
return err
}
var tcResp trustCenterQueryResponse
if err := json.Unmarshal(data, &tcResp); err != nil {
return fmt.Errorf("cannot parse response: %w", err)
}
if tcResp.Node == nil {
return fmt.Errorf("organization %s not found", flagOrg)
}
if tcResp.Node.Typename != "Organization" {
return fmt.Errorf("expected Organization node, got %s", tcResp.Node.Typename)
}
if tcResp.Node.TrustCenter == nil {
return fmt.Errorf("trust center not found for organization %s", flagOrg)
}
if f.IOStreams.IsInteractive() {
if flagTitle == "" {
err := huh.NewInput().
Title("Group title").
Value(&flagTitle).
Run()
if err != nil {
return err
}
}
if flagDescription == "" {
err := huh.NewText().
Title("Description").
Value(&flagDescription).
Run()
if err != nil {
return err
}
}
}
if flagTitle == "" {
return fmt.Errorf("title is required; pass --title or run interactively")
}
if flagDescription == "" {
return fmt.Errorf("description is required; pass --description or run interactively")
}
data, err = client.Do(
createMutation,
map[string]any{
"input": map[string]any{
"trustCenterId": tcResp.Node.TrustCenter.ID,
"title": flagTitle,
"description": flagDescription,
},
},
)
if err != nil {
return err
}
var resp createResponse
if err := json.Unmarshal(data, &resp); err != nil {
return fmt.Errorf("cannot parse response: %w", err)
}
g := resp.CreateCompliancePortalCommitmentGroup.CompliancePortalCommitmentGroupEdge.Node
_, _ = fmt.Fprintf(
f.IOStreams.Out,
"Created commitment group %s (%s)\n",
g.ID,
g.Title,
)
return nil
},
}
cmd.Flags().StringVar(&flagOrg, "org", "", "Organization ID")
cmd.Flags().StringVar(&flagTitle, "title", "", "Group title (required)")
cmd.Flags().StringVar(&flagDescription, "description", "", "Group description (required)")
return cmd
}

View File

@@ -0,0 +1,111 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package delete
import (
"fmt"
"github.com/charmbracelet/huh"
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil"
)
const deleteMutation = `
mutation($input: DeleteCompliancePortalCommitmentGroupInput!) {
deleteCompliancePortalCommitmentGroup(input: $input) {
deletedCompliancePortalCommitmentGroupId
}
}
`
func NewCmdDelete(f *cmdutil.Factory) *cobra.Command {
var flagYes bool
cmd := &cobra.Command{
Use: "delete <id>",
Short: "Delete a compliance portal commitment group",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if !flagYes {
if !f.IOStreams.IsInteractive() {
return fmt.Errorf("cannot delete commitment group: confirmation required, use --yes to confirm")
}
var confirmed bool
err := huh.NewConfirm().
Title(fmt.Sprintf("Delete commitment group %s?", args[0])).
Value(&confirmed).
Run()
if err != nil {
return err
}
if !confirmed {
return nil
}
}
cfg, err := f.Config()
if err != nil {
return err
}
host, hc, err := cfg.DefaultHost()
if err != nil {
return err
}
client := api.NewClient(
host,
hc.Token,
"/api/console/v1/graphql",
cfg.HTTPTimeoutDuration(),
cmdutil.TokenRefreshOption(cfg, host, hc),
)
_, err = client.Do(
deleteMutation,
map[string]any{
"input": map[string]any{
"id": args[0],
},
},
)
if err != nil {
return err
}
_, _ = fmt.Fprintf(
f.IOStreams.Out,
"Deleted commitment group %s\n",
args[0],
)
return nil
},
}
cmd.Flags().BoolVarP(&flagYes, "yes", "y", false, "Skip confirmation prompt")
return cmd
}

View File

@@ -0,0 +1,212 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package list
import (
"encoding/json"
"fmt"
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil"
)
const listQuery = `
query($id: ID!, $first: Int, $after: CursorKey, $orderBy: CompliancePortalCommitmentGroupOrder) {
node(id: $id) {
__typename
... on Organization {
trustCenter {
commitmentGroups(first: $first, after: $after, orderBy: $orderBy) {
totalCount
edges {
node {
id
title
description
rank
createdAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
}
`
type commitmentGroup struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Rank int `json:"rank"`
CreatedAt string `json:"createdAt"`
}
func NewCmdList(f *cmdutil.Factory) *cobra.Command {
var (
flagOrg string
flagLimit int
flagOrderBy string
flagOrderDir string
flagOutput *string
)
cmd := &cobra.Command{
Use: "list",
Short: "List compliance portal commitment groups",
Aliases: []string{"ls"},
Example: ` # List commitment groups in the default organization
prb trust-center commitment-group list
# List commitment groups sorted by rank
prb trust-center cg ls --order-by RANK`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if err := cmdutil.ValidateOutputFlag(flagOutput); err != nil {
return err
}
cfg, err := f.Config()
if err != nil {
return err
}
host, hc, err := cfg.DefaultHost()
if err != nil {
return err
}
client := api.NewClient(
host,
hc.Token,
"/api/console/v1/graphql",
cfg.HTTPTimeoutDuration(),
cmdutil.TokenRefreshOption(cfg, host, hc),
)
if flagOrg == "" {
flagOrg = hc.Organization
}
if flagOrg == "" {
return fmt.Errorf("organization is required; pass --org or set a default with 'prb auth login'")
}
variables := map[string]any{
"id": flagOrg,
}
if flagOrderBy != "" {
if err := cmdutil.ValidateEnum("order-by", flagOrderBy, []string{"RANK", "CREATED_AT", "UPDATED_AT"}); err != nil {
return err
}
variables["orderBy"] = map[string]any{
"field": flagOrderBy,
"direction": flagOrderDir,
}
}
groups, totalCount, err := api.Paginate(
client,
listQuery,
variables,
flagLimit,
func(data json.RawMessage) (*api.Connection[commitmentGroup], error) {
var resp struct {
Node *struct {
Typename string `json:"__typename"`
TrustCenter *struct {
CommitmentGroups api.Connection[commitmentGroup] `json:"commitmentGroups"`
} `json:"trustCenter"`
} `json:"node"`
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, err
}
if resp.Node == nil {
return nil, fmt.Errorf("organization %s not found", flagOrg)
}
if resp.Node.Typename != "Organization" {
return nil, fmt.Errorf("expected Organization node, got %s", resp.Node.Typename)
}
if resp.Node.TrustCenter == nil {
return nil, fmt.Errorf("trust center not found for organization %s", flagOrg)
}
return &resp.Node.TrustCenter.CommitmentGroups, nil
},
)
if err != nil {
return err
}
if *flagOutput == cmdutil.OutputJSON {
return cmdutil.PrintJSON(f.IOStreams.Out, groups)
}
if len(groups) == 0 {
_, _ = fmt.Fprintln(f.IOStreams.Out, "No commitment groups found.")
return nil
}
rows := make([][]string, 0, len(groups))
for _, g := range groups {
rows = append(rows, []string{
g.ID,
g.Title,
fmt.Sprintf("%d", g.Rank),
})
}
t := cmdutil.NewTable("ID", "TITLE", "RANK").Rows(rows...)
_, _ = fmt.Fprintln(f.IOStreams.Out, t)
if totalCount > len(groups) {
_, _ = fmt.Fprintf(
f.IOStreams.ErrOut,
"\nShowing %d of %d commitment groups\n",
len(groups),
totalCount,
)
}
return nil
},
}
cmd.Flags().StringVar(&flagOrg, "org", "", "Organization ID")
cmd.Flags().IntVarP(&flagLimit, "limit", "L", 30, "Maximum number of commitment groups to list")
cmd.Flags().StringVar(&flagOrderBy, "order-by", "", "Order by field (RANK, CREATED_AT, UPDATED_AT)")
cmd.Flags().StringVar(&flagOrderDir, "order-direction", "DESC", "Sort direction (ASC, DESC)")
flagOutput = cmdutil.AddOutputFlag(cmd)
return cmd
}

View File

@@ -0,0 +1,141 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package update
import (
"encoding/json"
"fmt"
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil"
)
const updateMutation = `
mutation($input: UpdateCompliancePortalCommitmentGroupInput!) {
updateCompliancePortalCommitmentGroup(input: $input) {
compliancePortalCommitmentGroup {
id
title
description
rank
}
}
}
`
type updateResponse struct {
UpdateCompliancePortalCommitmentGroup struct {
CompliancePortalCommitmentGroup struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Rank int `json:"rank"`
} `json:"compliancePortalCommitmentGroup"`
} `json:"updateCompliancePortalCommitmentGroup"`
}
func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command {
var (
flagTitle string
flagDescription string
flagRank int
)
cmd := &cobra.Command{
Use: "update <id>",
Short: "Update a compliance portal commitment group",
Example: ` # Update a commitment group title
prb trust-center commitment-group update <id> --title "Privacy"
# Update rank
prb trust-center cg update <id> --rank 2`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := f.Config()
if err != nil {
return err
}
host, hc, err := cfg.DefaultHost()
if err != nil {
return err
}
client := api.NewClient(
host,
hc.Token,
"/api/console/v1/graphql",
cfg.HTTPTimeoutDuration(),
cmdutil.TokenRefreshOption(cfg, host, hc),
)
input := map[string]any{
"id": args[0],
}
if cmd.Flags().Changed("title") {
input["title"] = flagTitle
}
if cmd.Flags().Changed("description") {
input["description"] = flagDescription
}
if cmd.Flags().Changed("rank") {
input["rank"] = flagRank
}
if len(input) == 1 {
return fmt.Errorf("at least one field must be specified for update")
}
data, err := client.Do(
updateMutation,
map[string]any{"input": input},
)
if err != nil {
return err
}
var resp updateResponse
if err := json.Unmarshal(data, &resp); err != nil {
return fmt.Errorf("cannot parse response: %w", err)
}
g := resp.UpdateCompliancePortalCommitmentGroup.CompliancePortalCommitmentGroup
_, _ = fmt.Fprintf(
f.IOStreams.Out,
"Updated commitment group %s (%s)\n",
g.ID,
g.Title,
)
return nil
},
}
cmd.Flags().StringVar(&flagTitle, "title", "", "Group title")
cmd.Flags().StringVar(&flagDescription, "description", "", "Group description")
cmd.Flags().IntVar(&flagRank, "rank", 0, "Display rank")
return cmd
}

View File

@@ -23,6 +23,8 @@ package trustcenter
import (
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cmd/cmdutil"
"go.probo.inc/probo/pkg/cmd/trust-center/commitment"
"go.probo.inc/probo/pkg/cmd/trust-center/commitmentgroup"
"go.probo.inc/probo/pkg/cmd/trust-center/file"
"go.probo.inc/probo/pkg/cmd/trust-center/reference"
"go.probo.inc/probo/pkg/cmd/trust-center/update"
@@ -39,6 +41,8 @@ func NewCmdTrustCenter(f *cmdutil.Factory) *cobra.Command {
cmd.AddCommand(view.NewCmdView(f))
cmd.AddCommand(update.NewCmdUpdate(f))
cmd.AddCommand(reference.NewCmdReference(f))
cmd.AddCommand(commitmentgroup.NewCmdCommitmentGroup(f))
cmd.AddCommand(commitment.NewCmdCommitment(f))
cmd.AddCommand(file.NewCmdFile(f))
return cmd

View File

@@ -7083,3 +7083,231 @@ func (r *Resolver) RemoveResourceAliasTool(ctx context.Context, req *mcp.CallToo
DeletedResourceID: input.ResourceID,
}, nil
}
// ListCommitmentGroupsTool handles the listCommitmentGroups tool
// List all commitment groups for a trust center
func (r *Resolver) ListCommitmentGroupsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListCommitmentGroupsInput) (*mcp.CallToolResult, types.ListCommitmentGroupsOutput, error) {
scope, err := r.Authorize(ctx, input.TrustCenterID, probo.ActionCompliancePortalCommitmentGroupList)
if err != nil {
return nil, types.ListCommitmentGroupsOutput{}, err
}
prb := r.proboSvc
pageOrderBy := page.OrderBy[coredata.CompliancePortalCommitmentGroupOrderField]{
Field: coredata.CompliancePortalCommitmentGroupOrderFieldRank,
Direction: page.OrderDirectionAsc,
}
if input.OrderBy != nil {
pageOrderBy = page.OrderBy[coredata.CompliancePortalCommitmentGroupOrderField]{
Field: input.OrderBy.Field,
Direction: input.OrderBy.Direction,
}
}
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
p, err := prb.CompliancePortalCommitmentGroups.ListForTrustCenterID(ctx, scope, input.TrustCenterID, cursor)
if err != nil {
return nil, types.ListCommitmentGroupsOutput{}, fmt.Errorf("cannot list commitment groups: %w", err)
}
return nil, types.NewListCommitmentGroupsOutput(p), nil
}
// AddCommitmentGroupTool handles the addCommitmentGroup tool
// Add a new commitment group to a trust center
func (r *Resolver) AddCommitmentGroupTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddCommitmentGroupInput) (*mcp.CallToolResult, types.AddCommitmentGroupOutput, error) {
scope, err := r.Authorize(ctx, input.TrustCenterID, probo.ActionCompliancePortalCommitmentGroupCreate)
if err != nil {
return nil, types.AddCommitmentGroupOutput{}, err
}
prb := r.proboSvc
group, err := prb.CompliancePortalCommitmentGroups.Create(
ctx, scope,
&probo.CreateCompliancePortalCommitmentGroupRequest{
TrustCenterID: input.TrustCenterID,
Title: input.Title,
Description: input.Description,
},
)
if err != nil {
return nil, types.AddCommitmentGroupOutput{}, fmt.Errorf("cannot add commitment group: %w", err)
}
return nil, types.AddCommitmentGroupOutput{CommitmentGroup: types.NewCommitmentGroup(group)}, nil
}
// UpdateCommitmentGroupTool handles the updateCommitmentGroup tool
// Update an existing commitment group
func (r *Resolver) UpdateCommitmentGroupTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateCommitmentGroupInput) (*mcp.CallToolResult, types.UpdateCommitmentGroupOutput, error) {
scope, err := r.Authorize(ctx, input.ID, probo.ActionCompliancePortalCommitmentGroupUpdate)
if err != nil {
return nil, types.UpdateCommitmentGroupOutput{}, err
}
prb := r.proboSvc
updateReq := &probo.UpdateCompliancePortalCommitmentGroupRequest{
ID: input.ID,
}
if title := UnwrapOmittable(input.Title); title != nil {
updateReq.Title = *title
}
if description := UnwrapOmittable(input.Description); description != nil {
updateReq.Description = *description
}
if rank := UnwrapOmittable(input.Rank); rank != nil {
updateReq.Rank = *rank
}
group, err := prb.CompliancePortalCommitmentGroups.Update(ctx, scope, updateReq)
if err != nil {
return nil, types.UpdateCommitmentGroupOutput{}, fmt.Errorf("cannot update commitment group: %w", err)
}
return nil, types.UpdateCommitmentGroupOutput{CommitmentGroup: types.NewCommitmentGroup(group)}, nil
}
// DeleteCommitmentGroupTool handles the deleteCommitmentGroup tool
// Delete a commitment group
func (r *Resolver) DeleteCommitmentGroupTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteCommitmentGroupInput) (*mcp.CallToolResult, types.DeleteCommitmentGroupOutput, error) {
scope, err := r.Authorize(ctx, input.ID, probo.ActionCompliancePortalCommitmentGroupDelete)
if err != nil {
return nil, types.DeleteCommitmentGroupOutput{}, err
}
prb := r.proboSvc
err = prb.CompliancePortalCommitmentGroups.Delete(ctx, scope, input.ID)
if err != nil {
return nil, types.DeleteCommitmentGroupOutput{}, fmt.Errorf("cannot delete commitment group: %w", err)
}
return nil, types.DeleteCommitmentGroupOutput{DeletedCommitmentGroupID: input.ID}, nil
}
// ListCommitmentsTool handles the listCommitments tool
// List all commitments in a commitment group
func (r *Resolver) ListCommitmentsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListCommitmentsInput) (*mcp.CallToolResult, types.ListCommitmentsOutput, error) {
scope, err := r.Authorize(ctx, input.GroupID, probo.ActionCompliancePortalCommitmentList)
if err != nil {
return nil, types.ListCommitmentsOutput{}, err
}
prb := r.proboSvc
pageOrderBy := page.OrderBy[coredata.CompliancePortalCommitmentOrderField]{
Field: coredata.CompliancePortalCommitmentOrderFieldRank,
Direction: page.OrderDirectionAsc,
}
if input.OrderBy != nil {
pageOrderBy = page.OrderBy[coredata.CompliancePortalCommitmentOrderField]{
Field: input.OrderBy.Field,
Direction: input.OrderBy.Direction,
}
}
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
p, err := prb.CompliancePortalCommitments.ListForGroupID(ctx, scope, input.GroupID, cursor)
if err != nil {
return nil, types.ListCommitmentsOutput{}, fmt.Errorf("cannot list commitments: %w", err)
}
return nil, types.NewListCommitmentsOutput(p), nil
}
// AddCommitmentTool handles the addCommitment tool
// Add a new commitment to a commitment group
func (r *Resolver) AddCommitmentTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddCommitmentInput) (*mcp.CallToolResult, types.AddCommitmentOutput, error) {
scope, err := r.Authorize(ctx, input.GroupID, probo.ActionCompliancePortalCommitmentCreate)
if err != nil {
return nil, types.AddCommitmentOutput{}, err
}
prb := r.proboSvc
commitment, err := prb.CompliancePortalCommitments.Create(
ctx, scope,
&probo.CreateCompliancePortalCommitmentRequest{
GroupID: input.GroupID,
Icon: input.Icon,
Eyebrow: input.Eyebrow,
Title: input.Title,
Description: input.Description,
},
)
if err != nil {
return nil, types.AddCommitmentOutput{}, fmt.Errorf("cannot add commitment: %w", err)
}
return nil, types.AddCommitmentOutput{Commitment: types.NewCommitment(commitment)}, nil
}
// UpdateCommitmentTool handles the updateCommitment tool
// Update an existing commitment
func (r *Resolver) UpdateCommitmentTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateCommitmentInput) (*mcp.CallToolResult, types.UpdateCommitmentOutput, error) {
scope, err := r.Authorize(ctx, input.ID, probo.ActionCompliancePortalCommitmentUpdate)
if err != nil {
return nil, types.UpdateCommitmentOutput{}, err
}
prb := r.proboSvc
updateReq := &probo.UpdateCompliancePortalCommitmentRequest{
ID: input.ID,
}
if icon := UnwrapOmittable(input.Icon); icon != nil {
updateReq.Icon = *icon
}
if eyebrow := UnwrapOmittable(input.Eyebrow); eyebrow != nil {
updateReq.Eyebrow = *eyebrow
}
if title := UnwrapOmittable(input.Title); title != nil {
updateReq.Title = *title
}
if description := UnwrapOmittable(input.Description); description != nil {
updateReq.Description = *description
}
if rank := UnwrapOmittable(input.Rank); rank != nil {
updateReq.Rank = *rank
}
commitment, err := prb.CompliancePortalCommitments.Update(ctx, scope, updateReq)
if err != nil {
return nil, types.UpdateCommitmentOutput{}, fmt.Errorf("cannot update commitment: %w", err)
}
return nil, types.UpdateCommitmentOutput{Commitment: types.NewCommitment(commitment)}, nil
}
// DeleteCommitmentTool handles the deleteCommitment tool
// Delete a commitment
func (r *Resolver) DeleteCommitmentTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteCommitmentInput) (*mcp.CallToolResult, types.DeleteCommitmentOutput, error) {
scope, err := r.Authorize(ctx, input.ID, probo.ActionCompliancePortalCommitmentDelete)
if err != nil {
return nil, types.DeleteCommitmentOutput{}, err
}
prb := r.proboSvc
err = prb.CompliancePortalCommitments.Delete(ctx, scope, input.ID)
if err != nil {
return nil, types.DeleteCommitmentOutput{}, fmt.Errorf("cannot delete commitment: %w", err)
}
return nil, types.DeleteCommitmentOutput{DeletedCommitmentID: input.ID}, nil
}

View File

@@ -9140,6 +9140,366 @@ components:
$ref: "#/components/schemas/GID"
description: Deleted trust center reference ID
CompliancePortalCommitmentIcon:
type: string
enum:
- LOCK_KEY
- EYE_SLASH
- FINGERPRINT
- SHIELD_WARNING
- SHIELD_CHECK
- SIREN
- KEY
- LOCK
- CLOUD
- DATABASE
- GLOBE
- EYE
- USERS
- CERTIFICATE
- GAVEL
- HEARTBEAT
- BELL
- BUG
- CODE
- SERVER
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentIcon
CompliancePortalCommitmentGroupOrderField:
type: string
enum:
- RANK
- CREATED_AT
- UPDATED_AT
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentGroupOrderField
CompliancePortalCommitmentGroupOrderBy:
type: object
required:
- field
- direction
properties:
field:
$ref: "#/components/schemas/CompliancePortalCommitmentGroupOrderField"
direction:
$ref: "#/components/schemas/OrderDirection"
CompliancePortalCommitmentOrderField:
type: string
enum:
- RANK
- CREATED_AT
- UPDATED_AT
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.CompliancePortalCommitmentOrderField
CompliancePortalCommitmentOrderBy:
type: object
required:
- field
- direction
properties:
field:
$ref: "#/components/schemas/CompliancePortalCommitmentOrderField"
direction:
$ref: "#/components/schemas/OrderDirection"
CommitmentGroup:
type: object
required:
- id
- title
- description
- rank
- created_at
- updated_at
properties:
id:
$ref: "#/components/schemas/GID"
title:
type: string
description:
type: string
rank:
type: integer
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
Commitment:
type: object
required:
- id
- group_id
- icon
- eyebrow
- title
- description
- rank
- created_at
- updated_at
properties:
id:
$ref: "#/components/schemas/GID"
group_id:
$ref: "#/components/schemas/GID"
icon:
$ref: "#/components/schemas/CompliancePortalCommitmentIcon"
eyebrow:
type: string
title:
type: string
description:
type: string
rank:
type: integer
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
ListCommitmentGroupsInput:
type: object
required:
- trust_center_id
properties:
trust_center_id:
$ref: "#/components/schemas/GID"
description: Trust center ID
order_by:
$ref: "#/components/schemas/CompliancePortalCommitmentGroupOrderBy"
description: Commitment group order by
size:
type: integer
description: Page size
cursor:
$ref: "#/components/schemas/CursorKey"
description: Page cursor
ListCommitmentGroupsOutput:
type: object
required:
- commitment_groups
properties:
next_cursor:
$ref: "#/components/schemas/CursorKey"
description: Next cursor
commitment_groups:
type: array
items:
$ref: "#/components/schemas/CommitmentGroup"
AddCommitmentGroupInput:
type: object
required:
- trust_center_id
- title
- description
properties:
trust_center_id:
$ref: "#/components/schemas/GID"
description: Trust center ID
title:
type: string
description: Group title
description:
type: string
description: Group description
AddCommitmentGroupOutput:
type: object
required:
- commitment_group
properties:
commitment_group:
$ref: "#/components/schemas/CommitmentGroup"
UpdateCommitmentGroupInput:
type: object
required:
- id
properties:
id:
$ref: "#/components/schemas/GID"
description: Commitment group ID
title:
type:
- string
- "null"
description: Group title
go.probo.inc/mcpgen/omittable: true
description:
type:
- string
- "null"
description: Group description
go.probo.inc/mcpgen/omittable: true
rank:
type:
- integer
- "null"
description: Group rank
go.probo.inc/mcpgen/omittable: true
UpdateCommitmentGroupOutput:
type: object
required:
- commitment_group
properties:
commitment_group:
$ref: "#/components/schemas/CommitmentGroup"
DeleteCommitmentGroupInput:
type: object
required:
- id
properties:
id:
$ref: "#/components/schemas/GID"
description: Commitment group ID
DeleteCommitmentGroupOutput:
type: object
required:
- deleted_commitment_group_id
properties:
deleted_commitment_group_id:
$ref: "#/components/schemas/GID"
description: Deleted commitment group ID
ListCommitmentsInput:
type: object
required:
- group_id
properties:
group_id:
$ref: "#/components/schemas/GID"
description: Commitment group ID
order_by:
$ref: "#/components/schemas/CompliancePortalCommitmentOrderBy"
description: Commitment order by
size:
type: integer
description: Page size
cursor:
$ref: "#/components/schemas/CursorKey"
description: Page cursor
ListCommitmentsOutput:
type: object
required:
- commitments
properties:
next_cursor:
$ref: "#/components/schemas/CursorKey"
description: Next cursor
commitments:
type: array
items:
$ref: "#/components/schemas/Commitment"
AddCommitmentInput:
type: object
required:
- group_id
- icon
- eyebrow
- title
- description
properties:
group_id:
$ref: "#/components/schemas/GID"
description: Commitment group ID
icon:
$ref: "#/components/schemas/CompliancePortalCommitmentIcon"
description: Commitment icon
eyebrow:
type: string
description: Commitment eyebrow
title:
type: string
description: Commitment title
description:
type: string
description: Commitment description
AddCommitmentOutput:
type: object
required:
- commitment
properties:
commitment:
$ref: "#/components/schemas/Commitment"
UpdateCommitmentInput:
type: object
required:
- id
properties:
id:
$ref: "#/components/schemas/GID"
description: Commitment ID
icon:
anyOf:
- $ref: "#/components/schemas/CompliancePortalCommitmentIcon"
- type: "null"
description: Commitment icon
go.probo.inc/mcpgen/omittable: true
eyebrow:
type:
- string
- "null"
description: Commitment eyebrow
go.probo.inc/mcpgen/omittable: true
title:
type:
- string
- "null"
description: Commitment title
go.probo.inc/mcpgen/omittable: true
description:
type:
- string
- "null"
description: Commitment description
go.probo.inc/mcpgen/omittable: true
rank:
type:
- integer
- "null"
description: Commitment rank
go.probo.inc/mcpgen/omittable: true
UpdateCommitmentOutput:
type: object
required:
- commitment
properties:
commitment:
$ref: "#/components/schemas/Commitment"
DeleteCommitmentInput:
type: object
required:
- id
properties:
id:
$ref: "#/components/schemas/GID"
description: Commitment ID
DeleteCommitmentOutput:
type: object
required:
- deleted_commitment_id
properties:
deleted_commitment_id:
$ref: "#/components/schemas/GID"
description: Deleted commitment ID
ResourceAlias:
type: object
required:
@@ -13656,6 +14016,74 @@ tools:
$ref: "#/components/schemas/DeleteTrustCenterReferenceInput"
outputSchema:
$ref: "#/components/schemas/DeleteTrustCenterReferenceOutput"
- name: listCommitmentGroups
description: List all commitment groups for a trust center
hints:
readonly: true
idempotent: true
inputSchema:
$ref: "#/components/schemas/ListCommitmentGroupsInput"
outputSchema:
$ref: "#/components/schemas/ListCommitmentGroupsOutput"
- name: addCommitmentGroup
description: Add a new commitment group to a trust center
hints:
readonly: false
inputSchema:
$ref: "#/components/schemas/AddCommitmentGroupInput"
outputSchema:
$ref: "#/components/schemas/AddCommitmentGroupOutput"
- name: updateCommitmentGroup
description: Update an existing commitment group
hints:
readonly: false
inputSchema:
$ref: "#/components/schemas/UpdateCommitmentGroupInput"
outputSchema:
$ref: "#/components/schemas/UpdateCommitmentGroupOutput"
- name: deleteCommitmentGroup
description: Delete a commitment group
hints:
readonly: false
destructive: true
inputSchema:
$ref: "#/components/schemas/DeleteCommitmentGroupInput"
outputSchema:
$ref: "#/components/schemas/DeleteCommitmentGroupOutput"
- name: listCommitments
description: List all commitments in a commitment group
hints:
readonly: true
idempotent: true
inputSchema:
$ref: "#/components/schemas/ListCommitmentsInput"
outputSchema:
$ref: "#/components/schemas/ListCommitmentsOutput"
- name: addCommitment
description: Add a new commitment to a commitment group
hints:
readonly: false
inputSchema:
$ref: "#/components/schemas/AddCommitmentInput"
outputSchema:
$ref: "#/components/schemas/AddCommitmentOutput"
- name: updateCommitment
description: Update an existing commitment
hints:
readonly: false
inputSchema:
$ref: "#/components/schemas/UpdateCommitmentInput"
outputSchema:
$ref: "#/components/schemas/UpdateCommitmentOutput"
- name: deleteCommitment
description: Delete a commitment
hints:
readonly: false
destructive: true
inputSchema:
$ref: "#/components/schemas/DeleteCommitmentInput"
outputSchema:
$ref: "#/components/schemas/DeleteCommitmentOutput"
- name: setResourceAlias
description: Set a resource alias for a resource
hints:

View File

@@ -0,0 +1,93 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package types
import (
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/page"
)
func NewCommitmentGroup(g *coredata.CompliancePortalCommitmentGroup) *CommitmentGroup {
return &CommitmentGroup{
ID: g.ID,
Title: g.Title,
Description: g.Description,
Rank: g.Rank,
CreatedAt: g.CreatedAt,
UpdatedAt: g.UpdatedAt,
}
}
func NewListCommitmentGroupsOutput(
p *page.Page[*coredata.CompliancePortalCommitmentGroup, coredata.CompliancePortalCommitmentGroupOrderField],
) ListCommitmentGroupsOutput {
groups := make([]*CommitmentGroup, 0, len(p.Data))
for _, g := range p.Data {
groups = append(groups, NewCommitmentGroup(g))
}
var nextCursor *page.CursorKey
if len(p.Data) > 0 {
cursorKey := p.Data[len(p.Data)-1].CursorKey(p.Cursor.OrderBy.Field)
nextCursor = &cursorKey
}
return ListCommitmentGroupsOutput{
NextCursor: nextCursor,
CommitmentGroups: groups,
}
}
func NewCommitment(c *coredata.CompliancePortalCommitment) *Commitment {
return &Commitment{
ID: c.ID,
GroupID: c.GroupID,
Icon: c.Icon,
Eyebrow: c.Eyebrow,
Title: c.Title,
Description: c.Description,
Rank: c.Rank,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
}
}
func NewListCommitmentsOutput(
p *page.Page[*coredata.CompliancePortalCommitment, coredata.CompliancePortalCommitmentOrderField],
) ListCommitmentsOutput {
commitments := make([]*Commitment, 0, len(p.Data))
for _, c := range p.Data {
commitments = append(commitments, NewCommitment(c))
}
var nextCursor *page.CursorKey
if len(p.Data) > 0 {
cursorKey := p.Data[len(p.Data)-1].CursorKey(p.Cursor.OrderBy.Field)
nextCursor = &cursorKey
}
return ListCommitmentsOutput{
NextCursor: nextCursor,
Commitments: commitments,
}
}