@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.
- package/README.md +125 -0
- package/assets/brand/moondog-lunar-record/moondog-logo-1x1.png +0 -0
- package/contracts/v1/capability-policy.schema.json +572 -0
- package/contracts/v1/definitions.schema.json +365 -0
- package/contracts/v1/library-track-observation.schema.json +138 -0
- package/contracts/v1/listening-event.schema.json +111 -0
- package/contracts/v1/profile-evidence.schema.json +237 -0
- package/contracts/v1/taste-event.schema.json +258 -0
- package/contracts/v1/tool-invocation.schema.json +318 -0
- package/contracts/v1/tool-receipt.schema.json +1066 -0
- package/contracts/v1/track-ref.schema.json +275 -0
- package/contracts/v2/profile-evidence.schema.json +284 -0
- package/package.json +105 -0
- package/scripts/contract-lib.mjs +361 -0
- package/scripts/contract-semantics.mjs +2630 -0
- package/scripts/demo-studio.mjs +58 -0
- package/scripts/moondog +52 -0
- package/scripts/moondog.mjs +954 -0
- package/src/core/apple-data-migration.mjs +50 -0
- package/src/core/apple-library-source-status.mjs +220 -0
- package/src/core/apple-projection-domain-services.mjs +2356 -0
- package/src/core/capability-catalog.mjs +577 -0
- package/src/core/listening-profile-domain-services.mjs +1010 -0
- package/src/core/lyric-library.mjs +121 -0
- package/src/core/lyric-profile.mjs +50 -0
- package/src/core/lyric-service.mjs +41 -0
- package/src/core/moondog-application.mjs +1827 -0
- package/src/core/state-directory.mjs +19 -0
- package/src/core/synthetic-domain-services.mjs +1485 -0
- package/src/core/uuid-v5.mjs +42 -0
- package/src/demo/fictional-spotify-history.mjs +400 -0
- package/src/demo/moondog-demo.mjs +355 -0
- package/src/demo/moondog-interactive-tasteprint-demo.mjs +293 -0
- package/src/demo/moondog-tasteprint-demo.mjs +631 -0
- package/src/importers/apple-music-library/import-batch.mjs +290 -0
- package/src/importers/apple-music-library/index.mjs +76 -0
- package/src/importers/apple-music-library/normalize.mjs +1126 -0
- package/src/importers/apple-music-library/parse-plist.mjs +357 -0
- package/src/importers/apple-music-library/projection-records.mjs +557 -0
- package/src/importers/apple-music-library/read-batch.mjs +276 -0
- package/src/importers/apple-music-library/sqlite-projection.mjs +1307 -0
- package/src/importers/apple-music-library/stable-ids.mjs +19 -0
- package/src/importers/apple-music-library/track-snapshot.schema.json +136 -0
- package/src/integrations/apple-music/catalog.mjs +770 -0
- package/src/integrations/cross-catalog-artist-identity.mjs +222 -0
- package/src/integrations/listenbrainz/history-file.mjs +560 -0
- package/src/integrations/lyrics/lrclib.mjs +55 -0
- package/src/integrations/open-music-similarity/artist-radio.mjs +806 -0
- package/src/integrations/public-playlists.mjs +104 -0
- package/src/integrations/spotify/account-data-history.mjs +545 -0
- package/src/integrations/spotify/account-data-profile.mjs +918 -0
- package/src/integrations/spotify/authentication.mjs +791 -0
- package/src/integrations/spotify/catalog-resolver.mjs +657 -0
- package/src/integrations/spotify/connection.mjs +125 -0
- package/src/integrations/spotify/credential-store.mjs +26 -0
- package/src/integrations/spotify/extended-streaming-history.mjs +525 -0
- package/src/integrations/spotify/history-archive.mjs +59 -0
- package/src/integrations/spotify/index.mjs +80 -0
- package/src/integrations/spotify/recent-activity.mjs +198 -0
- package/src/integrations/spotify/resolution-cache.mjs +260 -0
- package/src/integrations/spotify/service.mjs +713 -0
- package/src/integrations/spotify/settings.mjs +89 -0
- package/src/integrations/spotify/web-api-client.mjs +761 -0
- package/src/integrations/web/codex-web.mjs +263 -0
- package/src/integrations/youtube-music/takeout.mjs +143 -0
- package/src/memory/local-memory-store.mjs +1705 -0
- package/src/memory/reflection-worker.mjs +87 -0
- package/src/profile/apple-library-import.mjs +28 -0
- package/src/profile/history-import.mjs +163 -0
- package/src/profile/listener-corrections.mjs +217 -0
- package/src/profile/listening-history-store.mjs +2055 -0
- package/src/profile/listening-profile-projection.mjs +2520 -0
- package/src/profile/local-music-data.mjs +812 -0
- package/src/profile/music-import-bundle.mjs +91 -0
- package/src/profile/music-providers.mjs +25 -0
- package/src/profile/spotify-archive-catalog-hints.mjs +210 -0
- package/src/profile/spotify-archive-taste.mjs +482 -0
- package/src/runtime/pi/agent-runtime.mjs +6444 -0
- package/src/runtime/pi/authentication.mjs +104 -0
- package/src/runtime/pi/configured-runtime.mjs +98 -0
- package/src/runtime/pi/memory-agent-runtime.mjs +337 -0
- package/src/runtime/pi/model-catalog.mjs +62 -0
- package/src/runtime/pi/model-transport.mjs +117 -0
- package/src/runtime/pi/persistent-credential-store.mjs +670 -0
- package/src/runtime/pi/provider-registry.mjs +74 -0
- package/src/runtime/pi/runtime-settings.mjs +81 -0
- package/src/surfaces/cli/auth-command.mjs +355 -0
- package/src/surfaces/cli/brand-components.mjs +390 -0
- package/src/surfaces/cli/brand-theme.mjs +231 -0
- package/src/surfaces/cli/catalog-command.mjs +509 -0
- package/src/surfaces/cli/command-completions.mjs +113 -0
- package/src/surfaces/cli/data-command.mjs +214 -0
- package/src/surfaces/cli/format-output.mjs +750 -0
- package/src/surfaces/cli/history-import-view.mjs +445 -0
- package/src/surfaces/cli/import-guides.mjs +9 -0
- package/src/surfaces/cli/listenbrainz-command.mjs +129 -0
- package/src/surfaces/cli/listening-menu.mjs +171 -0
- package/src/surfaces/cli/profile-command.mjs +290 -0
- package/src/surfaces/cli/spotify-command.mjs +849 -0
- package/src/surfaces/cli/taste-profile-model.mjs +306 -0
- package/src/surfaces/cli/taste-profile-view.mjs +272 -0
- package/src/surfaces/cli/terminal-art.mjs +295 -0
- package/src/surfaces/cli/tui.mjs +1714 -0
- package/src/surfaces/cli/web-command.mjs +52 -0
- package/src/surfaces/html/tasteprint-artifact.mjs +96 -0
- package/src/surfaces/html/tasteprint.mjs +3575 -0
- package/src/surfaces/web/showcase.mjs +272 -0
- package/src/surfaces/web/studio-page.mjs +4148 -0
- package/src/surfaces/web/studio.mjs +2288 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { sanitizeTerminalText } from "./format-output.mjs";
|
|
2
|
+
|
|
3
|
+
function inline(value) {
|
|
4
|
+
return sanitizeTerminalText(value).replace(/\s+/gu, " ").replace(/[\\`*_[\]<>]/gu, "\\$&");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function formatWebSources(sources) {
|
|
8
|
+
return [
|
|
9
|
+
"### Public web sources (Codex)",
|
|
10
|
+
"",
|
|
11
|
+
...sources.map((source) => `- [${inline(source.title)}](<${source.url.replace(/[<>]/gu, encodeURIComponent)}>)${source.published_at ? ` - source date: ${inline(source.published_at)}` : ""}${source.retrieved_at ? ` - retrieved: ${source.retrieved_at}` : ""}`),
|
|
12
|
+
].join("\n");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function formatWebResearch(result) {
|
|
16
|
+
return [
|
|
17
|
+
`# Web ${result.kind}: ${result.status}`,
|
|
18
|
+
"",
|
|
19
|
+
`Retrieved: ${result.retrieved_at}${result.from_cache ? " (cached)" : ""}`,
|
|
20
|
+
"A Codex summary of public pages. The dates are when each page was published, which may not be when things happened.",
|
|
21
|
+
"",
|
|
22
|
+
sanitizeTerminalText(result.summary),
|
|
23
|
+
"",
|
|
24
|
+
"## Sources",
|
|
25
|
+
"",
|
|
26
|
+
...result.sources.flatMap((source) => [
|
|
27
|
+
`- [${inline(source.title)}](<${source.url.replace(/[<>]/gu, encodeURIComponent)}>)${source.published_at ? ` - source date: ${inline(source.published_at)}` : " - source date unavailable"}`,
|
|
28
|
+
` ${inline(source.snippet)}`,
|
|
29
|
+
]),
|
|
30
|
+
].join("\n");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function runWebCommand({ args = [], webResearch, json = false, signal } = {}) {
|
|
34
|
+
const [action = "help", ...values] = args;
|
|
35
|
+
if (action === "help" || action === "--help") {
|
|
36
|
+
return "# Moondog public web\n\n- `/web status` - inspect Codex CLI readiness without a model request\n- `/web search <query>` - search reviews, news, interviews or concert information\n- `/web read <https://public-page>` - read and summarize a public page\n\nThese commands use your existing Codex login and usage allowance. Results stay in the TUI. A 5-minute process-local cache avoids repeat requests. Ctrl+C cancels a request. No music-profile import is needed.\n\nShell equivalents: `moondog web status|search|read [arguments] [--json]`.";
|
|
37
|
+
}
|
|
38
|
+
if (!webResearch) throw new Error("Web lookups aren't available right now.");
|
|
39
|
+
if (action === "status" && values.length === 0) {
|
|
40
|
+
const status = webResearch.publicStatus();
|
|
41
|
+
return json ? JSON.stringify(status, null, 2) : `Web lookups through Codex: ${status.state.replaceAll("_", " ")}${status.reason ? ` (${status.reason.replaceAll("_", " ")})` : ""}. Checking this didn't search anything.`;
|
|
42
|
+
}
|
|
43
|
+
if (action === "search" && values.length > 0) {
|
|
44
|
+
const result = await webResearch.search({ query: values.join(" ") }, { signal });
|
|
45
|
+
return json ? JSON.stringify(result, null, 2) : formatWebResearch(result);
|
|
46
|
+
}
|
|
47
|
+
if (action === "read" && values.length === 1) {
|
|
48
|
+
const result = await webResearch.read({ url: values[0] }, { signal });
|
|
49
|
+
return json ? JSON.stringify(result, null, 2) : formatWebResearch(result);
|
|
50
|
+
}
|
|
51
|
+
throw new Error("Usage: /web status | /web search <query> | /web read <public-url>.");
|
|
52
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { chmod, mkdir, open } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { resolveListeningHistoryPath } from "../../profile/listening-history-store.mjs";
|
|
6
|
+
import {
|
|
7
|
+
renderTasteprintCardHtml,
|
|
8
|
+
renderTasteprintHtml,
|
|
9
|
+
} from "./tasteprint.mjs";
|
|
10
|
+
|
|
11
|
+
function timestampSlug(value) {
|
|
12
|
+
return new Date(value).toISOString().replaceAll(/[:.]/gu, "-");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function resolveTasteprintOutputPath({
|
|
16
|
+
outputPath,
|
|
17
|
+
environment = process.env,
|
|
18
|
+
generatedAt = new Date().toISOString(),
|
|
19
|
+
format = "full",
|
|
20
|
+
} = {}) {
|
|
21
|
+
if (!new Set(["full", "card"]).has(format)) {
|
|
22
|
+
throw new TypeError("Tasteprint artifact format must be full or card.");
|
|
23
|
+
}
|
|
24
|
+
if (outputPath !== undefined) {
|
|
25
|
+
if (typeof outputPath !== "string" || !outputPath.trim()) {
|
|
26
|
+
throw new TypeError("Tasteprint output path must be a non-empty string.");
|
|
27
|
+
}
|
|
28
|
+
const resolved = path.resolve(outputPath);
|
|
29
|
+
if (path.extname(resolved).toLocaleLowerCase("en-US") !== ".html") {
|
|
30
|
+
throw new TypeError("Tasteprint output path must end in .html.");
|
|
31
|
+
}
|
|
32
|
+
return { path: resolved, privateDirectory: false };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const privateRoot = path.join(
|
|
36
|
+
path.dirname(resolveListeningHistoryPath(environment)),
|
|
37
|
+
"tasteprints",
|
|
38
|
+
);
|
|
39
|
+
return {
|
|
40
|
+
path: path.join(
|
|
41
|
+
privateRoot,
|
|
42
|
+
`${format === "card" ? "tasteprint-card" : "tasteprint"}-${timestampSlug(generatedAt)}.html`,
|
|
43
|
+
),
|
|
44
|
+
privateDirectory: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function writeTasteprintArtifact(
|
|
49
|
+
profile,
|
|
50
|
+
{
|
|
51
|
+
outputPath,
|
|
52
|
+
environment = process.env,
|
|
53
|
+
generatedAt = new Date().toISOString(),
|
|
54
|
+
format = "full",
|
|
55
|
+
} = {},
|
|
56
|
+
) {
|
|
57
|
+
const destination = resolveTasteprintOutputPath({
|
|
58
|
+
outputPath,
|
|
59
|
+
environment,
|
|
60
|
+
generatedAt,
|
|
61
|
+
format,
|
|
62
|
+
});
|
|
63
|
+
const directory = path.dirname(destination.path);
|
|
64
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
65
|
+
if (destination.privateDirectory) await chmod(directory, 0o700);
|
|
66
|
+
|
|
67
|
+
const html = format === "card"
|
|
68
|
+
? renderTasteprintCardHtml(profile, { generatedAt })
|
|
69
|
+
: renderTasteprintHtml(profile, { generatedAt });
|
|
70
|
+
let handle;
|
|
71
|
+
try {
|
|
72
|
+
handle = await open(destination.path, "wx", 0o600);
|
|
73
|
+
await handle.writeFile(html, "utf8");
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error?.code === "EEXIST") {
|
|
76
|
+
throw new Error(`Tasteprint output already exists: ${destination.path}`);
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
} finally {
|
|
80
|
+
await handle?.close();
|
|
81
|
+
}
|
|
82
|
+
await chmod(destination.path, 0o600);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
artifact_version:
|
|
86
|
+
format === "card"
|
|
87
|
+
? "moondog-tasteprint-card-artifact/1"
|
|
88
|
+
: "moondog-tasteprint-artifact/1",
|
|
89
|
+
artifact_format: format,
|
|
90
|
+
path: destination.path,
|
|
91
|
+
bytes: Buffer.byteLength(html),
|
|
92
|
+
sha256: createHash("sha256").update(html).digest("hex"),
|
|
93
|
+
private: true,
|
|
94
|
+
network_requests: "none",
|
|
95
|
+
};
|
|
96
|
+
}
|