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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 177 additions & 78 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![CI](https://github.com/digidem/comapeo-cloud-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/digidem/comapeo-cloud-cli/actions/workflows/ci.yml)

A command-line interface for interacting with the Comapeo Cloud API.
A command-line interface for interacting with the CoMapeo Cloud API.

## Installation

Expand All @@ -12,151 +12,250 @@ npm install -g comapeo-cloud-cli

## Configuration

The CLI requires the following environment variables:
The CLI reads two environment variables:

- `SERVER_URL`: The URL of your Comapeo Cloud server
- `SERVER_BEARER_TOKEN`: Your API bearer token
- `SERVER_URL`: the URL of your CoMapeo Cloud server
- `SERVER_BEARER_TOKEN`: your API bearer token

You can set these in your environment or create a `.env` file. For temporary overrides, use these command-line options with any command:
Set them in your environment or in a `.env` file in the current directory.
`COMAPEO_SERVER_URL` / `COMAPEO_SERVER_TOKEN` are accepted as aliases when the
un-prefixed names would collide with other tooling.

- `-s, --server-url <url>` - Override server URL
- `-t, --server-token <token>` - Override bearer token
Every command also accepts per-invocation overrides:

- `-s, --server-url <url>`
- `-t, --server-token <token>`

```env
SERVER_URL=https://yourserver.com
SERVER_URL=https://yourserver.com/
SERVER_BEARER_TOKEN=your-token
```

## Usage
Optional: set `COMAPEO_PROJECT` to a project id or name to stop passing `-p` to
every command.

### Server Information
## Output

Get server info:
List commands choose their format automatically:

```bash
comapeo-cloud -s https://yourserver.com -t your-token info
```
| stdout | default format |
| -------------- | -------------- |
| a terminal | `table` |
| a pipe or file | `json` |

So `comapeo-cloud list-observations -p <id>` is readable by a human, and
`comapeo-cloud list-observations -p <id> \| jq '.[].docId'` keeps working in a
script with no extra flags. Override with `--format`:

### Projects
- `table` — aligned columns, truncated long values
- `json` — the full, untouched API records (default when piped)
- `ndjson` — one JSON record per line, the friendliest target for `jq`
- `csv` — every column, RFC 4180 quoting

List all projects:
Other global flags: `--columns <a,b,c>` (per command), `--timeout <ms>`,
`--no-color`, `-v/--verbose`, `-V/--version`.

## Projects

Project ids are 52-character strings that are painful to copy around, so `-p`
also accepts a **project name**:

```bash
comapeo-cloud -s https://yourserver.com -t your-token list-projects
comapeo-cloud list-projects # id + name table
comapeo-cloud list-projects --count # 10
comapeo-cloud list-projects --search vigilancia # name substring

comapeo-cloud list-observations -p "Expedição 2026" # resolves to the id
```

### Observations
Exact (case-insensitive) names win; otherwise a substring must identify exactly
one project. An ambiguous name lists the candidates and exits `2` rather than
guessing.

List observations for a project:
## Observations

```bash
comapeo-cloud -s https://yourserver.com -t your-token list-observations -p "proj_abc123"
comapeo-cloud list-observations -p <id|name>
```

### Attachments
### Counting

Get an attachment:
`--count` prints a bare number — no labels, no colour — so it composes:

```bash
comapeo-cloud -s https://yourserver.com -t your-token get-attachment \
-p "proj_abc123" \
--drive-id "drive_123" \
--type "photo" \
--name "filename.jpg" \
--variant "preview"
comapeo-cloud list-observations -p <id> --count # 56
comapeo-cloud list-observations -p <id> --count --category agua # 17
comapeo-cloud list-observations -p <id> --count --has audio # 21
```

For audio files:
Break the number down with `--group-by` (tab-separated `key<TAB>count`):

```bash
comapeo-cloud -s https://yourserver.com -t your-token get-attachment \
--project-id "proj_abc123" \
--drive-id "drive_123" \
--type "audio" \
--name "recording.mp3"
comapeo-cloud list-observations -p <id> --count --group-by category
comapeo-cloud list-observations -p <id> --count --group-by day
comapeo-cloud list-observations -p <id> --count --group-by tag:tipo-de-corpo-dagua
```

### Remote Alerts
Valid groups: `category`, `preset`, `day`, `device`, `media`, `tag:<field>`.

Create a remote detection alert:
> Presets are grouped by docId prefix (`preset:abb1b841`), not by name: the
> deployed servers do not expose a `/presets` route, so there is no preset name
> to resolve against.

`--summary` prints a block instead of records:

```bash
comapeo-cloud -s https://yourserver.com -t your-token create-alert \
--project-id "proj_abc123" \
--start-date "2024-01-01T00:00:00Z" \
--end-date "2024-01-02T00:00:00Z" \
--source-id "src_123" \
--alert-type "motion-detected" \
--lon -67.890 \
--lat 12.345
$ comapeo-cloud list-observations -p <id> --summary
matching 56
date range 2026-10-26 → 2026-10-29
devices 3
media 112 photos, 21 audio
no attachments 2
categories 9
```

List remote detection alerts for a project:
### Filtering

All filtering happens client-side: the API's `/projects/:id/observations`
endpoint takes no query parameters and always returns every document. That is
cheap enough for field projects (well under a megabyte of JSON) and means every
filter below can be combined freely.

| Flag | Meaning |
| ----------------------------------- | -------------------------------------------------------------------------- |
| `--category <id[,id]>` | match `tags.categoryId` |
| `--tag <key=value>` | match any tag; repeatable; `v1\|v2` for OR; arrays matched |
| `--preset <docId>` | match a preset docId prefix |
| `--since <date>` / `--until <date>` | `createdAt` bounds; `YYYY-MM-DD` includes the whole day |
| `--bbox <w,s,e,n>` | observations inside a lon/lat box |
| `--has <kind>` | has `photo`, `audio`, `video`, `attachment`, `location`, `notes`, `preset` |
| `--missing <kind>` | inverse of `--has` |
| `--search <text>` | substring over notes and tag values |
| `--include-deleted` | include tombstones (excluded by default) |
| `--only-deleted` | show only tombstones |
| `--sort <key>` | `createdAt` (default), `updatedAt`, `category`, `notes`, `media`, `id` |
| `--reverse` | reverse the sort |
| `--limit <n>` / `--offset <n>` | paginate the printed page |
| `--columns <a,b,c>` | pick table/csv columns |

**Deleted documents are excluded by default.** The API returns tombstones
mixed in with live records (`includeDeleted: true` server-side), and counting
them made "how many observations do we have?" wrong. `--count` now reports
live observations; use `--include-deleted` to see everything.

Examples:

```bash
comapeo-cloud -s https://yourserver.com -t your-token list-alerts
```
# everything tagged as a river, with audio, this week
comapeo-cloud list-observations -p <id> \
--tag tipo-de-corpo-dagua=rio --has audio --since 2026-10-26

### Data Export
# what still has no media attached?
comapeo-cloud list-observations -p <id> --missing attachment --format csv

Export data from a project as a zip containing attachments and geojoson file:

```bash
comapeo-cloud -s https://yourserver.com -t your-token export-geojson \
-p "proj_abc123" \
-o "export.zip"
# readable table, newest first
comapeo-cloud list-observations -p <id> --reverse --limit 20 --format table
```

### Health Check

Check server health:
## Attachments

```bash
comapeo-cloud healthcheck
comapeo-cloud get-attachment \
-u "https://server/projects/<id>/attachments/<drive>/photo/<name>" \
--variant thumbnail \
-o photo.jpg
```

## Development
Photo variants: `original`, `preview`, `thumbnail`. Audio only has `original`.
Pass `-o -` to stream the bytes to stdout.

1. Clone the repository
2. Install dependencies:
## Remote Alerts

```bash
bun install
comapeo-cloud list-alerts -p <id> --count
comapeo-cloud list-alerts -p <id> --since 2026-10-01 --alert-type motion-detected

comapeo-cloud create-alert -p <id> \
--start-date 2024-01-01T00:00:00Z \
--end-date 2024-01-02T00:00:00Z \
--source-id src_123 \
--alert-type motion-detected \
--lon -67.890 --lat 12.345
```

3. Build the project:
## Data Export

```bash
bun run build
# ZIP: GeoJSON + media
comapeo-cloud export-geojson -p <id> -o export.zip

# plain GeoJSON, no downloads
comapeo-cloud export-geojson -p <id> -o export.geojson --no-attachments

# export just a filtered slice
comapeo-cloud export-geojson -p <id> --category agua --since 2026-10-29 -o water.zip
```

4. Run tests:
The output extension decides the behaviour: `.zip` bundles `comapeo_data.geojson`
plus `images/` and `audio/` folders; `.geojson` writes the GeoJSON alone and
never downloads media. `--no-attachments` forces the metadata-only path.

`export-geojson` accepts the same filter flags as `list-observations`, so
whatever you can list you can also export.

Properties in the exported GeoJSON are the observation's tags (`categoryId`,
`notes`, and any preset-specific field) plus `$created`, `$modified`, `$version`,
`$photos` and, when present, `$audio`.

