anthropics/tres-import-contacts
Import contacts (address book entries) into TRES Finance from a CSV or XLSX file. Use this skill whenever the user wants to import, upload, or bulk-add contacts, address labels, or address book entries into TRES — whether from a file they filled in after using the export-3rd-party-contacts skill, or from any CSV/XLSX that maps addresses to names. Trigger phrases include: 'import contacts', 'upload contacts', 'add contacts from file', 'import address book', 'load contacts into TRES', 'bulk label addresses', 'import the contacts file', 'upload the filled contacts sheet'. Also trigger when the user says something like 'I filled in the contacts file, now import it' or 'here is my contacts spreadsheet, please upload it to TRES'. Do NOT trigger for exporting or extracting unidentified addresses — that's the export-3rd-party-contacts skill. Do NOT trigger for viewing or searching the existing address book.
npx skills add https://github.com/anthropics/claude-plugins-community --skill tres-import-contacts
Read a user-provided CSV or XLSX file containing address-to-name mappings and import them as contacts into the TRES address book. This is the second half of the contacts workflow: the user previously exported unidentified addresses (via the export skill), filled in names and tags, and now wants to push those labels back into TRES.
All GraphQL calls use the user-tres-finance MCP server (execute tool).
The file should have these columns (header names are matched case-insensitively):
| Contact Name | Contact Address | Contact Tag |
|---|---|---|
| Binance Hot Wallet | 0xABC... | Exchange |
| Vendor X | 0xDEF... | Vendor |
Exchange, Custodian)The column order doesn't matter — the skill matches by header name. The file may contain additional columns (like the "Address details" enrichment columns from the export skill) — those are ignored.
Call get_viewer (no arguments) to confirm the session is active and note the organization name.
The user will provide a CSV or XLSX file. Detect the format from the file extension.
For XLSX files:
Use Python with openpyxl==3.1.5. If not installed, stop and display:
> "openpyxl is not installed. Please run: python3 -m venv .venv && .venv/bin/pip install openpyxl==3.1.5"
None rows at the bottom of the sheet. Skip any row where all cells are None or empty strings.For CSV files:
Use Python's built-in csv module.
encoding='utf-8-sig' to handle the BOM (byte-order mark) that Excel adds when saving CSVs. Without this, the first header may appear as \ufeffContact Name and won't match.For each row:
After filtering, report to the user:
Then show a preview table of the first 10 rows that will be imported (address, name, tags) and ask the user to confirm before proceeding.
For small imports (<50 rows), skip this step and go straight to importing — fetching the entire address book for a handful of contacts adds unnecessary delay.
For larger imports, fetch the current address book to identify which addresses already have labels. This helps the user understand what will be created vs. updated.
query AddressBook($limit: Int, $offset: Int) {
customAccountNameLabel(limit: $limit, offset: $offset) {
results {
originalIdentifier
labelValue
tags
}
totalCount
}
}
Paginate through all results (500 at a time). Build a lookup dictionary keyed by originalIdentifier.lower().
Compare against the import list and report:
Important: setCustomAccountNameLabelTags is a replace operation, not append. If a contact currently has tags ["Exchange", "Custodian"] and the import file has ["Vendor"], the old tags will be overwritten. Warn the user about this for any contacts being updated that already have tags.
Present this breakdown to the user before proceeding. If there are updates, list a few examples showing old name → new name so the user can sanity-check.
For each validated row, make two API calls:
5a. Set the contact name:
mutation SetContactName($identifier: String, $labelValue: String) {
setCustomAccountName(identifier: $identifier, labelValue: $labelValue) {
accountTxsSummary {
accountIdentifier
displayName
}
}
}
Variables:
{
"identifier": "<the address>",
"labelValue": "<the contact name>"
}
5b. Set tags (only if the row has tags):
mutation SetContactTags($identifier: String!, $tags: [String]!) {
setCustomAccountNameLabelTags(identifier: $identifier, tags: $tags) {
accountTxsSummary {
accountIdentifier
displayName
}
}
}
Variables:
{
"identifier": "<the address>",
"tags": ["Exchange", "Custodian"]
}
Batching strategy: The API doesn't have a bulk endpoint. For small imports (<20 contacts), fire all name mutations in parallel, then all tag mutations in parallel — this is fast and safe. For larger imports, process in batches of 10 parallel calls to avoid overwhelming the API, and show progress to the user every 25 contacts (e.g. "Imported 25/142...").
Error handling: If a mutation fails for a specific address, log the error and continue with the next row. Don't stop the entire import. Collect all failures for the summary.
After all mutations complete, present a summary:
If there were failures, suggest the user can retry by running the skill again with the same file — already-imported contacts will just be updated (the mutation is idempotent for names).
setCustomAccountName mutation accepts addresses as-is. Don't normalize case — pass the original value from the file." Exchange , Custodian " → trim to ["Exchange", "Custodian"].setCustomAccountNameLabelTags replaces existing tags entirely. If the user only wants to add tags, they would need to merge with existing tags first (the skill does not do this automatically — it's a replace operation). Flag this to the user when updating existing contacts that already have tags.\ufeff) that corrupts the first column header. Always open CSVs with encoding='utf-8-sig'.; instead of ,. If comma parsing doesn't find the expected headers, retry with semicolon.None or empty.Take anthropics/tres-import-contacts from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference pip.
Without those the skill loads but fails at the first command.