egregore-artifacts 0.9.9 → 0.9.10
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/bin/cli.js +1 -1
- package/lib/index.js +4 -2
- package/lib/parsers/emissary.js +56 -0
- package/lib/shell.js +73 -0
- package/lib/templates/emissary.js +266 -0
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { execSync } from 'node:child_process';
|
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
|
|
7
|
-
const KNOWN_TYPES = ['quest', 'handoff', 'handoff-v1', 'activity', 'document', 'board', 'network'];
|
|
7
|
+
const KNOWN_TYPES = ['quest', 'handoff', 'handoff-v1', 'emissary', 'activity', 'document', 'board', 'network'];
|
|
8
8
|
const args = process.argv.slice(2);
|
|
9
9
|
|
|
10
10
|
let type, filePath;
|
package/lib/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { renderToHtml, renderSpecToHtml } from './render.js';
|
|
|
6
6
|
import { parseQuest } from './parsers/quest.js';
|
|
7
7
|
import { parseHandoff } from './parsers/handoff.js';
|
|
8
8
|
import { parseHandoffV1 } from './parsers/handoff-v1.js';
|
|
9
|
+
import { parseEmissary } from './parsers/emissary.js';
|
|
9
10
|
import { parseActivity } from './parsers/activity.js';
|
|
10
11
|
import { parseDocument } from './parsers/document.js';
|
|
11
12
|
import { parseBoard } from './parsers/board.js';
|
|
@@ -13,6 +14,7 @@ import { parseNetwork } from './parsers/network.js';
|
|
|
13
14
|
import { questTemplate } from './templates/quest.js';
|
|
14
15
|
import { handoffTemplate } from './templates/handoff.js';
|
|
15
16
|
import { handoffV1Template } from './templates/handoff-v1.js';
|
|
17
|
+
import { emissaryTemplate } from './templates/emissary.js';
|
|
16
18
|
import { activityTemplate } from './templates/activity.js';
|
|
17
19
|
import { documentTemplate } from './templates/document.js';
|
|
18
20
|
import { boardTemplate } from './templates/board.js';
|
|
@@ -21,8 +23,8 @@ import { openInBrowser } from './open.js';
|
|
|
21
23
|
import { setLinkContext, clearLinkContext } from './markdown.js';
|
|
22
24
|
import { derivedParent, loadComments } from './comments.js';
|
|
23
25
|
|
|
24
|
-
const PARSERS = { quest: parseQuest, handoff: parseHandoff, 'handoff-v1': parseHandoffV1, activity: parseActivity, document: parseDocument, board: parseBoard, network: parseNetwork };
|
|
25
|
-
const TEMPLATES = { quest: questTemplate, handoff: handoffTemplate, 'handoff-v1': handoffV1Template, activity: activityTemplate, document: documentTemplate, board: boardTemplate, network: networkTemplate };
|
|
26
|
+
const PARSERS = { quest: parseQuest, handoff: parseHandoff, 'handoff-v1': parseHandoffV1, emissary: parseEmissary, activity: parseActivity, document: parseDocument, board: parseBoard, network: parseNetwork };
|
|
27
|
+
const TEMPLATES = { quest: questTemplate, handoff: handoffTemplate, 'handoff-v1': handoffV1Template, emissary: emissaryTemplate, activity: activityTemplate, document: documentTemplate, board: boardTemplate, network: networkTemplate };
|
|
26
28
|
|
|
27
29
|
const COMMENTABLE_TYPES = new Set(['quest', 'handoff', 'document']);
|
|
28
30
|
export async function generateArtifact(type, input, options = {}) {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Parse the emissary human-render input.
|
|
2
|
+
//
|
|
3
|
+
// An emissary's human render is METADATA-ONLY. The input is NOT the full
|
|
4
|
+
// handoff v1 artifact — it is a small projection the API composes
|
|
5
|
+
// (topic / summary / kind / distribution_mode / author / CTA URLs /
|
|
6
|
+
// render mode), deliberately excluding receiver_instructions,
|
|
7
|
+
// body.prose, and body.executable_spec. Those live only in the agent
|
|
8
|
+
// payload at /emissary/e/{id}/raw and must never reach the human render.
|
|
9
|
+
//
|
|
10
|
+
// Input: file path to a JSON file, OR a JSON string, OR a parsed object.
|
|
11
|
+
// Output: the normalized object the emissary template consumes.
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
|
|
14
|
+
export function parseEmissary(input) {
|
|
15
|
+
let obj;
|
|
16
|
+
|
|
17
|
+
if (typeof input === 'string') {
|
|
18
|
+
if (input.trim().startsWith('{')) {
|
|
19
|
+
obj = JSON.parse(input);
|
|
20
|
+
} else {
|
|
21
|
+
if (!fs.existsSync(input)) throw new Error(`File not found: ${input}`);
|
|
22
|
+
obj = JSON.parse(fs.readFileSync(input, 'utf-8'));
|
|
23
|
+
}
|
|
24
|
+
} else if (typeof input === 'object' && input !== null) {
|
|
25
|
+
obj = input;
|
|
26
|
+
} else {
|
|
27
|
+
throw new Error('parseEmissary expects a file path, JSON string, or object');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!obj.topic) {
|
|
31
|
+
throw new Error('emissary render input is missing required field: topic');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const renderMode = obj.render_mode === 'custom' ? 'custom' : 'default';
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
id: obj.id || '',
|
|
38
|
+
topic: obj.topic,
|
|
39
|
+
summary: obj.summary || '',
|
|
40
|
+
kind: obj.kind || 'handoff',
|
|
41
|
+
distribution_mode: obj.distribution_mode || 'public',
|
|
42
|
+
author_name: obj.author_name || '',
|
|
43
|
+
author_verified: Boolean(obj.author_verified),
|
|
44
|
+
created_at: obj.created_at || null,
|
|
45
|
+
expires_at: obj.expires_at || null,
|
|
46
|
+
render_url: obj.render_url || '',
|
|
47
|
+
raw_url: obj.raw_url || '',
|
|
48
|
+
render_mode: renderMode,
|
|
49
|
+
// Custom user HTML — only honored for render_mode === 'custom'. The
|
|
50
|
+
// template embeds it inside a sandboxed iframe; it is never trusted
|
|
51
|
+
// as page chrome.
|
|
52
|
+
render_html: renderMode === 'custom' ? (obj.render_html || '') : null,
|
|
53
|
+
// Convenience for the shell title.
|
|
54
|
+
title: obj.topic,
|
|
55
|
+
};
|
|
56
|
+
}
|
package/lib/shell.js
CHANGED
|
@@ -208,6 +208,33 @@ export function htmlShell(bodyHtml, { title = 'Egregore Artifact', type = 'artif
|
|
|
208
208
|
color: var(--muted);
|
|
209
209
|
margin-bottom: 0.75rem;
|
|
210
210
|
}
|
|
211
|
+
.eg-copy-row {
|
|
212
|
+
display: flex;
|
|
213
|
+
flex-wrap: wrap;
|
|
214
|
+
gap: 8px;
|
|
215
|
+
margin-top: 12px;
|
|
216
|
+
}
|
|
217
|
+
.eg-copy-button {
|
|
218
|
+
appearance: none;
|
|
219
|
+
border: 1px solid var(--border);
|
|
220
|
+
background: var(--surface);
|
|
221
|
+
color: var(--black);
|
|
222
|
+
border-radius: 5px;
|
|
223
|
+
padding: 6px 10px;
|
|
224
|
+
font-family: var(--font-mono);
|
|
225
|
+
font-size: 12px;
|
|
226
|
+
line-height: 1.2;
|
|
227
|
+
cursor: pointer;
|
|
228
|
+
}
|
|
229
|
+
.eg-copy-button:hover {
|
|
230
|
+
border-color: var(--terracotta);
|
|
231
|
+
color: var(--terracotta);
|
|
232
|
+
}
|
|
233
|
+
.eg-copy-button.eg-copy-ok {
|
|
234
|
+
background: var(--success-bg);
|
|
235
|
+
border-color: var(--success-fg);
|
|
236
|
+
color: var(--success-fg);
|
|
237
|
+
}
|
|
211
238
|
|
|
212
239
|
/* Text */
|
|
213
240
|
.eg-lead {
|
|
@@ -734,6 +761,52 @@ export function htmlShell(bodyHtml, { title = 'Egregore Artifact', type = 'artif
|
|
|
734
761
|
<div class="eg-artifact" data-type="${escapeHtml(type)}">
|
|
735
762
|
${bodyHtml}
|
|
736
763
|
</div>
|
|
764
|
+
<script>
|
|
765
|
+
// Generic clipboard affordance for rendered artifacts. Buttons declare
|
|
766
|
+
// their payload with data-copy-text so templates do not need custom JS.
|
|
767
|
+
(function() {
|
|
768
|
+
function copyText(text) {
|
|
769
|
+
if (!text) return Promise.reject(new Error('nothing to copy'));
|
|
770
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
771
|
+
return navigator.clipboard.writeText(text);
|
|
772
|
+
}
|
|
773
|
+
return new Promise(function(resolve, reject) {
|
|
774
|
+
var ta = document.createElement('textarea');
|
|
775
|
+
ta.value = text;
|
|
776
|
+
ta.setAttribute('readonly', '');
|
|
777
|
+
ta.style.position = 'fixed';
|
|
778
|
+
ta.style.left = '-9999px';
|
|
779
|
+
document.body.appendChild(ta);
|
|
780
|
+
ta.select();
|
|
781
|
+
try {
|
|
782
|
+
if (document.execCommand && document.execCommand('copy')) resolve();
|
|
783
|
+
else reject(new Error('copy unavailable'));
|
|
784
|
+
} catch (e) {
|
|
785
|
+
reject(e);
|
|
786
|
+
} finally {
|
|
787
|
+
document.body.removeChild(ta);
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
document.addEventListener('click', function(e) {
|
|
793
|
+
var btn = e.target.closest && e.target.closest('[data-copy-text]');
|
|
794
|
+
if (!btn) return;
|
|
795
|
+
var original = btn.getAttribute('data-copy-label') || btn.textContent;
|
|
796
|
+
copyText(btn.getAttribute('data-copy-text')).then(function() {
|
|
797
|
+
btn.textContent = 'Copied';
|
|
798
|
+
btn.classList.add('eg-copy-ok');
|
|
799
|
+
setTimeout(function() {
|
|
800
|
+
btn.textContent = original;
|
|
801
|
+
btn.classList.remove('eg-copy-ok');
|
|
802
|
+
}, 1600);
|
|
803
|
+
}, function() {
|
|
804
|
+
btn.textContent = 'Select text';
|
|
805
|
+
setTimeout(function() { btn.textContent = original; }, 1600);
|
|
806
|
+
});
|
|
807
|
+
});
|
|
808
|
+
})();
|
|
809
|
+
</script>
|
|
737
810
|
<script>
|
|
738
811
|
// Comment composer — assembles a structured payload and copies it to the
|
|
739
812
|
// clipboard. The user pastes into Claude; the /comment skill recognizes
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// Emissary human-render → React element tree.
|
|
2
|
+
//
|
|
3
|
+
// METADATA-ONLY. This template renders the clean public preview shown at
|
|
4
|
+
// egregore.xyz/emissary/e/{id}: title, author + verified badge,
|
|
5
|
+
// distribution-mode badge, the one-paragraph summary, a CTA, the
|
|
6
|
+
// discovery block, and a footer. It deliberately renders NONE of the
|
|
7
|
+
// agent payload — no receiver_instructions, no body.prose, no
|
|
8
|
+
// executable_spec. The agent payload lives only at .../raw.
|
|
9
|
+
//
|
|
10
|
+
// The CTA is the PRIMARY consumer-chat channel for naive fetchers: a
|
|
11
|
+
// prominent, copy-able launch prompt that pairs user authorization with
|
|
12
|
+
// the /raw JSON endpoint. A structured source block also names /raw for
|
|
13
|
+
// readability extraction.
|
|
14
|
+
//
|
|
15
|
+
// For render_mode === 'custom', user-provided HTML/CSS is embedded in a
|
|
16
|
+
// sandboxed <iframe sandbox="allow-scripts" srcdoc="…">. Without
|
|
17
|
+
// allow-same-origin the iframe runs in a unique opaque origin — its
|
|
18
|
+
// scripts cannot read egregore.xyz cookies, localStorage, or auth
|
|
19
|
+
// tokens. Egregore still owns the page chrome (header, discovery block,
|
|
20
|
+
// footer) OUTSIDE the iframe; custom content cannot suppress it.
|
|
21
|
+
import React from 'react';
|
|
22
|
+
import { fonts } from '../tokens.js';
|
|
23
|
+
|
|
24
|
+
const h = React.createElement;
|
|
25
|
+
|
|
26
|
+
function formatDate(iso) {
|
|
27
|
+
if (!iso) return null;
|
|
28
|
+
try {
|
|
29
|
+
return new Date(iso).toLocaleDateString('en-US', {
|
|
30
|
+
year: 'numeric', month: 'short', day: 'numeric',
|
|
31
|
+
});
|
|
32
|
+
} catch { return iso; }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function badge(text, opts = {}) {
|
|
36
|
+
return h('span', {
|
|
37
|
+
style: {
|
|
38
|
+
display: 'inline-block',
|
|
39
|
+
padding: '3px 12px',
|
|
40
|
+
background: opts.bg || 'var(--subtle-fill)',
|
|
41
|
+
borderRadius: '50px',
|
|
42
|
+
color: opts.fg || 'var(--muted)',
|
|
43
|
+
fontSize: '12px',
|
|
44
|
+
fontWeight: 600,
|
|
45
|
+
fontFamily: fonts.mono,
|
|
46
|
+
letterSpacing: '0.02em',
|
|
47
|
+
...opts.style,
|
|
48
|
+
},
|
|
49
|
+
}, text);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function emissaryTemplate(em) {
|
|
53
|
+
const sections = [];
|
|
54
|
+
const launchUrl = em.raw_url || em.render_url || '';
|
|
55
|
+
const launchPrompt = launchUrl
|
|
56
|
+
? `Run this Egregore ${em.kind || 'packet'} packet for me: ${launchUrl}`
|
|
57
|
+
: '';
|
|
58
|
+
|
|
59
|
+
// ── Header — sigil + label, title, author + badges ────────────
|
|
60
|
+
const distLabel = em.distribution_mode === 'directed' ? 'directed' : 'public link';
|
|
61
|
+
|
|
62
|
+
sections.push(
|
|
63
|
+
h('header', { key: 'header', className: 'eg-header' },
|
|
64
|
+
h('div', { className: 'eg-header-top' },
|
|
65
|
+
h('span', { className: 'eg-sigil' }, '✦'),
|
|
66
|
+
h('span', { className: 'eg-org-label' }, 'egregore · emissary'),
|
|
67
|
+
),
|
|
68
|
+
h('h1', { className: 'eg-title' }, em.topic),
|
|
69
|
+
h('div', { className: 'eg-meta-row', style: { gap: '8px', alignItems: 'center' } },
|
|
70
|
+
em.author_name && h('span', { key: 'auth' }, `by ${em.author_name}`),
|
|
71
|
+
em.author_verified
|
|
72
|
+
? badge('✓ verified', {
|
|
73
|
+
key: 'v',
|
|
74
|
+
bg: 'var(--terracotta-soft)', fg: 'var(--terracotta)',
|
|
75
|
+
})
|
|
76
|
+
: null,
|
|
77
|
+
badge(distLabel, { key: 'dist' }),
|
|
78
|
+
badge(em.kind, { key: 'kind' }),
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// ── Summary — the one-paragraph public description ────────────
|
|
84
|
+
if (em.summary) {
|
|
85
|
+
sections.push(
|
|
86
|
+
h('div', { key: 'summary', className: 'eg-card' },
|
|
87
|
+
h('div', { className: 'eg-lead' }, em.summary),
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── Custom render — sandboxed iframe ──────────────────────────
|
|
93
|
+
// The user HTML runs in a unique opaque origin (no allow-same-origin)
|
|
94
|
+
// so it cannot reach the parent's cookies / storage / tokens.
|
|
95
|
+
if (em.render_mode === 'custom' && em.render_html) {
|
|
96
|
+
sections.push(
|
|
97
|
+
h('div', { key: 'custom', className: 'eg-card' },
|
|
98
|
+
h('iframe', {
|
|
99
|
+
sandbox: 'allow-scripts',
|
|
100
|
+
srcDoc: em.render_html,
|
|
101
|
+
title: 'Custom emissary render',
|
|
102
|
+
style: {
|
|
103
|
+
width: '100%',
|
|
104
|
+
minHeight: '320px',
|
|
105
|
+
border: '1px solid var(--border)',
|
|
106
|
+
borderRadius: '8px',
|
|
107
|
+
background: 'var(--surface)',
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── CTA ───────────────────────────────────────────────────────
|
|
115
|
+
sections.push(
|
|
116
|
+
h('div', { key: 'cta', className: 'eg-card' },
|
|
117
|
+
h('div', {
|
|
118
|
+
style: {
|
|
119
|
+
fontSize: '15px', fontWeight: 600, marginBottom: '6px',
|
|
120
|
+
fontFamily: fonts.sans,
|
|
121
|
+
},
|
|
122
|
+
}, 'Engage this emissary'),
|
|
123
|
+
h('div', { style: { color: 'var(--muted)', fontSize: '14px' } },
|
|
124
|
+
'Copy the launch prompt into Claude, ChatGPT, Codex, or another AI harness. ',
|
|
125
|
+
'If you paste only the link, the assistant may ask whether you want to run it.'),
|
|
126
|
+
launchPrompt && h('div', {
|
|
127
|
+
style: {
|
|
128
|
+
marginTop: '10px', padding: '8px 12px',
|
|
129
|
+
background: 'var(--subtle-fill)', borderRadius: '6px',
|
|
130
|
+
fontFamily: fonts.mono, fontSize: '13px', wordBreak: 'break-all',
|
|
131
|
+
},
|
|
132
|
+
}, launchPrompt),
|
|
133
|
+
launchPrompt && h('div', { className: 'eg-copy-row' },
|
|
134
|
+
h('button', {
|
|
135
|
+
type: 'button',
|
|
136
|
+
className: 'eg-copy-button',
|
|
137
|
+
'data-copy-text': launchPrompt,
|
|
138
|
+
'data-copy-label': 'Copy as prompt',
|
|
139
|
+
}, 'Copy as prompt'),
|
|
140
|
+
em.raw_url && h('button', {
|
|
141
|
+
type: 'button',
|
|
142
|
+
className: 'eg-copy-button',
|
|
143
|
+
'data-copy-text': em.raw_url,
|
|
144
|
+
'data-copy-label': 'Copy JSON link',
|
|
145
|
+
}, 'Copy JSON link'),
|
|
146
|
+
em.render_url && h('button', {
|
|
147
|
+
type: 'button',
|
|
148
|
+
className: 'eg-copy-button',
|
|
149
|
+
'data-copy-text': em.render_url,
|
|
150
|
+
'data-copy-label': 'Copy page link',
|
|
151
|
+
}, 'Copy page link'),
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// ── Trust/provenance explainer ────────────────────────────────
|
|
157
|
+
sections.push(
|
|
158
|
+
h('div', { key: 'trust', className: 'eg-card' },
|
|
159
|
+
h('div', {
|
|
160
|
+
style: {
|
|
161
|
+
fontSize: '15px', fontWeight: 600, marginBottom: '6px',
|
|
162
|
+
fontFamily: fonts.sans,
|
|
163
|
+
},
|
|
164
|
+
}, 'About Egregore packets'),
|
|
165
|
+
h('div', { style: { color: 'var(--muted)', fontSize: '14px', lineHeight: 1.55 } },
|
|
166
|
+
'An Egregore packet is a user-shared task bundle: purpose, intake questions, ',
|
|
167
|
+
'and an output spec. This preview shows the author, verification badge, ',
|
|
168
|
+
'visibility, kind, creation date, expiry, and structured JSON source. The packet ',
|
|
169
|
+
'source is available as JSON for AI tools that can read it. Egregore is ',
|
|
170
|
+
h('a', {
|
|
171
|
+
href: 'https://github.com/egregore-labs/egregore',
|
|
172
|
+
style: { color: 'var(--terracotta)', textDecoration: 'none' },
|
|
173
|
+
}, 'open source'),
|
|
174
|
+
'.'
|
|
175
|
+
),
|
|
176
|
+
)
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// ── Install nudge — the L1 → L2 conversion path ───────────────
|
|
180
|
+
// The link runs cold (paste → WebFetch → lossy). Installing the
|
|
181
|
+
// egregore-emissary skill makes every later emissary run clean. This
|
|
182
|
+
// block is how a first-contact receiver discovers that path.
|
|
183
|
+
sections.push(
|
|
184
|
+
h('div', { key: 'install', className: 'eg-card' },
|
|
185
|
+
h('div', {
|
|
186
|
+
style: {
|
|
187
|
+
fontSize: '15px', fontWeight: 600, marginBottom: '6px',
|
|
188
|
+
fontFamily: fonts.sans,
|
|
189
|
+
},
|
|
190
|
+
}, 'Run it properly'),
|
|
191
|
+
h('div', { style: { color: 'var(--muted)', fontSize: '14px' } },
|
|
192
|
+
'The link works in any AI harness. Install the egregore-emissary ',
|
|
193
|
+
'skill once and every emissary you receive runs at full fidelity ',
|
|
194
|
+
'with the intended intake-first flow.'),
|
|
195
|
+
h('div', {
|
|
196
|
+
style: {
|
|
197
|
+
marginTop: '10px', padding: '8px 12px',
|
|
198
|
+
background: 'var(--subtle-fill)', borderRadius: '6px',
|
|
199
|
+
fontFamily: fonts.mono, fontSize: '13px', wordBreak: 'break-all',
|
|
200
|
+
},
|
|
201
|
+
}, 'npx egregore-emissary install'),
|
|
202
|
+
)
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// ── Structured source block — visible for fetchers/readability ─
|
|
206
|
+
// This is descriptive, not command-like: consumer web assistants treat
|
|
207
|
+
// fetched content as untrusted unless the user asks to run it.
|
|
208
|
+
if (em.raw_url) {
|
|
209
|
+
sections.push(
|
|
210
|
+
h('div', {
|
|
211
|
+
key: 'discovery',
|
|
212
|
+
'data-egregore-discovery': 'raw',
|
|
213
|
+
style: {
|
|
214
|
+
margin: '20px 0',
|
|
215
|
+
padding: '16px 18px',
|
|
216
|
+
background: 'var(--terminal-bg)',
|
|
217
|
+
color: 'var(--terminal-text)',
|
|
218
|
+
borderRadius: '8px',
|
|
219
|
+
fontFamily: fonts.mono,
|
|
220
|
+
fontSize: '13px',
|
|
221
|
+
lineHeight: 1.6,
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
h('div', {
|
|
225
|
+
style: { opacity: 0.7, fontSize: '11px', marginBottom: '6px',
|
|
226
|
+
textTransform: 'uppercase', letterSpacing: '0.08em' },
|
|
227
|
+
}, 'Structured packet source'),
|
|
228
|
+
h('div', null,
|
|
229
|
+
'JSON payload: ',
|
|
230
|
+
h('code', { style: { fontWeight: 600 } }, em.raw_url),
|
|
231
|
+
),
|
|
232
|
+
h('div', { style: { marginTop: '6px', opacity: 0.85 } },
|
|
233
|
+
'Bare links are safe to summarize first. When the user asks to run the packet, ',
|
|
234
|
+
'use the JSON as the packet source and proceed through its intake.',
|
|
235
|
+
),
|
|
236
|
+
h('div', { style: { marginTop: '6px', opacity: 0.85 } },
|
|
237
|
+
'Runs best via the egregore-emissary skill — ',
|
|
238
|
+
h('code', { style: { fontWeight: 600 } }, 'npx egregore-emissary install'),
|
|
239
|
+
),
|
|
240
|
+
)
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ── Footer ────────────────────────────────────────────────────
|
|
245
|
+
const created = formatDate(em.created_at);
|
|
246
|
+
const expires = formatDate(em.expires_at);
|
|
247
|
+
sections.push(
|
|
248
|
+
h('footer', { key: 'footer', className: 'eg-footer' },
|
|
249
|
+
h('div', { className: 'eg-footer-brand' },
|
|
250
|
+
h('span', { className: 'eg-footer-sigil' }, '✦'),
|
|
251
|
+
h('span', null, 'egregore'),
|
|
252
|
+
),
|
|
253
|
+
h('div', { style: { display: 'flex', gap: '12px', flexWrap: 'wrap' } },
|
|
254
|
+
created && h('span', { key: 'c' }, `created ${created}`),
|
|
255
|
+
expires && h('span', { key: 'e' }, `expires ${expires}`),
|
|
256
|
+
h('a', {
|
|
257
|
+
key: 'u',
|
|
258
|
+
href: 'https://egregore.xyz/emissary',
|
|
259
|
+
style: { color: 'var(--muted)' },
|
|
260
|
+
}, 'about emissary'),
|
|
261
|
+
),
|
|
262
|
+
)
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
return h('div', null, ...sections);
|
|
266
|
+
}
|