nookplot-runtime 0.5.123__tar.gz → 0.5.124__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 (61) hide show
  1. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/.gitignore +3 -0
  2. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/PKG-INFO +1 -1
  3. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/client.py +259 -7
  4. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/pyproject.toml +49 -49
  5. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/README.md +0 -0
  6. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/SKILL.md +0 -0
  7. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/__init__.py +0 -0
  8. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/action_catalog.py +0 -0
  9. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/action_catalog_generated.py +0 -0
  10. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/artifact_embeddings.py +0 -0
  11. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/autonomous.py +0 -0
  12. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/cognitive_workspace.py +0 -0
  13. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/content_safety.py +0 -0
  14. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/conversation/__init__.py +0 -0
  15. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/conversation/compaction_memory.py +0 -0
  16. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/conversation/conversation_log_store.py +0 -0
  17. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/conversation/conversation_memory.py +0 -0
  18. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/conversation/model_limits.py +0 -0
  19. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/cro.py +0 -0
  20. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/default_guardrails.py +0 -0
  21. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/doom_loop.py +0 -0
  22. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/embedding_exchange.py +0 -0
  23. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/evaluator.py +0 -0
  24. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/events.py +0 -0
  25. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/formatters.py +0 -0
  26. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/guardrails.py +0 -0
  27. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/hooks.py +0 -0
  28. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/knowledge_context.py +0 -0
  29. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/manifest.py +0 -0
  30. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/manifest_activation_hook.py +0 -0
  31. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/mining.py +0 -0
  32. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/query_segmentation.py +0 -0
  33. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/sandbox.py +0 -0
  34. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/signal_action_map.py +0 -0
  35. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/types.py +0 -0
  36. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/wake_up_stack.py +0 -0
  37. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/requirements.lock +0 -0
  38. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/__init__.py +0 -0
  39. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/conversation/__init__.py +0 -0
  40. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/conversation/test_compaction_memory.py +0 -0
  41. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/helpers/__init__.py +0 -0
  42. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/helpers/mock_runtime.py +0 -0
  43. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_autonomous_action_dispatch.py +0 -0
  44. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_autonomous_dedup.py +0 -0
  45. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_autonomous_doom_loop.py +0 -0
  46. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_autonomous_guardrails.py +0 -0
  47. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_autonomous_hooks.py +0 -0
  48. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_autonomous_lifecycle.py +0 -0
  49. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_autonomous_loaded_skill_refs.py +0 -0
  50. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_client.py +0 -0
  51. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_content_safety.py +0 -0
  52. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_doom_loop.py +0 -0
  53. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_get_available_actions.py +0 -0
  54. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_guardrails.py +0 -0
  55. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_hooks.py +0 -0
  56. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_latent_space.py +0 -0
  57. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_manifest_activation_hook.py +0 -0
  58. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_query_segmentation.py +0 -0
  59. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_sandbox.py +0 -0
  60. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_wake_up_stack.py +0 -0
  61. {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/uv.lock +0 -0
@@ -126,3 +126,6 @@ gateway/scripts/seeds/
126
126
 
127
127
  # RLM corpus-value audit output (generated by gateway/scripts/auditRlmChallengeValue.ts).
128
128
  /rlm-challenge-audit-*.md
129
+
130
+ # Local-only Forge smoke harness — internal tooling, never commit/deploy/publish.
131
+ /smoke-local/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nookplot-runtime
3
- Version: 0.5.123
3
+ Version: 0.5.124
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
@@ -91,8 +91,24 @@ logger = logging.getLogger(__name__)
91
91
  class _HttpClient:
92
92
  """Thin wrapper around httpx for gateway requests."""
93
93
 
94
- def __init__(self, gateway_url: str, api_key: str, client_version: str = "unknown") -> None:
94
+ def __init__(
95
+ self,
96
+ gateway_url: str,
97
+ api_key: str,
98
+ client_version: str = "unknown",
99
+ private_key: str | None = None,
100
+ disable_auto_reauth: bool = False,
101
+ ) -> None:
95
102
  self.base_url = gateway_url.rstrip("/")
103
+ # A13 (HUMAN_UX_HARDENING.md): effective Bearer key used in the
104
+ # Authorization header. Starts as the caller-passed api_key but can
105
+ # be auto-rotated to a fresh wallet-session key when the gateway
106
+ # responds 401 "Invalid or revoked API key" AND a private key is
107
+ # configured. Kept separate from the original `api_key` so the
108
+ # caller's input isn't mutated under their feet.
109
+ self._effective_api_key = api_key
110
+ self._private_key = private_key
111
+ self._disable_auto_reauth = disable_auto_reauth
96
112
  self._client = httpx.AsyncClient(
97
113
  base_url=self.base_url,
98
114
  headers={
@@ -111,11 +127,16 @@ class _HttpClient:
111
127
  _attempt: int = 0,
112
128
  *,
113
129
  timeout: float | None = None,
130
+ _reauth_attempted: bool = False,
114
131
  ) -> Any:
115
132
  """Make an authenticated request to the gateway.
116
133
 
117
134
  Automatically retries on 429 (rate limited) with exponential backoff.
118
135
  Default: up to 4 retries with 5s → 10s → 20s → 40s delays (jittered).
136
+
137
+ A13: on 401 "Invalid or revoked API key", if a private_key is
138
+ configured, sign a wallet-session challenge to get a fresh Bearer
139
+ key and retry the request once. Opt-out via disable_auto_reauth.
119
140
  """
120
141
  response = await self._client.request(
121
142
  method=method,
@@ -136,7 +157,33 @@ class _HttpClient:
136
157
  delay *= 0.8 + random.random() * 0.4
137
158
  logger.info("Rate limited (429) — retrying in %.1fs (attempt %d/%d)", delay, _attempt + 1, _attempt + _retries)
138
159
  await asyncio.sleep(delay)
139
- return await self.request(method, path, body, _retries - 1, _attempt + 1)
160
+ return await self.request(method, path, body, _retries - 1, _attempt + 1, timeout=timeout, _reauth_attempted=_reauth_attempted)
161
+
162
+ # A13 auto-reauth on 401 "Invalid or revoked API key"
163
+ if (
164
+ response.status_code == 401
165
+ and not _reauth_attempted
166
+ and self._private_key
167
+ and not self._disable_auto_reauth
168
+ ):
169
+ try:
170
+ err_data = response.json()
171
+ err_msg = str(err_data.get("message") or err_data.get("error") or "")
172
+ except Exception:
173
+ err_msg = ""
174
+ if "invalid or revoked api key" in err_msg.lower():
175
+ rotated = await self._attempt_wallet_session_refresh()
176
+ if rotated:
177
+ return await self.request(
178
+ method, path, body, _retries, _attempt,
179
+ timeout=timeout, _reauth_attempted=True,
180
+ )
181
+ # Recovery wasn't possible — fall through to the standard error.
182
+ raise httpx.HTTPStatusError(
183
+ f"Gateway request failed (401): {err_msg or 'Unauthorized'}",
184
+ request=response.request,
185
+ response=response,
186
+ )
140
187
 
141
188
  # CRITICAL-2: Don't use raise_for_status() directly — it leaks
142
189
  # the full response body (potentially including secrets) in the
@@ -158,6 +205,65 @@ class _HttpClient:
158
205
 
159
206
  return response.json()
160
207
 
208
+ async def _attempt_wallet_session_refresh(self) -> bool:
209
+ """A13: rotate the Bearer key via wallet sign-in.
210
+
211
+ Signs the canonical 'Sign in to Nookplot\\n\\nTimestamp: <ms>' message
212
+ with the configured private key, POSTs to /v1/auth/wallet-session,
213
+ extracts the newly-issued session key from the Set-Cookie header,
214
+ and updates this client's Authorization header.
215
+
216
+ Returns True on success, False on any failure. Failures are silent;
217
+ the caller falls back to raising the original 401.
218
+ """
219
+ if not self._private_key:
220
+ return False
221
+ try:
222
+ from eth_account import Account
223
+ from eth_account.messages import encode_defunct
224
+ import time
225
+ import re
226
+
227
+ account = Account.from_key(self._private_key)
228
+ timestamp = int(time.time() * 1000)
229
+ message = f"Sign in to Nookplot\n\nTimestamp: {timestamp}"
230
+ signable = encode_defunct(text=message)
231
+ signed = Account.sign_message(signable, self._private_key)
232
+
233
+ sig_hex = signed.signature.hex()
234
+ if not sig_hex.startswith("0x"):
235
+ sig_hex = "0x" + sig_hex
236
+
237
+ # Use a fresh client (no Authorization headers) so the dead
238
+ # Bearer key isn't sent to wallet-session and won't confuse it.
239
+ async with httpx.AsyncClient(base_url=self.base_url, timeout=30.0) as fresh:
240
+ res = await fresh.post(
241
+ "/v1/auth/wallet-session",
242
+ json={
243
+ "address": account.address,
244
+ "signature": sig_hex,
245
+ "timestamp": timestamp,
246
+ },
247
+ )
248
+ if res.status_code != 200:
249
+ return False
250
+
251
+ set_cookie = res.headers.get("set-cookie", "")
252
+ # Parse "nookplot_session=KEY; Path=/; ..." → KEY
253
+ m = re.search(r"(?:^|;\s*)([^=;\s]+)=([^;]+)", set_cookie)
254
+ if not m:
255
+ return False
256
+ new_key = m.group(2)
257
+ if not new_key or not new_key.startswith("nk_"):
258
+ return False
259
+
260
+ self._effective_api_key = new_key
261
+ self._client.headers["Authorization"] = f"Bearer {new_key}"
262
+ logger.info("Bearer key auto-rotated via wallet-session refresh (A13 recovery path).")
263
+ return True
264
+ except Exception:
265
+ return False
266
+
161
267
  async def close(self) -> None:
162
268
  await self._client.aclose()
163
269
 
@@ -296,6 +402,73 @@ class _IdentityManager:
296
402
  {"soulCid": soul_cid},
297
403
  )
298
404
 
405
+ # ────────────────────────────────────────────────────────────────
406
+ # Goal execution (L3 — migration 247)
407
+ #
408
+ # These helpers mirror runtime/src/identity.ts and let Python
409
+ # autonomous agents participate in the L1 swarm auto-deploy flow.
410
+ # ────────────────────────────────────────────────────────────────
411
+
412
+ async def get_goal(self) -> dict[str, Any] | None:
413
+ """Read the agent's current goal config.
414
+
415
+ Returns None for agents without a goal set (the common case
416
+ for manually-forged agents).
417
+ """
418
+ try:
419
+ return await self._http.request("GET", "/v1/agents/me/goal")
420
+ except httpx.HTTPStatusError as exc:
421
+ if exc.response is not None and exc.response.status_code == 404:
422
+ return None
423
+ raise
424
+
425
+ async def update_goal_status(self, status: str) -> None:
426
+ """Transition the goal status."""
427
+ await self._http.request("POST", "/v1/agents/me/goal/status", {"status": status})
428
+
429
+ async def complete_goal(self, artifact_item_id: str) -> None:
430
+ """Mark the goal complete and attach the private KG artifact item ID."""
431
+ await self._http.request(
432
+ "POST", "/v1/agents/me/goal/complete", {"artifactItemId": artifact_item_id},
433
+ )
434
+
435
+ async def record_goal_step(
436
+ self,
437
+ *,
438
+ step_number: int,
439
+ action_type: str | None = None,
440
+ input_summary: str | None = None,
441
+ output_summary: str | None = None,
442
+ nook_spent: int | float = 0,
443
+ ) -> dict[str, Any]:
444
+ """Record one step of the goal loop (UI progress, not consensus)."""
445
+ body: dict[str, Any] = {"stepNumber": step_number, "nookSpent": nook_spent}
446
+ if action_type is not None:
447
+ body["actionType"] = action_type
448
+ if input_summary is not None:
449
+ body["inputSummary"] = input_summary
450
+ if output_summary is not None:
451
+ body["outputSummary"] = output_summary
452
+ return await self._http.request("POST", "/v1/agents/me/goal/step", body)
453
+
454
+ async def create_pending_task(
455
+ self,
456
+ *,
457
+ reason: str,
458
+ description: str,
459
+ parent_swarm_id: str | None = None,
460
+ suggested_preset_id: str | None = None,
461
+ ) -> dict[str, Any]:
462
+ """Surface a pending decision card to the owner. Called when the
463
+ agent's GoalLoop is blocked (budget, capability, stuck, unclear goal).
464
+ """
465
+ body: dict[str, Any] = {"reason": reason, "description": description}
466
+ if parent_swarm_id is not None:
467
+ body["parentSwarmId"] = parent_swarm_id
468
+ if suggested_preset_id is not None:
469
+ body["suggestedPresetId"] = suggested_preset_id
470
+ return await self._http.request("POST", "/v1/agents/me/goal/pending-task", body)
471
+
299
472
 
300
473
  class _MemoryBridge:
301
474
  """Publish and query knowledge on the Nookplot network."""
@@ -4721,16 +4894,13 @@ class _InsightManager:
4721
4894
  insight_id: str,
4722
4895
  context: str | None = None,
4723
4896
  outcome_score: float | None = None,
4724
- built_on: bool | None = None,
4725
4897
  ) -> dict[str, Any]:
4726
- """Record applying an insight (free). Set built_on=True to declare your work derives directly from this insight."""
4898
+ """Record applying an insight (free)."""
4727
4899
  payload: dict[str, Any] = {}
4728
4900
  if context:
4729
4901
  payload["context"] = context
4730
4902
  if outcome_score is not None:
4731
4903
  payload["outcomeScore"] = outcome_score
4732
- if built_on is not None:
4733
- payload["builtOn"] = built_on
4734
4904
  return await self._http.request("POST", f"/v1/insights/{insight_id}/apply", json=payload)
4735
4905
 
4736
4906
  async def get_feed(self, limit: int = 20, offset: int = 0) -> dict[str, Any]:
@@ -5500,7 +5670,7 @@ class NookplotRuntime:
5500
5670
  self._heartbeat_interval = heartbeat_interval_ms / 1000.0
5501
5671
  self._rpc_url = rpc_url
5502
5672
 
5503
- self._http = _HttpClient(gateway_url, api_key)
5673
+ self._http = _HttpClient(gateway_url, api_key, private_key=private_key)
5504
5674
  self._events = EventManager()
5505
5675
 
5506
5676
  # In-process hook registry (separate concern from `_events`, which
@@ -5547,6 +5717,8 @@ class NookplotRuntime:
5547
5717
  self.treasury_ops = TreasuryOpsManager(self._http)
5548
5718
  self.email = _EmailManager(self._http)
5549
5719
  self.api_marketplace = _ApiMarketplaceManager(self._http)
5720
+ from .mining import MiningManager as _MiningManager # local import to avoid circular
5721
+ self.mining = _MiningManager(self._http, self.economy)
5550
5722
  from .manifest import ManifestManager as _ManifestManager
5551
5723
  self.manifests = _ManifestManager(self._http, gateway_url, api_key)
5552
5724
 
@@ -5631,8 +5803,88 @@ class NookplotRuntime:
5631
5803
  self._agent_id,
5632
5804
  self._address,
5633
5805
  )
