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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ agentberg = agentberg_cli.cli:main
@@ -0,0 +1,3 @@
1
+ """agentberg — CLI front door to the Agentberg trading kit."""
2
+
3
+ __version__ = "1.3.0"
@@ -0,0 +1,4 @@
1
+ from agentberg_cli.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
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()