@audiofool/moondog 0.1.0 → 0.2.0-beta.2

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 (121) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +153 -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 +168 -36
  10. package/src/core/application-update.mjs +320 -0
  11. package/src/core/capability-catalog.mjs +278 -55
  12. package/src/core/listening-now.mjs +108 -0
  13. package/src/core/listening-profile-domain-services.mjs +153 -41
  14. package/src/core/listening-upkeep.mjs +31 -0
  15. package/src/core/moondog-application.mjs +1908 -104
  16. package/src/core/playback-preferences.mjs +97 -0
  17. package/src/core/spotify-listening-intent.mjs +121 -0
  18. package/src/core/synthetic-domain-services.mjs +24 -3
  19. package/src/demo/moondog-demo.mjs +4 -22
  20. package/src/i18n/index.mjs +285 -0
  21. package/src/i18n/messages/de.mjs +128 -0
  22. package/src/i18n/messages/en.mjs +129 -0
  23. package/src/i18n/messages/es.mjs +128 -0
  24. package/src/i18n/messages/fr.mjs +128 -0
  25. package/src/i18n/messages/it.mjs +128 -0
  26. package/src/i18n/messages/ja.mjs +128 -0
  27. package/src/i18n/messages/ko.mjs +128 -0
  28. package/src/i18n/messages/pt.mjs +128 -0
  29. package/src/i18n/messages/zh-Hant.mjs +128 -0
  30. package/src/i18n/messages/zh.mjs +128 -0
  31. package/src/i18n/preferences.mjs +34 -0
  32. package/src/i18n/screen/es.mjs +966 -0
  33. package/src/i18n/screen/ja.mjs +966 -0
  34. package/src/i18n/screen/pt.mjs +966 -0
  35. package/src/i18n/screen/zh.mjs +966 -0
  36. package/src/importers/apple-music-library/index.mjs +0 -16
  37. package/src/importers/apple-music-library/sqlite-projection.mjs +51 -9
  38. package/src/integrations/apple-music/catalog.mjs +123 -14
  39. package/src/integrations/apple-music/music-app.mjs +232 -0
  40. package/src/integrations/apple-music/storefront.mjs +28 -0
  41. package/src/integrations/apple-music/terminal-preview-player.mjs +221 -0
  42. package/src/integrations/lastfm/history.mjs +221 -0
  43. package/src/integrations/lastfm/settings.mjs +75 -0
  44. package/src/integrations/netease/ncm-cli-player.mjs +321 -0
  45. package/src/integrations/open-music-similarity/artist-radio.mjs +26 -18
  46. package/src/integrations/public-playlists.mjs +2 -1
  47. package/src/integrations/spotify/account-data-profile.mjs +14 -6
  48. package/src/integrations/spotify/authentication.mjs +149 -15
  49. package/src/integrations/spotify/catalog-resolver.mjs +8 -4
  50. package/src/integrations/spotify/connection.mjs +2 -1
  51. package/src/integrations/spotify/credential-store.mjs +4 -0
  52. package/src/integrations/spotify/extended-streaming-history.mjs +11 -11
  53. package/src/integrations/spotify/history-archive.mjs +1 -1
  54. package/src/integrations/spotify/library-snapshot.mjs +227 -0
  55. package/src/integrations/spotify/recent-activity.mjs +2 -0
  56. package/src/integrations/spotify/service.mjs +873 -69
  57. package/src/integrations/spotify/web-api-client.mjs +407 -127
  58. package/src/integrations/web/brave-web.mjs +45 -0
  59. package/src/integrations/web/codex-web.mjs +15 -82
  60. package/src/integrations/web/deepseek-web.mjs +81 -0
  61. package/src/integrations/web/web-research.mjs +119 -0
  62. package/src/integrations/web/web-result.mjs +215 -0
  63. package/src/integrations/youtube-music/takeout.mjs +3 -1
  64. package/src/memory/local-memory-store.mjs +469 -41
  65. package/src/profile/history-import.mjs +42 -6
  66. package/src/profile/listener-profile-build.mjs +378 -0
  67. package/src/profile/listener-profile-store.mjs +130 -0
  68. package/src/profile/listener-profile-verification.mjs +68 -0
  69. package/src/profile/listener-profile-worker.mjs +172 -0
  70. package/src/profile/listening-history-store.mjs +424 -8
  71. package/src/profile/listening-profile-projection.mjs +242 -76
  72. package/src/profile/local-music-data.mjs +1 -1
  73. package/src/profile/music-providers.mjs +11 -7
  74. package/src/profile/profile-exploration.mjs +84 -0
  75. package/src/profile/recorded-measures.mjs +24 -0
  76. package/src/profile/spotify-archive-taste.mjs +0 -4
  77. package/src/runtime/pi/agent-runtime.mjs +2204 -605
  78. package/src/runtime/pi/authentication.mjs +34 -1
  79. package/src/runtime/pi/configured-runtime.mjs +22 -6
  80. package/src/runtime/pi/conversation-name.mjs +53 -0
  81. package/src/runtime/pi/memory-agent-runtime.mjs +2 -2
  82. package/src/runtime/pi/model-catalog.mjs +28 -5
  83. package/src/runtime/pi/model-settings.mjs +202 -0
  84. package/src/runtime/pi/model-transport.mjs +10 -0
  85. package/src/runtime/pi/persistent-credential-store.mjs +67 -31
  86. package/src/runtime/pi/profile-build-runtime.mjs +160 -0
  87. package/src/runtime/pi/public-model-catalog.mjs +288 -0
  88. package/src/runtime/pi/runtime-settings.mjs +10 -2
  89. package/src/runtime/pi/skills/listener-profile/SKILL.md +46 -0
  90. package/src/runtime/pi/tool-trace.mjs +37 -0
  91. package/src/surfaces/cli/auth-command.mjs +12 -4
  92. package/src/surfaces/cli/brand-components.mjs +310 -132
  93. package/src/surfaces/cli/brand-theme.mjs +51 -28
  94. package/src/surfaces/cli/command-completions.mjs +58 -42
  95. package/src/surfaces/cli/format-output.mjs +199 -388
  96. package/src/surfaces/cli/history-import-view.mjs +292 -139
  97. package/src/surfaces/cli/import-guides.mjs +2 -0
  98. package/src/surfaces/cli/listener-profile-command.mjs +92 -0
  99. package/src/surfaces/cli/listening-menu.mjs +7 -4
  100. package/src/surfaces/cli/profile-command.mjs +30 -21
  101. package/src/surfaces/cli/seasons-chart.mjs +87 -0
  102. package/src/surfaces/cli/spotify-command.mjs +54 -6
  103. package/src/surfaces/cli/taste-profile-model.mjs +78 -79
  104. package/src/surfaces/cli/taste-profile-view.mjs +33 -26
  105. package/src/surfaces/cli/terminal-art.mjs +140 -35
  106. package/src/surfaces/cli/tui.mjs +1066 -357
  107. package/src/surfaces/cli/update-command.mjs +32 -0
  108. package/src/surfaces/cli/web-command.mjs +53 -14
  109. package/src/surfaces/html/tasteprint.mjs +43 -70
  110. package/contracts/v1/capability-policy.schema.json +0 -572
  111. package/contracts/v1/profile-evidence.schema.json +0 -237
  112. package/contracts/v1/tool-invocation.schema.json +0 -318
  113. package/contracts/v1/tool-receipt.schema.json +0 -1066
  114. package/scripts/demo-studio.mjs +0 -58
  115. package/src/core/apple-data-migration.mjs +0 -50
  116. package/src/demo/moondog-interactive-tasteprint-demo.mjs +0 -293
  117. package/src/demo/moondog-tasteprint-demo.mjs +0 -631
  118. package/src/integrations/spotify/index.mjs +0 -80
  119. package/src/surfaces/web/showcase.mjs +0 -272
  120. package/src/surfaces/web/studio-page.mjs +0 -4148
  121. 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,7 +338,7 @@ 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
  },
