@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
@@ -0,0 +1,142 @@
1
+ # WebSocket Events
2
+
3
+ The Calimero node pushes real-time events to subscribers over WebSocket.
4
+
5
+ ## Connect and subscribe
6
+
7
+ ```text
8
+ ws://localhost:2428/ws
9
+ ```
10
+
11
+ After connecting, send:
12
+
13
+ ```json
14
+ { "action": "subscribe", "contextIds": ["<context-id-1>", "<context-id-2>"] }
15
+ ```
16
+
17
+ You can subscribe to multiple context IDs in one message.
18
+
19
+ ---
20
+
21
+ ## Event types
22
+
23
+ | Type | When fired | Payload |
24
+ | ---------------- | ----------------------------------------------------- | ------------------------------ |
25
+ | `ExecutionEvent` | App called `app::emit!()` (Rust) or `env.emit()` (JS) | `{ events: ExecutionEvent[] }` |
26
+ | `StateMutation` | Any context member mutated shared state | `{ newRoot: string }` |
27
+
28
+ ---
29
+
30
+ ## ExecutionEvent schema
31
+
32
+ ```typescript
33
+ interface WsMessage {
34
+ contextId: string;
35
+ type: 'ExecutionEvent';
36
+ data: {
37
+ events: Array<{
38
+ kind: string; // matches Rust enum variant name or JS event name
39
+ data: number[] | object; // byte array (UTF-8 JSON) or plain object
40
+ }>;
41
+ };
42
+ }
43
+ ```
44
+
45
+ `data` is typically a UTF-8 JSON byte array when coming from a Rust app. Decode it:
46
+
47
+ ```typescript
48
+ function decodeEventData(data: unknown): unknown {
49
+ if (Array.isArray(data) && data.every((n) => typeof n === 'number')) {
50
+ try {
51
+ return JSON.parse(new TextDecoder().decode(new Uint8Array(data as number[])));
52
+ } catch {
53
+ return data; // leave as-is if not valid JSON
54
+ }
55
+ }
56
+ return data;
57
+ }
58
+ ```
59
+
60
+ Full consumption pattern:
61
+
62
+ ```typescript
63
+ ws.onmessage = (msg) => {
64
+ const envelope = JSON.parse(msg.data);
65
+ if (envelope.type === 'ExecutionEvent') {
66
+ for (const e of envelope.data.events) {
67
+ const payload = decodeEventData(e.data);
68
+ switch (e.kind) {
69
+ case 'PostCreated':
70
+ handlePostCreated(payload);
71
+ break;
72
+ case 'PostDeleted':
73
+ handlePostDeleted(payload);
74
+ break;
75
+ }
76
+ }
77
+ }
78
+ if (envelope.type === 'StateMutation') {
79
+ // A peer mutated shared state — refetch data if needed
80
+ refreshData();
81
+ }
82
+ };
83
+ ```
84
+
85
+ ---
86
+
87
+ ## StateMutation schema
88
+
89
+ ```typescript
90
+ interface WsMessage {
91
+ contextId: string;
92
+ type: 'StateMutation';
93
+ data: {
94
+ newRoot: string; // hex hash of the new CRDT state root after merge
95
+ };
96
+ }
97
+ ```
98
+
99
+ Use `StateMutation` to know when to refetch state from the node — it fires on every mutation from
100
+ any member, including the local node's own mutations.
101
+
102
+ ---
103
+
104
+ ## Unsubscribe
105
+
106
+ ```json
107
+ { "action": "unsubscribe", "contextIds": ["<context-id>"] }
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Emitting events from Rust
113
+
114
+ ```rust
115
+ #[app::event]
116
+ pub enum Event<'a> {
117
+ PostCreated { title: &'a str },
118
+ PostDeleted { id: u32 },
119
+ }
120
+
121
+ #[app::logic]
122
+ impl AppState {
123
+ pub fn create_post(&mut self, title: String) -> app::Result<()> {
124
+ // ...
125
+ app::emit!(Event::PostCreated { title: &title });
126
+ Ok(())
127
+ }
128
+ }
129
+ ```
130
+
131
+ The `kind` field in the WebSocket message will be `"PostCreated"` or `"PostDeleted"`.
132
+
133
+ ## Emitting events from TypeScript (SDK JS)
134
+
135
+ ```typescript
136
+ import * as env from '@calimero-network/calimero-sdk-js/env';
137
+
138
+ set(key: string, value: string): void {
139
+ this.items.set(key, value);
140
+ env.emit({ kind: 'ItemSet', data: { key, value } });
141
+ }
142
+ ```
@@ -0,0 +1,35 @@
1
+ # Rule: Application ≠ Context
2
+
3
+ **An application is not a context. Installing an app does not create a context.**
4
+
5
+ ## What this means
6
+
7
+ - `meroctl app install` → registers the WASM binary. Returns an `application-id`. No state exists
8
+ yet. No context exists yet.
9
+ - `meroctl context create --application-id <id>` → creates a context. Calls `init()`. State now
10
+ exists. The context has a `context-id`.
11
+
12
+ These are always **two separate commands**. You cannot skip the first step and jump straight to
13
+ calling methods.
14
+
15
+ ## Why this matters
16
+
17
+ When a developer says "install and run my app", the correct sequence is:
18
+
19
+ ```bash
20
+ meroctl app install --path app.wasm # → application-id
21
+ meroctl context create --application-id <id> # → context-id
22
+ meroctl call <context-id> my_method --args '{}'
23
+ ```
24
+
25
+ Not:
26
+
27
+ ```bash
28
+ meroctl app install --path app.wasm
29
+ meroctl call ??? # WRONG — no context yet
30
+ ```
31
+
32
+ ## Analogy
33
+
34
+ Think of an Application as a class definition and a Context as an instance of that class. You can
35
+ have many contexts (instances) from one application (class), each with completely isolated state.
@@ -0,0 +1,55 @@
1
+ # Rule: CRDT Types Only for Shared State
2
+
3
+ **Never use `std::collections` (Rust) or plain `Map`/`Array` (JS/TS) for shared application state.**
4
+
5
+ ## Forbidden for state fields
6
+
7
+ ```rust
8
+ // WRONG — will compile but cause data loss on concurrent writes:
9
+ struct AppState {
10
+ items: std::collections::HashMap<String, String>, // ❌
11
+ log: Vec<String>, // ❌
12
+ seen: std::collections::HashSet<String>, // ❌
13
+ }
14
+ ```
15
+
16
+ ```typescript
17
+ // WRONG — same problem in TypeScript:
18
+ @State
19
+ class AppState {
20
+ items: Map<string, string> = new Map(); // ❌
21
+ log: string[] = []; // ❌
22
+ }
23
+ ```
24
+
25
+ ## Required: use CRDT collections
26
+
27
+ ```rust
28
+ // CORRECT:
29
+ use calimero_storage::collections::{UnorderedMap, Vector, LwwRegister};
30
+
31
+ struct AppState {
32
+ items: UnorderedMap<String, LwwRegister<String>>, // ✓
33
+ log: Vector<String>, // ✓
34
+ }
35
+ ```
36
+
37
+ ```typescript
38
+ // CORRECT:
39
+ import { UnorderedMap, Vector } from '@calimero-network/calimero-sdk-js/collections';
40
+
41
+ @State
42
+ class AppState {
43
+ items: UnorderedMap<string, string> = new UnorderedMap(); // ✓
44
+ log: Vector<string> = new Vector(); // ✓
45
+ }
46
+ ```
47
+
48
+ ## Why
49
+
50
+ Standard collections have no merge semantics. When two context members both write to a `HashMap`
51
+ while offline, merging their states is undefined — the CRDT engine cannot resolve conflicts
52
+ automatically and data will be silently lost or overwritten.
53
+
54
+ CRDT collections are designed for exactly this scenario: they guarantee that all members converge to
55
+ the same state regardless of write order or network partitions.
@@ -1,24 +1,30 @@
1
1
  # calimero-desktop — Agent Instructions
2
2
 
3
- You are helping a developer integrate their app frontend with **Calimero Desktop SSO**.
3
+ You are helping a developer integrate their app frontend with **Calimero Desktop SSO** — the flow
4
+ where users open an app from the Desktop and are automatically logged in without a manual auth
5
+ screen.
6
+
7
+ > **NOT this skill** if the developer just needs to authenticate manually via the login form — that
8
+ > is handled entirely by `calimero-client-js`. This skill is specifically for reading SSO tokens
9
+ > that Calimero Desktop passes in the URL hash.
4
10
 
5
11
  ## What Desktop does
6
12
 
7
- Calimero Desktop opens app frontends in a browser window and passes auth tokens via the
8
- URL hash — so users are automatically logged in without going through the manual auth flow.
13
+ Calimero Desktop opens app frontends in a browser window and passes auth tokens via the URL hash —
14
+ so users are automatically logged in without going through the manual auth flow.
9
15
 
10
16
  ## Hash parameters passed by Desktop
11
17
 
12
- | Parameter | Type | Description |
13
- | --- | --- | --- |
14
- | `access_token` | string | JWT for authenticated node calls |
15
- | `refresh_token` | string | Token to obtain a new access token |
16
- | `node_url` | string | URL of the local node (`http://localhost:PORT`) |
17
- | `application_id` | string | The installed app's ID on this node |
18
+ | Parameter | Type | Description |
19
+ | ---------------- | ------ | ----------------------------------------------- |
20
+ | `access_token` | string | JWT for authenticated node calls |
21
+ | `refresh_token` | string | Token to obtain a new access token |
22
+ | `node_url` | string | URL of the local node (`http://localhost:PORT`) |
23
+ | `application_id` | string | The installed app's ID on this node |
18
24
 
