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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: CI

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
push:
branches:
- main
pull_request:

permissions:
contents: read

jobs:
test:
timeout-minutes: 15
strategy:
matrix:
include:
- os: ubuntu-latest
node: 20
- os: ubuntu-latest
node: 22
- os: macos-latest
node: 20
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- name: Install native MIDI build dependency
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install --yes libasound2-dev
- run: npm ci
- run: npm run check
- name: Pack, clean-install, and smoke-test the CLI
run: npm run test:package
87 changes: 65 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,48 @@ Requirements:
- a native MIDI toolchain supported by `@julusian/midi`
- on Linux, the ALSA development package required by RtMidi

Install the current GitHub version globally:

```sh
npm install --global --include=dev github:oveddan/mft-api
```

This installs the `mft-config` executable. GitHub source installs need
`--include=dev` because their installation-time build uses TypeScript. Production
installs that omit development dependencies should use a prebuilt tarball.

You can also install a release tarball when one is available:

```sh
npm install --global ./oveddan-mft-config-0.1.0.tgz
```

Confirm the command is ready:

```sh
mft-config --help
```

The former `mft-export` executable remains as a compatibility alias for now.
New scripts and documentation should use `mft-config`; the alias may be removed
in a future major release.

The package is not published to the npm registry. This repository does not yet
have an explicit host-tool license, so registry publishing remains blocked until
the owner chooses and adds one. The package metadata deliberately uses
`UNLICENSED` and `private: true` in the meantime. `UNLICENSED` does not grant
permission to redistribute or modify the package; usage terms remain pending.
Adding a license must include an explicit decision to keep or remove the
`private` publish guard.

### Contributor setup

```sh
git clone https://github.com/oveddan/mft-api.git
cd mft-api
npm install
npm run check
npm link
```

Build the CLI after making changes:
Expand All @@ -60,27 +97,27 @@ Build the CLI after making changes:
npm run build
```

The examples below use `node dist/cli.js`. After packaging or linking the npm
binary, the equivalent command name is `mft-export`.
`npm link` builds the source checkout and links `mft-config` for contributor
testing. End-user commands use the installed executable.

## Read the controller

List connected Twisters:

```sh
node dist/cli.js list
mft-config list
```

Export the only connected device:

```sh
node dist/cli.js export --out twister-config.json
mft-config export --out twister-config.json
```

If more than one Twister is connected, use the index shown by `list`:

```sh
node dist/cli.js export --device 1 --out twister-config.json
mft-config export --device 1 --out twister-config.json
```

The JSON includes the firmware and unit identity, detected bank count, all
Expand Down Expand Up @@ -232,9 +269,9 @@ minutes and are bound to the snapshot hash, firmware version, and device ID.
### Make the top row green when active and purple when inactive

```sh
node dist/cli.js export --out twister-config.json
mft-config export --out twister-config.json

node dist/cli.js plan \
mft-config plan \
--snapshot twister-config.json \
--set bank.1.encoder.1.colors.active=green \
--set bank.1.encoder.1.colors.inactive=purple \
Expand All @@ -246,23 +283,23 @@ node dist/cli.js plan \
--set bank.1.encoder.4.colors.inactive=purple \
--out patch-plan.json

node dist/cli.js apply --plan patch-plan.json --yes
mft-config apply --plan patch-plan.json --yes
```

### Make the first two push switches toggle on and off

Code `1` is `ccToggle`:

```sh
node dist/cli.js export --out twister-config.json
mft-config export --out twister-config.json

node dist/cli.js plan \
mft-config plan \
--snapshot twister-config.json \
--set bank.1.encoder.1.switch.action.code=1 \
--set bank.1.encoder.2.switch.action.code=1 \
--out patch-plan.json

node dist/cli.js apply --plan patch-plan.json --yes
mft-config apply --plan patch-plan.json --yes
```

### Change a knob's rotary and push MIDI mappings
Expand All @@ -271,9 +308,9 @@ This makes bank 2, encoder 5 send CC 20 on channel 3 when turned, and a
momentary CC 40 on channel 4 when pressed:

```sh
node dist/cli.js export --out twister-config.json
mft-config export --out twister-config.json

