agent-discogs 0.1.0__py3-none-any.whl
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.
- agent_discogs/__init__.py +151 -0
- agent_discogs/__main__.py +5 -0
- agent_discogs/client.py +30 -0
- agent_discogs/commands/__init__.py +0 -0
- agent_discogs/commands/cache.py +16 -0
- agent_discogs/commands/get.py +794 -0
- agent_discogs/commands/search.py +275 -0
- agent_discogs/commands/skills.py +92 -0
- agent_discogs/commands/status.py +22 -0
- agent_discogs/errors.py +149 -0
- agent_discogs/formatting.py +741 -0
- agent_discogs/json_output.py +66 -0
- agent_discogs/pagination.py +190 -0
- agent_discogs/projections.py +300 -0
- agent_discogs/py.typed +0 -0
- agent_discogs/refs.py +59 -0
- agent_discogs/skills/core/SKILL.md +140 -0
- agent_discogs/skills/core/references/commands.md +174 -0
- agent_discogs/skills/core/references/discogs-domain.md +278 -0
- agent_discogs/skills/core/references/pressings-guide.md +77 -0
- agent_discogs/skills/core/references/search-patterns.md +72 -0
- agent_discogs-0.1.0.dist-info/METADATA +210 -0
- agent_discogs-0.1.0.dist-info/RECORD +26 -0
- agent_discogs-0.1.0.dist-info/WHEEL +4 -0
- agent_discogs-0.1.0.dist-info/entry_points.txt +3 -0
- agent_discogs-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""agent-discogs: Token-efficient Discogs CLI for AI agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from agent_discogs.commands.cache import cache
|
|
11
|
+
from agent_discogs.commands.get import get, price, tracks
|
|
12
|
+
from agent_discogs.commands.search import search
|
|
13
|
+
from agent_discogs.commands.skills import skills
|
|
14
|
+
from agent_discogs.commands.status import status
|
|
15
|
+
|
|
16
|
+
ALIASES: dict[str, str] = {
|
|
17
|
+
"find": "search",
|
|
18
|
+
"query": "search",
|
|
19
|
+
"fetch": "get",
|
|
20
|
+
"show": "get",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_HELP_TEXT = """\
|
|
25
|
+
agent-discogs - token-efficient Discogs CLI for AI agents
|
|
26
|
+
|
|
27
|
+
Usage: agent-discogs <command> [args] [options]
|
|
28
|
+
|
|
29
|
+
Commands:
|
|
30
|
+
search [type] <query> Search database (aliases: find, query)
|
|
31
|
+
get <noun> <ref>... Get entity details, 1-10 refs (aliases: fetch, show)
|
|
32
|
+
tracks <ref>... Shortcut: get tracklist <ref>...
|
|
33
|
+
price <ref>... Shortcut: get price <ref>...
|
|
34
|
+
cache clear Clear HTTP cache
|
|
35
|
+
skills [get <name>] Print the bundled agent guide (start: skills get core)
|
|
36
|
+
status Show session and auth info
|
|
37
|
+
|
|
38
|
+
Search Types: artist, label, master, release
|
|
39
|
+
Get Nouns: artist, credits, identifiers (ids), label, master, price, release,
|
|
40
|
+
releases, tracklist, versions
|
|
41
|
+
|
|
42
|
+
Refs:
|
|
43
|
+
Search results return typed refs: @a3857 (artist), @r847868 (release),
|
|
44
|
+
@m3719 (master), @l647 (label). Use refs with get commands.
|
|
45
|
+
|
|
46
|
+
Options:
|
|
47
|
+
--json JSON output: a compact projection of the text view (search, get)
|
|
48
|
+
--full With --json: the raw SDK record instead of the projection
|
|
49
|
+
--limit N Results per page (search, get)
|
|
50
|
+
--page N Page number for server-side pages (search, get)
|
|
51
|
+
--after CURSOR Continue a client-side filtered list; copy from Next page /
|
|
52
|
+
Continue scan (search with --release-type official|unofficial,
|
|
53
|
+
get releases --role, get releases @l --year)
|
|
54
|
+
-v, --verbose get release: also print notes, credits, identifiers
|
|
55
|
+
-c, --compact get release: one-line "Tracks: N (mm:ss)" instead of the tracklist
|
|
56
|
+
(total omitted if any track lacks a duration)
|
|
57
|
+
--sort KEY Server-side order. get releases @a: year|title|format;
|
|
58
|
+
get versions: released|title|format|label|catno|country
|
|
59
|
+
--desc With --sort: descending
|
|
60
|
+
--year YEAR get versions: filter by year (server-side); get releases @l:
|
|
61
|
+
filter by year (client-side scan, continues via --after)
|
|
62
|
+
--version Show version and exit
|
|
63
|
+
--help Show this message and exit
|
|
64
|
+
|
|
65
|
+
Environment:
|
|
66
|
+
DISCOGS_TOKEN Personal access token (higher rate limit, required for price data)
|
|
67
|
+
|
|
68
|
+
Price data also needs seller settings filled out on the Discogs account the
|
|
69
|
+
token belongs to (discogs.com/settings/seller). Without them the API answers
|
|
70
|
+
404 and only 'price' is affected.
|
|
71
|
+
|
|
72
|
+
Examples:
|
|
73
|
+
agent-discogs search "The Downward Spiral"
|
|
74
|
+
agent-discogs search artist "Nine Inch Nails"
|
|
75
|
+
agent-discogs get release @r847868
|
|
76
|
+
agent-discogs get versions @m3719 --country US --format Vinyl
|
|
77
|
+
agent-discogs tracks @r847868
|
|
78
|
+
agent-discogs price @r847868
|
|
79
|
+
agent-discogs get releases @a3857 --role Main --limit 5
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class AliasGroup(click.Group):
|
|
84
|
+
"""Click group that supports command aliases."""
|
|
85
|
+
|
|
86
|
+
def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None:
|
|
87
|
+
cmd = super().get_command(ctx, cmd_name)
|
|
88
|
+
if cmd is not None:
|
|
89
|
+
return cmd
|
|
90
|
+
canonical = ALIASES.get(cmd_name)
|
|
91
|
+
if canonical is not None:
|
|
92
|
+
return super().get_command(ctx, canonical)
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
def resolve_command(
|
|
96
|
+
self, ctx: click.Context, args: list[str]
|
|
97
|
+
) -> tuple[str | None, click.Command | None, list[str]]:
|
|
98
|
+
# Ensure the canonical name is used so help text is consistent
|
|
99
|
+
_, cmd, remaining = super().resolve_command(ctx, args)
|
|
100
|
+
if cmd is not None:
|
|
101
|
+
return cmd.name, cmd, remaining
|
|
102
|
+
return None, None, args
|
|
103
|
+
|
|
104
|
+
def format_help(
|
|
105
|
+
self,
|
|
106
|
+
ctx: click.Context, # noqa: ARG002 # part of click's Command API
|
|
107
|
+
formatter: click.HelpFormatter,
|
|
108
|
+
) -> None:
|
|
109
|
+
formatter.write(_HELP_TEXT)
|
|
110
|
+
|
|
111
|
+
def invoke(self, ctx: click.Context) -> Any:
|
|
112
|
+
try:
|
|
113
|
+
return super().invoke(ctx)
|
|
114
|
+
except KeyboardInterrupt:
|
|
115
|
+
sys.exit(130)
|
|
116
|
+
except click.exceptions.Exit:
|
|
117
|
+
raise
|
|
118
|
+
except click.exceptions.Abort:
|
|
119
|
+
raise
|
|
120
|
+
except click.exceptions.UsageError:
|
|
121
|
+
raise
|
|
122
|
+
# Last-resort boundary: any escaped exception becomes agent-readable text
|
|
123
|
+
# instead of a traceback.
|
|
124
|
+
except Exception as e: # noqa: BLE001
|
|
125
|
+
from agent_discogs.errors import format_error
|
|
126
|
+
|
|
127
|
+
print(format_error(e), file=sys.stderr)
|
|
128
|
+
sys.exit(1)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@click.group(cls=AliasGroup, invoke_without_command=True)
|
|
132
|
+
@click.version_option(package_name="agent-discogs", prog_name="agent-discogs")
|
|
133
|
+
@click.pass_context
|
|
134
|
+
def cli(ctx: click.Context) -> None:
|
|
135
|
+
"""Token-efficient Discogs CLI for AI agents."""
|
|
136
|
+
if ctx.invoked_subcommand is None:
|
|
137
|
+
click.echo(ctx.get_help())
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
cli.add_command(cache)
|
|
141
|
+
cli.add_command(get)
|
|
142
|
+
cli.add_command(price)
|
|
143
|
+
cli.add_command(search)
|
|
144
|
+
cli.add_command(skills)
|
|
145
|
+
cli.add_command(status)
|
|
146
|
+
cli.add_command(tracks)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def main() -> None:
|
|
150
|
+
"""Entry point for the CLI."""
|
|
151
|
+
cli()
|
agent_discogs/client.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""SDK client initialization."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from discogs_sdk import Discogs
|
|
9
|
+
|
|
10
|
+
_client: Discogs | None = None
|
|
11
|
+
|
|
12
|
+
CACHE_DIR = (
|
|
13
|
+
Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache")) / "agent-discogs"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_client() -> Discogs:
|
|
18
|
+
"""Get or create the Discogs SDK client."""
|
|
19
|
+
global _client # noqa: PLW0603 # module-level singleton is the point
|
|
20
|
+
if _client is not None:
|
|
21
|
+
return _client
|
|
22
|
+
|
|
23
|
+
token = os.environ.get("DISCOGS_TOKEN")
|
|
24
|
+
_client = Discogs(token=token, cache=True, cache_dir=CACHE_DIR)
|
|
25
|
+
return _client
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def has_token() -> bool:
|
|
29
|
+
"""Check if a DISCOGS_TOKEN is configured."""
|
|
30
|
+
return bool(os.environ.get("DISCOGS_TOKEN"))
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Cache command handler."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from agent_discogs.client import get_client
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.command()
|
|
11
|
+
@click.argument("action", type=click.Choice(["clear"]))
|
|
12
|
+
def cache(action: str) -> None:
|
|
13
|
+
"""Manage cache."""
|
|
14
|
+
if action == "clear":
|
|
15
|
+
get_client().clear_cache()
|
|
16
|
+
print("Cache cleared.")
|