19
25
  ## Example URL
20
26
 
21
- ```
27
+ ```text
22
28
  https://your-app.com/#access_token=eyJ...&refresh_token=eyJ...&node_url=http://localhost:2428&application_id=abc123
23
29
  ```
24
30
 
@@ -38,10 +44,15 @@ if (accessToken && nodeUrl) {
38
44
 
39
45
  ## Critical rule
40
46
 
41
- Always fall back to manual login when hash params are absent. The app must work when
42
- opened in a regular browser, not only from Desktop.
47
+ Always fall back to manual login when hash params are absent. The app must work when opened in a
48
+ regular browser, not only from Desktop.
49
+
50
+ ## Related skills
51
+
52
+ - **`calimero-client-js`** — mero-js / mero-react API for auth, RPC, and WebSocket subscriptions
53
+ - **`calimero-core`** — auth flow (JWT tokens, login, refresh) and JSON-RPC protocol detail
43
54
 
44
55
  ## References
45
56
 
46
- See `references/` for full integration pattern and how Desktop discovers app URLs.
47
- See `rules/` for the fallback requirement.
57
+ See `references/` for full integration pattern and how Desktop discovers app URLs. See `rules/` for
58
+ the fallback requirement.
@@ -2,28 +2,39 @@
2
2
 
3
3
  ## Full startup flow
4
4
 
5
+ Use the storage helpers from `@calimero-network/calimero-client` — do **not** write to
6
+ `localStorage` directly. The helpers ensure correct key names and extract contextId +
7
+ executorPublicKey from the JWT automatically.
8
+
5
9
  ```typescript
