@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,39 +1,120 @@
1
1
  # CRDT State Collections
2
2
 
3
- Calimero provides conflict-free replicated data types for application state. These are the only correct way to store persistent, shared state.
3
+ Calimero provides conflict-free replicated data types for application state. All collections are
4
+ imported from `calimero_storage::collections`.
5
+
6
+ > **Critical:** Do NOT use `calimero_sdk::state::*` — that path no longer exists. The correct import
7
+ > is `calimero_storage::collections::*`.
4
8
 
5
9
  ## Available Collections
6
10
 
7
- | Type | Use for | std equivalent |
8
- | --- | --- | --- |
9
- | `calimero_sdk::state::Map<K, V>` | Key-value mapping | `HashMap<K, V>` |
10
- | `calimero_sdk::state::Set<T>` | Unique values | `HashSet<T>` |
11
- | `calimero_sdk::state::Vector<T>` | Ordered list | `Vec<T>` |
12
- | `calimero_sdk::state::UnorderedMap<K, V>` | Unordered map | `HashMap<K, V>` |
11
+ | Type | Use for | Notes |
12
+ | ------------------------- | --------------------------------------- | -------------------------------------------------- |
13
+ | `UnorderedMap<K, V>` | Key-value mapping | Most collection ops return `Result<>` use `?` |
14
+ | `UnorderedSet<T>` | Unique value set | |
15
+ | `Vector<T>` | Ordered list (append-only) | |
16
+ | `LwwRegister<T>` | Single last-write-wins value | Wrap map values: `UnorderedMap<K, LwwRegister<V>>` |
17
+ | `Counter` | Grow-only counter (GCounter by default) | `.increment()`, `.value()` |
18
+ | `Counter<true>` | PN-Counter (supports decrement) | Same API + `.decrement()` |
19
+ | `FrozenStorage<T>` | Immutable content-addressed entries | |
20
+ | `UserStorage<T>` | Per-member isolated storage | Not synced to other members |
21
+ | `ReplicatedGrowableArray` | CRDT text / ordered sequence | Collaborative editing |
22
+
23
+ ## Import
24
+
25
+ ```rust
26
+ use calimero_storage::collections::{
27
+ Counter, FrozenStorage, LwwRegister, ReplicatedGrowableArray,
28
+ UnorderedMap, UnorderedSet, UserStorage, Vector,
29
+ };
30
+ ```
13
31
 
14
32
  ## Usage
15
33
 
