@audiofool/moondog 0.1.0 → 0.2.0-beta.3

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.
Files changed (125) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +156 -68
  3. package/contracts/v1/definitions.schema.json +1 -169
  4. package/package.json +7 -23
  5. package/scripts/contract-lib.mjs +0 -7
  6. package/scripts/contract-semantics.mjs +0 -1904
  7. package/scripts/moondog.mjs +285 -74
  8. package/src/core/apple-library-source-status.mjs +0 -16
  9. package/src/core/apple-projection-domain-services.mjs +253 -53
  10. package/src/core/application-update.mjs +320 -0
  11. package/src/core/capability-catalog.mjs +290 -55
  12. package/src/core/listener-avoids.mjs +101 -0
  13. package/src/core/listening-now.mjs +108 -0
  14. package/src/core/listening-profile-domain-services.mjs +189 -41
  15. package/src/core/listening-upkeep.mjs +31 -0
  16. package/src/core/moondog-application.mjs +2115 -107
  17. package/src/core/playback-preferences.mjs +97 -0
  18. package/src/core/spotify-listening-intent.mjs +124 -0
  19. package/src/core/synthetic-domain-services.mjs +24 -3
  20. package/src/core/track-title.mjs +107 -0
  21. package/src/demo/moondog-demo.mjs +4 -22
  22. package/src/i18n/index.mjs +285 -0
  23. package/src/i18n/messages/de.mjs +135 -0
  24. package/src/i18n/messages/en.mjs +136 -0
  25. package/src/i18n/messages/es.mjs +135 -0
  26. package/src/i18n/messages/fr.mjs +135 -0
  27. package/src/i18n/messages/it.mjs +135 -0
  28. package/src/i18n/messages/ja.mjs +135 -0
  29. package/src/i18n/messages/ko.mjs +135 -0
  30. package/src/i18n/messages/pt.mjs +135 -0
  31. package/src/i18n/messages/zh-Hant.mjs +135 -0
  32. package/src/i18n/messages/zh.mjs +135 -0
  33. package/src/i18n/preferences.mjs +34 -0
  34. package/src/i18n/screen/es.mjs +967 -0
  35. package/src/i18n/screen/ja.mjs +967 -0
  36. package/src/i18n/screen/pt.mjs +967 -0
  37. package/src/i18n/screen/zh.mjs +967 -0
  38. package/src/importers/apple-music-library/index.mjs +0 -16
  39. package/src/importers/apple-music-library/sqlite-projection.mjs +51 -9
  40. package/src/integrations/apple-music/catalog.mjs +166 -14
  41. package/src/integrations/apple-music/music-app.mjs +232 -0
  42. package/src/integrations/apple-music/storefront.mjs +28 -0
  43. package/src/integrations/apple-music/terminal-preview-player.mjs +221 -0
  44. package/src/integrations/lastfm/history.mjs +221 -0
  45. package/src/integrations/lastfm/settings.mjs +75 -0
  46. package/src/integrations/netease/ncm-cli-player.mjs +328 -0
  47. package/src/integrations/open-music-similarity/artist-radio.mjs +26 -18
  48. package/src/integrations/public-playlists.mjs +2 -1
  49. package/src/integrations/spotify/account-data-profile.mjs +14 -6
  50. package/src/integrations/spotify/authentication.mjs +149 -15
  51. package/src/integrations/spotify/catalog-resolver.mjs +9 -6
  52. package/src/integrations/spotify/connection.mjs +2 -1
  53. package/src/integrations/spotify/credential-store.mjs +4 -0
  54. package/src/integrations/spotify/extended-streaming-history.mjs +11 -11
  55. package/src/integrations/spotify/history-archive.mjs +1 -1
  56. package/src/integrations/spotify/library-snapshot.mjs +227 -0
  57. package/src/integrations/spotify/recent-activity.mjs +2 -0
  58. package/src/integrations/spotify/service.mjs +873 -69
  59. package/src/integrations/spotify/web-api-client.mjs +410 -127
  60. package/src/integrations/web/brave-web.mjs +45 -0
  61. package/src/integrations/web/codex-web.mjs +15 -82
  62. package/src/integrations/web/deepseek-web.mjs +81 -0
  63. package/src/integrations/web/web-research.mjs +119 -0
  64. package/src/integrations/web/web-result.mjs +215 -0
  65. package/src/integrations/youtube-music/takeout.mjs +3 -1
  66. package/src/memory/local-memory-store.mjs +542 -57
  67. package/src/profile/history-import.mjs +42 -6
  68. package/src/profile/listener-corrections.mjs +22 -0
  69. package/src/profile/listener-profile-build.mjs +461 -0
  70. package/src/profile/listener-profile-store.mjs +130 -0
  71. package/src/profile/listener-profile-verification.mjs +68 -0
  72. package/src/profile/listener-profile-worker.mjs +179 -0
  73. package/src/profile/listening-history-store.mjs +640 -17
  74. package/src/profile/listening-profile-projection.mjs +454 -86
  75. package/src/profile/local-music-data.mjs +1 -1
  76. package/src/profile/moondog-suggestions.mjs +184 -0
  77. package/src/profile/music-providers.mjs +11 -7
  78. package/src/profile/profile-exploration.mjs +92 -0
  79. package/src/profile/recorded-measures.mjs +24 -0
  80. package/src/profile/spotify-archive-taste.mjs +0 -4
  81. package/src/runtime/pi/agent-runtime.mjs +2536 -692
  82. package/src/runtime/pi/authentication.mjs +34 -1
  83. package/src/runtime/pi/configured-runtime.mjs +22 -6
  84. package/src/runtime/pi/conversation-name.mjs +53 -0
  85. package/src/runtime/pi/memory-agent-runtime.mjs +2 -2
  86. package/src/runtime/pi/model-catalog.mjs +28 -5
  87. package/src/runtime/pi/model-settings.mjs +202 -0
  88. package/src/runtime/pi/model-transport.mjs +10 -0
  89. package/src/runtime/pi/persistent-credential-store.mjs +67 -31
  90. package/src/runtime/pi/profile-build-runtime.mjs +161 -0
  91. package/src/runtime/pi/public-model-catalog.mjs +288 -0
  92. package/src/runtime/pi/runtime-settings.mjs +10 -2
  93. package/src/runtime/pi/skills/listener-profile/SKILL.md +48 -0
  94. package/src/runtime/pi/tool-trace.mjs +37 -0
  95. package/src/surfaces/cli/auth-command.mjs +12 -4
  96. package/src/surfaces/cli/brand-components.mjs +310 -132
  97. package/src/surfaces/cli/brand-theme.mjs +51 -28
  98. package/src/surfaces/cli/command-completions.mjs +58 -42
  99. package/src/surfaces/cli/format-output.mjs +199 -388
  100. package/src/surfaces/cli/history-import-view.mjs +292 -139
  101. package/src/surfaces/cli/import-guides.mjs +2 -0
  102. package/src/surfaces/cli/listener-profile-command.mjs +92 -0
  103. package/src/surfaces/cli/listening-menu.mjs +7 -4
  104. package/src/surfaces/cli/profile-command.mjs +30 -21
  105. package/src/surfaces/cli/seasons-chart.mjs +87 -0
  106. package/src/surfaces/cli/spotify-command.mjs +54 -6
  107. package/src/surfaces/cli/taste-profile-model.mjs +78 -79
  108. package/src/surfaces/cli/taste-profile-view.mjs +33 -26
  109. package/src/surfaces/cli/terminal-art.mjs +140 -35
  110. package/src/surfaces/cli/tui.mjs +1066 -357
  111. package/src/surfaces/cli/update-command.mjs +32 -0
  112. package/src/surfaces/cli/web-command.mjs +53 -14
  113. package/src/surfaces/html/tasteprint.mjs +43 -70
  114. package/contracts/v1/capability-policy.schema.json +0 -572
  115. package/contracts/v1/profile-evidence.schema.json +0 -237
  116. package/contracts/v1/tool-invocation.schema.json +0 -318
  117. package/contracts/v1/tool-receipt.schema.json +0 -1066
  118. package/scripts/demo-studio.mjs +0 -58
  119. package/src/core/apple-data-migration.mjs +0 -50
  120. package/src/demo/moondog-interactive-tasteprint-demo.mjs +0 -293
  121. package/src/demo/moondog-tasteprint-demo.mjs +0 -631
  122. package/src/integrations/spotify/index.mjs +0 -80
  123. package/src/surfaces/web/showcase.mjs +0 -272
  124. package/src/surfaces/web/studio-page.mjs +0 -4148
  125. package/src/surfaces/web/studio.mjs +0 -2288