node dist/cli.js plan \
mft-config plan \
--snapshot twister-config.json \
--set bank.2.encoder.5.encoder.type.code=1 \
--set bank.2.encoder.5.encoder.midiChannel=3 \
Expand All @@ -283,39 +320,39 @@ node dist/cli.js plan \
--set bank.2.encoder.5.switch.midiNumber=40 \
--out patch-plan.json

node dist/cli.js apply --plan patch-plan.json --yes
mft-config apply --plan patch-plan.json --yes
```

### Set a velocity-sensitive spread indicator with a red detent

```sh
node dist/cli.js export --out twister-config.json
mft-config export --out twister-config.json

node dist/cli.js plan \
mft-config plan \
--snapshot twister-config.json \
--set bank.3.encoder.9.movement.code=2 \
--set bank.3.encoder.9.indicator.code=3 \
--set bank.3.encoder.9.detent.enabled=true \
--set bank.3.encoder.9.detent.color=red \
--out patch-plan.json

node dist/cli.js apply --plan patch-plan.json --yes
mft-config apply --plan patch-plan.json --yes
```

### Change global brightness and sleep behavior

```sh
node dist/cli.js export --out twister-config.json
mft-config export --out twister-config.json

node dist/cli.js plan \
mft-config plan \
--snapshot twister-config.json \
--set global.brightness.rgb=96 \
--set global.brightness.indicator=80 \
--set global.sleep.timeoutIndex=4 \
--set global.sleep.animation.code=0 \
--out patch-plan.json

node dist/cli.js apply --plan patch-plan.json --yes
mft-config apply --plan patch-plan.json --yes
```

Timeout index `4` means 10 minutes; sleep animation code `0` turns the lights
Expand All @@ -328,12 +365,18 @@ Before sending any configuration frame, `apply`:
1. validates the plan hash and expiry;
2. discovers the controller again and checks its identity and firmware;
3. takes a fresh complete snapshot and rejects stale expected values;
4. saves a timestamped JSON backup under `.mft-state/backups/`;
4. saves a timestamped JSON backup under the per-user state directory;
5. writes targets sequentially without automatic retries;
6. reads the full configuration after every target and compares it with the
expected state; and
7. records pending, verified, failed, or unknown outcomes in an append-only
journal.
journal.

By default, backups, the single-use-plan journal, and the last verified snapshot
are stored under `~/.mft-config/` (or `$XDG_STATE_HOME/mft-config/`). This stable
location prevents changing the working directory from bypassing plan-consumption
checks. Set `MFT_CONFIG_STATE_DIR` to choose an explicit alternative.
The override must be an absolute path.

Completed plans are single-use. See [`docs/write-safety.md`](docs/write-safety.md)
for protocol-level details and remaining limitations.
Expand Down
6 changes: 4 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 23 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
{
"name": "@djtechtools/mft-export",
"name": "@oveddan/mft-config",
"version": "0.1.0",
"private": true,
"type": "module",
"description": "Guarded MIDI Fighter Twister configuration exporter and planner",
"description": "Safely read, plan, and write MIDI Fighter Twister configuration",
"license": "UNLICENSED",
"repository": {
"type": "git",
"url": "git+https://github.com/oveddan/mft-api.git"
},
"bugs": {
"url": "https://github.com/oveddan/mft-api/issues"
},
"homepage": "https://github.com/oveddan/mft-api#readme",
"files": [
"dist/**/*.js",
"docs/write-safety.md",
"schema/*.json",
"README.md"
],
"bin": {
"mft-config": "./dist/cli.js",
"mft-export": "./dist/cli.js"
},
"scripts": {
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
"build": "tsc -p tsconfig.json",
"test": "tsx --test test/*.test.ts",
"check": "npm run build && npm test"
"check": "npm run build && npm test",
"prepare": "npm run build",
"prepack": "npm run clean && npm run build",
"test:package": "node scripts/packed-artifact-smoke.mjs"
},
"engines": {
"node": ">=20"
Expand Down
Loading
Loading