feat: add scriptable MTP CLI reusing the Kalam engine#467
Open
dhruv-anand-aintech wants to merge 2 commits into
Open
feat: add scriptable MTP CLI reusing the Kalam engine#467dhruv-anand-aintech wants to merge 2 commits into
dhruv-anand-aintech wants to merge 2 commits into
Conversation
Move the entire koffi FFI binding to the native kalam.dylib engine out of Kalam.js into a new Electron-free KalamEngine class. Kalam becomes a thin subclass that injects the Electron-resolved kalamLibPath and the Sentry-backed log, so its public API (new Kalam()) is unchanged and GUI behaviour is identical. This decouples the MTP engine from the Electron runtime (Sentry, electron, electron-root-path) so it can also be driven outside the GUI (e.g. a CLI). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a small command-line interface that drives OpenMTP's native Kalam MTP
engine from the terminal, enabling automation of MTP file operations on macOS
where libusb-based CLI tools fail with libusb_claim_interface() = -3.
Subcommands: list-devices, ls, upload, download (plus --storage, --all, --json).
The CLI constructs KalamEngine directly with a plain-Node dylib path resolver
and a console logger, reusing the exact same engine the GUI uses - no MTP logic
is reimplemented.
- ffi/kalam/cli/{index,mtpCli,resolveLibPath}.js + README
- bin: openmtp-cli, and a 'yarn mtp-cli' script
- Jest unit tests for arg parsing, storage resolution, path resolution, and
command dispatch (engine mocked; no physical device required)
- add jest/babel-jest devDeps and a 'yarn test' script
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author
|
Note on base branch: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds a small, scriptable command-line interface that drives
OpenMTP's native Kalam MTP engine from the terminal, enabling automation of
MTP file operations (list, upload, download) on macOS.
Motivation
On macOS, the standard
libmtpCLI tools (mtp-detect,mtp-sendfile, …)often cannot claim the USB interface of MTP devices — they fail with
libusb_claim_interface() = -3(LIBUSB_ERROR_ACCESS) because IOKit will nothand the interface to userspace libusb. OpenMTP's GUI works anyway because it
ships its own MTP engine (Kalam mode), but that engine was only reachable
through the Electron GUI — there was no scriptable access.
This makes automation impossible: CI, scripted backups, and side-loading files
onto devices like Garmin watches all require manual drag-and-drop in the GUI. I
hit this directly trying to side-load a file onto a Garmin watch and had to drag
it manually because no CLI could write to the device. This CLI fills that gap by
exposing the same working engine to the terminal.
What it does
Options:
--storage <id>,--all(include hidden, forls),--json,-h/--help. Abinentry (openmtp-cli) is also declared.How it reuses the existing Kalam engine (no MTP logic reimplemented)
The koffi FFI binding to
kalam.dylibpreviously lived inKalam.js, but itsmodule-level imports (
log→@sentry/electron/electron,binaries→electron-root-path) hard-couple it to the Electron runtime, so it can't beimported under plain Node.
To reuse — not reinvent — the engine, this PR makes a small, backward-compatible
refactor:
ffi/kalam/src/KalamEngine.js(new): the entire existing koffi FFIbinding, moved verbatim into an Electron-free class.
libPathandloggerare injected via the constructor.ffi/kalam/src/Kalam.js: now a thin subclass ofKalamEnginethatinjects the Electron-resolved
kalamLibPathand the Sentry-backedlog. Itspublic API is unchanged —
new Kalam()works exactly as before, so the GUI(
FileExplorerKalamDataSource) is untouched in behaviour.ffi/kalam/cli/: the CLI. It constructsKalamEnginedirectly with aplain-Node dylib path resolver (
resolveLibPath.js, mirroring thebuild/mac/bin/<arch>/kalam.dyliblayout withoutelectron-root-path) and aconsolelogger, then calls the sameinitialize/walk/transferFiles/
listStorages/disposemethods the GUI uses.I verified the native engine loads and runs under plain Node (outside Electron):
yarn mtp-cli list-devicescorrectly loadskalam.dylib, callsInitialize,and reports
no MTP devices foundwhen nothing is attached.Tests
Added Jest unit tests (33 tests, all passing) covering argument parsing, storage
resolution, the dylib path resolver, and full command dispatch with the engine
mocked — so they need no physical device:
yarn testyarn lintpasses clean across the whole project.Testing status (please read)
no device attached (clean
no MTP devices found).device to confirm a real
ls/upload/downloadround-trip. The transfercall paths mirror
FileExplorerKalamDataSourceexactly, but I'd appreciate amaintainer (or anyone with a device) confirming real-world transfers before
this is relied upon.
Notes / open questions for maintainers
developmentis far behindmasterand predates the current Kalam engine, so this targetsmaster. Happy to rebase ontodevelopmentif you prefer.yarn mtp-cli(node -r @babel/register),matching how the project runs its other
.jsentry scripts. Thebinentryis wired but, like the rest of the app, assumes the babel toolchain; happy to
adjust if you'd prefer a transpiled standalone bin in the packaged build.
jest/babel-jestas dev deps (pinned to29.5.0for compatibilitywith the project's
@babel/core@7.20) plus ayarn testscript. If you havea preferred test setup, I'll align to it.
Happy to iterate on naming, layout, or scope. Thanks for OpenMTP!
🤖 Generated with Claude Code