diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..23e19225
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,96 @@
+name: signal-cli CI
+
+on:
+ push:
+ branches:
+ - '**'
+ pull_request:
+ workflow_call:
+
+permissions:
+ contents: write # to fetch code (actions/checkout) and submit dependency graph (gradle/gradle-build-action)
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ java: [ '21', '24' ]
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up JDK
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'zulu'
+ java-version: ${{ matrix.java }}
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v4
+ with:
+ dependency-graph: generate-and-submit
+ - name: Install asciidoc
+ run: sudo apt update && sudo apt --no-install-recommends install -y asciidoc-base
+ - name: Build with Gradle
+ run: ./gradlew --no-daemon build
+ - name: Build man page
+ run: |
+ cd man
+ make install
+ - name: Add man page to archive
+ run: |
+ version=$(tar tf build/distributions/signal-cli-*.tar | head -n1 | sed 's|signal-cli-\([^/]*\)/.*|\1|')
+ echo $version
+ tar --transform="flags=r;s|man|signal-cli-${version}/man|" -rf build/distributions/signal-cli-${version}.tar man/man{1,5}
+ - name: Compress archive
+ run: gzip -n -9 build/distributions/signal-cli-*.tar
+ - name: Archive production artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: signal-cli-archive-${{ matrix.java }}
+ path: build/distributions/signal-cli-*.tar.gz
+
+ build-graalvm:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: graalvm/setup-graalvm@v1
+ with:
+ version: 'latest'
+ java-version: '21'
+ cache: 'gradle'
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ - name: Build with Gradle
+ run: ./gradlew --no-daemon nativeCompile
+ - name: Archive production artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: signal-cli-native
+ path: build/native/nativeCompile/signal-cli
+
+ build-client:
+ strategy:
+ matrix:
+ os:
+ - ubuntu
+ - macos
+ - windows
+ runs-on: ${{ matrix.os }}-latest
+ defaults:
+ run:
+ working-directory: ./client
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install rust
+ run: rustup default stable
+ - name: Build client
+ run: cargo build --release --verbose
+ - name: Archive production artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: signal-cli-client-${{ matrix.os }}
+ path: |
+ client/target/release/signal-cli-client
+ client/target/release/signal-cli-client.exe
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 00000000..f778268a
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,60 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ master ]
+ schedule:
+ - cron: '0 7 * * 4'
+
+permissions:
+ contents: read # to fetch code (actions/checkout)
+ security-events: write
+
+jobs:
+ analyse:
+ name: Analyse
+ runs-on: ubuntu-latest
+
+ steps:
+
+ - name: Setup Java JDK
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'zulu'
+ java-version: 21
+
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ # We must fetch at least the immediate parents so that if this is
+ # a pull request then we can checkout the head.
+ fetch-depth: 2
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ # Override language selection by uncommenting this and choosing your languages
+ # with:
+ # languages: go, javascript, csharp, python, cpp, java
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v3
+
+ # âšī¸ Command-line programs to run using the OS shell.
+ # đ https://git.io/JvXDl
+
+ # âī¸ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..b7ce03f1
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,257 @@
+name: release
+
+on:
+ push:
+ tags:
+ - v*
+
+permissions:
+ contents: write # to fetch code (actions/checkout) and create release
+
+env:
+ IMAGE_NAME: signal-cli
+ IMAGE_REGISTRY: ghcr.io/asamk
+ REGISTRY_USER: ${{ github.actor }}
+ REGISTRY_PASSWORD: ${{ github.token }}
+
+jobs:
+
+ ci_wf:
+ permissions:
+ contents: write
+ uses: AsamK/signal-cli/.github/workflows/ci.yml@master
+ # ${{ github.repository }} not accepted here
+
+ lib_to_jar:
+ needs: ci_wf
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+
+ outputs:
+ signal_cli_version: ${{ steps.cli_ver.outputs.version }}
+ release_id: ${{ steps.create_release.outputs.id }}
+
+ steps:
+
+ - name: Download signal-cli build from CI workflow
+ uses: actions/download-artifact@v4
+
+ - name: Get signal-cli version
+ id: cli_ver
+ run: |
+ ver="${GITHUB_REF_NAME#v}"
+ echo "version=${ver}" >> $GITHUB_OUTPUT
+
+ - name: Extract archive
+ run: |
+ tree .
+ ARCHIVE_DIR=$(ls signal-cli-archive-*/ -d | tail -n1)
+ tar -xzf ./"${ARCHIVE_DIR}"/*.tar.gz
+ mv ./"${ARCHIVE_DIR}"/*.tar.gz signal-cli-${{ steps.cli_ver.outputs.version }}.tar.gz
+ rm -rf signal-cli-archive-*/
+
+# - name: Get signal-client jar version
+# id: lib_ver
+# run: |
+# JAR_PREFIX=libsignal-client-
+# jar_file=$(find ./signal-cli-*/lib/ -name "$JAR_PREFIX*.jar")
+# jar_version=$(echo "$jar_file" | xargs basename | sed "s/$JAR_PREFIX//; s/.jar//")
+# echo "$jar_version"
+# echo "signal_client_version=${jar_version}" >> $GITHUB_OUTPUT
+#
+# - name: Download signal-client builds
+# env:
+# RELEASES_URL: https://github.com/signalapp/libsignal/releases/download/
+# FILE_NAMES: signal_jni.dll libsignal_jni.dylib
+# SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }}
+# run: |
+# for file_name in $FILE_NAMES; do
+# curl -sOL "${RELEASES_URL}/v${SIGNAL_CLIENT_VER}/${file_name}" # note: added v
+# done
+# tree .
+
+ - name: Compress native app
+ env:
+ SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }}
+ run: |
+ chmod +x signal-cli-native/signal-cli
+ tar -czf signal-cli-${SIGNAL_CLI_VER}-Linux-native.tar.gz -C signal-cli-native signal-cli
+ rm -rf signal-cli-native/
+
+# - name: Replace Windows lib
+# env:
+# SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }}
+# SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }}
+# run: |
+# mv signal_jni.dll libsignal_jni.so
+# zip -u ./signal-cli-*/lib/libsignal-client-${SIGNAL_CLIENT_VER}.jar ./libsignal_jni.so
+# tar -czf signal-cli-${SIGNAL_CLI_VER}-Windows.tar.gz signal-cli-*/
+#
+# - name: Replace macOS lib
+# env:
+# SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }}
+# SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }}
+# run: |
+# jar_file=./signal-cli-*/lib/libsignal-client-${SIGNAL_CLIENT_VER}.jar
+# zip -d $jar_file libsignal_jni.so
+# zip $jar_file libsignal_jni.dylib
+# tar -czf signal-cli-${SIGNAL_CLI_VER}-macOS.tar.gz signal-cli-*/
+
+ - name: Create release
+ id: create_release
+ uses: actions/create-release@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ tag_name: v${{ steps.cli_ver.outputs.version }} # note: added `v`
+ release_name: v${{ steps.cli_ver.outputs.version }} # note: added `v`
+ draft: true
+
+ - name: Upload archive
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}.tar.gz
+ asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}.tar.gz
+ asset_content_type: application/x-compressed-tar # .tar.gz
+
+# - name: Upload Linux archive
+# uses: actions/upload-release-asset@v1
+# env:
+# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+# with:
+# upload_url: ${{ steps.create_release.outputs.upload_url }}
+# asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux.tar.gz
+# asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux.tar.gz
+# asset_content_type: application/x-compressed-tar # .tar.gz
+
+ - name: Upload Linux native archive
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux-native.tar.gz
+ asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux-native.tar.gz
+ asset_content_type: application/x-compressed-tar # .tar.gz
+
+# - name: Upload windows archive
+# uses: actions/upload-release-asset@v1
+# env:
+# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+# with:
+# upload_url: ${{ steps.create_release.outputs.upload_url }}
+# asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Windows.tar.gz
+# asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Windows.tar.gz
+# asset_content_type: application/x-compressed-tar # .tar.gz
+#
+# - name: Upload macos archive
+# uses: actions/upload-release-asset@v1
+# env:
+# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+# with:
+# upload_url: ${{ steps.create_release.outputs.upload_url }}
+# asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-macOS.tar.gz
+# asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-macOS.tar.gz
+# asset_content_type: application/x-compressed-tar # .tar.gz
+
+ build-container:
+ needs: ci_wf
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Download signal-cli build from CI workflow
+ uses: actions/download-artifact@v4
+
+ - name: Get signal-cli version
+ id: cli_ver
+ run: |
+ ver="${GITHUB_REF_NAME#v}"
+ echo "version=${ver}" >> $GITHUB_OUTPUT
+
+ - name: Move archive file
+ run: |
+ ARCHIVE_DIR=$(ls signal-cli-archive-*/ -d | tail -n1)
+ tar xf ./"${ARCHIVE_DIR}"/*.tar.gz
+ rm -r signal-cli-archive-* signal-cli-native
+ mkdir -p build/install/
+ mv ./signal-cli-"${GITHUB_REF_NAME#v}"/ build/install/signal-cli
+
+ - name: Build Image
+ id: build_image
+ uses: redhat-actions/buildah-build@v2
+ with:
+ image: ${{ env.IMAGE_NAME }}
+ tags: latest ${{ github.sha }} ${{ steps.cli_ver.outputs.version }}
+ containerfiles:
+ ./Containerfile
+ oci: true
+
+ - name: Push To GHCR
+ uses: redhat-actions/push-to-registry@v2
+ id: push
+ with:
+ image: ${{ steps.build_image.outputs.image }}
+ tags: ${{ steps.build_image.outputs.tags }}
+ registry: ${{ env.IMAGE_REGISTRY }}
+ username: ${{ env.REGISTRY_USER }}
+ password: ${{ env.REGISTRY_PASSWORD }}
+
+ - name: Echo outputs
+ run: |
+ echo "${{ toJSON(steps.push.outputs) }}"
+
+ build-container-native:
+ needs: ci_wf
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Download signal-cli build from CI workflow
+ uses: actions/download-artifact@v4
+
+ - name: Get signal-cli version
+ id: cli_ver
+ run: |
+ ver="${GITHUB_REF_NAME#v}"
+ echo "version=${ver}" >> $GITHUB_OUTPUT
+
+ - name: Move archive file
+ run: |
+ mkdir -p build/native/nativeCompile/
+ chmod +x ./signal-cli-native/signal-cli
+ mv ./signal-cli-native/signal-cli build/native/nativeCompile/
+
+ - name: Build Image
+ id: build_image
+ uses: redhat-actions/buildah-build@v2
+ with:
+ image: ${{ env.IMAGE_NAME }}
+ tags: latest-native ${{ github.sha }}-native ${{ steps.cli_ver.outputs.version }}-native
+ containerfiles:
+ ./native.Containerfile
+ oci: true
+
+ - name: Push To GHCR
+ uses: redhat-actions/push-to-registry@v2
+ id: push
+ with:
+ image: ${{ steps.build_image.outputs.image }}
+ tags: ${{ steps.build_image.outputs.tags }}
+ registry: ${{ env.IMAGE_REGISTRY }}
+ username: ${{ env.REGISTRY_USER }}
+ password: ${{ env.REGISTRY_PASSWORD }}
+
+ - name: Echo outputs
+ run: |
+ echo "${{ toJSON(steps.push.outputs) }}"
diff --git a/.gitignore b/.gitignore
index e98305a3..2b6774c1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,15 @@
.gradle/
-.idea/
+.idea/*
+!.idea/codeStyles/
build/
*~
*.swp
*.iml
local.properties
+.classpath
+.project
+.settings/
+out/
+.DS_Store
+/bin/
+/test-config/
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 00000000..aa627792
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 00000000..79ee123c
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..74d0bdc0
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,1192 @@
+# Changelog
+
+## [Unreleased]
+
+## [0.13.18] - 2025-07-16
+
+Requires libsignal-client version 0.76.3.
+
+### Added
+
+- Added `--view-once` parameter to send command to send view once images
+
+### Fixed
+
+- Handle rate limit exception correctly when querying usernames
+
+### Improved
+
+- Shut down when dbus daemon connection goes away unexpectedly
+- In daemon mode, exit immediately if account check fails at startup
+- Improve behavior when sending to devices that have no available prekeys
+
+## [0.13.17] - 2025-06-28
+
+Requires libsignal-client version 0.76.0.
+
+### Fixed
+
+- Fix issue when loading an older inactive group
+- Close attachment input streams after upload
+- Fix storage sync behavior with unhandled fields
+
+### Changed
+
+- Improve behavior when pin data doesn't exist on the server
+
+## [0.13.16] - 2025-06-07
+
+Requires libsignal-client version 0.73.2.
+
+### Changed
+
+- Ensure every sent message gets a unique timestamp
+
+## [0.13.15] - 2025-05-08
+
+Requires libsignal-client version 0.70.0.
+
+### Fixed
+
+- Fix native access warning with Java 24
+- Fix storage sync loop due to old removed e164 field
+
+### Changed
+
+- Increased compatibility of native build with older/virtual CPUs
+
+## [0.13.14] - 2025-04-06
+
+Requires libsignal-client version 0.68.1.
+
+### Fixed
+
+- Fix pre key import from old data files
+
+### Changed
+
+- Use websocket connection instead of HTTP for more requests
+- Improve handling of messages with decryption error
+
+## [0.13.13] - 2025-02-28
+
+Requires libsignal-client version 0.66.2.
+
+### Added
+- Allow setting nickname and note with `updateContact` command
+
+### Fixed
+- Fix syncing nickname, note and expiration timer
+- Fix check for registered users with a proxy
+- Improve handling of storage records not yet supported by signal-cli
+- Fix contact sync for networks requiring proxy
+
+## [0.13.12] - 2025-01-18
+
+Requires libsignal-client version 0.65.2.
+
+### Fixed
+
+- Fix sync of contact nick name
+- Fix incorrectly marking recipients as unregistered after sync
+- Fix cause of database deadlock (Thanks @dukhaSlayer)
+- Fix parsing of account query param in events http endpoint
+
+### Changed
+
+- Enable sqlite WAL journal\_mode for improved performance
+
+## [0.13.11] - 2024-12-26
+
+Requires libsignal-client version 0.64.0.
+
+### Fixed
+- Fix issue with receiving messages that have an invalid destination
+
+## [0.13.10] - 2024-11-30
+
+Requires libsignal-client version 0.62.0.
+
+### Fixed
+
+- Fix receiving some unusual contact sync messages
+- Fix receiving expiration timer updates
+
+### Improved
+- Add support for new storage encryption scheme
+
+## [0.13.9] - 2024-10-28
+
+### Fixed
+
+- Fix verify command
+
+## [0.13.8] - 2024-10-26
+
+Requires libsignal-client version 0.58.2
+
+### Fixed
+
+- Fix sending large text messages
+- Fix setting message expiration timer with recent Signal apps
+
+### Improved
+
+- Add group name and timestamps on json message (Thanks @jailson-dias)
+
+## [0.13.7] - 2024-09-28
+
+Requires libsignal-client version 0.58.0
+
+### Fixed
+
+- Fix unnecessary log output
+- Fix issue with CDSI sync with invalid token
+
+## [0.13.6] - 2024-09-08
+
+Requires libsignal-client version 0.56.0
+
+### Improved
+
+- Send sync message to linked devices when sending read/viewed receipts
+
+### Fixed
+
+- Fix issue with sending to some groups
+- Fix CDSI sync if no token is stored
+- Fix possible db dead lock during storage sync
+
+## [0.13.5] - 2024-07-25
+
+Requires libsignal-client version 0.52.2
+
+### Fixed
+
+- Fixed device linking, due to new feature flag
+
+## [0.13.4] - 2024-06-06
+
+**Attention**: Now requires libsignal-client version 0.47.0
+
+### Improved
+
+- Improve username update error message
+- Update groups when using listGroups command
+
+### Fixed
+
+- Update libsignal to fix graalvm native startup
+- Fix issue with saving username link
+- Fix sendMessageRequestResponse type parameter parsing in JSON RPC mode
+- Fix getUserStatus command with only username parameter
+
+## [0.13.3] - 2024-04-19
+
+**Attention**: Now requires libsignal-client version 0.44.0
+
+### Added
+
+- Support for reading contact nickname and notes
+- Add `--internal` and `--detailed` parameters to `listContacts` command
+
+### Fixed
+
+- Fix issue with sending messages when a new session is created
+
+## [0.13.2] - 2024-03-23
+
+**Attention**: Now requires libsignal-client version 0.40.1
+
+### Added
+
+- Add `--username` parameter to `getUserStatus` command
+
+### Fixed
+
+- Fixed setting and retrieving PIN after server changes
+
+## [0.13.1] - 2024-02-27
+
+### Added
+
+- Add `--reregister` parameter to force registration of an already registered account
+
+### Fixed
+
+- Fixed rare issue with duplicate PNIs during migration
+
+### Improved
+
+- Show information when requesting voice verification without prior SMS verification
+- Username can now be set with an explicit discriminator (e.g. testname.000)
+- Improve behavior when PNI prekeys upload fails
+- Improve `submitRateLimitChallenge` error message if captcha is rejected by server
+- Only retry messages after an identity was trusted
+
+### Changed
+
+- Default number sharing to NOBODY, to match the official apps behavior.
+
+## [0.13.0] - 2024-02-18
+
+**Attention**: Now requires Java 21 and libsignal-client version 0.39.2
+
+### Breaking changes
+
+- Sending to the self number (+XXXX) now behaves the same as the `--note-to-self` parameter. To get the previous
+ behavior with notification, the `--notify-self` parameter can be added.
+
+### Added
+
+- New `--hidden` parameter for `removeContact` command
+- New `--notify-self` parameter for `send` command, for sending a non-sync message when self is part of the recipients
+ or groups.
+- New `--unrestricted-unidentified-sender`, `--discoverable-by-number`, `--number-sharing`, `--username`
+ and `--delete-username` parameter for `updateAccount` command
+- New `--bus-name` parameter for `daemon` command to use another D-Bus bus name
+- New `getAvatar` and `getSticker` commands to get avatar and sticker images
+- New `sendMessageRequestResponse` command to accept/delete message requests
+
+### Fixed
+
+- Improve issue with stale prekeys and receiving messages to PNI address
+
+### Improved
+
+- Better shutdown handling after Ctrl+C and SIGTERM
+- Implemented full remote storage sync.
+ Provides better contact and settings sync for linked devices.
+- `listContacts` doesn't list unregistered users anymore
+
+## [0.12.8] - 2024-02-06
+
+### Fixes
+
+- Update user agent
+
+## [0.12.7] - 2023-12-15
+
+**Attention**: Now requires native libsignal-client version 0.36.1
+
+### Fixes
+
+- Fix linking to an existing account
+
+## [0.12.6] - 2023-12-11
+
+### Fixes
+
+- Fix linking to an existing account
+- Fix migration from old account data
+
+## [0.12.5] - 2023-11-21
+
+### Fixes
+
+- Fix issue with joining groups by group link for new accounts
+- Fix receiving address of shared contact
+- Fix receiving sync edit messages in groups
+
+### Changed
+
+- Create safety numbers based on ACI instead of phone number
+
+## [0.12.4] - 2023-10-22
+
+### Fixes
+
+- Prevent ConcurrentModificationException
+- Update captcha help text
+
+## [0.12.3] - 2023-10-17
+
+### Added
+
+- Added `startChangeNumber` and `finishChangeNumber` commands to switch to another phone number
+- Added `--quote-attachment` parameter to `send` command
+- Added support for scannable safety numbers based on serviceId
+- Added `EditMessageReceived` signal for D-Bus interface
+- Added new exit code `5` for rate limit failures
+- Added full CDSI refresh to get current ACI/PNIs for known numbers regularly
+
+### Fixed
+
+- Correctly respond with delivery receipts for edit messages
+
+### Changed
+
+- JSON-RPC requests are now executed in parallel.
+ Clients should make sure to use the `id` field to get the correct response for a request.
+
+## [0.12.2] - 2023-09-30
+
+**Attention**: Now requires native libsignal-client version 0.32.1
+
+### Added
+
+- Added `--receive-mode` parameter for `jsonRpc` command
+- Add `libsignal_client_path` build property to override libsignal-client jar file
+
+### Changed
+
+- `jsonRpc` command now supports multi-account mode including registering and linking
+
+## [0.12.1] - 2023-08-26
+
+### Added
+
+- New `addStickerPack` command
+
+### Fixed
+
+- Fixed some issues with upgrading from older accounts
+
+### Changed
+
+- Reverted receive notification in JSON-RPC to old format, only explicit subscriptions should use the new format
+
+## [0.12.0] - 2023-08-11
+
+**Attention**: Now requires native libsignal-client version 0.30.0
+
+### Breaking changes
+
+- Adapt receive subscription notification in JSON-RPC to have payload in result field
+ - Before: `{"jsonrpc":"2.0","method":"receive","params":{"envelope":{ ... },"account":"+XXX","subscription":0}}`
+ - After:
+ `{"jsonrpc":"2.0","method":"receive","params":{"subscription":0,"result":{"envelope":{ ... },"account":"+XXX"}}}`
+
+### Added
+
+- Manage identities via DBus (Thanks @bublath)
+- Added support for SVR2 PINs
+
+### Fixed
+
+- Fixed finishLink/receive/register/verify commands for JSON-RPC
+- Update to the latest libsignal to fix various issues
+
+## [0.11.11] - 2023-05-24
+
+**Attention**: Now requires native libsignal-client version 0.25.0
+
+### Added
+
+- New `--text-style` and `--quote-text-style` flags for `send` command
+
+### Fixed
+
+- Fixed migration of older account files
+- Fix deleting old unregistered recipient
+
+## [0.11.10] - 2023-05-11
+
+**Attention**: Now requires native libsignal-client version 0.23.1
+
+### Added
+
+- Support for receiving and sending edit messages with `--edit-timestamp`
+
+## [0.11.9.1] - 2023-04-23
+
+### Fixed
+
+- Fix build with Java 20
+
+## [0.11.9] - 2023-04-22
+
+### Fixed
+
+- Workaround issue with linking to newer app versions
+
+## [0.11.8] - 2023-04-05
+
+### Added
+
+- Added file attachment attributes to JSON output (Thanks @signals-from-outer-space)
+
+### Fixed
+
+- Scrub E164 number from dbus paths
+- Fix sending large text messages to multiple recipient
+- Fix deleting old group in dbus mode
+- Fix issue with unknown identity serviceId
+
+### Improved
+
+- Relaxed Content-Type check in http daemon mode (Thanks @cedb)
+
+## [0.11.7] - 2023-02-19
+
+**Attention**: Now requires native libsignal-client version 0.22.0
+
+### Fixed
+
+- Fix issue with missing pni identity key
+- Fix graalvm sqlite issue (Thanks @Marvin A. Ruder)
+- Fix issue with forgetting recipient
+
+### Changed
+
+- Allow JSON-RPC commands without account param if only one account exists
+
+## [0.11.6] - 2022-12-18
+
+### Added
+
+- Allow using data URIs for updateGroup/updateProfile
+- New alive check endpoint for http daemon (Thanks @ced-b)
+
+### Fixed
+
+- Registration with voice verification now works if no system locale is set
+- Fixed retrieving attachments in JSON RPC mode (Thanks @ced-b)
+
+## [0.11.5.1] - 2022-11-09
+
+### Fixed
+
+- Fix updating from older signal-cli version
+
+## [0.11.5] - 2022-11-07
+
+**Attention**: Now requires native libsignal-client version 0.21.1
+
+### Added
+
+- Add `--http` flag to `daemon` command to provide a JSON-RPC http endpoint (`/api/v1/rpc`). (Thanks @ced-b)
+- The `receive` method is now also available in JSON-RPC daemon mode, for polling new messages.
+- Add `getAttachment` command to get attachment file base64 encoded. (Thanks @ced-b)
+- Add `--disable-send-log` to disable the message send log.
+- Add `--story` to `sendReaction` command, to react to stories.
+- Add `--story-timestamp` and `--story-author` to `send` command, to reply to stories.
+- Add `--max-messages` to `receive` command, to only receive a certain number of messages.
+
+### Changed
+
+- Send long text messages as attachment instead. This matches the behavior of the official clients.
+- Store attachments with a file extension, for common file types.
+
+## [0.11.4] - 2022-10-19
+
+### Added
+
+- Approve/Refuse group join requests, using same interface as adding/removing members
+- Add --ignore-stories flag to prevent receiving story messages
+
+### Fixed
+
+- Fixed issue with receiving messages that can't be decrypted
+- Do not discard incoming group join messages
+
+### Improved
+
+- Add code to receive new PNI after change number
+
+## [0.11.3] - 2022-10-07
+
+### Fixed
+
+- Fix sending messages to groups (in non-daemon mode)
+- Fix updating from older signal-cli version
+- Fix issue with handling decryption error message
+- Fix graalvm native build (Thanks @bentolor)
+
+## [0.11.2] - 2022-10-06
+
+### Fixed
+
+- Update user agent version to work with new Signal-Server check
+
+## [0.11.1] - 2022-10-05
+
+### Fixed
+
+- Fix sending group messages
+- Fix store migration issue on Windows
+- Fix building fat jars
+
+## [0.11.0] - 2022-10-02
+
+**Attention**: Now requires native libsignal-client version 0.20.0
+
+### Breaking changes
+
+- Changed meaning of `-v` flag from `--version` to `--verbose`.
+ So now extended logging can be achieved with `-vv`.
+- Remove deprecated fallback to reading from stdin if no message body is given.
+ To read a message from stdin, use the `--message-from-stdin` flag.
+
+### Added
+
+- Migrate PIN to new KBS enclave when Signal updates it
+- Add `--scrub-log` flag to remove possibly sensitive information from the log
+- Add `sendPaymentNotification` dbus method
+
+### Fixed
+
+- Fix an issue where messages were sent without sender phone number
+
+### Changed
+
+- Store data except base account data in sqlite database
+- Use new CDSI for contact discovery in compat mode
+
+## [0.10.11] - 2022-08-17
+
+**Attention**: Now requires native libsignal-client version 0.19.3
+
+### Added
+
+- Output content of received story messages
+
+## [0.10.10] - 2022-07-30
+
+**Attention**: Now requires native libsignal-client version 0.18.1
+
+### Fixed
+
+- Fix setPin/removePin commands which broke due to server side changes
+- Workaround GraalVM 22.2.0 issue with daemon connection
+
+## [0.10.9] - 2022-07-16
+
+### Changed
+
+- updateAccount command checks self number and PNI after updating account attributes
+
+### Fixed
+
+- Fixed small issue with syncing contacts from storage
+- Fixed issue with opening older account files
+
+## [0.10.8] - 2022-06-13
+
+### Added
+
+- Attachments can now be given as data: URIs with base64 data instead of just file paths (Thanks @KevinRoebert)
+- `version` command can now be used on the commandline, in addition to the `--version` flag.
+ In the next version the current short form `-v` will change its meaning to `--verbose`!
+
+### Improved
+
+- An account can now be registered on both LIVE and STAGING environment in the same config directory.
+- Logging output for registering has been extended.
+
+## [0.10.7] - 2022-05-29
+
+### Added
+
+- Added profile information to `listContacts` command output
+- Added filter flags for `listContacts` command
+- New `sendPaymentNotification` command to send payment receipt blobs
+- New `--given-name` and `--family-name` parameters for `updateContact` command
+- Implement sending link previews with `--preview-url`, `--preview-title` parameters for the `send` command
+- New `--send-read-receipts` parameter for `receive` and `daemon` commands for automatically marking received messages
+ as read
+
+### Fixed
+
+- Issue with endless growing `pre-keys-pni` data directory
+
+## [0.10.6] - 2022-05-19
+
+**Attention**: Now requires native libsignal-client version 0.17
+
+### Added
+
+- Check if account is used on the environment it was registered (live or staging)
+- New command `deleteLocalAccountData` to delete all local data of an unregistered account
+- New parameter `-g` for `listGroups` command to filter for specific groups
+
+### Fixed
+
+- Fix deleting a recipient which has no uuid
+
+### Changed
+
+- Show warning when sending a message and no profile name has been set.
+ (A profile name may become mandatory in the future)
+- After blocking a contact/group the profile key is now rotated
+- Only update profile keys from authoritative group changes
+
+## [0.10.5] - 2022-04-11
+
+**Attention**: Now requires native libsignal-client version 0.15
+
+### Added
+
+- New `--ban`, `--unban` flags for `updateGroup` command to ban users from joining by group link
+
+### Fixed
+
+- Fix plain text output of blocked group ids
+- Fix error output in case of rate limiting
+- Fix error when creating a group with no members
+- Fix adding recent Signal-Desktop versions as linked devices
+
+## [0.10.4.2] - 2022-03-17
+
+### Fixed
+
+- Crash in json output when receiving message from untrusted identity
+- Fix multi account commands for newly created accounts
+
+## [0.10.4.1] - 2022-03-02
+
+### Fixed
+
+- Linking to current apps (which currently don't include a PNI identity yet)
+- Show better error message when --target-timestamp is missing for sendReceipt
+
+## [0.10.4] - 2022-02-20
+
+### Added
+
+- Implement support for change number as linked device
+- Add `--message-from-stdin` flag for send command. The current behavior of
+ reading from stdin if the `-m` flag is not given, will be removed in a future
+ version.
+
+### Changed
+
+- Align receive timeout behavior for dbus client with cli and JSON-RPC.
+ Timeout is reset by every incoming message
+- Renamed `error` field in json receive response to `exception`
+
+### Fixed
+
+- Prevent a stale jsonrpc connection from interfering with message receiving
+
+## [0.10.3] - 2022-02-01
+
+### Added
+
+- MessageSendLog to cache sent message for 24h.
+ For resending messages in case the recipient fails to decrypt the message.
+- New global `--log-file` parameter to write logs to a separate file.
+ (`--verbose` can be used to increase the log level)
+
+### Improved
+
+- Better subscription handling for JSON-RPC `subscribeReceive` command
+
+### Fixed
+
+- Output receipt data for unsealed sender receipts again
+- Fix sending message resend requests to devices that happen to have the same deviceId
+
+## [0.10.2] - 2022-01-22
+
+### Fixed
+
+- Archive old sessions/sender keys when a recipient's identity key has changed
+- Fix profile fetch with an invalid LANG variable
+
+## [0.10.1] - 2022-01-16
+
+### Added
+
+- Send group messages with sender keys (more efficient for larger groups)
+- New command `listStickerPacks` to display all known sticker packs
+- New flag `--sticker` for `send` command to send stickers
+
+### Changed
+
+- Improve exit code for message sending.
+ Exit with 0 status code if the message was sent successfully to at least
+ one recipient, otherwise exit with status code 2 or 4 (for untrusted).
+- Download profiles in parallel for improved performance
+- `--verbose` flag can be specified multiple times for additional log output
+- Enable more security options for systemd service file
+- Rename sandbox to staging environment, to match the upstream name.
+
+### Fixed
+
+- The first incoming message after registration can now always be decrypted successfully
+- Ignore decryption failures from blocked contacts and don't send a resend request.
+
+## [0.10.0] - 2021-12-11
+
+**Attention**: Now requires Java 17 and libsignal-client version 0.11
+
+### Added
+
+- The daemon command now provides a JSON-RPC based socket interface (`--socket` and `--tcp`)
+- New daemon command flag `--receive-mode` to configure when messages are received
+- New daemon command flag `--no-receive-stdout` to prevent outputting messages on stdout
+- New command `listAccounts` that lists all registered local accounts
+- New command `removeContact`
+- Extend `send` command to allow sending mentions (`--mention`) and
+ quotes (`--quote-timestamp`, `--quote-author`, `--quote-message`, `--quote-mention`)
+- New dbus methods sendGroupTying, unregister, deleteAccount
+- New dbus events MessageReceivedV2, ReceiptReceivedV2, SyncMessageReceivedV2 that provide an extras parameter with
+ additional message info as a key/value map
+- New dbus method sendViewedReceipt (Thanks @John Freed)
+- New dbus object Configuration to read and update configuration values (Thanks @John Freed)
+- Payment info in json receive output (Thanks @technillogue)
+- `-c` alias for `--config` (Thanks @technillogue)
+
+### Changed
+
+- libzkgroup dependency is no longer required
+- Renamed `-u` and `--username` flags to `-a` and `--account` to prevent confusion with upcoming Signal usernames. The
+ old flags are also still supported for now.
+- Respect phone number sharing mode and unlisted state set by primary device
+- Adapt register command to reactivate account if possible.
+- dbus-java now uses Java 16 native unix sockets, which should provide better cross-platform compatibility
+- If sending to a recipient fails (e.g. unregistered) signal-cli now exits with a success exit code and prints
+ additional information about the failure.
+
+### Fixed
+
+- Registering an existing unregistered account now works reliably in daemon mode
+- Fixed an issue with loading some old config files without UUID
+- More reliable send behavior if some recipients are unregistered
+
+## [0.9.2] - 2021-10-24
+
+### Fixed
+
+- dbus `listNumbers` method works again
+
+### Changed
+
+- Improved provisioning error handling if the last steps fail
+- Adapt behavior of receive command as dbus client to match normal mode
+- Update captcha url for proof required handling
+
+## [0.9.1] - 2021-10-16
+
+**Attention**: Now requires native libzkgroup version 0.8
+
+### Added
+
+- New command `updateConfiguration` which allows setting configurations for linked devices
+- Improved dbus daemon for group handling, groups are now exported as separate dbus objects
+- Linked devices can be managed via dbus
+- New dbus methods sendTyping and sendReadReceipt (Thanks @JtheSaw)
+- New dbus methods submitRateLimitChallenge, isRegistered, listDevices, setExpirationTimer, sendContacts,
+ sendSyncRequest, uploadStickerPack, setPin and removePin (Thanks @John Freed)
+- New dbus method getSelfNumber
+
+### Fixed
+
+- Do not send message resend request to own device
+- Allow message from pending member to accept group invitations
+- Fix issue which could cause signal-cli to repeatedly send the same delivery receipts
+- Reconnect websocket after connection loss
+
+### Changed
+
+- Use new provisioning URL `sgnl://linkdevice` instead of `tsdevice:/`
+- The gradle command to build a graalvm native image is now `./gradlew nativeCompile`
+
+## [0.9.0] - 2021-09-12
+
+**Attention**: Now requires native libsignal-client version 0.9
+
+### Breaking changes
+
+- Removed deprecated `--json` parameter, use global parameter `--output=json` instead
+- Json output format of `listGroups` command changed:
+ Members are now arrays of `{"number":"...","uuid":"..."}` objects instead of arrays of strings.
+- Removed deprecated fallback data paths, only `$XDG_DATA_HOME/signal-cli` is used now
+ For those still using the old paths (`$HOME/.config/signal`, `$HOME/.config/textsecure`) you need to move those to the
+ new location.
+
+### Added
+
+- New global parameter `--trust-new-identities=always` to allow trusting any new identity key without verification
+- New parameter `--device-name` for `updateAccount` command to change the device name (also works for the primary
+ device)
+- New SignalControl DBus interface, to register/verify/link new accounts
+- New `jsonRpc` command that provides a JSON-RPC based API on stdout/stdin
+- Support for announcement groups
+- New parameter `--set-permission-send-messages` for `updateGroup` to create an announcement group
+- New `sendReceipt` command to send read and viewed receipts
+- Support for receiving sender key messages, mobile apps can now send messages more efficiently with server-side fan-out
+ to groups with signal-cli members.
+- Support for reading data from remote Signal storage. Now v2 groups will be shown after linking a new device.
+- New `submitRateLimitChallenge` command that can be used to lift some rate-limits by solving a captcha
+
+### Fixed
+
+- Store identity key correctly when sending a message after a recipient has changed keys
+
+## [0.8.5] - 2021-08-07
+
+### Added
+
+- Source name is included in JSON receive output (Thanks @technillogue)
+
+### Fixed
+
+- Allow updateContact command to only set expiration timer without requiring a name parameter
+
+## [0.8.4.1] - 2021-06-20
+
+### Fixed
+
+- Incorrect error handling in register command
+
+## [0.8.4] - 2021-06-13
+
+**Attention**: Now requires native libsignal-client version 0.8.1
+
+### Added
+
+- New parameters for `updateGroup` command for group v2 features:
+ `--description`, `--remove-member`, `--admin`, `--remove-admin`, `--reset-link`, `--link`,
+ `--set-permission-add-member`, `--set-permission-edit-details`, `--expiration`
+- New `--admin` parameter for `quitGroup` to set an admin before leaving the group
+- New `--delete` parameter for `quitGroup`, to delete the local group data
+- New 'sendTyping' command to send typing indicators
+
+### Fixed
+
+- Fixed issue that prevented registration with invalid locales
+- Prevent last admin of a group from leaving the group
+- All commands now show a short description with `--help`
+- Now a hint is shown if messages aren't received regularly
+- Group edit conflicts are now resolved automatically
+
+## [0.8.3] - 2021-05-13
+
+### Fixed
+
+- Upgrading from account files with older profiles
+- Building native image with graalvm
+
+## [0.8.2] - 2021-05-11
+
+### Added
+
+- A manual page for the DBus interface (Thanks @bublath, @exquo)
+- Remote message delete command (Thanks @adaptivegarage)
+- sendSyncRequest command to request complete contact/group list from primary device
+- New `--delete-account` argument for unregister (Dangerous)
+- New `--family-name` argument for updateProfile
+
+### Fixed
+
+- Sending reaction to group (Thanks @adaptivegarage)
+- Displaying of address for messages from untrusted identities
+- Handling of recipient number or uuid changes (e.g. after account deletions)
+- Only respond to sync requests from primary device
+- Display of quit group messages
+
+### Changed
+
+- Unlimited strength crypto is now enabled automatically for JREs that require it (Thanks @i-infra)
+- Only one identity key is stored per recipient and updated from profile (to match app behavior)
+- updateContact, block and unblock are now disabled for linked devices
+- After registering an empty profile is created so new groups can be joined immediately
+- If message decryption fails due to a broken session, the session is automatically renewed
+- Rework account storage for better reliability
+- Improved device linking flow
+ - Allow relinking existing account
+ - Encrypt/Decrypt device names
+
+## [0.8.1] - 2021-03-02
+
+### Added
+
+- New dbus commands: updateProfile, listNumbers, getContactNumber, quitGroup, isContactBlocked, isGroupBlocked,
+ isMember, joinGroup (Thanks @bublath)
+- Additional output for json format: shared contacts (Thanks @Atomic-Bean)
+- Improved plain text output to be more consistent and synced messages are now indented
+
+### Fixed
+
+- Issue with broken sessions with linked devices
+
+### Changed
+
+- Behavior of `trust` command improved, when trusting a new identity key all other known keys for
+ the same number are removed.
+
+## [0.8.0] - 2021-02-14
+
+**Attention**: For all signal protocol functionality an additional native library is now
+required: [libsignal-client](https://github.com/signalapp/libsignal-client/).
+See https://github.com/AsamK/signal-cli/wiki/Provide-native-lib-for-libsignal for more information.
+
+### Added
+
+- Experimental support for building a GraalVM native image
+- Support for setting profile about text and emoji
+
+### Fixed
+
+- Incorrect error message when removing a non-existent profile avatar
+
+## [0.7.4] - 2021-01-19
+
+### Changed
+
+- Notify linked devices after profile has been updated
+
+### Fixed
+
+- After registering a new account, receiving messages didn't work
+ You may have to register and verify again to fix the issue.
+- Creating v1 groups works again
+
+## [0.7.3] - 2021-01-17
+
+### Added
+
+- `getUserStatus` command to check if a user is registered on Signal (Thanks @Atomic-Bean)
+- Global `--verbose` flag to increase log level
+- Global `--output=json` flag, currently supported by `receive`, `daemon`, `getUserStatus`, `listGroups`
+- `--note-to-self` flag for `send` command to send a note to linked devices
+- More info for received messages in json output: stickers, viewOnce, typing, remoteDelete
+
+### Changed
+
+- signal-cli can now be used without the username `-u` flag
+ For daemon command all local users will be exposed as dbus objects.
+ If only one local user exists, all other commands will use that user,
+ otherwise a user has to be specified.
+- Messages sent to self number will be sent as normal Signal messages again, to
+ send a sync message, use the new `--note-to-self` flag
+- Ignore messages with group context sent by non group member
+- Profile key is sent along with all direct messages
+- In json output unnecessary fields that are null are now omitted
+
+### Fixed
+
+- Disable registration lock before removing the PIN
+- Fix PIN hash version to match the official clients.
+ If you had previously set a PIN you need to set it again to be able to unlock the registration lock later.
+- Issue with saving account file after linking
+
+## [0.7.2] - 2020-12-31
+
+### Added
+
+- Implement new registration lock PIN with `setPin` and `removePin` (with KBS)
+- Include quotes, mentions and reactions in json output (Thanks @Atomic-Bean)
+
+### Fixed
+
+- Retrieve avatars for v2 groups
+- Download attachment thumbnail for quoted attachments
+
+## [0.7.1] - 2020-12-21
+
+### Added
+
+- Accept group invitation with `updateGroup -g GROUP_ID`
+- Decline group invitation with `quitGroup -g GROUP_ID`
+- Join group via invitation link `joinGroup --uri https://signal.group/#...`
+
+### Fixed
+
+- Include group ids for v2 groups in json output
+
+## [0.7.0] - 2020-12-15
+
+### Added
+
+Support for groups of new type/v2
+
+- Sending and receiving
+- Updating name, avatar and adding members with `updateGroup`
+- Quit group and decline invitation with `quitGroup`
+- In the `listGroups` output v2 groups can be recognized by the longer groupId
+
+**Attention**: For the new group support to work the native libzkgroup library is required.
+See https://github.com/AsamK/signal-cli/wiki/Provide-native-lib-for-libsignal for more information.
+
+### Fixed
+
+- Rare NullPointerException when receiving messages
+
+## [0.6.12] - 2020-11-22
+
+### Added
+
+- Show additional message content (view once, remote delete, mention, âĻ) for received messages
+- `--captcha` parameter for `register` command, required for some IP ranges
+
+### Changed
+
+- Profile keys are now stored separately from contact list
+- Receipts from normal and unidentified messages now have the same format in json output
+
+### Fixed
+
+- Issue where some messages were sent with an old counter index
+
+## [0.6.11] - 2020-10-14
+
+- Fix issue with receiving message reactions
+
+## [0.6.10] - 2020-09-11
+
+- Fix issue when retrieving profiles
+- Workaround issue with libzkgroup on platforms other than linux x86_64
+
+## [0.6.9] - 2020-09-10
+
+- Minor bug fixes and improvements
+- dbus functionality now works on FreeBSD
+- signal-cli now requires Java 11
+
+**Warning: this version only works on Linux x86_64, will be fixed in 0.6.10**
+
+## [0.6.8] - 2020-05-22
+
+- Switch to hypfvieh dbus-java, which doesn't require a native library anymore (drops requirement of
+ libmatthew-unix-java)
+- Bugfixes for messages with uuids
+- Add `--expiration` parameter to `updateContact` command to set expiration timer
+
+## [0.6.7] - 2020-04-03
+
+- Send command now returns the timestamp of the sent message
+- DBus daemon: Publish received sync message to SyncMessageReceived signal
+- Fix issue with resolving e164/uuid addresses for sessions
+- Fix pack key length for sticker upload
+
+## [0.6.6] - 2020-03-29
+
+- Added listContacts command
+- Added block/unblock commands to block contacts and groups
+- Added uploadStickerPack command to upload sticker packs (see man page for more details)
+- Full support for sending and receiving unidentified sender messages
+- Support for message reactions with emojis
+- Internal: support recipients with uuids
+
+## [0.6.5] - 2019-11-11
+
+Supports receiving messages sent with unidentified sender
+
+## [0.6.4] - 2019-11-02
+
+- Fix rounding error for attachment ids in json output
+- Add additional info to json output
+- Add commands to update profile name and avatar
+- Add command to update contact names
+
+## [0.6.3] - 2019-09-05
+
+Bug fixes and small improvements
+
+## [0.6.2] - 2018-12-16
+
+- Fixes sending of group messages
+
+## [0.6.1] - 2018-12-09
+
+- Added getGroupIds dbus command
+- Use "NativePRNG" pseudo random number generator, if available
+- Switch default data path:
+ `$XDG_DATA_HOME/signal-cli` (`$HOME/.local/share/signal-cli`)
+ Existing data paths will continue to work (used as fallback)
+
+## [0.6.0] - 2018-05-03
+
+- Simple json output
+- dbus signal for receiving messages
+- Registration lock PIN
+- Output quoted message
+
+## [0.5.6] - 2017-06-16
+
+* new listGroups command
+* Support for attachments with file names
+* Support for complete contacts sync
+* Support for contact verification sync
+* DBus interface:
+* Get/Set group info
+* Get/Set contact info
+
+## [0.5.5] - 2017-02-18
+
+- fix receiving messages on linked devices
+- add unregister command
+
+## [0.5.4] - 2017-02-17
+
+- Fix linking of new devices
+
+## [0.5.3] - 2017-01-29
+
+* New commandline parameter for receive: --ignore-attachments
+* Updated dependencies
+
+## [0.5.2] - 2016-12-16
+
+- Add support for group info requests
+- Improve closing of file streams
+
+## [0.5.1] - 2016-11-18
+
+- Support new safety numbers (https://whispersystems.org/blog/safety-number-updates/)
+- Add a man page
+- Support sending disappearing messages, if the recipient has activated it
+
+## [0.5.0] - 2016-08-29
+
+- Check if a number is registered on Signal, before adding it to a group
+- Prevent sending to groups that the user has quit
+- Commands to trust new identity keys (see README)
+- Messages from untrusted identities are stored on disk and decrypted when the user trusts the identity
+- Timestamps shown in ISO 8601 format
+
+## [0.4.1] - 2016-07-18
+
+- Fix issue with creating groups
+- Lock config file to prevent parallel access by multiple instances of signal-cli
+- Improve return codes, always return non-zero code, when sending failed
+
+## [0.4.0] - 2016-06-19
+
+- Linking to Signal-Desktop and Signal-Android is now possible (Provisioning)
+- Added a contact store, mainly for syncing contacts with linked devices (editing not yet possible via cli)
+- Avatars for groups and contacts are now stored (new folder "avatars" in the config path)
+
+## [0.3.1] - 2016-04-03
+
+- Fix running with Oracle JRE 8
+- Fix registering
+- Fix unicode warning when compiling with non utf8 locale
+
+## [0.3.0] - 2016-04-02
+
+- Renamed textsecure-cli to signal-cli, following the rename of libtextsecure-java to libsignal-service-java
+- The experimental dbus interface was also renamed to org.asamk.Signal
+- Upload new prekeys to the server, when there are less than 20 left, prekeys are needed to create new sessions
+
+## [0.2.1] - 2016-02-10
+
+- Improve dbus service
+- New command line argument --config to specify config directory
+
+## [0.2.0] - 2015-12-30
+
+Added an experimental dbus interface, for sending and receiving messages (The interface is unstable and may change with
+future releases).
+
+This release works with Java 7 and 8.
+
+## [0.1.0] - 2015-11-28
+
+Add support for creating/updating groups and sending to them
+
+## [0.0.5] - 2015-11-21
+
+- Add receive timeout commandline parameter
+- Show message group info
+
+## [0.0.4] - 2015-09-22
+
+## [0.0.3] - 2015-08-07
+
+## [0.0.2] - 2015-07-08
+
+First release
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..c18cd8a8
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,18 @@
+# Question
+
+If you have a question you can ask it in the [GitHub discussions page](https://github.com/AsamK/signal-cli/discussions)
+
+# Report a bug
+
+- Search [existing issues](https://github.com/AsamK/signal-cli/issues?q=is%3Aissue) if it has been reported already
+- If you're unable to find an open issue addressing the
+ problem, [open a new one](https://github.com/AsamK/signal-cli/issues/new).
+ - Be sure to include a **title and clear description**, as much relevant information as possible.
+ - Specify the versions of signal-cli, libsignal-client (if self-compiled), JDK and OS you're using
+ - Specify if it's the normal java or the graalvm native version.
+ - Run the failing command with `--verbose` flag to get a more detailed log output and include that in the bug report
+
+# Pull request
+
+- Code style should match the existing code, IntelliJ users can use the auto formatter
+- Separate PRs should be opened for each implemented feature or bug fix
diff --git a/Containerfile b/Containerfile
new file mode 100644
index 00000000..361c2667
--- /dev/null
+++ b/Containerfile
@@ -0,0 +1,11 @@
+FROM docker.io/azul/zulu-openjdk:21-jre-headless
+
+LABEL org.opencontainers.image.source=https://github.com/AsamK/signal-cli
+LABEL org.opencontainers.image.description="signal-cli provides an unofficial commandline, dbus and JSON-RPC interface for the Signal messenger."
+LABEL org.opencontainers.image.licenses=GPL-3.0-only
+
+RUN useradd signal-cli --system --create-home --home-dir /var/lib/signal-cli
+ADD build/install/signal-cli /opt/signal-cli
+
+USER signal-cli
+ENTRYPOINT ["/opt/signal-cli/bin/signal-cli", "--config=/var/lib/signal-cli"]
diff --git a/FUNDING.yml b/FUNDING.yml
new file mode 100644
index 00000000..c2420609
--- /dev/null
+++ b/FUNDING.yml
@@ -0,0 +1,4 @@
+github: AsamK
+liberapay: asamk
+ko_fi: asamk
+#bitcoin: bc1qykae53fry8a8ycgdzgv0rlxfc959hmmllvz698
diff --git a/README.md b/README.md
index dcd69a0c..c7228c09 100644
--- a/README.md
+++ b/README.md
@@ -1,66 +1,164 @@
-# textsecure-cli
+# signal-cli
-textsecure-cli is a commandline interface for [libtextsecure-java](https://github.com/WhisperSystems/libtextsecure-java). It supports registering, verifying, sending and receiving messages. However receiving messages currently doesn't work, because libtextsecure-java [does not yet support registering for the websocket support](https://github.com/WhisperSystems/libtextsecure-java/pull/5). For registering you need a phone number where you can receive SMS or incoming calls.
-It is primarily intended to be used on servers to notify admins of important events.
+signal-cli is a commandline interface for the [Signal messenger](https://signal.org/).
+It supports registering, verifying, sending and receiving messages.
+signal-cli uses a [patched libsignal-service-java](https://github.com/Turasa/libsignal-service-java),
+extracted from the [Signal-Android source code](https://github.com/signalapp/Signal-Android/tree/main/libsignal-service).
+For registering you need a phone number where you can receive SMS or incoming calls.
+
+signal-cli is primarily intended to be used on servers to notify admins of important events.
+For this use-case, it has a daemon mode with JSON-RPC interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-jsonrpc.5.adoc))
+and D-BUS interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc)) .
+For the JSON-RPC interface there's also a simple [example client](https://github.com/AsamK/signal-cli/tree/master/client), written in Rust.
+
+signal-cli needs to be kept up-to-date to keep up with Signal-Server changes.
+The official Signal clients expire after three months and then the Signal-Server can make incompatible changes.
+So signal-cli releases older than three months may not work correctly.
+
+## Installation
+
+You can [build signal-cli](#building) yourself or use
+the [provided binary files](https://github.com/AsamK/signal-cli/releases/latest), which should work on Linux, macOS and
+Windows. There's also a [docker image and some Linux packages](https://github.com/AsamK/signal-cli/wiki/Binary-distributions) provided by the community.
+
+System requirements:
+
+- at least Java Runtime Environment (JRE) 21
+- native library: libsignal-client
+
+ The native libs are bundled for x86_64 Linux (with recent enough glibc), Windows and MacOS. For other
+ systems/architectures
+ see: [Provide native lib for libsignal](https://github.com/AsamK/signal-cli/wiki/Provide-native-lib-for-libsignal)
+
+### Install system-wide on Linux
+
+See [latest version](https://github.com/AsamK/signal-cli/releases).
+
+```sh
+export VERSION=
+wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz
+sudo tar xf signal-cli-"${VERSION}".tar.gz -C /opt
+sudo ln -sf /opt/signal-cli-"${VERSION}"/bin/signal-cli /usr/local/bin/
+```
+
+You can find further instructions on the Wiki:
+
+- [Quickstart](https://github.com/AsamK/signal-cli/wiki/Quickstart)
## Usage
-usage: textsecure-cli [-h] -u USERNAME {register,verify,send,receive} ...
+For a complete usage overview please read
+the [man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli.1.adoc) and
+the [wiki](https://github.com/AsamK/signal-cli/wiki).
-* Register a number
+Important: The ACCOUNT is your phone number in international format and must include the country calling code. Hence it
+should start with a "+" sign. (See [Wikipedia](https://en.wikipedia.org/wiki/List_of_country_calling_codes) for a list
+of all country codes.)
- textsecure-cli -u USERNAME register
+* Register a number (with SMS verification)
-* Register a number with voice verification
+ signal-cli -a ACCOUNT register
- textsecure-cli -u USERNAME register -v
+ You can register Signal using a landline number. In this case, you need to follow the procedure below:
+ * Attempt a SMS verification process first (`signal-cli -a ACCOUNT register`)
+ * You will get an error `400 (InvalidTransportModeException)`, this is normal
+ * Wait 60 seconds
+ * Attempt a voice call verification by adding the `--voice` switch and wait for the call:
-* Verify the number using the code received via SMS
+ ```sh
+ signal-cli -a ACCOUNT register --voice
+ ```
- textsecure-cli -u USERNAME verify CODE
+ Registering may require solving a CAPTCHA
+ challenge: [Registration with captcha](https://github.com/AsamK/signal-cli/wiki/Registration-with-captcha)
-* Send a message to one or more recipients
+* Verify the number using the code received via SMS or voice, optionally add `--pin PIN_CODE` if you've added a pin code
+ to your account
- textsecure-cli -u USERNAME send -m "This is a message" [RECIPIENT [RECIPIENT ...]]
+ signal-cli -a ACCOUNT verify CODE
+
+* Send a message
+
+ ```sh
+ signal-cli -a ACCOUNT send -m "This is a message" RECIPIENT
+ ```
+
+* Send a message to a username, usernames need to be prefixed with `u:`
+
+ ```sh
+ signal-cli -a ACCOUNT send -m "This is a message" u:USERNAME.000
+ ```
* Pipe the message content from another process.
- uname -a | textsecure-cli -u USERNAME send [RECIPIENT [RECIPIENT ...]]
+ uname -a | signal-cli -a ACCOUNT send --message-from-stdin RECIPIENT
+
+* Receive messages
+
+ signal-cli -a ACCOUNT receive
+
+**Hint**: The Signal protocol expects that incoming messages are regularly received (using `daemon` or `receive`
+command). This is required for the encryption to work efficiently and for getting updates to groups, expiration timer
+and other features.
## Storage
-The password and cryptographic keys are created when registering and stored in the current users home directory.
+The password and cryptographic keys are created when registering and stored in the current users home directory:
- $HOME/.config/textsecure/data/
+ $XDG_DATA_HOME/signal-cli/data/
+ $HOME/.local/share/signal-cli/data/
## Building
-This project uses [Gradle](http://gradle.org) for building and maintaining
-dependencies.
+This project uses [Gradle](http://gradle.org) for building and maintaining dependencies. If you have a recent gradle
+version installed, you can replace `./gradlew` with `gradle` in the following steps.
1. Checkout the source somewhere on your filesystem with
- git clone https://github.com/AsamK/textsecure-cli.git
+ git clone https://github.com/AsamK/signal-cli.git
2. Execute Gradle:
- ./gradlew build
+ ./gradlew build
-3. Create shell wrapper in *build/install/textsecure-cli/bin*:
+ 2a. Create shell wrapper in *build/install/signal-cli/bin*:
- ./gradlew installDist
+ ./gradlew installDist
-4. Create tar file in *build/distributions*:
+ 2b. Create tar file in *build/distributions*:
- ./gradlew distTar
+ ./gradlew distTar
-## Troubleshooting
-If you use a version of the Oracle JRE and get an InvalidKeyException you need to enable unlimited strength crypto. See https://stackoverflow.com/questions/6481627/java-security-illegal-key-size-or-default-parameters for instructions.
+ 2c. Create a fat tar file in *build/libs/signal-cli-fat*:
+
+ ./gradlew fatJar
+
+ 2d. Compile and run signal-cli:
+
+ ```sh
+ ./gradlew run --args="--help"
+ ```
+
+### Building a native binary with GraalVM (EXPERIMENTAL)
+
+It is possible to build a native binary with [GraalVM](https://www.graalvm.org). This is still experimental and will not
+work in all situations.
+
+1. [Install GraalVM and setup the environment](https://www.graalvm.org/docs/getting-started/#install-graalvm)
+2. Execute Gradle:
+
+ ./gradlew nativeCompile
+
+ The binary is available at *build/native/nativeCompile/signal-cli*
+
+## FAQ and Troubleshooting
+
+For frequently asked questions and issues have a look at the [wiki](https://github.com/AsamK/signal-cli/wiki/FAQ).
## License
-This project uses libtextsecure-java from Open Whisper Systems:
+This project uses libsignal-service-java from Open Whisper Systems:
-https://github.com/WhisperSystems/libtextsecure-java
+https://github.com/WhisperSystems/libsignal-service-java
Licensed under the GPLv3: http://www.gnu.org/licenses/gpl-3.0.html
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index b7250a83..00000000
--- a/build.gradle
+++ /dev/null
@@ -1,26 +0,0 @@
-apply plugin: 'java'
-apply plugin: 'application'
-
-sourceCompatibility = "1.8";
-
-mainClassName = 'cli.Main'
-
-repositories {
- mavenCentral()
-}
-
-dependencies {
- compile 'org.whispersystems:textsecure-java:1.6.2'
- compile 'com.madgag.spongycastle:prov:1.52.0.0'
- compile 'org.json:json:20141113'
- compile 'commons-io:commons-io:2.4'
- compile 'net.sourceforge.argparse4j:argparse4j:0.5.0'
-}
-
-jar {
- baseName = 'textsecure-cli'
- version = '0.0.2'
- manifest {
- attributes 'Main-Class': 'cli.Main'
- }
-}
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 00000000..ea69d97e
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,139 @@
+plugins {
+ java
+ application
+ eclipse
+ `check-lib-versions`
+ id("org.graalvm.buildtools.native") version "0.10.6"
+}
+
+allprojects {
+ group = "org.asamk"
+ version = "0.13.19-SNAPSHOT"
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_21
+ targetCompatibility = JavaVersion.VERSION_21
+
+ if (!JavaVersion.current().isCompatibleWith(targetCompatibility)) {
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(targetCompatibility.majorVersion))
+ }
+ }
+}
+
+application {
+ mainClass.set("org.asamk.signal.Main")
+ applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED")
+}
+
+graalvmNative {
+ binaries {
+ this["main"].run {
+ buildArgs.add("--install-exit-handlers")
+ buildArgs.add("-Dfile.encoding=UTF-8")
+ buildArgs.add("-J-Dfile.encoding=UTF-8")
+ buildArgs.add("-march=compatibility")
+ resources.autodetect()
+ configurationFileDirectories.from(file("graalvm-config-dir"))
+ if (System.getenv("GRAALVM_HOME") == null) {
+ toolchainDetection.set(true)
+ javaLauncher.set(javaToolchains.launcherFor {
+ languageVersion.set(JavaLanguageVersion.of(21))
+ })
+ } else {
+ toolchainDetection.set(false)
+ }
+ }
+ }
+}
+
+val artifactType = Attribute.of("artifactType", String::class.java)
+val minified = Attribute.of("minified", Boolean::class.javaObjectType)
+dependencies {
+ attributesSchema {
+ attribute(minified)
+ }
+ artifactTypes.getByName("jar") {
+ attributes.attribute(minified, false)
+ }
+}
+
+configurations.runtimeClasspath.configure {
+ attributes {
+ attribute(minified, true)
+ }
+}
+val excludePatterns = mapOf(
+ "libsignal-client" to setOf(
+ "libsignal_jni_testing_amd64.so",
+ "signal_jni_testing_amd64.dll",
+ "libsignal_jni_testing_amd64.dylib",
+ "libsignal_jni_testing_aarch64.dylib",
+ )
+)
+
+dependencies {
+ registerTransform(JarFileExcluder::class) {
+ from.attribute(minified, false).attribute(artifactType, "jar")
+ to.attribute(minified, true).attribute(artifactType, "jar")
+
+ parameters {
+ excludeFilesByArtifact = excludePatterns
+ }
+ }
+
+ implementation(libs.bouncycastle)
+ implementation(libs.jackson.databind)
+ implementation(libs.argparse4j)
+ implementation(libs.dbusjava)
+ implementation(libs.slf4j.api)
+ implementation(libs.slf4j.jul)
+ implementation(libs.logback)
+ implementation(project(":libsignal-cli"))
+}
+
+configurations {
+ implementation {
+ resolutionStrategy.failOnVersionConflict()
+ }
+}
+
+
+tasks.withType().configureEach {
+ isPreserveFileTimestamps = false
+ isReproducibleFileOrder = true
+}
+
+tasks.withType {
+ options.encoding = "UTF-8"
+}
+
+tasks.withType {
+ manifest {
+ attributes(
+ "Implementation-Title" to project.name,
+ "Implementation-Version" to project.version,
+ "Main-Class" to application.mainClass.get(),
+ "Enable-Native-Access" to "ALL-UNNAMED",
+ )
+ }
+}
+
+tasks.register("fatJar", type = Jar::class) {
+ archiveBaseName.set("${project.name}-fat")
+ exclude(
+ "META-INF/*.SF",
+ "META-INF/*.DSA",
+ "META-INF/*.RSA",
+ "META-INF/NOTICE*",
+ "META-INF/LICENSE*",
+ "META-INF/INDEX.LIST",
+ "**/module-info.class",
+ )
+ duplicatesStrategy = DuplicatesStrategy.WARN
+ doFirst {
+ from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
+ }
+ with(tasks.jar.get())
+}
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
new file mode 100644
index 00000000..3e8942b9
--- /dev/null
+++ b/buildSrc/build.gradle.kts
@@ -0,0 +1,28 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
+
+plugins {
+ `kotlin-dsl`
+}
+
+tasks.named>("compileKotlin").configure {
+ compilerOptions.jvmTarget.set(JvmTarget.JVM_17)
+}
+
+java {
+ targetCompatibility = JavaVersion.VERSION_17
+}
+
+repositories {
+ mavenCentral()
+}
+
+gradlePlugin {
+ plugins {
+ register("check-lib-versions") {
+ id = "check-lib-versions"
+ implementationClass = "CheckLibVersionsPlugin"
+ }
+ }
+}
diff --git a/buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt b/buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt
new file mode 100644
index 00000000..ae8ae4de
--- /dev/null
+++ b/buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt
@@ -0,0 +1,40 @@
+@file:Suppress("DEPRECATION")
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.artifacts.Dependency
+import javax.xml.parsers.DocumentBuilderFactory
+
+class CheckLibVersionsPlugin : Plugin {
+ override fun apply(project: Project) {
+ project.task("checkLibVersions") {
+ description =
+ "Find any 3rd party libraries which have released new versions to the central Maven repo since we last upgraded."
+ doLast {
+ project.configurations.flatMap { it.allDependencies }
+ .toSet()
+ .forEach { checkDependency(it) }
+ }
+ }
+ }
+
+ private fun Task.checkDependency(dependency: Dependency) {
+ val version = dependency.version
+ val group = dependency.group
+ val path = group?.replace(".", "/") ?: ""
+ val name = dependency.name
+ val metaDataUrl = "https://repo1.maven.org/maven2/$path/$name/maven-metadata.xml"
+ try {
+ val dbf = DocumentBuilderFactory.newInstance()
+ val db = dbf.newDocumentBuilder()
+ val doc = db.parse(metaDataUrl);
+ val newest = doc.getElementsByTagName("latest").item(0).textContent
+ if (version != newest.toString()) {
+ println("UPGRADE {\"group\": \"$group\", \"name\": \"$name\", \"current\": \"$version\", \"latest\": \"$newest\"}")
+ }
+ } catch (e: Throwable) {
+ logger.debug("Unable to download or parse {}: {}", metaDataUrl, e.message)
+ }
+ }
+}
diff --git a/buildSrc/src/main/kotlin/ExcludeFileFromJar.kt b/buildSrc/src/main/kotlin/ExcludeFileFromJar.kt
new file mode 100644
index 00000000..25862cc9
--- /dev/null
+++ b/buildSrc/src/main/kotlin/ExcludeFileFromJar.kt
@@ -0,0 +1,53 @@
+import org.gradle.api.artifacts.transform.*
+import org.gradle.api.file.FileSystemLocation
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.util.zip.ZipInputStream
+import java.util.zip.ZipOutputStream
+
+@CacheableTransform
+abstract class JarFileExcluder : TransformAction {
+ interface Parameters : TransformParameters {
+ @get:Input
+ var excludeFilesByArtifact: Map>
+ }
+
+ @get:PathSensitive(PathSensitivity.NAME_ONLY)
+ @get:InputArtifact
+ abstract val inputArtifact: Provider
+
+ override
+ fun transform(outputs: TransformOutputs) {
+ val fileName = inputArtifact.get().asFile.name
+ for (entry in parameters.excludeFilesByArtifact) {
+ if (fileName.startsWith(entry.key)) {
+ val nameWithoutExtension = fileName.substring(0, fileName.lastIndexOf("."))
+ excludeFiles(inputArtifact.get().asFile, entry.value, outputs.file("${nameWithoutExtension}.jar"))
+ return
+ }
+ }
+ outputs.file(inputArtifact)
+ }
+
+ private fun excludeFiles(artifact: File, excludeFiles: Set, jarFile: File) {
+ ZipInputStream(FileInputStream(artifact)).use { input ->
+ ZipOutputStream(FileOutputStream(jarFile)).use { output ->
+ var entry = input.nextEntry
+ while (entry != null) {
+ if (!excludeFiles.contains(entry.name)) {
+ output.putNextEntry(entry)
+ input.copyTo(output)
+ output.closeEntry()
+ }
+
+ entry = input.nextEntry
+ }
+ }
+ }
+ }
+}
diff --git a/client/.gitignore b/client/.gitignore
new file mode 100644
index 00000000..b83d2226
--- /dev/null
+++ b/client/.gitignore
@@ -0,0 +1 @@
+/target/
diff --git a/client/Cargo.lock b/client/Cargo.lock
new file mode 100644
index 00000000..01f28e0c
--- /dev/null
+++ b/client/Cargo.lock
@@ -0,0 +1,1756 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "addr2line"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "anstream"
+version = "0.6.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.98"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
+
+[[package]]
+name = "async-trait"
+version = "0.1.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "backtrace"
+version = "0.3.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
+dependencies = [
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bitflags"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
+
+[[package]]
+name = "bytes"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+
+[[package]]
+name = "cc"
+version = "1.2.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
+
+[[package]]
+name = "clap"
+version = "4.5.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+ "terminal_size",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+
+[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
+dependencies = [
+ "libc",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+
+[[package]]
+name = "futures-task"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+
+[[package]]
+name = "futures-timer"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
+
+[[package]]
+name = "futures-util"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+dependencies = [
+ "futures-core",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gimli"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+
+[[package]]
+name = "h2"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "http"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "hyper"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "log",
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "libc",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
+
+[[package]]
+name = "icu_properties"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "potential_utf",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
+
+[[package]]
+name = "icu_provider"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "idna"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "io-uring"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "jni"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
+dependencies = [
+ "cesu8",
+ "cfg-if",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror 1.0.69",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
+[[package]]
+name = "jsonrpsee"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fba77a59c4c644fd48732367624d1bcf6f409f9c9a286fbc71d2f1fc0b2ea16"
+dependencies = [
+ "jsonrpsee-core",
+ "jsonrpsee-http-client",
+ "jsonrpsee-proc-macros",
+ "jsonrpsee-types",
+ "tracing",
+]
+
+[[package]]
+name = "jsonrpsee-core"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "693c93cbb7db25f4108ed121304b671a36002c2db67dff2ee4391a688c738547"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-timer",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "jsonrpsee-types",
+ "pin-project",
+ "rustc-hash",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.12",
+ "tokio",
+ "tokio-stream",
+ "tower",
+ "tracing",
+]
+
+[[package]]
+name = "jsonrpsee-http-client"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6962d2bd295f75e97dd328891e58fce166894b974c1f7ce2e7597f02eeceb791"
+dependencies = [
+ "base64",
+ "http-body",
+ "hyper",
+ "hyper-rustls",
+ "hyper-util",
+ "jsonrpsee-core",
+ "jsonrpsee-types",
+ "rustls",
+ "rustls-platform-verifier",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.12",
+ "tokio",
+ "tower",
+ "url",
+]
+
+[[package]]
+name = "jsonrpsee-proc-macros"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fa4f5daed39f982a1bb9d15449a28347490ad42b212f8eaa2a2a344a0dce9e9"
+dependencies = [
+ "heck",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "jsonrpsee-types"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66df7256371c45621b3b7d2fb23aea923d577616b9c0e9c0b950a6ea5c2be0ca"
+dependencies = [
+ "http",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.12",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.174"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
+
+[[package]]
+name = "litemap"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
+
+[[package]]
+name = "log"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+
+[[package]]
+name = "memchr"
+version = "2.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+]
+
+[[package]]
+name = "mio"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "object"
+version = "0.36.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pin-project"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "potential_utf"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
+dependencies = [
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.95"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+
+[[package]]
+name = "rustix"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1"
+dependencies = [
+ "log",
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3"
+dependencies = [
+ "openssl-probe",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
+dependencies = [
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-platform-verifier"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1"
+dependencies = [
+ "core-foundation",
+ "core-foundation-sys",
+ "jni",
+ "log",
+ "once_cell",
+ "rustls",
+ "rustls-native-certs",
+ "rustls-platform-verifier-android",
+ "rustls-webpki",
+ "security-framework",
+ "security-framework-sys",
+ "webpki-root-certs 0.26.11",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rustls-platform-verifier-android"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "security-framework"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.140"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "signal-cli-client"
+version = "0.0.1"
+dependencies = [
+ "anyhow",
+ "bytes",
+ "clap",
+ "futures-util",
+ "jsonrpsee",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.12",
+ "tokio",
+ "tokio-util",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "socket2"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "2.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "terminal_size"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
+dependencies = [
+ "rustix",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+dependencies = [
+ "thiserror-impl 2.0.12",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tokio"
+version = "1.46.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "io-uring",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "slab",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
+
+[[package]]
+name = "toml_edit"
+version = "0.22.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "tower"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "url"
+version = "2.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "webpki-root-certs"
+version = "0.26.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e"
+dependencies = [
+ "webpki-root-certs 1.0.1",
+]
+
+[[package]]
+name = "webpki-root-certs"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86138b15b2b7d561bc4469e77027b8dd005a43dc502e9031d1f5afc8ce1f280e"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
+dependencies = [
+ "windows_aarch64_gnullvm 0.53.0",
+ "windows_aarch64_msvc 0.53.0",
+ "windows_i686_gnu 0.53.0",
+ "windows_i686_gnullvm 0.53.0",
+ "windows_i686_msvc 0.53.0",
+ "windows_x86_64_gnu 0.53.0",
+ "windows_x86_64_gnullvm 0.53.0",
+ "windows_x86_64_msvc 0.53.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+
+[[package]]
+name = "winnow"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "writeable"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
+
+[[package]]
+name = "yoke"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+
+[[package]]
+name = "zerotrie"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/client/Cargo.toml b/client/Cargo.toml
new file mode 100644
index 00000000..aff9ede4
--- /dev/null
+++ b/client/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "signal-cli-client"
+version = "0.0.1"
+edition = "2024"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1"
+clap = { version = "4", features = ["cargo", "derive", "wrap_help"] }
+serde = "1"
+serde_json = "1"
+tokio = { version = "1", features = ["rt", "macros", "net", "rt-multi-thread"] }
+jsonrpsee = { version = "0.25", features = [
+ "macros",
+ "async-client",
+ "http-client",
+] }
+bytes = "1"
+tokio-util = "0.7"
+futures-util = "0.3"
+thiserror = "2"
diff --git a/client/src/cli.rs b/client/src/cli.rs
new file mode 100644
index 00000000..7fa5d1c5
--- /dev/null
+++ b/client/src/cli.rs
@@ -0,0 +1,470 @@
+use std::{ffi::OsString, net::SocketAddr};
+
+use clap::{crate_version, Parser, Subcommand, ValueEnum};
+
+/// JSON-RPC client for signal-cli
+#[derive(Parser, Debug)]
+#[command(rename_all = "kebab-case", version = crate_version!())]
+pub struct Cli {
+ /// Account to use (for daemon in multi-account mode)
+ #[arg(short = 'a', long)]
+ pub account: Option,
+
+ /// TCP host and port of signal-cli daemon
+ #[arg(long, conflicts_with = "json_rpc_http")]
+ pub json_rpc_tcp: Option