@audiofool/moondog 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.
Files changed (109) hide show
  1. package/README.md +125 -0
  2. package/assets/brand/moondog-lunar-record/moondog-logo-1x1.png +0 -0
  3. package/contracts/v1/capability-policy.schema.json +572 -0
  4. package/contracts/v1/definitions.schema.json +365 -0
  5. package/contracts/v1/library-track-observation.schema.json +138 -0
  6. package/contracts/v1/listening-event.schema.json +111 -0
  7. package/contracts/v1/profile-evidence.schema.json +237 -0
  8. package/contracts/v1/taste-event.schema.json +258 -0
  9. package/contracts/v1/tool-invocation.schema.json +318 -0
  10. package/contracts/v1/tool-receipt.schema.json +1066 -0
  11. package/contracts/v1/track-ref.schema.json +275 -0
  12. package/contracts/v2/profile-evidence.schema.json +284 -0
  13. package/package.json +105 -0
  14. package/scripts/contract-lib.mjs +361 -0
  15. package/scripts/contract-semantics.mjs +2630 -0
  16. package/scripts/demo-studio.mjs +58 -0
  17. package/scripts/moondog +52 -0
  18. package/scripts/moondog.mjs +954 -0
  19. package/src/core/apple-data-migration.mjs +50 -0
  20. package/src/core/apple-library-source-status.mjs +220 -0
  21. package/src/core/apple-projection-domain-services.mjs +2356 -0
  22. package/src/core/capability-catalog.mjs +577 -0
  23. package/src/core/listening-profile-domain-services.mjs +1010 -0
  24. package/src/core/lyric-library.mjs +121 -0
  25. package/src/core/lyric-profile.mjs +50 -0
  26. package/src/core/lyric-service.mjs +41 -0
  27. package/src/core/moondog-application.mjs +1827 -0
  28. package/src/core/state-directory.mjs +19 -0
  29. package/src/core/synthetic-domain-services.mjs +1485 -0
  30. package/src/core/uuid-v5.mjs +42 -0
  31. package/src/demo/fictional-spotify-history.mjs +400 -0
  32. package/src/demo/moondog-demo.mjs +355 -0
  33. package/src/demo/moondog-interactive-tasteprint-demo.mjs +293 -0
  34. package/src/demo/moondog-tasteprint-demo.mjs +631 -0
  35. package/src/importers/apple-music-library/import-batch.mjs +290 -0
  36. package/src/importers/apple-music-library/index.mjs +76 -0
  37. package/src/importers/apple-music-library/normalize.mjs +1126 -0
  38. package/src/importers/apple-music-library/parse-plist.mjs +357 -0
  39. package/src/importers/apple-music-library/projection-records.mjs +557 -0
  40. package/src/importers/apple-music-library/read-batch.mjs +276 -0
  41. package/src/importers/apple-music-library/sqlite-projection.mjs +1307 -0
  42. package/src/importers/apple-music-library/stable-ids.mjs +19 -0
  43. package/src/importers/apple-music-library/track-snapshot.schema.json +136 -0
  44. package/src/integrations/apple-music/catalog.mjs +770 -0
  45. package/src/integrations/cross-catalog-artist-identity.mjs +222 -0
  46. package/src/integrations/listenbrainz/history-file.mjs +560 -0
  47. package/src/integrations/lyrics/lrclib.mjs +55 -0
  48. package/src/integrations/open-music-similarity/artist-radio.mjs +806 -0
  49. package/src/integrations/public-playlists.mjs +104 -0
  50. package/src/integrations/spotify/account-data-history.mjs +545 -0
  51. package/src/integrations/spotify/account-data-profile.mjs +918 -0
  52. package/src/integrations/spotify/authentication.mjs +791 -0
  53. package/src/integrations/spotify/catalog-resolver.mjs +657 -0
  54. package/src/integrations/spotify/connection.mjs +125 -0
  55. package/src/integrations/spotify/credential-store.mjs +26 -0
  56. package/src/integrations/spotify/extended-streaming-history.mjs +525 -0
  57. package/src/integrations/spotify/history-archive.mjs +59 -0
  58. package/src/integrations/spotify/index.mjs +80 -0
  59. package/src/integrations/spotify/recent-activity.mjs +198 -0
  60. package/src/integrations/spotify/resolution-cache.mjs +260 -0
  61. package/src/integrations/spotify/service.mjs +713 -0
  62. package/src/integrations/spotify/settings.mjs +89 -0
  63. package/src/integrations/spotify/web-api-client.mjs +761 -0
  64. package/src/integrations/web/codex-web.mjs +263 -0
  65. package/src/integrations/youtube-music/takeout.mjs +143 -0
  66. package/src/memory/local-memory-store.mjs +1705 -0
  67. package/src/memory/reflection-worker.mjs +87 -0
  68. package/src/profile/apple-library-import.mjs +28 -0
  69. package/src/profile/history-import.mjs +163 -0
  70. package/src/profile/listener-corrections.mjs +217 -0
  71. package/src/profile/listening-history-store.mjs +2055 -0
  72. package/src/profile/listening-profile-projection.mjs +2520 -0
  73. package/src/profile/local-music-data.mjs +812 -0
  74. package/src/profile/music-import-bundle.mjs +91 -0
  75. package/src/profile/music-providers.mjs +25 -0
  76. package/src/profile/spotify-archive-catalog-hints.mjs +210 -0
  77. package/src/profile/spotify-archive-taste.mjs +482 -0
  78. package/src/runtime/pi/agent-runtime.mjs +6444 -0
  79. package/src/runtime/pi/authentication.mjs +104 -0
  80. package/src/runtime/pi/configured-runtime.mjs +98 -0
  81. package/src/runtime/pi/memory-agent-runtime.mjs +337 -0
  82. package/src/runtime/pi/model-catalog.mjs +62 -0
  83. package/src/runtime/pi/model-transport.mjs +117 -0
  84. package/src/runtime/pi/persistent-credential-store.mjs +670 -0
  85. package/src/runtime/pi/provider-registry.mjs +74 -0
  86. package/src/runtime/pi/runtime-settings.mjs +81 -0
  87. package/src/surfaces/cli/auth-command.mjs +355 -0
  88. package/src/surfaces/cli/brand-components.mjs +390 -0
  89. package/src/surfaces/cli/brand-theme.mjs +231 -0
  90. package/src/surfaces/cli/catalog-command.mjs +509 -0
  91. package/src/surfaces/cli/command-completions.mjs +113 -0
  92. package/src/surfaces/cli/data-command.mjs +214 -0
  93. package/src/surfaces/cli/format-output.mjs +750 -0
  94. package/src/surfaces/cli/history-import-view.mjs +445 -0
  95. package/src/surfaces/cli/import-guides.mjs +9 -0
  96. package/src/surfaces/cli/listenbrainz-command.mjs +129 -0
  97. package/src/surfaces/cli/listening-menu.mjs +171 -0
  98. package/src/surfaces/cli/profile-command.mjs +290 -0
  99. package/src/surfaces/cli/spotify-command.mjs +849 -0
  100. package/src/surfaces/cli/taste-profile-model.mjs +306 -0
  101. package/src/surfaces/cli/taste-profile-view.mjs +272 -0
  102. package/src/surfaces/cli/terminal-art.mjs +295 -0
  103. package/src/surfaces/cli/tui.mjs +1714 -0
  104. package/src/surfaces/cli/web-command.mjs +52 -0
  105. package/src/surfaces/html/tasteprint-artifact.mjs +96 -0
  106. package/src/surfaces/html/tasteprint.mjs +3575 -0
  107. package/src/surfaces/web/showcase.mjs +272 -0
  108. package/src/surfaces/web/studio-page.mjs +4148 -0
  109. package/src/surfaces/web/studio.mjs +2288 -0