16
34
  ```rust
17
- use calimero_sdk::state::{Map, Vector};
35
+ use calimero_sdk::app;
36
+ use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize};
37
+ use calimero_storage::collections::{LwwRegister, UnorderedMap, Vector, Counter};
18
38
 
19
- #[derive(Default, BorshDeserialize, BorshSerialize)]
39
+ #[app::state]
40
+ #[derive(Debug, BorshSerialize, BorshDeserialize)]
41
+ #[borsh(crate = "calimero_sdk::borsh")]
20
42
  pub struct AppState {
21
- items: Map<String, String>,
22
- log: Vector<String>,
43
+ // Map values wrapped in LwwRegister for CRDT merge
44
+ items: UnorderedMap<String, LwwRegister<String>>,
45
+ log: Vector<String>,
46
+ counter: Counter,
23
47
  }
24
48
 
25
49
  #[app::logic]
26
50
  impl AppState {
27
- pub fn set(&mut self, key: String, value: String) {
28
- self.items.insert(key, value);
51
+ pub fn set(&mut self, key: String, value: String) -> app::Result<()> {
52
+ self.items.insert(key, value.into())?;
53
+ Ok(())
54
+ }
55
+
56
+ pub fn get(&self, key: &str) -> app::Result<Option<String>> {
57
+ Ok(self.items.get(key)?.map(|v| v.get().clone()))
29
58
  }
30
59
 
31
- pub fn get(&self, key: &str) -> Option<&String> {
32
- self.items.get(key)
60
+ pub fn append(&mut self, entry: String) -> app::Result<()> {
61
+ self.log.push(entry)?;
62
+ Ok(())
33
63
  }
34
64
 
35
- pub fn append(&mut self, entry: String) {
36
- self.log.push(entry);
65
+ pub fn increment(&mut self) -> app::Result<()> {
66
+ self.counter.increment()?;
67
+ Ok(())
68
+ }
69
+ }
70
+ ```
71
+
72
+ ## UnorderedMap patterns
73
+
74
+ ```rust
75
+ // Insert
76
+ self.items.insert(key, value.into())?;
77
+
78
+ // Get (returns Option<LwwRegister<V>>)
79
+ let val = self.items.get(&key)?.map(|v| v.get().clone());
80
+
81
+ // Contains
82
+ let exists = self.items.contains(&key)?;
83
+
84
+ // Remove
85
+ let old = self.items.remove(&key)?;
86
+
87
+ // All entries
88
+ let all: Vec<(K, V)> = self.items.entries()?.collect();
89
+
90
+ // In-place mutation via guard
91
+ if let Some(mut guard) = self.items.get_mut(&key)? {
92
+ guard.set(new_value);
93
+ }
94
+
95
+ // Entry API (like HashMap::entry)
96
+ use calimero_storage::collections::unordered_map::Entry;
97
+ let entry = self.items.entry(key)?;
98
+ let val = entry.or_insert(LwwRegister::new(default_value))?;
99
+
100
+ // Length
101
+ let n = self.items.len()?;
102
+
103
+ // Clear
104
+ self.items.clear()?;
105
+ ```
106
+
107
+ ## Constructor
108
+
109
+ Collections must be initialized with `::new()` — there is no Default impl:
110
+
111
+ ```rust
112
+ #[app::init]
113
+ pub fn init() -> AppState {
114
+ AppState {
115
+ items: UnorderedMap::new(),
116
+ log: Vector::new(),
117
+ counter: Counter::new(),
37
118
  }
38
119
  }
39
120
  ```
