Skip to content

Commit ec23f1a

Browse files
committed
feat: add item attachment and member tools
1 parent 8ff7353 commit ec23f1a

5 files changed

Lines changed: 294 additions & 15 deletions

File tree

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ As this project is in its initial stages, it exposes the following tools:
2020
| `view_session_info` | Session/account status from `pass-cli info` |
2121
| `view_user_info` | User account details from `pass-cli user info` |
2222
| `check_status` | Check user authentication status and CLI version |
23+
| `support` | Show Proton Pass CLI support guidance |
2324
| `inject` | Inject secrets into template files |
2425
| `run` | Run commands with secret references resolved |
2526
| `list_vaults` | List vaults |
@@ -44,13 +45,21 @@ As this project is in its initial stages, it exposes the following tools:
4445
| `create_wifi_item` | Create a WiFi item |
4546
| `create_custom_item` | Create a custom item from template payload |
4647
| `create_identity_item` | Create an identity item from template payload |
48+
| `move_item` | Move an item between vaults |
49+
| `trash_item` | Move an item to trash |
50+
| `untrash_item` | Restore an item from trash |
4751
| `update_item` | Update an item field set |
4852
| `delete_item` | Delete an item |
53+
| `download_item_attachment` | Download an item attachment |
54+
| `list_item_members` | List members of an item |
55+
| `update_item_member` | Update an item member role |
56+
| `remove_item_member` | Remove an item member |
4957
| `create_item_alias` | Create an alias item |
5058
| `share_item` | Share an item with a user |
5159
| `generate_item_totp` | Generate item TOTP codes |
5260
| `generate_random_password` | Generate a random password |
5361
| `generate_passphrase` | Generate a passphrase |
62+
| `generate_totp` | Generate TOTP from secret/URI |
5463
| `score_password` | Score password strength |
5564

5665
The `search_items` operation is additional functionality that is not provided by the base CLI.

docs/TOOL_SCHEMA_PLAN.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ Input summary convention:
283283
| `view_session_info` | `pass-cli info` | Implemented | `output?` | Account/session info |
284284
| `view_user_info` | `pass-cli user info` | Implemented | `output?` | User profile |
285285
| `update` | `pass-cli update` | Out of Scope (Out-of-Band) | n/a | n/a |
286-
| `support` | `pass-cli support` | Planned | none | Support guidance text |
286+
| `support` | `pass-cli support` | Implemented | none | Support guidance text |
287287
| `inject` | `pass-cli inject` | Implemented | `inFile`, `outFile?`, `fileMode?`, `force?`, `confirm` | Output path/status |
288288
| `run` | `pass-cli run` | Implemented | `command[]`, `envFile[]?`, `noMasking?`, `confirm` | Exit code/stdout/stderr summary |
289289

@@ -312,14 +312,14 @@ Input summary convention:
312312

313313
### Item Write and Lifecycle
314314

315-
| Tool | Source | Status | Input Summary | Output Summary |
316-
| -------------- | ----------------------- | ----------- | ------------------------------------------------------------------------- | ------------------------ | -------------------------- | ------------- |
317-
| `update_item` | `pass-cli item update` | Implemented | required: `fields[]`, `confirm`; selector: `shareId | vaultName`; xor: `itemId | itemTitle`; optional: none | Update status |
318-
| `move_item` | `pass-cli item move` | Planned | required: `confirm`; selector: source + destination + item selector (TBD) | Move status |
319-
| `delete_item` | `pass-cli item delete` | Implemented | required: `shareId`, `itemId`, `confirm`; optional: none | Delete status |
320-
| `share_item` | `pass-cli item share` | Implemented | required: `shareId`, `itemId`, `email`, `confirm`; optional: `role` | Share status |
321-
| `trash_item` | `pass-cli item trash` | Planned | required: `confirm`; selector: item selector (TBD) | Trash status |
322-
| `untrash_item` | `pass-cli item untrash` | Planned | required: `confirm`; selector: item selector (TBD) | Restore status |
315+
| Tool | Source | Status | Input Summary | Output Summary |
316+
| -------------- | ----------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
317+
| `update_item` | `pass-cli item update` | Implemented | required: `fields[]`, `confirm`; selector: `shareId \| vaultName`; xor: `itemId \| itemTitle` | Update status |
318+
| `move_item` | `pass-cli item move` | Implemented | required: `confirm`; source selector: `fromShareId \| fromVaultName`; destination selector: `toShareId \| toVaultName`; xor: `itemId \| itemTitle` | Move status |
319+
| `delete_item` | `pass-cli item delete` | Implemented | required: `shareId`, `itemId`, `confirm` | Delete status |
320+
| `share_item` | `pass-cli item share` | Implemented | required: `shareId`, `itemId`, `email`, `confirm`; optional: `role` | Share status |
321+
| `trash_item` | `pass-cli item trash` | Implemented | required: `confirm`; selector: `shareId? \| vaultName?`; xor: `itemId \| itemTitle` | Trash status |
322+
| `untrash_item` | `pass-cli item untrash` | Implemented | required: `confirm`; selector: `shareId? \| vaultName?`; xor: `itemId \| itemTitle` | Restore status |
323323

