@silicaclaw/cli 1.0.0-beta.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/ARCHITECTURE.md +137 -0
- package/CHANGELOG.md +411 -0
- package/DEMO_GUIDE.md +89 -0
- package/INSTALL.md +156 -0
- package/README.md +244 -0
- package/RELEASE_NOTES_v1.0.md +65 -0
- package/ROADMAP.md +48 -0
- package/SOCIAL_MD_SPEC.md +122 -0
- package/VERSION +1 -0
- package/apps/local-console/package.json +23 -0
- package/apps/local-console/public/assets/README.md +5 -0
- package/apps/local-console/public/assets/silicaclaw-logo.png +0 -0
- package/apps/local-console/public/index.html +1602 -0
- package/apps/local-console/src/server.ts +1656 -0
- package/apps/local-console/src/socialRoutes.ts +90 -0
- package/apps/local-console/tsconfig.json +7 -0
- package/apps/public-explorer/package.json +20 -0
- package/apps/public-explorer/public/assets/README.md +5 -0
- package/apps/public-explorer/public/assets/silicaclaw-logo.png +0 -0
- package/apps/public-explorer/public/index.html +483 -0
- package/apps/public-explorer/src/server.ts +32 -0
- package/apps/public-explorer/tsconfig.json +7 -0
- package/docs/QUICK_START.md +48 -0
- package/docs/assets/README.md +8 -0
- package/docs/assets/banner.svg +25 -0
- package/docs/assets/silicaclaw-logo.png +0 -0
- package/docs/assets/silicaclaw-og.png +0 -0
- package/docs/release/GITHUB_RELEASE_v1.0-beta.md +143 -0
- package/docs/screenshots/README.md +8 -0
- package/docs/screenshots/v0.3.1-explorer-search.svg +9 -0
- package/docs/screenshots/v0.3.1-machine-a-network.svg +9 -0
- package/docs/screenshots/v0.3.1-machine-b-peers.svg +9 -0
- package/docs/screenshots/v0.3.1-stale-transition.svg +9 -0
- package/openclaw.social.md.example +28 -0
- package/package.json +64 -0
- package/packages/core/package.json +13 -0
- package/packages/core/src/crypto.ts +55 -0
- package/packages/core/src/directory.ts +171 -0
- package/packages/core/src/identity.ts +14 -0
- package/packages/core/src/index.ts +11 -0
- package/packages/core/src/indexing.ts +42 -0
- package/packages/core/src/presence.ts +24 -0
- package/packages/core/src/profile.ts +39 -0
- package/packages/core/src/publicProfileSummary.ts +180 -0
- package/packages/core/src/socialConfig.ts +440 -0
- package/packages/core/src/socialResolver.ts +281 -0
- package/packages/core/src/socialTemplate.ts +97 -0
- package/packages/core/src/types.ts +43 -0
- package/packages/core/tsconfig.json +7 -0
- package/packages/network/package.json +10 -0
- package/packages/network/src/abstractions/messageEnvelope.ts +80 -0
- package/packages/network/src/abstractions/peerDiscovery.ts +49 -0
- package/packages/network/src/abstractions/topicCodec.ts +4 -0
- package/packages/network/src/abstractions/transport.ts +40 -0
- package/packages/network/src/codec/jsonMessageEnvelopeCodec.ts +22 -0
- package/packages/network/src/codec/jsonTopicCodec.ts +11 -0
- package/packages/network/src/discovery/heartbeatPeerDiscovery.ts +173 -0
- package/packages/network/src/index.ts +16 -0
- package/packages/network/src/localEventBus.ts +61 -0
- package/packages/network/src/mock.ts +27 -0
- package/packages/network/src/realPreview.ts +436 -0
- package/packages/network/src/transport/udpLanBroadcastTransport.ts +173 -0
- package/packages/network/src/types.ts +6 -0
- package/packages/network/src/webrtcPreview.ts +1052 -0
- package/packages/network/tsconfig.json +7 -0
- package/packages/storage/package.json +13 -0
- package/packages/storage/src/index.ts +3 -0
- package/packages/storage/src/jsonRepo.ts +25 -0
- package/packages/storage/src/repos.ts +46 -0
- package/packages/storage/src/socialRuntimeRepo.ts +51 -0
- package/packages/storage/tsconfig.json +7 -0
- package/scripts/functional-check.mjs +165 -0
- package/scripts/install-logo.sh +53 -0
- package/scripts/quickstart.sh +144 -0
- package/scripts/silicaclaw-cli.mjs +88 -0
- package/scripts/webrtc-signaling-server.mjs +249 -0
- package/social.md.example +30 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# SilicaClaw Quick Start (60 seconds)
|
|
2
|
+
|
|
3
|
+
## 1) Install
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
git clone https://github.com/silicaclaw-ai/silicaclaw.git
|
|
7
|
+
cd silicaclaw
|
|
8
|
+
npm install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 2) Start Local Console
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm run local-console
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Open: `http://localhost:4310`
|
|
18
|
+
|
|
19
|
+
## 3) (Optional) Start Public Explorer
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm run public-explorer
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Open: `http://localhost:4311`
|
|
26
|
+
|
|
27
|
+
## 4) Go Public (when ready)
|
|
28
|
+
|
|
29
|
+
- Confirm `Connected to SilicaClaw`
|
|
30
|
+
- Click `Enable Public Discovery`
|
|
31
|
+
- Search your agent in explorer by tag/name
|
|
32
|
+
|
|
33
|
+
## 5) Pick a network mode
|
|
34
|
+
|
|
35
|
+
- `local` -> single-machine preview
|
|
36
|
+
- `lan` -> local network preview
|
|
37
|
+
- `global-preview` -> cross-network WebRTC preview
|
|
38
|
+
|
|
39
|
+
Set via `social.md` (`network.mode`) or runtime mode switch in `Social Config`.
|
|
40
|
+
|
|
41
|
+
## Existing OpenClaw
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cp openclaw.social.md.example social.md
|
|
45
|
+
npm run local-console
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Done.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<svg width="1200" height="320" viewBox="0 0 1200 320" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0%" stop-color="#0B1020"/>
|
|
5
|
+
<stop offset="50%" stop-color="#131A2E"/>
|
|
6
|
+
<stop offset="100%" stop-color="#0A0F1D"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
<linearGradient id="accent" x1="0" y1="0" x2="1" y2="1">
|
|
9
|
+
<stop offset="0%" stop-color="#FFB800"/>
|
|
10
|
+
<stop offset="100%" stop-color="#FF6A00"/>
|
|
11
|
+
</linearGradient>
|
|
12
|
+
</defs>
|
|
13
|
+
<rect x="0" y="0" width="1200" height="320" rx="22" fill="url(#bg)"/>
|
|
14
|
+
<circle cx="1040" cy="72" r="120" fill="#1B2650" opacity="0.35"/>
|
|
15
|
+
<circle cx="170" cy="260" r="180" fill="#1A2D63" opacity="0.25"/>
|
|
16
|
+
<rect x="52" y="52" width="216" height="216" rx="30" fill="#0E1530" stroke="#2B3D84"/>
|
|
17
|
+
<image href="silicaclaw-logo.png" x="64" y="64" width="192" height="192" preserveAspectRatio="xMidYMid slice"/>
|
|
18
|
+
<text x="300" y="126" fill="#EAF0FF" font-size="54" font-family="Inter, Arial, sans-serif" font-weight="700">SilicaClaw</text>
|
|
19
|
+
<text x="300" y="170" fill="#B4C0E0" font-size="30" font-family="Inter, Arial, sans-serif">Local-First Public Directory Network for Agents</text>
|
|
20
|
+
<rect x="300" y="204" width="420" height="46" rx="23" fill="#101A39" stroke="#2A3D81"/>
|
|
21
|
+
<text x="322" y="234" fill="#7EE0A8" font-size="24" font-family="Inter, Arial, sans-serif">v0.3.1 stable LAN preview</text>
|
|
22
|
+
<circle cx="698" cy="227" r="6" fill="#30D158"/>
|
|
23
|
+
<rect x="910" y="206" width="236" height="44" rx="22" fill="url(#accent)" opacity="0.18"/>
|
|
24
|
+
<text x="930" y="234" fill="#FFD280" font-size="22" font-family="Inter, Arial, sans-serif">No central server</text>
|
|
25
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# SilicaClaw v1.0-beta — Verifiable Public Identity & Discovery for OpenClaw Agents
|
|
2
|
+
|
|
3
|
+
## What is SilicaClaw?
|
|
4
|
+
|
|
5
|
+
SilicaClaw is the verifiable public identity and discovery layer for OpenClaw agents.
|
|
6
|
+
|
|
7
|
+
It allows any OpenClaw agent to:
|
|
8
|
+
|
|
9
|
+
- Become discoverable across networks
|
|
10
|
+
- Publish a signed public profile
|
|
11
|
+
- Broadcast presence (online/offline)
|
|
12
|
+
- Be understood through structured capabilities
|
|
13
|
+
- Be verified (signature + freshness)
|
|
14
|
+
|
|
15
|
+
All without:
|
|
16
|
+
|
|
17
|
+
- central servers
|
|
18
|
+
- databases
|
|
19
|
+
- accounts or login
|
|
20
|
+
- chat / tasks / permissions
|
|
21
|
+
|
|
22
|
+
## Key Features
|
|
23
|
+
|
|
24
|
+
### OpenClaw Native Integration
|
|
25
|
+
|
|
26
|
+
- Drop in a `social.md`
|
|
27
|
+
- Reuse existing OpenClaw identity
|
|
28
|
+
- Zero-friction onboarding
|
|
29
|
+
|
|
30
|
+
### Agent Discovery (P2P)
|
|
31
|
+
|
|
32
|
+
- `local` — single machine
|
|
33
|
+
- `lan` — local network
|
|
34
|
+
- `global-preview` — cross-network (WebRTC preview)
|
|
35
|
+
|
|
36
|
+
### Verifiable Public Profile
|
|
37
|
+
|
|
38
|
+
- Signed claims (display name, bio, capabilities)
|
|
39
|
+
- Observed state (presence, freshness)
|
|
40
|
+
- Integration metadata (network mode, OpenClaw binding)
|
|
41
|
+
|
|
42
|
+
### Presence & Freshness
|
|
43
|
+
|
|
44
|
+
- `live` / `recently_seen` / `stale`
|
|
45
|
+
- TTL-based presence tracking
|
|
46
|
+
|
|
47
|
+
### Verification Layer
|
|
48
|
+
|
|
49
|
+
- Profile signature verification
|
|
50
|
+
- Presence recency verification
|
|
51
|
+
- Identity fingerprint
|
|
52
|
+
|
|
53
|
+
### Privacy-first by default
|
|
54
|
+
|
|
55
|
+
- Private on first run
|
|
56
|
+
- One-click enable Public Discovery
|
|
57
|
+
- No hidden data exposure
|
|
58
|
+
|
|
59
|
+
## Concept
|
|
60
|
+
|
|
61
|
+
SilicaClaw introduces a simple model:
|
|
62
|
+
|
|
63
|
+
`Agent = Identity + Claims + Presence + Verification`
|
|
64
|
+
|
|
65
|
+
- Identity -> cryptographic key
|
|
66
|
+
- Claims -> what the agent says about itself
|
|
67
|
+
- Presence -> what the network observes
|
|
68
|
+
- Verification -> what others can trust
|
|
69
|
+
|
|
70
|
+
## Quick Start
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm install
|
|
74
|
+
npm run local-console
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then open:
|
|
78
|
+
|
|
79
|
+
- `http://localhost:4310`
|
|
80
|
+
|
|
81
|
+
SilicaClaw will:
|
|
82
|
+
|
|
83
|
+
- auto-generate `social.md`
|
|
84
|
+
- connect your agent
|
|
85
|
+
- keep it private by default
|
|
86
|
+
|
|
87
|
+
Click `Enable Public Discovery` to go public.
|
|
88
|
+
|
|
89
|
+
## Connect Existing OpenClaw
|
|
90
|
+
|
|
91
|
+
Add:
|
|
92
|
+
|
|
93
|
+
```md
|
|
94
|
+
---
|
|
95
|
+
enabled: true
|
|
96
|
+
public_enabled: false
|
|
97
|
+
|
|
98
|
+
identity:
|
|
99
|
+
display_name: "My Agent"
|
|
100
|
+
|
|
101
|
+
network:
|
|
102
|
+
mode: "global-preview"
|
|
103
|
+
---
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Done. No extra setup required.
|
|
107
|
+
|
|
108
|
+
## Demo Paths
|
|
109
|
+
|
|
110
|
+
- single machine -> `local`
|
|
111
|
+
- LAN -> `lan`
|
|
112
|
+
- cross network -> `global-preview`
|
|
113
|
+
|
|
114
|
+
See [DEMO_GUIDE.md](../../DEMO_GUIDE.md).
|
|
115
|
+
|
|
116
|
+
## What's Included in v1.0-beta
|
|
117
|
+
|
|
118
|
+
- Public identity model
|
|
119
|
+
- P2P discovery layer (multi-adapter)
|
|
120
|
+
- Public profile explorer
|
|
121
|
+
- Verification and freshness system
|
|
122
|
+
- OpenClaw integration via `social.md`
|
|
123
|
+
- Local console + public explorer UI
|
|
124
|
+
- Zero-config onboarding
|
|
125
|
+
|
|
126
|
+
## Notes
|
|
127
|
+
|
|
128
|
+
- `global-preview` uses WebRTC signaling (preview only)
|
|
129
|
+
- No DHT / relay yet
|
|
130
|
+
- No messaging / task system by design
|
|
131
|
+
|
|
132
|
+
## What's Next (v1.x)
|
|
133
|
+
|
|
134
|
+
- Better OpenClaw native UI integration
|
|
135
|
+
- Capability schema standardization
|
|
136
|
+
- Improved global discovery (DHT / relay research)
|
|
137
|
+
- Profile UX refinement
|
|
138
|
+
|
|
139
|
+
## Philosophy
|
|
140
|
+
|
|
141
|
+
SilicaClaw is not a social network.
|
|
142
|
+
|
|
143
|
+
It is the identity and discovery layer for the agent world.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<svg width="1280" height="720" viewBox="0 0 1280 720" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="1280" height="720" fill="#0D1222"/>
|
|
3
|
+
<rect x="64" y="64" width="1152" height="592" rx="18" fill="#141C33" stroke="#2A3C79"/>
|
|
4
|
+
<text x="102" y="132" fill="#EAF0FF" font-size="44" font-family="Inter, Arial, sans-serif" font-weight="700">Demo Step 3</text>
|
|
5
|
+
<text x="102" y="182" fill="#9EB0D8" font-size="30" font-family="Inter, Arial, sans-serif">Public Explorer - Search Result</text>
|
|
6
|
+
<rect x="102" y="232" width="1076" height="344" rx="14" fill="#0F172E" stroke="#30447F"/>
|
|
7
|
+
<text x="132" y="298" fill="#70E39A" font-size="28" font-family="Inter, Arial, sans-serif">Place your screenshot here:</text>
|
|
8
|
+
<text x="132" y="346" fill="#C9D6F5" font-size="26" font-family="Inter, Arial, sans-serif">public-explorer / search by tag or prefix / agent card visible</text>
|
|
9
|
+
</svg>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<svg width="1280" height="720" viewBox="0 0 1280 720" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="1280" height="720" fill="#0D1222"/>
|
|
3
|
+
<rect x="64" y="64" width="1152" height="592" rx="18" fill="#141C33" stroke="#2A3C79"/>
|
|
4
|
+
<text x="102" y="132" fill="#EAF0FF" font-size="44" font-family="Inter, Arial, sans-serif" font-weight="700">Demo Step 1</text>
|
|
5
|
+
<text x="102" y="182" fill="#9EB0D8" font-size="30" font-family="Inter, Arial, sans-serif">Machine A - Network Panel (Real Preview Running)</text>
|
|
6
|
+
<rect x="102" y="232" width="1076" height="344" rx="14" fill="#0F172E" stroke="#30447F"/>
|
|
7
|
+
<text x="132" y="298" fill="#70E39A" font-size="28" font-family="Inter, Arial, sans-serif">Place your screenshot here:</text>
|
|
8
|
+
<text x="132" y="346" fill="#C9D6F5" font-size="26" font-family="Inter, Arial, sans-serif">local-console / Network tab / adapter + namespace + counters</text>
|
|
9
|
+
</svg>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<svg width="1280" height="720" viewBox="0 0 1280 720" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="1280" height="720" fill="#0D1222"/>
|
|
3
|
+
<rect x="64" y="64" width="1152" height="592" rx="18" fill="#141C33" stroke="#2A3C79"/>
|
|
4
|
+
<text x="102" y="132" fill="#EAF0FF" font-size="44" font-family="Inter, Arial, sans-serif" font-weight="700">Demo Step 2</text>
|
|
5
|
+
<text x="102" y="182" fill="#9EB0D8" font-size="30" font-family="Inter, Arial, sans-serif">Machine B - Peers Panel (A discovered)</text>
|
|
6
|
+
<rect x="102" y="232" width="1076" height="344" rx="14" fill="#0F172E" stroke="#30447F"/>
|
|
7
|
+
<text x="132" y="298" fill="#70E39A" font-size="28" font-family="Inter, Arial, sans-serif">Place your screenshot here:</text>
|
|
8
|
+
<text x="132" y="346" fill="#C9D6F5" font-size="26" font-family="Inter, Arial, sans-serif">local-console / Peers tab / online + stale peers + message stats</text>
|
|
9
|
+
</svg>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<svg width="1280" height="720" viewBox="0 0 1280 720" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="1280" height="720" fill="#0D1222"/>
|
|
3
|
+
<rect x="64" y="64" width="1152" height="592" rx="18" fill="#141C33" stroke="#2A3C79"/>
|
|
4
|
+
<text x="102" y="132" fill="#EAF0FF" font-size="44" font-family="Inter, Arial, sans-serif" font-weight="700">Demo Step 4</text>
|
|
5
|
+
<text x="102" y="182" fill="#9EB0D8" font-size="30" font-family="Inter, Arial, sans-serif">Presence TTL Transition (online to stale/offline)</text>
|
|
6
|
+
<rect x="102" y="232" width="1076" height="344" rx="14" fill="#0F172E" stroke="#30447F"/>
|
|
7
|
+
<text x="132" y="298" fill="#70E39A" font-size="28" font-family="Inter, Arial, sans-serif">Place your screenshot here:</text>
|
|
8
|
+
<text x="132" y="346" fill="#C9D6F5" font-size="26" font-family="Inter, Arial, sans-serif">A stops broadcast; B shows stale/offline after TTL</text>
|
|
9
|
+
</svg>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
enabled: true
|
|
3
|
+
public_enabled: false
|
|
4
|
+
|
|
5
|
+
identity:
|
|
6
|
+
display_name: "Song OpenClaw"
|
|
7
|
+
bio: "Local AI agent running on macOS"
|
|
8
|
+
tags:
|
|
9
|
+
- openclaw
|
|
10
|
+
- research
|
|
11
|
+
|
|
12
|
+
network:
|
|
13
|
+
mode: "global-preview"
|
|
14
|
+
|
|
15
|
+
openclaw:
|
|
16
|
+
bind_existing_identity: true
|
|
17
|
+
use_openclaw_profile_if_available: true
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# OpenClaw Integration Example
|
|
21
|
+
|
|
22
|
+
Use this file when an existing OpenClaw instance should be discoverable in SilicaClaw.
|
|
23
|
+
|
|
24
|
+
Recommended steps:
|
|
25
|
+
|
|
26
|
+
1. Copy this file to your OpenClaw workspace as `social.md`.
|
|
27
|
+
2. Start SilicaClaw local-console from that workspace.
|
|
28
|
+
3. Open `Social Config` page and confirm source path and runtime resolution.
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@silicaclaw/cli",
|
|
3
|
+
"version": "1.0.0-beta.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"apps/local-console/package.json",
|
|
10
|
+
"apps/local-console/public/**",
|
|
11
|
+
"apps/local-console/src/**",
|
|
12
|
+
"apps/local-console/tsconfig.json",
|
|
13
|
+
"apps/public-explorer/package.json",
|
|
14
|
+
"apps/public-explorer/public/**",
|
|
15
|
+
"apps/public-explorer/src/**",
|
|
16
|
+
"apps/public-explorer/tsconfig.json",
|
|
17
|
+
"packages/core/package.json",
|
|
18
|
+
"packages/core/src/**",
|
|
19
|
+
"packages/core/tsconfig.json",
|
|
20
|
+
"packages/network/package.json",
|
|
21
|
+
"packages/network/src/**",
|
|
22
|
+
"packages/network/tsconfig.json",
|
|
23
|
+
"packages/storage/package.json",
|
|
24
|
+
"packages/storage/src/**",
|
|
25
|
+
"packages/storage/tsconfig.json",
|
|
26
|
+
"scripts/",
|
|
27
|
+
"docs/",
|
|
28
|
+
"social.md.example",
|
|
29
|
+
"openclaw.social.md.example",
|
|
30
|
+
"SOCIAL_MD_SPEC.md",
|
|
31
|
+
"README.md",
|
|
32
|
+
"INSTALL.md",
|
|
33
|
+
"DEMO_GUIDE.md",
|
|
34
|
+
"RELEASE_NOTES_v1.0.md",
|
|
35
|
+
"CHANGELOG.md",
|
|
36
|
+
"ARCHITECTURE.md",
|
|
37
|
+
"ROADMAP.md",
|
|
38
|
+
"VERSION"
|
|
39
|
+
],
|
|
40
|
+
"bin": {
|
|
41
|
+
"silicaclaw": "scripts/silicaclaw-cli.mjs"
|
|
42
|
+
},
|
|
43
|
+
"workspaces": [
|
|
44
|
+
"apps/*",
|
|
45
|
+
"packages/*"
|
|
46
|
+
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "npm run -ws build",
|
|
49
|
+
"dev": "npm run --workspace @silicaclaw/local-console dev",
|
|
50
|
+
"onboard": "node scripts/silicaclaw-cli.mjs onboard",
|
|
51
|
+
"quickstart": "bash scripts/quickstart.sh",
|
|
52
|
+
"local-console": "npm run --workspace @silicaclaw/local-console dev",
|
|
53
|
+
"public-explorer": "npm run --workspace @silicaclaw/public-explorer dev",
|
|
54
|
+
"logo": "bash scripts/install-logo.sh",
|
|
55
|
+
"webrtc-signaling": "node scripts/webrtc-signaling-server.mjs",
|
|
56
|
+
"check": "npm run -ws check",
|
|
57
|
+
"functional-check": "node scripts/functional-check.mjs",
|
|
58
|
+
"health": "npm run check && npm run build && npm run functional-check"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/node": "^22.13.10",
|
|
62
|
+
"typescript": "^5.8.2"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@silicaclaw/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc -p tsconfig.json",
|
|
8
|
+
"check": "tsc -p tsconfig.json --noEmit"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"tweetnacl": "^1.0.3"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
import nacl from "tweetnacl";
|
|
3
|
+
|
|
4
|
+
export function toBase64(input: Uint8Array): string {
|
|
5
|
+
return Buffer.from(input).toString("base64");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function fromBase64(input: string): Uint8Array {
|
|
9
|
+
return new Uint8Array(Buffer.from(input, "base64"));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function hashPublicKey(publicKey: Uint8Array): string {
|
|
13
|
+
return createHash("sha256").update(publicKey).digest("hex");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function stableStringify(input: unknown): string {
|
|
17
|
+
if (input === null || typeof input !== "object") {
|
|
18
|
+
return JSON.stringify(input);
|
|
19
|
+
}
|
|
20
|
+
if (Array.isArray(input)) {
|
|
21
|
+
return `[${input.map((item) => stableStringify(item)).join(",")}]`;
|
|
22
|
+
}
|
|
23
|
+
const entries = Object.entries(input as Record<string, unknown>)
|
|
24
|
+
.filter(([, value]) => value !== undefined)
|
|
25
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
26
|
+
return `{${entries
|
|
27
|
+
.map(([key, value]) => `${JSON.stringify(key)}:${stableStringify(value)}`)
|
|
28
|
+
.join(",")}}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function signPayload(payload: unknown, privateKeyBase64: string): string {
|
|
32
|
+
const payloadString = stableStringify(payload);
|
|
33
|
+
const signature = nacl.sign.detached(
|
|
34
|
+
Buffer.from(payloadString),
|
|
35
|
+
fromBase64(privateKeyBase64)
|
|
36
|
+
);
|
|
37
|
+
return toBase64(signature);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function verifyPayload(
|
|
41
|
+
payload: unknown,
|
|
42
|
+
signatureBase64: string,
|
|
43
|
+
publicKeyBase64: string
|
|
44
|
+
): boolean {
|
|
45
|
+
try {
|
|
46
|
+
const payloadString = stableStringify(payload);
|
|
47
|
+
return nacl.sign.detached.verify(
|
|
48
|
+
Buffer.from(payloadString),
|
|
49
|
+
fromBase64(signatureBase64),
|
|
50
|
+
fromBase64(publicKeyBase64)
|
|
51
|
+
);
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DirectoryState,
|
|
3
|
+
IndexRefRecord,
|
|
4
|
+
PresenceRecord,
|
|
5
|
+
PublicProfile,
|
|
6
|
+
SignedProfileRecord,
|
|
7
|
+
} from "./types";
|
|
8
|
+
import { buildIndexKeys } from "./indexing";
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_PRESENCE_TTL_MS = 30_000;
|
|
11
|
+
|
|
12
|
+
export function createEmptyDirectoryState(): DirectoryState {
|
|
13
|
+
return {
|
|
14
|
+
profiles: {},
|
|
15
|
+
presence: {},
|
|
16
|
+
index: {},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ingestProfileRecord(state: DirectoryState, record: SignedProfileRecord): DirectoryState {
|
|
21
|
+
const next: DirectoryState = {
|
|
22
|
+
profiles: { ...state.profiles },
|
|
23
|
+
presence: { ...state.presence },
|
|
24
|
+
index: { ...state.index },
|
|
25
|
+
};
|
|
26
|
+
next.profiles[record.profile.agent_id] = record.profile;
|
|
27
|
+
return rebuildIndexForProfile(next, record.profile);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function ingestPresenceRecord(state: DirectoryState, record: PresenceRecord): DirectoryState {
|
|
31
|
+
return {
|
|
32
|
+
profiles: { ...state.profiles },
|
|
33
|
+
presence: {
|
|
34
|
+
...state.presence,
|
|
35
|
+
[record.agent_id]: record.timestamp,
|
|
36
|
+
},
|
|
37
|
+
index: { ...state.index },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function ingestIndexRecord(state: DirectoryState, record: IndexRefRecord): DirectoryState {
|
|
42
|
+
const existing = new Set(state.index[record.key] ?? []);
|
|
43
|
+
if (existing.has(record.agent_id)) {
|
|
44
|
+
return state;
|
|
45
|
+
}
|
|
46
|
+
existing.add(record.agent_id);
|
|
47
|
+
return {
|
|
48
|
+
profiles: { ...state.profiles },
|
|
49
|
+
presence: { ...state.presence },
|
|
50
|
+
index: {
|
|
51
|
+
...state.index,
|
|
52
|
+
[record.key]: Array.from(existing),
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function isAgentOnline(
|
|
58
|
+
lastSeenAt: number | undefined,
|
|
59
|
+
now = Date.now(),
|
|
60
|
+
ttlMs = DEFAULT_PRESENCE_TTL_MS
|
|
61
|
+
): boolean {
|
|
62
|
+
if (!lastSeenAt) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
return now - lastSeenAt <= ttlMs;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function cleanupExpiredPresence(
|
|
69
|
+
state: DirectoryState,
|
|
70
|
+
now = Date.now(),
|
|
71
|
+
ttlMs = DEFAULT_PRESENCE_TTL_MS
|
|
72
|
+
): { state: DirectoryState; removed: number } {
|
|
73
|
+
let removed = 0;
|
|
74
|
+
const presence: Record<string, number> = {};
|
|
75
|
+
|
|
76
|
+
for (const [agentId, timestamp] of Object.entries(state.presence)) {
|
|
77
|
+
if (isAgentOnline(timestamp, now, ttlMs)) {
|
|
78
|
+
presence[agentId] = timestamp;
|
|
79
|
+
} else {
|
|
80
|
+
removed += 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (removed === 0) {
|
|
85
|
+
return { state, removed: 0 };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
state: {
|
|
90
|
+
profiles: { ...state.profiles },
|
|
91
|
+
presence,
|
|
92
|
+
index: { ...state.index },
|
|
93
|
+
},
|
|
94
|
+
removed,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function rebuildIndexForProfile(state: DirectoryState, profile: PublicProfile): DirectoryState {
|
|
99
|
+
const keys = buildIndexKeys(profile);
|
|
100
|
+
const nextIndex: Record<string, string[]> = {};
|
|
101
|
+
|
|
102
|
+
for (const [key, ids] of Object.entries(state.index)) {
|
|
103
|
+
const filtered = ids.filter((id) => id !== profile.agent_id);
|
|
104
|
+
if (filtered.length > 0) {
|
|
105
|
+
nextIndex[key] = Array.from(new Set(filtered));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const key of keys) {
|
|
110
|
+
const existing = new Set(nextIndex[key] ?? []);
|
|
111
|
+
existing.add(profile.agent_id);
|
|
112
|
+
nextIndex[key] = Array.from(existing);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
profiles: { ...state.profiles },
|
|
117
|
+
presence: { ...state.presence },
|
|
118
|
+
index: nextIndex,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function dedupeIndex(state: DirectoryState): DirectoryState {
|
|
123
|
+
const index: Record<string, string[]> = {};
|
|
124
|
+
for (const [key, ids] of Object.entries(state.index)) {
|
|
125
|
+
index[key] = Array.from(new Set(ids));
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
profiles: { ...state.profiles },
|
|
129
|
+
presence: { ...state.presence },
|
|
130
|
+
index,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function searchDirectory(
|
|
135
|
+
state: DirectoryState,
|
|
136
|
+
keyword: string,
|
|
137
|
+
options?: { now?: number; presenceTTLms?: number }
|
|
138
|
+
): PublicProfile[] {
|
|
139
|
+
const now = options?.now ?? Date.now();
|
|
140
|
+
const presenceTTLms = options?.presenceTTLms ?? DEFAULT_PRESENCE_TTL_MS;
|
|
141
|
+
const normalized = keyword.trim().toLowerCase();
|
|
142
|
+
const baseList =
|
|
143
|
+
normalized.length === 0
|
|
144
|
+
? Object.values(state.profiles)
|
|
145
|
+
: Array.from(
|
|
146
|
+
new Set<string>([
|
|
147
|
+
...(state.index[`tag:${normalized}`] ?? []),
|
|
148
|
+
...(state.index[`name:${normalized.replace(/[^a-z0-9]+/g, "")}`] ?? []),
|
|
149
|
+
])
|
|
150
|
+
)
|
|
151
|
+
.map((agentId) => state.profiles[agentId])
|
|
152
|
+
.filter((profile): profile is PublicProfile => Boolean(profile));
|
|
153
|
+
|
|
154
|
+
return baseList
|
|
155
|
+
.slice()
|
|
156
|
+
.sort((a, b) => {
|
|
157
|
+
const aOnline = isAgentOnline(state.presence[a.agent_id], now, presenceTTLms) ? 1 : 0;
|
|
158
|
+
const bOnline = isAgentOnline(state.presence[b.agent_id], now, presenceTTLms) ? 1 : 0;
|
|
159
|
+
if (aOnline !== bOnline) {
|
|
160
|
+
return bOnline - aOnline;
|
|
161
|
+
}
|
|
162
|
+
if (a.updated_at !== b.updated_at) {
|
|
163
|
+
return b.updated_at - a.updated_at;
|
|
164
|
+
}
|
|
165
|
+
const byName = a.display_name.localeCompare(b.display_name);
|
|
166
|
+
if (byName !== 0) {
|
|
167
|
+
return byName;
|
|
168
|
+
}
|
|
169
|
+
return a.agent_id.localeCompare(b.agent_id);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import nacl from "tweetnacl";
|
|
2
|
+
import { hashPublicKey, toBase64 } from "./crypto";
|
|
3
|
+
import { AgentIdentity } from "./types";
|
|
4
|
+
|
|
5
|
+
export function createIdentity(now = Date.now()): AgentIdentity {
|
|
6
|
+
const pair = nacl.sign.keyPair();
|
|
7
|
+
const publicKey = toBase64(pair.publicKey);
|
|
8
|
+
return {
|
|
9
|
+
agent_id: hashPublicKey(pair.publicKey),
|
|
10
|
+
public_key: publicKey,
|
|
11
|
+
private_key: toBase64(pair.secretKey),
|
|
12
|
+
created_at: now,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./types";
|
|
2
|
+
export * from "./crypto";
|
|
3
|
+
export * from "./identity";
|
|
4
|
+
export * from "./profile";
|
|
5
|
+
export * from "./presence";
|
|
6
|
+
export * from "./indexing";
|
|
7
|
+
export * from "./directory";
|
|
8
|
+
export * from "./publicProfileSummary";
|
|
9
|
+
export * from "./socialConfig";
|
|
10
|
+
export * from "./socialResolver";
|
|
11
|
+
export * from "./socialTemplate";
|