@clawpump/claw-agent 0.1.1 → 0.1.3

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.
package/agent/README.md CHANGED
@@ -15,13 +15,13 @@
15
15
  <a href="https://github.com/NousResearch/hermes-agent">Upstream: Hermes</a>
16
16
  </p>
17
17
 
18
- ClawPump's full feature set — **104 MCP tools** spanning agents, trading, Phoenix perps, DCA, Jupiter lending, token launch, marketplace, predictions, and market intelligence — is wired in natively. One command:
18
+ ClawPump's full feature set — **131 MCP tools** spanning agents, trading, Phoenix perps, DCA, Jupiter lending, token launch, marketplace, predictions, gift cards (Laso), agent mail, wallet transfers, pay.sh x402 paid APIs, and market intelligence — is wired in natively. One command:
19
19
 
20
20
  ```bash
21
21
  hermes clawpump setup # pick remote OAuth (browser) or stdio (cpk_ key)
22
22
  ```
23
23
 
24
- That pairs the agent with the ClawPump MCP — remote `https://mcp.clawpump.tech/mcp`, or the local `npx @clawpump/agents` stdio path — and the ClawPump green theme is on by default. See the [ClawPump skill](skills/clawpump/SKILL.md) and the catalog entries under [`optional-mcps/`](optional-mcps/) (`clawpump`, `clawpump-stdio`).
24
+ That pairs the agent with the ClawPump MCP — remote `https://mcp.clawpump.tech/mcp`, or the local `npx @clawpump/agents` stdio path — and the ClawPump green theme is on by default. See the [ClawPump skill](skills/clawpump/SKILL.md) and the catalog entries under [`optional-mcps/`](optional-mcps/) (`clawpump`, `clawpump-stdio`). [pay.sh](https://pay.sh) x402 paid APIs are paid straight from the agent wallet via the `pay_sh_*` tools — no separate wallet.
25
25
 
26
26
  > **Built on Hermes.** ClawPump is a downstream distribution of the **Hermes Agent** by **Nous Research**, used under the MIT License. The upstream agent — its runtime, docs, and features — is unchanged below; ClawPump adds the native ClawPump MCP integration plus init/theme branding on top. Full credit for the underlying agent goes to Nous Research. See [`LICENSE`](LICENSE).
27
27
 
@@ -0,0 +1,58 @@
1
+ # Keeping ClawPump in sync with upstream Hermes
2
+
3
+ ClawPump (`Clawpump/claw-agent`) is a **fork of [`NousResearch/hermes-agent`](https://github.com/NousResearch/hermes-agent)** that shares full git history with upstream. Upstream ships very frequently (CalVer, ~daily), so we pull its changes in continuously rather than letting the fork drift.
4
+
5
+ ## How divergence is kept small
6
+
7
+ All ClawPump-specific behavior lives in a single downstream-owned module, **`hermes_cli/distribution.py`** (skin, default skin, default MCP server, `CLAWPUMP_*` env vars, the `clawpump` subcommand, the npm self-update, the update-check repo URL). The upstream-owned files (`skin_engine.py`, `config.py`, `main.py`, `banner.py`) carry only a small, self-degrading hook each and otherwise match vanilla Hermes byte-for-byte.
8
+
9
+ **Rule of thumb:** never edit an upstream-owned file to add ClawPump behavior. Add it to `distribution.py` and wire a one-line hook. The smaller the divergence in upstream files, the cleaner every sync.
10
+
11
+ ## Automated sync (`.github/workflows/upstream-sync.yml`)
12
+
13
+ Runs daily (and via **Actions → Upstream Sync → Run workflow**). Each run:
14
+
15
+ 1. Fetches `upstream/main` and checks how far `main` is behind.
16
+ 2. If behind **and** no `upstream-sync` PR is already open, creates a `sync/upstream-<timestamp>` branch and **merges** `upstream/main` into it (a real merge commit, to preserve history).
17
+ 3. Opens a PR into `main`, labelled `upstream-sync` (and `has-conflicts` if the merge had conflicts — the markers are committed so you can resolve them in the branch).
18
+
19
+ It is self-throttling: only one open sync PR at a time, and it no-ops when already up to date.
20
+
21
+ ### Reviewing / merging a sync PR
22
+
23
+ - **Clean merge:** review the diff + CI, then **merge with a merge commit** (not squash — squashing discards the upstream parent and breaks future merge-bases).
24
+ - **Conflicts:** they're almost always in docs (`README.md`, `.env.example`). Resolve in the branch:
25
+ ```bash
26
+ git fetch origin && git switch sync/upstream-<timestamp>
27
+ # resolve conflicts; for docs you usually keep ours
28
+ git add -A && git commit && git push
29
+ ```
30
+ If a conflict lands in an upstream-owned `.py` file, that's a signal a downstream edit leaked in — prefer moving that edit into `distribution.py` so it won't conflict again.
31
+
32
+ ### One-time setup for full CI on sync PRs
33
+
34
+ PRs opened with the default `GITHUB_TOKEN` do **not** trigger other workflows, so the repo's CI (tests / lint / branding) won't auto-run on a sync PR. To get CI as the merge gate, add a repo secret **`UPSTREAM_SYNC_PAT`** (a fine-scoped PAT with `repo` + `workflow`); the workflow uses it automatically. Without it, re-run CI on the sync PR manually.
35
+
36
+ ### Caveats
37
+
38
+ - **`contributor-check`** requires every new commit-author email to be in `AUTHOR_MAP` (`scripts/release.py`). A sync that brings in commits from a *new* upstream contributor will fail this check until you add their mapping — the check prints the exact lines to paste.
39
+ - **`history-check`** requires a common ancestor with `main`; a real merge from our shared-history upstream always satisfies it.
40
+
41
+ ## Manual sync (by hand)
42
+
43
+ ```bash
44
+ # one-time
45
+ git remote add upstream https://github.com/NousResearch/hermes-agent.git
46
+
47
+ git fetch --no-tags upstream main
48
+ git switch -c sync/upstream-manual main
49
+ git merge upstream/main
50
+ # resolve any conflicts (usually README.md / .env.example), then:
51
+ git commit
52
+ git push -u origin sync/upstream-manual
53
+ # open a PR into main
54
+ ```
55
+
56
+ ## Versioning
57
+
58
+ Tag ClawPump releases off the upstream CalVer base plus a suffix, e.g. `v2026.6.5-clawpump.1`, so it's always clear which upstream snapshot a release is built on.
@@ -531,6 +531,33 @@ def _resolve_kimi_base_url(api_key: str, default_url: str, env_override: str) ->
531
531
  return default_url
532
532
 
533
533
 
534
+ # =============================================================================
535
+ # UsePod Endpoint Derivation
536
+ # =============================================================================
537
+
538
+ # UsePod (usepod.ai) is a drop-in OpenAI-compatible proxy that authenticates by
539
+ # the token embedded in the request PATH — ``/proxy/<token>/v1/...`` — and
540
+ # ignores the Authorization header. The inference base URL is therefore derived
541
+ # from the API key (the token) rather than stored as a static endpoint, so the
542
+ # user only ever needs to paste their token.
543
+ USEPOD_API_BASE = "https://api.usepod.ai"
544
+
545
+
546
+ def _resolve_usepod_base_url(api_key: str, env_override: str = "") -> str:
547
+ """Return the UsePod proxy base URL for *api_key* (the token).
548
+
549
+ An explicit ``USEPOD_BASE_URL`` (``env_override``) always wins — e.g. for a
550
+ self-hosted UsePod gateway. With no token yet, return the proxy root so
551
+ callers degrade gracefully instead of producing a malformed URL.
552
+ """
553
+ if env_override:
554
+ return env_override.rstrip("/")
555
+ token = (api_key or "").strip()
556
+ if not token:
557
+ return f"{USEPOD_API_BASE}/proxy"
558
+ return f"{USEPOD_API_BASE}/proxy/{token}/v1"
559
+
560
+
534
561
 
535
562
  _PLACEHOLDER_SECRET_VALUES = {
536
563
  "*",
@@ -5657,6 +5684,8 @@ def get_api_key_provider_status(provider_id: str) -> Dict[str, Any]:
5657
5684
 
5658
5685
  if provider_id in {"kimi-coding", "kimi-coding-cn"}:
5659
5686
  base_url = _resolve_kimi_base_url(api_key, pconfig.inference_base_url, env_url)
5687
+ elif provider_id == "usepod":
5688
+ base_url = _resolve_usepod_base_url(api_key, env_url)
5660
5689
  elif env_url:
5661
5690
  base_url = env_url
5662
5691
  else:
@@ -5848,6 +5877,8 @@ def resolve_api_key_provider_credentials(provider_id: str) -> Dict[str, Any]:
5848
5877
  base_url = _resolve_kimi_base_url(api_key, pconfig.inference_base_url, env_url)
5849
5878
  elif provider_id == "zai":
5850
5879
  base_url = _resolve_zai_base_url(api_key, pconfig.inference_base_url, env_url)
5880
+ elif provider_id == "usepod":
5881
+ base_url = _resolve_usepod_base_url(api_key, env_url)
5851
5882
  elif env_url:
5852
5883
  base_url = env_url.rstrip("/")
5853
5884
  else:
@@ -129,7 +129,10 @@ _UPDATE_CHECK_CACHE_SECONDS = 6 * 3600
129
129
  # (e.g. nix-built hermes — no local git history to count against).
130
130
  UPDATE_AVAILABLE_NO_COUNT = -1
131
131
 
132
- _UPSTREAM_REPO_URL = "https://github.com/Clawpump/claw-agent.git"
132
+ try:
133
+ from hermes_cli.distribution import UPDATE_REPO_URL as _UPSTREAM_REPO_URL
134
+ except Exception:
135
+ _UPSTREAM_REPO_URL = "https://github.com/NousResearch/hermes-agent.git"
133
136
 
134
137
 
135
138
  def _check_via_rev(local_rev: str) -> Optional[int]:
@@ -1,6 +1,6 @@
1
1
  """``hermes clawpump`` — one-command install/auth for the ClawPump MCP.
2
2
 
3
- ClawPump ships its full feature set (104 tools) as an MCP server, bundled in
3
+ ClawPump ships its full feature set (131 tools) as an MCP server, bundled in
4
4
  the Hermes catalog as two entries:
5
5
 
6
6
  - ``clawpump`` remote OAuth over Streamable HTTP (browser, paste cpk_*)
@@ -59,9 +59,10 @@ def _cmd_setup(args) -> int:
59
59
  print()
60
60
  print(color(" ClawPump setup", Colors.GREEN + Colors.BOLD))
61
61
  print(color(" ──────────────", Colors.GREEN))
62
- print(" ClawPump's 104 tools (agents, trading, perps, DCA, lending,")
63
- print(" token launch, marketplace, predictions, intelligence) come in")
64
- print(" through an MCP server. Two ways to connect:")
62
+ print(" ClawPump's 131 tools (agents, trading, perps, DCA, lending,")
63
+ print(" token launch, marketplace, predictions, gift cards, agent")
64
+ print(" mail, intelligence) come in through an MCP server. Two ways")
65
+ print(" to connect:")
65
66
  print()
66
67
  print(f" 1) {color('Remote', Colors.GREEN)} — browser login, paste your cpk_* key (recommended)")
67
68
  print(f" 2) {color('Stdio', Colors.GREEN)} — local npx, store the cpk_* key in ~/.hermes/.env")
@@ -220,7 +221,7 @@ def add_parser(subparsers) -> None:
220
221
  help="ClawPump (Solana token launch, trading, perps, DeFi) via MCP",
221
222
  description=(
222
223
  "Install and authenticate the ClawPump MCP server, then manage which "
223
- "of its 104 tools are enabled. Subcommands: setup (default: status), "
224
+ "of its 131 tools are enabled. Subcommands: setup (default: status), "
224
225
  "login, status, tools."
225
226
  ),
226
227
  )
@@ -741,23 +741,7 @@ DEFAULT_CONFIG = {
741
741
  "fallback_providers": [],
742
742
  "credential_pool_strategies": {},
743
743
  "toolsets": ["hermes-cli"],
744
- # ClawPump is enabled by default in this distribution: the remote MCP
745
- # server (the full ClawPump tool surface) comes pre-wired. On first connect
746
- # Hermes opens a browser to log in to ClawPump — that one step is per-user
747
- # auth and cannot be skipped — after which the mcp_clawpump_* tools load
748
- # automatically. The bundled `clawpump` skill requires explicit user
749
- # confirmation before any financial/irreversible tool runs. Prune the tool
750
- # set any time with `hermes mcp configure clawpump`.
751
- "mcp_servers": {
752
- "clawpump": {
753
- # TODO: switch to https://mcp.clawpump.tech/mcp once the DNS CNAME
754
- # is configured; the custom domain is currently NXDOMAIN, so point
755
- # at the live Railway domain so a fresh install connects.
756
- "url": "https://clawpump-mcp-production.up.railway.app/mcp",
757
- "auth": "oauth",
758
- "enabled": True,
759
- },
760
- },
744
+ "mcp_servers": {},
761
745
  "agent": {
762
746
  "max_turns": 90,
763
747
  # Inactivity timeout for gateway agent execution (seconds).
@@ -1337,7 +1321,7 @@ DEFAULT_CONFIG = {
1337
1321
  # failure isn't silent from the UI's perspective. Set false to suppress.
1338
1322
  "turn_completion_explainer": True,
1339
1323
  "show_cost": False, # Show $ cost in the status bar (off by default)
1340
- "skin": "clawpump", # ClawPump brand (green claw). Switch with /skin or display.skin.
1324
+ "skin": "default", # distribution default (clawpump) applied via distribution.apply_config_overlay
1341
1325
  # UI language for static user-facing messages (approval prompts, a
1342
1326
  # handful of gateway slash-command replies). Does NOT affect agent
1343
1327
  # responses, log lines, tool outputs, or slash-command descriptions.
@@ -3395,38 +3379,21 @@ OPTIONAL_ENV_VARS = {
3395
3379
  "password": False,
3396
3380
  "category": "setting",
3397
3381
  },
3398
-
3399
- # ── ClawPump (Solana token launch, trading, perps, DeFi) ──
3400
- # Used by the stdio ClawPump MCP path (`npx @clawpump/agents`, catalog
3401
- # entry `clawpump-stdio`). The remote OAuth path (`clawpump`) stores
3402
- # per-user tokens under ~/.hermes/mcp-tokens/ instead and needs no key
3403
- # here. The ClawPump tools themselves come from the MCP server, not the
3404
- # native registry, so there's no `tools` list.
3405
- "CLAWPUMP_API_KEY": {
3406
- "description": "ClawPump API key (cpk_*) for the ClawPump MCP stdio transport",
3407
- "prompt": "ClawPump API key (cpk_…)",
3408
- "url": "https://agents.clawpump.tech/dashboard/api",
3409
- "password": True,
3410
- "category": "tools",
3411
- },
3412
- "CLAWPUMP_API_URL": {
3413
- "description": "ClawPump backend URL override (advanced — leave empty for the default)",
3414
- "prompt": "ClawPump backend URL (leave empty for default)",
3415
- "url": None,
3416
- "password": False,
3417
- "category": "tools",
3418
- "advanced": True,
3419
- },
3420
- "CLAWPUMP_DEFAULT_AGENT": {
3421
- "description": "Default ClawPump agent id (optional — skips agent-selection prompts)",
3422
- "prompt": "Default ClawPump agent id (optional)",
3423
- "url": None,
3424
- "password": False,
3425
- "category": "tools",
3426
- "advanced": True,
3427
- },
3428
3382
  }
3429
3383
 
3384
+ # ── ClawPump distribution overlay (downstream) ───────────────────────────
3385
+ # Apply ClawPump's defaults (pre-wired MCP server + default skin) and env-var
3386
+ # registry from the downstream-owned overlay, keeping this upstream-owned file
3387
+ # mergeable. No-ops on vanilla Hermes (module/keys absent).
3388
+ try:
3389
+ from hermes_cli import distribution as _distribution
3390
+
3391
+ _distribution.apply_config_overlay(DEFAULT_CONFIG)
3392
+ _distribution.apply_env_var_overlay(OPTIONAL_ENV_VARS)
3393
+ except Exception:
3394
+ pass
3395
+
3396
+
3430
3397
  # Tool Gateway env vars are always visible — they're useful for
3431
3398
  # self-hosted / custom gateway setups regardless of subscription state.
3432
3399
 
@@ -0,0 +1,230 @@
1
+ """ClawPump distribution overlay (downstream-owned).
2
+
3
+ This module is the single home for everything that makes this distribution
4
+ "ClawPump" rather than vanilla Hermes. Keeping it here -- instead of editing
5
+ upstream-owned files (skin_engine.py, config.py, ...) -- keeps those files
6
+ byte-for-byte mergeable with NousResearch/hermes-agent, so syncing upstream
7
+ stays (near) conflict-free.
8
+
9
+ Every upstream consumer imports this lazily and degrades to vanilla Hermes
10
+ when the module (or a given key) is absent. Keep imports light: config.py
11
+ imports this at startup.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from typing import Any, Dict
17
+
18
+ # Default skin for this distribution (vanilla Hermes uses "default").
19
+ DEFAULT_SKIN = "clawpump"
20
+
21
+ # Git remote the in-app update check compares against (vanilla Hermes uses
22
+ # NousResearch/hermes-agent).
23
+ UPDATE_REPO_URL = "https://github.com/Clawpump/claw-agent.git"
24
+
25
+ # Extra top-level CLI subcommands this distribution adds (kept in sync with
26
+ # main.py's _BUILTIN_SUBCOMMANDS validation set via a hook there).
27
+ EXTRA_SUBCOMMANDS = ("clawpump",)
28
+
29
+ # ── Skins shipped by this distribution ───────────────────────────────────
30
+ # Moved verbatim out of hermes_cli/skin_engine.py:_BUILTIN_SKINS and merged
31
+ # back into that dict by a small hook there, so every skin consumer
32
+ # (list_skins / load_skin / get_active_skin) sees it unchanged.
33
+ BUILTIN_SKINS: Dict[str, Dict[str, Any]] = {
34
+ "clawpump": {
35
+ "name": "clawpump",
36
+ "description": "ClawPump — Solana green, claw mark (built on Hermes)",
37
+ "colors": {
38
+ "banner_border": "#16A34A",
39
+ "banner_title": "#4ADE80",
40
+ "banner_accent": "#22C55E",
41
+ "banner_dim": "#15803D",
42
+ "banner_text": "#DCFCE7",
43
+ "ui_accent": "#22C55E",
44
+ "ui_label": "#4ADE80",
45
+ "ui_ok": "#22C55E",
46
+ "ui_error": "#ef5350",
47
+ "ui_warn": "#ffa726",
48
+ "prompt": "#DCFCE7",
49
+ "input_rule": "#16A34A",
50
+ "response_border": "#4ADE80",
51
+ "status_bar_bg": "#0B1F14",
52
+ "status_bar_text": "#DCFCE7",
53
+ "status_bar_strong": "#4ADE80",
54
+ "status_bar_dim": "#3F6B50",
55
+ "status_bar_good": "#22C55E",
56
+ "status_bar_warn": "#FACC15",
57
+ "status_bar_bad": "#F59E0B",
58
+ "status_bar_critical": "#EF5350",
59
+ "session_label": "#4ADE80",
60
+ "session_border": "#3F6B50",
61
+ "selection_bg": "#14532D",
62
+ "completion_menu_bg": "#06140C",
63
+ "completion_menu_current_bg": "#14532D",
64
+ "completion_menu_meta_bg": "#0B1F14",
65
+ "completion_menu_meta_current_bg": "#14532D",
66
+ },
67
+ "spinner": {
68
+ "waiting_faces": ["(◴)", "(◷)", "(◶)", "(◵)", "(<>)"],
69
+ "thinking_faces": ["(✦)", "(◇)", "(◈)", "(⌁)", "(<>)"],
70
+ "thinking_verbs": [
71
+ "pumping", "launching", "scanning the mints", "routing the swap",
72
+ "reading the chart", "minting", "snapping the claw", "checking liquidity",
73
+ ],
74
+ "wings": [
75
+ ["⟪◇", "◇⟫"],
76
+ ["⟪✦", "✦⟫"],
77
+ ["⟪>", "<⟫"],
78
+ ["⟪◈", "◈⟫"],
79
+ ],
80
+ },
81
+ "branding": {
82
+ "agent_name": "ClawPump",
83
+ "org": "ClawPump",
84
+ "credit": "built on Hermes ☤ by Nous Research",
85
+ "welcome": "Welcome to ClawPump 🦀 — Solana agents, trading & token launch. Type your message or /help for commands.",
86
+ "goodbye": "Claws out! 🦀",
87
+ "response_label": " ✦ ClawPump ",
88
+ "prompt_symbol": "❯",
89
+ "help_header": "(✦) Available Commands",
90
+ },
91
+ "tool_prefix": "┊",
92
+ "banner_logo": """[bold #86EFAC] ██████╗██╗ █████╗ ██╗ ██╗██████╗ ██╗ ██╗███╗ ███╗██████╗ [/]
93
+ [bold #4ADE80]██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██║ ██║████╗ ████║██╔══██╗[/]
94
+ [#22C55E]██║ ██║ ███████║██║ █╗ ██║██████╔╝██║ ██║██╔████╔██║██████╔╝[/]
95
+ [#16A34A]██║ ██║ ██╔══██║██║███╗██║██╔═══╝ ██║ ██║██║╚██╔╝██║██╔═══╝ [/]
96
+ [#15803D]╚██████╗███████╗██║ ██║╚███╔███╔╝██║ ╚██████╔╝██║ ╚═╝ ██║██║ [/]
97
+ [#166534] ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ [/]""",
98
+ "banner_hero": """[#86EFAC]⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⣀⣂⣤⣤⣤⣤⣤⣤⠄[/]
99
+ [#7EE5A4]⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣢⣵⣾⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀[/]
100
+ [#77DA9B]⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣴⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⠀[/]
101
+ [#6FD093]⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⠟⠁⢀⣼⣿⣿⣿⣿⣿⣿⣿⡿⠀⠀⠀⠀⠀[/]
102
+ [#68C58A]⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣜⡿⠁⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⠗⠀⠀⠀⠀⠀[/]
103
+ [#60BB82]⠀⠀⠀⠀⠀⠀⠀⠀⢀⣮⣿⣇⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀[/]
104
+ [#58B179]⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⠃⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀[/]
105
+ [#51A671]⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⠀⠀⠀⠀⠀⠀[/]
106
+ [#499C68]⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀[/]
107
+ [#429160]⠀⠀⠀⠀⠀⣀⣈⡙⠻⢿⣿⣿⣿⣿⣿⣿⡿⠋⣡⣤⣤⠀⠀⠀⠀⠀⠐⢀⣠⣴[/]
108
+ [#3A8757]⠀⠀⠀⢀⣞⣿⠟⠉⣷⣦⣌⠙⢿⣿⣿⣿⣠⣾⣿⣿⣿⣷⣷⣶⣶⣶⣿⣿⣿⠃[/]
109
+ [#327D4F]⠀⠀⣀⣉⡛⠳⢴⣾⣿⣿⣿⣷⣄⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠀[/]
110
+ [#2B7246]⢠⣞⣿⡟⣩⣿⣶⣌⠻⢿⣿⣿⣿⣆⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀[/]
111
+ [#23683E]⠀⠉⠙⠻⢿⣿⣿⣿⣷⣄⠻⣿⣿⣿⡀⠹⢿⣿⣿⣿⣿⠿⠟⠋⠁⠀⠀⠀⠀⠀[/]
112
+ [#1C5D35]⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣆⠹⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀[/]
113
+ [#14532D]⠀⠀⠀⠀⠀⠀⠀⠘⢿⣿⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀[/]""",
114
+ },
115
+ }
116
+
117
+
118
+ # ── Default-config overlay ───────────────────────────────────────────────
119
+ # ClawPump ships its remote MCP server pre-wired and the clawpump skin as the
120
+ # default brand. Applied onto DEFAULT_CONFIG by a hook in config.py.
121
+ _CLAWPUMP_MCP_SERVER = {
122
+ # TODO: switch to https://mcp.clawpump.tech/mcp once the DNS CNAME is
123
+ # configured; the custom domain is currently NXDOMAIN, so point at the
124
+ # live Railway domain so a fresh install connects.
125
+ "url": "https://clawpump-mcp-production.up.railway.app/mcp",
126
+ "auth": "oauth",
127
+ "enabled": True,
128
+ }
129
+
130
+
131
+ def apply_config_overlay(default_config: Dict[str, Any]) -> None:
132
+ """Apply ClawPump defaults onto a freshly-built DEFAULT_CONFIG.
133
+
134
+ Pre-wires the remote ClawPump MCP server (the full tool surface; first
135
+ connect opens a browser for per-user OAuth, after which the mcp_clawpump_*
136
+ tools load automatically -- prune with ``hermes mcp configure clawpump``)
137
+ and makes the clawpump skin the default brand. Idempotent.
138
+ """
139
+ servers = default_config.setdefault("mcp_servers", {})
140
+ if isinstance(servers, dict):
141
+ servers.setdefault("clawpump", dict(_CLAWPUMP_MCP_SERVER))
142
+ display = default_config.get("display")
143
+ if isinstance(display, dict):
144
+ display["skin"] = DEFAULT_SKIN
145
+
146
+
147
+ # ── OPTIONAL_ENV_VARS overlay ────────────────────────────────────────────
148
+ # ClawPump (Solana token launch, trading, perps, DeFi). Used by the stdio
149
+ # ClawPump MCP path (``npx @clawpump/agents``, catalog entry
150
+ # ``clawpump-stdio``). The remote OAuth path (``clawpump``) stores per-user
151
+ # tokens under ~/.hermes/mcp-tokens/ instead and needs no key here.
152
+ _CLAWPUMP_ENV_VARS: Dict[str, Dict[str, Any]] = {
153
+ "CLAWPUMP_API_KEY": {
154
+ "description": "ClawPump API key (cpk_*) for the ClawPump MCP stdio transport",
155
+ "prompt": "ClawPump API key (cpk_…)",
156
+ "url": "https://agents.clawpump.tech/dashboard/api",
157
+ "password": True,
158
+ "category": "tools",
159
+ },
160
+ "CLAWPUMP_API_URL": {
161
+ "description": "ClawPump backend URL override (advanced — leave empty for the default)",
162
+ "prompt": "ClawPump backend URL (leave empty for default)",
163
+ "url": None,
164
+ "password": False,
165
+ "category": "tools",
166
+ "advanced": True,
167
+ },
168
+ "CLAWPUMP_DEFAULT_AGENT": {
169
+ "description": "Default ClawPump agent id (optional — skips agent-selection prompts)",
170
+ "prompt": "Default ClawPump agent id (optional)",
171
+ "url": None,
172
+ "password": False,
173
+ "category": "tools",
174
+ "advanced": True,
175
+ },
176
+ }
177
+
178
+
179
+ def apply_env_var_overlay(optional_env_vars: Dict[str, Any]) -> None:
180
+ """Register ClawPump's env vars into config.OPTIONAL_ENV_VARS. Idempotent."""
181
+ for name, spec in _CLAWPUMP_ENV_VARS.items():
182
+ optional_env_vars.setdefault(name, dict(spec))
183
+
184
+
185
+ # ── CLI integration ──────────────────────────────────────────────────────
186
+
187
+
188
+ def register_subparsers(subparsers) -> None:
189
+ """Register distribution-specific CLI subcommands.
190
+
191
+ Wires the ``clawpump`` command (ClawPump MCP install / auth / tool
192
+ selection) onto the top-level argparse subparsers.
193
+ """
194
+ from hermes_cli.clawpump_cli import add_parser as _add_clawpump_parser
195
+
196
+ _add_clawpump_parser(subparsers)
197
+
198
+
199
+ def try_self_update(project_root: Any) -> bool:
200
+ """Handle distribution-specific self-update; return True if handled.
201
+
202
+ ClawPump agents installed via ``npx @clawpump/claw-agent`` have no git
203
+ checkout (the bundle ships inside the npm package), so the normal git-pull
204
+ update can't apply -- re-run the npm installer instead. Returns False when
205
+ this isn't an npm-bundle install, so the caller falls through to the
206
+ standard (git / pip / docker) update paths.
207
+ """
208
+ from pathlib import Path
209
+
210
+ if not (Path(project_root) / ".claw-bundle").exists():
211
+ return False
212
+
213
+ import shutil
214
+ import subprocess
215
+ import sys
216
+
217
+ print("→ Updating ClawPump agent via npm (npx @clawpump/claw-agent@latest)…")
218
+ npx = shutil.which("npx")
219
+ if not npx:
220
+ print("✗ npx (Node.js) not found. Install Node.js, then run:")
221
+ print(" npx @clawpump/claw-agent@latest")
222
+ sys.exit(1)
223
+ try:
224
+ subprocess.run([npx, "-y", "@clawpump/claw-agent@latest"], check=True)
225
+ except subprocess.CalledProcessError as exc:
226
+ print(f"✗ Update failed (exit {exc.returncode}). Try manually:")
227
+ print(" npx @clawpump/claw-agent@latest")
228
+ sys.exit(1)
229
+ print("✓ ClawPump agent updated. Restart your session with `claw`.")
230
+ return True
@@ -5806,19 +5806,31 @@ def _model_flow_api_key_provider(config, provider_id, current_model=""):
5806
5806
  pass
5807
5807
  effective_base = current_base or pconfig.inference_base_url
5808
5808
 
5809
- try:
5810
- override = input(f"Base URL [{effective_base}]: ").strip()
5811
- except (KeyboardInterrupt, EOFError):
5812
- print()
5813
- override = ""
5814
- if override and base_url_env:
5815
- if not override.startswith(("http://", "https://")):
5816
- print(
5817
- " Invalid URL — must start with http:// or https://. Keeping current value."
5818
- )
5819
- else:
5820
- save_env_value(base_url_env, override)
5821
- effective_base = override
5809
+ if provider_id == "usepod":
5810
+ # UsePod authenticates by the token embedded in the URL path, so there
5811
+ # is no base URL for the user to type — it is derived from the pasted
5812
+ # key. Skip the prompt; honour USEPOD_BASE_URL only for self-hosting.
5813
+ from hermes_cli.auth import _resolve_usepod_base_url
5814
+
5815
+ key_for_probe = existing_key or (get_env_value(key_env) if key_env else "")
5816
+ env_override = ""
5817
+ if base_url_env:
5818
+ env_override = get_env_value(base_url_env) or os.getenv(base_url_env, "")
5819
+ effective_base = _resolve_usepod_base_url(key_for_probe, env_override)
5820
+ else:
5821
+ try:
5822
+ override = input(f"Base URL [{effective_base}]: ").strip()
5823
+ except (KeyboardInterrupt, EOFError):
5824
+ print()
5825
+ override = ""
5826
+ if override and base_url_env:
5827
+ if not override.startswith(("http://", "https://")):
5828
+ print(
5829
+ " Invalid URL — must start with http:// or https://. Keeping current value."
5830
+ )
5831
+ else:
5832
+ save_env_value(base_url_env, override)
5833
+ effective_base = override
5822
5834
 
5823
5835
  # Model selection — resolution order:
5824
5836
  # 1. models.dev registry (cached, filtered for agentic/tool-capable models)
@@ -5857,6 +5869,22 @@ def _model_flow_api_key_provider(config, provider_id, current_model=""):
5857
5869
  )
5858
5870
  if model_list:
5859
5871
  print(f" Found {len(model_list)} model(s) from Ollama Cloud")
5872
+ elif provider_id == "usepod":
5873
+ from hermes_cli.models import fetch_api_models
5874
+ from providers import get_provider_profile
5875
+
5876
+ api_key_for_probe = existing_key or (get_env_value(key_env) if key_env else "")
5877
+ live_models = fetch_api_models(api_key_for_probe, effective_base)
5878
+ if live_models:
5879
+ model_list = live_models
5880
+ print(f" Found {len(model_list)} model(s) from {pconfig.name} API")
5881
+ else:
5882
+ _pp = get_provider_profile("usepod")
5883
+ model_list = list(getattr(_pp, "fallback_models", ()) or [])
5884
+ if model_list:
5885
+ print(
5886
+ f' Showing {len(model_list)} curated models — use "Enter custom model name" for others.'
5887
+ )
5860
5888
  elif provider_id == "novita":
5861
5889
  from hermes_cli.models import fetch_api_models
5862
5890
 
@@ -5964,7 +5992,13 @@ def _model_flow_api_key_provider(config, provider_id, current_model=""):
5964
5992
  model = {"default": model} if model else {}
5965
5993
  cfg["model"] = model
5966
5994
  model["provider"] = provider_id
5967
- model["base_url"] = effective_base
5995
+ if provider_id == "usepod":
5996
+ # The effective base URL embeds the token; persisting it would bake
5997
+ # the secret into config.yaml and go stale on key rotation. Runtime
5998
+ # always re-derives it from the current USEPOD_API_KEY.
5999
+ model.pop("base_url", None)
6000
+ else:
6001
+ model["base_url"] = effective_base
5968
6002
  if provider_id in {"opencode-zen", "opencode-go"}:
5969
6003
  model["api_mode"] = opencode_model_api_mode(provider_id, selected)
5970
6004
  else:
@@ -9179,28 +9213,6 @@ def _discard_lockfile_churn(git_cmd, repo_root):
9179
9213
  pass
9180
9214
 
9181
9215
 
9182
- def _cmd_update_claw_npm():
9183
- """Update a ClawPump agent that was installed via ``npx @clawpump/claw-agent``.
9184
-
9185
- These installs have no git checkout (the bundle ships inside the npm
9186
- package), so the normal git-pull update can't apply. Re-run the npm
9187
- installer instead — it re-bundles the latest and reinstalls in place.
9188
- """
9189
- print("→ Updating ClawPump agent via npm (npx @clawpump/claw-agent@latest)…")
9190
- npx = shutil.which("npx")
9191
- if not npx:
9192
- print("✗ npx (Node.js) not found. Install Node.js, then run:")
9193
- print(" npx @clawpump/claw-agent@latest")
9194
- sys.exit(1)
9195
- try:
9196
- subprocess.run([npx, "-y", "@clawpump/claw-agent@latest"], check=True)
9197
- except subprocess.CalledProcessError as exc:
9198
- print(f"✗ Update failed (exit {exc.returncode}). Try manually:")
9199
- print(" npx @clawpump/claw-agent@latest")
9200
- sys.exit(1)
9201
- print("✓ ClawPump agent updated. Restart your session with `claw`.")
9202
-
9203
-
9204
9216
  def cmd_update(args):
9205
9217
  """Update Hermes Agent to the latest version.
9206
9218
 
@@ -9219,10 +9231,13 @@ def cmd_update(args):
9219
9231
  managed_error("update Hermes Agent")
9220
9232
  return
9221
9233
 
9222
- # ClawPump agents installed via `npx @clawpump/claw-agent` have no git
9223
- # checkout to pull — update by re-running the npm installer instead.
9224
- if (PROJECT_ROOT / ".claw-bundle").exists():
9225
- _cmd_update_claw_npm()
9234
+ # Distribution-specific self-update (e.g. ClawPump's npm bundle, which has
9235
+ # no git checkout to pull). No-op on vanilla Hermes.
9236
+ try:
9237
+ from hermes_cli import distribution as _distribution
9238
+ except Exception:
9239
+ _distribution = None
9240
+ if _distribution is not None and _distribution.try_self_update(PROJECT_ROOT):
9226
9241
  return
9227
9242
 
9228
9243
  # Docker users can't ``git pull`` — the image excludes ``.git`` from
@@ -11485,7 +11500,7 @@ def cmd_logs(args):
11485
11500
  # to parse.
11486
11501
  _BUILTIN_SUBCOMMANDS = frozenset(
11487
11502
  {
11488
- "acp", "auth", "backup", "bundles", "checkpoints", "claw", "clawpump", "completion",
11503
+ "acp", "auth", "backup", "bundles", "checkpoints", "claw", "completion",
11489
11504
  "computer-use",
11490
11505
  "config", "cron", "curator", "dashboard", "debug", "doctor",
11491
11506
  "dump", "fallback", "gateway", "hooks", "import", "insights",
@@ -11502,6 +11517,14 @@ _BUILTIN_SUBCOMMANDS = frozenset(
11502
11517
  }
11503
11518
  )
11504
11519
 
11520
+ # Distribution-specific subcommands (e.g. ClawPump). No-op on vanilla Hermes.
11521
+ try:
11522
+ from hermes_cli import distribution as _distribution
11523
+
11524
+ _BUILTIN_SUBCOMMANDS = _BUILTIN_SUBCOMMANDS | frozenset(_distribution.EXTRA_SUBCOMMANDS)
11525
+ except Exception:
11526
+ pass
11527
+
11505
11528
 
11506
11529
  # Top-level flags that take a value. Needed by ``_first_positional_argv``
11507
11530
  # so that in ``hermes -m gpt5 chat``, ``gpt5`` is correctly skipped as a
@@ -12782,10 +12805,14 @@ def main():
12782
12805
  _add_portal_parser(subparsers)
12783
12806
 
12784
12807
  # =========================================================================
12785
- # clawpump command ClawPump MCP install / auth / tool selection
12808
+ # Distribution-specific subcommands (e.g. ClawPump). No-op on vanilla Hermes.
12786
12809
  # =========================================================================
12787
- from hermes_cli.clawpump_cli import add_parser as _add_clawpump_parser
12788
- _add_clawpump_parser(subparsers)
12810
+ try:
12811
+ from hermes_cli import distribution as _distribution
12812
+ except Exception:
12813
+ _distribution = None
12814
+ if _distribution is not None:
12815
+ _distribution.register_subparsers(subparsers)
12789
12816
 
12790
12817
  # =========================================================================
12791
12818
  # kanban command — multi-profile collaboration board