10
+ import {
11
+ setAppEndpointKey,
12
+ setAccessToken,
13
+ setRefreshToken,
14
+ setApplicationId,
15
+ setContextAndIdentityFromJWT,
16
+ getAuthConfig,
17
+ } from '@calimero-network/calimero-client';
18
+
6
19
  interface SSOParams {
7
20
  accessToken: string;
8
- refreshToken: string;
21
+ refreshToken: string | null;
9
22
  nodeUrl: string;
10
- applicationId: string;
23
+ applicationId: string | null;
11
24
  }
12
25
 
13
26
  function readDesktopSSO(): SSOParams | null {
14
27
  const hash = new URLSearchParams(window.location.hash.slice(1));
15
28
  const accessToken = hash.get('access_token');
16
- const refreshToken = hash.get('refresh_token');
17
29
  const nodeUrl = hash.get('node_url');
18
- const applicationId = hash.get('application_id');
19
30
 
20
31
  if (!accessToken || !nodeUrl) return null;
21
32
 
22
33
  return {
23
34
  accessToken,
24
- refreshToken: refreshToken ?? '',
35
+ refreshToken: hash.get('refresh_token'),
25
36
  nodeUrl,
26
- applicationId: applicationId ?? '',
37
+ applicationId: hash.get('application_id'),
27
38
  };
28
39
  }
29
40
 
