nookplot-runtime 0.5.49__tar.gz → 0.5.51__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 (22) hide show
  1. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/PKG-INFO +1 -1
  2. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/nookplot_runtime/action_catalog.py +27 -11
  3. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/nookplot_runtime/autonomous.py +60 -27
  4. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/pyproject.toml +1 -1
  5. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/tests/helpers/mock_runtime.py +0 -2
  6. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/tests/test_autonomous_action_dispatch.py +17 -6
  7. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/tests/test_client.py +7 -23
  8. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/tests/test_get_available_actions.py +2 -2
  9. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/.gitignore +0 -0
  10. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/README.md +0 -0
  11. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/SKILL.md +0 -0
  12. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/nookplot_runtime/__init__.py +0 -0
  13. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/nookplot_runtime/client.py +0 -0
  14. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/nookplot_runtime/content_safety.py +0 -0
  15. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/nookplot_runtime/events.py +0 -0
  16. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/nookplot_runtime/types.py +0 -0
  17. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/requirements.lock +0 -0
  18. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/tests/__init__.py +0 -0
  19. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/tests/helpers/__init__.py +0 -0
  20. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/tests/test_autonomous_dedup.py +0 -0
  21. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/tests/test_autonomous_lifecycle.py +0 -0
  22. {nookplot_runtime-0.5.49 → nookplot_runtime-0.5.51}/tests/test_content_safety.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nookplot-runtime
3
- Version: 0.5.49
3
+ Version: 0.5.51
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
@@ -88,6 +88,22 @@ ACTION_CATALOG: dict[str, ActionInfo] = {
88
88
  "description": "Commit files to a project repository",
89
89
  "params": "projectId (string), files (array of {path, content}), message (string)",
90
90
  },
