@sriinnu/harmon 0.1.0

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.
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Harmon CLI - Thin client that calls the daemon
3
+ */
4
+ import type { Command } from '@sriinnu/harmon-protocol';
5
+ export interface CLIConfig {
6
+ endpoint: string;
7
+ token?: string;
8
+ timeoutMs?: number;
9
+ }
10
+ export type ProviderName = 'spotify' | 'apple' | 'youtube';
11
+ export interface CookieRecord {
12
+ domain: string;
13
+ name: string;
14
+ path: string;
15
+ value: string;
16
+ expires?: string | null;
17
+ isSecure: boolean;
18
+ isHTTPOnly: boolean;
19
+ }
20
+ export declare function createCLI(config: CLIConfig): {
21
+ status(): Promise<unknown>;
22
+ command(cmd: Command): Promise<unknown>;
23
+ devices(): Promise<unknown>;
24
+ useDevice(deviceId: string): Promise<unknown>;
25
+ authLogin(): Promise<unknown>;
26
+ authLogout(): Promise<unknown>;
27
+ authImportCookies(cookies: CookieRecord[]): Promise<unknown>;
28
+ youtubeAuthLogin(): Promise<unknown>;
29
+ youtubeAuthLogout(): Promise<unknown>;
30
+ youtubeAuthRefresh(): Promise<unknown>;
31
+ appleAuthSetUserToken(token: string): Promise<unknown>;
32
+ appleAuthRefresh(): Promise<unknown>;
33
+ appleAuthLogout(): Promise<unknown>;
34
+ spotifySearch(query: string, type: string, options?: {
35
+ limit?: number;
36
+ offset?: number;
37
+ }): Promise<unknown>;
38
+ appleSearch(query: string, type: string, options?: {
39
+ limit?: number;
40
+ offset?: number;
41
+ }): Promise<unknown>;
42
+ appleLibraryTracks(options?: {
43
+ limit?: number;
44
+ }): Promise<unknown>;
45
+ applePlaylists(options?: {
46
+ limit?: number;
47
+ }): Promise<unknown>;
48
+ applePlaylistTracks(playlistId: string, options?: {
49
+ limit?: number;
50
+ }): Promise<unknown>;
51
+ appleRecommendations(options?: {
52
+ limit?: number;
53
+ seed?: string;
54
+ }): Promise<unknown>;
55
+ youtubeSearch(query: string, type: string, options?: {
56
+ limit?: number;
57
+ }): Promise<unknown>;
58
+ youtubeLibraryTracks(options?: {
59
+ limit?: number;
60
+ }): Promise<unknown>;
61
+ youtubePlaylists(options?: {
62
+ limit?: number;
63
+ }): Promise<unknown>;
64
+ youtubePlaylistTracks(playlistId: string, options?: {
65
+ limit?: number;
66
+ }): Promise<unknown>;
67
+ youtubeRecommendations(options?: {
68
+ limit?: number;
69
+ seed?: string;
70
+ }): Promise<unknown>;
71
+ applePlay(payload?: {
72
+ url?: string;
73
+ }): Promise<unknown>;
74
+ applePause(): Promise<unknown>;
75
+ appleNext(): Promise<unknown>;
76
+ applePrev(): Promise<unknown>;
77
+ appleNowPlaying(): Promise<unknown>;
78
+ youtubePlay(payload?: {
79
+ uri?: string;
80
+ }): Promise<unknown>;
81
+ youtubePause(): Promise<unknown>;
82
+ youtubeNext(): Promise<unknown>;
83
+ youtubePrev(): Promise<unknown>;
84
+ youtubeNowPlaying(): Promise<unknown>;
85
+ youtubeQueueAdd(uri: string): Promise<unknown>;
86
+ spotifyPlay(payload?: {
87
+ uri?: string;
88
+ contextUri?: string;
89
+ }): Promise<unknown>;
90
+ spotifyPlaylists(options?: {
91
+ limit?: number;
92
+ offset?: number;
93
+ }): Promise<unknown[]>;
94
+ spotifyPlaylistTracks(playlistId: string, options?: {
95
+ limit?: number;
96
+ offset?: number;
97
+ }): Promise<unknown[]>;
98
+ spotifyLibraryTracks(options?: {
99
+ limit?: number;
100
+ offset?: number;
101
+ }): Promise<unknown[]>;
102
+ spotifyRecommendations(options?: {
103
+ limit?: number;
104
+ seed?: string;
105
+ }): Promise<unknown>;
106
+ spotifyPause(): Promise<unknown>;
107
+ spotifyNext(): Promise<unknown>;
108
+ spotifyPrev(): Promise<unknown>;
109
+ spotifySeek(positionMs: number): Promise<unknown>;
110
+ spotifyVolume(volumePercent: number): Promise<unknown>;
111
+ spotifyShuffle(state: boolean): Promise<unknown>;
112
+ spotifyRepeat(state: "off" | "track" | "context"): Promise<unknown>;
113
+ spotifyNowPlaying(): Promise<unknown>;
114
+ spotifyQueueAdd(uri: string): Promise<unknown>;
115
+ smartSearch(query: string, options?: {
116
+ limit?: number;
117
+ }): Promise<unknown>;
118
+ smartPlay(options: {
119
+ query?: string;
120
+ uri?: string;
121
+ provider?: string;
122
+ }): Promise<unknown>;
123
+ recognize(audioBase64: string): Promise<unknown>;
124
+ };
125
+ export declare function getDefaultEndpoint(): string;
126
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAIxD,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3D,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;CACrB;AAMD,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS;;iBA4DpB,OAAO;;wBAMA,MAAM;;;+BASC,YAAY,EAAE;;;;iCAgBZ,MAAM;;;yBAUd,MAAM,QAAQ,MAAM,YAAY;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;uBAKrE,MAAM,QAAQ,MAAM,YAAY;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;iCAKzD;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;6BAKtB;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;oCAKX,MAAM,YAAY;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;mCAKrC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;yBAK3C,MAAM,QAAQ,MAAM,YAAY;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;mCAKxC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;+BAKtB;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;sCAKX,MAAM,YAAY;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;qCAKrC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;wBAK9C;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;0BAed;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;yBAejB,MAAM;0BAGL;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;+BAGhC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;sCAM5B,MAAM,YAAY;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;mCAMxD;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;qCAMjC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;;;;4BAc1C,MAAM;iCAGD,MAAM;0BAGb,OAAO;yBAGR,KAAK,GAAG,OAAO,GAAG,SAAS;;yBAM3B,MAAM;uBAKR,MAAM,YAAY;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;uBAKpC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE;2BAK/C,MAAM;EAItC;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C"}
package/dist/index.js ADDED
@@ -0,0 +1,255 @@
1
+ // @ts-nocheck
2
+
3
+
4
+ // src/index.ts
5
+ var DEFAULT_ENDPOINT = "http://127.0.0.1:17373";
6
+ function createCLI(config) {
7
+ const authHeaders = config.token ? { Authorization: `Bearer ${config.token}` } : {};
8
+ const timeoutMs = config.timeoutMs ?? 1e4;
9
+ let insecureWarned = false;
10
+ const requestJson = async (path, options = {}) => {
11
+ if (!insecureWarned && config.endpoint.startsWith("http://")) {
12
+ const parsed = new URL(config.endpoint);
13
+ const loopback = ["127.0.0.1", "::1", "localhost"].includes(parsed.hostname);
14
+ if (!loopback && config.token) {
15
+ console.warn(
16
+ "WARNING: Sending auth token over insecure HTTP to %s. Use HTTPS for remote daemons.",
17
+ parsed.hostname
18
+ );
19
+ insecureWarned = true;
20
+ }
21
+ }
22
+ const url = new URL(`${config.endpoint}${path}`);
23
+ if (options.query) {
24
+ for (const [key, value] of Object.entries(options.query)) {
25
+ if (value === void 0) continue;
26
+ url.searchParams.set(key, String(value));
27
+ }
28
+ }
29
+ const controller = new AbortController();
30
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
31
+ try {
32
+ const res = await fetch(url, {
33
+ method: options.method ?? "GET",
34
+ headers: { "Content-Type": "application/json", ...authHeaders },
35
+ body: options.body ? JSON.stringify(options.body) : void 0,
36
+ signal: controller.signal
37
+ });
38
+ if (res.status === 204) {
39
+ return null;
40
+ }
41
+ if (!res.ok) {
42
+ const detail = await res.text();
43
+ throw new Error(`${res.status} ${detail || res.statusText}`);
44
+ }
45
+ return await res.json();
46
+ } finally {
47
+ clearTimeout(timeout);
48
+ }
49
+ };
50
+ return {
51
+ async status() {
52
+ return requestJson("/v1/status");
53
+ },
54
+ async command(cmd) {
55
+ return requestJson("/v1/command", { method: "POST", body: cmd });
56
+ },
57
+ async devices() {
58
+ return requestJson("/v1/devices");
59
+ },
60
+ async useDevice(deviceId) {
61
+ return requestJson("/v1/device/use", { method: "POST", body: { deviceId } });
62
+ },
63
+ async authLogin() {
64
+ return requestJson("/v1/auth/spotify/login", { method: "POST" });
65
+ },
66
+ async authLogout() {
67
+ return requestJson("/v1/auth/spotify/logout", { method: "POST" });
68
+ },
69
+ async authImportCookies(cookies) {
70
+ return requestJson("/v1/auth/spotify/import", { method: "POST", body: { cookies } });
71
+ },
72
+ // YouTube auth
73
+ async youtubeAuthLogin() {
74
+ return requestJson("/v1/auth/youtube/login", { method: "POST" });
75
+ },
76
+ async youtubeAuthLogout() {
77
+ return requestJson("/v1/auth/youtube/logout", { method: "POST" });
78
+ },
79
+ async youtubeAuthRefresh() {
80
+ return requestJson("/v1/auth/youtube/refresh", { method: "POST" });
81
+ },
82
+ // Apple auth
83
+ async appleAuthSetUserToken(token) {
84
+ return requestJson("/v1/auth/apple/set-user-token", { method: "POST", body: { token } });
85
+ },
86
+ async appleAuthRefresh() {
87
+ return requestJson("/v1/auth/apple/refresh", { method: "POST" });
88
+ },
89
+ async appleAuthLogout() {
90
+ return requestJson("/v1/auth/apple/logout", { method: "POST" });
91
+ },
92
+ async spotifySearch(query, type, options) {
93
+ return requestJson("/v1/spotify/search", {
94
+ query: { q: query, type, limit: options?.limit, offset: options?.offset }
95
+ });
96
+ },
97
+ async appleSearch(query, type, options) {
98
+ return requestJson("/v1/apple/search", {
99
+ query: { q: query, type, limit: options?.limit, offset: options?.offset }
100
+ });
101
+ },
102
+ async appleLibraryTracks(options) {
103
+ return requestJson("/v1/apple/library/songs", {
104
+ query: { limit: options?.limit }
105
+ });
106
+ },
107
+ async applePlaylists(options) {
108
+ return requestJson("/v1/apple/library/playlists", {
109
+ query: { limit: options?.limit }
110
+ });
111
+ },
112
+ async applePlaylistTracks(playlistId, options) {
113
+ return requestJson(`/v1/apple/playlists/${encodeURIComponent(playlistId)}/tracks`, {
114
+ query: { limit: options?.limit }
115
+ });
116
+ },
117
+ async appleRecommendations(options) {
118
+ return requestJson("/v1/apple/recommendations", {
119
+ query: { limit: options?.limit, seed: options?.seed }
120
+ });
121
+ },
122
+ async youtubeSearch(query, type, options) {
123
+ return requestJson("/v1/youtube/search", {
124
+ query: { q: query, type, limit: options?.limit }
125
+ });
126
+ },
127
+ async youtubeLibraryTracks(options) {
128
+ return requestJson("/v1/youtube/library/tracks", {
129
+ query: { limit: options?.limit }
130
+ });
131
+ },
132
+ async youtubePlaylists(options) {
133
+ return requestJson("/v1/youtube/playlists", {
134
+ query: { limit: options?.limit }
135
+ });
136
+ },
137
+ async youtubePlaylistTracks(playlistId, options) {
138
+ return requestJson(`/v1/youtube/playlists/${encodeURIComponent(playlistId)}/tracks`, {
139
+ query: { limit: options?.limit }
140
+ });
141
+ },
142
+ async youtubeRecommendations(options) {
143
+ return requestJson("/v1/youtube/recommendations", {
144
+ query: { limit: options?.limit, seed: options?.seed }
145
+ });
146
+ },
147
+ async applePlay(payload) {
148
+ return requestJson("/v1/apple/play", { method: "POST", body: payload ?? {} });
149
+ },
150
+ async applePause() {
151
+ return requestJson("/v1/apple/pause", { method: "POST" });
152
+ },
153
+ async appleNext() {
154
+ return requestJson("/v1/apple/next", { method: "POST" });
155
+ },
156
+ async applePrev() {
157
+ return requestJson("/v1/apple/prev", { method: "POST" });
158
+ },
159
+ async appleNowPlaying() {
160
+ return requestJson("/v1/apple/now-playing");
161
+ },
162
+ async youtubePlay(payload) {
163
+ return requestJson("/v1/youtube/play", { method: "POST", body: payload ?? {} });
164
+ },
165
+ async youtubePause() {
166
+ return requestJson("/v1/youtube/pause", { method: "POST" });
167
+ },
168
+ async youtubeNext() {
169
+ return requestJson("/v1/youtube/next", { method: "POST" });
170
+ },
171
+ async youtubePrev() {
172
+ return requestJson("/v1/youtube/prev", { method: "POST" });
173
+ },
174
+ async youtubeNowPlaying() {
175
+ return requestJson("/v1/youtube/now-playing");
176
+ },
177
+ async youtubeQueueAdd(uri) {
178
+ return requestJson("/v1/youtube/queue", { method: "POST", body: { uri } });
179
+ },
180
+ async spotifyPlay(payload) {
181
+ return requestJson("/v1/spotify/play", { method: "POST", body: payload ?? {} });
182
+ },
183
+ async spotifyPlaylists(options) {
184
+ const result = await requestJson("/v1/spotify/playlists", {
185
+ query: { limit: options?.limit, offset: options?.offset }
186
+ });
187
+ return result.items ?? [];
188
+ },
189
+ async spotifyPlaylistTracks(playlistId, options) {
190
+ const result = await requestJson(`/v1/spotify/playlists/${encodeURIComponent(playlistId)}/tracks`, {
191
+ query: { limit: options?.limit, offset: options?.offset }
192
+ });
193
+ return result.items ?? [];
194
+ },
195
+ async spotifyLibraryTracks(options) {
196
+ const result = await requestJson("/v1/spotify/library/tracks", {
197
+ query: { limit: options?.limit, offset: options?.offset }
198
+ });
199
+ return result.items ?? [];
200
+ },
201
+ async spotifyRecommendations(options) {
202
+ return requestJson("/v1/spotify/recommendations", {
203
+ query: { limit: options?.limit, seed: options?.seed }
204
+ });
205
+ },
206
+ async spotifyPause() {
207
+ return requestJson("/v1/spotify/pause", { method: "POST" });
208
+ },
209
+ async spotifyNext() {
210
+ return requestJson("/v1/spotify/next", { method: "POST" });
211
+ },
212
+ async spotifyPrev() {
213
+ return requestJson("/v1/spotify/prev", { method: "POST" });
214
+ },
215
+ async spotifySeek(positionMs) {
216
+ return requestJson("/v1/spotify/seek", { method: "POST", body: { positionMs } });
217
+ },
218
+ async spotifyVolume(volumePercent) {
219
+ return requestJson("/v1/spotify/volume", { method: "POST", body: { volumePercent } });
220
+ },
221
+ async spotifyShuffle(state) {
222
+ return requestJson("/v1/spotify/shuffle", { method: "POST", body: { state } });
223
+ },
224
+ async spotifyRepeat(state) {
225
+ return requestJson("/v1/spotify/repeat", { method: "POST", body: { state } });
226
+ },
227
+ async spotifyNowPlaying() {
228
+ return requestJson("/v1/spotify/now-playing");
229
+ },
230
+ async spotifyQueueAdd(uri) {
231
+ return requestJson("/v1/spotify/queue", { method: "POST", body: { uri } });
232
+ },
233
+ // Smart cross-provider
234
+ async smartSearch(query, options) {
235
+ return requestJson("/v1/smart/search", {
236
+ query: { q: query, limit: options?.limit }
237
+ });
238
+ },
239
+ async smartPlay(options) {
240
+ return requestJson("/v1/smart/play", { method: "POST", body: options });
241
+ },
242
+ // Song recognition
243
+ async recognize(audioBase64) {
244
+ return requestJson("/v1/recognize", { method: "POST", body: { audio: audioBase64 } });
245
+ }
246
+ };
247
+ }
248
+ function getDefaultEndpoint() {
249
+ return process.env.HARMON_ENDPOINT || DEFAULT_ENDPOINT;
250
+ }
251
+ export {
252
+ createCLI,
253
+ getDefaultEndpoint
254
+ };
255
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts"],
4
+ "sourcesContent": ["/**\n * Harmon CLI - Thin client that calls the daemon\n */\n\nimport type { Command } from '@sriinnu/harmon-protocol';\n\nconst DEFAULT_ENDPOINT = 'http://127.0.0.1:17373';\n\nexport interface CLIConfig {\n endpoint: string;\n token?: string;\n timeoutMs?: number;\n}\n\nexport type ProviderName = 'spotify' | 'apple' | 'youtube';\n\nexport interface CookieRecord {\n domain: string;\n name: string;\n path: string;\n value: string;\n expires?: string | null;\n isSecure: boolean;\n isHTTPOnly: boolean;\n}\n\ninterface SpotifyPagedResponse<T> {\n items?: T[];\n}\n\nexport function createCLI(config: CLIConfig) {\n const authHeaders = config.token ? { Authorization: `Bearer ${config.token}` } : {};\n const timeoutMs = config.timeoutMs ?? 10000;\n let insecureWarned = false;\n\n const requestJson = async <T = unknown>(\n path: string,\n options: {\n method?: string;\n body?: Record<string, unknown>;\n query?: Record<string, string | number | undefined>;\n } = {}\n ): Promise<T> => {\n if (!insecureWarned && config.endpoint.startsWith('http://')) {\n const parsed = new URL(config.endpoint);\n const loopback = ['127.0.0.1', '::1', 'localhost'].includes(parsed.hostname);\n if (!loopback && config.token) {\n console.warn(\n 'WARNING: Sending auth token over insecure HTTP to %s. Use HTTPS for remote daemons.',\n parsed.hostname,\n );\n insecureWarned = true;\n }\n }\n\n const url = new URL(`${config.endpoint}${path}`);\n if (options.query) {\n for (const [key, value] of Object.entries(options.query)) {\n if (value === undefined) continue;\n url.searchParams.set(key, String(value));\n }\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const res = await fetch(url, {\n method: options.method ?? 'GET',\n headers: { 'Content-Type': 'application/json', ...authHeaders },\n body: options.body ? JSON.stringify(options.body) : undefined,\n signal: controller.signal,\n });\n if (res.status === 204) {\n return null as T;\n }\n if (!res.ok) {\n const detail = await res.text();\n throw new Error(`${res.status} ${detail || res.statusText}`);\n }\n return (await res.json()) as T;\n } finally {\n clearTimeout(timeout);\n }\n };\n\n return {\n async status() {\n return requestJson('/v1/status');\n },\n async command(cmd: Command) {\n return requestJson('/v1/command', { method: 'POST', body: cmd });\n },\n async devices() {\n return requestJson('/v1/devices');\n },\n async useDevice(deviceId: string) {\n return requestJson('/v1/device/use', { method: 'POST', body: { deviceId } });\n },\n async authLogin() {\n return requestJson('/v1/auth/spotify/login', { method: 'POST' });\n },\n async authLogout() {\n return requestJson('/v1/auth/spotify/logout', { method: 'POST' });\n },\n async authImportCookies(cookies: CookieRecord[]) {\n return requestJson('/v1/auth/spotify/import', { method: 'POST', body: { cookies } });\n },\n\n // YouTube auth\n async youtubeAuthLogin() {\n return requestJson('/v1/auth/youtube/login', { method: 'POST' });\n },\n async youtubeAuthLogout() {\n return requestJson('/v1/auth/youtube/logout', { method: 'POST' });\n },\n async youtubeAuthRefresh() {\n return requestJson('/v1/auth/youtube/refresh', { method: 'POST' });\n },\n\n // Apple auth\n async appleAuthSetUserToken(token: string) {\n return requestJson('/v1/auth/apple/set-user-token', { method: 'POST', body: { token } });\n },\n async appleAuthRefresh() {\n return requestJson('/v1/auth/apple/refresh', { method: 'POST' });\n },\n async appleAuthLogout() {\n return requestJson('/v1/auth/apple/logout', { method: 'POST' });\n },\n\n async spotifySearch(query: string, type: string, options?: { limit?: number; offset?: number }) {\n return requestJson('/v1/spotify/search', {\n query: { q: query, type, limit: options?.limit, offset: options?.offset },\n });\n },\n async appleSearch(query: string, type: string, options?: { limit?: number; offset?: number }) {\n return requestJson('/v1/apple/search', {\n query: { q: query, type, limit: options?.limit, offset: options?.offset },\n });\n },\n async appleLibraryTracks(options?: { limit?: number }) {\n return requestJson('/v1/apple/library/songs', {\n query: { limit: options?.limit },\n });\n },\n async applePlaylists(options?: { limit?: number }) {\n return requestJson('/v1/apple/library/playlists', {\n query: { limit: options?.limit },\n });\n },\n async applePlaylistTracks(playlistId: string, options?: { limit?: number }) {\n return requestJson(`/v1/apple/playlists/${encodeURIComponent(playlistId)}/tracks`, {\n query: { limit: options?.limit },\n });\n },\n async appleRecommendations(options?: { limit?: number; seed?: string }) {\n return requestJson('/v1/apple/recommendations', {\n query: { limit: options?.limit, seed: options?.seed },\n });\n },\n async youtubeSearch(query: string, type: string, options?: { limit?: number }) {\n return requestJson('/v1/youtube/search', {\n query: { q: query, type, limit: options?.limit },\n });\n },\n async youtubeLibraryTracks(options?: { limit?: number }) {\n return requestJson('/v1/youtube/library/tracks', {\n query: { limit: options?.limit },\n });\n },\n async youtubePlaylists(options?: { limit?: number }) {\n return requestJson('/v1/youtube/playlists', {\n query: { limit: options?.limit },\n });\n },\n async youtubePlaylistTracks(playlistId: string, options?: { limit?: number }) {\n return requestJson(`/v1/youtube/playlists/${encodeURIComponent(playlistId)}/tracks`, {\n query: { limit: options?.limit },\n });\n },\n async youtubeRecommendations(options?: { limit?: number; seed?: string }) {\n return requestJson('/v1/youtube/recommendations', {\n query: { limit: options?.limit, seed: options?.seed },\n });\n },\n async applePlay(payload?: { url?: string }) {\n return requestJson('/v1/apple/play', { method: 'POST', body: payload ?? {} });\n },\n async applePause() {\n return requestJson('/v1/apple/pause', { method: 'POST' });\n },\n async appleNext() {\n return requestJson('/v1/apple/next', { method: 'POST' });\n },\n async applePrev() {\n return requestJson('/v1/apple/prev', { method: 'POST' });\n },\n async appleNowPlaying() {\n return requestJson('/v1/apple/now-playing');\n },\n async youtubePlay(payload?: { uri?: string }) {\n return requestJson('/v1/youtube/play', { method: 'POST', body: payload ?? {} });\n },\n async youtubePause() {\n return requestJson('/v1/youtube/pause', { method: 'POST' });\n },\n async youtubeNext() {\n return requestJson('/v1/youtube/next', { method: 'POST' });\n },\n async youtubePrev() {\n return requestJson('/v1/youtube/prev', { method: 'POST' });\n },\n async youtubeNowPlaying() {\n return requestJson('/v1/youtube/now-playing');\n },\n async youtubeQueueAdd(uri: string) {\n return requestJson('/v1/youtube/queue', { method: 'POST', body: { uri } });\n },\n async spotifyPlay(payload?: { uri?: string; contextUri?: string }) {\n return requestJson('/v1/spotify/play', { method: 'POST', body: payload ?? {} });\n },\n async spotifyPlaylists(options?: { limit?: number; offset?: number }) {\n const result = await requestJson<SpotifyPagedResponse<unknown>>('/v1/spotify/playlists', {\n query: { limit: options?.limit, offset: options?.offset },\n });\n return result.items ?? [];\n },\n async spotifyPlaylistTracks(playlistId: string, options?: { limit?: number; offset?: number }) {\n const result = await requestJson<SpotifyPagedResponse<unknown>>(`/v1/spotify/playlists/${encodeURIComponent(playlistId)}/tracks`, {\n query: { limit: options?.limit, offset: options?.offset },\n });\n return result.items ?? [];\n },\n async spotifyLibraryTracks(options?: { limit?: number; offset?: number }) {\n const result = await requestJson<SpotifyPagedResponse<unknown>>('/v1/spotify/library/tracks', {\n query: { limit: options?.limit, offset: options?.offset },\n });\n return result.items ?? [];\n },\n async spotifyRecommendations(options?: { limit?: number; seed?: string }) {\n return requestJson('/v1/spotify/recommendations', {\n query: { limit: options?.limit, seed: options?.seed },\n });\n },\n async spotifyPause() {\n return requestJson('/v1/spotify/pause', { method: 'POST' });\n },\n async spotifyNext() {\n return requestJson('/v1/spotify/next', { method: 'POST' });\n },\n async spotifyPrev() {\n return requestJson('/v1/spotify/prev', { method: 'POST' });\n },\n async spotifySeek(positionMs: number) {\n return requestJson('/v1/spotify/seek', { method: 'POST', body: { positionMs } });\n },\n async spotifyVolume(volumePercent: number) {\n return requestJson('/v1/spotify/volume', { method: 'POST', body: { volumePercent } });\n },\n async spotifyShuffle(state: boolean) {\n return requestJson('/v1/spotify/shuffle', { method: 'POST', body: { state } });\n },\n async spotifyRepeat(state: 'off' | 'track' | 'context') {\n return requestJson('/v1/spotify/repeat', { method: 'POST', body: { state } });\n },\n async spotifyNowPlaying() {\n return requestJson('/v1/spotify/now-playing');\n },\n async spotifyQueueAdd(uri: string) {\n return requestJson('/v1/spotify/queue', { method: 'POST', body: { uri } });\n },\n\n // Smart cross-provider\n async smartSearch(query: string, options?: { limit?: number }) {\n return requestJson('/v1/smart/search', {\n query: { q: query, limit: options?.limit },\n });\n },\n async smartPlay(options: { query?: string; uri?: string; provider?: string }) {\n return requestJson('/v1/smart/play', { method: 'POST', body: options as Record<string, unknown> });\n },\n\n // Song recognition\n async recognize(audioBase64: string) {\n return requestJson('/v1/recognize', { method: 'POST', body: { audio: audioBase64 } });\n },\n };\n}\n\nexport function getDefaultEndpoint(): string {\n return process.env.HARMON_ENDPOINT || DEFAULT_ENDPOINT;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/logo.svg ADDED
@@ -0,0 +1,16 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" fill="none">
2
+ <!-- I am the CLI mark: one operator leaning into a terminal prompt. -->
3
+ <rect width="64" height="64" rx="16" fill="#F3E7D4"/>
4
+ <g stroke="#171513" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
5
+ <rect x="22" y="23" width="28" height="18" rx="5"/>
6
+ <path d="M26 31L30 34.5L26 38"/>
7
+ <path d="M34 38H42"/>
8
+ <path d="M18 49H50" opacity="0.4"/>
9
+ <path d="M14 28L22 31"/>
10
+ <path d="M12 49L14 36"/>
11
+ </g>
12
+ <g fill="#171513">
13
+ <circle cx="14" cy="24" r="5"/>
14
+ <path d="M8 37C10 32 18 32 20 37V43H8V37Z"/>
15
+ </g>
16
+ </svg>
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@sriinnu/harmon",
3
+ "version": "0.1.0",
4
+ "description": "CLI client for Harmon daemon",
5
+ "license": "AGPL-3.0-only",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/sriinnu/harmon.git",
9
+ "directory": "apps/harmon-cli"
10
+ },
11
+ "homepage": "https://github.com/sriinnu/harmon/tree/main/apps/harmon-cli#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/sriinnu/harmon/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "files": [
19
+ "dist/",
20
+ "README.md",
21
+ "SKILL.md",
22
+ "logo.svg"
23
+ ],
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ }
29
+ },
30
+ "bin": {
31
+ "harmon": "./dist/bin/harmon.js"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "dependencies": {
37
+ "commander": "^14.0.2"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^25.0.8",
41
+ "esbuild": "^0.27.4",
42
+ "typescript": "^5.9.3",
43
+ "@sriinnu/harmon-protocol": "0.1.0"
44
+ },
45
+ "scripts": {
46
+ "build": "node --experimental-strip-types ../../scripts/clean-build.ts ./dist ./tsconfig.tsbuildinfo && tsc",
47
+ "build:bundle": "node --experimental-strip-types scripts/bundle.ts",
48
+ "dev": "tsc --watch",
49
+ "lint": "tsc --noEmit",
50
+ "test": "vitest --root ../.. apps/harmon-cli/bin/runtime.test.js apps/harmon-cli/src/index.test.ts",
51
+ "test:run": "vitest --root ../.. run apps/harmon-cli/bin/runtime.test.js apps/harmon-cli/src/index.test.ts"
52
+ }
53
+ }