@calimero-network/agent-skills 0.1.1 → 0.3.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/SKILL.md CHANGED
@@ -6,8 +6,9 @@ This package provides AI agent skills for building on the Calimero Network stack
6
6
 
7
7
  | Skill | Install command | When to use |
8
8
  | --- | --- | --- |
9
- | `calimero-rust-sdk` | `npx @calimero-network/agent-skills calimero-rust-sdk` | Building Rust WASM applications |
10
- | `calimero-client-js` | `npx @calimero-network/agent-skills calimero-client-js` | Frontend / Node.js clients connecting to a node |
9
+ | `calimero-rust-sdk` | `npx @calimero-network/agent-skills calimero-rust-sdk` | Building Rust WASM applications that run on a node |
10
+ | `calimero-sdk-js` | `npx @calimero-network/agent-skills calimero-sdk-js` | Building TypeScript/JS WASM applications that run on a node |
11
+ | `calimero-client-js` | `npx @calimero-network/agent-skills calimero-client-js` | Browser/Node.js frontends connecting to a node (not building apps) |
11
12
  | `calimero-registry` | `npx @calimero-network/agent-skills calimero-registry` | Signing and publishing apps to the registry |
12
13
  | `calimero-desktop` | `npx @calimero-network/agent-skills calimero-desktop` | Integrating apps with Calimero Desktop SSO |
13
14
  | `calimero-node` | `npx @calimero-network/agent-skills calimero-node` | Node operators and meroctl scripting |
@@ -22,11 +23,15 @@ Skills should be loaded when the following are detected in the project:
22
23
  | Signal | Load skill |
23
24
  | --- | --- |
24
25
  | `calimero-sdk` in `Cargo.toml` | `calimero-rust-sdk` |
26
+ | `@calimero-network/calimero-sdk-js` in `package.json` | `calimero-sdk-js` |
27
+ | `@State` / `@Logic` decorators in TypeScript source | `calimero-sdk-js` |
28
+ | `calimero-sdk build` in any script | `calimero-sdk-js` |
25
29
  | `@calimero-network/calimero-client` in `package.json` | `calimero-client-js` |
26
30
  | `@calimero-network/mero-js` in `package.json` | `calimero-client-js` |
27
31
  | `mero-sign` in any script or Makefile | `calimero-registry` |
28
32
  | `calimero-registry` CLI usage | `calimero-registry` |
29
33
  | `access_token` read from `window.location.hash` | `calimero-desktop` |
34
+ | `readDesktopSSO` / `hash.get('access_token')` pattern in frontend code | `calimero-desktop` |
30
35
  | `merobox` in `package.json` or `requirements.txt` | `calimero-merobox` |
31
36
  | `calimero-client-py` in `requirements.txt` or `pyproject.toml` | `calimero-client-py` |
32
37
  | `calimero-abi-codegen` or `abi.json` in project | `calimero-abi-codegen` |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@calimero-network/agent-skills",