@@ -1,80 +0,0 @@
1
- export {
2
- SPOTIFY_WEB_API_BASE_URL,
3
- SPOTIFY_WEB_API_LIMITS,
4
- SpotifyWebApiError,
5
- createSpotifyWebApiClient,
6
- } from "./web-api-client.mjs";
7
-
8
- export {
9
- SPOTIFY_SERVICE_LIMITS,
10
- SpotifyServiceError,
11
- createSpotifyService,
12
- } from "./service.mjs";
13
-
14
- export {
15
- SPOTIFY_DEFAULT_REDIRECT_URI,
16
- SPOTIFY_DEFAULT_SCOPES,
17
- SpotifyAuthenticationError,
18
- createSpotifyAuthentication,
19
- } from "./authentication.mjs";
20
-
21
- export { createSpotifyCredentialStore } from "./credential-store.mjs";
22
- export { openSpotifyConnection } from "./connection.mjs";
23
-
24
- export {
25
- SPOTIFY_CATALOG_RESOLVER_LIMITS,
26
- SpotifyCatalogResolverError,
27
- createSpotifyCatalogResolver,
28
- } from "./catalog-resolver.mjs";
29
-
30
- export {
31
- SPOTIFY_RESOLUTION_CACHE_KEY_VERSION,
32
- SPOTIFY_RESOLUTION_CACHE_SCHEMA_VERSION,
33
- createInMemorySpotifyResolutionCache,
34
- defaultSpotifyResolutionCachePath,
35
- openSpotifyResolutionCache,
36
- resolveSpotifyResolutionCachePath,
37
- spotifyResolutionCacheKey,
38
- } from "./resolution-cache.mjs";
39
-
40
- export {
41
- SPOTIFY_LISTENING_EVENT_NAMESPACE,
42
- SPOTIFY_RECENT_ACTIVITY_SOURCE,
43
- SPOTIFY_TRACK_NAMESPACE,
44
- projectSpotifyRecentActivity,
45
- } from "./recent-activity.mjs";
46
-
47
- export {
48
- SPOTIFY_ACCOUNT_DATA_BATCH_NAMESPACE,
49
- SPOTIFY_ACCOUNT_DATA_EVENT_NAMESPACE,
50
- SPOTIFY_ACCOUNT_DATA_HISTORY_FORMAT,
51
- SPOTIFY_ACCOUNT_DATA_HISTORY_LIMITS,
52
- SPOTIFY_ACCOUNT_DATA_HISTORY_SCOPE,
53
- SPOTIFY_ACCOUNT_DATA_HISTORY_SOURCE,
54
- SPOTIFY_ACCOUNT_DATA_TRACK_NAMESPACE,
55
- SpotifyAccountDataHistoryError,
56
- projectSpotifyAccountDataHistory,
57
- readSpotifyAccountDataHistoryArchive,
58
- spotifyAccountDataEventIdentity,
59
- spotifyAccountDataTrackIdentity,
60
- } from "./account-data-history.mjs";
61
-
62
- export {
63
- SPOTIFY_EXTENDED_HISTORY_BATCH_NAMESPACE,
64
- SPOTIFY_EXTENDED_HISTORY_EVENT_NAMESPACE,
65
- SPOTIFY_EXTENDED_HISTORY_FORMAT,
66
- SPOTIFY_EXTENDED_HISTORY_LIMITS,
67
- SPOTIFY_EXTENDED_HISTORY_SCOPE,
68
- SPOTIFY_EXTENDED_HISTORY_SOURCE,
69
- SpotifyExtendedHistoryError,
70
- projectSpotifyExtendedStreamingHistory,
71
- readSpotifyExtendedStreamingHistoryArchive,
72
- } from "./extended-streaming-history.mjs";
73
-
74
- export { readSpotifyHistoryArchive } from "./history-archive.mjs";
75
-
76
- export {
77
- readSpotifyConfiguration,
78
- resolveSpotifySettingsFile,
79
- writeSpotifyConfiguration,
80
- } from "./settings.mjs";
@@ -1,272 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { readFile } from "node:fs/promises";
3
- import { createServer } from "node:http";
4
- import path from "node:path";
5
- import { fileURLToPath } from "node:url";
6
-
7
- export const MOONDOG_SHOWCASE_HOST = "127.0.0.1";
8
-
9
- const repositoryRoot = path.resolve(
10
- path.dirname(fileURLToPath(import.meta.url)),
11
- "../../..",
12
- );
13
-
14
- const showcaseContentSecurityPolicy = [
15
- "default-src 'none'",
16
- "base-uri 'none'",
17
- "form-action 'none'",
18
- "frame-ancestors 'none'",
19
- "img-src 'self'",
20
- "style-src 'unsafe-inline'",
21
- ].join("; ");
22
-
23
- const artifactContentSecurityPolicy = [
24
- "default-src 'none'",
25
- "base-uri 'none'",
26
- "form-action 'none'",
27
- "frame-ancestors 'none'",
28
- "img-src data:",
29
- "style-src 'unsafe-inline'",
30
- ].join("; ");
31
-
32
- export const MOONDOG_SHOWCASE_ROUTES = Object.freeze([
33
- {
34
- pathnames: ["/", "/showcase", "/showcase/", "/index.html"],
35
- relativePath: "showcase/index.html",
36
- contentType: "text/html; charset=utf-8",
37
- contentSecurityPolicy: showcaseContentSecurityPolicy,
38
- },
39
- {
40
- pathnames: [
41
- "/assets/brand/moondog-lunar-record/moondog-logo-1x1.png",
42
- ],
43
- relativePath:
44
- "assets/brand/moondog-lunar-record/moondog-logo-1x1.png",
45
- contentType: "image/png",
46
- contentSecurityPolicy: "default-src 'none'; frame-ancestors 'none'",
47
- },
48
- {
49
- pathnames: ["/assets/demo/moondog-time-machine-preview.png"],
50
- relativePath: "assets/demo/moondog-time-machine-preview.png",
51
- contentType: "image/png",
52
- contentSecurityPolicy: "default-src 'none'; frame-ancestors 'none'",
53
- },
54
- {
55
- pathnames: ["/assets/demo/moondog-offline-demo.gif"],
56
- relativePath: "assets/demo/moondog-offline-demo.gif",
57
- contentType: "image/gif",
58
- contentSecurityPolicy: "default-src 'none'; frame-ancestors 'none'",
59
- },
60
- {
61
- pathnames: ["/assets/demo/moondog-tasteprint-card-preview.png"],
62
- relativePath: "assets/demo/moondog-tasteprint-card-preview.png",
63
- contentType: "image/png",
64
- contentSecurityPolicy: "default-src 'none'; frame-ancestors 'none'",
65
- },
66
- {
67
- pathnames: ["/assets/demo/moondog-tasteprint-demo.html"],
68
- relativePath: "assets/demo/moondog-tasteprint-demo.html",
69
- contentType: "text/html; charset=utf-8",
70
- contentSecurityPolicy: artifactContentSecurityPolicy,
71
- },
72
- {
73
- pathnames: ["/assets/demo/moondog-tasteprint-card-demo.html"],
74
- relativePath: "assets/demo/moondog-tasteprint-card-demo.html",
75
- contentType: "text/html; charset=utf-8",
76
- contentSecurityPolicy: artifactContentSecurityPolicy,
77
- },
78
- ]);
79
-
80
- function baseHeaders(contentType, contentSecurityPolicy) {
81
- return {
82
- "Cache-Control": "no-store, max-age=0",
83
- "Content-Security-Policy": contentSecurityPolicy,
84
- "Content-Type": contentType,
85
- "Cross-Origin-Opener-Policy": "same-origin",
86
- "Cross-Origin-Resource-Policy": "same-origin",
87
- "Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=()",
88
- "Referrer-Policy": "no-referrer",
89
- "X-Content-Type-Options": "nosniff",
90
- "X-Frame-Options": "DENY",
91
- };
92
- }
93
-
94
- function send(response, statusCode, body, headers, { headOnly = false } = {}) {
95
- const buffer = Buffer.isBuffer(body) ? body : Buffer.from(String(body));
96
- response.writeHead(statusCode, {
97
- ...headers,
98
- "Content-Length": buffer.length,
99
- });
100
- response.end(headOnly ? undefined : buffer);
101
- }
102
-
103
- function plainTextHeaders() {
104
- return baseHeaders(
105
- "text/plain; charset=utf-8",
106
- "default-src 'none'; frame-ancestors 'none'",
107
- );
108
- }
109
-
110
- async function loadRoutes(root) {
111
- const routeMap = new Map();
112
- for (const route of MOONDOG_SHOWCASE_ROUTES) {
113
- const body = await readFile(path.join(root, route.relativePath));
114
- for (const pathname of route.pathnames) {
115
- routeMap.set(pathname, {
116
- body,
117
- headers: baseHeaders(
118
- route.contentType,
119
- route.contentSecurityPolicy,
120
- ),
121
- });
122
- }
123
- }
124
- return routeMap;
125
- }
126
-
127
- async function openBrowser(url) {
128
- const [command, args] = process.platform === "darwin"
129
- ? ["open", [url]]
130
- : process.platform === "win32"
131
- ? ["rundll32", ["url.dll,FileProtocolHandler", url]]
132
- : ["xdg-open", [url]];
133
- await new Promise((resolve, reject) => {
134
- const child = spawn(command, args, {
135
- detached: true,
136
- stdio: "ignore",
137
- windowsHide: true,
138
- });
139
- child.once("error", reject);
140
- child.once("spawn", () => {
141
- child.unref();
142
- resolve();
143
- });
144
- });
145
- }
146
-
147
- export async function startMoondogShowcase({
148
- host = MOONDOG_SHOWCASE_HOST,
149
- port = 0,
150
- root = repositoryRoot,
151
- } = {}) {
152
- if (host !== MOONDOG_SHOWCASE_HOST) {
153
- throw new TypeError("The Moondog showcase may only listen on 127.0.0.1.");
154
- }
155
- if (!Number.isInteger(port) || port < 0 || port > 65_535) {
156
- throw new TypeError("The Moondog showcase port is invalid.");
157
- }
158
- if (typeof root !== "string" || !path.isAbsolute(root)) {
159
- throw new TypeError("The Moondog showcase root must be absolute.");
160
- }
161
-
162
- const routes = await loadRoutes(root);
163
- const server = createServer((request, response) => {
164
- const method = request.method ?? "GET";
165
- if (method !== "GET" && method !== "HEAD") {
166
- request.resume();
167
- send(response, 405, "Method not allowed.\n", {
168
- ...plainTextHeaders(),
169
- Allow: "GET, HEAD",
170
- });
171
- return;
172
- }
173
-
174
- let pathname;
175
- try {
176
- pathname = new URL(request.url ?? "/", "http://127.0.0.1").pathname;
177
- } catch {
178
- send(response, 400, "Bad request.\n", plainTextHeaders(), {
179
- headOnly: method === "HEAD",
180
- });
181
- return;
182
- }
183
- const route = routes.get(pathname);
184
- if (!route) {
185
- send(response, 404, "Not found.\n", plainTextHeaders(), {
186
- headOnly: method === "HEAD",
187
- });
188
- return;
189
- }
190
- send(response, 200, route.body, route.headers, {
191
- headOnly: method === "HEAD",
192
- });
193
- });
194
-
195
- server.requestTimeout = 15_000;
196
- server.headersTimeout = 10_000;
197
- server.maxRequestsPerSocket = 50;
198
- server.on("clientError", (error, socket) => {
199
- if (error?.code !== "ECONNRESET" && socket.writable && !socket.destroyed) {
200
- socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
201
- }
202
- });
203
-
204
- await new Promise((resolve, reject) => {
205
- const onError = (error) => {
206
- server.off("listening", onListening);
207
- reject(error);
208
- };
209
- const onListening = () => {
210
- server.off("error", onError);
211
- resolve();
212
- };
213
- server.once("error", onError);
214
- server.once("listening", onListening);
215
- server.listen(port, host);
216
- });
217
-
218
- const address = server.address();
219
- if (!address || typeof address === "string") {
220
- await new Promise((resolve) => server.close(resolve));
221
- throw new Error("The Moondog showcase did not receive a loopback address.");
222
- }
223
- const url = `http://${host}:${address.port}/`;
224
- let closed = false;
225
-
226
- return {
227
- url,
228
- async close() {
229
- if (closed) return;
230
- closed = true;
231
- await new Promise((resolve, reject) => {
232
- server.close((error) => (error ? reject(error) : resolve()));
233
- });
234
- },
235
- };
236
- }
237
-
238
- export async function runMoondogShowcase({
239
- environment = process.env,
240
- stdout = process.stdout,
241
- stderr = process.stderr,
242
- launchBrowser = openBrowser,
243
- } = {}) {
244
- const showcase = await startMoondogShowcase();
245
- stdout.write(
246
- `Moondog showcase is ready at ${showcase.url}\nPublic-safe product tour with fictional personal-profile data and one dated public-catalog proof. No private listening history, analytics, or network resources. Press Ctrl+C to stop.\n`,
247
- );
248
- if (environment.MOONDOG_SHOWCASE_NO_OPEN !== "1") {
249
- try {
250
- await launchBrowser(showcase.url);
251
- } catch {
252
- stderr.write(
253
- "Moondog could not open a browser automatically. Use the local URL above.\n",
254
- );
255
- }
256
- }
257
-
258
- let stop;
259
- const stopped = new Promise((resolve) => {
260
- stop = resolve;
261
- });
262
- const onSignal = () => stop();
263
- process.once("SIGINT", onSignal);
264
- process.once("SIGTERM", onSignal);
265
- try {
266
- await stopped;
267
- } finally {
268
- process.off("SIGINT", onSignal);
269
- process.off("SIGTERM", onSignal);
270
- await showcase.close();
271
- }
272
- }