av-cli 0.1.0__tar.gz
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.
- av_cli-0.1.0/.gitignore +34 -0
- av_cli-0.1.0/PKG-INFO +9 -0
- av_cli-0.1.0/README.md +15 -0
- av_cli-0.1.0/pyproject.toml +24 -0
- av_cli-0.1.0/src/av_cli/__init__.py +1 -0
- av_cli-0.1.0/src/av_cli/main.py +147 -0
av_cli-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# GitHub Actions config secrets
|
|
2
|
+
.github/config-*.json
|
|
3
|
+
|
|
4
|
+
# Editor / IDE
|
|
5
|
+
.gemini
|
|
6
|
+
.vscode
|
|
7
|
+
|
|
8
|
+
# OS
|
|
9
|
+
*.DS_Store
|
|
10
|
+
|
|
11
|
+
# Environment
|
|
12
|
+
.venv
|
|
13
|
+
.env
|
|
14
|
+
.env.bak
|
|
15
|
+
.env*
|
|
16
|
+
|
|
17
|
+
# Python-generated files
|
|
18
|
+
__pycache__/
|
|
19
|
+
*.py[oc]
|
|
20
|
+
build/
|
|
21
|
+
dist/
|
|
22
|
+
wheels/
|
|
23
|
+
*.egg-info
|
|
24
|
+
|
|
25
|
+
# AWS SAM build artifacts
|
|
26
|
+
.aws-sam/
|
|
27
|
+
|
|
28
|
+
# Dev docs
|
|
29
|
+
dev_docs
|
|
30
|
+
|
|
31
|
+
# Generated / local files
|
|
32
|
+
requirements.txt
|
|
33
|
+
sessions.db
|
|
34
|
+
mcp.json
|
av_cli-0.1.0/PKG-INFO
ADDED
av_cli-0.1.0/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "av-cli"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "CLI wrapper for Alpha Vantage APIs"
|
|
5
|
+
requires-python = ">=3.10"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"av-api",
|
|
8
|
+
"click",
|
|
9
|
+
"httpx",
|
|
10
|
+
"loguru",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
av-cli = "av_cli.main:cli"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["hatchling"]
|
|
18
|
+
build-backend = "hatchling.build"
|
|
19
|
+
|
|
20
|
+
[tool.hatch.build.targets.wheel]
|
|
21
|
+
packages = ["src/av_cli"]
|
|
22
|
+
|
|
23
|
+
[tool.uv.sources]
|
|
24
|
+
av-api = { workspace = true }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Union, get_type_hints
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from av_cli import __version__
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _python_type_to_click(param_type):
|
|
13
|
+
"""Map a Python type hint to a Click type."""
|
|
14
|
+
if param_type == bool or param_type == 'bool':
|
|
15
|
+
return bool
|
|
16
|
+
if param_type == int or param_type == 'int':
|
|
17
|
+
return click.INT
|
|
18
|
+
if param_type == float or param_type == 'float':
|
|
19
|
+
return click.FLOAT
|
|
20
|
+
# Handle Optional[X]
|
|
21
|
+
if hasattr(param_type, '__origin__') and param_type.__origin__ is Union:
|
|
22
|
+
args = param_type.__args__
|
|
23
|
+
if len(args) == 2 and type(None) in args:
|
|
24
|
+
inner = args[0] if args[1] is type(None) else args[1]
|
|
25
|
+
return _python_type_to_click(inner)
|
|
26
|
+
return click.STRING
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _make_tool_command(func, tool_name):
|
|
30
|
+
"""Build a Click command for a single tool function."""
|
|
31
|
+
from av_api.registry import extract_description
|
|
32
|
+
|
|
33
|
+
sig = inspect.signature(func)
|
|
34
|
+
try:
|
|
35
|
+
hints = get_type_hints(func)
|
|
36
|
+
except Exception:
|
|
37
|
+
hints = {}
|
|
38
|
+
|
|
39
|
+
# Build short option flags, skipping conflicts with -h (help)
|
|
40
|
+
used_shorts = {'h'}
|
|
41
|
+
short_map = {}
|
|
42
|
+
for pname in sig.parameters:
|
|
43
|
+
for ch in pname.lower():
|
|
44
|
+
if ch.isalpha() and ch not in used_shorts:
|
|
45
|
+
used_shorts.add(ch)
|
|
46
|
+
short_map[pname] = f'-{ch}'
|
|
47
|
+
break
|
|
48
|
+
|
|
49
|
+
params = []
|
|
50
|
+
for pname, param in sig.parameters.items():
|
|
51
|
+
ptype = hints.get(pname, str)
|
|
52
|
+
click_type = _python_type_to_click(ptype)
|
|
53
|
+
short = short_map.get(pname)
|
|
54
|
+
|
|
55
|
+
if click_type is bool:
|
|
56
|
+
params.append(
|
|
57
|
+
click.Option(
|
|
58
|
+
[f'--{pname}/--no-{pname}'],
|
|
59
|
+
default=param.default if param.default is not inspect.Parameter.empty else False,
|
|
60
|
+
help=f'{pname} flag',
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
else:
|
|
64
|
+
required = param.default is inspect.Parameter.empty
|
|
65
|
+
decls = [f'--{pname}']
|
|
66
|
+
if short:
|
|
67
|
+
decls.append(short)
|
|
68
|
+
params.append(
|
|
69
|
+
click.Option(
|
|
70
|
+
decls,
|
|
71
|
+
type=click_type,
|
|
72
|
+
required=required,
|
|
73
|
+
default=None if required else param.default,
|
|
74
|
+
help=pname,
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def callback(**kwargs):
|
|
79
|
+
from av_api.context import set_api_key
|
|
80
|
+
|
|
81
|
+
ctx = click.get_current_context()
|
|
82
|
+
api_key = ctx.obj.get('api_key') or os.getenv('ALPHA_VANTAGE_API_KEY')
|
|
83
|
+
if not api_key:
|
|
84
|
+
click.echo('Error: API key required. Use --api-key or set ALPHA_VANTAGE_API_KEY.', err=True)
|
|
85
|
+
sys.exit(1)
|
|
86
|
+
|
|
87
|
+
set_api_key(api_key)
|
|
88
|
+
result = func(**kwargs)
|
|
89
|
+
|
|
90
|
+
if isinstance(result, str):
|
|
91
|
+
click.echo(result)
|
|
92
|
+
else:
|
|
93
|
+
click.echo(json.dumps(result, indent=2, default=str))
|
|
94
|
+
|
|
95
|
+
cmd = click.Command(
|
|
96
|
+
name=tool_name,
|
|
97
|
+
callback=callback,
|
|
98
|
+
params=params,
|
|
99
|
+
help=extract_description(func),
|
|
100
|
+
)
|
|
101
|
+
return cmd
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class ToolGroup(click.Group):
|
|
105
|
+
"""Click group that lazily loads tool commands from the registry."""
|
|
106
|
+
|
|
107
|
+
def __init__(self, *args, **kwargs):
|
|
108
|
+
super().__init__(*args, **kwargs)
|
|
109
|
+
self._tools_loaded = False
|
|
110
|
+
|
|
111
|
+
def _load_tools(self):
|
|
112
|
+
if self._tools_loaded:
|
|
113
|
+
return
|
|
114
|
+
self._tools_loaded = True
|
|
115
|
+
|
|
116
|
+
from av_api.registry import _all_tools_registry, ensure_tools_loaded
|
|
117
|
+
|
|
118
|
+
ensure_tools_loaded()
|
|
119
|
+
|
|
120
|
+
for func in _all_tools_registry:
|
|
121
|
+
name = func.__name__
|
|
122
|
+
cmd = _make_tool_command(func, name)
|
|
123
|
+
self.add_command(cmd)
|
|
124
|
+
|
|
125
|
+
def list_commands(self, ctx):
|
|
126
|
+
self._load_tools()
|
|
127
|
+
return super().list_commands(ctx)
|
|
128
|
+
|
|
129
|
+
def get_command(self, ctx, cmd_name):
|
|
130
|
+
self._load_tools()
|
|
131
|
+
return super().get_command(ctx, cmd_name.lower())
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@click.group(cls=ToolGroup, context_settings=dict(help_option_names=['-h', '--help']))
|
|
135
|
+
@click.version_option(version=__version__, prog_name="av-cli")
|
|
136
|
+
@click.option('--api-key', '-k', envvar='ALPHA_VANTAGE_API_KEY', help='Alpha Vantage API key')
|
|
137
|
+
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose logging')
|
|
138
|
+
@click.pass_context
|
|
139
|
+
def cli(ctx, api_key, verbose):
|
|
140
|
+
"""Alpha Vantage CLI - direct access to all Alpha Vantage API tools."""
|
|
141
|
+
ctx.ensure_object(dict)
|
|
142
|
+
ctx.obj['api_key'] = api_key
|
|
143
|
+
ctx.obj['verbose'] = verbose
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
cli()
|