@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.
- package/README.md +137 -17
- package/SKILL.md +31 -23
- package/package.json +2 -2
- package/scripts/install.js +3 -3
- package/scripts/test.js +6 -15
- package/skills/calimero-abi-codegen/SKILL.md +121 -22
- package/skills/calimero-abi-codegen/references/abi-format.md +3 -5
- package/skills/calimero-abi-codegen/references/generated-output.md +12 -4
- package/skills/calimero-abi-codegen/rules/schema-version.md +11 -4
- package/skills/calimero-abi-codegen/rules/unique-names.md +2 -6
- package/skills/calimero-client-js/SKILL.md +127 -22
- package/skills/calimero-client-js/references/auth.md +18 -10
- package/skills/calimero-client-js/references/rpc-calls.md +15 -21
- package/skills/calimero-client-js/references/sso.md +9 -9
- package/skills/calimero-client-js/references/websocket-events.md +73 -59
- package/skills/calimero-client-js/rules/camelcase-api.md +10 -7
- package/skills/calimero-client-js/rules/token-refresh.md +59 -21
- package/skills/calimero-client-py/SKILL.md +26 -10
- package/skills/calimero-client-py/references/api.md +41 -43
- package/skills/calimero-client-py/references/auth.md +7 -7
- package/skills/calimero-client-py/rules/async-usage.md +27 -31
- package/skills/calimero-client-py/rules/stable-node-name.md +7 -7
- package/skills/calimero-core/SKILL.md +135 -0
- package/skills/calimero-core/references/architecture.md +101 -0
- package/skills/calimero-core/references/jsonrpc-protocol.md +192 -0
- package/skills/calimero-core/references/namespaces-groups.md +94 -0
- package/skills/calimero-core/references/storage-types.md +118 -0
- package/skills/calimero-core/references/websocket-events.md +142 -0
- package/skills/calimero-core/rules/context-is-not-app.md +35 -0
- package/skills/calimero-core/rules/crdt-types-only.md +55 -0
- package/skills/calimero-desktop/SKILL.md +25 -14
- package/skills/calimero-desktop/references/sso-integration.md +49 -22
- package/skills/calimero-desktop/rules/sso-fallback.md +3 -2
- package/skills/calimero-merobox/SKILL.md +255 -28
- package/skills/calimero-merobox/references/ci-integration.md +3 -2
- package/skills/calimero-merobox/references/workflow-files.md +7 -5
- package/skills/calimero-merobox/rules/docker-required.md +7 -6
- package/skills/calimero-meroctl/SKILL.md +68 -0
- package/skills/calimero-meroctl/references/commands.md +177 -0
- package/skills/calimero-meroctl/references/scripting.md +80 -0
- package/skills/calimero-meroctl/rules/call-view-flag.md +28 -0
- package/skills/calimero-meroctl/rules/register-node-once.md +34 -0
- package/skills/calimero-merod/SKILL.md +49 -0
- package/skills/calimero-merod/references/health-endpoints.md +90 -0
- package/skills/calimero-merod/references/init-flags.md +84 -0
- package/skills/calimero-merod/rules/init-before-run.md +40 -0
- package/skills/calimero-merod/rules/port-assignments.md +33 -0
- package/skills/calimero-node/SKILL.md +52 -35
- package/skills/calimero-node/references/context-lifecycle.md +34 -17
- package/skills/calimero-node/references/meroctl-commands.md +89 -99
- package/skills/calimero-node/rules/app-vs-context.md +4 -4
- package/skills/calimero-registry/SKILL.md +110 -31
- package/skills/calimero-registry/references/bundle-and-push.md +99 -34
- package/skills/calimero-registry/references/manifest-format.md +56 -35
- package/skills/calimero-registry/references/mero-sign.md +10 -9
- package/skills/calimero-registry/rules/key-security.md +3 -2
- package/skills/calimero-registry/rules/sign-before-pack.md +5 -5
- package/skills/calimero-rust-sdk/SKILL.md +154 -44
- package/skills/calimero-rust-sdk/references/blob-api.md +119 -0
- package/skills/calimero-rust-sdk/references/event-handlers.md +122 -0
- package/skills/calimero-rust-sdk/references/events.md +2 -1
- package/skills/calimero-rust-sdk/references/examples.md +81 -29
- package/skills/calimero-rust-sdk/references/migrations.md +123 -0
- package/skills/calimero-rust-sdk/references/nested-crdts.md +113 -0
- package/skills/calimero-rust-sdk/references/private-storage.md +76 -34
- package/skills/calimero-rust-sdk/references/state-collections.md +106 -21
- package/skills/calimero-rust-sdk/references/user-and-frozen-storage.md +169 -0
- package/skills/calimero-rust-sdk/rules/app-macro-placement.md +5 -2
- package/skills/calimero-rust-sdk/rules/no-std-collections.md +5 -2
- package/skills/calimero-rust-sdk/rules/state-derives.md +45 -0
- package/skills/calimero-rust-sdk/rules/wasm-constraints.md +12 -10
- package/skills/calimero-sdk-js/SKILL.md +145 -0
- package/skills/calimero-sdk-js/references/build-pipeline.md +98 -0
- package/skills/calimero-sdk-js/references/collections.md +132 -0
- package/skills/calimero-sdk-js/references/events.md +63 -0
- package/skills/calimero-sdk-js/rules/crdt-only-state.md +47 -0
- package/skills/calimero-sdk-js/rules/no-console-log.md +38 -0
- 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
|
|
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
|
-
|
|
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:
|
|
1
|
+
# Rule: rpcClient handles token refresh automatically — but not all 401s
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
15
|
+
return await fn();
|
|
11
16
|
} catch (err: any) {
|
|
12
|
-
if (err?.code === 401
|
|
13
|
-
await refreshAccessToken();
|
|
14
|
-
return
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
##
|
|
72
|
+
## Summary
|
|
40
73
|
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
48
|
+
contexts = client.list_contexts() # no await — methods are synchronous
|
|
40
49
|
print(contexts)
|
|
41
50
|
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
22
|
+
contexts = client.list_contexts()
|
|
20
23
|
|
|
21
24
|
# Get a specific context
|
|
22
|
-
context =
|
|
25
|
+
context = client.get_context(context_id="abc123")
|
|
23
26
|
|
|
24
27
|
# Create a context (instance of an installed app)
|
|
25
|
-
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
|
-
|
|
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
|
-
|
|
41
|
+
client.sync_context(context_id="abc123")
|
|
36
42
|
|
|
37
43
|
# Sync all contexts
|
|
38
|
-
|
|
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 =
|
|
51
|
+
apps = client.list_applications()
|
|
46
52
|
|
|
47
53
|
# Get a specific app
|
|
48
|
-
app =
|
|
54
|
+
app = client.get_application(app_id="app-id")
|
|
49
55
|
|
|
50
56
|
# Install from URL (registry or direct)
|
|
51
|
-
result =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
91
|
+
identity = client.generate_context_identity()
|
|
104
92
|
# Returns: { "public_key": "...", "private_key": "..." }
|
|
105
93
|
|
|
106
94
|
# List identities for a context
|
|
107
|
-
identities =
|
|
95
|
+
identities = client.get_context_identities(context_id="ctx-id")
|
|
108
96
|
|
|
109
97
|
# Get client keys for a context
|
|
110
|
-
keys =
|
|
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 =
|
|
117
|
-
info =
|
|
118
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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 =
|
|
141
|
+
peers = client.get_peers_count()
|
|
144
142
|
|
|
145
143
|
# Get context storage info
|
|
146
|
-
storage =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1
|
+
# Rule: All client methods are synchronous — do NOT use await
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
# ✗
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
# ✓
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
45
|
+
## Note
|
|
50
46
|
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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.
|