@@ -0,0 +1,117 @@
1
+ import { setTimeout as delay } from "node:timers/promises";
2
+
3
+ const maximumRetries = 2;
4
+ const retryableCodes = new Set([
5
+ "ECONNRESET", "ECONNREFUSED", "EPIPE", "ETIMEDOUT", "EAI_AGAIN",
6
+ "ENOTFOUND", "ENETUNREACH", "EHOSTUNREACH",
7
+ "UND_ERR_CONNECT_TIMEOUT", "UND_ERR_HEADERS_TIMEOUT", "UND_ERR_SOCKET",
8
+ ]);
9
+ const certificateCodes = new Set([
10
+ "CERT_HAS_EXPIRED", "DEPTH_ZERO_SELF_SIGNED_CERT", "SELF_SIGNED_CERT_IN_CHAIN",
11
+ "UNABLE_TO_VERIFY_LEAF_SIGNATURE", "ERR_TLS_CERT_ALTNAME_INVALID",
12
+ ]);
13
+ const invalidRequestCodes = new Set([
14
+ "ERR_INVALID_URL", "ERR_INVALID_ARG_TYPE", "ERR_INVALID_ARG_VALUE",
15
+ "ERR_INVALID_STATE", "ERR_HTTP_INVALID_HEADER_VALUE", "ERR_INVALID_HTTP_TOKEN",
16
+ "UND_ERR_INVALID_ARG",
17
+ ]);
18
+ const publicCodes = new Set([...retryableCodes, ...certificateCodes]);
19
+ const publicCodePattern = new RegExp(`\\b(${[...publicCodes].join("|")})\\b`, "u");
20
+ const connectionMessagePattern = /^(?:fetch failed|failed to fetch|network (?:error|request failed)|connection error)\.?$|\b(?:socket hang up|other side closed|connection (?:reset|refused|lost)|getaddrinfo (?:ENOTFOUND|EAI_AGAIN)|(?:connect|connection|response headers|request) timed? out|websocket (?:closed|error))\b|^terminated$/iu;
21
+ const credentialMessagePattern = /(?:access|refresh|id)[_ -]?token|api[_ -]?key|authentication|unauthorized|forbidden|quota|billing/iu;
22
+
23
+ function errorChain(value) {
24
+ const pending = [value];
25
+ const seen = new Set();
26
+ const values = [];
27
+ while (pending.length && values.length < 8) {
28
+ const current = pending.shift();
29
+ if (!current || seen.has(current)) continue;
30
+ seen.add(current);
31
+ values.push(current);
32
+ if (current.cause) pending.push(current.cause);
33
+ if (Array.isArray(current.errors)) pending.push(...current.errors.slice(0, 8));
34
+ }
35
+ return values;
36
+ }
37
+
38
+ function connectionFailure(value) {
39
+ const chain = errorChain(value);
40
+ if (chain.some((error) => invalidRequestCodes.has(error.code) ||
41
+ Number.isInteger(error.status) || Number.isInteger(error.statusCode) ||
42
+ error.name === "AbortError")) return null;
43
+ const messages = chain.map((error) => typeof error === "string" ? error : error.message ?? "");
44
+ if (messages.some((message) => credentialMessagePattern.test(message))) return null;
45
+ const code = chain.map((error) => error.code).find((candidate) => publicCodes.has(candidate)) ??
46
+ messages.map((message) => message.match(publicCodePattern)?.[1]).find(Boolean);
47
+ if (code) return { code, retryable: retryableCodes.has(code) };
48
+ return messages.some((message) => connectionMessagePattern.test(message))
49
+ ? { code: undefined, retryable: true }
50
+ : null;
51
+ }
52
+
53
+ export function isModelConnectionFailure(value) {
54
+ return connectionFailure(value) !== null;
55
+ }
56
+
57
+ export function createModelConnectionError({ provider, cause, toolsExecuted = false }) {
58
+ const label = ({
59
+ "openai-codex": "OpenAI Codex", openai: "OpenAI", anthropic: "Anthropic",
60
+ deepseek: "DeepSeek", moonshotai: "Moonshot AI", "moonshotai-cn": "Moonshot AI China",
61
+ xai: "xAI", zai: "Z.AI",
62
+ })[provider] ?? (/^[a-z0-9][a-z0-9._-]{0,63}$/iu.test(provider ?? "") ? provider : "The model provider");
63
+ const code = connectionFailure(cause)?.code;
64
+ const error = new Error(
65
+ `${label} could not complete the model request${code ? ` (${code})` : ""}. Check your connection or proxy settings.`,
66
+ { cause },
67
+ );
68
+ error.code = "model_connection_failed";
69
+ error.toolsExecuted = toolsExecuted === true;
70
+ return error;
71
+ }
72
+
73
+ function replayableBody(input, init) {
74
+ // JSON strings and Codex's compressed buffers can be sent again unchanged.
75
+ // A caller-owned Request body or stream may already be consumed after failure.
76
+ if (input instanceof Request && input.body !== null) return false;
77
+ const body = init?.body;
78
+ return body == null || typeof body === "string" || body instanceof ArrayBuffer ||
79
+ ArrayBuffer.isView(body) || body instanceof URLSearchParams || body instanceof Blob;
80
+ }
81
+
82
+ async function retryDelay(milliseconds, signal) {
83
+ await delay(milliseconds, undefined, { signal });
84
+ }
85
+
86
+ /** Recover only rejected model HTTP fetches, before a Response or stream exists. */
87
+ export function createModelFetch({
88
+ provider,
89
+ fetchImpl = globalThis.fetch,
90
+ wait = retryDelay,
91
+ onRetry,
92
+ onFailure,
93
+ } = {}) {
94
+ return async (input, init) => {
95
+ const signal = init?.signal ?? (input instanceof Request ? input.signal : undefined);
96
+ const canReplay = replayableBody(input, init);
97
+ for (let attempt = 0; ; attempt += 1) {
98
+ signal?.throwIfAborted();
99
+ try {
100
+ // Status codes and response-body errors remain Pi's responsibility.
101
+ return await fetchImpl(input, init);
102
+ } catch (cause) {
103
+ if (signal?.aborted || cause?.name === "AbortError") throw cause;
104
+ const failure = connectionFailure(cause);
105
+ if (!failure) throw cause;
106
+ if (canReplay && failure.retryable && attempt < maximumRetries) {
107
+ onRetry?.({ attempt: attempt + 1, maxRetries: maximumRetries });
108
+ await wait(250 * 2 ** attempt, signal);
109
+ continue;
110
+ }
111
+ const error = createModelConnectionError({ provider, cause });
112
+ onFailure?.(error);
113
+ throw error;
114
+ }
115
+ }
116
+ };
117
+ }