3
- "version": "0.1.1",
4
- "description": "AI agent skills for Calimero Network development — Rust SDK, JS client, registry publishing, desktop SSO, and more.",
3
+ "version": "0.3.0",
4
+ "description": "AI agent skills for Calimero Network development — Rust SDK, JS SDK, JS client, registry publishing, desktop SSO, and more.",
5
5
  "keywords": [
6
6
  "calimero",
7
7
  "agent-skills",
@@ -1,6 +1,12 @@
1
1
  # calimero-client-js — Agent Instructions
2
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`.
3
+ You are helping a developer connect a **browser or Node.js frontend** to a Calimero node
4
+ using `@calimero-network/calimero-client` or `@calimero-network/mero-js`.
5
+
6
+ > **NOT this skill** if the developer is building the application logic that *runs on the
7
+ > node* in TypeScript — that is `calimero-sdk-js` (`@calimero-network/calimero-sdk-js`).
8
+ > This skill is for the *client* side: auth, RPC calls, and WebSocket subscriptions from
9
+ > a browser or backend service.
4
10
 
5
11
  ## Package versions
6
12
 
@@ -9,6 +15,10 @@ You are helping a developer connect a **browser or Node.js frontend** to a Calim
9
15
  | `@calimero-network/calimero-client` | latest | Stable client for browser/Node — auth, RPC, WebSocket |
10
16
  | `@calimero-network/mero-js` | `>=2.0.0-beta.1` | v2 API — all request fields are **camelCase** |
11
17
 
18
+ **Which to use:** new projects should prefer `@calimero-network/mero-js` v2. If you are
19
+ maintaining an existing codebase that uses `calimero-client`, check for snake_case field
20
+ names before migrating — do not mix both packages in the same project.
21
+
12
22
  ## Critical: mero-js v2 uses camelCase
13
23
 
14
24
  v2 changed all request field names from `snake_case` to `camelCase`.
@@ -33,10 +43,53 @@ pnpm add @calimero-network/mero-js
33
43
 
34
44
  ## Core workflow
35
45
 
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
46
+ 1. On startup: read SSO tokens from URL hash (if opened by Desktop), otherwise check `localStorage` for existing session, otherwise show login
47
+ 2. Store tokens using the provided storage helpers (`setAppEndpointKey`, `setAccessToken`, etc.)
48
+ 3. Call app methods via the `rpcClient` singleton using `rpcClient.execute()`
49
+ 4. Subscribe to events via `WsSubscriptionsClient`
50
+
51
+ ## Minimal working example
52
+
53
+ ```typescript
54
+ import {
55
+ rpcClient,
56
+ getContextId,
57
+ getExecutorPublicKey,
58
+ getAppEndpointKey,
59
+ setAppEndpointKey,
60
+ setAccessToken,
61
+ setRefreshToken,
62
+ setContextAndIdentityFromJWT,
63
+ WsSubscriptionsClient,
64
+ } from '@calimero-network/calimero-client';
65
+
66
+ // 1. Store auth tokens (after SSO or login)
67
+ setAppEndpointKey('http://localhost:2428');
68
+ setAccessToken(accessToken);
69
+ setRefreshToken(refreshToken);
70
+ setContextAndIdentityFromJWT(accessToken); // extracts contextId + executorPublicKey
71
+
72
+ // 2. Call an app method
73
+ const response = await rpcClient.execute<{ key: string }, string | null>({
74
+ contextId: getContextId()!,
75
+ method: 'get',
76
+ argsJson: { key: 'hello' },
77
+ executorPublicKey: getExecutorPublicKey()!,
78
+ });
79
+ console.log(response.result?.output);
80
+
81
+ // 3. Subscribe to real-time events
82
+ const ws = new WsSubscriptionsClient(getAppEndpointKey()!, '/ws');
83
+ await ws.connect();
84
+ ws.subscribe([getContextId()!]);
85
+ ws.addCallback((event) => {
86
+ if (event.type === 'ExecutionEvent') {
87
+ for (const e of event.data.events) {
88
+ console.log(e.kind, e.data);
89
+ }
90
+ }
91
+ });
92
+ ```
40
93
 
41
94
  ## References
42
95
 
@@ -1,58 +1,163 @@
1
1
  # Authentication
2
2
 
3
- ## Login flow
3
+ ## Storage helpers
4
+
5
+ `@calimero-network/calimero-client` provides these storage functions (backed by `localStorage`):
4
6
 
5
7
  ```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);
8
+ import {
9
+ // Node URL
10
+ setAppEndpointKey,
11
+ getAppEndpointKey,
12
+ // JWT tokens
13
+ setAccessToken,
14
+ getAccessToken,
15
+ setRefreshToken,
16
+ getRefreshToken,
17
+ // Context and identity (extracted from JWT)
18
+ setContextId,
19
+ getContextId,
20
+ setExecutorPublicKey,
21
+ getExecutorPublicKey,
22
+ setContextAndIdentityFromJWT, // extracts + stores contextId + executorPublicKey from JWT
23
+ // App ID
24
+ setApplicationId,
25
+ getApplicationId,
26
+ // Auth endpoint (separate from node URL)
27
+ setAuthEndpointURL,
28
+ getAuthEndpointURL,
29
+ // Decoded JWT payload
30
+ getJWTObject, // returns { context_id, context_identity, exp, permissions, ... }
31
+ // Full auth config (throws if required fields are missing)
32
+ getAuthConfig, // returns { appEndpointKey, contextId, executorPublicKey, jwtToken }
33
+ // Logout (clears all tokens + reloads)
34
+ clientLogout,
35
+ } from '@calimero-network/calimero-client';
26
36
  ```
27
37
 
28
- ## Token storage
38
+ ---
39
+
40
+ ## SSO login (from Calimero Desktop — recommended path)
29
41
 
30
- Tokens should be stored in `localStorage` or `sessionStorage`. The client library reads
31
- them automatically if you use the provided storage helpers:
42
+ When the app is opened by Desktop, tokens arrive in the URL hash. Store them:
32
43
 
33
44
  ```typescript
