Skip to content

First run: reset-admin and a sign-in reminder, choose your own account in setup, the nearest Audible marketplace - #389

Merged
KodeStar merged 36 commits into
mainfrom
feat/new-user-c
Nov 14, 2026
Merged

KodeStar merged 36 commits into
mainfrom
feat/new-user-c

Conversation

@KodeStar

@KodeStar KodeStar commented Nov 14, 2026 •

Copy link
Copy Markdown
Owner

A server installed with Docker (or any headless start) creates admin with a random password and prints it once. Recreate the container and it is gone, the console had no way to choose your own, and the sign-in page pointed at a setup link the log never had. This makes first sign-in recoverable and lets the admin pick their own account.

Closes #369, closes #192.

What changes

Lost the first-run password: reset-admin

  • New command audiosilo [--data DIR] reset-admin [username]. It sets a fresh random password for the only admin (or the one named) through the console's own set-password path, prints it, and revokes the first-run auth code printed beside the old password. Sessions end. A running server accepts the new password at once, and a cached session ends within a minute (restart to end it at once).
  • It refuses a missing or non-AudioSilo database, pending migrations, and a backup restore waiting for the next start. Exit codes: 0 ok, 1 refused or failed (if revoking the code fails, the new password is still printed first), 2 usage.
  • Until the first-run admin first signs in to the console with a password (or changes it), every later start logs a reminder with the exact command for where it runs: docker exec -u <uid>:<gid> <container> audiosilo --data /data reset-admin in our image (the server's own ids, so no root-owned files), the full /app/audiosilo/audiosilo --data /config path on the linuxserver image. The plaintext is never stored; the reminder is a hint only (owner decision, V2.7.0-PLAN decision 6).
  • The state is a server_state row first_admin written only by ensureAdmin when it creates the admin, so upgraded servers and --setup installs never show the reminder. No migration.
  • The auth token cache re-reads a cached row once it is a minute old, so a reset from another process reaches a running server.

Console setup flow

  • New first step Your account: change the username and choose a password (skippable). Reverses FIRST-RUN-PLAN.md decision 1 (V2.7.0-PLAN decision 5). The password changes before the rename, so a wrong current password leaves the name alone.
  • Connect a phone moves to the end and explains sign-in: scan with the app, the log's auth code works there, other people need an invite.
  • The sign-in page says what a new server's username and password are and where the password is; the "open the setup link" line shows only while /setup is open.
  • First-run setup: preselect the Audible marketplace nearest the admin, US only as the fallback #192: the Audible marketplace step preselects the marketplace nearest the admin (browser language region, then time zone, nearest-marketplace mapping, US fallback) while no region was ever chosen; config.yaml and AUDIOSILO_METADATA_REGION are never overridden. Settings > Community metadata offers the same suggestion as a row until a region is chosen. (The issue assumed the default was us; it is "No preference".)

Wire (all additive)

  • GET /admin/setup: printed_password (bool).
  • GET /admin/settings: metadata.region_chosen (bool).
  • PATCH /admin/users/{id}: optional username; 400 username_required, 409 username_taken. POST /admin/users also answers username_required.
  • POST /auth/password: a wrong current password is still 401, now with code: current_password_incorrect (shared with the player; shipped builds unaffected).
  • GET /setup on a server that never enabled the wizard now 303s to /admin (was 404), the same answer as a closed wizard, so the console's probe logs no 404 and a signed-out caller learns less than before.
  • Setup step ids: account first, phone last.

Tests and review

  • Go: reset-admin (one admin, named, several unnamed, unknown name, no admin, foreign file, pending migrations, pending restore, revoke failure, the code it revokes and keeps), the reminder state (upgrade, --setup, once-only writes, failed mark), rename authz (allowed and denied), the /setup answers, the token cache re-read. Console: the account step (order, wrong password, refused name), the locale/time zone mapping, preselect only when never chosen.
  • Gate green: scripts/build-admin.sh, go build/vet/test -race, golangci-lint, and the native-engine vet/test/lint.
  • Review: /simplify + /code-review two passes at the ★ level (high-x2, Fable 5.2), then two fix rounds at high (Opus).

Verified live (Playwright, real builds)

  • Fresh headless start: banner; restart: the reminder; reset-admin while running: the new password signs in at once, the old code and sessions get 401; after a console sign-in, no reminder. A data dir from main (3b03e1c) upgraded: no reminder. --setup: no reminder.
  • Docker: the printed docker exec -u 1026:100 ... command works on a non-root volume and files stay 1026:100.
  • Sign-in page: hint shown, no console errors, setup line only with /setup open.
  • Setup: Your account first (wrong password message in English and German, rename + new password, skip), phone step last with its Invite link.
  • Marketplace: en-GB/London -> United Kingdom, de-AT/Vienna -> Germany, unmapped -> United States, env-locked and config values kept, the Settings offer row.

Not verified: a real NAS (Synology/Unraid) container run.

Pairs with #390 (independent) and KodeStar/audiosilo-docs#307.

…first run

A first-run admin password lives only in the banner, so a recreated
container or a scrolled log left no way back in. The server now records
the banner's admin (server_state first_admin) and, until any admin signs
in with a password or that admin chooses their own, prints a framed
reminder at every start: where to sign in, as whom, and the reset-admin
command for this install (docker exec -u <uid>:<gid> inside a container).
An upgraded server or one set up with --setup has no record and never
prints it.

`audiosilo [--data dir] reset-admin [username]` sets a random password on
the only (or named) admin through the console's own rule, ending its
sessions, and prints it once. It refuses a missing database and one an
older release left with migrations pending.

For the console's new account step: PATCH /admin/users/{id} takes a
username (400 username_required, 409 username_taken), GET /admin/setup
says whether the caller still has the printed password, and a wrong
current password on POST /auth/password carries current_password_incorrect
so the console doesn't take it for a lost session. The setup steps now
start with account and end with phone.

GET /admin/settings adds metadata.region_chosen, false only while no
marketplace was ever set here, in config.yaml or by the environment, so
the console can offer the nearest one.

Part of #369. Part of #192.
The first step now lets the admin choose the username and password they
sign in with (a new password is asked for while they still have the one
the server printed), so the flow no longer ends with an admin still on a
generated name and password. The phone step comes last and says how
everyone signs in: the admin with these details, others by invite (a link
to People's invite). The sign-in page, when no /setup wizard is open,
says where a first-run password is printed and names reset-admin.

These files also carry the strings and types of the marketplace offer
that follows.

Part of #369.
A server whose marketplace was never set searches the US store first,
which mismatches most books outside the US. While settings say
metadata.region_chosen is false, the setup flow's metadata step
preselects the store nearest the browser (its languages' regions, then
its time zone, else the US) and Settings > Community metadata offers it
in one click, with "Keep no preference" as a choice of its own. Nothing
is saved until the admin acts.

Part of #192.
docker exec is root otherwise, and a file it made in /data would be
root's. The start-up hint already prints the -u form.

Part of #369.
The first-run admin's row was read and written by a second copy of the catalog's SQL, with its own version of the rule that an unreadable value counts as none. Both packages import store, so the accessors live there (GetState, PutState) and the catalog's names call them.

Part of #369.
The sign-in hint asked whether it runs in a container with its own copy of fsaccess.InContainer's two marker files. A new account with no username now returns the same ErrUsernameRequired a rename does (same text, so no wire change). reset-admin prints its failure once and picks the exit code after.

Part of #369.
The step's two hints named the length as {{min}} while the step passed count, so they read "At least  characters." The printed-password hint, the field's label and the mismatch refusal now use the People password dialog's strings (the same words in all six languages), and the optional hint takes count. The console keeps one MIN_PASSWORD (api/types.ts) for the setup and People models, which could not import each other's.

The step renames the signed-in admin through a new Session.updateUser, which only replaces the user, where it had signed in again with the stored token.

Part of #369.
send() had a second branch for the own-password path that parsed and threw the error on its own. The error is now read once and checkSession leaves the session alone for current_password_incorrect, beside the failed sign-in it already left alone.

Part of #369.
The Settings offer built "Country (store)" by hand; regionLabel in lib/regions.ts now serves it and the select options, and the offer works out the nearest marketplace only while one may be offered. The steps' doc comments carried their position, which moving the phone step had renumbered across four files; they name the step instead.

Part of #192.
A password sign-in and the admin's own password change each read the first_admin row and wrote it back whole. Landing together, the sign-in's write dropped chose_password, and the setup flow's account step asked for a new password again. Both notes now re-read and write the row in one transaction; the usual case, nothing to change, is still a single read and no write.

Part of #369.
FirstAdminWaiting returned the account ensureAdmin made even after another admin disabled or demoted it, so every start told the operator to sign in as an account the console refuses and to run reset-admin, which refuses it too. The hint now waits only for an enabled admin.

Part of #369.
…ir own

The console's People dialog changes the signed-in admin's password through PATCH /admin/users/{id}, which never recorded that the first-run admin chose it. printed_password stayed true, so the setup flow's account step still warned that the password came from the server log and refused Continue without another new one, and later starts kept printing the sign-in hint if that was the admin's first act. The handler now notes it when the caller patches their own password; one another admin sets for them is still not their choice.

Part of #369.
The console reads the setup envelope once a session, and saving a step swaps only its state, so printed_password stayed true after the admin set a password. Going Back to the step, or retrying after a later refusal, showed the server-log warning again and refused Continue without yet another new password. The cached envelope is now updated when the admin chooses one, in the step or in People's dialog on their own account.

Part of #369.
POST /admin/users kept its own empty-username check and answered a 400 with no code, beside the coded 400 a rename gives for the same rule. The handler check is gone: auth.CreateUser's ErrUsernameRequired goes through writeUserError (same status and message, now with the code). The console words the code in its own language, where the account step showed the server's English under the field.

Part of #369.
GET /admin/setup answered 500 for the whole envelope when the read behind printed_password failed, which closes the setup flow and the Overview card over a flag that only words one step. It now logs the failure and answers printed_password false.

Part of #369.
ensureAdmin stopped the start when the first_admin row could not be written, after the admin was created and before its password was printed. The next start then found an admin (no banner) and no row (no hint): the password was shown nowhere. The banner is printed first now, and a failed write is logged and only loses the later reminder.

Part of #369.
A backslash is not a plain POSIX word, so on Windows the sign-in hint printed 'C:\AudioSilo\audiosilo.exe' --data 'C:\AudioSilo\data' reset-admin. cmd.exe takes the single quotes as part of the word, so the pasted command failed. On Windows a path's backslashes are now plain and a word that needs quoting takes double quotes.

Part of #369.
PendingMigrations answers nothing pending for a file with no schema record, so an empty audiosilo.db left by a failed start, or the wrong folder's file, went on to store.Open, which ran every migration on it before reset-admin said there was no admin. store.Inspect now refuses such a file before anything opens it for writing; a newer release's database is still opened as the server opens it.

Part of #369.
…ion lasts

After a reset, a restart before the first sign-in still told the operator to use the password printed when the server first started, which the reset had just replaced. reset-admin now ends the hint: whoever ran it holds a working admin password.

Its output said a running server may let a checked session carry on until its next change. The bound is tighter and now tested: the server's token cache cannot see another process's write, so a session it already checked is refused at the server's next content write or after the session's own last-seen touch, a minute of use at most and the request that makes it. The output and CONVENTIONS.md say so, with the rest of this entry's first-run changes.

Part of #369.
ensureAdmin returned on a failed auth-code write after the admin was created, with no banner and no first_admin row. The next start then found an admin (no banner) and no row (no hint), so nothing ever pointed the owner at reset-admin for a password printed nowhere. The admin is recorded on that path too.

Part of #369.
The step compared the trimmed field with the stored username, so an account whose name has a space at either end (the server allows it) was renamed by Continue alone, and got a 409 under a field the admin never touched when the trimmed name was taken. A name left as it was is now kept as it is.

Part of #369.
The command passed -u "$PUID:$PGID", which are the compose file's values, not the host shell's: pasted where they are unset it ran as -u ":". It now shows 1000:1000 and says they are the compose file's PUID:PGID.

Part of #369.
UpdateUser mapped any unique violation on its UPDATE to ErrUsernameTaken, whether or not the change named a username. Only users.username is unique today; a later unique index would have answered 409 username_taken to a role or password change.

Part of #369.
The form worked out again whether the account is the signed-in admin's, which the dialog around it already knows.

Part of #369.
The token cache is emptied by the content generation, which moves for every write through the server's own handle but never for another process's. reset-admin beside a running server is the first such writer: the sessions it ended were still answered from the cache until the server's next content write, and an idle session's first request afterwards was always answered, which is enough to set the admin's password again or mint an API key.

A cached row is now read again once it is a minute old, so a running server refuses those sessions within a minute, used or not. The cost is one more read a minute for a token in use, Bridge and feed tokens included. reset-admin's output says "up to a minute" and still names a restart as the way to end them at once.

Part of #369.
… restore waits

The first start prints an unlimited, never-expiring auth code in the same
log as the admin password, so whoever can read that log can still pair a
device after the password is reset. reset-admin now also deletes that
admin's first-run code (found by its label, unlimited uses and no expiry,
which only ensureAdmin makes, so installs from before the first_admin row
are covered) and says so in its output.

A restore waiting for the next start replaces the database and the new
password with it, so reset-admin refuses while one is pending (or while
the marker can't be read) and exits 1 with what to do.

The console's account step keeps the code, since the phone step points
at it, but its notice no longer implies a new password closes the log's
exposure: it says the code still signs the apps in and to revoke it in
People > Invites.

Also tests that a first_admin row that can't be written only warns: the
banner still prints and the server starts.

Part of #369.
… read

A first_admin row that can't be read must not refuse the setup flow; the
account step then asks for nothing.

Part of #369.
The revoke test minted the initial code a second time after an invite had
superseded the one ensureAdmin printed, so it never proved ensureAdmin's
code has the shape RevokeInitialAdminCode matches. Keep the printed code
and set an unlimited code under another label beside it instead. The
restore-marker refusal cases now each expect their own message, so the
waiting-restore and unreadable-marker paths can't be swapped unseen.

Part of #369.
reset-admin returned as soon as deleting the initial admin code failed,
after SetPassword had already replaced the password and ended every
session, so the new password was never shown and the admin was locked
out. It now notes the sign-in, prints the password, and only then
returns the revoke's error.

Part of #369.
The console's sign-in page asks HEAD /setup whether the first-run wizard
is open, and on every normal server that answered 404, so each sign-in
page view logged a failed request in the browser's console. A wizard
that was never enabled now answers as a closed one does, a redirect to
/admin, which the probe reads as closed without an error. A signed-out
caller learns less than before: the 404 used to tell a server never
started with --setup from one whose wizard had closed.

Part of #369.
…password

After reset-admin the account step still said the password came from the
server log and to revoke the first-run code, though the new password went
to the terminal and the code was already revoked. reset-admin now notes
the reset as the admin's own password (auth.NoteResetPassword), so
printed_password turns false for the first-run admin; another admin's
reset leaves it as it was.

Part of #369.
A wrong current password failed the step after the rename had already
been saved, so the admin's name changed though Continue reported an
error. The password change goes first now: its refusal leaves the name
as it was, and a rename refused after it doesn't send the password again.

Part of #369.
NoteResetPassword only forwarded to NoteOwnPassword, so reset-admin now
calls that and its doc says reset-admin is the other way a first-run
admin's printed password ends. NoteAdminSignIn's doc no longer claims
reset-admin uses it. The printed-password test reuses resetDir.

Part of #369.
The new test that a wrong current password leaves a new name unsent
repeated the existing wrong-current-password test; that test now types a
new name and checks no PATCH went out.

Part of #369.
The never-enabled half of TestSetupWizard reused the database where the
first half had already made an admin, so its redirect and refusal came
from the closed wizard and would pass with the setupToken check gone. It
now has its own empty database, expects POST to be 409 and checks no
admin was made.

Part of #369.
The route comment still said /setup 404s when never enabled, and
CONVENTIONS.md said reset-admin ends the hint through NoteAdminSignIn and
leaves printed_password up. It now redirects to /admin either way, and a
reset of the first-run admin also ends printed_password.

Part of #369.
@KodeStar
KodeStar merged commit 965b00d into main Nov 14, 2026
4 checks passed
@KodeStar
KodeStar deleted the feat/new-user-c branch November 14, 2026 18:03
KodeStar added a commit that referenced this pull request Nov 14, 2026
… found, ASINs the database doesn't know; invite and Bridge job polish (#390)

A new user moving from Audiobookshelf could not find bulk matching, hit
a dead end when a book was not in the community database, and had no way
to see books whose own ASIN the database does not know. This puts Match
automatically where people look, gives "not found" a next step, and
folds in the invite and Bridge-job polish left from batch B.

Closes #370. Part of #386 (everything except the player's `has_cover`,
which needs a player change and stays open there).

## server#370: matching surfaces
- Overview and Library > Books show "N books not matched" with **Match
automatically**, opening the bulk match card.
- A **Not found** row in bulk match and the match dialog's empty state
say the book is not in the community database yet and still plays as
normal, with **Add it**, a link to
`https://meta.audiosilo.app/add?q=...` prefilled with, in order, an ASIN
the admin typed, the book's ASIN, the search words, or title and author.
(meta's add page is `/add`; `/contribute` takes no prefill.)
- Health gets its own state, **ASIN not in the community database**,
from the existing `unknown_asin_reports` table, listed while the
unknown-ASIN report (Help complete the community database) is on. Such
books still count as matched: making them unmatched would let Match
automatically replace an ASIN the admin set (follow-up). The table's
userstate rule now moves the content generation on a new or flipped row
so the Health cache stays exact (measured: one move per report commit).
- Findings that were wrong: tag ASINs have not counted as matched since
0067; Overview already linked to Not matched.

## server#386: polish
- Invite dialog: the certificate warning shows for https when the server
runs self-signed (any name) or the link is at an IP address, with a
sentence for each reason.
- Invite page: wording for someone who typed a code rather than
following a link.
- A second Share press while the share sheet is open is ignored.
- Settings > Bridge > Jobs: failed and cancelled jobs keep their last
step.
- LibriVox fetch ids are checked with `model.ValidSlug`; the `Adopted`
tri-state has named states; one console `failureCode`; `useTrackedJob`
moved into the bridge feature.

## Wire
Admin API only, additive: issue kind `unknown_asin` in `/admin/issues`,
`?issue=`, and the ignore endpoint. No migration, nothing the player
reads.

## Tests and review
- Go: the issue kind (listing, gating on the report, ignore allowed for
an admin, 403 member, 401 anonymous), the userstate writers against the
content generation on both engines. Console: the new surfaces, Add it
URL building (encoding of `&`, `#`, `+`, `%`, `=`, non-ASCII, lone
surrogates), certificate reasons per case, share in flight, jobs' last
step.
- Gate green: `scripts/build-admin.sh`, `go build/vet/test -race`,
golangci-lint, native-engine vet/test/lint.
- Review: /simplify + /code-review at the full level (high, Fable 5.2);
copy fixes after live verification (gate only).

## Verified live (Playwright, real builds)
- Overview + Books notice opens bulk match; Not found rows and the empty
match dialog show the wording and Add it with the expected `q`; meta's
`/add` page takes the prefill.
- A real unknown-ASIN report run (through a local proxy so the made-up
ASIN never reached meta) shows the Health state; ignoring it holds.
- Certificate warning: self-signed at a name, TLS off at an https IP,
none on plain http.
- Typed-code invite wording on an iPhone viewport; failed and cancelled
jobs' last step (seeded jobs, no Bridge).
- Second Share press: with a stubbed share only; **a real phone run is
still owed**.

Pairs with #389 (independent) and
KodeStar/audiosilo-docs#307.
KodeStar added a commit to KodeStar/audiosilo-docs that referenced this pull request Nov 14, 2026
…ng a missing book; invite and job wording (#307)

Documents v2.7.0 batch C on the server.

- **First run:** where the password is in a NAS or Docker container's
log, the start-up reminder, and `reset-admin` with the exact command per
install type (our image, Unraid/linuxserver, Synology, Linux package, a
terminal), its refusals and exit codes. The setup flow's nine steps,
with Your account first and Connect a phone last, and the marketplace
preselect.
- **Troubleshooting:** "I lost the first-run admin password" now leads
with `reset-admin` before resetting the database.
- **Matching:** the Overview/Books notice, Add it to the community
database (new "Adding a missing book" section in
community/meta-site.md), and Health's "ASIN not in the community
database" state.
- **Invites and Bridge:** both certificate warnings, the typed-code
invite wording, and a failed job's last step.
- **Developer Docs:** the additive API fields and codes
(`printed_password`, `metadata.region_chosen`, `PATCH /admin/users`
`username`, `current_password_incorrect`, `username_required`,
`unknown_asin`, the `/setup` 303), the first-run state, and the token
cache's one-minute re-read.
- **Screenshots:** a new setup/account.png; the setup flow, the login
page, and the Overview and Books shots with the not-matched notice
recaptured.

Reviewed at high (Opus) with every command, string and field
fact-checked against the server branches. `npm run build` green.

Pairs with KodeStar/audiosilo-server#389 and
KodeStar/audiosilo-server#390.
KodeStar added a commit that referenced this pull request Nov 14, 2026
…393)

Part of #366 (moving from Audiobookshelf ends up matched). This is the
identifiers half; the certificate error, the import inside setup and the
match offer follow in a second PR once #389 and #390 merge.

## What

An applied Audiobookshelf import now keeps ABS's ASIN and ISBN for every
ABS book it can pair with one of ours, as a **match hint**:

- New path-keyed table `book_hints` (migration 0074: `library_id`,
`path`, `source`, `asin`, `isbn`, `import_id` with 0 = none, `set_at`).
It survives a rebuild, follows a moved folder (`moveBookState`) and goes
with its library.
- A hint is never the book's own ASIN, never on the player wire and
never on `/admin/books` (tested). Nothing shows until a match run is
applied.
- Match runs and the match dialog send it: a book's own ids first, then
the ABS hint, then tag hints. A tag ASIN goes only when the import gave
neither identifier (metaserve asks the ASIN first and stops on a hit, so
a tag ASIN beside an ABS ISBN would outrank the import). A known ASIN
whose length, title and series agree matches confidently and is recorded
the normal way (`book_matches` how=auto); a disagreeing one goes to
review.
- Pairing: path tier and title tier, every ABS book item, all-access.
Contested and gone items are skipped; books already matched are counted,
not hinted. Undo removes that import's hints; the last applied import
owns them.
- Every import carries them. The review's summary gains an additive
`identifiers` block (`books`, `asin`, `isbn`, `by_title`,
`already_matched`), shown as a review row; the stored payload gains
`identified` (omitempty, payload version unchanged).
- An import that brings identifiers but no listening for the person can
now be applied ("No listening to import"), **unless** it would undo an
import already applied for them: then Apply is off with a warning, and
the server refuses with 409 `import_not_ready` and changes nothing.
"Listening" matches the console's rule exactly (sessions, days,
bookmarks, progress that is new or moves position, finished or finish
date).
- The console's note on what matching sends now names an ABS import.
- The LibriVox placement clears a deleted book's hint along with its
other leftovers.

## Tests

Catalog apply/undo/re-import/move/library delete/rebuild of hints, the
refusal (and that the applied import's rows are intact), the listening
rule table, pairing tiers and skips, plan summary counts, QueryFor
order, an ABS-hinted identifier hit confident vs disagreeing, migration
0074, the API's identifier-only apply and refusal, hints absent from
`/item` and `/admin/books`, admin-only import routes. Console:
identifiers row, identifier-only notice, disabled Apply + warning.

Gate: `scripts/build-admin.sh`, `go build/vet/test -race ./...`,
`golangci-lint run` (0 issues), native-tag vet/test -race/lint.

Reviews: one ★ review (two passes), one fix round.

## Verified live

Scratch server from this branch against a real Audiobookshelf 2.37.2 in
Docker and live meta.audiosilo.app, four tagless books with no
metadata.json:

1. Import with listening: review shows "ASIN or ISBN for matching: 4
books"; after Apply, 4 `book_hints` rows, `books.asin` empty, no hint in
`/item` or `/admin/books`.
2. Match automatically: 4 confident, applied as `book_matches` how=auto
on the hinted recordings (two only cleared the auto bar through the
identifier hit).
3. An ASIN meta doesn't know: no identifier reason, matched by
title/author/length to the right recording, never the fake ASIN.
4. Undo removes the hints; a new import writes them again; rescan keeps
them; a moved folder's hint follows its new path.
5. Identifier-only: a user with nothing applied gets the notice and
Apply works; a user with an applied import sees Apply off and the
warning, a direct POST gets 409 `import_not_ready`, their progress
byte-identical and their import still applied.

## Notes

- The de/es/fr/it/pt strings for the new console text are machine
translated: a native read is welcome.
- Follow-ups are filed separately (hint lifetime after a book is
deleted, a few pairing edge cases, undo dialog and history silent on
hints, index use for undo's deletes).
- Docs (admin/import.md and friends) land in one audiosilo-docs PR after
the second #366 PR.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant