@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,15 +1,33 @@
1
+ import { N_ } from "../i18n/index.mjs";
2
+
3
+ // Tool labels are marked English; screens translate them when shown.
1
4
  const catalog = [
5
+ {
6
+ id: "playback.service", version: "1", state: "enabled", effect: "write_local",
7
+ description: "Inspect or change the current music service through the same saved selection as the host's service picker.",
8
+ agent_tool: { name: "moondog_playback_service", label: N_("Choose a music service") },
9
+ },
10
+ {
11
+ id: "spotify.discovery.search", version: "1", state: "enabled", effect: "read_external", requires_spotify: true,
12
+ description: "Verify open-world music hypotheses with bounded Spotify queries, retaining a shared candidate pool.",
13
+ agent_tool: { name: "moondog_spotify_discover", label: N_("Discover verified Spotify tracks") },
14
+ },
15
+ {
16
+ id: "spotify.queue.batch", version: "1", state: "enabled", effect: "write_external", requires_spotify: true,
17
+ description: "Queue up to twelve verified tracks for an explicit listener request with Avoid, deduplication and partial receipts.",
18
+ agent_tool: { name: "moondog_spotify_queue_batch", label: N_("Queue the verified selection") },
19
+ },
2
20
  {
3
21
  id: "web.search", version: "1", state: "enabled", effect: "read_external",
4
- description: "Search public music reviews, news, interviews and event information through the local Codex CLI.",
5
- requires_web_research: true,
6
- agent_tool: { name: "moondog_web_search", label: "Search the public web" },
22
+ description: "Search public music reviews, news, interviews and event information through {provider}.",
23
+ requires_web: "search",
24
+ agent_tool: { name: "moondog_web_search", label: N_("Search the public web") },
7
25
  },
8
26
  {
9
27
  id: "web.read", version: "1", state: "enabled", effect: "read_external",
10
- description: "Read and summarize a public web page with dated source attribution through the local Codex CLI.",
11
- requires_web_research: true,
12
- agent_tool: { name: "moondog_web_read", label: "Read a public web page" },
28
+ description: "Read a public web page with dated source attribution through {provider}.",
29
+ requires_web: "read",
30
+ agent_tool: { name: "moondog_web_read", label: N_("Read a public web page") },
13
31
  },
14
32
  {
15
33
  id: "source.apple_music.status",
@@ -19,7 +37,7 @@ const catalog = [
19
37
  description: "Inspect aggregate Apple Music import readiness.",
20
38
  agent_tool: {
21
39
  name: "moondog_source_status",
22
- label: "Check your music sources",
40
+ label: N_("Check your music sources"),
23
41
  },
24
42
  },
25
43
  {
@@ -30,7 +48,7 @@ const catalog = [
30
48
  description: "Inspect evidence-backed profile readiness.",
31
49
  agent_tool: {
32
50
  name: "moondog_profile_status",
33
- label: "Check your profile",
51
+ label: N_("Check your profile"),
34
52
  },
35
53
  },
36
54
  {
@@ -41,7 +59,7 @@ const catalog = [
41
59
  description: "Inspect conversation and long-term memory readiness.",
42
60
  agent_tool: {
43
61
  name: "moondog_memory_status",
44
- label: "Check what I remember",
62
+ label: N_("Check what I remember"),
45
63
  },
46
64
  },
47
65
  {
@@ -52,7 +70,7 @@ const catalog = [
52
70
  description: "Inspect the Moondog capability registry.",
53
71
  agent_tool: {
54
72
  name: "moondog_capability_status",
55
- label: "Check what I can do",
73
+ label: N_("Check what I can do"),
56
74
  },
57
75
  },
58
76
  {
@@ -63,7 +81,7 @@ const catalog = [
63
81
  description: "Inspect the configured model runtime.",
64
82
  agent_tool: {
65
83
  name: "moondog_runtime_status",
66
- label: "Check the model connection",
84
+ label: N_("Check the model connection"),
67
85
  },
68
86
  },
69
87
  {
@@ -75,7 +93,7 @@ const catalog = [
75
93
  requires_domain_services: true,
76
94
  agent_tool: {
77
95
  name: "moondog_library_search",
78
- label: "Search your music library",
96
+ label: N_("Search your music library"),
79
97
  },
80
98
  },
81
99
  {
@@ -87,9 +105,27 @@ const catalog = [
87
105
  requires_profile_services: true,
88
106
  agent_tool: {
89
107
  name: "moondog_profile_summary",
90
- label: "Read your music profile",
108
+ label: N_("Read your music profile"),
91
109
  },
92
110
  },
111
+ {
112
+ id: "profile.saved", version: "1", state: "enabled", effect: "read_local",
113
+ description: "Read a durable listener-profile revision and trace a finding to its frozen evidence.",
114
+ requires_profile_build: true,
115
+ agent_tool: { name: "moondog_profile_saved", label: N_("Read your saved listening profile") },
116
+ },
117
+ {
118
+ id: "profile.build", version: "1", state: "enabled", effect: "write_local",
119
+ description: "Build or resume a grounded local listener-profile revision with the configured model.",
120
+ requires_profile_build: true,
121
+ agent_tool: { name: "moondog_profile_build", label: N_("Build your saved listening profile") },
122
+ },
123
+ {
124
+ id: "profile.explore", version: "1", state: "enabled", effect: "read_local",
125
+ description: "Analyze all retained profile evidence with coverage, full-library facets, search, and pagination.",
126
+ requires_profile_exploration: true,
127
+ agent_tool: { name: "moondog_profile_explore", label: N_("Explore your complete music profile") },
128
+ },
93
129
  {
94
130
  id: "profile.rediscovery",
95
131
  version: "1",
@@ -100,7 +136,7 @@ const catalog = [
100
136
  requires_rediscovery_services: true,
101
137
  agent_tool: {
102
138
  name: "moondog_rediscovery_candidates",
103
- label: "Find music worth another listen",
139
+ label: N_("Find music worth another listen"),
104
140
  },
105
141
  },
106
142
  {
@@ -113,7 +149,7 @@ const catalog = [
113
149
  requires_historical_return_services: true,
114
150
  agent_tool: {
115
151
  name: "moondog_historical_return_candidates",
116
- label: "Find music that came back",
152
+ label: N_("Find music that came back"),
117
153
  },
118
154
  },
119
155
  {
@@ -126,7 +162,7 @@ const catalog = [
126
162
  requires_time_capsule_services: true,
127
163
  agent_tool: {
128
164
  name: "moondog_time_capsule_candidates",
129
- label: "Travel through your years",
165
+ label: N_("Travel through your years"),
130
166
  },
131
167
  },
132
168
  {
@@ -139,7 +175,7 @@ const catalog = [
139
175
  requires_back_to_back_services: true,
140
176
  agent_tool: {
141
177
  name: "moondog_back_to_back_candidates",
142
- label: "Find music played back to back",
178
+ label: N_("Find music played back to back"),
143
179
  },
144
180
  },
145
181
  {
@@ -151,7 +187,7 @@ const catalog = [
151
187
  requires_profile_services: true,
152
188
  agent_tool: {
153
189
  name: "moondog_profile_explain",
154
- label: "Look at why it's in your profile",
190
+ label: N_("Look at why it's in your profile"),
155
191
  },
156
192
  },
157
193
  {
@@ -163,7 +199,7 @@ const catalog = [
163
199
  requires_playlist_services: true,
164
200
  agent_tool: {
165
201
  name: "moondog_playlist_plan",
166
- label: "Build a playlist plan",
202
+ label: N_("Build a playlist plan"),
167
203
  },
168
204
  },
169
205
  {
@@ -176,7 +212,7 @@ const catalog = [
176
212
  requires_music_catalog: true,
177
213
  agent_tool: {
178
214
  name: "moondog_music_artist_releases",
179
- label: "Find current artist releases",
215
+ label: N_("Find current artist releases"),
180
216
  },
181
217
  },
182
218
  {
@@ -189,9 +225,72 @@ const catalog = [
189
225
  requires_music_discovery: true,
190
226
  agent_tool: {
191
227
  name: "moondog_music_catalog_search",
192
- label: "Search outside your library",
228
+ label: N_("Search outside your library"),
193
229
  },
194
230
  },
231
+ {
232
+ id: "music.preview.play",
233
+ version: "1",
234
+ state: "enabled",
235
+ effect: "play_preview",
236
+ description:
237
+ "Play 30-second Apple Music previews of named songs or one album, as a queue in the host's player.",
238
+ requires_music_preview: true,
239
+ agent_tool: {
240
+ name: "moondog_music_preview",
241
+ label: N_("Play previews"),
242
+ },
243
+ },
244
+ // NetEase Cloud Music through ncm-cli. The application offers these only while NetEase is the
245
+ // chosen service and ncm-cli is signed in.
246
+ {
247
+ id: "netease.search",
248
+ version: "1",
249
+ state: "enabled",
250
+ effect: "read_external",
251
+ description: "Search NetEase Music for songs, with whether each one can be played.",
252
+ agent_tool: { name: "moondog_netease_search", label: N_("Search NetEase Music") },
253
+ },
254
+ {
255
+ id: "netease.play",
256
+ version: "1",
257
+ state: "enabled",
258
+ effect: "write_external",
259
+ description: "Play or queue songs on NetEase Music through ncm-cli.",
260
+ agent_tool: { name: "moondog_netease_play", label: N_("Play on NetEase Music") },
261
+ },
262
+ {
263
+ id: "netease.control",
264
+ version: "1",
265
+ state: "enabled",
266
+ effect: "write_external",
267
+ description: "Pause, resume, skip, go back, move in the song or stop NetEase Music.",
268
+ agent_tool: { name: "moondog_netease_control", label: N_("Control NetEase Music") },
269
+ },
270
+ {
271
+ id: "netease.now_playing",
272
+ version: "1",
273
+ state: "enabled",
274
+ effect: "read_external",
275
+ description: "Read what NetEase Music is playing and what is queued after it.",
276
+ agent_tool: { name: "moondog_netease_now_playing", label: N_("See what is playing") },
277
+ },
278
+ {
279
+ id: "listening.now",
280
+ version: "1",
281
+ state: "enabled",
282
+ effect: "read_external",
283
+ description: "Read what the listener plays right now in the Music app, on Spotify's active device or on NetEase, and the playlist it plays from.",
284
+ agent_tool: { name: "moondog_listening_now", label: N_("See what you're listening to") },
285
+ },
286
+ {
287
+ id: "music_app.control",
288
+ version: "1",
289
+ state: "enabled",
290
+ effect: "write_local",
291
+ description: "Pause, resume, skip or go back in the Music app on this Mac, which the listener plays themselves.",
292
+ agent_tool: { name: "moondog_music_app_control", label: N_("Control the Music app") },
293
+ },
195
294
  {
196
295
  id: "music.discovery.artist_similarity",
197
296
  version: "1",
@@ -202,7 +301,7 @@ const catalog = [
202
301
  requires_music_similarity: true,
203
302
  agent_tool: {
204
303
  name: "moondog_music_artist_similarity",
205
- label: "Find music nearby",
304
+ label: N_("Find music nearby"),
206
305
  },
207
306
  },
208
307
  {
@@ -214,7 +313,7 @@ const catalog = [
214
313
  requires_memory: true,
215
314
  agent_tool: {
216
315
  name: "moondog_memory_recall",
217
- label: "Remember what you've told me",
316
+ label: N_("Remember what you've told me"),
218
317
  },
219
318
  },
220
319
  {
@@ -227,7 +326,7 @@ const catalog = [
227
326
  requires_memory: true,
228
327
  agent_tool: {
229
328
  name: "moondog_memory_remember",
230
- label: "Remember something you said",
329
+ label: N_("Remember something you said"),
231
330
  },
232
331
  },
233
332
  {
@@ -239,9 +338,15 @@ const catalog = [
239
338
  requires_memory: true,
240
339
  agent_tool: {
241
340
  name: "moondog_memory_forget",
242
- label: "Forget something",
341
+ label: N_("Forget something"),
243
342
  },
244
343
  },
344
+ {
345
+ id: "taste.choice", version: "1", state: "enabled", effect: "write_local",
346
+ description: "Save a lasting Like or Avoid for an artist or song, or undo one, when the listener states it in their own words.",
347
+ requires_taste_choices: true,
348
+ agent_tool: { name: "moondog_taste_choice", label: N_("Save your choice") },
349
+ },
245
350
  {
246
351
  id: "spotify.history.import",
247
352
  version: "1",
@@ -255,11 +360,24 @@ const catalog = [
255
360
  version: "1",
256
361
  state: "enabled",
257
362
  effect: "read_external",
258
- description: "Inspect metadata-free Spotify playback state.",
363
+ description: "Inspect bounded Spotify playback metadata, device capabilities, and state.",
259
364
  requires_spotify: true,
260
365
  agent_tool: {
261
366
  name: "moondog_spotify_player_status",
262
- label: "Check what Spotify is playing",
367
+ label: N_("Check what Spotify is playing"),
368
+ },
369
+ },
370
+ {
371
+ id: "spotify.player.now_playing",
372
+ version: "1",
373
+ state: "enabled",
374
+ effect: "read_external",
375
+ description:
376
+ "Read full Spotify now-playing metadata: track, artists, album, progress, and device.",
377
+ requires_spotify: true,
378
+ agent_tool: {
379
+ name: "moondog_spotify_now_playing",
380
+ label: N_("See what's playing in detail"),
263
381
  },
264
382
  },
265
383
  {
@@ -271,7 +389,7 @@ const catalog = [
271
389
  requires_spotify: true,
272
390
  agent_tool: {
273
391
  name: "moondog_spotify_player_control",
274
- label: "Control Spotify playback",
392
+ label: N_("Control Spotify playback"),
275
393
  },
276
394
  },
277
395
  {
@@ -279,11 +397,48 @@ const catalog = [
279
397
  version: "1",
280
398
  state: "enabled",
281
399
  effect: "write_external",
282
- description: "Add one explicit Spotify URI to the playback queue.",
400
+ description:
401
+ "Add one resolved track, one explicit Spotify URI, or the pending plan to the playback queue.",
283
402
  requires_spotify: true,
284
403
  agent_tool: {
285
404
  name: "moondog_spotify_queue_add",
286
- label: "Add to Spotify queue",
405
+ label: N_("Add to Spotify queue"),
406
+ },
407
+ },
408
+ {
409
+ id: "spotify.queue.similar",
410
+ version: "1",
411
+ state: "enabled",
412
+ effect: "write_external",
413
+ description: "Queue a bounded artist-similarity selection from current Spotify playback, with filtering and partial receipts.",
414
+ requires_spotify: true,
415
+ requires_music_similarity: true,
416
+ agent_tool: { name: "moondog_spotify_queue_similar", label: N_("Queue more like this on Spotify") },
417
+ },
418
+ {
419
+ id: "spotify.queue.status",
420
+ version: "1",
421
+ state: "enabled",
422
+ effect: "read_external",
423
+ description:
424
+ "Inspect the current Spotify playback queue with track metadata.",
425
+ requires_spotify: true,
426
+ agent_tool: {
427
+ name: "moondog_spotify_queue_status",
428
+ label: N_("See what's queued on Spotify"),
429
+ },
430
+ },
431
+ {
432
+ id: "spotify.device.list",
433
+ version: "1",
434
+ state: "enabled",
435
+ effect: "read_external",
436
+ description:
437
+ "List visible Spotify Connect devices by name and type, without device identifiers.",
438
+ requires_spotify: true,
439
+ agent_tool: {
440
+ name: "moondog_spotify_devices",
441
+ label: N_("See your Spotify devices"),
287
442
  },
288
443
  },
289
444
  {
@@ -291,11 +446,12 @@ const catalog = [
291
446
  version: "1",
292
447
  state: "enabled",
293
448
  effect: "write_external",
294
- description: "Transfer Spotify playback to an explicit device ID.",
449
+ description:
450
+ "Transfer Spotify playback to a device the user named, or to a device ID the user pasted.",
295
451
  requires_spotify: true,
296
452
  agent_tool: {
297
453
  name: "moondog_spotify_device_transfer",
298
- label: "Move Spotify playback",
454
+ label: N_("Move Spotify playback"),
299
455
  },
300
456
  },
301
457
  {
@@ -304,12 +460,54 @@ const catalog = [
304
460
  state: "enabled",
305
461
  effect: "read_external",
306
462
  description:
307
- "Resolve trusted library tracks to deterministic Spotify catalog identities.",
463
+ "Resolve trusted library, history, and external catalog tracks to Spotify catalog identities by title, artist, and release.",
308
464
  requires_spotify: true,
309
465
  requires_playlist_services: true,
310
466
  agent_tool: {
311
467
  name: "moondog_spotify_resolve_tracks",
312
- label: "Find your songs on Spotify",
468
+ label: N_("Find your songs on Spotify"),
469
+ },
470
+ },
471
+ {
472
+ id: "spotify.top",
473
+ version: "1",
474
+ state: "enabled",
475
+ effect: "read_external",
476
+ description: "Read bounded Spotify top artists or tracks as transient calculated-affinity evidence.",
477
+ requires_spotify: true,
478
+ agent_tool: { name: "moondog_spotify_top", label: N_("Your Spotify top artists and tracks") },
479
+ },
480
+ {
481
+ id: "spotify.library.browse", version: "1", state: "enabled", effect: "read_external", requires_spotify: true,
482
+ description: "Browse bounded pages of saved Spotify tracks, albums, and shows.",
483
+ agent_tool: { name: "moondog_spotify_library_browse", label: N_("Browse your Spotify library") },
484
+ },
485
+ {
486
+ id: "spotify.history.recent", version: "1", state: "enabled", effect: "read_external", requires_spotify: true,
487
+ description: "Read a bounded page of recent Spotify listening with timestamps and cursors.",
488
+ agent_tool: { name: "moondog_spotify_history_recent", label: N_("Read recent Spotify listening") },
489
+ },
490
+ {
491
+ id: "spotify.catalog.items", version: "1", state: "enabled", effect: "read_external", requires_spotify: true,
492
+ description: "Read bounded album tracks or podcast episodes from a selected host reference.",
493
+ agent_tool: { name: "moondog_spotify_catalog_items", label: N_("Browse album tracks or podcast episodes") },
494
+ },
495
+ {
496
+ id: "spotify.library.remove", version: "1", state: "enabled", effect: "write_external", requires_spotify: true,
497
+ description: "Confirm removal of one selected saved track, album, episode, show, or owned private playlist.",
498
+ agent_tool: { name: "moondog_spotify_library_remove", label: N_("Remove a saved Spotify item") },
499
+ },
500
+ {
501
+ id: "spotify.search",
502
+ version: "1",
503
+ state: "enabled",
504
+ effect: "read_external",
505
+ description:
506
+ "Search the Spotify catalog for tracks by free text, with bounded results.",
507
+ requires_spotify: true,
508
+ agent_tool: {
509
+ name: "moondog_spotify_search",
510
+ label: N_("Search Spotify"),
313
511
  },
314
512
  },
315
513
  {
@@ -319,10 +517,9 @@ const catalog = [
319
517
  effect: "read_external",
320
518
  description: "Check whether resolved tracks are already saved in the Spotify library.",
321
519
  requires_spotify: true,
322
- requires_playlist_services: true,
323
520
  agent_tool: {
324
521
  name: "moondog_spotify_library_check",
325
- label: "Check Spotify saved tracks",
522
+ label: N_("Check Spotify saved tracks"),
326
523
  },
327
524
  },
328
525
  {
@@ -332,10 +529,9 @@ const catalog = [
332
529
  effect: "write_external",
333
530
  description: "Save explicitly requested resolved tracks to the Spotify library.",
334
531
  requires_spotify: true,
335
- requires_playlist_services: true,
336
532
  agent_tool: {
337
533
  name: "moondog_spotify_library_save",
338
- label: "Save tracks to Spotify library",
534
+ label: N_("Save tracks to Spotify library"),
339
535
  },
340
536
  },
341
537
  {
@@ -348,9 +544,23 @@ const catalog = [
348
544
  requires_spotify: true,
349
545
  agent_tool: {
350
546
  name: "moondog_spotify_playlist_read",
351
- label: "Look at your Spotify playlists",
547
+ label: N_("Look at your Spotify playlists"),
352
548
  },
353
549
  },
550
+ {
551
+ id: "spotify.playlist.edit.quick",
552
+ version: "1",
553
+ state: "enabled",
554
+ effect: "write_external",
555
+ description: "Apply one exact user-authorized rename or unambiguous single-track removal to an inspected owned private playlist.",
556
+ requires_spotify: true,
557
+ agent_tool: { name: "moondog_spotify_playlist_edit_quick", label: N_("Make an exact playlist edit") },
558
+ },
559
+ {
560
+ id: "spotify.playlist.remove", version: "1", state: "enabled", effect: "write_external", requires_spotify: true,
561
+ description: "Confirm and remove an owned private playlist from the current user's library without claiming global deletion.",
562
+ agent_tool: { name: "moondog_spotify_playlist_remove", label: N_("Remove a playlist from your library") },
563
+ },
354
564
  {
355
565
  id: "spotify.playlist.edit.preview",
356
566
  version: "1",
@@ -361,7 +571,7 @@ const catalog = [
361
571
  requires_spotify: true,
362
572
  agent_tool: {
363
573
  name: "moondog_spotify_playlist_edit_preview",
364
- label: "Preview a playlist change",
574
+ label: N_("Preview a playlist change"),
365
575
  },
366
576
  },
367
577
  {
@@ -374,7 +584,7 @@ const catalog = [
374
584
  requires_spotify: true,
375
585
  agent_tool: {
376
586
  name: "moondog_spotify_playlist_edit_apply",
377
- label: "Change a Spotify playlist",
587
+ label: N_("Change a Spotify playlist"),
378
588
  },
379
589
  },
380
590
  {
@@ -388,7 +598,7 @@ const catalog = [
388
598
  requires_playlist_services: true,
389
599
  agent_tool: {
390
600
  name: "moondog_spotify_playlist_write",
391
- label: "Save the playlist to Spotify",
601
+ label: N_("Save the playlist to Spotify"),
392
602
  },
393
603
  },
394
604
  {
@@ -421,10 +631,27 @@ function withRuntimeAvailability(
421
631
  musicCatalogReady,
422
632
  musicDiscoveryReady,
423
633
  musicSimilarityReady,
424
- webResearchReady,
634
+ webProviders,
635
+ profileExplorationReady,
636
+ profileBuildReady,
637
+ musicPreviewReady,
638
+ tasteChoicesReady,
425
639
  ) {
426
- if (capability.requires_web_research && !webResearchReady) {
427
- return { ...capability, state: "blocked", blocked_by: "codex_web_not_ready" };
640
+ if (capability.requires_taste_choices && !tasteChoicesReady) {
641
+ return { ...capability, state: "blocked", blocked_by: "taste_choices_not_ready" };
642
+ }
643
+ if (capability.requires_profile_build && !profileBuildReady) {
644
+ return { ...capability, state: "blocked", blocked_by: "profile_build_not_ready" };
645
+ }
646
+ if (capability.requires_profile_exploration && !profileExplorationReady) {
647
+ return { ...capability, state: "blocked", blocked_by: "profile_exploration_not_ready" };
648
+ }
649
+ if (capability.requires_web) {
650
+ // The description names the service in use, or the ones that could be set up.
651
+ const provider = webProviders[capability.requires_web];
652
+ capability = { ...capability, description: capability.description.replace("{provider}", provider ??
653
+ (capability.requires_web === "search" ? "DeepSeek, Brave Search or the Codex CLI, once one is set up" : "DeepSeek or the Codex CLI, once one is set up")) };
654
+ if (!provider) return { ...capability, state: "blocked", blocked_by: `web_${capability.requires_web}_not_ready` };
428
655
  }
429
656
  if (capability.requires_domain_services && !domainServicesReady) {
430
657
  return {
@@ -513,6 +740,13 @@ function withRuntimeAvailability(
513
740
  blocked_by: "music_similarity_not_ready",
514
741
  };
515
742
  }
743
+ if (capability.requires_music_preview && !musicPreviewReady) {
744
+ return {
745
+ ...capability,
746
+ state: "blocked",
747
+ blocked_by: "music_preview_not_ready",
748
+ };
749
+ }
516
750
  return capability;
517
751
  }
518
752
 
@@ -529,7 +763,12 @@ export function listCapabilities({
529
763
  musicCatalogReady = false,
530
764
  musicDiscoveryReady = false,
531
765
  musicSimilarityReady = false,
532
- webResearchReady = false,
766
+ webSearchProvider = null,
767
+ webReadProvider = null,
768
+ profileExplorationReady = false,
769
+ profileBuildReady = false,
770
+ musicPreviewReady = false,
771
+ tasteChoicesReady = false,
533
772
  } = {}) {
534
773
  return structuredClone(
535
774
  catalog.map((capability) =>
@@ -547,7 +786,11 @@ export function listCapabilities({
547
786
  musicCatalogReady,
548
787
  musicDiscoveryReady,
549
788
  musicSimilarityReady,
550
- webResearchReady,
789
+ { search: webSearchProvider, read: webReadProvider },
790
+ profileExplorationReady,
791
+ profileBuildReady,
792
+ musicPreviewReady,
793
+ tasteChoicesReady,
551
794
  ),
552
795
  ),
553
796
  );
@@ -567,11 +810,3 @@ export function listAgentCapabilityDescriptors(options = {}) {
567
810
  label: capability.agent_tool.label,
568
811
  }));
569
812
  }
570
-
571
- export function getAgentCapabilityByToolName(toolName, options = {}) {
572
- return (
573
- listAgentCapabilityDescriptors(options).find(
574
- (capability) => capability.tool_name === toolName,
575
- ) ?? null
576
- );
577
- }