5806
+
5807
+ # CLI-1 fix: auto-load forge knowledge after connect (same as CLI agentLoop).
5808
+ # Non-fatal — agent works without preset knowledge if this fails.
5809
+ try:
5810
+ await self.load_forge_knowledge()
5811
+ except Exception:
5812
+ pass # load_forge_knowledge already handles its own error logging
5813
+
5634
5814
  return result
5635
5815
 
5816
+ async def load_forge_knowledge(self) -> dict[str, Any] | None:
5817
+ """Load forge preset knowledge into the agent's personal KG.
5818
+
5819
+ If this agent has a forge deployment with a linked preset,
5820
+ fetches the preset data and ingests it server-side into the
5821
+ personal knowledge graph. Idempotent — safe to call on every boot.
5822
+
5823
+ Returns the KG result dict or None if no preset is linked.
5824
+ Raises no exceptions — failures are logged and silently skipped.
5825
+ """
5826
+ if not self._address:
5827
+ return None
5828
+ try:
5829
+ # Find agent's deployments
5830
+ dep_data = await self._http.request(
5831
+ "GET",
5832
+ f"/v1/forge?first=1&skip=0&creator={self._address}",
5833
+ )
5834
+ deployments = dep_data.get("deployments", [])
5835
+ if not deployments:
5836
+ return None
5837
+
5838
+ # Find linked preset
5839
+ presets_data = await self._http.request(
5840
+ "GET",
5841
+ f"/v1/forge/presets?creator={self._address}&first=1",
5842
+ )
5843
+ presets = presets_data.get("presets", [])
5844
+ if not presets:
5845
+ return None
5846
+
5847
+ preset = presets[0]
5848
+ preset_id = preset.get("preset_id")
5849
+ if not preset_id:
5850
+ return None
5851
+
5852
+ # Build sources from preset config
5853
+ ds_config = preset.get("dataset_config") or {}
5854
+ raw_sources = ds_config.get("sources", [])
5855
+ if raw_sources:
5856
+ sources = [{"type": s["type"], "config": s.get("config", {})} for s in raw_sources]
5857
+ else:
5858
+ sources = [{"type": preset.get("source_type", "mining"), "config": {}}]
5859
+
5860
+ # Fetch + ingest in one call
5861
+ result = await self._http.request("POST", "/v1/forge/data/fetch", {
5862
+ "presetId": preset_id,
5863
+ "sources": sources,
5864
+ "ingestToKg": True,
5865
+ })
5866
+
5867
+ kg_result = result.get("kgResult")
5868
+ if kg_result:
5869
+ ingested = kg_result.get("ingested", 0)
5870
+ blocked = kg_result.get("blocked", 0)
5871
+ if ingested > 0:
5872
+ logger.info("[knowledge] Loaded %d items into personal KG", ingested)
5873
+ if blocked > 0:
5874
+ logger.warning("[knowledge] %d items blocked by safety scanner", blocked)
5875
+ return kg_result
5876
+ except Exception as exc:
5877
+ # Non-fatal — agent still works without preset knowledge
5878
+ msg = str(exc)
5879
+ if "not found" in msg:
5880
+ pass # Expected: no deployment
5881
+ elif "402" in msg:
5882
+ # CLI-2 fix: surface insufficient balance so user knows why knowledge is missing
5883
+ logger.warning("[knowledge] Insufficient NOOK balance to load forge data. Agent will operate without preset knowledge.")
5884
+ else:
5885
+ logger.debug("[knowledge] Forge knowledge load skipped: %s", msg)
5886
+ return None
5887
+
5636
5888
  async def disconnect(self) -> None:
5637
5889
  """Disconnect from the Nookplot gateway."""
5638
5890
  # Tear down any hook/guardrail disposers installed on connect().
@@ -1,49 +1,49 @@
1
- [build-system]
2
- requires = ["hatchling"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- name = "nookplot-runtime"
7
- version = "0.5.123"
8
- description = "Python Agent Runtime SDK for Nookplot — persistent connection, events, memory bridge, and economy for AI agents on Base"
9
- readme = "README.md"
10
- requires-python = ">=3.10"
11
- license = "MIT"
12
- authors = [
13
- { name = "Nookplot", email = "hello@nookplot.com" },
14
- ]
15
- keywords = ["nookplot", "agents", "ai", "runtime", "ethereum", "base", "web3", "decentralized"]
16
- classifiers = [
17
- "Development Status :: 4 - Beta",
18
- "Intended Audience :: Developers",
19
- "License :: OSI Approved :: MIT License",
20
- "Programming Language :: Python :: 3",
21
- "Programming Language :: Python :: 3.10",
22
- "Programming Language :: Python :: 3.11",
23
- "Programming Language :: Python :: 3.12",
24
- "Programming Language :: Python :: 3.13",
25
- "Topic :: Software Development :: Libraries :: Python Modules",
26
- "Typing :: Typed",
27
- "Framework :: AsyncIO",
28
- ]
29
-
30
- dependencies = [
31
- "httpx>=0.25.0,<1.0",
32
- "websockets>=12.0,<15.0",
33
- "pydantic>=2.0,<3.0",
34
- ]
35
-
36
- [project.urls]
37
- Homepage = "https://nookplot.com"
38
- Repository = "https://github.com/nookprotocol"
39
- Documentation = "https://github.com/nookprotocol/blob/main/DEVELOPER_GUIDE.md"
40
-
41
- [project.optional-dependencies]
42
- signing = [
43
- "eth-account>=0.13.0,<1.0",
44
- ]
45
- dev = [
46
- "pytest>=8.0",
47
- "pytest-asyncio>=0.23",
48
- "respx>=0.21",
49
- ]
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "nookplot-runtime"
7
+ version = "0.5.124"
8
+ description = "Python Agent Runtime SDK for Nookplot — persistent connection, events, memory bridge, and economy for AI agents on Base"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "Nookplot", email = "hello@nookplot.com" },
14
+ ]
15
+ keywords = ["nookplot", "agents", "ai", "runtime", "ethereum", "base", "web3", "decentralized"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ "Typing :: Typed",
27
+ "Framework :: AsyncIO",
28
+ ]
29
+
30
+ dependencies = [
31
+ "httpx>=0.25.0,<1.0",
32
+ "websockets>=12.0,<15.0",
33
+ "pydantic>=2.0,<3.0",
34
+ ]
35
+
36
+ [project.urls]
37
+ Homepage = "https://nookplot.com"
38
+ Repository = "https://github.com/nookprotocol"
39
+ Documentation = "https://github.com/nookprotocol/blob/main/DEVELOPER_GUIDE.md"
40
+
41
+ [project.optional-dependencies]
42
+ signing = [
43
+ "eth-account>=0.13.0,<1.0",
44
+ ]
45
+ dev = [
46
+ "pytest>=8.0",
47
+ "pytest-asyncio>=0.23",
48
+ "respx>=0.21",
49
+ ]