@calimero-network/agent-skills 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -17
- package/SKILL.md +31 -23
- package/package.json +2 -2
- package/scripts/install.js +3 -3
- package/scripts/test.js +6 -15
- package/skills/calimero-abi-codegen/SKILL.md +121 -22
- package/skills/calimero-abi-codegen/references/abi-format.md +3 -5
- package/skills/calimero-abi-codegen/references/generated-output.md +12 -4
- package/skills/calimero-abi-codegen/rules/schema-version.md +11 -4
- package/skills/calimero-abi-codegen/rules/unique-names.md +2 -6
- package/skills/calimero-client-js/SKILL.md +127 -22
- package/skills/calimero-client-js/references/auth.md +18 -10
- package/skills/calimero-client-js/references/rpc-calls.md +15 -21
- package/skills/calimero-client-js/references/sso.md +9 -9
- package/skills/calimero-client-js/references/websocket-events.md +73 -59
- package/skills/calimero-client-js/rules/camelcase-api.md +10 -7
- package/skills/calimero-client-js/rules/token-refresh.md +59 -21
- package/skills/calimero-client-py/SKILL.md +26 -10
- package/skills/calimero-client-py/references/api.md +41 -43
- package/skills/calimero-client-py/references/auth.md +7 -7
- package/skills/calimero-client-py/rules/async-usage.md +27 -31
- package/skills/calimero-client-py/rules/stable-node-name.md +7 -7
- package/skills/calimero-core/SKILL.md +135 -0
- package/skills/calimero-core/references/architecture.md +101 -0
- package/skills/calimero-core/references/jsonrpc-protocol.md +192 -0
- package/skills/calimero-core/references/namespaces-groups.md +94 -0
- package/skills/calimero-core/references/storage-types.md +118 -0
- package/skills/calimero-core/references/websocket-events.md +142 -0
- package/skills/calimero-core/rules/context-is-not-app.md +35 -0
- package/skills/calimero-core/rules/crdt-types-only.md +55 -0
- package/skills/calimero-desktop/SKILL.md +25 -14
- package/skills/calimero-desktop/references/sso-integration.md +49 -22
- package/skills/calimero-desktop/rules/sso-fallback.md +3 -2
- package/skills/calimero-merobox/SKILL.md +255 -28
- package/skills/calimero-merobox/references/ci-integration.md +3 -2
- package/skills/calimero-merobox/references/workflow-files.md +7 -5
- package/skills/calimero-merobox/rules/docker-required.md +7 -6
- package/skills/calimero-meroctl/SKILL.md +68 -0
- package/skills/calimero-meroctl/references/commands.md +177 -0
- package/skills/calimero-meroctl/references/scripting.md +80 -0
- package/skills/calimero-meroctl/rules/call-view-flag.md +28 -0
- package/skills/calimero-meroctl/rules/register-node-once.md +34 -0
- package/skills/calimero-merod/SKILL.md +49 -0
- package/skills/calimero-merod/references/health-endpoints.md +90 -0
- package/skills/calimero-merod/references/init-flags.md +84 -0
- package/skills/calimero-merod/rules/init-before-run.md +40 -0
- package/skills/calimero-merod/rules/port-assignments.md +33 -0
- package/skills/calimero-node/SKILL.md +52 -35
- package/skills/calimero-node/references/context-lifecycle.md +34 -17
- package/skills/calimero-node/references/meroctl-commands.md +89 -99
- package/skills/calimero-node/rules/app-vs-context.md +4 -4
- package/skills/calimero-registry/SKILL.md +110 -31
- package/skills/calimero-registry/references/bundle-and-push.md +99 -34
- package/skills/calimero-registry/references/manifest-format.md +56 -35
- package/skills/calimero-registry/references/mero-sign.md +10 -9
- package/skills/calimero-registry/rules/key-security.md +3 -2
- package/skills/calimero-registry/rules/sign-before-pack.md +5 -5
- package/skills/calimero-rust-sdk/SKILL.md +154 -44
- package/skills/calimero-rust-sdk/references/blob-api.md +119 -0
- package/skills/calimero-rust-sdk/references/event-handlers.md +122 -0
- package/skills/calimero-rust-sdk/references/events.md +2 -1
- package/skills/calimero-rust-sdk/references/examples.md +81 -29
- package/skills/calimero-rust-sdk/references/migrations.md +123 -0
- package/skills/calimero-rust-sdk/references/nested-crdts.md +113 -0
- package/skills/calimero-rust-sdk/references/private-storage.md +76 -34
- package/skills/calimero-rust-sdk/references/state-collections.md +106 -21
- package/skills/calimero-rust-sdk/references/user-and-frozen-storage.md +169 -0
- package/skills/calimero-rust-sdk/rules/app-macro-placement.md +5 -2
- package/skills/calimero-rust-sdk/rules/no-std-collections.md +5 -2
- package/skills/calimero-rust-sdk/rules/state-derives.md +45 -0
- package/skills/calimero-rust-sdk/rules/wasm-constraints.md +12 -10
- package/skills/calimero-sdk-js/SKILL.md +145 -0
- package/skills/calimero-sdk-js/references/build-pipeline.md +98 -0
- package/skills/calimero-sdk-js/references/collections.md +132 -0
- package/skills/calimero-sdk-js/references/events.md +63 -0
- package/skills/calimero-sdk-js/rules/crdt-only-state.md +47 -0
- package/skills/calimero-sdk-js/rules/no-console-log.md +38 -0
- package/skills/calimero-sdk-js/rules/view-decorator.md +48 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# CRDT Collections
|
|
2
|
+
|
|
3
|
+
All persistent state in a `calimero-sdk-js` app must use CRDT types from
|
|
4
|
+
`@calimero-network/calimero-sdk-js/collections`.
|
|
5
|
+
|
|
6
|
+
## Counter (G-Counter)
|
|
7
|
+
|
|
8
|
+
Distributed counting. The value is the sum across all contributing nodes.
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
import { Counter } from '@calimero-network/calimero-sdk-js/collections';
|
|
12
|
+
|
|
13
|
+
const counter = new Counter();
|
|
14
|
+
counter.increment(); // +1
|
|
15
|
+
counter.incrementBy(5n); // +5 (bigint)
|
|
16
|
+
const total = counter.value(); // bigint
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
> Counter values are always `bigint`. Do not compare with `=== 0` — use `=== 0n`.
|
|
20
|
+
|
|
21
|
+
## UnorderedMap\<K, V\>
|
|
22
|
+
|
|
23
|
+
Key-value store with Last-Write-Wins conflict resolution per key.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { UnorderedMap } from '@calimero-network/calimero-sdk-js/collections';
|
|
27
|
+
|
|
28
|
+
const map = new UnorderedMap<string, string>();
|
|
29
|
+
map.set('key', 'value');
|
|
30
|
+
const val = map.get('key'); // 'value' | undefined
|
|
31
|
+
const exists = map.has('key'); // boolean
|
|
32
|
+
map.remove('key');
|
|
33
|
+
const all = map.entries(); // [['key', 'value'], ...]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## UnorderedSet\<T\>
|
|
37
|
+
|
|
38
|
+
Set of unique values with Last-Write-Wins per element.
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { UnorderedSet } from '@calimero-network/calimero-sdk-js/collections';
|
|
42
|
+
|
|
43
|
+
const set = new UnorderedSet<string>();
|
|
44
|
+
set.add('item'); // true on first insert, false if already present
|
|
45
|
+
set.has('item'); // true
|
|
46
|
+
set.delete('item');
|
|
47
|
+
const items = set.toArray();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Vector\<T\>
|
|
51
|
+
|
|
52
|
+
Ordered list. Conflicts resolved by position.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { Vector } from '@calimero-network/calimero-sdk-js/collections';
|
|
56
|
+
|
|
57
|
+
const vec = new Vector<string>();
|
|
58
|
+
vec.push('first');
|
|
59
|
+
vec.push('second');
|
|
60
|
+
const item = vec.get(0); // 'first'
|
|
61
|
+
const last = vec.pop(); // 'second'
|
|
62
|
+
const len = vec.len(); // 1 (bigint)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## LwwRegister\<T\>
|
|
66
|
+
|
|
67
|
+
Holds a single value. Last write wins based on timestamp.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { LwwRegister } from '@calimero-network/calimero-sdk-js/collections';
|
|
71
|
+
|
|
72
|
+
const reg = new LwwRegister<string>();
|
|
73
|
+
reg.set('current value');
|
|
74
|
+
const current = reg.get(); // 'current value' | undefined
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## UserStorage
|
|
78
|
+
|
|
79
|
+
User-owned signed data. Writes are verified by the owner's signature.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { UserStorage } from '@calimero-network/calimero-sdk-js/collections';
|
|
83
|
+
|
|
84
|
+
const storage = new UserStorage<string>();
|
|
85
|
+
storage.set(executorId, 'my value');
|
|
86
|
+
const val = storage.get(executorId);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## FrozenStorage
|
|
90
|
+
|
|
91
|
+
Immutable, content-addressed storage. Write once; content never changes.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { FrozenStorage } from '@calimero-network/calimero-sdk-js/collections';
|
|
95
|
+
|
|
96
|
+
const frozen = new FrozenStorage<string>();
|
|
97
|
+
const id = frozen.store('immutable content');
|
|
98
|
+
const val = frozen.get(id);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Nested collections
|
|
102
|
+
|
|
103
|
+
Nested structures propagate changes automatically — no manual re-serialization needed.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// Map<projectId, Set<tags>> — just works
|
|
107
|
+
const projectTags = new UnorderedMap<string, UnorderedSet<string>>();
|
|
108
|
+
|
|
109
|
+
const tags = new UnorderedSet<string>();
|
|
110
|
+
tags.add('urgent');
|
|
111
|
+
projectTags.set('proj:123', tags);
|
|
112
|
+
|
|
113
|
+
// Modifying the inner set propagates automatically
|
|
114
|
+
projectTags.get('proj:123')?.add('high-priority');
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## State field initialization
|
|
118
|
+
|
|
119
|
+
CRDT fields must be initialized inline in the `@State` class:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
@State
|
|
123
|
+
export class AppState {
|
|
124
|
+
// ✅ Inline initialization
|
|
125
|
+
items: UnorderedMap<string, string> = new UnorderedMap();
|
|
126
|
+
count: Counter = new Counter();
|
|
127
|
+
|
|
128
|
+
// ❌ Never use plain JS types for state
|
|
129
|
+
// plainMap: Map<string, string> = new Map();
|
|
130
|
+
// array: string[] = [];
|
|
131
|
+
}
|
|
132
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Events
|
|
2
|
+
|
|
3
|
+
Events let your app push real-time notifications to all context members. They are emitted during
|
|
4
|
+
mutation methods and received by clients via WebSocket.
|
|
5
|
+
|
|
6
|
+
## Emitting events
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
import { emit, emitWithHandler } from '@calimero-network/calimero-sdk-js';
|
|
10
|
+
|
|
11
|
+
// Simple event — clients receive it and dispatch themselves
|
|
12
|
+
emit({ type: 'ItemAdded', key: 'foo', value: 'bar' });
|
|
13
|
+
|
|
14
|
+
// Event with a named handler — clients call `onItemAdded(event)` if defined
|
|
15
|
+
emitWithHandler({ type: 'ItemAdded', key: 'foo' }, 'onItemAdded');
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Events can only be emitted inside mutation methods (not `@View()` methods).
|
|
19
|
+
|
|
20
|
+
## Receiving events on the client
|
|
21
|
+
|
|
22
|
+
Clients use `WsSubscriptionsClient` from `@calimero-network/calimero-client`:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import {
|
|
26
|
+
WsSubscriptionsClient,
|
|
27
|
+
getAppEndpointKey,
|
|
28
|
+
getContextId,
|
|
29
|
+
} from '@calimero-network/calimero-client';
|
|
30
|
+
|
|
31
|
+
const ws = new WsSubscriptionsClient(getAppEndpointKey()!, '/ws');
|
|
32
|
+
await ws.connect();
|
|
33
|
+
ws.subscribe([getContextId()!]);
|
|
34
|
+
|
|
35
|
+
ws.addCallback((event) => {
|
|
36
|
+
if (event.type === 'ExecutionEvent') {
|
|
37
|
+
for (const e of event.data.events) {
|
|
38
|
+
// e.kind — matches the `type` field from emit()
|
|
39
|
+
// e.data — the rest of the emitted object
|
|
40
|
+
console.log(e.kind, e.data);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Event typing (recommended)
|
|
47
|
+
|
|
48
|
+
Define a discriminated union to type your events:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
type AppEvent =
|
|
52
|
+
| { type: 'ItemAdded'; key: string; value: string }
|
|
53
|
+
| { type: 'ItemRemoved'; key: string };
|
|
54
|
+
|
|
55
|
+
// Emit
|
|
56
|
+
emit({ type: 'ItemAdded', key: 'foo', value: 'bar' } satisfies AppEvent);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Important
|
|
60
|
+
|
|
61
|
+
- Events are best-effort — clients may miss them if disconnected
|
|
62
|
+
- Do not rely on events for state consistency — use RPC calls to query current state
|
|
63
|
+
- Emitting from a `@View()` method is not supported
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Rule: All @State fields must be CRDT types
|
|
2
|
+
|
|
3
|
+
Every field on a `@State` class must be a CRDT collection from
|
|
4
|
+
`@calimero-network/calimero-sdk-js/collections`. Plain JavaScript types are not persisted or
|
|
5
|
+
synchronized.
|
|
6
|
+
|
|
7
|
+
## WRONG — plain JS types in state:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
@State
|
|
11
|
+
export class AppState {
|
|
12
|
+
items: Map<string, string> = new Map(); // ✗ — not a CRDT, not synced
|
|
13
|
+
tags: string[] = []; // ✗ — not a CRDT, not synced
|
|
14
|
+
name: string = ''; // ✗ — use LwwRegister instead
|
|
15
|
+
count: number = 0; // ✗ — use Counter instead
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## CORRECT — CRDT types:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
@State
|
|
23
|
+
export class AppState {
|
|
24
|
+
items: UnorderedMap<string, string> = new UnorderedMap();
|
|
25
|
+
tags: UnorderedSet<string> = new UnorderedSet();
|
|
26
|
+
name: LwwRegister<string> = new LwwRegister();
|
|
27
|
+
count: Counter = new Counter();
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Why this matters
|
|
32
|
+
|
|
33
|
+
Plain JS types are not serialized to the node's CRDT storage. State that uses `Map`, `Set`, `Array`,
|
|
34
|
+
or primitives directly will be lost on the next call and will never sync to other nodes in the
|
|
35
|
+
context.
|
|
36
|
+
|
|
37
|
+
## Choosing the right CRDT
|
|
38
|
+
|
|
39
|
+
| Data shape | Use |
|
|
40
|
+
| ------------------------- | -------------------- |
|
|
41
|
+
| Counting (monotonic) | `Counter` |
|
|
42
|
+
| Key-value lookup | `UnorderedMap<K, V>` |
|
|
43
|
+
| Unique membership | `UnorderedSet<T>` |
|
|
44
|
+
| Ordered list | `Vector<T>` |
|
|
45
|
+
| Single value (overwrites) | `LwwRegister<T>` |
|
|
46
|
+
| User-owned signed data | `UserStorage<T>` |
|
|
47
|
+
| Immutable content | `FrozenStorage<T>` |
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Rule: Use env.log() — not console.log()
|
|
2
|
+
|
|
3
|
+
`console` is not available in the QuickJS/WASM runtime. Calling `console.log()` will throw a runtime
|
|
4
|
+
error. Use `env.log()` from the SDK's env module instead.
|
|
5
|
+
|
|
6
|
+
## WRONG:
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
console.log('Processing item:', key); // ✗ — throws at runtime
|
|
10
|
+
console.error('Something went wrong'); // ✗ — throws at runtime
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## CORRECT:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import * as env from '@calimero-network/calimero-sdk-js/env';
|
|
17
|
+
|
|
18
|
+
env.log(`Processing item: ${key}`); // ✓ — output appears in node logs
|
|
19
|
+
env.log(`Error: ${error}`); // ✓
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## env.log() format
|
|
23
|
+
|
|
24
|
+
`env.log()` accepts a single string. Use template literals for dynamic values:
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
env.log(`set called: key=${key}, value=${value}`);
|
|
28
|
+
env.log(`counter is now ${this.count.value()}`);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Other env utilities
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import * as env from '@calimero-network/calimero-sdk-js/env';
|
|
35
|
+
|
|
36
|
+
// Get the calling executor's public key (Uint8Array, 32 bytes)
|
|
37
|
+
const executorId = env.executorId();
|
|
38
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Rule: @View() is required on read-only methods
|
|
2
|
+
|
|
3
|
+
Every method that does not modify state must be decorated with `@View()`. Without it, the runtime
|
|
4
|
+
will persist state after every call — even when nothing changed — causing unnecessary storage writes
|
|
5
|
+
and cross-node syncs.
|
|
6
|
+
|
|
7
|
+
## WRONG — read-only method without @View():
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
@Logic(AppState)
|
|
11
|
+
export class AppLogic extends AppState {
|
|
12
|
+
// ✗ — no @View(), triggers persistence on every call
|
|
13
|
+
getItem(key: string): string | null {
|
|
14
|
+
return this.items.get(key) ?? null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ✗ — returning a count without @View()
|
|
18
|
+
getCount(): bigint {
|
|
19
|
+
return this.count.value();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## CORRECT:
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
@Logic(AppState)
|
|
28
|
+
export class AppLogic extends AppState {
|
|
29
|
+
@View()
|
|
30
|
+
getItem(key: string): string | null {
|
|
31
|
+
return this.items.get(key) ?? null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@View()
|
|
35
|
+
getCount(): bigint {
|
|
36
|
+
return this.count.value();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## How to tell if a method needs @View()
|
|
42
|
+
|
|
43
|
+
A method is read-only if it:
|
|
44
|
+
|
|
45
|
+
- Does not call any mutation methods on CRDT fields (`set`, `insert`, `add`, `push`, `increment`,
|
|
46
|
+
etc.)
|
|
47
|
+
- Does not call `emit()` or `emitWithHandler()`
|
|
48
|
+
- Only reads values and returns results
|