enigma-memory 0.1.1 → 0.1.2
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/apps/browser-extension/manifest.json +41 -0
- package/apps/browser-extension/src/background.js +88 -0
- package/apps/browser-extension/src/content-script.js +602 -0
- package/apps/browser-extension/src/native-bridge.js +289 -0
- package/apps/cli/bin/enigma.mjs +209 -2
- package/apps/desktop/src/tray.js +231 -0
- package/docs/browser-extension-install.md +169 -0
- package/docs/developer-ecosystem.md +74 -0
- package/docs/hosted-cloud-product.md +68 -0
- package/docs/installers-and-desktop.md +76 -0
- package/docs/memory-benchmarks.md +51 -0
- package/docs/sdk-api.md +181 -0
- package/examples/ci/github-actions.yml +63 -0
- package/examples/node-basic-memory.mjs +84 -0
- package/package.json +20 -1
- package/packages/connectors/src/index.js +274 -39
- package/packages/hosted-cloud/src/index.js +538 -0
- package/packages/mcp-server/src/index.js +1 -1
- package/scripts/build-installer-assets.mjs +273 -0
- package/scripts/package-browser-extension.mjs +473 -0
- package/scripts/run-memory-benchmarks.mjs +585 -0
- package/scripts/verify-registry-install.mjs +6 -1
- package/templates/mcp-client-config.json +10 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
const NATIVE_HOST = 'com.enigma.native_host';
|
|
2
|
+
const PROTOCOL = 'enigma.native.browser.v1';
|
|
3
|
+
const REQUEST_TIMEOUT_MS = 30000;
|
|
4
|
+
const MAX_CONTEXT_CHARS = 32000;
|
|
5
|
+
const SAFE_NATIVE_ERROR_CODE = /^[A-Z][A-Z0-9_]{0,31}$/;
|
|
6
|
+
|
|
7
|
+
export const SUPPORTED_PROVIDERS = Object.freeze({
|
|
8
|
+
chatgpt: Object.freeze({ id: 'chatgpt', label: 'ChatGPT', hosts: Object.freeze(['chatgpt.com', 'chat.openai.com']) }),
|
|
9
|
+
claude: Object.freeze({ id: 'claude', label: 'Claude', hosts: Object.freeze(['claude.ai']) }),
|
|
10
|
+
kimi: Object.freeze({ id: 'kimi', label: 'Kimi', hosts: Object.freeze(['kimi.com', 'www.kimi.com', 'kimi.moonshot.cn']) }),
|
|
11
|
+
perplexity: Object.freeze({ id: 'perplexity', label: 'Perplexity', hosts: Object.freeze(['perplexity.ai', 'www.perplexity.ai']) })
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const RECEIPT_RAW_FIELD_NAMES = new Set([
|
|
15
|
+
'memory',
|
|
16
|
+
'memories',
|
|
17
|
+
'plaintext',
|
|
18
|
+
'plainText',
|
|
19
|
+
'raw',
|
|
20
|
+
'rawMemory',
|
|
21
|
+
'rawMemories',
|
|
22
|
+
'context',
|
|
23
|
+
'contextText',
|
|
24
|
+
'text',
|
|
25
|
+
'content',
|
|
26
|
+
'prompt'
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
export function detectProviderFromUrl(url) {
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
parsed = new URL(String(url));
|
|
33
|
+
} catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (parsed.protocol !== 'https:') return undefined;
|
|
38
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
39
|
+
for (const provider of Object.values(SUPPORTED_PROVIDERS)) {
|
|
40
|
+
if (provider.hosts.includes(hostname)) return provider;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function requestContextPack(input) {
|
|
46
|
+
const provider = requireSupportedProvider(input?.url, input?.providerId);
|
|
47
|
+
const response = await sendNativeMessage({
|
|
48
|
+
protocol: PROTOCOL,
|
|
49
|
+
id: requestId(),
|
|
50
|
+
type: 'enigma.browser.context.request',
|
|
51
|
+
provider: provider.id,
|
|
52
|
+
page: sanitizePage(input, provider),
|
|
53
|
+
selection: sanitizeSelection(input?.selection),
|
|
54
|
+
requirements: {
|
|
55
|
+
custody: 'local-only',
|
|
56
|
+
approval: 'user-click',
|
|
57
|
+
receipt: 'required',
|
|
58
|
+
receiptPlaintext: 'forbidden',
|
|
59
|
+
providerNativeMemory: 'cache-only',
|
|
60
|
+
storage: 'transient-extension-memory'
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return validateContextResponse(response);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function recordInsertionReceipt(input) {
|
|
68
|
+
const provider = requireSupportedProvider(input?.url, input?.providerId);
|
|
69
|
+
const receipt = sanitizeReceipt(input?.receipt);
|
|
70
|
+
const response = await sendNativeMessage({
|
|
71
|
+
protocol: PROTOCOL,
|
|
72
|
+
id: requestId(),
|
|
73
|
+
type: 'enigma.browser.insertion.record',
|
|
74
|
+
provider: provider.id,
|
|
75
|
+
page: sanitizePage(input, provider),
|
|
76
|
+
insertion: {
|
|
77
|
+
mode: input?.mode === 'replace-selection' ? 'replace-selection' : 'insert-at-cursor',
|
|
78
|
+
target: typeof input?.target === 'string' ? input.target.slice(0, 80) : 'prompt',
|
|
79
|
+
insertedCharCount: nonNegativeInteger(input?.insertedCharCount),
|
|
80
|
+
insertedAt: requireString(input?.insertedAt, 'insertion.insertedAt', 80),
|
|
81
|
+
receipt
|
|
82
|
+
},
|
|
83
|
+
requirements: {
|
|
84
|
+
plaintextInRecord: 'forbidden',
|
|
85
|
+
receiptPlaintext: 'forbidden'
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (!response || response.protocol !== PROTOCOL || response.type !== 'enigma.browser.insertion.recorded') {
|
|
90
|
+
throw new Error('Native host returned an invalid insertion receipt acknowledgement.');
|
|
91
|
+
}
|
|
92
|
+
if (response.ok !== true) {
|
|
93
|
+
throw new Error(nativeHostErrorMessage('Native host rejected insertion receipt.', response));
|
|
94
|
+
}
|
|
95
|
+
return Object.freeze({ ok: true, receipt });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function serializeError(error) {
|
|
99
|
+
if (error instanceof Error) return { message: sanitizeSerializableErrorMessage(error.message) };
|
|
100
|
+
return { message: 'Unexpected Enigma bridge error.' };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function requireSupportedProvider(url, expectedProviderId) {
|
|
104
|
+
const provider = detectProviderFromUrl(url);
|
|
105
|
+
if (!provider) throw new Error('Unsupported or unknown AI provider page.');
|
|
106
|
+
if (expectedProviderId !== undefined && expectedProviderId !== provider.id) {
|
|
107
|
+
throw new Error('Provider mismatch for active tab URL.');
|
|
108
|
+
}
|
|
109
|
+
return provider;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function sanitizePage(input, provider) {
|
|
113
|
+
const parsed = new URL(String(input?.url ?? ''));
|
|
114
|
+
return Object.freeze({
|
|
115
|
+
providerId: provider.id,
|
|
116
|
+
origin: parsed.origin,
|
|
117
|
+
hostname: parsed.hostname.toLowerCase(),
|
|
118
|
+
display: Object.freeze({
|
|
119
|
+
providerLabel: provider.label,
|
|
120
|
+
hostLabel: parsed.hostname.toLowerCase(),
|
|
121
|
+
titlePresent: typeof input?.title === 'string' && input.title.length > 0,
|
|
122
|
+
titleCharCount: typeof input?.title === 'string' ? input.title.length : 0
|
|
123
|
+
}),
|
|
124
|
+
topLevel: input?.topLevel !== false
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function sanitizeSelection(selection) {
|
|
129
|
+
if (!selection || typeof selection !== 'object') return undefined;
|
|
130
|
+
const text = typeof selection.text === 'string' ? selection.text : '';
|
|
131
|
+
if (text.length === 0) return undefined;
|
|
132
|
+
if (text.length > 4000) throw new Error('Selected page text exceeds 4000 characters.');
|
|
133
|
+
const source = typeof selection.source === 'string' && selection.source.length > 0 ? selection.source : 'user-selection';
|
|
134
|
+
if (source.length > 80) throw new Error('selection.source exceeds 80 characters.');
|
|
135
|
+
return Object.freeze({
|
|
136
|
+
text,
|
|
137
|
+
source
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function validateContextResponse(response) {
|
|
142
|
+
if (!response || response.protocol !== PROTOCOL || response.type !== 'enigma.browser.context.response') {
|
|
143
|
+
throw new Error('Native host returned an invalid Enigma context response.');
|
|
144
|
+
}
|
|
145
|
+
if (response.ok !== true) {
|
|
146
|
+
throw new Error(nativeHostErrorMessage('Native host rejected context request.', response));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const text = response.context?.text;
|
|
150
|
+
if (typeof text !== 'string' || text.length === 0) {
|
|
151
|
+
throw new Error('Native host did not return insertable context.');
|
|
152
|
+
}
|
|
153
|
+
if (text.length > MAX_CONTEXT_CHARS) {
|
|
154
|
+
throw new Error(`Native host context exceeds ${MAX_CONTEXT_CHARS} characters.`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const receipt = sanitizeReceipt(response.receipt);
|
|
158
|
+
return Object.freeze({
|
|
159
|
+
ok: true,
|
|
160
|
+
context: Object.freeze({
|
|
161
|
+
text,
|
|
162
|
+
mime: response.context?.mime === 'text/markdown' ? 'text/markdown' : 'text/plain',
|
|
163
|
+
charCount: text.length
|
|
164
|
+
}),
|
|
165
|
+
receipt
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function sanitizeReceipt(receipt) {
|
|
170
|
+
if (!receipt || typeof receipt !== 'object' || Array.isArray(receipt)) {
|
|
171
|
+
throw new Error('Enigma native host must return a receipt object.');
|
|
172
|
+
}
|
|
173
|
+
if (containsRawReceiptField(receipt)) {
|
|
174
|
+
throw new Error('Receipt contains a forbidden plaintext-like field.');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const id = requireString(receipt.id, 'receipt.id', 160);
|
|
178
|
+
const commitment = requireString(receipt.commitment ?? receipt.digest, 'receipt.commitment', 256);
|
|
179
|
+
const digestAlgorithm = requireString(receipt.digestAlgorithm, 'receipt.digestAlgorithm', 80);
|
|
180
|
+
const createdAt = requireString(receipt.createdAt, 'receipt.createdAt', 80);
|
|
181
|
+
|
|
182
|
+
return Object.freeze({ id, commitment, digestAlgorithm, createdAt });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function containsRawReceiptField(value, depth = 0) {
|
|
186
|
+
if (depth > 12 || value === null || typeof value !== 'object') return false;
|
|
187
|
+
if (Array.isArray(value)) return value.some((item) => containsRawReceiptField(item, depth + 1));
|
|
188
|
+
for (const [key, child] of Object.entries(value)) {
|
|
189
|
+
if (RECEIPT_RAW_FIELD_NAMES.has(key)) return true;
|
|
190
|
+
if (containsRawReceiptField(child, depth + 1)) return true;
|
|
191
|
+
}
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function requireString(value, name, maxLength) {
|
|
196
|
+
if (typeof value !== 'string' || value.length === 0) throw new Error(`Missing ${name}.`);
|
|
197
|
+
if (value.length > maxLength) throw new Error(`${name} exceeds ${maxLength} characters.`);
|
|
198
|
+
return value;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function nonNegativeInteger(value) {
|
|
202
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function requestId() {
|
|
206
|
+
const random = new Uint8Array(16);
|
|
207
|
+
crypto.getRandomValues(random);
|
|
208
|
+
return Array.from(random, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function sendNativeMessage(message) {
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
let settled = false;
|
|
214
|
+
const timeout = setTimeout(() => {
|
|
215
|
+
if (settled) return;
|
|
216
|
+
settled = true;
|
|
217
|
+
reject(new Error('Timed out waiting for Enigma native host.'));
|
|
218
|
+
}, REQUEST_TIMEOUT_MS);
|
|
219
|
+
|
|
220
|
+
chrome.runtime.sendNativeMessage(NATIVE_HOST, message, (response) => {
|
|
221
|
+
if (settled) return;
|
|
222
|
+
settled = true;
|
|
223
|
+
clearTimeout(timeout);
|
|
224
|
+
|
|
225
|
+
const lastError = chrome.runtime.lastError;
|
|
226
|
+
if (lastError) {
|
|
227
|
+
reject(new Error(nativeHostErrorMessage('Unable to reach Enigma native host.', lastError)));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (response && response.id !== message.id) {
|
|
231
|
+
reject(new Error('Native host response id did not match the request id.'));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
resolve(response);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function nativeHostErrorMessage(fallback, detail) {
|
|
240
|
+
const code = safeNativeErrorCode(detail);
|
|
241
|
+
return code ? `${fallback} (${code})` : fallback;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function safeNativeErrorCode(detail) {
|
|
245
|
+
if (!detail || typeof detail !== 'object') return undefined;
|
|
246
|
+
const code = detail.code ?? detail.errorCode;
|
|
247
|
+
if (typeof code !== 'string') return undefined;
|
|
248
|
+
const trimmed = code.trim();
|
|
249
|
+
return SAFE_NATIVE_ERROR_CODE.test(trimmed) ? trimmed : undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function sanitizeSerializableErrorMessage(message) {
|
|
253
|
+
if (typeof message !== 'string') return 'Unexpected Enigma bridge error.';
|
|
254
|
+
const trimmed = message.trim();
|
|
255
|
+
if (!trimmed) return 'Unexpected Enigma bridge error.';
|
|
256
|
+
if (trimmed.length > 240 || looksLikeSensitiveErrorText(trimmed) || !isKnownSafeErrorMessage(trimmed)) {
|
|
257
|
+
return 'Enigma action failed without exposing local memory.';
|
|
258
|
+
}
|
|
259
|
+
return trimmed;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function isKnownSafeErrorMessage(message) {
|
|
263
|
+
switch (message) {
|
|
264
|
+
case 'Unsupported or unknown AI provider page.':
|
|
265
|
+
case 'Provider mismatch for active tab URL.':
|
|
266
|
+
case 'Message provider does not match the active page.':
|
|
267
|
+
case 'Unsupported Enigma extension message.':
|
|
268
|
+
case 'Native host rejected context request.':
|
|
269
|
+
case 'Native host rejected insertion receipt.':
|
|
270
|
+
case 'Unable to reach Enigma native host.':
|
|
271
|
+
case 'Timed out waiting for Enigma native host.':
|
|
272
|
+
case 'Native host returned an invalid Enigma context response.':
|
|
273
|
+
case 'Native host returned an invalid insertion receipt acknowledgement.':
|
|
274
|
+
case 'Native host response id did not match the request id.':
|
|
275
|
+
case 'Native host did not return insertable context.':
|
|
276
|
+
case 'Enigma native host must return a receipt object.':
|
|
277
|
+
case 'Receipt contains a forbidden plaintext-like field.':
|
|
278
|
+
case 'Selected page text exceeds 4000 characters.':
|
|
279
|
+
case 'selection.source exceeds 80 characters.':
|
|
280
|
+
return true;
|
|
281
|
+
default:
|
|
282
|
+
return /^(?:Native host context exceeds|Missing [A-Za-z0-9_.]+|[A-Za-z0-9_.]+ exceeds) \d+ characters\.$/.test(message) ||
|
|
283
|
+
/^Missing [A-Za-z0-9_.]+\.$/.test(message);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function looksLikeSensitiveErrorText(text) {
|
|
288
|
+
return /https?:\/\/|[?&#](?:token|key|prompt|q)=|\b(selectedText|selected_text|plaintext|rawMemory|raw_memory|contextText|context_text)\b|["'](?:text|content|memory|raw|prompt|receipt)["']\s*:/i.test(text);
|
|
289
|
+
}
|
package/apps/cli/bin/enigma.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { createServer as createHttpServer } from 'node:http';
|
|
3
4
|
import { realpathSync } from 'node:fs';
|
|
4
5
|
import { access, mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
@@ -30,6 +31,24 @@ const DEFAULT_BUNDLE = '.enigma/bundle.json';
|
|
|
30
31
|
export const DEFAULT_RELAY_PORT = 8787;
|
|
31
32
|
export const DEFAULT_GATEWAY_PORT = 8797;
|
|
32
33
|
const DEFAULT_QUICKSTART_MEMORY = 'Enigma quickstart demo memory: local proof bundles can be created and verified without provider or cloud credentials.';
|
|
34
|
+
const DEFAULT_CROSS_MODEL_DEMO_BUNDLE = '.enigma/cross-model-demo-bundle.json';
|
|
35
|
+
const DEFAULT_CROSS_MODEL_MEMORY = 'Enigma cross-model demo memory: a local encrypted memory can be packaged for ChatGPT, Claude, Kimi, Cursor, and a local LLM without provider credentials.';
|
|
36
|
+
const CROSS_MODEL_PROFILES = Object.freeze([
|
|
37
|
+
{ id: 'chatgpt', provider: 'chatgpt', model: 'chatgpt-mcp-profile', label: 'ChatGPT' },
|
|
38
|
+
{ id: 'claude', provider: 'claude', model: 'claude-mcp-profile', label: 'Claude' },
|
|
39
|
+
{ id: 'kimi', provider: 'kimi', model: 'kimi-mcp-profile', label: 'Kimi' },
|
|
40
|
+
{ id: 'cursor', provider: 'cursor', model: 'cursor-mcp-profile', label: 'Cursor' },
|
|
41
|
+
{ id: 'local-llm', provider: 'local', model: 'local-llm-profile', label: 'Local LLM' },
|
|
42
|
+
]);
|
|
43
|
+
const CROSS_MODEL_CLAIM_BOUNDARIES = Object.freeze({
|
|
44
|
+
local_only: true,
|
|
45
|
+
provider_credentials_required: false,
|
|
46
|
+
provider_native_memory_canonical: false,
|
|
47
|
+
provider_deletion_proof: false,
|
|
48
|
+
model_forgetting_proof: false,
|
|
49
|
+
roi_or_savings_guarantee: false,
|
|
50
|
+
compliance_certification: false,
|
|
51
|
+
});
|
|
33
52
|
const PACKAGE_JSON_URL = new URL('../../../package.json', import.meta.url);
|
|
34
53
|
const SPECS_URL = new URL('../../../specs/', import.meta.url);
|
|
35
54
|
const IMPORTERS = Object.freeze({
|
|
@@ -191,6 +210,73 @@ async function quickstartMemoryTextFromFlags(flags) {
|
|
|
191
210
|
return DEFAULT_QUICKSTART_MEMORY;
|
|
192
211
|
}
|
|
193
212
|
|
|
213
|
+
async function crossModelMemoryTextFromFlags(flags) {
|
|
214
|
+
const textFile = getFlag(flags, ['memory-file', 'memoryFile', 'text-file', 'textFile']);
|
|
215
|
+
if (textFile === undefined) return DEFAULT_CROSS_MODEL_MEMORY;
|
|
216
|
+
if (textFile === true || textFile === '') throw new Error('Missing required --memory-file.');
|
|
217
|
+
return readFile(resolve(String(textFile)), 'utf8');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function activeMemoryCount(vault) {
|
|
221
|
+
return vault.activeAddresses instanceof Set ? vault.activeAddresses.size : 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function sha256Json(value) {
|
|
225
|
+
return `sha256:${createHash('sha256').update(JSON.stringify(value)).digest('hex')}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function contextPackPublicDigest(pack) {
|
|
229
|
+
return sha256Json({
|
|
230
|
+
schema: pack.schema,
|
|
231
|
+
context_pack_id: pack.context_pack_id,
|
|
232
|
+
provider: pack.provider,
|
|
233
|
+
model: pack.model,
|
|
234
|
+
purpose: pack.purpose,
|
|
235
|
+
memory_addresses: pack.memory_addresses,
|
|
236
|
+
receipt_hashes: pack.receipt_hashes,
|
|
237
|
+
active_set_root: pack.active_set_root,
|
|
238
|
+
receipt_log_root: pack.receipt_log_root,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function publicReceiptRefs(receipts) {
|
|
243
|
+
return (Array.isArray(receipts) ? receipts : []).map((receipt) => ({
|
|
244
|
+
receipt_id: receipt.receipt_id,
|
|
245
|
+
operation: receipt.operation,
|
|
246
|
+
memory_addr: receipt.memory_addr,
|
|
247
|
+
provider: receipt.provider,
|
|
248
|
+
model: receipt.model,
|
|
249
|
+
event_hash: receipt.event_hash,
|
|
250
|
+
receipt_log_root: receipt.receipt_log_root,
|
|
251
|
+
timestamp: receipt.timestamp,
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function publicContextPackSummary(pack) {
|
|
256
|
+
const receipts = publicReceiptRefs(pack.receipts);
|
|
257
|
+
return {
|
|
258
|
+
schema: pack.schema,
|
|
259
|
+
context_pack_ref: `enigma://context-pack/${pack.context_pack_id}`,
|
|
260
|
+
context_pack_id: pack.context_pack_id,
|
|
261
|
+
context_pack_digest: contextPackPublicDigest(pack),
|
|
262
|
+
provider: pack.provider,
|
|
263
|
+
model: pack.model,
|
|
264
|
+
purpose: pack.purpose,
|
|
265
|
+
memory_addresses: Array.isArray(pack.memory_addresses) ? [...pack.memory_addresses] : [],
|
|
266
|
+
memory_count: Array.isArray(pack.memory_addresses) ? pack.memory_addresses.length : 0,
|
|
267
|
+
receipt_count: receipts.length,
|
|
268
|
+
receipt_hashes: Array.isArray(pack.receipt_hashes) ? [...pack.receipt_hashes] : [],
|
|
269
|
+
receipts,
|
|
270
|
+
active_set_root: pack.active_set_root,
|
|
271
|
+
receipt_log_root: pack.receipt_log_root,
|
|
272
|
+
content_redacted: true,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function demoBundleRef(bundleWasSupplied) {
|
|
277
|
+
return bundleWasSupplied ? 'supplied_bundle' : DEFAULT_CROSS_MODEL_DEMO_BUNDLE;
|
|
278
|
+
}
|
|
279
|
+
|
|
194
280
|
async function readPackageJson() {
|
|
195
281
|
return readJson(PACKAGE_JSON_URL);
|
|
196
282
|
}
|
|
@@ -488,6 +574,119 @@ export async function quickstartCommand(flags, io) {
|
|
|
488
574
|
return verifyReport.ok === true ? 0 : 1;
|
|
489
575
|
}
|
|
490
576
|
|
|
577
|
+
export async function crossModelDemoCommand(flags, io) {
|
|
578
|
+
const bundleFlag = getFlag(flags, ['bundle', 'file']);
|
|
579
|
+
if (bundleFlag === true || bundleFlag === '') throw new Error('Missing required --bundle.');
|
|
580
|
+
const bundleWasSupplied = bundleFlag !== undefined;
|
|
581
|
+
const bundleInput = bundleWasSupplied ? String(bundleFlag) : DEFAULT_CROSS_MODEL_DEMO_BUNDLE;
|
|
582
|
+
const bundlePath = resolve(bundleInput);
|
|
583
|
+
const out = getFlag(flags, ['out']);
|
|
584
|
+
if (out === true || out === '') throw new Error('Missing required --out.');
|
|
585
|
+
const outPath = out === undefined ? undefined : resolve(String(out));
|
|
586
|
+
if (outPath !== undefined) ensureDistinctOutputPaths([bundlePath, outPath]);
|
|
587
|
+
|
|
588
|
+
const memoryFileWasSupplied = getFlag(flags, ['memory-file', 'memoryFile', 'text-file', 'textFile']) !== undefined;
|
|
589
|
+
let bundleCreated = false;
|
|
590
|
+
let vault;
|
|
591
|
+
let passport;
|
|
592
|
+
let demoMemoryAddr;
|
|
593
|
+
|
|
594
|
+
if (!bundleWasSupplied) {
|
|
595
|
+
vault = createVault({
|
|
596
|
+
subjectId: 'cross-model-demo-user',
|
|
597
|
+
displayName: 'Cross-model demo user',
|
|
598
|
+
passphrase: 'local-cross-model-demo-passphrase',
|
|
599
|
+
});
|
|
600
|
+
passport = createPassport({ vault, subjectId: vault.subject_id, displayName: 'Cross-model demo user' });
|
|
601
|
+
const remembered = remember({
|
|
602
|
+
vault,
|
|
603
|
+
passport,
|
|
604
|
+
text: await crossModelMemoryTextFromFlags(flags),
|
|
605
|
+
purpose: 'cross_model_demo_memory',
|
|
606
|
+
purpose_tags: ['cross-model-demo'],
|
|
607
|
+
metadata: { source: memoryFileWasSupplied ? 'local file supplied to demo' : 'generic cross-model demo memory' },
|
|
608
|
+
});
|
|
609
|
+
demoMemoryAddr = remembered.memory_addr;
|
|
610
|
+
bundleCreated = true;
|
|
611
|
+
} else {
|
|
612
|
+
const existed = await fileExists(bundlePath);
|
|
613
|
+
if (!existed) {
|
|
614
|
+
await ensureBundle(bundlePath, flags);
|
|
615
|
+
bundleCreated = true;
|
|
616
|
+
}
|
|
617
|
+
({ vault, passport } = await loadState(bundlePath));
|
|
618
|
+
const remembered = remember({
|
|
619
|
+
vault,
|
|
620
|
+
passport,
|
|
621
|
+
text: await crossModelMemoryTextFromFlags(flags),
|
|
622
|
+
purpose: 'cross_model_demo_memory',
|
|
623
|
+
purpose_tags: ['cross-model-demo'],
|
|
624
|
+
metadata: { source: memoryFileWasSupplied ? 'local file supplied to demo' : 'generic cross-model demo memory' },
|
|
625
|
+
});
|
|
626
|
+
demoMemoryAddr = remembered.memory_addr;
|
|
627
|
+
}
|
|
628
|
+
const memorySource = memoryFileWasSupplied ? 'memory_file' : 'generic_demo';
|
|
629
|
+
|
|
630
|
+
const limit = integerFlag(flags, ['limit'], 'limit', 1);
|
|
631
|
+
if (limit < 1) throw new Error('--limit must be at least 1.');
|
|
632
|
+
const receiptCountBeforeProfiles = Array.isArray(vault.receipts) ? vault.receipts.length : 0;
|
|
633
|
+
const profiles = [];
|
|
634
|
+
for (const profile of CROSS_MODEL_PROFILES) {
|
|
635
|
+
const pack = compileContextPack({
|
|
636
|
+
vault,
|
|
637
|
+
passport,
|
|
638
|
+
provider: profile.provider,
|
|
639
|
+
model: profile.model,
|
|
640
|
+
query: 'memory follows me across models',
|
|
641
|
+
purpose: `cross_model_demo:${profile.id}`,
|
|
642
|
+
memory_addresses: [demoMemoryAddr],
|
|
643
|
+
limit,
|
|
644
|
+
});
|
|
645
|
+
const contextPack = publicContextPackSummary(pack);
|
|
646
|
+
profiles.push({
|
|
647
|
+
profile: profile.id,
|
|
648
|
+
label: profile.label,
|
|
649
|
+
provider: profile.provider,
|
|
650
|
+
model: profile.model,
|
|
651
|
+
context_pack_ref: contextPack.context_pack_ref,
|
|
652
|
+
context_pack_id: contextPack.context_pack_id,
|
|
653
|
+
context_pack_digest: contextPack.context_pack_digest,
|
|
654
|
+
context_pack: contextPack,
|
|
655
|
+
receipt_count: contextPack.receipt_count,
|
|
656
|
+
memory_count: contextPack.memory_count,
|
|
657
|
+
provider_native_memory_canonical: false,
|
|
658
|
+
receipts: contextPack.receipts,
|
|
659
|
+
claim_boundaries: { ...CROSS_MODEL_CLAIM_BOUNDARIES },
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const bundle = await persistState(bundlePath, vault);
|
|
664
|
+
const report = {
|
|
665
|
+
ok: true,
|
|
666
|
+
schema: 'enigma.cross_model_demo.v1',
|
|
667
|
+
command: 'enigma demo cross-model',
|
|
668
|
+
story: 'One local Enigma memory is packaged as public-safe context pack references and receipts for ChatGPT, Claude, Kimi, Cursor, and a local LLM. No provider is called.',
|
|
669
|
+
bundle_ref: demoBundleRef(bundleWasSupplied),
|
|
670
|
+
bundle_supplied: bundleWasSupplied,
|
|
671
|
+
bundle_created: bundleCreated,
|
|
672
|
+
demo_only_vault: !bundleWasSupplied,
|
|
673
|
+
memory_source: memorySource,
|
|
674
|
+
demo_memory_addr: demoMemoryAddr,
|
|
675
|
+
profile_count: profiles.length,
|
|
676
|
+
profiles,
|
|
677
|
+
memory_count: activeMemoryCount(vault),
|
|
678
|
+
receipt_count: Array.isArray(bundle.receipts) ? bundle.receipts.length : 0,
|
|
679
|
+
generated_receipt_count: (Array.isArray(bundle.receipts) ? bundle.receipts.length : 0) - receiptCountBeforeProfiles,
|
|
680
|
+
provider_credentials_required: false,
|
|
681
|
+
provider_native_memory_canonical: false,
|
|
682
|
+
out_written: outPath !== undefined,
|
|
683
|
+
claim_boundaries: { ...CROSS_MODEL_CLAIM_BOUNDARIES },
|
|
684
|
+
};
|
|
685
|
+
if (outPath !== undefined) await writeJson(outPath, report);
|
|
686
|
+
print(report, io);
|
|
687
|
+
return 0;
|
|
688
|
+
}
|
|
689
|
+
|
|
491
690
|
async function rememberCommand(flags, io) {
|
|
492
691
|
const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
|
|
493
692
|
const { vault, passport } = await loadState(bundlePath);
|
|
@@ -1074,6 +1273,7 @@ function usage() {
|
|
|
1074
1273
|
commands: [
|
|
1075
1274
|
'init',
|
|
1076
1275
|
'quickstart',
|
|
1276
|
+
'demo cross-model',
|
|
1077
1277
|
'doctor',
|
|
1078
1278
|
'install',
|
|
1079
1279
|
'connect <client>',
|
|
@@ -1127,6 +1327,12 @@ function usage() {
|
|
|
1127
1327
|
'--memory-text <text>': 'Inline demo memory text for non-private demos only.',
|
|
1128
1328
|
'--overwrite': 'Replace existing quickstart output files.',
|
|
1129
1329
|
},
|
|
1330
|
+
cross_model_demo_options: {
|
|
1331
|
+
'--bundle <path>': `Reuse a local Enigma bundle. If omitted, ${DEFAULT_CROSS_MODEL_DEMO_BUNDLE} is recreated as a demo-only local vault.`,
|
|
1332
|
+
'--memory-file <path>': 'Seed the demo from a local file without echoing plaintext. Alias: --text-file.',
|
|
1333
|
+
'--out <path>': 'Write the same public-safe JSON report to a local file.',
|
|
1334
|
+
'--limit <n>': 'Maximum active memories per generated profile context pack. Defaults to 1 for the same-memory demo story.',
|
|
1335
|
+
},
|
|
1130
1336
|
native_host: {
|
|
1131
1337
|
bin: 'enigma-native-host',
|
|
1132
1338
|
host_name: 'com.enigma.native_host',
|
|
@@ -1192,16 +1398,17 @@ export async function main(argv = process.argv.slice(2), io = { stdout: process.
|
|
|
1192
1398
|
print(usage(), io);
|
|
1193
1399
|
return 0;
|
|
1194
1400
|
}
|
|
1195
|
-
const twoPartCommands = ['boundary', 'mcp', 'mesh', 'enterprise', 'capsule', 'relay', 'gateway', 'connect', 'disconnect', 'import', 'native-host', 'meter', 'settlement'];
|
|
1401
|
+
const twoPartCommands = ['boundary', 'mcp', 'mesh', 'enterprise', 'capsule', 'relay', 'gateway', 'connect', 'disconnect', 'import', 'native-host', 'meter', 'settlement', 'demo'];
|
|
1196
1402
|
const flags = parseArgs(twoPartCommands.includes(command) ? argv.slice(2) : argv.slice(1));
|
|
1197
1403
|
const positionalFile = optionalPositional(argv[2]);
|
|
1198
|
-
if ((flags.has('help') || argv.includes('-h')) && (((command === 'relay' || command === 'gateway') && (subcommand === 'serve' || subcommand === 'demo')) || (command === 'native-host' && (subcommand === 'manifest' || subcommand === 'install-plan')))) {
|
|
1404
|
+
if ((flags.has('help') || argv.includes('-h')) && (((command === 'relay' || command === 'gateway') && (subcommand === 'serve' || subcommand === 'demo')) || (command === 'native-host' && (subcommand === 'manifest' || subcommand === 'install-plan')) || (command === 'demo' && subcommand === 'cross-model'))) {
|
|
1199
1405
|
print(usage(), io);
|
|
1200
1406
|
return 0;
|
|
1201
1407
|
}
|
|
1202
1408
|
try {
|
|
1203
1409
|
if (command === 'init') return await initCommand(flags, io);
|
|
1204
1410
|
if (command === 'quickstart') return await quickstartCommand(flags, io);
|
|
1411
|
+
if (command === 'demo' && subcommand === 'cross-model') return await crossModelDemoCommand(flags, io);
|
|
1205
1412
|
if (command === 'doctor') return await doctorCommand(flags, io);
|
|
1206
1413
|
if (command === 'install') return await installCommand(flags, io);
|
|
1207
1414
|
if (command === 'connect') return await connectCommand(subcommand, flags, io);
|