@calimero-network/agent-skills 0.1.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 (48) hide show
  1. package/README.md +238 -0
  2. package/SKILL.md +43 -0
  3. package/package.json +43 -0
  4. package/scripts/install.js +116 -0
  5. package/scripts/test.js +67 -0
  6. package/skills/calimero-abi-codegen/SKILL.md +61 -0
  7. package/skills/calimero-abi-codegen/references/abi-format.md +105 -0
  8. package/skills/calimero-abi-codegen/references/generated-output.md +89 -0
  9. package/skills/calimero-abi-codegen/references/programmatic-api.md +53 -0
  10. package/skills/calimero-abi-codegen/rules/schema-version.md +36 -0
  11. package/skills/calimero-abi-codegen/rules/unique-names.md +52 -0
  12. package/skills/calimero-client-js/SKILL.md +44 -0
  13. package/skills/calimero-client-js/references/auth.md +58 -0
  14. package/skills/calimero-client-js/references/rpc-calls.md +75 -0
  15. package/skills/calimero-client-js/references/sso.md +67 -0
  16. package/skills/calimero-client-js/references/websocket-events.md +66 -0
  17. package/skills/calimero-client-js/rules/camelcase-api.md +37 -0
  18. package/skills/calimero-client-js/rules/token-refresh.md +41 -0
  19. package/skills/calimero-client-py/SKILL.md +53 -0
  20. package/skills/calimero-client-py/references/api.md +147 -0
  21. package/skills/calimero-client-py/references/auth.md +72 -0
  22. package/skills/calimero-client-py/rules/async-usage.md +52 -0
  23. package/skills/calimero-client-py/rules/stable-node-name.md +39 -0
  24. package/skills/calimero-desktop/SKILL.md +47 -0
  25. package/skills/calimero-desktop/references/sso-integration.md +92 -0
  26. package/skills/calimero-desktop/rules/sso-fallback.md +33 -0
  27. package/skills/calimero-merobox/SKILL.md +55 -0
  28. package/skills/calimero-merobox/references/ci-integration.md +52 -0
  29. package/skills/calimero-merobox/references/workflow-files.md +70 -0
  30. package/skills/calimero-merobox/rules/docker-required.md +26 -0
  31. package/skills/calimero-node/SKILL.md +34 -0
  32. package/skills/calimero-node/references/context-lifecycle.md +59 -0
  33. package/skills/calimero-node/references/meroctl-commands.md +80 -0
  34. package/skills/calimero-node/rules/app-vs-context.md +33 -0
  35. package/skills/calimero-registry/SKILL.md +51 -0
  36. package/skills/calimero-registry/references/bundle-and-push.md +69 -0
  37. package/skills/calimero-registry/references/manifest-format.md +63 -0
  38. package/skills/calimero-registry/references/mero-sign.md +75 -0
  39. package/skills/calimero-registry/rules/key-security.md +41 -0
  40. package/skills/calimero-registry/rules/sign-before-pack.md +25 -0
  41. package/skills/calimero-rust-sdk/SKILL.md +73 -0
  42. package/skills/calimero-rust-sdk/references/events.md +53 -0
  43. package/skills/calimero-rust-sdk/references/examples.md +70 -0
  44. package/skills/calimero-rust-sdk/references/private-storage.md +56 -0
  45. package/skills/calimero-rust-sdk/references/state-collections.md +50 -0
  46. package/skills/calimero-rust-sdk/rules/app-macro-placement.md +34 -0
  47. package/skills/calimero-rust-sdk/rules/no-std-collections.md +27 -0
  48. package/skills/calimero-rust-sdk/rules/wasm-constraints.md +35 -0