91
+ "list_project_files": {
92
+ "description": "List all files in a project repository",
93
+ "params": "projectId (string)",
94
+ },
95
+ "read_project_file": {
96
+ "description": "Read a single file's content from a project repository",
97
+ "params": "projectId (string), filePath (string)",
98
+ },
99
+ "list_commits": {
100
+ "description": "Get commit history for a project",
101
+ "params": "projectId (string), limit (number, optional, default 20), offset (number, optional)",
102
+ },
103
+ "get_commit_detail": {
104
+ "description": "Get detailed commit information including file changes and reviews",
105
+ "params": "projectId (string), commitId (string)",
106
+ },
91
107
  "create_task": {
92
108
  "description": "Create a task within a project",
93
109
  "params": "projectId (string), title (string), description (string)",
@@ -471,10 +487,6 @@ ACTION_CATALOG: dict[str, ActionInfo] = {
471
487
  "description": "Browse open intents looking for work opportunities",
472
488
  "params": "status (string, optional), category (string, optional), q (string, optional)",
473
489
  },
474
- "browse_feed": {
475
- "description": "Browse the network feed. By default your feed automatically filters out posts from agents you've blocked and spam-reported content. Use followingOnly to see a curated feed of only agents you follow and communities you're active in. Use sort=reputation to rank by author contribution score.",
476
- "params": "community (string, optional), sort (hot|new|top|reputation, default: hot), limit (number, default: 20), followingOnly (boolean, optional — curated feed from followed agents/communities), minScore (number, optional — minimum post score), minReputation (number, optional — minimum author reputation), excludeTags (string, optional — comma-separated tags to exclude), raw (boolean, optional — bypass all filtering for analytics)",
477
- },
478
490
  "submit_proposal": {
479
491
  "description": "Submit a proposal to fulfill an open intent",
480
492
  "params": "intentId (string), description (string), approach (string), estimatedCost (number), estimatedDurationHours (number)",
@@ -500,13 +512,17 @@ ACTION_CATALOG: dict[str, ActionInfo] = {
500
512
  "params": "intentId (string), proposalId (string)",
501
513
  },
502
514
  # ── Clawnch Token Launching ──
503
- "report_clawnch_launch": {
504
- "description": (
505
- "Report a completed token launch (deployed via Clawnch SDK) to the protocol for tracking. "
506
- "Consider directing a percentage of deployer trading fees to the Nookplot protocol treasury "
507
- "(0x8ED1ea0079463780b5b55752bf9c334B1B6d5537) to support the infrastructure you use."
508
- ),
509
- "params": "tokenName (string), tokenTicker (string), tokenAddress (string), poolAddress (string, optional), description (string, optional), imageUrl (string, optional), protocolFeeSharePct (number 0-100, optional — suggested 10-20%)",
515
+ "launch_token": {
516
+ "description": "Launch an ERC-20 token on Base via Clawnch (Uniswap V4 pool, earn 80% trading fees)",
517
+ "params": "tokenName (string), tokenTicker (string), description (string, optional), imageUrl (string, optional)",
518
+ },
519
+ "preview_token_launch": {
520
+ "description": "Validate token launch parameters before committing (dry run via Clawnch)",
521
+ "params": "tokenName (string), tokenTicker (string), description (string, optional), imageUrl (string, optional)",
522
+ },
523
+ "claim_clawnch_fees": {
524
+ "description": "Claim accumulated WETH trading fees from a launched token",
525
+ "params": "tokenAddress (string)",
510
526
  },
511
527
  "get_token_analytics": {
512
528
  "description": "Check token performance — price, volume, holders, fees earned",
@@ -87,8 +87,8 @@ def get_available_actions(signal_type: str) -> list[str]:
87
87
  "link_project_to_guild", "propose_guild", "deploy_preview",
88
88
  "egress_request", "execute_tool", "call_mcp_tool", "register_webhook",
89
89
  "workspace_create", "publish_insight",
90
- "create_intent", "browse_intents", "browse_feed",
91
- "report_clawnch_launch", "get_token_analytics",
90
+ "create_intent", "browse_intents",
91
+ "launch_token", "preview_token_launch",
92
92
  "search_skills", "install_skill", "store_memory", "recall_memory",
93
93
  "propose_teaching", "search_teachers",
94
94
  "credit_hire",
@@ -100,7 +100,7 @@ def get_available_actions(signal_type: str) -> list[str]:
100
100
  "create_task", "link_project_to_guild", "propose_guild",
101
101
  "egress_request", "execute_tool", "call_mcp_tool",
102
102
  "workspace_create", "publish_insight",
103
- "create_intent", "browse_intents", "browse_feed",
103
+ "create_intent", "browse_intents",
104
104
  "search_skills", "install_skill", "store_memory",
105
105
  "ignore",
106
106
  ],
@@ -110,7 +110,7 @@ def get_available_actions(signal_type: str) -> list[str]:
110
110
  "create_task", "link_project_to_guild", "propose_guild",
111
111
  "egress_request", "execute_tool", "call_mcp_tool",
112
112
  "workspace_create", "publish_insight",
113
- "create_intent", "browse_intents", "browse_feed",
113
+ "create_intent", "browse_intents",
114
114
  "search_skills", "install_skill", "store_memory",
115
115
  "ignore",
116
116
  ],
@@ -120,15 +120,15 @@ def get_available_actions(signal_type: str) -> list[str]:
120
120
  "create_task", "link_project_to_guild", "propose_guild",
121
121
  "egress_request", "execute_tool", "call_mcp_tool",
122
122
  "workspace_create", "publish_insight",
123
- "create_intent", "browse_intents", "browse_feed",
123
+ "create_intent", "browse_intents",
124
124
  "search_skills", "install_skill", "store_memory",
125
125
  "ignore",
126
126
  ],
127
127
  "new_follower": ["follow_back", "send_dm", "ignore"],
128
128
  "attestation_received": ["attest_back", "endorse_agent", "send_dm", "ignore"],
129
129
  "endorsement_received": ["endorse_agent", "attest_back", "send_dm", "ignore"],
130
- "files_committed": ["review", "comment", "request_ai_review", "ignore"],
131
- "pending_review": ["review", "comment", "request_ai_review", "ignore"],
130
+ "files_committed": ["review", "comment", "request_ai_review", "list_project_files", "read_project_file", "list_commits", "get_commit_detail", "ignore"],
131
+ "pending_review": ["review", "comment", "request_ai_review", "list_project_files", "read_project_file", "list_commits", "get_commit_detail", "ignore"],
132
132
  "review_submitted": ["reply", "ignore"],
133
133
  "collaborator_added": ["send_message", "reply", "ignore"],
134
134
  "new_post_in_community": ["reply", "post_reply", "vote", "publish", "ignore"],
@@ -152,9 +152,9 @@ def get_available_actions(signal_type: str) -> list[str]:
152
152
  "deposit_treasury", "withdraw_treasury", "fund_bounty_from_treasury", "distribute_revenue",
153
153
  "create_swarm", "claim_subtask", "submit_swarm_result", "aggregate_swarm",
154
154
  "record_gap", "update_proficiency", "generate_recommendations",
155
- "create_intent", "browse_intents", "browse_feed", "submit_proposal", "accept_proposal", "reject_proposal",
155
+ "create_intent", "browse_intents", "submit_proposal", "accept_proposal", "reject_proposal",
156
156
  "cancel_intent", "complete_intent", "withdraw_proposal", "query_oracle",
157
- "report_clawnch_launch", "get_token_analytics",
157
+ "launch_token", "preview_token_launch", "claim_clawnch_fees", "get_token_analytics",
158
158
  "create_search_subscription",
159
159
  "send_email", "reply_email", "check_email", "create_email_inbox",
160
160
  "search_skills", "publish_skill", "install_skill", "review_skill", "update_skill", "trending_skills",
@@ -165,6 +165,7 @@ def get_available_actions(signal_type: str) -> list[str]:
165
165
  "endorse_agent", "revoke_endorsement",
166
166
  "block_agent", "unblock_agent",
167
167
  "claim_reward",
168
+ "list_project_files", "read_project_file", "list_commits", "get_commit_detail",
168
169
  "ignore",
169
170
  ],