@@ -31,21 +42,29 @@ async function bootstrap() {
31
42
  const sso = readDesktopSSO();
32
43
 
33
44
  if (sso) {
45
+ // Store tokens via SDK helpers (sets localStorage keys correctly)
46
+ setAppEndpointKey(sso.nodeUrl);
47
+ setAccessToken(sso.accessToken);
48
+ if (sso.refreshToken) setRefreshToken(sso.refreshToken);
49
+ if (sso.applicationId) setApplicationId(sso.applicationId);
50
+ // Extracts contextId + executorPublicKey from JWT claims
51
+ setContextAndIdentityFromJWT(sso.accessToken);
52
+
34
53
  // Clear hash from URL bar (tokens shouldn't sit in browser history)
35
54
  history.replaceState(null, '', window.location.pathname + window.location.search);
36
55
 
37
- // Store for use by the client
38
- localStorage.setItem('calimero_node_url', sso.nodeUrl);
39
- localStorage.setItem('calimero_jwt', JSON.stringify({
40
- access_token: sso.accessToken,
41
- refresh_token: sso.refreshToken,
42
- }));
43
- localStorage.setItem('calimero_app_id', sso.applicationId);
44
-
45
- await renderAuthenticatedApp(sso);
46
- } else {
47
- renderLoginScreen();
56
+ renderAuthenticatedApp();
57
+ return;
58
+ }
59
+
60
+ // No SSO hash — check if already authenticated from a prior session
61
+ const config = getAuthConfig();
62
+ if (config.error === null) {
63
+ renderAuthenticatedApp();
64
+ return;
48
65
  }
66
+
67
+ renderLoginScreen();
49
68
  }
50
69
 
51
70
  document.addEventListener('DOMContentLoaded', bootstrap);
@@ -53,7 +72,8 @@ document.addEventListener('DOMContentLoaded', bootstrap);
53
72
 
54
73
  ## How Desktop discovers your app's frontend URL
55
74
 
56
- Desktop reads the `links.frontend` field from your app's `manifest.json` inside the installed bundle. To ensure Desktop can open your app correctly, set this field:
75
+ Desktop reads the `links.frontend` field from your app's `manifest.json` inside the installed
76
+ bundle. Set this field so Desktop can open your app:
57
77
 
58
78
  ```json
59
79
  {
@@ -70,18 +90,25 @@ Desktop opens this URL and appends the SSO hash params.
70
90
 
71
91
  ```typescript
72
92
  // App.tsx
93
+ import { getAuthConfig, setAccessToken, setAppEndpointKey,
94
+ setRefreshToken, setApplicationId, setContextAndIdentityFromJWT } from '@calimero-network/calimero-client';
95
+
73
96
  function App() {
74
97
  const [authState, setAuthState] = useState<'loading' | 'authenticated' | 'unauthenticated'>('loading');
75
98
 
76
99
  useEffect(() => {
77
100
  const sso = readDesktopSSO();
78
101
  if (sso) {
79
- storeTokens(sso);
80
- setAuthState('authenticated');
81
- } else if (hasStoredTokens()) {
102
+ setAppEndpointKey(sso.nodeUrl);
103
+ setAccessToken(sso.accessToken);
104
+ if (sso.refreshToken) setRefreshToken(sso.refreshToken);
105
+ if (sso.applicationId) setApplicationId(sso.applicationId);
106
+ setContextAndIdentityFromJWT(sso.accessToken);
107
+ history.replaceState(null, '', window.location.pathname);
82
108
  setAuthState('authenticated');
83
109
  } else {
84
- setAuthState('unauthenticated');
110
+ const config = getAuthConfig();
111
+ setAuthState(config.error === null ? 'authenticated' : 'unauthenticated');
85
112
  }
86
113
  }, []);
87
114
 
@@ -1,6 +1,7 @@
1
1
  # Rule: Always fall back to manual login if hash params are absent
2
2
 
3
3
  Your app must work in two scenarios:
4
+
4
5
  1. Opened from Calimero Desktop — SSO hash params present
5
6
  2. Opened directly in a browser — no hash params
6
7
 
@@ -29,5 +30,5 @@ if (token) {
29
30
 
30
31
  ## Why
31
32
 
32
- Desktop is not the only way users access apps. Developers test in browsers directly.
33
- Organizations may embed apps in other contexts. The app must be self-sufficient.
33
+ Desktop is not the only way users access apps. Developers test in browsers directly. Organizations
34
+ may embed apps in other contexts. The app must be self-sufficient.