hyperstack-core 1.3.0 → 1.5.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/LICENSE +21 -21
- package/README.md +247 -63
- package/SKILL.md +679 -40
- package/adapters/openclaw.js +221 -221
- package/cli.js +631 -500
- package/examples/before-after.js +110 -110
- package/examples/openclaw-multiagent.js +214 -214
- package/index.js +19 -18
- package/package.json +2 -1
- package/src/client.js +320 -267
- package/src/parser.js +305 -0
- package/templates/openclaw-multiagent.json +98 -98
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 CascadeAI
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CascadeAI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,100 +1,232 @@
|
|
|
1
1
|
# hyperstack-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Memory Hub for AI agents. Typed graph memory with episodic/semantic/working APIs, decision replay, utility-weighted edges, git-style branching, and agent identity. The only memory layer where agents can verify what they know, trace why they know it, and coordinate without an LLM in the loop. $0 per operation at any scale.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
npm i hyperstack-core
|
|
7
7
|
npx hyperstack-core init openclaw-multiagent
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
+
---
|
|
11
|
+
|
|
10
12
|
## The Problem
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
AI agent setups use markdown files for coordination:
|
|
13
15
|
|
|
14
16
|
```
|
|
15
17
|
# DECISIONS.md (append-only)
|
|
16
18
|
- 2026-02-15: Use Clerk for auth (coder-agent)
|
|
17
|
-
- 2026-02-15: Deploy needs auth migration first (deploy-agent)
|
|
18
19
|
- 2026-02-16: Migration blocks production deploy (ops-agent)
|
|
19
20
|
```
|
|
20
21
|
|
|
21
|
-
Try answering: **"What
|
|
22
|
+
Try answering: **"What breaks if auth changes?"**
|
|
22
23
|
|
|
23
|
-
With markdown: `grep -r "
|
|
24
|
+
With markdown: `grep -r "auth" *.md` — manual, fragile, returns text blobs.
|
|
24
25
|
|
|
25
26
|
## The Solution
|
|
26
27
|
|
|
27
|
-
```
|
|
28
|
+
```javascript
|
|
28
29
|
import { HyperStackClient } from "hyperstack-core";
|
|
29
30
|
|
|
30
31
|
const hs = new HyperStackClient({ apiKey: "hs_..." });
|
|
31
32
|
|
|
32
|
-
//
|
|
33
|
+
// Store a decision with typed relations
|
|
33
34
|
await hs.decide({
|
|
34
35
|
slug: "use-clerk",
|
|
35
36
|
title: "Use Clerk for auth",
|
|
36
37
|
body: "Better DX, lower cost, native Next.js support",
|
|
37
|
-
decidedBy: "agent-coder",
|
|
38
38
|
affects: ["auth-api"],
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
//
|
|
41
|
+
// Store a blocker
|
|
42
42
|
await hs.store({
|
|
43
43
|
slug: "migration-23",
|
|
44
44
|
title: "Auth migration to Clerk",
|
|
45
45
|
cardType: "task",
|
|
46
|
-
links: [
|
|
47
|
-
{ target: "deploy-prod", relation: "blocks" },
|
|
48
|
-
{ target: "use-clerk", relation: "depends_on" },
|
|
49
|
-
],
|
|
46
|
+
links: [{ target: "deploy-prod", relation: "blocks" }],
|
|
50
47
|
});
|
|
51
48
|
|
|
52
|
-
//
|
|
49
|
+
// What blocks deploy?
|
|
53
50
|
const result = await hs.blockers("deploy-prod");
|
|
54
51
|
// → { blockers: [{ slug: "migration-23", title: "Auth migration to Clerk" }] }
|
|
52
|
+
|
|
53
|
+
// What breaks if auth changes?
|
|
54
|
+
const impact = await hs.impact("use-clerk");
|
|
55
|
+
// → [auth-api, deploy-prod, billing-v2]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Typed relations, not text blobs. `task→blocks→deploy` is queryable. A paragraph in DECISIONS.md is not.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## MCP — Works in Cursor, Claude Desktop, VS Code, Windsurf
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"hyperstack": {
|
|
68
|
+
"command": "npx",
|
|
69
|
+
"args": ["-y", "hyperstack-mcp"],
|
|
70
|
+
"env": {
|
|
71
|
+
"HYPERSTACK_API_KEY": "hs_your_key",
|
|
72
|
+
"HYPERSTACK_WORKSPACE": "my-project",
|
|
73
|
+
"HYPERSTACK_AGENT_SLUG": "cursor-agent"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
15 MCP tools: `hs_store`, `hs_search`, `hs_smart_search`, `hs_decide`, `hs_commit`, `hs_feedback`, `hs_blockers`, `hs_graph`, `hs_impact`, `hs_recommend`, `hs_fork`, `hs_diff`, `hs_merge`, `hs_discard`, `hs_identify`, `hs_profile`, `hs_prune`, `hs_ingest`, `hs_inbox`, `hs_stats`
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## The Memory Hub — Three Memory Surfaces
|
|
85
|
+
|
|
86
|
+
Same typed graph, three distinct APIs with different retention behaviour.
|
|
87
|
+
|
|
88
|
+
### Episodic Memory — what happened and when
|
|
89
|
+
```
|
|
90
|
+
GET /api/cards?workspace=X&memoryType=episodic
|
|
91
|
+
```
|
|
92
|
+
- Event traces, agent actions, session history
|
|
93
|
+
- 30-day soft decay curve (agent-used cards decay at half rate)
|
|
94
|
+
- Returns `decayScore`, `daysSinceCreated`, `isStale` per card
|
|
95
|
+
|
|
96
|
+
### Semantic Memory — facts that never age
|
|
97
|
+
```
|
|
98
|
+
GET /api/cards?workspace=X&memoryType=semantic
|
|
99
|
+
```
|
|
100
|
+
- Decisions, people, projects, workflows, preferences
|
|
101
|
+
- Permanent — no decay, no expiry
|
|
102
|
+
- Returns `confidence`, `truth_stratum`, `isVerified` per card
|
|
103
|
+
|
|
104
|
+
### Working Memory — TTL-based scratchpad
|
|
105
|
+
```
|
|
106
|
+
GET /api/cards?workspace=X&memoryType=working
|
|
107
|
+
GET /api/cards?workspace=X&memoryType=working&includeExpired=true
|
|
55
108
|
```
|
|
109
|
+
- Cards with TTL set — auto-hides expired by default
|
|
110
|
+
- Agent-used cards get 1.5x TTL extension
|
|
111
|
+
- Returns `expiresAt`, `isExpired`, `ttlExtended` per card
|
|
56
112
|
|
|
57
|
-
|
|
113
|
+
---
|
|
58
114
|
|
|
59
|
-
##
|
|
115
|
+
## Decision Replay
|
|
60
116
|
|
|
61
|
-
|
|
62
|
-
import { createOpenClawAdapter } from "hyperstack-core/adapters/openclaw";
|
|
117
|
+
Reconstruct exactly what an agent knew when a decision was made.
|
|
63
118
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
119
|
+
```javascript
|
|
120
|
+
// What did the agent know when "use-clerk" was decided?
|
|
121
|
+
const replay = await hs.graph({ from: "use-clerk", mode: "replay" });
|
|
67
122
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
123
|
+
// replay.narrative:
|
|
124
|
+
// "Decision: [Use Clerk for Auth] made at 2026-02-19T20:59:00Z"
|
|
125
|
+
// "Agent knew 1 of 2 connected cards at decision time."
|
|
126
|
+
// "⚠️ 1 card(s) were modified after the decision (potential hindsight): [blocker-clerk-migration]"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Use cases: compliance audits, agent debugging, post-mortems.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Utility-Weighted Edges
|
|
134
|
+
|
|
135
|
+
The graph gets smarter the more you use it. Report success/failure after every agent task.
|
|
136
|
+
|
|
137
|
+
```javascript
|
|
138
|
+
// Report which cards helped the agent succeed
|
|
139
|
+
await hs.feedback({
|
|
140
|
+
cardSlugs: ["use-clerk", "auth-api"],
|
|
141
|
+
outcome: "success",
|
|
142
|
+
taskId: "task-auth-refactor"
|
|
72
143
|
});
|
|
73
144
|
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
145
|
+
// Retrieve most useful cards first
|
|
146
|
+
GET /api/cards?workspace=X&sortBy=utility
|
|
147
|
+
|
|
148
|
+
// Graph traversal weighted by utility
|
|
149
|
+
GET /api/graph?from=auth-api&weightBy=utility
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Cards that consistently help agents succeed get promoted. Cards in failed tasks decay.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Git-Style Memory Branching
|
|
157
|
+
|
|
158
|
+
Experiment safely without corrupting live memory.
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
// Fork before an experiment
|
|
162
|
+
const branch = await hs.fork({ branchName: "try-new-routing" });
|
|
163
|
+
|
|
164
|
+
// Make changes in the branch
|
|
165
|
+
await hs.store({ slug: "new-approach", title: "...", ... });
|
|
166
|
+
|
|
167
|
+
// See what changed
|
|
168
|
+
await hs.diff({ branchWorkspaceId: branch.branchWorkspaceId });
|
|
169
|
+
|
|
170
|
+
// Merge if it worked
|
|
171
|
+
await hs.merge({ branchWorkspaceId: branch.branchWorkspaceId, strategy: "branch-wins" });
|
|
172
|
+
|
|
173
|
+
// Or discard if it didn't
|
|
174
|
+
await hs.discard({ branchWorkspaceId: branch.branchWorkspaceId });
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Requires Pro plan or above.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Agent Identity + Trust
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
// Register at session start (idempotent)
|
|
185
|
+
await hs.identify({ agentSlug: "research-agent" });
|
|
186
|
+
|
|
187
|
+
// All hs.store() calls auto-stamp sourceAgent
|
|
188
|
+
await hs.store({ slug: "finding-001", ... });
|
|
189
|
+
|
|
190
|
+
// Check trust score
|
|
191
|
+
const profile = await hs.profile({ agentSlug: "research-agent" });
|
|
192
|
+
// → { trustScore: 0.84, verifiedCards: 42, cardCount: 50 }
|
|
193
|
+
// Formula: (verifiedCards/total)×0.7 + min(cardCount/100,1.0)×0.3
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Trust & Provenance
|
|
199
|
+
|
|
200
|
+
Every card carries epistemic metadata.
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
// Store with provenance
|
|
204
|
+
await hs.store({
|
|
205
|
+
slug: "finding-latency",
|
|
206
|
+
body: "p99 latency ~200ms under load",
|
|
207
|
+
confidence: 0.6,
|
|
208
|
+
truthStratum: "hypothesis" // draft | hypothesis | confirmed
|
|
81
209
|
});
|
|
82
210
|
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
211
|
+
// After verification
|
|
212
|
+
await hs.store({
|
|
213
|
+
slug: "finding-latency",
|
|
214
|
+
confidence: 0.95,
|
|
215
|
+
truthStratum: "confirmed",
|
|
216
|
+
verifiedBy: "human:deeq"
|
|
217
|
+
// verifiedAt auto-set server-side
|
|
218
|
+
});
|
|
86
219
|
```
|
|
87
220
|
|
|
221
|
+
---
|
|
222
|
+
|
|
88
223
|
## CLI
|
|
89
224
|
|
|
90
225
|
```bash
|
|
91
|
-
#
|
|
92
|
-
npx hyperstack-core init openclaw-multiagent
|
|
93
|
-
|
|
94
|
-
# Store cards
|
|
226
|
+
# Store a card
|
|
95
227
|
npx hyperstack-core store --slug "use-clerk" --title "Use Clerk" --type decision
|
|
96
228
|
|
|
97
|
-
# Record
|
|
229
|
+
# Record a decision
|
|
98
230
|
npx hyperstack-core decide --slug "use-clerk" --title "Use Clerk" --rationale "Better DX"
|
|
99
231
|
|
|
100
232
|
# Check blockers
|
|
@@ -106,48 +238,100 @@ npx hyperstack-core graph auth-api --depth 2
|
|
|
106
238
|
# Search
|
|
107
239
|
npx hyperstack-core search "authentication setup"
|
|
108
240
|
|
|
109
|
-
# List
|
|
241
|
+
# List
|
|
110
242
|
npx hyperstack-core list
|
|
111
243
|
```
|
|
112
244
|
|
|
113
|
-
|
|
245
|
+
---
|
|
114
246
|
|
|
115
|
-
|
|
116
|
-
|--|---|---|---|
|
|
117
|
-
| "What blocks deploy?" | Exact: 2 typed blockers | Fuzzy: 17 similar tasks | Generic entities |
|
|
118
|
-
| Relations | `task→blocks→deploy` (typed) | Auto-extracted (hallucinated) | Generic graph |
|
|
119
|
-
| Cost per op | **$0** (deterministic) | ~$0.002 (LLM extraction) | ~$0.002 |
|
|
120
|
-
| Multi-agent | Built-in agent tagging | Shared userId issues | No agent awareness |
|
|
121
|
-
| Setup | `npm i` + 1 env var | Docker + config | Docker + Neo4j |
|
|
247
|
+
## Self-Hosted Docker
|
|
122
248
|
|
|
123
|
-
|
|
249
|
+
```bash
|
|
250
|
+
# With OpenAI embeddings
|
|
251
|
+
docker run -d -p 3000:3000 \
|
|
252
|
+
-e DATABASE_URL=postgresql://... \
|
|
253
|
+
-e JWT_SECRET=your-secret \
|
|
254
|
+
-e OPENAI_API_KEY=sk-... \
|
|
255
|
+
ghcr.io/deeqyaqub1-cmd/hyperstack:latest
|
|
256
|
+
|
|
257
|
+
# Fully local — Ollama embeddings
|
|
258
|
+
docker run -d -p 3000:3000 \
|
|
259
|
+
-e DATABASE_URL=postgresql://... \
|
|
260
|
+
-e JWT_SECRET=your-secret \
|
|
261
|
+
-e EMBEDDING_BASE_URL=http://host.docker.internal:11434 \
|
|
262
|
+
-e EMBEDDING_MODEL=nomic-embed-text \
|
|
263
|
+
ghcr.io/deeqyaqub1-cmd/hyperstack:latest
|
|
264
|
+
|
|
265
|
+
# Keyword only — no embeddings needed
|
|
266
|
+
docker run -d -p 3000:3000 \
|
|
267
|
+
-e DATABASE_URL=postgresql://... \
|
|
268
|
+
-e JWT_SECRET=your-secret \
|
|
269
|
+
ghcr.io/deeqyaqub1-cmd/hyperstack:latest
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Then set `HYPERSTACK_BASE_URL=http://localhost:3000` in your config.
|
|
273
|
+
|
|
274
|
+
Full guide: [SELF_HOSTING.md](./SELF_HOSTING.md)
|
|
275
|
+
|
|
276
|
+
---
|
|
124
277
|
|
|
125
|
-
##
|
|
278
|
+
## Python + LangGraph
|
|
126
279
|
|
|
127
|
-
|
|
280
|
+
```bash
|
|
281
|
+
pip install hyperstack-py
|
|
282
|
+
pip install hyperstack-langgraph
|
|
283
|
+
```
|
|
128
284
|
|
|
285
|
+
```python
|
|
286
|
+
from hyperstack import HyperStack
|
|
287
|
+
hs = HyperStack(api_key="hs_...", workspace="my-project")
|
|
288
|
+
hs.identify(agent_slug="my-agent")
|
|
289
|
+
branch = hs.fork(branch_name="experiment")
|
|
290
|
+
hs.merge(branch_workspace_id=branch["branchWorkspaceId"], strategy="branch-wins")
|
|
129
291
|
```
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
—depends_on→ [decision:use-clerk]
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
from hyperstack_langgraph import HyperStackMemory
|
|
295
|
+
memory = HyperStackMemory(api_key="hs_...", workspace="my-project")
|
|
135
296
|
```
|
|
136
297
|
|
|
137
|
-
|
|
138
|
-
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Why Not Mem0 / Zep / Engram?
|
|
139
301
|
|
|
140
|
-
|
|
302
|
+
| Feature | HyperStack | Mem0 | Zep | Engram |
|
|
303
|
+
|---|---|---|---|---|
|
|
304
|
+
| Typed directed relations | ✅ 10 types | ❌ LLM-extracted | ❌ | ❌ generic |
|
|
305
|
+
| Utility-weighted edges | ✅ | ❌ | ❌ | ❌ |
|
|
306
|
+
| Git-style branching | ✅ | ❌ | ❌ | ❌ |
|
|
307
|
+
| Agent identity + trust | ✅ | ❌ | ❌ | ❌ |
|
|
308
|
+
| Provenance layer | ✅ | ❌ | ❌ | ❌ |
|
|
309
|
+
| Time-travel | ✅ | ❌ | ❌ | ❌ |
|
|
310
|
+
| Decision replay | ✅ | ❌ | ❌ | ❌ |
|
|
311
|
+
| Memory Hub segmentation | ✅ | ❌ | ❌ | ❌ |
|
|
312
|
+
| Self-hosted Docker | ✅ 1 command | ✅ complex | ✅ | ✅ |
|
|
313
|
+
| Cross-tool MCP | ✅ Cursor+Claude | ❌ | ❌ | ❌ |
|
|
314
|
+
| Cost per retrieval | **$0** | ~$0.002 LLM | ~$0.002 LLM | usage-based |
|
|
315
|
+
|
|
316
|
+
Mem0 finds "similar" cards. HyperStack finds **exactly** what blocks task #42.
|
|
317
|
+
|
|
318
|
+
---
|
|
141
319
|
|
|
142
320
|
## Setup
|
|
143
321
|
|
|
144
322
|
1. Get a free API key: [cascadeai.dev/hyperstack](https://cascadeai.dev/hyperstack)
|
|
145
323
|
2. `export HYPERSTACK_API_KEY=hs_your_key`
|
|
146
324
|
3. `npm i hyperstack-core`
|
|
147
|
-
4. `npx hyperstack-core init openclaw-multiagent`
|
|
148
325
|
|
|
149
|
-
|
|
150
|
-
|
|
326
|
+
| Plan | Price | Cards | Features |
|
|
327
|
+
|------|-------|-------|---------|
|
|
328
|
+
| Free | $0 | 10 | Search only |
|
|
329
|
+
| Pro | $29/mo | 100 | All modes + branching + identity + Memory Hub |
|
|
330
|
+
| Team | $59/mo | 500 | All modes + webhooks + agent tokens |
|
|
331
|
+
| Business | $149/mo | 2,000 | All modes + SSO + 20 members |
|
|
332
|
+
| Self-hosted | $0 | Unlimited | Full feature parity |
|
|
333
|
+
|
|
334
|
+
---
|
|
151
335
|
|
|
152
336
|
## License
|
|
153
337
|
|