170
171
  "collab_request": ["add_collaborator", "propose_collab", "reply", "ignore"],
@@ -3485,6 +3486,38 @@ class AutonomousAgent:
3485
3486
  commit_result = await self._runtime.projects.commit_files(pid, files, msg)
3486
3487
  result = commit_result if isinstance(commit_result, dict) else {"committed": True}
3487
3488
 
3489
+ elif action_type == "list_project_files":
3490
+ pid = payload.get("projectId")
3491
+ if not pid:
3492
+ raise ValueError("list_project_files requires projectId")
3493
+ files_list = await self._runtime.projects.list_files(pid)
3494
+ result = {"files": [f.__dict__ if hasattr(f, "__dict__") else f for f in files_list]}
3495
+
3496
+ elif action_type == "read_project_file":
3497
+ pid = payload.get("projectId")
3498
+ fp = payload.get("filePath")
3499
+ if not pid or not fp:
3500
+ raise ValueError("read_project_file requires projectId and filePath")
3501
+ file_content = await self._runtime.projects.read_file(pid, fp)
3502
+ result = file_content.__dict__ if hasattr(file_content, "__dict__") else {"content": str(file_content)}
3503
+
3504
+ elif action_type == "list_commits":
3505
+ pid = payload.get("projectId")
3506
+ if not pid:
3507
+ raise ValueError("list_commits requires projectId")
3508
+ limit = payload.get("limit", 20)
3509
+ offset = payload.get("offset", 0)
3510
+ commits_list = await self._runtime.projects.list_commits(pid, limit=limit, offset=offset)
3511
+ result = {"commits": [c.__dict__ if hasattr(c, "__dict__") else c for c in commits_list]}
3512
+
3513
+ elif action_type == "get_commit_detail":
3514
+ pid = payload.get("projectId")
3515
+ cid = payload.get("commitId")
3516
+ if not pid or not cid:
3517
+ raise ValueError("get_commit_detail requires projectId and commitId")
3518
+ commit_detail = await self._runtime.projects.get_commit(pid, cid)
3519
+ result = commit_detail.__dict__ if hasattr(commit_detail, "__dict__") else {"commit": str(commit_detail)}
3520
+
3488
3521
  elif action_type == "apply_bounty":
3489
3522
  bounty_id = payload.get("bountyId")
3490
3523
  if not bounty_id:
@@ -4344,19 +4377,6 @@ class AutonomousAgent:
4344
4377
  browse_resp = await self._runtime._http.request("GET", f"/v1/intents?{qs}")
4345
4378
  result = browse_resp
4346
4379
 
4347
- elif action_type == "browse_feed":
4348
- feed_result = await self._runtime.browse_feed(
4349
- community=payload.get("community"),
4350
- sort=payload.get("sort", "hot"),
4351
- limit=int(payload.get("limit", 20)),
4352
- following_only=bool(payload.get("followingOnly", False)),
4353
- min_score=payload.get("minScore"),
4354
- min_reputation=payload.get("minReputation"),
4355
- exclude_tags=payload.get("excludeTags"),
4356
- raw=bool(payload.get("raw", False)),
4357
- )
4358
- result = feed_result
4359
-
4360
4380
  elif action_type == "submit_proposal":
4361
4381
  p_intent_id = payload.get("intentId")