## Health Check

```bash
bun test
comapeo-cloud healthcheck # prints "ok" and exits 0
comapeo-cloud info
```

## Configuration Overrides
## Exit Codes

| Code | Meaning |
| ---- | -------------------------------------------------------------- |
| `0` | success |
| `1` | API or runtime failure |
| `2` | usage error (bad flag, missing config, ambiguous project name) |
| `3` | not found (unknown project, no matching records) |

All commands support temporary server configuration via CLI flags:
## Development

```bash
comapeo-cloud -s https://alt.server.com -t alt-token [command]
bun install
bun run build # tsc -> dist/
bun test # unit + CLI integration tests
bun run lint # prettier
```

## Publishing
`bun test` is what CI runs; `npm test` is wired to the same command because some
dependencies (`chalk` 5) are ESM-only and do not load under jest's CJS runtime.

### Layout

The package is automatically published to npm when a new release is created on GitHub. To create a new release:
```
src/index.ts entry point
src/cli.ts program wiring and global options
src/commands/ one module per command group
src/lib/api.ts config, axios client, project resolution, error mapping
src/lib/filters.ts pure filter predicates (no I/O)
src/lib/group.ts grouping and summary maths
src/lib/format.ts table / json / ndjson / csv renderers
src/lib/columns.ts column definitions per data type
src/__tests__/ unit tests + a synthetic fixture (no real field data)
```

1. Update the version in package.json
2. Create and push a new tag
3. Create a new release on GitHub
## Publishing

The GitHub Actions workflow will automatically:
The package is published to npm when a release is created on GitHub:

- Run tests
- Build the project
- Publish to npm
1. Update `version` in `package.json`
2. Create and push a tag
3. Create a GitHub release

## License

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "comapeo-cloud-cli",
"version": "1.0.3",
"version": "1.1.0",
"description": "CLI tool for interacting with Comapeo Cloud API",
"type": "module",
"main": "dist/index.js",
Expand All @@ -9,7 +9,7 @@
},
"scripts": {
"build": "tsc",
"test": "jest",
"test": "bun test",
"prepare": "npm run build",
"start": "node dist/index.js",
"lint": "prettier --write ."
Expand Down
Loading
Loading