A decentralized music tracking and discovery platform built on AT Protocol 🎵
rocksky.app
spotify
atproto
lastfm
musicbrainz
scrobbling
listenbrainz
1#!/usr/bin/env node
2
3import chalk from "chalk";
4import { albums } from "cmd/albums";
5import { artists } from "cmd/artists";
6import { createApiKey } from "cmd/create";
7import { mcp } from "cmd/mcp";
8import { nowplaying } from "cmd/nowplaying";
9import { scrobble } from "cmd/scrobble";
10import { scrobbles } from "cmd/scrobbles";
11import { search } from "cmd/search";
12import { stats } from "cmd/stats";
13import { tracks } from "cmd/tracks";
14import { whoami } from "cmd/whoami";
15import { Command } from "commander";
16import { version } from "../package.json" assert { type: "json" };
17import { login } from "./cmd/login";
18import { sync } from "cmd/sync";
19import { initializeDatabase } from "./drizzle";
20import { scrobbleApi } from "cmd/scrobble-api";
21
22await initializeDatabase();
23
24const program = new Command();
25
26program
27 .name("rocksky")
28 .description(
29 `
30 ___ __ __ _______ ____
31 / _ \\___ ____/ /__ ___ / /____ __ / ___/ / / _/
32 / , _/ _ \\/ __/ '_/(_-</ '_/ // / / /__/ /___/ /
33 /_/|_|\\___/\\__/_/\\_\\/___/_/\\_\\\\_, / \\___/____/___/
34 /___/
35 Command-line interface for Rocksky ${chalk.magentaBright(
36 "https://rocksky.app",
37 )} – scrobble tracks, view stats, and manage your listening history.`,
38 )
39 .version(version);
40
41program.configureHelp({
42 styleTitle: (str) => chalk.bold.cyan(str),
43 styleCommandText: (str) => chalk.yellow(str),
44 styleDescriptionText: (str) => chalk.white(str),
45 styleOptionText: (str) => chalk.green(str),
46 styleArgumentText: (str) => chalk.magenta(str),
47 styleSubcommandText: (str) => chalk.blue(str),
48});
49
50program.addHelpText(
51 "after",
52 `
53${chalk.bold("\nLearn more about Rocksky:")} https://docs.rocksky.app
54${chalk.bold("Join our Discord community:")} ${chalk.blueBright("https://discord.gg/EVcBy2fVa3")}
55`,
56);
57
58program
59 .command("login")
60 .argument("<handle>", "your AT Proto handle (e.g., <username>.bsky.social)")
61 .description("login with your AT Proto account and get a session token")
62 .action(login);
63
64program
65 .command("whoami")
66 .description("get the current logged-in user")
67 .action(whoami);
68
69program
70 .command("nowplaying")
71 .argument(
72 "[did]",
73 "the DID or handle of the user to get the now playing track for",
74 )
75 .description("get the currently playing track")
76 .action(nowplaying);
77
78program
79 .command("scrobbles")
80 .option("-s, --skip <number>", "number of scrobbles to skip")
81 .option("-l, --limit <number>", "number of scrobbles to limit")
82 .argument("[did]", "the DID or handle of the user to get the scrobbles for")
83 .description("display recently played tracks")
84 .action(scrobbles);
85
86program
87 .command("search")
88 .option("-a, --albums", "search for albums")
89 .option("-t, --tracks", "search for tracks")
90 .option("-u, --users", "search for users")
91 .option("-l, --limit <number>", "number of results to limit")
92 .argument(
93 "<query>",
94 "the search query, e.g., artist, album, title or account",
95 )
96 .description("search for tracks, albums, or accounts")
97 .action(search);
98
99program
100 .command("stats")
101 .option("-l, --limit <number>", "number of results to limit")
102 .argument("[did]", "the DID or handle of the user to get stats for")
103 .description("get the user's listening stats")
104 .action(stats);
105
106program
107 .command("artists")
108 .option("-l, --limit <number>", "number of results to limit")
109 .argument("[did]", "the DID or handle of the user to get artists for")
110 .description("get the user's top artists")
111 .action(artists);
112
113program
114 .command("albums")
115 .option("-l, --limit <number>", "number of results to limit")
116 .argument("[did]", "the DID or handle of the user to get albums for")
117 .description("get the user's top albums")
118 .action(albums);
119
120program
121 .command("tracks")
122 .option("-l, --limit <number>", "number of results to limit")
123 .argument("[did]", "the DID or handle of the user to get tracks for")
124 .description("get the user's top tracks")
125 .action(tracks);
126
127program
128 .command("scrobble")
129 .argument("<track>", "the title of the track")
130 .argument("<artist>", "the artist of the track")
131 .option("-t, --timestamp <timestamp>", "the timestamp of the scrobble")
132 .option("-d, --dry-run", "simulate the scrobble without actually sending it")
133 .description("scrobble a track to your profile")
134 .action(scrobble);
135
136program
137 .command("create")
138 .description("create a new API key")
139 .command("apikey")
140 .argument("<name>", "the name of the API key")
141 .option("-d, --description <description>", "the description of the API key")
142 .description("create a new API key")
143 .action(createApiKey);
144
145program
146 .command("mcp")
147 .description("starts an MCP server to use with Claude or other LLMs")
148 .action(mcp);
149
150program
151 .command("sync")
152 .description("sync your local Rocksky data from AT Protocol")
153 .action(sync);
154
155program
156 .command("scrobble-api")
157 .description("start a local listenbrainz/lastfm compatibility server")
158 .option("-p, --port <port>", "the port to listen on", "8778")
159 .action(scrobbleApi);
160
161program.parse(process.argv);