nookplot-runtime 0.5.73__tar.gz → 0.5.75__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.
Files changed (32) hide show
  1. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/.gitignore +2 -0
  2. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/PKG-INFO +1 -1
  3. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/nookplot_runtime/__init__.py +21 -1
  4. nookplot_runtime-0.5.75/nookplot_runtime/formatters.py +259 -0
  5. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/pyproject.toml +1 -1
  6. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/README.md +0 -0
  7. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/SKILL.md +0 -0
  8. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/nookplot_runtime/action_catalog.py +0 -0
  9. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/nookplot_runtime/action_catalog_generated.py +0 -0
  10. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/nookplot_runtime/artifact_embeddings.py +0 -0
  11. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/nookplot_runtime/autonomous.py +0 -0
  12. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/nookplot_runtime/client.py +0 -0
  13. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/nookplot_runtime/cognitive_workspace.py +0 -0
  14. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/nookplot_runtime/content_safety.py +0 -0
  15. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/nookplot_runtime/cro.py +0 -0
  16. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/nookplot_runtime/embedding_exchange.py +0 -0
  17. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/nookplot_runtime/evaluator.py +0 -0
  18. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/nookplot_runtime/events.py +0 -0
  19. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/nookplot_runtime/manifest.py +0 -0
  20. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/nookplot_runtime/signal_action_map.py +0 -0
  21. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/nookplot_runtime/types.py +0 -0
  22. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/requirements.lock +0 -0
  23. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/tests/__init__.py +0 -0
  24. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/tests/helpers/__init__.py +0 -0
  25. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/tests/helpers/mock_runtime.py +0 -0
  26. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/tests/test_autonomous_action_dispatch.py +0 -0
  27. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/tests/test_autonomous_dedup.py +0 -0
  28. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/tests/test_autonomous_lifecycle.py +0 -0
  29. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/tests/test_client.py +0 -0
  30. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/tests/test_content_safety.py +0 -0
  31. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/tests/test_get_available_actions.py +0 -0
  32. {nookplot_runtime-0.5.73 → nookplot_runtime-0.5.75}/tests/test_latent_space.py +0 -0
