@calimero-network/agent-skills 0.3.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 -28
- package/package.json +1 -1
- 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 +126 -31
- 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 -92
- package/skills/calimero-client-js/rules/camelcase-api.md +10 -7
- package/skills/calimero-client-js/rules/token-refresh.md +11 -11
- package/skills/calimero-client-py/SKILL.md +25 -13
- 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 +24 -19
- package/skills/calimero-desktop/references/sso-integration.md +2 -2
- 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 +50 -39
- 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 +9 -10
- package/skills/calimero-rust-sdk/rules/wasm-constraints.md +12 -10
- package/skills/calimero-sdk-js/SKILL.md +34 -26
- package/skills/calimero-sdk-js/references/build-pipeline.md +6 -6
- package/skills/calimero-sdk-js/references/collections.md +11 -11
- package/skills/calimero-sdk-js/references/events.md +7 -3
- package/skills/calimero-sdk-js/rules/crdt-only-state.md +18 -18
- package/skills/calimero-sdk-js/rules/no-console-log.md +6 -6
- package/skills/calimero-sdk-js/rules/view-decorator.md +6 -4
|
@@ -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,30 +1,30 @@
|
|
|
1
1
|
# calimero-desktop — Agent Instructions
|
|
2
2
|
|
|
3
|
-
You are helping a developer integrate their app frontend with **Calimero Desktop SSO** —
|
|
4
|
-
|
|
5
|
-
|
|
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
6
|
|
|
7
|
-
> **NOT this skill** if the developer just needs to authenticate manually via the login
|
|
8
|
-
>
|
|
9
|
-
>
|
|
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.
|
|
10
10
|
|
|
11
11
|
## What Desktop does
|
|
12
12
|
|
|
13
|
-
Calimero Desktop opens app frontends in a browser window and passes auth tokens via the
|
|
14
|
-
|
|
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.
|
|
15
15
|
|
|
16
16
|
## Hash parameters passed by Desktop
|
|
17
17
|
|
|
18
|
-
| Parameter
|
|
19
|
-
|
|
|
20
|
-
| `access_token`
|
|
21
|
-
| `refresh_token`
|
|
22
|
-
| `node_url`
|
|
23
|
-
| `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 |
|
|
24
24
|
|
|
25
25
|
## Example URL
|
|
26
26
|
|
|
27
|
-
```
|
|
27
|
+
```text
|
|
28
28
|
https://your-app.com/#access_token=eyJ...&refresh_token=eyJ...&node_url=http://localhost:2428&application_id=abc123
|
|
29
29
|
```
|
|
30
30
|
|
|
@@ -44,10 +44,15 @@ if (accessToken && nodeUrl) {
|
|
|
44
44
|
|
|
45
45
|
## Critical rule
|
|
46
46
|
|
|
47
|
-
Always fall back to manual login when hash params are absent. The app must work when
|
|
48
|
-
|
|
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
|
|
49
54
|
|
|
50
55
|
## References
|
|
51
56
|
|
|
52
|
-
See `references/` for full integration pattern and how Desktop discovers app URLs.
|
|
53
|
-
|
|
57
|
+
See `references/` for full integration pattern and how Desktop discovers app URLs. See `rules/` for
|
|
58
|
+
the fallback requirement.
|
|
@@ -72,8 +72,8 @@ document.addEventListener('DOMContentLoaded', bootstrap);
|
|
|
72
72
|
|
|
73
73
|
## How Desktop discovers your app's frontend URL
|
|
74
74
|
|
|
75
|
-
Desktop reads the `links.frontend` field from your app's `manifest.json` inside the
|
|
76
|
-
|
|
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:
|
|
77
77
|
|
|
78
78
|
```json
|
|
79
79
|
{
|
|
@@ -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
|
-
|
|
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.
|
|
@@ -1,54 +1,281 @@
|
|
|
1
1
|
# calimero-merobox — Agent Instructions
|
|
2
2
|
|
|
3
|
-
You are helping a developer set up a **local
|
|
3
|
+
You are helping a developer set up and test a **local Calimero network** using Merobox.
|
|
4
4
|
|
|
5
5
|
## What Merobox is
|
|
6
6
|
|
|
7
|
-
Merobox is a
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
7
|
+
Merobox is a Python CLI tool for running Calimero nodes in Docker containers. It handles:
|
|
8
|
+
|
|
9
|
+
- Starting/stopping nodes with `merobox run` / `merobox stop`
|
|
10
|
+
- App installation and method execution
|
|
11
|
+
- Identity and context management
|
|
12
|
+
- Automated multi-step test workflows via YAML (`merobox bootstrap run`)
|
|
13
|
+
- Multi-node orchestration for integration testing
|
|
12
14
|
|
|
13
15
|
## Install
|
|
14
16
|
|
|
15
17
|
```bash
|
|
16
|
-
#
|
|
18
|
+
# macOS
|
|
19
|
+
brew install merobox
|
|
20
|
+
|
|
21
|
+
# Ubuntu/Debian
|
|
22
|
+
curl -fsSL https://calimero-network.github.io/merobox/gpg.key \
|
|
23
|
+
| sudo tee /usr/share/keyrings/merobox.gpg > /dev/null
|
|
24
|
+
echo "deb [signed-by=/usr/share/keyrings/merobox.gpg] https://calimero-network.github.io/merobox stable main" \
|
|
25
|
+
| sudo tee /etc/apt/sources.list.d/merobox.list
|
|
26
|
+
sudo apt update && sudo apt install merobox
|
|
27
|
+
|
|
28
|
+
# pipx (any platform)
|
|
17
29
|
pipx install merobox
|
|
18
30
|
|
|
19
|
-
#
|
|
20
|
-
|
|
31
|
+
merobox --version # verify
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Requires Docker 20.10+ running.
|
|
35
|
+
|
|
36
|
+
## Node management
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Start a node
|
|
40
|
+
merobox run --name my-node
|
|
41
|
+
|
|
42
|
+
# Start with custom ports
|
|
43
|
+
merobox run --name my-node --server-port 2428 --swarm-port 2528
|
|
44
|
+
|
|
45
|
+
# List running nodes
|
|
46
|
+
merobox list
|
|
47
|
+
|
|
48
|
+
# Check node health
|
|
49
|
+
merobox health my-node
|
|
50
|
+
|
|
51
|
+
# View logs
|
|
52
|
+
merobox logs my-node
|
|
53
|
+
merobox logs my-node --follow # follow in real-time
|
|
21
54
|
|
|
22
|
-
#
|
|
55
|
+
# Stop a node
|
|
56
|
+
merobox stop my-node
|
|
57
|
+
|
|
58
|
+
# Delete all node data (destructive)
|
|
59
|
+
merobox nuke my-node
|
|
23
60
|
```
|
|
24
61
|
|
|
25
|
-
##
|
|
62
|
+
## App and context management
|
|
26
63
|
|
|
27
64
|
```bash
|
|
28
|
-
#
|
|
29
|
-
|
|
65
|
+
# Install a WASM app on a node
|
|
66
|
+
merobox install --node my-node --path ./app.wasm --dev
|
|
67
|
+
|
|
68
|
+
# List installed apps
|
|
69
|
+
merobox application list --node my-node
|
|
70
|
+
|
|
71
|
+
# Create a context
|
|
72
|
+
merobox context create --node my-node --application-id <app-id>
|
|
73
|
+
|
|
74
|
+
# List contexts
|
|
75
|
+
merobox context list --node my-node
|
|
76
|
+
|
|
77
|
+
# Call a method
|
|
78
|
+
merobox call my-node <context-id> <method> '{"key":"hello","value":"world"}'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Identity management
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
merobox identity generate --node my-node
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Blob storage
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
merobox blob upload --node my-node --file ./data.txt
|
|
91
|
+
merobox blob list-blobs --node my-node
|
|
92
|
+
merobox blob download --node my-node --blob-id <id> --output ./out.txt
|
|
93
|
+
merobox blob delete --node my-node --blob-id <id> --yes
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Workflow automation (bootstrap)
|
|
97
|
+
|
|
98
|
+
Workflows are the most powerful feature — YAML files that orchestrate multi-step scenarios.
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
merobox bootstrap run workflow.yml # execute
|
|
102
|
+
merobox bootstrap validate workflow.yml # validate only
|
|
103
|
+
merobox bootstrap create-sample # scaffold example
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Minimal workflow example
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
name: KV Store Test
|
|
110
|
+
steps:
|
|
111
|
+
- type: install_application
|
|
112
|
+
node: node-1
|
|
113
|
+
path: ./kv_store.wasm
|
|
114
|
+
outputs:
|
|
115
|
+
app_id: 'application_id'
|
|
116
|
+
|
|
117
|
+
- type: create_context
|
|
118
|
+
node: node-1
|
|
119
|
+
application_id: '{{app_id}}'
|
|
120
|
+
outputs:
|
|
121
|
+
ctx_id: 'context.context_id'
|
|
122
|
+
|
|
123
|
+
- type: create_identity
|
|
124
|
+
node: node-1
|
|
125
|
+
outputs:
|
|
126
|
+
pub: 'public_key'
|
|
127
|
+
|
|
128
|
+
- type: call
|
|
129
|
+
node: node-1
|
|
130
|
+
context_id: '{{ctx_id}}'
|
|
131
|
+
method: set
|
|
132
|
+
args:
|
|
133
|
+
key: 'hello'
|
|
134
|
+
value: 'world'
|
|
135
|
+
executor_public_key: '{{pub}}'
|
|
136
|
+
outputs:
|
|
137
|
+
result: 'output'
|
|
138
|
+
|
|
139
|
+
- type: assert
|
|
140
|
+
statements:
|
|
141
|
+
- 'is_set({{result}})'
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Available step types
|
|
145
|
+
|
|
146
|
+
| Step | What it does |
|
|
147
|
+
| --------------------- | ------------------------------------------------------- |
|
|
148
|
+
| `install_application` | Install WASM app, capture `application_id` |
|
|
149
|
+
| `create_context` | Create context, capture `context_id` and `seed` |
|
|
150
|
+
| `create_identity` | Create identity, capture `private_key` and `public_key` |
|
|
151
|
+
| `join_context` | Join a node to a context (targeted invitation) |
|
|
152
|
+
| `invite_open` | Create open invitation (anyone can join) |
|
|
153
|
+
| `join_open` | Join via open invitation |
|
|
154
|
+
| `call` | Execute app method, capture output |
|
|
155
|
+
| `wait` | Sleep N seconds |
|
|
156
|
+
| `repeat` | Loop with index variable |
|
|
157
|
+
| `assert` | Validate values (`is_set`, `contains`, `==`) |
|
|
158
|
+
| `json_assert` | JSON equality/subset checks |
|
|
159
|
+
| `upload_blob` | Upload file to blob storage, capture `blob_id` |
|
|
160
|
+
| `script` | Run a shell script |
|
|
161
|
+
| `fuzzy_test` | Randomized load test (30-60+ min) |
|
|
162
|
+
|
|
163
|
+
### Multi-node example
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
name: Two-Node Sync Test
|
|
167
|
+
steps:
|
|
168
|
+
- type: install_application
|
|
169
|
+
node: node-1
|
|
170
|
+
path: ./app.wasm
|
|
171
|
+
outputs:
|
|
172
|
+
app_id: 'application_id'
|
|
173
|
+
|
|
174
|
+
- type: create_context
|
|
175
|
+
node: node-1
|
|
176
|
+
application_id: '{{app_id}}'
|
|
177
|
+
outputs:
|
|
178
|
+
ctx: 'context.context_id'
|
|
179
|
+
|
|
180
|
+
- type: create_identity
|
|
181
|
+
node: node-1
|
|
182
|
+
outputs:
|
|
183
|
+
pub1: 'public_key'
|
|
184
|
+
|
|
185
|
+
- type: create_identity
|
|
186
|
+
node: node-2
|
|
187
|
+
outputs:
|
|
188
|
+
pub2: 'public_key'
|
|
189
|
+
|
|
190
|
+
# invite node-2 to join the context
|
|
191
|
+
- type: invite_open
|
|
192
|
+
node: node-1
|
|
193
|
+
context_id: '{{ctx}}'
|
|
194
|
+
granter_id: '{{pub1}}'
|
|
195
|
+
outputs:
|
|
196
|
+
invite: 'invitation'
|
|
197
|
+
|
|
198
|
+
- type: join_open
|
|
199
|
+
node: node-2
|
|
200
|
+
invitee_id: '{{pub2}}'
|
|
201
|
+
invitation: '{{invite}}'
|
|
202
|
+
|
|
203
|
+
- type: wait
|
|
204
|
+
seconds: 2 # allow sync
|
|
205
|
+
|
|
206
|
+
- type: call
|
|
207
|
+
node: node-1
|
|
208
|
+
context_id: '{{ctx}}'
|
|
209
|
+
method: set
|
|
210
|
+
args:
|
|
211
|
+
key: 'msg'
|
|
212
|
+
value: 'hello'
|
|
213
|
+
executor_public_key: '{{pub1}}'
|
|
214
|
+
|
|
215
|
+
- type: wait
|
|
216
|
+
seconds: 1
|
|
217
|
+
|
|
218
|
+
- type: call
|
|
219
|
+
node: node-2
|
|
220
|
+
context_id: '{{ctx}}'
|
|
221
|
+
method: get
|
|
222
|
+
args:
|
|
223
|
+
key: 'msg'
|
|
224
|
+
executor_public_key: '{{pub2}}'
|
|
225
|
+
outputs:
|
|
226
|
+
val: 'output'
|
|
227
|
+
|
|
228
|
+
- type: assert
|
|
229
|
+
statements:
|
|
230
|
+
- "contains({{val}}, 'hello')"
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Auth service (production-like setup)
|
|
234
|
+
|
|
235
|
+
```yaml
|
|
236
|
+
name: Workflow with Auth
|
|
237
|
+
auth_service: true # enables Traefik + auth middleware
|
|
238
|
+
|
|
30
239
|
nodes:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
240
|
+
count: 1
|
|
241
|
+
prefix: 'calimero-node'
|
|
242
|
+
image: 'ghcr.io/calimero-network/merod:edge'
|
|
243
|
+
|
|
244
|
+
steps:
|
|
245
|
+
- type: wait
|
|
246
|
+
seconds: 5
|
|
247
|
+
message: 'Waiting for auth service...'
|
|
248
|
+
```
|
|
36
249
|
|
|
37
|
-
|
|
38
|
-
merobox up --workflow workflow.yml
|
|
250
|
+
Node URL with auth: `http://node1.127.0.0.1.nip.io`
|
|
39
251
|
|
|
40
|
-
|
|
41
|
-
|
|
252
|
+
## Variable substitution
|
|
253
|
+
|
|
254
|
+
```yaml
|
|
255
|
+
{{variable_name}} # step output
|
|
256
|
+
{{env.MY_VAR}} # environment variable
|
|
257
|
+
{{iteration}} # loop index in repeat
|
|
258
|
+
{{random_int(1, 100)}} # random integer
|
|
259
|
+
{{random_string(8)}} # random string
|
|
260
|
+
{{uuid}} # UUID v4
|
|
261
|
+
{{timestamp}} # Unix timestamp
|
|
262
|
+
{{random_node}} # random node from list
|
|
42
263
|
```
|
|
43
264
|
|
|
44
265
|
## When to use Merobox vs meroctl
|
|
45
266
|
|
|
46
|
-
| Task
|
|
47
|
-
|
|
|
48
|
-
| Local multi-node dev and testing
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
267
|
+
| Task | Use |
|
|
268
|
+
| ---------------------------------------- | ------- |
|
|
269
|
+
| Local multi-node dev and testing | Merobox |
|
|
270
|
+
| CI pipeline with multi-step scenarios | Merobox |
|
|
271
|
+
| Quick single command against a live node | meroctl |
|
|
272
|
+
| Managing a production node | meroctl |
|
|
273
|
+
|
|
274
|
+
## Related skills
|
|
275
|
+
|
|
276
|
+
- **`calimero-merod`** — `merod` daemon setup (init, ports, health endpoint)
|
|
277
|
+
- **`calimero-meroctl`** — full `meroctl` CLI reference for scripting against a live node
|
|
278
|
+
- **`calimero-core`** — context/app model and namespace/group participation model
|
|
52
279
|
|
|
53
280
|
## References
|
|
54
281
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# CI Integration
|
|
2
2
|
|
|
3
|
-
Use Merobox in GitHub Actions or other CI systems to run integration tests against a real multi-node
|
|
3
|
+
Use Merobox in GitHub Actions or other CI systems to run integration tests against a real multi-node
|
|
4
|
+
network.
|
|
4
5
|
|
|
5
6
|
## GitHub Actions example
|
|
6
7
|
|
|
@@ -49,4 +50,4 @@ jobs:
|
|
|
49
50
|
## Reference workflow files
|
|
50
51
|
|
|
51
52
|
The Battleships repo has well-structured workflow examples:
|
|
52
|
-
https://github.com/calimero-network/battleships/tree/main/workflows
|
|
53
|
+
<https://github.com/calimero-network/battleships/tree/main/workflows>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Merobox Workflow Files
|
|
2
2
|
|
|
3
|
-
Workflow files define reusable network topologies. Commit them to your repo for repeatable local dev
|
|
3
|
+
Workflow files define reusable network topologies. Commit them to your repo for repeatable local dev
|
|
4
|
+
and CI environments.
|
|
4
5
|
|
|
5
6
|
## Minimal workflow
|
|
6
7
|
|
|
@@ -30,16 +31,16 @@ setup:
|
|
|
30
31
|
|
|
31
32
|
- step: create_context
|
|
32
33
|
node: node1
|
|
33
|
-
app_id:
|
|
34
|
+
app_id: '{{ install_app.app_id }}'
|
|
34
35
|
|
|
35
36
|
- step: invite_member
|
|
36
37
|
node: node1
|
|
37
|
-
context_id:
|
|
38
|
-
identity:
|
|
38
|
+
context_id: '{{ create_context.context_id }}'
|
|
39
|
+
identity: '{{ node2.identity }}'
|
|
39
40
|
|
|
40
41
|
- step: join_context
|
|
41
42
|
node: node2
|
|
42
|
-
invitation:
|
|
43
|
+
invitation: '{{ invite_member.invitation }}'
|
|
43
44
|
```
|
|
44
45
|
|
|
45
46
|
## Commands
|
|
@@ -64,6 +65,7 @@ merobox down --purge
|
|
|
64
65
|
## Accessing nodes after startup
|
|
65
66
|
|
|
66
67
|
Each node exposes its JSON-RPC at the configured port:
|
|
68
|
+
|
|
67
69
|
- `node1` → `http://localhost:2428`
|
|
68
70
|
- `node2` → `http://localhost:2429`
|
|
69
71
|
|