@calimero-network/agent-skills 0.2.0 → 0.4.0

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 (78) hide show
  1. package/README.md +137 -17
  2. package/SKILL.md +31 -23
  3. package/package.json +2 -2
  4. package/scripts/install.js +3 -3
  5. package/scripts/test.js +6 -15
  6. package/skills/calimero-abi-codegen/SKILL.md +121 -22
  7. package/skills/calimero-abi-codegen/references/abi-format.md +3 -5
  8. package/skills/calimero-abi-codegen/references/generated-output.md +12 -4
  9. package/skills/calimero-abi-codegen/rules/schema-version.md +11 -4
  10. package/skills/calimero-abi-codegen/rules/unique-names.md +2 -6
  11. package/skills/calimero-client-js/SKILL.md +127 -22
  12. package/skills/calimero-client-js/references/auth.md +18 -10
  13. package/skills/calimero-client-js/references/rpc-calls.md +15 -21
  14. package/skills/calimero-client-js/references/sso.md +9 -9
  15. package/skills/calimero-client-js/references/websocket-events.md +73 -59
  16. package/skills/calimero-client-js/rules/camelcase-api.md +10 -7
  17. package/skills/calimero-client-js/rules/token-refresh.md +59 -21
  18. package/skills/calimero-client-py/SKILL.md +26 -10
  19. package/skills/calimero-client-py/references/api.md +41 -43
  20. package/skills/calimero-client-py/references/auth.md +7 -7
  21. package/skills/calimero-client-py/rules/async-usage.md +27 -31
  22. package/skills/calimero-client-py/rules/stable-node-name.md +7 -7
  23. package/skills/calimero-core/SKILL.md +135 -0
  24. package/skills/calimero-core/references/architecture.md +101 -0
  25. package/skills/calimero-core/references/jsonrpc-protocol.md +192 -0
  26. package/skills/calimero-core/references/namespaces-groups.md +94 -0
  27. package/skills/calimero-core/references/storage-types.md +118 -0
  28. package/skills/calimero-core/references/websocket-events.md +142 -0
  29. package/skills/calimero-core/rules/context-is-not-app.md +35 -0
  30. package/skills/calimero-core/rules/crdt-types-only.md +55 -0
  31. package/skills/calimero-desktop/SKILL.md +25 -14
  32. package/skills/calimero-desktop/references/sso-integration.md +49 -22
  33. package/skills/calimero-desktop/rules/sso-fallback.md +3 -2
  34. package/skills/calimero-merobox/SKILL.md +255 -28
  35. package/skills/calimero-merobox/references/ci-integration.md +3 -2
  36. package/skills/calimero-merobox/references/workflow-files.md +7 -5
  37. package/skills/calimero-merobox/rules/docker-required.md +7 -6
  38. package/skills/calimero-meroctl/SKILL.md +68 -0
  39. package/skills/calimero-meroctl/references/commands.md +177 -0
  40. package/skills/calimero-meroctl/references/scripting.md +80 -0
  41. package/skills/calimero-meroctl/rules/call-view-flag.md +28 -0
  42. package/skills/calimero-meroctl/rules/register-node-once.md +34 -0
  43. package/skills/calimero-merod/SKILL.md +49 -0
  44. package/skills/calimero-merod/references/health-endpoints.md +90 -0
  45. package/skills/calimero-merod/references/init-flags.md +84 -0
  46. package/skills/calimero-merod/rules/init-before-run.md +40 -0
  47. package/skills/calimero-merod/rules/port-assignments.md +33 -0
  48. package/skills/calimero-node/SKILL.md +52 -35
  49. package/skills/calimero-node/references/context-lifecycle.md +34 -17
  50. package/skills/calimero-node/references/meroctl-commands.md +89 -99
  51. package/skills/calimero-node/rules/app-vs-context.md +4 -4
  52. package/skills/calimero-registry/SKILL.md +110 -31
  53. package/skills/calimero-registry/references/bundle-and-push.md +99 -34
  54. package/skills/calimero-registry/references/manifest-format.md +56 -35
  55. package/skills/calimero-registry/references/mero-sign.md +10 -9
  56. package/skills/calimero-registry/rules/key-security.md +3 -2
  57. package/skills/calimero-registry/rules/sign-before-pack.md +5 -5
  58. package/skills/calimero-rust-sdk/SKILL.md +154 -44
  59. package/skills/calimero-rust-sdk/references/blob-api.md +119 -0
  60. package/skills/calimero-rust-sdk/references/event-handlers.md +122 -0
  61. package/skills/calimero-rust-sdk/references/events.md +2 -1
  62. package/skills/calimero-rust-sdk/references/examples.md +81 -29
  63. package/skills/calimero-rust-sdk/references/migrations.md +123 -0
  64. package/skills/calimero-rust-sdk/references/nested-crdts.md +113 -0
  65. package/skills/calimero-rust-sdk/references/private-storage.md +76 -34
  66. package/skills/calimero-rust-sdk/references/state-collections.md +106 -21
  67. package/skills/calimero-rust-sdk/references/user-and-frozen-storage.md +169 -0
  68. package/skills/calimero-rust-sdk/rules/app-macro-placement.md +5 -2
  69. package/skills/calimero-rust-sdk/rules/no-std-collections.md +5 -2
  70. package/skills/calimero-rust-sdk/rules/state-derives.md +45 -0
  71. package/skills/calimero-rust-sdk/rules/wasm-constraints.md +12 -10
  72. package/skills/calimero-sdk-js/SKILL.md +145 -0
  73. package/skills/calimero-sdk-js/references/build-pipeline.md +98 -0
  74. package/skills/calimero-sdk-js/references/collections.md +132 -0
  75. package/skills/calimero-sdk-js/references/events.md +63 -0
  76. package/skills/calimero-sdk-js/rules/crdt-only-state.md +47 -0
  77. package/skills/calimero-sdk-js/rules/no-console-log.md +38 -0
  78. package/skills/calimero-sdk-js/rules/view-decorator.md +48 -0
