enigma-memory 0.1.0 → 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/README.md +367 -379
- 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 +347 -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 +22 -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 +410 -0
- package/templates/mcp-client-config.json +10 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { dirname, relative, resolve as resolvePath, sep } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
export const INSTALLER_ASSET_SCHEMA = 'enigma.installer_assets.v1';
|
|
8
|
+
export const INSTALLER_ASSET_PACKAGE = 'enigma-memory';
|
|
9
|
+
export const INSTALLER_ASSET_VERSION = '0.1.2';
|
|
10
|
+
export const INSTALLER_ASSET_GENERATED_AT = '1970-01-01T00:00:00.000Z';
|
|
11
|
+
|
|
12
|
+
const SCRIPT_PATH = fileURLToPath(import.meta.url);
|
|
13
|
+
const DEFAULT_OUTPUT_DIR = 'dist/installer-assets';
|
|
14
|
+
const SECRET_RE = /(?:bearer\s+[A-Za-z0-9._~+/=-]+|ghp_[A-Za-z0-9_]{16,}|npm_[A-Za-z0-9_-]{24,}|api[_-]?key\s*[=:]|password\s*[=:]|token\s*[=:])/iu;
|
|
15
|
+
const WINDOWS_ABSOLUTE_RE = /[A-Za-z]:\\(?:Users|tmp|Temp|Windows|ProgramData|Program Files)\\/u;
|
|
16
|
+
const POSIX_ABSOLUTE_RE = /(?:^|[\s"'`=:(])\/(?:Users|home|tmp|var|private|mnt|Volumes)\//u;
|
|
17
|
+
const CONTROL_RE = /[\0\r]/u;
|
|
18
|
+
|
|
19
|
+
function usage() {
|
|
20
|
+
return `Usage: node scripts/build-installer-assets.mjs --out-dir <dir> [--write|--dry-run]\n\nBuilds public-safe source installer assets for enigma-memory. Dry-run is the default and returns the deterministic manifest without writing files.\n`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readRequiredValue(argv, index, flag) {
|
|
24
|
+
const value = argv[index + 1];
|
|
25
|
+
if (value === undefined || value.startsWith('--')) throw new Error(`Missing value for ${flag}.`);
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function parseInstallerAssetArgs(argv = process.argv.slice(2)) {
|
|
30
|
+
const options = { outDir: DEFAULT_OUTPUT_DIR, dryRun: true, write: false };
|
|
31
|
+
let sawDryRun = false;
|
|
32
|
+
let sawWrite = false;
|
|
33
|
+
|
|
34
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
35
|
+
const arg = argv[index];
|
|
36
|
+
if (arg === '--help' || arg === '-h') {
|
|
37
|
+
options.help = true;
|
|
38
|
+
} else if (arg === '--out-dir') {
|
|
39
|
+
options.outDir = readRequiredValue(argv, index, arg);
|
|
40
|
+
index += 1;
|
|
41
|
+
} else if (arg === '--dry-run') {
|
|
42
|
+
sawDryRun = true;
|
|
43
|
+
options.dryRun = true;
|
|
44
|
+
options.write = false;
|
|
45
|
+
} else if (arg === '--write') {
|
|
46
|
+
sawWrite = true;
|
|
47
|
+
options.write = true;
|
|
48
|
+
options.dryRun = false;
|
|
49
|
+
} else {
|
|
50
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (sawDryRun && sawWrite) throw new Error('Use either --dry-run or --write, not both.');
|
|
55
|
+
rejectUnsafeOutputDir(options.outDir);
|
|
56
|
+
return options;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function rejectUnsafeOutputDir(value) {
|
|
60
|
+
const text = String(value ?? '');
|
|
61
|
+
if (text.length === 0) throw new Error('Output directory must not be empty.');
|
|
62
|
+
if (CONTROL_RE.test(text) || text.includes('\n')) throw new Error('Output directory contains an invalid control character.');
|
|
63
|
+
if (text.includes('..')) throw new Error('Output directory must not contain parent-directory traversal.');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function sha256(content) {
|
|
67
|
+
return createHash('sha256').update(content, 'utf8').digest('hex');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function normalizeAssetPath(path) {
|
|
71
|
+
return path.split('/').filter(Boolean).join('/');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function jsonStable(value) {
|
|
75
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function asset(path, content, mode = '0644') {
|
|
79
|
+
const normalizedPath = normalizeAssetPath(path);
|
|
80
|
+
assertPublicSafe(content, normalizedPath);
|
|
81
|
+
return Object.freeze({ path: normalizedPath, content, mode, bytes: Buffer.byteLength(content, 'utf8'), sha256: sha256(content) });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function compareAssetPath(left, right) {
|
|
85
|
+
if (left.path < right.path) return -1;
|
|
86
|
+
if (left.path > right.path) return 1;
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function assertPublicSafe(content, label = 'asset') {
|
|
91
|
+
const text = String(content ?? '');
|
|
92
|
+
if (SECRET_RE.test(text)) throw new Error(`${label} contains token-like or secret-like content.`);
|
|
93
|
+
if (WINDOWS_ABSOLUTE_RE.test(text) || POSIX_ABSOLUTE_RE.test(text)) throw new Error(`${label} contains a local absolute path.`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function windowsInstallerScript() {
|
|
97
|
+
return `# Enigma Memory source installer for Windows PowerShell.\n# Dry-run is the default; pass -Execute to mutate global npm state and create local quickstart files.\nparam(\n [switch]$Execute,\n [string]$Bundle = '.\\.enigma\\bundle.json'\n)\n$ErrorActionPreference = 'Stop'\n$PackageName = '${INSTALLER_ASSET_PACKAGE}'\n\nWrite-Output 'Enigma Memory Windows installer source asset'\nWrite-Output 'Default mode: dry-run. No native .exe, code-signing, tokens, or hosted credentials are included.'\nWrite-Output "Package: $PackageName"\nWrite-Output "Bundle: $Bundle"\n\n$steps = @(\n 'npm install -g enigma-memory',\n 'enigma quickstart --bundle <bundle> --overwrite',\n 'enigma doctor'\n)\n\nif (-not $Execute) {\n Write-Output 'Preview only. Re-run with -Execute after reviewing these steps.'\n $steps | ForEach-Object { Write-Output "DRY-RUN: $_" }\n exit 0\n}\n\nnpm install -g $PackageName\nenigma quickstart --bundle $Bundle --overwrite\nenigma doctor\n`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function linuxInstallerScript() {
|
|
101
|
+
return [
|
|
102
|
+
'#!/usr/bin/env sh',
|
|
103
|
+
'# Enigma Memory source installer for Linux.',
|
|
104
|
+
'# Dry-run is the default; pass --execute to mutate global npm state and create local quickstart files.',
|
|
105
|
+
'set -eu',
|
|
106
|
+
'',
|
|
107
|
+
'execute=0',
|
|
108
|
+
"bundle='./.enigma/bundle.json'",
|
|
109
|
+
'while [ "$#" -gt 0 ]; do',
|
|
110
|
+
' case "$1" in',
|
|
111
|
+
' --execute) execute=1 ;;',
|
|
112
|
+
' --dry-run) execute=0 ;;',
|
|
113
|
+
' --bundle) shift; bundle="${1:-}" ;;',
|
|
114
|
+
' --help|-h)',
|
|
115
|
+
" printf '%s\\n' 'Usage: ./install-linux.sh [--execute] [--bundle ./.enigma/bundle.json]'",
|
|
116
|
+
' exit 0',
|
|
117
|
+
' ;;',
|
|
118
|
+
' *)',
|
|
119
|
+
' printf \'%s\\n\' "Unknown argument: $1" >&2',
|
|
120
|
+
' exit 2',
|
|
121
|
+
' ;;',
|
|
122
|
+
' esac',
|
|
123
|
+
' shift',
|
|
124
|
+
'done',
|
|
125
|
+
'',
|
|
126
|
+
`package='${INSTALLER_ASSET_PACKAGE}'`,
|
|
127
|
+
"printf '%s\\n' 'Enigma Memory Linux installer source asset'",
|
|
128
|
+
"printf '%s\\n' 'Default mode: dry-run. No native package, signing key, tokens, or hosted credentials are included.'",
|
|
129
|
+
'printf \'%s\\n\' "Package: $package"',
|
|
130
|
+
'printf \'%s\\n\' "Bundle: $bundle"',
|
|
131
|
+
'',
|
|
132
|
+
'if [ "$execute" -ne 1 ]; then',
|
|
133
|
+
" printf '%s\\n' 'Preview only. Re-run with --execute after reviewing these steps.'",
|
|
134
|
+
" printf '%s\\n' 'DRY-RUN: npm install -g enigma-memory'",
|
|
135
|
+
" printf '%s\\n' 'DRY-RUN: enigma quickstart --bundle <bundle> --overwrite'",
|
|
136
|
+
" printf '%s\\n' 'DRY-RUN: enigma doctor'",
|
|
137
|
+
' exit 0',
|
|
138
|
+
'fi',
|
|
139
|
+
'',
|
|
140
|
+
'npm install -g "$package"',
|
|
141
|
+
'enigma quickstart --bundle "$bundle" --overwrite',
|
|
142
|
+
'enigma doctor',
|
|
143
|
+
'',
|
|
144
|
+
].join('\n');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function homebrewFormulaDraft() {
|
|
148
|
+
return `# Draft only. This formula is not submitted to a Homebrew tap by this generator.\n# Release engineering must replace the tarball URL and sha256 after a real source archive exists.\nclass EnigmaMemory < Formula\n desc "Provider-agnostic AI memory passport and offline-verifiable proof layer"\n homepage "https://github.com/Enigma-Memory/enigma-memory"\n url "https://example.invalid/enigma-memory-${INSTALLER_ASSET_VERSION}.tar.gz"\n sha256 "REPLACE_WITH_RELEASE_TARBALL_SHA256"\n license "Apache-2.0"\n\n depends_on "node"\n\n def install\n system "npm", "install", *Language::Node.local_npm_install_args\n bin.install_symlink libexec/"bin/enigma"\n bin.install_symlink libexec/"bin/enigma-verify"\n bin.install_symlink libexec/"bin/enigma-mcp"\n bin.install_symlink libexec/"bin/enigma-relay"\n bin.install_symlink libexec/"bin/enigma-gateway"\n bin.install_symlink libexec/"bin/enigma-native-host"\n end\n\n test do\n assert_match "enigma", shell_output("#{bin}/enigma --help")\n end\nend\n`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function macosPkgReadme() {
|
|
152
|
+
return `# macOS pkgbuild source manifest\n\nThis directory is an honest source plan for a future macOS package. It is not a signed .pkg and it does not claim notarization.\n\nCurrent supported path:\n\n\`\`\`sh\nnpm install -g ${INSTALLER_ASSET_PACKAGE}\nenigma quickstart --bundle ./.enigma/bundle.json --overwrite\nenigma doctor\n\`\`\`\n\nBlockers before shipping a native .pkg:\n\n- A reproducible package staging tree for the npm-installed command shims.\n- macOS pkgbuild/productbuild tooling on a macOS release runner.\n- Developer ID Installer certificate, signing identity selection, and notarization workflow.\n- Human review that package scripts do not print local absolute paths, credentials, account identifiers, raw memory, or provider transcripts.\n\nThe generated JSON manifest in this directory records those blockers explicitly.\n`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function macosPkgManifest() {
|
|
156
|
+
return jsonStable({
|
|
157
|
+
schema: 'enigma.macos_pkgbuild_manifest.v1',
|
|
158
|
+
package: INSTALLER_ASSET_PACKAGE,
|
|
159
|
+
version: INSTALLER_ASSET_VERSION,
|
|
160
|
+
generated_native_pkg: false,
|
|
161
|
+
source_only: true,
|
|
162
|
+
package_id: 'ai.enigma.memory',
|
|
163
|
+
install_prefix: '<homebrew-or-npm-managed-prefix>',
|
|
164
|
+
commands: [
|
|
165
|
+
['npm', 'install', '-g', INSTALLER_ASSET_PACKAGE],
|
|
166
|
+
['enigma', 'quickstart', '--bundle', '<bundle-path>', '--overwrite'],
|
|
167
|
+
['enigma', 'doctor'],
|
|
168
|
+
],
|
|
169
|
+
blockers: [
|
|
170
|
+
{ code: 'MACOS_PKGBUILD_TOOLING_REQUIRED', message: 'pkgbuild/productbuild must run on a macOS release runner.' },
|
|
171
|
+
{ code: 'MACOS_SIGNING_REQUIRED', message: 'A Developer ID Installer certificate and notarization workflow are required before distributing a .pkg.' },
|
|
172
|
+
{ code: 'PKG_STAGING_TREE_REQUIRED', message: 'Release engineering must define the staged file layout for command shims and package resources.' },
|
|
173
|
+
],
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function nativeInstallerBlockers(tooling = {}) {
|
|
178
|
+
const windows = [];
|
|
179
|
+
if (tooling.windowsExeBuilderAvailable !== true) windows.push({ code: 'WINDOWS_EXE_BUILDER_REQUIRED', message: 'No Windows .exe builder is configured by this source generator.' });
|
|
180
|
+
if (tooling.windowsCodeSigningAvailable !== true) windows.push({ code: 'WINDOWS_CODE_SIGNING_REQUIRED', message: 'Signed .exe distribution requires a Windows code-signing certificate and signing workflow.' });
|
|
181
|
+
|
|
182
|
+
const macos = [];
|
|
183
|
+
if (tooling.pkgbuildAvailable !== true || tooling.productbuildAvailable !== true) macos.push({ code: 'MACOS_PKGBUILD_TOOLING_REQUIRED', message: 'pkgbuild/productbuild are required on a macOS release runner.' });
|
|
184
|
+
if (tooling.macosSigningIdentityAvailable !== true) macos.push({ code: 'MACOS_SIGNING_REQUIRED', message: 'A Developer ID Installer certificate and notarization workflow are required before distributing a .pkg.' });
|
|
185
|
+
|
|
186
|
+
return Object.freeze({
|
|
187
|
+
windows_exe: Object.freeze({ generated: false, available: windows.length === 0, blockers: Object.freeze(windows) }),
|
|
188
|
+
macos_pkg: Object.freeze({ generated: false, available: macos.length === 0, blockers: Object.freeze(macos) }),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function buildInstallerAssets(options = {}, runtime = {}) {
|
|
193
|
+
const generatedAt = runtime.generatedAt ?? INSTALLER_ASSET_GENERATED_AT;
|
|
194
|
+
const dryRun = options.write === true || options.dryRun === false ? false : true;
|
|
195
|
+
const assets = [
|
|
196
|
+
asset('install-windows.ps1', windowsInstallerScript()),
|
|
197
|
+
asset('install-linux.sh', linuxInstallerScript(), '0755'),
|
|
198
|
+
asset('homebrew/enigma-memory.rb', homebrewFormulaDraft()),
|
|
199
|
+
asset('macos-pkgbuild/README.md', macosPkgReadme()),
|
|
200
|
+
asset('macos-pkgbuild/manifest.json', macosPkgManifest()),
|
|
201
|
+
].sort(compareAssetPath);
|
|
202
|
+
|
|
203
|
+
const files = assets.map(({ path, mode, bytes, sha256: digest }) => ({ path, mode, bytes, sha256: digest }));
|
|
204
|
+
const manifest = {
|
|
205
|
+
schema: INSTALLER_ASSET_SCHEMA,
|
|
206
|
+
package: INSTALLER_ASSET_PACKAGE,
|
|
207
|
+
version: INSTALLER_ASSET_VERSION,
|
|
208
|
+
generated_at: generatedAt,
|
|
209
|
+
mode: dryRun ? 'dry-run' : 'write',
|
|
210
|
+
dry_run: dryRun,
|
|
211
|
+
public_safe: true,
|
|
212
|
+
output_dir: '<requested-output-dir>',
|
|
213
|
+
npm_install_available_now: true,
|
|
214
|
+
source_assets_only: true,
|
|
215
|
+
generated_native_installers: false,
|
|
216
|
+
files,
|
|
217
|
+
native_installers: nativeInstallerBlockers(runtime.tooling),
|
|
218
|
+
safety: {
|
|
219
|
+
embeds_tokens: false,
|
|
220
|
+
embeds_local_absolute_paths: false,
|
|
221
|
+
embeds_raw_memory: false,
|
|
222
|
+
default_dry_run: true,
|
|
223
|
+
requires_write_flag_for_filesystem_mutation: true,
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
const manifestContent = jsonStable(manifest);
|
|
227
|
+
assertPublicSafe(manifestContent, 'installer-assets-manifest.json');
|
|
228
|
+
const manifestAsset = asset('installer-assets-manifest.json', manifestContent);
|
|
229
|
+
|
|
230
|
+
return Object.freeze({
|
|
231
|
+
...manifest,
|
|
232
|
+
files: Object.freeze(files),
|
|
233
|
+
assets: Object.freeze([...assets, manifestAsset].sort(compareAssetPath)),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function writeAssets(outDir, assets) {
|
|
238
|
+
const root = resolvePath(outDir);
|
|
239
|
+
for (const item of assets) {
|
|
240
|
+
const target = resolvePath(root, ...item.path.split('/'));
|
|
241
|
+
const rel = relative(root, target);
|
|
242
|
+
if (rel.startsWith('..') || rel === '' || rel.split(sep).includes('..')) throw new Error(`Refusing to write outside output directory: ${item.path}`);
|
|
243
|
+
await mkdir(dirname(target), { recursive: true });
|
|
244
|
+
await writeFile(target, item.content, { encoding: 'utf8', mode: Number.parseInt(item.mode, 8) });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export async function runBuildInstallerAssets(argv = process.argv.slice(2), io = {}) {
|
|
249
|
+
const stdout = io.stdout ?? process.stdout;
|
|
250
|
+
const stderr = io.stderr ?? process.stderr;
|
|
251
|
+
try {
|
|
252
|
+
const options = parseInstallerAssetArgs(argv);
|
|
253
|
+
if (options.help) {
|
|
254
|
+
stdout.write(usage());
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
const result = buildInstallerAssets(options, io.runtime ?? {});
|
|
258
|
+
if (!result.dry_run) await writeAssets(options.outDir, result.assets);
|
|
259
|
+
const publicResult = { ...result };
|
|
260
|
+
delete publicResult.assets;
|
|
261
|
+
stdout.write(`${jsonStable(publicResult)}`);
|
|
262
|
+
return 0;
|
|
263
|
+
} catch (error) {
|
|
264
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
265
|
+
stderr.write(`${message}\n`);
|
|
266
|
+
return 2;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (process.argv[1] === SCRIPT_PATH) {
|
|
271
|
+
const code = await runBuildInstallerAssets();
|
|
272
|
+
process.exitCode = code;
|
|
273
|
+
}
|