@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.
Files changed (91) hide show
  1. package/README.md +47 -11
  2. package/dist/cli/agent-targeting.d.ts +21 -0
  3. package/dist/cli/agent-targeting.d.ts.map +1 -0
  4. package/dist/cli/agent-targeting.js +44 -0
  5. package/dist/cli/agent-targeting.js.map +1 -0
  6. package/dist/cli/config.d.ts +4 -0
  7. package/dist/cli/config.d.ts.map +1 -1
  8. package/dist/cli/config.js +48 -0
  9. package/dist/cli/config.js.map +1 -1
  10. package/dist/cli/federation.d.ts +6 -1
  11. package/dist/cli/federation.d.ts.map +1 -1
  12. package/dist/cli/federation.js +222 -92
  13. package/dist/cli/federation.js.map +1 -1
  14. package/dist/cli/keychain.d.ts +22 -0
  15. package/dist/cli/keychain.d.ts.map +1 -0
  16. package/dist/cli/keychain.js +213 -0
  17. package/dist/cli/keychain.js.map +1 -0
  18. package/dist/cli/project.d.ts +1 -0
  19. package/dist/cli/project.d.ts.map +1 -1
  20. package/dist/cli/project.js +87 -7
  21. package/dist/cli/project.js.map +1 -1
  22. package/dist/cli/setup.d.ts +37 -0
  23. package/dist/cli/setup.d.ts.map +1 -1
  24. package/dist/cli/setup.js +130 -0
  25. package/dist/cli/setup.js.map +1 -1
  26. package/dist/cli.js +48 -7
  27. package/dist/cli.js.map +1 -1
  28. package/dist/daemon/heartbeat.d.ts +37 -0
  29. package/dist/daemon/heartbeat.d.ts.map +1 -1
  30. package/dist/daemon/heartbeat.js +195 -21
  31. package/dist/daemon/heartbeat.js.map +1 -1
  32. package/dist/daemon/keypair.d.ts.map +1 -1
  33. package/dist/daemon/keypair.js +144 -22
  34. package/dist/daemon/keypair.js.map +1 -1
  35. package/dist/daemon/message-handler.d.ts +8 -0
  36. package/dist/daemon/message-handler.d.ts.map +1 -1
  37. package/dist/daemon/message-handler.js +77 -20
  38. package/dist/daemon/message-handler.js.map +1 -1
  39. package/dist/daemon/notify.d.ts +6 -0
  40. package/dist/daemon/notify.d.ts.map +1 -1
  41. package/dist/daemon/notify.js +9 -2
  42. package/dist/daemon/notify.js.map +1 -1
  43. package/dist/daemon/openclaw-bridge.d.ts +6 -0
  44. package/dist/daemon/openclaw-bridge.d.ts.map +1 -1
  45. package/dist/daemon/openclaw-bridge.js +10 -2
  46. package/dist/daemon/openclaw-bridge.js.map +1 -1
  47. package/dist/daemon/peers.d.ts +31 -0
  48. package/dist/daemon/peers.d.ts.map +1 -1
  49. package/dist/daemon/peers.js +66 -4
  50. package/dist/daemon/peers.js.map +1 -1
  51. package/dist/daemon/projects.d.ts +9 -1
  52. package/dist/daemon/projects.d.ts.map +1 -1
  53. package/dist/daemon/projects.js +2 -1
  54. package/dist/daemon/projects.js.map +1 -1
  55. package/dist/daemon/rendezvous.d.ts.map +1 -1
  56. package/dist/daemon/rendezvous.js +9 -7
  57. package/dist/daemon/rendezvous.js.map +1 -1
  58. package/dist/daemon/reply-handler.d.ts.map +1 -1
  59. package/dist/daemon/reply-handler.js +2 -1
  60. package/dist/daemon/reply-handler.js.map +1 -1
  61. package/dist/daemon/scopes.d.ts +8 -0
  62. package/dist/daemon/scopes.d.ts.map +1 -1
  63. package/dist/daemon/scopes.js.map +1 -1
  64. package/dist/daemon/server.d.ts +128 -1
  65. package/dist/daemon/server.d.ts.map +1 -1
  66. package/dist/daemon/server.js +304 -57
  67. package/dist/daemon/server.js.map +1 -1
  68. package/dist/shared/config.d.ts +93 -0
  69. package/dist/shared/config.d.ts.map +1 -1
  70. package/dist/shared/config.js +111 -0
  71. package/dist/shared/config.js.map +1 -1
  72. package/dist/shared/help.js +2 -0
  73. package/dist/shared/help.js.map +1 -1
  74. package/dist/shared/signing.d.ts +49 -0
  75. package/dist/shared/signing.d.ts.map +1 -1
  76. package/dist/shared/signing.js +68 -0
  77. package/dist/shared/signing.js.map +1 -1
  78. package/dist/shared/tls.d.ts +27 -0
  79. package/dist/shared/tls.d.ts.map +1 -0
  80. package/dist/shared/tls.js +37 -0
  81. package/dist/shared/tls.js.map +1 -0
  82. package/docs/ARCHITECTURE.md +146 -0
  83. package/docs/CLI-REFERENCE.md +170 -2
  84. package/docs/MULTI-AGENT-PERSONAS-DESIGN.md +925 -0
  85. package/package.json +1 -1
  86. package/scripts/completion.bash +26 -2
  87. package/scripts/completion.zsh +10 -4
  88. package/scripts/render-ogp-overview-video.mjs +417 -0
  89. package/skills/ogp/SKILL.md +1 -1
  90. package/skills/ogp-expose/SKILL.md +1 -1
  91. package/skills/ogp-project/SKILL.md +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dp-pcs/ogp",
3
- "version": "0.5.0",
3
+ "version": "0.7.0-rc.1",
4
4
  "description": "Open Gateway Protocol (OGP) - Peer-to-peer federation daemon for OpenClaw AI gateways with cryptographic signatures",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -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
 
@@ -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('<', '&lt;')
147
+ .replaceAll('>', '&gt;')
148
+ .replaceAll('"', '&quot;');
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}`);
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  skill_name: ogp
3
- version: 2.6.0
3
+ version: 0.6.0
4
4
  description: >
5
5
  OGP (Open Gateway Protocol) — federated agent communication, peer management,
6
6
  and project collaboration across OpenClaw and Hermes gateways. Use when the user
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  skill_name: ogp-expose
3
- version: 0.4.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:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  skill_name: ogp-project
3
- version: 2.2.0
3
+ version: 0.6.0
4
4
  description: >
5
5
  Tool-agnostic project collaboration for AI assistants. Users keep their own tools
6
6
  (Linear, Jira, Obsidian, GitHub, iCloud, local files — anything). This skill makes