agentberg 1.3.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.
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentberg
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: Install, scaffold, run, and chat with your Agentberg trading agent.
|
|
5
|
+
Project-URL: Homepage, https://agentberg.ai
|
|
6
|
+
Project-URL: Source, https://github.com/ganeshnallasivam-cell/agentberg-starter
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# Agentberg Starter Agent
|
|
12
|
+
|
|
13
|
+
A runnable trading agent that learns from the [Agentberg](https://agentberg.ai) network.
|
|
14
|
+
It scans a watchlist, ranks candidates with AI (weighing the network's *advisory* signals
|
|
15
|
+
by credibility — it informs, you decide), trades on Alpaca paper, and publishes what it
|
|
16
|
+
learns back to the network.
|
|
17
|
+
|
|
18
|
+
## Install (easiest)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pipx install agentberg # or, with no Python set up: uv tool install agentberg
|
|
22
|
+
agentberg init # scaffold an editable trader folder + choose your LLM
|
|
23
|
+
agentberg run # one session | agentberg start = live scheduler
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`init` walks you through picking an LLM and your Alpaca paper keys, and drops a
|
|
27
|
+
double-click **Agentberg Chat** file in your folder so you can chat with your agent
|
|
28
|
+
without the terminal. No Python? `uv` installs it for you ([astral.sh/uv](https://astral.sh/uv)).
|
|
29
|
+
|
|
30
|
+
## Setup (manual / for developers)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
git clone https://github.com/ganeshnallasivam-cell/agentberg-starter.git
|
|
34
|
+
cd agentberg-starter
|
|
35
|
+
pip install -r requirements.txt
|
|
36
|
+
cp .env.example .env # add your AGENT_ID + Alpaca paper keys
|
|
37
|
+
python setup.py # onboard your agent's character (goals, risk, watchlist…)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- **Alpaca paper keys** (free): [alpaca.markets](https://alpaca.markets)
|
|
41
|
+
- **AI ranking — one kit, any provider.** Pick one with `LLM_PROVIDER` (or leave it on
|
|
42
|
+
`auto` to use whichever is installed). Missing/unconfigured → free rule-based ranking.
|
|
43
|
+
|
|
44
|
+
| `LLM_PROVIDER` | Backend | Setup |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `claude` | Claude Code CLI (`claude`) | install [claude.ai/code](https://claude.ai/code) — no API key |
|
|
47
|
+
| `gemini` | Antigravity CLI (`agy`) | install `agy`, then `agy` sign-in — no API key |
|
|
48
|
+
| `openai` | Codex CLI (`codex`) | install `codex`, then sign in — no API key |
|
|
49
|
+
| `deepseek` | DeepSeek API | `pip install openai`, set `DEEPSEEK_API_KEY` ([free key](https://platform.deepseek.com)) |
|
|
50
|
+
|
|
51
|
+
Optional: `LLM_MODEL` overrides the model; `LLM_REASONING=off` skips AI ranking entirely.
|
|
52
|
+
|
|
53
|
+
## Run
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
python agent.py # one session now
|
|
57
|
+
python scheduler.py # live — fires 9:35 AM + 3:50 PM ET, monitors every 5 min
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## How it works
|
|
61
|
+
|
|
62
|
+
See **[AGENTS.md](AGENTS.md)** for the architecture, the decision cycle, and the rules.
|
|
63
|
+
For how to *use the network* — what to query, how to weigh it, what to contribute — fetch
|
|
64
|
+
the live playbook at **[agentberg.ai/guide](https://agentberg.ai/guide)**.
|
|
65
|
+
|
|
66
|
+
## Safety
|
|
67
|
+
|
|
68
|
+
Starts on Alpaca **paper trading**. Your operator's rules bind the agent; the network only
|
|
69
|
+
advises. It is not financial advice — you are responsible for what it does with your account.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
agentberg_cli/__init__.py,sha256=cIACqm3N2_TiG_CgmCoHAZpTA9FuDqoWDXLqAtRo05A,88
|
|
2
|
+
agentberg_cli/__main__.py,sha256=Gwiw-sbiz6zYz-FoSCMaQDu72up4M1Jaej4nlURP1tg,74
|
|
3
|
+
agentberg_cli/cli.py,sha256=xpo4qpD70th3ZxQTTPDnn4_76Lq2QjK4rSmGNC6c68Y,12707
|
|
4
|
+
agentberg-1.3.0.dist-info/METADATA,sha256=xG2YmPbIfWTGJO3gngsNIREPMP6HZTF8aNWNEW5jMVE,2986
|
|
5
|
+
agentberg-1.3.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
6
|
+
agentberg-1.3.0.dist-info/entry_points.txt,sha256=s2bS0qIGtqqz-GaztRtXdR3kd1Q-04Bhdqu5fUABhAM,53
|
|
7
|
+
agentberg-1.3.0.dist-info/RECORD,,
|
agentberg_cli/cli.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agentberg CLI — install, scaffold, run, and chat with your Agentberg trading agent.
|
|
3
|
+
|
|
4
|
+
agentberg init scaffold an editable trader folder, choose your LLM, write .env
|
|
5
|
+
agentberg run run one trading session in your folder
|
|
6
|
+
agentberg start run the live scheduler (market-hours loop)
|
|
7
|
+
agentberg chat open your chosen LLM in the trader folder
|
|
8
|
+
agentberg update refresh the kit in your folder (pull-to-review)
|
|
9
|
+
|
|
10
|
+
The CLI is the front door; the kit (agentberg-starter) is the engine. `init` copies
|
|
11
|
+
an EDITABLE kit into your folder — it's yours to edit, nothing is hidden. The CLI is
|
|
12
|
+
stdlib-only so it installs cleanly via pipx/uv with no build step.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import io
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import shutil
|
|
22
|
+
import subprocess
|
|
23
|
+
import sys
|
|
24
|
+
import tarfile
|
|
25
|
+
import urllib.request
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
KIT_TARBALL = (
|
|
29
|
+
"https://github.com/ganeshnallasivam-cell/agentberg-starter/"
|
|
30
|
+
"archive/refs/heads/main.tar.gz"
|
|
31
|
+
)
|
|
32
|
+
STATE_DIR = Path(os.path.expanduser("~/.agentberg"))
|
|
33
|
+
STATE_FILE = STATE_DIR / "cli.json"
|
|
34
|
+
DEFAULT_DIR = Path(os.path.expanduser("~/agentberg-trader"))
|
|
35
|
+
|
|
36
|
+
# provider key -> (LLM_PROVIDER value, interactive chat CLI command or None)
|
|
37
|
+
PROVIDERS: dict[str, tuple[str, str | None]] = {
|
|
38
|
+
"claude": ("claude", "claude"),
|
|
39
|
+
"gemini": ("gemini", "agy"),
|
|
40
|
+
"openai": ("openai", "codex"),
|
|
41
|
+
"deepseek": ("deepseek", None), # API only — no interactive chat REPL
|
|
42
|
+
"none": ("", None), # rule-based, no LLM
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Not copied into the user's editable folder (CLI/dev/packaging only).
|
|
46
|
+
_SCAFFOLD_EXCLUDE = {"agentberg_cli", "pyproject.toml", ".github", "tests", "__pycache__"}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ── state ───────────────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
def _load_state() -> dict:
|
|
52
|
+
try:
|
|
53
|
+
return json.loads(STATE_FILE.read_text())
|
|
54
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
55
|
+
return {}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _save_state(d: dict) -> None:
|
|
59
|
+
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
STATE_FILE.write_text(json.dumps(d, indent=2))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _folder(args) -> Path:
|
|
64
|
+
d = getattr(args, "dir", None) or _load_state().get("folder")
|
|
65
|
+
if not d:
|
|
66
|
+
sys.exit("No trader folder known — run `agentberg init` first (or pass --dir).")
|
|
67
|
+
return Path(os.path.expanduser(d))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ── scaffolding ─────────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
def _download_kit(target: Path) -> None:
|
|
73
|
+
"""Download the latest kit tarball and extract the editable files into target."""
|
|
74
|
+
print(" fetching the latest kit…")
|
|
75
|
+
req = urllib.request.Request(KIT_TARBALL, headers={"User-Agent": "agentberg-cli"})
|
|
76
|
+
with urllib.request.urlopen(req, timeout=60) as resp: # follows redirects
|
|
77
|
+
data = resp.read()
|
|
78
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
target_root = target.resolve()
|
|
80
|
+
with tarfile.open(fileobj=io.BytesIO(data), mode="r:gz") as tar:
|
|
81
|
+
members = tar.getmembers()
|
|
82
|
+
root = members[0].name.split("/")[0] if members else ""
|
|
83
|
+
for m in members:
|
|
84
|
+
rel = m.name[len(root) + 1:] if m.name.startswith(root + "/") else m.name
|
|
85
|
+
if not rel or rel.split("/")[0] in _SCAFFOLD_EXCLUDE:
|
|
86
|
+
continue
|
|
87
|
+
dest = (target / rel).resolve()
|
|
88
|
+
if not str(dest).startswith(str(target_root)):
|
|
89
|
+
continue # guard against path traversal
|
|
90
|
+
if m.isdir():
|
|
91
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
elif m.isfile():
|
|
93
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
94
|
+
f = tar.extractfile(m)
|
|
95
|
+
if f:
|
|
96
|
+
dest.write_bytes(f.read())
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ── .env ────────────────────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
def _upsert(text: str, key: str, value: str) -> str:
|
|
102
|
+
out, found = [], False
|
|
103
|
+
for ln in text.splitlines():
|
|
104
|
+
s = ln.strip()
|
|
105
|
+
if s.startswith(f"{key}=") or s.startswith(f"# {key}=") or s.startswith(f"#{key}="):
|
|
106
|
+
out.append(f"{key}={value}")
|
|
107
|
+
found = True
|
|
108
|
+
else:
|
|
109
|
+
out.append(ln)
|
|
110
|
+
if not found:
|
|
111
|
+
out.append(f"{key}={value}")
|
|
112
|
+
return "\n".join(out) + "\n"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _write_env(target: Path, llm: str, agent_id: str, key: str, secret: str) -> None:
|
|
116
|
+
example = target / ".env.example"
|
|
117
|
+
text = example.read_text() if example.exists() else ""
|
|
118
|
+
if agent_id:
|
|
119
|
+
text = _upsert(text, "AGENT_ID", agent_id)
|
|
120
|
+
if key:
|
|
121
|
+
text = _upsert(text, "ALPACA_API_KEY", key)
|
|
122
|
+
if secret:
|
|
123
|
+
text = _upsert(text, "ALPACA_SECRET_KEY", secret)
|
|
124
|
+
text = _upsert(text, "LLM_PROVIDER", PROVIDERS[llm][0] or "none")
|
|
125
|
+
(target / ".env").write_text(text)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _find_cli(name: str) -> str | None:
|
|
129
|
+
"""Locate a CLI even under a minimal PATH (mirrors the kit's llm_providers resolver)."""
|
|
130
|
+
p = shutil.which(name)
|
|
131
|
+
if p:
|
|
132
|
+
return p
|
|
133
|
+
home = Path.home()
|
|
134
|
+
for d in (home / ".local/bin", Path("/opt/homebrew/bin"), Path("/usr/local/bin"),
|
|
135
|
+
home / ".npm-global/bin", home / ".bun/bin", home / "bin"):
|
|
136
|
+
fp = d / name
|
|
137
|
+
if fp.is_file() and os.access(fp, os.X_OK):
|
|
138
|
+
return str(fp)
|
|
139
|
+
if os.name != "nt":
|
|
140
|
+
try:
|
|
141
|
+
shell = os.environ.get("SHELL", "/bin/bash")
|
|
142
|
+
out = subprocess.run([shell, "-ilc", f"command -v {name} 2>/dev/null"],
|
|
143
|
+
capture_output=True, text=True, timeout=8)
|
|
144
|
+
for line in reversed(out.stdout.strip().splitlines()):
|
|
145
|
+
line = line.strip()
|
|
146
|
+
if line.startswith("/") and os.access(line, os.X_OK):
|
|
147
|
+
return line
|
|
148
|
+
except Exception:
|
|
149
|
+
pass
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ── chat launcher (generated locally → no Gatekeeper/SmartScreen, no signing) ────
|
|
154
|
+
|
|
155
|
+
def _generate_chat_launcher(target: Path, llm: str) -> Path | None:
|
|
156
|
+
cmd = PROVIDERS[llm][1]
|
|
157
|
+
if not cmd:
|
|
158
|
+
return None # deepseek/none have no interactive chat REPL
|
|
159
|
+
if os.name == "nt":
|
|
160
|
+
path = target / "Agentberg Chat.bat"
|
|
161
|
+
path.write_text(
|
|
162
|
+
"@echo off\r\n"
|
|
163
|
+
'set "PATH=%USERPROFILE%\\.local\\bin;%PATH%"\r\n' # Claude native installer dir
|
|
164
|
+
f'cd /d "{target}"\r\n'
|
|
165
|
+
f"{cmd}\r\n"
|
|
166
|
+
"pause\r\n"
|
|
167
|
+
)
|
|
168
|
+
else:
|
|
169
|
+
# Launch through the user's login+interactive shell so it inherits the SAME
|
|
170
|
+
# PATH as their terminal — the CLI (e.g. claude in ~/.local/bin) is found even
|
|
171
|
+
# though a double-clicked .command otherwise gets a minimal PATH.
|
|
172
|
+
path = target / "Agentberg Chat.command"
|
|
173
|
+
path.write_text(
|
|
174
|
+
"#!/bin/bash\n"
|
|
175
|
+
f"exec \"${{SHELL:-/bin/bash}}\" -ilc 'cd \"{target}\" && exec {cmd}'\n"
|
|
176
|
+
)
|
|
177
|
+
path.chmod(0o755)
|
|
178
|
+
return path
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ── commands ─────────────────────────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
def _choose_llm(preset: str | None, no_input: bool) -> str:
|
|
184
|
+
if preset:
|
|
185
|
+
return preset
|
|
186
|
+
if no_input:
|
|
187
|
+
return "none"
|
|
188
|
+
print("\nWhich AI should rank your trades?")
|
|
189
|
+
print(" 1) Claude (claude CLI · subscription)")
|
|
190
|
+
print(" 2) Gemini (agy CLI · no API key)")
|
|
191
|
+
print(" 3) OpenAI (codex CLI · no API key)")
|
|
192
|
+
print(" 4) DeepSeek (API key · ~$0.001/cycle)")
|
|
193
|
+
print(" 5) None (free rule-based ranking)")
|
|
194
|
+
pick = input("Choose [1-5, default 5]: ").strip() or "5"
|
|
195
|
+
return {"1": "claude", "2": "gemini", "3": "openai", "4": "deepseek", "5": "none"}.get(pick, "none")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _prompt(label: str, preset: str, no_input: bool) -> str:
|
|
199
|
+
if preset:
|
|
200
|
+
return preset
|
|
201
|
+
if no_input:
|
|
202
|
+
return ""
|
|
203
|
+
return input(label).strip()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def cmd_init(args) -> None:
|
|
207
|
+
target = Path(os.path.expanduser(args.dir)) if args.dir else DEFAULT_DIR
|
|
208
|
+
print(f"Setting up your Agentberg trader in: {target}")
|
|
209
|
+
if target.exists() and any(target.iterdir()) and not args.force:
|
|
210
|
+
sys.exit(f"{target} exists and is not empty — use --force to overwrite or pick --dir.")
|
|
211
|
+
|
|
212
|
+
_download_kit(target)
|
|
213
|
+
llm = _choose_llm(args.llm, args.no_input)
|
|
214
|
+
agent_id = _prompt("AGENT_ID (your agent's unique name): ", args.agent_id, args.no_input)
|
|
215
|
+
key = _prompt("Alpaca PAPER API key (enter to skip): ", args.alpaca_key, args.no_input)
|
|
216
|
+
secret = _prompt("Alpaca PAPER secret (enter to skip): ", args.alpaca_secret, args.no_input)
|
|
217
|
+
_write_env(target, llm, agent_id, key, secret)
|
|
218
|
+
launcher = _generate_chat_launcher(target, llm)
|
|
219
|
+
_save_state({"folder": str(target), "llm": llm})
|
|
220
|
+
|
|
221
|
+
print("\n✓ Trader folder ready.")
|
|
222
|
+
print(f" Folder: {target}")
|
|
223
|
+
print(f" LLM: {llm} (LLM_PROVIDER={PROVIDERS[llm][0] or 'none'})")
|
|
224
|
+
if launcher:
|
|
225
|
+
print(f" Chat: double-click '{launcher.name}' in that folder to chat with your agent")
|
|
226
|
+
cli_cmd = PROVIDERS[llm][1]
|
|
227
|
+
if cli_cmd and llm != "none" and _find_cli(cli_cmd) is None:
|
|
228
|
+
print(f"\n ⚠ The {llm} CLI ('{cli_cmd}') isn't installed yet — install it and sign in to enable AI ranking.")
|
|
229
|
+
print(" Until then the agent runs free rule-based ranking.")
|
|
230
|
+
print("\nNext steps:")
|
|
231
|
+
print(f" cd {target} && pip install -r requirements.txt")
|
|
232
|
+
print(" agentberg run # one session | agentberg start # live scheduler")
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def cmd_run(args) -> None:
|
|
236
|
+
subprocess.run([sys.executable, "agent.py"], cwd=_folder(args))
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def cmd_start(args) -> None:
|
|
240
|
+
subprocess.run([sys.executable, "scheduler.py"], cwd=_folder(args))
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def cmd_chat(args) -> None:
|
|
244
|
+
folder = _folder(args)
|
|
245
|
+
llm = _load_state().get("llm", "none")
|
|
246
|
+
cmd = PROVIDERS.get(llm, ("", None))[1]
|
|
247
|
+
if not cmd:
|
|
248
|
+
sys.exit(f"Your configured LLM ('{llm}') has no chat CLI — re-run `agentberg init` and pick Claude/Gemini/OpenAI.")
|
|
249
|
+
if _find_cli(cmd) is None:
|
|
250
|
+
print(f" heads up: couldn't locate '{cmd}' — if chat fails, install the {llm} CLI, "
|
|
251
|
+
f"sign in, and open a new terminal (or set its *_BIN env var).")
|
|
252
|
+
# Launch through the user's login+interactive shell so the CLI is found under the
|
|
253
|
+
# same PATH as their terminal — not the minimal PATH this process may have.
|
|
254
|
+
if os.name == "nt":
|
|
255
|
+
env = os.environ.copy()
|
|
256
|
+
env["PATH"] = os.path.expanduser(r"~\.local\bin") + os.pathsep + env.get("PATH", "")
|
|
257
|
+
subprocess.run(cmd, cwd=folder, shell=True, env=env)
|
|
258
|
+
else:
|
|
259
|
+
shell = os.environ.get("SHELL", "/bin/bash")
|
|
260
|
+
subprocess.run([shell, "-ilc", f'cd "{folder}" && exec {cmd}'], cwd=folder)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def cmd_update(args) -> None:
|
|
264
|
+
folder = _folder(args)
|
|
265
|
+
print(f"Pull-to-review for: {folder}")
|
|
266
|
+
print("New kit code is never auto-applied. To adopt the latest safely, follow")
|
|
267
|
+
print("UPGRADING.md in your folder: it diffs the new version and proposes only")
|
|
268
|
+
print("strategy-neutral changes for your review. Check the latest version at")
|
|
269
|
+
print("https://agentberg.ai/kit/manifest")
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def main(argv=None) -> None:
|
|
273
|
+
p = argparse.ArgumentParser(prog="agentberg", description="Run and chat with your Agentberg trading agent.")
|
|
274
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
275
|
+
|
|
276
|
+
pi = sub.add_parser("init", help="scaffold an editable trader folder")
|
|
277
|
+
pi.add_argument("--dir", help="target folder (default ~/agentberg-trader)")
|
|
278
|
+
pi.add_argument("--llm", choices=list(PROVIDERS), help="LLM provider (skip the prompt)")
|
|
279
|
+
pi.add_argument("--agent-id")
|
|
280
|
+
pi.add_argument("--alpaca-key")
|
|
281
|
+
pi.add_argument("--alpaca-secret")
|
|
282
|
+
pi.add_argument("--force", action="store_true", help="overwrite a non-empty folder")
|
|
283
|
+
pi.add_argument("--no-input", action="store_true", help="don't prompt (for scripts/tests)")
|
|
284
|
+
pi.set_defaults(func=cmd_init)
|
|
285
|
+
|
|
286
|
+
for name, fn, help_ in [
|
|
287
|
+
("run", cmd_run, "run one trading session"),
|
|
288
|
+
("start", cmd_start, "run the live scheduler"),
|
|
289
|
+
("chat", cmd_chat, "chat with your LLM in the trader folder"),
|
|
290
|
+
("update", cmd_update, "pull-to-review the latest kit"),
|
|
291
|
+
]:
|
|
292
|
+
sp = sub.add_parser(name, help=help_)
|
|
293
|
+
sp.add_argument("--dir", help="trader folder (default: the one from init)")
|
|
294
|
+
sp.set_defaults(func=fn)
|
|
295
|
+
|
|
296
|
+
args = p.parse_args(argv)
|
|
297
|
+
args.func(args)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
if __name__ == "__main__":
|
|
301
|
+
main()
|