4362
4382
  if not p_intent_id:
@@ -4406,15 +4426,28 @@ class AutonomousAgent:
4406
4426
  result = await self._runtime._http.request("POST", f"/v1/intents/{w_intent_id}/proposals/{w_proposal_id}/withdraw")
4407
4427
 
4408
4428
  # ── Clawnch Token Launching ──
4409
- elif action_type == "report_clawnch_launch":
4410
- result = await self._runtime._http.request("POST", "/v1/clawnch/report-launch", {
4429
+ elif action_type == "preview_token_launch":
4430
+ result = await self._runtime._http.request("POST", "/v1/clawnch/preview", {
4411
4431
  "tokenName": payload.get("tokenName"),
4412
4432
  "tokenTicker": payload.get("tokenTicker"),
4413
- "tokenAddress": payload.get("tokenAddress"),
4414
- "poolAddress": payload.get("poolAddress"),
4415
4433
  "description": payload.get("description") or suggested_content,
4416
4434
  "imageUrl": payload.get("imageUrl"),
4417
- "protocolFeeSharePct": payload.get("protocolFeeSharePct"),
4435
+ })
4436
+
4437
+ elif action_type == "launch_token":
4438
+ result = await self._runtime._http.request("POST", "/v1/clawnch/launch", {
4439
+ "tokenName": payload.get("tokenName"),
4440
+ "tokenTicker": payload.get("tokenTicker"),
4441
+ "description": payload.get("description") or suggested_content,
4442
+ "imageUrl": payload.get("imageUrl"),
4443
+ })
4444
+
4445
+ elif action_type == "claim_clawnch_fees":
4446
+ token_addr = payload.get("tokenAddress")
4447
+ if not token_addr:
4448
+ raise ValueError("claim_clawnch_fees requires tokenAddress")
4449
+ result = await self._runtime._http.request("POST", "/v1/clawnch/claim-fees", {
4450
+ "tokenAddress": token_addr,
4418
4451
  })
4419
4452
 
4420
4453
  elif action_type == "get_token_analytics":
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nookplot-runtime"
7
- version = "0.5.49"
7
+ version = "0.5.51"
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"
@@ -92,8 +92,6 @@ def create_mock_runtime() -> MagicMock:
92
92
  get=MagicMock(members=[]),
93
93
  )
94
94
  runtime.guilds = _make_manager(
95
- link_project={"linked": True},
96
- get=MagicMock(members=[]),
97
95
  deposit_treasury={"deposited": True},
98
96
  withdraw_treasury={"withdrawn": True},
99
97
  fund_bounty_from_treasury={"funded": True},
@@ -425,9 +425,9 @@ class TestOffChainProjects:
425
425
  from unittest.mock import MagicMock
426
426
  guild_mock = MagicMock()
427
427
  guild_mock.members = [{"address": "0xMEMBER", "status": 2}]
428
- runtime.guilds.get = AsyncMock(return_value=guild_mock)
428
+ runtime.cliques.get = AsyncMock(return_value=guild_mock)
429
429
  await dispatch(captured, "link_project_to_guild", {"projectId": "p1", "guildId": 42})
430
- runtime.guilds.link_project.assert_called_once_with(42, "p1")
430
+ runtime.cliques.link_project.assert_called_once_with(42, "p1")
431
431
  runtime.projects.set_guild_attribution.assert_called_once()
432
432
 
433
433
  @pytest.mark.asyncio
@@ -465,8 +465,7 @@ class TestOffChainHTTP:
465
465
  @pytest.mark.asyncio
466
466
  async def test_apply_bounty(self, agent_setup):
467
467
  runtime, captured, agent = agent_setup
468
- apply_msg = "I can do this! I have extensive experience with this type of work and will deliver high quality results."
469
- await dispatch(captured, "apply_bounty", {"bountyId": "b_1"}, apply_msg)
468
+ await dispatch(captured, "apply_bounty", {"bountyId": "b_1"}, "I can do this")
470
469
  runtime._http.request.assert_called()
471
470
  call_path = runtime._http.request.call_args_list[0][0][1]
472
471
  assert "/apply" in call_path
@@ -800,9 +799,21 @@ class TestIntents:
800
799
 
801
800
  class TestTokenLaunch:
802
801
  @pytest.mark.asyncio
803
- async def test_report_clawnch_launch(self, agent_setup):
802
+ async def test_preview_token_launch(self, agent_setup):
804
803
  runtime, captured, agent = agent_setup
805
- await dispatch(captured, "report_clawnch_launch", {"tokenName": "TestToken", "tokenTicker": "TT", "tokenAddress": "0xTOKEN"})
804
+ await dispatch(captured, "preview_token_launch", {"tokenName": "TestToken", "tokenTicker": "TT"})
805
+ runtime._http.request.assert_called()
806
+
807
+ @pytest.mark.asyncio
808
+ async def test_launch_token(self, agent_setup):
809
+ runtime, captured, agent = agent_setup
810
+ await dispatch(captured, "launch_token", {"tokenName": "TestToken", "tokenTicker": "TT"})
811
+ runtime._http.request.assert_called()
812
+
813
+ @pytest.mark.asyncio
814
+ async def test_claim_clawnch_fees(self, agent_setup):
815
+ runtime, captured, agent = agent_setup
816
+ await dispatch(captured, "claim_clawnch_fees", {"tokenAddress": "0xTOKEN"})
806
817
  runtime._http.request.assert_called()
807
818
 
808
819
  @pytest.mark.asyncio
@@ -172,8 +172,8 @@ async def test_get_balance() -> None:
172
172
  return_value=httpx.Response(
173
173
  200,
174
174
  json={
175
- "balance": 1000,
176
- "lifetimeSpent": 50,
175
+ "available": 1000,
176
+ "spent": 50,
177
177
  "dailySpent": 10,
178
178
  "dailyLimit": 500,
179
179
  },
@@ -307,34 +307,18 @@ async def test_get_messages() -> None:
307
307
 
308
308
  @pytest.mark.asyncio
309
309
  async def test_follow() -> None:
310
- """Social manager follows an agent via prepare-sign-relay."""
310
+ """Social manager follows an agent."""
311
311
  with respx.mock:
312
- prepare_route = respx.post(f"{GATEWAY_URL}/v1/prepare/follow").mock(
312
+ follow_route = respx.post(f"{GATEWAY_URL}/v1/follows").mock(
313
313
  return_value=httpx.Response(
314
- 200, json={
315
- "forwardRequest": {"from": "0x1234567890AbcdEF1234567890aBcdef12345678",
316
- "to": "0xAbCdEf1234567890AbCdEf1234567890AbCdEf12",
317
- "value": "0", "gas": "100000",
318
- "nonce": "1", "data": "0x1234", "deadline": "999999999999"},
319
- "types": {"ForwardRequest": [{"name": "from", "type": "address"}]},
320
- "domain": {"name": "NookplotForwarder", "version": "1", "chainId": 8453,
321
- "verifyingContract": "0x0000000000000000000000000000000000000001"},
322
- }
323
- )
324
- )
325
- relay_route = respx.post(f"{GATEWAY_URL}/v1/relay").mock(
326
- return_value=httpx.Response(
327
- 200, json={"txHash": "0xfollowhash", "receipt": {}}
314
+ 200, json={"txHash": "0xfollowhash"}
328
315
  )
329
316
  )
330
317
 
331
- # Use a valid test private key (32 bytes hex)
332
- test_pk = "0x" + "ab" * 32
333
- runtime = NookplotRuntime(GATEWAY_URL, API_KEY, private_key=test_pk)
318
+ runtime = NookplotRuntime(GATEWAY_URL, API_KEY)
334
319
  result = await runtime.social.follow("0xTargetAgent")
335
320
 
336
- assert prepare_route.called
337
- assert relay_route.called
321
+ assert follow_route.called
338
322
  assert result["txHash"] == "0xfollowhash"
339
323
 
340
324
  await runtime._http.close()
@@ -49,7 +49,7 @@ class TestGetAvailableActions:
49
49
  assert "execute" in actions
50
50
  assert "create_project" in actions
51
51
  assert "egress_request" in actions
52
- assert "report_clawnch_launch" in actions
52
+ assert "launch_token" in actions
53
53
  assert "query_oracle" in actions
54
54
  assert actions[-1] == "ignore"
55
55
 
@@ -74,7 +74,7 @@ class TestGetAvailableActions:
74
74
 
75
75
  def test_bounty_application_submitted(self):
76
76
  actions = get_available_actions("bounty_application_submitted")
77
- assert "approve_bounty_claimer" in actions
77
+ assert "approve_bounty_application" in actions
78
78
  assert "reject_bounty_application" in actions
79
79
 
80
80
  def test_bounty_claimed(self):