A command-line tool for managing Hypercerts on the AT Protocol. Create, edit, and manage impact claims, measurements, locations, attachments, and contributors with interactive menus or scriptable flags.
Built on bluesky-social/indigo patterns with huh-powered interactive terminal UI, designed for both human operators and CI/CD pipelines.
- Full CRUD for activities, measurements, locations, attachments, evaluations, and contributors
- Beautiful interactive forms powered by charmbracelet/huh -- themed inputs, selects, confirms, and multi-selects with keyboard navigation
- Live preview card - activity creation shows a real-time preview card alongside the form as you type (powered by bubbletea + lipgloss)
- Scriptable - all commands accept flags for automation
- Linked records - measurements and attachments link to activities via strongRefs
- Location Protocol v1.0 - geographic coordinates following the certified location standard
- Cascading deletes - removing an activity cleans up linked measurements and attachments
- Session persistence - auth tokens stored securely with automatic refresh
- Consistent theming - all interactive UI shares a single centralized theme
curl -sSL https://raw.githubusercontent.com/GainForest/hypercerts-cli/main/install.sh | bashgo install github.com/GainForest/hypercerts-cli/cmd/hc@v0.1.1After installation, the hc command will be available in your terminal.
git clone https://github.com/GainForest/hypercerts-cli
cd hypercerts-cli
make build
./hc --help- Go 1.25+
# 1. Login to your PDS
hc account login -u yourhandle.example.com -p your-app-password
# 2. Create an activity (the core hypercert)
hc activity create \
--title "Rainforest Carbon Study" \
--description "12-month carbon sequestration measurement"
# 3. Add a measurement linked to the activity
hc measurement create \
--metric "carbon sequestered" \
--unit "tonnes CO2" \
--value "1500"
# 4. Add a location for the measurement
hc location create \
--lat -3.4653 \
--lon -62.2159 \
--name "Amazon Basin Site A"
# 5. Attach evidence
hc attachment create \
--title "Field Report Q1" \
--uri "https://example.com/reports/q1-2025.pdf"
# 6. List everything
hc activity ls
hc measurement ls
hc location ls
hc attachment lshc account login -u handle -p password # Create auth session
hc account login --pds-host https://pds.example # Direct PDS override
hc account logout # Delete session
hc account status # Show DID, handle, PDSActivities are the core hypercert record (org.hypercerts.claim.activity).
# Create (interactive - prompts for fields)
hc activity create
# Create (non-interactive)
hc activity create \
--title "Ocean Cleanup Initiative" \
--description "Removing plastic from coastal waters" \
--start-date 2025-01-01 \
--end-date 2025-12-31 \
--work-scope "ocean-cleanup"
# List activities (shows linked measurement count)
hc activity ls # Table view
hc activity ls --json # JSON output
# Get activity details (includes linked measurements & attachments)
hc activity get <rkey>
# Edit (interactive or with flags)
hc activity edit <rkey>
hc activity edit <rkey> --title "Updated Title"
# Delete (cascades to linked measurements & attachments)
hc activity delete # Interactive multi-select
hc activity delete <rkey> # Direct
hc activity delete <rkey> -f # Skip confirmationMeasurements record impact metrics linked to activities (org.hypercerts.claim.measurement).
# Create (interactive - select activity, enter metrics)
hc measurement create
# Create (non-interactive)
hc measurement create \
--activity <activity-rkey> \
--metric "trees planted" \
--unit "count" \
--value "5000" \
--start-date 2025-01-01 \
--end-date 2025-06-30
# List all measurements
hc measurement ls
hc measurement ls --json
# Filter by activity
hc measurement ls --activity <activity-rkey>
# Edit
hc measurement edit <rkey>
hc measurement edit <rkey> --value "5500"
# Delete
hc measurement delete # Interactive multi-select
hc measurement delete <rkey> -f # Direct with forceAlias: hc meas works as shorthand.
Geographic locations following Location Protocol v1.0 (app.certified.location).
# Create (interactive - prompts for coordinates)
hc location create
# Create (non-interactive)
hc location create \
--lat 47.6062 \
--lon -122.3321 \
--name "Seattle HQ" \
--description "Main research facility"
# List locations
hc location ls
hc location ls --json
# Edit
hc location edit <rkey>
hc location edit <rkey> --name "Seattle Office"
# Delete
hc location delete # Interactive multi-select
hc location delete <rkey> -f # Direct with forceAlias: hc loc works as shorthand.
Technical details: Locations use LP v1.0 with OGC CRS84 coordinate reference system. Coordinates are stored as "lat, lon" string in the location.string field with locationType: "coordinate-decimal".
Evidence attachments linked to activities (org.hypercerts.claim.attachment).
# Create (interactive - select activities, enter URIs)
hc attachment create
# Create (non-interactive)
hc attachment create \
--activity <activity-rkey> \
--title "Audit Report 2025" \
--content-type report \
--uri "https://example.com/audit-2025.pdf"
# Multiple activities and URIs (comma-separated)
hc attachment create \
--activity "rkey1,rkey2" \
--title "Shared Evidence" \
--uri "https://example.com/doc1.pdf,https://example.com/doc2.pdf"
# List all attachments
hc attachment ls
hc attachment ls --json
# Filter by activity
hc attachment ls --activity <activity-rkey>
# Edit
hc attachment edit <rkey>
hc attachment edit <rkey> --title "Updated Report"
# Delete
hc attachment delete # Interactive multi-select
hc attachment delete <rkey> -f # Direct with forceAlias: hc attach works as shorthand.
Content types: report, audit, evidence, testimonial, methodology
Rights and license definitions for hypercerts (org.hypercerts.claim.rights).
# Create (interactive)
hc rights create
# Create (non-interactive)
hc rights create \
--name "Creative Commons BY 4.0" \
--type "CC-BY-4.0" \
--description "Attribution required, commercial use allowed"
# List rights
hc rights ls
hc rights ls --json
# Edit
hc rights edit <rkey>
hc rights edit <rkey> --name "Updated Name"
# Delete
hc rights delete # Interactive multi-select
hc rights delete <rkey> -f # Direct with forceRights can be linked to activities during creation via the optional fields menu.
Third-party evaluations for impact claims (org.hypercerts.claim.evaluation).
# Create (interactive - select activity, add evaluators, score)
hc evaluation create
# Create (non-interactive)
hc evaluation create \
--summary "Independent verification of carbon sequestration claims"
# List evaluations
hc evaluation ls
hc evaluation ls --json
# Edit
hc evaluation edit <rkey>
hc evaluation edit <rkey> --summary "Updated evaluation summary"
# Delete
hc evaluation delete # Interactive multi-select
hc evaluation delete <rkey> -f # Direct with forceAlias: hc eval works as shorthand.
Fields:
evaluators[]- Array of evaluator DIDs (required)summary- Brief evaluation summary (required)subject- StrongRef to the record being evaluated (optional)content[]- Array of content URIs (reports, methodology docs)measurements[]- Array of strongRefs to measurement recordsscore- Score object withmin,max, andvaluelocation- StrongRef to location record
Project groupings of activities (org.hypercerts.claim.collection).
# Create (interactive - select activities to include)
hc collection create
# Create (non-interactive)
hc collection create \
--title "Amazon Conservation 2025" \
--type "project" \
--description "All activities related to Amazon conservation"
# List collections
hc collection ls
hc collection ls --json
# Edit
hc collection edit <rkey>
hc collection edit <rkey> --title "Updated Project Name"
# Delete
hc collection delete # Interactive multi-select
hc collection delete <rkey> -f # Direct with forceAlias: hc coll works as shorthand.
Fields:
title- Collection title (required, max 80 graphemes)type- Collection type (optional, e.g. "project", "favorites")items[]- Array of items withitemIdentifier(strongRef) and optionalitemWeightshortDescription- Brief summary (optional, max 300 graphemes)location- StrongRef to location record (optional)
Funding receipts for tracking payments (org.hypercerts.funding.receipt).
# Create (interactive - prompts for payment details)
hc funding create
# Create (non-interactive)
hc funding create \
--from did:plc:sender123 \
--to did:plc:recipient456 \
--amount "1000.00" \
--currency "USD" \
--for <activity-rkey>
# List funding receipts
hc funding ls
hc funding ls --json
hc funding ls --activity <activity-rkey> # Filter by linked activity
# Edit
hc funding edit <rkey>
# Delete
hc funding delete # Interactive multi-select
hc funding delete <rkey> -f # Direct with forceAlias: hc fund works as shorthand.
Fields:
from- Sender DID (required)to- Recipient DID or name (required)amount- Payment amount as string (required)currency- Currency code: USD, EUR, ETH, etc. (required)paymentRail- Payment method: bank_transfer, credit_card, onchain, cash (optional)paymentNetwork- Network: arbitrum, ethereum, sepa, visa, paypal (optional)transactionId- Payment transaction reference (optional)for- AT-URI of linked activity (optional)notes- Additional context, max 500 chars (optional)occurredAt- When payment occurred (optional)
Reusable work scope tags with hierarchy (org.hypercerts.helper.workScopeTag).
# Create (interactive)
hc workscope create
# Create (non-interactive)
hc workscope create \
--key "climate-action" \
--label "Climate Action" \
--kind "topic" \
--description "Projects related to climate change mitigation"
# List work scope tags
hc workscope ls
hc workscope ls --json
hc workscope ls --kind topic # Filter by kind
# Edit
hc workscope edit <rkey>
hc workscope edit <rkey> --label "Updated Label"
# Delete
hc workscope delete # Interactive multi-select
hc workscope delete <rkey> -f # Direct with forceAlias: hc ws works as shorthand.
Fields:
key- Lowercase-hyphenated machine key (required, max 120 chars)label- Human-readable label (required, max 200 chars)kind- Category: topic, language, domain, method, tag (optional)description- Longer description (optional, max 1000 graphemes)parent- StrongRef to parent tag for hierarchy (optional)aliases[]- Alternative names (optional, max 50 items)
Contributor identity records (org.hypercerts.claim.contributorInformation).
# Create
hc contributor create
hc contributor create --identifier did:plc:abc123 --name "Alice Chen"
# List
hc contributor ls
hc contributor ls --json
# Edit
hc contributor edit <rkey>
hc contributor edit <rkey> --name "Alice M. Chen"
# Delete
hc contributor delete
hc contributor delete <rkey> -fAlias: hc contrib works as shorthand.
# Get any ATProto record by AT-URI
hc get at://did:plc:xxx/org.hypercerts.claim.activity/rkey
# List all records for an account
hc ls handle.example.com
hc ls handle.example.com --collection org.hypercerts.claim.activity
hc ls handle.example.com -c # List collection names only
# Resolve identity (DID document)
hc resolve handle.example.com
hc resolve -d handle.example.com # Just the DID ┌─────────────────┐
│ Activity │
│ (hypercert) │
└────────┬────────┘
│
┌───────────┬───────────┼───────────┬───────────┬───────────┐
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│Measuremt│ │Attachmnt│ │Contribut│ │ Rights │ │Location │ │Evaluatn │
│(metrics)│ │(evidence│ │ (people)│ │(license)│ │ (geo) │ │(3rd pty)│
└────┬────┘ └────┬────┘ └─────────┘ └─────────┘ └─────────┘ └────┬────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Location │ │Location │ │Measuremt│
│(optional│ │(optional│ │(linked) │
└─────────┘ └─────────┘ └─────────┘
Relationships:
- Measurements link to activities via
subject(strongRef) - Attachments link to activities via
subjects[](array of strongRefs) - Contributors are embedded in activities via
contributors[] - Rights link to activities via
rights(strongRef) - Locations link to activities via
locations[](array of strongRefs) - Locations can also be linked to measurements and attachments
- Evaluations link to activities via
subjectand can includemeasurements[]
All interactive UI is powered by charmbracelet/huh with the Charm theme, providing a consistent, polished terminal experience across every command.
Activity creation uses a bubbletea model that renders a two-column layout: the form on the left, and a live-updating preview card on the right. As you type, the card updates in real-time:
//// New Hypercert Activity /////////////////////////////////////////////
Activity Details ╭──────────────────────────────╮
│ │
Title │ Rainforest Carbon Study │
> Rainforest Carbon Study │ 12-month carbon measurement │
│ │
Short description │ Scope │
> 12-month carbon measurement │ climate-action │
│ │
Description │ Period │
> │ 2025-01-01 - 2025-12-31 │
│ │
Work scope │ │
> climate-action │ │
╰──────────────────────────────╯
//// enter next tab/shift+tab navigate ctrl+c cancel //////////////////
Other create and edit commands present grouped forms with validation, tab navigation between fields, and themed styling:
Edit Activity
Title
> Rainforest Carbon Study
Short description
> 12-month carbon sequestration measurement
Edit optional fields? Yes / No
enter next tab/shift+tab navigate ctrl+c cancel
Select an activity
> Rainforest Carbon Study 2025-06-15
Ocean Cleanup Initiative 2025-03-01
↑/↓ navigate / filter enter select ctrl+c cancel
Select activities
> [x] Rainforest Carbon Study 2025-06-15
[x] Ocean Cleanup Initiative 2025-03-01
[ ] Urban Garden Project 2025-04-15
↑/↓ navigate x toggle ctrl+a all enter confirm ctrl+c cancel
Select a contributor
> Alice Chen did:plc:abc123
Bob Martinez did:plc:xyz789
+ Create new contributor...
↑/↓ navigate enter select ctrl+c cancel
Delete confirmations use themed yes/no prompts:
Delete 3 activities?
This action cannot be undone
Yes / > No
←/→ toggle enter confirm
All forms, selects, and confirms share a single theme defined in internal/style/theme.go. To change the theme for the entire CLI, edit one line:
func Theme() *huh.Theme {
return huh.ThemeCharm() // or ThemeDracula(), ThemeCatppuccin(), ThemeBase16()
}| Collection | CLI Command | Description |
|---|---|---|
org.hypercerts.claim.activity |
activity |
Core hypercert / impact claim |
org.hypercerts.claim.measurement |
measurement |
Impact metrics linked to activity |
app.certified.location |
location |
Geographic location (LP v1.0) |
org.hypercerts.claim.attachment |
attachment |
Evidence documents/URIs |
org.hypercerts.claim.rights |
rights |
Rights and licenses |
org.hypercerts.claim.contributorInformation |
contributor |
Contributor identity |
org.hypercerts.claim.contributionDetails |
- | Contribution role (embedded in activity) |
org.hypercerts.claim.collection |
collection |
Project grouping |
org.hypercerts.claim.evaluation |
evaluation |
Third-party evaluation |
org.hypercerts.funding.receipt |
funding |
Funding receipt |
org.hypercerts.helper.workScopeTag |
workscope |
Work scope taxonomy tag |
| Variable | Description |
|---|---|
HYPER_USERNAME |
Handle or DID for auth |
HYPER_PASSWORD |
App password for auth |
ATP_PDS_HOST |
Override PDS URL |
ATP_PLC_HOST |
Override PLC directory URL (default: https://plc.directory) |
HYPER_LOG_LEVEL |
Log level: error, warn, info, debug |
Environment variables can also be set in a .env file in the working directory.
make build # Build binary (version injected via ldflags)
make test # Run all unit tests
make test-race # Run with race detector
make lint # golangci-lint run
make fmt # Format code
make coverage-html # Generate coverage report
make clean # Remove binary + coverage artifacts# Single test
go test -v -run TestConfirm ./internal/menu/...
# Test with pattern
go test -v -run "TestNormalizeDate/date_only" ./cmd/...
# All tests in a package
go test -v ./cmd/...hypercerts-cli/
├── cmd/hc/main.go # Entry point
├── Makefile # Build targets
├── go.mod, go.sum # Dependencies
│
├── cmd/
│ ├── root.go # BuildApp(), global flags, command tree
│ ├── util.go # Shared helpers (requireAuth, mapStr, buildStrongRef, etc.)
│ ├── util_test.go # Tests for helpers
│ ├── account.go # login / logout / status
│ ├── activity.go # Activity CRUD + selectActivity, cascading delete
│ ├── activityform.go # Bubbletea model: activity create with live preview card
│ ├── measurement.go # Measurement CRUD + selectActivity linking
│ ├── location.go # Location CRUD + selectLocation, selectLocations
│ ├── attachment.go # Attachment CRUD + content URIs, subjects[]
│ ├── rights.go # Rights CRUD + selectRights (licenses)
│ ├── evaluation.go # Evaluation CRUD + score, measurements[]
│ ├── collection.go # Collection CRUD + items[] with weights
│ ├── funding.go # Funding receipt CRUD + payment details
│ ├── workscope.go # Work scope tag CRUD + hierarchy
│ ├── contributor.go # Contributor CRUD + selectContributor
│ └── record.go # Top-level get / ls / resolve
│
└── internal/
├── atproto/
│ ├── collections.go # NSID constants (11 record types)
│ ├── auth.go # Session persistence (~/.local/state/hc/)
│ ├── auth_test.go # Auth tests
│ └── client.go # Record CRUD (Create/Get/Put/Delete/ListAll)
│
├── menu/
│ ├── confirm.go # Confirm(), ConfirmBulkDelete() (huh + text fallback)
│ ├── confirm_test.go
│ ├── select.go # SingleSelect[T], SingleSelectWithCreate[T]
│ └── multiselect.go # MultiSelect[T]
│
└── style/
└── theme.go # Centralized huh theme (style.Theme())
| Dependency | Version | Purpose |
|---|---|---|
bluesky-social/indigo |
v0.0.0-20260202 | ATProto SDK (identity, repo, API) |
charmbracelet/bubbletea |
v1.3.6 | TUI framework (activity form with live preview) |
charmbracelet/huh |
v0.8.0 | Interactive forms, selects, confirms (all TUI) |
charmbracelet/lipgloss |
v1.1.0 | Terminal styling (preview card layout) |
urfave/cli/v3 |
v3.6.2 | CLI framework |
adrg/xdg |
v0.5.3 | XDG directories |
joho/godotenv |
v1.5.1 | .env file loading |
golang.org/x/term |
v0.39.0 | TTY detection for huh/text fallback |
- Add NSID constant to
internal/atproto/collections.go - Create
cmd/<type>.gowith:type <type>Option struct { ... }for menu displayfetch<Type>s()to list recordsselect<Type>()for interactive selection (if needed)run<Type>Create/Edit/Delete/List()CLI actions
- Add
cmd<Type>variable tocmd/root.go - Wire into
BuildApp()Commands list - Add tests to
cmd/<type>_test.go - Update this README
Imports: Three groups separated by blank lines: stdlib, third-party, local.
import (
"context"
"fmt"
"github.com/urfave/cli/v3"
"github.com/GainForest/hypercerts-cli/internal/atproto"
)Naming:
- Files:
lowercase_underscores.go - Exported:
PascalCase - Unexported:
camelCase - CLI actions:
runXxxCreate,runXxxEdit, etc.
Output: Always use cmd.Root().Writer (not os.Stdout) for testability.
hc account status # Check current session
hc account login -u handle -p password # Re-authenticateMeasurements require an activity to link to. Create an activity first:
hc activity create --title "My Activity" --description "Description"
hc measurement create --activity <rkey-from-above>Coordinates must be valid:
- Latitude: -90 to 90
- Longitude: -180 to 180
# Valid
hc location create --lat 47.6062 --lon -122.3321
# Invalid (will error)
hc location create --lat 100 --lon -200Sessions are stored at ~/.local/state/hc/auth-session.json. If expired:
hc account logout
hc account login -u handle -p passwordSee LICENSE for details.
