npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@miadi/capture-service

v0.3.0

Published

The capture deployable — @miadi/capture's Recorder and TakeStore behind HTTP: start/pause/resume/stop, ranged playback, Groq transcription sidecars, and one registration POST per take to the chronicle registry. A take is durable on disk before the registr

Readme

@miadi/capture-service

The capture deployable — @miadi/capture's Recorder + TakeStore + driver seam behind HTTP, per rispecs/capture-service.spec.md and the vocabulary law (rispecs/capture-vocabulary.spec.md). Runs on the gaia server and, one day, on the Android device: same service, same routes, different drivers.

UI panels · MCP tools · forgewright (client only) · scripts
        ▲
@miadi/capture-client        envelope + fetch, register()
        ▲
@miadi/capture-service       THIS — HTTP over the core, deployable
        ▲
@miadi/capture               Recorder + TakeStore + CaptureDriver seam
        ▲
drivers                      file-import (reference) · termux (device-untested)

The strangler condition

Verbatim, as a rule:

gmtermux capture keeps running until the capture-service records its first real take on ilex. Nobody loses the ability to record during the transition.

No cutover date, no flag day. Until that first real take, the pixel prototype stays up and untouched — and this package makes no claim to have replaced it.

Routes

Born under /api/captures/* per the vocabulary law §8 — this service never carries the old /api/recordings name.

| Verb | Route | Answer | |---|---|---| | start | POST /api/captures/start | {success, filename, filepath, startedAt, context} | | pause | POST /api/captures/pause | {success, segments} | | resume | POST /api/captures/resume | {success, segment} | | stop | POST /api/captures/stop | {success, filename, tlid, segments, durationSeconds, capture, belonging, registration} | | transcribe | POST /api/captures/transcribe | {success, filename, cached, transcription} | | status | GET /api/captures/status | canonical RecorderStatus + {driver, takesDir, device, pendingRegistrations} | | list | GET /api/captures | {takes: [{filename, kind, sizeBytes, modifiedAt, hasProvenance, hasTranscription}]} | | audio | GET /api/captures/audio/<filename> | the bytes; honors Range: (206/416) for a seeking player | | describe | GET / | service identity and the route table |

Refusals answer with their status (400/404/409/413/503) and the error's own words in {success: false, error}.

Coupling note (vocabulary §8): @miadi/capture-client's CANONICAL_ROUTES control table still says /api/recordings/*; its register() already POSTs to /api/captures. Until the §8 rename executes on the client's route table, a canonical-dialect client reaches this service's registration route but not its control routes. That rename is coupling point 3 of the seat's next ordered act — it belongs to packages/capture-client, not here.

Belonging — 0, 1, or 2, never demanded

episode_path and composition may be declared in the start body (frozen into the take's context before any await — the Songbird rule) or the stop body (which wins field-by-field), or neither: the take registers as an inbox resident — a complete resident awaiting the human's later choice, not an error. Takes live under MIADI_CAPTURE_INBOX.

Registration — one record, one registry

On stop the service POSTs one record to {MIADI_CHRONICLE_MW_URL}/api/captures through @miadi/capture-client's register() (the record travels verbatim, honoring the id law). The wheel answers {success, capture, provider}; the client's normalizer is indifferent to the exact envelope — verified against normalizeRegister.

Registration failure never loses the take. The bytes and the .take.json provenance sidecar are durable on disk before the registry hears one word. A failed POST lands in .pending-registrations.json beside the takes (atomic writes via @miadi/node-service-kit; a corrupt queue file is preserved aside, never overwritten) and is retried at boot and via flushPending(). The status route reports pendingRegistrations so a stuck queue is visible, not silent.

Drivers

| Driver | Status | What it is | |---|---|---| | file-import | REAL, tested | The reference driver: "capture" by ingesting an existing audio/video/midi file (import_path in the start body). Deterministic, filesystem-only — it is what makes the gaia full-cycle acceptance run with no microphone. The imported file keeps its own extension, so kind inference stays honest. | | termux | BUILT, DEVICE-UNTESTED | termux-microphone-record behind the seam, command shapes lifted by specification from gmtermux web/pixel/recorder.js (-f <file> -l 0 -e aac -r 44100 / -q / -i). Argument arrays through execFile — no shell, unlike the ancestor. The ancestor's file-finalization stability wait lives inside stop(), per the driver contract. Command builders and process handling are unit-tested against a fake runner; no Termux exists where this was built — the first run on a real device is still owed. | | video capture | UNBUILT | Named as unbuilt in the spec; new ground. |

Joiners: FfmpegSegmentJoiner (ffmpeg -y -f concat -safe 0 -i <list> -c copy, the ancestor's join path — ffmpeg itself untested here, the command construction is) and ConcatSegmentJoiner (byte concatenation — exact for the reference cycle and raw formats; container formats need ffmpeg).

Transcription

The Groq contract per spec §3, behind a Transcriber interface with the live implementation never touched by tests:

  • two calls per take, model whisper-large-v3 — transcription (original language, default fr) then English translation;
  • Bearer $GROQ_API_KEY, from the process environment or ~/.env; no key → a plain 503, the take untouched;
  • result cached as <stem>.json beside the take — the sidecar name is load-bearing (forgewright reads it) and distinct from <file>.take.json; the same take is never sent twice (force: true overrides);
  • the ancestor's french/english keys are kept in the sidecar (english always; french when the language is fr) so the reader that exists today keeps reading, alongside parameterized transcription/translation.

Configuration

| Env | Default | Meaning | |---|---|---| | MIADI_CAPTURE_PORT | 8770 | Never 8768 — that port belongs to the pixel prototype and dies with it; the service refuses to boot on it. | | MIADI_CAPTURE_HOST | 0.0.0.0 | A capture service exists to be reached. | | MIADI_CAPTURE_INBOX | $MIADI_CHRONICLE_ROOT/capture-inbox, else ~/.miadi/capture-inbox | The take library and the inbox — one directory. | | MIADI_CHRONICLE_MW_URL | http://127.0.0.1:8040 | The chronicle wheel; registration POSTs to {MIADI_CHRONICLE_MW_URL}/api/captures. Read first; MW_API_URL_OVERRIDE / MW_API_URL are legacy fallbacks. | | MIADI_CAPTURE_DRIVER | file-import | file-import or termux. | | MIADI_CAPTURE_DEVICE | os.hostname() | The device identity, as it names itself. | | MIADI_CAPTURE_PUBLIC_URL | http://<local-ip>:<port> | Base of the uri a registration carries. | | MIADI_CAPTURE_LANGUAGE | fr | Original language for transcription calls. | | GROQ_API_KEY | — (env or ~/.env) | Absent → transcribe refuses plainly. |

Run: miadi-capture-service (bin) or npm start.

What the spec left open — decided here, marked here

These are implementation decisions this package had to make; none of them touches the spec's explicit unknowns (vocabulary §11), which stay held:

  1. Inbox fallback without a chronicle root: the spec's default is under $MIADI_CHRONICLE_ROOT; when that variable is absent the service falls back to ~/.miadi/capture-inbox rather than refusing to boot.
  2. The registration uri is the service's own ranged-GET playback URL ({public-url}/api/captures/audio/<filename>) — the registry holds URIs, never bytes, and this is the URI at which the bytes answer.
  3. Belonging merge order: stop-body wins field-by-field over start-frozen context. The timing ruling names both timings; it does not order them — "declared at stop is the normal path" decided the tiebreak.
  4. Assignment-later has no endpoint. Spec §5's open question (COPY bytes vs RE-POINT a URI on inbox assignment) is explicitly undecided; building an assignment route would resolve it by accident. Registration happens at stop; later assignment waits for the human's word. The id law is honored in the wire shape (id passthrough in buildRegistration) so the upsert is ready the day the question is answered.
  5. Sidecar content shape: the spec rules the sidecar's name; the content keeps the ancestor's keys plus parameterized fields (see Transcription).
  6. GROQ_API_KEY resolution order: process environment first, then ~/.env — the spec names ~/.env; a deployment that injects the environment directly should not be forced to write a file.

What is real vs. not

  • Real and tested: the HTTP service, the file-import cycle end to end (start → stop → durable take + sidecar → one registration → list → ranged playback → transcription sidecar → cache), the durable registration queue, the belonging algebra, the port law, the traversal gate.
  • Built, device-untested: the termux driver (no device here), the ffmpeg joiner (no ffmpeg invoked in tests), the live Groq transcriber (no live API calls in tests — the contract is exercised against a stub fetch).
  • Unbuilt: video capture; Android deployment (supervision, boot, permissions, port, mechanism) stays UNKNOWN-HOLD per spec §7 — a workstream of its own.

Tests

npm test — builds, then node --test test/*.test.mjs. 43 tests, all hermetic: temp directories, stub runners, stub registry, stub fetch. No microphone, no ffmpeg, no Termux, no live Groq, no wheel required.


🌸: The doorknob turns the same way on a server as on a phone — this service is the hand that fits both, and the inbox is its kindest room: a take with no home yet is not lost, it is simply still being asked where it wants to live.