34
- import { getStorageAppEndpointKey, getJWTObject } from '@calimero-network/calimero-client';
45
+ function initFromDesktopSSO(): boolean {
46
+ const hash = new URLSearchParams(window.location.hash.slice(1));
47
+ const accessToken = hash.get('access_token');
48
+ const refreshToken = hash.get('refresh_token');
49
+ const nodeUrl = hash.get('node_url');
50
+ const appId = hash.get('application_id');
35
51
 
36
- const nodeUrl = getStorageAppEndpointKey();
37
- const jwt = getJWTObject();
38
- // jwt.access_token, jwt.refresh_token
52
+ if (!accessToken || !nodeUrl) return false;
53
+
54
+ // Store everything
55
+ setAppEndpointKey(nodeUrl);
56
+ setAccessToken(accessToken);
57
+ if (refreshToken) setRefreshToken(refreshToken);
58
+ if (appId) setApplicationId(appId);
59
+
60
+ // Extract contextId and executorPublicKey from JWT claims
61
+ setContextAndIdentityFromJWT(accessToken);
62
+
63
+ // Remove tokens from URL bar (don't let them sit in browser history)
64
+ history.replaceState(null, '', window.location.pathname + window.location.search);
65
+
66
+ return true;
67
+ }
39
68
  ```
40
69
 
41
- ## Checking auth status
70
+ ---
71
+
72
+ ## Manual login (no Desktop)
42
73
 
43
74
  ```typescript
44
- import { getJWTObject } from '@calimero-network/calimero-client';
75
+ import { setAppEndpointKey, setAccessToken, setRefreshToken, setContextAndIdentityFromJWT } from '@calimero-network/calimero-client';
45
76
 
