silver-music-notifier 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,6 +4,8 @@ Track a list of artists and get notified of their new music releases from
4
4
  [MusicBrainz](https://musicbrainz.org). Drive it from the terminal, or launch a
5
5
  local web UI (built with [silver-ui](https://silver-ui.com)).
6
6
 
7
+ ![silver-music-notifier web UI](screenshot.png)
8
+
7
9
  ## Install
8
10
 
9
11
  ```bash
package/dist/cli/index.js CHANGED
@@ -15,7 +15,7 @@ import { MusicBrainzApi } from "musicbrainz-api";
15
15
  // package.json with { type: 'json' }
16
16
  var package_default = {
17
17
  name: "silver-music-notifier",
18
- version: "1.1.0",
18
+ version: "1.1.1",
19
19
  description: "Track artists and get notified of their new music releases from MusicBrainz, via CLI or a local web UI.",
20
20
  license: "MIT",
21
21
  author: "Andrey Goder <andy.goder@gmail.com>",
@@ -41,7 +41,8 @@ var package_default = {
41
41
  "silver-music-notifier": "dist/cli/index.js"
42
42
  },
43
43
  files: [
44
- "dist"
44
+ "dist",
45
+ "screenshot.png"
45
46
  ],
46
47
  publishConfig: {
47
48
  access: "public"
@@ -459,6 +460,7 @@ var Release = class _Release {
459
460
  }
460
461
  static list(opts = {}) {
461
462
  const lastRefresh = Settings.getLastRefreshAt();
463
+ const settings = Settings.load();
462
464
  const rows = AppDb.getDefault().prepare(
463
465
  `SELECT rg.mbid, rg.artist_mbid, a.name AS artist_name, rg.title,
464
466
  rg.primary_type, rg.secondary_types, rg.first_release_date,
@@ -481,7 +483,13 @@ var Release = class _Release {
481
483
  lastRefresh
482
484
  })
483
485
  );
484
- const filtered = opts.onlyNew ? items.filter((i) => i.isNew) : items;
486
+ const matching = items.filter(
487
+ (item) => settings.includeRelease({
488
+ primaryType: item.primaryType,
489
+ secondaryTypes: item.secondaryTypes ? item.secondaryTypes.split(", ") : []
490
+ })
491
+ );
492
+ const filtered = opts.onlyNew ? matching.filter((i) => i.isNew) : matching;
485
493
  return opts.limit ? filtered.slice(0, opts.limit) : filtered;
486
494
  }