324324
### Item Creation
325325

@@ -350,10 +350,10 @@ Notes:
350350
| Tool | Source | Status | Input Summary | Output Summary |
351351
| -------------------------- | ----------------------------------- | ----------- | ------------------------------------------------------------ | -------------------- |
352352
| `create_item_alias` | `pass-cli item alias create` | Implemented | `shareId \| vaultName`, `prefix`, `output?`, `confirm` | Alias item |
353-
| `download_item_attachment` | `pass-cli item attachment download` | Planned | `shareId`, `itemId`, `attachmentId`, `outputPath`, `confirm` | Download status/path |
354-
| `list_item_members` | `pass-cli item member list` | Planned | `shareId`, `itemId`, `output?` | Member list |
355-
| `update_item_member` | `pass-cli item member update` | Planned | `shareId`, `memberShareId`, `role`, `confirm` | Update status |
356-
| `remove_item_member` | `pass-cli item member remove` | Planned | `shareId`, `memberShareId`, `confirm` | Remove status |
353+
| `download_item_attachment` | `pass-cli item attachment download` | Implemented | `shareId`, `itemId`, `attachmentId`, `outputPath`, `confirm` | Download status/path |
354+
| `list_item_members` | `pass-cli item member list` | Implemented | `shareId`, `itemId`, `output?` | Member list |
355+
| `update_item_member` | `pass-cli item member update` | Implemented | `shareId`, `memberShareId`, `role`, `confirm` | Update status |
356+
| `remove_item_member` | `pass-cli item member remove` | Implemented | `shareId`, `memberShareId`, `confirm` | Remove status |
357357

358358
### Share, Invite, Password, TOTP, User, Settings, SSH Agent
359359

@@ -366,7 +366,7 @@ Notes:
366366
| `generate_random_password` | `pass-cli password generate random` | Implemented | generation flags | Password value/metadata |
367367
| `generate_passphrase` | `pass-cli password generate passphrase` | Implemented | generation flags | Passphrase value/metadata |
368368
| `score_password` | `pass-cli password score` | Implemented | `password` | Strength report |
369-
| `generate_totp` | `pass-cli totp generate` | Planned | `secretOrUri`, `output?` | TOTP value |
369+
| `generate_totp` | `pass-cli totp generate` | Implemented | `secretOrUri`, `output?` | TOTP value |
370370
| `view_settings` | `pass-cli settings view` | Implemented | none | Settings object |
371371
| `set_default_vault` | `pass-cli settings set default-vault` | Implemented | `vaultName \| shareId`, `confirm` | Set status |
372372
| `unset_default_vault` | `pass-cli settings unset default-vault` | Implemented | `confirm` | Unset status |

src/server/register-tools.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
createCreditCardItemInputSchema,
1111
createCustomItemHandler,
1212
createCustomItemInputSchema,
13+
downloadItemAttachmentHandler,
14+
downloadItemAttachmentInputSchema,
1315
createIdentityItemHandler,
1416
createIdentityItemInputSchema,
1517
createItemAliasHandler,
@@ -32,12 +34,18 @@ import {
3234
deleteItemInputSchema,
3335
itemTotpHandler,
3436
itemTotpInputSchema,
37+
listItemMembersHandler,
38+
listItemMembersInputSchema,
3539
listItemsHandler,
3640
listItemsInputSchema,
41+
removeItemMemberHandler,
42+
removeItemMemberInputSchema,
3743
searchItemsHandler,
3844
searchItemsInputSchema,
3945
shareItemHandler,
4046
shareItemInputSchema,
47+
updateItemMemberHandler,
48+
updateItemMemberInputSchema,
4149
updateItemHandler,
4250
updateItemInputSchema,
4351
viewItemHandler,
@@ -468,6 +476,15 @@ export function registerTools(
468476
withAuthErrorHandling(async (input) => deleteItemHandler(passCli, input)),
469477
);
470478

479+
server.registerTool(
480+
"download_item_attachment",
481+
{
482+
description: "Download an item attachment to a local path.",
483+
inputSchema: downloadItemAttachmentInputSchema,
484+
},
485+
withAuthErrorHandling(async (input) => downloadItemAttachmentHandler(passCli, input)),
486+
);
487+
471488
server.registerTool(
472489
"create_item_alias",
473490
{
@@ -477,6 +494,33 @@ export function registerTools(
477494
withAuthErrorHandling(async (input) => createItemAliasHandler(passCli, input)),
478495
);
479496

497+
server.registerTool(
498+
"list_item_members",
499+
{
500+
description: "List item members.",
501+
inputSchema: listItemMembersInputSchema,
502+
},
503+
withAuthErrorHandling(async (input) => listItemMembersHandler(passCli, input)),
504+
);
505+
506+
server.registerTool(
507+
"update_item_member",
508+
{
509+
description: "Update an item member role.",
510+
inputSchema: updateItemMemberInputSchema,
511+
},
512+
withAuthErrorHandling(async (input) => updateItemMemberHandler(passCli, input)),
513+
);
514+
515+
server.registerTool(
516+
"remove_item_member",
517+
{
518+
description: "Remove an item member.",
519+
inputSchema: removeItemMemberInputSchema,
520+
},
521+
withAuthErrorHandling(async (input) => removeItemMemberHandler(passCli, input)),
522+
);
523+
480524
server.registerTool(
481525
"share_item",
482526
{

src/tools/item.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,14 @@ export const untrashItemInputSchema = z
425425
message: "Provide exactly one of itemId or itemTitle.",
426426
});
427427

428+
export const downloadItemAttachmentInputSchema = z.object({
429+
shareId: z.string().max(100).describe("Share ID containing the item"),
430+
itemId: z.string().max(100).describe("Item ID containing the attachment"),
431+
attachmentId: z.string().max(100).describe("Attachment ID to download"),
432+
outputPath: z.string().min(1).max(4096).describe("Output path for downloaded attachment"),
433+
confirm: z.boolean().optional().describe("Must be true to execute the write operation"),
434+
});
435+
428436
export const deleteItemInputSchema = z.object({
429437
shareId: z.string().max(100).describe("Share ID containing the item to delete"),
430438
itemId: z.string().max(100).describe("Item ID to delete"),
@@ -441,6 +449,25 @@ export const shareItemInputSchema = z.object({
441449
confirm: z.boolean().optional().describe("Must be true to execute the write operation"),
442450
});
443451

452+
export const listItemMembersInputSchema = z.object({
453+
shareId: z.string().max(100).describe("Share ID containing the item"),
454+
itemId: z.string().max(100).describe("Item ID"),
455+
output: z.enum(["json", "human"]).default("json").describe("Output format"),
456+
});
457+
458+
export const updateItemMemberInputSchema = z.object({
459+
shareId: z.string().max(100).describe("Share ID containing the item"),
460+
memberShareId: z.string().max(100).describe("Member share ID"),
461+
role: z.enum(SHARE_ROLE_OPTIONS).describe("Role for the member"),
462+
confirm: z.boolean().optional().describe("Must be true to execute the write operation"),
463+
});
464+
465+
export const removeItemMemberInputSchema = z.object({
466+
shareId: z.string().max(100).describe("Share ID containing the item"),
467+
memberShareId: z.string().max(100).describe("Member share ID"),
468+
confirm: z.boolean().optional().describe("Must be true to execute the write operation"),
469+
});
470+
444471
export const createItemAliasInputSchema = z
445472
.object({
446473
shareId: z.string().max(100).optional().describe("Share ID where alias item will be created"),
@@ -474,8 +501,12 @@ export type MoveItemInput = z.infer<typeof moveItemInputSchema>;
474501
export type UpdateItemInput = z.infer<typeof updateItemInputSchema>;
475502
export type TrashItemInput = z.infer<typeof trashItemInputSchema>;
476503
export type UntrashItemInput = z.infer<typeof untrashItemInputSchema>;
504+
export type DownloadItemAttachmentInput = z.infer<typeof downloadItemAttachmentInputSchema>;
477505
export type DeleteItemInput = z.infer<typeof deleteItemInputSchema>;
478506
export type ShareItemInput = z.infer<typeof shareItemInputSchema>;
507+
export type ListItemMembersInput = z.infer<typeof listItemMembersInputSchema>;
508+
export type UpdateItemMemberInput = z.infer<typeof updateItemMemberInputSchema>;
509+
export type RemoveItemMemberInput = z.infer<typeof removeItemMemberInputSchema>;
479510
export type CreateItemAliasInput = z.infer<typeof createItemAliasInputSchema>;
480511

481512
export async function listItemsHandler(
@@ -946,6 +977,28 @@ export async function untrashItemHandler(
946977
return asTextContent(out || "OK");
947978
}
948979

980+
export async function downloadItemAttachmentHandler(
981+
passCli: PassCliRunner,
982+
{ shareId, itemId, attachmentId, outputPath, confirm }: DownloadItemAttachmentInput,
983+
) {
984+
requireWriteGate(confirm);
985+
const { stdout, stderr } = await passCli([
986+
"item",
987+
"attachment",
988+
"download",
989+
"--share-id",
990+
shareId,
991+
"--item-id",
992+
itemId,
993+
"--attachment-id",
994+
attachmentId,
995+
"--output",
996+
outputPath,
997+
]);
998+
const out = joinStdoutStderr(stdout, stderr);
999+
return asTextContent(out || "OK");
1000+
}
1001+
9491002
export async function deleteItemHandler(
9501003
passCli: PassCliRunner,
9511004
{ shareId, itemId, confirm }: DeleteItemInput,
@@ -975,6 +1028,62 @@ export async function shareItemHandler(
9751028
return asTextContent(out || "OK");
9761029
}
9771030

1031+
export async function listItemMembersHandler(
1032+
passCli: PassCliRunner,
1033+
{ shareId, itemId, output }: ListItemMembersInput,
1034+
) {
1035+
const { stdout } = await passCli([
1036+
"item",
1037+
"member",
1038+
"list",
1039+
"--share-id",
1040+
shareId,
1041+
"--item-id",
1042+
itemId,
1043+
"--output",
1044+
output,
1045+
]);
1046+
return asTextContent(asJsonTextOrRaw(stdout));
1047+
}
1048+
1049+
export async function updateItemMemberHandler(
1050+
passCli: PassCliRunner,
1051+
{ shareId, memberShareId, role, confirm }: UpdateItemMemberInput,
1052+
) {
1053+
requireWriteGate(confirm);
1054+
const { stdout, stderr } = await passCli([
1055+
"item",
1056+
"member",
1057+
"update",
1058+
"--share-id",
1059+
shareId,
1060+
"--member-share-id",
1061+
memberShareId,
1062+
"--role",
1063+
role,
1064+
]);
1065+
const out = joinStdoutStderr(stdout, stderr);
1066+
return asTextContent(out || "OK");
1067+
}
1068+
1069+
export async function removeItemMemberHandler(
1070+
passCli: PassCliRunner,
1071+
{ shareId, memberShareId, confirm }: RemoveItemMemberInput,
1072+
) {
1073+
requireWriteGate(confirm);
1074+
const { stdout, stderr } = await passCli([
1075+
"item",
1076+
"member",
1077+
"remove",
1078+
"--share-id",
1079+
shareId,
1080+
"--member-share-id",
1081+
memberShareId,
1082+
]);
1083+
const out = joinStdoutStderr(stdout, stderr);
1084+
return asTextContent(out || "OK");
1085+
}
1086+
9781087
export async function createItemAliasHandler(
9791088
passCli: PassCliRunner,
9801089
{ shareId, vaultName, prefix, output, confirm }: CreateItemAliasInput,

0 commit comments

Comments
 (0)