@@ -41,10 +122,14 @@ impl AppState {
41
122
  ## Keys and values must implement
42
123
 
43
124
  - `BorshSerialize + BorshDeserialize`
44
- - `Clone` (for most operations)
125
+ - Values in UnorderedMap are typically wrapped in `LwwRegister<V>` this handles conflict
126
+ resolution automatically
45
127
 
46
128
  ## Important
47
129
 
48
- CRDT collections handle concurrent writes from different context members automatically —
49
- you never need to manually resolve conflicts. Writes from different members are merged
50
- deterministically using the DAG-based sync engine.
130
+ CRDT collections handle concurrent writes from different context members automatically — you never
131
+ need to manually resolve conflicts. Writes from different members are merged deterministically using
132
+ the DAG-based sync engine.
133
+
134
+ Collection operations are **fallible** — always propagate errors with `?`. Panicking inside a WASM
135
+ method aborts the execution and rolls back state.
@@ -0,0 +1,169 @@
1
+ # UserStorage and FrozenStorage
2
+
3
+ Two specialised CRDT collection types for per-member and immutable data.
4
+
5
+ ---
6
+
7
+ ## UserStorage\<T\>
8
+
9
+ Stores a separate value of type `T` per context member (keyed by public key). Each member can only
10
+ read/write their own value via the current executor identity. Reading another member's value
11
+ requires explicitly passing their public key.
12
+
13
+ `T` must implement `BorshSerialize + BorshDeserialize + Mergeable + Default`.
14
+
15
+ ### Simple example (single value per user)
16
+
17
+ ```rust
18
+ use calimero_storage::collections::{LwwRegister, UserStorage};
19
+
20
+ #[app::state]
21
+ pub struct AppState {
22
+ // Each member stores their own string value
23
+ user_names: UserStorage<LwwRegister<String>>,
24
+ }
25
+
26
+ #[app::logic]
27
+ impl AppState {
28
+ // Write the current executor's value
29
+ pub fn set_my_name(&mut self, name: String) -> app::Result<()> {
30
+ self.user_names.insert(name.into())?;
31
+ Ok(())
32
+ }
33
+
34
+ // Read the current executor's value
35
+ pub fn get_my_name(&self) -> app::Result<Option<String>> {
36
+ Ok(self.user_names.get()?.map(|v| v.get().clone()))
37
+ }
38
+
39
+ // Read a specific user's value
40
+ pub fn get_name_for(&self, user_key: calimero_sdk::PublicKey) -> app::Result<Option<String>> {
41
+ Ok(self.user_names.get_for_user(&user_key)?.map(|v| v.get().clone()))
42
+ }
43
+ }
44
+ ```
45
+
46
+ ### Nested example (map per user)
47
+
48
+ When `T` is a collection, it must implement `Mergeable` manually (or via `#[derive(Mergeable)]`):
49
+
50
+ ```rust
51
+ use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize};
52
+ use calimero_storage::collections::{LwwRegister, Mergeable, UnorderedMap, UserStorage};
53
+
54
+ #[derive(Debug, BorshSerialize, BorshDeserialize, Default)]
55
+ #[borsh(crate = "calimero_sdk::borsh")]
56
+ struct UserKvMap {
57
+ map: UnorderedMap<String, LwwRegister<String>>,
58
+ }
59
+
60
+ impl Mergeable for UserKvMap {
61
+ fn merge(&mut self, other: &Self)
62
+ -> Result<(), calimero_storage::collections::crdt_meta::MergeError>
63
+ {
64
+ self.map.merge(&other.map)
65
+ }
66
+ }
67
+
68
+ #[app::state]
69
+ pub struct AppState {
70
+ user_data: UserStorage<UserKvMap>,
71
+ }
72
+
73
+ #[app::logic]
74
+ impl AppState {
75
+ pub fn set_user_kv(&mut self, key: String, value: String) -> app::Result<()> {
76
+ // get-modify-put pattern
77
+ let mut data = self.user_data.get()?.unwrap_or_default();
78
+ data.map.insert(key, value.into())?;
79
+ self.user_data.insert(data)?;
80
+ Ok(())
81
+ }
82
+
83
+ pub fn get_user_kv(&self, key: &str) -> app::Result<Option<String>> {
84
+ let data = self.user_data.get()?;
85
+ match data {
86
+ Some(d) => Ok(d.map.get(key)?.map(|v| v.get().clone())),
87
+ None => Ok(None),
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ ### Key methods
94
+
95
+ | Method | Description |
96
+ | -------------------------------- | -------------------------------- |
97
+ | `insert(value: T)?` | Store value for current executor |
98
+ | `get()?` | Get value for current executor |
99
+ | `get_for_user(key: &PublicKey)?` | Get value for a specific user |
100
+
101
+ ---
102
+
103
+ ## FrozenStorage\<T\>
104
+
105
+ Content-addressed, **immutable** storage. Values are keyed by their SHA-256 hash. Once inserted, a
106
+ value can never be modified or deleted. All members share the same frozen entries (unlike
107
+ `UserStorage`).
108
+
109
+ `T` must implement `BorshSerialize + BorshDeserialize + Into<FrozenValue<T>>`. Typically `T` is
110
+ `String` or a `Vec<u8>`.
111
+
112
+ ```rust
113
+ use calimero_storage::collections::FrozenStorage;
114
+
115
+ #[app::state]
116
+ pub struct AppState {
117
+ frozen: FrozenStorage<String>,
118
+ }
119
+
120
+ #[app::logic]
121
+ impl AppState {
122
+ /// Insert a value — returns its 32-byte SHA-256 hash
123
+ pub fn add_frozen(&mut self, value: String) -> app::Result<[u8; 32]> {
124
+ let hash = self.frozen.insert(value.into())?;
125
+ Ok(hash)
126
+ }
127
+
128
+ /// Retrieve by hash
129
+ pub fn get_frozen(&self, hash: [u8; 32]) -> app::Result<Option<String>> {
130
+ Ok(self.frozen.get(&hash)?.map(|v| v.clone()))
131
+ }
132
+ }
133
+ ```
134
+
135
+ ### Typical usage pattern
136
+
137
+ Since hashes are `[u8; 32]`, encode them as hex or base58 strings for JSON:
138
+
139
+ ```rust
140
+ use hex;
141
+
142
+ pub fn add_frozen(&mut self, value: String) -> app::Result<String> {
143
+ let hash = self.frozen.insert(value.into())?;
144
+ Ok(hex::encode(hash)) // return as hex string for JSON clients
145
+ }
146
+
147
+ pub fn get_frozen(&self, hash_hex: String) -> app::Result<Option<String>> {
148
+ let mut hash = [0u8; 32];
149
+ hex::decode_to_slice(&hash_hex, &mut hash)
150
+ .map_err(|_| /* your error type */)?;
151
+ Ok(self.frozen.get(&hash)?.map(|v| v.clone()))
152
+ }
153
+ ```
154
+
155
+ ### Frozen storage key methods
156
+
157
+ | Method | Description |
158
+ | -------------------------- | ------------------------------------ |
159
+ | `insert(value: T.into())?` | Store value, returns `[u8; 32]` hash |
160
+ | `get(&hash)?` | Retrieve value by hash |
161
+
162
+ ---
163
+
164
+ ## Cargo.toml — add hex for hash encoding
165
+
166
+ ```toml
167
+ [dependencies]
168
+ hex = "0.4"
169
+ ```
@@ -29,6 +29,9 @@ impl AppState {
29
29
  }
30
30
  ```
31
31
 
32
- **Why:** `#[app::state]` and `#[app::logic]` are proc macros that transform impl blocks to register the type with the SDK runtime. The struct itself only needs the derive macros (`Default`, `BorshDeserialize`, `BorshSerialize`).
32
+ **Why:** `#[app::state]` and `#[app::logic]` are proc macros that transform impl blocks to register
33
+ the type with the SDK runtime. The struct itself only needs the derive macros (`Default`,
34
+ `BorshDeserialize`, `BorshSerialize`).
33
35
 
34
- **Also:** `#[app::init]` marks the constructor — called once when the context is created. It must return the state type, not `Self`.
36
+ **Also:** `#[app::init]` marks the constructor — called once when the context is created. It must
37
+ return the state type, not `Self`.
@@ -22,6 +22,9 @@ pub struct AppState {
22
22
  }
23
23
  ```
24
24
 
25
- **Why:** `std::collections` types do not participate in the CRDT merge process. Data written to them by one node will never reach other context members. The bug is silent — the app compiles and runs correctly on one node, but state diverges in multi-member contexts.
25
+ **Why:** `std::collections` types do not participate in the CRDT merge process. Data written to them
26
+ by one node will never reach other context members. The bug is silent — the app compiles and runs
27
+ correctly on one node, but state diverges in multi-member contexts.
26
28
 
27
- **Applies to:** `HashMap`, `BTreeMap`, `HashSet`, `BTreeSet`, `Vec` (for shared state). `Vec` is fine for temporary local values inside a method, just not as a field in the state struct.
29
+ **Applies to:** `HashMap`, `BTreeMap`, `HashSet`, `BTreeSet`, `Vec` (for shared state). `Vec` is
30
+ fine for temporary local values inside a method, just not as a field in the state struct.
@@ -0,0 +1,45 @@
1
+ # Rule: State struct must derive Default, BorshDeserialize, BorshSerialize
2
+
3
+ The three derives are all required. Missing any one of them causes a **runtime panic**, not a
4
+ compile error — the app will install and start, then crash when the context is first accessed.
5
+
6
+ ## WRONG — missing derives:
7
+
8
+ ```rust
9
+ // ✗ Missing all derives — runtime panic on first call
10
+ pub struct AppState {
11
+ items: UnorderedMap<String, String>,
12
+ }
13
+
14
+ // ✗ Missing BorshSerialize — state cannot be persisted
15
+ #[derive(Default, BorshDeserialize)]
16
+ pub struct AppState {
17
+ items: UnorderedMap<String, String>,
18
+ }
19
+ ```
20
+
21
+ ## CORRECT:
22
+
23
+ ```rust
24
+ #[derive(Default, BorshDeserialize, BorshSerialize)]
25
+ pub struct AppState {
26
+ items: UnorderedMap<String, String>,
27
+ }
28
+ ```
29
+
30
+ ## What each derive does
31
+
32
+ | Derive | Required for |
33
+ | ------------------ | ---------------------------------------------------------- |
34
+ | `Default` | `#[app::init]` calls `AppState::default()` as the baseline |
35
+ | `BorshDeserialize` | Loading state from node storage on every method call |
36
+ | `BorshSerialize` | Saving state to node storage after every mutation |
37
+
38
+ ## Import
39
+
40
+ ```rust
41
+ use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize};
42
+ ```
43
+
44
+ Do not use `borsh` crate directly — import from `calimero_sdk::borsh` to ensure version
45
+ compatibility.
@@ -2,16 +2,16 @@
2
2
 
3
3
  Calimero apps run in a sandboxed WASM environment. The following are **not available**:
4
4
 
5
- | Forbidden | Use instead |
6
- | --- | --- |
7
- | `std::thread::spawn` | Not supported — WASM is single-threaded |
5
+ | Forbidden | Use instead |
6
+ | --------------------------------------------- | ----------------------------------------------- |
7
+ | `std::thread::spawn` | Not supported — WASM is single-threaded |
8
8
  | `tokio`, `async-std`, `async fn` in app logic | Not supported — all app methods are synchronous |
9
- | `std::fs`, file I/O | Use `env::private_storage_write/read` |
10
- | `std::net`, HTTP calls outbound | Not supported in WASM runtime |
11
- | `println!`, `eprintln!` | Use `calimero_sdk::env::log(&str)` |
12
- | System time (`std::time::SystemTime`) | Use `calimero_sdk::env::block_timestamp()` |
13
- | Environment variables | Not accessible from WASM |
14
- | Random number generation via `rand` | Use `calimero_sdk::env::random_seed()` |
9
+ | `std::fs`, file I/O | Use `env::private_storage_write/read` |
10
+ | `std::net`, HTTP calls outbound | Not supported in WASM runtime |
11
+ | `println!`, `eprintln!` | Use `calimero_sdk::env::log(&str)` |
12
+ | System time (`std::time::SystemTime`) | Use `calimero_sdk::env::block_timestamp()` |
13
+ | Environment variables | Not accessible from WASM |
14
+ | Random number generation via `rand` | Use `calimero_sdk::env::random_seed()` |
15
15
 
16
16
  ## Logging
17
17
 
@@ -32,4 +32,6 @@ let ts = env::block_timestamp(); // u64 nanoseconds
32
32
 
33
33
  ## Why
34
34
 
35
- The `merod` WASM runtime is deterministic by design. Non-deterministic operations (threads, I/O, time, random) would break the CRDT synchronization guarantees — every node must reach the same state given the same sequence of operations.
35
+ The `merod` WASM runtime is deterministic by design. Non-deterministic operations (threads, I/O,
36
+ time, random) would break the CRDT synchronization guarantees — every node must reach the same state
37
+ given the same sequence of operations.
@@ -0,0 +1,145 @@
1
+ # calimero-sdk-js — Agent Instructions
2
+
3
+ You are helping a developer **build a Calimero P2P application in TypeScript** using
4
+ `@calimero-network/calimero-sdk-js`. The app compiles to WebAssembly and runs inside the `merod`
5
+ node runtime.
6
+
7
+ > **NOT this skill** if the developer is connecting a browser/Node.js _frontend_ to a node (that's
8
+ > `calimero-client-js` / `@calimero-network/calimero-client`). This skill is for writing the
9
+ > application logic that _runs on the node_.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pnpm add @calimero-network/calimero-sdk-js
15
+ pnpm add -D @calimero-network/calimero-cli-js typescript
16
+ ```
17
+
18
+ The CLI's `postinstall` hook downloads QuickJS, WASI-SDK, and Binaryen automatically. If you used
19
+ `--ignore-scripts`, re-run with `pnpm install --ignore-scripts=false`.
20
+
21
+ ## Core concepts
22
+
23
+ | Concept | What it is |
24
+ | -------------------------- | -------------------------------------------------------------------------------------- |
25
+ | `@State` class | Persisted data — fields must be CRDT types |
26
+ | `@Logic(StateClass)` class | Entry points callable via JSON-RPC; must extend the state class |
27
+ | `@Init` static method | Seeds the initial state when context is first created |
28
+ | `@View()` method | Read-only — skips persistence; required for query methods |
29
+ | CRDT collection | Conflict-free type (`Counter`, `UnorderedMap`, etc.) — all state fields must use these |
30
+
31
+ ## Minimal app
32
+
33
+ ```typescript
34
+ import { State, Logic, Init, View } from '@calimero-network/calimero-sdk-js';
35
+ import { UnorderedMap } from '@calimero-network/calimero-sdk-js/collections';
36
+ import * as env from '@calimero-network/calimero-sdk-js/env';
37
+
38
+ @State
39
+ export class KvState {
40
+ items: UnorderedMap<string, string> = new UnorderedMap();
41
+ }
42
+
43
+ @Logic(KvState)
44
+ export class KvLogic extends KvState {
45
+ @Init
46
+ static init(): KvState {
47
+ return new KvState();
48
+ }
49
+
50
+ set(key: string, value: string): void {
51
+ env.log(`set ${key}`);
52
+ this.items.set(key, value);
53
+ }
54
+
55
+ @View()
56
+ get(key: string): string | null {
57
+ return this.items.get(key) ?? null;
58
+ }
59
+ }
60
+ ```
61
+
62
+ ## CRDT collections quick reference
63
+
64
+ | Type | Use case | Key ops |
65
+ | ------------------- | --------------------------------------- | -------------------------------------------------- |
66
+ | `Counter` | Distributed counting (returns `bigint`) | `increment()`, `incrementBy(n)`, `value()` |
67
+ | `UnorderedMap<K,V>` | Key-value store (LWW per key) | `set()`, `get()`, `has()`, `remove()`, `entries()` |
68
+ | `UnorderedSet<T>` | Unique membership (LWW per element) | `add()`, `has()`, `delete()`, `toArray()` |
69
+ | `Vector<T>` | Ordered list | `push()`, `get(i)`, `pop()`, `len()` |
70
+ | `LwwRegister<T>` | Single value (timestamp LWW) | `set()`, `get()` |
71
+
72
+ Nested collections (`Map<K, Set<V>>`) propagate changes automatically — no manual re-serialization.
73
+
74
+ ## Build & deploy
75
+
76
+ ```bash
77
+ # Build to WASM
78
+ npx calimero-sdk build src/index.ts -o build/service.wasm
79
+
80
+ # Install on node
81
+ meroctl --node-name <NODE> app install \
82
+ --path build/service.wasm \
83
+ --context-id <CONTEXT_ID>
84
+
85
+ # Call a method
86
+ meroctl --node-name <NODE> call \
87
+ --context-id <CONTEXT_ID> \
88
+ --method set \
89
+ --args '{"key":"hello","value":"world"}'
90
+
91
+ # Call a view
92
+ meroctl --node-name <NODE> call \
93
+ --context-id <CONTEXT_ID> \
94
+ --method get \
95
+ --args '{"key":"hello"}'
96
+ ```
97
+
98
+ ## Key rules
99
+
100
+ - All `@State` fields must be CRDT types — never plain `Map`, `Set`, `Array`, or primitives
101
+ - `@View()` is **required** on every read-only method — omitting it causes unnecessary persistence
102
+ - Use `env.log()` not `console.log()` — `console` is not available in the WASM runtime
103
+ - `Counter.value()` returns `bigint`, not `number`
104
+ - `@Init` must be a static method that returns the state class instance
105
+ - `@Logic(StateClass)` must extend the state class
106
+ - No async, no I/O, no threads in app logic — the WASM runtime is synchronous
107
+ - **Windows: building is not supported natively — use WSL** (QuickJS/WASI-SDK toolchain requires
108
+ Linux/macOS)
109
+
110
+ ## Events
111
+
112
+ ```typescript
113
+ import { emit } from '@calimero-network/calimero-sdk-js';
114
+
115
+ // Inside a mutation method:
116
+ emit({ type: 'ItemAdded', key: 'foo', value: 'bar' });
117
+ ```
118
+
119
+ Events are pushed to all context members via WebSocket. Clients subscribe using
120
+ `WsSubscriptionsClient` from `@calimero-network/calimero-client`.
121
+
122
+ ## Private storage (node-local)
123
+
124
+ ```typescript
125
+ import { createPrivateEntry } from '@calimero-network/calimero-sdk-js';
126
+
127
+ const secret = createPrivateEntry<string>();
128
+ secret.set('my-api-key');
129
+ const val = secret.get();
130
+ ```
131
+
132
+ Private entries are never broadcast to other nodes.
133
+
134
+ ## Related skills
135
+
136
+ - **`calimero-core`** — runtime concepts (context/app model, JSON-RPC protocol, WebSocket events,
137
+ CRDT type taxonomy)
138
+ - **`calimero-meroctl`** — full `meroctl` CLI reference for deploying and testing the app
139
+ - **`calimero-client-js`** — connecting a browser/React frontend to the node (not building app
140
+ logic)
141
+
142
+ ## References
143
+
144
+ See `references/` for CRDT collections, events, and build pipeline details. See `rules/` for hard
145
+ constraints.
@@ -0,0 +1,98 @@
1
+ # Build Pipeline
2
+
3
+ `@calimero-network/calimero-cli-js` compiles your TypeScript app to a `.wasm` binary.
4
+
5
+ ## Pipeline stages
6
+
7
+ ```text
8
+ TypeScript → Rollup → QuickJS → WASI-SDK → Binaryen → .wasm
9
+ Source Bundle C bytecode WASM Optimized
10
+ ```
11
+
12
+ ## Setup
13
+
14
+ ```bash
15
+ # Install SDK and CLI
16
+ pnpm add @calimero-network/calimero-sdk-js
17
+ pnpm add -D @calimero-network/calimero-cli-js typescript
18
+
19
+ # If postinstall didn't run (--ignore-scripts was set):
20
+ pnpm install --ignore-scripts=false
21
+ # Or manually:
22
+ pnpm --filter @calimero-network/calimero-cli-js run install-deps
23
+ ```
24
+
25
+ ## package.json (minimal)
26
+
27
+ ```json
28
+ {
29
+ "dependencies": {
30
+ "@calimero-network/calimero-sdk-js": "^0.1.0"
31
+ },
32
+ "devDependencies": {
33
+ "@calimero-network/calimero-cli-js": "^0.1.0",
34
+ "typescript": "^5.0.0"
35
+ },
36
+ "scripts": {
37
+ "build": "calimero-sdk build src/index.ts -o build/service.wasm"
38
+ }
39
+ }
40
+ ```
41
+
42
+ ## Build command
43
+
44
+ ```bash
45
+ # Build
46
+ npx calimero-sdk build src/index.ts -o build/service.wasm
47
+
48
+ # Or via npm script
49
+ pnpm build
50
+ ```
51
+
52
+ ## Deploy to node
53
+
54
+ ```bash
55
+ # Install the app
56
+ meroctl --node-name <NODE> app install \
57
+ --path build/service.wasm \
58
+ --context-id <CONTEXT_ID>
59
+
60
+ # Create a new context (runs @Init)
61
+ meroctl --node-name <NODE> context create \
62
+ --app-id <APP_ID>
63
+
64
+ # Call a mutation
65
+ meroctl --node-name <NODE> call \
66
+ --context-id <CONTEXT_ID> \
67
+ --method set \
68
+ --args '{"key":"hello","value":"world"}'
69
+
70
+ # Call a view
71
+ meroctl --node-name <NODE> call \
72
+ --context-id <CONTEXT_ID> \
73
+ --method get \
74
+ --args '{"key":"hello"}'
75
+ ```
76
+
77
+ ## End-to-end test with Merobox
78
+
79
+ Each example in the SDK ships a Merobox workflow for multi-node E2E testing:
80
+
81
+ ```bash
82
+ merobox bootstrap run examples/counter/workflows/counter-js.yml --log-level=trace
83
+ ```
84
+
85
+ ## Platform support
86
+
87
+ | Platform | Supported |
88
+ | ------------------ | ------------ |
89
+ | macOS (x64, arm64) | Yes |
90
+ | Linux (x64, arm64) | Yes |
91
+ | Windows (native) | No — use WSL |
92
+
93
+ ## Definition of done (before PR)
94
+
95
+ 1. `pnpm lint` passes
96
+ 2. `pnpm format:check` passes
97
+ 3. `pnpm test` passes
98
+ 4. Example app builds: `cd examples/counter && pnpm build`