@dp-pcs/ogp 0.5.0 → 0.7.0-rc.1
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 +47 -11
- package/dist/cli/agent-targeting.d.ts +21 -0
- package/dist/cli/agent-targeting.d.ts.map +1 -0
- package/dist/cli/agent-targeting.js +44 -0
- package/dist/cli/agent-targeting.js.map +1 -0
- package/dist/cli/config.d.ts +4 -0
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +48 -0
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/federation.d.ts +6 -1
- package/dist/cli/federation.d.ts.map +1 -1
- package/dist/cli/federation.js +222 -92
- package/dist/cli/federation.js.map +1 -1
- package/dist/cli/keychain.d.ts +22 -0
- package/dist/cli/keychain.d.ts.map +1 -0
- package/dist/cli/keychain.js +213 -0
- package/dist/cli/keychain.js.map +1 -0
- package/dist/cli/project.d.ts +1 -0
- package/dist/cli/project.d.ts.map +1 -1
- package/dist/cli/project.js +87 -7
- package/dist/cli/project.js.map +1 -1
- package/dist/cli/setup.d.ts +37 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +130 -0
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli.js +48 -7
- package/dist/cli.js.map +1 -1
- package/dist/daemon/heartbeat.d.ts +37 -0
- package/dist/daemon/heartbeat.d.ts.map +1 -1
- package/dist/daemon/heartbeat.js +195 -21
- package/dist/daemon/heartbeat.js.map +1 -1
- package/dist/daemon/keypair.d.ts.map +1 -1
- package/dist/daemon/keypair.js +144 -22
- package/dist/daemon/keypair.js.map +1 -1
- package/dist/daemon/message-handler.d.ts +8 -0
- package/dist/daemon/message-handler.d.ts.map +1 -1
- package/dist/daemon/message-handler.js +77 -20
- package/dist/daemon/message-handler.js.map +1 -1
- package/dist/daemon/notify.d.ts +6 -0
- package/dist/daemon/notify.d.ts.map +1 -1
- package/dist/daemon/notify.js +9 -2
- package/dist/daemon/notify.js.map +1 -1
- package/dist/daemon/openclaw-bridge.d.ts +6 -0
- package/dist/daemon/openclaw-bridge.d.ts.map +1 -1
- package/dist/daemon/openclaw-bridge.js +10 -2
- package/dist/daemon/openclaw-bridge.js.map +1 -1
- package/dist/daemon/peers.d.ts +31 -0
- package/dist/daemon/peers.d.ts.map +1 -1
- package/dist/daemon/peers.js +66 -4
- package/dist/daemon/peers.js.map +1 -1
- package/dist/daemon/projects.d.ts +9 -1
- package/dist/daemon/projects.d.ts.map +1 -1
- package/dist/daemon/projects.js +2 -1
- package/dist/daemon/projects.js.map +1 -1
- package/dist/daemon/rendezvous.d.ts.map +1 -1
- package/dist/daemon/rendezvous.js +9 -7
- package/dist/daemon/rendezvous.js.map +1 -1
- package/dist/daemon/reply-handler.d.ts.map +1 -1
- package/dist/daemon/reply-handler.js +2 -1
- package/dist/daemon/reply-handler.js.map +1 -1
- package/dist/daemon/scopes.d.ts +8 -0
- package/dist/daemon/scopes.d.ts.map +1 -1
- package/dist/daemon/scopes.js.map +1 -1
- package/dist/daemon/server.d.ts +128 -1
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +304 -57
- package/dist/daemon/server.js.map +1 -1
- package/dist/shared/config.d.ts +93 -0
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/shared/config.js +111 -0
- package/dist/shared/config.js.map +1 -1
- package/dist/shared/help.js +2 -0
- package/dist/shared/help.js.map +1 -1
- package/dist/shared/signing.d.ts +49 -0
- package/dist/shared/signing.d.ts.map +1 -1
- package/dist/shared/signing.js +68 -0
- package/dist/shared/signing.js.map +1 -1
- package/dist/shared/tls.d.ts +27 -0
- package/dist/shared/tls.d.ts.map +1 -0
- package/dist/shared/tls.js +37 -0
- package/dist/shared/tls.js.map +1 -0
- package/docs/ARCHITECTURE.md +146 -0
- package/docs/CLI-REFERENCE.md +170 -2
- package/docs/MULTI-AGENT-PERSONAS-DESIGN.md +925 -0
- package/package.json +1 -1
- package/scripts/completion.bash +26 -2
- package/scripts/completion.zsh +10 -4
- package/scripts/render-ogp-overview-video.mjs +417 -0
- package/skills/ogp/SKILL.md +1 -1
- package/skills/ogp-expose/SKILL.md +1 -1
- package/skills/ogp-project/SKILL.md +1 -1
package/package.json
CHANGED
package/scripts/completion.bash
CHANGED
|
@@ -30,7 +30,7 @@ _ogp_completion() {
|
|
|
30
30
|
|
|
31
31
|
# Top-level commands
|
|
32
32
|
if [ $COMP_CWORD -eq 1 ]; then
|
|
33
|
-
opts="setup start stop status federation agent-comms config expose expose-stop shutdown install uninstall intent project completion"
|
|
33
|
+
opts="setup start stop status whoami federation agent-comms config expose expose-stop shutdown install uninstall intent project completion"
|
|
34
34
|
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
35
35
|
return 0
|
|
36
36
|
fi
|
|
@@ -38,11 +38,21 @@ _ogp_completion() {
|
|
|
38
38
|
# federation subcommands
|
|
39
39
|
if [ "$cmd" = "federation" ]; then
|
|
40
40
|
if [ $COMP_CWORD -eq 2 ]; then
|
|
41
|
-
opts="list status request connect invite accept approve reject remove alias tag untag ping send scopes grant agent"
|
|
41
|
+
opts="list status request connect invite accept approve reject remove alias tag untag update-identity ping send scopes grant agent"
|
|
42
42
|
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
43
43
|
return 0
|
|
44
44
|
fi
|
|
45
45
|
|
|
46
|
+
# --to-agent (B0032 v0.7.0): targets a specific persona on the peer.
|
|
47
|
+
# Available on: federation send, federation agent
|
|
48
|
+
if [[ "$subcmd" == "send" || "$subcmd" == "agent" ]]; then
|
|
49
|
+
if [[ "$cur" == --* ]]; then
|
|
50
|
+
opts="--to-agent"
|
|
51
|
+
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
52
|
+
return 0
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
|
|
46
56
|
# For federation commands that need peer-id, we could add dynamic peer completion here
|
|
47
57
|
# TODO: ogp federation list --quiet to get peer IDs/aliases
|
|
48
58
|
return 0
|
|
@@ -110,6 +120,20 @@ _ogp_completion() {
|
|
|
110
120
|
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
111
121
|
return 0
|
|
112
122
|
fi
|
|
123
|
+
|
|
124
|
+
# --to-agent (B0032 v0.7.0): targets a specific persona on the peer.
|
|
125
|
+
# Available on: project contribute (auto-push), project send-contribution
|
|
126
|
+
if [[ "$subcmd" == "contribute" || "$subcmd" == "send-contribution" ]]; then
|
|
127
|
+
if [[ "$cur" == --* ]]; then
|
|
128
|
+
if [ "$subcmd" = "contribute" ]; then
|
|
129
|
+
opts="--metadata --local-only --to-agent"
|
|
130
|
+
else
|
|
131
|
+
opts="--metadata --to-agent"
|
|
132
|
+
fi
|
|
133
|
+
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
134
|
+
return 0
|
|
135
|
+
fi
|
|
136
|
+
fi
|
|
113
137
|
return 0
|
|
114
138
|
fi
|
|
115
139
|
|
package/scripts/completion.zsh
CHANGED
|
@@ -25,6 +25,7 @@ _ogp() {
|
|
|
25
25
|
start\:"Start OGP daemon"
|
|
26
26
|
stop\:"Stop OGP daemon"
|
|
27
27
|
status\:"Show daemon status"
|
|
28
|
+
whoami\:"Show current identity and configuration"
|
|
28
29
|
federation\:"Manage federation"
|
|
29
30
|
agent-comms\:"Configure agent-to-agent messaging"
|
|
30
31
|
config\:"Manage configuration"
|
|
@@ -83,6 +84,7 @@ _ogp_federation() {
|
|
|
83
84
|
alias\:"Set user-friendly alias for peer"
|
|
84
85
|
tag\:"Add tags to a peer (local categorization)"
|
|
85
86
|
untag\:"Remove tags from a peer"
|
|
87
|
+
update-identity\:"Send updated identity information to approved peer"
|
|
86
88
|
ping\:"Ping peer gateway to test connectivity"
|
|
87
89
|
send\:"Send message to federated peer"
|
|
88
90
|
scopes\:"Show scope grants for peer"
|
|
@@ -125,7 +127,8 @@ _ogp_federation() {
|
|
|
125
127
|
_arguments \
|
|
126
128
|
'1:peer-id:' \
|
|
127
129
|
'2:intent:' \
|
|
128
|
-
'3:payload:'
|
|
130
|
+
'3:payload:' \
|
|
131
|
+
'--to-agent[Target a specific persona on the peer]:persona:'
|
|
129
132
|
;;
|
|
130
133
|
grant)
|
|
131
134
|
_arguments \
|
|
@@ -142,7 +145,8 @@ _ogp_federation() {
|
|
|
142
145
|
'(-p --priority)'{-p,--priority}'[Priority level]:priority:(low normal high)' \
|
|
143
146
|
'(-c --conversation)'{-c,--conversation}'[Conversation ID]:conversation-id:' \
|
|
144
147
|
'(-w --wait)'{-w,--wait}'[Wait for reply]' \
|
|
145
|
-
'(-t --timeout)'{-t,--timeout}'[Reply timeout in milliseconds]:timeout:'
|
|
148
|
+
'(-t --timeout)'{-t,--timeout}'[Reply timeout in milliseconds]:timeout:' \
|
|
149
|
+
'--to-agent[Target a specific persona on the peer]:persona:'
|
|
146
150
|
;;
|
|
147
151
|
esac
|
|
148
152
|
;;
|
|
@@ -338,7 +342,8 @@ _ogp_project() {
|
|
|
338
342
|
'2:type:' \
|
|
339
343
|
'3:summary:' \
|
|
340
344
|
'--metadata[Additional structured data as JSON]:metadata:' \
|
|
341
|
-
'--local-only[Skip auto-push to federated peers]'
|
|
345
|
+
'--local-only[Skip auto-push to federated peers]' \
|
|
346
|
+
'--to-agent[Target a persona on each peer auto-push target]:persona:'
|
|
342
347
|
;;
|
|
343
348
|
query)
|
|
344
349
|
_arguments \
|
|
@@ -362,7 +367,8 @@ _ogp_project() {
|
|
|
362
367
|
'2:project-id:' \
|
|
363
368
|
'3:type:' \
|
|
364
369
|
'4:summary:' \
|
|
365
|
-
'--metadata[Additional structured data as JSON]:metadata:'
|
|
370
|
+
'--metadata[Additional structured data as JSON]:metadata:' \
|
|
371
|
+
'--to-agent[Target a specific persona on the peer]:persona:'
|
|
366
372
|
;;
|
|
367
373
|
query-peer)
|
|
368
374
|
_arguments \
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
|
+
|
|
6
|
+
const outDir = 'artifacts/ogp-overview-video';
|
|
7
|
+
const slideDir = join(outDir, 'slides');
|
|
8
|
+
mkdirSync(slideDir, { recursive: true });
|
|
9
|
+
|
|
10
|
+
const renderSlidesOnly = process.argv.includes('--slides-only');
|
|
11
|
+
|
|
12
|
+
const width = 1920;
|
|
13
|
+
const height = 1080;
|
|
14
|
+
const duration = 5.8;
|
|
15
|
+
|
|
16
|
+
const palette = {
|
|
17
|
+
ink: '#10131c',
|
|
18
|
+
panel: '#171d2a',
|
|
19
|
+
panel2: '#1d2738',
|
|
20
|
+
line: '#3c485f',
|
|
21
|
+
text: '#f4f7fb',
|
|
22
|
+
muted: '#9eacc2',
|
|
23
|
+
teal: '#37d2c3',
|
|
24
|
+
amber: '#f3bb55',
|
|
25
|
+
coral: '#ff6f61',
|
|
26
|
+
blue: '#7ca7ff',
|
|
27
|
+
green: '#84d977'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const slides = [
|
|
31
|
+
{
|
|
32
|
+
kicker: '@dp-pcs/ogp',
|
|
33
|
+
title: 'Open Gateway Protocol',
|
|
34
|
+
subtitle: 'BGP-style peering for AI gateways: signed, scoped, human-approved federation.',
|
|
35
|
+
body: [
|
|
36
|
+
'Agents can collaborate across different gateways without sharing credentials, memory, or raw context.',
|
|
37
|
+
'The gateway is the trust boundary.'
|
|
38
|
+
],
|
|
39
|
+
visual: 'network',
|
|
40
|
+
narration: 'Open Gateway Protocol, or OGP, is federation for AI gateways. The simple version: your assistant can call your coworker\'s assistant directly, without either of you copy-pasting messages or sharing credentials.'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
kicker: 'The Problem',
|
|
44
|
+
title: 'Useful agents are trapped in local silos',
|
|
45
|
+
subtitle: 'OpenClaw, Hermes, and future gateways all have their own tools, memory, and security boundaries.',
|
|
46
|
+
body: [
|
|
47
|
+
'Today, collaboration usually means a human relays messages by hand.',
|
|
48
|
+
'That is slow, lossy, and impossible to audit at scale.'
|
|
49
|
+
],
|
|
50
|
+
visual: 'silos',
|
|
51
|
+
narration: 'The problem is that useful agents live inside local silos. OpenClaw, Hermes, and other gateways have their own tools and memory. Humans end up acting as the relay, which is slow and hard to audit.'
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
kicker: 'The Model',
|
|
55
|
+
title: 'Peer relationships, not central authority',
|
|
56
|
+
subtitle: 'Each gateway owns a keypair and forms bilateral relationships with peers.',
|
|
57
|
+
body: [
|
|
58
|
+
'Discovery starts at /.well-known/ogp.',
|
|
59
|
+
'A request becomes usable only after human approval.'
|
|
60
|
+
],
|
|
61
|
+
visual: 'handshake',
|
|
62
|
+
narration: 'OGP uses peer relationships instead of central authority. Each gateway owns a keypair. Federation starts with public discovery, then a signed request, then explicit human approval.'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
kicker: 'Trust Boundary',
|
|
66
|
+
title: 'Agents stay contained inside their gateway',
|
|
67
|
+
subtitle: 'Peers send signed intents. They do not get direct access to tools, memory, shells, or model sessions.',
|
|
68
|
+
body: [
|
|
69
|
+
'Every cross-gateway message is attributable to a peer key.',
|
|
70
|
+
'Every inbound action runs through policy before it reaches an agent.'
|
|
71
|
+
],
|
|
72
|
+
visual: 'boundary',
|
|
73
|
+
narration: 'The important design choice is containment. Agents never leave their own gateway. A peer sends a signed intent, and the receiving gateway decides whether that intent is allowed before it reaches an agent.'
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
kicker: 'Scope Control',
|
|
77
|
+
title: 'Three layers of no',
|
|
78
|
+
subtitle: 'Capabilities say what a gateway can support. Grants say what this peer may use. Runtime enforcement checks every message.',
|
|
79
|
+
body: [
|
|
80
|
+
'Per-peer intents, topic limits, and rate limits make trust granular.',
|
|
81
|
+
'Revocation is unilateral and immediate.'
|
|
82
|
+
],
|
|
83
|
+
visual: 'layers',
|
|
84
|
+
narration: 'Permissions are not all-or-nothing. OGP has three layers: what the gateway can support, what this specific peer is granted, and what the runtime doorman allows for each message.'
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
kicker: 'Agent-Comms',
|
|
88
|
+
title: 'Structured messages between assistants',
|
|
89
|
+
subtitle: 'Topic routing, priorities, conversation IDs, and signed replies make remote collaboration practical.',
|
|
90
|
+
body: [
|
|
91
|
+
'Use cases include memory questions, project status, task handoffs, and peer-to-peer debugging.',
|
|
92
|
+
'Replies can return by callback or polling.'
|
|
93
|
+
],
|
|
94
|
+
visual: 'messages',
|
|
95
|
+
narration: 'On top of federation, OGP has agent-comms. That gives assistants topic routing, priorities, conversation threads, and signed replies, so remote collaboration can be structured instead of just chat pasted into chat.'
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
kicker: 'Projects',
|
|
99
|
+
title: 'Collaboration context without shared tooling',
|
|
100
|
+
subtitle: 'Project intents move high-level facts: join, contribute, query, and status.',
|
|
101
|
+
body: [
|
|
102
|
+
'A peer can use Beads, Linear, a notebook, or nothing at all.',
|
|
103
|
+
'OGP moves concise project facts across the federation boundary.'
|
|
104
|
+
],
|
|
105
|
+
visual: 'project',
|
|
106
|
+
narration: 'Projects are optional collaboration boundaries on top of federation. They do not require everyone to use the same task tool. OGP moves concise facts like joins, contributions, queries, and status.'
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
kicker: 'Multi-Framework',
|
|
110
|
+
title: 'One protocol, multiple runtimes',
|
|
111
|
+
subtitle: 'The reference daemon runs alongside OpenClaw, Hermes, or standalone setups with isolated state.',
|
|
112
|
+
body: [
|
|
113
|
+
'Meta-config supports multiple framework homes and daemon ports.',
|
|
114
|
+
'The wire protocol stays gateway-neutral.'
|
|
115
|
+
],
|
|
116
|
+
visual: 'frameworks',
|
|
117
|
+
narration: 'The reference implementation already supports multiple runtimes. It can run beside OpenClaw, Hermes, or a standalone gateway. The local adapter changes, but the wire protocol stays neutral.'
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
kicker: 'Where It Is Going',
|
|
121
|
+
title: 'One trust relationship, many agent personas',
|
|
122
|
+
subtitle: 'The v0.7 design advertises multiple addressable personas under one gateway keypair.',
|
|
123
|
+
body: [
|
|
124
|
+
'Junior, Sterling, Apollo, or any specialist can be visible per peer and per scope.',
|
|
125
|
+
'No extra federation handshake for every agent.'
|
|
126
|
+
],
|
|
127
|
+
visual: 'personas',
|
|
128
|
+
narration: 'The next design step is multi-agent personas. One human-level trust relationship can expose multiple addressable assistants, with grants that decide which peer can reach which persona.'
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
kicker: 'Demo Takeaway',
|
|
132
|
+
title: 'OGP turns agent collaboration into infrastructure',
|
|
133
|
+
subtitle: 'Signed messages, scoped grants, audit trails, and revocation give personal and team agents a real federation layer.',
|
|
134
|
+
body: [
|
|
135
|
+
'Not a chat bridge. Not a shared account. A protocol boundary.',
|
|
136
|
+
'Built in TypeScript, shipping as @dp-pcs/ogp.'
|
|
137
|
+
],
|
|
138
|
+
visual: 'final',
|
|
139
|
+
narration: 'The takeaway: OGP turns agent collaboration into infrastructure. Not a chat bridge. Not a shared account. A protocol boundary with signed messages, scoped grants, audit trails, and revocation.'
|
|
140
|
+
}
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
function esc(value) {
|
|
144
|
+
return String(value)
|
|
145
|
+
.replaceAll('&', '&')
|
|
146
|
+
.replaceAll('<', '<')
|
|
147
|
+
.replaceAll('>', '>')
|
|
148
|
+
.replaceAll('"', '"');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function wrapText(text, maxChars) {
|
|
152
|
+
const words = text.split(/\s+/);
|
|
153
|
+
const lines = [];
|
|
154
|
+
let line = '';
|
|
155
|
+
for (const word of words) {
|
|
156
|
+
const next = line ? `${line} ${word}` : word;
|
|
157
|
+
if (next.length > maxChars && line) {
|
|
158
|
+
lines.push(line);
|
|
159
|
+
line = word;
|
|
160
|
+
} else {
|
|
161
|
+
line = next;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (line) lines.push(line);
|
|
165
|
+
return lines;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function textBlock(lines, x, y, maxChars, size, fill, gap = Math.round(size * 1.35), weight = 500) {
|
|
169
|
+
const out = [];
|
|
170
|
+
let cursor = y;
|
|
171
|
+
for (const source of lines) {
|
|
172
|
+
for (const line of wrapText(source, maxChars)) {
|
|
173
|
+
out.push(`<text x="${x}" y="${cursor}" font-size="${size}" font-weight="${weight}" fill="${fill}">${esc(line)}</text>`);
|
|
174
|
+
cursor += gap;
|
|
175
|
+
}
|
|
176
|
+
cursor += Math.round(gap * 0.35);
|
|
177
|
+
}
|
|
178
|
+
return out.join('\n');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function node(x, y, label, accent, sub = '') {
|
|
182
|
+
return `
|
|
183
|
+
<rect x="${x - 150}" y="${y - 72}" width="300" height="144" rx="18" fill="${palette.panel}" stroke="${accent}" stroke-width="3"/>
|
|
184
|
+
<circle cx="${x - 100}" cy="${y - 20}" r="16" fill="${accent}"/>
|
|
185
|
+
<text x="${x - 70}" y="${y - 18}" font-size="28" font-weight="800" fill="${palette.text}">${esc(label)}</text>
|
|
186
|
+
<text x="${x - 100}" y="${y + 30}" font-size="20" fill="${palette.muted}">${esc(sub)}</text>
|
|
187
|
+
`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function arrow(x1, y1, x2, y2, color = palette.teal) {
|
|
191
|
+
const angle = Math.atan2(y2 - y1, x2 - x1);
|
|
192
|
+
const head = 18;
|
|
193
|
+
const hx1 = x2 - head * Math.cos(angle - Math.PI / 7);
|
|
194
|
+
const hy1 = y2 - head * Math.sin(angle - Math.PI / 7);
|
|
195
|
+
const hx2 = x2 - head * Math.cos(angle + Math.PI / 7);
|
|
196
|
+
const hy2 = y2 - head * Math.sin(angle + Math.PI / 7);
|
|
197
|
+
return `
|
|
198
|
+
<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${color}" stroke-width="5" stroke-linecap="round"/>
|
|
199
|
+
<path d="M ${x2} ${y2} L ${hx1} ${hy1} L ${hx2} ${hy2} Z" fill="${color}"/>
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function visual(name) {
|
|
204
|
+
switch (name) {
|
|
205
|
+
case 'network':
|
|
206
|
+
return `
|
|
207
|
+
${node(1230, 390, 'Gateway A', palette.teal, 'David + Junior')}
|
|
208
|
+
${node(1600, 390, 'Gateway B', palette.amber, 'Stan + agent')}
|
|
209
|
+
${arrow(1385, 390, 1445, 390, palette.blue)}
|
|
210
|
+
${arrow(1445, 430, 1385, 430, palette.coral)}
|
|
211
|
+
<text x="1362" y="330" font-size="22" fill="${palette.muted}">signed OGP</text>
|
|
212
|
+
<rect x="1140" y="575" width="560" height="160" rx="20" fill="${palette.panel2}" stroke="${palette.line}"/>
|
|
213
|
+
<text x="1190" y="635" font-size="28" font-weight="800" fill="${palette.text}">No central relay</text>
|
|
214
|
+
<text x="1190" y="690" font-size="24" fill="${palette.muted}">No shared credentials. No copied context.</text>
|
|
215
|
+
`;
|
|
216
|
+
case 'silos':
|
|
217
|
+
return `
|
|
218
|
+
<rect x="1110" y="270" width="240" height="470" rx="28" fill="${palette.panel}" stroke="${palette.teal}" stroke-width="3"/>
|
|
219
|
+
<rect x="1440" y="270" width="240" height="470" rx="28" fill="${palette.panel}" stroke="${palette.amber}" stroke-width="3"/>
|
|
220
|
+
<text x="1170" y="350" font-size="32" font-weight="800" fill="${palette.text}">OpenClaw</text>
|
|
221
|
+
<text x="1512" y="350" font-size="32" font-weight="800" fill="${palette.text}">Hermes</text>
|
|
222
|
+
<line x1="1350" y1="510" x2="1440" y2="510" stroke="${palette.coral}" stroke-width="5" stroke-dasharray="16 16"/>
|
|
223
|
+
<text x="1295" y="575" font-size="24" fill="${palette.coral}">human relay</text>
|
|
224
|
+
<text x="1150" y="670" font-size="22" fill="${palette.muted}">tools</text>
|
|
225
|
+
<text x="1150" y="710" font-size="22" fill="${palette.muted}">memory</text>
|
|
226
|
+
<text x="1485" y="670" font-size="22" fill="${palette.muted}">tools</text>
|
|
227
|
+
<text x="1485" y="710" font-size="22" fill="${palette.muted}">memory</text>
|
|
228
|
+
`;
|
|
229
|
+
case 'handshake':
|
|
230
|
+
return `
|
|
231
|
+
${node(1090, 310, '1. Discover', palette.blue, '/.well-known/ogp')}
|
|
232
|
+
${node(1435, 500, '2. Request', palette.teal, 'signed body')}
|
|
233
|
+
${node(1090, 690, '3. Approve', palette.amber, 'human gate')}
|
|
234
|
+
${arrow(1235, 340, 1295, 455, palette.blue)}
|
|
235
|
+
${arrow(1295, 545, 1235, 655, palette.amber)}
|
|
236
|
+
`;
|
|
237
|
+
case 'boundary':
|
|
238
|
+
return `
|
|
239
|
+
<rect x="1110" y="250" width="630" height="500" rx="30" fill="${palette.panel}" stroke="${palette.teal}" stroke-width="4"/>
|
|
240
|
+
<text x="1170" y="330" font-size="36" font-weight="900" fill="${palette.text}">Gateway policy boundary</text>
|
|
241
|
+
<rect x="1190" y="400" width="200" height="110" rx="16" fill="${palette.panel2}" stroke="${palette.line}"/>
|
|
242
|
+
<text x="1245" y="468" font-size="30" font-weight="800" fill="${palette.text}">Doorman</text>
|
|
243
|
+
<rect x="1490" y="400" width="160" height="110" rx="16" fill="${palette.panel2}" stroke="${palette.line}"/>
|
|
244
|
+
<text x="1534" y="468" font-size="30" font-weight="800" fill="${palette.text}">Agent</text>
|
|
245
|
+
${arrow(1030, 455, 1180, 455, palette.coral)}
|
|
246
|
+
${arrow(1395, 455, 1480, 455, palette.green)}
|
|
247
|
+
<text x="1035" y="410" font-size="22" fill="${palette.muted}">signed intent</text>
|
|
248
|
+
<text x="1400" y="410" font-size="22" fill="${palette.muted}">allowed</text>
|
|
249
|
+
`;
|
|
250
|
+
case 'layers':
|
|
251
|
+
return [0, 1, 2].map((i) => {
|
|
252
|
+
const labels = ['Capabilities', 'Peer grants', 'Runtime doorman'];
|
|
253
|
+
const subs = ['what I can support', 'what you may use', 'this request passes?'];
|
|
254
|
+
const colors = [palette.blue, palette.amber, palette.teal];
|
|
255
|
+
const y = 300 + i * 155;
|
|
256
|
+
return `<rect x="1110" y="${y}" width="620" height="105" rx="18" fill="${palette.panel}" stroke="${colors[i]}" stroke-width="3"/>
|
|
257
|
+
<text x="1160" y="${y + 44}" font-size="30" font-weight="900" fill="${palette.text}">${labels[i]}</text>
|
|
258
|
+
<text x="1160" y="${y + 82}" font-size="23" fill="${palette.muted}">${subs[i]}</text>`;
|
|
259
|
+
}).join('\n');
|
|
260
|
+
case 'messages':
|
|
261
|
+
return `
|
|
262
|
+
<rect x="1110" y="265" width="620" height="475" rx="26" fill="${palette.panel}" stroke="${palette.line}"/>
|
|
263
|
+
<text x="1170" y="340" font-size="30" font-weight="900" fill="${palette.text}">agent-comms</text>
|
|
264
|
+
<text x="1170" y="410" font-size="25" fill="${palette.teal}">topic: project-alpha</text>
|
|
265
|
+
<text x="1170" y="470" font-size="25" fill="${palette.amber}">priority: high</text>
|
|
266
|
+
<text x="1170" y="530" font-size="25" fill="${palette.blue}">conversationId: sprint-42</text>
|
|
267
|
+
<text x="1170" y="590" font-size="25" fill="${palette.green}">reply: signed callback</text>
|
|
268
|
+
<rect x="1190" y="645" width="460" height="46" rx="12" fill="${palette.panel2}"/>
|
|
269
|
+
<text x="1220" y="676" font-size="22" fill="${palette.muted}">structured enough to automate</text>
|
|
270
|
+
`;
|
|
271
|
+
case 'project':
|
|
272
|
+
return `
|
|
273
|
+
<rect x="1120" y="300" width="580" height="390" rx="28" fill="${palette.panel}" stroke="${palette.line}"/>
|
|
274
|
+
${['project.join', 'project.contribute', 'project.query', 'project.status'].map((label, i) => {
|
|
275
|
+
const y = 365 + i * 75;
|
|
276
|
+
const c = [palette.teal, palette.amber, palette.blue, palette.green][i];
|
|
277
|
+
return `<circle cx="1180" cy="${y}" r="15" fill="${c}"/><text x="1225" y="${y + 9}" font-size="30" font-weight="800" fill="${palette.text}">${label}</text>`;
|
|
278
|
+
}).join('\n')}
|
|
279
|
+
`;
|
|
280
|
+
case 'frameworks':
|
|
281
|
+
return `
|
|
282
|
+
${node(1120, 350, 'OpenClaw', palette.teal, '~/.ogp-openclaw')}
|
|
283
|
+
${node(1510, 350, 'Hermes', palette.amber, '~/.ogp-hermes')}
|
|
284
|
+
<rect x="1208" y="590" width="540" height="130" rx="22" fill="${palette.panel2}" stroke="${palette.blue}" stroke-width="3"/>
|
|
285
|
+
<text x="1270" y="645" font-size="34" font-weight="900" fill="${palette.text}">Same OGP wire protocol</text>
|
|
286
|
+
<text x="1270" y="690" font-size="23" fill="${palette.muted}">adapter changes, federation stays stable</text>
|
|
287
|
+
`;
|
|
288
|
+
case 'personas':
|
|
289
|
+
return `
|
|
290
|
+
<rect x="1090" y="250" width="670" height="500" rx="30" fill="${palette.panel}" stroke="${palette.teal}" stroke-width="4"/>
|
|
291
|
+
<text x="1160" y="320" font-size="32" font-weight="900" fill="${palette.text}">David's gateway keypair</text>
|
|
292
|
+
${['Junior - primary', 'Sterling - finance', 'Apollo - research'].map((label, i) => {
|
|
293
|
+
const y = 405 + i * 95;
|
|
294
|
+
const c = [palette.teal, palette.amber, palette.blue][i];
|
|
295
|
+
return `<rect x="1190" y="${y}" width="440" height="62" rx="16" fill="${palette.panel2}" stroke="${c}" stroke-width="2"/>
|
|
296
|
+
<text x="1230" y="${y + 41}" font-size="28" font-weight="800" fill="${palette.text}">${label}</text>`;
|
|
297
|
+
}).join('\n')}
|
|
298
|
+
<text x="1160" y="705" font-size="24" fill="${palette.muted}">peer x intent x persona grants</text>
|
|
299
|
+
`;
|
|
300
|
+
case 'final':
|
|
301
|
+
return `
|
|
302
|
+
<circle cx="1385" cy="500" r="220" fill="${palette.panel}" stroke="${palette.teal}" stroke-width="5"/>
|
|
303
|
+
<text x="1286" y="482" font-size="54" font-weight="950" fill="${palette.text}">OGP</text>
|
|
304
|
+
<text x="1190" y="545" font-size="28" fill="${palette.muted}">signed scoped federation</text>
|
|
305
|
+
<text x="1185" y="680" font-size="26" fill="${palette.amber}">npm install -g @dp-pcs/ogp</text>
|
|
306
|
+
`;
|
|
307
|
+
default:
|
|
308
|
+
return '';
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function svg(slide, index) {
|
|
313
|
+
const progress = `${String(index + 1).padStart(2, '0')} / ${String(slides.length).padStart(2, '0')}`;
|
|
314
|
+
const titleLines = wrapText(slide.title, 32);
|
|
315
|
+
const titleSvg = titleLines.map((line, i) =>
|
|
316
|
+
`<text x="140" y="${230 + i * 82}" font-size="76" font-weight="950" fill="${palette.text}">${esc(line)}</text>`
|
|
317
|
+
).join('\n');
|
|
318
|
+
const subtitleY = titleLines.length > 1 ? 390 : 306;
|
|
319
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
320
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
|
|
321
|
+
<defs>
|
|
322
|
+
<radialGradient id="glow" cx="68%" cy="35%" r="55%">
|
|
323
|
+
<stop offset="0%" stop-color="#25445b"/>
|
|
324
|
+
<stop offset="55%" stop-color="#141b27"/>
|
|
325
|
+
<stop offset="100%" stop-color="${palette.ink}"/>
|
|
326
|
+
</radialGradient>
|
|
327
|
+
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
|
328
|
+
<feDropShadow dx="0" dy="22" stdDeviation="20" flood-color="#000000" flood-opacity="0.28"/>
|
|
329
|
+
</filter>
|
|
330
|
+
</defs>
|
|
331
|
+
<rect width="${width}" height="${height}" fill="url(#glow)"/>
|
|
332
|
+
<path d="M 0 980 C 330 890 630 1030 960 945 C 1320 852 1590 975 1920 870 L 1920 1080 L 0 1080 Z" fill="#0b0f17" opacity="0.88"/>
|
|
333
|
+
<g font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">
|
|
334
|
+
<text x="140" y="120" font-size="24" font-weight="800" fill="${palette.teal}">${esc(slide.kicker.toUpperCase())}</text>
|
|
335
|
+
${titleSvg}
|
|
336
|
+
${textBlock([slide.subtitle], 140, subtitleY, 48, 34, palette.muted, 46, 500)}
|
|
337
|
+
<g transform="translate(0, 40)">
|
|
338
|
+
${textBlock(slide.body, 170, 520, 49, 28, palette.text, 40, 600)}
|
|
339
|
+
</g>
|
|
340
|
+
<g filter="url(#shadow)">
|
|
341
|
+
${visual(slide.visual)}
|
|
342
|
+
</g>
|
|
343
|
+
<text x="140" y="980" font-size="22" fill="${palette.muted}">Open Gateway Protocol</text>
|
|
344
|
+
<text x="1700" y="980" font-size="22" fill="${palette.muted}">${progress}</text>
|
|
345
|
+
</g>
|
|
346
|
+
</svg>`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (renderSlidesOnly) {
|
|
350
|
+
rmSync(slideDir, { recursive: true, force: true });
|
|
351
|
+
}
|
|
352
|
+
mkdirSync(slideDir, { recursive: true });
|
|
353
|
+
|
|
354
|
+
const pngs = [];
|
|
355
|
+
for (let i = 0; i < slides.length; i++) {
|
|
356
|
+
const base = `slide-${String(i + 1).padStart(2, '0')}`;
|
|
357
|
+
const svgPath = join(slideDir, `${base}.svg`);
|
|
358
|
+
const pngPath = join(slideDir, `${base}.png`);
|
|
359
|
+
writeFileSync(svgPath, svg(slides[i], i));
|
|
360
|
+
pngs.push(pngPath);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const storyboard = slides.map((slide, i) => [
|
|
364
|
+
`## ${i + 1}. ${slide.title}`,
|
|
365
|
+
`Kicker: ${slide.kicker}`,
|
|
366
|
+
`Subtitle: ${slide.subtitle}`,
|
|
367
|
+
'',
|
|
368
|
+
slide.body.map((item) => `- ${item}`).join('\n'),
|
|
369
|
+
'',
|
|
370
|
+
`Narration: ${slide.narration}`,
|
|
371
|
+
''
|
|
372
|
+
].join('\n')).join('\n');
|
|
373
|
+
writeFileSync(join(outDir, 'storyboard.md'), `# OGP Overview Demo Storyboard\n\n${storyboard}`);
|
|
374
|
+
|
|
375
|
+
const narrationText = slides.map((slide) => slide.narration).join('\n\n');
|
|
376
|
+
writeFileSync(join(outDir, 'narration.txt'), narrationText);
|
|
377
|
+
|
|
378
|
+
if (renderSlidesOnly) {
|
|
379
|
+
console.log(`Wrote SVG slides and narration scaffold to ${outDir}`);
|
|
380
|
+
process.exit(0);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
for (const png of pngs) {
|
|
384
|
+
if (!existsSync(png)) {
|
|
385
|
+
throw new Error(`Missing PNG slide ${png}. Run SVG-to-PNG capture before final rendering.`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const concatPath = join(outDir, 'slides.ffconcat');
|
|
390
|
+
const concatLines = ['ffconcat version 1.0'];
|
|
391
|
+
for (const png of pngs) {
|
|
392
|
+
const absolutePng = resolve(png);
|
|
393
|
+
concatLines.push(`file '${absolutePng.replaceAll("'", "'\\''")}'`);
|
|
394
|
+
concatLines.push(`duration ${duration}`);
|
|
395
|
+
}
|
|
396
|
+
concatLines.push(`file '${resolve(pngs.at(-1)).replaceAll("'", "'\\''")}'`);
|
|
397
|
+
writeFileSync(concatPath, concatLines.join('\n') + '\n');
|
|
398
|
+
|
|
399
|
+
const finalVideo = join(outDir, 'ogp-overview-demo.mp4');
|
|
400
|
+
execFileSync('ffmpeg', [
|
|
401
|
+
'-y',
|
|
402
|
+
'-f', 'concat',
|
|
403
|
+
'-safe', '0',
|
|
404
|
+
'-i', concatPath,
|
|
405
|
+
'-vf', 'format=yuv420p,fps=30',
|
|
406
|
+
'-r', '30',
|
|
407
|
+
'-c:v', 'libx264',
|
|
408
|
+
'-movflags', '+faststart',
|
|
409
|
+
'-pix_fmt', 'yuv420p',
|
|
410
|
+
finalVideo
|
|
411
|
+
], { stdio: 'inherit' });
|
|
412
|
+
|
|
413
|
+
if (!existsSync(finalVideo)) {
|
|
414
|
+
throw new Error(`Render failed: ${finalVideo} was not created`);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
console.log(`Rendered ${finalVideo}`);
|
package/skills/ogp/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
skill_name: ogp-expose
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.6.0
|
|
4
4
|
description: Expose OGP via a public HTTPS endpoint, usually a stable Cloudflare hostname or named tunnel. Use when the user wants to verify or fix gateway reachability, align `gatewayUrl` with the real public endpoint, or set up temporary cloudflared/ngrok exposure for testing.
|
|
5
5
|
trigger: Use when the user wants to expose their OGP daemon to the internet, get a public URL for federation, or set up a tunnel for peer discovery.
|
|
6
6
|
requires:
|