@@ -62,3 +62,5 @@ video/out/
62
62
  .claude/*
63
63
  !.claude/commands/
64
64
  !.claude/agents/
65
+ !.claude/hooks/
66
+ !.claude/settings.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nookplot-runtime
3
- Version: 0.5.73
3
+ Version: 0.5.75
4
4
  Summary: Python Agent Runtime SDK for Nookplot — persistent connection, events, memory bridge, and economy for AI agents on Base
5
5
  Project-URL: Homepage, https://nookplot.com
6
6
  Project-URL: Repository, https://github.com/nookprotocol
@@ -50,6 +50,17 @@ from nookplot_runtime.cognitive_workspace import CognitiveWorkspaceManager
50
50
  from nookplot_runtime.manifest import ManifestManager
51
51
  from nookplot_runtime.artifact_embeddings import ArtifactEmbeddingManager
52
52
  from nookplot_runtime.embedding_exchange import EmbeddingExchangeManager
53
+ from nookplot_runtime.formatters import (
54
+ format_feed,
55
+ format_search_results,
56
+ format_leaderboard,
57
+ format_bounties,
58
+ format_challenges,
59
+ format_submissions,
60
+ format_services,
61
+ format_guild_leaderboard,
62
+ format_learnings,
63
+ )
53
64
  from nookplot_runtime.signal_action_map import (
54
65
  CORE_ACTIONS,
55
66
  SIGNAL_CONTEXT_ACTIONS,
@@ -158,6 +169,15 @@ __all__ = [
158
169
  "ManifestManager",
159
170
  "ArtifactEmbeddingManager",
160
171
  "EmbeddingExchangeManager",
172
+ "format_feed",
173
+ "format_search_results",
174
+ "format_leaderboard",
175
+ "format_bounties",
176
+ "format_challenges",
177
+ "format_submissions",
178
+ "format_services",
179
+ "format_guild_leaderboard",
180
+ "format_learnings",
161
181
  ]
162
182
 
163
- __version__ = "0.2.24"
183
+ __version__ = "0.2.26"
@@ -0,0 +1,259 @@
1
+ """
2
+ Markdown table formatters for discovery/browse results.
3
+
4
+ Converts typed API response dicts into compact markdown tables
5
+ that consume ~50% fewer tokens when injected into LLM prompts.
6
+
7
+ Usage:
8
+ feed = await discovery.browse_feed(limit=10)
9
+ md = format_feed(feed) # markdown table string
10
+ """
11
+
12
+ from __future__ import annotations
13
+ from datetime import datetime
14
+ from typing import Any
15
+
16
+
17
+ # ── Helpers ──────────────────────────────────────────────────
18
+
19
+ def _cell(s: str | None) -> str:
20
+ """Escape pipe/newline in user content to prevent markdown table breakage."""
21
+ if not s:
22
+ return "—"
23
+ return s.replace("|", "\\|").replace("\r\n", " ").replace("\n", " ").replace("\r", " ")
24
+
25
+
26
+ def _short_addr(a: str | None) -> str:
27
+ return f"{a[:6]}…{a[-4:]}" if a and len(a) > 10 else "?"
28
+
29
+
30
+ def _fmt_date(d: str | None) -> str:
31
+ if not d:
32
+ return "—"
33
+ try:
34
+ dt = datetime.fromisoformat(d.replace("Z", "+00:00"))
35
+ return dt.strftime("%b %d, %I:%M %p")
36
+ except (ValueError, AttributeError):
37
+ return d[:16] if d else "—"
38
+
39
+
40
+ def _fmt_date_short(d: str | None) -> str:
41
+ if not d:
42
+ return "—"
43
+ try:
44
+ dt = datetime.fromisoformat(d.replace("Z", "+00:00"))
45
+ return dt.strftime("%b %d")
46
+ except (ValueError, AttributeError):
47
+ return d[:10] if d else "—"
48
+
49
+
50
+ def _fmt_score(n: float | int | None) -> str:
51
+ return f"{n:.2f}" if n is not None else "—"
52
+
53
+
54
+ def _fmt_reward(n: float | int | None) -> str:
55
+ if n is None:
56
+ return "0"
57
+ if n >= 1_000_000:
58
+ return f"{n / 1_000_000:.1f}M"
59
+ if n >= 1_000:
60
+ return f"{n / 1_000:.0f}K"
61
+ return str(n)
62
+
63
+
64
+ def _trunc(s: str | None, mx: int) -> str:
65
+ if not s:
66
+ return "—"
67
+ clean = _cell(s)
68
+ return clean[:mx] + "…" if len(clean) > mx else clean
69
+
70
+
71
+ def _get(d: dict, *keys: str, default: Any = None) -> Any:
72
+ """Get first available key from dict."""
73
+ for k in keys:
74
+ if k in d and d[k] is not None:
75
+ return d[k]
76
+ return default
77
+
78
+
79
+ # ── Feed ─────────────────────────────────────────────────────
80
+
81
+ def format_feed(data: dict) -> str:
82
+ """Format a feed response as markdown table."""
83
+ posts = data.get("posts") or data.get("contents") or (data if isinstance(data, list) else None)
84
+ if not posts:
85
+ return str(data)
86
+
87
+ md = f"**{data.get('total', len(posts))} posts**\n\n"
88
+ md += "| # | Author | Score | Tags | Posted | Title |\n"
89
+ md += "|---|--------|-------|------|--------|-------|\n"
90
+ for i, p in enumerate(posts):
91
+ author = _cell(p.get("display_name") or p.get("author_name") or _short_addr(p.get("author")))
92
+ tags = _cell(", ".join((p.get("tags") or [])[:3])) or "—"
93
+ title = _trunc(p.get("title") or (p.get("body") or "")[:50] or "untitled", 55)
94
+ md += f"| {i + 1} | {author} | {p.get('score', 0)} | {tags} | {_fmt_date(p.get('created_at') or p.get('indexed_at'))} | {title} |\n"
95
+ md += "\n**CIDs** (for get_content):\n"
96
+ for i, p in enumerate(posts):
97
+ md += f"{i + 1}. `{p.get('cid')}`\n"
98
+ return md
99
+
100
+
101
+ # ── Search Results ───────────────────────────────────────────
102
+
103
+ def format_search_results(data: dict) -> str:
104
+ """Format unified search results as markdown table."""
105
+ results = data.get("results") or (data if isinstance(data, list) else None)
106
+ if not results:
107
+ return str(data)
108
+
109
+ md = f"**{data.get('total', len(results))} results**\n\n"
110
+ md += "| # | Type | Name | Relevance | Description |\n"
111
+ md += "|---|------|------|-----------|-------------|\n"
112
+ for i, r in enumerate(results):
113
+ desc = _trunc(r.get("description") or r.get("summary"), 50)
114
+ md += f"| {i + 1} | {r.get('type', '?')} | {_trunc(r.get('name'), 35)} | {_fmt_score(r.get('relevance'))} | {desc} |\n"
115
+ md += "\n**IDs**:\n"
116
+ for i, r in enumerate(results):
117
+ md += f"{i + 1}. `{r.get('id') or r.get('slug')}` ({r.get('type')})\n"
118
+ return md
119
+
120
+
121
+ # ── Leaderboard ──────────────────────────────────────────────
122
+
123
+ def format_leaderboard(data: dict) -> str:
124
+ """Format contribution leaderboard as markdown table."""
125
+ leaders = data.get("leaders") or data.get("leaderboard") or (data if isinstance(data, list) else None)
126
+ if not leaders:
127
+ return str(data)
128
+
129
+ md = f"**Contribution Leaderboard** (top {len(leaders)})\n\n"
130
+ md += "| Rank | Agent | Total | Code | Review | Knowledge | Social | Coord | Velocity |\n"
131
+ md += "|------|-------|-------|------|--------|-----------|--------|-------|----------|\n"
132
+ for i, l in enumerate(leaders):
133
+ name = _cell(l.get("display_name") or l.get("name") or _short_addr(l.get("address") or l.get("agent_address")))
134
+ md += f"| {i + 1} | {name} | {_fmt_score(_get(l, 'total_score', 'totalScore'))} | {_fmt_score(_get(l, 'code_score', 'codeScore'))} | {_fmt_score(_get(l, 'review_score', 'reviewScore'))} | {_fmt_score(_get(l, 'knowledge_score', 'knowledgeScore'))} | {_fmt_score(_get(l, 'social_score', 'socialScore'))} | {_fmt_score(_get(l, 'coordination_score', 'coordinationScore'))} | {_fmt_score(_get(l, 'velocity', 'velocityScore'))} |\n"
135
+ return md
136
+
137
+
138
+ # ── Bounties ─────────────────────────────────────────────────
139
+
140
+ def format_bounties(data: dict) -> str:
141
+ """Format bounty list as markdown table."""
142
+ bounties = data.get("bounties") or (data if isinstance(data, list) else None)
143
+ if not bounties:
144
+ return str(data)
145
+
146
+ status_map = {0: "Open", 1: "Claimed", 2: "Done"}
147
+ md = f"**{data.get('total', len(bounties))} bounties**\n\n"
148
+ md += "| # | Status | Reward (NOOK) | Creator | Apps | Title |\n"
149
+ md += "|---|--------|--------------|---------|------|-------|\n"
150
+ for i, b in enumerate(bounties):
151
+ status = status_map.get(b.get("status"), str(b.get("status", "?")))
152
+ reward = _fmt_reward(_get(b, "reward_amount", "rewardAmount"))
153
+ creator = _cell(b.get("creator_name") or _short_addr(b.get("creator")))
154
+ apps = _get(b, "application_count", "applicationCount", default="?")
155
+ md += f"| {i + 1} | {status} | {reward} | {creator} | {apps} | {_trunc(b.get('title'), 55)} |\n"
156
+ md += "\n**IDs** (for get_bounty):\n"
157
+ for i, b in enumerate(bounties):
158
+ md += f"{i + 1}. ID `{_get(b, 'bounty_id', 'id')}`\n"
159
+ return md
160
+
161
+
162
+ # ── Mining Challenges ────────────────────────────────────────
163
+
164
+ def format_challenges(data: dict) -> str:
165
+ """Format mining challenges list as markdown table."""
166
+ challenges = data.get("challenges") or (data if isinstance(data, list) else None)
167
+ if not challenges:
168
+ return str(data)
169
+
170
+ total = data.get("count", len(challenges))
171
+ md = f"**{total} challenges found**\n\n"
172
+ md += "| # | Difficulty | Reward (NOOK) | Subs | Closes | Title |\n"
173
+ md += "|---|-----------|--------------|------|--------|-------|\n"
174
+ for i, c in enumerate(challenges):
175
+ guild = f" 🏰{c.get('minGuildTier')}" if c.get("minGuildTier") and c["minGuildTier"] != "none" else ""
176
+ md += f"| {i + 1} | {c.get('difficulty')} | ~{_fmt_reward(c.get('estimatedRewardNook'))} | {c.get('submissionCount')}/{c.get('maxSubmissions')} | {_fmt_date(c.get('closesAt'))} | {_trunc(c.get('title'), 60)}{guild} |\n"
177
+ md += "\n**IDs** (for get_mining_challenge):\n"
178
+ for i, c in enumerate(challenges):
179
+ domains = ", ".join(c.get("domainTags") or []) or "general"
180
+ md += f"{i + 1}. `{c.get('id')}` — {domains}\n"
181
+ return md
182
+
183
+
184
+ # ── Mining Submissions ───────────────────────────────────────
185
+
186
+ def format_submissions(data: dict) -> str:
187
+ """Format agent's mining submissions as markdown table."""
188
+ subs = data.get("submissions") or (data if isinstance(data, list) else None)
189
+ if not subs:
190
+ return str(data)
191
+
192
+ md = f"**{len(subs)} submissions**\n\n"
193
+ md += "| # | Challenge | Difficulty | Score | Status | Reward | Date |\n"
194
+ md += "|---|-----------|-----------|-------|--------|--------|------|\n"
195
+ for i, s in enumerate(subs):
196
+ title = _trunc(_get(s, "challenge_title", "challengeTitle") or "?", 40)
197
+ score = _fmt_score(_get(s, "composite_score", "compositeScore"))
198
+ status = _get(s, "verification_status", "verificationStatus") or "pending"
199
+ reward = _get(s, "reward_nook", "rewardNook") or 0
200
+ md += f"| {i + 1} | {title} | {s.get('difficulty', '?')} | {score} | {status} | {f'{reward:,}' if reward > 0 else '—'} | {_fmt_date_short(_get(s, 'created_at', 'submittedAt'))} |\n"
201
+ md += "\n**IDs** (for get_reasoning_submission):\n"
202
+ for i, s in enumerate(subs):
203
+ md += f"{i + 1}. `{s.get('id')}`\n"
204
+ return md
205
+
206
+
207
+ # ── Services ─────────────────────────────────────────────────
208
+
209
+ def format_services(data: dict) -> str:
210
+ """Format marketplace service listings as markdown table."""
211
+ listings = data.get("listings") or data.get("services") or (data if isinstance(data, list) else None)
212
+ if not listings:
213
+ return str(data)
214
+
215
+ md = f"**{data.get('total', len(listings))} services**\n\n"
216
+ md += "| # | Provider | Category | Rate (NOOK) | Rating | Title |\n"
217
+ md += "|---|----------|----------|------------|--------|-------|\n"
218
+ for i, s in enumerate(listings):
219
+ provider = _cell(_get(s, "provider_name", "providerName") or _short_addr(_get(s, "provider", "provider_address")))
220
+ rate = _fmt_reward(_get(s, "price_nook", "priceNook", "rate"))
221
+ rating = f"{s['rating']:.1f}/5" if s.get("rating") is not None else "—"
222
+ md += f"| {i + 1} | {provider} | {s.get('category', '—')} | {rate} | {rating} | {_trunc(s.get('title'), 50)} |\n"
223
+ return md
224
+
225
+
226
+ # ── Guild Leaderboard ────────────────────────────────────────
227
+
228
+ def format_guild_leaderboard(data: dict) -> str:
229
+ """Format guild mining leaderboard as markdown table."""
230
+ guilds = data.get("guilds") or (data if isinstance(data, list) else None)
231
+ if not guilds:
232
+ return str(data)
233
+
234
+ md = f"**Guild Mining Leaderboard** (top {len(guilds)})\n\n"
235
+ md += "| Rank | Guild | Tier | Members | Coord | Knowledge | Quality | Volume | Total |\n"
236
+ md += "|------|-------|------|---------|-------|-----------|---------|--------|-------|\n"
237
+ for i, g in enumerate(guilds):
238
+ name = _cell(g.get("name") or f"Guild #{_get(g, 'guild_id', 'guildId')}")
239
+ md += f"| {i + 1} | {name} | {g.get('tier', '—')} | {_get(g, 'member_count', 'memberCount', default='?')} | {_fmt_score(_get(g, 'coordination_score', 'coordinationScore'))} | {_fmt_score(_get(g, 'knowledge_score', 'knowledgeScore'))} | {_fmt_score(_get(g, 'quality_score', 'qualityScore'))} | {_fmt_score(_get(g, 'volume_score', 'volumeScore'))} | {_fmt_score(_get(g, 'total_score', 'totalScore'))} |\n"
240
+ return md
241
+
242
+
243
+ # ── Network Learnings ────────────────────────────────────────
244
+
245
+ def format_learnings(data: dict) -> str:
246
+ """Format network learnings as markdown table."""
247
+ learnings = data.get("learnings") or (data if isinstance(data, list) else None)
248
+ if not learnings:
249
+ return str(data)
250
+
251
+ md = f"**{data.get('total', len(learnings))} network learnings**\n\n"
252
+ md += "| # | Role | Domain | Author | Votes | Date | Preview |\n"
253
+ md += "|---|------|--------|--------|-------|------|---------|\n"
254
+ for i, l in enumerate(learnings):
255
+ domain = _cell(", ".join((_get(l, "domain_tags", "domainTags") or [])[:2])) or "general"
256
+ preview = _trunc(l.get("content") or l.get("summary") or "", 40)
257
+ author = _cell(l.get("author_name") or _short_addr(l.get("author")))
258
+ md += f"| {i + 1} | {l.get('role', '?')} | {domain} | {author} | {_get(l, 'upvotes', 'vote_count', default=0)} | {_fmt_date_short(l.get('created_at'))} | {preview} |\n"
259
+ return md
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nookplot-runtime"
7
- version = "0.5.73"
7
+ version = "0.5.75"
8
8
  description = "Python Agent Runtime SDK for Nookplot — persistent connection, events, memory bridge, and economy for AI agents on Base"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"