245
344
  {
@@ -255,11 +354,24 @@ const catalog = [
255
354
  version: "1",
256
355
  state: "enabled",
257
356
  effect: "read_external",
258
- description: "Inspect metadata-free Spotify playback state.",
357
+ description: "Inspect bounded Spotify playback metadata, device capabilities, and state.",
259
358
  requires_spotify: true,
260
359
  agent_tool: {
261
360
  name: "moondog_spotify_player_status",
262
- label: "Check what Spotify is playing",
361
+ label: N_("Check what Spotify is playing"),
362
+ },
363
+ },
364
+ {
365
+ id: "spotify.player.now_playing",
366
+ version: "1",
367
+ state: "enabled",
368
+ effect: "read_external",
369
+ description:
370
+ "Read full Spotify now-playing metadata: track, artists, album, progress, and device.",
371
+ requires_spotify: true,
372
+ agent_tool: {
373
+ name: "moondog_spotify_now_playing",
374
+ label: N_("See what's playing in detail"),
263
375
  },
264
376
  },
265
377
  {
@@ -271,7 +383,7 @@ const catalog = [
271
383
  requires_spotify: true,
272
384
  agent_tool: {
273
385
  name: "moondog_spotify_player_control",
274
- label: "Control Spotify playback",
386
+ label: N_("Control Spotify playback"),
275
387
  },
276
388
  },
277
389
  {
@@ -279,11 +391,48 @@ const catalog = [
279
391
  version: "1",
280
392
  state: "enabled",
281
393
  effect: "write_external",
282
- description: "Add one explicit Spotify URI to the playback queue.",
394
+ description:
395
+ "Add one resolved track, one explicit Spotify URI, or the pending plan to the playback queue.",
283
396
  requires_spotify: true,
284
397
  agent_tool: {
285
398
  name: "moondog_spotify_queue_add",
286
- label: "Add to Spotify queue",
399
+ label: N_("Add to Spotify queue"),
400
+ },
401
+ },
402
+ {
403
+ id: "spotify.queue.similar",
404
+ version: "1",
405
+ state: "enabled",
406
+ effect: "write_external",
407
+ description: "Queue a bounded artist-similarity selection from current Spotify playback, with filtering and partial receipts.",
408
+ requires_spotify: true,
409
+ requires_music_similarity: true,
410
+ agent_tool: { name: "moondog_spotify_queue_similar", label: N_("Queue more like this on Spotify") },
411
+ },
412
+ {
413
+ id: "spotify.queue.status",
414
+ version: "1",
415
+ state: "enabled",
416
+ effect: "read_external",
417
+ description:
418
+ "Inspect the current Spotify playback queue with track metadata.",
419
+ requires_spotify: true,
420
+ agent_tool: {
421
+ name: "moondog_spotify_queue_status",
422
+ label: N_("See what's queued on Spotify"),
423
+ },
424
+ },
425
+ {
426
+ id: "spotify.device.list",
427
+ version: "1",
428
+ state: "enabled",
429
+ effect: "read_external",
430
+ description:
431
+ "List visible Spotify Connect devices by name and type, without device identifiers.",
432
+ requires_spotify: true,
433
+ agent_tool: {
434
+ name: "moondog_spotify_devices",
435
+ label: N_("See your Spotify devices"),
287
436
  },
288
437
  },
289
438
  {
@@ -291,11 +440,12 @@ const catalog = [
291
440
  version: "1",
292
441
  state: "enabled",
293
442
  effect: "write_external",
294
- description: "Transfer Spotify playback to an explicit device ID.",
443
+ description:
444
+ "Transfer Spotify playback to a device the user named, or to a device ID the user pasted.",
295
445
  requires_spotify: true,
296
446
  agent_tool: {
297
447
  name: "moondog_spotify_device_transfer",
298
- label: "Move Spotify playback",
448
+ label: N_("Move Spotify playback"),
299
449
  },
300
450
  },
301
451
  {
@@ -304,12 +454,54 @@ const catalog = [
304
454
  state: "enabled",
305
455
  effect: "read_external",
306
456
  description:
307
- "Resolve trusted library tracks to deterministic Spotify catalog identities.",
457
+ "Resolve trusted library, history, and external catalog tracks to Spotify catalog identities by title, artist, and release.",
308
458
  requires_spotify: true,
309
459
  requires_playlist_services: true,
310
460
  agent_tool: {
311
461
  name: "moondog_spotify_resolve_tracks",
312
- label: "Find your songs on Spotify",
462
+ label: N_("Find your songs on Spotify"),
463
+ },
464
+ },
465
+ {
466
+ id: "spotify.top",
467
+ version: "1",
468
+ state: "enabled",
469
+ effect: "read_external",
470
+ description: "Read bounded Spotify top artists or tracks as transient calculated-affinity evidence.",
471
+ requires_spotify: true,
472
+ agent_tool: { name: "moondog_spotify_top", label: N_("Your Spotify top artists and tracks") },
473
+ },
474
+ {
475
+ id: "spotify.library.browse", version: "1", state: "enabled", effect: "read_external", requires_spotify: true,
476
+ description: "Browse bounded pages of saved Spotify tracks, albums, and shows.",
477
+ agent_tool: { name: "moondog_spotify_library_browse", label: N_("Browse your Spotify library") },
478
+ },
479
+ {
480
+ id: "spotify.history.recent", version: "1", state: "enabled", effect: "read_external", requires_spotify: true,
481
+ description: "Read a bounded page of recent Spotify listening with timestamps and cursors.",
482
+ agent_tool: { name: "moondog_spotify_history_recent", label: N_("Read recent Spotify listening") },
483
+ },
484
+ {
485
+ id: "spotify.catalog.items", version: "1", state: "enabled", effect: "read_external", requires_spotify: true,
486
+ description: "Read bounded album tracks or podcast episodes from a selected host reference.",
487
+ agent_tool: { name: "moondog_spotify_catalog_items", label: N_("Browse album tracks or podcast episodes") },
488
+ },
489
+ {
490
+ id: "spotify.library.remove", version: "1", state: "enabled", effect: "write_external", requires_spotify: true,
491
+ description: "Confirm removal of one selected saved track, album, episode, show, or owned private playlist.",
492
+ agent_tool: { name: "moondog_spotify_library_remove", label: N_("Remove a saved Spotify item") },
493
+ },
494
+ {
495
+ id: "spotify.search",
496
+ version: "1",
497
+ state: "enabled",
498
+ effect: "read_external",
499
+ description:
500
+ "Search the Spotify catalog for tracks by free text, with bounded results.",
501
+ requires_spotify: true,
502
+ agent_tool: {
503
+ name: "moondog_spotify_search",
504
+ label: N_("Search Spotify"),
313
505
  },
314
506
  },
315
507
  {
@@ -319,10 +511,9 @@ const catalog = [
319
511
  effect: "read_external",
320
512
  description: "Check whether resolved tracks are already saved in the Spotify library.",
321
513
  requires_spotify: true,
322
- requires_playlist_services: true,
323
514
  agent_tool: {
324
515
  name: "moondog_spotify_library_check",
325
- label: "Check Spotify saved tracks",
516
+ label: N_("Check Spotify saved tracks"),
326
517
  },
327
518
  },
328
519
  {
@@ -332,10 +523,9 @@ const catalog = [
332
523
  effect: "write_external",
333
524
  description: "Save explicitly requested resolved tracks to the Spotify library.",
334
525
  requires_spotify: true,
335
- requires_playlist_services: true,
336
526
  agent_tool: {
337
527
  name: "moondog_spotify_library_save",
338
- label: "Save tracks to Spotify library",
528
+ label: N_("Save tracks to Spotify library"),
339
529
  },
340
530
  },
341
531
  {
@@ -348,9 +538,23 @@ const catalog = [
348
538
  requires_spotify: true,
349
539
  agent_tool: {
350
540
  name: "moondog_spotify_playlist_read",
351
- label: "Look at your Spotify playlists",
541
+ label: N_("Look at your Spotify playlists"),
352
542
  },
353
543
  },
544
+ {
545
+ id: "spotify.playlist.edit.quick",
546
+ version: "1",
547
+ state: "enabled",
548
+ effect: "write_external",
549
+ description: "Apply one exact user-authorized rename or unambiguous single-track removal to an inspected owned private playlist.",
550
+ requires_spotify: true,
551
+ agent_tool: { name: "moondog_spotify_playlist_edit_quick", label: N_("Make an exact playlist edit") },
552
+ },
553
+ {
554
+ id: "spotify.playlist.remove", version: "1", state: "enabled", effect: "write_external", requires_spotify: true,
555
+ description: "Confirm and remove an owned private playlist from the current user's library without claiming global deletion.",
556
+ agent_tool: { name: "moondog_spotify_playlist_remove", label: N_("Remove a playlist from your library") },
557
+ },
354
558
  {
355
559
  id: "spotify.playlist.edit.preview",
356
560
  version: "1",
@@ -361,7 +565,7 @@ const catalog = [
361
565
  requires_spotify: true,
362
566
  agent_tool: {
363
567
  name: "moondog_spotify_playlist_edit_preview",
364
- label: "Preview a playlist change",
568
+ label: N_("Preview a playlist change"),
365
569
  },
366
570
  },
367
571
  {
@@ -374,7 +578,7 @@ const catalog = [
374
578
  requires_spotify: true,
375
579
  agent_tool: {
376
580
  name: "moondog_spotify_playlist_edit_apply",
377
- label: "Change a Spotify playlist",
581
+ label: N_("Change a Spotify playlist"),
378
582
  },
379
583
  },
380
584
  {
@@ -388,7 +592,7 @@ const catalog = [
388
592
  requires_playlist_services: true,
389
593
  agent_tool: {
390
594
  name: "moondog_spotify_playlist_write",
391
- label: "Save the playlist to Spotify",
595
+ label: N_("Save the playlist to Spotify"),
392
596
  },
393
597
  },
394
598
  {
@@ -421,10 +625,23 @@ function withRuntimeAvailability(
421
625
  musicCatalogReady,
422
626
  musicDiscoveryReady,
423
627
  musicSimilarityReady,
424
- webResearchReady,
628
+ webProviders,
629
+ profileExplorationReady,
630
+ profileBuildReady,
631
+ musicPreviewReady,
425
632
  ) {
426
- if (capability.requires_web_research && !webResearchReady) {
427
- return { ...capability, state: "blocked", blocked_by: "codex_web_not_ready" };
633
+ if (capability.requires_profile_build && !profileBuildReady) {
634
+ return { ...capability, state: "blocked", blocked_by: "profile_build_not_ready" };
635
+ }
636
+ if (capability.requires_profile_exploration && !profileExplorationReady) {
637
+ return { ...capability, state: "blocked", blocked_by: "profile_exploration_not_ready" };
638
+ }
639
+ if (capability.requires_web) {
640
+ // The description names the service in use, or the ones that could be set up.
641
+ const provider = webProviders[capability.requires_web];
642
+ capability = { ...capability, description: capability.description.replace("{provider}", provider ??
643
+ (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")) };
644
+ if (!provider) return { ...capability, state: "blocked", blocked_by: `web_${capability.requires_web}_not_ready` };
428
645
  }
429
646
  if (capability.requires_domain_services && !domainServicesReady) {
430
647
  return {
@@ -513,6 +730,13 @@ function withRuntimeAvailability(
513
730
  blocked_by: "music_similarity_not_ready",
514
731
  };
515
732
  }
733
+ if (capability.requires_music_preview && !musicPreviewReady) {
734
+ return {
735
+ ...capability,
736
+ state: "blocked",
737
+ blocked_by: "music_preview_not_ready",
738
+ };
739
+ }
516
740
  return capability;
517
741
  }
518
742
 
@@ -529,7 +753,11 @@ export function listCapabilities({
529
753
  musicCatalogReady = false,
530
754
  musicDiscoveryReady = false,
531
755
  musicSimilarityReady = false,
532
- webResearchReady = false,
756
+ webSearchProvider = null,
757
+ webReadProvider = null,
758
+ profileExplorationReady = false,
759
+ profileBuildReady = false,
760
+ musicPreviewReady = false,
533
761
  } = {}) {
534
762
  return structuredClone(
535
763
  catalog.map((capability) =>
@@ -547,7 +775,10 @@ export function listCapabilities({
547
775
  musicCatalogReady,
548
776
  musicDiscoveryReady,
549
777
  musicSimilarityReady,
550
- webResearchReady,
778
+ { search: webSearchProvider, read: webReadProvider },
779
+ profileExplorationReady,
780
+ profileBuildReady,
781
+ musicPreviewReady,
551
782
  ),
552
783
  ),
553
784
  );
@@ -567,11 +798,3 @@ export function listAgentCapabilityDescriptors(options = {}) {
567
798
  label: capability.agent_tool.label,
568
799
  }));
569
800
  }
570
-
571
- export function getAgentCapabilityByToolName(toolName, options = {}) {
572
- return (
573
- listAgentCapabilityDescriptors(options).find(
574
- (capability) => capability.tool_name === toolName,
575
- ) ?? null
576
- );
577
- }