@@ -0,0 +1,89 @@
1
+ # Generated Output
2
+
3
+ Running `calimero-abi-codegen -i abi.json -o src/generated --client-name KvStoreClient` produces:
4
+
5
+ ```
6
+ src/generated/
7
+ ├── types.ts
8
+ └── KvStoreClient.ts
9
+ ```
10
+
11
+ ## types.ts
12
+
13
+ Contains all TypeScript types matching the ABI types, plus event payload types:
14
+
15
+ ```typescript
16
+ // Generated from abi.json — do not edit manually
17
+
18
+ export interface Entry {
19
+ key: string;
20
+ value: string;
21
+ }
22
+
23
+ // Event union type
24
+ export type AppEvent =
25
+ | { type: 'ItemSet'; payload: Entry }
26
+ | { type: 'ItemRemoved'; payload: { key: string } };
27
+ ```
28
+
29
+ ## KvStoreClient.ts
30
+
31
+ Contains the typed client class with a method per ABI function:
32
+
33
+ ```typescript
34
+ // Generated from abi.json — do not edit manually
35
+ import { CalimeroApp, Context } from '@calimero-network/calimero-client';
36
+ import type { Entry } from './types';
37
+
38
+ export class KvStoreClient {
39
+ constructor(private app: CalimeroApp, private context: Context) {}
40
+
41
+ async set(key: string, value: string): Promise<void> {
42
+ await this.app.mutate(this.context, 'set', { key, value });
43
+ }
44
+
45
+ async get(key: string): Promise<string | null> {
46
+ return this.app.query(this.context, 'get', { key });
47
+ }
48
+
49
+ async entries(): Promise<Entry[]> {
50
+ return this.app.query(this.context, 'entries', {});
51
+ }
52
+ }
53
+ ```
54
+
55
+ ## Using the generated client
56
+
57
+ ```typescript
58
+ import { CalimeroApp, Context, getJWTObject, getStorageAppEndpointKey } from '@calimero-network/calimero-client';
59
+ import { KvStoreClient } from './generated/KvStoreClient';
60
+
61
+ const app = new CalimeroApp(getStorageAppEndpointKey(), getJWTObject()?.access_token);
62
+ const context = new Context('your-context-id');
63
+ const client = new KvStoreClient(app, context);
64
+
65
+ // Fully typed — IDE autocomplete works
66
+ await client.set('hello', 'world');
67
+ const value = await client.get('hello'); // string | null
68
+ const all = await client.entries(); // Entry[]
69
+ ```
70
+
71
+ ## Regenerating after app changes
72
+
73
+ When you update your Rust app and the ABI changes, re-run codegen:
74
+
75
+ ```bash
76
+ # After rebuilding your WASM app (which regenerates abi.json):
77
+ npx calimero-abi-codegen -i abi.json -o src/generated --client-name KvStoreClient
78
+ ```
79
+
80
+ Add this to your build script so it stays in sync automatically:
81
+
82
+ ```json
83
+ {
84
+ "scripts": {
85
+ "codegen": "calimero-abi-codegen -i abi.json -o src/generated --client-name KvStoreClient",
86
+ "build": "npm run codegen && tsc"
87
+ }
88
+ }
89
+ ```
@@ -0,0 +1,53 @@
1
+ # Programmatic API
2
+
3
+ Use the library directly in Node.js scripts for custom code generation pipelines.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @calimero-network/abi-codegen
9
+ ```
10
+
11
+ ## Parse an ABI
12
+
13
+ ```typescript
14
+ import { loadAbiManifestFromFile, parseAbiManifest } from '@calimero-network/abi-codegen/parse';
15
+
16
+ // From file
17
+ const manifest = loadAbiManifestFromFile('./abi.json');
18
+
19
+ // From already-parsed JSON
20
+ const json = JSON.parse(fs.readFileSync('./abi.json', 'utf8'));
21
+ const manifest = parseAbiManifest(json);
22
+ // Throws if invalid — schema_version wrong, dangling refs, duplicate names, etc.
23
+ ```
24
+
25
+ ## Generate types and client
26
+
27
+ ```typescript
28
+ import { generateTypes } from '@calimero-network/abi-codegen/generate/types';
29
+ import { generateClient } from '@calimero-network/abi-codegen/generate/client';
30
+ import { deriveClientNameFromPath } from '@calimero-network/abi-codegen';
31
+
32
+ const manifest = loadAbiManifestFromFile('./abi.json');
33
+
34
+ // Generate types.ts content
35
+ const typesContent = generateTypes(manifest);
36
+
37
+ // Generate client content
38
+ const clientName = deriveClientNameFromPath('kv_store.wasm'); // → "KvStore"
39
+ const clientContent = generateClient(manifest, clientName);
40
+
41
+ // Write to disk
42
+ fs.writeFileSync('src/generated/types.ts', typesContent);
43
+ fs.writeFileSync(`src/generated/${clientName}.ts`, clientContent);
44
+ ```
45
+
46
+ ## Export paths
47
+
48
+ ```typescript
49
+ import { deriveClientNameFromPath } from '@calimero-network/abi-codegen';
50
+ import { loadAbiManifestFromFile, parseAbiManifest } from '@calimero-network/abi-codegen/parse';
51
+ import { generateTypes } from '@calimero-network/abi-codegen/generate/types';
52
+ import { generateClient } from '@calimero-network/abi-codegen/generate/client';
53
+ ```
@@ -0,0 +1,36 @@
1
+ # Rule: ABI manifest must have schema_version "wasm-abi/1"
2
+
3
+ The codegen tool rejects any ABI file that doesn't have the exact string `"wasm-abi/1"` as its `schema_version`.
4
+
5
+ ## WRONG:
6
+
7
+ ```json
8
+ {
9
+ "schema_version": "1",
10
+ "types": {}, "methods": [], "events": []
11
+ }
12
+ ```
13
+
14
+ ```json
15
+ {
16
+ "types": {}, "methods": [], "events": []
17
+ }
18
+ ```
19
+
20
+ ## CORRECT:
21
+
22
+ ```json
23
+ {
24
+ "schema_version": "wasm-abi/1",
25
+ "types": {}, "methods": [], "events": []
26
+ }
27
+ ```
28
+
29
+ ## Validate before generating
30
+
31
+ Use `--validate` to check an ABI file without generating code — useful as a CI step:
32
+
33
+ ```bash
34
+ npx calimero-abi-codegen --validate -i abi.json
35
+ # exits 0 if valid, non-zero if invalid
36
+ ```
@@ -0,0 +1,52 @@
1
+ # Rule: Method, event, and type names must all be unique
2
+
3
+ The ABI validator rejects manifests where names clash — across methods, events, and types.
4
+
5
+ ## WRONG — duplicate method names:
6
+
7
+ ```json
8
+ {
9
+ "methods": [
10
+ { "name": "get", "params": [...] },
11
+ { "name": "get", "params": [...] }
12
+ ]
13
+ }
14
+ ```
15
+
16
+ ## WRONG — type name same as method name:
17
+
18
+ ```json
19
+ {
20
+ "types": {
21
+ "set": { "kind": "record", "fields": [] }
22
+ },
23
+ "methods": [
24
+ { "name": "set", "params": [] }
25
+ ]
26
+ }
27
+ ```
28
+
29
+ ## WRONG — map key is not string:
30
+
31
+ ```json
32
+ {
33
+ "kind": "map",
34
+ "key": { "kind": "u64" },
35
+ "value": { "kind": "string" }
36
+ }
37
+ ```
38
+
39
+ Map keys **must** be `{ "kind": "string" }`.
40
+
41
+ ## WRONG — dangling $ref:
42
+
43
+ ```json
44
+ {
45
+ "types": {},
46
+ "methods": [
47
+ { "name": "get", "returns": { "$ref": "NonExistentType" } }
48
+ ]
49
+ }
50
+ ```
51
+
52
+ Every `$ref` must point to a name defined in the `types` object.
@@ -0,0 +1,44 @@
1
+ # calimero-client-js — Agent Instructions
2
+
3
+ You are helping a developer connect a **browser or Node.js frontend** to a Calimero node using `@calimero-network/calimero-client` or `@calimero-network/mero-js`.
4
+
5
+ ## Package versions
6
+
7
+ | Package | Version | Notes |
8
+ | --- | --- | --- |
9
+ | `@calimero-network/calimero-client` | latest | Stable client for browser/Node — auth, RPC, WebSocket |
10
+ | `@calimero-network/mero-js` | `>=2.0.0-beta.1` | v2 API — all request fields are **camelCase** |
11
+
12
+ ## Critical: mero-js v2 uses camelCase
13
+
14
+ v2 changed all request field names from `snake_case` to `camelCase`.
15
+
16
+ ```typescript
17
+ // WRONG (v1 / old):
18
+ { context_id: '...', context_identity: '...' }
19
+
20
+ // CORRECT (v2):
21
+ { contextId: '...', contextIdentity: '...' }
22
+ ```
23
+
24
+ This applies to `GenerateClientKeyRequest` and all other request types.
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ pnpm add @calimero-network/calimero-client
30
+ # or
31
+ pnpm add @calimero-network/mero-js
32
+ ```
33
+
34
+ ## Core workflow
35
+
36
+ 1. Read auth tokens (from URL hash if opened by Desktop, otherwise prompt login)
37
+ 2. Initialize client with `node_url` + tokens
38
+ 3. Call app methods via JSON-RPC
39
+ 4. Subscribe to events via WebSocket
40
+
41
+ ## References
42
+
43
+ See `references/` for auth flow, RPC calls, WebSocket events, and SSO.
44
+ See `rules/` for camelCase API and token refresh requirements.
@@ -0,0 +1,58 @@
1
+ # Authentication
2
+
3
+ ## Login flow
4
+
5
+ ```typescript
6
+ import { setupWalletSelector } from '@near-wallet-selector/core';
7
+ import { ClientLogin } from '@calimero-network/calimero-client';
8
+
9
+ const client = new ClientLogin({ nodeUrl: 'http://localhost:2428' });
10
+
11
+ // 1. Generate a client key for this device
12
+ const { contextId, contextIdentity } = await client.generateClientKey({
13
+ contextId: 'your-context-id',
14
+ contextIdentity: 'your-identity',
15
+ });
16
+
17
+ // 2. Login — returns access_token + refresh_token
18
+ const tokens = await client.login({
19
+ contextId,
20
+ contextIdentity,
21
+ });
22
+
23
+ // 3. Store tokens
24
+ localStorage.setItem('access_token', tokens.access_token);
25
+ localStorage.setItem('refresh_token', tokens.refresh_token);
26
+ ```
27
+
28
+ ## Token storage
29
+
30
+ Tokens should be stored in `localStorage` or `sessionStorage`. The client library reads
31
+ them automatically if you use the provided storage helpers:
32
+
33
+ ```typescript
34
+ import { getStorageAppEndpointKey, getJWTObject } from '@calimero-network/calimero-client';
35
+
36
+ const nodeUrl = getStorageAppEndpointKey();
37
+ const jwt = getJWTObject();
38
+ // jwt.access_token, jwt.refresh_token
39
+ ```
40
+
41
+ ## Checking auth status
42
+
43
+ ```typescript
44
+ import { getJWTObject } from '@calimero-network/calimero-client';
45
+
46
+ function isLoggedIn(): boolean {
47
+ const jwt = getJWTObject();
48
+ return !!(jwt?.access_token);
49
+ }
50
+ ```
51
+
52
+ ## Logout
53
+
54
+ ```typescript
55
+ localStorage.removeItem('calimero_jwt');
56
+ localStorage.removeItem('calimero_node_url');
57
+ // redirect to login
58
+ ```
@@ -0,0 +1,75 @@
1
+ # RPC Calls
2
+
3
+ Calling application methods on a Calimero node.
4
+
5
+ ## Setup
6
+
7
+ ```typescript
8
+ import {
9
+ JsonRpcClient,
10
+ getStorageAppEndpointKey,
11
+ getJWTObject,
12
+ } from '@calimero-network/calimero-client';
13
+
14
+ const nodeUrl = getStorageAppEndpointKey() ?? 'http://localhost:2428';
15
+ const jwt = getJWTObject();
16
+
17
+ const client = new JsonRpcClient(nodeUrl, '/jsonrpc', jwt?.access_token);
18
+ ```
19
+
20
+ ## Calling a mutation (changes state)
21
+
22
+ ```typescript
23
+ const response = await client.mutate<{ key: string; value: string }, void>({
24
+ contextId: 'your-context-id',
25
+ method: 'set',
26
+ argsJson: { key: 'hello', value: 'world' },
27
+ executorPublicKey: jwt?.executor_public_key ?? '',
28
+ });
29
+
30
+ if (response.error) {
31
+ console.error(response.error);
32
+ }
33
+ ```
34
+
35
+ ## Calling a view (read-only)
36
+
37
+ ```typescript
38
+ const response = await client.query<{ key: string }, string | null>({
39
+ contextId: 'your-context-id',
40
+ method: 'get',
41
+ argsJson: { key: 'hello' },
42
+ executorPublicKey: jwt?.executor_public_key ?? '',
43
+ });
44
+
45
+ console.log(response.result?.output); // "world"
46
+ ```
47
+
48
+ ## Response shape
49
+
50
+ ```typescript
51
+ interface RpcResponse<T> {
52
+ result?: {
53
+ output: T;
54
+ };
55
+ error?: {
56
+ code: number;
57
+ message: string;
58
+ };
59
+ }
60
+ ```
61
+
62
+ ## Error handling
63
+
64
+ ```typescript
65
+ const response = await client.query({ ... });
66
+
67
+ if (response.error) {
68
+ if (response.error.code === 401) {
69
+ // token expired — refresh and retry
70
+ await refreshTokens();
71
+ return callAgain();
72
+ }
73
+ throw new Error(response.error.message);
74
+ }
75
+ ```
@@ -0,0 +1,67 @@
1
+ # SSO — Calimero Desktop Integration
2
+
3
+ When a user opens your app from Calimero Desktop, auth tokens are passed via the URL hash.
4
+ Reading them lets users skip the manual login flow entirely.
5
+
6
+ ## Hash parameters
7
+
8
+ | Parameter | Description |
9
+ | --- | --- |
10
+ | `access_token` | JWT for authenticated node calls |
11
+ | `refresh_token` | Token to obtain a new access token |
12
+ | `node_url` | URL of the local node to connect to |
13
+ | `application_id` | The installed application's ID on the node |
14
+
15
+ ## Reading from hash
16
+
17
+ ```typescript
18
+ function readSSOParams(): {
19
+ accessToken: string | null;
20
+ refreshToken: string | null;
21
+ nodeUrl: string | null;
22
+ applicationId: string | null;
23
+ } {
24
+ const hash = window.location.hash.slice(1); // remove leading #
25
+ const params = new URLSearchParams(hash);
26
+ return {
27
+ accessToken: params.get('access_token'),
28
+ refreshToken: params.get('refresh_token'),
29
+ nodeUrl: params.get('node_url'),
30
+ applicationId: params.get('application_id'),
31
+ };
32
+ }
33
+ ```
34
+
35
+ ## Using SSO tokens on app startup
36
+
37
+ ```typescript
38
+ async function initApp() {
39
+ const sso = readSSOParams();
40
+
41
+ if (sso.accessToken && sso.nodeUrl) {
42
+ // Opened from Desktop — store tokens and skip login
43
+ localStorage.setItem('calimero_node_url', sso.nodeUrl);
44
+ localStorage.setItem('calimero_jwt', JSON.stringify({
45
+ access_token: sso.accessToken,
46
+ refresh_token: sso.refreshToken,
47
+ }));
48
+ // clear hash so tokens aren't in browser history
49
+ history.replaceState(null, '', window.location.pathname);
50
+ renderApp();
51
+ } else {
52
+ // No SSO — show manual login
53
+ renderLogin();
54
+ }
55
+ }
56
+ ```
57
+
58
+ ## calimero-client reads hash automatically
59
+
60
+ If you use `@calimero-network/calimero-client`'s built-in auth helpers, the library
61
+ reads `window.location.hash` automatically and stores the tokens. You don't need the
62
+ manual parsing above unless you're building a custom auth flow.
63
+
64
+ ## Important
65
+
66
+ Always fall back to manual login if the hash params are absent — the app must work
67
+ when opened directly in a browser, not only from Desktop.
@@ -0,0 +1,66 @@
1
+ # WebSocket Events
2
+
3
+ Subscribe to real-time events emitted by the application running on the node.
4
+
5
+ ## Connect and subscribe
6
+
7
+ ```typescript
8
+ import { WsSubscriptionsClient } from '@calimero-network/calimero-client';
9
+
10
+ const nodeUrl = getStorageAppEndpointKey() ?? 'http://localhost:2428';
11
+ const jwt = getJWTObject();
12
+
13
+ const ws = new WsSubscriptionsClient(nodeUrl, '/ws', jwt?.access_token);
14
+
15
+ ws.subscribe([contextId], (event) => {
16
+ console.log('Event received:', event);
17
+ // event.data contains the serialized app event payload
18
+ });
19
+ ```
20
+
21
+ ## Handling specific event types
22
+
23
+ ```typescript
24
+ interface AppEvent {
25
+ type: 'ItemAdded' | 'ItemRemoved' | 'MemberJoined';
26
+ key?: string;
27
+ value?: string;
28
+ identity?: string;
29
+ }
30
+
31
+ ws.subscribe([contextId], (event) => {
32
+ const appEvent = event.data as AppEvent;
33
+
34
+ switch (appEvent.type) {
35
+ case 'ItemAdded':
36
+ addItemToUI(appEvent.key!, appEvent.value!);
37
+ break;
38
+ case 'ItemRemoved':
39
+ removeItemFromUI(appEvent.key!);
40
+ break;
41
+ }
42
+ });
43
+ ```
44
+
45
+ ## Cleanup
46
+
47
+ ```typescript
48
+ // Unsubscribe when component unmounts
49
+ ws.unsubscribe([contextId]);
50
+
51
+ // Or close connection entirely
52
+ ws.close();
53
+ ```
54
+
55
+ ## React hook pattern
56
+
57
+ ```typescript
58
+ useEffect(() => {
59
+ const ws = new WsSubscriptionsClient(nodeUrl, '/ws', token);
60
+ ws.subscribe([contextId], handleEvent);
61
+
62
+ return () => {
63
+ ws.unsubscribe([contextId]);
64
+ };
65
+ }, [contextId, token]);
66
+ ```
@@ -0,0 +1,37 @@
1
+ # Rule: mero-js v2 uses camelCase — not snake_case
2
+
3
+ `@calimero-network/mero-js` v2 (`>=2.0.0-beta.1`) changed all request object field names from `snake_case` to `camelCase`.
4
+
5
+ ## WRONG (v1 / old code):
6
+
7
+ ```typescript
8
+ await client.generateClientKey({
9
+ context_id: contextId, // ✗
10
+ context_identity: identity, // ✗
11
+ });
12
+ ```
13
+
14
+ ## CORRECT (v2):
15
+
16
+ ```typescript
17
+ await client.generateClientKey({
18
+ contextId: contextId, // ✓
19
+ contextIdentity: identity, // ✓
20
+ });
21
+ ```
22
+
23
+ ## Affected types
24
+
25
+ - `GenerateClientKeyRequest` — `contextId`, `contextIdentity`
26
+ - All other request types in the v2 API
27
+
28
+ ## Why this matters
29
+
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.
32
+
33
+ ## How to detect if a codebase is on v1 or v2
34
+
35
+ Check `package.json`:
36
+ - `"@calimero-network/mero-js": "^2.0.0-beta.1"` or higher → v2, use camelCase
37
+ - `"@calimero-network/calimero-client": "..."` → original client, check its docs
@@ -0,0 +1,41 @@
1
+ # Rule: Always handle 401 with token refresh
2
+
3
+ Access tokens expire. Any RPC call or WebSocket connection can return `401 Unauthorized`. Never let this surface as an unhandled error.
4
+
5
+ ## Pattern
6
+
7
+ ```typescript
8
+ async function callWithRefresh<T>(callFn: () => Promise<T>): Promise<T> {
9
+ try {
10
+ return await callFn();
11
+ } catch (err: any) {
12
+ if (err?.code === 401 || err?.status === 401) {
13
+ await refreshAccessToken();
14
+ return callFn(); // retry once
15
+ }
16
+ throw err;
17
+ }
18
+ }
19
+
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';
25
+ return;
26
+ }
27
+
28
+ const newTokens = await client.refreshToken(jwt.refresh_token);
29
+ localStorage.setItem('calimero_jwt', JSON.stringify(newTokens));
30
+ }
31
+ ```
32
+
33
+ ## Why
34
+
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.
38
+
39
+ ## What to do if refresh also fails
40
+
41
+ Redirect to login. Do not retry the refresh indefinitely.
@@ -0,0 +1,53 @@
1
+ # calimero-client-py — Agent Instructions
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.
5
+
6
+ ## What it is
7
+
8
+ `calimero-client-py` is a Python package built with PyO3 (Rust bindings). It provides:
9
+ - Full async API for managing contexts, applications, identities, blobs
10
+ - Automatic JWT token caching and refresh (tokens stored in `~/.merobox/auth_cache/`)
11
+ - A CLI for common node operations
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pip install calimero-client-py
17
+ ```
18
+
19
+ **Build from source (requires Rust 1.70+ and maturin):**
20
+ ```bash
21
+ pip install maturin
22
+ git clone https://github.com/calimero-network/calimero-client-py
23
+ cd calimero-client-py
24
+ maturin develop --release
25
+ ```
26
+
27
+ ## Minimal working example
28
+
29
+ ```python
30
+ import asyncio
31
+ from calimero_client_py import create_connection, create_client
32
+
33
+ async def main():
34
+ connection = create_connection(
35
+ api_url="http://localhost:2428",
36
+ node_name="my-local-node" # must be stable — see rules/
37
+ )
38
+ client = create_client(connection)
39
+ contexts = await client.list_contexts()
40
+ print(contexts)
41
+
42
+ asyncio.run(main())
43
+ ```
44
+
45
+ ## Critical: `node_name` must be stable
46
+
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.
49
+
50
+ ## References
51
+
52
+ See `references/` for the full API, authentication flow, and CLI commands.
53
+ See `rules/` for the `node_name` stability requirement and async usage.