@@ -1,13 +1,14 @@
1
1
  # Rule: mero-js v2 uses camelCase — not snake_case
2
2
 
3
- `@calimero-network/mero-js` v2 (`>=2.0.0-beta.1`) changed all request object field names from `snake_case` to `camelCase`.
3
+ `@calimero-network/mero-js` v2 (`>=2.0.0-beta.1`) changed all request object field names from
4
+ `snake_case` to `camelCase`.
4
5
 
5
6
  ## WRONG (v1 / old code):
6
7
 
7
8
  ```typescript
8
9
  await client.generateClientKey({
9
- context_id: contextId, // ✗
10
- context_identity: identity, // ✗
10
+ context_id: contextId, // ✗
11
+ context_identity: identity, // ✗
11
12
  });
12
13
  ```
13
14
 
@@ -15,8 +16,8 @@ await client.generateClientKey({
15
16
 
16
17
  ```typescript
17
18
  await client.generateClientKey({
18
- contextId: contextId, // ✓
19
- contextIdentity: identity, // ✓
19
+ contextId: contextId, // ✓
20
+ contextIdentity: identity, // ✓
20
21
  });
21
22
  ```
22
23
 
@@ -27,11 +28,13 @@ await client.generateClientKey({
27
28
 
28
29
  ## Why this matters
29
30
 
30
- The v2 mero-js package does not alias the old snake_case keys. Passing `context_id` silently
31
- sends `undefined` to the server, which returns a cryptic error rather than a clear "wrong field name" message.
31
+ The v2 mero-js package does not alias the old snake_case keys. Passing `context_id` silently sends
32
+ `undefined` to the server, which returns a cryptic error rather than a clear "wrong field name"
33
+ message.
32
34
 
33
35
  ## How to detect if a codebase is on v1 or v2
34
36
 
35
37
  Check `package.json`:
38
+
36
39
  - `"@calimero-network/mero-js": "^2.0.0-beta.1"` or higher → v2, use camelCase
37
40
  - `"@calimero-network/calimero-client": "..."` → original client, check its docs
@@ -1,41 +1,79 @@
1
- # Rule: Always handle 401 with token refresh
1
+ # Rule: rpcClient handles token refresh automatically — but not all 401s
2
2
 
3
- Access tokens expire. Any RPC call or WebSocket connection can return `401 Unauthorized`. Never let this surface as an unhandled error.
3
+ `rpcClient` (from `@calimero-network/calimero-client`) automatically retries with a refreshed token
4
+ when the server returns `401 token_expired`. **You do not need to implement a refresh wrapper around
5
+ `rpcClient.execute()` calls.**
4
6
 
5
- ## Pattern
7
+ What you DO need to handle: 401s that cannot be refreshed.
8
+
9
+ ## WRONG — manual refresh wrapper (not needed for rpcClient):
6
10
 
7
11
  ```typescript
8
- async function callWithRefresh<T>(callFn: () => Promise<T>): Promise<T> {
12
+ // Don't do this rpcClient already retries on token_expired
13
+ async function callWithRefresh<T>(fn: () => Promise<T>) {
9
14
  try {
10
- return await callFn();
15
+ return await fn();
11
16
  } catch (err: any) {
12
- if (err?.code === 401 || err?.status === 401) {
13
- await refreshAccessToken();
14
- return callFn(); // retry once
17
+ if (err?.code === 401) {
18
+ await refreshAccessToken(); // rpcClient already does this
19
+ return fn();
15
20
  }
16
21
  throw err;
17
22
  }
18
23
  }
24
+ ```
25
+
26
+ ## CORRECT — handle only non-refreshable auth failures:
27
+
28
+ ```typescript
29
+ const response = await rpcClient.execute({ ... });
30
+
31
+ if (response.error) {
32
+ const authError = response.error.headers?.['x-auth-error'];
19
33
 
20
- async function refreshAccessToken() {
21
- const jwt = getJWTObject();
22
- if (!jwt?.refresh_token) {
23
- // No refresh token — redirect to login
24
- window.location.href = '/login';
34
+ if (response.error.code === 401) {
35
+ if (authError === 'token_revoked' || authError === 'invalid_token') {
36
+ // Token is invalid — cannot be refreshed. Send user to login.
37
+ clientLogout();
38
+ return;
39
+ }
40
+ // token_expired: rpcClient already retried and the retry failed.
41
+ // This means the refresh token is also expired — send to login.
42
+ clientLogout();
25
43
  return;
26
44
  }
27
45
 
28
- const newTokens = await client.refreshToken(jwt.refresh_token);
29
- localStorage.setItem('calimero_jwt', JSON.stringify(newTokens));
46
+ throw new Error(response.error.error.cause.info?.message ?? 'Unknown error');
30
47
  }
31
48
  ```
32
49
 
33
- ## Why
50
+ ## WebSocket connections are different — no auto-refresh
51
+
52
+ `WsSubscriptionsClient` does **not** auto-refresh tokens. If the token expires while a WebSocket
53
+ connection is open, events will stop arriving silently. Reconnect manually:
34
54
 
35
- Access tokens are short-lived by design. Without refresh handling, users will see
36
- random authentication errors mid-session. The refresh token is longer-lived and
37
- should be used to silently re-authenticate.
55
+ ```typescript
56
+ async function connectWithAuth() {
57
+ const ws = new WsSubscriptionsClient(getAppEndpointKey()!, '/ws');
58
+ try {
59
+ await ws.connect();
60
+ ws.subscribe([getContextId()!]);
61
+ ws.addCallback(handleEvent);
62
+ } catch (err: any) {
63
+ if (err?.status === 401) {
64
+ // Token expired before WS connect — logout and re-authenticate
65
+ clientLogout();
66
+ }
67
+ throw err;
68
+ }
69
+ }
70
+ ```
38
71
 
39
- ## What to do if refresh also fails
72
+ ## Summary
40
73
 
41
- Redirect to login. Do not retry the refresh indefinitely.
74
+ | Scenario | Handled by |
75
+ | --------------------------------- | --------------------------------------------- |
76
+ | `401 token_expired` on RPC call | `rpcClient` automatically (transparent retry) |
77
+ | `401 token_revoked` on RPC call | You — redirect to login |
78
+ | `401 invalid_token` on RPC call | You — redirect to login |
79
+ | Auth failure on WebSocket connect | You — catch and redirect to login |
@@ -1,15 +1,23 @@
1
1
  # calimero-client-py — Agent Instructions
2
2
 
3
- You are helping a developer use the **Calimero Python client** (`calimero-client-py`) to
4
- interact with a Calimero node from Python — automation scripts, backend services, or CLI tools.
3
+ You are helping a developer use the **Calimero Python client** (`calimero-client-py`) to interact
4
+ with a Calimero node from Python — automation scripts, backend services, or CLI tools.
5
+
6
+ > **NOT this skill** if the developer is building the application logic that runs on the node — that
7
+ > is `calimero-rust-sdk` (Rust/WASM) or `calimero-sdk-js` (TypeScript/WASM). This skill is for
8
+ > Python code that _calls_ the node from outside.
5
9
 
6
10
  ## What it is
7
11
 
8
12
  `calimero-client-py` is a Python package built with PyO3 (Rust bindings). It provides:
9
- - Full async API for managing contexts, applications, identities, blobs
13
+
14
+ - Full API for managing contexts, applications, identities, blobs, namespaces, and groups
10
15
  - Automatic JWT token caching and refresh (tokens stored in `~/.merobox/auth_cache/`)
11
16
  - A CLI for common node operations
12
17
 
18
+ > **All methods are synchronous** — the Tokio runtime is managed internally by the Rust bindings. Do
19
+ > NOT use `await` with any client methods.
20
+
13
21
  ## Install
14
22
 
15
23
  ```bash
@@ -17,6 +25,7 @@ pip install calimero-client-py
17
25
  ```
18
26
 
19
27
  **Build from source (requires Rust 1.70+ and maturin):**
28
+
20
29
  ```bash
21
30
  pip install maturin
22
31
  git clone https://github.com/calimero-network/calimero-client-py
@@ -30,24 +39,31 @@ maturin develop --release
30
39
  import asyncio
31
40
  from calimero_client_py import create_connection, create_client
32
41
 
33
- async def main():
42
+ def main():
34
43
  connection = create_connection(
35
44
  api_url="http://localhost:2428",
36
45
  node_name="my-local-node" # must be stable — see rules/
37
46
  )
38
47
  client = create_client(connection)
39
- contexts = await client.list_contexts()
48
+ contexts = client.list_contexts() # no await — methods are synchronous
40
49
  print(contexts)
41
50
 
42
- asyncio.run(main())
51
+ main()
43
52
  ```
44
53
 
45
54
  ## Critical: `node_name` must be stable
46
55
 
47
- `node_name` is used to derive the token cache filename. If it changes between runs,
48
- the client loses its stored tokens and will need to re-authenticate.
56
+ `node_name` is used to derive the token cache filename. If it changes between runs, the client loses
57
+ its stored tokens and will need to re-authenticate.
58
+
59
+ ## Related skills
60
+
61
+ - **`calimero-core`** — context/app model and JSON-RPC protocol that the Python client wraps
62
+ - **`calimero-merod`** — `merod` daemon setup if the developer is also running the node
63
+ - **`calimero-meroctl`** — `meroctl` CLI reference for quick manual operations alongside Python
64
+ scripts
49
65
 
50
66
  ## References
51
67
 
52
- See `references/` for the full API, authentication flow, and CLI commands.
53
- See `rules/` for the `node_name` stability requirement and async usage.
68
+ See `references/` for the full API, authentication flow, and CLI commands. See `rules/` for the
69
+ `node_name` stability requirement and sync API usage.
@@ -1,5 +1,8 @@
1
1
  # Python Client API Reference
2
2
 
3
+ > All methods are **synchronous** — no `await` needed. The async Tokio runtime is managed internally
4
+ > by the Rust bindings.
5
+
3
6
  ## Setup
4
7
 
5
8
  ```python
@@ -16,52 +19,55 @@ client = create_client(connection)
16
19
 
17
20
  ```python
18
21
  # List all contexts
19
- contexts = await client.list_contexts()
22
+ contexts = client.list_contexts()
20
23
 
21
24
  # Get a specific context
22
- context = await client.get_context(context_id="abc123")
25
+ context = client.get_context(context_id="abc123")
23
26
 
24
27
  # Create a context (instance of an installed app)
25
- context = await client.create_context(
28
+ context = client.create_context(
26
29
  application_id="app-id",
27
30
  protocol="near", # "near" | "ethereum" | "icp" | "starknet"
28
31
  params=None # optional JSON string
29
32
  )
30
33
 
31
34
  # Delete a context
32
- await client.delete_context(context_id="abc123")
35
+ client.delete_context(context_id="abc123")
36
+
37
+ # Join a context (by context ID)
38
+ client.join_context(context_id="abc123")
33
39
 
34
40
  # Sync a context with peers
35
- await client.sync_context(context_id="abc123")
41
+ client.sync_context(context_id="abc123")
36
42
 
37
43
  # Sync all contexts
38
- await client.sync_all_contexts()
44
+ client.sync_all_contexts()
39
45
  ```
40
46
 
41
47
  ## Application management
42
48
 
43
49
  ```python
44
50
  # List installed apps
45
- apps = await client.list_applications()
51
+ apps = client.list_applications()
46
52
 
47
53
  # Get a specific app
48
- app = await client.get_application(app_id="app-id")
54
+ app = client.get_application(app_id="app-id")
49
55
 
50
56
  # Install from URL (registry or direct)
51
- result = await client.install_application(
57
+ result = client.install_application(
52
58
  url="https://registry.calimero.network/myapp/1.0.0",
53
59
  hash=None, # optional content hash for verification
54
60
  metadata=None # optional bytes
55
61
  )
56
62
 
57
63
  # Install from local path (dev mode)
58
- result = await client.install_dev_application(
64
+ result = client.install_dev_application(
59
65
  path="./target/wasm32-unknown-unknown/release/myapp.wasm",
60
66
  metadata=None
61
67
  )
62
68
 
63
69
  # Uninstall
64
- await client.uninstall_application(app_id="app-id")
70
+ client.uninstall_application(app_id="app-id")
65
71
  ```
66
72
 
67
73
  ## Calling app methods
@@ -70,7 +76,7 @@ await client.uninstall_application(app_id="app-id")
70
76
  import json
71
77
 
72
78
  # Execute a method (mutation or view)
73
- result = await client.execute_function(
79
+ result = client.execute_function(
74
80
  context_id="ctx-id",
75
81
  method="set",
76
82
  args=json.dumps({"key": "hello", "value": "world"}),
@@ -78,59 +84,51 @@ result = await client.execute_function(
78
84
  )
79
85
  ```
80
86
 
81
- ## Context membership
82
-
83
- ```python
84
- # Invite a member
85
- invitation = await client.invite_to_context(
86
- context_id="ctx-id",
87
- inviter_id="inviter-public-key",
88
- invitee_id="invitee-public-key"
89
- )
90
-
91
- # Join a context (on the invitee's node)
92
- await client.join_context(
93
- context_id="ctx-id",
94
- invitee_id="invitee-public-key",
95
- invitation_payload=invitation["payload"]
96
- )
97
- ```
98
-
99
87
  ## Identity management
100
88
 
101
89
  ```python
102
90
  # Generate a new context identity
103
- identity = await client.generate_context_identity()
91
+ identity = client.generate_context_identity()
104
92
  # Returns: { "public_key": "...", "private_key": "..." }
105
93
 
106
94
  # List identities for a context
107
- identities = await client.get_context_identities(context_id="ctx-id")
95
+ identities = client.get_context_identities(context_id="ctx-id")
108
96
 
109
97
  # Get client keys for a context
110
- keys = await client.get_context_client_keys(context_id="ctx-id")
98
+ keys = client.get_context_client_keys(context_id="ctx-id")
111
99
  ```
112
100
 
113
101
  ## Blob management
114
102
 
115
103
  ```python
116
- blobs = await client.list_blobs()
117
- info = await client.get_blob_info(blob_id="blob-id")
118
- await client.delete_blob(blob_id="blob-id")
104
+ blobs = client.list_blobs()
105
+ info = client.get_blob_info(blob_id="blob-id")
106
+ client.delete_blob(blob_id="blob-id")
107
+
108
+ # Upload bytes
109
+ result = client.upload_blob(data=b"hello", context_id=None)
110
+
111
+ # Download bytes
112
+ data = client.download_blob(blob_id="blob-id", context_id=None)
119
113
  ```
120
114
 
121
115
  ## Alias management
122
116
 
123
117
  ```python
124
118
  # Create aliases for easier reference
125
- await client.create_context_alias(alias="my-context", context_id="ctx-id")
126
- await client.create_application_alias(alias="my-app", application_id="app-id")
127
- await client.create_context_identity_alias(
119
+ client.create_context_alias(alias="my-context", context_id="ctx-id")
120
+ client.create_application_alias(alias="my-app", application_id="app-id")
121
+ client.create_context_identity_alias(
128
122
  context_id="ctx-id", alias="alice", public_key="pubkey"
129
123
  )
130
124
 
131
125
  # Delete aliases
132
- await client.delete_context_alias(alias="my-context")
133
- await client.delete_context_identity_alias(alias="alice", context_id="ctx-id")
126
+ client.delete_context_alias(alias="my-context")
127
+ client.delete_context_identity_alias(alias="alice", context_id="ctx-id")
128
+
129
+ # Lookup / resolve aliases
130
+ client.lookup_context_alias(alias="my-context")
131
+ client.resolve_context_alias(alias="my-context")
134
132
  ```
135
133
 
136
134
  ## Utility
@@ -140,8 +138,8 @@ await client.delete_context_identity_alias(alias="alice", context_id="ctx-id")
140
138
  url = client.get_api_url()
141
139
 
142
140
  # Get connected peers count
143
- peers = await client.get_peers_count()
141
+ peers = client.get_peers_count()
144
142
 
145
143
  # Get context storage info
146
- storage = await client.get_context_storage(context_id="ctx-id")
144
+ storage = client.get_context_storage(context_id="ctx-id")
147
145
  ```
@@ -1,11 +1,11 @@
1
1
  # Authentication & Token Caching
2
2
 
3
- The Python client automatically manages JWT tokens — loading from cache, using them in
4
- requests, and refreshing on 401.
3
+ The Python client automatically manages JWT tokens — loading from cache, using them in requests, and
4
+ refreshing on 401.
5
5
 
6
6
  ## Token cache location
7
7
 
8
- ```
8
+ ```text
9
9
  ~/.merobox/auth_cache/{sanitized_node_name}-{hash}.json
10
10
  ```
11
11
 
@@ -18,8 +18,8 @@ path = get_token_cache_path("my-node")
18
18
 
19
19
  ## First-time authentication
20
20
 
21
- On the first run there are no cached tokens. You need to complete an auth flow (login via
22
- the admin dashboard or meroctl) and then seed the cache manually:
21
+ On the first run there are no cached tokens. You need to complete an auth flow (login via the admin
22
+ dashboard or meroctl) and then seed the cache manually:
23
23
 
24
24
  ```python
25
25
  import json
@@ -54,8 +54,8 @@ client = create_client(connection)
54
54
 
55
55
  ## Token refresh
56
56
 
57
- The client automatically retries with a refreshed token on `401 Unauthorized`.
58
- Refreshed tokens are written back to the cache file automatically.
57
+ The client automatically retries with a refreshed token on `401 Unauthorized`. Refreshed tokens are
58
+ written back to the cache file automatically.
59
59
 
60
60
  ## CI / automated environments
61
61
 
@@ -1,52 +1,48 @@
1
- # Rule: All client methods are asyncmust be awaited
1
+ # Rule: All client methods are synchronousdo NOT use await
2
2
 
3
- All `client.*` methods return coroutines. They must be called with `await` inside an
4
- `async` function, and the event loop must be running.
3
+ Despite being built on an async Rust/Tokio runtime internally, all `client.*` methods are
4
+ **synchronous** from Python's perspective. Do NOT use `async`/`await`.
5
5
 
6
6
  ## WRONG:
7
7
 
8
8
  ```python
9
- # ✗ Missing await — returns a coroutine object, not the result
10
- contexts = client.list_contexts()
11
- print(contexts) # prints <coroutine object ...>
12
-
13
- # ✗ Calling outside async context
14
- def main():
15
- client.list_contexts() # won't run
9
+ # ✗ Adding await — methods are not coroutines
10
+ async def main():
11
+ contexts = await client.list_contexts() # TypeError: object is not awaitable
16
12
  ```
17
13
 
18
14
  ## CORRECT:
19
15
 
20
16
  ```python
21
- # ✓ Always await
22
- async def main():
23
- contexts = await client.list_contexts()
17
+ # ✓ Call methods directly — no await needed
18
+ def main():
19
+ conn = create_connection(api_url="http://localhost:2428", node_name="dev")
20
+ client = create_client(conn)
21
+ contexts = client.list_contexts() # synchronous, returns result directly
24
22
  print(contexts)
25
23
 
26
- asyncio.run(main())
24
+ main()
27
25
  ```
28
26
 
29
27
  ## In scripts
30
28
 
31
29
  ```python
32
- import asyncio
33
30
  from calimero_client_py import create_connection, create_client
34
-
35
- async def main():
36
- conn = create_connection(api_url="http://localhost:2428", node_name="dev")
37
- client = create_client(conn)
38
- result = await client.execute_function(
39
- context_id="ctx-id",
40
- method="get",
41
- args='{"key":"hello"}',
42
- executor_public_key="pubkey"
43
- )
44
- print(result)
45
-
46
- asyncio.run(main())
31
+ import json
32
+
33
+ conn = create_connection(api_url="http://localhost:2428", node_name="dev")
34
+ client = create_client(conn)
35
+
36
+ result = client.execute_function(
37
+ context_id="ctx-id",
38
+ method="get",
39
+ args=json.dumps({"key": "hello"}),
40
+ executor_public_key="pubkey"
41
+ )
42
+ print(result)
47
43
  ```
48
44
 
49
- ## Note: `create_connection` and `create_client` are synchronous
45
+ ## Note
50
46
 
51
- Only the API methods on the `client` object are async. The setup functions are regular
52
- synchronous calls.
47
+ The Tokio async runtime is embedded in the Rust bindings and blocks internally. From Python's view,
48
+ every call is a normal blocking function call.
@@ -1,7 +1,7 @@
1
1
  # Rule: node_name must be stable and unique per node
2
2
 
3
- `node_name` is used to derive the token cache filename. Changing it between runs
4
- means the client can't find the cached tokens and will fail to authenticate.
3
+ `node_name` is used to derive the token cache filename. Changing it between runs means the client
4
+ can't find the cached tokens and will fail to authenticate.
5
5
 
6
6
  ## WRONG — dynamic or None:
7
7
 
@@ -29,11 +29,11 @@ connection = create_connection(api_url=url, node_name=node_name)
29
29
 
30
30
  ## Why
31
31
 
32
- Token cache files are named `{sanitized_node_name}-{hash}.json` in `~/.merobox/auth_cache/`.
33
- If `node_name` varies, a new cache file is created each run and existing tokens are never
34
- reused — forcing re-authentication on every invocation.
32
+ Token cache files are named `{sanitized_node_name}-{hash}.json` in `~/.merobox/auth_cache/`. If
33
+ `node_name` varies, a new cache file is created each run and existing tokens are never reused —
34
+ forcing re-authentication on every invocation.
35
35
 
36
36
  ## Also: unique per remote node
37
37
 
38
- If you connect to multiple nodes in the same script, use a different `node_name` for each.
39
- Sharing a `node_name` across two different nodes will mix up their token caches.
38
+ If you connect to multiple nodes in the same script, use a different `node_name` for each. Sharing a
39
+ `node_name` across two different nodes will mix up their token caches.