diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..c5fc08f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,56 @@ +name: Bug report +description: Something does not work as documented +labels: [bug] +body: + - type: markdown + attributes: + value: | + Thanks for reporting a problem. Please never include your Equasis password, your + credentials file, or pages saved with `--save-html` that still contain your name. + - type: input + id: version + attributes: + label: equasis-cli version + description: Output of `equasis --version` + placeholder: equasis 3.0.0 + validations: + required: true + - type: input + id: environment + attributes: + label: Operating system and Python version + placeholder: macOS 15, Python 3.13 + validations: + required: true + - type: dropdown + id: mode + attributes: + label: Mode + options: + - Command line + - Interactive shell + - Python API + validations: + required: true + - type: textarea + id: command + attributes: + label: Command + description: The exact command you ran + render: shell + validations: + required: true + - type: textarea + id: expected + attributes: + label: What you expected + validations: + required: true + - type: textarea + id: actual + attributes: + label: What happened + description: Include the output and exit code. Re-running with `--debug` often helps. + render: text + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..54fdf83 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Security vulnerability + url: https://github.com/rhinonix/equasis-cli/security/advisories/new + about: Report security issues privately. + - name: Commercial licensing + url: https://github.com/rhinonix/equasis-cli/blob/main/LICENSING.md + about: Using equasis-cli commercially. diff --git a/.github/ISSUE_TEMPLATE/equasis_changed.yml b/.github/ISSUE_TEMPLATE/equasis_changed.yml new file mode 100644 index 0000000..cc39d40 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/equasis_changed.yml @@ -0,0 +1,42 @@ +name: Equasis page changed +description: Exit code 6, "could not read the Equasis page", or data that is missing or in the wrong fields +labels: [bug] +body: + - type: markdown + attributes: + value: | + Equasis sometimes changes its website, which can break parsing. A copy of the page + makes this quick to fix. + + 1. Re-run the failing command with `--save-html pages --refresh`. + 2. Open the saved page for the failing request in a text editor and replace your + name in the "Welcome" section with `Test User`. + 3. Attach the edited page below. + - type: input + id: version + attributes: + label: equasis-cli version + description: Output of `equasis --version` + validations: + required: true + - type: textarea + id: command + attributes: + label: Command and output + render: text + validations: + required: true + - type: textarea + id: page + attributes: + label: Saved page (with your name removed) + description: Drag the HTML file here, or describe what looks different on equasis.org. + validations: + required: false + - type: checkboxes + id: privacy + attributes: + label: Privacy + options: + - label: I removed my name from any attached page + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..ca8b836 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,19 @@ +name: Feature request +description: Suggest an improvement +labels: [enhancement] +body: + - type: textarea + id: problem + attributes: + label: What are you trying to do? + description: Describe the research or workflow problem, not only the solution. + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed solution + - type: textarea + id: alternatives + attributes: + label: Alternatives you have considered diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4a0159d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +## Summary + + + +## Checklist + +- [ ] Tests added or updated (parser changes include a fixture made with `tests/tools/make_fixture.py`) +- [ ] `ruff check .`, `ruff format --check .`, `mypy`, and `pytest` pass +- [ ] Documentation updated for user-visible changes +- [ ] Entry added under "Unreleased" in `CHANGELOG.md` +- [ ] No credentials or personal information in code, fixtures, or logs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..603b95b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,79 @@ +name: Release + +# Publishing uses PyPI trusted publishing (OpenID Connect), so no API tokens are stored. +# - Run manually ("Run workflow") to publish to TestPyPI for a dry run. +# - Publishing a GitHub release publishes to PyPI after approval of the "pypi" environment. +# See RELEASING.md for the one-time setup. + +on: + release: + types: [published] + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: Build distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + - name: Check that the tag matches the package version + if: github.event_name == 'release' + env: + TAG: ${{ github.event.release.tag_name }} + run: | + version=$(python -c "import re, pathlib; print(re.search(r'__version__ = \"(.+)\"', pathlib.Path('src/equasis_cli/_version.py').read_text()).group(1))") + if [ "v$version" != "$TAG" ]; then + echo "::error::Tag $TAG does not match package version $version" + exit 1 + fi + - name: Build and check + run: | + python -m pip install build twine + python -m build + python -m twine check --strict dist/* + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: dist + path: dist/ + + testpypi: + name: Publish to TestPyPI + if: github.event_name == 'workflow_dispatch' + needs: build + runs-on: ubuntu-latest + environment: + name: testpypi + url: https://test.pypi.org/p/equasis-cli + permissions: + id-token: write + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 + with: + repository-url: https://test.pypi.org/legacy/ + + pypi: + name: Publish to PyPI + if: github.event_name == 'release' + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/equasis-cli + permissions: + id-token: write + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b54f81c..469074a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,5 @@ +# Recorded Equasis pages are test data and must stay byte-for-byte as generated. +exclude: ^tests/fixtures/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..df059a4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,111 @@ +# Changelog + +All notable changes to equasis-cli are documented here. The format follows +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses +[Semantic Versioning](https://semver.org/). + +## [Unreleased] + +## [3.0.0] - 2026-09-16 + +A rebuild focused on reliability, correct data, and scripting. Existing users should read +**Upgrading from 2.x** below. + +### Fixed + +- Interactive `batch` did nothing ([#16](https://github.com/rhinonix/equasis-cli/issues/16)). +- Interactive `/output file.json` and `/output file.csv` wrote table text instead of JSON + or CSV ([#17](https://github.com/rhinonix/equasis-cli/issues/17)). +- Interactive `fleet` did nothing ([#18](https://github.com/rhinonix/equasis-cli/issues/18)). +- An expired Equasis session was reported as "No vessel found". The session is now renewed + automatically. +- An inspection reported to two PSC regimes produced an extra, garbled inspection record. +- Every inspection was flagged as a detention in table output. +- Tables were read by column position, so values could land in the wrong fields. + Columns are now matched by their headers. +- Permanent HTTP errors such as 404 were retried, and `Retry-After` was ignored. +- Status messages were printed into JSON and CSV output, and every failure exited with + status 0. +- CSV output did not quote values containing commas and had no header for single vessels. +- `--output` and `--output-file` only worked before the command name. +- `--debug` had no effect, and `--continue-on-error` did nothing. +- Batch files with inline `#` comments sent the comment to Equasis as part of the IMO number. +- Interactive log messages appeared twice, and typographic quotes and parameters such as + `/company-file` were not recognized. +- Search and fleet results beyond the first page were missing. +- The Comoros flag was reported with the code `XCM` instead of `COM`. + +### Added + +- Indicators: name, flag, and management changes; recent inspections, detentions, and + deficiencies; grey or black listed flags; USCG targeting; class withdrawals and + suspensions; P&I insurance; and whether the MMSI matches the flag state. +- New vessel data: safety management certificates, P&I insurance, class renewal surveys, + classification history, human element deficiencies, and company roles with dates. +- Fleet data: class, detention counts, and the roles the company holds for each vessel. +- `search --imo`, `--mmsi`, and `--call-sign` for exact lookups, plus `--type`, + `--max-pages`, and `--all-pages`. +- `fleet` accepts company numbers (`--company-id`), prompts when a name is ambiguous, and + supports `--first-match`. +- JSON Lines output (`-f jsonl`), which streams batch results as they complete. +- A 24-hour page cache with `--no-cache`, `--refresh`, and `equasis cache [info|clear]` + ([#2](https://github.com/rhinonix/equasis-cli/issues/2)). +- `--delay`, `--save-html`, `--quiet`, `--no-color`, `configure --test`, and `--version`. +- Documented exit codes for scripting. +- Batch input from standard input (`--imo-file -`) and IMO check-digit warnings. +- Interactive `help COMMAND`, `cache`, `/refresh`, `/pages`, and `/first`. +- A typed Python API (`EquasisClient`, models, and `compute_indicators`). +- Tests with recorded Equasis pages, continuous integration on Linux, macOS, and Windows, + and a weekly live check against equasis.org. + +### Changed + +- Relicensed from CC BY-NC-SA 4.0 to the PolyForm Noncommercial License 1.0.0, with an + additional permission for journalism and nonprofit research (see LICENSING.md). +- Python 3.10 or later is required. +- JSON output uses a versioned envelope (`schema_version`, `type`, `retrieved_at`), ISO 8601 + dates, and integer tonnages. The layout is documented in docs/output-schema.md. +- CSV output was redesigned: one row per vessel with key facts and indicators, and batch + files use a `lookup_status` column. +- Batches continue after failures by default; use `--fail-fast` to stop. +- Credentials are written atomically with owner-only permissions. A warning is shown if + the credentials file is readable by others. +- `-f/--format` is the name of the format option (`--output` still works) and + `-o/--output-file` sets the output file. +- Progress messages, warnings, and errors are written to stderr. + +### Removed + +- The `.env` file references (credentials come from `configure --setup` or environment + variables) and the unused `python-dotenv` and `lxml` dependencies. +- The unused `--batch` option. +- The startup banner before command output. `--no-banner` is still accepted and has no + effect. + +### Upgrading from 2.x + +- Update scripts that read JSON: vessel fields are under `.vessel` (for example + `.vessel.name` instead of `.basic_info.name`), and dates are ISO 8601. +- Update scripts that read CSV to use the new column names. +- Scripts that relied on exit status 0 for failures should check for the new codes. +- The Python API changed: use `EquasisClient.get_vessel`, `search`, `search_ships`, and + `get_fleet`. + +## [2.0.0] - 2025-10-01 + +### Added + +- Full-screen interactive shell with command menus, loading indicators, and scroll mode. +- Company search and fleet retrieval by company number. + +## [1.0.0] - 2025-09-27 + +### Added + +- First stable release: vessel lookup across the Ship Info, Inspections, and Ship History + pages, vessel search, fleets, batch processing, retries, and credential management. + +[Unreleased]: https://github.com/rhinonix/equasis-cli/compare/v3.0.0...HEAD +[3.0.0]: https://github.com/rhinonix/equasis-cli/compare/v2.0.0...v3.0.0 +[2.0.0]: https://github.com/rhinonix/equasis-cli/compare/v1.0.0...v2.0.0 +[1.0.0]: https://github.com/rhinonix/equasis-cli/releases/tag/v1.0.0 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..078b6ee --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,84 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [rhinonix.github.exclaim769@slmail.me](mailto:rhinonix.github.exclaim769@slmail.me). All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6a1e71c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,62 @@ +# Contributing + +Thank you for helping improve equasis-cli. Bug reports, parser fixes for Equasis website +changes, and documentation improvements are especially welcome. + +## Reporting problems + +- **Equasis changed its website** (exit code 6, or missing or wrong data): use the + "Equasis page changed" issue template. Re-run the command with `--save-html DIR` and + attach the relevant page after removing your name from it. Your name appears in the + "Welcome" section near the top of the page. +- **Other bugs**: use the bug report template and include `equasis --version`, your + operating system, the exact command, and the output with `--debug`. +- **Security issues**: do not open a public issue; see [SECURITY.md](SECURITY.md). + +Never post your Equasis password, the contents of your credentials file, or unredacted +pages saved with `--save-html`. + +## Development setup + +```bash +git clone https://github.com/rhinonix/equasis-cli.git +cd equasis-cli +python3 -m venv .venv +source .venv/bin/activate # Windows: .venv\Scripts\activate +pip install -e ".[dev]" +pre-commit install +``` + +See [docs/development.md](docs/development.md) for the project layout, tests, and test +fixtures. + +## Pull requests + +1. Open an issue first for larger changes so the approach can be agreed. +2. Keep each pull request focused, and write commit messages that explain what changed + and why. +3. Add or update tests. Parser changes need a fixture recorded from the real page (see + [docs/development.md](docs/development.md#test-fixtures)). +4. Update the documentation and add an entry under "Unreleased" in + [CHANGELOG.md](CHANGELOG.md) for user-visible changes. +5. Make sure the checks pass: + + ```bash + ruff check . + ruff format --check . + mypy + pytest + ``` + +## Licensing of contributions + +equasis-cli is licensed under the PolyForm Noncommercial License 1.0.0, and commercial +licenses are available separately (see [LICENSING.md](LICENSING.md)). So that the project +can continue to offer both, you will be asked to agree to a contributor license agreement +(CLA) before your first pull request is merged. You keep the copyright to your +contribution; the agreement allows it to be distributed under both licenses. + +## Code of conduct + +This project follows the [Contributor Covenant](CODE_OF_CONDUCT.md). By participating you +agree to uphold it. diff --git a/README.md b/README.md index 192b081..196a23b 100644 --- a/README.md +++ b/README.md @@ -1,1347 +1,191 @@ -# Equasis CLI Tool +# equasis-cli -![Alt text](https://github.com/user-attachments/assets/d8efd954-438b-4e91-b000-8391b2f0321f "Screenshot of equasis-cli") +[![CI](https://github.com/rhinonix/equasis-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/rhinonix/equasis-cli/actions/workflows/ci.yml) +[![PyPI](https://img.shields.io/pypi/v/equasis-cli.svg)](https://pypi.org/project/equasis-cli/) +[![Python](https://img.shields.io/pypi/pyversions/equasis-cli.svg)](https://pypi.org/project/equasis-cli/) +[![License: PolyForm Noncommercial](https://img.shields.io/badge/license-PolyForm%20Noncommercial%201.0.0-lightgrey.svg)](LICENSING.md) -[![Python Version](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/) -[![License: PolyForm Noncommercial 1.0.0](https://img.shields.io/badge/license-PolyForm%20Noncommercial%201.0.0-lightgrey.svg)](LICENSING.md) -[![GitHub Stars](https://img.shields.io/github/stars/rhinonix/equasis-cli?style=social)](https://github.com/rhinonix/equasis-cli/stargazers) -[![GitHub Issues](https://img.shields.io/github/issues/rhinonix/equasis-cli)](https://github.com/rhinonix/equasis-cli/issues) +Look up vessels, port state control inspections, ownership history, and company fleets +in [Equasis](https://www.equasis.org) from the command line, an interactive shell, or +Python. -A command-line tool for accessing Equasis maritime intelligence. Created to solve a real problem: investigating large numbers of vessels and management companies is cumbersome through the web interface. This tool provides a scriptable way to query vessel data, export in multiple formats, and analyze fleets efficiently. +Investigating many vessels or companies through the Equasis website is slow and manual. +equasis-cli turns it into scriptable commands that produce tables, JSON, JSON Lines, or +CSV, handle batches of hundreds of vessels, and summarize the signals researchers look for. -> **Major Update**: Now provides comprehensive vessel intelligence with 50+ data points including management companies, PSC inspections, historical names/flags, and classification details - all collected automatically from multiple Equasis tabs. - -> [!IMPORTANT] -> The HTML parsing may need updates if Equasis changes their website structure. If you encounter parsing errors or missing data, please [submit an issue](https://github.com/rhinonix/equasis-cli/issues) with details about the error. Developers are welcome to submit pull requests with fixes. - -## Table of Contents - -- [Overview](#overview) -- [Features](#features) -- [Prerequisites](#prerequisites) -- [Installation](#installation) -- [Configuration](#configuration) -- [Usage](#usage) -- [Output Formats](#output-formats) -- [Examples](#examples) -- [Development](#development) -- [Troubleshooting](#troubleshooting) -- [Legal and Compliance](#legal-and-compliance) -- [Contributing](#contributing) -- [License](#license) - -## Overview - -Equasis CLI Tool is a Python-based command-line application that interfaces with the Equasis maritime database to retrieve vessel and fleet information. Unlike web-based tools, this CLI provides: - -- Fast, scriptable access to maritime data -- Multiple output formats (table, JSON, CSV) -- Batch processing capabilities -- Integration with automation workflows -- Clean separation from GUI dependencies +![Screenshot of the equasis-cli interactive shell](https://github.com/user-attachments/assets/d8efd954-438b-4e91-b000-8391b2f0321f) ## Features -### Current Features - -#### Core Vessel Intelligence - -- **Comprehensive Vessel Lookup**: Get complete vessel profiles with 50+ data points from multiple Equasis tabs -- **3-Tab Data Collection**: Automatically fetches Ship Info, Inspections, and Ship History data -- **Management Company Details**: Current and historical management with roles and dates -- **PSC Inspection History**: Port State Control inspections with detention records -- **Historical Vessel Data**: Name changes, flag changes, and ownership history over time -- **Classification Society Info**: Current and historical classification with status -- **Geographical Tracking**: Vessel location history and movement patterns -- **Flag Performance**: Paris MOU and Tokyo MOU ratings, USCG targeting status - -#### Search & Fleet Operations - -- **Vessel Search by IMO**: Look up comprehensive vessel information using IMO numbers -- **Vessel Search by Name**: Search for vessels using partial or complete names -- **Fleet Information**: Retrieve fleet data for shipping companies -- **Batch Processing**: Process multiple vessels or companies in a single operation with progress tracking - -#### Technical Features - -- **Professional Package Structure**: Modular architecture ready for distribution (Homebrew, PyPI) -- **Multiple Output Formats**: Export data as formatted tables, structured JSON, or CSV -- **File Output**: Save results directly to files -- **Session Management**: Automatic login and session handling with error recovery -- **Rate Limiting**: Built-in delays to respect server resources (1-second intervals) -- **Environment Variables**: Secure credential management via .env files -- **Comprehensive Logging**: Debug and monitor operations with detailed logging -- **Enterprise-Grade Retry Logic**: Exponential backoff with jitter for network resilience - -### [Planned Features](https://github.com/users/rhinonix/projects/1/views/1) - -#### Near-Term Enhancements - -- **Data Caching**: Reduce redundant API calls with intelligent caching -- **Enhanced Search/Fleet Parsing**: Apply comprehensive parsing to search results -- **Progress Indicators**: Visual feedback for long operations -- **Configuration Files**: User preferences and custom settings - -#### Medium-Term Goals - -- **Homebrew Distribution**: Native macOS installation via `brew install equasis-cli` -- **Additional Maritime Data**: Integration with other vessel databases -- **Export Formats**: Excel, PDF reports, specialized maritime formats -- **Vessel Analytics**: Risk assessment and compliance scoring -- **Fleet Intelligence**: Company-wide vessel analysis and reporting - -#### Advanced Features - -- **Maritime Intelligence Platform**: Beyond basic vessel lookup -- **API Service**: RESTful API for integration with other maritime tools -- **Visualization**: Charts and graphs for historical vessel data -- **Automated Monitoring**: Alerts for vessel status changes - -## Prerequisites - -- **Python 3.10+**: Required for modern features and type hints -- **Equasis Account**: Valid username and password from equasis.org -- **Internet Connection**: Required for accessing Equasis servers - -### System Requirements - -- macOS, Linux, or Windows -- 50MB disk space for virtual environment -- Stable internet connection +- **Complete vessel profiles**: particulars, flag performance, management companies, + classification and surveys, safety management certificates, P&I insurance, recent + sightings, PSC inspections with detentions and deficiencies, and name, flag, class, and + company history +- **Indicators**: renames, reflagging, and management changes; recent detentions; grey or + black listed flags; class withdrawals; missing P&I cover; MMSI and flag mismatches. + All are facts from Equasis data, with no opaque scores. +- **Search**: ships and companies by name, or ships by exact IMO number, MMSI, or call sign +- **Fleets**: every vessel linked to a company, with roles, class, and detention counts +- **Batches**: hundreds of vessels or companies from a file, continuing past failures and + streaming results as they complete +- **Output for people and programs**: readable tables, or versioned JSON, JSON Lines, and + CSV. Results go to stdout, messages to stderr, and exit codes are documented. +- **Interactive shell** with command menus, history, and scrolling +- **Considerate by default**: request pacing, retries with backoff, and a 24-hour page cache ## Installation -### Prerequisites - -- **pipx**: Recommended for isolated installation of Python CLI tools -- **Python 3.10+**: Required for the application -- **Git** (optional): For cloning the repository - -### Install pipx (if not already installed) - -```bash -# On macOS with Homebrew -brew install pipx - -# On other systems, use pip -python3 -m pip install --user pipx - -# Add pipx to PATH -pipx ensurepath - -# Restart your terminal or run: -source ~/.zshrc # or ~/.bashrc -``` - -### Install Equasis CLI - -#### Option 1: Install from Source (Recommended for Development) - -```bash -# Clone or download the project -cd ~/Projects/ # or your preferred location -git clone equasis-cli # or download and extract -cd equasis-cli - -# Install with pipx in editable mode -pipx install -e . - -# Verify installation -equasis --help -``` - -#### Option 2: Install from Git (if available) - -```bash -# Install directly from repository -pipx install git+ -``` - -#### Option 3: Traditional Virtual Environment (Alternative) - -If you prefer not to use pipx: - -```bash -cd ~/Projects/equasis-cli - -# Create and activate virtual environment -python3 -m venv venv -source venv/bin/activate - -# Install dependencies -pip install -r requirements.txt - -# Install in development mode -pip install -e . - -# Note: You'll need to activate the venv each time you use the tool -``` - -## Configuration - -### Recommended: Secure Credential Storage +equasis-cli requires Python 3.10 or later. Installing with [pipx](https://pipx.pypa.io) +keeps it isolated from other Python packages: -Set up your Equasis credentials using the built-in credential manager: - -```bash -# Interactive credential setup (recommended) -equasis configure --setup -``` - -This securely stores your credentials in `~/.config/equasis-cli/credentials.json` with proper file permissions (owner-only access). Works from any directory and follows industry standards. - -### Alternative Methods - -**Environment Variables:** -```bash -# Set environment variables (works globally) -export EQUASIS_USERNAME=your_username_here -export EQUASIS_PASSWORD=your_password_here -``` - -**Command Line Arguments:** ```bash -# Pass credentials directly (least secure, not recommended) -equasis --username your_user --password your_pass vessel --imo 9074729 +pipx install equasis-cli ``` -### Credential Management Commands - -```bash -# Set up credentials interactively -equasis configure --setup - -# Check credential status (helpful for debugging) -equasis configure --show - -# Remove stored credentials -equasis configure --clear -``` - -### Security Features - -- **XDG-compliant storage**: Follows industry standards (`~/.config/equasis-cli/`) -- **Secure file permissions**: Config file readable only by owner (600 permissions) -- **Credential hierarchy**: Command-line args override environment vars override config file -- **Cross-platform**: Works on macOS, Linux, Windows with appropriate config directories -- **Global operation**: No dependency on project directories or local files - -## Usage - -The tool is available globally after installation via pipx. No need to activate virtual environments or navigate to specific directories. - -### Interactive Mode (Recommended) - -Simply type `equasis` with no arguments to enter interactive mode: - -```bash -equasis -``` - -This launches a **modern professional TUI** with advanced features: - -**TUI Features:** -- **Fixed input area** at bottom (doesn't scroll away) -- **Persistent status bar** showing connection, format, and operation status -- **Gradient color scheme** (professional magenta-to-purple gradient banner) -- **Context-aware ephemeral menus** - Press `?` for help, `Tab` for slash parameters -- **Interactive company selection** - Automatic disambiguation for fleet queries -- **Ephemeral help menu** - Press `?` to show available commands -- **Vim/Less-style scrolling** - Navigate output with `j`/`k` keys -- **Animated loading indicators** - Spinner with shine effect during operations -- **Custom styled output** - Gradient ASCII art and syntax highlighting -- **Auto-completion** - Tab completion for slash parameters - -**Example session:** -``` -──────────────────────────────────────── -> vessel /imo 9074729 -──────────────────────────────────────── -[vessel data table appears here] -──────────────────────────────────────── -● Connected • Format: table • ✓ Vessel 9074729 found - -──────────────────────────────────────── -> search /name "MAERSK" -──────────────────────────────────────── -[search results appear here] -──────────────────────────────────────── -● Connected • Format: table • ✓ Found 45 vessel(s) -> exit -``` - -**Interactive Commands:** -```bash -# Vessel lookup -vessel /imo 9074729 -vessel /imo 9074729 /format json -vessel /imo 9074729 /output vessel_data.json - -# Vessel search -search /name "MAERSK" -search /imo 9074729 # Search by IMO (useful for companies) -search /name "EVER GIVEN" /format json - -# Fleet information -fleet /company "MSC" -fleet /company "MAERSK LINE" /format csv - -# Batch processing -batch /imos "9074729,8515128,9632179" -batch /file fleet_imos.txt /format json /output results.json -batch /companies "MSC,MAERSK,COSCO" /progress - -# Utility commands -format json # Set default output format -status # Show connection status -clear # Clear screen -? # Quick help (clears screen first) -help # Full command list -help vessel # Command-specific help -exit # Exit interactive mode -``` - -**Keyboard Shortcuts:** -- **`?`** - Toggle help menu (keyboard shortcuts overlay) -- **`/`** - Show context-aware command/parameter menu with filtering -- **`Tab`** - Select from menu (replaces from last `/` for parameters) -- **`Enter`** - Execute command -- **`Up/Down`** - Navigate menu / scroll output / command history (context-aware) -- **`Esc`** - Enter scroll mode (navigate output buffer) -- **`i`** - Return to input mode from scroll mode -- **`Ctrl+K/J`** - Quick scroll up/down (5 lines) -- **`Page Up/Down`**, **`Shift+Up/Down`** - Scroll output buffer -- **`Ctrl+C`** - Cancel input and hide menus -- **`Ctrl+D`** - Exit application - -**Interactive Mode Benefits:** -- **Persistent Session**: Authenticate once, run multiple queries -- **Modern Syntax**: Use `/param value` slash command syntax -- **Fast Queries**: No re-authentication between commands -- **Context-Aware Menus**: Commands without `/`, parameters with `/` -- **Format Control**: Set default output format or override per-command -- **File Output**: Save results with `/output filename.ext` -- **Batch Processing**: Process multiple vessels or companies efficiently -- **Scroll Mode**: Vim/less-style navigation for reviewing output - -### Traditional CLI Mode - -You can also use traditional command-line syntax: - -```bash -equasis [global_options] command [command_options] -``` - -### Global Options - -- `--username USERNAME`: Equasis username (optional if set in .env) -- `--password PASSWORD`: Equasis password (optional if set in .env) -- `--output FORMAT`: Output format (table, json, csv) - default: table -- `--output-file FILE`: Save output to file instead of printing - -### Tab Completion - -The tool includes comprehensive tab completion for zsh with intelligent suggestions and examples. - -**Completion Features:** - -- **Commands**: `equasis ` shows available commands with descriptions -- **Command-specific options**: `equasis vessel ` shows only vessel-related options -- **Output formats**: `equasis --output ` shows format options with descriptions -- **Real examples**: Completion provides actual IMO numbers, vessel names, and company names -- **Context-aware**: Different completions for different commands - -**What you can complete:** - -- Commands: `vessel`, `search`, `fleet` -- Global options: `--username`, `--password`, `--output`, `--output-file`, `--help` -- Output formats: `table`, `json`, `csv` (with descriptions) -- Example IMO numbers: Real vessels like EVER GIVEN (9074729) -- Example vessel names: "EVER GIVEN", "MAERSK", "COSCO", etc. -- Major shipping companies: "MAERSK LINE", "MSC", "COSCO SHIPPING", etc. - -**Setting up tab completion:** +Alternatives: `uv tool install equasis-cli`, or `pip install equasis-cli` inside a +virtual environment. -1. **Ensure completion directory exists:** +## Getting started - ```bash - mkdir -p ~/.zsh/completions - ``` - -2. **Copy completion script** (if installed from source): +1. Register for a free account at [equasis.org](https://www.equasis.org). +2. Store your credentials. This checks that the login works and saves the credentials to + a file only you can read: ```bash - cp ~/Projects/equasis-cli/completions/equasis_completion.zsh ~/.zsh/completions/_equasis + equasis configure --setup ``` -3. **Add to .zshrc** (if not already present): + You can also set `EQUASIS_USERNAME` and `EQUASIS_PASSWORD` in the environment. - ```bash - echo 'fpath=(~/.zsh/completions $fpath)' >> ~/.zshrc - echo 'autoload -Uz compinit && compinit' >> ~/.zshrc - ``` +3. Look up a vessel: -4. **Reload shell:** ```bash - source ~/.zshrc + equasis vessel 9811000 ``` -**Usage tips:** - -- Always add a space before pressing TAB: `equasis vessel --imo ` (not `equasis vessel --imo`) -- Use TAB at any point to see available options -- Select from examples or type your own values -- Descriptions help you understand what each option does - -### Commands - -#### Interactive Mode Commands - -When in interactive mode (started with `equasis`), use these commands: - -##### vessel - -Get comprehensive vessel information by IMO number. - -```bash -> vessel /imo 9074729 -> vessel /imo 9074729 /format json -> vessel /imo 9074729 /output vessel_data.json -``` - -**Parameters:** -- `/imo IMO_NUMBER`: Required. The IMO number of the vessel -- `/format FORMAT`: Optional. Output format (table, json, csv) -- `/output FILE_OR_FORMAT`: Optional. Save to file or set format - -##### search - -Search for vessels by name (partial matches supported). - -```bash -> search /name "MAERSK" -> search /name "EVER GIVEN" /format json -> search /name "CONTAINER" /output results.csv -``` - -**Parameters:** -- `/name VESSEL_NAME`: Required. Full or partial vessel name -- `/format FORMAT`: Optional. Output format (table, json, csv) -- `/output FILE_OR_FORMAT`: Optional. Save to file or set format - -##### fleet - -Get fleet information for a shipping company. - -```bash -> fleet /company "MSC" -> fleet /company "MAERSK LINE" /format json -> fleet /company "COSCO" /output fleet_data.json -``` - -**Parameters:** -- `/company COMPANY_NAME`: Required. Company name or identifier -- `/format FORMAT`: Optional. Output format (table, json, csv) -- `/output FILE_OR_FORMAT`: Optional. Save to file or set format - -##### format - -Set default output format for all commands. - -```bash -> format json -> format table -> format csv -``` - -##### status - -Show current session status. - -```bash -> status -``` - -Displays: -- Connection status -- Current output format -- Debug mode status - -##### clear - -Clear the terminal screen. - -```bash -> clear -``` - -##### help - -Show help information. - -```bash -> help # List all commands -> help vessel # Show help for vessel command -> help output # Show help for file output -``` - -##### exit / quit - -Exit interactive mode. - -```bash -> exit -> quit -``` - -You can also use `Ctrl+D` to exit. - -#### Traditional CLI Mode Commands - -When using traditional syntax: - -##### vessel - -```bash -equasis vessel --imo IMO_NUMBER -``` - -**Options:** -- `--imo IMO_NUMBER`: Required. The IMO number of the vessel - -##### search - -```bash -equasis search --name VESSEL_NAME -``` - -**Options:** -- `--name VESSEL_NAME`: Required. Full or partial vessel name - -##### fleet - -```bash -equasis fleet --company COMPANY_NAME -``` - -**Options:** -- `--company COMPANY_NAME`: Required. Company name or identifier - -## Output Formats - -### Table Format (Default) - Comprehensive Intelligence - -Human-readable formatted output with comprehensive vessel intelligence. - -``` -Vessel Information (Comprehensive): -=================================== -IMO Number: 8515128 -Name: ALMAHER -Flag: St.Kitts and Nevis (KNA) -Call Sign: V4FN6 -MMSI: 341787001 -Type: Passenger/Ro-Ro Ship (vehicles) -Gross Tonnage: 1792 -DWT: 964 -Year Built: 1986 -Status: In Service/Commission -Last Update: 02/07/2025 - -Management Companies (3): ------------------------------- -• AL JADARA MEMIZA (Ship manager/Commercial manager) -• AL JADARA MEMIZA (Registered owner) -• UNKNOWN (ISM Manager) - -Classification (1): ----------------- -• International Register of Shipping (IS) - Withdrawn - -Recent PSC Inspections (1 of 1): ------------------------------------ -• 03/04/2009: Tokyo MoU - ⚠️ Detention: N - -Historical Names (3 total): ----------------------- -• ALMAHER (since 01/07/2024) -• St. Anthony de Padua (since 01/08/2012) -• Cebu Ferry 2 (since 01/04/2009) -``` - -### JSON Format - Rich Nested Structure - -Structured data with comprehensive vessel intelligence for programmatic processing. - -```json -{ - "basic_info": { - "imo": "8515128", - "name": "ALMAHER", - "flag": "St.Kitts and Nevis", - "flag_code": "KNA", - "call_sign": "V4FN6", - "mmsi": "341787001", - "vessel_type": "Passenger/Ro-Ro Ship (vehicles)", - "gross_tonnage": "1792", - "dwt": "964", - "year_built": "1986", - "status": "In Service/Commission", - "last_update": "02/07/2025" - }, - "management": [ - { - "name": "AL JADARA MEMIZA", - "role": "Ship manager/Commercial manager", - "imo": "0050002", - "date_effect": "since 01/07/2024" - } - ], - "inspections": [ - { - "date": "03/04/2009", - "psc_organization": "Tokyo MoU", - "detention": "N", - "authority": "Japan", - "port": "Osaka" - } - ], - "historical_names": [ - { - "name": "ALMAHER", - "date_effect": "since 01/07/2024", - "source": "Equasis" - } - ] -} -``` - -### CSV Format - -Basic CSV format for spreadsheet compatibility (basic fields only). - -```csv -8515128,ALMAHER,St.Kitts and Nevis,Passenger/Ro-Ro Ship,964,1792,1986 -``` - -## Examples - -### Interactive Mode Examples - -#### Starting Interactive Mode - -```bash -# Simply run equasis with no arguments -equasis -``` - -You'll see the banner and enter interactive mode: - -``` - _ ___ - ___ ____ ___ ______ ______(_)____ _____/ (_) - / _ \/ __ `/ / / / __ `/ ___/ / ___/_____/ ___/ / / -/ __/ /_/ / /_/ / /_/ (__ ) (__ )_____/ /__/ / / -\___/\__, /\__,_/\__,_/____/_/____/ \___/_/_/ - /_/ - -Maritime Intelligence Tool v2.0.0 - -┌──────────────────────────────────────────────────────────────────────────┐ -│ Important Disclaimers │ -└──────────────────────────────────────────────────────────────────────────┘ -• This tool is NOT associated with, endorsed by, or affiliated with Equasis -• May break if Equasis changes their website structure -• Users must monitor for changes and update parsing logic accordingly -• Respect Equasis Terms of Service and rate limiting -• Use for legitimate maritime research and safety purposes only - -Type 'help' for available commands or 'exit' to quit. -Press ? for quick help. - -> -``` - -#### Comprehensive Vessel Lookup - -Get complete vessel intelligence with management, inspections, and history: - -```bash -> vessel /imo 9074729 - -# Get data in JSON format -> vessel /imo 9074729 /format json - -# Save to file (format auto-detected from extension) -> vessel /imo 9074729 /output vessel_data.json - -# Set JSON as default format for all subsequent commands -> format json -> vessel /imo 9074729 -``` - -**What you get**: Complete vessel profile including management companies, PSC inspection history, historical names/flags, classification details, and geographical tracking - all from a single command! - -#### Batch Processing Multiple Vessels and Companies +## Usage -Process multiple vessels at once: +### Vessels ```bash -# Process multiple IMO numbers directly -> batch /imos "9074729,9632179,9839333" - -# Process IMOs from a file -> batch /file fleet_imos.txt - -# Save batch results to JSON -> batch /imos "9074729,9632179" /format json /output batch_results.json - -# Process with CSV output -> batch /file my_vessels.txt /format csv /output fleet_analysis.csv +equasis vessel 9811000 # full profile as a table +equasis vessel 9811000 -f json # JSON document +equasis vessel 9811000 -o ever-given.json # format follows the file extension +equasis vessel 9811000 9074729 -o vessels.csv # several vessels: one CSV row each +equasis vessel --imo-file fleet.txt -o results.jsonl ``` -Process multiple companies for fleet analysis: - -```bash -# Process multiple companies directly -> batch /companies "MSC,MAERSK,COSCO" - -# Process companies from a file -> batch /company-file shipping_companies.txt +List files contain one IMO number per line. Blank lines and `#` comments are ignored, and +`-` reads from standard input: -# Save company batch results to JSON -> batch /companies "MSC,EVERGREEN" /format json /output fleet_comparison.json - -# Process with progress indicators -> batch /companies "MSC,MAERSK,COSCO,EVERGREEN" /format csv /output major_fleets.csv -``` - -**File Format for Batch Processing:** ```text -# fleet_imos.txt - IMO numbers with comments -9074729 # EMMA MAERSK -9632179 # MSC OSCAR -9839333 # EVER GIVEN -# Empty lines are ignored - -# shipping_companies.txt - Company names with comments -MSC # Mediterranean Shipping Company -MAERSK # A.P. Moller-Maersk -COSCO SHIPPING # China COSCO Shipping -# Comments and empty lines are supported -``` - -#### Multiple Queries in One Session - -```bash -> vessel /imo 9074729 -> search /name "MAERSK" -> fleet /company "MSC" -> status -> exit -``` - -No re-authentication needed between commands! - -### Traditional CLI Examples - -#### Comprehensive Vessel Lookup - -```bash -# Using environment variables for credentials (recommended) -equasis vessel --imo 8515128 - -# Using command-line credentials -equasis --username myuser --password mypass vessel --imo 8515128 - -# Use tab completion for IMO examples -equasis vessel --imo # Shows real vessel examples like EVER GIVEN - -# Get comprehensive data in JSON format -equasis --output json vessel --imo 8515128 - -# Save comprehensive report to file -equasis --output json --output-file vessel_intelligence.json vessel --imo 8515128 - -# BATCH PROCESSING: Multiple vessels at once -equasis vessel --imo 9074729 9632179 9839333 --progress - -# Process IMOs from a file -equasis vessel --imo-file fleet_imos.txt --output csv --output-file results.csv - -# Continue on errors with progress display -equasis vessel --imo-file large_fleet.txt --continue-on-error --progress -``` - -#### Search by Name - -Interactive mode: -```bash -> search /name "MAERSK" -> search /name "EVER GIVEN" /output results.json -``` - -Traditional CLI: -```bash -# Search for vessels with "MAERSK" in the name -equasis search --name "MAERSK" - -# Use tab completion for vessel name examples -equasis search --name # Shows examples like "EVER GIVEN", "MAERSK" - -# Partial name search -equasis search --name "EVER" -``` - -#### Fleet Information - -Interactive mode: -```bash -> fleet /company "MSC" -> fleet /company "MAERSK LINE" /output fleet.csv -``` - -Traditional CLI: -```bash -# Get fleet information for a company -equasis fleet --company "MAERSK LINE" - -# Use tab completion for company examples -equasis fleet --company # Shows major shipping companies -``` - -### Tab Completion Examples - -```bash -# Complete commands -equasis # Shows: vessel, search, fleet - -# Complete command-specific options -equasis vessel # Shows: --imo, --help, --username, etc. -equasis search # Shows: --name, --help, --username, etc. - -# Complete with real examples -equasis vessel --imo # Shows real IMO numbers with vessel names -equasis fleet --company # Shows major shipping companies - -# Complete output formats with descriptions -equasis --output # Shows: table, json, csv with descriptions +# Vessels of interest +9811000 # EVER GIVEN +9074729 ``` -#### Different Output Formats - -Interactive mode: -```bash -# Set default format -> format json -> vessel /imo 9074729 - -# Override format for one command -> vessel /imo 9074729 /format csv +Batches keep going when a lookup fails and report every item. Add `--fail-fast` to stop at +the first failure. -# Save to file (format auto-detected) -> vessel /imo 9074729 /output vessel_data.json -> vessel /imo 9074729 /output vessel_data.csv -``` +### Search -Traditional CLI: ```bash -# JSON output -equasis --output json vessel --imo 9074729 - -# CSV output -equasis --output csv vessel --imo 9074729 - -# Save to file -equasis --output json --output-file vessel_data.json vessel --imo 9074729 -``` - -### Batch Processing - -#### Built-in Batch Processing (Recommended) - -**Vessel Batch Processing:** -```bash -# Process multiple IMO numbers directly -equasis vessel --imo 9074729 9632179 9839333 --output json - -# Process from file with progress indicators -equasis vessel --imo-file fleet_imos.txt --progress --output csv --output-file batch_results.csv - -# Continue on errors instead of stopping -equasis vessel --imo-file fleet_imos.txt --continue-on-error --progress -``` - -**Company Fleet Batch Processing:** -```bash -# Process multiple companies directly -equasis fleet --company "MSC" "MAERSK" "COSCO" --output json - -# Process from file with progress indicators -equasis fleet --company-file shipping_companies.txt --progress --output csv --output-file fleet_analysis.csv - -# Continue on errors instead of stopping -equasis fleet --company-file shipping_companies.txt --continue-on-error --progress -``` - -#### Manual Batch Processing (Legacy) - -```bash -# Process multiple IMO numbers from a file (manual approach) -while read imo; do - echo "Processing IMO: $imo" - equasis vessel --imo "$imo" --output csv >> vessels.csv - sleep 2 # Be respectful to the server -done < imo_list.txt -``` - -#### Integration with Other Tools - -Traditional CLI works great with pipes: - -```bash -# Use with jq for JSON processing -equasis --output json vessel --imo 9074729 | jq '.name' - -# Pipe to grep for filtering -equasis search --name "CONTAINER" | grep "Singapore" - -# Count results -equasis search --name "MAERSK" --output csv | wc -l - -# Use tab completion with pipes -equasis vessel --imo # Select example, then continue with pipe -equasis vessel --imo 9074729 | head -5 -``` - -Interactive mode is best for exploration, traditional CLI for scripting. - -## Architecture & Data Collection - -### Comprehensive Intelligence Strategy - -The Equasis CLI uses a **3-request multi-tab strategy** to collect comprehensive vessel data: - -1. **Ship Info Tab**: Management, classification, geographical data -2. **Inspections Tab**: PSC inspection history and detention records -3. **Ship History Tab**: Historical names, flags, and ownership changes - -Each vessel lookup automatically: - -- Authenticates with Equasis using your credentials -- Makes 3 targeted requests to different Equasis tabs -- Parses rich HTML data structures from each tab -- Merges all data into a comprehensive vessel profile -- Formats output with smart summarization - -### Data Architecture - -``` -Single IMO Request → 3 Parallel Tab Requests → Comprehensive Profile - -┌─────────────────┐ ┌──────────────┐ ┌─────────────────────┐ -│ Ship Info Tab │ │Inspections │ │ Ship History Tab │ -│ │ │Tab │ │ │ -│ • Management │ │ │ │ • Historical Names │ -│ • Classification│ → │ • PSC Records│ → │ • Historical Flags │ -│ • Geographical │ │ • Detentions │ │ • Historical Owners │ -│ • Flag Performance│ │ • Authorities│ │ • Name Changes │ -└─────────────────┘ └──────────────┘ └─────────────────────┘ - ↓ ↓ ↓ - └─────────────── Merge Data ──────────────┘ - ↓ - ┌─────────────────────┐ - │ Complete Vessel │ - │ Intelligence Profile│ - │ │ - │ 50+ Data Points │ - │ Across 9 Categories │ - └─────────────────────┘ -``` - -### Professional Package Structure - -``` -equasis-cli/ -├── equasis_cli/ # Main package -│ ├── __init__.py # Package initialization -│ ├── main.py # CLI interface & argument parsing -│ ├── client.py # EquasisClient & authentication -│ ├── parser.py # HTML parser & data structures -│ └── formatter.py # Output formatting -├── setup.py # Package distribution config -├── requirements.txt # Dependencies -├── completions/ # Tab completion -│ └── equasis_completion.zsh -├── old_files/ # Backup of original code -└── .env # Credentials (not in git) -``` - -## Development - -### Updated Project Structure - -The tool now uses a professional package structure: - -``` -equasis-cli/ -├── equasis_cli/ # Main package directory -│ ├── __init__.py # Package initialization & public API -│ ├── main.py # CLI interface & argument parsing -│ ├── client.py # EquasisClient & web scraping -│ ├── parser.py # Comprehensive HTML parser -│ └── formatter.py # Output formatting (table/JSON/CSV) -├── setup.py # Package distribution configuration -├── requirements.txt # Python dependencies -├── completions/ # Tab completion scripts -│ └── equasis_completion.zsh # Zsh completion (unchanged) -├── local/ # Development and archive files -│ ├── archive/ # Original monolithic code backup -│ ├── development/ # Intermediate development versions -│ └── debug/ # HTML debug files and analysis tools -├── .env # Environment variables (not in git) -├── .env.example # Environment template -├── SESSION_SUMMARY.md # Development session documentation -├── SOLUTION_SUMMARY.md # Technical implementation notes -├── README.md # This file -└── MANIFEST.in # Package manifest +equasis search "EVER GIVEN" # ships and companies by name +equasis search TORM --type companies # companies only +equasis search --mmsi 353136000 # exact identifier (also --imo, --call-sign) +equasis search MAERSK --all-pages # every page of results (default: 3 pages) ``` -### Setting Up Development Environment +### Fleets ```bash -# Clone or download the project -cd ~/Projects/ -git clone equasis-cli # or create directory manually -cd equasis-cli - -# Install in editable mode with pipx (recommended) -pipx install -e . - -# Or use traditional virtual environment -python3 -m venv venv -source venv/bin/activate -pip install -e . +equasis fleet "TORM A/S" # company name +equasis fleet --company-id 0310062 # 7-digit Equasis company number +equasis fleet --company-file companies.txt -o fleets.csv ``` -### Making Changes +When a name matches several companies, equasis-cli asks you to choose (in a terminal) or +lists the matches and exits. Use `--company-id` or `--first-match` in scripts. -With the new package structure, changes to individual modules are automatically available with pipx editable installation: +### Interactive shell -```bash -# Code changes in equasis_cli/*.py files (automatic) -# No action needed - changes are immediately available - -# After modifying setup.py, requirements.txt, or package structure -cd ~/Projects/equasis-cli -pipx install -e . --force +Run `equasis` with no arguments: -# Alternative: upgrade command -pipx upgrade equasis-cli +```text +> vessel /imo 9811000 +> search /name "EVER GIVEN" +> fleet /company "TORM A/S" /output torm.csv +> batch /file fleet.txt /format json /output results.json +> help batch ``` -### Module-Specific Development +Type `/` for a menu of commands or parameters and press `?` for keyboard shortcuts. -```bash -# Test individual components -cd ~/Projects/equasis-cli +### Output and scripting -# Test parser independently -python3 -c "from equasis_cli.parser import EquasisParser; print('Parser loaded successfully')" - -# Test client authentication -python3 -c "from equasis_cli.client import EquasisClient; print('Client loaded successfully')" - -# Test output formatting -python3 -c "from equasis_cli.formatter import OutputFormatter; print('Formatter loaded successfully')" -``` - -### Package Management +| Format | Use | +| --- | --- | +| `table` | Reading in a terminal (default) | +| `json` | A single document with `schema_version`, `retrieved_at`, results, and indicators | +| `jsonl` | One JSON object per line; batches stream results as they complete | +| `csv` | Spreadsheets; one row per vessel with key facts and indicator columns | ```bash -# List installed pipx packages -pipx list - -# Show package details -pipx list --include-deps - -# Uninstall if needed -pipx uninstall equasis-cli - -# Reinstall from scratch -pipx install -e . --force +equasis vessel 9811000 -f json | jq '.indicators.observations' ``` -### Debugging +Exit codes: `0` success, `1` error, `2` invalid usage, `3` authentication failed, +`4` not found, `5` some batch items failed, `6` Equasis page layout changed, +`130` interrupted. -Enable debug logging by modifying the logging level: - -```python -logging.basicConfig(level=logging.DEBUG) -``` +See [docs/usage.md](docs/usage.md) for every command and option and +[docs/output-schema.md](docs/output-schema.md) for the output fields. -Or set environment variable: - -```bash -export PYTHONPATH=$(pwd) -python3 -c "import logging; logging.basicConfig(level=logging.DEBUG)" equasis_cli.py vessel --imo 9074729 -``` - -### Testing HTML Parsing - -Since Equasis HTML structure may change, you can test parsing with saved HTML: +### Python ```python -def test_parsing(): - with open("sample_vessel_page.html", "r") as f: - html = f.read() - - # Import the client class - from equasis_cli import EquasisClient +from equasis_cli import EquasisClient +from equasis_cli.enrichment import compute_indicators - client = EquasisClient("", "") - result = client._parse_vessel_info(html, "1234567") - print(result) - - -if __name__ == "__main__": - test_parsing() +with EquasisClient("you@example.com", "password") as client: + vessel = client.get_vessel("9811000") + print(vessel.name, vessel.flag, len(vessel.inspections)) + print(compute_indicators(vessel).observations) ``` -### Extending Functionality - -The modular design makes it easy to add features: - -1. **Add new search types**: Extend the argument parser and add methods to `EquasisClient` -2. **Create new formatters**: Add methods to `OutputFormatter` class -3. **Add data validation**: Create validation functions for input data -4. **Implement caching**: Add caching layer to reduce redundant requests - -## Network Resilience & Retry Logic - -equasis-cli includes enterprise-grade retry functionality to handle network issues and server-side problems gracefully. - -### Automatic Retry Features - -- **Exponential Backoff**: Delays increase exponentially between retry attempts (1s, 2s, 4s, etc.) -- **Jitter**: Random variance added to prevent "thundering herd" problems -- **Smart Error Classification**: Distinguishes between retryable and non-retryable errors -- **Configurable Parameters**: Customizable retry limits and timing - -### Retried Error Types - -**Network Errors (Always Retried):** -- Connection timeouts and failures -- DNS resolution failures -- Network interruptions -- Remote connection drops - -**HTTP Errors (Conditionally Retried):** -- `429` Too Many Requests -- `500` Internal Server Error -- `502` Bad Gateway -- `503` Service Unavailable -- `504` Gateway Timeout -- CloudFlare error codes (520-524) - -**Non-Retried Errors:** -- `401` Unauthorized (credential issues) -- `404` Not Found (vessel doesn't exist) -- `400` Bad Request (malformed request) - -### Default Retry Configuration +## Responsible use -- **Maximum Retries**: 3 attempts per request -- **Base Delay**: 1 second -- **Maximum Delay**: 60 seconds -- **Backoff Multiplier**: 2.0 (exponential) -- **Jitter**: ±25% randomization +equasis-cli reads the same pages you would open in a browser, using your own Equasis +account. Equasis is a free public service, so please: -Example retry sequence for a connection error: -``` -Attempt 1: Immediate -Attempt 2: ~1s delay -Attempt 3: ~2s delay -Attempt 4: ~4s delay -``` - -### Viewing Retry Activity - -Enable debug logging to see retry attempts: -```bash -equasis --debug vessel --imo 9074729 -``` +- Follow the [Equasis terms and conditions](https://www.equasis.org). +- Keep request volumes modest. Requests are paced at one per second by default (`--delay`), + each vessel takes three requests, and pages are cached for 24 hours (`--refresh` bypasses + the cache). Split very large batches across days. +- Verify important findings on equasis.org before relying on them. ## Troubleshooting -### Common Issues - -#### Command Not Found - -``` -zsh: command not found: equasis -``` - -**Solutions:** - -1. Ensure pipx installation was successful: `pipx list` -2. Check that pipx is in your PATH: `pipx ensurepath` then restart terminal -3. Verify the package is installed: `pipx list | grep equasis` -4. If using virtual environment instead of pipx, ensure it's activated - -#### Tab Completion Not Working - -**Solutions:** - -1. Ensure completion script exists: `ls ~/.zsh/completions/_equasis` -2. Check completion script is properly formatted (no syntax errors) -3. Verify your `.zshrc` has completion initialization: - ```bash - grep -E "(fpath|compinit)" ~/.zshrc - ``` - Should show: - ```bash - fpath=(~/.zsh/completions $fpath) - autoload -Uz compinit && compinit - ``` -4. Clear completion cache and reload: - ```bash - rm -f ~/.zcompdump* - source ~/.zshrc - ``` -5. Test with a simple completion first: `equasis ` - -#### Completion Shows Wrong Options - -**Solutions:** - -1. Ensure you're using spaces correctly: `equasis vessel --imo ` (space before TAB) -2. Clear old cached completions: `unfunction _equasis 2>/dev/null && autoload -Uz compinit && compinit` -3. Reinstall completion script: `cp ~/Projects/equasis-cli/completions/equasis_completion.zsh ~/.zsh/completions/_equasis` - -#### Connection Issues - -``` -Error: Failed to access login page: 500 -``` - -**Solutions:** - -1. Check your internet connection -2. Verify Equasis website is accessible (equasis.org) -3. Try again later (server may be temporarily unavailable) -4. Check if you're behind a firewall or proxy - -#### Parsing Errors - -``` -Error: No vessel found with IMO: 1234567 -``` - -**Solutions:** - -1. Verify the IMO number is correct and exists in Equasis -2. The HTML parsing may need adjustment for current Equasis structure -3. Enable debug logging to see raw HTML response -4. Check if Equasis has changed their page structure - -#### Python Environment Issues - -``` -ModuleNotFoundError: No module named 'requests' -``` - -**Solutions:** - -1. Ensure virtual environment is activated -2. Reinstall requirements: `pip install -r requirements.txt` -3. Check Python version compatibility - -### Rate Limiting - -If you encounter rate limiting: - -1. Increase delay between requests in the code -2. Reduce concurrent operations -3. Implement exponential backoff -4. Consider contacting Equasis for API access - -### Debug Mode - -Enable comprehensive logging: - -```bash -PYTHONPATH=$(pwd) python3 -c " -import logging -logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') -exec(open('equasis_cli.py').read()) -" vessel --imo 9074729 -``` - -## Legal and Compliance - -### Terms of Service - -- This tool accesses publicly available data from Equasis -- Users must comply with Equasis Terms of Service -- Respect rate limits and server resources -- Use for legitimate maritime research and safety purposes only - -### Best Practices - -- **Rate Limiting**: Don't overload Equasis servers -- **Data Usage**: Use retrieved data responsibly -- **Authentication**: Keep credentials secure -- **Compliance**: Follow maritime industry regulations - -### Disclaimer - -- This tool is for educational and research purposes -- Users are responsible for compliance with applicable laws -- No warranty is provided for data accuracy or availability -- The tool may break if Equasis changes their website structure +| Problem | What to do | +| --- | --- | +| `authentication failed` (exit 3) | Run `equasis configure --test`, and check that you can log in on equasis.org | +| `could not read the Equasis page` (exit 6) | Equasis may have changed its website. Re-run with `--save-html DIR` and [open an issue](https://github.com/rhinonix/equasis-cli/issues/new/choose). Remove your name from saved pages before attaching them. | +| `rate limiting requests (HTTP 429)` | Wait a while, then retry with a larger `--delay` | +| Unexpected or stale results | Add `--refresh`, or run `equasis cache clear` | +| Anything else | Re-run with `--debug` to log HTTP activity | ## Contributing -Contributions are welcome! Please follow these guidelines: - -### Reporting Issues - -1. Check existing issues before creating new ones -2. Provide detailed error messages and steps to reproduce -3. Include your Python version and operating system -4. Don't include credentials in issue reports - -### Submitting Changes - -1. Fork the repository -2. Create a feature branch -3. Make your changes with appropriate tests -4. Update documentation as needed -5. Submit a pull request with clear description - -### Development Guidelines - -- Follow PEP 8 style guidelines -- Add type hints for new functions -- Include docstrings for public methods -- Test changes with different Equasis data -- Update README for new features +Bug reports and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) and +[docs/development.md](docs/development.md). Report security issues privately as described +in [SECURITY.md](SECURITY.md). ## License equasis-cli is source-available software licensed under the -[PolyForm Noncommercial License 1.0.0](LICENSE). - -- Free for personal, research, educational, nonprofit, and government use -- Journalists and nonprofit researchers have an additional permission, described in [LICENSING.md](LICENSING.md) -- Commercial use requires a separate license: contact [rhinonix.github.exclaim769@slmail.me](mailto:rhinonix.github.exclaim769@slmail.me) - -Releases up to and including v2.0.0 remain available under the -[Creative Commons Attribution-NonCommercial-ShareAlike 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) -license. - ---- +[PolyForm Noncommercial License 1.0.0](LICENSE). It is free for personal, research, +educational, nonprofit, and government use, and journalists and nonprofit researchers have +an additional permission. Commercial use requires a separate license. See +[LICENSING.md](LICENSING.md) for details and contact information. -**Note**: This tool interfaces with Equasis through web scraping. The HTML parsing may need updates if Equasis changes their website structure. Users should monitor for any changes and update the parsing logic accordingly. +equasis-cli is not affiliated with or endorsed by Equasis. diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..cbb96fe --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,89 @@ +# Releasing + +Releases are published to [PyPI](https://pypi.org/p/equasis-cli) by the +[Release workflow](.github/workflows/release.yml) using +[trusted publishing](https://docs.pypi.org/trusted-publishers/): PyPI trusts this +repository's workflow directly, so no API tokens are created or stored. + +## One-time setup + +Do these steps once, before the first release. + +### 1. Create the accounts + +1. Register at [pypi.org](https://pypi.org/account/register/) and at + [test.pypi.org](https://test.pypi.org/account/register/). TestPyPI is a separate + practice site with its own accounts. +2. Verify your e-mail address on both sites. +3. Enable two-factor authentication on both (Account settings, then "Add 2FA with + authentication application"). PyPI requires it. Store the recovery codes somewhere safe. + +### 2. Register the trusted publishers + +The project does not exist on PyPI yet, so add a *pending* publisher; the first upload +creates the project. + +On **test.pypi.org**, open [Account settings, Publishing](https://test.pypi.org/manage/account/publishing/) +and add a GitHub pending publisher with: + +| Field | Value | +| --- | --- | +| PyPI project name | `equasis-cli` | +| Owner | `rhinonix` | +| Repository name | `equasis-cli` | +| Workflow name | `release.yml` | +| Environment name | `testpypi` | + +Repeat on **pypi.org** at [Account settings, Publishing](https://pypi.org/manage/account/publishing/) +with the same values, except **Environment name** `pypi`. + +### 3. Create the GitHub environments + +In the repository, open **Settings, Environments**: + +1. Create an environment named `testpypi`. +2. Create an environment named `pypi`. Under **Deployment protection rules**, enable + **Required reviewers** and add yourself. Nothing is then published to PyPI until you + approve it. + +## Every release + +1. **Prepare the release pull request.** + - Set the version in `src/equasis_cli/_version.py` (for example `3.1.0`). + - In `CHANGELOG.md`, rename "Unreleased" to the version and date, and add a new empty + "Unreleased" section and comparison link. + - Merge the pull request after CI passes. + +2. **Dry run on TestPyPI.** In the **Actions** tab, open the Release workflow, choose + **Run workflow** on `main`, and wait for it to finish. Then check the package in a clean + environment: + + ```bash + pipx install --index-url https://test.pypi.org/simple/ \ + --pip-args="--extra-index-url https://pypi.org/simple/" equasis-cli + equasis --version + equasis vessel 9811000 + pipx uninstall equasis-cli + ``` + + A version number can be uploaded to TestPyPI only once. To repeat a dry run for the + same version, add a suffix such as `3.1.0rc1` to the version first. + +3. **Publish.** Create a GitHub release: + - Go to **Releases**, then **Draft a new release**. + - Create a new tag named `v` plus the version (for example `v3.1.0`) on `main`. The + workflow checks that the tag matches the package version. + - Use the version as the title, and paste the changelog section as the description. + - Choose **Publish release**. + +4. **Approve.** Open the running Release workflow and approve the `pypi` deployment. + +5. **Verify.** + + ```bash + pipx install equasis-cli # or: pipx upgrade equasis-cli + equasis --version + ``` + +Published versions cannot be replaced on PyPI. If something is wrong, fix it and release +a new version; a broken release can be yanked from the project's PyPI page. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..1e61841 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,40 @@ +# Security policy + +## Supported versions + +Security fixes are released for the latest minor version of equasis-cli. + +| Version | Supported | +| --- | --- | +| 3.x | Yes | +| 2.x and earlier | No | + +## Reporting a vulnerability + +Please report vulnerabilities privately, either through +[GitHub private vulnerability reporting](https://github.com/rhinonix/equasis-cli/security/advisories/new) +or by e-mail to +[rhinonix.github.exclaim769@slmail.me](mailto:rhinonix.github.exclaim769@slmail.me). + +Include a description of the issue, the steps to reproduce it, the affected version, and +its potential impact. You can expect an acknowledgement within five working days. Please +allow time for a fix to be released before disclosing the issue publicly. + +## Scope + +Examples of issues to report: + +- Exposure of Equasis credentials, for example through logs, error messages, file + permissions, or saved output +- Writing files outside the location the user asked for +- Vulnerabilities in how equasis-cli handles content received from Equasis + +Problems with the Equasis website itself should be reported to Equasis. + +## Handling credentials safely + +- Prefer `equasis configure --setup` or environment variables over `--password`, which is + visible in shell history and process listings. +- The credentials file and page cache are created readable only by you. Keep it that way. +- Pages saved with `--save-html` and the page cache contain your Equasis account name. + Remove it before sharing a page. diff --git a/completions/_equasis b/completions/_equasis new file mode 100644 index 0000000..e8eb741 --- /dev/null +++ b/completions/_equasis @@ -0,0 +1,98 @@ +#compdef equasis +# zsh completion for equasis-cli. Copy this file to a directory on $fpath. + +_equasis_formats() { + local -a formats=( + 'table:readable table (default)' + 'json:JSON document' + 'jsonl:JSON Lines, one record per line' + 'csv:comma-separated values' + ) + _describe 'output format' formats +} + +_equasis_common=( + '(-f --format --output)'{-f,--format}'[output format]:format:_equasis_formats' + '(-o --output-file)'{-o,--output-file}'[write results to a file]:file:_files' + '(-q --quiet)'{-q,--quiet}'[only print results and errors]' + '--no-color[disable colored messages]' + '--username[Equasis username (e-mail address)]:username:' + '--password[Equasis password]:password:' + '--delay[minimum pause between requests]:seconds:' + '--debug[log HTTP activity to stderr]' + '--save-html[save Equasis pages for troubleshooting]:directory:_files -/' + '--no-cache[do not read or write the page cache]' + '--refresh[fetch fresh pages even if cached]' +) + +_equasis() { + local curcontext="$curcontext" state line + typeset -A opt_args + + _arguments -C \ + '(- *)'{-h,--help}'[show help]' \ + '(- *)--version[show version]' \ + '--interactive[start the interactive shell]' \ + $_equasis_common \ + '1: :->command' \ + '*:: :->arguments' && return + + case $state in + command) + local -a commands=( + 'vessel:full vessel profile by IMO number' + 'search:search ships and companies' + 'fleet:vessels associated with a company' + 'configure:manage stored credentials' + 'cache:show or clear cached Equasis pages' + 'interactive:start the interactive shell' + ) + _describe 'command' commands + ;; + arguments) + case $line[1] in + vessel) + _arguments \ + '--imo-file[read IMO numbers from a file]:file:_files' \ + '--fail-fast[stop at the first failed lookup]' \ + $_equasis_common \ + '*:IMO number:' + ;; + search) + _arguments \ + '--imo[find a ship by IMO number]:IMO number:' \ + '--mmsi[find a ship by MMSI]:MMSI:' \ + '--call-sign[find a ship by call sign]:call sign:' \ + '--type[result types]:type:(all ships companies)' \ + '(--all-pages)--max-pages[result pages to fetch]:pages:' \ + '(--max-pages)--all-pages[fetch every page]' \ + $_equasis_common \ + '1:name:' + ;; + fleet) + _arguments \ + '*--company-id[Equasis company number]:company number:' \ + '--company-file[read companies from a file]:file:_files' \ + '--first-match[use the best match for ambiguous names]' \ + '--max-pages[limit fleet pages]:pages:' \ + '--fail-fast[stop at the first failed lookup]' \ + $_equasis_common \ + '*:company name or number:' + ;; + configure) + _arguments \ + '(--show --test --clear)--setup[store credentials interactively]' \ + '(--setup --test --clear)--show[show where credentials come from]' \ + '(--setup --show --clear)--test[check that the credentials work]' \ + '(--setup --show --test)--clear[delete stored credentials]' \ + $_equasis_common + ;; + cache) + _arguments $_equasis_common '1:action:(info clear)' + ;; + esac + ;; + esac +} + +_equasis "$@" diff --git a/completions/equasis.bash b/completions/equasis.bash new file mode 100644 index 0000000..654060b --- /dev/null +++ b/completions/equasis.bash @@ -0,0 +1,54 @@ +# bash completion for equasis-cli. Source this file from ~/.bashrc. + +_equasis() { + local cur prev words cword + _init_completion 2>/dev/null || { + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + words=("${COMP_WORDS[@]}") + cword=$COMP_CWORD + } + + local commands="vessel search fleet configure cache interactive" + local common="--format --output-file --quiet --no-color --username --password --delay --debug --save-html --no-cache --refresh --help" + + case "$prev" in + -f|--format|--output) + COMPREPLY=($(compgen -W "table json jsonl csv" -- "$cur")) + return + ;; + -o|--output-file|--imo-file|--company-file) + COMPREPLY=($(compgen -f -- "$cur")) + return + ;; + --save-html) + COMPREPLY=($(compgen -d -- "$cur")) + return + ;; + --type) + COMPREPLY=($(compgen -W "all ships companies" -- "$cur")) + return + ;; + esac + + local command="" word + for word in "${words[@]:1:cword-1}"; do + case "$word" in + vessel|search|fleet|configure|cache|interactive) command="$word"; break ;; + esac + done + + local options + case "$command" in + "") options="$commands --version --interactive $common" ;; + vessel) options="--imo-file --fail-fast $common" ;; + search) options="--imo --mmsi --call-sign --type --max-pages --all-pages $common" ;; + fleet) options="--company-id --company-file --first-match --max-pages --fail-fast $common" ;; + configure) options="--setup --show --test --clear $common" ;; + cache) options="info clear $common" ;; + interactive) options="" ;; + esac + COMPREPLY=($(compgen -W "$options" -- "$cur")) +} + +complete -F _equasis equasis diff --git a/completions/equasis_completion.zsh b/completions/equasis_completion.zsh deleted file mode 100644 index 20c40d5..0000000 --- a/completions/equasis_completion.zsh +++ /dev/null @@ -1,127 +0,0 @@ -#compdef equasis - -_equasis() { - local context state state_descr line - typeset -A opt_args - - _arguments -C \ - '(--help -h)'{--help,-h}'[Show help message]' \ - '--username[Equasis username]:username:' \ - '--password[Equasis password]:password:' \ - '--output[Output format]:format:_equasis_output_formats' \ - '--output-file[Output file path]:file:_files' \ - '1: :_equasis_commands' \ - '*:: :->command_args' \ - && return 0 - - case $state in - command_args) - case $words[1] in - vessel) - _equasis_vessel_args - ;; - search) - _equasis_search_args - ;; - fleet) - _equasis_fleet_args - ;; - esac - ;; - esac -} - -_equasis_commands() { - local -a commands=( - 'vessel:Get detailed vessel information by IMO number' - 'search:Search for vessels by name (supports partial matches)' - 'fleet:Get fleet information for a shipping company' - ) - _describe 'equasis commands' commands -} - -_equasis_vessel_args() { - _arguments \ - '(--help -h)'{--help,-h}'[Show help for vessel command]' \ - '--imo[IMO number (7 digits)]:imo_number:_equasis_imo_examples' \ - '--username[Equasis username]:username:' \ - '--password[Equasis password]:password:' \ - '--output[Output format]:format:_equasis_output_formats' \ - '--output-file[Output file path]:file:_files' -} - -_equasis_search_args() { - _arguments \ - '(--help -h)'{--help,-h}'[Show help for search command]' \ - '--name[Vessel name (partial match supported)]:vessel_name:_equasis_vessel_examples' \ - '--username[Equasis username]:username:' \ - '--password[Equasis password]:password:' \ - '--output[Output format]:format:_equasis_output_formats' \ - '--output-file[Output file path]:file:_files' -} - -_equasis_fleet_args() { - _arguments \ - '(--help -h)'{--help,-h}'[Show help for fleet command]' \ - '--company[Company name or identifier]:company_name:_equasis_company_examples' \ - '--username[Equasis username]:username:' \ - '--password[Equasis password]:password:' \ - '--output[Output format]:format:_equasis_output_formats' \ - '--output-file[Output file path]:file:_files' -} - -_equasis_output_formats() { - local -a formats=( - 'table:Human-readable table format (default)' - 'json:JSON format for programmatic use' - 'csv:Comma-separated values for spreadsheets' - ) - _describe 'output formats' formats -} - -_equasis_imo_examples() { - # Provide actual example IMO numbers that users can select - local -a examples=( - '9074729:EVER GIVEN (container ship)' - '9321483:MSC GULSUN (container ship)' - '9516579:HMM ALGECIRAS (container ship)' - '9811000:SYMPHONY OF THE SEAS (cruise ship)' - '9398538:ALLURE OF THE SEAS (cruise ship)' - ) - _describe 'example IMO numbers' examples - # Also allow typing custom numbers - _message 'or enter 7-digit IMO number' -} - -_equasis_vessel_examples() { - # Provide example vessel names - local -a examples=( - '"EVER GIVEN":Famous container ship' - '"MSC GULSUN":Large container vessel' - '"MAERSK":Search all Maersk vessels' - '"COSCO":Search all COSCO vessels' - '"SYMPHONY":Search Symphony vessels' - ) - _describe 'example vessel names' examples - _message 'or enter vessel name (partial matches supported)' -} - -_equasis_company_examples() { - # Provide common shipping companies as actual completions - local -a companies=( - '"MAERSK LINE":A.P. Moller - Maersk' - '"MSC":Mediterranean Shipping Company' - '"COSCO SHIPPING":China COSCO Shipping' - '"CMA CGM":CMA CGM Group' - '"HAPAG-LLOYD":Hapag-Lloyd' - '"EVERGREEN MARINE":Evergreen Marine' - '"YANG MING":Yang Ming Marine Transport' - '"HMM":Hyundai Merchant Marine' - '"ONE":Ocean Network Express' - '"ZIM":ZIM Integrated Shipping Services' - ) - _describe 'major shipping companies' companies - _message 'or enter company name' -} - -_equasis "$@" diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..f1a6b45 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,104 @@ +# Development guide + +## Setup + +```bash +git clone https://github.com/rhinonix/equasis-cli.git +cd equasis-cli +python3 -m venv .venv +source .venv/bin/activate # Windows: .venv\Scripts\activate +pip install -e ".[dev]" +pre-commit install +``` + +## Checks + +```bash +ruff check . # lint +ruff format . # format +mypy # strict type checking of src/ +pytest # offline test suite +pytest --cov # with coverage +``` + +CI runs the same checks on Python 3.10 to 3.14 on Linux, and on 3.10 and 3.14 on macOS and +Windows. It also builds the package and smoke-tests the wheel. + +### Live tests + +`tests/test_live.py` makes a handful of real requests to equasis.org to detect website +changes. The tests are skipped unless enabled: + +```bash +EQUASIS_LIVE_TESTS=1 pytest tests/test_live.py -v +``` + +They use your configured credentials. The "Live canary" workflow runs them weekly when the +repository secrets `EQUASIS_USERNAME` and `EQUASIS_PASSWORD` are set. + +## Project layout + +```text +src/equasis_cli/ + cli.py argument parsing, exit codes, command execution + tui/app.py interactive shell (prompt_toolkit layout and key bindings) + tui/commands.py interactive command parsing and execution, no terminal code + service.py batch processing and company resolution shared by both interfaces + client.py EquasisClient: login, session renewal, vessels, search, fleets + transport.py HTTP session, request pacing, retries, response capture + cache.py on-disk page cache + parsing/ one parser per Equasis page type + html.py table and panel helpers (header lookup, rowspan handling) + page.py classifies login, not-found, and error pages + ship.py Ship Info, Inspections, Ship History + search.py search results + fleet.py Fleet info + enrichment/ indicators and country and MID reference data + models.py dataclasses for all results + output.py table, JSON, JSON Lines, and CSV rendering; file writing + credentials.py credential resolution and storage + validation.py IMO, MMSI, and company number validation; list parsing + exceptions.py exception hierarchy + console.py stderr messages +tests/ + fixtures/ anonymized pages recorded from equasis.org + tools/make_fixture.py +``` + +Requests flow through the layers in one direction: `cli.py` or `tui/commands.py` calls +`service.py` or `client.py`, which uses `transport.py` and `cache.py` to fetch pages and +`parsing/` to turn them into `models.py` objects. `output.py` renders the objects. + +### Parsing principles + +- Find panels by their visible title and table columns by their header text, never by + position. Equasis reorders panels between pages. +- Ignore rows marked `hidden-lg hidden-md`, which duplicate content for small screens. +- Expand `rowspan` cells. Equasis nests the spanned row inside the previous ``, so + rows are collected recursively. +- Raise `LayoutChangedError` when an expected heading or column is missing, so users get a + clear message instead of silently wrong data. + +## Test fixtures + +Parser tests run against real pages stored in `tests/fixtures/`. To add or refresh one: + +1. Capture the page: `equasis vessel 9811000 --save-html /tmp/pages --refresh`. Files are + numbered in request order, for example `003-get-restricted-ShipInfo.html`. +2. Anonymize and minimize it: + + ```bash + python tests/tools/make_fixture.py /tmp/pages/003-get-restricted-ShipInfo.html \ + tests/fixtures/ship_info_9811000.html + ``` + +3. Check that parsing the fixture gives the same result as parsing the original page. +4. Run `pytest tests/test_parsing_pages.py`. It fails if any fixture still contains an + account name. +5. Update `tests/fixtures/README.md`. + +Never commit pages that have not been through `make_fixture.py`. + +## Releasing + +See [RELEASING.md](../RELEASING.md). diff --git a/docs/output-schema.md b/docs/output-schema.md new file mode 100644 index 0000000..8635edd --- /dev/null +++ b/docs/output-schema.md @@ -0,0 +1,318 @@ +# Output formats and schema + +equasis-cli writes four formats. `table` is for reading in a terminal and may change +between releases. `json`, `jsonl`, and `csv` are for programs, and changes to them follow +the rules below. + +- [Conventions](#conventions) +- [JSON](#json) +- [JSON Lines](#json-lines) +- [CSV](#csv) +- [Vessel fields](#vessel-fields) +- [Indicators](#indicators) +- [Search fields](#search-fields) +- [Fleet fields](#fleet-fields) +- [Stability](#stability) + +## Conventions + +- Dates are ISO 8601 (`2026-09-15`). Timestamps are UTC (`2026-09-16T15:08:00Z`). +- Tonnages, years, and counts are integers. +- Values Equasis does not show are `null` in JSON and empty in CSV. +- Text is exactly as Equasis shows it, with whitespace normalized. +- Lists of history records are ordered newest first, as on Equasis. +- JSON and CSV files are UTF-8. + +## JSON + +Every JSON document has the same envelope: + +| Field | Description | +| --- | --- | +| `schema_version` | Version of this layout (currently `1`) | +| `type` | `vessel`, `search`, `fleet`, `batch_vessels`, or `batch_fleets` | +| `retrieved_at` | When the Equasis pages were fetched. This is earlier than now if they came from the cache. | +| `source` | `https://www.equasis.org` | + +The payload key depends on the type: + +| `type` | Payload | +| --- | --- | +| `vessel` | `vessel` ([vessel fields](#vessel-fields)) and `indicators` ([indicators](#indicators)) | +| `search` | `search` ([search fields](#search-fields)) | +| `fleet` | `fleet` ([fleet fields](#fleet-fields)) | +| `batch_vessels`, `batch_fleets` | `summary` and `results` (see below) | + +Example, with lists shortened to one entry: + +```json +{ + "schema_version": 1, + "type": "vessel", + "retrieved_at": "2026-09-16T15:08:00Z", + "source": "https://www.equasis.org", + "vessel": { + "imo": "9811000", + "name": "EVER GIVEN", + "flag": "Panama", + "flag_code": "PAN", + "call_sign": "H3RC", + "mmsi": "353136000", + "gross_tonnage": 219079, + "deadweight": 199489, + "ship_type": "Container Ship", + "year_built": 2018, + "status": "In Service/Commission", + "status_since": "2021-11-14", + "particulars_updated": "2026-09-15", + "flag_performance": { + "paris_mou": "White", + "tokyo_mou": "White", + "uscg_targeted": true, + "iacs_classed": true, + "detention_rate_36_months": 0.0 + }, + "management": [ + { + "company_id": "1135348", + "name": "BERNHARD SCHULTE-HKG LP", + "role": "Ship manager/Commercial manager", + "address": "Room 2602, K Wah Centre, 191, Java Road, North Point, Hong Kong, China.", + "since": "2021-03-01" + } + ], + "class_status": [ + {"society": "American Bureau of Shipping (IACS)", "status": "Delivered", "since": "2018-10-25", "reason": null} + ], + "class_surveys": [ + {"society": "American Bureau of Shipping (IACS)", "last_renewal": "2023-07-12", "next_renewal": "2028-09-24"} + ], + "safety_certificates": [ + { + "society": "American Bureau of Shipping (IACS)", + "survey_date": "2023-12-04", + "expiry_date": "2029-01-31", + "status_change_date": "2018-09-25", + "status": "Delivered", + "reason": null, + "convention": "Convention" + } + ], + "pandi": [{"name": "UK P&I Club", "inception": "2018-09-25"}], + "sightings": [{"period": "September 2026", "areas": ["West Europe"], "source": "MarineTraffic"}], + "inspections": [ + { + "authority": "Greece", + "port": "Piraeus", + "date": "2026-04-29", + "detained": false, + "reports": [ + { + "psc_organisation": "Paris MoU", + "inspection_type": "Initial inspection", + "duration_days": null, + "deficiencies": null, + "inspection_id": "6406427" + } + ], + "deficiencies": null + } + ], + "human_element_deficiencies": [], + "name_history": [{"name": "EVER GIVEN", "since": "2018-09-01", "until": null, "source": "IHS Maritime"}], + "flag_history": [{"flag": "Panama", "since": "2018-09-01", "until": null, "source": "IHS Maritime"}], + "class_history": [ + {"society": "American Bureau of Shipping (IACS)", "survey_date": "2023-07-12", "source": "American Bureau of Shipping"} + ], + "company_history": [ + { + "company": "BERNHARD SCHULTE-HKG LP", + "role": "Ship manager/Commercial manager", + "since": "2021-03-01", + "until": null, + "source": "IHS Maritime" + } + ], + "warnings": [], + "retrieved_at": "2026-09-16T15:08:00Z" + }, + "indicators": { + "reference_date": "2026-09-16", + "age_years": 8, + "name_changes_12_months": 0, + "name_changes_36_months": 0, + "flag_changes_12_months": 0, + "flag_changes_36_months": 0, + "management_changes_36_months": 0, + "inspections_36_months": 6, + "detentions_36_months": 0, + "deficiencies_36_months": 19, + "last_inspection": "2026-04-29", + "last_detention": null, + "flag_on_paris_mou_grey_or_black_list": false, + "flag_on_tokyo_mou_grey_or_black_list": false, + "uscg_targeted_flag": true, + "iacs_classed": true, + "class_withdrawn_or_suspended_36_months": [], + "has_pandi_insurance": true, + "mmsi_country": "Panama", + "mmsi_matches_flag": true, + "observations": ["Flag is targeted by the US Coast Guard"] + } +} +``` + +### Batch documents + +| Field | Description | +| --- | --- | +| `summary.total` | Number of inputs | +| `summary.succeeded`, `summary.failed` | Counts by outcome | +| `summary.started_at` | When the batch started | +| `results[]` | One record per input, in input order | + +Each record in `results` has: + +| Field | Description | +| --- | --- | +| `query` | The input as given (IMO number, company name, or company number) | +| `status` | `ok`, `not_found`, `ambiguous` (fleets only), or `error` | +| `error` | Explanation when `status` is not `ok` | +| `warnings` | Non-fatal problems, such as a failed IMO check digit | +| `elapsed_seconds` | Time spent on this item | +| `vessel` or `fleet` | The result, or `null` | +| `indicators` | Vessel batches only: [indicators](#indicators) for the vessel, or `null` | + +## JSON Lines + +JSON Lines output has one JSON object per line: + +| Result | Lines | +| --- | --- | +| Vessel | One line containing the full JSON document | +| Search | One line per result, with `"kind": "ship"` or `"kind": "company"` added | +| Fleet | One line per vessel, with `company_id` and `company_name` added | +| Batch | One line per input with the fields of a batch record, written as soon as that item completes | + +Because batch lines are streamed, a partially completed batch still leaves valid output. + +## CSV + +CSV files have a header row, quote values where needed, and use `true` and `false` for +booleans. Multiple values in one cell are separated by `; `. + +**Vessels**: one row per vessel: + +| Column | Description | +| --- | --- | +| `imo`, `name`, `flag`, `flag_code`, `call_sign`, `mmsi` | Identity | +| `gross_tonnage`, `deadweight`, `ship_type`, `year_built` | Particulars | +| `status`, `status_since`, `particulars_updated` | Status | +| `paris_mou`, `tokyo_mou`, `uscg_targeted` | Flag performance | +| `registered_owner`, `ship_manager`, `ism_manager` | Current companies in these roles | +| `class_societies` | Classification societies, excluding those with status `Withdrawn` | +| `pandi` | P&I insurers | +| `inspections`, `detentions`, `last_inspection` | All recorded PSC inspections | +| `name_changes`, `flag_changes` | All recorded changes | +| `age_years`, `name_changes_36_months`, `flag_changes_36_months`, `management_changes_36_months`, `inspections_36_months`, `detentions_36_months`, `mmsi_matches_flag`, `observations` | [Indicators](#indicators) | +| `warnings` | Parts of the profile that could not be read | + +**Vessel batches** have the same columns, preceded by `query`, `lookup_status`, and +`error`. + +**Search results**: `kind` (`ship` or `company`), `imo`, `company_id`, `name`, +`gross_tonnage`, `ship_type`, `year_built`, `flag`, `address`. + +**Fleets**: one row per vessel: `company_id`, `company_name`, `imo`, `name`, +`gross_tonnage`, `ship_type`, `year_built`, `flag`, `class_societies`, +`detentions_as_ism_manager_3y`, `detentions_all_companies_3y`, `roles`. + +**Fleet batches** have the fleet columns preceded by `query`, `lookup_status`, and +`error`, with one row per vessel. A failed company has a single row. + +## Vessel fields + +| Field | Description | +| --- | --- | +| `imo`, `name` | IMO number and current name | +| `flag`, `flag_code` | Flag state name and ISO 3166-1 alpha-3 code | +| `call_sign`, `mmsi` | Radio identifiers | +| `gross_tonnage`, `deadweight` | Tonnages | +| `ship_type`, `year_built` | Type and year of build | +| `status`, `status_since` | For example `In Service/Commission`, `Broken Up` | +| `particulars_updated` | When Equasis last updated the particulars | +| `flag_performance` | `paris_mou` and `tokyo_mou` list colour (`White`, `Grey`, `Black`), `uscg_targeted`, `iacs_classed`, `detention_rate_36_months` (percent) | +| `management[]` | Current companies: `company_id`, `name`, `role`, `address`, `since` | +| `class_status[]` | Classification status: `society`, `status`, `since`, `reason` | +| `class_surveys[]` | Renewal surveys: `society`, `last_renewal`, `next_renewal` | +| `safety_certificates[]` | `society`, `survey_date`, `expiry_date`, `status_change_date`, `status`, `reason`, `convention` | +| `pandi[]` | P&I insurers: `name`, `inception` | +| `sightings[]` | `period` (month), `areas`, `source` | +| `inspections[]` | PSC inspections: `authority` (port state), `port`, `date`, `detained`, `deficiencies`, `reports[]` | +| `inspections[].reports[]` | One entry per PSC regime the inspection was reported to: `psc_organisation`, `inspection_type`, `duration_days`, `deficiencies`, `inspection_id` | +| `human_element_deficiencies[]` | `psc_organisation`, `authority`, `port`, `inspection_type`, `date`, `deficiencies` | +| `name_history[]`, `flag_history[]` | `name` or `flag`, `since`, `until`, `source` | +| `class_history[]` | Class renewal surveys: `society`, `survey_date`, `source` | +| `company_history[]` | `company`, `role`, `since`, `until`, `source` | +| `warnings[]` | Parts of the profile that could not be read | +| `retrieved_at` | When the pages were fetched | + +Notes: + +- An inspection reported to more than one PSC regime appears once, with one entry per + regime in `reports`. `inspections[].deficiencies` is the highest count any regime + reported. +- A deficiency count of `null` means Equasis showed no number, which usually means none + were recorded. +- `until` is not published by Equasis. It is derived from the `since` date of the next + newer record (for companies, the next newer record with the same role). +- Dates Equasis gives only to the month (`during 09/2024`) use the first day of that month. + +## Indicators + +Indicators are computed from the vessel profile relative to `reference_date` (the +retrieval date). They are facts, not a risk score. + +| Field | Description | +| --- | --- | +| `age_years` | Years since the year of build | +| `name_changes_12_months`, `name_changes_36_months` | Renames that took effect in the window | +| `flag_changes_12_months`, `flag_changes_36_months` | Flag changes that took effect in the window | +| `management_changes_36_months` | Changes of registered owner, ship manager, or ISM manager | +| `inspections_36_months`, `detentions_36_months`, `deficiencies_36_months` | PSC activity in the window | +| `last_inspection`, `last_detention` | Most recent dates on record | +| `flag_on_paris_mou_grey_or_black_list`, `flag_on_tokyo_mou_grey_or_black_list` | Flag performance list membership | +| `uscg_targeted_flag`, `iacs_classed` | As reported by Equasis | +| `class_withdrawn_or_suspended_36_months` | `"Society: Status"` entries whose status changed in the window | +| `has_pandi_insurance` | Whether any P&I insurer is recorded | +| `mmsi_country`, `mmsi_matches_flag` | Country allocated the MMSI's Maritime Identification Digits, and whether it is the flag state. `null` when either value is unavailable. | +| `observations` | Plain-language sentences describing the notable indicators | + +## Search fields + +| Field | Description | +| --- | --- | +| `query` | The search text, or the identifiers searched for | +| `ships[]` | `imo`, `name`, `gross_tonnage`, `ship_type`, `year_built`, `flag` | +| `companies[]` | `company_id`, `name`, `address` | +| `total_ships` | Total ship matches reported by Equasis; can exceed `len(ships)` when results were limited by `--max-pages` | +| `retrieved_at` | When the pages were fetched | + +## Fleet fields + +| Field | Description | +| --- | --- | +| `company` | `company_id`, `name`, `address`, `status`, `last_update` | +| `vessels[]` | `imo`, `name`, `gross_tonnage`, `ship_type`, `year_built`, `flag`, `class_societies`, `detentions_as_ism_manager_3y`, `detentions_all_companies_3y`, `roles[]` (`role`, `since`) | +| `total_vessels` | Total reported by Equasis; can exceed `len(vessels)` when limited by `--max-pages` | +| `retrieved_at` | When the pages were fetched | + +## Stability + +Within a `schema_version`: + +- Fields and CSV columns are never removed or renamed, and their types do not change. +- New fields and CSV columns may be added. Read CSV files by column name, not position. + +Incompatible changes increase `schema_version` and are listed in the +[changelog](../CHANGELOG.md). diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..58048d2 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,244 @@ +# Usage reference + +This page covers every command and option. For a quick introduction see the +[README](../README.md); for output fields see [output-schema.md](output-schema.md). + +- [Credentials](#credentials) +- [Global options](#global-options) +- [vessel](#vessel) +- [search](#search) +- [fleet](#fleet) +- [configure](#configure) +- [cache](#cache) +- [Interactive shell](#interactive-shell) +- [Batch input files](#batch-input-files) +- [Exit codes](#exit-codes) +- [Environment variables](#environment-variables) +- [Shell completion](#shell-completion) + +## Credentials + +equasis-cli logs in with your own Equasis account and looks for credentials in this order: + +1. `--username` and `--password` options +2. `EQUASIS_USERNAME` and `EQUASIS_PASSWORD` environment variables +3. The credentials file written by `equasis configure --setup` + +The credentials file is `$XDG_CONFIG_HOME/equasis-cli/credentials.json` +(`~/.config/equasis-cli/credentials.json` by default) on Linux and macOS, and +`%APPDATA%\equasis-cli\credentials.json` on Windows. It is created readable only by you, +and equasis-cli warns if its permissions allow other users to read it. + +Avoid `--password` where possible: command-line arguments are visible in shell history and +process listings. + +## Global options + +These options work before or after the command name, so `equasis -f json vessel 9811000` +and `equasis vessel 9811000 -f json` are equivalent. + +| Option | Description | +| --- | --- | +| `-f`, `--format {table,json,jsonl,csv}` | Output format. Defaults to the format implied by `--output-file`, otherwise `table`. | +| `-o`, `--output-file PATH` | Write results to `PATH` instead of standard output. Parent directories are created; the file is replaced atomically. | +| `-q`, `--quiet` | Print only results, warnings, and errors | +| `--no-color` | Disable colored messages (also honoured: `NO_COLOR`) | +| `--username USERNAME` | Equasis username (the e-mail address you registered with) | +| `--password PASSWORD` | Equasis password | +| `--delay SECONDS` | Minimum pause between requests to Equasis (default `1.0`) | +| `--no-cache` | Neither read nor write the page cache | +| `--refresh` | Fetch fresh pages even when cached copies exist (fresh pages are still cached) | +| `--debug` | Log HTTP requests, retries, and cache use to stderr | +| `--save-html DIR` | Save every page received from Equasis to `DIR`. Saved pages include your account name. | +| `--version` | Print the version and exit | + +File extensions map to formats as follows: `.json` is JSON, `.jsonl` and `.ndjson` are +JSON Lines, `.csv` is CSV, and `.txt` is a table. An explicit `--format` always wins. + +Results are written to standard output. Progress messages, warnings, and errors go to +standard error, so output can be piped safely. + +## vessel + +```text +equasis vessel [IMO ...] [--imo-file FILE] [--fail-fast] +``` + +Retrieves the full profile of a vessel from the Ship Info, Inspections, and Ship History +pages: particulars, flag performance, management, classification, safety management +certificates, P&I insurance, sightings, PSC inspections, and name, flag, class, and company +history, plus [indicators](output-schema.md#indicators). + +IMO numbers may be written as `9811000` or `IMO 9811000`. A number that fails the IMO +check digit is still looked up, with a warning that it may contain a typo. + +One IMO number produces a single vessel document. Several numbers, or any `--imo-file`, +produce a batch report: + +- Every input appears in the report with a status of `ok`, `not_found`, or `error`. +- Processing continues after failures unless `--fail-fast` is given. +- With JSON Lines output, each result is written as soon as it is available. +- The exit code is `5` if any item failed. + +If a secondary page (Inspections or Ship History) cannot be read, the profile is still +returned and the problem is listed under `warnings`. + +## search + +```text +equasis search QUERY [--type {all,ships,companies}] [--max-pages N | --all-pages] +equasis search --imo IMO | --mmsi MMSI | --call-sign CALL_SIGN +``` + +A name search matches partial names and returns both ships and companies. Equasis returns +100 results per page. equasis-cli fetches up to 3 pages by default; use `--max-pages` or +`--all-pages` for more. A warning is shown when results were truncated. + +Identifier searches use Equasis advanced search and return ships whose IMO number, MMSI, +or call sign match exactly. + +The exit code is `4` if nothing matched. + +## fleet + +```text +equasis fleet [COMPANY ...] [--company-id NUMBER ...] [--company-file FILE] + [--first-match] [--max-pages N] [--fail-fast] +``` + +Retrieves every vessel associated with a company: particulars, current class, detention +counts for the last three years, and the roles the company holds (registered owner, ship +manager, ISM manager, and so on). + +A company can be given by name or by its 7-digit Equasis company number (shown by +`equasis search NAME --type companies`). Names are resolved as follows: + +1. A single case-insensitive exact name match is used. +2. A search that returns exactly one company is used. +3. Otherwise the name is ambiguous. In a terminal you are asked to choose; elsewhere the + matches are listed and the command exits with code `4`. `--first-match` uses the best + match instead. + +Several companies, or any `--company-file`, produce a batch report as for `vessel`. +Ambiguous names in a batch are reported with status `ambiguous` unless `--first-match` is +set. + +## configure + +| Command | Description | +| --- | --- | +| `equasis configure --setup` | Prompt for credentials, check them against Equasis, and save them | +| `equasis configure --test` | Log in with the configured credentials | +| `equasis configure --show` | Show which credential sources are set (never the password) | +| `equasis configure --clear` | Delete the credentials file | + +## cache + +```text +equasis cache [info|clear] +``` + +Equasis pages are cached for 24 hours so repeated lookups do not send the same requests +again. This also means an interrupted batch can be re-run without fetching completed items +a second time. Login, error, and not-found pages are never cached. + +The cache is stored in `$XDG_CACHE_HOME/equasis-cli` (`~/.cache/equasis-cli` by default), +or `%LOCALAPPDATA%\equasis-cli\Cache` on Windows. `EQUASIS_CACHE_DIR` overrides the +location. Cached pages contain your Equasis account name, so the directory is readable +only by you. + +`equasis cache` shows the location, number of pages, and size; `equasis cache clear` +deletes the cached pages. + +## Interactive shell + +Start the shell by running `equasis` with no arguments (or `equasis interactive`). The +session stays logged in between commands. + +| Command | Parameters | +| --- | --- | +| `vessel` | `/imo IMO`, `/refresh`, `/format FORMAT`, `/output FILE` | +| `search` | `/name NAME` or `/imo IMO` or `/mmsi MMSI` or `/callsign CALLSIGN`; `/type all\|ships\|companies`, `/pages N\|all`, `/refresh`, `/format`, `/output` | +| `fleet` | `/company NAME` or `/id NUMBER`; `/first`, `/pages N\|all`, `/refresh`, `/format`, `/output` | +| `batch` | `/imos "IMO,IMO"` or `/file FILE` or `/companies "NAME,NAME"` or `/company-file FILE`; `/fail-fast`, `/first`, `/refresh`, `/format`, `/output` | +| `format` | `table`, `json`, `jsonl`, or `csv`: sets the default format for the session | +| `cache` | `info` or `clear` | +| `status` | Connection, account, credential source, and default format | +| `help` | `help` lists commands; `help COMMAND` shows its parameters | +| `clear` | Clears the output | +| `exit`, `quit` | Leaves the shell | + +Quote values that contain spaces: `search /name "EVER GIVEN"`. Straight and typographic +quotes both work. `/output FILE` picks the format from the file extension, and +`/output json` (a format name without an extension) sets the format for that command. + +When a company name is ambiguous, `fleet` lists the matches with their company numbers. +Repeat the command with `/id NUMBER`, or add `/first`. + +Keyboard shortcuts: + +| Key | Action | +| --- | --- | +| `/` | Show commands, or the parameters of the current command | +| `Tab` | Insert the highlighted menu item, or complete a command name | +| `Up`, `Down` | Move through the menu, or recall previous commands | +| `Esc` | Close the menu; with no menu open, scroll the output (press `i`, `q`, or start typing to return) | +| `j`, `k`, arrows | Scroll one line while scrolling the output | +| `PgUp`, `PgDn`, `Shift+Up`, `Shift+Down`, `Ctrl+K`, `Ctrl+J` | Scroll the output | +| `?` | Show or hide the shortcut list (on an empty input line) | +| `Ctrl+C` | Clear the input line | +| `Ctrl+D` | Exit | + +## Batch input files + +`--imo-file`, `--company-file`, and the shell's `/file` and `/company-file` read one item +per line: + +```text +# Comments start with '#', either on their own line +9811000 # or after a value +9074729 + +9321483 +``` + +Blank lines are ignored and duplicates are removed. On the command line, `-` reads the +list from standard input: + +```bash +cut -d, -f1 vessels.csv | tail -n +2 | equasis vessel --imo-file - -o results.jsonl +``` + +## Exit codes + +| Code | Meaning | +| --- | --- | +| `0` | Success | +| `1` | Error (network failure, unexpected Equasis response) | +| `2` | Invalid usage or input (for example a malformed IMO number) | +| `3` | Missing credentials or authentication failed | +| `4` | Vessel, company, or search results not found; ambiguous company name | +| `5` | Batch completed, but at least one item failed | +| `6` | An Equasis page did not have the expected layout; please report it | +| `130` | Interrupted | + +## Environment variables + +| Variable | Purpose | +| --- | --- | +| `EQUASIS_USERNAME`, `EQUASIS_PASSWORD` | Credentials | +| `EQUASIS_CACHE_DIR` | Cache location | +| `XDG_CONFIG_HOME`, `XDG_CACHE_HOME` | Base directories for credentials and cache | +| `NO_COLOR` | Disable colored messages | + +## Shell completion + +Completion scripts for zsh and bash are in [`completions/`](../completions). + +```bash +# zsh: copy to a directory on $fpath, then restart the shell +cp completions/_equasis ~/.zsh/completions/ + +# bash: source from ~/.bashrc +source /path/to/equasis-cli/completions/equasis.bash +``` diff --git a/src/equasis_cli/_version.py b/src/equasis_cli/_version.py index 5b151d5..4afa5ff 100644 --- a/src/equasis_cli/_version.py +++ b/src/equasis_cli/_version.py @@ -1,3 +1,3 @@ """Package version (single source of truth).""" -__version__ = "3.0.0.dev0" +__version__ = "3.0.0" diff --git a/src/equasis_cli/models.py b/src/equasis_cli/models.py index f046e24..b8fa04b 100644 --- a/src/equasis_cli/models.py +++ b/src/equasis_cli/models.py @@ -340,7 +340,10 @@ def to_jsonable(value: Any) -> Any: if isinstance(value, enum.Enum): return value.value if isinstance(value, dt.datetime): - return value.isoformat() + if value.tzinfo is None: + return value.isoformat(timespec="seconds") + utc = value.astimezone(dt.timezone.utc).isoformat(timespec="seconds") + return utc.replace("+00:00", "Z") if isinstance(value, dt.date): return value.isoformat() if isinstance(value, dict): diff --git a/src/equasis_cli/tui/commands.py b/src/equasis_cli/tui/commands.py index 946f61e..5fce6ae 100644 --- a/src/equasis_cli/tui/commands.py +++ b/src/equasis_cli/tui/commands.py @@ -80,7 +80,7 @@ def param(self, name: str) -> Param | None: CommandSpec( "vessel", "Full vessel profile by IMO number", - "vessel /imo IMO [/format FORMAT] [/output FILE]", + "vessel /imo IMO [/refresh] [/format FORMAT] [/output FILE]", (Param("imo", "IMO number (required)"), REFRESH, FORMAT, OUTPUT), ("Includes management, classification, PSC inspections and history.",), ), @@ -88,7 +88,7 @@ def param(self, name: str) -> Param | None: "search", "Search ships and companies", "search /name NAME | /imo IMO | /mmsi MMSI | /callsign CALLSIGN " - "[/type all|ships|companies] [/pages N] [/format FORMAT] [/output FILE]", + "[/type all|ships|companies] [/pages N] [/refresh] [/format FORMAT] [/output FILE]", ( Param("name", "name to search for (partial matches)"), Param("imo", "exact IMO number"), @@ -104,7 +104,8 @@ def param(self, name: str) -> Param | None: CommandSpec( "fleet", "Vessels associated with a company", - "fleet /company NAME | /id NUMBER [/first] [/pages N] [/format FORMAT] [/output FILE]", + "fleet /company NAME | /id NUMBER [/first] [/pages N] [/refresh] [/format FORMAT] " + "[/output FILE]", ( Param("company", "company name"), Param("id", "7-digit Equasis company number"), @@ -123,7 +124,7 @@ def param(self, name: str) -> Param | None: "batch", "Look up many vessels or companies", 'batch /imos "IMO,IMO" | /file FILE | /companies "NAME,NAME" | /company-file FILE ' - "[/fail-fast] [/first] [/format FORMAT] [/output FILE]", + "[/fail-fast] [/first] [/refresh] [/format FORMAT] [/output FILE]", ( Param("imos", "comma-separated IMO numbers"), Param("file", "file with one IMO number per line"), diff --git a/tests/test_completions.py b/tests/test_completions.py new file mode 100644 index 0000000..06814a6 --- /dev/null +++ b/tests/test_completions.py @@ -0,0 +1,40 @@ +"""Keep the shell completion scripts in sync with the argument parser.""" + +from __future__ import annotations + +import argparse +from pathlib import Path + +import pytest + +from equasis_cli.cli import build_parser + +COMPLETIONS = Path(__file__).parent.parent / "completions" + + +def visible_options(parser: argparse.ArgumentParser) -> set[str]: + options = set() + for action in parser._actions: + if action.help == argparse.SUPPRESS: + continue + options.update(o for o in action.option_strings if o.startswith("--")) + return options + + +def parsers() -> dict[str, argparse.ArgumentParser]: + root = build_parser() + found = {"": root} + for action in root._actions: + if isinstance(action, argparse._SubParsersAction): + found.update(action.choices) + return found + + +@pytest.mark.parametrize("script", ["_equasis", "equasis.bash"]) +def test_completion_scripts_cover_every_command_and_option(script: str) -> None: + content = (COMPLETIONS / script).read_text(encoding="utf-8") + for name, parser in parsers().items(): + if name: + assert name in content, f"{script} is missing the '{name}' command" + for option in visible_options(parser) - {"--help", "--version"}: + assert option in content, f"{script} is missing {option} ({name or 'global'})" diff --git a/tests/test_output.py b/tests/test_output.py index 7a02429..73c0252 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -42,12 +42,15 @@ def test_resolve_format_prefers_explicit_then_extension() -> None: def test_vessel_json_envelope_is_valid_and_complete() -> None: - document = json.loads(output.render(fixture_vessel(), "json", retrieved_at=MOMENT)) + fixture = fixture_vessel() + fixture.retrieved_at = MOMENT + document = json.loads(output.render(fixture, "json")) assert document["schema_version"] == 1 assert document["type"] == "vessel" assert document["retrieved_at"] == "2026-09-16T12:00:00Z" vessel = document["vessel"] + assert vessel["retrieved_at"] == "2026-09-16T12:00:00Z" assert vessel["imo"] == "9074729" assert vessel["gross_tonnage"] == 15899 assert vessel["status_since"] == "2025-09-25"