487
495
  static dismiss(mbid) {
@@ -1238,7 +1246,7 @@ async function ensureMbContact() {
1238
1246
  // package.json
1239
1247
  var package_default2 = {
1240
1248
  name: "silver-music-notifier",
1241
- version: "1.1.0",
1249
+ version: "1.1.1",
1242
1250
  description: "Track artists and get notified of their new music releases from MusicBrainz, via CLI or a local web UI.",
1243
1251
  license: "MIT",
1244
1252
  author: "Andrey Goder <andy.goder@gmail.com>",
@@ -1264,7 +1272,8 @@ var package_default2 = {
1264
1272
  "silver-music-notifier": "dist/cli/index.js"
1265
1273
  },
1266
1274
  files: [
1267
- "dist"
1275
+ "dist",
1276
+ "screenshot.png"
1268
1277
  ],
1269
1278
  publishConfig: {
1270
1279
  access: "public"
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/index.ts","../../src/server/index.ts","../../src/lib/musicbrainz.ts","../../package.json","../../src/lib/AppDb.ts","../../src/lib/paths.ts","../../src/lib/releaseTypes.ts","../../src/lib/Settings.ts","../../src/lib/notify.ts","../../src/lib/Release.ts","../../src/lib/formatReleaseDate.ts","../../src/lib/Artist.ts","../../src/lib/refresh.ts","../../src/cli/commands/web.ts","../../src/cli/commands/list.ts","../../src/cli/commands/add.ts","../../src/cli/commands/remove.ts","../../src/cli/commands/refresh.ts","../../src/cli/commands/releases.ts","../../src/cli/commands/dismiss.ts","../../src/cli/commands/config.ts","../../src/cli/commands/clearData.ts","../../src/cli/ensureContact.ts","../../package.json"],"sourcesContent":["import {Command} from 'commander';\nimport {registerWeb} from './commands/web.js';\nimport {registerList} from './commands/list.js';\nimport {registerAdd} from './commands/add.js';\nimport {registerRemove} from './commands/remove.js';\nimport {registerRefresh} from './commands/refresh.js';\nimport {registerReleases} from './commands/releases.js';\nimport {registerDismiss} from './commands/dismiss.js';\nimport {registerConfig} from './commands/config.js';\nimport {registerClearData} from './commands/clearData.js';\nimport {ensureMbContact} from './ensureContact.js';\nimport packageJson from '../../package.json';\n\nconst program = new Command();\n\n// Commands that don't hit the MusicBrainz API and must work before a contact is\n// configured (e.g. `config` to set it, `clear-data` to wipe local state).\nconst CONTACT_EXEMPT_COMMANDS = new Set(['config', 'clear-data', 'dismiss']);\n\n// A configured MusicBrainz contact is required. Ensure it once at the root\n// (prompting interactively on first use) rather than in each command.\nprogram.hook('preAction', async (_thisCommand, actionCommand) => {\n for (let cmd: Command | null = actionCommand; cmd; cmd = cmd.parent) {\n if (CONTACT_EXEMPT_COMMANDS.has(cmd.name())) {\n return;\n }\n }\n await ensureMbContact();\n});\n\nprogram\n .name('silver-music-notifier')\n .description(\n 'Track artists and get notified of their new music releases from MusicBrainz.',\n )\n .version(packageJson.version);\n\nregisterWeb(program);\nregisterList(program);\nregisterAdd(program);\nregisterRemove(program);\nregisterRefresh(program);\nregisterReleases(program);\nregisterDismiss(program);\nregisterConfig(program);\nregisterClearData(program);\n\nprogram.parseAsync(process.argv).catch(err => {\n console.error(err instanceof Error ? err.message : err);\n process.exit(1);\n});\n","import express, {type Request, type Response} from 'express';\nimport {fileURLToPath} from 'node:url';\nimport {existsSync} from 'node:fs';\nimport {dirname, join} from 'node:path';\nimport {searchArtist} from '../lib/musicbrainz.js';\nimport {refresh, refreshArtist} from '../lib/refresh.js';\nimport {sendTestEmail} from '../lib/notify.js';\nimport {Artist} from '../lib/Artist.js';\nimport {Release} from '../lib/Release.js';\nimport {Settings, type SettingsPatch} from '../lib/Settings.js';\n\n// Locate the built web assets. When bundled by tsup the file lives at\n// dist/cli/index.js, so dist/web is two levels up; during `tsx` dev runs the\n// file is src/server/index.ts and dist/web may not exist yet (the dev server is\n// served by Vite instead). We key on index.html so a stale/empty dist/web isn't\n// mistaken for a real build.\nfunction webDir(): string | null {\n const here = dirname(fileURLToPath(import.meta.url));\n const candidates = [\n join(here, '..', 'web'), // dist/cli/index.js -> dist/web\n join(here, '..', '..', 'dist', 'web'), // src/server/index.ts -> dist/web\n ];\n return candidates.find(c => existsSync(join(c, 'index.html'))) ?? null;\n}\n\nfunction asyncRoute(\n fn: (req: Request, res: Response) => Promise<void>,\n): (req: Request, res: Response) => void {\n return (req, res) => {\n fn(req, res).catch(err => {\n console.error(err);\n res\n .status(500)\n .json({error: err instanceof Error ? err.message : String(err)});\n });\n };\n}\n\nexport function createApp() {\n const app = express();\n app.use(express.json());\n\n const api = express.Router();\n\n api.get('/artists', (_req, res) => {\n res.json(Artist.list());\n });\n\n api.get(\n '/artists/search',\n asyncRoute(async (req, res) => {\n const q = String(req.query.q ?? '').trim();\n if (!q) {\n res.json([]);\n return;\n }\n res.json(await searchArtist(q));\n }),\n );\n\n api.post(\n '/artists',\n asyncRoute(async (req, res) => {\n const {mbid, name, sortName, disambiguation, type, country} =\n req.body ?? {};\n if (!mbid || !name) {\n res.status(400).json({error: 'mbid and name are required'});\n return;\n }\n const added = Artist.add({\n mbid,\n name,\n sortName,\n disambiguation,\n type,\n country,\n });\n const artist = added ? Artist.get(mbid) : undefined;\n if (artist) {\n await refreshArtist(artist, {notify: false});\n }\n res.json({added});\n }),\n );\n\n api.delete('/artists/:mbid', (req, res) => {\n const artist = Artist.get(req.params.mbid);\n if (!artist) {\n res.status(404).json({error: 'artist not tracked'});\n return;\n }\n artist.remove();\n res.json({removed: artist});\n });\n\n api.get('/releases', (req, res) => {\n const onlyNew = req.query.new === '1' || req.query.new === 'true';\n const limit = req.query.limit ? Number(req.query.limit) : undefined;\n res.json(Release.list({onlyNew, limit}));\n });\n\n api.post('/releases/:mbid/dismiss', (req, res) => {\n const dismissed = Release.dismiss(req.params.mbid);\n if (!dismissed) {\n res.status(404).json({error: 'release not found'});\n return;\n }\n res.json({dismissed});\n });\n\n api.post(\n '/refresh',\n asyncRoute(async (_req, res) => {\n const summary = await refresh();\n res.json(summary);\n }),\n );\n\n api.get('/settings', (_req, res) => {\n res.json(Settings.load());\n });\n\n api.put('/settings', (req, res) => {\n const patch = req.body as SettingsPatch;\n res.json(Settings.save(patch));\n });\n\n api.post(\n '/settings/test-email',\n asyncRoute(async (req, res) => {\n // Allow testing with the posted settings (saved first), or current ones.\n const patch = req.body as SettingsPatch | undefined;\n const settings =\n patch && Object.keys(patch).length\n ? Settings.save(patch)\n : Settings.load();\n await sendTestEmail(settings);\n res.json({ok: true});\n }),\n );\n\n app.use('/api', api);\n\n // Serve the built SPA (production). In dev, Vite serves the frontend and\n // proxies /api here, so a missing web dir is fine.\n const dir = webDir();\n if (dir) {\n app.use(express.static(dir));\n app.get('*', (_req, res) => {\n res.sendFile(join(dir, 'index.html'));\n });\n } else {\n app.get('*', (_req, res) => {\n res\n .status(503)\n .type('text')\n .send('Web assets are not built. Run `npm run build` and try again.');\n });\n }\n\n return app;\n}\n\n// Start the web server, resolving with the port it bound to. Rejects on a\n// listen error (e.g. EADDRINUSE) so callers can retry on a different port.\nexport function startServer(port: number): Promise<number> {\n return new Promise((resolve, reject) => {\n const server = createApp().listen(port);\n server.once('listening', () => {\n server.removeListener('error', reject);\n resolve(port);\n });\n server.once('error', reject);\n });\n}\n","import {MusicBrainzApi} from 'musicbrainz-api';\nimport packageJson from '../../package.json' with {type: 'json'};\nimport {Settings} from './Settings.js';\nimport {\n RELEASE_GROUP_PRIMARY_TYPES,\n RELEASE_GROUP_SECONDARY_TYPES,\n type ReleaseGroupPrimaryType,\n type ReleaseGroupSecondaryType,\n} from './releaseTypes.js';\n\nexport {\n RELEASE_GROUP_PRIMARY_TYPES,\n RELEASE_GROUP_SECONDARY_TYPES,\n type ReleaseGroupPrimaryType,\n type ReleaseGroupSecondaryType,\n} from './releaseTypes.js';\n\n// App version is informational for the MusicBrainz User-Agent.\nconst APP_VERSION = packageJson.version;\n\nlet client: MusicBrainzApi | null = null;\n\nfunction api(): MusicBrainzApi {\n // Recreate if the contact changed; cheap enough and keeps the User-Agent honest.\n const contact = Settings.musicBrainzContact();\n if (\n !client ||\n (client as unknown as {_contact?: string})._contact !== contact\n ) {\n client = new MusicBrainzApi({\n appName: 'silver-music-notifier',\n appVersion: APP_VERSION,\n appContactInfo: contact,\n });\n (client as unknown as {_contact?: string})._contact = contact;\n }\n return client;\n}\n\nexport interface ArtistSearchResult {\n mbid: string;\n name: string;\n sortName: string;\n disambiguation: string;\n country?: string;\n type?: string;\n}\n\nexport async function searchArtist(\n query: string,\n): Promise<ArtistSearchResult[]> {\n const result = await api().search('artist', {query, limit: 10});\n return (result.artists ?? []).map(a => ({\n mbid: a.id,\n name: a.name,\n sortName: a['sort-name'],\n disambiguation: a.disambiguation,\n country: a.country,\n type: a.type,\n }));\n}\n\nexport interface ReleaseGroup {\n mbid: string;\n title: string;\n primaryType: ReleaseGroupPrimaryType | null;\n secondaryTypes: ReleaseGroupSecondaryType[];\n firstReleaseDate: string | null;\n}\n\nfunction releaseGroupPrimaryType(\n value: unknown,\n): ReleaseGroupPrimaryType | null {\n return RELEASE_GROUP_PRIMARY_TYPES.includes(value as ReleaseGroupPrimaryType)\n ? (value as ReleaseGroupPrimaryType)\n : null;\n}\n\nfunction releaseGroupSecondaryTypes(\n values: unknown,\n): ReleaseGroupSecondaryType[] {\n if (!Array.isArray(values)) {\n return [];\n }\n return values.filter((value): value is ReleaseGroupSecondaryType =>\n RELEASE_GROUP_SECONDARY_TYPES.includes(value as ReleaseGroupSecondaryType),\n );\n}\n\n// Page through every release-group credited to an artist. The musicbrainz-api\n// client handles rate limiting internally.\nexport async function fetchReleaseGroups(\n artistMbid: string,\n): Promise<ReleaseGroup[]> {\n const out: ReleaseGroup[] = [];\n const limit = 100;\n let offset = 0;\n while (true) {\n const res = await api().browse('release-group', {\n artist: artistMbid,\n limit,\n offset,\n });\n const groups = res['release-groups'] ?? [];\n for (const g of groups) {\n out.push({\n mbid: g.id,\n title: g.title,\n primaryType: releaseGroupPrimaryType(g['primary-type']),\n secondaryTypes: releaseGroupSecondaryTypes(g['secondary-types']),\n firstReleaseDate: g['first-release-date'] || null,\n });\n }\n const total = res['release-group-count'] ?? out.length;\n offset += groups.length;\n if (groups.length === 0 || offset >= total) {\n break;\n }\n }\n return out;\n}\n","{\n \"name\": \"silver-music-notifier\",\n \"version\": \"1.1.0\",\n \"description\": \"Track artists and get notified of their new music releases from MusicBrainz, via CLI or a local web UI.\",\n \"license\": \"MIT\",\n \"author\": \"Andrey Goder <andy.goder@gmail.com>\",\n \"homepage\": \"https://github.com/czarandy/silver-music-notifier#readme\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/czarandy/silver-music-notifier.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/czarandy/silver-music-notifier/issues\"\n },\n \"keywords\": [\n \"musicbrainz\",\n \"music\",\n \"new-releases\",\n \"release-radar\",\n \"notifier\",\n \"cli\",\n \"sqlite\"\n ],\n \"type\": \"module\",\n \"bin\": {\n \"silver-music-notifier\": \"dist/cli/index.js\"\n },\n \"files\": [\n \"dist\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"engines\": {\n \"node\": \">=22.12.0\"\n },\n \"scripts\": {\n \"dev\": \"concurrently -n server,web -c blue,magenta \\\"tsx watch src/cli/index.ts web --no-open\\\" \\\"vite\\\"\",\n \"build\": \"npm run build:bundle && npm run build:web\",\n \"build:bundle\": \"tsup\",\n \"build:web\": \"vite build\",\n \"prepare\": \"husky && npm run build\",\n \"refresh\": \"tsx src/cli/index.ts refresh\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"lint\": \"eslint .\",\n \"lint:fix\": \"eslint --fix .\",\n \"format\": \"prettier --write .\",\n \"format:check\": \"prettier --check .\",\n \"check:exports\": \"publint\",\n \"smoke:package\": \"npm run build && node scripts/package-smoke-test.mjs\",\n \"release\": \"bash scripts/release.sh\"\n },\n \"lint-staged\": {\n \"*.{ts,tsx}\": [\n \"eslint --fix --no-warn-ignored\",\n \"prettier --write\"\n ],\n \"*.{js,jsx,json,css,md,html}\": \"prettier --write\"\n },\n \"dependencies\": {\n \"@inquirer/prompts\": \"^7.2.0\",\n \"@tanstack/react-query\": \"^5.101.0\",\n \"better-sqlite3\": \"^11.7.0\",\n \"commander\": \"^13.0.0\",\n \"env-paths\": \"^3.0.0\",\n \"express\": \"^4.21.2\",\n \"musicbrainz-api\": \"^1.2.1\",\n \"nodemailer\": \"^9.0.0\",\n \"open\": \"^10.1.0\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.4\",\n \"@types/better-sqlite3\": \"^7.6.12\",\n \"@types/express\": \"^4.17.21\",\n \"@types/node\": \"^22.10.0\",\n \"@types/nodemailer\": \"^6.4.17\",\n \"@types/react\": \"^19.0.0\",\n \"@types/react-dom\": \"^19.0.0\",\n \"@vitejs/plugin-react\": \"^6.0.2\",\n \"concurrently\": \"^9.1.0\",\n \"eslint\": \"^9.39.4\",\n \"globals\": \"^15.15.0\",\n \"husky\": \"^9.1.7\",\n \"lint-staged\": \"^15.5.2\",\n \"prettier\": \"^3.8.4\",\n \"publint\": \"^0.3.21\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"silver-ui\": \"^0.7.1\",\n \"tsup\": \"^8.3.5\",\n \"tsx\": \"^4.19.2\",\n \"typescript\": \"^5.7.2\",\n \"typescript-eslint\": \"^8.61.0\",\n \"vite\": \"^8.0.16\",\n \"vitest\": \"^4.1.8\"\n },\n \"overrides\": {\n \"esbuild\": \"0.28.1\",\n \"shell-quote\": \"1.8.4\"\n }\n}\n","import Database from 'better-sqlite3';\nimport {dbPath} from './paths.js';\n\nexport class AppDb {\n private static defaultDb: AppDb | null = null;\n\n readonly connection: Database.Database;\n\n constructor(path = dbPath()) {\n this.connection = new Database(path);\n this.initialize();\n }\n\n static getDefault(): Database.Database {\n this.defaultDb ??= new AppDb();\n return this.defaultDb.connection;\n }\n\n static closeDefault(): void {\n this.defaultDb?.close();\n this.defaultDb = null;\n }\n\n close(): void {\n this.connection.close();\n }\n\n private initialize(): void {\n this.connection.pragma('journal_mode = WAL');\n this.connection.pragma('foreign_keys = ON');\n this.connection.exec(`\n CREATE TABLE IF NOT EXISTS artists (\n mbid TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n sort_name TEXT,\n disambiguation TEXT,\n type TEXT,\n country TEXT,\n added_at TEXT NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS release_groups (\n mbid TEXT PRIMARY KEY,\n artist_mbid TEXT NOT NULL REFERENCES artists(mbid) ON DELETE CASCADE,\n title TEXT NOT NULL,\n primary_type TEXT,\n secondary_types TEXT,\n first_release_date TEXT,\n first_seen_at TEXT NOT NULL,\n last_seen_at TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_rg_artist ON release_groups(artist_mbid);\n\n CREATE TABLE IF NOT EXISTS settings (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n );\n `);\n this.ensureColumn('artists', 'type', 'TEXT');\n this.ensureColumn('artists', 'country', 'TEXT');\n this.ensureColumn('release_groups', 'dismissed_at', 'TEXT');\n }\n\n private ensureColumn(\n table: string,\n column: string,\n definition: string,\n ): void {\n const columns = this.connection\n .prepare(`PRAGMA table_info(${table})`)\n .all() as Array<{name: string}>;\n if (!columns.some(c => c.name === column)) {\n this.connection.exec(\n `ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`,\n );\n }\n }\n}\n","import envPaths from 'env-paths';\nimport {mkdirSync} from 'node:fs';\nimport {join} from 'node:path';\n\n// Resolve the per-user data directory. Defaults to the OS-appropriate data dir\n// (e.g. ~/.local/share/silver-music-notifier on Linux), overridable via\n// SILVER_MUSIC_NOTIFIER_DATA_DIR.\nexport function dataDir(): string {\n const dir =\n process.env.SILVER_MUSIC_NOTIFIER_DATA_DIR ??\n envPaths('silver-music-notifier', {suffix: ''}).data;\n mkdirSync(dir, {recursive: true});\n return dir;\n}\n\nexport function dbPath(): string {\n return join(dataDir(), 'data.db');\n}\n","// MusicBrainz release-group type vocabularies. Kept in a dependency-free module\n// so both the MusicBrainz client and Settings can use them without a circular\n// import.\n\nexport const RELEASE_GROUP_PRIMARY_TYPES = [\n 'Album',\n 'Single',\n 'EP',\n 'Broadcast',\n 'Other',\n] as const;\n\nexport type ReleaseGroupPrimaryType =\n (typeof RELEASE_GROUP_PRIMARY_TYPES)[number];\n\nexport const RELEASE_GROUP_SECONDARY_TYPES = [\n 'Compilation',\n 'Soundtrack',\n 'Spokenword',\n 'Interview',\n 'Audiobook',\n 'Audio drama',\n 'Live',\n 'Remix',\n 'DJ-mix',\n 'Mixtape/Street',\n 'Demo',\n 'Field recording',\n] as const;\n\nexport type ReleaseGroupSecondaryType =\n (typeof RELEASE_GROUP_SECONDARY_TYPES)[number];\n\nexport function isPrimaryType(\n value: unknown,\n): value is ReleaseGroupPrimaryType {\n return RELEASE_GROUP_PRIMARY_TYPES.includes(value as ReleaseGroupPrimaryType);\n}\n\nexport function isSecondaryType(\n value: unknown,\n): value is ReleaseGroupSecondaryType {\n return RELEASE_GROUP_SECONDARY_TYPES.includes(\n value as ReleaseGroupSecondaryType,\n );\n}\n","import {AppDb} from './AppDb.js';\nimport {\n isPrimaryType,\n isSecondaryType,\n type ReleaseGroupPrimaryType,\n type ReleaseGroupSecondaryType,\n} from './releaseTypes.js';\n\nexport type SmtpProvider = 'gmail' | 'custom';\n\nexport interface SmtpSettings {\n // Provider preset. 'gmail' fixes host/port/secure so the user only supplies\n // their address and an app password; 'custom' exposes the raw SMTP fields.\n provider: SmtpProvider;\n host: string;\n port: number;\n secure: boolean;\n user: string;\n pass: string;\n from: string;\n to: string;\n}\n\n// Fixed SMTP connection settings for Gmail. When the Gmail provider is\n// selected these override whatever host/port/secure are stored.\nconst GMAIL_SMTP = {host: 'smtp.gmail.com', port: 465, secure: true} as const;\n\nexport interface NotifySettings {\n inPage: boolean;\n email: boolean;\n}\n\nexport interface MusicBrainzSettings {\n contact: string;\n}\n\nexport interface ReleaseFilterSettings {\n // Release-group primary types to keep when refreshing. Types not listed are\n // filtered out (not stored).\n primaryTypes: ReleaseGroupPrimaryType[];\n // Release-group secondary types to exclude when refreshing. A release-group\n // carrying any of these secondary types is filtered out. Empty by default.\n excludeSecondaryTypes: ReleaseGroupSecondaryType[];\n}\n\nexport interface SettingsInput {\n notify: NotifySettings;\n smtp: SmtpSettings;\n musicbrainz: MusicBrainzSettings;\n releaseFilter: ReleaseFilterSettings;\n}\n\nexport type SettingsPatch = Partial<{\n notify: Partial<NotifySettings>;\n smtp: Partial<SmtpSettings>;\n musicbrainz: Partial<MusicBrainzSettings>;\n releaseFilter: Partial<ReleaseFilterSettings>;\n}>;\n\nconst DEFAULT_SETTINGS: SettingsInput = {\n notify: {\n inPage: true,\n email: false,\n },\n smtp: {\n provider: 'gmail',\n host: '',\n port: 587,\n secure: false,\n user: '',\n pass: '',\n from: '',\n to: '',\n },\n musicbrainz: {\n contact: '',\n },\n releaseFilter: {\n // Default to full studio albums only.\n primaryTypes: ['Album'],\n // Drop the common \"album\" variants that aren't new studio releases.\n excludeSecondaryTypes: ['Remix', 'Live', 'Compilation', 'Mixtape/Street'],\n },\n};\n\nconst CONFIG_KEY = 'config';\nconst LAST_REFRESH_KEY = 'last_refresh_at';\n\nexport class Settings {\n readonly notify: NotifySettings;\n readonly smtp: SmtpSettings;\n readonly musicbrainz: MusicBrainzSettings;\n readonly releaseFilter: ReleaseFilterSettings;\n\n constructor(input: SettingsInput = DEFAULT_SETTINGS) {\n this.notify = {...input.notify};\n this.smtp = {...input.smtp};\n this.musicbrainz = {...input.musicbrainz};\n this.releaseFilter = {\n // Drop any unknown values that may have been persisted by an older or\n // hand-edited config.\n primaryTypes: input.releaseFilter.primaryTypes.filter(isPrimaryType),\n excludeSecondaryTypes:\n input.releaseFilter.excludeSecondaryTypes.filter(isSecondaryType),\n };\n }\n\n static defaults(): Settings {\n return new Settings(DEFAULT_SETTINGS);\n }\n\n static load(): Settings {\n const raw = Settings.readRaw(CONFIG_KEY);\n if (!raw) {\n return Settings.defaults();\n }\n\n let parsed: SettingsPatch;\n try {\n parsed = JSON.parse(raw) as SettingsPatch;\n } catch {\n return Settings.defaults();\n }\n\n return Settings.defaults().merge(parsed);\n }\n\n static save(patch: SettingsPatch): Settings {\n const next = Settings.load().merge(patch);\n Settings.writeRaw(CONFIG_KEY, JSON.stringify(next));\n return next;\n }\n\n static getLastRefreshAt(): string | null {\n return Settings.readRaw(LAST_REFRESH_KEY) ?? null;\n }\n\n static setLastRefreshAt(iso: string): void {\n Settings.writeRaw(LAST_REFRESH_KEY, iso);\n }\n\n static clearLastRefreshAt(): void {\n AppDb.getDefault()\n .prepare('DELETE FROM settings WHERE key = ?')\n .run(LAST_REFRESH_KEY);\n }\n\n static musicBrainzContact(): string {\n return Settings.load().musicBrainzContact();\n }\n\n merge(patch: SettingsPatch): Settings {\n return new Settings({\n notify: {...this.notify, ...patch.notify},\n smtp: {...this.smtp, ...patch.smtp},\n musicbrainz: {...this.musicbrainz, ...patch.musicbrainz},\n releaseFilter: {...this.releaseFilter, ...patch.releaseFilter},\n });\n }\n\n // Whether a release-group with this primary type should be kept on refresh.\n supportPrimaryType(type: ReleaseGroupPrimaryType | null): boolean {\n return type !== null && this.releaseFilter.primaryTypes.includes(type);\n }\n\n // Whether a release-group with these secondary types should be kept on\n // refresh. It is filtered out if it carries any excluded secondary type.\n supportSecondaryTypes(types: ReleaseGroupSecondaryType[]): boolean {\n return !types.some(t =>\n this.releaseFilter.excludeSecondaryTypes.includes(t),\n );\n }\n\n // Whether a release-group should be kept on refresh, applying both the\n // primary-type and secondary-type filters.\n includeRelease(group: {\n primaryType: ReleaseGroupPrimaryType | null;\n secondaryTypes: ReleaseGroupSecondaryType[];\n }): boolean {\n return (\n this.supportPrimaryType(group.primaryType) &&\n this.supportSecondaryTypes(group.secondaryTypes)\n );\n }\n\n // SMTP settings with the provider preset applied. For Gmail the host, port,\n // and secure flag are fixed, so connection code should use this rather than\n // reading this.smtp directly.\n resolvedSmtp(): SmtpSettings {\n if (this.smtp.provider === 'gmail') {\n return {...this.smtp, ...GMAIL_SMTP};\n }\n return this.smtp;\n }\n\n smtpIsConfigured(): boolean {\n const smtp = this.resolvedSmtp();\n return Boolean(smtp.host && smtp.user && smtp.to);\n }\n\n // The MusicBrainz contact string used in the API User-Agent. MusicBrainz\n // requires a meaningful contact (an email or URL) and throttles/blocks\n // requests without one, so this is mandatory.\n musicBrainzContact(): string {\n const contact = this.musicbrainz.contact.trim();\n if (!contact) {\n throw new Error(\n 'MusicBrainz contact is not set. MusicBrainz requires a contact (email or ' +\n 'URL) to query its API. Set it in the web UI Settings, or run:\\n' +\n ' silver-music-notifier config set musicbrainz.contact you@example.com',\n );\n }\n return contact;\n }\n\n private static readRaw(key: string): string | undefined {\n const row = AppDb.getDefault()\n .prepare('SELECT value FROM settings WHERE key = ?')\n .get(key) as {value: string} | undefined;\n return row?.value;\n }\n\n private static writeRaw(key: string, value: string): void {\n AppDb.getDefault()\n .prepare(\n 'INSERT INTO settings (key, value) VALUES (?, ?) ' +\n 'ON CONFLICT(key) DO UPDATE SET value = excluded.value',\n )\n .run(key, value);\n }\n}\n","import nodemailer from 'nodemailer';\nimport {Settings} from './Settings.js';\nimport {Release} from './Release.js';\nimport {formatReleaseDate} from './formatReleaseDate.js';\nimport type {NewRelease} from './refresh.js';\nimport type {\n ReleaseGroupPrimaryType,\n ReleaseGroupSecondaryType,\n} from './releaseTypes.js';\n\nfunction subjectLine(r: NewRelease): string {\n return `New Release: ${r.title} by ${r.artistName}`;\n}\n\nfunction emailHtml(r: NewRelease): string {\n const type = [r.primaryType, ...r.secondaryTypes].filter(Boolean).join(' / ');\n const title = `<strong>${escapeHtml(r.title)}</strong>`;\n const artist = escapeHtml(r.artistName);\n const typeText = type ? ` (${escapeHtml(type)})` : '';\n const dateText = r.firstReleaseDate\n ? ` was released on ${escapeHtml(formatReleaseDate(r.firstReleaseDate))}`\n : ' is out';\n return `<div style=\"font-family:system-ui,sans-serif\">\n <p>${title} by ${artist}${typeText}${dateText}.</p>\n </div>`;\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(\n /[&<>\"']/g,\n c =>\n ({'&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;'})[\n c\n ]!,\n );\n}\n\nfunction transport(s: Settings) {\n const smtp = s.resolvedSmtp();\n return nodemailer.createTransport({\n host: smtp.host,\n port: smtp.port,\n secure: smtp.secure,\n auth: smtp.user ? {user: smtp.user, pass: smtp.pass} : undefined,\n });\n}\n\n// Send one notification email for a single release. `subjectPrefix` lets the\n// test email reuse the exact same layout while marking the subject as a test.\nasync function sendReleaseEmail(\n release: NewRelease,\n s: Settings,\n subjectPrefix = '',\n): Promise<void> {\n await transport(s).sendMail({\n from: s.smtp.from || s.smtp.user,\n to: s.smtp.to,\n subject: subjectPrefix + subjectLine(release),\n html: emailHtml(release),\n });\n}\n\n// Dispatch notifications for newly-discovered releases according to user\n// settings, sending one email per release. Email fails soft: a broken SMTP\n// config (or a single failed send) must not prevent the refresh itself from\n// succeeding or block the remaining emails.\nexport async function notifyNewReleases(\n newReleases: NewRelease[],\n): Promise<void> {\n if (newReleases.length === 0) {\n return;\n }\n const s = Settings.load();\n\n if (s.notify.email) {\n if (!s.smtpIsConfigured()) {\n console.warn('Email enabled but SMTP not configured — skipping email.');\n } else {\n for (const release of newReleases) {\n try {\n await sendReleaseEmail(release, s);\n } catch (err) {\n console.error(\n `Email notification failed for \"${release.title}\":`,\n errMsg(err),\n );\n }\n }\n }\n }\n}\n\n// A representative release to preview in the test email: the newest tracked\n// release, or a synthetic sample when nothing is tracked yet.\nfunction sampleRelease(): NewRelease {\n const [latest] = Release.list({limit: 1});\n if (latest) {\n return {\n mbid: latest.mbid,\n artistMbid: latest.artistMbid,\n artistName: latest.artistName,\n title: latest.title,\n primaryType: latest.primaryType as ReleaseGroupPrimaryType | null,\n secondaryTypes: latest.secondaryTypes\n ? (latest.secondaryTypes.split(', ') as ReleaseGroupSecondaryType[])\n : [],\n firstReleaseDate: latest.firstReleaseDate,\n };\n }\n return {\n mbid: 'sample',\n artistMbid: 'sample',\n artistName: 'Example Artist',\n title: 'Example Album',\n primaryType: 'Album',\n secondaryTypes: [],\n firstReleaseDate: new Date().toISOString().slice(0, 10),\n };\n}\n\n// Send a test email using the current (or provided) SMTP settings. It mirrors a\n// real release notification (using the newest tracked release) so the user can\n// see exactly what they'll get, but prefixes the subject with [TEST]. Throws on\n// failure so callers can surface the error to the user.\nexport async function sendTestEmail(override?: Settings): Promise<void> {\n const s = override ?? Settings.load();\n if (!s.smtpIsConfigured()) {\n throw new Error(\n 'SMTP is not configured (host, user, and recipient required).',\n );\n }\n await sendReleaseEmail(sampleRelease(), s, '[TEST] ');\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n","import {AppDb} from './AppDb.js';\nimport {Settings} from './Settings.js';\n\nexport interface ReleaseListOptions {\n onlyNew?: boolean;\n limit?: number;\n}\n\nexport class Release {\n readonly mbid: string;\n readonly artistMbid: string;\n readonly artistName: string;\n readonly title: string;\n readonly primaryType: string | null;\n readonly secondaryTypes: string | null;\n readonly firstReleaseDate: string | null;\n readonly firstSeenAt: string;\n readonly isNew: boolean;\n\n private constructor(input: {\n mbid: string;\n artistMbid: string;\n artistName: string;\n title: string;\n primaryType: string | null;\n secondaryTypes: string | null;\n firstReleaseDate: string | null;\n firstSeenAt: string;\n dismissedAt: string | null;\n lastRefresh: string | null;\n }) {\n this.mbid = input.mbid;\n this.artistMbid = input.artistMbid;\n this.artistName = input.artistName;\n this.title = input.title;\n this.primaryType = input.primaryType;\n this.secondaryTypes = input.secondaryTypes;\n this.firstReleaseDate = input.firstReleaseDate;\n this.firstSeenAt = input.firstSeenAt;\n this.isNew =\n input.dismissedAt == null &&\n input.lastRefresh != null &&\n input.firstSeenAt >= input.lastRefresh;\n }\n\n static list(opts: ReleaseListOptions = {}): Release[] {\n const lastRefresh = Settings.getLastRefreshAt();\n const rows = AppDb.getDefault()\n .prepare(\n `SELECT rg.mbid, rg.artist_mbid, a.name AS artist_name, rg.title,\n rg.primary_type, rg.secondary_types, rg.first_release_date,\n rg.first_seen_at, rg.dismissed_at\n FROM release_groups rg\n JOIN artists a ON a.mbid = rg.artist_mbid\n ORDER BY (rg.first_release_date IS NULL), rg.first_release_date DESC, rg.title`,\n )\n .all() as Array<{\n mbid: string;\n artist_mbid: string;\n artist_name: string;\n title: string;\n primary_type: string | null;\n secondary_types: string | null;\n first_release_date: string | null;\n first_seen_at: string;\n dismissed_at: string | null;\n }>;\n\n // A release is \"new\" if it was first seen at or after the previous refresh\n // started, meaning it showed up in the most recent refresh.\n const items = rows.map(\n row =>\n new Release({\n mbid: row.mbid,\n artistMbid: row.artist_mbid,\n artistName: row.artist_name,\n title: row.title,\n primaryType: row.primary_type,\n secondaryTypes: row.secondary_types,\n firstReleaseDate: row.first_release_date,\n firstSeenAt: row.first_seen_at,\n dismissedAt: row.dismissed_at,\n lastRefresh,\n }),\n );\n const filtered = opts.onlyNew ? items.filter(i => i.isNew) : items;\n return opts.limit ? filtered.slice(0, opts.limit) : filtered;\n }\n\n static dismiss(mbid: string): boolean {\n const res = AppDb.getDefault()\n .prepare('UPDATE release_groups SET dismissed_at = ? WHERE mbid = ?')\n .run(new Date().toISOString(), mbid);\n return res.changes > 0;\n }\n}\n","const monthNames = [\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n];\n\nexport function formatReleaseDate(value: string | null): string {\n if (!value) {\n return '—';\n }\n const [year, month, day] = value.split('-').map(Number);\n if (!year) {\n return value;\n }\n if (month && !day) {\n return `${monthNames[month - 1]} ${year}`;\n }\n if (!month || !day) {\n return value;\n }\n return `${day} ${monthNames[month - 1]} ${year}`;\n}\n","import {AppDb} from './AppDb.js';\n\ninterface ArtistRow {\n mbid: string;\n name: string;\n sort_name: string | null;\n disambiguation: string | null;\n type: string | null;\n country: string | null;\n added_at: string;\n}\n\nexport interface ArtistAddInput {\n mbid: string;\n name: string;\n sortName?: string | null;\n disambiguation?: string | null;\n type?: string | null;\n country?: string | null;\n}\n\nexport class Artist {\n readonly mbid: string;\n readonly name: string;\n readonly sortName: string | null;\n readonly disambiguation: string | null;\n readonly type: string | null;\n readonly country: string | null;\n readonly addedAt: string;\n\n private constructor(row: ArtistRow) {\n this.mbid = row.mbid;\n this.name = row.name;\n this.sortName = row.sort_name;\n this.disambiguation = row.disambiguation;\n this.type = row.type;\n this.country = row.country;\n this.addedAt = row.added_at;\n }\n\n static list(): Artist[] {\n const rows = AppDb.getDefault()\n .prepare('SELECT * FROM artists ORDER BY name COLLATE NOCASE')\n .all() as ArtistRow[];\n return rows.map(row => new Artist(row));\n }\n\n static get(mbid: string): Artist | undefined {\n const row = AppDb.getDefault()\n .prepare('SELECT * FROM artists WHERE mbid = ?')\n .get(mbid) as ArtistRow | undefined;\n return row ? new Artist(row) : undefined;\n }\n\n static getByMbidOrName(idOrName: string): Artist | undefined {\n const db = AppDb.getDefault();\n const row =\n (db.prepare('SELECT * FROM artists WHERE mbid = ?').get(idOrName) as\n | ArtistRow\n | undefined) ??\n (db\n .prepare('SELECT * FROM artists WHERE name = ? COLLATE NOCASE')\n .get(idOrName) as ArtistRow | undefined);\n return row ? new Artist(row) : undefined;\n }\n\n // Insert (or no-op if already tracked). Returns true if newly added.\n static add(input: ArtistAddInput): boolean {\n const res = AppDb.getDefault()\n .prepare(\n `INSERT INTO artists\n (mbid, name, sort_name, disambiguation, type, country, added_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(mbid) DO NOTHING`,\n )\n .run(\n input.mbid,\n input.name,\n input.sortName ?? null,\n input.disambiguation ?? null,\n input.type ?? null,\n input.country ?? null,\n new Date().toISOString(),\n );\n return res.changes > 0;\n }\n\n // Cascades to release_groups through the database foreign key.\n remove(): void {\n AppDb.getDefault()\n .prepare('DELETE FROM artists WHERE mbid = ?')\n .run(this.mbid);\n }\n\n // Delete every tracked artist (cascading to release_groups). Returns the\n // number of artists and releases removed.\n static clearAll(): {artists: number; releases: number} {\n const db = AppDb.getDefault();\n const {n: releases} = db\n .prepare('SELECT COUNT(*) AS n FROM release_groups')\n .get() as {n: number};\n const result = db.prepare('DELETE FROM artists').run();\n return {artists: result.changes, releases};\n }\n}\n","import {AppDb} from './AppDb.js';\nimport {\n fetchReleaseGroups,\n type ReleaseGroupPrimaryType,\n type ReleaseGroupSecondaryType,\n} from './musicbrainz.js';\nimport {Settings} from './Settings.js';\nimport {notifyNewReleases} from './notify.js';\nimport {Artist} from './Artist.js';\n\nexport interface NewRelease {\n mbid: string;\n artistMbid: string;\n artistName: string;\n title: string;\n primaryType: ReleaseGroupPrimaryType | null;\n secondaryTypes: ReleaseGroupSecondaryType[];\n firstReleaseDate: string | null;\n}\n\nexport interface RefreshSummary {\n scannedArtists: number;\n newCount: number;\n newReleases: NewRelease[];\n errors: {artist: string; message: string}[];\n startedAt: string;\n}\n\nexport interface RefreshOptions {\n notify?: boolean;\n onProgress?: (artist: Artist, index: number, total: number) => void;\n}\n\nasync function refreshArtists(\n artists: Artist[],\n opts: RefreshOptions,\n persistLastRefresh: boolean,\n // When true, releases discovered in this run are recorded as already-seen so\n // they don't show \"New\" badges. Used when first adding an artist: their whole\n // back-catalog is the baseline, not news (mirrors the no-notify behavior).\n markReleasesSeen: boolean,\n): Promise<RefreshSummary> {\n const db = AppDb.getDefault();\n const startedAt = new Date().toISOString();\n const settings = Settings.load();\n\n // Fail fast (once, before hitting the network per-artist) if the required\n // MusicBrainz contact is missing.\n if (artists.length > 0) {\n settings.musicBrainzContact();\n }\n\n const existing = db.prepare('SELECT 1 FROM release_groups WHERE mbid = ?');\n const insert = db.prepare(`\n INSERT INTO release_groups\n (mbid, artist_mbid, title, primary_type, secondary_types,\n first_release_date, first_seen_at, last_seen_at, dismissed_at)\n VALUES (@mbid, @artist_mbid, @title, @primary_type, @secondary_types,\n @first_release_date, @now, @now, @dismissed_at)\n ON CONFLICT(mbid) DO UPDATE SET\n title = excluded.title,\n primary_type = excluded.primary_type,\n secondary_types = excluded.secondary_types,\n first_release_date = excluded.first_release_date,\n last_seen_at = excluded.last_seen_at\n `);\n\n const newReleases: NewRelease[] = [];\n const errors: RefreshSummary['errors'] = [];\n\n for (let i = 0; i < artists.length; i++) {\n const artist = artists[i];\n opts.onProgress?.(artist, i, artists.length);\n try {\n const groups = await fetchReleaseGroups(artist.mbid);\n const now = new Date().toISOString();\n const apply = db.transaction(() => {\n for (const g of groups) {\n // Skip release-groups filtered out by primary type, or carrying an\n // excluded secondary type.\n if (!settings.includeRelease(g)) {\n continue;\n }\n const seen = existing.get(g.mbid);\n insert.run({\n mbid: g.mbid,\n artist_mbid: artist.mbid,\n title: g.title,\n primary_type: g.primaryType,\n secondary_types: g.secondaryTypes.join(', ') || null,\n first_release_date: g.firstReleaseDate,\n now,\n dismissed_at: markReleasesSeen ? now : null,\n });\n if (!seen) {\n newReleases.push({\n mbid: g.mbid,\n artistMbid: artist.mbid,\n artistName: artist.name,\n title: g.title,\n primaryType: g.primaryType,\n secondaryTypes: g.secondaryTypes,\n firstReleaseDate: g.firstReleaseDate,\n });\n }\n }\n });\n apply();\n } catch (err) {\n errors.push({\n artist: artist.name,\n message: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n if (persistLastRefresh) {\n Settings.setLastRefreshAt(startedAt);\n }\n\n const summary: RefreshSummary = {\n scannedArtists: artists.length,\n newCount: newReleases.length,\n newReleases,\n errors,\n startedAt,\n };\n\n if (opts.notify !== false && newReleases.length > 0) {\n await notifyNewReleases(newReleases);\n }\n\n return summary;\n}\n\n// Fetch release-groups for one newly tracked artist without advancing the\n// global refresh marker for already-tracked releases.\nexport async function refreshArtist(\n artist: Artist,\n opts: RefreshOptions = {},\n): Promise<RefreshSummary> {\n return refreshArtists([artist], opts, false, true);\n}\n\n// Fetch release-groups for every tracked artist, upsert them, and collect the\n// ones we'd never seen before. Shared by the CLI `refresh` command and the\n// server's POST /api/refresh route.\nexport async function refresh(\n opts: RefreshOptions = {},\n): Promise<RefreshSummary> {\n return refreshArtists(Artist.list(), opts, true, false);\n}\n","import type {Command} from 'commander';\nimport {startServer} from '../../server/index.js';\n\n// How many consecutive ports to try before giving up.\nconst MAX_PORT_ATTEMPTS = 10;\n\nfunction isAddrInUse(err: unknown): boolean {\n return (\n typeof err === 'object' &&\n err !== null &&\n (err as {code?: string}).code === 'EADDRINUSE'\n );\n}\n\nexport function registerWeb(program: Command): void {\n program\n .command('web')\n .description('Launch the local web UI')\n .option('-p, --port <port>', 'port to listen on', '3001')\n .option('--no-open', 'do not open a browser window')\n .action(async (opts: {port: string; open: boolean}) => {\n const requested = Number(opts.port);\n let bound: number | undefined;\n for (let port = requested; port < requested + MAX_PORT_ATTEMPTS; port++) {\n try {\n bound = await startServer(port);\n break;\n } catch (err) {\n if (!isAddrInUse(err)) {\n throw err;\n }\n console.log(`Port ${port} is in use, trying ${port + 1}…`);\n }\n }\n if (bound === undefined) {\n throw new Error(\n `Could not find a free port in range ` +\n `${requested}–${requested + MAX_PORT_ATTEMPTS - 1}.`,\n );\n }\n\n const url = `http://localhost:${bound}`;\n console.log(`silver-music-notifier web UI running at ${url}`);\n if (opts.open) {\n try {\n const {default: open} = await import('open');\n await open(url);\n } catch {\n console.log('(could not open browser automatically)');\n }\n }\n });\n}\n","import type {Command} from 'commander';\nimport {Artist} from '../../lib/Artist.js';\n\nexport function registerList(program: Command): void {\n program\n .command('list')\n .description('List tracked artists')\n .action(() => {\n const artists = Artist.list();\n if (artists.length === 0) {\n console.log(\n 'No artists tracked yet. Add one with: silver-music-notifier add <name>',\n );\n return;\n }\n for (const a of artists) {\n const extra = a.disambiguation ? ` (${a.disambiguation})` : '';\n console.log(`${a.name}${extra} — ${a.mbid}`);\n }\n console.log(\n `\\n${artists.length} artist${artists.length === 1 ? '' : 's'} tracked.`,\n );\n });\n}\n","import type {Command} from 'commander';\nimport {searchArtist} from '../../lib/musicbrainz.js';\nimport {Artist} from '../../lib/Artist.js';\nimport {refreshArtist} from '../../lib/refresh.js';\n\nasync function refreshIfAdded(added: boolean, mbid: string): Promise<void> {\n if (!added) {\n return;\n }\n const artist = Artist.get(mbid);\n if (artist) {\n await refreshArtist(artist, {notify: false});\n }\n}\n\nexport function registerAdd(program: Command): void {\n program\n .command('add')\n .description('Search MusicBrainz and add an artist to track')\n .argument('<query>', 'artist name to search for')\n .option(\n '--mbid <mbid>',\n 'add this exact MusicBrainz artist MBID, skipping search',\n )\n .option('-y, --yes', 'add the top search result without prompting')\n .action(async (query: string, opts: {mbid?: string; yes?: boolean}) => {\n if (opts.mbid) {\n const added = Artist.add({mbid: opts.mbid, name: query});\n await refreshIfAdded(added, opts.mbid);\n console.log(added ? `Added ${query}.` : `${query} is already tracked.`);\n return;\n }\n\n const results = await searchArtist(query);\n if (results.length === 0) {\n console.log(`No MusicBrainz artists found for \"${query}\".`);\n return;\n }\n\n let chosen = results[0];\n if (!opts.yes && results.length > 1) {\n const {select} = await import('@inquirer/prompts');\n const mbid = await select({\n message: 'Which artist?',\n choices: results.map(r => ({\n name: [\n r.name,\n r.disambiguation ? `(${r.disambiguation})` : '',\n r.type ? `· ${r.type}` : '',\n r.country ? `· ${r.country}` : '',\n ]\n .filter(Boolean)\n .join(' '),\n value: r.mbid,\n })),\n });\n chosen = results.find(r => r.mbid === mbid)!;\n }\n\n const added = Artist.add({\n mbid: chosen.mbid,\n name: chosen.name,\n sortName: chosen.sortName,\n disambiguation: chosen.disambiguation,\n type: chosen.type,\n country: chosen.country,\n });\n await refreshIfAdded(added, chosen.mbid);\n console.log(\n added\n ? `Added ${chosen.name} (${chosen.mbid}).`\n : `${chosen.name} is already tracked.`,\n );\n });\n}\n","import type {Command} from 'commander';\nimport {Artist} from '../../lib/Artist.js';\n\nexport function registerRemove(program: Command): void {\n program\n .command('remove')\n .alias('rm')\n .description('Stop tracking an artist (by MBID or name)')\n .argument('<idOrName>', 'MusicBrainz MBID or exact artist name')\n .action((idOrName: string) => {\n const artist = Artist.getByMbidOrName(idOrName);\n if (!artist) {\n console.log(`No tracked artist matched \"${idOrName}\".`);\n process.exitCode = 1;\n return;\n }\n artist.remove();\n console.log(`Removed ${artist.name} (${artist.mbid}).`);\n });\n}\n","import type {Command} from 'commander';\nimport {refresh} from '../../lib/refresh.js';\n\nexport function registerRefresh(program: Command): void {\n program\n .command('refresh')\n .description(\n 'Fetch releases for all tracked artists and notify on new ones',\n )\n .option('--no-notify', 'skip desktop/email notifications')\n .action(async (opts: {notify: boolean}) => {\n const summary = await refresh({\n notify: opts.notify,\n onProgress: (artist, i, total) => {\n process.stdout.write(\n `\\r[${i + 1}/${total}] ${artist.name}`.padEnd(60),\n );\n },\n });\n process.stdout.write('\\r'.padEnd(60) + '\\r');\n\n console.log(\n `Scanned ${summary.scannedArtists} artist${summary.scannedArtists === 1 ? '' : 's'}; ` +\n `${summary.newCount} new release${summary.newCount === 1 ? '' : 's'}.`,\n );\n for (const r of summary.newReleases) {\n const type = [r.primaryType, ...r.secondaryTypes]\n .filter(Boolean)\n .join(' / ');\n console.log(\n ` + ${r.artistName} — ${r.title}` +\n (type ? ` [${type}]` : '') +\n (r.firstReleaseDate ? ` (${r.firstReleaseDate})` : ''),\n );\n }\n for (const e of summary.errors) {\n console.error(` ! ${e.artist}: ${e.message}`);\n }\n });\n}\n","import type {Command} from 'commander';\nimport {Release} from '../../lib/Release.js';\n\nexport function registerReleases(program: Command): void {\n program\n .command('releases')\n .description('List known releases, newest first')\n .option('--new', 'only show releases discovered in the last refresh')\n .option('-n, --limit <n>', 'limit the number of rows', v => Number(v))\n .action((opts: {new?: boolean; limit?: number}) => {\n const items = Release.list({onlyNew: opts.new, limit: opts.limit});\n if (items.length === 0) {\n console.log(\n opts.new\n ? 'No new releases since the last refresh.'\n : 'No releases yet. Run: silver-music-notifier refresh',\n );\n return;\n }\n for (const r of items) {\n const date = r.firstReleaseDate ?? '—'.padEnd(10);\n const type = [r.primaryType, r.secondaryTypes]\n .filter(Boolean)\n .join(' / ');\n const flag = r.isNew ? 'NEW ' : ' ';\n console.log(\n `${flag}${date.padEnd(11)} ${r.artistName} — ${r.title}${type ? ` [${type}]` : ''}`,\n );\n }\n });\n}\n","import type {Command} from 'commander';\nimport {Release} from '../../lib/Release.js';\n\nexport function registerDismiss(program: Command): void {\n program\n .command('dismiss')\n .description('Dismiss the New badge for a release')\n .argument('<releaseMbid>', 'MusicBrainz release-group MBID')\n .action((releaseMbid: string) => {\n const dismissed = Release.dismiss(releaseMbid);\n if (!dismissed) {\n console.log(`No release matched \"${releaseMbid}\".`);\n process.exitCode = 1;\n return;\n }\n console.log(`Dismissed ${releaseMbid}.`);\n });\n}\n","import type {Command} from 'commander';\nimport {Settings, type SettingsPatch} from '../../lib/Settings.js';\nimport {\n RELEASE_GROUP_PRIMARY_TYPES,\n RELEASE_GROUP_SECONDARY_TYPES,\n} from '../../lib/releaseTypes.js';\n\nconst PRIMARY_TYPES_KEY = 'releaseFilter.primaryTypes';\nconst SECONDARY_TYPES_KEY = 'releaseFilter.excludeSecondaryTypes';\n\n// Parse a comma-separated list (e.g. \"Album,EP\") and validate every entry\n// against the allowed vocabulary.\nfunction parseTypeList(\n value: string,\n valid: readonly string[],\n label: string,\n): string[] {\n const types = value\n .split(',')\n .map(t => t.trim())\n .filter(Boolean);\n const invalid = types.filter(t => !valid.includes(t));\n if (invalid.length > 0) {\n throw new Error(\n `Invalid ${label}(s): ${invalid.join(', ')}. Valid: ${valid.join(', ')}`,\n );\n }\n return types;\n}\n\n// Flatten the settings object into dotted keys for display/editing, masking the\n// SMTP password so it is never printed.\nfunction flatten(s: Settings): Record<string, string> {\n return {\n 'notify.inPage': String(s.notify.inPage),\n 'notify.email': String(s.notify.email),\n 'smtp.host': s.smtp.host,\n 'smtp.port': String(s.smtp.port),\n 'smtp.secure': String(s.smtp.secure),\n 'smtp.user': s.smtp.user,\n 'smtp.pass': s.smtp.pass ? '********' : '',\n 'smtp.from': s.smtp.from,\n 'smtp.to': s.smtp.to,\n 'musicbrainz.contact': s.musicbrainz.contact,\n [PRIMARY_TYPES_KEY]: s.releaseFilter.primaryTypes.join(','),\n [SECONDARY_TYPES_KEY]: s.releaseFilter.excludeSecondaryTypes.join(','),\n };\n}\n\nfunction coerce(\n key: string,\n value: string,\n): boolean | number | string | string[] {\n if (/^(notify\\.|smtp\\.secure$)/.test(key)) {\n return value === 'true' || value === '1';\n }\n if (key === 'smtp.port') {\n return Number(value);\n }\n if (key === PRIMARY_TYPES_KEY) {\n return parseTypeList(value, RELEASE_GROUP_PRIMARY_TYPES, 'primary type');\n }\n if (key === SECONDARY_TYPES_KEY) {\n return parseTypeList(\n value,\n RELEASE_GROUP_SECONDARY_TYPES,\n 'secondary type',\n );\n }\n return value;\n}\n\nfunction patchFor(\n key: string,\n value: boolean | number | string | string[],\n): SettingsPatch {\n const [group, field] = key.split('.');\n return {[group]: {[field]: value}} as unknown as SettingsPatch;\n}\n\nexport function registerConfig(program: Command): void {\n const config = program.command('config').description('View or edit settings');\n\n config\n .command('get', {isDefault: true})\n .description('Print settings (or a single key)')\n .argument('[key]', 'dotted key, e.g. notify.email')\n .action((key?: string) => {\n const flat = flatten(Settings.load());\n if (key) {\n if (!(key in flat)) {\n console.error(`Unknown key: ${key}`);\n process.exitCode = 1;\n return;\n }\n console.log(flat[key]);\n return;\n }\n for (const [k, v] of Object.entries(flat)) {\n console.log(`${k} = ${v}`);\n }\n });\n\n config\n .command('set')\n .description('Set a settings key')\n .argument('<key>', 'dotted key, e.g. smtp.host')\n .argument('<value>', 'new value')\n .action((key: string, value: string) => {\n const valid = new Set(Object.keys(flatten(Settings.load())));\n if (!valid.has(key)) {\n console.error(\n `Unknown key: ${key}\\nValid keys: ${[...valid].join(', ')}`,\n );\n process.exitCode = 1;\n return;\n }\n Settings.save(patchFor(key, coerce(key, value)));\n console.log(`Set ${key}.`);\n });\n}\n","import type {Command} from 'commander';\nimport {Artist} from '../../lib/Artist.js';\nimport {Settings} from '../../lib/Settings.js';\n\nexport function registerClearData(program: Command): void {\n program\n .command('clear-data')\n .description('Delete all tracked artists and stored releases')\n .option('-y, --yes', 'skip the confirmation prompt')\n .action(async (opts: {yes?: boolean}) => {\n if (!opts.yes) {\n if (!process.stdin.isTTY) {\n console.error(\n 'Refusing to clear data without confirmation. Re-run with --yes.',\n );\n process.exitCode = 1;\n return;\n }\n const {confirm} = await import('@inquirer/prompts');\n const confirmed = await confirm({\n message:\n 'Delete ALL tracked artists and stored releases? This cannot be undone.',\n default: false,\n });\n if (!confirmed) {\n console.log('Aborted.');\n return;\n }\n }\n\n const {artists, releases} = Artist.clearAll();\n Settings.clearLastRefreshAt();\n console.log(\n `Cleared ${artists} artist${artists === 1 ? '' : 's'} and ` +\n `${releases} release${releases === 1 ? '' : 's'}. Settings were kept.`,\n );\n });\n}\n","import {Settings} from '../lib/Settings.js';\n\n// MusicBrainz requires a contact (email or URL) in the API User-Agent. The first\n// time a command needs the API and no contact is set, prompt for one and save\n// it. In a non-interactive context (no TTY, e.g. cron/CI) fall back to throwing\n// the standard guidance error instead of hanging on a prompt.\nexport async function ensureMbContact(): Promise<void> {\n if (Settings.load().musicbrainz.contact.trim()) {\n return;\n }\n\n if (!process.stdin.isTTY) {\n Settings.musicBrainzContact(); // throws with setup guidance\n return;\n }\n\n const {input} = await import('@inquirer/prompts');\n const contact = await input({\n message:\n 'MusicBrainz requires a contact (email or URL) to use its API. Enter one:',\n validate: value => {\n const v = value.trim();\n if (!v) {\n return 'A contact is required.';\n }\n if (!v.includes('@') && !v.includes('.')) {\n return 'Enter an email address or a URL.';\n }\n return true;\n },\n });\n\n Settings.save({musicbrainz: {contact: contact.trim()}});\n console.log(\n 'Saved. Change it later with: silver-music-notifier config set musicbrainz.contact <value>\\n',\n );\n}\n","{\n \"name\": \"silver-music-notifier\",\n \"version\": \"1.1.0\",\n \"description\": \"Track artists and get notified of their new music releases from MusicBrainz, via CLI or a local web UI.\",\n \"license\": \"MIT\",\n \"author\": \"Andrey Goder <andy.goder@gmail.com>\",\n \"homepage\": \"https://github.com/czarandy/silver-music-notifier#readme\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/czarandy/silver-music-notifier.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/czarandy/silver-music-notifier/issues\"\n },\n \"keywords\": [\n \"musicbrainz\",\n \"music\",\n \"new-releases\",\n \"release-radar\",\n \"notifier\",\n \"cli\",\n \"sqlite\"\n ],\n \"type\": \"module\",\n \"bin\": {\n \"silver-music-notifier\": \"dist/cli/index.js\"\n },\n \"files\": [\n \"dist\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"engines\": {\n \"node\": \">=22.12.0\"\n },\n \"scripts\": {\n \"dev\": \"concurrently -n server,web -c blue,magenta \\\"tsx watch src/cli/index.ts web --no-open\\\" \\\"vite\\\"\",\n \"build\": \"npm run build:bundle && npm run build:web\",\n \"build:bundle\": \"tsup\",\n \"build:web\": \"vite build\",\n \"prepare\": \"husky && npm run build\",\n \"refresh\": \"tsx src/cli/index.ts refresh\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"lint\": \"eslint .\",\n \"lint:fix\": \"eslint --fix .\",\n \"format\": \"prettier --write .\",\n \"format:check\": \"prettier --check .\",\n \"check:exports\": \"publint\",\n \"smoke:package\": \"npm run build && node scripts/package-smoke-test.mjs\",\n \"release\": \"bash scripts/release.sh\"\n },\n \"lint-staged\": {\n \"*.{ts,tsx}\": [\n \"eslint --fix --no-warn-ignored\",\n \"prettier --write\"\n ],\n \"*.{js,jsx,json,css,md,html}\": \"prettier --write\"\n },\n \"dependencies\": {\n \"@inquirer/prompts\": \"^7.2.0\",\n \"@tanstack/react-query\": \"^5.101.0\",\n \"better-sqlite3\": \"^11.7.0\",\n \"commander\": \"^13.0.0\",\n \"env-paths\": \"^3.0.0\",\n \"express\": \"^4.21.2\",\n \"musicbrainz-api\": \"^1.2.1\",\n \"nodemailer\": \"^9.0.0\",\n \"open\": \"^10.1.0\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.4\",\n \"@types/better-sqlite3\": \"^7.6.12\",\n \"@types/express\": \"^4.17.21\",\n \"@types/node\": \"^22.10.0\",\n \"@types/nodemailer\": \"^6.4.17\",\n \"@types/react\": \"^19.0.0\",\n \"@types/react-dom\": \"^19.0.0\",\n \"@vitejs/plugin-react\": \"^6.0.2\",\n \"concurrently\": \"^9.1.0\",\n \"eslint\": \"^9.39.4\",\n \"globals\": \"^15.15.0\",\n \"husky\": \"^9.1.7\",\n \"lint-staged\": \"^15.5.2\",\n \"prettier\": \"^3.8.4\",\n \"publint\": \"^0.3.21\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"silver-ui\": \"^0.7.1\",\n \"tsup\": \"^8.3.5\",\n \"tsx\": \"^4.19.2\",\n \"typescript\": \"^5.7.2\",\n \"typescript-eslint\": \"^8.61.0\",\n \"vite\": \"^8.0.16\",\n \"vitest\": \"^4.1.8\"\n },\n \"overrides\": {\n \"esbuild\": \"0.28.1\",\n \"shell-quote\": \"1.8.4\"\n }\n}\n"],"mappings":"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","names":["join","join","api","program","program","program","added","program","program","program","program","program","program","package_default","package_default"]}
1
+ {"version":3,"sources":["../../src/cli/index.ts","../../src/server/index.ts","../../src/lib/musicbrainz.ts","../../package.json","../../src/lib/AppDb.ts","../../src/lib/paths.ts","../../src/lib/releaseTypes.ts","../../src/lib/Settings.ts","../../src/lib/notify.ts","../../src/lib/Release.ts","../../src/lib/formatReleaseDate.ts","../../src/lib/Artist.ts","../../src/lib/refresh.ts","../../src/cli/commands/web.ts","../../src/cli/commands/list.ts","../../src/cli/commands/add.ts","../../src/cli/commands/remove.ts","../../src/cli/commands/refresh.ts","../../src/cli/commands/releases.ts","../../src/cli/commands/dismiss.ts","../../src/cli/commands/config.ts","../../src/cli/commands/clearData.ts","../../src/cli/ensureContact.ts","../../package.json"],"sourcesContent":["import {Command} from 'commander';\nimport {registerWeb} from './commands/web.js';\nimport {registerList} from './commands/list.js';\nimport {registerAdd} from './commands/add.js';\nimport {registerRemove} from './commands/remove.js';\nimport {registerRefresh} from './commands/refresh.js';\nimport {registerReleases} from './commands/releases.js';\nimport {registerDismiss} from './commands/dismiss.js';\nimport {registerConfig} from './commands/config.js';\nimport {registerClearData} from './commands/clearData.js';\nimport {ensureMbContact} from './ensureContact.js';\nimport packageJson from '../../package.json';\n\nconst program = new Command();\n\n// Commands that don't hit the MusicBrainz API and must work before a contact is\n// configured (e.g. `config` to set it, `clear-data` to wipe local state).\nconst CONTACT_EXEMPT_COMMANDS = new Set(['config', 'clear-data', 'dismiss']);\n\n// A configured MusicBrainz contact is required. Ensure it once at the root\n// (prompting interactively on first use) rather than in each command.\nprogram.hook('preAction', async (_thisCommand, actionCommand) => {\n for (let cmd: Command | null = actionCommand; cmd; cmd = cmd.parent) {\n if (CONTACT_EXEMPT_COMMANDS.has(cmd.name())) {\n return;\n }\n }\n await ensureMbContact();\n});\n\nprogram\n .name('silver-music-notifier')\n .description(\n 'Track artists and get notified of their new music releases from MusicBrainz.',\n )\n .version(packageJson.version);\n\nregisterWeb(program);\nregisterList(program);\nregisterAdd(program);\nregisterRemove(program);\nregisterRefresh(program);\nregisterReleases(program);\nregisterDismiss(program);\nregisterConfig(program);\nregisterClearData(program);\n\nprogram.parseAsync(process.argv).catch(err => {\n console.error(err instanceof Error ? err.message : err);\n process.exit(1);\n});\n","import express, {type Request, type Response} from 'express';\nimport {fileURLToPath} from 'node:url';\nimport {existsSync} from 'node:fs';\nimport {dirname, join} from 'node:path';\nimport {searchArtist} from '../lib/musicbrainz.js';\nimport {refresh, refreshArtist} from '../lib/refresh.js';\nimport {sendTestEmail} from '../lib/notify.js';\nimport {Artist} from '../lib/Artist.js';\nimport {Release} from '../lib/Release.js';\nimport {Settings, type SettingsPatch} from '../lib/Settings.js';\n\n// Locate the built web assets. When bundled by tsup the file lives at\n// dist/cli/index.js, so dist/web is two levels up; during `tsx` dev runs the\n// file is src/server/index.ts and dist/web may not exist yet (the dev server is\n// served by Vite instead). We key on index.html so a stale/empty dist/web isn't\n// mistaken for a real build.\nfunction webDir(): string | null {\n const here = dirname(fileURLToPath(import.meta.url));\n const candidates = [\n join(here, '..', 'web'), // dist/cli/index.js -> dist/web\n join(here, '..', '..', 'dist', 'web'), // src/server/index.ts -> dist/web\n ];\n return candidates.find(c => existsSync(join(c, 'index.html'))) ?? null;\n}\n\nfunction asyncRoute(\n fn: (req: Request, res: Response) => Promise<void>,\n): (req: Request, res: Response) => void {\n return (req, res) => {\n fn(req, res).catch(err => {\n console.error(err);\n res\n .status(500)\n .json({error: err instanceof Error ? err.message : String(err)});\n });\n };\n}\n\nexport function createApp() {\n const app = express();\n app.use(express.json());\n\n const api = express.Router();\n\n api.get('/artists', (_req, res) => {\n res.json(Artist.list());\n });\n\n api.get(\n '/artists/search',\n asyncRoute(async (req, res) => {\n const q = String(req.query.q ?? '').trim();\n if (!q) {\n res.json([]);\n return;\n }\n res.json(await searchArtist(q));\n }),\n );\n\n api.post(\n '/artists',\n asyncRoute(async (req, res) => {\n const {mbid, name, sortName, disambiguation, type, country} =\n req.body ?? {};\n if (!mbid || !name) {\n res.status(400).json({error: 'mbid and name are required'});\n return;\n }\n const added = Artist.add({\n mbid,\n name,\n sortName,\n disambiguation,\n type,\n country,\n });\n const artist = added ? Artist.get(mbid) : undefined;\n if (artist) {\n await refreshArtist(artist, {notify: false});\n }\n res.json({added});\n }),\n );\n\n api.delete('/artists/:mbid', (req, res) => {\n const artist = Artist.get(req.params.mbid);\n if (!artist) {\n res.status(404).json({error: 'artist not tracked'});\n return;\n }\n artist.remove();\n res.json({removed: artist});\n });\n\n api.get('/releases', (req, res) => {\n const onlyNew = req.query.new === '1' || req.query.new === 'true';\n const limit = req.query.limit ? Number(req.query.limit) : undefined;\n res.json(Release.list({onlyNew, limit}));\n });\n\n api.post('/releases/:mbid/dismiss', (req, res) => {\n const dismissed = Release.dismiss(req.params.mbid);\n if (!dismissed) {\n res.status(404).json({error: 'release not found'});\n return;\n }\n res.json({dismissed});\n });\n\n api.post(\n '/refresh',\n asyncRoute(async (_req, res) => {\n const summary = await refresh();\n res.json(summary);\n }),\n );\n\n api.get('/settings', (_req, res) => {\n res.json(Settings.load());\n });\n\n api.put('/settings', (req, res) => {\n const patch = req.body as SettingsPatch;\n res.json(Settings.save(patch));\n });\n\n api.post(\n '/settings/test-email',\n asyncRoute(async (req, res) => {\n // Allow testing with the posted settings (saved first), or current ones.\n const patch = req.body as SettingsPatch | undefined;\n const settings =\n patch && Object.keys(patch).length\n ? Settings.save(patch)\n : Settings.load();\n await sendTestEmail(settings);\n res.json({ok: true});\n }),\n );\n\n app.use('/api', api);\n\n // Serve the built SPA (production). In dev, Vite serves the frontend and\n // proxies /api here, so a missing web dir is fine.\n const dir = webDir();\n if (dir) {\n app.use(express.static(dir));\n app.get('*', (_req, res) => {\n res.sendFile(join(dir, 'index.html'));\n });\n } else {\n app.get('*', (_req, res) => {\n res\n .status(503)\n .type('text')\n .send('Web assets are not built. Run `npm run build` and try again.');\n });\n }\n\n return app;\n}\n\n// Start the web server, resolving with the port it bound to. Rejects on a\n// listen error (e.g. EADDRINUSE) so callers can retry on a different port.\nexport function startServer(port: number): Promise<number> {\n return new Promise((resolve, reject) => {\n const server = createApp().listen(port);\n server.once('listening', () => {\n server.removeListener('error', reject);\n resolve(port);\n });\n server.once('error', reject);\n });\n}\n","import {MusicBrainzApi} from 'musicbrainz-api';\nimport packageJson from '../../package.json' with {type: 'json'};\nimport {Settings} from './Settings.js';\nimport {\n RELEASE_GROUP_PRIMARY_TYPES,\n RELEASE_GROUP_SECONDARY_TYPES,\n type ReleaseGroupPrimaryType,\n type ReleaseGroupSecondaryType,\n} from './releaseTypes.js';\n\nexport {\n RELEASE_GROUP_PRIMARY_TYPES,\n RELEASE_GROUP_SECONDARY_TYPES,\n type ReleaseGroupPrimaryType,\n type ReleaseGroupSecondaryType,\n} from './releaseTypes.js';\n\n// App version is informational for the MusicBrainz User-Agent.\nconst APP_VERSION = packageJson.version;\n\nlet client: MusicBrainzApi | null = null;\n\nfunction api(): MusicBrainzApi {\n // Recreate if the contact changed; cheap enough and keeps the User-Agent honest.\n const contact = Settings.musicBrainzContact();\n if (\n !client ||\n (client as unknown as {_contact?: string})._contact !== contact\n ) {\n client = new MusicBrainzApi({\n appName: 'silver-music-notifier',\n appVersion: APP_VERSION,\n appContactInfo: contact,\n });\n (client as unknown as {_contact?: string})._contact = contact;\n }\n return client;\n}\n\nexport interface ArtistSearchResult {\n mbid: string;\n name: string;\n sortName: string;\n disambiguation: string;\n country?: string;\n type?: string;\n}\n\nexport async function searchArtist(\n query: string,\n): Promise<ArtistSearchResult[]> {\n const result = await api().search('artist', {query, limit: 10});\n return (result.artists ?? []).map(a => ({\n mbid: a.id,\n name: a.name,\n sortName: a['sort-name'],\n disambiguation: a.disambiguation,\n country: a.country,\n type: a.type,\n }));\n}\n\nexport interface ReleaseGroup {\n mbid: string;\n title: string;\n primaryType: ReleaseGroupPrimaryType | null;\n secondaryTypes: ReleaseGroupSecondaryType[];\n firstReleaseDate: string | null;\n}\n\nfunction releaseGroupPrimaryType(\n value: unknown,\n): ReleaseGroupPrimaryType | null {\n return RELEASE_GROUP_PRIMARY_TYPES.includes(value as ReleaseGroupPrimaryType)\n ? (value as ReleaseGroupPrimaryType)\n : null;\n}\n\nfunction releaseGroupSecondaryTypes(\n values: unknown,\n): ReleaseGroupSecondaryType[] {\n if (!Array.isArray(values)) {\n return [];\n }\n return values.filter((value): value is ReleaseGroupSecondaryType =>\n RELEASE_GROUP_SECONDARY_TYPES.includes(value as ReleaseGroupSecondaryType),\n );\n}\n\n// Page through every release-group credited to an artist. The musicbrainz-api\n// client handles rate limiting internally.\nexport async function fetchReleaseGroups(\n artistMbid: string,\n): Promise<ReleaseGroup[]> {\n const out: ReleaseGroup[] = [];\n const limit = 100;\n let offset = 0;\n while (true) {\n const res = await api().browse('release-group', {\n artist: artistMbid,\n limit,\n offset,\n });\n const groups = res['release-groups'] ?? [];\n for (const g of groups) {\n out.push({\n mbid: g.id,\n title: g.title,\n primaryType: releaseGroupPrimaryType(g['primary-type']),\n secondaryTypes: releaseGroupSecondaryTypes(g['secondary-types']),\n firstReleaseDate: g['first-release-date'] || null,\n });\n }\n const total = res['release-group-count'] ?? out.length;\n offset += groups.length;\n if (groups.length === 0 || offset >= total) {\n break;\n }\n }\n return out;\n}\n","{\n \"name\": \"silver-music-notifier\",\n \"version\": \"1.1.1\",\n \"description\": \"Track artists and get notified of their new music releases from MusicBrainz, via CLI or a local web UI.\",\n \"license\": \"MIT\",\n \"author\": \"Andrey Goder <andy.goder@gmail.com>\",\n \"homepage\": \"https://github.com/czarandy/silver-music-notifier#readme\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/czarandy/silver-music-notifier.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/czarandy/silver-music-notifier/issues\"\n },\n \"keywords\": [\n \"musicbrainz\",\n \"music\",\n \"new-releases\",\n \"release-radar\",\n \"notifier\",\n \"cli\",\n \"sqlite\"\n ],\n \"type\": \"module\",\n \"bin\": {\n \"silver-music-notifier\": \"dist/cli/index.js\"\n },\n \"files\": [\n \"dist\",\n \"screenshot.png\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"engines\": {\n \"node\": \">=22.12.0\"\n },\n \"scripts\": {\n \"dev\": \"concurrently -n server,web -c blue,magenta \\\"tsx watch src/cli/index.ts web --no-open\\\" \\\"vite\\\"\",\n \"build\": \"npm run build:bundle && npm run build:web\",\n \"build:bundle\": \"tsup\",\n \"build:web\": \"vite build\",\n \"prepare\": \"husky && npm run build\",\n \"refresh\": \"tsx src/cli/index.ts refresh\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"lint\": \"eslint .\",\n \"lint:fix\": \"eslint --fix .\",\n \"format\": \"prettier --write .\",\n \"format:check\": \"prettier --check .\",\n \"check:exports\": \"publint\",\n \"smoke:package\": \"npm run build && node scripts/package-smoke-test.mjs\",\n \"release\": \"bash scripts/release.sh\"\n },\n \"lint-staged\": {\n \"*.{ts,tsx}\": [\n \"eslint --fix --no-warn-ignored\",\n \"prettier --write\"\n ],\n \"*.{js,jsx,json,css,md,html}\": \"prettier --write\"\n },\n \"dependencies\": {\n \"@inquirer/prompts\": \"^7.2.0\",\n \"@tanstack/react-query\": \"^5.101.0\",\n \"better-sqlite3\": \"^11.7.0\",\n \"commander\": \"^13.0.0\",\n \"env-paths\": \"^3.0.0\",\n \"express\": \"^4.21.2\",\n \"musicbrainz-api\": \"^1.2.1\",\n \"nodemailer\": \"^9.0.0\",\n \"open\": \"^10.1.0\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.4\",\n \"@types/better-sqlite3\": \"^7.6.12\",\n \"@types/express\": \"^4.17.21\",\n \"@types/node\": \"^22.10.0\",\n \"@types/nodemailer\": \"^6.4.17\",\n \"@types/react\": \"^19.0.0\",\n \"@types/react-dom\": \"^19.0.0\",\n \"@vitejs/plugin-react\": \"^6.0.2\",\n \"concurrently\": \"^9.1.0\",\n \"eslint\": \"^9.39.4\",\n \"globals\": \"^15.15.0\",\n \"husky\": \"^9.1.7\",\n \"lint-staged\": \"^15.5.2\",\n \"prettier\": \"^3.8.4\",\n \"publint\": \"^0.3.21\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"silver-ui\": \"^0.7.1\",\n \"tsup\": \"^8.3.5\",\n \"tsx\": \"^4.19.2\",\n \"typescript\": \"^5.7.2\",\n \"typescript-eslint\": \"^8.61.0\",\n \"vite\": \"^8.0.16\",\n \"vitest\": \"^4.1.8\"\n },\n \"overrides\": {\n \"esbuild\": \"0.28.1\",\n \"shell-quote\": \"1.8.4\"\n }\n}\n","import Database from 'better-sqlite3';\nimport {dbPath} from './paths.js';\n\nexport class AppDb {\n private static defaultDb: AppDb | null = null;\n\n readonly connection: Database.Database;\n\n constructor(path = dbPath()) {\n this.connection = new Database(path);\n this.initialize();\n }\n\n static getDefault(): Database.Database {\n this.defaultDb ??= new AppDb();\n return this.defaultDb.connection;\n }\n\n static closeDefault(): void {\n this.defaultDb?.close();\n this.defaultDb = null;\n }\n\n close(): void {\n this.connection.close();\n }\n\n private initialize(): void {\n this.connection.pragma('journal_mode = WAL');\n this.connection.pragma('foreign_keys = ON');\n this.connection.exec(`\n CREATE TABLE IF NOT EXISTS artists (\n mbid TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n sort_name TEXT,\n disambiguation TEXT,\n type TEXT,\n country TEXT,\n added_at TEXT NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS release_groups (\n mbid TEXT PRIMARY KEY,\n artist_mbid TEXT NOT NULL REFERENCES artists(mbid) ON DELETE CASCADE,\n title TEXT NOT NULL,\n primary_type TEXT,\n secondary_types TEXT,\n first_release_date TEXT,\n first_seen_at TEXT NOT NULL,\n last_seen_at TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_rg_artist ON release_groups(artist_mbid);\n\n CREATE TABLE IF NOT EXISTS settings (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n );\n `);\n this.ensureColumn('artists', 'type', 'TEXT');\n this.ensureColumn('artists', 'country', 'TEXT');\n this.ensureColumn('release_groups', 'dismissed_at', 'TEXT');\n }\n\n private ensureColumn(\n table: string,\n column: string,\n definition: string,\n ): void {\n const columns = this.connection\n .prepare(`PRAGMA table_info(${table})`)\n .all() as Array<{name: string}>;\n if (!columns.some(c => c.name === column)) {\n this.connection.exec(\n `ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`,\n );\n }\n }\n}\n","import envPaths from 'env-paths';\nimport {mkdirSync} from 'node:fs';\nimport {join} from 'node:path';\n\n// Resolve the per-user data directory. Defaults to the OS-appropriate data dir\n// (e.g. ~/.local/share/silver-music-notifier on Linux), overridable via\n// SILVER_MUSIC_NOTIFIER_DATA_DIR.\nexport function dataDir(): string {\n const dir =\n process.env.SILVER_MUSIC_NOTIFIER_DATA_DIR ??\n envPaths('silver-music-notifier', {suffix: ''}).data;\n mkdirSync(dir, {recursive: true});\n return dir;\n}\n\nexport function dbPath(): string {\n return join(dataDir(), 'data.db');\n}\n","// MusicBrainz release-group type vocabularies. Kept in a dependency-free module\n// so both the MusicBrainz client and Settings can use them without a circular\n// import.\n\nexport const RELEASE_GROUP_PRIMARY_TYPES = [\n 'Album',\n 'Single',\n 'EP',\n 'Broadcast',\n 'Other',\n] as const;\n\nexport type ReleaseGroupPrimaryType =\n (typeof RELEASE_GROUP_PRIMARY_TYPES)[number];\n\nexport const RELEASE_GROUP_SECONDARY_TYPES = [\n 'Compilation',\n 'Soundtrack',\n 'Spokenword',\n 'Interview',\n 'Audiobook',\n 'Audio drama',\n 'Live',\n 'Remix',\n 'DJ-mix',\n 'Mixtape/Street',\n 'Demo',\n 'Field recording',\n] as const;\n\nexport type ReleaseGroupSecondaryType =\n (typeof RELEASE_GROUP_SECONDARY_TYPES)[number];\n\nexport function isPrimaryType(\n value: unknown,\n): value is ReleaseGroupPrimaryType {\n return RELEASE_GROUP_PRIMARY_TYPES.includes(value as ReleaseGroupPrimaryType);\n}\n\nexport function isSecondaryType(\n value: unknown,\n): value is ReleaseGroupSecondaryType {\n return RELEASE_GROUP_SECONDARY_TYPES.includes(\n value as ReleaseGroupSecondaryType,\n );\n}\n","import {AppDb} from './AppDb.js';\nimport {\n isPrimaryType,\n isSecondaryType,\n type ReleaseGroupPrimaryType,\n type ReleaseGroupSecondaryType,\n} from './releaseTypes.js';\n\nexport type SmtpProvider = 'gmail' | 'custom';\n\nexport interface SmtpSettings {\n // Provider preset. 'gmail' fixes host/port/secure so the user only supplies\n // their address and an app password; 'custom' exposes the raw SMTP fields.\n provider: SmtpProvider;\n host: string;\n port: number;\n secure: boolean;\n user: string;\n pass: string;\n from: string;\n to: string;\n}\n\n// Fixed SMTP connection settings for Gmail. When the Gmail provider is\n// selected these override whatever host/port/secure are stored.\nconst GMAIL_SMTP = {host: 'smtp.gmail.com', port: 465, secure: true} as const;\n\nexport interface NotifySettings {\n inPage: boolean;\n email: boolean;\n}\n\nexport interface MusicBrainzSettings {\n contact: string;\n}\n\nexport interface ReleaseFilterSettings {\n // Release-group primary types to keep when refreshing. Types not listed are\n // filtered out (not stored).\n primaryTypes: ReleaseGroupPrimaryType[];\n // Release-group secondary types to exclude when refreshing. A release-group\n // carrying any of these secondary types is filtered out. Empty by default.\n excludeSecondaryTypes: ReleaseGroupSecondaryType[];\n}\n\nexport interface SettingsInput {\n notify: NotifySettings;\n smtp: SmtpSettings;\n musicbrainz: MusicBrainzSettings;\n releaseFilter: ReleaseFilterSettings;\n}\n\nexport type SettingsPatch = Partial<{\n notify: Partial<NotifySettings>;\n smtp: Partial<SmtpSettings>;\n musicbrainz: Partial<MusicBrainzSettings>;\n releaseFilter: Partial<ReleaseFilterSettings>;\n}>;\n\nconst DEFAULT_SETTINGS: SettingsInput = {\n notify: {\n inPage: true,\n email: false,\n },\n smtp: {\n provider: 'gmail',\n host: '',\n port: 587,\n secure: false,\n user: '',\n pass: '',\n from: '',\n to: '',\n },\n musicbrainz: {\n contact: '',\n },\n releaseFilter: {\n // Default to full studio albums only.\n primaryTypes: ['Album'],\n // Drop the common \"album\" variants that aren't new studio releases.\n excludeSecondaryTypes: ['Remix', 'Live', 'Compilation', 'Mixtape/Street'],\n },\n};\n\nconst CONFIG_KEY = 'config';\nconst LAST_REFRESH_KEY = 'last_refresh_at';\n\nexport class Settings {\n readonly notify: NotifySettings;\n readonly smtp: SmtpSettings;\n readonly musicbrainz: MusicBrainzSettings;\n readonly releaseFilter: ReleaseFilterSettings;\n\n constructor(input: SettingsInput = DEFAULT_SETTINGS) {\n this.notify = {...input.notify};\n this.smtp = {...input.smtp};\n this.musicbrainz = {...input.musicbrainz};\n this.releaseFilter = {\n // Drop any unknown values that may have been persisted by an older or\n // hand-edited config.\n primaryTypes: input.releaseFilter.primaryTypes.filter(isPrimaryType),\n excludeSecondaryTypes:\n input.releaseFilter.excludeSecondaryTypes.filter(isSecondaryType),\n };\n }\n\n static defaults(): Settings {\n return new Settings(DEFAULT_SETTINGS);\n }\n\n static load(): Settings {\n const raw = Settings.readRaw(CONFIG_KEY);\n if (!raw) {\n return Settings.defaults();\n }\n\n let parsed: SettingsPatch;\n try {\n parsed = JSON.parse(raw) as SettingsPatch;\n } catch {\n return Settings.defaults();\n }\n\n return Settings.defaults().merge(parsed);\n }\n\n static save(patch: SettingsPatch): Settings {\n const next = Settings.load().merge(patch);\n Settings.writeRaw(CONFIG_KEY, JSON.stringify(next));\n return next;\n }\n\n static getLastRefreshAt(): string | null {\n return Settings.readRaw(LAST_REFRESH_KEY) ?? null;\n }\n\n static setLastRefreshAt(iso: string): void {\n Settings.writeRaw(LAST_REFRESH_KEY, iso);\n }\n\n static clearLastRefreshAt(): void {\n AppDb.getDefault()\n .prepare('DELETE FROM settings WHERE key = ?')\n .run(LAST_REFRESH_KEY);\n }\n\n static musicBrainzContact(): string {\n return Settings.load().musicBrainzContact();\n }\n\n merge(patch: SettingsPatch): Settings {\n return new Settings({\n notify: {...this.notify, ...patch.notify},\n smtp: {...this.smtp, ...patch.smtp},\n musicbrainz: {...this.musicbrainz, ...patch.musicbrainz},\n releaseFilter: {...this.releaseFilter, ...patch.releaseFilter},\n });\n }\n\n // Whether a release-group with this primary type should be kept on refresh.\n supportPrimaryType(type: ReleaseGroupPrimaryType | null): boolean {\n return type !== null && this.releaseFilter.primaryTypes.includes(type);\n }\n\n // Whether a release-group with these secondary types should be kept on\n // refresh. It is filtered out if it carries any excluded secondary type.\n supportSecondaryTypes(types: ReleaseGroupSecondaryType[]): boolean {\n return !types.some(t =>\n this.releaseFilter.excludeSecondaryTypes.includes(t),\n );\n }\n\n // Whether a release-group should be kept on refresh, applying both the\n // primary-type and secondary-type filters.\n includeRelease(group: {\n primaryType: ReleaseGroupPrimaryType | null;\n secondaryTypes: ReleaseGroupSecondaryType[];\n }): boolean {\n return (\n this.supportPrimaryType(group.primaryType) &&\n this.supportSecondaryTypes(group.secondaryTypes)\n );\n }\n\n // SMTP settings with the provider preset applied. For Gmail the host, port,\n // and secure flag are fixed, so connection code should use this rather than\n // reading this.smtp directly.\n resolvedSmtp(): SmtpSettings {\n if (this.smtp.provider === 'gmail') {\n return {...this.smtp, ...GMAIL_SMTP};\n }\n return this.smtp;\n }\n\n smtpIsConfigured(): boolean {\n const smtp = this.resolvedSmtp();\n return Boolean(smtp.host && smtp.user && smtp.to);\n }\n\n // The MusicBrainz contact string used in the API User-Agent. MusicBrainz\n // requires a meaningful contact (an email or URL) and throttles/blocks\n // requests without one, so this is mandatory.\n musicBrainzContact(): string {\n const contact = this.musicbrainz.contact.trim();\n if (!contact) {\n throw new Error(\n 'MusicBrainz contact is not set. MusicBrainz requires a contact (email or ' +\n 'URL) to query its API. Set it in the web UI Settings, or run:\\n' +\n ' silver-music-notifier config set musicbrainz.contact you@example.com',\n );\n }\n return contact;\n }\n\n private static readRaw(key: string): string | undefined {\n const row = AppDb.getDefault()\n .prepare('SELECT value FROM settings WHERE key = ?')\n .get(key) as {value: string} | undefined;\n return row?.value;\n }\n\n private static writeRaw(key: string, value: string): void {\n AppDb.getDefault()\n .prepare(\n 'INSERT INTO settings (key, value) VALUES (?, ?) ' +\n 'ON CONFLICT(key) DO UPDATE SET value = excluded.value',\n )\n .run(key, value);\n }\n}\n","import nodemailer from 'nodemailer';\nimport {Settings} from './Settings.js';\nimport {Release} from './Release.js';\nimport {formatReleaseDate} from './formatReleaseDate.js';\nimport type {NewRelease} from './refresh.js';\nimport type {\n ReleaseGroupPrimaryType,\n ReleaseGroupSecondaryType,\n} from './releaseTypes.js';\n\nfunction subjectLine(r: NewRelease): string {\n return `New Release: ${r.title} by ${r.artistName}`;\n}\n\nfunction emailHtml(r: NewRelease): string {\n const type = [r.primaryType, ...r.secondaryTypes].filter(Boolean).join(' / ');\n const title = `<strong>${escapeHtml(r.title)}</strong>`;\n const artist = escapeHtml(r.artistName);\n const typeText = type ? ` (${escapeHtml(type)})` : '';\n const dateText = r.firstReleaseDate\n ? ` was released on ${escapeHtml(formatReleaseDate(r.firstReleaseDate))}`\n : ' is out';\n return `<div style=\"font-family:system-ui,sans-serif\">\n <p>${title} by ${artist}${typeText}${dateText}.</p>\n </div>`;\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(\n /[&<>\"']/g,\n c =>\n ({'&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;'})[\n c\n ]!,\n );\n}\n\nfunction transport(s: Settings) {\n const smtp = s.resolvedSmtp();\n return nodemailer.createTransport({\n host: smtp.host,\n port: smtp.port,\n secure: smtp.secure,\n auth: smtp.user ? {user: smtp.user, pass: smtp.pass} : undefined,\n });\n}\n\n// Send one notification email for a single release. `subjectPrefix` lets the\n// test email reuse the exact same layout while marking the subject as a test.\nasync function sendReleaseEmail(\n release: NewRelease,\n s: Settings,\n subjectPrefix = '',\n): Promise<void> {\n await transport(s).sendMail({\n from: s.smtp.from || s.smtp.user,\n to: s.smtp.to,\n subject: subjectPrefix + subjectLine(release),\n html: emailHtml(release),\n });\n}\n\n// Dispatch notifications for newly-discovered releases according to user\n// settings, sending one email per release. Email fails soft: a broken SMTP\n// config (or a single failed send) must not prevent the refresh itself from\n// succeeding or block the remaining emails.\nexport async function notifyNewReleases(\n newReleases: NewRelease[],\n): Promise<void> {\n if (newReleases.length === 0) {\n return;\n }\n const s = Settings.load();\n\n if (s.notify.email) {\n if (!s.smtpIsConfigured()) {\n console.warn('Email enabled but SMTP not configured — skipping email.');\n } else {\n for (const release of newReleases) {\n try {\n await sendReleaseEmail(release, s);\n } catch (err) {\n console.error(\n `Email notification failed for \"${release.title}\":`,\n errMsg(err),\n );\n }\n }\n }\n }\n}\n\n// A representative release to preview in the test email: the newest tracked\n// release, or a synthetic sample when nothing is tracked yet.\nfunction sampleRelease(): NewRelease {\n const [latest] = Release.list({limit: 1});\n if (latest) {\n return {\n mbid: latest.mbid,\n artistMbid: latest.artistMbid,\n artistName: latest.artistName,\n title: latest.title,\n primaryType: latest.primaryType as ReleaseGroupPrimaryType | null,\n secondaryTypes: latest.secondaryTypes\n ? (latest.secondaryTypes.split(', ') as ReleaseGroupSecondaryType[])\n : [],\n firstReleaseDate: latest.firstReleaseDate,\n };\n }\n return {\n mbid: 'sample',\n artistMbid: 'sample',\n artistName: 'Example Artist',\n title: 'Example Album',\n primaryType: 'Album',\n secondaryTypes: [],\n firstReleaseDate: new Date().toISOString().slice(0, 10),\n };\n}\n\n// Send a test email using the current (or provided) SMTP settings. It mirrors a\n// real release notification (using the newest tracked release) so the user can\n// see exactly what they'll get, but prefixes the subject with [TEST]. Throws on\n// failure so callers can surface the error to the user.\nexport async function sendTestEmail(override?: Settings): Promise<void> {\n const s = override ?? Settings.load();\n if (!s.smtpIsConfigured()) {\n throw new Error(\n 'SMTP is not configured (host, user, and recipient required).',\n );\n }\n await sendReleaseEmail(sampleRelease(), s, '[TEST] ');\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n","import {AppDb} from './AppDb.js';\nimport {Settings} from './Settings.js';\nimport type {\n ReleaseGroupPrimaryType,\n ReleaseGroupSecondaryType,\n} from './releaseTypes.js';\n\nexport interface ReleaseListOptions {\n onlyNew?: boolean;\n limit?: number;\n}\n\nexport class Release {\n readonly mbid: string;\n readonly artistMbid: string;\n readonly artistName: string;\n readonly title: string;\n readonly primaryType: string | null;\n readonly secondaryTypes: string | null;\n readonly firstReleaseDate: string | null;\n readonly firstSeenAt: string;\n readonly isNew: boolean;\n\n private constructor(input: {\n mbid: string;\n artistMbid: string;\n artistName: string;\n title: string;\n primaryType: string | null;\n secondaryTypes: string | null;\n firstReleaseDate: string | null;\n firstSeenAt: string;\n dismissedAt: string | null;\n lastRefresh: string | null;\n }) {\n this.mbid = input.mbid;\n this.artistMbid = input.artistMbid;\n this.artistName = input.artistName;\n this.title = input.title;\n this.primaryType = input.primaryType;\n this.secondaryTypes = input.secondaryTypes;\n this.firstReleaseDate = input.firstReleaseDate;\n this.firstSeenAt = input.firstSeenAt;\n this.isNew =\n input.dismissedAt == null &&\n input.lastRefresh != null &&\n input.firstSeenAt >= input.lastRefresh;\n }\n\n static list(opts: ReleaseListOptions = {}): Release[] {\n const lastRefresh = Settings.getLastRefreshAt();\n const settings = Settings.load();\n const rows = AppDb.getDefault()\n .prepare(\n `SELECT rg.mbid, rg.artist_mbid, a.name AS artist_name, rg.title,\n rg.primary_type, rg.secondary_types, rg.first_release_date,\n rg.first_seen_at, rg.dismissed_at\n FROM release_groups rg\n JOIN artists a ON a.mbid = rg.artist_mbid\n ORDER BY (rg.first_release_date IS NULL), rg.first_release_date DESC, rg.title`,\n )\n .all() as Array<{\n mbid: string;\n artist_mbid: string;\n artist_name: string;\n title: string;\n primary_type: string | null;\n secondary_types: string | null;\n first_release_date: string | null;\n first_seen_at: string;\n dismissed_at: string | null;\n }>;\n\n // A release is \"new\" if it was first seen at or after the previous refresh\n // started, meaning it showed up in the most recent refresh.\n const items = rows.map(\n row =>\n new Release({\n mbid: row.mbid,\n artistMbid: row.artist_mbid,\n artistName: row.artist_name,\n title: row.title,\n primaryType: row.primary_type,\n secondaryTypes: row.secondary_types,\n firstReleaseDate: row.first_release_date,\n firstSeenAt: row.first_seen_at,\n dismissedAt: row.dismissed_at,\n lastRefresh,\n }),\n );\n // Re-apply the current release-type filters so releases that no longer\n // match the user's settings (e.g. a secondary type they just chose to\n // exclude) drop out of the list without requiring a refresh.\n const matching = items.filter(item =>\n settings.includeRelease({\n primaryType: item.primaryType as ReleaseGroupPrimaryType | null,\n secondaryTypes: item.secondaryTypes\n ? (item.secondaryTypes.split(', ') as ReleaseGroupSecondaryType[])\n : [],\n }),\n );\n const filtered = opts.onlyNew ? matching.filter(i => i.isNew) : matching;\n return opts.limit ? filtered.slice(0, opts.limit) : filtered;\n }\n\n static dismiss(mbid: string): boolean {\n const res = AppDb.getDefault()\n .prepare('UPDATE release_groups SET dismissed_at = ? WHERE mbid = ?')\n .run(new Date().toISOString(), mbid);\n return res.changes > 0;\n }\n}\n","const monthNames = [\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n];\n\nexport function formatReleaseDate(value: string | null): string {\n if (!value) {\n return '—';\n }\n const [year, month, day] = value.split('-').map(Number);\n if (!year) {\n return value;\n }\n if (month && !day) {\n return `${monthNames[month - 1]} ${year}`;\n }\n if (!month || !day) {\n return value;\n }\n return `${day} ${monthNames[month - 1]} ${year}`;\n}\n","import {AppDb} from './AppDb.js';\n\ninterface ArtistRow {\n mbid: string;\n name: string;\n sort_name: string | null;\n disambiguation: string | null;\n type: string | null;\n country: string | null;\n added_at: string;\n}\n\nexport interface ArtistAddInput {\n mbid: string;\n name: string;\n sortName?: string | null;\n disambiguation?: string | null;\n type?: string | null;\n country?: string | null;\n}\n\nexport class Artist {\n readonly mbid: string;\n readonly name: string;\n readonly sortName: string | null;\n readonly disambiguation: string | null;\n readonly type: string | null;\n readonly country: string | null;\n readonly addedAt: string;\n\n private constructor(row: ArtistRow) {\n this.mbid = row.mbid;\n this.name = row.name;\n this.sortName = row.sort_name;\n this.disambiguation = row.disambiguation;\n this.type = row.type;\n this.country = row.country;\n this.addedAt = row.added_at;\n }\n\n static list(): Artist[] {\n const rows = AppDb.getDefault()\n .prepare('SELECT * FROM artists ORDER BY name COLLATE NOCASE')\n .all() as ArtistRow[];\n return rows.map(row => new Artist(row));\n }\n\n static get(mbid: string): Artist | undefined {\n const row = AppDb.getDefault()\n .prepare('SELECT * FROM artists WHERE mbid = ?')\n .get(mbid) as ArtistRow | undefined;\n return row ? new Artist(row) : undefined;\n }\n\n static getByMbidOrName(idOrName: string): Artist | undefined {\n const db = AppDb.getDefault();\n const row =\n (db.prepare('SELECT * FROM artists WHERE mbid = ?').get(idOrName) as\n | ArtistRow\n | undefined) ??\n (db\n .prepare('SELECT * FROM artists WHERE name = ? COLLATE NOCASE')\n .get(idOrName) as ArtistRow | undefined);\n return row ? new Artist(row) : undefined;\n }\n\n // Insert (or no-op if already tracked). Returns true if newly added.\n static add(input: ArtistAddInput): boolean {\n const res = AppDb.getDefault()\n .prepare(\n `INSERT INTO artists\n (mbid, name, sort_name, disambiguation, type, country, added_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(mbid) DO NOTHING`,\n )\n .run(\n input.mbid,\n input.name,\n input.sortName ?? null,\n input.disambiguation ?? null,\n input.type ?? null,\n input.country ?? null,\n new Date().toISOString(),\n );\n return res.changes > 0;\n }\n\n // Cascades to release_groups through the database foreign key.\n remove(): void {\n AppDb.getDefault()\n .prepare('DELETE FROM artists WHERE mbid = ?')\n .run(this.mbid);\n }\n\n // Delete every tracked artist (cascading to release_groups). Returns the\n // number of artists and releases removed.\n static clearAll(): {artists: number; releases: number} {\n const db = AppDb.getDefault();\n const {n: releases} = db\n .prepare('SELECT COUNT(*) AS n FROM release_groups')\n .get() as {n: number};\n const result = db.prepare('DELETE FROM artists').run();\n return {artists: result.changes, releases};\n }\n}\n","import {AppDb} from './AppDb.js';\nimport {\n fetchReleaseGroups,\n type ReleaseGroupPrimaryType,\n type ReleaseGroupSecondaryType,\n} from './musicbrainz.js';\nimport {Settings} from './Settings.js';\nimport {notifyNewReleases} from './notify.js';\nimport {Artist} from './Artist.js';\n\nexport interface NewRelease {\n mbid: string;\n artistMbid: string;\n artistName: string;\n title: string;\n primaryType: ReleaseGroupPrimaryType | null;\n secondaryTypes: ReleaseGroupSecondaryType[];\n firstReleaseDate: string | null;\n}\n\nexport interface RefreshSummary {\n scannedArtists: number;\n newCount: number;\n newReleases: NewRelease[];\n errors: {artist: string; message: string}[];\n startedAt: string;\n}\n\nexport interface RefreshOptions {\n notify?: boolean;\n onProgress?: (artist: Artist, index: number, total: number) => void;\n}\n\nasync function refreshArtists(\n artists: Artist[],\n opts: RefreshOptions,\n persistLastRefresh: boolean,\n // When true, releases discovered in this run are recorded as already-seen so\n // they don't show \"New\" badges. Used when first adding an artist: their whole\n // back-catalog is the baseline, not news (mirrors the no-notify behavior).\n markReleasesSeen: boolean,\n): Promise<RefreshSummary> {\n const db = AppDb.getDefault();\n const startedAt = new Date().toISOString();\n const settings = Settings.load();\n\n // Fail fast (once, before hitting the network per-artist) if the required\n // MusicBrainz contact is missing.\n if (artists.length > 0) {\n settings.musicBrainzContact();\n }\n\n const existing = db.prepare('SELECT 1 FROM release_groups WHERE mbid = ?');\n const insert = db.prepare(`\n INSERT INTO release_groups\n (mbid, artist_mbid, title, primary_type, secondary_types,\n first_release_date, first_seen_at, last_seen_at, dismissed_at)\n VALUES (@mbid, @artist_mbid, @title, @primary_type, @secondary_types,\n @first_release_date, @now, @now, @dismissed_at)\n ON CONFLICT(mbid) DO UPDATE SET\n title = excluded.title,\n primary_type = excluded.primary_type,\n secondary_types = excluded.secondary_types,\n first_release_date = excluded.first_release_date,\n last_seen_at = excluded.last_seen_at\n `);\n\n const newReleases: NewRelease[] = [];\n const errors: RefreshSummary['errors'] = [];\n\n for (let i = 0; i < artists.length; i++) {\n const artist = artists[i];\n opts.onProgress?.(artist, i, artists.length);\n try {\n const groups = await fetchReleaseGroups(artist.mbid);\n const now = new Date().toISOString();\n const apply = db.transaction(() => {\n for (const g of groups) {\n // Skip release-groups filtered out by primary type, or carrying an\n // excluded secondary type.\n if (!settings.includeRelease(g)) {\n continue;\n }\n const seen = existing.get(g.mbid);\n insert.run({\n mbid: g.mbid,\n artist_mbid: artist.mbid,\n title: g.title,\n primary_type: g.primaryType,\n secondary_types: g.secondaryTypes.join(', ') || null,\n first_release_date: g.firstReleaseDate,\n now,\n dismissed_at: markReleasesSeen ? now : null,\n });\n if (!seen) {\n newReleases.push({\n mbid: g.mbid,\n artistMbid: artist.mbid,\n artistName: artist.name,\n title: g.title,\n primaryType: g.primaryType,\n secondaryTypes: g.secondaryTypes,\n firstReleaseDate: g.firstReleaseDate,\n });\n }\n }\n });\n apply();\n } catch (err) {\n errors.push({\n artist: artist.name,\n message: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n if (persistLastRefresh) {\n Settings.setLastRefreshAt(startedAt);\n }\n\n const summary: RefreshSummary = {\n scannedArtists: artists.length,\n newCount: newReleases.length,\n newReleases,\n errors,\n startedAt,\n };\n\n if (opts.notify !== false && newReleases.length > 0) {\n await notifyNewReleases(newReleases);\n }\n\n return summary;\n}\n\n// Fetch release-groups for one newly tracked artist without advancing the\n// global refresh marker for already-tracked releases.\nexport async function refreshArtist(\n artist: Artist,\n opts: RefreshOptions = {},\n): Promise<RefreshSummary> {\n return refreshArtists([artist], opts, false, true);\n}\n\n// Fetch release-groups for every tracked artist, upsert them, and collect the\n// ones we'd never seen before. Shared by the CLI `refresh` command and the\n// server's POST /api/refresh route.\nexport async function refresh(\n opts: RefreshOptions = {},\n): Promise<RefreshSummary> {\n return refreshArtists(Artist.list(), opts, true, false);\n}\n","import type {Command} from 'commander';\nimport {startServer} from '../../server/index.js';\n\n// How many consecutive ports to try before giving up.\nconst MAX_PORT_ATTEMPTS = 10;\n\nfunction isAddrInUse(err: unknown): boolean {\n return (\n typeof err === 'object' &&\n err !== null &&\n (err as {code?: string}).code === 'EADDRINUSE'\n );\n}\n\nexport function registerWeb(program: Command): void {\n program\n .command('web')\n .description('Launch the local web UI')\n .option('-p, --port <port>', 'port to listen on', '3001')\n .option('--no-open', 'do not open a browser window')\n .action(async (opts: {port: string; open: boolean}) => {\n const requested = Number(opts.port);\n let bound: number | undefined;\n for (let port = requested; port < requested + MAX_PORT_ATTEMPTS; port++) {\n try {\n bound = await startServer(port);\n break;\n } catch (err) {\n if (!isAddrInUse(err)) {\n throw err;\n }\n console.log(`Port ${port} is in use, trying ${port + 1}…`);\n }\n }\n if (bound === undefined) {\n throw new Error(\n `Could not find a free port in range ` +\n `${requested}–${requested + MAX_PORT_ATTEMPTS - 1}.`,\n );\n }\n\n const url = `http://localhost:${bound}`;\n console.log(`silver-music-notifier web UI running at ${url}`);\n if (opts.open) {\n try {\n const {default: open} = await import('open');\n await open(url);\n } catch {\n console.log('(could not open browser automatically)');\n }\n }\n });\n}\n","import type {Command} from 'commander';\nimport {Artist} from '../../lib/Artist.js';\n\nexport function registerList(program: Command): void {\n program\n .command('list')\n .description('List tracked artists')\n .action(() => {\n const artists = Artist.list();\n if (artists.length === 0) {\n console.log(\n 'No artists tracked yet. Add one with: silver-music-notifier add <name>',\n );\n return;\n }\n for (const a of artists) {\n const extra = a.disambiguation ? ` (${a.disambiguation})` : '';\n console.log(`${a.name}${extra} — ${a.mbid}`);\n }\n console.log(\n `\\n${artists.length} artist${artists.length === 1 ? '' : 's'} tracked.`,\n );\n });\n}\n","import type {Command} from 'commander';\nimport {searchArtist} from '../../lib/musicbrainz.js';\nimport {Artist} from '../../lib/Artist.js';\nimport {refreshArtist} from '../../lib/refresh.js';\n\nasync function refreshIfAdded(added: boolean, mbid: string): Promise<void> {\n if (!added) {\n return;\n }\n const artist = Artist.get(mbid);\n if (artist) {\n await refreshArtist(artist, {notify: false});\n }\n}\n\nexport function registerAdd(program: Command): void {\n program\n .command('add')\n .description('Search MusicBrainz and add an artist to track')\n .argument('<query>', 'artist name to search for')\n .option(\n '--mbid <mbid>',\n 'add this exact MusicBrainz artist MBID, skipping search',\n )\n .option('-y, --yes', 'add the top search result without prompting')\n .action(async (query: string, opts: {mbid?: string; yes?: boolean}) => {\n if (opts.mbid) {\n const added = Artist.add({mbid: opts.mbid, name: query});\n await refreshIfAdded(added, opts.mbid);\n console.log(added ? `Added ${query}.` : `${query} is already tracked.`);\n return;\n }\n\n const results = await searchArtist(query);\n if (results.length === 0) {\n console.log(`No MusicBrainz artists found for \"${query}\".`);\n return;\n }\n\n let chosen = results[0];\n if (!opts.yes && results.length > 1) {\n const {select} = await import('@inquirer/prompts');\n const mbid = await select({\n message: 'Which artist?',\n choices: results.map(r => ({\n name: [\n r.name,\n r.disambiguation ? `(${r.disambiguation})` : '',\n r.type ? `· ${r.type}` : '',\n r.country ? `· ${r.country}` : '',\n ]\n .filter(Boolean)\n .join(' '),\n value: r.mbid,\n })),\n });\n chosen = results.find(r => r.mbid === mbid)!;\n }\n\n const added = Artist.add({\n mbid: chosen.mbid,\n name: chosen.name,\n sortName: chosen.sortName,\n disambiguation: chosen.disambiguation,\n type: chosen.type,\n country: chosen.country,\n });\n await refreshIfAdded(added, chosen.mbid);\n console.log(\n added\n ? `Added ${chosen.name} (${chosen.mbid}).`\n : `${chosen.name} is already tracked.`,\n );\n });\n}\n","import type {Command} from 'commander';\nimport {Artist} from '../../lib/Artist.js';\n\nexport function registerRemove(program: Command): void {\n program\n .command('remove')\n .alias('rm')\n .description('Stop tracking an artist (by MBID or name)')\n .argument('<idOrName>', 'MusicBrainz MBID or exact artist name')\n .action((idOrName: string) => {\n const artist = Artist.getByMbidOrName(idOrName);\n if (!artist) {\n console.log(`No tracked artist matched \"${idOrName}\".`);\n process.exitCode = 1;\n return;\n }\n artist.remove();\n console.log(`Removed ${artist.name} (${artist.mbid}).`);\n });\n}\n","import type {Command} from 'commander';\nimport {refresh} from '../../lib/refresh.js';\n\nexport function registerRefresh(program: Command): void {\n program\n .command('refresh')\n .description(\n 'Fetch releases for all tracked artists and notify on new ones',\n )\n .option('--no-notify', 'skip desktop/email notifications')\n .action(async (opts: {notify: boolean}) => {\n const summary = await refresh({\n notify: opts.notify,\n onProgress: (artist, i, total) => {\n process.stdout.write(\n `\\r[${i + 1}/${total}] ${artist.name}`.padEnd(60),\n );\n },\n });\n process.stdout.write('\\r'.padEnd(60) + '\\r');\n\n console.log(\n `Scanned ${summary.scannedArtists} artist${summary.scannedArtists === 1 ? '' : 's'}; ` +\n `${summary.newCount} new release${summary.newCount === 1 ? '' : 's'}.`,\n );\n for (const r of summary.newReleases) {\n const type = [r.primaryType, ...r.secondaryTypes]\n .filter(Boolean)\n .join(' / ');\n console.log(\n ` + ${r.artistName} — ${r.title}` +\n (type ? ` [${type}]` : '') +\n (r.firstReleaseDate ? ` (${r.firstReleaseDate})` : ''),\n );\n }\n for (const e of summary.errors) {\n console.error(` ! ${e.artist}: ${e.message}`);\n }\n });\n}\n","import type {Command} from 'commander';\nimport {Release} from '../../lib/Release.js';\n\nexport function registerReleases(program: Command): void {\n program\n .command('releases')\n .description('List known releases, newest first')\n .option('--new', 'only show releases discovered in the last refresh')\n .option('-n, --limit <n>', 'limit the number of rows', v => Number(v))\n .action((opts: {new?: boolean; limit?: number}) => {\n const items = Release.list({onlyNew: opts.new, limit: opts.limit});\n if (items.length === 0) {\n console.log(\n opts.new\n ? 'No new releases since the last refresh.'\n : 'No releases yet. Run: silver-music-notifier refresh',\n );\n return;\n }\n for (const r of items) {\n const date = r.firstReleaseDate ?? '—'.padEnd(10);\n const type = [r.primaryType, r.secondaryTypes]\n .filter(Boolean)\n .join(' / ');\n const flag = r.isNew ? 'NEW ' : ' ';\n console.log(\n `${flag}${date.padEnd(11)} ${r.artistName} — ${r.title}${type ? ` [${type}]` : ''}`,\n );\n }\n });\n}\n","import type {Command} from 'commander';\nimport {Release} from '../../lib/Release.js';\n\nexport function registerDismiss(program: Command): void {\n program\n .command('dismiss')\n .description('Dismiss the New badge for a release')\n .argument('<releaseMbid>', 'MusicBrainz release-group MBID')\n .action((releaseMbid: string) => {\n const dismissed = Release.dismiss(releaseMbid);\n if (!dismissed) {\n console.log(`No release matched \"${releaseMbid}\".`);\n process.exitCode = 1;\n return;\n }\n console.log(`Dismissed ${releaseMbid}.`);\n });\n}\n","import type {Command} from 'commander';\nimport {Settings, type SettingsPatch} from '../../lib/Settings.js';\nimport {\n RELEASE_GROUP_PRIMARY_TYPES,\n RELEASE_GROUP_SECONDARY_TYPES,\n} from '../../lib/releaseTypes.js';\n\nconst PRIMARY_TYPES_KEY = 'releaseFilter.primaryTypes';\nconst SECONDARY_TYPES_KEY = 'releaseFilter.excludeSecondaryTypes';\n\n// Parse a comma-separated list (e.g. \"Album,EP\") and validate every entry\n// against the allowed vocabulary.\nfunction parseTypeList(\n value: string,\n valid: readonly string[],\n label: string,\n): string[] {\n const types = value\n .split(',')\n .map(t => t.trim())\n .filter(Boolean);\n const invalid = types.filter(t => !valid.includes(t));\n if (invalid.length > 0) {\n throw new Error(\n `Invalid ${label}(s): ${invalid.join(', ')}. Valid: ${valid.join(', ')}`,\n );\n }\n return types;\n}\n\n// Flatten the settings object into dotted keys for display/editing, masking the\n// SMTP password so it is never printed.\nfunction flatten(s: Settings): Record<string, string> {\n return {\n 'notify.inPage': String(s.notify.inPage),\n 'notify.email': String(s.notify.email),\n 'smtp.host': s.smtp.host,\n 'smtp.port': String(s.smtp.port),\n 'smtp.secure': String(s.smtp.secure),\n 'smtp.user': s.smtp.user,\n 'smtp.pass': s.smtp.pass ? '********' : '',\n 'smtp.from': s.smtp.from,\n 'smtp.to': s.smtp.to,\n 'musicbrainz.contact': s.musicbrainz.contact,\n [PRIMARY_TYPES_KEY]: s.releaseFilter.primaryTypes.join(','),\n [SECONDARY_TYPES_KEY]: s.releaseFilter.excludeSecondaryTypes.join(','),\n };\n}\n\nfunction coerce(\n key: string,\n value: string,\n): boolean | number | string | string[] {\n if (/^(notify\\.|smtp\\.secure$)/.test(key)) {\n return value === 'true' || value === '1';\n }\n if (key === 'smtp.port') {\n return Number(value);\n }\n if (key === PRIMARY_TYPES_KEY) {\n return parseTypeList(value, RELEASE_GROUP_PRIMARY_TYPES, 'primary type');\n }\n if (key === SECONDARY_TYPES_KEY) {\n return parseTypeList(\n value,\n RELEASE_GROUP_SECONDARY_TYPES,\n 'secondary type',\n );\n }\n return value;\n}\n\nfunction patchFor(\n key: string,\n value: boolean | number | string | string[],\n): SettingsPatch {\n const [group, field] = key.split('.');\n return {[group]: {[field]: value}} as unknown as SettingsPatch;\n}\n\nexport function registerConfig(program: Command): void {\n const config = program.command('config').description('View or edit settings');\n\n config\n .command('get', {isDefault: true})\n .description('Print settings (or a single key)')\n .argument('[key]', 'dotted key, e.g. notify.email')\n .action((key?: string) => {\n const flat = flatten(Settings.load());\n if (key) {\n if (!(key in flat)) {\n console.error(`Unknown key: ${key}`);\n process.exitCode = 1;\n return;\n }\n console.log(flat[key]);\n return;\n }\n for (const [k, v] of Object.entries(flat)) {\n console.log(`${k} = ${v}`);\n }\n });\n\n config\n .command('set')\n .description('Set a settings key')\n .argument('<key>', 'dotted key, e.g. smtp.host')\n .argument('<value>', 'new value')\n .action((key: string, value: string) => {\n const valid = new Set(Object.keys(flatten(Settings.load())));\n if (!valid.has(key)) {\n console.error(\n `Unknown key: ${key}\\nValid keys: ${[...valid].join(', ')}`,\n );\n process.exitCode = 1;\n return;\n }\n Settings.save(patchFor(key, coerce(key, value)));\n console.log(`Set ${key}.`);\n });\n}\n","import type {Command} from 'commander';\nimport {Artist} from '../../lib/Artist.js';\nimport {Settings} from '../../lib/Settings.js';\n\nexport function registerClearData(program: Command): void {\n program\n .command('clear-data')\n .description('Delete all tracked artists and stored releases')\n .option('-y, --yes', 'skip the confirmation prompt')\n .action(async (opts: {yes?: boolean}) => {\n if (!opts.yes) {\n if (!process.stdin.isTTY) {\n console.error(\n 'Refusing to clear data without confirmation. Re-run with --yes.',\n );\n process.exitCode = 1;\n return;\n }\n const {confirm} = await import('@inquirer/prompts');\n const confirmed = await confirm({\n message:\n 'Delete ALL tracked artists and stored releases? This cannot be undone.',\n default: false,\n });\n if (!confirmed) {\n console.log('Aborted.');\n return;\n }\n }\n\n const {artists, releases} = Artist.clearAll();\n Settings.clearLastRefreshAt();\n console.log(\n `Cleared ${artists} artist${artists === 1 ? '' : 's'} and ` +\n `${releases} release${releases === 1 ? '' : 's'}. Settings were kept.`,\n );\n });\n}\n","import {Settings} from '../lib/Settings.js';\n\n// MusicBrainz requires a contact (email or URL) in the API User-Agent. The first\n// time a command needs the API and no contact is set, prompt for one and save\n// it. In a non-interactive context (no TTY, e.g. cron/CI) fall back to throwing\n// the standard guidance error instead of hanging on a prompt.\nexport async function ensureMbContact(): Promise<void> {\n if (Settings.load().musicbrainz.contact.trim()) {\n return;\n }\n\n if (!process.stdin.isTTY) {\n Settings.musicBrainzContact(); // throws with setup guidance\n return;\n }\n\n const {input} = await import('@inquirer/prompts');\n const contact = await input({\n message:\n 'MusicBrainz requires a contact (email or URL) to use its API. Enter one:',\n validate: value => {\n const v = value.trim();\n if (!v) {\n return 'A contact is required.';\n }\n if (!v.includes('@') && !v.includes('.')) {\n return 'Enter an email address or a URL.';\n }\n return true;\n },\n });\n\n Settings.save({musicbrainz: {contact: contact.trim()}});\n console.log(\n 'Saved. Change it later with: silver-music-notifier config set musicbrainz.contact <value>\\n',\n );\n}\n","{\n \"name\": \"silver-music-notifier\",\n \"version\": \"1.1.1\",\n \"description\": \"Track artists and get notified of their new music releases from MusicBrainz, via CLI or a local web UI.\",\n \"license\": \"MIT\",\n \"author\": \"Andrey Goder <andy.goder@gmail.com>\",\n \"homepage\": \"https://github.com/czarandy/silver-music-notifier#readme\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/czarandy/silver-music-notifier.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/czarandy/silver-music-notifier/issues\"\n },\n \"keywords\": [\n \"musicbrainz\",\n \"music\",\n \"new-releases\",\n \"release-radar\",\n \"notifier\",\n \"cli\",\n \"sqlite\"\n ],\n \"type\": \"module\",\n \"bin\": {\n \"silver-music-notifier\": \"dist/cli/index.js\"\n },\n \"files\": [\n \"dist\",\n \"screenshot.png\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"engines\": {\n \"node\": \">=22.12.0\"\n },\n \"scripts\": {\n \"dev\": \"concurrently -n server,web -c blue,magenta \\\"tsx watch src/cli/index.ts web --no-open\\\" \\\"vite\\\"\",\n \"build\": \"npm run build:bundle && npm run build:web\",\n \"build:bundle\": \"tsup\",\n \"build:web\": \"vite build\",\n \"prepare\": \"husky && npm run build\",\n \"refresh\": \"tsx src/cli/index.ts refresh\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"lint\": \"eslint .\",\n \"lint:fix\": \"eslint --fix .\",\n \"format\": \"prettier --write .\",\n \"format:check\": \"prettier --check .\",\n \"check:exports\": \"publint\",\n \"smoke:package\": \"npm run build && node scripts/package-smoke-test.mjs\",\n \"release\": \"bash scripts/release.sh\"\n },\n \"lint-staged\": {\n \"*.{ts,tsx}\": [\n \"eslint --fix --no-warn-ignored\",\n \"prettier --write\"\n ],\n \"*.{js,jsx,json,css,md,html}\": \"prettier --write\"\n },\n \"dependencies\": {\n \"@inquirer/prompts\": \"^7.2.0\",\n \"@tanstack/react-query\": \"^5.101.0\",\n \"better-sqlite3\": \"^11.7.0\",\n \"commander\": \"^13.0.0\",\n \"env-paths\": \"^3.0.0\",\n \"express\": \"^4.21.2\",\n \"musicbrainz-api\": \"^1.2.1\",\n \"nodemailer\": \"^9.0.0\",\n \"open\": \"^10.1.0\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.4\",\n \"@types/better-sqlite3\": \"^7.6.12\",\n \"@types/express\": \"^4.17.21\",\n \"@types/node\": \"^22.10.0\",\n \"@types/nodemailer\": \"^6.4.17\",\n \"@types/react\": \"^19.0.0\",\n \"@types/react-dom\": \"^19.0.0\",\n \"@vitejs/plugin-react\": \"^6.0.2\",\n \"concurrently\": \"^9.1.0\",\n \"eslint\": \"^9.39.4\",\n \"globals\": \"^15.15.0\",\n \"husky\": \"^9.1.7\",\n \"lint-staged\": \"^15.5.2\",\n \"prettier\": \"^3.8.4\",\n \"publint\": \"^0.3.21\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"silver-ui\": \"^0.7.1\",\n \"tsup\": \"^8.3.5\",\n \"tsx\": \"^4.19.2\",\n \"typescript\": \"^5.7.2\",\n \"typescript-eslint\": \"^8.61.0\",\n \"vite\": \"^8.0.16\",\n \"vitest\": \"^4.1.8\"\n },\n \"overrides\": {\n \"esbuild\": \"0.28.1\",\n \"shell-quote\": \"1.8.4\"\n }\n}\n"],"mappings":"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","names":["join","join","api","program","program","program","added","program","program","program","program","program","program","package_default","package_default"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "silver-music-notifier",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Track artists and get notified of their new music releases from MusicBrainz, via CLI or a local web UI.",
5
5
  "license": "MIT",
6
6
  "author": "Andrey Goder <andy.goder@gmail.com>",
@@ -26,7 +26,8 @@
26
26
  "silver-music-notifier": "dist/cli/index.js"
27
27
  },
28
28
  "files": [
29
- "dist"
29
+ "dist",
30
+ "screenshot.png"
30
31
  ],
31
32
  "publishConfig": {
32
33
  "access": "public"
package/screenshot.png ADDED
Binary file