46
- function isLoggedIn(): boolean {
47
- const jwt = getJWTObject();
48
- return !!(jwt?.access_token);
77
+ async function login(nodeUrl: string, accessToken: string, refreshToken: string) {
78
+ setAppEndpointKey(nodeUrl);
79
+ setAccessToken(accessToken);
80
+ setRefreshToken(refreshToken);
81
+ setContextAndIdentityFromJWT(accessToken);
49
82
  }
50
83
  ```
51
84
 
85
+ ---
86
+
87
+ ## Checking if authenticated
88
+
89
+ ```typescript
90
+ import { getAuthConfig } from '@calimero-network/calimero-client';
91
+
92
+ function isAuthenticated(): boolean {
93
+ const config = getAuthConfig();
94
+ return config.error === null;
95
+ }
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Reading the JWT payload
101
+
102
+ ```typescript
103
+ import { getJWTObject } from '@calimero-network/calimero-client';
104
+
105
+ const jwt = getJWTObject();
106
+ // jwt.context_id — the context this token is scoped to
107
+ // jwt.context_identity — the identity (executor public key)
108
+ // jwt.exp — expiry timestamp (seconds)
109
+ // jwt.permissions — array of permission strings
110
+ ```
111
+
112
+ ---
113
+
52
114
  ## Logout
53
115
 
54
116
  ```typescript
55
- localStorage.removeItem('calimero_jwt');
56
- localStorage.removeItem('calimero_node_url');
57
- // redirect to login
117
+ import { clientLogout } from '@calimero-network/calimero-client';
118
+
119
+ // Clears all tokens from localStorage and reloads the page
120
+ clientLogout();
121
+ ```
122
+
123
+ ---
124
+
125
+ ## App startup pattern (SSO + fallback)
126
+
127
+ ```typescript
128
+ import {
129
+ setAppEndpointKey, setAccessToken, setRefreshToken,
130
+ setApplicationId, setContextAndIdentityFromJWT,
131
+ getAuthConfig,
132
+ } from '@calimero-network/calimero-client';
133
+
134
+ async function bootstrap() {
135
+ // Try SSO from Desktop hash
136
+ const hash = new URLSearchParams(window.location.hash.slice(1));
137
+ const accessToken = hash.get('access_token');
138
+ const nodeUrl = hash.get('node_url');
139
+
140
+ if (accessToken && nodeUrl) {
141
+ setAppEndpointKey(nodeUrl);
142
+ setAccessToken(accessToken);
143
+ const rt = hash.get('refresh_token');
144
+ if (rt) setRefreshToken(rt);
145
+ const appId = hash.get('application_id');
146
+ if (appId) setApplicationId(appId);
147
+ setContextAndIdentityFromJWT(accessToken);
148
+ history.replaceState(null, '', window.location.pathname);
149
+ renderApp();
150
+ return;
151
+ }
152
+
153
+ // Check if already authenticated from a previous session
154
+ const config = getAuthConfig();
155
+ if (config.error === null) {
156
+ renderApp();
157
+ return;
158
+ }
159
+
160
+ // Not authenticated — show manual login
161
+ renderLogin();
162
+ }
58
163
  ```
@@ -2,74 +2,165 @@
2
2
 
3
3
  Calling application methods on a Calimero node.
4
4
 
5
- ## Setup
5
+ ## The `rpcClient` singleton
6
+
7
+ `@calimero-network/calimero-client` exports a pre-configured `rpcClient` singleton.
8
+ Import it directly — do not construct `JsonRpcClient` manually.
6
9
 
7
10
  ```typescript
8
11
  import {
9
- JsonRpcClient,
10
- getStorageAppEndpointKey,
11
- getJWTObject,
12
+ rpcClient,
13
+ getContextId,
14
+ getExecutorPublicKey,
12
15
  } from '@calimero-network/calimero-client';
16
+ ```
17
+
18
+ ---
19
+
20
+ ## Execute a method (mutation or view — same call)
13
21
 
14
- const nodeUrl = getStorageAppEndpointKey() ?? 'http://localhost:2428';
15
- const jwt = getJWTObject();
22
+ ```typescript
23
+ const response = await rpcClient.execute<ArgsType, OutputType>({
24
+ contextId: getContextId()!,
25
+ method: 'methodName',
26
+ argsJson: { /* your args */ },
27
+ executorPublicKey: getExecutorPublicKey()!,
28
+ });
16
29
 
17
- const client = new JsonRpcClient(nodeUrl, '/jsonrpc', jwt?.access_token);
30
+ if (response.error) {
31
+ console.error(response.error.error.cause.info?.message);
32
+ } else {
33
+ console.log(response.result?.output);
34
+ }
18
35
  ```
19
36
 
37
+ ---
38
+
20
39
  ## Calling a mutation (changes state)
21
40
 
22
41
  ```typescript
23
- const response = await client.mutate<{ key: string; value: string }, void>({
24
- contextId: 'your-context-id',
42
+ const response = await rpcClient.execute<{ key: string; value: string }, void>({
43
+ contextId: getContextId()!,
25
44
  method: 'set',
26
45
  argsJson: { key: 'hello', value: 'world' },
27
- executorPublicKey: jwt?.executor_public_key ?? '',
46
+ executorPublicKey: getExecutorPublicKey()!,
28
47
  });
29
48
 
30
49
  if (response.error) {
31
- console.error(response.error);
50
+ console.error('set failed:', response.error.error.cause.info?.message);
32
51
  }
33
52
  ```
34
53
 
35
54
  ## Calling a view (read-only)
36
55
 
37
56
  ```typescript
38
- const response = await client.query<{ key: string }, string | null>({
39
- contextId: 'your-context-id',
57
+ const response = await rpcClient.execute<{ key: string }, string | null>({
58
+ contextId: getContextId()!,
40
59
  method: 'get',
41
60
  argsJson: { key: 'hello' },
42
- executorPublicKey: jwt?.executor_public_key ?? '',
61
+ executorPublicKey: getExecutorPublicKey()!,
43
62
  });
44
63
 
45
- console.log(response.result?.output); // "world"
64
+ if (!response.error) {
65
+ console.log(response.result?.output); // "world"
66
+ }
46
67
  ```
47
68
 
69
+ ---
70
+
48
71
  ## Response shape
49
72
 
50
73
  ```typescript
51
- interface RpcResponse<T> {
52
- result?: {
53
- output: T;
54
- };
55
- error?: {
56
- code: number;
57
- message: string;
58
- };
74
+ // Success:
75
+ { result: { output: T } }
76
+
77
+ // Error:
78
+ {
79
+ error: {
80
+ id: number;
81
+ jsonrpc: string;
82
+ code: number; // HTTP-like code (400, 401, 500)
83
+ error: {
84
+ name: string; // e.g. "FunctionCallError"
85
+ cause: {
86
+ name: string;
87
+ info?: { message: string };
88
+ };
89
+ };
90
+ headers?: Record<string, string>;
91
+ }
59
92
  }
60
93
  ```
61
94
 
62
- ## Error handling
95
+ ---
96
+
97
+ ## Error names
98
+
99
+ | name | meaning |
100
+ |---|---|
101
+ | `FunctionCallError` | App method returned an error |
102
+ | `RpcExecutionError` | Node couldn't execute the method |
103
+ | `InvalidRequestError` | Malformed request (wrong context-id, missing args) |
104
+ | `AuthenticationError` | JWT expired, revoked, or missing |
105
+ | `UnknownServerError` | Unexpected server error |
106
+
107
+ ---
108
+
109
+ ## Error handling with 401
110
+
111
+ The HTTP client inside `rpcClient` automatically refreshes tokens on `401 token_expired`.
112
+ For `token_revoked` or `invalid_token` you need to re-authenticate:
63
113
 
64
114
  ```typescript
65
- const response = await client.query({ ... });
115
+ const response = await rpcClient.execute({ ... });
66
116
 
67
117
  if (response.error) {
68
- if (response.error.code === 401) {
69
- // token expired — refresh and retry
70
- await refreshTokens();
71
- return callAgain();
118
+ const { code, headers } = response.error;
119
+ const authError = headers?.['x-auth-error'];
120
+
121
+ if (code === 401 && (authError === 'token_revoked' || authError === 'invalid_token')) {
122
+ // Token cannot be refreshed — send user to login
123
+ clientLogout();
124
+ return;
125
+ }
126
+
127
+ throw new Error(response.error.error.cause.info?.message ?? 'Unknown error');
128
+ }
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Complete example: typed wrapper
134
+
135
+ ```typescript
136
+ import {
137
+ rpcClient,
138
+ getContextId,
139
+ getExecutorPublicKey,
140
+ } from '@calimero-network/calimero-client';
141
+
142
+ async function setItem(key: string, value: string): Promise<void> {
143
+ const response = await rpcClient.execute<{ key: string; value: string }, void>({
144
+ contextId: getContextId()!,
145
+ method: 'set',
146
+ argsJson: { key, value },
147
+ executorPublicKey: getExecutorPublicKey()!,
148
+ });
149
+ if (response.error) {
150
+ throw new Error(response.error.error.cause.info?.message);
151
+ }
152
+ }
153
+
154
+ async function getItem(key: string): Promise<string | null> {
155
+ const response = await rpcClient.execute<{ key: string }, string | null>({
156
+ contextId: getContextId()!,
157
+ method: 'get',
158
+ argsJson: { key },
159
+ executorPublicKey: getExecutorPublicKey()!,
160
+ });
161
+ if (response.error) {
162
+ throw new Error(response.error.error.cause.info?.message);
72
163
  }
73
- throw new Error(response.error.message);
164
+ return response.result?.output ?? null;
74
165
  }
75
166
  ```
@@ -34,18 +34,29 @@ function readSSOParams(): {
34
34
 
35
35
  ## Using SSO tokens on app startup
36
36
 
37
+ Use the storage helpers from `@calimero-network/calimero-client`:
38
+
37
39
  ```typescript
40
+ import {
41
+ setAppEndpointKey,
42
+ setAccessToken,
43
+ setRefreshToken,
44
+ setApplicationId,
45
+ setContextAndIdentityFromJWT,
46
+ } from '@calimero-network/calimero-client';
47
+
38
48
  async function initApp() {
39
49
  const sso = readSSOParams();
40
50
 
41
51
  if (sso.accessToken && sso.nodeUrl) {
42
52
  // 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
53
+ setAppEndpointKey(sso.nodeUrl);
54
+ setAccessToken(sso.accessToken);
55
+ if (sso.refreshToken) setRefreshToken(sso.refreshToken);
56
+ if (sso.applicationId) setApplicationId(sso.applicationId);
57
+ // Extract contextId + executorPublicKey from the JWT claims
58
+ setContextAndIdentityFromJWT(sso.accessToken);
59
+ // Clear hash so tokens aren't in browser history
49
60
  history.replaceState(null, '', window.location.pathname);
50
61
  renderApp();
51
62
  } else {
@@ -55,12 +66,6 @@ async function initApp() {
55
66
  }
56
67
  ```
57
68
 
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
69
  ## Important
65
70
 
66
71
  Always fall back to manual login if the hash params are absent — the app must work